@smbdy/icons-react 1.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/LICENSE +21 -0
- package/README.md +118 -0
- package/dist/aave-4NKY7G5P.js +9 -0
- package/dist/aave-4NKY7G5P.js.map +1 -0
- package/dist/aave-EZ44VEFF.cjs +7 -0
- package/dist/aave-EZ44VEFF.cjs.map +1 -0
- package/dist/bnb-NS3U5Y5W.cjs +7 -0
- package/dist/bnb-NS3U5Y5W.cjs.map +1 -0
- package/dist/bnb-VEXN3I2D.js +9 -0
- package/dist/bnb-VEXN3I2D.js.map +1 -0
- package/dist/brands.cjs +7 -0
- package/dist/brands.cjs.map +1 -0
- package/dist/brands.d.cts +10 -0
- package/dist/brands.d.ts +10 -0
- package/dist/brands.js +9 -0
- package/dist/brands.js.map +1 -0
- package/dist/chains.cjs +7 -0
- package/dist/chains.cjs.map +1 -0
- package/dist/chains.d.cts +10 -0
- package/dist/chains.d.ts +10 -0
- package/dist/chains.js +9 -0
- package/dist/chains.js.map +1 -0
- package/dist/chunk-35GWSGWA.js +139 -0
- package/dist/chunk-35GWSGWA.js.map +1 -0
- package/dist/chunk-4HVBGJ3B.cjs +136 -0
- package/dist/chunk-4HVBGJ3B.cjs.map +1 -0
- package/dist/chunk-5GKWV4K2.js +24 -0
- package/dist/chunk-5GKWV4K2.js.map +1 -0
- package/dist/chunk-7P3MIHDC.js +41 -0
- package/dist/chunk-7P3MIHDC.js.map +1 -0
- package/dist/chunk-C2QAKFGG.js +36 -0
- package/dist/chunk-C2QAKFGG.js.map +1 -0
- package/dist/chunk-CZ5BDQQZ.cjs +31 -0
- package/dist/chunk-CZ5BDQQZ.cjs.map +1 -0
- package/dist/chunk-FKEVFNX6.cjs +26 -0
- package/dist/chunk-FKEVFNX6.cjs.map +1 -0
- package/dist/chunk-GQ4M4EFF.cjs +33 -0
- package/dist/chunk-GQ4M4EFF.cjs.map +1 -0
- package/dist/chunk-ICEGTX2E.js +46 -0
- package/dist/chunk-ICEGTX2E.js.map +1 -0
- package/dist/chunk-IEWJOWAP.cjs +120 -0
- package/dist/chunk-IEWJOWAP.cjs.map +1 -0
- package/dist/chunk-KHCWITFW.cjs +36 -0
- package/dist/chunk-KHCWITFW.cjs.map +1 -0
- package/dist/chunk-KJL5NVUU.cjs +43 -0
- package/dist/chunk-KJL5NVUU.cjs.map +1 -0
- package/dist/chunk-LMGL65KI.cjs +191 -0
- package/dist/chunk-LMGL65KI.cjs.map +1 -0
- package/dist/chunk-OPA5IJQV.js +123 -0
- package/dist/chunk-OPA5IJQV.js.map +1 -0
- package/dist/chunk-OVJUO4KZ.js +39 -0
- package/dist/chunk-OVJUO4KZ.js.map +1 -0
- package/dist/chunk-PDGRSONP.cjs +38 -0
- package/dist/chunk-PDGRSONP.cjs.map +1 -0
- package/dist/chunk-QAWTPM2S.js +29 -0
- package/dist/chunk-QAWTPM2S.js.map +1 -0
- package/dist/chunk-R2LI45CO.js +34 -0
- package/dist/chunk-R2LI45CO.js.map +1 -0
- package/dist/chunk-UX3FC265.cjs +21 -0
- package/dist/chunk-UX3FC265.cjs.map +1 -0
- package/dist/chunk-WARMK44Y.cjs +26 -0
- package/dist/chunk-WARMK44Y.cjs.map +1 -0
- package/dist/chunk-WH52PT4Y.js +29 -0
- package/dist/chunk-WH52PT4Y.js.map +1 -0
- package/dist/chunk-XWYUXPMU.js +193 -0
- package/dist/chunk-XWYUXPMU.js.map +1 -0
- package/dist/compat.cjs +54 -0
- package/dist/compat.cjs.map +1 -0
- package/dist/compat.d.cts +20 -0
- package/dist/compat.d.ts +20 -0
- package/dist/compat.js +56 -0
- package/dist/compat.js.map +1 -0
- package/dist/frames.cjs +7 -0
- package/dist/frames.cjs.map +1 -0
- package/dist/frames.d.cts +14 -0
- package/dist/frames.d.ts +14 -0
- package/dist/frames.js +9 -0
- package/dist/frames.js.map +1 -0
- package/dist/github-fallback-W3AHQ4U6.js +248 -0
- package/dist/github-fallback-W3AHQ4U6.js.map +1 -0
- package/dist/github-fallback-WEXDJI4H.cjs +246 -0
- package/dist/github-fallback-WEXDJI4H.cjs.map +1 -0
- package/dist/index.cjs +14 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/link-HVLWBKZ5.cjs +7 -0
- package/dist/link-HVLWBKZ5.cjs.map +1 -0
- package/dist/link-M5GUQP3F.js +9 -0
- package/dist/link-M5GUQP3F.js.map +1 -0
- package/dist/pteusde-T5QFFBX5.js +9 -0
- package/dist/pteusde-T5QFFBX5.js.map +1 -0
- package/dist/pteusde-ZTEAA2UN.cjs +7 -0
- package/dist/pteusde-ZTEAA2UN.cjs.map +1 -0
- package/dist/tokens.cjs +35 -0
- package/dist/tokens.cjs.map +1 -0
- package/dist/tokens.d.cts +64 -0
- package/dist/tokens.d.ts +64 -0
- package/dist/tokens.js +37 -0
- package/dist/tokens.js.map +1 -0
- package/dist/uni-LS35CB5G.cjs +7 -0
- package/dist/uni-LS35CB5G.cjs.map +1 -0
- package/dist/uni-UB66QGDS.js +9 -0
- package/dist/uni-UB66QGDS.js.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useIconConfig
|
|
5
|
+
} from "./chunk-ICEGTX2E.js";
|
|
6
|
+
|
|
7
|
+
// src/github-fallback.tsx
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { resolve } from "@smbdy/icons/resolve";
|
|
10
|
+
|
|
11
|
+
// src/svg-to-react.ts
|
|
12
|
+
import { createElement } from "react";
|
|
13
|
+
|
|
14
|
+
// src/sanitize-svg.ts
|
|
15
|
+
import createDOMPurify from "dompurify";
|
|
16
|
+
var FORBID_TAGS = ["script", "foreignObject", "text", "image", "style"];
|
|
17
|
+
var purifierCache = null;
|
|
18
|
+
function getPurifier() {
|
|
19
|
+
if (purifierCache) return purifierCache;
|
|
20
|
+
if (typeof window === "undefined") return null;
|
|
21
|
+
const p = createDOMPurify(
|
|
22
|
+
window
|
|
23
|
+
);
|
|
24
|
+
p.addHook("uponSanitizeAttribute", (_node, ev) => {
|
|
25
|
+
if (ev.attrName === "href" || ev.attrName === "xlink:href") {
|
|
26
|
+
const v = typeof ev.attrValue === "string" ? ev.attrValue : "";
|
|
27
|
+
if (!v.startsWith("#")) ev.keepAttr = false;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
purifierCache = p;
|
|
31
|
+
return p;
|
|
32
|
+
}
|
|
33
|
+
function sanitizeSvgRoot(svgText) {
|
|
34
|
+
if (typeof DOMParser === "undefined") return null;
|
|
35
|
+
const purifier = getPurifier();
|
|
36
|
+
if (!purifier) return null;
|
|
37
|
+
const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
|
|
38
|
+
const root = doc.documentElement;
|
|
39
|
+
if (root.nodeName !== "svg") return null;
|
|
40
|
+
purifier.sanitize(root, {
|
|
41
|
+
USE_PROFILES: { svg: true, svgFilters: true },
|
|
42
|
+
FORBID_TAGS,
|
|
43
|
+
IN_PLACE: true
|
|
44
|
+
});
|
|
45
|
+
return root;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/svg-attributes.ts
|
|
49
|
+
function toReactAttributeName(name) {
|
|
50
|
+
if (name === "class") return "className";
|
|
51
|
+
if (name.startsWith("aria-") || name.startsWith("data-")) return name;
|
|
52
|
+
return name.replace(
|
|
53
|
+
/[:-]([a-z])/g,
|
|
54
|
+
(_, letter) => letter.toUpperCase()
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function styleStringToObject(style) {
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const rule of style.split(";")) {
|
|
60
|
+
const [rawProperty, ...rawValue] = rule.split(":");
|
|
61
|
+
const property = rawProperty?.trim();
|
|
62
|
+
const value = rawValue.join(":").trim();
|
|
63
|
+
if (!property || !value) continue;
|
|
64
|
+
const reactProperty = property.replace(
|
|
65
|
+
/-([a-z])/g,
|
|
66
|
+
(_, letter) => letter.toUpperCase()
|
|
67
|
+
);
|
|
68
|
+
result[reactProperty] = value;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/svg-to-react.ts
|
|
74
|
+
function svgNodeToReact(node, key) {
|
|
75
|
+
if (node.nodeType === Node.TEXT_NODE) return node.textContent;
|
|
76
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return null;
|
|
77
|
+
const element = node;
|
|
78
|
+
const props = { key };
|
|
79
|
+
for (const attr of Array.from(element.attributes)) {
|
|
80
|
+
const attrName = toReactAttributeName(attr.name);
|
|
81
|
+
props[attrName] = attr.name === "style" ? styleStringToObject(attr.value) : attr.value;
|
|
82
|
+
}
|
|
83
|
+
const children = Array.from(element.childNodes).map((child, index) => svgNodeToReact(child, `${key}-${index}`)).filter((child) => child !== null);
|
|
84
|
+
return createElement(element.tagName, props, ...children);
|
|
85
|
+
}
|
|
86
|
+
function svgTextToReact(svgText) {
|
|
87
|
+
const root = sanitizeSvgRoot(svgText);
|
|
88
|
+
if (!root) return null;
|
|
89
|
+
const viewBox = root.getAttribute("viewBox") ?? "0 0 32 32";
|
|
90
|
+
const node = Array.from(root.childNodes).map((child, index) => svgNodeToReact(child, String(index))).filter((child) => child !== null);
|
|
91
|
+
return { node, viewBox };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/github-fallback.tsx
|
|
95
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
96
|
+
var svgCache = /* @__PURE__ */ new Map();
|
|
97
|
+
var cacheOrder = [];
|
|
98
|
+
var MAX_CACHE = 200;
|
|
99
|
+
var NEGATIVE_CACHE_TTL_MS = 6e4;
|
|
100
|
+
var FETCH_TIMEOUT_MS = 8e3;
|
|
101
|
+
function cacheGet(url) {
|
|
102
|
+
const entry = svgCache.get(url);
|
|
103
|
+
if (entry === void 0) return void 0;
|
|
104
|
+
if (entry.value !== null) return entry.value;
|
|
105
|
+
if (Date.now() - entry.ts > NEGATIVE_CACHE_TTL_MS) {
|
|
106
|
+
svgCache.delete(url);
|
|
107
|
+
const idx = cacheOrder.indexOf(url);
|
|
108
|
+
if (idx !== -1) cacheOrder.splice(idx, 1);
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function cacheSet(url, value) {
|
|
114
|
+
if (!svgCache.has(url)) {
|
|
115
|
+
cacheOrder.push(url);
|
|
116
|
+
if (cacheOrder.length > MAX_CACHE) {
|
|
117
|
+
const evict = cacheOrder.shift();
|
|
118
|
+
svgCache.delete(evict);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
svgCache.set(url, { value, ts: Date.now() });
|
|
122
|
+
}
|
|
123
|
+
function toAssetType(type) {
|
|
124
|
+
if (type === "token") return "tokens";
|
|
125
|
+
if (type === "chain") return "chains";
|
|
126
|
+
return "brands";
|
|
127
|
+
}
|
|
128
|
+
function inferType(id, explicitType) {
|
|
129
|
+
if (explicitType) return toAssetType(explicitType);
|
|
130
|
+
const hit = resolve(id);
|
|
131
|
+
return hit ? toAssetType(hit.type) : null;
|
|
132
|
+
}
|
|
133
|
+
function buildUrl(baseUrl, branch, type, id, variant) {
|
|
134
|
+
return `${baseUrl}/${branch}/assets/${type}/${id}_${variant}.svg`;
|
|
135
|
+
}
|
|
136
|
+
async function fetchSvg(url, signal) {
|
|
137
|
+
const cached = cacheGet(url);
|
|
138
|
+
if (cached !== void 0) return cached;
|
|
139
|
+
try {
|
|
140
|
+
const res = await fetch(url, { signal });
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
cacheSet(url, null);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const text = await res.text();
|
|
146
|
+
cacheSet(url, text);
|
|
147
|
+
return text;
|
|
148
|
+
} catch {
|
|
149
|
+
if (!signal.aborted) cacheSet(url, null);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function resolveSvg(baseUrl, branch, id, explicitType, variant, signal) {
|
|
154
|
+
const type = inferType(id, explicitType);
|
|
155
|
+
if (type) {
|
|
156
|
+
return fetchSvg(buildUrl(baseUrl, branch, type, id, variant), signal);
|
|
157
|
+
}
|
|
158
|
+
const types = ["tokens", "chains", "brands"];
|
|
159
|
+
const results = await Promise.all(
|
|
160
|
+
types.map(
|
|
161
|
+
(t) => fetchSvg(buildUrl(baseUrl, branch, t, id, variant), signal)
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
return results.find((result) => result !== null) ?? null;
|
|
165
|
+
}
|
|
166
|
+
function getCachedContent(baseUrl, branch, explicitType, id, variant) {
|
|
167
|
+
const type = inferType(id, explicitType);
|
|
168
|
+
if (!type) return null;
|
|
169
|
+
const cached = cacheGet(buildUrl(baseUrl, branch, type, id, variant));
|
|
170
|
+
return typeof cached === "string" ? svgTextToReact(cached) : null;
|
|
171
|
+
}
|
|
172
|
+
function GithubFallback({
|
|
173
|
+
id,
|
|
174
|
+
iconType,
|
|
175
|
+
variant,
|
|
176
|
+
size = 32,
|
|
177
|
+
fallback,
|
|
178
|
+
...props
|
|
179
|
+
}) {
|
|
180
|
+
const { baseUrl, branch } = useIconConfig();
|
|
181
|
+
const requestKey = `${baseUrl}|${branch}|${iconType ?? "auto"}|${id}|${variant}`;
|
|
182
|
+
const cachedContent = useMemo(
|
|
183
|
+
() => getCachedContent(baseUrl, branch, iconType, id, variant),
|
|
184
|
+
[baseUrl, branch, iconType, id, variant]
|
|
185
|
+
);
|
|
186
|
+
const [state, setState] = useState(() => ({
|
|
187
|
+
key: requestKey,
|
|
188
|
+
content: cachedContent,
|
|
189
|
+
failed: false
|
|
190
|
+
}));
|
|
191
|
+
const currentState = state.key === requestKey ? state : {
|
|
192
|
+
key: requestKey,
|
|
193
|
+
content: cachedContent,
|
|
194
|
+
failed: false
|
|
195
|
+
};
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (currentState.content || currentState.failed) return;
|
|
198
|
+
const ac = new AbortController();
|
|
199
|
+
const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
|
|
200
|
+
resolveSvg(baseUrl, branch, id, iconType, variant, ac.signal).then(
|
|
201
|
+
(svgText) => {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
if (ac.signal.aborted) return;
|
|
204
|
+
setState(() => {
|
|
205
|
+
if (!svgText) {
|
|
206
|
+
return { key: requestKey, content: null, failed: true };
|
|
207
|
+
}
|
|
208
|
+
const content = svgTextToReact(svgText);
|
|
209
|
+
return content ? { key: requestKey, content, failed: false } : { key: requestKey, content: null, failed: true };
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
return () => {
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
ac.abort();
|
|
216
|
+
};
|
|
217
|
+
}, [
|
|
218
|
+
baseUrl,
|
|
219
|
+
branch,
|
|
220
|
+
currentState.failed,
|
|
221
|
+
currentState.content,
|
|
222
|
+
id,
|
|
223
|
+
requestKey,
|
|
224
|
+
iconType,
|
|
225
|
+
variant
|
|
226
|
+
]);
|
|
227
|
+
if (currentState.failed) return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? null });
|
|
228
|
+
if (!currentState.content) return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? null });
|
|
229
|
+
const monoProps = variant === "mono" ? { fill: "currentColor" } : {};
|
|
230
|
+
return /* @__PURE__ */ jsx(
|
|
231
|
+
"svg",
|
|
232
|
+
{
|
|
233
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
234
|
+
width: size,
|
|
235
|
+
height: size,
|
|
236
|
+
viewBox: currentState.content.viewBox,
|
|
237
|
+
role: "img",
|
|
238
|
+
"aria-label": id,
|
|
239
|
+
...monoProps,
|
|
240
|
+
...props,
|
|
241
|
+
children: currentState.content.node
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
export {
|
|
246
|
+
GithubFallback
|
|
247
|
+
};
|
|
248
|
+
//# sourceMappingURL=github-fallback-W3AHQ4U6.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).\ninterface CacheEntry {\n value: string | null\n ts: number\n}\n\nconst svgCache = new Map<string, CacheEntry>()\nconst cacheOrder: string[] = []\nconst MAX_CACHE = 200\nconst NEGATIVE_CACHE_TTL_MS = 60_000\nconst FETCH_TIMEOUT_MS = 8_000\n\nfunction cacheGet(url: string): string | null | undefined {\n const entry = svgCache.get(url)\n if (entry === undefined) return undefined\n // Successful entries never expire (within LRU window)\n if (entry.value !== null) return entry.value\n // Negative entries expire after TTL\n if (Date.now() - entry.ts > NEGATIVE_CACHE_TTL_MS) {\n svgCache.delete(url)\n const idx = cacheOrder.indexOf(url)\n if (idx !== -1) cacheOrder.splice(idx, 1)\n return undefined\n }\n return null\n}\n\nfunction cacheSet(url: string, value: string | null) {\n if (!svgCache.has(url)) {\n cacheOrder.push(url)\n if (cacheOrder.length > MAX_CACHE) {\n const evict = cacheOrder.shift()!\n svgCache.delete(evict)\n }\n }\n svgCache.set(url, { value, ts: Date.now() })\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\nasync function fetchSvg(\n url: string,\n signal: AbortSignal,\n): Promise<string | null> {\n const cached = cacheGet(url)\n if (cached !== undefined) return cached\n\n try {\n const res = await fetch(url, { signal })\n if (!res.ok) {\n cacheSet(url, null)\n return null\n }\n const text = await res.text()\n cacheSet(url, text)\n return text\n } catch {\n if (!signal.aborted) cacheSet(url, null)\n return null\n }\n}\n\nasync function resolveSvg(\n baseUrl: string,\n branch: string,\n id: string,\n explicitType: IconType | undefined,\n variant: string,\n signal: AbortSignal,\n): Promise<string | null> {\n const type = inferType(id, explicitType)\n\n if (type) {\n return fetchSvg(buildUrl(baseUrl, branch, type, id, variant), signal)\n }\n\n // Unknown type — probe all three and keep token-first result ordering.\n const types: AssetType[] = ['tokens', 'chains', 'brands']\n const results = await Promise.all(\n types.map((t) =>\n fetchSvg(buildUrl(baseUrl, branch, t, id, variant), signal),\n ),\n )\n return results.find((result) => result !== null) ?? null\n}\n\nexport interface GithubFallbackProps extends SVGProps<SVGSVGElement> {\n id: string\n iconType?: IconType\n variant: 'full' | 'mono'\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 explicitType: IconType | undefined,\n id: string,\n variant: string,\n): SvgReactContent | null {\n const type = inferType(id, explicitType)\n if (!type) return null\n const cached = cacheGet(buildUrl(baseUrl, branch, type, id, variant))\n return typeof cached === 'string' ? svgTextToReact(cached) : null\n}\n\nexport function GithubFallback({\n id,\n iconType,\n variant,\n size = 32,\n fallback,\n ...props\n}: GithubFallbackProps) {\n const { baseUrl, branch } = useIconConfig()\n const requestKey = `${baseUrl}|${branch}|${iconType ?? 'auto'}|${id}|${variant}`\n const cachedContent = useMemo(\n () => getCachedContent(baseUrl, branch, iconType, id, variant),\n [baseUrl, branch, iconType, 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 const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS)\n\n resolveSvg(baseUrl, branch, id, iconType, variant, ac.signal).then(\n (svgText) => {\n clearTimeout(timer)\n if (ac.signal.aborted) return\n setState(() => {\n if (!svgText) {\n return { key: requestKey, content: null, failed: true }\n }\n const content = svgTextToReact(svgText)\n return content\n ? { key: requestKey, content, failed: false }\n : { key: requestKey, content: null, failed: true }\n })\n },\n )\n\n return () => {\n clearTimeout(timer)\n ac.abort()\n }\n }, [\n baseUrl,\n branch,\n currentState.failed,\n currentState.content,\n id,\n requestKey,\n iconType,\n variant,\n ])\n\n if (currentState.failed) return <>{fallback ?? null}</>\n if (!currentState.content) return <>{fallback ?? null}</>\n\n const monoProps = variant === '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={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']\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 })\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.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 const reactProperty = property.replace(/-([a-z])/g, (_, letter: string) =>\n letter.toUpperCase(),\n )\n result[reactProperty] = value\n }\n return result\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,246 @@
|
|
|
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 _chunkKJL5NVUUcjs = require('./chunk-KJL5NVUU.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"];
|
|
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
|
+
});
|
|
28
|
+
purifierCache = p;
|
|
29
|
+
return p;
|
|
30
|
+
}
|
|
31
|
+
function sanitizeSvgRoot(svgText) {
|
|
32
|
+
if (typeof DOMParser === "undefined") return null;
|
|
33
|
+
const purifier = getPurifier();
|
|
34
|
+
if (!purifier) return null;
|
|
35
|
+
const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
|
|
36
|
+
const root = doc.documentElement;
|
|
37
|
+
if (root.nodeName !== "svg") return null;
|
|
38
|
+
purifier.sanitize(root, {
|
|
39
|
+
USE_PROFILES: { svg: true, svgFilters: true },
|
|
40
|
+
FORBID_TAGS,
|
|
41
|
+
IN_PLACE: true
|
|
42
|
+
});
|
|
43
|
+
return root;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/svg-attributes.ts
|
|
47
|
+
function toReactAttributeName(name) {
|
|
48
|
+
if (name === "class") return "className";
|
|
49
|
+
if (name.startsWith("aria-") || name.startsWith("data-")) return name;
|
|
50
|
+
return name.replace(
|
|
51
|
+
/[:-]([a-z])/g,
|
|
52
|
+
(_, letter) => letter.toUpperCase()
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
function styleStringToObject(style) {
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const rule of style.split(";")) {
|
|
58
|
+
const [rawProperty, ...rawValue] = rule.split(":");
|
|
59
|
+
const property = _optionalChain([rawProperty, 'optionalAccess', _2 => _2.trim, 'call', _3 => _3()]);
|
|
60
|
+
const value = rawValue.join(":").trim();
|
|
61
|
+
if (!property || !value) continue;
|
|
62
|
+
const reactProperty = property.replace(
|
|
63
|
+
/-([a-z])/g,
|
|
64
|
+
(_, letter) => letter.toUpperCase()
|
|
65
|
+
);
|
|
66
|
+
result[reactProperty] = value;
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/svg-to-react.ts
|
|
72
|
+
function svgNodeToReact(node, key) {
|
|
73
|
+
if (node.nodeType === Node.TEXT_NODE) return node.textContent;
|
|
74
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return null;
|
|
75
|
+
const element = node;
|
|
76
|
+
const props = { key };
|
|
77
|
+
for (const attr of Array.from(element.attributes)) {
|
|
78
|
+
const attrName = toReactAttributeName(attr.name);
|
|
79
|
+
props[attrName] = attr.name === "style" ? styleStringToObject(attr.value) : attr.value;
|
|
80
|
+
}
|
|
81
|
+
const children = Array.from(element.childNodes).map((child, index) => svgNodeToReact(child, `${key}-${index}`)).filter((child) => child !== null);
|
|
82
|
+
return _react.createElement.call(void 0, element.tagName, props, ...children);
|
|
83
|
+
}
|
|
84
|
+
function svgTextToReact(svgText) {
|
|
85
|
+
const root = sanitizeSvgRoot(svgText);
|
|
86
|
+
if (!root) return null;
|
|
87
|
+
const viewBox = _nullishCoalesce(root.getAttribute("viewBox"), () => ( "0 0 32 32"));
|
|
88
|
+
const node = Array.from(root.childNodes).map((child, index) => svgNodeToReact(child, String(index))).filter((child) => child !== null);
|
|
89
|
+
return { node, viewBox };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/github-fallback.tsx
|
|
93
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
94
|
+
var svgCache = /* @__PURE__ */ new Map();
|
|
95
|
+
var cacheOrder = [];
|
|
96
|
+
var MAX_CACHE = 200;
|
|
97
|
+
var NEGATIVE_CACHE_TTL_MS = 6e4;
|
|
98
|
+
var FETCH_TIMEOUT_MS = 8e3;
|
|
99
|
+
function cacheGet(url) {
|
|
100
|
+
const entry = svgCache.get(url);
|
|
101
|
+
if (entry === void 0) return void 0;
|
|
102
|
+
if (entry.value !== null) return entry.value;
|
|
103
|
+
if (Date.now() - entry.ts > NEGATIVE_CACHE_TTL_MS) {
|
|
104
|
+
svgCache.delete(url);
|
|
105
|
+
const idx = cacheOrder.indexOf(url);
|
|
106
|
+
if (idx !== -1) cacheOrder.splice(idx, 1);
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function cacheSet(url, value) {
|
|
112
|
+
if (!svgCache.has(url)) {
|
|
113
|
+
cacheOrder.push(url);
|
|
114
|
+
if (cacheOrder.length > MAX_CACHE) {
|
|
115
|
+
const evict = cacheOrder.shift();
|
|
116
|
+
svgCache.delete(evict);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
svgCache.set(url, { value, ts: Date.now() });
|
|
120
|
+
}
|
|
121
|
+
function toAssetType(type) {
|
|
122
|
+
if (type === "token") return "tokens";
|
|
123
|
+
if (type === "chain") return "chains";
|
|
124
|
+
return "brands";
|
|
125
|
+
}
|
|
126
|
+
function inferType(id, explicitType) {
|
|
127
|
+
if (explicitType) return toAssetType(explicitType);
|
|
128
|
+
const hit = _resolve.resolve.call(void 0, id);
|
|
129
|
+
return hit ? toAssetType(hit.type) : null;
|
|
130
|
+
}
|
|
131
|
+
function buildUrl(baseUrl, branch, type, id, variant) {
|
|
132
|
+
return `${baseUrl}/${branch}/assets/${type}/${id}_${variant}.svg`;
|
|
133
|
+
}
|
|
134
|
+
async function fetchSvg(url, signal) {
|
|
135
|
+
const cached = cacheGet(url);
|
|
136
|
+
if (cached !== void 0) return cached;
|
|
137
|
+
try {
|
|
138
|
+
const res = await fetch(url, { signal });
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
cacheSet(url, null);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const text = await res.text();
|
|
144
|
+
cacheSet(url, text);
|
|
145
|
+
return text;
|
|
146
|
+
} catch (e) {
|
|
147
|
+
if (!signal.aborted) cacheSet(url, null);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function resolveSvg(baseUrl, branch, id, explicitType, variant, signal) {
|
|
152
|
+
const type = inferType(id, explicitType);
|
|
153
|
+
if (type) {
|
|
154
|
+
return fetchSvg(buildUrl(baseUrl, branch, type, id, variant), signal);
|
|
155
|
+
}
|
|
156
|
+
const types = ["tokens", "chains", "brands"];
|
|
157
|
+
const results = await Promise.all(
|
|
158
|
+
types.map(
|
|
159
|
+
(t) => fetchSvg(buildUrl(baseUrl, branch, t, id, variant), signal)
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
return _nullishCoalesce(results.find((result) => result !== null), () => ( null));
|
|
163
|
+
}
|
|
164
|
+
function getCachedContent(baseUrl, branch, explicitType, id, variant) {
|
|
165
|
+
const type = inferType(id, explicitType);
|
|
166
|
+
if (!type) return null;
|
|
167
|
+
const cached = cacheGet(buildUrl(baseUrl, branch, type, id, variant));
|
|
168
|
+
return typeof cached === "string" ? svgTextToReact(cached) : null;
|
|
169
|
+
}
|
|
170
|
+
function GithubFallback({
|
|
171
|
+
id,
|
|
172
|
+
iconType,
|
|
173
|
+
variant,
|
|
174
|
+
size = 32,
|
|
175
|
+
fallback,
|
|
176
|
+
...props
|
|
177
|
+
}) {
|
|
178
|
+
const { baseUrl, branch } = _chunkKJL5NVUUcjs.useIconConfig.call(void 0, );
|
|
179
|
+
const requestKey = `${baseUrl}|${branch}|${_nullishCoalesce(iconType, () => ( "auto"))}|${id}|${variant}`;
|
|
180
|
+
const cachedContent = _react.useMemo.call(void 0,
|
|
181
|
+
() => getCachedContent(baseUrl, branch, iconType, id, variant),
|
|
182
|
+
[baseUrl, branch, iconType, id, variant]
|
|
183
|
+
);
|
|
184
|
+
const [state, setState] = _react.useState.call(void 0, () => ({
|
|
185
|
+
key: requestKey,
|
|
186
|
+
content: cachedContent,
|
|
187
|
+
failed: false
|
|
188
|
+
}));
|
|
189
|
+
const currentState = state.key === requestKey ? state : {
|
|
190
|
+
key: requestKey,
|
|
191
|
+
content: cachedContent,
|
|
192
|
+
failed: false
|
|
193
|
+
};
|
|
194
|
+
_react.useEffect.call(void 0, () => {
|
|
195
|
+
if (currentState.content || currentState.failed) return;
|
|
196
|
+
const ac = new AbortController();
|
|
197
|
+
const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
|
|
198
|
+
resolveSvg(baseUrl, branch, id, iconType, variant, ac.signal).then(
|
|
199
|
+
(svgText) => {
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
if (ac.signal.aborted) return;
|
|
202
|
+
setState(() => {
|
|
203
|
+
if (!svgText) {
|
|
204
|
+
return { key: requestKey, content: null, failed: true };
|
|
205
|
+
}
|
|
206
|
+
const content = svgTextToReact(svgText);
|
|
207
|
+
return content ? { key: requestKey, content, failed: false } : { key: requestKey, content: null, failed: true };
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
return () => {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
ac.abort();
|
|
214
|
+
};
|
|
215
|
+
}, [
|
|
216
|
+
baseUrl,
|
|
217
|
+
branch,
|
|
218
|
+
currentState.failed,
|
|
219
|
+
currentState.content,
|
|
220
|
+
id,
|
|
221
|
+
requestKey,
|
|
222
|
+
iconType,
|
|
223
|
+
variant
|
|
224
|
+
]);
|
|
225
|
+
if (currentState.failed) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: _nullishCoalesce(fallback, () => ( null)) });
|
|
226
|
+
if (!currentState.content) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: _nullishCoalesce(fallback, () => ( null)) });
|
|
227
|
+
const monoProps = variant === "mono" ? { fill: "currentColor" } : {};
|
|
228
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
229
|
+
"svg",
|
|
230
|
+
{
|
|
231
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
232
|
+
width: size,
|
|
233
|
+
height: size,
|
|
234
|
+
viewBox: currentState.content.viewBox,
|
|
235
|
+
role: "img",
|
|
236
|
+
"aria-label": id,
|
|
237
|
+
...monoProps,
|
|
238
|
+
...props,
|
|
239
|
+
children: currentState.content.node
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
exports.GithubFallback = GithubFallback;
|
|
246
|
+
//# sourceMappingURL=github-fallback-WEXDJI4H.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/icons/icons/packages/react/dist/github-fallback-WEXDJI4H.cjs","../src/github-fallback.tsx","../src/svg-to-react.ts","../src/sanitize-svg.ts","../src/svg-attributes.ts"],"names":[],"mappings":"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","file":"/home/runner/work/icons/icons/packages/react/dist/github-fallback-WEXDJI4H.cjs","sourcesContent":[null,"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).\ninterface CacheEntry {\n value: string | null\n ts: number\n}\n\nconst svgCache = new Map<string, CacheEntry>()\nconst cacheOrder: string[] = []\nconst MAX_CACHE = 200\nconst NEGATIVE_CACHE_TTL_MS = 60_000\nconst FETCH_TIMEOUT_MS = 8_000\n\nfunction cacheGet(url: string): string | null | undefined {\n const entry = svgCache.get(url)\n if (entry === undefined) return undefined\n // Successful entries never expire (within LRU window)\n if (entry.value !== null) return entry.value\n // Negative entries expire after TTL\n if (Date.now() - entry.ts > NEGATIVE_CACHE_TTL_MS) {\n svgCache.delete(url)\n const idx = cacheOrder.indexOf(url)\n if (idx !== -1) cacheOrder.splice(idx, 1)\n return undefined\n }\n return null\n}\n\nfunction cacheSet(url: string, value: string | null) {\n if (!svgCache.has(url)) {\n cacheOrder.push(url)\n if (cacheOrder.length > MAX_CACHE) {\n const evict = cacheOrder.shift()!\n svgCache.delete(evict)\n }\n }\n svgCache.set(url, { value, ts: Date.now() })\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\nasync function fetchSvg(\n url: string,\n signal: AbortSignal,\n): Promise<string | null> {\n const cached = cacheGet(url)\n if (cached !== undefined) return cached\n\n try {\n const res = await fetch(url, { signal })\n if (!res.ok) {\n cacheSet(url, null)\n return null\n }\n const text = await res.text()\n cacheSet(url, text)\n return text\n } catch {\n if (!signal.aborted) cacheSet(url, null)\n return null\n }\n}\n\nasync function resolveSvg(\n baseUrl: string,\n branch: string,\n id: string,\n explicitType: IconType | undefined,\n variant: string,\n signal: AbortSignal,\n): Promise<string | null> {\n const type = inferType(id, explicitType)\n\n if (type) {\n return fetchSvg(buildUrl(baseUrl, branch, type, id, variant), signal)\n }\n\n // Unknown type — probe all three and keep token-first result ordering.\n const types: AssetType[] = ['tokens', 'chains', 'brands']\n const results = await Promise.all(\n types.map((t) =>\n fetchSvg(buildUrl(baseUrl, branch, t, id, variant), signal),\n ),\n )\n return results.find((result) => result !== null) ?? null\n}\n\nexport interface GithubFallbackProps extends SVGProps<SVGSVGElement> {\n id: string\n iconType?: IconType\n variant: 'full' | 'mono'\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 explicitType: IconType | undefined,\n id: string,\n variant: string,\n): SvgReactContent | null {\n const type = inferType(id, explicitType)\n if (!type) return null\n const cached = cacheGet(buildUrl(baseUrl, branch, type, id, variant))\n return typeof cached === 'string' ? svgTextToReact(cached) : null\n}\n\nexport function GithubFallback({\n id,\n iconType,\n variant,\n size = 32,\n fallback,\n ...props\n}: GithubFallbackProps) {\n const { baseUrl, branch } = useIconConfig()\n const requestKey = `${baseUrl}|${branch}|${iconType ?? 'auto'}|${id}|${variant}`\n const cachedContent = useMemo(\n () => getCachedContent(baseUrl, branch, iconType, id, variant),\n [baseUrl, branch, iconType, 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 const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS)\n\n resolveSvg(baseUrl, branch, id, iconType, variant, ac.signal).then(\n (svgText) => {\n clearTimeout(timer)\n if (ac.signal.aborted) return\n setState(() => {\n if (!svgText) {\n return { key: requestKey, content: null, failed: true }\n }\n const content = svgTextToReact(svgText)\n return content\n ? { key: requestKey, content, failed: false }\n : { key: requestKey, content: null, failed: true }\n })\n },\n )\n\n return () => {\n clearTimeout(timer)\n ac.abort()\n }\n }, [\n baseUrl,\n branch,\n currentState.failed,\n currentState.content,\n id,\n requestKey,\n iconType,\n variant,\n ])\n\n if (currentState.failed) return <>{fallback ?? null}</>\n if (!currentState.content) return <>{fallback ?? null}</>\n\n const monoProps = variant === '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={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']\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 })\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.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 const reactProperty = property.replace(/-([a-z])/g, (_, letter: string) =>\n letter.toUpperCase(),\n )\n result[reactProperty] = value\n }\n return result\n}\n"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
+
|
|
3
|
+
var _chunkLMGL65KIcjs = require('./chunk-LMGL65KI.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
var _chunkKJL5NVUUcjs = require('./chunk-KJL5NVUU.cjs');
|
|
7
|
+
require('./chunk-4HVBGJ3B.cjs');
|
|
8
|
+
require('./chunk-CZ5BDQQZ.cjs');
|
|
9
|
+
require('./chunk-KHCWITFW.cjs');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
exports.Icon = _chunkLMGL65KIcjs.Icon; exports.IconProvider = _chunkKJL5NVUUcjs.IconProvider;
|
|
14
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/icons/icons/packages/react/dist/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B,gCAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACF,6FAAC","file":"/home/runner/work/icons/icons/packages/react/dist/index.cjs"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode, SVGProps } from 'react';
|
|
3
|
+
import { IconType } from '@smbdy/icons';
|
|
4
|
+
|
|
5
|
+
interface IconContextValue {
|
|
6
|
+
enableFallback?: boolean;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
branch?: string;
|
|
9
|
+
}
|
|
10
|
+
declare function IconProvider({ children, enableFallback, baseUrl, branch, }: IconContextValue & {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}): react_jsx_runtime.JSX.Element;
|
|
13
|
+
|
|
14
|
+
interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'type'> {
|
|
15
|
+
value: string | number;
|
|
16
|
+
type?: IconType;
|
|
17
|
+
mono?: boolean;
|
|
18
|
+
size?: number | string;
|
|
19
|
+
fallback?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
declare function Icon({ value, type: explicitType, mono, size, fallback, ...props }: IconProps): react_jsx_runtime.JSX.Element;
|
|
22
|
+
|
|
23
|
+
export { Icon, type IconContextValue, type IconProps, IconProvider };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode, SVGProps } from 'react';
|
|
3
|
+
import { IconType } from '@smbdy/icons';
|
|
4
|
+
|
|
5
|
+
interface IconContextValue {
|
|
6
|
+
enableFallback?: boolean;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
branch?: string;
|
|
9
|
+
}
|
|
10
|
+
declare function IconProvider({ children, enableFallback, baseUrl, branch, }: IconContextValue & {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}): react_jsx_runtime.JSX.Element;
|
|
13
|
+
|
|
14
|
+
interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'type'> {
|
|
15
|
+
value: string | number;
|
|
16
|
+
type?: IconType;
|
|
17
|
+
mono?: boolean;
|
|
18
|
+
size?: number | string;
|
|
19
|
+
fallback?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
declare function Icon({ value, type: explicitType, mono, size, fallback, ...props }: IconProps): react_jsx_runtime.JSX.Element;
|
|
22
|
+
|
|
23
|
+
export { Icon, type IconContextValue, type IconProps, IconProvider };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Icon
|
|
5
|
+
} from "./chunk-XWYUXPMU.js";
|
|
6
|
+
import {
|
|
7
|
+
IconProvider
|
|
8
|
+
} from "./chunk-ICEGTX2E.js";
|
|
9
|
+
import "./chunk-35GWSGWA.js";
|
|
10
|
+
import "./chunk-R2LI45CO.js";
|
|
11
|
+
import "./chunk-OVJUO4KZ.js";
|
|
12
|
+
export {
|
|
13
|
+
Icon,
|
|
14
|
+
IconProvider
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|