refactor(packages): dry up core, html, and react UI architecture (#699)

This commit is contained in:
rahim
2026-03-03 23:27:53 -08:00
committed by GitHub
parent 8bdd4ce7fa
commit 1edeadefed
110 changed files with 1349 additions and 1359 deletions
@@ -12,9 +12,9 @@ import { TimeSliderRoot } from '../time-slider-root';
// --- Hoisted mock data (available inside vi.mock factories) ---
const { mockSliderHandle, mockTimeState, mockBufferState } = vi.hoisted(() => ({
mockSliderHandle: () => ({
interaction: {
const { mockSliderApi, mockTimeState, mockBufferState } = vi.hoisted(() => ({
mockSliderApi: () => ({
input: {
current: {
pointerPercent: 0,
dragPercent: 0,
@@ -52,7 +52,7 @@ const { mockSliderHandle, mockTimeState, mockBufferState } = vi.hoisted(() => ({
vi.mock('@videojs/core/dom', async (importOriginal) => {
const orig: Record<string, unknown> = await importOriginal();
return { ...orig, createSlider: vi.fn(mockSliderHandle) };
return { ...orig, createSlider: vi.fn(mockSliderApi) };
});
vi.mock('@videojs/store/react', () => ({
@@ -3,14 +3,14 @@
import { TimeSliderCore, TimeSliderDataAttrs } from '@videojs/core';
import { getTimeSliderCSSVars, logMissingFeature, selectBuffer, selectTime } from '@videojs/core/dom';
import { formatTime } from '@videojs/utils/time';
import { forwardRef, useRef, useState } from 'react';
import { forwardRef, useState } from 'react';
import { usePlayer } from '../../player/context';
import type { UIComponentProps } from '../../utils/types';
import { useLatestRef } from '../../utils/use-latest-ref';
import { renderElement } from '../../utils/use-render';
import { useSlider } from '../hooks/use-slider';
import { SliderProvider } from '../slider/slider-context';
import { SliderProvider } from '../slider/context';
const noopSeek = (): Promise<number> => Promise.resolve(0);
@@ -46,65 +46,38 @@ export const TimeSliderRoot = forwardRef<HTMLDivElement, TimeSliderRootProps>(
// Keep a ref to the latest media state for callbacks that fire outside the render cycle.
const mediaRef = useLatestRef(time && buffer ? { ...time, ...buffer } : null);
// Holds the target time (seconds) between commit and seek completion, preventing the slider
// from snapping back to the stale `currentTime` while the async seek settles.
const pendingSeekRef = useRef<number | null>(null);
const duration = time?.duration ?? 0;
const range = duration || 1;
const { state, cssVars, rootRef, thumbRef, rootProps, thumbProps } = useSlider<TimeSliderCore.State>({
computeState: (interaction) => {
computeState: (input) => {
core.setInput(input);
if (!time || !buffer) {
return core.getTimeState(
{ currentTime: 0, duration: 0, seeking: false, seek: noopSeek, buffered: [], seekable: [] },
interaction
);
core.setMedia({
currentTime: 0,
duration: 0,
seeking: false,
seek: noopSeek,
buffered: [],
seekable: [],
});
} else {
core.setMedia({ ...time, ...buffer });
}
const baseState = core.getTimeState({ ...time, ...buffer }, interaction);
// After drag release, `dragging` resets before the async seek completes. Hold the
// slider at the committed position until `currentTime` catches up.
const pending = pendingSeekRef.current;
if (!interaction.dragging && pending !== null) {
if (Math.abs(time.currentTime - pending) < 0.5) {
// Seek landed — clear pending.
pendingSeekRef.current = null;
} else {
const dur = time.duration || 1;
const fillPercent = (pending / dur) * 100;
return { ...baseState, value: pending, fillPercent };
}
}
return baseState;
return core.getState();
},
getPercent: () => (duration > 0 ? ((time?.currentTime ?? 0) / duration) * 100 : 0),
getStepPercent: () => (step / range) * 100,
getLargeStepPercent: () => (largeStep / range) * 100,
getStepPercent: () => core.getStepPercent(),
getLargeStepPercent: () => core.getLargeStepPercent(),
orientation,
disabled,
commitThrottle,
adjustPercent: (rawPercent, thumbSize, trackSize) =>
core.adjustPercentForAlignment(rawPercent, thumbSize, trackSize),
getCSSVars: getTimeSliderCSSVars,
onValueChange: (percent) => {
// Track the target position for visual hold during pointer interaction.
const media = mediaRef.current;
if (media) {
pendingSeekRef.current = (percent / 100) * (media.duration || 0);
}
},
onValueCommit: (percent) => {
const media = mediaRef.current;
if (!media) return;
const seconds = (percent / 100) * (media.duration || 0);
pendingSeekRef.current = seconds;
// seek() is async — catch rejection if the media target isn't attached yet.
media.seek(seconds).catch(() => {
pendingSeekRef.current = null;
});
if (media) media.seek(core.valueFromPercent(percent));
},
onDragStart,
onDragEnd,