UNPKG

react-svg

Version:

A React component that injects SVG into the DOM.

181 lines (180 loc) 6.03 kB
"use client"; import { SVGInjector } from "@tanem/svg-injector"; import * as React from "react"; //#region src/owner-window.ts const ownerWindow = (node) => { return ((node === null || node === void 0 ? void 0 : node.ownerDocument) || document).defaultView || window; }; //#endregion //#region src/ReactSVG.tsx const svgNamespace = "http://www.w3.org/2000/svg"; const xlinkNamespace = "http://www.w3.org/1999/xlink"; const idPrefix = `react-svg-${Math.random().toString(36).slice(2, 6)}`; let idCounter = 0; const ReactSVG = React.forwardRef(({ afterInjection = () => void 0, beforeInjection = () => void 0, desc = "", evalScripts = "never", fallback: Fallback, httpRequestWithCredentials = false, loading: Loading, loadingDelay = 0, onError = () => void 0, renumerateIRIElements = true, src, title = "", useRequestCache = true, wrapper = "div", ...rest }, forwardedRef) => { const [hasError, setHasError] = React.useState(false); const [isLoading, setIsLoading] = React.useState(true); const [hasLoadingDelayElapsed, setHasLoadingDelayElapsed] = React.useState(loadingDelay <= 0); const [injectionId, setInjectionId] = React.useState(0); const hasInjectedRef = React.useRef(false); const reactWrapperRef = React.useRef(null); const callbacksRef = React.useRef({ afterInjection, beforeInjection, onError }); React.useEffect(() => { callbacksRef.current = { afterInjection, beforeInjection, onError }; }); const loadingDelayRef = React.useRef(loadingDelay); React.useEffect(() => { loadingDelayRef.current = loadingDelay; }); const refCallback = React.useCallback((reactWrapper) => { reactWrapperRef.current = reactWrapper; if (typeof forwardedRef === "function") forwardedRef(reactWrapper); else if (forwardedRef) forwardedRef.current = reactWrapper; }, [forwardedRef]); React.useEffect(() => { const reactWrapper = reactWrapperRef.current; /* istanbul ignore next */ if (!(reactWrapper instanceof ownerWindow(reactWrapper).Node)) return; let isActive = true; let nonReactWrapper = null; const removeSVG = () => { if (nonReactWrapper === null || nonReactWrapper === void 0 ? void 0 : nonReactWrapper.parentNode) { nonReactWrapper.parentNode.removeChild(nonReactWrapper); nonReactWrapper = null; } }; setHasError(false); setIsLoading(true); setHasLoadingDelayElapsed(loadingDelayRef.current <= 0); if (hasInjectedRef.current) setInjectionId((id) => id + 1); else hasInjectedRef.current = true; let nonReactTarget; if (wrapper === "svg") { nonReactWrapper = document.createElementNS(svgNamespace, wrapper); nonReactWrapper.setAttribute("xmlns", svgNamespace); nonReactWrapper.setAttribute("xmlns:xlink", xlinkNamespace); nonReactTarget = document.createElementNS(svgNamespace, wrapper); } else { nonReactWrapper = document.createElement(wrapper); nonReactTarget = document.createElement(wrapper); } nonReactWrapper.appendChild(nonReactTarget); nonReactTarget.dataset.src = src; reactWrapper.appendChild(nonReactWrapper); const handleError = (error) => { removeSVG(); if (isActive) { setHasError(true); setIsLoading(false); } callbacksRef.current.onError(error); }; const afterEach = (error, svg) => { if (error) { handleError(error); return; } if (!isActive) return; setIsLoading(false); try { callbacksRef.current.afterInjection(svg); } catch (afterInjectionError) { handleError(afterInjectionError); } }; const beforeEach = (svg) => { svg.setAttribute("role", "img"); const ariaLabelledBy = []; const ariaDescribedBy = []; if (title) { const originalTitle = svg.querySelector(":scope > title"); if (originalTitle) svg.removeChild(originalTitle); const titleId = `${idPrefix}-title-${++idCounter}`; const newTitle = document.createElementNS(svgNamespace, "title"); newTitle.id = titleId; newTitle.textContent = title; svg.prepend(newTitle); ariaLabelledBy.push(titleId); } if (desc) { const originalDesc = svg.querySelector(":scope > desc"); if (originalDesc) svg.removeChild(originalDesc); const descId = `${idPrefix}-desc-${++idCounter}`; const newDesc = document.createElementNS(svgNamespace, "desc"); newDesc.id = descId; newDesc.textContent = desc; const existingTitle = svg.querySelector(":scope > title"); if (existingTitle) existingTitle.after(newDesc); else svg.prepend(newDesc); ariaDescribedBy.push(descId); } if (ariaLabelledBy.length > 0) svg.setAttribute("aria-labelledby", ariaLabelledBy.join(" ")); if (ariaDescribedBy.length > 0) svg.setAttribute("aria-describedby", ariaDescribedBy.join(" ")); try { callbacksRef.current.beforeInjection(svg); } catch (error) { handleError(error); } }; SVGInjector(nonReactTarget, { afterEach, beforeEach, cacheRequests: useRequestCache, evalScripts, httpRequestWithCredentials, renumerateIRIElements }); return () => { isActive = false; removeSVG(); }; }, [ desc, evalScripts, httpRequestWithCredentials, renumerateIRIElements, src, title, useRequestCache, wrapper ]); React.useEffect(() => { if (!isLoading) return; if (loadingDelay <= 0) { setHasLoadingDelayElapsed(true); return; } setHasLoadingDelayElapsed(false); const timeoutId = setTimeout(() => { setHasLoadingDelayElapsed(true); }, loadingDelay); return () => { clearTimeout(timeoutId); }; }, [ injectionId, isLoading, loadingDelay ]); const Wrapper = wrapper; return /* @__PURE__ */ React.createElement(Wrapper, { ...rest, ref: refCallback, ...wrapper === "svg" ? { xmlns: svgNamespace, xmlnsXlink: xlinkNamespace } : {} }, isLoading && hasLoadingDelayElapsed && Loading && /* @__PURE__ */ React.createElement(Loading, null), hasError && Fallback && /* @__PURE__ */ React.createElement(Fallback, null)); }); ReactSVG.displayName = "ReactSVG"; //#endregion export { ReactSVG }; //# sourceMappingURL=react-svg.mjs.map