react-inlinesvg
Advanced tools
Comparing version 4.0.6 to 4.1.0-0
@@ -17,3 +17,3 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
type PreProcessorCallback = (code: string) => string; | ||
interface Props extends Omit<React.SVGProps<SVGElement>, 'onLoad' | 'onError' | 'ref'> { | ||
type Props = Simplify<Omit<React.SVGProps<SVGElement>, 'onLoad' | 'onError' | 'ref'> & { | ||
baseURL?: string; | ||
@@ -33,3 +33,3 @@ cacheRequests?: boolean; | ||
uniquifyIDs?: boolean; | ||
} | ||
}>; | ||
interface State { | ||
@@ -47,2 +47,5 @@ content: string; | ||
} | ||
type Simplify<T> = { | ||
[KeyType in keyof T]: T[KeyType]; | ||
} & {}; | ||
type Status = (typeof STATUS)[keyof typeof STATUS]; | ||
@@ -76,3 +79,3 @@ interface StorageItem { | ||
export { ErrorCallback, FetchError, LoadCallback, PlainObject, PreProcessorCallback, Props, State, Status, StorageItem, cacheStore, InlineSVG as default }; | ||
export { ErrorCallback, FetchError, LoadCallback, PlainObject, PreProcessorCallback, Props, Simplify, State, Status, StorageItem, cacheStore, InlineSVG as default }; | ||
export = InlineSVG |
@@ -42,3 +42,3 @@ "use strict"; | ||
module.exports = __toCommonJS(src_exports); | ||
var React = __toESM(require("react")); | ||
var import_react2 = require("react"); | ||
var import_react_from_dom = __toESM(require("react-from-dom")); | ||
@@ -242,255 +242,298 @@ | ||
// src/hooks.tsx | ||
var import_react = require("react"); | ||
function usePrevious(state) { | ||
const ref = (0, import_react.useRef)(); | ||
(0, import_react.useEffect)(() => { | ||
ref.current = state; | ||
}); | ||
return ref.current; | ||
} | ||
// src/index.tsx | ||
var import_jsx_runtime = require("react/jsx-runtime"); | ||
var cacheStore; | ||
var ReactInlineSVG = class extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
__publicField(this, "hash"); | ||
__publicField(this, "isActive", false); | ||
__publicField(this, "isInitialized", false); | ||
__publicField(this, "fetchContent", async () => { | ||
const { fetchOptions, src } = this.props; | ||
const content = await request(src, fetchOptions); | ||
this.handleLoad(content); | ||
}); | ||
__publicField(this, "handleError", (error) => { | ||
const { onError } = this.props; | ||
const status = error.message === "Browser does not support SVG" ? STATUS.UNSUPPORTED : STATUS.FAILED; | ||
if (this.isActive) { | ||
this.setState({ status }, () => { | ||
if (typeof onError === "function") { | ||
onError(error); | ||
} | ||
}); | ||
} | ||
}); | ||
__publicField(this, "handleLoad", (content, hasCache = false) => { | ||
if (this.isActive) { | ||
this.setState( | ||
{ | ||
content, | ||
isCached: hasCache, | ||
status: STATUS.LOADED | ||
}, | ||
this.getElement | ||
); | ||
} | ||
}); | ||
this.state = { | ||
content: "", | ||
element: null, | ||
isCached: !!props.cacheRequests && cacheStore.isCached(props.src), | ||
status: STATUS.IDLE | ||
}; | ||
this.hash = props.uniqueHash ?? randomString(8); | ||
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; | ||
} | ||
componentDidMount() { | ||
this.isActive = true; | ||
if (!canUseDOM() || this.isInitialized) { | ||
return; | ||
} | ||
const { status } = this.state; | ||
const { src } = this.props; | ||
try { | ||
if (status === STATUS.IDLE) { | ||
if (!isSupportedEnvironment()) { | ||
throw new Error("Browser does not support SVG"); | ||
[...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})`); | ||
} | ||
if (!src) { | ||
throw new Error("Missing src"); | ||
return attribute; | ||
}); | ||
replaceableAttributes.forEach((r) => { | ||
const attribute = attributes.find((a) => a.name === r); | ||
if (attribute && !isDataValue(r, attribute.value)) { | ||
attribute.value = `${attribute.value}__${hash}`; | ||
} | ||
this.load(); | ||
} | ||
} catch (error) { | ||
this.handleError(error); | ||
}); | ||
} | ||
this.isInitialized = true; | ||
} | ||
componentDidUpdate(previousProps, previousState) { | ||
if (!canUseDOM()) { | ||
return; | ||
if (d.children.length) { | ||
return updateSVGAttributes(d, options); | ||
} | ||
const { isCached, status } = this.state; | ||
const { description, onLoad, src, title } = this.props; | ||
if (previousState.status !== STATUS.READY && status === STATUS.READY) { | ||
if (onLoad) { | ||
onLoad(src, isCached); | ||
return d; | ||
}); | ||
return node; | ||
} | ||
function getNode(options) { | ||
const { | ||
baseURL, | ||
content, | ||
description, | ||
handleError, | ||
hash, | ||
preProcessor, | ||
title, | ||
uniquifyIDs = false | ||
} = options; | ||
try { | ||
const svgText = processSVG(content, preProcessor); | ||
const node = (0, import_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 (previousProps.src !== src) { | ||
if (!src) { | ||
this.handleError(new Error("Missing src")); | ||
return; | ||
if (typeof title !== "undefined") { | ||
const originalTitle = svg.querySelector("title"); | ||
if (originalTitle?.parentNode) { | ||
originalTitle.parentNode.removeChild(originalTitle); | ||
} | ||
this.load(); | ||
if (title) { | ||
const titleElement = document.createElementNS("http://www.w3.org/2000/svg", "title"); | ||
titleElement.innerHTML = title; | ||
svg.prepend(titleElement); | ||
} | ||
} | ||
if (previousProps.title !== title || previousProps.description !== description) { | ||
this.getElement(); | ||
} | ||
return svg; | ||
} catch (error) { | ||
return handleError(error); | ||
} | ||
componentWillUnmount() { | ||
this.isActive = false; | ||
} | ||
function processSVG(content, preProcessor) { | ||
if (preProcessor) { | ||
return preProcessor(content); | ||
} | ||
getElement() { | ||
return content; | ||
} | ||
function ReactInlineSVG(props) { | ||
const { | ||
cacheRequests = true, | ||
children = null, | ||
description, | ||
fetchOptions, | ||
innerRef, | ||
loader = null, | ||
onError, | ||
onLoad, | ||
src, | ||
title, | ||
uniqueHash | ||
} = props; | ||
const [state, setState] = (0, import_react2.useReducer)( | ||
(previousState2, nextState) => ({ | ||
...previousState2, | ||
...nextState | ||
}), | ||
{ | ||
content: "", | ||
element: null, | ||
isCached: cacheRequests && cacheStore.isCached(props.src), | ||
status: STATUS.IDLE | ||
} | ||
); | ||
const { content, element, isCached, status } = state; | ||
const previousProps = usePrevious(props); | ||
const previousState = usePrevious(state); | ||
const hash = (0, import_react2.useRef)(uniqueHash ?? randomString(8)); | ||
const isActive = (0, import_react2.useRef)(false); | ||
const isInitialized = (0, import_react2.useRef)(false); | ||
const handleError = (0, import_react2.useCallback)( | ||
(error) => { | ||
if (isActive.current) { | ||
setState({ | ||
status: error.message === "Browser does not support SVG" ? STATUS.UNSUPPORTED : STATUS.FAILED | ||
}); | ||
onError?.(error); | ||
} | ||
}, | ||
[onError] | ||
); | ||
const handleLoad = (0, import_react2.useCallback)((loadedContent, hasCache = false) => { | ||
if (isActive.current) { | ||
setState({ | ||
content: loadedContent, | ||
isCached: hasCache, | ||
status: STATUS.LOADED | ||
}); | ||
} | ||
}, []); | ||
const getElement = (0, import_react2.useCallback)(() => { | ||
try { | ||
const node = this.getNode(); | ||
const element = (0, import_react_from_dom.default)(node); | ||
if (!element || !React.isValidElement(element)) { | ||
const node = getNode({ ...props, handleError, hash: hash.current, content }); | ||
const convertedElement = (0, import_react_from_dom.default)(node); | ||
if (!convertedElement || !(0, import_react2.isValidElement)(convertedElement)) { | ||
throw new Error("Could not convert the src to a React element"); | ||
} | ||
this.setState({ | ||
element, | ||
setState({ | ||
element: convertedElement, | ||
status: STATUS.READY | ||
}); | ||
} catch (error) { | ||
this.handleError(new Error(error.message)); | ||
handleError(new Error(error.message)); | ||
} | ||
} | ||
getNode() { | ||
const { description, title } = this.props; | ||
}, [content, handleError, props]); | ||
const fetchContent = (0, import_react2.useCallback)(async () => { | ||
const responseContent = await request(src, fetchOptions); | ||
handleLoad(responseContent); | ||
}, [fetchOptions, handleLoad, src]); | ||
const getContent = (0, import_react2.useCallback)(async () => { | ||
const dataURI = /^data:image\/svg[^,]*?(;base64)?,(.*)/u.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) { | ||
handleLoad(inlineSrc); | ||
return; | ||
} | ||
try { | ||
const svgText = this.processSVG(); | ||
const node = (0, import_react_from_dom.default)(svgText, { nodeOnly: true }); | ||
if (!node || !(node instanceof SVGSVGElement)) { | ||
throw new Error("Could not convert the src to a DOM Node"); | ||
if (cacheRequests) { | ||
const cachedContent = await cacheStore.get(src, fetchOptions); | ||
handleLoad(cachedContent, true); | ||
} else { | ||
await fetchContent(); | ||
} | ||
const svg = this.updateSVGAttributes(node); | ||
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 this.handleError(error); | ||
handleError(error); | ||
} | ||
} | ||
load() { | ||
if (this.isActive) { | ||
this.setState( | ||
{ | ||
content: "", | ||
element: null, | ||
isCached: false, | ||
status: STATUS.LOADING | ||
}, | ||
async () => { | ||
const { cacheRequests, fetchOptions, src } = this.props; | ||
const dataURI = /^data:image\/svg[^,]*?(;base64)?,(.*)/u.exec(src); | ||
let inlineSrc; | ||
if (dataURI) { | ||
inlineSrc = dataURI[1] ? window.atob(dataURI[2]) : decodeURIComponent(dataURI[2]); | ||
} else if (src.includes("<svg")) { | ||
inlineSrc = src; | ||
}, [cacheRequests, fetchContent, fetchOptions, handleError, handleLoad, src]); | ||
const load = (0, import_react2.useCallback)(async () => { | ||
if (isActive.current) { | ||
setState({ | ||
content: "", | ||
element: null, | ||
isCached: false, | ||
status: STATUS.LOADING | ||
}); | ||
} | ||
}, []); | ||
(0, import_react2.useEffect)( | ||
() => { | ||
isActive.current = true; | ||
if (!canUseDOM() || isInitialized.current) { | ||
return () => void 0; | ||
} | ||
try { | ||
if (status === STATUS.IDLE) { | ||
if (!isSupportedEnvironment()) { | ||
throw new Error("Browser does not support SVG"); | ||
} | ||
if (inlineSrc) { | ||
this.handleLoad(inlineSrc); | ||
return; | ||
if (!src) { | ||
throw new Error("Missing src"); | ||
} | ||
try { | ||
if (cacheRequests) { | ||
const content = await cacheStore.get(src, fetchOptions); | ||
this.handleLoad(content, true); | ||
} else { | ||
await this.fetchContent(); | ||
} | ||
} catch (error) { | ||
this.handleError(error); | ||
} | ||
load(); | ||
} | ||
); | ||
} catch (error) { | ||
handleError(error); | ||
} | ||
isInitialized.current = true; | ||
return () => { | ||
isActive.current = false; | ||
}; | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[] | ||
); | ||
(0, import_react2.useEffect)(() => { | ||
if (!canUseDOM()) { | ||
return; | ||
} | ||
} | ||
processSVG() { | ||
const { content } = this.state; | ||
const { preProcessor } = this.props; | ||
if (preProcessor) { | ||
return preProcessor(content); | ||
if (!previousProps) { | ||
return; | ||
} | ||
return content; | ||
} | ||
updateSVGAttributes(node) { | ||
const { baseURL = "", uniquifyIDs } = this.props; | ||
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]}__${this.hash})`); | ||
} | ||
return attribute; | ||
}); | ||
replaceableAttributes.forEach((r) => { | ||
const attribute = attributes.find((a) => a.name === r); | ||
if (attribute && !isDataValue(r, attribute.value)) { | ||
attribute.value = `${attribute.value}__${this.hash}`; | ||
} | ||
}); | ||
if (previousProps.src !== src) { | ||
if (!src) { | ||
handleError(new Error("Missing src")); | ||
return; | ||
} | ||
if (d.children.length) { | ||
return this.updateSVGAttributes(d); | ||
} | ||
return d; | ||
}); | ||
return node; | ||
} | ||
render() { | ||
const { element, status } = this.state; | ||
const { children = null, innerRef, loader = null } = this.props; | ||
const elementProps = omit( | ||
this.props, | ||
"baseURL", | ||
"cacheRequests", | ||
"children", | ||
"description", | ||
"fetchOptions", | ||
"innerRef", | ||
"loader", | ||
"onError", | ||
"onLoad", | ||
"preProcessor", | ||
"src", | ||
"title", | ||
"uniqueHash", | ||
"uniquifyIDs" | ||
); | ||
if (!canUseDOM()) { | ||
return loader; | ||
load(); | ||
} else if (previousProps.title !== title || previousProps.description !== description) { | ||
getElement(); | ||
} | ||
if (element) { | ||
return React.cloneElement(element, { ref: innerRef, ...elementProps }); | ||
}, [ | ||
description, | ||
getElement, | ||
handleError, | ||
isCached, | ||
load, | ||
onLoad, | ||
previousProps, | ||
previousState, | ||
src, | ||
status, | ||
title | ||
]); | ||
(0, import_react2.useEffect)(() => { | ||
if (!previousState) { | ||
return; | ||
} | ||
if ([STATUS.UNSUPPORTED, STATUS.FAILED].includes(status)) { | ||
return children; | ||
if (previousState.status !== STATUS.LOADING && status === STATUS.LOADING) { | ||
getContent(); | ||
} | ||
if (previousState.status !== STATUS.LOADED && status === STATUS.LOADED) { | ||
getElement(); | ||
} | ||
if (previousState.status !== STATUS.READY && status === STATUS.READY) { | ||
onLoad?.(src, isCached); | ||
} | ||
}, [getContent, getElement, isCached, onLoad, previousState, src, status]); | ||
const elementProps = omit( | ||
props, | ||
"baseURL", | ||
"cacheRequests", | ||
"children", | ||
"description", | ||
"fetchOptions", | ||
"innerRef", | ||
"loader", | ||
"onError", | ||
"onLoad", | ||
"preProcessor", | ||
"src", | ||
"title", | ||
"uniqueHash", | ||
"uniquifyIDs" | ||
); | ||
if (!canUseDOM()) { | ||
return loader; | ||
} | ||
}; | ||
__publicField(ReactInlineSVG, "defaultProps", { | ||
cacheRequests: true, | ||
uniquifyIDs: false | ||
}); | ||
if (element) { | ||
return (0, import_react2.cloneElement)(element, { ref: innerRef, ...elementProps }); | ||
} | ||
if ([STATUS.UNSUPPORTED, STATUS.FAILED].includes(status)) { | ||
return children; | ||
} | ||
return loader; | ||
} | ||
function InlineSVG(props) { | ||
@@ -501,5 +544,5 @@ if (!cacheStore) { | ||
const { loader } = props; | ||
const hasCallback = React.useRef(false); | ||
const [isReady, setReady] = React.useState(cacheStore.isReady); | ||
React.useEffect(() => { | ||
const hasCallback = (0, import_react2.useRef)(false); | ||
const [isReady, setReady] = (0, import_react2.useState)(cacheStore.isReady); | ||
(0, import_react2.useEffect)(() => { | ||
if (!hasCallback.current) { | ||
@@ -506,0 +549,0 @@ cacheStore.onReady(() => { |
{ | ||
"name": "react-inlinesvg", | ||
"version": "4.0.6", | ||
"version": "4.1.0-0", | ||
"description": "An SVG loader for React", | ||
@@ -51,3 +51,3 @@ "author": "Gil Barbara <gilbarbara@gmail.com>", | ||
"peerDependencies": { | ||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" | ||
"react": "16.8 - 18" | ||
}, | ||
@@ -59,14 +59,11 @@ "dependencies": { | ||
"@gilbarbara/eslint-config": "^0.5.4", | ||
"@gilbarbara/helpers": "^0.8.6", | ||
"@gilbarbara/prettier-config": "^1.0.0", | ||
"@gilbarbara/tsconfig": "^0.2.3", | ||
"@size-limit/preset-small-lib": "^9.0.0", | ||
"@size-limit/preset-small-lib": "^10.0.1", | ||
"@testing-library/jest-dom": "^6.1.4", | ||
"@testing-library/react": "^14.0.0", | ||
"@types/exenv": "^1.2.0", | ||
"@types/fetch-mock": "^7.3.6", | ||
"@types/node": "^20.8.2", | ||
"@types/node-fetch": "^2.6.6", | ||
"@types/react": "^18.2.24", | ||
"@types/react-dom": "^18.2.8", | ||
"@types/node": "^20.8.8", | ||
"@types/react": "^18.2.31", | ||
"@types/react-dom": "^18.2.14", | ||
"@vitejs/plugin-react": "^4.1.0", | ||
"@vitest/coverage-v8": "^0.34.6", | ||
@@ -82,4 +79,4 @@ "browser-cache-mock": "^0.1.7", | ||
"react-dom": "^18.2.0", | ||
"repo-tools": "^0.2.2", | ||
"size-limit": "^9.0.0", | ||
"repo-tools": "^0.3.1", | ||
"size-limit": "^10.0.1", | ||
"start-server-and-test": "^2.0.1", | ||
@@ -86,0 +83,0 @@ "ts-node": "^10.9.1", |
@@ -10,18 +10,20 @@ import * as React from 'react'; | ||
export interface Props extends Omit<React.SVGProps<SVGElement>, 'onLoad' | 'onError' | 'ref'> { | ||
baseURL?: string; | ||
cacheRequests?: boolean; | ||
children?: React.ReactNode; | ||
description?: string; | ||
fetchOptions?: RequestInit; | ||
innerRef?: React.Ref<SVGElement>; | ||
loader?: React.ReactNode; | ||
onError?: ErrorCallback; | ||
onLoad?: LoadCallback; | ||
preProcessor?: PreProcessorCallback; | ||
src: string; | ||
title?: string | null; | ||
uniqueHash?: string; | ||
uniquifyIDs?: boolean; | ||
} | ||
export type Props = Simplify< | ||
Omit<React.SVGProps<SVGElement>, 'onLoad' | 'onError' | 'ref'> & { | ||
baseURL?: string; | ||
cacheRequests?: boolean; | ||
children?: React.ReactNode; | ||
description?: string; | ||
fetchOptions?: RequestInit; | ||
innerRef?: React.Ref<SVGElement>; | ||
loader?: React.ReactNode; | ||
onError?: ErrorCallback; | ||
onLoad?: LoadCallback; | ||
preProcessor?: PreProcessorCallback; | ||
src: string; | ||
title?: string | null; | ||
uniqueHash?: string; | ||
uniquifyIDs?: boolean; | ||
} | ||
>; | ||
@@ -42,2 +44,4 @@ export interface State { | ||
export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {}; | ||
export type Status = (typeof STATUS)[keyof typeof STATUS]; | ||
@@ -44,0 +48,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
131657
28
23
1842
1