mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
import type { PlayerStore } from '@videojs/core/dom';
|
|
import type { ReactiveController, ReactiveControllerHost } from '@videojs/element';
|
|
import { ContextConsumer } from '@videojs/element/context';
|
|
import type { InferStoreState, Selector } from '@videojs/store';
|
|
import { StoreController } from '@videojs/store/html';
|
|
|
|
import type { PlayerContext } from './context';
|
|
|
|
export type PlayerControllerHost = ReactiveControllerHost & HTMLElement;
|
|
|
|
/**
|
|
* Reactive controller for accessing player store state.
|
|
*
|
|
* Without selector: Returns the store, does NOT subscribe to changes.
|
|
* With selector: Returns selected state, subscribes with shallowEqual comparison.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // Store access (no subscription)
|
|
* class Controls extends MediaElement {
|
|
* #player = new PlayerController(this, playerContext);
|
|
*
|
|
* handleClick() {
|
|
* this.#player.value.setVolume(0.5);
|
|
* }
|
|
* }
|
|
*
|
|
* // Selector-based subscription
|
|
* class PlayButton extends MediaElement {
|
|
* #playback = new PlayerController(this, playerContext, selectPlayback);
|
|
* }
|
|
* ```
|
|
*/
|
|
export class PlayerController<Store extends PlayerStore, Result = Store> implements ReactiveController {
|
|
readonly #host: PlayerControllerHost;
|
|
readonly #selector: Selector<InferStoreState<Store>, Result> | undefined;
|
|
|
|
#consumer: ContextConsumer<PlayerContext<Store>, PlayerControllerHost>;
|
|
#store: StoreController<Store, Result> | null = null;
|
|
|
|
/**
|
|
* @label Without Selector
|
|
* @param host - The host element that owns this controller.
|
|
* @param context - Player context to resolve the store from.
|
|
*/
|
|
constructor(host: PlayerControllerHost, context: PlayerContext<Store>);
|
|
/**
|
|
* @label With Selector
|
|
* @param host - The host element that owns this controller.
|
|
* @param context - Player context to resolve the store from.
|
|
* @param selector - Derives a value from the player store state.
|
|
*/
|
|
constructor(
|
|
host: PlayerControllerHost,
|
|
context: PlayerContext<Store>,
|
|
selector: Selector<InferStoreState<Store>, Result>
|
|
);
|
|
constructor(
|
|
host: PlayerControllerHost,
|
|
context: PlayerContext<Store>,
|
|
selector?: Selector<InferStoreState<Store>, Result>
|
|
) {
|
|
this.#host = host;
|
|
this.#selector = selector;
|
|
|
|
this.#consumer = new ContextConsumer(host, {
|
|
context,
|
|
callback: (ctx) => this.#connect(ctx),
|
|
subscribe: true,
|
|
});
|
|
|
|
host.addController(this);
|
|
}
|
|
|
|
get value(): Result | undefined {
|
|
const store = this.#consumer.value;
|
|
if (!store) return undefined;
|
|
|
|
// Without selector: return store directly
|
|
if (!this.#selector) return store as unknown as Result;
|
|
|
|
// With selector: use StoreController
|
|
return this.#store?.value;
|
|
}
|
|
|
|
get displayName(): string | undefined {
|
|
return this.#selector?.displayName;
|
|
}
|
|
|
|
hostConnected(): void {
|
|
const store = this.#consumer.value;
|
|
if (store) this.#connect(store);
|
|
}
|
|
|
|
hostDisconnected(): void {
|
|
this.#store = null;
|
|
}
|
|
|
|
#connect(store: Store): void {
|
|
if (!this.#store && this.#selector) {
|
|
this.#store = new StoreController(this.#host, store, this.#selector);
|
|
}
|
|
}
|
|
}
|
|
|
|
export namespace PlayerController {
|
|
export type Host = PlayerControllerHost;
|
|
|
|
export type Constructor<Store extends PlayerStore = PlayerStore, Result = Store> = typeof PlayerController<
|
|
Store,
|
|
Result
|
|
>;
|
|
}
|