UNPKG

@ariakit/react-core

Version:

Ariakit React core

475 lines (404 loc) 16.9 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _I5RHU6G2cjs = require('./I5RHU6G2.cjs'); var _L634CRNJcjs = require('./L634CRNJ.cjs'); var _X32SK43Ncjs = require('./X32SK43N.cjs'); var _W32FX7DMcjs = require('./W32FX7DM.cjs'); var _PVARJ43Vcjs = require('./PVARJ43V.cjs'); var _2ULOZ743cjs = require('./2ULOZ743.cjs'); var _S6PDCN4Xcjs = require('./S6PDCN4X.cjs'); var _LCWSLOYUcjs = require('./LCWSLOYU.cjs'); var _56NPFF7Pcjs = require('./56NPFF7P.cjs'); var _4OS43GOEcjs = require('./4OS43GOE.cjs'); var _26P4PLHIcjs = require('./26P4PLHI.cjs'); var _YVOQ2BCBcjs = require('./YVOQ2BCB.cjs'); var _YZWMAQPAcjs = require('./YZWMAQPA.cjs'); var _E3FLL4LHcjs = require('./E3FLL4LH.cjs'); var _5WCU5NVKcjs = require('./5WCU5NVK.cjs'); var _YAKNSXYIcjs = require('./YAKNSXYI.cjs'); var _EMYYI4CZcjs = require('./EMYYI4CZ.cjs'); var _WBFXWJUHcjs = require('./WBFXWJUH.cjs'); var _MZ2HG624cjs = require('./MZ2HG624.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 _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, ); 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 = _WBFXWJUHcjs.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 = _5WCU5NVKcjs.useDialogProviderContext.call(void 0, ); const ref = _react.useRef.call(void 0, null); const store = _E3FLL4LHcjs.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 } = _MZ2HG624cjs.usePortalRef.call(void 0, portal, props.portalRef); const preserveTabOrderProp = props.preserveTabOrder; const preserveTabOrder = _EMYYI4CZcjs.useStoreState.call(void 0, store, (state) => preserveTabOrderProp && !modal && state.mounted ); const id = _MZ2HG624cjs.useId.call(void 0, props.id); const open = _EMYYI4CZcjs.useStoreState.call(void 0, store, "open"); const mounted = _EMYYI4CZcjs.useStoreState.call(void 0, store, "mounted"); const contentElement = _EMYYI4CZcjs.useStoreState.call(void 0, store, "contentElement"); const hidden = _YZWMAQPAcjs.isHidden.call(void 0, mounted, props.hidden, props.alwaysVisible); _S6PDCN4Xcjs.usePreventBodyScroll.call(void 0, contentElement, id, preventBodyScroll && !hidden); _PVARJ43Vcjs.useHideOnInteractOutside.call(void 0, store, hideOnInteractOutside, domReady); const { wrapElement, nestedDialogs } = _2ULOZ743cjs.useNestedDialogs.call(void 0, store); props = _MZ2HG624cjs.useWrapElement.call(void 0, props, wrapElement, [wrapElement]); _MZ2HG624cjs.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") return; if (dialog && _dom.contains.call(void 0, dialog, activeElement)) return; store.setDisclosureElement(activeElement); }, [store, open]); if (isSafariBrowser) { _react.useEffect.call(void 0, () => { if (!mounted) return; const { disclosureElement } = store.getState(); if (!disclosureElement) return; if (!_dom.isButton.call(void 0, disclosureElement)) return; const onMouseDown = () => { let receivedFocus = false; const onFocus = () => { receivedFocus = true; }; const options = { capture: true, once: true }; disclosureElement.addEventListener("focusin", onFocus, options); _events.queueBeforeEvent.call(void 0, disclosureElement, "mouseup", () => { disclosureElement.removeEventListener("focusin", onFocus, true); if (receivedFocus) return; _focus.focusIfNeeded.call(void 0, disclosureElement); }); }; disclosureElement.addEventListener("mousedown", onMouseDown); return () => { disclosureElement.removeEventListener("mousedown", onMouseDown); }; }, [store, mounted]); } _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]); _MZ2HG624cjs.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; _MZ2HG624cjs.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 = _MZ2HG624cjs.useEvent.call(void 0, getPersistentElements); _MZ2HG624cjs.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 = _MZ2HG624cjs.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(() => { element.focus(); if (!isSafariBrowser) return; if (!isElementFocusable) return; element.scrollIntoView({ block: "nearest", inline: "nearest" }); }); }, [ open, mayAutoFocusOnShow, domReady, contentElement, initialFocus, portal, preserveTabOrder, autoFocusOnShowProp ]); const mayAutoFocusOnHide = !!autoFocusOnHide; const autoFocusOnHideProp = _MZ2HG624cjs.useBooleanEvent.call(void 0, autoFocusOnHide); const [hasOpened, setHasOpened] = _react.useState.call(void 0, false); _react.useEffect.call(void 0, () => { if (!open) return; setHasOpened(true); return () => setHasOpened(false); }, [open]); const focusOnHide = _react.useCallback.call(void 0, (dialog, retry = true) => { 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({ preventScroll: true }); }, [store, finalFocus, autoFocusOnHideProp] ); const focusedOnHideRef = _react.useRef.call(void 0, false); _MZ2HG624cjs.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 = _MZ2HG624cjs.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(); }; return _events.addGlobalEventListener.call(void 0, "keydown", onKeyDown, true); }, [store, domReady, mounted, hideOnEscapeProp]); props = _MZ2HG624cjs.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 = _MZ2HG624cjs.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, _4OS43GOEcjs.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 = _MZ2HG624cjs.useWrapElement.call(void 0, props, (element) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _5WCU5NVKcjs.DialogScopedContextProvider, { value: store, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _5WCU5NVKcjs.DialogHeadingContext.Provider, { value: setHeadingId, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _5WCU5NVKcjs.DialogDescriptionContext.Provider, { value: setDescriptionId, children: element }) }) }), [store] ); props = { id, "data-dialog": "", role: "dialog", tabIndex: focusable ? -1 : void 0, "aria-labelledby": headingId, "aria-describedby": descriptionId, ...props, ref: _MZ2HG624cjs.useMergeRefs.call(void 0, ref, props.ref) }; props = _X32SK43Ncjs.useFocusableContainer.call(void 0, { ...props, autoFocusOnShow: autoFocusEnabled }); props = _YZWMAQPAcjs.useDisclosureContent.call(void 0, { store, ...props }); props = _YAKNSXYIcjs.useFocusable.call(void 0, { ...props, focusable }); props = _I5RHU6G2cjs.usePortal.call(void 0, { portal, ...props, portalRef, preserveTabOrder }); return props; }); function createDialogComponent(Component, useProviderContext = _5WCU5NVKcjs.useDialogProviderContext) { return _WBFXWJUHcjs.forwardRef.call(void 0, function DialogComponent(props) { const context = useProviderContext(); const store = props.store || context; const mounted = _EMYYI4CZcjs.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( _WBFXWJUHcjs.forwardRef.call(void 0, function Dialog2(props) { const htmlProps = useDialog(props); return _WBFXWJUHcjs.createElement.call(void 0, TagName, htmlProps); }), _5WCU5NVKcjs.useDialogProviderContext ); exports.useDialog = useDialog; exports.createDialogComponent = createDialogComponent; exports.Dialog = Dialog;