--- status: implemented date: 2026-03-05 --- # Destroy Lifecycle: DestroyMixin ## Decision The deferred destruction lifecycle is implemented as a **composable mixin** (`DestroyMixin`) exported from `@videojs/element`, rather than being added directly to `ReactiveElement`. `ReactiveElement` stays structurally compatible with Lit's `ReactiveElement`. ## Architecture ``` @videojs/utils/dom → deferFrames() composable utility @videojs/element → DestroyMixin(HTMLElement) — generic, works with any base @videojs/html → MediaElement = DestroyMixin(ReactiveElement) + hostDestroyed() @videojs/core → HlsCustomMedia = DestroyMixin(HTMLElement) via CustomMediaMixin ``` ### DestroyMixin Adds `destroyed`, `destroy()`, `destroyCallback()`, deferred 2-rAF scheduling on disconnect, cancel on reconnect, and `keep-alive` attribute support. Works with any `HTMLElement` subclass. ### MediaElement Composes `DestroyMixin(ReactiveElement)` and bridges `destroyCallback()` to `hostDestroyed()` on reactive controllers. Also guards `performUpdate()` when destroyed. ### HlsCustomMedia Uses `DestroyMixin(HTMLElement)` as the base for `CustomMediaMixin`. The `DelegateMedia` class (from `DelegateMixin`) overrides `destroyCallback()` to call `this.#delegate.destroy?.()`, which destroys the HLS engine. ## Why a Mixin - `ReactiveElement` stays Lit-compatible (no added public surface) - Works with `HTMLElement` directly (no `ReactiveElement` required for custom media elements) - Composable — any element can opt in ## Alternatives Considered - **Put everything on `ReactiveElement`** — Simpler (direct `#controllers` access), but breaks Lit structural compat. Rejected. - **Separate `MediaElement` class without mixin** — Doesn't compose with `CustomMediaMixin(HTMLElement)` for custom media elements. Rejected.