diff --git a/packages/gitbook/e2e/util.ts b/packages/gitbook/e2e/util.ts index 1248d53a6..607e53807 100644 --- a/packages/gitbook/e2e/util.ts +++ b/packages/gitbook/e2e/util.ts @@ -443,6 +443,11 @@ export async function waitForIcons(page: Page) { return true; } + const svgSymbol = icon.querySelector('[data-testid="symbol-use"]'); + if (svgSymbol) { + return icon.dataset.gbIIconSymbolState === 'loaded'; + } + const maskImage = icon.querySelector('[data-testid="mask-image"]'); if (!maskImage) { throw new Error('No mask-image element'); diff --git a/packages/icons/src/IconSymbolLoader.tsx b/packages/icons/src/IconSymbolLoader.tsx index b6675ecf4..c1c8720c3 100644 --- a/packages/icons/src/IconSymbolLoader.tsx +++ b/packages/icons/src/IconSymbolLoader.tsx @@ -5,6 +5,58 @@ import * as React from 'react'; const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; const pendingSymbolLoads = new Map>(); +/** + * Ensure a symbol referenced by an inline `` exists after hydration, fetching it from the + * internal symbol route only when the SSR sprite did not already include it. + */ +export function IconSymbolLoader(props: { + instanceId: string; + symbolId: string; + style: string; + icon: string; + loaderURL: string; +}) { + const { instanceId, symbolId, style, icon, loaderURL } = props; + + React.useEffect(() => { + let mounted = true; + + if (hasSymbol(symbolId)) { + setIconState(instanceId, true); + return; + } + + loadSymbol(symbolId, loaderURL, style, icon).then((loaded) => { + if (!mounted) { + return; + } + + setIconState(instanceId, loaded); + }); + + return () => { + mounted = false; + }; + }, [icon, instanceId, loaderURL, style, symbolId]); + + return null; +} + +/** + * A root SVG element used to host the loaded symbols as a spritesheet. This is used as a reference point for the `` elements in the icons. + */ +function getSpriteRoot(): SVGSVGElement { + const existing = document.getElementById('gb-icon-sprite-root'); + if (existing instanceof SVGSVGElement) { + return existing; + } + + return createSpriteRoot(); +} + +/** + * Creates the root SVG element for the spritesheet if it doesn't already exist, and appends it to the document body. + */ function createSpriteRoot(): SVGSVGElement { const spriteRoot = document.createElementNS(SVG_NAMESPACE, 'svg'); spriteRoot.setAttribute('id', 'gb-icon-sprite-root'); @@ -19,15 +71,6 @@ function createSpriteRoot(): SVGSVGElement { return spriteRoot; } -function getSpriteRoot(): SVGSVGElement { - const existing = document.getElementById('gb-icon-sprite-root'); - if (existing instanceof SVGSVGElement) { - return existing; - } - - return createSpriteRoot(); -} - function hasSymbol(symbolId: string): boolean { return document.getElementById(symbolId) instanceof SVGElement; } @@ -96,7 +139,7 @@ async function loadSymbol(symbolId: string, loaderURL: string, style: string, ic return request; } -function setIconFallbackState(instanceId: string, failed: boolean) { +function setIconState(instanceId: string, loaded: boolean) { const icon = document.querySelector( `svg[data-gb-icon-instance="${instanceId}"]` ); @@ -105,52 +148,8 @@ function setIconFallbackState(instanceId: string, failed: boolean) { } const symbolUse = icon.querySelector('[data-testid="symbol-use"]'); - const fallback = icon.querySelector('[data-testid="mask-fallback"]'); - if (symbolUse) { - symbolUse.style.display = failed ? 'none' : ''; + symbolUse.style.display = loaded ? '' : 'none'; } - - if (fallback) { - fallback.style.display = failed ? 'block' : 'none'; - } - - icon.setAttribute('data-gb-icon-symbol-state', failed ? 'failed' : 'loaded'); -} - -/** - * Ensure a symbol referenced by an inline `` exists after hydration, fetching it from the - * internal symbol route only when the SSR sprite did not already include it. - */ -export function IconSymbolLoader(props: { - instanceId: string; - symbolId: string; - style: string; - icon: string; - loaderURL: string; -}) { - const { instanceId, symbolId, style, icon, loaderURL } = props; - - React.useEffect(() => { - let mounted = true; - - if (hasSymbol(symbolId)) { - setIconFallbackState(instanceId, false); - return; - } - - loadSymbol(symbolId, loaderURL, style, icon).then((loaded) => { - if (!mounted) { - return; - } - - setIconFallbackState(instanceId, !loaded); - }); - - return () => { - mounted = false; - }; - }, [icon, instanceId, loaderURL, style, symbolId]); - - return null; + icon.setAttribute('data-gb-icon-symbol-state', loaded ? 'loaded' : 'failed'); }