@yahoo/uds-create-config 4.28.3 → 4.29.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.
Files changed (32) hide show
  1. package/dist/configs/CanvasConfig.d.ts +9 -3
  2. package/dist/configs/react-native-system.d.ts +6 -2
  3. package/dist/configs/react-native-system.js +1 -1
  4. package/dist/configs/system.d.ts +6 -2
  5. package/dist/configs/system.js +1 -1
  6. package/dist/entities/system/CanvasRole.js +3 -1
  7. package/dist/entities/system/Component.js +2 -2
  8. package/dist/entities/system/Icon.d.ts +10 -6
  9. package/dist/entities/system/Icon.js +20 -8
  10. package/dist/entities/system/defineComponent.d.ts +1 -1
  11. package/dist/entities/system/glyph-identity.js +25 -1
  12. package/dist/entities/system/icon-domain-invariants.d.ts +7 -6
  13. package/dist/entities/system/icon-domain-invariants.js +49 -8
  14. package/dist/entities/system/icon-library.d.ts +3 -2
  15. package/dist/entities/system/icon-library.js +8 -9
  16. package/dist/entities/system/icon-metadata.d.ts +33 -1
  17. package/dist/entities/system/icon-metadata.js +69 -10
  18. package/dist/entities/system/runtime-modules.d.ts +3 -0
  19. package/dist/entities/system/runtime-modules.js +18 -8
  20. package/dist/framework/Config.d.ts +17 -4
  21. package/dist/framework/Config.js +80 -19
  22. package/dist/framework/defineEntity.js +8 -5
  23. package/dist/framework/projections.d.ts +26 -7
  24. package/dist/framework/projections.js +65 -21
  25. package/dist/framework/prop-surface.d.ts +5 -5
  26. package/dist/framework/prop-surface.js +2 -2
  27. package/dist/framework/utils/group.d.ts +3 -1
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +4 -4
  30. package/dist/migrations/2.0.0/v1-artifact.d.ts +3 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +1 -1
@@ -1631,7 +1631,9 @@ declare const Node: EntityClass<z.ZodObject<{
1631
1631
  }: {
1632
1632
  input: GlyphNamespace;
1633
1633
  body: Readonly<Record<string, unknown>>;
1634
- }) => void;
1634
+ }) => {
1635
+ items: Record<string, unknown>;
1636
+ };
1635
1637
  }>, EntityClass<z.ZodObject<{
1636
1638
  version: z.ZodOptional<z.ZodString>;
1637
1639
  exports: z.ZodOptional<SubEntityClass<z.ZodObject<{}, z.core.$strict>>>;
@@ -3195,7 +3197,9 @@ declare const CanvasConfig: ConfigClass<{
3195
3197
  }: {
3196
3198
  input: GlyphNamespace;
3197
3199
  body: Readonly<Record<string, unknown>>;
3198
- }) => void;
3200
+ }) => {
3201
+ items: Record<string, unknown>;
3202
+ };
3199
3203
  }>, EntityClass<z.ZodObject<{
3200
3204
  version: z.ZodOptional<z.ZodString>;
3201
3205
  exports: z.ZodOptional<SubEntityClass<z.ZodObject<{}, z.core.$strict>>>;
@@ -5024,7 +5028,9 @@ declare function createCanvasConfig(system: AnySystemConfig): ConfigInstance<{
5024
5028
  }: {
5025
5029
  input: GlyphNamespace;
5026
5030
  body: Readonly<Record<string, unknown>>;
5027
- }) => void;
5031
+ }) => {
5032
+ items: Record<string, unknown>;
5033
+ };
5028
5034
  }>, EntityClass<z.ZodObject<{
5029
5035
  version: z.ZodOptional<z.ZodString>;
5030
5036
  exports: z.ZodOptional<SubEntityClass<z.ZodObject<{}, z.core.$strict>>>;
@@ -1781,7 +1781,9 @@ declare const ReactNativeSystem: ConfigClass<{
1781
1781
  }: {
1782
1782
  input: GlyphNamespace;
1783
1783
  body: Readonly<Record<string, unknown>>;
1784
- }) => void;
1784
+ }) => {
1785
+ items: Record<string, unknown>;
1786
+ };
1785
1787
  }>;
1786
1788
  packages: EntityClass<_$zod.ZodObject<{
1787
1789
  version: _$zod.ZodOptional<_$zod.ZodString>;
@@ -3988,7 +3990,9 @@ declare const ReactNativeSystem: ConfigClass<{
3988
3990
  }: {
3989
3991
  input: GlyphNamespace;
3990
3992
  body: Readonly<Record<string, unknown>>;
3991
- }) => void;
3993
+ }) => {
3994
+ items: Record<string, unknown>;
3995
+ };
3992
3996
  }>;
3993
3997
  packages: EntityClass<_$zod.ZodObject<{
3994
3998
  version: _$zod.ZodOptional<_$zod.ZodString>;
@@ -1,6 +1,6 @@
1
1
  import { Font } from "../entities/system/Font.js";
2
- import { Composite } from "../entities/system/Composite.js";
3
2
  import { Package } from "../entities/system/Package.js";
3
+ import { Composite } from "../entities/system/Composite.js";
4
4
  import { File } from "../entities/system/File.js";
5
5
  import { Component } from "../entities/system/Component.js";
6
6
  import { Icon } from "../entities/system/Icon.js";
@@ -1634,7 +1634,9 @@ declare const System: ConfigClass<{
1634
1634
  }: {
1635
1635
  input: GlyphNamespace;
1636
1636
  body: Readonly<Record<string, unknown>>;
1637
- }) => void;
1637
+ }) => {
1638
+ items: Record<string, unknown>;
1639
+ };
1638
1640
  }>;
1639
1641
  packages: EntityClass<_$zod.ZodObject<{
1640
1642
  version: _$zod.ZodOptional<_$zod.ZodString>;
@@ -3716,7 +3718,9 @@ declare const System: ConfigClass<{
3716
3718
  }: {
3717
3719
  input: GlyphNamespace;
3718
3720
  body: Readonly<Record<string, unknown>>;
3719
- }) => void;
3721
+ }) => {
3722
+ items: Record<string, unknown>;
3723
+ };
3720
3724
  }>;
3721
3725
  packages: EntityClass<_$zod.ZodObject<{
3722
3726
  version: _$zod.ZodOptional<_$zod.ZodString>;
@@ -1,9 +1,9 @@
1
1
  import { Font } from "../entities/system/Font.js";
2
+ import { Package } from "../entities/system/Package.js";
2
3
  import { Composite } from "../entities/system/Composite.js";
3
4
  import { Modifier } from "../entities/system/Modifier.js";
4
5
  import { Settings } from "../entities/system/Settings.js";
5
6
  import { StyleProperty } from "../entities/system/StyleProperty.js";
6
- import { Package } from "../entities/system/Package.js";
7
7
  import { File } from "../entities/system/File.js";
8
8
  import { Token } from "../entities/system/Token.js";
9
9
  import { Component } from "../entities/system/Component.js";
@@ -1,4 +1,5 @@
1
1
  import { defineEntity } from "../../framework/defineEntity.js";
2
+ import { iconDomainInvariants } from "./icon-domain-invariants.js";
2
3
  import { Component } from "./Component.js";
3
4
  import { z } from "zod";
4
5
  //#region src/entities/system/CanvasRole.ts
@@ -40,7 +41,8 @@ const CanvasRole = defineEntity({
40
41
  axes: z.array(z.string()).optional().describe("The props the picker offers as axes (`variant`, `size`), each with the values the prop itself declares.")
41
42
  }).optional().describe("What the tool exposes of the component. Read by the `icon` role: the picker draws every glyph through this component and offers the named props as its axes.")
42
43
  })).describe(`The components a canvas insert tool draws with. The entity's path is the tool: one of ${CANVAS_ROLES.map((role) => `\`${role}\``).join(", ")}. A role with no entity registered has its tool disabled.`) }),
43
- schemas: { create: { description: `Register a canvas insert tool for this system. \`path\` is the role, one of ${CANVAS_ROLES.map((role) => `\`${role}\``).join(", ")}; \`data.entries\` names the components that tool inserts, one marked \`default\`. A system whose roles are unregistered has those canvas tools disabled, so register \`frame\` and \`text\` once a layout primitive and a text primitive exist.` } }
44
+ schemas: { create: { description: `Register a canvas insert tool for this system. \`path\` is the role, one of ${CANVAS_ROLES.map((role) => `\`${role}\``).join(", ")}; \`data.entries\` names the components that tool inserts, one marked \`default\`. A system whose roles are unregistered has those canvas tools disabled, so register \`frame\` and \`text\` once a layout primitive and a text primitive exist.` } },
45
+ invariants: (config) => iconDomainInvariants(config)
44
46
  });
45
47
  //#endregion
46
48
  export { CANVAS_ROLES, CanvasRole };
@@ -8,6 +8,7 @@ import { derivedColor, gradient } from "./color.js";
8
8
  import { childrenPolicy, forwardedPropName } from "./element.js";
9
9
  import { listedIn } from "../../framework/utils/enumerated.js";
10
10
  import { valueSchemaOf } from "../../framework/value-domain.js";
11
+ import { Package } from "./Package.js";
11
12
  import { bindPreviewGlyphs, glyphLeafOf } from "./glyph-identity.js";
12
13
  import { Style, addBagKeyIssues, anatomyPropValue, bagNameEdges, bagRewriteName, bindBagTokens, collectBag, isPlainRecord, openBag, styleValue } from "./style-bag.js";
13
14
  import { storedPreview } from "../../framework/preview-store.js";
@@ -17,7 +18,6 @@ import { slotAnatomyPropOf, slotTargetsOf, withoutTruthyTerm } from "../../frame
17
18
  import { listSlots, namedSlots, slotHostedBy, slotOf } from "../../spec/slot.js";
18
19
  import { cellAt } from "../../spec/cell.js";
19
20
  import { instanceAt } from "../../spec/instance.js";
20
- import { Package } from "./Package.js";
21
21
  import { File } from "./File.js";
22
22
  import { iconDomainInvariants } from "./icon-domain-invariants.js";
23
23
  import { rootLayerInvariants } from "./root-layer-invariants.js";
@@ -505,7 +505,7 @@ function propSchemaFor(config, owner) {
505
505
  code: "custom",
506
506
  input: decl,
507
507
  path: ["accepts"],
508
- message: routed ? `\`accepts\` says what may FILL a content hole — an icon set, so any glyph it registers can be dropped in (\`Button.startIcon\` accepts \`Icon.ref('phosphor')\`), or a component. \`${layer}/${prop}\` is a ${routed.type} prop, so it takes a value rather than content. Remove \`accepts\`, or point \`target\` at a prop declared \`{ type: 'slot' }\`.` : `\`accepts\` says what may FILL a content hole — an icon set, or a component. The \`${layer}\` layer is a plain tag, and \`children\` is the only prop a tag carries content through — so either target \`${layer}/children\`, or remove \`accepts\`.`
508
+ message: routed ? `\`accepts\` says what may FILL a content hole — an icon set, so any glyph it registers can be dropped in (\`EmptyState.icon\` accepts \`Icon.ref('phosphor')\`), or a component. \`${layer}/${prop}\` is a ${routed.type} prop, so it takes a value rather than content. Remove \`accepts\`, or point \`target\` at a prop declared \`{ type: 'slot' }\`.` : `\`accepts\` says what may FILL a content hole — an icon set, or a component. The \`${layer}\` layer is a plain tag, and \`children\` is the only prop a tag carries content through — so either target \`${layer}/children\`, or remove \`accepts\`.`
509
509
  });
510
510
  });
511
511
  }
@@ -82,11 +82,13 @@ declare const Icon: EntityClass<z.ZodObject<{
82
82
  }: GroupComputedInput) => string | undefined;
83
83
  }, {
84
84
  /**
85
- * The package's module namespace, so an authored glyph (`name={StarIcon}`) is matched to its
86
- * member by reference rather than by anything the component says about itself: Tabler's
87
- * `displayName` is not its export name. Read once while the config file is evaluated and never
88
- * stored; only the names under `items` are recorded, so a deprecated alias of a listed export
89
- * resolves to the listed name.
85
+ * The package's module namespace. The set's members are its glyph exports, named by the rule
86
+ * the metadata format declares (`glyphExportRule`), so an alias pair is one member and the
87
+ * package's infrastructure exports are none; `items` lays facts over members that exist, and a
88
+ * key naming no member is refused so a typo cannot invent a glyph. Each member's identity is
89
+ * recorded by reference, so an authored glyph (`name={StarIcon}`) is matched to its member by
90
+ * the object rather than by anything the component says about itself, and an alias resolves to
91
+ * the member it is. Read once while the config file is evaluated and never stored.
90
92
  */
91
93
  readonly glyphs: ({
92
94
  input,
@@ -94,7 +96,9 @@ declare const Icon: EntityClass<z.ZodObject<{
94
96
  }: {
95
97
  input: GlyphNamespace;
96
98
  body: Readonly<Record<string, unknown>>;
97
- }) => void;
99
+ }) => {
100
+ items: Record<string, unknown>;
101
+ };
98
102
  }>;
99
103
  //#endregion
100
104
  export { Icon, IconVariantAs, iconVariantAs };
@@ -1,9 +1,10 @@
1
1
  import { isRef, pathOf } from "../../framework/utils/refs.js";
2
2
  import { defineEntity } from "../../framework/defineEntity.js";
3
- import { glyphOfLibrary, libraryGlyphFacts, recordGlyphIdentities } from "./glyph-identity.js";
3
+ import { spellValues } from "../../framework/value-domain.js";
4
4
  import { Package } from "./Package.js";
5
+ import { iconMemberMetadata, iconMetadataDeclaration, iconMetadataFormat } from "./icon-metadata.js";
6
+ import { glyphMembersOf, glyphOfLibrary, libraryGlyphFacts, recordGlyphIdentities } from "./glyph-identity.js";
5
7
  import { iconDomainInvariants } from "./icon-domain-invariants.js";
6
- import { iconMemberMetadata, iconMetadataDeclaration } from "./icon-metadata.js";
7
8
  import { z } from "zod";
8
9
  //#region src/entities/system/Icon.ts
9
10
  /**
@@ -74,19 +75,30 @@ const Icon = defineEntity({
74
75
  },
75
76
  groupInputs: {
76
77
  /**
77
- * The package's module namespace, so an authored glyph (`name={StarIcon}`) is matched to its
78
- * member by reference rather than by anything the component says about itself: Tabler's
79
- * `displayName` is not its export name. Read once while the config file is evaluated and never
80
- * stored; only the names under `items` are recorded, so a deprecated alias of a listed export
81
- * resolves to the listed name.
78
+ * The package's module namespace. The set's members are its glyph exports, named by the rule
79
+ * the metadata format declares (`glyphExportRule`), so an alias pair is one member and the
80
+ * package's infrastructure exports are none; `items` lays facts over members that exist, and a
81
+ * key naming no member is refused so a typo cannot invent a glyph. Each member's identity is
82
+ * recorded by reference, so an authored glyph (`name={StarIcon}`) is matched to its member by
83
+ * the object rather than by anything the component says about itself, and an alias resolves to
84
+ * the member it is. Read once while the config file is evaluated and never stored.
82
85
  */
83
86
  glyphs: ({ input, body }) => {
84
87
  if (!isRef(body.package)) throw new Error("Icon.group: `glyphs` needs `package` on the same group, since a glyph is identified as an export of that module.");
88
+ const format = iconMetadataFormat.safeParse(body.metadata?.format);
89
+ const members = glyphMembersOf({
90
+ namespace: input,
91
+ format: format.success ? format.data : void 0
92
+ });
93
+ const overlay = body.items ?? {};
94
+ const unknown = Object.keys(overlay).filter((name) => !members.includes(name));
95
+ if (unknown.length > 0) throw new Error(`Icon.group: \`items\` names ${spellValues(unknown)}, which ${unknown.length === 1 ? "is not a glyph export" : "are not glyph exports"} of \`glyphs\`${members.length === 0 ? " (the namespace has no glyph components; the package may not be installed)" : ""}. \`items\` lays facts over the members the package exports; it cannot add one.`);
85
96
  recordGlyphIdentities({
86
97
  namespace: input,
87
- names: Object.keys(body.items ?? {}),
98
+ names: members,
88
99
  specifier: pathOf(body.package)
89
100
  });
101
+ return { items: Object.fromEntries(members.map((name) => [name, overlay[name] ?? {}])) };
90
102
  } },
91
103
  /**
92
104
  * A value bound to a library may be the glyph component itself, or the `package:` export ref the
@@ -87,7 +87,7 @@ interface SlotDecl {
87
87
  /**
88
88
  * What may FILL this hole, as refs. Two shapes, both in this repo:
89
89
  *
90
- * - an icon LIBRARY — `Button.startIcon` accepts `Icon.ref('phosphor')`, so any glyph that library
90
+ * - an icon LIBRARY — `EmptyState.icon` accepts `Icon.ref('phosphor')`, so any glyph that library
91
91
  * registers can be dropped in
92
92
  * - a COMPONENT — `PanelSegment` accepts `Component.ref('studio/PanelSegment/PanelSegmentCell')`
93
93
  *
@@ -1,5 +1,6 @@
1
1
  import { isRef, kindOf, memberOf, pathOf } from "../../framework/utils/refs.js";
2
2
  import { spellValues } from "../../framework/value-domain.js";
3
+ import { glyphExportRule } from "./icon-metadata.js";
3
4
  import { memberLeaves } from "../../framework/projections.js";
4
5
  import { z } from "zod";
5
6
  //#region src/entities/system/glyph-identity.ts
@@ -19,6 +20,29 @@ function isComponentType(value) {
19
20
  return typeof value === "object" && value !== null && "$$typeof" in value && !("props" in value);
20
21
  }
21
22
  /**
23
+ * The members a package namespace stands for: its component exports that the format's rule calls
24
+ * glyphs, each under the name the rule makes canonical, in export order with an alias pair
25
+ * collapsed to one member. A canonical spelling the namespace does not export as a component keeps
26
+ * the export's own name, so a rule reading a `displayName` the package left out still names a
27
+ * member the module has.
28
+ */
29
+ function glyphMembersOf({ namespace, format }) {
30
+ const rule = glyphExportRule(format);
31
+ const members = /* @__PURE__ */ new Set();
32
+ for (const [name, value] of Object.entries(namespace)) {
33
+ if (!isComponentType(value) || !rule.isGlyph({
34
+ name,
35
+ value
36
+ })) continue;
37
+ const canonical = rule.canonical({
38
+ name,
39
+ value
40
+ });
41
+ members.add(isComponentType(namespace[canonical]) ? canonical : name);
42
+ }
43
+ return [...members];
44
+ }
45
+ /**
22
46
  * Record which export each of `names` is in `namespace`, for the components among them.
23
47
  *
24
48
  * Only the listed names are recorded, so an alias the package also exports (Phosphor's deprecated
@@ -195,4 +219,4 @@ function bindPreviewGlyphs({ config, props }) {
195
219
  return bind(props);
196
220
  }
197
221
  //#endregion
198
- export { bindPreviewGlyphs, glyphIdentityOf, glyphLeafOf, glyphOfLibrary, glyphPackageRef, glyphPackageRefOf, isComponentType, libraryGlyphFacts, recordGlyphIdentities };
222
+ export { bindPreviewGlyphs, glyphIdentityOf, glyphLeafOf, glyphMembersOf, glyphOfLibrary, glyphPackageRef, glyphPackageRefOf, isComponentType, libraryGlyphFacts, recordGlyphIdentities };
@@ -1,11 +1,12 @@
1
1
  import type { Config } from '../../framework/Config';
2
2
  /**
3
- * A prop whose domain is a list of icon-library refs resolves a value by leaf alone, so one leaf
4
- * held by two of its libraries would draw whichever the host looked up first. This refuses that
5
- * state, naming the component, the prop and both libraries.
3
+ * The rules that hold between icon libraries and the components that draw them: a leaf two bound
4
+ * libraries both ship ({@link collisions}), a glyph nothing bound ({@link unboundGlyphs}), and a
5
+ * member's baked style the drawer's `variant` prop does not declare ({@link bakedVariants}).
6
6
  *
7
- * Declared from both `Component` and `Icon`, since `assertInvariants` runs only the patched kind's
8
- * rules: a component binding two libraries is a component write, and a glyph added to one of them
9
- * later is an icon write, and either can create the collision.
7
+ * Declared from `Component`, `Icon` and `CanvasRole`, since `assertInvariants` runs only the patched
8
+ * kind's rules: a component binding two libraries is a component write, a glyph added to one of them
9
+ * later or a style baked onto a member is an icon write, and a role entry choosing another drawer is
10
+ * a canvas-role write, and any of them can create one of these states.
10
11
  */
11
12
  export declare function iconDomainInvariants(config: Config): string[];
@@ -1,21 +1,62 @@
1
1
  import { isRef, kindOf, memberOf, pathOf } from "../../framework/utils/refs.js";
2
+ import { spellValues } from "../../framework/value-domain.js";
2
3
  import { collectBag, isPlainRecord } from "./style-bag.js";
3
4
  import { createSliceMemo } from "../../framework/memo.js";
4
- import { glyphLibrariesOf, memberLeaves, refListOf } from "../../framework/projections.js";
5
+ import { glyphComponentOf, glyphLibrariesOf, memberLeaves, propValueDomain, refListOf, resolveComponentProps } from "../../framework/projections.js";
5
6
  //#region src/entities/system/icon-domain-invariants.ts
6
7
  /**
7
- * A prop whose domain is a list of icon-library refs resolves a value by leaf alone, so one leaf
8
- * held by two of its libraries would draw whichever the host looked up first. This refuses that
9
- * state, naming the component, the prop and both libraries.
8
+ * The rules that hold between icon libraries and the components that draw them: a leaf two bound
9
+ * libraries both ship ({@link collisions}), a glyph nothing bound ({@link unboundGlyphs}), and a
10
+ * member's baked style the drawer's `variant` prop does not declare ({@link bakedVariants}).
10
11
  *
11
- * Declared from both `Component` and `Icon`, since `assertInvariants` runs only the patched kind's
12
- * rules: a component binding two libraries is a component write, and a glyph added to one of them
13
- * later is an icon write, and either can create the collision.
12
+ * Declared from `Component`, `Icon` and `CanvasRole`, since `assertInvariants` runs only the patched
13
+ * kind's rules: a component binding two libraries is a component write, a glyph added to one of them
14
+ * later or a style baked onto a member is an icon write, and a role entry choosing another drawer is
15
+ * a canvas-role write, and any of them can create one of these states.
14
16
  */
15
17
  function iconDomainInvariants(config) {
16
- return [...collisions(config), ...unboundGlyphs(config)];
18
+ return [
19
+ ...collisions(config),
20
+ ...unboundGlyphs(config),
21
+ ...bakedVariants(config)
22
+ ];
17
23
  }
18
24
  /**
25
+ * A member that bakes `variants` names the styles it draws in its package's words, and the drawer
26
+ * that places it may declare a `variant` prop of its own. When it does, every baked word has to be
27
+ * one the prop declares, else the panel would narrow to a style the drawer cannot draw. A drawer
28
+ * with no `variant` prop (a set whose styles are separate exports) is never checked, since the
29
+ * member is the style. The bake writes each member through an ordinary icon update, so the rule
30
+ * runs on the build with no wiring of its own.
31
+ */
32
+ const bakedVariants = createSliceMemo({
33
+ kinds: [
34
+ "component",
35
+ "icon",
36
+ "canvas-role"
37
+ ],
38
+ compute: (config) => {
39
+ const problems = [];
40
+ for (const group of config.groups("icon")) {
41
+ const drawer = glyphComponentOf(config, group.path);
42
+ if (!drawer) continue;
43
+ const declaration = resolveComponentProps(config, drawer.path).variant;
44
+ if (declaration?.type !== "variant") continue;
45
+ const declared = propValueDomain(config, declaration)?.leaves ?? [];
46
+ const allowed = new Set(declared);
47
+ for (const member of config.members("icon", group.path)) {
48
+ const baked = member.body.variants;
49
+ if (!Array.isArray(baked)) continue;
50
+ for (const value of baked) {
51
+ if (typeof value !== "string" || allowed.has(value)) continue;
52
+ problems.push(`Icon library "${group.path}" member "${member.leaf}" bakes the variant "${value}", which the drawer "${drawer.path}" does not declare on its \`variant\` prop (${declared.length === 0 ? "it declares no values" : spellValues(declared)}); a set's styles have to be words its drawer draws.`);
53
+ }
54
+ }
55
+ }
56
+ return problems;
57
+ }
58
+ });
59
+ /**
19
60
  * A glyph authored as its component leaves the open-bag pass as a `package:` export ref, and the
20
61
  * owner-bound pass binds that to the member when the key's domain is a library drawing from that
21
62
  * module. One left in a bag is a glyph nothing bound, and this refuses it by what went wrong: no
@@ -94,8 +94,9 @@ declare function resolveIconLibraries({
94
94
  * {@link glyphComponentOf}.
95
95
  */
96
96
  /**
97
- * The libraries the canvas's icon tool offers: those its component's glyph prop is bound to. Empty
98
- * when the `icon` canvas role is unregistered, which is also when the tool is disabled.
97
+ * The libraries the canvas's icon tool offers: the union of every entry's glyph domain, in entry
98
+ * and then declaration order, each library once. Empty when the `icon` canvas role is unregistered,
99
+ * which is also when the tool is disabled.
99
100
  */
100
101
  declare function iconToolLibraries(config: Config | null | undefined): IconLibrary[];
101
102
  declare function librariesBoundBy({
@@ -1,5 +1,5 @@
1
1
  import { createSliceMemo } from "../../framework/memo.js";
2
- import { glyphComponentOf, glyphLibrariesOf, iconToolOf, resolveComponentProps } from "../../framework/projections.js";
2
+ import { glyphComponentOf, glyphLibrariesOf, iconToolFor, iconToolsOf, resolveComponentProps } from "../../framework/projections.js";
3
3
  import { isValueSlot } from "../../framework/prop-surface.js";
4
4
  import { Component } from "./Component.js";
5
5
  import { parseGlyphType } from "../../spec/glyph-jsx.js";
@@ -162,8 +162,7 @@ function placedGlyphElement(config, glyph, props = {}) {
162
162
  type: glyph,
163
163
  props
164
164
  };
165
- const tool = iconToolOf(config);
166
- const defaults = tool && tool.component === bound.path ? tool.defaultProps : {};
165
+ const defaults = iconToolFor(config, parsed.slug)?.defaultProps ?? {};
167
166
  return {
168
167
  type: `component:${bound.path}`,
169
168
  props: {
@@ -207,15 +206,15 @@ function resolveIconLibraries({ config, hints }) {
207
206
  * {@link glyphComponentOf}.
208
207
  */
209
208
  /**
210
- * The libraries the canvas's icon tool offers: those its component's glyph prop is bound to. Empty
211
- * when the `icon` canvas role is unregistered, which is also when the tool is disabled.
209
+ * The libraries the canvas's icon tool offers: the union of every entry's glyph domain, in entry
210
+ * and then declaration order, each library once. Empty when the `icon` canvas role is unregistered,
211
+ * which is also when the tool is disabled.
212
212
  */
213
213
  function iconToolLibraries(config) {
214
214
  if (!config) return NO_LIBRARIES;
215
- const tool = iconToolOf(config);
216
- if (!tool) return NO_LIBRARIES;
217
- const declaration = resolveComponentProps(config, tool.component)[tool.glyph];
218
- return glyphLibrariesOf(declaration?.value).map((path) => iconLibrary(config, path)).filter((library) => library !== void 0);
215
+ const paths = /* @__PURE__ */ new Set();
216
+ for (const tool of iconToolsOf(config)) for (const path of tool.libraries) paths.add(path);
217
+ return [...paths].map((path) => iconLibrary(config, path)).filter((library) => library !== void 0);
219
218
  }
220
219
  function librariesBoundBy({ config, component }) {
221
220
  if (!config) return NO_LIBRARIES;
@@ -54,6 +54,30 @@ declare const iconMetadataDeclaration: z.ZodObject<{
54
54
  type IconMetadataDeclaration = z.infer<typeof iconMetadataDeclaration>;
55
55
  /** A format adapter: raw source data in, per-member metadata out. Throws on a shape it can't read. */
56
56
  type IconMetadataAdapter = (raw: unknown) => Record<string, IconMemberMetadata>;
57
+ /**
58
+ * Which of a package's component exports are glyphs, and which name of an alias pair is the member.
59
+ * A format's rule, beside its adapter, because the package the adapter describes is the one that
60
+ * decides both: Phosphor exports every glyph as `XIcon` beside a deprecated bare `X`, and Tabler
61
+ * exports 66 of its glyphs under two `Icon*` names. `Icon.group({ glyphs })` runs the rule over the
62
+ * namespace's component exports to name the set's members, so the caller has already dropped
63
+ * anything that is not a component.
64
+ */
65
+ interface GlyphExportRule {
66
+ /** Whether a component export of this name is a glyph rather than infrastructure (`IconBase`). */
67
+ readonly isGlyph: (args: {
68
+ name: string;
69
+ value: unknown;
70
+ }) => boolean;
71
+ /** The member name an export stands for: itself, or the spelling the alias pair collapses to. */
72
+ readonly canonical: (args: {
73
+ name: string;
74
+ value: unknown;
75
+ }) => string;
76
+ }
77
+ /** The glyph export rule a set's metadata format declares; a set with no format keeps every
78
+ * component export under its own name. */
79
+ declare function glyphExportRule(format: IconMetadataFormat | undefined): GlyphExportRule;
80
+ declare function iconMetadataSourceFile(format: IconMetadataFormat): string | undefined;
57
81
  /**
58
82
  * The uds format IS this file schema — `{ [memberName]: { categories?, tags?, codepoint?,
59
83
  * variants?, knockoutVariants? } }` —
@@ -72,7 +96,15 @@ type IconMetadataFile = z.infer<typeof iconMetadataFile>;
72
96
  declare function iconMetadataFileJsonSchema(): Record<string, unknown>;
73
97
  /** `brand-4chan` → `IconBrand4chan`: the react package's export for a slug. */
74
98
  declare function tablerExportName(slug: string): string;
99
+ /** `IconStar` plus `filled` → `IconStarFilled`: the export a non-outline style is drawn by. */
100
+ declare function tablerStyledExportName({
101
+ base,
102
+ style
103
+ }: {
104
+ base: string;
105
+ style: string;
106
+ }): string;
75
107
  /** Run the named format's adapter. The host resolved and read `raw`; this stays pure. */
76
108
  declare function iconMetadata(format: IconMetadataFormat, raw: unknown): Record<string, IconMemberMetadata>;
77
109
  //#endregion
78
- export { ICON_METADATA_FORMATS, IconMemberMetadata, IconMetadataAdapter, IconMetadataDeclaration, IconMetadataFile, IconMetadataFormat, iconMemberMetadata, iconMetadata, iconMetadataDeclaration, iconMetadataFile, iconMetadataFileJsonSchema, iconMetadataFormat, tablerExportName };
110
+ export { GlyphExportRule, ICON_METADATA_FORMATS, IconMemberMetadata, IconMetadataAdapter, IconMetadataDeclaration, IconMetadataFile, IconMetadataFormat, glyphExportRule, iconMemberMetadata, iconMetadata, iconMetadataDeclaration, iconMetadataFile, iconMetadataFileJsonSchema, iconMetadataFormat, iconMetadataSourceFile, tablerExportName, tablerStyledExportName };
@@ -52,6 +52,40 @@ const iconMetadataDeclaration = z.strictObject({
52
52
  from: Package,
53
53
  format: iconMetadataFormat
54
54
  });
55
+ /** Every component export is a glyph under its own name: the rule for `uds` and for a set that
56
+ * declares no metadata format. */
57
+ const everyComponentExport = {
58
+ isGlyph: () => true,
59
+ canonical: ({ name }) => name
60
+ };
61
+ const GLYPH_EXPORT_RULES = {
62
+ phosphor: {
63
+ isGlyph: ({ name }) => name.endsWith("Icon"),
64
+ canonical: ({ name }) => name.endsWith("Icon") ? name : `${name}Icon`
65
+ },
66
+ uds: everyComponentExport,
67
+ tabler: {
68
+ isGlyph: ({ name }) => name.startsWith("Icon"),
69
+ canonical: ({ name, value }) => {
70
+ const displayName = value?.displayName;
71
+ return typeof displayName === "string" && displayName.length > 0 ? `Icon${displayName}` : name;
72
+ }
73
+ }
74
+ };
75
+ /** The glyph export rule a set's metadata format declares; a set with no format keeps every
76
+ * component export under its own name. */
77
+ function glyphExportRule(format) {
78
+ return format === void 0 ? everyComponentExport : GLYPH_EXPORT_RULES[format];
79
+ }
80
+ /**
81
+ * The file inside the source package a format's catalogue is, for a package whose manifest names no
82
+ * entry: `@tabler/icons` exports only `./icons/*` and ships `icons.json` at its root. A format
83
+ * without one reads the package's own entry.
84
+ */
85
+ const SOURCE_FILES = { tabler: "icons.json" };
86
+ function iconMetadataSourceFile(format) {
87
+ return SOURCE_FILES[format];
88
+ }
55
89
  /**
56
90
  * The uds format IS this file schema — `{ [memberName]: { categories?, tags?, codepoint?,
57
91
  * variants?, knockoutVariants? } }` —
@@ -107,33 +141,58 @@ const uds = (raw) => {
107
141
  };
108
142
  /**
109
143
  * 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
144
+ * carries a singular `category`, `tags` that mix strings, numbers and the odd `null` (the four
145
+ * `*-number-0` entries spell one tag as `null`), and a `styles` record whose keys are the styles the
146
+ * glyph draws (`outline` always, `filled` for a subset), each with its own codepoint. Loose for the
112
147
  * same reason as Phosphor's.
113
148
  */
114
149
  const tablerCatalog = z.record(z.string().min(1), z.looseObject({
115
150
  name: z.string().min(1),
116
151
  category: z.string().optional(),
117
- tags: z.array(z.union([z.string(), z.number()])).optional(),
152
+ tags: z.array(z.union([
153
+ z.string(),
154
+ z.number(),
155
+ z.null()
156
+ ])).optional(),
118
157
  styles: z.record(z.string().min(1), z.looseObject({ unicode: z.string().optional() }))
119
158
  }));
120
159
  /** `brand-4chan` → `IconBrand4chan`: the react package's export for a slug. */
121
160
  function tablerExportName(slug) {
122
161
  return `Icon${slug.split("-").map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("")}`;
123
162
  }
163
+ /** `IconStar` plus `filled` → `IconStarFilled`: the export a non-outline style is drawn by. */
164
+ function tablerStyledExportName({ base, style }) {
165
+ if (style === "outline") return base;
166
+ return `${base}${style.charAt(0).toUpperCase()}${style.slice(1)}`;
167
+ }
168
+ /** A style's codepoint from its hex `unicode`. Whole-string hex only: `parseInt` would read `ea1z`
169
+ * as `ea1` and store a wrong glyph. */
170
+ function tablerCodepoint(unicode) {
171
+ if (unicode === void 0 || !/^[0-9a-fA-F]+$/.test(unicode)) return;
172
+ return Number.parseInt(unicode, 16);
173
+ }
124
174
  const tabler = (raw) => {
125
175
  const parsed = tablerCatalog.safeParse(raw);
126
176
  if (!parsed.success) throw new Error(`tabler metadata source did not match @tabler/icons' icons.json shape — ${describeIssues(parsed.error)}`);
127
177
  const members = {};
128
178
  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)] = {
179
+ const base = tablerExportName(slug);
180
+ const tags = (entry.tags ?? []).filter((tag) => tag !== null).map(String);
181
+ const shared = {
132
182
  ...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)
183
+ ...tags.length > 0 ? { tags } : {}
136
184
  };
185
+ for (const [style, styled] of Object.entries(entry.styles)) {
186
+ const codepoint = tablerCodepoint(styled.unicode);
187
+ members[tablerStyledExportName({
188
+ base,
189
+ style
190
+ })] = {
191
+ ...shared,
192
+ ...codepoint !== void 0 ? { codepoint } : {},
193
+ variants: [style]
194
+ };
195
+ }
137
196
  }
138
197
  return members;
139
198
  };
@@ -148,4 +207,4 @@ function iconMetadata(format, raw) {
148
207
  return ADAPTERS[format](raw);
149
208
  }
150
209
  //#endregion
151
- export { ICON_METADATA_FORMATS, iconMemberMetadata, iconMetadata, iconMetadataDeclaration, iconMetadataFile, iconMetadataFileJsonSchema, iconMetadataFormat, tablerExportName };
210
+ export { ICON_METADATA_FORMATS, glyphExportRule, iconMemberMetadata, iconMetadata, iconMetadataDeclaration, iconMetadataFile, iconMetadataFileJsonSchema, iconMetadataFormat, iconMetadataSourceFile, tablerExportName, tablerStyledExportName };
@@ -11,6 +11,9 @@ interface DeclaredRuntimeModule {
11
11
  readonly exports?: readonly string[];
12
12
  /** This module also supplies the members of an icon library. */
13
13
  readonly iconLibrary?: true;
14
+ /** This module is an icon library's metadata catalogue, read by the build and imported by
15
+ * nothing a rendered spec reaches. */
16
+ readonly catalogue?: true;
14
17
  }
15
18
  /**
16
19
  * The npm modules this config itself declares at runtime.
@@ -23,14 +23,24 @@ function declaredRuntimeModules(config) {
23
23
  for (const group of Icon.groups(config)) {
24
24
  const named = group.meta.package;
25
25
  const specifier = isRef(named) ? pathOf(named) : void 0;
26
- if (specifier === void 0) continue;
27
- const existing = bySpecifier.get(specifier);
28
- bySpecifier.set(specifier, {
29
- specifier,
30
- ...existing?.version ? { version: existing.version } : {},
31
- ...existing?.exports ? { exports: existing.exports } : {},
32
- iconLibrary: true
33
- });
26
+ if (specifier !== void 0) {
27
+ const existing = bySpecifier.get(specifier);
28
+ bySpecifier.set(specifier, {
29
+ ...existing,
30
+ specifier,
31
+ iconLibrary: true
32
+ });
33
+ }
34
+ const source = group.meta.metadata?.from;
35
+ const catalogue = isRef(source) ? pathOf(source) : void 0;
36
+ if (catalogue !== void 0) {
37
+ const existing = bySpecifier.get(catalogue);
38
+ bySpecifier.set(catalogue, {
39
+ ...existing,
40
+ specifier: catalogue,
41
+ catalogue: true
42
+ });
43
+ }
34
44
  }
35
45
  return [...bySpecifier.values()].sort((left, right) => left.specifier.localeCompare(right.specifier));
36
46
  }