let order: string[] = []; const listeners = new Set<() => void>(); let host: HTMLDivElement | undefined; export type FloatingWindowEntry = { id: string; title: string; focus: () => void; close: () => void }; const registered = new Map; }>(); let entries: readonly FloatingWindowEntry[] = []; function publishWindows() { entries = order.flatMap((id) => { const record = registered.get(id); return record ? [record.entry] : []; }); listeners.forEach((listener) => listener()); } export const floatingWindowOrder = () => order; /** Stable between changes, ordered from the back window to the foreground. */ export const floatingWindowEntries = () => entries; export const subscribeFloatingWindows = (listener: () => void) => { listeners.add(listener); return () => { listeners.delete(listener); }; }; export function registerFloatingWindow(entry: FloatingWindowEntry) { const previous = registered.get(entry.id); if (previous) { // Callbacks can change without invalidating the subscribed presentation. previous.actions.focus = entry.focus; previous.actions.close = entry.close; if (previous.entry.title === entry.title) return; previous.entry = { ...previous.entry, title: entry.title }; } else { const actions = { focus: entry.focus, close: entry.close }; registered.set(entry.id, { actions, entry: { id: entry.id, title: entry.title, focus: () => { if (registered.get(entry.id)?.actions === actions) actions.focus(); }, close: () => { if (registered.get(entry.id)?.actions === actions) actions.close(); } } }); if (!order.includes(entry.id)) order = [...order, entry.id]; } publishWindows(); } export function raiseFloatingWindow(id: string) { if (order.at(-1) === id) return; order = [...order.filter((item) => item !== id), id]; publishWindows(); } export function removeFloatingWindow(id: string) { if (!order.includes(id) && !registered.has(id)) return; registered.delete(id); order = order.filter((item) => item !== id); publishWindows(); } export function floatingWindowHost() { if (!host?.isConnected) { host = document.createElement("div"); // One stacking context keeps windows below dialogs and newly opened menus. host.className = "pointer-events-none fixed inset-0 z-50"; host.dataset.floatingWindowLayer = ""; document.body.appendChild(host); } return host; }