Files
ersatztv/web/src/primaryAction.ts
T
2026-07-15 22:31:31 +02:00

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;
}