UNPKG

@stratakit/foundations

Version:

Foundational pieces of StrataKit

110 lines (109 loc) 3.85 kB
import { jsx } from "react/jsx-runtime"; import * as React from "react"; import { Role } from "@ariakit/react/role"; import cx from "classnames"; import { useLatestRef, useSafeContext } from "./~hooks.js"; import { forwardRef, getOwnerDocument, parseDOM } from "./~utils.js"; import { HtmlSanitizerContext, spriteSheetId, useRootNode } from "./Root.internal.js"; const DEFAULT_ICON_HASH = "#icon"; const Icon = forwardRef((props, forwardedRef) => { const { href: hrefProp, size, alt, ...rest } = props; const isDecorative = !alt; const hrefBase = useNormalizedHrefBase(hrefProp); return /* @__PURE__ */ jsx(Role.svg, { "aria-hidden": isDecorative ? "true" : void 0, role: isDecorative ? void 0 : "img", "aria-label": isDecorative ? void 0 : alt, ...rest, "data-_sk-size": size, className: cx("\u{1F95D}Icon", props.className), ref: forwardedRef, children: hrefBase ? /* @__PURE__ */ jsx("use", { href: toIconHref(hrefBase) }) : null }); }); DEV: Icon.displayName = "Icon"; function toIconHref(hrefBase) { if (!hrefBase.includes("#")) return `${hrefBase}${DEFAULT_ICON_HASH}`; return hrefBase; } function useNormalizedHrefBase(rawHref) { const generatedId = React.useId(); const sanitizeHtml = useLatestRef(useSafeContext(HtmlSanitizerContext)); const rootNode = useRootNode(); const inlineHref = React.useRef(void 0); const getClientSnapshot = () => { const ownerDocument = getOwnerDocument(rootNode); if (!rawHref || !ownerDocument) return void 0; if (isHttpProtocol(rawHref, ownerDocument)) return rawHref; return inlineHref.current; }; const subscribe = React.useCallback((notify) => { const ownerDocument_0 = getOwnerDocument(rootNode); const spriteSheet = ownerDocument_0?.getElementById(spriteSheetId); if (!rawHref || !ownerDocument_0 || !spriteSheet) return () => { }; if (isHttpProtocol(rawHref, ownerDocument_0)) return () => { }; const cache = spriteSheet[/* @__PURE__ */ Symbol.for("\u{1F95D}")]?.icons; if (!cache) return () => { }; const prefix = `\u{1F95D}${generatedId}`; if (cache.has(rawHref)) { inlineHref.current = cache.get(rawHref); notify(); return () => { }; } const abortController = new AbortController(); const { signal } = abortController; (async () => { try { const resourceUrl = new URL(rawHref, ownerDocument_0.baseURI); const hash = resourceUrl.hash || DEFAULT_ICON_HASH; resourceUrl.hash = ""; const response = await fetch(resourceUrl.href, { signal }); if (!response.ok) { throw new Error(`Failed to fetch ${resourceUrl.href}`); } const fetchedSvgString = sanitizeHtml.current(await response.text()); const parsedSvgContent = parseDOM(fetchedSvgString, { ownerDocument: ownerDocument_0 }); const symbols = parsedSvgContent.querySelectorAll("symbol"); for (const symbol of symbols) { symbol.id = `${prefix}--${symbol.id}`; if (ownerDocument_0.getElementById(symbol.id)) continue; spriteSheet.appendChild(symbol.cloneNode(true)); } inlineHref.current = `#${prefix}--${hash.slice(1)}`; cache.set(rawHref, inlineHref.current); if (!signal.aborted) notify(); } catch (error) { if (signal.aborted) return; console.error(error); } })(); return () => abortController.abort(); }, [rawHref, rootNode, sanitizeHtml, generatedId]); return React.useSyncExternalStore(subscribe, getClientSnapshot, () => rawHref); } function isHttpProtocol(url, ownerDocument) { const { protocol } = new URL(url, ownerDocument.baseURI); return ["http:", "https:"].includes(protocol); } export { Icon };