@yahoo/uds-create-config 5.57.1 → 5.59.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.
@@ -17,6 +17,7 @@
17
17
  * The returned handle is a ref AND an `Authored`, so it composes into other components and
18
18
  * registers through the generic bridge.
19
19
  */
20
+ import { type PropsOrRules } from '../entities/system/prop-rules';
20
21
  import type { Authored } from '../framework/authoring';
21
22
  import { type Ref } from '../framework/utils/refs';
22
23
  /** A component handle — a ref (import/reference it), an `Authored` (register it), and a
@@ -69,7 +70,7 @@ interface Body<L, SP extends string, MOD extends string, P extends Record<string
69
70
  when: Record<string, string>;
70
71
  layers: LayerOverrides<L, SP, MOD>;
71
72
  }>;
72
- defaultProps?: Record<string, unknown>;
73
+ defaultProps?: PropsOrRules<Record<string, unknown>>;
73
74
  label?: string;
74
75
  description?: string;
75
76
  }
@@ -13,7 +13,8 @@ import { StoredDefaultCondition } from "../../framework/default-conditions.js";
13
13
  import { StoredPreviewCondition } from "../../framework/preview-store.js";
14
14
  import { RepeatSpec } from "../../framework/render-spec.js";
15
15
  import { ComponentSourceInput } from "./component-source-fields.js";
16
- import { AsInput, AuthoredDefaultsOf, LayerMap, PhantomCallProps, PhantomReadProps, PropMap, RenderArgs, StateMap, StyleRuleInput } from "./defineComponent.js";
16
+ import { PropsOrRules } from "./prop-rules.js";
17
+ import { AsInput, AuthoredDefaultsOf, LayerMap, PhantomCallProps, PhantomReadProps, PropMap, RenderArgs, StateMap, StyleRuleInput, UnconditionalDefaults } from "./defineComponent.js";
17
18
  import { listSlots, namedSlots, slotHostedBy, slotOf, slotsFor } from "../../spec/slot.js";
18
19
  import { cellAt } from "../../spec/cell.js";
19
20
  import { instanceAt } from "../../spec/instance.js";
@@ -871,7 +872,9 @@ declare const ComponentEntity: EntityClass<z.ZodObject<{
871
872
  }: ComputedInput) => string[];
872
873
  }, false, Record<never, never>, Record<never, never>>;
873
874
  /** Source extensions declare their structure through .render(). */
874
- type ComponentRefDelta = ComponentSourceInput<Partial<CreateInput<typeof componentFields>>>;
875
+ type ComponentRefDelta = Omit<ComponentSourceInput<Partial<CreateInput<typeof componentFields>>>, 'defaultProps'> & {
876
+ /** A record, or `.defaultProps`'s rule list; checked against the props by `ExtensionDefaults`. */readonly defaultProps?: PropsOrRules<Record<string, unknown>>;
877
+ };
875
878
  type ExactComponentDelta<Delta> = Delta & { [Key in Exclude<keyof Delta, keyof ComponentRefDelta>]: never };
876
879
  type ContractLayers<Contract> = Contract extends {
877
880
  layers: infer Layers;
@@ -908,7 +911,7 @@ type ResolvedDefaults<Contract, Delta> = Delta extends {
908
911
  readonly defaultProps: infer Defaults;
909
912
  } ? OverlayContract<Contract extends {
910
913
  defaultProps: infer Base;
911
- } ? Base : Record<never, never>, Defaults> : Contract extends {
914
+ } ? Base : Record<never, never>, UnconditionalDefaults<Defaults>> : Contract extends {
912
915
  defaultProps: infer Defaults;
913
916
  } ? Defaults : Record<never, never>;
914
917
  type ResolvedForwards<Contract, Delta> = Delta extends {
@@ -27,9 +27,9 @@ import { instanceAt } from "../../spec/instance.js";
27
27
  import { forwardOptions, forwardsWrite } from "./component-forwards.js";
28
28
  import { assertComponentSourceInput } from "./component-source-fields.js";
29
29
  import { checkStyleSet, styleSetEdits } from "./component-style-set.js";
30
- import { storedDefaults } from "./conditional-defaults.js";
31
30
  import { forwardRouteInvariants, refusedRoutes } from "./forward-route-invariants.js";
32
31
  import { iconDomainInvariants } from "./icon-domain-invariants.js";
32
+ import { storedDefaults } from "./prop-rules.js";
33
33
  import { rootLayerInvariants } from "./root-layer-invariants.js";
34
34
  import { Token } from "./Token.js";
35
35
  import { z } from "zod";
@@ -1199,7 +1199,7 @@ const Preview = defineSubEntity({
1199
1199
  });
1200
1200
  }
1201
1201
  });
1202
- const DEFAULT_CONDITIONS_DESCRIPTION = "Defaults that hold only under a condition on this component's other props, laid over `defaultProps` the way a style rule is over the unconditional one: a list of entries, each identified by its condition. `{ \"when\": { \"modal\": false }, \"props\": { \"includeScrim\": false } }` defaults `includeScrim` to `false` while `modal` is `false`, and to its `defaultProps` value otherwise. When several entries hold, the later one wins on the prop it sets. Both sides name the component's own variant, string, number or boolean props, a prop set here is never part of a condition, and a value the call site passes always wins. Authored as `conditionalDefault(fallback, [{ when, value }])` inside `.defaultProps`.";
1202
+ const DEFAULT_CONDITIONS_DESCRIPTION = "Defaults that hold only under a condition on this component's other props, laid over `defaultProps` the way a style rule is over the unconditional one: a list of entries, each identified by its condition. `{ \"when\": { \"modal\": false }, \"props\": { \"includeScrim\": false } }` defaults `includeScrim` to `false` while `modal` is `false`, and to its `defaultProps` value otherwise. When several entries hold, the later one wins on the prop it sets. Both sides name the component's own variant, string, number or boolean props, a prop set here is never part of a condition, and a value the call site passes always wins. Authored as a rule in `.defaultProps([{ when, props }])`.";
1203
1203
  const defaultConditionShapeError = "a conditional default is `{ \"when\": { \"<prop>\": <value> }, \"props\": { \"<prop>\": <value> } }`: both name this component's own variant, string, number or boolean props (not `as`), `when` at one of their values.";
1204
1204
  const defaultConditionBase = z.object({
1205
1205
  when: z.record(z.string(), z.unknown()),
@@ -1811,9 +1811,9 @@ const componentFields = z.strictObject({
1811
1811
  anatomy: AnatomyNode.optional().describe("The tree of what this component renders, keyed by node. Each node names what it renders (a layer, for anything a style rule reaches) and lists its `children`; the node keyed `root` is the top. A one-layer component may omit it. Anatomy read from an extension's own source file replaces the inherited tree; edits to a borrowed render merge into its tree."),
1812
1812
  defaultProps: Style.optional(),
1813
1813
  /** The defaults that hold only under a condition on the component's other props — see
1814
- * `conditionalDefault`. */
1814
+ * `.defaultProps([{ when, props }])`. */
1815
1815
  defaultPropsWhen: orList(DefaultCondition).optional().describe(DEFAULT_CONDITIONS_DESCRIPTION),
1816
- /** What the tile and the matrix render with — see `preview({ defaultProps, when })`. Read off
1816
+ /** What the tile and the matrix render with — see `.preview({ defaultProps })`. Read off
1817
1817
  * `.preview()` at build, and written per cell from the matrix. */
1818
1818
  preview: orList(Preview).optional().describe(PREVIEW_DESCRIPTION),
1819
1819
  /**
@@ -2989,7 +2989,7 @@ function componentRef(path) {
2989
2989
  assertComponentSourceInput(delta);
2990
2990
  const extended = base.extend({
2991
2991
  ...delta,
2992
- ...storedDefaults(delta)
2992
+ ...delta.defaultProps === void 0 ? {} : storedDefaults(delta.defaultProps)
2993
2993
  });
2994
2994
  Object.defineProperty(extended, "render", {
2995
2995
  enumerable: false,
@@ -3,7 +3,7 @@ import { RegisteredComponentHandles, RegisteredGroupPaths, RegisteredModifiers,
3
3
  import { Authored } from "../../framework/authoring.js";
4
4
  import { StyleValue } from "./style-bag.js";
5
5
  import { ComponentSourceInput } from "./component-source-fields.js";
6
- import { ConditionalDefault } from "./conditional-defaults.js";
6
+ import { PropsOrRules } from "./prop-rules.js";
7
7
  import { VariantValues } from "./Component.js";
8
8
  import { AllHTMLAttributes, ComponentPropsWithRef, ComponentType, ElementType, JSX, ReactElement, ReactNode, Ref } from "react";
9
9
 
@@ -664,11 +664,17 @@ type ForwardedPropsOf<L extends LayerMap, F extends string> = [NonRootForward<L,
664
664
  type DefaultsOf<P extends PropMap> = Partial<DeclaredProps<P>> & {
665
665
  [key: string]: unknown;
666
666
  };
667
- /** {@link DefaultsOf} as `.defaultProps` takes it: a declared prop other than `as` may also default
668
- * through {@link conditionalDefault}. */
669
- type AuthoredDefaultsOf<P extends PropMap> = { [K in keyof DeclaredProps<P>]?: K extends 'as' ? DeclaredProps<P>[K] : DeclaredProps<P>[K] | ConditionalDefault<DeclaredProps<P>[K]> } & {
670
- [key: string]: unknown;
671
- };
667
+ /** {@link DefaultsOf} as `.defaultProps` and `.extend({ defaultProps })` take it: the defaults, or
668
+ * a list of rules giving them under conditions. */
669
+ type AuthoredDefaultsOf<P extends PropMap> = PropsOrRules<DefaultsOf<P>>;
670
+ /** The defaults a component applies with nothing set: a record as given, or its unconditional
671
+ * rules folded together. */
672
+ type UnconditionalDefaults<D> = D extends readonly (infer Rule)[] ? [UnconditionalProps<Rule>] extends [never] ? Record<never, never> : UnionToIntersection<UnconditionalProps<Rule>> : D;
673
+ type UnconditionalProps<Rule> = Rule extends {
674
+ when: Readonly<Record<string, unknown>>;
675
+ } ? never : Rule extends {
676
+ props: infer Props;
677
+ } ? Props : never;
672
678
  /**
673
679
  * `T` without the keys in `K` — `Omit` that leaves an index signature alone.
674
680
  *
@@ -785,10 +791,11 @@ F extends string = never, /** The states `.states()` declared — what the rende
785
791
  * `const`, so an `as` keeps its literal type: without it `{ as: 'input' }` widens to `string` and
786
792
  * there's no tag left to resolve the element's props against.
787
793
  *
788
- * A default that depends on another prop is a `conditionalDefault`:
789
- * `includeScrim: conditionalDefault(true, [{ when: { modal: false }, value: false }])`.
794
+ * Defaults that depend on other props are rules, the way style rules are: a rule without `when`
795
+ * holds always, and a later rule wins on the props it sets.
796
+ * `[{ props: { includeScrim: true } }, { when: { modal: false }, props: { includeScrim: false } }]`.
790
797
  */
791
- defaultProps<const D2 extends AuthoredDefaultsOf<P>>(defaults: D2): ComponentBuilder<L, P, E, D2, F, S>;
798
+ defaultProps<const D2 extends AuthoredDefaultsOf<P>>(defaults: D2): ComponentBuilder<L, P, E, UnconditionalDefaults<D2>, F, S>;
792
799
  /**
793
800
  * The layers whose element props this component ALSO exposes, keyed by layer.
794
801
  *
@@ -832,34 +839,23 @@ F extends string = never, /** The states `.states()` declared — what the rende
832
839
  */
833
840
  renderCanvas<E2 extends E = E>(fn: (args: Without<RenderArgs<L, P, S>, keyof E2> & E2) => unknown): ComponentBuilder<L, P, E, D, F, S>;
834
841
  /**
835
- * What a preview of this component STARTS from — the props a single tile is rendered with.
842
+ * What a preview tile renders with. `defaultProps` takes what `.defaultProps` does: a record, or a
843
+ * list of rules.
836
844
  *
837
- * Only `defaultProps`, because the grid itself is derived: every axis a matrix could sweep is
838
- * already declared in `styles`, as the conditions the rules apply under, and the modifier columns
839
- * are the `_hover`/`_active` blocks those rules contain. An authored matrix restated all of it and
840
- * could disagree with it — a variant added to `styles` and forgotten in the matrix simply never
841
- * appeared. See `previewMatrix`.
845
+ * Only content, because the grid itself is derived: every axis a matrix could sweep is declared in
846
+ * `styles`, as the conditions the rules apply under, and the modifier columns are the
847
+ * `_hover`/`_active` blocks those rules contain. See `previewMatrix`.
848
+ *
849
+ * A rule with `when` gives the content a cell needs at one point on an axis:
850
+ * `{ when: { type: 'image' }, props: { src } }` gives the image kind a photo. A condition naming a
851
+ * prop no rule styles makes that prop an axis too, with the value named.
842
852
  */
843
853
  preview(spec: {
844
- defaultProps?: DefaultsOf<P>;
845
- /**
846
- * Preview props that hold only under a condition — the same `when` grammar a style rule uses.
847
- * The grid is derived from the styles; what it cannot derive is the CONTENT a cell needs at a
848
- * given point on an axis: `{ when: { type: 'image' }, props: { src } }` gives the image kind a
849
- * photo, `{ when: { invalid: true }, props: { selected: true } }` shows the invalid state checked.
850
- * A condition naming a prop no rule styles makes that prop an axis too, with the value named —
851
- * so a preview can say a boolean is worth a column the way a rule would.
852
- */
853
- when?: readonly PreviewCondition<P>[];
854
+ defaultProps?: PropsOrRules<DefaultsOf<P>>;
854
855
  }): ComponentBuilder<L, P, E, D, F, S>;
855
856
  toStored(): Record<string, unknown>;
856
857
  assignPath(path: string): void;
857
858
  }
858
- /** Preview props that hold under a condition on the cell's props — see `preview({ when })`. */
859
- interface PreviewCondition<P extends PropMap = PropMap> {
860
- readonly when: Partial<Record<keyof P & string, unknown>>;
861
- readonly props: DefaultsOf<P>;
862
- }
863
859
  /** A component under authorship, with its declarations forgotten — what a registry holds. */
864
860
  type AuthoredComponent = ComponentBuilder<LayerMap, PropMap, unknown, unknown>;
865
861
  /**
@@ -901,4 +897,4 @@ interface DefineComponent {
901
897
  declare function definePropsFirst<const P extends PropMap<LayerPath>>(props: P & NoInfer<{ [K in keyof P]: Exact<P[K], AuthoredProp<LayerPath>> }>): ComponentBuilder<LayerMap, P>;
902
898
  declare const defineComponent: DefineComponent;
903
899
  //#endregion
904
- export { AsInput, AuthoredComponent, AuthoredDefaultsOf, AuthoredProp, ComponentBuilder, ComponentPropsOf, GlyphComponent, GlyphValue, LayerElement, LayerMap, LinkedHandle, ModifierProps, PhantomCallProps, PhantomReadProps, PropCondition, PropDecl, PropMap, RegisteredMembers, RenderArgs, SlotCodeForm, SlotDecl, StateMap, StyleProps, StyleRuleInput, defineComponent };
900
+ export { AsInput, AuthoredComponent, AuthoredDefaultsOf, AuthoredProp, ComponentBuilder, ComponentPropsOf, GlyphComponent, GlyphValue, LayerElement, LayerMap, LinkedHandle, ModifierProps, PhantomCallProps, PhantomReadProps, PropCondition, PropDecl, PropMap, RegisteredMembers, RenderArgs, SlotCodeForm, SlotDecl, StateMap, StyleProps, StyleRuleInput, UnconditionalDefaults, defineComponent };
@@ -1,8 +1,7 @@
1
- import { canonicalCondition, isRef, kindOf, memberRef, pathOf } from "../../framework/utils/refs.js";
1
+ import { isRef, kindOf, memberRef, pathOf } from "../../framework/utils/refs.js";
2
2
  import { glyphIdentityOf, glyphPackageRefOf, isComponentType } from "./glyph-identity.js";
3
- import "../../framework/preview-store.js";
4
3
  import { assertComponentSourceInput } from "./component-source-fields.js";
5
- import { isConditionalDefault, storedDefaults } from "./conditional-defaults.js";
4
+ import { keyedPropRules, storedDefaults } from "./prop-rules.js";
6
5
  import { Component, layerRef, storedStyleRules } from "./Component.js";
7
6
  //#region src/entities/system/defineComponent.ts
8
7
  function build(body) {
@@ -25,10 +24,7 @@ function build(body) {
25
24
  metadata: (meta) => next(meta),
26
25
  render: () => handle,
27
26
  renderCanvas: () => handle,
28
- preview: (spec) => {
29
- if (spec?.defaultProps === void 0 && !spec?.when?.length) return handle;
30
- return next({ preview: spec });
31
- },
27
+ preview: (spec) => spec?.defaultProps === void 0 ? handle : next({ preview: spec.defaultProps }),
32
28
  toStored: () => toStored(body),
33
29
  assignPath(path) {
34
30
  handle.path = path;
@@ -146,7 +142,6 @@ function storableElement(value) {
146
142
  */
147
143
  function storableValue(value) {
148
144
  if (value === null) return null;
149
- if (isConditionalDefault(value)) throw new Error("`conditionalDefault` is a default, so it goes in `.defaultProps` or `.extend({ defaultProps })`. A preview conditions its props with `.preview({ when: [{ when, props }] })`.");
150
145
  const type = typeof value;
151
146
  if (type === "string" || type === "number" || type === "boolean") return value;
152
147
  if (isComponentType(value)) return storableComponentRef(value);
@@ -376,21 +371,19 @@ function toStored(body) {
376
371
  assertComponentSourceInput(body);
377
372
  const stored = { ...body };
378
373
  if (body.preview !== void 0) {
379
- const authored = body.preview;
380
374
  const keyed = {};
381
- const defaults = authored.defaultProps !== void 0 ? storableProps(authored.defaultProps) : void 0;
382
- if (defaults !== void 0) keyed["*"] = {
383
- when: null,
384
- props: defaults
385
- };
386
- for (const { when, props } of authored.when ?? []) keyed[canonicalCondition(when)] = {
387
- when,
388
- props: storableProps(props) ?? {}
389
- };
375
+ for (const [key, { when, props }] of keyedPropRules(body.preview)) {
376
+ const storable = storableProps(props);
377
+ if (storable === void 0 && when === null) continue;
378
+ keyed[key] = {
379
+ when,
380
+ props: storable ?? {}
381
+ };
382
+ }
390
383
  if (Object.keys(keyed).length === 0) delete stored.preview;
391
384
  else stored.preview = keyed;
392
385
  }
393
- Object.assign(stored, storedDefaults(body));
386
+ if (body.defaultProps !== void 0) Object.assign(stored, storedDefaults(body.defaultProps));
394
387
  if (body.props && typeof body.props === "object") {
395
388
  const props = {};
396
389
  for (const [name, decl] of Object.entries(body.props)) props[name] = storedProp(decl, body.layers);
@@ -2,6 +2,7 @@ import { collectRefs, isMintedRow, ref, sourceOf, splitRef } from "../../framewo
2
2
  import { isConfigRejection, rejectData } from "../../framework/rejection.js";
3
3
  import { blockedBy } from "../../framework/plan.js";
4
4
  import { excludedCondition } from "../../framework/source-integrity.js";
5
+ import { sameOrigin } from "../../framework/Config.js";
5
6
  import { PIN_KIND, covers, declarationOf, entriesFor, excludePaths, excludes, includePaths, narrowDeclaration } from "./link-declaration.js";
6
7
  import { planCopy, sourceEntries } from "./copy-plan.js";
7
8
  //#region src/entities/system/link-plan.ts
@@ -40,7 +41,7 @@ function sharedWithOrigin({ config, source, slug, kind, path }) {
40
41
  kind,
41
42
  path
42
43
  });
43
- if (!theirs || !mine || theirs.key !== mine.key) return void 0;
44
+ if (!theirs || !mine || !sameOrigin(theirs, mine)) return void 0;
44
45
  return theirs.pin.displayName ?? theirs.key;
45
46
  }
46
47
  /** Another pin whose declaration covers the path and whose source has it: the pin the path would
@@ -0,0 +1,10 @@
1
+ //#region src/entities/system/prop-rules.d.ts
2
+ /** One rule: `props` hold while every prop `when` names has its value, or always with no `when`. */
3
+ interface PropRule<Props = Record<string, unknown>> {
4
+ readonly when?: Readonly<Record<string, unknown>> | null;
5
+ readonly props: Props;
6
+ }
7
+ /** The props given unconditionally, or a list of rules. */
8
+ type PropsOrRules<Props> = Props | readonly PropRule<Props>[];
9
+ //#endregion
10
+ export { PropRule, PropsOrRules };
@@ -0,0 +1,42 @@
1
+ import { canonicalCondition } from "../../framework/utils/refs.js";
2
+ //#region src/entities/system/prop-rules.ts
3
+ /**
4
+ * Rules keyed by condition, `*` for the unconditional one. Two rules with the same condition are
5
+ * one: their props merge, the later value winning, and the merged rule takes the later one's place.
6
+ */
7
+ function keyedPropRules(authored) {
8
+ const rules = Array.isArray(authored) ? authored : [{ props: authored }];
9
+ const keyed = /* @__PURE__ */ new Map();
10
+ for (const rule of rules) {
11
+ const key = canonicalCondition(rule.when);
12
+ const prior = keyed.get(key);
13
+ keyed.delete(key);
14
+ keyed.set(key, {
15
+ when: key === "*" ? null : { ...rule.when },
16
+ props: {
17
+ ...prior?.props,
18
+ ...rule.props
19
+ }
20
+ });
21
+ }
22
+ return keyed;
23
+ }
24
+ /**
25
+ * Authored defaults as stored: the unconditional rule in `defaultProps`, each other in
26
+ * `defaultPropsWhen`. A plain record stores as it was written, with no `defaultPropsWhen`.
27
+ */
28
+ function storedDefaults(authored) {
29
+ if (!Array.isArray(authored)) return { defaultProps: { ...authored } };
30
+ const keyed = keyedPropRules(authored);
31
+ const defaultPropsWhen = {};
32
+ for (const [key, { when, props }] of keyed) if (when) defaultPropsWhen[key] = {
33
+ when,
34
+ props
35
+ };
36
+ return {
37
+ defaultProps: keyed.get("*")?.props ?? {},
38
+ ...Object.keys(defaultPropsWhen).length > 0 ? { defaultPropsWhen } : {}
39
+ };
40
+ }
41
+ //#endregion
42
+ export { keyedPropRules, storedDefaults };
@@ -1972,10 +1972,7 @@ declare class Config {
1972
1972
  * kind's `sourceOrigin`, so the two pins may name different versions. `undefined` for a source of
1973
1973
  * one hop, or one whose system no pin here reaches.
1974
1974
  *
1975
- * The two pins match when they agree on every origin field both set, and share at least one. Not on
1976
- * the whole key: a pin by path is stamped with its sibling's id when it's pushed, so the Bridge's
1977
- * `yos` reads `configPath` and `systemId` where this config's pin to the same system names only the
1978
- * id — and a `configPath` is relative to the config holding it, so it can't be compared across two.
1975
+ * The two pins match by {@link sameOriginFields}.
1979
1976
  */
1980
1977
  private directRoute;
1981
1978
  /**
@@ -2569,11 +2566,15 @@ declare class Config {
2569
2566
  * to a system linked to a foundation answers with the foundation. A row a materialized artifact
2570
2567
  * flattened to a literal has no pointer left, so the pin whose `link` declaration covers the path
2571
2568
  * stands in, provided the row still reads as the source's ({@link matchesSource}); one the
2572
- * publisher changed is its own. Two answers with one `key` are one entity reached by two routes,
2573
- * whatever versions the pins name: the key is the pin kind's `sourceOrigin`, not its identity.
2569
+ * publisher changed is its own, and so is a linked row this config lays a value of its own over: the
2570
+ * Bridge's `spectrum/blue/0` points at Yahoo OS's with the Bridge's colours on it, which is the
2571
+ * Bridge's entity, not a second route to Yahoo OS's. Two answers that are {@link sameOrigin} are one
2572
+ * entity reached by two routes, whatever versions the pins name: they compare the pin kind's
2573
+ * `sourceOrigin`, not its identity.
2574
2574
  *
2575
2575
  * `pin` is the pin entity at the hop where the answer was found, for a surface that names the
2576
- * origin the way it names any source.
2576
+ * origin the way it names any source. `key` is its origin fields joined, for a surface with no
2577
+ * name to show.
2577
2578
  */
2578
2579
  originOf({
2579
2580
  kind,
@@ -2581,10 +2582,7 @@ declare class Config {
2581
2582
  }: {
2582
2583
  kind: string;
2583
2584
  path: string;
2584
- }): {
2585
- key: string;
2586
- pin: Entity;
2587
- } | undefined;
2585
+ }): Origin | undefined;
2588
2586
  /**
2589
2587
  * The pin whose `link` declaration covers `kind:path` while the row there still reads as that
2590
2588
  * source's: how a row a materialized artifact flattened to a literal is traced back to the pin it
@@ -2899,5 +2897,14 @@ declare class Config {
2899
2897
  * links from the provided sources. A declared `linkedKind` with no provided source throws. */
2900
2898
  protected hydrateFrom(stored: StoredConfig, options?: HydrationOptions): this;
2901
2899
  }
2900
+ /** Where an entity originates ({@link Config.originOf}): the pin at the hop that answered, and its origin fields. */
2901
+ interface Origin {
2902
+ readonly key: string;
2903
+ /** The pin kind's `sourceOrigin` fields as the pin reads them, `''` where unset. */
2904
+ readonly fields: readonly string[];
2905
+ readonly pin: Entity;
2906
+ }
2907
+ /** Whether two {@link Config.originOf} answers are one origin, by {@link sameOriginFields}. */
2908
+ declare function sameOrigin(a: Origin, b: Origin): boolean;
2902
2909
  //#endregion
2903
- export { AppliedOp, AttachedSources, BatchReceipt, Config, CssGrammar, Edition, HydrationOptions, ListOptions, LiveConfig, Patch, RecordedSource, SERIALIZED_CONFIG_VERSION, SerializedConfig, SettledSource, SkippedOp, SourceCollision, SourcesBySlug, StoredConfig, SubPatch, TreeNode };
2910
+ export { AppliedOp, AttachedSources, BatchReceipt, Config, CssGrammar, Edition, HydrationOptions, ListOptions, LiveConfig, Origin, Patch, RecordedSource, SERIALIZED_CONFIG_VERSION, SerializedConfig, SettledSource, SkippedOp, SourceCollision, SourcesBySlug, StoredConfig, SubPatch, TreeNode, sameOrigin };
@@ -3992,10 +3992,7 @@ var Config = class Config {
3992
3992
  * kind's `sourceOrigin`, so the two pins may name different versions. `undefined` for a source of
3993
3993
  * one hop, or one whose system no pin here reaches.
3994
3994
  *
3995
- * The two pins match when they agree on every origin field both set, and share at least one. Not on
3996
- * the whole key: a pin by path is stamped with its sibling's id when it's pushed, so the Bridge's
3997
- * `yos` reads `configPath` and `systemId` where this config's pin to the same system names only the
3998
- * id — and a `configPath` is relative to the config holding it, so it can't be compared across two.
3995
+ * The two pins match by {@link sameOriginFields}.
3999
3996
  */
4000
3997
  directRoute(source) {
4001
3998
  const hops = sourceHops(source);
@@ -4009,8 +4006,7 @@ var Config = class Config {
4009
4006
  if (theirs === void 0) continue;
4010
4007
  const direct = this.sourceSlugs().find((slug) => {
4011
4008
  const mine = this.sourceFields(sourceKind, slug, "sourceOrigin");
4012
- const shared = theirs.flatMap((value, field) => value !== "" && mine?.[field] ? [field] : []);
4013
- return shared.length > 0 && shared.every((field) => theirs[field] === mine?.[field]);
4009
+ return mine !== void 0 && sameOriginFields(theirs, mine);
4014
4010
  });
4015
4011
  if (direct !== void 0) return direct;
4016
4012
  }
@@ -5519,15 +5515,21 @@ var Config = class Config {
5519
5515
  * to a system linked to a foundation answers with the foundation. A row a materialized artifact
5520
5516
  * flattened to a literal has no pointer left, so the pin whose `link` declaration covers the path
5521
5517
  * stands in, provided the row still reads as the source's ({@link matchesSource}); one the
5522
- * publisher changed is its own. Two answers with one `key` are one entity reached by two routes,
5523
- * whatever versions the pins name: the key is the pin kind's `sourceOrigin`, not its identity.
5518
+ * publisher changed is its own, and so is a linked row this config lays a value of its own over: the
5519
+ * Bridge's `spectrum/blue/0` points at Yahoo OS's with the Bridge's colours on it, which is the
5520
+ * Bridge's entity, not a second route to Yahoo OS's. Two answers that are {@link sameOrigin} are one
5521
+ * entity reached by two routes, whatever versions the pins name: they compare the pin kind's
5522
+ * `sourceOrigin`, not its identity.
5524
5523
  *
5525
5524
  * `pin` is the pin entity at the hop where the answer was found, for a surface that names the
5526
- * origin the way it names any source.
5525
+ * origin the way it names any source. `key` is its origin fields joined, for a surface with no
5526
+ * name to show.
5527
5527
  */
5528
5528
  originOf({ kind, path }) {
5529
5529
  const held = this.resolve(kind, path);
5530
5530
  if (!held) return void 0;
5531
+ const stored = this.storedBody(kind, path);
5532
+ if (isExtendedRef(stored) && Object.keys(stored.extend).length > 0) return;
5531
5533
  let through = held.borrowedFrom;
5532
5534
  if (through === void 0 && held.copiedThrough !== void 0) {
5533
5535
  if (this.copyDiffers({
@@ -5543,11 +5545,12 @@ var Config = class Config {
5543
5545
  });
5544
5546
  if (upstream) return upstream;
5545
5547
  for (const sourceKind of this.sourceKinds()) {
5546
- const key = this.sourceKey(sourceKind, through, "sourceOrigin");
5548
+ const fields = this.sourceFields(sourceKind, through, "sourceOrigin");
5547
5549
  const pin = this.resolve(sourceKind, through);
5548
- if (key === void 0 || !pin) continue;
5550
+ if (fields === void 0 || !pin) continue;
5549
5551
  return {
5550
- key,
5552
+ key: fields.join("\0"),
5553
+ fields,
5551
5554
  pin
5552
5555
  };
5553
5556
  }
@@ -6325,6 +6328,21 @@ function extendBody(base, delta) {
6325
6328
  /** The paths each config is reading through a re-pointed link, for {@link Config.laidLinkedBody} to
6326
6329
  * find a loop by. Keyed on the config's state, which every read of one config shares. */
6327
6330
  const redirecting = /* @__PURE__ */ new WeakMap();
6331
+ /** Whether two {@link Config.originOf} answers are one origin, by {@link sameOriginFields}. */
6332
+ function sameOrigin(a, b) {
6333
+ return sameOriginFields(a.fields, b.fields);
6334
+ }
6335
+ /**
6336
+ * Whether two pins' `sourceOrigin` fields name one system: they agree on every field both set, and
6337
+ * share at least one. Not the whole key: a pin by path is stamped with its sibling's id when it's
6338
+ * pushed, so the Bridge's `yos` reads `configPath` and `systemId` where a pin to the same system by id
6339
+ * names only the id — and a `configPath` is relative to the config holding it, so it can't be compared
6340
+ * across two.
6341
+ */
6342
+ function sameOriginFields(theirs, mine) {
6343
+ const shared = theirs.flatMap((value, field) => value !== "" && mine[field] ? [field] : []);
6344
+ return shared.length > 0 && shared.every((field) => theirs[field] === mine[field]);
6345
+ }
6328
6346
  /**
6329
6347
  * The entity an `extend` re-points its path to, and the rest of it: a link's `override` written as a
6330
6348
  * ref lands in the stored `extend` as its `__ref`, beside whatever the `extend` verb adds.
@@ -6539,4 +6557,4 @@ function loadGroupChange(meta, kind, path, raw, phase = "author") {
6539
6557
  };
6540
6558
  }
6541
6559
  //#endregion
6542
- export { Config, SERIALIZED_CONFIG_VERSION };
6560
+ export { Config, SERIALIZED_CONFIG_VERSION, sameOrigin };
@@ -22,5 +22,32 @@ declare function defaultsFor({
22
22
  body: unknown;
23
23
  given: Readonly<Record<string, unknown>>;
24
24
  }): Record<string, unknown>;
25
+ /** One condition a prop defaults under, seen from that prop: the value it takes while `when` holds. */
26
+ interface PropDefaultCondition<V = unknown> {
27
+ readonly when: Readonly<Record<string, unknown>>;
28
+ readonly value: V;
29
+ }
30
+ /** The conditions one prop defaults under, in stored order, each with the value it gives the prop. */
31
+ declare function propDefaultConditions({
32
+ defaultPropsWhen,
33
+ prop
34
+ }: {
35
+ defaultPropsWhen: Readonly<Record<string, StoredDefaultCondition>> | undefined;
36
+ prop: string;
37
+ }): PropDefaultCondition[];
38
+ /**
39
+ * `defaultPropsWhen` with one prop's conditions replaced by `entries`: the prop leaves every entry it
40
+ * was in, an entry left setting nothing goes, and each new condition joins the entry already keyed
41
+ * by it or is added at the end. What a surface editing one prop's conditions writes back whole.
42
+ */
43
+ declare function withPropDefaultConditions({
44
+ defaultPropsWhen,
45
+ prop,
46
+ entries
47
+ }: {
48
+ defaultPropsWhen: Readonly<Record<string, StoredDefaultCondition>> | undefined;
49
+ prop: string;
50
+ entries: readonly PropDefaultCondition[];
51
+ }): Record<string, StoredDefaultCondition>;
25
52
  //#endregion
26
- export { StoredDefaultCondition, defaultsFor, storedDefaultConditions };
53
+ export { PropDefaultCondition, StoredDefaultCondition, defaultsFor, propDefaultConditions, storedDefaultConditions, withPropDefaultConditions };
@@ -1,5 +1,5 @@
1
1
  import { isPlainObject } from "./utils/field-path.js";
2
- import { refLeaf } from "./utils/refs.js";
2
+ import { canonicalCondition, refLeaf } from "./utils/refs.js";
3
3
  //#region src/framework/default-conditions.ts
4
4
  /**
5
5
  * A component's stored `defaultPropsWhen` collection, read off a body with nothing else in scope:
@@ -31,5 +31,42 @@ function defaultsFor({ body, given }) {
31
31
  })) Object.assign(out, props);
32
32
  return out;
33
33
  }
34
+ /** The conditions one prop defaults under, in stored order, each with the value it gives the prop. */
35
+ function propDefaultConditions({ defaultPropsWhen, prop }) {
36
+ return Object.values(defaultPropsWhen ?? {}).flatMap(({ when, props }) => props && Object.hasOwn(props, prop) ? [{
37
+ when,
38
+ value: props[prop]
39
+ }] : []);
40
+ }
41
+ /**
42
+ * `defaultPropsWhen` with one prop's conditions replaced by `entries`: the prop leaves every entry it
43
+ * was in, an entry left setting nothing goes, and each new condition joins the entry already keyed
44
+ * by it or is added at the end. What a surface editing one prop's conditions writes back whole.
45
+ */
46
+ function withPropDefaultConditions({ defaultPropsWhen, prop, entries }) {
47
+ const out = {};
48
+ for (const [key, { when, props }] of Object.entries(defaultPropsWhen ?? {})) {
49
+ const { [prop]: _left, ...rest } = props ?? {};
50
+ if (Object.keys(rest).length > 0) out[key] = {
51
+ when,
52
+ props: rest
53
+ };
54
+ }
55
+ for (const entry of entries) {
56
+ const key = canonicalCondition(entry.when);
57
+ const existing = out[key];
58
+ out[key] = existing ? {
59
+ ...existing,
60
+ props: {
61
+ ...existing.props,
62
+ [prop]: entry.value
63
+ }
64
+ } : {
65
+ when: { ...entry.when },
66
+ props: { [prop]: entry.value }
67
+ };
68
+ }
69
+ return out;
70
+ }
34
71
  //#endregion
35
- export { defaultsFor, storedDefaultConditions };
72
+ export { defaultsFor, propDefaultConditions, storedDefaultConditions, withPropDefaultConditions };
@@ -788,7 +788,7 @@ declare function validateComponentProps(config: Config, componentPath: string, p
788
788
  * serialization is for storage and the wire.
789
789
  */
790
790
  declare function previewDefaults(config: Config, path: string,
791
- /** The cell's own prop values. Given, every `preview({ when })` entry that holds for the cell —
791
+ /** The cell's own prop values. Given, every conditional preview rule that holds for the cell —
792
792
  * tested against the defaults under these values, as a rule's `when` is — adds its props. */
793
793
 
794
794
  cell?: Record<string, unknown>,
@@ -799,7 +799,7 @@ options?: {
799
799
  readonly firstVariants?: boolean;
800
800
  }): Record<string, unknown>;
801
801
  /**
802
- * Only what the `preview({ when })` entries add for a cell — the props of every condition that holds
802
+ * Only what the conditional preview rules add for a cell — the props of every condition that holds
803
803
  * for the cell's values over the defaults, later entries over earlier. Nothing else: a caller that
804
804
  * merges these onto a cell's own props is adding the content the author tied to that coordinate,
805
805
  * without restating defaults that would shadow an edit.
@@ -1996,7 +1996,7 @@ function firstVariantValues(props) {
1996
1996
  return out;
1997
1997
  }
1998
1998
  /**
1999
- * Only what the `preview({ when })` entries add for a cell — the props of every condition that holds
1999
+ * Only what the conditional preview rules add for a cell — the props of every condition that holds
2000
2000
  * for the cell's values over the defaults, later entries over earlier. Nothing else: a caller that
2001
2001
  * merges these onto a cell's own props is adding the content the author tied to that coordinate,
2002
2002
  * without restating defaults that would shadow an edit.