import type { Attachment } from "svelte/attachments"; export function popoverSingleton() { let openHide: (() => void) | undefined; return { claim(hide: () => void) { if (openHide && openHide !== hide) openHide(); openHide = hide; }, release(hide: () => void) { if (openHide === hide) openHide = undefined; } }; } export function popoverAnchorName(prefix: string, id: string) { return `--${prefix}-${id.replace(/[^a-zA-Z0-9_-]/g, "-")}`; } export type PopoverOrigin = "tl" | "tr" | "bl" | "br"; const corners: Record = { tl: "top-left", tr: "top-right", bl: "bottom-left", br: "bottom-right" }; const along = (center: number, start: number, extent: number, near: string, far: string) => { if (extent === 0) return "center"; const fraction = (center - start) / extent; if (fraction < 1 / 3) return near; if (fraction > 2 / 3) return far; return "center"; }; export const popoverOrigin = ( anchor: () => HTMLElement | undefined, origin?: () => PopoverOrigin | undefined ): Attachment => (node) => { const measure = () => { const fixed = origin?.(); if (fixed) { node.setAttribute("data-popover-origin", corners[fixed]); return; } const trigger = anchor()?.getBoundingClientRect(); if (!trigger) return; const panel = node.getBoundingClientRect(); if (panel.width === 0 || panel.height === 0) return; const block = along( trigger.top + trigger.height / 2, panel.top, panel.height, "top", "bottom" ); const inline = along( trigger.left + trigger.width / 2, panel.left, panel.width, "left", "right" ); node.setAttribute("data-popover-origin", `${block}-${inline}`); }; const onToggle = (event: ToggleEvent) => { if (event.newState === "open") measure(); }; node.addEventListener("toggle", onToggle); return () => node.removeEventListener("toggle", onToggle); };