mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor(react): replace prototype-walking and inferred class props (#1376)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -67,13 +67,11 @@ export { Time } from './ui/time';
|
||||
export { TimeSlider } from './ui/time-slider';
|
||||
export { Tooltip, type TooltipContextValue, useTooltipContext } from './ui/tooltip';
|
||||
export { VolumeSlider } from './ui/volume-slider';
|
||||
|
||||
// Media utilities
|
||||
export { attachMediaElement } from './utils/attach-media-element';
|
||||
export { mediaProps } from './utils/media-props';
|
||||
// Utilities
|
||||
export { mergeProps } from './utils/merge-props';
|
||||
export type { HTMLProps, RenderFunction, RenderProp, UIComponentProps } from './utils/types';
|
||||
// Media utilities
|
||||
export { useAttachMedia } from './utils/use-attach-media';
|
||||
export { composeRefs, useComposedRefs } from './utils/use-composed-refs';
|
||||
export { useDestroy } from './utils/use-destroy';
|
||||
export { useLatestRef } from './utils/use-latest-ref';
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { DashMedia } from '@videojs/core/dom/media/dash';
|
||||
import type { InferClassProps } from '@videojs/utils/types';
|
||||
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
|
||||
import type { DashMediaProps } from '@videojs/core/dom/media/dash';
|
||||
import { DashMedia, dashMediaDefaultProps } from '@videojs/core/dom/media/dash';
|
||||
import type { ReactNode, VideoHTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { attachMediaElement } from '../../utils/attach-media-element';
|
||||
import { mediaProps } from '../../utils/media-props';
|
||||
import { useAttachMedia } from '../../utils/use-attach-media';
|
||||
import { useComposedRefs } from '../../utils/use-composed-refs';
|
||||
import { useMediaInstance } from '../../utils/use-media-instance';
|
||||
import { useSyncProps } from '../../utils/use-sync-props';
|
||||
|
||||
export type DashVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>> &
|
||||
InferClassProps<typeof DashMedia>;
|
||||
export interface DashVideoProps
|
||||
extends Omit<VideoHTMLAttributes<HTMLVideoElement>, keyof DashMediaProps>,
|
||||
Partial<DashMediaProps> {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const DashVideo = forwardRef<HTMLVideoElement, DashVideoProps>(function DashVideo({ children, ...props }, ref) {
|
||||
const mediaApi = useMediaInstance(DashMedia);
|
||||
|
||||
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
|
||||
const media = useMediaInstance(DashMedia);
|
||||
const attachRef = useAttachMedia(media);
|
||||
const composedRef = useComposedRefs(attachRef, ref);
|
||||
const htmlProps = useSyncProps(media, props, dashMediaDefaultProps);
|
||||
|
||||
return (
|
||||
<video ref={composedRef} {...mediaProps(mediaApi, DashMedia, props)}>
|
||||
<video ref={composedRef} {...htmlProps}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { HlsMedia } from '@videojs/core/dom/media/hls';
|
||||
import type { InferClassProps } from '@videojs/utils/types';
|
||||
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
|
||||
import type { HlsMediaProps } from '@videojs/core/dom/media/hls';
|
||||
import { HlsMedia, hlsMediaDefaultProps } from '@videojs/core/dom/media/hls';
|
||||
import type { ReactNode, VideoHTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { attachMediaElement } from '../../utils/attach-media-element';
|
||||
import { mediaProps } from '../../utils/media-props';
|
||||
import { useAttachMedia } from '../../utils/use-attach-media';
|
||||
import { useComposedRefs } from '../../utils/use-composed-refs';
|
||||
import { useMediaInstance } from '../../utils/use-media-instance';
|
||||
import { useSyncProps } from '../../utils/use-sync-props';
|
||||
|
||||
export type HlsVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>> & InferClassProps<typeof HlsMedia>;
|
||||
export interface HlsVideoProps
|
||||
extends Omit<VideoHTMLAttributes<HTMLVideoElement>, keyof HlsMediaProps>,
|
||||
Partial<HlsMediaProps> {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const HlsVideo = forwardRef<HTMLVideoElement, HlsVideoProps>(function HlsVideo({ children, ...props }, ref) {
|
||||
const mediaApi = useMediaInstance(HlsMedia);
|
||||
|
||||
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
|
||||
const media = useMediaInstance(HlsMedia);
|
||||
const attachRef = useAttachMedia(media);
|
||||
const composedRef = useComposedRefs(attachRef, ref);
|
||||
const htmlProps = useSyncProps(media, props, hlsMediaDefaultProps);
|
||||
|
||||
return (
|
||||
<video ref={composedRef} {...mediaProps(mediaApi, HlsMedia, props)}>
|
||||
<video ref={composedRef} {...htmlProps}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
import { MuxAudioMedia } from '@videojs/core/dom/media/mux';
|
||||
import type { InferClassProps } from '@videojs/utils/types';
|
||||
import type { AudioHTMLAttributes, PropsWithChildren } from 'react';
|
||||
'use client';
|
||||
|
||||
import type { MuxMediaProps } from '@videojs/core/dom/media/mux';
|
||||
import { MuxAudioMedia, muxMediaDefaultProps } from '@videojs/core/dom/media/mux';
|
||||
import type { AudioHTMLAttributes, ReactNode } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { attachMediaElement } from '../../utils/attach-media-element';
|
||||
import { mediaProps } from '../../utils/media-props';
|
||||
import { useAttachMedia } from '../../utils/use-attach-media';
|
||||
import { useComposedRefs } from '../../utils/use-composed-refs';
|
||||
import { useMediaInstance } from '../../utils/use-media-instance';
|
||||
import { useSyncProps } from '../../utils/use-sync-props';
|
||||
|
||||
export type MuxAudioProps = PropsWithChildren<AudioHTMLAttributes<HTMLAudioElement>> &
|
||||
InferClassProps<typeof MuxAudioMedia>;
|
||||
export interface MuxAudioProps
|
||||
extends Omit<AudioHTMLAttributes<HTMLAudioElement>, keyof MuxMediaProps>,
|
||||
Partial<MuxMediaProps> {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const MuxAudio = forwardRef<HTMLAudioElement, MuxAudioProps>(({ children, ...props }, ref) => {
|
||||
const mediaApi = useMediaInstance(MuxAudioMedia);
|
||||
|
||||
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
|
||||
export const MuxAudio = forwardRef<HTMLAudioElement, MuxAudioProps>(function MuxAudio({ children, ...props }, ref) {
|
||||
const media = useMediaInstance(MuxAudioMedia);
|
||||
const attachRef = useAttachMedia(media);
|
||||
const composedRef = useComposedRefs(attachRef, ref);
|
||||
const htmlProps = useSyncProps(media, props, muxMediaDefaultProps);
|
||||
|
||||
return (
|
||||
<audio ref={composedRef} {...mediaProps(mediaApi, MuxAudioMedia, props)}>
|
||||
<audio ref={composedRef} {...htmlProps}>
|
||||
{children}
|
||||
</audio>
|
||||
);
|
||||
});
|
||||
|
||||
export default MuxAudio;
|
||||
export namespace MuxAudio {
|
||||
export type Props = MuxAudioProps;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { MuxVideoMedia } from '@videojs/core/dom/media/mux';
|
||||
import type { InferClassProps } from '@videojs/utils/types';
|
||||
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
|
||||
import type { MuxMediaProps } from '@videojs/core/dom/media/mux';
|
||||
import { MuxVideoMedia, muxMediaDefaultProps } from '@videojs/core/dom/media/mux';
|
||||
import type { ReactNode, VideoHTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { attachMediaElement } from '../../utils/attach-media-element';
|
||||
import { mediaProps } from '../../utils/media-props';
|
||||
import { useAttachMedia } from '../../utils/use-attach-media';
|
||||
import { useComposedRefs } from '../../utils/use-composed-refs';
|
||||
import { useMediaInstance } from '../../utils/use-media-instance';
|
||||
import { useSyncProps } from '../../utils/use-sync-props';
|
||||
|
||||
export type MuxVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>> &
|
||||
InferClassProps<typeof MuxVideoMedia>;
|
||||
export interface MuxVideoProps
|
||||
extends Omit<VideoHTMLAttributes<HTMLVideoElement>, keyof MuxMediaProps>,
|
||||
Partial<MuxMediaProps> {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const MuxVideo = forwardRef<HTMLVideoElement, MuxVideoProps>(function MuxVideo({ children, ...props }, ref) {
|
||||
const mediaApi = useMediaInstance(MuxVideoMedia);
|
||||
|
||||
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
|
||||
const media = useMediaInstance(MuxVideoMedia);
|
||||
const attachRef = useAttachMedia(media);
|
||||
const composedRef = useComposedRefs(attachRef, ref);
|
||||
const htmlProps = useSyncProps(media, props, muxMediaDefaultProps);
|
||||
|
||||
return (
|
||||
<video ref={composedRef} {...mediaProps(mediaApi, MuxVideoMedia, props)}>
|
||||
<video ref={composedRef} {...htmlProps}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
'use client';
|
||||
|
||||
import { NativeHlsMedia } from '@videojs/core/dom/media/native-hls';
|
||||
import type { InferClassProps } from '@videojs/utils/types';
|
||||
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
|
||||
import type { NativeHlsMediaProps } from '@videojs/core/dom/media/native-hls';
|
||||
import { NativeHlsMedia, nativeHlsMediaDefaultProps } from '@videojs/core/dom/media/native-hls';
|
||||
import type { ReactNode, VideoHTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { attachMediaElement } from '../../utils/attach-media-element';
|
||||
import { mediaProps } from '../../utils/media-props';
|
||||
import { useAttachMedia } from '../../utils/use-attach-media';
|
||||
import { useComposedRefs } from '../../utils/use-composed-refs';
|
||||
import { useMediaInstance } from '../../utils/use-media-instance';
|
||||
import { useSyncProps } from '../../utils/use-sync-props';
|
||||
|
||||
export type NativeHlsVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>> &
|
||||
InferClassProps<typeof NativeHlsMedia>;
|
||||
export interface NativeHlsVideoProps
|
||||
extends Omit<VideoHTMLAttributes<HTMLVideoElement>, keyof NativeHlsMediaProps>,
|
||||
Partial<NativeHlsMediaProps> {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const NativeHlsVideo = forwardRef<HTMLVideoElement, NativeHlsVideoProps>(function NativeHlsVideo(
|
||||
{ children, ...props },
|
||||
ref
|
||||
) {
|
||||
const mediaApi = useMediaInstance(NativeHlsMedia);
|
||||
|
||||
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
|
||||
const media = useMediaInstance(NativeHlsMedia);
|
||||
const attachRef = useAttachMedia(media);
|
||||
const composedRef = useComposedRefs(attachRef, ref);
|
||||
const htmlProps = useSyncProps(media, props, nativeHlsMediaDefaultProps);
|
||||
|
||||
return (
|
||||
<video ref={composedRef} {...mediaProps(mediaApi, NativeHlsMedia, props)}>
|
||||
<video ref={composedRef} {...htmlProps}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
'use client';
|
||||
|
||||
import { SimpleHlsMedia } from '@videojs/core/dom/media/simple-hls';
|
||||
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
|
||||
import type { SpfMediaProps } from '@videojs/spf/dom';
|
||||
import { spfMediaDefaultProps } from '@videojs/spf/dom';
|
||||
import type { ReactNode, VideoHTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { attachMediaElement } from '../../utils/attach-media-element';
|
||||
import { mediaProps } from '../../utils/media-props';
|
||||
import { useAttachMedia } from '../../utils/use-attach-media';
|
||||
import { useComposedRefs } from '../../utils/use-composed-refs';
|
||||
import { useMediaInstance } from '../../utils/use-media-instance';
|
||||
import { useSyncProps } from '../../utils/use-sync-props';
|
||||
|
||||
export type SimpleHlsVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>>;
|
||||
export interface SimpleHlsVideoProps
|
||||
extends Omit<VideoHTMLAttributes<HTMLVideoElement>, keyof SpfMediaProps>,
|
||||
Partial<SpfMediaProps> {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const SimpleHlsVideo = forwardRef<HTMLVideoElement, SimpleHlsVideoProps>(function SimpleHlsVideo(
|
||||
{ children, ...props },
|
||||
ref
|
||||
) {
|
||||
const mediaApi = useMediaInstance(SimpleHlsMedia);
|
||||
|
||||
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
|
||||
const media = useMediaInstance(SimpleHlsMedia);
|
||||
const attachRef = useAttachMedia(media);
|
||||
const composedRef = useComposedRefs(attachRef, ref);
|
||||
const htmlProps = useSyncProps(media, props, spfMediaDefaultProps);
|
||||
|
||||
return (
|
||||
<video ref={composedRef} {...mediaProps(mediaApi, SimpleHlsMedia, props)}>
|
||||
<video ref={composedRef} {...htmlProps}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
interface Attachable {
|
||||
attach(target: EventTarget): void;
|
||||
detach(): void;
|
||||
}
|
||||
|
||||
export function attachMediaElement<T extends HTMLVideoElement>(media: Attachable): (element: T | null) => void {
|
||||
return (element: T | null) => {
|
||||
if (element) {
|
||||
media.attach(element);
|
||||
} else {
|
||||
media.detach();
|
||||
}
|
||||
// React 19+ accepts a cleanup function as the return value
|
||||
return () => media.detach();
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { Media } from '@videojs/core';
|
||||
|
||||
type AnyClass = abstract new (...args: any[]) => any;
|
||||
|
||||
function getSettableProps(DelegateClass: AnyClass): Set<string> {
|
||||
const props = new Set<string>();
|
||||
for (let proto = DelegateClass.prototype; proto && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
|
||||
for (const key of Object.getOwnPropertyNames(proto)) {
|
||||
const desc = Object.getOwnPropertyDescriptor(proto, key);
|
||||
if (desc?.set) props.add(key);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
export function mediaProps(media: Media, DelegateClass: AnyClass, props: Record<string, any>) {
|
||||
const delegateKeys = getSettableProps(DelegateClass);
|
||||
const rest: Record<string, any> = {};
|
||||
|
||||
for (const key of Object.keys(props)) {
|
||||
if (delegateKeys.has(key)) {
|
||||
const value = props[key];
|
||||
if ((media as any)[key] !== value) {
|
||||
(media as any)[key] = value;
|
||||
}
|
||||
} else {
|
||||
rest[key] = props[key];
|
||||
}
|
||||
}
|
||||
|
||||
return rest;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import type { MediaEngineHost } from '@videojs/core';
|
||||
import type { RefCallback } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function useAttachMedia<T extends HTMLMediaElement>(media: MediaEngineHost): RefCallback<T> {
|
||||
return useCallback(
|
||||
(element: T | null) => {
|
||||
if (element) media.attach?.(element);
|
||||
else media.detach?.();
|
||||
return () => media.detach?.();
|
||||
},
|
||||
[media]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { isUndefined } from '@videojs/utils/predicate';
|
||||
|
||||
export function useSyncProps<Props extends object, Rest extends Record<string, unknown>>(
|
||||
target: Props,
|
||||
props: Partial<Props> & Rest,
|
||||
defaults: Props
|
||||
): Omit<Rest, keyof Props> {
|
||||
const rest: Record<string, unknown> = {};
|
||||
|
||||
for (const key in props) {
|
||||
if (key in defaults) {
|
||||
const value = isUndefined(props[key]) ? (defaults as Record<string, unknown>)[key] : props[key];
|
||||
if (target[key as keyof typeof target] !== value) target[key as keyof typeof target] = value as any;
|
||||
} else {
|
||||
rest[key] = props[key];
|
||||
}
|
||||
}
|
||||
|
||||
return rest as Omit<Rest, keyof Props>;
|
||||
}
|
||||
Reference in New Issue
Block a user