feat: add a solution for React preview time display (#50)

This commit is contained in:
Wesley Luyten
2025-10-13 13:07:10 -05:00
committed by GitHub
parent 75a54e9b5e
commit f78b09fd16
16 changed files with 215 additions and 59 deletions
@@ -6,6 +6,7 @@ export interface TimeSliderState extends SliderState {
currentTime: number;
duration: number;
requestSeek: (time: number) => void;
requestPreview: (time: number) => void;
_currentTimeText: string;
_durationText: string;
}
@@ -70,10 +71,13 @@ export class TimeSlider extends Slider {
#handlePointerMove(event: PointerEvent) {
super.handleEvent(event);
const { _dragging, _pointerRatio, duration, requestSeek } = super.getState() as TimeSliderState;
const { _dragging, _pointerRatio, duration, requestSeek, requestPreview } = super.getState() as TimeSliderState;
const previewTime = _pointerRatio * duration;
requestPreview(previewTime);
if (_dragging) {
this.#seekingTime = _pointerRatio * duration;
this.#seekingTime = previewTime;
requestSeek(this.#seekingTime);
}
}
@@ -0,0 +1,36 @@
/**
* @fileoverview Preview time display component state definition
*
* This module provides the component state definition for preview time display
* components across HTML, React, and React Native platforms. The preview time
* display is a read-only component that shows the preview time when hovering
* over the time slider.
*/
/**
* State interface for preview time display components
*/
export interface PreviewTimeDisplayState {
/** The preview time value in seconds */
previewTime: number | undefined;
}
/**
* Preview time display component state definition following VJS-10 patterns.
* This provides a read-only display component that shows the preview time.
*/
export interface PreviewTimeDisplayStateDefinition {
keys: (keyof PreviewTimeDisplayState)[];
stateTransform: (rawState: any) => PreviewTimeDisplayState;
}
/**
* Preview time display state definition
* Defines the core state logic that can be shared between implementations
*/
export const previewTimeDisplayStateDefinition: PreviewTimeDisplayStateDefinition = {
keys: ['previewTime'],
stateTransform: (rawState: any) => ({
previewTime: rawState.previewTime ?? 0,
}),
};
@@ -1,10 +1,12 @@
export interface TimeSliderState {
currentTime: number;
duration: number;
previewTime: number;
}
export interface TimeSliderMethods {
requestSeek: (time: number) => void;
requestPreview: (time: number) => void;
}
export interface TimeSliderStateDefinition {
@@ -18,14 +20,18 @@ export interface TimeSliderStateDefinition {
* Defines the core state logic that can be shared between implementations
*/
export const timeSliderStateDefinition: TimeSliderStateDefinition = {
keys: ['currentTime', 'duration'],
keys: ['currentTime', 'duration', 'previewTime'],
stateTransform: (rawState: any) => ({
currentTime: rawState.currentTime ?? 0,
duration: rawState.duration ?? 0,
previewTime: rawState.previewTime ?? 0,
}),
createRequestMethods: dispatch => ({
requestSeek: (time: number) => {
dispatch({ type: 'seekrequest', detail: time });
},
requestPreview: (time: number) => {
dispatch({ type: 'previewrequest', detail: time });
},
}),
};
+16 -9
View File
@@ -41,6 +41,7 @@ export interface StateMediator {
currentTime: FacadeProp<HTMLMediaElement['currentTime']>;
duration: ReadonlyFacadeProp<HTMLMediaElement['duration']>;
seekable: ReadonlyFacadeProp<[number, number] | undefined>;
previewTime: FacadeProp<number>;
fullscreen: FacadeProp<boolean>;
}
@@ -58,7 +59,7 @@ export function createMediaStore({
}: {
media?: any;
container?: any;
stateMediator: Partial<StateMediator> & Pick<StateMediator, 'paused'>;
stateMediator: Partial<StateMediator>;
}): MediaStore {
const stateOwners: StateOwners = {};
const store = map<any>({});
@@ -117,16 +118,21 @@ export function createMediaStore({
} else if (type === 'containerstateownerchangerequest') {
updateStateOwners({ container: detail });
} else {
for (const stateObject of Object.values(stateMediator).filter(
(stateMediatorEntry): stateMediatorEntry is FacadeProp<any, any, any> => 'set' in stateMediatorEntry,
)) {
const { set, actions } = stateObject;
if (actions[type]) {
Object.entries(stateMediator).forEach(([stateName, stateObject]) => {
const { set, actions } = stateObject as FacadeProp<any, any, any>;
if (actions?.[type]) {
const actionFn = actions[type];
const actionValue = actionFn(action);
(set as FacadeSetter<any>)(actionValue, stateOwners);
if (set) {
(set as FacadeSetter<any>)(actionValue, stateOwners);
} else {
// If there is no setter, store the action value in the store.
// Might need revisiting.
store.setKey(stateName, actionValue);
}
}
}
});
}
},
@@ -155,9 +161,10 @@ export function createMediaStore({
};
}
function getInitialState(stateMediator: Partial<StateMediator> & Pick<StateMediator, 'paused'>, stateOwners: any) {
function getInitialState(stateMediator: Partial<StateMediator>, stateOwners: any) {
const initialState: any = {};
for (const [stateName, { get }] of Object.entries(stateMediator)) {
if (!get) continue;
initialState[stateName] = get(stateOwners);
}
return initialState;
+1
View File
@@ -3,6 +3,7 @@ export * from './component-state-definitions/duration-display';
export * from './component-state-definitions/fullscreen-button';
export * from './component-state-definitions/mute-button';
export * from './component-state-definitions/play-button';
export * from './component-state-definitions/preview-time-display';
export * from './component-state-definitions/time-slider';
export * from './component-state-definitions/volume-slider';
export * from './factory';
+2 -1
View File
@@ -4,11 +4,12 @@ import { createMediaStore as factory } from './factory';
import { audible } from './state-mediators/audible';
import { fullscreenable } from './state-mediators/fullscreenable';
import { playable } from './state-mediators/playable';
import { preview } from './state-mediators/preview';
import { temporal } from './state-mediators/temporal';
// Example of default media store with default state mediator definitions. (CJP)
// NOTE: We can also change the API to take an array of stateMediators (or either/both) (CJP)
const stateMediator = { ...playable, ...audible, ...temporal, ...fullscreenable };
const stateMediator = { ...playable, ...audible, ...temporal, ...fullscreenable, ...preview };
type Params = Partial<Parameters<typeof factory>[0]>;
@@ -1,4 +1,5 @@
export { audible } from './audible';
export { fullscreenable } from './fullscreenable';
export { playable } from './playable';
export { preview } from './preview';
export { temporal } from './temporal';
@@ -0,0 +1,7 @@
export const preview = {
previewTime: {
actions: {
previewrequest: ({ detail }: Pick<CustomEvent<any>, 'detail'> = { detail: 0 }): number => +detail,
},
},
};