69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import {
|
|
createContext,
|
|
createElement,
|
|
type ReactNode,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState
|
|
} from 'react';
|
|
|
|
type PrimaryActionHandler = () => void;
|
|
|
|
interface PrimaryActionRegistration {
|
|
owner: symbol;
|
|
routeId: string;
|
|
handler: PrimaryActionHandler;
|
|
}
|
|
|
|
interface PrimaryActionContextValue {
|
|
registration: PrimaryActionRegistration | undefined;
|
|
register: (owner: symbol, routeId: string, handler: PrimaryActionHandler) => () => void;
|
|
}
|
|
|
|
const PrimaryActionContext = createContext<PrimaryActionContextValue | undefined>(undefined);
|
|
|
|
/** Owns the single primary-action registration shared by the shell and active screen. */
|
|
export function PrimaryActionProvider({ children }: { children: ReactNode }) {
|
|
const [registration, setRegistration] = useState<PrimaryActionRegistration>();
|
|
|
|
const register = useCallback((owner: symbol, routeId: string, handler: PrimaryActionHandler) => {
|
|
setRegistration({ owner, routeId, handler });
|
|
|
|
return () => {
|
|
setRegistration((current) => (current?.owner === owner ? undefined : current));
|
|
};
|
|
}, []);
|
|
|
|
const value = useMemo(() => ({ registration, register }), [register, registration]);
|
|
|
|
return createElement(PrimaryActionContext.Provider, { value }, children);
|
|
}
|
|
|
|
/** Register a screen's primary action. Rendering outside the provider is intentionally harmless. */
|
|
export function usePrimaryAction(routeId: string, handler: PrimaryActionHandler): void {
|
|
const handlerRef = useRef(handler);
|
|
const [owner] = useState(() => Symbol('primary-action-owner'));
|
|
const register = useContext(PrimaryActionContext)?.register;
|
|
|
|
useEffect(() => {
|
|
handlerRef.current = handler;
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!register) {
|
|
return;
|
|
}
|
|
|
|
return register(owner, routeId, () => handlerRef.current());
|
|
}, [owner, register, routeId]);
|
|
}
|
|
|
|
/** Return the active handler only when its registration belongs to the requested route. */
|
|
export function usePrimaryActionHandler(routeId: string): PrimaryActionHandler | undefined {
|
|
const registration = useContext(PrimaryActionContext)?.registration;
|
|
return registration?.routeId === routeId ? registration.handler : undefined;
|
|
}
|