@ariakit/react-core
Version:
Ariakit React core
475 lines (404 loc) • 16.9 kB
JavaScript
"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;