-
-
Notifications
You must be signed in to change notification settings - Fork 102
/
Copy pathRemoteSvgIcon.tsx
78 lines (68 loc) · 2.25 KB
/
RemoteSvgIcon.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import {
createEffect,
createMemo,
createResource,
createSignal,
createUniqueId,
JSXElement,
on,
Suspense,
} from 'solid-js';
import {Loading} from '../Loader';
import {SvgIcon, SvgIconProps} from './SvgIcon';
export interface SvgExternalIconProps {
content?: string | (() => Promise<typeof import('*.svg')>) | null;
delay?: number;
size?: SvgIconProps['size'];
}
/**
* Randomize all svg ids of `fill` property when using url(#reference) and defs.
* ATTENTION: this fixed a Chrome/Firefox bug which is unable to render svg icons
* if loaded dynamically while using <defs/> and fill property with `url(#reference)`
*
* @param svgContent
*/
function randomizeSvgUrlIds(svgContent: string): string {
const extractSvgUrl = /url\(([^)]+)\)/g;
const regexpResult = [...extractSvgUrl[Symbol.matchAll](svgContent)];
const groups = regexpResult.map(result => [result[0], result[1]] as const);
return groups.reduce((acc, [url, id]) => {
const uniqueUid = createUniqueId();
return acc
.replaceAll(url, `url(#${uniqueUid})`)
.replaceAll(`id="${id.split('#')[1]}"`, `id="${uniqueUid}"`);
}, svgContent);
}
export function RemoteSvgIcon(props: SvgExternalIconProps): JSXElement {
const [src, setSrc] = createSignal<SvgExternalIconProps['content'] | null>();
const content = createMemo(() => props.content);
const [data] = createResource(src, async content => {
const svgResponse =
typeof content === 'string'
? await fetch(content).then(res => res.text())
: await content().then(e => e.default);
if (props.delay) {
await new Promise(r => setTimeout(r, props.delay));
}
const innerHTML = randomizeSvgUrlIds(svgResponse);
const parsedDocument = new DOMParser().parseFromString(
innerHTML,
'text/html',
);
const svg = parsedDocument.body.childNodes[0] as SVGElement;
return {
innerHTML: (svg as SVGElement).innerHTML,
viewBox: svg.getAttribute('viewBox') || undefined,
};
});
createEffect(
on(content, content => {
setSrc(() => content as SvgExternalIconProps['content']);
}),
);
return (
<Suspense fallback={<Loading size={props.size ?? 'md'} />}>
<SvgIcon {...(data() || {})} size={props.size ?? 'md'} />
</Suspense>
);
}