Skip to content

Integrate DarkModeToggle with component-lifecycle Library #92

@palcarazm

Description

@palcarazm

Short Description of the Feature

Refactor DarkModeToggle class to extend Component<P> from the component-lifecycle library, delegating lifecycle management (initialization, attachment, disposal, destruction) to the library's state machine. This replaces the manual _destroyed flag with a deterministic, observable lifecycle while preserving the existing public API surface and event system.

Current behavior: DarkModeToggle manages lifecycle via a boolean flag (_destroyed) and explicit destroy() method. Construction immediately initializes and attaches the component.

Target behavior: DarkModeToggle extends Component<"darkmode"> where the constructor calls super(element), then internally invokes this.init() and this.attach(). Lifecycle hooks (doInit(), doAttach(), doDispose(), doDestroy()) encapsulate setup/teardown logic.

Expected Benefits

  • Standardized lifecycle: Deterministic state machine (idleinitializedattacheddisposeddestroyed) with validation
  • Observability: Automatic emission of typed lifecycle events (darkmode:initialized, darkmode:attached, darkmode:disposed, darkmode:destroyed) without manual dispatch
  • Resource management: Clear separation of concerns via hooks ensures proper cleanup and prevents memory leaks
  • Introspection: Built-in state query methods (isAttached(), isDestroyed(), etc.) replace manual flag checks
  • Zero regression: Existing public API (toggle(), light(), dark(), setStorageType(), destroy()) unchanged; existing darkmode:change events continue to work alongside new lifecycle events

Acceptance Criteria

  • DarkModeToggle extends Component<"darkmode"> from component-lifecycle
  • Static readonly PREFIX property set to "darkmode"
  • Constructor calls super(element) as first statement, then this.init() and this.attach() internally
  • doInit() hook contains: StorageManager creation, applyPreferredScheme() (state restoration), and initial state calculation
  • doAttach() hook contains: DomManager creation, EventManager creation, setupCrossInstanceSync() (global event listener registration), and initial syncState()
  • doDispose() hook removes global event listeners (handleExternalThemeChange)
  • doDestroy() hook contains current destroy() logic: dom.destroy(), property deletion, and any final cleanup
  • Manual _destroyed flag and ensureNotDestroyed() method are removed; replaced with isDestroyed() from base class
  • All existing public methods (toggle, light, dark, setStorageType, destroy) continue to work without signature changes
  • Existing darkmode:change events are still dispatched via EventManager.dispatch() in addition to new lifecycle events
  • Auto-initialization via [data-plugin="bs-darkmode-toggle"] continues to work (constructor handles init()/attach() internally)
  • Unit tests pass with no regressions (existing test suite validates API surface)

Documentation

Event Emission Flow

Lifecycle events (automatic from base class):

  • darkmode:initialized → emitted after doInit() completes
  • darkmode:attached → emitted after doAttach() completes
  • darkmode:disposed → emitted after doDispose() completes
  • darkmode:destroyed → emitted after doDestroy() completes

Domain events (preserved existing behavior):

  • darkmode:change → still dispatched via EventManager.dispatch() when theme changes (triggered by user interaction or programmatic methods)

Backward compatibility: Existing listeners for "change" (legacy) and "darkmode:change" continue to work unchanged.

See component-lifecycle events doc

State Query Methods (New Capabilities)

These methods from Component become available without additional implementation:

Method Replaces
isAttached() Manual state tracking
isDestroyed() _destroyed flag + ensureNotDestroyed()
isDisposed() New capability
isInitialized() New capability
isIdle() New capability
state (getter) Returns current LifecycleState enum

See component-lifecycle lifecycle doc

Public API Compatibility Matrix

Method Current behavior New behavior Regression risk
toggle(silent?) Changes state, syncs DOM, triggers events Same (delegates to state.do()) None
light(silent?) Same as above Same None
dark(silent?) Same as above Same None
setStorageType(type) Changes storage provider, persists Same None
destroy() Cleans up, sets _destroyed=true Calls super.destroy()doDestroy() None (idempotent)

component-lifecycle API

See component-lifecycle API doc

Additional Comments

Risks & Mitigations

Risk Mitigation
Circular dependency: Calling this.init() and this.attach() inside constructor before this is fully initialized component-lifecycle base class is designed for this pattern; hooks are called after construction completes
Double initialization: Existing code might call init()/attach() manually if exposed component-lifecycle check is the transition can be performed, init(), attach(), dispose(), destroy() are idempotent
Event duplication: Both lifecycle and domain events could cause duplicate handling Lifecycle events are distinct names (darkmode:attached vs darkmode:change); no overlap
Global listener lifecycle: handleExternalThemeChange registered in doAttach(), removed in doDispose() Supports reattachment pattern (dispose → attach works correctly)
Test suite updates: Existing mocks may assume _destroyed flag exists Update tests to use isDestroyed() or remove flag assertions; no functional changes required

Dependencies

  • Required package: component-lifecycle (version as specified in user's links)
  • No peer dependency changes: Bootstrap 5, Bootstrap Toggle, etc., remain unchanged

Out of Scope

  • Changing the public API surface (methods, parameters, return types)
  • Modifying existing darkmode:change event behavior or payload
  • Migrating existing instances or providing codemods
  • Changing storage, DOM management, or state reduction logic beyond lifecycle delegation

Feature Request Checklist

  • Confirm that you agree to follow the project's code of conduct.
  • Confirm that you have reviewed open and rejected feature requests to ensure novelty.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions