@elaraai/east-ui 1.0.39 → 1.0.41

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.
Files changed (121) hide show
  1. package/dist/src/collections/calendar/index.d.ts +275 -62
  2. package/dist/src/collections/calendar/index.d.ts.map +1 -1
  3. package/dist/src/collections/calendar/index.js +168 -51
  4. package/dist/src/collections/calendar/index.js.map +1 -1
  5. package/dist/src/collections/calendar/types.d.ts +228 -22
  6. package/dist/src/collections/calendar/types.d.ts.map +1 -1
  7. package/dist/src/collections/calendar/types.js +132 -18
  8. package/dist/src/collections/calendar/types.js.map +1 -1
  9. package/dist/src/collections/deck/index.d.ts +2229 -0
  10. package/dist/src/collections/deck/index.d.ts.map +1 -0
  11. package/dist/src/collections/deck/index.js +461 -0
  12. package/dist/src/collections/deck/index.js.map +1 -0
  13. package/dist/src/collections/deck/types.d.ts +327 -0
  14. package/dist/src/collections/deck/types.d.ts.map +1 -0
  15. package/dist/src/collections/deck/types.js +206 -0
  16. package/dist/src/collections/deck/types.js.map +1 -0
  17. package/dist/src/collections/index.d.ts +2 -0
  18. package/dist/src/collections/index.d.ts.map +1 -1
  19. package/dist/src/collections/index.js +2 -0
  20. package/dist/src/collections/index.js.map +1 -1
  21. package/dist/src/collections/value-tree/index.d.ts +380 -0
  22. package/dist/src/collections/value-tree/index.d.ts.map +1 -0
  23. package/dist/src/collections/value-tree/index.js +688 -0
  24. package/dist/src/collections/value-tree/index.js.map +1 -0
  25. package/dist/src/collections/value-tree/materialize.d.ts +65 -0
  26. package/dist/src/collections/value-tree/materialize.d.ts.map +1 -0
  27. package/dist/src/collections/value-tree/materialize.js +238 -0
  28. package/dist/src/collections/value-tree/materialize.js.map +1 -0
  29. package/dist/src/collections/value-tree/types.d.ts +324 -0
  30. package/dist/src/collections/value-tree/types.d.ts.map +1 -0
  31. package/dist/src/collections/value-tree/types.js +168 -0
  32. package/dist/src/collections/value-tree/types.js.map +1 -0
  33. package/dist/src/component.d.ts +1047 -61
  34. package/dist/src/component.d.ts.map +1 -1
  35. package/dist/src/component.js +97 -3
  36. package/dist/src/component.js.map +1 -1
  37. package/dist/src/index.d.ts +3 -3
  38. package/dist/src/index.d.ts.map +1 -1
  39. package/dist/src/index.js +2 -2
  40. package/dist/src/index.js.map +1 -1
  41. package/dist/src/internal.d.ts +4 -3
  42. package/dist/src/internal.d.ts.map +1 -1
  43. package/dist/src/internal.js +3 -3
  44. package/dist/src/internal.js.map +1 -1
  45. package/dist/src/layout/splitter/index.d.ts +1 -0
  46. package/dist/src/layout/splitter/index.d.ts.map +1 -1
  47. package/dist/src/layout/splitter/index.js +2 -1
  48. package/dist/src/layout/splitter/index.js.map +1 -1
  49. package/dist/src/layout/splitter/types.d.ts +9 -0
  50. package/dist/src/layout/splitter/types.d.ts.map +1 -1
  51. package/dist/src/layout/splitter/types.js +4 -0
  52. package/dist/src/layout/splitter/types.js.map +1 -1
  53. package/dist/src/navigation/app/index.d.ts +135 -0
  54. package/dist/src/navigation/app/index.d.ts.map +1 -0
  55. package/dist/src/navigation/app/index.js +191 -0
  56. package/dist/src/navigation/app/index.js.map +1 -0
  57. package/dist/src/navigation/index.d.ts +1 -0
  58. package/dist/src/navigation/index.d.ts.map +1 -1
  59. package/dist/src/navigation/index.js +1 -0
  60. package/dist/src/navigation/index.js.map +1 -1
  61. package/dist/src/navigation/pages/types.d.ts +15 -1
  62. package/dist/src/navigation/pages/types.d.ts.map +1 -1
  63. package/dist/src/navigation/pages/types.js.map +1 -1
  64. package/dist/src/platform/slice/impl.js +2 -2
  65. package/dist/src/platform/slice/impl.js.map +1 -1
  66. package/dist/src/runtime/collections/calendar.d.ts +10 -5
  67. package/dist/src/runtime/collections/calendar.d.ts.map +1 -1
  68. package/dist/src/runtime/collections/calendar.js +10 -5
  69. package/dist/src/runtime/collections/calendar.js.map +1 -1
  70. package/dist/src/runtime/collections/deck.d.ts +1066 -0
  71. package/dist/src/runtime/collections/deck.d.ts.map +1 -0
  72. package/dist/src/runtime/collections/deck.js +54 -0
  73. package/dist/src/runtime/collections/deck.js.map +1 -0
  74. package/dist/src/runtime/collections/index.d.ts +2 -0
  75. package/dist/src/runtime/collections/index.d.ts.map +1 -1
  76. package/dist/src/runtime/collections/index.js +2 -0
  77. package/dist/src/runtime/collections/index.js.map +1 -1
  78. package/dist/src/runtime/collections/value-tree.d.ts +202 -0
  79. package/dist/src/runtime/collections/value-tree.d.ts.map +1 -0
  80. package/dist/src/runtime/collections/value-tree.js +49 -0
  81. package/dist/src/runtime/collections/value-tree.js.map +1 -0
  82. package/dist/src/runtime/navigation/app.d.ts +50 -0
  83. package/dist/src/runtime/navigation/app.d.ts.map +1 -0
  84. package/dist/src/runtime/navigation/app.js +51 -0
  85. package/dist/src/runtime/navigation/app.js.map +1 -0
  86. package/dist/src/runtime/navigation/index.d.ts +1 -0
  87. package/dist/src/runtime/navigation/index.d.ts.map +1 -1
  88. package/dist/src/runtime/navigation/index.js +1 -0
  89. package/dist/src/runtime/navigation/index.js.map +1 -1
  90. package/dist/test/collections/calendar.examples.d.ts +2 -0
  91. package/dist/test/collections/calendar.examples.d.ts.map +1 -1
  92. package/dist/test/collections/calendar.examples.js +74 -42
  93. package/dist/test/collections/calendar.examples.js.map +1 -1
  94. package/dist/test/collections/deck.examples.d.ts +8 -0
  95. package/dist/test/collections/deck.examples.d.ts.map +1 -0
  96. package/dist/test/collections/deck.examples.js +169 -0
  97. package/dist/test/collections/deck.examples.js.map +1 -0
  98. package/dist/test/collections/planner.examples.js +2 -2
  99. package/dist/test/collections/value-tree.examples.d.ts +10 -0
  100. package/dist/test/collections/value-tree.examples.d.ts.map +1 -0
  101. package/dist/test/collections/value-tree.examples.js +263 -0
  102. package/dist/test/collections/value-tree.examples.js.map +1 -0
  103. package/dist/test/display/image.examples.js +1 -1
  104. package/dist/test/display/image.examples.js.map +1 -1
  105. package/dist/test/layout/aligned-stack.examples.js +4 -4
  106. package/dist/test/layout/aligned-stack.examples.js.map +1 -1
  107. package/dist/test/layout/dock.examples.js +6 -6
  108. package/dist/test/layout/dock.examples.js.map +1 -1
  109. package/dist/test/layout/splitter.examples.d.ts +1 -0
  110. package/dist/test/layout/splitter.examples.d.ts.map +1 -1
  111. package/dist/test/layout/splitter.examples.js +11 -0
  112. package/dist/test/layout/splitter.examples.js.map +1 -1
  113. package/dist/test/navigation/app.examples.d.ts +22 -0
  114. package/dist/test/navigation/app.examples.d.ts.map +1 -0
  115. package/dist/test/navigation/app.examples.js +116 -0
  116. package/dist/test/navigation/app.examples.js.map +1 -0
  117. package/dist/test/navigation/nav-list.examples.js +2 -2
  118. package/dist/test/navigation/nav-list.examples.js.map +1 -1
  119. package/dist/test/overlays/drawer.examples.js +2 -2
  120. package/dist/test/overlays/drawer.examples.js.map +1 -1
  121. package/package.json +3 -3
@@ -0,0 +1,688 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /**
6
+ * ValueTree — the editable value-driven tree (#360).
7
+ *
8
+ * `ValueTree.Root(value, opts)` walks the STATIC East type of `value` at
9
+ * authoring time and materializes it into the fixed recursive
10
+ * {@link ValueTreeNodeType} IR — automatic tree formation from ANY East
11
+ * type, leaf-type-aware editing, and compound/collection editing
12
+ * (add/remove array rows and dict entries, variant tag switching,
13
+ * option toggling).
14
+ *
15
+ * Two edit surfaces, one contract:
16
+ *
17
+ * - **Value handlers** (the default): `onUpdate` receives the whole
18
+ * value with the edit applied; `at: [ValueTree.at(Type, p => …, fn)]`
19
+ * scopes receive just the rebuilt SUBTREE at their path. Every edit
20
+ * dispatches to the deepest scope whose path prefixes the edit path
21
+ * and bubbles up to `onUpdate` when no scope matches. The rebuild is
22
+ * an authoring-time macro over the static type — hosts never
23
+ * hand-apply paths.
24
+ * - **Path callbacks** (the escape hatch): `onEdit` / `onInsert` /
25
+ * `onRemove` / `onTag` receive the raw typed path and payload for
26
+ * hosts that own a finer-grained store. A raw callback overrides the
27
+ * generated handler for its event.
28
+ *
29
+ * The host owns the data either way: write the new value and let the
30
+ * Reactive re-materialization refresh the tree (the same contract as
31
+ * every collection surface).
32
+ */
33
+ import { ArrayType, East, Expr, FunctionType, NullType, RecursiveExpr, StringType, StructType, defaultValue, some, none, variant, } from "@elaraai/east";
34
+ import { UIComponentType } from "../../component.js";
35
+ import { ValueTreeStepType, ValueTreePathType, ValueTreeLeafType, ValueTreeNodeType, ValueTreeStyleType, ValueTreeRootType, } from "./types.js";
36
+ export { ValueTreeStepType, ValueTreePathType, ValueTreeLeafType, ValueTreeNodeType, ValueTreeStyleType, ValueTreeRootType, } from "./types.js";
37
+ import { materialize, applyEdit } from "./materialize.js";
38
+ export { materialize, applyEdit, } from "./materialize.js";
39
+ /** Beyond this many unrollings of a RecursiveType the subtree prints as
40
+ * an `opaque` node (a lazy "expand further" is the #360 follow-up). */
41
+ const MAX_RECURSION_DEPTH = 6;
42
+ /** Total materializer expansions per tree — caps generated IR size for
43
+ * wide recursive types, where a pure depth limit still explodes
44
+ * (branching^depth). Exhausted subtrees print as `opaque`. */
45
+ const MAX_MATERIALIZED_NODES = 4096;
46
+ /** Expansion budget for each generated edit-application wrapper —
47
+ * the rebuild macro mirrors the materializer's recursion, so it gets
48
+ * the same cap; exhausted subtrees pass through unchanged (the
49
+ * materializer shows them as read-only `opaque` anyway). */
50
+ const MAX_REBUILD_NODES = 4096;
51
+ /** Longest opaque summary before the printed value is clamped. */
52
+ const MAX_OPAQUE_PRINT = 80n;
53
+ /** The dict-entry element of a dict node. */
54
+ const DictEntryType = StructType({ key: StringType, node: ValueTreeNodeType });
55
+ /** The struct-field element of a struct node. */
56
+ const StructFieldType = StructType({ name: StringType, node: ValueTreeNodeType });
57
+ /** True when a Variant type is exactly the Option shape (`some` + `none`). */
58
+ function isOptionType(t) {
59
+ if (t.type !== "Variant")
60
+ return false;
61
+ const tags = Object.keys(t.cases);
62
+ return tags.length === 2 && tags.includes("some") && tags.includes("none");
63
+ }
64
+ /** Clamps a printed value for opaque summaries (large collections would
65
+ * otherwise dump their whole print into a single row). */
66
+ const clampPrint = East.function([StringType], StringType, (_$, p) => p.length().greater(MAX_OPAQUE_PRINT).ifElse(() => p.substring(0n, MAX_OPAQUE_PRINT).concat("…"), () => p));
67
+ /** Kind-aware opaque summary — "Set · 12 items" beats an unbounded print. */
68
+ function opaqueSummary(t, v) {
69
+ switch (t.type) {
70
+ case "Set":
71
+ return East.str `Set · ${v.size()} items`;
72
+ case "Blob":
73
+ return East.str `Blob · ${v.size()} bytes`;
74
+ case "Vector":
75
+ return East.str `Vector · ${v.length()} values`;
76
+ case "Matrix": {
77
+ const m = v;
78
+ return East.str `Matrix · ${m.rows()}×${m.cols()}`;
79
+ }
80
+ case "Ref":
81
+ return East.str `Ref · ${clampPrint(East.print(v.get()))}`;
82
+ case "Function":
83
+ case "AsyncFunction":
84
+ return East.value("Function", StringType);
85
+ default:
86
+ return clampPrint(East.print(v));
87
+ }
88
+ }
89
+ /**
90
+ * Materialize `v` (typed `t`) into a {@link ValueTreeNodeType} expression.
91
+ * Recursion is over the STATIC type — the generated IR walks the value
92
+ * eagerly (the `mapRows` discipline: expressions expand once per type
93
+ * shape, user data flows through East collection ops).
94
+ */
95
+ function nodeOf(t, v, depth, state) {
96
+ state.budget -= 1;
97
+ if (depth > MAX_RECURSION_DEPTH || state.budget <= 0) {
98
+ return East.value(variant("opaque", clampPrint(East.print(v))), ValueTreeNodeType);
99
+ }
100
+ switch (t.type) {
101
+ case "Null":
102
+ return East.value(variant("leaf", variant("null", null)), ValueTreeNodeType);
103
+ case "Boolean":
104
+ return East.value(variant("leaf", variant("boolean", v)), ValueTreeNodeType);
105
+ case "Integer":
106
+ return East.value(variant("leaf", variant("integer", v)), ValueTreeNodeType);
107
+ case "Float":
108
+ return East.value(variant("leaf", variant("float", v)), ValueTreeNodeType);
109
+ case "String":
110
+ return East.value(variant("leaf", variant("string", v)), ValueTreeNodeType);
111
+ case "DateTime":
112
+ return East.value(variant("leaf", variant("datetime", v)), ValueTreeNodeType);
113
+ case "Struct": {
114
+ const fields = t.fields;
115
+ const fieldNodes = Object.entries(fields).map(([name, ft]) => ({
116
+ name,
117
+ node: nodeOf(ft, v[name], depth, state),
118
+ }));
119
+ return East.value(variant("struct", {
120
+ fields: East.value(fieldNodes, ArrayType(StructFieldType)),
121
+ }), ValueTreeNodeType);
122
+ }
123
+ case "Array": {
124
+ const elem = t.value;
125
+ const fn = East.function([elem], ValueTreeNodeType, (_$, x) => nodeOf(elem, x, depth, state));
126
+ const items = v
127
+ .map((_$, x) => fn(x));
128
+ return East.value(variant("array", { items }), ValueTreeNodeType);
129
+ }
130
+ case "Dict": {
131
+ const { key, value } = t;
132
+ const editable = key.type === "String";
133
+ const fn = East.function([value, key], DictEntryType, (_$, x, k) => ({
134
+ key: editable
135
+ ? k
136
+ : East.print(k),
137
+ node: nodeOf(value, x, depth, state),
138
+ }));
139
+ const entries = v
140
+ .toArray((_$, x, k) => fn(x, k));
141
+ return East.value(variant("dict", { entries, editable }), ValueTreeNodeType);
142
+ }
143
+ case "Variant": {
144
+ const cases = t.cases;
145
+ const tags = Object.keys(cases);
146
+ if (isOptionType(t)) {
147
+ const someType = cases["some"];
148
+ return v.match({
149
+ some: (_$, payload) => East.value(variant("option", {
150
+ value: some(nodeOf(someType, payload, depth, state)),
151
+ }), ValueTreeNodeType),
152
+ none: () => East.value(variant("option", {
153
+ value: none,
154
+ }), ValueTreeNodeType),
155
+ });
156
+ }
157
+ const handlers = {};
158
+ for (const tag of tags) {
159
+ handlers[tag] = (_$, payload) => East.value(variant("variant", {
160
+ tag,
161
+ tags: East.value(tags, ArrayType(StringType)),
162
+ value: nodeOf(cases[tag], payload, depth, state),
163
+ }), ValueTreeNodeType);
164
+ }
165
+ return v
166
+ .match(handlers);
167
+ }
168
+ case "Recursive":
169
+ // A recursive-typed expression only exposes unwrap()/wrap —
170
+ // unwrap to the inner node type before descending.
171
+ return nodeOf(t.node, v.unwrap(), depth + 1, state);
172
+ default:
173
+ // Sets, blobs, vectors, matrices, refs, functions — summarized.
174
+ return East.value(variant("opaque", opaqueSummary(t, v)), ValueTreeNodeType);
175
+ }
176
+ }
177
+ /** Leaf case tag of {@link ValueTreeLeafType} for a primitive type. */
178
+ function leafTag(t) {
179
+ switch (t.type) {
180
+ case "String": return "string";
181
+ case "Integer": return "integer";
182
+ case "Float": return "float";
183
+ case "Boolean": return "boolean";
184
+ case "DateTime": return "datetime";
185
+ default: return undefined;
186
+ }
187
+ }
188
+ /** Zero value as an East expression, or undefined where no zero exists
189
+ * (functions, matrices) — the corresponding insert/tag branch then
190
+ * passes the value through unchanged. */
191
+ function tryZeroExpr(t) {
192
+ try {
193
+ return East.value(zeroValue(t), t);
194
+ }
195
+ catch {
196
+ return undefined;
197
+ }
198
+ }
199
+ /**
200
+ * Rebuild `v` (typed `t`) with `op` applied at `path` — the write-side
201
+ * mirror of {@link nodeOf}. Expands per static type shape; `si` is the
202
+ * number of path steps already consumed (static: one per nesting level),
203
+ * `depth` counts RecursiveType unrollings against the same cap as the
204
+ * materializer (edits cannot originate beyond what materialized).
205
+ *
206
+ * Op semantics at the addressed node:
207
+ * - `edit`: the path ends AT a primitive leaf — its typed payload replaces it.
208
+ * - `insert`: the path ends with `append` (array) or the NEW `key` (dict).
209
+ * - `remove`: the path ends with the element `index` / entry `key`.
210
+ * - `tag`: the path ends AT an option (tag "some"/"none") or variant node;
211
+ * the payload becomes the target case's zero value.
212
+ */
213
+ function rebuild(t, v, path, si, depth, state, op) {
214
+ state.budget -= 1;
215
+ if (depth > MAX_RECURSION_DEPTH || state.budget <= 0) {
216
+ return v;
217
+ }
218
+ const p = path;
219
+ const step = () => p.get(BigInt(si));
220
+ const atEnd = (n) => p.size().equals(BigInt(n));
221
+ switch (t.type) {
222
+ case "Null":
223
+ return v;
224
+ case "Boolean":
225
+ case "Integer":
226
+ case "Float":
227
+ case "String":
228
+ case "DateTime": {
229
+ if (op.kind !== "edit")
230
+ return v;
231
+ const tag = leafTag(t);
232
+ return op.leaf.unwrap(tag);
233
+ }
234
+ case "Struct": {
235
+ const fields = Object.entries(t.fields);
236
+ if (fields.length === 0)
237
+ return v;
238
+ const vs = v;
239
+ let chain = v;
240
+ for (const [name, ft] of [...fields].reverse()) {
241
+ const rebuiltFields = {};
242
+ for (const [n] of fields) {
243
+ rebuiltFields[n] = n === name
244
+ ? rebuild(ft, vs[n], path, si + 1, depth, state, op)
245
+ : vs[n];
246
+ }
247
+ const prev = chain;
248
+ chain = step().hasTag("field")
249
+ .and(() => step().unwrap("field").equals(name))
250
+ .ifElse(() => East.value(rebuiltFields, t), () => prev);
251
+ }
252
+ return chain;
253
+ }
254
+ case "Array": {
255
+ const elem = t.value;
256
+ const av = v;
257
+ const descend = () => av.map((_$, x, i) => i.equals(step().unwrap("index"))
258
+ .ifElse(() => rebuild(elem, x, path, si + 1, depth, state, op), () => x));
259
+ if (op.kind === "insert") {
260
+ const zero = tryZeroExpr(elem);
261
+ if (zero === undefined)
262
+ return descend();
263
+ return atEnd(si + 1)
264
+ .and(() => step().hasTag("append"))
265
+ .ifElse(() => av.concat(East.value([zero], ArrayType(elem))), descend);
266
+ }
267
+ if (op.kind === "remove") {
268
+ return atEnd(si + 1).ifElse(() => av.filter((_$, _x, i) => i.notEquals(step().unwrap("index"))), descend);
269
+ }
270
+ return descend();
271
+ }
272
+ case "Dict": {
273
+ const { key: kt, value: vt } = t;
274
+ if (kt.type !== "String")
275
+ return v;
276
+ const dv = v;
277
+ const descend = () => dv.map((_$, val, k) => k.equals(step().unwrap("key"))
278
+ .ifElse(() => rebuild(vt, val, path, si + 1, depth, state, op), () => val));
279
+ if (op.kind === "insert") {
280
+ const zero = tryZeroExpr(vt);
281
+ if (zero === undefined)
282
+ return descend();
283
+ // Tail-return style (no `$.return`): these run inside the
284
+ // ASYNC generated wrappers, where a Return's control-flow
285
+ // exception would escape the sync call boundary.
286
+ const insertFn = East.function([t, StringType], t, ($, d, k) => {
287
+ const next = $.let(d.copy());
288
+ $(next
289
+ .insertOrUpdate(k, zero));
290
+ return next;
291
+ });
292
+ return atEnd(si + 1)
293
+ .and(() => step().hasTag("key"))
294
+ .ifElse(() => insertFn(v, step().unwrap("key")), descend);
295
+ }
296
+ if (op.kind === "remove") {
297
+ const removeFn = East.function([t, StringType], t, ($, d, k) => {
298
+ const next = $.let(d.copy());
299
+ $(next
300
+ .tryDelete(k));
301
+ return next;
302
+ });
303
+ return atEnd(si + 1)
304
+ .and(() => step().hasTag("key"))
305
+ .ifElse(() => removeFn(v, step().unwrap("key")), descend);
306
+ }
307
+ return descend();
308
+ }
309
+ case "Variant": {
310
+ const cases = t.cases;
311
+ const mv = v;
312
+ if (isOptionType(t)) {
313
+ const inner = cases["some"];
314
+ const descend = () => mv.match({
315
+ some: (_$, payload) => East.value(some(rebuild(inner, payload, path, si + 1, depth, state, op)), t),
316
+ none: () => East.value(none, t),
317
+ });
318
+ if (op.kind === "tag") {
319
+ const zero = tryZeroExpr(inner);
320
+ const toSome = zero !== undefined
321
+ ? East.value(some(zero), t)
322
+ : v;
323
+ return atEnd(si).ifElse(() => op.tag.equals("some")
324
+ .ifElse(() => toSome, () => East.value(none, t)), descend);
325
+ }
326
+ return descend();
327
+ }
328
+ const tags = Object.keys(cases);
329
+ const descend = () => {
330
+ const handlers = {};
331
+ for (const tag of tags) {
332
+ handlers[tag] = (_$, payload) => East.value(variant(tag, rebuild(cases[tag], payload, path, si + 1, depth, state, op)), t);
333
+ }
334
+ return mv.match(handlers);
335
+ };
336
+ if (op.kind === "tag") {
337
+ let chain = v;
338
+ for (const tag of [...tags].reverse()) {
339
+ const zero = tryZeroExpr(cases[tag]);
340
+ if (zero === undefined)
341
+ continue;
342
+ const target = East.value(variant(tag, zero), t);
343
+ const prev = chain;
344
+ chain = op.tag.equals(tag)
345
+ .ifElse(() => target, () => prev);
346
+ }
347
+ const tagChain = chain;
348
+ return atEnd(si).ifElse(() => tagChain, descend);
349
+ }
350
+ return descend();
351
+ }
352
+ case "Recursive": {
353
+ const inner = rebuild(t.node, v.unwrap(), path, si, depth + 1, state, op);
354
+ // Field/element sites require the exact wrapped type — re-wrap
355
+ // the rebuilt inner value (subtype transparency does not apply
356
+ // to heap slots).
357
+ return RecursiveExpr.wrap(inner, t, Expr.fromAst);
358
+ }
359
+ default:
360
+ // Sets, blobs, vectors, matrices, refs, functions — read-only.
361
+ return v;
362
+ }
363
+ }
364
+ /** Runtime probe: records navigation steps against the static type.
365
+ * Members are lazy (getters / closures) so recursive types terminate. */
366
+ function probeOf(t, steps) {
367
+ const probe = {};
368
+ Object.defineProperty(probe, "__vtSubtype", { value: t, enumerable: false });
369
+ Object.defineProperty(probe, "__vtSteps", { value: steps, enumerable: false });
370
+ let ct = t;
371
+ while (ct.type === "Recursive")
372
+ ct = ct.node;
373
+ if (ct.type === "Struct") {
374
+ for (const [name, ft] of Object.entries(ct.fields)) {
375
+ Object.defineProperty(probe, name, {
376
+ get: () => probeOf(ft, [...steps, { kind: "field", name }]),
377
+ enumerable: true,
378
+ });
379
+ }
380
+ }
381
+ else if (ct.type === "Array") {
382
+ const elem = ct.value;
383
+ probe["item"] = (index) => probeOf(elem, [...steps, { kind: "index", index: BigInt(index) }]);
384
+ }
385
+ else if (ct.type === "Dict") {
386
+ const { key, value } = ct;
387
+ if (key.type === "String") {
388
+ probe["entry"] = (k) => probeOf(value, [...steps, { kind: "key", key: k }]);
389
+ }
390
+ }
391
+ else if (isOptionType(ct)) {
392
+ const inner = ct.cases["some"];
393
+ probe["some"] = () => probeOf(inner, [...steps, { kind: "some" }]);
394
+ }
395
+ return probe;
396
+ }
397
+ /**
398
+ * Builds a scoped subtree handler for {@link ValueTree.Root}'s `at`
399
+ * option: navigate the root type with the typed probe, and receive the
400
+ * rebuilt SUBTREE at that path whenever an edit lands inside it.
401
+ *
402
+ * @typeParam T - The root value's East type
403
+ * @typeParam S - The subtree type at the accessor's path (inferred)
404
+ * @param root - The root value's East type (drives probe navigation)
405
+ * @param path - Accessor over the typed probe selecting the subtree
406
+ * @param onUpdate - Handler receiving the rebuilt subtree value
407
+ * @returns An opaque scope for the `at` option
408
+ *
409
+ * @example
410
+ * ```ts
411
+ * import { East, DictType, StringType, FloatType, NullType } from "@elaraai/east";
412
+ * import { ValueTree } from "@elaraai/east-ui";
413
+ *
414
+ * const RatesType = DictType(StringType, FloatType);
415
+ * const scope = ValueTree.at(RatesType, p => p.entry("base"),
416
+ * East.function([FloatType], NullType, (_$, _next) => null));
417
+ * ```
418
+ */
419
+ function scopeAt(root, path, onUpdate) {
420
+ const target = path(probeOf(root, []));
421
+ if (target?.__vtSteps === undefined) {
422
+ throw new Error("ValueTree.at: the path accessor must return a probe position (e.g. p => p.machines)");
423
+ }
424
+ return { steps: target.__vtSteps, subtype: target.__vtSubtype, handler: onUpdate };
425
+ }
426
+ /** Walks the static type along scope steps (RecursiveType transparent);
427
+ * throws when a step does not fit the type — a scope built against a
428
+ * different root type than the tree's value. */
429
+ function navigateType(t, steps) {
430
+ let ct = t;
431
+ for (const st of steps) {
432
+ while (ct.type === "Recursive")
433
+ ct = ct.node;
434
+ if (st.kind === "field") {
435
+ const ft = ct.fields?.[st.name];
436
+ if (ct.type !== "Struct" || ft === undefined) {
437
+ throw new Error(`ValueTree.at: no struct field '${st.name}' at scope path`);
438
+ }
439
+ ct = ft;
440
+ }
441
+ else if (st.kind === "index") {
442
+ if (ct.type !== "Array")
443
+ throw new Error("ValueTree.at: 'item' step on a non-array at scope path");
444
+ ct = ct.value;
445
+ }
446
+ else if (st.kind === "key") {
447
+ if (ct.type !== "Dict")
448
+ throw new Error("ValueTree.at: 'entry' step on a non-dict at scope path");
449
+ ct = ct.value;
450
+ }
451
+ else {
452
+ if (!isOptionType(ct))
453
+ throw new Error("ValueTree.at: 'some' step on a non-option at scope path");
454
+ ct = ct.cases["some"];
455
+ }
456
+ }
457
+ return ct;
458
+ }
459
+ /** Navigates a value expression along scope steps (RecursiveType
460
+ * unwrapped as consumed; the terminal value keeps its wrapper). */
461
+ function navigateExpr(root, t, steps) {
462
+ let e = root;
463
+ let ct = t;
464
+ for (const st of steps) {
465
+ while (ct.type === "Recursive") {
466
+ e = e.unwrap();
467
+ ct = ct.node;
468
+ }
469
+ if (st.kind === "field") {
470
+ e = e[st.name];
471
+ ct = ct.fields[st.name];
472
+ }
473
+ else if (st.kind === "index") {
474
+ e = e.get(st.index);
475
+ ct = ct.value;
476
+ }
477
+ else if (st.kind === "key") {
478
+ e = e.get(st.key);
479
+ ct = ct.value;
480
+ }
481
+ else {
482
+ e = e.unwrap("some");
483
+ ct = ct.cases["some"];
484
+ }
485
+ }
486
+ return e;
487
+ }
488
+ /** Runtime prefix-match condition for a scope against an edit path.
489
+ * `extra` is 1 for container ops (insert/remove: the affected element's
490
+ * container must sit strictly below the scope root — removing the
491
+ * scope's own subtree dispatches to its PARENT scope, whose value
492
+ * still exists) and 0 for edit/tag. */
493
+ function scopeMatch(path, scope, extra) {
494
+ const p = path;
495
+ let cond = p.size().greaterEqual(BigInt(scope.steps.length + extra));
496
+ scope.steps.forEach((st, i) => {
497
+ const stepAt = () => p.get(BigInt(i));
498
+ if (st.kind === "field") {
499
+ cond = cond.and(() => stepAt().hasTag("field")
500
+ .and(() => stepAt().unwrap("field").equals(st.name)));
501
+ }
502
+ else if (st.kind === "index") {
503
+ cond = cond.and(() => stepAt().hasTag("index")
504
+ .and(() => stepAt().unwrap("index").equals(st.index)));
505
+ }
506
+ else if (st.kind === "key") {
507
+ cond = cond.and(() => stepAt().hasTag("key")
508
+ .and(() => stepAt().unwrap("key").equals(st.key)));
509
+ }
510
+ else {
511
+ cond = cond.and(() => stepAt().hasTag("some"));
512
+ }
513
+ });
514
+ return cond;
515
+ }
516
+ /** Invokes a user handler expression (`FunctionType` value) with one arg. */
517
+ function callHandler(handler, arg) {
518
+ const fn = East.value(handler);
519
+ return fn.call(arg);
520
+ }
521
+ // ============================================================================
522
+ // Factory
523
+ // ============================================================================
524
+ /**
525
+ * Creates a ValueTree — the editable tree of any East value.
526
+ *
527
+ * @typeParam T - The value's East type (captured statically)
528
+ * @param value - The value to materialize (any East type)
529
+ * @param options - Value handlers, raw path callbacks + style
530
+ * ({@link ValueTreeOptions})
531
+ * @returns An East expression of `UIComponentType`
532
+ *
533
+ * @example
534
+ * ```ts
535
+ * import { East, DictType, StringType, StructType, FloatType } from "@elaraai/east";
536
+ * import { ValueTree, UIComponentType } from "@elaraai/east-ui";
537
+ *
538
+ * const ConfigType = DictType(StringType, StructType({ rate: FloatType, label: StringType }));
539
+ * const view = East.function([ConfigType], UIComponentType, (_$, config) =>
540
+ * ValueTree.Root(config),
541
+ * );
542
+ * ```
543
+ */
544
+ function createValueTree(value, options) {
545
+ const v = East.value(value);
546
+ const t = Expr.type(v);
547
+ const root = nodeOf(t, v, 0, { budget: MAX_MATERIALIZED_NODES });
548
+ const scopes = options?.at ?? [];
549
+ for (const scope of scopes) {
550
+ // Fail at authoring time when a scope was built against a
551
+ // different root type than the tree's value.
552
+ navigateType(t, scope.steps);
553
+ }
554
+ const wantsGenerated = options?.onUpdate !== undefined || scopes.length > 0;
555
+ /** Deepest-first dispatch: scoped handlers, then `onUpdate`. */
556
+ const emitDispatch = ($, path, newRoot, extra) => {
557
+ const ordered = [...scopes].sort((a, b) => b.steps.length - a.steps.length);
558
+ const emit = ($b, remaining) => {
559
+ const scope = remaining[0];
560
+ if (scope === undefined) {
561
+ if (options?.onUpdate !== undefined) {
562
+ $b(callHandler(options.onUpdate, newRoot));
563
+ }
564
+ return;
565
+ }
566
+ const subtree = navigateExpr(newRoot, t, scope.steps);
567
+ $b.if(scopeMatch(path, scope, extra), ($t) => {
568
+ $t(callHandler(scope.handler, subtree));
569
+ }).else(($e) => {
570
+ emit($e, remaining.slice(1));
571
+ });
572
+ };
573
+ emit($, ordered);
574
+ };
575
+ // Generated wrappers are ASYNC: user handlers routinely write State
576
+ // (an async platform effect), and sync handlers are subtypes.
577
+ const genOnEdit = () => East.asyncFunction([ValueTreePathType, ValueTreeLeafType], NullType, ($, p, leaf) => {
578
+ const next = $.let(rebuild(t, v, p, 0, 0, { budget: MAX_REBUILD_NODES }, { kind: "edit", leaf }));
579
+ emitDispatch($, p, next, 0);
580
+ });
581
+ const genOnInsert = () => East.asyncFunction([ValueTreePathType], NullType, ($, p) => {
582
+ const next = $.let(rebuild(t, v, p, 0, 0, { budget: MAX_REBUILD_NODES }, { kind: "insert" }));
583
+ emitDispatch($, p, next, 1);
584
+ });
585
+ const genOnRemove = () => East.asyncFunction([ValueTreePathType], NullType, ($, p) => {
586
+ const next = $.let(rebuild(t, v, p, 0, 0, { budget: MAX_REBUILD_NODES }, { kind: "remove" }));
587
+ emitDispatch($, p, next, 1);
588
+ });
589
+ const genOnTag = () => East.asyncFunction([ValueTreePathType, StringType], NullType, ($, p, tag) => {
590
+ const next = $.let(rebuild(t, v, p, 0, 0, { budget: MAX_REBUILD_NODES }, { kind: "tag", tag }));
591
+ emitDispatch($, p, next, 0);
592
+ });
593
+ const onEdit = options?.onEdit ?? (wantsGenerated ? genOnEdit() : undefined);
594
+ const onInsert = options?.onInsert ?? (wantsGenerated ? genOnInsert() : undefined);
595
+ const onRemove = options?.onRemove ?? (wantsGenerated ? genOnRemove() : undefined);
596
+ const onTag = options?.onTag ?? (wantsGenerated ? genOnTag() : undefined);
597
+ const style = options?.style;
598
+ // Sync raw callbacks widen into the async slots at runtime
599
+ // (FunctionType <: AsyncFunctionType) — the casts only quiet TS.
600
+ return East.value(variant("ValueTree", {
601
+ root,
602
+ onEdit: onEdit !== undefined ? some(onEdit) : none,
603
+ onInsert: onInsert !== undefined ? some(onInsert) : none,
604
+ onRemove: onRemove !== undefined ? some(onRemove) : none,
605
+ onTag: onTag !== undefined ? some(onTag) : none,
606
+ style: style !== undefined ? some(East.value({
607
+ height: style.height !== undefined ? some(style.height) : none,
608
+ maxHeight: style.maxHeight !== undefined ? some(style.maxHeight) : none,
609
+ }, ValueTreeStyleType)) : none,
610
+ }), UIComponentType);
611
+ }
612
+ /**
613
+ * A host-side zero value for any East data type — the default payload
614
+ * for inserted elements, toggled options and switched variant cases.
615
+ *
616
+ * @param t - The East type to zero
617
+ * @returns The TypeScript `ValueTypeOf` zero value
618
+ * @throws When the type has no default (functions; recursion below the
619
+ * top level)
620
+ *
621
+ * @remarks
622
+ * Delegates to east's {@link defaultValue} (option variants default to
623
+ * `none` — their sorted first case), unrolling a top-level
624
+ * RecursiveType wrapper first.
625
+ */
626
+ function zeroValue(t) {
627
+ let ct = t;
628
+ while (ct.type === "Recursive")
629
+ ct = ct.node;
630
+ return defaultValue(ct);
631
+ }
632
+ // ============================================================================
633
+ // Namespace export
634
+ // ============================================================================
635
+ /**
636
+ * ValueTree component namespace.
637
+ *
638
+ * @remarks
639
+ * `ValueTree.Root(value, options)` materializes any East value into the
640
+ * editable tree; `ValueTree.at(Type, p => …, handler)` builds a scoped
641
+ * subtree handler for the `at` option; `ValueTree.zero(Type)` builds
642
+ * the host-side default element for raw `onInsert` handlers.
643
+ */
644
+ export const ValueTree = {
645
+ /**
646
+ * Creates a ValueTree — the editable tree of any East value.
647
+ *
648
+ * @param value - The value to materialize (any East type)
649
+ * @param options - Value handlers, raw callbacks + style
650
+ * ({@link ValueTreeOptions})
651
+ * @returns An East expression of `UIComponentType`
652
+ */
653
+ Root: createValueTree,
654
+ /**
655
+ * Builds a scoped subtree handler for the `at` option — the deepest
656
+ * scope whose path prefixes an edit's path receives the rebuilt
657
+ * subtree; unmatched edits bubble to `onUpdate`.
658
+ */
659
+ at: scopeAt,
660
+ /** Host-side zero value for any East type (for raw `onInsert` handlers). */
661
+ zero: zeroValue,
662
+ /**
663
+ * Host-side materializer — a DECODED value + its type into the node tree
664
+ * the renderer consumes (for callers holding a value, not an `Expr`; the
665
+ * East-side {@link ValueTree.Root} materializes an expression instead).
666
+ */
667
+ materialize,
668
+ /**
669
+ * Host-side edit application — apply one reported ValueTree edit (leaf /
670
+ * insert / remove / tag) to a decoded value, returning the new value.
671
+ */
672
+ applyEdit,
673
+ Types: {
674
+ /** The full ValueTree payload. */
675
+ Root: ValueTreeRootType,
676
+ /** The materialized node tree. */
677
+ Node: ValueTreeNodeType,
678
+ /** A node path. */
679
+ Path: ValueTreePathType,
680
+ /** One path step. */
681
+ Step: ValueTreeStepType,
682
+ /** A primitive leaf value / leaf edit. */
683
+ Leaf: ValueTreeLeafType,
684
+ /** Container style. */
685
+ Style: ValueTreeStyleType,
686
+ },
687
+ };
688
+ //# sourceMappingURL=index.js.map