@rohal12/spindle 0.59.14 → 0.59.16

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.
@@ -124,6 +124,11 @@ var LINK_SEPARATORS = [
124
124
  ];
125
125
  var NO_SELECTORS = {};
126
126
  /**
127
+ * A CommonMark email autolink, `<` an address `>` (without braces, which
128
+ * are markup), as markdown reads one.
129
+ */
130
+ var EMAIL_AUTOLINK = /^<[a-zA-Z0-9.!#$%&'*+/=?^_`|~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*>$/;
131
+ /**
127
132
  * A link's display text and target, from its text `inner` (at `at` in the
128
133
  * input), with where the target is written.
129
134
  */
@@ -401,10 +406,11 @@ function peg$parse(input, options) {
401
406
  const peg$c10 = " ";
402
407
  const peg$c11 = "}";
403
408
  const peg$c12 = "/";
404
- const peg$c13 = "</";
405
- const peg$c14 = "=";
406
- const peg$c15 = "\"";
407
- const peg$c16 = "'";
409
+ const peg$c13 = "@";
410
+ const peg$c14 = "</";
411
+ const peg$c15 = "=";
412
+ const peg$c16 = "\"";
413
+ const peg$c17 = "'";
408
414
  const peg$r0 = /^[^\\{[<]/;
409
415
  const peg$r1 = /^[{}]/;
410
416
  const peg$r2 = /^[$_@%(!\/a-zA-Z]/;
@@ -415,14 +421,16 @@ function peg$parse(input, options) {
415
421
  const peg$r7 = /^[$_@%(!]/;
416
422
  const peg$r8 = /^[a-zA-Z]/;
417
423
  const peg$r9 = /^[^ \t\n\r\v\f\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF}]/;
418
- const peg$r10 = /^[\t-\r \/>\xA0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\uFEFF]/;
419
- const peg$r11 = /^[a-zA-Z0-9\-]/;
420
- const peg$r12 = /^[\t-\r "'\/<->{}\xA0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\uFEFF]/;
421
- const peg$r13 = /^[^"]/;
422
- const peg$r14 = /^[^']/;
423
- const peg$r15 = /^["']/;
424
- const peg$r16 = /^[^ \t\n\r\v\f\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF>]/;
425
- const peg$r17 = /^[ \t\n\r\v\f\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/;
424
+ const peg$r10 = /^[a-zA-Z0-9.!#$%&'*+\/=?\^_`|~\-]/;
425
+ const peg$r11 = /^[a-zA-Z0-9.\-]/;
426
+ const peg$r12 = /^[\t-\r \/>\xA0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\uFEFF]/;
427
+ const peg$r13 = /^[a-zA-Z0-9\-]/;
428
+ const peg$r14 = /^[\t-\r "'\/<->{}\xA0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\uFEFF]/;
429
+ const peg$r15 = /^[^"]/;
430
+ const peg$r16 = /^[^']/;
431
+ const peg$r17 = /^["']/;
432
+ const peg$r18 = /^[^ \t\n\r\v\f\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF>]/;
433
+ const peg$r19 = /^[ \t\n\r\v\f\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/;
426
434
  const peg$e0 = peg$anyExpectation();
427
435
  const peg$e1 = peg$classExpectation([
428
436
  "\\",
@@ -504,6 +512,37 @@ function peg$parse(input, options) {
504
512
  "}"
505
513
  ], true, false, false);
506
514
  const peg$e24 = peg$classExpectation([
515
+ ["a", "z"],
516
+ ["A", "Z"],
517
+ ["0", "9"],
518
+ ".",
519
+ "!",
520
+ "#",
521
+ "$",
522
+ "%",
523
+ "&",
524
+ "'",
525
+ "*",
526
+ "+",
527
+ "/",
528
+ "=",
529
+ "?",
530
+ "^",
531
+ "_",
532
+ "`",
533
+ "|",
534
+ "~",
535
+ "-"
536
+ ], false, false, false);
537
+ const peg$e25 = peg$literalExpectation("@", false);
538
+ const peg$e26 = peg$classExpectation([
539
+ ["a", "z"],
540
+ ["A", "Z"],
541
+ ["0", "9"],
542
+ ".",
543
+ "-"
544
+ ], false, false, false);
545
+ const peg$e27 = peg$classExpectation([
507
546
  [" ", "\r"],
508
547
  " ",
509
548
  "/",
@@ -517,14 +556,14 @@ function peg$parse(input, options) {
517
556
  " ",
518
557
  ""
519
558
  ], false, false, false);
520
- const peg$e25 = peg$literalExpectation("</", false);
521
- const peg$e26 = peg$classExpectation([
559
+ const peg$e28 = peg$literalExpectation("</", false);
560
+ const peg$e29 = peg$classExpectation([
522
561
  ["a", "z"],
523
562
  ["A", "Z"],
524
563
  ["0", "9"],
525
564
  "-"
526
565
  ], false, false, false);
527
- const peg$e27 = peg$classExpectation([
566
+ const peg$e30 = peg$classExpectation([
528
567
  [" ", "\r"],
529
568
  " ",
530
569
  "\"",
@@ -542,13 +581,13 @@ function peg$parse(input, options) {
542
581
  " ",
543
582
  ""
544
583
  ], false, false, false);
545
- const peg$e28 = peg$literalExpectation("=", false);
546
- const peg$e29 = peg$literalExpectation("\"", false);
547
- const peg$e30 = peg$classExpectation(["\""], true, false, false);
548
- const peg$e31 = peg$literalExpectation("'", false);
549
- const peg$e32 = peg$classExpectation(["'"], true, false, false);
550
- const peg$e33 = peg$classExpectation(["\"", "'"], false, false, false);
551
- const peg$e34 = peg$classExpectation([
584
+ const peg$e31 = peg$literalExpectation("=", false);
585
+ const peg$e32 = peg$literalExpectation("\"", false);
586
+ const peg$e33 = peg$classExpectation(["\""], true, false, false);
587
+ const peg$e34 = peg$literalExpectation("'", false);
588
+ const peg$e35 = peg$classExpectation(["'"], true, false, false);
589
+ const peg$e36 = peg$classExpectation(["\"", "'"], false, false, false);
590
+ const peg$e37 = peg$classExpectation([
552
591
  " ",
553
592
  " ",
554
593
  "\n",
@@ -566,7 +605,7 @@ function peg$parse(input, options) {
566
605
  "",
567
606
  ">"
568
607
  ], true, false, false);
569
- const peg$e35 = peg$classExpectation([
608
+ const peg$e38 = peg$classExpectation([
570
609
  " ",
571
610
  " ",
572
611
  "\n",
@@ -859,16 +898,22 @@ function peg$parse(input, options) {
859
898
  function peg$f67(s) {
860
899
  fail("A closing tag starts with a letter after " + LB + "/", location());
861
900
  }
862
- function peg$f68() {
901
+ function peg$f68(value) {
902
+ return EMAIL_AUTOLINK.test(value);
903
+ }
904
+ function peg$f69(value) {
905
+ return lexeme("text", location(), { value });
906
+ }
907
+ function peg$f70() {
863
908
  return !textMode;
864
909
  }
865
- function peg$f69(tag) {
910
+ function peg$f71(tag) {
866
911
  return VOID_TAGS.has(tag.lower);
867
912
  }
868
- function peg$f70(tag) {
913
+ function peg$f72(tag) {
869
914
  return null;
870
915
  }
871
- function peg$f71(tag, list, slash) {
916
+ function peg$f73(tag, list, slash) {
872
917
  const lower = tag.toLowerCase();
873
918
  return lexeme("html", location(), {
874
919
  tag,
@@ -878,7 +923,7 @@ function peg$parse(input, options) {
878
923
  isSelfClose: !!slash || VOID_TAGS.has(lower)
879
924
  });
880
925
  }
881
- function peg$f72(tag, bad) {
926
+ function peg$f74(tag, bad) {
882
927
  const c = input.charAt(bad);
883
928
  if (!c) fail("Unclosed tag <" + tag + ": no > ends it (write &lt; for a < that starts no tag)", location());
884
929
  const at = {
@@ -896,7 +941,7 @@ function peg$parse(input, options) {
896
941
  };
897
942
  fail("Unexpected " + JSON.stringify(c) + " in the tag <" + tag + ">: expected an attribute, or > to end the tag", at);
898
943
  }
899
- function peg$f73(tag) {
944
+ function peg$f75(tag) {
900
945
  return lexeme("html", location(), {
901
946
  tag,
902
947
  lower: tag.toLowerCase(),
@@ -905,44 +950,44 @@ function peg$parse(input, options) {
905
950
  isSelfClose: false
906
951
  });
907
952
  }
908
- function peg$f74(name) {
953
+ function peg$f76(name) {
909
954
  attrCode = hooks.isCodeAttribute(name);
910
955
  return true;
911
956
  }
912
- function peg$f75(name, v) {
957
+ function peg$f77(name, v) {
913
958
  return v;
914
959
  }
915
- function peg$f76(name, value) {
960
+ function peg$f78(name, value) {
916
961
  return [name, value ?? ""];
917
962
  }
918
- function peg$f77(v) {
963
+ function peg$f79(v) {
919
964
  return v;
920
965
  }
921
- function peg$f78(v) {
966
+ function peg$f80(v) {
922
967
  return v;
923
968
  }
924
- function peg$f79(q) {
969
+ function peg$f81(q) {
925
970
  fail("Unclosed attribute value: the " + q + " here has no closing " + q, location());
926
971
  }
927
- function peg$f80() {
972
+ function peg$f82() {
928
973
  return !attrCode;
929
974
  }
930
- function peg$f81(bs) {
975
+ function peg$f83(bs) {
931
976
  return bs.length % 2 === 1;
932
977
  }
933
- function peg$f82() {
978
+ function peg$f84() {
934
979
  return !attrCode;
935
980
  }
936
- function peg$f83(start, nameEnd) {
981
+ function peg$f85(start, nameEnd) {
937
982
  return skipTo(hooks.closeBrace(input, nameEnd, start));
938
983
  }
939
- function peg$f84(start, nameEnd) {
984
+ function peg$f86(start, nameEnd) {
940
985
  return skipEnd - offset();
941
986
  }
942
- function peg$f85() {
987
+ function peg$f87() {
943
988
  return !attrCode;
944
989
  }
945
- function peg$f86() {
990
+ function peg$f88() {
946
991
  return attrCode;
947
992
  }
948
993
  let peg$currPos = options.peg$currPos | 0;
@@ -1133,14 +1178,17 @@ function peg$parse(input, options) {
1133
1178
  if (s0 === peg$FAILED) {
1134
1179
  s0 = peg$parseLinkLexeme();
1135
1180
  if (s0 === peg$FAILED) {
1136
- s0 = peg$parseHtmlElement();
1181
+ s0 = peg$parseEmailAutolink();
1137
1182
  if (s0 === peg$FAILED) {
1138
- s0 = peg$parseVoidCloser();
1183
+ s0 = peg$parseHtmlElement();
1139
1184
  if (s0 === peg$FAILED) {
1140
- s0 = peg$parseValue();
1185
+ s0 = peg$parseVoidCloser();
1141
1186
  if (s0 === peg$FAILED) {
1142
- s0 = peg$parseMacroElement();
1143
- if (s0 === peg$FAILED) s0 = peg$parseLiteral();
1187
+ s0 = peg$parseValue();
1188
+ if (s0 === peg$FAILED) {
1189
+ s0 = peg$parseMacroElement();
1190
+ if (s0 === peg$FAILED) s0 = peg$parseLiteral();
1191
+ }
1144
1192
  }
1145
1193
  }
1146
1194
  }
@@ -1488,35 +1536,18 @@ function peg$parse(input, options) {
1488
1536
  if (s0 === peg$FAILED) {
1489
1537
  s0 = peg$parseLinkLexeme();
1490
1538
  if (s0 === peg$FAILED) {
1491
- s0 = peg$currPos;
1492
- peg$savedPos = peg$currPos;
1493
- s1 = peg$f29();
1494
- if (s1) s1 = void 0;
1495
- else s1 = peg$FAILED;
1496
- if (s1 !== peg$FAILED) {
1497
- s2 = peg$parseHtmlOpen();
1498
- if (s2 !== peg$FAILED) {
1499
- peg$savedPos = s0;
1500
- s0 = peg$f30(s2);
1501
- } else {
1502
- peg$currPos = s0;
1503
- s0 = peg$FAILED;
1504
- }
1505
- } else {
1506
- peg$currPos = s0;
1507
- s0 = peg$FAILED;
1508
- }
1539
+ s0 = peg$parseEmailAutolink();
1509
1540
  if (s0 === peg$FAILED) {
1510
1541
  s0 = peg$currPos;
1511
1542
  peg$savedPos = peg$currPos;
1512
- s1 = peg$f31();
1543
+ s1 = peg$f29();
1513
1544
  if (s1) s1 = void 0;
1514
1545
  else s1 = peg$FAILED;
1515
1546
  if (s1 !== peg$FAILED) {
1516
- s2 = peg$parseHtmlClose();
1547
+ s2 = peg$parseHtmlOpen();
1517
1548
  if (s2 !== peg$FAILED) {
1518
1549
  peg$savedPos = s0;
1519
- s0 = peg$f32(s2);
1550
+ s0 = peg$f30(s2);
1520
1551
  } else {
1521
1552
  peg$currPos = s0;
1522
1553
  s0 = peg$FAILED;
@@ -1526,37 +1557,57 @@ function peg$parse(input, options) {
1526
1557
  s0 = peg$FAILED;
1527
1558
  }
1528
1559
  if (s0 === peg$FAILED) {
1529
- s0 = peg$parseValue();
1560
+ s0 = peg$currPos;
1561
+ peg$savedPos = peg$currPos;
1562
+ s1 = peg$f31();
1563
+ if (s1) s1 = void 0;
1564
+ else s1 = peg$FAILED;
1565
+ if (s1 !== peg$FAILED) {
1566
+ s2 = peg$parseHtmlClose();
1567
+ if (s2 !== peg$FAILED) {
1568
+ peg$savedPos = s0;
1569
+ s0 = peg$f32(s2);
1570
+ } else {
1571
+ peg$currPos = s0;
1572
+ s0 = peg$FAILED;
1573
+ }
1574
+ } else {
1575
+ peg$currPos = s0;
1576
+ s0 = peg$FAILED;
1577
+ }
1530
1578
  if (s0 === peg$FAILED) {
1531
- s0 = peg$currPos;
1532
- s1 = peg$parseMacroTag();
1533
- if (s1 !== peg$FAILED) {
1534
- s2 = peg$currPos;
1535
- peg$savedPos = peg$currPos;
1536
- s3 = peg$f33(s1);
1537
- if (s3) s3 = void 0;
1538
- else s3 = peg$FAILED;
1539
- if (s3 !== peg$FAILED) {
1540
- s4 = peg$parseRawTokens();
1541
- if (s4 !== peg$FAILED) {
1542
- peg$savedPos = s2;
1543
- s2 = peg$f34(s1, s4);
1579
+ s0 = peg$parseValue();
1580
+ if (s0 === peg$FAILED) {
1581
+ s0 = peg$currPos;
1582
+ s1 = peg$parseMacroTag();
1583
+ if (s1 !== peg$FAILED) {
1584
+ s2 = peg$currPos;
1585
+ peg$savedPos = peg$currPos;
1586
+ s3 = peg$f33(s1);
1587
+ if (s3) s3 = void 0;
1588
+ else s3 = peg$FAILED;
1589
+ if (s3 !== peg$FAILED) {
1590
+ s4 = peg$parseRawTokens();
1591
+ if (s4 !== peg$FAILED) {
1592
+ peg$savedPos = s2;
1593
+ s2 = peg$f34(s1, s4);
1594
+ } else {
1595
+ peg$currPos = s2;
1596
+ s2 = peg$FAILED;
1597
+ }
1544
1598
  } else {
1545
1599
  peg$currPos = s2;
1546
1600
  s2 = peg$FAILED;
1547
1601
  }
1602
+ if (s2 === peg$FAILED) s2 = null;
1603
+ peg$savedPos = s0;
1604
+ s0 = peg$f35(s1, s2);
1548
1605
  } else {
1549
- peg$currPos = s2;
1550
- s2 = peg$FAILED;
1606
+ peg$currPos = s0;
1607
+ s0 = peg$FAILED;
1551
1608
  }
1552
- if (s2 === peg$FAILED) s2 = null;
1553
- peg$savedPos = s0;
1554
- s0 = peg$f35(s1, s2);
1555
- } else {
1556
- peg$currPos = s0;
1557
- s0 = peg$FAILED;
1609
+ if (s0 === peg$FAILED) s0 = peg$parseLiteral();
1558
1610
  }
1559
- if (s0 === peg$FAILED) s0 = peg$parseLiteral();
1560
1611
  }
1561
1612
  }
1562
1613
  }
@@ -2698,23 +2749,136 @@ function peg$parse(input, options) {
2698
2749
  else s0 = s1;
2699
2750
  return s0;
2700
2751
  }
2752
+ function peg$parseEmailAutolink() {
2753
+ let s0, s1, s2, s3, s4, s5, s6, s7;
2754
+ s0 = peg$currPos;
2755
+ s1 = peg$currPos;
2756
+ s2 = peg$currPos;
2757
+ if (input.charCodeAt(peg$currPos) === 60) {
2758
+ s3 = peg$c6;
2759
+ peg$currPos++;
2760
+ } else {
2761
+ s3 = peg$FAILED;
2762
+ if (peg$silentFails === 0) peg$fail(peg$e10);
2763
+ }
2764
+ if (s3 !== peg$FAILED) {
2765
+ s4 = [];
2766
+ s5 = input.charAt(peg$currPos);
2767
+ if (peg$r10.test(s5)) peg$currPos++;
2768
+ else {
2769
+ s5 = peg$FAILED;
2770
+ if (peg$silentFails === 0) peg$fail(peg$e24);
2771
+ }
2772
+ if (s5 !== peg$FAILED) while (s5 !== peg$FAILED) {
2773
+ s4.push(s5);
2774
+ s5 = input.charAt(peg$currPos);
2775
+ if (peg$r10.test(s5)) peg$currPos++;
2776
+ else {
2777
+ s5 = peg$FAILED;
2778
+ if (peg$silentFails === 0) peg$fail(peg$e24);
2779
+ }
2780
+ }
2781
+ else s4 = peg$FAILED;
2782
+ if (s4 !== peg$FAILED) {
2783
+ if (input.charCodeAt(peg$currPos) === 64) {
2784
+ s5 = peg$c13;
2785
+ peg$currPos++;
2786
+ } else {
2787
+ s5 = peg$FAILED;
2788
+ if (peg$silentFails === 0) peg$fail(peg$e25);
2789
+ }
2790
+ if (s5 !== peg$FAILED) {
2791
+ s6 = [];
2792
+ s7 = input.charAt(peg$currPos);
2793
+ if (peg$r11.test(s7)) peg$currPos++;
2794
+ else {
2795
+ s7 = peg$FAILED;
2796
+ if (peg$silentFails === 0) peg$fail(peg$e26);
2797
+ }
2798
+ if (s7 !== peg$FAILED) while (s7 !== peg$FAILED) {
2799
+ s6.push(s7);
2800
+ s7 = input.charAt(peg$currPos);
2801
+ if (peg$r11.test(s7)) peg$currPos++;
2802
+ else {
2803
+ s7 = peg$FAILED;
2804
+ if (peg$silentFails === 0) peg$fail(peg$e26);
2805
+ }
2806
+ }
2807
+ else s6 = peg$FAILED;
2808
+ if (s6 !== peg$FAILED) {
2809
+ if (input.charCodeAt(peg$currPos) === 62) {
2810
+ s7 = peg$c1;
2811
+ peg$currPos++;
2812
+ } else {
2813
+ s7 = peg$FAILED;
2814
+ if (peg$silentFails === 0) peg$fail(peg$e3);
2815
+ }
2816
+ if (s7 !== peg$FAILED) {
2817
+ s3 = [
2818
+ s3,
2819
+ s4,
2820
+ s5,
2821
+ s6,
2822
+ s7
2823
+ ];
2824
+ s2 = s3;
2825
+ } else {
2826
+ peg$currPos = s2;
2827
+ s2 = peg$FAILED;
2828
+ }
2829
+ } else {
2830
+ peg$currPos = s2;
2831
+ s2 = peg$FAILED;
2832
+ }
2833
+ } else {
2834
+ peg$currPos = s2;
2835
+ s2 = peg$FAILED;
2836
+ }
2837
+ } else {
2838
+ peg$currPos = s2;
2839
+ s2 = peg$FAILED;
2840
+ }
2841
+ } else {
2842
+ peg$currPos = s2;
2843
+ s2 = peg$FAILED;
2844
+ }
2845
+ if (s2 !== peg$FAILED) s1 = input.substring(s1, peg$currPos);
2846
+ else s1 = s2;
2847
+ if (s1 !== peg$FAILED) {
2848
+ peg$savedPos = peg$currPos;
2849
+ s2 = peg$f68(s1);
2850
+ if (s2) s2 = void 0;
2851
+ else s2 = peg$FAILED;
2852
+ if (s2 !== peg$FAILED) {
2853
+ peg$savedPos = s0;
2854
+ s0 = peg$f69(s1);
2855
+ } else {
2856
+ peg$currPos = s0;
2857
+ s0 = peg$FAILED;
2858
+ }
2859
+ } else {
2860
+ peg$currPos = s0;
2861
+ s0 = peg$FAILED;
2862
+ }
2863
+ return s0;
2864
+ }
2701
2865
  function peg$parseVoidCloser() {
2702
2866
  let s0, s1, s2, s3;
2703
2867
  s0 = peg$currPos;
2704
2868
  peg$savedPos = peg$currPos;
2705
- s1 = peg$f68();
2869
+ s1 = peg$f70();
2706
2870
  if (s1) s1 = void 0;
2707
2871
  else s1 = peg$FAILED;
2708
2872
  if (s1 !== peg$FAILED) {
2709
2873
  s2 = peg$parseHtmlClose();
2710
2874
  if (s2 !== peg$FAILED) {
2711
2875
  peg$savedPos = peg$currPos;
2712
- s3 = peg$f69(s2);
2876
+ s3 = peg$f71(s2);
2713
2877
  if (s3) s3 = void 0;
2714
2878
  else s3 = peg$FAILED;
2715
2879
  if (s3 !== peg$FAILED) {
2716
2880
  peg$savedPos = s0;
2717
- s0 = peg$f70(s2);
2881
+ s0 = peg$f72(s2);
2718
2882
  } else {
2719
2883
  peg$currPos = s0;
2720
2884
  s0 = peg$FAILED;
@@ -2766,7 +2930,7 @@ function peg$parse(input, options) {
2766
2930
  }
2767
2931
  if (s6 !== peg$FAILED) {
2768
2932
  peg$savedPos = s0;
2769
- s0 = peg$f71(s2, s3, s5);
2933
+ s0 = peg$f73(s2, s3, s5);
2770
2934
  } else {
2771
2935
  peg$currPos = s0;
2772
2936
  s0 = peg$FAILED;
@@ -2794,10 +2958,10 @@ function peg$parse(input, options) {
2794
2958
  s3 = peg$currPos;
2795
2959
  peg$silentFails++;
2796
2960
  s4 = input.charAt(peg$currPos);
2797
- if (peg$r10.test(s4)) peg$currPos++;
2961
+ if (peg$r12.test(s4)) peg$currPos++;
2798
2962
  else {
2799
2963
  s4 = peg$FAILED;
2800
- if (peg$silentFails === 0) peg$fail(peg$e24);
2964
+ if (peg$silentFails === 0) peg$fail(peg$e27);
2801
2965
  }
2802
2966
  if (s4 === peg$FAILED) {
2803
2967
  s4 = peg$currPos;
@@ -2831,7 +2995,7 @@ function peg$parse(input, options) {
2831
2995
  s5 = peg$parse_();
2832
2996
  s6 = peg$parseHere();
2833
2997
  peg$savedPos = s0;
2834
- s0 = peg$f72(s2, s6);
2998
+ s0 = peg$f74(s2, s6);
2835
2999
  } else {
2836
3000
  peg$currPos = s0;
2837
3001
  s0 = peg$FAILED;
@@ -2850,12 +3014,12 @@ function peg$parse(input, options) {
2850
3014
  function peg$parseHtmlClose() {
2851
3015
  let s0, s1, s2, s4;
2852
3016
  s0 = peg$currPos;
2853
- if (input.substr(peg$currPos, 2) === peg$c13) {
2854
- s1 = peg$c13;
3017
+ if (input.substr(peg$currPos, 2) === peg$c14) {
3018
+ s1 = peg$c14;
2855
3019
  peg$currPos += 2;
2856
3020
  } else {
2857
3021
  s1 = peg$FAILED;
2858
- if (peg$silentFails === 0) peg$fail(peg$e25);
3022
+ if (peg$silentFails === 0) peg$fail(peg$e28);
2859
3023
  }
2860
3024
  if (s1 !== peg$FAILED) {
2861
3025
  s2 = peg$parseTagName();
@@ -2870,7 +3034,7 @@ function peg$parse(input, options) {
2870
3034
  }
2871
3035
  if (s4 !== peg$FAILED) {
2872
3036
  peg$savedPos = s0;
2873
- s0 = peg$f73(s2);
3037
+ s0 = peg$f75(s2);
2874
3038
  } else {
2875
3039
  peg$currPos = s0;
2876
3040
  s0 = peg$FAILED;
@@ -2898,18 +3062,18 @@ function peg$parse(input, options) {
2898
3062
  if (s2 !== peg$FAILED) {
2899
3063
  s3 = [];
2900
3064
  s4 = input.charAt(peg$currPos);
2901
- if (peg$r11.test(s4)) peg$currPos++;
3065
+ if (peg$r13.test(s4)) peg$currPos++;
2902
3066
  else {
2903
3067
  s4 = peg$FAILED;
2904
- if (peg$silentFails === 0) peg$fail(peg$e26);
3068
+ if (peg$silentFails === 0) peg$fail(peg$e29);
2905
3069
  }
2906
3070
  while (s4 !== peg$FAILED) {
2907
3071
  s3.push(s4);
2908
3072
  s4 = input.charAt(peg$currPos);
2909
- if (peg$r11.test(s4)) peg$currPos++;
3073
+ if (peg$r13.test(s4)) peg$currPos++;
2910
3074
  else {
2911
3075
  s4 = peg$FAILED;
2912
- if (peg$silentFails === 0) peg$fail(peg$e26);
3076
+ if (peg$silentFails === 0) peg$fail(peg$e29);
2913
3077
  }
2914
3078
  }
2915
3079
  s2 = [s2, s3];
@@ -2932,10 +3096,10 @@ function peg$parse(input, options) {
2932
3096
  s5 = peg$currPos;
2933
3097
  peg$silentFails++;
2934
3098
  s6 = input.charAt(peg$currPos);
2935
- if (peg$r12.test(s6)) peg$currPos++;
3099
+ if (peg$r14.test(s6)) peg$currPos++;
2936
3100
  else {
2937
3101
  s6 = peg$FAILED;
2938
- if (peg$silentFails === 0) peg$fail(peg$e27);
3102
+ if (peg$silentFails === 0) peg$fail(peg$e30);
2939
3103
  }
2940
3104
  peg$silentFails--;
2941
3105
  if (s6 === peg$FAILED) s5 = void 0;
@@ -2968,10 +3132,10 @@ function peg$parse(input, options) {
2968
3132
  s5 = peg$currPos;
2969
3133
  peg$silentFails++;
2970
3134
  s6 = input.charAt(peg$currPos);
2971
- if (peg$r12.test(s6)) peg$currPos++;
3135
+ if (peg$r14.test(s6)) peg$currPos++;
2972
3136
  else {
2973
3137
  s6 = peg$FAILED;
2974
- if (peg$silentFails === 0) peg$fail(peg$e27);
3138
+ if (peg$silentFails === 0) peg$fail(peg$e30);
2975
3139
  }
2976
3140
  peg$silentFails--;
2977
3141
  if (s6 === peg$FAILED) s5 = void 0;
@@ -3004,25 +3168,25 @@ function peg$parse(input, options) {
3004
3168
  else s2 = s3;
3005
3169
  if (s2 !== peg$FAILED) {
3006
3170
  peg$savedPos = peg$currPos;
3007
- s3 = peg$f74(s2);
3171
+ s3 = peg$f76(s2);
3008
3172
  if (s3) s3 = void 0;
3009
3173
  else s3 = peg$FAILED;
3010
3174
  if (s3 !== peg$FAILED) {
3011
3175
  s4 = peg$currPos;
3012
3176
  s5 = peg$parse_();
3013
3177
  if (input.charCodeAt(peg$currPos) === 61) {
3014
- s6 = peg$c14;
3178
+ s6 = peg$c15;
3015
3179
  peg$currPos++;
3016
3180
  } else {
3017
3181
  s6 = peg$FAILED;
3018
- if (peg$silentFails === 0) peg$fail(peg$e28);
3182
+ if (peg$silentFails === 0) peg$fail(peg$e31);
3019
3183
  }
3020
3184
  if (s6 !== peg$FAILED) {
3021
3185
  peg$parse_();
3022
3186
  s8 = peg$parseAttrValue();
3023
3187
  if (s8 !== peg$FAILED) {
3024
3188
  peg$savedPos = s4;
3025
- s4 = peg$f75(s2, s8);
3189
+ s4 = peg$f77(s2, s8);
3026
3190
  } else {
3027
3191
  peg$currPos = s4;
3028
3192
  s4 = peg$FAILED;
@@ -3033,7 +3197,7 @@ function peg$parse(input, options) {
3033
3197
  }
3034
3198
  if (s4 === peg$FAILED) s4 = null;
3035
3199
  peg$savedPos = s0;
3036
- s0 = peg$f76(s2, s4);
3200
+ s0 = peg$f78(s2, s4);
3037
3201
  } else {
3038
3202
  peg$currPos = s0;
3039
3203
  s0 = peg$FAILED;
@@ -3048,11 +3212,11 @@ function peg$parse(input, options) {
3048
3212
  let s0, s1, s2, s3, s4;
3049
3213
  s0 = peg$currPos;
3050
3214
  if (input.charCodeAt(peg$currPos) === 34) {
3051
- s1 = peg$c15;
3215
+ s1 = peg$c16;
3052
3216
  peg$currPos++;
3053
3217
  } else {
3054
3218
  s1 = peg$FAILED;
3055
- if (peg$silentFails === 0) peg$fail(peg$e29);
3219
+ if (peg$silentFails === 0) peg$fail(peg$e32);
3056
3220
  }
3057
3221
  if (s1 !== peg$FAILED) {
3058
3222
  s2 = peg$currPos;
@@ -3060,10 +3224,10 @@ function peg$parse(input, options) {
3060
3224
  s4 = peg$parseValuePart();
3061
3225
  if (s4 === peg$FAILED) {
3062
3226
  s4 = input.charAt(peg$currPos);
3063
- if (peg$r13.test(s4)) peg$currPos++;
3227
+ if (peg$r15.test(s4)) peg$currPos++;
3064
3228
  else {
3065
3229
  s4 = peg$FAILED;
3066
- if (peg$silentFails === 0) peg$fail(peg$e30);
3230
+ if (peg$silentFails === 0) peg$fail(peg$e33);
3067
3231
  }
3068
3232
  }
3069
3233
  while (s4 !== peg$FAILED) {
@@ -3071,24 +3235,24 @@ function peg$parse(input, options) {
3071
3235
  s4 = peg$parseValuePart();
3072
3236
  if (s4 === peg$FAILED) {
3073
3237
  s4 = input.charAt(peg$currPos);
3074
- if (peg$r13.test(s4)) peg$currPos++;
3238
+ if (peg$r15.test(s4)) peg$currPos++;
3075
3239
  else {
3076
3240
  s4 = peg$FAILED;
3077
- if (peg$silentFails === 0) peg$fail(peg$e30);
3241
+ if (peg$silentFails === 0) peg$fail(peg$e33);
3078
3242
  }
3079
3243
  }
3080
3244
  }
3081
3245
  s2 = input.substring(s2, peg$currPos);
3082
3246
  if (input.charCodeAt(peg$currPos) === 34) {
3083
- s3 = peg$c15;
3247
+ s3 = peg$c16;
3084
3248
  peg$currPos++;
3085
3249
  } else {
3086
3250
  s3 = peg$FAILED;
3087
- if (peg$silentFails === 0) peg$fail(peg$e29);
3251
+ if (peg$silentFails === 0) peg$fail(peg$e32);
3088
3252
  }
3089
3253
  if (s3 !== peg$FAILED) {
3090
3254
  peg$savedPos = s0;
3091
- s0 = peg$f77(s2);
3255
+ s0 = peg$f79(s2);
3092
3256
  } else {
3093
3257
  peg$currPos = s0;
3094
3258
  s0 = peg$FAILED;
@@ -3100,11 +3264,11 @@ function peg$parse(input, options) {
3100
3264
  if (s0 === peg$FAILED) {
3101
3265
  s0 = peg$currPos;
3102
3266
  if (input.charCodeAt(peg$currPos) === 39) {
3103
- s1 = peg$c16;
3267
+ s1 = peg$c17;
3104
3268
  peg$currPos++;
3105
3269
  } else {
3106
3270
  s1 = peg$FAILED;
3107
- if (peg$silentFails === 0) peg$fail(peg$e31);
3271
+ if (peg$silentFails === 0) peg$fail(peg$e34);
3108
3272
  }
3109
3273
  if (s1 !== peg$FAILED) {
3110
3274
  s2 = peg$currPos;
@@ -3112,10 +3276,10 @@ function peg$parse(input, options) {
3112
3276
  s4 = peg$parseValuePart();
3113
3277
  if (s4 === peg$FAILED) {
3114
3278
  s4 = input.charAt(peg$currPos);
3115
- if (peg$r14.test(s4)) peg$currPos++;
3279
+ if (peg$r16.test(s4)) peg$currPos++;
3116
3280
  else {
3117
3281
  s4 = peg$FAILED;
3118
- if (peg$silentFails === 0) peg$fail(peg$e32);
3282
+ if (peg$silentFails === 0) peg$fail(peg$e35);
3119
3283
  }
3120
3284
  }
3121
3285
  while (s4 !== peg$FAILED) {
@@ -3123,24 +3287,24 @@ function peg$parse(input, options) {
3123
3287
  s4 = peg$parseValuePart();
3124
3288
  if (s4 === peg$FAILED) {
3125
3289
  s4 = input.charAt(peg$currPos);
3126
- if (peg$r14.test(s4)) peg$currPos++;
3290
+ if (peg$r16.test(s4)) peg$currPos++;
3127
3291
  else {
3128
3292
  s4 = peg$FAILED;
3129
- if (peg$silentFails === 0) peg$fail(peg$e32);
3293
+ if (peg$silentFails === 0) peg$fail(peg$e35);
3130
3294
  }
3131
3295
  }
3132
3296
  }
3133
3297
  s2 = input.substring(s2, peg$currPos);
3134
3298
  if (input.charCodeAt(peg$currPos) === 39) {
3135
- s3 = peg$c16;
3299
+ s3 = peg$c17;
3136
3300
  peg$currPos++;
3137
3301
  } else {
3138
3302
  s3 = peg$FAILED;
3139
- if (peg$silentFails === 0) peg$fail(peg$e31);
3303
+ if (peg$silentFails === 0) peg$fail(peg$e34);
3140
3304
  }
3141
3305
  if (s3 !== peg$FAILED) {
3142
3306
  peg$savedPos = s0;
3143
- s0 = peg$f78(s2);
3307
+ s0 = peg$f80(s2);
3144
3308
  } else {
3145
3309
  peg$currPos = s0;
3146
3310
  s0 = peg$FAILED;
@@ -3152,32 +3316,32 @@ function peg$parse(input, options) {
3152
3316
  if (s0 === peg$FAILED) {
3153
3317
  s0 = peg$currPos;
3154
3318
  s1 = input.charAt(peg$currPos);
3155
- if (peg$r15.test(s1)) peg$currPos++;
3319
+ if (peg$r17.test(s1)) peg$currPos++;
3156
3320
  else {
3157
3321
  s1 = peg$FAILED;
3158
- if (peg$silentFails === 0) peg$fail(peg$e33);
3322
+ if (peg$silentFails === 0) peg$fail(peg$e36);
3159
3323
  }
3160
3324
  if (s1 !== peg$FAILED) {
3161
3325
  peg$savedPos = s0;
3162
- s1 = peg$f79(s1);
3326
+ s1 = peg$f81(s1);
3163
3327
  }
3164
3328
  s0 = s1;
3165
3329
  if (s0 === peg$FAILED) {
3166
3330
  s0 = peg$currPos;
3167
3331
  s1 = [];
3168
3332
  s2 = input.charAt(peg$currPos);
3169
- if (peg$r16.test(s2)) peg$currPos++;
3333
+ if (peg$r18.test(s2)) peg$currPos++;
3170
3334
  else {
3171
3335
  s2 = peg$FAILED;
3172
- if (peg$silentFails === 0) peg$fail(peg$e34);
3336
+ if (peg$silentFails === 0) peg$fail(peg$e37);
3173
3337
  }
3174
3338
  while (s2 !== peg$FAILED) {
3175
3339
  s1.push(s2);
3176
3340
  s2 = input.charAt(peg$currPos);
3177
- if (peg$r16.test(s2)) peg$currPos++;
3341
+ if (peg$r18.test(s2)) peg$currPos++;
3178
3342
  else {
3179
3343
  s2 = peg$FAILED;
3180
- if (peg$silentFails === 0) peg$fail(peg$e34);
3344
+ if (peg$silentFails === 0) peg$fail(peg$e37);
3181
3345
  }
3182
3346
  }
3183
3347
  s0 = input.substring(s0, peg$currPos);
@@ -3190,7 +3354,7 @@ function peg$parse(input, options) {
3190
3354
  let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13;
3191
3355
  s0 = peg$currPos;
3192
3356
  peg$savedPos = peg$currPos;
3193
- s1 = peg$f80();
3357
+ s1 = peg$f82();
3194
3358
  if (s1) s1 = void 0;
3195
3359
  else s1 = peg$FAILED;
3196
3360
  if (s1 !== peg$FAILED) {
@@ -3219,7 +3383,7 @@ function peg$parse(input, options) {
3219
3383
  if (s2 !== peg$FAILED) {
3220
3384
  s3 = peg$currPos;
3221
3385
  peg$savedPos = peg$currPos;
3222
- s4 = peg$f81(s2);
3386
+ s4 = peg$f83(s2);
3223
3387
  if (s4) s4 = void 0;
3224
3388
  else s4 = peg$FAILED;
3225
3389
  if (s4 !== peg$FAILED) {
@@ -3258,7 +3422,7 @@ function peg$parse(input, options) {
3258
3422
  if (s0 === peg$FAILED) {
3259
3423
  s0 = peg$currPos;
3260
3424
  peg$savedPos = peg$currPos;
3261
- s1 = peg$f82();
3425
+ s1 = peg$f84();
3262
3426
  if (s1) s1 = void 0;
3263
3427
  else s1 = peg$FAILED;
3264
3428
  if (s1 !== peg$FAILED) {
@@ -3285,12 +3449,12 @@ function peg$parse(input, options) {
3285
3449
  if (s6 !== peg$FAILED) {
3286
3450
  s7 = peg$parseHere();
3287
3451
  peg$savedPos = peg$currPos;
3288
- s8 = peg$f83(s4, s7);
3452
+ s8 = peg$f85(s4, s7);
3289
3453
  if (s8) s8 = void 0;
3290
3454
  else s8 = peg$FAILED;
3291
3455
  if (s8 !== peg$FAILED) {
3292
3456
  s9 = peg$currPos;
3293
- s10 = peg$f84(s4, s7);
3457
+ s10 = peg$f86(s4, s7);
3294
3458
  s11 = peg$currPos;
3295
3459
  s12 = [];
3296
3460
  if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
@@ -3372,7 +3536,7 @@ function peg$parse(input, options) {
3372
3536
  if (s0 === peg$FAILED) {
3373
3537
  s0 = peg$currPos;
3374
3538
  peg$savedPos = peg$currPos;
3375
- s1 = peg$f85();
3539
+ s1 = peg$f87();
3376
3540
  if (s1) s1 = void 0;
3377
3541
  else s1 = peg$FAILED;
3378
3542
  if (s1 !== peg$FAILED) {
@@ -3463,7 +3627,7 @@ function peg$parse(input, options) {
3463
3627
  if (s0 === peg$FAILED) {
3464
3628
  s0 = peg$currPos;
3465
3629
  peg$savedPos = peg$currPos;
3466
- s1 = peg$f86();
3630
+ s1 = peg$f88();
3467
3631
  if (s1) s1 = void 0;
3468
3632
  else s1 = peg$FAILED;
3469
3633
  if (s1 !== peg$FAILED) {
@@ -3545,10 +3709,10 @@ function peg$parse(input, options) {
3545
3709
  function peg$parse_c() {
3546
3710
  let s0;
3547
3711
  s0 = input.charAt(peg$currPos);
3548
- if (peg$r17.test(s0)) peg$currPos++;
3712
+ if (peg$r19.test(s0)) peg$currPos++;
3549
3713
  else {
3550
3714
  s0 = peg$FAILED;
3551
- if (peg$silentFails === 0) peg$fail(peg$e35);
3715
+ if (peg$silentFails === 0) peg$fail(peg$e38);
3552
3716
  }
3553
3717
  return s0;
3554
3718
  }
@@ -10122,13 +10286,35 @@ function parseDelay(str) {
10122
10286
  return parseFloat(trimmed);
10123
10287
  }
10124
10288
  //#endregion
10289
+ //#region src/utils/listeners.ts
10290
+ /**
10291
+ * A set of listeners to call when something happens: `subscribe` adds one and
10292
+ * returns the function that removes it, `notify` calls each one. A listener
10293
+ * removed or added while they are called is called or not as the set was
10294
+ * when `notify` started.
10295
+ */
10296
+ function createListeners() {
10297
+ const listeners = /* @__PURE__ */ new Set();
10298
+ return {
10299
+ subscribe(listener) {
10300
+ listeners.add(listener);
10301
+ return () => {
10302
+ listeners.delete(listener);
10303
+ };
10304
+ },
10305
+ notify() {
10306
+ for (const listener of [...listeners]) listener();
10307
+ }
10308
+ };
10309
+ }
10310
+ //#endregion
10125
10311
  //#region src/runtime-errors.ts
10126
10312
  var errors = [];
10127
10313
  var nextId = 1;
10128
- var listeners$1 = /* @__PURE__ */ new Set();
10314
+ var listeners$1 = createListeners();
10129
10315
  function update(next) {
10130
10316
  errors = next;
10131
- for (const listener of listeners$1) listener();
10317
+ listeners$1.notify();
10132
10318
  }
10133
10319
  /**
10134
10320
  * Show `error` on the page, after `context` (what failed). The same error
@@ -10164,12 +10350,7 @@ function getRuntimeErrors() {
10164
10350
  return errors;
10165
10351
  }
10166
10352
  /** Call `listener` whenever the shown errors change; returns unsubscribe. */
10167
- function subscribeRuntimeErrors(listener) {
10168
- listeners$1.add(listener);
10169
- return () => {
10170
- listeners$1.delete(listener);
10171
- };
10172
- }
10353
+ var subscribeRuntimeErrors = listeners$1.subscribe;
10173
10354
  //#endregion
10174
10355
  //#region src/components/macros/arg-utils.ts
10175
10356
  /**
@@ -10890,7 +11071,7 @@ function getPRNGPull() {
10890
11071
  * (see src/tooling.ts).
10891
11072
  */
10892
11073
  var TRANSFORM_CACHE_MAX = 500;
10893
- var NAMESPACES$1 = {
11074
+ var NAMESPACES$2 = {
10894
11075
  $: "variables",
10895
11076
  _: "temporary",
10896
11077
  "@": "locals",
@@ -10923,7 +11104,7 @@ function transform(expr, goal = "expression") {
10923
11104
  result += expr.slice(at, ref.start);
10924
11105
  if (ref.shorthand) result += `${ref.name}: `;
10925
11106
  else if (IDENT_END_RE.test(result.slice(-2))) result += " ";
10926
- result += `${NAMESPACES$1[ref.sigil]}["${ref.name}"]`;
11107
+ result += `${NAMESPACES$2[ref.sigil]}["${ref.name}"]`;
10927
11108
  at = ref.end;
10928
11109
  }
10929
11110
  result += expr.slice(at);
@@ -11890,6 +12071,19 @@ var InlineContext = createContext(false);
11890
12071
  */
11891
12072
  var RawTextContext = createContext(false);
11892
12073
  /**
12074
+ * The view content is rendered in: '' for the story interface, and an id of
12075
+ * its own for each passage and dialog on display (see useViewScope). The
12076
+ * radiobuttons of a variable form a native group within their view only:
12077
+ * one group across views would let a dialog's checked button uncheck the
12078
+ * passage's when it mounts (#396).
12079
+ */
12080
+ var ViewScopeContext = createContext("");
12081
+ var viewScopes = 0;
12082
+ /** A new view scope (see ViewScopeContext), kept while mounted. */
12083
+ function useViewScope() {
12084
+ return useState(() => `v${++viewScopes}`)[0];
12085
+ }
12086
+ /**
11893
12087
  * True inside an `<svg>` element. SVG attributes are case-sensitive and have
11894
12088
  * no live form properties, so they are set as written (see splitAttributes).
11895
12089
  */
@@ -13323,11 +13517,60 @@ function extraKeys(collection) {
13323
13517
  const keys = Object.keys(collection);
13324
13518
  return Array.isArray(collection) ? keys.filter((k) => !isIndexKey(k)) : keys;
13325
13519
  }
13520
+ /** The entries of a Map, as the built-in iterator gives them. */
13521
+ function mapEntries(map) {
13522
+ return isDraft(map) ? map.entries() : Map.prototype.entries.call(map);
13523
+ }
13524
+ /** The members of a Set, as the built-in iterator gives them. */
13525
+ function setMembers(set) {
13526
+ return isDraft(set) ? set.values() : Set.prototype.values.call(set);
13527
+ }
13528
+ /** The number of entries of a Map or members of a Set. */
13529
+ function collectionSize(collection) {
13530
+ if (isDraft(collection)) return collection.size;
13531
+ const proto = collection instanceof Map ? Map.prototype : Set.prototype;
13532
+ return Reflect.get(proto, "size", collection);
13533
+ }
13534
+ /** The value a Map holds for `key`. */
13535
+ function mapGet(map, key) {
13536
+ return isDraft(map) ? map.get(key) : Map.prototype.get.call(map, key);
13537
+ }
13538
+ /** Make a Map hold `value` for `key`. */
13539
+ function mapSet(map, key, value) {
13540
+ if (isDraft(map)) map.set(key, value);
13541
+ else Map.prototype.set.call(map, key, value);
13542
+ }
13326
13543
  //#endregion
13327
13544
  //#region src/class-registry.ts
13328
13545
  var registry = /* @__PURE__ */ new Map();
13329
13546
  var ctorToName = /* @__PURE__ */ new Map();
13547
+ /**
13548
+ * Built-ins whose instances hold their value in internal slots that a save
13549
+ * cannot rebuild: a registered class cannot extend them. (Array, Map, Set,
13550
+ * Date, RegExp and Error can be extended, see classData.)
13551
+ */
13552
+ var UNSUPPORTED_BASES = [
13553
+ ArrayBuffer,
13554
+ DataView,
13555
+ Object.getPrototypeOf(Int8Array),
13556
+ URL,
13557
+ URLSearchParams,
13558
+ Number,
13559
+ String,
13560
+ Boolean,
13561
+ Promise,
13562
+ WeakMap,
13563
+ WeakSet,
13564
+ Function
13565
+ ];
13566
+ /**
13567
+ * Register `ctor` as `name`, so that its instances keep their class through
13568
+ * copies, history and saves. Throws for a class extending a built-in whose
13569
+ * instances cannot be saved (see UNSUPPORTED_BASES).
13570
+ */
13330
13571
  function registerClass(name, ctor) {
13572
+ const base = typeof ctor === "function" ? UNSUPPORTED_BASES.find((b) => ctor.prototype instanceof b) : void 0;
13573
+ if (base) throw new TypeError(`spindle: Cannot register class "${name}": it extends ${base.name}, whose instances cannot be saved (a registered class may extend Array, Map, Set, Date, RegExp or Error)`);
13331
13574
  registry.set(name, ctor);
13332
13575
  ctorToName.set(ctor, name);
13333
13576
  }
@@ -13394,16 +13637,22 @@ function ownData(value, keys = Object.keys(value)) {
13394
13637
  for (const key of keys) defineData(data, key, value[key]);
13395
13638
  return data;
13396
13639
  }
13397
- /** The kinds of collection a registered class may extend. */
13398
- var COLLECTION_KINDS = {
13640
+ /**
13641
+ * The built-ins a registered class may extend whose value is not in its
13642
+ * keys, saved as their kind (see classData).
13643
+ */
13644
+ var BUILTIN_KINDS = {
13399
13645
  Array,
13400
13646
  Map,
13401
- Set
13647
+ Set,
13648
+ Date,
13649
+ RegExp
13402
13650
  };
13403
13651
  /**
13404
13652
  * The saved form of a registered class instance: its own data (see
13405
- * ownData), or for a collection its kind, elements and extra keys, read
13406
- * with the built-in methods, which a subclass may override (#391).
13653
+ * ownData), or for a subclass of a built-in kind (see BUILTIN_KINDS) its
13654
+ * kind, contents and extra keys, read with the built-in methods, which a
13655
+ * subclass may override (#391, #393).
13407
13656
  */
13408
13657
  function classData(value) {
13409
13658
  const extra = () => ownData(value, extraKeys(value));
@@ -13418,12 +13667,26 @@ function classData(value) {
13418
13667
  }
13419
13668
  if (value instanceof Map) return [
13420
13669
  "Map",
13421
- Array.from(Map.prototype.entries.call(value)),
13670
+ Array.from(mapEntries(value)),
13422
13671
  extra()
13423
13672
  ];
13424
13673
  if (value instanceof Set) return [
13425
13674
  "Set",
13426
- Array.from(Set.prototype.values.call(value)),
13675
+ Array.from(setMembers(value)),
13676
+ extra()
13677
+ ];
13678
+ if (value instanceof Date) return [
13679
+ "Date",
13680
+ Date.prototype.getTime.call(value),
13681
+ extra()
13682
+ ];
13683
+ if (value instanceof RegExp) return [
13684
+ "RegExp",
13685
+ [
13686
+ Reflect.get(RegExp.prototype, "source", value),
13687
+ Reflect.get(RegExp.prototype, "flags", value),
13688
+ value.lastIndex
13689
+ ],
13427
13690
  extra()
13428
13691
  ];
13429
13692
  return ownData(value);
@@ -13459,22 +13722,44 @@ function reducers() {
13459
13722
  return out;
13460
13723
  }
13461
13724
  /**
13462
- * The collection of a registered class that a `[kind, contents, keys]`
13463
- * list holds (see classData). A cycle through the collection revives it
13464
- * before its list is complete (devalue holds unread parts as holes), so the
13465
- * collection is kept per list and filled with what the list holds so far.
13725
+ * The RegExp of a `[source, flags, lastIndex]` list (an `R` entry, or the
13726
+ * contents of a RegExp subclass), or undefined if the list is malformed.
13727
+ */
13728
+ function newRegExp(data) {
13729
+ if (!Array.isArray(data) || typeof data[0] !== "string" || typeof data[1] !== "string" || !Number.isSafeInteger(data[2])) return;
13730
+ const re = new RegExp(data[0], data[1]);
13731
+ re.lastIndex = data[2];
13732
+ return re;
13733
+ }
13734
+ /**
13735
+ * A new Date or RegExp holding `contents` (see classData), or undefined if
13736
+ * they are malformed.
13737
+ */
13738
+ function newAtomic(kind, contents) {
13739
+ if (kind === "RegExp") return newRegExp(contents);
13740
+ return typeof contents === "number" ? new Date(contents) : void 0;
13741
+ }
13742
+ /**
13743
+ * The built-in of a registered class that a `[kind, contents, keys]` list
13744
+ * holds (see classData). A cycle through a collection revives it before its
13745
+ * list is complete (devalue holds unread parts as holes), so the instance
13746
+ * is kept per list and filled with what the list holds so far. (The
13747
+ * contents of a Date or RegExp hold no objects, so they are complete.)
13466
13748
  */
13467
13749
  var collections = /* @__PURE__ */ new WeakMap();
13468
- function reviveCollection(name, ctor, list) {
13750
+ function reviveBuiltin(name, ctor, list) {
13469
13751
  const [kind, contents, keys] = list;
13470
- const base = typeof kind === "string" && hasOwn(COLLECTION_KINDS, kind) ? COLLECTION_KINDS[kind] : void 0;
13471
- if (list.length !== 3 || !base || !(ctor.prototype instanceof base) || hasOwn(list, 1) && !Array.isArray(contents) || hasOwn(list, 2) && !isPlainData(keys)) throw malformedClass(name);
13752
+ const base = typeof kind === "string" && hasOwn(BUILTIN_KINDS, kind) ? BUILTIN_KINDS[kind] : void 0;
13753
+ const atomic = kind === "Date" || kind === "RegExp";
13754
+ if (list.length !== 3 || !base || !(ctor.prototype instanceof base) || !atomic && hasOwn(list, 1) && !Array.isArray(contents) || hasOwn(list, 2) && !isPlainData(keys)) throw malformedClass(name);
13472
13755
  let made = collections.get(list);
13473
13756
  if (!made) {
13474
- made = Object.setPrototypeOf(new base(), ctor.prototype);
13757
+ const value = atomic ? newAtomic(kind, contents) : new base();
13758
+ if (!value) throw malformedClass(name);
13759
+ made = Object.setPrototypeOf(value, ctor.prototype);
13475
13760
  collections.set(list, made);
13476
13761
  }
13477
- if (Array.isArray(contents)) fillCollection(made, contents);
13762
+ if (!atomic && Array.isArray(contents)) fillCollection(made, contents);
13478
13763
  if (isPlainData(keys)) for (const key of Object.keys(keys)) defineData(made, key, keys[key]);
13479
13764
  return made;
13480
13765
  }
@@ -13498,13 +13783,13 @@ function fillCollection(made, contents) {
13498
13783
  /** Restore a registered class instance in place, so cycles through it hold. */
13499
13784
  function reviveClass(name) {
13500
13785
  return (data) => {
13501
- if (Array.isArray(data)) return reviveCollection(name, registeredCtor(name), data);
13786
+ if (Array.isArray(data)) return reviveBuiltin(name, registeredCtor(name), data);
13502
13787
  if (!isPlainData(data)) {
13503
13788
  if (isObject(data) && registeredClassName(data) === name) return data;
13504
13789
  throw malformedClass(name);
13505
13790
  }
13506
13791
  const ctor = registeredCtor(name);
13507
- if (Object.values(COLLECTION_KINDS).some((c) => ctor.prototype instanceof c)) throw malformedClass(name);
13792
+ if (Object.values(BUILTIN_KINDS).some((c) => ctor.prototype instanceof c)) throw malformedClass(name);
13508
13793
  Object.setPrototypeOf(data, ctor.prototype);
13509
13794
  if (data instanceof Error) hideErrorKeys(data);
13510
13795
  return data;
@@ -13566,9 +13851,8 @@ function reviverFor(key) {
13566
13851
  };
13567
13852
  if (key === KEYS_TAG) return reviveKeyed;
13568
13853
  if (key === REGEXP_TAG) return (data) => {
13569
- if (!Array.isArray(data) || typeof data[0] !== "string" || typeof data[1] !== "string" || !Number.isSafeInteger(data[2])) throw new TypeError("spindle: Malformed regular expression");
13570
- const re = new RegExp(data[0], data[1]);
13571
- re.lastIndex = data[2];
13854
+ const re = newRegExp(data);
13855
+ if (!re) throw new TypeError("spindle: Malformed regular expression");
13572
13856
  return re;
13573
13857
  };
13574
13858
  if (key.startsWith(CLASS_PREFIX)) return reviveClass(key.slice(2));
@@ -14170,10 +14454,12 @@ function shallowCopy(value) {
14170
14454
  * written in place.
14171
14455
  *
14172
14456
  * The path goes through own properties of plain objects, class instances
14173
- * and arrays (by index); anything else throws a TypeError rather than
14174
- * writing where no clone or save would see it: an inherited property such
14175
- * as a method counts as missing, and Map, Set, Date and RegExp values and
14176
- * other keys of arrays are refused, as is a "__proto__" segment.
14457
+ * and arrays (by index), and, outside a draft, any key of an instance of a
14458
+ * registered subclass of a built-in (#408); anything else throws a
14459
+ * TypeError rather than writing where no clone or save would see it: an
14460
+ * inherited property such as a method counts as missing, and Map, Set,
14461
+ * Date and RegExp values and other keys of arrays are refused, as is a
14462
+ * "__proto__" segment.
14177
14463
  */
14178
14464
  function setByPath(root, segments, value, options = {}) {
14179
14465
  const parent = walkToParent(root, segments, options.createMissing ?? false);
@@ -14208,8 +14494,15 @@ function deleteByPath(root, segments) {
14208
14494
  function checkSegments(segments) {
14209
14495
  if (segments.includes("__proto__")) throw new TypeError(`spindle: Cannot use "__proto__" in a variable path ("${segments.join(".")}")`);
14210
14496
  }
14211
- /** Throw unless `holder` can take `key` as story state (see setByPath). */
14212
- function checkHolder(holder, key, segments, depth) {
14497
+ /**
14498
+ * Throw unless `holder` can take `key` as story state (see setByPath). An
14499
+ * instance of a registered subclass of a built-in keeps its own fields
14500
+ * through clones and saves, so it takes any key (#408) when written in
14501
+ * place: a draft of one, or one a draft shares, is refused as other
14502
+ * built-ins are, and the caller writes the whole value instead.
14503
+ */
14504
+ function checkHolder(holder, key, segments, depth, inPlace) {
14505
+ if (inPlace && registeredClassName(holder) !== void 0) return;
14213
14506
  const kind = builtinName(holder) ?? (Array.isArray(holder) && !isArrayKey(key) ? "an array" : "");
14214
14507
  if (kind) throw new TypeError(`spindle: Cannot set property "${key}" on ${kind} (at "${segments.slice(0, depth).join(".")}")`);
14215
14508
  }
@@ -14225,7 +14518,7 @@ function walkToParent(root, segments, createMissing) {
14225
14518
  let current = root;
14226
14519
  for (let i = 0; i < segments.length - 1; i++) {
14227
14520
  const seg = segments[i];
14228
- checkHolder(current, seg, segments, i);
14521
+ checkHolder(current, seg, segments, i, !copyOnWrite);
14229
14522
  let next = hasOwn(current, seg) ? current[seg] : void 0;
14230
14523
  if (next == null || typeof next !== "object") {
14231
14524
  if (!createMissing) throw new TypeError(`spindle: Cannot set property "${segments[i + 1]}" on ${next === null ? "null" : typeof next} (at "${segments.slice(0, i + 1).join(".")}")`);
@@ -14237,7 +14530,7 @@ function walkToParent(root, segments, createMissing) {
14237
14530
  }
14238
14531
  current = next;
14239
14532
  }
14240
- checkHolder(current, segments[segments.length - 1], segments, segments.length - 1);
14533
+ checkHolder(current, segments[segments.length - 1], segments, segments.length - 1, !copyOnWrite);
14241
14534
  return current;
14242
14535
  }
14243
14536
  //#endregion
@@ -14248,13 +14541,13 @@ function isPlainObject(value) {
14248
14541
  return proto === Object.prototype || proto === null;
14249
14542
  }
14250
14543
  /**
14251
- * `empty`, a new collection (Array, Map or Set) standing for `obj`, made
14252
- * like it: an instance of a registered subclass keeps its class and its
14253
- * extra keys (#391), with the values `value` gives for them; other
14254
- * collections are plain. `made` gets it before its keys are filled, so
14544
+ * `empty`, a new built-in (an Array, Map, Set, Date or RegExp) standing for
14545
+ * `obj`, made like it: an instance of a registered subclass keeps its class
14546
+ * and its extra keys (#391, #393), with the values `value` gives for them;
14547
+ * other built-ins are plain. `made` gets it before its keys are filled, so
14255
14548
  * that cycles through it find it.
14256
14549
  */
14257
- function collectionLike(obj, empty, made, value) {
14550
+ function builtinLike(obj, empty, made, value) {
14258
14551
  made(empty);
14259
14552
  if (registeredClassName(obj) === void 0) return empty;
14260
14553
  Object.setPrototypeOf(empty, Object.getPrototypeOf(obj));
@@ -14287,11 +14580,11 @@ function deepClone(value, options = {}) {
14287
14580
  return copy;
14288
14581
  }
14289
14582
  function cloneBuiltin(val) {
14290
- if (val instanceof Date) return keep(val, new Date(val.getTime()));
14583
+ if (val instanceof Date) return builtin(val, new Date(Date.prototype.getTime.call(val)));
14291
14584
  if (val instanceof RegExp) {
14292
- const copy = new RegExp(val.source, val.flags);
14585
+ const copy = new RegExp(Reflect.get(RegExp.prototype, "source", val), Reflect.get(RegExp.prototype, "flags", val));
14293
14586
  copy.lastIndex = val.lastIndex;
14294
- return keep(val, copy);
14587
+ return builtin(val, copy);
14295
14588
  }
14296
14589
  if (val instanceof ArrayBuffer) return keep(val, val.slice(0));
14297
14590
  if (ArrayBuffer.isView(val)) {
@@ -14322,29 +14615,29 @@ function deepClone(value, options = {}) {
14322
14615
  const settle = options.settle;
14323
14616
  return settle && isObjectValue(copy) && isObjectValue(original) ? settle(copy, original) : copy;
14324
14617
  }
14325
- /** `empty`, made the copy of the collection `obj` (see collectionLike). */
14326
- const collection = (obj, empty) => collectionLike(obj, empty, (copy) => keep(obj, copy), (key) => clone(obj[key]));
14618
+ /** `empty`, made the copy of the built-in `obj` (see builtinLike). */
14619
+ const builtin = (obj, empty) => builtinLike(obj, empty, (copy) => keep(obj, copy), (key) => clone(obj[key]));
14327
14620
  function clone(val) {
14328
14621
  if (val === null || typeof val !== "object") return val;
14329
14622
  const obj = val;
14330
14623
  if (seen.has(obj)) return seen.get(obj);
14331
14624
  if (Array.isArray(val)) {
14332
- const arr = collection(obj, new Array(val.length));
14625
+ const arr = builtin(obj, new Array(val.length));
14333
14626
  for (let i = 0; i < val.length; i++) if (i in val) arr[i] = clone(val[i]);
14334
14627
  return arr;
14335
14628
  }
14336
14629
  if (val instanceof Map) {
14337
- const copy = collection(obj, /* @__PURE__ */ new Map());
14338
- for (const [k, v] of val) Map.prototype.set.call(copy, settled(clone(k), k), clone(v));
14630
+ const copy = builtin(obj, /* @__PURE__ */ new Map());
14631
+ for (const [k, v] of mapEntries(val)) Map.prototype.set.call(copy, settled(clone(k), k), clone(v));
14339
14632
  return copy;
14340
14633
  }
14341
14634
  if (val instanceof Set) {
14342
- const copy = collection(obj, /* @__PURE__ */ new Set());
14343
- for (const v of val) Set.prototype.add.call(copy, settled(clone(v), v));
14635
+ const copy = builtin(obj, /* @__PURE__ */ new Set());
14636
+ for (const v of setMembers(val)) Set.prototype.add.call(copy, settled(clone(v), v));
14344
14637
  return copy;
14345
14638
  }
14346
- const builtin = cloneBuiltin(obj);
14347
- if (builtin !== void 0) return builtin;
14639
+ const copied = cloneBuiltin(obj);
14640
+ if (copied !== void 0) return copied;
14348
14641
  const keepsProto = registeredClassName(obj) !== void 0 || isPlainObject(val);
14349
14642
  if (!keepsProto && options.keepUnregistered) return val;
14350
14643
  return copyKeys(obj, keep(obj, keepsProto ? Object.create(Object.getPrototypeOf(obj)) : {}));
@@ -14447,11 +14740,11 @@ function shareEqual(prev, curr) {
14447
14740
  const cr = c;
14448
14741
  /** What the result holds at `key` of `c`. */
14449
14742
  const shareKey = (key) => share(hasOwn(pr, key) ? pr[key] : void 0, cr[key]);
14450
- /** `empty`, made the result for the collection `c` (see collectionLike). */
14451
- const collection = (empty) => collectionLike(c, empty, (out) => done.set(c, out), shareKey);
14743
+ /** `empty`, made the result for the collection `c` (see builtinLike). */
14744
+ const collection = (empty) => builtinLike(c, empty, (out) => done.set(c, out), shareKey);
14452
14745
  if (c instanceof Map && p instanceof Map) {
14453
14746
  const out = collection(/* @__PURE__ */ new Map());
14454
- for (const [k, v] of c) Map.prototype.set.call(out, fresh(k), share(p.get(k), v));
14747
+ for (const [k, v] of mapEntries(c)) Map.prototype.set.call(out, fresh(k), share(mapGet(p, k), v));
14455
14748
  return out;
14456
14749
  }
14457
14750
  if (Array.isArray(c) && Array.isArray(p)) {
@@ -14479,8 +14772,14 @@ function equalBytes(a, b) {
14479
14772
  }
14480
14773
  /** Equality of the content of atomic built-ins (see isAtomic) but Map/Set. */
14481
14774
  function equalBuiltin(a, b, assumed) {
14482
- if (a instanceof Date) return Object.is(a.getTime(), b.getTime());
14483
- if (a instanceof RegExp) return String(a) === String(b) && a.lastIndex === b.lastIndex;
14775
+ if (a instanceof Date) {
14776
+ const time = Date.prototype.getTime;
14777
+ return Object.is(time.call(a), time.call(b)) && equalExtraKeys(a, b, assumed);
14778
+ }
14779
+ if (a instanceof RegExp) {
14780
+ const text = RegExp.prototype.toString;
14781
+ return text.call(a) === text.call(b) && a.lastIndex === b.lastIndex && equalExtraKeys(a, b, assumed);
14782
+ }
14484
14783
  if (ArrayBuffer.isView(a)) {
14485
14784
  const v = b;
14486
14785
  return a.byteOffset === v.byteOffset && a.byteLength === v.byteLength && equal(a.buffer, v.buffer, assumed);
@@ -14499,18 +14798,20 @@ function equalKeys(a, b, assumed, keysOf = Object.keys) {
14499
14798
  for (const key of keys) if (!hasOwn(bo, key) || !equal(ao[key], bo[key], assumed)) return false;
14500
14799
  return true;
14501
14800
  }
14502
- /** Built-in collection prototypes, whose instances hold no extra keys. */
14503
- var COLLECTION_PROTOS = new Set([
14801
+ /** Built-in prototypes (see builtinLike), whose instances hold no extra keys. */
14802
+ var BUILTIN_PROTOS = new Set([
14504
14803
  Array.prototype,
14505
14804
  Map.prototype,
14506
- Set.prototype
14805
+ Set.prototype,
14806
+ Date.prototype,
14807
+ RegExp.prototype
14507
14808
  ]);
14508
14809
  /**
14509
- * Equality of the extra keys (see extraKeys) of two collections of one
14810
+ * Equality of the extra keys (see extraKeys) of two built-ins of one
14510
14811
  * class: those of a subclass instance are part of its value.
14511
14812
  */
14512
14813
  function equalExtraKeys(a, b, assumed) {
14513
- return COLLECTION_PROTOS.has(Object.getPrototypeOf(a)) || equalKeys(a, b, assumed, extraKeys);
14814
+ return BUILTIN_PROTOS.has(Object.getPrototypeOf(a)) || equalKeys(a, b, assumed, extraKeys);
14514
14815
  }
14515
14816
  /** `assumed` tracks the pairs of objects met (see Pairs). */
14516
14817
  function equal(a, b, assumed) {
@@ -14521,9 +14822,9 @@ function equal(a, b, assumed) {
14521
14822
  if (met !== void 0) return met;
14522
14823
  if (a instanceof Map || a instanceof Set) {
14523
14824
  const bc = b;
14524
- if (a.size !== bc.size) return false;
14525
- const ai = a.entries();
14526
- const bi = bc.entries();
14825
+ if (collectionSize(a) !== collectionSize(bc)) return false;
14826
+ const ai = a instanceof Map ? mapEntries(a) : setMembers(a);
14827
+ const bi = bc instanceof Map ? mapEntries(bc) : setMembers(bc);
14527
14828
  for (let x = ai.next(), y = bi.next(); !x.done; x = ai.next(), y = bi.next()) if (!equal(x.value, y.value, assumed)) return false;
14528
14829
  return equalExtraKeys(a, b, assumed);
14529
14830
  }
@@ -14703,13 +15004,13 @@ function entryChildren(value) {
14703
15004
  const children = [];
14704
15005
  if (value instanceof Map) {
14705
15006
  let index = 0;
14706
- for (const [k, v] of value) {
15007
+ for (const [k, v] of mapEntries(value)) {
14707
15008
  children.push([entryKey("k", index), k], [entryKey("v", index), v]);
14708
15009
  index++;
14709
15010
  }
14710
15011
  } else if (value instanceof Set) {
14711
15012
  let index = 0;
14712
- for (const member of value) children.push([entryKey("s", index++), member]);
15013
+ for (const member of setMembers(value)) children.push([entryKey("s", index++), member]);
14713
15014
  } else if (value instanceof Error) {
14714
15015
  for (const key of ["cause", "errors"]) if (hasOwn(value, key)) children.push([entryKey(key === "cause" ? "c" : "e", 0), value[key]]);
14715
15016
  } else if (ArrayBuffer.isView(value)) children.push([entryKey("b", 0), value.buffer]);
@@ -14741,8 +15042,9 @@ function setEntryValue(root, path, value) {
14741
15042
  const holder = getByEntryPath(root, path.slice(0, -1));
14742
15043
  const match = /^\0v(\d+)$/.exec(path[path.length - 1]);
14743
15044
  if (!(holder instanceof Map) || !match) return false;
14744
- const key = [...holder.keys()][Number(match[1])];
14745
- holder.set(key, value);
15045
+ const entry = [...mapEntries(holder)][Number(match[1])];
15046
+ if (!entry) return false;
15047
+ mapSet(holder, entry[0], value);
14746
15048
  return true;
14747
15049
  }
14748
15050
  /**
@@ -14811,7 +15113,8 @@ function aliasMoved(x, y, at, earlier, later) {
14811
15113
  if (xs && ys) {
14812
15114
  if (Object.getPrototypeOf(x) !== Object.getPrototypeOf(y)) return false;
14813
15115
  if (x instanceof Map || x instanceof Set) {
14814
- if (x.size !== y.size) return false;
15116
+ const ySize = collectionSize(y);
15117
+ if (collectionSize(x) !== ySize) return false;
14815
15118
  }
14816
15119
  const held = new Map(ys);
14817
15120
  for (const [segment, a] of xs) if (held.has(segment)) pairs.push([
@@ -15382,11 +15685,13 @@ async function slotRecord(ifid, slot) {
15382
15685
  async function fillSlot(ifid, slot, saveId) {
15383
15686
  await (await getBackend()).setMeta(slotMetaKey(ifid, slot), saveId);
15384
15687
  if (isNamedSlot(slot)) await updateSlotIndex(ifid, (slots) => slots.includes(slot) ? slots : [...slots, slot]);
15688
+ announceSlots(ifid);
15385
15689
  }
15386
15690
  /** Clear a slot: its pointer, and its entry in the slot index. */
15387
15691
  async function emptySlot(ifid, slot) {
15388
15692
  await (await getBackend()).deleteMeta(slotMetaKey(ifid, slot));
15389
15693
  if (isNamedSlot(slot)) await updateSlotIndex(ifid, (slots) => slots.filter((s) => s !== slot));
15694
+ announceSlots(ifid);
15390
15695
  }
15391
15696
  /** Clear every slot holding a save whose ID passes `test`. */
15392
15697
  async function emptySlotsHolding(ifid, test) {
@@ -15395,6 +15700,46 @@ async function emptySlotsHolding(ifid, test) {
15395
15700
  if (id && test(id)) await emptySlot(ifid, slot);
15396
15701
  }
15397
15702
  }
15703
+ var SLOTS_CHANNEL = "spindle.slots";
15704
+ /** Every story's slots: what clearing all data announces. */
15705
+ var ALL_STORIES = "*";
15706
+ var slotsChannel;
15707
+ function getSlotsChannel() {
15708
+ if (slotsChannel === void 0) try {
15709
+ slotsChannel = new BroadcastChannel(SLOTS_CHANNEL);
15710
+ slotsChannel.unref?.();
15711
+ } catch {
15712
+ slotsChannel = null;
15713
+ }
15714
+ return slotsChannel;
15715
+ }
15716
+ /** Tell the story's other tabs that its slots changed. */
15717
+ function announceSlots(ifid) {
15718
+ try {
15719
+ getSlotsChannel()?.postMessage(ifid);
15720
+ } catch {}
15721
+ }
15722
+ /**
15723
+ * Call `onChange` when another tab may have filled or emptied the story's
15724
+ * slots: when it says so, and when this tab is shown again. Returns the
15725
+ * function that stops it.
15726
+ */
15727
+ function watchSlotChanges(ifid, onChange) {
15728
+ const channel = getSlotsChannel();
15729
+ const onMessage = (event) => {
15730
+ if (event.data === ifid || event.data === ALL_STORIES) onChange();
15731
+ };
15732
+ const onShown = () => {
15733
+ if (document.visibilityState === "visible") onChange();
15734
+ };
15735
+ const doc = typeof document === "undefined" ? void 0 : document;
15736
+ channel?.addEventListener("message", onMessage);
15737
+ doc?.addEventListener("visibilitychange", onShown);
15738
+ return () => {
15739
+ channel?.removeEventListener("message", onMessage);
15740
+ doc?.removeEventListener("visibilitychange", onShown);
15741
+ };
15742
+ }
15398
15743
  /** The `custom` keys that say which slot a save lives in. */
15399
15744
  var slotCustom = (slot) => ({
15400
15745
  isAutosave: !isNamedSlot(slot),
@@ -15648,6 +15993,7 @@ function clearGameData(ifid) {
15648
15993
  await backend.deletePlaythroughsByIfid(ifid);
15649
15994
  await backend.deleteMetaByIfid(ifid);
15650
15995
  clearSession(ifid);
15996
+ announceSlots(ifid);
15651
15997
  });
15652
15998
  }
15653
15999
  var clearAllData = queued(clearAllDataNow);
@@ -15662,6 +16008,7 @@ async function clearAllDataNow() {
15662
16008
  }
15663
16009
  for (const key of toRemove) sessionStorage.removeItem(key);
15664
16010
  } catch {}
16011
+ announceSlots(ALL_STORIES);
15665
16012
  }
15666
16013
  /**
15667
16014
  * Delete a playthrough with its saves, emptying the slots that held them.
@@ -15746,7 +16093,7 @@ function initializeStory(session) {
15746
16093
  }
15747
16094
  //#endregion
15748
16095
  //#region src/execute-mutation.ts
15749
- var NAMESPACES = [
16096
+ var NAMESPACES$1 = [
15750
16097
  "variables",
15751
16098
  "temporary",
15752
16099
  "transient"
@@ -15827,9 +16174,9 @@ function deeplyFrozen(value, seen = /* @__PURE__ */ new Set()) {
15827
16174
  if (!Object.isFrozen(value)) return false;
15828
16175
  seen.add(value);
15829
16176
  if (value instanceof Map) {
15830
- for (const [key, entry] of value) if (!deeplyFrozen(key, seen) || !deeplyFrozen(entry, seen)) return false;
16177
+ for (const [key, entry] of mapEntries(value)) if (!deeplyFrozen(key, seen) || !deeplyFrozen(entry, seen)) return false;
15831
16178
  } else if (value instanceof Set) {
15832
- for (const member of value) if (!deeplyFrozen(member, seen)) return false;
16179
+ for (const member of setMembers(value)) if (!deeplyFrozen(member, seen)) return false;
15833
16180
  }
15834
16181
  return Reflect.ownKeys(value).every((key) => {
15835
16182
  const desc = Object.getOwnPropertyDescriptor(value, key);
@@ -15869,7 +16216,7 @@ function mirror(draft, change, scopes = activeScopes, cloner = clonerOf) {
15869
16216
  }
15870
16217
  /** A cloner of a copy of its own (no references shared with others). */
15871
16218
  var clonerOf = (target) => clonerFor(target);
15872
- var NAMESPACE_KEYS$1 = new Set(NAMESPACES);
16219
+ var NAMESPACE_KEYS$1 = new Set(NAMESPACES$1);
15873
16220
  /**
15874
16221
  * Runs store updates on the running code's working copies. They stay the
15875
16222
  * code's own, so nothing may freeze them.
@@ -15964,7 +16311,7 @@ function routeStoreUpdate(recipe) {
15964
16311
  transient: inner.work.transient
15965
16312
  };
15966
16313
  const [next, patches] = scratch.produceWithPatches(view, recipe);
15967
- const changes = NAMESPACES.flatMap((ns) => {
16314
+ const changes = NAMESPACES$1.flatMap((ns) => {
15968
16315
  const own = patches.filter((p) => p.path[0] === ns);
15969
16316
  return own.length ? writtenPaths(view[ns], next[ns], own).map((change) => ({
15970
16317
  ...change,
@@ -16375,11 +16722,28 @@ function connectTriggersToStore() {
16375
16722
  function reinitTriggerState() {
16376
16723
  for (const trigger of triggers) trigger.lastResult = evalCondition(trigger.condition);
16377
16724
  }
16725
+ /**
16726
+ * Forget every watcher and close every dialog on display, whoever opened it
16727
+ * (restart): a `{dialog}` or menubar dialog of the story interface, which
16728
+ * stays mounted, would otherwise stay open over the new game (#397).
16729
+ */
16378
16730
  function resetTriggers() {
16379
16731
  triggers = [];
16380
- dialogQueue = [];
16381
- dialogHostCallbacks?.closeAll();
16732
+ resets++;
16733
+ closeAllOpenDialogs();
16734
+ resetListeners.notify();
16382
16735
  }
16736
+ var resets = 0;
16737
+ var resetListeners = createListeners();
16738
+ /** How many times resetTriggers has forgotten every watcher. */
16739
+ function triggerResets() {
16740
+ return resets;
16741
+ }
16742
+ /**
16743
+ * Call `listener` after each resetTriggers (a restart): a {watch} that stays
16744
+ * mounted, in the story interface, registers its watcher again (#402).
16745
+ */
16746
+ var subscribeTriggerResets = resetListeners.subscribe;
16383
16747
  function subscribeTriggerDialogs(cb) {
16384
16748
  dialogNotify = cb;
16385
16749
  if (dialogQueue.length > 0) cb();
@@ -16690,6 +17054,15 @@ function startVariables(defaults) {
16690
17054
  return initialVars;
16691
17055
  }
16692
17056
  /**
17057
+ * Fresh transient variables from their declared defaults (init, restart,
17058
+ * load). Transients are never saved, so an instance of an unregistered
17059
+ * class (an engine, a library object) is kept as it is, as mutation code
17060
+ * keeps it (#321): a copy would lose its class and methods (#398).
17061
+ */
17062
+ function freshTransients(defaults) {
17063
+ return createNamespace(deepClone(defaults, { keepUnregistered: true }));
17064
+ }
17065
+ /**
16693
17066
  * Enter the start passage with `variables`, as the only history moment
16694
17067
  * (init, restart). Call it inside a store update.
16695
17068
  */
@@ -16697,7 +17070,7 @@ function enterStart(state, passage, variables, transientDefaults) {
16697
17070
  state.currentPassage = passage;
16698
17071
  state.navigationId++;
16699
17072
  state.variables = variables;
16700
- state.transient = createNamespace(deepClone(transientDefaults));
17073
+ state.transient = freshTransients(transientDefaults);
16701
17074
  state.temporary = createNamespace();
16702
17075
  state.history = [{
16703
17076
  passage,
@@ -16858,8 +17231,21 @@ var latestStateApplied = 0;
16858
17231
  var slotLoadApplying = null;
16859
17232
  /** A replacement of the game state applied at its call. */
16860
17233
  function replaceStateNow() {
16861
- latestStateApplied = ++stateReplacementsIssued;
17234
+ applyReplacement(++stateReplacementsIssued);
16862
17235
  }
17236
+ var replacementListeners = createListeners();
17237
+ /** Record that replacement `n` is applied, and tell the listeners. */
17238
+ function applyReplacement(n) {
17239
+ latestStateApplied = n;
17240
+ replacementListeners.notify();
17241
+ }
17242
+ /**
17243
+ * Call `listener` whenever a boot, restart or load replaces the game state:
17244
+ * work the replaced game started that would write into the new one later (a
17245
+ * click body's timer in the story interface, which stays mounted) is to
17246
+ * stop (#401).
17247
+ */
17248
+ var subscribeStateReplacement = replacementListeners.subscribe;
16863
17249
  var runtimeUnsubs = [];
16864
17250
  var inRuntimePhase = false;
16865
17251
  /**
@@ -17091,6 +17477,16 @@ function recordKnownSave(set, slot, known) {
17091
17477
  }
17092
17478
  });
17093
17479
  }
17480
+ /** Look up which slots hold a save again, as an operation queued now. */
17481
+ function refreshKnownSaves(set, ifid) {
17482
+ return populateKnownSaves(ifid).then((known) => {
17483
+ set((state) => {
17484
+ state.knownSaves = known;
17485
+ });
17486
+ });
17487
+ }
17488
+ /** Stops following the slot changes of other tabs (see init). */
17489
+ var stopWatchingSlots;
17094
17490
  /**
17095
17491
  * Queue the clearing of saved data, then restart now: the new playthrough
17096
17492
  * is stored after it, and operations issued from here on belong to the new
@@ -17172,6 +17568,10 @@ var useStoryStore = create()(storyStore((set, get) => ({
17172
17568
  console.error("spindle: failed to init save system", err);
17173
17569
  return "";
17174
17570
  }));
17571
+ stopWatchingSlots?.();
17572
+ stopWatchingSlots = watchSlotChanges(storyData.ifid, () => {
17573
+ refreshKnownSaves(set, storyData.ifid).catch(() => {});
17574
+ });
17175
17575
  },
17176
17576
  navigate: (passageName) => {
17177
17577
  if (navigationTriggerPhase) {
@@ -17391,12 +17791,7 @@ var useStoryStore = create()(storyStore((set, get) => ({
17391
17791
  });
17392
17792
  if (playthroughId !== "" && playthroughId === current) switchToPlaythrough(replacementId, deletion);
17393
17793
  else if (current === "") switchToLookedUpPlaythrough(deletion.then((replaced) => replaced ? replacementId : established, () => established));
17394
- return reported(deletion.then(async () => {
17395
- const known = await populateKnownSaves(storyData.ifid);
17396
- set((state) => {
17397
- state.knownSaves = known;
17398
- });
17399
- }), "spindle: failed to delete playthrough");
17794
+ return reported(deletion.then(() => refreshKnownSaves(set, storyData.ifid)), "spindle: failed to delete playthrough");
17400
17795
  },
17401
17796
  getSavePayload: () => {
17402
17797
  const { currentPassage, variables, history, historyIndex, visitCounts, renderCounts } = get();
@@ -17433,7 +17828,7 @@ var useStoryStore = create()(storyStore((set, get) => ({
17433
17828
  return;
17434
17829
  }
17435
17830
  assertPassagesExist(get().storyData, payload);
17436
- latestStateApplied = replacement;
17831
+ applyReplacement(replacement);
17437
17832
  emit("beforeload", slot);
17438
17833
  const ifid = get().storyData?.ifid;
17439
17834
  if (playthroughId && ifid) switchToLoadedPlaythrough(ifid, playthroughId);
@@ -17456,7 +17851,7 @@ var useStoryStore = create()(storyStore((set, get) => ({
17456
17851
  state.visitCounts = createCounts(payload.visitCounts);
17457
17852
  state.renderCounts = createCounts(payload.renderCounts);
17458
17853
  state.temporary = createNamespace();
17459
- state.transient = createNamespace(deepClone(get().transientDefaults));
17854
+ state.transient = freshTransients(get().transientDefaults);
17460
17855
  });
17461
17856
  if (payload.watchers) restoreMacroWatchers(payload.watchers);
17462
17857
  reinitTriggerState();
@@ -17686,6 +18081,7 @@ function PassageDialog({ passageName, fallbackMarkup, panelClass, onClose, dismi
17686
18081
  }
17687
18082
  }, [markup, nobr]);
17688
18083
  const panelRef = useRef(null);
18084
+ const viewScope = useViewScope();
17689
18085
  useModalFocus(panelRef, ".dialog-body", dismissible, stableOnClose);
17690
18086
  useLayoutEffect(() => registerOpenDialog(stableOnClose), [stableOnClose]);
17691
18087
  useLayoutEffect(() => {
@@ -17715,7 +18111,10 @@ function PassageDialog({ passageName, fallbackMarkup, panelClass, onClose, dismi
17715
18111
  children: "✕"
17716
18112
  }), /* @__PURE__ */ jsx("div", {
17717
18113
  class: "dialog-body",
17718
- children: content
18114
+ children: /* @__PURE__ */ jsx(ViewScopeContext.Provider, {
18115
+ value: viewScope,
18116
+ children: content
18117
+ })
17719
18118
  })]
17720
18119
  })
17721
18120
  })
@@ -18105,37 +18504,85 @@ var settings = {
18105
18504
  //#endregion
18106
18505
  //#region src/utils/control-edits.ts
18107
18506
  /**
18108
- * Records the edits a reader made through a control bound to a story
18109
- * variable (`{textbox}`, `{checkbox}`, ...). {for} tells these apart from a
18110
- * list that is replaced: typing into a control inside an iteration changes
18111
- * its item, but must not remount the iteration and drop the control's focus.
18507
+ * Records the edits made to the items of a list from inside a {for}
18508
+ * iteration, which {for} tells apart from a list that is replaced:
18509
+ *
18510
+ * - a reader editing a variable through a control (`{textbox}`,
18511
+ * `{checkbox}`, ...): typing into a control inside an iteration changes
18512
+ * its item, but must not remount the iteration and drop the control's
18513
+ * focus (#369);
18514
+ * - mutation code an iteration runs as it renders (`{set $party[@i].hp +=
18515
+ * 1}`): remounting would run the iteration's mount-only macros, and so the
18516
+ * write, again for every write it makes (#400).
18517
+ *
18112
18518
  * An edit is attributed to the variable path it wrote, so a replacement made
18113
- * elsewhere (a watcher reacting to the edit) still remounts.
18519
+ * elsewhere (a watcher reacting to the edit, a button outside the loop)
18520
+ * still remounts.
18114
18521
  */
18115
18522
  var version = 0;
18116
- var MAX_LOG = 256;
18523
+ var MAX_LOG = 4096;
18524
+ /** Edits in the order made: paths below the namespaces (`['variables', 'a']`). */
18117
18525
  var log = [];
18118
- function noteControlEdit(segments) {
18526
+ function note(path) {
18119
18527
  log.push({
18120
18528
  version: ++version,
18121
- segments
18529
+ path
18122
18530
  });
18123
18531
  if (log.length > MAX_LOG) log.shift();
18124
18532
  }
18533
+ /** Note an edit a control made to the story variable at `segments`. */
18534
+ function noteControlEdit(segments) {
18535
+ note(["variables", ...segments]);
18536
+ }
18537
+ /**
18538
+ * Whether mutation code runs inside a {for} iteration: {for} provides true
18539
+ * around its iterations, and the writes of such code are noted as edits of
18540
+ * the items they reach (see runAsItemEdit).
18541
+ */
18542
+ var ItemEditContext = createContext(false);
18543
+ var NAMESPACES = [
18544
+ "variables",
18545
+ "temporary",
18546
+ "transient"
18547
+ ];
18548
+ /** Run `run`, noting each variable path it changes as an edit. */
18549
+ function runAsItemEdit(run) {
18550
+ const before = readState();
18551
+ try {
18552
+ run();
18553
+ } finally {
18554
+ const after = readState();
18555
+ for (const ns of NAMESPACES) {
18556
+ if (before[ns] === after[ns]) continue;
18557
+ for (const { path } of diffPaths(before[ns], after[ns], true)) note([ns, ...path]);
18558
+ }
18559
+ }
18560
+ }
18125
18561
  function controlEditVersion() {
18126
18562
  return version;
18127
18563
  }
18128
18564
  /**
18129
- * Whether an edit since `since` wrote inside `item`, element `index` of
18130
- * `list`: the path leads through the item (compared by reference in
18131
- * `variables`), or, for a primitive item, ends at its slot in the list.
18132
- */
18133
- function editedItem(since, variables, list, item, index) {
18134
- return log.some(({ version: v, segments }) => {
18135
- if (v <= since) return false;
18136
- if (typeof item === "object" && item !== null) return segments.some((_, n) => getByPath(variables, segments.slice(0, n + 1)) === item);
18137
- return segments[segments.length - 1] === String(index) && getByPath(variables, segments.slice(0, -1)) === list;
18138
- });
18565
+ * A test of whether an edit made since `since` wrote inside `item`, element
18566
+ * `index` of `list`: the path leads through the item (compared by reference
18567
+ * in `state`), or, for a primitive item, ends at its slot in the list.
18568
+ */
18569
+ function editedItems(since, state) {
18570
+ const through = /* @__PURE__ */ new Set();
18571
+ const ends = /* @__PURE__ */ new Map();
18572
+ for (let i = log.length - 1; i >= 0 && log[i].version > since; i--) {
18573
+ const { path } = log[i];
18574
+ let node = state;
18575
+ path.forEach((seg, n) => {
18576
+ if (n === path.length - 1 && node !== null && typeof node === "object") {
18577
+ const keys = ends.get(node) ?? /* @__PURE__ */ new Set();
18578
+ keys.add(seg);
18579
+ ends.set(node, keys);
18580
+ }
18581
+ node = node !== null && typeof node === "object" ? getByPath(node, [seg]) : void 0;
18582
+ if (node !== null && typeof node === "object") through.add(node);
18583
+ });
18584
+ }
18585
+ return (list, item, index) => typeof item === "object" && item !== null ? through.has(item) : ends.get(list)?.has(String(index)) === true;
18139
18586
  }
18140
18587
  //#endregion
18141
18588
  //#region src/action-registry.ts
@@ -18332,6 +18779,7 @@ function defineMacro(config, source = "builtin") {
18332
18779
  id = resolve(id);
18333
18780
  }
18334
18781
  const { update, getValues } = useContext(LocalsUpdateContext);
18782
+ const inIteration = useContext(ItemEditContext);
18335
18783
  const renderOptions = useRenderOptions();
18336
18784
  const renderNodes$1 = (nodes, options) => renderNodes(nodes, {
18337
18785
  ...renderOptions,
@@ -18362,7 +18810,7 @@ function defineMacro(config, source = "builtin") {
18362
18810
  id,
18363
18811
  resolve,
18364
18812
  cls: macroClass(config.name, className),
18365
- mutate: (code) => executeMutation(code, getValues(), update),
18813
+ mutate: inIteration ? (code) => runAsItemEdit(() => executeMutation(code, getValues(), update)) : (code) => executeMutation(code, getValues(), update),
18366
18814
  update,
18367
18815
  getValues,
18368
18816
  wrap: (content) => wrapContent(className, id, content)
@@ -18668,7 +19116,7 @@ function createStoryAPI() {
18668
19116
  },
18669
19117
  deferRender() {
18670
19118
  useStoryStore.getState().deferRender();
18671
- readyPromise = new Promise((resolve) => {
19119
+ readyPromise ??= new Promise((resolve) => {
18672
19120
  readyResolve = resolve;
18673
19121
  });
18674
19122
  },
@@ -19227,16 +19675,25 @@ var PRIMITIVE_SAMPLES = {
19227
19675
  number: Object(0),
19228
19676
  boolean: Object(false)
19229
19677
  };
19230
- function inferSchema(value) {
19678
+ /**
19679
+ * The schema of a default value. An object met again (a backreference, a
19680
+ * shared subtree) has the schema made for it the first time, so a cyclic
19681
+ * default gives a schema that refers to itself instead of recursing forever.
19682
+ */
19683
+ function inferSchema(value, seen = /* @__PURE__ */ new Map()) {
19231
19684
  if (Array.isArray(value)) return { type: "array" };
19232
19685
  if (value === null) return { type: "null" };
19233
19686
  if (typeof value === "object") {
19687
+ const known = seen.get(value);
19688
+ if (known) return known;
19234
19689
  const fields = /* @__PURE__ */ new Map();
19235
- for (const [key, val] of Object.entries(value)) fields.set(key, inferSchema(val));
19236
- return {
19690
+ const schema = {
19237
19691
  type: "object",
19238
19692
  fields
19239
19693
  };
19694
+ seen.set(value, schema);
19695
+ for (const [key, val] of Object.entries(value)) fields.set(key, inferSchema(val, seen));
19696
+ return schema;
19240
19697
  }
19241
19698
  const jsType = typeof value;
19242
19699
  if (!VALID_VAR_TYPES.has(jsType)) throw new Error(`Unsupported type "${jsType}" for value ${String(value)}. Expected number, string, boolean, array, or object.`);
@@ -19734,23 +20191,32 @@ defineMacro({
19734
20191
  * Content-derived string key for a value, used to remount components when
19735
20192
  * the value's contents change (e.g. {for} iterations, #45).
19736
20193
  *
19737
- * JSON-compatible values (strings, finite numbers, booleans, null, arrays and
19738
- * plain objects of those) produce exactly their `JSON.stringify` output. Other
19739
- * values get distinct, unambiguous markers so their contents are reflected
19740
- * too: Map and Set entries (nested at any depth), Date, RegExp, BigInt,
19741
- * undefined, NaN/±Infinity, symbols (by description), functions (by name)
19742
- * and registered class instances. A hole in an array reads as undefined.
19743
- * Cyclic references become a back-reference marker instead of throwing,
19744
- * and the function never throws.
20194
+ * A tree of JSON-compatible values (strings, finite numbers, booleans, null,
20195
+ * arrays and plain objects of those) produces exactly its `JSON.stringify`
20196
+ * output. Other values get distinct, unambiguous markers so their contents
20197
+ * are reflected too: Map and Set entries (nested at any depth), Date,
20198
+ * RegExp, BigInt, undefined, NaN/±Infinity, symbols (by description),
20199
+ * functions (by name) and registered class instances, with the class name
20200
+ * and, for a subclass of a built-in, its own fields (#407). A hole in an
20201
+ * array reads as undefined.
20202
+ *
20203
+ * An object met again, through a cycle or a second reference to it, is a
20204
+ * marker naming where it was first met rather than another copy of its
20205
+ * contents, so the key grows with the objects a value holds, not with the
20206
+ * paths to them (#406). The function never throws.
19745
20207
  */
19746
20208
  function stableKey(value) {
19747
20209
  try {
19748
- return keyOf(value, []);
20210
+ return keyOf(value, /* @__PURE__ */ new Map());
19749
20211
  } catch {
19750
20212
  return "<unkeyable>";
19751
20213
  }
19752
20214
  }
19753
- function keyOf(val, ancestors) {
20215
+ /**
20216
+ * The key of `val`. `seen` numbers the objects keyed so far, in the order
20217
+ * they were first met.
20218
+ */
20219
+ function keyOf(val, seen) {
19754
20220
  switch (typeof val) {
19755
20221
  case "string": return JSON.stringify(val);
19756
20222
  case "number": return Number.isFinite(val) ? JSON.stringify(val) : String(val);
@@ -19762,22 +20228,28 @@ function keyOf(val, ancestors) {
19762
20228
  }
19763
20229
  if (val === null) return "null";
19764
20230
  const obj = val;
19765
- const depth = ancestors.indexOf(obj);
19766
- if (depth !== -1) return `<cycle ${ancestors.length - depth}>`;
19767
- if (val instanceof Date) return `Date(${val.getTime()})`;
19768
- if (val instanceof RegExp) return `RegExp(${String(val)}@${val.lastIndex})`;
19769
- ancestors.push(obj);
19770
- try {
19771
- if (Array.isArray(val)) return `[${Array.from(val, (v) => keyOf(v, ancestors)).join(",")}]`;
19772
- if (val instanceof Map) return `Map[${[...val].map(([k, v]) => `[${keyOf(k, ancestors)},${keyOf(v, ancestors)}]`).join(",")}]`;
19773
- if (val instanceof Set) return `Set[${[...val].map((v) => keyOf(v, ancestors)).join(",")}]`;
19774
- const record = obj;
19775
- const body = Object.keys(record).map((k) => `${JSON.stringify(k)}:${keyOf(record[k], ancestors)}`).join(",");
19776
- const className = registeredClassName(obj);
19777
- return className === void 0 ? `{${body}}` : `Class(${JSON.stringify(className)}){${body}}`;
19778
- } finally {
19779
- ancestors.pop();
20231
+ const first = seen.get(obj);
20232
+ if (first !== void 0) return `<ref ${first}>`;
20233
+ const className = registeredClassName(obj);
20234
+ const named = (key) => className === void 0 ? key : `Class(${JSON.stringify(className)})${key}`;
20235
+ const withFields = (key) => {
20236
+ const keys = className === void 0 ? [] : extraKeys(obj);
20237
+ return named(keys.length ? `${key}${fieldsOf(obj, keys, seen)}` : key);
20238
+ };
20239
+ if (val instanceof Date || val instanceof RegExp) {
20240
+ if (className !== void 0 && extraKeys(obj).length) seen.set(obj, seen.size);
20241
+ return withFields(val instanceof Date ? `Date(${Date.prototype.getTime.call(val)})` : `RegExp(${RegExp.prototype.toString.call(val)}@${val.lastIndex})`);
19780
20242
  }
20243
+ seen.set(obj, seen.size);
20244
+ if (Array.isArray(val)) return withFields(`[${Array.from(val, (v) => keyOf(v, seen)).join(",")}]`);
20245
+ if (val instanceof Map) return withFields(`Map[${Array.from(mapEntries(val), ([k, v]) => `[${keyOf(k, seen)},${keyOf(v, seen)}]`).join(",")}]`);
20246
+ if (val instanceof Set) return withFields(`Set[${Array.from(setMembers(val), (v) => keyOf(v, seen)).join(",")}]`);
20247
+ return named(fieldsOf(obj, Object.keys(obj), seen));
20248
+ }
20249
+ /** `{"key":value,...}` for the `keys` of `obj`. */
20250
+ function fieldsOf(obj, keys, seen) {
20251
+ const record = obj;
20252
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${keyOf(record[k], seen)}`).join(",")}}`;
19781
20253
  }
19782
20254
  //#endregion
19783
20255
  //#region src/components/macros/For.tsx
@@ -19859,12 +20331,11 @@ defineMacro({
19859
20331
  });
19860
20332
  }
19861
20333
  const { itemVar, indexVar } = loop;
19862
- const since = keys.edits;
19863
- const { variables } = readState();
20334
+ const edited = editedItems(keys.edits, readState());
19864
20335
  keys.edits = controlEditVersion();
19865
20336
  const generations = list.map((item, i) => {
19866
20337
  const contents = stableKey(item);
19867
- if (keys.generations[i] === void 0 || contents !== keys.contents[i] && !editedItem(since, variables, list, item, i)) keys.generations[i] = keys.next++;
20338
+ if (keys.generations[i] === void 0 || contents !== keys.contents[i] && !edited(list, item, i)) keys.generations[i] = keys.next++;
19868
20339
  keys.contents[i] = contents;
19869
20340
  return keys.generations[i];
19870
20341
  });
@@ -19877,7 +20348,10 @@ defineMacro({
19877
20348
  indexValue: i,
19878
20349
  children
19879
20350
  }, `${i}-${generations[i]}`));
19880
- return ctx.wrap(content);
20351
+ return ctx.wrap(/* @__PURE__ */ jsx(ItemEditContext.Provider, {
20352
+ value: true,
20353
+ children: content
20354
+ }));
19881
20355
  },
19882
20356
  text({ rawArgs, children = [] }, ctx) {
19883
20357
  const { itemVar, indexVar, listExpr } = loopArgs(rawArgs, ctx.args);
@@ -19968,7 +20442,9 @@ var frozenAlways = () => true;
19968
20442
  * once it has rendered, so it runs once per click. It stays mounted
19969
20443
  * until the owning macro unmounts (its passage changes, say), so work it
19970
20444
  * starts in an effect, such as the timer of a {timed} or {repeat}, can
19971
- * finish; the passage leaving cancels it.
20445
+ * finish; the passage leaving cancels it, and so does a restart or a load
20446
+ * replacing the game, which a control of the story interface outlives
20447
+ * (#401).
19972
20448
  *
19973
20449
  * A detached render starts with no contexts, so the hook captures the ones
19974
20450
  * the owning macro sees and provides them again: the locals scope (read
@@ -19990,8 +20466,17 @@ function useDetachedBody() {
19990
20466
  const repeat = useContext(RepeatContext);
19991
20467
  const closeDialog = useContext(DialogCloseContext);
19992
20468
  const mounted = useRef([]);
19993
- useEffect(() => () => {
19994
- for (const container of mounted.current.splice(0)) render(null, container);
20469
+ useEffect(() => {
20470
+ const unsubscribe = subscribeStateReplacement(() => {
20471
+ const replaced = mounted.current.splice(0);
20472
+ queueMicrotask(() => {
20473
+ for (const container of replaced) render(null, container);
20474
+ });
20475
+ });
20476
+ return () => {
20477
+ unsubscribe();
20478
+ for (const container of mounted.current.splice(0)) render(null, container);
20479
+ };
19995
20480
  }, []);
19996
20481
  const run = (children) => {
19997
20482
  const locals = liveLocalsView(updater.getValues);
@@ -21162,6 +21647,7 @@ defineMacro({
21162
21647
  render(_props, ctx) {
21163
21648
  const radioValue = ctx.args.value ?? "";
21164
21649
  const label = ctx.args.label ?? "";
21650
+ const view = ctx.hooks.useContext(ViewScopeContext);
21165
21651
  useVariableAction(ctx, {
21166
21652
  type: "radiobutton",
21167
21653
  key: `$${ctx.varName}:${radioValue}`,
@@ -21173,7 +21659,7 @@ defineMacro({
21173
21659
  class: ctx.cls,
21174
21660
  children: [/* @__PURE__ */ jsx("input", {
21175
21661
  type: "radio",
21176
- name: `radio-${ctx.varName}`,
21662
+ name: view ? `radio-${view}-${ctx.varName}` : `radio-${ctx.varName}`,
21177
21663
  checked: ctx.value === radioValue,
21178
21664
  onChange: () => ctx.setValue(radioValue)
21179
21665
  }), label ? ` ${label}` : null]
@@ -21332,27 +21818,31 @@ function Passage({ passage, dataTransition, navigationId }) {
21332
21818
  if (elRef.current) emitFromRender("passagerender", passage.name, elRef.current);
21333
21819
  }, [passage.name, navigationId]);
21334
21820
  const nobr = passage.tags.includes("nobr");
21335
- const inner = /* @__PURE__ */ jsxs("div", {
21336
- ref: elRef,
21337
- class: "passage",
21338
- "data-passage": passage.name,
21339
- "data-tags": passage.tags.join(" "),
21340
- "data-transition": dataTransition,
21341
- children: [
21342
- headerContent && /* @__PURE__ */ jsx("div", {
21343
- class: "passage-header",
21344
- children: headerContent
21345
- }),
21346
- content,
21347
- footerContent && /* @__PURE__ */ jsx("div", {
21348
- class: "passage-footer",
21349
- children: footerContent
21350
- }),
21351
- doneContent && /* @__PURE__ */ jsx("div", {
21352
- hidden: true,
21353
- children: doneContent
21354
- })
21355
- ]
21821
+ const viewScope = useViewScope();
21822
+ const inner = /* @__PURE__ */ jsx(ViewScopeContext.Provider, {
21823
+ value: viewScope,
21824
+ children: /* @__PURE__ */ jsxs("div", {
21825
+ ref: elRef,
21826
+ class: "passage",
21827
+ "data-passage": passage.name,
21828
+ "data-tags": passage.tags.join(" "),
21829
+ "data-transition": dataTransition,
21830
+ children: [
21831
+ headerContent && /* @__PURE__ */ jsx("div", {
21832
+ class: "passage-header",
21833
+ children: headerContent
21834
+ }),
21835
+ content,
21836
+ footerContent && /* @__PURE__ */ jsx("div", {
21837
+ class: "passage-footer",
21838
+ children: footerContent
21839
+ }),
21840
+ doneContent && /* @__PURE__ */ jsx("div", {
21841
+ hidden: true,
21842
+ children: doneContent
21843
+ })
21844
+ ]
21845
+ })
21356
21846
  });
21357
21847
  return nobr ? /* @__PURE__ */ jsx(NobrContext.Provider, {
21358
21848
  value: true,
@@ -21650,9 +22140,12 @@ defineMacro({
21650
22140
  const { hooks } = ctx;
21651
22141
  const { condition, options } = ctx.args;
21652
22142
  if (condition === void 0) return null;
21653
- const registered = hooks.useRef(false);
21654
- if (!registered.current) {
21655
- registered.current = true;
22143
+ const frozen = hooks.useContext(FrozenStateContext);
22144
+ const [, rerender] = hooks.useState(0);
22145
+ hooks.useEffect(() => subscribeTriggerResets(() => rerender((n) => n + 1)), []);
22146
+ const registered = hooks.useRef(null);
22147
+ if (registered.current === null || registered.current !== triggerResets() && !frozen?.(useStoryStore.getState())) {
22148
+ registered.current = triggerResets();
21656
22149
  addMacroTrigger(condition, options);
21657
22150
  }
21658
22151
  return null;