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.
@@ -293,7 +293,7 @@ function processResult(result, scope) {
293
293
  }
294
294
  return result !== NOTHING ? result : void 0;
295
295
  }
296
- function finalize(rootScope, value, path) {
296
+ function finalize(rootScope, value, path2) {
297
297
  if (isFrozen(value))
298
298
  return value;
299
299
  const useStrictIteration = rootScope.immer_.shouldUseStrictIteration();
@@ -301,7 +301,7 @@ function finalize(rootScope, value, path) {
301
301
  if (!state) {
302
302
  each(
303
303
  value,
304
- (key, childValue) => finalizeProperty(rootScope, state, value, key, childValue, path),
304
+ (key, childValue) => finalizeProperty(rootScope, state, value, key, childValue, path2),
305
305
  useStrictIteration
306
306
  );
307
307
  return value;
@@ -331,16 +331,16 @@ function finalize(rootScope, value, path) {
331
331
  result,
332
332
  key,
333
333
  childValue,
334
- path,
334
+ path2,
335
335
  isSet2
336
336
  ),
337
337
  useStrictIteration
338
338
  );
339
339
  maybeFreeze(rootScope, result, false);
340
- if (path && rootScope.patches_) {
340
+ if (path2 && rootScope.patches_) {
341
341
  getPlugin("Patches").generatePatches_(
342
342
  state,
343
- path,
343
+ path2,
344
344
  rootScope.patches_,
345
345
  rootScope.inversePatches_
346
346
  );
@@ -362,9 +362,9 @@ function finalizeProperty(rootScope, parentState, targetObject, prop, childValue
362
362
  if (childValue === targetObject)
363
363
  die(5);
364
364
  if (isDraft(childValue)) {
365
- const path = rootPath && parentState && parentState.type_ !== 3 && // Set objects are atomic since they have no keys.
365
+ const path2 = rootPath && parentState && parentState.type_ !== 3 && // Set objects are atomic since they have no keys.
366
366
  !has(parentState.assigned_, prop) ? rootPath.concat(prop) : void 0;
367
- const res = finalize(rootScope, childValue, path);
367
+ const res = finalize(rootScope, childValue, path2);
368
368
  set(targetObject, prop, res);
369
369
  if (isDraft(res)) {
370
370
  rootScope.canAutoFreeze_ = false;
@@ -754,12 +754,6 @@ var produce = immer.produce;
754
754
 
755
755
  // src/path.js
756
756
  var NONE = /* @__PURE__ */ Symbol("NONE");
757
- var readKey = (value, key, dval = null) => {
758
- if (value == null) return dval;
759
- if (value instanceof Map) return value.has(key) ? value.get(key) : dval;
760
- if (value instanceof Set) return value.has(key) ? key : dval;
761
- return Object.hasOwn(value, key) ? value[key] : dval;
762
- };
763
757
  var writeKey = (value, key, next) => {
764
758
  if (value instanceof Map) value.set(key, next);
765
759
  else if (value instanceof Set) {
@@ -767,20 +761,15 @@ var writeKey = (value, key, next) => {
767
761
  value.add(next);
768
762
  } else value[key] = next;
769
763
  };
770
- var writeSeqKey = (value, key, next) => {
771
- if (value instanceof Map || value instanceof Set || Array.isArray(value) || Object.hasOwn(value, key))
772
- writeKey(value, key, next);
773
- else if (typeof value?.set === "function") value.set(key, next);
774
- else writeKey(value, key, next);
775
- };
764
+ 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);
776
765
  var Step = class {
777
- lookup(_v, dval = null) {
778
- return dval;
779
- }
780
766
  setDraftValue(_root, _v) {
781
767
  }
782
- enterFrame(stack, next) {
783
- return stack.enter(next, {}, true);
768
+ // Re-enter this step while rebuilding a stack. `renderPath` is the dispatch
769
+ // position AFTER this step: a rebuilt frame must carry the same render path
770
+ // the renderer had there, or the provides it publishes would be located wrong.
771
+ enterFrame(stack, next, renderPath) {
772
+ return stack.enter(next, {}, true, renderPath);
784
773
  }
785
774
  toAbstractPathStep() {
786
775
  return this;
@@ -797,41 +786,29 @@ var Step = class {
797
786
  return null;
798
787
  }
799
788
  };
800
- var BindStep = class _BindStep extends Step {
789
+ var BindStep = class extends Step {
801
790
  constructor(binds) {
802
791
  super();
803
792
  this.binds = binds;
804
793
  }
805
- lookup(v, _dval) {
794
+ lookup(v) {
806
795
  return v;
807
796
  }
808
- enterFrame(stack, next) {
809
- return stack.enter(next, { ...this.binds }, false);
810
- }
811
- withIndex(i) {
812
- return new _BindStep({ ...this.binds, key: i });
813
- }
814
- withKey(key) {
815
- return new _BindStep({ ...this.binds, key });
797
+ enterFrame(stack, next, renderPath) {
798
+ return stack.enter(next, { ...this.binds }, false, renderPath);
816
799
  }
817
800
  toAbstractPathStep() {
818
801
  return null;
819
802
  }
820
803
  };
821
- var ScopeBindStep = class _ScopeBindStep extends BindStep {
804
+ var ScopeBindStep = class extends BindStep {
822
805
  constructor(val, binds = {}) {
823
806
  super(binds);
824
807
  this.val = val;
825
808
  }
826
- enterFrame(stack, next) {
809
+ enterFrame(stack, next, renderPath) {
827
810
  const dyn = this.val.evalAsHandler(stack)?.call(stack.it) ?? {};
828
- return stack.enter(next, { ...this.binds, ...dyn }, false);
829
- }
830
- withIndex(i) {
831
- return new _ScopeBindStep(this.val, { ...this.binds, key: i });
832
- }
833
- withKey(key) {
834
- return new _ScopeBindStep(this.val, { ...this.binds, key });
811
+ return stack.enter(next, { ...this.binds, ...dyn }, false, renderPath);
835
812
  }
836
813
  };
837
814
  var FieldStep = class extends Step {
@@ -839,18 +816,12 @@ var FieldStep = class extends Step {
839
816
  super();
840
817
  this.field = field;
841
818
  }
842
- lookup(v, dval = null) {
843
- return readKey(v, this.field, dval);
819
+ lookup(v) {
820
+ return seqGet(v, this.field, NONE);
844
821
  }
845
822
  setDraftValue(root, v) {
846
823
  writeKey(root, this.field, v);
847
824
  }
848
- withIndex(i) {
849
- return new SeqStep(this.field, i);
850
- }
851
- withKey(k) {
852
- return new SeqStep(this.field, k);
853
- }
854
825
  toKey() {
855
826
  return { field: this.field };
856
827
  }
@@ -861,15 +832,15 @@ var SeqStep = class extends Step {
861
832
  this.field = field;
862
833
  this.key = key;
863
834
  }
864
- lookup(v, dval = null) {
865
- return seqGet(readKey(v, this.field, null), this.key, dval);
835
+ lookup(v) {
836
+ return seqGet(seqGet(v, this.field, null), this.key, NONE);
866
837
  }
867
838
  setDraftValue(root, v) {
868
- const seq = readKey(root, this.field, null);
839
+ const seq = seqGet(root, this.field, null);
869
840
  if (seq != null) writeSeqKey(seq, this.key, v);
870
841
  }
871
- enterFrame(stack, next) {
872
- return stack.enter(next, { key: this.key }, true);
842
+ enterFrame(stack, next, renderPath) {
843
+ return stack.enter(next, { key: this.key }, true, renderPath);
873
844
  }
874
845
  toKey() {
875
846
  return { field: this.field, key: this.key };
@@ -881,20 +852,20 @@ var SeqAccessStep = class extends Step {
881
852
  this.seqField = seqField;
882
853
  this.keyField = keyField;
883
854
  }
884
- lookup(v, dval = null) {
885
- const seq = readKey(v, this.seqField, NONE);
886
- const key = readKey(v, this.keyField, NONE);
887
- return key !== NONE && seq !== NONE ? seqGet(seq, key, dval) : dval;
855
+ lookup(v) {
856
+ const seq = seqGet(v, this.seqField, NONE);
857
+ const key = seqGet(v, this.keyField, NONE);
858
+ return key !== NONE && seq !== NONE ? seqGet(seq, key, NONE) : NONE;
888
859
  }
889
860
  setDraftValue(root, v) {
890
- const seq = readKey(root, this.seqField, NONE);
891
- const key = readKey(root, this.keyField, NONE);
861
+ const seq = seqGet(root, this.seqField, NONE);
862
+ const key = seqGet(root, this.keyField, NONE);
892
863
  if (seq !== NONE && key !== NONE) writeSeqKey(seq, key, v);
893
864
  }
894
865
  // Resolve `keyField` against `v` now and freeze it as a literal-key `SeqStep`, so a
895
866
  // later lookup/setValue lands on this same item even if `keyField` changes meanwhile.
896
867
  pinKey(v) {
897
- const key = readKey(v, this.keyField, NONE);
868
+ const key = seqGet(v, this.keyField, NONE);
898
869
  return key === NONE ? this : new SeqStep(this.seqField, key);
899
870
  }
900
871
  // The key is a *field reference* resolved live, so it is unknown without a value;
@@ -905,70 +876,62 @@ var SeqAccessStep = class extends Step {
905
876
  }
906
877
  };
907
878
  var EachBindStep = class extends Step {
908
- constructor(iterInfo, key) {
879
+ constructor(eachNode, key) {
909
880
  super();
910
- this.iterInfo = iterInfo;
881
+ this.eachNode = eachNode;
911
882
  this.key = key;
912
883
  }
913
- lookup(v, _dval) {
884
+ lookup(v) {
914
885
  return v;
915
886
  }
916
887
  // Replay the renderer's per-item binds (key, value + any @enrich-with binds)
917
888
  // so a rebuilt stack matches the one @each rendered with.
918
- enterFrame(stack, next) {
919
- return stack.enter(next, this.iterInfo.enrichBinds(stack, this.key), false);
889
+ enterFrame(stack, next, renderPath) {
890
+ return stack.enter(next, this.eachNode.enrichBinds(stack, this.key), false, renderPath);
920
891
  }
921
892
  toAbstractPathStep() {
922
893
  return null;
923
894
  }
924
895
  };
925
896
  var EachRenderItStep = class extends SeqStep {
926
- enterFrame(stack, next) {
927
- return stack.enter(next, { key: this.key, value: next }, false).enter(next, {}, true);
897
+ enterFrame(stack, next, renderPath) {
898
+ return stack.enter(next, { key: this.key, value: next }, false, renderPath).enter(next, {}, true, renderPath);
928
899
  }
929
900
  toAbstractPathStep() {
930
901
  return new SeqStep(this.field, this.key);
931
902
  }
932
903
  };
933
- function warnRawDynStep(op, step) {
934
- console.warn(`Path.${op} reached a DynStep: call toTransactionPath() first`, step);
904
+ function stepToJson(step) {
905
+ if (step instanceof SeqStep) return { f: step.field, k: step.key };
906
+ if (step instanceof FieldStep) return { f: step.field };
907
+ if (step instanceof SeqAccessStep) return { f: step.seqField, a: step.keyField };
908
+ return null;
935
909
  }
936
- var DynStep = class extends Step {
937
- constructor(producerCompId, producerSteps) {
938
- super();
939
- this.producerCompId = producerCompId;
940
- this.producerSteps = producerSteps;
941
- this.interiorCids = /* @__PURE__ */ new Set();
942
- }
943
- // Steps spliced into the transaction path in place of this marker.
944
- teleportSteps() {
945
- return this.producerSteps;
946
- }
947
- lookup(_v, dval = null) {
948
- warnRawDynStep("lookup", this);
949
- return dval;
950
- }
951
- enterFrame(stack, _next) {
952
- warnRawDynStep("enterFrame", this);
953
- return stack;
954
- }
955
- };
956
- var DynEachStep = class extends DynStep {
957
- constructor(producerCompId, producerSteps, key) {
958
- super(producerCompId, producerSteps);
959
- this.key = key;
960
- }
961
- teleportSteps() {
962
- const { producerSteps, key } = this;
963
- if (producerSteps.length === 0) return producerSteps;
964
- const last = producerSteps[producerSteps.length - 1];
965
- if (!(last instanceof FieldStep)) {
966
- console.warn("DynEachStep: seq-access dynamic cannot be iterated", this);
967
- return producerSteps;
968
- }
969
- return producerSteps.slice(0, -1).concat(new SeqStep(last.field, key));
910
+ function stepFromJson(j) {
911
+ if (j == null || typeof j.f !== "string") return null;
912
+ if (j.k !== void 0) return new SeqStep(j.f, j.k);
913
+ if (j.a !== void 0) return new SeqAccessStep(j.f, j.a);
914
+ return new FieldStep(j.f);
915
+ }
916
+ function pathToJson(path2) {
917
+ const out = [];
918
+ for (const step of path2.steps) {
919
+ const j = stepToJson(step);
920
+ if (j === null) return null;
921
+ out.push(j);
970
922
  }
971
- };
923
+ return out;
924
+ }
925
+ function pathFromJson(j) {
926
+ if (!Array.isArray(j)) return null;
927
+ const steps = [];
928
+ for (const item of j) {
929
+ const step = stepFromJson(item);
930
+ if (step === null) return null;
931
+ steps.push(step);
932
+ }
933
+ return new Path(steps);
934
+ }
972
935
  var Path = class _Path {
973
936
  constructor(steps = []) {
974
937
  this.steps = steps;
@@ -976,43 +939,13 @@ var Path = class _Path {
976
939
  concat(steps) {
977
940
  return new _Path(this.steps.concat(steps));
978
941
  }
979
- popStep() {
980
- return new _Path(this.steps.slice(0, -1));
981
- }
982
- // The dispatch path: frame-only steps removed, one step per crossed component
983
- // (DynStep included). `popStep` over it bubbles through every component.
942
+ // Frame-only steps removed, one step per crossed component: `popStep` over the
943
+ // result bubbles through every component.
984
944
  compact() {
985
945
  const out = [];
986
946
  for (const step of this.steps) {
987
947
  const s = step.toAbstractPathStep();
988
- if (s !== null) {
989
- if (s !== step) s._originCid = step._originCid;
990
- out.push(s);
991
- }
992
- }
993
- return new _Path(out);
994
- }
995
- // The abstract path used to apply a transaction: every DynStep is teleported —
996
- // the steps interior to its producer..consumer span are dropped and the
997
- // producer's own path spliced in — so a mutation lands on the data's real
998
- // location. A path with no DynStep is returned unchanged.
999
- toTransactionPath() {
1000
- let hasDyn = false;
1001
- for (const step of this.steps)
1002
- if (step instanceof DynStep) {
1003
- hasDyn = true;
1004
- break;
1005
- }
1006
- if (!hasDyn) return this;
1007
- const out = [];
1008
- for (const step of this.steps) {
1009
- if (step instanceof DynStep) {
1010
- while (out.length > 0 && step.interiorCids.has(out[out.length - 1]._originCid)) out.pop();
1011
- for (const ts of step.teleportSteps()) {
1012
- ts._originCid = step.producerCompId;
1013
- out.push(ts);
1014
- }
1015
- } else out.push(step);
948
+ if (s !== null) out.push(s);
1016
949
  }
1017
950
  return new _Path(out);
1018
951
  }
@@ -1020,7 +953,6 @@ var Path = class _Path {
1020
953
  // key as it is *now* so a later lookup/setValue lands on the same item even if the
1021
954
  // keyField changed meanwhile (e.g. the selected tab moved while an intent was in
1022
955
  // flight). Returns a new Path with those steps replaced; `this` if nothing pinned.
1023
- // Must be called on a transaction path (no DynSteps — call toTransactionPath first).
1024
956
  pinKeys(root) {
1025
957
  let curVal = root;
1026
958
  let out = null;
@@ -1028,7 +960,7 @@ var Path = class _Path {
1028
960
  const step = this.steps[i];
1029
961
  const pinned = step.pinKey(curVal);
1030
962
  if (pinned !== step) (out ??= this.steps.slice())[i] = pinned;
1031
- curVal = step.lookup(curVal, NONE);
963
+ curVal = step.lookup(curVal);
1032
964
  if (curVal === NONE) break;
1033
965
  }
1034
966
  return out ? new _Path(out) : this;
@@ -1036,20 +968,20 @@ var Path = class _Path {
1036
968
  lookup(v, dval = null) {
1037
969
  let curVal = v;
1038
970
  for (const step of this.steps) {
1039
- curVal = step.lookup(curVal, NONE);
971
+ curVal = step.lookup(curVal);
1040
972
  if (curVal === NONE) return dval;
1041
973
  }
1042
974
  return curVal;
1043
975
  }
1044
976
  // The values entered along the path, root→leaf (root included): index 0 is `root`,
1045
977
  // the last entry is the leaf this path resolves to. Stops early at the first
1046
- // unresolvable step. Call on a transaction path (no DynSteps). Used to walk the
1047
- // component instances on a dispatch path (filter via Components.getCompFor).
978
+ // unresolvable step. Used to walk the component instances on a dispatch path
979
+ // (filter via Components.getCompFor).
1048
980
  resolveChain(root) {
1049
981
  const out = [root];
1050
982
  let curVal = root;
1051
983
  for (const step of this.steps) {
1052
- curVal = step.lookup(curVal, NONE);
984
+ curVal = step.lookup(curVal);
1053
985
  if (curVal === NONE) break;
1054
986
  out.push(curVal);
1055
987
  }
@@ -1058,7 +990,7 @@ var Path = class _Path {
1058
990
  // A flat `[{ field, key? }]` list of the addressing steps, skipping frame-only
1059
991
  // steps (binds). Generic path introspection so tooling (e.g. the storybook
1060
992
  // activity log) can identify which subtree a transaction touched without
1061
- // depending on Step internals. Call on a transaction path (no DynSteps).
993
+ // depending on Step internals.
1062
994
  toKeys() {
1063
995
  const out = [];
1064
996
  for (const step of this.steps) {
@@ -1072,69 +1004,146 @@ var Path = class _Path {
1072
1004
  return produce(root, (draft) => {
1073
1005
  let parent = draft;
1074
1006
  for (let i = 0; i < this.steps.length - 1; i++) {
1075
- parent = this.steps[i].lookup(parent, NONE);
1007
+ parent = this.steps[i].lookup(parent);
1076
1008
  if (parent === NONE) return;
1077
1009
  }
1078
1010
  this.steps.at(-1).setDraftValue(parent, v);
1079
1011
  });
1080
1012
  }
1013
+ };
1014
+ function walkItems(stack, items, renderPath, path2) {
1015
+ let prev = stack.it;
1016
+ for (const step of items) {
1017
+ const next = step.lookup(prev);
1018
+ if (next === NONE) {
1019
+ console.warn("bad PathItem", { root: stack.it, step, path: path2 });
1020
+ return null;
1021
+ }
1022
+ renderPath = renderPath.pushItem(step);
1023
+ stack = step.enterFrame(stack, next, renderPath);
1024
+ prev = next;
1025
+ }
1026
+ return [stack, renderPath];
1027
+ }
1028
+ var EMPTY_PATH = new Path([]);
1029
+ var DispatchPath = class _DispatchPath {
1030
+ constructor(frames = [{ base: EMPTY_PATH, items: [] }]) {
1031
+ this.frames = frames;
1032
+ }
1033
+ // Plain addressing steps in one frame based at the root: what a caller means
1034
+ // by "this position" when it has no continuation of its own.
1035
+ static ofSteps(steps) {
1036
+ return new _DispatchPath([{ base: EMPTY_PATH, items: steps.slice() }]);
1037
+ }
1038
+ get top() {
1039
+ return this.frames[this.frames.length - 1];
1040
+ }
1041
+ _withTopItems(items) {
1042
+ const frames = this.frames.slice();
1043
+ frames[frames.length - 1] = { base: this.top.base, items };
1044
+ return new _DispatchPath(frames);
1045
+ }
1046
+ concat(steps) {
1047
+ return this._withTopItems(this.top.items.concat(steps));
1048
+ }
1049
+ pushItem(step) {
1050
+ return this.concat([step]);
1051
+ }
1052
+ pushFrame(base) {
1053
+ return new _DispatchPath(this.frames.concat({ base, items: [] }));
1054
+ }
1055
+ // Whether bubbling has anywhere left to go: another step in this frame, or a
1056
+ // visual caller underneath it. (A path always has at least one frame.)
1057
+ canPop() {
1058
+ return this.frames.length > 1 || this.frames[0].items.length > 0;
1059
+ }
1060
+ // One component closer to the root. At the top of a frame that is popping back
1061
+ // to the visual caller, not to the producer's own parent — the caller is where
1062
+ // the `*name` was written, and where an unhandled message should keep going.
1063
+ popStep() {
1064
+ const { top } = this;
1065
+ if (top.items.length > 0) return this._withTopItems(top.items.slice(0, -1));
1066
+ if (this.frames.length > 1) return new _DispatchPath(this.frames.slice(0, -1));
1067
+ return this;
1068
+ }
1069
+ // Drop frame-only steps inside every frame independently; a frame's base is
1070
+ // already addressing-only.
1071
+ compact() {
1072
+ return new _DispatchPath(
1073
+ this.frames.map(({ base, items }) => ({ base, items: new Path(items).compact().steps }))
1074
+ );
1075
+ }
1076
+ // A stable string for the ADDRESS this path denotes, for the render cache. The
1077
+ // same immutable value can sit at two places in the tree, and a subtree rendered
1078
+ // at one of them bakes that address in — the provides it publishes are located
1079
+ // there. Frame-only steps address nothing, so they are compacted out: two sites
1080
+ // that differ only in binds do render the same subtree.
1081
+ get addressKey() {
1082
+ this._addressKey ??= renderPathText(this.toTransactionPath().compact());
1083
+ return this._addressKey;
1084
+ }
1085
+ // The active transaction address: the top frame's absolute base followed by
1086
+ // its ordinary descendant steps. This is where a mutation lands.
1087
+ toTransactionPath() {
1088
+ const { base, items } = this.top;
1089
+ return base.concat(items);
1090
+ }
1091
+ // Rebuild the render stack this path was dispatched from. A frame with a base
1092
+ // re-enters at that absolute value first — replaying the resume a `*name`
1093
+ // render performed — and then walks its ordinary items.
1081
1094
  buildStack(stack) {
1082
- let prev = stack.it;
1083
- for (const step of this.steps) {
1084
- const next = step.lookup(prev, NONE);
1085
- if (next === NONE) {
1086
- console.warn("bad PathItem", { root: stack.it, step, path: this });
1087
- return null;
1095
+ let renderPath = new _DispatchPath();
1096
+ for (let i = 0; i < this.frames.length; i++) {
1097
+ const { base, items } = this.frames[i];
1098
+ if (i > 0 || base.steps.length > 0) {
1099
+ const baseValue = base.lookup(stack.root, NONE);
1100
+ if (baseValue === NONE) {
1101
+ console.warn("bad frame base", { base, path: this });
1102
+ return null;
1103
+ }
1104
+ renderPath = renderPath.pushFrame(base);
1105
+ stack = stack.enter(baseValue, {}, true, renderPath);
1088
1106
  }
1089
- stack = step.enterFrame(stack, next);
1090
- prev = next;
1107
+ const walked = walkItems(stack, items, renderPath, this);
1108
+ if (walked === null) return null;
1109
+ [stack, renderPath] = walked;
1091
1110
  }
1092
1111
  return stack;
1093
1112
  }
1094
- static fromNodeAndEventName(node, eventName, rootNode, maxDepth, comps, stopOnNoEvent = true) {
1095
- const pathSteps = [];
1096
- const pendingDyns = [];
1113
+ // Reconstruct the dispatch position of a DOM event from the `data-*` stamps and
1114
+ // `§…§` meta comments the renderer left on the way down. `stopOnNoEvent` is off for
1115
+ // drag events, whose path is needed even where no handler is registered.
1116
+ static fromNodeAndEventName(node, eventName, rootNode, comps, stopOnNoEvent = true) {
1117
+ const parts = [];
1097
1118
  const bubbles = BUBBLING_EVENTS.has(eventName);
1098
- let depth = 0;
1099
1119
  let eventIds = [];
1100
1120
  let handlers = null;
1101
- let nodeIds = [];
1121
+ let nodeRefs = [];
1102
1122
  let isLeafComponent = true;
1103
1123
  const crossComponent = (cidNum, vid) => {
1104
1124
  const comp = comps.getComponentForId(cidNum);
1105
- let pushStep = true;
1125
+ let pushPart = true;
1106
1126
  if (handlers === null && (isLeafComponent || bubbles)) {
1107
1127
  handlers = findHandlers(comp, eventIds, vid, eventName);
1108
1128
  if (handlers === null) {
1109
1129
  if (isLeafComponent && stopOnNoEvent && !bubbles) return false;
1110
1130
  } else if (!isLeafComponent) {
1111
- pathSteps.length = 0;
1112
- pendingDyns.length = 0;
1113
- pushStep = false;
1131
+ parts.length = 0;
1132
+ pushPart = false;
1114
1133
  }
1115
1134
  }
1116
1135
  isLeafComponent = false;
1117
- for (const dyn of pendingDyns) dyn.interiorCids.add(cidNum);
1118
- if (pushStep) {
1119
- const step = resolvePathStep(comp, nodeIds, vid);
1120
- if (step) {
1121
- step._originCid = cidNum;
1122
- pathSteps.push(step);
1123
- if (step instanceof DynStep) {
1124
- step.interiorCids.add(cidNum);
1125
- pendingDyns.push(step);
1126
- }
1127
- }
1136
+ if (pushPart) {
1137
+ const part = resolvePathPart(comp, nodeRefs, vid);
1138
+ if (part) parts.push(part);
1128
1139
  }
1129
- for (let i = pendingDyns.length - 1; i >= 0; i--)
1130
- if (pendingDyns[i].producerCompId === cidNum) pendingDyns.splice(i, 1);
1131
1140
  eventIds = [];
1132
- nodeIds = [];
1141
+ nodeRefs = [];
1133
1142
  return true;
1134
1143
  };
1135
- while (node && node !== rootNode && depth < maxDepth) {
1144
+ while (node && node !== rootNode) {
1136
1145
  if (node?.dataset) {
1137
- const { eid, cid, vid } = node.dataset;
1146
+ const { eid, cid, vid, rp } = node.dataset;
1138
1147
  if (eid !== void 0) eventIds.push(eid);
1139
1148
  const metas = metaChain(node.previousSibling);
1140
1149
  let sawComp = false;
@@ -1142,21 +1151,46 @@ var Path = class _Path {
1142
1151
  if (m.$ === "Comp") {
1143
1152
  sawComp = true;
1144
1153
  if (!crossComponent(m.cid, m.vid)) return NO_EVENT_INFO;
1145
- nodeIds.push({ nid: m.nid });
1154
+ nodeRefs.push({ nid: m.nid, base: pathFromJson(m.base) });
1146
1155
  } else {
1147
- nodeIds.push({ nid: m.nid, si: m.si, sk: m.sk });
1156
+ nodeRefs.push({ nid: m.nid, si: m.si, sk: m.sk });
1157
+ }
1158
+ }
1159
+ if (!sawComp && cid !== void 0) {
1160
+ if (!crossComponent(+cid, vid)) return NO_EVENT_INFO;
1161
+ if (rp !== void 0) {
1162
+ const base = parseRenderPath(rp);
1163
+ if (base !== null) nodeRefs.push({ base });
1148
1164
  }
1149
1165
  }
1150
- if (!sawComp && cid !== void 0 && !crossComponent(+cid, vid)) return NO_EVENT_INFO;
1151
1166
  }
1152
- depth += 1;
1153
1167
  node = node.parentNode;
1154
1168
  }
1155
- if (pendingDyns.length > 0)
1156
- console.warn("event reconstruction: dynamic-var producer not found", pendingDyns);
1157
- return [new _Path(pathSteps.reverse()), handlers];
1169
+ parts.reverse();
1170
+ let path2 = new _DispatchPath();
1171
+ for (const part of parts)
1172
+ path2 = part.base !== void 0 ? path2.pushFrame(part.base) : path2.pushItem(part.step);
1173
+ return [path2, handlers];
1174
+ }
1175
+ };
1176
+ function keyedPath(path2, key) {
1177
+ const steps = path2.steps;
1178
+ const last = steps[steps.length - 1];
1179
+ if (last instanceof SeqStep || last instanceof FieldStep)
1180
+ return new Path(steps.slice(0, -1).concat(new SeqStep(last.field, key)));
1181
+ return null;
1182
+ }
1183
+ function renderPathText(path2) {
1184
+ return JSON.stringify(pathToJson(path2));
1185
+ }
1186
+ function parseRenderPath(text) {
1187
+ try {
1188
+ return pathFromJson(JSON.parse(text));
1189
+ } catch (err) {
1190
+ console.warn("bad render path", err, text);
1191
+ return null;
1158
1192
  }
1159
- };
1193
+ }
1160
1194
  function metaChain(n) {
1161
1195
  const out = [];
1162
1196
  while (n?.nodeType === 8 && n.textContent[0] === "§") {
@@ -1201,28 +1235,28 @@ var StepCtx = class _StepCtx {
1201
1235
  resolveNode() {
1202
1236
  return this.comp.getNodeForId(+this.meta.nid, this.vid);
1203
1237
  }
1204
- applyKey(pi) {
1205
- if (pi === null) return null;
1206
- const m = this.meta;
1207
- if (m.si !== void 0) return pi.withIndex(+m.si);
1208
- if (m.sk !== void 0) return pi.withKey(m.sk);
1209
- return pi;
1210
- }
1211
1238
  };
1212
- function resolvePathStep(comp, nodeIds, vid) {
1213
- for (let i = 0; i < nodeIds.length; i++) {
1214
- const ctx = new StepCtx(comp, nodeIds, i, vid);
1215
- const step = ctx.resolveNode().toPathStep(ctx);
1216
- if (step !== null) return step;
1239
+ function resolvePathPart(comp, nodeRefs, vid) {
1240
+ for (let i = 0; i < nodeRefs.length; i++) {
1241
+ const ref = nodeRefs[i];
1242
+ if (ref.base != null) return { base: ref.base };
1243
+ if (ref.nid === void 0 || ref.nid === null) continue;
1244
+ const ctx = new StepCtx(comp, nodeRefs, i, vid);
1245
+ const step = ctx.resolveNode()?.toPathStep(ctx) ?? null;
1246
+ if (step !== null) return { step };
1217
1247
  }
1218
1248
  return null;
1219
1249
  }
1220
1250
  var NO_EVENT_INFO = [null, null];
1221
1251
  var BUBBLING_EVENTS = /* @__PURE__ */ new Set(["drop"]);
1252
+ var path = () => new PathBuilder();
1222
1253
  var PathBuilder = class {
1223
1254
  constructor() {
1224
1255
  this.pathChanges = [];
1225
1256
  }
1257
+ toPath() {
1258
+ return new Path(this.pathChanges);
1259
+ }
1226
1260
  add(pathChange) {
1227
1261
  this.pathChanges.push(pathChange);
1228
1262
  return this;
@@ -1238,10 +1272,8 @@ var PathBuilder = class {
1238
1272
  }
1239
1273
  };
1240
1274
 
1241
- // src/util/env.js
1242
- var isMac = (globalThis.navigator?.userAgent ?? "").toLowerCase().includes("mac");
1243
-
1244
1275
  // src/value.js
1276
+ var isMac = (globalThis.navigator?.userAgent ?? "").toLowerCase().includes("mac");
1245
1277
  var VALID_VAL_ID_RE = /^[a-zA-Z][a-zA-Z0-9_]*\??$/;
1246
1278
  var isValidValId = (name) => VALID_VAL_ID_RE.test(name);
1247
1279
  var VALID_FLOAT_RE = /^-?[0-9]+(\.[0-9]+)?$/;
@@ -1423,22 +1455,10 @@ function _parsePredicate(s, tokens, px) {
1423
1455
  }
1424
1456
  return new PredicateVal(pred, args);
1425
1457
  }
1426
- function kindOf(val) {
1427
- if (val === null) return 0;
1428
- if (val instanceof ConstVal) return val.kind;
1429
- if (val instanceof StrTplVal) return val.kind;
1430
- if (val instanceof SeqAccessVal) return K_SEQ;
1431
- if (val instanceof FieldVal) return K_FIELD;
1432
- if (val instanceof MethodVal) return K_METHOD;
1433
- if (val instanceof BindVal) return K_BIND;
1434
- if (val instanceof DynVal) return K_DYN;
1435
- if (val instanceof NameVal) return K_NAME;
1436
- if (val instanceof EventMemberVal) return K_EVENT;
1437
- return 0;
1438
- }
1458
+ var kindOf = (val) => val?.kind ?? 0;
1439
1459
  var BaseVal = class {
1440
- render(_stack, _rx) {
1441
- }
1460
+ // No kind: never accepted by a group (PredicateVal, which only parseBool builds).
1461
+ kind = 0;
1442
1462
  eval(_stack) {
1443
1463
  }
1444
1464
  toPathItem() {
@@ -1458,9 +1478,6 @@ var ConstVal = class extends BaseVal {
1458
1478
  this.val = val;
1459
1479
  this.kind = kind;
1460
1480
  }
1461
- render(_stack, _rx) {
1462
- return this.val;
1463
- }
1464
1481
  eval(_stack) {
1465
1482
  return this.val;
1466
1483
  }
@@ -1486,9 +1503,7 @@ var PredicateVal = class extends BaseVal {
1486
1503
  return `${this.pred.name} ${this.args.map(String).join(" ")}`;
1487
1504
  }
1488
1505
  };
1489
- var VarVal = class extends BaseVal {
1490
- };
1491
- var StrTplVal = class _StrTplVal extends VarVal {
1506
+ var StrTplVal = class _StrTplVal extends BaseVal {
1492
1507
  constructor(vals) {
1493
1508
  super();
1494
1509
  this.vals = vals;
@@ -1501,9 +1516,6 @@ var StrTplVal = class _StrTplVal extends VarVal {
1501
1516
  for (const v of this.vals) if (!(v instanceof ConstVal) || v.fromMacroVar) return false;
1502
1517
  return true;
1503
1518
  }
1504
- render(stack, _rx) {
1505
- return this.eval(stack);
1506
- }
1507
1519
  eval(stack) {
1508
1520
  const strs = new Array(this.vals.length);
1509
1521
  for (let i = 0; i < this.vals.length; i++) strs[i] = this.vals[i]?.eval(stack, "");
@@ -1540,7 +1552,8 @@ var StrTplVal = class _StrTplVal extends VarVal {
1540
1552
  return new _StrTplVal(lo === 0 && hi === vals.length ? vals : vals.slice(lo, hi));
1541
1553
  }
1542
1554
  };
1543
- var NameVal = class extends VarVal {
1555
+ var NameVal = class extends BaseVal {
1556
+ kind = K_NAME;
1544
1557
  constructor(name) {
1545
1558
  super();
1546
1559
  this.name = name;
@@ -1590,6 +1603,7 @@ var EVENT_CONVENIENCES = {
1590
1603
  dragKey: nullSafe((info) => info.lookupBind("key"))
1591
1604
  };
1592
1605
  var EventMemberVal = class extends BaseVal {
1606
+ kind = K_EVENT;
1593
1607
  constructor(members) {
1594
1608
  super();
1595
1609
  this.members = members;
@@ -1612,24 +1626,21 @@ var EventMemberVal = class extends BaseVal {
1612
1626
  return `e.${this.members.join(".")}`;
1613
1627
  }
1614
1628
  };
1615
- var RenderVal = class extends BaseVal {
1616
- render(stack, _rx) {
1617
- return this.eval(stack);
1618
- }
1619
- };
1620
- var RenderNameVal = class extends RenderVal {
1629
+ var SigilNameVal = class extends BaseVal {
1621
1630
  constructor(name) {
1622
1631
  super();
1623
1632
  this.name = name;
1624
1633
  }
1634
+ toString() {
1635
+ return this.sigil + this.name;
1636
+ }
1625
1637
  };
1626
- var BindVal = class extends RenderNameVal {
1638
+ var BindVal = class extends SigilNameVal {
1639
+ kind = K_BIND;
1640
+ sigil = "@";
1627
1641
  eval(stack) {
1628
1642
  return stack.lookupBind(this.name);
1629
1643
  }
1630
- toString() {
1631
- return `@${this.name}`;
1632
- }
1633
1644
  };
1634
1645
  var BindMemberVal = class extends BindVal {
1635
1646
  constructor(name, member) {
@@ -1644,37 +1655,35 @@ var BindMemberVal = class extends BindVal {
1644
1655
  return `@${this.name}.${this.member}`;
1645
1656
  }
1646
1657
  };
1647
- var DynVal = class extends RenderNameVal {
1658
+ var DynVal = class extends SigilNameVal {
1659
+ kind = K_DYN;
1660
+ sigil = "*";
1648
1661
  eval(stack) {
1649
1662
  return stack.lookupDynamic(this.name);
1650
1663
  }
1651
- toString() {
1652
- return `*${this.name}`;
1653
- }
1654
1664
  };
1655
- var FieldVal = class extends RenderNameVal {
1665
+ var FieldVal = class extends SigilNameVal {
1666
+ kind = K_FIELD;
1667
+ sigil = ".";
1656
1668
  eval(stack) {
1657
1669
  return stack.lookupFieldRaw(this.name);
1658
1670
  }
1659
1671
  toPathItem() {
1660
1672
  return new FieldStep(this.name);
1661
1673
  }
1662
- toString() {
1663
- return `.${this.name}`;
1664
- }
1665
1674
  };
1666
- var MethodVal = class extends RenderNameVal {
1675
+ var MethodVal = class extends SigilNameVal {
1676
+ kind = K_METHOD;
1677
+ sigil = "$";
1667
1678
  eval(stack) {
1668
1679
  return stack.lookupMethod(this.name);
1669
1680
  }
1670
1681
  evalAsHandler(stack) {
1671
1682
  return stack.lookupFieldRaw(this.name);
1672
1683
  }
1673
- toString() {
1674
- return `$${this.name}`;
1675
- }
1676
1684
  };
1677
- var SeqAccessVal = class extends RenderVal {
1685
+ var SeqAccessVal = class extends BaseVal {
1686
+ kind = K_SEQ;
1678
1687
  constructor(seqVal, keyVal) {
1679
1688
  super();
1680
1689
  this.seqVal = seqVal;
@@ -1697,9 +1706,6 @@ var Attributes = class {
1697
1706
  constructor(items) {
1698
1707
  this.items = items;
1699
1708
  }
1700
- static parse(attributes, px, parseAll = false) {
1701
- return new AttrParser(px).parse(attributes, parseAll);
1702
- }
1703
1709
  isConstant() {
1704
1710
  return false;
1705
1711
  }
@@ -1717,10 +1723,16 @@ function parseDirectiveValue(px, directiveName, source, parser) {
1717
1723
  }
1718
1724
  return val;
1719
1725
  }
1726
+ var ITER_DIRECTIVES = {
1727
+ when: "whenVal",
1728
+ "loop-with": "loopWithVal",
1729
+ "enrich-with": "enrichWithVal"
1730
+ };
1731
+ var parseIterDirective = (px, name, s) => parseDirectiveValue(px, name, s, parseAlterHandler);
1720
1732
  function parseIterationDirectives(attributes, px) {
1721
1733
  const parseNamed = (name) => {
1722
1734
  const attr = attributes.getNamedItem(`@${name}`);
1723
- return attr ? parseDirectiveValue(px, name, attr.value, parseAlterHandler) : null;
1735
+ return attr ? parseIterDirective(px, name, attr.value) : null;
1724
1736
  };
1725
1737
  return { whenVal: parseNamed("when"), loopWithVal: parseNamed("loop-with") };
1726
1738
  }
@@ -1761,11 +1773,22 @@ var AttrParser = class {
1761
1773
  this.px.onParseIssue("bad-value", info);
1762
1774
  }
1763
1775
  }
1764
- parseThen(s) {
1765
- if (this.ifAttr) this.ifAttr.thenVal = parseText(s, this.px) ?? NOT_SET_VAL;
1776
+ // `@then` / `@else` fill the branches of the `@if.<attr>` on the same element.
1777
+ parseBranch(slot, name, s) {
1778
+ if (this.ifAttr === null) return this._orphan(name, s, "if");
1779
+ this.ifAttr[slot] = parseText(s, this.px) ?? NOT_SET_VAL;
1780
+ }
1781
+ // `@when` / `@loop-with` / `@enrich-with` amend the `@each` on the same element. A
1782
+ // loop-less `@enrich-with` is a scope of its own; the other two need the loop.
1783
+ parseIter(name, s) {
1784
+ const val = parseIterDirective(this.px, name, s);
1785
+ if (this.eachAttr !== null) this.eachAttr[ITER_DIRECTIVES[name]] = val;
1786
+ else if (name === "enrich-with") this.pushWrapper("scope", s, val);
1787
+ else this._orphan(name, s, "each");
1766
1788
  }
1767
- parseElse(value) {
1768
- if (this.ifAttr) this.ifAttr.elseVal = parseText(value, this.px) ?? NOT_SET_VAL;
1789
+ // A directive that only means something next to another one, written alone.
1790
+ _orphan(name, value, needs) {
1791
+ this.px.onParseIssue("orphan-directive", { name, value, needs });
1769
1792
  }
1770
1793
  parseEvent(directiveName, value) {
1771
1794
  const [eventName, ...modifiers] = directiveName.slice(3).split("+");
@@ -1779,77 +1802,51 @@ var AttrParser = class {
1779
1802
  this.events.add(eventName, handler, modifiers);
1780
1803
  }
1781
1804
  }
1782
- _parseDirectiveValue(directiveName, s, parserFn) {
1783
- return parseDirectiveValue(this.px, directiveName, s, parserFn);
1784
- }
1785
1805
  parseDirective(s, directiveName) {
1786
1806
  switch (directiveName) {
1787
1807
  case "dangerouslysetinnerhtml":
1788
1808
  this.attrs ??= [];
1789
- this.attrs.push(new RawHtmlAttr(this._parseDirectiveValue(directiveName, s, parseText)));
1809
+ this.attrs.push(new RawHtmlAttr(parseDirectiveValue(this.px, directiveName, s, parseText)));
1790
1810
  this.hasDynamic = true;
1791
1811
  return;
1792
1812
  case "push-view":
1793
- this.pushWrapper("push-view", s, this._parseDirectiveValue(directiveName, s, parseText));
1813
+ this.pushWrapper("push-view", s, parseDirectiveValue(this.px, directiveName, s, parseText));
1794
1814
  return;
1795
1815
  case "text":
1796
- this.textChild = this._parseDirectiveValue(directiveName, s, parseText);
1816
+ this.textChild = parseDirectiveValue(this.px, directiveName, s, parseText);
1797
1817
  return;
1798
1818
  case "show":
1799
- this.pushWrapper("show", s, this._parseDirectiveValue(directiveName, s, parseBool));
1819
+ this.pushWrapper("show", s, parseDirectiveValue(this.px, directiveName, s, parseBool));
1800
1820
  return;
1801
1821
  case "hide":
1802
- this.pushWrapper("hide", s, this._parseDirectiveValue(directiveName, s, parseBool));
1822
+ this.pushWrapper("hide", s, parseDirectiveValue(this.px, directiveName, s, parseBool));
1803
1823
  return;
1804
1824
  case "each": {
1805
- const val = this._parseDirectiveValue(directiveName, s, parseSequence);
1825
+ const val = parseDirectiveValue(this.px, directiveName, s, parseSequence);
1806
1826
  this.eachAttr = this.pushWrapper("each", s, val);
1807
1827
  return;
1808
1828
  }
1809
- case "enrich-with":
1810
- if (this.eachAttr !== null)
1811
- this.eachAttr.enrichWithVal = this._parseDirectiveValue(
1812
- directiveName,
1813
- s,
1814
- parseAlterHandler
1815
- );
1816
- else
1817
- this.pushWrapper(
1818
- "scope",
1819
- s,
1820
- this._parseDirectiveValue(directiveName, s, parseAlterHandler)
1821
- );
1822
- return;
1823
1829
  case "when":
1824
- this._parseWhen(s);
1825
- return;
1826
1830
  case "loop-with":
1827
- this._parseLoopWith(s);
1831
+ case "enrich-with":
1832
+ this.parseIter(directiveName, s);
1828
1833
  return;
1829
1834
  case "then":
1830
- this.parseThen(s);
1835
+ this.parseBranch("thenVal", directiveName, s);
1831
1836
  return;
1832
1837
  case "else":
1833
- this.parseElse(s);
1838
+ this.parseBranch("elseVal", directiveName, s);
1834
1839
  return;
1835
1840
  }
1836
1841
  if (directiveName.startsWith("on.")) this.parseEvent(directiveName, s);
1837
1842
  else if (directiveName.startsWith("if.")) this.parseIf(directiveName, s);
1838
- else if (directiveName.startsWith("then.")) this.parseThen(s);
1839
- else if (directiveName.startsWith("else.")) this.parseElse(s);
1843
+ else if (directiveName.startsWith("then.")) this.parseBranch("thenVal", directiveName, s);
1844
+ else if (directiveName.startsWith("else.")) this.parseBranch("elseVal", directiveName, s);
1840
1845
  else {
1841
1846
  const info = { name: directiveName, value: s };
1842
1847
  this.px.onParseIssue("unknown-directive", info);
1843
1848
  }
1844
1849
  }
1845
- _parseWhen(s) {
1846
- if (this.eachAttr !== null)
1847
- this.eachAttr.whenVal = this._parseDirectiveValue("when", s, parseAlterHandler);
1848
- }
1849
- _parseLoopWith(s) {
1850
- if (this.eachAttr !== null)
1851
- this.eachAttr.loopWithVal = this._parseDirectiveValue("loop-with", s, parseAlterHandler);
1852
- }
1853
1850
  parse(attributes, parseAll = false) {
1854
1851
  for (const { name, value } of attributes) {
1855
1852
  const charCode = name.charCodeAt(0);
@@ -2202,19 +2199,21 @@ var VNode = class _VNode extends VBase {
2202
2199
  const createOpts = attrs.is != null ? { is: attrs.is } : void 0;
2203
2200
  const node = ns === null ? doc.createElement(tag, createOpts) : doc.createElementNS(ns, tag, createOpts);
2204
2201
  const cOpts = childOpts(this, ns, opts);
2205
- if ("value" in attrs || "checked" in attrs) {
2206
- const { value, checked, ...rest } = attrs;
2207
- applyProperties(node, rest);
2208
- appendChildNodes(node, this.childs, cOpts);
2209
- if (value !== void 0) applyValueLast(node, value);
2210
- if (checked !== void 0) setProp(node, "checked", checked, false);
2211
- } else {
2212
- applyProperties(node, attrs);
2213
- appendChildNodes(node, this.childs, cOpts);
2214
- }
2202
+ applyAttrs(node, attrs, () => appendChildNodes(node, this.childs, cOpts));
2215
2203
  return node;
2216
2204
  }
2217
2205
  };
2206
+ function applyAttrs(node, attrs, placeChildren) {
2207
+ const hasValue = "value" in attrs;
2208
+ const hasChecked = "checked" in attrs;
2209
+ if (hasValue || hasChecked) {
2210
+ const { value: _v, checked: _c, ...rest } = attrs;
2211
+ applyProperties(node, rest);
2212
+ } else applyProperties(node, attrs);
2213
+ placeChildren();
2214
+ if (hasValue) applyValueLast(node, attrs.value);
2215
+ if (hasChecked) setProp(node, "checked", attrs.checked, isNamespaced(node));
2216
+ }
2218
2217
  function diffProps(a, b) {
2219
2218
  if (a === b) return null;
2220
2219
  let diff = null;
@@ -2245,22 +2244,16 @@ function morphNode(domNode, source, target, opts) {
2245
2244
  }
2246
2245
  if (type === 1 && source.isSameKind(target)) {
2247
2246
  const propsDiff = diffProps(source.attrs, target.attrs);
2248
- const hasValue = propsDiff != null && "value" in propsDiff;
2249
- const hasChecked = propsDiff != null && "checked" in propsDiff;
2250
- if (propsDiff) {
2251
- if (hasValue || hasChecked) {
2252
- const { value: _v, checked: _c, ...rest } = propsDiff;
2253
- applyProperties(domNode, rest);
2254
- } else applyProperties(domNode, propsDiff);
2255
- }
2256
- if (!target.attrs.dangerouslySetInnerHTML) {
2247
+ const placeChildren = () => {
2248
+ if (target.attrs.dangerouslySetInnerHTML) return;
2257
2249
  const ns = effectiveNs(target, opts);
2258
2250
  morphChildren(domNode, source.childs, target.childs, childOpts(target, ns, opts));
2251
+ };
2252
+ if (propsDiff) applyAttrs(domNode, propsDiff, placeChildren);
2253
+ else placeChildren();
2254
+ if (!(propsDiff && "value" in propsDiff) && source.tag === "SELECT") {
2255
+ if (target.attrs.value !== void 0) applyValueLast(domNode, target.attrs.value);
2259
2256
  }
2260
- if (hasValue) applyValueLast(domNode, propsDiff.value);
2261
- else if (source.tag === "SELECT" && target.attrs.value !== void 0)
2262
- applyValueLast(domNode, target.attrs.value);
2263
- if (hasChecked) setProp(domNode, "checked", propsDiff.checked, false);
2264
2257
  return domNode;
2265
2258
  }
2266
2259
  if (type === 11) {
@@ -2379,20 +2372,26 @@ function h(tagName, properties, children, namespace) {
2379
2372
  }
2380
2373
 
2381
2374
  // src/anode.js
2382
- function resolveDynProducer(comp, name) {
2383
- const own = comp?.provide?.[name];
2384
- const producerComp = own != null ? comp : comp?.scope?.lookupProvider(name) ?? null;
2385
- const producerProvide = own ?? producerComp?.provide?.[name];
2386
- if (producerComp == null || producerProvide == null) return null;
2387
- const pi = producerProvide.val?.toPathItem?.() ?? null;
2388
- return { producerCompId: producerComp.id, producerSteps: pi ? [pi] : [] };
2375
+ function renderTarget(val, stack) {
2376
+ if (val instanceof DynVal) {
2377
+ const loc = stack.lookupDynamicLocated(val.name);
2378
+ if (loc?.path == null) return [loc?.value ?? null, stack.renderPath, null];
2379
+ return [loc.value, stack.renderPath.pushFrame(loc.path), loc.path];
2380
+ }
2381
+ const step = val.toPathItem?.() ?? null;
2382
+ if (step === null) return [val.eval(stack), stack.renderPath, null];
2383
+ const path2 = stack.renderPath.pushItem(step);
2384
+ return [val.eval(stack), path2, stack.pendingFrame ? path2.toTransactionPath() : null];
2389
2385
  }
2390
2386
  var BaseNode = class {
2391
2387
  render(_stack, _rx) {
2392
2388
  return null;
2393
2389
  }
2394
- setDataAttr(key, val) {
2395
- console.warn("setDataAttr not implemented for", this, { key, val });
2390
+ // Stamp a `data-*` attribute on the element(s) this node renders. A no-op for
2391
+ // nodes that produce no element of their own (text, `<x render*>`): a view whose
2392
+ // root is one of those records its component boundary in the `§Comp§` meta
2393
+ // comment instead (see Renderer._rValComp).
2394
+ setDataAttr(_key, _val) {
2396
2395
  }
2397
2396
  isConstant() {
2398
2397
  return false;
@@ -2431,21 +2430,12 @@ var TextNode = class extends BaseNode {
2431
2430
  isConstant() {
2432
2431
  return true;
2433
2432
  }
2434
- setDataAttr(_key, _val) {
2435
- }
2436
2433
  };
2437
2434
  var CommentNode = class extends TextNode {
2438
2435
  render(_stack, rx) {
2439
2436
  return rx.renderComment(this.val);
2440
2437
  }
2441
2438
  };
2442
- function optimizeChilds(childs) {
2443
- for (let i = 0; i < childs.length; i++) {
2444
- const child = childs[i];
2445
- if (child.isConstant()) childs[i] = new RenderOnceNode(child);
2446
- else child.optimize();
2447
- }
2448
- }
2449
2439
  function optimizeNode(node) {
2450
2440
  if (node.isConstant()) return new RenderOnceNode(node);
2451
2441
  node.optimize();
@@ -2460,7 +2450,8 @@ var ChildsNode = class extends BaseNode {
2460
2450
  return this.childs.every((v) => v.isConstant());
2461
2451
  }
2462
2452
  optimize() {
2463
- optimizeChilds(this.childs);
2453
+ const { childs } = this;
2454
+ for (let i = 0; i < childs.length; i++) childs[i] = optimizeNode(childs[i]);
2464
2455
  }
2465
2456
  };
2466
2457
  var DomNode = class extends ChildsNode {
@@ -2472,8 +2463,7 @@ var DomNode = class extends ChildsNode {
2472
2463
  }
2473
2464
  render(stack, rx) {
2474
2465
  const childNodes = new Array(this.childs.length);
2475
- for (let i = 0; i < childNodes.length; i++)
2476
- childNodes[i] = this.childs[i]?.render?.(stack, rx) ?? null;
2466
+ for (let i = 0; i < childNodes.length; i++) childNodes[i] = this.childs[i].render(stack, rx);
2477
2467
  return rx.renderTag(this.tagName, this.attrs.eval(stack), childNodes, this.namespace);
2478
2468
  }
2479
2469
  setDataAttr(key, val) {
@@ -2485,7 +2475,7 @@ var DomNode = class extends ChildsNode {
2485
2475
  };
2486
2476
  var FragmentNode = class extends ChildsNode {
2487
2477
  render(stack, rx) {
2488
- return rx.renderFragment(this.childs.map((c) => c?.render(stack, rx)));
2478
+ return rx.renderFragment(this.childs.map((c) => c.render(stack, rx)));
2489
2479
  }
2490
2480
  setDataAttr(key, val) {
2491
2481
  for (const child of this.childs) child.setDataAttr(key, val);
@@ -2499,8 +2489,10 @@ var ANode = class _ANode extends BaseNode {
2499
2489
  this.nodeId = nodeId;
2500
2490
  this.val = val;
2501
2491
  }
2502
- toPathStep(ctx) {
2503
- return ctx.applyKey(this.val?.toPathItem?.() ?? null);
2492
+ // The addressing step this node contributes to an event's dispatch path, given
2493
+ // the `StepCtx` of the meta that named it; null when it addresses nothing.
2494
+ toPathStep(_ctx) {
2495
+ return this.val?.toPathItem?.() ?? null;
2504
2496
  }
2505
2497
  static parse(html2, px) {
2506
2498
  const nodes = px.parseHTML(html2);
@@ -2516,8 +2508,8 @@ var ANode = class _ANode extends BaseNode {
2516
2508
  return maybeFragment(trimmed);
2517
2509
  }
2518
2510
  static fromDOM(node, px) {
2519
- if (node instanceof px.Text) return new TextNode(node.textContent);
2520
- else if (node instanceof px.Comment) return new CommentNode(node.textContent);
2511
+ if (node.nodeType === 3) return new TextNode(node.textContent);
2512
+ if (node.nodeType === 8) return new CommentNode(node.textContent);
2521
2513
  const { childNodes, attributes: attrs, tagName: tag } = node;
2522
2514
  const childs = [];
2523
2515
  for (let i = 0; i < childNodes.length; i++) {
@@ -2535,11 +2527,11 @@ var ANode = class _ANode extends BaseNode {
2535
2527
  const slotName = attrs.getNamedItem("name")?.value ?? "_";
2536
2528
  return px.frame.macroSlots[slotName] ?? maybeFragment(childs);
2537
2529
  }
2538
- const [nAttrs, wrappers] = Attributes.parse(attrs, px, true);
2530
+ const [nAttrs, wrappers] = new AttrParser(px).parse(attrs, true);
2539
2531
  px.onAttributes(nAttrs, wrappers, null, true, tag);
2540
2532
  return wrap(px.newMacroNode(macroName, nAttrs.toMacroVars(), childs), px, wrappers);
2541
2533
  } else if (VALID_NODE_RE.test(tag)) {
2542
- const [nAttrs, wrappers, textChild] = Attributes.parse(attrs, px);
2534
+ const [nAttrs, wrappers, textChild] = new AttrParser(px).parse(attrs);
2543
2535
  px.onAttributes(nAttrs, wrappers, textChild, false, tag);
2544
2536
  if (textChild) childs.unshift(new RenderTextNode(null, textChild));
2545
2537
  const domChilds = tag !== "PRE" ? condenseChildsWhites(childs) : childs;
@@ -2701,39 +2693,32 @@ var RenderViewId = class extends ANode {
2701
2693
  evalViewName(stack) {
2702
2694
  return this.viewVal ? this.viewVal.eval(stack) : null;
2703
2695
  }
2704
- // A `<x render*>` produces no DOM element of its own to carry `data-cid`;
2705
- // when it is a view's root the component boundary is recorded in the `Comp`
2706
- // meta comment instead (see Renderer._rValComp), so this is a no-op.
2707
- setDataAttr(_key, _val) {
2708
- }
2709
2696
  };
2710
- function dynRenderStep(comp, name, key) {
2711
- const p = resolveDynProducer(comp, name);
2712
- if (!p) return null;
2713
- return key === void 0 ? new DynStep(p.producerCompId, p.producerSteps) : new DynEachStep(p.producerCompId, p.producerSteps, key);
2714
- }
2715
2697
  var RenderNode = class extends RenderViewId {
2716
2698
  render(stack, rx) {
2717
- const newStack = stack.enter(this.val.eval(stack), {}, true);
2718
- return rx.renderIt(newStack, this, "", this.evalViewName(stack));
2699
+ const [value, renderPath, base] = renderTarget(this.val, stack);
2700
+ const newStack = stack.enter(value, {}, true, renderPath, false);
2701
+ return rx.renderIt(newStack, this, this.evalViewName(stack), base);
2719
2702
  }
2703
+ // A `*name` target contributes no step: the site recorded the absolute base it
2704
+ // resumed at, and event reconstruction turns that into a continuation frame.
2720
2705
  toPathStep(ctx) {
2721
- if (this.val instanceof DynVal) return dynRenderStep(ctx.comp, this.val.name);
2706
+ if (this.val instanceof DynVal) return null;
2722
2707
  return super.toPathStep(ctx);
2723
2708
  }
2724
2709
  };
2725
2710
  var RenderItNode = class extends RenderViewId {
2726
2711
  render(stack, rx) {
2727
- const newStack = stack.enter(stack.it, {}, true);
2728
- return rx.renderIt(newStack, this, "", this.evalViewName(stack));
2712
+ const base = stack.pendingFrame ? stack.renderPath.toTransactionPath() : null;
2713
+ const newStack = stack.enter(stack.it, {}, true, stack.renderPath, false);
2714
+ return rx.renderIt(newStack, this, this.evalViewName(stack), base);
2729
2715
  }
2730
2716
  toPathStep(ctx) {
2731
2717
  const next = ctx.next();
2732
2718
  if (next === null) return null;
2733
2719
  const nextNode = next.resolveNode();
2734
2720
  if (nextNode instanceof EachNode && next.hasKey) {
2735
- if (nextNode.val instanceof DynVal)
2736
- return dynRenderStep(ctx.comp, nextNode.val.name, next.key);
2721
+ if (nextNode.val instanceof DynVal) return null;
2737
2722
  return new EachRenderItStep(nextNode.val.name, next.key);
2738
2723
  }
2739
2724
  return null;
@@ -2755,9 +2740,6 @@ var RenderTextNode = class extends ANode {
2755
2740
  render(stack, _rx) {
2756
2741
  return this.val.eval(stack);
2757
2742
  }
2758
- // Renders to a text node, which can't carry `data-cid`.
2759
- setDataAttr(_key, _val) {
2760
- }
2761
2743
  };
2762
2744
  var RenderOnceNode = class extends BaseNode {
2763
2745
  constructor(node) {
@@ -2832,36 +2814,46 @@ var ScopeNode = class extends WrapperNode {
2832
2814
  var EachNode = class extends WrapperNode {
2833
2815
  constructor(nodeId, val) {
2834
2816
  super(nodeId, val);
2835
- this.iterInfo = new IterInfo(val, null, null, null);
2817
+ this.iterInfo = new IterInfo();
2836
2818
  }
2837
2819
  render(stack, rx) {
2838
- return rx.renderEachWhen(stack, this.iterInfo, this.node, this.nodeId);
2839
- }
2840
- toPathStep(ctx) {
2841
- return ctx.hasKey ? new EachBindStep(this.iterInfo, ctx.key) : null;
2842
- }
2843
- static register = true;
2844
- };
2845
- var IterInfo = class {
2846
- constructor(val, whenVal, loopWithVal, enrichWithVal) {
2847
- this.val = val;
2848
- this.whenVal = whenVal;
2849
- this.loopWithVal = loopWithVal;
2850
- this.enrichWithVal = enrichWithVal;
2820
+ return rx.renderEachWhen(stack, this);
2851
2821
  }
2852
- eval(stack) {
2853
- const seq = this.val.eval(stack) ?? [];
2854
- const filter = this.whenVal?.evalAsHandler(stack) ?? filterAlwaysTrue;
2855
- const loopWith = this.loopWithVal?.evalAsHandler(stack) ?? nullLoopWith;
2856
- const enricher = this.enrichWithVal?.evalAsHandler(stack) ?? null;
2857
- return { seq, filter, loopWith, enricher };
2822
+ // The sequence and the three loop handlers, resolved against `stack`.
2823
+ evalIter(stack) {
2824
+ const { whenVal, loopWithVal, enrichWithVal } = this.iterInfo;
2825
+ return {
2826
+ seq: this.val.eval(stack) ?? [],
2827
+ filter: whenVal?.evalAsHandler(stack) ?? filterAlwaysTrue,
2828
+ loopWith: loopWithVal?.evalAsHandler(stack) ?? nullLoopWith,
2829
+ enricher: enrichWithVal?.evalAsHandler(stack) ?? null
2830
+ };
2858
2831
  }
2859
2832
  // Rebuild the per-item binds for `key` (see iteration.js bindsForKey).
2860
2833
  enrichBinds(stack, key) {
2861
- const { seq, filter, loopWith, enricher } = this.eval(stack);
2834
+ const { seq, filter, loopWith, enricher } = this.evalIter(stack);
2862
2835
  const ctx = makeLoopCtx(stack, filter);
2863
2836
  return bindsForKey({ seq, it: stack.it, loopWith, enricher, ctx }, key);
2864
2837
  }
2838
+ toPathStep(ctx) {
2839
+ return ctx.hasKey ? new EachBindStep(this, ctx.key) : null;
2840
+ }
2841
+ // Where one item lives, for the render path. `@each` re-binds `it` to the item
2842
+ // whether or not the body is a component, so the position moves either way and
2843
+ // the step has to address it — `.rows` iterated at `key` IS `rows[key]`.
2844
+ // Null for a dynamic sequence: `*rows` carries its own absolute path and enters
2845
+ // a continuation frame instead (see Renderer.renderEachWhen).
2846
+ itemStep(key) {
2847
+ return this.val instanceof FieldVal ? new SeqStep(this.val.name, key) : null;
2848
+ }
2849
+ static register = true;
2850
+ };
2851
+ var IterInfo = class {
2852
+ constructor() {
2853
+ this.whenVal = null;
2854
+ this.loopWithVal = null;
2855
+ this.enrichWithVal = null;
2856
+ }
2865
2857
  };
2866
2858
  function xOp(consumed = [], { wrappable = false, wrapper = null, ignoresChildren = false } = {}) {
2867
2859
  return { consumed: new Set(consumed), wrappable, wrapper, ignoresChildren };
@@ -2888,26 +2880,31 @@ var WRAPPER_NODES = {
2888
2880
  scope: ScopeNode,
2889
2881
  "push-view": PushViewNameNode
2890
2882
  };
2891
- var ParseContext = class _ParseContext {
2892
- constructor(document2, Text, Comment, nodes, events, macroNodes, frame, parent) {
2893
- this.nodes = nodes ?? [];
2894
- this.events = events ?? [];
2895
- this.macroNodes = macroNodes ?? [];
2896
- this.parent = parent ?? null;
2897
- this.frame = frame ?? {};
2898
- this.document = document2 ?? globalThis.document;
2899
- this.Text = Text ?? globalThis.Text;
2900
- this.Comment = Comment ?? globalThis.Comment;
2901
- this.cacheConstNodes = true;
2883
+ var ParseContext = class {
2884
+ constructor(document2 = globalThis.document) {
2885
+ this.document = document2;
2886
+ this.nodes = [];
2887
+ this.events = [];
2888
+ this.macroNodes = [];
2889
+ this.parent = null;
2890
+ this.frame = {};
2902
2891
  this.currentTag = null;
2903
2892
  }
2904
2893
  isInsideMacro(name) {
2905
2894
  return this.frame.macroName === name || this.parent?.isInsideMacro(name);
2906
2895
  }
2896
+ // A macro body parses in a child context that shares every accumulator with
2897
+ // its parent (nodes, events, macroNodes, and whatever a subclass collects) and
2898
+ // differs only in its frame. Cloned rather than constructed so a subclass
2899
+ // keeps collecting inside macro bodies without re-implementing this — the
2900
+ // lint and class-set contexts used to lose (or had to copy) exactly that.
2907
2901
  enterMacro(macroName, macroVars, macroSlots) {
2908
- const { document: document2, Text, Comment, nodes, events, macroNodes } = this;
2909
- const frame = { macroName, macroVars, macroSlots };
2910
- return new _ParseContext(document2, Text, Comment, nodes, events, macroNodes, frame, this);
2902
+ const child = Object.create(Object.getPrototypeOf(this));
2903
+ return Object.assign(child, this, {
2904
+ frame: { macroName, macroVars, macroSlots },
2905
+ parent: this,
2906
+ currentTag: null
2907
+ });
2911
2908
  }
2912
2909
  parseHTML(html2) {
2913
2910
  const t = this.document.createElement("template");
@@ -2972,10 +2969,10 @@ var isBlockDomNode = (n) => {
2972
2969
  return node instanceof DomNode && HTML_BLOCK_TAGS.has(node.tagName);
2973
2970
  };
2974
2971
  var isEmptyText = (c) => c instanceof TextNode && c.val === "";
2975
- var isIgnorableXChild = (c) => c instanceof CommentNode || (c.isWhiteSpace?.() ?? false);
2972
+ var isIgnorableXChild = (c) => c instanceof CommentNode || c.isWhiteSpace();
2976
2973
  var hasMeaningfulChilds = (childs) => childs.some((c) => !isIgnorableXChild(c));
2977
2974
  function trimEdgeWhite(node) {
2978
- if (!node.isWhiteSpace?.()) return false;
2975
+ if (!node.isWhiteSpace()) return false;
2979
2976
  node.condenseWhiteSpace();
2980
2977
  return true;
2981
2978
  }
@@ -2986,7 +2983,7 @@ function condenseChildsWhites(childs) {
2986
2983
  if (last > 0 && trimEdgeWhite(childs[last])) emptied = true;
2987
2984
  for (let i = 1; i < last; i++) {
2988
2985
  const cur = childs[i];
2989
- if (!(cur.isWhiteSpace?.() && cur.hasNewLine())) continue;
2986
+ if (!(cur.isWhiteSpace() && cur.hasNewLine())) continue;
2990
2987
  const bothBlock = isBlockDomNode(childs[i - 1]) && isBlockDomNode(childs[i + 1]);
2991
2988
  cur.condenseWhiteSpace(bothBlock ? "" : " ");
2992
2989
  if (bothBlock) emptied = true;
@@ -2994,12 +2991,12 @@ function condenseChildsWhites(childs) {
2994
2991
  return emptied ? childs.filter((c) => !isEmptyText(c)) : childs;
2995
2992
  }
2996
2993
  var View = class {
2997
- constructor(name, rawView = "No View Defined", style = "", anode = null, ctx = null) {
2994
+ constructor(name, rawView = "No View Defined", style = "") {
2998
2995
  this.name = name;
2999
- this.anode = anode;
3000
- this.style = style;
3001
- this.ctx = ctx;
3002
2996
  this.rawView = rawView;
2997
+ this.style = style;
2998
+ this.anode = null;
2999
+ this.ctx = null;
3003
3000
  }
3004
3001
  compile(ctx, scope, cid) {
3005
3002
  this.ctx = ctx;
@@ -3007,7 +3004,7 @@ var View = class {
3007
3004
  this.anode.setDataAttr("data-cid", cid);
3008
3005
  this.anode.setDataAttr("data-vid", this.name);
3009
3006
  this.ctx.compile(scope);
3010
- if (ctx.cacheConstNodes) this.anode = optimizeNode(this.anode);
3007
+ this.anode = optimizeNode(this.anode);
3011
3008
  }
3012
3009
  render(stack, rx) {
3013
3010
  if (this.anode === null) {
@@ -3032,7 +3029,7 @@ var NodeEvents = class {
3032
3029
  getHandlersFor(eventName) {
3033
3030
  let r = null;
3034
3031
  for (const handler of this.handlers)
3035
- if (handler.handlesEventName(eventName)) {
3032
+ if (handler.name === eventName) {
3036
3033
  r ??= [];
3037
3034
  r.push(handler);
3038
3035
  }
@@ -3046,9 +3043,6 @@ var NodeEvent = class {
3046
3043
  this.modifierWrapper = compileModifiers(name, modifiers);
3047
3044
  this.modifiers = modifiers;
3048
3045
  }
3049
- handlesEventName(name) {
3050
- return this.name === name;
3051
- }
3052
3046
  getHandlerAndArgs(stack, event) {
3053
3047
  const r = this.handlerCall.getHandlerAndArgs(stack, event);
3054
3048
  r[0] = this.modifierWrapper(r[0], event);
@@ -3100,68 +3094,286 @@ function compileModifiers(eventName, names) {
3100
3094
  };
3101
3095
  }
3102
3096
 
3103
- // src/components.js
3104
- var COMPONENT = /* @__PURE__ */ Symbol.for("tutuca.component");
3105
- var Components = class {
3106
- constructor() {
3107
- this.byId = /* @__PURE__ */ new Map();
3108
- }
3109
- registerComponent(Comp) {
3110
- this.byId.set(Comp[COMPONENT].id, Comp);
3111
- }
3112
- getComponentForId(id) {
3113
- return this.byId.get(id) ?? null;
3097
+ // src/stack.js
3098
+ var STOP = /* @__PURE__ */ Symbol("STOP");
3099
+ var NEXT = /* @__PURE__ */ Symbol("NEXT");
3100
+ var DEFAULT_ROUTE = ["dyn", "lex"];
3101
+ var isTypeName = (s) => {
3102
+ const c = s.charCodeAt(0);
3103
+ return c >= 65 && c <= 90;
3104
+ };
3105
+ function lookup(chain, name, dv = null) {
3106
+ let n = chain;
3107
+ while (n !== null) {
3108
+ const r = n[0].lookup(name);
3109
+ if (r === STOP) return dv;
3110
+ if (r !== NEXT) return r;
3111
+ n = n[1];
3114
3112
  }
3115
- getCompFor(v) {
3116
- const Comp = v?.constructor;
3117
- return Comp?.[COMPONENT] ? Comp : null;
3113
+ return dv;
3114
+ }
3115
+ var BindFrame = class {
3116
+ constructor(it, binds, isFrame) {
3117
+ this.it = it;
3118
+ this.binds = binds;
3119
+ this.isFrame = isFrame;
3118
3120
  }
3119
- getHandlerFor(v, name, key) {
3120
- return this.getCompFor(v)?.[key][name] ?? null;
3121
+ lookup(name) {
3122
+ const v = this.binds[name];
3123
+ return v === void 0 ? this.isFrame ? STOP : NEXT : v;
3121
3124
  }
3122
- // The `lex` leg of an intent's route: the handlers registered on the scope chain of
3123
- // the component that raised it, innermost first. A list, not a first match — see
3124
- // ComponentStack.lookupIntentChain.
3125
- getIntentChainFor(v, name) {
3126
- return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
3125
+ };
3126
+ var DynFrame = class {
3127
+ constructor(binds, types) {
3128
+ this.binds = binds;
3129
+ this.types = types;
3127
3130
  }
3128
- compileStyles() {
3129
- const styles = [];
3130
- for (const Comp of this.byId.values()) styles.push(Comp[COMPONENT].compileStyle());
3131
- return styles.join("\n");
3131
+ lookup(name) {
3132
+ const v = (isTypeName(name) ? this.types : this.binds)[name];
3133
+ return v === void 0 ? NEXT : v;
3132
3134
  }
3133
3135
  };
3134
- var ComponentStack = class _ComponentStack {
3135
- constructor(comps = new Components(), parent = null) {
3136
- this.comps = comps;
3137
- this.parent = parent;
3138
- this.byName = {};
3139
- this.intentsByName = {};
3140
- this.macros = {};
3136
+ function computeViewsId(views) {
3137
+ let s = "";
3138
+ let n = views;
3139
+ while (n !== null) {
3140
+ s += n[0];
3141
+ n = n[1];
3141
3142
  }
3142
- enter() {
3143
- return new _ComponentStack(this.comps, this);
3143
+ return s === "main" ? "" : s;
3144
+ }
3145
+ var Stack = class _Stack {
3146
+ constructor(fields) {
3147
+ Object.assign(this, fields);
3144
3148
  }
3145
- registerComponents(comps, opts) {
3146
- const { aliases = {} } = opts ?? {};
3147
- for (let i = 0; i < comps.length; i++) {
3148
- const Comp = comps[i];
3149
- Comp[COMPONENT].scope = this.enter();
3150
- this.comps.registerComponent(Comp);
3151
- this.byName[Comp.name] = Comp;
3149
+ _with(patch) {
3150
+ return new _Stack({ ...this, ...patch });
3151
+ }
3152
+ // Evaluate every provide the entered component publishes and push them as one
3153
+ // dynBinds frame, keyed by NAME. A value is published together with the absolute
3154
+ // path it lives at: the same declaration is read as `*name` AND resumed at by
3155
+ // `<x render="*name">`, so a consumer needs both halves. Types go in the same
3156
+ // frame's other map. No-op with no provides.
3157
+ _pushProvides() {
3158
+ const comp = this.comps.getCompFor(this.it);
3159
+ if (comp == null) return this;
3160
+ const { provide, provideType } = comp;
3161
+ const binds = {};
3162
+ const types = {};
3163
+ let has2 = false;
3164
+ const base = this._publishBase();
3165
+ for (const k in provide) {
3166
+ const step = provide[k].toPathItem?.() ?? null;
3167
+ if (step === null) continue;
3168
+ const value = provide[k].eval(this);
3169
+ binds[k] = { value, path: base === null ? null : base.concat([step]) };
3170
+ has2 = true;
3152
3171
  }
3153
- for (const alias in aliases) {
3154
- const comp = this.byName[aliases[alias]];
3155
- console.assert(this.byName[alias] === void 0, "alias overrides component", alias);
3156
- if (comp !== void 0) this.byName[alias] = comp;
3157
- else console.warn("alias", alias, "to inexistent component", aliases[alias]);
3172
+ for (const k in provideType) {
3173
+ types[k] = provideType[k];
3174
+ has2 = true;
3158
3175
  }
3176
+ if (!has2) return this;
3177
+ return this._with({ dynBinds: [new DynFrame(binds, types), this.dynBinds] });
3178
+ }
3179
+ // The absolute address of the component being entered, or null when this
3180
+ // position cannot be written down as one. Frame-only steps carry bindings and
3181
+ // address nothing, so they are compacted away first; what is left is checked
3182
+ // against the value actually being rendered, because a scope CAN move the
3183
+ // render position without contributing an addressing step (a plain `@each`
3184
+ // body re-binds `it` to the item while its rebuild step is an identity).
3185
+ //
3186
+ // A null base still publishes the VALUE — `*name` reads it fine — but there is
3187
+ // nowhere to resume, so `<x render="*name">` renders nothing rather than
3188
+ // editing whatever happens to live at the address we guessed.
3189
+ _publishBase() {
3190
+ const base = this.renderPath.toTransactionPath().compact();
3191
+ return base.lookup(this.root, NOT_FOUND) === this.it ? base : null;
3192
+ }
3193
+ static root(comps, it, ctx = null) {
3194
+ return new _Stack({
3195
+ comps,
3196
+ root: it,
3197
+ it,
3198
+ binds: [new BindFrame(it, {}, true), null],
3199
+ dynBinds: [new DynFrame({}, {}), null],
3200
+ views: ["main", null],
3201
+ viewsId: "",
3202
+ renderPath: new DispatchPath(),
3203
+ pendingFrame: false,
3204
+ ctx
3205
+ })._pushProvides();
3206
+ }
3207
+ // `renderPath` defaults to this stack's own: an ordinary scope does not move.
3208
+ // `pendingFrame` clears on a component frame (which emits the base) and is
3209
+ // inherited by transparent scopes, which have to carry it to the next boundary.
3210
+ enter(it, bindings = {}, isFrame = true, renderPath = this.renderPath, pendingFrame = null) {
3211
+ const stack = this._with({
3212
+ it,
3213
+ binds: [new BindFrame(it, bindings, isFrame), this.binds],
3214
+ renderPath,
3215
+ pendingFrame: pendingFrame ?? (isFrame ? false : this.pendingFrame)
3216
+ });
3217
+ return isFrame ? stack._pushProvides() : stack;
3159
3218
  }
3160
- registerMacros(macros) {
3161
- for (const key in macros) {
3162
- const lower = key.toLowerCase();
3163
- console.assert(this.macros[lower] === void 0, "macro key collision", lower);
3164
- this.macros[lower] = macros[key];
3219
+ pushViewName(name) {
3220
+ const views = [name, this.views];
3221
+ return this._with({ views, viewsId: computeViewsId(views) });
3222
+ }
3223
+ // Published types are stable per scope and would only churn the render cache, so
3224
+ // the cache key covers values alone.
3225
+ _pushDynBindValuesToArray(arr, comp) {
3226
+ for (const k in comp.provide) arr.push(this.lookupDynamic(k));
3227
+ for (const k in comp.lookup) arr.push(this.lookupDynamic(k));
3228
+ }
3229
+ // `*name`: the nearest binding above (including this component's own provides,
3230
+ // pushed on entering it), else a path registered in the component's lexical
3231
+ // scope, else this component's declared default, else null.
3232
+ //
3233
+ // One chain walk and no producer resolution: a lookup names what it WANTS, so the
3234
+ // frame it wants is keyed by that name. The default belongs to the CONSUMER's
3235
+ // declaration and is evaluated against the consumer's stack, which is why it is
3236
+ // consulted only after the whole chain has missed.
3237
+ lookupDynamicLocated(name) {
3238
+ if (isTypeName(name)) return null;
3239
+ const v = lookup(this.dynBinds, name);
3240
+ if (v != null) return v;
3241
+ const comp = this.comps.getCompFor(this.it);
3242
+ if (comp == null) return null;
3243
+ const path2 = comp.scope?.lookupPath?.(name) ?? null;
3244
+ if (path2 !== null) {
3245
+ const value2 = path2.lookup(this.root, NOT_FOUND);
3246
+ if (value2 !== NOT_FOUND) return { value: value2, path: path2 };
3247
+ }
3248
+ const dval = comp.lookup[name] ?? null;
3249
+ if (dval === null) return null;
3250
+ const step = dval.toPathItem?.() ?? null;
3251
+ const value = dval.eval(this);
3252
+ return step === null ? { value, path: null } : { value, path: this.renderPath.toTransactionPath().concat([step]) };
3253
+ }
3254
+ lookupDynamic(name) {
3255
+ if (isTypeName(name)) return lookup(this.dynBinds, name);
3256
+ return this.lookupDynamicLocated(name)?.value ?? null;
3257
+ }
3258
+ lookupBind(name) {
3259
+ return lookup(this.binds, name);
3260
+ }
3261
+ lookupFieldRaw(name) {
3262
+ return this.it[name] ?? null;
3263
+ }
3264
+ lookupMethod(name) {
3265
+ const fn = this.it[name];
3266
+ return fn instanceof Function ? fn.call(this.it) : null;
3267
+ }
3268
+ // The dispatched DOM event / drag info, read only by EventMemberVal's
3269
+ // `e.<member>` handler args. Null outside a live event transaction (`ctx` is
3270
+ // then a send/intent transaction, which carries no `e`).
3271
+ lookupEvent() {
3272
+ return this.ctx?.e ?? null;
3273
+ }
3274
+ lookupDragInfo() {
3275
+ return this.ctx?.dragInfo ?? null;
3276
+ }
3277
+ getHandlerFor(name, key) {
3278
+ return this.comps.getHandlerFor(this.it, name, key);
3279
+ }
3280
+ lookupBestView(views, defaultViewName) {
3281
+ let n = this.views;
3282
+ while (n !== null) {
3283
+ const view = views[n[0]];
3284
+ if (view !== void 0) return view;
3285
+ n = n[1];
3286
+ }
3287
+ return views[defaultViewName];
3288
+ }
3289
+ };
3290
+ var NOT_FOUND = /* @__PURE__ */ Symbol("NOT_FOUND");
3291
+
3292
+ // src/components.js
3293
+ var COMPONENT = /* @__PURE__ */ Symbol.for("tutuca.component");
3294
+ var Components = class {
3295
+ constructor() {
3296
+ this.byId = /* @__PURE__ */ new Map();
3297
+ }
3298
+ registerComponent(Comp) {
3299
+ this.byId.set(Comp.id, Comp);
3300
+ }
3301
+ getComponentForId(id) {
3302
+ return this.byId.get(id) ?? null;
3303
+ }
3304
+ getCompFor(v) {
3305
+ const Comp = v?.constructor;
3306
+ return Comp?.[COMPONENT] ? Comp : null;
3307
+ }
3308
+ getHandlerFor(v, name, key) {
3309
+ return this.getCompFor(v)?.[key][name] ?? null;
3310
+ }
3311
+ // The `lex` leg of an intent's route: the handlers registered on the scope chain of
3312
+ // the component that raised it, innermost first. A list, not a first match — see
3313
+ // ComponentStack.lookupIntentChain.
3314
+ getIntentChainFor(v, name) {
3315
+ return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
3316
+ }
3317
+ compileStyles() {
3318
+ const styles = [];
3319
+ for (const Comp of this.byId.values()) styles.push(Comp.compileStyle());
3320
+ return styles.join("\n");
3321
+ }
3322
+ };
3323
+ var ComponentStack = class _ComponentStack {
3324
+ constructor(comps = new Components(), parent = null) {
3325
+ this.comps = comps;
3326
+ this.parent = parent;
3327
+ this.byName = {};
3328
+ this.intentsByName = {};
3329
+ this.macros = {};
3330
+ this.paths = {};
3331
+ }
3332
+ enter() {
3333
+ return new _ComponentStack(this.comps, this);
3334
+ }
3335
+ registerComponents(comps, opts) {
3336
+ const { aliases = {}, paths } = opts ?? {};
3337
+ if (paths) this.registerPaths(paths);
3338
+ for (let i = 0; i < comps.length; i++) {
3339
+ const Comp = comps[i];
3340
+ Comp.scope = this.enter();
3341
+ this.comps.registerComponent(Comp);
3342
+ this.byName[Comp.name] = Comp;
3343
+ }
3344
+ for (const alias in aliases) {
3345
+ const comp = this.byName[aliases[alias]];
3346
+ console.assert(this.byName[alias] === void 0, "alias overrides component", alias);
3347
+ if (comp !== void 0) this.byName[alias] = comp;
3348
+ else console.warn("alias", alias, "to inexistent component", aliases[alias]);
3349
+ }
3350
+ }
3351
+ // Register lowercase names as absolute paths from the app state root. A
3352
+ // descendant that declares one in its `lookup` reads and renders `*name` without
3353
+ // anything above it publishing one — which is what makes a session, a theme or a
3354
+ // host-owned value available in its natural registration scope, instead of forcing
3355
+ // an application root whose only job is to `provide` it. Register on a nested
3356
+ // scope to narrow a name; nearest registration wins.
3357
+ //
3358
+ // Uppercase names are ignored: a component TYPE is what `lookupComponent` already
3359
+ // answers, and a type has no path.
3360
+ registerPaths(paths) {
3361
+ for (const name in paths) {
3362
+ if (isTypeName(name)) {
3363
+ console.warn("registerPaths: a type name has no path", name);
3364
+ continue;
3365
+ }
3366
+ this.paths[name] = paths[name].toPath();
3367
+ }
3368
+ }
3369
+ lookupPath(name) {
3370
+ return this.paths[name] ?? this.parent?.lookupPath(name) ?? null;
3371
+ }
3372
+ registerMacros(macros) {
3373
+ for (const key in macros) {
3374
+ const lower = key.toLowerCase();
3375
+ console.assert(this.macros[lower] === void 0, "macro key collision", lower);
3376
+ this.macros[lower] = macros[key];
3165
3377
  }
3166
3378
  }
3167
3379
  getCompFor(v) {
@@ -3188,97 +3400,82 @@ var ComponentStack = class _ComponentStack {
3188
3400
  lookupComponent(name) {
3189
3401
  return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
3190
3402
  }
3191
- // The component in this scope chain that provides `name`, innermost first. A lookup
3192
- // names a value without naming its producer, so the render-target teleport
3193
- // (resolveDynProducer) recovers the producer here instead. Sound because
3194
- // PROVIDE_NAME_COLLISION keeps a provide name to one producer per chain; with two,
3195
- // this returns the innermost and the linter has already reported the ambiguity.
3196
- lookupProvider(name) {
3403
+ // Whether anything in this scope chain provides `name`. Existence only: a lookup
3404
+ // names what it WANTS and takes whoever is nearest above it at render time, so
3405
+ // there is no producer to identify — several components may publish one name and
3406
+ // the live render ancestry decides. Used by the linter to tell a lookup that can
3407
+ // be satisfied from one that never will be.
3408
+ hasProvider(name) {
3197
3409
  for (const compName in this.byName) {
3198
- const Comp = this.byName[compName];
3199
- if (Comp.provide?.[name] !== void 0) return Comp;
3410
+ if (this.byName[compName].provide?.[name] !== void 0) return true;
3200
3411
  }
3201
- return this.parent?.lookupProvider(name) ?? null;
3412
+ return this.parent?.hasProvider(name) ?? false;
3202
3413
  }
3203
3414
  lookupMacro(name) {
3204
3415
  return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
3205
3416
  }
3206
3417
  };
3207
- var ProvideInfo = class {
3208
- constructor(val) {
3209
- this.val = val;
3210
- }
3211
- };
3212
- var LookupInfo = class {
3213
- constructor(val) {
3214
- this.val = val;
3215
- }
3216
- };
3217
3418
  var isString = (v) => typeof v === "string";
3218
- var isTypeName = (s) => {
3219
- const c = s.charCodeAt(0);
3220
- return c >= 65 && c <= 90;
3221
- };
3222
3419
  var _rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
3223
3420
  var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
3224
3421
  var _compId = 0;
3225
- var Component = class {
3226
- constructor(Class, o) {
3227
- this.id = _compId++;
3228
- this.name = o.name ?? "UnkComp";
3229
- this.Class = Class;
3230
- this.views = { main: new View("main", o.view, o.style) };
3231
- this.commonStyle = o.commonStyle ?? "";
3232
- this.globalStyle = o.globalStyle ?? "";
3233
- this.receive = o.receive ?? {};
3234
- this.intent = o.intent ?? {};
3235
- this.alter = o.alter ?? {};
3236
- for (const name in o.views ?? {}) {
3237
- const v = o.views[name];
3238
- const { view, style } = isString(v) ? { view: v } : v;
3239
- this.views[name] = new View(name, view, style);
3240
- }
3241
- this._rawProvide = o.provide ?? {};
3242
- this._rawLookup = o.lookup ?? [];
3243
- this.provide = {};
3244
- this.provideType = {};
3245
- this.lookup = {};
3246
- this.scope = null;
3247
- this.spec = o;
3248
- this.extra = {};
3249
- for (const key of Object.keys(o)) if (!KNOWN_SPEC_KEYS.has(key)) this.extra[key] = o[key];
3250
- }
3422
+ function initComponent(Class, o) {
3423
+ Class[COMPONENT] = Class;
3424
+ Class.id = _compId++;
3425
+ Class.views = { main: new View("main", o.view, o.style) };
3426
+ Class.commonStyle = o.commonStyle ?? "";
3427
+ Class.globalStyle = o.globalStyle ?? "";
3428
+ Class.receive = o.receive ?? {};
3429
+ Class.intent = o.intent ?? {};
3430
+ Class.alter = o.alter ?? {};
3431
+ for (const name in o.views ?? {}) {
3432
+ const v = o.views[name];
3433
+ const { view, style } = isString(v) ? { view: v } : v;
3434
+ Class.views[name] = new View(name, view, style);
3435
+ }
3436
+ Class._rawProvide = o.provide ?? {};
3437
+ Class._rawLookup = o.lookup ?? [];
3438
+ Class.provide = {};
3439
+ Class.provideType = {};
3440
+ Class.lookup = {};
3441
+ Class.scope = null;
3442
+ Class.spec = o;
3443
+ Class.extra = {};
3444
+ for (const key of Object.keys(o)) if (!KNOWN_SPEC_KEYS.has(key)) Class.extra[key] = o[key];
3445
+ return Object.assign(Class, COMPONENT_METHODS);
3446
+ }
3447
+ var COMPONENT_METHODS = {
3251
3448
  compile(ParseContext2) {
3252
3449
  for (const name in this.views)
3253
3450
  this.views[name].compile(new ParseContext2(), this.scope, this.id);
3254
3451
  const ctx = this.views.main.ctx;
3255
3452
  for (const key in this._rawProvide) {
3256
3453
  if (isTypeName(key)) {
3257
- if (this._rawProvide[key] === "self") this.provideType[key] = this.Class;
3454
+ if (this._rawProvide[key] === "self") this.provideType[key] = this;
3258
3455
  continue;
3259
3456
  }
3260
3457
  const val = parseProvide(this._rawProvide[key], ctx);
3261
- if (val) this.provide[key] = new ProvideInfo(val);
3458
+ if (val) this.provide[key] = val;
3262
3459
  }
3263
3460
  for (const entry of this._rawLookup) {
3264
3461
  const name = isString(entry) ? entry : isString(entry?.name) ? entry.name : null;
3265
3462
  if (name === null) continue;
3266
3463
  const defStr = isString(entry?.default) ? entry.default : null;
3267
- this.lookup[name] = new LookupInfo(defStr === null ? null : parseField(defStr, ctx));
3464
+ this.lookup[name] = defStr === null ? null : parseField(defStr, ctx);
3268
3465
  }
3269
3466
  for (const key in this.lookup)
3270
3467
  if (this.provide[key] !== void 0)
3271
3468
  console.warn("name declared in both provide and lookup", this.name, key);
3272
- }
3469
+ },
3273
3470
  getView(name) {
3274
3471
  return this.views[name] ?? this.views.main;
3275
- }
3276
- getEventForId(id, name = "main") {
3277
- return this.getView(name).ctx.getEventForId(id);
3278
- }
3279
- getNodeForId(id, name = "main") {
3280
- return this.getView(name).ctx.getNodeForId(id);
3281
- }
3472
+ },
3473
+ getEventForId(id, viewName) {
3474
+ return this.getView(viewName).ctx.getEventForId(id);
3475
+ },
3476
+ getNodeForId(id, viewName) {
3477
+ return this.getView(viewName).ctx.getNodeForId(id);
3478
+ },
3282
3479
  compileStyle() {
3283
3480
  const { id, commonStyle, globalStyle, views } = this;
3284
3481
  const styles = commonStyle ? [`[data-cid="${id}"]{${commonStyle}}`] : [];
@@ -3291,170 +3488,38 @@ var Component = class {
3291
3488
  }
3292
3489
  };
3293
3490
 
3294
- // src/stack.js
3295
- var STOP = /* @__PURE__ */ Symbol("STOP");
3296
- var NEXT = /* @__PURE__ */ Symbol("NEXT");
3297
- var DEFAULT_ROUTE = ["dyn", "lex"];
3298
- function routeLookup(route, lex, dyn) {
3299
- for (let i = 0; i < route.length; i++) {
3300
- const leg = route[i];
3301
- if (leg === "dyn") {
3302
- const v = dyn();
3303
- if (v != null) return v;
3304
- } else if (leg === "lex") {
3305
- const v = lex();
3306
- if (v != null) return v;
3307
- } else {
3308
- console.warn("unknown lookup route leg", leg, '- expected "dyn" or "lex"');
3309
- }
3310
- }
3491
+ // src/oo.js
3492
+ var BAD_VALUE = /* @__PURE__ */ Symbol("BadValue");
3493
+ var COERCE_NONE = (_v) => null;
3494
+ var COERCE_MAP = (v) => {
3495
+ if (v instanceof Map) return new Map(v);
3496
+ if (Array.isArray(v) || isPlainObject(v))
3497
+ return new Map(Array.isArray(v) ? v : Object.entries(v));
3311
3498
  return null;
3312
- }
3313
- function lookup(chain, name, dv = null) {
3314
- let n = chain;
3315
- while (n !== null) {
3316
- const r = n[0].lookup(name);
3317
- if (r === STOP) return dv;
3318
- if (r !== NEXT) return r;
3319
- n = n[1];
3320
- }
3321
- return dv;
3322
- }
3323
- var BindFrame = class {
3324
- constructor(it, binds, isFrame) {
3325
- this.it = it;
3326
- this.binds = binds;
3327
- this.isFrame = isFrame;
3328
- }
3329
- lookup(name) {
3330
- const v = this.binds[name];
3331
- return v === void 0 ? this.isFrame ? STOP : NEXT : v;
3332
- }
3333
3499
  };
3334
- var ObjectFrame = class {
3335
- constructor(binds) {
3336
- this.binds = binds;
3337
- }
3338
- lookup(key) {
3339
- const v = this.binds[key];
3340
- return v === void 0 ? NEXT : v;
3341
- }
3500
+ var FIELD_TYPES = {
3501
+ any: [(_v) => true, COERCE_NONE, () => null],
3502
+ text: [(v) => typeof v === "string", (v) => v?.toString?.() ?? "", () => ""],
3503
+ int: [Number.isInteger, (v) => Number.isFinite(v) ? Math.trunc(v) : null, () => 0],
3504
+ float: [Number.isFinite, COERCE_NONE, () => 0],
3505
+ bool: [(v) => typeof v === "boolean", (v) => !!v, () => false],
3506
+ list: [Array.isArray, (v) => Array.isArray(v) ? [...v] : null, () => []],
3507
+ object: [isPlainObject, (v) => isPlainObject(v) ? { ...v } : null, () => ({})],
3508
+ map: [(v) => v instanceof Map, COERCE_MAP, () => /* @__PURE__ */ new Map()],
3509
+ set: [
3510
+ (v) => v instanceof Set,
3511
+ (v) => v instanceof Set || Array.isArray(v) ? new Set(v) : null,
3512
+ () => /* @__PURE__ */ new Set()
3513
+ ]
3342
3514
  };
3343
- function computeViewsId(views) {
3344
- let s = "";
3345
- let n = views;
3346
- while (n !== null) {
3347
- s += n[0];
3348
- n = n[1];
3349
- }
3350
- return s === "main" ? "" : s;
3351
- }
3352
- var Stack = class _Stack {
3353
- constructor(comps, it, binds, dynBinds, views, viewsId, ctx = null) {
3354
- this.comps = comps;
3355
- this.it = it;
3356
- this.binds = binds;
3357
- this.dynBinds = dynBinds;
3358
- this.views = views;
3359
- this.viewsId = viewsId;
3360
- this.ctx = ctx;
3361
- }
3362
- // Evaluate every provide the entered component publishes and push them as one
3363
- // dynBinds frame, keyed by NAME. Published types go in the same frame: a type name
3364
- // starts A-Z and a value name does not, so the two namespaces cannot collide and
3365
- // nearest-ancestor-wins falls out of frame order for both. No-op with no provides.
3366
- _pushProvides() {
3367
- const comp = this.comps.getCompFor(this.it);
3368
- if (comp == null) return this;
3369
- const { provide, provideType } = comp;
3370
- const dynObj = {};
3371
- let has2 = false;
3372
- for (const k in provide) {
3373
- dynObj[k] = provide[k].val.eval(this);
3374
- has2 = true;
3375
- }
3376
- for (const k in provideType) {
3377
- dynObj[k] = provideType[k];
3378
- has2 = true;
3379
- }
3380
- if (!has2) return this;
3381
- const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
3382
- const { comps, it, binds, views, viewsId, ctx } = this;
3383
- return new _Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
3384
- }
3385
- static root(comps, it, ctx) {
3386
- const binds = [new BindFrame(it, {}, true), null];
3387
- const dynBinds = [new ObjectFrame({}), null];
3388
- const views = ["main", null];
3389
- return new _Stack(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();
3390
- }
3391
- enter(it, bindings = {}, isFrame = true) {
3392
- const { comps, binds, dynBinds, views, viewsId, ctx } = this;
3393
- const newBinds = [new BindFrame(it, bindings, isFrame), binds];
3394
- const stack = new _Stack(comps, it, newBinds, dynBinds, views, viewsId, ctx);
3395
- return isFrame ? stack._pushProvides() : stack;
3396
- }
3397
- pushViewName(name) {
3398
- const { comps, it, binds, dynBinds, views, ctx } = this;
3399
- const newViews = [name, views];
3400
- return new _Stack(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
3401
- }
3402
- // Published types are stable per scope and would only churn the render cache, so
3403
- // the cache key covers values alone.
3404
- _pushDynBindValuesToArray(arr, comp) {
3405
- for (const k in comp.provide) arr.push(this.lookupDynamic(k));
3406
- for (const k in comp.lookup) arr.push(this.lookupDynamic(k));
3407
- }
3408
- // `*name`: the nearest binding above (including this component's own provides,
3409
- // pushed on entering it), else this component's declared default, else null.
3410
- lookupDynamic(name) {
3411
- const v = lookup(this.dynBinds, name);
3412
- if (v != null) return v;
3413
- const comp = this.comps.getCompFor(this.it);
3414
- return comp?.lookup[name]?.val?.eval(this) ?? null;
3415
- }
3416
- lookupBind(name) {
3417
- return lookup(this.binds, name);
3418
- }
3419
- lookupFieldRaw(name) {
3420
- return this.it[name] ?? null;
3421
- }
3422
- lookupMethod(name) {
3423
- const fn = this.it[name];
3424
- return fn instanceof Function ? fn.call(this.it) : null;
3425
- }
3426
- // The dispatched DOM event / drag info, read only by EventMemberVal's
3427
- // `e.<member>` handler args. Null outside a live event transaction.
3428
- lookupEvent() {
3429
- return this.ctx?.event ?? null;
3430
- }
3431
- lookupDragInfo() {
3432
- return this.ctx?.dragInfo ?? null;
3433
- }
3434
- getHandlerFor(name, key) {
3435
- return this.comps.getHandlerFor(this.it, name, key);
3436
- }
3437
- lookupBestView(views, defaultViewName) {
3438
- let n = this.views;
3439
- while (n !== null) {
3440
- const view = views[n[0]];
3441
- if (view !== void 0) return view;
3442
- n = n[1];
3443
- }
3444
- return views[defaultViewName];
3445
- }
3446
- };
3447
-
3448
- // src/oo.js
3449
- var BAD_VALUE = /* @__PURE__ */ Symbol("BadValue");
3450
- var nullCoercer = (v) => v;
3451
3515
  var Field = class {
3452
- constructor(type, name, typeCheck, coercer, defaultValue = null) {
3516
+ // `type` names an entry of FIELD_TYPES; `defaultValue` falls back to the type's own.
3517
+ constructor(type, name, defaultValue, [typeCheck, coercer, makeDefault] = FIELD_TYPES[type]) {
3453
3518
  this.type = type;
3454
3519
  this.name = name;
3455
3520
  this.typeCheck = typeCheck;
3456
3521
  this.coercer = coercer;
3457
- this.defaultValue = defaultValue;
3522
+ this.defaultValue = defaultValue === void 0 ? makeDefault() : defaultValue;
3458
3523
  }
3459
3524
  isValid(v) {
3460
3525
  return this.typeCheck(v);
@@ -3468,81 +3533,14 @@ var Field = class {
3468
3533
  return this.coerceOr(v, this.defaultValue);
3469
3534
  }
3470
3535
  };
3471
- var CHECK_TYPE_ANY = (_v) => true;
3472
- var CHECK_TYPE_INT = Number.isInteger;
3473
- var CHECK_TYPE_FLOAT = Number.isFinite;
3474
- var CHECK_TYPE_BOOL = (v) => typeof v === "boolean";
3475
- var CHECK_TYPE_STRING = (v) => typeof v === "string";
3476
- var CHECK_TYPE_LIST = Array.isArray;
3477
- var CHECK_TYPE_OBJECT = isPlainObject;
3478
- var CHECK_TYPE_MAP = (v) => v instanceof Map;
3479
- var CHECK_TYPE_SET = (v) => v instanceof Set;
3480
- var COERCE_NONE = (_v) => null;
3481
- var COERCE_BOOL = (v) => !!v;
3482
- var COERCE_STRING = (v) => v?.toString?.() ?? "";
3483
- var COERCE_INT = (v) => Number.isFinite(v) ? Math.trunc(v) : null;
3484
- var COERCE_LIST = (v) => Array.isArray(v) ? [...v] : null;
3485
- var COERCE_OBJECT = (v) => isPlainObject(v) ? { ...v } : null;
3486
- var COERCE_MAP = (v) => {
3487
- if (v instanceof Map) return new Map(v);
3488
- if (Array.isArray(v) || isPlainObject(v))
3489
- return new Map(Array.isArray(v) ? v : Object.entries(v));
3490
- return null;
3491
- };
3492
- var COERCE_SET = (v) => v instanceof Set || Array.isArray(v) ? new Set(v) : null;
3493
- var FieldBool = class extends Field {
3494
- constructor(name, defaultValue = false) {
3495
- super("bool", name, CHECK_TYPE_BOOL, COERCE_BOOL, defaultValue);
3496
- }
3497
- };
3498
- var FieldAny = class extends Field {
3499
- constructor(name, defaultValue = null) {
3500
- super("any", name, CHECK_TYPE_ANY, nullCoercer, defaultValue);
3501
- }
3502
- };
3503
- var FieldString = class extends Field {
3504
- constructor(name, defaultValue = "") {
3505
- super("text", name, CHECK_TYPE_STRING, COERCE_STRING, defaultValue);
3506
- }
3507
- };
3508
- var FieldInt = class extends Field {
3509
- constructor(name, defaultValue = 0) {
3510
- super("int", name, CHECK_TYPE_INT, COERCE_INT, defaultValue);
3511
- }
3512
- };
3513
- var FieldFloat = class extends Field {
3514
- constructor(name, defaultValue = 0) {
3515
- super("float", name, CHECK_TYPE_FLOAT, COERCE_NONE, defaultValue);
3516
- }
3517
- };
3518
- var metaOf = (v) => v?.constructor?.[COMPONENT] ?? v?.constructor?.getMetaClass?.();
3536
+ var metaOf = (v) => v?.constructor?.getMetaClass?.();
3519
3537
  var getTypeName = (v) => metaOf(v)?.name ?? null;
3520
3538
  var FieldComp = class extends Field {
3521
3539
  constructor(type, name, args) {
3522
- super(type, name, (v) => getTypeName(v) === type, nullCoercer, null);
3540
+ super(type, name, null, [(v) => getTypeName(v) === type, COERCE_NONE, () => null]);
3523
3541
  this.args = args;
3524
3542
  }
3525
3543
  };
3526
- var FieldList = class extends Field {
3527
- constructor(name, defaultValue = []) {
3528
- super("list", name, CHECK_TYPE_LIST, COERCE_LIST, defaultValue);
3529
- }
3530
- };
3531
- var FieldObject = class extends Field {
3532
- constructor(name, defaultValue = {}) {
3533
- super("object", name, CHECK_TYPE_OBJECT, COERCE_OBJECT, defaultValue);
3534
- }
3535
- };
3536
- var FieldMap = class extends Field {
3537
- constructor(name, defaultValue = /* @__PURE__ */ new Map()) {
3538
- super("map", name, CHECK_TYPE_MAP, COERCE_MAP, defaultValue);
3539
- }
3540
- };
3541
- var FieldSet = class extends Field {
3542
- constructor(name, defaultValue = /* @__PURE__ */ new Set()) {
3543
- super("set", name, CHECK_TYPE_SET, COERCE_SET, defaultValue);
3544
- }
3545
- };
3546
3544
  function mkCompField(field, scope, args) {
3547
3545
  const Comp = scope?.lookupComponent(field.type) ?? null;
3548
3546
  if (Comp === null)
@@ -3551,112 +3549,66 @@ function mkCompField(field, scope, args) {
3551
3549
  );
3552
3550
  return Comp?.make({ ...field.args, ...args }, { scope }) ?? null;
3553
3551
  }
3554
- var ClassBuilder = class {
3555
- constructor(name) {
3556
- this.name = name;
3557
- this.fields = {};
3558
- this.compFields = /* @__PURE__ */ new Set();
3559
- this._methods = {};
3560
- this._statics = {};
3561
- }
3562
- build() {
3563
- const { name, fields, compFields, _methods } = this;
3564
- const defaults = Object.fromEntries(
3565
- Object.entries(fields).map(([fieldName, field]) => [fieldName, field.defaultValue])
3566
- );
3567
- const Class = {
3568
- [name]: class {
3569
- constructor(values = {}) {
3570
- Object.assign(this, defaults, values);
3571
- }
3572
- }
3573
- }[name];
3574
- Class[DRAFTABLE] = true;
3575
- Object.assign(Class.prototype, _methods);
3576
- const metaClass = { fields, name, methods: _methods };
3577
- Object.assign(
3578
- Class,
3579
- {
3580
- getMetaClass: () => metaClass,
3581
- make(inArgs = {}, opts = {}) {
3582
- const args = {};
3583
- const scope = opts.scope ?? this[COMPONENT]?.scope ?? this.scope;
3584
- for (const key in inArgs) {
3585
- const field = fields[key];
3586
- if (compFields.has(key)) args[key] = mkCompField(field, scope, inArgs[key]);
3587
- else if (field === void 0)
3588
- console.warn("extra argument to constructor:", name, key, inArgs);
3589
- else args[key] = field.coerceOrDefault(inArgs[key]);
3590
- }
3591
- for (const key of compFields)
3592
- if (args[key] === void 0) args[key] = mkCompField(fields[key], scope, inArgs[key]);
3593
- return freeze(new this(args), true);
3594
- }
3595
- },
3596
- this._statics
3597
- );
3598
- return Class;
3599
- }
3600
- methods(proto) {
3601
- for (const k in proto) this._methods[k] = proto[k];
3602
- }
3603
- statics(proto) {
3604
- for (const k in proto) this._statics[k] = proto[k];
3605
- }
3606
- addField(name, dval, FieldCls) {
3607
- const field = new FieldCls(name, dval);
3608
- this.fields[name] = field;
3609
- return field;
3610
- }
3611
- addCompField(name, type, args) {
3612
- const field = new FieldComp(type, name, args);
3613
- this.compFields.add(name);
3614
- this.fields[name] = field;
3615
- return field;
3616
- }
3617
- };
3618
- var FIELD_CLASS = /* @__PURE__ */ Symbol.for("tutuca.fieldClass");
3619
- var fieldsByTypeName = {
3620
- text: FieldString,
3621
- int: FieldInt,
3622
- float: FieldFloat,
3623
- bool: FieldBool,
3624
- list: FieldList,
3625
- object: FieldObject,
3626
- map: FieldMap,
3627
- set: FieldSet,
3628
- any: FieldAny
3629
- };
3630
- function fieldFromDescriptor(name, value) {
3631
- const FieldCls = fieldsByTypeName[value.type] ?? FieldAny;
3632
- const probe = new FieldCls(name);
3633
- return [FieldCls, probe.coerceOr(value.defaultValue, probe.defaultValue)];
3552
+ function fieldFromSpec(name, value) {
3553
+ const type = typeof value;
3554
+ if (type === "string") return new Field("text", name, value);
3555
+ if (type === "number") return new Field("float", name, value);
3556
+ if (type === "boolean") return new Field("bool", name, value);
3557
+ if (Array.isArray(value)) return new Field("list", name, [...value]);
3558
+ if (value instanceof Set) return new Field("set", name, new Set(value));
3559
+ if (value instanceof Map) return new Field("map", name, new Map(value));
3560
+ if (value?.type && Object.hasOwn(value, "defaultValue")) {
3561
+ const t = value.type in FIELD_TYPES ? value.type : "any";
3562
+ const probe = new Field(t, name);
3563
+ return new Field(t, name, probe.coerceOr(value.defaultValue, probe.defaultValue));
3564
+ }
3565
+ if (value?.component && value?.args !== void 0)
3566
+ return new FieldComp(value.component, name, value.args);
3567
+ if (isPlainObject(value)) return new Field("object", name, { ...value });
3568
+ return new Field("any", name, value);
3634
3569
  }
3635
- function classFromData(name, { fields = {}, methods, statics }) {
3636
- const b = new ClassBuilder(name);
3637
- for (const field in fields) {
3638
- const value = fields[field];
3639
- const type = typeof value;
3640
- if (type === "string") b.addField(field, value, FieldString);
3641
- else if (type === "number") b.addField(field, value, FieldFloat);
3642
- else if (type === "boolean") b.addField(field, value, FieldBool);
3643
- else if (Array.isArray(value)) b.addField(field, [...value], FieldList);
3644
- else if (value instanceof Set) b.addField(field, new Set(value), FieldSet);
3645
- else if (value instanceof Map) b.addField(field, new Map(value), FieldMap);
3646
- else if (value?.type && Object.hasOwn(value, "defaultValue")) {
3647
- const [FieldCls, dval] = fieldFromDescriptor(field, value);
3648
- b.addField(field, dval, FieldCls);
3649
- } else if (value?.component && value?.args !== void 0)
3650
- b.addCompField(field, value.component, value.args);
3651
- else if (isPlainObject(value)) b.addField(field, { ...value }, FieldObject);
3652
- else {
3653
- const FieldCls = value?.[FIELD_CLASS] ?? FieldAny;
3654
- b.addField(field, value, FieldCls);
3570
+ function classFromData(name, { fields: spec = {}, methods = {}, statics = {} }) {
3571
+ const fields = {};
3572
+ for (const key in spec) fields[key] = fieldFromSpec(key, spec[key]);
3573
+ const defaults = Object.fromEntries(
3574
+ Object.entries(fields).map(([key, field]) => [key, field.defaultValue])
3575
+ );
3576
+ const Class = {
3577
+ [name]: class {
3578
+ constructor(values = {}) {
3579
+ Object.assign(this, defaults, values);
3580
+ }
3655
3581
  }
3656
- }
3657
- if (methods) b.methods(methods);
3658
- if (statics) b.statics(statics);
3659
- return b.build();
3582
+ }[name];
3583
+ Class[DRAFTABLE] = true;
3584
+ Object.assign(Class.prototype, methods);
3585
+ Object.assign(
3586
+ Class,
3587
+ {
3588
+ fields,
3589
+ methods,
3590
+ // The documented way for instance code to reach its class's fields
3591
+ // (`this.constructor.getMetaClass().fields.x.defaultValue`).
3592
+ getMetaClass: () => Class,
3593
+ make(inArgs = {}, opts = {}) {
3594
+ const args = {};
3595
+ const scope = opts.scope ?? this.scope;
3596
+ for (const key in inArgs) {
3597
+ const field = fields[key];
3598
+ if (field === void 0)
3599
+ console.warn("extra argument to constructor:", name, key, inArgs);
3600
+ else if (field instanceof FieldComp) args[key] = mkCompField(field, scope, inArgs[key]);
3601
+ else args[key] = field.coerceOrDefault(inArgs[key]);
3602
+ }
3603
+ for (const key in fields)
3604
+ if (fields[key] instanceof FieldComp && args[key] === void 0)
3605
+ args[key] = mkCompField(fields[key], scope, void 0);
3606
+ return freeze(new this(args), true);
3607
+ }
3608
+ },
3609
+ statics
3610
+ );
3611
+ return Class;
3660
3612
  }
3661
3613
  function validateDraftFields(current2, draft) {
3662
3614
  const meta = metaOf(current2);
@@ -3672,14 +3624,11 @@ function validateDraftFields(current2, draft) {
3672
3624
  }
3673
3625
  }
3674
3626
  }
3675
- var META_KEYS = "name id fields methods views receive intent alter provide provideType lookup spec extra commonStyle globalStyle scope _rawProvide _rawLookup".split(
3676
- " "
3677
- );
3678
3627
  var RESERVED_COMPONENT_STATICS = /* @__PURE__ */ new Set([
3679
- ...META_KEYS,
3680
- "make",
3681
- "getMetaClass",
3682
- ...Object.getOwnPropertyNames(Component.prototype)
3628
+ ..."name id fields methods views receive intent alter provide provideType lookup spec extra commonStyle globalStyle scope _rawProvide _rawLookup make getMetaClass".split(
3629
+ " "
3630
+ ),
3631
+ ...Object.keys(COMPONENT_METHODS)
3683
3632
  ]);
3684
3633
  function assertNoReservedComponentStatics(statics = {}) {
3685
3634
  for (const name in statics) {
@@ -3688,32 +3637,10 @@ function assertNoReservedComponentStatics(statics = {}) {
3688
3637
  }
3689
3638
  }
3690
3639
  }
3691
- Component.fromSpec = (opts) => {
3640
+ function component(opts) {
3692
3641
  assertNoReservedComponentStatics(opts.statics);
3693
- const Class = classFromData(opts.name, opts);
3694
- const comp = new Component(Class, opts);
3695
- const metaClass = Class.getMetaClass();
3696
- comp.fields = metaClass.fields;
3697
- comp.methods = metaClass.methods;
3698
- Class.getMetaClass = () => comp;
3699
- Class[COMPONENT] = comp;
3700
- for (const key of META_KEYS)
3701
- if (!Object.hasOwn(Class, key))
3702
- Object.defineProperty(Class, key, {
3703
- get() {
3704
- return comp[key];
3705
- },
3706
- set(v) {
3707
- comp[key] = v;
3708
- },
3709
- configurable: true
3710
- });
3711
- for (const key of Object.getOwnPropertyNames(Component.prototype))
3712
- if (key !== "constructor" && !Object.hasOwn(Class, key))
3713
- Class[key] = (...args) => comp[key](...args);
3714
- return Class;
3715
- };
3716
- var component = (opts) => Component.fromSpec(opts);
3642
+ return initComponent(classFromData(opts.name ?? "UnkComp", opts), opts);
3643
+ }
3717
3644
 
3718
3645
  // src/transactor.js
3719
3646
  var State = class {
@@ -3754,14 +3681,7 @@ var Transactor = class {
3754
3681
  // carry: kind, name, args, path, pathKeys, targetPath, handler, handlerName,
3755
3682
  // matched, before, after, parent, timestamp. Purely observational.
3756
3683
  observe(cb) {
3757
- this._observers.push(cb);
3758
- return () => {
3759
- const i = this._observers.indexOf(cb);
3760
- if (i !== -1) this._observers.splice(i, 1);
3761
- };
3762
- }
3763
- _emit(record) {
3764
- for (const cb of this._observers) cb(record);
3684
+ return subscribe(this._observers, cb);
3765
3685
  }
3766
3686
  // Record a refusal: a dispatch the runtime could not carry out. Kinds are
3767
3687
  // open strings; today the runtime raises NO_HANDLER (a receive name with no
@@ -3780,20 +3700,16 @@ var Transactor = class {
3780
3700
  // Subscribe to refusal records as they happen. Returns an unsubscribe fn,
3781
3701
  // like observe().
3782
3702
  observeRefusals(cb) {
3783
- this._refusalObservers.push(cb);
3784
- return () => {
3785
- const i = this._refusalObservers.indexOf(cb);
3786
- if (i !== -1) this._refusalObservers.splice(i, 1);
3787
- };
3703
+ return subscribe(this._refusalObservers, cb);
3788
3704
  }
3789
3705
  // Build and dispatch one observer record. Pins field-resolved keys (e.g. a
3790
3706
  // `.a[.selId]` render target reconstructed from a DOM event) against the root the
3791
3707
  // handler read, so pathKeys carries concrete keys instead of dropping the dynamic
3792
3708
  // step. No-op when nobody is observing.
3793
- _emitRecord(root, { kind, name, args, path, targetPath, handler, handlerName, matched, before, after, parent }) {
3709
+ _emitRecord(root, { kind, name, args, path: path2, targetPath, handler, handlerName, matched, before, after, parent }) {
3794
3710
  if (this._observers.length === 0) return;
3795
- const pinned = path.pinKeys(root);
3796
- this._emit({
3711
+ const pinned = path2.pinKeys(root);
3712
+ const record = {
3797
3713
  kind,
3798
3714
  name,
3799
3715
  args: args ?? null,
@@ -3807,14 +3723,14 @@ var Transactor = class {
3807
3723
  after,
3808
3724
  parent,
3809
3725
  timestamp: Date.now()
3810
- });
3726
+ };
3727
+ for (const cb of this._observers) cb(record);
3811
3728
  }
3812
3729
  // The observer record for a settled transaction. The resolved handler
3813
3730
  // (`_resolvedHandler`/`_matched`) and per-leaf before/after (`_before`/`_after`)
3814
3731
  // were captured while the handler ran (see callHandler / Transaction.run).
3815
3732
  _emitTransaction(transaction, root) {
3816
3733
  if (this._observers.length === 0) return;
3817
- if (transaction._resolvedHandler === void 0) return;
3818
3734
  this._emitRecord(root, {
3819
3735
  kind: transaction.observeKind,
3820
3736
  name: transaction.observeName,
@@ -3828,33 +3744,29 @@ var Transactor = class {
3828
3744
  parent: transaction.parentTransaction
3829
3745
  });
3830
3746
  }
3831
- // Make `child` a tracked unit of `parent`'s subtree: the parent's completion stays open
3832
- // until the child's *whole* subtree settles. Tracking happens at dispatch time — during
3833
- // the parent's handler or afterTransaction — while the parent's self-unit is still held,
3834
- // so the parent counter can't reach zero before the child is registered. Returns `child`.
3835
- _link(child, parent) {
3836
- if (parent) {
3837
- const release = parent.completion.track();
3838
- child.completion.whenSubtreeSettled().then(release);
3839
- }
3840
- return child;
3841
- }
3842
3747
  // `origin` is where the message came FROM, pinned now for the same reason an
3843
3748
  // intent's answerPath is: a reply must reach the sender that asked even if a key
3844
3749
  // moved while the message was in flight. Null when nobody is waiting — a host
3845
3750
  // sendAtRoot or a view's own `@on.*` — and ctx.sendReply refuses on that.
3846
- pushSend(path, name, args = [], parent = null, origin = null) {
3847
- const t = new SendEvent(path, this, name, args, parent);
3848
- t.origin = origin === null ? null : origin.toTransactionPath().pinKeys(this.state.val);
3751
+ //
3752
+ // The parent's subtree stays open until this message's whole subtree settles. The
3753
+ // unit is taken now, at dispatch — during the parent's handler or afterTransaction,
3754
+ // while the parent's self-unit is still held — so its counter can't reach zero first.
3755
+ pushSend(path2, name, args = [], parent = null, origin = null, txnPath = null) {
3756
+ const t = new SendEvent(path2, this, name, args, parent);
3757
+ t.txnPath = txnPath;
3758
+ t.origin = origin;
3759
+ t.originPinned = origin === null ? null : origin.toTransactionPath().pinKeys(this.state.val);
3849
3760
  this.pushTransaction(t);
3850
- return this._link(t, parent);
3761
+ if (parent) t.completion.carry(parent.completion.track());
3762
+ return t;
3851
3763
  }
3852
3764
  // Raise an intent: a job the sender did not address, walked along a route until
3853
3765
  // something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
3854
3766
  // `opts.livePath` opts out of pinning the answer's destination keys.
3855
- pushIntent(path, name, args = [], opts = {}, parent = null) {
3767
+ pushIntent(path2, name, args = [], opts = {}, parent = null) {
3856
3768
  const release = parent ? parent.completion.track() : null;
3857
- const walk = new IntentWalk(this, path, name, args, opts, parent, release);
3769
+ const walk = new IntentWalk(this, path2, name, args, opts, parent, release);
3858
3770
  walk.advance();
3859
3771
  return walk;
3860
3772
  }
@@ -3871,7 +3783,7 @@ var Transactor = class {
3871
3783
  return this.transactions.length > 0;
3872
3784
  }
3873
3785
  transactNext() {
3874
- if (this.hasPendingTransactions) this.transact(this.transactions.shift());
3786
+ this.transact(this.transactions.shift());
3875
3787
  }
3876
3788
  transact(transaction) {
3877
3789
  try {
@@ -3884,20 +3796,31 @@ var Transactor = class {
3884
3796
  } else console.warn("undefined new state", { curState, transaction });
3885
3797
  } finally {
3886
3798
  transaction.ensureWalkAdvanced?.();
3887
- transaction._completion?.ensureSelfSettled();
3888
- transaction._completion?.releaseSelf();
3799
+ transaction._completion?.finish();
3889
3800
  }
3890
3801
  }
3891
- transactInputNow(path, event, eventHandler, dragInfo) {
3892
- this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
3802
+ transactInputNow(path2, event, eventHandler, dragInfo) {
3803
+ this.transact(new InputEvent(path2, event, eventHandler, this, dragInfo));
3893
3804
  }
3894
3805
  };
3895
3806
  function nullHandler() {
3896
3807
  return this;
3897
3808
  }
3809
+ function subscribe(list, cb) {
3810
+ list.push(cb);
3811
+ return () => {
3812
+ const i = list.indexOf(cb);
3813
+ if (i !== -1) list.splice(i, 1);
3814
+ };
3815
+ }
3816
+ function warnUnknownLeg(what, leg) {
3817
+ console.warn(`unknown ${what} route leg`, leg, '- expected "dyn" or "lex"');
3818
+ return null;
3819
+ }
3898
3820
  var Transaction = class {
3899
- constructor(path, transactor, parentTransaction = null) {
3900
- this.path = path;
3821
+ constructor(path2, transactor, parentTransaction = null) {
3822
+ this.path = path2;
3823
+ this.txnPath = null;
3901
3824
  this.transactor = transactor;
3902
3825
  this.parentTransaction = parentTransaction;
3903
3826
  this._completion = null;
@@ -3917,39 +3840,41 @@ var Transaction = class {
3917
3840
  whenSubtreeSettled() {
3918
3841
  return this.completion.whenSubtreeSettled();
3919
3842
  }
3920
- afterTransaction() {
3921
- }
3922
- // Ending a walk and forwarding belong to the two dispatch buckets; the base warns
3923
- // rather than throwing, so a stray call from the wrong place is a message and not a
3924
- // crash. `walk` is undefined here, which is how ctx.reply/ctx.fail tell the two apart.
3925
- stop() {
3926
- warnNotIntent("stop");
3927
- }
3928
- forward(_opts) {
3929
- console.warn('ctx.forward() needs a "receive" or "intent" handler - ignored');
3930
- }
3931
- // The kind reported to observers (see Transactor.observe); null on the base.
3932
- get observeKind() {
3933
- return null;
3934
- }
3935
- // The name reported to observers; null on the base. Overridden by NameArgs (the
3936
- // dispatched message name) and InputEvent (the DOM event type). Kept separate from
3937
- // `name`/`ctx.name` so it can't change handler-visible behavior.
3938
- get observeName() {
3939
- return null;
3940
- }
3843
+ // Every transaction kind defines `observeKind` / `observeName` (what observers see;
3844
+ // kept apart from `name`/`ctx.name` so they can't change handler-visible behavior),
3845
+ // `getHandlerAndArgs(root, instance, comps)`, and the three `_forward*` hooks below.
3941
3846
  callHandler(root, instance, draft, comps) {
3942
3847
  const [handler, args] = this.getHandlerAndArgs(root, instance, comps);
3943
3848
  this._resolvedHandler = handler;
3944
3849
  return handler.apply(instance, [draft, ...args]);
3945
3850
  }
3946
- getHandlerAndArgs(_root, _instance, _comps) {
3947
- return null;
3851
+ // `forward` is where a name can LEAVE the component: the message being handled is
3852
+ // raised again as an INTENT under the same name (`_forwardName()`) with the same
3853
+ // arguments unless `opts.args` says otherwise, from the position `_forwardPath()`
3854
+ // names. Deferred to afterTransaction so the state the handler produced is in place
3855
+ // before the walk starts. (`IntentEvent` overrides both: in an intent body, forward
3856
+ // amends the walk that is already running.)
3857
+ forward(opts) {
3858
+ this._forward = opts ?? {};
3948
3859
  }
3949
- // The path used to apply the mutation. Teleports dynamic-var renders so it lands on
3950
- // the data's real location (the dispatch `this.path` keeps intermediates).
3860
+ afterTransaction() {
3861
+ const f = this._forward;
3862
+ if (f === void 0) return;
3863
+ this._forward = void 0;
3864
+ const name = this._forwardName();
3865
+ if (name === void 0) {
3866
+ this.transactor.refuse("FORWARD_NO_NAME", {});
3867
+ return;
3868
+ }
3869
+ const { args = this._forwardArgs(), ...rest } = f;
3870
+ this.transactor.pushIntent(this._forwardPath(), name, args, rest, this);
3871
+ }
3872
+ // The path used to apply the mutation: the ACTIVE frame of the dispatch path, so
3873
+ // an event inside a `<x render="*name">` subtree updates the value where it
3874
+ // really lives (the dispatch `this.path` keeps the visual callers, for bubbling).
3875
+ // `txnPath`, when pinned by the sender, wins.
3951
3876
  getTransactionPath() {
3952
- return this.path.toTransactionPath().compact();
3877
+ return this.txnPath ?? this.path.toTransactionPath();
3953
3878
  }
3954
3879
  run(curRoot, comps) {
3955
3880
  const txnPath = this.getTransactionPath();
@@ -3966,19 +3891,27 @@ var Transaction = class {
3966
3891
  }
3967
3892
  };
3968
3893
  var InputEvent = class extends Transaction {
3969
- constructor(path, e, handler, transactor, dragInfo) {
3970
- super(path, transactor);
3894
+ constructor(path2, e, handler, transactor, dragInfo) {
3895
+ super(path2, transactor);
3971
3896
  this.e = e;
3972
3897
  this.handler = handler;
3973
3898
  this.dragInfo = dragInfo;
3974
3899
  this._dispatchPath = null;
3975
3900
  }
3976
- // Frame steps removed, DynStep + one step per crossed component kept: bubbling
3977
- // it visits every component (including intermediates of a dynamic-var render).
3901
+ // Frame-only steps removed, one step per crossed component kept — inside every
3902
+ // continuation frame independently, so bubbling it visits every component and
3903
+ // then returns to the caller that wrote the `*name`.
3978
3904
  get dispatchPath() {
3979
3905
  this._dispatchPath ??= this.path.compact();
3980
3906
  return this._dispatchPath;
3981
3907
  }
3908
+ // Only a DOM event's path carries frame-only bind steps (they replay the handler's
3909
+ // arguments, but address no state); the compacted path is where the mutation lands,
3910
+ // so a handler inside @each still updates the component that owns the view. Every
3911
+ // other transaction is dispatched at an already-compacted position.
3912
+ getTransactionPath() {
3913
+ return this.dispatchPath.toTransactionPath();
3914
+ }
3982
3915
  // A DOM event is an ADDRESSED message like any other — it reaches the component that
3983
3916
  // owns the view and stops. It keeps its own class only because it resolves its handler
3984
3917
  // from the compiled view and runs synchronously, not because it is a different channel.
@@ -3988,47 +3921,36 @@ var InputEvent = class extends Transaction {
3988
3921
  get observeName() {
3989
3922
  return this.e?.type ?? null;
3990
3923
  }
3991
- // A view's name is a message. `forward` is where that name can LEAVE the component:
3992
- // it becomes an intent with the same name and the same arguments, so a view that says
3993
- // `@on.click="saveDraft .text"` never has to change when an ancestor takes the job over.
3994
- // (A `$method` handler has a name too, so forwarding one raises an intent under it.)
3995
- forward(opts) {
3996
- this._forward = opts ?? {};
3997
- }
3998
- afterTransaction() {
3999
- const f = this._forward;
4000
- if (f === void 0) return;
4001
- this._forward = void 0;
4002
- const { args = this._handlerArgs ?? [], ...rest } = f;
3924
+ // A view's name is a message, and forwarding it means a view that says
3925
+ // `@on.click="saveDraft .text"` never has to change when an ancestor takes the job
3926
+ // over. (A `$method` handler has a name too, so forwarding one raises an intent under
3927
+ // it.) `handler` is a NodeEvent, whose own `name` is the DOM event type; the MESSAGE
3928
+ // name is the one the view wrote, on the handler call it wraps.
3929
+ _forwardName() {
4003
3930
  const hv = this.handler?.handlerCall?.handlerVal ?? this.handler?.handlerVal;
4004
- const name = hv?.name;
4005
- if (name === void 0) {
4006
- this.transactor.refuse("FORWARD_NO_NAME", {});
4007
- return;
4008
- }
4009
- this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
3931
+ return hv?.name;
3932
+ }
3933
+ _forwardArgs() {
3934
+ return this._handlerArgs ?? [];
3935
+ }
3936
+ _forwardPath() {
3937
+ return this.dispatchPath;
4010
3938
  }
4011
3939
  getHandlerAndArgs(root, _instance, comps) {
4012
- const stack = this.path.toTransactionPath().buildStack(Stack.root(comps, root, this));
3940
+ const stack = this.path.buildStack(Stack.root(comps, root, this));
4013
3941
  const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
4014
3942
  this._handlerArgs = [...args];
4015
- const path = this.dispatchPath;
4016
- args.push(new EventContext(path, this.transactor, this));
3943
+ const path2 = this.dispatchPath;
3944
+ args.push(new EventContext(path2, this.transactor, this));
4017
3945
  return [handler, args];
4018
3946
  }
4019
- // The dispatched DOM event, read only through `e.<member>` handler args
4020
- // (src/value.js EventMemberVal via Stack.lookupEvent). There is no bare
4021
- // implicit-name vocabulary anymore: every arg carries a sigil.
4022
- get event() {
4023
- return this.e;
4024
- }
4025
3947
  };
4026
3948
  var NameArgsTransaction = class extends Transaction {
4027
- constructor(path, transactor, name, args, parentTransaction) {
4028
- super(path, transactor, parentTransaction);
3949
+ constructor(path2, transactor, name, args, parentTransaction) {
3950
+ super(path2, transactor, parentTransaction);
4029
3951
  this.name = name;
4030
3952
  this.args = args;
4031
- this.targetPath = path;
3953
+ this.targetPath = path2;
4032
3954
  }
4033
3955
  handlerProp = null;
4034
3956
  // NameArgs verbs map their handler bucket straight to the observed kind:
@@ -4039,6 +3961,17 @@ var NameArgsTransaction = class extends Transaction {
4039
3961
  get observeName() {
4040
3962
  return this.name;
4041
3963
  }
3964
+ // `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
3965
+ // intent, keeping its name and payload.
3966
+ _forwardName() {
3967
+ return this.name;
3968
+ }
3969
+ _forwardArgs() {
3970
+ return this.args;
3971
+ }
3972
+ _forwardPath() {
3973
+ return this.path;
3974
+ }
4042
3975
  getHandlerForName(comp) {
4043
3976
  const handlers = comp?.[this.handlerProp];
4044
3977
  const exact = handlers?.[this.name];
@@ -4069,24 +4002,11 @@ var SendEvent = class extends NameArgsTransaction {
4069
4002
  get observeKind() {
4070
4003
  return this._isAnswer ? "answer" : "receive";
4071
4004
  }
4072
- // `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
4073
- // intent, keeping its name and payload. This is what lets a view's name leave the
4074
- // component without the view changing.
4075
- forward(opts) {
4076
- this._forward = opts ?? {};
4077
- }
4078
- afterTransaction() {
4079
- const f = this._forward;
4080
- if (f === void 0) return;
4081
- this._forward = void 0;
4082
- const { args = this.args, ...rest } = f;
4083
- this.transactor.pushIntent(this.path, this.name, args, rest, this);
4084
- }
4085
4005
  };
4086
4006
  var IntentEvent = class extends NameArgsTransaction {
4087
4007
  handlerProp = "intent";
4088
- constructor(path, transactor, walk) {
4089
- super(path, transactor, walk.name, walk.args, walk.parent);
4008
+ constructor(path2, transactor, walk) {
4009
+ super(path2, transactor, walk.name, walk.args, walk.parent);
4090
4010
  this.walk = walk;
4091
4011
  this.targetPath = walk.origin;
4092
4012
  }
@@ -4095,9 +4015,6 @@ var IntentEvent = class extends NameArgsTransaction {
4095
4015
  forward(opts) {
4096
4016
  this.walk.amend(opts, this.path);
4097
4017
  }
4098
- stop() {
4099
- this.walk.finish(null, null);
4100
- }
4101
4018
  afterTransaction() {
4102
4019
  this.ensureWalkAdvanced();
4103
4020
  }
@@ -4113,17 +4030,17 @@ var PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
4113
4030
  var REFUSAL_RING_CAP = 200;
4114
4031
  var INTENT_DEPTH = 64;
4115
4032
  var IntentWalk = class {
4116
- constructor(transactor, path, name, args, opts, parent, release) {
4033
+ constructor(transactor, path2, name, args, opts, parent, release) {
4117
4034
  this.transactor = transactor;
4118
4035
  this.name = name;
4119
4036
  this.args = args;
4120
4037
  this.route = opts?.route ?? DEFAULT_ROUTE;
4121
4038
  this.parent = parent;
4122
4039
  this.release = release;
4123
- this.origin = path;
4124
- this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
4040
+ this.origin = path2;
4041
+ this.answerPath = opts?.livePath ? null : path2.toTransactionPath().pinKeys(transactor.state.val);
4125
4042
  this.legIndex = 0;
4126
- this.dynAt = path;
4043
+ this.dynAt = path2;
4127
4044
  this.hops = 0;
4128
4045
  this.ended = false;
4129
4046
  }
@@ -4135,7 +4052,7 @@ var IntentWalk = class {
4135
4052
  while (this.legIndex < this.route.length) {
4136
4053
  const leg = this.route[this.legIndex];
4137
4054
  if (leg === "dyn") {
4138
- if (this.dynAt.steps.length === 0) {
4055
+ if (!this.dynAt.canPop()) {
4139
4056
  this.legIndex++;
4140
4057
  continue;
4141
4058
  }
@@ -4147,7 +4064,7 @@ var IntentWalk = class {
4147
4064
  this.legIndex++;
4148
4065
  return this._tryLex();
4149
4066
  }
4150
- console.warn("unknown intent route leg", leg, '- expected "dyn" or "lex"');
4067
+ warnUnknownLeg("intent", leg);
4151
4068
  this.legIndex++;
4152
4069
  }
4153
4070
  this.exhaust("noHandler");
@@ -4232,11 +4149,11 @@ var IntentWalk = class {
4232
4149
  if (this.ended) return;
4233
4150
  this.ended = true;
4234
4151
  if (name === null) return this.release?.();
4235
- const path = this.answerPath ?? this.origin;
4236
- const t = new SendEvent(path, this.transactor, name, args, this.parent);
4152
+ const t = new SendEvent(this.origin, this.transactor, name, args, this.parent);
4153
+ t.txnPath = this.answerPath;
4237
4154
  t._isAnswer = true;
4238
4155
  this.transactor.pushTransaction(t);
4239
- if (this.release) t.completion.whenSubtreeSettled().then(this.release);
4156
+ if (this.release) t.completion.carry(this.release);
4240
4157
  }
4241
4158
  };
4242
4159
  var Completion = class {
@@ -4244,12 +4161,12 @@ var Completion = class {
4244
4161
  this.val = void 0;
4245
4162
  this.selfSettled = false;
4246
4163
  this.subtreeSettled = false;
4247
- this.pending = 1;
4164
+ this.pending = 0;
4248
4165
  this._selfResolve = null;
4249
4166
  this._selfPromise = null;
4250
4167
  this._subtreeResolve = null;
4251
4168
  this._subtreePromise = null;
4252
- this._selfReleased = false;
4169
+ this._releaseSelf = this.track();
4253
4170
  }
4254
4171
  whenSettled() {
4255
4172
  if (this.selfSettled) return Promise.resolve(this.val);
@@ -4272,38 +4189,38 @@ var Completion = class {
4272
4189
  this.val = val;
4273
4190
  this._selfResolve?.(val);
4274
4191
  }
4275
- // Settle self even when no handler produced a value (undefined-state / throw paths).
4276
- ensureSelfSettled() {
4277
- if (!this.selfSettled) this.markSelfSettled(this.val);
4192
+ // The transaction is done processing: self is settled (with the value the handler
4193
+ // produced, or none on the undefined-state / throw paths) and the self-unit goes.
4194
+ finish() {
4195
+ this.markSelfSettled(this.val);
4196
+ this._releaseSelf();
4278
4197
  }
4279
- // Register an outstanding unit; returns a one-shot release.
4198
+ // Register an outstanding unit; returns its one-shot release. The subtree settles
4199
+ // when the last unit is released.
4280
4200
  track() {
4281
4201
  this.pending++;
4282
4202
  let done = false;
4283
4203
  return () => {
4284
4204
  if (done) return;
4285
4205
  done = true;
4286
- this._release();
4206
+ if (--this.pending === 0) {
4207
+ this.subtreeSettled = true;
4208
+ this._subtreeResolve?.(this.val);
4209
+ }
4287
4210
  };
4288
4211
  }
4289
- releaseSelf() {
4290
- if (this._selfReleased) return;
4291
- this._selfReleased = true;
4292
- this._release();
4293
- }
4294
- _release() {
4295
- if (--this.pending === 0) {
4296
- this.subtreeSettled = true;
4297
- this._subtreeResolve?.(this.val);
4298
- }
4212
+ // Hand a unit held elsewhere to this subtree: `release` runs once this transaction
4213
+ // and everything it spawned have settled.
4214
+ carry(release) {
4215
+ this.whenSubtreeSettled().then(release);
4299
4216
  }
4300
4217
  };
4301
4218
  var Dispatcher = class {
4302
- constructor(path, transactor, parentTransaction, root = transactor.state.val) {
4303
- this.path = path;
4219
+ constructor(path2, transactor, parentTransaction) {
4220
+ this.path = path2;
4304
4221
  this.transactor = transactor;
4305
4222
  this.parent = parentTransaction;
4306
- this.root = root;
4223
+ this.root = transactor.state.val;
4307
4224
  }
4308
4225
  // Walk the component instances on this ctx's path, leaf→root, calling
4309
4226
  // callback(Component, instance). Return false from the callback to stop early.
@@ -4328,28 +4245,37 @@ var Dispatcher = class {
4328
4245
  // replayed; provides are pushed on every component frame either way, so a provide
4329
4246
  // that reads a loop binding is the one case this cannot reproduce faithfully.
4330
4247
  _stack() {
4331
- this._stackMemo ??= this.path.toTransactionPath().buildStack(Stack.root(this.transactor.comps, this.root, this.parent));
4248
+ this._stackMemo ??= this.path.buildStack(
4249
+ Stack.root(this.transactor.comps, this.root, this.parent)
4250
+ );
4332
4251
  return this._stackMemo;
4333
4252
  }
4334
4253
  // Resolve a name the way the renderer would. `opts.route` takes the same legs in
4335
4254
  // the same spelling as `ctx.intent` — `["dyn"]` the render ancestry, `["lex"]` the
4336
4255
  // registration scope, default both.
4256
+ // Legs are tried in route order, like the intent walker's; an unknown leg warns and
4257
+ // is skipped, and an empty route resolves to null.
4337
4258
  lookup(name, opts) {
4338
- return routeLookup(
4339
- opts?.route ?? DEFAULT_ROUTE,
4340
- () => this._lookupLex(name),
4341
- () => this._stack()?.lookupDynamic(name) ?? null
4342
- );
4259
+ for (const leg of opts?.route ?? DEFAULT_ROUTE) {
4260
+ const v = leg === "dyn" ? this._stack()?.lookupDynamic(name) ?? null : leg === "lex" ? this._lookupLex(name) : warnUnknownLeg("lookup", leg);
4261
+ if (v != null) return v;
4262
+ }
4263
+ return null;
4343
4264
  }
4344
4265
  // The `lex` leg without a Stack: the scope of the component whose handler is
4345
- // running, which is the leaf of this ctx's path.
4266
+ // running, which is the leaf of this ctx's path. A type name resolves to the
4267
+ // component registered under it; a value name to a path registered under it
4268
+ // (see ComponentStack.registerPaths), read against the current root.
4346
4269
  _lookupLex(name) {
4347
4270
  let Comp = null;
4348
4271
  this.walkPath((c) => {
4349
4272
  Comp = c;
4350
4273
  return false;
4351
4274
  });
4352
- return Comp?.scope?.lookupComponent(name) ?? null;
4275
+ const scope = Comp?.scope;
4276
+ if (scope == null) return null;
4277
+ if (isTypeName(name)) return scope.lookupComponent(name) ?? null;
4278
+ return scope.lookupPath(name)?.lookup(this.root) ?? null;
4353
4279
  }
4354
4280
  // A component lookup is a value lookup constrained to a component. The `lex` leg
4355
4281
  // can only ever answer with one; the `dyn` leg reads a binding an ancestor
@@ -4371,8 +4297,8 @@ var Dispatcher = class {
4371
4297
  send(name, args) {
4372
4298
  return this.sendAtPath(this.path, name, args);
4373
4299
  }
4374
- sendAtPath(path, name, args) {
4375
- return this.transactor.pushSend(path, name, args, this.parent, this.path);
4300
+ sendAtPath(path2, name, args) {
4301
+ return this.transactor.pushSend(path2, name, args, this.parent, this.path);
4376
4302
  }
4377
4303
  // An intent is ROUTED: it names a job and walks until something answers. The verb
4378
4304
  // does not decide which scope answers — `opts.route` does, written here at the call
@@ -4380,8 +4306,8 @@ var Dispatcher = class {
4380
4306
  intent(name, args, opts) {
4381
4307
  return this.intentAtPath(this.path, name, args, opts);
4382
4308
  }
4383
- intentAtPath(path, name, args, opts) {
4384
- return this.transactor.pushIntent(path, name, args, opts, this.parent);
4309
+ intentAtPath(path2, name, args, opts) {
4310
+ return this.transactor.pushIntent(path2, name, args, opts, this.parent);
4385
4311
  }
4386
4312
  };
4387
4313
  var EventContext = class extends Dispatcher {
@@ -4410,11 +4336,19 @@ var EventContext = class extends Dispatcher {
4410
4336
  this.transactor.refuse("NO_SENDER", { name });
4411
4337
  return null;
4412
4338
  }
4413
- return this.sendAtPath(origin, name, args);
4339
+ return this.transactor.pushSend(
4340
+ origin,
4341
+ name,
4342
+ args,
4343
+ this.parent,
4344
+ this.path,
4345
+ this.parent.originPinned
4346
+ );
4414
4347
  }
4415
- // End the walk answering nothing — "served, and no answer".
4348
+ // End the walk answering nothing — "served, and no answer". Like reply/fail,
4349
+ // legal only in an `intent` handler.
4416
4350
  stop() {
4417
- return this.parent.stop();
4351
+ return this.parent.walk === void 0 ? warnNotIntent("stop") : this.parent.walk.finish(null, null);
4418
4352
  }
4419
4353
  // From an `intent` body: hand the intent to the next hop, optionally amending it.
4420
4354
  // From a `receive` body: turn the message that arrived into an intent. One word from
@@ -4442,7 +4376,7 @@ var PathChanges = class extends PathBuilder {
4442
4376
  }
4443
4377
  };
4444
4378
  function rootDispatcher(transactor) {
4445
- return new Dispatcher(new Path([]), transactor, null);
4379
+ return new Dispatcher(new DispatchPath(), transactor, null);
4446
4380
  }
4447
4381
 
4448
4382
  // src/app.js
@@ -4455,7 +4389,6 @@ var App = class {
4455
4389
  this.transactor = new Transactor(comps, null);
4456
4390
  this.ParseContext = ParseContext2;
4457
4391
  this.renderer = renderer;
4458
- this.maxEventNodeDepth = Infinity;
4459
4392
  this._transactNextBatchId = this._evictCacheId = null;
4460
4393
  this._eventNames = new Set(_evs);
4461
4394
  this.dragInfo = this.curDragOver = null;
@@ -4482,18 +4415,17 @@ var App = class {
4482
4415
  _dispatchEvent(e) {
4483
4416
  const { type } = e;
4484
4417
  const isDrag = type === "dragover" || type === "dragstart" || type === "dragend" || type === "drop";
4485
- const { rootNode: root, maxEventNodeDepth: maxDepth, comps, transactor } = this;
4486
- const [path, handlers] = Path.fromNodeAndEventName(
4418
+ const { rootNode, comps, transactor } = this;
4419
+ const [path2, handlers] = DispatchPath.fromNodeAndEventName(
4487
4420
  e.target,
4488
4421
  type,
4489
- root,
4490
- maxDepth,
4422
+ rootNode,
4491
4423
  comps,
4492
4424
  !isDrag
4493
4425
  );
4494
- if (isDrag) this._handleDragEvent(e, type, path);
4495
- if (path !== null && handlers !== null)
4496
- for (const handler of handlers) transactor.transactInputNow(path, e, handler, this.dragInfo);
4426
+ if (isDrag) this._handleDragEvent(e, type, path2);
4427
+ if (path2 !== null && handlers !== null)
4428
+ for (const handler of handlers) transactor.transactInputNow(path2, e, handler, this.dragInfo);
4497
4429
  }
4498
4430
  _handleTouchEvent(e) {
4499
4431
  const { type } = e;
@@ -4536,7 +4468,7 @@ var App = class {
4536
4468
  this._touch = null;
4537
4469
  }
4538
4470
  }
4539
- _handleDragEvent(e, type, path) {
4471
+ _handleDragEvent(e, type, path2) {
4540
4472
  if (type === "dragover") {
4541
4473
  const dropTarget = getClosestDropTarget(e.target, this.rootNode, Infinity);
4542
4474
  if (dropTarget !== null) {
@@ -4548,10 +4480,10 @@ var App = class {
4548
4480
  } else if (type === "dragstart") {
4549
4481
  e.target.dataset.dragging = 1;
4550
4482
  const rootValue = this.state.val;
4551
- const txnPath = path.compact().toTransactionPath();
4483
+ const txnPath = path2.compact().toTransactionPath();
4552
4484
  const value = txnPath.lookup(rootValue);
4553
4485
  const dragType = e.target.dataset.dragtype ?? "?";
4554
- const stack = path.toTransactionPath().buildStack(this.makeStack(rootValue));
4486
+ const stack = path2.buildStack(this.makeStack(rootValue));
4555
4487
  this.dragInfo = new DragInfo(stack, value, dragType, e.target);
4556
4488
  } else if (type === "drop") {
4557
4489
  e.preventDefault();
@@ -4596,10 +4528,9 @@ var App = class {
4596
4528
  }
4597
4529
  compile() {
4598
4530
  for (const Comp of this.comps.byId.values()) {
4599
- const meta = Comp[COMPONENT];
4600
- meta.compile(this.ParseContext);
4601
- for (const key in meta.views)
4602
- for (const name of meta.views[key].ctx.genEventNames()) this._eventNames.add(name);
4531
+ Comp.compile(this.ParseContext);
4532
+ for (const key in Comp.views)
4533
+ for (const name of Comp.views[key].ctx.genEventNames()) this._eventNames.add(name);
4603
4534
  }
4604
4535
  this._compiled = true;
4605
4536
  }
@@ -4622,11 +4553,13 @@ var App = class {
4622
4553
  }
4623
4554
  stop() {
4624
4555
  this.stopCacheEvictionInterval();
4556
+ clearTimeout(this._transactNextBatchId);
4557
+ this._transactNextBatchId = null;
4625
4558
  for (const name of this._eventNames)
4626
4559
  this.rootNode.removeEventListener(name, this, listenerOpts(name));
4627
4560
  }
4628
4561
  sendAtRoot(name, args) {
4629
- this.transactor.pushSend(new Path([]), name, args);
4562
+ this.transactor.pushSend(new DispatchPath(), name, args);
4630
4563
  }
4631
4564
  registerComponents(comps, opts) {
4632
4565
  const scope = this.compStack.enter();
@@ -4701,7 +4634,7 @@ var DragInfo = class {
4701
4634
  };
4702
4635
 
4703
4636
  // src/util/parsectx.js
4704
- var ParseCtxClassSetCollector = class _ParseCtxClassSetCollector extends ParseContext {
4637
+ var ParseCtxClassSetCollector = class extends ParseContext {
4705
4638
  constructor(...args) {
4706
4639
  super(...args);
4707
4640
  this.classes = /* @__PURE__ */ new Set();
@@ -4711,22 +4644,6 @@ var ParseCtxClassSetCollector = class _ParseCtxClassSetCollector extends ParseCo
4711
4644
  this.classes.add(v);
4712
4645
  }
4713
4646
  }
4714
- enterMacro(macroName, macroVars, macroSlots) {
4715
- const { document: document2, Text, Comment, nodes, events, macroNodes } = this;
4716
- const frame = { macroName, macroVars, macroSlots };
4717
- const v = new _ParseCtxClassSetCollector(
4718
- document2,
4719
- Text,
4720
- Comment,
4721
- nodes,
4722
- events,
4723
- macroNodes,
4724
- frame,
4725
- this
4726
- );
4727
- v.classes = this.classes;
4728
- return v;
4729
- }
4730
4647
  onAttributes(attrs, _wrapperAttrs, _textChild, _isMacroCall, _tag) {
4731
4648
  if (Array.isArray(attrs.items)) {
4732
4649
  for (const attr of attrs.items) {
@@ -4786,28 +4703,21 @@ var NullDomCache = class {
4786
4703
  set(_keys, _cacheKey, _v) {
4787
4704
  }
4788
4705
  evict() {
4789
- return { hit: 0, miss: 0, badKey: 0 };
4790
4706
  }
4791
4707
  };
4792
4708
  var WeakMapDomCache = class {
4793
4709
  constructor() {
4794
- this.hit = this.miss = this.badKey = 0;
4795
4710
  this.keysByLen = /* @__PURE__ */ new Map();
4796
4711
  }
4797
- _returnValue(r) {
4798
- if (r === void 0) this.miss += 1;
4799
- else this.hit += 1;
4800
- return r;
4801
- }
4802
4712
  get(keys, cacheKey) {
4803
4713
  const len = keys.length;
4804
4714
  let cur = this.keysByLen.get(len);
4805
- if (!cur) return this._returnValue(void 0);
4715
+ if (!cur) return void 0;
4806
4716
  for (let i = 0; i < len - 1; i++) {
4807
4717
  cur = cur.get(keys[i]);
4808
- if (!cur) return this._returnValue(void 0);
4718
+ if (!cur) return void 0;
4809
4719
  }
4810
- return this._returnValue(cur.get(keys[len - 1])?.[cacheKey]);
4720
+ return cur.get(keys[len - 1])?.[cacheKey];
4811
4721
  }
4812
4722
  set(keys, cacheKey, v) {
4813
4723
  const len = keys.length;
@@ -4820,10 +4730,7 @@ var WeakMapDomCache = class {
4820
4730
  const key = keys[i];
4821
4731
  let next = cur.get(key);
4822
4732
  if (!next) {
4823
- if (!isWeakKey(key)) {
4824
- this.badKey += 1;
4825
- return;
4826
- }
4733
+ if (!isWeakKey(key)) return;
4827
4734
  next = /* @__PURE__ */ new WeakMap();
4828
4735
  cur.set(key, next);
4829
4736
  }
@@ -4833,23 +4740,38 @@ var WeakMapDomCache = class {
4833
4740
  const leaf = cur.get(lastKey);
4834
4741
  if (leaf) leaf[cacheKey] = v;
4835
4742
  else if (isWeakKey(lastKey)) cur.set(lastKey, { [cacheKey]: v });
4836
- else this.badKey += 1;
4837
4743
  }
4838
4744
  evict() {
4839
- const { hit, miss, badKey } = this;
4840
- this.hit = this.miss = this.badKey = 0;
4841
4745
  this.keysByLen = /* @__PURE__ */ new Map();
4842
- return { hit, miss, badKey };
4843
4746
  }
4844
4747
  };
4845
4748
 
4846
4749
  // src/renderer.js
4847
- var DATASET_ATTRS = ["nid", "cid", "eid", "vid", "si", "sk"];
4750
+ function stampRenderBase(vdom, text) {
4751
+ if (vdom instanceof VNode)
4752
+ return new VNode(
4753
+ vdom.tag,
4754
+ { ...vdom.attrs, "data-rp": text },
4755
+ vdom.childs,
4756
+ vdom.key,
4757
+ vdom.namespace
4758
+ );
4759
+ if (vdom instanceof VFragment)
4760
+ return new VFragment(vdom.childs.map((c) => stampRenderBase(c, text)));
4761
+ return vdom;
4762
+ }
4848
4763
  var Renderer = class {
4849
4764
  constructor(comps) {
4850
4765
  this.comps = comps;
4851
4766
  this.cache = new WeakMapDomCache();
4852
- this.renderTag = h;
4767
+ }
4768
+ // Parse nodes build their VDOM through the renderer, never by importing vdom.js
4769
+ // themselves: a component may have been compiled by ANOTHER copy of the library
4770
+ // (a docs example imports the dist bundle while the host app runs from src), and
4771
+ // `render()` recognizes VNodes by `instanceof` against ITS copy's classes. Going
4772
+ // through `rx` guarantees the rendering copy makes them.
4773
+ renderTag(tag, attrs, childs, namespace) {
4774
+ return h(tag, attrs, childs, namespace);
4853
4775
  }
4854
4776
  renderFragment(childs) {
4855
4777
  return new VFragment(childs);
@@ -4860,38 +4782,24 @@ var Renderer = class {
4860
4782
  setNullCache() {
4861
4783
  this.cache = new NullDomCache();
4862
4784
  }
4863
- // Library utilities for consumers embedding tutuca: render a value to a detached
4864
- // DOM node / an HTML string. Not called by the framework itself.
4865
- renderToDOM(stack, val) {
4866
- const rootNode = document.createElement("div");
4867
- const rOpts = { document };
4868
- render(h("DIV", null, [this.renderRoot(stack, val)]), rootNode, rOpts);
4869
- return rootNode.childNodes[0];
4870
- }
4871
- renderToString(stack, val, cleanAttrs = true) {
4872
- const dom = this.renderToDOM(stack, val);
4873
- if (cleanAttrs) {
4874
- const nodes = dom.querySelectorAll("[data-nid],[data-cid],[data-eid]");
4875
- for (const { dataset } of nodes) for (const name of DATASET_ATTRS) delete dataset[name];
4876
- }
4877
- return dom.innerHTML;
4878
- }
4879
4785
  renderRoot(stack, val, viewName = null) {
4880
4786
  const comp = this.comps.getCompFor(val);
4881
4787
  if (comp === null) return null;
4882
4788
  return this._rValComp(stack, val, comp, comp.getView(viewName).anode, "ROOT", viewName);
4883
4789
  }
4884
- renderIt(stack, node, key, viewName) {
4790
+ // Render `stack.it` at a `<x render*>` site (`node`). `base` is the absolute
4791
+ // path the site resumed at, when it rendered a `*name` (see _rValComp).
4792
+ renderIt(stack, node, viewName, base = null) {
4885
4793
  const comp = this.comps.getCompFor(stack.it);
4886
- return comp ? this._rValComp(stack, stack.it, comp, node, key, viewName) : null;
4794
+ return comp ? this._rValComp(stack, stack.it, comp, node, "", viewName, base) : null;
4887
4795
  }
4888
4796
  // `node` is the parse node of the render site (`<x render>` / `render-it` /
4889
4797
  // `render-each`, or the view's root anode for the app root). It keys the
4890
4798
  // cache as a globally-unique object: node ids alone are unique only within a
4891
4799
  // single view, so the same value rendered by two components (e.g. through a
4892
4800
  // shared dynamic-var sequence) would otherwise collide in the cache.
4893
- _rValComp(stack, val, comp, node, key, viewName) {
4894
- const cacheKey = `${viewName ?? ""}${stack.viewsId ?? ""}${key}`;
4801
+ _rValComp(stack, val, comp, node, key, viewName, base = null) {
4802
+ const cacheKey = `${viewName ?? ""}${stack.viewsId ?? ""}${key}${stack.renderPath.addressKey}`;
4895
4803
  const cachePath = [node, val];
4896
4804
  stack._pushDynBindValuesToArray(cachePath, comp);
4897
4805
  const cachedNode = this.cache.get(cachePath, cacheKey);
@@ -4899,30 +4807,43 @@ var Renderer = class {
4899
4807
  const view = viewName ? comp.getView(viewName) : stack.lookupBestView(comp.views, "main");
4900
4808
  const body = this.renderView(view, stack);
4901
4809
  if (body == null) return null;
4810
+ const baseJson = base === null ? null : pathToJson(base);
4902
4811
  const meta = this._renderMetadata({
4903
4812
  $: "Comp",
4904
4813
  nid: node?.nodeId ?? null,
4905
4814
  cid: comp.id,
4906
- vid: view.name
4815
+ vid: view.name,
4816
+ ...baseJson === null ? null : { base: baseJson }
4907
4817
  });
4908
- const dom = new VFragment([meta, body]);
4818
+ const dom = new VFragment([
4819
+ meta,
4820
+ baseJson === null ? body : stampRenderBase(body, JSON.stringify(baseJson))
4821
+ ]);
4909
4822
  this.cache.set(cachePath, cacheKey, dom);
4910
4823
  return dom;
4911
4824
  }
4912
4825
  pushEachEntry(r, nid, attrName, key, dom) {
4913
4826
  r.push(this._renderMetadata({ $: "Each", nid, [attrName]: key }), dom);
4914
4827
  }
4915
- renderEachWhen(stack, iterInfo, view, nid) {
4916
- const { seq, filter, loopWith, enricher } = iterInfo.eval(stack);
4828
+ renderEachWhen(stack, each2) {
4829
+ const { val: seqVal, node: view, nodeId: nid } = each2;
4830
+ const { seq, filter, loopWith, enricher } = each2.evalIter(stack);
4831
+ const seqPath = seqVal instanceof DynVal ? stack.lookupDynamicLocated(seqVal.name)?.path ?? null : null;
4917
4832
  const r = [];
4918
4833
  const it = stack.it;
4919
4834
  const renderOne = (key, value, attrName, binds) => {
4835
+ const itemBase = seqPath === null ? null : keyedPath(seqPath, key);
4836
+ const itemStep = itemBase === null ? each2.itemStep(key) : null;
4837
+ const itemPath = itemBase !== null ? stack.renderPath.pushFrame(itemBase) : itemStep !== null ? stack.renderPath.pushItem(itemStep) : stack.renderPath;
4920
4838
  const cachePath = enricher ? [view, it, value] : [view, value];
4921
- const cacheKey = `${stack.viewsId ?? ""}${nid}${key}`;
4839
+ const cacheKey = `${stack.viewsId ?? ""}${nid}${key}${itemPath.addressKey}`;
4922
4840
  const cachedNode = this.cache.get(cachePath, cacheKey);
4923
4841
  if (cachedNode) this.pushEachEntry(r, nid, attrName, key, cachedNode);
4924
4842
  else {
4925
- const dom = this.renderView(view, stack.enter(value, binds, false));
4843
+ const dom = this.renderView(
4844
+ view,
4845
+ stack.enter(value, binds, false, itemPath, itemBase !== null)
4846
+ );
4926
4847
  if (dom != null) this.pushEachEntry(r, nid, attrName, key, dom);
4927
4848
  this.cache.set(cachePath, cacheKey, dom);
4928
4849
  }
@@ -4986,17 +4907,18 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
4986
4907
  var css = String.raw;
4987
4908
  var html = String.raw;
4988
4909
  var macro = (defaults, rawView) => new Macro(defaults, rawView);
4910
+ var devOnly = (name, result) => {
4911
+ console.warn(`${name} is a no-op in the core tutuca build; import tutuca/dev for the real one`);
4912
+ return result;
4913
+ };
4989
4914
  function check(_app) {
4990
- return { error: 0, warn: 0, hint: 0, dummyCheck: true };
4915
+ return devOnly("check", { error: 0, warn: 0, hint: 0 });
4991
4916
  }
4992
4917
  async function test(_opts) {
4993
- return null;
4918
+ return devOnly("test", null);
4994
4919
  }
4995
4920
  function collectIterBindings() {
4996
- console.warn(
4997
- "collectIterBindings is a no-op in the core tutuca build; use the tutuca-dev build for a functional implementation"
4998
- );
4999
- return [];
4921
+ return devOnly("collectIterBindings", []);
5000
4922
  }
5001
4923
  function tutuca(nodeOrSelector) {
5002
4924
  const rootNode = typeof nodeOrSelector === "string" ? document.querySelector(nodeOrSelector) : nodeOrSelector;
@@ -5020,7 +4942,6 @@ async function compileClassesToStyleText(app, compileClasses, Ctx = ParseCtxClas
5020
4942
  }
5021
4943
  export {
5022
4944
  COMPONENT,
5023
- FIELD_CLASS,
5024
4945
  PASS,
5025
4946
  ParseContext,
5026
4947
  SEQ_INFO,
@@ -5034,6 +4955,7 @@ export {
5034
4955
  html,
5035
4956
  injectCss,
5036
4957
  macro,
4958
+ path,
5037
4959
  phaseOps,
5038
4960
  resolveArgs,
5039
4961
  rootDispatcher,