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
@@ -16,12 +16,14 @@ export interface SliderContextValue {
formatValue?: ((value: number, type: 'current' | 'pointer') => string) | undefined;
}
const SliderContext = createContext<SliderContextValue | undefined>(undefined);
const SliderContext = createContext<SliderContextValue | null>(null);
export function SliderProvider({ value, children }: { value: SliderContextValue; children: React.ReactNode }) {
return <SliderContext.Provider value={value}>{children}</SliderContext.Provider>;
}
export function useSliderContext(): SliderContextValue | undefined {
return useContext(SliderContext);
export function useSliderContext(): SliderContextValue {
const ctx = useContext(SliderContext);
if (!ctx) throw new Error('Slider compound components must be used within a Slider.Root');
return ctx;
}
+6 -23
View File
@@ -1,35 +1,18 @@
'use client';
import type { SliderState } from '@videojs/core';
import type { ForwardedRef } from 'react';
import { forwardRef } from 'react';
import type { UIComponentProps } from '../../utils/types';
import { renderElement } from '../../utils/use-render';
import { useSliderContext } from './slider-context';
import { createContextPart } from '../create-context-part';
import { useSliderContext } from './context';
export interface SliderBufferProps extends UIComponentProps<'div', SliderState> {}
/** Displays the buffered range on the slider track. */
export const SliderBuffer = forwardRef(function SliderBuffer(
componentProps: SliderBufferProps,
forwardedRef: ForwardedRef<HTMLDivElement>
) {
const { render, className, style, ...elementProps } = componentProps;
const context = useSliderContext();
if (!context) return null;
return renderElement(
'div',
{ render, className, style },
{
state: context.state,
stateAttrMap: context.stateAttrMap,
ref: forwardedRef,
props: [elementProps],
}
);
export const SliderBuffer = createContextPart<SliderBufferProps, SliderState>({
displayName: 'SliderBuffer',
tag: 'div',
useContext: useSliderContext,
});
export namespace SliderBuffer {
+6 -23
View File
@@ -1,35 +1,18 @@
'use client';
import type { SliderState } from '@videojs/core';
import type { ForwardedRef } from 'react';
import { forwardRef } from 'react';
import type { UIComponentProps } from '../../utils/types';
import { renderElement } from '../../utils/use-render';
import { useSliderContext } from './slider-context';
import { createContextPart } from '../create-context-part';
import { useSliderContext } from './context';
export interface SliderFillProps extends UIComponentProps<'div', SliderState> {}
/** Displays the filled portion from start to the current value. */
export const SliderFill = forwardRef(function SliderFill(
componentProps: SliderFillProps,
forwardedRef: ForwardedRef<HTMLDivElement>
) {
const { render, className, style, ...elementProps } = componentProps;
const context = useSliderContext();
if (!context) return null;
return renderElement(
'div',
{ render, className, style },
{
state: context.state,
stateAttrMap: context.stateAttrMap,
ref: forwardedRef,
props: [elementProps],
}
);
export const SliderFill = createContextPart<SliderFillProps, SliderState>({
displayName: 'SliderFill',
tag: 'div',
useContext: useSliderContext,
});
export namespace SliderFill {
+7 -7
View File
@@ -8,7 +8,7 @@ import { forwardRef, useState } from 'react';
import type { UIComponentProps } from '../../utils/types';
import { renderElement } from '../../utils/use-render';
import { useSlider } from '../hooks/use-slider';
import { SliderProvider } from './slider-context';
import { SliderProvider } from './context';
export interface SliderRootProps extends UIComponentProps<'div', SliderCore.State>, SliderCore.Props {
onValueChange?: ((value: number) => void) | undefined;
@@ -44,9 +44,6 @@ export const SliderRoot = forwardRef(function SliderRoot(
const [core] = useState(() => new SliderCore());
core.setProps({ label, min, max, step, largeStep, orientation, disabled, thumbAlignment });
const { min: resolvedMin, max: resolvedMax, step: resolvedStep, largeStep: resolvedLargeStep } = core.props;
const range = resolvedMax - resolvedMin || 1;
const {
state,
cssVars,
@@ -55,10 +52,13 @@ export const SliderRoot = forwardRef(function SliderRoot(
rootProps,
thumbProps,
} = useSlider({
computeState: (interaction) => core.getState(interaction, value),
computeState: (input) => {
core.setInput(input);
return core.getSliderState(value);
},
getPercent: () => core.percentFromValue(value),
getStepPercent: () => (resolvedStep / range) * 100,
getLargeStepPercent: () => (resolvedLargeStep / range) * 100,
getStepPercent: () => core.getStepPercent(),
getLargeStepPercent: () => core.getLargeStepPercent(),
orientation,
disabled,
adjustPercent: (rawPercent, thumbSize, trackSize) =>
@@ -6,7 +6,7 @@ import { forwardRef } from 'react';
import type { UIComponentProps } from '../../utils/types';
import { renderElement } from '../../utils/use-render';
import { useSliderContext } from './slider-context';
import { useSliderContext } from './context';
export interface SliderThumbProps extends UIComponentProps<'div', SliderState> {}
@@ -18,8 +18,6 @@ export const SliderThumb = forwardRef(function SliderThumb(
const { render, className, style, ...elementProps } = componentProps;
const context = useSliderContext();
if (!context) return null;
const { state, thumbRef, thumbProps, getAttrs } = context;
const attrs = getAttrs(state);
+6 -23
View File
@@ -1,35 +1,18 @@
'use client';
import type { SliderState } from '@videojs/core';
import type { ForwardedRef } from 'react';
import { forwardRef } from 'react';
import type { UIComponentProps } from '../../utils/types';
import { renderElement } from '../../utils/use-render';
import { useSliderContext } from './slider-context';
import { createContextPart } from '../create-context-part';
import { useSliderContext } from './context';
export interface SliderTrackProps extends UIComponentProps<'div', SliderState> {}
/** Contains the slider's visual track and interactive hit zone. */
export const SliderTrack = forwardRef(function SliderTrack(
componentProps: SliderTrackProps,
forwardedRef: ForwardedRef<HTMLDivElement>
) {
const { render, className, style, ...elementProps } = componentProps;
const context = useSliderContext();
if (!context) return null;
return renderElement(
'div',
{ render, className, style },
{
state: context.state,
stateAttrMap: context.stateAttrMap,
ref: forwardedRef,
props: [elementProps],
}
);
export const SliderTrack = createContextPart<SliderTrackProps, SliderState>({
displayName: 'SliderTrack',
tag: 'div',
useContext: useSliderContext,
});
export namespace SliderTrack {
@@ -6,7 +6,7 @@ import { forwardRef } from 'react';
import type { UIComponentProps } from '../../utils/types';
import { renderElement } from '../../utils/use-render';
import { useSliderContext } from './slider-context';
import { useSliderContext } from './context';
export interface SliderValueProps extends UIComponentProps<'output', SliderState> {
/** Which slider value to display: the current position or the pointer position. */
@@ -23,8 +23,6 @@ export const SliderValue = forwardRef(function SliderValue(
const { render, className, style, type = 'current', format, ...elementProps } = componentProps;
const context = useSliderContext();
if (!context) return null;
const { state, pointerValue, formatValue } = context;
const rawValue = type === 'pointer' ? pointerValue : state.value;
@@ -9,9 +9,9 @@ import { SliderThumb } from '../slider-thumb';
import { SliderTrack } from '../slider-track';
import { SliderValue } from '../slider-value';
const { mockSliderHandle } = vi.hoisted(() => ({
mockSliderHandle: () => ({
interaction: {
const { mockSliderApi } = vi.hoisted(() => ({
mockSliderApi: () => ({
input: {
current: {
pointerPercent: 0,
dragPercent: 0,
@@ -37,7 +37,7 @@ const { mockSliderHandle } = 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', () => ({
@@ -97,10 +97,8 @@ describe('SliderTrack', () => {
expect(container.querySelector('[data-testid="track"]')).toBeTruthy();
});
it('returns null outside of SliderRoot', () => {
const { container } = render(<SliderTrack />);
expect(container.firstElementChild).toBeNull();
it('throws outside of SliderRoot', () => {
expect(() => render(<SliderTrack />)).toThrow('Slider compound components must be used within a Slider.Root');
});
it('forwards ref', () => {
@@ -126,10 +124,8 @@ describe('SliderFill', () => {
expect(container.querySelector('[data-testid="fill"]')).toBeTruthy();
});
it('returns null outside of SliderRoot', () => {
const { container } = render(<SliderFill />);
expect(container.firstElementChild).toBeNull();
it('throws outside of SliderRoot', () => {
expect(() => render(<SliderFill />)).toThrow('Slider compound components must be used within a Slider.Root');
});
});
@@ -144,10 +140,8 @@ describe('SliderBuffer', () => {
expect(container.querySelector('[data-testid="buffer"]')).toBeTruthy();
});
it('returns null outside of SliderRoot', () => {
const { container } = render(<SliderBuffer />);
expect(container.firstElementChild).toBeNull();
it('throws outside of SliderRoot', () => {
expect(() => render(<SliderBuffer />)).toThrow('Slider compound components must be used within a Slider.Root');
});
});
@@ -162,10 +156,8 @@ describe('SliderThumb', () => {
expect(container.querySelector('[data-testid="thumb"]')).toBeTruthy();
});
it('returns null outside of SliderRoot', () => {
const { container } = render(<SliderThumb />);
expect(container.firstElementChild).toBeNull();
it('throws outside of SliderRoot', () => {
expect(() => render(<SliderThumb />)).toThrow('Slider compound components must be used within a Slider.Root');
});
it('forwards ref', () => {
@@ -203,10 +195,8 @@ describe('SliderValue', () => {
expect(el?.tagName).toBe('OUTPUT');
});
it('returns null outside of SliderRoot', () => {
const { container } = render(<SliderValue />);
expect(container.firstElementChild).toBeNull();
it('throws outside of SliderRoot', () => {
expect(() => render(<SliderValue />)).toThrow('Slider compound components must be used within a Slider.Root');
});
it('displays rounded value by default', () => {