mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
186 lines
5.4 KiB
TypeScript
186 lines
5.4 KiB
TypeScript
import type { FC, ReactNode } from 'react';
|
|
import type { TasksRecord } from '../core/queue';
|
|
import type { AnySlice, UnionSliceRequests, UnionSliceState, UnionSliceTarget, UnionSliceTasks } from '../core/slice';
|
|
import type { StoreConfig } from '../core/store';
|
|
|
|
import { isNull, isUndefined } from '@videojs/utils/predicate';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import { Store } from '../core/store';
|
|
import { StoreContextProvider, useParentStore, useStoreContext } from './context';
|
|
import { useRequest as useRequestBase, useSelector as useSelectorBase, useTasks as useTasksBase } from './hooks';
|
|
|
|
// ----------------------------------------
|
|
// Types
|
|
// ----------------------------------------
|
|
|
|
export interface CreateStoreConfig<Slices extends AnySlice[]> extends StoreConfig<UnionSliceTarget<Slices>, Slices> {
|
|
/**
|
|
* Display name for React DevTools.
|
|
*/
|
|
displayName?: string;
|
|
}
|
|
|
|
export interface ProviderProps<Slices extends AnySlice[]> {
|
|
children: ReactNode;
|
|
/**
|
|
* Optional pre-created store instance.
|
|
* If provided, the Provider will use this store instead of creating one.
|
|
* The Provider will NOT destroy this store on unmount.
|
|
*/
|
|
store?: Store<UnionSliceTarget<Slices>, Slices>;
|
|
/**
|
|
* If true, inherits the store from a parent Provider context instead of creating a new one.
|
|
* Useful when wrapping a skin with your own Provider to add custom hooks.
|
|
* Defaults to false (isolated store).
|
|
*/
|
|
inherit?: boolean;
|
|
}
|
|
|
|
export interface CreateStoreResult<Slices extends AnySlice[]> {
|
|
/**
|
|
* Provider component that creates and manages the store lifecycle.
|
|
*/
|
|
Provider: FC<ProviderProps<Slices>>;
|
|
|
|
/**
|
|
* Returns the typed store instance from context.
|
|
*/
|
|
useStore: () => Store<UnionSliceTarget<Slices>, Slices>;
|
|
|
|
/**
|
|
* Subscribes to a selected portion of state.
|
|
* Re-renders only when the selected value changes.
|
|
*/
|
|
useSelector: <T>(selector: (state: UnionSliceState<Slices>) => T) => T;
|
|
|
|
/**
|
|
* Returns the request map or a selected request.
|
|
*/
|
|
useRequest: {
|
|
(): UnionSliceRequests<Slices>;
|
|
<T>(selector: (requests: UnionSliceRequests<Slices>) => T): T;
|
|
};
|
|
|
|
/**
|
|
* Subscribes to task state changes.
|
|
* Returns the current tasks map from the queue.
|
|
*/
|
|
useTasks: () => TasksRecord<UnionSliceTasks<Slices>>;
|
|
|
|
/**
|
|
* Creates a new store instance.
|
|
* Useful for imperative access or creating a store before render.
|
|
*/
|
|
create: () => Store<UnionSliceTarget<Slices>, Slices>;
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// Implementation
|
|
// ----------------------------------------
|
|
|
|
/**
|
|
* Creates a store factory that returns a Provider and typed hooks.
|
|
*
|
|
* @param config - Store configuration including slices and optional lifecycle hooks
|
|
* @returns An object containing Provider, hooks, and a create function
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { Provider, useStore, useSelector, useRequest, useTasks, create } = createStore({
|
|
* slices: [playbackSlice, presentationSlice],
|
|
* });
|
|
* ```
|
|
*/
|
|
export function createStore<Slices extends AnySlice[]>(config: CreateStoreConfig<Slices>): CreateStoreResult<Slices> {
|
|
type Target = UnionSliceTarget<Slices>;
|
|
type State = UnionSliceState<Slices>;
|
|
type Requests = UnionSliceRequests<Slices>;
|
|
type Tasks = UnionSliceTasks<Slices>;
|
|
type StoreType = Store<Target, Slices>;
|
|
|
|
function create(): StoreType {
|
|
return new Store(config);
|
|
}
|
|
|
|
/**
|
|
* Provider component that manages store lifecycle.
|
|
*
|
|
* Resolution order:
|
|
* 1. If `store` prop provided, uses that store (no cleanup on unmount)
|
|
* 2. If `inherit={true}` and parent store exists, uses parent store (no cleanup)
|
|
* 3. Otherwise, creates a new store and destroys it on unmount
|
|
*/
|
|
function Provider({ children, store: providedStore, inherit = false }: ProviderProps<Slices>): ReactNode {
|
|
const parentStore = useParentStore();
|
|
const shouldInherit = inherit && !isNull(parentStore);
|
|
|
|
const [store] = useState<StoreType>(() => {
|
|
if (!isUndefined(providedStore)) {
|
|
return providedStore;
|
|
}
|
|
|
|
if (shouldInherit) {
|
|
return parentStore as StoreType;
|
|
}
|
|
|
|
return create();
|
|
});
|
|
|
|
// Only destroy if we created the store (not provided, not inherited)
|
|
const isOwner = isUndefined(providedStore) && !shouldInherit;
|
|
|
|
useEffect(() => {
|
|
if (isOwner) {
|
|
return () => store.destroy();
|
|
}
|
|
|
|
return undefined;
|
|
}, [store, isOwner]);
|
|
|
|
return <StoreContextProvider store={store}>{children}</StoreContextProvider>;
|
|
}
|
|
|
|
// Set display name for React DevTools
|
|
if (config.displayName) {
|
|
Provider.displayName = `${config.displayName}.Provider`;
|
|
}
|
|
|
|
function useStore(): StoreType {
|
|
return useStoreContext() as StoreType;
|
|
}
|
|
|
|
function useSelector<T>(selector: (state: State) => T): T {
|
|
const store = useStore();
|
|
return useSelectorBase(store, selector);
|
|
}
|
|
|
|
function useRequest(): Requests;
|
|
function useRequest<T>(selector: (requests: Requests) => T): T;
|
|
function useRequest<T>(selector?: (requests: Requests) => T): Requests | T {
|
|
const store = useStore();
|
|
const requests = useRequestBase(store);
|
|
|
|
if (isUndefined(selector)) {
|
|
return requests;
|
|
}
|
|
|
|
return selector(requests);
|
|
}
|
|
|
|
function useTasks(): TasksRecord<Tasks> {
|
|
const store = useStore();
|
|
return useTasksBase(store);
|
|
}
|
|
|
|
return {
|
|
Provider,
|
|
useStore,
|
|
useSelector,
|
|
useRequest: useRequest as CreateStoreResult<Slices>['useRequest'],
|
|
useTasks,
|
|
create,
|
|
};
|
|
}
|