@itwin/itwinui-react
Version:
A react component library for iTwinUI
99 lines (98 loc) • 3.34 kB
JavaScript
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useLatestRef, useLayoutEffect } from '../hooks/index.js';
import { useHydration } from '../providers/index.js';
let isBrowser = 'undefined' != typeof document;
let supportsDSD =
isBrowser && 'shadowRootMode' in HTMLTemplateElement.prototype;
let supportsAdoptedStylesheets =
isBrowser && 'adoptedStyleSheets' in Document.prototype;
export const ShadowRoot = ({ children, css, flush = true }) => {
let isHydrating = 'hydrating' === useHydration();
if (!isBrowser)
return React.createElement(
'template',
{
shadowrootmode: 'open',
},
css && React.createElement('style', null, css),
children,
);
if (supportsDSD && isHydrating) return null;
return React.createElement(
ClientShadowRoot,
{
css: css,
flush: flush,
},
children,
);
};
let ClientShadowRoot = ({ children, css, flush = true }) => {
let templateRef = React.useRef(null);
let shadowRoot = useShadowRoot(templateRef, {
css,
flush,
});
let fallbackCss =
!supportsAdoptedStylesheets && css
? React.createElement('style', null, css)
: null;
return shadowRoot
? ReactDOM.createPortal(
React.createElement(React.Fragment, null, fallbackCss, children),
shadowRoot,
)
: React.createElement('template', {
ref: templateRef,
});
};
function useShadowRoot(templateRef, { css = '', flush = true }) {
let [shadowRoot, setShadowRoot] = React.useState(null);
let styleSheet = React.useRef(void 0);
let latestCss = useLatestRef(css);
let latestShadowRoot = useLatestRef(shadowRoot);
let createStyleSheet = React.useCallback(
(shadow) => {
if (shadow && supportsAdoptedStylesheets) {
let currentWindow = shadow.ownerDocument.defaultView || globalThis;
if (styleSheet.current instanceof currentWindow.CSSStyleSheet) return;
styleSheet.current = new currentWindow.CSSStyleSheet();
shadow.adoptedStyleSheets.push(styleSheet.current);
if (latestCss.current)
styleSheet.current.replaceSync(latestCss.current);
}
},
[latestCss],
);
useLayoutEffect(() => {
let parent = templateRef.current?.parentElement;
if (!parent) return;
let setupOrReuseShadowRoot = () => {
if (parent.shadowRoot && null === latestShadowRoot.current)
parent.shadowRoot.replaceChildren();
let shadow =
parent.shadowRoot ||
parent.attachShadow({
mode: 'open',
});
createStyleSheet(shadow);
if (flush) ReactDOM.flushSync(() => setShadowRoot(shadow));
else setShadowRoot(shadow);
};
if (flush) queueMicrotask(() => setupOrReuseShadowRoot());
else setupOrReuseShadowRoot();
return () => void setShadowRoot(null);
}, [templateRef, createStyleSheet, latestShadowRoot, flush]);
useLayoutEffect(() => {
if (css && supportsAdoptedStylesheets) styleSheet.current?.replaceSync(css);
}, [css]);
React.useEffect(() => {
let listener = () => createStyleSheet(latestShadowRoot.current);
window.addEventListener('appui:reparent', listener);
return () => {
window.removeEventListener('appui:reparent', listener);
};
}, [createStyleSheet, latestShadowRoot]);
return shadowRoot;
}