feat(html): implement duration display component

Add HTML duration display component using VJS-10 hook-style architecture:
- Custom element with shadow DOM containing single span
- Uses duration display state definition from media-store
- Minimal implementation without styling or ARIA attributes
- Follows toConnectedHTMLComponent pattern for consistency

Component renders formatted duration (e.g., "4:32") in a simple span element.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2025-09-12 15:59:15 -07:00
committed by Christian Pillsbury
co-authored by Claude
parent d0036fc7d6
commit e35dd44be8
2 changed files with 125 additions and 0 deletions
@@ -0,0 +1,124 @@
import {
toConnectedHTMLComponent,
StateHook,
PropsHook,
} from '../utils/component-factory';
import { durationDisplayStateDefinition } from '@vjs-10/media-store';
import { namedNodeMapToObject } from '../utils/element-utils.js';
export function getTemplateHTML(
this: typeof DurationDisplayBase,
_attrs: Record<string, string>,
_props: Record<string, any> = {},
) {
return /* html */ `
<span></span>
`;
}
export class DurationDisplayBase extends HTMLElement {
static shadowRootOptions = {
mode: 'open' as ShadowRootMode,
};
static getTemplateHTML = getTemplateHTML;
_state:
| {
duration: number | undefined;
isValidDuration: boolean;
formattedDuration: string;
durationPhrase: string;
}
| undefined;
constructor() {
super();
if (!this.shadowRoot) {
this.attachShadow(
(this.constructor as typeof DurationDisplayBase).shadowRootOptions,
);
const attrs = namedNodeMapToObject(this.attributes);
const html = (
this.constructor as typeof DurationDisplayBase
).getTemplateHTML(attrs);
const shadowRoot = this.shadowRoot as unknown as ShadowRoot;
shadowRoot.setHTMLUnsafe
? shadowRoot.setHTMLUnsafe(html)
: (shadowRoot.innerHTML = html);
}
}
get duration() {
return this._state?.duration;
}
get formattedDuration() {
return this._state?.formattedDuration || '--:--';
}
get durationPhrase() {
return this._state?.durationPhrase || 'Duration unknown';
}
_update(_props: any, state: any) {
this._state = state;
// Update the span content
const spanElement = this.shadowRoot?.querySelector('span') as HTMLElement;
if (spanElement) {
spanElement.textContent = state.formattedDuration;
}
}
}
/**
* DurationDisplay state hook - equivalent to React's useDurationDisplayState
* Handles media store state subscription and transformation
*/
export const useDurationDisplayState: StateHook<{
duration: number | undefined;
isValidDuration: boolean;
formattedDuration: string;
durationPhrase: string;
}> = {
keys: [...durationDisplayStateDefinition.keys],
transform: (rawState, _mediaStore) => ({
...durationDisplayStateDefinition.stateTransform(rawState),
// Duration display is read-only, so no request methods needed
}),
};
/**
* DurationDisplay props hook - equivalent to React's useDurationDisplayProps
* Handles element attributes and properties based on state
*/
export const useDurationDisplayProps: PropsHook<{
duration: number | undefined;
isValidDuration: boolean;
formattedDuration: string;
durationPhrase: string;
}> = (_state, _element) => {
const baseProps: Record<string, any> = {};
return baseProps;
};
/**
* Connected DurationDisplay component using hook-style architecture
* Equivalent to React's DurationDisplay = toConnectedComponent(...)
*/
export const DurationDisplay = toConnectedHTMLComponent(
DurationDisplayBase,
useDurationDisplayState,
useDurationDisplayProps,
'DurationDisplay',
);
// Register the custom element
if (!globalThis.customElements.get('media-duration-display')) {
// @ts-ignore - Custom element constructor compatibility
globalThis.customElements.define('media-duration-display', DurationDisplay);
}
export default DurationDisplay;
+1
View File
@@ -6,6 +6,7 @@ export { PlayButton } from './components/media-play-button.js';
export { MuteButton } from './components/media-mute-button.js';
export { VolumeRange } from './components/media-volume-range.js';
export { FullscreenButton } from './components/media-fullscreen-button.js';
export { DurationDisplay } from './components/media-duration-display.js';
export function defineVjsPlayer() {
/** @TODO - Reimplement me (at least as a POC) (CJP) */