"use client"; // Commands the command palette hands to the surface that can run them (the // mail page composes and searches). Loosely coupled through a window event, // like the in-app mailto: requests, because that state lives deep inside the // page component. When no surface takes a command, the palette parks it and // navigates to mail, which runs it once it is ready. import { useEffect, useRef } from 'react'; export type AppCommand = 'compose' | 'search'; export const COMMAND_PALETTE_OPEN_EVENT = 'bulwark:command-palette'; /** Open the command palette from a button or menu (phones have no Ctrl+K). */ export function openCommandPalette(): void { if (typeof window !== 'undefined') window.dispatchEvent(new Event(COMMAND_PALETTE_OPEN_EVENT)); } const EVENT = 'bulwark:app-command'; let pending: AppCommand | null = null; /** Run a command on a mounted surface. False when nothing took it. */ export function requestAppCommand(command: AppCommand): boolean { if (typeof window === 'undefined') return false; const uncancelled = window.dispatchEvent(new CustomEvent(EVENT, { detail: command, cancelable: true })); return !uncancelled; } /** Keep a command for the next surface that registers a handler for it. */ export function parkAppCommand(command: AppCommand): void { pending = command; } /** Test helper. */ export function peekParkedAppCommand(): AppCommand | null { return pending; } /** * Let this surface run palette commands while `enabled`. A command parked * before the surface was ready runs as soon as it is. */ export function useAppCommands(handlers: Partial void>>, enabled: boolean): void { const ref = useRef(handlers); useEffect(() => { ref.current = handlers; }); useEffect(() => { if (!enabled) return; const onCommand = (event: Event) => { const handler = ref.current[(event as CustomEvent).detail]; if (!handler) return; event.preventDefault(); handler(); }; window.addEventListener(EVENT, onCommand); if (pending && ref.current[pending]) { const handler = ref.current[pending]!; pending = null; handler(); } return () => window.removeEventListener(EVENT, onCommand); }, [enabled]); }