@kekonic/diagrams-icons 1.0.0-rc.4

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kekonic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # `@kekonic/diagrams-icons`
2
+
3
+ Icon resolution for KDiagram nodes — **Lucide by default**, plus optional Iconify collections and compact built-in stroke glyphs.
4
+
5
+ ## Lucide-first DSL
6
+
7
+ Pick any [Lucide](https://lucide.dev/icons/) icon by kebab-case name:
8
+
9
+ ```kdiagram
10
+ ada: user "Ada" { icon: user }
11
+ cart: service "Checkout" { icon: shopping-cart }
12
+ api: gateway "API" { icon: lucide:waypoints }
13
+ ```
14
+
15
+ Bare names resolve to `lucide:<name>`. A few short names (`database`, `queue`, …) keep KDiagram's tiny built-in stroke set; everything else is Lucide unless you qualify a collection.
16
+
17
+ ## Other collections
18
+
19
+ ```text
20
+ lucide:cloud → Lucide (default for bare names)
21
+ mdi:database → Material Design Icons
22
+ logos:aws → brand logo
23
+ carbon:kubernetes → IBM Carbon
24
+ simple-icons:vercel → Simple Icons
25
+ builtin:server → compact KDiagram stroke glyph
26
+ ```
27
+
28
+ ```kdiagram
29
+ aws: icon { icon: logos:aws }
30
+ api: service "API" { icon: mdi:api }
31
+ ```
32
+
33
+ ## Icon color
34
+
35
+ Icons keep **brand / embedded colors by default**. Tint only the glyph with `iconColor` or
36
+ `--icon-color` (shell unchanged):
37
+
38
+ ```kdiagram
39
+ cart: service "Checkout" {
40
+ icon: shopping-cart
41
+ iconColor: #f97316
42
+ }
43
+
44
+ aws: cloud "AWS" { icon: logos:aws }
45
+
46
+ style accentIcon {
47
+ --icon-color: #38bdf8
48
+ }
49
+ api: gateway "API" { icon: waypoints; styles: [accentIcon] }
50
+ ```
51
+
52
+ `iconPaint: theme` forces monochrome to `--icon-color` / `--node-stroke` (rarely needed).
53
+ `iconPaint: brand` is the default.
54
+
55
+ Helpers: `defaultIconPaint()`, `resolveIconPaint(rawId, override?, { hasIconColor })`. Pass
56
+ `paint` / `color` on `renderIconById` / `renderIconSvg` when rendering outside the DSL.
57
+
58
+ ## Lazy loading
59
+
60
+ Node and CLI renders load installed `@iconify-json/*` collections locally. Browser renders fetch only
61
+ the requested icon names from the Iconify API and cache them; complete multi-megabyte collections are
62
+ not emitted into the default browser bundle.
63
+
64
+ ```ts
65
+ import { preloadIcons, collectIconIds, resolveIcon } from "@kekonic/diagrams-icons";
66
+
67
+ await preloadIcons(collectIconIds(graph.nodes));
68
+ const glyph = resolveIcon("lucide:shopping-cart");
69
+ ```
70
+
71
+ The KDiagram render pipeline preloads automatically before SVG draw.
72
+
73
+ For CSP-controlled or offline browser apps, use `registerIcon`, `registerCollection`, or
74
+ `registerCollectionLoader`. `setIconifyApiBaseUrl` points per-icon requests at a self-hosted Iconify
75
+ API-compatible endpoint.
76
+
77
+ Local Node hosts can build that bounded API response with `loadIconSubset(prefix, names)`. It reads
78
+ installed collections, resolves aliases, and returns only the requested glyphs; validate and limit
79
+ all request parameters before calling it. Kekonic Diagrams Studio uses this pattern to keep icons offline
80
+ without adding complete multi-megabyte collections to its browser bundle.
81
+
82
+ ## API
83
+
84
+ - `parseIconId` / `normalizeIconId`
85
+ - `defaultIconPaint` / `resolveIconPaint`
86
+ - `preloadIcons` / `preloadCollections` / `resolveIcon` / `renderIconById`
87
+ - `registerIcon` / `registerCollection` / `registerCollectionLoader`
88
+ - `setIconifyApiBaseUrl` / `BUILTIN_ICON_ALIASES`
89
+ - `loadIconSubset` (trusted local Node hosts)
@@ -0,0 +1,157 @@
1
+ import { IconifyJSON } from "@iconify/types";
2
+
3
+ //#region src/types.d.ts
4
+ /** Parsed icon reference. */
5
+ type ParsedIconId = {
6
+ /** Collection prefix (`builtin`, `mdi`, `logos`, …). */prefix: string; /** Icon name within the collection. */
7
+ name: string; /** Canonical `prefix:name`. */
8
+ id: string;
9
+ };
10
+ /** Resolved drawable icon. */
11
+ type ResolvedIcon = {
12
+ id: string; /** Inner SVG markup (paths/groups), not a root `<svg>`. */
13
+ body: string;
14
+ viewBox: string;
15
+ width: number;
16
+ height: number;
17
+ /**
18
+ * `stroke` — KDiagram builtins (currentColor stroke).
19
+ * `fill` — typical Iconify glyphs (currentColor or embedded fills).
20
+ */
21
+ paint: "stroke" | "fill";
22
+ };
23
+ /** How fills/strokes are applied when rendering into a themed node. */
24
+ type IconPaintMode = "theme" | "brand";
25
+ type IconRenderOptions = {
26
+ /** Target glyph height in px; width follows viewBox aspect (default 20). */height?: number; /** Max width as a multiple of height (default 1.75). */
27
+ maxAspect?: number; /** Paint policy override; otherwise derived from collection / iconColor. */
28
+ paint?: IconPaintMode;
29
+ /**
30
+ * Explicit icon ink (CSS color). When set, theme-paints the glyph to this
31
+ * value (or `--icon-color` cascade) without changing the node shell.
32
+ */
33
+ color?: string; /** Extra class on the outer `<g>`. */
34
+ className?: string;
35
+ };
36
+ type IconDisplaySize = {
37
+ width: number;
38
+ height: number;
39
+ };
40
+ //#endregion
41
+ //#region src/parse-icon-id.d.ts
42
+ /** Short names that map to KDiagram's built-in stroke glyphs. */
43
+ declare const BUILTIN_ICON_NAMES: readonly ["database", "queue", "user", "choice", "success", "warning", "gateway", "event", "server", "note", "service"];
44
+ type BuiltinIconName = (typeof BUILTIN_ICON_NAMES)[number];
45
+ /**
46
+ * Friendly aliases → Iconify (or builtin) ids.
47
+ * Bare Lucide names work without a prefix (`icon: shopping-cart` → `lucide:shopping-cart`).
48
+ */
49
+ declare const BUILTIN_ICON_ALIASES: Record<string, string>;
50
+ /** Normalize raw DSL / kind icon strings into `prefix:name`. */
51
+ declare function normalizeIconId(raw: string): string;
52
+ declare function parseIconId(raw: string): ParsedIconId | null;
53
+ declare function listBuiltinIconIds(): string[];
54
+ //#endregion
55
+ //#region src/builtins.d.ts
56
+ /** Compact stroke glyphs used by kind defaults (zero network / JSON). */
57
+ declare const BUILTIN_ICONS: Record<BuiltinIconName, Omit<ResolvedIcon, "id">>;
58
+ declare function resolveBuiltinIcon(name: string): ResolvedIcon | null;
59
+ //#endregion
60
+ //#region src/collections.d.ts
61
+ type CollectionLoader = () => Promise<IconifyJSON>;
62
+ /**
63
+ * Built-in Iconify collections (architecture + general).
64
+ * Each is loaded only on first use of that prefix.
65
+ */
66
+ declare const DEFAULT_COLLECTION_LOADERS: Record<string, CollectionLoader>;
67
+ declare function listDefaultCollections(): string[];
68
+ //#endregion
69
+ //#region src/subset.d.ts
70
+ /**
71
+ * Load a bounded, flattened subset of a built-in Iconify collection.
72
+ *
73
+ * This is intended for trusted local hosts that serve requested icons from their installed
74
+ * packages. Default collection loading is Node-only; browser clients should request the subset
75
+ * from their same-origin host.
76
+ */
77
+ declare function loadIconSubset(prefix: string, names: Iterable<string>): Promise<IconifyJSON | null>;
78
+ //#endregion
79
+ //#region src/adapt-colors.d.ts
80
+ /**
81
+ * Adapt Iconify bodies for themed diagram nodes.
82
+ * Lucide/MDI often use currentColor already; brand logos (logos:aws) hardcode
83
+ * dark fills that disappear on dark node backgrounds. Rewrite near-black ink to
84
+ * currentColor while keeping chromatic brand accents (e.g. AWS orange).
85
+ */
86
+ /** True when a hardcoded color is "ink" that should follow the node theme. */
87
+ declare function isThemeableInk(color: string): boolean;
88
+ /** Rewrite dark hardcoded fill/stroke paints to currentColor. */
89
+ declare function adaptIconBodyColors(body: string): string;
90
+ //#endregion
91
+ //#region src/paint.d.ts
92
+ /**
93
+ * Default paint: keep embedded brand fills.
94
+ * Opt into `theme` (or set `iconColor`) to monochrome/tint with the diagram.
95
+ */
96
+ declare function defaultIconPaint(_prefix?: string): IconPaintMode;
97
+ /**
98
+ * Resolve effective paint for an icon id.
99
+ * Author override (`theme` | `brand`) wins; `iconColor` forces theme tint;
100
+ * otherwise brand (keep embedded fills).
101
+ */
102
+ declare function resolveIconPaint(rawId: string, override?: IconPaintMode | null, opts?: {
103
+ hasIconColor?: boolean;
104
+ }): IconPaintMode;
105
+ //#endregion
106
+ //#region src/size.d.ts
107
+ /**
108
+ * Fixed-height, aspect-ratio width for an icon glyph.
109
+ * Clamps width to `maxAspect * height` so extreme banners don't blow out cards.
110
+ */
111
+ declare function iconDisplaySize(icon: Pick<{
112
+ width: number;
113
+ height: number;
114
+ }, "width" | "height">, height: number, options?: {
115
+ maxAspect?: number;
116
+ }): IconDisplaySize;
117
+ //#endregion
118
+ //#region src/resolve.d.ts
119
+ /** Register a one-off icon (custom architecture SVG body + viewBox). */
120
+ declare function registerIcon(id: string, icon: Omit<ResolvedIcon, "id">): void;
121
+ /** Register or replace a whole Iconify JSON collection (eager). */
122
+ declare function registerCollection(prefix: string, data: IconifyJSON): void;
123
+ /** Hook a lazy loader for a custom / extra Iconify prefix. */
124
+ declare function registerCollectionLoader(prefix: string, loader: CollectionLoader): void;
125
+ /** Override the Iconify API origin used for per-icon browser loading. */
126
+ declare function setIconifyApiBaseUrl(url: string): void;
127
+ /**
128
+ * Synchronous lookup — works for builtins, customs, and anything already preloaded.
129
+ * Returns null if the collection has not been loaded yet or the icon is missing.
130
+ */
131
+ declare function resolveIcon(raw: string): ResolvedIcon | null;
132
+ /** Ensure collections for the given icon ids are loaded, then cache each icon. */
133
+ declare function preloadIcons(rawIds: Iterable<string>): Promise<void>;
134
+ /** Warm one or more collections without resolving specific icons. */
135
+ declare function preloadCollections(prefixes: string[]): Promise<void>;
136
+ /** Collect unique icon ids from a graph-like node list. */
137
+ declare function collectIconIds(nodes: Iterable<{
138
+ icon?: string | null | undefined;
139
+ }>): string[];
140
+ /** Test helper — clear caches between cases. */
141
+ declare function resetIconCaches(): void;
142
+ //#endregion
143
+ //#region src/render.d.ts
144
+ /**
145
+ * Emit an SVG fragment positioned at (cx, cy) for a resolved icon.
146
+ * Uses nested `<svg>` so Iconify viewBoxes scale cleanly.
147
+ *
148
+ * Brand paint (default): keep embedded fills (logos / simple-icons).
149
+ * Theme paint: Lucide/builtins use `currentColor`; we set CSS `color` and adapt
150
+ * near-black ink so glyphs pick up `--icon-color` or the parent node's stroke.
151
+ */
152
+ declare function renderIconSvg(icon: ResolvedIcon, cx: number, cy: number, options?: IconRenderOptions): string;
153
+ /** Resolve + render; returns empty string when the icon cannot be resolved. */
154
+ declare function renderIconById(raw: string, cx: number, cy: number, options?: IconRenderOptions): string;
155
+ //#endregion
156
+ export { BUILTIN_ICONS, BUILTIN_ICON_ALIASES, BUILTIN_ICON_NAMES, type BuiltinIconName, type CollectionLoader, DEFAULT_COLLECTION_LOADERS, type IconDisplaySize, type IconPaintMode, type IconRenderOptions, type ParsedIconId, type ResolvedIcon, adaptIconBodyColors, collectIconIds, defaultIconPaint, iconDisplaySize, isThemeableInk, listBuiltinIconIds, listDefaultCollections, loadIconSubset, normalizeIconId, parseIconId, preloadCollections, preloadIcons, registerCollection, registerCollectionLoader, registerIcon, renderIconById, renderIconSvg, resetIconCaches, resolveBuiltinIcon, resolveIcon, resolveIconPaint, setIconifyApiBaseUrl };
157
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,506 @@
1
+ import { getIconData, iconToSVG, replaceIDs } from "@iconify/utils";
2
+ //#region src/parse-icon-id.ts
3
+ const QUALIFIED = /^([a-z0-9][a-z0-9_-]*):([a-z0-9][a-z0-9_-]*)$/i;
4
+ /** Short names that map to KDiagram's built-in stroke glyphs. */
5
+ const BUILTIN_ICON_NAMES = [
6
+ "database",
7
+ "queue",
8
+ "user",
9
+ "choice",
10
+ "success",
11
+ "warning",
12
+ "gateway",
13
+ "event",
14
+ "server",
15
+ "note",
16
+ "service"
17
+ ];
18
+ /**
19
+ * Friendly aliases → Iconify (or builtin) ids.
20
+ * Bare Lucide names work without a prefix (`icon: shopping-cart` → `lucide:shopping-cart`).
21
+ */
22
+ const BUILTIN_ICON_ALIASES = {
23
+ cart: "lucide:shopping-cart",
24
+ cloud: "lucide:cloud",
25
+ api: "lucide:waypoints",
26
+ person: "lucide:user",
27
+ actor: "lucide:user",
28
+ lambda: "logos:aws-lambda",
29
+ aws: "logos:aws",
30
+ azure: "logos:microsoft-azure",
31
+ gcp: "logos:google-cloud",
32
+ k8s: "logos:kubernetes",
33
+ docker: "logos:docker",
34
+ postgres: "logos:postgresql",
35
+ redis: "logos:redis",
36
+ kafka: "logos:kafka",
37
+ github: "logos:github-icon",
38
+ vercel: "simple-icons:vercel"
39
+ };
40
+ /** Normalize raw DSL / kind icon strings into `prefix:name`. */
41
+ function normalizeIconId(raw) {
42
+ const trimmed = raw.trim();
43
+ if (!trimmed || trimmed === "none") return "none";
44
+ const alias = BUILTIN_ICON_ALIASES[trimmed.toLowerCase()];
45
+ if (alias) return alias;
46
+ if (QUALIFIED.test(trimmed)) return trimmed.toLowerCase();
47
+ if (BUILTIN_ICON_NAMES.includes(trimmed.toLowerCase())) return `builtin:${trimmed.toLowerCase()}`;
48
+ return `lucide:${trimmed.toLowerCase()}`;
49
+ }
50
+ function parseIconId(raw) {
51
+ const id = normalizeIconId(raw);
52
+ if (id === "none") return null;
53
+ const m = QUALIFIED.exec(id);
54
+ if (!m) return null;
55
+ return {
56
+ prefix: m[1].toLowerCase(),
57
+ name: m[2].toLowerCase(),
58
+ id
59
+ };
60
+ }
61
+ function listBuiltinIconIds() {
62
+ return BUILTIN_ICON_NAMES.map((n) => `builtin:${n}`);
63
+ }
64
+ //#endregion
65
+ //#region src/builtins.ts
66
+ const VB = "0 0 20 20";
67
+ function stroke(body) {
68
+ return {
69
+ body: `<g class="flow-node-icon-glyph" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" opacity="0.88">${body}</g>`,
70
+ viewBox: VB,
71
+ width: 20,
72
+ height: 20,
73
+ paint: "stroke"
74
+ };
75
+ }
76
+ /** Compact stroke glyphs used by kind defaults (zero network / JSON). */
77
+ const BUILTIN_ICONS = {
78
+ database: stroke(`<ellipse cx="10" cy="4" rx="8" ry="3"/><path d="M2 4v12c0 1.7 3.6 3 8 3s8-1.3 8-3V4"/><path d="M2 10c0 1.7 3.6 3 8 3s8-1.3 8-3"/>`),
79
+ queue: stroke(`<path d="M5 5h8a4 4 0 0 1 0 10H5a4 4 0 0 1 0-10Z"/><path d="M5 5a4 4 0 0 0 0 10"/>`),
80
+ user: stroke(`<circle cx="10" cy="7" r="4"/><path d="M3 18c0-3.9 3.1-7 7-7s7 3.1 7 7"/>`),
81
+ choice: stroke(`<path d="M10 3 17 10 10 17 3 10Z"/><path d="M10 7v6M7 10h6"/>`),
82
+ success: stroke(`<path d="M6 10l3 3 6-7"/>`),
83
+ warning: stroke(`<path d="M10 4 17 17H3Z"/><path d="M10 9v4M10 15h.01"/>`),
84
+ gateway: stroke(`<path d="M3 10h14M10 3v14M6 6l8 8M14 6 6 14"/>`),
85
+ event: stroke(`<path d="M5 10h10M10 5v10"/><circle cx="10" cy="10" r="7"/>`),
86
+ server: stroke(`<rect x="3" y="3" width="14" height="14" rx="2"/><path d="M6 7h8M6 10h8M6 13h8"/><circle cx="15" cy="7" r="0.8" fill="currentColor" stroke="none"/>`),
87
+ note: stroke(`<path d="M5 3h8l4 4v10H5Z"/><path d="M13 3v4h4"/>`),
88
+ service: stroke(`<rect x="3" y="3" width="14" height="14" rx="3"/><path d="M6 8h8M6 11h8M6 14h5"/>`)
89
+ };
90
+ function resolveBuiltinIcon(name) {
91
+ const key = name.toLowerCase();
92
+ const glyph = BUILTIN_ICONS[key];
93
+ if (!glyph) return null;
94
+ return {
95
+ id: `builtin:${key}`,
96
+ ...glyph
97
+ };
98
+ }
99
+ //#endregion
100
+ //#region src/collections.ts
101
+ /**
102
+ * Node can `require()` Iconify JSON directly. Keep this off the browser path —
103
+ * Vite serves JSON as JS modules, so `import(..., { with: { type: "json" } })`
104
+ * fails with a MIME type error in the studio.
105
+ */
106
+ async function loadViaNodeRequire(specifier) {
107
+ if (typeof window !== "undefined") return null;
108
+ try {
109
+ const moduleBuiltin = globalThis.process?.getBuiltinModule?.("module");
110
+ if (!moduleBuiltin) return null;
111
+ const { createRequire } = moduleBuiltin;
112
+ return createRequire(import.meta.url)(specifier);
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+ /**
118
+ * Load a complete Iconify JSON collection in Node. Browser rendering resolves only
119
+ * the requested icons through the Iconify API in `resolve.ts`; keeping static JSON
120
+ * imports out of this module prevents bundlers from emitting multi-megabyte chunks.
121
+ */
122
+ async function loadIconifyJson(specifier) {
123
+ const fromNode = await loadViaNodeRequire(specifier);
124
+ if (fromNode) return fromNode;
125
+ throw new Error(`Complete Iconify collection "${specifier}" is unavailable in the browser; resolve individual icons or register a collection`);
126
+ }
127
+ /**
128
+ * Built-in Iconify collections (architecture + general).
129
+ * Each is loaded only on first use of that prefix.
130
+ */
131
+ const DEFAULT_COLLECTION_LOADERS = {
132
+ mdi: () => loadIconifyJson("@iconify-json/mdi/icons.json"),
133
+ logos: () => loadIconifyJson("@iconify-json/logos/icons.json"),
134
+ lucide: () => loadIconifyJson("@iconify-json/lucide/icons.json"),
135
+ carbon: () => loadIconifyJson("@iconify-json/carbon/icons.json"),
136
+ "simple-icons": () => loadIconifyJson("@iconify-json/simple-icons/icons.json")
137
+ };
138
+ function listDefaultCollections() {
139
+ return Object.keys(DEFAULT_COLLECTION_LOADERS);
140
+ }
141
+ //#endregion
142
+ //#region src/subset.ts
143
+ /**
144
+ * Load a bounded, flattened subset of a built-in Iconify collection.
145
+ *
146
+ * This is intended for trusted local hosts that serve requested icons from their installed
147
+ * packages. Default collection loading is Node-only; browser clients should request the subset
148
+ * from their same-origin host.
149
+ */
150
+ async function loadIconSubset(prefix, names) {
151
+ const normalizedPrefix = prefix.toLowerCase();
152
+ const loader = DEFAULT_COLLECTION_LOADERS[normalizedPrefix];
153
+ if (!loader) return null;
154
+ const collection = await loader();
155
+ const icons = {};
156
+ for (const name of new Set(names)) {
157
+ const icon = getIconData(collection, name);
158
+ if (icon) icons[name] = icon;
159
+ }
160
+ return {
161
+ prefix: normalizedPrefix,
162
+ width: collection.width,
163
+ height: collection.height,
164
+ icons
165
+ };
166
+ }
167
+ //#endregion
168
+ //#region src/adapt-colors.ts
169
+ /**
170
+ * Adapt Iconify bodies for themed diagram nodes.
171
+ * Lucide/MDI often use currentColor already; brand logos (logos:aws) hardcode
172
+ * dark fills that disappear on dark node backgrounds. Rewrite near-black ink to
173
+ * currentColor while keeping chromatic brand accents (e.g. AWS orange).
174
+ */
175
+ function parseHex(color) {
176
+ const raw = color.trim().toLowerCase();
177
+ if (raw === "black") return {
178
+ r: 0,
179
+ g: 0,
180
+ b: 0
181
+ };
182
+ if (raw === "white") return {
183
+ r: 255,
184
+ g: 255,
185
+ b: 255
186
+ };
187
+ const short = /^#([0-9a-f]{3})$/i.exec(raw);
188
+ if (short) {
189
+ const h = short[1];
190
+ return {
191
+ r: Number.parseInt(h[0] + h[0], 16),
192
+ g: Number.parseInt(h[1] + h[1], 16),
193
+ b: Number.parseInt(h[2] + h[2], 16)
194
+ };
195
+ }
196
+ const long = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(raw);
197
+ if (long) {
198
+ const h = long[1];
199
+ return {
200
+ r: Number.parseInt(h.slice(0, 2), 16),
201
+ g: Number.parseInt(h.slice(2, 4), 16),
202
+ b: Number.parseInt(h.slice(4, 6), 16)
203
+ };
204
+ }
205
+ const rgb = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i.exec(raw);
206
+ if (rgb) return {
207
+ r: Number(rgb[1]),
208
+ g: Number(rgb[2]),
209
+ b: Number(rgb[3])
210
+ };
211
+ return null;
212
+ }
213
+ /** WCAG relative luminance (sRGB). */
214
+ function relativeLuminance(r, g, b) {
215
+ const lin = [
216
+ r,
217
+ g,
218
+ b
219
+ ].map((c) => {
220
+ const s = c / 255;
221
+ return s <= .03928 ? s / 12.92 : ((s + .055) / 1.055) ** 2.4;
222
+ });
223
+ return .2126 * lin[0] + .7152 * lin[1] + .0722 * lin[2];
224
+ }
225
+ /** True when a hardcoded color is "ink" that should follow the node theme. */
226
+ function isThemeableInk(color) {
227
+ const value = color.trim().toLowerCase();
228
+ if (!value || value === "none" || value === "currentcolor" || value === "transparent" || value.startsWith("url(") || value.startsWith("var(")) return false;
229
+ const rgb = parseHex(value);
230
+ if (!rgb) return false;
231
+ const lum = relativeLuminance(rgb.r, rgb.g, rgb.b);
232
+ const max = Math.max(rgb.r, rgb.g, rgb.b);
233
+ const min = Math.min(rgb.r, rgb.g, rgb.b);
234
+ const saturation = max === 0 ? 0 : (max - min) / max;
235
+ if (lum <= .28 && saturation < .45) return true;
236
+ if (lum <= .12) return true;
237
+ return false;
238
+ }
239
+ /** Rewrite dark hardcoded fill/stroke paints to currentColor. */
240
+ function adaptIconBodyColors(body) {
241
+ return body.replace(/\b(fill|stroke)="([^"]*)"/gi, (match, attr, value) => {
242
+ if (!isThemeableInk(value)) return match;
243
+ return `${attr}="currentColor"`;
244
+ });
245
+ }
246
+ //#endregion
247
+ //#region src/paint.ts
248
+ /**
249
+ * Default paint: keep embedded brand fills.
250
+ * Opt into `theme` (or set `iconColor`) to monochrome/tint with the diagram.
251
+ */
252
+ function defaultIconPaint(_prefix) {
253
+ return "brand";
254
+ }
255
+ /**
256
+ * Resolve effective paint for an icon id.
257
+ * Author override (`theme` | `brand`) wins; `iconColor` forces theme tint;
258
+ * otherwise brand (keep embedded fills).
259
+ */
260
+ function resolveIconPaint(rawId, override, opts) {
261
+ if (override === "theme" || override === "brand") return override;
262
+ if (opts?.hasIconColor) return "theme";
263
+ const parsed = parseIconId(rawId);
264
+ if (!parsed) return "brand";
265
+ return defaultIconPaint(parsed.prefix);
266
+ }
267
+ //#endregion
268
+ //#region src/size.ts
269
+ const DEFAULT_MAX_ASPECT = 1.75;
270
+ /**
271
+ * Fixed-height, aspect-ratio width for an icon glyph.
272
+ * Clamps width to `maxAspect * height` so extreme banners don't blow out cards.
273
+ */
274
+ function iconDisplaySize(icon, height, options = {}) {
275
+ const maxAspect = options.maxAspect ?? DEFAULT_MAX_ASPECT;
276
+ const aspect = icon.width > 0 && icon.height > 0 ? icon.width / icon.height : 1;
277
+ const width = Math.min(height * aspect, height * maxAspect);
278
+ return {
279
+ width: Math.max(0, width),
280
+ height: Math.max(0, height)
281
+ };
282
+ }
283
+ //#endregion
284
+ //#region src/resolve.ts
285
+ const collectionCache = /* @__PURE__ */ new Map();
286
+ const collectionLoaders = new Map(Object.entries(DEFAULT_COLLECTION_LOADERS));
287
+ const collectionPromises = /* @__PURE__ */ new Map();
288
+ const iconCache = /* @__PURE__ */ new Map();
289
+ const customIcons = /* @__PURE__ */ new Map();
290
+ const customCollectionLoaders = /* @__PURE__ */ new Set();
291
+ let iconifyApiBaseUrl = "https://api.iconify.design";
292
+ function viewBoxFromIcon(data) {
293
+ const left = data.left ?? 0;
294
+ const top = data.top ?? 0;
295
+ const width = data.width ?? 16;
296
+ const height = data.height ?? 16;
297
+ return {
298
+ viewBox: `${left} ${top} ${width} ${height}`,
299
+ width,
300
+ height
301
+ };
302
+ }
303
+ function fromIconify(id, data) {
304
+ const built = iconToSVG(data, {
305
+ height: "unset",
306
+ width: "unset"
307
+ });
308
+ const meta = viewBoxFromIcon(data);
309
+ return {
310
+ id,
311
+ body: replaceIDs(built.body),
312
+ viewBox: built.attributes.viewBox ?? meta.viewBox,
313
+ width: meta.width,
314
+ height: meta.height,
315
+ paint: "fill"
316
+ };
317
+ }
318
+ /** Register a one-off icon (custom architecture SVG body + viewBox). */
319
+ function registerIcon(id, icon) {
320
+ const normalized = normalizeIconId(id);
321
+ const resolved = {
322
+ id: normalized,
323
+ ...icon
324
+ };
325
+ customIcons.set(normalized, resolved);
326
+ iconCache.set(normalized, resolved);
327
+ }
328
+ /** Register or replace a whole Iconify JSON collection (eager). */
329
+ function registerCollection(prefix, data) {
330
+ const key = prefix.toLowerCase();
331
+ collectionCache.set(key, data);
332
+ collectionLoaders.set(key, async () => data);
333
+ for (const [id, value] of iconCache) if (id.startsWith(`${key}:`) && value === null) iconCache.delete(id);
334
+ }
335
+ /** Hook a lazy loader for a custom / extra Iconify prefix. */
336
+ function registerCollectionLoader(prefix, loader) {
337
+ const key = prefix.toLowerCase();
338
+ collectionLoaders.set(key, loader);
339
+ customCollectionLoaders.add(key);
340
+ }
341
+ /** Override the Iconify API origin used for per-icon browser loading. */
342
+ function setIconifyApiBaseUrl(url) {
343
+ iconifyApiBaseUrl = url.replace(/\/$/, "");
344
+ }
345
+ async function loadCollection(prefix) {
346
+ const key = prefix.toLowerCase();
347
+ const cached = collectionCache.get(key);
348
+ if (cached) return cached;
349
+ let pending = collectionPromises.get(key);
350
+ if (!pending) {
351
+ const loader = collectionLoaders.get(key);
352
+ if (!loader) {
353
+ collectionPromises.set(key, Promise.resolve(null));
354
+ return null;
355
+ }
356
+ pending = loader().then((data) => {
357
+ collectionCache.set(key, data);
358
+ return data;
359
+ }).catch((err) => {
360
+ collectionPromises.delete(key);
361
+ console.warn(`[kdiagram-icons] failed to load collection "${key}"`, err);
362
+ return null;
363
+ });
364
+ collectionPromises.set(key, pending);
365
+ }
366
+ return pending;
367
+ }
368
+ function resolveFromLoaded(prefix, name, id) {
369
+ if (prefix === "builtin") return resolveBuiltinIcon(name);
370
+ const custom = customIcons.get(id);
371
+ if (custom) return custom;
372
+ const set = collectionCache.get(prefix);
373
+ if (!set) return null;
374
+ const data = getIconData(set, name);
375
+ if (!data) return null;
376
+ return fromIconify(id, data);
377
+ }
378
+ function mergeCollection(existing, incoming) {
379
+ if (!existing) return incoming;
380
+ return {
381
+ ...existing,
382
+ ...incoming,
383
+ icons: {
384
+ ...existing.icons,
385
+ ...incoming.icons
386
+ },
387
+ aliases: {
388
+ ...existing.aliases,
389
+ ...incoming.aliases
390
+ }
391
+ };
392
+ }
393
+ async function loadRemoteIcons(prefix, names) {
394
+ if (typeof fetch !== "function" || names.length === 0) return;
395
+ const params = new URLSearchParams({ icons: names.join(",") });
396
+ const response = await fetch(`${iconifyApiBaseUrl}/${encodeURIComponent(prefix)}.json?${params}`);
397
+ if (!response.ok) throw new Error(`Iconify API returned ${response.status} for collection "${prefix}"`);
398
+ const data = await response.json();
399
+ collectionCache.set(prefix, mergeCollection(collectionCache.get(prefix), data));
400
+ }
401
+ /**
402
+ * Synchronous lookup — works for builtins, customs, and anything already preloaded.
403
+ * Returns null if the collection has not been loaded yet or the icon is missing.
404
+ */
405
+ function resolveIcon(raw) {
406
+ const parsed = parseIconId(raw);
407
+ if (!parsed) return null;
408
+ if (iconCache.has(parsed.id)) return iconCache.get(parsed.id) ?? null;
409
+ const resolved = resolveFromLoaded(parsed.prefix, parsed.name, parsed.id);
410
+ if (resolved || collectionCache.has(parsed.prefix) || parsed.prefix === "builtin") iconCache.set(parsed.id, resolved);
411
+ return resolved;
412
+ }
413
+ /** Ensure collections for the given icon ids are loaded, then cache each icon. */
414
+ async function preloadIcons(rawIds) {
415
+ const parsed = [...rawIds].map((raw) => parseIconId(raw)).filter((p) => p != null);
416
+ const grouped = /* @__PURE__ */ new Map();
417
+ for (const icon of parsed) {
418
+ if (icon.prefix === "builtin" || iconCache.has(icon.id)) continue;
419
+ const names = grouped.get(icon.prefix) ?? [];
420
+ if (!names.includes(icon.name)) names.push(icon.name);
421
+ grouped.set(icon.prefix, names);
422
+ }
423
+ const inBrowser = typeof window !== "undefined";
424
+ await Promise.all([...grouped].map(async ([prefix, names]) => {
425
+ const loaded = collectionCache.get(prefix);
426
+ const missingNames = loaded ? names.filter((name) => !getIconData(loaded, name)) : names;
427
+ if (missingNames.length === 0) return;
428
+ if (!inBrowser || customCollectionLoaders.has(prefix)) {
429
+ await loadCollection(prefix);
430
+ return;
431
+ }
432
+ await loadRemoteIcons(prefix, missingNames);
433
+ })).catch((err) => {
434
+ console.warn("[kdiagram-icons] failed to preload requested icons", err);
435
+ });
436
+ for (const p of parsed) {
437
+ if (iconCache.has(p.id)) continue;
438
+ iconCache.set(p.id, resolveFromLoaded(p.prefix, p.name, p.id));
439
+ }
440
+ }
441
+ /** Warm one or more collections without resolving specific icons. */
442
+ async function preloadCollections(prefixes) {
443
+ const inBrowser = typeof window !== "undefined";
444
+ await Promise.all(prefixes.map((p) => {
445
+ const prefix = p.toLowerCase();
446
+ if (inBrowser && !customCollectionLoaders.has(prefix)) {
447
+ console.warn(`[kdiagram-icons] preloadCollections("${prefix}") requires registerCollectionLoader in the browser; individual icons load on demand`);
448
+ return Promise.resolve(null);
449
+ }
450
+ return loadCollection(prefix);
451
+ }));
452
+ }
453
+ /** Collect unique icon ids from a graph-like node list. */
454
+ function collectIconIds(nodes) {
455
+ const ids = /* @__PURE__ */ new Set();
456
+ for (const node of nodes) {
457
+ if (!node.icon || node.icon === "none") continue;
458
+ const normalized = normalizeIconId(node.icon);
459
+ if (normalized !== "none") ids.add(normalized);
460
+ }
461
+ return [...ids];
462
+ }
463
+ /** Test helper — clear caches between cases. */
464
+ function resetIconCaches() {
465
+ iconCache.clear();
466
+ collectionCache.clear();
467
+ collectionPromises.clear();
468
+ customCollectionLoaders.clear();
469
+ iconifyApiBaseUrl = "https://api.iconify.design";
470
+ collectionLoaders.clear();
471
+ for (const [prefix, loader] of Object.entries(DEFAULT_COLLECTION_LOADERS)) collectionLoaders.set(prefix, loader);
472
+ }
473
+ //#endregion
474
+ //#region src/render.ts
475
+ /** Icon ink: explicit --icon-color, else node stroke / theme fallback. */
476
+ const ICON_COLOR = "var(--icon-color, var(--node-stroke, var(--kd-node-stroke)))";
477
+ /**
478
+ * Emit an SVG fragment positioned at (cx, cy) for a resolved icon.
479
+ * Uses nested `<svg>` so Iconify viewBoxes scale cleanly.
480
+ *
481
+ * Brand paint (default): keep embedded fills (logos / simple-icons).
482
+ * Theme paint: Lucide/builtins use `currentColor`; we set CSS `color` and adapt
483
+ * near-black ink so glyphs pick up `--icon-color` or the parent node's stroke.
484
+ */
485
+ function renderIconSvg(icon, cx, cy, options = {}) {
486
+ const { width, height } = iconDisplaySize(icon, options.height ?? 20, { maxAspect: options.maxAspect });
487
+ const paint = options.paint ?? resolveIconPaint(icon.id, void 0, { hasIconColor: Boolean(options.color) });
488
+ const className = options.className ?? "flow-node-icon-mark";
489
+ const body = paint === "theme" ? adaptIconBodyColors(icon.body) : icon.body;
490
+ const colorValue = options.color ?? (paint === "theme" ? ICON_COLOR : "");
491
+ const colorAttrs = colorValue ? `color="${colorValue}"` : "";
492
+ return `<g class="${className}" transform="translate(${cx - width / 2}, ${cy - height / 2})"${colorAttrs ? ` ${colorAttrs}` : ""}><svg width="${width}" height="${height}" viewBox="${icon.viewBox}" overflow="visible" aria-hidden="true">${body}</svg></g>`;
493
+ }
494
+ /** Resolve + render; returns empty string when the icon cannot be resolved. */
495
+ function renderIconById(raw, cx, cy, options = {}) {
496
+ const icon = resolveIcon(raw);
497
+ if (!icon) return "";
498
+ return renderIconSvg(icon, cx, cy, {
499
+ ...options,
500
+ paint: options.paint ?? resolveIconPaint(raw, void 0, { hasIconColor: Boolean(options.color) })
501
+ });
502
+ }
503
+ //#endregion
504
+ export { BUILTIN_ICONS, BUILTIN_ICON_ALIASES, BUILTIN_ICON_NAMES, DEFAULT_COLLECTION_LOADERS, adaptIconBodyColors, collectIconIds, defaultIconPaint, iconDisplaySize, isThemeableInk, listBuiltinIconIds, listDefaultCollections, loadIconSubset, normalizeIconId, parseIconId, preloadCollections, preloadIcons, registerCollection, registerCollectionLoader, registerIcon, renderIconById, renderIconSvg, resetIconCaches, resolveBuiltinIcon, resolveIcon, resolveIconPaint, setIconifyApiBaseUrl };
505
+
506
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/parse-icon-id.ts","../src/builtins.ts","../src/collections.ts","../src/subset.ts","../src/adapt-colors.ts","../src/paint.ts","../src/size.ts","../src/resolve.ts","../src/render.ts"],"sourcesContent":["import type { ParsedIconId } from \"./types.ts\";\n\nconst QUALIFIED = /^([a-z0-9][a-z0-9_-]*):([a-z0-9][a-z0-9_-]*)$/i;\n\n/** Short names that map to KDiagram's built-in stroke glyphs. */\nexport const BUILTIN_ICON_NAMES = [\n \"database\",\n \"queue\",\n \"user\",\n \"choice\",\n \"success\",\n \"warning\",\n \"gateway\",\n \"event\",\n \"server\",\n \"note\",\n \"service\",\n] as const;\n\nexport type BuiltinIconName = (typeof BUILTIN_ICON_NAMES)[number];\n\n/**\n * Friendly aliases → Iconify (or builtin) ids.\n * Bare Lucide names work without a prefix (`icon: shopping-cart` → `lucide:shopping-cart`).\n */\nexport const BUILTIN_ICON_ALIASES: Record<string, string> = {\n cart: \"lucide:shopping-cart\",\n cloud: \"lucide:cloud\",\n api: \"lucide:waypoints\",\n person: \"lucide:user\",\n actor: \"lucide:user\",\n lambda: \"logos:aws-lambda\",\n aws: \"logos:aws\",\n azure: \"logos:microsoft-azure\",\n gcp: \"logos:google-cloud\",\n k8s: \"logos:kubernetes\",\n docker: \"logos:docker\",\n postgres: \"logos:postgresql\",\n redis: \"logos:redis\",\n kafka: \"logos:kafka\",\n github: \"logos:github-icon\",\n vercel: \"simple-icons:vercel\",\n};\n\n/** Normalize raw DSL / kind icon strings into `prefix:name`. */\nexport function normalizeIconId(raw: string): string {\n const trimmed = raw.trim();\n if (!trimmed || trimmed === \"none\") return \"none\";\n const alias = BUILTIN_ICON_ALIASES[trimmed.toLowerCase()];\n if (alias) return alias;\n if (QUALIFIED.test(trimmed)) return trimmed.toLowerCase();\n if ((BUILTIN_ICON_NAMES as readonly string[]).includes(trimmed.toLowerCase())) {\n return `builtin:${trimmed.toLowerCase()}`;\n }\n // Bare unknown names map to Lucide (any Lucide icon by kebab-case name).\n return `lucide:${trimmed.toLowerCase()}`;\n}\n\nexport function parseIconId(raw: string): ParsedIconId | null {\n const id = normalizeIconId(raw);\n if (id === \"none\") return null;\n const m = QUALIFIED.exec(id);\n if (!m) return null;\n return { prefix: m[1]!.toLowerCase(), name: m[2]!.toLowerCase(), id };\n}\n\nexport function listBuiltinIconIds(): string[] {\n return BUILTIN_ICON_NAMES.map((n) => `builtin:${n}`);\n}\n","import type { ResolvedIcon } from \"./types.ts\";\nimport type { BuiltinIconName } from \"./parse-icon-id.ts\";\n\nconst VB = \"0 0 20 20\";\n\nfunction stroke(body: string): Omit<ResolvedIcon, \"id\"> {\n return {\n body: `<g class=\"flow-node-icon-glyph\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.88\">${body}</g>`,\n viewBox: VB,\n width: 20,\n height: 20,\n paint: \"stroke\",\n };\n}\n\n/** Compact stroke glyphs used by kind defaults (zero network / JSON). */\nexport const BUILTIN_ICONS: Record<BuiltinIconName, Omit<ResolvedIcon, \"id\">> = {\n database: stroke(\n `<ellipse cx=\"10\" cy=\"4\" rx=\"8\" ry=\"3\"/><path d=\"M2 4v12c0 1.7 3.6 3 8 3s8-1.3 8-3V4\"/><path d=\"M2 10c0 1.7 3.6 3 8 3s8-1.3 8-3\"/>`,\n ),\n queue: stroke(\n `<path d=\"M5 5h8a4 4 0 0 1 0 10H5a4 4 0 0 1 0-10Z\"/><path d=\"M5 5a4 4 0 0 0 0 10\"/>`,\n ),\n user: stroke(`<circle cx=\"10\" cy=\"7\" r=\"4\"/><path d=\"M3 18c0-3.9 3.1-7 7-7s7 3.1 7 7\"/>`),\n choice: stroke(`<path d=\"M10 3 17 10 10 17 3 10Z\"/><path d=\"M10 7v6M7 10h6\"/>`),\n success: stroke(`<path d=\"M6 10l3 3 6-7\"/>`),\n warning: stroke(`<path d=\"M10 4 17 17H3Z\"/><path d=\"M10 9v4M10 15h.01\"/>`),\n gateway: stroke(`<path d=\"M3 10h14M10 3v14M6 6l8 8M14 6 6 14\"/>`),\n event: stroke(`<path d=\"M5 10h10M10 5v10\"/><circle cx=\"10\" cy=\"10\" r=\"7\"/>`),\n server: stroke(\n `<rect x=\"3\" y=\"3\" width=\"14\" height=\"14\" rx=\"2\"/><path d=\"M6 7h8M6 10h8M6 13h8\"/><circle cx=\"15\" cy=\"7\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\"/>`,\n ),\n note: stroke(`<path d=\"M5 3h8l4 4v10H5Z\"/><path d=\"M13 3v4h4\"/>`),\n service: stroke(\n `<rect x=\"3\" y=\"3\" width=\"14\" height=\"14\" rx=\"3\"/><path d=\"M6 8h8M6 11h8M6 14h5\"/>`,\n ),\n};\n\nexport function resolveBuiltinIcon(name: string): ResolvedIcon | null {\n const key = name.toLowerCase() as BuiltinIconName;\n const glyph = BUILTIN_ICONS[key];\n if (!glyph) return null;\n return { id: `builtin:${key}`, ...glyph };\n}\n","import type { IconifyJSON } from \"@iconify/types\";\n\nexport type CollectionLoader = () => Promise<IconifyJSON>;\n\n/**\n * Node can `require()` Iconify JSON directly. Keep this off the browser path —\n * Vite serves JSON as JS modules, so `import(..., { with: { type: \"json\" } })`\n * fails with a MIME type error in the studio.\n */\nasync function loadViaNodeRequire(specifier: string): Promise<IconifyJSON | null> {\n if (typeof window !== \"undefined\") return null;\n try {\n const runtimeProcess = (\n globalThis as typeof globalThis & {\n process?: { getBuiltinModule?: (id: string) => unknown };\n }\n ).process;\n const moduleBuiltin = runtimeProcess?.getBuiltinModule?.(\"module\") as\n | typeof import(\"node:module\")\n | undefined;\n if (!moduleBuiltin) return null;\n\n const { createRequire } = moduleBuiltin;\n const require = createRequire(import.meta.url);\n return require(specifier) as IconifyJSON;\n } catch {\n return null;\n }\n}\n\n/**\n * Load a complete Iconify JSON collection in Node. Browser rendering resolves only\n * the requested icons through the Iconify API in `resolve.ts`; keeping static JSON\n * imports out of this module prevents bundlers from emitting multi-megabyte chunks.\n */\nasync function loadIconifyJson(specifier: string): Promise<IconifyJSON> {\n const fromNode = await loadViaNodeRequire(specifier);\n if (fromNode) return fromNode;\n throw new Error(\n `Complete Iconify collection \"${specifier}\" is unavailable in the browser; resolve individual icons or register a collection`,\n );\n}\n\n/**\n * Built-in Iconify collections (architecture + general).\n * Each is loaded only on first use of that prefix.\n */\nexport const DEFAULT_COLLECTION_LOADERS: Record<string, CollectionLoader> = {\n mdi: () => loadIconifyJson(\"@iconify-json/mdi/icons.json\"),\n logos: () => loadIconifyJson(\"@iconify-json/logos/icons.json\"),\n lucide: () => loadIconifyJson(\"@iconify-json/lucide/icons.json\"),\n carbon: () => loadIconifyJson(\"@iconify-json/carbon/icons.json\"),\n \"simple-icons\": () => loadIconifyJson(\"@iconify-json/simple-icons/icons.json\"),\n};\n\nexport function listDefaultCollections(): string[] {\n return Object.keys(DEFAULT_COLLECTION_LOADERS);\n}\n","import type { IconifyIcon, IconifyJSON } from \"@iconify/types\";\nimport { getIconData } from \"@iconify/utils\";\nimport { DEFAULT_COLLECTION_LOADERS } from \"./collections.ts\";\n\n/**\n * Load a bounded, flattened subset of a built-in Iconify collection.\n *\n * This is intended for trusted local hosts that serve requested icons from their installed\n * packages. Default collection loading is Node-only; browser clients should request the subset\n * from their same-origin host.\n */\nexport async function loadIconSubset(\n prefix: string,\n names: Iterable<string>,\n): Promise<IconifyJSON | null> {\n const normalizedPrefix = prefix.toLowerCase();\n const loader = DEFAULT_COLLECTION_LOADERS[normalizedPrefix];\n if (!loader) return null;\n\n const collection = await loader();\n const icons: Record<string, IconifyIcon> = {};\n for (const name of new Set(names)) {\n const icon = getIconData(collection, name);\n if (icon) icons[name] = icon;\n }\n\n return {\n prefix: normalizedPrefix,\n width: collection.width,\n height: collection.height,\n icons,\n };\n}\n","/**\n * Adapt Iconify bodies for themed diagram nodes.\n * Lucide/MDI often use currentColor already; brand logos (logos:aws) hardcode\n * dark fills that disappear on dark node backgrounds. Rewrite near-black ink to\n * currentColor while keeping chromatic brand accents (e.g. AWS orange).\n */\n\nfunction parseHex(color: string): { r: number; g: number; b: number } | null {\n const raw = color.trim().toLowerCase();\n if (raw === \"black\") return { r: 0, g: 0, b: 0 };\n if (raw === \"white\") return { r: 255, g: 255, b: 255 };\n const short = /^#([0-9a-f]{3})$/i.exec(raw);\n if (short) {\n const h = short[1]!;\n return {\n r: Number.parseInt(h[0]! + h[0]!, 16),\n g: Number.parseInt(h[1]! + h[1]!, 16),\n b: Number.parseInt(h[2]! + h[2]!, 16),\n };\n }\n const long = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(raw);\n if (long) {\n const h = long[1]!;\n return {\n r: Number.parseInt(h.slice(0, 2), 16),\n g: Number.parseInt(h.slice(2, 4), 16),\n b: Number.parseInt(h.slice(4, 6), 16),\n };\n }\n const rgb = /^rgba?\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)/i.exec(raw);\n if (rgb) {\n return {\n r: Number(rgb[1]),\n g: Number(rgb[2]),\n b: Number(rgb[3]),\n };\n }\n return null;\n}\n\n/** WCAG relative luminance (sRGB). */\nfunction relativeLuminance(r: number, g: number, b: number): number {\n const lin = [r, g, b].map((c) => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * lin[0]! + 0.7152 * lin[1]! + 0.0722 * lin[2]!;\n}\n\n/** True when a hardcoded color is \"ink\" that should follow the node theme. */\nexport function isThemeableInk(color: string): boolean {\n const value = color.trim().toLowerCase();\n if (\n !value ||\n value === \"none\" ||\n value === \"currentcolor\" ||\n value === \"transparent\" ||\n value.startsWith(\"url(\") ||\n value.startsWith(\"var(\")\n ) {\n return false;\n }\n const rgb = parseHex(value);\n if (!rgb) return false;\n // Near-black / dark slate — invisible on dark node fills; theme it.\n // Keep saturated brand colors (AWS orange, etc.).\n const lum = relativeLuminance(rgb.r, rgb.g, rgb.b);\n const max = Math.max(rgb.r, rgb.g, rgb.b);\n const min = Math.min(rgb.r, rgb.g, rgb.b);\n const saturation = max === 0 ? 0 : (max - min) / max;\n if (lum <= 0.28 && saturation < 0.45) return true;\n if (lum <= 0.12) return true;\n return false;\n}\n\n/** Rewrite dark hardcoded fill/stroke paints to currentColor. */\nexport function adaptIconBodyColors(body: string): string {\n return body.replace(/\\b(fill|stroke)=\"([^\"]*)\"/gi, (match, attr: string, value: string) => {\n if (!isThemeableInk(value)) return match;\n return `${attr}=\"currentColor\"`;\n });\n}\n","import { parseIconId } from \"./parse-icon-id.ts\";\nimport type { IconPaintMode } from \"./types.ts\";\n\n/**\n * Default paint: keep embedded brand fills.\n * Opt into `theme` (or set `iconColor`) to monochrome/tint with the diagram.\n */\nexport function defaultIconPaint(_prefix?: string): IconPaintMode {\n return \"brand\";\n}\n\n/**\n * Resolve effective paint for an icon id.\n * Author override (`theme` | `brand`) wins; `iconColor` forces theme tint;\n * otherwise brand (keep embedded fills).\n */\nexport function resolveIconPaint(\n rawId: string,\n override?: IconPaintMode | null,\n opts?: { hasIconColor?: boolean },\n): IconPaintMode {\n if (override === \"theme\" || override === \"brand\") return override;\n if (opts?.hasIconColor) return \"theme\";\n const parsed = parseIconId(rawId);\n if (!parsed) return \"brand\";\n return defaultIconPaint(parsed.prefix);\n}\n","import type { IconDisplaySize } from \"./types.ts\";\n\nconst DEFAULT_MAX_ASPECT = 1.75;\n\n/**\n * Fixed-height, aspect-ratio width for an icon glyph.\n * Clamps width to `maxAspect * height` so extreme banners don't blow out cards.\n */\nexport function iconDisplaySize(\n icon: Pick<{ width: number; height: number }, \"width\" | \"height\">,\n height: number,\n options: { maxAspect?: number } = {},\n): IconDisplaySize {\n const maxAspect = options.maxAspect ?? DEFAULT_MAX_ASPECT;\n const aspect = icon.width > 0 && icon.height > 0 ? icon.width / icon.height : 1;\n const width = Math.min(height * aspect, height * maxAspect);\n return { width: Math.max(0, width), height: Math.max(0, height) };\n}\n","import type { IconifyJSON, IconifyIcon } from \"@iconify/types\";\nimport { getIconData, iconToSVG, replaceIDs } from \"@iconify/utils\";\nimport { resolveBuiltinIcon } from \"./builtins.ts\";\nimport { DEFAULT_COLLECTION_LOADERS, type CollectionLoader } from \"./collections.ts\";\nimport { normalizeIconId, parseIconId } from \"./parse-icon-id.ts\";\nimport type { ResolvedIcon } from \"./types.ts\";\n\nconst collectionCache = new Map<string, IconifyJSON>();\nconst collectionLoaders = new Map<string, CollectionLoader>(\n Object.entries(DEFAULT_COLLECTION_LOADERS),\n);\nconst collectionPromises = new Map<string, Promise<IconifyJSON | null>>();\nconst iconCache = new Map<string, ResolvedIcon | null>();\nconst customIcons = new Map<string, ResolvedIcon>();\nconst customCollectionLoaders = new Set<string>();\nlet iconifyApiBaseUrl = \"https://api.iconify.design\";\n\nfunction viewBoxFromIcon(data: IconifyIcon): { viewBox: string; width: number; height: number } {\n const left = data.left ?? 0;\n const top = data.top ?? 0;\n const width = data.width ?? 16;\n const height = data.height ?? 16;\n return {\n viewBox: `${left} ${top} ${width} ${height}`,\n width,\n height,\n };\n}\n\nfunction fromIconify(id: string, data: IconifyIcon): ResolvedIcon {\n const built = iconToSVG(data, { height: \"unset\", width: \"unset\" });\n const meta = viewBoxFromIcon(data);\n // Keep original fills; theme adaptation happens at render when paint is `theme`.\n const body = replaceIDs(built.body);\n return {\n id,\n body,\n viewBox: built.attributes.viewBox ?? meta.viewBox,\n width: meta.width,\n height: meta.height,\n paint: \"fill\",\n };\n}\n\n/** Register a one-off icon (custom architecture SVG body + viewBox). */\nexport function registerIcon(id: string, icon: Omit<ResolvedIcon, \"id\">): void {\n const normalized = normalizeIconId(id);\n const resolved: ResolvedIcon = { id: normalized, ...icon };\n customIcons.set(normalized, resolved);\n iconCache.set(normalized, resolved);\n}\n\n/** Register or replace a whole Iconify JSON collection (eager). */\nexport function registerCollection(prefix: string, data: IconifyJSON): void {\n const key = prefix.toLowerCase();\n collectionCache.set(key, data);\n collectionLoaders.set(key, async () => data);\n // Invalidate cached misses for this prefix.\n for (const [id, value] of iconCache) {\n if (id.startsWith(`${key}:`) && value === null) iconCache.delete(id);\n }\n}\n\n/** Hook a lazy loader for a custom / extra Iconify prefix. */\nexport function registerCollectionLoader(prefix: string, loader: CollectionLoader): void {\n const key = prefix.toLowerCase();\n collectionLoaders.set(key, loader);\n customCollectionLoaders.add(key);\n}\n\n/** Override the Iconify API origin used for per-icon browser loading. */\nexport function setIconifyApiBaseUrl(url: string): void {\n iconifyApiBaseUrl = url.replace(/\\/$/, \"\");\n}\n\nasync function loadCollection(prefix: string): Promise<IconifyJSON | null> {\n const key = prefix.toLowerCase();\n const cached = collectionCache.get(key);\n if (cached) return cached;\n let pending = collectionPromises.get(key);\n if (!pending) {\n const loader = collectionLoaders.get(key);\n if (!loader) {\n collectionPromises.set(key, Promise.resolve(null));\n return null;\n }\n pending = loader()\n .then((data) => {\n collectionCache.set(key, data);\n return data;\n })\n .catch((err) => {\n // Allow retry after fixing deps / Vite resolution.\n collectionPromises.delete(key);\n console.warn(`[kdiagram-icons] failed to load collection \"${key}\"`, err);\n return null;\n });\n collectionPromises.set(key, pending);\n }\n return pending;\n}\n\nfunction resolveFromLoaded(prefix: string, name: string, id: string): ResolvedIcon | null {\n if (prefix === \"builtin\") return resolveBuiltinIcon(name);\n const custom = customIcons.get(id);\n if (custom) return custom;\n const set = collectionCache.get(prefix);\n if (!set) return null;\n const data = getIconData(set, name);\n if (!data) return null;\n return fromIconify(id, data);\n}\n\nfunction mergeCollection(existing: IconifyJSON | undefined, incoming: IconifyJSON): IconifyJSON {\n if (!existing) return incoming;\n return {\n ...existing,\n ...incoming,\n icons: { ...existing.icons, ...incoming.icons },\n aliases: { ...existing.aliases, ...incoming.aliases },\n };\n}\n\nasync function loadRemoteIcons(prefix: string, names: string[]): Promise<void> {\n if (typeof fetch !== \"function\" || names.length === 0) return;\n const params = new URLSearchParams({ icons: names.join(\",\") });\n const response = await fetch(`${iconifyApiBaseUrl}/${encodeURIComponent(prefix)}.json?${params}`);\n if (!response.ok) {\n throw new Error(`Iconify API returned ${response.status} for collection \"${prefix}\"`);\n }\n const data = (await response.json()) as IconifyJSON;\n collectionCache.set(prefix, mergeCollection(collectionCache.get(prefix), data));\n}\n\n/**\n * Synchronous lookup — works for builtins, customs, and anything already preloaded.\n * Returns null if the collection has not been loaded yet or the icon is missing.\n */\nexport function resolveIcon(raw: string): ResolvedIcon | null {\n const parsed = parseIconId(raw);\n if (!parsed) return null;\n if (iconCache.has(parsed.id)) return iconCache.get(parsed.id) ?? null;\n const resolved = resolveFromLoaded(parsed.prefix, parsed.name, parsed.id);\n if (resolved || collectionCache.has(parsed.prefix) || parsed.prefix === \"builtin\") {\n iconCache.set(parsed.id, resolved);\n }\n return resolved;\n}\n\n/** Ensure collections for the given icon ids are loaded, then cache each icon. */\nexport async function preloadIcons(rawIds: Iterable<string>): Promise<void> {\n const parsed = [...rawIds]\n .map((raw) => parseIconId(raw))\n .filter((p): p is NonNullable<typeof p> => p != null);\n\n const grouped = new Map<string, string[]>();\n for (const icon of parsed) {\n if (icon.prefix === \"builtin\" || iconCache.has(icon.id)) continue;\n const names = grouped.get(icon.prefix) ?? [];\n if (!names.includes(icon.name)) names.push(icon.name);\n grouped.set(icon.prefix, names);\n }\n\n const inBrowser = typeof window !== \"undefined\";\n await Promise.all(\n [...grouped].map(async ([prefix, names]) => {\n const loaded = collectionCache.get(prefix);\n const missingNames = loaded ? names.filter((name) => !getIconData(loaded, name)) : names;\n if (missingNames.length === 0) return;\n if (!inBrowser || customCollectionLoaders.has(prefix)) {\n await loadCollection(prefix);\n return;\n }\n await loadRemoteIcons(prefix, missingNames);\n }),\n ).catch((err) => {\n console.warn(\"[kdiagram-icons] failed to preload requested icons\", err);\n });\n\n for (const p of parsed) {\n if (iconCache.has(p.id)) continue;\n iconCache.set(p.id, resolveFromLoaded(p.prefix, p.name, p.id));\n }\n}\n\n/** Warm one or more collections without resolving specific icons. */\nexport async function preloadCollections(prefixes: string[]): Promise<void> {\n const inBrowser = typeof window !== \"undefined\";\n await Promise.all(\n prefixes.map((p) => {\n const prefix = p.toLowerCase();\n if (inBrowser && !customCollectionLoaders.has(prefix)) {\n console.warn(\n `[kdiagram-icons] preloadCollections(\"${prefix}\") requires registerCollectionLoader in the browser; individual icons load on demand`,\n );\n return Promise.resolve(null);\n }\n return loadCollection(prefix);\n }),\n );\n}\n\n/** Collect unique icon ids from a graph-like node list. */\nexport function collectIconIds(nodes: Iterable<{ icon?: string | null | undefined }>): string[] {\n const ids = new Set<string>();\n for (const node of nodes) {\n if (!node.icon || node.icon === \"none\") continue;\n const normalized = normalizeIconId(node.icon);\n if (normalized !== \"none\") ids.add(normalized);\n }\n return [...ids];\n}\n\n/** Test helper — clear caches between cases. */\nexport function resetIconCaches(): void {\n iconCache.clear();\n collectionCache.clear();\n collectionPromises.clear();\n customCollectionLoaders.clear();\n iconifyApiBaseUrl = \"https://api.iconify.design\";\n collectionLoaders.clear();\n for (const [prefix, loader] of Object.entries(DEFAULT_COLLECTION_LOADERS)) {\n collectionLoaders.set(prefix, loader);\n }\n}\n","import { adaptIconBodyColors } from \"./adapt-colors.ts\";\nimport { resolveIconPaint } from \"./paint.ts\";\nimport { resolveIcon } from \"./resolve.ts\";\nimport { iconDisplaySize } from \"./size.ts\";\nimport type { IconPaintMode, IconRenderOptions, ResolvedIcon } from \"./types.ts\";\n\n/** Icon ink: explicit --icon-color, else node stroke / theme fallback. */\nconst ICON_COLOR = \"var(--icon-color, var(--node-stroke, var(--kd-node-stroke)))\";\n\n/**\n * Emit an SVG fragment positioned at (cx, cy) for a resolved icon.\n * Uses nested `<svg>` so Iconify viewBoxes scale cleanly.\n *\n * Brand paint (default): keep embedded fills (logos / simple-icons).\n * Theme paint: Lucide/builtins use `currentColor`; we set CSS `color` and adapt\n * near-black ink so glyphs pick up `--icon-color` or the parent node's stroke.\n */\nexport function renderIconSvg(\n icon: ResolvedIcon,\n cx: number,\n cy: number,\n options: IconRenderOptions = {},\n): string {\n const targetHeight = options.height ?? 20;\n const { width, height } = iconDisplaySize(icon, targetHeight, {\n maxAspect: options.maxAspect,\n });\n const paint: IconPaintMode =\n options.paint ?? resolveIconPaint(icon.id, undefined, { hasIconColor: Boolean(options.color) });\n const className = options.className ?? \"flow-node-icon-mark\";\n const body = paint === \"theme\" ? adaptIconBodyColors(icon.body) : icon.body;\n const colorValue = options.color ?? (paint === \"theme\" ? ICON_COLOR : \"\");\n const colorAttrs = colorValue ? `color=\"${colorValue}\"` : \"\";\n const x = cx - width / 2;\n const y = cy - height / 2;\n const colorPart = colorAttrs ? ` ${colorAttrs}` : \"\";\n return `<g class=\"${className}\" transform=\"translate(${x}, ${y})\"${colorPart}><svg width=\"${width}\" height=\"${height}\" viewBox=\"${icon.viewBox}\" overflow=\"visible\" aria-hidden=\"true\">${body}</svg></g>`;\n}\n\n/** Resolve + render; returns empty string when the icon cannot be resolved. */\nexport function renderIconById(\n raw: string,\n cx: number,\n cy: number,\n options: IconRenderOptions = {},\n): string {\n const icon = resolveIcon(raw);\n if (!icon) return \"\";\n return renderIconSvg(icon, cx, cy, {\n ...options,\n paint:\n options.paint ?? resolveIconPaint(raw, undefined, { hasIconColor: Boolean(options.color) }),\n });\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@kekonic/diagrams-icons",
3
+ "version": "1.0.0-rc.4",
4
+ "description": "Built-in and Iconify icon resolution, lazy loading, paint adaptation, and SVG rendering for KDiagram.",
5
+ "homepage": "https://github.com/kekonic/diagrams#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/kekonic/diagrams/issues"
8
+ },
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/kekonic/diagrams.git",
13
+ "directory": "packages/diagrams-icons"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "type": "module",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.mts",
22
+ "import": "./dist/index.mjs"
23
+ }
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "dependencies": {
29
+ "@iconify-json/carbon": "^1.2.25",
30
+ "@iconify-json/logos": "^1.2.12",
31
+ "@iconify-json/lucide": "^1.2.68",
32
+ "@iconify-json/mdi": "^1.2.3",
33
+ "@iconify-json/simple-icons": "^1.2.53",
34
+ "@iconify/types": "^2.0.0",
35
+ "@iconify/utils": "^3.0.2"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^26",
39
+ "typescript": "^5",
40
+ "vitest": "^4"
41
+ },
42
+ "engines": {
43
+ "node": ">=22.18.0"
44
+ },
45
+ "scripts": {
46
+ "build": "vp pack",
47
+ "test": "vp test"
48
+ }
49
+ }