@ariakit/react-core
Version:
Ariakit React core
264 lines (240 loc) • 9.22 kB
JavaScript
;Object.defineProperty(exports, "__esModule", {value: true});
var _UI5KWBYVcjs = require('./UI5KWBYV.cjs');
var _VMIEHJBRcjs = require('./VMIEHJBR.cjs');
var _WBFXWJUHcjs = require('./WBFXWJUH.cjs');
var _MZ2HG624cjs = require('./MZ2HG624.cjs');
var _XMDZRF6Ycjs = require('./XMDZRF6Y.cjs');
// src/portal/portal.tsx
var _dom = require('@ariakit/core/utils/dom');
var _events = require('@ariakit/core/utils/events');
var _focus = require('@ariakit/core/utils/focus');
var _react = require('react');
var _reactdom = require('react-dom');
var _jsxruntime = require('react/jsx-runtime');
var TagName = "div";
function getRootElement(element) {
return _dom.getDocument.call(void 0, element).body;
}
function getPortalElement(element, portalElement) {
if (!portalElement) {
return _dom.getDocument.call(void 0, element).createElement("div");
}
if (typeof portalElement === "function") {
return portalElement(element);
}
return portalElement;
}
function getRandomId(prefix = "id") {
return `${prefix ? `${prefix}-` : ""}${Math.random().toString(36).slice(2, 8)}`;
}
function queueFocus(element) {
queueMicrotask(() => {
element == null ? void 0 : element.focus();
});
}
var usePortal = _WBFXWJUHcjs.createHook.call(void 0, function usePortal2({
preserveTabOrder,
preserveTabOrderAnchor,
portalElement,
portalRef,
portal = true,
...props
}) {
const ref = _react.useRef.call(void 0, null);
const refProp = _MZ2HG624cjs.useMergeRefs.call(void 0, ref, props.ref);
const context = _react.useContext.call(void 0, _UI5KWBYVcjs.PortalContext);
const [portalNode, setPortalNode] = _react.useState.call(void 0, null);
const [anchorPortalNode, setAnchorPortalNode] = _react.useState.call(void 0,
null
);
const outerBeforeRef = _react.useRef.call(void 0, null);
const innerBeforeRef = _react.useRef.call(void 0, null);
const innerAfterRef = _react.useRef.call(void 0, null);
const outerAfterRef = _react.useRef.call(void 0, null);
_MZ2HG624cjs.useSafeLayoutEffect.call(void 0, () => {
const element = ref.current;
if (!element || !portal) {
setPortalNode(null);
return;
}
const portalEl = getPortalElement(element, portalElement);
if (!portalEl) {
setPortalNode(null);
return;
}
const isPortalInDocument = portalEl.isConnected;
if (!isPortalInDocument) {
const rootElement = context || getRootElement(element);
rootElement.appendChild(portalEl);
}
if (!portalEl.id) {
portalEl.id = element.id ? `portal/${element.id}` : getRandomId();
}
setPortalNode(portalEl);
_XMDZRF6Ycjs.setRef.call(void 0, portalRef, portalEl);
if (isPortalInDocument) return;
return () => {
portalEl.remove();
_XMDZRF6Ycjs.setRef.call(void 0, portalRef, null);
};
}, [portal, portalElement, context, portalRef]);
_MZ2HG624cjs.useSafeLayoutEffect.call(void 0, () => {
if (!portal) return;
if (!preserveTabOrder) return;
if (!preserveTabOrderAnchor) return;
const doc = _dom.getDocument.call(void 0, preserveTabOrderAnchor);
const element = doc.createElement("span");
element.style.position = "fixed";
preserveTabOrderAnchor.insertAdjacentElement("afterend", element);
setAnchorPortalNode(element);
return () => {
element.remove();
setAnchorPortalNode(null);
};
}, [portal, preserveTabOrder, preserveTabOrderAnchor]);
_react.useEffect.call(void 0, () => {
if (!portalNode) return;
if (!preserveTabOrder) return;
let raf = 0;
const onFocus = (event) => {
if (!_events.isFocusEventOutside.call(void 0, event)) return;
const focusing = event.type === "focusin";
cancelAnimationFrame(raf);
if (focusing) {
return _focus.restoreFocusIn.call(void 0, portalNode);
}
raf = requestAnimationFrame(() => {
_focus.disableFocusIn.call(void 0, portalNode, true);
});
};
portalNode.addEventListener("focusin", onFocus, true);
portalNode.addEventListener("focusout", onFocus, true);
return () => {
cancelAnimationFrame(raf);
portalNode.removeEventListener("focusin", onFocus, true);
portalNode.removeEventListener("focusout", onFocus, true);
};
}, [portalNode, preserveTabOrder]);
props = _MZ2HG624cjs.useWrapElement.call(void 0,
props,
(element) => {
element = // While the portal node is not in the DOM, we need to pass the
// current context to the portal context, otherwise it's going to
// reset to the body element on nested portals.
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _UI5KWBYVcjs.PortalContext.Provider, { value: portalNode || context, children: element });
if (!portal) return element;
if (!portalNode) {
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"span",
{
ref: refProp,
id: props.id,
style: { position: "fixed" },
hidden: true
}
);
}
element = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
preserveTabOrder && portalNode && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_VMIEHJBRcjs.FocusTrap,
{
ref: innerBeforeRef,
"data-focus-trap": props.id,
className: "__focus-trap-inner-before",
onFocus: (event) => {
if (_events.isFocusEventOutside.call(void 0, event, portalNode)) {
queueFocus(_focus.getNextTabbable.call(void 0, ));
} else {
queueFocus(outerBeforeRef.current);
}
}
}
),
element,
preserveTabOrder && portalNode && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_VMIEHJBRcjs.FocusTrap,
{
ref: innerAfterRef,
"data-focus-trap": props.id,
className: "__focus-trap-inner-after",
onFocus: (event) => {
if (_events.isFocusEventOutside.call(void 0, event, portalNode)) {
queueFocus(_focus.getPreviousTabbable.call(void 0, ));
} else {
queueFocus(outerAfterRef.current);
}
}
}
)
] });
if (portalNode) {
element = _reactdom.createPortal.call(void 0, element, portalNode);
}
let preserveTabOrderElement = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
preserveTabOrder && portalNode && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_VMIEHJBRcjs.FocusTrap,
{
ref: outerBeforeRef,
"data-focus-trap": props.id,
className: "__focus-trap-outer-before",
onFocus: (event) => {
const fromOuter = event.relatedTarget === outerAfterRef.current;
if (!fromOuter && _events.isFocusEventOutside.call(void 0, event, portalNode)) {
queueFocus(innerBeforeRef.current);
} else {
queueFocus(_focus.getPreviousTabbable.call(void 0, ));
}
}
}
),
preserveTabOrder && // We're using position: fixed here so that the browser doesn't
// add margin to the element when setting gap on a parent element.
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { "aria-owns": portalNode == null ? void 0 : portalNode.id, style: { position: "fixed" } }),
preserveTabOrder && portalNode && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_VMIEHJBRcjs.FocusTrap,
{
ref: outerAfterRef,
"data-focus-trap": props.id,
className: "__focus-trap-outer-after",
onFocus: (event) => {
if (_events.isFocusEventOutside.call(void 0, event, portalNode)) {
queueFocus(innerAfterRef.current);
} else {
const nextTabbable = _focus.getNextTabbable.call(void 0, );
if (nextTabbable === innerBeforeRef.current) {
requestAnimationFrame(() => {
var _a;
return (_a = _focus.getNextTabbable.call(void 0, )) == null ? void 0 : _a.focus();
});
return;
}
queueFocus(nextTabbable);
}
}
}
)
] });
if (anchorPortalNode && preserveTabOrder) {
preserveTabOrderElement = _reactdom.createPortal.call(void 0,
preserveTabOrderElement,
anchorPortalNode
);
}
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
preserveTabOrderElement,
element
] });
},
[portalNode, context, portal, props.id, preserveTabOrder, anchorPortalNode]
);
props = {
...props,
ref: refProp
};
return props;
});
var Portal = _WBFXWJUHcjs.forwardRef.call(void 0, function Portal2(props) {
const htmlProps = usePortal(props);
return _WBFXWJUHcjs.createElement.call(void 0, TagName, htmlProps);
});
exports.usePortal = usePortal; exports.Portal = Portal;