vsn 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -22,16 +22,21 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ArrayExpression: () => ArrayExpression,
24
24
  ArrayPattern: () => ArrayPattern,
25
+ AssertError: () => AssertError,
26
+ AssertNode: () => AssertNode,
25
27
  AssignmentNode: () => AssignmentNode,
26
28
  AwaitExpression: () => AwaitExpression,
27
29
  BaseNode: () => BaseNode,
28
30
  BehaviorNode: () => BehaviorNode,
29
31
  BinaryExpression: () => BinaryExpression,
30
32
  BlockNode: () => BlockNode,
33
+ BreakNode: () => BreakNode,
31
34
  CallExpression: () => CallExpression,
35
+ ContinueNode: () => ContinueNode,
32
36
  DeclarationNode: () => DeclarationNode,
33
37
  DirectiveExpression: () => DirectiveExpression,
34
38
  Engine: () => Engine,
39
+ ForEachNode: () => ForEachNode,
35
40
  ForNode: () => ForNode,
36
41
  FunctionDeclarationNode: () => FunctionDeclarationNode,
37
42
  FunctionExpression: () => FunctionExpression,
@@ -51,8 +56,6 @@ __export(index_exports, {
51
56
  ReturnNode: () => ReturnNode,
52
57
  SelectorNode: () => SelectorNode,
53
58
  SpreadElement: () => SpreadElement,
54
- StateBlockNode: () => StateBlockNode,
55
- StateEntryNode: () => StateEntryNode,
56
59
  TemplateExpression: () => TemplateExpression,
57
60
  TernaryExpression: () => TernaryExpression,
58
61
  TokenType: () => TokenType,
@@ -88,6 +91,9 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
88
91
  TokenType2["While"] = "While";
89
92
  TokenType2["Try"] = "Try";
90
93
  TokenType2["Catch"] = "Catch";
94
+ TokenType2["Assert"] = "Assert";
95
+ TokenType2["Break"] = "Break";
96
+ TokenType2["Continue"] = "Continue";
91
97
  TokenType2["LBrace"] = "LBrace";
92
98
  TokenType2["RBrace"] = "RBrace";
93
99
  TokenType2["LParen"] = "LParen";
@@ -103,7 +109,9 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
103
109
  TokenType2["Greater"] = "Greater";
104
110
  TokenType2["Less"] = "Less";
105
111
  TokenType2["Plus"] = "Plus";
112
+ TokenType2["PlusPlus"] = "PlusPlus";
106
113
  TokenType2["Minus"] = "Minus";
114
+ TokenType2["MinusMinus"] = "MinusMinus";
107
115
  TokenType2["Tilde"] = "Tilde";
108
116
  TokenType2["Star"] = "Star";
109
117
  TokenType2["Slash"] = "Slash";
@@ -143,6 +151,9 @@ var KEYWORDS = {
143
151
  while: "While" /* While */,
144
152
  try: "Try" /* Try */,
145
153
  catch: "Catch" /* Catch */,
154
+ assert: "Assert" /* Assert */,
155
+ break: "Break" /* Break */,
156
+ continue: "Continue" /* Continue */,
146
157
  true: "Boolean" /* Boolean */,
147
158
  false: "Boolean" /* Boolean */,
148
159
  null: "Null" /* Null */
@@ -244,8 +255,20 @@ var Lexer = class {
244
255
  readIdentifier() {
245
256
  const start = this.position();
246
257
  let value = "";
247
- while (!this.eof() && (this.isAlphaNumeric(this.peek()) || this.peek() === "_" || this.peek() === "-")) {
248
- value += this.next();
258
+ while (!this.eof()) {
259
+ const ch = this.peek();
260
+ if (this.isAlphaNumeric(ch) || ch === "_") {
261
+ value += this.next();
262
+ continue;
263
+ }
264
+ if (ch === "-") {
265
+ if (this.peek(1) === "-") {
266
+ break;
267
+ }
268
+ value += this.next();
269
+ continue;
270
+ }
271
+ break;
249
272
  }
250
273
  const keywordType = KEYWORDS[value];
251
274
  if (keywordType) {
@@ -386,6 +409,16 @@ var Lexer = class {
386
409
  this.next();
387
410
  return this.token("Pipe" /* Pipe */, "|>", start);
388
411
  }
412
+ if (ch === "+" && next === "+") {
413
+ this.next();
414
+ this.next();
415
+ return this.token("PlusPlus" /* PlusPlus */, "++", start);
416
+ }
417
+ if (ch === "-" && next === "-") {
418
+ this.next();
419
+ this.next();
420
+ return this.token("MinusMinus" /* MinusMinus */, "--", start);
421
+ }
389
422
  if (ch === "." && next === "." && this.peek(2) === ".") {
390
423
  this.next();
391
424
  this.next();
@@ -488,11 +521,20 @@ var BaseNode = class {
488
521
  async prepare(_context) {
489
522
  return;
490
523
  }
491
- async evaluate(_context) {
524
+ evaluate(_context) {
492
525
  return void 0;
493
526
  }
494
527
  };
495
- async function evaluateWithChildScope(context, block) {
528
+ function isPromiseLike(value) {
529
+ return Boolean(value) && typeof value.then === "function";
530
+ }
531
+ function resolveMaybe(value, next) {
532
+ if (isPromiseLike(value)) {
533
+ return value.then(next);
534
+ }
535
+ return next(value);
536
+ }
537
+ function evaluateWithChildScope(context, block) {
496
538
  const scope = context.scope;
497
539
  if (!scope || !scope.createChild) {
498
540
  return block.evaluate(context);
@@ -500,7 +542,7 @@ async function evaluateWithChildScope(context, block) {
500
542
  const previousScope = context.scope;
501
543
  context.scope = scope.createChild();
502
544
  try {
503
- return await block.evaluate(context);
545
+ return block.evaluate(context);
504
546
  } finally {
505
547
  context.scope = previousScope;
506
548
  }
@@ -526,15 +568,25 @@ var BlockNode = class extends BaseNode {
526
568
  super("Block");
527
569
  this.statements = statements;
528
570
  }
529
- async evaluate(context) {
530
- for (const statement of this.statements) {
531
- if (context.returning) {
532
- break;
533
- }
534
- if (statement && typeof statement.evaluate === "function") {
535
- await statement.evaluate(context);
571
+ evaluate(context) {
572
+ let index = 0;
573
+ const run = () => {
574
+ while (index < this.statements.length) {
575
+ if (context.returning || context.breaking || context.continuing) {
576
+ break;
577
+ }
578
+ const statement = this.statements[index];
579
+ index += 1;
580
+ if (statement && typeof statement.evaluate === "function") {
581
+ const result = statement.evaluate(context);
582
+ if (isPromiseLike(result)) {
583
+ return result.then(() => run());
584
+ }
585
+ }
536
586
  }
537
- }
587
+ return void 0;
588
+ };
589
+ return run();
538
590
  }
539
591
  };
540
592
  var SelectorNode = class extends BaseNode {
@@ -544,136 +596,186 @@ var SelectorNode = class extends BaseNode {
544
596
  }
545
597
  };
546
598
  var BehaviorNode = class extends BaseNode {
547
- constructor(selector, body) {
599
+ constructor(selector, body, flags = {}, flagArgs = {}) {
548
600
  super("Behavior");
549
601
  this.selector = selector;
550
602
  this.body = body;
551
- }
552
- };
553
- var StateEntryNode = class extends BaseNode {
554
- constructor(name, value, important) {
555
- super("StateEntry");
556
- this.name = name;
557
- this.value = value;
558
- this.important = important;
559
- }
560
- };
561
- var StateBlockNode = class extends BaseNode {
562
- constructor(entries) {
563
- super("StateBlock");
564
- this.entries = entries;
603
+ this.flags = flags;
604
+ this.flagArgs = flagArgs;
565
605
  }
566
606
  };
567
607
  var OnBlockNode = class extends BaseNode {
568
- constructor(eventName, args, body, modifiers = []) {
608
+ constructor(eventName, args, body, flags = {}, flagArgs = {}) {
569
609
  super("OnBlock");
570
610
  this.eventName = eventName;
571
611
  this.args = args;
572
612
  this.body = body;
573
- this.modifiers = modifiers;
613
+ this.flags = flags;
614
+ this.flagArgs = flagArgs;
574
615
  }
575
616
  };
576
617
  var AssignmentNode = class extends BaseNode {
577
- constructor(target, value, operator = "=") {
618
+ constructor(target, value, operator = "=", prefix = false) {
578
619
  super("Assignment");
579
620
  this.target = target;
580
621
  this.value = value;
581
622
  this.operator = operator;
623
+ this.prefix = prefix;
582
624
  }
583
- async evaluate(context) {
625
+ evaluate(context) {
584
626
  if (!context.scope || !context.scope.setPath) {
585
627
  return void 0;
586
628
  }
587
- const value = await this.value.evaluate(context);
588
- if (this.operator !== "=") {
589
- return await this.applyCompoundAssignment(context, value);
590
- }
591
- if (this.target instanceof IdentifierExpression && this.target.name.startsWith("root.") && context.rootScope) {
592
- const path = this.target.name.slice("root.".length);
593
- context.rootScope.setPath?.(path, value);
594
- return value;
595
- }
596
- if (this.target instanceof MemberExpression || this.target instanceof IndexExpression) {
597
- const resolved = await this.resolveAssignmentTarget(context);
598
- if (resolved?.scope?.setPath) {
599
- resolved.scope.setPath(resolved.path, value);
600
- return value;
629
+ if (this.operator === "++" || this.operator === "--") {
630
+ return this.applyIncrement(context);
631
+ }
632
+ const value = this.value.evaluate(context);
633
+ return resolveMaybe(value, (resolvedValue) => {
634
+ if (this.operator !== "=") {
635
+ return this.applyCompoundAssignment(context, resolvedValue);
636
+ }
637
+ if (this.target instanceof IdentifierExpression && this.target.name.startsWith("root.") && context.rootScope) {
638
+ const path = this.target.name.slice("root.".length);
639
+ context.rootScope.setPath?.(`self.${path}`, resolvedValue);
640
+ return resolvedValue;
641
+ }
642
+ if (this.target instanceof MemberExpression || this.target instanceof IndexExpression) {
643
+ const resolved = this.resolveAssignmentTarget(context);
644
+ return resolveMaybe(resolved, (resolvedTarget) => {
645
+ if (resolvedTarget?.scope?.setPath) {
646
+ resolvedTarget.scope.setPath(resolvedTarget.path, resolvedValue);
647
+ return resolvedValue;
648
+ }
649
+ this.assignTarget(context, this.target, resolvedValue);
650
+ return resolvedValue;
651
+ });
601
652
  }
602
- }
603
- this.assignTarget(context, this.target, value);
604
- return value;
653
+ this.assignTarget(context, this.target, resolvedValue);
654
+ return resolvedValue;
655
+ });
605
656
  }
606
- async applyCompoundAssignment(context, value) {
657
+ applyCompoundAssignment(context, value) {
607
658
  if (!context.scope || !context.scope.setPath) {
608
659
  return void 0;
609
660
  }
610
- const resolved = await this.resolveAssignmentTarget(context);
611
- if (!resolved) {
612
- throw new Error("Compound assignment requires a simple identifier or member path");
613
- }
614
- const { scope, path } = resolved;
615
- const current = scope?.getPath ? scope.getPath(path) : void 0;
616
- let result;
617
- if (this.operator === "+=") {
618
- result = current + value;
619
- } else if (this.operator === "-=") {
620
- result = current - value;
621
- } else if (this.operator === "*=") {
622
- result = current * value;
623
- } else {
624
- result = current / value;
661
+ const resolved = this.resolveAssignmentTarget(context);
662
+ return resolveMaybe(resolved, (resolvedTarget) => {
663
+ if (!resolvedTarget) {
664
+ throw new Error("Compound assignment requires a simple identifier or member path");
665
+ }
666
+ const { scope, path } = resolvedTarget;
667
+ const current = scope?.getPath ? scope.getPath(path) : void 0;
668
+ let result;
669
+ if (this.operator === "+=") {
670
+ result = current + value;
671
+ } else if (this.operator === "-=") {
672
+ result = current - value;
673
+ } else if (this.operator === "*=") {
674
+ result = current * value;
675
+ } else {
676
+ result = current / value;
677
+ }
678
+ scope?.setPath?.(path, result);
679
+ return result;
680
+ });
681
+ }
682
+ applyIncrement(context) {
683
+ if (!context.scope || !context.scope.setPath) {
684
+ return void 0;
625
685
  }
626
- scope?.setPath?.(path, result);
627
- return result;
686
+ const resolved = this.resolveAssignmentTarget(context);
687
+ return resolveMaybe(resolved, (resolvedTarget) => {
688
+ if (!resolvedTarget) {
689
+ throw new Error("Increment/decrement requires a simple identifier or member path");
690
+ }
691
+ const { scope, path } = resolvedTarget;
692
+ const current = scope?.getPath ? scope.getPath(path) : void 0;
693
+ const numeric = typeof current === "number" ? current : Number(current);
694
+ const delta = this.operator === "++" ? 1 : -1;
695
+ const next = (Number.isNaN(numeric) ? 0 : numeric) + delta;
696
+ scope?.setPath?.(path, next);
697
+ return this.prefix ? next : numeric;
698
+ });
628
699
  }
629
- async resolveAssignmentTarget(context) {
700
+ resolveAssignmentTarget(context) {
630
701
  if (this.target instanceof IdentifierExpression) {
631
702
  const isRoot = this.target.name.startsWith("root.");
632
703
  const rawPath = isRoot ? this.target.name.slice("root.".length) : this.target.name;
633
704
  if (isRoot) {
705
+ if (context.rootScope) {
706
+ return { scope: context.rootScope, path: `self.${rawPath}` };
707
+ }
634
708
  return { scope: context.scope, path: `root.${rawPath}` };
635
709
  }
636
710
  return { scope: context.scope, path: rawPath };
637
711
  }
638
712
  if (this.target instanceof MemberExpression) {
639
713
  const resolvedPath = this.target.getIdentifierPath();
640
- if (!resolvedPath) {
641
- return null;
642
- }
643
- const path = resolvedPath.path;
644
- const isRoot = path.startsWith("root.");
645
- const rawPath = isRoot ? path.slice("root.".length) : path;
646
- if (isRoot) {
647
- return { scope: context.scope, path: `root.${rawPath}` };
714
+ if (resolvedPath) {
715
+ const path = resolvedPath.path;
716
+ const isRoot = path.startsWith("root.");
717
+ const rawPath = isRoot ? path.slice("root.".length) : path;
718
+ if (isRoot) {
719
+ if (context.rootScope) {
720
+ return { scope: context.rootScope, path: `self.${rawPath}` };
721
+ }
722
+ return { scope: context.scope, path: `root.${rawPath}` };
723
+ }
724
+ return { scope: context.scope, path: rawPath };
648
725
  }
649
- return { scope: context.scope, path: rawPath };
726
+ const targetExpr = this.target;
727
+ const basePath = this.resolveTargetPath(context, targetExpr.target);
728
+ return resolveMaybe(basePath, (resolvedBase) => {
729
+ if (!resolvedBase) {
730
+ return null;
731
+ }
732
+ const path = `${resolvedBase}.${targetExpr.property}`;
733
+ const isRoot = path.startsWith("root.");
734
+ const rawPath = isRoot ? path.slice("root.".length) : path;
735
+ if (isRoot) {
736
+ if (context.rootScope) {
737
+ return { scope: context.rootScope, path: `self.${rawPath}` };
738
+ }
739
+ return { scope: context.scope, path: `root.${rawPath}` };
740
+ }
741
+ return { scope: context.scope, path: rawPath };
742
+ });
650
743
  }
651
744
  if (this.target instanceof IndexExpression) {
652
- const path = await this.resolveIndexPath(context, this.target);
653
- if (!path) {
654
- return null;
655
- }
656
- const isRoot = path.startsWith("root.");
657
- const rawPath = isRoot ? path.slice("root.".length) : path;
658
- if (isRoot) {
659
- return { scope: context.scope, path: `root.${rawPath}` };
660
- }
661
- return { scope: context.scope, path: rawPath };
745
+ const path = this.resolveIndexPath(context, this.target);
746
+ return resolveMaybe(path, (resolvedPath) => {
747
+ if (!resolvedPath) {
748
+ return null;
749
+ }
750
+ const isRoot = resolvedPath.startsWith("root.");
751
+ const rawPath = isRoot ? resolvedPath.slice("root.".length) : resolvedPath;
752
+ if (isRoot) {
753
+ if (context.rootScope) {
754
+ return { scope: context.rootScope, path: `self.${rawPath}` };
755
+ }
756
+ return { scope: context.scope, path: `root.${rawPath}` };
757
+ }
758
+ return { scope: context.scope, path: rawPath };
759
+ });
662
760
  }
663
761
  return null;
664
762
  }
665
- async resolveIndexPath(context, expr) {
666
- const base = await this.resolveTargetPath(context, expr.target);
667
- if (!base) {
668
- return null;
669
- }
670
- const indexValue = await expr.index.evaluate(context);
671
- if (indexValue == null) {
672
- return null;
673
- }
674
- return `${base}.${indexValue}`;
763
+ resolveIndexPath(context, expr) {
764
+ const base = this.resolveTargetPath(context, expr.target);
765
+ return resolveMaybe(base, (resolvedBase) => {
766
+ if (!resolvedBase) {
767
+ return null;
768
+ }
769
+ const indexValue = expr.index.evaluate(context);
770
+ return resolveMaybe(indexValue, (resolvedIndex) => {
771
+ if (resolvedIndex == null) {
772
+ return null;
773
+ }
774
+ return `${resolvedBase}.${resolvedIndex}`;
775
+ });
776
+ });
675
777
  }
676
- async resolveTargetPath(context, target) {
778
+ resolveTargetPath(context, target) {
677
779
  if (target instanceof IdentifierExpression) {
678
780
  return target.name;
679
781
  }
@@ -689,6 +791,10 @@ var AssignmentNode = class extends BaseNode {
689
791
  if (!context.scope || !context.scope.setPath) {
690
792
  return;
691
793
  }
794
+ if (target instanceof DirectiveExpression) {
795
+ this.assignDirectiveTarget(context, target, value);
796
+ return;
797
+ }
692
798
  if (target instanceof IdentifierExpression) {
693
799
  context.scope.setPath(target.name, value);
694
800
  return;
@@ -730,19 +836,99 @@ var AssignmentNode = class extends BaseNode {
730
836
  return;
731
837
  }
732
838
  }
839
+ assignDirectiveTarget(context, target, value) {
840
+ const element = context.element;
841
+ if (!element) {
842
+ return;
843
+ }
844
+ if (target.kind === "attr") {
845
+ if (target.name === "value") {
846
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
847
+ element.value = value == null ? "" : String(value);
848
+ element.setAttribute("value", element.value);
849
+ return;
850
+ }
851
+ if (element instanceof HTMLSelectElement) {
852
+ element.value = value == null ? "" : String(value);
853
+ return;
854
+ }
855
+ }
856
+ if (target.name === "checked" && element instanceof HTMLInputElement) {
857
+ const checked = value === true || value === "true" || value === 1 || value === "1";
858
+ element.checked = checked;
859
+ if (checked) {
860
+ element.setAttribute("checked", "");
861
+ } else {
862
+ element.removeAttribute("checked");
863
+ }
864
+ return;
865
+ }
866
+ if (target.name === "html" && element instanceof HTMLElement) {
867
+ element.innerHTML = value == null ? "" : String(value);
868
+ return;
869
+ }
870
+ element.setAttribute(target.name, value == null ? "" : String(value));
871
+ return;
872
+ }
873
+ if (target.kind === "style" && element instanceof HTMLElement) {
874
+ element.style.setProperty(target.name, value == null ? "" : String(value));
875
+ }
876
+ }
733
877
  };
734
878
  var ReturnNode = class extends BaseNode {
735
879
  constructor(value) {
736
880
  super("Return");
737
881
  this.value = value;
738
882
  }
739
- async evaluate(context) {
883
+ evaluate(context) {
740
884
  if (context.returning) {
741
885
  return context.returnValue;
742
886
  }
743
- context.returnValue = this.value ? await this.value.evaluate(context) : void 0;
744
- context.returning = true;
745
- return context.returnValue;
887
+ const nextValue = this.value ? this.value.evaluate(context) : void 0;
888
+ return resolveMaybe(nextValue, (resolved) => {
889
+ context.returnValue = resolved;
890
+ context.returning = true;
891
+ return context.returnValue;
892
+ });
893
+ }
894
+ };
895
+ var BreakNode = class extends BaseNode {
896
+ constructor() {
897
+ super("Break");
898
+ }
899
+ evaluate(context) {
900
+ context.breaking = true;
901
+ return void 0;
902
+ }
903
+ };
904
+ var ContinueNode = class extends BaseNode {
905
+ constructor() {
906
+ super("Continue");
907
+ }
908
+ evaluate(context) {
909
+ context.continuing = true;
910
+ return void 0;
911
+ }
912
+ };
913
+ var AssertError = class extends Error {
914
+ constructor(message = "Assertion failed") {
915
+ super(message);
916
+ this.name = "AssertError";
917
+ }
918
+ };
919
+ var AssertNode = class extends BaseNode {
920
+ constructor(test) {
921
+ super("Assert");
922
+ this.test = test;
923
+ }
924
+ evaluate(context) {
925
+ const value = this.test.evaluate(context);
926
+ return resolveMaybe(value, (resolved) => {
927
+ if (!resolved) {
928
+ throw new AssertError();
929
+ }
930
+ return resolved;
931
+ });
746
932
  }
747
933
  };
748
934
  var IfNode = class extends BaseNode {
@@ -752,14 +938,17 @@ var IfNode = class extends BaseNode {
752
938
  this.consequent = consequent;
753
939
  this.alternate = alternate;
754
940
  }
755
- async evaluate(context) {
756
- const condition = await this.test.evaluate(context);
757
- if (condition) {
758
- return evaluateWithChildScope(context, this.consequent);
759
- }
760
- if (this.alternate) {
761
- return evaluateWithChildScope(context, this.alternate);
762
- }
941
+ evaluate(context) {
942
+ const condition = this.test.evaluate(context);
943
+ return resolveMaybe(condition, (resolved) => {
944
+ if (resolved) {
945
+ return evaluateWithChildScope(context, this.consequent);
946
+ }
947
+ if (this.alternate) {
948
+ return evaluateWithChildScope(context, this.alternate);
949
+ }
950
+ return void 0;
951
+ });
763
952
  }
764
953
  };
765
954
  var WhileNode = class extends BaseNode {
@@ -768,21 +957,104 @@ var WhileNode = class extends BaseNode {
768
957
  this.test = test;
769
958
  this.body = body;
770
959
  }
771
- async evaluate(context) {
960
+ evaluate(context) {
772
961
  const previousScope = context.scope;
773
962
  if (context.scope?.createChild) {
774
963
  context.scope = context.scope.createChild();
775
964
  }
776
- try {
777
- while (await this.test.evaluate(context)) {
778
- await this.body.evaluate(context);
779
- if (context.returning) {
780
- break;
965
+ const run = () => {
966
+ const condition = this.test.evaluate(context);
967
+ return resolveMaybe(condition, (resolved) => {
968
+ if (!resolved || context.returning) {
969
+ return void 0;
970
+ }
971
+ const bodyResult = this.body.evaluate(context);
972
+ return resolveMaybe(bodyResult, () => {
973
+ if (context.breaking) {
974
+ context.breaking = false;
975
+ return void 0;
976
+ }
977
+ if (context.continuing) {
978
+ context.continuing = false;
979
+ }
980
+ return run();
981
+ });
982
+ });
983
+ };
984
+ const result = run();
985
+ if (isPromiseLike(result)) {
986
+ return result.finally(() => {
987
+ context.scope = previousScope;
988
+ });
989
+ }
990
+ context.scope = previousScope;
991
+ return result;
992
+ }
993
+ };
994
+ var ForEachNode = class extends BaseNode {
995
+ constructor(target, iterable, kind, body) {
996
+ super("ForEach");
997
+ this.target = target;
998
+ this.iterable = iterable;
999
+ this.kind = kind;
1000
+ this.body = body;
1001
+ }
1002
+ evaluate(context) {
1003
+ const iterableValue = this.iterable.evaluate(context);
1004
+ return resolveMaybe(iterableValue, (resolved) => {
1005
+ const entries = this.getEntries(resolved);
1006
+ const previousScope = context.scope;
1007
+ let bodyScope = context.scope;
1008
+ if (context.scope?.createChild) {
1009
+ bodyScope = context.scope.createChild();
1010
+ }
1011
+ let index = 0;
1012
+ const loop = () => {
1013
+ if (index >= entries.length || context.returning) {
1014
+ context.scope = previousScope;
1015
+ return void 0;
781
1016
  }
1017
+ const value = entries[index];
1018
+ index += 1;
1019
+ context.scope = bodyScope;
1020
+ context.scope?.setPath?.(this.target.name, value);
1021
+ const bodyResult = this.body.evaluate(context);
1022
+ return resolveMaybe(bodyResult, () => {
1023
+ if (context.breaking) {
1024
+ context.breaking = false;
1025
+ context.scope = previousScope;
1026
+ return void 0;
1027
+ }
1028
+ if (context.continuing) {
1029
+ context.continuing = false;
1030
+ }
1031
+ context.scope = previousScope;
1032
+ return loop();
1033
+ });
1034
+ };
1035
+ return loop();
1036
+ });
1037
+ }
1038
+ getEntries(value) {
1039
+ if (value == null) {
1040
+ return [];
1041
+ }
1042
+ if (this.kind === "in") {
1043
+ if (typeof value === "object") {
1044
+ return Object.keys(value);
782
1045
  }
783
- } finally {
784
- context.scope = previousScope;
1046
+ return [];
785
1047
  }
1048
+ if (typeof value === "string") {
1049
+ return Array.from(value);
1050
+ }
1051
+ if (typeof value[Symbol.iterator] === "function") {
1052
+ return Array.from(value);
1053
+ }
1054
+ if (typeof value === "object") {
1055
+ return Object.values(value);
1056
+ }
1057
+ return [];
786
1058
  }
787
1059
  };
788
1060
  var ForNode = class extends BaseNode {
@@ -793,27 +1065,45 @@ var ForNode = class extends BaseNode {
793
1065
  this.update = update;
794
1066
  this.body = body;
795
1067
  }
796
- async evaluate(context) {
797
- if (this.init) {
798
- await this.init.evaluate(context);
799
- }
800
- const previousScope = context.scope;
801
- let bodyScope = context.scope;
802
- if (context.scope?.createChild) {
803
- bodyScope = context.scope.createChild();
804
- }
805
- while (this.test ? await this.test.evaluate(context) : true) {
806
- context.scope = bodyScope;
807
- await this.body.evaluate(context);
808
- if (context.returning) {
809
- break;
810
- }
811
- context.scope = previousScope;
812
- if (this.update) {
813
- await this.update.evaluate(context);
814
- }
815
- }
816
- context.scope = previousScope;
1068
+ evaluate(context) {
1069
+ const initResult = this.init ? this.init.evaluate(context) : void 0;
1070
+ const run = () => {
1071
+ const previousScope = context.scope;
1072
+ let bodyScope = context.scope;
1073
+ if (context.scope?.createChild) {
1074
+ bodyScope = context.scope.createChild();
1075
+ }
1076
+ const loop = () => {
1077
+ const testResult = this.test ? this.test.evaluate(context) : true;
1078
+ return resolveMaybe(testResult, (passed) => {
1079
+ if (!passed || context.returning) {
1080
+ context.scope = previousScope;
1081
+ return void 0;
1082
+ }
1083
+ context.scope = bodyScope;
1084
+ const bodyResult = this.body.evaluate(context);
1085
+ return resolveMaybe(bodyResult, () => {
1086
+ if (context.returning) {
1087
+ context.scope = previousScope;
1088
+ return void 0;
1089
+ }
1090
+ if (context.breaking) {
1091
+ context.breaking = false;
1092
+ context.scope = previousScope;
1093
+ return void 0;
1094
+ }
1095
+ context.scope = previousScope;
1096
+ if (context.continuing) {
1097
+ context.continuing = false;
1098
+ }
1099
+ const updateResult = this.update ? this.update.evaluate(context) : void 0;
1100
+ return resolveMaybe(updateResult, () => loop());
1101
+ });
1102
+ });
1103
+ };
1104
+ return loop();
1105
+ };
1106
+ return resolveMaybe(initResult, () => run());
817
1107
  }
818
1108
  };
819
1109
  var TryNode = class extends BaseNode {
@@ -823,10 +1113,8 @@ var TryNode = class extends BaseNode {
823
1113
  this.errorName = errorName;
824
1114
  this.handler = handler;
825
1115
  }
826
- async evaluate(context) {
827
- try {
828
- return await evaluateWithChildScope(context, this.body);
829
- } catch (error) {
1116
+ evaluate(context) {
1117
+ const handleError = (error) => {
830
1118
  if (context.returning) {
831
1119
  return context.returnValue;
832
1120
  }
@@ -844,11 +1132,23 @@ var TryNode = class extends BaseNode {
844
1132
  scope.setPath(`self.${this.errorName}`, error);
845
1133
  }
846
1134
  }
847
- await this.handler.evaluate(context);
848
- if (scope && scope.setPath && handlerScope === previousScope) {
849
- scope.setPath(this.errorName, previous);
1135
+ const handlerResult = this.handler.evaluate(context);
1136
+ return resolveMaybe(handlerResult, () => {
1137
+ if (scope && scope.setPath && handlerScope === previousScope) {
1138
+ scope.setPath(this.errorName, previous);
1139
+ }
1140
+ context.scope = previousScope;
1141
+ return void 0;
1142
+ });
1143
+ };
1144
+ try {
1145
+ const bodyResult = evaluateWithChildScope(context, this.body);
1146
+ if (isPromiseLike(bodyResult)) {
1147
+ return bodyResult.catch((error) => handleError(error));
850
1148
  }
851
- context.scope = previousScope;
1149
+ return bodyResult;
1150
+ } catch (error) {
1151
+ return handleError(error);
852
1152
  }
853
1153
  }
854
1154
  };
@@ -868,11 +1168,35 @@ var FunctionExpression = class extends BaseNode {
868
1168
  this.body = body;
869
1169
  this.isAsync = isAsync;
870
1170
  }
871
- async evaluate(context) {
1171
+ evaluate(context) {
872
1172
  const scope = context.scope;
873
1173
  const globals = context.globals;
874
1174
  const element = context.element;
875
- return async (...args) => {
1175
+ if (this.isAsync) {
1176
+ return (...args) => {
1177
+ const activeScope = scope?.createChild ? scope.createChild() : scope;
1178
+ const inner = {
1179
+ scope: activeScope,
1180
+ rootScope: context.rootScope,
1181
+ ...globals ? { globals } : {},
1182
+ ...element ? { element } : {},
1183
+ returnValue: void 0,
1184
+ returning: false,
1185
+ breaking: false,
1186
+ continuing: false
1187
+ };
1188
+ const previousValues = /* @__PURE__ */ new Map();
1189
+ const applyResult = activeScope ? this.applyParams(activeScope, previousValues, inner, args) : void 0;
1190
+ const bodyResult = resolveMaybe(applyResult, () => this.body.evaluate(inner));
1191
+ const finalResult = resolveMaybe(bodyResult, () => inner.returnValue);
1192
+ return Promise.resolve(finalResult).finally(() => {
1193
+ if (activeScope && activeScope === scope) {
1194
+ this.restoreParams(activeScope, previousValues);
1195
+ }
1196
+ });
1197
+ };
1198
+ }
1199
+ return (...args) => {
876
1200
  const activeScope = scope?.createChild ? scope.createChild() : scope;
877
1201
  const inner = {
878
1202
  scope: activeScope,
@@ -880,47 +1204,69 @@ var FunctionExpression = class extends BaseNode {
880
1204
  ...globals ? { globals } : {},
881
1205
  ...element ? { element } : {},
882
1206
  returnValue: void 0,
883
- returning: false
1207
+ returning: false,
1208
+ breaking: false,
1209
+ continuing: false
884
1210
  };
885
- if (activeScope) {
886
- const previousValues = /* @__PURE__ */ new Map();
887
- await this.applyParams(activeScope, previousValues, inner, args);
888
- await this.body.evaluate(inner);
889
- if (activeScope === scope) {
890
- this.restoreParams(activeScope, previousValues);
891
- }
892
- } else {
893
- await this.body.evaluate(inner);
1211
+ const previousValues = /* @__PURE__ */ new Map();
1212
+ const applyResult = activeScope ? this.applyParams(activeScope, previousValues, inner, args) : void 0;
1213
+ const bodyResult = resolveMaybe(applyResult, () => this.body.evaluate(inner));
1214
+ const finalResult = resolveMaybe(bodyResult, () => inner.returnValue);
1215
+ if (isPromiseLike(finalResult)) {
1216
+ return finalResult.finally(() => {
1217
+ if (activeScope && activeScope === scope) {
1218
+ this.restoreParams(activeScope, previousValues);
1219
+ }
1220
+ });
1221
+ }
1222
+ if (activeScope && activeScope === scope) {
1223
+ this.restoreParams(activeScope, previousValues);
894
1224
  }
895
- return inner.returnValue;
1225
+ return finalResult;
896
1226
  };
897
1227
  }
898
- async applyParams(scope, previousValues, context, args) {
899
- if (!scope || !scope.setPath) {
1228
+ applyParams(scope, previousValues, context, args) {
1229
+ if (!scope) {
900
1230
  return;
901
1231
  }
902
- let argIndex = 0;
903
- for (const param of this.params) {
904
- const name = param.name;
905
- if (!name) {
906
- continue;
907
- }
908
- previousValues.set(name, scope.getPath(name));
909
- if (param.rest) {
910
- scope.setPath(`self.${name}`, args.slice(argIndex));
911
- argIndex = args.length;
912
- continue;
913
- }
914
- let value = args[argIndex];
915
- if (value === void 0 && param.defaultValue) {
916
- value = await param.defaultValue.evaluate(context);
917
- }
918
- scope.setPath(`self.${name}`, value);
919
- argIndex += 1;
1232
+ const setPath = scope.setPath?.bind(scope);
1233
+ if (!setPath) {
1234
+ return;
920
1235
  }
1236
+ const params = this.params;
1237
+ const applyAt = (paramIndex, argIndex) => {
1238
+ for (let i = paramIndex; i < params.length; i += 1) {
1239
+ const param = params[i];
1240
+ const name = param.name;
1241
+ if (!name) {
1242
+ continue;
1243
+ }
1244
+ previousValues.set(name, scope.getPath(name));
1245
+ if (param.rest) {
1246
+ setPath(`self.${name}`, args.slice(argIndex));
1247
+ return;
1248
+ }
1249
+ let value = args[argIndex];
1250
+ if (value === void 0 && param.defaultValue) {
1251
+ const defaultValue = param.defaultValue.evaluate(context);
1252
+ return resolveMaybe(defaultValue, (resolvedDefault) => {
1253
+ setPath(`self.${name}`, resolvedDefault);
1254
+ return applyAt(i + 1, argIndex + 1);
1255
+ });
1256
+ }
1257
+ setPath(`self.${name}`, value);
1258
+ argIndex += 1;
1259
+ }
1260
+ return;
1261
+ };
1262
+ return applyAt(0, 0);
921
1263
  }
922
1264
  restoreParams(scope, previousValues) {
923
- if (!scope || !scope.setPath) {
1265
+ if (!scope) {
1266
+ return;
1267
+ }
1268
+ const setPath = scope.setPath?.bind(scope);
1269
+ if (!setPath) {
924
1270
  return;
925
1271
  }
926
1272
  for (const param of this.params) {
@@ -928,7 +1274,7 @@ var FunctionExpression = class extends BaseNode {
928
1274
  if (!name) {
929
1275
  continue;
930
1276
  }
931
- scope.setPath(name, previousValues.get(name));
1277
+ setPath(name, previousValues.get(name));
932
1278
  }
933
1279
  }
934
1280
  };
@@ -947,7 +1293,7 @@ var IdentifierExpression = class extends BaseNode {
947
1293
  super("Identifier");
948
1294
  this.name = name;
949
1295
  }
950
- async evaluate(context) {
1296
+ evaluate(context) {
951
1297
  if (this.name.startsWith("root.") && context.rootScope) {
952
1298
  const path = this.name.slice("root.".length);
953
1299
  return context.rootScope.getPath(`self.${path}`);
@@ -992,7 +1338,7 @@ var LiteralExpression = class extends BaseNode {
992
1338
  super("Literal");
993
1339
  this.value = value;
994
1340
  }
995
- async evaluate() {
1341
+ evaluate() {
996
1342
  return this.value;
997
1343
  }
998
1344
  };
@@ -1001,30 +1347,41 @@ var TemplateExpression = class extends BaseNode {
1001
1347
  super("TemplateExpression");
1002
1348
  this.parts = parts;
1003
1349
  }
1004
- async evaluate(context) {
1350
+ evaluate(context) {
1005
1351
  let result = "";
1006
- for (const part of this.parts) {
1007
- const value = await part.evaluate(context);
1008
- result += value == null ? "" : String(value);
1009
- }
1010
- return result;
1352
+ let index = 0;
1353
+ const run = () => {
1354
+ while (index < this.parts.length) {
1355
+ const part = this.parts[index];
1356
+ index += 1;
1357
+ const value = part.evaluate(context);
1358
+ return resolveMaybe(value, (resolved) => {
1359
+ result += resolved == null ? "" : String(resolved);
1360
+ return run();
1361
+ });
1362
+ }
1363
+ return result;
1364
+ };
1365
+ return run();
1011
1366
  }
1012
1367
  };
1013
1368
  var UnaryExpression = class extends BaseNode {
1014
1369
  constructor(operator, argument) {
1015
1370
  super("UnaryExpression");
1016
1371
  this.operator = operator;
1017
- this.argument = argument;
1018
- }
1019
- async evaluate(context) {
1020
- const value = await this.argument.evaluate(context);
1021
- if (this.operator === "!") {
1022
- return !value;
1023
- }
1024
- if (this.operator === "-") {
1025
- return -value;
1026
- }
1027
- return value;
1372
+ this.argument = argument;
1373
+ }
1374
+ evaluate(context) {
1375
+ const value = this.argument.evaluate(context);
1376
+ return resolveMaybe(value, (resolved) => {
1377
+ if (this.operator === "!") {
1378
+ return !resolved;
1379
+ }
1380
+ if (this.operator === "-") {
1381
+ return -resolved;
1382
+ }
1383
+ return resolved;
1384
+ });
1028
1385
  }
1029
1386
  };
1030
1387
  var BinaryExpression = class extends BaseNode {
@@ -1034,61 +1391,71 @@ var BinaryExpression = class extends BaseNode {
1034
1391
  this.left = left;
1035
1392
  this.right = right;
1036
1393
  }
1037
- async evaluate(context) {
1038
- if (this.operator === "&&") {
1039
- const leftValue = await this.left.evaluate(context);
1040
- return leftValue && await this.right.evaluate(context);
1041
- }
1042
- if (this.operator === "||") {
1043
- const leftValue = await this.left.evaluate(context);
1044
- return leftValue || await this.right.evaluate(context);
1045
- }
1046
- if (this.operator === "??") {
1047
- const leftValue = await this.left.evaluate(context);
1048
- return leftValue ?? await this.right.evaluate(context);
1049
- }
1050
- const left = await this.left.evaluate(context);
1051
- const right = await this.right.evaluate(context);
1052
- if (this.operator === "+") {
1053
- return left + right;
1054
- }
1055
- if (this.operator === "-") {
1056
- return left - right;
1057
- }
1058
- if (this.operator === "*") {
1059
- return left * right;
1060
- }
1061
- if (this.operator === "/") {
1062
- return left / right;
1063
- }
1064
- if (this.operator === "%") {
1065
- return left % right;
1066
- }
1067
- if (this.operator === "==") {
1068
- return left == right;
1069
- }
1070
- if (this.operator === "!=") {
1071
- return left != right;
1072
- }
1073
- if (this.operator === "===") {
1074
- return left === right;
1075
- }
1076
- if (this.operator === "!==") {
1077
- return left !== right;
1078
- }
1079
- if (this.operator === "<") {
1080
- return left < right;
1081
- }
1082
- if (this.operator === ">") {
1083
- return left > right;
1084
- }
1085
- if (this.operator === "<=") {
1086
- return left <= right;
1087
- }
1088
- if (this.operator === ">=") {
1089
- return left >= right;
1090
- }
1091
- return void 0;
1394
+ evaluate(context) {
1395
+ const leftValue = this.left.evaluate(context);
1396
+ return resolveMaybe(leftValue, (resolvedLeft) => {
1397
+ if (this.operator === "&&") {
1398
+ if (!resolvedLeft) {
1399
+ return resolvedLeft;
1400
+ }
1401
+ return this.right.evaluate(context);
1402
+ }
1403
+ if (this.operator === "||") {
1404
+ if (resolvedLeft) {
1405
+ return resolvedLeft;
1406
+ }
1407
+ return this.right.evaluate(context);
1408
+ }
1409
+ if (this.operator === "??") {
1410
+ if (resolvedLeft !== null && resolvedLeft !== void 0) {
1411
+ return resolvedLeft;
1412
+ }
1413
+ return this.right.evaluate(context);
1414
+ }
1415
+ const rightValue = this.right.evaluate(context);
1416
+ return resolveMaybe(rightValue, (resolvedRight) => {
1417
+ if (this.operator === "+") {
1418
+ return resolvedLeft + resolvedRight;
1419
+ }
1420
+ if (this.operator === "-") {
1421
+ return resolvedLeft - resolvedRight;
1422
+ }
1423
+ if (this.operator === "*") {
1424
+ return resolvedLeft * resolvedRight;
1425
+ }
1426
+ if (this.operator === "/") {
1427
+ return resolvedLeft / resolvedRight;
1428
+ }
1429
+ if (this.operator === "%") {
1430
+ return resolvedLeft % resolvedRight;
1431
+ }
1432
+ if (this.operator === "==") {
1433
+ return resolvedLeft == resolvedRight;
1434
+ }
1435
+ if (this.operator === "!=") {
1436
+ return resolvedLeft != resolvedRight;
1437
+ }
1438
+ if (this.operator === "===") {
1439
+ return resolvedLeft === resolvedRight;
1440
+ }
1441
+ if (this.operator === "!==") {
1442
+ return resolvedLeft !== resolvedRight;
1443
+ }
1444
+ if (this.operator === "<") {
1445
+ return resolvedLeft < resolvedRight;
1446
+ }
1447
+ if (this.operator === ">") {
1448
+ return resolvedLeft > resolvedRight;
1449
+ }
1450
+ if (this.operator === "<=") {
1451
+ return resolvedLeft <= resolvedRight;
1452
+ }
1453
+ if (this.operator === ">=") {
1454
+ return resolvedLeft >= resolvedRight;
1455
+ }
1456
+ return void 0;
1457
+ });
1458
+ });
1092
1459
  }
1093
1460
  };
1094
1461
  var TernaryExpression = class extends BaseNode {
@@ -1098,12 +1465,14 @@ var TernaryExpression = class extends BaseNode {
1098
1465
  this.consequent = consequent;
1099
1466
  this.alternate = alternate;
1100
1467
  }
1101
- async evaluate(context) {
1102
- const condition = await this.test.evaluate(context);
1103
- if (condition) {
1104
- return this.consequent.evaluate(context);
1105
- }
1106
- return this.alternate.evaluate(context);
1468
+ evaluate(context) {
1469
+ const condition = this.test.evaluate(context);
1470
+ return resolveMaybe(condition, (resolved) => {
1471
+ if (resolved) {
1472
+ return this.consequent.evaluate(context);
1473
+ }
1474
+ return this.alternate.evaluate(context);
1475
+ });
1107
1476
  }
1108
1477
  };
1109
1478
  var MemberExpression = class _MemberExpression extends BaseNode {
@@ -1113,11 +1482,11 @@ var MemberExpression = class _MemberExpression extends BaseNode {
1113
1482
  this.property = property;
1114
1483
  this.optional = optional;
1115
1484
  }
1116
- async evaluate(context) {
1117
- const resolved = await this.resolve(context);
1118
- return resolved?.value;
1485
+ evaluate(context) {
1486
+ const resolved = this.resolve(context);
1487
+ return resolveMaybe(resolved, (resolvedValue) => resolvedValue?.value);
1119
1488
  }
1120
- async resolve(context) {
1489
+ resolve(context) {
1121
1490
  const path = this.getIdentifierPath();
1122
1491
  if (path) {
1123
1492
  const resolved = this.resolveFromScope(context, path);
@@ -1129,11 +1498,13 @@ var MemberExpression = class _MemberExpression extends BaseNode {
1129
1498
  return resolvedGlobal;
1130
1499
  }
1131
1500
  }
1132
- const target = await this.target.evaluate(context);
1133
- if (target == null) {
1134
- return { value: void 0, target, optional: this.optional };
1135
- }
1136
- return { value: target[this.property], target, optional: this.optional };
1501
+ const target = this.target.evaluate(context);
1502
+ return resolveMaybe(target, (resolvedTarget) => {
1503
+ if (resolvedTarget == null) {
1504
+ return { value: void 0, target: resolvedTarget, optional: this.optional };
1505
+ }
1506
+ return { value: resolvedTarget[this.property], target: resolvedTarget, optional: this.optional };
1507
+ });
1137
1508
  }
1138
1509
  getIdentifierPath() {
1139
1510
  const targetPath = this.getTargetIdentifierPath();
@@ -1209,25 +1580,39 @@ var CallExpression = class extends BaseNode {
1209
1580
  this.callee = callee;
1210
1581
  this.args = args;
1211
1582
  }
1212
- async evaluate(context) {
1213
- const resolved = await this.resolveCallee(context);
1214
- const fn = resolved?.fn ?? await this.callee.evaluate(context);
1215
- if (typeof fn !== "function") {
1216
- return void 0;
1217
- }
1218
- const values = [];
1219
- for (const arg of this.args) {
1220
- values.push(await arg.evaluate(context));
1221
- }
1222
- return fn.apply(resolved?.thisArg, values);
1583
+ evaluate(context) {
1584
+ const resolved = this.resolveCallee(context);
1585
+ return resolveMaybe(resolved, (resolvedCallee) => {
1586
+ const fnValue = resolvedCallee?.fn ?? this.callee.evaluate(context);
1587
+ return resolveMaybe(fnValue, (resolvedFn) => {
1588
+ if (typeof resolvedFn !== "function") {
1589
+ return void 0;
1590
+ }
1591
+ const values = [];
1592
+ const evalArgs = (index) => {
1593
+ for (let i = index; i < this.args.length; i += 1) {
1594
+ const arg = this.args[i];
1595
+ const argValue = arg.evaluate(context);
1596
+ return resolveMaybe(argValue, (resolvedArg) => {
1597
+ values.push(resolvedArg);
1598
+ return evalArgs(i + 1);
1599
+ });
1600
+ }
1601
+ return resolvedFn.apply(resolvedCallee?.thisArg, values);
1602
+ };
1603
+ return evalArgs(0);
1604
+ });
1605
+ });
1223
1606
  }
1224
- async resolveCallee(context) {
1607
+ resolveCallee(context) {
1225
1608
  if (this.callee instanceof MemberExpression) {
1226
- const resolved = await this.callee.resolve(context);
1227
- if (!resolved) {
1228
- return void 0;
1229
- }
1230
- return { fn: resolved.value, thisArg: resolved.target };
1609
+ const resolved = this.callee.resolve(context);
1610
+ return resolveMaybe(resolved, (resolvedValue) => {
1611
+ if (!resolvedValue) {
1612
+ return void 0;
1613
+ }
1614
+ return { fn: resolvedValue.value, thisArg: resolvedValue.target };
1615
+ });
1231
1616
  }
1232
1617
  if (!(this.callee instanceof IdentifierExpression)) {
1233
1618
  return void 0;
@@ -1269,27 +1654,40 @@ var ArrayExpression = class extends BaseNode {
1269
1654
  super("ArrayExpression");
1270
1655
  this.elements = elements;
1271
1656
  }
1272
- async evaluate(context) {
1657
+ evaluate(context) {
1273
1658
  const values = [];
1274
- for (const element of this.elements) {
1275
- if (element instanceof SpreadElement) {
1276
- const spreadValue = await element.value.evaluate(context);
1277
- if (spreadValue == null) {
1278
- continue;
1659
+ const pushElements = (value) => {
1660
+ if (value == null) {
1661
+ return;
1662
+ }
1663
+ const iterator = value[Symbol.iterator];
1664
+ if (typeof iterator === "function") {
1665
+ for (const entry of value) {
1666
+ values.push(entry);
1279
1667
  }
1280
- const iterator = spreadValue[Symbol.iterator];
1281
- if (typeof iterator === "function") {
1282
- for (const entry of spreadValue) {
1283
- values.push(entry);
1284
- }
1285
- } else {
1286
- values.push(spreadValue);
1668
+ } else {
1669
+ values.push(value);
1670
+ }
1671
+ };
1672
+ const evalAt = (index) => {
1673
+ for (let i = index; i < this.elements.length; i += 1) {
1674
+ const element = this.elements[i];
1675
+ if (element instanceof SpreadElement) {
1676
+ const spreadValue = element.value.evaluate(context);
1677
+ return resolveMaybe(spreadValue, (resolvedSpread) => {
1678
+ pushElements(resolvedSpread);
1679
+ return evalAt(i + 1);
1680
+ });
1287
1681
  }
1288
- continue;
1682
+ const value = element.evaluate(context);
1683
+ return resolveMaybe(value, (resolvedValue) => {
1684
+ values.push(resolvedValue);
1685
+ return evalAt(i + 1);
1686
+ });
1289
1687
  }
1290
- values.push(await element.evaluate(context));
1291
- }
1292
- return values;
1688
+ return values;
1689
+ };
1690
+ return evalAt(0);
1293
1691
  }
1294
1692
  };
1295
1693
  var ObjectExpression = class extends BaseNode {
@@ -1297,24 +1695,39 @@ var ObjectExpression = class extends BaseNode {
1297
1695
  super("ObjectExpression");
1298
1696
  this.entries = entries;
1299
1697
  }
1300
- async evaluate(context) {
1698
+ evaluate(context) {
1301
1699
  const result = {};
1302
- for (const entry of this.entries) {
1303
- if ("spread" in entry) {
1304
- const spreadValue = await entry.spread.evaluate(context);
1305
- if (spreadValue != null) {
1306
- Object.assign(result, spreadValue);
1700
+ const evalAt = (index) => {
1701
+ for (let i = index; i < this.entries.length; i += 1) {
1702
+ const entry = this.entries[i];
1703
+ if ("spread" in entry) {
1704
+ const spreadValue = entry.spread.evaluate(context);
1705
+ return resolveMaybe(spreadValue, (resolvedSpread) => {
1706
+ if (resolvedSpread != null) {
1707
+ Object.assign(result, resolvedSpread);
1708
+ }
1709
+ return evalAt(i + 1);
1710
+ });
1307
1711
  }
1308
- continue;
1309
- }
1310
- if ("computed" in entry && entry.computed) {
1311
- const keyValue = await entry.keyExpr.evaluate(context);
1312
- result[String(keyValue)] = await entry.value.evaluate(context);
1313
- } else {
1314
- result[entry.key] = await entry.value.evaluate(context);
1712
+ if ("computed" in entry && entry.computed) {
1713
+ const keyValue = entry.keyExpr.evaluate(context);
1714
+ return resolveMaybe(keyValue, (resolvedKey) => {
1715
+ const entryValue = entry.value.evaluate(context);
1716
+ return resolveMaybe(entryValue, (resolvedValue) => {
1717
+ result[String(resolvedKey)] = resolvedValue;
1718
+ return evalAt(i + 1);
1719
+ });
1720
+ });
1721
+ }
1722
+ const value = entry.value.evaluate(context);
1723
+ return resolveMaybe(value, (resolvedValue) => {
1724
+ result[entry.key] = resolvedValue;
1725
+ return evalAt(i + 1);
1726
+ });
1315
1727
  }
1316
- }
1317
- return result;
1728
+ return result;
1729
+ };
1730
+ return evalAt(0);
1318
1731
  }
1319
1732
  };
1320
1733
  var IndexExpression = class extends BaseNode {
@@ -1323,16 +1736,30 @@ var IndexExpression = class extends BaseNode {
1323
1736
  this.target = target;
1324
1737
  this.index = index;
1325
1738
  }
1326
- async evaluate(context) {
1327
- const target = await this.target.evaluate(context);
1328
- if (target == null) {
1329
- return void 0;
1330
- }
1331
- const index = await this.index.evaluate(context);
1332
- if (index == null) {
1333
- return void 0;
1739
+ evaluate(context) {
1740
+ const target = this.target.evaluate(context);
1741
+ return resolveMaybe(target, (resolvedTarget) => {
1742
+ if (resolvedTarget == null) {
1743
+ return void 0;
1744
+ }
1745
+ const index = this.index.evaluate(context);
1746
+ return resolveMaybe(index, (resolvedIndex) => {
1747
+ if (resolvedIndex == null) {
1748
+ return void 0;
1749
+ }
1750
+ const key = this.normalizeIndexKey(resolvedTarget, resolvedIndex);
1751
+ return resolvedTarget[key];
1752
+ });
1753
+ });
1754
+ }
1755
+ normalizeIndexKey(target, index) {
1756
+ if (Array.isArray(target) && typeof index === "string" && index.trim() !== "") {
1757
+ const numeric = Number(index);
1758
+ if (!Number.isNaN(numeric)) {
1759
+ return numeric;
1760
+ }
1334
1761
  }
1335
- return target[index];
1762
+ return index;
1336
1763
  }
1337
1764
  };
1338
1765
  var DirectiveExpression = class extends BaseNode {
@@ -1341,7 +1768,7 @@ var DirectiveExpression = class extends BaseNode {
1341
1768
  this.kind = kind;
1342
1769
  this.name = name;
1343
1770
  }
1344
- async evaluate(context) {
1771
+ evaluate(context) {
1345
1772
  const element = context.element;
1346
1773
  if (!element) {
1347
1774
  return `${this.kind}:${this.name}`;
@@ -1355,6 +1782,12 @@ var DirectiveExpression = class extends BaseNode {
1355
1782
  return element.value;
1356
1783
  }
1357
1784
  }
1785
+ if (this.name === "text" && element instanceof HTMLElement) {
1786
+ return element.innerText;
1787
+ }
1788
+ if (this.name === "content" && element instanceof HTMLElement) {
1789
+ return element.textContent ?? "";
1790
+ }
1358
1791
  if (this.name === "checked" && element instanceof HTMLInputElement) {
1359
1792
  return element.checked;
1360
1793
  }
@@ -1374,9 +1807,9 @@ var AwaitExpression = class extends BaseNode {
1374
1807
  super("AwaitExpression");
1375
1808
  this.argument = argument;
1376
1809
  }
1377
- async evaluate(context) {
1378
- const value = await this.argument.evaluate(context);
1379
- return await value;
1810
+ evaluate(context) {
1811
+ const value = this.argument.evaluate(context);
1812
+ return Promise.resolve(value);
1380
1813
  }
1381
1814
  };
1382
1815
  var QueryExpression = class extends BaseNode {
@@ -1385,7 +1818,7 @@ var QueryExpression = class extends BaseNode {
1385
1818
  this.direction = direction;
1386
1819
  this.selector = selector;
1387
1820
  }
1388
- async evaluate(context) {
1821
+ evaluate(context) {
1389
1822
  const selector = this.selector.trim();
1390
1823
  if (!selector) {
1391
1824
  return [];
@@ -1451,6 +1884,9 @@ var TokenStream = class {
1451
1884
  let count = 0;
1452
1885
  for (let i = this.index; i < this.tokens.length; i++) {
1453
1886
  const token = this.tokens[i];
1887
+ if (!token) {
1888
+ continue;
1889
+ }
1454
1890
  if (token.type === "Whitespace" /* Whitespace */) {
1455
1891
  continue;
1456
1892
  }
@@ -1461,6 +1897,29 @@ var TokenStream = class {
1461
1897
  }
1462
1898
  return null;
1463
1899
  }
1900
+ indexAfterDelimited(openType, closeType, offset = 0) {
1901
+ const first = this.peekNonWhitespace(offset);
1902
+ if (!first || first.type !== openType) {
1903
+ return null;
1904
+ }
1905
+ let index = offset + 1;
1906
+ let depth = 1;
1907
+ while (true) {
1908
+ const token = this.peekNonWhitespace(index);
1909
+ if (!token) {
1910
+ return null;
1911
+ }
1912
+ if (token.type === openType) {
1913
+ depth += 1;
1914
+ } else if (token.type === closeType) {
1915
+ depth -= 1;
1916
+ if (depth === 0) {
1917
+ return index + 1;
1918
+ }
1919
+ }
1920
+ index += 1;
1921
+ }
1922
+ }
1464
1923
  };
1465
1924
 
1466
1925
  // src/parser/parser.ts
@@ -1468,11 +1927,14 @@ var Parser = class _Parser {
1468
1927
  stream;
1469
1928
  source;
1470
1929
  customFlags;
1930
+ behaviorFlags;
1471
1931
  allowImplicitSemicolon = false;
1472
1932
  awaitStack = [];
1933
+ functionDepth = 0;
1473
1934
  constructor(input, options) {
1474
1935
  this.source = input;
1475
- this.customFlags = options?.customFlags ?? /* @__PURE__ */ new Set();
1936
+ this.customFlags = options?.customFlags ?? /* @__PURE__ */ new Set(["important", "trusted", "debounce"]);
1937
+ this.behaviorFlags = options?.behaviorFlags ?? /* @__PURE__ */ new Set();
1476
1938
  const lexer = new Lexer(input);
1477
1939
  this.stream = new TokenStream(lexer.tokenize());
1478
1940
  }
@@ -1512,8 +1974,9 @@ var Parser = class _Parser {
1512
1974
  this.stream.skipWhitespace();
1513
1975
  this.stream.expect("Behavior" /* Behavior */);
1514
1976
  const selector = this.parseSelector();
1977
+ const { flags, flagArgs } = this.parseBehaviorFlags();
1515
1978
  const body = this.parseBlock({ allowDeclarations: true });
1516
- return new BehaviorNode(selector, body);
1979
+ return new BehaviorNode(selector, body, flags, flagArgs);
1517
1980
  });
1518
1981
  }
1519
1982
  parseSelector() {
@@ -1527,6 +1990,9 @@ var Parser = class _Parser {
1527
1990
  if (token.type === "LBrace" /* LBrace */) {
1528
1991
  break;
1529
1992
  }
1993
+ if (token.type === "Bang" /* Bang */) {
1994
+ break;
1995
+ }
1530
1996
  if (token.type === "Whitespace" /* Whitespace */) {
1531
1997
  this.stream.next();
1532
1998
  if (sawNonWhitespace && selectorText[selectorText.length - 1] !== " ") {
@@ -1542,6 +2008,10 @@ var Parser = class _Parser {
1542
2008
  }
1543
2009
  return new SelectorNode(selectorText.trim());
1544
2010
  }
2011
+ parseBehaviorFlags() {
2012
+ const result = this.parseFlags(this.behaviorFlags, "behavior modifier");
2013
+ return { flags: result.flags, flagArgs: result.flagArgs };
2014
+ }
1545
2015
  parseUseStatement() {
1546
2016
  return this.wrapErrors(() => {
1547
2017
  this.stream.expect("Use" /* Use */);
@@ -1625,6 +2095,7 @@ ${caret}`;
1625
2095
  }
1626
2096
  parseBlock(options) {
1627
2097
  const allowDeclarations = options?.allowDeclarations ?? false;
2098
+ const allowReturn = options?.allowReturn ?? this.functionDepth > 0;
1628
2099
  this.stream.skipWhitespace();
1629
2100
  this.stream.expect("LBrace" /* LBrace */);
1630
2101
  const statements = [];
@@ -1686,7 +2157,7 @@ ${caret}`;
1686
2157
  }
1687
2158
  sawConstruct = true;
1688
2159
  }
1689
- statements.push(this.parseStatement());
2160
+ statements.push(this.parseStatement({ allowReturn }));
1690
2161
  }
1691
2162
  }
1692
2163
  return new BlockNode(statements);
@@ -1705,6 +2176,15 @@ ${caret}`;
1705
2176
  }
1706
2177
  return this.parseReturnStatement();
1707
2178
  }
2179
+ if (next.type === "Assert" /* Assert */) {
2180
+ return this.parseAssertStatement();
2181
+ }
2182
+ if (next.type === "Break" /* Break */) {
2183
+ return this.parseBreakStatement();
2184
+ }
2185
+ if (next.type === "Continue" /* Continue */) {
2186
+ return this.parseContinueStatement();
2187
+ }
1708
2188
  if (allowBlocks && next.type === "On" /* On */) {
1709
2189
  return this.parseOnBlock();
1710
2190
  }
@@ -1740,44 +2220,6 @@ ${caret}`;
1740
2220
  }
1741
2221
  throw new Error(`Unexpected token ${next.type}`);
1742
2222
  }
1743
- parseStateBlock() {
1744
- this.stream.expect("State" /* State */);
1745
- this.stream.skipWhitespace();
1746
- this.stream.expect("LBrace" /* LBrace */);
1747
- const entries = [];
1748
- while (true) {
1749
- this.stream.skipWhitespace();
1750
- const next = this.stream.peek();
1751
- if (!next) {
1752
- throw new Error("Unterminated state block");
1753
- }
1754
- if (next.type === "RBrace" /* RBrace */) {
1755
- this.stream.next();
1756
- break;
1757
- }
1758
- const nameToken = this.stream.expect("Identifier" /* Identifier */);
1759
- this.stream.skipWhitespace();
1760
- this.stream.expect("Colon" /* Colon */);
1761
- this.stream.skipWhitespace();
1762
- const value = this.parseExpression();
1763
- this.stream.skipWhitespace();
1764
- let important = false;
1765
- if (this.stream.peek()?.type === "Bang" /* Bang */) {
1766
- this.stream.next();
1767
- this.stream.skipWhitespace();
1768
- const importantToken = this.stream.next();
1769
- if (importantToken.type === "Identifier" /* Identifier */ && importantToken.value === "important") {
1770
- important = true;
1771
- } else {
1772
- throw new Error("Expected 'important' after '!'");
1773
- }
1774
- }
1775
- this.stream.skipWhitespace();
1776
- this.stream.expect("Semicolon" /* Semicolon */);
1777
- entries.push(new StateEntryNode(nameToken.value, value, important));
1778
- }
1779
- return new StateBlockNode(entries);
1780
- }
1781
2223
  parseOnBlock() {
1782
2224
  this.stream.expect("On" /* On */);
1783
2225
  this.stream.skipWhitespace();
@@ -1805,22 +2247,9 @@ ${caret}`;
1805
2247
  }
1806
2248
  throw new Error(`Unexpected token in on() args: ${next.type}`);
1807
2249
  }
1808
- const modifiers = this.parseOnModifiers();
2250
+ const { flags, flagArgs } = this.parseFlags(this.customFlags, "flag");
1809
2251
  const body = this.parseBlock({ allowDeclarations: false });
1810
- return new OnBlockNode(event, args, body, modifiers);
1811
- }
1812
- parseOnModifiers() {
1813
- const modifiers = [];
1814
- while (true) {
1815
- this.stream.skipWhitespace();
1816
- if (this.stream.peek()?.type !== "Bang" /* Bang */) {
1817
- break;
1818
- }
1819
- this.stream.next();
1820
- const name = this.stream.expect("Identifier" /* Identifier */).value;
1821
- modifiers.push(name);
1822
- }
1823
- return modifiers;
2252
+ return new OnBlockNode(event, args, body, flags, flagArgs);
1824
2253
  }
1825
2254
  parseAssignment() {
1826
2255
  const target = this.parseAssignmentTarget();
@@ -2030,6 +2459,11 @@ ${caret}`;
2030
2459
  if (!token) {
2031
2460
  throw new Error("Expected expression");
2032
2461
  }
2462
+ if (token.type === "PlusPlus" /* PlusPlus */ || token.type === "MinusMinus" /* MinusMinus */) {
2463
+ this.stream.next();
2464
+ const argument = this.parseUnaryExpression();
2465
+ return this.createIncrementNode(token, argument, true);
2466
+ }
2033
2467
  if (token.type === "Bang" /* Bang */) {
2034
2468
  this.stream.next();
2035
2469
  const argument = this.parseUnaryExpression();
@@ -2045,7 +2479,31 @@ ${caret}`;
2045
2479
  const argument = this.parseUnaryExpression();
2046
2480
  return new AwaitExpression(argument);
2047
2481
  }
2048
- return this.parseCallExpression();
2482
+ return this.parsePostfixExpression();
2483
+ }
2484
+ parsePostfixExpression() {
2485
+ let expr = this.parseCallExpression();
2486
+ while (true) {
2487
+ this.stream.skipWhitespace();
2488
+ const token = this.stream.peek();
2489
+ if (!token) {
2490
+ break;
2491
+ }
2492
+ if (token.type === "PlusPlus" /* PlusPlus */ || token.type === "MinusMinus" /* MinusMinus */) {
2493
+ this.stream.next();
2494
+ expr = this.createIncrementNode(token, expr, false);
2495
+ continue;
2496
+ }
2497
+ break;
2498
+ }
2499
+ return expr;
2500
+ }
2501
+ createIncrementNode(token, argument, prefix) {
2502
+ if (!(argument instanceof IdentifierExpression) && !(argument instanceof MemberExpression) && !(argument instanceof IndexExpression) && !(argument instanceof DirectiveExpression)) {
2503
+ throw new Error("Increment/decrement requires a mutable target");
2504
+ }
2505
+ const operator = token.type === "PlusPlus" /* PlusPlus */ ? "++" : "--";
2506
+ return new AssignmentNode(argument, new LiteralExpression(1), operator, prefix);
2049
2507
  }
2050
2508
  parseCallExpression() {
2051
2509
  let expr = this.parsePrimaryExpression();
@@ -2357,6 +2815,7 @@ ${caret}`;
2357
2815
  this.stream.expect("LBrace" /* LBrace */);
2358
2816
  const statements = [];
2359
2817
  this.awaitStack.push(allowAwait);
2818
+ this.functionDepth += 1;
2360
2819
  try {
2361
2820
  while (true) {
2362
2821
  this.stream.skipWhitespace();
@@ -2368,9 +2827,10 @@ ${caret}`;
2368
2827
  this.stream.next();
2369
2828
  break;
2370
2829
  }
2371
- statements.push(this.parseStatement({ allowBlocks: false, allowReturn: true }));
2830
+ statements.push(this.parseStatement({ allowBlocks: true, allowReturn: true }));
2372
2831
  }
2373
2832
  } finally {
2833
+ this.functionDepth -= 1;
2374
2834
  this.awaitStack.pop();
2375
2835
  }
2376
2836
  return new BlockNode(statements);
@@ -2566,7 +3026,7 @@ ${caret}`;
2566
3026
  const operator = this.parseDeclarationOperator();
2567
3027
  this.stream.skipWhitespace();
2568
3028
  const value = this.parseExpression();
2569
- const { flags, flagArgs } = this.parseFlags();
3029
+ const { flags, flagArgs } = this.parseFlags(this.customFlags, "flag");
2570
3030
  this.stream.skipWhitespace();
2571
3031
  this.stream.expect("Semicolon" /* Semicolon */);
2572
3032
  return new DeclarationNode(target, operator, value, flags, flagArgs);
@@ -2607,7 +3067,7 @@ ${caret}`;
2607
3067
  }
2608
3068
  return ":";
2609
3069
  }
2610
- parseFlags() {
3070
+ parseFlags(allowed, errorLabel) {
2611
3071
  const flags = {};
2612
3072
  const flagArgs = {};
2613
3073
  while (true) {
@@ -2617,59 +3077,107 @@ ${caret}`;
2617
3077
  }
2618
3078
  this.stream.next();
2619
3079
  const name = this.stream.expect("Identifier" /* Identifier */).value;
2620
- if (name === "important") {
2621
- flags.important = true;
2622
- } else if (name === "trusted") {
2623
- flags.trusted = true;
2624
- } else if (name === "debounce") {
2625
- flags.debounce = true;
2626
- if (this.stream.peek()?.type === "LParen" /* LParen */) {
2627
- this.stream.next();
2628
- this.stream.skipWhitespace();
2629
- const numberToken = this.stream.expect("Number" /* Number */);
2630
- flagArgs.debounce = Number(numberToken.value);
2631
- this.stream.skipWhitespace();
2632
- this.stream.expect("RParen" /* RParen */);
2633
- } else {
2634
- flagArgs.debounce = 200;
2635
- }
2636
- } else if (this.customFlags.has(name)) {
2637
- flags[name] = true;
2638
- const customArg = this.parseCustomFlagArg();
2639
- if (customArg !== void 0) {
2640
- flagArgs[name] = customArg;
2641
- }
3080
+ if (allowed && !allowed.has(name)) {
3081
+ throw new Error(`Unknown ${errorLabel} ${name}`);
3082
+ }
3083
+ flags[name] = true;
3084
+ const customArg = this.parseCustomFlagArg();
3085
+ if (customArg !== void 0) {
3086
+ flagArgs[name] = customArg;
3087
+ }
3088
+ }
3089
+ return { flags, flagArgs };
3090
+ }
3091
+ parseCustomFlagArg() {
3092
+ if (this.stream.peek()?.type !== "LParen" /* LParen */) {
3093
+ return void 0;
3094
+ }
3095
+ this.stream.next();
3096
+ this.stream.skipWhitespace();
3097
+ const token = this.stream.peek();
3098
+ if (!token) {
3099
+ throw new Error("Unterminated flag arguments");
3100
+ }
3101
+ const value = this.parseCustomFlagLiteral();
3102
+ this.stream.skipWhitespace();
3103
+ this.stream.expect("RParen" /* RParen */);
3104
+ return value;
3105
+ }
3106
+ parseCustomFlagLiteral() {
3107
+ const token = this.stream.peek();
3108
+ if (!token) {
3109
+ throw new Error("Unterminated flag arguments");
3110
+ }
3111
+ if (token.type === "Number" /* Number */) {
3112
+ return Number(this.stream.next().value);
3113
+ }
3114
+ if (token.type === "String" /* String */) {
3115
+ return this.stream.next().value;
3116
+ }
3117
+ if (token.type === "Boolean" /* Boolean */) {
3118
+ return this.stream.next().value === "true";
3119
+ }
3120
+ if (token.type === "Identifier" /* Identifier */) {
3121
+ return this.stream.next().value;
3122
+ }
3123
+ if (token.type === "LBracket" /* LBracket */) {
3124
+ return this.parseCustomFlagArray();
3125
+ }
3126
+ if (token.type === "LBrace" /* LBrace */) {
3127
+ return this.parseCustomFlagObject();
3128
+ }
3129
+ throw new Error(`Unsupported flag argument ${token.type}`);
3130
+ }
3131
+ parseCustomFlagArray() {
3132
+ this.stream.expect("LBracket" /* LBracket */);
3133
+ const items = [];
3134
+ while (true) {
3135
+ this.stream.skipWhitespace();
3136
+ const next = this.stream.peek();
3137
+ if (!next) {
3138
+ throw new Error("Unterminated flag array");
3139
+ }
3140
+ if (next.type === "RBracket" /* RBracket */) {
3141
+ this.stream.next();
3142
+ break;
3143
+ }
3144
+ items.push(this.parseCustomFlagLiteral());
3145
+ this.stream.skipWhitespace();
3146
+ if (this.stream.peek()?.type === "Comma" /* Comma */) {
3147
+ this.stream.next();
3148
+ }
3149
+ }
3150
+ return items;
3151
+ }
3152
+ parseCustomFlagObject() {
3153
+ this.stream.expect("LBrace" /* LBrace */);
3154
+ const obj = {};
3155
+ while (true) {
3156
+ this.stream.skipWhitespace();
3157
+ const next = this.stream.peek();
3158
+ if (!next) {
3159
+ throw new Error("Unterminated flag object");
3160
+ }
3161
+ if (next.type === "RBrace" /* RBrace */) {
3162
+ this.stream.next();
3163
+ break;
3164
+ }
3165
+ let key;
3166
+ if (next.type === "Identifier" /* Identifier */ || next.type === "String" /* String */) {
3167
+ key = this.stream.next().value;
2642
3168
  } else {
2643
- throw new Error(`Unknown flag ${name}`);
3169
+ throw new Error(`Unsupported flag object key ${next.type}`);
3170
+ }
3171
+ this.stream.skipWhitespace();
3172
+ this.stream.expect("Colon" /* Colon */);
3173
+ this.stream.skipWhitespace();
3174
+ obj[key] = this.parseCustomFlagLiteral();
3175
+ this.stream.skipWhitespace();
3176
+ if (this.stream.peek()?.type === "Comma" /* Comma */) {
3177
+ this.stream.next();
2644
3178
  }
2645
3179
  }
2646
- return { flags, flagArgs };
2647
- }
2648
- parseCustomFlagArg() {
2649
- if (this.stream.peek()?.type !== "LParen" /* LParen */) {
2650
- return void 0;
2651
- }
2652
- this.stream.next();
2653
- this.stream.skipWhitespace();
2654
- const token = this.stream.peek();
2655
- if (!token) {
2656
- throw new Error("Unterminated flag arguments");
2657
- }
2658
- let value;
2659
- if (token.type === "Number" /* Number */) {
2660
- value = Number(this.stream.next().value);
2661
- } else if (token.type === "String" /* String */) {
2662
- value = this.stream.next().value;
2663
- } else if (token.type === "Boolean" /* Boolean */) {
2664
- value = this.stream.next().value === "true";
2665
- } else if (token.type === "Identifier" /* Identifier */) {
2666
- value = this.stream.next().value;
2667
- } else {
2668
- throw new Error(`Unsupported flag argument ${token.type}`);
2669
- }
2670
- this.stream.skipWhitespace();
2671
- this.stream.expect("RParen" /* RParen */);
2672
- return value;
3180
+ return obj;
2673
3181
  }
2674
3182
  isDeclarationStart() {
2675
3183
  const first = this.stream.peekNonWhitespace(0);
@@ -2694,33 +3202,29 @@ ${caret}`;
2694
3202
  }
2695
3203
  if (first.type === "Identifier" /* Identifier */) {
2696
3204
  let index = 1;
2697
- while (this.stream.peekNonWhitespace(index)?.type === "Dot" /* Dot */ && this.stream.peekNonWhitespace(index + 1)?.type === "Identifier" /* Identifier */) {
2698
- index += 2;
2699
- }
2700
- while (this.stream.peekNonWhitespace(index)?.type === "LBracket" /* LBracket */) {
2701
- let depth = 0;
2702
- while (true) {
2703
- const token = this.stream.peekNonWhitespace(index);
2704
- if (!token) {
3205
+ while (true) {
3206
+ const token = this.stream.peekNonWhitespace(index);
3207
+ if (!token) {
3208
+ return false;
3209
+ }
3210
+ if (token.type === "Dot" /* Dot */ && this.stream.peekNonWhitespace(index + 1)?.type === "Identifier" /* Identifier */) {
3211
+ index += 2;
3212
+ continue;
3213
+ }
3214
+ if (token.type === "LBracket" /* LBracket */) {
3215
+ const indexAfter = this.stream.indexAfterDelimited("LBracket" /* LBracket */, "RBracket" /* RBracket */, index);
3216
+ if (indexAfter === null) {
2705
3217
  return false;
2706
3218
  }
2707
- if (token.type === "LBracket" /* LBracket */) {
2708
- depth += 1;
2709
- } else if (token.type === "RBracket" /* RBracket */) {
2710
- depth -= 1;
2711
- if (depth === 0) {
2712
- index += 1;
2713
- break;
2714
- }
2715
- }
2716
- index += 1;
3219
+ index = indexAfter;
3220
+ continue;
2717
3221
  }
3222
+ break;
2718
3223
  }
2719
3224
  return this.isAssignmentOperatorStart(index);
2720
3225
  }
2721
3226
  if (first.type === "At" /* At */ || first.type === "Dollar" /* Dollar */) {
2722
3227
  const second = this.stream.peekNonWhitespace(1);
2723
- const third = this.stream.peekNonWhitespace(2);
2724
3228
  return second?.type === "Identifier" /* Identifier */ && this.isAssignmentOperatorStart(2);
2725
3229
  }
2726
3230
  if (first.type === "LBrace" /* LBrace */ || first.type === "LBracket" /* LBracket */) {
@@ -2758,17 +3262,6 @@ ${caret}`;
2758
3262
  }
2759
3263
  return false;
2760
3264
  }
2761
- isCallStart() {
2762
- const first = this.stream.peekNonWhitespace(0);
2763
- if (!first || first.type !== "Identifier" /* Identifier */) {
2764
- return false;
2765
- }
2766
- let index = 1;
2767
- while (this.stream.peekNonWhitespace(index)?.type === "Dot" /* Dot */ && this.stream.peekNonWhitespace(index + 1)?.type === "Identifier" /* Identifier */) {
2768
- index += 2;
2769
- }
2770
- return this.stream.peekNonWhitespace(index)?.type === "LParen" /* LParen */;
2771
- }
2772
3265
  isExpressionStatementStart() {
2773
3266
  const first = this.stream.peekNonWhitespace(0);
2774
3267
  if (!first) {
@@ -2798,50 +3291,22 @@ ${caret}`;
2798
3291
  if (this.stream.peekNonWhitespace(index)?.type !== "LParen" /* LParen */) {
2799
3292
  return false;
2800
3293
  }
2801
- index += 1;
2802
- let depth = 1;
2803
- while (true) {
2804
- const token = this.stream.peekNonWhitespace(index);
2805
- if (!token) {
2806
- return false;
2807
- }
2808
- if (token.type === "LParen" /* LParen */) {
2809
- depth += 1;
2810
- } else if (token.type === "RParen" /* RParen */) {
2811
- depth -= 1;
2812
- if (depth === 0) {
2813
- index += 1;
2814
- break;
2815
- }
2816
- }
2817
- index += 1;
3294
+ const indexAfterParams = this.stream.indexAfterDelimited("LParen" /* LParen */, "RParen" /* RParen */, index);
3295
+ if (indexAfterParams === null) {
3296
+ return false;
2818
3297
  }
2819
- return this.stream.peekNonWhitespace(index)?.type === "LBrace" /* LBrace */;
3298
+ return this.stream.peekNonWhitespace(indexAfterParams)?.type === "LBrace" /* LBrace */;
2820
3299
  }
2821
3300
  isArrowFunctionStart() {
2822
3301
  const first = this.stream.peekNonWhitespace(0);
2823
3302
  if (!first || first.type !== "LParen" /* LParen */) {
2824
3303
  return false;
2825
3304
  }
2826
- let index = 1;
2827
- let depth = 1;
2828
- while (true) {
2829
- const token = this.stream.peekNonWhitespace(index);
2830
- if (!token) {
2831
- return false;
2832
- }
2833
- if (token.type === "LParen" /* LParen */) {
2834
- depth += 1;
2835
- } else if (token.type === "RParen" /* RParen */) {
2836
- depth -= 1;
2837
- if (depth === 0) {
2838
- index += 1;
2839
- break;
2840
- }
2841
- }
2842
- index += 1;
3305
+ const indexAfterParams = this.stream.indexAfterDelimited("LParen" /* LParen */, "RParen" /* RParen */, 0);
3306
+ if (indexAfterParams === null) {
3307
+ return false;
2843
3308
  }
2844
- return this.stream.peekNonWhitespace(index)?.type === "Arrow" /* Arrow */;
3309
+ return this.stream.peekNonWhitespace(indexAfterParams)?.type === "Arrow" /* Arrow */;
2845
3310
  }
2846
3311
  isAsyncArrowFunctionStart() {
2847
3312
  const first = this.stream.peekNonWhitespace(0);
@@ -2851,25 +3316,11 @@ ${caret}`;
2851
3316
  if (this.stream.peekNonWhitespace(1)?.type !== "LParen" /* LParen */) {
2852
3317
  return false;
2853
3318
  }
2854
- let index = 2;
2855
- let depth = 1;
2856
- while (true) {
2857
- const token = this.stream.peekNonWhitespace(index);
2858
- if (!token) {
2859
- return false;
2860
- }
2861
- if (token.type === "LParen" /* LParen */) {
2862
- depth += 1;
2863
- } else if (token.type === "RParen" /* RParen */) {
2864
- depth -= 1;
2865
- if (depth === 0) {
2866
- index += 1;
2867
- break;
2868
- }
2869
- }
2870
- index += 1;
3319
+ const indexAfterParams = this.stream.indexAfterDelimited("LParen" /* LParen */, "RParen" /* RParen */, 1);
3320
+ if (indexAfterParams === null) {
3321
+ return false;
2871
3322
  }
2872
- return this.stream.peekNonWhitespace(index)?.type === "Arrow" /* Arrow */;
3323
+ return this.stream.peekNonWhitespace(indexAfterParams)?.type === "Arrow" /* Arrow */;
2873
3324
  }
2874
3325
  isFunctionExpressionAssignmentStart() {
2875
3326
  const first = this.stream.peekNonWhitespace(0);
@@ -2886,25 +3337,11 @@ ${caret}`;
2886
3337
  if (this.stream.peekNonWhitespace(index)?.type !== "LParen" /* LParen */) {
2887
3338
  return false;
2888
3339
  }
2889
- index += 1;
2890
- let depth = 1;
2891
- while (true) {
2892
- const token = this.stream.peekNonWhitespace(index);
2893
- if (!token) {
2894
- return false;
2895
- }
2896
- if (token.type === "LParen" /* LParen */) {
2897
- depth += 1;
2898
- } else if (token.type === "RParen" /* RParen */) {
2899
- depth -= 1;
2900
- if (depth === 0) {
2901
- index += 1;
2902
- break;
2903
- }
2904
- }
2905
- index += 1;
3340
+ const indexAfterParams = this.stream.indexAfterDelimited("LParen" /* LParen */, "RParen" /* RParen */, index);
3341
+ if (indexAfterParams === null) {
3342
+ return false;
2906
3343
  }
2907
- return this.stream.peekNonWhitespace(index)?.type === "Arrow" /* Arrow */;
3344
+ return this.stream.peekNonWhitespace(indexAfterParams)?.type === "Arrow" /* Arrow */;
2908
3345
  }
2909
3346
  parseExpressionStatement() {
2910
3347
  const expr = this.parseExpression();
@@ -2919,7 +3356,7 @@ ${caret}`;
2919
3356
  const test = this.parseExpression();
2920
3357
  this.stream.skipWhitespace();
2921
3358
  this.stream.expect("RParen" /* RParen */);
2922
- const consequent = this.parseBlock({ allowDeclarations: false });
3359
+ const consequent = this.parseConditionalBody();
2923
3360
  this.stream.skipWhitespace();
2924
3361
  let alternate;
2925
3362
  if (this.stream.peek()?.type === "Else" /* Else */) {
@@ -2929,11 +3366,19 @@ ${caret}`;
2929
3366
  const nested = this.parseIfBlock();
2930
3367
  alternate = new BlockNode([nested]);
2931
3368
  } else {
2932
- alternate = this.parseBlock({ allowDeclarations: false });
3369
+ alternate = this.parseConditionalBody();
2933
3370
  }
2934
3371
  }
2935
3372
  return new IfNode(test, consequent, alternate);
2936
3373
  }
3374
+ parseConditionalBody() {
3375
+ this.stream.skipWhitespace();
3376
+ if (this.stream.peek()?.type === "LBrace" /* LBrace */) {
3377
+ return this.parseBlock({ allowDeclarations: false });
3378
+ }
3379
+ const statement = this.parseStatement({ allowBlocks: false, allowReturn: this.functionDepth > 0 });
3380
+ return new BlockNode([statement]);
3381
+ }
2937
3382
  parseWhileBlock() {
2938
3383
  this.stream.expect("While" /* While */);
2939
3384
  this.stream.skipWhitespace();
@@ -2950,6 +3395,21 @@ ${caret}`;
2950
3395
  this.stream.skipWhitespace();
2951
3396
  this.stream.expect("LParen" /* LParen */);
2952
3397
  this.stream.skipWhitespace();
3398
+ const eachKind = this.detectForEachKind();
3399
+ if (eachKind) {
3400
+ const target = this.parseForEachTarget();
3401
+ this.stream.skipWhitespace();
3402
+ const keyword = this.stream.expect("Identifier" /* Identifier */);
3403
+ if (keyword.value !== eachKind) {
3404
+ throw new Error(`Expected '${eachKind}' but got '${keyword.value}'`);
3405
+ }
3406
+ this.stream.skipWhitespace();
3407
+ const iterable = this.parseExpression();
3408
+ this.stream.skipWhitespace();
3409
+ this.stream.expect("RParen" /* RParen */);
3410
+ const body2 = this.parseBlock({ allowDeclarations: false });
3411
+ return new ForEachNode(target, iterable, eachKind, body2);
3412
+ }
2953
3413
  let init;
2954
3414
  if (this.stream.peek()?.type !== "Semicolon" /* Semicolon */) {
2955
3415
  init = this.parseForClause();
@@ -2973,6 +3433,43 @@ ${caret}`;
2973
3433
  const body = this.parseBlock({ allowDeclarations: false });
2974
3434
  return new ForNode(init, test, update, body);
2975
3435
  }
3436
+ detectForEachKind() {
3437
+ let offset = 0;
3438
+ let depth = 0;
3439
+ while (true) {
3440
+ const token = this.stream.peekNonWhitespace(offset);
3441
+ if (!token) {
3442
+ return null;
3443
+ }
3444
+ if (token.type === "LParen" /* LParen */ || token.type === "LBracket" /* LBracket */ || token.type === "LBrace" /* LBrace */) {
3445
+ depth += 1;
3446
+ } else if (token.type === "RParen" /* RParen */ || token.type === "RBracket" /* RBracket */ || token.type === "RBrace" /* RBrace */) {
3447
+ if (depth === 0) {
3448
+ return null;
3449
+ }
3450
+ depth -= 1;
3451
+ }
3452
+ if (depth === 0) {
3453
+ if (token.type === "Semicolon" /* Semicolon */) {
3454
+ return null;
3455
+ }
3456
+ if (token.type === "Identifier" /* Identifier */ && (token.value === "in" || token.value === "of")) {
3457
+ return token.value;
3458
+ }
3459
+ }
3460
+ offset += 1;
3461
+ }
3462
+ }
3463
+ parseForEachTarget() {
3464
+ const token = this.stream.peek();
3465
+ if (!token) {
3466
+ throw new Error("Expected for-each target");
3467
+ }
3468
+ if (token.type !== "Identifier" /* Identifier */) {
3469
+ throw new Error("for-in/of target must be an identifier");
3470
+ }
3471
+ return new IdentifierExpression(this.stream.next().value);
3472
+ }
2976
3473
  parseForClause() {
2977
3474
  if (this.isAssignmentStart()) {
2978
3475
  return this.parseAssignmentExpression();
@@ -3068,9 +3565,6 @@ ${caret}`;
3068
3565
  const body = this.parseFunctionBlockWithAwait(isAsync);
3069
3566
  return new FunctionDeclarationNode(name, params, body, isAsync);
3070
3567
  }
3071
- parseFunctionBlock() {
3072
- return this.parseFunctionBlockWithAwait(false);
3073
- }
3074
3568
  parseReturnStatement() {
3075
3569
  this.stream.expect("Return" /* Return */);
3076
3570
  this.stream.skipWhitespace();
@@ -3083,6 +3577,23 @@ ${caret}`;
3083
3577
  this.stream.expect("Semicolon" /* Semicolon */);
3084
3578
  return new ReturnNode(value);
3085
3579
  }
3580
+ parseAssertStatement() {
3581
+ this.stream.expect("Assert" /* Assert */);
3582
+ this.stream.skipWhitespace();
3583
+ const test = this.parseExpression();
3584
+ this.consumeStatementTerminator();
3585
+ return new AssertNode(test);
3586
+ }
3587
+ parseBreakStatement() {
3588
+ this.stream.expect("Break" /* Break */);
3589
+ this.consumeStatementTerminator();
3590
+ return new BreakNode();
3591
+ }
3592
+ parseContinueStatement() {
3593
+ this.stream.expect("Continue" /* Continue */);
3594
+ this.consumeStatementTerminator();
3595
+ return new ContinueNode();
3596
+ }
3086
3597
  parseArrowFunctionExpression(isAsync = false) {
3087
3598
  const params = this.parseFunctionParams();
3088
3599
  this.stream.skipWhitespace();
@@ -3192,6 +3703,7 @@ var Scope = class _Scope {
3192
3703
  root;
3193
3704
  listeners = /* @__PURE__ */ new Map();
3194
3705
  anyListeners = /* @__PURE__ */ new Set();
3706
+ isEachItem = false;
3195
3707
  createChild() {
3196
3708
  return new _Scope(this);
3197
3709
  }
@@ -3494,18 +4006,19 @@ var Engine = class _Engine {
3494
4006
  eachBindings = /* @__PURE__ */ new WeakMap();
3495
4007
  lifecycleBindings = /* @__PURE__ */ new WeakMap();
3496
4008
  behaviorRegistry = [];
4009
+ behaviorRegistryHashes = /* @__PURE__ */ new Set();
3497
4010
  behaviorBindings = /* @__PURE__ */ new WeakMap();
3498
4011
  behaviorListeners = /* @__PURE__ */ new WeakMap();
3499
4012
  behaviorId = 0;
3500
4013
  codeCache = /* @__PURE__ */ new Map();
3501
4014
  behaviorCache = /* @__PURE__ */ new Map();
3502
4015
  observer;
3503
- observerRoot;
3504
4016
  attributeHandlers = [];
3505
4017
  globals = {};
3506
4018
  importantFlags = /* @__PURE__ */ new WeakMap();
3507
4019
  inlineDeclarations = /* @__PURE__ */ new WeakMap();
3508
4020
  flagHandlers = /* @__PURE__ */ new Map();
4021
+ behaviorModifiers = /* @__PURE__ */ new Map();
3509
4022
  pendingAdded = /* @__PURE__ */ new Set();
3510
4023
  pendingRemoved = /* @__PURE__ */ new Set();
3511
4024
  pendingUpdated = /* @__PURE__ */ new Set();
@@ -3514,10 +4027,119 @@ var Engine = class _Engine {
3514
4027
  diagnostics;
3515
4028
  logger;
3516
4029
  pendingUses = [];
4030
+ pendingAutoBindToScope = [];
4031
+ scopeWatchers = /* @__PURE__ */ new WeakMap();
4032
+ executionStack = [];
4033
+ groupProxyCache = /* @__PURE__ */ new WeakMap();
3517
4034
  constructor(options = {}) {
3518
4035
  this.diagnostics = options.diagnostics ?? false;
3519
4036
  this.logger = options.logger ?? console;
3520
4037
  this.registerGlobal("console", console);
4038
+ this.registerFlag("important");
4039
+ this.registerFlag("trusted");
4040
+ this.registerFlag("debounce", {
4041
+ onEventBind: ({ args }) => ({
4042
+ debounceMs: typeof args === "number" ? args : 200
4043
+ })
4044
+ });
4045
+ this.registerFlag("prevent", {
4046
+ onEventBefore: ({ event }) => {
4047
+ event?.preventDefault();
4048
+ }
4049
+ });
4050
+ this.registerFlag("stop", {
4051
+ onEventBefore: ({ event }) => {
4052
+ event?.stopPropagation();
4053
+ }
4054
+ });
4055
+ this.registerFlag("self", {
4056
+ onEventBefore: ({ event, element }) => {
4057
+ const target = event?.target;
4058
+ if (!(target instanceof Node)) {
4059
+ return false;
4060
+ }
4061
+ return target === element;
4062
+ }
4063
+ });
4064
+ this.registerFlag("outside", {
4065
+ onEventBind: ({ element }) => ({ listenerTarget: element.ownerDocument }),
4066
+ onEventBefore: ({ event, element }) => {
4067
+ const target = event?.target;
4068
+ if (!(target instanceof Node)) {
4069
+ return false;
4070
+ }
4071
+ return !element.contains(target);
4072
+ }
4073
+ });
4074
+ this.registerFlag("once", {
4075
+ onEventBind: () => ({ options: { once: true } })
4076
+ });
4077
+ this.registerFlag("passive", {
4078
+ onEventBind: () => ({ options: { passive: true } })
4079
+ });
4080
+ this.registerFlag("capture", {
4081
+ onEventBind: () => ({ options: { capture: true } })
4082
+ });
4083
+ this.registerFlag("shift", {
4084
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "shift")
4085
+ });
4086
+ this.registerFlag("ctrl", {
4087
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "ctrl")
4088
+ });
4089
+ this.registerFlag("control", {
4090
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "ctrl")
4091
+ });
4092
+ this.registerFlag("alt", {
4093
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "alt")
4094
+ });
4095
+ this.registerFlag("meta", {
4096
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "meta")
4097
+ });
4098
+ this.registerFlag("enter", {
4099
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "enter")
4100
+ });
4101
+ this.registerFlag("escape", {
4102
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "escape")
4103
+ });
4104
+ this.registerFlag("esc", {
4105
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "escape")
4106
+ });
4107
+ this.registerFlag("tab", {
4108
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "tab")
4109
+ });
4110
+ this.registerFlag("space", {
4111
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "space")
4112
+ });
4113
+ this.registerFlag("up", {
4114
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowup")
4115
+ });
4116
+ this.registerFlag("down", {
4117
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowdown")
4118
+ });
4119
+ this.registerFlag("left", {
4120
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowleft")
4121
+ });
4122
+ this.registerFlag("right", {
4123
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowright")
4124
+ });
4125
+ this.registerFlag("arrowup", {
4126
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowup")
4127
+ });
4128
+ this.registerFlag("arrowdown", {
4129
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowdown")
4130
+ });
4131
+ this.registerFlag("arrowleft", {
4132
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowleft")
4133
+ });
4134
+ this.registerFlag("arrowright", {
4135
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowright")
4136
+ });
4137
+ this.registerFlag("delete", {
4138
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "delete")
4139
+ });
4140
+ this.registerFlag("backspace", {
4141
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "backspace")
4142
+ });
3521
4143
  this.registerGlobal("list", {
3522
4144
  async map(items, fn) {
3523
4145
  if (!Array.isArray(items) || typeof fn !== "function") {
@@ -3555,6 +4177,96 @@ var Engine = class _Engine {
3555
4177
  }
3556
4178
  });
3557
4179
  this.registerDefaultAttributeHandlers();
4180
+ this.registerFlag("int", {
4181
+ transformValue: (_context, value) => this.coerceInt(value)
4182
+ });
4183
+ this.registerFlag("float", {
4184
+ transformValue: (_context, value) => this.coerceFloat(value)
4185
+ });
4186
+ this.registerBehaviorModifier("group", {
4187
+ onConstruct: ({ args, scope, rootScope, behavior, element }) => {
4188
+ const key = typeof args === "string" ? args : void 0;
4189
+ if (!key) {
4190
+ return;
4191
+ }
4192
+ const targetScope = this.getGroupTargetScope(element, behavior, scope, rootScope);
4193
+ const existing = targetScope.getPath?.(key);
4194
+ const list = Array.isArray(existing) ? existing : [];
4195
+ const proxy = this.getGroupProxy(scope);
4196
+ if (!list.includes(proxy)) {
4197
+ list.push(proxy);
4198
+ targetScope.setPath?.(key, list);
4199
+ } else if (!Array.isArray(existing)) {
4200
+ targetScope.setPath?.(key, list);
4201
+ }
4202
+ },
4203
+ onUnbind: ({ args, scope, rootScope, behavior, element }) => {
4204
+ const key = typeof args === "string" ? args : void 0;
4205
+ if (!key) {
4206
+ return;
4207
+ }
4208
+ const targetScope = this.getGroupTargetScope(element, behavior, scope, rootScope);
4209
+ const existing = targetScope.getPath?.(key);
4210
+ if (!Array.isArray(existing)) {
4211
+ return;
4212
+ }
4213
+ const proxy = this.getGroupProxy(scope);
4214
+ const next = existing.filter((entry) => entry !== proxy);
4215
+ if (next.length !== existing.length) {
4216
+ targetScope.setPath?.(key, next);
4217
+ }
4218
+ }
4219
+ });
4220
+ }
4221
+ getGroupTargetScope(element, behavior, scope, rootScope) {
4222
+ let targetScope = rootScope ?? scope;
4223
+ if (behavior.parentSelector) {
4224
+ const parentElement = element.closest(behavior.parentSelector);
4225
+ if (parentElement) {
4226
+ targetScope = this.getScope(parentElement);
4227
+ }
4228
+ }
4229
+ return targetScope;
4230
+ }
4231
+ getGroupProxy(scope) {
4232
+ const cached = this.groupProxyCache.get(scope);
4233
+ if (cached) {
4234
+ return cached;
4235
+ }
4236
+ const proxy = new Proxy(
4237
+ {},
4238
+ {
4239
+ get: (_target, prop) => {
4240
+ if (typeof prop === "symbol") {
4241
+ return void 0;
4242
+ }
4243
+ if (prop === "__scope") {
4244
+ return scope;
4245
+ }
4246
+ return scope.getPath(String(prop));
4247
+ },
4248
+ set: (_target, prop, value) => {
4249
+ if (typeof prop === "symbol") {
4250
+ return false;
4251
+ }
4252
+ scope.setPath(String(prop), value);
4253
+ return true;
4254
+ },
4255
+ has: (_target, prop) => {
4256
+ if (typeof prop === "symbol") {
4257
+ return false;
4258
+ }
4259
+ return scope.getPath(String(prop)) !== void 0;
4260
+ },
4261
+ getOwnPropertyDescriptor: () => ({
4262
+ enumerable: true,
4263
+ configurable: true
4264
+ }),
4265
+ ownKeys: () => []
4266
+ }
4267
+ );
4268
+ this.groupProxyCache.set(scope, proxy);
4269
+ return proxy;
3558
4270
  }
3559
4271
  async mount(root) {
3560
4272
  const documentRoot = root.ownerDocument;
@@ -3581,7 +4293,10 @@ var Engine = class _Engine {
3581
4293
  this.disconnectObserver();
3582
4294
  }
3583
4295
  registerBehaviors(source) {
3584
- const program = new Parser(source, { customFlags: new Set(this.flagHandlers.keys()) }).parseProgram();
4296
+ const program = new Parser(source, {
4297
+ customFlags: new Set(this.flagHandlers.keys()),
4298
+ behaviorFlags: new Set(this.behaviorModifiers.keys())
4299
+ }).parseProgram();
3585
4300
  for (const use of program.uses) {
3586
4301
  if (use.flags?.wait) {
3587
4302
  this.pendingUses.push(this.waitForUseGlobal(use));
@@ -3605,11 +4320,14 @@ var Engine = class _Engine {
3605
4320
  Object.assign(this.globals, values);
3606
4321
  }
3607
4322
  registerFlag(name, handler = {}) {
4323
+ this.flagHandlers.set(name, handler);
4324
+ }
4325
+ registerBehaviorModifier(name, handler = {}) {
3608
4326
  const reserved = /* @__PURE__ */ new Set(["important", "trusted", "debounce"]);
3609
4327
  if (reserved.has(name)) {
3610
- throw new Error(`Flag '${name}' is reserved`);
4328
+ throw new Error(`Behavior modifier '${name}' is reserved`);
3611
4329
  }
3612
- this.flagHandlers.set(name, handler);
4330
+ this.behaviorModifiers.set(name, handler);
3613
4331
  }
3614
4332
  getRegistryStats() {
3615
4333
  return {
@@ -3717,7 +4435,6 @@ var Engine = class _Engine {
3717
4435
  if (this.observer) {
3718
4436
  return;
3719
4437
  }
3720
- this.observerRoot = root;
3721
4438
  this.observerFlush = debounce(() => this.flushObserverQueue(), 10);
3722
4439
  this.observer = new MutationObserver((mutations) => {
3723
4440
  for (const mutation of mutations) {
@@ -3747,7 +4464,6 @@ var Engine = class _Engine {
3747
4464
  disconnectObserver() {
3748
4465
  this.observer?.disconnect();
3749
4466
  this.observer = void 0;
3750
- this.observerRoot = void 0;
3751
4467
  this.pendingAdded.clear();
3752
4468
  this.pendingRemoved.clear();
3753
4469
  this.pendingUpdated.clear();
@@ -3776,6 +4492,8 @@ var Engine = class _Engine {
3776
4492
  if (this.behaviorBindings.has(node)) {
3777
4493
  this.runBehaviorDestruct(node);
3778
4494
  }
4495
+ this.cleanupScopeWatchers(node);
4496
+ this.cleanupBehaviorListeners(node);
3779
4497
  for (const child of Array.from(node.querySelectorAll("*"))) {
3780
4498
  if (this.lifecycleBindings.has(child)) {
3781
4499
  this.runDestruct(child);
@@ -3783,6 +4501,8 @@ var Engine = class _Engine {
3783
4501
  if (this.behaviorBindings.has(child)) {
3784
4502
  this.runBehaviorDestruct(child);
3785
4503
  }
4504
+ this.cleanupScopeWatchers(child);
4505
+ this.cleanupBehaviorListeners(child);
3786
4506
  }
3787
4507
  }
3788
4508
  handleAddedNode(node) {
@@ -3806,13 +4526,13 @@ var Engine = class _Engine {
3806
4526
  }
3807
4527
  async applyBehaviors(root) {
3808
4528
  await this.waitForUses();
3809
- if (this.behaviorRegistry.length === 0) {
3810
- return;
3811
- }
3812
- const elements = [root, ...Array.from(root.querySelectorAll("*"))];
3813
- for (const element of elements) {
3814
- await this.reapplyBehaviorsForElement(element);
4529
+ if (this.behaviorRegistry.length > 0) {
4530
+ const elements = [root, ...Array.from(root.querySelectorAll("*"))];
4531
+ for (const element of elements) {
4532
+ await this.reapplyBehaviorsForElement(element);
4533
+ }
3815
4534
  }
4535
+ this.flushAutoBindQueue();
3816
4536
  }
3817
4537
  async reapplyBehaviorsForElement(element) {
3818
4538
  if (this.behaviorRegistry.length === 0) {
@@ -3844,15 +4564,18 @@ var Engine = class _Engine {
3844
4564
  const rootScope = this.getBehaviorRootScope(element, behavior);
3845
4565
  this.applyBehaviorFunctions(element, scope, behavior.functions, rootScope);
3846
4566
  await this.applyBehaviorDeclarations(element, scope, behavior.declarations, rootScope);
4567
+ await this.applyBehaviorModifierHook("onBind", behavior, element, scope, rootScope);
3847
4568
  if (behavior.construct) {
3848
4569
  await this.safeExecuteBlock(behavior.construct, scope, element, rootScope);
3849
4570
  }
4571
+ await this.applyBehaviorModifierHook("onConstruct", behavior, element, scope, rootScope);
3850
4572
  for (const onBlock of behavior.onBlocks) {
3851
4573
  this.attachBehaviorOnHandler(
3852
4574
  element,
3853
4575
  onBlock.event,
3854
4576
  onBlock.body,
3855
- onBlock.modifiers,
4577
+ onBlock.flags,
4578
+ onBlock.flagArgs,
3856
4579
  onBlock.args,
3857
4580
  behavior.id,
3858
4581
  rootScope
@@ -3862,10 +4585,11 @@ var Engine = class _Engine {
3862
4585
  }
3863
4586
  unbindBehaviorForElement(behavior, element, scope, bound) {
3864
4587
  bound.delete(behavior.id);
4588
+ const rootScope = this.getBehaviorRootScope(element, behavior);
3865
4589
  if (behavior.destruct) {
3866
- const rootScope = this.getBehaviorRootScope(element, behavior);
3867
4590
  void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
3868
4591
  }
4592
+ void this.applyBehaviorModifierHook("onDestruct", behavior, element, scope, rootScope);
3869
4593
  const listenerMap = this.behaviorListeners.get(element);
3870
4594
  const listeners = listenerMap?.get(behavior.id);
3871
4595
  if (listeners) {
@@ -3874,6 +4598,7 @@ var Engine = class _Engine {
3874
4598
  }
3875
4599
  listenerMap?.delete(behavior.id);
3876
4600
  }
4601
+ void this.applyBehaviorModifierHook("onUnbind", behavior, element, scope, rootScope);
3877
4602
  this.logDiagnostic("unbind", element, behavior);
3878
4603
  }
3879
4604
  runBehaviorDestruct(element) {
@@ -3883,11 +4608,15 @@ var Engine = class _Engine {
3883
4608
  }
3884
4609
  const scope = this.getScope(element);
3885
4610
  for (const behavior of this.behaviorRegistry) {
3886
- if (!bound.has(behavior.id) || !behavior.destruct) {
4611
+ if (!bound.has(behavior.id) || !behavior.destruct && !this.behaviorHasModifierHooks(behavior)) {
3887
4612
  continue;
3888
4613
  }
3889
4614
  const rootScope = this.getBehaviorRootScope(element, behavior);
3890
- void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
4615
+ if (behavior.destruct) {
4616
+ void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
4617
+ }
4618
+ void this.applyBehaviorModifierHook("onDestruct", behavior, element, scope, rootScope);
4619
+ void this.applyBehaviorModifierHook("onUnbind", behavior, element, scope, rootScope);
3891
4620
  }
3892
4621
  }
3893
4622
  attachAttributes(element) {
@@ -3971,6 +4700,7 @@ var Engine = class _Engine {
3971
4700
  const fragment = element.content.cloneNode(true);
3972
4701
  const roots = Array.from(fragment.children);
3973
4702
  const itemScope = new Scope(scope);
4703
+ itemScope.isEachItem = true;
3974
4704
  itemScope.setPath(`self.${binding.itemName}`, item);
3975
4705
  if (binding.indexName) {
3976
4706
  itemScope.setPath(`self.${binding.indexName}`, index);
@@ -4009,7 +4739,93 @@ var Engine = class _Engine {
4009
4739
  if (name.includes(":to")) {
4010
4740
  return "to";
4011
4741
  }
4012
- return "both";
4742
+ return "auto";
4743
+ }
4744
+ resolveBindConfig(element, expr, scope, direction) {
4745
+ if (direction !== "auto") {
4746
+ return {
4747
+ direction,
4748
+ seedFromScope: false,
4749
+ syncToScope: direction === "to" || direction === "both",
4750
+ deferToScope: false
4751
+ };
4752
+ }
4753
+ if (this.isInEachScope(scope)) {
4754
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4755
+ }
4756
+ if (this.isFormControl(element)) {
4757
+ if (this.hasScopeValue(scope, expr)) {
4758
+ return { direction: "both", seedFromScope: true, syncToScope: false, deferToScope: false };
4759
+ }
4760
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: true };
4761
+ }
4762
+ if (this.hasScopeValue(scope, expr)) {
4763
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4764
+ }
4765
+ if (this.hasElementValue(element)) {
4766
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: true };
4767
+ }
4768
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4769
+ }
4770
+ isFormControl(element) {
4771
+ return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement;
4772
+ }
4773
+ hasScopeValue(scope, expr) {
4774
+ const key = expr.trim();
4775
+ if (!key) {
4776
+ return false;
4777
+ }
4778
+ const value = scope.get(key);
4779
+ return value !== void 0 && value !== null;
4780
+ }
4781
+ hasElementValue(element) {
4782
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
4783
+ return element.value.length > 0;
4784
+ }
4785
+ return (element.textContent ?? "").trim().length > 0;
4786
+ }
4787
+ coerceInt(value) {
4788
+ if (value == null || value === "") {
4789
+ return value;
4790
+ }
4791
+ const num = typeof value === "number" ? value : Number.parseInt(String(value), 10);
4792
+ return Number.isNaN(num) ? value : num;
4793
+ }
4794
+ coerceFloat(value) {
4795
+ if (value == null || value === "") {
4796
+ return value;
4797
+ }
4798
+ const num = typeof value === "number" ? value : Number.parseFloat(String(value));
4799
+ return Number.isNaN(num) ? value : num;
4800
+ }
4801
+ isInEachScope(scope) {
4802
+ let cursor = scope;
4803
+ while (cursor) {
4804
+ if (cursor.isEachItem) {
4805
+ return true;
4806
+ }
4807
+ cursor = cursor.parent;
4808
+ }
4809
+ return false;
4810
+ }
4811
+ flushAutoBindQueue() {
4812
+ if (this.pendingAutoBindToScope.length === 0) {
4813
+ return;
4814
+ }
4815
+ const pending = this.pendingAutoBindToScope;
4816
+ this.pendingAutoBindToScope = [];
4817
+ for (const entry of pending) {
4818
+ if (!entry.element.isConnected) {
4819
+ continue;
4820
+ }
4821
+ if (this.hasScopeValue(entry.scope, entry.expr)) {
4822
+ continue;
4823
+ }
4824
+ if (!this.hasElementValue(entry.element)) {
4825
+ continue;
4826
+ }
4827
+ applyBindToScope(entry.element, entry.expr, entry.scope);
4828
+ }
4013
4829
  }
4014
4830
  hasVsnAttributes(element) {
4015
4831
  return element.getAttributeNames().some((name) => name.startsWith("vsn-"));
@@ -4034,7 +4850,7 @@ var Engine = class _Engine {
4034
4850
  }
4035
4851
  return void 0;
4036
4852
  }
4037
- watch(scope, expr, handler) {
4853
+ watch(scope, expr, handler, element) {
4038
4854
  const key = expr.trim();
4039
4855
  if (!key) {
4040
4856
  return;
@@ -4049,29 +4865,70 @@ var Engine = class _Engine {
4049
4865
  }
4050
4866
  if (target) {
4051
4867
  target.on(key, handler);
4868
+ if (element) {
4869
+ this.trackScopeWatcher(element, target, "path", handler, key);
4870
+ }
4052
4871
  return;
4053
4872
  }
4054
4873
  let cursor = scope;
4055
4874
  while (cursor) {
4056
4875
  cursor.on(key, handler);
4876
+ if (element) {
4877
+ this.trackScopeWatcher(element, cursor, "path", handler, key);
4878
+ }
4057
4879
  cursor = cursor.parent;
4058
4880
  }
4059
4881
  }
4060
- watchWithDebounce(scope, expr, handler, debounceMs) {
4061
- if (debounceMs) {
4062
- this.watch(scope, expr, debounce(handler, debounceMs));
4063
- } else {
4064
- this.watch(scope, expr, handler);
4065
- }
4882
+ watchWithDebounce(scope, expr, handler, debounceMs, element) {
4883
+ const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4884
+ this.watch(scope, expr, effectiveHandler, element);
4066
4885
  }
4067
- watchAllScopes(scope, handler, debounceMs) {
4886
+ watchAllScopes(scope, handler, debounceMs, element) {
4068
4887
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4069
4888
  let cursor = scope;
4070
4889
  while (cursor) {
4071
4890
  cursor.onAny(effectiveHandler);
4891
+ if (element) {
4892
+ this.trackScopeWatcher(element, cursor, "any", effectiveHandler);
4893
+ }
4072
4894
  cursor = cursor.parent;
4073
4895
  }
4074
4896
  }
4897
+ trackScopeWatcher(element, scope, kind, handler, key) {
4898
+ const watchers = this.scopeWatchers.get(element) ?? [];
4899
+ watchers.push({ scope, kind, handler, ...key ? { key } : {} });
4900
+ this.scopeWatchers.set(element, watchers);
4901
+ }
4902
+ cleanupScopeWatchers(element) {
4903
+ const watchers = this.scopeWatchers.get(element);
4904
+ if (!watchers) {
4905
+ return;
4906
+ }
4907
+ for (const watcher of watchers) {
4908
+ if (watcher.kind === "any") {
4909
+ watcher.scope.offAny(watcher.handler);
4910
+ continue;
4911
+ }
4912
+ if (watcher.key) {
4913
+ watcher.scope.off(watcher.key, watcher.handler);
4914
+ }
4915
+ }
4916
+ this.scopeWatchers.delete(element);
4917
+ }
4918
+ cleanupBehaviorListeners(element) {
4919
+ const listenerMap = this.behaviorListeners.get(element);
4920
+ if (!listenerMap) {
4921
+ return;
4922
+ }
4923
+ for (const listeners of listenerMap.values()) {
4924
+ for (const listener of listeners) {
4925
+ listener.target.removeEventListener(listener.event, listener.handler, listener.options);
4926
+ }
4927
+ }
4928
+ listenerMap.clear();
4929
+ this.behaviorListeners.delete(element);
4930
+ this.behaviorBindings.delete(element);
4931
+ }
4075
4932
  parseOnAttribute(name, value) {
4076
4933
  if (!name.startsWith("vsn-on:")) {
4077
4934
  return null;
@@ -4081,111 +4938,56 @@ var Engine = class _Engine {
4081
4938
  if (!event) {
4082
4939
  return null;
4083
4940
  }
4084
- const descriptor = this.parseEventDescriptor(event);
4085
- if (!descriptor.event) {
4086
- return null;
4087
- }
4088
- let debounceMs;
4089
- const modifiers = [];
4090
- for (const flag of flags) {
4091
- if (flag.startsWith("debounce")) {
4092
- const match = flag.match(/debounce\((\d+)\)/);
4093
- debounceMs = match ? Number(match[1]) : 200;
4094
- continue;
4095
- }
4096
- modifiers.push(flag);
4941
+ if (event.includes(".")) {
4942
+ throw new Error("vsn:on does not support dot modifiers; use !flags instead");
4097
4943
  }
4098
- const combinedModifiers = [...modifiers, ...descriptor.modifiers];
4944
+ const { flagMap, flagArgs } = this.parseInlineFlags(flags);
4099
4945
  const config = {
4100
- event: descriptor.event,
4946
+ event,
4101
4947
  code: value,
4102
- ...debounceMs !== void 0 ? { debounceMs } : {},
4103
- ...combinedModifiers.length > 0 ? { modifiers: combinedModifiers } : {},
4104
- ...descriptor.keyModifiers.length > 0 ? { keyModifiers: descriptor.keyModifiers } : {}
4948
+ flags: flagMap,
4949
+ flagArgs
4105
4950
  };
4106
4951
  return config;
4107
4952
  }
4108
- parseEventDescriptor(raw) {
4109
- const parts = raw.split(".").map((part) => part.trim()).filter(Boolean);
4110
- const event = parts.shift() ?? "";
4111
- const modifiers = [];
4112
- const keyModifiers = [];
4113
- const modifierSet = /* @__PURE__ */ new Set(["outside", "self"]);
4114
- for (const part of parts) {
4115
- if (modifierSet.has(part)) {
4116
- modifiers.push(part);
4117
- } else {
4118
- keyModifiers.push(part);
4953
+ parseInlineFlags(parts) {
4954
+ const flagMap = {};
4955
+ const flagArgs = {};
4956
+ for (const raw of parts) {
4957
+ const trimmed = raw.trim();
4958
+ if (!trimmed) {
4959
+ continue;
4119
4960
  }
4120
- }
4121
- return { event, keyModifiers, modifiers };
4122
- }
4123
- matchesKeyModifiers(event, keyModifiers) {
4124
- if (!keyModifiers || keyModifiers.length === 0) {
4125
- return true;
4126
- }
4127
- if (!(event instanceof KeyboardEvent)) {
4128
- return false;
4129
- }
4130
- const modifierChecks = {
4131
- shift: event.shiftKey,
4132
- ctrl: event.ctrlKey,
4133
- control: event.ctrlKey,
4134
- alt: event.altKey,
4135
- meta: event.metaKey
4136
- };
4137
- const keyAliases = {
4138
- esc: "escape",
4139
- escape: "escape",
4140
- enter: "enter",
4141
- tab: "tab",
4142
- space: "space",
4143
- spacebar: "space",
4144
- up: "arrowup",
4145
- down: "arrowdown",
4146
- left: "arrowleft",
4147
- right: "arrowright",
4148
- arrowup: "arrowup",
4149
- arrowdown: "arrowdown",
4150
- arrowleft: "arrowleft",
4151
- arrowright: "arrowright",
4152
- delete: "delete",
4153
- backspace: "backspace"
4154
- };
4155
- let key = event.key?.toLowerCase() ?? "";
4156
- if (key === " ") {
4157
- key = "space";
4158
- }
4159
- for (const rawModifier of keyModifiers) {
4160
- const modifier = rawModifier.toLowerCase();
4161
- if (modifier in modifierChecks) {
4162
- if (!modifierChecks[modifier]) {
4163
- return false;
4164
- }
4961
+ const match = trimmed.match(/^([a-zA-Z][\w-]*)(?:\((.+)\))?$/);
4962
+ if (!match) {
4165
4963
  continue;
4166
4964
  }
4167
- const expectedKey = keyAliases[modifier] ?? modifier;
4168
- if (key !== expectedKey) {
4169
- return false;
4965
+ const name = match[1] ?? "";
4966
+ if (!name) {
4967
+ continue;
4968
+ }
4969
+ if (!this.flagHandlers.has(name)) {
4970
+ throw new Error(`Unknown flag ${name}`);
4971
+ }
4972
+ flagMap[name] = true;
4973
+ if (match[2] !== void 0) {
4974
+ flagArgs[name] = this.parseInlineFlagArg(match[2]);
4170
4975
  }
4171
4976
  }
4172
- return true;
4977
+ return { flagMap, flagArgs };
4173
4978
  }
4174
- matchesTargetModifiers(element, event, modifiers) {
4175
- if (!modifiers || modifiers.length === 0) {
4979
+ parseInlineFlagArg(raw) {
4980
+ const trimmed = raw.trim();
4981
+ if (trimmed === "true") {
4176
4982
  return true;
4177
4983
  }
4178
- const target = event?.target;
4179
- if (!target || !(target instanceof Node)) {
4180
- return !modifiers.includes("self") && !modifiers.includes("outside");
4181
- }
4182
- if (modifiers.includes("self") && target !== element) {
4984
+ if (trimmed === "false") {
4183
4985
  return false;
4184
4986
  }
4185
- if (modifiers.includes("outside") && element.contains(target)) {
4186
- return false;
4987
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
4988
+ return Number(trimmed);
4187
4989
  }
4188
- return true;
4990
+ return trimmed;
4189
4991
  }
4190
4992
  describeElement(element) {
4191
4993
  const tag = element.tagName.toLowerCase();
@@ -4227,51 +5029,50 @@ var Engine = class _Engine {
4227
5029
  }
4228
5030
  }
4229
5031
  attachOnHandler(element, config) {
4230
- const options = this.getListenerOptions(config.modifiers);
4231
- const listenerTarget = config.modifiers?.includes("outside") ? element.ownerDocument : element;
5032
+ const { listenerTarget, options, debounceMs } = this.getEventBindingConfig(
5033
+ element,
5034
+ config.flags,
5035
+ config.flagArgs
5036
+ );
4232
5037
  let effectiveHandler;
4233
5038
  const handler = async (event) => {
4234
5039
  if (!element.isConnected) {
4235
5040
  listenerTarget.removeEventListener(config.event, effectiveHandler, options);
4236
5041
  return;
4237
5042
  }
4238
- if (!this.matchesKeyModifiers(event, config.keyModifiers)) {
4239
- return;
4240
- }
4241
- if (!this.matchesTargetModifiers(element, event, config.modifiers)) {
5043
+ const scope = this.getScope(element);
5044
+ if (!this.applyEventFlagBefore(element, scope, config.flags, config.flagArgs, event)) {
4242
5045
  return;
4243
5046
  }
4244
- this.applyEventModifiers(event, config.modifiers);
4245
- const scope = this.getScope(element);
4246
5047
  try {
4247
5048
  await this.execute(config.code, scope, element);
4248
5049
  this.evaluate(element);
4249
5050
  } catch (error) {
4250
5051
  this.emitError(element, error);
5052
+ } finally {
5053
+ this.applyEventFlagAfter(element, scope, config.flags, config.flagArgs, event);
4251
5054
  }
4252
5055
  };
4253
- effectiveHandler = config.debounceMs ? debounce(handler, config.debounceMs) : handler;
5056
+ effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4254
5057
  listenerTarget.addEventListener(config.event, effectiveHandler, options);
4255
5058
  }
4256
- attachBehaviorOnHandler(element, event, body, modifiers, args, behaviorId, rootScope) {
4257
- const descriptor = this.parseEventDescriptor(event);
4258
- const combinedModifiers = modifiers ? [...modifiers, ...descriptor.modifiers] : descriptor.modifiers.length > 0 ? [...descriptor.modifiers] : void 0;
4259
- const listenerTarget = combinedModifiers?.includes("outside") ? element.ownerDocument : element;
5059
+ attachBehaviorOnHandler(element, event, body, flags, flagArgs, args, behaviorId, rootScope) {
5060
+ if (event.includes(".")) {
5061
+ throw new Error("vsn:on does not support dot modifiers; use !flags instead");
5062
+ }
5063
+ const { listenerTarget, options, debounceMs } = this.getEventBindingConfig(element, flags, flagArgs);
4260
5064
  const handler = async (evt) => {
4261
- if (!this.matchesKeyModifiers(evt, descriptor.keyModifiers)) {
4262
- return;
4263
- }
4264
- if (!this.matchesTargetModifiers(element, evt, combinedModifiers)) {
5065
+ const scope = this.getScope(element);
5066
+ if (!this.applyEventFlagBefore(element, scope, flags, flagArgs, evt)) {
4265
5067
  return;
4266
5068
  }
4267
- this.applyEventModifiers(evt, combinedModifiers);
4268
- const scope = this.getScope(element);
4269
5069
  const previousValues = /* @__PURE__ */ new Map();
4270
5070
  if (args && args.length > 0) {
4271
5071
  const argName = args[0];
4272
5072
  if (argName) {
4273
5073
  previousValues.set(argName, scope.getPath(argName));
4274
- scope.setPath(argName, evt);
5074
+ const [nextArg] = this.applyEventFlagArgTransforms(element, scope, flags, flagArgs, evt);
5075
+ scope.setPath(argName, nextArg);
4275
5076
  }
4276
5077
  }
4277
5078
  let failed = false;
@@ -4284,16 +5085,17 @@ var Engine = class _Engine {
4284
5085
  for (const [name, value] of previousValues.entries()) {
4285
5086
  scope.setPath(name, value);
4286
5087
  }
5088
+ this.applyEventFlagAfter(element, scope, flags, flagArgs, evt);
4287
5089
  }
4288
5090
  if (!failed) {
4289
5091
  this.evaluate(element);
4290
5092
  }
4291
5093
  };
4292
- const options = this.getListenerOptions(combinedModifiers);
4293
- listenerTarget.addEventListener(descriptor.event, handler, options);
5094
+ const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
5095
+ listenerTarget.addEventListener(event, effectiveHandler, options);
4294
5096
  const listenerMap = this.behaviorListeners.get(element) ?? /* @__PURE__ */ new Map();
4295
5097
  const listeners = listenerMap.get(behaviorId) ?? [];
4296
- listeners.push({ target: listenerTarget, event: descriptor.event, handler, options });
5098
+ listeners.push({ target: listenerTarget, event, handler: effectiveHandler, options });
4297
5099
  listenerMap.set(behaviorId, listeners);
4298
5100
  this.behaviorListeners.set(element, listenerMap);
4299
5101
  }
@@ -4324,33 +5126,158 @@ var Engine = class _Engine {
4324
5126
  Promise.resolve().then(handler);
4325
5127
  }
4326
5128
  }
4327
- applyEventModifiers(event, modifiers) {
4328
- if (!event || !modifiers || modifiers.length === 0) {
4329
- return;
5129
+ getEventBindingConfig(element, flags, flagArgs) {
5130
+ let listenerTarget = element;
5131
+ let options = {};
5132
+ let debounceMs;
5133
+ for (const name of Object.keys(flags)) {
5134
+ const handler = this.flagHandlers.get(name);
5135
+ if (!handler?.onEventBind) {
5136
+ continue;
5137
+ }
5138
+ const patch = handler.onEventBind({
5139
+ name,
5140
+ args: flagArgs[name],
5141
+ element,
5142
+ scope: this.getScope(element),
5143
+ rootScope: void 0,
5144
+ event: void 0,
5145
+ engine: this
5146
+ });
5147
+ if (!patch) {
5148
+ continue;
5149
+ }
5150
+ if (patch.listenerTarget) {
5151
+ listenerTarget = patch.listenerTarget;
5152
+ }
5153
+ if (patch.options) {
5154
+ options = { ...options, ...patch.options };
5155
+ }
5156
+ if (patch.debounceMs !== void 0) {
5157
+ debounceMs = patch.debounceMs;
5158
+ }
5159
+ }
5160
+ return {
5161
+ listenerTarget,
5162
+ ...Object.keys(options).length > 0 ? { options } : {},
5163
+ ...debounceMs !== void 0 ? { debounceMs } : {}
5164
+ };
5165
+ }
5166
+ applyEventFlagBefore(element, scope, flags, flagArgs, event) {
5167
+ for (const name of Object.keys(flags)) {
5168
+ const handler = this.flagHandlers.get(name);
5169
+ if (!handler?.onEventBefore) {
5170
+ continue;
5171
+ }
5172
+ const result = handler.onEventBefore({
5173
+ name,
5174
+ args: flagArgs[name],
5175
+ element,
5176
+ scope,
5177
+ rootScope: void 0,
5178
+ event,
5179
+ engine: this
5180
+ });
5181
+ if (result === false) {
5182
+ return false;
5183
+ }
4330
5184
  }
4331
- for (const modifier of modifiers) {
4332
- if (modifier === "prevent") {
4333
- event.preventDefault();
4334
- } else if (modifier === "stop") {
4335
- event.stopPropagation();
5185
+ return true;
5186
+ }
5187
+ applyEventFlagAfter(element, scope, flags, flagArgs, event) {
5188
+ for (const name of Object.keys(flags)) {
5189
+ const handler = this.flagHandlers.get(name);
5190
+ if (!handler?.onEventAfter) {
5191
+ continue;
4336
5192
  }
5193
+ handler.onEventAfter({
5194
+ name,
5195
+ args: flagArgs[name],
5196
+ element,
5197
+ scope,
5198
+ rootScope: void 0,
5199
+ event,
5200
+ engine: this
5201
+ });
4337
5202
  }
4338
5203
  }
4339
- getListenerOptions(modifiers) {
4340
- if (!modifiers || modifiers.length === 0) {
4341
- return void 0;
5204
+ applyEventFlagArgTransforms(element, scope, flags, flagArgs, event) {
5205
+ let args = [event];
5206
+ for (const name of Object.keys(flags)) {
5207
+ const handler = this.flagHandlers.get(name);
5208
+ if (!handler?.transformEventArgs) {
5209
+ continue;
5210
+ }
5211
+ const nextArgs = handler.transformEventArgs(
5212
+ {
5213
+ name,
5214
+ args: flagArgs[name],
5215
+ element,
5216
+ scope,
5217
+ rootScope: void 0,
5218
+ event,
5219
+ engine: this
5220
+ },
5221
+ args
5222
+ );
5223
+ if (Array.isArray(nextArgs)) {
5224
+ args = nextArgs;
5225
+ }
5226
+ }
5227
+ return args;
5228
+ }
5229
+ matchesKeyFlag(event, flag) {
5230
+ if (!(event instanceof KeyboardEvent)) {
5231
+ return false;
5232
+ }
5233
+ const modifierChecks = {
5234
+ shift: event.shiftKey,
5235
+ ctrl: event.ctrlKey,
5236
+ alt: event.altKey,
5237
+ meta: event.metaKey
5238
+ };
5239
+ if (flag in modifierChecks) {
5240
+ return modifierChecks[flag] ?? false;
4342
5241
  }
4343
- const options = {};
4344
- if (modifiers.includes("once")) {
4345
- options.once = true;
5242
+ const keyAliases = {
5243
+ escape: "escape",
5244
+ esc: "escape",
5245
+ enter: "enter",
5246
+ tab: "tab",
5247
+ space: "space",
5248
+ spacebar: "space",
5249
+ up: "arrowup",
5250
+ down: "arrowdown",
5251
+ left: "arrowleft",
5252
+ right: "arrowright",
5253
+ arrowup: "arrowup",
5254
+ arrowdown: "arrowdown",
5255
+ arrowleft: "arrowleft",
5256
+ arrowright: "arrowright",
5257
+ delete: "delete",
5258
+ backspace: "backspace"
5259
+ };
5260
+ let key = event.key?.toLowerCase() ?? "";
5261
+ if (key === " ") {
5262
+ key = "space";
4346
5263
  }
4347
- if (modifiers.includes("passive")) {
4348
- options.passive = true;
5264
+ const expectedKey = keyAliases[flag] ?? flag;
5265
+ return key === expectedKey;
5266
+ }
5267
+ async withExecutionElement(element, fn) {
5268
+ if (!element) {
5269
+ await fn();
5270
+ return;
4349
5271
  }
4350
- if (modifiers.includes("capture")) {
4351
- options.capture = true;
5272
+ this.executionStack.push(element);
5273
+ try {
5274
+ await fn();
5275
+ } finally {
5276
+ this.executionStack.pop();
4352
5277
  }
4353
- return Object.keys(options).length > 0 ? options : void 0;
5278
+ }
5279
+ getCurrentElement() {
5280
+ return this.executionStack[this.executionStack.length - 1];
4354
5281
  }
4355
5282
  async execute(code, scope, element, rootScope) {
4356
5283
  let block = this.codeCache.get(code);
@@ -4358,22 +5285,26 @@ var Engine = class _Engine {
4358
5285
  block = Parser.parseInline(code);
4359
5286
  this.codeCache.set(code, block);
4360
5287
  }
4361
- const context = {
4362
- scope,
4363
- rootScope,
4364
- globals: this.globals,
4365
- ...element ? { element } : {}
4366
- };
4367
- await block.evaluate(context);
5288
+ await this.withExecutionElement(element, async () => {
5289
+ const context = {
5290
+ scope,
5291
+ rootScope,
5292
+ globals: this.globals,
5293
+ ...element ? { element } : {}
5294
+ };
5295
+ await block.evaluate(context);
5296
+ });
4368
5297
  }
4369
5298
  async executeBlock(block, scope, element, rootScope) {
4370
- const context = {
4371
- scope,
4372
- rootScope,
4373
- globals: this.globals,
4374
- ...element ? { element } : {}
4375
- };
4376
- await block.evaluate(context);
5299
+ await this.withExecutionElement(element, async () => {
5300
+ const context = {
5301
+ scope,
5302
+ rootScope,
5303
+ globals: this.globals,
5304
+ ...element ? { element } : {}
5305
+ };
5306
+ await block.evaluate(context);
5307
+ });
4377
5308
  }
4378
5309
  async safeExecute(code, scope, element, rootScope) {
4379
5310
  try {
@@ -4396,15 +5327,26 @@ var Engine = class _Engine {
4396
5327
  collectBehavior(behavior, parentSelector, rootSelectorOverride) {
4397
5328
  const selector = parentSelector ? `${parentSelector} ${behavior.selector.selectorText}` : behavior.selector.selectorText;
4398
5329
  const rootSelector = rootSelectorOverride ?? (parentSelector ?? behavior.selector.selectorText);
5330
+ const behaviorHash = this.hashBehavior(behavior);
5331
+ const hash = `${selector}::${rootSelector}::${behaviorHash}`;
5332
+ if (this.behaviorRegistryHashes.has(hash)) {
5333
+ return;
5334
+ }
4399
5335
  const cached = this.getCachedBehavior(behavior);
4400
- this.behaviorRegistry.push({
5336
+ const entry = {
4401
5337
  id: this.behaviorId += 1,
5338
+ hash,
4402
5339
  selector,
4403
5340
  rootSelector,
4404
5341
  specificity: this.computeSpecificity(selector),
4405
5342
  order: this.behaviorRegistry.length,
4406
- ...cached
4407
- });
5343
+ flags: behavior.flags ?? {},
5344
+ flagArgs: behavior.flagArgs ?? {},
5345
+ ...cached,
5346
+ ...parentSelector ? { parentSelector } : {}
5347
+ };
5348
+ this.behaviorRegistry.push(entry);
5349
+ this.behaviorRegistryHashes.add(hash);
4408
5350
  this.collectNestedBehaviors(behavior.body, selector, rootSelector);
4409
5351
  }
4410
5352
  collectNestedBehaviors(block, parentSelector, rootSelector) {
@@ -4477,7 +5419,8 @@ var Engine = class _Engine {
4477
5419
  blocks.push({
4478
5420
  event: statement.eventName,
4479
5421
  body: statement.body,
4480
- modifiers: statement.modifiers,
5422
+ flags: statement.flags,
5423
+ flagArgs: statement.flagArgs,
4481
5424
  args: statement.args
4482
5425
  });
4483
5426
  }
@@ -4542,6 +5485,8 @@ var Engine = class _Engine {
4542
5485
  return {
4543
5486
  type,
4544
5487
  selector: node.selector?.selectorText ?? "",
5488
+ flags: node.flags ?? {},
5489
+ flagArgs: node.flagArgs ?? {},
4545
5490
  body: this.normalizeNode(node.body)
4546
5491
  };
4547
5492
  }
@@ -4559,6 +5504,8 @@ var Engine = class _Engine {
4559
5504
  type,
4560
5505
  eventName: node.eventName ?? "",
4561
5506
  args: Array.isArray(node.args) ? node.args : [],
5507
+ flags: node.flags ?? {},
5508
+ flagArgs: node.flagArgs ?? {},
4562
5509
  body: this.normalizeNode(node.body)
4563
5510
  };
4564
5511
  }
@@ -4576,21 +5523,9 @@ var Engine = class _Engine {
4576
5523
  return {
4577
5524
  type,
4578
5525
  target: this.normalizeNode(node.target),
4579
- value: this.normalizeNode(node.value)
4580
- };
4581
- }
4582
- if (type === "StateBlock") {
4583
- return {
4584
- type,
4585
- entries: Array.isArray(node.entries) ? node.entries.map((entry) => this.normalizeNode(entry)) : []
4586
- };
4587
- }
4588
- if (type === "StateEntry") {
4589
- return {
4590
- type,
4591
- name: node.name ?? "",
4592
5526
  value: this.normalizeNode(node.value),
4593
- important: Boolean(node.important)
5527
+ operator: node.operator ?? "",
5528
+ prefix: Boolean(node.prefix)
4594
5529
  };
4595
5530
  }
4596
5531
  if (type === "FunctionDeclaration") {
@@ -4624,6 +5559,15 @@ var Engine = class _Engine {
4624
5559
  value: this.normalizeNode(node.value ?? null)
4625
5560
  };
4626
5561
  }
5562
+ if (type === "Assert") {
5563
+ return {
5564
+ type,
5565
+ test: this.normalizeNode(node.test)
5566
+ };
5567
+ }
5568
+ if (type === "Break" || type === "Continue") {
5569
+ return { type };
5570
+ }
4627
5571
  if (type === "If") {
4628
5572
  return {
4629
5573
  type,
@@ -4648,6 +5592,15 @@ var Engine = class _Engine {
4648
5592
  body: this.normalizeNode(node.body)
4649
5593
  };
4650
5594
  }
5595
+ if (type === "ForEach") {
5596
+ return {
5597
+ type,
5598
+ kind: node.kind ?? "of",
5599
+ target: this.normalizeNode(node.target),
5600
+ iterable: this.normalizeNode(node.iterable),
5601
+ body: this.normalizeNode(node.body)
5602
+ };
5603
+ }
4651
5604
  if (type === "Try") {
4652
5605
  return {
4653
5606
  type,
@@ -4770,7 +5723,9 @@ var Engine = class _Engine {
4770
5723
  globals: this.globals,
4771
5724
  element,
4772
5725
  returnValue: void 0,
4773
- returning: false
5726
+ returning: false,
5727
+ breaking: false,
5728
+ continuing: false
4774
5729
  };
4775
5730
  const previousValues = /* @__PURE__ */ new Map();
4776
5731
  await this.applyFunctionParams(callScope, declaration.params, previousValues, context, args);
@@ -4821,6 +5776,7 @@ var Engine = class _Engine {
4821
5776
  const context = { scope, rootScope, element };
4822
5777
  const operator = declaration.operator;
4823
5778
  const debounceMs = declaration.flags.debounce ? declaration.flagArgs.debounce ?? 200 : void 0;
5779
+ const transform = (value) => this.applyCustomFlagTransforms(value, element, scope, declaration);
4824
5780
  const importantKey = this.getImportantKey(declaration);
4825
5781
  if (!declaration.flags.important && importantKey && this.isImportant(element, importantKey)) {
4826
5782
  return;
@@ -4831,7 +5787,8 @@ var Engine = class _Engine {
4831
5787
  this.applyCustomFlags(element, scope, declaration);
4832
5788
  if (declaration.target instanceof IdentifierExpression) {
4833
5789
  const value = await declaration.value.evaluate(context);
4834
- scope.setPath(declaration.target.name, value);
5790
+ const transformed = this.applyCustomFlagTransforms(value, element, scope, declaration);
5791
+ scope.setPath(declaration.target.name, transformed);
4835
5792
  if (declaration.flags.important && importantKey) {
4836
5793
  this.markImportant(element, importantKey);
4837
5794
  }
@@ -4844,7 +5801,7 @@ var Engine = class _Engine {
4844
5801
  const exprIdentifier = declaration.value instanceof IdentifierExpression ? declaration.value.name : void 0;
4845
5802
  if (operator === ":>") {
4846
5803
  if (exprIdentifier) {
4847
- this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope);
5804
+ this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope, transform);
4848
5805
  }
4849
5806
  if (declaration.flags.important && importantKey) {
4850
5807
  this.markImportant(element, importantKey);
@@ -4852,11 +5809,12 @@ var Engine = class _Engine {
4852
5809
  return;
4853
5810
  }
4854
5811
  if (operator === ":=" && exprIdentifier) {
4855
- this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope);
5812
+ this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope, transform);
4856
5813
  }
4857
5814
  if (!exprIdentifier) {
4858
5815
  const value = await declaration.value.evaluate(context);
4859
- this.setDirectiveValue(element, target, value, declaration.flags.trusted);
5816
+ const transformed = this.applyCustomFlagTransforms(value, element, scope, declaration);
5817
+ this.setDirectiveValue(element, target, transformed, declaration.flags.trusted);
4860
5818
  const shouldWatch2 = operator === ":<" || operator === ":=";
4861
5819
  if (shouldWatch2) {
4862
5820
  this.applyDirectiveFromExpression(
@@ -4906,6 +5864,63 @@ var Engine = class _Engine {
4906
5864
  });
4907
5865
  }
4908
5866
  }
5867
+ applyCustomFlagTransforms(value, element, scope, declaration) {
5868
+ if (this.flagHandlers.size === 0) {
5869
+ return value;
5870
+ }
5871
+ let nextValue = value;
5872
+ for (const [name, handler] of this.flagHandlers) {
5873
+ if (!declaration.flags[name] || !handler.transformValue) {
5874
+ continue;
5875
+ }
5876
+ nextValue = handler.transformValue(
5877
+ {
5878
+ name,
5879
+ args: declaration.flagArgs[name],
5880
+ element,
5881
+ scope,
5882
+ declaration
5883
+ },
5884
+ nextValue
5885
+ );
5886
+ }
5887
+ return nextValue;
5888
+ }
5889
+ async applyBehaviorModifierHook(hook, behavior, element, scope, rootScope) {
5890
+ if (this.behaviorModifiers.size === 0) {
5891
+ return;
5892
+ }
5893
+ for (const [name, handler] of this.behaviorModifiers) {
5894
+ if (!behavior.flags?.[name]) {
5895
+ continue;
5896
+ }
5897
+ const callback = handler[hook];
5898
+ if (!callback) {
5899
+ continue;
5900
+ }
5901
+ await callback({
5902
+ name,
5903
+ args: behavior.flagArgs?.[name],
5904
+ element,
5905
+ scope,
5906
+ rootScope,
5907
+ behavior,
5908
+ engine: this
5909
+ });
5910
+ }
5911
+ }
5912
+ behaviorHasModifierHooks(behavior) {
5913
+ if (this.behaviorModifiers.size === 0) {
5914
+ return false;
5915
+ }
5916
+ const flags = behavior.flags ?? {};
5917
+ for (const name of Object.keys(flags)) {
5918
+ if (flags[name] && this.behaviorModifiers.has(name)) {
5919
+ return true;
5920
+ }
5921
+ }
5922
+ return false;
5923
+ }
4909
5924
  applyDirectiveFromScope(element, target, expr, scope, trusted, debounceMs, watch = true, rootScope) {
4910
5925
  if (target.kind === "attr" && target.name === "html" && element instanceof HTMLElement) {
4911
5926
  const handler2 = () => {
@@ -4922,7 +5937,7 @@ var Engine = class _Engine {
4922
5937
  const useRoot = expr.startsWith("root.") && rootScope;
4923
5938
  const sourceScope = useRoot ? rootScope : scope;
4924
5939
  const watchExpr = useRoot ? expr.slice("root.".length) : expr;
4925
- this.watchWithDebounce(sourceScope, watchExpr, handler2, debounceMs);
5940
+ this.watchWithDebounce(sourceScope, watchExpr, handler2, debounceMs, element);
4926
5941
  }
4927
5942
  return;
4928
5943
  }
@@ -4941,7 +5956,7 @@ var Engine = class _Engine {
4941
5956
  const useRoot = expr.startsWith("root.") && rootScope;
4942
5957
  const sourceScope = useRoot ? rootScope : scope;
4943
5958
  const watchExpr = useRoot ? expr.slice("root.".length) : expr;
4944
- this.watchWithDebounce(sourceScope, watchExpr, handler, debounceMs);
5959
+ this.watchWithDebounce(sourceScope, watchExpr, handler, debounceMs, element);
4945
5960
  }
4946
5961
  }
4947
5962
  applyDirectiveFromExpression(element, target, expr, scope, trusted, debounceMs, rootScope) {
@@ -4953,45 +5968,49 @@ var Engine = class _Engine {
4953
5968
  void handler();
4954
5969
  this.watchAllScopes(scope, () => {
4955
5970
  void handler();
4956
- }, debounceMs);
5971
+ }, debounceMs, element);
4957
5972
  }
4958
- applyDirectiveToScope(element, target, expr, scope, debounceMs, rootScope) {
5973
+ applyDirectiveToScope(element, target, expr, scope, debounceMs, rootScope, transform) {
4959
5974
  const useRoot = expr.startsWith("root.") && rootScope;
4960
5975
  const targetScope = useRoot ? rootScope : scope;
4961
5976
  const targetExpr = useRoot ? `self.${expr.slice("root.".length)}` : expr;
4962
5977
  if (target.kind === "attr" && target.name === "value") {
4963
- this.applyValueBindingToScope(element, targetExpr, debounceMs, targetScope);
5978
+ this.applyValueBindingToScope(element, targetExpr, debounceMs, targetScope, transform);
4964
5979
  return;
4965
5980
  }
4966
5981
  if (target.kind === "attr" && target.name === "checked") {
4967
- this.applyCheckedBindingToScope(element, targetExpr, debounceMs, targetScope);
5982
+ this.applyCheckedBindingToScope(element, targetExpr, debounceMs, targetScope, transform);
4968
5983
  return;
4969
5984
  }
4970
5985
  const value = this.getDirectiveValue(element, target);
4971
5986
  if (value != null) {
4972
- targetScope.set(targetExpr, value);
5987
+ const nextValue = transform ? transform(value) : value;
5988
+ targetScope.set(targetExpr, nextValue);
4973
5989
  }
4974
5990
  }
4975
- applyCheckedBindingToScope(element, expr, debounceMs, scope) {
5991
+ applyCheckedBindingToScope(element, expr, debounceMs, scope, transform) {
4976
5992
  if (!(element instanceof HTMLInputElement)) {
4977
5993
  return;
4978
5994
  }
4979
5995
  const handler = () => {
4980
5996
  const targetScope = scope ?? this.getScope(element);
4981
- targetScope.set(expr, element.checked);
5997
+ const value = transform ? transform(element.checked) : element.checked;
5998
+ targetScope.set(expr, value);
4982
5999
  };
4983
6000
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4984
6001
  effectiveHandler();
4985
6002
  element.addEventListener("change", effectiveHandler);
4986
6003
  element.addEventListener("input", effectiveHandler);
4987
6004
  }
4988
- applyValueBindingToScope(element, expr, debounceMs, scope) {
6005
+ applyValueBindingToScope(element, expr, debounceMs, scope, transform) {
4989
6006
  if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)) {
4990
6007
  return;
4991
6008
  }
4992
6009
  const handler = () => {
4993
6010
  const targetScope = scope ?? this.getScope(element);
4994
- applyBindToScope(element, expr, targetScope);
6011
+ const value = element.value;
6012
+ const nextValue = transform ? transform(value) : value;
6013
+ targetScope.set(expr, nextValue);
4995
6014
  };
4996
6015
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4997
6016
  effectiveHandler();
@@ -5008,6 +6027,14 @@ var Engine = class _Engine {
5008
6027
  return;
5009
6028
  }
5010
6029
  if (target.kind === "attr") {
6030
+ if (target.name === "text" && element instanceof HTMLElement) {
6031
+ element.innerText = value == null ? "" : String(value);
6032
+ return;
6033
+ }
6034
+ if (target.name === "content" && element instanceof HTMLElement) {
6035
+ element.textContent = value == null ? "" : String(value);
6036
+ return;
6037
+ }
5011
6038
  if (target.name === "value") {
5012
6039
  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
5013
6040
  element.value = value == null ? "" : String(value);
@@ -5038,6 +6065,12 @@ var Engine = class _Engine {
5038
6065
  }
5039
6066
  getDirectiveValue(element, target) {
5040
6067
  if (target.kind === "attr") {
6068
+ if (target.name === "text" && element instanceof HTMLElement) {
6069
+ return element.innerText;
6070
+ }
6071
+ if (target.name === "content" && element instanceof HTMLElement) {
6072
+ return element.textContent ?? "";
6073
+ }
5041
6074
  if (target.name === "value") {
5042
6075
  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
5043
6076
  return element.value;
@@ -5073,17 +6106,27 @@ var Engine = class _Engine {
5073
6106
  id: "vsn-bind",
5074
6107
  match: (name) => name.startsWith("vsn-bind"),
5075
6108
  handle: (element, name, value, scope) => {
5076
- const direction = this.parseBindDirection(name);
5077
- this.bindBindings.set(element, { expr: value, direction });
5078
- if (direction === "to" || direction === "both") {
6109
+ const parsedDirection = this.parseBindDirection(name);
6110
+ const config = this.resolveBindConfig(element, value, scope, parsedDirection);
6111
+ const direction = config.direction;
6112
+ const auto = parsedDirection === "auto";
6113
+ this.bindBindings.set(element, { expr: value, direction, auto });
6114
+ if (!auto && (direction === "to" || direction === "both")) {
5079
6115
  this.markInlineDeclaration(element, `state:${value}`);
5080
6116
  }
5081
- if (direction === "to" || direction === "both") {
6117
+ if (config.seedFromScope) {
6118
+ applyBindToElement(element, value, scope);
6119
+ }
6120
+ if (config.deferToScope) {
6121
+ this.pendingAutoBindToScope.push({ element, expr: value, scope });
6122
+ } else if (config.syncToScope) {
5082
6123
  applyBindToScope(element, value, scope);
6124
+ }
6125
+ if (direction === "to" || direction === "both") {
5083
6126
  this.attachBindInputHandler(element, value);
5084
6127
  }
5085
6128
  if (direction === "from" || direction === "both") {
5086
- this.watch(scope, value, () => applyBindToElement(element, value, scope));
6129
+ this.watch(scope, value, () => applyBindToElement(element, value, scope), element);
5087
6130
  }
5088
6131
  }
5089
6132
  });
@@ -5095,7 +6138,7 @@ var Engine = class _Engine {
5095
6138
  if (element instanceof HTMLElement) {
5096
6139
  applyIf(element, value, scope);
5097
6140
  }
5098
- this.watch(scope, value, () => this.evaluate(element));
6141
+ this.watch(scope, value, () => this.evaluate(element), element);
5099
6142
  }
5100
6143
  });
5101
6144
  this.registerAttributeHandler({
@@ -5106,7 +6149,7 @@ var Engine = class _Engine {
5106
6149
  if (element instanceof HTMLElement) {
5107
6150
  applyShow(element, value, scope);
5108
6151
  }
5109
- this.watch(scope, value, () => this.evaluate(element));
6152
+ this.watch(scope, value, () => this.evaluate(element), element);
5110
6153
  }
5111
6154
  });
5112
6155
  this.registerAttributeHandler({
@@ -5122,7 +6165,7 @@ var Engine = class _Engine {
5122
6165
  this.handleTrustedHtml(element);
5123
6166
  }
5124
6167
  }
5125
- this.watch(scope, value, () => this.evaluate(element));
6168
+ this.watch(scope, value, () => this.evaluate(element), element);
5126
6169
  }
5127
6170
  });
5128
6171
  this.registerAttributeHandler({
@@ -5135,7 +6178,7 @@ var Engine = class _Engine {
5135
6178
  }
5136
6179
  this.eachBindings.set(element, { ...config, rendered: [] });
5137
6180
  this.renderEach(element);
5138
- this.watch(scope, config.listExpr, () => this.renderEach(element));
6181
+ this.watch(scope, config.listExpr, () => this.renderEach(element), element);
5139
6182
  }
5140
6183
  });
5141
6184
  this.registerAttributeHandler({
@@ -5190,15 +6233,27 @@ function parseCFS(source) {
5190
6233
  const parser = new Parser(source);
5191
6234
  return parser.parseProgram();
5192
6235
  }
6236
+ if (typeof window !== "undefined") {
6237
+ window["parseCFS"] = parseCFS;
6238
+ }
5193
6239
  function autoMount(root = document) {
5194
6240
  if (typeof document === "undefined") {
5195
6241
  return null;
5196
6242
  }
5197
6243
  const engine = new Engine();
6244
+ globalThis.VSNEngine = engine;
5198
6245
  const startTime = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
5199
6246
  const mount = () => {
5200
6247
  const target = root instanceof Document ? root.body : root;
5201
6248
  if (target) {
6249
+ const plugins = globalThis.VSNPlugins;
6250
+ if (plugins && typeof plugins === "object") {
6251
+ for (const plugin of Object.values(plugins)) {
6252
+ if (typeof plugin === "function") {
6253
+ plugin(engine);
6254
+ }
6255
+ }
6256
+ }
5202
6257
  const sources = Array.from(document.querySelectorAll('script[type="text/vsn"]')).map((script) => script.textContent ?? "").join("\n");
5203
6258
  if (sources.trim()) {
5204
6259
  engine.registerBehaviors(sources);
@@ -5210,9 +6265,9 @@ function autoMount(root = document) {
5210
6265
  }
5211
6266
  };
5212
6267
  if (document.readyState === "loading") {
5213
- document.addEventListener("DOMContentLoaded", mount, { once: true });
6268
+ document.addEventListener("DOMContentLoaded", () => setTimeout(mount, 0), { once: true });
5214
6269
  } else {
5215
- mount();
6270
+ setTimeout(mount, 0);
5216
6271
  }
5217
6272
  return engine;
5218
6273
  }
@@ -5226,16 +6281,21 @@ if (typeof document !== "undefined") {
5226
6281
  0 && (module.exports = {
5227
6282
  ArrayExpression,
5228
6283
  ArrayPattern,
6284
+ AssertError,
6285
+ AssertNode,
5229
6286
  AssignmentNode,
5230
6287
  AwaitExpression,
5231
6288
  BaseNode,
5232
6289
  BehaviorNode,
5233
6290
  BinaryExpression,
5234
6291
  BlockNode,
6292
+ BreakNode,
5235
6293
  CallExpression,
6294
+ ContinueNode,
5236
6295
  DeclarationNode,
5237
6296
  DirectiveExpression,
5238
6297
  Engine,
6298
+ ForEachNode,
5239
6299
  ForNode,
5240
6300
  FunctionDeclarationNode,
5241
6301
  FunctionExpression,
@@ -5255,8 +6315,6 @@ if (typeof document !== "undefined") {
5255
6315
  ReturnNode,
5256
6316
  SelectorNode,
5257
6317
  SpreadElement,
5258
- StateBlockNode,
5259
- StateEntryNode,
5260
6318
  TemplateExpression,
5261
6319
  TernaryExpression,
5262
6320
  TokenType,