@yahoo/uds-create-config 5.34.3 → 5.35.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.
@@ -0,0 +1,405 @@
1
+ import { kindOf, refLeaf } from "../framework/utils/refs.js";
2
+ import { declaredPropDefaults } from "../framework/projections.js";
3
+ import { graftPreviewElements } from "./preview-elements.js";
4
+ import { instanceSpec } from "../framework/render-spec.js";
5
+ import { canvasCellAt, cellAxes } from "./cell.js";
6
+ import { Component } from "../entities/system/Component.js";
7
+ //#region src/spec/bake.ts
8
+ /** The element an empty slot holds. */
9
+ const EMPTY_SLOT = "Slot";
10
+ /**
11
+ * Each component element with what the cell its props pick gives it, where it sets nothing itself
12
+ * and the component wouldn't render it by itself: a plain value on its props, content for an empty
13
+ * slot as elements. A value or a slot the element sets is its own and stays. Slot content lands as
14
+ * the cell authored it, so a component inside it is not baked in turn.
15
+ */
16
+ function withCellValues(config, spec) {
17
+ let elements;
18
+ for (const key of Object.keys(spec.elements)) {
19
+ const element = (elements ?? spec.elements)[key];
20
+ if (!element || typeof element.type !== "string") continue;
21
+ if (kindOf(element.type) !== "component") continue;
22
+ const component = Component.get(config, element.type);
23
+ if (!component) continue;
24
+ const own = element.props ?? {};
25
+ const seed = seedAt({
26
+ config,
27
+ path: component.path,
28
+ props: own
29
+ });
30
+ let props;
31
+ let slots;
32
+ for (const prop of seed.props) {
33
+ if (own[prop] !== void 0) continue;
34
+ if (holdsContent(elements ?? spec.elements, element, prop)) continue;
35
+ const fills = seed.root.slots?.[prop];
36
+ if (!fills) {
37
+ props ??= { ...own };
38
+ props[prop] = seed.root.props?.[prop];
39
+ continue;
40
+ }
41
+ elements ??= { ...spec.elements };
42
+ const added = [];
43
+ for (const fill of fills) {
44
+ const to = graftSubtree({
45
+ source: seed.spec,
46
+ from: fill,
47
+ into: elements,
48
+ root: key
49
+ }).get(fill);
50
+ if (to) added.push(to);
51
+ }
52
+ slots ??= { ...element.slots ?? {} };
53
+ slots[prop] = added;
54
+ }
55
+ if (!props && !slots) continue;
56
+ elements ??= { ...spec.elements };
57
+ elements[key] = {
58
+ ...element,
59
+ ...props ? { props } : {},
60
+ ...slots ? { slots } : {}
61
+ };
62
+ }
63
+ return elements ? {
64
+ ...spec,
65
+ elements
66
+ } : spec;
67
+ }
68
+ /** The component placed at the cell `props` pick, with what that cell bakes in: the seed a slot of
69
+ * an instance is restored from. */
70
+ function cellSeedSpec({ config, path, props }) {
71
+ const axes = new Set(cellAxes(config, path));
72
+ const picked = Object.fromEntries(Object.entries(props).filter(([prop]) => axes.has(prop)));
73
+ let byKey = bakedByConfig.get(config);
74
+ if (!byKey) {
75
+ byKey = /* @__PURE__ */ new Map();
76
+ bakedByConfig.set(config, byKey);
77
+ }
78
+ const key = `${path}\u0000${stable(picked)}`;
79
+ const hit = byKey.get(key);
80
+ if (hit) return hit;
81
+ const made = withCellValues(config, instanceSpec(path, picked));
82
+ byKey.set(key, made);
83
+ return made;
84
+ }
85
+ const bakedByConfig = /* @__PURE__ */ new WeakMap();
86
+ const NO_EDITS = [];
87
+ /** The content props a placed element has changed away from what its cell bakes: what a panel
88
+ * marks edited, and what a reset puts back. The axes that pick the cell are never content. */
89
+ function editedCellContent({ config, spec, elementKey }) {
90
+ const at = bakedAt({
91
+ config,
92
+ spec,
93
+ elementKey
94
+ });
95
+ if (!at) return NO_EDITS;
96
+ const out = [];
97
+ for (const prop of at.content) {
98
+ if (at.shows(spec, prop)) continue;
99
+ const slot = fillsOf(at.root, prop).length > 0;
100
+ out.push({
101
+ prop,
102
+ slot,
103
+ cellValue: slot ? void 0 : at.root.props?.[prop]
104
+ });
105
+ }
106
+ return out.length > 0 ? out : NO_EDITS;
107
+ }
108
+ /**
109
+ * After a placed element's props change, the content props it still shows as its old cell bakes
110
+ * them, which take what the new cell bakes. Content that differs from the old cell is the designer's
111
+ * own and stays. Empty unless a prop that picks the cell changed.
112
+ */
113
+ function followingContent({ config, spec, elementKey, before }) {
114
+ const now = bakedAt({
115
+ config,
116
+ spec,
117
+ elementKey
118
+ });
119
+ if (!now) return NO_PROPS;
120
+ const after = spec.elements[elementKey]?.props ?? {};
121
+ if (now.axes.every((prop) => stable(before[prop]) === stable(after[prop]))) return NO_PROPS;
122
+ const was = bakedAt({
123
+ config,
124
+ spec,
125
+ elementKey,
126
+ props: before
127
+ });
128
+ if (!was) return NO_PROPS;
129
+ const out = [];
130
+ for (const prop of new Set([...was.content, ...now.content])) if (was.shows(spec, prop) && !now.shows(spec, prop)) out.push(prop);
131
+ return out.length > 0 ? out : NO_PROPS;
132
+ }
133
+ const NO_PROPS = [];
134
+ /** `spec` with the content {@link followingContent} names swapped for what the new cell bakes.
135
+ * Answers `spec` itself when nothing follows. */
136
+ function followCellContent({ config, spec, elementKey, before }) {
137
+ const props = followingContent({
138
+ config,
139
+ spec,
140
+ elementKey,
141
+ before
142
+ });
143
+ const now = props.length > 0 ? bakedAt({
144
+ config,
145
+ spec,
146
+ elementKey
147
+ }) : null;
148
+ if (!now) return spec;
149
+ let out = spec;
150
+ for (const prop of props) out = replaceContent({
151
+ spec: out,
152
+ elementKey,
153
+ prop,
154
+ from: now
155
+ });
156
+ return out;
157
+ }
158
+ /** One content prop of a placed element put back to what its cell bakes. */
159
+ function resetCellContent({ config, spec, elementKey, prop }) {
160
+ const at = bakedAt({
161
+ config,
162
+ spec,
163
+ elementKey
164
+ });
165
+ if (!at || at.shows(spec, prop)) return spec;
166
+ return replaceContent({
167
+ spec,
168
+ elementKey,
169
+ prop,
170
+ from: at
171
+ });
172
+ }
173
+ function bakedAt({ config, spec, elementKey, props }) {
174
+ const element = spec.elements[elementKey];
175
+ if (!element || typeof element.type !== "string") return null;
176
+ const component = Component.get(config, element.type);
177
+ if (!component) return null;
178
+ const path = component.path;
179
+ const picking = props ?? element.props ?? {};
180
+ const baked = cellSeedSpec({
181
+ config,
182
+ path,
183
+ props: picking
184
+ });
185
+ const root = baked.elements[baked.root];
186
+ const axes = cellAxes(config, path);
187
+ const axisSet = new Set(axes);
188
+ return {
189
+ baked,
190
+ root,
191
+ axes,
192
+ content: seedAt({
193
+ config,
194
+ path,
195
+ props: picking
196
+ }).props.filter((prop) => !axisSet.has(prop)),
197
+ shows: (against, prop) => {
198
+ const el = against.elements[elementKey];
199
+ if (!el) return false;
200
+ return contentOf(against.elements, el, prop) === contentOf(baked.elements, root, prop);
201
+ }
202
+ };
203
+ }
204
+ /** `spec` with `prop`'s content on the element swapped for what `from` bakes. */
205
+ function replaceContent({ spec, elementKey, prop, from }) {
206
+ const element = spec.elements[elementKey];
207
+ const elements = { ...spec.elements };
208
+ const props = { ...element.props ?? {} };
209
+ const value = from.root.props?.[prop];
210
+ if (value === void 0) delete props[prop];
211
+ else props[prop] = value;
212
+ const asChildren = prop === "children" && (element.children?.length ?? 0) > 0;
213
+ for (const fill of fillsOf(element, prop)) for (const key of subtreeKeys(spec.elements, fill)) delete elements[key];
214
+ const grafted = [];
215
+ for (const fill of from.root.slots?.[prop] ?? []) {
216
+ const root = graftSubtree({
217
+ source: from.baked,
218
+ from: fill,
219
+ into: elements,
220
+ root: elementKey
221
+ }).get(fill);
222
+ if (root) grafted.push(root);
223
+ }
224
+ if (asChildren) elements[elementKey] = {
225
+ ...element,
226
+ props,
227
+ children: grafted
228
+ };
229
+ else {
230
+ const slots = { ...element.slots ?? {} };
231
+ if (grafted.length > 0) slots[prop] = grafted;
232
+ else delete slots[prop];
233
+ elements[elementKey] = {
234
+ ...element,
235
+ props,
236
+ slots
237
+ };
238
+ }
239
+ return {
240
+ ...spec,
241
+ elements
242
+ };
243
+ }
244
+ /** The keys of the subtree at `from`, itself included. */
245
+ function subtreeKeys(elements, from) {
246
+ const seen = /* @__PURE__ */ new Set();
247
+ const visit = (key) => {
248
+ if (seen.has(key) || !elements[key]) return;
249
+ seen.add(key);
250
+ const element = elements[key];
251
+ for (const child of element?.children ?? []) visit(child);
252
+ for (const fills of Object.values(element?.slots ?? {})) for (const fill of fills) visit(fill);
253
+ };
254
+ visit(from);
255
+ return seen;
256
+ }
257
+ const seedsByConfig = /* @__PURE__ */ new WeakMap();
258
+ function seedAt({ config, path, props }) {
259
+ const cell = canvasCellAt({
260
+ config,
261
+ path,
262
+ props: withoutStaleValues(config, path, props)
263
+ });
264
+ let byKey = seedsByConfig.get(config);
265
+ if (!byKey) {
266
+ byKey = /* @__PURE__ */ new Map();
267
+ seedsByConfig.set(config, byKey);
268
+ }
269
+ const key = `${path}\u0000${JSON.stringify(cell.coordinate)}`;
270
+ const hit = byKey.get(key);
271
+ if (hit) return hit;
272
+ const values = {};
273
+ for (const [prop, value] of Object.entries(cell.props)) if (!givesItself(config, path, prop, value)) values[prop] = value;
274
+ const spec = graftPreviewElements(instanceSpec(path, values), values);
275
+ const root = spec.elements[spec.root];
276
+ const made = {
277
+ spec,
278
+ root,
279
+ props: Object.keys(values).filter((prop) => root.props?.[prop] !== void 0 || (root.slots?.[prop]?.length ?? 0) > 0)
280
+ };
281
+ byKey.set(key, made);
282
+ return made;
283
+ }
284
+ /** Whether the element already holds content for `prop`: slot fills other than an empty `Slot`, or
285
+ * structural children for `children`. */
286
+ function holdsContent(elements, element, prop) {
287
+ if (prop === "children" && (element.children?.length ?? 0) > 0) return true;
288
+ return (element.slots?.[prop] ?? []).some((fill) => elements[fill]?.type !== EMPTY_SLOT);
289
+ }
290
+ /** The keys an element's content for `prop` sits under: its slot fills, or for `children`, its
291
+ * structural children, where a placed instance keeps them. */
292
+ function fillsOf(element, prop) {
293
+ if (prop === "children" && (element.children?.length ?? 0) > 0) return element.children ?? [];
294
+ return element.slots?.[prop] ?? [];
295
+ }
296
+ /** One prop's content on an element, value and slot subtree alike, spelled without element keys so
297
+ * two placements of the same content compare equal. An empty `Slot` reads as no content. */
298
+ function contentOf(elements, element, prop) {
299
+ const tree = (key) => {
300
+ const el = elements[key];
301
+ if (!el) return null;
302
+ const slots = {};
303
+ for (const [slot, fills] of Object.entries(el.slots ?? {})) {
304
+ if (slot === "children" && (el.children?.length ?? 0) > 0) continue;
305
+ slots[slot] = fills.map(tree);
306
+ }
307
+ return {
308
+ type: el.type,
309
+ props: withoutMarkers(el.props),
310
+ children: ((el.children?.length ?? 0) > 0 ? el.children ?? [] : el.slots?.children ?? []).map(tree),
311
+ slots
312
+ };
313
+ };
314
+ const fills = fillsOf(element, prop).filter((key) => elements[key]?.type !== EMPTY_SLOT);
315
+ return stable({
316
+ value: element.props?.[prop],
317
+ fills: fills.map(tree)
318
+ });
319
+ }
320
+ function withoutMarkers(props) {
321
+ const out = {};
322
+ for (const [key, value] of Object.entries(props ?? {})) if (!key.startsWith("data-uds-")) out[key] = value;
323
+ return out;
324
+ }
325
+ /** JSON with object keys sorted, so two equal values spell the same string. */
326
+ function stable(value) {
327
+ return JSON.stringify(value, (_key, inner) => {
328
+ if (!inner || typeof inner !== "object" || Array.isArray(inner)) return inner;
329
+ return Object.fromEntries(Object.entries(inner).sort(([a], [b]) => a.localeCompare(b)));
330
+ });
331
+ }
332
+ /** Copy the subtree at `from` out of `source` into `into` under fresh keys, its references rewritten
333
+ * to them. A source key spelled under the source's root is respelled under `root`, the element the
334
+ * content lands on. Answers each copied key with its new key. */
335
+ function graftSubtree({ source, from, into, root }) {
336
+ const remap = /* @__PURE__ */ new Map();
337
+ const visit = (key) => {
338
+ if (remap.has(key) || !source.elements[key]) return;
339
+ const base = key.startsWith(source.root) ? `${root}${key.slice(source.root.length)}` : `${root}__${key}`;
340
+ let candidate = base;
341
+ for (let n = 1; candidate in into; n++) candidate = `${base}__${n}`;
342
+ remap.set(key, candidate);
343
+ into[candidate] = source.elements[key];
344
+ const element = source.elements[key];
345
+ for (const child of element?.children ?? []) visit(child);
346
+ for (const fills of Object.values(element?.slots ?? {})) for (const fill of fills) visit(fill);
347
+ };
348
+ visit(from);
349
+ for (const to of remap.values()) {
350
+ const element = into[to];
351
+ into[to] = {
352
+ ...element,
353
+ ...element.children ? { children: element.children.map((key) => remap.get(key) ?? key) } : {},
354
+ ...element.slots ? { slots: Object.fromEntries(Object.entries(element.slots).map(([slot, fills]) => [slot, fills.map((key) => remap.get(key) ?? key)])) } : {}
355
+ };
356
+ }
357
+ return remap;
358
+ }
359
+ /** Whether a component renders `value` for `prop` with nothing set: its own default is that value,
360
+ * or it has none and the value is an off boolean. */
361
+ function givesItself(config, path, prop, value) {
362
+ const own = codeDefaults(config, path)[prop];
363
+ return own === void 0 ? value === false : stable(refLeaf(own)) === stable(refLeaf(value));
364
+ }
365
+ const codeDefaultsByConfig = /* @__PURE__ */ new WeakMap();
366
+ /** What a component gives a prop by itself: its declared defaults under `defaultProps`. */
367
+ function codeDefaults(config, path) {
368
+ let byPath = codeDefaultsByConfig.get(config);
369
+ if (!byPath) {
370
+ byPath = /* @__PURE__ */ new Map();
371
+ codeDefaultsByConfig.set(config, byPath);
372
+ }
373
+ const hit = byPath.get(path);
374
+ if (hit) return hit;
375
+ const body = config.resolve("component", path)?.body;
376
+ const made = {
377
+ ...declaredPropDefaults(body?.props),
378
+ ...body?.defaultProps
379
+ };
380
+ byPath.set(path, made);
381
+ return made;
382
+ }
383
+ /** `props` without a variant value its component no longer declares, so a removed value picks the
384
+ * cell as unset does. A domain named by a ref (an icon library) is taken as it stands. */
385
+ function withoutStaleValues(config, path, props) {
386
+ const decls = (config.resolve("component", path)?.body)?.props;
387
+ let out;
388
+ for (const [prop, value] of Object.entries(props)) {
389
+ if (value === void 0 || typeof value === "object") continue;
390
+ const domain = variantDomain(decls?.[prop]);
391
+ if (!domain || domain.has(String(value))) continue;
392
+ out ??= { ...props };
393
+ delete out[prop];
394
+ }
395
+ return out ?? props;
396
+ }
397
+ /** The values a variant declaration allows, or `undefined` when it isn't one this can list. */
398
+ function variantDomain(decl) {
399
+ const { type, value } = decl ?? {};
400
+ if (type !== "variant") return void 0;
401
+ if (Array.isArray(value)) return new Set(value.map((entry) => String(refLeaf(entry))));
402
+ if (value && typeof value === "object" && !("__ref" in value)) return new Set(Object.keys(value));
403
+ }
404
+ //#endregion
405
+ export { cellSeedSpec, editedCellContent, followCellContent, followingContent, resetCellContent, withCellValues };
@@ -27,6 +27,7 @@ declare class Cell {
27
27
  private memoDefaults;
28
28
  private memoConditional;
29
29
  private memoProps;
30
+ private memoContent;
30
31
  private memoEntry;
31
32
  private memoHolding;
32
33
  private memoOwn;
@@ -40,6 +41,16 @@ declare class Cell {
40
41
  get conditional(): Record<string, unknown>;
41
42
  /** The values in force: the defaults, what holds for the cell, then the coordinate itself. */
42
43
  get props(): Record<string, unknown>;
44
+ /** The preview content the cell shows: the `*` entry, then every entry that holds for it. What a
45
+ * drop seeds and a placed instance compares its own content against; no declared or code
46
+ * default is in it. */
47
+ get content(): Record<string, unknown>;
48
+ /**
49
+ * Whether this is the component's default on the canvas: the cell "Set as default" chose, where a
50
+ * drop lands. Every axis the coordinate names matches the default cell's, the coordinate names at
51
+ * least one, and no modifier is forced. {@link isDefaults} asks about the defaults in code.
52
+ */
53
+ isCanvasDefault(modifiers?: readonly string[]): boolean;
43
54
  /** The `preview` entry whose condition is exactly this coordinate, or `undefined`. */
44
55
  get entry(): IndexedPreviewCondition | undefined;
45
56
  /** The conditional `preview` entries that hold for this cell, in stored order. */
@@ -167,5 +178,25 @@ declare function cellAt({
167
178
  path: string;
168
179
  cell: SlotCell;
169
180
  }): Cell;
181
+ /** The props and states whose values pick a cell of a component's grid: its variants, its booleans
182
+ * and its states. */
183
+ declare function cellAxes(config: Config, path: string): readonly string[];
184
+ /**
185
+ * The coordinate of a component's default cell on the canvas: the preview's defaults, with each
186
+ * boolean prop and each state they leave unset at its rest value, the way the grid spells that cell
187
+ * (`busy: false`).
188
+ */
189
+ declare function canvasDefaultCoordinate(config: Config, path: string): Readonly<SlotCell>;
190
+ /** The cell a placed instance's props pick: the default cell's coordinate with the instance's own
191
+ * axis values over it. With no props, the default cell itself. */
192
+ declare function canvasCellAt({
193
+ config,
194
+ path,
195
+ props
196
+ }: {
197
+ config: Config;
198
+ path: string;
199
+ props?: Readonly<Record<string, unknown>>;
200
+ }): Cell;
170
201
  //#endregion
171
- export { Cell, CellSlot, GoverningCondition, cellAt };
202
+ export { Cell, CellSlot, GoverningCondition, canvasCellAt, canvasDefaultCoordinate, cellAt, cellAxes };
package/dist/spec/cell.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import { canonicalCondition, refLeaf } from "../framework/utils/refs.js";
2
+ import { createSliceMemo } from "../framework/memo.js";
3
+ import { componentStates } from "../framework/component-states.js";
4
+ import { unconditionalPreviewProps } from "../framework/preview-store.js";
2
5
  import { holdingPreviewConditions, previewConditionalProps, previewDefaults } from "../framework/projections.js";
3
6
  import { propApplies, surfaceProps } from "../framework/prop-surface.js";
4
7
  import { ruleApplies } from "../framework/layer-styles.js";
@@ -32,6 +35,7 @@ var Cell = class {
32
35
  memoDefaults = UNSET;
33
36
  memoConditional = UNSET;
34
37
  memoProps = UNSET;
38
+ memoContent = UNSET;
35
39
  memoEntry = UNSET;
36
40
  memoHolding = UNSET;
37
41
  memoOwn = UNSET;
@@ -62,6 +66,31 @@ var Cell = class {
62
66
  };
63
67
  return this.memoProps;
64
68
  }
69
+ /** The preview content the cell shows: the `*` entry, then every entry that holds for it. What a
70
+ * drop seeds and a placed instance compares its own content against; no declared or code
71
+ * default is in it. */
72
+ get content() {
73
+ if (this.memoContent === UNSET) this.memoContent = {
74
+ ...unconditionalPreviewProps(this.config.resolve("component", this.component)?.body) ?? {},
75
+ ...this.conditional
76
+ };
77
+ return this.memoContent;
78
+ }
79
+ /**
80
+ * Whether this is the component's default on the canvas: the cell "Set as default" chose, where a
81
+ * drop lands. Every axis the coordinate names matches the default cell's, the coordinate names at
82
+ * least one, and no modifier is forced. {@link isDefaults} asks about the defaults in code.
83
+ */
84
+ isCanvasDefault(modifiers = []) {
85
+ if (modifiers.length > 0) return false;
86
+ const entries = Object.entries(this.coordinate);
87
+ if (entries.length === 0) return false;
88
+ const defaults = canvasDefaultCoordinate(this.config, this.component);
89
+ return entries.every(([prop, value]) => {
90
+ const held = defaults[prop];
91
+ return held !== void 0 && String(held) === String(value);
92
+ });
93
+ }
65
94
  /** The `preview` entry whose condition is exactly this coordinate, or `undefined`. */
66
95
  get entry() {
67
96
  if (this.memoEntry === UNSET) this.memoEntry = previewConditionAt({
@@ -357,5 +386,59 @@ function cellAt({ config, path, cell }) {
357
386
  byKey.set(key, made);
358
387
  return made;
359
388
  }
389
+ /** The props and states whose values pick a cell of a component's grid: its variants, its booleans
390
+ * and its states. */
391
+ function cellAxes(config, path) {
392
+ const cache = canvasCache(config);
393
+ const hit = cache.axes.get(path);
394
+ if (hit) return hit;
395
+ const axes = [];
396
+ const props = (config.resolve("component", path)?.body)?.props;
397
+ for (const [prop, decl] of Object.entries(props ?? {})) {
398
+ const type = decl?.type;
399
+ if (type === "variant" || type === "boolean") axes.push(prop);
400
+ }
401
+ for (const state of componentStates(config, path)) axes.push(state.name);
402
+ cache.axes.set(path, axes);
403
+ return axes;
404
+ }
405
+ /**
406
+ * The coordinate of a component's default cell on the canvas: the preview's defaults, with each
407
+ * boolean prop and each state they leave unset at its rest value, the way the grid spells that cell
408
+ * (`busy: false`).
409
+ */
410
+ function canvasDefaultCoordinate(config, path) {
411
+ const cache = canvasCache(config);
412
+ const hit = cache.coordinates.get(path);
413
+ if (hit) return hit;
414
+ const coordinate = {};
415
+ const props = (config.resolve("component", path)?.body)?.props;
416
+ for (const [prop, decl] of Object.entries(props ?? {})) if (decl?.type === "boolean") coordinate[prop] = false;
417
+ for (const state of componentStates(config, path)) coordinate[state.name] = state.type === "boolean" ? false : state.default;
418
+ const defaults = previewDefaults(config, path);
419
+ for (const axis of cellAxes(config, path)) if (defaults[axis] !== void 0) coordinate[axis] = defaults[axis];
420
+ const made = coordinate;
421
+ cache.coordinates.set(path, made);
422
+ return made;
423
+ }
424
+ /** The cell a placed instance's props pick: the default cell's coordinate with the instance's own
425
+ * axis values over it. With no props, the default cell itself. */
426
+ function canvasCellAt({ config, path, props }) {
427
+ const coordinate = { ...canvasDefaultCoordinate(config, path) };
428
+ for (const axis of cellAxes(config, path)) if (props?.[axis] !== void 0) coordinate[axis] = props[axis];
429
+ return cellAt({
430
+ config,
431
+ path,
432
+ cell: coordinate
433
+ });
434
+ }
435
+ /** Per component slice, each component's cell axes and default coordinate asked for so far. */
436
+ const canvasCache = createSliceMemo({
437
+ kinds: ["component"],
438
+ compute: (_config) => ({
439
+ axes: /* @__PURE__ */ new Map(),
440
+ coordinates: /* @__PURE__ */ new Map()
441
+ })
442
+ });
360
443
  //#endregion
361
- export { cellAt };
444
+ export { canvasCellAt, canvasDefaultCoordinate, cellAt, cellAxes };