diff --git a/packages/html/src/index.ts b/packages/html/src/index.ts
index fc94a957..57d4fda9 100644
--- a/packages/html/src/index.ts
+++ b/packages/html/src/index.ts
@@ -4,6 +4,7 @@ export * from '@videojs/core/dom';
// Store
export type { Comparator, Selector } from '@videojs/store';
export { createSelector, shallowEqual } from '@videojs/store';
+
// Player
export * from './player/context';
export * from './player/create-player';
@@ -11,8 +12,7 @@ export * from './player/player-controller';
export * from './player/player-mixin';
export * from './store/container-mixin';
export * from './store/provider-mixin';
-// Store bindings
export * from './store/types';
// Primitives
-export { MediaElement } from './ui/media-element';
+export * from './ui/media-element';
diff --git a/packages/html/src/player/create-player.ts b/packages/html/src/player/create-player.ts
index c6cba892..9176c726 100644
--- a/packages/html/src/player/create-player.ts
+++ b/packages/html/src/player/create-player.ts
@@ -3,6 +3,8 @@ import { combine, createStore } from '@videojs/store';
import { type ContainerMixin, createContainerMixin } from '../store/container-mixin';
import { createProviderMixin, type ProviderMixin } from '../store/provider-mixin';
+import type { PlayerElementConstructor } from '../store/types';
+import { MediaElement } from '../ui/media-element';
import { type PlayerContext, playerContext } from './context';
import { PlayerController } from './player-controller';
import { createPlayerMixin, type PlayerMixin } from './player-mixin';
@@ -21,6 +23,9 @@ export interface CreatePlayerResult {
/** Player controller bound to this player's context. */
PlayerController: PlayerController.Constructor;
+ /** Pre-composed player element ready for customElements.define(). */
+ PlayerElement: PlayerElementConstructor;
+
/** Mixin for a complete player element (provider + container). */
PlayerMixin: PlayerMixin;
@@ -37,28 +42,21 @@ export interface CreatePlayerResult {
* @example
* ```ts
* import { features } from '@videojs/core/dom';
+ * import { createPlayer, MediaElement } from '@videojs/html';
*
- * const {
- * context,
- * create,
- * PlayerController,
- * PlayerMixin,
- * ProviderMixin,
- * ContainerMixin,
- * } = createPlayer({
+ * const { PlayerElement, PlayerController, context } = createPlayer({
* features: [...features.video],
* });
*
- * // Complete player element
- * class VideoPlayer extends PlayerMixin(ReactiveElement) {}
+ * // Simple: register pre-composed PlayerElement
+ * customElements.define('video-player', PlayerElement);
*
- * // Or separate provider/container
- * class MyPlayer extends ProviderMixin(ReactiveElement) {}
- * class MyContainer extends ContainerMixin(ReactiveElement) {}
+ * // Custom: extend with PlayerMixin
+ * class MyPlayer extends PlayerMixin(MediaElement) {}
*
- * // Control element
- * class PlayButton extends ReactiveElement {
- * #playback = new PlayerController(this, selectPlayback);
+ * // Control element with selector
+ * class PlayButton extends MediaElement {
+ * #playback = new PlayerController(this, context, selectPlayback);
* }
* ```
*/
@@ -76,14 +74,15 @@ export function createPlayer(
const ctx = playerContext as PlayerContext;
const PlayerMixin = createPlayerMixin(ctx, create);
+ const PlayerElement = PlayerMixin(MediaElement);
const ProviderMixin = createProviderMixin(ctx, create);
const ContainerMixin = createContainerMixin(ctx);
- const BoundPlayerController = PlayerController as unknown as PlayerController.Constructor;
return {
context: ctx,
create,
- PlayerController: BoundPlayerController,
+ PlayerController,
+ PlayerElement,
PlayerMixin,
ProviderMixin,
ContainerMixin,
diff --git a/packages/html/src/player/player-controller.ts b/packages/html/src/player/player-controller.ts
index a2fdc42f..1f75f376 100644
--- a/packages/html/src/player/player-controller.ts
+++ b/packages/html/src/player/player-controller.ts
@@ -3,7 +3,6 @@ import type { ReactiveController, ReactiveControllerHost } from '@lit/reactive-e
import type { PlayerStore } from '@videojs/core/dom';
import type { InferStoreState, Selector } from '@videojs/store';
import { StoreController } from '@videojs/store/lit';
-import type { Constructor as Ctor } from '@videojs/utils/types';
import type { PlayerContext, PlayerContextValue } from './context';
@@ -18,7 +17,7 @@ export type PlayerControllerHost = ReactiveControllerHost & HTMLElement;
* @example
* ```ts
* // Store access (no subscription)
- * class Controls extends ReactiveElement {
+ * class Controls extends MediaElement {
* #player = new PlayerController(this, playerContext);
*
* handleClick() {
@@ -27,7 +26,7 @@ export type PlayerControllerHost = ReactiveControllerHost & HTMLElement;
* }
*
* // Selector-based subscription
- * class PlayButton extends ReactiveElement {
+ * class PlayButton extends MediaElement {
* #playback = new PlayerController(this, playerContext, selectPlayback);
* }
* ```
@@ -95,7 +94,8 @@ export class PlayerController impleme
export namespace PlayerController {
export type Host = PlayerControllerHost;
- export type Constructor = Ctor<
- typeof PlayerController
+ export type Constructor = typeof PlayerController<
+ Store,
+ Result
>;
}
diff --git a/packages/html/src/player/player-mixin.ts b/packages/html/src/player/player-mixin.ts
index a9408885..dbd609b6 100644
--- a/packages/html/src/player/player-mixin.ts
+++ b/packages/html/src/player/player-mixin.ts
@@ -1,19 +1,16 @@
-import type { ReactiveElement } from '@lit/reactive-element';
import type { PlayerStore } from '@videojs/core/dom';
-import type { Constructor } from '@videojs/utils/types';
-
+import type { MediaElementConstructor } from '@/ui/media-element';
import { createContainerMixin } from '../store/container-mixin';
import { createProviderMixin } from '../store/provider-mixin';
-import type { PlayerProvider } from '../store/types';
+import type { PlayerProviderConstructor } from '../store/types';
import type { PlayerContext } from './context';
-export interface PlayerElement extends PlayerProvider {}
+type Result = Class &
+ PlayerProviderConstructor;
-type Base = Constructor;
-
-type Result = Class & PlayerElement;
-
-export type PlayerMixin = (BaseClass: Class) => Result;
+export type PlayerMixin = (
+ BaseClass: Class
+) => Result;
/**
* Creates a mixin that combines provider and container functionality.
@@ -27,7 +24,7 @@ export function createPlayerMixin(
const ProviderMixin = createProviderMixin(context, factory);
const ContainerMixin = createContainerMixin(context);
- return (BaseClass: Class) => {
+ return (BaseClass: Class) => {
return ContainerMixin(ProviderMixin(BaseClass)) as unknown as Result;
};
}
diff --git a/packages/html/src/player/tests/create-player.test.ts b/packages/html/src/player/tests/create-player.test.ts
new file mode 100644
index 00000000..2acb64fb
--- /dev/null
+++ b/packages/html/src/player/tests/create-player.test.ts
@@ -0,0 +1,34 @@
+import { features } from '@videojs/core/dom';
+import { describe, expect, it } from 'vitest';
+
+import { createPlayer } from '../create-player';
+
+describe('createPlayer', () => {
+ it('returns expected exports', () => {
+ const result = createPlayer({ features: [...features.video] });
+
+ expect(result.context).toBeDefined();
+ expect(result.create).toBeInstanceOf(Function);
+ expect(result.PlayerController).toBeDefined();
+ expect(result.PlayerElement).toBeDefined();
+ expect(result.PlayerMixin).toBeInstanceOf(Function);
+ expect(result.ProviderMixin).toBeInstanceOf(Function);
+ expect(result.ContainerMixin).toBeInstanceOf(Function);
+ });
+
+ it('create() returns a store instance', () => {
+ const { create } = createPlayer({ features: [...features.video] });
+ const store = create();
+
+ expect(store.attach).toBeInstanceOf(Function);
+ expect(store.subscribe).toBeInstanceOf(Function);
+ expect(store.destroy).toBeInstanceOf(Function);
+ });
+
+ it('PlayerElement is a valid custom element class', () => {
+ const { PlayerElement } = createPlayer({ features: [...features.video] });
+
+ expect(typeof PlayerElement).toBe('function');
+ expect(PlayerElement.prototype).toBeDefined();
+ });
+});
diff --git a/packages/html/src/store/container-mixin.ts b/packages/html/src/store/container-mixin.ts
index 0592a37e..d28336d5 100644
--- a/packages/html/src/store/container-mixin.ts
+++ b/packages/html/src/store/container-mixin.ts
@@ -1,22 +1,18 @@
import { ContextConsumer } from '@lit/context';
-import type { ReactiveElement } from '@lit/reactive-element';
import type { MediaContainer, PlayerStore, PlayerTarget } from '@videojs/core/dom';
import { listen, querySlot } from '@videojs/utils/dom';
import { Disposer } from '@videojs/utils/events';
import { noop } from '@videojs/utils/function';
-import type { Constructor } from '@videojs/utils/types';
-
+import type { MediaElementConstructor } from '@/ui/media-element';
import type { PlayerContext } from '../player/context';
-import type { PlayerConsumer } from './types';
+import type { PlayerConsumer, PlayerConsumerConstructor } from './types';
-type Base = Constructor;
-
-type Result = Class & Constructor>;
-
-export type ContainerMixin = (BaseClass: Class) => Result;
+export type ContainerMixin = (
+ BaseClass: Class
+) => Class & PlayerConsumerConstructor;
export function createContainerMixin(context: PlayerContext): ContainerMixin {
- return (BaseClass: Class) => {
+ return (BaseClass: Class) => {
class PlayerContainerElement extends BaseClass implements PlayerConsumer, MediaContainer {
#detach = noop;
#disposer = new Disposer();
diff --git a/packages/html/src/store/provider-mixin.ts b/packages/html/src/store/provider-mixin.ts
index 9ded5742..b7d4347d 100644
--- a/packages/html/src/store/provider-mixin.ts
+++ b/packages/html/src/store/provider-mixin.ts
@@ -1,23 +1,19 @@
import { ContextProvider } from '@lit/context';
-import type { ReactiveElement } from '@lit/reactive-element';
import type { Media, PlayerStore } from '@videojs/core/dom';
import { isNull } from '@videojs/utils/predicate';
-import type { Constructor } from '@videojs/utils/types';
-
+import type { MediaElementConstructor } from '@/ui/media-element';
import type { PlayerContext } from '../player/context';
-import type { PlayerProvider } from './types';
+import type { PlayerProvider, PlayerProviderConstructor } from './types';
-type Base = Constructor;
-
-type Result = Class & Constructor>;
-
-export type ProviderMixin = (BaseClass: Class) => Result;
+export type ProviderMixin = (
+ BaseClass: Class
+) => Class & PlayerProviderConstructor;
export function createProviderMixin(
context: PlayerContext,
factory: () => Store
): ProviderMixin {
- return (BaseClass: Class) => {
+ return (BaseClass: Class) => {
class PlayerProviderElement extends BaseClass implements PlayerProvider {
#store: Store | null = null;
#media: Media | null = null;
diff --git a/packages/html/src/store/types.ts b/packages/html/src/store/types.ts
index d155f39f..59c59fb5 100644
--- a/packages/html/src/store/types.ts
+++ b/packages/html/src/store/types.ts
@@ -1,10 +1,32 @@
import type { Media, PlayerStore } from '@videojs/core/dom';
+import type { Constructor } from '@videojs/utils/types';
+import type { MediaElement } from '@/ui/media-element';
-export interface PlayerProvider {
+// ----------------------------------------
+// PlayerElement
+// ----------------------------------------
+
+export interface PlayerElement extends MediaElement, PlayerProvider {}
+
+export interface PlayerElementConstructor extends Constructor> {}
+
+// ----------------------------------------
+// PlayerProvider
+// ----------------------------------------
+
+export interface PlayerProvider extends MediaElement {
readonly store: Store;
media: Media | null;
}
-export interface PlayerConsumer {
+export interface PlayerProviderConstructor extends Constructor> {}
+
+// ----------------------------------------
+// PlayerConsumer
+// ----------------------------------------
+
+export interface PlayerConsumer extends MediaElement {
readonly store: Store | null;
}
+
+export interface PlayerConsumerConstructor extends Constructor> {}
diff --git a/packages/html/src/ui/media-element.ts b/packages/html/src/ui/media-element.ts
index 866862d9..9f2763ed 100644
--- a/packages/html/src/ui/media-element.ts
+++ b/packages/html/src/ui/media-element.ts
@@ -1,3 +1,6 @@
import { ReactiveElement } from '@lit/reactive-element';
+import type { Constructor } from '@videojs/utils/types';
export class MediaElement extends ReactiveElement {}
+
+export interface MediaElementConstructor extends Constructor {}