mirror of
https://github.com/zoriya/v10.git
synced 2026-08-15 10:23:32 +00:00
- Add container state owner to StateOwners type for fullscreen support - Rename mediaelementchangerequest to mediastateownerchangerequest - Add containerstateownerchangerequest event handling - Refactor updateStateOwners to support multiple state owners - Update all usage locations across HTML and React packages - Validated with Playwright testing on both demo applications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
138 lines
4.3 KiB
TypeScript
138 lines
4.3 KiB
TypeScript
'use client';
|
|
/** @TODO !!! Revisit for SSR (CJP) */
|
|
import type { Context, ReactNode } from 'react';
|
|
import React, { createContext, useContext, useMemo } from 'react';
|
|
import { createMediaStore } from '@vjs-10/media-store';
|
|
import { useSyncExternalStoreWithSelector } from './useSyncExternalStoreWithSelector.js';
|
|
|
|
const identity = (x?: any) => x;
|
|
|
|
/**
|
|
* @description The {@link https://react.dev/learn/passing-data-deeply-with-context#context-an-alternative-to-passing-props|React Context}
|
|
* used "under the hood" for media ui state updates, state change requests, and the hooks and providers that integrate with this context.
|
|
* It is unlikely that you will/should be using `MediaContext` directly.
|
|
*
|
|
* @see {@link MediaProvider}
|
|
* @see {@link useMediaDispatch}
|
|
* @see {@link useMediaSelector}
|
|
*/
|
|
export const MediaContext: Context<any | null> = createContext<any | null>(
|
|
null,
|
|
);
|
|
|
|
export const MediaProvider = ({ children }: { children: ReactNode }) => {
|
|
const value = useMemo(() => createMediaStore(), []);
|
|
// useEffect(() => {
|
|
// value?.dispatch({
|
|
// type: 'documentelementchangerequest',
|
|
// detail: globalThis.document,
|
|
// });
|
|
// return () => {
|
|
// value?.dispatch({
|
|
// type: 'documentelementchangerequest',
|
|
// detail: undefined,
|
|
// });
|
|
// };
|
|
// }, []);
|
|
return (
|
|
<MediaContext.Provider value={value}>{children}</MediaContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useMediaStore = () => {
|
|
const store = useContext(MediaContext);
|
|
return store;
|
|
};
|
|
|
|
export const useMediaDispatch = () => {
|
|
const store = useContext(MediaContext);
|
|
const dispatch = store?.dispatch ?? identity;
|
|
return (value: any) => {
|
|
return dispatch(value);
|
|
};
|
|
};
|
|
|
|
export const useMediaRef = () => {
|
|
const dispatch = useMediaDispatch();
|
|
return (mediaEl: any | null | undefined) => {
|
|
// NOTE: This should get invoked with `null` when using as a `ref` callback whenever
|
|
// the corresponding react media element instance (e.g. a `<video>`) is being removed.
|
|
/*
|
|
{ type: 'mediastateownerchangerequest', detail: media }
|
|
*/
|
|
dispatch({ type: 'mediastateownerchangerequest', detail: mediaEl });
|
|
};
|
|
};
|
|
|
|
export const refEquality = (a: any, b: any) => a === b;
|
|
|
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
|
|
/**
|
|
* Slightly modified version of React's shallowEqual, with optimizations for Arrays
|
|
* so we may treat them specifically as unequal if they are not a) both arrays
|
|
* or b) don't contain the same (shallowly compared) elements.
|
|
*/
|
|
export const shallowEqual = (objA: any, objB: any): boolean => {
|
|
// Using Object.is as a first pass, as it covers a lot of the "simple" cases that are
|
|
// more complex than strict equality and is a built-in. For discussion, see, e.g.:
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#description
|
|
if (Object.is(objA, objB)) {
|
|
return true;
|
|
}
|
|
|
|
// Since we've done an Object.is() check immediately above, we can safely assume non-objects (or null-valued objects)
|
|
// are not equal, so can early bail for those as well.
|
|
if (
|
|
typeof objA !== 'object' ||
|
|
objA === null ||
|
|
typeof objB !== 'object' ||
|
|
objB === null
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (Array.isArray(objA)) {
|
|
// Early "cheap" array compares
|
|
if (!Array.isArray(objB) || objA.length !== objB.length) return false;
|
|
// Shallow compare for arrays
|
|
return objA.some((vVal, i) => objB[i] === vVal);
|
|
}
|
|
|
|
const keysA = Object.keys(objA);
|
|
const keysB = Object.keys(objB);
|
|
|
|
if (keysA.length !== keysB.length) {
|
|
return false;
|
|
}
|
|
|
|
// Test for A's keys different from B.
|
|
for (let i = 0; i < keysA.length; i++) {
|
|
// NOTE: Since we've already guaranteed the keys list lengths are the same, we can safely cast to string here (CJP)
|
|
if (
|
|
!hasOwnProperty.call(objB, keysA[i] as string) ||
|
|
!Object.is(objA[keysA[i] as string], objB[keysA[i] as string])
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
export const useMediaSelector = <S = any,>(
|
|
selector: (state: any) => S,
|
|
equalityFn = refEquality,
|
|
) => {
|
|
const store = useContext(MediaContext);
|
|
const selectedState = useSyncExternalStoreWithSelector(
|
|
store?.subscribe ?? identity,
|
|
store?.getState ?? identity,
|
|
store?.getState ?? identity,
|
|
selector,
|
|
equalityFn,
|
|
) as S;
|
|
|
|
return selectedState;
|
|
};
|