{"version":3,"file":"Popover.cjs","names":[],"sources":["../../../src/components/Popover/Popover.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — trigger and children are the two halves, placement\n * is where it lands, and open/onOpenChange/defaultOpen is the standard controlled-\n * or-not pair. closeOnEsc and closeOnOutsideClick are separate because a popover\n * holding a form wants one and not the other.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Popover.module.css\";\n\nexport type PopoverPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface PopoverProps {\n    /** Trigger element. Receives `onClick`/`aria-expanded`/`aria-controls`. */\n    trigger: ReactElement<{\n        onClick?: (e: React.MouseEvent) => void;\n        \"aria-expanded\"?: boolean;\n        \"aria-controls\"?: string;\n    }>;\n    children: ReactNode;\n    placement?: PopoverPlacement;\n    /** Controlled open state. */\n    open?: boolean;\n    /** Called when the user toggles or dismisses. */\n    onOpenChange?: (open: boolean) => void;\n    /** Default open state for uncontrolled usage. */\n    defaultOpen?: boolean;\n    /** Close on Escape. Default true. */\n    closeOnEsc?: boolean;\n    /** Close on outside click. Default true. */\n    closeOnOutsideClick?: boolean;\n    className?: string;\n}\n\n/**\n * Lightweight popover. Renders a positioned panel anchored to a trigger,\n * dismissed on outside click / Escape. Does NOT include collision detection\n * or smart positioning — pair with Floating UI if you need that.\n */\nexport function Popover({\n    trigger,\n    children,\n    placement = \"bottom\",\n    open,\n    onOpenChange,\n    defaultOpen = false,\n    closeOnEsc = true,\n    closeOnOutsideClick = true,\n    className,\n}: PopoverProps) {\n    const isControlled = open !== undefined;\n    const [internalOpen, setInternalOpen] = useState<boolean>(defaultOpen);\n    const isOpen = isControlled ? open : internalOpen;\n    const id = useId();\n    const rootRef = useRef<HTMLSpanElement>(null);\n\n    const setOpen = useCallback(\n        (next: boolean): void => {\n            if (!isControlled) setInternalOpen(next);\n            onOpenChange?.(next);\n        },\n        [isControlled, onOpenChange],\n    );\n\n    useEffect(() => {\n        if (!isOpen) return;\n        const onKey = (event: KeyboardEvent): void => {\n            if (closeOnEsc && event.key === \"Escape\") setOpen(false);\n        };\n        const onDown = (event: MouseEvent): void => {\n            if (!closeOnOutsideClick) return;\n            if (rootRef.current && !rootRef.current.contains(event.target as Node)) {\n                setOpen(false);\n            }\n        };\n        window.addEventListener(\"keydown\", onKey);\n        window.addEventListener(\"mousedown\", onDown);\n        return () => {\n            window.removeEventListener(\"keydown\", onKey);\n            window.removeEventListener(\"mousedown\", onDown);\n        };\n    }, [isOpen, closeOnEsc, closeOnOutsideClick, setOpen]);\n\n    const handleTriggerClick = (event: React.MouseEvent): void => {\n        trigger.props.onClick?.(event);\n        setOpen(!isOpen);\n    };\n\n    const triggerClone = {\n        ...trigger,\n        props: {\n            ...trigger.props,\n            onClick: handleTriggerClick,\n            \"aria-expanded\": isOpen,\n            \"aria-controls\": id,\n        },\n    } as ReactElement;\n\n    return (\n        <span ref={rootRef} className={styles.root}>\n            {triggerClone}\n            {isOpen && (\n                <div\n                    id={id}\n                    role=\"dialog\"\n                    className={cn(styles.popover, styles[placement], className)}\n                >\n                    {children}\n                </div>\n            )}\n        </span>\n    );\n}\n"],"mappings":"8HAwCA,SAAgB,EAAQ,CACpB,UACA,WACA,YAAY,SACZ,OACA,eACA,cAAc,GACd,aAAa,GACb,sBAAsB,GACtB,aACa,CACb,IAAM,EAAe,IAAS,IAAA,GACxB,CAAC,EAAc,IAAA,EAAmB,EAAA,SAAA,CAAkB,CAAW,EAC/D,EAAS,EAAe,EAAO,EAC/B,GAAA,EAAK,EAAA,MAAA,CAAM,EACX,GAAA,EAAU,EAAA,OAAA,CAAwB,IAAI,EAEtC,GAAA,EAAU,EAAA,YAAA,CACX,GAAwB,CAChB,GAAc,EAAgB,CAAI,EACvC,IAAe,CAAI,CACvB,EACA,CAAC,EAAc,CAAY,CAC/B,GAEA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAQ,OACb,IAAM,EAAS,GAA+B,CACtC,GAAc,EAAM,MAAQ,UAAU,EAAQ,EAAK,CAC3D,EACM,EAAU,GAA4B,CACnC,GACD,EAAQ,SAAW,CAAC,EAAQ,QAAQ,SAAS,EAAM,MAAc,GACjE,EAAQ,EAAK,CAErB,EAGA,OAFA,OAAO,iBAAiB,UAAW,CAAK,EACxC,OAAO,iBAAiB,YAAa,CAAM,MAC9B,CACT,OAAO,oBAAoB,UAAW,CAAK,EAC3C,OAAO,oBAAoB,YAAa,CAAM,CAClD,CACJ,EAAG,CAAC,EAAQ,EAAY,EAAqB,CAAO,CAAC,EAErD,IAAM,EAAsB,GAAkC,CAC1D,EAAQ,MAAM,UAAU,CAAK,EAC7B,EAAQ,CAAC,CAAM,CACnB,EAEM,EAAe,CACjB,GAAG,EACH,MAAO,CACH,GAAG,EAAQ,MACX,QAAS,EACT,gBAAiB,EACjB,gBAAiB,CACrB,CACJ,EAEA,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,IAAK,EAAS,UAAW,EAAA,QAAO,KAAtC,SAAA,CACK,EACA,IACG,EAAA,EAAA,IAAA,CAAC,MAAD,CACQ,KACJ,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,EAAA,QAAO,GAAY,CAAS,EAEzD,UACA,CAAA,CAEP,GAEd"}