react-inlinesvg 2.2.2 → 3.0.1

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/src/index.tsx CHANGED
@@ -1,19 +1,21 @@
1
1
  import * as React from 'react';
2
-
3
2
  import convert from 'react-from-dom';
4
3
 
5
- import {
6
- STATUS,
7
- canUseDOM,
8
- isSupportedEnvironment,
9
- randomString,
10
- removeProperties,
11
- } from './helpers';
4
+ import { canUseDOM, isSupportedEnvironment, omit, randomString, STATUS } from './helpers';
12
5
  import { FetchError, Props, State, StorageItem } from './types';
13
6
 
14
- const cacheStore: { [key: string]: StorageItem } = Object.create(null);
7
+ export const cacheStore: { [key: string]: StorageItem } = Object.create(null);
15
8
 
16
9
  export default class InlineSVG extends React.PureComponent<Props, State> {
10
+ private isInitialized = false;
11
+ private isActive = false;
12
+ private readonly hash: string;
13
+
14
+ public static defaultProps = {
15
+ cacheRequests: true,
16
+ uniquifyIDs: false,
17
+ };
18
+
17
19
  constructor(props: Props) {
18
20
  super(props);
19
21
 
@@ -27,18 +29,10 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
27
29
  this.hash = props.uniqueHash || randomString(8);
28
30
  }
29
31
 
30
- private isActive = false;
31
- private readonly hash: string;
32
-
33
- public static defaultProps = {
34
- cacheRequests: true,
35
- uniquifyIDs: false,
36
- };
37
-
38
32
  public componentDidMount(): void {
39
33
  this.isActive = true;
40
34
 
41
- if (!canUseDOM()) {
35
+ if (!canUseDOM() || this.isInitialized) {
42
36
  return;
43
37
  }
44
38
 
@@ -60,12 +54,14 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
60
54
 
61
55
  this.load();
62
56
  }
63
- } catch (error) {
57
+ } catch (error: any) {
64
58
  this.handleError(error);
65
59
  }
60
+
61
+ this.isInitialized = true;
66
62
  }
67
63
 
68
- public componentDidUpdate(prevProps: Props, prevState: State): void {
64
+ public componentDidUpdate(previousProps: Props, previousState: State): void {
69
65
  if (!canUseDOM()) {
70
66
  return;
71
67
  }
@@ -73,16 +69,17 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
73
69
  const { hasCache, status } = this.state;
74
70
  const { onLoad, src } = this.props;
75
71
 
76
- if (prevState.status !== STATUS.READY && status === STATUS.READY) {
72
+ if (previousState.status !== STATUS.READY && status === STATUS.READY) {
77
73
  /* istanbul ignore else */
78
74
  if (onLoad) {
79
75
  onLoad(src, hasCache);
80
76
  }
81
77
  }
82
78
 
83
- if (prevProps.src !== src) {
79
+ if (previousProps.src !== src) {
84
80
  if (!src) {
85
81
  this.handleError(new Error('Missing src'));
82
+
86
83
  return;
87
84
  }
88
85
 
@@ -94,60 +91,6 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
94
91
  this.isActive = false;
95
92
  }
96
93
 
97
- private processSVG() {
98
- const { content } = this.state;
99
- const { preProcessor } = this.props;
100
-
101
- if (preProcessor) {
102
- return preProcessor(content);
103
- }
104
-
105
- return content;
106
- }
107
-
108
- private updateSVGAttributes(node: SVGSVGElement): SVGSVGElement {
109
- const { baseURL = '', uniquifyIDs } = this.props;
110
- const replaceableAttributes = ['id', 'href', 'xlink:href', 'xlink:role', 'xlink:arcrole'];
111
- const linkAttributes = ['href', 'xlink:href'];
112
- const isDataValue = (name: string, value: string) =>
113
- linkAttributes.indexOf(name) >= 0 && (value ? value.indexOf('#') < 0 : false);
114
-
115
- if (!uniquifyIDs) {
116
- return node;
117
- }
118
-
119
- [...node.children].map((d) => {
120
- if (d.attributes && d.attributes.length) {
121
- const attributes = Object.values(d.attributes).map((a) => {
122
- const attr = a;
123
- const match = a.value.match(/url\((.*?)\)/);
124
-
125
- if (match && match[1]) {
126
- attr.value = a.value.replace(match[0], `url(${baseURL}${match[1]}__${this.hash})`);
127
- }
128
-
129
- return attr;
130
- });
131
-
132
- replaceableAttributes.forEach((r) => {
133
- const attribute = attributes.find((a) => a.name === r);
134
-
135
- if (attribute && !isDataValue(r, attribute.value)) {
136
- attribute.value = `${attribute.value}__${this.hash}`;
137
- }
138
- });
139
- }
140
-
141
- if (d.children.length) {
142
- return this.updateSVGAttributes(d as SVGSVGElement);
143
- }
144
-
145
- return d;
146
- });
147
-
148
- return node;
149
- }
150
-
151
94
  private getNode() {
152
95
  const { description, title } = this.props;
153
96
 
@@ -169,6 +112,7 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
169
112
  }
170
113
 
171
114
  const descElement = document.createElement('desc');
115
+
172
116
  descElement.innerHTML = description;
173
117
  svg.prepend(descElement);
174
118
  }
@@ -181,12 +125,13 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
181
125
  }
182
126
 
183
127
  const titleElement = document.createElement('title');
128
+
184
129
  titleElement.innerHTML = title;
185
130
  svg.prepend(titleElement);
186
131
  }
187
132
 
188
133
  return svg;
189
- } catch (error) {
134
+ } catch (error: any) {
190
135
  return this.handleError(error);
191
136
  }
192
137
  }
@@ -204,64 +149,11 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
204
149
  element,
205
150
  status: STATUS.READY,
206
151
  });
207
- } catch (error) {
152
+ } catch (error: any) {
208
153
  this.handleError(new Error(error.message));
209
154
  }
210
155
  }
211
156
 
212
- private load() {
213
- /* istanbul ignore else */
214
- if (this.isActive) {
215
- this.setState(
216
- {
217
- content: '',
218
- element: null,
219
- status: STATUS.LOADING,
220
- },
221
- () => {
222
- const { cacheRequests, src } = this.props;
223
- const cache = cacheRequests && cacheStore[src];
224
-
225
- if (cache) {
226
- /* istanbul ignore else */
227
- if (cache.status === STATUS.LOADING) {
228
- cache.queue.push(this.handleCacheQueue);
229
- } else if (cache.status === STATUS.LOADED) {
230
- this.handleLoad(cache.content);
231
- }
232
- return;
233
- }
234
-
235
- const dataURI = src.match(/data:image\/svg[^,]*?(;base64)?,(.*)/);
236
- let inlineSrc;
237
-
238
- if (dataURI) {
239
- inlineSrc = dataURI[1] ? atob(dataURI[2]) : decodeURIComponent(dataURI[2]);
240
- } else if (src.indexOf('<svg') >= 0) {
241
- inlineSrc = src;
242
- }
243
-
244
- if (inlineSrc) {
245
- this.handleLoad(inlineSrc);
246
- return;
247
- }
248
-
249
- this.request();
250
- },
251
- );
252
- }
253
- }
254
-
255
- private handleCacheQueue = (content: string | Error) => {
256
- /* istanbul ignore else */
257
- if (typeof content === 'string') {
258
- this.handleLoad(content);
259
- return;
260
- }
261
-
262
- this.handleError(content);
263
- };
264
-
265
157
  private handleLoad = (content: string) => {
266
158
  /* istanbul ignore else */
267
159
  if (this.isActive) {
@@ -292,15 +184,15 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
292
184
  };
293
185
 
294
186
  private request = () => {
295
- const { cacheRequests, src } = this.props;
187
+ const { cacheRequests, fetchOptions, src } = this.props;
296
188
 
297
189
  try {
298
190
  if (cacheRequests) {
299
- cacheStore[src] = { content: '', status: STATUS.LOADING, queue: [] };
191
+ cacheStore[src] = { content: '', status: STATUS.LOADING };
300
192
  }
301
193
 
302
- return fetch(src)
303
- .then((response) => {
194
+ return fetch(src, fetchOptions)
195
+ .then(response => {
304
196
  const contentType = response.headers.get('content-type');
305
197
  const [fileType] = (contentType || '').split(/ ?; ?/);
306
198
 
@@ -308,13 +200,24 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
308
200
  throw new Error('Not found');
309
201
  }
310
202
 
311
- if (!['image/svg+xml', 'text/plain'].some((d) => fileType.indexOf(d) >= 0)) {
203
+ if (!['image/svg+xml', 'text/plain'].some(d => fileType.includes(d))) {
312
204
  throw new Error(`Content type isn't valid: ${fileType}`);
313
205
  }
314
206
 
315
207
  return response.text();
316
208
  })
317
- .then((content) => {
209
+ .then(content => {
210
+ const { src: currentSrc } = this.props;
211
+
212
+ // the current src don't match the previous one, skipping...
213
+ if (src !== currentSrc) {
214
+ if (cacheStore[src].status === STATUS.LOADING) {
215
+ delete cacheStore[src];
216
+ }
217
+
218
+ return;
219
+ }
220
+
318
221
  this.handleLoad(content);
319
222
 
320
223
  /* istanbul ignore else */
@@ -325,16 +228,10 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
325
228
  if (cache) {
326
229
  cache.content = content;
327
230
  cache.status = STATUS.LOADED;
328
-
329
- cache.queue = cache.queue.filter((cb) => {
330
- cb(content);
331
-
332
- return false;
333
- });
334
231
  }
335
232
  }
336
233
  })
337
- .catch((error) => {
234
+ .catch(error => {
338
235
  this.handleError(error);
339
236
 
340
237
  /* istanbul ignore else */
@@ -343,28 +240,119 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
343
240
 
344
241
  /* istanbul ignore else */
345
242
  if (cache) {
346
- cache.queue.forEach((cb: (content: string) => void) => {
347
- cb(error);
348
- });
349
-
350
243
  delete cacheStore[src];
351
244
  }
352
245
  }
353
246
  });
354
- } catch (error) {
247
+ } catch (error: any) {
355
248
  return this.handleError(new Error(error.message));
356
249
  }
357
250
  };
358
251
 
252
+ private load() {
253
+ /* istanbul ignore else */
254
+ if (this.isActive) {
255
+ this.setState(
256
+ {
257
+ content: '',
258
+ element: null,
259
+ status: STATUS.LOADING,
260
+ },
261
+ () => {
262
+ const { cacheRequests, src } = this.props;
263
+ const cache = cacheRequests && cacheStore[src];
264
+
265
+ if (cache && cache.status === STATUS.LOADED) {
266
+ this.handleLoad(cache.content);
267
+
268
+ return;
269
+ }
270
+
271
+ const dataURI = src.match(/data:image\/svg[^,]*?(;base64)?,(.*)/);
272
+ let inlineSrc;
273
+
274
+ if (dataURI) {
275
+ inlineSrc = dataURI[1] ? window.atob(dataURI[2]) : decodeURIComponent(dataURI[2]);
276
+ } else if (src.includes('<svg')) {
277
+ inlineSrc = src;
278
+ }
279
+
280
+ if (inlineSrc) {
281
+ this.handleLoad(inlineSrc);
282
+
283
+ return;
284
+ }
285
+
286
+ this.request();
287
+ },
288
+ );
289
+ }
290
+ }
291
+
292
+ private updateSVGAttributes(node: SVGSVGElement): SVGSVGElement {
293
+ const { baseURL = '', uniquifyIDs } = this.props;
294
+ const replaceableAttributes = ['id', 'href', 'xlink:href', 'xlink:role', 'xlink:arcrole'];
295
+ const linkAttributes = ['href', 'xlink:href'];
296
+ const isDataValue = (name: string, value: string) =>
297
+ linkAttributes.includes(name) && (value ? !value.includes('#') : false);
298
+
299
+ if (!uniquifyIDs) {
300
+ return node;
301
+ }
302
+
303
+ [...node.children].map(d => {
304
+ if (d.attributes && d.attributes.length) {
305
+ const attributes = Object.values(d.attributes).map(a => {
306
+ const attribute = a;
307
+ const match = a.value.match(/url\((.*?)\)/);
308
+
309
+ if (match && match[1]) {
310
+ attribute.value = a.value.replace(match[0], `url(${baseURL}${match[1]}__${this.hash})`);
311
+ }
312
+
313
+ return attribute;
314
+ });
315
+
316
+ replaceableAttributes.forEach(r => {
317
+ const attribute = attributes.find(a => a.name === r);
318
+
319
+ if (attribute && !isDataValue(r, attribute.value)) {
320
+ attribute.value = `${attribute.value}__${this.hash}`;
321
+ }
322
+ });
323
+ }
324
+
325
+ if (d.children.length) {
326
+ return this.updateSVGAttributes(d as SVGSVGElement);
327
+ }
328
+
329
+ return d;
330
+ });
331
+
332
+ return node;
333
+ }
334
+
335
+ private processSVG() {
336
+ const { content } = this.state;
337
+ const { preProcessor } = this.props;
338
+
339
+ if (preProcessor) {
340
+ return preProcessor(content);
341
+ }
342
+
343
+ return content;
344
+ }
345
+
359
346
  public render(): React.ReactNode {
360
347
  const { element, status } = this.state;
361
348
  const { children = null, innerRef, loader = null } = this.props;
362
- const elementProps = removeProperties(
349
+ const elementProps = omit(
363
350
  this.props,
364
351
  'baseURL',
365
352
  'cacheRequests',
366
353
  'children',
367
354
  'description',
355
+ 'fetchOptions',
368
356
  'innerRef',
369
357
  'loader',
370
358
  'onError',
@@ -384,7 +372,7 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
384
372
  return React.cloneElement(element as React.ReactElement, { ref: innerRef, ...elementProps });
385
373
  }
386
374
 
387
- if ([STATUS.UNSUPPORTED, STATUS.FAILED].indexOf(status) > -1) {
375
+ if ([STATUS.UNSUPPORTED, STATUS.FAILED].includes(status)) {
388
376
  return children;
389
377
  }
390
378
 
package/src/types.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import * as React from 'react';
2
2
 
3
- type Callback = (...args: any[]) => void;
4
-
5
3
  export type ErrorCallback = (error: Error | FetchError) => void;
6
4
  export type LoadCallback = (src: string, isCached: boolean) => void;
7
5
  export type PlainObject<T = unknown> = Record<string | number | symbol, T>;
@@ -12,6 +10,7 @@ export interface Props extends Omit<React.SVGProps<SVGElement>, 'onLoad' | 'onEr
12
10
  cacheRequests?: boolean;
13
11
  children?: React.ReactNode;
14
12
  description?: string;
13
+ fetchOptions?: RequestInit;
15
14
  innerRef?: React.Ref<SVGElement>;
16
15
  loader?: React.ReactNode;
17
16
  onError?: ErrorCallback;
@@ -39,6 +38,5 @@ export interface FetchError extends Error {
39
38
 
40
39
  export interface StorageItem {
41
40
  content: string;
42
- queue: Callback[];
43
41
  status: string;
44
42
  }