@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
131 lines (130 loc) • 4.4 kB
JavaScript
"use client";
import _extends from "@babel/runtime-corejs3/helpers/esm/extends";
import React, { useContext, useMemo, useReducer, useRef } from 'react';
import { createPortal } from 'react-dom';
import classnames from 'classnames';
import IsolatedStyleScope, { IsolatedStyleScopeContext } from "../../shared/IsolatedStyleScope.js";
import { useIsomorphicLayoutEffect as useLayoutEffect } from "../../shared/helpers/useIsomorphicLayoutEffect.js";
const PortalRootContext = React.createContext(null);
export function PortalRootProvider(props) {
const {
id,
insideSelector,
beforeSelector,
children
} = props;
const value = useMemo(() => ({
id,
insideSelector,
beforeSelector
}), [id, insideSelector, beforeSelector]);
return React.createElement(PortalRootContext.Provider, {
value: value
}, children);
}
function PortalRoot(props = {}) {
var _ref;
const {
id: idProp,
insideSelector: insideSelectorProp,
beforeSelector: beforeSelectorProp,
innerRef,
className,
style,
children,
...rest
} = props;
const [, forceUpdate] = useReducer(() => ({}), {});
const {
style: scopeStyle
} = useContext(IsolatedStyleScopeContext) || {};
const selectorContext = useContext(PortalRootContext);
const hasPropOverride = typeof insideSelectorProp !== 'undefined' || typeof beforeSelectorProp !== 'undefined';
const insideSelector = hasPropOverride ? insideSelectorProp : selectorContext === null || selectorContext === void 0 ? void 0 : selectorContext.insideSelector;
const beforeSelector = hasPropOverride ? beforeSelectorProp : selectorContext === null || selectorContext === void 0 ? void 0 : selectorContext.beforeSelector;
const effectiveId = (_ref = idProp !== null && idProp !== void 0 ? idProp : selectorContext === null || selectorContext === void 0 ? void 0 : selectorContext.id) !== null && _ref !== void 0 ? _ref : 'eufemia-portal-root';
const initialElement = useMemo(() => {
if (idProp && typeof document !== 'undefined') {
return document.getElementById(idProp);
}
return getOrCreatePortalElement({
id: effectiveId,
insideSelector,
beforeSelector
});
}, [effectiveId, idProp, insideSelector, beforeSelector]);
const localRef = useRef(initialElement);
useLayoutEffect(() => {
if (idProp || insideSelector || beforeSelector) {
const elem = getOrCreatePortalElement({
id: effectiveId,
insideSelector,
beforeSelector
});
if (localRef.current !== elem) {
localRef.current = elem;
forceUpdate();
}
}
if (innerRef && localRef.current) {
if (typeof innerRef === 'function') {
innerRef(localRef.current);
} else {
const ref = innerRef;
ref.current = localRef.current;
}
}
}, [effectiveId, idProp, innerRef, insideSelector, beforeSelector]);
const portalElement = localRef.current || initialElement;
if (!portalElement) {
return null;
}
return createPortal(React.createElement(IsolatedStyleScope, {
scopeHash: "auto",
disableCoreStyleWrapper: true,
uniqueKey: false
}, React.createElement("div", _extends({
className: classnames("dnb-core-style eufemia-portal-root", className),
style: {
...scopeStyle,
...style
}
}, rest), children)), portalElement);
}
export function getOrCreatePortalElement({
id,
insideSelector,
beforeSelector
}) {
if (typeof document === 'undefined') {
return null;
}
let elem = document.getElementById(id);
if (!elem) {
let parent = document.body;
let referenceNode = document.body.firstChild;
if (beforeSelector) {
const target = document.querySelector(beforeSelector);
if (target && target.parentElement) {
parent = target.parentElement;
referenceNode = target;
}
} else if (insideSelector) {
const target = document.querySelector(insideSelector);
if (target) {
parent = target;
referenceNode = target.firstChild;
}
}
elem = document.createElement('div');
elem.setAttribute('id', id);
parent.insertBefore(elem, referenceNode);
}
if (!elem.hasAttribute('role')) {
elem.setAttribute('role', 'presentation');
}
return elem;
}
PortalRoot.Provider = PortalRootProvider;
export default PortalRoot;
//# sourceMappingURL=PortalRoot.js.map