@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
140 lines (139 loc) • 4.46 kB
JavaScript
"use client";
import React, { useContext, useMemo, useReducer, useRef } from 'react';
import { createPortal } from 'react-dom';
import clsx from 'clsx';
import IsolatedStyleScope, { IsolatedStyleScopeContext } from "../../shared/IsolatedStyleScope.js";
import { useIsomorphicLayoutEffect as useLayoutEffect } from "../../shared/helpers/useIsomorphicLayoutEffect.js";
import { jsx as _jsx } from "react/jsx-runtime";
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 _jsx(PortalRootContext, {
value: value,
children: children
});
}
function PortalRootInstance(props = {}) {
var _ref;
const {
id: idProp,
insideSelector: insideSelectorProp,
beforeSelector: beforeSelectorProp,
ref: refProp,
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 (refProp && localRef.current) {
if (typeof refProp === 'function') {
refProp(localRef.current);
} else {
const ref = refProp;
ref.current = localRef.current;
}
}
}, [effectiveId, idProp, refProp, insideSelector, beforeSelector]);
const portalElement = localRef.current || initialElement;
if (!portalElement) {
return null;
}
return createPortal(_jsx(IsolatedStyleScope, {
scopeHash: "auto",
disableCoreStyleWrapper: true,
uniqueKey: false,
children: _jsx("div", {
className: clsx("dnb-core-style eufemia-portal-root", className),
style: {
...scopeStyle,
...style
},
...rest,
children: 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;
}
function PortalRoot(props) {
return _jsx(PortalRootInstance, {
...props
});
}
PortalRoot.Provider = PortalRootProvider;
export default PortalRoot;
//# sourceMappingURL=PortalRoot.js.map