react-inlinesvg 4.0.5 → 4.1.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,7 +6,15 @@ var __publicField = (obj, key, value) => {
6
6
  };
7
7
 
8
8
  // src/index.tsx
9
- import * as React from "react";
9
+ import {
10
+ cloneElement,
11
+ isValidElement,
12
+ useCallback,
13
+ useEffect as useEffect2,
14
+ useReducer,
15
+ useRef as useRef2,
16
+ useState
17
+ } from "react";
10
18
  import convert from "react-from-dom";
11
19
 
12
20
  // src/config.ts
@@ -91,13 +99,16 @@ var CacheStore = class {
91
99
  let usePersistentCache = false;
92
100
  if (canUseDOM()) {
93
101
  cacheName = window.REACT_INLINESVG_CACHE_NAME ?? CACHE_NAME;
94
- usePersistentCache = !!window.REACT_INLINESVG_PERSISTENT_CACHE;
102
+ usePersistentCache = !!window.REACT_INLINESVG_PERSISTENT_CACHE && "caches" in window;
95
103
  }
96
104
  if (usePersistentCache) {
97
105
  caches.open(cacheName).then((cache) => {
98
106
  this.cacheApi = cache;
99
107
  this.isReady = true;
100
108
  this.subscribers.forEach((callback) => callback());
109
+ }).catch((error) => {
110
+ this.isReady = true;
111
+ console.error(`Failed to open cache: ${error.message}`);
101
112
  });
102
113
  } else {
103
114
  this.isReady = true;
@@ -202,267 +213,310 @@ var CacheStore = class {
202
213
  }
203
214
  };
204
215
 
216
+ // src/hooks.tsx
217
+ import { useEffect, useRef } from "react";
218
+ function usePrevious(state) {
219
+ const ref = useRef();
220
+ useEffect(() => {
221
+ ref.current = state;
222
+ });
223
+ return ref.current;
224
+ }
225
+
205
226
  // src/index.tsx
206
227
  import { jsx } from "react/jsx-runtime";
207
228
  var cacheStore;
208
- var ReactInlineSVG = class extends React.PureComponent {
209
- constructor(props) {
210
- super(props);
211
- __publicField(this, "hash");
212
- __publicField(this, "isActive", false);
213
- __publicField(this, "isInitialized", false);
214
- __publicField(this, "fetchContent", async () => {
215
- const { fetchOptions, src } = this.props;
216
- const content = await request(src, fetchOptions);
217
- this.handleLoad(content);
218
- });
219
- __publicField(this, "handleError", (error) => {
220
- const { onError } = this.props;
221
- const status = error.message === "Browser does not support SVG" ? STATUS.UNSUPPORTED : STATUS.FAILED;
222
- if (this.isActive) {
223
- this.setState({ status }, () => {
224
- if (typeof onError === "function") {
225
- onError(error);
226
- }
227
- });
228
- }
229
- });
230
- __publicField(this, "handleLoad", (content, hasCache = false) => {
231
- if (this.isActive) {
232
- this.setState(
233
- {
234
- content,
235
- isCached: hasCache,
236
- status: STATUS.LOADED
237
- },
238
- this.getElement
239
- );
240
- }
241
- });
242
- this.state = {
243
- content: "",
244
- element: null,
245
- isCached: !!props.cacheRequests && cacheStore.isCached(props.src),
246
- status: STATUS.IDLE
247
- };
248
- this.hash = props.uniqueHash ?? randomString(8);
229
+ function updateSVGAttributes(node, options) {
230
+ const { baseURL = "", hash, uniquifyIDs } = options;
231
+ const replaceableAttributes = ["id", "href", "xlink:href", "xlink:role", "xlink:arcrole"];
232
+ const linkAttributes = ["href", "xlink:href"];
233
+ const isDataValue = (name, value) => linkAttributes.includes(name) && (value ? !value.includes("#") : false);
234
+ if (!uniquifyIDs) {
235
+ return node;
249
236
  }
250
- componentDidMount() {
251
- this.isActive = true;
252
- if (!canUseDOM() || this.isInitialized) {
253
- return;
254
- }
255
- const { status } = this.state;
256
- const { src } = this.props;
257
- try {
258
- if (status === STATUS.IDLE) {
259
- if (!isSupportedEnvironment()) {
260
- throw new Error("Browser does not support SVG");
237
+ [...node.children].forEach((d) => {
238
+ if (d.attributes?.length) {
239
+ const attributes = Object.values(d.attributes).map((a) => {
240
+ const attribute = a;
241
+ const match = /url\((.*?)\)/.exec(a.value);
242
+ if (match?.[1]) {
243
+ attribute.value = a.value.replace(match[0], `url(${baseURL}${match[1]}__${hash})`);
261
244
  }
262
- if (!src) {
263
- throw new Error("Missing src");
245
+ return attribute;
246
+ });
247
+ replaceableAttributes.forEach((r) => {
248
+ const attribute = attributes.find((a) => a.name === r);
249
+ if (attribute && !isDataValue(r, attribute.value)) {
250
+ attribute.value = `${attribute.value}__${hash}`;
264
251
  }
265
- this.load();
266
- }
267
- } catch (error) {
268
- this.handleError(error);
252
+ });
269
253
  }
270
- this.isInitialized = true;
271
- }
272
- componentDidUpdate(previousProps, previousState) {
273
- if (!canUseDOM()) {
274
- return;
254
+ if (d.children.length) {
255
+ return updateSVGAttributes(d, options);
275
256
  }
276
- const { isCached, status } = this.state;
277
- const { description, onLoad, src, title } = this.props;
278
- if (previousState.status !== STATUS.READY && status === STATUS.READY) {
279
- if (onLoad) {
280
- onLoad(src, isCached);
281
- }
257
+ return d;
258
+ });
259
+ return node;
260
+ }
261
+ function getNode(options) {
262
+ const {
263
+ baseURL,
264
+ content,
265
+ description,
266
+ handleError,
267
+ hash,
268
+ preProcessor,
269
+ title,
270
+ uniquifyIDs = false
271
+ } = options;
272
+ try {
273
+ const svgText = processSVG(content, preProcessor);
274
+ const node = convert(svgText, { nodeOnly: true });
275
+ if (!node || !(node instanceof SVGSVGElement)) {
276
+ throw new Error("Could not convert the src to a DOM Node");
282
277
  }
283
- if (previousProps.src !== src) {
284
- if (!src) {
285
- this.handleError(new Error("Missing src"));
286
- return;
278
+ const svg = updateSVGAttributes(node, { baseURL, hash, uniquifyIDs });
279
+ if (description) {
280
+ const originalDesc = svg.querySelector("desc");
281
+ if (originalDesc?.parentNode) {
282
+ originalDesc.parentNode.removeChild(originalDesc);
287
283
  }
288
- this.load();
284
+ const descElement = document.createElementNS("http://www.w3.org/2000/svg", "desc");
285
+ descElement.innerHTML = description;
286
+ svg.prepend(descElement);
289
287
  }
290
- if (previousProps.title !== title || previousProps.description !== description) {
291
- this.getElement();
288
+ if (typeof title !== "undefined") {
289
+ const originalTitle = svg.querySelector("title");
290
+ if (originalTitle?.parentNode) {
291
+ originalTitle.parentNode.removeChild(originalTitle);
292
+ }
293
+ if (title) {
294
+ const titleElement = document.createElementNS("http://www.w3.org/2000/svg", "title");
295
+ titleElement.innerHTML = title;
296
+ svg.prepend(titleElement);
297
+ }
292
298
  }
299
+ return svg;
300
+ } catch (error) {
301
+ return handleError(error);
293
302
  }
294
- componentWillUnmount() {
295
- this.isActive = false;
303
+ }
304
+ function processSVG(content, preProcessor) {
305
+ if (preProcessor) {
306
+ return preProcessor(content);
296
307
  }
297
- getElement() {
308
+ return content;
309
+ }
310
+ function ReactInlineSVG(props) {
311
+ const {
312
+ cacheRequests = true,
313
+ children = null,
314
+ description,
315
+ fetchOptions,
316
+ innerRef,
317
+ loader = null,
318
+ onError,
319
+ onLoad,
320
+ src,
321
+ title,
322
+ uniqueHash
323
+ } = props;
324
+ const [state, setState] = useReducer(
325
+ (previousState2, nextState) => ({
326
+ ...previousState2,
327
+ ...nextState
328
+ }),
329
+ {
330
+ content: "",
331
+ element: null,
332
+ isCached: cacheRequests && cacheStore.isCached(props.src),
333
+ status: STATUS.IDLE
334
+ }
335
+ );
336
+ const { content, element, isCached, status } = state;
337
+ const previousProps = usePrevious(props);
338
+ const previousState = usePrevious(state);
339
+ const hash = useRef2(uniqueHash ?? randomString(8));
340
+ const isActive = useRef2(false);
341
+ const isInitialized = useRef2(false);
342
+ const handleError = useCallback(
343
+ (error) => {
344
+ if (isActive.current) {
345
+ setState({
346
+ status: error.message === "Browser does not support SVG" ? STATUS.UNSUPPORTED : STATUS.FAILED
347
+ });
348
+ onError?.(error);
349
+ }
350
+ },
351
+ [onError]
352
+ );
353
+ const handleLoad = useCallback((loadedContent, hasCache = false) => {
354
+ if (isActive.current) {
355
+ setState({
356
+ content: loadedContent,
357
+ isCached: hasCache,
358
+ status: STATUS.LOADED
359
+ });
360
+ }
361
+ }, []);
362
+ const getElement = useCallback(() => {
298
363
  try {
299
- const node = this.getNode();
300
- const element = convert(node);
301
- if (!element || !React.isValidElement(element)) {
364
+ const node = getNode({ ...props, handleError, hash: hash.current, content });
365
+ const convertedElement = convert(node);
366
+ if (!convertedElement || !isValidElement(convertedElement)) {
302
367
  throw new Error("Could not convert the src to a React element");
303
368
  }
304
- this.setState({
305
- element,
369
+ setState({
370
+ element: convertedElement,
306
371
  status: STATUS.READY
307
372
  });
308
373
  } catch (error) {
309
- this.handleError(new Error(error.message));
374
+ handleError(new Error(error.message));
375
+ }
376
+ }, [content, handleError, props]);
377
+ const fetchContent = useCallback(async () => {
378
+ const responseContent = await request(src, fetchOptions);
379
+ handleLoad(responseContent);
380
+ }, [fetchOptions, handleLoad, src]);
381
+ const getContent = useCallback(async () => {
382
+ const dataURI = /^data:image\/svg[^,]*?(;base64)?,(.*)/u.exec(src);
383
+ let inlineSrc;
384
+ if (dataURI) {
385
+ inlineSrc = dataURI[1] ? window.atob(dataURI[2]) : decodeURIComponent(dataURI[2]);
386
+ } else if (src.includes("<svg")) {
387
+ inlineSrc = src;
388
+ }
389
+ if (inlineSrc) {
390
+ handleLoad(inlineSrc);
391
+ return;
310
392
  }
311
- }
312
- getNode() {
313
- const { description, title } = this.props;
314
393
  try {
315
- const svgText = this.processSVG();
316
- const node = convert(svgText, { nodeOnly: true });
317
- if (!node || !(node instanceof SVGSVGElement)) {
318
- throw new Error("Could not convert the src to a DOM Node");
319
- }
320
- const svg = this.updateSVGAttributes(node);
321
- if (description) {
322
- const originalDesc = svg.querySelector("desc");
323
- if (originalDesc?.parentNode) {
324
- originalDesc.parentNode.removeChild(originalDesc);
325
- }
326
- const descElement = document.createElementNS("http://www.w3.org/2000/svg", "desc");
327
- descElement.innerHTML = description;
328
- svg.prepend(descElement);
394
+ if (cacheRequests) {
395
+ const cachedContent = await cacheStore.get(src, fetchOptions);
396
+ handleLoad(cachedContent, true);
397
+ } else {
398
+ await fetchContent();
329
399
  }
330
- if (typeof title !== "undefined") {
331
- const originalTitle = svg.querySelector("title");
332
- if (originalTitle?.parentNode) {
333
- originalTitle.parentNode.removeChild(originalTitle);
334
- }
335
- if (title) {
336
- const titleElement = document.createElementNS("http://www.w3.org/2000/svg", "title");
337
- titleElement.innerHTML = title;
338
- svg.prepend(titleElement);
339
- }
340
- }
341
- return svg;
342
400
  } catch (error) {
343
- return this.handleError(error);
401
+ handleError(error);
344
402
  }
345
- }
346
- load() {
347
- if (this.isActive) {
348
- this.setState(
349
- {
350
- content: "",
351
- element: null,
352
- isCached: false,
353
- status: STATUS.LOADING
354
- },
355
- async () => {
356
- const { cacheRequests, fetchOptions, src } = this.props;
357
- const dataURI = /^data:image\/svg[^,]*?(;base64)?,(.*)/u.exec(src);
358
- let inlineSrc;
359
- if (dataURI) {
360
- inlineSrc = dataURI[1] ? window.atob(dataURI[2]) : decodeURIComponent(dataURI[2]);
361
- } else if (src.includes("<svg")) {
362
- inlineSrc = src;
363
- }
364
- if (inlineSrc) {
365
- this.handleLoad(inlineSrc);
366
- return;
403
+ }, [cacheRequests, fetchContent, fetchOptions, handleError, handleLoad, src]);
404
+ const load = useCallback(async () => {
405
+ if (isActive.current) {
406
+ setState({
407
+ content: "",
408
+ element: null,
409
+ isCached: false,
410
+ status: STATUS.LOADING
411
+ });
412
+ }
413
+ }, []);
414
+ useEffect2(
415
+ () => {
416
+ isActive.current = true;
417
+ if (!canUseDOM() || isInitialized.current) {
418
+ return () => void 0;
419
+ }
420
+ try {
421
+ if (status === STATUS.IDLE) {
422
+ if (!isSupportedEnvironment()) {
423
+ throw new Error("Browser does not support SVG");
367
424
  }
368
- try {
369
- if (cacheRequests) {
370
- const content = await cacheStore.get(src, fetchOptions);
371
- this.handleLoad(content, true);
372
- } else {
373
- await this.fetchContent();
374
- }
375
- } catch (error) {
376
- this.handleError(error);
425
+ if (!src) {
426
+ throw new Error("Missing src");
377
427
  }
428
+ load();
378
429
  }
379
- );
430
+ } catch (error) {
431
+ handleError(error);
432
+ }
433
+ isInitialized.current = true;
434
+ return () => {
435
+ isActive.current = false;
436
+ };
437
+ },
438
+ // eslint-disable-next-line react-hooks/exhaustive-deps
439
+ []
440
+ );
441
+ useEffect2(() => {
442
+ if (!canUseDOM()) {
443
+ return;
380
444
  }
381
- }
382
- processSVG() {
383
- const { content } = this.state;
384
- const { preProcessor } = this.props;
385
- if (preProcessor) {
386
- return preProcessor(content);
445
+ if (!previousProps) {
446
+ return;
387
447
  }
388
- return content;
389
- }
390
- updateSVGAttributes(node) {
391
- const { baseURL = "", uniquifyIDs } = this.props;
392
- const replaceableAttributes = ["id", "href", "xlink:href", "xlink:role", "xlink:arcrole"];
393
- const linkAttributes = ["href", "xlink:href"];
394
- const isDataValue = (name, value) => linkAttributes.includes(name) && (value ? !value.includes("#") : false);
395
- if (!uniquifyIDs) {
396
- return node;
397
- }
398
- [...node.children].forEach((d) => {
399
- if (d.attributes?.length) {
400
- const attributes = Object.values(d.attributes).map((a) => {
401
- const attribute = a;
402
- const match = /url\((.*?)\)/.exec(a.value);
403
- if (match?.[1]) {
404
- attribute.value = a.value.replace(match[0], `url(${baseURL}${match[1]}__${this.hash})`);
405
- }
406
- return attribute;
407
- });
408
- replaceableAttributes.forEach((r) => {
409
- const attribute = attributes.find((a) => a.name === r);
410
- if (attribute && !isDataValue(r, attribute.value)) {
411
- attribute.value = `${attribute.value}__${this.hash}`;
412
- }
413
- });
414
- }
415
- if (d.children.length) {
416
- return this.updateSVGAttributes(d);
448
+ if (previousProps.src !== src) {
449
+ if (!src) {
450
+ handleError(new Error("Missing src"));
451
+ return;
417
452
  }
418
- return d;
419
- });
420
- return node;
421
- }
422
- render() {
423
- const { element, status } = this.state;
424
- const { children = null, innerRef, loader = null } = this.props;
425
- const elementProps = omit(
426
- this.props,
427
- "baseURL",
428
- "cacheRequests",
429
- "children",
430
- "description",
431
- "fetchOptions",
432
- "innerRef",
433
- "loader",
434
- "onError",
435
- "onLoad",
436
- "preProcessor",
437
- "src",
438
- "title",
439
- "uniqueHash",
440
- "uniquifyIDs"
441
- );
442
- if (!canUseDOM()) {
443
- return loader;
453
+ load();
454
+ } else if (previousProps.title !== title || previousProps.description !== description) {
455
+ getElement();
456
+ }
457
+ }, [
458
+ description,
459
+ getElement,
460
+ handleError,
461
+ isCached,
462
+ load,
463
+ onLoad,
464
+ previousProps,
465
+ previousState,
466
+ src,
467
+ status,
468
+ title
469
+ ]);
470
+ useEffect2(() => {
471
+ if (!previousState) {
472
+ return;
473
+ }
474
+ if (previousState.status !== STATUS.LOADING && status === STATUS.LOADING) {
475
+ getContent();
444
476
  }
445
- if (element) {
446
- return React.cloneElement(element, { ref: innerRef, ...elementProps });
477
+ if (previousState.status !== STATUS.LOADED && status === STATUS.LOADED) {
478
+ getElement();
447
479
  }
448
- if ([STATUS.UNSUPPORTED, STATUS.FAILED].includes(status)) {
449
- return children;
480
+ if (previousState.status !== STATUS.READY && status === STATUS.READY) {
481
+ onLoad?.(src, isCached);
450
482
  }
483
+ }, [getContent, getElement, isCached, onLoad, previousState, src, status]);
484
+ const elementProps = omit(
485
+ props,
486
+ "baseURL",
487
+ "cacheRequests",
488
+ "children",
489
+ "description",
490
+ "fetchOptions",
491
+ "innerRef",
492
+ "loader",
493
+ "onError",
494
+ "onLoad",
495
+ "preProcessor",
496
+ "src",
497
+ "title",
498
+ "uniqueHash",
499
+ "uniquifyIDs"
500
+ );
501
+ if (!canUseDOM()) {
451
502
  return loader;
452
503
  }
453
- };
454
- __publicField(ReactInlineSVG, "defaultProps", {
455
- cacheRequests: true,
456
- uniquifyIDs: false
457
- });
504
+ if (element) {
505
+ return cloneElement(element, { ref: innerRef, ...elementProps });
506
+ }
507
+ if ([STATUS.UNSUPPORTED, STATUS.FAILED].includes(status)) {
508
+ return children;
509
+ }
510
+ return loader;
511
+ }
458
512
  function InlineSVG(props) {
459
513
  if (!cacheStore) {
460
514
  cacheStore = new CacheStore();
461
515
  }
462
516
  const { loader } = props;
463
- const hasCallback = React.useRef(false);
464
- const [isReady, setReady] = React.useState(cacheStore.isReady);
465
- React.useEffect(() => {
517
+ const hasCallback = useRef2(false);
518
+ const [isReady, setReady] = useState(cacheStore.isReady);
519
+ useEffect2(() => {
466
520
  if (!hasCallback.current) {
467
521
  cacheStore.onReady(() => {
468
522
  setReady(true);