This commit is contained in:
Rahim
2025-09-22 23:39:30 +10:00
parent d3221ec211
commit d2b2dd4868
132 changed files with 860 additions and 969 deletions
+4 -4
View File
@@ -12,7 +12,7 @@ module.exports = [
sourcemap: true,
},
watch: {
clearScreen: false
clearScreen: false,
},
external: (id) => {
// Don't externalize relative imports (starts with . or /)
@@ -24,7 +24,7 @@ module.exports = [
},
plugins: [
resolve({
extensions: ['.js', '.jsx', '.ts', '.tsx']
extensions: ['.js', '.jsx', '.ts', '.tsx'],
}),
commonjs(),
typescript({
@@ -43,7 +43,7 @@ module.exports = [
sourcemap: true,
},
watch: {
clearScreen: false
clearScreen: false,
},
external: (id) => {
// Don't externalize relative imports (starts with . or /)
@@ -55,7 +55,7 @@ module.exports = [
},
plugins: [
resolve({
extensions: ['.js', '.jsx', '.ts', '.tsx']
extensions: ['.js', '.jsx', '.ts', '.tsx'],
}),
commonjs(),
typescript({
+1 -1
View File
@@ -4,4 +4,4 @@ export * as MediaVolumeHighIcon from './media-volume-high-icon.js';
export * as MediaVolumeLowIcon from './media-volume-low-icon.js';
export * as MediaVolumeOffIcon from './media-volume-off-icon.js';
export * as MediaFullscreenEnterIcon from './media-fullscreen-enter-icon.js';
export * as MediaFullscreenExitIcon from './media-fullscreen-exit-icon.js';
export * as MediaFullscreenExitIcon from './media-fullscreen-exit-icon.js';
@@ -23,4 +23,4 @@ export class MediaChromeIcon extends HTMLElement {
this.shadowRoot!.innerHTML = (this.constructor as typeof MediaChromeIcon).getTemplateHTML();
}
}
}
}
@@ -1,6 +1,7 @@
import { MediaChromeIcon } from './media-chrome-icon.js';
import { SVG_ICONS } from '@vjs-10/icons';
import { MediaChromeIcon } from './media-chrome-icon.js';
export function getTemplateHTML() {
return /* html */ `
${MediaChromeIcon.getTemplateHTML()}
@@ -17,4 +18,4 @@ export class MediaFullscreenEnterIcon extends MediaChromeIcon {
static getTemplateHTML = getTemplateHTML;
}
customElements.define('media-fullscreen-enter-icon', MediaFullscreenEnterIcon);
customElements.define('media-fullscreen-enter-icon', MediaFullscreenEnterIcon);
@@ -1,6 +1,7 @@
import { MediaChromeIcon } from './media-chrome-icon.js';
import { SVG_ICONS } from '@vjs-10/icons';
import { MediaChromeIcon } from './media-chrome-icon.js';
export function getTemplateHTML() {
return /* html */ `
${MediaChromeIcon.getTemplateHTML()}
@@ -17,4 +18,4 @@ export class MediaFullscreenExitIcon extends MediaChromeIcon {
static getTemplateHTML = getTemplateHTML;
}
customElements.define('media-fullscreen-exit-icon', MediaFullscreenExitIcon);
customElements.define('media-fullscreen-exit-icon', MediaFullscreenExitIcon);
@@ -1,6 +1,7 @@
import { MediaChromeIcon } from './media-chrome-icon.js';
import { SVG_ICONS } from '@vjs-10/icons';
import { MediaChromeIcon } from './media-chrome-icon.js';
export function getTemplateHTML() {
return /* html */ `
${MediaChromeIcon.getTemplateHTML()}
@@ -12,4 +13,4 @@ export class MediaPauseIcon extends MediaChromeIcon {
static getTemplateHTML = getTemplateHTML;
}
customElements.define('media-pause-icon', MediaPauseIcon);
customElements.define('media-pause-icon', MediaPauseIcon);
@@ -1,6 +1,7 @@
import { MediaChromeIcon } from './media-chrome-icon.js';
import { SVG_ICONS } from '@vjs-10/icons';
import { MediaChromeIcon } from './media-chrome-icon.js';
export function getTemplateHTML() {
return /* html */ `
${MediaChromeIcon.getTemplateHTML()}
@@ -17,4 +18,4 @@ export class MediaPlayIcon extends MediaChromeIcon {
static getTemplateHTML = getTemplateHTML;
}
customElements.define('media-play-icon', MediaPlayIcon);
customElements.define('media-play-icon', MediaPlayIcon);
@@ -1,7 +1,7 @@
import { MediaChromeIcon } from './media-chrome-icon.js';
import { SVG_ICONS } from '@vjs-10/icons';
import { MediaChromeIcon } from './media-chrome-icon.js';
export function getTemplateHTML() {
return /* html */ `
${MediaChromeIcon.getTemplateHTML()}
@@ -1,7 +1,7 @@
import { MediaChromeIcon } from './media-chrome-icon.js';
import { SVG_ICONS } from '@vjs-10/icons';
import { MediaChromeIcon } from './media-chrome-icon.js';
export function getTemplateHTML() {
return /* html */ `
${MediaChromeIcon.getTemplateHTML()}
@@ -1,7 +1,7 @@
import { MediaChromeIcon } from './media-chrome-icon.js';
import { SVG_ICONS } from '@vjs-10/icons';
import { MediaChromeIcon } from './media-chrome-icon.js';
export function getTemplateHTML() {
return /* html */ `
${MediaChromeIcon.getTemplateHTML()}
+1 -1
View File
@@ -10,4 +10,4 @@
"incremental": false
},
"exclude": ["dist", "node_modules"]
}
}
+1 -1
View File
@@ -6,4 +6,4 @@
},
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"]
}
}
@@ -39,4 +39,4 @@
"publishConfig": {
"access": "public"
}
}
}
@@ -1 +1 @@
export * from './media-provider';
export * from './media-provider';
@@ -6,4 +6,4 @@
},
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"]
}
}
@@ -18,4 +18,4 @@ export default defineConfig({
incremental: false,
},
},
});
});
+1 -1
View File
@@ -38,4 +38,4 @@
"publishConfig": {
"access": "public"
}
}
}
+1 -1
View File
@@ -1 +1 @@
// Store integration utilities for HTML/Web Components
// Store integration utilities for HTML/Web Components
+1 -1
View File
@@ -6,4 +6,4 @@
},
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"]
}
}
@@ -18,4 +18,4 @@ export default defineConfig({
incremental: false,
},
},
});
});
+4 -4
View File
@@ -12,7 +12,7 @@ module.exports = [
sourcemap: true,
},
watch: {
clearScreen: false
clearScreen: false,
},
external: (id) => {
// Don't externalize relative imports (starts with . or /)
@@ -24,7 +24,7 @@ module.exports = [
},
plugins: [
resolve({
extensions: ['.js', '.jsx', '.ts', '.tsx']
extensions: ['.js', '.jsx', '.ts', '.tsx'],
}),
commonjs(),
typescript({
@@ -43,7 +43,7 @@ module.exports = [
sourcemap: true,
},
watch: {
clearScreen: false
clearScreen: false,
},
external: (id) => {
// Don't externalize relative imports (starts with . or /)
@@ -55,7 +55,7 @@ module.exports = [
},
plugins: [
resolve({
extensions: ['.js', '.jsx', '.ts', '.tsx']
extensions: ['.js', '.jsx', '.ts', '.tsx'],
}),
commonjs(),
typescript({
@@ -3,7 +3,7 @@ import { namedNodeMapToObject } from '../utils/element-utils.js';
export function getTemplateHTML(
this: typeof MediaChromeButton,
_attrs: Record<string, string>,
_props: Record<string, any> = {},
_props: Record<string, any> = {}
) {
return /* html */ `
<style>
@@ -31,20 +31,14 @@ export class MediaChromeButton extends HTMLElement {
if (!this.shadowRoot) {
// Set up the Shadow DOM if not using Declarative Shadow DOM.
this.attachShadow(
(this.constructor as typeof MediaChromeButton).shadowRootOptions,
);
this.attachShadow((this.constructor as typeof MediaChromeButton).shadowRootOptions);
const attrs = namedNodeMapToObject(this.attributes);
const html = (
this.constructor as typeof MediaChromeButton
).getTemplateHTML(attrs);
const html = (this.constructor as typeof MediaChromeButton).getTemplateHTML(attrs);
// From MDN: setHTMLUnsafe should be used instead of ShadowRoot.innerHTML
// when a string of HTML may contain declarative shadow roots.
const shadowRoot = this.shadowRoot as unknown as ShadowRoot;
shadowRoot.setHTMLUnsafe
? shadowRoot.setHTMLUnsafe(html)
: (shadowRoot.innerHTML = html);
shadowRoot.setHTMLUnsafe ? shadowRoot.setHTMLUnsafe(html) : (shadowRoot.innerHTML = html);
}
this.addEventListener('click', this);
@@ -1,12 +1,6 @@
import {
toConnectedHTMLComponent,
StateHook,
PropsHook,
} from '../utils/component-factory';
import {
currentTimeDisplayStateDefinition,
formatDisplayTime,
} from '@vjs-10/media-store';
import { currentTimeDisplayStateDefinition, formatDisplayTime } from '@vjs-10/media-store';
import { PropsHook, StateHook, toConnectedHTMLComponent } from '../utils/component-factory';
export class CurrentTimeDisplayBase extends HTMLElement {
static shadowRootOptions = {
@@ -25,9 +19,7 @@ export class CurrentTimeDisplayBase extends HTMLElement {
super();
if (!this.shadowRoot) {
this.attachShadow(
(this.constructor as typeof CurrentTimeDisplayBase).shadowRootOptions,
);
this.attachShadow((this.constructor as typeof CurrentTimeDisplayBase).shadowRootOptions);
}
}
@@ -43,11 +35,7 @@ export class CurrentTimeDisplayBase extends HTMLElement {
return this.hasAttribute('show-remaining');
}
attributeChangedCallback(
name: string,
_oldValue: string | null,
_newValue: string | null,
) {
attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null) {
if (name === 'show-remaining' && this._state) {
// Re-render with current state when show-remaining attribute changes
this._update({}, this._state);
@@ -104,16 +92,13 @@ export const CurrentTimeDisplay = toConnectedHTMLComponent(
CurrentTimeDisplayBase,
useCurrentTimeDisplayState,
useCurrentTimeDisplayProps,
'CurrentTimeDisplay',
'CurrentTimeDisplay'
);
// Register the custom element
if (!globalThis.customElements.get('media-current-time-display')) {
// @ts-ignore - Custom element constructor compatibility
globalThis.customElements.define(
'media-current-time-display',
CurrentTimeDisplay,
);
globalThis.customElements.define('media-current-time-display', CurrentTimeDisplay);
}
export default CurrentTimeDisplay;
@@ -1,15 +1,12 @@
import {
toConnectedHTMLComponent,
StateHook,
PropsHook,
} from '../utils/component-factory';
import { durationDisplayStateDefinition, formatDisplayTime } from '@vjs-10/media-store';
import { PropsHook, StateHook, toConnectedHTMLComponent } from '../utils/component-factory';
import { namedNodeMapToObject } from '../utils/element-utils.js';
export function getTemplateHTML(
this: typeof DurationDisplayBase,
_attrs: Record<string, string>,
_props: Record<string, any> = {},
_props: Record<string, any> = {}
) {
return /* html */ `
<span></span>
@@ -32,18 +29,12 @@ export class DurationDisplayBase extends HTMLElement {
super();
if (!this.shadowRoot) {
this.attachShadow(
(this.constructor as typeof DurationDisplayBase).shadowRootOptions,
);
this.attachShadow((this.constructor as typeof DurationDisplayBase).shadowRootOptions);
const attrs = namedNodeMapToObject(this.attributes);
const html = (
this.constructor as typeof DurationDisplayBase
).getTemplateHTML(attrs);
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);
shadowRoot.setHTMLUnsafe ? shadowRoot.setHTMLUnsafe(html) : (shadowRoot.innerHTML = html);
}
}
@@ -53,7 +44,7 @@ export class DurationDisplayBase extends HTMLElement {
_update(_props: any, state: any) {
this._state = state;
// Update the span content with formatted duration
const spanElement = this.shadowRoot?.querySelector('span') as HTMLElement;
if (spanElement) {
@@ -95,7 +86,7 @@ export const DurationDisplay = toConnectedHTMLComponent(
DurationDisplayBase,
useDurationDisplayState,
useDurationDisplayProps,
'DurationDisplay',
'DurationDisplay'
);
// Register the custom element
@@ -104,4 +95,4 @@ if (!globalThis.customElements.get('media-duration-display')) {
globalThis.customElements.define('media-duration-display', DurationDisplay);
}
export default DurationDisplay;
export default DurationDisplay;
@@ -1,11 +1,8 @@
import {
toConnectedHTMLComponent,
StateHook,
PropsHook,
} from '../utils/component-factory';
import { MediaChromeButton } from './media-chrome-button';
import { fullscreenButtonStateDefinition } from '@vjs-10/media-store';
import { PropsHook, StateHook, toConnectedHTMLComponent } from '../utils/component-factory';
import { MediaChromeButton } from './media-chrome-button';
export class FullscreenButtonBase extends MediaChromeButton {
_state:
| {
@@ -50,9 +47,7 @@ export const useFullscreenButtonState: StateHook<{ fullscreen: boolean }> = {
keys: fullscreenButtonStateDefinition.keys,
transform: (rawState, mediaStore) => ({
...fullscreenButtonStateDefinition.stateTransform(rawState),
...fullscreenButtonStateDefinition.createRequestMethods(
mediaStore.dispatch,
),
...fullscreenButtonStateDefinition.createRequestMethods(mediaStore.dispatch),
}),
};
@@ -60,10 +55,7 @@ export const useFullscreenButtonState: StateHook<{ fullscreen: boolean }> = {
* FullscreenButton props hook - equivalent to React's useFullscreenButtonProps
* Handles element attributes and properties based on state
*/
export const useFullscreenButtonProps: PropsHook<{ fullscreen: boolean }> = (
state,
_element,
) => {
export const useFullscreenButtonProps: PropsHook<{ fullscreen: boolean }> = (state, _element) => {
const baseProps: Record<string, any> = {
/** data attributes/props */
['data-fullscreen']: state.fullscreen,
@@ -89,7 +81,7 @@ export const FullscreenButton = toConnectedHTMLComponent(
FullscreenButtonBase,
useFullscreenButtonState,
useFullscreenButtonProps,
'FullscreenButton',
'FullscreenButton'
);
// NOTE: In this architecture it will be important to decouple component class definitions from their registration in the CustomElementsRegistry. (CJP)
@@ -1,11 +1,8 @@
import {
toConnectedHTMLComponent,
StateHook,
PropsHook,
} from '../utils/component-factory';
import { MediaChromeButton } from './media-chrome-button';
import { muteButtonStateDefinition } from '@vjs-10/media-store';
import { PropsHook, StateHook, toConnectedHTMLComponent } from '../utils/component-factory';
import { MediaChromeButton } from './media-chrome-button';
export class MuteButtonBase extends MediaChromeButton {
_state:
| {
@@ -99,7 +96,7 @@ export const MuteButton = toConnectedHTMLComponent(
MuteButtonBase,
useMuteButtonState,
useMuteButtonProps,
'MuteButton',
'MuteButton'
);
// NOTE: In this architecture it will be important to decouple component class definitions from their registration in the CustomElementsRegistry. (CJP)
@@ -1,15 +1,10 @@
import {
toConnectedHTMLComponent,
StateHook,
PropsHook,
} from '../utils/component-factory';
import { MediaChromeButton } from './media-chrome-button';
import { playButtonStateDefinition } from '@vjs-10/media-store';
import { PropsHook, StateHook, toConnectedHTMLComponent } from '../utils/component-factory';
import { MediaChromeButton } from './media-chrome-button';
export class PlayButtonBase extends MediaChromeButton {
_state:
| { paused: boolean; requestPlay: () => void; requestPause: () => void }
| undefined;
_state: { paused: boolean; requestPlay: () => void; requestPause: () => void } | undefined;
constructor() {
super();
@@ -58,10 +53,7 @@ export const usePlayButtonState: StateHook<{ paused: boolean }> = {
* PlayButton props hook - equivalent to React's usePlayButtonProps
* Handles element attributes and properties based on state
*/
export const usePlayButtonProps: PropsHook<{ paused: boolean }> = (
state,
_element,
) => {
export const usePlayButtonProps: PropsHook<{ paused: boolean }> = (state, _element) => {
const baseProps: Record<string, any> = {
/** data attributes/props */
['data-paused']: state.paused,
@@ -87,7 +79,7 @@ export const PlayButton = toConnectedHTMLComponent(
PlayButtonBase,
usePlayButtonState,
usePlayButtonProps,
'PlayButton',
'PlayButton'
);
// NOTE: In this architecture it will be important to decouple component class definitions from their registration in the CustomElementsRegistry. (CJP)
@@ -1,10 +1,7 @@
import {
toConnectedHTMLComponent,
StateHook,
PropsHook,
} from '../utils/component-factory';
import { timeRangeStateDefinition } from '@vjs-10/media-store';
import { PropsHook, StateHook, toConnectedHTMLComponent } from '../utils/component-factory';
// Utility functions for pointer position and seek time calculations
const calculatePointerRatio = (clientX: number, rect: DOMRect): number => {
const x = clientX - rect.left;
@@ -15,10 +12,7 @@ const calculateSeekTimeFromRatio = (ratio: number, duration: number): number =>
return (ratio / 100) * duration;
};
const calculateSeekTimeFromPointerEvent = (
event: PointerEvent,
duration: number
): number => {
const calculateSeekTimeFromPointerEvent = (event: PointerEvent, duration: number): number => {
const rect = (event.target as HTMLElement).getBoundingClientRect();
const ratio = calculatePointerRatio(event.clientX, rect);
return calculateSeekTimeFromRatio(ratio, duration);
@@ -42,7 +36,7 @@ export class TimeRangeRootBase extends HTMLElement {
constructor() {
super();
// Add pointer event listeners
this.addEventListener('pointerdown', this);
this.addEventListener('pointermove', this);
@@ -80,7 +74,7 @@ export class TimeRangeRootBase extends HTMLElement {
this._dragging = true;
const seekTime = calculateSeekTimeFromPointerEvent(event, this._state!.duration);
this._state!.requestSeek(seekTime);
// Capture pointer events
this.setPointerCapture(event.pointerId);
}
@@ -100,7 +94,7 @@ export class TimeRangeRootBase extends HTMLElement {
private _handlePointerUp(event: PointerEvent) {
this.releasePointerCapture(event.pointerId);
if (this._dragging && this._trackElement && this._pointerPosition !== null) {
const seekTime = calculateSeekTimeFromRatio(this._pointerPosition, this._state!.duration);
this._state!.requestSeek(seekTime);
@@ -126,23 +120,23 @@ export class TimeRangeRootBase extends HTMLElement {
_update(props: any, state: any) {
this._state = state;
// Find track element
this._trackElement = this.querySelector('media-time-range-track') as HTMLElement;
// Calculate slider fill percentage
const sliderFill = this._dragging && this._pointerPosition !== null
? this._pointerPosition
: state.duration > 0
? (state.currentTime / state.duration) * 100
: 0;
const sliderFill =
this._dragging && this._pointerPosition !== null
? this._pointerPosition
: state.duration > 0
? (state.currentTime / state.duration) * 100
: 0;
// Update CSS custom properties
this.style.setProperty('--slider-fill', `${Math.round(sliderFill)}%`);
this.style.setProperty('--slider-pointer',
this._hovering && this._pointerPosition !== null
? `${Math.round(this._pointerPosition)}%`
: '0%'
this.style.setProperty(
'--slider-pointer',
this._hovering && this._pointerPosition !== null ? `${Math.round(this._pointerPosition)}%` : '0%'
);
// Update ARIA attributes
@@ -152,7 +146,7 @@ export class TimeRangeRootBase extends HTMLElement {
this.setAttribute('aria-valuemax', '100');
this.setAttribute('aria-valuenow', sliderFill.toString());
this.setAttribute('aria-valuetext', props['aria-valuetext'] || '');
// Update data attributes
this.setAttribute('data-current-time', state.currentTime.toString());
this.setAttribute('data-duration', state.duration.toString());
@@ -300,7 +294,7 @@ export const TimeRangeRoot = toConnectedHTMLComponent(
TimeRangeRootBase,
useTimeRangeRootState,
useTimeRangeRootProps,
'TimeRangeRoot',
'TimeRangeRoot'
);
/**
@@ -310,7 +304,7 @@ export const TimeRangeTrack = toConnectedHTMLComponent(
TimeRangeTrackBase,
{ keys: [], transform: () => ({}) },
useTimeRangeTrackProps,
'TimeRangeTrack',
'TimeRangeTrack'
);
/**
@@ -320,7 +314,7 @@ export const TimeRangeProgress = toConnectedHTMLComponent(
TimeRangeProgressBase,
{ keys: [], transform: () => ({}) },
useTimeRangeProgressProps,
'TimeRangeProgress',
'TimeRangeProgress'
);
/**
@@ -330,7 +324,7 @@ export const TimeRangePointer = toConnectedHTMLComponent(
TimeRangePointerBase,
{ keys: [], transform: () => ({}) },
useTimeRangePointerProps,
'TimeRangePointer',
'TimeRangePointer'
);
/**
@@ -340,7 +334,7 @@ export const TimeRangeThumb = toConnectedHTMLComponent(
TimeRangeThumbBase,
{ keys: [], transform: () => ({}) },
useTimeRangeThumbProps,
'TimeRangeThumb',
'TimeRangeThumb'
);
/**
@@ -1,10 +1,7 @@
import {
toConnectedHTMLComponent,
StateHook,
PropsHook,
} from '../utils/component-factory';
import { volumeRangeStateDefinition } from '@vjs-10/media-store';
import { PropsHook, StateHook, toConnectedHTMLComponent } from '../utils/component-factory';
export class VolumeRangeBase extends HTMLElement {
_state:
| {
@@ -119,7 +116,7 @@ export const VolumeRange = toConnectedHTMLComponent(
VolumeRangeBase,
useVolumeRangeState,
useVolumeRangeProps,
'VolumeRange',
'VolumeRange'
);
// NOTE: In this architecture it will be important to decouple component class definitions from their registration in the CustomElementsRegistry. (CJP)
+1 -1
View File
@@ -1,7 +1,7 @@
import { ConsumerMixin } from '@open-wc/context-protocol';
export function getTemplateHTML() {
return /* html */`
return /* html */ `
<slot name="media"></slot>
<slot></slot>
`;
@@ -26,7 +26,7 @@ export const toConnectedHTMLComponent = <TState = any>(
BaseClass: CustomElementConstructor,
stateHook: StateHook<TState>,
propsHook: PropsHook<TState>,
displayName?: string,
displayName?: string
) => {
const ConnectedComponent = class extends ConsumerMixin(BaseClass) {
static get observedAttributes(): string[] {
+1 -1
View File
@@ -10,4 +10,4 @@
"incremental": false
},
"exclude": ["dist", "node_modules"]
}
}
+1 -1
View File
@@ -6,4 +6,4 @@
},
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"]
}
}