@yahoo/uds-create-config 4.10.2 → 4.10.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.
Files changed (103) hide show
  1. package/dist/configs/CanvasConfig.d.ts +165 -77
  2. package/dist/configs/documentation.d.ts +1 -1
  3. package/dist/configs/react-native-system.d.ts +111 -52
  4. package/dist/configs/system.d.ts +121 -87
  5. package/dist/configs/system.js +4 -0
  6. package/dist/entities/documentation/Documentation.d.ts +1 -1
  7. package/dist/entities/native/NativeModifier.d.ts +1 -1
  8. package/dist/entities/native/NativeSettings.d.ts +1 -1
  9. package/dist/entities/native/NativeStyleProperty.d.ts +2 -2
  10. package/dist/entities/native/NativeToken.d.ts +4 -3
  11. package/dist/entities/system/CanvasRole.d.ts +1 -1
  12. package/dist/entities/system/Component.d.ts +23 -13
  13. package/dist/entities/system/Component.js +41 -15
  14. package/dist/entities/system/Composite.d.ts +1 -1
  15. package/dist/entities/system/Device.d.ts +1 -1
  16. package/dist/entities/system/File.d.ts +2 -2
  17. package/dist/entities/system/Font.d.ts +1 -1
  18. package/dist/entities/system/GlobalStyle.d.ts +1 -1
  19. package/dist/entities/system/Guidance.d.ts +1 -1
  20. package/dist/entities/system/GuidanceStyle.d.ts +1 -1
  21. package/dist/entities/system/Icon.d.ts +63 -11
  22. package/dist/entities/system/Icon.js +70 -14
  23. package/dist/entities/system/LinkedSystem.d.ts +1 -1
  24. package/dist/entities/system/Modifier.d.ts +2 -2
  25. package/dist/entities/system/Motion.d.ts +1 -1
  26. package/dist/entities/system/Package.d.ts +1 -1
  27. package/dist/entities/system/Settings.d.ts +1 -1
  28. package/dist/entities/system/StyleProperty.d.ts +12 -19
  29. package/dist/entities/system/StyleProperty.js +7 -10
  30. package/dist/entities/system/Token.d.ts +4 -3
  31. package/dist/entities/system/defineComponent.d.ts +21 -6
  32. package/dist/entities/system/defineComponent.js +7 -12
  33. package/dist/entities/system/glyph-identity.d.ts +7 -0
  34. package/dist/entities/system/glyph-identity.js +153 -0
  35. package/dist/entities/system/icon-domain-invariants.d.ts +11 -0
  36. package/dist/entities/system/icon-domain-invariants.js +178 -0
  37. package/dist/entities/system/icon-library.d.ts +93 -34
  38. package/dist/entities/system/icon-library.js +218 -50
  39. package/dist/entities/system/icon-metadata.d.ts +10 -4
  40. package/dist/entities/system/icon-metadata.js +45 -5
  41. package/dist/entities/system/runtime-modules.js +5 -5
  42. package/dist/entities/system/style-bag.js +14 -2
  43. package/dist/framework/Config.d.ts +1 -1
  44. package/dist/framework/Config.js +34 -5
  45. package/dist/framework/defineConfig.d.ts +6 -3
  46. package/dist/framework/defineEntity.d.ts +54 -15
  47. package/dist/framework/defineEntity.js +56 -2
  48. package/dist/framework/projections.d.ts +40 -2
  49. package/dist/framework/projections.js +99 -5
  50. package/dist/framework/prop-surface.d.ts +6 -5
  51. package/dist/framework/prop-surface.js +12 -14
  52. package/dist/framework/registry.d.ts +17 -1
  53. package/dist/framework/registry.js +2 -1
  54. package/dist/framework/render-spec.d.ts +6 -5
  55. package/dist/framework/render-spec.js +16 -11
  56. package/dist/framework/schema-version.d.ts +1 -1
  57. package/dist/framework/schema-version.js +5 -5
  58. package/dist/framework/signature.js +5 -1
  59. package/dist/framework/utils/group.d.ts +15 -1
  60. package/dist/framework/utils/ref-grammar.d.ts +35 -0
  61. package/dist/framework/utils/ref-grammar.js +76 -0
  62. package/dist/framework/utils/refs.d.ts +13 -45
  63. package/dist/framework/utils/refs.js +31 -94
  64. package/dist/framework/value-domain.js +53 -8
  65. package/dist/index.d.ts +6 -6
  66. package/dist/index.js +5 -5
  67. package/dist/migrations/2.0.0/v1-artifact.d.ts +62 -45
  68. package/dist/migrations/2.0.0/v1-artifact.js +5 -7
  69. package/dist/migrations/20260921044042_icon_library_without_component.d.ts +19 -0
  70. package/dist/migrations/20260921044042_icon_library_without_component.js +86 -0
  71. package/dist/migrations/20260922013625_icon_libraries.d.ts +18 -0
  72. package/dist/migrations/20260922013625_icon_libraries.js +8 -0
  73. package/dist/renderer/UdsRenderer.d.ts +8 -1
  74. package/dist/renderer/UdsRenderer.js +7 -3
  75. package/dist/renderer/glyphEntries.d.ts +32 -0
  76. package/dist/renderer/glyphEntries.js +48 -0
  77. package/dist/renderer/glyphRenderable.d.ts +10 -0
  78. package/dist/renderer/glyphRenderable.js +40 -0
  79. package/dist/renderer/icon-library.d.ts +18 -11
  80. package/dist/renderer/index.d.ts +4 -2
  81. package/dist/renderer/index.js +4 -2
  82. package/dist/renderer/resolveGlyph.d.ts +47 -0
  83. package/dist/renderer/resolveGlyph.js +78 -0
  84. package/dist/renderer/wrapRegistry.d.ts +13 -4
  85. package/dist/renderer/wrapRegistry.js +7 -5
  86. package/dist/renderer/wrappers/component-refs.js +6 -2
  87. package/dist/renderer/wrappers/glyph-props.d.ts +15 -0
  88. package/dist/renderer/wrappers/glyph-props.js +65 -0
  89. package/dist/spec/glyph-jsx.d.ts +41 -0
  90. package/dist/spec/{asset-jsx.js → glyph-jsx.js} +7 -25
  91. package/dist/spec/index.d.ts +2 -2
  92. package/dist/spec/index.js +2 -2
  93. package/dist/spec/jsxToSpec.d.ts +10 -4
  94. package/dist/spec/jsxToSpec.js +21 -4
  95. package/dist/spec/specToJsx.d.ts +20 -9
  96. package/dist/spec/specToJsx.js +77 -16
  97. package/dist/tsconfig.tsbuildinfo +1 -1
  98. package/package.json +2 -2
  99. package/dist/renderer/assetEntries.d.ts +0 -37
  100. package/dist/renderer/assetEntries.js +0 -38
  101. package/dist/renderer/assetRenderable.d.ts +0 -25
  102. package/dist/renderer/assetRenderable.js +0 -19
  103. package/dist/spec/asset-jsx.d.ts +0 -61
@@ -1,45 +1,105 @@
1
1
  import { Config } from "../../framework/Config.js";
2
+ import { Icon, IconVariantAs } from "./Icon.js";
2
3
 
3
4
  //#region src/entities/system/icon-library.d.ts
4
- /** An icon library, as its group node describes it. */
5
- interface IconLibrary {
6
- /** The group path — the `slug` half of a placed glyph's type. */
5
+ /**
6
+ * An icon library as its group node reads: the path, the stored set facts (`sizes`, `variants`,
7
+ * `package`, `variantAs`, `metadata`) and the computeds the entity declares (`members`, `module`,
8
+ * `version`), plus the per-member and drawer-side facts this projection derives. Typed off
9
+ * `Icon.groups`, so a field added on the entity is visible here without a second declaration.
10
+ */
11
+ type IconLibrary = {
7
12
  readonly path: string;
8
- /** The glyphs it ships, by leaf name. */
9
- readonly members: readonly string[];
10
- /** Named sizes (`sm: 16`). */
13
+ } & Undefinable<Omit<ReturnType<typeof Icon.groups>[number]['meta'], 'items' | 'sizes' | 'variants'>> & {
11
14
  readonly sizes: Readonly<Record<string, number>>;
12
- /** Weights/styles the set ships. */
13
15
  readonly variants: readonly string[];
14
- /** The component a placed glyph renders through, if the set declares one. */
15
- readonly component?: string;
16
- /** The npm module that supplies the library's glyph implementations. */
17
- readonly module?: string;
18
- /** The version of the package the glyphs come from, when the set pins one —
19
- * what an assets index shows beside the library's name. */
20
- readonly version?: string;
21
- /** The variant the bound component declares in its `defaultProps` — what a
22
- * picker seeds when nothing is stored. Passed through as DECLARED; a policy
23
- * read validates against {@link variants} before trusting it. */
16
+ /** For a ragged set, the members whose own `variants` differ from the set's, by leaf. A member
17
+ * listing exactly the set's variants is left out, so the map stays as sparse as the
18
+ * raggedness. */
19
+ readonly memberVariants?: Readonly<Record<string, readonly string[]>>;
20
+ /** The variant the library's drawer declares in its `defaultProps` — what a picker seeds when nothing is
21
+ * stored. Passed through as declared; a policy read validates it against {@link variants}
22
+ * before trusting it. */
24
23
  readonly defaultVariant?: string;
25
- /** The size bucket the bound component declares in its `defaultProps`.
26
- * Same contract as {@link defaultVariant}: declared, not validated. */
24
+ /** The size bucket the library's drawer declares in its `defaultProps`. Same contract as
25
+ * {@link defaultVariant}: declared, not validated. */
27
26
  readonly defaultSizeKey?: string;
28
- }
27
+ };
28
+ /** A field that may hold `undefined` may be left out: a computed's `string | undefined` reads as an
29
+ * optional key, so a literal need not spell the absence. */
30
+ type Undefinable<T> = { readonly [K in keyof T as undefined extends T[K] ? K : never]?: T[K] } & { readonly [K in keyof T as undefined extends T[K] ? never : K]: T[K] };
29
31
  /** The spec `type` a placed glyph of this library carries — its own ref. */
30
- declare function assetType(library: IconLibrary, member: string): string;
32
+ declare function glyphType(library: IconLibrary, member: string): string;
31
33
  /** One library by its group path — `undefined` when the config has no such set. */
32
34
  declare function iconLibrary(config: Config | null | undefined, path: string): IconLibrary | undefined;
35
+ /**
36
+ * The exports a glyph is drawn from: its own name, plus the export each `variantAs.export`
37
+ * template names for it. A per-glyph module has to carry all of them, since the glyph resolver
38
+ * swaps between them at render; a `prop` strategy or no declaration needs the bare export alone.
39
+ * With `variants`, the member's own list, only the templates for variants it ships are added: a
40
+ * ragged set's member has no export for the rest, and naming one fails the module's bundle.
41
+ */
42
+ declare function glyphExportNames({
43
+ variantAs,
44
+ name,
45
+ variants
46
+ }: {
47
+ variantAs: IconVariantAs | undefined;
48
+ name: string;
49
+ variants?: readonly string[];
50
+ }): string[];
51
+ /**
52
+ * The element a glyph is PLACED as: an instance of the component that draws from its library, the
53
+ * member on that component's glyph prop and the set's axes as the component's own props — so a
54
+ * placed glyph paints exactly as the component would, and the panel edits it as one. A library no
55
+ * component draws from places the member raw, under its own `icon:` type.
56
+ *
57
+ * Only for placing. A node a render wrote (`<StarIcon size={12} />`) stays the raw member it is.
58
+ */
59
+ declare function placedGlyphElement(config: Config | null | undefined, glyph: string, props?: Record<string, unknown>): {
60
+ type: string;
61
+ props: Record<string, unknown>;
62
+ };
33
63
  /** Every icon library the config declares. */
34
64
  declare function iconLibraries(config: Config | null | undefined): IconLibrary[];
35
65
  /**
36
- * The library a surface should show: the hinted one when a caller carries a slug (a slot's
37
- * `accepts` ref, a stored fill), else the first library that actually has members. One home for
38
- * that policy — the toolbar, placement, and the panel all resolve through here, so no surface
39
- * carries its own idea of a default set. A hint that names no library resolves to `undefined`
40
- * rather than falling back: the caller knows whether a stale slug should show empty or pick anew.
66
+ * The libraries a surface should offer: the hinted ones in the order given (a prop whose domain
67
+ * lists several sets), else every library that actually has members. One home for that policy — the
68
+ * toolbar, placement and the panel all resolve through here, so no surface carries its own idea of
69
+ * a default set. A hint naming no library is dropped rather than failing the list, so one stale
70
+ * slug in a pair still shows the other set. An empty list of hints offers nothing: the caller had a
71
+ * domain and it named no library, which is not the same as having no domain.
41
72
  */
42
- declare function resolveIconLibrary(config: Config | null | undefined, hint?: string): IconLibrary | undefined;
73
+ declare function resolveIconLibraries({
74
+ config,
75
+ hints
76
+ }: {
77
+ config: Config | null | undefined;
78
+ hints?: readonly string[];
79
+ }): IconLibrary[];
80
+ /**
81
+ * The libraries a component binds: every icon group one of its variant props names as its domain,
82
+ * in the order the props and their lists declare them. A surface handed a bare glyph name by the
83
+ * component resolves it against this list, so a set outside the domain never answers for one in it.
84
+ * A facet ref (`icon:phosphor#variants`) offers the set's variants rather than its glyphs, so it
85
+ * binds nothing — `glyphLibrariesOf` is the one reading of the edge, shared with
86
+ * {@link glyphComponentOf}.
87
+ */
88
+ declare function librariesBoundBy({
89
+ config,
90
+ component
91
+ }: {
92
+ config: Config | null | undefined;
93
+ component: string;
94
+ }): IconLibrary[];
95
+ /** The library holding `member`, among the ones a surface offers. */
96
+ declare function libraryHolding({
97
+ libraries,
98
+ member
99
+ }: {
100
+ libraries: readonly IconLibrary[];
101
+ member: string | undefined;
102
+ }): IconLibrary | undefined;
43
103
  /**
44
104
  * Every category the library's members carry, RAW as stored (lowercase, unsorted within a member,
45
105
  * plural per member), deduplicated and sorted for a filter menu. Casing is display policy — a
@@ -47,13 +107,12 @@ declare function resolveIconLibrary(config: Config | null | undefined, hint?: st
47
107
  */
48
108
  declare function iconCategories(config: Config | null | undefined, libraryPath: string): readonly string[];
49
109
  /**
50
- * The variants a placed glyph may take.
51
- *
52
- * The whole library's variants: nothing in the model describes one glyph's own set yet
53
- * (config-v2 model gap 8 — per-item `variants` narrowing). So a set that ships a variant only
54
- * some of its glyphs support will offer it for all of them until that closes.
110
+ * The variants a placed glyph may take: the member's own `variants` when its metadata lists them (a
111
+ * ragged set), else the whole library's. Kept in the library's order, so a picker's axis reads the
112
+ * same across glyphs. A member listing only variants the set doesn't ship falls back to the set's,
113
+ * so the axis is never empty for a set that ships one.
55
114
  */
56
- declare function memberVariantOptions(library: IconLibrary, _member: string): readonly string[];
115
+ declare function memberVariantOptions(library: IconLibrary, member: string): readonly string[];
57
116
  /**
58
117
  * Whether an element type declares a prop that may survive layer-style projection. A placed
59
118
  * glyph's type isn't a component, so its prop surface comes from its library instead: the
@@ -61,4 +120,4 @@ declare function memberVariantOptions(library: IconLibrary, _member: string): re
61
120
  */
62
121
  declare function isDeclaredElementProp(config: Config | null | undefined, elementType: string, propName: string): boolean;
63
122
  //#endregion
64
- export { IconLibrary, assetType, iconCategories, iconLibraries, iconLibrary, isDeclaredElementProp, memberVariantOptions, resolveIconLibrary };
123
+ export { IconLibrary, glyphExportNames, glyphType, iconCategories, iconLibraries, iconLibrary, isDeclaredElementProp, librariesBoundBy, libraryHolding, memberVariantOptions, placedGlyphElement, resolveIconLibraries };
@@ -1,68 +1,237 @@
1
- import { pathOf } from "../../framework/utils/refs.js";
2
- import { resolveComponentProps } from "../../framework/projections.js";
1
+ import { createSliceMemo } from "../../framework/memo.js";
2
+ import { glyphComponentOf, glyphLibrariesOf, resolveComponentProps } from "../../framework/projections.js";
3
3
  import { Component } from "./Component.js";
4
4
  import { Icon } from "./Icon.js";
5
- import { parseAssetType } from "../../spec/asset-jsx.js";
5
+ import { parseGlyphType } from "../../spec/glyph-jsx.js";
6
6
  //#region src/entities/system/icon-library.ts
7
7
  /**
8
- * The icon-library READ projection — an icon group's node and members as one plain value, beside
9
- * the entity that stores them.
8
+ * Icon-library reads — an icon group's node with its computeds, and the policy reads over it.
10
9
  *
11
10
  * An icon is an entity (`icon:phosphor/StarIcon`) and everything describing the SET lives on the
12
- * emergent group node — the sizes and variants it ships, and the component a placed glyph renders
13
- * through. {@link IconLibrary} is that group node read once, so a surface asks a plain value
14
- * rather than re-querying the config per member.
11
+ * group node: the sizes and variants it ships, the package its glyphs come from, and the members
12
+ * and module the entity computes from those. {@link IconLibrary} is that node flattened with its
13
+ * path, so a surface asks a plain value rather than re-querying the config per member.
15
14
  *
16
15
  * A placed glyph's spec `type` IS the member's ref (`icon:phosphor/StarIcon`) — the same identity
17
- * the config uses, which is what `parseAssetType` reads.
16
+ * the config uses, which is what `parseGlyphType` reads.
18
17
  *
19
18
  * This is the one home for the question "what icon sets does this system have" — Studio owns only
20
- * UI policy on top (picker state, grid geometry, recents, search ranking). Like `Component.list`,
21
- * the functions here build fresh values per call: consume them in place or memoize on the config.
19
+ * UI policy on top (picker state, grid geometry, recents, search ranking). A library is cached on
20
+ * the slices it reads — the set (`icon`), its drawer's defaults (`component`) and the package row
21
+ * its version comes from (`package`) — so it is reference-stable across calls on one config and
22
+ * safe in a dep array.
22
23
  */
23
- const EMPTY_VARIANTS = Object.freeze([]);
24
- const EMPTY_SIZES = Object.freeze({});
25
24
  const EMPTY_CATEGORIES = Object.freeze([]);
25
+ const EMPTY_SIZES = Object.freeze({});
26
+ const EMPTY_VARIANTS = Object.freeze([]);
27
+ const NO_LIBRARIES = [];
26
28
  /** The spec `type` a placed glyph of this library carries — its own ref. */
27
- function assetType(library, member) {
29
+ function glyphType(library, member) {
28
30
  return `icon:${library.path}/${member}`;
29
31
  }
30
32
  /** One library by its group path — `undefined` when the config has no such set. */
31
33
  function iconLibrary(config, path) {
32
34
  if (!config) return void 0;
33
- const meta = config.groupMeta("icon", path);
34
- if (!meta) return void 0;
35
- const component = meta.component ? pathOf(meta.component) : void 0;
36
- const defaults = component ? Component.get(config, component)?.defaultProps : void 0;
37
- const defaultVariant = typeof defaults?.variant === "string" ? defaults.variant : void 0;
38
- const defaultSizeKey = typeof defaults?.size === "string" ? defaults.size : void 0;
35
+ const cache = librariesOf(config);
36
+ if (cache.has(path)) return cache.get(path);
37
+ const library = readIconLibrary(config, path);
38
+ cache.set(path, library);
39
+ return library;
40
+ }
41
+ /** Per-config cache of {@link iconLibrary} answers. `package` is in the key because the set's
42
+ * `version` resolves through the package row, not off the group node. The member walk underneath
43
+ * keys on the icon slice alone, so a component write re-reads only each library's drawer defaults. */
44
+ const librariesOf = createSliceMemo({
45
+ kinds: [
46
+ "icon",
47
+ "component",
48
+ "package"
49
+ ],
50
+ compute: (_config) => /* @__PURE__ */ new Map()
51
+ });
52
+ function readIconLibrary(config, path) {
53
+ const group = Icon.groups(config).find((entry) => entry.path === path);
54
+ if (!group) return void 0;
55
+ const { items: _items, sizes, variants, ...meta } = group.meta;
56
+ const set = setMembers({
57
+ config,
58
+ path,
59
+ declared: variants
60
+ });
39
61
  return {
62
+ ...meta,
40
63
  path,
41
- members: config.members("icon", path).map((member) => member.path.startsWith(`${path}/`) ? member.path.slice(path.length + 1) : member.path),
42
- sizes: meta.sizes ?? EMPTY_SIZES,
43
- variants: meta.variants ?? EMPTY_VARIANTS,
44
- ...component !== void 0 ? { component } : {},
45
- ...typeof meta.module === "string" ? { module: meta.module } : {},
46
- ...meta.version !== void 0 ? { version: meta.version } : {},
47
- ...defaultVariant !== void 0 ? { defaultVariant } : {},
48
- ...defaultSizeKey !== void 0 ? { defaultSizeKey } : {}
64
+ members: set.members,
65
+ sizes: sizes ?? EMPTY_SIZES,
66
+ variants: variants ?? EMPTY_VARIANTS,
67
+ ...set.memberVariants ? { memberVariants: set.memberVariants } : {},
68
+ ...drawerDefaults({
69
+ config,
70
+ library: path
71
+ })
72
+ };
73
+ }
74
+ /**
75
+ * The defaults the set's drawer declares: the component whose own variant prop names this library
76
+ * carries "what a bare glyph looks like", read back here so every picker seeds from that one
77
+ * declaration rather than its own literal. A library no component draws from has no defaults.
78
+ */
79
+ function drawerDefaults({ config, library }) {
80
+ const drawer = glyphComponentOf(config, library);
81
+ if (!drawer) return {};
82
+ const defaults = Component.get(config, drawer.path)?.defaultProps;
83
+ const variant = defaults?.variant;
84
+ const size = defaults?.size;
85
+ return {
86
+ ...typeof variant === "string" ? { defaultVariant: variant } : {},
87
+ ...typeof size === "string" ? { defaultSizeKey: size } : {}
88
+ };
89
+ }
90
+ /** Per-config cache of the member walk, which is the cost a large set carries. */
91
+ const membersOf = createSliceMemo({
92
+ kinds: ["icon"],
93
+ compute: (_config) => /* @__PURE__ */ new Map()
94
+ });
95
+ function setMembers({ config, path, declared }) {
96
+ const walked = membersOf(config);
97
+ const hit = walked.get(path);
98
+ if (hit) return hit;
99
+ const members = [];
100
+ const memberVariants = {};
101
+ for (const member of config.members("icon", path)) {
102
+ const leaf = member.path.startsWith(`${path}/`) ? member.path.slice(path.length + 1) : member.path;
103
+ members.push(leaf);
104
+ const own = ownVariantsOf(member.body);
105
+ if (own && own.length > 0 && !sameVariants({
106
+ own,
107
+ declared
108
+ })) memberVariants[leaf] = own;
109
+ }
110
+ const set = {
111
+ members,
112
+ ...Object.keys(memberVariants).length > 0 ? { memberVariants } : {}
113
+ };
114
+ walked.set(path, set);
115
+ return set;
116
+ }
117
+ /** A member body's `variants`, when it is a list of names. */
118
+ function ownVariantsOf(body) {
119
+ const declared = body.variants;
120
+ if (!Array.isArray(declared)) return void 0;
121
+ return declared.filter((variant) => typeof variant === "string");
122
+ }
123
+ /** Whether a member's list names exactly the set's variants, in any order. */
124
+ function sameVariants({ own, declared }) {
125
+ if (!declared || declared.length !== own.length) return false;
126
+ const set = new Set(declared);
127
+ return own.every((variant) => set.has(variant));
128
+ }
129
+ /**
130
+ * The exports a glyph is drawn from: its own name, plus the export each `variantAs.export`
131
+ * template names for it. A per-glyph module has to carry all of them, since the glyph resolver
132
+ * swaps between them at render; a `prop` strategy or no declaration needs the bare export alone.
133
+ * With `variants`, the member's own list, only the templates for variants it ships are added: a
134
+ * ragged set's member has no export for the rest, and naming one fails the module's bundle.
135
+ */
136
+ function glyphExportNames({ variantAs, name, variants }) {
137
+ const names = new Set([name]);
138
+ if (variantAs && "export" in variantAs) for (const [variant, template] of Object.entries(variantAs.export)) {
139
+ if (variants && !variants.includes(variant)) continue;
140
+ names.add(template.replaceAll("{name}", name));
141
+ }
142
+ return [...names];
143
+ }
144
+ /**
145
+ * The element a glyph is PLACED as: an instance of the component that draws from its library, the
146
+ * member on that component's glyph prop and the set's axes as the component's own props — so a
147
+ * placed glyph paints exactly as the component would, and the panel edits it as one. A library no
148
+ * component draws from places the member raw, under its own `icon:` type.
149
+ *
150
+ * Only for placing. A node a render wrote (`<StarIcon size={12} />`) stays the raw member it is.
151
+ */
152
+ function placedGlyphElement(config, glyph, props = {}) {
153
+ const parsed = parseGlyphType(glyph);
154
+ const drawer = parsed && config ? glyphComponentOf(config, parsed.slug) : void 0;
155
+ if (!parsed || !drawer) return {
156
+ type: glyph,
157
+ props
158
+ };
159
+ return {
160
+ type: `component:${drawer.path}`,
161
+ props: {
162
+ [drawer.prop]: parsed.member,
163
+ ...props
164
+ }
49
165
  };
50
166
  }
51
167
  /** Every icon library the config declares. */
52
168
  function iconLibraries(config) {
53
- if (!config) return [];
54
- return Icon.groups(config).map(({ path }) => iconLibrary(config, path)).filter((library) => library !== void 0);
169
+ if (!config) return NO_LIBRARIES;
170
+ return allLibrariesOf(config);
55
171
  }
172
+ const allLibrariesOf = createSliceMemo({
173
+ kinds: [
174
+ "icon",
175
+ "component",
176
+ "package"
177
+ ],
178
+ compute: (config) => Icon.groups(config).map(({ path }) => iconLibrary(config, path)).filter((library) => library !== void 0)
179
+ });
56
180
  /**
57
- * The library a surface should show: the hinted one when a caller carries a slug (a slot's
58
- * `accepts` ref, a stored fill), else the first library that actually has members. One home for
59
- * that policy — the toolbar, placement, and the panel all resolve through here, so no surface
60
- * carries its own idea of a default set. A hint that names no library resolves to `undefined`
61
- * rather than falling back: the caller knows whether a stale slug should show empty or pick anew.
181
+ * The libraries a surface should offer: the hinted ones in the order given (a prop whose domain
182
+ * lists several sets), else every library that actually has members. One home for that policy — the
183
+ * toolbar, placement and the panel all resolve through here, so no surface carries its own idea of
184
+ * a default set. A hint naming no library is dropped rather than failing the list, so one stale
185
+ * slug in a pair still shows the other set. An empty list of hints offers nothing: the caller had a
186
+ * domain and it named no library, which is not the same as having no domain.
62
187
  */
63
- function resolveIconLibrary(config, hint) {
64
- if (hint) return iconLibrary(config, hint);
65
- return iconLibraries(config).find((library) => library.members.length > 0);
188
+ function resolveIconLibraries({ config, hints }) {
189
+ if (hints !== void 0) return hints.map((hint) => iconLibrary(config, hint)).filter((library) => library !== void 0);
190
+ return iconLibraries(config).filter((library) => library.members.length > 0);
191
+ }
192
+ /**
193
+ * The libraries a component binds: every icon group one of its variant props names as its domain,
194
+ * in the order the props and their lists declare them. A surface handed a bare glyph name by the
195
+ * component resolves it against this list, so a set outside the domain never answers for one in it.
196
+ * A facet ref (`icon:phosphor#variants`) offers the set's variants rather than its glyphs, so it
197
+ * binds nothing — `glyphLibrariesOf` is the one reading of the edge, shared with
198
+ * {@link glyphComponentOf}.
199
+ */
200
+ function librariesBoundBy({ config, component }) {
201
+ if (!config) return NO_LIBRARIES;
202
+ const cache = boundLibrariesOf(config);
203
+ const hit = cache.get(component);
204
+ if (hit) return hit;
205
+ const bound = readBoundLibraries({
206
+ config,
207
+ component
208
+ });
209
+ cache.set(component, bound);
210
+ return bound;
211
+ }
212
+ /** Per-config cache of {@link librariesBoundBy}, keyed on the slices a prop's domain and the
213
+ * libraries it names read. */
214
+ const boundLibrariesOf = createSliceMemo({
215
+ kinds: [
216
+ "component",
217
+ "icon",
218
+ "package"
219
+ ],
220
+ compute: (_config) => /* @__PURE__ */ new Map()
221
+ });
222
+ function readBoundLibraries({ config, component }) {
223
+ const paths = /* @__PURE__ */ new Set();
224
+ for (const declaration of Object.values(resolveComponentProps(config, component))) {
225
+ const declared = declaration;
226
+ if (declared?.type !== "variant") continue;
227
+ for (const path of glyphLibrariesOf(declared.value)) paths.add(path);
228
+ }
229
+ return [...paths].map((path) => iconLibrary(config, path)).filter((library) => library !== void 0);
230
+ }
231
+ /** The library holding `member`, among the ones a surface offers. */
232
+ function libraryHolding({ libraries, member }) {
233
+ if (!member) return void 0;
234
+ return libraries.find((library) => library.members.includes(member));
66
235
  }
67
236
  /**
68
237
  * Every category the library's members carry, RAW as stored (lowercase, unsorted within a member,
@@ -79,14 +248,16 @@ function iconCategories(config, libraryPath) {
79
248
  return [...categories].sort((a, b) => a.localeCompare(b));
80
249
  }
81
250
  /**
82
- * The variants a placed glyph may take.
83
- *
84
- * The whole library's variants: nothing in the model describes one glyph's own set yet
85
- * (config-v2 model gap 8 — per-item `variants` narrowing). So a set that ships a variant only
86
- * some of its glyphs support will offer it for all of them until that closes.
251
+ * The variants a placed glyph may take: the member's own `variants` when its metadata lists them (a
252
+ * ragged set), else the whole library's. Kept in the library's order, so a picker's axis reads the
253
+ * same across glyphs. A member listing only variants the set doesn't ship falls back to the set's,
254
+ * so the axis is never empty for a set that ships one.
87
255
  */
88
- function memberVariantOptions(library, _member) {
89
- return library.variants;
256
+ function memberVariantOptions(library, member) {
257
+ const own = library.memberVariants?.[member];
258
+ if (!own || own.length === 0) return library.variants;
259
+ const narrowed = library.variants.filter((variant) => own.includes(variant));
260
+ return narrowed.length > 0 ? narrowed : library.variants;
90
261
  }
91
262
  /**
92
263
  * Whether an element type declares a prop that may survive layer-style projection. A placed
@@ -101,16 +272,13 @@ function isDeclaredElementProp(config, elementType, propName) {
101
272
  if (propName.startsWith("--")) return false;
102
273
  return config.resolve("style-property", propName) === void 0;
103
274
  }
104
- const parsed = parseAssetType(elementType);
275
+ const parsed = parseGlyphType(elementType);
105
276
  if (!parsed) return false;
106
277
  const library = iconLibrary(config, parsed.slug);
107
278
  if (!library?.members.includes(parsed.member)) return false;
108
- if (library.component) {
109
- if (resolveComponentProps(config, library.component)[propName] !== void 0) return true;
110
- }
111
279
  if (propName === "size") return Object.keys(library.sizes).length > 0;
112
280
  if (propName === "variant") return memberVariantOptions(library, parsed.member).length > 0;
113
281
  return false;
114
282
  }
115
283
  //#endregion
116
- export { assetType, iconCategories, iconLibraries, iconLibrary, isDeclaredElementProp, memberVariantOptions, resolveIconLibrary };
284
+ export { glyphExportNames, glyphType, iconCategories, iconLibraries, iconLibrary, isDeclaredElementProp, librariesBoundBy, libraryHolding, memberVariantOptions, placedGlyphElement, resolveIconLibraries };
@@ -16,10 +16,11 @@ import { z } from "zod";
16
16
  * name). The list and the adapter table are one file so they cannot drift apart.
17
17
  */
18
18
  /** The formats an icon group's `metadata.format` may declare — exactly the shipped adapters. */
19
- declare const ICON_METADATA_FORMATS: readonly ["phosphor", "uds"];
19
+ declare const ICON_METADATA_FORMATS: readonly ["phosphor", "uds", "tabler"];
20
20
  declare const iconMetadataFormat: z.ZodEnum<{
21
21
  uds: "uds";
22
22
  phosphor: "phosphor";
23
+ tabler: "tabler";
23
24
  }>;
24
25
  type IconMetadataFormat = (typeof ICON_METADATA_FORMATS)[number];
25
26
  /**
@@ -30,6 +31,7 @@ declare const iconMemberMetadata: z.ZodObject<{
30
31
  categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
31
32
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
32
33
  codepoint: z.ZodOptional<z.ZodNumber>;
34
+ variants: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
35
  knockoutVariants: z.ZodOptional<z.ZodArray<z.ZodString>>;
34
36
  }, z.core.$strict>;
35
37
  type IconMemberMetadata = z.infer<typeof iconMemberMetadata>;
@@ -42,10 +44,11 @@ declare const iconMetadataDeclaration: z.ZodObject<{
42
44
  from: EntityClass<z.ZodObject<{
43
45
  version: z.ZodOptional<z.ZodString>;
44
46
  exports: z.ZodOptional<SubEntityClass<z.ZodObject<{}, z.core.$strict>>>;
45
- }, z.core.$strict>, z.ZodObject<Record<never, never>, z.core.$strip>, "package", Record<never, never>, Record<never, never>, false>;
47
+ }, z.core.$strict>, z.ZodObject<Record<never, never>, z.core.$strip>, "package", Record<never, never>, Record<never, never>, false, Record<never, never>, Record<never, never>>;
46
48
  format: z.ZodEnum<{
47
49
  uds: "uds";
48
50
  phosphor: "phosphor";
51
+ tabler: "tabler";
49
52
  }>;
50
53
  }, z.core.$strict>;
51
54
  type IconMetadataDeclaration = z.infer<typeof iconMetadataDeclaration>;
@@ -53,7 +56,7 @@ type IconMetadataDeclaration = z.infer<typeof iconMetadataDeclaration>;
53
56
  type IconMetadataAdapter = (raw: unknown) => Record<string, IconMemberMetadata>;
54
57
  /**
55
58
  * The uds format IS this file schema — `{ [memberName]: { categories?, tags?, codepoint?,
56
- * knockoutVariants? } }` —
59
+ * variants?, knockoutVariants? } }` —
57
60
  * the escape hatch a consumer's own script writes. Exported (with a JSON-Schema form below) so
58
61
  * writing that script is a validated contract rather than a guess.
59
62
  */
@@ -61,12 +64,15 @@ declare const iconMetadataFile: z.ZodRecord<z.ZodString, z.ZodObject<{
61
64
  categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
62
65
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
63
66
  codepoint: z.ZodOptional<z.ZodNumber>;
67
+ variants: z.ZodOptional<z.ZodArray<z.ZodString>>;
64
68
  knockoutVariants: z.ZodOptional<z.ZodArray<z.ZodString>>;
65
69
  }, z.core.$strict>>;
66
70
  type IconMetadataFile = z.infer<typeof iconMetadataFile>;
67
71
  /** The uds file contract as JSON Schema — what a generator stamps into `$schema`-aware tooling. */
68
72
  declare function iconMetadataFileJsonSchema(): Record<string, unknown>;
73
+ /** `brand-4chan` → `IconBrand4chan`: the react package's export for a slug. */
74
+ declare function tablerExportName(slug: string): string;
69
75
  /** Run the named format's adapter. The host resolved and read `raw`; this stays pure. */
70
76
  declare function iconMetadata(format: IconMetadataFormat, raw: unknown): Record<string, IconMemberMetadata>;
71
77
  //#endregion
72
- export { ICON_METADATA_FORMATS, IconMemberMetadata, IconMetadataAdapter, IconMetadataDeclaration, IconMetadataFile, IconMetadataFormat, iconMemberMetadata, iconMetadata, iconMetadataDeclaration, iconMetadataFile, iconMetadataFileJsonSchema, iconMetadataFormat };
78
+ export { ICON_METADATA_FORMATS, IconMemberMetadata, IconMetadataAdapter, IconMetadataDeclaration, IconMetadataFile, IconMetadataFormat, iconMemberMetadata, iconMetadata, iconMetadataDeclaration, iconMetadataFile, iconMetadataFileJsonSchema, iconMetadataFormat, tablerExportName };
@@ -14,7 +14,11 @@ import { z } from "zod";
14
14
  * name). The list and the adapter table are one file so they cannot drift apart.
15
15
  */
16
16
  /** The formats an icon group's `metadata.format` may declare — exactly the shipped adapters. */
17
- const ICON_METADATA_FORMATS = ["phosphor", "uds"];
17
+ const ICON_METADATA_FORMATS = [
18
+ "phosphor",
19
+ "uds",
20
+ "tabler"
21
+ ];
18
22
  const iconMetadataFormat = z.enum(ICON_METADATA_FORMATS);
19
23
  /**
20
24
  * One member's descriptive facts — the same schema `Icon.fields` registers, owned here so the
@@ -29,6 +33,9 @@ const iconMemberMetadata = z.strictObject({
29
33
  tags: z.array(z.string()).optional(),
30
34
  /** The glyph's code point in a font-backed set — what a native glyphmap reads. */
31
35
  codepoint: z.number().optional(),
36
+ /** The subset of the set's `variants` this glyph ships, for a ragged set (Tabler draws `filled`
37
+ * for a third of its glyphs). Absent means every variant the set declares. */
38
+ variants: z.array(z.string().min(1)).optional(),
32
39
  /** Variants whose glyph paints a literal white fill by design (a "knockout" meant for dark or
33
40
  * branded surfaces) rather than `currentColor`. Such a glyph is invisible on a light surface,
34
41
  * so pickers and catalogs preview these variants on a dark one. Rendering is untouched — the
@@ -47,7 +54,7 @@ const iconMetadataDeclaration = z.strictObject({
47
54
  });
48
55
  /**
49
56
  * The uds format IS this file schema — `{ [memberName]: { categories?, tags?, codepoint?,
50
- * knockoutVariants? } }` —
57
+ * variants?, knockoutVariants? } }` —
51
58
  * the escape hatch a consumer's own script writes. Exported (with a JSON-Schema form below) so
52
59
  * writing that script is a validated contract rather than a guess.
53
60
  */
@@ -95,17 +102,50 @@ const phosphor = (raw) => {
95
102
  };
96
103
  const uds = (raw) => {
97
104
  const parsed = iconMetadataFile.safeParse(raw);
98
- if (!parsed.success) throw new Error(`uds metadata source did not match the uds file contract ({ [memberName]: { categories?, tags?, codepoint?, knockoutVariants? } }) — ${describeIssues(parsed.error)}`);
105
+ if (!parsed.success) throw new Error(`uds metadata source did not match the uds file contract ({ [memberName]: { categories?, tags?, codepoint?, variants?, knockoutVariants? } }) — ${describeIssues(parsed.error)}`);
99
106
  return parsed.data;
100
107
  };
108
+ /**
109
+ * Tabler's catalogue: `@tabler/icons` ships `icons.json`, a record keyed by kebab slug. Each entry
110
+ * carries a singular `category`, `tags` that mix strings and numbers, and a `styles` record whose
111
+ * keys are the variants the glyph draws (`outline` always, `filled` for a subset). Loose for the
112
+ * same reason as Phosphor's.
113
+ */
114
+ const tablerCatalog = z.record(z.string().min(1), z.looseObject({
115
+ name: z.string().min(1),
116
+ category: z.string().optional(),
117
+ tags: z.array(z.union([z.string(), z.number()])).optional(),
118
+ styles: z.record(z.string().min(1), z.looseObject({ unicode: z.string().optional() }))
119
+ }));
120
+ /** `brand-4chan` → `IconBrand4chan`: the react package's export for a slug. */
121
+ function tablerExportName(slug) {
122
+ return `Icon${slug.split("-").map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("")}`;
123
+ }
124
+ const tabler = (raw) => {
125
+ const parsed = tablerCatalog.safeParse(raw);
126
+ if (!parsed.success) throw new Error(`tabler metadata source did not match @tabler/icons' icons.json shape — ${describeIssues(parsed.error)}`);
127
+ const members = {};
128
+ for (const [slug, entry] of Object.entries(parsed.data)) {
129
+ const outline = entry.styles.outline?.unicode;
130
+ const codepoint = outline !== void 0 && /^[0-9a-fA-F]+$/.test(outline) ? Number.parseInt(outline, 16) : void 0;
131
+ members[tablerExportName(slug)] = {
132
+ ...entry.category ? { categories: [entry.category] } : {},
133
+ ...entry.tags && entry.tags.length > 0 ? { tags: entry.tags.map(String) } : {},
134
+ ...codepoint !== void 0 ? { codepoint } : {},
135
+ variants: Object.keys(entry.styles)
136
+ };
137
+ }
138
+ return members;
139
+ };
101
140
  /** Exhaustive by type: a format added to the list without an adapter here fails to compile. */
102
141
  const ADAPTERS = {
103
142
  phosphor,
104
- uds
143
+ uds,
144
+ tabler
105
145
  };
106
146
  /** Run the named format's adapter. The host resolved and read `raw`; this stays pure. */
107
147
  function iconMetadata(format, raw) {
108
148
  return ADAPTERS[format](raw);
109
149
  }
110
150
  //#endregion
111
- export { ICON_METADATA_FORMATS, iconMemberMetadata, iconMetadata, iconMetadataDeclaration, iconMetadataFile, iconMetadataFileJsonSchema, iconMetadataFormat };
151
+ export { ICON_METADATA_FORMATS, iconMemberMetadata, iconMetadata, iconMetadataDeclaration, iconMetadataFile, iconMetadataFileJsonSchema, iconMetadataFormat, tablerExportName };
@@ -1,3 +1,4 @@
1
+ import { isRef, pathOf } from "../../framework/utils/refs.js";
1
2
  import { Icon } from "./Icon.js";
2
3
  //#region src/entities/system/runtime-modules.ts
3
4
  /**
@@ -20,14 +21,13 @@ function declaredRuntimeModules(config) {
20
21
  });
21
22
  }
22
23
  for (const group of Icon.groups(config)) {
23
- const specifier = group.meta.module;
24
- if (typeof specifier !== "string") continue;
24
+ const named = group.meta.package;
25
+ const specifier = isRef(named) ? pathOf(named) : void 0;
26
+ if (specifier === void 0) continue;
25
27
  const existing = bySpecifier.get(specifier);
26
- const groupVersion = group.meta.version;
27
- const version = existing?.version ?? (typeof groupVersion === "string" ? groupVersion : void 0);
28
28
  bySpecifier.set(specifier, {
29
29
  specifier,
30
- ...version ? { version } : {},
30
+ ...existing?.version ? { version: existing.version } : {},
31
31
  ...existing?.exports ? { exports: existing.exports } : {},
32
32
  iconLibrary: true
33
33
  });