@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.
- package/dist/entities/system/Component.js +10 -5
- package/dist/entities/system/glyph-identity.js +70 -22
- package/dist/entities/system/style-bag.js +4 -3
- package/dist/framework/render-spec.d.ts +2 -1
- package/dist/framework/render-spec.js +20 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -1
- package/dist/spec/bake.d.ts +84 -0
- package/dist/spec/bake.js +405 -0
- package/dist/spec/cell.d.ts +32 -1
- package/dist/spec/cell.js +84 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -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 };
|
package/dist/spec/cell.d.ts
CHANGED
|
@@ -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 };
|