@smbdy/icons-react 0.2.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.
Files changed (167) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +33 -0
  4. package/README.md +125 -0
  5. package/dist/_all-BBSV7W5A.js +12 -0
  6. package/dist/_all-BBSV7W5A.js.map +1 -0
  7. package/dist/_all-Q3TOEXIL.cjs +12 -0
  8. package/dist/_all-Q3TOEXIL.cjs.map +1 -0
  9. package/dist/aave.full-KYPGIYIN.cjs +7 -0
  10. package/dist/aave.full-KYPGIYIN.cjs.map +1 -0
  11. package/dist/aave.full-QNOLSN64.js +7 -0
  12. package/dist/aave.full-QNOLSN64.js.map +1 -0
  13. package/dist/aave.mono-DFM2DC7D.js +7 -0
  14. package/dist/aave.mono-DFM2DC7D.js.map +1 -0
  15. package/dist/aave.mono-HESKUGIT.cjs +7 -0
  16. package/dist/aave.mono-HESKUGIT.cjs.map +1 -0
  17. package/dist/bnb.full-ECKVIICN.js +7 -0
  18. package/dist/bnb.full-ECKVIICN.js.map +1 -0
  19. package/dist/bnb.full-OVSE5GL5.cjs +7 -0
  20. package/dist/bnb.full-OVSE5GL5.cjs.map +1 -0
  21. package/dist/bnb.mono-BJKFGB4W.js +7 -0
  22. package/dist/bnb.mono-BJKFGB4W.js.map +1 -0
  23. package/dist/bnb.mono-ZGBCCQNU.cjs +7 -0
  24. package/dist/bnb.mono-ZGBCCQNU.cjs.map +1 -0
  25. package/dist/brands-FNU44Q5D.cjs +6 -0
  26. package/dist/brands-FNU44Q5D.cjs.map +1 -0
  27. package/dist/brands-VFBGFQDR.js +6 -0
  28. package/dist/brands-VFBGFQDR.js.map +1 -0
  29. package/dist/brands.cjs +7 -0
  30. package/dist/brands.cjs.map +1 -0
  31. package/dist/brands.d.cts +6 -0
  32. package/dist/brands.d.ts +6 -0
  33. package/dist/brands.js +7 -0
  34. package/dist/brands.js.map +1 -0
  35. package/dist/chains-4PZO7QNB.cjs +6 -0
  36. package/dist/chains-4PZO7QNB.cjs.map +1 -0
  37. package/dist/chains-DRDCRCHX.js +6 -0
  38. package/dist/chains-DRDCRCHX.js.map +1 -0
  39. package/dist/chains.cjs +7 -0
  40. package/dist/chains.cjs.map +1 -0
  41. package/dist/chains.d.cts +6 -0
  42. package/dist/chains.d.ts +6 -0
  43. package/dist/chains.js +7 -0
  44. package/dist/chains.js.map +1 -0
  45. package/dist/chunk-26TL622R.js +185 -0
  46. package/dist/chunk-26TL622R.js.map +1 -0
  47. package/dist/chunk-3KPQTLV2.js +25 -0
  48. package/dist/chunk-3KPQTLV2.js.map +1 -0
  49. package/dist/chunk-5SMAZ4HC.cjs +46 -0
  50. package/dist/chunk-5SMAZ4HC.cjs.map +1 -0
  51. package/dist/chunk-6QMWOF6K.cjs +13 -0
  52. package/dist/chunk-6QMWOF6K.cjs.map +1 -0
  53. package/dist/chunk-6XTX7IGY.cjs +22 -0
  54. package/dist/chunk-6XTX7IGY.cjs.map +1 -0
  55. package/dist/chunk-77JHKGHS.js +17 -0
  56. package/dist/chunk-77JHKGHS.js.map +1 -0
  57. package/dist/chunk-AFPT5VBY.cjs +13 -0
  58. package/dist/chunk-AFPT5VBY.cjs.map +1 -0
  59. package/dist/chunk-BM7JPYFS.cjs +19 -0
  60. package/dist/chunk-BM7JPYFS.cjs.map +1 -0
  61. package/dist/chunk-CRZCJKV5.cjs +224 -0
  62. package/dist/chunk-CRZCJKV5.cjs.map +1 -0
  63. package/dist/chunk-EH2XXR25.cjs +22 -0
  64. package/dist/chunk-EH2XXR25.cjs.map +1 -0
  65. package/dist/chunk-EKA2NTJ5.cjs +211 -0
  66. package/dist/chunk-EKA2NTJ5.cjs.map +1 -0
  67. package/dist/chunk-ERIDAEIF.js +22 -0
  68. package/dist/chunk-ERIDAEIF.js.map +1 -0
  69. package/dist/chunk-GNITCB7H.cjs +19 -0
  70. package/dist/chunk-GNITCB7H.cjs.map +1 -0
  71. package/dist/chunk-H2CCXC2D.cjs +16 -0
  72. package/dist/chunk-H2CCXC2D.cjs.map +1 -0
  73. package/dist/chunk-HJRZBSWY.cjs +51 -0
  74. package/dist/chunk-HJRZBSWY.cjs.map +1 -0
  75. package/dist/chunk-I5KA3VJG.js +224 -0
  76. package/dist/chunk-I5KA3VJG.js.map +1 -0
  77. package/dist/chunk-IVJRG2GG.js +46 -0
  78. package/dist/chunk-IVJRG2GG.js.map +1 -0
  79. package/dist/chunk-JA27XHAD.js +19 -0
  80. package/dist/chunk-JA27XHAD.js.map +1 -0
  81. package/dist/chunk-KMTZSDBE.js +40 -0
  82. package/dist/chunk-KMTZSDBE.js.map +1 -0
  83. package/dist/chunk-N7LPPHJT.cjs +185 -0
  84. package/dist/chunk-N7LPPHJT.cjs.map +1 -0
  85. package/dist/chunk-OL25DU4X.cjs +23 -0
  86. package/dist/chunk-OL25DU4X.cjs.map +1 -0
  87. package/dist/chunk-PJMAHDQ2.cjs +25 -0
  88. package/dist/chunk-PJMAHDQ2.cjs.map +1 -0
  89. package/dist/chunk-T7LMMTZ2.js +23 -0
  90. package/dist/chunk-T7LMMTZ2.js.map +1 -0
  91. package/dist/chunk-TLBOMZFB.js +19 -0
  92. package/dist/chunk-TLBOMZFB.js.map +1 -0
  93. package/dist/chunk-U2YXVQS5.cjs +40 -0
  94. package/dist/chunk-U2YXVQS5.cjs.map +1 -0
  95. package/dist/chunk-V6AIRXAS.js +16 -0
  96. package/dist/chunk-V6AIRXAS.js.map +1 -0
  97. package/dist/chunk-VBAYCH3Q.js +13 -0
  98. package/dist/chunk-VBAYCH3Q.js.map +1 -0
  99. package/dist/chunk-VUNBMIIF.cjs +17 -0
  100. package/dist/chunk-VUNBMIIF.cjs.map +1 -0
  101. package/dist/chunk-WIG74DHT.js +51 -0
  102. package/dist/chunk-WIG74DHT.js.map +1 -0
  103. package/dist/chunk-XJEH7SUV.js +13 -0
  104. package/dist/chunk-XJEH7SUV.js.map +1 -0
  105. package/dist/chunk-XQEXQBRL.js +211 -0
  106. package/dist/chunk-XQEXQBRL.js.map +1 -0
  107. package/dist/chunk-YYLVPTHE.js +22 -0
  108. package/dist/chunk-YYLVPTHE.js.map +1 -0
  109. package/dist/compat.cjs +55 -0
  110. package/dist/compat.cjs.map +1 -0
  111. package/dist/compat.d.cts +20 -0
  112. package/dist/compat.d.ts +20 -0
  113. package/dist/compat.js +55 -0
  114. package/dist/compat.js.map +1 -0
  115. package/dist/frames.cjs +7 -0
  116. package/dist/frames.cjs.map +1 -0
  117. package/dist/frames.d.cts +14 -0
  118. package/dist/frames.d.ts +14 -0
  119. package/dist/frames.js +7 -0
  120. package/dist/frames.js.map +1 -0
  121. package/dist/github-fallback-FDNEH2BW.js +275 -0
  122. package/dist/github-fallback-FDNEH2BW.js.map +1 -0
  123. package/dist/github-fallback-YJY3LJG7.cjs +275 -0
  124. package/dist/github-fallback-YJY3LJG7.cjs.map +1 -0
  125. package/dist/icon-props-Cbax4n57.d.cts +9 -0
  126. package/dist/icon-props-Cbax4n57.d.ts +9 -0
  127. package/dist/index.cjs +15 -0
  128. package/dist/index.cjs.map +1 -0
  129. package/dist/index.d.cts +23 -0
  130. package/dist/index.d.ts +23 -0
  131. package/dist/index.js +15 -0
  132. package/dist/index.js.map +1 -0
  133. package/dist/link.full-BLJRZF6N.cjs +7 -0
  134. package/dist/link.full-BLJRZF6N.cjs.map +1 -0
  135. package/dist/link.full-DYTFNQHD.js +7 -0
  136. package/dist/link.full-DYTFNQHD.js.map +1 -0
  137. package/dist/link.mono-EXHT3WNS.cjs +7 -0
  138. package/dist/link.mono-EXHT3WNS.cjs.map +1 -0
  139. package/dist/link.mono-UJUAYACL.js +7 -0
  140. package/dist/link.mono-UJUAYACL.js.map +1 -0
  141. package/dist/pteusde.full-64LQVQ62.js +7 -0
  142. package/dist/pteusde.full-64LQVQ62.js.map +1 -0
  143. package/dist/pteusde.full-XSAPIA7I.cjs +7 -0
  144. package/dist/pteusde.full-XSAPIA7I.cjs.map +1 -0
  145. package/dist/pteusde.mono-B7RIQ4FK.cjs +7 -0
  146. package/dist/pteusde.mono-B7RIQ4FK.cjs.map +1 -0
  147. package/dist/pteusde.mono-NXQFXTCE.js +7 -0
  148. package/dist/pteusde.mono-NXQFXTCE.js.map +1 -0
  149. package/dist/tokens-3LM2CSL4.cjs +12 -0
  150. package/dist/tokens-3LM2CSL4.cjs.map +1 -0
  151. package/dist/tokens-SMSY4G7E.js +12 -0
  152. package/dist/tokens-SMSY4G7E.js.map +1 -0
  153. package/dist/tokens.cjs +90 -0
  154. package/dist/tokens.cjs.map +1 -0
  155. package/dist/tokens.d.cts +24 -0
  156. package/dist/tokens.d.ts +24 -0
  157. package/dist/tokens.js +90 -0
  158. package/dist/tokens.js.map +1 -0
  159. package/dist/uni.full-6CTLVORZ.js +7 -0
  160. package/dist/uni.full-6CTLVORZ.js.map +1 -0
  161. package/dist/uni.full-RYRRWMS4.cjs +7 -0
  162. package/dist/uni.full-RYRRWMS4.cjs.map +1 -0
  163. package/dist/uni.mono-B7C2JVKM.js +7 -0
  164. package/dist/uni.mono-B7C2JVKM.js.map +1 -0
  165. package/dist/uni.mono-LK6DAZYZ.cjs +7 -0
  166. package/dist/uni.mono-LK6DAZYZ.cjs.map +1 -0
  167. package/package.json +125 -0
@@ -0,0 +1,14 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties } from 'react';
3
+
4
+ interface FrameWrapperProps {
5
+ frame: string;
6
+ mono?: boolean;
7
+ size?: number | string;
8
+ children?: ReactNode;
9
+ }
10
+ declare function FrameWrapper({ frame, mono, size, children, }: FrameWrapperProps): react.DetailedReactHTMLElement<react.HTMLAttributes<HTMLElement>, HTMLElement> | react.DetailedReactHTMLElement<{
11
+ style: CSSProperties;
12
+ }, HTMLElement>;
13
+
14
+ export { FrameWrapper, type FrameWrapperProps };
@@ -0,0 +1,14 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties } from 'react';
3
+
4
+ interface FrameWrapperProps {
5
+ frame: string;
6
+ mono?: boolean;
7
+ size?: number | string;
8
+ children?: ReactNode;
9
+ }
10
+ declare function FrameWrapper({ frame, mono, size, children, }: FrameWrapperProps): react.DetailedReactHTMLElement<react.HTMLAttributes<HTMLElement>, HTMLElement> | react.DetailedReactHTMLElement<{
11
+ style: CSSProperties;
12
+ }, HTMLElement>;
13
+
14
+ export { FrameWrapper, type FrameWrapperProps };
package/dist/frames.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ FrameWrapper
3
+ } from "./chunk-26TL622R.js";
4
+ export {
5
+ FrameWrapper
6
+ };
7
+ //# sourceMappingURL=frames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,275 @@
1
+ import {
2
+ useIconConfig
3
+ } from "./chunk-KMTZSDBE.js";
4
+
5
+ // src/github-fallback.tsx
6
+ import { useEffect, useMemo, useState } from "react";
7
+ import { resolve } from "@smbdy/icons/resolve";
8
+
9
+ // src/svg-to-react.ts
10
+ import { createElement } from "react";
11
+
12
+ // src/sanitize-svg.ts
13
+ import createDOMPurify from "dompurify";
14
+ var FORBID_TAGS = ["script", "foreignObject", "text", "image", "style", "use"];
15
+ var purifierCache = null;
16
+ function getPurifier() {
17
+ if (purifierCache) return purifierCache;
18
+ if (typeof window === "undefined") return null;
19
+ const p = createDOMPurify(
20
+ window
21
+ );
22
+ p.addHook("uponSanitizeAttribute", (_node, ev) => {
23
+ if (ev.attrName === "href" || ev.attrName === "xlink:href") {
24
+ const v = typeof ev.attrValue === "string" ? ev.attrValue : "";
25
+ if (!v.startsWith("#")) ev.keepAttr = false;
26
+ }
27
+ if (ev.attrName === "style") {
28
+ const v = typeof ev.attrValue === "string" ? ev.attrValue : "";
29
+ if (/url\s*\(\s*['"]?\s*(?!#)/i.test(v)) ev.keepAttr = false;
30
+ }
31
+ });
32
+ purifierCache = p;
33
+ return p;
34
+ }
35
+ function sanitizeSvgRoot(svgText) {
36
+ if (typeof DOMParser === "undefined") return null;
37
+ const purifier = getPurifier();
38
+ if (!purifier) return null;
39
+ const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
40
+ const root = doc.documentElement;
41
+ if (root.nodeName !== "svg") return null;
42
+ purifier.sanitize(root, {
43
+ USE_PROFILES: { svg: true, svgFilters: true },
44
+ FORBID_TAGS,
45
+ IN_PLACE: true
46
+ });
47
+ return root;
48
+ }
49
+
50
+ // src/svg-attributes.ts
51
+ function toReactAttributeName(name) {
52
+ if (name === "class") return "className";
53
+ if (name === "tabindex") return "tabIndex";
54
+ if (name.startsWith("aria-") || name.startsWith("data-")) return name;
55
+ return name.replace(
56
+ /[:-]([a-z])/g,
57
+ (_, letter) => letter.toUpperCase()
58
+ );
59
+ }
60
+ function styleStringToObject(style) {
61
+ const result = {};
62
+ for (const rule of style.split(";")) {
63
+ const [rawProperty, ...rawValue] = rule.split(":");
64
+ const property = rawProperty?.trim();
65
+ const value = rawValue.join(":").trim();
66
+ if (!property || !value) continue;
67
+ const reactProperty = property.startsWith("--") ? property : property.replace(
68
+ /-([a-z])/g,
69
+ (_, letter) => letter.toUpperCase()
70
+ );
71
+ result[reactProperty] = value;
72
+ }
73
+ return result;
74
+ }
75
+
76
+ // src/svg-to-react.ts
77
+ function svgNodeToReact(node, key) {
78
+ if (node.nodeType === Node.TEXT_NODE) return node.textContent;
79
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
80
+ const element = node;
81
+ const props = { key };
82
+ for (const attr of Array.from(element.attributes)) {
83
+ const attrName = toReactAttributeName(attr.name);
84
+ props[attrName] = attr.name === "style" ? styleStringToObject(attr.value) : attr.value;
85
+ }
86
+ const children = Array.from(element.childNodes).map((child, index) => svgNodeToReact(child, `${key}-${index}`)).filter((child) => child !== null);
87
+ return createElement(element.tagName, props, ...children);
88
+ }
89
+ function svgTextToReact(svgText) {
90
+ const root = sanitizeSvgRoot(svgText);
91
+ if (!root) return null;
92
+ const viewBox = root.getAttribute("viewBox") ?? "0 0 32 32";
93
+ const node = Array.from(root.childNodes).map((child, index) => svgNodeToReact(child, String(index))).filter((child) => child !== null);
94
+ return { node, viewBox };
95
+ }
96
+
97
+ // src/github-fallback.tsx
98
+ import { Fragment, jsx } from "react/jsx-runtime";
99
+ var svgCache = /* @__PURE__ */ new Map();
100
+ var MAX_CACHE = 200;
101
+ var NEGATIVE_CACHE_TTL_MS = 6e4;
102
+ var FETCH_TIMEOUT_MS = 8e3;
103
+ function cacheEntry(url) {
104
+ const entry = svgCache.get(url);
105
+ if (entry === void 0) return void 0;
106
+ if (entry.value !== null) return entry;
107
+ if (Date.now() - entry.ts > NEGATIVE_CACHE_TTL_MS) return void 0;
108
+ return entry;
109
+ }
110
+ function cacheGet(url) {
111
+ const entry = cacheEntry(url);
112
+ return entry === void 0 ? void 0 : entry.value;
113
+ }
114
+ function cacheSet(url, value) {
115
+ if (!svgCache.has(url) && svgCache.size >= MAX_CACHE) {
116
+ const oldest = svgCache.keys().next().value;
117
+ if (oldest !== void 0) svgCache.delete(oldest);
118
+ }
119
+ svgCache.set(url, { value, ts: Date.now() });
120
+ }
121
+ function cachedContentFor(url, text) {
122
+ const entry = svgCache.get(url);
123
+ if (entry && entry.content) return entry.content;
124
+ const content = svgTextToReact(text);
125
+ if (content && entry) entry.content = content;
126
+ return content;
127
+ }
128
+ function toAssetType(type) {
129
+ if (type === "token") return "tokens";
130
+ if (type === "chain") return "chains";
131
+ return "brands";
132
+ }
133
+ function inferType(id, explicitType) {
134
+ if (explicitType) return toAssetType(explicitType);
135
+ const hit = resolve(id);
136
+ return hit ? toAssetType(hit.type) : null;
137
+ }
138
+ function buildUrl(baseUrl, branch, type, id, variant) {
139
+ return `${baseUrl}/${branch}/assets/${type}/${id}_${variant}.svg`;
140
+ }
141
+ var inFlight = /* @__PURE__ */ new Map();
142
+ function fetchSvg(url) {
143
+ const cached = cacheGet(url);
144
+ if (cached !== void 0) return Promise.resolve(cached);
145
+ svgCache.delete(url);
146
+ let pending = inFlight.get(url);
147
+ if (pending) return pending;
148
+ pending = (async () => {
149
+ const ac = new AbortController();
150
+ const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
151
+ try {
152
+ const res = await fetch(url, { signal: ac.signal });
153
+ if (!res.ok) {
154
+ cacheSet(url, null);
155
+ return null;
156
+ }
157
+ const text = await res.text();
158
+ cacheSet(url, text);
159
+ return text;
160
+ } catch {
161
+ return null;
162
+ } finally {
163
+ clearTimeout(timer);
164
+ inFlight.delete(url);
165
+ }
166
+ })();
167
+ inFlight.set(url, pending);
168
+ return pending;
169
+ }
170
+ async function resolveSvg(baseUrl, branch, id, type, variant) {
171
+ if (type) {
172
+ const url = buildUrl(baseUrl, branch, type, id, variant);
173
+ const text = await fetchSvg(url);
174
+ return text === null ? null : { url, text };
175
+ }
176
+ const types = ["tokens", "chains", "brands"];
177
+ const urls = types.map((t) => buildUrl(baseUrl, branch, t, id, variant));
178
+ const results = await Promise.all(urls.map((url) => fetchSvg(url)));
179
+ const index = results.findIndex((result) => result !== null);
180
+ return index === -1 ? null : { url: urls[index], text: results[index] };
181
+ }
182
+ function getCachedContent(baseUrl, branch, type, id, variant) {
183
+ if (!type) return null;
184
+ const url = buildUrl(baseUrl, branch, type, id, variant);
185
+ const cached = cacheGet(url);
186
+ return typeof cached === "string" ? cachedContentFor(url, cached) : null;
187
+ }
188
+ function GithubFallback({
189
+ id,
190
+ rawValue,
191
+ iconType,
192
+ mono = false,
193
+ size = 32,
194
+ fallback,
195
+ ...props
196
+ }) {
197
+ const variant = mono ? "mono" : "full";
198
+ const { baseUrl, branch } = useIconConfig();
199
+ const type = inferType(id, iconType);
200
+ const requestKey = `${baseUrl}|${branch}|${iconType ?? "auto"}|${id}|${variant}`;
201
+ const cachedContent = useMemo(
202
+ () => getCachedContent(baseUrl, branch, type, id, variant),
203
+ [baseUrl, branch, type, id, variant]
204
+ );
205
+ const [state, setState] = useState(() => ({
206
+ key: requestKey,
207
+ content: cachedContent,
208
+ failed: false
209
+ }));
210
+ const currentState = state.key === requestKey ? state : {
211
+ key: requestKey,
212
+ content: cachedContent,
213
+ failed: false
214
+ };
215
+ useEffect(() => {
216
+ if (currentState.content || currentState.failed) return;
217
+ let cancelled = false;
218
+ resolveSvg(baseUrl, branch, id, type, variant).then((resolved) => {
219
+ if (cancelled) return;
220
+ if (!resolved) {
221
+ setState({ key: requestKey, content: null, failed: true });
222
+ return;
223
+ }
224
+ const content = cachedContentFor(resolved.url, resolved.text);
225
+ if (!content) cacheSet(resolved.url, null);
226
+ setState({
227
+ key: requestKey,
228
+ content,
229
+ failed: !content
230
+ });
231
+ });
232
+ return () => {
233
+ cancelled = true;
234
+ };
235
+ }, [
236
+ baseUrl,
237
+ branch,
238
+ currentState.failed,
239
+ currentState.content,
240
+ id,
241
+ requestKey,
242
+ type,
243
+ variant
244
+ ]);
245
+ useEffect(() => {
246
+ if (!currentState.failed) return;
247
+ const timer = setTimeout(() => {
248
+ setState(
249
+ (s) => s.key === requestKey && s.failed ? { key: requestKey, content: null, failed: false } : s
250
+ );
251
+ }, NEGATIVE_CACHE_TTL_MS);
252
+ return () => clearTimeout(timer);
253
+ }, [currentState.failed, requestKey]);
254
+ if (currentState.failed) return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? null });
255
+ if (!currentState.content) return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? null });
256
+ const monoProps = mono ? { fill: "currentColor" } : {};
257
+ return /* @__PURE__ */ jsx(
258
+ "svg",
259
+ {
260
+ xmlns: "http://www.w3.org/2000/svg",
261
+ width: size,
262
+ height: size,
263
+ viewBox: currentState.content.viewBox,
264
+ role: "img",
265
+ "aria-label": rawValue !== void 0 ? String(rawValue) : id,
266
+ ...monoProps,
267
+ ...props,
268
+ children: currentState.content.node
269
+ }
270
+ );
271
+ }
272
+ export {
273
+ GithubFallback
274
+ };
275
+ //# sourceMappingURL=github-fallback-FDNEH2BW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/github-fallback.tsx","../src/svg-to-react.ts","../src/sanitize-svg.ts","../src/svg-attributes.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react'\nimport type { ReactNode, SVGProps } from 'react'\nimport { resolve } from '@smbdy/icons/resolve'\nimport { useIconConfig } from './icon-provider'\nimport { svgTextToReact } from './svg-to-react'\nimport type { SvgReactContent } from './svg-to-react'\nimport type { IconType } from './types'\n\n// Module-level SVG cache, keyed by URL. null = negative cache (tried & failed).\n// `content` memoises the converted React description so repeated mounts of\n// the same fallback icon reuse one sanitize/parse pass instead of re-running\n// it per instance. React elements are immutable, so sharing one\n// SvgReactContent across many component instances is safe.\ninterface CacheEntry {\n value: string | null\n ts: number\n content?: SvgReactContent\n}\n\nconst svgCache = new Map<string, CacheEntry>()\nconst MAX_CACHE = 200\nconst NEGATIVE_CACHE_TTL_MS = 60_000\nconst FETCH_TIMEOUT_MS = 8_000\n\n// Pure read — safe from the render phase (getCachedContent runs in a\n// useMemo). An expired negative entry is reported as absent but NOT deleted\n// here; fetchSvg purges it in event/effect context, and stragglers are\n// bounded by FIFO eviction anyway.\nfunction cacheEntry(url: string): CacheEntry | undefined {\n const entry = svgCache.get(url)\n if (entry === undefined) return undefined\n // Successful entries never expire (until FIFO eviction)\n if (entry.value !== null) return entry\n // Negative entries expire after TTL\n if (Date.now() - entry.ts > NEGATIVE_CACHE_TTL_MS) return undefined\n return entry\n}\n\nfunction cacheGet(url: string): string | null | undefined {\n const entry = cacheEntry(url)\n return entry === undefined ? undefined : entry.value\n}\n\nfunction cacheSet(url: string, value: string | null) {\n // Map preserves insertion order, so the first key is the oldest. Only a\n // NEW key grows the cache; overwriting an existing key (negative ->\n // positive) keeps its original position.\n if (!svgCache.has(url) && svgCache.size >= MAX_CACHE) {\n const oldest = svgCache.keys().next().value\n if (oldest !== undefined) svgCache.delete(oldest)\n }\n svgCache.set(url, { value, ts: Date.now() })\n}\n\n// Memoise the converted content on a positive cache entry. Only non-null\n// conversions are stored (SSR returns null when DOMParser is missing — never\n// cache that). Returns the converted content, or null when unconvertible.\nfunction cachedContentFor(url: string, text: string): SvgReactContent | null {\n const entry = svgCache.get(url)\n if (entry && entry.content) return entry.content\n const content = svgTextToReact(text)\n if (content && entry) entry.content = content\n return content\n}\n\ntype AssetType = 'tokens' | 'chains' | 'brands'\n\nfunction toAssetType(type: IconType): AssetType {\n if (type === 'token') return 'tokens'\n if (type === 'chain') return 'chains'\n return 'brands'\n}\n\nfunction inferType(id: string, explicitType?: IconType): AssetType | null {\n if (explicitType) return toAssetType(explicitType)\n // Ask the resolution module (token-first per ADR-0001) rather than\n // probing the metadata maps per type.\n const hit = resolve(id)\n return hit ? toAssetType(hit.type) : null\n}\n\nfunction buildUrl(\n baseUrl: string,\n branch: string,\n type: AssetType,\n id: string,\n variant: string,\n): string {\n return `${baseUrl}/${branch}/assets/${type}/${id}_${variant}.svg`\n}\n\n// One network request per URL no matter how many components want it: a\n// token list rendering 50 rows of the same unknown asset must not fire 50\n// fetches. The shared request is never aborted by any single component —\n// unmounted callers just ignore the settled value — and carries its own\n// timeout instead of a caller signal.\nconst inFlight = new Map<string, Promise<string | null>>()\n\nfunction fetchSvg(url: string): Promise<string | null> {\n const cached = cacheGet(url)\n if (cached !== undefined) return Promise.resolve(cached)\n // A live entry would have been returned above, so anything still stored\n // under this URL is an expired negative entry — purge it here (event/\n // effect context) so the refreshed entry re-enters FIFO order cleanly.\n svgCache.delete(url)\n\n let pending = inFlight.get(url)\n if (pending) return pending\n\n pending = (async () => {\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS)\n try {\n const res = await fetch(url, { signal: ac.signal })\n if (!res.ok) {\n // A definitive miss (404 etc.) is negative-cached for the TTL.\n cacheSet(url, null)\n return null\n }\n const text = await res.text()\n cacheSet(url, text)\n return text\n } catch {\n // Timeouts and network errors are transient — do NOT negative-cache\n // them, so the next attempt (remount or TTL retry) reaches the\n // network again.\n return null\n } finally {\n clearTimeout(timer)\n inFlight.delete(url)\n }\n })()\n\n inFlight.set(url, pending)\n return pending\n}\n\ninterface ResolvedSvg {\n url: string\n text: string\n}\n\nasync function resolveSvg(\n baseUrl: string,\n branch: string,\n id: string,\n type: AssetType | null,\n variant: string,\n): Promise<ResolvedSvg | null> {\n if (type) {\n const url = buildUrl(baseUrl, branch, type, id, variant)\n const text = await fetchSvg(url)\n return text === null ? null : { url, text }\n }\n\n // Unknown type — probe all three and keep token-first result ordering.\n const types: AssetType[] = ['tokens', 'chains', 'brands']\n const urls = types.map((t) => buildUrl(baseUrl, branch, t, id, variant))\n const results = await Promise.all(urls.map((url) => fetchSvg(url)))\n const index = results.findIndex((result) => result !== null)\n return index === -1 ? null : { url: urls[index], text: results[index]! }\n}\n\nexport interface GithubFallbackProps extends Omit<\n SVGProps<SVGSVGElement>,\n 'ref'\n> {\n id: string\n /** The original user-supplied value, used for the accessible name. */\n rawValue?: string | number\n iconType?: IconType\n mono?: boolean\n size?: number | string\n fallback?: ReactNode\n}\n\ninterface FallbackState {\n key: string\n content: SvgReactContent | null\n failed: boolean\n}\n\nfunction getCachedContent(\n baseUrl: string,\n branch: string,\n type: AssetType | null,\n id: string,\n variant: string,\n): SvgReactContent | null {\n if (!type) return null\n const url = buildUrl(baseUrl, branch, type, id, variant)\n const cached = cacheGet(url)\n return typeof cached === 'string' ? cachedContentFor(url, cached) : null\n}\n\nexport function GithubFallback({\n id,\n rawValue,\n iconType,\n mono = false,\n size = 32,\n fallback,\n ...props\n}: GithubFallbackProps) {\n const variant = mono ? 'mono' : 'full'\n const { baseUrl, branch } = useIconConfig()\n // Resolve the asset type once per render (pure, cheap-ish lookup) and\n // thread it through both the cache-read and the fetch path so core\n // `resolve` runs at most once.\n const type = inferType(id, iconType)\n const requestKey = `${baseUrl}|${branch}|${iconType ?? 'auto'}|${id}|${variant}`\n const cachedContent = useMemo(\n () => getCachedContent(baseUrl, branch, type, id, variant),\n [baseUrl, branch, type, id, variant],\n )\n const [state, setState] = useState<FallbackState>(() => ({\n key: requestKey,\n content: cachedContent,\n failed: false,\n }))\n const currentState =\n state.key === requestKey\n ? state\n : {\n key: requestKey,\n content: cachedContent,\n failed: false,\n }\n\n useEffect(() => {\n if (currentState.content || currentState.failed) return\n\n let cancelled = false\n resolveSvg(baseUrl, branch, id, type, variant).then((resolved) => {\n if (cancelled) return\n if (!resolved) {\n setState({ key: requestKey, content: null, failed: true })\n return\n }\n // Convert once and memoise on the cache entry so other mounts of the\n // same URL reuse it. A null conversion (unparseable text) is\n // negative-cached so other mounts don't re-attempt the parse;\n // transient network misses never reach here (resolved is null).\n const content = cachedContentFor(resolved.url, resolved.text)\n if (!content) cacheSet(resolved.url, null)\n setState({\n key: requestKey,\n content,\n failed: !content,\n })\n })\n\n return () => {\n cancelled = true\n }\n }, [\n baseUrl,\n branch,\n currentState.failed,\n currentState.content,\n id,\n requestKey,\n type,\n variant,\n ])\n\n // A failure is not forever: long-lived components (dashboards) get one\n // retry per negative-cache window. Clearing `failed` re-arms the fetch\n // effect; within-TTL retries are absorbed by the negative cache, so the\n // network sees at most one request per URL per TTL.\n useEffect(() => {\n if (!currentState.failed) return\n const timer = setTimeout(() => {\n setState((s) =>\n s.key === requestKey && s.failed\n ? { key: requestKey, content: null, failed: false }\n : s,\n )\n }, NEGATIVE_CACHE_TTL_MS)\n return () => clearTimeout(timer)\n }, [currentState.failed, requestKey])\n\n if (currentState.failed) return <>{fallback ?? null}</>\n if (!currentState.content) return <>{fallback ?? null}</>\n\n const monoProps = mono ? { fill: 'currentColor' } : {}\n\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox={currentState.content.viewBox}\n role=\"img\"\n aria-label={rawValue !== undefined ? String(rawValue) : id}\n {...monoProps}\n {...props}\n >\n {currentState.content.node}\n </svg>\n )\n}\n","import { createElement } from 'react'\nimport type { ReactNode } from 'react'\nimport { sanitizeSvgRoot } from './sanitize-svg'\nimport { styleStringToObject, toReactAttributeName } from './svg-attributes'\n\nexport interface SvgReactContent {\n node: ReactNode\n viewBox: string\n}\n\nfunction svgNodeToReact(node: ChildNode, key: string): ReactNode {\n if (node.nodeType === Node.TEXT_NODE) return node.textContent\n if (node.nodeType !== Node.ELEMENT_NODE) return null\n\n const element = node as Element\n const props: Record<string, unknown> = { key }\n for (const attr of Array.from(element.attributes)) {\n const attrName = toReactAttributeName(attr.name)\n props[attrName] =\n attr.name === 'style' ? styleStringToObject(attr.value) : attr.value\n }\n\n const children = Array.from(element.childNodes)\n .map((child, index) => svgNodeToReact(child, `${key}-${index}`))\n .filter((child) => child !== null)\n\n return createElement(element.tagName, props, ...children)\n}\n\n/**\n * Sanitize an SVG string and convert its children to React nodes in one\n * pass over a single parsed DOM. Returns the converted children and the\n * root's viewBox; the caller renders its own `<svg>` wrapper so it controls\n * sizing and accessibility attributes.\n *\n * Returns null when the input is not a parseable `<svg>` document (or when\n * no DOM is available, e.g. during SSR).\n */\nexport function svgTextToReact(svgText: string): SvgReactContent | null {\n const root = sanitizeSvgRoot(svgText)\n if (!root) return null\n const viewBox = root.getAttribute('viewBox') ?? '0 0 32 32'\n const node = Array.from(root.childNodes)\n .map((child, index) => svgNodeToReact(child, String(index)))\n .filter((child) => child !== null)\n return { node, viewBox }\n}\n","import createDOMPurify from 'dompurify'\nimport type { DOMPurify } from 'dompurify'\n\n// `<use>` and `<script>` are blocked by DOMPurify's SVG profile already\n// (svgDisallowed in the upstream source); listing them here is defense in\n// depth in case a future profile change relaxes that.\nconst FORBID_TAGS = ['script', 'foreignObject', 'text', 'image', 'style', 'use']\n\nlet purifierCache: DOMPurify | null = null\n\nfunction getPurifier(): DOMPurify | null {\n if (purifierCache) return purifierCache\n if (typeof window === 'undefined') return null\n const p = createDOMPurify(\n window as unknown as Parameters<typeof createDOMPurify>[0],\n )\n // ALLOWED_URI_REGEXP can't be used to restrict href/xlink:href without also\n // killing geometry attrs like cx/cy/r (DOMPurify applies the URI regex to\n // every non-URI-safe attribute value). A scoped hook on this DOMPurify\n // instance lets us reject non-fragment href values without touching the\n // global DOMPurify state — important for consumers who use DOMPurify\n // elsewhere in their app.\n p.addHook('uponSanitizeAttribute', (_node, ev) => {\n if (ev.attrName === 'href' || ev.attrName === 'xlink:href') {\n const v = typeof ev.attrValue === 'string' ? ev.attrValue : ''\n if (!v.startsWith('#')) ev.keepAttr = false\n }\n // style=\"fill:url(http://...)\" survives the SVG profile — a remote\n // fetch (tracking-pixel surface) we never need: drop any style value\n // referencing a URL that isn't a local fragment.\n if (ev.attrName === 'style') {\n const v = typeof ev.attrValue === 'string' ? ev.attrValue : ''\n if (/url\\s*\\(\\s*['\"]?\\s*(?!#)/i.test(v)) ev.keepAttr = false\n }\n })\n purifierCache = p\n return p\n}\n\n// DOMPurify's string-mode sanitize parses input as HTML, which silently\n// drops case-sensitive SVG attributes (viewBox, preserveAspectRatio). Parse\n// with image/svg+xml first and sanitize in place to keep the SVG-namespace\n// context and attribute case. svg-to-react.ts converts the returned root to\n// React nodes from this same DOM — no serialize/re-parse round-trip.\nexport function sanitizeSvgRoot(svgText: string): SVGSVGElement | null {\n if (typeof DOMParser === 'undefined') return null\n const purifier = getPurifier()\n if (!purifier) return null\n const doc = new DOMParser().parseFromString(svgText, 'image/svg+xml')\n const root = doc.documentElement\n if (root.nodeName !== 'svg') return null\n purifier.sanitize(root, {\n USE_PROFILES: { svg: true, svgFilters: true },\n FORBID_TAGS,\n IN_PLACE: true,\n })\n return root as unknown as SVGSVGElement\n}\n\nexport function sanitizeSvg(svgText: string): string {\n const root = sanitizeSvgRoot(svgText)\n if (!root) return ''\n return new XMLSerializer().serializeToString(root)\n}\n","// Single source of truth for converting SVG markup attributes into their\n// React equivalents. Two adapters sit on this interface: the build pipeline\n// (scripts/svg-to-jsx.ts emits JSX strings for generated components) and the\n// runtime network fallback (svg-to-react.ts builds React nodes from fetched\n// SVGs). Sharing the rule guarantees an icon renders identically whether it\n// shipped in the bundle or arrived over the wire.\n\nexport function toReactAttributeName(name: string): string {\n if (name === 'class') return 'className'\n if (name === 'tabindex') return 'tabIndex'\n if (name.startsWith('aria-') || name.startsWith('data-')) return name\n return name.replace(/[:-]([a-z])/g, (_, letter: string) =>\n letter.toUpperCase(),\n )\n}\n\nexport function styleStringToObject(style: string): Record<string, string> {\n const result: Record<string, string> = {}\n for (const rule of style.split(';')) {\n const [rawProperty, ...rawValue] = rule.split(':')\n const property = rawProperty?.trim()\n const value = rawValue.join(':').trim()\n if (!property || !value) continue\n // CSS custom properties pass through verbatim — camelising\n // \"--brand-color\" would mangle it into \"-BrandColor\".\n const reactProperty = property.startsWith('--')\n ? property\n : property.replace(/-([a-z])/g, (_, letter: string) =>\n letter.toUpperCase(),\n )\n result[reactProperty] = value\n }\n return result\n}\n"],"mappings":";;;;;AAAA,SAAS,WAAW,SAAS,gBAAgB;AAE7C,SAAS,eAAe;;;ACFxB,SAAS,qBAAqB;;;ACA9B,OAAO,qBAAqB;AAM5B,IAAM,cAAc,CAAC,UAAU,iBAAiB,QAAQ,SAAS,SAAS,KAAK;AAE/E,IAAI,gBAAkC;AAEtC,SAAS,cAAgC;AACvC,MAAI,cAAe,QAAO;AAC1B,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AAOA,IAAE,QAAQ,yBAAyB,CAAC,OAAO,OAAO;AAChD,QAAI,GAAG,aAAa,UAAU,GAAG,aAAa,cAAc;AAC1D,YAAM,IAAI,OAAO,GAAG,cAAc,WAAW,GAAG,YAAY;AAC5D,UAAI,CAAC,EAAE,WAAW,GAAG,EAAG,IAAG,WAAW;AAAA,IACxC;AAIA,QAAI,GAAG,aAAa,SAAS;AAC3B,YAAM,IAAI,OAAO,GAAG,cAAc,WAAW,GAAG,YAAY;AAC5D,UAAI,4BAA4B,KAAK,CAAC,EAAG,IAAG,WAAW;AAAA,IACzD;AAAA,EACF,CAAC;AACD,kBAAgB;AAChB,SAAO;AACT;AAOO,SAAS,gBAAgB,SAAuC;AACrE,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,QAAM,WAAW,YAAY;AAC7B,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,MAAM,IAAI,UAAU,EAAE,gBAAgB,SAAS,eAAe;AACpE,QAAM,OAAO,IAAI;AACjB,MAAI,KAAK,aAAa,MAAO,QAAO;AACpC,WAAS,SAAS,MAAM;AAAA,IACtB,cAAc,EAAE,KAAK,MAAM,YAAY,KAAK;AAAA,IAC5C;AAAA,IACA,UAAU;AAAA,EACZ,CAAC;AACD,SAAO;AACT;;;AClDO,SAAS,qBAAqB,MAAsB;AACzD,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,SAAS,WAAY,QAAO;AAChC,MAAI,KAAK,WAAW,OAAO,KAAK,KAAK,WAAW,OAAO,EAAG,QAAO;AACjE,SAAO,KAAK;AAAA,IAAQ;AAAA,IAAgB,CAAC,GAAG,WACtC,OAAO,YAAY;AAAA,EACrB;AACF;AAEO,SAAS,oBAAoB,OAAuC;AACzE,QAAM,SAAiC,CAAC;AACxC,aAAW,QAAQ,MAAM,MAAM,GAAG,GAAG;AACnC,UAAM,CAAC,aAAa,GAAG,QAAQ,IAAI,KAAK,MAAM,GAAG;AACjD,UAAM,WAAW,aAAa,KAAK;AACnC,UAAM,QAAQ,SAAS,KAAK,GAAG,EAAE,KAAK;AACtC,QAAI,CAAC,YAAY,CAAC,MAAO;AAGzB,UAAM,gBAAgB,SAAS,WAAW,IAAI,IAC1C,WACA,SAAS;AAAA,MAAQ;AAAA,MAAa,CAAC,GAAG,WAChC,OAAO,YAAY;AAAA,IACrB;AACJ,WAAO,aAAa,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;;;AFvBA,SAAS,eAAe,MAAiB,KAAwB;AAC/D,MAAI,KAAK,aAAa,KAAK,UAAW,QAAO,KAAK;AAClD,MAAI,KAAK,aAAa,KAAK,aAAc,QAAO;AAEhD,QAAM,UAAU;AAChB,QAAM,QAAiC,EAAE,IAAI;AAC7C,aAAW,QAAQ,MAAM,KAAK,QAAQ,UAAU,GAAG;AACjD,UAAM,WAAW,qBAAqB,KAAK,IAAI;AAC/C,UAAM,QAAQ,IACZ,KAAK,SAAS,UAAU,oBAAoB,KAAK,KAAK,IAAI,KAAK;AAAA,EACnE;AAEA,QAAM,WAAW,MAAM,KAAK,QAAQ,UAAU,EAC3C,IAAI,CAAC,OAAO,UAAU,eAAe,OAAO,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,EAC9D,OAAO,CAAC,UAAU,UAAU,IAAI;AAEnC,SAAO,cAAc,QAAQ,SAAS,OAAO,GAAG,QAAQ;AAC1D;AAWO,SAAS,eAAe,SAAyC;AACtE,QAAM,OAAO,gBAAgB,OAAO;AACpC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,UAAU,KAAK,aAAa,SAAS,KAAK;AAChD,QAAM,OAAO,MAAM,KAAK,KAAK,UAAU,EACpC,IAAI,CAAC,OAAO,UAAU,eAAe,OAAO,OAAO,KAAK,CAAC,CAAC,EAC1D,OAAO,CAAC,UAAU,UAAU,IAAI;AACnC,SAAO,EAAE,MAAM,QAAQ;AACzB;;;AD4OkC;AAvQlC,IAAM,WAAW,oBAAI,IAAwB;AAC7C,IAAM,YAAY;AAClB,IAAM,wBAAwB;AAC9B,IAAM,mBAAmB;AAMzB,SAAS,WAAW,KAAqC;AACvD,QAAM,QAAQ,SAAS,IAAI,GAAG;AAC9B,MAAI,UAAU,OAAW,QAAO;AAEhC,MAAI,MAAM,UAAU,KAAM,QAAO;AAEjC,MAAI,KAAK,IAAI,IAAI,MAAM,KAAK,sBAAuB,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,SAAS,KAAwC;AACxD,QAAM,QAAQ,WAAW,GAAG;AAC5B,SAAO,UAAU,SAAY,SAAY,MAAM;AACjD;AAEA,SAAS,SAAS,KAAa,OAAsB;AAInD,MAAI,CAAC,SAAS,IAAI,GAAG,KAAK,SAAS,QAAQ,WAAW;AACpD,UAAM,SAAS,SAAS,KAAK,EAAE,KAAK,EAAE;AACtC,QAAI,WAAW,OAAW,UAAS,OAAO,MAAM;AAAA,EAClD;AACA,WAAS,IAAI,KAAK,EAAE,OAAO,IAAI,KAAK,IAAI,EAAE,CAAC;AAC7C;AAKA,SAAS,iBAAiB,KAAa,MAAsC;AAC3E,QAAM,QAAQ,SAAS,IAAI,GAAG;AAC9B,MAAI,SAAS,MAAM,QAAS,QAAO,MAAM;AACzC,QAAM,UAAU,eAAe,IAAI;AACnC,MAAI,WAAW,MAAO,OAAM,UAAU;AACtC,SAAO;AACT;AAIA,SAAS,YAAY,MAA2B;AAC9C,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,SAAS,QAAS,QAAO;AAC7B,SAAO;AACT;AAEA,SAAS,UAAU,IAAY,cAA2C;AACxE,MAAI,aAAc,QAAO,YAAY,YAAY;AAGjD,QAAM,MAAM,QAAQ,EAAE;AACtB,SAAO,MAAM,YAAY,IAAI,IAAI,IAAI;AACvC;AAEA,SAAS,SACP,SACA,QACA,MACA,IACA,SACQ;AACR,SAAO,GAAG,OAAO,IAAI,MAAM,WAAW,IAAI,IAAI,EAAE,IAAI,OAAO;AAC7D;AAOA,IAAM,WAAW,oBAAI,IAAoC;AAEzD,SAAS,SAAS,KAAqC;AACrD,QAAM,SAAS,SAAS,GAAG;AAC3B,MAAI,WAAW,OAAW,QAAO,QAAQ,QAAQ,MAAM;AAIvD,WAAS,OAAO,GAAG;AAEnB,MAAI,UAAU,SAAS,IAAI,GAAG;AAC9B,MAAI,QAAS,QAAO;AAEpB,aAAW,YAAY;AACrB,UAAM,KAAK,IAAI,gBAAgB;AAC/B,UAAM,QAAQ,WAAW,MAAM,GAAG,MAAM,GAAG,gBAAgB;AAC3D,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;AAClD,UAAI,CAAC,IAAI,IAAI;AAEX,iBAAS,KAAK,IAAI;AAClB,eAAO;AAAA,MACT;AACA,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,eAAS,KAAK,IAAI;AAClB,aAAO;AAAA,IACT,QAAQ;AAIN,aAAO;AAAA,IACT,UAAE;AACA,mBAAa,KAAK;AAClB,eAAS,OAAO,GAAG;AAAA,IACrB;AAAA,EACF,GAAG;AAEH,WAAS,IAAI,KAAK,OAAO;AACzB,SAAO;AACT;AAOA,eAAe,WACb,SACA,QACA,IACA,MACA,SAC6B;AAC7B,MAAI,MAAM;AACR,UAAM,MAAM,SAAS,SAAS,QAAQ,MAAM,IAAI,OAAO;AACvD,UAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,WAAO,SAAS,OAAO,OAAO,EAAE,KAAK,KAAK;AAAA,EAC5C;AAGA,QAAM,QAAqB,CAAC,UAAU,UAAU,QAAQ;AACxD,QAAM,OAAO,MAAM,IAAI,CAAC,MAAM,SAAS,SAAS,QAAQ,GAAG,IAAI,OAAO,CAAC;AACvE,QAAM,UAAU,MAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,QAAQ,SAAS,GAAG,CAAC,CAAC;AAClE,QAAM,QAAQ,QAAQ,UAAU,CAAC,WAAW,WAAW,IAAI;AAC3D,SAAO,UAAU,KAAK,OAAO,EAAE,KAAK,KAAK,KAAK,GAAG,MAAM,QAAQ,KAAK,EAAG;AACzE;AAqBA,SAAS,iBACP,SACA,QACA,MACA,IACA,SACwB;AACxB,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,MAAM,SAAS,SAAS,QAAQ,MAAM,IAAI,OAAO;AACvD,QAAM,SAAS,SAAS,GAAG;AAC3B,SAAO,OAAO,WAAW,WAAW,iBAAiB,KAAK,MAAM,IAAI;AACtE;AAEO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,OAAO;AAAA,EACP;AAAA,EACA,GAAG;AACL,GAAwB;AACtB,QAAM,UAAU,OAAO,SAAS;AAChC,QAAM,EAAE,SAAS,OAAO,IAAI,cAAc;AAI1C,QAAM,OAAO,UAAU,IAAI,QAAQ;AACnC,QAAM,aAAa,GAAG,OAAO,IAAI,MAAM,IAAI,YAAY,MAAM,IAAI,EAAE,IAAI,OAAO;AAC9E,QAAM,gBAAgB;AAAA,IACpB,MAAM,iBAAiB,SAAS,QAAQ,MAAM,IAAI,OAAO;AAAA,IACzD,CAAC,SAAS,QAAQ,MAAM,IAAI,OAAO;AAAA,EACrC;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,OAAO;AAAA,IACvD,KAAK;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,EACV,EAAE;AACF,QAAM,eACJ,MAAM,QAAQ,aACV,QACA;AAAA,IACE,KAAK;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAEN,YAAU,MAAM;AACd,QAAI,aAAa,WAAW,aAAa,OAAQ;AAEjD,QAAI,YAAY;AAChB,eAAW,SAAS,QAAQ,IAAI,MAAM,OAAO,EAAE,KAAK,CAAC,aAAa;AAChE,UAAI,UAAW;AACf,UAAI,CAAC,UAAU;AACb,iBAAS,EAAE,KAAK,YAAY,SAAS,MAAM,QAAQ,KAAK,CAAC;AACzD;AAAA,MACF;AAKA,YAAM,UAAU,iBAAiB,SAAS,KAAK,SAAS,IAAI;AAC5D,UAAI,CAAC,QAAS,UAAS,SAAS,KAAK,IAAI;AACzC,eAAS;AAAA,QACP,KAAK;AAAA,QACL;AAAA,QACA,QAAQ,CAAC;AAAA,MACX,CAAC;AAAA,IACH,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAMD,YAAU,MAAM;AACd,QAAI,CAAC,aAAa,OAAQ;AAC1B,UAAM,QAAQ,WAAW,MAAM;AAC7B;AAAA,QAAS,CAAC,MACR,EAAE,QAAQ,cAAc,EAAE,SACtB,EAAE,KAAK,YAAY,SAAS,MAAM,QAAQ,MAAM,IAChD;AAAA,MACN;AAAA,IACF,GAAG,qBAAqB;AACxB,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,aAAa,QAAQ,UAAU,CAAC;AAEpC,MAAI,aAAa,OAAQ,QAAO,gCAAG,sBAAY,MAAK;AACpD,MAAI,CAAC,aAAa,QAAS,QAAO,gCAAG,sBAAY,MAAK;AAEtD,QAAM,YAAY,OAAO,EAAE,MAAM,eAAe,IAAI,CAAC;AAErD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS,aAAa,QAAQ;AAAA,MAC9B,MAAK;AAAA,MACL,cAAY,aAAa,SAAY,OAAO,QAAQ,IAAI;AAAA,MACvD,GAAG;AAAA,MACH,GAAG;AAAA,MAEH,uBAAa,QAAQ;AAAA;AAAA,EACxB;AAEJ;","names":[]}
@@ -0,0 +1,275 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+ var _chunkU2YXVQS5cjs = require('./chunk-U2YXVQS5.cjs');
4
+
5
+ // src/github-fallback.tsx
6
+ var _react = require('react');
7
+ var _resolve = require('@smbdy/icons/resolve');
8
+
9
+ // src/svg-to-react.ts
10
+
11
+
12
+ // src/sanitize-svg.ts
13
+ var _dompurify = require('dompurify'); var _dompurify2 = _interopRequireDefault(_dompurify);
14
+ var FORBID_TAGS = ["script", "foreignObject", "text", "image", "style", "use"];
15
+ var purifierCache = null;
16
+ function getPurifier() {
17
+ if (purifierCache) return purifierCache;
18
+ if (typeof window === "undefined") return null;
19
+ const p = _dompurify2.default.call(void 0,
20
+ window
21
+ );
22
+ p.addHook("uponSanitizeAttribute", (_node, ev) => {
23
+ if (ev.attrName === "href" || ev.attrName === "xlink:href") {
24
+ const v = typeof ev.attrValue === "string" ? ev.attrValue : "";
25
+ if (!v.startsWith("#")) ev.keepAttr = false;
26
+ }
27
+ if (ev.attrName === "style") {
28
+ const v = typeof ev.attrValue === "string" ? ev.attrValue : "";
29
+ if (/url\s*\(\s*['"]?\s*(?!#)/i.test(v)) ev.keepAttr = false;
30
+ }
31
+ });
32
+ purifierCache = p;
33
+ return p;
34
+ }
35
+ function sanitizeSvgRoot(svgText) {
36
+ if (typeof DOMParser === "undefined") return null;
37
+ const purifier = getPurifier();
38
+ if (!purifier) return null;
39
+ const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
40
+ const root = doc.documentElement;
41
+ if (root.nodeName !== "svg") return null;
42
+ purifier.sanitize(root, {
43
+ USE_PROFILES: { svg: true, svgFilters: true },
44
+ FORBID_TAGS,
45
+ IN_PLACE: true
46
+ });
47
+ return root;
48
+ }
49
+
50
+ // src/svg-attributes.ts
51
+ function toReactAttributeName(name) {
52
+ if (name === "class") return "className";
53
+ if (name === "tabindex") return "tabIndex";
54
+ if (name.startsWith("aria-") || name.startsWith("data-")) return name;
55
+ return name.replace(
56
+ /[:-]([a-z])/g,
57
+ (_, letter) => letter.toUpperCase()
58
+ );
59
+ }
60
+ function styleStringToObject(style) {
61
+ const result = {};
62
+ for (const rule of style.split(";")) {
63
+ const [rawProperty, ...rawValue] = rule.split(":");
64
+ const property = _optionalChain([rawProperty, 'optionalAccess', _2 => _2.trim, 'call', _3 => _3()]);
65
+ const value = rawValue.join(":").trim();
66
+ if (!property || !value) continue;
67
+ const reactProperty = property.startsWith("--") ? property : property.replace(
68
+ /-([a-z])/g,
69
+ (_, letter) => letter.toUpperCase()
70
+ );
71
+ result[reactProperty] = value;
72
+ }
73
+ return result;
74
+ }
75
+
76
+ // src/svg-to-react.ts
77
+ function svgNodeToReact(node, key) {
78
+ if (node.nodeType === Node.TEXT_NODE) return node.textContent;
79
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
80
+ const element = node;
81
+ const props = { key };
82
+ for (const attr of Array.from(element.attributes)) {
83
+ const attrName = toReactAttributeName(attr.name);
84
+ props[attrName] = attr.name === "style" ? styleStringToObject(attr.value) : attr.value;
85
+ }
86
+ const children = Array.from(element.childNodes).map((child, index) => svgNodeToReact(child, `${key}-${index}`)).filter((child) => child !== null);
87
+ return _react.createElement.call(void 0, element.tagName, props, ...children);
88
+ }
89
+ function svgTextToReact(svgText) {
90
+ const root = sanitizeSvgRoot(svgText);
91
+ if (!root) return null;
92
+ const viewBox = _nullishCoalesce(root.getAttribute("viewBox"), () => ( "0 0 32 32"));
93
+ const node = Array.from(root.childNodes).map((child, index) => svgNodeToReact(child, String(index))).filter((child) => child !== null);
94
+ return { node, viewBox };
95
+ }
96
+
97
+ // src/github-fallback.tsx
98
+ var _jsxruntime = require('react/jsx-runtime');
99
+ var svgCache = /* @__PURE__ */ new Map();
100
+ var MAX_CACHE = 200;
101
+ var NEGATIVE_CACHE_TTL_MS = 6e4;
102
+ var FETCH_TIMEOUT_MS = 8e3;
103
+ function cacheEntry(url) {
104
+ const entry = svgCache.get(url);
105
+ if (entry === void 0) return void 0;
106
+ if (entry.value !== null) return entry;
107
+ if (Date.now() - entry.ts > NEGATIVE_CACHE_TTL_MS) return void 0;
108
+ return entry;
109
+ }
110
+ function cacheGet(url) {
111
+ const entry = cacheEntry(url);
112
+ return entry === void 0 ? void 0 : entry.value;
113
+ }
114
+ function cacheSet(url, value) {
115
+ if (!svgCache.has(url) && svgCache.size >= MAX_CACHE) {
116
+ const oldest = svgCache.keys().next().value;
117
+ if (oldest !== void 0) svgCache.delete(oldest);
118
+ }
119
+ svgCache.set(url, { value, ts: Date.now() });
120
+ }
121
+ function cachedContentFor(url, text) {
122
+ const entry = svgCache.get(url);
123
+ if (entry && entry.content) return entry.content;
124
+ const content = svgTextToReact(text);
125
+ if (content && entry) entry.content = content;
126
+ return content;
127
+ }
128
+ function toAssetType(type) {
129
+ if (type === "token") return "tokens";
130
+ if (type === "chain") return "chains";
131
+ return "brands";
132
+ }
133
+ function inferType(id, explicitType) {
134
+ if (explicitType) return toAssetType(explicitType);
135
+ const hit = _resolve.resolve.call(void 0, id);
136
+ return hit ? toAssetType(hit.type) : null;
137
+ }
138
+ function buildUrl(baseUrl, branch, type, id, variant) {
139
+ return `${baseUrl}/${branch}/assets/${type}/${id}_${variant}.svg`;
140
+ }
141
+ var inFlight = /* @__PURE__ */ new Map();
142
+ function fetchSvg(url) {
143
+ const cached = cacheGet(url);
144
+ if (cached !== void 0) return Promise.resolve(cached);
145
+ svgCache.delete(url);
146
+ let pending = inFlight.get(url);
147
+ if (pending) return pending;
148
+ pending = (async () => {
149
+ const ac = new AbortController();
150
+ const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
151
+ try {
152
+ const res = await fetch(url, { signal: ac.signal });
153
+ if (!res.ok) {
154
+ cacheSet(url, null);
155
+ return null;
156
+ }
157
+ const text = await res.text();
158
+ cacheSet(url, text);
159
+ return text;
160
+ } catch (e) {
161
+ return null;
162
+ } finally {
163
+ clearTimeout(timer);
164
+ inFlight.delete(url);
165
+ }
166
+ })();
167
+ inFlight.set(url, pending);
168
+ return pending;
169
+ }
170
+ async function resolveSvg(baseUrl, branch, id, type, variant) {
171
+ if (type) {
172
+ const url = buildUrl(baseUrl, branch, type, id, variant);
173
+ const text = await fetchSvg(url);
174
+ return text === null ? null : { url, text };
175
+ }
176
+ const types = ["tokens", "chains", "brands"];
177
+ const urls = types.map((t) => buildUrl(baseUrl, branch, t, id, variant));
178
+ const results = await Promise.all(urls.map((url) => fetchSvg(url)));
179
+ const index = results.findIndex((result) => result !== null);
180
+ return index === -1 ? null : { url: urls[index], text: results[index] };
181
+ }
182
+ function getCachedContent(baseUrl, branch, type, id, variant) {
183
+ if (!type) return null;
184
+ const url = buildUrl(baseUrl, branch, type, id, variant);
185
+ const cached = cacheGet(url);
186
+ return typeof cached === "string" ? cachedContentFor(url, cached) : null;
187
+ }
188
+ function GithubFallback({
189
+ id,
190
+ rawValue,
191
+ iconType,
192
+ mono = false,
193
+ size = 32,
194
+ fallback,
195
+ ...props
196
+ }) {
197
+ const variant = mono ? "mono" : "full";
198
+ const { baseUrl, branch } = _chunkU2YXVQS5cjs.useIconConfig.call(void 0, );
199
+ const type = inferType(id, iconType);
200
+ const requestKey = `${baseUrl}|${branch}|${_nullishCoalesce(iconType, () => ( "auto"))}|${id}|${variant}`;
201
+ const cachedContent = _react.useMemo.call(void 0,
202
+ () => getCachedContent(baseUrl, branch, type, id, variant),
203
+ [baseUrl, branch, type, id, variant]
204
+ );
205
+ const [state, setState] = _react.useState.call(void 0, () => ({
206
+ key: requestKey,
207
+ content: cachedContent,
208
+ failed: false
209
+ }));
210
+ const currentState = state.key === requestKey ? state : {
211
+ key: requestKey,
212
+ content: cachedContent,
213
+ failed: false
214
+ };
215
+ _react.useEffect.call(void 0, () => {
216
+ if (currentState.content || currentState.failed) return;
217
+ let cancelled = false;
218
+ resolveSvg(baseUrl, branch, id, type, variant).then((resolved) => {
219
+ if (cancelled) return;
220
+ if (!resolved) {
221
+ setState({ key: requestKey, content: null, failed: true });
222
+ return;
223
+ }
224
+ const content = cachedContentFor(resolved.url, resolved.text);
225
+ if (!content) cacheSet(resolved.url, null);
226
+ setState({
227
+ key: requestKey,
228
+ content,
229
+ failed: !content
230
+ });
231
+ });
232
+ return () => {
233
+ cancelled = true;
234
+ };
235
+ }, [
236
+ baseUrl,
237
+ branch,
238
+ currentState.failed,
239
+ currentState.content,
240
+ id,
241
+ requestKey,
242
+ type,
243
+ variant
244
+ ]);
245
+ _react.useEffect.call(void 0, () => {
246
+ if (!currentState.failed) return;
247
+ const timer = setTimeout(() => {
248
+ setState(
249
+ (s) => s.key === requestKey && s.failed ? { key: requestKey, content: null, failed: false } : s
250
+ );
251
+ }, NEGATIVE_CACHE_TTL_MS);
252
+ return () => clearTimeout(timer);
253
+ }, [currentState.failed, requestKey]);
254
+ if (currentState.failed) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: _nullishCoalesce(fallback, () => ( null)) });
255
+ if (!currentState.content) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: _nullishCoalesce(fallback, () => ( null)) });
256
+ const monoProps = mono ? { fill: "currentColor" } : {};
257
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
258
+ "svg",
259
+ {
260
+ xmlns: "http://www.w3.org/2000/svg",
261
+ width: size,
262
+ height: size,
263
+ viewBox: currentState.content.viewBox,
264
+ role: "img",
265
+ "aria-label": rawValue !== void 0 ? String(rawValue) : id,
266
+ ...monoProps,
267
+ ...props,
268
+ children: currentState.content.node
269
+ }
270
+ );
271
+ }
272
+
273
+
274
+ exports.GithubFallback = GithubFallback;
275
+ //# sourceMappingURL=github-fallback-YJY3LJG7.cjs.map