UNPKG

@ariakit/react-core

Version:

Ariakit React core

483 lines (420 loc) 17.4 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _6NFJUQ7Qcjs = require('./6NFJUQ7Q.cjs'); var _L634CRNJcjs = require('./L634CRNJ.cjs'); var _CXFFS6H4cjs = require('./CXFFS6H4.cjs'); var _W32FX7DMcjs = require('./W32FX7DM.cjs'); var _ERSQB5XOcjs = require('./ERSQB5XO.cjs'); var _HMJY2Y4Hcjs = require('./HMJY2Y4H.cjs'); var _JQCSYNKYcjs = require('./JQCSYNKY.cjs'); var _LCWSLOYUcjs = require('./LCWSLOYU.cjs'); var _56NPFF7Pcjs = require('./56NPFF7P.cjs'); var _O5T5YIJ4cjs = require('./O5T5YIJ4.cjs'); var _26P4PLHIcjs = require('./26P4PLHI.cjs'); var _YVOQ2BCBcjs = require('./YVOQ2BCB.cjs'); var _DRXSFB5Ocjs = require('./DRXSFB5O.cjs'); var _EM3Q5QVVcjs = require('./EM3Q5QVV.cjs'); var _WDV6EJ2Ucjs = require('./WDV6EJ2U.cjs'); var _BB34VMZXcjs = require('./BB34VMZX.cjs'); var _RDNUVX4Vcjs = require('./RDNUVX4V.cjs'); var _SQLDFLTEcjs = require('./SQLDFLTE.cjs'); var _4KGS3DOWcjs = require('./4KGS3DOW.cjs'); // src/dialog/dialog.tsx var _dom = require('@ariakit/core/utils/dom'); var _events = require('@ariakit/core/utils/events'); var _focus = require('@ariakit/core/utils/focus'); var _misc = require('@ariakit/core/utils/misc'); var _platform = require('@ariakit/core/utils/platform'); var _store = require('@ariakit/core/utils/store'); var _react = require('react'); var _jsxruntime = require('react/jsx-runtime'); var TagName = "div"; var isSafariBrowser = _platform.isSafari.call(void 0, ); function isAlreadyFocusingAnotherElement(dialog) { const activeElement = _dom.getActiveElement.call(void 0, dialog); if (!activeElement) return false; if (dialog && _dom.contains.call(void 0, dialog, activeElement)) return false; if (_focus.isFocusable.call(void 0, activeElement)) return true; return false; } function getElementFromProp(prop, focusable = false) { if (!prop) return null; const element = "current" in prop ? prop.current : prop; if (!element) return null; if (focusable) return _focus.isFocusable.call(void 0, element) ? element : null; return element; } var useDialog = _SQLDFLTEcjs.createHook.call(void 0, function useDialog2({ store: storeProp, open: openProp, onClose, focusable = true, modal = true, portal = modal, backdrop = modal, hideOnEscape = true, hideOnInteractOutside = true, getPersistentElements, preventBodyScroll = modal, autoFocusOnShow = true, autoFocusOnHide = true, initialFocus, finalFocus, unmountOnHide, unstable_treeSnapshotKey, ...props }) { const context = _WDV6EJ2Ucjs.useDialogProviderContext.call(void 0, ); const ref = _react.useRef.call(void 0, null); const store = _EM3Q5QVVcjs.useDialogStore.call(void 0, { store: storeProp || context, open: openProp, setOpen(open2) { if (open2) return; const dialog = ref.current; if (!dialog) return; const event = new Event("close", { bubbles: false, cancelable: true }); if (onClose) { dialog.addEventListener("close", onClose, { once: true }); } dialog.dispatchEvent(event); if (!event.defaultPrevented) return; store.setOpen(true); } }); const { portalRef, domReady } = _4KGS3DOWcjs.usePortalRef.call(void 0, portal, props.portalRef); const preserveTabOrderProp = props.preserveTabOrder; const preserveTabOrder = _RDNUVX4Vcjs.useStoreState.call(void 0, store, (state) => preserveTabOrderProp && !modal && state.mounted ); const id = _4KGS3DOWcjs.useId.call(void 0, props.id); const open = _RDNUVX4Vcjs.useStoreState.call(void 0, store, "open"); const mounted = _RDNUVX4Vcjs.useStoreState.call(void 0, store, "mounted"); const contentElement = _RDNUVX4Vcjs.useStoreState.call(void 0, store, "contentElement"); const hidden = _DRXSFB5Ocjs.isHidden.call(void 0, mounted, props.hidden, props.alwaysVisible); _JQCSYNKYcjs.usePreventBodyScroll.call(void 0, contentElement, id, preventBodyScroll && !hidden); const interactedOutsideRef = _react.useRef.call(void 0, false); _4KGS3DOWcjs.useSafeLayoutEffect.call(void 0, () => { return _store.sync.call(void 0, store, ["open"], (state) => { if (!state.open) return; interactedOutsideRef.current = false; }); }, [store]); _ERSQB5XOcjs.useHideOnInteractOutside.call(void 0, store, hideOnInteractOutside, domReady, interactedOutsideRef ); const { wrapElement, nestedDialogs } = _HMJY2Y4Hcjs.useNestedDialogs.call(void 0, store); props = _4KGS3DOWcjs.useWrapElement.call(void 0, props, wrapElement, [wrapElement]); const lastMousedownRef = _react.useRef.call(void 0, null); if (isSafariBrowser) { _react.useEffect.call(void 0, () => { if (!domReady) return; const dialog = ref.current; if (!dialog) return; const doc = _dom.getDocument.call(void 0, dialog); const onMousedown = (event) => { lastMousedownRef.current = event.target; }; doc.addEventListener("mousedown", onMousedown, true); return () => { doc.removeEventListener("mousedown", onMousedown, true); }; }, [domReady]); } _4KGS3DOWcjs.useSafeLayoutEffect.call(void 0, () => { if (!open) return; const dialog = ref.current; const activeElement = _dom.getActiveElement.call(void 0, dialog, true); if (!activeElement) return; if (activeElement.tagName === "BODY") { const fallback = lastMousedownRef.current; lastMousedownRef.current = null; if (!(fallback == null ? void 0 : fallback.isConnected)) return; if (!_focus.isFocusable.call(void 0, fallback)) return; if (dialog && _dom.contains.call(void 0, dialog, fallback)) return; store.setDisclosureElement(fallback); return; } if (dialog && _dom.contains.call(void 0, dialog, activeElement)) return; store.setDisclosureElement(activeElement); }, [store, open]); _react.useEffect.call(void 0, () => { if (!mounted) return; if (!domReady) return; const dialog = ref.current; if (!dialog) return; const win = _dom.getWindow.call(void 0, dialog); const viewport = win.visualViewport || win; const setViewportHeight = () => { var _a, _b; const height = (_b = (_a = win.visualViewport) == null ? void 0 : _a.height) != null ? _b : win.innerHeight; dialog.style.setProperty("--dialog-viewport-height", `${height}px`); }; setViewportHeight(); viewport.addEventListener("resize", setViewportHeight); return () => { viewport.removeEventListener("resize", setViewportHeight); }; }, [mounted, domReady]); _react.useEffect.call(void 0, () => { if (!modal) return; if (!mounted) return; if (!domReady) return; const dialog = ref.current; if (!dialog) return; const existingDismiss = dialog.querySelector("[data-dialog-dismiss]"); if (existingDismiss) return; return _W32FX7DMcjs.prependHiddenDismiss.call(void 0, dialog, store.hide); }, [store, modal, mounted, domReady]); _4KGS3DOWcjs.useSafeLayoutEffect.call(void 0, () => { if (!_56NPFF7Pcjs.supportsInert.call(void 0, )) return; if (open) return; if (!mounted) return; if (!domReady) return; const dialog = ref.current; if (!dialog) return; return _LCWSLOYUcjs.disableTree.call(void 0, dialog); }, [open, mounted, domReady]); const canTakeTreeSnapshot = open && domReady; _4KGS3DOWcjs.useSafeLayoutEffect.call(void 0, () => { if (!id) return; if (!canTakeTreeSnapshot) return; const dialog = ref.current; return _YVOQ2BCBcjs.createWalkTreeSnapshot.call(void 0, id, [dialog]); }, [id, canTakeTreeSnapshot, unstable_treeSnapshotKey]); const getPersistentElementsProp = _4KGS3DOWcjs.useEvent.call(void 0, getPersistentElements); _4KGS3DOWcjs.useSafeLayoutEffect.call(void 0, () => { if (!id) return; if (!canTakeTreeSnapshot) return; const { disclosureElement } = store.getState(); const dialog = ref.current; const persistentElements = getPersistentElementsProp() || []; const allElements = [ dialog, ...persistentElements, ...nestedDialogs.map((dialog2) => dialog2.getState().contentElement) ]; if (modal) { return _misc.chain.call(void 0, _26P4PLHIcjs.markTreeOutside.call(void 0, id, allElements), _LCWSLOYUcjs.disableTreeOutside.call(void 0, id, allElements) ); } return _26P4PLHIcjs.markTreeOutside.call(void 0, id, [disclosureElement, ...allElements]); }, [ id, store, canTakeTreeSnapshot, getPersistentElementsProp, nestedDialogs, modal, unstable_treeSnapshotKey ]); const mayAutoFocusOnShow = !!autoFocusOnShow; const autoFocusOnShowProp = _4KGS3DOWcjs.useBooleanEvent.call(void 0, autoFocusOnShow); const [autoFocusEnabled, setAutoFocusEnabled] = _react.useState.call(void 0, false); _react.useEffect.call(void 0, () => { if (!open) return; if (!mayAutoFocusOnShow) return; if (!domReady) return; if (!(contentElement == null ? void 0 : contentElement.isConnected)) return; const element = getElementFromProp(initialFocus, true) || // If no initial focus is specified, we try to focus the first element // with the autofocus attribute. If it's an Ariakit component, the // Focusable component will consume the autoFocus prop and add the // data-autofocus attribute to the element instead. contentElement.querySelector( "[data-autofocus=true],[autofocus]" ) || // We have to fallback to the first focusable element otherwise portaled // dialogs with preserveTabOrder set to true will not receive focus // properly because the elements aren't tabbable until the dialog receives // focus. _focus.getFirstTabbableIn.call(void 0, contentElement, true, portal && preserveTabOrder) || // Finally, we fallback to the dialog element itself. contentElement; const isElementFocusable = _focus.isFocusable.call(void 0, element); if (!autoFocusOnShowProp(isElementFocusable ? element : null)) return; setAutoFocusEnabled(true); queueMicrotask(() => { if (!store.getState().open) return; element.focus(); if (!isSafariBrowser) return; if (!isElementFocusable) return; element.scrollIntoView({ block: "nearest", inline: "nearest" }); }); }, [ open, mayAutoFocusOnShow, domReady, contentElement, initialFocus, portal, preserveTabOrder, store, autoFocusOnShowProp ]); const mayAutoFocusOnHide = !!autoFocusOnHide; const autoFocusOnHideProp = _4KGS3DOWcjs.useBooleanEvent.call(void 0, autoFocusOnHide); const [hasOpened, setHasOpened] = _react.useState.call(void 0, false); _4KGS3DOWcjs.useSafeLayoutEffect.call(void 0, () => { if (!open) return; setHasOpened(true); return () => setHasOpened(false); }, [open]); const focusOnHide = _react.useCallback.call(void 0, (dialog, retry = true) => { if (interactedOutsideRef.current) return; const { disclosureElement } = store.getState(); if (isAlreadyFocusingAnotherElement(dialog)) return; let element = getElementFromProp(finalFocus) || disclosureElement; if (element == null ? void 0 : element.id) { const doc = _dom.getDocument.call(void 0, element); const selector = `[aria-activedescendant="${element.id}"]`; const composite = doc.querySelector(selector); if (composite) { element = composite; } } if (element && !_focus.isFocusable.call(void 0, element)) { const maybeParentDialog = element.closest("[data-dialog]"); if (maybeParentDialog == null ? void 0 : maybeParentDialog.id) { const doc = _dom.getDocument.call(void 0, maybeParentDialog); const selector = `[aria-controls~="${maybeParentDialog.id}"]`; const control = doc.querySelector(selector); if (control) { element = control; } } } const isElementFocusable = element && _focus.isFocusable.call(void 0, element); if (!isElementFocusable && retry) { requestAnimationFrame(() => focusOnHide(dialog, false)); return; } if (!autoFocusOnHideProp(isElementFocusable ? element : null)) return; if (!isElementFocusable) return; element == null ? void 0 : element.focus(); }, [store, finalFocus, autoFocusOnHideProp] ); const focusedOnHideRef = _react.useRef.call(void 0, false); _4KGS3DOWcjs.useSafeLayoutEffect.call(void 0, () => { if (open) return; if (!hasOpened) return; if (!mayAutoFocusOnHide) return; const dialog = ref.current; focusedOnHideRef.current = true; focusOnHide(dialog); }, [open, hasOpened, domReady, mayAutoFocusOnHide, focusOnHide]); _react.useEffect.call(void 0, () => { if (!hasOpened) return; if (!mayAutoFocusOnHide) return; const dialog = ref.current; return () => { if (focusedOnHideRef.current) { focusedOnHideRef.current = false; return; } focusOnHide(dialog); }; }, [hasOpened, mayAutoFocusOnHide, focusOnHide]); const hideOnEscapeProp = _4KGS3DOWcjs.useBooleanEvent.call(void 0, hideOnEscape); _react.useEffect.call(void 0, () => { if (!domReady) return; if (!mounted) return; const onKeyDown = (event) => { if (event.key !== "Escape") return; if (event.defaultPrevented) return; const dialog = ref.current; if (!dialog) return; if (_26P4PLHIcjs.isElementMarked.call(void 0, dialog)) return; const target = event.target; if (!target) return; const { disclosureElement } = store.getState(); const isValidTarget = () => { if (target.tagName === "BODY") return true; if (_dom.contains.call(void 0, dialog, target)) return true; if (!disclosureElement) return true; if (_dom.contains.call(void 0, disclosureElement, target)) return true; return false; }; if (!isValidTarget()) return; if (!hideOnEscapeProp(event)) return; store.hide(); }; const win = contentElement ? _dom.getWindow.call(void 0, contentElement) : void 0; return _events.addGlobalEventListener.call(void 0, "keydown", onKeyDown, true, win); }, [store, domReady, mounted, contentElement, hideOnEscapeProp]); props = _4KGS3DOWcjs.useWrapElement.call(void 0, props, (element) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _L634CRNJcjs.HeadingLevel, { level: modal ? 1 : void 0, children: element }), [modal] ); const hiddenProp = props.hidden; const alwaysVisible = props.alwaysVisible; props = _4KGS3DOWcjs.useWrapElement.call(void 0, props, (element) => { if (!backdrop) return element; return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _O5T5YIJ4cjs.DialogBackdrop, { store, backdrop, hidden: hiddenProp, alwaysVisible } ), element ] }); }, [store, backdrop, hiddenProp, alwaysVisible] ); const [headingId, setHeadingId] = _react.useState.call(void 0, ); const [descriptionId, setDescriptionId] = _react.useState.call(void 0, ); props = _4KGS3DOWcjs.useWrapElement.call(void 0, props, (element) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _WDV6EJ2Ucjs.DialogScopedContextProvider, { value: store, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _WDV6EJ2Ucjs.DialogHeadingContext.Provider, { value: setHeadingId, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _WDV6EJ2Ucjs.DialogDescriptionContext.Provider, { value: setDescriptionId, children: element }) }) }), [store] ); props = { "data-dialog": "", role: "dialog", tabIndex: focusable ? -1 : void 0, "aria-labelledby": props["aria-label"] != null ? void 0 : headingId, "aria-describedby": descriptionId, ...props, id, ref: _4KGS3DOWcjs.useMergeRefs.call(void 0, ref, props.ref) }; props = _CXFFS6H4cjs.useFocusableContainer.call(void 0, { ...props, autoFocusOnShow: autoFocusEnabled }); props = _DRXSFB5Ocjs.useDisclosureContent.call(void 0, { store, ...props }); props = _BB34VMZXcjs.useFocusable.call(void 0, { ...props, focusable }); props = _6NFJUQ7Qcjs.usePortal.call(void 0, { portal, ...props, portalRef, preserveTabOrder }); return props; }); function createDialogComponent(Component, useProviderContext = _WDV6EJ2Ucjs.useDialogProviderContext) { return _SQLDFLTEcjs.forwardRef.call(void 0, function DialogComponent(props) { const context = useProviderContext(); const store = props.store || context; const mounted = _RDNUVX4Vcjs.useStoreState.call(void 0, store, (state) => !props.unmountOnHide || (state == null ? void 0 : state.mounted) || !!props.open ); if (!mounted) return null; return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Component, { ...props }); }); } var Dialog = createDialogComponent( _SQLDFLTEcjs.forwardRef.call(void 0, function Dialog2(props) { const htmlProps = useDialog(props); return _SQLDFLTEcjs.createElement.call(void 0, TagName, htmlProps); }), _WDV6EJ2Ucjs.useDialogProviderContext ); exports.useDialog = useDialog; exports.createDialogComponent = createDialogComponent; exports.Dialog = Dialog;