chore(site): rewrite media element builder for MediaHost architecture (#1334)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Darius Cepulis
2026-04-14 12:26:29 -05:00
committed by GitHub
co-authored by Claude Opus 4.6
parent b731cba6ba
commit d8cd59e0fd
11 changed files with 505 additions and 311 deletions
@@ -0,0 +1,84 @@
/**
* Mock media contract types — mirrors packages/core/src/core/media/types.ts.
*
* Exercises: event extraction from capability event interfaces.
* VideoEvents extends all capability events (including TextTrackListEvents).
* AudioEvents extends a subset (no text track events).
*/
export interface EventLike<Detail = void> {
readonly type: string;
readonly timeStamp: number;
readonly detail?: Detail;
}
export interface MediaPlaybackEvents {
play: EventLike;
playing: EventLike;
waiting: EventLike;
}
export interface MediaPauseEvents {
pause: EventLike;
ended: EventLike;
}
export interface MediaSeekEvents {
timeupdate: EventLike;
durationchange: EventLike;
seeking: EventLike;
seeked: EventLike;
loadedmetadata: EventLike;
}
export interface MediaSourceEvents {
loadstart: EventLike;
emptied: EventLike;
canplay: EventLike;
canplaythrough: EventLike;
loadeddata: EventLike;
}
export interface MediaVolumeEvents {
volumechange: EventLike;
}
export interface MediaPlaybackRateEvents {
ratechange: EventLike;
}
export interface MediaBufferEvents {
progress: EventLike;
}
export interface MediaErrorEvents {
error: EventLike;
}
export interface TextTrackListEvents {
addtrack: EventLike;
removetrack: EventLike;
changetrack: EventLike;
trackmodechange: EventLike;
}
export interface VideoEvents
extends MediaPlaybackEvents,
MediaPauseEvents,
MediaSeekEvents,
MediaSourceEvents,
MediaVolumeEvents,
MediaPlaybackRateEvents,
MediaBufferEvents,
MediaErrorEvents,
TextTrackListEvents {}
export interface AudioEvents
extends MediaPlaybackEvents,
MediaPauseEvents,
MediaSeekEvents,
MediaSourceEvents,
MediaVolumeEvents,
MediaPlaybackRateEvents,
MediaBufferEvents,
MediaErrorEvents {}
@@ -1,11 +1,13 @@
/**
* Mock complex delegate — mirrors HlsMediaBase.
* Mock complex host — mirrors HlsMedia.
*
* Exercises: multiple getter/setter pairs with JSDoc descriptions,
* readonly properties, boolean type, overlap with native Attributes
* readonly properties, boolean type, overlap with native attributes
* (src, preload) that should be deduplicated by the builder.
*/
export class ComplexDelegate {
import { HTMLVideoElementHost } from '../simple';
export class ComplexHost extends HTMLVideoElementHost {
#src: string = '';
#type: string | undefined;
#preferPlayback: string | undefined = 'mse';
@@ -69,10 +71,4 @@ export class ComplexDelegate {
get engine(): object | null {
return this.#engine;
}
attach(_target: EventTarget): void {}
detach(): void {}
destroy(): void {}
}
export class ComplexCustomMedia {}
@@ -1,37 +1,14 @@
/**
* 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.
* Exercises: shared native attributes (via static properties), CSS vars,
* and slots that the builder reads to populate media element references.
* Slots are parsed from getVideoTemplateHTML / getCommonTemplateHTML.
*
* 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. */
@@ -68,26 +45,44 @@ function getVideoTemplateHTML(attrs: Record<string, string>): string {
`;
}
function getAudioTemplateHTML(attrs: Record<string, string>): string {
return /*html*/ `
<style>
audio { width: 100%; }
</style>
<slot name="media">
<audio></audio>
</slot>
<slot></slot>
`;
function getCommonTemplateHTML(tag: string) {
return (attrs: Record<string, string>) => {
return /*html*/ `
<style>
${tag} { width: 100%; }
</style>
<slot name="media">
<${tag}></${tag}>
</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;
}
// Stub — the builder parses the AST, it doesn't run the code.
// Mirrors the real CustomMediaElement factory signature.
export function CustomMediaElement(tag: string, Host: any) {
class CustomMedia {
static getTemplateHTML = tag === 'video' ? getVideoTemplateHTML : getCommonTemplateHTML(tag);
static shadowRootOptions = { mode: 'open' };
export const CustomVideoElement = class {
static getTemplateHTML = getVideoTemplateHTML;
};
export const CustomAudioElement = class {
static getTemplateHTML = getAudioTemplateHTML;
};
static properties = {
autoPictureInPicture: { type: Boolean },
autoplay: { type: Boolean },
controls: { type: Boolean },
controlsList: { type: String },
crossOrigin: { type: String },
defaultMuted: { type: Boolean, attribute: 'muted' },
disablePictureInPicture: { type: Boolean },
disableRemotePlayback: { type: Boolean },
loading: { type: String },
loop: { type: Boolean },
playsInline: { type: Boolean },
poster: { type: String },
preload: { type: String },
src: { type: String },
};
}
return CustomMedia;
}
@@ -1,13 +1,13 @@
/**
* Mock extending delegate — mirrors MuxMediaBase extending HlsMediaBase.
* Mock extending host — mirrors MuxVideoMedia extending HlsMedia.
*
* Exercises: delegate inheritance. The builder must walk the extends chain
* to extract properties from both this class and its parent (ComplexDelegate).
* Exercises: host inheritance. The builder must walk the extends chain
* to extract properties from both this class and its parent (ComplexHost).
* Child properties override parent properties of the same name.
*/
import { ComplexDelegate } from '../complex';
import { ComplexHost } from '../complex';
export class ExtendingDelegate extends ComplexDelegate {
export class ExtendingHost extends ComplexHost {
#playbackId: string = '';
#customDomain: string = '';
@@ -38,5 +38,3 @@ export class ExtendingDelegate extends ComplexDelegate {
super.debug = value;
}
}
export class ExtendingCustomMedia {}
@@ -1,10 +1,18 @@
/**
* Mock simple delegate mirrors DashMediaBase.
* Mock simple host mirrors DashMedia.
*
* Exercises: minimal delegate with just src (read-write) and engine (readonly).
* Exercises: minimal host with just src (read-write) and engine (readonly).
* No JSDoc on properties tests that missing descriptions produce undefined.
*/
export class SimpleDelegate {
// Stub — the builder walks the prototype chain and stops here.
export class HTMLVideoElementHost {
attach(_target: EventTarget): void {}
detach(): void {}
destroy(): void {}
}
export class SimpleHost extends HTMLVideoElementHost {
#src: string = '';
#engine: object = {};
@@ -19,10 +27,4 @@ export class SimpleDelegate {
get engine(): object {
return this.#engine;
}
attach(_target: EventTarget): void {}
detach(): void {}
destroy(): void {}
}
export class SimpleCustomMedia {}
@@ -1,17 +1,16 @@
/**
* Mock complex media element mirrors HlsVideo.
*
* Exercises: standard mixin composition with a complex delegate
* that has JSDoc descriptions on its getter/setters.
* Exercises: standard composition with CustomMediaElement factory
* and a complex host 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.
import { ComplexHost } from '../../../../core/src/dom/media/complex';
import { CustomMediaElement } from '../../../../core/src/dom/media/custom-media-element';
// Stub — 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) {}
export class ComplexVideo extends MediaAttachMixin(CustomMediaElement('video', ComplexHost)) {}
@@ -1,15 +1,13 @@
/**
* Mock extending media element mirrors MuxVideo.
*
* Exercises: media element with a delegate that inherits from another delegate.
* Exercises: media element with a host that inherits from another host.
*/
import { ExtendingCustomMedia, ExtendingDelegate } from '../../../../core/src/dom/media/extending';
import { CustomMediaElement } from '../../../../core/src/dom/media/custom-media-element';
import { ExtendingHost } 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) {}
export class ExtendingVideo extends MediaAttachMixin(CustomMediaElement('video', ExtendingHost)) {}
@@ -1,18 +1,16 @@
/**
* 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
* Exercises: standard composition with CustomMediaElement factory.
* The builder follows this import chain to discover the host class
* and resolve its properties.
*/
import { SimpleCustomMedia, SimpleDelegate } from '../../../../core/src/dom/media/simple';
import { CustomMediaElement } from '../../../../core/src/dom/media/custom-media-element';
import { SimpleHost } from '../../../../core/src/dom/media/simple';
// Stubs — the builder parses the AST, it doesn't run the code.
// Stub — 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) {}
export class SimpleVideo extends MediaAttachMixin(CustomMediaElement('video', SimpleHost)) {}