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