diff --git a/.claude/plans/store-bindings.md b/.claude/plans/store-bindings.md index 098f0e01..e4ffeaa7 100644 --- a/.claude/plans/store-bindings.md +++ b/.claude/plans/store-bindings.md @@ -75,165 +75,20 @@ Types live next to implementations (no separate `types.ts`). --- -## Phase 2: Lit Bindings (`@videojs/store/lit`) +## Phase 2: Lit Bindings (`@videojs/store/lit`) [DONE] -All DOM/Lit bindings live together since they all depend on `@lit/context`. +> Refer to [PR #289](https://github.com/videojs/v10/pull/289) for implementation details. -### 2.0 @lit/context Research +Basic Lit bindings for the store: -Key findings from analyzing `@lit/context@1.1.6`: +- Controllers: `SelectorController`, `RequestController`, `TasksController` for reactive state +- Mixins: `StoreProviderMixin`, `StoreAttachMixin`, `StoreMixin` (combined) for custom elements +- `createStore()` factory returning typed mixins, context, and `create()` function +- Auto-attach media elements via slot change observation +- Proper cleanup on disconnect (slot listeners, subscriptions) +- Sync controller values on reconnect to avoid stale state -**Dynamic Value Updates:** - -- `ContextProvider.setValue(newValue, force?)` notifies all subscribed consumers -- Uses `Object.is()` for equality - swapping store objects triggers updates automatically -- `force = true` needed only for in-place mutations (same reference) - -**Subscription Model:** - -- Consumers must opt-in: `subscribe: true` in `@consume()` or `ContextConsumer` -- Without subscription, consumers only receive initial value -- Provider stores callbacks in `Map` -- `updateObservers()` iterates all callbacks on value change - -**Store Swapping Pattern (validated):** - -```typescript -class MySkin extends HTMLElement { - #provider = new ContextProvider(this, { context: storeContext }); - - set store(newStore: Store) { - this.#provider.setValue(newStore); // Notifies all subscribers - } -} -``` - -### 2.1 `createStore` - -**File:** `packages/store/src/lit/create-store.ts` - -```typescript -import type { Context } from '@lit/context'; -import type { ReactiveControllerHost } from '@lit/reactive-element'; -import type { AnySlice, InferSliceTarget, StoreConfig } from '../core'; - -import { createContext } from '@lit/context'; - -export interface CreateStoreConfig extends StoreConfig< - InferSliceTarget, - Slices -> {} - -export interface CreateStoreResult { - /** Combined mixin: provides store via context AND auto-attaches slotted media */ - StoreMixin: >(Base: T) => T; - /** Mixin that provides store via context (no auto-attach) */ - StoreProviderMixin: >(Base: T) => T; - /** Mixin that auto-attaches slotted media elements (requires store from context) */ - StoreAttachMixin: >(Base: T) => T; - /** Context for consuming store in controllers */ - context: Context, Slices>>; - /** Creates a store instance for imperative access */ - create: () => Store, Slices>; -} - -export function createStore(config: CreateStoreConfig): CreateStoreResult; -``` - -**Implementation details:** - -- Uses `@lit/context` for W3C Context Protocol -- Context key auto-generated per `createStore()` call (unique Symbol) -- `StoreMixin`: Combined mixin, equivalent to `StoreAttachMixin(StoreProviderMixin(Base))` -- `StoreProviderMixin`: Mixin that: - - Creates store instance - - Uses `ContextProvider` internally - - Exposes `store` setter that calls `provider.setValue(newStore)` -- `StoreAttachMixin`: Mixin that: - - Consumes store from context - - Observes slotted elements for `