feat(site): add util reference pipeline (#537)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Darius Cepulis
2026-02-24 15:34:34 -06:00
committed by GitHub
co-authored by Claude Opus 4.6
parent c11395ece1
commit 78112fbefd
143 changed files with 7031 additions and 481 deletions
@@ -5,7 +5,7 @@ frameworkTitle:
description: Loading indicator that displays when the video player is buffering or waiting for data
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -70,4 +70,4 @@ Hide and show the indicator based on the `data-visible` attribute.
</StyleCase>
</FrameworkCase>
<ApiReference component="BufferingIndicator" />
<ComponentReference component="BufferingIndicator" />
@@ -0,0 +1,35 @@
---
title: ContainerMixin
description: Mixin that consumes player context and auto-attaches media elements
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`ContainerMixin` creates a class that consumes the player store from context and automatically attaches `<video>` or `<audio>` elements found within it. It uses a `MutationObserver` to watch for media element changes and calls `store.attach()` to keep the store's media target in sync.
### When to use ContainerMixin
Use `ContainerMixin` when the <DocsLink slug="reference/provider-mixin">provider</DocsLink> and container live in different elements. This is common when the store owner sits higher in the tree (e.g., a layout shell) while the media element sits deeper in a content area.
For a single element that both owns the store and attaches media, use <DocsLink slug="reference/player-mixin">`PlayerMixin`</DocsLink> instead.
### How media discovery works
On `connectedCallback`, `ContainerMixin` sets up a `MutationObserver` on its children. When a `<video>` or `<audio>` element is added or removed, it calls `store.attach({ media, container })` or cleans up the previous attachment. The observer is torn down on `disconnectedCallback`.
### Split composition
Pair `ContainerMixin` with <DocsLink slug="reference/provider-mixin">`ProviderMixin`</DocsLink> for full control over which element owns the store and which discovers the media:
```ts
const { ProviderMixin, ContainerMixin } = createPlayer({ features: features.video });
// Provider owns the store, sits at the top
class PlayerShell extends ProviderMixin(MediaElement) {}
// Container discovers media, sits deeper in the tree
class MediaRegion extends ContainerMixin(MediaElement) {}
```
<UtilReference util="ContainerMixin" />
+2 -2
View File
@@ -5,7 +5,7 @@ frameworkTitle:
description: Container component for composing and auto-hiding video player controls on user interaction
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -105,4 +105,4 @@ No ARIA role is applied to `<media-controls>` — it is a layout wrapper, not a
</StyleCase>
</FrameworkCase>
<ApiReference component="Controls" />
<ComponentReference component="Controls" />
@@ -0,0 +1,35 @@
---
title: createPlayer
description: Factory function that creates a player instance with typed store, Provider component, Container, and hooks
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import Demo from "@/components/docs/demos/Demo.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import BasicUsageDemoReact from "@/components/docs/demos/create-player/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/create-player/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/create-player/react/css/BasicUsage.css?raw";
`createPlayer` is the entry point for setting up a Video.js player in React. It accepts a configuration object with a `features` array and returns hooks and components for building a player.
The hook is typed according to the provided features, giving you full type safety for state selectors and actions.
## Examples
### Basic Usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}
>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<UtilReference util="createPlayer" />
@@ -0,0 +1,32 @@
---
title: createSelector
description: Create a type-safe selector for a store slice's state
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
`createSelector` creates a type-safe selector function for a given slice. The returned selector extracts that slice's state from the full store state, or returns `undefined` if the slice is not configured.
The built-in selectors (<DocsLink slug="reference/select-playback">`selectPlayback`</DocsLink>, <DocsLink slug="reference/select-buffer">`selectBuffer`</DocsLink>, etc.) are all created with `createSelector`. Use it to create selectors for custom slices.
```ts
import { createSelector } from '@videojs/store';
import { myCustomSlice } from './my-custom-slice';
const selectCustom = createSelector(myCustomSlice);
// Use with usePlayer (React) or PlayerController (HTML)
const state = selectCustom(store.state);
```
<FrameworkCase frameworks={["react"]}>
Pass selectors to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> or <DocsLink slug="reference/use-store">`useStore`</DocsLink> for reactive subscriptions.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass selectors to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> or <DocsLink slug="reference/store-controller">`StoreController`</DocsLink> for reactive subscriptions.
</FrameworkCase>
<UtilReference util="createSelector" />
@@ -5,7 +5,7 @@ frameworkTitle:
description: Accessible fullscreen toggle button with keyboard support and state reflection
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -93,4 +93,4 @@ Renders a `<button>` with an automatic `aria-label`: "Enter fullscreen" or "Exit
</StyleCase>
</FrameworkCase>
<ApiReference component="FullscreenButton" />
<ComponentReference component="FullscreenButton" />
@@ -0,0 +1,51 @@
---
title: createPlayer
description: Factory function that creates a player instance with typed store, mixins, and controller for HTML custom elements
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import Demo from "@/components/docs/demos/Demo.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import BasicUsageDemoHtml from "@/components/docs/demos/html-create-player/html/css/BasicUsage.astro";
import basicUsageHtml from "@/components/docs/demos/html-create-player/html/css/BasicUsage.html?raw";
import basicUsageHtmlCss from "@/components/docs/demos/html-create-player/html/css/BasicUsage.css?raw";
import basicUsageHtmlTs from "@/components/docs/demos/html-create-player/html/css/BasicUsage.ts?raw";
`createPlayer` is the entry point for setting up a Video.js player with HTML custom elements. It accepts a configuration object with a `features` array and returns everything needed to build a player: a pre-composed `PlayerElement`, typed <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink>, `context`, individual mixins, and a `create` factory.
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
const {
PlayerElement,
PlayerController,
context,
PlayerMixin,
ProviderMixin,
ContainerMixin,
create,
} = createPlayer({ features: features.video });
// Register the pre-composed player element
customElements.define('video-player', PlayerElement);
```
The simplest approach is to register `PlayerElement` directly. For custom behavior, use the individual mixins to compose your own element classes.
## Examples
### Basic Usage
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "index.html", code: basicUsageHtml, lang: "html" },
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
]}>
<BasicUsageDemoHtml />
</Demo>
</StyleCase>
<UtilReference util="createPlayer" slug="html-create-player" />
@@ -5,7 +5,7 @@ frameworkTitle:
description: Accessible mute/unmute button with keyboard support and volume state reflection
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -124,4 +124,4 @@ Renders a `<button>` with an automatic `aria-label`: "Unmute" when muted, "Mute"
</StyleCase>
</FrameworkCase>
<ApiReference component="MuteButton" />
<ComponentReference component="MuteButton" />
@@ -5,7 +5,7 @@ frameworkTitle:
description: Accessible picture-in-picture toggle button with keyboard support and state reflection
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -89,4 +89,4 @@ Renders a `<button>` with an automatic `aria-label`: "Enter PiP" or "Exit PiP".
</StyleCase>
</FrameworkCase>
<ApiReference component="PipButton" />
<ComponentReference component="PipButton" />
@@ -5,7 +5,7 @@ frameworkTitle:
description: Accessible play/pause button with keyboard support and customizable rendering
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -92,4 +92,4 @@ Renders a `<button>` element with an automatic `aria-label` that updates based o
</StyleCase>
</FrameworkCase>
<ApiReference component="PlayButton" />
<ComponentReference component="PlayButton" />
@@ -0,0 +1,45 @@
---
title: playerContext
description: The default player context instance for consuming the player store in controllers
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`playerContext` is a Web Components context token that carries the player store through the DOM tree. Provider elements publish the store to this context, and consumer elements (via <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> or <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink>) read from it.
Pass it as the second argument to `PlayerController`:
```ts
import { PlayerController, MediaElement, playerContext, selectPlayback } from '@videojs/html';
class PlayButton extends MediaElement {
#playback = new PlayerController(this, playerContext, selectPlayback);
render() {
const playback = this.#playback.value;
// Use playback state to render
}
}
```
### Import directly or destructure from createPlayer
<DocsLink slug="reference/html-create-player">`createPlayer`</DocsLink> returns a `context` property that is the same `playerContext` instance. Both approaches reference the same object:
```ts
// Direct import
import { playerContext } from '@videojs/html';
// Destructured — identical object
const { context } = createPlayer({ features: features.video });
context === playerContext; // true
```
Use the direct import when building reusable elements that don't depend on a specific `createPlayer` call. Destructure from `createPlayer` when you want all player utilities from a single result.
### When you need this
Most elements should use <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> (which accepts a context argument) rather than importing `playerContext` directly. Import it directly when you need to pass the context token to a lower-level API like <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink> or <DocsLink slug="reference/provider-mixin">`ProviderMixin`</DocsLink>.
<UtilReference util="playerContext" />
@@ -0,0 +1,36 @@
---
title: PlayerController
description: Reactive controller for accessing player store state in HTML custom elements
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import Demo from "@/components/docs/demos/Demo.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import BasicUsageDemoHtml from "@/components/docs/demos/player-controller/html/css/BasicUsage.astro";
import basicUsageHtml from "@/components/docs/demos/player-controller/html/css/BasicUsage.html?raw";
import basicUsageHtmlCss from "@/components/docs/demos/player-controller/html/css/BasicUsage.css?raw";
import basicUsageHtmlTs from "@/components/docs/demos/player-controller/html/css/BasicUsage.ts?raw";
`PlayerController` is a reactive controller that consumes the player store from <DocsLink slug="reference/player-context">`playerContext`</DocsLink>. Without a selector it returns the store instance directly (no subscription — use this for actions). With a selector it returns the selected value and subscribes to changes, triggering a host update on shallow-equal change. Access the current value via `.value`, which returns `undefined` until connected to a provider.
## Examples
### Basic Usage
<FrameworkCase frameworks={["html"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "index.html", code: basicUsageHtml, lang: "html" },
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
]}
>
<BasicUsageDemoHtml />
</Demo>
</StyleCase>
</FrameworkCase>
<UtilReference util="PlayerController" />
@@ -0,0 +1,41 @@
---
title: PlayerMixin
description: Composed mixin combining ProviderMixin and ContainerMixin for a complete player element
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`PlayerMixin` combines <DocsLink slug="reference/provider-mixin">`ProviderMixin`</DocsLink> and <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink> into a single mixin. The resulting class both owns the player store and auto-attaches media elements, making it a complete player element. It is equivalent to `ProviderMixin(ContainerMixin(Base))` — it creates the store on connect, observes the DOM for `<video>` or `<audio>` children, and publishes the store to descendant elements via context.
### Choose your approach
| Approach | When to use |
|----------|-------------|
| `PlayerElement` | Simplest setup. Register and use directly -- no custom class needed. |
| `PlayerMixin(Base)` | Custom player element with additional lifecycle logic or methods. |
| <DocsLink slug="reference/provider-mixin">`ProviderMixin`</DocsLink> + <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink> | Provider and container are separate elements (e.g., store owner is a layout shell, media lives in a content region). |
### PlayerElement vs PlayerMixin
For the simplest setup, use the pre-composed `PlayerElement` from `createPlayer`:
```ts
const { PlayerElement } = createPlayer({ features: features.video });
customElements.define('video-player', PlayerElement);
```
Use `PlayerMixin` when you need to add custom behavior:
```ts
class MyPlayer extends PlayerMixin(MediaElement) {
connectedCallback() {
super.connectedCallback();
this.store.subscribe(() => {
// Custom logic on state changes
});
}
}
```
<UtilReference util="PlayerMixin" />
+2 -2
View File
@@ -5,7 +5,7 @@ frameworkTitle:
description: Poster image component that displays a thumbnail until video playback starts
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -90,4 +90,4 @@ Unlike the native `<video poster>` attribute, this component allows you to provi
</StyleCase>
</FrameworkCase>
<ApiReference component="Poster" />
<ComponentReference component="Poster" />
@@ -0,0 +1,33 @@
---
title: ProviderMixin
description: Mixin that provides player context to descendant elements
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`ProviderMixin` creates a class that provides the player store to descendant elements via context. It owns the store lifecycle: creates the store lazily on first access and destroys it on disconnect.
### Store lifecycle
The store is created lazily using the factory from `createPlayer`. On `connectedCallback`, the mixin publishes the store to context so descendant elements can consume it. On `disconnectedCallback`, it calls `store.destroy()` and cleans up.
Any <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> or <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink> element below the provider in the DOM tree receives the store automatically through context.
### When to split provider and container
Use `ProviderMixin` separate from <DocsLink slug="reference/container-mixin">`ContainerMixin`</DocsLink> when the store owner is a different element from the one containing the media. This is common in complex layouts:
```ts
const { ProviderMixin, ContainerMixin } = createPlayer({ features: features.video });
// Layout shell owns the store
class AppShell extends ProviderMixin(MediaElement) {}
// Content region discovers and attaches the media element
class VideoRegion extends ContainerMixin(MediaElement) {}
```
When a single element should handle both, use <DocsLink slug="reference/player-mixin">`PlayerMixin`</DocsLink> instead.
<UtilReference util="ProviderMixin" />
@@ -0,0 +1,71 @@
---
title: renderElement
description: Utility for rendering UI component elements with state-driven props and render prop support
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import Demo from "@/components/docs/demos/Demo.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import BasicUsageDemoReact from "@/components/docs/demos/render-element/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/render-element/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/render-element/react/css/BasicUsage.css?raw";
`renderElement` renders a UI component element, handling default tag rendering, render props (element or function), props merging, ref composition, and state-driven `className`/`style`.
```tsx
import { renderElement } from "@videojs/react";
function PlayButton({ className, style, render, ...props }) {
const state = { paused: true };
return renderElement(
"button",
{ className, style, render },
{
state,
ref: buttonRef,
props: [{ type: "button", "aria-label": "Play" }, props],
},
);
}
```
The `className` and `style` component props accept either static values or functions that receive the current state:
```tsx
<PlayButton
className={(state) => (state.paused ? "paused" : "playing")}
style={(state) => ({ opacity: state.paused ? 0.5 : 1 })}
/>
```
The `render` prop lets consumers fully customize the rendered element while preserving all internal props and refs:
```tsx
<PlayButton
render={(props, state) => (
<button {...props}>{state.paused ? "Play" : "Pause"}</button>
)}
/>
```
## Examples
### Basic Usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}
>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<UtilReference util="renderElement" />
@@ -5,7 +5,7 @@ frameworkTitle:
description: Accessible seek button for skipping forward or backward by a configurable number of seconds
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -74,4 +74,4 @@ Renders a `<button>` with an automatic `aria-label` describing the action, e.g.
</StyleCase>
</FrameworkCase>
<ApiReference component="SeekButton" />
<ComponentReference component="SeekButton" />
@@ -0,0 +1,49 @@
---
title: selectBuffer
description: Select the buffer state slice from the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["react"]}>
Pass `selectBuffer` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to buffer state. Returns `undefined` if the buffer feature is not configured.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectBuffer` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe to buffer state. Returns `undefined` if the buffer feature is not configured.
</FrameworkCase>
The returned state includes buffered ranges and percent buffered.
<FrameworkCase frameworks={["react"]}>
```tsx
import { usePlayer } from '@videojs/react';
import { selectBuffer } from '@videojs/core/dom';
function BufferBar() {
const buffer = usePlayer(selectBuffer);
if (!buffer) return null;
return (
<div style={{ width: `${buffer.percentBuffered * 100}%` }} />
);
}
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
import { selectBuffer } from '@videojs/core/dom';
const { PlayerController, context } = createPlayer({ features: features.video });
class BufferBar extends MediaElement {
#buffer = new PlayerController(this, context, selectBuffer);
}
```
</FrameworkCase>
<UtilReference util="selectBuffer" />
@@ -0,0 +1,51 @@
---
title: selectControls
description: Select the controls state slice from the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["react"]}>
Pass `selectControls` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to controls state. Returns `undefined` if the controls feature is not configured.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectControls` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe to controls state. Returns `undefined` if the controls feature is not configured.
</FrameworkCase>
The returned state includes whether controls are visible and whether the user is active (interacting with the player).
<FrameworkCase frameworks={["react"]}>
```tsx
import { usePlayer } from '@videojs/react';
import { selectControls } from '@videojs/core/dom';
function ControlsOverlay({ children }: { children: React.ReactNode }) {
const controls = usePlayer(selectControls);
if (!controls) return null;
return (
<div style={{ opacity: controls.visible ? 1 : 0 }}>
{children}
</div>
);
}
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
import { selectControls } from '@videojs/core/dom';
const { PlayerController, context } = createPlayer({ features: features.video });
class ControlsOverlay extends MediaElement {
#controls = new PlayerController(this, context, selectControls);
}
```
</FrameworkCase>
<UtilReference util="selectControls" />
@@ -0,0 +1,51 @@
---
title: selectFullscreen
description: Select the fullscreen state slice from the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["react"]}>
Pass `selectFullscreen` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to fullscreen state. Returns `undefined` if the fullscreen feature is not configured.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectFullscreen` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe to fullscreen state. Returns `undefined` if the fullscreen feature is not configured.
</FrameworkCase>
The returned state includes whether fullscreen is active and its availability on the current platform.
<FrameworkCase frameworks={["react"]}>
```tsx
import { usePlayer } from '@videojs/react';
import { selectFullscreen } from '@videojs/core/dom';
function FullscreenButton() {
const fs = usePlayer(selectFullscreen);
if (!fs || fs.availability !== 'available') return null;
return (
<button onClick={fs.toggle}>
{fs.active ? 'Exit fullscreen' : 'Fullscreen'}
</button>
);
}
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
import { selectFullscreen } from '@videojs/core/dom';
const { PlayerController, context } = createPlayer({ features: features.video });
class FullscreenButton extends MediaElement {
#fullscreen = new PlayerController(this, context, selectFullscreen);
}
```
</FrameworkCase>
<UtilReference util="selectFullscreen" />
@@ -0,0 +1,51 @@
---
title: selectPiP
description: Select the picture-in-picture state slice from the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["react"]}>
Pass `selectPiP` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to picture-in-picture state. Returns `undefined` if the PiP feature is not configured.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectPiP` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe to picture-in-picture state. Returns `undefined` if the PiP feature is not configured.
</FrameworkCase>
The returned state includes whether PiP is active and its availability on the current platform.
<FrameworkCase frameworks={["react"]}>
```tsx
import { usePlayer } from '@videojs/react';
import { selectPiP } from '@videojs/core/dom';
function PiPButton() {
const pip = usePlayer(selectPiP);
if (!pip || pip.availability !== 'available') return null;
return (
<button onClick={pip.toggle}>
{pip.active ? 'Exit PiP' : 'Picture-in-Picture'}
</button>
);
}
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
import { selectPiP } from '@videojs/core/dom';
const { PlayerController, context } = createPlayer({ features: features.video });
class PiPButton extends MediaElement {
#pip = new PlayerController(this, context, selectPiP);
}
```
</FrameworkCase>
<UtilReference util="selectPiP" />
@@ -0,0 +1,47 @@
---
title: selectPlayback
description: Select the playback state slice from the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["react"]}>
Pass `selectPlayback` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to playback state. Returns `undefined` if the playback feature is not configured.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectPlayback` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe to playback state. Returns `undefined` if the playback feature is not configured.
</FrameworkCase>
The returned state includes `paused`, `ended`, and action methods like `play`, `pause`, and `toggle`.
<FrameworkCase frameworks={["react"]}>
```tsx
import { usePlayer } from '@videojs/react';
import { selectPlayback } from '@videojs/core/dom';
function PlayButton() {
const playback = usePlayer(selectPlayback);
if (!playback) return null;
return <button onClick={playback.toggle}>{playback.paused ? 'Play' : 'Pause'}</button>;
}
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
import { selectPlayback } from '@videojs/core/dom';
const { PlayerController, context } = createPlayer({ features: features.video });
class PlayButton extends MediaElement {
#playback = new PlayerController(this, context, selectPlayback);
}
```
</FrameworkCase>
<UtilReference util="selectPlayback" />
@@ -0,0 +1,47 @@
---
title: selectSource
description: Select the source state slice from the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["react"]}>
Pass `selectSource` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to source state. Returns `undefined` if the source feature is not configured.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectSource` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe to source state. Returns `undefined` if the source feature is not configured.
</FrameworkCase>
The returned state includes the current `src` and `type`.
<FrameworkCase frameworks={["react"]}>
```tsx
import { usePlayer } from '@videojs/react';
import { selectSource } from '@videojs/core/dom';
function SourceInfo() {
const source = usePlayer(selectSource);
if (!source) return null;
return <span>{source.src}</span>;
}
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
import { selectSource } from '@videojs/core/dom';
const { PlayerController, context } = createPlayer({ features: features.video });
class SourceInfo extends MediaElement {
#source = new PlayerController(this, context, selectSource);
}
```
</FrameworkCase>
<UtilReference util="selectSource" />
@@ -0,0 +1,51 @@
---
title: selectTime
description: Select the time state slice from the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["react"]}>
Pass `selectTime` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to time state. Returns `undefined` if the time feature is not configured.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectTime` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe to time state. Returns `undefined` if the time feature is not configured.
</FrameworkCase>
The returned state includes `currentTime`, `duration`, and the `seek` action method.
<FrameworkCase frameworks={["react"]}>
```tsx
import { usePlayer } from '@videojs/react';
import { selectTime } from '@videojs/core/dom';
function TimeDisplay() {
const time = usePlayer(selectTime);
if (!time) return null;
return (
<span>
{Math.floor(time.currentTime)} / {Math.floor(time.duration)}
</span>
);
}
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
import { selectTime } from '@videojs/core/dom';
const { PlayerController, context } = createPlayer({ features: features.video });
class TimeDisplay extends MediaElement {
#time = new PlayerController(this, context, selectTime);
}
```
</FrameworkCase>
<UtilReference util="selectTime" />
@@ -0,0 +1,56 @@
---
title: selectVolume
description: Select the volume state slice from the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["react"]}>
Pass `selectVolume` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe to volume state. Returns `undefined` if the volume feature is not configured.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectVolume` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe to volume state. Returns `undefined` if the volume feature is not configured.
</FrameworkCase>
The returned state includes `volume`, `muted`, and action methods like `setVolume` and `setMuted`.
<FrameworkCase frameworks={["react"]}>
```tsx
import { usePlayer } from '@videojs/react';
import { selectVolume } from '@videojs/core/dom';
function VolumeSlider() {
const vol = usePlayer(selectVolume);
if (!vol) return null;
return (
<input
type="range"
min={0}
max={1}
step={0.01}
value={vol.volume}
onChange={(e) => vol.setVolume(Number(e.target.value))}
/>
);
}
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, features, MediaElement } from '@videojs/html';
import { selectVolume } from '@videojs/core/dom';
const { PlayerController, context } = createPlayer({ features: features.video });
class VolumeSlider extends MediaElement {
#volume = new PlayerController(this, context, selectVolume);
}
```
</FrameworkCase>
<UtilReference util="selectVolume" />
@@ -0,0 +1,59 @@
---
title: SnapshotController
description: Reactive controller for subscribing to a State container in HTML custom elements
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`SnapshotController` subscribes to a `State` container and triggers host updates when state changes. It has two overloads:
**Full state** -- returns the entire state object. Re-renders on any state change.
```ts
class Display extends HTMLElement {
#state = new SnapshotController(this, sliderState);
render() {
const value = this.#state.value;
// Full state object
}
}
```
**With selector** -- returns a derived value from state. Re-renders only when the selected value changes (using shallow equality).
```ts
class SliderValue extends HTMLElement {
#value = new SnapshotController(this, sliderState, (s) => s.value);
render() {
return this.#value.value; // Only the selected slice
}
}
```
Use `.track(state)` to switch to a different `State` container at runtime.
### SnapshotController vs StoreController
Both are reactive controllers, but they operate at different levels:
| | `SnapshotController` | <DocsLink slug="reference/store-controller">`StoreController`</DocsLink> |
| -------------- | ------------------------------ | ------------------------------------------------------------------------ |
| **Input** | `State` container | Store instance or context |
| **Subscribes** | Always | Only with a selector |
| **Use case** | Subscribe to raw state changes | Access store actions and optionally subscribe |
`SnapshotController` is lower-level. It works with `State` containers directly -- the reactive primitives that back a store. Use it when building custom controllers or working outside the player store system.
<DocsLink slug="reference/store-controller">`StoreController`</DocsLink> is
higher-level. It resolves a store from a context or direct reference and
internally creates a `SnapshotController` when you pass a selector. Use it for
player UI elements.
### Lifecycle
`SnapshotController` subscribes on `hostConnected()` and unsubscribes on `hostDisconnected()`. The React equivalent is <DocsLink slug="reference/use-snapshot">`useSnapshot`</DocsLink>.
<UtilReference util="SnapshotController" />
@@ -0,0 +1,48 @@
---
title: StoreController
description: Reactive controller for subscribing to store state in HTML custom elements
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`StoreController` is a reactive controller for accessing store state and actions in custom elements. It accepts a store instance or a context, resolving the store automatically.
**Without selector** -- returns the store instance directly. Does NOT subscribe to changes. Use this to access store actions.
```ts
class VolumeControl extends HTMLElement {
#store = new StoreController(this, storeSource);
handleClick() {
this.#store.value.setVolume(0.5);
}
}
```
**With selector** -- returns the selected value and subscribes to changes. Triggers a host update when the selected value changes (using shallow equality).
```ts
class PlayButton extends HTMLElement {
#playback = new StoreController(this, storeSource, selectPlayback);
render() {
const playback = this.#playback.value;
// Re-renders when playback state changes
}
}
```
### StoreController vs PlayerController vs SnapshotController
| | `StoreController` | <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> | <DocsLink slug="reference/snapshot-controller">`SnapshotController`</DocsLink> |
|---|---|---|---|
| **Input** | Any store or context | Player store context | `State` container |
| **Typed to** | Generic store | Player features | Raw state |
| **Use case** | General-purpose store access | Player UI elements | Low-level state subscription |
<DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> is a typed wrapper around `StoreController` scoped to the player store. For player UI, prefer `PlayerController`. Use `StoreController` when working with a custom store outside the player system.
When you pass a selector, `StoreController` internally creates a <DocsLink slug="reference/snapshot-controller">`SnapshotController`</DocsLink> on the store's `$state` container. Without a selector, no subscription is created -- you get the store instance for imperative access to actions.
<UtilReference util="StoreController" />
+2 -2
View File
@@ -5,7 +5,7 @@ frameworkTitle:
description: Time display components for showing current time, duration, and remaining time in a video player
---
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -238,4 +238,4 @@ No `aria-live` region is used — time updates too frequently and might overwhel
</StyleCase>
</FrameworkCase>
<ApiReference component="Time" />
<ComponentReference component="Time" />
@@ -0,0 +1,36 @@
---
title: useButton
description: Hook for creating accessible button components with keyboard and pointer interaction
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import Demo from "@/components/docs/demos/Demo.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import BasicUsageDemoReact from "@/components/docs/demos/use-button/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/use-button/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/use-button/react/css/BasicUsage.css?raw";
`useButton` provides button behavior including keyboard activation and accessibility checks. It returns a `getButtonProps` function for spreading onto a `<button>` element and a `buttonRef` for validation.
`getButtonProps` merges internal button props (click/keyboard handlers, disabled state) with any external props you pass in. Use <DocsLink slug="reference/render-element">`renderElement`</DocsLink> to render the button with state-driven props and render prop support. In development mode, `buttonRef` warns if the rendered element is not a `<button>`.
## Examples
### Basic Usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}
>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<UtilReference util="useButton" />
@@ -0,0 +1,47 @@
---
title: useMediaRegistration
description: Hook to register a custom media element with the player context
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
`useMediaRegistration` returns a setter function for registering a media element with the player context. The built-in `<Video>` and `<Audio>` components use this internally -- you only need it when building a custom media element.
```tsx
import { useMediaRegistration } from "@videojs/react";
import { useRef, useEffect } from "react";
function CustomMedia({ src }: { src: string }) {
const setMedia = useMediaRegistration();
const ref = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (ref.current && setMedia) {
setMedia(ref.current);
return () => setMedia(null);
}
}, [setMedia]);
return <video ref={ref} src={src} />;
}
```
## Who needs this
You only need `useMediaRegistration` if you're replacing the built-in `<Video>` or `<Audio>` components with a custom element. For example, if you're...
- Wrapping a third-party video player
- Using a `<canvas>` or WebGL-based renderer
- Building a custom `<audio>` element with additional markup
For standard `<video>` and `<audio>` playback, use the built-in components.
## Cleanup pattern
Always return a cleanup function that passes `null` to the setter. This detaches the media element when the component unmounts, preventing stale references in the store.
## Safe outside Provider
Returns `undefined` when called outside a Player `Provider`. Check the return value before using it -- this avoids crashes in components that may render outside the player tree.
<UtilReference util="useMediaRegistration" />
@@ -0,0 +1,34 @@
---
title: useMedia
description: Hook to access the underlying media element from within a Player Provider
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import Demo from "@/components/docs/demos/Demo.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import BasicUsageDemoReact from "@/components/docs/demos/use-media/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/use-media/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/use-media/react/css/BasicUsage.css?raw";
`useMedia` returns the current `HTMLMediaElement` (or `null` if no media element has been registered yet). Use it to interact directly with the native media element when needed. It must be called within a Player `Provider`. The media element becomes available after a `<Video>` or `<Audio>` component mounts inside the provider tree. To register a custom media element instead of the built-in components, see <DocsLink slug="reference/use-media-registration">`useMediaRegistration`</DocsLink>.
## Examples
### Basic Usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}
>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<UtilReference util="useMedia" />
@@ -0,0 +1,47 @@
---
title: usePlayerContext
description: Hook to access the full player context including store, media element, and media setter
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`usePlayerContext` returns the full `PlayerContextValue` object, which includes the store, the current media element, and the media setter.
```tsx
import { usePlayerContext } from "@videojs/react";
function DebugPanel() {
const { store, media, setMedia } = usePlayerContext();
return (
<pre>
{JSON.stringify(
{
hasStore: !!store,
hasMedia: !!media,
tagName: media?.tagName,
},
null,
2,
)}
</pre>
);
}
```
Throws an error if called outside a Player `Provider`.
### Prefer higher-level hooks
For most use cases, use the focused hooks instead:
| Need | Hook |
| -------------------------- | ----------------------------------------------------------------------------------- |
| Store access with selector | <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> |
| Current media element | <DocsLink slug="reference/use-media">`useMedia`</DocsLink> |
| Register custom media | <DocsLink slug="reference/use-media-registration">`useMediaRegistration`</DocsLink> |
These hooks read from the same context internally. `usePlayerContext` exposes the raw context value -- use it when you need multiple context fields in one call or when building a custom abstraction over the player context.
<UtilReference util="usePlayerContext" />
@@ -0,0 +1,56 @@
---
title: usePlayer
description: Hook to access the player store from within a Player Provider
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import Demo from "@/components/docs/demos/Demo.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import StoreAccessDemoReact from "@/components/docs/demos/use-player/react/css/StoreAccess";
import storeAccessReactTsx from "@/components/docs/demos/use-player/react/css/StoreAccess.tsx?raw";
import storeAccessReactCss from "@/components/docs/demos/use-player/react/css/StoreAccess.css?raw";
import SelectorDemoReact from "@/components/docs/demos/use-player/react/css/Selector";
import selectorReactTsx from "@/components/docs/demos/use-player/react/css/Selector.tsx?raw";
import selectorReactCss from "@/components/docs/demos/use-player/react/css/Selector.css?raw";
`usePlayer` gives you access to the player store from any component within a Player `Provider`. It has two overloads — without arguments for direct store access, or with a selector for reactive state subscriptions. The scoped `usePlayer` returned from <DocsLink slug="reference/create-player">`createPlayer`</DocsLink> is typed to your specific features, while the standalone export from `@videojs/react` returns an untyped store.
## Examples
### Store Access
Call `usePlayer()` without arguments to get the store instance. Use this for imperative actions like play, pause, and volume changes. The component does not re-render on state changes.
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "App.tsx", code: storeAccessReactTsx, lang: "tsx" },
{ title: "App.css", code: storeAccessReactCss, lang: "css" },
]}
>
<StoreAccessDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
### Selector Subscription
Pass a selector function to subscribe to specific state. The component re-renders when the selected value changes, using shallow equality by default.
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "App.tsx", code: selectorReactTsx, lang: "tsx" },
{ title: "App.css", code: selectorReactCss, lang: "css" },
]}
>
<SelectorDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<UtilReference util="usePlayer" />
@@ -0,0 +1,46 @@
---
title: useSelector
description: Low-level hook for subscribing to derived state with customizable equality checks
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`useSelector` is a low-level hook that subscribes to an external store using React's `useSyncExternalStore`. It accepts a `subscribe` function, a `getSnapshot` function, a `selector` to derive state, and an optional `isEqual` comparator (defaults to `shallowEqual`).
```tsx
import { useSelector, shallowEqual } from "@videojs/store/react";
function TimeDisplay({ store }) {
const time = useSelector(
(cb) => store.subscribe(cb),
() => store.state,
(state) => ({ current: state.currentTime, duration: state.duration }),
shallowEqual,
);
return (
<span>
{time.current} / {time.duration}
</span>
);
}
```
### Relationship to useStore and useSnapshot
Both `useStore` and `useSnapshot` are built on `useSelector`:
| Hook | Input | Use case |
| ---------------------------------------------------------------- | ------------------------- | --------------------------------------- |
| <DocsLink slug="reference/use-store">`useStore`</DocsLink> | Store instance | Player and store access with selector |
| <DocsLink slug="reference/use-snapshot">`useSnapshot`</DocsLink> | `State` container | Subscribe to raw state changes |
| `useSelector` | Custom subscribe/snapshot | Full control over subscription plumbing |
Prefer <DocsLink slug="reference/use-store">`useStore`</DocsLink> for store-backed state and <DocsLink slug="reference/use-snapshot">`useSnapshot`</DocsLink> for `State` containers. Use `useSelector` when you need to integrate with a non-standard external source or customize the equality comparison.
### Equality comparison
The `isEqual` parameter controls when React re-renders. The default `shallowEqual` compares object properties one level deep -- sufficient for most selector return values. Pass a custom comparator for deeply nested objects or when you need reference equality (`Object.is`).
<UtilReference util="useSelector" />
@@ -0,0 +1,42 @@
---
title: useSnapshot
description: Hook to subscribe to a State container's current value
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`useSnapshot` subscribes to a `State` container and returns its current value, re-rendering when the value changes. It has two overloads:
**Full state** -- returns the entire state object.
```tsx
function Display({ state }) {
const value = useSnapshot(state);
return <span>{value.count}</span>;
}
```
**With selector** -- returns a derived value from the state, re-rendering only when the selected value changes. Pass a custom comparator as the third argument when needed.
```tsx
function Count({ state }) {
const count = useSnapshot(state, (s) => s.count);
return <span>{count}</span>;
}
```
### State containers vs stores
A `State` container is a reactive primitive that holds an object value and notifies subscribers on change. Stores are built on top of `State` containers but add features, actions, and lifecycle.
| Hook | Input | Subscribes to |
| ---------------------------------------------------------- | -------------- | -------------------------------- |
| `useSnapshot` | `State<T>` | Raw state container |
| <DocsLink slug="reference/use-store">`useStore`</DocsLink> | Store instance | Store-backed state with features |
Use `useSnapshot` when working with standalone `State` containers outside the player store system -- for example, custom state in component libraries. For player state, use <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> or <DocsLink slug="reference/use-store">`useStore`</DocsLink>.
`useSnapshot` is built on <DocsLink slug="reference/use-selector">`useSelector`</DocsLink> and uses `shallowEqual` by default. The HTML equivalent is `SnapshotController`.
<UtilReference util="useSnapshot" />
@@ -0,0 +1,56 @@
---
title: useStore
description: Hook to access store state and actions with optional selector-based subscriptions
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import Demo from "@/components/docs/demos/Demo.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import StoreAccessDemoReact from "@/components/docs/demos/use-store/react/css/StoreAccess";
import storeAccessReactTsx from "@/components/docs/demos/use-store/react/css/StoreAccess.tsx?raw";
import storeAccessReactCss from "@/components/docs/demos/use-store/react/css/StoreAccess.css?raw";
import SelectorDemoReact from "@/components/docs/demos/use-store/react/css/Selector";
import selectorReactTsx from "@/components/docs/demos/use-store/react/css/Selector.tsx?raw";
import selectorReactCss from "@/components/docs/demos/use-store/react/css/Selector.css?raw";
`useStore` subscribes to a store instance directly. It has the same two overloads as <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> — without a selector for store access, or with a selector for reactive state. Within a player provider, `usePlayer` is usually simpler since it reads the store from context. Reach for `useStore` when you have a store instance directly or need to derive computed values from the store.
## Examples
### Store Access
Call `useStore(store)` without a selector to get the store instance back for imperative actions. The component does not subscribe to state changes.
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "App.tsx", code: storeAccessReactTsx, lang: "tsx" },
{ title: "App.css", code: storeAccessReactCss, lang: "css" },
]}
>
<StoreAccessDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
### Selector Subscription
Pass a selector to derive and subscribe to computed values from the store. The component re-renders when the derived value changes, using shallow equality by default.
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo
files={[
{ title: "App.tsx", code: selectorReactTsx, lang: "tsx" },
{ title: "App.css", code: selectorReactCss, lang: "css" },
]}
>
<SelectorDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<UtilReference util="useStore" />
@@ -1,12 +1,12 @@
---
title: Write reference pages
description: How to create component API reference pages for the Video.js documentation site
description: How to create API reference pages for the Video.js documentation site
---
import Aside from '@/components/Aside.astro';
import DocsLink from '@/components/docs/DocsLink.astro';
This guide covers how to create component reference pages — the API documentation under `reference/` in the docs sidebar.
This guide covers how to create API reference pages — both component references and util references (hooks, controllers, mixins) under `reference/` in the docs sidebar.
<Aside type="tip">
Reference pages are scaffolded with the `api-reference` Claude skill. Run `/api-reference play-button` to generate a reference page interactively.
@@ -24,13 +24,13 @@ The component should be feature-complete enough that its props, state, and data
## Generate the API reference JSON
The api-docs-builder extracts type information from TypeScript sources and outputs JSON files that the `<ApiReference />` component renders as tables.
The api-docs-builder extracts type information from TypeScript sources and outputs JSON files that `<ComponentReference />` and `<UtilReference />` components render as tables.
```bash
pnpm -F site api-docs
```
This generates JSON to `site/src/content/generated-api-reference/{name}.json`. These files are gitignored and regenerated automatically on `pnpm dev` and `pnpm build`.
This generates JSON to `site/src/content/generated-component-reference/{name}.json` and `site/src/content/generated-util-reference/{name}.json`. These files are gitignored and regenerated automatically on `pnpm dev` and `pnpm build`.
### Builder naming conventions
@@ -127,7 +127,7 @@ Use `frameworkTitle` to show the HTML custom element tag name when the HTML fram
### Imports
```tsx
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
@@ -166,17 +166,84 @@ After imports, the page follows this order:
- **Accessibility** — ARIA attributes, keyboard interactions
- Other sections as appropriate
3. **Examples** — at least BasicUsage, wrapped in `<Demo>` with `<FrameworkCase>`
4. **`<ApiReference />`** — renders the generated JSON as props, state, and data attribute tables
4. **`<ComponentReference />`** — renders the generated JSON as props, state, and data attribute tables
```mdx
<ApiReference component="PlayButton" />
<ComponentReference component="PlayButton" />
```
The component automatically handles single-part and multi-part layouts.
## Create a util reference page
Util reference pages document React hooks/utilities and HTML controllers/mixins. Unlike component pages, they don't have demos or anatomy sections.
### Structure
```mdx
---
title: usePlayer
description: Hook to access the player store
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
## Import
\`\`\`tsx
import { usePlayer } from '@videojs/react';
\`\`\`
## Usage
Explain usage patterns, overloads, and code examples.
<UtilReference util="usePlayer" />
```
### Key differences from component pages
- No `frameworkTitle` — util pages are framework-specific (React-only or HTML-only)
- No demos or anatomy — focus on import, usage examples, and the generated reference tables
- Use `<UtilReference util="..." />` instead of `<ComponentReference component="..." />`
- The `util` prop takes the PascalCase or camelCase name (e.g., `"usePlayer"`, `"PlayerController"`)
### Util auto-discovery rules
The builder discovers utils from package entry points two ways:
**Naming conventions** (no tag needed):
- `use*` hooks: `usePlayer`, `useMedia`
- `*Controller` classes: `PlayerController`, `StoreController`
- `create*` factories and mixins: `createPlayer`, `createProviderMixin`
- `select*` selectors: `selectPlayback`, `selectVolume`
**`@public` JSDoc tag** (everything else):
- Utilities that don't match a convention: `mergeProps`, `renderElement`
- Context objects: `playerContext`
If your export matches a naming convention, skip the `@public` tag.
### The slug prop
The `util` prop takes the export name (`"usePlayer"`, `"PlayerController"`). If the generated JSON slug doesn't match `kebabCase(util)` -- like the HTML `createPlayer` whose slug is `html-create-player` -- pass `slug` explicitly:
```mdx
<UtilReference util="createPlayer" slug="html-create-player" />
```
### Generated JSON
Util reference JSON is at `site/src/content/generated-util-reference/{slug}.json`. The builder generates it from the discovery pipeline in `site/scripts/api-docs-builder/src/util-handler.ts`.
## Add to the sidebar
Open `site/src/docs.config.ts` and add your page alphabetically within the Components section:
Open `site/src/docs.config.ts` and add your page alphabetically within the appropriate section:
- **Components** — UI component reference pages
- **Selectors** — State selectors (visible to both frameworks)
- **Hooks & Utilities** (`frameworks: ['react']`) — React hooks and utilities
- **Controllers & Mixins** (`frameworks: ['html']`) — HTML controllers and mixins
```ts
{
@@ -200,6 +267,11 @@ Open `site/src/docs.config.ts` and add your page alphabetically within the Compo
For reference, look at existing pages:
**Component pages:**
- <DocsLink slug="reference/play-button">PlayButton</DocsLink> — single-part, interactive
- <DocsLink slug="reference/controls">Controls</DocsLink> — behavior-heavy (auto-hide)
- <DocsLink slug="reference/time">Time</DocsLink> — multi-part, formatting
**Util pages:**
- <DocsLink slug="reference/use-player">usePlayer</DocsLink> — React hook, multi-overload
- <DocsLink slug="reference/player-controller">PlayerController</DocsLink> — HTML controller