@optionfactory/fml 9.0.0-rc3 → 9.0.0-rc31

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.
Files changed (44) hide show
  1. package/README.md +16 -88
  2. package/dist/client-errors.iife.js +40 -49
  3. package/dist/client-errors.iife.js.map +1 -1
  4. package/dist/client-errors.iife.min.js +1 -1
  5. package/dist/client-errors.iife.min.js.map +1 -1
  6. package/dist/custom-elements.json +491 -51
  7. package/dist/fml.css +25 -20
  8. package/dist/fml.css.map +1 -1
  9. package/dist/fml.iife.js +5866 -2563
  10. package/dist/fml.iife.js.map +1 -1
  11. package/dist/fml.iife.min.js +1 -1
  12. package/dist/fml.iife.min.js.map +1 -1
  13. package/dist/ftl.d.mts +425 -231
  14. package/dist/ftl.iife.js +774 -1044
  15. package/dist/ftl.iife.js.map +1 -1
  16. package/dist/ftl.iife.min.js +1 -1
  17. package/dist/ftl.iife.min.js.map +1 -1
  18. package/dist/ftl.min.mjs +1 -1
  19. package/dist/ftl.min.mjs.map +1 -1
  20. package/dist/ftl.mjs +774 -1044
  21. package/dist/ftl.mjs.map +1 -1
  22. package/dist/ful.css +25 -20
  23. package/dist/ful.css.map +1 -1
  24. package/dist/ful.d.mts +3048 -556
  25. package/dist/ful.iife.js +4977 -1395
  26. package/dist/ful.iife.js.map +1 -1
  27. package/dist/ful.iife.min.js +1 -1
  28. package/dist/ful.iife.min.js.map +1 -1
  29. package/dist/ful.min.mjs +1 -1
  30. package/dist/ful.min.mjs.map +1 -1
  31. package/dist/ful.mjs +4975 -1396
  32. package/dist/ful.mjs.map +1 -1
  33. package/dist/httpc.d.mts +143 -123
  34. package/dist/httpc.iife.js +163 -175
  35. package/dist/httpc.iife.js.map +1 -1
  36. package/dist/httpc.iife.min.js +1 -1
  37. package/dist/httpc.iife.min.js.map +1 -1
  38. package/dist/httpc.min.mjs +1 -1
  39. package/dist/httpc.min.mjs.map +1 -1
  40. package/dist/httpc.mjs +163 -175
  41. package/dist/httpc.mjs.map +1 -1
  42. package/dist/vscode.html-custom-data.json +239 -39
  43. package/dist/web-types.json +498 -51
  44. package/package.json +2 -2
package/dist/ftl.iife.js CHANGED
@@ -2,12 +2,8 @@ var ftl = (function (exports) {
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * A Map bounded by entry count, evicting in insertion order: not an LRU (a hit
6
- * does not refresh an entry), just a cap keeping unbounded key spaces (parsed
7
- * expressions, compiled masks, formatter instances) from growing forever. The
8
- * capacity is sized so eviction never happens on a sane page: hitting it means
9
- * dynamically generated keys, where FIFO's worst case (evicting a hot entry)
10
- * costs one recomputation.
5
+ * A Map bounded by entry count, evicting the oldest entry when full. A hit does
6
+ * not refresh an entry: eviction follows insertion order, not use.
11
7
  */
12
8
  class BoundedCache {
13
9
  #max;
@@ -36,6 +32,7 @@ var ftl = (function (exports) {
36
32
  this.#entries.set(key, value);
37
33
  return value;
38
34
  }
35
+ /** @returns {number} the number of entries held */
39
36
  get size() {
40
37
  return this.#entries.size;
41
38
  }
@@ -74,14 +71,10 @@ var ftl = (function (exports) {
74
71
  },
75
72
  };
76
73
 
77
- // @generated by Peggy 5.1.0.
74
+ // @generated by Peggy 5.1.1.
78
75
  //
79
76
  // https://peggyjs.org/
80
77
 
81
- //keyword classification lives here, not in the grammar's ordered choice: the
82
- //identifier rule consumes the whole word and the action decides, so no keyword
83
- //can grab a longer identifier's prefix. A Map, not a plain object: `in` on the
84
- //latter would classify inherited names such as `constructor` as keywords
85
78
  const keywords = new Map([
86
79
  ['true', true],
87
80
  ['false', false],
@@ -144,10 +137,9 @@ var ftl = (function (exports) {
144
137
  return s;
145
138
  }
146
139
 
147
- function literalEscape(s) {
140
+ function escape(s, special) {
148
141
  return unicodeEscape(s
149
- .replace(/\\/g, "\\\\")
150
- .replace(/"/g, "\\\"")
142
+ .replace(special, "\\$&")
151
143
  .replace(/\0/g, "\\0")
152
144
  .replace(/\t/g, "\\t")
153
145
  .replace(/\n/g, "\\n")
@@ -156,18 +148,12 @@ var ftl = (function (exports) {
156
148
  .replace(/[\x10-\x1F\x7F-\x9F]/g, ch => "\\x" + hex(ch)));
157
149
  }
158
150
 
151
+ function literalEscape(s) {
152
+ return escape(s, /[\\"]/g);
153
+ }
154
+
159
155
  function classEscape(s) {
160
- return unicodeEscape(s
161
- .replace(/\\/g, "\\\\")
162
- .replace(/\]/g, "\\]")
163
- .replace(/\^/g, "\\^")
164
- .replace(/-/g, "\\-")
165
- .replace(/\0/g, "\\0")
166
- .replace(/\t/g, "\\t")
167
- .replace(/\n/g, "\\n")
168
- .replace(/\r/g, "\\r")
169
- .replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
170
- .replace(/[\x10-\x1F\x7F-\x9F]/g, ch => "\\x" + hex(ch)));
156
+ return escape(s, /[\\\]^-]/g);
171
157
  }
172
158
 
173
159
  const DESCRIBE_EXPECTATION_FNS = {
@@ -285,14 +271,6 @@ var ftl = (function (exports) {
285
271
  const peg$c53 = "<=";
286
272
  const peg$c54 = "<";
287
273
 
288
- const peg$r0 = /^[^\\{]/;
289
- const peg$r1 = /^[0-9]/;
290
- const peg$r2 = /^[^']/;
291
- const peg$r3 = /^[^"]/;
292
- const peg$r4 = /^[^\\`{]/;
293
- const peg$r5 = /^[a-zA-Z$_]/;
294
- const peg$r6 = /^[a-zA-Z$_0-9]/;
295
- const peg$r7 = /^[ \t\n\r]/;
296
274
 
297
275
  const peg$e0 = peg$literalExpectation("{{{{", false);
298
276
  const peg$e1 = peg$literalExpectation("}}}}", false);
@@ -450,9 +428,10 @@ var ftl = (function (exports) {
450
428
  let peg$maxFailExpected = options.peg$maxFailExpected || [];
451
429
  let peg$silentFails = options.peg$silentFails | 0;
452
430
 
453
- let peg$resultsCache = {};
431
+ let peg$resultsCache = new Map();
454
432
 
455
433
  let peg$result;
434
+ let peg$charCode;
456
435
 
457
436
  if (options.startRule) {
458
437
  if (!(options.startRule in peg$startRuleFunctions)) {
@@ -571,7 +550,7 @@ var ftl = (function (exports) {
571
550
  let s0, s1;
572
551
 
573
552
  const key = peg$currPos * 38 + 0;
574
- const cached = peg$resultsCache[key];
553
+ const cached = peg$resultsCache.get(key);
575
554
 
576
555
  if (cached) {
577
556
  peg$currPos = cached.nextPos;
@@ -580,13 +559,13 @@ var ftl = (function (exports) {
580
559
  }
581
560
 
582
561
  s0 = [];
583
- s1 = peg$parseTemplatedExpression();
584
- while (s1 !== peg$FAILED) {
585
- s0.push(s1);
562
+ for (;;) {
586
563
  s1 = peg$parseTemplatedExpression();
564
+ if (s1 === peg$FAILED) { break; }
565
+ s0.push(s1);
587
566
  }
588
567
 
589
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
568
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
590
569
 
591
570
  return s0;
592
571
  }
@@ -595,7 +574,7 @@ var ftl = (function (exports) {
595
574
  let s0, s1, s2, s3, s4;
596
575
 
597
576
  const key = peg$currPos * 38 + 1;
598
- const cached = peg$resultsCache[key];
577
+ const cached = peg$resultsCache.get(key);
599
578
 
600
579
  if (cached) {
601
580
  peg$currPos = cached.nextPos;
@@ -691,7 +670,7 @@ var ftl = (function (exports) {
691
670
  }
692
671
  }
693
672
 
694
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
673
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
695
674
 
696
675
  return s0;
697
676
  }
@@ -700,7 +679,7 @@ var ftl = (function (exports) {
700
679
  let s0;
701
680
 
702
681
  const key = peg$currPos * 38 + 2;
703
- const cached = peg$resultsCache[key];
682
+ const cached = peg$resultsCache.get(key);
704
683
 
705
684
  if (cached) {
706
685
  peg$currPos = cached.nextPos;
@@ -716,7 +695,7 @@ var ftl = (function (exports) {
716
695
  if (peg$silentFails === 0) { peg$fail(peg$e0); }
717
696
  }
718
697
 
719
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
698
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
720
699
 
721
700
  return s0;
722
701
  }
@@ -725,7 +704,7 @@ var ftl = (function (exports) {
725
704
  let s0;
726
705
 
727
706
  const key = peg$currPos * 38 + 3;
728
- const cached = peg$resultsCache[key];
707
+ const cached = peg$resultsCache.get(key);
729
708
 
730
709
  if (cached) {
731
710
  peg$currPos = cached.nextPos;
@@ -741,7 +720,7 @@ var ftl = (function (exports) {
741
720
  if (peg$silentFails === 0) { peg$fail(peg$e1); }
742
721
  }
743
722
 
744
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
723
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
745
724
 
746
725
  return s0;
747
726
  }
@@ -750,7 +729,7 @@ var ftl = (function (exports) {
750
729
  let s0;
751
730
 
752
731
  const key = peg$currPos * 38 + 4;
753
- const cached = peg$resultsCache[key];
732
+ const cached = peg$resultsCache.get(key);
754
733
 
755
734
  if (cached) {
756
735
  peg$currPos = cached.nextPos;
@@ -766,7 +745,7 @@ var ftl = (function (exports) {
766
745
  if (peg$silentFails === 0) { peg$fail(peg$e2); }
767
746
  }
768
747
 
769
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
748
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
770
749
 
771
750
  return s0;
772
751
  }
@@ -775,7 +754,7 @@ var ftl = (function (exports) {
775
754
  let s0;
776
755
 
777
756
  const key = peg$currPos * 38 + 5;
778
- const cached = peg$resultsCache[key];
757
+ const cached = peg$resultsCache.get(key);
779
758
 
780
759
  if (cached) {
781
760
  peg$currPos = cached.nextPos;
@@ -791,7 +770,7 @@ var ftl = (function (exports) {
791
770
  if (peg$silentFails === 0) { peg$fail(peg$e3); }
792
771
  }
793
772
 
794
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
773
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
795
774
 
796
775
  return s0;
797
776
  }
@@ -800,7 +779,7 @@ var ftl = (function (exports) {
800
779
  let s0;
801
780
 
802
781
  const key = peg$currPos * 38 + 6;
803
- const cached = peg$resultsCache[key];
782
+ const cached = peg$resultsCache.get(key);
804
783
 
805
784
  if (cached) {
806
785
  peg$currPos = cached.nextPos;
@@ -816,7 +795,7 @@ var ftl = (function (exports) {
816
795
  if (peg$silentFails === 0) { peg$fail(peg$e4); }
817
796
  }
818
797
 
819
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
798
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
820
799
 
821
800
  return s0;
822
801
  }
@@ -825,7 +804,7 @@ var ftl = (function (exports) {
825
804
  let s0;
826
805
 
827
806
  const key = peg$currPos * 38 + 7;
828
- const cached = peg$resultsCache[key];
807
+ const cached = peg$resultsCache.get(key);
829
808
 
830
809
  if (cached) {
831
810
  peg$currPos = cached.nextPos;
@@ -841,7 +820,7 @@ var ftl = (function (exports) {
841
820
  if (peg$silentFails === 0) { peg$fail(peg$e5); }
842
821
  }
843
822
 
844
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
823
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
845
824
 
846
825
  return s0;
847
826
  }
@@ -850,7 +829,7 @@ var ftl = (function (exports) {
850
829
  let s0, s1, s2, s3, s4, s5, s6;
851
830
 
852
831
  const key = peg$currPos * 38 + 8;
853
- const cached = peg$resultsCache[key];
832
+ const cached = peg$resultsCache.get(key);
854
833
 
855
834
  if (cached) {
856
835
  peg$currPos = cached.nextPos;
@@ -860,211 +839,104 @@ var ftl = (function (exports) {
860
839
 
861
840
  s0 = peg$currPos;
862
841
  s1 = [];
863
- s2 = peg$currPos;
864
- s3 = [];
865
- s4 = input.charAt(peg$currPos);
866
- if (peg$r0.test(s4)) {
867
- peg$currPos++;
868
- } else {
869
- s4 = peg$FAILED;
870
- if (peg$silentFails === 0) { peg$fail(peg$e6); }
871
- }
872
- if (s4 !== peg$FAILED) {
873
- while (s4 !== peg$FAILED) {
874
- s3.push(s4);
842
+ for (;;) {
843
+ s2 = peg$currPos;
844
+ s3 = [];
845
+ for (;;) {
875
846
  s4 = input.charAt(peg$currPos);
876
- if (peg$r0.test(s4)) {
847
+ if (s4 !== "" && !(peg$charCode = s4.charCodeAt(0), peg$charCode === 92 || peg$charCode === 123)) {
877
848
  peg$currPos++;
878
849
  } else {
879
850
  s4 = peg$FAILED;
880
851
  if (peg$silentFails === 0) { peg$fail(peg$e6); }
881
852
  }
853
+ if (s4 === peg$FAILED) { break; }
854
+ s3.push(s4);
882
855
  }
883
- } else {
884
- s3 = peg$FAILED;
885
- }
886
- if (s3 !== peg$FAILED) {
887
- s2 = input.substring(s2, peg$currPos);
888
- } else {
889
- s2 = s3;
890
- }
891
- if (s2 === peg$FAILED) {
892
- s2 = peg$currPos;
893
- if (input.charCodeAt(peg$currPos) === 92) {
894
- s3 = peg$c7;
895
- peg$currPos++;
896
- } else {
856
+ if (s3.length < 1) {
897
857
  s3 = peg$FAILED;
898
- if (peg$silentFails === 0) { peg$fail(peg$e7); }
899
858
  }
900
859
  if (s3 !== peg$FAILED) {
901
- if (input.length > peg$currPos) {
902
- s4 = input.charAt(peg$currPos);
903
- peg$currPos++;
904
- } else {
905
- s4 = peg$FAILED;
906
- if (peg$silentFails === 0) { peg$fail(peg$e8); }
907
- }
908
- if (s4 !== peg$FAILED) {
909
- s2 = peg$f4(s4);
910
- } else {
911
- peg$currPos = s2;
912
- s2 = peg$FAILED;
913
- }
860
+ s2 = input.substring(s2, peg$currPos);
914
861
  } else {
915
- peg$currPos = s2;
916
- s2 = peg$FAILED;
862
+ s2 = s3;
917
863
  }
918
864
  if (s2 === peg$FAILED) {
919
865
  s2 = peg$currPos;
920
- s3 = peg$currPos;
921
- if (input.charCodeAt(peg$currPos) === 123) {
922
- s4 = peg$c9;
866
+ if (input.charCodeAt(peg$currPos) === 92) {
867
+ s3 = peg$c7;
923
868
  peg$currPos++;
924
869
  } else {
925
- s4 = peg$FAILED;
926
- if (peg$silentFails === 0) { peg$fail(peg$e9); }
870
+ s3 = peg$FAILED;
871
+ if (peg$silentFails === 0) { peg$fail(peg$e7); }
927
872
  }
928
- if (s4 !== peg$FAILED) {
929
- s5 = peg$currPos;
930
- peg$silentFails++;
931
- if (input.charCodeAt(peg$currPos) === 123) {
932
- s6 = peg$c9;
873
+ if (s3 !== peg$FAILED) {
874
+ if (input.length > peg$currPos) {
875
+ s4 = input.charAt(peg$currPos);
933
876
  peg$currPos++;
934
877
  } else {
935
- s6 = peg$FAILED;
936
- if (peg$silentFails === 0) { peg$fail(peg$e9); }
937
- }
938
- peg$silentFails--;
939
- if (s6 === peg$FAILED) {
940
- s5 = undefined;
941
- } else {
942
- peg$currPos = s5;
943
- s5 = peg$FAILED;
878
+ s4 = peg$FAILED;
879
+ if (peg$silentFails === 0) { peg$fail(peg$e8); }
944
880
  }
945
- if (s5 !== peg$FAILED) {
946
- s4 = [s4, s5];
947
- s3 = s4;
881
+ if (s4 !== peg$FAILED) {
882
+ s2 = peg$f4(s4);
948
883
  } else {
949
- peg$currPos = s3;
950
- s3 = peg$FAILED;
951
- }
952
- } else {
953
- peg$currPos = s3;
954
- s3 = peg$FAILED;
955
- }
956
- if (s3 !== peg$FAILED) {
957
- s2 = input.substring(s2, peg$currPos);
958
- } else {
959
- s2 = s3;
960
- }
961
- }
962
- }
963
- if (s2 !== peg$FAILED) {
964
- while (s2 !== peg$FAILED) {
965
- s1.push(s2);
966
- s2 = peg$currPos;
967
- s3 = [];
968
- s4 = input.charAt(peg$currPos);
969
- if (peg$r0.test(s4)) {
970
- peg$currPos++;
971
- } else {
972
- s4 = peg$FAILED;
973
- if (peg$silentFails === 0) { peg$fail(peg$e6); }
974
- }
975
- if (s4 !== peg$FAILED) {
976
- while (s4 !== peg$FAILED) {
977
- s3.push(s4);
978
- s4 = input.charAt(peg$currPos);
979
- if (peg$r0.test(s4)) {
980
- peg$currPos++;
981
- } else {
982
- s4 = peg$FAILED;
983
- if (peg$silentFails === 0) { peg$fail(peg$e6); }
984
- }
884
+ peg$currPos = s2;
885
+ s2 = peg$FAILED;
985
886
  }
986
887
  } else {
987
- s3 = peg$FAILED;
988
- }
989
- if (s3 !== peg$FAILED) {
990
- s2 = input.substring(s2, peg$currPos);
991
- } else {
992
- s2 = s3;
888
+ peg$currPos = s2;
889
+ s2 = peg$FAILED;
993
890
  }
994
891
  if (s2 === peg$FAILED) {
995
892
  s2 = peg$currPos;
996
- if (input.charCodeAt(peg$currPos) === 92) {
997
- s3 = peg$c7;
893
+ s3 = peg$currPos;
894
+ if (input.charCodeAt(peg$currPos) === 123) {
895
+ s4 = peg$c9;
998
896
  peg$currPos++;
999
897
  } else {
1000
- s3 = peg$FAILED;
1001
- if (peg$silentFails === 0) { peg$fail(peg$e7); }
1002
- }
1003
- if (s3 !== peg$FAILED) {
1004
- if (input.length > peg$currPos) {
1005
- s4 = input.charAt(peg$currPos);
1006
- peg$currPos++;
1007
- } else {
1008
- s4 = peg$FAILED;
1009
- if (peg$silentFails === 0) { peg$fail(peg$e8); }
1010
- }
1011
- if (s4 !== peg$FAILED) {
1012
- s2 = peg$f4(s4);
1013
- } else {
1014
- peg$currPos = s2;
1015
- s2 = peg$FAILED;
1016
- }
1017
- } else {
1018
- peg$currPos = s2;
1019
- s2 = peg$FAILED;
898
+ s4 = peg$FAILED;
899
+ if (peg$silentFails === 0) { peg$fail(peg$e9); }
1020
900
  }
1021
- if (s2 === peg$FAILED) {
1022
- s2 = peg$currPos;
1023
- s3 = peg$currPos;
901
+ if (s4 !== peg$FAILED) {
902
+ s5 = peg$currPos;
903
+ peg$silentFails++;
1024
904
  if (input.charCodeAt(peg$currPos) === 123) {
1025
- s4 = peg$c9;
905
+ s6 = peg$c9;
1026
906
  peg$currPos++;
1027
907
  } else {
1028
- s4 = peg$FAILED;
908
+ s6 = peg$FAILED;
1029
909
  if (peg$silentFails === 0) { peg$fail(peg$e9); }
1030
910
  }
1031
- if (s4 !== peg$FAILED) {
1032
- s5 = peg$currPos;
1033
- peg$silentFails++;
1034
- if (input.charCodeAt(peg$currPos) === 123) {
1035
- s6 = peg$c9;
1036
- peg$currPos++;
1037
- } else {
1038
- s6 = peg$FAILED;
1039
- if (peg$silentFails === 0) { peg$fail(peg$e9); }
1040
- }
1041
- peg$silentFails--;
1042
- if (s6 === peg$FAILED) {
1043
- s5 = undefined;
1044
- } else {
1045
- peg$currPos = s5;
1046
- s5 = peg$FAILED;
1047
- }
1048
- if (s5 !== peg$FAILED) {
1049
- s4 = [s4, s5];
1050
- s3 = s4;
1051
- } else {
1052
- peg$currPos = s3;
1053
- s3 = peg$FAILED;
1054
- }
911
+ peg$silentFails--;
912
+ if (s6 === peg$FAILED) {
913
+ s5 = undefined;
1055
914
  } else {
1056
- peg$currPos = s3;
1057
- s3 = peg$FAILED;
915
+ peg$currPos = s5;
916
+ s5 = peg$FAILED;
1058
917
  }
1059
- if (s3 !== peg$FAILED) {
1060
- s2 = input.substring(s2, peg$currPos);
918
+ if (s5 !== peg$FAILED) {
919
+ s4 = [s4, s5];
920
+ s3 = s4;
1061
921
  } else {
1062
- s2 = s3;
922
+ peg$currPos = s3;
923
+ s3 = peg$FAILED;
1063
924
  }
925
+ } else {
926
+ peg$currPos = s3;
927
+ s3 = peg$FAILED;
928
+ }
929
+ if (s3 !== peg$FAILED) {
930
+ s2 = input.substring(s2, peg$currPos);
931
+ } else {
932
+ s2 = s3;
1064
933
  }
1065
934
  }
1066
935
  }
1067
- } else {
936
+ if (s2 === peg$FAILED) { break; }
937
+ s1.push(s2);
938
+ }
939
+ if (s1.length < 1) {
1068
940
  s1 = peg$FAILED;
1069
941
  }
1070
942
  if (s1 !== peg$FAILED) {
@@ -1072,7 +944,7 @@ var ftl = (function (exports) {
1072
944
  }
1073
945
  s0 = s1;
1074
946
 
1075
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
947
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1076
948
 
1077
949
  return s0;
1078
950
  }
@@ -1081,7 +953,7 @@ var ftl = (function (exports) {
1081
953
  let s0, s2;
1082
954
 
1083
955
  const key = peg$currPos * 38 + 9;
1084
- const cached = peg$resultsCache[key];
956
+ const cached = peg$resultsCache.get(key);
1085
957
 
1086
958
  if (cached) {
1087
959
  peg$currPos = cached.nextPos;
@@ -1100,7 +972,7 @@ var ftl = (function (exports) {
1100
972
  s0 = peg$FAILED;
1101
973
  }
1102
974
 
1103
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
975
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1104
976
 
1105
977
  return s0;
1106
978
  }
@@ -1109,7 +981,7 @@ var ftl = (function (exports) {
1109
981
  let s0, s1, s3, s5, s7, s9;
1110
982
 
1111
983
  const key = peg$currPos * 38 + 10;
1112
- const cached = peg$resultsCache[key];
984
+ const cached = peg$resultsCache.get(key);
1113
985
 
1114
986
  if (cached) {
1115
987
  peg$currPos = cached.nextPos;
@@ -1169,7 +1041,7 @@ var ftl = (function (exports) {
1169
1041
  s0 = peg$parseElvisExpression();
1170
1042
  }
1171
1043
 
1172
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1044
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1173
1045
 
1174
1046
  return s0;
1175
1047
  }
@@ -1178,7 +1050,7 @@ var ftl = (function (exports) {
1178
1050
  let s0, s1, s3, s5;
1179
1051
 
1180
1052
  const key = peg$currPos * 38 + 11;
1181
- const cached = peg$resultsCache[key];
1053
+ const cached = peg$resultsCache.get(key);
1182
1054
 
1183
1055
  if (cached) {
1184
1056
  peg$currPos = cached.nextPos;
@@ -1218,7 +1090,7 @@ var ftl = (function (exports) {
1218
1090
  s0 = peg$parseNullCoalescingExpression();
1219
1091
  }
1220
1092
 
1221
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1093
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1222
1094
 
1223
1095
  return s0;
1224
1096
  }
@@ -1227,7 +1099,7 @@ var ftl = (function (exports) {
1227
1099
  let s0, s1, s3, s5;
1228
1100
 
1229
1101
  const key = peg$currPos * 38 + 12;
1230
- const cached = peg$resultsCache[key];
1102
+ const cached = peg$resultsCache.get(key);
1231
1103
 
1232
1104
  if (cached) {
1233
1105
  peg$currPos = cached.nextPos;
@@ -1267,7 +1139,7 @@ var ftl = (function (exports) {
1267
1139
  s0 = peg$parseOrExpression();
1268
1140
  }
1269
1141
 
1270
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1142
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1271
1143
 
1272
1144
  return s0;
1273
1145
  }
@@ -1276,7 +1148,7 @@ var ftl = (function (exports) {
1276
1148
  let s0, s1, s2, s3, s5, s7;
1277
1149
 
1278
1150
  const key = peg$currPos * 38 + 13;
1279
- const cached = peg$resultsCache[key];
1151
+ const cached = peg$resultsCache.get(key);
1280
1152
 
1281
1153
  if (cached) {
1282
1154
  peg$currPos = cached.nextPos;
@@ -1288,30 +1160,7 @@ var ftl = (function (exports) {
1288
1160
  s1 = peg$parseAndExpression();
1289
1161
  if (s1 !== peg$FAILED) {
1290
1162
  s2 = [];
1291
- s3 = peg$currPos;
1292
- peg$parse_();
1293
- if (input.substr(peg$currPos, 2) === peg$c22) {
1294
- s5 = peg$c22;
1295
- peg$currPos += 2;
1296
- } else {
1297
- s5 = peg$FAILED;
1298
- if (peg$silentFails === 0) { peg$fail(peg$e14); }
1299
- }
1300
- if (s5 !== peg$FAILED) {
1301
- peg$parse_();
1302
- s7 = peg$parseAndExpression();
1303
- if (s7 !== peg$FAILED) {
1304
- s3 = s7;
1305
- } else {
1306
- peg$currPos = s3;
1307
- s3 = peg$FAILED;
1308
- }
1309
- } else {
1310
- peg$currPos = s3;
1311
- s3 = peg$FAILED;
1312
- }
1313
- while (s3 !== peg$FAILED) {
1314
- s2.push(s3);
1163
+ for (;;) {
1315
1164
  s3 = peg$currPos;
1316
1165
  peg$parse_();
1317
1166
  if (input.substr(peg$currPos, 2) === peg$c22) {
@@ -1334,6 +1183,8 @@ var ftl = (function (exports) {
1334
1183
  peg$currPos = s3;
1335
1184
  s3 = peg$FAILED;
1336
1185
  }
1186
+ if (s3 === peg$FAILED) { break; }
1187
+ s2.push(s3);
1337
1188
  }
1338
1189
  s0 = peg$f10(s1, s2);
1339
1190
  } else {
@@ -1341,7 +1192,7 @@ var ftl = (function (exports) {
1341
1192
  s0 = peg$FAILED;
1342
1193
  }
1343
1194
 
1344
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1195
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1345
1196
 
1346
1197
  return s0;
1347
1198
  }
@@ -1350,7 +1201,7 @@ var ftl = (function (exports) {
1350
1201
  let s0, s1, s2, s3, s5, s7;
1351
1202
 
1352
1203
  const key = peg$currPos * 38 + 14;
1353
- const cached = peg$resultsCache[key];
1204
+ const cached = peg$resultsCache.get(key);
1354
1205
 
1355
1206
  if (cached) {
1356
1207
  peg$currPos = cached.nextPos;
@@ -1362,30 +1213,7 @@ var ftl = (function (exports) {
1362
1213
  s1 = peg$parseEqExpression();
1363
1214
  if (s1 !== peg$FAILED) {
1364
1215
  s2 = [];
1365
- s3 = peg$currPos;
1366
- peg$parse_();
1367
- if (input.substr(peg$currPos, 2) === peg$c24) {
1368
- s5 = peg$c24;
1369
- peg$currPos += 2;
1370
- } else {
1371
- s5 = peg$FAILED;
1372
- if (peg$silentFails === 0) { peg$fail(peg$e15); }
1373
- }
1374
- if (s5 !== peg$FAILED) {
1375
- peg$parse_();
1376
- s7 = peg$parseEqExpression();
1377
- if (s7 !== peg$FAILED) {
1378
- s3 = s7;
1379
- } else {
1380
- peg$currPos = s3;
1381
- s3 = peg$FAILED;
1382
- }
1383
- } else {
1384
- peg$currPos = s3;
1385
- s3 = peg$FAILED;
1386
- }
1387
- while (s3 !== peg$FAILED) {
1388
- s2.push(s3);
1216
+ for (;;) {
1389
1217
  s3 = peg$currPos;
1390
1218
  peg$parse_();
1391
1219
  if (input.substr(peg$currPos, 2) === peg$c24) {
@@ -1408,6 +1236,8 @@ var ftl = (function (exports) {
1408
1236
  peg$currPos = s3;
1409
1237
  s3 = peg$FAILED;
1410
1238
  }
1239
+ if (s3 === peg$FAILED) { break; }
1240
+ s2.push(s3);
1411
1241
  }
1412
1242
  s0 = peg$f11(s1, s2);
1413
1243
  } else {
@@ -1415,7 +1245,7 @@ var ftl = (function (exports) {
1415
1245
  s0 = peg$FAILED;
1416
1246
  }
1417
1247
 
1418
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1248
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1419
1249
 
1420
1250
  return s0;
1421
1251
  }
@@ -1424,7 +1254,7 @@ var ftl = (function (exports) {
1424
1254
  let s0, s1, s2, s3, s5, s7;
1425
1255
 
1426
1256
  const key = peg$currPos * 38 + 15;
1427
- const cached = peg$resultsCache[key];
1257
+ const cached = peg$resultsCache.get(key);
1428
1258
 
1429
1259
  if (cached) {
1430
1260
  peg$currPos = cached.nextPos;
@@ -1436,24 +1266,7 @@ var ftl = (function (exports) {
1436
1266
  s1 = peg$parseRelExpression();
1437
1267
  if (s1 !== peg$FAILED) {
1438
1268
  s2 = [];
1439
- s3 = peg$currPos;
1440
- peg$parse_();
1441
- s5 = peg$parseEqualityOp();
1442
- if (s5 !== peg$FAILED) {
1443
- peg$parse_();
1444
- s7 = peg$parseRelExpression();
1445
- if (s7 !== peg$FAILED) {
1446
- s3 = peg$f12(s1, s5, s7);
1447
- } else {
1448
- peg$currPos = s3;
1449
- s3 = peg$FAILED;
1450
- }
1451
- } else {
1452
- peg$currPos = s3;
1453
- s3 = peg$FAILED;
1454
- }
1455
- while (s3 !== peg$FAILED) {
1456
- s2.push(s3);
1269
+ for (;;) {
1457
1270
  s3 = peg$currPos;
1458
1271
  peg$parse_();
1459
1272
  s5 = peg$parseEqualityOp();
@@ -1470,6 +1283,8 @@ var ftl = (function (exports) {
1470
1283
  peg$currPos = s3;
1471
1284
  s3 = peg$FAILED;
1472
1285
  }
1286
+ if (s3 === peg$FAILED) { break; }
1287
+ s2.push(s3);
1473
1288
  }
1474
1289
  s0 = peg$f13(s1, s2);
1475
1290
  } else {
@@ -1477,7 +1292,7 @@ var ftl = (function (exports) {
1477
1292
  s0 = peg$FAILED;
1478
1293
  }
1479
1294
 
1480
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1295
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1481
1296
 
1482
1297
  return s0;
1483
1298
  }
@@ -1486,7 +1301,7 @@ var ftl = (function (exports) {
1486
1301
  let s0, s1, s2, s3, s5, s7;
1487
1302
 
1488
1303
  const key = peg$currPos * 38 + 16;
1489
- const cached = peg$resultsCache[key];
1304
+ const cached = peg$resultsCache.get(key);
1490
1305
 
1491
1306
  if (cached) {
1492
1307
  peg$currPos = cached.nextPos;
@@ -1498,24 +1313,7 @@ var ftl = (function (exports) {
1498
1313
  s1 = peg$parseNotExpression();
1499
1314
  if (s1 !== peg$FAILED) {
1500
1315
  s2 = [];
1501
- s3 = peg$currPos;
1502
- peg$parse_();
1503
- s5 = peg$parseRelationalOp();
1504
- if (s5 !== peg$FAILED) {
1505
- peg$parse_();
1506
- s7 = peg$parseNotExpression();
1507
- if (s7 !== peg$FAILED) {
1508
- s3 = peg$f14(s1, s5, s7);
1509
- } else {
1510
- peg$currPos = s3;
1511
- s3 = peg$FAILED;
1512
- }
1513
- } else {
1514
- peg$currPos = s3;
1515
- s3 = peg$FAILED;
1516
- }
1517
- while (s3 !== peg$FAILED) {
1518
- s2.push(s3);
1316
+ for (;;) {
1519
1317
  s3 = peg$currPos;
1520
1318
  peg$parse_();
1521
1319
  s5 = peg$parseRelationalOp();
@@ -1532,6 +1330,8 @@ var ftl = (function (exports) {
1532
1330
  peg$currPos = s3;
1533
1331
  s3 = peg$FAILED;
1534
1332
  }
1333
+ if (s3 === peg$FAILED) { break; }
1334
+ s2.push(s3);
1535
1335
  }
1536
1336
  s0 = peg$f15(s1, s2);
1537
1337
  } else {
@@ -1539,7 +1339,7 @@ var ftl = (function (exports) {
1539
1339
  s0 = peg$FAILED;
1540
1340
  }
1541
1341
 
1542
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1342
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1543
1343
 
1544
1344
  return s0;
1545
1345
  }
@@ -1548,7 +1348,7 @@ var ftl = (function (exports) {
1548
1348
  let s0, s1, s3;
1549
1349
 
1550
1350
  const key = peg$currPos * 38 + 17;
1551
- const cached = peg$resultsCache[key];
1351
+ const cached = peg$resultsCache.get(key);
1552
1352
 
1553
1353
  if (cached) {
1554
1354
  peg$currPos = cached.nextPos;
@@ -1581,7 +1381,7 @@ var ftl = (function (exports) {
1581
1381
  s0 = peg$parseAccess();
1582
1382
  }
1583
1383
 
1584
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1384
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1585
1385
 
1586
1386
  return s0;
1587
1387
  }
@@ -1590,7 +1390,7 @@ var ftl = (function (exports) {
1590
1390
  let s0, s1, s3, s4, s5;
1591
1391
 
1592
1392
  const key = peg$currPos * 38 + 18;
1593
- const cached = peg$resultsCache[key];
1393
+ const cached = peg$resultsCache.get(key);
1594
1394
 
1595
1395
  if (cached) {
1596
1396
  peg$currPos = cached.nextPos;
@@ -1603,29 +1403,20 @@ var ftl = (function (exports) {
1603
1403
  if (s1 !== peg$FAILED) {
1604
1404
  peg$parse_();
1605
1405
  s3 = [];
1606
- s4 = peg$currPos;
1607
- s5 = peg$parseAccessExpression();
1608
- if (s5 !== peg$FAILED) {
1609
- peg$parse_();
1610
- s4 = s5;
1611
- } else {
1612
- peg$currPos = s4;
1613
- s4 = peg$FAILED;
1614
- }
1615
- if (s4 !== peg$FAILED) {
1616
- while (s4 !== peg$FAILED) {
1617
- s3.push(s4);
1618
- s4 = peg$currPos;
1619
- s5 = peg$parseAccessExpression();
1620
- if (s5 !== peg$FAILED) {
1621
- peg$parse_();
1622
- s4 = s5;
1623
- } else {
1624
- peg$currPos = s4;
1625
- s4 = peg$FAILED;
1626
- }
1406
+ for (;;) {
1407
+ s4 = peg$currPos;
1408
+ s5 = peg$parseAccessExpression();
1409
+ if (s5 !== peg$FAILED) {
1410
+ peg$parse_();
1411
+ s4 = s5;
1412
+ } else {
1413
+ peg$currPos = s4;
1414
+ s4 = peg$FAILED;
1627
1415
  }
1628
- } else {
1416
+ if (s4 === peg$FAILED) { break; }
1417
+ s3.push(s4);
1418
+ }
1419
+ if (s3.length < 1) {
1629
1420
  s3 = peg$FAILED;
1630
1421
  }
1631
1422
  if (s3 !== peg$FAILED) {
@@ -1642,7 +1433,7 @@ var ftl = (function (exports) {
1642
1433
  s0 = peg$parseGroupingExpression();
1643
1434
  }
1644
1435
 
1645
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1436
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1646
1437
 
1647
1438
  return s0;
1648
1439
  }
@@ -1651,7 +1442,7 @@ var ftl = (function (exports) {
1651
1442
  let s0;
1652
1443
 
1653
1444
  const key = peg$currPos * 38 + 19;
1654
- const cached = peg$resultsCache[key];
1445
+ const cached = peg$resultsCache.get(key);
1655
1446
 
1656
1447
  if (cached) {
1657
1448
  peg$currPos = cached.nextPos;
@@ -1667,7 +1458,7 @@ var ftl = (function (exports) {
1667
1458
  }
1668
1459
  }
1669
1460
 
1670
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1461
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1671
1462
 
1672
1463
  return s0;
1673
1464
  }
@@ -1676,7 +1467,7 @@ var ftl = (function (exports) {
1676
1467
  let s0, s1, s3;
1677
1468
 
1678
1469
  const key = peg$currPos * 38 + 20;
1679
- const cached = peg$resultsCache[key];
1470
+ const cached = peg$resultsCache.get(key);
1680
1471
 
1681
1472
  if (cached) {
1682
1473
  peg$currPos = cached.nextPos;
@@ -1721,7 +1512,7 @@ var ftl = (function (exports) {
1721
1512
  if (peg$silentFails === 0) { peg$fail(peg$e17); }
1722
1513
  }
1723
1514
 
1724
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1515
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1725
1516
 
1726
1517
  return s0;
1727
1518
  }
@@ -1730,7 +1521,7 @@ var ftl = (function (exports) {
1730
1521
  let s0, s1, s2, s4, s6;
1731
1522
 
1732
1523
  const key = peg$currPos * 38 + 21;
1733
- const cached = peg$resultsCache[key];
1524
+ const cached = peg$resultsCache.get(key);
1734
1525
 
1735
1526
  if (cached) {
1736
1527
  peg$currPos = cached.nextPos;
@@ -1789,7 +1580,7 @@ var ftl = (function (exports) {
1789
1580
  if (peg$silentFails === 0) { peg$fail(peg$e20); }
1790
1581
  }
1791
1582
 
1792
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1583
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1793
1584
 
1794
1585
  return s0;
1795
1586
  }
@@ -1798,7 +1589,7 @@ var ftl = (function (exports) {
1798
1589
  let s0, s1, s2, s4, s5, s6, s7, s9;
1799
1590
 
1800
1591
  const key = peg$currPos * 38 + 22;
1801
- const cached = peg$resultsCache[key];
1592
+ const cached = peg$resultsCache.get(key);
1802
1593
 
1803
1594
  if (cached) {
1804
1595
  peg$currPos = cached.nextPos;
@@ -1840,30 +1631,7 @@ var ftl = (function (exports) {
1840
1631
  s4 = null;
1841
1632
  }
1842
1633
  s5 = [];
1843
- s6 = peg$currPos;
1844
- if (input.charCodeAt(peg$currPos) === 44) {
1845
- s7 = peg$c34;
1846
- peg$currPos++;
1847
- } else {
1848
- s7 = peg$FAILED;
1849
- if (peg$silentFails === 0) { peg$fail(peg$e25); }
1850
- }
1851
- if (s7 !== peg$FAILED) {
1852
- peg$parse_();
1853
- s9 = peg$parseTernaryExpression();
1854
- if (s9 !== peg$FAILED) {
1855
- peg$parse_();
1856
- s6 = s9;
1857
- } else {
1858
- peg$currPos = s6;
1859
- s6 = peg$FAILED;
1860
- }
1861
- } else {
1862
- peg$currPos = s6;
1863
- s6 = peg$FAILED;
1864
- }
1865
- while (s6 !== peg$FAILED) {
1866
- s5.push(s6);
1634
+ for (;;) {
1867
1635
  s6 = peg$currPos;
1868
1636
  if (input.charCodeAt(peg$currPos) === 44) {
1869
1637
  s7 = peg$c34;
@@ -1886,6 +1654,8 @@ var ftl = (function (exports) {
1886
1654
  peg$currPos = s6;
1887
1655
  s6 = peg$FAILED;
1888
1656
  }
1657
+ if (s6 === peg$FAILED) { break; }
1658
+ s5.push(s6);
1889
1659
  }
1890
1660
  if (input.charCodeAt(peg$currPos) === 41) {
1891
1661
  s6 = peg$c36;
@@ -1910,7 +1680,7 @@ var ftl = (function (exports) {
1910
1680
  if (peg$silentFails === 0) { peg$fail(peg$e23); }
1911
1681
  }
1912
1682
 
1913
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1683
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1914
1684
 
1915
1685
  return s0;
1916
1686
  }
@@ -1919,7 +1689,7 @@ var ftl = (function (exports) {
1919
1689
  let s0, s1, s3, s5;
1920
1690
 
1921
1691
  const key = peg$currPos * 38 + 23;
1922
- const cached = peg$resultsCache[key];
1692
+ const cached = peg$resultsCache.get(key);
1923
1693
 
1924
1694
  if (cached) {
1925
1695
  peg$currPos = cached.nextPos;
@@ -1971,7 +1741,7 @@ var ftl = (function (exports) {
1971
1741
  }
1972
1742
  }
1973
1743
 
1974
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1744
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
1975
1745
 
1976
1746
  return s0;
1977
1747
  }
@@ -1980,7 +1750,7 @@ var ftl = (function (exports) {
1980
1750
  let s0, s1, s2, s4, s5, s6, s7, s9;
1981
1751
 
1982
1752
  const key = peg$currPos * 38 + 24;
1983
- const cached = peg$resultsCache[key];
1753
+ const cached = peg$resultsCache.get(key);
1984
1754
 
1985
1755
  if (cached) {
1986
1756
  peg$currPos = cached.nextPos;
@@ -2014,30 +1784,7 @@ var ftl = (function (exports) {
2014
1784
  s4 = null;
2015
1785
  }
2016
1786
  s5 = [];
2017
- s6 = peg$currPos;
2018
- if (input.charCodeAt(peg$currPos) === 44) {
2019
- s7 = peg$c34;
2020
- peg$currPos++;
2021
- } else {
2022
- s7 = peg$FAILED;
2023
- if (peg$silentFails === 0) { peg$fail(peg$e25); }
2024
- }
2025
- if (s7 !== peg$FAILED) {
2026
- peg$parse_();
2027
- s9 = peg$parseTernaryExpression();
2028
- if (s9 !== peg$FAILED) {
2029
- peg$parse_();
2030
- s6 = s9;
2031
- } else {
2032
- peg$currPos = s6;
2033
- s6 = peg$FAILED;
2034
- }
2035
- } else {
2036
- peg$currPos = s6;
2037
- s6 = peg$FAILED;
2038
- }
2039
- while (s6 !== peg$FAILED) {
2040
- s5.push(s6);
1787
+ for (;;) {
2041
1788
  s6 = peg$currPos;
2042
1789
  if (input.charCodeAt(peg$currPos) === 44) {
2043
1790
  s7 = peg$c34;
@@ -2060,6 +1807,8 @@ var ftl = (function (exports) {
2060
1807
  peg$currPos = s6;
2061
1808
  s6 = peg$FAILED;
2062
1809
  }
1810
+ if (s6 === peg$FAILED) { break; }
1811
+ s5.push(s6);
2063
1812
  }
2064
1813
  if (input.charCodeAt(peg$currPos) === 41) {
2065
1814
  s6 = peg$c36;
@@ -2088,7 +1837,7 @@ var ftl = (function (exports) {
2088
1837
  if (peg$silentFails === 0) { peg$fail(peg$e27); }
2089
1838
  }
2090
1839
 
2091
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1840
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
2092
1841
 
2093
1842
  return s0;
2094
1843
  }
@@ -2097,7 +1846,7 @@ var ftl = (function (exports) {
2097
1846
  let s0;
2098
1847
 
2099
1848
  const key = peg$currPos * 38 + 25;
2100
- const cached = peg$resultsCache[key];
1849
+ const cached = peg$resultsCache.get(key);
2101
1850
 
2102
1851
  if (cached) {
2103
1852
  peg$currPos = cached.nextPos;
@@ -2116,7 +1865,7 @@ var ftl = (function (exports) {
2116
1865
  }
2117
1866
  }
2118
1867
 
2119
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
1868
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
2120
1869
 
2121
1870
  return s0;
2122
1871
  }
@@ -2125,7 +1874,7 @@ var ftl = (function (exports) {
2125
1874
  let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9;
2126
1875
 
2127
1876
  const key = peg$currPos * 38 + 26;
2128
- const cached = peg$resultsCache[key];
1877
+ const cached = peg$resultsCache.get(key);
2129
1878
 
2130
1879
  if (cached) {
2131
1880
  peg$currPos = cached.nextPos;
@@ -2149,25 +1898,18 @@ var ftl = (function (exports) {
2149
1898
  }
2150
1899
  s4 = peg$currPos;
2151
1900
  s5 = [];
2152
- s6 = input.charAt(peg$currPos);
2153
- if (peg$r1.test(s6)) {
2154
- peg$currPos++;
2155
- } else {
2156
- s6 = peg$FAILED;
2157
- if (peg$silentFails === 0) { peg$fail(peg$e30); }
2158
- }
2159
- if (s6 !== peg$FAILED) {
2160
- while (s6 !== peg$FAILED) {
2161
- s5.push(s6);
2162
- s6 = input.charAt(peg$currPos);
2163
- if (peg$r1.test(s6)) {
2164
- peg$currPos++;
2165
- } else {
2166
- s6 = peg$FAILED;
2167
- if (peg$silentFails === 0) { peg$fail(peg$e30); }
2168
- }
1901
+ for (;;) {
1902
+ s6 = input.charAt(peg$currPos);
1903
+ if ((peg$charCode = s6.charCodeAt(0), (peg$charCode >= 48 && peg$charCode <= 57))) {
1904
+ peg$currPos++;
1905
+ } else {
1906
+ s6 = peg$FAILED;
1907
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
2169
1908
  }
2170
- } else {
1909
+ if (s6 === peg$FAILED) { break; }
1910
+ s5.push(s6);
1911
+ }
1912
+ if (s5.length < 1) {
2171
1913
  s5 = peg$FAILED;
2172
1914
  }
2173
1915
  if (s5 !== peg$FAILED) {
@@ -2181,22 +1923,16 @@ var ftl = (function (exports) {
2181
1923
  }
2182
1924
  if (s7 !== peg$FAILED) {
2183
1925
  s8 = [];
2184
- s9 = input.charAt(peg$currPos);
2185
- if (peg$r1.test(s9)) {
2186
- peg$currPos++;
2187
- } else {
2188
- s9 = peg$FAILED;
2189
- if (peg$silentFails === 0) { peg$fail(peg$e30); }
2190
- }
2191
- while (s9 !== peg$FAILED) {
2192
- s8.push(s9);
1926
+ for (;;) {
2193
1927
  s9 = input.charAt(peg$currPos);
2194
- if (peg$r1.test(s9)) {
1928
+ if ((peg$charCode = s9.charCodeAt(0), (peg$charCode >= 48 && peg$charCode <= 57))) {
2195
1929
  peg$currPos++;
2196
1930
  } else {
2197
1931
  s9 = peg$FAILED;
2198
1932
  if (peg$silentFails === 0) { peg$fail(peg$e30); }
2199
1933
  }
1934
+ if (s9 === peg$FAILED) { break; }
1935
+ s8.push(s9);
2200
1936
  }
2201
1937
  s7 = [s7, s8];
2202
1938
  s6 = s7;
@@ -2224,25 +1960,18 @@ var ftl = (function (exports) {
2224
1960
  }
2225
1961
  if (s5 !== peg$FAILED) {
2226
1962
  s6 = [];
2227
- s7 = input.charAt(peg$currPos);
2228
- if (peg$r1.test(s7)) {
2229
- peg$currPos++;
2230
- } else {
2231
- s7 = peg$FAILED;
2232
- if (peg$silentFails === 0) { peg$fail(peg$e30); }
2233
- }
2234
- if (s7 !== peg$FAILED) {
2235
- while (s7 !== peg$FAILED) {
2236
- s6.push(s7);
2237
- s7 = input.charAt(peg$currPos);
2238
- if (peg$r1.test(s7)) {
2239
- peg$currPos++;
2240
- } else {
2241
- s7 = peg$FAILED;
2242
- if (peg$silentFails === 0) { peg$fail(peg$e30); }
2243
- }
1963
+ for (;;) {
1964
+ s7 = input.charAt(peg$currPos);
1965
+ if ((peg$charCode = s7.charCodeAt(0), (peg$charCode >= 48 && peg$charCode <= 57))) {
1966
+ peg$currPos++;
1967
+ } else {
1968
+ s7 = peg$FAILED;
1969
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
2244
1970
  }
2245
- } else {
1971
+ if (s7 === peg$FAILED) { break; }
1972
+ s6.push(s7);
1973
+ }
1974
+ if (s6.length < 1) {
2246
1975
  s6 = peg$FAILED;
2247
1976
  }
2248
1977
  if (s6 !== peg$FAILED) {
@@ -2279,7 +2008,7 @@ var ftl = (function (exports) {
2279
2008
  if (peg$silentFails === 0) { peg$fail(peg$e28); }
2280
2009
  }
2281
2010
 
2282
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2011
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
2283
2012
 
2284
2013
  return s0;
2285
2014
  }
@@ -2288,7 +2017,7 @@ var ftl = (function (exports) {
2288
2017
  let s0, s1, s2, s3, s4;
2289
2018
 
2290
2019
  const key = peg$currPos * 38 + 27;
2291
- const cached = peg$resultsCache[key];
2020
+ const cached = peg$resultsCache.get(key);
2292
2021
 
2293
2022
  if (cached) {
2294
2023
  peg$currPos = cached.nextPos;
@@ -2308,22 +2037,16 @@ var ftl = (function (exports) {
2308
2037
  if (s1 !== peg$FAILED) {
2309
2038
  s2 = peg$currPos;
2310
2039
  s3 = [];
2311
- s4 = input.charAt(peg$currPos);
2312
- if (peg$r2.test(s4)) {
2313
- peg$currPos++;
2314
- } else {
2315
- s4 = peg$FAILED;
2316
- if (peg$silentFails === 0) { peg$fail(peg$e33); }
2317
- }
2318
- while (s4 !== peg$FAILED) {
2319
- s3.push(s4);
2040
+ for (;;) {
2320
2041
  s4 = input.charAt(peg$currPos);
2321
- if (peg$r2.test(s4)) {
2042
+ if (s4 !== "" && !(peg$charCode = s4.charCodeAt(0), peg$charCode === 39)) {
2322
2043
  peg$currPos++;
2323
2044
  } else {
2324
2045
  s4 = peg$FAILED;
2325
2046
  if (peg$silentFails === 0) { peg$fail(peg$e33); }
2326
2047
  }
2048
+ if (s4 === peg$FAILED) { break; }
2049
+ s3.push(s4);
2327
2050
  }
2328
2051
  s2 = input.substring(s2, peg$currPos);
2329
2052
  if (input.charCodeAt(peg$currPos) === 39) {
@@ -2355,22 +2078,16 @@ var ftl = (function (exports) {
2355
2078
  if (s1 !== peg$FAILED) {
2356
2079
  s2 = peg$currPos;
2357
2080
  s3 = [];
2358
- s4 = input.charAt(peg$currPos);
2359
- if (peg$r3.test(s4)) {
2360
- peg$currPos++;
2361
- } else {
2362
- s4 = peg$FAILED;
2363
- if (peg$silentFails === 0) { peg$fail(peg$e35); }
2364
- }
2365
- while (s4 !== peg$FAILED) {
2366
- s3.push(s4);
2081
+ for (;;) {
2367
2082
  s4 = input.charAt(peg$currPos);
2368
- if (peg$r3.test(s4)) {
2083
+ if (s4 !== "" && !(peg$charCode = s4.charCodeAt(0), peg$charCode === 34)) {
2369
2084
  peg$currPos++;
2370
2085
  } else {
2371
2086
  s4 = peg$FAILED;
2372
2087
  if (peg$silentFails === 0) { peg$fail(peg$e35); }
2373
2088
  }
2089
+ if (s4 === peg$FAILED) { break; }
2090
+ s3.push(s4);
2374
2091
  }
2375
2092
  s2 = input.substring(s2, peg$currPos);
2376
2093
  if (input.charCodeAt(peg$currPos) === 34) {
@@ -2401,16 +2118,13 @@ var ftl = (function (exports) {
2401
2118
  }
2402
2119
  if (s1 !== peg$FAILED) {
2403
2120
  s2 = [];
2404
- s3 = peg$parseTemplateStringExpression();
2405
- if (s3 === peg$FAILED) {
2406
- s3 = peg$parseTemplateStringLiteral();
2407
- }
2408
- while (s3 !== peg$FAILED) {
2409
- s2.push(s3);
2121
+ for (;;) {
2410
2122
  s3 = peg$parseTemplateStringExpression();
2411
2123
  if (s3 === peg$FAILED) {
2412
2124
  s3 = peg$parseTemplateStringLiteral();
2413
2125
  }
2126
+ if (s3 === peg$FAILED) { break; }
2127
+ s2.push(s3);
2414
2128
  }
2415
2129
  if (input.charCodeAt(peg$currPos) === 96) {
2416
2130
  s3 = peg$c42;
@@ -2437,7 +2151,7 @@ var ftl = (function (exports) {
2437
2151
  if (peg$silentFails === 0) { peg$fail(peg$e31); }
2438
2152
  }
2439
2153
 
2440
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2154
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
2441
2155
 
2442
2156
  return s0;
2443
2157
  }
@@ -2446,7 +2160,7 @@ var ftl = (function (exports) {
2446
2160
  let s0, s1, s3, s5;
2447
2161
 
2448
2162
  const key = peg$currPos * 38 + 28;
2449
- const cached = peg$resultsCache[key];
2163
+ const cached = peg$resultsCache.get(key);
2450
2164
 
2451
2165
  if (cached) {
2452
2166
  peg$currPos = cached.nextPos;
@@ -2495,7 +2209,7 @@ var ftl = (function (exports) {
2495
2209
  if (peg$silentFails === 0) { peg$fail(peg$e37); }
2496
2210
  }
2497
2211
 
2498
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2212
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
2499
2213
 
2500
2214
  return s0;
2501
2215
  }
@@ -2504,134 +2218,71 @@ var ftl = (function (exports) {
2504
2218
  let s0, s1, s2, s3, s4;
2505
2219
 
2506
2220
  const key = peg$currPos * 38 + 29;
2507
- const cached = peg$resultsCache[key];
2221
+ const cached = peg$resultsCache.get(key);
2508
2222
 
2509
2223
  if (cached) {
2510
2224
  peg$currPos = cached.nextPos;
2511
2225
 
2512
2226
  return cached.result;
2513
2227
  }
2514
-
2515
- peg$silentFails++;
2516
- s0 = peg$currPos;
2517
- s1 = [];
2518
- s2 = peg$currPos;
2519
- s3 = [];
2520
- s4 = input.charAt(peg$currPos);
2521
- if (peg$r4.test(s4)) {
2522
- peg$currPos++;
2523
- } else {
2524
- s4 = peg$FAILED;
2525
- if (peg$silentFails === 0) { peg$fail(peg$e40); }
2526
- }
2527
- if (s4 !== peg$FAILED) {
2528
- while (s4 !== peg$FAILED) {
2529
- s3.push(s4);
2530
- s4 = input.charAt(peg$currPos);
2531
- if (peg$r4.test(s4)) {
2532
- peg$currPos++;
2533
- } else {
2534
- s4 = peg$FAILED;
2535
- if (peg$silentFails === 0) { peg$fail(peg$e40); }
2536
- }
2537
- }
2538
- } else {
2539
- s3 = peg$FAILED;
2540
- }
2541
- if (s3 !== peg$FAILED) {
2542
- s2 = input.substring(s2, peg$currPos);
2543
- } else {
2544
- s2 = s3;
2545
- }
2546
- if (s2 === peg$FAILED) {
2547
- s2 = peg$currPos;
2548
- if (input.charCodeAt(peg$currPos) === 92) {
2549
- s3 = peg$c7;
2550
- peg$currPos++;
2551
- } else {
2552
- s3 = peg$FAILED;
2553
- if (peg$silentFails === 0) { peg$fail(peg$e7); }
2554
- }
2555
- if (s3 !== peg$FAILED) {
2556
- if (input.length > peg$currPos) {
2557
- s4 = input.charAt(peg$currPos);
2558
- peg$currPos++;
2559
- } else {
2560
- s4 = peg$FAILED;
2561
- if (peg$silentFails === 0) { peg$fail(peg$e8); }
2562
- }
2563
- if (s4 !== peg$FAILED) {
2564
- s2 = peg$f28(s4);
2565
- } else {
2566
- peg$currPos = s2;
2567
- s2 = peg$FAILED;
2568
- }
2569
- } else {
2570
- peg$currPos = s2;
2571
- s2 = peg$FAILED;
2572
- }
2573
- }
2574
- if (s2 !== peg$FAILED) {
2575
- while (s2 !== peg$FAILED) {
2576
- s1.push(s2);
2577
- s2 = peg$currPos;
2578
- s3 = [];
2228
+
2229
+ peg$silentFails++;
2230
+ s0 = peg$currPos;
2231
+ s1 = [];
2232
+ for (;;) {
2233
+ s2 = peg$currPos;
2234
+ s3 = [];
2235
+ for (;;) {
2579
2236
  s4 = input.charAt(peg$currPos);
2580
- if (peg$r4.test(s4)) {
2237
+ if (s4 !== "" && !(peg$charCode = s4.charCodeAt(0), peg$charCode === 92 || peg$charCode === 96 || peg$charCode === 123)) {
2581
2238
  peg$currPos++;
2582
2239
  } else {
2583
2240
  s4 = peg$FAILED;
2584
2241
  if (peg$silentFails === 0) { peg$fail(peg$e40); }
2585
2242
  }
2586
- if (s4 !== peg$FAILED) {
2587
- while (s4 !== peg$FAILED) {
2588
- s3.push(s4);
2589
- s4 = input.charAt(peg$currPos);
2590
- if (peg$r4.test(s4)) {
2591
- peg$currPos++;
2592
- } else {
2593
- s4 = peg$FAILED;
2594
- if (peg$silentFails === 0) { peg$fail(peg$e40); }
2595
- }
2596
- }
2243
+ if (s4 === peg$FAILED) { break; }
2244
+ s3.push(s4);
2245
+ }
2246
+ if (s3.length < 1) {
2247
+ s3 = peg$FAILED;
2248
+ }
2249
+ if (s3 !== peg$FAILED) {
2250
+ s2 = input.substring(s2, peg$currPos);
2251
+ } else {
2252
+ s2 = s3;
2253
+ }
2254
+ if (s2 === peg$FAILED) {
2255
+ s2 = peg$currPos;
2256
+ if (input.charCodeAt(peg$currPos) === 92) {
2257
+ s3 = peg$c7;
2258
+ peg$currPos++;
2597
2259
  } else {
2598
2260
  s3 = peg$FAILED;
2261
+ if (peg$silentFails === 0) { peg$fail(peg$e7); }
2599
2262
  }
2600
2263
  if (s3 !== peg$FAILED) {
2601
- s2 = input.substring(s2, peg$currPos);
2602
- } else {
2603
- s2 = s3;
2604
- }
2605
- if (s2 === peg$FAILED) {
2606
- s2 = peg$currPos;
2607
- if (input.charCodeAt(peg$currPos) === 92) {
2608
- s3 = peg$c7;
2264
+ if (input.length > peg$currPos) {
2265
+ s4 = input.charAt(peg$currPos);
2609
2266
  peg$currPos++;
2610
2267
  } else {
2611
- s3 = peg$FAILED;
2612
- if (peg$silentFails === 0) { peg$fail(peg$e7); }
2268
+ s4 = peg$FAILED;
2269
+ if (peg$silentFails === 0) { peg$fail(peg$e8); }
2613
2270
  }
2614
- if (s3 !== peg$FAILED) {
2615
- if (input.length > peg$currPos) {
2616
- s4 = input.charAt(peg$currPos);
2617
- peg$currPos++;
2618
- } else {
2619
- s4 = peg$FAILED;
2620
- if (peg$silentFails === 0) { peg$fail(peg$e8); }
2621
- }
2622
- if (s4 !== peg$FAILED) {
2623
- s2 = peg$f28(s4);
2624
- } else {
2625
- peg$currPos = s2;
2626
- s2 = peg$FAILED;
2627
- }
2271
+ if (s4 !== peg$FAILED) {
2272
+ s2 = peg$f28(s4);
2628
2273
  } else {
2629
2274
  peg$currPos = s2;
2630
2275
  s2 = peg$FAILED;
2631
2276
  }
2277
+ } else {
2278
+ peg$currPos = s2;
2279
+ s2 = peg$FAILED;
2632
2280
  }
2633
2281
  }
2634
- } else {
2282
+ if (s2 === peg$FAILED) { break; }
2283
+ s1.push(s2);
2284
+ }
2285
+ if (s1.length < 1) {
2635
2286
  s1 = peg$FAILED;
2636
2287
  }
2637
2288
  if (s1 !== peg$FAILED) {
@@ -2658,7 +2309,7 @@ var ftl = (function (exports) {
2658
2309
  if (peg$silentFails === 0) { peg$fail(peg$e39); }
2659
2310
  }
2660
2311
 
2661
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2312
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
2662
2313
 
2663
2314
  return s0;
2664
2315
  }
@@ -2667,7 +2318,7 @@ var ftl = (function (exports) {
2667
2318
  let s0, s1, s3, s5, s6, s7, s9;
2668
2319
 
2669
2320
  const key = peg$currPos * 38 + 30;
2670
- const cached = peg$resultsCache[key];
2321
+ const cached = peg$resultsCache.get(key);
2671
2322
 
2672
2323
  if (cached) {
2673
2324
  peg$currPos = cached.nextPos;
@@ -2692,30 +2343,7 @@ var ftl = (function (exports) {
2692
2343
  }
2693
2344
  peg$parse_();
2694
2345
  s5 = [];
2695
- s6 = peg$currPos;
2696
- if (input.charCodeAt(peg$currPos) === 44) {
2697
- s7 = peg$c34;
2698
- peg$currPos++;
2699
- } else {
2700
- s7 = peg$FAILED;
2701
- if (peg$silentFails === 0) { peg$fail(peg$e25); }
2702
- }
2703
- if (s7 !== peg$FAILED) {
2704
- peg$parse_();
2705
- s9 = peg$parseTernaryExpression();
2706
- if (s9 !== peg$FAILED) {
2707
- peg$parse_();
2708
- s6 = s9;
2709
- } else {
2710
- peg$currPos = s6;
2711
- s6 = peg$FAILED;
2712
- }
2713
- } else {
2714
- peg$currPos = s6;
2715
- s6 = peg$FAILED;
2716
- }
2717
- while (s6 !== peg$FAILED) {
2718
- s5.push(s6);
2346
+ for (;;) {
2719
2347
  s6 = peg$currPos;
2720
2348
  if (input.charCodeAt(peg$currPos) === 44) {
2721
2349
  s7 = peg$c34;
@@ -2738,6 +2366,8 @@ var ftl = (function (exports) {
2738
2366
  peg$currPos = s6;
2739
2367
  s6 = peg$FAILED;
2740
2368
  }
2369
+ if (s6 === peg$FAILED) { break; }
2370
+ s5.push(s6);
2741
2371
  }
2742
2372
  if (input.charCodeAt(peg$currPos) === 93) {
2743
2373
  s6 = peg$c31;
@@ -2762,7 +2392,7 @@ var ftl = (function (exports) {
2762
2392
  if (peg$silentFails === 0) { peg$fail(peg$e41); }
2763
2393
  }
2764
2394
 
2765
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2395
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
2766
2396
 
2767
2397
  return s0;
2768
2398
  }
@@ -2771,7 +2401,7 @@ var ftl = (function (exports) {
2771
2401
  let s0, s1, s3, s4, s5, s6, s7, s8, s9, s11, s13;
2772
2402
 
2773
2403
  const key = peg$currPos * 38 + 31;
2774
- const cached = peg$resultsCache[key];
2404
+ const cached = peg$resultsCache.get(key);
2775
2405
 
2776
2406
  if (cached) {
2777
2407
  peg$currPos = cached.nextPos;
@@ -2823,50 +2453,7 @@ var ftl = (function (exports) {
2823
2453
  }
2824
2454
  s4 = peg$parse_();
2825
2455
  s5 = [];
2826
- s6 = peg$currPos;
2827
- if (input.charCodeAt(peg$currPos) === 44) {
2828
- s7 = peg$c34;
2829
- peg$currPos++;
2830
- } else {
2831
- s7 = peg$FAILED;
2832
- if (peg$silentFails === 0) { peg$fail(peg$e25); }
2833
- }
2834
- if (s7 !== peg$FAILED) {
2835
- s8 = peg$parse_();
2836
- s9 = peg$parseStringLiteral();
2837
- if (s9 !== peg$FAILED) {
2838
- peg$parse_();
2839
- if (input.charCodeAt(peg$currPos) === 58) {
2840
- s11 = peg$c15;
2841
- peg$currPos++;
2842
- } else {
2843
- s11 = peg$FAILED;
2844
- if (peg$silentFails === 0) { peg$fail(peg$e11); }
2845
- }
2846
- if (s11 !== peg$FAILED) {
2847
- peg$parse_();
2848
- s13 = peg$parseTernaryExpression();
2849
- if (s13 !== peg$FAILED) {
2850
- peg$parse_();
2851
- s6 = [ s9, s13 ];
2852
- } else {
2853
- peg$currPos = s6;
2854
- s6 = peg$FAILED;
2855
- }
2856
- } else {
2857
- peg$currPos = s6;
2858
- s6 = peg$FAILED;
2859
- }
2860
- } else {
2861
- peg$currPos = s6;
2862
- s6 = peg$FAILED;
2863
- }
2864
- } else {
2865
- peg$currPos = s6;
2866
- s6 = peg$FAILED;
2867
- }
2868
- while (s6 !== peg$FAILED) {
2869
- s5.push(s6);
2456
+ for (;;) {
2870
2457
  s6 = peg$currPos;
2871
2458
  if (input.charCodeAt(peg$currPos) === 44) {
2872
2459
  s7 = peg$c34;
@@ -2909,6 +2496,8 @@ var ftl = (function (exports) {
2909
2496
  peg$currPos = s6;
2910
2497
  s6 = peg$FAILED;
2911
2498
  }
2499
+ if (s6 === peg$FAILED) { break; }
2500
+ s5.push(s6);
2912
2501
  }
2913
2502
  if (input.charCodeAt(peg$currPos) === 125) {
2914
2503
  s6 = peg$c43;
@@ -2933,7 +2522,7 @@ var ftl = (function (exports) {
2933
2522
  if (peg$silentFails === 0) { peg$fail(peg$e42); }
2934
2523
  }
2935
2524
 
2936
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2525
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
2937
2526
 
2938
2527
  return s0;
2939
2528
  }
@@ -2942,7 +2531,7 @@ var ftl = (function (exports) {
2942
2531
  let s0, s1, s2, s3, s4;
2943
2532
 
2944
2533
  const key = peg$currPos * 38 + 32;
2945
- const cached = peg$resultsCache[key];
2534
+ const cached = peg$resultsCache.get(key);
2946
2535
 
2947
2536
  if (cached) {
2948
2537
  peg$currPos = cached.nextPos;
@@ -3000,7 +2589,7 @@ var ftl = (function (exports) {
3000
2589
  if (peg$silentFails === 0) { peg$fail(peg$e43); }
3001
2590
  }
3002
2591
 
3003
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2592
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
3004
2593
 
3005
2594
  return s0;
3006
2595
  }
@@ -3009,7 +2598,7 @@ var ftl = (function (exports) {
3009
2598
  let s0, s1, s2, s3, s4;
3010
2599
 
3011
2600
  const key = peg$currPos * 38 + 33;
3012
- const cached = peg$resultsCache[key];
2601
+ const cached = peg$resultsCache.get(key);
3013
2602
 
3014
2603
  if (cached) {
3015
2604
  peg$currPos = cached.nextPos;
@@ -3020,7 +2609,7 @@ var ftl = (function (exports) {
3020
2609
  s0 = peg$currPos;
3021
2610
  s1 = peg$currPos;
3022
2611
  s2 = input.charAt(peg$currPos);
3023
- if (peg$r5.test(s2)) {
2612
+ if ((peg$charCode = s2.charCodeAt(0), (peg$charCode >= 97 && peg$charCode <= 122) || (peg$charCode >= 65 && peg$charCode <= 90) || peg$charCode === 36 || peg$charCode === 95)) {
3024
2613
  peg$currPos++;
3025
2614
  } else {
3026
2615
  s2 = peg$FAILED;
@@ -3028,22 +2617,16 @@ var ftl = (function (exports) {
3028
2617
  }
3029
2618
  if (s2 !== peg$FAILED) {
3030
2619
  s3 = [];
3031
- s4 = input.charAt(peg$currPos);
3032
- if (peg$r6.test(s4)) {
3033
- peg$currPos++;
3034
- } else {
3035
- s4 = peg$FAILED;
3036
- if (peg$silentFails === 0) { peg$fail(peg$e46); }
3037
- }
3038
- while (s4 !== peg$FAILED) {
3039
- s3.push(s4);
2620
+ for (;;) {
3040
2621
  s4 = input.charAt(peg$currPos);
3041
- if (peg$r6.test(s4)) {
2622
+ if ((peg$charCode = s4.charCodeAt(0), (peg$charCode >= 97 && peg$charCode <= 122) || (peg$charCode >= 65 && peg$charCode <= 90) || peg$charCode === 36 || peg$charCode === 95 || (peg$charCode >= 48 && peg$charCode <= 57))) {
3042
2623
  peg$currPos++;
3043
2624
  } else {
3044
2625
  s4 = peg$FAILED;
3045
2626
  if (peg$silentFails === 0) { peg$fail(peg$e46); }
3046
2627
  }
2628
+ if (s4 === peg$FAILED) { break; }
2629
+ s3.push(s4);
3047
2630
  }
3048
2631
  s2 = [s2, s3];
3049
2632
  s1 = s2;
@@ -3057,7 +2640,7 @@ var ftl = (function (exports) {
3057
2640
  s0 = s1;
3058
2641
  }
3059
2642
 
3060
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2643
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
3061
2644
 
3062
2645
  return s0;
3063
2646
  }
@@ -3066,7 +2649,7 @@ var ftl = (function (exports) {
3066
2649
  let s0, s1;
3067
2650
 
3068
2651
  const key = peg$currPos * 38 + 34;
3069
- const cached = peg$resultsCache[key];
2652
+ const cached = peg$resultsCache.get(key);
3070
2653
 
3071
2654
  if (cached) {
3072
2655
  peg$currPos = cached.nextPos;
@@ -3087,7 +2670,7 @@ var ftl = (function (exports) {
3087
2670
  if (peg$silentFails === 0) { peg$fail(peg$e47); }
3088
2671
  }
3089
2672
 
3090
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2673
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
3091
2674
 
3092
2675
  return s0;
3093
2676
  }
@@ -3096,7 +2679,7 @@ var ftl = (function (exports) {
3096
2679
  let s0;
3097
2680
 
3098
2681
  const key = peg$currPos * 38 + 35;
3099
- const cached = peg$resultsCache[key];
2682
+ const cached = peg$resultsCache.get(key);
3100
2683
 
3101
2684
  if (cached) {
3102
2685
  peg$currPos = cached.nextPos;
@@ -3121,7 +2704,7 @@ var ftl = (function (exports) {
3121
2704
  }
3122
2705
  }
3123
2706
 
3124
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2707
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
3125
2708
 
3126
2709
  return s0;
3127
2710
  }
@@ -3130,7 +2713,7 @@ var ftl = (function (exports) {
3130
2713
  let s0;
3131
2714
 
3132
2715
  const key = peg$currPos * 38 + 36;
3133
- const cached = peg$resultsCache[key];
2716
+ const cached = peg$resultsCache.get(key);
3134
2717
 
3135
2718
  if (cached) {
3136
2719
  peg$currPos = cached.nextPos;
@@ -3173,7 +2756,7 @@ var ftl = (function (exports) {
3173
2756
  }
3174
2757
  }
3175
2758
 
3176
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2759
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
3177
2760
 
3178
2761
  return s0;
3179
2762
  }
@@ -3182,7 +2765,7 @@ var ftl = (function (exports) {
3182
2765
  let s0, s1;
3183
2766
 
3184
2767
  const key = peg$currPos * 38 + 37;
3185
- const cached = peg$resultsCache[key];
2768
+ const cached = peg$resultsCache.get(key);
3186
2769
 
3187
2770
  if (cached) {
3188
2771
  peg$currPos = cached.nextPos;
@@ -3192,26 +2775,20 @@ var ftl = (function (exports) {
3192
2775
 
3193
2776
  peg$silentFails++;
3194
2777
  s0 = [];
3195
- s1 = input.charAt(peg$currPos);
3196
- if (peg$r7.test(s1)) {
3197
- peg$currPos++;
3198
- } else {
3199
- s1 = peg$FAILED;
3200
- if (peg$silentFails === 0) { peg$fail(peg$e54); }
3201
- }
3202
- while (s1 !== peg$FAILED) {
3203
- s0.push(s1);
2778
+ for (;;) {
3204
2779
  s1 = input.charAt(peg$currPos);
3205
- if (peg$r7.test(s1)) {
2780
+ if ((peg$charCode = s1.charCodeAt(0), peg$charCode === 32 || peg$charCode === 9 || peg$charCode === 10 || peg$charCode === 13)) {
3206
2781
  peg$currPos++;
3207
2782
  } else {
3208
2783
  s1 = peg$FAILED;
3209
2784
  if (peg$silentFails === 0) { peg$fail(peg$e54); }
3210
2785
  }
2786
+ if (s1 === peg$FAILED) { break; }
2787
+ s0.push(s1);
3211
2788
  }
3212
2789
  peg$silentFails--;
3213
2790
 
3214
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2791
+ peg$resultsCache.set(key, { nextPos: peg$currPos, result: s0 });
3215
2792
 
3216
2793
  return s0;
3217
2794
  }
@@ -3253,10 +2830,10 @@ var ftl = (function (exports) {
3253
2830
  /**
3254
2831
  * The one lookup against a data stack: the innermost overlay carrying the name
3255
2832
  * wins, `self` is the innermost overlay itself, and a function overlay counts
3256
- * like an object one. Every lookup that resolves a name goes through this, so the
3257
- * imperative facades cannot drift from what a template sees.
2833
+ * like an object one.
3258
2834
  * @param {any[]} dataStack
3259
2835
  * @param {string|symbol} prop
2836
+ * @returns {any} undefined when no overlay carries the name
3260
2837
  */
3261
2838
  const resolveInStack = (dataStack, prop) => {
3262
2839
  if (prop === 'self') {
@@ -3273,6 +2850,8 @@ var ftl = (function (exports) {
3273
2850
  return undefined;
3274
2851
  };
3275
2852
 
2853
+ const BOUND = Symbol('BOUND');
2854
+
3276
2855
  /**
3277
2856
  * Evaluates a parsed expression against a scope. One instance per evaluation:
3278
2857
  * it holds the modules and the data stack that names resolve against, and keeps
@@ -3281,9 +2860,11 @@ var ftl = (function (exports) {
3281
2860
  class EvaluatingVisitor {
3282
2861
  #modules;
3283
2862
  #dataStack;
3284
- constructor(modules, dataStack) {
2863
+ #bound;
2864
+ constructor(modules, dataStack, bound) {
3285
2865
  this.#modules = modules;
3286
2866
  this.#dataStack = dataStack;
2867
+ this.#bound = bound ?? dataStack.length;
3287
2868
  }
3288
2869
  #resolve(prop) {
3289
2870
  return resolveInStack(this.#dataStack, prop);
@@ -3307,8 +2888,9 @@ var ftl = (function (exports) {
3307
2888
  #cached_resolve_proxy;
3308
2889
  #resolve_proxy() {
3309
2890
  if (!this.#cached_resolve_proxy) {
2891
+ const bound = (name) => resolveInStack(this.#dataStack.slice(0, this.#bound), name);
3310
2892
  this.#cached_resolve_proxy = new Proxy(this.#dataStack, {
3311
- get: (target, prop) => this.#resolve(prop),
2893
+ get: (target, prop) => (prop === BOUND ? bound : this.#resolve(prop)),
3312
2894
  });
3313
2895
  }
3314
2896
  return this.#cached_resolve_proxy;
@@ -3320,8 +2902,7 @@ var ftl = (function (exports) {
3320
2902
  return this.visit(node.lhs) || this.visit(node.rhs);
3321
2903
  }
3322
2904
  [nodes.nullc](node) {
3323
- const lhs = this.visit(node.lhs);
3324
- return lhs !== null && lhs !== undefined ? lhs : this.visit(node.rhs);
2905
+ return this.visit(node.lhs) ?? this.visit(node.rhs);
3325
2906
  }
3326
2907
  [nodes.not](node) {
3327
2908
  return !this.visit(node.expr);
@@ -3388,8 +2969,7 @@ var ftl = (function (exports) {
3388
2969
  return this.visit(node.cond) ? this.visit(node.ifTrue) : this.visit(node.ifFalse);
3389
2970
  }
3390
2971
  [nodes.elv](node) {
3391
- const cond = this.visit(node.cond);
3392
- return cond ? cond : this.visit(node.ifFalse);
2972
+ return this.visit(node.cond) || this.visit(node.ifFalse);
3393
2973
  }
3394
2974
  [nodes.access](node) {
3395
2975
  let prev;
@@ -3455,16 +3035,33 @@ var ftl = (function (exports) {
3455
3035
  * spliced into the expression text.
3456
3036
  */
3457
3037
  class Expressions {
3038
+ /** Parses the whole text as one expression: `user.name`. */
3458
3039
  static MODE_EXPRESSION = Symbol('MODE_EXPRESSION');
3040
+ /**
3041
+ * Parses text with interpolations, `Hello {{ user.name }}`, evaluating to
3042
+ * its parts: `{ type, value }` for each literal run and each `{{ }}` (text),
3043
+ * `{{{ }}}` (html) or `{{{{ }}}}` (node) interpolation, in order.
3044
+ */
3459
3045
  static MODE_TEMPLATED = Symbol('MODE_TEMPLATED');
3046
+ /**
3047
+ * The key under which the receiver of a module function answers a lookup
3048
+ * against the bound data alone: `this[Expressions.BOUND](name)` resolves
3049
+ * the name in the layers the scope was created with (a registry's data,
3050
+ * the data handed to a Template factory) and never in an overlay pushed
3051
+ * afterwards, by `data-tpl-each`, `data-tpl-with` or `withOverlay`. It is
3052
+ * how a module reads what the page configured without the rendered data
3053
+ * being able to supply or replace it. No name on the data stack reaches it.
3054
+ */
3055
+ static BOUND = BOUND;
3460
3056
 
3461
3057
  static #astCache = new BoundedCache(1000);
3462
3058
 
3463
3059
  /**
3464
- * Parses an expression.
3060
+ * Parses an expression, caching the ast by mode and text.
3465
3061
  * @param {string} expression
3466
- * @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode]
3467
- * @returns the ast
3062
+ * @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode] MODE_EXPRESSION when omitted
3063
+ * @returns {any} the ast
3064
+ * @throws a peggy syntax error when the text does not parse
3468
3065
  */
3469
3066
  static parse(expression, mode) {
3470
3067
  const key = mode?.toString() + expression;
@@ -3475,26 +3072,28 @@ var ftl = (function (exports) {
3475
3072
  );
3476
3073
  }
3477
3074
  /**
3478
- * Evaluates an expression.
3075
+ * Evaluates a parsed ast against the modules and the data stack.
3479
3076
  * @param {{[k: string]: any } | null | undefined } modules
3480
- * @param {any[]} dataStack
3481
- * @param {any} ast
3482
- * @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode]
3483
- * @returns the result
3077
+ * @param {any[]} dataStack innermost overlay last
3078
+ * @param {any} ast what `parse` answered
3079
+ * @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode] the mode it was parsed in
3080
+ * @param {number} [bound] how many layers, from the outermost, are the bound data; all of them when omitted
3081
+ * @returns {any} the value, or the list of parts in MODE_TEMPLATED
3484
3082
  */
3485
- static evaluate(modules, dataStack, ast, mode) {
3486
- return new EvaluatingVisitor(modules, dataStack).visitRoot(ast, mode === Expressions.MODE_TEMPLATED);
3083
+ static evaluate(modules, dataStack, ast, mode, bound) {
3084
+ return new EvaluatingVisitor(modules, dataStack, bound).visitRoot(ast, mode === Expressions.MODE_TEMPLATED);
3487
3085
  }
3488
3086
  /**
3489
3087
  * Parses and evaluates an expression.
3490
3088
  * @param {{ [x: string]: any; } | null | undefined} modules
3491
- * @param {any[]} dataStack
3089
+ * @param {any[]} dataStack innermost overlay last
3492
3090
  * @param {string} expression
3493
- * @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode]
3494
- * @returns the result
3091
+ * @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode] MODE_EXPRESSION when omitted
3092
+ * @param {number} [bound] how many layers, from the outermost, are the bound data; all of them when omitted
3093
+ * @returns {any} the value, or the list of parts in MODE_TEMPLATED
3495
3094
  */
3496
- static interpret(modules, dataStack, expression, mode) {
3497
- return Expressions.evaluate(modules, dataStack, Expressions.parse(expression, mode), mode);
3095
+ static interpret(modules, dataStack, expression, mode, bound) {
3096
+ return Expressions.evaluate(modules, dataStack, Expressions.parse(expression, mode), mode, bound);
3498
3097
  }
3499
3098
  }
3500
3099
 
@@ -3503,48 +3102,89 @@ var ftl = (function (exports) {
3503
3102
  * that a bare identifier resolves against, as a single value. Adding an overlay
3504
3103
  * returns a new evaluator instead of modifying this one, so a template can add
3505
3104
  * data for one subtree without affecting the rest of the render.
3105
+ *
3106
+ * The layers an evaluator is created with are its bound data; the overlays
3107
+ * pushed afterwards are not. A bare identifier resolves against both, while a
3108
+ * module function can ask for the bound data alone through `Expressions.BOUND`.
3506
3109
  */
3507
3110
  class ExpressionEvaluator {
3508
3111
  #modules;
3509
3112
  #dataStack;
3510
- constructor(modules, dataStack) {
3113
+ #bound;
3114
+ /**
3115
+ * @param {{ [k: string]: any } | null | undefined} modules
3116
+ * @param {any[]} dataStack innermost overlay last
3117
+ * @param {number} [bound] how many layers, from the outermost, are the bound data; all of them when omitted
3118
+ */
3119
+ constructor(modules, dataStack, bound) {
3511
3120
  this.#modules = modules;
3512
3121
  this.#dataStack = dataStack;
3122
+ this.#bound = bound ?? dataStack.length;
3513
3123
  }
3124
+ /**
3125
+ * A new evaluator with one more module: `#name:fn()` resolves into it, and an
3126
+ * empty name merges a whole map whose functions resolve bare, as `#fn()`.
3127
+ * @param {string} name
3128
+ * @param {object} value
3129
+ * @returns {ExpressionEvaluator}
3130
+ */
3514
3131
  withModule(name, value) {
3515
3132
  const module = name ? { [name]: value } : value;
3516
- return new ExpressionEvaluator({ ...this.#modules, ...module }, this.#dataStack);
3133
+ return new ExpressionEvaluator({ ...this.#modules, ...module }, this.#dataStack, this.#bound);
3517
3134
  }
3135
+ /**
3136
+ * A new evaluator with the data pushed as the innermost overlays, the last
3137
+ * one winning a shared name. The bound data stays what it was.
3138
+ * @param {...any} data
3139
+ * @returns {ExpressionEvaluator}
3140
+ */
3518
3141
  withOverlay(...data) {
3519
3142
  return new ExpressionEvaluator(
3520
3143
  this.#modules,
3521
3144
  data.length === 0 ? this.#dataStack : [...this.#dataStack, ...data],
3145
+ this.#bound,
3522
3146
  );
3523
3147
  }
3524
3148
  /**
3525
3149
  * Resolves a name against the data stack, with no parse round trip: the
3526
3150
  * lookup a template's bare identifier makes, for an imperative caller.
3527
3151
  * @param {string} name
3152
+ * @returns {any} undefined when no overlay carries the name
3528
3153
  */
3529
3154
  resolve(name) {
3530
3155
  return resolveInStack(this.#dataStack, name);
3531
3156
  }
3532
- /** Evaluates an expression against this scope. */
3157
+ /**
3158
+ * Evaluates an expression against this scope.
3159
+ * @param {string} expression
3160
+ * @returns {any}
3161
+ */
3533
3162
  evaluateExpression(expression) {
3534
- return Expressions.interpret(this.#modules, this.#dataStack, expression, Expressions.MODE_EXPRESSION);
3163
+ return Expressions.interpret(
3164
+ this.#modules,
3165
+ this.#dataStack,
3166
+ expression,
3167
+ Expressions.MODE_EXPRESSION,
3168
+ this.#bound,
3169
+ );
3535
3170
  }
3536
- /** Evaluates a templated text, the `{{ }}` form, against this scope. */
3171
+ /**
3172
+ * Evaluates a templated text, the `{{ }}` form, against this scope.
3173
+ * @param {string} text
3174
+ * @returns {{ type: symbol, value: any }[]} its parts, as MODE_TEMPLATED describes
3175
+ */
3537
3176
  evaluateTemplated(text) {
3538
- return Expressions.interpret(this.#modules, this.#dataStack, text, Expressions.MODE_TEMPLATED);
3177
+ return Expressions.interpret(this.#modules, this.#dataStack, text, Expressions.MODE_TEMPLATED, this.#bound);
3539
3178
  }
3540
3179
  }
3541
3180
 
3542
- /** Creates and inspects the DocumentFragments a Template renders into. */
3181
+ /** Creates, serializes and inspects DocumentFragments. */
3543
3182
  class Fragments {
3544
3183
  /**
3545
- * Creates a DocumentFragment from an string.
3546
- * @param {...string} html
3547
- * @returns {DocumentFragment} the fragment
3184
+ * Parses html into a DocumentFragment adopted by the document, each piece
3185
+ * trimmed and the pieces joined.
3186
+ * @param {...string} html
3187
+ * @returns {DocumentFragment}
3548
3188
  */
3549
3189
  static fromHtml(...html) {
3550
3190
  const el = document.createElement('template');
@@ -3552,11 +3192,11 @@ var ftl = (function (exports) {
3552
3192
  return document.adoptNode(el.content);
3553
3193
  }
3554
3194
  /**
3555
- * Creates a string representation (HTML) of a DocumentFragment, consuming it:
3556
- * the nodes are moved out, not copied, and the fragment is left empty. Pass
3195
+ * Serializes a fragment, or a single node, to html, consuming it: the nodes
3196
+ * are moved out, not copied, and a fragment is left empty. Pass
3557
3197
  * `fragment.cloneNode(true)` to keep the original usable.
3558
- * @param {DocumentFragment} fragment
3559
- * @returns {string} the html
3198
+ * @param {DocumentFragment|Node} fragment
3199
+ * @returns {string}
3560
3200
  */
3561
3201
  static toHtml(fragment) {
3562
3202
  var el = document.createElement('template');
@@ -3564,17 +3204,17 @@ var ftl = (function (exports) {
3564
3204
  return el.innerHTML;
3565
3205
  }
3566
3206
  /**
3567
- * Checks if a fragment contains only blank text nodes
3207
+ * Whether a fragment holds no element and no text but whitespace.
3568
3208
  * @param {DocumentFragment} fragment
3569
- * @returns {boolean} true if the fragment is blank
3209
+ * @returns {boolean}
3570
3210
  */
3571
3211
  static isBlank(fragment) {
3572
3212
  return fragment.childElementCount === 0 && fragment.textContent.trim() === '';
3573
3213
  }
3574
3214
  /**
3575
- * Creates a DocumentFragment from nodes.
3576
- * @param {...Node} nodes
3577
- * @returns {DocumentFragment} the fragment
3215
+ * Moves the nodes, in order, into a new DocumentFragment.
3216
+ * @param {...Node} nodes
3217
+ * @returns {DocumentFragment}
3578
3218
  */
3579
3219
  static from(...nodes) {
3580
3220
  const fragment = new DocumentFragment();
@@ -3584,9 +3224,10 @@ var ftl = (function (exports) {
3584
3224
  return fragment;
3585
3225
  }
3586
3226
  /**
3587
- * Creates a DocumentFragment from childNodes of an element.
3227
+ * Moves the children of a node into a new DocumentFragment, leaving the
3228
+ * node empty.
3588
3229
  * @param {Node} el
3589
- * @returns {DocumentFragment} the fragment
3230
+ * @returns {DocumentFragment}
3590
3231
  */
3591
3232
  static fromChildNodes(el) {
3592
3233
  const fragment = new DocumentFragment();
@@ -3597,23 +3238,23 @@ var ftl = (function (exports) {
3597
3238
  }
3598
3239
  }
3599
3240
 
3600
- /** Attribute reads and writes where a nullish value removes the attribute instead of setting it to the string 'null'. */
3241
+ /** Attribute helpers: unique ids, defaults, prefixed forwarding, and writes where a nullish value removes the attribute. */
3601
3242
  class Attributes {
3602
3243
  static id = 0;
3603
3244
  /**
3604
- * Creates a unique id with the given prefix.
3245
+ * A page-unique id under the prefix, as `prefix-n`.
3605
3246
  * @param {string} prefix
3606
- * @returns
3247
+ * @returns {string}
3607
3248
  */
3608
3249
  static uid(prefix) {
3609
3250
  return `${prefix}-${++Attributes.id}`;
3610
3251
  }
3611
3252
  /**
3612
- * Sets an attribute if not present.
3253
+ * Sets an attribute when the element does not carry it.
3613
3254
  * @param {Element} el
3614
3255
  * @param {string} k
3615
3256
  * @param {string} v
3616
- * @returns
3257
+ * @returns {string|null} the attribute's value afterwards
3617
3258
  */
3618
3259
  static defaultValue(el, k, v) {
3619
3260
  if (!el.hasAttribute(k)) {
@@ -3622,7 +3263,9 @@ var ftl = (function (exports) {
3622
3263
  return el.getAttribute(k);
3623
3264
  }
3624
3265
  /**
3625
- * Forwards prefixed attributes from an element to another (removing the prefix).
3266
+ * Copies every attribute whose name starts with the prefix onto another
3267
+ * element, under the name without the prefix. `<prefix>class` adds its
3268
+ * classes to the target's instead of replacing them.
3626
3269
  * @param {string} prefix
3627
3270
  * @param {Element} from
3628
3271
  * @param {Element} to
@@ -3645,7 +3288,7 @@ var ftl = (function (exports) {
3645
3288
  });
3646
3289
  }
3647
3290
  /**
3648
- * Sets the value of an attribute. nullish values remove the attribute.
3291
+ * Sets an attribute, or removes it when the value is nullish.
3649
3292
  * @param {Element} el
3650
3293
  * @param {string} attr
3651
3294
  * @param {string | null | undefined} value
@@ -3667,13 +3310,14 @@ var ftl = (function (exports) {
3667
3310
  */
3668
3311
  class LightSlots {
3669
3312
  /**
3670
- * Extracts light slots from an element. For non default slots in a template tag, the content is extracted.
3313
+ * Takes an element's children apart into slots, leaving the element empty.
3314
+ * A named child is removed and contributes what `slotFromNode` answers;
3315
+ * children sharing a name are gathered in order. A child with an empty
3316
+ * `slot` belongs to the default slot, in document order among the rest.
3671
3317
  * @param {Element} el
3672
- * @returns the slots
3318
+ * @returns {Record<string, DocumentFragment>} the slots by name, `default` always present
3673
3319
  */
3674
3320
  static from(el) {
3675
- //the platform reads slot="" as the default slot: such children stay in
3676
- //the document order of the default content, their claim stripped like any named one
3677
3321
  for (const child of el.children) {
3678
3322
  if (child.matches('[slot=""]')) {
3679
3323
  child.removeAttribute('slot');
@@ -3688,7 +3332,7 @@ var ftl = (function (exports) {
3688
3332
  el.removeAttribute('slot');
3689
3333
  return [slot, LightSlots.slotFromNode(el)];
3690
3334
  });
3691
- const slots = {};
3335
+ const slots = Object.create(null);
3692
3336
  slots.default = new DocumentFragment();
3693
3337
  slots.default.append(...el.childNodes);
3694
3338
  for (const [name, el] of namedSlots) {
@@ -3699,6 +3343,12 @@ var ftl = (function (exports) {
3699
3343
  }
3700
3344
  return slots;
3701
3345
  }
3346
+ /**
3347
+ * What a slotted node contributes: a template's content, the parsed markup
3348
+ * of a `<script type="text/html">`, or the node itself.
3349
+ * @param {Element} el
3350
+ * @returns {Element|DocumentFragment}
3351
+ */
3702
3352
  static slotFromNode(el) {
3703
3353
  if (el instanceof HTMLTemplateElement) {
3704
3354
  return document.adoptNode(el.content);
@@ -3710,12 +3360,13 @@ var ftl = (function (exports) {
3710
3360
  }
3711
3361
  }
3712
3362
 
3713
- /** Waits for the parser and for the document, for elements that upgrade before their own markup is complete. */
3363
+ /** Waits on the parser and the document, and queries an element's own children. */
3714
3364
  class Nodes {
3715
3365
  /**
3716
- * Checks if an element is already parsed.
3366
+ * Whether the parser has moved past the element: the element, or one of
3367
+ * its ancestors, has a next sibling.
3717
3368
  * @param {Element} el
3718
- * @returns
3369
+ * @returns {boolean}
3719
3370
  */
3720
3371
  static isParsed(el) {
3721
3372
  for (let c = /** @type {Node | null} */ (el); c; c = c.parentNode) {
@@ -3727,13 +3378,12 @@ var ftl = (function (exports) {
3727
3378
  }
3728
3379
 
3729
3380
  /**
3730
- * Waits for the document's DOMContentLoaded, resolving immediately when
3731
- * that moment already passed. 'interactive' alone cannot tell a document
3732
- * still waiting for deferred scripts (the event comes, however late) from
3733
- * a module imported past it (the event is gone): the two events order
3734
- * themselves, DOMContentLoaded always precedes load, so racing the two
3735
- * resolves with DCL whenever it is still coming and with load otherwise,
3736
- * never early.
3381
+ * Waits for the document's DOMContentLoaded. Resolves at once when that
3382
+ * moment already passed, and for a document with no view, which receives
3383
+ * no events. Never resolves early for a document still running its
3384
+ * deferred scripts.
3385
+ * @param {Document} doc
3386
+ * @returns {Promise<void>}
3737
3387
  */
3738
3388
  static waitDomContentLoaded(doc) {
3739
3389
  if (doc.readyState === 'loading') {
@@ -3746,7 +3396,13 @@ var ftl = (function (exports) {
3746
3396
  }
3747
3397
  const win = doc.defaultView;
3748
3398
  if (win === null) {
3749
- //a viewless document can receive no event at all
3399
+ return Promise.resolve();
3400
+ }
3401
+ // interactive spans both the deferred scripts and the time between DOMContentLoaded and load
3402
+ const [navigation] = /** @type {PerformanceNavigationTiming[]} */ (
3403
+ win.performance?.getEntriesByType('navigation') ?? []
3404
+ );
3405
+ if (navigation?.domContentLoadedEventStart > 0) {
3750
3406
  return Promise.resolve();
3751
3407
  }
3752
3408
  return new Promise((resolve) => {
@@ -3765,22 +3421,12 @@ var ftl = (function (exports) {
3765
3421
  * resolves once the element, or any of its ancestors, gains a next sibling,
3766
3422
  * which is the parser having moved past this subtree. The document's
3767
3423
  * DOMContentLoaded is the deadline. Resolves immediately for an element
3768
- * that is already parsed.
3769
- *
3770
- * Every ancestor is watched, not only the parent, because that is what the
3771
- * predicate reads: an element last among its siblings becomes parsed when
3772
- * an ancestor gains one, and a childList observer on the parent never sees
3773
- * that. Formatted markup usually hides the difference, the whitespace
3774
- * before a closing tag being a text node the parent does gain, so the gap
3775
- * shows on whitespace-free markup, where the wait fell back to the
3776
- * deadline. One observer takes many targets, so the cost is one observe()
3777
- * per level, and it is disconnected at the first checkpoint past the
3778
- * element either way.
3779
- * @param {any} el
3780
- * @returns {Promise<any>}
3424
+ * that is already parsed, or once the parser has finished the document.
3425
+ * @param {Element} el
3426
+ * @returns {Promise<Element>} the element
3781
3427
  */
3782
3428
  static waitParsed(el) {
3783
- if (Nodes.isParsed(el)) {
3429
+ if (Nodes.isParsed(el) || el.ownerDocument.readyState !== 'loading') {
3784
3430
  return Promise.resolve(el);
3785
3431
  }
3786
3432
  return new Promise((resolve) => {
@@ -3802,10 +3448,10 @@ var ftl = (function (exports) {
3802
3448
  }
3803
3449
 
3804
3450
  /**
3805
- * Returns the first child of the element element (if exists) matching the selector.
3806
- * @param {Element} el
3451
+ * The first child element matching the selector, descendants excluded.
3452
+ * @param {Element|DocumentFragment} el
3807
3453
  * @param {string} selector
3808
- * @returns
3454
+ * @returns {Element|null}
3809
3455
  */
3810
3456
  static queryChildren(el, selector) {
3811
3457
  for (const c of el.children) {
@@ -3816,10 +3462,10 @@ var ftl = (function (exports) {
3816
3462
  return null;
3817
3463
  }
3818
3464
  /**
3819
- * Returns all children of the element matching the selector.
3820
- * @param {Element} el
3465
+ * Every child element matching the selector, descendants excluded.
3466
+ * @param {Element|DocumentFragment} el
3821
3467
  * @param {string} selector
3822
- * @returns
3468
+ * @returns {Element[]}
3823
3469
  */
3824
3470
  static queryChildrenAll(el, selector) {
3825
3471
  const r = [];
@@ -3876,9 +3522,11 @@ var ftl = (function (exports) {
3876
3522
 
3877
3523
  /** The `data-tpl-*` commands, each taking the node it is written on and the scope it evaluates in. */
3878
3524
  class CommandsHandler {
3879
- //the order is the semantics: each command sees the node as the ones before
3880
- //it left it, so tplIf gates before tplWith/tplEach push their overlay and
3881
- //tplWhen gates after (inside the sub-render, where the overlay is in scope)
3525
+ /**
3526
+ * The order the commands run in on one element, each seeing the node as the
3527
+ * ones before left it: tplIf gates before tplWith and tplEach push their
3528
+ * overlay, tplWhen gates after, inside the scope that overlay opened.
3529
+ */
3882
3530
  static ORDERED_COMMANDS = [
3883
3531
  'tplIf',
3884
3532
  'tplWith',
@@ -3889,8 +3537,7 @@ var ftl = (function (exports) {
3889
3537
  'tplRemove',
3890
3538
  'tplVerbatim',
3891
3539
  ];
3892
- //same body as tplWhen: the two differ only in ORDERED_COMMANDS position,
3893
- //if evaluates in the outer scope, before with/each
3540
+ /** Removes the node when the expression is falsy, evaluated before tplWith and tplEach open their overlay. */
3894
3541
  static tplIf(node, expression, ops, evaluator) {
3895
3542
  const accept = evaluator.evaluateExpression(expression);
3896
3543
  if (!accept) {
@@ -3905,49 +3552,37 @@ var ftl = (function (exports) {
3905
3552
  .render();
3906
3553
  ops.replace(node, newNode);
3907
3554
  }
3555
+ /**
3556
+ * Renders the node once per entry, in place of the node. A Map iterates as
3557
+ * `{ key, value }` entries in its own order, a plain object as `{ key, value }`
3558
+ * entries in `Object.entries` order, and any other iterable as itself; a
3559
+ * value that is none of these throws. Each render sees its entry under
3560
+ * `data-tpl-var`, or as the whole overlay without one. `data-tpl-stat` names
3561
+ * an overlay beneath the entry carrying `index`, `count`, `size`, `first`,
3562
+ * `last`, `even` and `odd`, so an entry property of the same name wins; the
3563
+ * size, and so `last`, is null for a collection that knows neither its
3564
+ * `length` nor its `size`, which is never consumed to learn it.
3565
+ */
3908
3566
  static tplEach(node, expression, ops, evaluator) {
3909
3567
  const varName = ops.popData(node, 'tplVar');
3910
3568
  const statName = ops.popData(node, 'tplStat');
3911
3569
  const template = new Template(node, evaluator);
3912
3570
  const evaluated = evaluator.evaluateExpression(expression);
3913
- //keyed collections iterate as {key, value} entries: a Map in its own
3914
- //order, a non-iterable plain dict in Object.entries order. Plain alone:
3915
- //a class instance or a response wrapper standing where an array was
3916
- //expected still fails loudly below, and any other iterable (a Set, a
3917
- //generator, an entries() iterator) iterates as itself
3918
- const proto = evaluated === null || typeof evaluated !== 'object' ? undefined : Object.getPrototypeOf(evaluated);
3919
- const keyed =
3920
- evaluated instanceof Map
3921
- ? [...evaluated]
3922
- : !evaluated?.[Symbol.iterator] && (proto === Object.prototype || proto === null)
3923
- ? Object.entries(evaluated)
3924
- : null;
3571
+ const proto =
3572
+ evaluated === null || typeof evaluated !== 'object' ? undefined : Object.getPrototypeOf(evaluated);
3573
+ const plain = !evaluated?.[Symbol.iterator] && (proto === Object.prototype || proto === null);
3574
+ const dict = plain ? Object.entries(evaluated) : null;
3575
+ const keyed = evaluated instanceof Map ? [...evaluated] : dict;
3925
3576
  const entries = keyed === null ? evaluated : keyed.map(([key, value]) => ({ key, value }));
3926
3577
  if (!entries?.[Symbol.iterator]) {
3927
3578
  throw new Error(`Expected an iterable got '${evaluated}'`);
3928
3579
  }
3929
- if (!statName) {
3930
- for (const v of entries) {
3931
- ops.prepend(node, template.withOverlay(varName ? { [varName]: v } : v).render());
3932
- }
3933
- ops.remove(node);
3934
- return;
3935
- }
3936
- //the stat rides grouped under its declared name, overlaid below the
3937
- //item, so data can never collide with its fields and an item property
3938
- //sharing the stat's very name wins, as data always does. The size is
3939
- //read where the collection already knows it (arrays and keyed entries
3940
- //by length, sets and the like by size): an arbitrary iterator is never
3941
- //consumed to learn it, so its size and last read null, the unknown
3942
- const size = Array.isArray(entries)
3943
- ? entries.length
3944
- : typeof entries.length === 'number'
3945
- ? entries.length
3946
- : typeof entries.size === 'number'
3947
- ? entries.size
3948
- : null;
3580
+ const measured = statName ? entries : null;
3581
+ const counted = typeof measured?.length === 'number' ? measured.length : measured?.size;
3582
+ const size = typeof counted === 'number' ? counted : null;
3949
3583
  let index = 0;
3950
3584
  for (const v of entries) {
3585
+ const item = varName ? { [varName]: v } : v;
3951
3586
  const stat = {
3952
3587
  index,
3953
3588
  count: index + 1,
@@ -3957,16 +3592,13 @@ var ftl = (function (exports) {
3957
3592
  even: index % 2 === 0,
3958
3593
  odd: index % 2 === 1,
3959
3594
  };
3960
- ops.prepend(
3961
- node,
3962
- template.withOverlay({ [statName]: stat }, varName ? { [varName]: v } : v).render(),
3963
- );
3595
+ const layers = statName ? [{ [statName]: stat }, item] : [item];
3596
+ ops.prepend(node, template.withOverlay(...layers).render());
3964
3597
  ++index;
3965
3598
  }
3966
3599
  ops.remove(node);
3967
3600
  }
3968
- //same body as tplIf: the two differ only in ORDERED_COMMANDS position,
3969
- //when evaluates after with/each, in the scope their overlay opened
3601
+ /** Removes the node when the expression is falsy, evaluated in the overlay tplWith or tplEach opened. */
3970
3602
  static tplWhen(node, expression, ops, evaluator) {
3971
3603
  const accept = evaluator.evaluateExpression(expression);
3972
3604
  if (!accept) {
@@ -3980,12 +3612,8 @@ var ftl = (function (exports) {
3980
3612
  static tplRemove(node, value, ops, evaluator) {
3981
3613
  switch (value.toLowerCase()) {
3982
3614
  case 'tag': {
3983
- const fragment = new DocumentFragment();
3984
- while (node.firstChild) {
3985
- fragment.appendChild(node.firstChild);
3986
- }
3615
+ const fragment = Fragments.fromChildNodes(node);
3987
3616
  if ('tplVerbatim' in node.dataset) {
3988
- //we are removing the parent element so we have to handle the lower priority commands
3989
3617
  ops.popData(node, 'tplVerbatim');
3990
3618
  ops.replace(node, fragment);
3991
3619
  } else {
@@ -4021,6 +3649,9 @@ var ftl = (function (exports) {
4021
3649
  }
4022
3650
  const tuples = Array.isArray(attributesAndValues[0]) ? attributesAndValues : [attributesAndValues];
4023
3651
  tuples.forEach(([k, v]) => {
3652
+ if (v === null || v === undefined) {
3653
+ return;
3654
+ }
4024
3655
  node.setAttribute(k, v);
4025
3656
  });
4026
3657
  }
@@ -4048,12 +3679,10 @@ var ftl = (function (exports) {
4048
3679
  }
4049
3680
  }
4050
3681
 
4051
- // Module-isolated string cache for dataset-to-attribute conversions
4052
3682
  const attributeCache = new BoundedCache(1000);
4053
3683
 
4054
3684
  /**
4055
- * Converts a tpl camelCase dataset key into a kebab-case attribute name.
4056
- * Uses a cache lookup to avoid repetitive regex/string splitting.
3685
+ * The attribute a `tpl` dataset key targets: `tplAriaLabel` is `aria-label`.
4057
3686
  * @param {string} dataSetKey
4058
3687
  * @returns {string}
4059
3688
  */
@@ -4075,38 +3704,40 @@ var ftl = (function (exports) {
4075
3704
  */
4076
3705
  class Template {
4077
3706
  /**
4078
- * Creates a template from a string.
3707
+ * Creates a template from html.
4079
3708
  * @param {string} html
4080
3709
  * @param {{ [k: string] : any }?} [modules]
4081
- * @param {...*} data
4082
- * @returns the template
3710
+ * @param {...*} data the data stack, innermost last
3711
+ * @returns {Template}
4083
3712
  */
4084
3713
  static fromHtml(html, modules, ...data) {
4085
3714
  return new Template(Fragments.fromHtml(html), new ExpressionEvaluator(modules, data));
4086
3715
  }
4087
3716
 
4088
3717
  /**
4089
- * Creates a template from the content of the first template element matching the selector.
3718
+ * Creates a template from the content of the first element matching the
3719
+ * selector, which is adopted: the template element is left empty.
4090
3720
  * @param {string} selector for an HTMLTemplateElement
4091
3721
  * @param {{ [k: string] : any }?} [modules]
4092
- * @param {...*} data
4093
- * @returns the template
3722
+ * @param {...*} data the data stack, innermost last
3723
+ * @returns {Template}
3724
+ * @throws when the selector matches no template element
4094
3725
  */
4095
3726
  static fromSelector(selector, modules, ...data) {
4096
3727
  const templateEl = document.querySelector(selector);
4097
3728
  if (!(templateEl instanceof HTMLTemplateElement)) {
4098
3729
  throw new Error('template selector does not match any template tag');
4099
3730
  }
4100
- const fragment = document.adoptNode(templateEl.content);
4101
- return new Template(fragment, new ExpressionEvaluator(modules, data));
3731
+ return Template.fromTemplate(templateEl, modules, ...data);
4102
3732
  }
4103
3733
 
4104
3734
  /**
4105
- * Creates a template from the content of an HTMLTemplateElement.
3735
+ * Creates a template from the content of a template element, which is
3736
+ * adopted: the template element is left empty.
4106
3737
  * @param {HTMLTemplateElement} templateEl
4107
3738
  * @param {{ [k: string] : any }?} [modules]
4108
- * @param {...*} data
4109
- * @returns the template
3739
+ * @param {...*} data the data stack, innermost last
3740
+ * @returns {Template}
4110
3741
  */
4111
3742
  static fromTemplate(templateEl, modules, ...data) {
4112
3743
  const fragment = document.adoptNode(templateEl.content);
@@ -4114,11 +3745,12 @@ var ftl = (function (exports) {
4114
3745
  }
4115
3746
 
4116
3747
  /**
4117
- * Creates a template from a DocumentFragment.
3748
+ * Creates a template over a fragment, which is imported afresh on every
3749
+ * render and never modified.
4118
3750
  * @param {DocumentFragment} fragment
4119
3751
  * @param { { [k: string] : any }? } [modules]
4120
- * @param {...*} data
4121
- * @returns the template
3752
+ * @param {...*} data the data stack, innermost last
3753
+ * @returns {Template}
4122
3754
  */
4123
3755
  static fromFragment(fragment, modules, ...data) {
4124
3756
  return new Template(fragment, new ExpressionEvaluator(modules, data));
@@ -4138,28 +3770,32 @@ var ftl = (function (exports) {
4138
3770
  * Creates a new Template rendering in another scope: the one way to rebind
4139
3771
  * a compiled template to a registry's modules and data.
4140
3772
  * @param {ExpressionEvaluator} evaluator
3773
+ * @returns {Template}
4141
3774
  */
4142
3775
  withEvaluator(evaluator) {
4143
3776
  return new Template(this.#fragment, evaluator);
4144
3777
  }
4145
3778
  /**
4146
- * Creates a new Template replacing the fragment.
3779
+ * Creates a new Template over another fragment, in the same scope.
4147
3780
  * @param {DocumentFragment} fragment
3781
+ * @returns {Template}
4148
3782
  */
4149
3783
  withFragment(fragment) {
4150
3784
  return new Template(fragment, this.#evaluator);
4151
3785
  }
4152
3786
  /**
4153
- * Creates a new Template with a new module added.
3787
+ * Creates a new Template with a module added, as `ExpressionEvaluator.withModule` does.
4154
3788
  * @param {string?} name
4155
3789
  * @param {{[k: string]: any}} value
3790
+ * @returns {Template}
4156
3791
  */
4157
3792
  withModule(name, value) {
4158
3793
  return new Template(this.#fragment, this.#evaluator.withModule(name, value));
4159
3794
  }
4160
3795
  /**
4161
- * Creates a new Template with new a data overlay added to the stack.
3796
+ * Creates a new Template with the data pushed as the innermost overlays.
4162
3797
  * @param {...*} data
3798
+ * @returns {Template}
4163
3799
  */
4164
3800
  withOverlay(...data) {
4165
3801
  return new Template(this.#fragment, this.#evaluator.withOverlay(...data));
@@ -4168,7 +3804,7 @@ var ftl = (function (exports) {
4168
3804
  * Evaluates an expression in this template's scope, widened by the overlays.
4169
3805
  * @param {string} expression
4170
3806
  * @param {...*} data
4171
- * @returns the evaluated expression result
3807
+ * @returns {any}
4172
3808
  */
4173
3809
  evaluateExpression(expression, ...data) {
4174
3810
  return this.#evaluator.withOverlay(...data).evaluateExpression(expression);
@@ -4177,33 +3813,29 @@ var ftl = (function (exports) {
4177
3813
  * Evaluates a templated text, the `{{ }}` form, in this template's scope.
4178
3814
  * @param {string} text
4179
3815
  * @param {...*} data
4180
- * @returns the parts the text evaluates to
3816
+ * @returns {{ type: symbol, value: any }[]} the parts, as `Expressions.MODE_TEMPLATED` describes
4181
3817
  */
4182
3818
  evaluateTemplated(text, ...data) {
4183
3819
  return this.#evaluator.withOverlay(...data).evaluateTemplated(text);
4184
3820
  }
4185
3821
  /**
4186
- * Returns an expression evaluator with bound modules and dataStack.
3822
+ * The scope this template renders in.
3823
+ * @returns {ExpressionEvaluator}
4187
3824
  */
4188
3825
  evaluator() {
4189
3826
  return this.#evaluator;
4190
3827
  }
4191
3828
  /**
4192
- * Renders the template.
4193
- * @returns a DocumentFragment
3829
+ * Renders the template into a new fragment, applying the `data-tpl-*`
3830
+ * commands and bindings and the `{{ }}` interpolations.
3831
+ * @returns {DocumentFragment}
3832
+ * @throws {RenderError} naming the node and the directive that failed
4194
3833
  */
4195
3834
  render() {
4196
3835
  try {
4197
3836
  const ops = new NodeOperations();
4198
3837
  const imported = document.importNode(this.#fragment, true);
4199
- const fragment =
4200
- imported.nodeType === Node.DOCUMENT_FRAGMENT_NODE
4201
- ? imported
4202
- : (() => {
4203
- const d = new DocumentFragment();
4204
- d.appendChild(imported);
4205
- return d;
4206
- })();
3838
+ const fragment = imported.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? imported : Fragments.from(imported);
4207
3839
  const iterator = document.createNodeIterator(
4208
3840
  fragment,
4209
3841
  NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
@@ -4229,8 +3861,6 @@ var ftl = (function (exports) {
4229
3861
  try {
4230
3862
  CommandsHandler[command](el, value, ops, this.#evaluator);
4231
3863
  } catch (ex) {
4232
- //the directive is popped before it runs, so the open tag no
4233
- //longer carries it: the frame names it and its expression
4234
3864
  throw RenderError.wrap(`Error evaluating data-tpl-${toAttr(command)}="${value}"`, el, ex);
4235
3865
  }
4236
3866
  if (ops.removed(el)) {
@@ -4253,17 +3883,13 @@ var ftl = (function (exports) {
4253
3883
  el.setAttribute(attributeName, evaluated);
4254
3884
  }
4255
3885
  } catch (ex) {
4256
- throw RenderError.wrap(`Error evaluating data-tpl-${toAttr(dataSetKey)}="${expression}"`, el, ex);
3886
+ throw RenderError.wrap(`Error evaluating data-tpl-${attributeName}="${expression}"`, el, ex);
4257
3887
  }
4258
3888
  }
4259
3889
  }
4260
3890
  ops.cleanup();
4261
3891
  return fragment;
4262
3892
  } catch (ex) {
4263
- //a command, a text node or a nested render already named the node it
4264
- //failed on: wrapping again would add a frame for the fragment that
4265
- //contains it, one per level, serializing the whole template into the
4266
- //message that survives. Only a failure outside those is framed here
4267
3893
  if (ex instanceof RenderError) {
4268
3894
  throw ex;
4269
3895
  }
@@ -4271,22 +3897,26 @@ var ftl = (function (exports) {
4271
3897
  }
4272
3898
  }
4273
3899
  /**
4274
- * Renders this template on the Element (replacing children).
3900
+ * Renders this template into the element, replacing its children.
4275
3901
  * @param {Element} el
3902
+ * @throws {RenderError}
4276
3903
  */
4277
3904
  renderTo(el) {
4278
3905
  el.replaceChildren(this.render());
4279
3906
  }
4280
3907
  /**
4281
- * Renders this template appending the resulting fragment to the Element.
3908
+ * Renders this template and appends the result to the element.
4282
3909
  * @param {Element} el
3910
+ * @throws {RenderError}
4283
3911
  */
4284
3912
  appendTo(el) {
4285
3913
  el.appendChild(this.render());
4286
3914
  }
4287
3915
  /**
4288
- * Renders this template on the first Element matching the selector (replacing children), if exists.
3916
+ * Renders this template into the first element matching the selector,
3917
+ * replacing its children; does nothing when none matches.
4289
3918
  * @param {string} selector
3919
+ * @throws {RenderError}
4290
3920
  */
4291
3921
  renderToSelector(selector) {
4292
3922
  const el = document.querySelector(selector);
@@ -4295,8 +3925,10 @@ var ftl = (function (exports) {
4295
3925
  }
4296
3926
  }
4297
3927
  /**
4298
- * Renders this template appending the resulting fragment to the Element matching the selector, if exists.
3928
+ * Renders this template and appends the result to the first element
3929
+ * matching the selector; does nothing when none matches.
4299
3930
  * @param {string} selector
3931
+ * @throws {RenderError}
4300
3932
  */
4301
3933
  appendToSelector(selector) {
4302
3934
  const el = document.querySelector(selector);
@@ -4320,30 +3952,26 @@ var ftl = (function (exports) {
4320
3952
  }
4321
3953
 
4322
3954
  /**
4323
- * A render failure, one frame per nesting level. Each frame names the node it
4324
- * failed on and what was being evaluated there, so the chain reads as the path
4325
- * from the template's root down to the offending expression. The frame carries
4326
- * the node's identification only, an open tag rather than its whole subtree:
4327
- * the markup is serialized on demand through `html`, and the live node stays on
4328
- * `node`, so a failure costs no clone and a nested failure does not embed the
4329
- * page in its own message.
3955
+ * A render failure, one frame per nesting level, chained through `cause` from
3956
+ * the outermost frame down to the error that failed the expression. A frame's
3957
+ * message names what was being evaluated and the node's open tag, with the
3958
+ * attributes it still carries: the directive being evaluated is already off
3959
+ * the tag. The subtree is not in the message; `html` serializes it on demand.
4330
3960
  */
4331
3961
  class RenderError extends Error {
4332
- /**
4333
- * How many frames a chain keeps. The innermost are the specific ones, so a
4334
- * deeper nesting drops the outer context rather than the failure site: three
4335
- * frames name the offending node and the two levels that hold it, which is
4336
- * the path a reader follows without the page arriving with it.
4337
- */
3962
+ /** How many frames a chain keeps: a deeper nesting drops the outermost ones and marks the chain `truncated`. */
4338
3963
  static FRAMES = 3;
4339
3964
  #node;
4340
3965
  #depth;
4341
- /** true when the budget dropped the outer frames of this chain */
3966
+ /** Whether the chain dropped outer frames to stay within FRAMES. */
4342
3967
  truncated = false;
4343
3968
  /**
4344
- * Frames a failure, unless the chain already spent its budget: then the
4345
- * cause travels on, marked so a report can say the outer context was
4346
- * dropped.
3969
+ * Frames a failure, unless the chain already holds FRAMES frames: then the
3970
+ * cause is returned as it is, marked `truncated`.
3971
+ * @param {string} message
3972
+ * @param {Node} nodeOrFragment the node that failed
3973
+ * @param {any} cause
3974
+ * @returns {RenderError}
4347
3975
  */
4348
3976
  static wrap(message, nodeOrFragment, cause) {
4349
3977
  if (cause instanceof RenderError && cause.depth >= RenderError.FRAMES) {
@@ -4352,27 +3980,44 @@ var ftl = (function (exports) {
4352
3980
  }
4353
3981
  return new RenderError(message, nodeOrFragment, cause);
4354
3982
  }
3983
+ /**
3984
+ * @param {string} message
3985
+ * @param {Node} nodeOrFragment the node that failed
3986
+ * @param {any} cause
3987
+ */
4355
3988
  constructor(message, nodeOrFragment, cause) {
4356
3989
  super(`${message} in \`${RenderError.describe(nodeOrFragment)}\``, { cause });
4357
3990
  this.name = 'RenderError';
4358
3991
  this.#node = nodeOrFragment;
4359
3992
  this.#depth = (cause instanceof RenderError ? cause.depth : 0) + 1;
4360
3993
  }
4361
- /** How many frames this chain carries, this one included. */
3994
+ /**
3995
+ * How many frames this chain carries, this one included.
3996
+ * @returns {number}
3997
+ */
4362
3998
  get depth() {
4363
3999
  return this.#depth;
4364
4000
  }
4365
- /** The node the render failed on, live: it keeps its place in the fragment being built. */
4001
+ /**
4002
+ * The node the render failed on, live in the fragment being built.
4003
+ * @returns {Node}
4004
+ */
4366
4005
  get node() {
4367
4006
  return this.#node;
4368
4007
  }
4369
- /** The node's markup, serialized when asked for rather than on every failure. */
4008
+ /**
4009
+ * The failed node's markup, whitespace trimmed, serialized on each read.
4010
+ * @returns {string}
4011
+ */
4370
4012
  get html() {
4371
4013
  return RenderError.stringify(this.#node);
4372
4014
  }
4373
4015
  /**
4374
4016
  * What identifies a node in a frame: an element by its open tag, a text node
4375
- * by its source, a fragment by the open tags of the elements it holds.
4017
+ * by its source, a fragment by the open tags of the elements it holds, cut
4018
+ * at 120 characters.
4019
+ * @param {Node} nodeOrFragment
4020
+ * @returns {string}
4376
4021
  */
4377
4022
  static describe(nodeOrFragment) {
4378
4023
  if (nodeOrFragment.nodeType === Node.TEXT_NODE) {
@@ -4391,10 +4036,13 @@ var ftl = (function (exports) {
4391
4036
  static #ellipsize(text) {
4392
4037
  return text.length > 120 ? `${text.slice(0, 120)}…` : text;
4393
4038
  }
4039
+ /**
4040
+ * A node's markup with its whitespace-only text dropped and the rest trimmed.
4041
+ * @param {Node} nodeOrFragment left untouched: a clone is serialized
4042
+ * @returns {string}
4043
+ */
4394
4044
  static stringify(nodeOrFragment) {
4395
- const t = document.createElement('template');
4396
- t.content.appendChild(RenderError.#cleanup(nodeOrFragment.cloneNode(true)));
4397
- return t.innerHTML;
4045
+ return Fragments.toHtml(RenderError.#cleanup(nodeOrFragment.cloneNode(true)));
4398
4046
  }
4399
4047
  static #cleanup(node) {
4400
4048
  if (node.nodeType === Node.TEXT_NODE) {
@@ -4416,31 +4064,28 @@ var ftl = (function (exports) {
4416
4064
  }
4417
4065
  }
4418
4066
 
4419
- /**
4420
- * Tracks the elements waiting to render. `ready` resolves once the queue has
4421
- * drained and never rejects; the promise kept per element resolves when that
4422
- * element has rendered and rejects with whatever its upgrade threw.
4423
- */
4424
4067
  class UpgradeQueue {
4425
4068
  #q = new Map();
4426
4069
  #readyResolve;
4427
4070
  #ready = new Promise((resolve) => {
4428
4071
  this.#readyResolve = resolve;
4429
4072
  });
4073
+ #configured = /** @type {PromiseWithResolvers<void>} */ (Promise.withResolvers());
4430
4074
  constructor() {
4431
- Nodes.waitDomContentLoaded(document).then(() => {
4075
+ Promise.all([Nodes.waitDomContentLoaded(document), this.#configured.promise]).then(() => {
4432
4076
  this.#start();
4433
4077
  });
4434
4078
  }
4435
- /**
4436
- * Waits for the page's readiness: the promise resolves right after the
4437
- * ftl:ready event is dispatched, and immediately when that moment already
4438
- * passed.
4439
- * @returns {Promise<void>}
4440
- */
4441
4079
  ready() {
4442
4080
  return this.#ready;
4443
4081
  }
4082
+ /**
4083
+ * Marks the registry as configured: until then nothing is defined, so an
4084
+ * empty queue says nothing about the page and readiness is held back.
4085
+ */
4086
+ configured() {
4087
+ this.#configured.resolve();
4088
+ }
4444
4089
  async #start() {
4445
4090
  await this.settled();
4446
4091
  document.dispatchEvent(
@@ -4453,13 +4098,8 @@ var ftl = (function (exports) {
4453
4098
  }
4454
4099
  enqueue(el) {
4455
4100
  if (this.#q.has(el)) {
4456
- //already upgrading, can happen when disconnecting an element
4457
- //while it's already queued for upgrade (e.g.: ful-form)
4458
4101
  return;
4459
4102
  }
4460
- //one entry, two signals: readiness waits on a signal that only ever
4461
- //resolves, so nothing here attaches a rejection handler to the upgrade
4462
- //itself and a component that fails is still reported the way it always was
4463
4103
  const { promise: finished, resolve: markFinished } = /** @type {PromiseWithResolvers<void>} */ (
4464
4104
  Promise.withResolvers()
4465
4105
  );
@@ -4471,11 +4111,6 @@ var ftl = (function (exports) {
4471
4111
  });
4472
4112
  this.#q.set(el, { upgrade, finished });
4473
4113
  }
4474
- /**
4475
- * The one fixed-point loop: drains the accepted entries, including the ones
4476
- * enqueued while waiting, since a component is only queued once its parent
4477
- * connects it and a single pass would miss everything nested.
4478
- */
4479
4114
  async #drain(accept, pick) {
4480
4115
  for (;;) {
4481
4116
  const pending = Array.from(this.#q)
@@ -4488,22 +4123,28 @@ var ftl = (function (exports) {
4488
4123
  }
4489
4124
  }
4490
4125
  /**
4491
- * Waits for the whole queue to drain. Never rejects: a component that fails
4492
- * does not hold the others back, and readiness means the queue drained rather
4493
- * than that everything worked.
4126
+ * Waits for the whole queue to drain, the upgrades enqueued meanwhile included.
4127
+ * Never rejects: a failed upgrade counts as drained.
4128
+ * @returns {Promise<void>}
4494
4129
  */
4495
4130
  settled() {
4496
- return this.#drain(() => true, (entry) => entry.finished);
4131
+ return this.#drain(
4132
+ () => true,
4133
+ (entry) => entry.finished,
4134
+ );
4497
4135
  }
4498
- /** Waits for the accepted upgrades, rejecting with the first that failed. */
4136
+ /**
4137
+ * Waits for the accepted upgrades, the ones enqueued meanwhile included,
4138
+ * rejecting with the first that failed.
4139
+ * @param {(el: Element) => boolean} accept
4140
+ * @returns {Promise<void>}
4141
+ */
4499
4142
  upgraded(accept) {
4500
4143
  return this.#drain(accept, (entry) => entry.upgrade);
4501
4144
  }
4502
- /** The pending upgrade of one element, undefined when it is not queued. */
4503
4145
  whenUpgraded(el) {
4504
4146
  return this.#q.get(el)?.upgrade;
4505
4147
  }
4506
- /** The elements whose upgrade is still pending, in queue order. */
4507
4148
  pending() {
4508
4149
  return Array.from(this.#q.keys());
4509
4150
  }
@@ -4514,8 +4155,8 @@ var ftl = (function (exports) {
4514
4155
  * and attribute mappers. Elements defined before configure() are deferred and
4515
4156
  * defined by it; from then on every defineElement takes effect immediately.
4516
4157
  * The exported `registry` singleton is the page's own; a `new Registry()` is a
4517
- * separate instance, and Rendering and the ftl:ready machinery wait on the
4518
- * singleton alone.
4158
+ * separate instance with its own upgrade queue, which dispatches its own
4159
+ * ftl:ready on the document, while Rendering waits on the singleton alone.
4519
4160
  */
4520
4161
  class Registry {
4521
4162
  #tagToClass = {};
@@ -4584,9 +4225,12 @@ var ftl = (function (exports) {
4584
4225
  * Registers a custom element under its tag: the class is augmented with its
4585
4226
  * BITS (observed attributes, mappers, templates) and handed to the platform.
4586
4227
  * Before configure() the definition is deferred, so import order never
4587
- * matters.
4228
+ * matters. The element resolves its templates and components through this
4229
+ * registry, whichever one a module imports, and its `static config` is read
4230
+ * here, so a page replacing it does so before the definition.
4588
4231
  * @param {string} tag
4589
4232
  * @param {*} klass a ParsedElement subclass
4233
+ * @returns {Registry} this registry
4590
4234
  */
4591
4235
  defineElement(tag, klass) {
4592
4236
  if (!this.#configured) {
@@ -4604,13 +4248,8 @@ var ftl = (function (exports) {
4604
4248
  * ancestors', so a base class declares what every subclass keeps observing
4605
4249
  * (a protocol attribute such as Field's disabled claim) and a leaf refines a
4606
4250
  * mapping, or moves a name's position, without repeating the whole list.
4607
- *
4608
- * The walk stops where the platform's own class hierarchy begins: no earlier
4609
- * stop can work, since a registered ancestor carries an own BITS of its own,
4610
- * and nothing above the elements declares anything.
4611
- *
4612
- * Everything deriving a component's attribute vocabulary reads it here, so
4613
- * the runtime and whatever documents it cannot walk the chain differently.
4251
+ * The observed attributes are applied in the composed order, a re-declared
4252
+ * name taking the position of its last declaration.
4614
4253
  * @param {*} klass a ParsedElement subclass
4615
4254
  * @returns {{ observed: string[], attributes: string[] }}
4616
4255
  */
@@ -4626,14 +4265,7 @@ var ftl = (function (exports) {
4626
4265
  * The property an observed attribute drives, by the platform's own
4627
4266
  * dash-to-camel rule: the one `dataset` applies, so `clear-invalid-on-change`
4628
4267
  * would reach `clearInvalidOnChange` the way `data-clear-invalid-on-change`
4629
- * reaches `dataset.clearInvalidOnChange`. A single-word name is returned
4630
- * unchanged, which is what every observed attribute in the library is.
4631
- *
4632
- * It exists because the observed tier is the one that becomes properties:
4633
- * without it an attribute could only be observed if its name happened to be
4634
- * a usable identifier, which is why every multiword observed attribute here
4635
- * used to be squashed into one word while the configuration tier, which
4636
- * never becomes a property, spelled the same idea with a dash.
4268
+ * reaches `dataset.clearInvalidOnChange`.
4637
4269
  *
4638
4270
  * Only this direction is mapped. A property never derives its attribute: a
4639
4271
  * setter reflects through `reflectTo`, naming the attribute it writes.
@@ -4646,10 +4278,6 @@ var ftl = (function (exports) {
4646
4278
  #augmentAndDefineElement(tag, klass) {
4647
4279
  const { observed, attributes } = Registry.declarationsOf(klass);
4648
4280
  const { template, templates, slots, mappers, config } = klass;
4649
- //a name a subclass re-declares takes the subclass's position, which is
4650
- //how a field whose value setter reads its own shape attributes declares
4651
- //that its value lands after them: the order the declarations compose in
4652
- //is the order the base applies them
4653
4281
  const declaredNames = observed.map((a) => a.split(':')[0]);
4654
4282
  const observedNames = [...new Set(declaredNames.reverse())].reverse();
4655
4283
  const attrToMapper = [...attributes, ...observed].reduce((acc, a) => {
@@ -4668,20 +4296,13 @@ var ftl = (function (exports) {
4668
4296
  const nameToTemplate = Object.fromEntries(namesAndTemplates);
4669
4297
 
4670
4298
  klass.BITS = {
4671
- //the defining registry travels with the definition: an element resolves
4672
- //its templates and its components through the registry that defined it,
4673
- //not through whichever one a module happened to import
4674
4299
  registry: this,
4675
4300
  enqueue: (el) => this.#upgradeQueue.enqueue(el),
4676
4301
  SLOTS: slots,
4677
- //the class's own constants, overlaid on its templates as `config`:
4678
- //read here with the rest of the declaration, so a page replacing them
4679
- //does it before configure() like every other definition-time choice
4680
4302
  CONFIG: config,
4681
4303
  OBSERVED: observedNames,
4682
4304
  DECLARED: [...new Set([...observedNames, ...attributes.map((a) => a.split(':')[0])])],
4683
4305
  ATTR_TO_MAPPER: attrToMapper,
4684
- //resolved once here rather than at every attribute write
4685
4306
  ATTR_TO_PROPERTY: Object.fromEntries(observedNames.map((a) => [a, Registry.propertyOf(a)])),
4686
4307
  TEMPLATES: nameToTemplate,
4687
4308
  };
@@ -4693,6 +4314,7 @@ var ftl = (function (exports) {
4693
4314
  * as `#fn`.
4694
4315
  * @param {string} name
4695
4316
  * @param {object} value
4317
+ * @returns {Registry} this registry
4696
4318
  */
4697
4319
  defineModule(name, value) {
4698
4320
  const module = name ? { [name]: value } : value;
@@ -4703,6 +4325,7 @@ var ftl = (function (exports) {
4703
4325
  /**
4704
4326
  * Replaces the whole module map.
4705
4327
  * @param {object} ms
4328
+ * @returns {Registry} this registry
4706
4329
  */
4707
4330
  defineModules(ms) {
4708
4331
  this.#modules = ms;
@@ -4714,6 +4337,7 @@ var ftl = (function (exports) {
4714
4337
  * through component().
4715
4338
  * @param {string} name
4716
4339
  * @param {*} value
4340
+ * @returns {Registry} this registry
4717
4341
  */
4718
4342
  defineComponent(name, value) {
4719
4343
  this.#components[name] = value;
@@ -4722,6 +4346,7 @@ var ftl = (function (exports) {
4722
4346
  /**
4723
4347
  * Replaces the data stack the templates evaluate over.
4724
4348
  * @param {...any} data
4349
+ * @returns {Registry} this registry
4725
4350
  */
4726
4351
  defineData(...data) {
4727
4352
  this.#data = data;
@@ -4731,6 +4356,7 @@ var ftl = (function (exports) {
4731
4356
  /**
4732
4357
  * Appends to the data stack, the later entry winning a shared name.
4733
4358
  * @param {...any} data
4359
+ * @returns {Registry} this registry
4734
4360
  */
4735
4361
  defineOverlay(...data) {
4736
4362
  this.#data = [...this.#data, ...data];
@@ -4742,6 +4368,7 @@ var ftl = (function (exports) {
4742
4368
  * `name:type` declaration.
4743
4369
  * @param {string} k
4744
4370
  * @param {{ unmarshal(str: string|null, name: string, el: Element): any, marshal(value: any, name: string, el: Element): string|null }} v
4371
+ * @returns {Registry} this registry
4745
4372
  */
4746
4373
  defineMapper(k, v) {
4747
4374
  this.#mappers[k] = v;
@@ -4750,6 +4377,7 @@ var ftl = (function (exports) {
4750
4377
  /**
4751
4378
  * Hands the registry over to the plugin's configure.
4752
4379
  * @param {{ configure(registry: Registry): void }} p
4380
+ * @returns {Registry} this registry
4753
4381
  */
4754
4382
  plugin(p) {
4755
4383
  p.configure(this);
@@ -4757,7 +4385,10 @@ var ftl = (function (exports) {
4757
4385
  }
4758
4386
  /**
4759
4387
  * Defines every element deferred so far; from then on, defineElement takes
4760
- * effect immediately.
4388
+ * effect immediately. Readiness waits for the first call: a page that
4389
+ * configures late (translations fetched first) gets its ftl:ready once the
4390
+ * elements this defines have upgraded, not at DOMContentLoaded.
4391
+ * @returns {Registry} this registry
4761
4392
  */
4762
4393
  configure() {
4763
4394
  for (const [tag, klass] of Object.entries(this.#tagToClass)) {
@@ -4765,27 +4396,43 @@ var ftl = (function (exports) {
4765
4396
  delete this.#tagToClass[tag];
4766
4397
  }
4767
4398
  this.#configured = true;
4399
+ this.#upgradeQueue.configured();
4768
4400
  return this;
4769
4401
  }
4770
4402
  /**
4771
4403
  * Waits for the queued upgrades the filter accepts, rejecting with the first
4772
- * that failed: the rejecting barrier Rendering is a facade over.
4404
+ * that failed, the upgrades enqueued meanwhile included: the rejecting
4405
+ * barrier Rendering is a facade over.
4773
4406
  * @param {(el: Element) => boolean} accept
4407
+ * @returns {Promise<void>}
4774
4408
  */
4775
4409
  settle(accept) {
4776
4410
  return this.#upgradeQueue.upgraded(accept);
4777
4411
  }
4778
- /** The pending upgrade of one element, undefined when it is not queued. */
4412
+ /**
4413
+ * The pending upgrade of one element: resolving when it has rendered,
4414
+ * rejecting with what its upgrade threw.
4415
+ * @param {Element} el
4416
+ * @returns {Promise<void>|undefined} undefined when the element is not queued
4417
+ */
4779
4418
  whenUpgraded(el) {
4780
4419
  return this.#upgradeQueue.whenUpgraded(el);
4781
4420
  }
4782
- /** The elements whose upgrade is still pending, in queue order. */
4421
+ /**
4422
+ * The elements whose upgrade is still pending, in queue order.
4423
+ * @returns {Element[]}
4424
+ */
4783
4425
  pending() {
4784
4426
  return this.#upgradeQueue.pending();
4785
4427
  }
4786
4428
  /**
4787
- * Waits for the page's readiness: the same moment the ftl:ready event is
4788
- * dispatched at, resolving immediately when that moment already passed.
4429
+ * Waits for this registry's readiness: resolves right after its ftl:ready
4430
+ * event is dispatched on the document, and immediately when that already
4431
+ * happened. The event is dispatched once DOMContentLoaded has passed,
4432
+ * configure() has been called and the queue has drained, so a registry
4433
+ * that is never configured is never ready. Never rejects: a failed
4434
+ * upgrade counts as settled, and its rejection is left unhandled so it
4435
+ * still reaches the console and the page's error reporting.
4789
4436
  * @returns {Promise<void>}
4790
4437
  */
4791
4438
  ready() {
@@ -4798,11 +4445,16 @@ var ftl = (function (exports) {
4798
4445
  * The scope every template on this registry renders in: the modules and the
4799
4446
  * data stack, as one value. Replaced whenever either is defined, so a holder
4800
4447
  * of an older one keeps rendering against what it was handed.
4448
+ * @returns {ExpressionEvaluator}
4801
4449
  */
4802
4450
  evaluator() {
4803
4451
  return this.#evaluator;
4804
4452
  }
4805
- /** The component registered under the name, undefined when none is. */
4453
+ /**
4454
+ * The component registered under the name.
4455
+ * @param {string} name
4456
+ * @returns {any} undefined when none is
4457
+ */
4806
4458
  component(name) {
4807
4459
  return this.#components[name];
4808
4460
  }
@@ -4810,33 +4462,59 @@ var ftl = (function (exports) {
4810
4462
 
4811
4463
  const registry = new Registry();
4812
4464
 
4813
- /** The Template factories, each bound to the page registry's scope. */
4814
4465
  /** The Template factories bound to the page's own registry: the same four sources, with its modules and data already applied. */
4815
4466
  class Templates {
4467
+ /**
4468
+ * @param {string} html
4469
+ * @returns {Template}
4470
+ */
4816
4471
  static fromHtml(html) {
4817
4472
  return Template.fromHtml(html).withEvaluator(registry.evaluator());
4818
4473
  }
4474
+ /**
4475
+ * @param {string} selector for an HTMLTemplateElement
4476
+ * @returns {Template}
4477
+ */
4819
4478
  static fromSelector(selector) {
4820
4479
  return Template.fromSelector(selector).withEvaluator(registry.evaluator());
4821
4480
  }
4481
+ /**
4482
+ * @param {HTMLTemplateElement} templateEl consumed: its content is adopted
4483
+ * @returns {Template}
4484
+ */
4822
4485
  static fromTemplate(templateEl) {
4823
4486
  return Template.fromTemplate(templateEl).withEvaluator(registry.evaluator());
4824
4487
  }
4488
+ /**
4489
+ * @param {DocumentFragment} fragment
4490
+ * @returns {Template}
4491
+ */
4825
4492
  static fromFragment(fragment) {
4826
4493
  return Template.fromFragment(fragment).withEvaluator(registry.evaluator());
4827
4494
  }
4828
4495
  }
4829
4496
 
4830
4497
  /**
4831
- * Awaitable rendering barriers over the registry's upgrade queue. These are
4832
- * the rejecting waits: a failed upgrade among the awaited components rejects
4833
- * the wait, where registry.ready() only ever means the queue drained and
4834
- * leaves a failed component to its own unhandled-rejection report.
4498
+ * Waits for elements queued on the page's `registry` to render. Unlike
4499
+ * `registry.ready()`, these reject when an awaited upgrade fails. An element
4500
+ * defined by another Registry is awaited through that registry instead.
4835
4501
  */
4836
4502
  class Rendering {
4503
+ /**
4504
+ * Waits for the element and its descendants, the ones their own renders
4505
+ * enqueue included.
4506
+ * @param {Element} el
4507
+ * @returns {Promise<void>} rejecting with the first upgrade that failed
4508
+ */
4837
4509
  static waitFor(el) {
4838
4510
  return registry.settle((child) => el.contains(child));
4839
4511
  }
4512
+ /**
4513
+ * Waits for the element's descendants, the element itself excluded: what a
4514
+ * component awaits from inside its own render.
4515
+ * @param {Element} el
4516
+ * @returns {Promise<void>} rejecting with the first upgrade that failed
4517
+ */
4840
4518
  static waitForChildren(el) {
4841
4519
  return registry.settle((child) => el !== child && el.contains(child));
4842
4520
  }
@@ -4850,9 +4528,15 @@ var ftl = (function (exports) {
4850
4528
  * @property {(val: any, name: string, el: Element) => string|null} marshal
4851
4529
  */
4852
4530
 
4531
+ /**
4532
+ * The base of every fml element. A subclass declares its attributes, slots and
4533
+ * templates in statics and builds its dom in `render`; the base reads the light
4534
+ * slots, reads the declared attributes through their mappers, renders once, and
4535
+ * then forwards observed attribute writes to the matching properties. An element
4536
+ * resolves templates and components through the registry that defined it.
4537
+ */
4853
4538
  class ParsedElement extends HTMLElement {
4854
4539
  static BITS = {
4855
- //an element the registry never defined still answers the page's own
4856
4540
  registry,
4857
4541
  enqueue: (el) => {},
4858
4542
  SLOTS: false,
@@ -4876,14 +4560,12 @@ var ftl = (function (exports) {
4876
4560
  this.internals = this.attachInternals();
4877
4561
  }
4878
4562
  /**
4879
- * The platform's window into the element's own state. The base attaches it
4880
- * for every element, so a subclass never calls attachInternals itself: the
4881
- * platform allows it once, and a second call throws. Form association is a
4882
- * property of the definition rather than of who attached, so a subclass
4883
- * declaring `static formAssociated` still gets the form apis here.
4884
- *
4885
- * A subclass must not redeclare it: a class field with no initializer
4886
- * assigns undefined after super() returns, which would wipe it.
4563
+ * The element's ElementInternals, attached by the base for every element: a
4564
+ * subclass never calls attachInternals itself, since a second call throws.
4565
+ * A subclass declaring `static formAssociated` gets the form apis here.
4566
+ * A subclass must not redeclare the field: a class field without an
4567
+ * initializer assigns undefined after super() returns, wiping it.
4568
+ * @type {ElementInternals}
4887
4569
  */
4888
4570
  internals;
4889
4571
  #parsed = false;
@@ -4900,26 +4582,36 @@ var ftl = (function (exports) {
4900
4582
  #mapper(attr) {
4901
4583
  const mapper = this.#bits().ATTR_TO_MAPPER[attr];
4902
4584
  if (!mapper) {
4903
- //an attribute the class never declared has no type to read it as:
4904
- //say so rather than dying on the missing mapper. Content this
4905
- //element does not own, such as a custom loader's own configuration,
4906
- //is read with getAttribute, the platform's own answer
4907
4585
  throw new Error(
4908
4586
  `${this.constructor.name} declares no attribute '${attr}': declare it in static observed or static attributes, or read it with getAttribute`,
4909
4587
  );
4910
4588
  }
4911
4589
  return mapper;
4912
4590
  }
4591
+ /**
4592
+ * Reads an attribute value through the mapper it was declared with.
4593
+ * @param {string} attr
4594
+ * @param {string|null} str
4595
+ * @returns {any}
4596
+ * @throws when the class declares no such attribute; read undeclared ones with getAttribute
4597
+ */
4913
4598
  unmarshal(attr, str) {
4914
4599
  return this.#mapper(attr).unmarshal(str, attr, this);
4915
4600
  }
4601
+ /**
4602
+ * Writes a value as the attribute string its declared mapper produces.
4603
+ * @param {string} attr
4604
+ * @param {any} value
4605
+ * @returns {string|null} null for a value that removes the attribute
4606
+ * @throws when the class declares no such attribute
4607
+ */
4916
4608
  marshal(attr, value) {
4917
4609
  return this.#mapper(attr).marshal(value, attr, this);
4918
4610
  }
4919
4611
  /**
4920
- * @param {string} [name] - The name of the template target, defaults to 'default'
4612
+ * The registry that defined this element, the page's own for an undefined one.
4613
+ * @returns {typeof registry}
4921
4614
  */
4922
- /** The registry that defined this element, the page's own for an undefined one. */
4923
4615
  get _registry() {
4924
4616
  return this.#bits().registry;
4925
4617
  }
@@ -4927,10 +4619,18 @@ var ftl = (function (exports) {
4927
4619
  * The component registered under the name on this element's registry, the
4928
4620
  * one every ful loader and mapper resolves through.
4929
4621
  * @param {string} name
4622
+ * @returns {any} undefined when none is registered
4930
4623
  */
4931
4624
  component(name) {
4932
4625
  return this.#bits().registry.component(name);
4933
4626
  }
4627
+ /**
4628
+ * One of the class's templates, bound to its registry's scope with the
4629
+ * class's `static config` overlaid as `config`.
4630
+ * @param {string} [name] 'default', the `static template`, when omitted
4631
+ * @returns {Template}
4632
+ * @throws when the class declares no template of that name
4633
+ */
4934
4634
  template(name) {
4935
4635
  const target = this.#bits().TEMPLATES[name ?? 'default'];
4936
4636
  if (!target) {
@@ -4950,15 +4650,9 @@ var ftl = (function (exports) {
4950
4650
  if (oldValue === newValue) {
4951
4651
  return;
4952
4652
  }
4953
- //the mute is the attribute being reflected, not the element: a component
4954
- //that legitimately changes another observed attribute while one reflects
4955
- //is a change like any other, and used to be swallowed
4956
4653
  if (this.#reflecting.has(attr)) {
4957
4654
  return;
4958
4655
  }
4959
- //the properties go live only once the render is done: before that, an
4960
- //attribute write lands in the observed snapshot and the base applies it
4961
- //with the rest of the declared state
4962
4656
  if (!this.#parsed) {
4963
4657
  if (this.#pending !== null && attr in this.#pending) {
4964
4658
  this.#pending[attr] = this.unmarshal(attr, newValue);
@@ -4968,14 +4662,18 @@ var ftl = (function (exports) {
4968
4662
  this[this.#bits().ATTR_TO_PROPERTY[attr]] = this.unmarshal(attr, newValue);
4969
4663
  }
4970
4664
  /**
4971
- * Upgrades once: reads the declared attributes, keeps the observed half open
4972
- * to writes made while the render is pending, applies them to the properties
4973
- * when the render returns, and only then lets an attribute write forward.
4665
+ * Upgrades once, however often the element is connected: reads the declared
4666
+ * attributes, keeps the observed ones open to writes made while the render is
4667
+ * pending, renders, then applies the observed values to their properties in
4668
+ * the order the registry composed them, a base class's before a subclass's.
4669
+ * Only then do attribute writes forward to properties, and the element gains
4670
+ * the `rendered` custom state. A render that throws leaves the forwarding
4671
+ * shut and the state unset.
4974
4672
  *
4975
4673
  * An observed attribute drives the property the registry's `propertyOf`
4976
4674
  * names, so a hyphenated attribute is authored with its dashes and read as
4977
- * a camelCase property. A single-word attribute is its own property name,
4978
- * which is what every observed attribute in the library is.
4675
+ * a camelCase property.
4676
+ * @returns {Promise<void>} rejecting with what the render threw
4979
4677
  */
4980
4678
  async upgrade() {
4981
4679
  if (this.#started) {
@@ -4983,9 +4681,6 @@ var ftl = (function (exports) {
4983
4681
  }
4984
4682
  this.#started = true;
4985
4683
  const slots = this.#bits().SLOTS ? LightSlots.from(this) : undefined;
4986
- //both tiers are read here: the observed ones forward to a property once
4987
- //the render is done, the rest are the element's configuration, read the
4988
- //one time and never again
4989
4684
  const declared = Object.fromEntries(
4990
4685
  this.#bits().DECLARED.map((attribute) => [
4991
4686
  attribute,
@@ -4997,21 +4692,11 @@ var ftl = (function (exports) {
4997
4692
  this.#pending = declared;
4998
4693
  try {
4999
4694
  await this.render({ slots });
5000
- //the declared state reaches the properties once the dom the setters
5001
- //drive exists, in the order the registry composed the declarations:
5002
- //a base class's attributes before the subclass's own
5003
4695
  const properties = this.#bits().ATTR_TO_PROPERTY;
5004
4696
  for (const name of this.#bits().OBSERVED) {
5005
4697
  this[properties[name]] = declared[name];
5006
4698
  }
5007
- //the properties go live once the render is done: from here on, an
5008
- //attribute write forwards to the property. A render that threw
5009
- //leaves it shut, so a later attribute write cannot reach setters
5010
- //that assume pieces the failed render never adopted
5011
4699
  this.#parsed = true;
5012
- //the same moment, said to css. :defined is true from the constructor,
5013
- //which is before the dom exists, so a guard written against it reveals
5014
- //an element that has nothing in it yet
5015
4700
  this.internals.states.add('rendered');
5016
4701
  } finally {
5017
4702
  this.#pending = null;
@@ -5022,8 +4707,9 @@ var ftl = (function (exports) {
5022
4707
  * drive, and the base applies the declared state onto the properties as
5023
4708
  * soon as it returns. A render needing a declared value while it builds
5024
4709
  * reads it through `declared(name)`. The slots are undefined for an element
5025
- * declaring no slots.
5026
- * @param {{ slots: any }} c
4710
+ * declaring no slots. It may be async; the upgrade awaits it.
4711
+ * @param {{ slots: Record<string, DocumentFragment> | undefined }} c
4712
+ * @returns {void | Promise<void>}
5027
4713
  */
5028
4714
  render(c) {}
5029
4715
  /**
@@ -5035,12 +4721,9 @@ var ftl = (function (exports) {
5035
4721
  * observed name answers the snapshot while the render is pending, kept
5036
4722
  * open to attribute writes landing in that window, and the live attribute
5037
4723
  * afterwards, the property being live by then.
5038
- *
5039
- * The snapshot exists rather than a read of the dom because an element may
5040
- * write its own observed attributes while it renders, whether a reflection
5041
- * or a value the platform normalizes on the way in, and what the author
5042
- * declared is what the base applies, not what the render left behind.
5043
4724
  * @param {string} name
4725
+ * @returns {any}
4726
+ * @throws when the class declares no such attribute
5044
4727
  */
5045
4728
  declared(name) {
5046
4729
  if (name in this.#frozen) {
@@ -5051,15 +4734,18 @@ var ftl = (function (exports) {
5051
4734
  }
5052
4735
  return this.unmarshal(name, this.getAttribute(name));
5053
4736
  }
5054
- /** Whether the element's render completed: the moment its properties went live. */
4737
+ /**
4738
+ * Whether the element's render completed: the moment its properties went
4739
+ * live, which css reads as `:state(rendered)`. Unlike `:defined`, which holds
4740
+ * from the constructor, it is false until the dom exists.
4741
+ * @returns {boolean}
4742
+ */
5055
4743
  get rendered() {
5056
4744
  return this.#parsed;
5057
4745
  }
5058
4746
  /**
5059
4747
  * Projects a property back onto its observed attribute, marshalled through
5060
- * the mapper the attribute was declared with: the one place a property
5061
- * reaches its attribute, so a setter never has to know how its own type
5062
- * serializes.
4748
+ * the mapper the attribute was declared with.
5063
4749
  *
5064
4750
  * A value the attribute already carries is not written at all, so reflecting
5065
4751
  * what an attribute write just delivered ends there rather than looping, and
@@ -5090,18 +4776,37 @@ var ftl = (function (exports) {
5090
4776
  */
5091
4777
 
5092
4778
  /**
5093
- * The receiver contract of the module functions: bound to a proxy over the
5094
- * data stack when called from a template, or to a plain object by Localization.of().
4779
+ * The receiver contract of the module functions: a plain object carrying the
4780
+ * translations and the locale, as Localization.of() binds them, or the proxy
4781
+ * over the data stack a template calls them with.
5095
4782
  *
5096
4783
  * @typedef {{ l10n?: Messages, locale?: string }} Receiver
5097
4784
  */
5098
4785
 
4786
+ /**
4787
+ * The translations or the locale of a receiver. From a template they are read
4788
+ * from the bound data alone (the registry's overlays, or the data handed to
4789
+ * the Template factory): the rows and responses a render pushes on top are
4790
+ * what arrives later, and must not supply or replace the page's own messages.
4791
+ * @param {any} receiver
4792
+ * @param {'l10n' | 'locale'} name
4793
+ * @returns {any}
4794
+ */
4795
+ const configured = (receiver, name) => {
4796
+ const bound = receiver[Expressions.BOUND];
4797
+ return typeof bound === 'function' ? bound(name) : receiver[name];
4798
+ };
4799
+
5099
4800
  const PLACEHOLDER = /\{(\w+)\}/g;
5100
4801
  const POSITIONAL = /\{(\d+)\}/g;
5101
4802
  /** @param {any} v @returns {v is Record<string, string>} */
5102
- const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
4803
+ const isPlainObject = (v) =>
4804
+ typeof v === 'object' && v !== null && [Object.prototype, null].includes(Object.getPrototypeOf(v));
4805
+
4806
+ /** @param {any} problem @returns {string|undefined} the reason, unless it is missing or blank */
4807
+ const reasonOf = (problem) =>
4808
+ typeof problem?.reason === 'string' && problem.reason.trim() !== '' ? problem.reason : undefined;
5103
4809
 
5104
- /** every l10n problem is reported once per session: a missing key in a row template must not flood the console */
5105
4810
  const warned = new Set();
5106
4811
  const warnOnce = (problem) => {
5107
4812
  if (!warned.has(problem)) {
@@ -5112,11 +4817,7 @@ var ftl = (function (exports) {
5112
4817
 
5113
4818
  const formatters = new BoundedCache(100);
5114
4819
  /**
5115
- * Intl construction is the expensive part of formatting (locale parsing, CLDR
5116
- * lookups), while formatting on a built instance is near free: a list rendering
5117
- * a size per row must not rebuild a NumberFormat per cell, so instances are
5118
- * memoized by constructor, locale and options.
5119
- *
4820
+ * An Intl instance, memoized by constructor, locale and options.
5120
4821
  * @param {{ new (locale: string | undefined, options: any): any }} ctor
5121
4822
  * @param {string | undefined} locale
5122
4823
  * @param {any} [options]
@@ -5130,35 +4831,40 @@ var ftl = (function (exports) {
5130
4831
  * The translations as a template module: `#l10n:t()` for messages, plus date,
5131
4832
  * number and bytes formatting in the page's locale. The template form and the
5132
4833
  * imperative `of()` facade both resolve through the registry's scope, so they
5133
- * always produce the same result for the same key.
4834
+ * always produce the same result for the same key. A template reads `l10n`
4835
+ * and `locale` from its bound data only, the most recent layer winning: an
4836
+ * overlay pushed while rendering (a row, a response) never reaches them.
5134
4837
  */
5135
4838
  class Localization {
5136
4839
  /**
5137
4840
  * Resolves a message from the translations and interpolates its arguments.
5138
- * A single plain object argument interpolates named placeholders
5139
- * ({name}); anything else interpolates positional ones ({0}).
4841
+ * A single plain object argument (an object literal, or one with a null
4842
+ * prototype) interpolates named placeholders ({name}); anything else interpolates positional ones ({0}).
5140
4843
  * A plural leaf selects its form through Intl.PluralRules over the
5141
4844
  * numeric {count} named argument.
5142
4845
  *
5143
4846
  * @param {string} key
5144
4847
  * @param {...any} args
4848
+ * Each problem (a missing key, placeholder or plural form) is warned once
4849
+ * per page.
4850
+ *
5145
4851
  * @this {Receiver}
5146
4852
  * @returns {string} the message, or the key itself when the translations do not carry it
5147
4853
  */
5148
4854
  static t(key, ...args) {
5149
- const messages = this.l10n ?? {};
5150
- let message = messages[key];
4855
+ const messages = configured(this, 'l10n') ?? {};
4856
+ let message = Object.hasOwn(messages, key) ? messages[key] : undefined;
5151
4857
  if (message === undefined) {
5152
4858
  warnOnce(`missing message "${key}"`);
5153
4859
  return key;
5154
4860
  }
4861
+ const named = args.length === 1 && isPlainObject(args[0]) ? args[0] : null;
5155
4862
  if (isPlainObject(message)) {
5156
- const named = args.length === 1 && isPlainObject(args[0]) ? args[0] : {};
5157
- if (typeof named.count !== 'number') {
4863
+ if (typeof named?.count !== 'number') {
5158
4864
  warnOnce(`plural message "${key}" needs a numeric {count}`);
5159
4865
  message = message.other;
5160
4866
  } else {
5161
- const locale = this.locale ?? navigator?.language ?? 'en';
4867
+ const locale = configured(this, 'locale') ?? navigator?.language ?? 'en';
5162
4868
  const category = formatter(Intl.PluralRules, locale).select(named.count);
5163
4869
  message = message[category] ?? message.other;
5164
4870
  }
@@ -5167,10 +4873,9 @@ var ftl = (function (exports) {
5167
4873
  warnOnce(`plural message "${key}" has no "other" form`);
5168
4874
  return key;
5169
4875
  }
5170
- if (args.length === 1 && isPlainObject(args[0])) {
5171
- const named = args[0];
4876
+ if (named) {
5172
4877
  return message.replace(PLACEHOLDER, (literal, name) => {
5173
- if (!(name in named)) {
4878
+ if (!Object.hasOwn(named, name)) {
5174
4879
  warnOnce(`message "${key}" wants {${name}}`);
5175
4880
  return literal;
5176
4881
  }
@@ -5196,9 +4901,10 @@ var ftl = (function (exports) {
5196
4901
  * @param {Date | number} value
5197
4902
  * @param {Intl.DateTimeFormatOptions} [options]
5198
4903
  * @this {Receiver}
4904
+ * @returns {string}
5199
4905
  */
5200
4906
  static date(value, options) {
5201
- return formatter(Intl.DateTimeFormat, this.locale, options).format(value);
4907
+ return formatter(Intl.DateTimeFormat, configured(this, 'locale'), options).format(value);
5202
4908
  }
5203
4909
 
5204
4910
  /**
@@ -5207,9 +4913,10 @@ var ftl = (function (exports) {
5207
4913
  * @param {number} value
5208
4914
  * @param {Intl.NumberFormatOptions} [options]
5209
4915
  * @this {Receiver}
4916
+ * @returns {string}
5210
4917
  */
5211
4918
  static number(value, options) {
5212
- return formatter(Intl.NumberFormat, this.locale, options).format(value);
4919
+ return formatter(Intl.NumberFormat, configured(this, 'locale'), options).format(value);
5213
4920
  }
5214
4921
 
5215
4922
  /**
@@ -5219,9 +4926,10 @@ var ftl = (function (exports) {
5219
4926
  *
5220
4927
  * @param {number} value
5221
4928
  * @this {Receiver}
4929
+ * @returns {string}
5222
4930
  */
5223
4931
  static bytes(value) {
5224
- const format = formatter(Intl.NumberFormat, this.locale, { maximumFractionDigits: 2 }).format;
4932
+ const format = formatter(Intl.NumberFormat, configured(this, 'locale'), { maximumFractionDigits: 2 }).format;
5225
4933
  if (value >= 1024 * 1024 * 1024) {
5226
4934
  return `${format(value / 1024 / 1024 / 1024)}GiB`;
5227
4935
  }
@@ -5234,16 +4942,37 @@ var ftl = (function (exports) {
5234
4942
  return `${format(value)}B`;
5235
4943
  }
5236
4944
 
4945
+ /**
4946
+ * What went wrong, as text for the reader. A value carrying a non-empty
4947
+ * `problems` array reads as their reasons, one per line, a blank or missing
4948
+ * reason reading as the `failure.no-reason` message. Any other value with a
4949
+ * `message` reads as that message; anything else as its string form, and
4950
+ * null or undefined as the empty string.
4951
+ *
4952
+ * @param {any} value what was thrown or rejected with
4953
+ * @this {Receiver}
4954
+ * @returns {string}
4955
+ */
4956
+ static failure(value) {
4957
+ if (Array.isArray(value?.problems) && value.problems.length > 0) {
4958
+ return value.problems.map((p) => reasonOf(p) ?? Localization.t.call(this, 'failure.no-reason')).join('\n');
4959
+ }
4960
+ if (value?.message !== undefined) {
4961
+ return String(value.message);
4962
+ }
4963
+ return value == null ? '' : String(value);
4964
+ }
4965
+
5237
4966
  /**
5238
4967
  * An imperative facade over the module functions, resolving the translations
5239
- * and the locale from the registry overlays on every call.
4968
+ * and the locale from the registry overlays on every call, the same lookup a
4969
+ * template makes: a facade taken at module scope, before the plugin
4970
+ * configures, still sees the translations.
5240
4971
  *
5241
4972
  * @param {{ locale?: string }} [overrides] an explicit locale, winning over the registry one
4973
+ * @returns {{ t: (key: string, ...args: any[]) => string, date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string, number: (value: number, options?: Intl.NumberFormatOptions) => string, bytes: (value: number) => string, failure: (value: any) => string }}
5242
4974
  */
5243
4975
  static of(overrides = {}) {
5244
- //resolved per call, not per facade: a module-scope `Localization.of()` is
5245
- //bound before the plugin configures. The registry's own evaluator does the
5246
- //lookup, so this cannot drift from what a template sees
5247
4976
  const resolve = (prop) => registry.evaluator().resolve(prop);
5248
4977
  /** @param {any} fn */
5249
4978
  const bind =
@@ -5255,6 +4984,7 @@ var ftl = (function (exports) {
5255
4984
  date: bind(Localization.date),
5256
4985
  number: bind(Localization.number),
5257
4986
  bytes: bind(Localization.bytes),
4987
+ failure: bind(Localization.failure),
5258
4988
  };
5259
4989
  }
5260
4990
  }