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/README.md +1 -1
- 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 +5 -6
- package/esm/index.js +67 -81
- package/esm/index.js.map +1 -1
- package/esm/types.d.ts +0 -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 +5 -6
- package/lib/index.js +78 -88
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +0 -3
- package/package.json +54 -56
- package/src/helpers.ts +13 -8
- package/src/index.tsx +124 -146
- package/src/types.ts +0 -3
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(
|
|
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 (
|
|
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 (
|
|
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
|
|
188
|
+
cacheStore[src] = { content: '', status: STATUS.LOADING };
|
|
299
189
|
}
|
|
300
190
|
|
|
301
191
|
return fetch(src, fetchOptions)
|
|
302
|
-
.then(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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].
|
|
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
|
}
|