vsn 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,6 +20,9 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
20
20
  TokenType2["While"] = "While";
21
21
  TokenType2["Try"] = "Try";
22
22
  TokenType2["Catch"] = "Catch";
23
+ TokenType2["Assert"] = "Assert";
24
+ TokenType2["Break"] = "Break";
25
+ TokenType2["Continue"] = "Continue";
23
26
  TokenType2["LBrace"] = "LBrace";
24
27
  TokenType2["RBrace"] = "RBrace";
25
28
  TokenType2["LParen"] = "LParen";
@@ -35,7 +38,9 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
35
38
  TokenType2["Greater"] = "Greater";
36
39
  TokenType2["Less"] = "Less";
37
40
  TokenType2["Plus"] = "Plus";
41
+ TokenType2["PlusPlus"] = "PlusPlus";
38
42
  TokenType2["Minus"] = "Minus";
43
+ TokenType2["MinusMinus"] = "MinusMinus";
39
44
  TokenType2["Tilde"] = "Tilde";
40
45
  TokenType2["Star"] = "Star";
41
46
  TokenType2["Slash"] = "Slash";
@@ -75,6 +80,9 @@ var KEYWORDS = {
75
80
  while: "While" /* While */,
76
81
  try: "Try" /* Try */,
77
82
  catch: "Catch" /* Catch */,
83
+ assert: "Assert" /* Assert */,
84
+ break: "Break" /* Break */,
85
+ continue: "Continue" /* Continue */,
78
86
  true: "Boolean" /* Boolean */,
79
87
  false: "Boolean" /* Boolean */,
80
88
  null: "Null" /* Null */
@@ -176,8 +184,20 @@ var Lexer = class {
176
184
  readIdentifier() {
177
185
  const start = this.position();
178
186
  let value = "";
179
- while (!this.eof() && (this.isAlphaNumeric(this.peek()) || this.peek() === "_" || this.peek() === "-")) {
180
- value += this.next();
187
+ while (!this.eof()) {
188
+ const ch = this.peek();
189
+ if (this.isAlphaNumeric(ch) || ch === "_") {
190
+ value += this.next();
191
+ continue;
192
+ }
193
+ if (ch === "-") {
194
+ if (this.peek(1) === "-") {
195
+ break;
196
+ }
197
+ value += this.next();
198
+ continue;
199
+ }
200
+ break;
181
201
  }
182
202
  const keywordType = KEYWORDS[value];
183
203
  if (keywordType) {
@@ -318,6 +338,16 @@ var Lexer = class {
318
338
  this.next();
319
339
  return this.token("Pipe" /* Pipe */, "|>", start);
320
340
  }
341
+ if (ch === "+" && next === "+") {
342
+ this.next();
343
+ this.next();
344
+ return this.token("PlusPlus" /* PlusPlus */, "++", start);
345
+ }
346
+ if (ch === "-" && next === "-") {
347
+ this.next();
348
+ this.next();
349
+ return this.token("MinusMinus" /* MinusMinus */, "--", start);
350
+ }
321
351
  if (ch === "." && next === "." && this.peek(2) === ".") {
322
352
  this.next();
323
353
  this.next();
@@ -420,11 +450,20 @@ var BaseNode = class {
420
450
  async prepare(_context) {
421
451
  return;
422
452
  }
423
- async evaluate(_context) {
453
+ evaluate(_context) {
424
454
  return void 0;
425
455
  }
426
456
  };
427
- async function evaluateWithChildScope(context, block) {
457
+ function isPromiseLike(value) {
458
+ return Boolean(value) && typeof value.then === "function";
459
+ }
460
+ function resolveMaybe(value, next) {
461
+ if (isPromiseLike(value)) {
462
+ return value.then(next);
463
+ }
464
+ return next(value);
465
+ }
466
+ function evaluateWithChildScope(context, block) {
428
467
  const scope = context.scope;
429
468
  if (!scope || !scope.createChild) {
430
469
  return block.evaluate(context);
@@ -432,7 +471,7 @@ async function evaluateWithChildScope(context, block) {
432
471
  const previousScope = context.scope;
433
472
  context.scope = scope.createChild();
434
473
  try {
435
- return await block.evaluate(context);
474
+ return block.evaluate(context);
436
475
  } finally {
437
476
  context.scope = previousScope;
438
477
  }
@@ -458,15 +497,25 @@ var BlockNode = class extends BaseNode {
458
497
  super("Block");
459
498
  this.statements = statements;
460
499
  }
461
- async evaluate(context) {
462
- for (const statement of this.statements) {
463
- if (context.returning) {
464
- break;
465
- }
466
- if (statement && typeof statement.evaluate === "function") {
467
- await statement.evaluate(context);
500
+ evaluate(context) {
501
+ let index = 0;
502
+ const run = () => {
503
+ while (index < this.statements.length) {
504
+ if (context.returning || context.breaking || context.continuing) {
505
+ break;
506
+ }
507
+ const statement = this.statements[index];
508
+ index += 1;
509
+ if (statement && typeof statement.evaluate === "function") {
510
+ const result = statement.evaluate(context);
511
+ if (isPromiseLike(result)) {
512
+ return result.then(() => run());
513
+ }
514
+ }
468
515
  }
469
- }
516
+ return void 0;
517
+ };
518
+ return run();
470
519
  }
471
520
  };
472
521
  var SelectorNode = class extends BaseNode {
@@ -476,136 +525,186 @@ var SelectorNode = class extends BaseNode {
476
525
  }
477
526
  };
478
527
  var BehaviorNode = class extends BaseNode {
479
- constructor(selector, body) {
528
+ constructor(selector, body, flags = {}, flagArgs = {}) {
480
529
  super("Behavior");
481
530
  this.selector = selector;
482
531
  this.body = body;
483
- }
484
- };
485
- var StateEntryNode = class extends BaseNode {
486
- constructor(name, value, important) {
487
- super("StateEntry");
488
- this.name = name;
489
- this.value = value;
490
- this.important = important;
491
- }
492
- };
493
- var StateBlockNode = class extends BaseNode {
494
- constructor(entries) {
495
- super("StateBlock");
496
- this.entries = entries;
532
+ this.flags = flags;
533
+ this.flagArgs = flagArgs;
497
534
  }
498
535
  };
499
536
  var OnBlockNode = class extends BaseNode {
500
- constructor(eventName, args, body, modifiers = []) {
537
+ constructor(eventName, args, body, flags = {}, flagArgs = {}) {
501
538
  super("OnBlock");
502
539
  this.eventName = eventName;
503
540
  this.args = args;
504
541
  this.body = body;
505
- this.modifiers = modifiers;
542
+ this.flags = flags;
543
+ this.flagArgs = flagArgs;
506
544
  }
507
545
  };
508
546
  var AssignmentNode = class extends BaseNode {
509
- constructor(target, value, operator = "=") {
547
+ constructor(target, value, operator = "=", prefix = false) {
510
548
  super("Assignment");
511
549
  this.target = target;
512
550
  this.value = value;
513
551
  this.operator = operator;
552
+ this.prefix = prefix;
514
553
  }
515
- async evaluate(context) {
554
+ evaluate(context) {
516
555
  if (!context.scope || !context.scope.setPath) {
517
556
  return void 0;
518
557
  }
519
- const value = await this.value.evaluate(context);
520
- if (this.operator !== "=") {
521
- return await this.applyCompoundAssignment(context, value);
522
- }
523
- if (this.target instanceof IdentifierExpression && this.target.name.startsWith("root.") && context.rootScope) {
524
- const path = this.target.name.slice("root.".length);
525
- context.rootScope.setPath?.(path, value);
526
- return value;
527
- }
528
- if (this.target instanceof MemberExpression || this.target instanceof IndexExpression) {
529
- const resolved = await this.resolveAssignmentTarget(context);
530
- if (resolved?.scope?.setPath) {
531
- resolved.scope.setPath(resolved.path, value);
532
- return value;
558
+ if (this.operator === "++" || this.operator === "--") {
559
+ return this.applyIncrement(context);
560
+ }
561
+ const value = this.value.evaluate(context);
562
+ return resolveMaybe(value, (resolvedValue) => {
563
+ if (this.operator !== "=") {
564
+ return this.applyCompoundAssignment(context, resolvedValue);
565
+ }
566
+ if (this.target instanceof IdentifierExpression && this.target.name.startsWith("root.") && context.rootScope) {
567
+ const path = this.target.name.slice("root.".length);
568
+ context.rootScope.setPath?.(`self.${path}`, resolvedValue);
569
+ return resolvedValue;
570
+ }
571
+ if (this.target instanceof MemberExpression || this.target instanceof IndexExpression) {
572
+ const resolved = this.resolveAssignmentTarget(context);
573
+ return resolveMaybe(resolved, (resolvedTarget) => {
574
+ if (resolvedTarget?.scope?.setPath) {
575
+ resolvedTarget.scope.setPath(resolvedTarget.path, resolvedValue);
576
+ return resolvedValue;
577
+ }
578
+ this.assignTarget(context, this.target, resolvedValue);
579
+ return resolvedValue;
580
+ });
533
581
  }
534
- }
535
- this.assignTarget(context, this.target, value);
536
- return value;
582
+ this.assignTarget(context, this.target, resolvedValue);
583
+ return resolvedValue;
584
+ });
537
585
  }
538
- async applyCompoundAssignment(context, value) {
586
+ applyCompoundAssignment(context, value) {
539
587
  if (!context.scope || !context.scope.setPath) {
540
588
  return void 0;
541
589
  }
542
- const resolved = await this.resolveAssignmentTarget(context);
543
- if (!resolved) {
544
- throw new Error("Compound assignment requires a simple identifier or member path");
545
- }
546
- const { scope, path } = resolved;
547
- const current = scope?.getPath ? scope.getPath(path) : void 0;
548
- let result;
549
- if (this.operator === "+=") {
550
- result = current + value;
551
- } else if (this.operator === "-=") {
552
- result = current - value;
553
- } else if (this.operator === "*=") {
554
- result = current * value;
555
- } else {
556
- result = current / value;
590
+ const resolved = this.resolveAssignmentTarget(context);
591
+ return resolveMaybe(resolved, (resolvedTarget) => {
592
+ if (!resolvedTarget) {
593
+ throw new Error("Compound assignment requires a simple identifier or member path");
594
+ }
595
+ const { scope, path } = resolvedTarget;
596
+ const current = scope?.getPath ? scope.getPath(path) : void 0;
597
+ let result;
598
+ if (this.operator === "+=") {
599
+ result = current + value;
600
+ } else if (this.operator === "-=") {
601
+ result = current - value;
602
+ } else if (this.operator === "*=") {
603
+ result = current * value;
604
+ } else {
605
+ result = current / value;
606
+ }
607
+ scope?.setPath?.(path, result);
608
+ return result;
609
+ });
610
+ }
611
+ applyIncrement(context) {
612
+ if (!context.scope || !context.scope.setPath) {
613
+ return void 0;
557
614
  }
558
- scope?.setPath?.(path, result);
559
- return result;
615
+ const resolved = this.resolveAssignmentTarget(context);
616
+ return resolveMaybe(resolved, (resolvedTarget) => {
617
+ if (!resolvedTarget) {
618
+ throw new Error("Increment/decrement requires a simple identifier or member path");
619
+ }
620
+ const { scope, path } = resolvedTarget;
621
+ const current = scope?.getPath ? scope.getPath(path) : void 0;
622
+ const numeric = typeof current === "number" ? current : Number(current);
623
+ const delta = this.operator === "++" ? 1 : -1;
624
+ const next = (Number.isNaN(numeric) ? 0 : numeric) + delta;
625
+ scope?.setPath?.(path, next);
626
+ return this.prefix ? next : numeric;
627
+ });
560
628
  }
561
- async resolveAssignmentTarget(context) {
629
+ resolveAssignmentTarget(context) {
562
630
  if (this.target instanceof IdentifierExpression) {
563
631
  const isRoot = this.target.name.startsWith("root.");
564
632
  const rawPath = isRoot ? this.target.name.slice("root.".length) : this.target.name;
565
633
  if (isRoot) {
634
+ if (context.rootScope) {
635
+ return { scope: context.rootScope, path: `self.${rawPath}` };
636
+ }
566
637
  return { scope: context.scope, path: `root.${rawPath}` };
567
638
  }
568
639
  return { scope: context.scope, path: rawPath };
569
640
  }
570
641
  if (this.target instanceof MemberExpression) {
571
642
  const resolvedPath = this.target.getIdentifierPath();
572
- if (!resolvedPath) {
573
- return null;
574
- }
575
- const path = resolvedPath.path;
576
- const isRoot = path.startsWith("root.");
577
- const rawPath = isRoot ? path.slice("root.".length) : path;
578
- if (isRoot) {
579
- return { scope: context.scope, path: `root.${rawPath}` };
643
+ if (resolvedPath) {
644
+ const path = resolvedPath.path;
645
+ const isRoot = path.startsWith("root.");
646
+ const rawPath = isRoot ? path.slice("root.".length) : path;
647
+ if (isRoot) {
648
+ if (context.rootScope) {
649
+ return { scope: context.rootScope, path: `self.${rawPath}` };
650
+ }
651
+ return { scope: context.scope, path: `root.${rawPath}` };
652
+ }
653
+ return { scope: context.scope, path: rawPath };
580
654
  }
581
- return { scope: context.scope, path: rawPath };
655
+ const targetExpr = this.target;
656
+ const basePath = this.resolveTargetPath(context, targetExpr.target);
657
+ return resolveMaybe(basePath, (resolvedBase) => {
658
+ if (!resolvedBase) {
659
+ return null;
660
+ }
661
+ const path = `${resolvedBase}.${targetExpr.property}`;
662
+ const isRoot = path.startsWith("root.");
663
+ const rawPath = isRoot ? path.slice("root.".length) : path;
664
+ if (isRoot) {
665
+ if (context.rootScope) {
666
+ return { scope: context.rootScope, path: `self.${rawPath}` };
667
+ }
668
+ return { scope: context.scope, path: `root.${rawPath}` };
669
+ }
670
+ return { scope: context.scope, path: rawPath };
671
+ });
582
672
  }
583
673
  if (this.target instanceof IndexExpression) {
584
- const path = await this.resolveIndexPath(context, this.target);
585
- if (!path) {
586
- return null;
587
- }
588
- const isRoot = path.startsWith("root.");
589
- const rawPath = isRoot ? path.slice("root.".length) : path;
590
- if (isRoot) {
591
- return { scope: context.scope, path: `root.${rawPath}` };
592
- }
593
- return { scope: context.scope, path: rawPath };
674
+ const path = this.resolveIndexPath(context, this.target);
675
+ return resolveMaybe(path, (resolvedPath) => {
676
+ if (!resolvedPath) {
677
+ return null;
678
+ }
679
+ const isRoot = resolvedPath.startsWith("root.");
680
+ const rawPath = isRoot ? resolvedPath.slice("root.".length) : resolvedPath;
681
+ if (isRoot) {
682
+ if (context.rootScope) {
683
+ return { scope: context.rootScope, path: `self.${rawPath}` };
684
+ }
685
+ return { scope: context.scope, path: `root.${rawPath}` };
686
+ }
687
+ return { scope: context.scope, path: rawPath };
688
+ });
594
689
  }
595
690
  return null;
596
691
  }
597
- async resolveIndexPath(context, expr) {
598
- const base = await this.resolveTargetPath(context, expr.target);
599
- if (!base) {
600
- return null;
601
- }
602
- const indexValue = await expr.index.evaluate(context);
603
- if (indexValue == null) {
604
- return null;
605
- }
606
- return `${base}.${indexValue}`;
692
+ resolveIndexPath(context, expr) {
693
+ const base = this.resolveTargetPath(context, expr.target);
694
+ return resolveMaybe(base, (resolvedBase) => {
695
+ if (!resolvedBase) {
696
+ return null;
697
+ }
698
+ const indexValue = expr.index.evaluate(context);
699
+ return resolveMaybe(indexValue, (resolvedIndex) => {
700
+ if (resolvedIndex == null) {
701
+ return null;
702
+ }
703
+ return `${resolvedBase}.${resolvedIndex}`;
704
+ });
705
+ });
607
706
  }
608
- async resolveTargetPath(context, target) {
707
+ resolveTargetPath(context, target) {
609
708
  if (target instanceof IdentifierExpression) {
610
709
  return target.name;
611
710
  }
@@ -621,6 +720,10 @@ var AssignmentNode = class extends BaseNode {
621
720
  if (!context.scope || !context.scope.setPath) {
622
721
  return;
623
722
  }
723
+ if (target instanceof DirectiveExpression) {
724
+ this.assignDirectiveTarget(context, target, value);
725
+ return;
726
+ }
624
727
  if (target instanceof IdentifierExpression) {
625
728
  context.scope.setPath(target.name, value);
626
729
  return;
@@ -662,19 +765,99 @@ var AssignmentNode = class extends BaseNode {
662
765
  return;
663
766
  }
664
767
  }
768
+ assignDirectiveTarget(context, target, value) {
769
+ const element = context.element;
770
+ if (!element) {
771
+ return;
772
+ }
773
+ if (target.kind === "attr") {
774
+ if (target.name === "value") {
775
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
776
+ element.value = value == null ? "" : String(value);
777
+ element.setAttribute("value", element.value);
778
+ return;
779
+ }
780
+ if (element instanceof HTMLSelectElement) {
781
+ element.value = value == null ? "" : String(value);
782
+ return;
783
+ }
784
+ }
785
+ if (target.name === "checked" && element instanceof HTMLInputElement) {
786
+ const checked = value === true || value === "true" || value === 1 || value === "1";
787
+ element.checked = checked;
788
+ if (checked) {
789
+ element.setAttribute("checked", "");
790
+ } else {
791
+ element.removeAttribute("checked");
792
+ }
793
+ return;
794
+ }
795
+ if (target.name === "html" && element instanceof HTMLElement) {
796
+ element.innerHTML = value == null ? "" : String(value);
797
+ return;
798
+ }
799
+ element.setAttribute(target.name, value == null ? "" : String(value));
800
+ return;
801
+ }
802
+ if (target.kind === "style" && element instanceof HTMLElement) {
803
+ element.style.setProperty(target.name, value == null ? "" : String(value));
804
+ }
805
+ }
665
806
  };
666
807
  var ReturnNode = class extends BaseNode {
667
808
  constructor(value) {
668
809
  super("Return");
669
810
  this.value = value;
670
811
  }
671
- async evaluate(context) {
812
+ evaluate(context) {
672
813
  if (context.returning) {
673
814
  return context.returnValue;
674
815
  }
675
- context.returnValue = this.value ? await this.value.evaluate(context) : void 0;
676
- context.returning = true;
677
- return context.returnValue;
816
+ const nextValue = this.value ? this.value.evaluate(context) : void 0;
817
+ return resolveMaybe(nextValue, (resolved) => {
818
+ context.returnValue = resolved;
819
+ context.returning = true;
820
+ return context.returnValue;
821
+ });
822
+ }
823
+ };
824
+ var BreakNode = class extends BaseNode {
825
+ constructor() {
826
+ super("Break");
827
+ }
828
+ evaluate(context) {
829
+ context.breaking = true;
830
+ return void 0;
831
+ }
832
+ };
833
+ var ContinueNode = class extends BaseNode {
834
+ constructor() {
835
+ super("Continue");
836
+ }
837
+ evaluate(context) {
838
+ context.continuing = true;
839
+ return void 0;
840
+ }
841
+ };
842
+ var AssertError = class extends Error {
843
+ constructor(message = "Assertion failed") {
844
+ super(message);
845
+ this.name = "AssertError";
846
+ }
847
+ };
848
+ var AssertNode = class extends BaseNode {
849
+ constructor(test) {
850
+ super("Assert");
851
+ this.test = test;
852
+ }
853
+ evaluate(context) {
854
+ const value = this.test.evaluate(context);
855
+ return resolveMaybe(value, (resolved) => {
856
+ if (!resolved) {
857
+ throw new AssertError();
858
+ }
859
+ return resolved;
860
+ });
678
861
  }
679
862
  };
680
863
  var IfNode = class extends BaseNode {
@@ -684,14 +867,17 @@ var IfNode = class extends BaseNode {
684
867
  this.consequent = consequent;
685
868
  this.alternate = alternate;
686
869
  }
687
- async evaluate(context) {
688
- const condition = await this.test.evaluate(context);
689
- if (condition) {
690
- return evaluateWithChildScope(context, this.consequent);
691
- }
692
- if (this.alternate) {
693
- return evaluateWithChildScope(context, this.alternate);
694
- }
870
+ evaluate(context) {
871
+ const condition = this.test.evaluate(context);
872
+ return resolveMaybe(condition, (resolved) => {
873
+ if (resolved) {
874
+ return evaluateWithChildScope(context, this.consequent);
875
+ }
876
+ if (this.alternate) {
877
+ return evaluateWithChildScope(context, this.alternate);
878
+ }
879
+ return void 0;
880
+ });
695
881
  }
696
882
  };
697
883
  var WhileNode = class extends BaseNode {
@@ -700,21 +886,104 @@ var WhileNode = class extends BaseNode {
700
886
  this.test = test;
701
887
  this.body = body;
702
888
  }
703
- async evaluate(context) {
889
+ evaluate(context) {
704
890
  const previousScope = context.scope;
705
891
  if (context.scope?.createChild) {
706
892
  context.scope = context.scope.createChild();
707
893
  }
708
- try {
709
- while (await this.test.evaluate(context)) {
710
- await this.body.evaluate(context);
711
- if (context.returning) {
712
- break;
894
+ const run = () => {
895
+ const condition = this.test.evaluate(context);
896
+ return resolveMaybe(condition, (resolved) => {
897
+ if (!resolved || context.returning) {
898
+ return void 0;
899
+ }
900
+ const bodyResult = this.body.evaluate(context);
901
+ return resolveMaybe(bodyResult, () => {
902
+ if (context.breaking) {
903
+ context.breaking = false;
904
+ return void 0;
905
+ }
906
+ if (context.continuing) {
907
+ context.continuing = false;
908
+ }
909
+ return run();
910
+ });
911
+ });
912
+ };
913
+ const result = run();
914
+ if (isPromiseLike(result)) {
915
+ return result.finally(() => {
916
+ context.scope = previousScope;
917
+ });
918
+ }
919
+ context.scope = previousScope;
920
+ return result;
921
+ }
922
+ };
923
+ var ForEachNode = class extends BaseNode {
924
+ constructor(target, iterable, kind, body) {
925
+ super("ForEach");
926
+ this.target = target;
927
+ this.iterable = iterable;
928
+ this.kind = kind;
929
+ this.body = body;
930
+ }
931
+ evaluate(context) {
932
+ const iterableValue = this.iterable.evaluate(context);
933
+ return resolveMaybe(iterableValue, (resolved) => {
934
+ const entries = this.getEntries(resolved);
935
+ const previousScope = context.scope;
936
+ let bodyScope = context.scope;
937
+ if (context.scope?.createChild) {
938
+ bodyScope = context.scope.createChild();
939
+ }
940
+ let index = 0;
941
+ const loop = () => {
942
+ if (index >= entries.length || context.returning) {
943
+ context.scope = previousScope;
944
+ return void 0;
713
945
  }
946
+ const value = entries[index];
947
+ index += 1;
948
+ context.scope = bodyScope;
949
+ context.scope?.setPath?.(this.target.name, value);
950
+ const bodyResult = this.body.evaluate(context);
951
+ return resolveMaybe(bodyResult, () => {
952
+ if (context.breaking) {
953
+ context.breaking = false;
954
+ context.scope = previousScope;
955
+ return void 0;
956
+ }
957
+ if (context.continuing) {
958
+ context.continuing = false;
959
+ }
960
+ context.scope = previousScope;
961
+ return loop();
962
+ });
963
+ };
964
+ return loop();
965
+ });
966
+ }
967
+ getEntries(value) {
968
+ if (value == null) {
969
+ return [];
970
+ }
971
+ if (this.kind === "in") {
972
+ if (typeof value === "object") {
973
+ return Object.keys(value);
714
974
  }
715
- } finally {
716
- context.scope = previousScope;
975
+ return [];
717
976
  }
977
+ if (typeof value === "string") {
978
+ return Array.from(value);
979
+ }
980
+ if (typeof value[Symbol.iterator] === "function") {
981
+ return Array.from(value);
982
+ }
983
+ if (typeof value === "object") {
984
+ return Object.values(value);
985
+ }
986
+ return [];
718
987
  }
719
988
  };
720
989
  var ForNode = class extends BaseNode {
@@ -725,27 +994,45 @@ var ForNode = class extends BaseNode {
725
994
  this.update = update;
726
995
  this.body = body;
727
996
  }
728
- async evaluate(context) {
729
- if (this.init) {
730
- await this.init.evaluate(context);
731
- }
732
- const previousScope = context.scope;
733
- let bodyScope = context.scope;
734
- if (context.scope?.createChild) {
735
- bodyScope = context.scope.createChild();
736
- }
737
- while (this.test ? await this.test.evaluate(context) : true) {
738
- context.scope = bodyScope;
739
- await this.body.evaluate(context);
740
- if (context.returning) {
741
- break;
742
- }
743
- context.scope = previousScope;
744
- if (this.update) {
745
- await this.update.evaluate(context);
746
- }
747
- }
748
- context.scope = previousScope;
997
+ evaluate(context) {
998
+ const initResult = this.init ? this.init.evaluate(context) : void 0;
999
+ const run = () => {
1000
+ const previousScope = context.scope;
1001
+ let bodyScope = context.scope;
1002
+ if (context.scope?.createChild) {
1003
+ bodyScope = context.scope.createChild();
1004
+ }
1005
+ const loop = () => {
1006
+ const testResult = this.test ? this.test.evaluate(context) : true;
1007
+ return resolveMaybe(testResult, (passed) => {
1008
+ if (!passed || context.returning) {
1009
+ context.scope = previousScope;
1010
+ return void 0;
1011
+ }
1012
+ context.scope = bodyScope;
1013
+ const bodyResult = this.body.evaluate(context);
1014
+ return resolveMaybe(bodyResult, () => {
1015
+ if (context.returning) {
1016
+ context.scope = previousScope;
1017
+ return void 0;
1018
+ }
1019
+ if (context.breaking) {
1020
+ context.breaking = false;
1021
+ context.scope = previousScope;
1022
+ return void 0;
1023
+ }
1024
+ context.scope = previousScope;
1025
+ if (context.continuing) {
1026
+ context.continuing = false;
1027
+ }
1028
+ const updateResult = this.update ? this.update.evaluate(context) : void 0;
1029
+ return resolveMaybe(updateResult, () => loop());
1030
+ });
1031
+ });
1032
+ };
1033
+ return loop();
1034
+ };
1035
+ return resolveMaybe(initResult, () => run());
749
1036
  }
750
1037
  };
751
1038
  var TryNode = class extends BaseNode {
@@ -755,10 +1042,8 @@ var TryNode = class extends BaseNode {
755
1042
  this.errorName = errorName;
756
1043
  this.handler = handler;
757
1044
  }
758
- async evaluate(context) {
759
- try {
760
- return await evaluateWithChildScope(context, this.body);
761
- } catch (error) {
1045
+ evaluate(context) {
1046
+ const handleError = (error) => {
762
1047
  if (context.returning) {
763
1048
  return context.returnValue;
764
1049
  }
@@ -776,11 +1061,23 @@ var TryNode = class extends BaseNode {
776
1061
  scope.setPath(`self.${this.errorName}`, error);
777
1062
  }
778
1063
  }
779
- await this.handler.evaluate(context);
780
- if (scope && scope.setPath && handlerScope === previousScope) {
781
- scope.setPath(this.errorName, previous);
1064
+ const handlerResult = this.handler.evaluate(context);
1065
+ return resolveMaybe(handlerResult, () => {
1066
+ if (scope && scope.setPath && handlerScope === previousScope) {
1067
+ scope.setPath(this.errorName, previous);
1068
+ }
1069
+ context.scope = previousScope;
1070
+ return void 0;
1071
+ });
1072
+ };
1073
+ try {
1074
+ const bodyResult = evaluateWithChildScope(context, this.body);
1075
+ if (isPromiseLike(bodyResult)) {
1076
+ return bodyResult.catch((error) => handleError(error));
782
1077
  }
783
- context.scope = previousScope;
1078
+ return bodyResult;
1079
+ } catch (error) {
1080
+ return handleError(error);
784
1081
  }
785
1082
  }
786
1083
  };
@@ -800,11 +1097,35 @@ var FunctionExpression = class extends BaseNode {
800
1097
  this.body = body;
801
1098
  this.isAsync = isAsync;
802
1099
  }
803
- async evaluate(context) {
1100
+ evaluate(context) {
804
1101
  const scope = context.scope;
805
1102
  const globals = context.globals;
806
1103
  const element = context.element;
807
- return async (...args) => {
1104
+ if (this.isAsync) {
1105
+ return (...args) => {
1106
+ const activeScope = scope?.createChild ? scope.createChild() : scope;
1107
+ const inner = {
1108
+ scope: activeScope,
1109
+ rootScope: context.rootScope,
1110
+ ...globals ? { globals } : {},
1111
+ ...element ? { element } : {},
1112
+ returnValue: void 0,
1113
+ returning: false,
1114
+ breaking: false,
1115
+ continuing: false
1116
+ };
1117
+ const previousValues = /* @__PURE__ */ new Map();
1118
+ const applyResult = activeScope ? this.applyParams(activeScope, previousValues, inner, args) : void 0;
1119
+ const bodyResult = resolveMaybe(applyResult, () => this.body.evaluate(inner));
1120
+ const finalResult = resolveMaybe(bodyResult, () => inner.returnValue);
1121
+ return Promise.resolve(finalResult).finally(() => {
1122
+ if (activeScope && activeScope === scope) {
1123
+ this.restoreParams(activeScope, previousValues);
1124
+ }
1125
+ });
1126
+ };
1127
+ }
1128
+ return (...args) => {
808
1129
  const activeScope = scope?.createChild ? scope.createChild() : scope;
809
1130
  const inner = {
810
1131
  scope: activeScope,
@@ -812,47 +1133,69 @@ var FunctionExpression = class extends BaseNode {
812
1133
  ...globals ? { globals } : {},
813
1134
  ...element ? { element } : {},
814
1135
  returnValue: void 0,
815
- returning: false
1136
+ returning: false,
1137
+ breaking: false,
1138
+ continuing: false
816
1139
  };
817
- if (activeScope) {
818
- const previousValues = /* @__PURE__ */ new Map();
819
- await this.applyParams(activeScope, previousValues, inner, args);
820
- await this.body.evaluate(inner);
821
- if (activeScope === scope) {
822
- this.restoreParams(activeScope, previousValues);
823
- }
824
- } else {
825
- await this.body.evaluate(inner);
1140
+ const previousValues = /* @__PURE__ */ new Map();
1141
+ const applyResult = activeScope ? this.applyParams(activeScope, previousValues, inner, args) : void 0;
1142
+ const bodyResult = resolveMaybe(applyResult, () => this.body.evaluate(inner));
1143
+ const finalResult = resolveMaybe(bodyResult, () => inner.returnValue);
1144
+ if (isPromiseLike(finalResult)) {
1145
+ return finalResult.finally(() => {
1146
+ if (activeScope && activeScope === scope) {
1147
+ this.restoreParams(activeScope, previousValues);
1148
+ }
1149
+ });
1150
+ }
1151
+ if (activeScope && activeScope === scope) {
1152
+ this.restoreParams(activeScope, previousValues);
826
1153
  }
827
- return inner.returnValue;
1154
+ return finalResult;
828
1155
  };
829
1156
  }
830
- async applyParams(scope, previousValues, context, args) {
831
- if (!scope || !scope.setPath) {
1157
+ applyParams(scope, previousValues, context, args) {
1158
+ if (!scope) {
832
1159
  return;
833
1160
  }
834
- let argIndex = 0;
835
- for (const param of this.params) {
836
- const name = param.name;
837
- if (!name) {
838
- continue;
839
- }
840
- previousValues.set(name, scope.getPath(name));
841
- if (param.rest) {
842
- scope.setPath(`self.${name}`, args.slice(argIndex));
843
- argIndex = args.length;
844
- continue;
845
- }
846
- let value = args[argIndex];
847
- if (value === void 0 && param.defaultValue) {
848
- value = await param.defaultValue.evaluate(context);
849
- }
850
- scope.setPath(`self.${name}`, value);
851
- argIndex += 1;
1161
+ const setPath = scope.setPath?.bind(scope);
1162
+ if (!setPath) {
1163
+ return;
852
1164
  }
1165
+ const params = this.params;
1166
+ const applyAt = (paramIndex, argIndex) => {
1167
+ for (let i = paramIndex; i < params.length; i += 1) {
1168
+ const param = params[i];
1169
+ const name = param.name;
1170
+ if (!name) {
1171
+ continue;
1172
+ }
1173
+ previousValues.set(name, scope.getPath(name));
1174
+ if (param.rest) {
1175
+ setPath(`self.${name}`, args.slice(argIndex));
1176
+ return;
1177
+ }
1178
+ let value = args[argIndex];
1179
+ if (value === void 0 && param.defaultValue) {
1180
+ const defaultValue = param.defaultValue.evaluate(context);
1181
+ return resolveMaybe(defaultValue, (resolvedDefault) => {
1182
+ setPath(`self.${name}`, resolvedDefault);
1183
+ return applyAt(i + 1, argIndex + 1);
1184
+ });
1185
+ }
1186
+ setPath(`self.${name}`, value);
1187
+ argIndex += 1;
1188
+ }
1189
+ return;
1190
+ };
1191
+ return applyAt(0, 0);
853
1192
  }
854
1193
  restoreParams(scope, previousValues) {
855
- if (!scope || !scope.setPath) {
1194
+ if (!scope) {
1195
+ return;
1196
+ }
1197
+ const setPath = scope.setPath?.bind(scope);
1198
+ if (!setPath) {
856
1199
  return;
857
1200
  }
858
1201
  for (const param of this.params) {
@@ -860,7 +1203,7 @@ var FunctionExpression = class extends BaseNode {
860
1203
  if (!name) {
861
1204
  continue;
862
1205
  }
863
- scope.setPath(name, previousValues.get(name));
1206
+ setPath(name, previousValues.get(name));
864
1207
  }
865
1208
  }
866
1209
  };
@@ -879,7 +1222,7 @@ var IdentifierExpression = class extends BaseNode {
879
1222
  super("Identifier");
880
1223
  this.name = name;
881
1224
  }
882
- async evaluate(context) {
1225
+ evaluate(context) {
883
1226
  if (this.name.startsWith("root.") && context.rootScope) {
884
1227
  const path = this.name.slice("root.".length);
885
1228
  return context.rootScope.getPath(`self.${path}`);
@@ -924,7 +1267,7 @@ var LiteralExpression = class extends BaseNode {
924
1267
  super("Literal");
925
1268
  this.value = value;
926
1269
  }
927
- async evaluate() {
1270
+ evaluate() {
928
1271
  return this.value;
929
1272
  }
930
1273
  };
@@ -933,30 +1276,41 @@ var TemplateExpression = class extends BaseNode {
933
1276
  super("TemplateExpression");
934
1277
  this.parts = parts;
935
1278
  }
936
- async evaluate(context) {
1279
+ evaluate(context) {
937
1280
  let result = "";
938
- for (const part of this.parts) {
939
- const value = await part.evaluate(context);
940
- result += value == null ? "" : String(value);
941
- }
942
- return result;
1281
+ let index = 0;
1282
+ const run = () => {
1283
+ while (index < this.parts.length) {
1284
+ const part = this.parts[index];
1285
+ index += 1;
1286
+ const value = part.evaluate(context);
1287
+ return resolveMaybe(value, (resolved) => {
1288
+ result += resolved == null ? "" : String(resolved);
1289
+ return run();
1290
+ });
1291
+ }
1292
+ return result;
1293
+ };
1294
+ return run();
943
1295
  }
944
1296
  };
945
1297
  var UnaryExpression = class extends BaseNode {
946
1298
  constructor(operator, argument) {
947
1299
  super("UnaryExpression");
948
1300
  this.operator = operator;
949
- this.argument = argument;
950
- }
951
- async evaluate(context) {
952
- const value = await this.argument.evaluate(context);
953
- if (this.operator === "!") {
954
- return !value;
955
- }
956
- if (this.operator === "-") {
957
- return -value;
958
- }
959
- return value;
1301
+ this.argument = argument;
1302
+ }
1303
+ evaluate(context) {
1304
+ const value = this.argument.evaluate(context);
1305
+ return resolveMaybe(value, (resolved) => {
1306
+ if (this.operator === "!") {
1307
+ return !resolved;
1308
+ }
1309
+ if (this.operator === "-") {
1310
+ return -resolved;
1311
+ }
1312
+ return resolved;
1313
+ });
960
1314
  }
961
1315
  };
962
1316
  var BinaryExpression = class extends BaseNode {
@@ -966,61 +1320,71 @@ var BinaryExpression = class extends BaseNode {
966
1320
  this.left = left;
967
1321
  this.right = right;
968
1322
  }
969
- async evaluate(context) {
970
- if (this.operator === "&&") {
971
- const leftValue = await this.left.evaluate(context);
972
- return leftValue && await this.right.evaluate(context);
973
- }
974
- if (this.operator === "||") {
975
- const leftValue = await this.left.evaluate(context);
976
- return leftValue || await this.right.evaluate(context);
977
- }
978
- if (this.operator === "??") {
979
- const leftValue = await this.left.evaluate(context);
980
- return leftValue ?? await this.right.evaluate(context);
981
- }
982
- const left = await this.left.evaluate(context);
983
- const right = await this.right.evaluate(context);
984
- if (this.operator === "+") {
985
- return left + right;
986
- }
987
- if (this.operator === "-") {
988
- return left - right;
989
- }
990
- if (this.operator === "*") {
991
- return left * right;
992
- }
993
- if (this.operator === "/") {
994
- return left / right;
995
- }
996
- if (this.operator === "%") {
997
- return left % right;
998
- }
999
- if (this.operator === "==") {
1000
- return left == right;
1001
- }
1002
- if (this.operator === "!=") {
1003
- return left != right;
1004
- }
1005
- if (this.operator === "===") {
1006
- return left === right;
1007
- }
1008
- if (this.operator === "!==") {
1009
- return left !== right;
1010
- }
1011
- if (this.operator === "<") {
1012
- return left < right;
1013
- }
1014
- if (this.operator === ">") {
1015
- return left > right;
1016
- }
1017
- if (this.operator === "<=") {
1018
- return left <= right;
1019
- }
1020
- if (this.operator === ">=") {
1021
- return left >= right;
1022
- }
1023
- return void 0;
1323
+ evaluate(context) {
1324
+ const leftValue = this.left.evaluate(context);
1325
+ return resolveMaybe(leftValue, (resolvedLeft) => {
1326
+ if (this.operator === "&&") {
1327
+ if (!resolvedLeft) {
1328
+ return resolvedLeft;
1329
+ }
1330
+ return this.right.evaluate(context);
1331
+ }
1332
+ if (this.operator === "||") {
1333
+ if (resolvedLeft) {
1334
+ return resolvedLeft;
1335
+ }
1336
+ return this.right.evaluate(context);
1337
+ }
1338
+ if (this.operator === "??") {
1339
+ if (resolvedLeft !== null && resolvedLeft !== void 0) {
1340
+ return resolvedLeft;
1341
+ }
1342
+ return this.right.evaluate(context);
1343
+ }
1344
+ const rightValue = this.right.evaluate(context);
1345
+ return resolveMaybe(rightValue, (resolvedRight) => {
1346
+ if (this.operator === "+") {
1347
+ return resolvedLeft + resolvedRight;
1348
+ }
1349
+ if (this.operator === "-") {
1350
+ return resolvedLeft - resolvedRight;
1351
+ }
1352
+ if (this.operator === "*") {
1353
+ return resolvedLeft * resolvedRight;
1354
+ }
1355
+ if (this.operator === "/") {
1356
+ return resolvedLeft / resolvedRight;
1357
+ }
1358
+ if (this.operator === "%") {
1359
+ return resolvedLeft % resolvedRight;
1360
+ }
1361
+ if (this.operator === "==") {
1362
+ return resolvedLeft == resolvedRight;
1363
+ }
1364
+ if (this.operator === "!=") {
1365
+ return resolvedLeft != resolvedRight;
1366
+ }
1367
+ if (this.operator === "===") {
1368
+ return resolvedLeft === resolvedRight;
1369
+ }
1370
+ if (this.operator === "!==") {
1371
+ return resolvedLeft !== resolvedRight;
1372
+ }
1373
+ if (this.operator === "<") {
1374
+ return resolvedLeft < resolvedRight;
1375
+ }
1376
+ if (this.operator === ">") {
1377
+ return resolvedLeft > resolvedRight;
1378
+ }
1379
+ if (this.operator === "<=") {
1380
+ return resolvedLeft <= resolvedRight;
1381
+ }
1382
+ if (this.operator === ">=") {
1383
+ return resolvedLeft >= resolvedRight;
1384
+ }
1385
+ return void 0;
1386
+ });
1387
+ });
1024
1388
  }
1025
1389
  };
1026
1390
  var TernaryExpression = class extends BaseNode {
@@ -1030,12 +1394,14 @@ var TernaryExpression = class extends BaseNode {
1030
1394
  this.consequent = consequent;
1031
1395
  this.alternate = alternate;
1032
1396
  }
1033
- async evaluate(context) {
1034
- const condition = await this.test.evaluate(context);
1035
- if (condition) {
1036
- return this.consequent.evaluate(context);
1037
- }
1038
- return this.alternate.evaluate(context);
1397
+ evaluate(context) {
1398
+ const condition = this.test.evaluate(context);
1399
+ return resolveMaybe(condition, (resolved) => {
1400
+ if (resolved) {
1401
+ return this.consequent.evaluate(context);
1402
+ }
1403
+ return this.alternate.evaluate(context);
1404
+ });
1039
1405
  }
1040
1406
  };
1041
1407
  var MemberExpression = class _MemberExpression extends BaseNode {
@@ -1045,11 +1411,11 @@ var MemberExpression = class _MemberExpression extends BaseNode {
1045
1411
  this.property = property;
1046
1412
  this.optional = optional;
1047
1413
  }
1048
- async evaluate(context) {
1049
- const resolved = await this.resolve(context);
1050
- return resolved?.value;
1414
+ evaluate(context) {
1415
+ const resolved = this.resolve(context);
1416
+ return resolveMaybe(resolved, (resolvedValue) => resolvedValue?.value);
1051
1417
  }
1052
- async resolve(context) {
1418
+ resolve(context) {
1053
1419
  const path = this.getIdentifierPath();
1054
1420
  if (path) {
1055
1421
  const resolved = this.resolveFromScope(context, path);
@@ -1061,11 +1427,13 @@ var MemberExpression = class _MemberExpression extends BaseNode {
1061
1427
  return resolvedGlobal;
1062
1428
  }
1063
1429
  }
1064
- const target = await this.target.evaluate(context);
1065
- if (target == null) {
1066
- return { value: void 0, target, optional: this.optional };
1067
- }
1068
- return { value: target[this.property], target, optional: this.optional };
1430
+ const target = this.target.evaluate(context);
1431
+ return resolveMaybe(target, (resolvedTarget) => {
1432
+ if (resolvedTarget == null) {
1433
+ return { value: void 0, target: resolvedTarget, optional: this.optional };
1434
+ }
1435
+ return { value: resolvedTarget[this.property], target: resolvedTarget, optional: this.optional };
1436
+ });
1069
1437
  }
1070
1438
  getIdentifierPath() {
1071
1439
  const targetPath = this.getTargetIdentifierPath();
@@ -1141,25 +1509,39 @@ var CallExpression = class extends BaseNode {
1141
1509
  this.callee = callee;
1142
1510
  this.args = args;
1143
1511
  }
1144
- async evaluate(context) {
1145
- const resolved = await this.resolveCallee(context);
1146
- const fn = resolved?.fn ?? await this.callee.evaluate(context);
1147
- if (typeof fn !== "function") {
1148
- return void 0;
1149
- }
1150
- const values = [];
1151
- for (const arg of this.args) {
1152
- values.push(await arg.evaluate(context));
1153
- }
1154
- return fn.apply(resolved?.thisArg, values);
1512
+ evaluate(context) {
1513
+ const resolved = this.resolveCallee(context);
1514
+ return resolveMaybe(resolved, (resolvedCallee) => {
1515
+ const fnValue = resolvedCallee?.fn ?? this.callee.evaluate(context);
1516
+ return resolveMaybe(fnValue, (resolvedFn) => {
1517
+ if (typeof resolvedFn !== "function") {
1518
+ return void 0;
1519
+ }
1520
+ const values = [];
1521
+ const evalArgs = (index) => {
1522
+ for (let i = index; i < this.args.length; i += 1) {
1523
+ const arg = this.args[i];
1524
+ const argValue = arg.evaluate(context);
1525
+ return resolveMaybe(argValue, (resolvedArg) => {
1526
+ values.push(resolvedArg);
1527
+ return evalArgs(i + 1);
1528
+ });
1529
+ }
1530
+ return resolvedFn.apply(resolvedCallee?.thisArg, values);
1531
+ };
1532
+ return evalArgs(0);
1533
+ });
1534
+ });
1155
1535
  }
1156
- async resolveCallee(context) {
1536
+ resolveCallee(context) {
1157
1537
  if (this.callee instanceof MemberExpression) {
1158
- const resolved = await this.callee.resolve(context);
1159
- if (!resolved) {
1160
- return void 0;
1161
- }
1162
- return { fn: resolved.value, thisArg: resolved.target };
1538
+ const resolved = this.callee.resolve(context);
1539
+ return resolveMaybe(resolved, (resolvedValue) => {
1540
+ if (!resolvedValue) {
1541
+ return void 0;
1542
+ }
1543
+ return { fn: resolvedValue.value, thisArg: resolvedValue.target };
1544
+ });
1163
1545
  }
1164
1546
  if (!(this.callee instanceof IdentifierExpression)) {
1165
1547
  return void 0;
@@ -1201,27 +1583,40 @@ var ArrayExpression = class extends BaseNode {
1201
1583
  super("ArrayExpression");
1202
1584
  this.elements = elements;
1203
1585
  }
1204
- async evaluate(context) {
1586
+ evaluate(context) {
1205
1587
  const values = [];
1206
- for (const element of this.elements) {
1207
- if (element instanceof SpreadElement) {
1208
- const spreadValue = await element.value.evaluate(context);
1209
- if (spreadValue == null) {
1210
- continue;
1588
+ const pushElements = (value) => {
1589
+ if (value == null) {
1590
+ return;
1591
+ }
1592
+ const iterator = value[Symbol.iterator];
1593
+ if (typeof iterator === "function") {
1594
+ for (const entry of value) {
1595
+ values.push(entry);
1211
1596
  }
1212
- const iterator = spreadValue[Symbol.iterator];
1213
- if (typeof iterator === "function") {
1214
- for (const entry of spreadValue) {
1215
- values.push(entry);
1216
- }
1217
- } else {
1218
- values.push(spreadValue);
1597
+ } else {
1598
+ values.push(value);
1599
+ }
1600
+ };
1601
+ const evalAt = (index) => {
1602
+ for (let i = index; i < this.elements.length; i += 1) {
1603
+ const element = this.elements[i];
1604
+ if (element instanceof SpreadElement) {
1605
+ const spreadValue = element.value.evaluate(context);
1606
+ return resolveMaybe(spreadValue, (resolvedSpread) => {
1607
+ pushElements(resolvedSpread);
1608
+ return evalAt(i + 1);
1609
+ });
1219
1610
  }
1220
- continue;
1611
+ const value = element.evaluate(context);
1612
+ return resolveMaybe(value, (resolvedValue) => {
1613
+ values.push(resolvedValue);
1614
+ return evalAt(i + 1);
1615
+ });
1221
1616
  }
1222
- values.push(await element.evaluate(context));
1223
- }
1224
- return values;
1617
+ return values;
1618
+ };
1619
+ return evalAt(0);
1225
1620
  }
1226
1621
  };
1227
1622
  var ObjectExpression = class extends BaseNode {
@@ -1229,24 +1624,39 @@ var ObjectExpression = class extends BaseNode {
1229
1624
  super("ObjectExpression");
1230
1625
  this.entries = entries;
1231
1626
  }
1232
- async evaluate(context) {
1627
+ evaluate(context) {
1233
1628
  const result = {};
1234
- for (const entry of this.entries) {
1235
- if ("spread" in entry) {
1236
- const spreadValue = await entry.spread.evaluate(context);
1237
- if (spreadValue != null) {
1238
- Object.assign(result, spreadValue);
1629
+ const evalAt = (index) => {
1630
+ for (let i = index; i < this.entries.length; i += 1) {
1631
+ const entry = this.entries[i];
1632
+ if ("spread" in entry) {
1633
+ const spreadValue = entry.spread.evaluate(context);
1634
+ return resolveMaybe(spreadValue, (resolvedSpread) => {
1635
+ if (resolvedSpread != null) {
1636
+ Object.assign(result, resolvedSpread);
1637
+ }
1638
+ return evalAt(i + 1);
1639
+ });
1239
1640
  }
1240
- continue;
1241
- }
1242
- if ("computed" in entry && entry.computed) {
1243
- const keyValue = await entry.keyExpr.evaluate(context);
1244
- result[String(keyValue)] = await entry.value.evaluate(context);
1245
- } else {
1246
- result[entry.key] = await entry.value.evaluate(context);
1641
+ if ("computed" in entry && entry.computed) {
1642
+ const keyValue = entry.keyExpr.evaluate(context);
1643
+ return resolveMaybe(keyValue, (resolvedKey) => {
1644
+ const entryValue = entry.value.evaluate(context);
1645
+ return resolveMaybe(entryValue, (resolvedValue) => {
1646
+ result[String(resolvedKey)] = resolvedValue;
1647
+ return evalAt(i + 1);
1648
+ });
1649
+ });
1650
+ }
1651
+ const value = entry.value.evaluate(context);
1652
+ return resolveMaybe(value, (resolvedValue) => {
1653
+ result[entry.key] = resolvedValue;
1654
+ return evalAt(i + 1);
1655
+ });
1247
1656
  }
1248
- }
1249
- return result;
1657
+ return result;
1658
+ };
1659
+ return evalAt(0);
1250
1660
  }
1251
1661
  };
1252
1662
  var IndexExpression = class extends BaseNode {
@@ -1255,16 +1665,30 @@ var IndexExpression = class extends BaseNode {
1255
1665
  this.target = target;
1256
1666
  this.index = index;
1257
1667
  }
1258
- async evaluate(context) {
1259
- const target = await this.target.evaluate(context);
1260
- if (target == null) {
1261
- return void 0;
1262
- }
1263
- const index = await this.index.evaluate(context);
1264
- if (index == null) {
1265
- return void 0;
1668
+ evaluate(context) {
1669
+ const target = this.target.evaluate(context);
1670
+ return resolveMaybe(target, (resolvedTarget) => {
1671
+ if (resolvedTarget == null) {
1672
+ return void 0;
1673
+ }
1674
+ const index = this.index.evaluate(context);
1675
+ return resolveMaybe(index, (resolvedIndex) => {
1676
+ if (resolvedIndex == null) {
1677
+ return void 0;
1678
+ }
1679
+ const key = this.normalizeIndexKey(resolvedTarget, resolvedIndex);
1680
+ return resolvedTarget[key];
1681
+ });
1682
+ });
1683
+ }
1684
+ normalizeIndexKey(target, index) {
1685
+ if (Array.isArray(target) && typeof index === "string" && index.trim() !== "") {
1686
+ const numeric = Number(index);
1687
+ if (!Number.isNaN(numeric)) {
1688
+ return numeric;
1689
+ }
1266
1690
  }
1267
- return target[index];
1691
+ return index;
1268
1692
  }
1269
1693
  };
1270
1694
  var DirectiveExpression = class extends BaseNode {
@@ -1273,7 +1697,7 @@ var DirectiveExpression = class extends BaseNode {
1273
1697
  this.kind = kind;
1274
1698
  this.name = name;
1275
1699
  }
1276
- async evaluate(context) {
1700
+ evaluate(context) {
1277
1701
  const element = context.element;
1278
1702
  if (!element) {
1279
1703
  return `${this.kind}:${this.name}`;
@@ -1287,6 +1711,12 @@ var DirectiveExpression = class extends BaseNode {
1287
1711
  return element.value;
1288
1712
  }
1289
1713
  }
1714
+ if (this.name === "text" && element instanceof HTMLElement) {
1715
+ return element.innerText;
1716
+ }
1717
+ if (this.name === "content" && element instanceof HTMLElement) {
1718
+ return element.textContent ?? "";
1719
+ }
1290
1720
  if (this.name === "checked" && element instanceof HTMLInputElement) {
1291
1721
  return element.checked;
1292
1722
  }
@@ -1306,9 +1736,9 @@ var AwaitExpression = class extends BaseNode {
1306
1736
  super("AwaitExpression");
1307
1737
  this.argument = argument;
1308
1738
  }
1309
- async evaluate(context) {
1310
- const value = await this.argument.evaluate(context);
1311
- return await value;
1739
+ evaluate(context) {
1740
+ const value = this.argument.evaluate(context);
1741
+ return Promise.resolve(value);
1312
1742
  }
1313
1743
  };
1314
1744
  var QueryExpression = class extends BaseNode {
@@ -1317,7 +1747,7 @@ var QueryExpression = class extends BaseNode {
1317
1747
  this.direction = direction;
1318
1748
  this.selector = selector;
1319
1749
  }
1320
- async evaluate(context) {
1750
+ evaluate(context) {
1321
1751
  const selector = this.selector.trim();
1322
1752
  if (!selector) {
1323
1753
  return [];
@@ -1383,6 +1813,9 @@ var TokenStream = class {
1383
1813
  let count = 0;
1384
1814
  for (let i = this.index; i < this.tokens.length; i++) {
1385
1815
  const token = this.tokens[i];
1816
+ if (!token) {
1817
+ continue;
1818
+ }
1386
1819
  if (token.type === "Whitespace" /* Whitespace */) {
1387
1820
  continue;
1388
1821
  }
@@ -1393,6 +1826,29 @@ var TokenStream = class {
1393
1826
  }
1394
1827
  return null;
1395
1828
  }
1829
+ indexAfterDelimited(openType, closeType, offset = 0) {
1830
+ const first = this.peekNonWhitespace(offset);
1831
+ if (!first || first.type !== openType) {
1832
+ return null;
1833
+ }
1834
+ let index = offset + 1;
1835
+ let depth = 1;
1836
+ while (true) {
1837
+ const token = this.peekNonWhitespace(index);
1838
+ if (!token) {
1839
+ return null;
1840
+ }
1841
+ if (token.type === openType) {
1842
+ depth += 1;
1843
+ } else if (token.type === closeType) {
1844
+ depth -= 1;
1845
+ if (depth === 0) {
1846
+ return index + 1;
1847
+ }
1848
+ }
1849
+ index += 1;
1850
+ }
1851
+ }
1396
1852
  };
1397
1853
 
1398
1854
  // src/parser/parser.ts
@@ -1400,11 +1856,14 @@ var Parser = class _Parser {
1400
1856
  stream;
1401
1857
  source;
1402
1858
  customFlags;
1859
+ behaviorFlags;
1403
1860
  allowImplicitSemicolon = false;
1404
1861
  awaitStack = [];
1862
+ functionDepth = 0;
1405
1863
  constructor(input, options) {
1406
1864
  this.source = input;
1407
- this.customFlags = options?.customFlags ?? /* @__PURE__ */ new Set();
1865
+ this.customFlags = options?.customFlags ?? /* @__PURE__ */ new Set(["important", "trusted", "debounce"]);
1866
+ this.behaviorFlags = options?.behaviorFlags ?? /* @__PURE__ */ new Set();
1408
1867
  const lexer = new Lexer(input);
1409
1868
  this.stream = new TokenStream(lexer.tokenize());
1410
1869
  }
@@ -1444,8 +1903,9 @@ var Parser = class _Parser {
1444
1903
  this.stream.skipWhitespace();
1445
1904
  this.stream.expect("Behavior" /* Behavior */);
1446
1905
  const selector = this.parseSelector();
1906
+ const { flags, flagArgs } = this.parseBehaviorFlags();
1447
1907
  const body = this.parseBlock({ allowDeclarations: true });
1448
- return new BehaviorNode(selector, body);
1908
+ return new BehaviorNode(selector, body, flags, flagArgs);
1449
1909
  });
1450
1910
  }
1451
1911
  parseSelector() {
@@ -1459,6 +1919,9 @@ var Parser = class _Parser {
1459
1919
  if (token.type === "LBrace" /* LBrace */) {
1460
1920
  break;
1461
1921
  }
1922
+ if (token.type === "Bang" /* Bang */) {
1923
+ break;
1924
+ }
1462
1925
  if (token.type === "Whitespace" /* Whitespace */) {
1463
1926
  this.stream.next();
1464
1927
  if (sawNonWhitespace && selectorText[selectorText.length - 1] !== " ") {
@@ -1474,6 +1937,10 @@ var Parser = class _Parser {
1474
1937
  }
1475
1938
  return new SelectorNode(selectorText.trim());
1476
1939
  }
1940
+ parseBehaviorFlags() {
1941
+ const result = this.parseFlags(this.behaviorFlags, "behavior modifier");
1942
+ return { flags: result.flags, flagArgs: result.flagArgs };
1943
+ }
1477
1944
  parseUseStatement() {
1478
1945
  return this.wrapErrors(() => {
1479
1946
  this.stream.expect("Use" /* Use */);
@@ -1557,6 +2024,7 @@ ${caret}`;
1557
2024
  }
1558
2025
  parseBlock(options) {
1559
2026
  const allowDeclarations = options?.allowDeclarations ?? false;
2027
+ const allowReturn = options?.allowReturn ?? this.functionDepth > 0;
1560
2028
  this.stream.skipWhitespace();
1561
2029
  this.stream.expect("LBrace" /* LBrace */);
1562
2030
  const statements = [];
@@ -1618,7 +2086,7 @@ ${caret}`;
1618
2086
  }
1619
2087
  sawConstruct = true;
1620
2088
  }
1621
- statements.push(this.parseStatement());
2089
+ statements.push(this.parseStatement({ allowReturn }));
1622
2090
  }
1623
2091
  }
1624
2092
  return new BlockNode(statements);
@@ -1637,6 +2105,15 @@ ${caret}`;
1637
2105
  }
1638
2106
  return this.parseReturnStatement();
1639
2107
  }
2108
+ if (next.type === "Assert" /* Assert */) {
2109
+ return this.parseAssertStatement();
2110
+ }
2111
+ if (next.type === "Break" /* Break */) {
2112
+ return this.parseBreakStatement();
2113
+ }
2114
+ if (next.type === "Continue" /* Continue */) {
2115
+ return this.parseContinueStatement();
2116
+ }
1640
2117
  if (allowBlocks && next.type === "On" /* On */) {
1641
2118
  return this.parseOnBlock();
1642
2119
  }
@@ -1672,44 +2149,6 @@ ${caret}`;
1672
2149
  }
1673
2150
  throw new Error(`Unexpected token ${next.type}`);
1674
2151
  }
1675
- parseStateBlock() {
1676
- this.stream.expect("State" /* State */);
1677
- this.stream.skipWhitespace();
1678
- this.stream.expect("LBrace" /* LBrace */);
1679
- const entries = [];
1680
- while (true) {
1681
- this.stream.skipWhitespace();
1682
- const next = this.stream.peek();
1683
- if (!next) {
1684
- throw new Error("Unterminated state block");
1685
- }
1686
- if (next.type === "RBrace" /* RBrace */) {
1687
- this.stream.next();
1688
- break;
1689
- }
1690
- const nameToken = this.stream.expect("Identifier" /* Identifier */);
1691
- this.stream.skipWhitespace();
1692
- this.stream.expect("Colon" /* Colon */);
1693
- this.stream.skipWhitespace();
1694
- const value = this.parseExpression();
1695
- this.stream.skipWhitespace();
1696
- let important = false;
1697
- if (this.stream.peek()?.type === "Bang" /* Bang */) {
1698
- this.stream.next();
1699
- this.stream.skipWhitespace();
1700
- const importantToken = this.stream.next();
1701
- if (importantToken.type === "Identifier" /* Identifier */ && importantToken.value === "important") {
1702
- important = true;
1703
- } else {
1704
- throw new Error("Expected 'important' after '!'");
1705
- }
1706
- }
1707
- this.stream.skipWhitespace();
1708
- this.stream.expect("Semicolon" /* Semicolon */);
1709
- entries.push(new StateEntryNode(nameToken.value, value, important));
1710
- }
1711
- return new StateBlockNode(entries);
1712
- }
1713
2152
  parseOnBlock() {
1714
2153
  this.stream.expect("On" /* On */);
1715
2154
  this.stream.skipWhitespace();
@@ -1737,22 +2176,9 @@ ${caret}`;
1737
2176
  }
1738
2177
  throw new Error(`Unexpected token in on() args: ${next.type}`);
1739
2178
  }
1740
- const modifiers = this.parseOnModifiers();
2179
+ const { flags, flagArgs } = this.parseFlags(this.customFlags, "flag");
1741
2180
  const body = this.parseBlock({ allowDeclarations: false });
1742
- return new OnBlockNode(event, args, body, modifiers);
1743
- }
1744
- parseOnModifiers() {
1745
- const modifiers = [];
1746
- while (true) {
1747
- this.stream.skipWhitespace();
1748
- if (this.stream.peek()?.type !== "Bang" /* Bang */) {
1749
- break;
1750
- }
1751
- this.stream.next();
1752
- const name = this.stream.expect("Identifier" /* Identifier */).value;
1753
- modifiers.push(name);
1754
- }
1755
- return modifiers;
2181
+ return new OnBlockNode(event, args, body, flags, flagArgs);
1756
2182
  }
1757
2183
  parseAssignment() {
1758
2184
  const target = this.parseAssignmentTarget();
@@ -1962,6 +2388,11 @@ ${caret}`;
1962
2388
  if (!token) {
1963
2389
  throw new Error("Expected expression");
1964
2390
  }
2391
+ if (token.type === "PlusPlus" /* PlusPlus */ || token.type === "MinusMinus" /* MinusMinus */) {
2392
+ this.stream.next();
2393
+ const argument = this.parseUnaryExpression();
2394
+ return this.createIncrementNode(token, argument, true);
2395
+ }
1965
2396
  if (token.type === "Bang" /* Bang */) {
1966
2397
  this.stream.next();
1967
2398
  const argument = this.parseUnaryExpression();
@@ -1977,7 +2408,31 @@ ${caret}`;
1977
2408
  const argument = this.parseUnaryExpression();
1978
2409
  return new AwaitExpression(argument);
1979
2410
  }
1980
- return this.parseCallExpression();
2411
+ return this.parsePostfixExpression();
2412
+ }
2413
+ parsePostfixExpression() {
2414
+ let expr = this.parseCallExpression();
2415
+ while (true) {
2416
+ this.stream.skipWhitespace();
2417
+ const token = this.stream.peek();
2418
+ if (!token) {
2419
+ break;
2420
+ }
2421
+ if (token.type === "PlusPlus" /* PlusPlus */ || token.type === "MinusMinus" /* MinusMinus */) {
2422
+ this.stream.next();
2423
+ expr = this.createIncrementNode(token, expr, false);
2424
+ continue;
2425
+ }
2426
+ break;
2427
+ }
2428
+ return expr;
2429
+ }
2430
+ createIncrementNode(token, argument, prefix) {
2431
+ if (!(argument instanceof IdentifierExpression) && !(argument instanceof MemberExpression) && !(argument instanceof IndexExpression) && !(argument instanceof DirectiveExpression)) {
2432
+ throw new Error("Increment/decrement requires a mutable target");
2433
+ }
2434
+ const operator = token.type === "PlusPlus" /* PlusPlus */ ? "++" : "--";
2435
+ return new AssignmentNode(argument, new LiteralExpression(1), operator, prefix);
1981
2436
  }
1982
2437
  parseCallExpression() {
1983
2438
  let expr = this.parsePrimaryExpression();
@@ -2289,6 +2744,7 @@ ${caret}`;
2289
2744
  this.stream.expect("LBrace" /* LBrace */);
2290
2745
  const statements = [];
2291
2746
  this.awaitStack.push(allowAwait);
2747
+ this.functionDepth += 1;
2292
2748
  try {
2293
2749
  while (true) {
2294
2750
  this.stream.skipWhitespace();
@@ -2300,9 +2756,10 @@ ${caret}`;
2300
2756
  this.stream.next();
2301
2757
  break;
2302
2758
  }
2303
- statements.push(this.parseStatement({ allowBlocks: false, allowReturn: true }));
2759
+ statements.push(this.parseStatement({ allowBlocks: true, allowReturn: true }));
2304
2760
  }
2305
2761
  } finally {
2762
+ this.functionDepth -= 1;
2306
2763
  this.awaitStack.pop();
2307
2764
  }
2308
2765
  return new BlockNode(statements);
@@ -2498,7 +2955,7 @@ ${caret}`;
2498
2955
  const operator = this.parseDeclarationOperator();
2499
2956
  this.stream.skipWhitespace();
2500
2957
  const value = this.parseExpression();
2501
- const { flags, flagArgs } = this.parseFlags();
2958
+ const { flags, flagArgs } = this.parseFlags(this.customFlags, "flag");
2502
2959
  this.stream.skipWhitespace();
2503
2960
  this.stream.expect("Semicolon" /* Semicolon */);
2504
2961
  return new DeclarationNode(target, operator, value, flags, flagArgs);
@@ -2539,7 +2996,7 @@ ${caret}`;
2539
2996
  }
2540
2997
  return ":";
2541
2998
  }
2542
- parseFlags() {
2999
+ parseFlags(allowed, errorLabel) {
2543
3000
  const flags = {};
2544
3001
  const flagArgs = {};
2545
3002
  while (true) {
@@ -2549,59 +3006,107 @@ ${caret}`;
2549
3006
  }
2550
3007
  this.stream.next();
2551
3008
  const name = this.stream.expect("Identifier" /* Identifier */).value;
2552
- if (name === "important") {
2553
- flags.important = true;
2554
- } else if (name === "trusted") {
2555
- flags.trusted = true;
2556
- } else if (name === "debounce") {
2557
- flags.debounce = true;
2558
- if (this.stream.peek()?.type === "LParen" /* LParen */) {
2559
- this.stream.next();
2560
- this.stream.skipWhitespace();
2561
- const numberToken = this.stream.expect("Number" /* Number */);
2562
- flagArgs.debounce = Number(numberToken.value);
2563
- this.stream.skipWhitespace();
2564
- this.stream.expect("RParen" /* RParen */);
2565
- } else {
2566
- flagArgs.debounce = 200;
2567
- }
2568
- } else if (this.customFlags.has(name)) {
2569
- flags[name] = true;
2570
- const customArg = this.parseCustomFlagArg();
2571
- if (customArg !== void 0) {
2572
- flagArgs[name] = customArg;
2573
- }
3009
+ if (allowed && !allowed.has(name)) {
3010
+ throw new Error(`Unknown ${errorLabel} ${name}`);
3011
+ }
3012
+ flags[name] = true;
3013
+ const customArg = this.parseCustomFlagArg();
3014
+ if (customArg !== void 0) {
3015
+ flagArgs[name] = customArg;
3016
+ }
3017
+ }
3018
+ return { flags, flagArgs };
3019
+ }
3020
+ parseCustomFlagArg() {
3021
+ if (this.stream.peek()?.type !== "LParen" /* LParen */) {
3022
+ return void 0;
3023
+ }
3024
+ this.stream.next();
3025
+ this.stream.skipWhitespace();
3026
+ const token = this.stream.peek();
3027
+ if (!token) {
3028
+ throw new Error("Unterminated flag arguments");
3029
+ }
3030
+ const value = this.parseCustomFlagLiteral();
3031
+ this.stream.skipWhitespace();
3032
+ this.stream.expect("RParen" /* RParen */);
3033
+ return value;
3034
+ }
3035
+ parseCustomFlagLiteral() {
3036
+ const token = this.stream.peek();
3037
+ if (!token) {
3038
+ throw new Error("Unterminated flag arguments");
3039
+ }
3040
+ if (token.type === "Number" /* Number */) {
3041
+ return Number(this.stream.next().value);
3042
+ }
3043
+ if (token.type === "String" /* String */) {
3044
+ return this.stream.next().value;
3045
+ }
3046
+ if (token.type === "Boolean" /* Boolean */) {
3047
+ return this.stream.next().value === "true";
3048
+ }
3049
+ if (token.type === "Identifier" /* Identifier */) {
3050
+ return this.stream.next().value;
3051
+ }
3052
+ if (token.type === "LBracket" /* LBracket */) {
3053
+ return this.parseCustomFlagArray();
3054
+ }
3055
+ if (token.type === "LBrace" /* LBrace */) {
3056
+ return this.parseCustomFlagObject();
3057
+ }
3058
+ throw new Error(`Unsupported flag argument ${token.type}`);
3059
+ }
3060
+ parseCustomFlagArray() {
3061
+ this.stream.expect("LBracket" /* LBracket */);
3062
+ const items = [];
3063
+ while (true) {
3064
+ this.stream.skipWhitespace();
3065
+ const next = this.stream.peek();
3066
+ if (!next) {
3067
+ throw new Error("Unterminated flag array");
3068
+ }
3069
+ if (next.type === "RBracket" /* RBracket */) {
3070
+ this.stream.next();
3071
+ break;
3072
+ }
3073
+ items.push(this.parseCustomFlagLiteral());
3074
+ this.stream.skipWhitespace();
3075
+ if (this.stream.peek()?.type === "Comma" /* Comma */) {
3076
+ this.stream.next();
3077
+ }
3078
+ }
3079
+ return items;
3080
+ }
3081
+ parseCustomFlagObject() {
3082
+ this.stream.expect("LBrace" /* LBrace */);
3083
+ const obj = {};
3084
+ while (true) {
3085
+ this.stream.skipWhitespace();
3086
+ const next = this.stream.peek();
3087
+ if (!next) {
3088
+ throw new Error("Unterminated flag object");
3089
+ }
3090
+ if (next.type === "RBrace" /* RBrace */) {
3091
+ this.stream.next();
3092
+ break;
3093
+ }
3094
+ let key;
3095
+ if (next.type === "Identifier" /* Identifier */ || next.type === "String" /* String */) {
3096
+ key = this.stream.next().value;
2574
3097
  } else {
2575
- throw new Error(`Unknown flag ${name}`);
3098
+ throw new Error(`Unsupported flag object key ${next.type}`);
3099
+ }
3100
+ this.stream.skipWhitespace();
3101
+ this.stream.expect("Colon" /* Colon */);
3102
+ this.stream.skipWhitespace();
3103
+ obj[key] = this.parseCustomFlagLiteral();
3104
+ this.stream.skipWhitespace();
3105
+ if (this.stream.peek()?.type === "Comma" /* Comma */) {
3106
+ this.stream.next();
2576
3107
  }
2577
3108
  }
2578
- return { flags, flagArgs };
2579
- }
2580
- parseCustomFlagArg() {
2581
- if (this.stream.peek()?.type !== "LParen" /* LParen */) {
2582
- return void 0;
2583
- }
2584
- this.stream.next();
2585
- this.stream.skipWhitespace();
2586
- const token = this.stream.peek();
2587
- if (!token) {
2588
- throw new Error("Unterminated flag arguments");
2589
- }
2590
- let value;
2591
- if (token.type === "Number" /* Number */) {
2592
- value = Number(this.stream.next().value);
2593
- } else if (token.type === "String" /* String */) {
2594
- value = this.stream.next().value;
2595
- } else if (token.type === "Boolean" /* Boolean */) {
2596
- value = this.stream.next().value === "true";
2597
- } else if (token.type === "Identifier" /* Identifier */) {
2598
- value = this.stream.next().value;
2599
- } else {
2600
- throw new Error(`Unsupported flag argument ${token.type}`);
2601
- }
2602
- this.stream.skipWhitespace();
2603
- this.stream.expect("RParen" /* RParen */);
2604
- return value;
3109
+ return obj;
2605
3110
  }
2606
3111
  isDeclarationStart() {
2607
3112
  const first = this.stream.peekNonWhitespace(0);
@@ -2626,33 +3131,29 @@ ${caret}`;
2626
3131
  }
2627
3132
  if (first.type === "Identifier" /* Identifier */) {
2628
3133
  let index = 1;
2629
- while (this.stream.peekNonWhitespace(index)?.type === "Dot" /* Dot */ && this.stream.peekNonWhitespace(index + 1)?.type === "Identifier" /* Identifier */) {
2630
- index += 2;
2631
- }
2632
- while (this.stream.peekNonWhitespace(index)?.type === "LBracket" /* LBracket */) {
2633
- let depth = 0;
2634
- while (true) {
2635
- const token = this.stream.peekNonWhitespace(index);
2636
- if (!token) {
3134
+ while (true) {
3135
+ const token = this.stream.peekNonWhitespace(index);
3136
+ if (!token) {
3137
+ return false;
3138
+ }
3139
+ if (token.type === "Dot" /* Dot */ && this.stream.peekNonWhitespace(index + 1)?.type === "Identifier" /* Identifier */) {
3140
+ index += 2;
3141
+ continue;
3142
+ }
3143
+ if (token.type === "LBracket" /* LBracket */) {
3144
+ const indexAfter = this.stream.indexAfterDelimited("LBracket" /* LBracket */, "RBracket" /* RBracket */, index);
3145
+ if (indexAfter === null) {
2637
3146
  return false;
2638
3147
  }
2639
- if (token.type === "LBracket" /* LBracket */) {
2640
- depth += 1;
2641
- } else if (token.type === "RBracket" /* RBracket */) {
2642
- depth -= 1;
2643
- if (depth === 0) {
2644
- index += 1;
2645
- break;
2646
- }
2647
- }
2648
- index += 1;
3148
+ index = indexAfter;
3149
+ continue;
2649
3150
  }
3151
+ break;
2650
3152
  }
2651
3153
  return this.isAssignmentOperatorStart(index);
2652
3154
  }
2653
3155
  if (first.type === "At" /* At */ || first.type === "Dollar" /* Dollar */) {
2654
3156
  const second = this.stream.peekNonWhitespace(1);
2655
- const third = this.stream.peekNonWhitespace(2);
2656
3157
  return second?.type === "Identifier" /* Identifier */ && this.isAssignmentOperatorStart(2);
2657
3158
  }
2658
3159
  if (first.type === "LBrace" /* LBrace */ || first.type === "LBracket" /* LBracket */) {
@@ -2690,17 +3191,6 @@ ${caret}`;
2690
3191
  }
2691
3192
  return false;
2692
3193
  }
2693
- isCallStart() {
2694
- const first = this.stream.peekNonWhitespace(0);
2695
- if (!first || first.type !== "Identifier" /* Identifier */) {
2696
- return false;
2697
- }
2698
- let index = 1;
2699
- while (this.stream.peekNonWhitespace(index)?.type === "Dot" /* Dot */ && this.stream.peekNonWhitespace(index + 1)?.type === "Identifier" /* Identifier */) {
2700
- index += 2;
2701
- }
2702
- return this.stream.peekNonWhitespace(index)?.type === "LParen" /* LParen */;
2703
- }
2704
3194
  isExpressionStatementStart() {
2705
3195
  const first = this.stream.peekNonWhitespace(0);
2706
3196
  if (!first) {
@@ -2730,50 +3220,22 @@ ${caret}`;
2730
3220
  if (this.stream.peekNonWhitespace(index)?.type !== "LParen" /* LParen */) {
2731
3221
  return false;
2732
3222
  }
2733
- index += 1;
2734
- let depth = 1;
2735
- while (true) {
2736
- const token = this.stream.peekNonWhitespace(index);
2737
- if (!token) {
2738
- return false;
2739
- }
2740
- if (token.type === "LParen" /* LParen */) {
2741
- depth += 1;
2742
- } else if (token.type === "RParen" /* RParen */) {
2743
- depth -= 1;
2744
- if (depth === 0) {
2745
- index += 1;
2746
- break;
2747
- }
2748
- }
2749
- index += 1;
3223
+ const indexAfterParams = this.stream.indexAfterDelimited("LParen" /* LParen */, "RParen" /* RParen */, index);
3224
+ if (indexAfterParams === null) {
3225
+ return false;
2750
3226
  }
2751
- return this.stream.peekNonWhitespace(index)?.type === "LBrace" /* LBrace */;
3227
+ return this.stream.peekNonWhitespace(indexAfterParams)?.type === "LBrace" /* LBrace */;
2752
3228
  }
2753
3229
  isArrowFunctionStart() {
2754
3230
  const first = this.stream.peekNonWhitespace(0);
2755
3231
  if (!first || first.type !== "LParen" /* LParen */) {
2756
3232
  return false;
2757
3233
  }
2758
- let index = 1;
2759
- let depth = 1;
2760
- while (true) {
2761
- const token = this.stream.peekNonWhitespace(index);
2762
- if (!token) {
2763
- return false;
2764
- }
2765
- if (token.type === "LParen" /* LParen */) {
2766
- depth += 1;
2767
- } else if (token.type === "RParen" /* RParen */) {
2768
- depth -= 1;
2769
- if (depth === 0) {
2770
- index += 1;
2771
- break;
2772
- }
2773
- }
2774
- index += 1;
3234
+ const indexAfterParams = this.stream.indexAfterDelimited("LParen" /* LParen */, "RParen" /* RParen */, 0);
3235
+ if (indexAfterParams === null) {
3236
+ return false;
2775
3237
  }
2776
- return this.stream.peekNonWhitespace(index)?.type === "Arrow" /* Arrow */;
3238
+ return this.stream.peekNonWhitespace(indexAfterParams)?.type === "Arrow" /* Arrow */;
2777
3239
  }
2778
3240
  isAsyncArrowFunctionStart() {
2779
3241
  const first = this.stream.peekNonWhitespace(0);
@@ -2783,25 +3245,11 @@ ${caret}`;
2783
3245
  if (this.stream.peekNonWhitespace(1)?.type !== "LParen" /* LParen */) {
2784
3246
  return false;
2785
3247
  }
2786
- let index = 2;
2787
- let depth = 1;
2788
- while (true) {
2789
- const token = this.stream.peekNonWhitespace(index);
2790
- if (!token) {
2791
- return false;
2792
- }
2793
- if (token.type === "LParen" /* LParen */) {
2794
- depth += 1;
2795
- } else if (token.type === "RParen" /* RParen */) {
2796
- depth -= 1;
2797
- if (depth === 0) {
2798
- index += 1;
2799
- break;
2800
- }
2801
- }
2802
- index += 1;
3248
+ const indexAfterParams = this.stream.indexAfterDelimited("LParen" /* LParen */, "RParen" /* RParen */, 1);
3249
+ if (indexAfterParams === null) {
3250
+ return false;
2803
3251
  }
2804
- return this.stream.peekNonWhitespace(index)?.type === "Arrow" /* Arrow */;
3252
+ return this.stream.peekNonWhitespace(indexAfterParams)?.type === "Arrow" /* Arrow */;
2805
3253
  }
2806
3254
  isFunctionExpressionAssignmentStart() {
2807
3255
  const first = this.stream.peekNonWhitespace(0);
@@ -2818,25 +3266,11 @@ ${caret}`;
2818
3266
  if (this.stream.peekNonWhitespace(index)?.type !== "LParen" /* LParen */) {
2819
3267
  return false;
2820
3268
  }
2821
- index += 1;
2822
- let depth = 1;
2823
- while (true) {
2824
- const token = this.stream.peekNonWhitespace(index);
2825
- if (!token) {
2826
- return false;
2827
- }
2828
- if (token.type === "LParen" /* LParen */) {
2829
- depth += 1;
2830
- } else if (token.type === "RParen" /* RParen */) {
2831
- depth -= 1;
2832
- if (depth === 0) {
2833
- index += 1;
2834
- break;
2835
- }
2836
- }
2837
- index += 1;
3269
+ const indexAfterParams = this.stream.indexAfterDelimited("LParen" /* LParen */, "RParen" /* RParen */, index);
3270
+ if (indexAfterParams === null) {
3271
+ return false;
2838
3272
  }
2839
- return this.stream.peekNonWhitespace(index)?.type === "Arrow" /* Arrow */;
3273
+ return this.stream.peekNonWhitespace(indexAfterParams)?.type === "Arrow" /* Arrow */;
2840
3274
  }
2841
3275
  parseExpressionStatement() {
2842
3276
  const expr = this.parseExpression();
@@ -2851,7 +3285,7 @@ ${caret}`;
2851
3285
  const test = this.parseExpression();
2852
3286
  this.stream.skipWhitespace();
2853
3287
  this.stream.expect("RParen" /* RParen */);
2854
- const consequent = this.parseBlock({ allowDeclarations: false });
3288
+ const consequent = this.parseConditionalBody();
2855
3289
  this.stream.skipWhitespace();
2856
3290
  let alternate;
2857
3291
  if (this.stream.peek()?.type === "Else" /* Else */) {
@@ -2861,11 +3295,19 @@ ${caret}`;
2861
3295
  const nested = this.parseIfBlock();
2862
3296
  alternate = new BlockNode([nested]);
2863
3297
  } else {
2864
- alternate = this.parseBlock({ allowDeclarations: false });
3298
+ alternate = this.parseConditionalBody();
2865
3299
  }
2866
3300
  }
2867
3301
  return new IfNode(test, consequent, alternate);
2868
3302
  }
3303
+ parseConditionalBody() {
3304
+ this.stream.skipWhitespace();
3305
+ if (this.stream.peek()?.type === "LBrace" /* LBrace */) {
3306
+ return this.parseBlock({ allowDeclarations: false });
3307
+ }
3308
+ const statement = this.parseStatement({ allowBlocks: false, allowReturn: this.functionDepth > 0 });
3309
+ return new BlockNode([statement]);
3310
+ }
2869
3311
  parseWhileBlock() {
2870
3312
  this.stream.expect("While" /* While */);
2871
3313
  this.stream.skipWhitespace();
@@ -2882,6 +3324,21 @@ ${caret}`;
2882
3324
  this.stream.skipWhitespace();
2883
3325
  this.stream.expect("LParen" /* LParen */);
2884
3326
  this.stream.skipWhitespace();
3327
+ const eachKind = this.detectForEachKind();
3328
+ if (eachKind) {
3329
+ const target = this.parseForEachTarget();
3330
+ this.stream.skipWhitespace();
3331
+ const keyword = this.stream.expect("Identifier" /* Identifier */);
3332
+ if (keyword.value !== eachKind) {
3333
+ throw new Error(`Expected '${eachKind}' but got '${keyword.value}'`);
3334
+ }
3335
+ this.stream.skipWhitespace();
3336
+ const iterable = this.parseExpression();
3337
+ this.stream.skipWhitespace();
3338
+ this.stream.expect("RParen" /* RParen */);
3339
+ const body2 = this.parseBlock({ allowDeclarations: false });
3340
+ return new ForEachNode(target, iterable, eachKind, body2);
3341
+ }
2885
3342
  let init;
2886
3343
  if (this.stream.peek()?.type !== "Semicolon" /* Semicolon */) {
2887
3344
  init = this.parseForClause();
@@ -2905,6 +3362,43 @@ ${caret}`;
2905
3362
  const body = this.parseBlock({ allowDeclarations: false });
2906
3363
  return new ForNode(init, test, update, body);
2907
3364
  }
3365
+ detectForEachKind() {
3366
+ let offset = 0;
3367
+ let depth = 0;
3368
+ while (true) {
3369
+ const token = this.stream.peekNonWhitespace(offset);
3370
+ if (!token) {
3371
+ return null;
3372
+ }
3373
+ if (token.type === "LParen" /* LParen */ || token.type === "LBracket" /* LBracket */ || token.type === "LBrace" /* LBrace */) {
3374
+ depth += 1;
3375
+ } else if (token.type === "RParen" /* RParen */ || token.type === "RBracket" /* RBracket */ || token.type === "RBrace" /* RBrace */) {
3376
+ if (depth === 0) {
3377
+ return null;
3378
+ }
3379
+ depth -= 1;
3380
+ }
3381
+ if (depth === 0) {
3382
+ if (token.type === "Semicolon" /* Semicolon */) {
3383
+ return null;
3384
+ }
3385
+ if (token.type === "Identifier" /* Identifier */ && (token.value === "in" || token.value === "of")) {
3386
+ return token.value;
3387
+ }
3388
+ }
3389
+ offset += 1;
3390
+ }
3391
+ }
3392
+ parseForEachTarget() {
3393
+ const token = this.stream.peek();
3394
+ if (!token) {
3395
+ throw new Error("Expected for-each target");
3396
+ }
3397
+ if (token.type !== "Identifier" /* Identifier */) {
3398
+ throw new Error("for-in/of target must be an identifier");
3399
+ }
3400
+ return new IdentifierExpression(this.stream.next().value);
3401
+ }
2908
3402
  parseForClause() {
2909
3403
  if (this.isAssignmentStart()) {
2910
3404
  return this.parseAssignmentExpression();
@@ -3000,9 +3494,6 @@ ${caret}`;
3000
3494
  const body = this.parseFunctionBlockWithAwait(isAsync);
3001
3495
  return new FunctionDeclarationNode(name, params, body, isAsync);
3002
3496
  }
3003
- parseFunctionBlock() {
3004
- return this.parseFunctionBlockWithAwait(false);
3005
- }
3006
3497
  parseReturnStatement() {
3007
3498
  this.stream.expect("Return" /* Return */);
3008
3499
  this.stream.skipWhitespace();
@@ -3015,6 +3506,23 @@ ${caret}`;
3015
3506
  this.stream.expect("Semicolon" /* Semicolon */);
3016
3507
  return new ReturnNode(value);
3017
3508
  }
3509
+ parseAssertStatement() {
3510
+ this.stream.expect("Assert" /* Assert */);
3511
+ this.stream.skipWhitespace();
3512
+ const test = this.parseExpression();
3513
+ this.consumeStatementTerminator();
3514
+ return new AssertNode(test);
3515
+ }
3516
+ parseBreakStatement() {
3517
+ this.stream.expect("Break" /* Break */);
3518
+ this.consumeStatementTerminator();
3519
+ return new BreakNode();
3520
+ }
3521
+ parseContinueStatement() {
3522
+ this.stream.expect("Continue" /* Continue */);
3523
+ this.consumeStatementTerminator();
3524
+ return new ContinueNode();
3525
+ }
3018
3526
  parseArrowFunctionExpression(isAsync = false) {
3019
3527
  const params = this.parseFunctionParams();
3020
3528
  this.stream.skipWhitespace();
@@ -3124,6 +3632,7 @@ var Scope = class _Scope {
3124
3632
  root;
3125
3633
  listeners = /* @__PURE__ */ new Map();
3126
3634
  anyListeners = /* @__PURE__ */ new Set();
3635
+ isEachItem = false;
3127
3636
  createChild() {
3128
3637
  return new _Scope(this);
3129
3638
  }
@@ -3426,18 +3935,19 @@ var Engine = class _Engine {
3426
3935
  eachBindings = /* @__PURE__ */ new WeakMap();
3427
3936
  lifecycleBindings = /* @__PURE__ */ new WeakMap();
3428
3937
  behaviorRegistry = [];
3938
+ behaviorRegistryHashes = /* @__PURE__ */ new Set();
3429
3939
  behaviorBindings = /* @__PURE__ */ new WeakMap();
3430
3940
  behaviorListeners = /* @__PURE__ */ new WeakMap();
3431
3941
  behaviorId = 0;
3432
3942
  codeCache = /* @__PURE__ */ new Map();
3433
3943
  behaviorCache = /* @__PURE__ */ new Map();
3434
3944
  observer;
3435
- observerRoot;
3436
3945
  attributeHandlers = [];
3437
3946
  globals = {};
3438
3947
  importantFlags = /* @__PURE__ */ new WeakMap();
3439
3948
  inlineDeclarations = /* @__PURE__ */ new WeakMap();
3440
3949
  flagHandlers = /* @__PURE__ */ new Map();
3950
+ behaviorModifiers = /* @__PURE__ */ new Map();
3441
3951
  pendingAdded = /* @__PURE__ */ new Set();
3442
3952
  pendingRemoved = /* @__PURE__ */ new Set();
3443
3953
  pendingUpdated = /* @__PURE__ */ new Set();
@@ -3446,10 +3956,119 @@ var Engine = class _Engine {
3446
3956
  diagnostics;
3447
3957
  logger;
3448
3958
  pendingUses = [];
3959
+ pendingAutoBindToScope = [];
3960
+ scopeWatchers = /* @__PURE__ */ new WeakMap();
3961
+ executionStack = [];
3962
+ groupProxyCache = /* @__PURE__ */ new WeakMap();
3449
3963
  constructor(options = {}) {
3450
3964
  this.diagnostics = options.diagnostics ?? false;
3451
3965
  this.logger = options.logger ?? console;
3452
3966
  this.registerGlobal("console", console);
3967
+ this.registerFlag("important");
3968
+ this.registerFlag("trusted");
3969
+ this.registerFlag("debounce", {
3970
+ onEventBind: ({ args }) => ({
3971
+ debounceMs: typeof args === "number" ? args : 200
3972
+ })
3973
+ });
3974
+ this.registerFlag("prevent", {
3975
+ onEventBefore: ({ event }) => {
3976
+ event?.preventDefault();
3977
+ }
3978
+ });
3979
+ this.registerFlag("stop", {
3980
+ onEventBefore: ({ event }) => {
3981
+ event?.stopPropagation();
3982
+ }
3983
+ });
3984
+ this.registerFlag("self", {
3985
+ onEventBefore: ({ event, element }) => {
3986
+ const target = event?.target;
3987
+ if (!(target instanceof Node)) {
3988
+ return false;
3989
+ }
3990
+ return target === element;
3991
+ }
3992
+ });
3993
+ this.registerFlag("outside", {
3994
+ onEventBind: ({ element }) => ({ listenerTarget: element.ownerDocument }),
3995
+ onEventBefore: ({ event, element }) => {
3996
+ const target = event?.target;
3997
+ if (!(target instanceof Node)) {
3998
+ return false;
3999
+ }
4000
+ return !element.contains(target);
4001
+ }
4002
+ });
4003
+ this.registerFlag("once", {
4004
+ onEventBind: () => ({ options: { once: true } })
4005
+ });
4006
+ this.registerFlag("passive", {
4007
+ onEventBind: () => ({ options: { passive: true } })
4008
+ });
4009
+ this.registerFlag("capture", {
4010
+ onEventBind: () => ({ options: { capture: true } })
4011
+ });
4012
+ this.registerFlag("shift", {
4013
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "shift")
4014
+ });
4015
+ this.registerFlag("ctrl", {
4016
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "ctrl")
4017
+ });
4018
+ this.registerFlag("control", {
4019
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "ctrl")
4020
+ });
4021
+ this.registerFlag("alt", {
4022
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "alt")
4023
+ });
4024
+ this.registerFlag("meta", {
4025
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "meta")
4026
+ });
4027
+ this.registerFlag("enter", {
4028
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "enter")
4029
+ });
4030
+ this.registerFlag("escape", {
4031
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "escape")
4032
+ });
4033
+ this.registerFlag("esc", {
4034
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "escape")
4035
+ });
4036
+ this.registerFlag("tab", {
4037
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "tab")
4038
+ });
4039
+ this.registerFlag("space", {
4040
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "space")
4041
+ });
4042
+ this.registerFlag("up", {
4043
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowup")
4044
+ });
4045
+ this.registerFlag("down", {
4046
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowdown")
4047
+ });
4048
+ this.registerFlag("left", {
4049
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowleft")
4050
+ });
4051
+ this.registerFlag("right", {
4052
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowright")
4053
+ });
4054
+ this.registerFlag("arrowup", {
4055
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowup")
4056
+ });
4057
+ this.registerFlag("arrowdown", {
4058
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowdown")
4059
+ });
4060
+ this.registerFlag("arrowleft", {
4061
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowleft")
4062
+ });
4063
+ this.registerFlag("arrowright", {
4064
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "arrowright")
4065
+ });
4066
+ this.registerFlag("delete", {
4067
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "delete")
4068
+ });
4069
+ this.registerFlag("backspace", {
4070
+ onEventBefore: ({ event }) => this.matchesKeyFlag(event, "backspace")
4071
+ });
3453
4072
  this.registerGlobal("list", {
3454
4073
  async map(items, fn) {
3455
4074
  if (!Array.isArray(items) || typeof fn !== "function") {
@@ -3487,6 +4106,96 @@ var Engine = class _Engine {
3487
4106
  }
3488
4107
  });
3489
4108
  this.registerDefaultAttributeHandlers();
4109
+ this.registerFlag("int", {
4110
+ transformValue: (_context, value) => this.coerceInt(value)
4111
+ });
4112
+ this.registerFlag("float", {
4113
+ transformValue: (_context, value) => this.coerceFloat(value)
4114
+ });
4115
+ this.registerBehaviorModifier("group", {
4116
+ onConstruct: ({ args, scope, rootScope, behavior, element }) => {
4117
+ const key = typeof args === "string" ? args : void 0;
4118
+ if (!key) {
4119
+ return;
4120
+ }
4121
+ const targetScope = this.getGroupTargetScope(element, behavior, scope, rootScope);
4122
+ const existing = targetScope.getPath?.(key);
4123
+ const list = Array.isArray(existing) ? existing : [];
4124
+ const proxy = this.getGroupProxy(scope);
4125
+ if (!list.includes(proxy)) {
4126
+ list.push(proxy);
4127
+ targetScope.setPath?.(key, list);
4128
+ } else if (!Array.isArray(existing)) {
4129
+ targetScope.setPath?.(key, list);
4130
+ }
4131
+ },
4132
+ onUnbind: ({ args, scope, rootScope, behavior, element }) => {
4133
+ const key = typeof args === "string" ? args : void 0;
4134
+ if (!key) {
4135
+ return;
4136
+ }
4137
+ const targetScope = this.getGroupTargetScope(element, behavior, scope, rootScope);
4138
+ const existing = targetScope.getPath?.(key);
4139
+ if (!Array.isArray(existing)) {
4140
+ return;
4141
+ }
4142
+ const proxy = this.getGroupProxy(scope);
4143
+ const next = existing.filter((entry) => entry !== proxy);
4144
+ if (next.length !== existing.length) {
4145
+ targetScope.setPath?.(key, next);
4146
+ }
4147
+ }
4148
+ });
4149
+ }
4150
+ getGroupTargetScope(element, behavior, scope, rootScope) {
4151
+ let targetScope = rootScope ?? scope;
4152
+ if (behavior.parentSelector) {
4153
+ const parentElement = element.closest(behavior.parentSelector);
4154
+ if (parentElement) {
4155
+ targetScope = this.getScope(parentElement);
4156
+ }
4157
+ }
4158
+ return targetScope;
4159
+ }
4160
+ getGroupProxy(scope) {
4161
+ const cached = this.groupProxyCache.get(scope);
4162
+ if (cached) {
4163
+ return cached;
4164
+ }
4165
+ const proxy = new Proxy(
4166
+ {},
4167
+ {
4168
+ get: (_target, prop) => {
4169
+ if (typeof prop === "symbol") {
4170
+ return void 0;
4171
+ }
4172
+ if (prop === "__scope") {
4173
+ return scope;
4174
+ }
4175
+ return scope.getPath(String(prop));
4176
+ },
4177
+ set: (_target, prop, value) => {
4178
+ if (typeof prop === "symbol") {
4179
+ return false;
4180
+ }
4181
+ scope.setPath(String(prop), value);
4182
+ return true;
4183
+ },
4184
+ has: (_target, prop) => {
4185
+ if (typeof prop === "symbol") {
4186
+ return false;
4187
+ }
4188
+ return scope.getPath(String(prop)) !== void 0;
4189
+ },
4190
+ getOwnPropertyDescriptor: () => ({
4191
+ enumerable: true,
4192
+ configurable: true
4193
+ }),
4194
+ ownKeys: () => []
4195
+ }
4196
+ );
4197
+ this.groupProxyCache.set(scope, proxy);
4198
+ return proxy;
3490
4199
  }
3491
4200
  async mount(root) {
3492
4201
  const documentRoot = root.ownerDocument;
@@ -3513,7 +4222,10 @@ var Engine = class _Engine {
3513
4222
  this.disconnectObserver();
3514
4223
  }
3515
4224
  registerBehaviors(source) {
3516
- const program = new Parser(source, { customFlags: new Set(this.flagHandlers.keys()) }).parseProgram();
4225
+ const program = new Parser(source, {
4226
+ customFlags: new Set(this.flagHandlers.keys()),
4227
+ behaviorFlags: new Set(this.behaviorModifiers.keys())
4228
+ }).parseProgram();
3517
4229
  for (const use of program.uses) {
3518
4230
  if (use.flags?.wait) {
3519
4231
  this.pendingUses.push(this.waitForUseGlobal(use));
@@ -3537,11 +4249,14 @@ var Engine = class _Engine {
3537
4249
  Object.assign(this.globals, values);
3538
4250
  }
3539
4251
  registerFlag(name, handler = {}) {
4252
+ this.flagHandlers.set(name, handler);
4253
+ }
4254
+ registerBehaviorModifier(name, handler = {}) {
3540
4255
  const reserved = /* @__PURE__ */ new Set(["important", "trusted", "debounce"]);
3541
4256
  if (reserved.has(name)) {
3542
- throw new Error(`Flag '${name}' is reserved`);
4257
+ throw new Error(`Behavior modifier '${name}' is reserved`);
3543
4258
  }
3544
- this.flagHandlers.set(name, handler);
4259
+ this.behaviorModifiers.set(name, handler);
3545
4260
  }
3546
4261
  getRegistryStats() {
3547
4262
  return {
@@ -3649,7 +4364,6 @@ var Engine = class _Engine {
3649
4364
  if (this.observer) {
3650
4365
  return;
3651
4366
  }
3652
- this.observerRoot = root;
3653
4367
  this.observerFlush = debounce(() => this.flushObserverQueue(), 10);
3654
4368
  this.observer = new MutationObserver((mutations) => {
3655
4369
  for (const mutation of mutations) {
@@ -3679,7 +4393,6 @@ var Engine = class _Engine {
3679
4393
  disconnectObserver() {
3680
4394
  this.observer?.disconnect();
3681
4395
  this.observer = void 0;
3682
- this.observerRoot = void 0;
3683
4396
  this.pendingAdded.clear();
3684
4397
  this.pendingRemoved.clear();
3685
4398
  this.pendingUpdated.clear();
@@ -3708,6 +4421,8 @@ var Engine = class _Engine {
3708
4421
  if (this.behaviorBindings.has(node)) {
3709
4422
  this.runBehaviorDestruct(node);
3710
4423
  }
4424
+ this.cleanupScopeWatchers(node);
4425
+ this.cleanupBehaviorListeners(node);
3711
4426
  for (const child of Array.from(node.querySelectorAll("*"))) {
3712
4427
  if (this.lifecycleBindings.has(child)) {
3713
4428
  this.runDestruct(child);
@@ -3715,6 +4430,8 @@ var Engine = class _Engine {
3715
4430
  if (this.behaviorBindings.has(child)) {
3716
4431
  this.runBehaviorDestruct(child);
3717
4432
  }
4433
+ this.cleanupScopeWatchers(child);
4434
+ this.cleanupBehaviorListeners(child);
3718
4435
  }
3719
4436
  }
3720
4437
  handleAddedNode(node) {
@@ -3738,13 +4455,13 @@ var Engine = class _Engine {
3738
4455
  }
3739
4456
  async applyBehaviors(root) {
3740
4457
  await this.waitForUses();
3741
- if (this.behaviorRegistry.length === 0) {
3742
- return;
3743
- }
3744
- const elements = [root, ...Array.from(root.querySelectorAll("*"))];
3745
- for (const element of elements) {
3746
- await this.reapplyBehaviorsForElement(element);
4458
+ if (this.behaviorRegistry.length > 0) {
4459
+ const elements = [root, ...Array.from(root.querySelectorAll("*"))];
4460
+ for (const element of elements) {
4461
+ await this.reapplyBehaviorsForElement(element);
4462
+ }
3747
4463
  }
4464
+ this.flushAutoBindQueue();
3748
4465
  }
3749
4466
  async reapplyBehaviorsForElement(element) {
3750
4467
  if (this.behaviorRegistry.length === 0) {
@@ -3776,15 +4493,18 @@ var Engine = class _Engine {
3776
4493
  const rootScope = this.getBehaviorRootScope(element, behavior);
3777
4494
  this.applyBehaviorFunctions(element, scope, behavior.functions, rootScope);
3778
4495
  await this.applyBehaviorDeclarations(element, scope, behavior.declarations, rootScope);
4496
+ await this.applyBehaviorModifierHook("onBind", behavior, element, scope, rootScope);
3779
4497
  if (behavior.construct) {
3780
4498
  await this.safeExecuteBlock(behavior.construct, scope, element, rootScope);
3781
4499
  }
4500
+ await this.applyBehaviorModifierHook("onConstruct", behavior, element, scope, rootScope);
3782
4501
  for (const onBlock of behavior.onBlocks) {
3783
4502
  this.attachBehaviorOnHandler(
3784
4503
  element,
3785
4504
  onBlock.event,
3786
4505
  onBlock.body,
3787
- onBlock.modifiers,
4506
+ onBlock.flags,
4507
+ onBlock.flagArgs,
3788
4508
  onBlock.args,
3789
4509
  behavior.id,
3790
4510
  rootScope
@@ -3794,10 +4514,11 @@ var Engine = class _Engine {
3794
4514
  }
3795
4515
  unbindBehaviorForElement(behavior, element, scope, bound) {
3796
4516
  bound.delete(behavior.id);
4517
+ const rootScope = this.getBehaviorRootScope(element, behavior);
3797
4518
  if (behavior.destruct) {
3798
- const rootScope = this.getBehaviorRootScope(element, behavior);
3799
4519
  void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
3800
4520
  }
4521
+ void this.applyBehaviorModifierHook("onDestruct", behavior, element, scope, rootScope);
3801
4522
  const listenerMap = this.behaviorListeners.get(element);
3802
4523
  const listeners = listenerMap?.get(behavior.id);
3803
4524
  if (listeners) {
@@ -3806,6 +4527,7 @@ var Engine = class _Engine {
3806
4527
  }
3807
4528
  listenerMap?.delete(behavior.id);
3808
4529
  }
4530
+ void this.applyBehaviorModifierHook("onUnbind", behavior, element, scope, rootScope);
3809
4531
  this.logDiagnostic("unbind", element, behavior);
3810
4532
  }
3811
4533
  runBehaviorDestruct(element) {
@@ -3815,11 +4537,15 @@ var Engine = class _Engine {
3815
4537
  }
3816
4538
  const scope = this.getScope(element);
3817
4539
  for (const behavior of this.behaviorRegistry) {
3818
- if (!bound.has(behavior.id) || !behavior.destruct) {
4540
+ if (!bound.has(behavior.id) || !behavior.destruct && !this.behaviorHasModifierHooks(behavior)) {
3819
4541
  continue;
3820
4542
  }
3821
4543
  const rootScope = this.getBehaviorRootScope(element, behavior);
3822
- void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
4544
+ if (behavior.destruct) {
4545
+ void this.safeExecuteBlock(behavior.destruct, scope, element, rootScope);
4546
+ }
4547
+ void this.applyBehaviorModifierHook("onDestruct", behavior, element, scope, rootScope);
4548
+ void this.applyBehaviorModifierHook("onUnbind", behavior, element, scope, rootScope);
3823
4549
  }
3824
4550
  }
3825
4551
  attachAttributes(element) {
@@ -3903,6 +4629,7 @@ var Engine = class _Engine {
3903
4629
  const fragment = element.content.cloneNode(true);
3904
4630
  const roots = Array.from(fragment.children);
3905
4631
  const itemScope = new Scope(scope);
4632
+ itemScope.isEachItem = true;
3906
4633
  itemScope.setPath(`self.${binding.itemName}`, item);
3907
4634
  if (binding.indexName) {
3908
4635
  itemScope.setPath(`self.${binding.indexName}`, index);
@@ -3941,7 +4668,93 @@ var Engine = class _Engine {
3941
4668
  if (name.includes(":to")) {
3942
4669
  return "to";
3943
4670
  }
3944
- return "both";
4671
+ return "auto";
4672
+ }
4673
+ resolveBindConfig(element, expr, scope, direction) {
4674
+ if (direction !== "auto") {
4675
+ return {
4676
+ direction,
4677
+ seedFromScope: false,
4678
+ syncToScope: direction === "to" || direction === "both",
4679
+ deferToScope: false
4680
+ };
4681
+ }
4682
+ if (this.isInEachScope(scope)) {
4683
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4684
+ }
4685
+ if (this.isFormControl(element)) {
4686
+ if (this.hasScopeValue(scope, expr)) {
4687
+ return { direction: "both", seedFromScope: true, syncToScope: false, deferToScope: false };
4688
+ }
4689
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: true };
4690
+ }
4691
+ if (this.hasScopeValue(scope, expr)) {
4692
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4693
+ }
4694
+ if (this.hasElementValue(element)) {
4695
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: true };
4696
+ }
4697
+ return { direction: "both", seedFromScope: false, syncToScope: false, deferToScope: false };
4698
+ }
4699
+ isFormControl(element) {
4700
+ return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement;
4701
+ }
4702
+ hasScopeValue(scope, expr) {
4703
+ const key = expr.trim();
4704
+ if (!key) {
4705
+ return false;
4706
+ }
4707
+ const value = scope.get(key);
4708
+ return value !== void 0 && value !== null;
4709
+ }
4710
+ hasElementValue(element) {
4711
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
4712
+ return element.value.length > 0;
4713
+ }
4714
+ return (element.textContent ?? "").trim().length > 0;
4715
+ }
4716
+ coerceInt(value) {
4717
+ if (value == null || value === "") {
4718
+ return value;
4719
+ }
4720
+ const num = typeof value === "number" ? value : Number.parseInt(String(value), 10);
4721
+ return Number.isNaN(num) ? value : num;
4722
+ }
4723
+ coerceFloat(value) {
4724
+ if (value == null || value === "") {
4725
+ return value;
4726
+ }
4727
+ const num = typeof value === "number" ? value : Number.parseFloat(String(value));
4728
+ return Number.isNaN(num) ? value : num;
4729
+ }
4730
+ isInEachScope(scope) {
4731
+ let cursor = scope;
4732
+ while (cursor) {
4733
+ if (cursor.isEachItem) {
4734
+ return true;
4735
+ }
4736
+ cursor = cursor.parent;
4737
+ }
4738
+ return false;
4739
+ }
4740
+ flushAutoBindQueue() {
4741
+ if (this.pendingAutoBindToScope.length === 0) {
4742
+ return;
4743
+ }
4744
+ const pending = this.pendingAutoBindToScope;
4745
+ this.pendingAutoBindToScope = [];
4746
+ for (const entry of pending) {
4747
+ if (!entry.element.isConnected) {
4748
+ continue;
4749
+ }
4750
+ if (this.hasScopeValue(entry.scope, entry.expr)) {
4751
+ continue;
4752
+ }
4753
+ if (!this.hasElementValue(entry.element)) {
4754
+ continue;
4755
+ }
4756
+ applyBindToScope(entry.element, entry.expr, entry.scope);
4757
+ }
3945
4758
  }
3946
4759
  hasVsnAttributes(element) {
3947
4760
  return element.getAttributeNames().some((name) => name.startsWith("vsn-"));
@@ -3966,7 +4779,7 @@ var Engine = class _Engine {
3966
4779
  }
3967
4780
  return void 0;
3968
4781
  }
3969
- watch(scope, expr, handler) {
4782
+ watch(scope, expr, handler, element) {
3970
4783
  const key = expr.trim();
3971
4784
  if (!key) {
3972
4785
  return;
@@ -3981,29 +4794,70 @@ var Engine = class _Engine {
3981
4794
  }
3982
4795
  if (target) {
3983
4796
  target.on(key, handler);
4797
+ if (element) {
4798
+ this.trackScopeWatcher(element, target, "path", handler, key);
4799
+ }
3984
4800
  return;
3985
4801
  }
3986
4802
  let cursor = scope;
3987
4803
  while (cursor) {
3988
4804
  cursor.on(key, handler);
4805
+ if (element) {
4806
+ this.trackScopeWatcher(element, cursor, "path", handler, key);
4807
+ }
3989
4808
  cursor = cursor.parent;
3990
4809
  }
3991
4810
  }
3992
- watchWithDebounce(scope, expr, handler, debounceMs) {
3993
- if (debounceMs) {
3994
- this.watch(scope, expr, debounce(handler, debounceMs));
3995
- } else {
3996
- this.watch(scope, expr, handler);
3997
- }
4811
+ watchWithDebounce(scope, expr, handler, debounceMs, element) {
4812
+ const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4813
+ this.watch(scope, expr, effectiveHandler, element);
3998
4814
  }
3999
- watchAllScopes(scope, handler, debounceMs) {
4815
+ watchAllScopes(scope, handler, debounceMs, element) {
4000
4816
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4001
4817
  let cursor = scope;
4002
4818
  while (cursor) {
4003
4819
  cursor.onAny(effectiveHandler);
4820
+ if (element) {
4821
+ this.trackScopeWatcher(element, cursor, "any", effectiveHandler);
4822
+ }
4004
4823
  cursor = cursor.parent;
4005
4824
  }
4006
4825
  }
4826
+ trackScopeWatcher(element, scope, kind, handler, key) {
4827
+ const watchers = this.scopeWatchers.get(element) ?? [];
4828
+ watchers.push({ scope, kind, handler, ...key ? { key } : {} });
4829
+ this.scopeWatchers.set(element, watchers);
4830
+ }
4831
+ cleanupScopeWatchers(element) {
4832
+ const watchers = this.scopeWatchers.get(element);
4833
+ if (!watchers) {
4834
+ return;
4835
+ }
4836
+ for (const watcher of watchers) {
4837
+ if (watcher.kind === "any") {
4838
+ watcher.scope.offAny(watcher.handler);
4839
+ continue;
4840
+ }
4841
+ if (watcher.key) {
4842
+ watcher.scope.off(watcher.key, watcher.handler);
4843
+ }
4844
+ }
4845
+ this.scopeWatchers.delete(element);
4846
+ }
4847
+ cleanupBehaviorListeners(element) {
4848
+ const listenerMap = this.behaviorListeners.get(element);
4849
+ if (!listenerMap) {
4850
+ return;
4851
+ }
4852
+ for (const listeners of listenerMap.values()) {
4853
+ for (const listener of listeners) {
4854
+ listener.target.removeEventListener(listener.event, listener.handler, listener.options);
4855
+ }
4856
+ }
4857
+ listenerMap.clear();
4858
+ this.behaviorListeners.delete(element);
4859
+ this.behaviorBindings.delete(element);
4860
+ }
4007
4861
  parseOnAttribute(name, value) {
4008
4862
  if (!name.startsWith("vsn-on:")) {
4009
4863
  return null;
@@ -4013,111 +4867,56 @@ var Engine = class _Engine {
4013
4867
  if (!event) {
4014
4868
  return null;
4015
4869
  }
4016
- const descriptor = this.parseEventDescriptor(event);
4017
- if (!descriptor.event) {
4018
- return null;
4019
- }
4020
- let debounceMs;
4021
- const modifiers = [];
4022
- for (const flag of flags) {
4023
- if (flag.startsWith("debounce")) {
4024
- const match = flag.match(/debounce\((\d+)\)/);
4025
- debounceMs = match ? Number(match[1]) : 200;
4026
- continue;
4027
- }
4028
- modifiers.push(flag);
4870
+ if (event.includes(".")) {
4871
+ throw new Error("vsn:on does not support dot modifiers; use !flags instead");
4029
4872
  }
4030
- const combinedModifiers = [...modifiers, ...descriptor.modifiers];
4873
+ const { flagMap, flagArgs } = this.parseInlineFlags(flags);
4031
4874
  const config = {
4032
- event: descriptor.event,
4875
+ event,
4033
4876
  code: value,
4034
- ...debounceMs !== void 0 ? { debounceMs } : {},
4035
- ...combinedModifiers.length > 0 ? { modifiers: combinedModifiers } : {},
4036
- ...descriptor.keyModifiers.length > 0 ? { keyModifiers: descriptor.keyModifiers } : {}
4877
+ flags: flagMap,
4878
+ flagArgs
4037
4879
  };
4038
4880
  return config;
4039
4881
  }
4040
- parseEventDescriptor(raw) {
4041
- const parts = raw.split(".").map((part) => part.trim()).filter(Boolean);
4042
- const event = parts.shift() ?? "";
4043
- const modifiers = [];
4044
- const keyModifiers = [];
4045
- const modifierSet = /* @__PURE__ */ new Set(["outside", "self"]);
4046
- for (const part of parts) {
4047
- if (modifierSet.has(part)) {
4048
- modifiers.push(part);
4049
- } else {
4050
- keyModifiers.push(part);
4882
+ parseInlineFlags(parts) {
4883
+ const flagMap = {};
4884
+ const flagArgs = {};
4885
+ for (const raw of parts) {
4886
+ const trimmed = raw.trim();
4887
+ if (!trimmed) {
4888
+ continue;
4051
4889
  }
4052
- }
4053
- return { event, keyModifiers, modifiers };
4054
- }
4055
- matchesKeyModifiers(event, keyModifiers) {
4056
- if (!keyModifiers || keyModifiers.length === 0) {
4057
- return true;
4058
- }
4059
- if (!(event instanceof KeyboardEvent)) {
4060
- return false;
4061
- }
4062
- const modifierChecks = {
4063
- shift: event.shiftKey,
4064
- ctrl: event.ctrlKey,
4065
- control: event.ctrlKey,
4066
- alt: event.altKey,
4067
- meta: event.metaKey
4068
- };
4069
- const keyAliases = {
4070
- esc: "escape",
4071
- escape: "escape",
4072
- enter: "enter",
4073
- tab: "tab",
4074
- space: "space",
4075
- spacebar: "space",
4076
- up: "arrowup",
4077
- down: "arrowdown",
4078
- left: "arrowleft",
4079
- right: "arrowright",
4080
- arrowup: "arrowup",
4081
- arrowdown: "arrowdown",
4082
- arrowleft: "arrowleft",
4083
- arrowright: "arrowright",
4084
- delete: "delete",
4085
- backspace: "backspace"
4086
- };
4087
- let key = event.key?.toLowerCase() ?? "";
4088
- if (key === " ") {
4089
- key = "space";
4090
- }
4091
- for (const rawModifier of keyModifiers) {
4092
- const modifier = rawModifier.toLowerCase();
4093
- if (modifier in modifierChecks) {
4094
- if (!modifierChecks[modifier]) {
4095
- return false;
4096
- }
4890
+ const match = trimmed.match(/^([a-zA-Z][\w-]*)(?:\((.+)\))?$/);
4891
+ if (!match) {
4097
4892
  continue;
4098
4893
  }
4099
- const expectedKey = keyAliases[modifier] ?? modifier;
4100
- if (key !== expectedKey) {
4101
- return false;
4894
+ const name = match[1] ?? "";
4895
+ if (!name) {
4896
+ continue;
4897
+ }
4898
+ if (!this.flagHandlers.has(name)) {
4899
+ throw new Error(`Unknown flag ${name}`);
4900
+ }
4901
+ flagMap[name] = true;
4902
+ if (match[2] !== void 0) {
4903
+ flagArgs[name] = this.parseInlineFlagArg(match[2]);
4102
4904
  }
4103
4905
  }
4104
- return true;
4906
+ return { flagMap, flagArgs };
4105
4907
  }
4106
- matchesTargetModifiers(element, event, modifiers) {
4107
- if (!modifiers || modifiers.length === 0) {
4908
+ parseInlineFlagArg(raw) {
4909
+ const trimmed = raw.trim();
4910
+ if (trimmed === "true") {
4108
4911
  return true;
4109
4912
  }
4110
- const target = event?.target;
4111
- if (!target || !(target instanceof Node)) {
4112
- return !modifiers.includes("self") && !modifiers.includes("outside");
4113
- }
4114
- if (modifiers.includes("self") && target !== element) {
4913
+ if (trimmed === "false") {
4115
4914
  return false;
4116
4915
  }
4117
- if (modifiers.includes("outside") && element.contains(target)) {
4118
- return false;
4916
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
4917
+ return Number(trimmed);
4119
4918
  }
4120
- return true;
4919
+ return trimmed;
4121
4920
  }
4122
4921
  describeElement(element) {
4123
4922
  const tag = element.tagName.toLowerCase();
@@ -4159,51 +4958,50 @@ var Engine = class _Engine {
4159
4958
  }
4160
4959
  }
4161
4960
  attachOnHandler(element, config) {
4162
- const options = this.getListenerOptions(config.modifiers);
4163
- const listenerTarget = config.modifiers?.includes("outside") ? element.ownerDocument : element;
4961
+ const { listenerTarget, options, debounceMs } = this.getEventBindingConfig(
4962
+ element,
4963
+ config.flags,
4964
+ config.flagArgs
4965
+ );
4164
4966
  let effectiveHandler;
4165
4967
  const handler = async (event) => {
4166
4968
  if (!element.isConnected) {
4167
4969
  listenerTarget.removeEventListener(config.event, effectiveHandler, options);
4168
4970
  return;
4169
4971
  }
4170
- if (!this.matchesKeyModifiers(event, config.keyModifiers)) {
4171
- return;
4172
- }
4173
- if (!this.matchesTargetModifiers(element, event, config.modifiers)) {
4972
+ const scope = this.getScope(element);
4973
+ if (!this.applyEventFlagBefore(element, scope, config.flags, config.flagArgs, event)) {
4174
4974
  return;
4175
4975
  }
4176
- this.applyEventModifiers(event, config.modifiers);
4177
- const scope = this.getScope(element);
4178
4976
  try {
4179
4977
  await this.execute(config.code, scope, element);
4180
4978
  this.evaluate(element);
4181
4979
  } catch (error) {
4182
4980
  this.emitError(element, error);
4981
+ } finally {
4982
+ this.applyEventFlagAfter(element, scope, config.flags, config.flagArgs, event);
4183
4983
  }
4184
4984
  };
4185
- effectiveHandler = config.debounceMs ? debounce(handler, config.debounceMs) : handler;
4985
+ effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4186
4986
  listenerTarget.addEventListener(config.event, effectiveHandler, options);
4187
4987
  }
4188
- attachBehaviorOnHandler(element, event, body, modifiers, args, behaviorId, rootScope) {
4189
- const descriptor = this.parseEventDescriptor(event);
4190
- const combinedModifiers = modifiers ? [...modifiers, ...descriptor.modifiers] : descriptor.modifiers.length > 0 ? [...descriptor.modifiers] : void 0;
4191
- const listenerTarget = combinedModifiers?.includes("outside") ? element.ownerDocument : element;
4988
+ attachBehaviorOnHandler(element, event, body, flags, flagArgs, args, behaviorId, rootScope) {
4989
+ if (event.includes(".")) {
4990
+ throw new Error("vsn:on does not support dot modifiers; use !flags instead");
4991
+ }
4992
+ const { listenerTarget, options, debounceMs } = this.getEventBindingConfig(element, flags, flagArgs);
4192
4993
  const handler = async (evt) => {
4193
- if (!this.matchesKeyModifiers(evt, descriptor.keyModifiers)) {
4194
- return;
4195
- }
4196
- if (!this.matchesTargetModifiers(element, evt, combinedModifiers)) {
4994
+ const scope = this.getScope(element);
4995
+ if (!this.applyEventFlagBefore(element, scope, flags, flagArgs, evt)) {
4197
4996
  return;
4198
4997
  }
4199
- this.applyEventModifiers(evt, combinedModifiers);
4200
- const scope = this.getScope(element);
4201
4998
  const previousValues = /* @__PURE__ */ new Map();
4202
4999
  if (args && args.length > 0) {
4203
5000
  const argName = args[0];
4204
5001
  if (argName) {
4205
5002
  previousValues.set(argName, scope.getPath(argName));
4206
- scope.setPath(argName, evt);
5003
+ const [nextArg] = this.applyEventFlagArgTransforms(element, scope, flags, flagArgs, evt);
5004
+ scope.setPath(argName, nextArg);
4207
5005
  }
4208
5006
  }
4209
5007
  let failed = false;
@@ -4216,16 +5014,17 @@ var Engine = class _Engine {
4216
5014
  for (const [name, value] of previousValues.entries()) {
4217
5015
  scope.setPath(name, value);
4218
5016
  }
5017
+ this.applyEventFlagAfter(element, scope, flags, flagArgs, evt);
4219
5018
  }
4220
5019
  if (!failed) {
4221
5020
  this.evaluate(element);
4222
5021
  }
4223
5022
  };
4224
- const options = this.getListenerOptions(combinedModifiers);
4225
- listenerTarget.addEventListener(descriptor.event, handler, options);
5023
+ const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
5024
+ listenerTarget.addEventListener(event, effectiveHandler, options);
4226
5025
  const listenerMap = this.behaviorListeners.get(element) ?? /* @__PURE__ */ new Map();
4227
5026
  const listeners = listenerMap.get(behaviorId) ?? [];
4228
- listeners.push({ target: listenerTarget, event: descriptor.event, handler, options });
5027
+ listeners.push({ target: listenerTarget, event, handler: effectiveHandler, options });
4229
5028
  listenerMap.set(behaviorId, listeners);
4230
5029
  this.behaviorListeners.set(element, listenerMap);
4231
5030
  }
@@ -4256,33 +5055,158 @@ var Engine = class _Engine {
4256
5055
  Promise.resolve().then(handler);
4257
5056
  }
4258
5057
  }
4259
- applyEventModifiers(event, modifiers) {
4260
- if (!event || !modifiers || modifiers.length === 0) {
4261
- return;
5058
+ getEventBindingConfig(element, flags, flagArgs) {
5059
+ let listenerTarget = element;
5060
+ let options = {};
5061
+ let debounceMs;
5062
+ for (const name of Object.keys(flags)) {
5063
+ const handler = this.flagHandlers.get(name);
5064
+ if (!handler?.onEventBind) {
5065
+ continue;
5066
+ }
5067
+ const patch = handler.onEventBind({
5068
+ name,
5069
+ args: flagArgs[name],
5070
+ element,
5071
+ scope: this.getScope(element),
5072
+ rootScope: void 0,
5073
+ event: void 0,
5074
+ engine: this
5075
+ });
5076
+ if (!patch) {
5077
+ continue;
5078
+ }
5079
+ if (patch.listenerTarget) {
5080
+ listenerTarget = patch.listenerTarget;
5081
+ }
5082
+ if (patch.options) {
5083
+ options = { ...options, ...patch.options };
5084
+ }
5085
+ if (patch.debounceMs !== void 0) {
5086
+ debounceMs = patch.debounceMs;
5087
+ }
5088
+ }
5089
+ return {
5090
+ listenerTarget,
5091
+ ...Object.keys(options).length > 0 ? { options } : {},
5092
+ ...debounceMs !== void 0 ? { debounceMs } : {}
5093
+ };
5094
+ }
5095
+ applyEventFlagBefore(element, scope, flags, flagArgs, event) {
5096
+ for (const name of Object.keys(flags)) {
5097
+ const handler = this.flagHandlers.get(name);
5098
+ if (!handler?.onEventBefore) {
5099
+ continue;
5100
+ }
5101
+ const result = handler.onEventBefore({
5102
+ name,
5103
+ args: flagArgs[name],
5104
+ element,
5105
+ scope,
5106
+ rootScope: void 0,
5107
+ event,
5108
+ engine: this
5109
+ });
5110
+ if (result === false) {
5111
+ return false;
5112
+ }
4262
5113
  }
4263
- for (const modifier of modifiers) {
4264
- if (modifier === "prevent") {
4265
- event.preventDefault();
4266
- } else if (modifier === "stop") {
4267
- event.stopPropagation();
5114
+ return true;
5115
+ }
5116
+ applyEventFlagAfter(element, scope, flags, flagArgs, event) {
5117
+ for (const name of Object.keys(flags)) {
5118
+ const handler = this.flagHandlers.get(name);
5119
+ if (!handler?.onEventAfter) {
5120
+ continue;
4268
5121
  }
5122
+ handler.onEventAfter({
5123
+ name,
5124
+ args: flagArgs[name],
5125
+ element,
5126
+ scope,
5127
+ rootScope: void 0,
5128
+ event,
5129
+ engine: this
5130
+ });
4269
5131
  }
4270
5132
  }
4271
- getListenerOptions(modifiers) {
4272
- if (!modifiers || modifiers.length === 0) {
4273
- return void 0;
5133
+ applyEventFlagArgTransforms(element, scope, flags, flagArgs, event) {
5134
+ let args = [event];
5135
+ for (const name of Object.keys(flags)) {
5136
+ const handler = this.flagHandlers.get(name);
5137
+ if (!handler?.transformEventArgs) {
5138
+ continue;
5139
+ }
5140
+ const nextArgs = handler.transformEventArgs(
5141
+ {
5142
+ name,
5143
+ args: flagArgs[name],
5144
+ element,
5145
+ scope,
5146
+ rootScope: void 0,
5147
+ event,
5148
+ engine: this
5149
+ },
5150
+ args
5151
+ );
5152
+ if (Array.isArray(nextArgs)) {
5153
+ args = nextArgs;
5154
+ }
5155
+ }
5156
+ return args;
5157
+ }
5158
+ matchesKeyFlag(event, flag) {
5159
+ if (!(event instanceof KeyboardEvent)) {
5160
+ return false;
5161
+ }
5162
+ const modifierChecks = {
5163
+ shift: event.shiftKey,
5164
+ ctrl: event.ctrlKey,
5165
+ alt: event.altKey,
5166
+ meta: event.metaKey
5167
+ };
5168
+ if (flag in modifierChecks) {
5169
+ return modifierChecks[flag] ?? false;
4274
5170
  }
4275
- const options = {};
4276
- if (modifiers.includes("once")) {
4277
- options.once = true;
5171
+ const keyAliases = {
5172
+ escape: "escape",
5173
+ esc: "escape",
5174
+ enter: "enter",
5175
+ tab: "tab",
5176
+ space: "space",
5177
+ spacebar: "space",
5178
+ up: "arrowup",
5179
+ down: "arrowdown",
5180
+ left: "arrowleft",
5181
+ right: "arrowright",
5182
+ arrowup: "arrowup",
5183
+ arrowdown: "arrowdown",
5184
+ arrowleft: "arrowleft",
5185
+ arrowright: "arrowright",
5186
+ delete: "delete",
5187
+ backspace: "backspace"
5188
+ };
5189
+ let key = event.key?.toLowerCase() ?? "";
5190
+ if (key === " ") {
5191
+ key = "space";
4278
5192
  }
4279
- if (modifiers.includes("passive")) {
4280
- options.passive = true;
5193
+ const expectedKey = keyAliases[flag] ?? flag;
5194
+ return key === expectedKey;
5195
+ }
5196
+ async withExecutionElement(element, fn) {
5197
+ if (!element) {
5198
+ await fn();
5199
+ return;
4281
5200
  }
4282
- if (modifiers.includes("capture")) {
4283
- options.capture = true;
5201
+ this.executionStack.push(element);
5202
+ try {
5203
+ await fn();
5204
+ } finally {
5205
+ this.executionStack.pop();
4284
5206
  }
4285
- return Object.keys(options).length > 0 ? options : void 0;
5207
+ }
5208
+ getCurrentElement() {
5209
+ return this.executionStack[this.executionStack.length - 1];
4286
5210
  }
4287
5211
  async execute(code, scope, element, rootScope) {
4288
5212
  let block = this.codeCache.get(code);
@@ -4290,22 +5214,26 @@ var Engine = class _Engine {
4290
5214
  block = Parser.parseInline(code);
4291
5215
  this.codeCache.set(code, block);
4292
5216
  }
4293
- const context = {
4294
- scope,
4295
- rootScope,
4296
- globals: this.globals,
4297
- ...element ? { element } : {}
4298
- };
4299
- await block.evaluate(context);
5217
+ await this.withExecutionElement(element, async () => {
5218
+ const context = {
5219
+ scope,
5220
+ rootScope,
5221
+ globals: this.globals,
5222
+ ...element ? { element } : {}
5223
+ };
5224
+ await block.evaluate(context);
5225
+ });
4300
5226
  }
4301
5227
  async executeBlock(block, scope, element, rootScope) {
4302
- const context = {
4303
- scope,
4304
- rootScope,
4305
- globals: this.globals,
4306
- ...element ? { element } : {}
4307
- };
4308
- await block.evaluate(context);
5228
+ await this.withExecutionElement(element, async () => {
5229
+ const context = {
5230
+ scope,
5231
+ rootScope,
5232
+ globals: this.globals,
5233
+ ...element ? { element } : {}
5234
+ };
5235
+ await block.evaluate(context);
5236
+ });
4309
5237
  }
4310
5238
  async safeExecute(code, scope, element, rootScope) {
4311
5239
  try {
@@ -4328,15 +5256,26 @@ var Engine = class _Engine {
4328
5256
  collectBehavior(behavior, parentSelector, rootSelectorOverride) {
4329
5257
  const selector = parentSelector ? `${parentSelector} ${behavior.selector.selectorText}` : behavior.selector.selectorText;
4330
5258
  const rootSelector = rootSelectorOverride ?? (parentSelector ?? behavior.selector.selectorText);
5259
+ const behaviorHash = this.hashBehavior(behavior);
5260
+ const hash = `${selector}::${rootSelector}::${behaviorHash}`;
5261
+ if (this.behaviorRegistryHashes.has(hash)) {
5262
+ return;
5263
+ }
4331
5264
  const cached = this.getCachedBehavior(behavior);
4332
- this.behaviorRegistry.push({
5265
+ const entry = {
4333
5266
  id: this.behaviorId += 1,
5267
+ hash,
4334
5268
  selector,
4335
5269
  rootSelector,
4336
5270
  specificity: this.computeSpecificity(selector),
4337
5271
  order: this.behaviorRegistry.length,
4338
- ...cached
4339
- });
5272
+ flags: behavior.flags ?? {},
5273
+ flagArgs: behavior.flagArgs ?? {},
5274
+ ...cached,
5275
+ ...parentSelector ? { parentSelector } : {}
5276
+ };
5277
+ this.behaviorRegistry.push(entry);
5278
+ this.behaviorRegistryHashes.add(hash);
4340
5279
  this.collectNestedBehaviors(behavior.body, selector, rootSelector);
4341
5280
  }
4342
5281
  collectNestedBehaviors(block, parentSelector, rootSelector) {
@@ -4409,7 +5348,8 @@ var Engine = class _Engine {
4409
5348
  blocks.push({
4410
5349
  event: statement.eventName,
4411
5350
  body: statement.body,
4412
- modifiers: statement.modifiers,
5351
+ flags: statement.flags,
5352
+ flagArgs: statement.flagArgs,
4413
5353
  args: statement.args
4414
5354
  });
4415
5355
  }
@@ -4474,6 +5414,8 @@ var Engine = class _Engine {
4474
5414
  return {
4475
5415
  type,
4476
5416
  selector: node.selector?.selectorText ?? "",
5417
+ flags: node.flags ?? {},
5418
+ flagArgs: node.flagArgs ?? {},
4477
5419
  body: this.normalizeNode(node.body)
4478
5420
  };
4479
5421
  }
@@ -4491,6 +5433,8 @@ var Engine = class _Engine {
4491
5433
  type,
4492
5434
  eventName: node.eventName ?? "",
4493
5435
  args: Array.isArray(node.args) ? node.args : [],
5436
+ flags: node.flags ?? {},
5437
+ flagArgs: node.flagArgs ?? {},
4494
5438
  body: this.normalizeNode(node.body)
4495
5439
  };
4496
5440
  }
@@ -4508,21 +5452,9 @@ var Engine = class _Engine {
4508
5452
  return {
4509
5453
  type,
4510
5454
  target: this.normalizeNode(node.target),
4511
- value: this.normalizeNode(node.value)
4512
- };
4513
- }
4514
- if (type === "StateBlock") {
4515
- return {
4516
- type,
4517
- entries: Array.isArray(node.entries) ? node.entries.map((entry) => this.normalizeNode(entry)) : []
4518
- };
4519
- }
4520
- if (type === "StateEntry") {
4521
- return {
4522
- type,
4523
- name: node.name ?? "",
4524
5455
  value: this.normalizeNode(node.value),
4525
- important: Boolean(node.important)
5456
+ operator: node.operator ?? "",
5457
+ prefix: Boolean(node.prefix)
4526
5458
  };
4527
5459
  }
4528
5460
  if (type === "FunctionDeclaration") {
@@ -4556,6 +5488,15 @@ var Engine = class _Engine {
4556
5488
  value: this.normalizeNode(node.value ?? null)
4557
5489
  };
4558
5490
  }
5491
+ if (type === "Assert") {
5492
+ return {
5493
+ type,
5494
+ test: this.normalizeNode(node.test)
5495
+ };
5496
+ }
5497
+ if (type === "Break" || type === "Continue") {
5498
+ return { type };
5499
+ }
4559
5500
  if (type === "If") {
4560
5501
  return {
4561
5502
  type,
@@ -4580,6 +5521,15 @@ var Engine = class _Engine {
4580
5521
  body: this.normalizeNode(node.body)
4581
5522
  };
4582
5523
  }
5524
+ if (type === "ForEach") {
5525
+ return {
5526
+ type,
5527
+ kind: node.kind ?? "of",
5528
+ target: this.normalizeNode(node.target),
5529
+ iterable: this.normalizeNode(node.iterable),
5530
+ body: this.normalizeNode(node.body)
5531
+ };
5532
+ }
4583
5533
  if (type === "Try") {
4584
5534
  return {
4585
5535
  type,
@@ -4702,7 +5652,9 @@ var Engine = class _Engine {
4702
5652
  globals: this.globals,
4703
5653
  element,
4704
5654
  returnValue: void 0,
4705
- returning: false
5655
+ returning: false,
5656
+ breaking: false,
5657
+ continuing: false
4706
5658
  };
4707
5659
  const previousValues = /* @__PURE__ */ new Map();
4708
5660
  await this.applyFunctionParams(callScope, declaration.params, previousValues, context, args);
@@ -4753,6 +5705,7 @@ var Engine = class _Engine {
4753
5705
  const context = { scope, rootScope, element };
4754
5706
  const operator = declaration.operator;
4755
5707
  const debounceMs = declaration.flags.debounce ? declaration.flagArgs.debounce ?? 200 : void 0;
5708
+ const transform = (value) => this.applyCustomFlagTransforms(value, element, scope, declaration);
4756
5709
  const importantKey = this.getImportantKey(declaration);
4757
5710
  if (!declaration.flags.important && importantKey && this.isImportant(element, importantKey)) {
4758
5711
  return;
@@ -4763,7 +5716,8 @@ var Engine = class _Engine {
4763
5716
  this.applyCustomFlags(element, scope, declaration);
4764
5717
  if (declaration.target instanceof IdentifierExpression) {
4765
5718
  const value = await declaration.value.evaluate(context);
4766
- scope.setPath(declaration.target.name, value);
5719
+ const transformed = this.applyCustomFlagTransforms(value, element, scope, declaration);
5720
+ scope.setPath(declaration.target.name, transformed);
4767
5721
  if (declaration.flags.important && importantKey) {
4768
5722
  this.markImportant(element, importantKey);
4769
5723
  }
@@ -4776,7 +5730,7 @@ var Engine = class _Engine {
4776
5730
  const exprIdentifier = declaration.value instanceof IdentifierExpression ? declaration.value.name : void 0;
4777
5731
  if (operator === ":>") {
4778
5732
  if (exprIdentifier) {
4779
- this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope);
5733
+ this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope, transform);
4780
5734
  }
4781
5735
  if (declaration.flags.important && importantKey) {
4782
5736
  this.markImportant(element, importantKey);
@@ -4784,11 +5738,12 @@ var Engine = class _Engine {
4784
5738
  return;
4785
5739
  }
4786
5740
  if (operator === ":=" && exprIdentifier) {
4787
- this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope);
5741
+ this.applyDirectiveToScope(element, target, exprIdentifier, scope, debounceMs, rootScope, transform);
4788
5742
  }
4789
5743
  if (!exprIdentifier) {
4790
5744
  const value = await declaration.value.evaluate(context);
4791
- this.setDirectiveValue(element, target, value, declaration.flags.trusted);
5745
+ const transformed = this.applyCustomFlagTransforms(value, element, scope, declaration);
5746
+ this.setDirectiveValue(element, target, transformed, declaration.flags.trusted);
4792
5747
  const shouldWatch2 = operator === ":<" || operator === ":=";
4793
5748
  if (shouldWatch2) {
4794
5749
  this.applyDirectiveFromExpression(
@@ -4838,6 +5793,63 @@ var Engine = class _Engine {
4838
5793
  });
4839
5794
  }
4840
5795
  }
5796
+ applyCustomFlagTransforms(value, element, scope, declaration) {
5797
+ if (this.flagHandlers.size === 0) {
5798
+ return value;
5799
+ }
5800
+ let nextValue = value;
5801
+ for (const [name, handler] of this.flagHandlers) {
5802
+ if (!declaration.flags[name] || !handler.transformValue) {
5803
+ continue;
5804
+ }
5805
+ nextValue = handler.transformValue(
5806
+ {
5807
+ name,
5808
+ args: declaration.flagArgs[name],
5809
+ element,
5810
+ scope,
5811
+ declaration
5812
+ },
5813
+ nextValue
5814
+ );
5815
+ }
5816
+ return nextValue;
5817
+ }
5818
+ async applyBehaviorModifierHook(hook, behavior, element, scope, rootScope) {
5819
+ if (this.behaviorModifiers.size === 0) {
5820
+ return;
5821
+ }
5822
+ for (const [name, handler] of this.behaviorModifiers) {
5823
+ if (!behavior.flags?.[name]) {
5824
+ continue;
5825
+ }
5826
+ const callback = handler[hook];
5827
+ if (!callback) {
5828
+ continue;
5829
+ }
5830
+ await callback({
5831
+ name,
5832
+ args: behavior.flagArgs?.[name],
5833
+ element,
5834
+ scope,
5835
+ rootScope,
5836
+ behavior,
5837
+ engine: this
5838
+ });
5839
+ }
5840
+ }
5841
+ behaviorHasModifierHooks(behavior) {
5842
+ if (this.behaviorModifiers.size === 0) {
5843
+ return false;
5844
+ }
5845
+ const flags = behavior.flags ?? {};
5846
+ for (const name of Object.keys(flags)) {
5847
+ if (flags[name] && this.behaviorModifiers.has(name)) {
5848
+ return true;
5849
+ }
5850
+ }
5851
+ return false;
5852
+ }
4841
5853
  applyDirectiveFromScope(element, target, expr, scope, trusted, debounceMs, watch = true, rootScope) {
4842
5854
  if (target.kind === "attr" && target.name === "html" && element instanceof HTMLElement) {
4843
5855
  const handler2 = () => {
@@ -4854,7 +5866,7 @@ var Engine = class _Engine {
4854
5866
  const useRoot = expr.startsWith("root.") && rootScope;
4855
5867
  const sourceScope = useRoot ? rootScope : scope;
4856
5868
  const watchExpr = useRoot ? expr.slice("root.".length) : expr;
4857
- this.watchWithDebounce(sourceScope, watchExpr, handler2, debounceMs);
5869
+ this.watchWithDebounce(sourceScope, watchExpr, handler2, debounceMs, element);
4858
5870
  }
4859
5871
  return;
4860
5872
  }
@@ -4873,7 +5885,7 @@ var Engine = class _Engine {
4873
5885
  const useRoot = expr.startsWith("root.") && rootScope;
4874
5886
  const sourceScope = useRoot ? rootScope : scope;
4875
5887
  const watchExpr = useRoot ? expr.slice("root.".length) : expr;
4876
- this.watchWithDebounce(sourceScope, watchExpr, handler, debounceMs);
5888
+ this.watchWithDebounce(sourceScope, watchExpr, handler, debounceMs, element);
4877
5889
  }
4878
5890
  }
4879
5891
  applyDirectiveFromExpression(element, target, expr, scope, trusted, debounceMs, rootScope) {
@@ -4885,45 +5897,49 @@ var Engine = class _Engine {
4885
5897
  void handler();
4886
5898
  this.watchAllScopes(scope, () => {
4887
5899
  void handler();
4888
- }, debounceMs);
5900
+ }, debounceMs, element);
4889
5901
  }
4890
- applyDirectiveToScope(element, target, expr, scope, debounceMs, rootScope) {
5902
+ applyDirectiveToScope(element, target, expr, scope, debounceMs, rootScope, transform) {
4891
5903
  const useRoot = expr.startsWith("root.") && rootScope;
4892
5904
  const targetScope = useRoot ? rootScope : scope;
4893
5905
  const targetExpr = useRoot ? `self.${expr.slice("root.".length)}` : expr;
4894
5906
  if (target.kind === "attr" && target.name === "value") {
4895
- this.applyValueBindingToScope(element, targetExpr, debounceMs, targetScope);
5907
+ this.applyValueBindingToScope(element, targetExpr, debounceMs, targetScope, transform);
4896
5908
  return;
4897
5909
  }
4898
5910
  if (target.kind === "attr" && target.name === "checked") {
4899
- this.applyCheckedBindingToScope(element, targetExpr, debounceMs, targetScope);
5911
+ this.applyCheckedBindingToScope(element, targetExpr, debounceMs, targetScope, transform);
4900
5912
  return;
4901
5913
  }
4902
5914
  const value = this.getDirectiveValue(element, target);
4903
5915
  if (value != null) {
4904
- targetScope.set(targetExpr, value);
5916
+ const nextValue = transform ? transform(value) : value;
5917
+ targetScope.set(targetExpr, nextValue);
4905
5918
  }
4906
5919
  }
4907
- applyCheckedBindingToScope(element, expr, debounceMs, scope) {
5920
+ applyCheckedBindingToScope(element, expr, debounceMs, scope, transform) {
4908
5921
  if (!(element instanceof HTMLInputElement)) {
4909
5922
  return;
4910
5923
  }
4911
5924
  const handler = () => {
4912
5925
  const targetScope = scope ?? this.getScope(element);
4913
- targetScope.set(expr, element.checked);
5926
+ const value = transform ? transform(element.checked) : element.checked;
5927
+ targetScope.set(expr, value);
4914
5928
  };
4915
5929
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4916
5930
  effectiveHandler();
4917
5931
  element.addEventListener("change", effectiveHandler);
4918
5932
  element.addEventListener("input", effectiveHandler);
4919
5933
  }
4920
- applyValueBindingToScope(element, expr, debounceMs, scope) {
5934
+ applyValueBindingToScope(element, expr, debounceMs, scope, transform) {
4921
5935
  if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)) {
4922
5936
  return;
4923
5937
  }
4924
5938
  const handler = () => {
4925
5939
  const targetScope = scope ?? this.getScope(element);
4926
- applyBindToScope(element, expr, targetScope);
5940
+ const value = element.value;
5941
+ const nextValue = transform ? transform(value) : value;
5942
+ targetScope.set(expr, nextValue);
4927
5943
  };
4928
5944
  const effectiveHandler = debounceMs ? debounce(handler, debounceMs) : handler;
4929
5945
  effectiveHandler();
@@ -4940,6 +5956,14 @@ var Engine = class _Engine {
4940
5956
  return;
4941
5957
  }
4942
5958
  if (target.kind === "attr") {
5959
+ if (target.name === "text" && element instanceof HTMLElement) {
5960
+ element.innerText = value == null ? "" : String(value);
5961
+ return;
5962
+ }
5963
+ if (target.name === "content" && element instanceof HTMLElement) {
5964
+ element.textContent = value == null ? "" : String(value);
5965
+ return;
5966
+ }
4943
5967
  if (target.name === "value") {
4944
5968
  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
4945
5969
  element.value = value == null ? "" : String(value);
@@ -4970,6 +5994,12 @@ var Engine = class _Engine {
4970
5994
  }
4971
5995
  getDirectiveValue(element, target) {
4972
5996
  if (target.kind === "attr") {
5997
+ if (target.name === "text" && element instanceof HTMLElement) {
5998
+ return element.innerText;
5999
+ }
6000
+ if (target.name === "content" && element instanceof HTMLElement) {
6001
+ return element.textContent ?? "";
6002
+ }
4973
6003
  if (target.name === "value") {
4974
6004
  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
4975
6005
  return element.value;
@@ -5005,17 +6035,27 @@ var Engine = class _Engine {
5005
6035
  id: "vsn-bind",
5006
6036
  match: (name) => name.startsWith("vsn-bind"),
5007
6037
  handle: (element, name, value, scope) => {
5008
- const direction = this.parseBindDirection(name);
5009
- this.bindBindings.set(element, { expr: value, direction });
5010
- if (direction === "to" || direction === "both") {
6038
+ const parsedDirection = this.parseBindDirection(name);
6039
+ const config = this.resolveBindConfig(element, value, scope, parsedDirection);
6040
+ const direction = config.direction;
6041
+ const auto = parsedDirection === "auto";
6042
+ this.bindBindings.set(element, { expr: value, direction, auto });
6043
+ if (!auto && (direction === "to" || direction === "both")) {
5011
6044
  this.markInlineDeclaration(element, `state:${value}`);
5012
6045
  }
5013
- if (direction === "to" || direction === "both") {
6046
+ if (config.seedFromScope) {
6047
+ applyBindToElement(element, value, scope);
6048
+ }
6049
+ if (config.deferToScope) {
6050
+ this.pendingAutoBindToScope.push({ element, expr: value, scope });
6051
+ } else if (config.syncToScope) {
5014
6052
  applyBindToScope(element, value, scope);
6053
+ }
6054
+ if (direction === "to" || direction === "both") {
5015
6055
  this.attachBindInputHandler(element, value);
5016
6056
  }
5017
6057
  if (direction === "from" || direction === "both") {
5018
- this.watch(scope, value, () => applyBindToElement(element, value, scope));
6058
+ this.watch(scope, value, () => applyBindToElement(element, value, scope), element);
5019
6059
  }
5020
6060
  }
5021
6061
  });
@@ -5027,7 +6067,7 @@ var Engine = class _Engine {
5027
6067
  if (element instanceof HTMLElement) {
5028
6068
  applyIf(element, value, scope);
5029
6069
  }
5030
- this.watch(scope, value, () => this.evaluate(element));
6070
+ this.watch(scope, value, () => this.evaluate(element), element);
5031
6071
  }
5032
6072
  });
5033
6073
  this.registerAttributeHandler({
@@ -5038,7 +6078,7 @@ var Engine = class _Engine {
5038
6078
  if (element instanceof HTMLElement) {
5039
6079
  applyShow(element, value, scope);
5040
6080
  }
5041
- this.watch(scope, value, () => this.evaluate(element));
6081
+ this.watch(scope, value, () => this.evaluate(element), element);
5042
6082
  }
5043
6083
  });
5044
6084
  this.registerAttributeHandler({
@@ -5054,7 +6094,7 @@ var Engine = class _Engine {
5054
6094
  this.handleTrustedHtml(element);
5055
6095
  }
5056
6096
  }
5057
- this.watch(scope, value, () => this.evaluate(element));
6097
+ this.watch(scope, value, () => this.evaluate(element), element);
5058
6098
  }
5059
6099
  });
5060
6100
  this.registerAttributeHandler({
@@ -5067,7 +6107,7 @@ var Engine = class _Engine {
5067
6107
  }
5068
6108
  this.eachBindings.set(element, { ...config, rendered: [] });
5069
6109
  this.renderEach(element);
5070
- this.watch(scope, config.listExpr, () => this.renderEach(element));
6110
+ this.watch(scope, config.listExpr, () => this.renderEach(element), element);
5071
6111
  }
5072
6112
  });
5073
6113
  this.registerAttributeHandler({
@@ -5122,15 +6162,27 @@ function parseCFS(source) {
5122
6162
  const parser = new Parser(source);
5123
6163
  return parser.parseProgram();
5124
6164
  }
6165
+ if (typeof window !== "undefined") {
6166
+ window["parseCFS"] = parseCFS;
6167
+ }
5125
6168
  function autoMount(root = document) {
5126
6169
  if (typeof document === "undefined") {
5127
6170
  return null;
5128
6171
  }
5129
6172
  const engine = new Engine();
6173
+ globalThis.VSNEngine = engine;
5130
6174
  const startTime = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
5131
6175
  const mount = () => {
5132
6176
  const target = root instanceof Document ? root.body : root;
5133
6177
  if (target) {
6178
+ const plugins = globalThis.VSNPlugins;
6179
+ if (plugins && typeof plugins === "object") {
6180
+ for (const plugin of Object.values(plugins)) {
6181
+ if (typeof plugin === "function") {
6182
+ plugin(engine);
6183
+ }
6184
+ }
6185
+ }
5134
6186
  const sources = Array.from(document.querySelectorAll('script[type="text/vsn"]')).map((script) => script.textContent ?? "").join("\n");
5135
6187
  if (sources.trim()) {
5136
6188
  engine.registerBehaviors(sources);
@@ -5142,9 +6194,9 @@ function autoMount(root = document) {
5142
6194
  }
5143
6195
  };
5144
6196
  if (document.readyState === "loading") {
5145
- document.addEventListener("DOMContentLoaded", mount, { once: true });
6197
+ document.addEventListener("DOMContentLoaded", () => setTimeout(mount, 0), { once: true });
5146
6198
  } else {
5147
- mount();
6199
+ setTimeout(mount, 0);
5148
6200
  }
5149
6201
  return engine;
5150
6202
  }
@@ -5157,16 +6209,21 @@ if (typeof document !== "undefined") {
5157
6209
  export {
5158
6210
  ArrayExpression,
5159
6211
  ArrayPattern,
6212
+ AssertError,
6213
+ AssertNode,
5160
6214
  AssignmentNode,
5161
6215
  AwaitExpression,
5162
6216
  BaseNode,
5163
6217
  BehaviorNode,
5164
6218
  BinaryExpression,
5165
6219
  BlockNode,
6220
+ BreakNode,
5166
6221
  CallExpression,
6222
+ ContinueNode,
5167
6223
  DeclarationNode,
5168
6224
  DirectiveExpression,
5169
6225
  Engine,
6226
+ ForEachNode,
5170
6227
  ForNode,
5171
6228
  FunctionDeclarationNode,
5172
6229
  FunctionExpression,
@@ -5186,8 +6243,6 @@ export {
5186
6243
  ReturnNode,
5187
6244
  SelectorNode,
5188
6245
  SpreadElement,
5189
- StateBlockNode,
5190
- StateEntryNode,
5191
6246
  TemplateExpression,
5192
6247
  TernaryExpression,
5193
6248
  TokenType,