{"version":3,"file":"ModalsManager.cjs","names":[],"sources":["../../../src/components/ModalsManager/ModalsManager.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines — the imperative API (open, close, confirm, prompt) and\n * the stack that renders it live in one file so the returned promise and the mounted\n * element cannot drift apart.\n */\nimport { createContext, useCallback, useContext, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ConfirmDialog } from \"@/components/ConfirmDialog\";\nimport { Modal } from \"@/components/Modal\";\nimport type { ModalSize } from \"@/components/Modal\";\nimport styles from \"./ModalsManager.module.css\";\n\n/** Options accepted by {@link ModalsApi.open}. */\nexport interface OpenModalOptions {\n    /** Header title. */\n    title?: ReactNode;\n    /** Modal body content. */\n    children: ReactNode;\n    /** Dialog size. Default `md`. */\n    size?: ModalSize;\n    /** Allow closing by clicking the backdrop. Default `true`. */\n    closeOnBackdrop?: boolean;\n    /** Allow closing with the Esc key. Default `true`. */\n    closeOnEsc?: boolean;\n    /** Hide the header close button. */\n    hideCloseButton?: boolean;\n    /** Called after the modal is removed from the stack. */\n    onClose?: () => void;\n}\n\n/** Options accepted by {@link ModalsApi.confirm}. */\nexport interface ConfirmModalOptions {\n    /** Header title. */\n    title?: ReactNode;\n    /** Prompt message rendered in the dialog body. */\n    message: ReactNode;\n    /** Confirm button label. Default `Confirmar`. */\n    confirmLabel?: string;\n    /** Cancel button label. Default `Cancelar`. */\n    cancelLabel?: string;\n    /** Render the confirm button in the danger variant. */\n    danger?: boolean;\n    /** Called when the user confirms. */\n    onConfirm?: () => void | Promise<void>;\n    /** Called when the user cancels or dismisses. */\n    onCancel?: () => void;\n}\n\n/** Imperative API returned by {@link useModals}. */\nexport interface ModalsApi {\n    /** Push a content modal. Returns its stack id. */\n    open: (options: OpenModalOptions) => string;\n    /** Push a confirmation dialog. Returns its stack id. */\n    confirm: (options: ConfirmModalOptions) => string;\n    /** Remove the modal with the given id. */\n    close: (id: string) => void;\n    /** Remove every modal from the stack. */\n    closeAll: () => void;\n}\n\ninterface ContentEntry {\n    kind: \"content\";\n    id: string;\n    options: OpenModalOptions;\n}\n\ninterface ConfirmEntry {\n    kind: \"confirm\";\n    id: string;\n    options: ConfirmModalOptions;\n}\n\ntype StackEntry = ContentEntry | ConfirmEntry;\n\nconst ModalsContext = createContext<ModalsApi | null>(null);\n\n/** Props for {@link ModalsProvider}. */\nexport interface ModalsProviderProps {\n    children: ReactNode;\n}\n\n/**\n * Provides imperative modal control via {@link useModals} and renders the open\n * modal stack. Mount once near the app root.\n *\n * @example\n * <ModalsProvider>\n *     <App />\n * </ModalsProvider>\n */\nexport function ModalsProvider({ children }: ModalsProviderProps) {\n    const [stack, setStack] = useState<StackEntry[]>([]);\n    const counter = useRef(0);\n\n    const nextId = useCallback((): string => {\n        counter.current += 1;\n        return `modal-${counter.current}`;\n    }, []);\n\n    const close = useCallback((id: string): void => {\n        setStack((current) => current.filter((entry) => entry.id !== id));\n    }, []);\n\n    const closeAll = useCallback((): void => {\n        setStack([]);\n    }, []);\n\n    const open = useCallback(\n        (options: OpenModalOptions): string => {\n            const id = nextId();\n            setStack((current) => [...current, { kind: \"content\", id, options }]);\n            return id;\n        },\n        [nextId],\n    );\n\n    const confirm = useCallback(\n        (options: ConfirmModalOptions): string => {\n            const id = nextId();\n            setStack((current) => [...current, { kind: \"confirm\", id, options }]);\n            return id;\n        },\n        [nextId],\n    );\n\n    const api = useMemo<ModalsApi>(\n        () => ({ open, confirm, close, closeAll }),\n        [open, confirm, close, closeAll],\n    );\n\n    return (\n        <ModalsContext.Provider value={api}>\n            {children}\n            <div className={styles.stack}>\n                {stack.map((entry) =>\n                    entry.kind === \"content\" ? (\n                        <ContentModal key={entry.id} entry={entry} close={close} />\n                    ) : (\n                        <ConfirmModal key={entry.id} entry={entry} close={close} />\n                    ),\n                )}\n            </div>\n        </ModalsContext.Provider>\n    );\n}\n\ninterface ContentModalProps {\n    entry: ContentEntry;\n    close: (id: string) => void;\n}\n\nfunction ContentModal({ entry, close }: ContentModalProps) {\n    const { options, id } = entry;\n    const handleClose = (): void => {\n        options.onClose?.();\n        close(id);\n    };\n    return (\n        <Modal\n            open\n            onClose={handleClose}\n            title={options.title}\n            size={options.size}\n            closeOnBackdrop={options.closeOnBackdrop}\n            closeOnEsc={options.closeOnEsc}\n            hideCloseButton={options.hideCloseButton}\n        >\n            {options.children}\n        </Modal>\n    );\n}\n\ninterface ConfirmModalProps {\n    entry: ConfirmEntry;\n    close: (id: string) => void;\n}\n\nfunction ConfirmModal({ entry, close }: ConfirmModalProps) {\n    const { options, id } = entry;\n    const [loading, setLoading] = useState(false);\n\n    const handleConfirm = async (): Promise<void> => {\n        try {\n            setLoading(true);\n            await options.onConfirm?.();\n        } finally {\n            setLoading(false);\n            close(id);\n        }\n    };\n\n    const handleCancel = (): void => {\n        options.onCancel?.();\n        close(id);\n    };\n\n    return (\n        <ConfirmDialog\n            open\n            title={options.title ?? \"\"}\n            description={options.message}\n            confirmLabel={options.confirmLabel}\n            cancelLabel={options.cancelLabel}\n            variant={options.danger ? \"danger\" : \"primary\"}\n            loading={loading}\n            onConfirm={handleConfirm}\n            onCancel={handleCancel}\n        />\n    );\n}\n\n/**\n * Access the imperative modals API. Must be used within a {@link ModalsProvider}.\n *\n * @example\n * const modals = useModals();\n * modals.confirm({ message: \"Excluir item?\", danger: true, onConfirm: del });\n *\n * @throws Error when called outside a {@link ModalsProvider}.\n */\nexport function useModals(): ModalsApi {\n    const api = useContext(ModalsContext);\n    if (api === null) {\n        throw new Error(\"useModals must be used within a <ModalsProvider>\");\n    }\n    return api;\n}\n"],"mappings":"oLA0EA,IAAM,GAAA,EAAgB,EAAA,cAAA,CAAgC,IAAI,EAgB1D,SAAgB,EAAe,CAAE,YAAiC,CAC9D,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAuB,CAAC,CAAC,EAC7C,GAAA,EAAU,EAAA,OAAA,CAAO,CAAC,EAElB,GAAA,EAAS,EAAA,YAAA,MACX,EAAQ,SAAW,EACZ,SAAS,EAAQ,WACzB,CAAC,CAAC,EAEC,GAAA,EAAQ,EAAA,YAAA,CAAa,GAAqB,CAC5C,EAAU,GAAY,EAAQ,OAAQ,GAAU,EAAM,KAAO,CAAE,CAAC,CACpE,EAAG,CAAC,CAAC,EAEC,GAAA,EAAW,EAAA,YAAA,KAAwB,CACrC,EAAS,CAAC,CAAC,CACf,EAAG,CAAC,CAAC,EAEC,GAAA,EAAO,EAAA,YAAA,CACR,GAAsC,CACnC,IAAM,EAAK,EAAO,EAElB,OADA,EAAU,GAAY,CAAC,GAAG,EAAS,CAAE,KAAM,UAAW,KAAI,SAAQ,CAAC,CAAC,EAC7D,CACX,EACA,CAAC,CAAM,CACX,EAEM,GAAA,EAAU,EAAA,YAAA,CACX,GAAyC,CACtC,IAAM,EAAK,EAAO,EAElB,OADA,EAAU,GAAY,CAAC,GAAG,EAAS,CAAE,KAAM,UAAW,KAAI,SAAQ,CAAC,CAAC,EAC7D,CACX,EACA,CAAC,CAAM,CACX,EAEM,GAAA,EAAM,EAAA,QAAA,MACD,CAAE,OAAM,UAAS,QAAO,UAAS,GACxC,CAAC,EAAM,EAAS,EAAO,CAAQ,CACnC,EAEA,OACI,EAAA,EAAA,KAAA,CAAC,EAAc,SAAf,CAAwB,MAAO,EAA/B,SAAA,CACK,GACD,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,MAClB,SAAA,EAAM,IAAK,GACR,EAAM,OAAS,WACX,EAAA,EAAA,IAAA,CAAC,EAAD,CAAoC,QAAc,OAAQ,EAAvC,EAAM,EAAiC,GAE1D,EAAA,EAAA,IAAA,CAAC,EAAD,CAAoC,QAAc,OAAQ,EAAvC,EAAM,EAAiC,CAElE,CACC,CAAA,CACe,GAEhC,CAOA,SAAS,EAAa,CAAE,QAAO,SAA4B,CACvD,GAAM,CAAE,UAAS,MAAO,EAKxB,OACI,EAAA,EAAA,IAAA,CAAC,EAAA,MAAD,CACI,KAAA,GACA,YAPwB,CAC5B,EAAQ,UAAU,EAClB,EAAM,CAAE,CACZ,EAKQ,MAAO,EAAQ,MACf,KAAM,EAAQ,KACd,gBAAiB,EAAQ,gBACzB,WAAY,EAAQ,WACpB,gBAAiB,EAAQ,gBAExB,SAAA,EAAQ,QACN,CAAA,CAEf,CAOA,SAAS,EAAa,CAAE,QAAO,SAA4B,CACvD,GAAM,CAAE,UAAS,MAAO,EAClB,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAS,EAAK,EAiB5C,OACI,EAAA,EAAA,IAAA,CAAC,EAAA,cAAD,CACI,KAAA,GACA,MAAO,EAAQ,OAAS,GACxB,YAAa,EAAQ,QACrB,aAAc,EAAQ,aACtB,YAAa,EAAQ,YACrB,QAAS,EAAQ,OAAS,SAAW,UAC5B,UACT,UAAW,SAxB8B,CAC7C,GAAI,CACA,EAAW,EAAI,EACf,MAAM,EAAQ,YAAY,CAC9B,QAAU,CACN,EAAW,EAAK,EAChB,EAAM,CAAE,CACZ,CACJ,EAiBQ,aAfyB,CAC7B,EAAQ,WAAW,EACnB,EAAM,CAAE,CACZ,CAaK,CAAA,CAET,CAWA,SAAgB,GAAuB,CACnC,IAAM,GAAA,EAAM,EAAA,WAAA,CAAW,CAAa,EACpC,GAAI,IAAQ,KACR,MAAU,MAAM,kDAAkD,EAEtE,OAAO,CACX"}