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