tutuca 0.13.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,12 +51,6 @@ enableMapSet();
51
51
 
52
52
  // src/path.js
53
53
  var NONE = /* @__PURE__ */ Symbol("NONE");
54
- var readKey = (value, key, dval = null) => {
55
- if (value == null) return dval;
56
- if (value instanceof Map) return value.has(key) ? value.get(key) : dval;
57
- if (value instanceof Set) return value.has(key) ? key : dval;
58
- return Object.hasOwn(value, key) ? value[key] : dval;
59
- };
60
54
  var writeKey = (value, key, next) => {
61
55
  if (value instanceof Map) value.set(key, next);
62
56
  else if (value instanceof Set) {
@@ -64,20 +58,15 @@ var writeKey = (value, key, next) => {
64
58
  value.add(next);
65
59
  } else value[key] = next;
66
60
  };
67
- var writeSeqKey = (value, key, next) => {
68
- if (value instanceof Map || value instanceof Set || Array.isArray(value) || Object.hasOwn(value, key))
69
- writeKey(value, key, next);
70
- else if (typeof value?.set === "function") value.set(key, next);
71
- else writeKey(value, key, next);
72
- };
61
+ var writeSeqKey = (value, key, next) => !(value instanceof Map || value instanceof Set || Array.isArray(value)) && !Object.hasOwn(value, key) && typeof value?.set === "function" ? value.set(key, next) : writeKey(value, key, next);
73
62
  var Step = class {
74
- lookup(_v, dval = null) {
75
- return dval;
76
- }
77
63
  setDraftValue(_root, _v) {
78
64
  }
79
- enterFrame(stack, next) {
80
- return stack.enter(next, {}, true);
65
+ // Re-enter this step while rebuilding a stack. `renderPath` is the dispatch
66
+ // position AFTER this step: a rebuilt frame must carry the same render path
67
+ // the renderer had there, or the provides it publishes would be located wrong.
68
+ enterFrame(stack, next, renderPath) {
69
+ return stack.enter(next, {}, true, renderPath);
81
70
  }
82
71
  toAbstractPathStep() {
83
72
  return this;
@@ -94,41 +83,29 @@ var Step = class {
94
83
  return null;
95
84
  }
96
85
  };
97
- var BindStep = class _BindStep extends Step {
86
+ var BindStep = class extends Step {
98
87
  constructor(binds) {
99
88
  super();
100
89
  this.binds = binds;
101
90
  }
102
- lookup(v, _dval) {
91
+ lookup(v) {
103
92
  return v;
104
93
  }
105
- enterFrame(stack, next) {
106
- return stack.enter(next, { ...this.binds }, false);
107
- }
108
- withIndex(i) {
109
- return new _BindStep({ ...this.binds, key: i });
110
- }
111
- withKey(key) {
112
- return new _BindStep({ ...this.binds, key });
94
+ enterFrame(stack, next, renderPath) {
95
+ return stack.enter(next, { ...this.binds }, false, renderPath);
113
96
  }
114
97
  toAbstractPathStep() {
115
98
  return null;
116
99
  }
117
100
  };
118
- var ScopeBindStep = class _ScopeBindStep extends BindStep {
101
+ var ScopeBindStep = class extends BindStep {
119
102
  constructor(val, binds = {}) {
120
103
  super(binds);
121
104
  this.val = val;
122
105
  }
123
- enterFrame(stack, next) {
106
+ enterFrame(stack, next, renderPath) {
124
107
  const dyn = this.val.evalAsHandler(stack)?.call(stack.it) ?? {};
125
- return stack.enter(next, { ...this.binds, ...dyn }, false);
126
- }
127
- withIndex(i) {
128
- return new _ScopeBindStep(this.val, { ...this.binds, key: i });
129
- }
130
- withKey(key) {
131
- return new _ScopeBindStep(this.val, { ...this.binds, key });
108
+ return stack.enter(next, { ...this.binds, ...dyn }, false, renderPath);
132
109
  }
133
110
  };
134
111
  var FieldStep = class extends Step {
@@ -136,18 +113,12 @@ var FieldStep = class extends Step {
136
113
  super();
137
114
  this.field = field;
138
115
  }
139
- lookup(v, dval = null) {
140
- return readKey(v, this.field, dval);
116
+ lookup(v) {
117
+ return seqGet(v, this.field, NONE);
141
118
  }
142
119
  setDraftValue(root, v) {
143
120
  writeKey(root, this.field, v);
144
121
  }
145
- withIndex(i) {
146
- return new SeqStep(this.field, i);
147
- }
148
- withKey(k) {
149
- return new SeqStep(this.field, k);
150
- }
151
122
  toKey() {
152
123
  return { field: this.field };
153
124
  }
@@ -158,15 +129,15 @@ var SeqStep = class extends Step {
158
129
  this.field = field;
159
130
  this.key = key;
160
131
  }
161
- lookup(v, dval = null) {
162
- return seqGet(readKey(v, this.field, null), this.key, dval);
132
+ lookup(v) {
133
+ return seqGet(seqGet(v, this.field, null), this.key, NONE);
163
134
  }
164
135
  setDraftValue(root, v) {
165
- const seq = readKey(root, this.field, null);
136
+ const seq = seqGet(root, this.field, null);
166
137
  if (seq != null) writeSeqKey(seq, this.key, v);
167
138
  }
168
- enterFrame(stack, next) {
169
- return stack.enter(next, { key: this.key }, true);
139
+ enterFrame(stack, next, renderPath) {
140
+ return stack.enter(next, { key: this.key }, true, renderPath);
170
141
  }
171
142
  toKey() {
172
143
  return { field: this.field, key: this.key };
@@ -178,20 +149,20 @@ var SeqAccessStep = class extends Step {
178
149
  this.seqField = seqField;
179
150
  this.keyField = keyField;
180
151
  }
181
- lookup(v, dval = null) {
182
- const seq = readKey(v, this.seqField, NONE);
183
- const key = readKey(v, this.keyField, NONE);
184
- return key !== NONE && seq !== NONE ? seqGet(seq, key, dval) : dval;
152
+ lookup(v) {
153
+ const seq = seqGet(v, this.seqField, NONE);
154
+ const key = seqGet(v, this.keyField, NONE);
155
+ return key !== NONE && seq !== NONE ? seqGet(seq, key, NONE) : NONE;
185
156
  }
186
157
  setDraftValue(root, v) {
187
- const seq = readKey(root, this.seqField, NONE);
188
- const key = readKey(root, this.keyField, NONE);
158
+ const seq = seqGet(root, this.seqField, NONE);
159
+ const key = seqGet(root, this.keyField, NONE);
189
160
  if (seq !== NONE && key !== NONE) writeSeqKey(seq, key, v);
190
161
  }
191
162
  // Resolve `keyField` against `v` now and freeze it as a literal-key `SeqStep`, so a
192
163
  // later lookup/setValue lands on this same item even if `keyField` changes meanwhile.
193
164
  pinKey(v) {
194
- const key = readKey(v, this.keyField, NONE);
165
+ const key = seqGet(v, this.keyField, NONE);
195
166
  return key === NONE ? this : new SeqStep(this.seqField, key);
196
167
  }
197
168
  // The key is a *field reference* resolved live, so it is unknown without a value;
@@ -202,70 +173,62 @@ var SeqAccessStep = class extends Step {
202
173
  }
203
174
  };
204
175
  var EachBindStep = class extends Step {
205
- constructor(iterInfo, key) {
176
+ constructor(eachNode, key) {
206
177
  super();
207
- this.iterInfo = iterInfo;
178
+ this.eachNode = eachNode;
208
179
  this.key = key;
209
180
  }
210
- lookup(v, _dval) {
181
+ lookup(v) {
211
182
  return v;
212
183
  }
213
184
  // Replay the renderer's per-item binds (key, value + any @enrich-with binds)
214
185
  // so a rebuilt stack matches the one @each rendered with.
215
- enterFrame(stack, next) {
216
- return stack.enter(next, this.iterInfo.enrichBinds(stack, this.key), false);
186
+ enterFrame(stack, next, renderPath) {
187
+ return stack.enter(next, this.eachNode.enrichBinds(stack, this.key), false, renderPath);
217
188
  }
218
189
  toAbstractPathStep() {
219
190
  return null;
220
191
  }
221
192
  };
222
193
  var EachRenderItStep = class extends SeqStep {
223
- enterFrame(stack, next) {
224
- return stack.enter(next, { key: this.key, value: next }, false).enter(next, {}, true);
194
+ enterFrame(stack, next, renderPath) {
195
+ return stack.enter(next, { key: this.key, value: next }, false, renderPath).enter(next, {}, true, renderPath);
225
196
  }
226
197
  toAbstractPathStep() {
227
198
  return new SeqStep(this.field, this.key);
228
199
  }
229
200
  };
230
- function warnRawDynStep(op, step) {
231
- console.warn(`Path.${op} reached a DynStep: call toTransactionPath() first`, step);
201
+ function stepToJson(step) {
202
+ if (step instanceof SeqStep) return { f: step.field, k: step.key };
203
+ if (step instanceof FieldStep) return { f: step.field };
204
+ if (step instanceof SeqAccessStep) return { f: step.seqField, a: step.keyField };
205
+ return null;
232
206
  }
233
- var DynStep = class extends Step {
234
- constructor(producerCompId, producerSteps) {
235
- super();
236
- this.producerCompId = producerCompId;
237
- this.producerSteps = producerSteps;
238
- this.interiorCids = /* @__PURE__ */ new Set();
239
- }
240
- // Steps spliced into the transaction path in place of this marker.
241
- teleportSteps() {
242
- return this.producerSteps;
243
- }
244
- lookup(_v, dval = null) {
245
- warnRawDynStep("lookup", this);
246
- return dval;
247
- }
248
- enterFrame(stack, _next) {
249
- warnRawDynStep("enterFrame", this);
250
- return stack;
251
- }
252
- };
253
- var DynEachStep = class extends DynStep {
254
- constructor(producerCompId, producerSteps, key) {
255
- super(producerCompId, producerSteps);
256
- this.key = key;
257
- }
258
- teleportSteps() {
259
- const { producerSteps, key } = this;
260
- if (producerSteps.length === 0) return producerSteps;
261
- const last = producerSteps[producerSteps.length - 1];
262
- if (!(last instanceof FieldStep)) {
263
- console.warn("DynEachStep: seq-access dynamic cannot be iterated", this);
264
- return producerSteps;
265
- }
266
- return producerSteps.slice(0, -1).concat(new SeqStep(last.field, key));
207
+ function stepFromJson(j) {
208
+ if (j == null || typeof j.f !== "string") return null;
209
+ if (j.k !== void 0) return new SeqStep(j.f, j.k);
210
+ if (j.a !== void 0) return new SeqAccessStep(j.f, j.a);
211
+ return new FieldStep(j.f);
212
+ }
213
+ function pathToJson(path2) {
214
+ const out = [];
215
+ for (const step of path2.steps) {
216
+ const j = stepToJson(step);
217
+ if (j === null) return null;
218
+ out.push(j);
267
219
  }
268
- };
220
+ return out;
221
+ }
222
+ function pathFromJson(j) {
223
+ if (!Array.isArray(j)) return null;
224
+ const steps = [];
225
+ for (const item of j) {
226
+ const step = stepFromJson(item);
227
+ if (step === null) return null;
228
+ steps.push(step);
229
+ }
230
+ return new Path(steps);
231
+ }
269
232
  var Path = class _Path {
270
233
  constructor(steps = []) {
271
234
  this.steps = steps;
@@ -273,43 +236,13 @@ var Path = class _Path {
273
236
  concat(steps) {
274
237
  return new _Path(this.steps.concat(steps));
275
238
  }
276
- popStep() {
277
- return new _Path(this.steps.slice(0, -1));
278
- }
279
- // The dispatch path: frame-only steps removed, one step per crossed component
280
- // (DynStep included). `popStep` over it bubbles through every component.
239
+ // Frame-only steps removed, one step per crossed component: `popStep` over the
240
+ // result bubbles through every component.
281
241
  compact() {
282
242
  const out = [];
283
243
  for (const step of this.steps) {
284
244
  const s = step.toAbstractPathStep();
285
- if (s !== null) {
286
- if (s !== step) s._originCid = step._originCid;
287
- out.push(s);
288
- }
289
- }
290
- return new _Path(out);
291
- }
292
- // The abstract path used to apply a transaction: every DynStep is teleported —
293
- // the steps interior to its producer..consumer span are dropped and the
294
- // producer's own path spliced in — so a mutation lands on the data's real
295
- // location. A path with no DynStep is returned unchanged.
296
- toTransactionPath() {
297
- let hasDyn = false;
298
- for (const step of this.steps)
299
- if (step instanceof DynStep) {
300
- hasDyn = true;
301
- break;
302
- }
303
- if (!hasDyn) return this;
304
- const out = [];
305
- for (const step of this.steps) {
306
- if (step instanceof DynStep) {
307
- while (out.length > 0 && step.interiorCids.has(out[out.length - 1]._originCid)) out.pop();
308
- for (const ts of step.teleportSteps()) {
309
- ts._originCid = step.producerCompId;
310
- out.push(ts);
311
- }
312
- } else out.push(step);
245
+ if (s !== null) out.push(s);
313
246
  }
314
247
  return new _Path(out);
315
248
  }
@@ -317,7 +250,6 @@ var Path = class _Path {
317
250
  // key as it is *now* so a later lookup/setValue lands on the same item even if the
318
251
  // keyField changed meanwhile (e.g. the selected tab moved while an intent was in
319
252
  // flight). Returns a new Path with those steps replaced; `this` if nothing pinned.
320
- // Must be called on a transaction path (no DynSteps — call toTransactionPath first).
321
253
  pinKeys(root) {
322
254
  let curVal = root;
323
255
  let out = null;
@@ -325,7 +257,7 @@ var Path = class _Path {
325
257
  const step = this.steps[i];
326
258
  const pinned = step.pinKey(curVal);
327
259
  if (pinned !== step) (out ??= this.steps.slice())[i] = pinned;
328
- curVal = step.lookup(curVal, NONE);
260
+ curVal = step.lookup(curVal);
329
261
  if (curVal === NONE) break;
330
262
  }
331
263
  return out ? new _Path(out) : this;
@@ -333,20 +265,20 @@ var Path = class _Path {
333
265
  lookup(v, dval = null) {
334
266
  let curVal = v;
335
267
  for (const step of this.steps) {
336
- curVal = step.lookup(curVal, NONE);
268
+ curVal = step.lookup(curVal);
337
269
  if (curVal === NONE) return dval;
338
270
  }
339
271
  return curVal;
340
272
  }
341
273
  // The values entered along the path, root→leaf (root included): index 0 is `root`,
342
274
  // the last entry is the leaf this path resolves to. Stops early at the first
343
- // unresolvable step. Call on a transaction path (no DynSteps). Used to walk the
344
- // component instances on a dispatch path (filter via Components.getCompFor).
275
+ // unresolvable step. Used to walk the component instances on a dispatch path
276
+ // (filter via Components.getCompFor).
345
277
  resolveChain(root) {
346
278
  const out = [root];
347
279
  let curVal = root;
348
280
  for (const step of this.steps) {
349
- curVal = step.lookup(curVal, NONE);
281
+ curVal = step.lookup(curVal);
350
282
  if (curVal === NONE) break;
351
283
  out.push(curVal);
352
284
  }
@@ -355,7 +287,7 @@ var Path = class _Path {
355
287
  // A flat `[{ field, key? }]` list of the addressing steps, skipping frame-only
356
288
  // steps (binds). Generic path introspection so tooling (e.g. the storybook
357
289
  // activity log) can identify which subtree a transaction touched without
358
- // depending on Step internals. Call on a transaction path (no DynSteps).
290
+ // depending on Step internals.
359
291
  toKeys() {
360
292
  const out = [];
361
293
  for (const step of this.steps) {
@@ -369,69 +301,146 @@ var Path = class _Path {
369
301
  return (0, immer_exports.produce)(root, (draft) => {
370
302
  let parent = draft;
371
303
  for (let i = 0; i < this.steps.length - 1; i++) {
372
- parent = this.steps[i].lookup(parent, NONE);
304
+ parent = this.steps[i].lookup(parent);
373
305
  if (parent === NONE) return;
374
306
  }
375
307
  this.steps.at(-1).setDraftValue(parent, v);
376
308
  });
377
309
  }
310
+ };
311
+ function walkItems(stack, items, renderPath, path2) {
312
+ let prev = stack.it;
313
+ for (const step of items) {
314
+ const next = step.lookup(prev);
315
+ if (next === NONE) {
316
+ console.warn("bad PathItem", { root: stack.it, step, path: path2 });
317
+ return null;
318
+ }
319
+ renderPath = renderPath.pushItem(step);
320
+ stack = step.enterFrame(stack, next, renderPath);
321
+ prev = next;
322
+ }
323
+ return [stack, renderPath];
324
+ }
325
+ var EMPTY_PATH = new Path([]);
326
+ var DispatchPath = class _DispatchPath {
327
+ constructor(frames = [{ base: EMPTY_PATH, items: [] }]) {
328
+ this.frames = frames;
329
+ }
330
+ // Plain addressing steps in one frame based at the root: what a caller means
331
+ // by "this position" when it has no continuation of its own.
332
+ static ofSteps(steps) {
333
+ return new _DispatchPath([{ base: EMPTY_PATH, items: steps.slice() }]);
334
+ }
335
+ get top() {
336
+ return this.frames[this.frames.length - 1];
337
+ }
338
+ _withTopItems(items) {
339
+ const frames = this.frames.slice();
340
+ frames[frames.length - 1] = { base: this.top.base, items };
341
+ return new _DispatchPath(frames);
342
+ }
343
+ concat(steps) {
344
+ return this._withTopItems(this.top.items.concat(steps));
345
+ }
346
+ pushItem(step) {
347
+ return this.concat([step]);
348
+ }
349
+ pushFrame(base) {
350
+ return new _DispatchPath(this.frames.concat({ base, items: [] }));
351
+ }
352
+ // Whether bubbling has anywhere left to go: another step in this frame, or a
353
+ // visual caller underneath it. (A path always has at least one frame.)
354
+ canPop() {
355
+ return this.frames.length > 1 || this.frames[0].items.length > 0;
356
+ }
357
+ // One component closer to the root. At the top of a frame that is popping back
358
+ // to the visual caller, not to the producer's own parent — the caller is where
359
+ // the `*name` was written, and where an unhandled message should keep going.
360
+ popStep() {
361
+ const { top } = this;
362
+ if (top.items.length > 0) return this._withTopItems(top.items.slice(0, -1));
363
+ if (this.frames.length > 1) return new _DispatchPath(this.frames.slice(0, -1));
364
+ return this;
365
+ }
366
+ // Drop frame-only steps inside every frame independently; a frame's base is
367
+ // already addressing-only.
368
+ compact() {
369
+ return new _DispatchPath(
370
+ this.frames.map(({ base, items }) => ({ base, items: new Path(items).compact().steps }))
371
+ );
372
+ }
373
+ // A stable string for the ADDRESS this path denotes, for the render cache. The
374
+ // same immutable value can sit at two places in the tree, and a subtree rendered
375
+ // at one of them bakes that address in — the provides it publishes are located
376
+ // there. Frame-only steps address nothing, so they are compacted out: two sites
377
+ // that differ only in binds do render the same subtree.
378
+ get addressKey() {
379
+ this._addressKey ??= renderPathText(this.toTransactionPath().compact());
380
+ return this._addressKey;
381
+ }
382
+ // The active transaction address: the top frame's absolute base followed by
383
+ // its ordinary descendant steps. This is where a mutation lands.
384
+ toTransactionPath() {
385
+ const { base, items } = this.top;
386
+ return base.concat(items);
387
+ }
388
+ // Rebuild the render stack this path was dispatched from. A frame with a base
389
+ // re-enters at that absolute value first — replaying the resume a `*name`
390
+ // render performed — and then walks its ordinary items.
378
391
  buildStack(stack) {
379
- let prev = stack.it;
380
- for (const step of this.steps) {
381
- const next = step.lookup(prev, NONE);
382
- if (next === NONE) {
383
- console.warn("bad PathItem", { root: stack.it, step, path: this });
384
- return null;
392
+ let renderPath = new _DispatchPath();
393
+ for (let i = 0; i < this.frames.length; i++) {
394
+ const { base, items } = this.frames[i];
395
+ if (i > 0 || base.steps.length > 0) {
396
+ const baseValue = base.lookup(stack.root, NONE);
397
+ if (baseValue === NONE) {
398
+ console.warn("bad frame base", { base, path: this });
399
+ return null;
400
+ }
401
+ renderPath = renderPath.pushFrame(base);
402
+ stack = stack.enter(baseValue, {}, true, renderPath);
385
403
  }
386
- stack = step.enterFrame(stack, next);
387
- prev = next;
404
+ const walked = walkItems(stack, items, renderPath, this);
405
+ if (walked === null) return null;
406
+ [stack, renderPath] = walked;
388
407
  }
389
408
  return stack;
390
409
  }
391
- static fromNodeAndEventName(node, eventName, rootNode, maxDepth, comps, stopOnNoEvent = true) {
392
- const pathSteps = [];
393
- const pendingDyns = [];
410
+ // Reconstruct the dispatch position of a DOM event from the `data-*` stamps and
411
+ // `§…§` meta comments the renderer left on the way down. `stopOnNoEvent` is off for
412
+ // drag events, whose path is needed even where no handler is registered.
413
+ static fromNodeAndEventName(node, eventName, rootNode, comps, stopOnNoEvent = true) {
414
+ const parts = [];
394
415
  const bubbles = BUBBLING_EVENTS.has(eventName);
395
- let depth = 0;
396
416
  let eventIds = [];
397
417
  let handlers = null;
398
- let nodeIds = [];
418
+ let nodeRefs = [];
399
419
  let isLeafComponent = true;
400
420
  const crossComponent = (cidNum, vid) => {
401
421
  const comp = comps.getComponentForId(cidNum);
402
- let pushStep = true;
422
+ let pushPart = true;
403
423
  if (handlers === null && (isLeafComponent || bubbles)) {
404
424
  handlers = findHandlers(comp, eventIds, vid, eventName);
405
425
  if (handlers === null) {
406
426
  if (isLeafComponent && stopOnNoEvent && !bubbles) return false;
407
427
  } else if (!isLeafComponent) {
408
- pathSteps.length = 0;
409
- pendingDyns.length = 0;
410
- pushStep = false;
428
+ parts.length = 0;
429
+ pushPart = false;
411
430
  }
412
431
  }
413
432
  isLeafComponent = false;
414
- for (const dyn of pendingDyns) dyn.interiorCids.add(cidNum);
415
- if (pushStep) {
416
- const step = resolvePathStep(comp, nodeIds, vid);
417
- if (step) {
418
- step._originCid = cidNum;
419
- pathSteps.push(step);
420
- if (step instanceof DynStep) {
421
- step.interiorCids.add(cidNum);
422
- pendingDyns.push(step);
423
- }
424
- }
433
+ if (pushPart) {
434
+ const part = resolvePathPart(comp, nodeRefs, vid);
435
+ if (part) parts.push(part);
425
436
  }
426
- for (let i = pendingDyns.length - 1; i >= 0; i--)
427
- if (pendingDyns[i].producerCompId === cidNum) pendingDyns.splice(i, 1);
428
437
  eventIds = [];
429
- nodeIds = [];
438
+ nodeRefs = [];
430
439
  return true;
431
440
  };
432
- while (node && node !== rootNode && depth < maxDepth) {
441
+ while (node && node !== rootNode) {
433
442
  if (node?.dataset) {
434
- const { eid, cid, vid } = node.dataset;
443
+ const { eid, cid, vid, rp } = node.dataset;
435
444
  if (eid !== void 0) eventIds.push(eid);
436
445
  const metas = metaChain(node.previousSibling);
437
446
  let sawComp = false;
@@ -439,21 +448,46 @@ var Path = class _Path {
439
448
  if (m.$ === "Comp") {
440
449
  sawComp = true;
441
450
  if (!crossComponent(m.cid, m.vid)) return NO_EVENT_INFO;
442
- nodeIds.push({ nid: m.nid });
451
+ nodeRefs.push({ nid: m.nid, base: pathFromJson(m.base) });
443
452
  } else {
444
- nodeIds.push({ nid: m.nid, si: m.si, sk: m.sk });
453
+ nodeRefs.push({ nid: m.nid, si: m.si, sk: m.sk });
454
+ }
455
+ }
456
+ if (!sawComp && cid !== void 0) {
457
+ if (!crossComponent(+cid, vid)) return NO_EVENT_INFO;
458
+ if (rp !== void 0) {
459
+ const base = parseRenderPath(rp);
460
+ if (base !== null) nodeRefs.push({ base });
445
461
  }
446
462
  }
447
- if (!sawComp && cid !== void 0 && !crossComponent(+cid, vid)) return NO_EVENT_INFO;
448
463
  }
449
- depth += 1;
450
464
  node = node.parentNode;
451
465
  }
452
- if (pendingDyns.length > 0)
453
- console.warn("event reconstruction: dynamic-var producer not found", pendingDyns);
454
- return [new _Path(pathSteps.reverse()), handlers];
466
+ parts.reverse();
467
+ let path2 = new _DispatchPath();
468
+ for (const part of parts)
469
+ path2 = part.base !== void 0 ? path2.pushFrame(part.base) : path2.pushItem(part.step);
470
+ return [path2, handlers];
471
+ }
472
+ };
473
+ function keyedPath(path2, key) {
474
+ const steps = path2.steps;
475
+ const last = steps[steps.length - 1];
476
+ if (last instanceof SeqStep || last instanceof FieldStep)
477
+ return new Path(steps.slice(0, -1).concat(new SeqStep(last.field, key)));
478
+ return null;
479
+ }
480
+ function renderPathText(path2) {
481
+ return JSON.stringify(pathToJson(path2));
482
+ }
483
+ function parseRenderPath(text) {
484
+ try {
485
+ return pathFromJson(JSON.parse(text));
486
+ } catch (err) {
487
+ console.warn("bad render path", err, text);
488
+ return null;
455
489
  }
456
- };
490
+ }
457
491
  function metaChain(n) {
458
492
  const out = [];
459
493
  while (n?.nodeType === 8 && n.textContent[0] === "§") {
@@ -498,28 +532,28 @@ var StepCtx = class _StepCtx {
498
532
  resolveNode() {
499
533
  return this.comp.getNodeForId(+this.meta.nid, this.vid);
500
534
  }
501
- applyKey(pi) {
502
- if (pi === null) return null;
503
- const m = this.meta;
504
- if (m.si !== void 0) return pi.withIndex(+m.si);
505
- if (m.sk !== void 0) return pi.withKey(m.sk);
506
- return pi;
507
- }
508
535
  };
509
- function resolvePathStep(comp, nodeIds, vid) {
510
- for (let i = 0; i < nodeIds.length; i++) {
511
- const ctx = new StepCtx(comp, nodeIds, i, vid);
512
- const step = ctx.resolveNode().toPathStep(ctx);
513
- if (step !== null) return step;
536
+ function resolvePathPart(comp, nodeRefs, vid) {
537
+ for (let i = 0; i < nodeRefs.length; i++) {
538
+ const ref = nodeRefs[i];
539
+ if (ref.base != null) return { base: ref.base };
540
+ if (ref.nid === void 0 || ref.nid === null) continue;
541
+ const ctx = new StepCtx(comp, nodeRefs, i, vid);
542
+ const step = ctx.resolveNode()?.toPathStep(ctx) ?? null;
543
+ if (step !== null) return { step };
514
544
  }
515
545
  return null;
516
546
  }
517
547
  var NO_EVENT_INFO = [null, null];
518
548
  var BUBBLING_EVENTS = /* @__PURE__ */ new Set(["drop"]);
549
+ var path = () => new PathBuilder();
519
550
  var PathBuilder = class {
520
551
  constructor() {
521
552
  this.pathChanges = [];
522
553
  }
554
+ toPath() {
555
+ return new Path(this.pathChanges);
556
+ }
523
557
  add(pathChange) {
524
558
  this.pathChanges.push(pathChange);
525
559
  return this;
@@ -535,10 +569,8 @@ var PathBuilder = class {
535
569
  }
536
570
  };
537
571
 
538
- // src/util/env.js
539
- var isMac = (globalThis.navigator?.userAgent ?? "").toLowerCase().includes("mac");
540
-
541
572
  // src/value.js
573
+ var isMac = (globalThis.navigator?.userAgent ?? "").toLowerCase().includes("mac");
542
574
  var VALID_VAL_ID_RE = /^[a-zA-Z][a-zA-Z0-9_]*\??$/;
543
575
  var isValidValId = (name) => VALID_VAL_ID_RE.test(name);
544
576
  var VALID_FLOAT_RE = /^-?[0-9]+(\.[0-9]+)?$/;
@@ -720,22 +752,10 @@ function _parsePredicate(s, tokens, px) {
720
752
  }
721
753
  return new PredicateVal(pred, args);
722
754
  }
723
- function kindOf(val) {
724
- if (val === null) return 0;
725
- if (val instanceof ConstVal) return val.kind;
726
- if (val instanceof StrTplVal) return val.kind;
727
- if (val instanceof SeqAccessVal) return K_SEQ;
728
- if (val instanceof FieldVal) return K_FIELD;
729
- if (val instanceof MethodVal) return K_METHOD;
730
- if (val instanceof BindVal) return K_BIND;
731
- if (val instanceof DynVal) return K_DYN;
732
- if (val instanceof NameVal) return K_NAME;
733
- if (val instanceof EventMemberVal) return K_EVENT;
734
- return 0;
735
- }
755
+ var kindOf = (val) => val?.kind ?? 0;
736
756
  var BaseVal = class {
737
- render(_stack, _rx) {
738
- }
757
+ // No kind: never accepted by a group (PredicateVal, which only parseBool builds).
758
+ kind = 0;
739
759
  eval(_stack) {
740
760
  }
741
761
  toPathItem() {
@@ -755,9 +775,6 @@ var ConstVal = class extends BaseVal {
755
775
  this.val = val;
756
776
  this.kind = kind;
757
777
  }
758
- render(_stack, _rx) {
759
- return this.val;
760
- }
761
778
  eval(_stack) {
762
779
  return this.val;
763
780
  }
@@ -783,9 +800,7 @@ var PredicateVal = class extends BaseVal {
783
800
  return `${this.pred.name} ${this.args.map(String).join(" ")}`;
784
801
  }
785
802
  };
786
- var VarVal = class extends BaseVal {
787
- };
788
- var StrTplVal = class _StrTplVal extends VarVal {
803
+ var StrTplVal = class _StrTplVal extends BaseVal {
789
804
  constructor(vals) {
790
805
  super();
791
806
  this.vals = vals;
@@ -798,9 +813,6 @@ var StrTplVal = class _StrTplVal extends VarVal {
798
813
  for (const v of this.vals) if (!(v instanceof ConstVal) || v.fromMacroVar) return false;
799
814
  return true;
800
815
  }
801
- render(stack, _rx) {
802
- return this.eval(stack);
803
- }
804
816
  eval(stack) {
805
817
  const strs = new Array(this.vals.length);
806
818
  for (let i = 0; i < this.vals.length; i++) strs[i] = this.vals[i]?.eval(stack, "");
@@ -837,7 +849,8 @@ var StrTplVal = class _StrTplVal extends VarVal {
837
849
  return new _StrTplVal(lo === 0 && hi === vals.length ? vals : vals.slice(lo, hi));
838
850
  }
839
851
  };
840
- var NameVal = class extends VarVal {
852
+ var NameVal = class extends BaseVal {
853
+ kind = K_NAME;
841
854
  constructor(name) {
842
855
  super();
843
856
  this.name = name;
@@ -887,6 +900,7 @@ var EVENT_CONVENIENCES = {
887
900
  dragKey: nullSafe((info) => info.lookupBind("key"))
888
901
  };
889
902
  var EventMemberVal = class extends BaseVal {
903
+ kind = K_EVENT;
890
904
  constructor(members) {
891
905
  super();
892
906
  this.members = members;
@@ -909,24 +923,21 @@ var EventMemberVal = class extends BaseVal {
909
923
  return `e.${this.members.join(".")}`;
910
924
  }
911
925
  };
912
- var RenderVal = class extends BaseVal {
913
- render(stack, _rx) {
914
- return this.eval(stack);
915
- }
916
- };
917
- var RenderNameVal = class extends RenderVal {
926
+ var SigilNameVal = class extends BaseVal {
918
927
  constructor(name) {
919
928
  super();
920
929
  this.name = name;
921
930
  }
931
+ toString() {
932
+ return this.sigil + this.name;
933
+ }
922
934
  };
923
- var BindVal = class extends RenderNameVal {
935
+ var BindVal = class extends SigilNameVal {
936
+ kind = K_BIND;
937
+ sigil = "@";
924
938
  eval(stack) {
925
939
  return stack.lookupBind(this.name);
926
940
  }
927
- toString() {
928
- return `@${this.name}`;
929
- }
930
941
  };
931
942
  var BindMemberVal = class extends BindVal {
932
943
  constructor(name, member) {
@@ -941,37 +952,35 @@ var BindMemberVal = class extends BindVal {
941
952
  return `@${this.name}.${this.member}`;
942
953
  }
943
954
  };
944
- var DynVal = class extends RenderNameVal {
955
+ var DynVal = class extends SigilNameVal {
956
+ kind = K_DYN;
957
+ sigil = "*";
945
958
  eval(stack) {
946
959
  return stack.lookupDynamic(this.name);
947
960
  }
948
- toString() {
949
- return `*${this.name}`;
950
- }
951
961
  };
952
- var FieldVal = class extends RenderNameVal {
962
+ var FieldVal = class extends SigilNameVal {
963
+ kind = K_FIELD;
964
+ sigil = ".";
953
965
  eval(stack) {
954
966
  return stack.lookupFieldRaw(this.name);
955
967
  }
956
968
  toPathItem() {
957
969
  return new FieldStep(this.name);
958
970
  }
959
- toString() {
960
- return `.${this.name}`;
961
- }
962
971
  };
963
- var MethodVal = class extends RenderNameVal {
972
+ var MethodVal = class extends SigilNameVal {
973
+ kind = K_METHOD;
974
+ sigil = "$";
964
975
  eval(stack) {
965
976
  return stack.lookupMethod(this.name);
966
977
  }
967
978
  evalAsHandler(stack) {
968
979
  return stack.lookupFieldRaw(this.name);
969
980
  }
970
- toString() {
971
- return `$${this.name}`;
972
- }
973
981
  };
974
- var SeqAccessVal = class extends RenderVal {
982
+ var SeqAccessVal = class extends BaseVal {
983
+ kind = K_SEQ;
975
984
  constructor(seqVal, keyVal) {
976
985
  super();
977
986
  this.seqVal = seqVal;
@@ -994,9 +1003,6 @@ var Attributes = class {
994
1003
  constructor(items) {
995
1004
  this.items = items;
996
1005
  }
997
- static parse(attributes, px, parseAll = false) {
998
- return new AttrParser(px).parse(attributes, parseAll);
999
- }
1000
1006
  isConstant() {
1001
1007
  return false;
1002
1008
  }
@@ -1014,10 +1020,16 @@ function parseDirectiveValue(px, directiveName, source, parser) {
1014
1020
  }
1015
1021
  return val;
1016
1022
  }
1023
+ var ITER_DIRECTIVES = {
1024
+ when: "whenVal",
1025
+ "loop-with": "loopWithVal",
1026
+ "enrich-with": "enrichWithVal"
1027
+ };
1028
+ var parseIterDirective = (px, name, s) => parseDirectiveValue(px, name, s, parseAlterHandler);
1017
1029
  function parseIterationDirectives(attributes, px) {
1018
1030
  const parseNamed = (name) => {
1019
1031
  const attr = attributes.getNamedItem(`@${name}`);
1020
- return attr ? parseDirectiveValue(px, name, attr.value, parseAlterHandler) : null;
1032
+ return attr ? parseIterDirective(px, name, attr.value) : null;
1021
1033
  };
1022
1034
  return { whenVal: parseNamed("when"), loopWithVal: parseNamed("loop-with") };
1023
1035
  }
@@ -1058,11 +1070,22 @@ var AttrParser = class {
1058
1070
  this.px.onParseIssue("bad-value", info);
1059
1071
  }
1060
1072
  }
1061
- parseThen(s) {
1062
- if (this.ifAttr) this.ifAttr.thenVal = parseText(s, this.px) ?? NOT_SET_VAL;
1073
+ // `@then` / `@else` fill the branches of the `@if.<attr>` on the same element.
1074
+ parseBranch(slot, name, s) {
1075
+ if (this.ifAttr === null) return this._orphan(name, s, "if");
1076
+ this.ifAttr[slot] = parseText(s, this.px) ?? NOT_SET_VAL;
1077
+ }
1078
+ // `@when` / `@loop-with` / `@enrich-with` amend the `@each` on the same element. A
1079
+ // loop-less `@enrich-with` is a scope of its own; the other two need the loop.
1080
+ parseIter(name, s) {
1081
+ const val = parseIterDirective(this.px, name, s);
1082
+ if (this.eachAttr !== null) this.eachAttr[ITER_DIRECTIVES[name]] = val;
1083
+ else if (name === "enrich-with") this.pushWrapper("scope", s, val);
1084
+ else this._orphan(name, s, "each");
1063
1085
  }
1064
- parseElse(value) {
1065
- if (this.ifAttr) this.ifAttr.elseVal = parseText(value, this.px) ?? NOT_SET_VAL;
1086
+ // A directive that only means something next to another one, written alone.
1087
+ _orphan(name, value, needs) {
1088
+ this.px.onParseIssue("orphan-directive", { name, value, needs });
1066
1089
  }
1067
1090
  parseEvent(directiveName, value) {
1068
1091
  const [eventName, ...modifiers] = directiveName.slice(3).split("+");
@@ -1076,77 +1099,51 @@ var AttrParser = class {
1076
1099
  this.events.add(eventName, handler, modifiers);
1077
1100
  }
1078
1101
  }
1079
- _parseDirectiveValue(directiveName, s, parserFn) {
1080
- return parseDirectiveValue(this.px, directiveName, s, parserFn);
1081
- }
1082
1102
  parseDirective(s, directiveName) {
1083
1103
  switch (directiveName) {
1084
1104
  case "dangerouslysetinnerhtml":
1085
1105
  this.attrs ??= [];
1086
- this.attrs.push(new RawHtmlAttr(this._parseDirectiveValue(directiveName, s, parseText)));
1106
+ this.attrs.push(new RawHtmlAttr(parseDirectiveValue(this.px, directiveName, s, parseText)));
1087
1107
  this.hasDynamic = true;
1088
1108
  return;
1089
1109
  case "push-view":
1090
- this.pushWrapper("push-view", s, this._parseDirectiveValue(directiveName, s, parseText));
1110
+ this.pushWrapper("push-view", s, parseDirectiveValue(this.px, directiveName, s, parseText));
1091
1111
  return;
1092
1112
  case "text":
1093
- this.textChild = this._parseDirectiveValue(directiveName, s, parseText);
1113
+ this.textChild = parseDirectiveValue(this.px, directiveName, s, parseText);
1094
1114
  return;
1095
1115
  case "show":
1096
- this.pushWrapper("show", s, this._parseDirectiveValue(directiveName, s, parseBool));
1116
+ this.pushWrapper("show", s, parseDirectiveValue(this.px, directiveName, s, parseBool));
1097
1117
  return;
1098
1118
  case "hide":
1099
- this.pushWrapper("hide", s, this._parseDirectiveValue(directiveName, s, parseBool));
1119
+ this.pushWrapper("hide", s, parseDirectiveValue(this.px, directiveName, s, parseBool));
1100
1120
  return;
1101
1121
  case "each": {
1102
- const val = this._parseDirectiveValue(directiveName, s, parseSequence);
1122
+ const val = parseDirectiveValue(this.px, directiveName, s, parseSequence);
1103
1123
  this.eachAttr = this.pushWrapper("each", s, val);
1104
1124
  return;
1105
1125
  }
1106
- case "enrich-with":
1107
- if (this.eachAttr !== null)
1108
- this.eachAttr.enrichWithVal = this._parseDirectiveValue(
1109
- directiveName,
1110
- s,
1111
- parseAlterHandler
1112
- );
1113
- else
1114
- this.pushWrapper(
1115
- "scope",
1116
- s,
1117
- this._parseDirectiveValue(directiveName, s, parseAlterHandler)
1118
- );
1119
- return;
1120
1126
  case "when":
1121
- this._parseWhen(s);
1122
- return;
1123
1127
  case "loop-with":
1124
- this._parseLoopWith(s);
1128
+ case "enrich-with":
1129
+ this.parseIter(directiveName, s);
1125
1130
  return;
1126
1131
  case "then":
1127
- this.parseThen(s);
1132
+ this.parseBranch("thenVal", directiveName, s);
1128
1133
  return;
1129
1134
  case "else":
1130
- this.parseElse(s);
1135
+ this.parseBranch("elseVal", directiveName, s);
1131
1136
  return;
1132
1137
  }
1133
1138
  if (directiveName.startsWith("on.")) this.parseEvent(directiveName, s);
1134
1139
  else if (directiveName.startsWith("if.")) this.parseIf(directiveName, s);
1135
- else if (directiveName.startsWith("then.")) this.parseThen(s);
1136
- else if (directiveName.startsWith("else.")) this.parseElse(s);
1140
+ else if (directiveName.startsWith("then.")) this.parseBranch("thenVal", directiveName, s);
1141
+ else if (directiveName.startsWith("else.")) this.parseBranch("elseVal", directiveName, s);
1137
1142
  else {
1138
1143
  const info = { name: directiveName, value: s };
1139
1144
  this.px.onParseIssue("unknown-directive", info);
1140
1145
  }
1141
1146
  }
1142
- _parseWhen(s) {
1143
- if (this.eachAttr !== null)
1144
- this.eachAttr.whenVal = this._parseDirectiveValue("when", s, parseAlterHandler);
1145
- }
1146
- _parseLoopWith(s) {
1147
- if (this.eachAttr !== null)
1148
- this.eachAttr.loopWithVal = this._parseDirectiveValue("loop-with", s, parseAlterHandler);
1149
- }
1150
1147
  parse(attributes, parseAll = false) {
1151
1148
  for (const { name, value } of attributes) {
1152
1149
  const charCode = name.charCodeAt(0);
@@ -1499,19 +1496,21 @@ var VNode = class _VNode extends VBase {
1499
1496
  const createOpts = attrs.is != null ? { is: attrs.is } : void 0;
1500
1497
  const node = ns === null ? doc.createElement(tag, createOpts) : doc.createElementNS(ns, tag, createOpts);
1501
1498
  const cOpts = childOpts(this, ns, opts);
1502
- if ("value" in attrs || "checked" in attrs) {
1503
- const { value, checked, ...rest } = attrs;
1504
- applyProperties(node, rest);
1505
- appendChildNodes(node, this.childs, cOpts);
1506
- if (value !== void 0) applyValueLast(node, value);
1507
- if (checked !== void 0) setProp(node, "checked", checked, false);
1508
- } else {
1509
- applyProperties(node, attrs);
1510
- appendChildNodes(node, this.childs, cOpts);
1511
- }
1499
+ applyAttrs(node, attrs, () => appendChildNodes(node, this.childs, cOpts));
1512
1500
  return node;
1513
1501
  }
1514
1502
  };
1503
+ function applyAttrs(node, attrs, placeChildren) {
1504
+ const hasValue = "value" in attrs;
1505
+ const hasChecked = "checked" in attrs;
1506
+ if (hasValue || hasChecked) {
1507
+ const { value: _v, checked: _c, ...rest } = attrs;
1508
+ applyProperties(node, rest);
1509
+ } else applyProperties(node, attrs);
1510
+ placeChildren();
1511
+ if (hasValue) applyValueLast(node, attrs.value);
1512
+ if (hasChecked) setProp(node, "checked", attrs.checked, isNamespaced(node));
1513
+ }
1515
1514
  function diffProps(a, b) {
1516
1515
  if (a === b) return null;
1517
1516
  let diff = null;
@@ -1542,22 +1541,16 @@ function morphNode(domNode, source, target, opts) {
1542
1541
  }
1543
1542
  if (type === 1 && source.isSameKind(target)) {
1544
1543
  const propsDiff = diffProps(source.attrs, target.attrs);
1545
- const hasValue = propsDiff != null && "value" in propsDiff;
1546
- const hasChecked = propsDiff != null && "checked" in propsDiff;
1547
- if (propsDiff) {
1548
- if (hasValue || hasChecked) {
1549
- const { value: _v, checked: _c, ...rest } = propsDiff;
1550
- applyProperties(domNode, rest);
1551
- } else applyProperties(domNode, propsDiff);
1552
- }
1553
- if (!target.attrs.dangerouslySetInnerHTML) {
1544
+ const placeChildren = () => {
1545
+ if (target.attrs.dangerouslySetInnerHTML) return;
1554
1546
  const ns = effectiveNs(target, opts);
1555
1547
  morphChildren(domNode, source.childs, target.childs, childOpts(target, ns, opts));
1548
+ };
1549
+ if (propsDiff) applyAttrs(domNode, propsDiff, placeChildren);
1550
+ else placeChildren();
1551
+ if (!(propsDiff && "value" in propsDiff) && source.tag === "SELECT") {
1552
+ if (target.attrs.value !== void 0) applyValueLast(domNode, target.attrs.value);
1556
1553
  }
1557
- if (hasValue) applyValueLast(domNode, propsDiff.value);
1558
- else if (source.tag === "SELECT" && target.attrs.value !== void 0)
1559
- applyValueLast(domNode, target.attrs.value);
1560
- if (hasChecked) setProp(domNode, "checked", propsDiff.checked, false);
1561
1554
  return domNode;
1562
1555
  }
1563
1556
  if (type === 11) {
@@ -1676,20 +1669,26 @@ function h(tagName, properties, children, namespace) {
1676
1669
  }
1677
1670
 
1678
1671
  // src/anode.js
1679
- function resolveDynProducer(comp, name) {
1680
- const own = comp?.provide?.[name];
1681
- const producerComp = own != null ? comp : comp?.scope?.lookupProvider(name) ?? null;
1682
- const producerProvide = own ?? producerComp?.provide?.[name];
1683
- if (producerComp == null || producerProvide == null) return null;
1684
- const pi = producerProvide.val?.toPathItem?.() ?? null;
1685
- return { producerCompId: producerComp.id, producerSteps: pi ? [pi] : [] };
1672
+ function renderTarget(val, stack) {
1673
+ if (val instanceof DynVal) {
1674
+ const loc = stack.lookupDynamicLocated(val.name);
1675
+ if (loc?.path == null) return [loc?.value ?? null, stack.renderPath, null];
1676
+ return [loc.value, stack.renderPath.pushFrame(loc.path), loc.path];
1677
+ }
1678
+ const step = val.toPathItem?.() ?? null;
1679
+ if (step === null) return [val.eval(stack), stack.renderPath, null];
1680
+ const path2 = stack.renderPath.pushItem(step);
1681
+ return [val.eval(stack), path2, stack.pendingFrame ? path2.toTransactionPath() : null];
1686
1682
  }
1687
1683
  var BaseNode = class {
1688
1684
  render(_stack, _rx) {
1689
1685
  return null;
1690
1686
  }
1691
- setDataAttr(key, val) {
1692
- console.warn("setDataAttr not implemented for", this, { key, val });
1687
+ // Stamp a `data-*` attribute on the element(s) this node renders. A no-op for
1688
+ // nodes that produce no element of their own (text, `<x render*>`): a view whose
1689
+ // root is one of those records its component boundary in the `§Comp§` meta
1690
+ // comment instead (see Renderer._rValComp).
1691
+ setDataAttr(_key, _val) {
1693
1692
  }
1694
1693
  isConstant() {
1695
1694
  return false;
@@ -1728,21 +1727,12 @@ var TextNode = class extends BaseNode {
1728
1727
  isConstant() {
1729
1728
  return true;
1730
1729
  }
1731
- setDataAttr(_key, _val) {
1732
- }
1733
1730
  };
1734
1731
  var CommentNode = class extends TextNode {
1735
1732
  render(_stack, rx) {
1736
1733
  return rx.renderComment(this.val);
1737
1734
  }
1738
1735
  };
1739
- function optimizeChilds(childs) {
1740
- for (let i = 0; i < childs.length; i++) {
1741
- const child = childs[i];
1742
- if (child.isConstant()) childs[i] = new RenderOnceNode(child);
1743
- else child.optimize();
1744
- }
1745
- }
1746
1736
  function optimizeNode(node) {
1747
1737
  if (node.isConstant()) return new RenderOnceNode(node);
1748
1738
  node.optimize();
@@ -1757,7 +1747,8 @@ var ChildsNode = class extends BaseNode {
1757
1747
  return this.childs.every((v) => v.isConstant());
1758
1748
  }
1759
1749
  optimize() {
1760
- optimizeChilds(this.childs);
1750
+ const { childs } = this;
1751
+ for (let i = 0; i < childs.length; i++) childs[i] = optimizeNode(childs[i]);
1761
1752
  }
1762
1753
  };
1763
1754
  var DomNode = class extends ChildsNode {
@@ -1769,8 +1760,7 @@ var DomNode = class extends ChildsNode {
1769
1760
  }
1770
1761
  render(stack, rx) {
1771
1762
  const childNodes = new Array(this.childs.length);
1772
- for (let i = 0; i < childNodes.length; i++)
1773
- childNodes[i] = this.childs[i]?.render?.(stack, rx) ?? null;
1763
+ for (let i = 0; i < childNodes.length; i++) childNodes[i] = this.childs[i].render(stack, rx);
1774
1764
  return rx.renderTag(this.tagName, this.attrs.eval(stack), childNodes, this.namespace);
1775
1765
  }
1776
1766
  setDataAttr(key, val) {
@@ -1782,7 +1772,7 @@ var DomNode = class extends ChildsNode {
1782
1772
  };
1783
1773
  var FragmentNode = class extends ChildsNode {
1784
1774
  render(stack, rx) {
1785
- return rx.renderFragment(this.childs.map((c) => c?.render(stack, rx)));
1775
+ return rx.renderFragment(this.childs.map((c) => c.render(stack, rx)));
1786
1776
  }
1787
1777
  setDataAttr(key, val) {
1788
1778
  for (const child of this.childs) child.setDataAttr(key, val);
@@ -1796,8 +1786,10 @@ var ANode = class _ANode extends BaseNode {
1796
1786
  this.nodeId = nodeId;
1797
1787
  this.val = val;
1798
1788
  }
1799
- toPathStep(ctx) {
1800
- return ctx.applyKey(this.val?.toPathItem?.() ?? null);
1789
+ // The addressing step this node contributes to an event's dispatch path, given
1790
+ // the `StepCtx` of the meta that named it; null when it addresses nothing.
1791
+ toPathStep(_ctx) {
1792
+ return this.val?.toPathItem?.() ?? null;
1801
1793
  }
1802
1794
  static parse(html2, px) {
1803
1795
  const nodes = px.parseHTML(html2);
@@ -1813,8 +1805,8 @@ var ANode = class _ANode extends BaseNode {
1813
1805
  return maybeFragment(trimmed);
1814
1806
  }
1815
1807
  static fromDOM(node, px) {
1816
- if (node instanceof px.Text) return new TextNode(node.textContent);
1817
- else if (node instanceof px.Comment) return new CommentNode(node.textContent);
1808
+ if (node.nodeType === 3) return new TextNode(node.textContent);
1809
+ if (node.nodeType === 8) return new CommentNode(node.textContent);
1818
1810
  const { childNodes, attributes: attrs, tagName: tag } = node;
1819
1811
  const childs = [];
1820
1812
  for (let i = 0; i < childNodes.length; i++) {
@@ -1832,11 +1824,11 @@ var ANode = class _ANode extends BaseNode {
1832
1824
  const slotName = attrs.getNamedItem("name")?.value ?? "_";
1833
1825
  return px.frame.macroSlots[slotName] ?? maybeFragment(childs);
1834
1826
  }
1835
- const [nAttrs, wrappers] = Attributes.parse(attrs, px, true);
1827
+ const [nAttrs, wrappers] = new AttrParser(px).parse(attrs, true);
1836
1828
  px.onAttributes(nAttrs, wrappers, null, true, tag);
1837
1829
  return wrap(px.newMacroNode(macroName, nAttrs.toMacroVars(), childs), px, wrappers);
1838
1830
  } else if (VALID_NODE_RE.test(tag)) {
1839
- const [nAttrs, wrappers, textChild] = Attributes.parse(attrs, px);
1831
+ const [nAttrs, wrappers, textChild] = new AttrParser(px).parse(attrs);
1840
1832
  px.onAttributes(nAttrs, wrappers, textChild, false, tag);
1841
1833
  if (textChild) childs.unshift(new RenderTextNode(null, textChild));
1842
1834
  const domChilds = tag !== "PRE" ? condenseChildsWhites(childs) : childs;
@@ -1998,39 +1990,32 @@ var RenderViewId = class extends ANode {
1998
1990
  evalViewName(stack) {
1999
1991
  return this.viewVal ? this.viewVal.eval(stack) : null;
2000
1992
  }
2001
- // A `<x render*>` produces no DOM element of its own to carry `data-cid`;
2002
- // when it is a view's root the component boundary is recorded in the `Comp`
2003
- // meta comment instead (see Renderer._rValComp), so this is a no-op.
2004
- setDataAttr(_key, _val) {
2005
- }
2006
1993
  };
2007
- function dynRenderStep(comp, name, key) {
2008
- const p = resolveDynProducer(comp, name);
2009
- if (!p) return null;
2010
- return key === void 0 ? new DynStep(p.producerCompId, p.producerSteps) : new DynEachStep(p.producerCompId, p.producerSteps, key);
2011
- }
2012
1994
  var RenderNode = class extends RenderViewId {
2013
1995
  render(stack, rx) {
2014
- const newStack = stack.enter(this.val.eval(stack), {}, true);
2015
- return rx.renderIt(newStack, this, "", this.evalViewName(stack));
1996
+ const [value, renderPath, base] = renderTarget(this.val, stack);
1997
+ const newStack = stack.enter(value, {}, true, renderPath, false);
1998
+ return rx.renderIt(newStack, this, this.evalViewName(stack), base);
2016
1999
  }
2000
+ // A `*name` target contributes no step: the site recorded the absolute base it
2001
+ // resumed at, and event reconstruction turns that into a continuation frame.
2017
2002
  toPathStep(ctx) {
2018
- if (this.val instanceof DynVal) return dynRenderStep(ctx.comp, this.val.name);
2003
+ if (this.val instanceof DynVal) return null;
2019
2004
  return super.toPathStep(ctx);
2020
2005
  }
2021
2006
  };
2022
2007
  var RenderItNode = class extends RenderViewId {
2023
2008
  render(stack, rx) {
2024
- const newStack = stack.enter(stack.it, {}, true);
2025
- return rx.renderIt(newStack, this, "", this.evalViewName(stack));
2009
+ const base = stack.pendingFrame ? stack.renderPath.toTransactionPath() : null;
2010
+ const newStack = stack.enter(stack.it, {}, true, stack.renderPath, false);
2011
+ return rx.renderIt(newStack, this, this.evalViewName(stack), base);
2026
2012
  }
2027
2013
  toPathStep(ctx) {
2028
2014
  const next = ctx.next();
2029
2015
  if (next === null) return null;
2030
2016
  const nextNode = next.resolveNode();
2031
2017
  if (nextNode instanceof EachNode && next.hasKey) {
2032
- if (nextNode.val instanceof DynVal)
2033
- return dynRenderStep(ctx.comp, nextNode.val.name, next.key);
2018
+ if (nextNode.val instanceof DynVal) return null;
2034
2019
  return new EachRenderItStep(nextNode.val.name, next.key);
2035
2020
  }
2036
2021
  return null;
@@ -2052,9 +2037,6 @@ var RenderTextNode = class extends ANode {
2052
2037
  render(stack, _rx) {
2053
2038
  return this.val.eval(stack);
2054
2039
  }
2055
- // Renders to a text node, which can't carry `data-cid`.
2056
- setDataAttr(_key, _val) {
2057
- }
2058
2040
  };
2059
2041
  var RenderOnceNode = class extends BaseNode {
2060
2042
  constructor(node) {
@@ -2129,35 +2111,45 @@ var ScopeNode = class extends WrapperNode {
2129
2111
  var EachNode = class extends WrapperNode {
2130
2112
  constructor(nodeId, val) {
2131
2113
  super(nodeId, val);
2132
- this.iterInfo = new IterInfo(val, null, null, null);
2114
+ this.iterInfo = new IterInfo();
2133
2115
  }
2134
2116
  render(stack, rx) {
2135
- return rx.renderEachWhen(stack, this.iterInfo, this.node, this.nodeId);
2117
+ return rx.renderEachWhen(stack, this);
2118
+ }
2119
+ // The sequence and the three loop handlers, resolved against `stack`.
2120
+ evalIter(stack) {
2121
+ const { whenVal, loopWithVal, enrichWithVal } = this.iterInfo;
2122
+ return {
2123
+ seq: this.val.eval(stack) ?? [],
2124
+ filter: whenVal?.evalAsHandler(stack) ?? filterAlwaysTrue,
2125
+ loopWith: loopWithVal?.evalAsHandler(stack) ?? nullLoopWith,
2126
+ enricher: enrichWithVal?.evalAsHandler(stack) ?? null
2127
+ };
2128
+ }
2129
+ // Rebuild the per-item binds for `key` (see iteration.js bindsForKey).
2130
+ enrichBinds(stack, key) {
2131
+ const { seq, filter, loopWith, enricher } = this.evalIter(stack);
2132
+ const ctx = makeLoopCtx(stack, filter);
2133
+ return bindsForKey({ seq, it: stack.it, loopWith, enricher, ctx }, key);
2136
2134
  }
2137
2135
  toPathStep(ctx) {
2138
- return ctx.hasKey ? new EachBindStep(this.iterInfo, ctx.key) : null;
2136
+ return ctx.hasKey ? new EachBindStep(this, ctx.key) : null;
2137
+ }
2138
+ // Where one item lives, for the render path. `@each` re-binds `it` to the item
2139
+ // whether or not the body is a component, so the position moves either way and
2140
+ // the step has to address it — `.rows` iterated at `key` IS `rows[key]`.
2141
+ // Null for a dynamic sequence: `*rows` carries its own absolute path and enters
2142
+ // a continuation frame instead (see Renderer.renderEachWhen).
2143
+ itemStep(key) {
2144
+ return this.val instanceof FieldVal ? new SeqStep(this.val.name, key) : null;
2139
2145
  }
2140
2146
  static register = true;
2141
2147
  };
2142
2148
  var IterInfo = class {
2143
- constructor(val, whenVal, loopWithVal, enrichWithVal) {
2144
- this.val = val;
2145
- this.whenVal = whenVal;
2146
- this.loopWithVal = loopWithVal;
2147
- this.enrichWithVal = enrichWithVal;
2148
- }
2149
- eval(stack) {
2150
- const seq = this.val.eval(stack) ?? [];
2151
- const filter = this.whenVal?.evalAsHandler(stack) ?? filterAlwaysTrue;
2152
- const loopWith = this.loopWithVal?.evalAsHandler(stack) ?? nullLoopWith;
2153
- const enricher = this.enrichWithVal?.evalAsHandler(stack) ?? null;
2154
- return { seq, filter, loopWith, enricher };
2155
- }
2156
- // Rebuild the per-item binds for `key` (see iteration.js bindsForKey).
2157
- enrichBinds(stack, key) {
2158
- const { seq, filter, loopWith, enricher } = this.eval(stack);
2159
- const ctx = makeLoopCtx(stack, filter);
2160
- return bindsForKey({ seq, it: stack.it, loopWith, enricher, ctx }, key);
2149
+ constructor() {
2150
+ this.whenVal = null;
2151
+ this.loopWithVal = null;
2152
+ this.enrichWithVal = null;
2161
2153
  }
2162
2154
  };
2163
2155
  function xOp(consumed = [], { wrappable = false, wrapper = null, ignoresChildren = false } = {}) {
@@ -2185,26 +2177,31 @@ var WRAPPER_NODES = {
2185
2177
  scope: ScopeNode,
2186
2178
  "push-view": PushViewNameNode
2187
2179
  };
2188
- var ParseContext = class _ParseContext {
2189
- constructor(document2, Text, Comment, nodes, events, macroNodes, frame, parent) {
2190
- this.nodes = nodes ?? [];
2191
- this.events = events ?? [];
2192
- this.macroNodes = macroNodes ?? [];
2193
- this.parent = parent ?? null;
2194
- this.frame = frame ?? {};
2195
- this.document = document2 ?? globalThis.document;
2196
- this.Text = Text ?? globalThis.Text;
2197
- this.Comment = Comment ?? globalThis.Comment;
2198
- this.cacheConstNodes = true;
2180
+ var ParseContext = class {
2181
+ constructor(document2 = globalThis.document) {
2182
+ this.document = document2;
2183
+ this.nodes = [];
2184
+ this.events = [];
2185
+ this.macroNodes = [];
2186
+ this.parent = null;
2187
+ this.frame = {};
2199
2188
  this.currentTag = null;
2200
2189
  }
2201
2190
  isInsideMacro(name) {
2202
2191
  return this.frame.macroName === name || this.parent?.isInsideMacro(name);
2203
2192
  }
2193
+ // A macro body parses in a child context that shares every accumulator with
2194
+ // its parent (nodes, events, macroNodes, and whatever a subclass collects) and
2195
+ // differs only in its frame. Cloned rather than constructed so a subclass
2196
+ // keeps collecting inside macro bodies without re-implementing this — the
2197
+ // lint and class-set contexts used to lose (or had to copy) exactly that.
2204
2198
  enterMacro(macroName, macroVars, macroSlots) {
2205
- const { document: document2, Text, Comment, nodes, events, macroNodes } = this;
2206
- const frame = { macroName, macroVars, macroSlots };
2207
- return new _ParseContext(document2, Text, Comment, nodes, events, macroNodes, frame, this);
2199
+ const child = Object.create(Object.getPrototypeOf(this));
2200
+ return Object.assign(child, this, {
2201
+ frame: { macroName, macroVars, macroSlots },
2202
+ parent: this,
2203
+ currentTag: null
2204
+ });
2208
2205
  }
2209
2206
  parseHTML(html2) {
2210
2207
  const t = this.document.createElement("template");
@@ -2269,10 +2266,10 @@ var isBlockDomNode = (n) => {
2269
2266
  return node instanceof DomNode && HTML_BLOCK_TAGS.has(node.tagName);
2270
2267
  };
2271
2268
  var isEmptyText = (c) => c instanceof TextNode && c.val === "";
2272
- var isIgnorableXChild = (c) => c instanceof CommentNode || (c.isWhiteSpace?.() ?? false);
2269
+ var isIgnorableXChild = (c) => c instanceof CommentNode || c.isWhiteSpace();
2273
2270
  var hasMeaningfulChilds = (childs) => childs.some((c) => !isIgnorableXChild(c));
2274
2271
  function trimEdgeWhite(node) {
2275
- if (!node.isWhiteSpace?.()) return false;
2272
+ if (!node.isWhiteSpace()) return false;
2276
2273
  node.condenseWhiteSpace();
2277
2274
  return true;
2278
2275
  }
@@ -2283,7 +2280,7 @@ function condenseChildsWhites(childs) {
2283
2280
  if (last > 0 && trimEdgeWhite(childs[last])) emptied = true;
2284
2281
  for (let i = 1; i < last; i++) {
2285
2282
  const cur = childs[i];
2286
- if (!(cur.isWhiteSpace?.() && cur.hasNewLine())) continue;
2283
+ if (!(cur.isWhiteSpace() && cur.hasNewLine())) continue;
2287
2284
  const bothBlock = isBlockDomNode(childs[i - 1]) && isBlockDomNode(childs[i + 1]);
2288
2285
  cur.condenseWhiteSpace(bothBlock ? "" : " ");
2289
2286
  if (bothBlock) emptied = true;
@@ -2291,12 +2288,12 @@ function condenseChildsWhites(childs) {
2291
2288
  return emptied ? childs.filter((c) => !isEmptyText(c)) : childs;
2292
2289
  }
2293
2290
  var View = class {
2294
- constructor(name, rawView = "No View Defined", style = "", anode = null, ctx = null) {
2291
+ constructor(name, rawView = "No View Defined", style = "") {
2295
2292
  this.name = name;
2296
- this.anode = anode;
2297
- this.style = style;
2298
- this.ctx = ctx;
2299
2293
  this.rawView = rawView;
2294
+ this.style = style;
2295
+ this.anode = null;
2296
+ this.ctx = null;
2300
2297
  }
2301
2298
  compile(ctx, scope, cid) {
2302
2299
  this.ctx = ctx;
@@ -2304,7 +2301,7 @@ var View = class {
2304
2301
  this.anode.setDataAttr("data-cid", cid);
2305
2302
  this.anode.setDataAttr("data-vid", this.name);
2306
2303
  this.ctx.compile(scope);
2307
- if (ctx.cacheConstNodes) this.anode = optimizeNode(this.anode);
2304
+ this.anode = optimizeNode(this.anode);
2308
2305
  }
2309
2306
  render(stack, rx) {
2310
2307
  if (this.anode === null) {
@@ -2329,7 +2326,7 @@ var NodeEvents = class {
2329
2326
  getHandlersFor(eventName) {
2330
2327
  let r = null;
2331
2328
  for (const handler of this.handlers)
2332
- if (handler.handlesEventName(eventName)) {
2329
+ if (handler.name === eventName) {
2333
2330
  r ??= [];
2334
2331
  r.push(handler);
2335
2332
  }
@@ -2343,9 +2340,6 @@ var NodeEvent = class {
2343
2340
  this.modifierWrapper = compileModifiers(name, modifiers);
2344
2341
  this.modifiers = modifiers;
2345
2342
  }
2346
- handlesEventName(name) {
2347
- return this.name === name;
2348
- }
2349
2343
  getHandlerAndArgs(stack, event) {
2350
2344
  const r = this.handlerCall.getHandlerAndArgs(stack, event);
2351
2345
  r[0] = this.modifierWrapper(r[0], event);
@@ -2397,69 +2391,287 @@ function compileModifiers(eventName, names) {
2397
2391
  };
2398
2392
  }
2399
2393
 
2400
- // src/components.js
2401
- var COMPONENT = /* @__PURE__ */ Symbol.for("tutuca.component");
2402
- var Components = class {
2403
- constructor() {
2404
- this.byId = /* @__PURE__ */ new Map();
2405
- }
2406
- registerComponent(Comp) {
2407
- this.byId.set(Comp[COMPONENT].id, Comp);
2408
- }
2409
- getComponentForId(id) {
2410
- return this.byId.get(id) ?? null;
2394
+ // src/stack.js
2395
+ var STOP = /* @__PURE__ */ Symbol("STOP");
2396
+ var NEXT = /* @__PURE__ */ Symbol("NEXT");
2397
+ var DEFAULT_ROUTE = ["dyn", "lex"];
2398
+ var isTypeName = (s) => {
2399
+ const c = s.charCodeAt(0);
2400
+ return c >= 65 && c <= 90;
2401
+ };
2402
+ function lookup(chain, name, dv = null) {
2403
+ let n = chain;
2404
+ while (n !== null) {
2405
+ const r = n[0].lookup(name);
2406
+ if (r === STOP) return dv;
2407
+ if (r !== NEXT) return r;
2408
+ n = n[1];
2411
2409
  }
2412
- getCompFor(v) {
2413
- const Comp = v?.constructor;
2414
- return Comp?.[COMPONENT] ? Comp : null;
2410
+ return dv;
2411
+ }
2412
+ var BindFrame = class {
2413
+ constructor(it, binds, isFrame) {
2414
+ this.it = it;
2415
+ this.binds = binds;
2416
+ this.isFrame = isFrame;
2415
2417
  }
2416
- getHandlerFor(v, name, key) {
2417
- return this.getCompFor(v)?.[key][name] ?? null;
2418
+ lookup(name) {
2419
+ const v = this.binds[name];
2420
+ return v === void 0 ? this.isFrame ? STOP : NEXT : v;
2418
2421
  }
2419
- // The `lex` leg of an intent's route: the handlers registered on the scope chain of
2420
- // the component that raised it, innermost first. A list, not a first match — see
2421
- // ComponentStack.lookupIntentChain.
2422
- getIntentChainFor(v, name) {
2423
- return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
2422
+ };
2423
+ var DynFrame = class {
2424
+ constructor(binds, types) {
2425
+ this.binds = binds;
2426
+ this.types = types;
2424
2427
  }
2425
- compileStyles() {
2426
- const styles = [];
2427
- for (const Comp of this.byId.values()) styles.push(Comp[COMPONENT].compileStyle());
2428
- return styles.join("\n");
2428
+ lookup(name) {
2429
+ const v = (isTypeName(name) ? this.types : this.binds)[name];
2430
+ return v === void 0 ? NEXT : v;
2429
2431
  }
2430
2432
  };
2431
- var ComponentStack = class _ComponentStack {
2432
- constructor(comps = new Components(), parent = null) {
2433
- this.comps = comps;
2434
- this.parent = parent;
2435
- this.byName = {};
2436
- this.intentsByName = {};
2437
- this.macros = {};
2433
+ function computeViewsId(views) {
2434
+ let s = "";
2435
+ let n = views;
2436
+ while (n !== null) {
2437
+ s += n[0];
2438
+ n = n[1];
2438
2439
  }
2439
- enter() {
2440
- return new _ComponentStack(this.comps, this);
2440
+ return s === "main" ? "" : s;
2441
+ }
2442
+ var Stack = class _Stack {
2443
+ constructor(fields) {
2444
+ Object.assign(this, fields);
2441
2445
  }
2442
- registerComponents(comps, opts) {
2443
- const { aliases = {} } = opts ?? {};
2444
- for (let i = 0; i < comps.length; i++) {
2445
- const Comp = comps[i];
2446
- Comp[COMPONENT].scope = this.enter();
2447
- this.comps.registerComponent(Comp);
2448
- this.byName[Comp.name] = Comp;
2446
+ _with(patch) {
2447
+ return new _Stack({ ...this, ...patch });
2448
+ }
2449
+ // Evaluate every provide the entered component publishes and push them as one
2450
+ // dynBinds frame, keyed by NAME. A value is published together with the absolute
2451
+ // path it lives at: the same declaration is read as `*name` AND resumed at by
2452
+ // `<x render="*name">`, so a consumer needs both halves. Types go in the same
2453
+ // frame's other map. No-op with no provides.
2454
+ _pushProvides() {
2455
+ const comp = this.comps.getCompFor(this.it);
2456
+ if (comp == null) return this;
2457
+ const { provide, provideType } = comp;
2458
+ const binds = {};
2459
+ const types = {};
2460
+ let has = false;
2461
+ const base = this._publishBase();
2462
+ for (const k in provide) {
2463
+ const step = provide[k].toPathItem?.() ?? null;
2464
+ if (step === null) continue;
2465
+ const value = provide[k].eval(this);
2466
+ binds[k] = { value, path: base === null ? null : base.concat([step]) };
2467
+ has = true;
2449
2468
  }
2450
- for (const alias in aliases) {
2451
- const comp = this.byName[aliases[alias]];
2452
- console.assert(this.byName[alias] === void 0, "alias overrides component", alias);
2453
- if (comp !== void 0) this.byName[alias] = comp;
2454
- else console.warn("alias", alias, "to inexistent component", aliases[alias]);
2469
+ for (const k in provideType) {
2470
+ types[k] = provideType[k];
2471
+ has = true;
2455
2472
  }
2473
+ if (!has) return this;
2474
+ return this._with({ dynBinds: [new DynFrame(binds, types), this.dynBinds] });
2475
+ }
2476
+ // The absolute address of the component being entered, or null when this
2477
+ // position cannot be written down as one. Frame-only steps carry bindings and
2478
+ // address nothing, so they are compacted away first; what is left is checked
2479
+ // against the value actually being rendered, because a scope CAN move the
2480
+ // render position without contributing an addressing step (a plain `@each`
2481
+ // body re-binds `it` to the item while its rebuild step is an identity).
2482
+ //
2483
+ // A null base still publishes the VALUE — `*name` reads it fine — but there is
2484
+ // nowhere to resume, so `<x render="*name">` renders nothing rather than
2485
+ // editing whatever happens to live at the address we guessed.
2486
+ _publishBase() {
2487
+ const base = this.renderPath.toTransactionPath().compact();
2488
+ return base.lookup(this.root, NOT_FOUND) === this.it ? base : null;
2489
+ }
2490
+ static root(comps, it, ctx = null) {
2491
+ return new _Stack({
2492
+ comps,
2493
+ root: it,
2494
+ it,
2495
+ binds: [new BindFrame(it, {}, true), null],
2496
+ dynBinds: [new DynFrame({}, {}), null],
2497
+ views: ["main", null],
2498
+ viewsId: "",
2499
+ renderPath: new DispatchPath(),
2500
+ pendingFrame: false,
2501
+ ctx
2502
+ })._pushProvides();
2503
+ }
2504
+ // `renderPath` defaults to this stack's own: an ordinary scope does not move.
2505
+ // `pendingFrame` clears on a component frame (which emits the base) and is
2506
+ // inherited by transparent scopes, which have to carry it to the next boundary.
2507
+ enter(it, bindings = {}, isFrame = true, renderPath = this.renderPath, pendingFrame = null) {
2508
+ const stack = this._with({
2509
+ it,
2510
+ binds: [new BindFrame(it, bindings, isFrame), this.binds],
2511
+ renderPath,
2512
+ pendingFrame: pendingFrame ?? (isFrame ? false : this.pendingFrame)
2513
+ });
2514
+ return isFrame ? stack._pushProvides() : stack;
2456
2515
  }
2457
- registerMacros(macros) {
2458
- for (const key in macros) {
2459
- const lower = key.toLowerCase();
2460
- console.assert(this.macros[lower] === void 0, "macro key collision", lower);
2461
- this.macros[lower] = macros[key];
2462
- }
2516
+ pushViewName(name) {
2517
+ const views = [name, this.views];
2518
+ return this._with({ views, viewsId: computeViewsId(views) });
2519
+ }
2520
+ // Published types are stable per scope and would only churn the render cache, so
2521
+ // the cache key covers values alone.
2522
+ _pushDynBindValuesToArray(arr, comp) {
2523
+ for (const k in comp.provide) arr.push(this.lookupDynamic(k));
2524
+ for (const k in comp.lookup) arr.push(this.lookupDynamic(k));
2525
+ }
2526
+ // `*name`: the nearest binding above (including this component's own provides,
2527
+ // pushed on entering it), else a path registered in the component's lexical
2528
+ // scope, else this component's declared default, else null.
2529
+ //
2530
+ // One chain walk and no producer resolution: a lookup names what it WANTS, so the
2531
+ // frame it wants is keyed by that name. The default belongs to the CONSUMER's
2532
+ // declaration and is evaluated against the consumer's stack, which is why it is
2533
+ // consulted only after the whole chain has missed.
2534
+ lookupDynamicLocated(name) {
2535
+ if (isTypeName(name)) return null;
2536
+ const v = lookup(this.dynBinds, name);
2537
+ if (v != null) return v;
2538
+ const comp = this.comps.getCompFor(this.it);
2539
+ if (comp == null) return null;
2540
+ const path2 = comp.scope?.lookupPath?.(name) ?? null;
2541
+ if (path2 !== null) {
2542
+ const value2 = path2.lookup(this.root, NOT_FOUND);
2543
+ if (value2 !== NOT_FOUND) return { value: value2, path: path2 };
2544
+ }
2545
+ const dval = comp.lookup[name] ?? null;
2546
+ if (dval === null) return null;
2547
+ const step = dval.toPathItem?.() ?? null;
2548
+ const value = dval.eval(this);
2549
+ return step === null ? { value, path: null } : { value, path: this.renderPath.toTransactionPath().concat([step]) };
2550
+ }
2551
+ lookupDynamic(name) {
2552
+ if (isTypeName(name)) return lookup(this.dynBinds, name);
2553
+ return this.lookupDynamicLocated(name)?.value ?? null;
2554
+ }
2555
+ lookupBind(name) {
2556
+ return lookup(this.binds, name);
2557
+ }
2558
+ lookupFieldRaw(name) {
2559
+ return this.it[name] ?? null;
2560
+ }
2561
+ lookupMethod(name) {
2562
+ const fn = this.it[name];
2563
+ return fn instanceof Function ? fn.call(this.it) : null;
2564
+ }
2565
+ // The dispatched DOM event / drag info, read only by EventMemberVal's
2566
+ // `e.<member>` handler args. Null outside a live event transaction (`ctx` is
2567
+ // then a send/intent transaction, which carries no `e`).
2568
+ lookupEvent() {
2569
+ return this.ctx?.e ?? null;
2570
+ }
2571
+ lookupDragInfo() {
2572
+ return this.ctx?.dragInfo ?? null;
2573
+ }
2574
+ getHandlerFor(name, key) {
2575
+ return this.comps.getHandlerFor(this.it, name, key);
2576
+ }
2577
+ lookupBestView(views, defaultViewName) {
2578
+ let n = this.views;
2579
+ while (n !== null) {
2580
+ const view = views[n[0]];
2581
+ if (view !== void 0) return view;
2582
+ n = n[1];
2583
+ }
2584
+ return views[defaultViewName];
2585
+ }
2586
+ };
2587
+ var NOT_FOUND = /* @__PURE__ */ Symbol("NOT_FOUND");
2588
+
2589
+ // src/components.js
2590
+ var COMPONENT = /* @__PURE__ */ Symbol.for("tutuca.component");
2591
+ var Components = class {
2592
+ constructor() {
2593
+ this.byId = /* @__PURE__ */ new Map();
2594
+ }
2595
+ registerComponent(Comp) {
2596
+ this.byId.set(Comp.id, Comp);
2597
+ }
2598
+ getComponentForId(id) {
2599
+ return this.byId.get(id) ?? null;
2600
+ }
2601
+ getCompFor(v) {
2602
+ const Comp = v?.constructor;
2603
+ return Comp?.[COMPONENT] ? Comp : null;
2604
+ }
2605
+ getHandlerFor(v, name, key) {
2606
+ return this.getCompFor(v)?.[key][name] ?? null;
2607
+ }
2608
+ // The `lex` leg of an intent's route: the handlers registered on the scope chain of
2609
+ // the component that raised it, innermost first. A list, not a first match — see
2610
+ // ComponentStack.lookupIntentChain.
2611
+ getIntentChainFor(v, name) {
2612
+ return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
2613
+ }
2614
+ compileStyles() {
2615
+ const styles = [];
2616
+ for (const Comp of this.byId.values()) styles.push(Comp.compileStyle());
2617
+ return styles.join("\n");
2618
+ }
2619
+ };
2620
+ var ComponentStack = class _ComponentStack {
2621
+ constructor(comps = new Components(), parent = null) {
2622
+ this.comps = comps;
2623
+ this.parent = parent;
2624
+ this.byName = {};
2625
+ this.intentsByName = {};
2626
+ this.macros = {};
2627
+ this.paths = {};
2628
+ }
2629
+ enter() {
2630
+ return new _ComponentStack(this.comps, this);
2631
+ }
2632
+ registerComponents(comps, opts) {
2633
+ const { aliases = {}, paths } = opts ?? {};
2634
+ if (paths) this.registerPaths(paths);
2635
+ for (let i = 0; i < comps.length; i++) {
2636
+ const Comp = comps[i];
2637
+ Comp.scope = this.enter();
2638
+ this.comps.registerComponent(Comp);
2639
+ this.byName[Comp.name] = Comp;
2640
+ }
2641
+ for (const alias in aliases) {
2642
+ const comp = this.byName[aliases[alias]];
2643
+ console.assert(this.byName[alias] === void 0, "alias overrides component", alias);
2644
+ if (comp !== void 0) this.byName[alias] = comp;
2645
+ else console.warn("alias", alias, "to inexistent component", aliases[alias]);
2646
+ }
2647
+ }
2648
+ // Register lowercase names as absolute paths from the app state root. A
2649
+ // descendant that declares one in its `lookup` reads and renders `*name` without
2650
+ // anything above it publishing one — which is what makes a session, a theme or a
2651
+ // host-owned value available in its natural registration scope, instead of forcing
2652
+ // an application root whose only job is to `provide` it. Register on a nested
2653
+ // scope to narrow a name; nearest registration wins.
2654
+ //
2655
+ // Uppercase names are ignored: a component TYPE is what `lookupComponent` already
2656
+ // answers, and a type has no path.
2657
+ registerPaths(paths) {
2658
+ for (const name in paths) {
2659
+ if (isTypeName(name)) {
2660
+ console.warn("registerPaths: a type name has no path", name);
2661
+ continue;
2662
+ }
2663
+ this.paths[name] = paths[name].toPath();
2664
+ }
2665
+ }
2666
+ lookupPath(name) {
2667
+ return this.paths[name] ?? this.parent?.lookupPath(name) ?? null;
2668
+ }
2669
+ registerMacros(macros) {
2670
+ for (const key in macros) {
2671
+ const lower = key.toLowerCase();
2672
+ console.assert(this.macros[lower] === void 0, "macro key collision", lower);
2673
+ this.macros[lower] = macros[key];
2674
+ }
2463
2675
  }
2464
2676
  getCompFor(v) {
2465
2677
  return this.comps.getCompFor(v);
@@ -2485,97 +2697,82 @@ var ComponentStack = class _ComponentStack {
2485
2697
  lookupComponent(name) {
2486
2698
  return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
2487
2699
  }
2488
- // The component in this scope chain that provides `name`, innermost first. A lookup
2489
- // names a value without naming its producer, so the render-target teleport
2490
- // (resolveDynProducer) recovers the producer here instead. Sound because
2491
- // PROVIDE_NAME_COLLISION keeps a provide name to one producer per chain; with two,
2492
- // this returns the innermost and the linter has already reported the ambiguity.
2493
- lookupProvider(name) {
2700
+ // Whether anything in this scope chain provides `name`. Existence only: a lookup
2701
+ // names what it WANTS and takes whoever is nearest above it at render time, so
2702
+ // there is no producer to identify — several components may publish one name and
2703
+ // the live render ancestry decides. Used by the linter to tell a lookup that can
2704
+ // be satisfied from one that never will be.
2705
+ hasProvider(name) {
2494
2706
  for (const compName in this.byName) {
2495
- const Comp = this.byName[compName];
2496
- if (Comp.provide?.[name] !== void 0) return Comp;
2707
+ if (this.byName[compName].provide?.[name] !== void 0) return true;
2497
2708
  }
2498
- return this.parent?.lookupProvider(name) ?? null;
2709
+ return this.parent?.hasProvider(name) ?? false;
2499
2710
  }
2500
2711
  lookupMacro(name) {
2501
2712
  return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
2502
2713
  }
2503
2714
  };
2504
- var ProvideInfo = class {
2505
- constructor(val) {
2506
- this.val = val;
2507
- }
2508
- };
2509
- var LookupInfo = class {
2510
- constructor(val) {
2511
- this.val = val;
2512
- }
2513
- };
2514
2715
  var isString = (v) => typeof v === "string";
2515
- var isTypeName = (s) => {
2516
- const c = s.charCodeAt(0);
2517
- return c >= 65 && c <= 90;
2518
- };
2519
2716
  var _rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
2520
2717
  var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
2521
2718
  var _compId = 0;
2522
- var Component = class {
2523
- constructor(Class, o) {
2524
- this.id = _compId++;
2525
- this.name = o.name ?? "UnkComp";
2526
- this.Class = Class;
2527
- this.views = { main: new View("main", o.view, o.style) };
2528
- this.commonStyle = o.commonStyle ?? "";
2529
- this.globalStyle = o.globalStyle ?? "";
2530
- this.receive = o.receive ?? {};
2531
- this.intent = o.intent ?? {};
2532
- this.alter = o.alter ?? {};
2533
- for (const name in o.views ?? {}) {
2534
- const v = o.views[name];
2535
- const { view, style } = isString(v) ? { view: v } : v;
2536
- this.views[name] = new View(name, view, style);
2537
- }
2538
- this._rawProvide = o.provide ?? {};
2539
- this._rawLookup = o.lookup ?? [];
2540
- this.provide = {};
2541
- this.provideType = {};
2542
- this.lookup = {};
2543
- this.scope = null;
2544
- this.spec = o;
2545
- this.extra = {};
2546
- for (const key of Object.keys(o)) if (!KNOWN_SPEC_KEYS.has(key)) this.extra[key] = o[key];
2547
- }
2719
+ function initComponent(Class, o) {
2720
+ Class[COMPONENT] = Class;
2721
+ Class.id = _compId++;
2722
+ Class.views = { main: new View("main", o.view, o.style) };
2723
+ Class.commonStyle = o.commonStyle ?? "";
2724
+ Class.globalStyle = o.globalStyle ?? "";
2725
+ Class.receive = o.receive ?? {};
2726
+ Class.intent = o.intent ?? {};
2727
+ Class.alter = o.alter ?? {};
2728
+ for (const name in o.views ?? {}) {
2729
+ const v = o.views[name];
2730
+ const { view, style } = isString(v) ? { view: v } : v;
2731
+ Class.views[name] = new View(name, view, style);
2732
+ }
2733
+ Class._rawProvide = o.provide ?? {};
2734
+ Class._rawLookup = o.lookup ?? [];
2735
+ Class.provide = {};
2736
+ Class.provideType = {};
2737
+ Class.lookup = {};
2738
+ Class.scope = null;
2739
+ Class.spec = o;
2740
+ Class.extra = {};
2741
+ for (const key of Object.keys(o)) if (!KNOWN_SPEC_KEYS.has(key)) Class.extra[key] = o[key];
2742
+ return Object.assign(Class, COMPONENT_METHODS);
2743
+ }
2744
+ var COMPONENT_METHODS = {
2548
2745
  compile(ParseContext2) {
2549
2746
  for (const name in this.views)
2550
2747
  this.views[name].compile(new ParseContext2(), this.scope, this.id);
2551
2748
  const ctx = this.views.main.ctx;
2552
2749
  for (const key in this._rawProvide) {
2553
2750
  if (isTypeName(key)) {
2554
- if (this._rawProvide[key] === "self") this.provideType[key] = this.Class;
2751
+ if (this._rawProvide[key] === "self") this.provideType[key] = this;
2555
2752
  continue;
2556
2753
  }
2557
2754
  const val = parseProvide(this._rawProvide[key], ctx);
2558
- if (val) this.provide[key] = new ProvideInfo(val);
2755
+ if (val) this.provide[key] = val;
2559
2756
  }
2560
2757
  for (const entry of this._rawLookup) {
2561
2758
  const name = isString(entry) ? entry : isString(entry?.name) ? entry.name : null;
2562
2759
  if (name === null) continue;
2563
2760
  const defStr = isString(entry?.default) ? entry.default : null;
2564
- this.lookup[name] = new LookupInfo(defStr === null ? null : parseField(defStr, ctx));
2761
+ this.lookup[name] = defStr === null ? null : parseField(defStr, ctx);
2565
2762
  }
2566
2763
  for (const key in this.lookup)
2567
2764
  if (this.provide[key] !== void 0)
2568
2765
  console.warn("name declared in both provide and lookup", this.name, key);
2569
- }
2766
+ },
2570
2767
  getView(name) {
2571
2768
  return this.views[name] ?? this.views.main;
2572
- }
2573
- getEventForId(id, name = "main") {
2574
- return this.getView(name).ctx.getEventForId(id);
2575
- }
2576
- getNodeForId(id, name = "main") {
2577
- return this.getView(name).ctx.getNodeForId(id);
2578
- }
2769
+ },
2770
+ getEventForId(id, viewName) {
2771
+ return this.getView(viewName).ctx.getEventForId(id);
2772
+ },
2773
+ getNodeForId(id, viewName) {
2774
+ return this.getView(viewName).ctx.getNodeForId(id);
2775
+ },
2579
2776
  compileStyle() {
2580
2777
  const { id, commonStyle, globalStyle, views } = this;
2581
2778
  const styles = commonStyle ? [`[data-cid="${id}"]{${commonStyle}}`] : [];
@@ -2588,170 +2785,38 @@ var Component = class {
2588
2785
  }
2589
2786
  };
2590
2787
 
2591
- // src/stack.js
2592
- var STOP = /* @__PURE__ */ Symbol("STOP");
2593
- var NEXT = /* @__PURE__ */ Symbol("NEXT");
2594
- var DEFAULT_ROUTE = ["dyn", "lex"];
2595
- function routeLookup(route, lex, dyn) {
2596
- for (let i = 0; i < route.length; i++) {
2597
- const leg = route[i];
2598
- if (leg === "dyn") {
2599
- const v = dyn();
2600
- if (v != null) return v;
2601
- } else if (leg === "lex") {
2602
- const v = lex();
2603
- if (v != null) return v;
2604
- } else {
2605
- console.warn("unknown lookup route leg", leg, '- expected "dyn" or "lex"');
2606
- }
2607
- }
2788
+ // src/oo.js
2789
+ var BAD_VALUE = /* @__PURE__ */ Symbol("BadValue");
2790
+ var COERCE_NONE = (_v) => null;
2791
+ var COERCE_MAP = (v) => {
2792
+ if (v instanceof Map) return new Map(v);
2793
+ if (Array.isArray(v) || isPlainObject(v))
2794
+ return new Map(Array.isArray(v) ? v : Object.entries(v));
2608
2795
  return null;
2609
- }
2610
- function lookup(chain, name, dv = null) {
2611
- let n = chain;
2612
- while (n !== null) {
2613
- const r = n[0].lookup(name);
2614
- if (r === STOP) return dv;
2615
- if (r !== NEXT) return r;
2616
- n = n[1];
2617
- }
2618
- return dv;
2619
- }
2620
- var BindFrame = class {
2621
- constructor(it, binds, isFrame) {
2622
- this.it = it;
2623
- this.binds = binds;
2624
- this.isFrame = isFrame;
2625
- }
2626
- lookup(name) {
2627
- const v = this.binds[name];
2628
- return v === void 0 ? this.isFrame ? STOP : NEXT : v;
2629
- }
2630
2796
  };
2631
- var ObjectFrame = class {
2632
- constructor(binds) {
2633
- this.binds = binds;
2634
- }
2635
- lookup(key) {
2636
- const v = this.binds[key];
2637
- return v === void 0 ? NEXT : v;
2638
- }
2797
+ var FIELD_TYPES = {
2798
+ any: [(_v) => true, COERCE_NONE, () => null],
2799
+ text: [(v) => typeof v === "string", (v) => v?.toString?.() ?? "", () => ""],
2800
+ int: [Number.isInteger, (v) => Number.isFinite(v) ? Math.trunc(v) : null, () => 0],
2801
+ float: [Number.isFinite, COERCE_NONE, () => 0],
2802
+ bool: [(v) => typeof v === "boolean", (v) => !!v, () => false],
2803
+ list: [Array.isArray, (v) => Array.isArray(v) ? [...v] : null, () => []],
2804
+ object: [isPlainObject, (v) => isPlainObject(v) ? { ...v } : null, () => ({})],
2805
+ map: [(v) => v instanceof Map, COERCE_MAP, () => /* @__PURE__ */ new Map()],
2806
+ set: [
2807
+ (v) => v instanceof Set,
2808
+ (v) => v instanceof Set || Array.isArray(v) ? new Set(v) : null,
2809
+ () => /* @__PURE__ */ new Set()
2810
+ ]
2639
2811
  };
2640
- function computeViewsId(views) {
2641
- let s = "";
2642
- let n = views;
2643
- while (n !== null) {
2644
- s += n[0];
2645
- n = n[1];
2646
- }
2647
- return s === "main" ? "" : s;
2648
- }
2649
- var Stack = class _Stack {
2650
- constructor(comps, it, binds, dynBinds, views, viewsId, ctx = null) {
2651
- this.comps = comps;
2652
- this.it = it;
2653
- this.binds = binds;
2654
- this.dynBinds = dynBinds;
2655
- this.views = views;
2656
- this.viewsId = viewsId;
2657
- this.ctx = ctx;
2658
- }
2659
- // Evaluate every provide the entered component publishes and push them as one
2660
- // dynBinds frame, keyed by NAME. Published types go in the same frame: a type name
2661
- // starts A-Z and a value name does not, so the two namespaces cannot collide and
2662
- // nearest-ancestor-wins falls out of frame order for both. No-op with no provides.
2663
- _pushProvides() {
2664
- const comp = this.comps.getCompFor(this.it);
2665
- if (comp == null) return this;
2666
- const { provide, provideType } = comp;
2667
- const dynObj = {};
2668
- let has = false;
2669
- for (const k in provide) {
2670
- dynObj[k] = provide[k].val.eval(this);
2671
- has = true;
2672
- }
2673
- for (const k in provideType) {
2674
- dynObj[k] = provideType[k];
2675
- has = true;
2676
- }
2677
- if (!has) return this;
2678
- const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
2679
- const { comps, it, binds, views, viewsId, ctx } = this;
2680
- return new _Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
2681
- }
2682
- static root(comps, it, ctx) {
2683
- const binds = [new BindFrame(it, {}, true), null];
2684
- const dynBinds = [new ObjectFrame({}), null];
2685
- const views = ["main", null];
2686
- return new _Stack(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();
2687
- }
2688
- enter(it, bindings = {}, isFrame = true) {
2689
- const { comps, binds, dynBinds, views, viewsId, ctx } = this;
2690
- const newBinds = [new BindFrame(it, bindings, isFrame), binds];
2691
- const stack = new _Stack(comps, it, newBinds, dynBinds, views, viewsId, ctx);
2692
- return isFrame ? stack._pushProvides() : stack;
2693
- }
2694
- pushViewName(name) {
2695
- const { comps, it, binds, dynBinds, views, ctx } = this;
2696
- const newViews = [name, views];
2697
- return new _Stack(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
2698
- }
2699
- // Published types are stable per scope and would only churn the render cache, so
2700
- // the cache key covers values alone.
2701
- _pushDynBindValuesToArray(arr, comp) {
2702
- for (const k in comp.provide) arr.push(this.lookupDynamic(k));
2703
- for (const k in comp.lookup) arr.push(this.lookupDynamic(k));
2704
- }
2705
- // `*name`: the nearest binding above (including this component's own provides,
2706
- // pushed on entering it), else this component's declared default, else null.
2707
- lookupDynamic(name) {
2708
- const v = lookup(this.dynBinds, name);
2709
- if (v != null) return v;
2710
- const comp = this.comps.getCompFor(this.it);
2711
- return comp?.lookup[name]?.val?.eval(this) ?? null;
2712
- }
2713
- lookupBind(name) {
2714
- return lookup(this.binds, name);
2715
- }
2716
- lookupFieldRaw(name) {
2717
- return this.it[name] ?? null;
2718
- }
2719
- lookupMethod(name) {
2720
- const fn = this.it[name];
2721
- return fn instanceof Function ? fn.call(this.it) : null;
2722
- }
2723
- // The dispatched DOM event / drag info, read only by EventMemberVal's
2724
- // `e.<member>` handler args. Null outside a live event transaction.
2725
- lookupEvent() {
2726
- return this.ctx?.event ?? null;
2727
- }
2728
- lookupDragInfo() {
2729
- return this.ctx?.dragInfo ?? null;
2730
- }
2731
- getHandlerFor(name, key) {
2732
- return this.comps.getHandlerFor(this.it, name, key);
2733
- }
2734
- lookupBestView(views, defaultViewName) {
2735
- let n = this.views;
2736
- while (n !== null) {
2737
- const view = views[n[0]];
2738
- if (view !== void 0) return view;
2739
- n = n[1];
2740
- }
2741
- return views[defaultViewName];
2742
- }
2743
- };
2744
-
2745
- // src/oo.js
2746
- var BAD_VALUE = /* @__PURE__ */ Symbol("BadValue");
2747
- var nullCoercer = (v) => v;
2748
2812
  var Field = class {
2749
- constructor(type, name, typeCheck, coercer, defaultValue = null) {
2813
+ // `type` names an entry of FIELD_TYPES; `defaultValue` falls back to the type's own.
2814
+ constructor(type, name, defaultValue, [typeCheck, coercer, makeDefault] = FIELD_TYPES[type]) {
2750
2815
  this.type = type;
2751
2816
  this.name = name;
2752
2817
  this.typeCheck = typeCheck;
2753
2818
  this.coercer = coercer;
2754
- this.defaultValue = defaultValue;
2819
+ this.defaultValue = defaultValue === void 0 ? makeDefault() : defaultValue;
2755
2820
  }
2756
2821
  isValid(v) {
2757
2822
  return this.typeCheck(v);
@@ -2765,81 +2830,14 @@ var Field = class {
2765
2830
  return this.coerceOr(v, this.defaultValue);
2766
2831
  }
2767
2832
  };
2768
- var CHECK_TYPE_ANY = (_v) => true;
2769
- var CHECK_TYPE_INT = Number.isInteger;
2770
- var CHECK_TYPE_FLOAT = Number.isFinite;
2771
- var CHECK_TYPE_BOOL = (v) => typeof v === "boolean";
2772
- var CHECK_TYPE_STRING = (v) => typeof v === "string";
2773
- var CHECK_TYPE_LIST = Array.isArray;
2774
- var CHECK_TYPE_OBJECT = isPlainObject;
2775
- var CHECK_TYPE_MAP = (v) => v instanceof Map;
2776
- var CHECK_TYPE_SET = (v) => v instanceof Set;
2777
- var COERCE_NONE = (_v) => null;
2778
- var COERCE_BOOL = (v) => !!v;
2779
- var COERCE_STRING = (v) => v?.toString?.() ?? "";
2780
- var COERCE_INT = (v) => Number.isFinite(v) ? Math.trunc(v) : null;
2781
- var COERCE_LIST = (v) => Array.isArray(v) ? [...v] : null;
2782
- var COERCE_OBJECT = (v) => isPlainObject(v) ? { ...v } : null;
2783
- var COERCE_MAP = (v) => {
2784
- if (v instanceof Map) return new Map(v);
2785
- if (Array.isArray(v) || isPlainObject(v))
2786
- return new Map(Array.isArray(v) ? v : Object.entries(v));
2787
- return null;
2788
- };
2789
- var COERCE_SET = (v) => v instanceof Set || Array.isArray(v) ? new Set(v) : null;
2790
- var FieldBool = class extends Field {
2791
- constructor(name, defaultValue = false) {
2792
- super("bool", name, CHECK_TYPE_BOOL, COERCE_BOOL, defaultValue);
2793
- }
2794
- };
2795
- var FieldAny = class extends Field {
2796
- constructor(name, defaultValue = null) {
2797
- super("any", name, CHECK_TYPE_ANY, nullCoercer, defaultValue);
2798
- }
2799
- };
2800
- var FieldString = class extends Field {
2801
- constructor(name, defaultValue = "") {
2802
- super("text", name, CHECK_TYPE_STRING, COERCE_STRING, defaultValue);
2803
- }
2804
- };
2805
- var FieldInt = class extends Field {
2806
- constructor(name, defaultValue = 0) {
2807
- super("int", name, CHECK_TYPE_INT, COERCE_INT, defaultValue);
2808
- }
2809
- };
2810
- var FieldFloat = class extends Field {
2811
- constructor(name, defaultValue = 0) {
2812
- super("float", name, CHECK_TYPE_FLOAT, COERCE_NONE, defaultValue);
2813
- }
2814
- };
2815
- var metaOf = (v) => v?.constructor?.[COMPONENT] ?? v?.constructor?.getMetaClass?.();
2833
+ var metaOf = (v) => v?.constructor?.getMetaClass?.();
2816
2834
  var getTypeName = (v) => metaOf(v)?.name ?? null;
2817
2835
  var FieldComp = class extends Field {
2818
2836
  constructor(type, name, args) {
2819
- super(type, name, (v) => getTypeName(v) === type, nullCoercer, null);
2837
+ super(type, name, null, [(v) => getTypeName(v) === type, COERCE_NONE, () => null]);
2820
2838
  this.args = args;
2821
2839
  }
2822
2840
  };
2823
- var FieldList = class extends Field {
2824
- constructor(name, defaultValue = []) {
2825
- super("list", name, CHECK_TYPE_LIST, COERCE_LIST, defaultValue);
2826
- }
2827
- };
2828
- var FieldObject = class extends Field {
2829
- constructor(name, defaultValue = {}) {
2830
- super("object", name, CHECK_TYPE_OBJECT, COERCE_OBJECT, defaultValue);
2831
- }
2832
- };
2833
- var FieldMap = class extends Field {
2834
- constructor(name, defaultValue = /* @__PURE__ */ new Map()) {
2835
- super("map", name, CHECK_TYPE_MAP, COERCE_MAP, defaultValue);
2836
- }
2837
- };
2838
- var FieldSet = class extends Field {
2839
- constructor(name, defaultValue = /* @__PURE__ */ new Set()) {
2840
- super("set", name, CHECK_TYPE_SET, COERCE_SET, defaultValue);
2841
- }
2842
- };
2843
2841
  function mkCompField(field, scope, args) {
2844
2842
  const Comp = scope?.lookupComponent(field.type) ?? null;
2845
2843
  if (Comp === null)
@@ -2848,112 +2846,66 @@ function mkCompField(field, scope, args) {
2848
2846
  );
2849
2847
  return Comp?.make({ ...field.args, ...args }, { scope }) ?? null;
2850
2848
  }
2851
- var ClassBuilder = class {
2852
- constructor(name) {
2853
- this.name = name;
2854
- this.fields = {};
2855
- this.compFields = /* @__PURE__ */ new Set();
2856
- this._methods = {};
2857
- this._statics = {};
2858
- }
2859
- build() {
2860
- const { name, fields, compFields, _methods } = this;
2861
- const defaults = Object.fromEntries(
2862
- Object.entries(fields).map(([fieldName, field]) => [fieldName, field.defaultValue])
2863
- );
2864
- const Class = {
2865
- [name]: class {
2866
- constructor(values = {}) {
2867
- Object.assign(this, defaults, values);
2868
- }
2869
- }
2870
- }[name];
2871
- Class[immer_exports.immerable] = true;
2872
- Object.assign(Class.prototype, _methods);
2873
- const metaClass = { fields, name, methods: _methods };
2874
- Object.assign(
2875
- Class,
2876
- {
2877
- getMetaClass: () => metaClass,
2878
- make(inArgs = {}, opts = {}) {
2879
- const args = {};
2880
- const scope = opts.scope ?? this[COMPONENT]?.scope ?? this.scope;
2881
- for (const key in inArgs) {
2882
- const field = fields[key];
2883
- if (compFields.has(key)) args[key] = mkCompField(field, scope, inArgs[key]);
2884
- else if (field === void 0)
2885
- console.warn("extra argument to constructor:", name, key, inArgs);
2886
- else args[key] = field.coerceOrDefault(inArgs[key]);
2887
- }
2888
- for (const key of compFields)
2889
- if (args[key] === void 0) args[key] = mkCompField(fields[key], scope, inArgs[key]);
2890
- return (0, immer_exports.freeze)(new this(args), true);
2891
- }
2892
- },
2893
- this._statics
2894
- );
2895
- return Class;
2896
- }
2897
- methods(proto) {
2898
- for (const k in proto) this._methods[k] = proto[k];
2899
- }
2900
- statics(proto) {
2901
- for (const k in proto) this._statics[k] = proto[k];
2902
- }
2903
- addField(name, dval, FieldCls) {
2904
- const field = new FieldCls(name, dval);
2905
- this.fields[name] = field;
2906
- return field;
2907
- }
2908
- addCompField(name, type, args) {
2909
- const field = new FieldComp(type, name, args);
2910
- this.compFields.add(name);
2911
- this.fields[name] = field;
2912
- return field;
2913
- }
2914
- };
2915
- var FIELD_CLASS = /* @__PURE__ */ Symbol.for("tutuca.fieldClass");
2916
- var fieldsByTypeName = {
2917
- text: FieldString,
2918
- int: FieldInt,
2919
- float: FieldFloat,
2920
- bool: FieldBool,
2921
- list: FieldList,
2922
- object: FieldObject,
2923
- map: FieldMap,
2924
- set: FieldSet,
2925
- any: FieldAny
2926
- };
2927
- function fieldFromDescriptor(name, value) {
2928
- const FieldCls = fieldsByTypeName[value.type] ?? FieldAny;
2929
- const probe = new FieldCls(name);
2930
- return [FieldCls, probe.coerceOr(value.defaultValue, probe.defaultValue)];
2849
+ function fieldFromSpec(name, value) {
2850
+ const type = typeof value;
2851
+ if (type === "string") return new Field("text", name, value);
2852
+ if (type === "number") return new Field("float", name, value);
2853
+ if (type === "boolean") return new Field("bool", name, value);
2854
+ if (Array.isArray(value)) return new Field("list", name, [...value]);
2855
+ if (value instanceof Set) return new Field("set", name, new Set(value));
2856
+ if (value instanceof Map) return new Field("map", name, new Map(value));
2857
+ if (value?.type && Object.hasOwn(value, "defaultValue")) {
2858
+ const t = value.type in FIELD_TYPES ? value.type : "any";
2859
+ const probe = new Field(t, name);
2860
+ return new Field(t, name, probe.coerceOr(value.defaultValue, probe.defaultValue));
2861
+ }
2862
+ if (value?.component && value?.args !== void 0)
2863
+ return new FieldComp(value.component, name, value.args);
2864
+ if (isPlainObject(value)) return new Field("object", name, { ...value });
2865
+ return new Field("any", name, value);
2931
2866
  }
2932
- function classFromData(name, { fields = {}, methods, statics }) {
2933
- const b = new ClassBuilder(name);
2934
- for (const field in fields) {
2935
- const value = fields[field];
2936
- const type = typeof value;
2937
- if (type === "string") b.addField(field, value, FieldString);
2938
- else if (type === "number") b.addField(field, value, FieldFloat);
2939
- else if (type === "boolean") b.addField(field, value, FieldBool);
2940
- else if (Array.isArray(value)) b.addField(field, [...value], FieldList);
2941
- else if (value instanceof Set) b.addField(field, new Set(value), FieldSet);
2942
- else if (value instanceof Map) b.addField(field, new Map(value), FieldMap);
2943
- else if (value?.type && Object.hasOwn(value, "defaultValue")) {
2944
- const [FieldCls, dval] = fieldFromDescriptor(field, value);
2945
- b.addField(field, dval, FieldCls);
2946
- } else if (value?.component && value?.args !== void 0)
2947
- b.addCompField(field, value.component, value.args);
2948
- else if (isPlainObject(value)) b.addField(field, { ...value }, FieldObject);
2949
- else {
2950
- const FieldCls = value?.[FIELD_CLASS] ?? FieldAny;
2951
- b.addField(field, value, FieldCls);
2867
+ function classFromData(name, { fields: spec = {}, methods = {}, statics = {} }) {
2868
+ const fields = {};
2869
+ for (const key in spec) fields[key] = fieldFromSpec(key, spec[key]);
2870
+ const defaults = Object.fromEntries(
2871
+ Object.entries(fields).map(([key, field]) => [key, field.defaultValue])
2872
+ );
2873
+ const Class = {
2874
+ [name]: class {
2875
+ constructor(values = {}) {
2876
+ Object.assign(this, defaults, values);
2877
+ }
2952
2878
  }
2953
- }
2954
- if (methods) b.methods(methods);
2955
- if (statics) b.statics(statics);
2956
- return b.build();
2879
+ }[name];
2880
+ Class[immer_exports.immerable] = true;
2881
+ Object.assign(Class.prototype, methods);
2882
+ Object.assign(
2883
+ Class,
2884
+ {
2885
+ fields,
2886
+ methods,
2887
+ // The documented way for instance code to reach its class's fields
2888
+ // (`this.constructor.getMetaClass().fields.x.defaultValue`).
2889
+ getMetaClass: () => Class,
2890
+ make(inArgs = {}, opts = {}) {
2891
+ const args = {};
2892
+ const scope = opts.scope ?? this.scope;
2893
+ for (const key in inArgs) {
2894
+ const field = fields[key];
2895
+ if (field === void 0)
2896
+ console.warn("extra argument to constructor:", name, key, inArgs);
2897
+ else if (field instanceof FieldComp) args[key] = mkCompField(field, scope, inArgs[key]);
2898
+ else args[key] = field.coerceOrDefault(inArgs[key]);
2899
+ }
2900
+ for (const key in fields)
2901
+ if (fields[key] instanceof FieldComp && args[key] === void 0)
2902
+ args[key] = mkCompField(fields[key], scope, void 0);
2903
+ return (0, immer_exports.freeze)(new this(args), true);
2904
+ }
2905
+ },
2906
+ statics
2907
+ );
2908
+ return Class;
2957
2909
  }
2958
2910
  function validateDraftFields(current, draft) {
2959
2911
  const meta = metaOf(current);
@@ -2969,14 +2921,11 @@ function validateDraftFields(current, draft) {
2969
2921
  }
2970
2922
  }
2971
2923
  }
2972
- var META_KEYS = "name id fields methods views receive intent alter provide provideType lookup spec extra commonStyle globalStyle scope _rawProvide _rawLookup".split(
2973
- " "
2974
- );
2975
2924
  var RESERVED_COMPONENT_STATICS = /* @__PURE__ */ new Set([
2976
- ...META_KEYS,
2977
- "make",
2978
- "getMetaClass",
2979
- ...Object.getOwnPropertyNames(Component.prototype)
2925
+ ..."name id fields methods views receive intent alter provide provideType lookup spec extra commonStyle globalStyle scope _rawProvide _rawLookup make getMetaClass".split(
2926
+ " "
2927
+ ),
2928
+ ...Object.keys(COMPONENT_METHODS)
2980
2929
  ]);
2981
2930
  function assertNoReservedComponentStatics(statics = {}) {
2982
2931
  for (const name in statics) {
@@ -2985,32 +2934,10 @@ function assertNoReservedComponentStatics(statics = {}) {
2985
2934
  }
2986
2935
  }
2987
2936
  }
2988
- Component.fromSpec = (opts) => {
2937
+ function component(opts) {
2989
2938
  assertNoReservedComponentStatics(opts.statics);
2990
- const Class = classFromData(opts.name, opts);
2991
- const comp = new Component(Class, opts);
2992
- const metaClass = Class.getMetaClass();
2993
- comp.fields = metaClass.fields;
2994
- comp.methods = metaClass.methods;
2995
- Class.getMetaClass = () => comp;
2996
- Class[COMPONENT] = comp;
2997
- for (const key of META_KEYS)
2998
- if (!Object.hasOwn(Class, key))
2999
- Object.defineProperty(Class, key, {
3000
- get() {
3001
- return comp[key];
3002
- },
3003
- set(v) {
3004
- comp[key] = v;
3005
- },
3006
- configurable: true
3007
- });
3008
- for (const key of Object.getOwnPropertyNames(Component.prototype))
3009
- if (key !== "constructor" && !Object.hasOwn(Class, key))
3010
- Class[key] = (...args) => comp[key](...args);
3011
- return Class;
3012
- };
3013
- var component = (opts) => Component.fromSpec(opts);
2939
+ return initComponent(classFromData(opts.name ?? "UnkComp", opts), opts);
2940
+ }
3014
2941
 
3015
2942
  // src/transactor.js
3016
2943
  var State = class {
@@ -3051,14 +2978,7 @@ var Transactor = class {
3051
2978
  // carry: kind, name, args, path, pathKeys, targetPath, handler, handlerName,
3052
2979
  // matched, before, after, parent, timestamp. Purely observational.
3053
2980
  observe(cb) {
3054
- this._observers.push(cb);
3055
- return () => {
3056
- const i = this._observers.indexOf(cb);
3057
- if (i !== -1) this._observers.splice(i, 1);
3058
- };
3059
- }
3060
- _emit(record) {
3061
- for (const cb of this._observers) cb(record);
2981
+ return subscribe(this._observers, cb);
3062
2982
  }
3063
2983
  // Record a refusal: a dispatch the runtime could not carry out. Kinds are
3064
2984
  // open strings; today the runtime raises NO_HANDLER (a receive name with no
@@ -3077,20 +2997,16 @@ var Transactor = class {
3077
2997
  // Subscribe to refusal records as they happen. Returns an unsubscribe fn,
3078
2998
  // like observe().
3079
2999
  observeRefusals(cb) {
3080
- this._refusalObservers.push(cb);
3081
- return () => {
3082
- const i = this._refusalObservers.indexOf(cb);
3083
- if (i !== -1) this._refusalObservers.splice(i, 1);
3084
- };
3000
+ return subscribe(this._refusalObservers, cb);
3085
3001
  }
3086
3002
  // Build and dispatch one observer record. Pins field-resolved keys (e.g. a
3087
3003
  // `.a[.selId]` render target reconstructed from a DOM event) against the root the
3088
3004
  // handler read, so pathKeys carries concrete keys instead of dropping the dynamic
3089
3005
  // step. No-op when nobody is observing.
3090
- _emitRecord(root, { kind, name, args, path, targetPath, handler, handlerName, matched, before, after, parent }) {
3006
+ _emitRecord(root, { kind, name, args, path: path2, targetPath, handler, handlerName, matched, before, after, parent }) {
3091
3007
  if (this._observers.length === 0) return;
3092
- const pinned = path.pinKeys(root);
3093
- this._emit({
3008
+ const pinned = path2.pinKeys(root);
3009
+ const record = {
3094
3010
  kind,
3095
3011
  name,
3096
3012
  args: args ?? null,
@@ -3104,14 +3020,14 @@ var Transactor = class {
3104
3020
  after,
3105
3021
  parent,
3106
3022
  timestamp: Date.now()
3107
- });
3023
+ };
3024
+ for (const cb of this._observers) cb(record);
3108
3025
  }
3109
3026
  // The observer record for a settled transaction. The resolved handler
3110
3027
  // (`_resolvedHandler`/`_matched`) and per-leaf before/after (`_before`/`_after`)
3111
3028
  // were captured while the handler ran (see callHandler / Transaction.run).
3112
3029
  _emitTransaction(transaction, root) {
3113
3030
  if (this._observers.length === 0) return;
3114
- if (transaction._resolvedHandler === void 0) return;
3115
3031
  this._emitRecord(root, {
3116
3032
  kind: transaction.observeKind,
3117
3033
  name: transaction.observeName,
@@ -3125,33 +3041,29 @@ var Transactor = class {
3125
3041
  parent: transaction.parentTransaction
3126
3042
  });
3127
3043
  }
3128
- // Make `child` a tracked unit of `parent`'s subtree: the parent's completion stays open
3129
- // until the child's *whole* subtree settles. Tracking happens at dispatch time — during
3130
- // the parent's handler or afterTransaction — while the parent's self-unit is still held,
3131
- // so the parent counter can't reach zero before the child is registered. Returns `child`.
3132
- _link(child, parent) {
3133
- if (parent) {
3134
- const release = parent.completion.track();
3135
- child.completion.whenSubtreeSettled().then(release);
3136
- }
3137
- return child;
3138
- }
3139
3044
  // `origin` is where the message came FROM, pinned now for the same reason an
3140
3045
  // intent's answerPath is: a reply must reach the sender that asked even if a key
3141
3046
  // moved while the message was in flight. Null when nobody is waiting — a host
3142
3047
  // sendAtRoot or a view's own `@on.*` — and ctx.sendReply refuses on that.
3143
- pushSend(path, name, args = [], parent = null, origin = null) {
3144
- const t = new SendEvent(path, this, name, args, parent);
3145
- t.origin = origin === null ? null : origin.toTransactionPath().pinKeys(this.state.val);
3048
+ //
3049
+ // The parent's subtree stays open until this message's whole subtree settles. The
3050
+ // unit is taken now, at dispatch — during the parent's handler or afterTransaction,
3051
+ // while the parent's self-unit is still held — so its counter can't reach zero first.
3052
+ pushSend(path2, name, args = [], parent = null, origin = null, txnPath = null) {
3053
+ const t = new SendEvent(path2, this, name, args, parent);
3054
+ t.txnPath = txnPath;
3055
+ t.origin = origin;
3056
+ t.originPinned = origin === null ? null : origin.toTransactionPath().pinKeys(this.state.val);
3146
3057
  this.pushTransaction(t);
3147
- return this._link(t, parent);
3058
+ if (parent) t.completion.carry(parent.completion.track());
3059
+ return t;
3148
3060
  }
3149
3061
  // Raise an intent: a job the sender did not address, walked along a route until
3150
3062
  // something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
3151
3063
  // `opts.livePath` opts out of pinning the answer's destination keys.
3152
- pushIntent(path, name, args = [], opts = {}, parent = null) {
3064
+ pushIntent(path2, name, args = [], opts = {}, parent = null) {
3153
3065
  const release = parent ? parent.completion.track() : null;
3154
- const walk = new IntentWalk(this, path, name, args, opts, parent, release);
3066
+ const walk = new IntentWalk(this, path2, name, args, opts, parent, release);
3155
3067
  walk.advance();
3156
3068
  return walk;
3157
3069
  }
@@ -3168,7 +3080,7 @@ var Transactor = class {
3168
3080
  return this.transactions.length > 0;
3169
3081
  }
3170
3082
  transactNext() {
3171
- if (this.hasPendingTransactions) this.transact(this.transactions.shift());
3083
+ this.transact(this.transactions.shift());
3172
3084
  }
3173
3085
  transact(transaction) {
3174
3086
  try {
@@ -3181,20 +3093,31 @@ var Transactor = class {
3181
3093
  } else console.warn("undefined new state", { curState, transaction });
3182
3094
  } finally {
3183
3095
  transaction.ensureWalkAdvanced?.();
3184
- transaction._completion?.ensureSelfSettled();
3185
- transaction._completion?.releaseSelf();
3096
+ transaction._completion?.finish();
3186
3097
  }
3187
3098
  }
3188
- transactInputNow(path, event, eventHandler, dragInfo) {
3189
- this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
3099
+ transactInputNow(path2, event, eventHandler, dragInfo) {
3100
+ this.transact(new InputEvent(path2, event, eventHandler, this, dragInfo));
3190
3101
  }
3191
3102
  };
3192
3103
  function nullHandler() {
3193
3104
  return this;
3194
3105
  }
3106
+ function subscribe(list, cb) {
3107
+ list.push(cb);
3108
+ return () => {
3109
+ const i = list.indexOf(cb);
3110
+ if (i !== -1) list.splice(i, 1);
3111
+ };
3112
+ }
3113
+ function warnUnknownLeg(what, leg) {
3114
+ console.warn(`unknown ${what} route leg`, leg, '- expected "dyn" or "lex"');
3115
+ return null;
3116
+ }
3195
3117
  var Transaction = class {
3196
- constructor(path, transactor, parentTransaction = null) {
3197
- this.path = path;
3118
+ constructor(path2, transactor, parentTransaction = null) {
3119
+ this.path = path2;
3120
+ this.txnPath = null;
3198
3121
  this.transactor = transactor;
3199
3122
  this.parentTransaction = parentTransaction;
3200
3123
  this._completion = null;
@@ -3214,39 +3137,41 @@ var Transaction = class {
3214
3137
  whenSubtreeSettled() {
3215
3138
  return this.completion.whenSubtreeSettled();
3216
3139
  }
3217
- afterTransaction() {
3218
- }
3219
- // Ending a walk and forwarding belong to the two dispatch buckets; the base warns
3220
- // rather than throwing, so a stray call from the wrong place is a message and not a
3221
- // crash. `walk` is undefined here, which is how ctx.reply/ctx.fail tell the two apart.
3222
- stop() {
3223
- warnNotIntent("stop");
3224
- }
3225
- forward(_opts) {
3226
- console.warn('ctx.forward() needs a "receive" or "intent" handler - ignored');
3227
- }
3228
- // The kind reported to observers (see Transactor.observe); null on the base.
3229
- get observeKind() {
3230
- return null;
3231
- }
3232
- // The name reported to observers; null on the base. Overridden by NameArgs (the
3233
- // dispatched message name) and InputEvent (the DOM event type). Kept separate from
3234
- // `name`/`ctx.name` so it can't change handler-visible behavior.
3235
- get observeName() {
3236
- return null;
3237
- }
3140
+ // Every transaction kind defines `observeKind` / `observeName` (what observers see;
3141
+ // kept apart from `name`/`ctx.name` so they can't change handler-visible behavior),
3142
+ // `getHandlerAndArgs(root, instance, comps)`, and the three `_forward*` hooks below.
3238
3143
  callHandler(root, instance, draft, comps) {
3239
3144
  const [handler, args] = this.getHandlerAndArgs(root, instance, comps);
3240
3145
  this._resolvedHandler = handler;
3241
3146
  return handler.apply(instance, [draft, ...args]);
3242
3147
  }
3243
- getHandlerAndArgs(_root, _instance, _comps) {
3244
- return null;
3148
+ // `forward` is where a name can LEAVE the component: the message being handled is
3149
+ // raised again as an INTENT under the same name (`_forwardName()`) with the same
3150
+ // arguments unless `opts.args` says otherwise, from the position `_forwardPath()`
3151
+ // names. Deferred to afterTransaction so the state the handler produced is in place
3152
+ // before the walk starts. (`IntentEvent` overrides both: in an intent body, forward
3153
+ // amends the walk that is already running.)
3154
+ forward(opts) {
3155
+ this._forward = opts ?? {};
3245
3156
  }
3246
- // The path used to apply the mutation. Teleports dynamic-var renders so it lands on
3247
- // the data's real location (the dispatch `this.path` keeps intermediates).
3157
+ afterTransaction() {
3158
+ const f = this._forward;
3159
+ if (f === void 0) return;
3160
+ this._forward = void 0;
3161
+ const name = this._forwardName();
3162
+ if (name === void 0) {
3163
+ this.transactor.refuse("FORWARD_NO_NAME", {});
3164
+ return;
3165
+ }
3166
+ const { args = this._forwardArgs(), ...rest } = f;
3167
+ this.transactor.pushIntent(this._forwardPath(), name, args, rest, this);
3168
+ }
3169
+ // The path used to apply the mutation: the ACTIVE frame of the dispatch path, so
3170
+ // an event inside a `<x render="*name">` subtree updates the value where it
3171
+ // really lives (the dispatch `this.path` keeps the visual callers, for bubbling).
3172
+ // `txnPath`, when pinned by the sender, wins.
3248
3173
  getTransactionPath() {
3249
- return this.path.toTransactionPath().compact();
3174
+ return this.txnPath ?? this.path.toTransactionPath();
3250
3175
  }
3251
3176
  run(curRoot, comps) {
3252
3177
  const txnPath = this.getTransactionPath();
@@ -3263,19 +3188,27 @@ var Transaction = class {
3263
3188
  }
3264
3189
  };
3265
3190
  var InputEvent = class extends Transaction {
3266
- constructor(path, e, handler, transactor, dragInfo) {
3267
- super(path, transactor);
3191
+ constructor(path2, e, handler, transactor, dragInfo) {
3192
+ super(path2, transactor);
3268
3193
  this.e = e;
3269
3194
  this.handler = handler;
3270
3195
  this.dragInfo = dragInfo;
3271
3196
  this._dispatchPath = null;
3272
3197
  }
3273
- // Frame steps removed, DynStep + one step per crossed component kept: bubbling
3274
- // it visits every component (including intermediates of a dynamic-var render).
3198
+ // Frame-only steps removed, one step per crossed component kept — inside every
3199
+ // continuation frame independently, so bubbling it visits every component and
3200
+ // then returns to the caller that wrote the `*name`.
3275
3201
  get dispatchPath() {
3276
3202
  this._dispatchPath ??= this.path.compact();
3277
3203
  return this._dispatchPath;
3278
3204
  }
3205
+ // Only a DOM event's path carries frame-only bind steps (they replay the handler's
3206
+ // arguments, but address no state); the compacted path is where the mutation lands,
3207
+ // so a handler inside @each still updates the component that owns the view. Every
3208
+ // other transaction is dispatched at an already-compacted position.
3209
+ getTransactionPath() {
3210
+ return this.dispatchPath.toTransactionPath();
3211
+ }
3279
3212
  // A DOM event is an ADDRESSED message like any other — it reaches the component that
3280
3213
  // owns the view and stops. It keeps its own class only because it resolves its handler
3281
3214
  // from the compiled view and runs synchronously, not because it is a different channel.
@@ -3285,47 +3218,36 @@ var InputEvent = class extends Transaction {
3285
3218
  get observeName() {
3286
3219
  return this.e?.type ?? null;
3287
3220
  }
3288
- // A view's name is a message. `forward` is where that name can LEAVE the component:
3289
- // it becomes an intent with the same name and the same arguments, so a view that says
3290
- // `@on.click="saveDraft .text"` never has to change when an ancestor takes the job over.
3291
- // (A `$method` handler has a name too, so forwarding one raises an intent under it.)
3292
- forward(opts) {
3293
- this._forward = opts ?? {};
3294
- }
3295
- afterTransaction() {
3296
- const f = this._forward;
3297
- if (f === void 0) return;
3298
- this._forward = void 0;
3299
- const { args = this._handlerArgs ?? [], ...rest } = f;
3221
+ // A view's name is a message, and forwarding it means a view that says
3222
+ // `@on.click="saveDraft .text"` never has to change when an ancestor takes the job
3223
+ // over. (A `$method` handler has a name too, so forwarding one raises an intent under
3224
+ // it.) `handler` is a NodeEvent, whose own `name` is the DOM event type; the MESSAGE
3225
+ // name is the one the view wrote, on the handler call it wraps.
3226
+ _forwardName() {
3300
3227
  const hv = this.handler?.handlerCall?.handlerVal ?? this.handler?.handlerVal;
3301
- const name = hv?.name;
3302
- if (name === void 0) {
3303
- this.transactor.refuse("FORWARD_NO_NAME", {});
3304
- return;
3305
- }
3306
- this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
3228
+ return hv?.name;
3229
+ }
3230
+ _forwardArgs() {
3231
+ return this._handlerArgs ?? [];
3232
+ }
3233
+ _forwardPath() {
3234
+ return this.dispatchPath;
3307
3235
  }
3308
3236
  getHandlerAndArgs(root, _instance, comps) {
3309
- const stack = this.path.toTransactionPath().buildStack(Stack.root(comps, root, this));
3237
+ const stack = this.path.buildStack(Stack.root(comps, root, this));
3310
3238
  const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
3311
3239
  this._handlerArgs = [...args];
3312
- const path = this.dispatchPath;
3313
- args.push(new EventContext(path, this.transactor, this));
3240
+ const path2 = this.dispatchPath;
3241
+ args.push(new EventContext(path2, this.transactor, this));
3314
3242
  return [handler, args];
3315
3243
  }
3316
- // The dispatched DOM event, read only through `e.<member>` handler args
3317
- // (src/value.js EventMemberVal via Stack.lookupEvent). There is no bare
3318
- // implicit-name vocabulary anymore: every arg carries a sigil.
3319
- get event() {
3320
- return this.e;
3321
- }
3322
3244
  };
3323
3245
  var NameArgsTransaction = class extends Transaction {
3324
- constructor(path, transactor, name, args, parentTransaction) {
3325
- super(path, transactor, parentTransaction);
3246
+ constructor(path2, transactor, name, args, parentTransaction) {
3247
+ super(path2, transactor, parentTransaction);
3326
3248
  this.name = name;
3327
3249
  this.args = args;
3328
- this.targetPath = path;
3250
+ this.targetPath = path2;
3329
3251
  }
3330
3252
  handlerProp = null;
3331
3253
  // NameArgs verbs map their handler bucket straight to the observed kind:
@@ -3336,6 +3258,17 @@ var NameArgsTransaction = class extends Transaction {
3336
3258
  get observeName() {
3337
3259
  return this.name;
3338
3260
  }
3261
+ // `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
3262
+ // intent, keeping its name and payload.
3263
+ _forwardName() {
3264
+ return this.name;
3265
+ }
3266
+ _forwardArgs() {
3267
+ return this.args;
3268
+ }
3269
+ _forwardPath() {
3270
+ return this.path;
3271
+ }
3339
3272
  getHandlerForName(comp) {
3340
3273
  const handlers = comp?.[this.handlerProp];
3341
3274
  const exact = handlers?.[this.name];
@@ -3366,24 +3299,11 @@ var SendEvent = class extends NameArgsTransaction {
3366
3299
  get observeKind() {
3367
3300
  return this._isAnswer ? "answer" : "receive";
3368
3301
  }
3369
- // `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
3370
- // intent, keeping its name and payload. This is what lets a view's name leave the
3371
- // component without the view changing.
3372
- forward(opts) {
3373
- this._forward = opts ?? {};
3374
- }
3375
- afterTransaction() {
3376
- const f = this._forward;
3377
- if (f === void 0) return;
3378
- this._forward = void 0;
3379
- const { args = this.args, ...rest } = f;
3380
- this.transactor.pushIntent(this.path, this.name, args, rest, this);
3381
- }
3382
3302
  };
3383
3303
  var IntentEvent = class extends NameArgsTransaction {
3384
3304
  handlerProp = "intent";
3385
- constructor(path, transactor, walk) {
3386
- super(path, transactor, walk.name, walk.args, walk.parent);
3305
+ constructor(path2, transactor, walk) {
3306
+ super(path2, transactor, walk.name, walk.args, walk.parent);
3387
3307
  this.walk = walk;
3388
3308
  this.targetPath = walk.origin;
3389
3309
  }
@@ -3392,9 +3312,6 @@ var IntentEvent = class extends NameArgsTransaction {
3392
3312
  forward(opts) {
3393
3313
  this.walk.amend(opts, this.path);
3394
3314
  }
3395
- stop() {
3396
- this.walk.finish(null, null);
3397
- }
3398
3315
  afterTransaction() {
3399
3316
  this.ensureWalkAdvanced();
3400
3317
  }
@@ -3410,17 +3327,17 @@ var PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
3410
3327
  var REFUSAL_RING_CAP = 200;
3411
3328
  var INTENT_DEPTH = 64;
3412
3329
  var IntentWalk = class {
3413
- constructor(transactor, path, name, args, opts, parent, release) {
3330
+ constructor(transactor, path2, name, args, opts, parent, release) {
3414
3331
  this.transactor = transactor;
3415
3332
  this.name = name;
3416
3333
  this.args = args;
3417
3334
  this.route = opts?.route ?? DEFAULT_ROUTE;
3418
3335
  this.parent = parent;
3419
3336
  this.release = release;
3420
- this.origin = path;
3421
- this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
3337
+ this.origin = path2;
3338
+ this.answerPath = opts?.livePath ? null : path2.toTransactionPath().pinKeys(transactor.state.val);
3422
3339
  this.legIndex = 0;
3423
- this.dynAt = path;
3340
+ this.dynAt = path2;
3424
3341
  this.hops = 0;
3425
3342
  this.ended = false;
3426
3343
  }
@@ -3432,7 +3349,7 @@ var IntentWalk = class {
3432
3349
  while (this.legIndex < this.route.length) {
3433
3350
  const leg = this.route[this.legIndex];
3434
3351
  if (leg === "dyn") {
3435
- if (this.dynAt.steps.length === 0) {
3352
+ if (!this.dynAt.canPop()) {
3436
3353
  this.legIndex++;
3437
3354
  continue;
3438
3355
  }
@@ -3444,7 +3361,7 @@ var IntentWalk = class {
3444
3361
  this.legIndex++;
3445
3362
  return this._tryLex();
3446
3363
  }
3447
- console.warn("unknown intent route leg", leg, '- expected "dyn" or "lex"');
3364
+ warnUnknownLeg("intent", leg);
3448
3365
  this.legIndex++;
3449
3366
  }
3450
3367
  this.exhaust("noHandler");
@@ -3529,11 +3446,11 @@ var IntentWalk = class {
3529
3446
  if (this.ended) return;
3530
3447
  this.ended = true;
3531
3448
  if (name === null) return this.release?.();
3532
- const path = this.answerPath ?? this.origin;
3533
- const t = new SendEvent(path, this.transactor, name, args, this.parent);
3449
+ const t = new SendEvent(this.origin, this.transactor, name, args, this.parent);
3450
+ t.txnPath = this.answerPath;
3534
3451
  t._isAnswer = true;
3535
3452
  this.transactor.pushTransaction(t);
3536
- if (this.release) t.completion.whenSubtreeSettled().then(this.release);
3453
+ if (this.release) t.completion.carry(this.release);
3537
3454
  }
3538
3455
  };
3539
3456
  var Completion = class {
@@ -3541,12 +3458,12 @@ var Completion = class {
3541
3458
  this.val = void 0;
3542
3459
  this.selfSettled = false;
3543
3460
  this.subtreeSettled = false;
3544
- this.pending = 1;
3461
+ this.pending = 0;
3545
3462
  this._selfResolve = null;
3546
3463
  this._selfPromise = null;
3547
3464
  this._subtreeResolve = null;
3548
3465
  this._subtreePromise = null;
3549
- this._selfReleased = false;
3466
+ this._releaseSelf = this.track();
3550
3467
  }
3551
3468
  whenSettled() {
3552
3469
  if (this.selfSettled) return Promise.resolve(this.val);
@@ -3569,38 +3486,38 @@ var Completion = class {
3569
3486
  this.val = val;
3570
3487
  this._selfResolve?.(val);
3571
3488
  }
3572
- // Settle self even when no handler produced a value (undefined-state / throw paths).
3573
- ensureSelfSettled() {
3574
- if (!this.selfSettled) this.markSelfSettled(this.val);
3489
+ // The transaction is done processing: self is settled (with the value the handler
3490
+ // produced, or none on the undefined-state / throw paths) and the self-unit goes.
3491
+ finish() {
3492
+ this.markSelfSettled(this.val);
3493
+ this._releaseSelf();
3575
3494
  }
3576
- // Register an outstanding unit; returns a one-shot release.
3495
+ // Register an outstanding unit; returns its one-shot release. The subtree settles
3496
+ // when the last unit is released.
3577
3497
  track() {
3578
3498
  this.pending++;
3579
3499
  let done = false;
3580
3500
  return () => {
3581
3501
  if (done) return;
3582
3502
  done = true;
3583
- this._release();
3503
+ if (--this.pending === 0) {
3504
+ this.subtreeSettled = true;
3505
+ this._subtreeResolve?.(this.val);
3506
+ }
3584
3507
  };
3585
3508
  }
3586
- releaseSelf() {
3587
- if (this._selfReleased) return;
3588
- this._selfReleased = true;
3589
- this._release();
3590
- }
3591
- _release() {
3592
- if (--this.pending === 0) {
3593
- this.subtreeSettled = true;
3594
- this._subtreeResolve?.(this.val);
3595
- }
3509
+ // Hand a unit held elsewhere to this subtree: `release` runs once this transaction
3510
+ // and everything it spawned have settled.
3511
+ carry(release) {
3512
+ this.whenSubtreeSettled().then(release);
3596
3513
  }
3597
3514
  };
3598
3515
  var Dispatcher = class {
3599
- constructor(path, transactor, parentTransaction, root = transactor.state.val) {
3600
- this.path = path;
3516
+ constructor(path2, transactor, parentTransaction) {
3517
+ this.path = path2;
3601
3518
  this.transactor = transactor;
3602
3519
  this.parent = parentTransaction;
3603
- this.root = root;
3520
+ this.root = transactor.state.val;
3604
3521
  }
3605
3522
  // Walk the component instances on this ctx's path, leaf→root, calling
3606
3523
  // callback(Component, instance). Return false from the callback to stop early.
@@ -3625,28 +3542,37 @@ var Dispatcher = class {
3625
3542
  // replayed; provides are pushed on every component frame either way, so a provide
3626
3543
  // that reads a loop binding is the one case this cannot reproduce faithfully.
3627
3544
  _stack() {
3628
- this._stackMemo ??= this.path.toTransactionPath().buildStack(Stack.root(this.transactor.comps, this.root, this.parent));
3545
+ this._stackMemo ??= this.path.buildStack(
3546
+ Stack.root(this.transactor.comps, this.root, this.parent)
3547
+ );
3629
3548
  return this._stackMemo;
3630
3549
  }
3631
3550
  // Resolve a name the way the renderer would. `opts.route` takes the same legs in
3632
3551
  // the same spelling as `ctx.intent` — `["dyn"]` the render ancestry, `["lex"]` the
3633
3552
  // registration scope, default both.
3553
+ // Legs are tried in route order, like the intent walker's; an unknown leg warns and
3554
+ // is skipped, and an empty route resolves to null.
3634
3555
  lookup(name, opts) {
3635
- return routeLookup(
3636
- opts?.route ?? DEFAULT_ROUTE,
3637
- () => this._lookupLex(name),
3638
- () => this._stack()?.lookupDynamic(name) ?? null
3639
- );
3556
+ for (const leg of opts?.route ?? DEFAULT_ROUTE) {
3557
+ const v = leg === "dyn" ? this._stack()?.lookupDynamic(name) ?? null : leg === "lex" ? this._lookupLex(name) : warnUnknownLeg("lookup", leg);
3558
+ if (v != null) return v;
3559
+ }
3560
+ return null;
3640
3561
  }
3641
3562
  // The `lex` leg without a Stack: the scope of the component whose handler is
3642
- // running, which is the leaf of this ctx's path.
3563
+ // running, which is the leaf of this ctx's path. A type name resolves to the
3564
+ // component registered under it; a value name to a path registered under it
3565
+ // (see ComponentStack.registerPaths), read against the current root.
3643
3566
  _lookupLex(name) {
3644
3567
  let Comp = null;
3645
3568
  this.walkPath((c) => {
3646
3569
  Comp = c;
3647
3570
  return false;
3648
3571
  });
3649
- return Comp?.scope?.lookupComponent(name) ?? null;
3572
+ const scope = Comp?.scope;
3573
+ if (scope == null) return null;
3574
+ if (isTypeName(name)) return scope.lookupComponent(name) ?? null;
3575
+ return scope.lookupPath(name)?.lookup(this.root) ?? null;
3650
3576
  }
3651
3577
  // A component lookup is a value lookup constrained to a component. The `lex` leg
3652
3578
  // can only ever answer with one; the `dyn` leg reads a binding an ancestor
@@ -3668,8 +3594,8 @@ var Dispatcher = class {
3668
3594
  send(name, args) {
3669
3595
  return this.sendAtPath(this.path, name, args);
3670
3596
  }
3671
- sendAtPath(path, name, args) {
3672
- return this.transactor.pushSend(path, name, args, this.parent, this.path);
3597
+ sendAtPath(path2, name, args) {
3598
+ return this.transactor.pushSend(path2, name, args, this.parent, this.path);
3673
3599
  }
3674
3600
  // An intent is ROUTED: it names a job and walks until something answers. The verb
3675
3601
  // does not decide which scope answers — `opts.route` does, written here at the call
@@ -3677,8 +3603,8 @@ var Dispatcher = class {
3677
3603
  intent(name, args, opts) {
3678
3604
  return this.intentAtPath(this.path, name, args, opts);
3679
3605
  }
3680
- intentAtPath(path, name, args, opts) {
3681
- return this.transactor.pushIntent(path, name, args, opts, this.parent);
3606
+ intentAtPath(path2, name, args, opts) {
3607
+ return this.transactor.pushIntent(path2, name, args, opts, this.parent);
3682
3608
  }
3683
3609
  };
3684
3610
  var EventContext = class extends Dispatcher {
@@ -3707,11 +3633,19 @@ var EventContext = class extends Dispatcher {
3707
3633
  this.transactor.refuse("NO_SENDER", { name });
3708
3634
  return null;
3709
3635
  }
3710
- return this.sendAtPath(origin, name, args);
3636
+ return this.transactor.pushSend(
3637
+ origin,
3638
+ name,
3639
+ args,
3640
+ this.parent,
3641
+ this.path,
3642
+ this.parent.originPinned
3643
+ );
3711
3644
  }
3712
- // End the walk answering nothing — "served, and no answer".
3645
+ // End the walk answering nothing — "served, and no answer". Like reply/fail,
3646
+ // legal only in an `intent` handler.
3713
3647
  stop() {
3714
- return this.parent.stop();
3648
+ return this.parent.walk === void 0 ? warnNotIntent("stop") : this.parent.walk.finish(null, null);
3715
3649
  }
3716
3650
  // From an `intent` body: hand the intent to the next hop, optionally amending it.
3717
3651
  // From a `receive` body: turn the message that arrived into an intent. One word from
@@ -3739,7 +3673,7 @@ var PathChanges = class extends PathBuilder {
3739
3673
  }
3740
3674
  };
3741
3675
  function rootDispatcher(transactor) {
3742
- return new Dispatcher(new Path([]), transactor, null);
3676
+ return new Dispatcher(new DispatchPath(), transactor, null);
3743
3677
  }
3744
3678
 
3745
3679
  // src/app.js
@@ -3752,7 +3686,6 @@ var App = class {
3752
3686
  this.transactor = new Transactor(comps, null);
3753
3687
  this.ParseContext = ParseContext2;
3754
3688
  this.renderer = renderer;
3755
- this.maxEventNodeDepth = Infinity;
3756
3689
  this._transactNextBatchId = this._evictCacheId = null;
3757
3690
  this._eventNames = new Set(_evs);
3758
3691
  this.dragInfo = this.curDragOver = null;
@@ -3779,18 +3712,17 @@ var App = class {
3779
3712
  _dispatchEvent(e) {
3780
3713
  const { type } = e;
3781
3714
  const isDrag = type === "dragover" || type === "dragstart" || type === "dragend" || type === "drop";
3782
- const { rootNode: root, maxEventNodeDepth: maxDepth, comps, transactor } = this;
3783
- const [path, handlers] = Path.fromNodeAndEventName(
3715
+ const { rootNode, comps, transactor } = this;
3716
+ const [path2, handlers] = DispatchPath.fromNodeAndEventName(
3784
3717
  e.target,
3785
3718
  type,
3786
- root,
3787
- maxDepth,
3719
+ rootNode,
3788
3720
  comps,
3789
3721
  !isDrag
3790
3722
  );
3791
- if (isDrag) this._handleDragEvent(e, type, path);
3792
- if (path !== null && handlers !== null)
3793
- for (const handler of handlers) transactor.transactInputNow(path, e, handler, this.dragInfo);
3723
+ if (isDrag) this._handleDragEvent(e, type, path2);
3724
+ if (path2 !== null && handlers !== null)
3725
+ for (const handler of handlers) transactor.transactInputNow(path2, e, handler, this.dragInfo);
3794
3726
  }
3795
3727
  _handleTouchEvent(e) {
3796
3728
  const { type } = e;
@@ -3833,7 +3765,7 @@ var App = class {
3833
3765
  this._touch = null;
3834
3766
  }
3835
3767
  }
3836
- _handleDragEvent(e, type, path) {
3768
+ _handleDragEvent(e, type, path2) {
3837
3769
  if (type === "dragover") {
3838
3770
  const dropTarget = getClosestDropTarget(e.target, this.rootNode, Infinity);
3839
3771
  if (dropTarget !== null) {
@@ -3845,10 +3777,10 @@ var App = class {
3845
3777
  } else if (type === "dragstart") {
3846
3778
  e.target.dataset.dragging = 1;
3847
3779
  const rootValue = this.state.val;
3848
- const txnPath = path.compact().toTransactionPath();
3780
+ const txnPath = path2.compact().toTransactionPath();
3849
3781
  const value = txnPath.lookup(rootValue);
3850
3782
  const dragType = e.target.dataset.dragtype ?? "?";
3851
- const stack = path.toTransactionPath().buildStack(this.makeStack(rootValue));
3783
+ const stack = path2.buildStack(this.makeStack(rootValue));
3852
3784
  this.dragInfo = new DragInfo(stack, value, dragType, e.target);
3853
3785
  } else if (type === "drop") {
3854
3786
  e.preventDefault();
@@ -3893,10 +3825,9 @@ var App = class {
3893
3825
  }
3894
3826
  compile() {
3895
3827
  for (const Comp of this.comps.byId.values()) {
3896
- const meta = Comp[COMPONENT];
3897
- meta.compile(this.ParseContext);
3898
- for (const key in meta.views)
3899
- for (const name of meta.views[key].ctx.genEventNames()) this._eventNames.add(name);
3828
+ Comp.compile(this.ParseContext);
3829
+ for (const key in Comp.views)
3830
+ for (const name of Comp.views[key].ctx.genEventNames()) this._eventNames.add(name);
3900
3831
  }
3901
3832
  this._compiled = true;
3902
3833
  }
@@ -3919,11 +3850,13 @@ var App = class {
3919
3850
  }
3920
3851
  stop() {
3921
3852
  this.stopCacheEvictionInterval();
3853
+ clearTimeout(this._transactNextBatchId);
3854
+ this._transactNextBatchId = null;
3922
3855
  for (const name of this._eventNames)
3923
3856
  this.rootNode.removeEventListener(name, this, listenerOpts(name));
3924
3857
  }
3925
3858
  sendAtRoot(name, args) {
3926
- this.transactor.pushSend(new Path([]), name, args);
3859
+ this.transactor.pushSend(new DispatchPath(), name, args);
3927
3860
  }
3928
3861
  registerComponents(comps, opts) {
3929
3862
  const scope = this.compStack.enter();
@@ -4005,28 +3938,21 @@ var NullDomCache = class {
4005
3938
  set(_keys, _cacheKey, _v) {
4006
3939
  }
4007
3940
  evict() {
4008
- return { hit: 0, miss: 0, badKey: 0 };
4009
3941
  }
4010
3942
  };
4011
3943
  var WeakMapDomCache = class {
4012
3944
  constructor() {
4013
- this.hit = this.miss = this.badKey = 0;
4014
3945
  this.keysByLen = /* @__PURE__ */ new Map();
4015
3946
  }
4016
- _returnValue(r) {
4017
- if (r === void 0) this.miss += 1;
4018
- else this.hit += 1;
4019
- return r;
4020
- }
4021
3947
  get(keys, cacheKey) {
4022
3948
  const len = keys.length;
4023
3949
  let cur = this.keysByLen.get(len);
4024
- if (!cur) return this._returnValue(void 0);
3950
+ if (!cur) return void 0;
4025
3951
  for (let i = 0; i < len - 1; i++) {
4026
3952
  cur = cur.get(keys[i]);
4027
- if (!cur) return this._returnValue(void 0);
3953
+ if (!cur) return void 0;
4028
3954
  }
4029
- return this._returnValue(cur.get(keys[len - 1])?.[cacheKey]);
3955
+ return cur.get(keys[len - 1])?.[cacheKey];
4030
3956
  }
4031
3957
  set(keys, cacheKey, v) {
4032
3958
  const len = keys.length;
@@ -4039,10 +3965,7 @@ var WeakMapDomCache = class {
4039
3965
  const key = keys[i];
4040
3966
  let next = cur.get(key);
4041
3967
  if (!next) {
4042
- if (!isWeakKey(key)) {
4043
- this.badKey += 1;
4044
- return;
4045
- }
3968
+ if (!isWeakKey(key)) return;
4046
3969
  next = /* @__PURE__ */ new WeakMap();
4047
3970
  cur.set(key, next);
4048
3971
  }
@@ -4052,23 +3975,38 @@ var WeakMapDomCache = class {
4052
3975
  const leaf = cur.get(lastKey);
4053
3976
  if (leaf) leaf[cacheKey] = v;
4054
3977
  else if (isWeakKey(lastKey)) cur.set(lastKey, { [cacheKey]: v });
4055
- else this.badKey += 1;
4056
3978
  }
4057
3979
  evict() {
4058
- const { hit, miss, badKey } = this;
4059
- this.hit = this.miss = this.badKey = 0;
4060
3980
  this.keysByLen = /* @__PURE__ */ new Map();
4061
- return { hit, miss, badKey };
4062
3981
  }
4063
3982
  };
4064
3983
 
4065
3984
  // src/renderer.js
4066
- var DATASET_ATTRS = ["nid", "cid", "eid", "vid", "si", "sk"];
3985
+ function stampRenderBase(vdom, text) {
3986
+ if (vdom instanceof VNode)
3987
+ return new VNode(
3988
+ vdom.tag,
3989
+ { ...vdom.attrs, "data-rp": text },
3990
+ vdom.childs,
3991
+ vdom.key,
3992
+ vdom.namespace
3993
+ );
3994
+ if (vdom instanceof VFragment)
3995
+ return new VFragment(vdom.childs.map((c) => stampRenderBase(c, text)));
3996
+ return vdom;
3997
+ }
4067
3998
  var Renderer = class {
4068
3999
  constructor(comps) {
4069
4000
  this.comps = comps;
4070
4001
  this.cache = new WeakMapDomCache();
4071
- this.renderTag = h;
4002
+ }
4003
+ // Parse nodes build their VDOM through the renderer, never by importing vdom.js
4004
+ // themselves: a component may have been compiled by ANOTHER copy of the library
4005
+ // (a docs example imports the dist bundle while the host app runs from src), and
4006
+ // `render()` recognizes VNodes by `instanceof` against ITS copy's classes. Going
4007
+ // through `rx` guarantees the rendering copy makes them.
4008
+ renderTag(tag, attrs, childs, namespace) {
4009
+ return h(tag, attrs, childs, namespace);
4072
4010
  }
4073
4011
  renderFragment(childs) {
4074
4012
  return new VFragment(childs);
@@ -4079,38 +4017,24 @@ var Renderer = class {
4079
4017
  setNullCache() {
4080
4018
  this.cache = new NullDomCache();
4081
4019
  }
4082
- // Library utilities for consumers embedding tutuca: render a value to a detached
4083
- // DOM node / an HTML string. Not called by the framework itself.
4084
- renderToDOM(stack, val) {
4085
- const rootNode = document.createElement("div");
4086
- const rOpts = { document };
4087
- render(h("DIV", null, [this.renderRoot(stack, val)]), rootNode, rOpts);
4088
- return rootNode.childNodes[0];
4089
- }
4090
- renderToString(stack, val, cleanAttrs = true) {
4091
- const dom = this.renderToDOM(stack, val);
4092
- if (cleanAttrs) {
4093
- const nodes = dom.querySelectorAll("[data-nid],[data-cid],[data-eid]");
4094
- for (const { dataset } of nodes) for (const name of DATASET_ATTRS) delete dataset[name];
4095
- }
4096
- return dom.innerHTML;
4097
- }
4098
4020
  renderRoot(stack, val, viewName = null) {
4099
4021
  const comp = this.comps.getCompFor(val);
4100
4022
  if (comp === null) return null;
4101
4023
  return this._rValComp(stack, val, comp, comp.getView(viewName).anode, "ROOT", viewName);
4102
4024
  }
4103
- renderIt(stack, node, key, viewName) {
4025
+ // Render `stack.it` at a `<x render*>` site (`node`). `base` is the absolute
4026
+ // path the site resumed at, when it rendered a `*name` (see _rValComp).
4027
+ renderIt(stack, node, viewName, base = null) {
4104
4028
  const comp = this.comps.getCompFor(stack.it);
4105
- return comp ? this._rValComp(stack, stack.it, comp, node, key, viewName) : null;
4029
+ return comp ? this._rValComp(stack, stack.it, comp, node, "", viewName, base) : null;
4106
4030
  }
4107
4031
  // `node` is the parse node of the render site (`<x render>` / `render-it` /
4108
4032
  // `render-each`, or the view's root anode for the app root). It keys the
4109
4033
  // cache as a globally-unique object: node ids alone are unique only within a
4110
4034
  // single view, so the same value rendered by two components (e.g. through a
4111
4035
  // shared dynamic-var sequence) would otherwise collide in the cache.
4112
- _rValComp(stack, val, comp, node, key, viewName) {
4113
- const cacheKey = `${viewName ?? ""}${stack.viewsId ?? ""}${key}`;
4036
+ _rValComp(stack, val, comp, node, key, viewName, base = null) {
4037
+ const cacheKey = `${viewName ?? ""}${stack.viewsId ?? ""}${key}${stack.renderPath.addressKey}`;
4114
4038
  const cachePath = [node, val];
4115
4039
  stack._pushDynBindValuesToArray(cachePath, comp);
4116
4040
  const cachedNode = this.cache.get(cachePath, cacheKey);
@@ -4118,30 +4042,43 @@ var Renderer = class {
4118
4042
  const view = viewName ? comp.getView(viewName) : stack.lookupBestView(comp.views, "main");
4119
4043
  const body = this.renderView(view, stack);
4120
4044
  if (body == null) return null;
4045
+ const baseJson = base === null ? null : pathToJson(base);
4121
4046
  const meta = this._renderMetadata({
4122
4047
  $: "Comp",
4123
4048
  nid: node?.nodeId ?? null,
4124
4049
  cid: comp.id,
4125
- vid: view.name
4050
+ vid: view.name,
4051
+ ...baseJson === null ? null : { base: baseJson }
4126
4052
  });
4127
- const dom = new VFragment([meta, body]);
4053
+ const dom = new VFragment([
4054
+ meta,
4055
+ baseJson === null ? body : stampRenderBase(body, JSON.stringify(baseJson))
4056
+ ]);
4128
4057
  this.cache.set(cachePath, cacheKey, dom);
4129
4058
  return dom;
4130
4059
  }
4131
4060
  pushEachEntry(r, nid, attrName, key, dom) {
4132
4061
  r.push(this._renderMetadata({ $: "Each", nid, [attrName]: key }), dom);
4133
4062
  }
4134
- renderEachWhen(stack, iterInfo, view, nid) {
4135
- const { seq, filter, loopWith, enricher } = iterInfo.eval(stack);
4063
+ renderEachWhen(stack, each) {
4064
+ const { val: seqVal, node: view, nodeId: nid } = each;
4065
+ const { seq, filter, loopWith, enricher } = each.evalIter(stack);
4066
+ const seqPath = seqVal instanceof DynVal ? stack.lookupDynamicLocated(seqVal.name)?.path ?? null : null;
4136
4067
  const r = [];
4137
4068
  const it = stack.it;
4138
4069
  const renderOne = (key, value, attrName, binds) => {
4070
+ const itemBase = seqPath === null ? null : keyedPath(seqPath, key);
4071
+ const itemStep = itemBase === null ? each.itemStep(key) : null;
4072
+ const itemPath = itemBase !== null ? stack.renderPath.pushFrame(itemBase) : itemStep !== null ? stack.renderPath.pushItem(itemStep) : stack.renderPath;
4139
4073
  const cachePath = enricher ? [view, it, value] : [view, value];
4140
- const cacheKey = `${stack.viewsId ?? ""}${nid}${key}`;
4074
+ const cacheKey = `${stack.viewsId ?? ""}${nid}${key}${itemPath.addressKey}`;
4141
4075
  const cachedNode = this.cache.get(cachePath, cacheKey);
4142
4076
  if (cachedNode) this.pushEachEntry(r, nid, attrName, key, cachedNode);
4143
4077
  else {
4144
- const dom = this.renderView(view, stack.enter(value, binds, false));
4078
+ const dom = this.renderView(
4079
+ view,
4080
+ stack.enter(value, binds, false, itemPath, itemBase !== null)
4081
+ );
4145
4082
  if (dom != null) this.pushEachEntry(r, nid, attrName, key, dom);
4146
4083
  this.cache.set(cachePath, cacheKey, dom);
4147
4084
  }
@@ -4205,17 +4142,18 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
4205
4142
  var css = String.raw;
4206
4143
  var html = String.raw;
4207
4144
  var macro = (defaults, rawView) => new Macro(defaults, rawView);
4145
+ var devOnly = (name, result) => {
4146
+ console.warn(`${name} is a no-op in the core tutuca build; import tutuca/dev for the real one`);
4147
+ return result;
4148
+ };
4208
4149
  function check(_app) {
4209
- return { error: 0, warn: 0, hint: 0, dummyCheck: true };
4150
+ return devOnly("check", { error: 0, warn: 0, hint: 0 });
4210
4151
  }
4211
4152
  async function test(_opts) {
4212
- return null;
4153
+ return devOnly("test", null);
4213
4154
  }
4214
4155
  function collectIterBindings() {
4215
- console.warn(
4216
- "collectIterBindings is a no-op in the core tutuca build; use the tutuca-dev build for a functional implementation"
4217
- );
4218
- return [];
4156
+ return devOnly("collectIterBindings", []);
4219
4157
  }
4220
4158
  function tutuca(nodeOrSelector) {
4221
4159
  const rootNode = typeof nodeOrSelector === "string" ? document.querySelector(nodeOrSelector) : nodeOrSelector;
@@ -4225,7 +4163,6 @@ function tutuca(nodeOrSelector) {
4225
4163
  }
4226
4164
  export {
4227
4165
  COMPONENT,
4228
- FIELD_CLASS,
4229
4166
  PASS,
4230
4167
  ParseContext,
4231
4168
  SEQ_INFO,
@@ -4237,6 +4174,7 @@ export {
4237
4174
  html,
4238
4175
  injectCss,
4239
4176
  macro,
4177
+ path,
4240
4178
  phaseOps,
4241
4179
  resolveArgs,
4242
4180
  rootDispatcher,