{"version":3,"sources":["../src/use-modal.ts"],"sourcesContent":["import { callAllHandlers } from \"@chakra-ui/shared-utils\"\nimport { PropGetter } from \"@chakra-ui/react-types\"\nimport { mergeRefs } from \"@chakra-ui/react-use-merge-refs\"\nimport { hideOthers } from \"aria-hidden\"\n\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from \"react\"\nimport { modalManager, useModalManager } from \"./modal-manager\"\n\nexport interface UseModalProps {\n /**\n * If `true`, the modal will be open.\n */\n isOpen: boolean\n /**\n * The `id` of the modal\n */\n id?: string\n /**\n * Callback invoked to close the modal.\n */\n onClose(): void\n /**\n * If `true`, the modal will close when the overlay is clicked\n * @default true\n */\n closeOnOverlayClick?: boolean\n /**\n * If `true`, the modal will close when the `Esc` key is pressed\n * @default true\n */\n closeOnEsc?: boolean\n /**\n * Callback fired when the overlay is clicked.\n */\n onOverlayClick?(): void\n /**\n * Callback fired when the escape key is pressed and focus is within modal\n */\n onEsc?(): void\n /**\n * A11y: If `true`, the siblings of the `modal` will have `aria-hidden`\n * set to `true` so that screen readers can only see the `modal`.\n *\n * This is commonly known as making the other elements **inert**\n *\n * @default true\n */\n useInert?: boolean\n}\n\n/**\n * Modal hook that manages all the logic for the modal dialog widget\n * and returns prop getters, state and actions.\n *\n * @param props\n */\nexport function useModal(props: UseModalProps) {\n const {\n isOpen,\n onClose,\n id,\n closeOnOverlayClick = true,\n closeOnEsc = true,\n useInert = true,\n onOverlayClick: onOverlayClickProp,\n onEsc,\n } = props\n\n const dialogRef = useRef(null)\n const overlayRef = useRef(null)\n\n const [dialogId, headerId, bodyId] = useIds(\n id,\n `chakra-modal`,\n `chakra-modal--header`,\n `chakra-modal--body`,\n )\n\n /**\n * Hook used to polyfill `aria-modal` for older browsers.\n * It uses `aria-hidden` to all other nodes.\n *\n * @see https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/\n */\n useAriaHidden(dialogRef, isOpen && useInert)\n /**\n * Hook used to manage multiple or nested modals\n */\n const index = useModalManager(dialogRef, isOpen)\n\n const mouseDownTarget = useRef(null)\n\n const onMouseDown = useCallback((event: React.MouseEvent) => {\n mouseDownTarget.current = event.target\n }, [])\n\n const onKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.stopPropagation()\n\n if (closeOnEsc) {\n onClose?.()\n }\n\n onEsc?.()\n }\n },\n [closeOnEsc, onClose, onEsc],\n )\n\n const [headerMounted, setHeaderMounted] = useState(false)\n const [bodyMounted, setBodyMounted] = useState(false)\n\n const getDialogProps: PropGetter = useCallback(\n (props = {}, ref = null) => ({\n role: \"dialog\",\n ...props,\n ref: mergeRefs(ref, dialogRef),\n id: dialogId,\n tabIndex: -1,\n \"aria-modal\": true,\n \"aria-labelledby\": headerMounted ? headerId : undefined,\n \"aria-describedby\": bodyMounted ? bodyId : undefined,\n onClick: callAllHandlers(props.onClick, (event: React.MouseEvent) =>\n event.stopPropagation(),\n ),\n }),\n [bodyId, bodyMounted, dialogId, headerId, headerMounted],\n )\n\n const onOverlayClick = useCallback(\n (event: React.MouseEvent) => {\n event.stopPropagation()\n /**\n * Make sure the event starts and ends on the same DOM element.\n *\n * This is used to prevent the modal from closing when you\n * start dragging from the content, and release drag outside the content.\n *\n * We prevent this because it is technically not a considered \"click outside\"\n */\n if (mouseDownTarget.current !== event.target) return\n\n /**\n * When you click on the overlay, we want to remove only the topmost modal\n */\n if (!modalManager.isTopModal(dialogRef.current)) return\n\n if (closeOnOverlayClick) {\n onClose?.()\n }\n\n onOverlayClickProp?.()\n },\n [onClose, closeOnOverlayClick, onOverlayClickProp],\n )\n\n const getDialogContainerProps: PropGetter = useCallback(\n (props = {}, ref = null) => ({\n ...props,\n ref: mergeRefs(ref, overlayRef),\n onClick: callAllHandlers(props.onClick, onOverlayClick),\n onKeyDown: callAllHandlers(props.onKeyDown, onKeyDown),\n onMouseDown: callAllHandlers(props.onMouseDown, onMouseDown),\n }),\n [onKeyDown, onMouseDown, onOverlayClick],\n )\n\n return {\n isOpen,\n onClose,\n headerId,\n bodyId,\n setBodyMounted,\n setHeaderMounted,\n dialogRef,\n overlayRef,\n getDialogProps,\n getDialogContainerProps,\n index,\n }\n}\n\nexport type UseModalReturn = ReturnType\n\n/**\n * Modal hook to polyfill `aria-modal`.\n *\n * It applies `aria-hidden` to elements behind the modal\n * to indicate that they're `inert`.\n *\n * @param ref React ref of the node\n * @param shouldHide whether `aria-hidden` should be applied\n */\nexport function useAriaHidden(\n ref: React.RefObject,\n shouldHide: boolean,\n) {\n // save current ref in a local var to trigger the effect on identity change\n const currentElement = ref.current\n\n useEffect(() => {\n // keep using `ref.current` inside the effect\n // it may have changed during render and the execution of the effect\n if (!ref.current || !shouldHide) return undefined\n\n return hideOthers(ref.current)\n }, [shouldHide, ref, currentElement])\n}\n\nfunction useIds(idProp?: string, ...prefixes: string[]) {\n const reactId = useId()\n const id = idProp || reactId\n return useMemo(() => {\n return prefixes.map((prefix) => `${prefix}-${id}`)\n }, [id, prefixes])\n}\n"],"mappings":"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","names":["props"]}