UNPKG

@stratakit/foundations

Version:
202 lines (201 loc) 6.35 kB
import { jsx, jsxs } from "react/jsx-runtime"; import * as React from "react"; import * as ReactDOM from "react-dom"; import { PortalContext } from "@ariakit/react/portal"; import { Role } from "@ariakit/react/role"; import cx from "classnames"; import componentsCss from "./~components.css.js"; import { useLayoutEffect, useMergedRefs } from "./~hooks.js"; import foundationsCss from "./~styles.css.js"; import { forwardRef, getOwnerDocument, getWindow, identity, isDocument } from "./~utils.js"; import { HtmlSanitizerContext, RootNodeContext, spriteSheetId, useRootNode } from "./Root.internal.js"; import { loadStyles } from "./styles.internal.js"; const css = foundationsCss + componentsCss; const stack = new Error()?.stack?.split("Error")?.at(-1)?.trim() || ""; const Root = forwardRef((props, forwardedRef) => { throwIfNotSingleton(); const { children, synchronizeColorScheme = false, unstable_htmlSanitizer = identity, ...rest } = props; const [portalContainer, setPortalContainer] = React.useState(null); return /* @__PURE__ */ jsxs(RootInternal, { ...rest, ref: forwardedRef, children: [ /* @__PURE__ */ jsx(Styles, {}), /* @__PURE__ */ jsx(Fonts, {}), /* @__PURE__ */ jsx(InlineSpriteSheet, {}), synchronizeColorScheme ? /* @__PURE__ */ jsx(SynchronizeColorScheme, { colorScheme: props.colorScheme }) : null, /* @__PURE__ */ jsx( PortalContainer, { colorScheme: props.colorScheme, density: props.density, ref: setPortalContainer } ), /* @__PURE__ */ jsx(PortalContext.Provider, { value: portalContainer, children: /* @__PURE__ */ jsx(HtmlSanitizerContext.Provider, { value: unstable_htmlSanitizer, children }) }) ] }); }); DEV: Root.displayName = "Root"; const RootInternal = forwardRef( (props, forwardedRef) => { const { children, colorScheme, density, ...rest } = props; const [rootNode, setRootNode] = React.useState(null); const findRootNodeFromRef = React.useCallback((element) => { if (!element) return; const rootNode2 = element.getRootNode(); if (!isDocument(rootNode2) && !isShadow(rootNode2)) return; setRootNode(rootNode2); }, []); return /* @__PURE__ */ jsx( Role, { ...rest, className: cx("\u{1F95D}-root", props.className), "data-kiwi-theme": colorScheme, "data-kiwi-density": density, ref: useMergedRefs(forwardedRef, findRootNodeFromRef), children: /* @__PURE__ */ jsx(RootNodeContext.Provider, { value: rootNode, children }) } ); } ); function SynchronizeColorScheme({ colorScheme }) { const rootNode = useRootNode(); useLayoutEffect(() => { if (!rootNode) return; if (isDocument(rootNode)) { rootNode.documentElement.dataset.colorScheme = colorScheme; const meta = rootNode.querySelector("meta[name='color-scheme']"); if (meta) meta.content = colorScheme; } else if (isShadow(rootNode)) { rootNode.host.dataset.colorScheme = colorScheme; } }, [rootNode, colorScheme]); return null; } const PortalContainer = forwardRef((props, forwardedRef) => { const rootNode = useRootNode(); if (!rootNode) return null; const destination = isDocument(rootNode) ? rootNode.body : rootNode; if (!destination) return null; return ReactDOM.createPortal( /* @__PURE__ */ jsx( "div", { className: "\u{1F95D}-root", "data-kiwi-theme": props.colorScheme, "data-kiwi-density": props.density, style: { display: "contents" }, ref: forwardedRef } ), destination ); }); function Styles() { const rootNode = useRootNode(); useLayoutEffect(() => { if (!rootNode) return; const { cleanup } = loadStyles(rootNode, { css }); return cleanup; }, [rootNode]); return null; } function Fonts() { const rootNode = useRootNode(); useLayoutEffect(() => { if (!rootNode) return; loadFonts(rootNode); }, [rootNode]); return null; } function InlineSpriteSheet() { const rootNode = useRootNode(); React.useEffect( function maybeCreateSpriteSheet() { const ownerDocument = getOwnerDocument(rootNode); if (!ownerDocument) return; const spriteSheet = ownerDocument?.getElementById(spriteSheetId); if (spriteSheet) return; const svg = ownerDocument.createElementNS( "http://www.w3.org/2000/svg", "svg" ); svg.id = spriteSheetId; svg.style.display = "none"; Object.defineProperty(svg, Symbol.for("\u{1F95D}"), { value: { icons: /* @__PURE__ */ new Map() } // Map of icon URLs that have already been inlined. }); ownerDocument.body.appendChild(svg); return () => { if (svg.isConnected) { ownerDocument.body.removeChild(svg); } }; }, [rootNode] ); return null; } function loadFonts(rootNode) { const ownerWindow = getWindow(rootNode); if (!ownerWindow?.document?.fonts || Array.from(ownerWindow.document.fonts).some( (font) => font.family === "InterVariable" )) { return; } const interStyles = { normal: "https://rsms.me/inter/font-files/InterVariable.woff2?v=4.1", italic: "https://rsms.me/inter/font-files/InterVariable-Italic.woff2?v=4.1" }; for (const [style, url] of Object.entries(interStyles)) { const font = new ownerWindow.FontFace( "InterVariable", `url(${url}) format("woff2")`, { display: "swap", weight: "100 900", style } ); ownerWindow.document.fonts.add(font); } } function throwIfNotSingleton() { const symbol = Symbol.for("@stratakit/foundations"); const _globalThis = globalThis; _globalThis[symbol] ??= { versions: /* @__PURE__ */ new Set() }; if (stack) _globalThis[symbol].versions?.add(stack); if ((_globalThis[symbol].versions?.size || 0) > 1) { console.table( Array.from(_globalThis[symbol].versions || []).map((stack2) => ({ "@stratakit/foundations location": stack2 })) ); throw new Error( `Multiple instances of @stratakit/foundations detected. This can lead to unexpected behavior.` ); } } function isShadow(node) { return node instanceof ShadowRoot || node?.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !!node?.host; } export { Root };