@yahoo/uds-create-config 5.55.0 → 5.55.1

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.
@@ -755,6 +755,17 @@ function withNone(value, declaration) {
755
755
  if (declaration?.required === true) return value;
756
756
  return z.union([value, z.literal(false)]);
757
757
  }
758
+ /** Optional values build their domain on first use, not while the bag enumerates its keys. */
759
+ function optionalBagValue(build) {
760
+ let schema;
761
+ return z.unknown().transform((input, ctx) => {
762
+ schema ??= build();
763
+ const result = schema.safeParse(input);
764
+ if (result.success) return result.data;
765
+ for (const issue of result.error.issues) ctx.addIssue({ ...issue });
766
+ return z.NEVER;
767
+ }).optional();
768
+ }
758
769
  /** One element's bag, built. Reached only through the memo above. */
759
770
  function buildBagFor(config, layerElement) {
760
771
  const shape = { ...stylePropShape(config) };
@@ -762,13 +773,15 @@ function buildBagFor(config, layerElement) {
762
773
  const glyphProps = component ? glyphPropsOf(config, component) : {};
763
774
  for (const [name, decl] of Object.entries(component ? resolveComponentProps(config, component) : {})) {
764
775
  const forwarded = decl?.type === "forward";
765
- const value = styleBagValue({
766
- config,
767
- declaration: decl,
768
- name,
769
- glyphLibraries: forwarded ? glyphProps[name] : void 0
776
+ shape[name] = optionalBagValue(() => {
777
+ const value = styleBagValue({
778
+ config,
779
+ declaration: decl,
780
+ name,
781
+ glyphLibraries: forwarded ? glyphProps[name] : void 0
782
+ });
783
+ return forwarded ? withNone(value, decl) : value;
770
784
  });
771
- shape[name] = (forwarded ? withNone(value, decl) : value).optional();
772
785
  }
773
786
  const declared = component ? (config.resolve("component", component)?.toJSON())?.props ?? {} : {};
774
787
  for (const [name, decl] of Object.entries(declared)) {
@@ -819,14 +832,14 @@ function stylePropShape(config) {
819
832
  const cached = stylePropShapes.get(config);
820
833
  if (cached) return cached;
821
834
  const shape = {};
822
- for (const prop of config.list("style-property")) shape[prop.path] = styleBagValue({
835
+ for (const prop of config.list("style-property")) shape[prop.path] = optionalBagValue(() => styleBagValue({
823
836
  config,
824
837
  declaration: {
825
838
  type: "styleProperty",
826
839
  value: { __ref: `style-property:${prop.path}` }
827
840
  },
828
841
  name: prop.path
829
- }).optional();
842
+ }));
830
843
  stylePropShapes.set(config, shape);
831
844
  return shape;
832
845
  }
@@ -55,7 +55,7 @@ function isCreatedComponent(config, path) {
55
55
  if (isNativeConfig(config)) return false;
56
56
  const component = Component.get(config, path);
57
57
  if (!component || component.source !== void 0) return false;
58
- const { file, export: exportName, anatomy } = component.body;
58
+ const { file, export: exportName, anatomy } = component;
59
59
  return !file && !exportName && Object.keys(anatomy ?? {}).length > 0;
60
60
  }
61
61
  /**
@@ -133,7 +133,7 @@ function componentsByFile(config) {
133
133
  if (cached) return cached;
134
134
  const byFile = /* @__PURE__ */ new Map();
135
135
  for (const component of Component.list(config)) {
136
- const { file, export: exportName } = component.body;
136
+ const { file, export: exportName } = component;
137
137
  if (!file || !exportName) continue;
138
138
  const ref = splitRef(file.__ref);
139
139
  if (ref.source) continue;
@@ -163,7 +163,7 @@ function componentRegistryImports(config, filePath) {
163
163
  const byExport = /* @__PURE__ */ new Map();
164
164
  const ambiguous = /* @__PURE__ */ new Set();
165
165
  for (const component of byFile.get(target.path) ?? []) {
166
- const { export: exportName } = component.body;
166
+ const { export: exportName } = component;
167
167
  if (!exportName) continue;
168
168
  if (byExport.has(exportName)) ambiguous.add(exportName);
169
169
  byExport.set(exportName, {
@@ -221,7 +221,8 @@ function componentModuleBuildPlan(config, filePath) {
221
221
  const parsed = splitRef(target);
222
222
  if (parsed.kind !== "component" || parsed.source) return false;
223
223
  const component = Component.get(config, parsed.path);
224
- const { file, export: exportName } = component?.body ?? {};
224
+ const file = component?.file;
225
+ const exportName = component?.export;
225
226
  if (!component || !file || !exportName) return false;
226
227
  const owner = splitRef(file.__ref);
227
228
  if (owner.kind !== "file" || owner.source) return false;
@@ -257,7 +258,7 @@ function componentModuleBuildPlan(config, filePath) {
257
258
  const file = File.get(config, next);
258
259
  if (!file?.hash) continue;
259
260
  recordEntity(`file:${next}`);
260
- const imports = file.body.imports ?? {};
261
+ const imports = file.imports ?? {};
261
262
  const registryImports = new Map(componentRegistryImports(config, next).map((entry) => [entry.specifier, entry]));
262
263
  for (const [specifier, imported] of Object.entries(imports)) {
263
264
  const registryImport = registryImports.get(specifier);
@@ -305,7 +306,7 @@ function componentModuleSource(config, componentPathOrRef) {
305
306
  if (isNativeConfig(config)) return void 0;
306
307
  const component = Component.get(config, componentPathOrRef);
307
308
  if (!component) return void 0;
308
- const { file, export: exportName } = component.body;
309
+ const { file, export: exportName } = component;
309
310
  if (!file || !exportName) {
310
311
  if (!isCreatedComponent(config, component.path)) return void 0;
311
312
  const moduleKey = createdModuleKey(config, component.path);
@@ -405,7 +406,7 @@ function cachedView(consumer, owner, slug, shadowed) {
405
406
  function linkedComponentModule(config, componentPathOrRef) {
406
407
  const component = Component.get(config, componentPathOrRef);
407
408
  if (!component) return void 0;
408
- const { file, export: exportName } = component.body;
409
+ const { file, export: exportName } = component;
409
410
  if (!file || !exportName) return void 0;
410
411
  const asked = typeof componentPathOrRef === "string" ? componentPathOrRef : componentPathOrRef.__ref;
411
412
  const parsed = asked.includes(":") ? splitRef(asked) : void 0;
@@ -422,8 +423,7 @@ function linkedComponentModule(config, componentPathOrRef) {
422
423
  const moduleKey = componentModuleKey(view, filePath);
423
424
  if (!moduleKey) return void 0;
424
425
  const authored = Component.list(owner).find((candidate) => {
425
- const fields = candidate.body;
426
- return fields.export === exportName && fields.file !== void 0 && splitRef(fields.file.__ref).path === filePath;
426
+ return candidate.export === exportName && candidate.file !== void 0 && splitRef(candidate.file.__ref).path === filePath;
427
427
  });
428
428
  return {
429
429
  source: {
@@ -280,6 +280,7 @@ function cachesFor(snapshot, sources) {
280
280
  if (found) return found;
281
281
  const bucket = {
282
282
  sources: current,
283
+ entities: /* @__PURE__ */ new Map(),
283
284
  extensions: /* @__PURE__ */ new Map(),
284
285
  overlays: /* @__PURE__ */ new Map(),
285
286
  lists: /* @__PURE__ */ new Map(),
@@ -2916,7 +2917,13 @@ var Config = class Config {
2916
2917
  return new Entity(path, withComputed, [...entry.fieldKeys, ...Object.keys(computed)]);
2917
2918
  }
2918
2919
  resolve(kind, path) {
2919
- return this.resolvedIn(kind, path)?.entity;
2920
+ const cache = this.caches.entities;
2921
+ const key = `${kind}\u0000${path}`;
2922
+ const cached = cache.get(key);
2923
+ if (cached !== void 0) return cached ?? void 0;
2924
+ const resolved = this.resolvedIn(kind, path)?.entity;
2925
+ cache.set(key, resolved ?? null);
2926
+ return resolved;
2920
2927
  }
2921
2928
  /**
2922
2929
  * Whether this config holds `target`'s entity, at the path the source spells it, through `slug`.
@@ -45,6 +45,20 @@ function interactionHolds(state, key, value) {
45
45
  if (on(state[key])) return true;
46
46
  return on(state[`${key}@${value === true || value === "true" ? "root" : String(value)}`]);
47
47
  }
48
+ const authoredBags = /* @__PURE__ */ new WeakMap();
49
+ /** Every cell reads the same authored rules under its own coordinate. */
50
+ function resolvedBag({ config, bag }) {
51
+ let bags = authoredBags.get(config);
52
+ if (!bags) {
53
+ bags = /* @__PURE__ */ new WeakMap();
54
+ authoredBags.set(config, bags);
55
+ }
56
+ const cached = bags.get(bag);
57
+ if (cached) return cached;
58
+ const resolved = authoredBag(config, resolveAppliedComposites(config, bag));
59
+ bags.set(bag, resolved);
60
+ return resolved;
61
+ }
48
62
  /**
49
63
  * A bag's values as declarations, flattening any forced modifier block one specificity step up.
50
64
  *
@@ -99,7 +113,10 @@ function layerStyles(config, path, props = {}, options = {}) {
99
113
  Object.values(styles).forEach((rule, index) => {
100
114
  if (!ruleApplies(rule.when, state)) return;
101
115
  for (const [layer, bag] of Object.entries(rule.layers ?? {})) collectBag({
102
- bag: authoredBag(config, resolveAppliedComposites(config, bag ?? {})),
116
+ bag: resolvedBag({
117
+ config,
118
+ bag: bag ?? {}
119
+ }),
103
120
  forced,
104
121
  specificity: ruleSpecificity(rule.when),
105
122
  push: (key, value, specificity) => declarations.push({
@@ -1,8 +1,8 @@
1
1
  import { Snapshot } from "./snapshot.js";
2
2
 
3
3
  //#region src/framework/memo.d.ts
4
- /** The structural shape a memo needs — any config-like that can produce its snapshot. Structural
5
- * rather than an import of `Config`, which would be a cycle (Config memoizes through this). */
4
+ /** An immutable config value. A changed snapshot or source requires a new value.
5
+ * Structural to avoid importing `Config`, which memoizes through this module. */
6
6
  interface HasSnapshot {
7
7
  readonly snapshot: Snapshot;
8
8
  /**
@@ -63,7 +63,9 @@ function sameSliceKeys({ before, after }) {
63
63
  function createSliceMemo({ kinds, compute }) {
64
64
  if (kinds.length === 0) throw new Error("createSliceMemo needs at least one kind — a computation that reads nothing has nothing to key on");
65
65
  const root = /* @__PURE__ */ new WeakMap();
66
+ const byConfig = /* @__PURE__ */ new WeakMap();
66
67
  return (config) => {
68
+ if (byConfig.has(config)) return byConfig.get(config);
67
69
  const keys = sliceKeys({
68
70
  config,
69
71
  kinds
@@ -78,9 +80,13 @@ function createSliceMemo({ kinds, compute }) {
78
80
  level = next;
79
81
  }
80
82
  const cached = level.has(RESULT) ? level.get(RESULT) : MISS;
81
- if (cached !== MISS) return cached;
83
+ if (cached !== MISS) {
84
+ byConfig.set(config, cached);
85
+ return cached;
86
+ }
82
87
  const value = compute(config);
83
88
  level.set(RESULT, value);
89
+ byConfig.set(config, value);
84
90
  return value;
85
91
  };
86
92
  }
@@ -164,6 +164,7 @@ declare function routesContent(config: Config, layers: Readonly<Record<string, u
164
164
  }): boolean;
165
165
  /**
166
166
  * Every prop a component exposes, in declaration order, projected for a control surface.
167
+ * Independent reads reuse the result for this immutable config.
167
168
  *
168
169
  * A `motion` prop is included as a scalar so it can't silently disappear from a surface that lists
169
170
  * what a component accepts; a caller with no editor for it can fall back to a generic one.
@@ -277,13 +277,25 @@ function slotProp(config, componentPath, name, decl) {
277
277
  ...routedKind === "composite" ? { compositeName: refPath(routed?.value) } : {}
278
278
  };
279
279
  }
280
+ const surfaces = /* @__PURE__ */ new WeakMap();
280
281
  /**
281
282
  * Every prop a component exposes, in declaration order, projected for a control surface.
283
+ * Independent reads reuse the result for this immutable config.
282
284
  *
283
285
  * A `motion` prop is included as a scalar so it can't silently disappear from a surface that lists
284
286
  * what a component accepts; a caller with no editor for it can fall back to a generic one.
285
287
  */
286
288
  function surfaceProps(config, componentPath, seen = /* @__PURE__ */ new Set()) {
289
+ let cache;
290
+ if (seen.size === 0) {
291
+ cache = surfaces.get(config);
292
+ if (!cache) {
293
+ cache = /* @__PURE__ */ new Map();
294
+ surfaces.set(config, cache);
295
+ }
296
+ const cached = cache.get(componentPath);
297
+ if (cached) return cached;
298
+ }
287
299
  const declared = resolveComponentProps(config, componentPath);
288
300
  const layers = (Component.get(config, componentPath)?.toJSON())?.layers;
289
301
  const props = [];
@@ -346,6 +358,7 @@ function surfaceProps(config, componentPath, seen = /* @__PURE__ */ new Set()) {
346
358
  required
347
359
  }, when));
348
360
  }
361
+ cache?.set(componentPath, props);
349
362
  return props;
350
363
  }
351
364
  /**
@@ -206,22 +206,9 @@ declare function componentSpec(config: Config, path: string): ComponentSpec;
206
206
  */
207
207
  declare function previewSpec(config: Config, path: string): RenderSpec | undefined;
208
208
  interface ExpandOptions {
209
- /**
210
- * The spec `type`s the renderer already has a component for — in practice the keys of the
211
- * codegen-emitted registry.
212
- *
213
- * A component in this set is LEFT NAMED, deliberately: its emitted module is the real thing, with
214
- * whatever its render closes over (a hook, a context, an event handler) intact, and re-deriving it
215
- * from data would be a worse copy of something that already exists. Expansion is for what the
216
- * registry CAN'T name — which is exactly a component whose structure is data and whose module was
217
- * never built.
218
- *
219
- * Omitted, everything the config describes expands, which is what a test wants and what a caller
220
- * with no registry to consult gets.
221
- *
222
- * Only `has` is asked, so a caller can answer from its registry directly instead of copying every
223
- * key into a `Set` first.
224
- */
209
+ /** Membership lookup for types the renderer can draw. A Set or a registry-backed `has` works.
210
+ * Recognized components keep their compiled modules, including hooks and handlers.
211
+ * Omitted, everything the config describes expands. */
225
212
  readonly rendered?: Pick<ReadonlySet<string>, 'has'>;
226
213
  /** The functions `$computed` values call. Absent, a computed value stays on the element for the
227
214
  * renderer, and a node repeated over a computed list emits once. */