@yahoo/uds-create-config 4.39.1 → 4.40.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.
@@ -92,14 +92,14 @@ declare const ReactNativeSystemDefinition: ConfigClass<{
92
92
  filter: "filter";
93
93
  transform: "transform";
94
94
  position: "position";
95
+ color: "color";
96
+ inset: "inset";
95
97
  end: "end";
96
98
  start: "start";
97
99
  bottom: "bottom";
98
100
  left: "left";
99
101
  right: "right";
100
102
  top: "top";
101
- color: "color";
102
- inset: "inset";
103
103
  flex: "flex";
104
104
  cursor: "cursor";
105
105
  direction: "direction";
@@ -9,14 +9,14 @@ declare const rnStyleKey: z.ZodEnum<{
9
9
  filter: "filter";
10
10
  transform: "transform";
11
11
  position: "position";
12
+ color: "color";
13
+ inset: "inset";
12
14
  end: "end";
13
15
  start: "start";
14
16
  bottom: "bottom";
15
17
  left: "left";
16
18
  right: "right";
17
19
  top: "top";
18
- color: "color";
19
- inset: "inset";
20
20
  flex: "flex";
21
21
  cursor: "cursor";
22
22
  direction: "direction";
@@ -176,14 +176,14 @@ declare const NativeStyleProperty: EntityClass<z.ZodObject<Omit<{
176
176
  filter: "filter";
177
177
  transform: "transform";
178
178
  position: "position";
179
+ color: "color";
180
+ inset: "inset";
179
181
  end: "end";
180
182
  start: "start";
181
183
  bottom: "bottom";
182
184
  left: "left";
183
185
  right: "right";
184
186
  top: "top";
185
- color: "color";
186
- inset: "inset";
187
187
  flex: "flex";
188
188
  cursor: "cursor";
189
189
  direction: "direction";
@@ -40,7 +40,13 @@ interface LayerClass extends Named<typeof LayerDefinition> {}
40
40
  declare function forwardedNames(config: Config, element: unknown, entry: unknown): string[] | null;
41
41
  declare const ForwardDefinition: SubEntityClass<z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodArray<z.ZodObject<{
42
42
  __ref: z.ZodString;
43
- }, z.core.$strip>>]>>;
43
+ }, z.core.$strip>>]> | z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodArray<z.ZodObject<{
44
+ __ref: z.ZodString;
45
+ }, z.core.$strip>>]>, z.ZodTransform<"*" | {
46
+ __ref: string;
47
+ }[], "*" | {
48
+ __ref: string;
49
+ }[]>>>;
44
50
  interface ForwardClass extends Named<typeof ForwardDefinition> {}
45
51
  /**
46
52
  * One value a variant prop offers — the keyed collection under its `value`.
@@ -1,11 +1,11 @@
1
1
  import { reject, rejectData } from "../../framework/rejection.js";
2
- import { canonicalCondition, isRef, kindOf, pathOf, ref, refLeaf, refSchema, splitRef } from "../../framework/utils/refs.js";
2
+ import { canonicalCondition, isRef, kindOf, memberRef, pathOf, ref, refLeaf, refSchema, splitRef } from "../../framework/utils/refs.js";
3
3
  import { isPlainObject } from "../../framework/utils/field-path.js";
4
4
  import { pathInput } from "../../framework/schemas.js";
5
- import { defineEntity } from "../../framework/defineEntity.js";
6
- import { defineSubEntity } from "../../framework/defineSubEntity.js";
7
5
  import { derivedColor, gradient } from "./color.js";
8
6
  import { shadowValue } from "./shadow.js";
7
+ import { defineEntity } from "../../framework/defineEntity.js";
8
+ import { defineSubEntity } from "../../framework/defineSubEntity.js";
9
9
  import { childrenPolicy, forwardedPropName } from "./element.js";
10
10
  import { listedIn } from "../../framework/utils/enumerated.js";
11
11
  import { valueSchemaOf } from "../../framework/value-domain.js";
@@ -13,7 +13,7 @@ import { Package } from "./Package.js";
13
13
  import { bindPreviewGlyphs, glyphLeafOf } from "./glyph-identity.js";
14
14
  import { Style, addBagKeyIssues, anatomyPropValue, bagNameEdges, bagRewriteName, bindBagTokens, collectBag, isPlainRecord, openBag, styleValue } from "./style-bag.js";
15
15
  import { storedPreview } from "../../framework/preview-store.js";
16
- import { glyphPropsOf, propValueDomain, refListOf, resolveComponentProps, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
16
+ import { glyphPropsOf, propOwner, propValueDomain, refListOf, resolveComponentProps, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
17
17
  import { carriesContent, intoName, isValueSlot, renderBoundProp, routedPropIn, routesContent } from "../../framework/prop-surface.js";
18
18
  import { slotAnatomyPropOf, slotTargetsOf, withoutTruthyTerm } from "../../framework/render-spec.js";
19
19
  import { listSlots, namedSlots, slotHostedBy, slotOf } from "../../spec/slot.js";
@@ -239,7 +239,7 @@ function forwardedNames(config, element, entry) {
239
239
  if (entry !== "*" || !isRef(element)) return null;
240
240
  return Object.keys(resolveComponentProps(config, pathOf(element.__ref)));
241
241
  }
242
- const Forward = defineSubEntity({
242
+ const ForwardDefinition = defineSubEntity({
243
243
  name: "forward",
244
244
  label: "Forward",
245
245
  labelPlural: "Forwards",
@@ -296,9 +296,30 @@ const Forward = defineSubEntity({
296
296
  message: `layers "${key}" and "${otherKey}" would both forward ${shared.slice(0, 3).map((n) => `"${n}"`).join(", ")}${shared.length > 3 ? ` (+${shared.length - 3} more)` : ""}. Route one explicitly with bind().`
297
297
  });
298
298
  }
299
- });
299
+ }).transform((value) => declaringForwardRefs(config, element, value));
300
300
  }
301
301
  });
302
+ /**
303
+ * A narrowing list with each ref naming the component that DECLARES its prop — `defineComponent`'s
304
+ * `storedForwards`, asked of the config instead of the handles.
305
+ *
306
+ * The builder walks a layer's handle to find the declaration, and a layer written as a ref has no
307
+ * handle to walk: `.layers({ root: Component.ref('Pressable') })` mints `component:Pressable#props/width`
308
+ * for a `width` Pressable only forwards from Box, a ref that resolves to nothing. That is the spelling
309
+ * of every layer a consumer builds on a component borrowed through a link, so the config — which can
310
+ * resolve the element — re-points it here, where the same forward on the source's own handle already
311
+ * names Box. A ref whose prop nothing in the chain declares is left as it was, for validation to judge.
312
+ */
313
+ function declaringForwardRefs(config, element, value) {
314
+ if (value === "*" || !isRef(element)) return value;
315
+ return value.map((entry) => {
316
+ const name = forwardedPropName(entry);
317
+ const owner = name && propOwner(config, pathOf(element.__ref), name);
318
+ if (!name || !owner || pathOf(entry.__ref) === owner) return entry;
319
+ return memberRef("component", owner, "props", name);
320
+ });
321
+ }
322
+ const Forward = ForwardDefinition;
302
323
  const VariantValue = defineSubEntity({
303
324
  name: "value",
304
325
  label: "Value",
@@ -515,7 +536,30 @@ function propSchemaFor(config, owner) {
515
536
  path: ["accepts"],
516
537
  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\`.`
517
538
  });
518
- });
539
+ }).overwrite((decl) => declaringInto(config, layers, decl));
540
+ }
541
+ /**
542
+ * A route's `into` naming the component that DECLARES its prop — `storedRoutedForward`, asked of the
543
+ * config instead of the handles, as {@link declaringForwardRefs} is for `forwards`.
544
+ *
545
+ * The builder walks the target layer's handle to find the declaration, and a layer written as a ref
546
+ * has none: `{ type: 'forward', target: 'root/children' }` over `root: Component.ref('Pressable')`
547
+ * kept the bare name `children` where the same route over the source's own handle stores
548
+ * `component:Box#props/children`. A name is invisible to the dependency graph, so a rename of the
549
+ * prop never reached it. The config resolves the element and mints the ref here. A prop nothing in
550
+ * the chain declares is the DOM's (`aria-label` on the element's tag) and keeps its name.
551
+ */
552
+ function declaringInto(config, layers, decl) {
553
+ if (!isPlainObject(decl) || decl.type !== "forward" || !isRef(decl.target)) return decl;
554
+ const element = layers?.[pathOf(decl.target)];
555
+ if (!isRef(element)) return decl;
556
+ const name = intoName(decl.into);
557
+ const owner = propOwner(config, pathOf(element), name);
558
+ if (!owner || isRef(decl.into) && pathOf(decl.into) === owner) return decl;
559
+ return {
560
+ ...decl,
561
+ into: memberRef("component", owner, "props", name)
562
+ };
519
563
  }
520
564
  /**
521
565
  * A condition narrowed to one owner's props: each key a sibling, each value one of that sibling's
@@ -1,6 +1,6 @@
1
1
  import { refSchema } from "../../framework/utils/refs.js";
2
- import { defineEntity } from "../../framework/defineEntity.js";
3
2
  import { derivedColor } from "./color.js";
3
+ import { defineEntity } from "../../framework/defineEntity.js";
4
4
  import { z } from "zod";
5
5
  //#region src/entities/system/GlobalStyle.ts
6
6
  /**
@@ -1,7 +1,7 @@
1
1
  import { isRef, pathOf, ref, sourceOf } from "../../framework/utils/refs.js";
2
+ import { composedValueSchemas } from "./composed-values.js";
2
3
  import { defineEntity } from "../../framework/defineEntity.js";
3
4
  import { defineSubEntity } from "../../framework/defineSubEntity.js";
4
- import { composedValueSchemas } from "./composed-values.js";
5
5
  import { Font } from "./Font.js";
6
6
  import { Modifier } from "./Modifier.js";
7
7
  import { authoringPhantom } from "../../framework/utils/authoring-phantom.js";
@@ -1,6 +1,6 @@
1
1
  import { isExtendedRef, isRef, ref, refSchema, splitRef } from "../../framework/utils/refs.js";
2
- import { defineSubEntity } from "../../framework/defineSubEntity.js";
3
2
  import { composedValueSchemas } from "./composed-values.js";
3
+ import { defineSubEntity } from "../../framework/defineSubEntity.js";
4
4
  import { isCssProperty, kebabCase } from "../../css/values.js";
5
5
  import { valueSchemaOf } from "../../framework/value-domain.js";
6
6
  import { glyphLeafOf, glyphPackageRef } from "./glyph-identity.js";
@@ -2094,6 +2094,18 @@ declare class Config {
2094
2094
  withDerivedRows(): this;
2095
2095
  /** {@link withDerivedRows} for a derivation already in hand. */
2096
2096
  private withRowsOf;
2097
+ /**
2098
+ * Re-coerce every authored item that names a row just written — {@link coerceOwned}'s second pass,
2099
+ * for the rows a link brings.
2100
+ *
2101
+ * A member's schema is built from the config, so it binds against whatever rows exist when it runs,
2102
+ * and an authored config is coerced at `register`, before any source is attached. A consumer's
2103
+ * component built on a borrowed one — `root: Component.ref('Pressable')`, forwarding `width` — was
2104
+ * judged against a config with no Pressable in it, and kept a ref at Pressable for a prop Pressable
2105
+ * only forwards from Box. Only the dependents of what this pass wrote can change, so only they are
2106
+ * asked again; a minted row is remade from its source and has nothing of its own to coerce.
2107
+ */
2108
+ private coerceReferrers;
2097
2109
  /**
2098
2110
  * The rows an edit changes, derived inside that edit's own apply.
2099
2111
  *
@@ -3997,7 +3997,44 @@ var Config = class Config {
3997
3997
  withRowsOf(derived) {
3998
3998
  const moved = derived.changes.filter((change) => !this.holdsDerivedRow(change));
3999
3999
  if (moved.length === 0) return this;
4000
- return this.derive({ snapshot: this.state.snapshot.withChanges(moved) });
4000
+ return this.derive({ snapshot: this.state.snapshot.withChanges(moved) }).coerceReferrers(moved);
4001
+ }
4002
+ /**
4003
+ * Re-coerce every authored item that names a row just written — {@link coerceOwned}'s second pass,
4004
+ * for the rows a link brings.
4005
+ *
4006
+ * A member's schema is built from the config, so it binds against whatever rows exist when it runs,
4007
+ * and an authored config is coerced at `register`, before any source is attached. A consumer's
4008
+ * component built on a borrowed one — `root: Component.ref('Pressable')`, forwarding `width` — was
4009
+ * judged against a config with no Pressable in it, and kept a ref at Pressable for a prop Pressable
4010
+ * only forwards from Box. Only the dependents of what this pass wrote can change, so only they are
4011
+ * asked again; a minted row is remade from its source and has nothing of its own to coerce.
4012
+ */
4013
+ coerceReferrers(written) {
4014
+ const { snapshot } = this.state;
4015
+ const dependents = /* @__PURE__ */ new Set();
4016
+ for (const change of written) {
4017
+ if (change.target !== "item" || !change.value) continue;
4018
+ for (const dependent of this.referrersOf(snapshot, change.kind, change.path)) dependents.add(dependent);
4019
+ }
4020
+ const changes = [];
4021
+ for (const dependent of dependents) {
4022
+ const kind = kindOf(dependent);
4023
+ const path = pathOf(dependent);
4024
+ const entry = this.entityOf(kind);
4025
+ const before = snapshot.get(kind, path)?.toJSON();
4026
+ if (!entry || Object.keys(entry.subEntities).length === 0) continue;
4027
+ if (!before || isRef(before) || isMintedRow(before)) continue;
4028
+ const after = this.normalizeMembers(entry, before);
4029
+ if (JSON.stringify(after) === JSON.stringify(before)) continue;
4030
+ changes.push({
4031
+ kind,
4032
+ target: "item",
4033
+ path,
4034
+ value: new Entity(path, after, entry.fieldKeys)
4035
+ });
4036
+ }
4037
+ return changes.length === 0 ? this : this.derive({ snapshot: snapshot.withChanges(changes) });
4001
4038
  }
4002
4039
  /**
4003
4040
  * The rows an edit changes, derived inside that edit's own apply.
@@ -124,7 +124,8 @@ interface MisdeclaredOverlay {
124
124
  type LeafAddress = string | readonly string[];
125
125
  /** Every leaf address under `body`, as segments, so a member key that contains a dot stays one key. */
126
126
  declare function leafAddresses(body: unknown): (readonly string[])[];
127
- /** The value at a leaf address, or `undefined` where the body has none. A ref is one value, not walked into. */
127
+ /** The value at a leaf address, or `undefined` where the body has none. A ref and a composed value are
128
+ * each one value, not walked into. */
128
129
  declare function valueAt(body: unknown, field: LeafAddress): unknown;
129
130
  /** The two things a local leaf can be to the source: a value it defines, replaced, or one it lacks, added. */
130
131
  type OverlayVerb = 'override' | 'extend';
@@ -1,7 +1,22 @@
1
1
  import { collectRefs, isExtendedRef, isRef, kindOf, memberOf, pathOf, sourceOf, splitRef } from "./utils/refs.js";
2
2
  import { isPlainObject, setAtPath } from "./utils/field-path.js";
3
+ import { isComposedValue } from "../entities/system/composed-values.js";
3
4
  //#region src/framework/source-integrity.ts
4
5
  /**
6
+ * Whether a config's links hold — the two ways a borrow can be broken by the other side.
7
+ *
8
+ * Both are pure questions about a `Config` with its sources attached, which is why they live here rather
9
+ * than in the build that refuses on them. Three surfaces need the same answers: `uds build` refuses,
10
+ * Create's update sweep asks which borrows a new version drops, and an editor will want to say so before
11
+ * the push. Written once, they can't disagree, and a sweep that disagreed with the build would look like
12
+ * a branch that merged clean and then wouldn't compile.
13
+ *
14
+ * The two provenance walks are the rest of it, and what every consumer reads. `sourcedFields` answers
15
+ * where an entity's values come from, and `borrowedSlug` answers whether the entity itself is someone
16
+ * else's. The framework installs both as computeds on every kind (`sources`, `borrowedFrom`), so nothing
17
+ * re-derives them and no kind has to opt in.
18
+ */
19
+ /**
5
20
  * Every `@source` ref in `config` that does not resolve against its attached source.
6
21
  *
7
22
  * This is the guarantee the emitted types can only approximate. A union narrows what an author can type, but
@@ -189,18 +204,42 @@ function redundantQualifiers(config) {
189
204
  function segmentsOf(field) {
190
205
  return typeof field === "string" ? field.split(".") : field;
191
206
  }
192
- /** A leaf is anything that is not a plain object to walk into. A ref is one value, not a shape. */
207
+ /**
208
+ * Whether a leaf walk goes into `value`: a record, but not a ref and not a composed value
209
+ * (`alpha(…)`, `shadow(…)`, a gradient, a transition). Both are one value however many fields spell
210
+ * them, so an `alpha()` laid over a source's ref is an override of that ref, not an extension of an
211
+ * `amount` the source never had.
212
+ */
213
+ const isWalkable = (value) => isPlainObject(value) && !isRef(value) && !isComposedValue(value);
214
+ /**
215
+ * A leaf is anything {@link isWalkable} does not walk into.
216
+ *
217
+ * An EMPTY record below the root is a leaf too: it is a member whose value carries no fields — a
218
+ * package export is `{}`, its identity is its key. Walked into, it contributes no address at all, so a
219
+ * member added that way vanished from every overlay classified here.
220
+ */
193
221
  function leafEntries(value, prefix = []) {
194
- if (!isPlainObject(value) || isRef(value)) return [[prefix, value]];
222
+ if (!isWalkable(value)) return [[prefix, value]];
223
+ if (prefix.length > 0 && Object.keys(value).length === 0) return [[prefix, value]];
195
224
  return Object.entries(value).flatMap(([key, child]) => leafEntries(child, [...prefix, key]));
196
225
  }
197
226
  /** Every leaf address under `body`, as segments, so a member key that contains a dot stays one key. */
198
227
  function leafAddresses(body) {
199
228
  return leafEntries(body).map(([address]) => address).filter((address) => address.length > 0);
200
229
  }
201
- /** The value at a leaf address, or `undefined` where the body has none. A ref is one value, not walked into. */
230
+ /** The value at a leaf address, or `undefined` where the body has none. A ref and a composed value are
231
+ * each one value, not walked into. */
202
232
  function valueAt(body, field) {
203
- return segmentsOf(field).reduce((at, key) => isPlainObject(at) && !isRef(at) ? at[key] : void 0, body);
233
+ return segmentsOf(field).reduce((at, key) => isWalkable(at) ? at[key] : void 0, body);
234
+ }
235
+ /**
236
+ * Whether a component leaf sits under a style rule's `when` (`styles.<key>.when…`). That's the rule's
237
+ * name, not its content: a rule is stored under the key its condition spells, so a condition restated
238
+ * over a rule the source has is the source's own, and a rule list, which has no key to write, has to
239
+ * restate it to reach the rule at all. An extension there is never wrong.
240
+ */
241
+ function isRuleCondition(field) {
242
+ return field[0] === "styles" && field[2] === "when";
204
243
  }
205
244
  /**
206
245
  * The verb one local leaf is against the source's value at the same address: `extend` where the
@@ -256,13 +295,16 @@ function misdeclaredOverlays(config) {
256
295
  const overlay = config.sourceOverlay(slug, kind, path);
257
296
  if (!overlay) continue;
258
297
  const theirs = source.resolve(kind, path)?.toJSON();
259
- for (const field of leafAddresses(overlay.extend)) if (valueAt(theirs, field) !== void 0) found.push({
260
- slug,
261
- kind,
262
- path,
263
- declared: "extend",
264
- field
265
- });
298
+ for (const field of leafAddresses(overlay.extend)) {
299
+ if (kind === "component" && isRuleCondition(field)) continue;
300
+ if (valueAt(theirs, field) !== void 0) found.push({
301
+ slug,
302
+ kind,
303
+ path,
304
+ declared: "extend",
305
+ field
306
+ });
307
+ }
266
308
  for (const field of leafAddresses(overlay.override)) if (valueAt(theirs, field) === void 0) found.push({
267
309
  slug,
268
310
  kind,
package/dist/index.js CHANGED
@@ -5,15 +5,15 @@ import { ENTITY_PATH_MESSAGE, MINTED, collectRefs, entityRefOf, isEntityPath, is
5
5
  import { describePatch } from "./framework/plan.js";
6
6
  import { applyPathDelta, isPlainObject, namedFields, pathDelta, pathSegments, setAtPath, touchedFields } from "./framework/utils/field-path.js";
7
7
  import { declaredField, deriveCreateSchema, deriveUpdateSchema, fieldKeys } from "./framework/utils/field-schema.js";
8
+ import { DERIVED_COLOR_SPACE, alpha, darken, isDerivedColor, isGradient, lighten, linearGradient, mix, renderDerivedColor, renderGradient } from "./entities/system/color.js";
9
+ import { isShadow, renderShadow, shadow } from "./entities/system/shadow.js";
10
+ import { isTransition, renderTransition, transition } from "./entities/system/transition.js";
11
+ import { composedValueAuthoring, composedValueFormOf, composedValueForms, composedValueSchemas, isComposedValue, renderComposedValue } from "./entities/system/composed-values.js";
8
12
  import { classifyOverlay, danglingDirectives, danglingSourcedRefs, leafAddresses, leafVerb, misdeclaredOverlays, overlayVerb, redundantQualifiers, unadoptedDirectives, unreadableBorrows, unstatableConditions, unstatableOverrides, valueAt } from "./framework/source-integrity.js";
9
13
  import { opsForKind } from "./framework/registry.js";
10
14
  import { GROUP, brandGroup, isGroupBody } from "./framework/utils/group.js";
11
15
  import { defineEntity } from "./framework/defineEntity.js";
12
16
  import { defineSubEntity } from "./framework/defineSubEntity.js";
13
- import { DERIVED_COLOR_SPACE, alpha, darken, isDerivedColor, isGradient, lighten, linearGradient, mix, renderDerivedColor, renderGradient } from "./entities/system/color.js";
14
- import { isShadow, renderShadow, shadow } from "./entities/system/shadow.js";
15
- import { isTransition, renderTransition, transition } from "./entities/system/transition.js";
16
- import { composedValueAuthoring, composedValueFormOf, composedValueForms, composedValueSchemas, isComposedValue, renderComposedValue } from "./entities/system/composed-values.js";
17
17
  import { VOID_ELEMENTS, childrenPolicy, forwardsOf, ownerOf, partsOf, renderedElement, renderedTarget, rootLayerOf } from "./entities/system/element.js";
18
18
  import { Font, FontFile, cssString, fontFamilyStack } from "./entities/system/Font.js";
19
19
  import { CSS_PROPERTY_NAMES, CSS_WIDE_KEYWORDS, cssProperty, cssValueIssue, isCssProperty } from "./css/values.js";