@yahoo/uds-create-config 5.35.0 → 5.35.2

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.
@@ -664,10 +664,11 @@ function canonicalWhen(when) {
664
664
  function styleBagValue({ config, declaration, name = "<prop>", glyphLibraries }) {
665
665
  const domain = valueSchemaOf(declaration, config);
666
666
  const bound = declaration?.value;
667
- if (glyphLibraries && glyphLibraries.length > 0) return z.union([glyphMemberRefIn({
667
+ if (glyphLibraries && glyphLibraries.length > 0) return glyphMemberRefIn({
668
668
  config,
669
- libraries: glyphLibraries
670
- }), domain]);
669
+ libraries: glyphLibraries,
670
+ otherwise: domain
671
+ });
671
672
  if (declaration?.type === "slot" && !isValueSlot(declaration)) return z.union([domain, z.never()], { error: () => `\`${name}\` is a slot, so a rule may set it to a string of content and nothing else. A glyph or a nested element is an anatomy node under the layer's node — \`{ "element": { "__ref": "icon:<library>/<Name>" }, "visible": { "$state": "<prop>", "eq": "<value>" } }\` to show one per variant value — and the instance's own content arrives through the prop.` });
672
673
  if (!isRef(bound) || kindOf(bound.__ref) !== "style-property") return domain;
673
674
  const property = pathOf(bound.__ref);
@@ -1140,10 +1141,14 @@ const Preview = defineSubEntity({
1140
1141
  for (const [name, decl] of Object.entries(body.states ?? {})) when[name] = stateConditionValue(decl).optional();
1141
1142
  return z.strictObject({
1142
1143
  when: z.strictObject(when, { error: previewShapeError }).nullable().default(null),
1143
- props: z.record(z.string(), z.unknown()).transform((props) => bindPreviewGlyphs({
1144
+ props: z.record(z.string(), z.unknown()).transform((props, ctx) => bindPreviewGlyphs({
1144
1145
  config,
1145
1146
  props,
1146
- glyphProps
1147
+ glyphProps,
1148
+ refuse: (message) => ctx.addIssue({
1149
+ code: "custom",
1150
+ message
1151
+ })
1147
1152
  }))
1148
1153
  }, { error: previewShapeError });
1149
1154
  }
@@ -173,15 +173,55 @@ function glyphOfLibrary({ library, module, leaves }) {
173
173
  return identity.name;
174
174
  });
175
175
  }
176
+ /** Why a string under a glyph key is refused: it names no member of the libraries the key reaches. */
177
+ function notAMember({ spelled, libraries }) {
178
+ return `\`${spelled}\` is not a member of ${libraries.map((library) => `the icon library "${library}"`).join(" or ")}, which this key draws from.`;
179
+ }
180
+ /**
181
+ * The member ref a written glyph name spells under `libraries`: the member's name (`StarIcon`), its
182
+ * path (`phosphor/StarIcon`) or its ref's spelling (`icon:phosphor/StarIcon`). A bare name resolves
183
+ * under the first listed library holding it, in the order the key's declaration names them.
184
+ * `undefined` when no listed library holds what the string names.
185
+ */
186
+ function spelledGlyphRef({ config, libraries, spelled }) {
187
+ const colon = spelled.indexOf(":");
188
+ const path = colon !== -1 && spelled.slice(0, colon) === "icon" ? spelled.slice(colon + 1) : spelled;
189
+ for (const library of libraries) {
190
+ const name = path.startsWith(`${library}/`) ? path.slice(library.length + 1) : path;
191
+ if (memberLeaves(config, `icon:${library}`).includes(name)) return { __ref: `icon:${library}/${name}` };
192
+ }
193
+ }
176
194
  /**
177
- * The glyph form for a key that reaches a library through a forward (`icon` → `icon/name`): the
178
- * component, its `package:` ref, the member's name a surface writes, or the member ref a stored
179
- * config already holds, answered as the member ref `icon:<library>/<name>`. The ref names its library, so a key reaching several resolves
180
- * without a lookup. Each library's arm refuses by cause, so a glyph of a library the key does not
181
- * reach says so.
195
+ * The glyph form for a key that reaches a library through a forward (`icon` → `icon/name`),
196
+ * answered as the member ref `icon:<library>/<name>`: the component, its `package:` ref, the member
197
+ * ref a stored config holds, or a string spelling a member ({@link spelledGlyphRef}). The ref names
198
+ * its library, so a key reaching several resolves without a lookup.
199
+ *
200
+ * Every string is answered here, so a string that names no member is refused rather than stored as
201
+ * text the receiving prop cannot draw. The empty string is the one exception: it is how a surface
202
+ * clears a glyph over a default, and passes through as written. Each library's component arm refuses
203
+ * by cause, so a glyph of a library the key does not reach says so. `otherwise` takes every value
204
+ * that is not a string.
182
205
  */
183
- function glyphMemberRefIn({ config, libraries }) {
184
- const arms = libraries.map((library) => {
206
+ function glyphMemberRefIn({ config, libraries, otherwise }) {
207
+ const spelledArm = z.string().transform((spelled, ctx) => {
208
+ if (spelled === "") return spelled;
209
+ const ref = spelledGlyphRef({
210
+ config,
211
+ libraries,
212
+ spelled
213
+ });
214
+ if (ref) return ref;
215
+ ctx.addIssue({
216
+ code: "custom",
217
+ message: notAMember({
218
+ spelled,
219
+ libraries
220
+ })
221
+ });
222
+ return z.NEVER;
223
+ });
224
+ const componentArms = libraries.map((library) => {
185
225
  const facts = libraryGlyphFacts({
186
226
  config,
187
227
  target: `icon:${library}`
@@ -193,14 +233,14 @@ function glyphMemberRefIn({ config, libraries }) {
193
233
  const name = path.slice(library.length + 1);
194
234
  return path.startsWith(`${library}/`) && facts.leaves.includes(name) ? name : void 0;
195
235
  };
196
- return z.union([
197
- z.custom((value) => memberOfRef(value) !== void 0).transform((value) => toRef(memberOfRef(value))),
198
- z.string().refine((name) => facts.leaves.includes(name)).transform(toRef),
199
- glyphOfLibrary(facts).transform((name) => toRef(name))
200
- ]);
236
+ return z.union([z.custom((value) => memberOfRef(value) !== void 0).transform((value) => toRef(memberOfRef(value))), glyphOfLibrary(facts).transform((name) => toRef(name))]);
201
237
  });
202
- if (arms.length === 1) return arms[0];
203
- return z.union(arms);
238
+ const nonString = z.custom((value) => typeof value !== "string").pipe(otherwise);
239
+ return z.union([
240
+ spelledArm,
241
+ ...componentArms,
242
+ nonString
243
+ ]);
204
244
  }
205
245
  /** `icon:<library>/<name>` for a glyph identity a library of this config claims; `undefined` when
206
246
  * none does. */
@@ -218,11 +258,11 @@ function glyphMemberRefOf(config, identity) {
218
258
  * way `defaultProps` are. Once an owner is known, this pass binds each such ref to
219
259
  * `icon:<library>/<name>` through the library whose package ships it, at the top level and inside a
220
260
  * stored element's props, which is the spelling the build writes when it reads `.preview()` from
221
- * source. A member's name written under one of the owner's glyph props (`glyphProps`) binds through
222
- * the first of that prop's libraries holding it. A `package:` ref no library claims is another
223
- * package's export and stays as written.
261
+ * source. A string spelling a member under one of the owner's glyph props (`glyphProps`) binds
262
+ * through that prop's libraries ({@link spelledGlyphRef}), and one naming no member is refused. A
263
+ * `package:` ref no library claims is another package's export and stays as written.
224
264
  */
225
- function bindPreviewGlyphs({ config, props, glyphProps = {} }) {
265
+ function bindPreviewGlyphs({ config, props, glyphProps = {}, refuse }) {
226
266
  const bind = (value) => {
227
267
  if (typeof value !== "object" || value === null) return value;
228
268
  if (Array.isArray(value)) return value.map(bind);
@@ -234,10 +274,18 @@ function bindPreviewGlyphs({ config, props, glyphProps = {} }) {
234
274
  };
235
275
  const out = bind(props);
236
276
  for (const [prop, libraries] of Object.entries(glyphProps)) {
237
- const name = out[prop];
238
- if (typeof name !== "string") continue;
239
- const library = libraries.find((path) => memberLeaves(config, `icon:${path}`).includes(name));
240
- if (library) out[prop] = { __ref: `icon:${library}/${name}` };
277
+ const spelled = out[prop];
278
+ if (typeof spelled !== "string" || spelled === "") continue;
279
+ const ref = spelledGlyphRef({
280
+ config,
281
+ libraries,
282
+ spelled
283
+ });
284
+ if (ref) out[prop] = ref;
285
+ else refuse?.(notAMember({
286
+ spelled,
287
+ libraries
288
+ }));
241
289
  }
242
290
  return out;
243
291
  }
@@ -59,10 +59,11 @@ const Style = defineSubEntity({
59
59
  config,
60
60
  body: parent
61
61
  })[key];
62
- if (libraries && libraries.length > 0) return z.union([glyphMemberRefIn({
62
+ if (libraries && libraries.length > 0) return glyphMemberRefIn({
63
63
  config,
64
- libraries
65
- }), styleValue]);
64
+ libraries,
65
+ otherwise: styleValue
66
+ });
66
67
  }
67
68
  if (declared) return styleValue;
68
69
  if (!config.resolve("style-property", key)) return styleValue;
@@ -389,7 +389,8 @@ declare function withoutTruthyTerm({
389
389
  * rather than 51 conditioned siblings copying a library's index into every component that draws a
390
390
  * glyph.
391
391
  *
392
- * Two ways the domain is known, in order: the prop delegates it (`value` is a ref to the library, or
392
+ * A member ref (`icon:phosphor/StarIcon`), the form a glyph is stored in, names the type itself when
393
+ * its library is one the prop draws from ({@link memberRefType}). A name is looked up in the prop's domain, which is known two ways, in order: the prop delegates it (`value` is a ref to the library, or
393
394
  * a list of refs for a host drawing from several, which is the shape that keeps 1,530 names in one
394
395
  * place), or the prop enumerates its values and the member is found by asking which group declares
395
396
  * it. The second exists because a config migrated from v1 spells an icon prop as an enumeration, and
@@ -869,18 +869,24 @@ function spentBy({ condition, prop }) {
869
869
  * rather than 51 conditioned siblings copying a library's index into every component that draws a
870
870
  * glyph.
871
871
  *
872
- * Two ways the domain is known, in order: the prop delegates it (`value` is a ref to the library, or
872
+ * A member ref (`icon:phosphor/StarIcon`), the form a glyph is stored in, names the type itself when
873
+ * its library is one the prop draws from ({@link memberRefType}). A name is looked up in the prop's domain, which is known two ways, in order: the prop delegates it (`value` is a ref to the library, or
873
874
  * a list of refs for a host drawing from several, which is the shape that keeps 1,530 names in one
874
875
  * place), or the prop enumerates its values and the member is found by asking which group declares
875
876
  * it. The second exists because a config migrated from v1 spells an icon prop as an enumeration, and
876
877
  * a glyph is no less resolvable for it.
877
878
  */
878
879
  function boundElementType(config, componentPath, prop, value) {
879
- if (typeof value !== "string" || !value) return void 0;
880
- if (value.includes("/")) return void 0;
881
880
  const decl = resolveComponentProps(config, componentPath)[prop];
882
881
  const single = refOf(decl?.value);
883
882
  const targets = refListOf(decl?.value) ?? (single ? [single] : []);
883
+ if (isRef(value) && kindOf(value) === "icon") return memberRefType({
884
+ config,
885
+ targets,
886
+ value
887
+ });
888
+ if (typeof value !== "string" || !value) return void 0;
889
+ if (value.includes("/")) return void 0;
884
890
  if (targets.length > 0) {
885
891
  for (const target of targets) {
886
892
  if (memberOf(target) !== void 0) continue;
@@ -893,6 +899,17 @@ function boundElementType(config, componentPath, prop, value) {
893
899
  return matches.length === 1 ? `icon:${matches[0]?.path}` : void 0;
894
900
  }
895
901
  /**
902
+ * The type a member ref names, when the member exists and belongs to one of the prop's libraries
903
+ * (`targets`); a prop that delegates no domain takes any member the config holds. A ref outside
904
+ * them is `undefined`, so the node falls back as it does for a name no library holds.
905
+ */
906
+ function memberRefType({ config, targets, value }) {
907
+ if (!config.resolveRef(value.__ref)) return void 0;
908
+ if (targets.length === 0) return value.__ref;
909
+ const path = pathOf(value);
910
+ return targets.some((target) => kindOf(target) === "icon" && memberOf(target) === void 0 && path.startsWith(`${pathOf(target)}/`)) ? value.__ref : void 0;
911
+ }
912
+ /**
896
913
  * The props of an instance that the component CONSUMES rather than passes on — the same split the
897
914
  * emitted module's destructuring makes (`downlevel.ts`, `styleRuleBindings`).
898
915
  *