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/README.md +23 -13
- package/esm/helpers.d.ts +1 -1
- package/esm/helpers.js +8 -7
- package/esm/helpers.js.map +1 -1
- package/esm/index.d.ts +10 -7
- package/esm/index.js +328 -251
- package/esm/index.js.map +1 -1
- package/esm/types.d.ts +1 -3
- package/lib/helpers.d.ts +1 -1
- package/lib/helpers.js +10 -9
- package/lib/helpers.js.map +1 -1
- package/lib/index.d.ts +10 -7
- package/lib/index.js +335 -253
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +1 -3
- package/package.json +59 -60
- package/src/helpers.ts +13 -8
- package/src/index.tsx +139 -151
- package/src/types.ts +1 -3
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(
|
|
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 (
|
|
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 (
|
|
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
|
|
191
|
+
cacheStore[src] = { content: '', status: STATUS.LOADING };
|
|
300
192
|
}
|
|
301
193
|
|
|
302
|
-
return fetch(src)
|
|
303
|
-
.then(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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].
|
|
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
|
}
|