feat(site): media element API reference builder (#1256)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Darius Cepulis
2026-04-07 14:49:33 -05:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 0c8e19a4e0
commit cf357ad9a7
16 changed files with 1258 additions and 0 deletions
@@ -0,0 +1,78 @@
/**
* Mock complex delegate — mirrors HlsMediaDelegate.
*
* Exercises: multiple getter/setter pairs with JSDoc descriptions,
* readonly properties, boolean type, overlap with native Attributes
* (src, preload) that should be deduplicated by the builder.
*/
export class ComplexDelegate {
#src: string = '';
#type: string | undefined;
#preferPlayback: string | undefined = 'mse';
#config: Record<string, unknown> = {};
#debug: boolean = false;
#preload: string = 'metadata';
#engine: object | null = null;
get src(): string {
return this.#src;
}
set src(value: string) {
this.#src = value;
}
/** Explicit source type. When unset, inferred from the source URL extension. */
get type(): string | undefined {
return this.#type;
}
set type(value: string | undefined) {
this.#type = value;
}
/** Whether to prefer `'mse'` or `'native'` playback. */
get preferPlayback(): string | undefined {
return this.#preferPlayback;
}
set preferPlayback(value: string | undefined) {
this.#preferPlayback = value;
}
get config(): Record<string, unknown> {
return this.#config;
}
set config(value: Record<string, unknown>) {
this.#config = value;
}
/** Enable debug logging. */
get debug(): boolean {
return this.#debug;
}
set debug(value: boolean) {
this.#debug = value;
}
get preload(): string {
return this.#preload;
}
set preload(value: string) {
this.#preload = value;
}
/** The underlying playback engine instance. */
get engine(): object | null {
return this.#engine;
}
attach(_target: EventTarget): void {}
detach(): void {}
destroy(): void {}
}
export class ComplexCustomMedia {}
@@ -0,0 +1,93 @@
/**
* Mock custom media element infrastructure.
*
* Exercises: shared Events, Attributes, and CSS vars that the builder reads
* to populate media element references. Slots are parsed from the template
* HTML (getVideoTemplateHTML / getAudioTemplateHTML), not from exported arrays.
*
* VideoCSSVars/AudioCSSVars follow the `{ camelKey: '--var-name' }` pattern
* with JSDoc descriptions, matching UI component css-vars files.
*/
export const Events = [
'abort',
'canplay',
'durationchange',
'ended',
'pause',
'play',
'timeupdate',
'volumechange',
] as const;
export const Attributes = [
'autoplay',
'controls',
'crossorigin',
'loop',
'muted',
'playsinline',
'poster',
'preload',
'src',
] as const;
/** CSS custom property names for video elements. */
export const VideoCSSVars = {
/** Border radius of the video element. */
borderRadius: '--media-video-border-radius',
/** Object fit for the video. */
objectFit: '--media-object-fit',
/** Object position for the video. */
objectPosition: '--media-object-position',
/** Duration of the caption track transition. */
captionTrackDuration: '--media-caption-track-duration',
/** Delay before the caption track transition. */
captionTrackDelay: '--media-caption-track-delay',
/** Vertical offset of the caption track. */
captionTrackY: '--media-caption-track-y',
} as const;
/** CSS custom property names for audio elements. */
export const AudioCSSVars = {} as const;
// Minimal template stubs — the builder parses <slot> elements from these.
function getVideoTemplateHTML(attrs: Record<string, string>): string {
return /*html*/ `
<style>
video {
border-radius: var(${VideoCSSVars.borderRadius});
object-fit: var(${VideoCSSVars.objectFit}, contain);
object-position: var(${VideoCSSVars.objectPosition}, center);
}
</style>
<slot name="media">
<video></video>
</slot>
<slot></slot>
`;
}
function getAudioTemplateHTML(attrs: Record<string, string>): string {
return /*html*/ `
<style>
audio { width: 100%; }
</style>
<slot name="media">
<audio></audio>
</slot>
<slot></slot>
`;
}
// Minimal stubs — the builder only needs to detect these by name, not run them.
export function CustomMediaMixin(base: any, _opts: any) {
return base;
}
export const CustomVideoElement = class {
static getTemplateHTML = getVideoTemplateHTML;
};
export const CustomAudioElement = class {
static getTemplateHTML = getAudioTemplateHTML;
};
@@ -0,0 +1,42 @@
/**
* Mock extending delegate — mirrors MuxMediaDelegate extending HlsMediaDelegate.
*
* Exercises: delegate inheritance. The builder must walk the extends chain
* to extract properties from both this class and its parent (ComplexDelegate).
* Child properties override parent properties of the same name.
*/
import { ComplexDelegate } from '../complex';
export class ExtendingDelegate extends ComplexDelegate {
#playbackId: string = '';
#customDomain: string = '';
/** The playback ID for the video. */
get playbackId(): string {
return this.#playbackId;
}
set playbackId(value: string) {
this.#playbackId = value;
}
/** Custom domain for asset delivery. */
get customDomain(): string {
return this.#customDomain;
}
set customDomain(value: string) {
this.#customDomain = value;
}
/** Overrides parent debug — adds network logging. */
get debug(): boolean {
return super.debug;
}
set debug(value: boolean) {
super.debug = value;
}
}
export class ExtendingCustomMedia {}
@@ -0,0 +1,28 @@
/**
* Mock simple delegate — mirrors DashMediaDelegate.
*
* Exercises: minimal delegate with just src (read-write) and engine (readonly).
* No JSDoc on properties — tests that missing descriptions produce undefined.
*/
export class SimpleDelegate {
#src: string = '';
#engine: object = {};
get src(): string {
return this.#src;
}
set src(value: string) {
this.#src = value;
}
get engine(): object {
return this.#engine;
}
attach(_target: EventTarget): void {}
detach(): void {}
destroy(): void {}
}
export class SimpleCustomMedia {}
@@ -0,0 +1,13 @@
/**
* Mock background video registration — mirrors define/media/background-video.ts.
*
* Exercises: exclusion. BackgroundVideo uses MediaAttachMixin(HTMLElement)
* without MediaPropsMixin. The builder should discover this file (it has
* static tagName) but skip it because parseMixinChain returns null.
* Its API reference is manually maintained in MDX (#1243).
*/
import { BackgroundVideo } from '../../media/background-video';
export class BackgroundVideoElement extends BackgroundVideo {
static readonly tagName = 'background-video';
}
@@ -0,0 +1,11 @@
/**
* Mock complex video element registration — mirrors define/media/hls-video.ts.
*
* Exercises: element discovery via static tagName in define/media/*.ts,
* with a delegate that has JSDoc and overlapping native attributes.
*/
import { ComplexVideo } from '../../media/complex-video';
export class ComplexVideoElement extends ComplexVideo {
static readonly tagName = 'complex-video';
}
@@ -0,0 +1,14 @@
/**
* Mock media container registration — mirrors define/media/container.ts.
*
* Exercises: container exclusion. The real container.ts does NOT define a
* new class with `static tagName` inline — it imports an already-defined
* class. The builder should exclude this from media element discovery.
*/
class MediaContainerElement {
static readonly tagName = 'media-container';
}
// No `export class ... extends` with `static tagName` — the class is
// defined elsewhere and only registered here.
export { MediaContainerElement };
@@ -0,0 +1,10 @@
/**
* Mock extending video element registration — mirrors define/media/mux-video.ts.
*
* Exercises: element with a delegate that extends another delegate.
*/
import { ExtendingVideo } from '../../media/extending-video';
export class ExtendingVideoElement extends ExtendingVideo {
static readonly tagName = 'extending-video';
}
@@ -0,0 +1,10 @@
/**
* Mock simple video element registration — mirrors define/media/dash-video.ts.
*
* Exercises: element discovery via static tagName in define/media/*.ts.
*/
import { SimpleVideo } from '../../media/simple-video';
export class SimpleVideoElement extends SimpleVideo {
static readonly tagName = 'simple-video';
}
@@ -0,0 +1,12 @@
/**
* Mock background video — mirrors the real BackgroundVideo.
*
* Exercises: exclusion of elements that use MediaAttachMixin(HTMLElement)
* without MediaPropsMixin. The builder's parseMixinChain returns null
* because there is no MediaPropsMixin call in the extends chain.
*/
function MediaAttachMixin(base: any) {
return base;
}
export class BackgroundVideo extends MediaAttachMixin(Object) {}
@@ -0,0 +1,17 @@
/**
* Mock complex media element — mirrors HlsVideo.
*
* Exercises: standard mixin composition with a complex delegate
* that has JSDoc descriptions on its getter/setters.
*/
import { ComplexCustomMedia, ComplexDelegate } from '../../../../core/src/dom/media/complex';
// Stubs — the builder parses the AST, it doesn't run the code.
function MediaAttachMixin(base: any) {
return base;
}
function MediaPropsMixin(base: any, _delegate: any) {
return base;
}
export class ComplexVideo extends MediaPropsMixin(MediaAttachMixin(ComplexCustomMedia), ComplexDelegate) {}
@@ -0,0 +1,15 @@
/**
* Mock extending media element — mirrors MuxVideo.
*
* Exercises: media element with a delegate that inherits from another delegate.
*/
import { ExtendingCustomMedia, ExtendingDelegate } from '../../../../core/src/dom/media/extending';
function MediaAttachMixin(base: any) {
return base;
}
function MediaPropsMixin(base: any, _delegate: any) {
return base;
}
export class ExtendingVideo extends MediaPropsMixin(MediaAttachMixin(ExtendingCustomMedia), ExtendingDelegate) {}
@@ -0,0 +1,18 @@
/**
* Mock simple media element — mirrors DashVideo.
*
* Exercises: standard mixin composition with a simple delegate.
* The builder follows this import chain to discover the delegate class
* and resolve its properties.
*/
import { SimpleCustomMedia, SimpleDelegate } from '../../../../core/src/dom/media/simple';
// Stubs — the builder parses the AST, it doesn't run the code.
function MediaAttachMixin(base: any) {
return base;
}
function MediaPropsMixin(base: any, _delegate: any) {
return base;
}
export class SimpleVideo extends MediaPropsMixin(MediaAttachMixin(SimpleCustomMedia), SimpleDelegate) {}