@sigx/lynx-icons 0.4.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Andreas Ekdahl
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,132 @@
1
+ # @sigx/lynx-icons
2
+
3
+ `<Icon set name />` for sigx-lynx, with build-time tree-shaking so only the glyphs you actually render ship in the bundle. Pairs with adapter packages — [`@sigx/lynx-icons-fa-free`](../lynx-icons-fa-free) for Font Awesome Free, [`@sigx/lynx-icons-lucide`](../lynx-icons-lucide) for Lucide — and is wired by [`@sigx/lynx-plugin`](../lynx-plugin)'s icons slice.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @sigx/lynx-icons @sigx/lynx-icons-fa-free
9
+ pnpm add @fortawesome/fontawesome-free @fortawesome/free-solid-svg-icons
10
+ ```
11
+
12
+ ```ts
13
+ // signalx.config.ts
14
+ import { defineLynxConfig } from '@sigx/lynx-cli/config';
15
+
16
+ export default defineLynxConfig({
17
+ iconSets: [
18
+ { id: 'fa', source: '@sigx/lynx-icons-fa-free', styles: ['solid'] },
19
+ ],
20
+ });
21
+ ```
22
+
23
+ `pnpm dev` / `sigx build` does the rest — `@sigx/lynx-plugin` scans your `.tsx` for `<Icon set= name=>` usages, asks the adapter for each glyph, and writes generated codepoint / SVG maps into `node_modules/.cache/sigx-lynx-icons/`. Unreferenced glyphs are never imported.
24
+
25
+ ## Usage
26
+
27
+ ```tsx
28
+ import { Icon } from '@sigx/lynx-icons';
29
+
30
+ <Icon set="fa" name="user" />
31
+ <Icon set="fa" name="house" size={20} color="#0D9488" />
32
+ <Icon set="lucide" name="search" size={16} />
33
+ ```
34
+
35
+ | Prop | Type | Default | Notes |
36
+ | --- | --- | --- | --- |
37
+ | `set` | `string` | — | Must match an `iconSets[].id` in `signalx.config.ts` (or a `defineIconSet({ id, … })` call at runtime). |
38
+ | `name` | `string` | — | Glyph name in the set's canonical kebab-case (`chevron-right`, not `chevronRight` / `ChevronRight`). |
39
+ | `size` | `number` | `16` | Both width and height, in CSS px. |
40
+ | `color` | `string` | `'currentColor'` | Substituted into the SVG template at render time (data: URIs don't reliably inherit `currentColor`). |
41
+ | `class` | `string` | — | Forwarded to the host element (`<text>` for font mode, `<image>` for SVG mode, `<view>` for missing glyphs). |
42
+
43
+ The component renders `<text style={{ fontFamily: setId, … }}>{codepoint}</text>` when a codepoint is available (font mode), or an `<image>` wrapping an SVG data URI otherwise (SVG mode). When neither is registered, an empty `<view>` of the same size renders so layout doesn't jump.
44
+
45
+ ## Dynamic names
46
+
47
+ The build-time scanner only sees literal `name="…"` strings. Computed names — `<Icon name={state.icon} />` — fall through to the missing-glyph placeholder unless you tell the plugin which glyphs to include.
48
+
49
+ **Known list — enumerate the names**:
50
+
51
+ ```ts
52
+ iconSets: [
53
+ {
54
+ id: 'fa',
55
+ source: '@sigx/lynx-icons-fa-free',
56
+ styles: ['solid'],
57
+ include: ['user', 'house', 'gear'], // force-include for dynamic <Icon name={…}>
58
+ },
59
+ ],
60
+ ```
61
+
62
+ **Unknown list (JSON-driven UIs, server-driven content, etc.) — `include: ['*']`**:
63
+
64
+ ```ts
65
+ iconSets: [
66
+ {
67
+ id: 'fa',
68
+ source: '@sigx/lynx-icons-fa-free',
69
+ styles: ['solid'],
70
+ include: ['*'], // ship the full FA-solid catalog
71
+ },
72
+ ],
73
+ ```
74
+
75
+ Trade-off: shipping a full set noticeably grows the bundle (the showcase goes from ~336 kB → ~2.65 MB when FA solid's ~1 900 glyphs are bundled). Build output prints the exact glyph count so you can audit it:
76
+
77
+ ```
78
+ [@sigx/lynx-plugin] icons: fa bundling 1956 glyphs (include: ['*'])
79
+ ```
80
+
81
+ Use it only on the sets that genuinely need dynamic names — mix per set with the tree-shaken default for everything else. Coming in v1.1: font mode swaps the SVG-per-glyph payload for a single subsetted TTF, which is dramatically smaller for full-catalog scenarios.
82
+
83
+ ## Custom sets
84
+
85
+ For one-off in-app icons that don't warrant a full adapter package, use `defineIconSet`:
86
+
87
+ ```ts
88
+ import { defineIconSet } from '@sigx/lynx-icons';
89
+
90
+ defineIconSet({
91
+ id: 'brand',
92
+ glyphs: {
93
+ logo: { svg: { svg: '<svg viewBox="0 0 24 24" fill="__COLOR__"><path d="M3 12L12 3l9 9-9 9z"/></svg>' } },
94
+ },
95
+ });
96
+
97
+ // Anywhere:
98
+ <Icon set="brand" name="logo" />
99
+ ```
100
+
101
+ `__COLOR__` placeholders in the SVG string get replaced with the user-supplied `color` (or `currentColor`) at render time.
102
+
103
+ ## Writing your own adapter
104
+
105
+ Adapter packages are plain Node modules with a default export matching this contract:
106
+
107
+ ```ts
108
+ import type { IconAdapter } from '@sigx/lynx-icons';
109
+
110
+ const adapter: IconAdapter = {
111
+ styles: ['solid'],
112
+ getGlyph(style, name) {
113
+ // Return { codepoint?, svg } or null
114
+ },
115
+ getFontPath(style) {
116
+ // Absolute path to a TTF for font-mode subsetting, or null.
117
+ },
118
+ };
119
+ export default adapter;
120
+ ```
121
+
122
+ The plugin dynamically `import()`s the adapter from the consumer's `node_modules` at build start, then calls `getGlyph` for every scanned `<Icon set= name=>` usage. `getFontPath` is reserved for the v1.1 font-subsetting pipeline and can return `null` for SVG-only sets.
123
+
124
+ ## v1 limitations
125
+
126
+ - **Font mode** (build-time TTF subsetting + base64-inlined `@font-face`) ships in v1.1. v1 renders every glyph as an inline SVG `<image>`. The plumbing for `mode: 'font'` is in the schema but currently no-ops.
127
+ - **HMR** for newly-added icon names requires a `pnpm dev` restart. The v1 scanner is a one-shot regex pass at plugin start; v1.1 replaces it with a real SWC-AST Rspack loader.
128
+ - **FA Pro** and **Iconify** adapters are v1.1 follow-ups.
129
+
130
+ ## Reference app
131
+
132
+ `examples/showcase/src/screens/Settings.tsx` shows fa-solid, fa-brands, and lucide icons rendered side by side. The generated cache lives at `examples/showcase/node_modules/.cache/sigx-lynx-icons/` after `pnpm build`.
package/dist/Icon.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { type Define } from '@sigx/lynx';
2
+ import '@sigx/lynx-icons/__font-face.css';
3
+ export type IconProps = Define.Prop<'set', string, true> & Define.Prop<'name', string, true> & Define.Prop<'size', number, false> & Define.Prop<'color', string, false> & Define.Prop<'class', string, false>;
4
+ export declare function sanitizeColor(color: string): string;
5
+ export declare function inlineSvg(template: string, color: string): string;
6
+ /**
7
+ * Render a glyph from a registered icon set.
8
+ *
9
+ * Sets are declared in `signalx.config.ts` via `iconSets: [...]` (build-time,
10
+ * tree-shaken) or via `defineIconSet({ id, glyphs })` (runtime, ad-hoc).
11
+ *
12
+ * Resolution order: **SVG first**, then codepoint, then missing placeholder.
13
+ *
14
+ * - SVG-mode sets render with Lynx's native `<svg content={...}>` element
15
+ * (the engine parses the inline XML; no JSX children, no data: URIs).
16
+ * - Font-mode sets fall back to a single character inside a `<text>` element
17
+ * with a matching `font-family`. The plugin only emits codepoints when the
18
+ * matching `@font-face` has also been registered (v1.1+); v1 always hits
19
+ * the SVG branch.
20
+ * - Missing glyphs render an empty `<view>` of the same size so layout
21
+ * doesn't jump.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * <Icon set="fa" name="user" size={20} color="#333" />
26
+ * <Icon set="lucide" name="search" size={16} />
27
+ * ```
28
+ */
29
+ export declare const Icon: import("@sigx/runtime-core").ComponentFactory<IconProps, void, {}>;
@@ -0,0 +1,28 @@
1
+ //#region src/registry.ts
2
+ var e = {}, t = {};
3
+ function n(n) {
4
+ let r = {}, i = {};
5
+ for (let [e, t] of Object.entries(n.glyphs)) t.codepoint !== void 0 && (r[e] = t.codepoint), t.svg && (i[e] = t.svg);
6
+ Object.keys(r).length > 0 && (e[n.id] = r), Object.keys(i).length > 0 && (t[n.id] = i);
7
+ }
8
+ function r(t, n, r) {
9
+ return t[n]?.[r] ?? e[n]?.[r];
10
+ }
11
+ function i(e, n, r) {
12
+ return e[n]?.[r] ?? t[n]?.[r];
13
+ }
14
+ function a(e, t, n, a) {
15
+ let o = i(t, n, a);
16
+ if (o) return { svg: o };
17
+ let s = r(e, n, a);
18
+ if (s !== void 0) return { codepoint: s };
19
+ }
20
+ //#endregion
21
+ //#region src/defineIconSet.ts
22
+ function o(e) {
23
+ return n(e), e;
24
+ }
25
+ //#endregion
26
+ export { a as n, o as t };
27
+
28
+ //# sourceMappingURL=defineIconSet-BIFjZq08.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineIconSet-BIFjZq08.js","names":[],"sources":["../src/registry.ts","../src/defineIconSet.ts"],"sourcesContent":["import type { CodepointMap, GlyphSvg, IconSetDef, SvgMap } from './types';\n\nconst runtimeCodepoints: CodepointMap = {};\nconst runtimeSvgs: SvgMap = {};\n\nexport function registerIconSet(def: IconSetDef): void {\n const codepoints: Record<string, number> = {};\n const svgs: Record<string, GlyphSvg> = {};\n for (const [name, g] of Object.entries(def.glyphs)) {\n if (g.codepoint !== undefined) codepoints[name] = g.codepoint;\n if (g.svg) svgs[name] = g.svg;\n }\n if (Object.keys(codepoints).length > 0) runtimeCodepoints[def.id] = codepoints;\n if (Object.keys(svgs).length > 0) runtimeSvgs[def.id] = svgs;\n}\n\nexport function lookupCodepoint(\n buildTime: CodepointMap,\n set: string,\n name: string,\n): number | undefined {\n return buildTime[set]?.[name] ?? runtimeCodepoints[set]?.[name];\n}\n\nexport function lookupSvg(\n buildTime: SvgMap,\n set: string,\n name: string,\n): GlyphSvg | undefined {\n return buildTime[set]?.[name] ?? runtimeSvgs[set]?.[name];\n}\n\nexport function lookupGlyph(\n buildTimeCodepoints: CodepointMap,\n buildTimeSvgs: SvgMap,\n set: string,\n name: string,\n): { codepoint?: number; svg?: GlyphSvg } | undefined {\n const svg = lookupSvg(buildTimeSvgs, set, name);\n if (svg) return { svg };\n\n const codepoint = lookupCodepoint(buildTimeCodepoints, set, name);\n if (codepoint !== undefined) return { codepoint };\n\n return undefined;\n}\n","import { registerIconSet } from './registry';\nimport type { IconSetDef } from './types';\n\n/**\n * Register a custom icon set at module load time.\n *\n * Build-time sets declared in `signalx.config.ts` are auto-detected and\n * tree-shaken; `defineIconSet` is the escape hatch for ad-hoc sets defined\n * directly in app code (e.g. a small private set used in one screen).\n *\n * @example\n * ```ts\n * defineIconSet({\n * id: 'brand',\n * glyphs: {\n * logo: {\n * svg: {\n * svg: '<svg viewBox=\"0 0 24 24\" fill=\"__COLOR__\"><path d=\"M3 12L12 3l9 9-9 9z\"/></svg>',\n * },\n * },\n * },\n * });\n * ```\n *\n * The inner `svg` value is raw SVG markup. `__COLOR__` placeholders in that\n * markup get substituted with the user's `color` prop at render time.\n */\nexport function defineIconSet(def: IconSetDef): IconSetDef {\n registerIconSet(def);\n return def;\n}\n\nexport type { IconSetDef } from './types';\n"],"mappings":";AAEA,IAAM,IAAkC,EAAE,EACpC,IAAsB,EAAE;AAE9B,SAAgB,EAAgB,GAAuB;CACnD,IAAM,IAAqC,EAAE,EACvC,IAAiC,EAAE;CACzC,KAAK,IAAM,CAAC,GAAM,MAAM,OAAO,QAAQ,EAAI,OAAO,EAE9C,AADI,EAAE,cAAc,KAAA,MAAW,EAAW,KAAQ,EAAE,YAChD,EAAE,QAAK,EAAK,KAAQ,EAAE;CAG9B,AADI,OAAO,KAAK,EAAW,CAAC,SAAS,MAAG,EAAkB,EAAI,MAAM,IAChE,OAAO,KAAK,EAAK,CAAC,SAAS,MAAG,EAAY,EAAI,MAAM;;AAG5D,SAAgB,EACZ,GACA,GACA,GACkB;CAClB,OAAO,EAAU,KAAO,MAAS,EAAkB,KAAO;;AAG9D,SAAgB,EACZ,GACA,GACA,GACoB;CACpB,OAAO,EAAU,KAAO,MAAS,EAAY,KAAO;;AAGxD,SAAgB,EACZ,GACA,GACA,GACA,GACkD;CAClD,IAAM,IAAM,EAAU,GAAe,GAAK,EAAK;CAC/C,IAAI,GAAK,OAAO,EAAE,QAAK;CAEvB,IAAM,IAAY,EAAgB,GAAqB,GAAK,EAAK;CACjE,IAAI,MAAc,KAAA,GAAW,OAAO,EAAE,cAAW;;;;ACfrD,SAAgB,EAAc,GAA6B;CAEvD,OADA,EAAgB,EAAI,EACb"}
@@ -0,0 +1,27 @@
1
+ import type { IconSetDef } from './types';
2
+ /**
3
+ * Register a custom icon set at module load time.
4
+ *
5
+ * Build-time sets declared in `signalx.config.ts` are auto-detected and
6
+ * tree-shaken; `defineIconSet` is the escape hatch for ad-hoc sets defined
7
+ * directly in app code (e.g. a small private set used in one screen).
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * defineIconSet({
12
+ * id: 'brand',
13
+ * glyphs: {
14
+ * logo: {
15
+ * svg: {
16
+ * svg: '<svg viewBox="0 0 24 24" fill="__COLOR__"><path d="M3 12L12 3l9 9-9 9z"/></svg>',
17
+ * },
18
+ * },
19
+ * },
20
+ * });
21
+ * ```
22
+ *
23
+ * The inner `svg` value is raw SVG markup. `__COLOR__` placeholders in that
24
+ * markup get substituted with the user's `color` prop at render time.
25
+ */
26
+ export declare function defineIconSet(def: IconSetDef): IconSetDef;
27
+ export type { IconSetDef } from './types';
@@ -0,0 +1,2 @@
1
+ import { t as e } from "./defineIconSet-BIFjZq08.js";
2
+ export { e as defineIconSet };
@@ -0,0 +1,4 @@
1
+ export { Icon } from './Icon';
2
+ export type { IconProps } from './Icon';
3
+ export { defineIconSet } from './defineIconSet';
4
+ export type { GlyphData, GlyphSvg, IconAdapter, IconSetDef } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,50 @@
1
+ import { n as e, t } from "./defineIconSet-BIFjZq08.js";
2
+ import { component as n } from "@sigx/lynx";
3
+ import { codepoints as r } from "@sigx/lynx-icons/__codepoints";
4
+ import { svgs as i } from "@sigx/lynx-icons/__svgs";
5
+ import "@sigx/lynx-icons/__font-face.css";
6
+ import { jsx as a } from "@sigx/lynx/jsx-runtime";
7
+ //#region src/Icon.tsx
8
+ var o = /^(?:currentColor|[a-zA-Z]+|#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla)\(\s*[\d.,%\s]+\))$/;
9
+ function s(e) {
10
+ return o.test(e) ? e : "currentColor";
11
+ }
12
+ function c(e, t) {
13
+ return e.replace(/__COLOR__/g, s(t));
14
+ }
15
+ var l = n(({ props: t }) => () => {
16
+ let n = t.size ?? 16, o = t.set, s = t.name, l = {
17
+ width: n,
18
+ height: n
19
+ }, u = e(r, i, o, s);
20
+ if (u?.svg) {
21
+ let e = t.color ?? "currentColor";
22
+ return /* @__PURE__ */ a("svg", {
23
+ content: c(u.svg.svg, e),
24
+ class: t.class,
25
+ style: l
26
+ });
27
+ }
28
+ if (u?.codepoint !== void 0) {
29
+ let e = {
30
+ fontFamily: o,
31
+ fontSize: n,
32
+ lineHeight: `${n}px`,
33
+ width: n,
34
+ height: n
35
+ };
36
+ return t.color && (e.color = t.color), /* @__PURE__ */ a("text", {
37
+ class: t.class,
38
+ style: e,
39
+ children: String.fromCodePoint(u.codepoint)
40
+ });
41
+ }
42
+ return /* @__PURE__ */ a("view", {
43
+ class: t.class,
44
+ style: l
45
+ });
46
+ });
47
+ //#endregion
48
+ export { l as Icon, t as defineIconSet };
49
+
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/Icon.tsx"],"sourcesContent":["import { component, type Define } from '@sigx/lynx';\nimport { codepoints } from '@sigx/lynx-icons/__codepoints';\nimport { svgs } from '@sigx/lynx-icons/__svgs';\nimport '@sigx/lynx-icons/__font-face.css';\nimport { lookupGlyph } from './registry';\n\nexport type IconProps =\n & Define.Prop<'set', string, true>\n & Define.Prop<'name', string, true>\n & Define.Prop<'size', number, false>\n & Define.Prop<'color', string, false>\n & Define.Prop<'class', string, false>;\n\n/**\n * Match a conservative subset of valid CSS color formats:\n * - `currentColor` and named colors (`red`, `dodgerblue`, `transparent` …)\n * - `#rgb` / `#rgba` / `#rrggbb` / `#rrggbbaa`\n * - `rgb(...)` / `rgba(...)` / `hsl(...)` / `hsla(...)` with digits, dots,\n * commas, percent signs, and whitespace inside the parens\n *\n * Anything else (quotes, angle brackets, ampersands, arbitrary HTML) falls\n * back to `currentColor`. The `color` prop ends up substituted directly into\n * the SVG markup that we hand to Lynx's `<svg content={…}>` parser, so a\n * value like `red\" stroke=\"…` would break out of the `fill=\"\"` attribute.\n * Allow-list, not escape-list — keeps the surface area provably small.\n */\nconst SAFE_COLOR_RE =\n /^(?:currentColor|[a-zA-Z]+|#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla)\\(\\s*[\\d.,%\\s]+\\))$/;\n\nexport function sanitizeColor(color: string): string {\n return SAFE_COLOR_RE.test(color) ? color : 'currentColor';\n}\n\nexport function inlineSvg(template: string, color: string): string {\n return template.replace(/__COLOR__/g, sanitizeColor(color));\n}\n\n/**\n * Render a glyph from a registered icon set.\n *\n * Sets are declared in `signalx.config.ts` via `iconSets: [...]` (build-time,\n * tree-shaken) or via `defineIconSet({ id, glyphs })` (runtime, ad-hoc).\n *\n * Resolution order: **SVG first**, then codepoint, then missing placeholder.\n *\n * - SVG-mode sets render with Lynx's native `<svg content={...}>` element\n * (the engine parses the inline XML; no JSX children, no data: URIs).\n * - Font-mode sets fall back to a single character inside a `<text>` element\n * with a matching `font-family`. The plugin only emits codepoints when the\n * matching `@font-face` has also been registered (v1.1+); v1 always hits\n * the SVG branch.\n * - Missing glyphs render an empty `<view>` of the same size so layout\n * doesn't jump.\n *\n * @example\n * ```tsx\n * <Icon set=\"fa\" name=\"user\" size={20} color=\"#333\" />\n * <Icon set=\"lucide\" name=\"search\" size={16} />\n * ```\n */\nexport const Icon = component<IconProps>(({ props }) => {\n return () => {\n const size = props.size ?? 16;\n const set = props.set;\n const name = props.name;\n const sizeStyle = { width: size, height: size } as const;\n\n const glyph = lookupGlyph(codepoints, svgs, set, name);\n if (glyph?.svg) {\n const color = props.color ?? 'currentColor';\n return (\n <svg\n content={inlineSvg(glyph.svg.svg, color)}\n class={props.class}\n style={sizeStyle}\n />\n );\n }\n\n if (glyph?.codepoint !== undefined) {\n const textStyle: Record<string, string | number> = {\n fontFamily: set,\n fontSize: size,\n lineHeight: `${size}px`,\n width: size,\n height: size,\n };\n if (props.color) textStyle.color = props.color;\n return (\n <text class={props.class} style={textStyle}>\n {String.fromCodePoint(glyph.codepoint)}\n </text>\n );\n }\n\n return <view class={props.class} style={sizeStyle} />;\n };\n});\n"],"mappings":";;;;;;;AA0BA,IAAM,IACF;AAEJ,SAAgB,EAAc,GAAuB;CACjD,OAAO,EAAc,KAAK,EAAM,GAAG,IAAQ;;AAG/C,SAAgB,EAAU,GAAkB,GAAuB;CAC/D,OAAO,EAAS,QAAQ,cAAc,EAAc,EAAM,CAAC;;AA0B/D,IAAa,IAAO,GAAsB,EAAE,qBAC3B;CACT,IAAM,IAAO,EAAM,QAAQ,IACrB,IAAM,EAAM,KACZ,IAAO,EAAM,MACb,IAAY;EAAE,OAAO;EAAM,QAAQ;EAAM,EAEzC,IAAQ,EAAY,GAAY,GAAM,GAAK,EAAK;CACtD,IAAI,GAAO,KAAK;EACZ,IAAM,IAAQ,EAAM,SAAS;EAC7B,OACI,kBAAC,OAAD;GACI,SAAS,EAAU,EAAM,IAAI,KAAK,EAAM;GACxC,OAAO,EAAM;GACb,OAAO;GACT,CAAA;;CAIV,IAAI,GAAO,cAAc,KAAA,GAAW;EAChC,IAAM,IAA6C;GAC/C,YAAY;GACZ,UAAU;GACV,YAAY,GAAG,EAAK;GACpB,OAAO;GACP,QAAQ;GACX;EAED,OADI,EAAM,UAAO,EAAU,QAAQ,EAAM,QAErC,kBAAC,QAAD;GAAM,OAAO,EAAM;GAAO,OAAO;aAC5B,OAAO,cAAc,EAAM,UAAU;GACnC,CAAA;;CAIf,OAAO,kBAAC,QAAD;EAAM,OAAO,EAAM;EAAO,OAAO;EAAa,CAAA;EAE3D"}
@@ -0,0 +1,8 @@
1
+ import type { CodepointMap, GlyphSvg, IconSetDef, SvgMap } from './types';
2
+ export declare function registerIconSet(def: IconSetDef): void;
3
+ export declare function lookupCodepoint(buildTime: CodepointMap, set: string, name: string): number | undefined;
4
+ export declare function lookupSvg(buildTime: SvgMap, set: string, name: string): GlyphSvg | undefined;
5
+ export declare function lookupGlyph(buildTimeCodepoints: CodepointMap, buildTimeSvgs: SvgMap, set: string, name: string): {
6
+ codepoint?: number;
7
+ svg?: GlyphSvg;
8
+ } | undefined;
@@ -0,0 +1,9 @@
1
+ import type { CodepointMap } from '../types';
2
+ /**
3
+ * Empty stub. The @sigx/lynx-plugin icons slice replaces this module via an
4
+ * Rspack alias at build time, populating it with the codepoints used by the
5
+ * app's `<Icon>` calls. Without the plugin, no built-in sets are registered
6
+ * and all icons fall back to `defineIconSet`-registered sets (if any) or the
7
+ * missing-glyph placeholder.
8
+ */
9
+ export declare const codepoints: CodepointMap;
@@ -0,0 +1,6 @@
1
+ //#region src/stubs/codepoints.ts
2
+ var e = {};
3
+ //#endregion
4
+ export { e as codepoints };
5
+
6
+ //# sourceMappingURL=codepoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codepoints.js","names":[],"sources":["../../src/stubs/codepoints.ts"],"sourcesContent":["import type { CodepointMap } from '../types';\n\n/**\n * Empty stub. The @sigx/lynx-plugin icons slice replaces this module via an\n * Rspack alias at build time, populating it with the codepoints used by the\n * app's `<Icon>` calls. Without the plugin, no built-in sets are registered\n * and all icons fall back to `defineIconSet`-registered sets (if any) or the\n * missing-glyph placeholder.\n */\nexport const codepoints: CodepointMap = {};\n"],"mappings":";AASA,IAAa,IAA2B,EAAE"}
@@ -0,0 +1,10 @@
1
+ import type { CodepointMap } from '../types';
2
+
3
+ /**
4
+ * Empty stub. The @sigx/lynx-plugin icons slice replaces this module via an
5
+ * Rspack alias at build time, populating it with the codepoints used by the
6
+ * app's `<Icon>` calls. Without the plugin, no built-in sets are registered
7
+ * and all icons fall back to `defineIconSet`-registered sets (if any) or the
8
+ * missing-glyph placeholder.
9
+ */
10
+ export const codepoints: CodepointMap = {};
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Empty stub. The @sigx/lynx-plugin icons slice replaces this module via an
3
+ * Rspack alias at build time, replacing it with @font-face declarations
4
+ * (base64-inlined subset TTFs) for every icon set in `signalx.config.ts`
5
+ * that uses font mode.
6
+ */
@@ -0,0 +1,7 @@
1
+ // Type stub for the side-effect CSS import:
2
+ //
3
+ // import '@sigx/lynx-icons/__font-face.css';
4
+ //
5
+ // The actual CSS is replaced by @sigx/lynx-plugin's icons slice at build
6
+ // time with @font-face declarations for every font-mode icon set.
7
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { SvgMap } from '../types';
2
+ /**
3
+ * Empty stub. The @sigx/lynx-plugin icons slice replaces this module via an
4
+ * Rspack alias at build time, populating it with the SVG path data for the
5
+ * icons used by the app's `<Icon>` calls.
6
+ */
7
+ export declare const svgs: SvgMap;
@@ -0,0 +1,6 @@
1
+ //#region src/stubs/svgs.ts
2
+ var e = {};
3
+ //#endregion
4
+ export { e as svgs };
5
+
6
+ //# sourceMappingURL=svgs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svgs.js","names":[],"sources":["../../src/stubs/svgs.ts"],"sourcesContent":["import type { SvgMap } from '../types';\n\n/**\n * Empty stub. The @sigx/lynx-plugin icons slice replaces this module via an\n * Rspack alias at build time, populating it with the SVG path data for the\n * icons used by the app's `<Icon>` calls.\n */\nexport const svgs: SvgMap = {};\n"],"mappings":";AAOA,IAAa,IAAe,EAAE"}
@@ -0,0 +1,8 @@
1
+ import type { SvgMap } from '../types';
2
+
3
+ /**
4
+ * Empty stub. The @sigx/lynx-plugin icons slice replaces this module via an
5
+ * Rspack alias at build time, populating it with the SVG path data for the
6
+ * icons used by the app's `<Icon>` calls.
7
+ */
8
+ export const svgs: SvgMap = {};
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Per-glyph vector data. Used by SVG-mode rendering.
3
+ *
4
+ * `svg` is a complete `<svg …>…</svg>` string with `__COLOR__` placeholders
5
+ * wherever the user-supplied `color` should be substituted. This lets each
6
+ * adapter ship the native shape of its icons (FA = filled paths, lucide =
7
+ * stroked paths) without the core component needing to know per-adapter
8
+ * styling.
9
+ *
10
+ * @example FA solid: `<svg viewBox="0 0 448 512" fill="__COLOR__"><path d="..."/></svg>`
11
+ * @example lucide: `<svg viewBox="0 0 24 24" fill="none" stroke="__COLOR__" stroke-width="2" ...><path .../><circle .../></svg>`
12
+ */
13
+ export interface GlyphSvg {
14
+ svg: string;
15
+ }
16
+ /**
17
+ * Build-time virtual module shape: glyph name → unicode codepoint, keyed by set id.
18
+ * Populated by the @sigx/lynx-plugin icons slice; the stub ships an empty record
19
+ * so the core package builds standalone.
20
+ */
21
+ export type CodepointMap = Record<string, Record<string, number>>;
22
+ /**
23
+ * Build-time virtual module shape: glyph name → SVG data, keyed by set id.
24
+ * Populated by the @sigx/lynx-plugin icons slice.
25
+ */
26
+ export type SvgMap = Record<string, Record<string, GlyphSvg>>;
27
+ /**
28
+ * Runtime-registered icon set, for ad-hoc / app-local sets created via
29
+ * `defineIconSet`. Build-time sets bypass this and live in the virtual modules.
30
+ */
31
+ export interface IconSetDef {
32
+ id: string;
33
+ glyphs: Record<string, {
34
+ codepoint?: number;
35
+ svg?: GlyphSvg;
36
+ }>;
37
+ fontFamily?: string;
38
+ }
39
+ /**
40
+ * Glyph data exposed by an adapter package at build time.
41
+ * `codepoint` enables font-mode subsetting; `svg` enables SVG-mode rendering.
42
+ * Adapters should populate both when possible (FA does), or only `svg` for
43
+ * SVG-only sets (e.g. lucide).
44
+ *
45
+ * `svg` is a complete `<svg>…</svg>` string with `__COLOR__` placeholders
46
+ * for the user's color; see {@link GlyphSvg} for the rendering contract.
47
+ */
48
+ export interface GlyphData {
49
+ codepoint?: number;
50
+ svg: string;
51
+ }
52
+ /**
53
+ * Build-time adapter contract. Implemented by packages like
54
+ * `@sigx/lynx-icons-fa-free` and consumed by `@sigx/lynx-plugin`'s icons slice
55
+ * to resolve glyph data for every `<Icon set="..." name="..." />` usage.
56
+ *
57
+ * Adapters should expose a default export of this shape from their main entry.
58
+ */
59
+ export interface IconAdapter {
60
+ /** Style variants the adapter supports. e.g. ['solid', 'regular', 'brands'] for FA, [''] (single empty style) for lucide. */
61
+ styles: string[];
62
+ /** Resolve a glyph by style + name. Return `null` when the glyph is unknown. */
63
+ getGlyph(style: string, name: string): GlyphData | null;
64
+ /**
65
+ * Absolute filesystem path to the source TTF for `style`, or null when the
66
+ * adapter only supports SVG mode. The plugin reads this file and runs it
67
+ * through `subset-font` to produce per-app subsets.
68
+ */
69
+ getFontPath(style: string): string | null;
70
+ /**
71
+ * Enumerate every glyph name the adapter can resolve for `style`, in
72
+ * the same kebab-case form `getGlyph` accepts. Used by the plugin when
73
+ * a project sets `iconSets[].include: ['*']` (e.g. JSON-driven UIs where
74
+ * the names aren't known at build time) — the plugin pulls the full
75
+ * catalog into the bundle instead of relying on the JSX scanner.
76
+ */
77
+ listGlyphs(style: string): string[];
78
+ }
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@sigx/lynx-icons",
3
+ "version": "0.4.0",
4
+ "description": "Icon component and registry for sigx-lynx — works with adapter packages like @sigx/lynx-icons-fa-free and @sigx/lynx-icons-lucide.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./__codepoints": {
15
+ "types": "./dist/stubs/codepoints.d.ts",
16
+ "import": "./dist/stubs/codepoints.js",
17
+ "default": "./dist/stubs/codepoints.js"
18
+ },
19
+ "./__svgs": {
20
+ "types": "./dist/stubs/svgs.d.ts",
21
+ "import": "./dist/stubs/svgs.js",
22
+ "default": "./dist/stubs/svgs.js"
23
+ },
24
+ "./__font-face.css": {
25
+ "types": "./dist/stubs/font-face.css.d.ts",
26
+ "default": "./dist/stubs/font-face.css"
27
+ },
28
+ "./signalx-module.json": "./signalx-module.json",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "signalx-module.json",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "peerDependencies": {
38
+ "@sigx/lynx": "^0.4.0"
39
+ },
40
+ "devDependencies": {
41
+ "@sigx/vite": "^0.4.3",
42
+ "typescript": "^6.0.3",
43
+ "@sigx/lynx": "^0.4.0"
44
+ },
45
+ "author": "Andreas Ekdahl",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/signalxjs/lynx.git",
50
+ "directory": "packages/lynx-icons"
51
+ },
52
+ "homepage": "https://github.com/signalxjs/lynx/tree/main/packages/lynx-icons",
53
+ "bugs": {
54
+ "url": "https://github.com/signalxjs/lynx/issues"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "keywords": [
60
+ "signalx",
61
+ "sigx",
62
+ "lynx",
63
+ "icon",
64
+ "icons",
65
+ "font-awesome",
66
+ "lucide"
67
+ ],
68
+ "scripts": {
69
+ "build": "vite build && tsgo --emitDeclarationOnly && node ../../scripts/copy-assets.mjs src/stubs dist/stubs",
70
+ "dev": "vite build --watch"
71
+ }
72
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "Icons",
3
+ "package": "@sigx/lynx-icons",
4
+ "description": "Registers Lynx's <svg> element (via XElement/SVG + ServalSVG) so <Icon> can render in SVG mode.",
5
+ "platforms": ["android", "ios"],
6
+ "android": {
7
+ "dependencies": ["org.lynxsdk.lynx:xelement-svg:3.7.0"]
8
+ },
9
+ "ios": {
10
+ "pods": {
11
+ "XElement/SVG": "3.7.0"
12
+ }
13
+ }
14
+ }