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
@@ -4,6 +4,7 @@ import type { MediaBufferState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
export const bufferFeature = definePlayerFeature({
name: 'buffer',
state: (): MediaBufferState => ({
buffered: [],
seekable: [],
@@ -8,6 +8,7 @@ const IDLE_DELAY = 2000;
const TAP_THRESHOLD = 250;
export const controlsFeature = definePlayerFeature({
name: 'controls',
state: (): MediaControlsState => ({
userActive: true,
controlsVisible: true,
@@ -12,6 +12,7 @@ import { exitPiP, isPiPActive } from '../../presentation/pip';
import type { WebKitVideoElement } from '../../presentation/types';
export const fullscreenFeature = definePlayerFeature({
name: 'fullscreen',
state: ({ target }): MediaFullscreenState => ({
fullscreen: false,
fullscreenAvailability: 'unavailable',
@@ -7,6 +7,7 @@ import { enterPiP, exitPiP, isPiPActive, isPiPSupported } from '../../presentati
import type { WebKitVideoElement } from '../../presentation/types';
export const pipFeature = definePlayerFeature({
name: 'pip',
state: ({ target }): MediaPictureInPictureState => ({
pip: false,
pipAvailability: 'unavailable',
@@ -6,6 +6,7 @@ import { definePlayerFeature } from '../../feature';
const DEFAULT_RATES: readonly number[] = [1, 1.2, 1.5, 1.7, 2];
export const playbackRateFeature = definePlayerFeature({
name: 'playbackRate',
state: ({ target }): MediaPlaybackRateState => ({
playbackRates: DEFAULT_RATES,
playbackRate: 1,
@@ -4,6 +4,7 @@ import type { MediaPlaybackState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
export const playbackFeature = definePlayerFeature({
name: 'playback',
state: ({ target }): MediaPlaybackState => ({
paused: true,
ended: false,
@@ -4,6 +4,7 @@ import type { MediaSourceState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
export const sourceFeature = definePlayerFeature({
name: 'source',
state: ({ target, signals }): MediaSourceState => ({
source: null,
canPlay: false,
@@ -4,6 +4,7 @@ import type { MediaTextCue, MediaTextTrackState } from '../../../core/media/stat
import { definePlayerFeature } from '../../feature';
export const textTrackFeature = definePlayerFeature({
name: 'textTrack',
state: (): MediaTextTrackState => ({
chaptersCues: [],
thumbnailCues: [],
@@ -6,6 +6,7 @@ import { hasMetadata } from '../../media/predicate';
import { signalKeys } from '../signal-keys';
export const timeFeature = definePlayerFeature({
name: 'time',
state: ({ target, signals }): MediaTimeState => ({
currentTime: 0,
duration: 0,
@@ -4,6 +4,7 @@ import type { MediaFeatureAvailability, MediaVolumeState } from '../../../core/m
import { definePlayerFeature } from '../../feature';
export const volumeFeature = definePlayerFeature({
name: 'volume',
state: ({ target }): MediaVolumeState => ({
volume: 1,
muted: false,
+8 -8
View File
@@ -1,8 +1,8 @@
import type { State } from '@videojs/store';
import { listen } from '@videojs/utils/dom';
import type { PopoverInteraction } from '../../../core/ui/popover/popover-core';
import type { PopoverInput } from '../../../core/ui/popover/popover-core';
import type { UIFocusEvent, UIPointerEvent } from '../event';
import type { TransitionHandler } from '../transition';
import type { TransitionApi } from '../transition';
export type PopoverOpenChangeReason = 'click' | 'hover' | 'focus' | 'escape' | 'outside-click' | 'blur';
@@ -12,7 +12,7 @@ export interface PopoverChangeDetails {
}
export interface PopoverOptions {
transition: TransitionHandler;
transition: TransitionApi;
onOpenChange: (open: boolean, details: PopoverChangeDetails) => void;
/** Fires after open/close animations complete. */
onOpenChangeComplete?: (open: boolean) => void;
@@ -37,8 +37,8 @@ export interface PopoverPopupProps {
onFocusOut: (event: UIFocusEvent) => void;
}
export interface PopoverHandle {
interaction: State<PopoverInteraction>;
export interface PopoverApi {
input: State<PopoverInput>;
triggerProps: PopoverTriggerProps;
popupProps: PopoverPopupProps;
readonly triggerElement: HTMLElement | null;
@@ -49,7 +49,7 @@ export interface PopoverHandle {
destroy: () => void;
}
export function createPopover(options: PopoverOptions): PopoverHandle {
export function createPopover(options: PopoverOptions): PopoverApi {
const { transition, onOpenChange, closeOnEscape, closeOnOutsideClick } = options;
const state = transition.state;
@@ -299,7 +299,7 @@ export function createPopover(options: PopoverOptions): PopoverHandle {
popupEl = el;
if (el) {
// If the interaction is already open (e.g., React mount after state
// If the popover is already open (e.g., React mount after state
// change), show the popover now. In `applyOpen` the element may not
// have been in the DOM yet, so the earlier `tryShowPopover` was a no-op.
if (state.current.active) {
@@ -316,7 +316,7 @@ export function createPopover(options: PopoverOptions): PopoverHandle {
}
return {
interaction: state,
input: state,
triggerProps,
popupProps,
get triggerElement() {
@@ -1,10 +1,10 @@
import { vi } from 'vitest';
import { createTransitionHandler } from '../../transition';
import { createTransition } from '../../transition';
import { createPopover, type PopoverChangeDetails } from '../popover';
export function createTestPopover(overrides?: Partial<Parameters<typeof createPopover>[0]>) {
const onOpenChange = vi.fn<(open: boolean, details: PopoverChangeDetails) => void>();
const transition = overrides?.transition ?? createTransitionHandler();
const transition = overrides?.transition ?? createTransition();
const popover = createPopover({
transition,
onOpenChange,
@@ -5,16 +5,16 @@ import { createTestPopover } from './popover-helpers';
describe('createPopover', () => {
it('starts closed', () => {
const { popover } = createTestPopover();
expect(popover.interaction.current).toEqual({ active: false, status: 'idle' });
expect(popover.input.current).toEqual({ active: false, status: 'idle' });
});
describe('open/close', () => {
it('updates interaction state and calls onOpenChange when opening', () => {
it('updates input state and calls onOpenChange when opening', () => {
const { popover, onOpenChange } = createTestPopover();
popover.open();
expect(popover.interaction.current.active).toBe(true);
expect(popover.input.current.active).toBe(true);
expect(onOpenChange).toHaveBeenCalledWith(true, { reason: 'click' });
});
@@ -23,7 +23,7 @@ describe('createPopover', () => {
popover.open();
expect(popover.interaction.current).toEqual({ active: true, status: 'starting' });
expect(popover.input.current).toEqual({ active: true, status: 'starting' });
});
it('calls onOpenChange when closing', () => {
@@ -35,7 +35,7 @@ describe('createPopover', () => {
popover.close();
// active stays true until close animation completes
expect(popover.interaction.current.active).toBe(true);
expect(popover.input.current.active).toBe(true);
expect(onOpenChange).toHaveBeenCalledWith(false, { reason: 'click' });
});
@@ -45,7 +45,7 @@ describe('createPopover', () => {
popover.open();
popover.close();
expect(popover.interaction.current).toEqual({ active: true, status: 'ending' });
expect(popover.input.current).toEqual({ active: true, status: 'ending' });
});
it('does not call onOpenChange if already open', () => {
@@ -95,7 +95,7 @@ describe('createPopover', () => {
popover.triggerProps.onClick(event);
expect(popover.interaction.current.active).toBe(true);
expect(popover.input.current.active).toBe(true);
expect(onOpenChange).toHaveBeenCalledWith(true, expect.objectContaining({ reason: 'click' }));
});
@@ -108,7 +108,7 @@ describe('createPopover', () => {
popover.triggerProps.onClick({ preventDefault: vi.fn() } as unknown as UIEvent);
// active stays true until close animation completes
expect(popover.interaction.current.active).toBe(true);
expect(popover.input.current.active).toBe(true);
expect(onOpenChange).toHaveBeenCalledWith(false, expect.objectContaining({ reason: 'click' }));
});
@@ -122,8 +122,8 @@ describe('createPopover', () => {
// Click during close animation should re-open
popover.triggerProps.onClick({ preventDefault: vi.fn() } as unknown as UIEvent);
expect(popover.interaction.current.active).toBe(true);
expect(popover.interaction.current.status).not.toBe('ending');
expect(popover.input.current.active).toBe(true);
expect(popover.input.current.status).not.toBe('ending');
expect(onOpenChange).toHaveBeenCalledWith(true, expect.objectContaining({ reason: 'click' }));
});
});
@@ -156,7 +156,7 @@ describe('createPopover', () => {
popover.open();
expect(onOpenChange).not.toHaveBeenCalled();
expect(popover.interaction.current.active).toBe(false);
expect(popover.input.current.active).toBe(false);
});
});
@@ -165,13 +165,13 @@ describe('createPopover', () => {
const { popover } = createTestPopover();
const callback = vi.fn();
popover.interaction.subscribe(callback);
popover.input.subscribe(callback);
popover.open();
flush();
expect(callback).toHaveBeenCalled();
expect(popover.interaction.current.active).toBe(true);
expect(popover.input.current.active).toBe(true);
});
});
});
+17 -17
View File
@@ -3,7 +3,7 @@ import { listen } from '@videojs/utils/dom';
import { throttle } from '@videojs/utils/function';
import { clamp, roundToStep } from '@videojs/utils/number';
import { isNull } from '@videojs/utils/predicate';
import type { SliderInteraction } from '../../core/ui/slider/slider-core';
import type { SliderInput } from '../../core/ui/slider/slider-core';
import { getPercentFromPointerEvent } from '../utils/pointer';
import type { UIKeyboardEvent, UIPointerEvent } from './event';
@@ -50,8 +50,8 @@ export interface SliderThumbProps {
onBlur: () => void;
}
export interface SliderHandle {
interaction: State<SliderInteraction>;
export interface SliderApi {
input: State<SliderInput>;
rootProps: SliderRootProps;
thumbProps: SliderThumbProps;
destroy: () => void;
@@ -60,8 +60,8 @@ export interface SliderHandle {
/** Intentional drag threshold — number of pointermove events before drag starts. */
const DRAG_THRESHOLD = 2;
export function createSlider(options: SliderOptions): SliderHandle {
const state = createState<SliderInteraction>({
export function createSlider(options: SliderOptions): SliderApi {
const input = createState<SliderInput>({
pointerPercent: 0,
dragPercent: 0,
dragging: false,
@@ -97,10 +97,10 @@ export function createSlider(options: SliderOptions): SliderHandle {
function endDrag(): void {
if (!isDragging) {
state.patch({ pointing: false, pointerPercent: 0 });
input.patch({ pointing: false, pointerPercent: 0 });
} else {
isDragging = false;
state.patch({ dragging: false, pointing: false, pointerPercent: 0 });
input.patch({ dragging: false, pointing: false, pointerPercent: 0 });
options.onDragEnd?.();
}
@@ -128,17 +128,17 @@ export function createSlider(options: SliderOptions): SliderHandle {
if (!isDragging && moveCount >= DRAG_THRESHOLD) {
isDragging = true;
state.patch({ dragging: true, dragPercent: percent, pointerPercent: percent });
input.patch({ dragging: true, dragPercent: percent, pointerPercent: percent });
options.onDragStart?.();
options.onValueChange?.(percent);
throttledCommit?.(percent);
} else if (isDragging) {
state.patch({ dragPercent: percent, pointerPercent: percent });
input.patch({ dragPercent: percent, pointerPercent: percent });
options.onValueChange?.(percent);
throttledCommit?.(percent);
} else {
// Below drag threshold — update hover preview only.
state.patch({ pointerPercent: percent });
input.patch({ pointerPercent: percent });
}
}
@@ -180,7 +180,7 @@ export function createSlider(options: SliderOptions): SliderHandle {
const percent = getPercentFromPointerEvent(event, cachedRect, options.getOrientation(), cachedRTL);
state.patch({ pointing: true, pointerPercent: percent, dragPercent: percent });
input.patch({ pointing: true, pointerPercent: percent, dragPercent: percent });
options.onValueChange?.(percent);
// Focus the thumb for keyboard follow-up and screen reader tracking.
@@ -197,12 +197,12 @@ export function createSlider(options: SliderOptions): SliderHandle {
const rect = el.getBoundingClientRect();
const percent = getPercentFromPointerEvent(event, rect, options.getOrientation(), options.isRTL());
state.patch({ pointing: true, pointerPercent: percent });
input.patch({ pointing: true, pointerPercent: percent });
},
onPointerLeave() {
if (isDragging) return;
state.patch({ pointing: false, pointerPercent: 0 });
input.patch({ pointing: false, pointerPercent: 0 });
},
};
@@ -266,25 +266,25 @@ export function createSlider(options: SliderOptions): SliderHandle {
if (newPercent !== null) {
event.preventDefault();
newPercent = clamp(newPercent, 0, 100);
state.patch({ pointerPercent: newPercent, dragPercent: newPercent });
input.patch({ pointerPercent: newPercent, dragPercent: newPercent });
options.onValueChange?.(newPercent);
options.onValueCommit?.(newPercent);
}
},
onFocus() {
state.patch({ focused: true });
input.patch({ focused: true });
},
onBlur() {
state.patch({ focused: false });
input.patch({ focused: false });
},
};
listen(abort.signal, 'abort', cleanup, { once: true });
return {
interaction: state,
input,
rootProps,
thumbProps,
destroy() {
+31 -31
View File
@@ -95,12 +95,12 @@ describe('createSlider', () => {
});
describe('shape', () => {
it('returns interaction, rootProps, thumbProps, and destroy', () => {
it('returns state, rootProps, thumbProps, and destroy', () => {
const slider = createSlider(createOptions());
expect(slider.interaction).toBeDefined();
expect(slider.interaction.current).toBeDefined();
expect(slider.interaction.subscribe).toBeTypeOf('function');
expect(slider.input).toBeDefined();
expect(slider.input.current).toBeDefined();
expect(slider.input.subscribe).toBeTypeOf('function');
expect(slider.rootProps.onPointerDown).toBeTypeOf('function');
expect(slider.rootProps.onPointerMove).toBeTypeOf('function');
expect(slider.rootProps.onPointerLeave).toBeTypeOf('function');
@@ -112,10 +112,10 @@ describe('createSlider', () => {
slider.destroy();
});
it('has correct initial interaction state', () => {
it('has correct initial state', () => {
const slider = createSlider(createOptions());
expect(slider.interaction.current).toEqual({
expect(slider.input.current).toEqual({
pointerPercent: 0,
dragPercent: 0,
dragging: false,
@@ -135,8 +135,8 @@ describe('createSlider', () => {
slider.rootProps.onPointerDown(pointerEvent({ clientX: 100 }));
flush();
expect(slider.interaction.current.pointing).toBe(true);
expect(slider.interaction.current.pointerPercent).toBe(50);
expect(slider.input.current.pointing).toBe(true);
expect(slider.input.current.pointerPercent).toBe(50);
slider.destroy();
});
@@ -185,7 +185,7 @@ describe('createSlider', () => {
flush();
expect(onValueChange).not.toHaveBeenCalled();
expect(slider.interaction.current.pointing).toBe(false);
expect(slider.input.current.pointing).toBe(false);
slider.destroy();
});
@@ -202,13 +202,13 @@ describe('createSlider', () => {
// First move — below threshold
fireDocumentPointerMove({ clientX: 60 });
flush();
expect(slider.interaction.current.dragging).toBe(false);
expect(slider.input.current.dragging).toBe(false);
expect(onDragStart).not.toHaveBeenCalled();
// Second move — meets threshold
fireDocumentPointerMove({ clientX: 80 });
flush();
expect(slider.interaction.current.dragging).toBe(true);
expect(slider.input.current.dragging).toBe(true);
expect(onDragStart).toHaveBeenCalledOnce();
slider.destroy();
@@ -246,7 +246,7 @@ describe('createSlider', () => {
fireDocumentPointerMove({ clientX: 100 });
flush();
expect(slider.interaction.current.dragPercent).toBe(50);
expect(slider.input.current.dragPercent).toBe(50);
slider.destroy();
});
@@ -267,8 +267,8 @@ describe('createSlider', () => {
expect(onValueCommit).toHaveBeenCalledWith(50);
expect(onDragEnd).toHaveBeenCalled();
expect(slider.interaction.current.dragging).toBe(false);
expect(slider.interaction.current.pointing).toBe(false);
expect(slider.input.current.dragging).toBe(false);
expect(slider.input.current.pointing).toBe(false);
slider.destroy();
});
@@ -312,7 +312,7 @@ describe('createSlider', () => {
flush();
expect(onDragEnd).toHaveBeenCalled();
expect(slider.interaction.current.dragging).toBe(false);
expect(slider.input.current.dragging).toBe(false);
slider.destroy();
});
@@ -340,13 +340,13 @@ describe('createSlider', () => {
fireDocumentPointerMove({ clientX: 60 });
fireDocumentPointerMove({ clientX: 80 });
flush();
expect(slider.interaction.current.dragging).toBe(true);
expect(slider.input.current.dragging).toBe(true);
// Stale: buttons = 0, mouse pointer
fireDocumentPointerMove({ clientX: 100, buttons: 0, pointerType: 'mouse' });
flush();
expect(slider.interaction.current.dragging).toBe(false);
expect(slider.input.current.dragging).toBe(false);
expect(onDragEnd).toHaveBeenCalled();
slider.destroy();
@@ -374,13 +374,13 @@ describe('createSlider', () => {
fireDocumentPointerMove({ clientX: 60 });
fireDocumentPointerMove({ clientX: 80 });
flush();
expect(slider.interaction.current.dragging).toBe(true);
expect(slider.input.current.dragging).toBe(true);
// Touch with buttons=0 should NOT trigger stale drag detection
fireDocumentPointerMove({ clientX: 100, buttons: 0, pointerType: 'touch' });
flush();
expect(slider.interaction.current.dragging).toBe(true);
expect(slider.input.current.dragging).toBe(true);
expect(onDragEnd).not.toHaveBeenCalled();
slider.destroy();
@@ -395,8 +395,8 @@ describe('createSlider', () => {
slider.rootProps.onPointerMove(pointerEvent({ clientX: 60 }));
flush();
expect(slider.interaction.current.pointing).toBe(true);
expect(slider.interaction.current.pointerPercent).toBe(30);
expect(slider.input.current.pointing).toBe(true);
expect(slider.input.current.pointerPercent).toBe(30);
slider.destroy();
});
@@ -409,8 +409,8 @@ describe('createSlider', () => {
slider.rootProps.onPointerLeave(pointerEvent());
flush();
expect(slider.interaction.current.pointing).toBe(false);
expect(slider.interaction.current.pointerPercent).toBe(0);
expect(slider.input.current.pointing).toBe(false);
expect(slider.input.current.pointerPercent).toBe(0);
slider.destroy();
});
@@ -423,12 +423,12 @@ describe('createSlider', () => {
fireDocumentPointerMove({ clientX: 60 });
fireDocumentPointerMove({ clientX: 80 });
flush();
expect(slider.interaction.current.dragging).toBe(true);
expect(slider.input.current.dragging).toBe(true);
slider.rootProps.onPointerLeave(pointerEvent());
flush();
expect(slider.interaction.current.pointing).toBe(true);
expect(slider.input.current.pointing).toBe(true);
slider.destroy();
});
@@ -761,7 +761,7 @@ describe('createSlider', () => {
slider.thumbProps.onFocus();
flush();
expect(slider.interaction.current.focused).toBe(true);
expect(slider.input.current.focused).toBe(true);
slider.destroy();
});
@@ -773,7 +773,7 @@ describe('createSlider', () => {
slider.thumbProps.onBlur();
flush();
expect(slider.interaction.current.focused).toBe(false);
expect(slider.input.current.focused).toBe(false);
slider.destroy();
});
@@ -788,7 +788,7 @@ describe('createSlider', () => {
slider.rootProps.onPointerDown(pointerEvent({ clientY: 25 }));
flush();
expect(slider.interaction.current.pointerPercent).toBe(75);
expect(slider.input.current.pointerPercent).toBe(75);
slider.destroy();
});
@@ -800,7 +800,7 @@ describe('createSlider', () => {
slider.rootProps.onPointerDown(pointerEvent({ clientX: 50 }));
flush();
expect(slider.interaction.current.pointerPercent).toBe(25);
expect(slider.input.current.pointerPercent).toBe(25);
slider.destroy();
});
@@ -817,7 +817,7 @@ describe('createSlider', () => {
flush();
// Same result as vertical + LTR — RTL has no effect.
expect(slider.interaction.current.pointerPercent).toBe(75);
expect(slider.input.current.pointerPercent).toBe(75);
slider.destroy();
});
@@ -833,7 +833,7 @@ describe('createSlider', () => {
slider.rootProps.onPointerDown(pointerEvent({ clientX: 50 }));
flush();
expect(slider.interaction.current.pointerPercent).toBe(75);
expect(slider.input.current.pointerPercent).toBe(75);
slider.destroy();
});
@@ -1,15 +1,15 @@
import { describe, expect, it, vi } from 'vitest';
import { createTransitionHandler } from '../transition';
import { createTransition } from '../transition';
describe('createTransitionHandler', () => {
describe('createTransition', () => {
it('starts with idle state', () => {
const handler = createTransitionHandler();
const handler = createTransition();
expect(handler.state.current).toEqual({ active: false, status: 'idle' });
});
describe('open', () => {
it('patches open and starting status synchronously', () => {
const handler = createTransitionHandler();
const handler = createTransition();
handler.open();
@@ -17,7 +17,7 @@ describe('createTransitionHandler', () => {
});
it('transitions to idle after one RAF', async () => {
const handler = createTransitionHandler();
const handler = createTransition();
const promise = handler.open();
expect(handler.state.current.status).toBe('starting');
@@ -33,7 +33,7 @@ describe('createTransitionHandler', () => {
describe('close', () => {
it('patches ending status synchronously', () => {
const handler = createTransitionHandler();
const handler = createTransition();
const el = document.createElement('div');
// Open first
@@ -45,7 +45,7 @@ describe('createTransitionHandler', () => {
});
it('keeps open true during close animation', () => {
const handler = createTransitionHandler();
const handler = createTransition();
const el = document.createElement('div');
handler.open();
@@ -56,7 +56,7 @@ describe('createTransitionHandler', () => {
});
it('handles null element gracefully', async () => {
const handler = createTransitionHandler();
const handler = createTransition();
handler.open();
const promise = handler.close(null);
@@ -74,7 +74,7 @@ describe('createTransitionHandler', () => {
describe('cancel', () => {
it('resets status to idle', () => {
const handler = createTransitionHandler();
const handler = createTransition();
handler.open();
expect(handler.state.current.status).toBe('starting');
@@ -84,7 +84,7 @@ describe('createTransitionHandler', () => {
});
it('preserves open state', () => {
const handler = createTransitionHandler();
const handler = createTransition();
handler.open();
handler.cancel();
@@ -94,7 +94,7 @@ describe('createTransitionHandler', () => {
});
it('is a no-op when already idle', () => {
const handler = createTransitionHandler();
const handler = createTransition();
const callback = vi.fn();
handler.state.subscribe(callback);
@@ -107,7 +107,7 @@ describe('createTransitionHandler', () => {
describe('destroy', () => {
it('prevents further open calls from updating state', () => {
const handler = createTransitionHandler();
const handler = createTransition();
handler.destroy();
handler.open();
@@ -118,7 +118,7 @@ describe('createTransitionHandler', () => {
});
it('is idempotent', () => {
const handler = createTransitionHandler();
const handler = createTransition();
handler.destroy();
handler.destroy(); // should not throw
+2 -2
View File
@@ -8,7 +8,7 @@ export interface CreateThumbnailOptions {
onStateChange: () => void;
}
export interface ThumbnailHandle {
export interface ThumbnailApi {
readonly loading: boolean;
readonly error: boolean;
readonly naturalWidth: number;
@@ -19,7 +19,7 @@ export interface ThumbnailHandle {
destroy(): void;
}
export function createThumbnail(options: CreateThumbnailOptions): ThumbnailHandle {
export function createThumbnail(options: CreateThumbnailOptions): ThumbnailApi {
const { getContainer, getImg, onStateChange } = options;
const core = new ThumbnailCore();
const abort = new AbortController();
+2 -2
View File
@@ -2,7 +2,7 @@ import { createState, type State } from '@videojs/store';
import { noop } from '@videojs/utils/function';
import type { TransitionState } from '../../core/ui/transition';
export interface TransitionHandler {
export interface TransitionApi {
state: State<TransitionState>;
open(): Promise<void>;
close(el: HTMLElement | null): Promise<void>;
@@ -21,7 +21,7 @@ export interface TransitionHandler {
* element stays mounted), then after a double-RAF waits for
* `getAnimations()` to settle before patching `{ active: false, status: 'idle' }`.
*/
export function createTransitionHandler(): TransitionHandler {
export function createTransition(): TransitionApi {
const state = createState<TransitionState>({ active: false, status: 'idle' });
let destroyed = false;
+3 -1
View File
@@ -10,7 +10,9 @@ import { isFunction, isUndefined } from '@videojs/utils/predicate';
* - `undefined` removes the attribute
* - Other props are set as string attributes
*/
export function applyElementProps(element: HTMLElement, props: object, signal?: AbortSignal): void {
export function applyElementProps(element: HTMLElement, props: object, options?: { signal?: AbortSignal }): void {
const signal = options?.signal;
for (const [key, value] of Object.entries(props)) {
if (isFunction(value) && key.startsWith('on')) {
const event = key.slice(2).toLowerCase();