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.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +263 -228
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +271 -228
- package/dist/index.mjs.map +1 -1
- package/dist/provider.js +2 -1
- package/dist/provider.js.map +1 -1
- package/dist/provider.mjs +3 -1
- package/dist/provider.mjs.map +1 -1
- package/package.json +12 -12
- package/src/index.tsx +195 -270
- package/src/{cache.ts → modules/cache.ts} +3 -2
- package/src/{helpers.ts → modules/helpers.ts} +1 -1
- package/src/modules/hooks.tsx +11 -0
- package/src/modules/utils.ts +122 -0
- package/src/provider.tsx +1 -1
- package/src/types.ts +20 -16
package/src/index.tsx
CHANGED
|
@@ -1,331 +1,256 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
cloneElement,
|
|
3
|
+
isValidElement,
|
|
4
|
+
ReactElement,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useReducer,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react';
|
|
2
11
|
import convert from 'react-from-dom';
|
|
3
12
|
|
|
4
|
-
import CacheStore from './cache';
|
|
5
13
|
import { STATUS } from './config';
|
|
6
|
-
import
|
|
14
|
+
import CacheStore from './modules/cache';
|
|
15
|
+
import { canUseDOM, isSupportedEnvironment, omit, randomString, request } from './modules/helpers';
|
|
16
|
+
import { usePrevious } from './modules/hooks';
|
|
17
|
+
import { getNode } from './modules/utils';
|
|
7
18
|
import { FetchError, Props, State, Status } from './types';
|
|
8
19
|
|
|
9
20
|
// eslint-disable-next-line import/no-mutable-exports
|
|
10
21
|
export let cacheStore: CacheStore;
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
function ReactInlineSVG(props: Props) {
|
|
24
|
+
const {
|
|
25
|
+
cacheRequests = true,
|
|
26
|
+
children = null,
|
|
27
|
+
description,
|
|
28
|
+
fetchOptions,
|
|
29
|
+
innerRef,
|
|
30
|
+
loader = null,
|
|
31
|
+
onError,
|
|
32
|
+
onLoad,
|
|
33
|
+
src,
|
|
34
|
+
title,
|
|
35
|
+
uniqueHash,
|
|
36
|
+
} = props;
|
|
37
|
+
const [state, setState] = useReducer(
|
|
38
|
+
(previousState: State, nextState: Partial<State>) => ({
|
|
39
|
+
...previousState,
|
|
40
|
+
...nextState,
|
|
41
|
+
}),
|
|
42
|
+
{
|
|
26
43
|
content: '',
|
|
27
44
|
element: null,
|
|
28
|
-
isCached: !!props.cacheRequests && cacheStore.isCached(props.src),
|
|
29
|
-
status: STATUS.IDLE,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
this.hash = props.uniqueHash ?? randomString(8);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public componentDidMount(): void {
|
|
36
|
-
this.isActive = true;
|
|
37
|
-
|
|
38
|
-
if (!canUseDOM() || this.isInitialized) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const { status } = this.state;
|
|
43
|
-
const { src } = this.props;
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
if (status === STATUS.IDLE) {
|
|
47
|
-
if (!isSupportedEnvironment()) {
|
|
48
|
-
throw new Error('Browser does not support SVG');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!src) {
|
|
52
|
-
throw new Error('Missing src');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.load();
|
|
56
|
-
}
|
|
57
|
-
} catch (error: any) {
|
|
58
|
-
this.handleError(error);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
this.isInitialized = true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public componentDidUpdate(previousProps: Props, previousState: State): void {
|
|
65
|
-
if (!canUseDOM()) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const { isCached, status } = this.state;
|
|
70
|
-
const { description, onLoad, src, title } = this.props;
|
|
71
45
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
46
|
+
isCached: cacheRequests && cacheStore.isCached(props.src),
|
|
47
|
+
status: STATUS.IDLE,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
const { content, element, isCached, status } = state;
|
|
51
|
+
const previousProps = usePrevious(props);
|
|
52
|
+
const previousState = usePrevious(state);
|
|
53
|
+
|
|
54
|
+
const hash = useRef(uniqueHash ?? randomString(8));
|
|
55
|
+
const isActive = useRef(false);
|
|
56
|
+
const isInitialized = useRef(false);
|
|
57
|
+
|
|
58
|
+
const handleError = useCallback(
|
|
59
|
+
(error: Error | FetchError) => {
|
|
60
|
+
if (isActive.current) {
|
|
61
|
+
setState({
|
|
62
|
+
status:
|
|
63
|
+
error.message === 'Browser does not support SVG' ? STATUS.UNSUPPORTED : STATUS.FAILED,
|
|
64
|
+
});
|
|
81
65
|
|
|
82
|
-
|
|
66
|
+
onError?.(error);
|
|
83
67
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
68
|
+
},
|
|
69
|
+
[onError],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleLoad = useCallback((loadedContent: string, hasCache = false) => {
|
|
73
|
+
if (isActive.current) {
|
|
74
|
+
setState({
|
|
75
|
+
content: loadedContent,
|
|
76
|
+
isCached: hasCache,
|
|
77
|
+
status: STATUS.LOADED,
|
|
78
|
+
});
|
|
90
79
|
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public componentWillUnmount(): void {
|
|
94
|
-
this.isActive = false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private fetchContent = async () => {
|
|
98
|
-
const { fetchOptions, src } = this.props;
|
|
80
|
+
}, []);
|
|
99
81
|
|
|
100
|
-
|
|
82
|
+
const fetchContent = useCallback(async () => {
|
|
83
|
+
const responseContent: string = await request(src, fetchOptions);
|
|
101
84
|
|
|
102
|
-
|
|
103
|
-
};
|
|
85
|
+
handleLoad(responseContent);
|
|
86
|
+
}, [fetchOptions, handleLoad, src]);
|
|
104
87
|
|
|
105
|
-
|
|
88
|
+
const getElement = useCallback(() => {
|
|
106
89
|
try {
|
|
107
|
-
const node =
|
|
108
|
-
const
|
|
90
|
+
const node = getNode({ ...props, handleError, hash: hash.current, content }) as Node;
|
|
91
|
+
const convertedElement = convert(node);
|
|
109
92
|
|
|
110
|
-
if (!
|
|
93
|
+
if (!convertedElement || !isValidElement(convertedElement)) {
|
|
111
94
|
throw new Error('Could not convert the src to a React element');
|
|
112
95
|
}
|
|
113
96
|
|
|
114
|
-
|
|
115
|
-
element,
|
|
97
|
+
setState({
|
|
98
|
+
element: convertedElement,
|
|
116
99
|
status: STATUS.READY,
|
|
117
100
|
});
|
|
118
101
|
} catch (error: any) {
|
|
119
|
-
|
|
102
|
+
handleError(new Error(error.message));
|
|
120
103
|
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
private getNode() {
|
|
124
|
-
const { description, title } = this.props;
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const svgText = this.processSVG();
|
|
128
|
-
const node = convert(svgText, { nodeOnly: true });
|
|
129
|
-
|
|
130
|
-
if (!node || !(node instanceof SVGSVGElement)) {
|
|
131
|
-
throw new Error('Could not convert the src to a DOM Node');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const svg = this.updateSVGAttributes(node);
|
|
135
|
-
|
|
136
|
-
if (description) {
|
|
137
|
-
const originalDesc = svg.querySelector('desc');
|
|
138
|
-
|
|
139
|
-
if (originalDesc?.parentNode) {
|
|
140
|
-
originalDesc.parentNode.removeChild(originalDesc);
|
|
141
|
-
}
|
|
104
|
+
}, [content, handleError, props]);
|
|
142
105
|
|
|
143
|
-
|
|
106
|
+
const getContent = useCallback(async () => {
|
|
107
|
+
const dataURI = /^data:image\/svg[^,]*?(;base64)?,(.*)/u.exec(src);
|
|
108
|
+
let inlineSrc;
|
|
144
109
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
110
|
+
if (dataURI) {
|
|
111
|
+
inlineSrc = dataURI[1] ? window.atob(dataURI[2]) : decodeURIComponent(dataURI[2]);
|
|
112
|
+
} else if (src.includes('<svg')) {
|
|
113
|
+
inlineSrc = src;
|
|
114
|
+
}
|
|
148
115
|
|
|
149
|
-
|
|
150
|
-
|
|
116
|
+
if (inlineSrc) {
|
|
117
|
+
handleLoad(inlineSrc);
|
|
151
118
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
155
121
|
|
|
156
|
-
|
|
157
|
-
|
|
122
|
+
try {
|
|
123
|
+
if (cacheRequests) {
|
|
124
|
+
const cachedContent = await cacheStore.get(src, fetchOptions);
|
|
158
125
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
126
|
+
handleLoad(cachedContent, true);
|
|
127
|
+
} else {
|
|
128
|
+
await fetchContent();
|
|
162
129
|
}
|
|
163
|
-
|
|
164
|
-
return svg;
|
|
165
130
|
} catch (error: any) {
|
|
166
|
-
|
|
131
|
+
handleError(error);
|
|
167
132
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (typeof onError === 'function') {
|
|
178
|
-
onError(error);
|
|
179
|
-
}
|
|
133
|
+
}, [cacheRequests, fetchContent, fetchOptions, handleError, handleLoad, src]);
|
|
134
|
+
|
|
135
|
+
const load = useCallback(async () => {
|
|
136
|
+
if (isActive.current) {
|
|
137
|
+
setState({
|
|
138
|
+
content: '',
|
|
139
|
+
element: null,
|
|
140
|
+
isCached: false,
|
|
141
|
+
status: STATUS.LOADING,
|
|
180
142
|
});
|
|
181
143
|
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
private handleLoad = (content: string, hasCache = false) => {
|
|
185
|
-
if (this.isActive) {
|
|
186
|
-
this.setState(
|
|
187
|
-
{
|
|
188
|
-
content,
|
|
189
|
-
isCached: hasCache,
|
|
190
|
-
status: STATUS.LOADED,
|
|
191
|
-
},
|
|
192
|
-
this.getElement,
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
private load() {
|
|
198
|
-
if (this.isActive) {
|
|
199
|
-
this.setState(
|
|
200
|
-
{
|
|
201
|
-
content: '',
|
|
202
|
-
element: null,
|
|
203
|
-
isCached: false,
|
|
204
|
-
status: STATUS.LOADING,
|
|
205
|
-
},
|
|
206
|
-
async () => {
|
|
207
|
-
const { cacheRequests, fetchOptions, src } = this.props;
|
|
208
|
-
|
|
209
|
-
const dataURI = /^data:image\/svg[^,]*?(;base64)?,(.*)/u.exec(src);
|
|
210
|
-
let inlineSrc;
|
|
211
|
-
|
|
212
|
-
if (dataURI) {
|
|
213
|
-
inlineSrc = dataURI[1] ? window.atob(dataURI[2]) : decodeURIComponent(dataURI[2]);
|
|
214
|
-
} else if (src.includes('<svg')) {
|
|
215
|
-
inlineSrc = src;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (inlineSrc) {
|
|
219
|
-
this.handleLoad(inlineSrc);
|
|
144
|
+
}, []);
|
|
220
145
|
|
|
221
|
-
|
|
222
|
-
|
|
146
|
+
// Run on mount
|
|
147
|
+
useEffect(
|
|
148
|
+
() => {
|
|
149
|
+
isActive.current = true;
|
|
223
150
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
151
|
+
if (!canUseDOM() || isInitialized.current) {
|
|
152
|
+
return () => undefined;
|
|
153
|
+
}
|
|
227
154
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
} catch (error: any) {
|
|
233
|
-
this.handleError(error);
|
|
155
|
+
try {
|
|
156
|
+
if (status === STATUS.IDLE) {
|
|
157
|
+
if (!isSupportedEnvironment()) {
|
|
158
|
+
throw new Error('Browser does not support SVG');
|
|
234
159
|
}
|
|
235
|
-
},
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
160
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
161
|
+
if (!src) {
|
|
162
|
+
throw new Error('Missing src');
|
|
163
|
+
}
|
|
243
164
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
165
|
+
load();
|
|
166
|
+
}
|
|
167
|
+
} catch (error: any) {
|
|
168
|
+
handleError(error);
|
|
169
|
+
}
|
|
247
170
|
|
|
248
|
-
|
|
249
|
-
}
|
|
171
|
+
isInitialized.current = true;
|
|
250
172
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
173
|
+
return () => {
|
|
174
|
+
isActive.current = false;
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
178
|
+
[],
|
|
179
|
+
);
|
|
257
180
|
|
|
258
|
-
|
|
259
|
-
|
|
181
|
+
// Handle prop changes
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (!canUseDOM()) {
|
|
184
|
+
return;
|
|
260
185
|
}
|
|
261
186
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const attribute = a;
|
|
266
|
-
const match = /url\((.*?)\)/.exec(a.value);
|
|
267
|
-
|
|
268
|
-
if (match?.[1]) {
|
|
269
|
-
attribute.value = a.value.replace(match[0], `url(${baseURL}${match[1]}__${this.hash})`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return attribute;
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
replaceableAttributes.forEach(r => {
|
|
276
|
-
const attribute = attributes.find(a => a.name === r);
|
|
187
|
+
if (!previousProps) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
277
190
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
});
|
|
282
|
-
}
|
|
191
|
+
if (previousProps.src !== src) {
|
|
192
|
+
if (!src) {
|
|
193
|
+
handleError(new Error('Missing src'));
|
|
283
194
|
|
|
284
|
-
|
|
285
|
-
return this.updateSVGAttributes(d as SVGSVGElement);
|
|
195
|
+
return;
|
|
286
196
|
}
|
|
287
197
|
|
|
288
|
-
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
public render(): React.ReactNode {
|
|
295
|
-
const { element, status } = this.state;
|
|
296
|
-
const { children = null, innerRef, loader = null } = this.props;
|
|
297
|
-
const elementProps = omit(
|
|
298
|
-
this.props,
|
|
299
|
-
'baseURL',
|
|
300
|
-
'cacheRequests',
|
|
301
|
-
'children',
|
|
302
|
-
'description',
|
|
303
|
-
'fetchOptions',
|
|
304
|
-
'innerRef',
|
|
305
|
-
'loader',
|
|
306
|
-
'onError',
|
|
307
|
-
'onLoad',
|
|
308
|
-
'preProcessor',
|
|
309
|
-
'src',
|
|
310
|
-
'title',
|
|
311
|
-
'uniqueHash',
|
|
312
|
-
'uniquifyIDs',
|
|
313
|
-
);
|
|
198
|
+
load();
|
|
199
|
+
} else if (previousProps.title !== title || previousProps.description !== description) {
|
|
200
|
+
getElement();
|
|
201
|
+
}
|
|
202
|
+
}, [description, getElement, handleError, load, previousProps, src, title]);
|
|
314
203
|
|
|
315
|
-
|
|
316
|
-
|
|
204
|
+
// handle state
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (!previousState) {
|
|
207
|
+
return;
|
|
317
208
|
}
|
|
318
209
|
|
|
319
|
-
if (
|
|
320
|
-
|
|
210
|
+
if (previousState.status !== STATUS.LOADING && status === STATUS.LOADING) {
|
|
211
|
+
getContent();
|
|
321
212
|
}
|
|
322
213
|
|
|
323
|
-
if (
|
|
324
|
-
|
|
214
|
+
if (previousState.status !== STATUS.LOADED && status === STATUS.LOADED) {
|
|
215
|
+
getElement();
|
|
325
216
|
}
|
|
326
217
|
|
|
218
|
+
if (previousState.status !== STATUS.READY && status === STATUS.READY) {
|
|
219
|
+
onLoad?.(src, isCached);
|
|
220
|
+
}
|
|
221
|
+
}, [getContent, getElement, isCached, onLoad, previousState, src, status]);
|
|
222
|
+
|
|
223
|
+
const elementProps = omit(
|
|
224
|
+
props,
|
|
225
|
+
'baseURL',
|
|
226
|
+
'cacheRequests',
|
|
227
|
+
'children',
|
|
228
|
+
'description',
|
|
229
|
+
'fetchOptions',
|
|
230
|
+
'innerRef',
|
|
231
|
+
'loader',
|
|
232
|
+
'onError',
|
|
233
|
+
'onLoad',
|
|
234
|
+
'preProcessor',
|
|
235
|
+
'src',
|
|
236
|
+
'title',
|
|
237
|
+
'uniqueHash',
|
|
238
|
+
'uniquifyIDs',
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (!canUseDOM()) {
|
|
327
242
|
return loader;
|
|
328
243
|
}
|
|
244
|
+
|
|
245
|
+
if (element) {
|
|
246
|
+
return cloneElement(element as ReactElement, { ref: innerRef, ...elementProps });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (([STATUS.UNSUPPORTED, STATUS.FAILED] as Status[]).includes(status)) {
|
|
250
|
+
return children;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return loader;
|
|
329
254
|
}
|
|
330
255
|
|
|
331
256
|
export default function InlineSVG(props: Props) {
|
|
@@ -334,10 +259,10 @@ export default function InlineSVG(props: Props) {
|
|
|
334
259
|
}
|
|
335
260
|
|
|
336
261
|
const { loader } = props;
|
|
337
|
-
const hasCallback =
|
|
338
|
-
const [isReady, setReady] =
|
|
262
|
+
const hasCallback = useRef(false);
|
|
263
|
+
const [isReady, setReady] = useState(cacheStore.isReady);
|
|
339
264
|
|
|
340
|
-
|
|
265
|
+
useEffect(() => {
|
|
341
266
|
if (!hasCallback.current) {
|
|
342
267
|
cacheStore.onReady(() => {
|
|
343
268
|
setReady(true);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { CACHE_MAX_RETRIES, CACHE_NAME, STATUS } from './config';
|
|
2
1
|
import { canUseDOM, request, sleep } from './helpers';
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
import { CACHE_MAX_RETRIES, CACHE_NAME, STATUS } from '../config';
|
|
4
|
+
import { StorageItem } from '../types';
|
|
4
5
|
|
|
5
6
|
export default class CacheStore {
|
|
6
7
|
private cacheApi: Cache | undefined;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import convert from 'react-from-dom';
|
|
2
|
+
|
|
3
|
+
import { Props, State } from '../types';
|
|
4
|
+
|
|
5
|
+
interface GetNodeOptions extends Props, Pick<State, 'content'> {
|
|
6
|
+
handleError: (error: Error) => void;
|
|
7
|
+
hash: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface UpdateSVGAttributesOptions extends Pick<Props, 'baseURL' | 'uniquifyIDs'> {
|
|
11
|
+
hash: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getNode(options: GetNodeOptions) {
|
|
15
|
+
const {
|
|
16
|
+
baseURL,
|
|
17
|
+
content,
|
|
18
|
+
description,
|
|
19
|
+
handleError,
|
|
20
|
+
hash,
|
|
21
|
+
preProcessor,
|
|
22
|
+
title,
|
|
23
|
+
uniquifyIDs = false,
|
|
24
|
+
} = options;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const svgText = processSVG(content, preProcessor);
|
|
28
|
+
const node = convert(svgText, { nodeOnly: true });
|
|
29
|
+
|
|
30
|
+
if (!node || !(node instanceof SVGSVGElement)) {
|
|
31
|
+
throw new Error('Could not convert the src to a DOM Node');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const svg = updateSVGAttributes(node, { baseURL, hash, uniquifyIDs });
|
|
35
|
+
|
|
36
|
+
if (description) {
|
|
37
|
+
const originalDesc = svg.querySelector('desc');
|
|
38
|
+
|
|
39
|
+
if (originalDesc?.parentNode) {
|
|
40
|
+
originalDesc.parentNode.removeChild(originalDesc);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const descElement = document.createElementNS('http://www.w3.org/2000/svg', 'desc');
|
|
44
|
+
|
|
45
|
+
descElement.innerHTML = description;
|
|
46
|
+
svg.prepend(descElement);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof title !== 'undefined') {
|
|
50
|
+
const originalTitle = svg.querySelector('title');
|
|
51
|
+
|
|
52
|
+
if (originalTitle?.parentNode) {
|
|
53
|
+
originalTitle.parentNode.removeChild(originalTitle);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (title) {
|
|
57
|
+
const titleElement = document.createElementNS('http://www.w3.org/2000/svg', 'title');
|
|
58
|
+
|
|
59
|
+
titleElement.innerHTML = title;
|
|
60
|
+
svg.prepend(titleElement);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return svg;
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
return handleError(error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function processSVG(content: string, preProcessor?: Props['preProcessor']) {
|
|
71
|
+
if (preProcessor) {
|
|
72
|
+
return preProcessor(content);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return content;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function updateSVGAttributes(
|
|
79
|
+
node: SVGSVGElement,
|
|
80
|
+
options: UpdateSVGAttributesOptions,
|
|
81
|
+
): SVGSVGElement {
|
|
82
|
+
const { baseURL = '', hash, uniquifyIDs } = options;
|
|
83
|
+
const replaceableAttributes = ['id', 'href', 'xlink:href', 'xlink:role', 'xlink:arcrole'];
|
|
84
|
+
const linkAttributes = ['href', 'xlink:href'];
|
|
85
|
+
const isDataValue = (name: string, value: string) =>
|
|
86
|
+
linkAttributes.includes(name) && (value ? !value.includes('#') : false);
|
|
87
|
+
|
|
88
|
+
if (!uniquifyIDs) {
|
|
89
|
+
return node;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
[...node.children].forEach(d => {
|
|
93
|
+
if (d.attributes?.length) {
|
|
94
|
+
const attributes = Object.values(d.attributes).map(a => {
|
|
95
|
+
const attribute = a;
|
|
96
|
+
const match = /url\((.*?)\)/.exec(a.value);
|
|
97
|
+
|
|
98
|
+
if (match?.[1]) {
|
|
99
|
+
attribute.value = a.value.replace(match[0], `url(${baseURL}${match[1]}__${hash})`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return attribute;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
replaceableAttributes.forEach(r => {
|
|
106
|
+
const attribute = attributes.find(a => a.name === r);
|
|
107
|
+
|
|
108
|
+
if (attribute && !isDataValue(r, attribute.value)) {
|
|
109
|
+
attribute.value = `${attribute.value}__${hash}`;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (d.children.length) {
|
|
115
|
+
return updateSVGAttributes(d as SVGSVGElement, options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return d;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return node;
|
|
122
|
+
}
|