-
Notifications
You must be signed in to change notification settings - Fork 71
Expand file tree
/
Copy pathcontext.tsx
More file actions
160 lines (140 loc) · 5.56 KB
/
Copy pathcontext.tsx
File metadata and controls
160 lines (140 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
'use client';
import type { Media } from '@videojs/core';
import type { MediaContainer, PopupGroup } from '@videojs/core/dom';
import type { UnknownState, UnknownStore } from '@videojs/store';
import { useStore } from '@videojs/store/react';
import type { Dispatch, HTMLAttributes, ReactNode, PointerEvent as ReactPointerEvent, SetStateAction } from 'react';
import { createContext, forwardRef, useContext, useEffect, useRef } from 'react';
import { useComposedRefs } from '../utils/use-composed-refs';
export interface PlayerContextValue {
store: UnknownStore;
media: Media | null;
setMedia: Dispatch<SetStateAction<Media | null>>;
container: MediaContainer | null;
setContainer: Dispatch<SetStateAction<HTMLElement | null>>;
popupGroup?: PopupGroup;
}
const PlayerContext = createContext<PlayerContextValue | null>(null);
const EMPTY_UNSUBSCRIBE = () => {};
const EMPTY_STORE = {
state: {} as UnknownState,
subscribe: () => EMPTY_UNSUBSCRIBE,
} as Pick<UnknownStore, 'state' | 'subscribe'>;
export function PlayerContextProvider({
value,
children,
}: {
value: PlayerContextValue;
children: ReactNode;
}): ReactNode {
return <PlayerContext.Provider value={value}>{children}</PlayerContext.Provider>;
}
/** Access the full player context value. Throws if used outside a Player Provider. */
export function usePlayerContext(): PlayerContextValue {
const ctx = useContext(PlayerContext);
if (!ctx) throw new Error('usePlayerContext must be used within a Player Provider');
return ctx;
}
/**
* Access the player store from within a Player Provider.
*
* This standalone hook has no knowledge of your configured features, so it
* returns an untyped `UnknownStore` whose state properties are typed as
* `unknown`. For typed access, use the `usePlayer` returned by `createPlayer()`,
* or pass a premade selector to recover the type from its return value.
*
* @label Without Selector
*/
export function usePlayer(): UnknownStore;
/**
* Select a value from the player store. Re-renders when the selected value changes.
*
* The selector receives `UnknownState`, so an inline selector returns `unknown`.
* Pass a premade selector (e.g. `selectPlayback`) to get a typed result.
*
* @label With Selector
* @param selector - Derives a value from the player store state.
*/
export function usePlayer<R>(selector: (state: UnknownState) => R): R;
export function usePlayer<R>(selector?: (state: UnknownState) => R) {
const { store } = usePlayerContext();
return useStore(store, selector as any);
}
/**
* Access player state when available, but return `undefined` outside Provider.
*
* This is useful for components that can operate without player context
* (e.g. they accept fully explicit props as a fallback).
*/
/** @label Without Selector */
export function useOptionalPlayer(): UnknownStore | undefined;
/** @label With Selector */
export function useOptionalPlayer<R>(selector: (state: UnknownState) => R): R | undefined;
export function useOptionalPlayer<R>(selector?: (state: UnknownState) => R) {
const ctx = useContext(PlayerContext);
const store = (ctx?.store ?? (EMPTY_STORE as unknown as UnknownStore)) as UnknownStore;
const value = useStore(store, (ctx ? selector : undefined) as any);
return ctx ? value : undefined;
}
/** Access the media element from within a Player Provider. */
export function useMedia(): Media | null {
const { media } = usePlayerContext();
return media;
}
/** Access the container element from within a Player Provider. */
export function useContainer(): MediaContainer | null {
const { container } = usePlayerContext();
return container;
}
/** Access the container element when a Player Provider is available. */
export function useOptionalContainer(): MediaContainer | null {
const ctx = useContext(PlayerContext);
return ctx?.container ?? null;
}
/** Access the interactive popup group when a Player Provider is available. */
export function useOptionalPopupGroup(): PopupGroup | undefined {
const ctx = useContext(PlayerContext);
return ctx?.popupGroup;
}
/** Access the media attach setter for connecting a media element to the player. */
export function useMediaAttach(): Dispatch<SetStateAction<Media | null>> | undefined {
const ctx = useContext(PlayerContext);
return ctx?.setMedia;
}
/** Access the container attach setter for connecting a container element to the player. */
export function useContainerAttach(): Dispatch<SetStateAction<HTMLElement | null>> | undefined {
const ctx = useContext(PlayerContext);
return ctx?.setContainer;
}
export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
}
export const Container = forwardRef<HTMLDivElement, ContainerProps>(function Container(
{ children, tabIndex = 0, ...props },
ref
) {
const setContainer = useContainerAttach();
const internalRef = useRef<HTMLDivElement>(null);
const composedRef = useComposedRefs(ref, internalRef);
useEffect(() => {
setContainer?.(internalRef.current);
return () => setContainer?.(null);
}, [setContainer]);
const handlePointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
props.onPointerUp?.(event);
const el = internalRef.current;
if (!el) return;
// If nothing inside has focus, grab it so keyboard events reach hotkey listeners.
if (!el.contains(document.activeElement) || document.activeElement === document.body) {
el.focus({ preventScroll: true });
}
};
return (
<div ref={composedRef} tabIndex={tabIndex} {...props} onPointerUp={handlePointerUp}>
{children}
</div>
);
});
export namespace Container {
export type Props = ContainerProps;
}