UNPKG

react-inlinesvg

Version:
333 lines (332 loc) 10.6 kB
"use client"; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const require_provider = require("./provider-B5LIZTJG.cjs"); let react = require("react"); let react_from_dom = require("react-from-dom"); react_from_dom = require_provider.__toESM(react_from_dom); //#region src/modules/hooks.tsx function useMount(effect) { (0, react.useEffect)(effect, []); } function usePrevious(state) { const ref = (0, react.useRef)(void 0); (0, react.useEffect)(() => { ref.current = state; }); return ref.current; } //#endregion //#region src/modules/utils.ts function uniquifyStyleIds(svgText, hash, baseURL) { const idMatches = svgText.matchAll(/\bid=(["'])([^"']+)\1/g); const ids = [...new Set([...idMatches].map((m) => m[2]))]; if (!ids.length) return svgText; ids.sort((a, b) => b.length - a.length); return svgText.replace(/<style[^>]*>([\S\s]*?)<\/style>/gi, (fullMatch, cssContent) => { let modified = cssContent; for (const id of ids) { const escaped = id.replace(/[$()*+.?[\\\]^{|}]/g, "\\$&"); modified = modified.replace(new RegExp(`url\\((['"]?)#${escaped}\\1\\)`, "g"), `url($1${baseURL}#${id}__${hash}$1)`); modified = modified.replace(new RegExp(`#${escaped}(?![a-zA-Z0-9_-])`, "g"), `#${id}__${hash}`); } return fullMatch.replace(cssContent, modified); }); } function getNode(options) { const { baseURL, content, description, handleError, hash, preProcessor, title, uniquifyIDs = false } = options; try { let svgText = preProcessor ? preProcessor(content) : content; if (uniquifyIDs) svgText = uniquifyStyleIds(svgText, hash, baseURL ?? ""); const node = (0, react_from_dom.default)(svgText, { nodeOnly: true }); if (!node || !(node instanceof SVGSVGElement)) throw new Error("Could not convert the src to a DOM Node"); const svg = updateSVGAttributes(node, { baseURL, hash, uniquifyIDs }); if (description) { const originalDesc = svg.querySelector("desc"); if (originalDesc?.parentNode) originalDesc.parentNode.removeChild(originalDesc); const descElement = document.createElementNS("http://www.w3.org/2000/svg", "desc"); descElement.innerHTML = description; svg.prepend(descElement); } if (typeof title !== "undefined") { const originalTitle = svg.querySelector("title"); if (originalTitle?.parentNode) originalTitle.parentNode.removeChild(originalTitle); if (title) { const titleElement = document.createElementNS("http://www.w3.org/2000/svg", "title"); titleElement.innerHTML = title; svg.prepend(titleElement); } } return svg; } catch (error) { return handleError(error); } } function updateSVGAttributes(node, options) { const { baseURL = "", hash, uniquifyIDs } = options; const replaceableAttributes = [ "id", "href", "xlink:href", "xlink:role", "xlink:arcrole" ]; const linkAttributes = ["href", "xlink:href"]; const isDataValue = (name, value) => linkAttributes.includes(name) && (value ? !value.includes("#") : false); if (!uniquifyIDs) return node; [...node.children].forEach((d) => { if (d.attributes?.length) { const attributes = Object.values(d.attributes).map((a) => { const attribute = a; const match = /url\((.*?)\)/.exec(a.value); if (match?.[1]) attribute.value = a.value.replace(match[0], `url(${baseURL}${match[1]}__${hash})`); return attribute; }); replaceableAttributes.forEach((r) => { const attribute = attributes.find((a) => a.name === r); if (attribute && !isDataValue(r, attribute.value)) attribute.value = `${attribute.value}__${hash}`; }); } if (d.children.length) return updateSVGAttributes(d, options); return d; }); return node; } //#endregion //#region src/modules/useInlineSVG.ts function useInlineSVG(props, cacheStore) { const { baseURL, cacheRequests = true, description, fetchOptions, onError, onLoad, preProcessor, src, title, uniqueHash, uniquifyIDs } = props; const hash = (0, react.useRef)(uniqueHash ?? require_provider.randomString(8)); const fetchOptionsRef = (0, react.useRef)(fetchOptions); const onErrorRef = (0, react.useRef)(onError); const onLoadRef = (0, react.useRef)(onLoad); const preProcessorRef = (0, react.useRef)(preProcessor); fetchOptionsRef.current = fetchOptions; onErrorRef.current = onError; onLoadRef.current = onLoad; preProcessorRef.current = preProcessor; const [state, setState] = (0, react.useReducer)((previousState, nextState) => ({ ...previousState, ...nextState }), { content: "", element: null, isCached: false, status: require_provider.STATUS.IDLE }, (initial) => { if (!(cacheRequests && cacheStore.isCached(src))) return initial; const cachedContent = cacheStore.getContent(src); try { const node = getNode({ ...props, handleError: () => {}, hash: hash.current, content: cachedContent }); if (!node) return { ...initial, content: cachedContent, isCached: true, status: require_provider.STATUS.LOADED }; const convertedElement = (0, react_from_dom.default)(node); if (convertedElement && (0, react.isValidElement)(convertedElement)) return { content: cachedContent, element: convertedElement, isCached: true, status: require_provider.STATUS.READY }; } catch {} return { ...initial, content: cachedContent, isCached: true, status: require_provider.STATUS.LOADED }; }); const { content, element, isCached, status } = state; const previousProps = usePrevious(props); const previousState = usePrevious(state); const isActive = (0, react.useRef)(false); const isInitialized = (0, react.useRef)(false); const handleError = (0, react.useCallback)((error) => { if (isActive.current) { setState({ status: error.message === "Browser does not support SVG" ? require_provider.STATUS.UNSUPPORTED : require_provider.STATUS.FAILED }); onErrorRef.current?.(error); } }, []); const getElement = (0, react.useCallback)(() => { try { const convertedElement = (0, react_from_dom.default)(getNode({ baseURL, content, description, handleError, hash: hash.current, preProcessor: preProcessorRef.current, src, title, uniquifyIDs })); if (!convertedElement || !(0, react.isValidElement)(convertedElement)) throw new Error("Could not convert the src to a React element"); setState({ element: convertedElement, status: require_provider.STATUS.READY }); } catch (error) { handleError(error); } }, [ baseURL, content, description, handleError, src, title, uniquifyIDs ]); useMount(() => { isActive.current = true; if (!require_provider.canUseDOM() || isInitialized.current) return; try { if (status === require_provider.STATUS.READY) onLoadRef.current?.(src, isCached); else if (status === require_provider.STATUS.IDLE) { if (!require_provider.isSupportedEnvironment()) throw new Error("Browser does not support SVG"); if (!src) throw new Error("Missing src"); setState({ content: "", element: null, isCached: false, status: require_provider.STATUS.LOADING }); } } catch (error) { handleError(error); } isInitialized.current = true; return () => { isActive.current = false; }; }); (0, react.useEffect)(() => { if (!require_provider.canUseDOM() || !previousProps) return; if (previousProps.src !== src) { if (!src) { handleError(/* @__PURE__ */ new Error("Missing src")); return; } setState({ content: "", element: null, isCached: false, status: require_provider.STATUS.LOADING }); } }, [ handleError, previousProps, src ]); (0, react.useEffect)(() => { if (status !== require_provider.STATUS.LOADING) return; const controller = new AbortController(); let active = true; (async () => { try { const dataURI = /^data:image\/svg[^,]*?(;base64)?,(.*)/.exec(src); let inlineSrc; if (dataURI) inlineSrc = dataURI[1] ? window.atob(dataURI[2]) : decodeURIComponent(dataURI[2]); else if (src.includes("<svg")) inlineSrc = src; if (inlineSrc) { if (active) setState({ content: inlineSrc, isCached: false, status: require_provider.STATUS.LOADED }); return; } const fetchParameters = { ...fetchOptionsRef.current, signal: controller.signal }; let loadedContent; let hasCache = false; if (cacheRequests) { hasCache = cacheStore.isCached(src); loadedContent = await cacheStore.get(src, fetchParameters); } else loadedContent = await require_provider.request(src, fetchParameters); if (active) setState({ content: loadedContent, isCached: hasCache, status: require_provider.STATUS.LOADED }); } catch (error) { if (active && error.name !== "AbortError") handleError(error); } })(); return () => { active = false; controller.abort(); }; }, [ cacheRequests, cacheStore, handleError, src, status ]); (0, react.useEffect)(() => { if (status === require_provider.STATUS.LOADED && content) getElement(); }, [ content, getElement, status ]); (0, react.useEffect)(() => { if (!require_provider.canUseDOM() || !previousProps || previousProps.src !== src) return; if (previousProps.title !== title || previousProps.description !== description) getElement(); }, [ description, getElement, previousProps, src, title ]); (0, react.useEffect)(() => { if (!previousState) return; if (status === require_provider.STATUS.READY && previousState.status !== require_provider.STATUS.READY) onLoadRef.current?.(src, isCached); }, [ isCached, previousState, src, status ]); return { element, status }; } //#endregion //#region src/index.tsx const cacheStore = new require_provider.CacheStore(); function InlineSVG(props) { const { children = null, innerRef, loader = null } = props; const { element, status } = useInlineSVG(props, require_provider.useCacheStore() ?? cacheStore); if (!require_provider.canUseDOM()) return loader; if (element) return (0, react.cloneElement)(element, { ref: innerRef, ...require_provider.omit(props, "baseURL", "cacheRequests", "children", "description", "fetchOptions", "innerRef", "loader", "onError", "onLoad", "preProcessor", "src", "title", "uniqueHash", "uniquifyIDs") }); if ([require_provider.STATUS.UNSUPPORTED, require_provider.STATUS.FAILED].includes(status)) return children; return loader; } //#endregion exports.cacheStore = cacheStore; exports.default = InlineSVG; //# sourceMappingURL=index.cjs.map