UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

99 lines (98 loc) 3.34 kB
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; }