vsn 1.0.2 → 1.0.4

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;
781
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();
782
1010
  }
783
- } finally {
784
- context.scope = previousScope;
1011
+ let index = 0;
1012
+ const loop = () => {
1013
+ if (index >= entries.length || context.returning) {
1014
+ context.scope = previousScope;
1015
+ return void 0;
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);
1045
+ }
1046
+ return [];
1047
+ }
1048
+ if (typeof value === "string") {
1049
+ return Array.from(value);
785
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
+ });
894
1221
  }
895
- return inner.returnValue;
1222
+ if (activeScope && activeScope === scope) {
1223
+ this.restoreParams(activeScope, previousValues);
1224
+ }
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);
1232
+ const setPath = scope.setPath?.bind(scope);
1233
+ if (!setPath) {
1234
+ return;
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;
917
1259
  }
918
- scope.setPath(`self.${name}`, value);
919
- argIndex += 1;
920
- }
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,13 +1347,22 @@ 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 {
@@ -1016,15 +1371,17 @@ var UnaryExpression = class extends BaseNode {
1016
1371
  this.operator = operator;
1017
1372
  this.argument = argument;
1018
1373
  }
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;
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", "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,30 +3077,13 @@ ${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
- }
2642
- } else {
2643
- throw new Error(`Unknown flag ${name}`);
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;
2644
3087
  }
2645
3088
  }
2646
3089
  return { flags, flagArgs };
@@ -2655,21 +3098,86 @@ ${caret}`;
2655
3098
  if (!token) {
2656
3099
  throw new Error("Unterminated flag arguments");
2657
3100
  }
2658
- let value;
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
+ }
2659
3111
  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}`);
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;
3168
+ } else {
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();
3178
+ }
2669
3179
  }
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
  }
@@ -3420,17 +3932,14 @@ function applyShow(element, expression, scope) {
3420
3932
  }
3421
3933
 
3422
3934
  // src/runtime/html.ts
3423
- function sanitizeHtml(value) {
3424
- return value.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
3425
- }
3426
- function applyHtml(element, expression, scope, trusted) {
3935
+ function applyHtml(element, expression, scope) {
3427
3936
  const key = expression.trim();
3428
3937
  if (!key) {
3429
3938
  return;
3430
3939
  }
3431
3940
  const value = scope.get(key);
3432
3941
  const html = value == null ? "" : String(value);
3433
- element.innerHTML = trusted ? html : sanitizeHtml(html);
3942
+ element.innerHTML = html;
3434
3943
  }
3435
3944
 
3436
3945
  // src/runtime/http.ts
@@ -3450,7 +3959,7 @@ async function applyGet(element, config, scope, onHtmlApplied) {
3450
3959
  }
3451
3960
  if (config.swap === "outer") {
3452
3961
  const wrapper = document.createElement("div");
3453
- applyHtml(wrapper, "__html", { get: () => html }, config.trusted);
3962
+ applyHtml(wrapper, "__html", { get: () => html });
3454
3963
  const replacement = wrapper.firstElementChild;
3455
3964
  if (replacement && target.parentNode) {
3456
3965
  target.parentNode.replaceChild(replacement, target);
@@ -3458,7 +3967,7 @@ async function applyGet(element, config, scope, onHtmlApplied) {
3458
3967
  }
3459
3968
  return;
3460
3969
  }
3461
- applyHtml(target, "__html", { get: () => html }, config.trusted);
3970
+ applyHtml(target, "__html", { get: () => html });
3462
3971
  onHtmlApplied?.(target);
3463
3972
  }
3464
3973
  function resolveTarget(element, selector) {
@@ -3494,18 +4003,19 @@ var Engine = class _Engine {
3494
4003
  eachBindings = /* @__PURE__ */ new WeakMap();
3495
4004
  lifecycleBindings = /* @__PURE__ */ new WeakMap();
3496
4005
  behaviorRegistry = [];
4006
+ behaviorRegistryHashes = /* @__PURE__ */ new Set();
3497
4007
  behaviorBindings = /* @__PURE__ */ new WeakMap();
3498
4008
  behaviorListeners = /* @__PURE__ */ new WeakMap();
3499
4009
  behaviorId = 0;
3500
4010
  codeCache = /* @__PURE__ */ new Map();
3501
4011
  behaviorCache = /* @__PURE__ */ new Map();
3502
4012
  observer;
3503
- observerRoot;
3504
4013
  attributeHandlers = [];
3505
4014
  globals = {};
3506
4015
  importantFlags = /* @__PURE__ */ new WeakMap();
3507
4016
  inlineDeclarations = /* @__PURE__ */ new WeakMap();
3508
4017
  flagHandlers = /* @__PURE__ */ new Map();
4018
+ behaviorModifiers = /* @__PURE__ */ new Map();
3509
4019
  pendingAdded = /* @__PURE__ */ new Set();
3510
4020
  pendingRemoved = /* @__PURE__ */ new Set();
3511
4021
  pendingUpdated = /* @__PURE__ */ new Set();
@@ -3514,10 +4024,118 @@ var Engine = class _Engine {
3514
4024
  diagnostics;
3515
4025
  logger;
3516
4026
  pendingUses = [];
4027
+ pendingAutoBindToScope = [];
4028
+ scopeWatchers = /* @__PURE__ */ new WeakMap();
4029
+ executionStack = [];
4030
+ groupProxyCache = /* @__PURE__ */ new WeakMap();
3517
4031
  constructor(options = {}) {
3518
4032
  this.diagnostics = options.diagnostics ?? false;
3519
4033
  this.logger = options.logger ?? console;
3520
4034
  this.registerGlobal("console", console);
4035
+ this.registerFlag("important");
4036
+ this.registerFlag("debounce", {
4037
+ onEventBind: ({ args }) => ({
4038
+ debounceMs: typeof args === "number" ? args : 200
4039
+ })
4040
+ });
4041
+ this.registerFlag("prevent", {
4042
+ onEventBefore: ({ event }) => {
4043
+ event?.preventDefault();
4044
+ }
4045
+ });
4046
+ this.registerFlag("stop", {
4047
+ onEventBefore: ({ event }) => {
4048
+ event?.stopPropagation();
4049
+ }
4050
+ });
4051
+ this.registerFlag("self", {
4052
+ onEventBefore: ({ event, element }) => {
4053
+ const target = event?.target;
4054
+ if (!(target instanceof Node)) {
4055
+ return false;
4056
+ }
4057
+ return target === element;
4058
+ }
4059
+ });
4060
+ this.registerFlag("outside", {
4061
+ onEventBind: ({ element }) => ({ listenerTarget: element.ownerDocument }),
4062
+ onEventBefore: ({ event, element }) => {
4063
+ const target = event?.target;
4064
+ if (!(target instanceof Node)) {
4065
+ return false;
4066
+ }
4067
+ return !element.contains(target);
4068
+ }
4069
+ });
4070
+ this.registerFlag("once", {
4071
+ onEventBind: () => ({ options: { once: true } })
4072
+ });
4073
+ this.registerFlag("passive", {
4074
+ onEventBind: () => ({ options: { passive: true } })
4075
+ });
4076
+ this.registerFlag("capture", {
4077
+ onEventBind: () => ({ options: { capture: true } })
4078
+ });
4079
+ this.registerFlag("shift", {
4080
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "shift")
4081
+ });
4082
+ this.registerFlag("ctrl", {
4083
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "ctrl")
4084
+ });
4085
+ this.registerFlag("control", {
4086
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "ctrl")
4087
+ });
4088
+ this.registerFlag("alt", {
4089
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "alt")
4090
+ });
4091
+ this.registerFlag("meta", {
4092
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "meta")
4093
+ });
4094
+ this.registerFlag("enter", {
4095
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "enter")
4096
+ });
4097
+ this.registerFlag("escape", {
4098
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "escape")
4099
+ });
4100
+ this.registerFlag("esc", {
4101
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "escape")
4102
+ });
4103
+ this.registerFlag("tab", {
4104
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "tab")
4105
+ });
4106
+ this.registerFlag("space", {
4107
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "space")
4108
+ });
4109
+ this.registerFlag("up", {
4110
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowup")
4111
+ });
4112
+ this.registerFlag("down", {
4113
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowdown")
4114
+ });
4115
+ this.registerFlag("left", {
4116
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowleft")
4117
+ });
4118
+ this.registerFlag("right", {
4119
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowright")
4120
+ });
4121
+ this.registerFlag("arrowup", {
4122
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowup")
4123
+ });
4124
+ this.registerFlag("arrowdown", {
4125
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowdown")
4126
+ });
4127
+ this.registerFlag("arrowleft", {
4128
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowleft")
4129
+ });
4130
+ this.registerFlag("arrowright", {
4131
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowright")
4132
+ });
4133
+ this.registerFlag("delete", {
4134
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "delete")
4135
+ });
4136
+ this.registerFlag("backspace", {
4137
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "backspace")
4138
+ });
3521
4139
  this.registerGlobal("list", {
3522
4140
  async map(items, fn) {
3523
4141
  if (!Array.isArray(items) || typeof fn !== "function") {
@@ -3555,6 +4173,96 @@ var Engine = class _Engine {
3555
4173
  }
3556
4174
  });
3557
4175
  this.registerDefaultAttributeHandlers();
4176
+ this.registerFlag("int", {
4177
+ transformValue: (_context, value) => this.coerceInt(value)
4178
+ });
4179
+ this.registerFlag("float", {
4180
+ transformValue: (_context, value) => this.coerceFloat(value)
4181
+ });
4182
+ this.registerBehaviorModifier("group", {
4183
+ onConstruct: ({ args, scope, rootScope, behavior, element }) => {
4184
+ const key = typeof args === "string" ? args : void 0;
4185
+ if (!key) {
4186
+ return;
4187
+ }
4188
+ const targetScope = this.getGroupTargetScope(element, behavior, scope, rootScope);
4189
+ const existing = targetScope.getPath?.(key);
4190
+ const list = Array.isArray(existing) ? existing : [];
4191
+ const proxy = this.getGroupProxy(scope);
4192
+ if (!list.includes(proxy)) {
4193
+ list.push(proxy);
4194
+ targetScope.setPath?.(key, list);
4195
+ } else if (!Array.isArray(existing)) {
4196
+ targetScope.setPath?.(key, list);
4197
+ }
4198
+ },
4199
+ onUnbind: ({ args, scope, rootScope, behavior, element }) => {
4200
+ const key = typeof args === "string" ? args : void 0;
4201
+ if (!key) {
4202
+ return;
4203
+ }
4204
+ const targetScope = this.getGroupTargetScope(element, behavior, scope, rootScope);
4205
+ const existing = targetScope.getPath?.(key);
4206
+ if (!Array.isArray(existing)) {
4207
+ return;
4208
+ }
4209
+ const proxy = this.getGroupProxy(scope);
4210
+ const next = existing.filter((entry) => entry !== proxy);
4211
+ if (next.length !== existing.length) {
4212
+ targetScope.setPath?.(key, next);
4213
+ }
4214
+ }
4215
+ });
4216
+ }
4217
+ getGroupTargetScope(element, behavior, scope, rootScope) {
4218
+ let targetScope = rootScope ?? scope;
4219
+ if (behavior.parentSelector) {
4220
+ const parentElement = element.closest(behavior.parentSelector);
4221
+ if (parentElement) {
4222
+ targetScope = this.getScope(parentElement);
4223
+ }
4224
+ }
4225
+ return targetScope;
4226
+ }
4227
+ getGroupProxy(scope) {
4228
+ const cached = this.groupProxyCache.get(scope);
4229
+ if (cached) {
4230
+ return cached;
4231
+ }
4232
+ const proxy = new Proxy(
4233
+ {},
4234
+ {
4235
+ get: (_target, prop) => {
4236
+ if (typeof prop === "symbol") {
4237
+ return void 0;
4238
+ }
4239
+ if (prop === "__scope") {
4240
+ return scope;
4241
+ }
4242
+ return scope.getPath(String(prop));
4243
+ },
4244
+ set: (_target, prop, value) => {
4245
+ if (typeof prop === "symbol") {
4246
+ return false;
4247
+ }
4248
+ scope.setPath(String(prop), value);
4249
+ return true;
4250
+ },
4251
+ has: (_target, prop) => {
4252
+ if (typeof prop === "symbol") {
4253
+ return false;
4254
+ }
4255
+ return scope.getPath(String(prop)) !== void 0;
4256
+ },
4257
+ getOwnPropertyDescriptor: () => ({
4258
+ enumerable: true,
4259
+ configurable: true
4260
+ }),
4261
+ ownKeys: () => []
4262
+ }
4263
+ );
4264
+ this.groupProxyCache.set(scope, proxy);
4265
+ return proxy;
3558
4266
  }
3559
4267
  async mount(root) {
3560
4268
  const documentRoot = root.ownerDocument;
@@ -3581,7 +4289,10 @@ var Engine = class _Engine {
3581
4289
  this.disconnectObserver();
3582
4290
  }
3583
4291
  registerBehaviors(source) {
3584
- const program = new Parser(source, { customFlags: new Set(this.flagHandlers.keys()) }).parseProgram();
4292
+ const program = new Parser(source, {
4293
+ customFlags: new Set(this.flagHandlers.keys()),
4294
+ behaviorFlags: new Set(this.behaviorModifiers.keys())
4295
+ }).parseProgram();
3585
4296
  for (const use of program.uses) {
3586
4297
  if (use.flags?.wait) {
3587
4298
  this.pendingUses.push(this.waitForUseGlobal(use));
@@ -3605,11 +4316,14 @@ var Engine = class _Engine {
3605
4316
  Object.assign(this.globals, values);
3606
4317
  }
3607
4318
  registerFlag(name, handler = {}) {
3608
- const reserved = /* @__PURE__ */ new Set(["important", "trusted", "debounce"]);
4319
+ this.flagHandlers.set(name, handler);
4320
+ }
4321
+ registerBehaviorModifier(name, handler = {}) {
4322
+ const reserved = /* @__PURE__ */ new Set(["important", "debounce"]);
3609
4323
  if (reserved.has(name)) {
3610
- throw new Error(`Flag '${name}' is reserved`);
4324
+ throw new Error(`Behavior modifier '${name}' is reserved`);
3611
4325
  }
3612
- this.flagHandlers.set(name, handler);
4326
+ this.behaviorModifiers.set(name, handler);
3613
4327
  }
3614
4328
  getRegistryStats() {
3615
4329
  return {
@@ -3707,17 +4421,14 @@ var Engine = class _Engine {
3707
4421
  }
3708
4422
  const htmlBinding = this.htmlBindings.get(element);
3709
4423
  if (htmlBinding && element instanceof HTMLElement) {
3710
- applyHtml(element, htmlBinding.expr, scope, htmlBinding.trusted);
3711
- if (htmlBinding.trusted) {
3712
- this.handleTrustedHtml(element);
3713
- }
4424
+ applyHtml(element, htmlBinding.expr, scope);
4425
+ this.handleHtmlBehaviors(element);
3714
4426
  }
3715
4427
  }
3716
4428
  attachObserver(root) {
3717
4429
  if (this.observer) {
3718
4430
  return;
3719
4431
  }
3720
- this.observerRoot = root;
3721
4432
  this.observerFlush = debounce(() => this.flushObserverQueue(), 10);
3722
4433
  this.observer = new MutationObserver((mutations) => {
3723
4434
  for (const mutation of mutations) {
@@ -3747,7 +4458,6 @@ var Engine = class _Engine {
3747
4458
  disconnectObserver() {
3748
4459
  this.observer?.disconnect();
3749
4460
  this.observer = void 0;
3750
- this.observerRoot = void 0;
3751
4461
  this.pendingAdded.clear();
3752
4462
  this.pendingRemoved.clear();
3753
4463
  this.pendingUpdated.clear();
@@ -3776,6 +4486,8 @@ var Engine = class _Engine {
3776
4486
  if (this.behaviorBindings.has(node)) {
3777
4487
  this.runBehaviorDestruct(node);
3778
4488
  }
4489
+ this.cleanupScopeWatchers(node);
4490
+ this.cleanupBehaviorListeners(node);
3779
4491
  for (const child of Array.from(node.querySelectorAll("*"))) {
3780
4492
  if (this.lifecycleBindings.has(child)) {
3781
4493
  this.runDestruct(child);
@@ -3783,6 +4495,8 @@ var Engine = class _Engine {
3783
4495
  if (this.behaviorBindings.has(child)) {
3784
4496
  this.runBehaviorDestruct(child);
3785
4497
  }
4498
+ this.cleanupScopeWatchers(child);
4499
+ this.cleanupBehaviorListeners(child);
3786
4500
  }
3787
4501
  }
3788
4502
  handleAddedNode(node) {
@@ -3806,13 +4520,13 @@ var Engine = class _Engine {
3806
4520
  }
3807
4521
  async applyBehaviors(root) {
3808
4522
  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);
4523
+ if (this.behaviorRegistry.length > 0) {
4524
+ const elements = [root, ...Array.from(root.querySelectorAll("*"))];
4525
+ for (const element of elements) {
4526
+ await this.reapplyBehaviorsForElement(element);
4527
+ }
3815
4528
  }
4529
+ this.flushAutoBindQueue();
3816
4530
  }
3817
4531
  async reapplyBehaviorsForElement(element) {
3818
4532
  if (this.behaviorRegistry.length === 0) {
@@ -3844,15 +4558,18 @@ var Engine = class _Engine {
3844
4558
  const rootScope = this.getBehaviorRootScope(element, behavior);
3845
4559
  this.applyBehaviorFunctions(element, scope, behavior.functions, rootScope);
3846
4560
  await this.applyBehaviorDeclarations(element, scope, behavior.declarations, rootScope);
4561
+ await this.applyBehaviorModifierHook("onBind", behavior, element, scope, rootScope);
3847
4562
  if (behavior.construct) {
3848
4563
  await this.safeExecuteBlock(behavior.construct, scope, element, rootScope);
3849
4564
  }
4565
+ await this.applyBehaviorModifierHook("onConstruct", behavior, element, scope, rootScope);
3850
4566
  for (const onBlock of behavior.onBlocks) {
3851
4567
  this.attachBehaviorOnHandler(
3852
4568
  element,
3853
4569
  onBlock.event,
3854
4570
  onBlock.body,
3855
- onBlock.modifiers,
4571
+ onBlock.flags,
4572
+ onBlock.flagArgs,
3856
4573
  onBlock.args,
3857
4574
  behavior.id,
3858
4575
  rootScope
@@ -3862,10 +4579,11 @@ var Engine = class _Engine {
3862
4579
  }
3863
4580
  unbindBehaviorForElement(behavior, element, scope, bound) {
3864
4581
  bound.delete(behavior.id);
4582
+ const rootScope = this.getBehaviorRootScope(element, behavior);
3865
4583
  if (behavior.destruct) {
3866
- const rootScope = this.getBehaviorRootScope(element, behavior);
3867
4584
  void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
3868
4585
  }
4586
+ void this.applyBehaviorModifierHook("onDestruct", behavior, element, scope, rootScope);
3869
4587
  const listenerMap = this.behaviorListeners.get(element);
3870
4588
  const listeners = listenerMap?.get(behavior.id);
3871
4589
  if (listeners) {
@@ -3874,6 +4592,7 @@ var Engine = class _Engine {
3874
4592
  }
3875
4593
  listenerMap?.delete(behavior.id);
3876
4594
  }
4595
+ void this.applyBehaviorModifierHook("onUnbind", behavior, element, scope, rootScope);
3877
4596
  this.logDiagnostic("unbind", element, behavior);
3878
4597
  }
3879
4598
  runBehaviorDestruct(element) {
@@ -3883,11 +4602,15 @@ var Engine = class _Engine {
3883
4602
  }
3884
4603
  const scope = this.getScope(element);
3885
4604
  for (const behavior of this.behaviorRegistry) {
3886
- if (!bound.has(behavior.id) || !behavior.destruct) {
4605
+ if (!bound.has(behavior.id) || !behavior.destruct && !this.behaviorHasModifierHooks(behavior)) {
3887
4606
  continue;
3888
4607
  }
3889
4608
  const rootScope = this.getBehaviorRootScope(element, behavior);
3890
- void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
4609
+ if (behavior.destruct) {
4610
+ void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
4611
+ }
4612
+ void this.applyBehaviorModifierHook("onDestruct", behavior, element, scope, rootScope);
4613
+ void this.applyBehaviorModifierHook("onUnbind", behavior, element, scope, rootScope);
3891
4614
  }
3892
4615
  }
3893
4616
  attachAttributes(element) {
@@ -3971,6 +4694,7 @@ var Engine = class _Engine {
3971
4694
  const fragment = element.content.cloneNode(true);
3972
4695
  const roots = Array.from(fragment.children);
3973
4696
  const itemScope = new Scope(scope);
4697
+ itemScope.isEachItem = true;
3974
4698
  itemScope.setPath(`self.${binding.itemName}`, item);
3975
4699
  if (binding.indexName) {
3976
4700
  itemScope.setPath(`self.${binding.indexName}`, index);
@@ -4009,7 +4733,93 @@ var Engine = class _Engine {
4009
4733
  if (name.includes(":to")) {
4010
4734
  return "to";
4011
4735
  }
4012
- return "both";
4736
+ return "auto";
4737
+ }
4738
+ resolveBindConfig(element, expr, scope, direction) {
4739
+ if (direction !== "auto") {
4740
+ return {
4741
+ direction,
4742
+ seedFromScope: false,
4743
+ syncToScope: direction === "to" || direction === "both",
4744
+ deferToScope: false
4745
+ };
4746
+ }
4747
+ if (this.isInEachScope(scope)) {
4748
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4749
+ }
4750
+ if (this.isFormControl(element)) {
4751
+ if (this.hasScopeValue(scope, expr)) {
4752
+ return { direction: "both", seedFromScope: true, syncToScope: false, deferToScope: false };
4753
+ }
4754
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: true };
4755
+ }
4756
+ if (this.hasScopeValue(scope, expr)) {
4757
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4758
+ }
4759
+ if (this.hasElementValue(element)) {
4760
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: true };
4761
+ }
4762
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4763
+ }
4764
+ isFormControl(element) {
4765
+ return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement;
4766
+ }
4767
+ hasScopeValue(scope, expr) {
4768
+ const key = expr.trim();
4769
+ if (!key) {
4770
+ return false;
4771
+ }
4772
+ const value = scope.get(key);
4773
+ return value !== void 0 && value !== null;
4774
+ }
4775
+ hasElementValue(element) {
4776
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
4777
+ return element.value.length > 0;
4778
+ }
4779
+ return (element.textContent ?? "").trim().length > 0;
4780
+ }
4781
+ coerceInt(value) {
4782
+ if (value == null || value === "") {
4783
+ return value;
4784
+ }
4785
+ const num = typeof value === "number" ? value : Number.parseInt(String(value), 10);
4786
+ return Number.isNaN(num) ? value : num;
4787
+ }
4788
+ coerceFloat(value) {
4789
+ if (value == null || value === "") {
4790
+ return value;
4791
+ }
4792
+ const num = typeof value === "number" ? value : Number.parseFloat(String(value));
4793
+ return Number.isNaN(num) ? value : num;
4794
+ }
4795
+ isInEachScope(scope) {
4796
+ let cursor = scope;
4797
+ while (cursor) {
4798
+ if (cursor.isEachItem) {
4799
+ return true;
4800
+ }
4801
+ cursor = cursor.parent;
4802
+ }
4803
+ return false;
4804
+ }
4805
+ flushAutoBindQueue() {
4806
+ if (this.pendingAutoBindToScope.length === 0) {
4807
+ return;
4808
+ }
4809
+ const pending = this.pendingAutoBindToScope;
4810
+ this.pendingAutoBindToScope = [];
4811
+ for (const entry of pending) {
4812
+ if (!entry.element.isConnected) {
4813
+ continue;
4814
+ }
4815
+ if (this.hasScopeValue(entry.scope, entry.expr)) {
4816
+ continue;
4817
+ }
4818
+ if (!this.hasElementValue(entry.element)) {
4819
+ continue;
4820
+ }
4821
+ applyBindToScope(entry.element, entry.expr, entry.scope);
4822
+ }
4013
4823
  }
4014
4824
  hasVsnAttributes(element) {
4015
4825
  return element.getAttributeNames().some((name) => name.startsWith("vsn-"));
@@ -4034,7 +4844,7 @@ var Engine = class _Engine {
4034
4844
  }
4035
4845
  return void 0;
4036
4846
  }
4037
- watch(scope, expr, handler) {
4847
+ watch(scope, expr, handler, element) {
4038
4848
  const key = expr.trim();
4039
4849
  if (!key) {
4040
4850
  return;
@@ -4049,29 +4859,70 @@ var Engine = class _Engine {
4049
4859
  }
4050
4860
  if (target) {
4051
4861
  target.on(key, handler);
4862
+ if (element) {
4863
+ this.trackScopeWatcher(element, target, "path", handler, key);
4864
+ }
4052
4865
  return;
4053
4866
  }
4054
4867
  let cursor = scope;
4055
4868
  while (cursor) {
4056
4869
  cursor.on(key, handler);
4870
+ if (element) {
4871
+ this.trackScopeWatcher(element, cursor, "path", handler, key);
4872
+ }
4057
4873
  cursor = cursor.parent;
4058
4874
  }
4059
4875
  }
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
- }
4876
+ watchWithDebounce(scope, expr, handler, debounceMs, element) {
4877
+ const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4878
+ this.watch(scope, expr, effectiveHandler, element);
4066
4879
  }
4067
- watchAllScopes(scope, handler, debounceMs) {
4880
+ watchAllScopes(scope, handler, debounceMs, element) {
4068
4881
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4069
4882
  let cursor = scope;
4070
4883
  while (cursor) {
4071
4884
  cursor.onAny(effectiveHandler);
4885
+ if (element) {
4886
+ this.trackScopeWatcher(element, cursor, "any", effectiveHandler);
4887
+ }
4072
4888
  cursor = cursor.parent;
4073
4889
  }
4074
4890
  }
4891
+ trackScopeWatcher(element, scope, kind, handler, key) {
4892
+ const watchers = this.scopeWatchers.get(element) ?? [];
4893
+ watchers.push({ scope, kind, handler, ...key ? { key } : {} });
4894
+ this.scopeWatchers.set(element, watchers);
4895
+ }
4896
+ cleanupScopeWatchers(element) {
4897
+ const watchers = this.scopeWatchers.get(element);
4898
+ if (!watchers) {
4899
+ return;
4900
+ }
4901
+ for (const watcher of watchers) {
4902
+ if (watcher.kind === "any") {
4903
+ watcher.scope.offAny(watcher.handler);
4904
+ continue;
4905
+ }
4906
+ if (watcher.key) {
4907
+ watcher.scope.off(watcher.key, watcher.handler);
4908
+ }
4909
+ }
4910
+ this.scopeWatchers.delete(element);
4911
+ }
4912
+ cleanupBehaviorListeners(element) {
4913
+ const listenerMap = this.behaviorListeners.get(element);
4914
+ if (!listenerMap) {
4915
+ return;
4916
+ }
4917
+ for (const listeners of listenerMap.values()) {
4918
+ for (const listener of listeners) {
4919
+ listener.target.removeEventListener(listener.event, listener.handler, listener.options);
4920
+ }
4921
+ }
4922
+ listenerMap.clear();
4923
+ this.behaviorListeners.delete(element);
4924
+ this.behaviorBindings.delete(element);
4925
+ }
4075
4926
  parseOnAttribute(name, value) {
4076
4927
  if (!name.startsWith("vsn-on:")) {
4077
4928
  return null;
@@ -4081,111 +4932,56 @@ var Engine = class _Engine {
4081
4932
  if (!event) {
4082
4933
  return null;
4083
4934
  }
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);
4935
+ if (event.includes(".")) {
4936
+ throw new Error("vsn:on does not support dot modifiers; use !flags instead");
4097
4937
  }
4098
- const combinedModifiers = [...modifiers, ...descriptor.modifiers];
4938
+ const { flagMap, flagArgs } = this.parseInlineFlags(flags);
4099
4939
  const config = {
4100
- event: descriptor.event,
4940
+ event,
4101
4941
  code: value,
4102
- ...debounceMs !== void 0 ? { debounceMs } : {},
4103
- ...combinedModifiers.length > 0 ? { modifiers: combinedModifiers } : {},
4104
- ...descriptor.keyModifiers.length > 0 ? { keyModifiers: descriptor.keyModifiers } : {}
4942
+ flags: flagMap,
4943
+ flagArgs
4105
4944
  };
4106
4945
  return config;
4107
4946
  }
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);
4947
+ parseInlineFlags(parts) {
4948
+ const flagMap = {};
4949
+ const flagArgs = {};
4950
+ for (const raw of parts) {
4951
+ const trimmed = raw.trim();
4952
+ if (!trimmed) {
4953
+ continue;
4119
4954
  }
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
- }
4955
+ const match = trimmed.match(/^([a-zA-Z][\w-]*)(?:\((.+)\))?$/);
4956
+ if (!match) {
4165
4957
  continue;
4166
4958
  }
4167
- const expectedKey = keyAliases[modifier] ?? modifier;
4168
- if (key !== expectedKey) {
4169
- return false;
4959
+ const name = match[1] ?? "";
4960
+ if (!name) {
4961
+ continue;
4962
+ }
4963
+ if (!this.flagHandlers.has(name)) {
4964
+ throw new Error(`Unknown flag ${name}`);
4965
+ }
4966
+ flagMap[name] = true;
4967
+ if (match[2] !== void 0) {
4968
+ flagArgs[name] = this.parseInlineFlagArg(match[2]);
4170
4969
  }
4171
4970
  }
4172
- return true;
4971
+ return { flagMap, flagArgs };
4173
4972
  }
4174
- matchesTargetModifiers(element, event, modifiers) {
4175
- if (!modifiers || modifiers.length === 0) {
4973
+ parseInlineFlagArg(raw) {
4974
+ const trimmed = raw.trim();
4975
+ if (trimmed === "true") {
4176
4976
  return true;
4177
4977
  }
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) {
4978
+ if (trimmed === "false") {
4183
4979
  return false;
4184
4980
  }
4185
- if (modifiers.includes("outside") && element.contains(target)) {
4186
- return false;
4981
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
4982
+ return Number(trimmed);
4187
4983
  }
4188
- return true;
4984
+ return trimmed;
4189
4985
  }
4190
4986
  describeElement(element) {
4191
4987
  const tag = element.tagName.toLowerCase();
@@ -4227,51 +5023,50 @@ var Engine = class _Engine {
4227
5023
  }
4228
5024
  }
4229
5025
  attachOnHandler(element, config) {
4230
- const options = this.getListenerOptions(config.modifiers);
4231
- const listenerTarget = config.modifiers?.includes("outside") ? element.ownerDocument : element;
5026
+ const { listenerTarget, options, debounceMs } = this.getEventBindingConfig(
5027
+ element,
5028
+ config.flags,
5029
+ config.flagArgs
5030
+ );
4232
5031
  let effectiveHandler;
4233
5032
  const handler = async (event) => {
4234
5033
  if (!element.isConnected) {
4235
5034
  listenerTarget.removeEventListener(config.event, effectiveHandler, options);
4236
5035
  return;
4237
5036
  }
4238
- if (!this.matchesKeyModifiers(event, config.keyModifiers)) {
4239
- return;
4240
- }
4241
- if (!this.matchesTargetModifiers(element, event, config.modifiers)) {
5037
+ const scope = this.getScope(element);
5038
+ if (!this.applyEventFlagBefore(element, scope, config.flags, config.flagArgs, event)) {
4242
5039
  return;
4243
5040
  }
4244
- this.applyEventModifiers(event, config.modifiers);
4245
- const scope = this.getScope(element);
4246
5041
  try {
4247
5042
  await this.execute(config.code, scope, element);
4248
5043
  this.evaluate(element);
4249
5044
  } catch (error) {
4250
5045
  this.emitError(element, error);
5046
+ } finally {
5047
+ this.applyEventFlagAfter(element, scope, config.flags, config.flagArgs, event);
4251
5048
  }
4252
5049
  };
4253
- effectiveHandler = config.debounceMs ? debounce(handler, config.debounceMs) : handler;
5050
+ effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4254
5051
  listenerTarget.addEventListener(config.event, effectiveHandler, options);
4255
5052
  }
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;
5053
+ attachBehaviorOnHandler(element, event, body, flags, flagArgs, args, behaviorId, rootScope) {
5054
+ if (event.includes(".")) {
5055
+ throw new Error("vsn:on does not support dot modifiers; use !flags instead");
5056
+ }
5057
+ const { listenerTarget, options, debounceMs } = this.getEventBindingConfig(element, flags, flagArgs);
4260
5058
  const handler = async (evt) => {
4261
- if (!this.matchesKeyModifiers(evt, descriptor.keyModifiers)) {
4262
- return;
4263
- }
4264
- if (!this.matchesTargetModifiers(element, evt, combinedModifiers)) {
5059
+ const scope = this.getScope(element);
5060
+ if (!this.applyEventFlagBefore(element, scope, flags, flagArgs, evt)) {
4265
5061
  return;
4266
5062
  }
4267
- this.applyEventModifiers(evt, combinedModifiers);
4268
- const scope = this.getScope(element);
4269
5063
  const previousValues = /* @__PURE__ */ new Map();
4270
5064
  if (args && args.length > 0) {
4271
5065
  const argName = args[0];
4272
5066
  if (argName) {
4273
5067
  previousValues.set(argName, scope.getPath(argName));
4274
- scope.setPath(argName, evt);
5068
+ const [nextArg] = this.applyEventFlagArgTransforms(element, scope, flags, flagArgs, evt);
5069
+ scope.setPath(argName, nextArg);
4275
5070
  }
4276
5071
  }
4277
5072
  let failed = false;
@@ -4284,16 +5079,17 @@ var Engine = class _Engine {
4284
5079
  for (const [name, value] of previousValues.entries()) {
4285
5080
  scope.setPath(name, value);
4286
5081
  }
5082
+ this.applyEventFlagAfter(element, scope, flags, flagArgs, evt);
4287
5083
  }
4288
5084
  if (!failed) {
4289
5085
  this.evaluate(element);
4290
5086
  }
4291
5087
  };
4292
- const options = this.getListenerOptions(combinedModifiers);
4293
- listenerTarget.addEventListener(descriptor.event, handler, options);
5088
+ const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
5089
+ listenerTarget.addEventListener(event, effectiveHandler, options);
4294
5090
  const listenerMap = this.behaviorListeners.get(element) ?? /* @__PURE__ */ new Map();
4295
5091
  const listeners = listenerMap.get(behaviorId) ?? [];
4296
- listeners.push({ target: listenerTarget, event: descriptor.event, handler, options });
5092
+ listeners.push({ target: listenerTarget, event, handler: effectiveHandler, options });
4297
5093
  listenerMap.set(behaviorId, listeners);
4298
5094
  this.behaviorListeners.set(element, listenerMap);
4299
5095
  }
@@ -4305,9 +5101,7 @@ var Engine = class _Engine {
4305
5101
  }
4306
5102
  try {
4307
5103
  await applyGet(element, config, this.getScope(element), (target) => {
4308
- if (config.trusted) {
4309
- this.handleTrustedHtml(target);
4310
- }
5104
+ this.handleHtmlBehaviors(target);
4311
5105
  });
4312
5106
  } catch (error) {
4313
5107
  console.warn("vsn:getError", error);
@@ -4324,33 +5118,158 @@ var Engine = class _Engine {
4324
5118
  Promise.resolve().then(handler);
4325
5119
  }
4326
5120
  }
4327
- applyEventModifiers(event, modifiers) {
4328
- if (!event || !modifiers || modifiers.length === 0) {
4329
- return;
5121
+ getEventBindingConfig(element, flags, flagArgs) {
5122
+ let listenerTarget = element;
5123
+ let options = {};
5124
+ let debounceMs;
5125
+ for (const name of Object.keys(flags)) {
5126
+ const handler = this.flagHandlers.get(name);
5127
+ if (!handler?.onEventBind) {
5128
+ continue;
5129
+ }
5130
+ const patch = handler.onEventBind({
5131
+ name,
5132
+ args: flagArgs[name],
5133
+ element,
5134
+ scope: this.getScope(element),
5135
+ rootScope: void 0,
5136
+ event: void 0,
5137
+ engine: this
5138
+ });
5139
+ if (!patch) {
5140
+ continue;
5141
+ }
5142
+ if (patch.listenerTarget) {
5143
+ listenerTarget = patch.listenerTarget;
5144
+ }
5145
+ if (patch.options) {
5146
+ options = { ...options, ...patch.options };
5147
+ }
5148
+ if (patch.debounceMs !== void 0) {
5149
+ debounceMs = patch.debounceMs;
5150
+ }
5151
+ }
5152
+ return {
5153
+ listenerTarget,
5154
+ ...Object.keys(options).length > 0 ? { options } : {},
5155
+ ...debounceMs !== void 0 ? { debounceMs } : {}
5156
+ };
5157
+ }
5158
+ applyEventFlagBefore(element, scope, flags, flagArgs, event) {
5159
+ for (const name of Object.keys(flags)) {
5160
+ const handler = this.flagHandlers.get(name);
5161
+ if (!handler?.onEventBefore) {
5162
+ continue;
5163
+ }
5164
+ const result = handler.onEventBefore({
5165
+ name,
5166
+ args: flagArgs[name],
5167
+ element,
5168
+ scope,
5169
+ rootScope: void 0,
5170
+ event,
5171
+ engine: this
5172
+ });
5173
+ if (result === false) {
5174
+ return false;
5175
+ }
4330
5176
  }
4331
- for (const modifier of modifiers) {
4332
- if (modifier === "prevent") {
4333
- event.preventDefault();
4334
- } else if (modifier === "stop") {
4335
- event.stopPropagation();
5177
+ return true;
5178
+ }
5179
+ applyEventFlagAfter(element, scope, flags, flagArgs, event) {
5180
+ for (const name of Object.keys(flags)) {
5181
+ const handler = this.flagHandlers.get(name);
5182
+ if (!handler?.onEventAfter) {
5183
+ continue;
4336
5184
  }
5185
+ handler.onEventAfter({
5186
+ name,
5187
+ args: flagArgs[name],
5188
+ element,
5189
+ scope,
5190
+ rootScope: void 0,
5191
+ event,
5192
+ engine: this
5193
+ });
4337
5194
  }
4338
5195
  }
4339
- getListenerOptions(modifiers) {
4340
- if (!modifiers || modifiers.length === 0) {
4341
- return void 0;
5196
+ applyEventFlagArgTransforms(element, scope, flags, flagArgs, event) {
5197
+ let args = [event];
5198
+ for (const name of Object.keys(flags)) {
5199
+ const handler = this.flagHandlers.get(name);
5200
+ if (!handler?.transformEventArgs) {
5201
+ continue;
5202
+ }
5203
+ const nextArgs = handler.transformEventArgs(
5204
+ {
5205
+ name,
5206
+ args: flagArgs[name],
5207
+ element,
5208
+ scope,
5209
+ rootScope: void 0,
5210
+ event,
5211
+ engine: this
5212
+ },
5213
+ args
5214
+ );
5215
+ if (Array.isArray(nextArgs)) {
5216
+ args = nextArgs;
5217
+ }
4342
5218
  }
4343
- const options = {};
4344
- if (modifiers.includes("once")) {
4345
- options.once = true;
5219
+ return args;
5220
+ }
5221
+ matchesKeyFlag(event, flag) {
5222
+ if (!(event instanceof KeyboardEvent)) {
5223
+ return false;
5224
+ }
5225
+ const modifierChecks = {
5226
+ shift: event.shiftKey,
5227
+ ctrl: event.ctrlKey,
5228
+ alt: event.altKey,
5229
+ meta: event.metaKey
5230
+ };
5231
+ if (flag in modifierChecks) {
5232
+ return modifierChecks[flag] ?? false;
5233
+ }
5234
+ const keyAliases = {
5235
+ escape: "escape",
5236
+ esc: "escape",
5237
+ enter: "enter",
5238
+ tab: "tab",
5239
+ space: "space",
5240
+ spacebar: "space",
5241
+ up: "arrowup",
5242
+ down: "arrowdown",
5243
+ left: "arrowleft",
5244
+ right: "arrowright",
5245
+ arrowup: "arrowup",
5246
+ arrowdown: "arrowdown",
5247
+ arrowleft: "arrowleft",
5248
+ arrowright: "arrowright",
5249
+ delete: "delete",
5250
+ backspace: "backspace"
5251
+ };
5252
+ let key = event.key?.toLowerCase() ?? "";
5253
+ if (key === " ") {
5254
+ key = "space";
4346
5255
  }
4347
- if (modifiers.includes("passive")) {
4348
- options.passive = true;
5256
+ const expectedKey = keyAliases[flag] ?? flag;
5257
+ return key === expectedKey;
5258
+ }
5259
+ async withExecutionElement(element, fn) {
5260
+ if (!element) {
5261
+ await fn();
5262
+ return;
4349
5263
  }
4350
- if (modifiers.includes("capture")) {
4351
- options.capture = true;
5264
+ this.executionStack.push(element);
5265
+ try {
5266
+ await fn();
5267
+ } finally {
5268
+ this.executionStack.pop();
4352
5269
  }
4353
- return Object.keys(options).length > 0 ? options : void 0;
5270
+ }
5271
+ getCurrentElement() {
5272
+ return this.executionStack[this.executionStack.length - 1];
4354
5273
  }
4355
5274
  async execute(code, scope, element, rootScope) {
4356
5275
  let block = this.codeCache.get(code);
@@ -4358,22 +5277,26 @@ var Engine = class _Engine {
4358
5277
  block = Parser.parseInline(code);
4359
5278
  this.codeCache.set(code, block);
4360
5279
  }
4361
- const context = {
4362
- scope,
4363
- rootScope,
4364
- globals: this.globals,
4365
- ...element ? { element } : {}
4366
- };
4367
- await block.evaluate(context);
5280
+ await this.withExecutionElement(element, async () => {
5281
+ const context = {
5282
+ scope,
5283
+ rootScope,
5284
+ globals: this.globals,
5285
+ ...element ? { element } : {}
5286
+ };
5287
+ await block.evaluate(context);
5288
+ });
4368
5289
  }
4369
5290
  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);
5291
+ await this.withExecutionElement(element, async () => {
5292
+ const context = {
5293
+ scope,
5294
+ rootScope,
5295
+ globals: this.globals,
5296
+ ...element ? { element } : {}
5297
+ };
5298
+ await block.evaluate(context);
5299
+ });
4377
5300
  }
4378
5301
  async safeExecute(code, scope, element, rootScope) {
4379
5302
  try {
@@ -4396,15 +5319,26 @@ var Engine = class _Engine {
4396
5319
  collectBehavior(behavior, parentSelector, rootSelectorOverride) {
4397
5320
  const selector = parentSelector ? `${parentSelector} ${behavior.selector.selectorText}` : behavior.selector.selectorText;
4398
5321
  const rootSelector = rootSelectorOverride ?? (parentSelector ?? behavior.selector.selectorText);
5322
+ const behaviorHash = this.hashBehavior(behavior);
5323
+ const hash = `${selector}::${rootSelector}::${behaviorHash}`;
5324
+ if (this.behaviorRegistryHashes.has(hash)) {
5325
+ return;
5326
+ }
4399
5327
  const cached = this.getCachedBehavior(behavior);
4400
- this.behaviorRegistry.push({
5328
+ const entry = {
4401
5329
  id: this.behaviorId += 1,
5330
+ hash,
4402
5331
  selector,
4403
5332
  rootSelector,
4404
5333
  specificity: this.computeSpecificity(selector),
4405
5334
  order: this.behaviorRegistry.length,
4406
- ...cached
4407
- });
5335
+ flags: behavior.flags ?? {},
5336
+ flagArgs: behavior.flagArgs ?? {},
5337
+ ...cached,
5338
+ ...parentSelector ? { parentSelector } : {}
5339
+ };
5340
+ this.behaviorRegistry.push(entry);
5341
+ this.behaviorRegistryHashes.add(hash);
4408
5342
  this.collectNestedBehaviors(behavior.body, selector, rootSelector);
4409
5343
  }
4410
5344
  collectNestedBehaviors(block, parentSelector, rootSelector) {
@@ -4477,7 +5411,8 @@ var Engine = class _Engine {
4477
5411
  blocks.push({
4478
5412
  event: statement.eventName,
4479
5413
  body: statement.body,
4480
- modifiers: statement.modifiers,
5414
+ flags: statement.flags,
5415
+ flagArgs: statement.flagArgs,
4481
5416
  args: statement.args
4482
5417
  });
4483
5418
  }
@@ -4542,6 +5477,8 @@ var Engine = class _Engine {
4542
5477
  return {
4543
5478
  type,
4544
5479
  selector: node.selector?.selectorText ?? "",
5480
+ flags: node.flags ?? {},
5481
+ flagArgs: node.flagArgs ?? {},
4545
5482
  body: this.normalizeNode(node.body)
4546
5483
  };
4547
5484
  }
@@ -4559,6 +5496,8 @@ var Engine = class _Engine {
4559
5496
  type,
4560
5497
  eventName: node.eventName ?? "",
4561
5498
  args: Array.isArray(node.args) ? node.args : [],
5499
+ flags: node.flags ?? {},
5500
+ flagArgs: node.flagArgs ?? {},
4562
5501
  body: this.normalizeNode(node.body)
4563
5502
  };
4564
5503
  }
@@ -4576,21 +5515,9 @@ var Engine = class _Engine {
4576
5515
  return {
4577
5516
  type,
4578
5517
  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
5518
  value: this.normalizeNode(node.value),
4593
- important: Boolean(node.important)
5519
+ operator: node.operator ?? "",
5520
+ prefix: Boolean(node.prefix)
4594
5521
  };
4595
5522
  }
4596
5523
  if (type === "FunctionDeclaration") {
@@ -4624,6 +5551,15 @@ var Engine = class _Engine {
4624
5551
  value: this.normalizeNode(node.value ?? null)
4625
5552
  };
4626
5553
  }
5554
+ if (type === "Assert") {
5555
+ return {
5556
+ type,
5557
+ test: this.normalizeNode(node.test)
5558
+ };
5559
+ }
5560
+ if (type === "Break" || type === "Continue") {
5561
+ return { type };
5562
+ }
4627
5563
  if (type === "If") {
4628
5564
  return {
4629
5565
  type,
@@ -4648,6 +5584,15 @@ var Engine = class _Engine {
4648
5584
  body: this.normalizeNode(node.body)
4649
5585
  };
4650
5586
  }
5587
+ if (type === "ForEach") {
5588
+ return {
5589
+ type,
5590
+ kind: node.kind ?? "of",
5591
+ target: this.normalizeNode(node.target),
5592
+ iterable: this.normalizeNode(node.iterable),
5593
+ body: this.normalizeNode(node.body)
5594
+ };
5595
+ }
4651
5596
  if (type === "Try") {
4652
5597
  return {
4653
5598
  type,
@@ -4770,7 +5715,9 @@ var Engine = class _Engine {
4770
5715
  globals: this.globals,
4771
5716
  element,
4772
5717
  returnValue: void 0,
4773
- returning: false
5718
+ returning: false,
5719
+ breaking: false,
5720
+ continuing: false
4774
5721
  };
4775
5722
  const previousValues = /* @__PURE__ */ new Map();
4776
5723
  await this.applyFunctionParams(callScope, declaration.params, previousValues, context, args);
@@ -4821,6 +5768,7 @@ var Engine = class _Engine {
4821
5768
  const context = { scope, rootScope, element };
4822
5769
  const operator = declaration.operator;
4823
5770
  const debounceMs = declaration.flags.debounce ? declaration.flagArgs.debounce ?? 200 : void 0;
5771
+ const transform = (value) => this.applyCustomFlagTransforms(value, element, scope, declaration);
4824
5772
  const importantKey = this.getImportantKey(declaration);
4825
5773
  if (!declaration.flags.important && importantKey && this.isImportant(element, importantKey)) {
4826
5774
  return;
@@ -4831,7 +5779,8 @@ var Engine = class _Engine {
4831
5779
  this.applyCustomFlags(element, scope, declaration);
4832
5780
  if (declaration.target instanceof IdentifierExpression) {
4833
5781
  const value = await declaration.value.evaluate(context);
4834
- scope.setPath(declaration.target.name, value);
5782
+ const transformed = this.applyCustomFlagTransforms(value, element, scope, declaration);
5783
+ scope.setPath(declaration.target.name, transformed);
4835
5784
  if (declaration.flags.important && importantKey) {
4836
5785
  this.markImportant(element, importantKey);
4837
5786
  }
@@ -4844,7 +5793,7 @@ var Engine = class _Engine {
4844
5793
  const exprIdentifier = declaration.value instanceof IdentifierExpression ? declaration.value.name : void 0;
4845
5794
  if (operator === ":>") {
4846
5795
  if (exprIdentifier) {
4847
- this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope);
5796
+ this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope, transform);
4848
5797
  }
4849
5798
  if (declaration.flags.important && importantKey) {
4850
5799
  this.markImportant(element, importantKey);
@@ -4852,11 +5801,12 @@ var Engine = class _Engine {
4852
5801
  return;
4853
5802
  }
4854
5803
  if (operator === ":=" && exprIdentifier) {
4855
- this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope);
5804
+ this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope, transform);
4856
5805
  }
4857
5806
  if (!exprIdentifier) {
4858
5807
  const value = await declaration.value.evaluate(context);
4859
- this.setDirectiveValue(element, target, value, declaration.flags.trusted);
5808
+ const transformed = this.applyCustomFlagTransforms(value, element, scope, declaration);
5809
+ this.setDirectiveValue(element, target, transformed);
4860
5810
  const shouldWatch2 = operator === ":<" || operator === ":=";
4861
5811
  if (shouldWatch2) {
4862
5812
  this.applyDirectiveFromExpression(
@@ -4864,7 +5814,6 @@ var Engine = class _Engine {
4864
5814
  target,
4865
5815
  declaration.value,
4866
5816
  scope,
4867
- declaration.flags.trusted,
4868
5817
  debounceMs,
4869
5818
  rootScope
4870
5819
  );
@@ -4880,7 +5829,6 @@ var Engine = class _Engine {
4880
5829
  target,
4881
5830
  exprIdentifier,
4882
5831
  scope,
4883
- declaration.flags.trusted,
4884
5832
  debounceMs,
4885
5833
  shouldWatch,
4886
5834
  rootScope
@@ -4906,23 +5854,78 @@ var Engine = class _Engine {
4906
5854
  });
4907
5855
  }
4908
5856
  }
4909
- applyDirectiveFromScope(element, target, expr, scope, trusted, debounceMs, watch = true, rootScope) {
5857
+ applyCustomFlagTransforms(value, element, scope, declaration) {
5858
+ if (this.flagHandlers.size === 0) {
5859
+ return value;
5860
+ }
5861
+ let nextValue = value;
5862
+ for (const [name, handler] of this.flagHandlers) {
5863
+ if (!declaration.flags[name] || !handler.transformValue) {
5864
+ continue;
5865
+ }
5866
+ nextValue = handler.transformValue(
5867
+ {
5868
+ name,
5869
+ args: declaration.flagArgs[name],
5870
+ element,
5871
+ scope,
5872
+ declaration
5873
+ },
5874
+ nextValue
5875
+ );
5876
+ }
5877
+ return nextValue;
5878
+ }
5879
+ async applyBehaviorModifierHook(hook, behavior, element, scope, rootScope) {
5880
+ if (this.behaviorModifiers.size === 0) {
5881
+ return;
5882
+ }
5883
+ for (const [name, handler] of this.behaviorModifiers) {
5884
+ if (!behavior.flags?.[name]) {
5885
+ continue;
5886
+ }
5887
+ const callback = handler[hook];
5888
+ if (!callback) {
5889
+ continue;
5890
+ }
5891
+ await callback({
5892
+ name,
5893
+ args: behavior.flagArgs?.[name],
5894
+ element,
5895
+ scope,
5896
+ rootScope,
5897
+ behavior,
5898
+ engine: this
5899
+ });
5900
+ }
5901
+ }
5902
+ behaviorHasModifierHooks(behavior) {
5903
+ if (this.behaviorModifiers.size === 0) {
5904
+ return false;
5905
+ }
5906
+ const flags = behavior.flags ?? {};
5907
+ for (const name of Object.keys(flags)) {
5908
+ if (flags[name] && this.behaviorModifiers.has(name)) {
5909
+ return true;
5910
+ }
5911
+ }
5912
+ return false;
5913
+ }
5914
+ applyDirectiveFromScope(element, target, expr, scope, debounceMs, watch = true, rootScope) {
4910
5915
  if (target.kind === "attr" && target.name === "html" && element instanceof HTMLElement) {
4911
5916
  const handler2 = () => {
4912
5917
  const useRoot = expr.startsWith("root.") && rootScope;
4913
5918
  const sourceScope = useRoot ? rootScope : scope;
4914
5919
  const localExpr = useRoot ? `self.${expr.slice("root.".length)}` : expr;
4915
- applyHtml(element, localExpr, sourceScope, Boolean(trusted));
5920
+ applyHtml(element, localExpr, sourceScope);
4916
5921
  };
4917
5922
  handler2();
4918
- if (trusted) {
4919
- this.handleTrustedHtml(element);
4920
- }
5923
+ this.handleHtmlBehaviors(element);
4921
5924
  if (watch) {
4922
5925
  const useRoot = expr.startsWith("root.") && rootScope;
4923
5926
  const sourceScope = useRoot ? rootScope : scope;
4924
5927
  const watchExpr = useRoot ? expr.slice("root.".length) : expr;
4925
- this.watchWithDebounce(sourceScope, watchExpr, handler2, debounceMs);
5928
+ this.watchWithDebounce(sourceScope, watchExpr, handler2, debounceMs, element);
4926
5929
  }
4927
5930
  return;
4928
5931
  }
@@ -4934,80 +5937,90 @@ var Engine = class _Engine {
4934
5937
  if (value == null) {
4935
5938
  return;
4936
5939
  }
4937
- this.setDirectiveValue(element, target, value, trusted);
5940
+ this.setDirectiveValue(element, target, value);
4938
5941
  };
4939
5942
  handler();
4940
5943
  if (watch) {
4941
5944
  const useRoot = expr.startsWith("root.") && rootScope;
4942
5945
  const sourceScope = useRoot ? rootScope : scope;
4943
5946
  const watchExpr = useRoot ? expr.slice("root.".length) : expr;
4944
- this.watchWithDebounce(sourceScope, watchExpr, handler, debounceMs);
5947
+ this.watchWithDebounce(sourceScope, watchExpr, handler, debounceMs, element);
4945
5948
  }
4946
5949
  }
4947
- applyDirectiveFromExpression(element, target, expr, scope, trusted, debounceMs, rootScope) {
5950
+ applyDirectiveFromExpression(element, target, expr, scope, debounceMs, rootScope) {
4948
5951
  const handler = async () => {
4949
5952
  const context = { scope, rootScope, element };
4950
5953
  const value = await expr.evaluate(context);
4951
- this.setDirectiveValue(element, target, value, trusted);
5954
+ this.setDirectiveValue(element, target, value);
4952
5955
  };
4953
5956
  void handler();
4954
5957
  this.watchAllScopes(scope, () => {
4955
5958
  void handler();
4956
- }, debounceMs);
5959
+ }, debounceMs, element);
4957
5960
  }
4958
- applyDirectiveToScope(element, target, expr, scope, debounceMs, rootScope) {
5961
+ applyDirectiveToScope(element, target, expr, scope, debounceMs, rootScope, transform) {
4959
5962
  const useRoot = expr.startsWith("root.") && rootScope;
4960
5963
  const targetScope = useRoot ? rootScope : scope;
4961
5964
  const targetExpr = useRoot ? `self.${expr.slice("root.".length)}` : expr;
4962
5965
  if (target.kind === "attr" && target.name === "value") {
4963
- this.applyValueBindingToScope(element, targetExpr, debounceMs, targetScope);
5966
+ this.applyValueBindingToScope(element, targetExpr, debounceMs, targetScope, transform);
4964
5967
  return;
4965
5968
  }
4966
5969
  if (target.kind === "attr" && target.name === "checked") {
4967
- this.applyCheckedBindingToScope(element, targetExpr, debounceMs, targetScope);
5970
+ this.applyCheckedBindingToScope(element, targetExpr, debounceMs, targetScope, transform);
4968
5971
  return;
4969
5972
  }
4970
5973
  const value = this.getDirectiveValue(element, target);
4971
5974
  if (value != null) {
4972
- targetScope.set(targetExpr, value);
5975
+ const nextValue = transform ? transform(value) : value;
5976
+ targetScope.set(targetExpr, nextValue);
4973
5977
  }
4974
5978
  }
4975
- applyCheckedBindingToScope(element, expr, debounceMs, scope) {
5979
+ applyCheckedBindingToScope(element, expr, debounceMs, scope, transform) {
4976
5980
  if (!(element instanceof HTMLInputElement)) {
4977
5981
  return;
4978
5982
  }
4979
5983
  const handler = () => {
4980
5984
  const targetScope = scope ?? this.getScope(element);
4981
- targetScope.set(expr, element.checked);
5985
+ const value = transform ? transform(element.checked) : element.checked;
5986
+ targetScope.set(expr, value);
4982
5987
  };
4983
5988
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4984
5989
  effectiveHandler();
4985
5990
  element.addEventListener("change", effectiveHandler);
4986
5991
  element.addEventListener("input", effectiveHandler);
4987
5992
  }
4988
- applyValueBindingToScope(element, expr, debounceMs, scope) {
5993
+ applyValueBindingToScope(element, expr, debounceMs, scope, transform) {
4989
5994
  if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)) {
4990
5995
  return;
4991
5996
  }
4992
5997
  const handler = () => {
4993
5998
  const targetScope = scope ?? this.getScope(element);
4994
- applyBindToScope(element, expr, targetScope);
5999
+ const value = element.value;
6000
+ const nextValue = transform ? transform(value) : value;
6001
+ targetScope.set(expr, nextValue);
4995
6002
  };
4996
6003
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4997
6004
  effectiveHandler();
4998
6005
  element.addEventListener("input", effectiveHandler);
4999
6006
  element.addEventListener("change", effectiveHandler);
5000
6007
  }
5001
- setDirectiveValue(element, target, value, trusted) {
6008
+ setDirectiveValue(element, target, value) {
5002
6009
  if (target.kind === "attr" && target.name === "html" && element instanceof HTMLElement) {
5003
6010
  const html = value == null ? "" : String(value);
5004
- element.innerHTML = trusted ? html : html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
5005
- if (trusted) {
5006
- this.handleTrustedHtml(element);
5007
- }
6011
+ element.innerHTML = html;
6012
+ this.handleHtmlBehaviors(element);
5008
6013
  return;
5009
6014
  }
5010
6015
  if (target.kind === "attr") {
6016
+ if (target.name === "text" && element instanceof HTMLElement) {
6017
+ element.innerText = value == null ? "" : String(value);
6018
+ return;
6019
+ }
6020
+ if (target.name === "content" && element instanceof HTMLElement) {
6021
+ element.textContent = value == null ? "" : String(value);
6022
+ return;
6023
+ }
5011
6024
  if (target.name === "value") {
5012
6025
  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
5013
6026
  element.value = value == null ? "" : String(value);
@@ -5038,6 +6051,12 @@ var Engine = class _Engine {
5038
6051
  }
5039
6052
  getDirectiveValue(element, target) {
5040
6053
  if (target.kind === "attr") {
6054
+ if (target.name === "text" && element instanceof HTMLElement) {
6055
+ return element.innerText;
6056
+ }
6057
+ if (target.name === "content" && element instanceof HTMLElement) {
6058
+ return element.textContent ?? "";
6059
+ }
5041
6060
  if (target.name === "value") {
5042
6061
  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
5043
6062
  return element.value;
@@ -5056,7 +6075,7 @@ var Engine = class _Engine {
5056
6075
  }
5057
6076
  return void 0;
5058
6077
  }
5059
- handleTrustedHtml(root) {
6078
+ handleHtmlBehaviors(root) {
5060
6079
  const scripts = Array.from(root.querySelectorAll('script[type="text/vsn"]'));
5061
6080
  if (scripts.length === 0) {
5062
6081
  return;
@@ -5073,17 +6092,27 @@ var Engine = class _Engine {
5073
6092
  id: "vsn-bind",
5074
6093
  match: (name) => name.startsWith("vsn-bind"),
5075
6094
  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") {
6095
+ const parsedDirection = this.parseBindDirection(name);
6096
+ const config = this.resolveBindConfig(element, value, scope, parsedDirection);
6097
+ const direction = config.direction;
6098
+ const auto = parsedDirection === "auto";
6099
+ this.bindBindings.set(element, { expr: value, direction, auto });
6100
+ if (!auto && (direction === "to" || direction === "both")) {
5079
6101
  this.markInlineDeclaration(element, `state:${value}`);
5080
6102
  }
5081
- if (direction === "to" || direction === "both") {
6103
+ if (config.seedFromScope) {
6104
+ applyBindToElement(element, value, scope);
6105
+ }
6106
+ if (config.deferToScope) {
6107
+ this.pendingAutoBindToScope.push({ element, expr: value, scope });
6108
+ } else if (config.syncToScope) {
5082
6109
  applyBindToScope(element, value, scope);
6110
+ }
6111
+ if (direction === "to" || direction === "both") {
5083
6112
  this.attachBindInputHandler(element, value);
5084
6113
  }
5085
6114
  if (direction === "from" || direction === "both") {
5086
- this.watch(scope, value, () => applyBindToElement(element, value, scope));
6115
+ this.watch(scope, value, () => applyBindToElement(element, value, scope), element);
5087
6116
  }
5088
6117
  }
5089
6118
  });
@@ -5095,7 +6124,7 @@ var Engine = class _Engine {
5095
6124
  if (element instanceof HTMLElement) {
5096
6125
  applyIf(element, value, scope);
5097
6126
  }
5098
- this.watch(scope, value, () => this.evaluate(element));
6127
+ this.watch(scope, value, () => this.evaluate(element), element);
5099
6128
  }
5100
6129
  });
5101
6130
  this.registerAttributeHandler({
@@ -5106,23 +6135,20 @@ var Engine = class _Engine {
5106
6135
  if (element instanceof HTMLElement) {
5107
6136
  applyShow(element, value, scope);
5108
6137
  }
5109
- this.watch(scope, value, () => this.evaluate(element));
6138
+ this.watch(scope, value, () => this.evaluate(element), element);
5110
6139
  }
5111
6140
  });
5112
6141
  this.registerAttributeHandler({
5113
6142
  id: "vsn-html",
5114
6143
  match: (name) => name.startsWith("vsn-html"),
5115
- handle: (element, name, value, scope) => {
5116
- const trusted = name.includes("!trusted");
5117
- this.htmlBindings.set(element, { expr: value, trusted });
6144
+ handle: (element, _name, value, scope) => {
6145
+ this.htmlBindings.set(element, { expr: value });
5118
6146
  this.markInlineDeclaration(element, "attr:html");
5119
6147
  if (element instanceof HTMLElement) {
5120
- applyHtml(element, value, scope, trusted);
5121
- if (trusted) {
5122
- this.handleTrustedHtml(element);
5123
- }
6148
+ applyHtml(element, value, scope);
6149
+ this.handleHtmlBehaviors(element);
5124
6150
  }
5125
- this.watch(scope, value, () => this.evaluate(element));
6151
+ this.watch(scope, value, () => this.evaluate(element), element);
5126
6152
  }
5127
6153
  });
5128
6154
  this.registerAttributeHandler({
@@ -5135,14 +6161,13 @@ var Engine = class _Engine {
5135
6161
  }
5136
6162
  this.eachBindings.set(element, { ...config, rendered: [] });
5137
6163
  this.renderEach(element);
5138
- this.watch(scope, config.listExpr, () => this.renderEach(element));
6164
+ this.watch(scope, config.listExpr, () => this.renderEach(element), element);
5139
6165
  }
5140
6166
  });
5141
6167
  this.registerAttributeHandler({
5142
6168
  id: "vsn-get",
5143
6169
  match: (name) => name.startsWith("vsn-get"),
5144
6170
  handle: (element, name) => {
5145
- const trusted = name.includes("!trusted");
5146
6171
  const autoLoad = name.includes("!load");
5147
6172
  const url = element.getAttribute(name) ?? "";
5148
6173
  const target = element.getAttribute("vsn-target") ?? void 0;
@@ -5150,7 +6175,6 @@ var Engine = class _Engine {
5150
6175
  const config = {
5151
6176
  url,
5152
6177
  swap,
5153
- trusted,
5154
6178
  ...target ? { targetSelector: target } : {}
5155
6179
  };
5156
6180
  this.getBindings.set(element, config);
@@ -5190,15 +6214,27 @@ function parseCFS(source) {
5190
6214
  const parser = new Parser(source);
5191
6215
  return parser.parseProgram();
5192
6216
  }
6217
+ if (typeof window !== "undefined") {
6218
+ window["parseCFS"] = parseCFS;
6219
+ }
5193
6220
  function autoMount(root = document) {
5194
6221
  if (typeof document === "undefined") {
5195
6222
  return null;
5196
6223
  }
5197
6224
  const engine = new Engine();
6225
+ globalThis.VSNEngine = engine;
5198
6226
  const startTime = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
5199
6227
  const mount = () => {
5200
6228
  const target = root instanceof Document ? root.body : root;
5201
6229
  if (target) {
6230
+ const plugins = globalThis.VSNPlugins;
6231
+ if (plugins && typeof plugins === "object") {
6232
+ for (const plugin of Object.values(plugins)) {
6233
+ if (typeof plugin === "function") {
6234
+ plugin(engine);
6235
+ }
6236
+ }
6237
+ }
5202
6238
  const sources = Array.from(document.querySelectorAll('script[type="text/vsn"]')).map((script) => script.textContent ?? "").join("\n");
5203
6239
  if (sources.trim()) {
5204
6240
  engine.registerBehaviors(sources);
@@ -5210,9 +6246,9 @@ function autoMount(root = document) {
5210
6246
  }
5211
6247
  };
5212
6248
  if (document.readyState === "loading") {
5213
- document.addEventListener("DOMContentLoaded", mount, { once: true });
6249
+ document.addEventListener("DOMContentLoaded", () => setTimeout(mount, 0), { once: true });
5214
6250
  } else {
5215
- mount();
6251
+ setTimeout(mount, 0);
5216
6252
  }
5217
6253
  return engine;
5218
6254
  }
@@ -5226,16 +6262,21 @@ if (typeof document !== "undefined") {
5226
6262
  0 && (module.exports = {
5227
6263
  ArrayExpression,
5228
6264
  ArrayPattern,
6265
+ AssertError,
6266
+ AssertNode,
5229
6267
  AssignmentNode,
5230
6268
  AwaitExpression,
5231
6269
  BaseNode,
5232
6270
  BehaviorNode,
5233
6271
  BinaryExpression,
5234
6272
  BlockNode,
6273
+ BreakNode,
5235
6274
  CallExpression,
6275
+ ContinueNode,
5236
6276
  DeclarationNode,
5237
6277
  DirectiveExpression,
5238
6278
  Engine,
6279
+ ForEachNode,
5239
6280
  ForNode,
5240
6281
  FunctionDeclarationNode,
5241
6282
  FunctionExpression,
@@ -5255,8 +6296,6 @@ if (typeof document !== "undefined") {
5255
6296
  ReturnNode,
5256
6297
  SelectorNode,
5257
6298
  SpreadElement,
5258
- StateBlockNode,
5259
- StateEntryNode,
5260
6299
  TemplateExpression,
5261
6300
  TernaryExpression,
5262
6301
  TokenType,