@rohal12/spindle 0.59.12 → 0.59.14

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.
@@ -9893,6 +9893,86 @@ function parseMarkup(source, options = {}) {
9893
9893
  function tokenizeMarkup(source, options = {}) {
9894
9894
  return run$2(source, "Tokens", options);
9895
9895
  }
9896
+ /**
9897
+ * `tokenizeMarkup` for half-typed markup: the tokens it can read, and every
9898
+ * error, instead of throwing at the first. At a malformed tag, the tokens
9899
+ * before it are kept, its first character is read as text and tokenizing
9900
+ * resumes after it, so what follows is read as if the tag had not been
9901
+ * started. Tokens and errors have offsets in `source`; for well-formed
9902
+ * markup the tokens are those of `tokenizeMarkup` and there are no errors.
9903
+ */
9904
+ function tokenizeMarkupTolerant(source, options = {}) {
9905
+ const tokens = [];
9906
+ const errors = [];
9907
+ const lastBrace = source.lastIndexOf("}");
9908
+ const { closeBrace } = {
9909
+ ...defaultHooks,
9910
+ ...options.hooks
9911
+ };
9912
+ /** `text` shifted by `base`, tokenized: the tokens or the error. */
9913
+ const attempt = (text, base) => {
9914
+ const hooks = {
9915
+ ...options.hooks,
9916
+ closeBrace: (input, codeStart, lenientStart) => base + Math.min(codeStart, lenientStart) > lastBrace ? -1 : closeBrace(input, codeStart, lenientStart)
9917
+ };
9918
+ try {
9919
+ return tokenizeMarkup(text, {
9920
+ ...options,
9921
+ hooks
9922
+ }).map((token) => shift(token, base));
9923
+ } catch (error) {
9924
+ if (!(error instanceof MarkupError)) throw error;
9925
+ return error;
9926
+ }
9927
+ };
9928
+ let base = 0;
9929
+ while (base <= source.length) {
9930
+ const rest = source.slice(base);
9931
+ const result = attempt(rest, base);
9932
+ if (Array.isArray(result)) {
9933
+ tokens.push(...result);
9934
+ break;
9935
+ }
9936
+ const at = base + result.offset;
9937
+ const { line, column } = lineColumn(source, at);
9938
+ errors.push(new MarkupError(result.reason, at, line, column));
9939
+ let cut = result.offset;
9940
+ let prefix = attempt(rest.slice(0, cut), base);
9941
+ while (!Array.isArray(prefix)) {
9942
+ cut = prefix.offset;
9943
+ prefix = attempt(rest.slice(0, cut), base);
9944
+ }
9945
+ tokens.push(...prefix);
9946
+ if (cut < rest.length) {
9947
+ const start = base + cut;
9948
+ tokens.push({
9949
+ type: "text",
9950
+ value: rest[cut],
9951
+ start,
9952
+ end: start + 1
9953
+ });
9954
+ }
9955
+ base += cut + 1;
9956
+ }
9957
+ return {
9958
+ tokens,
9959
+ errors
9960
+ };
9961
+ }
9962
+ /** `token` with its offsets moved `by` on. */
9963
+ function shift(token, by) {
9964
+ if (by === 0) return token;
9965
+ const moved = {
9966
+ ...token,
9967
+ start: token.start + by,
9968
+ end: token.end + by
9969
+ };
9970
+ if (moved.type === "link") {
9971
+ moved.targetStart += by;
9972
+ moved.targetEnd += by;
9973
+ }
9974
+ return moved;
9975
+ }
9896
9976
  //#endregion
9897
9977
  //#region src/utils/namespace.ts
9898
9978
  /** The variable name no namespace can hold. */
@@ -10567,28 +10647,34 @@ function parseWidgetDef(rawArgs) {
10567
10647
  return widgetDef(parseMacroArgs(rawArgs, WIDGET_PARAMETERS));
10568
10648
  }
10569
10649
  /**
10570
- * A definition: its arguments (read like a {widget} macro's, see
10571
- * parseWidgetDef) and its body. Macro names are not case-sensitive.
10572
- */
10573
- var WIDGET_DEFINITION = /\{widget\s+([^}]*)\}([\s\S]*?)\{\/widget\}/gi;
10574
- /** `{@children}`, also with selectors such as `{.highlight @children}`. */
10575
- var CHILDREN_PLACEHOLDER = /\{[^{}]*@children\s*\}/;
10576
- /**
10577
10650
  * The names of the block widgets (those whose body renders `{@children}`)
10578
10651
  * that StoryInit and the passages tagged `widget` define. They must be
10579
10652
  * known as block macros before any passage is parsed, so that passages
10580
10653
  * invoking them nest their content whatever the passage order.
10654
+ *
10655
+ * The passages are read as flat tokens, which need no block macros to be
10656
+ * known, so a `{@children}` in an HTML comment or in a `{do}` body is text
10657
+ * here as it is to the parser, and does not make a block widget (#387).
10581
10658
  */
10582
10659
  function blockWidgetNames(passages) {
10583
10660
  const names = [];
10584
10661
  for (const passage of passages) {
10585
10662
  if (passage.name !== "StoryInit" && !passage.tags?.includes("widget")) continue;
10586
- for (const match of passage.content.matchAll(WIDGET_DEFINITION)) {
10587
- if (!CHILDREN_PLACEHOLDER.test(match[2])) continue;
10663
+ const open = [];
10664
+ for (const token of tokenizeMarkupTolerant(passage.content).tokens) if (token.type === "macro" && token.name.toLowerCase() === "widget") {
10665
+ if (!token.isClose) {
10666
+ open.push({
10667
+ rawArgs: token.rawArgs,
10668
+ isBlock: false
10669
+ });
10670
+ continue;
10671
+ }
10672
+ const def = open.pop();
10673
+ if (!def?.isBlock) continue;
10588
10674
  try {
10589
- names.push(parseWidgetDef(match[1]).name);
10675
+ names.push(parseWidgetDef(def.rawArgs).name);
10590
10676
  } catch {}
10591
- }
10677
+ } else if (token.type === "variable" && token.scope === "local" && token.name === "children" && open.length > 0) open[open.length - 1].isBlock = true;
10592
10678
  }
10593
10679
  return names;
10594
10680
  }
@@ -11108,6 +11194,13 @@ function wrapContent(className, id, content) {
11108
11194
  function hasInterpolation(s) {
11109
11195
  return s.includes("{") || s.includes("}");
11110
11196
  }
11197
+ /** The children context for an invocation's `children`, or null if none. */
11198
+ function widgetChildrenOf(children, outer) {
11199
+ return children?.length ? {
11200
+ nodes: children,
11201
+ outer: outer ?? null
11202
+ } : null;
11203
+ }
11111
11204
  var parseCache = /* @__PURE__ */ new Map();
11112
11205
  var PARSE_CACHE_LIMIT = 2e3;
11113
11206
  /**
@@ -11193,7 +11286,7 @@ var TextEvaluator = class {
11193
11286
  if (node.scope === "local" && node.name === "children") {
11194
11287
  const children = scope.widgetChildren;
11195
11288
  if (!children) return "";
11196
- return this.nested("children", children, scope, depth, { widgetChildren: null });
11289
+ return this.nested("children", children.nodes, scope, depth, { widgetChildren: children.outer });
11197
11290
  }
11198
11291
  return this.variable(node.scope, node.name, scope);
11199
11292
  case "expression": try {
@@ -11256,7 +11349,7 @@ var TextEvaluator = class {
11256
11349
  }
11257
11350
  return this.nested(node.name, widget.body, scope, depth, {
11258
11351
  locals,
11259
- widgetChildren: node.children.length > 0 ? node.children : null
11352
+ widgetChildren: widgetChildrenOf(node.children, scope.widgetChildren)
11260
11353
  });
11261
11354
  }
11262
11355
  const text = getMacroText(node.name);
@@ -11514,7 +11607,8 @@ function WidgetInvocation(props) {
11514
11607
  function WidgetContent({ body, params, rawArgs, invocationChildren }) {
11515
11608
  const parentValues = useRenderOptions().locals;
11516
11609
  const [mergedVars, mergedTemps, mergedLocals, mergedTrans] = useMergedLocals();
11517
- const childrenValue = invocationChildren?.length ? invocationChildren : null;
11610
+ const outerChildren = useContext(WidgetChildrenContext);
11611
+ const childrenValue = useMemo(() => widgetChildrenOf(invocationChildren, outerChildren), [invocationChildren, outerChildren]);
11518
11612
  const argExprs = rawArgs ? splitArgs(rawArgs) : [];
11519
11613
  const values = [];
11520
11614
  for (let i = 0; i < params.length; i++) {
@@ -11791,7 +11885,7 @@ var InlineContext = createContext(false);
11791
11885
  /**
11792
11886
  * True while rendering inside an element whose content is not markdown: SVG
11793
11887
  * (whose namespace `<p>` wrappers would break) and the preformatted `<pre>`
11794
- * and `<textarea>`, whose text (indentation, `#`, `*`, ...) is literal.
11888
+ * `<textarea>` and `<style>`, whose text (indentation, `#`, `*`, ...) is literal.
11795
11889
  * Macro and widget bodies read it so their content stays literal too.
11796
11890
  */
11797
11891
  var RawTextContext = createContext(false);
@@ -11802,7 +11896,11 @@ var RawTextContext = createContext(false);
11802
11896
  var SvgContext = createContext(false);
11803
11897
  var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
11804
11898
  /** Elements whose content is literal text, not markdown. */
11805
- var PREFORMATTED_ELEMENTS = new Set(["pre", "textarea"]);
11899
+ var PREFORMATTED_ELEMENTS = new Set([
11900
+ "pre",
11901
+ "textarea",
11902
+ "style"
11903
+ ]);
11806
11904
  var WidgetChildrenContext = createContext(null);
11807
11905
  function placeholderHtml(nonce, index) {
11808
11906
  return `<span data-tw=${nonce}:${index}></span>`;
@@ -12021,6 +12119,7 @@ var INLINE_ELEMENTS = new Set([
12021
12119
  "span",
12022
12120
  "strong",
12023
12121
  "sub",
12122
+ "summary",
12024
12123
  "sup",
12025
12124
  "time",
12026
12125
  "u",
@@ -12225,6 +12324,12 @@ function resolveAttributeValue(name, value, scope, errors) {
12225
12324
  for (const error of result.errors) errors.push([name, error]);
12226
12325
  return result.text;
12227
12326
  }
12327
+ /** The index of the summary element opening `nodes`, or -1 when none does. */
12328
+ function leadingSummaryIndex(nodes) {
12329
+ const at = nodes.findIndex((n) => n.type !== "text" || n.value.trim() !== "");
12330
+ const first = nodes[at];
12331
+ return first?.type === "html" && first.tag.toLowerCase() === "summary" ? at : -1;
12332
+ }
12228
12333
  function HtmlNodeRenderer({ node }) {
12229
12334
  const scope = useTextScope();
12230
12335
  const { nobr, locals, raw: inRaw, inline: parentInline } = useRenderOptions();
@@ -12244,11 +12349,17 @@ function HtmlNodeRenderer({ node }) {
12244
12349
  let children = void 0;
12245
12350
  if (node.children.length > 0) if (inRaw || isRawRoot) children = renderInlineNodes(node.children);
12246
12351
  else {
12247
- children = renderNodes(node.children, {
12352
+ const summaryAt = tag === "details" ? leadingSummaryIndex(node.children) : -1;
12353
+ const body = (nodes) => renderNodes(nodes, {
12248
12354
  nobr,
12249
12355
  locals,
12250
12356
  inline: isInline
12251
12357
  });
12358
+ children = summaryAt === -1 ? body(node.children) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
12359
+ body(node.children.slice(0, summaryAt)),
12360
+ renderSingleNode(node.children[summaryAt]),
12361
+ body(node.children.slice(summaryAt + 1))
12362
+ ] });
12252
12363
  if (isInline !== parentInline) children = /* @__PURE__ */ jsx(InlineContext.Provider, {
12253
12364
  value: isInline,
12254
12365
  children
@@ -12265,10 +12376,13 @@ function HtmlNodeRenderer({ node }) {
12265
12376
  }) : element);
12266
12377
  }
12267
12378
  function ChildrenSlot() {
12268
- const childrenAST = useContext(WidgetChildrenContext);
12379
+ const children = useContext(WidgetChildrenContext);
12269
12380
  const renderOptions = useRenderOptions();
12270
- if (!childrenAST || childrenAST.length === 0) return null;
12271
- return /* @__PURE__ */ jsx(Fragment$1, { children: renderNodes(childrenAST, renderOptions) });
12381
+ if (!children) return null;
12382
+ return /* @__PURE__ */ jsx(WidgetChildrenContext.Provider, {
12383
+ value: children.outer,
12384
+ children: renderNodes(children.nodes, renderOptions)
12385
+ });
12272
12386
  }
12273
12387
  /**
12274
12388
  * Stable per-node keys. Keying rendered children by AST node identity (not
@@ -13177,6 +13291,39 @@ function stringify_primitive(thing) {
13177
13291
  return String(thing);
13178
13292
  }
13179
13293
  //#endregion
13294
+ //#region src/utils/value-kinds.ts
13295
+ var toStringTag = (value) => Object.prototype.toString.call(value).slice(8, -1);
13296
+ /** Temporal values are immutable: copies may share them. */
13297
+ var isTemporal = (value) => toStringTag(value).startsWith("Temporal.");
13298
+ /** new Number(), new String(), new Boolean(), Object(1n). */
13299
+ var isBoxed = (value) => value instanceof Number || value instanceof String || value instanceof Boolean || toStringTag(value) === "BigInt";
13300
+ /**
13301
+ * Values compared and copied as a whole, by their content: they have no
13302
+ * own keys to merge by, or their keys are not the data (a property written
13303
+ * into one would be dropped by clones and saves, and by Immer for Map and
13304
+ * Set).
13305
+ */
13306
+ function isAtomic(value) {
13307
+ return value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof URL || value instanceof URLSearchParams || value instanceof Error || isBoxed(value) || isTemporal(value);
13308
+ }
13309
+ /** "a Map", "an Error", ... for an atomic value (see isAtomic), else undefined. */
13310
+ function atomicName(value) {
13311
+ if (!isAtomic(value)) return void 0;
13312
+ const tag = value instanceof Error ? "Error" : toStringTag(value);
13313
+ return `${/^[AEIOU]/.test(tag) ? "an" : "a"} ${tag}`;
13314
+ }
13315
+ /** Whether `key` is an index of an array element. */
13316
+ var isIndexKey = (key) => key !== "4294967295" && String(Number(key) >>> 0) === key;
13317
+ /**
13318
+ * The own enumerable string keys of a collection (an Array, Map or Set) that
13319
+ * are not its elements: what an instance of a subclass may hold besides
13320
+ * them.
13321
+ */
13322
+ function extraKeys(collection) {
13323
+ const keys = Object.keys(collection);
13324
+ return Array.isArray(collection) ? keys.filter((k) => !isIndexKey(k)) : keys;
13325
+ }
13326
+ //#endregion
13180
13327
  //#region src/class-registry.ts
13181
13328
  var registry = /* @__PURE__ */ new Map();
13182
13329
  var ctorToName = /* @__PURE__ */ new Map();
@@ -13216,6 +13363,9 @@ var ERROR_HIDDEN_KEYS$1 = [
13216
13363
  "errors"
13217
13364
  ];
13218
13365
  var isObject = (v) => typeof v === "object" && v !== null;
13366
+ /** Whether `v` is a plain object (as devalue revives one). */
13367
+ var isPlainData = (v) => isObject(v) && Object.getPrototypeOf(v) === Object.prototype;
13368
+ var malformedClass = (name) => /* @__PURE__ */ new TypeError(`spindle: Malformed data for class "${name}"`);
13219
13369
  var defineData = (target, key, value) => {
13220
13370
  Object.defineProperty(target, key, {
13221
13371
  value,
@@ -13235,15 +13385,49 @@ function refuseSymbolKeys(value) {
13235
13385
  * Own enumerable keys of `value`, as a plain object; for an error also its
13236
13386
  * message, cause and (AggregateError) errors, which are not enumerable.
13237
13387
  */
13238
- function ownData(value) {
13388
+ function ownData(value, keys = Object.keys(value)) {
13239
13389
  refuseSymbolKeys(value);
13240
13390
  const data = {};
13241
13391
  if (value instanceof Error) {
13242
13392
  for (const key of ERROR_HIDDEN_KEYS$1) if (hasOwn(value, key)) defineData(data, key, value[key]);
13243
13393
  }
13244
- for (const key of Object.keys(value)) defineData(data, key, value[key]);
13394
+ for (const key of keys) defineData(data, key, value[key]);
13245
13395
  return data;
13246
13396
  }
13397
+ /** The kinds of collection a registered class may extend. */
13398
+ var COLLECTION_KINDS = {
13399
+ Array,
13400
+ Map,
13401
+ Set
13402
+ };
13403
+ /**
13404
+ * 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).
13407
+ */
13408
+ function classData(value) {
13409
+ const extra = () => ownData(value, extraKeys(value));
13410
+ if (Array.isArray(value)) {
13411
+ const items = new Array(value.length);
13412
+ for (let i = 0; i < value.length; i++) if (i in value) items[i] = value[i];
13413
+ return [
13414
+ "Array",
13415
+ items,
13416
+ extra()
13417
+ ];
13418
+ }
13419
+ if (value instanceof Map) return [
13420
+ "Map",
13421
+ Array.from(Map.prototype.entries.call(value)),
13422
+ extra()
13423
+ ];
13424
+ if (value instanceof Set) return [
13425
+ "Set",
13426
+ Array.from(Set.prototype.values.call(value)),
13427
+ extra()
13428
+ ];
13429
+ return ownData(value);
13430
+ }
13247
13431
  /** Make an error's message, cause and errors non-enumerable again. */
13248
13432
  function hideErrorKeys(value) {
13249
13433
  for (const key of ERROR_HIDDEN_KEYS$1) if (hasOwn(value, key)) Object.defineProperty(value, key, { enumerable: false });
@@ -13252,7 +13436,7 @@ function hideErrorKeys(value) {
13252
13436
  var isEscapedOneCharKey = (key) => key.length === 1 && JSON.stringify(key).length > 3;
13253
13437
  function reducers() {
13254
13438
  const out = {};
13255
- for (const [name, ctor] of registry) out[CLASS_PREFIX + name] = (v) => isObject(v) && Object.getPrototypeOf(v) === ctor.prototype && ownData(v);
13439
+ for (const [name, ctor] of registry) out[CLASS_PREFIX + name] = (v) => isObject(v) && Object.getPrototypeOf(v) === ctor.prototype && classData(v);
13256
13440
  for (const [proto, name] of BUILTIN_ERRORS) out[ERROR_PREFIX + name] = (v) => isObject(v) && Object.getPrototypeOf(v) === proto && ownData(v);
13257
13441
  out[SYMBOL_TAG] = (v) => {
13258
13442
  if (typeof v !== "symbol") return false;
@@ -13265,7 +13449,7 @@ function reducers() {
13265
13449
  v.lastIndex
13266
13450
  ];
13267
13451
  out[KEYS_TAG] = (v) => {
13268
- if (!isObject(v) || Object.getPrototypeOf(v) !== Object.prototype) return false;
13452
+ if (!isPlainData(v)) return false;
13269
13453
  const keys = Object.keys(v);
13270
13454
  if (!keys.some(isEscapedOneCharKey)) return false;
13271
13455
  refuseSymbolKeys(v);
@@ -13274,27 +13458,71 @@ function reducers() {
13274
13458
  };
13275
13459
  return out;
13276
13460
  }
13461
+ /**
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.
13466
+ */
13467
+ var collections = /* @__PURE__ */ new WeakMap();
13468
+ function reviveCollection(name, ctor, list) {
13469
+ 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);
13472
+ let made = collections.get(list);
13473
+ if (!made) {
13474
+ made = Object.setPrototypeOf(new base(), ctor.prototype);
13475
+ collections.set(list, made);
13476
+ }
13477
+ if (Array.isArray(contents)) fillCollection(made, contents);
13478
+ if (isPlainData(keys)) for (const key of Object.keys(keys)) defineData(made, key, keys[key]);
13479
+ return made;
13480
+ }
13481
+ /** Set the elements of a revived collection, with the built-in methods. */
13482
+ function fillCollection(made, contents) {
13483
+ if (Array.isArray(made)) {
13484
+ made.length = contents.length;
13485
+ for (let i = 0; i < contents.length; i++) if (hasOwn(contents, i)) made[i] = contents[i];
13486
+ else delete made[i];
13487
+ } else if (made instanceof Map) {
13488
+ Map.prototype.clear.call(made);
13489
+ for (const entry of contents) {
13490
+ if (!Array.isArray(entry) || entry.length !== 2) throw new TypeError("spindle: Malformed map entries");
13491
+ Map.prototype.set.call(made, entry[0], entry[1]);
13492
+ }
13493
+ } else {
13494
+ Set.prototype.clear.call(made);
13495
+ for (const member of contents) Set.prototype.add.call(made, member);
13496
+ }
13497
+ }
13277
13498
  /** Restore a registered class instance in place, so cycles through it hold. */
13278
13499
  function reviveClass(name) {
13279
13500
  return (data) => {
13280
- if (!isObject(data) || Object.getPrototypeOf(data) !== Object.prototype) {
13501
+ if (Array.isArray(data)) return reviveCollection(name, registeredCtor(name), data);
13502
+ if (!isPlainData(data)) {
13281
13503
  if (isObject(data) && registeredClassName(data) === name) return data;
13282
- throw new TypeError(`spindle: Malformed data for class "${name}"`);
13504
+ throw malformedClass(name);
13283
13505
  }
13284
- const ctor = registry.get(name);
13285
- if (!ctor) throw new TypeError(`spindle: The save holds an instance of class "${name}", which is not registered`);
13506
+ const ctor = registeredCtor(name);
13507
+ if (Object.values(COLLECTION_KINDS).some((c) => ctor.prototype instanceof c)) throw malformedClass(name);
13286
13508
  Object.setPrototypeOf(data, ctor.prototype);
13287
13509
  if (data instanceof Error) hideErrorKeys(data);
13288
13510
  return data;
13289
13511
  };
13290
13512
  }
13513
+ /** The class registered as `name`; throws if there is none. */
13514
+ function registeredCtor(name) {
13515
+ const ctor = registry.get(name);
13516
+ if (!ctor) throw new TypeError(`spindle: The save holds an instance of class "${name}", which is not registered`);
13517
+ return ctor;
13518
+ }
13291
13519
  /** Restore a built-in error in place, like a class instance. */
13292
13520
  function reviveError(name) {
13293
13521
  const ctor = ERROR_CTORS.get(name);
13294
13522
  if (!ctor) return void 0;
13295
13523
  return (data) => {
13296
13524
  if (Object.getPrototypeOf(data) === ctor.prototype) return data;
13297
- if (!isObject(data) || Object.getPrototypeOf(data) !== Object.prototype) throw new TypeError(`spindle: Malformed data for "${name}"`);
13525
+ if (!isPlainData(data)) throw new TypeError(`spindle: Malformed data for "${name}"`);
13298
13526
  Object.setPrototypeOf(data, ctor.prototype);
13299
13527
  hideErrorKeys(data);
13300
13528
  return data;
@@ -13891,28 +14119,6 @@ function getBackendType() {
13891
14119
  return _backend?.type ?? "memory";
13892
14120
  }
13893
14121
  //#endregion
13894
- //#region src/utils/value-kinds.ts
13895
- var toStringTag = (value) => Object.prototype.toString.call(value).slice(8, -1);
13896
- /** Temporal values are immutable: copies may share them. */
13897
- var isTemporal = (value) => toStringTag(value).startsWith("Temporal.");
13898
- /** new Number(), new String(), new Boolean(), Object(1n). */
13899
- var isBoxed = (value) => value instanceof Number || value instanceof String || value instanceof Boolean || toStringTag(value) === "BigInt";
13900
- /**
13901
- * Values compared and copied as a whole, by their content: they have no
13902
- * own keys to merge by, or their keys are not the data (a property written
13903
- * into one would be dropped by clones and saves, and by Immer for Map and
13904
- * Set).
13905
- */
13906
- function isAtomic(value) {
13907
- return value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof URL || value instanceof URLSearchParams || value instanceof Error || isBoxed(value) || isTemporal(value);
13908
- }
13909
- /** "a Map", "an Error", ... for an atomic value (see isAtomic), else undefined. */
13910
- function atomicName(value) {
13911
- if (!isAtomic(value)) return void 0;
13912
- const tag = value instanceof Error ? "Error" : toStringTag(value);
13913
- return `${/^[AEIOU]/.test(tag) ? "an" : "a"} ${tag}`;
13914
- }
13915
- //#endregion
13916
14122
  //#region src/utils/object-path.ts
13917
14123
  /**
13918
14124
  * Traverse dot-path segments on an object and return the nested value.
@@ -14041,6 +14247,20 @@ function isPlainObject(value) {
14041
14247
  const proto = Object.getPrototypeOf(value);
14042
14248
  return proto === Object.prototype || proto === null;
14043
14249
  }
14250
+ /**
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
14255
+ * that cycles through it find it.
14256
+ */
14257
+ function collectionLike(obj, empty, made, value) {
14258
+ made(empty);
14259
+ if (registeredClassName(obj) === void 0) return empty;
14260
+ Object.setPrototypeOf(empty, Object.getPrototypeOf(obj));
14261
+ for (const key of extraKeys(obj)) setOwn(empty, key, value(key));
14262
+ return empty;
14263
+ }
14044
14264
  /** Own keys of an error that are not enumerable but are data. */
14045
14265
  var ERROR_HIDDEN_KEYS = [
14046
14266
  "message",
@@ -14102,23 +14322,25 @@ function deepClone(value, options = {}) {
14102
14322
  const settle = options.settle;
14103
14323
  return settle && isObjectValue(copy) && isObjectValue(original) ? settle(copy, original) : copy;
14104
14324
  }
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]));
14105
14327
  function clone(val) {
14106
14328
  if (val === null || typeof val !== "object") return val;
14107
14329
  const obj = val;
14108
14330
  if (seen.has(obj)) return seen.get(obj);
14109
14331
  if (Array.isArray(val)) {
14110
- const arr = keep(obj, new Array(val.length));
14332
+ const arr = collection(obj, new Array(val.length));
14111
14333
  for (let i = 0; i < val.length; i++) if (i in val) arr[i] = clone(val[i]);
14112
14334
  return arr;
14113
14335
  }
14114
14336
  if (val instanceof Map) {
14115
- const copy = keep(obj, /* @__PURE__ */ new Map());
14116
- for (const [k, v] of val) copy.set(settled(clone(k), k), clone(v));
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));
14117
14339
  return copy;
14118
14340
  }
14119
14341
  if (val instanceof Set) {
14120
- const copy = keep(obj, /* @__PURE__ */ new Set());
14121
- for (const v of val) copy.add(settled(clone(v), v));
14342
+ const copy = collection(obj, /* @__PURE__ */ new Set());
14343
+ for (const v of val) Set.prototype.add.call(copy, settled(clone(v), v));
14122
14344
  return copy;
14123
14345
  }
14124
14346
  const builtin = cloneBuiltin(obj);
@@ -14221,23 +14443,26 @@ function shareEqual(prev, curr) {
14221
14443
  if (placed !== void 0) return placed;
14222
14444
  if (!isObjectValue(p)) return fresh(c);
14223
14445
  if (reuse(p, c)) return p;
14446
+ const pr = p;
14447
+ const cr = c;
14448
+ /** What the result holds at `key` of `c`. */
14449
+ 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);
14224
14452
  if (c instanceof Map && p instanceof Map) {
14225
- const out = /* @__PURE__ */ new Map();
14226
- done.set(c, out);
14227
- for (const [k, v] of c) out.set(fresh(k), share(p.get(k), v));
14453
+ 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));
14228
14455
  return out;
14229
14456
  }
14230
14457
  if (Array.isArray(c) && Array.isArray(p)) {
14231
- const out = new Array(c.length);
14232
- done.set(c, out);
14458
+ const out = collection(new Array(c.length));
14233
14459
  for (let i = 0; i < c.length; i++) if (i in c) out[i] = share(p[i], c[i]);
14234
14460
  return out;
14235
14461
  }
14236
14462
  if (mergesWith(p, c, false)) {
14237
14463
  const out = Object.create(Object.getPrototypeOf(c));
14238
14464
  done.set(c, out);
14239
- const cr = c;
14240
- for (const key of Object.keys(cr)) setOwn(out, key, share(hasOwn(p, key) ? p[key] : void 0, cr[key]));
14465
+ for (const key of Object.keys(cr)) setOwn(out, key, shareKey(key));
14241
14466
  return out;
14242
14467
  }
14243
14468
  return fresh(c);
@@ -14266,14 +14491,27 @@ function equalBuiltin(a, b, assumed) {
14266
14491
  if (isTemporal(a)) return String(a) === String(b);
14267
14492
  if (a instanceof Error) return ERROR_HIDDEN_KEYS.every((key) => hasOwn(a, key) === hasOwn(b, key) && equal(a[key], b[key], assumed)) && equalKeys(a, b, assumed);
14268
14493
  }
14269
- function equalKeys(a, b, assumed) {
14494
+ function equalKeys(a, b, assumed, keysOf = Object.keys) {
14270
14495
  const ao = a;
14271
14496
  const bo = b;
14272
- const keys = Object.keys(ao);
14273
- if (keys.length !== Object.keys(bo).length) return false;
14497
+ const keys = keysOf(ao);
14498
+ if (keys.length !== keysOf(bo).length) return false;
14274
14499
  for (const key of keys) if (!hasOwn(bo, key) || !equal(ao[key], bo[key], assumed)) return false;
14275
14500
  return true;
14276
14501
  }
14502
+ /** Built-in collection prototypes, whose instances hold no extra keys. */
14503
+ var COLLECTION_PROTOS = new Set([
14504
+ Array.prototype,
14505
+ Map.prototype,
14506
+ Set.prototype
14507
+ ]);
14508
+ /**
14509
+ * Equality of the extra keys (see extraKeys) of two collections of one
14510
+ * class: those of a subclass instance are part of its value.
14511
+ */
14512
+ function equalExtraKeys(a, b, assumed) {
14513
+ return COLLECTION_PROTOS.has(Object.getPrototypeOf(a)) || equalKeys(a, b, assumed, extraKeys);
14514
+ }
14277
14515
  /** `assumed` tracks the pairs of objects met (see Pairs). */
14278
14516
  function equal(a, b, assumed) {
14279
14517
  if (Object.is(a, b) && !(assumed.strict && isObjectValue(a))) return true;
@@ -14287,7 +14525,7 @@ function equal(a, b, assumed) {
14287
14525
  const ai = a.entries();
14288
14526
  const bi = bc.entries();
14289
14527
  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;
14290
- return true;
14528
+ return equalExtraKeys(a, b, assumed);
14291
14529
  }
14292
14530
  const builtin = equalBuiltin(a, b, assumed);
14293
14531
  if (builtin !== void 0) return builtin;
@@ -14295,7 +14533,7 @@ function equal(a, b, assumed) {
14295
14533
  const bc = b;
14296
14534
  if (a.length !== bc.length) return false;
14297
14535
  for (let i = 0; i < a.length; i++) if (i in a !== i in bc || !equal(a[i], bc[i], assumed)) return false;
14298
- return true;
14536
+ return equalExtraKeys(a, b, assumed);
14299
14537
  }
14300
14538
  return equalKeys(a, b, assumed);
14301
14539
  }
@@ -14314,7 +14552,7 @@ function isMergeable(value) {
14314
14552
  * one class (see isMergeable), or, with `arrays`, both are arrays.
14315
14553
  */
14316
14554
  function mergesWith(a, b, arrays) {
14317
- if (arrays && (Array.isArray(a) || Array.isArray(b))) return Array.isArray(a) && Array.isArray(b);
14555
+ if (arrays && (Array.isArray(a) || Array.isArray(b))) return Array.isArray(a) && Array.isArray(b) && Object.getPrototypeOf(a) === Object.getPrototypeOf(b);
14318
14556
  return isMergeable(a) && isMergeable(b) && Object.getPrototypeOf(a) === Object.getPrototypeOf(b);
14319
14557
  }
14320
14558
  /**
@@ -15582,6 +15820,31 @@ var clonersFor = (paths) => {
15582
15820
  function frozenCopy(value) {
15583
15821
  return freeze(cloneValue(value), true);
15584
15822
  }
15823
+ /** Whether `value` and everything it holds is frozen. */
15824
+ function deeplyFrozen(value, seen = /* @__PURE__ */ new Set()) {
15825
+ if (typeof value !== "object" || value === null || seen.has(value)) return true;
15826
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return true;
15827
+ if (!Object.isFrozen(value)) return false;
15828
+ seen.add(value);
15829
+ if (value instanceof Map) {
15830
+ for (const [key, entry] of value) if (!deeplyFrozen(key, seen) || !deeplyFrozen(entry, seen)) return false;
15831
+ } else if (value instanceof Set) {
15832
+ for (const member of value) if (!deeplyFrozen(member, seen)) return false;
15833
+ }
15834
+ return Reflect.ownKeys(value).every((key) => {
15835
+ const desc = Object.getOwnPropertyDescriptor(value, key);
15836
+ return !desc || !("value" in desc) || deeplyFrozen(desc.value, seen);
15837
+ });
15838
+ }
15839
+ /**
15840
+ * A value read from the store, made safe to hand out: the store holds plain
15841
+ * values frozen, but not registered class instances, whose methods could
15842
+ * change the recorded history through the live object. Such a value is
15843
+ * returned as a frozen copy, like a read inside mutation code.
15844
+ */
15845
+ function readOnlyValue(value) {
15846
+ return deeplyFrozen(value) ? value : frozenCopy(value);
15847
+ }
15585
15848
  /**
15586
15849
  * Apply a write that reached the store while mutations execute to their
15587
15850
  * `work` and `base` copies (see MutationScope), so the code sees it and
@@ -17265,19 +17528,39 @@ var FOCUSABLE = [
17265
17528
  "select:not([disabled])",
17266
17529
  "textarea:not([disabled])",
17267
17530
  "iframe",
17268
- "[contenteditable=\"true\"]",
17531
+ "summary",
17532
+ "[contenteditable]",
17269
17533
  "[tabindex]"
17270
17534
  ].join(",");
17271
17535
  /**
17536
+ * Whether `el` is in a closed `<details>`, other than in the summary that
17537
+ * toggles it: the browser renders none of that content (#389).
17538
+ */
17539
+ function inClosedDetails(el) {
17540
+ for (let e = el.parentElement; e; e = e.parentElement) {
17541
+ if (e.localName !== "details" || e.hasAttribute("open")) continue;
17542
+ if (!Array.from(e.children).find((c) => c.localName === "summary")?.contains(el)) return true;
17543
+ }
17544
+ return false;
17545
+ }
17546
+ /**
17272
17547
  * Whether CSS leaves `el` in the tab sequence: not `visibility: hidden`
17273
- * (inherited, so the computed value covers ancestors) and not inside a
17274
- * `display: none` element.
17548
+ * (inherited, so the computed value covers ancestors), not inside a
17549
+ * `display: none` element and not in the hidden content of a closed
17550
+ * `<details>`.
17275
17551
  */
17276
17552
  function isRendered(el) {
17277
17553
  const { visibility } = getComputedStyle(el);
17278
17554
  if (visibility === "hidden" || visibility === "collapse") return false;
17279
17555
  for (let e = el; e; e = e.parentElement) if (getComputedStyle(e).display === "none") return false;
17280
- return true;
17556
+ return !inClosedDetails(el);
17557
+ }
17558
+ /**
17559
+ * Whether `el` can be focused at all: shown, rendered, enabled (also by its
17560
+ * fieldset) and not inert.
17561
+ */
17562
+ function isAvailable(el) {
17563
+ return !el.hidden && !el.closest("[hidden], [inert]") && !el.matches(":disabled") && isRendered(el);
17281
17564
  }
17282
17565
  /** The `tabindex` of `el` as an integer, or null when absent or invalid. */
17283
17566
  function tabIndexAttr(el) {
@@ -17286,15 +17569,21 @@ function tabIndexAttr(el) {
17286
17569
  return parseInt(raw, 10);
17287
17570
  }
17288
17571
  /**
17289
- * Whether the tab key can reach `el`: a negative tabindex only allows
17290
- * scripted focus, and of a radio group the browser tabs to the checked
17291
- * member only (the first one when none is checked).
17572
+ * Whether the tab key can reach `el`, an available element (see
17573
+ * isAvailable): a negative tabindex only allows scripted focus, and of a
17574
+ * radio group the browser tabs to the checked member only (the first one
17575
+ * when none is checked), of the members it can reach.
17292
17576
  */
17293
17577
  function isTabStop(el) {
17294
17578
  const tabIndex = tabIndexAttr(el);
17295
17579
  if (tabIndex !== null && tabIndex < 0) return false;
17580
+ if (el.hasAttribute("contenteditable") && !el.matches("a[href], button, input, select, textarea, iframe, summary") && (!el.isContentEditable || el.parentElement?.isContentEditable)) return false;
17581
+ if (el.localName === "summary") {
17582
+ const details = el.parentElement;
17583
+ if (details?.localName !== "details" || Array.from(details.children).find((c) => c.localName === "summary") !== el) return tabIndex !== null;
17584
+ }
17296
17585
  if (el instanceof HTMLInputElement && el.type === "radio" && el.name) {
17297
- const group = Array.from((el.form ?? el.getRootNode()).querySelectorAll("input[type=\"radio\"]")).filter((r) => r.name === el.name && r.form === el.form && !r.disabled);
17586
+ const group = Array.from((el.form ?? el.getRootNode()).querySelectorAll("input[type=\"radio\"]")).filter((r) => r.name === el.name && r.form === el.form && (tabIndexAttr(r) ?? 0) >= 0 && isAvailable(r));
17298
17587
  return el === (group.find((r) => r.checked) ?? group[0]);
17299
17588
  }
17300
17589
  return true;
@@ -17304,7 +17593,7 @@ function isTabStop(el) {
17304
17593
  * first, ascending, then the others in document order.
17305
17594
  */
17306
17595
  function focusables(root) {
17307
- const stops = Array.from(root.querySelectorAll(FOCUSABLE)).filter((el) => !el.hidden && !el.closest("[hidden], [inert]") && !el.matches(":disabled") && isRendered(el) && isTabStop(el));
17596
+ const stops = Array.from(root.querySelectorAll(FOCUSABLE)).filter((el) => isAvailable(el) && isTabStop(el));
17308
17597
  const positive = (el) => Math.max(tabIndexAttr(el) ?? 0, 0);
17309
17598
  return [...stops.filter((el) => positive(el) > 0).sort((a, b) => positive(a) - positive(b)), ...stops.filter((el) => positive(el) === 0)];
17310
17599
  }
@@ -17816,18 +18105,38 @@ var settings = {
17816
18105
  //#endregion
17817
18106
  //#region src/utils/control-edits.ts
17818
18107
  /**
17819
- * Counts the edits a reader made through a control bound to a story
18108
+ * Records the edits a reader made through a control bound to a story
17820
18109
  * variable (`{textbox}`, `{checkbox}`, ...). {for} tells these apart from a
17821
18110
  * list that is replaced: typing into a control inside an iteration changes
17822
18111
  * its item, but must not remount the iteration and drop the control's focus.
18112
+ * An edit is attributed to the variable path it wrote, so a replacement made
18113
+ * elsewhere (a watcher reacting to the edit) still remounts.
17823
18114
  */
17824
18115
  var version = 0;
17825
- function noteControlEdit() {
17826
- version++;
18116
+ var MAX_LOG = 256;
18117
+ var log = [];
18118
+ function noteControlEdit(segments) {
18119
+ log.push({
18120
+ version: ++version,
18121
+ segments
18122
+ });
18123
+ if (log.length > MAX_LOG) log.shift();
17827
18124
  }
17828
18125
  function controlEditVersion() {
17829
18126
  return version;
17830
18127
  }
18128
+ /**
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
+ });
18139
+ }
17831
18140
  //#endregion
17832
18141
  //#region src/action-registry.ts
17833
18142
  /** The fields of a StoryAction that are left out when unset. */
@@ -18073,7 +18382,7 @@ function defineMacro(config, source = "builtin") {
18073
18382
  ctx.value = useStoryStore((s) => getByPath(s.variables, segments));
18074
18383
  ctx.getValue = () => getByPath(readState().variables, segments);
18075
18384
  ctx.setValue = (value) => {
18076
- noteControlEdit();
18385
+ noteControlEdit(segments);
18077
18386
  mutateState((work, adopt) => {
18078
18387
  setByPath(work.variables, segments, adopt(value), { createMissing: true });
18079
18388
  });
@@ -18208,7 +18517,7 @@ function createStoryAPI() {
18208
18517
  const source = scope ?? useStoryStore.getState();
18209
18518
  const namespace = isTransient ? source.transient : source.variables;
18210
18519
  const value = key.includes(".") ? getByPath(namespace, key.split(".")) : ownValue(namespace, key);
18211
- return scope ? frozenCopy(value) : value;
18520
+ return scope ? frozenCopy(value) : readOnlyValue(value);
18212
18521
  },
18213
18522
  set(nameOrVars, value) {
18214
18523
  const entries = typeof nameOrVars === "string" ? [[nameOrVars, value]] : Object.entries(nameOrVars);
@@ -18920,7 +19229,8 @@ var PRIMITIVE_SAMPLES = {
18920
19229
  };
18921
19230
  function inferSchema(value) {
18922
19231
  if (Array.isArray(value)) return { type: "array" };
18923
- if (value !== null && typeof value === "object") {
19232
+ if (value === null) return { type: "null" };
19233
+ if (typeof value === "object") {
18924
19234
  const fields = /* @__PURE__ */ new Map();
18925
19235
  for (const [key, val] of Object.entries(value)) fields.set(key, inferSchema(val));
18926
19236
  return {
@@ -18983,6 +19293,7 @@ function validateRef(ref, schema) {
18983
19293
  for (let i = 1; i < parts.length; i++) {
18984
19294
  const part = parts[i];
18985
19295
  if (current.type === "array") return null;
19296
+ if (current.type === "null") return null;
18986
19297
  const sample = PRIMITIVE_SAMPLES[current.type];
18987
19298
  if (sample && part in sample) {
18988
19299
  const memberType = typeof sample[part];
@@ -19211,23 +19522,31 @@ function assignmentArgs(rawArgs, args) {
19211
19522
  /**
19212
19523
  * Evaluate `expr` against the current values — the store's state and the
19213
19524
  * enclosing locals scope's live values (`getLocals`) — and write the result
19214
- * to the target if it differs from the previous output (always, the first
19525
+ * to the target if it differs from the previous output or the target's value (always, the first
19215
19526
  * time). Reading live values rather than the render's snapshot means the
19216
19527
  * first computation sees a local a preceding {set} just assigned (the scope's
19217
19528
  * context value only catches up on its re-render), and the first computation
19218
19529
  * and later recomputations read the same source.
19219
19530
  */
19220
- function computeAndApply(expr, name, isTemp, isLocal, getLocals, rawArgs, prevRef, localsUpdate) {
19531
+ function computeAndApply(expr, name, isTemp, isLocal, getLocals, rawArgs, prevRef, lastNavigation, localsUpdate) {
19221
19532
  const location = currentSourceLocation();
19222
19533
  let newValue;
19534
+ let current = prevRef.current;
19535
+ let navigated = false;
19536
+ const { navigationId } = useStoryStore.getState();
19223
19537
  try {
19224
19538
  const { variables, temporary, transient } = readState();
19539
+ if (!isLocal) {
19540
+ current = ownValue(isTemp ? temporary : variables, name);
19541
+ navigated = navigationId !== lastNavigation.current;
19542
+ }
19225
19543
  newValue = evaluate(expr, variables, temporary, getLocals(), transient);
19226
19544
  } catch (err) {
19227
19545
  logMacroError(`computed ${rawArgs}`, err, location);
19228
19546
  return;
19229
19547
  }
19230
- if (!deepEqualStrict(prevRef.current, newValue)) {
19548
+ lastNavigation.current = navigationId;
19549
+ if (!deepEqualStrict(prevRef.current, newValue) || navigated && !deepEqualStrict(current, newValue)) {
19231
19550
  prevRef.current = newValue;
19232
19551
  if (isLocal) try {
19233
19552
  localsUpdate(name, newValue);
@@ -19276,7 +19595,8 @@ defineMacro({
19276
19595
  const name = target.slice(1);
19277
19596
  const localsUpdate = isLocal ? ctx.update : null;
19278
19597
  const prevOutput = ctx.hooks.useRef(UNSET);
19279
- const compute = () => computeAndApply(expr, name, isTemp, isLocal, ctx.getValues, rawArgs, prevOutput, localsUpdate);
19598
+ const lastNavigation = ctx.hooks.useRef(-1);
19599
+ const compute = () => computeAndApply(expr, name, isTemp, isLocal, ctx.getValues, rawArgs, prevOutput, lastNavigation, localsUpdate);
19280
19600
  const ran = ctx.hooks.useRef(false);
19281
19601
  if (!ran.current) {
19282
19602
  ran.current = true;
@@ -19539,11 +19859,12 @@ defineMacro({
19539
19859
  });
19540
19860
  }
19541
19861
  const { itemVar, indexVar } = loop;
19542
- const edited = controlEditVersion() !== keys.edits;
19862
+ const since = keys.edits;
19863
+ const { variables } = readState();
19543
19864
  keys.edits = controlEditVersion();
19544
19865
  const generations = list.map((item, i) => {
19545
19866
  const contents = stableKey(item);
19546
- if (keys.generations[i] === void 0 || contents !== keys.contents[i] && !edited) keys.generations[i] = keys.next++;
19867
+ if (keys.generations[i] === void 0 || contents !== keys.contents[i] && !editedItem(since, variables, list, item, i)) keys.generations[i] = keys.next++;
19547
19868
  keys.contents[i] = contents;
19548
19869
  return keys.generations[i];
19549
19870
  });
@@ -19937,12 +20258,13 @@ defineMacro({
19937
20258
  const containerRef = useRef(null);
19938
20259
  const innerRef = useRef(null);
19939
20260
  const [clip, setClip] = useState();
19940
- const [totalChars, setTotalChars] = useState(0);
20261
+ const [measured, setMeasured] = useState(null);
20262
+ const totalChars = measured ?? 0;
19941
20263
  const [visibleChars, setVisibleChars] = useState(0);
19942
20264
  const visibleCharsRef = useRef(0);
19943
20265
  visibleCharsRef.current = visibleChars;
19944
20266
  useEffect(() => {
19945
- if (containerRef.current) setTotalChars((containerRef.current.textContent || "").length);
20267
+ if (containerRef.current) setMeasured((containerRef.current.textContent || "").length);
19946
20268
  });
19947
20269
  useEffect(() => {
19948
20270
  if (totalChars === 0) return;
@@ -19958,7 +20280,7 @@ defineMacro({
19958
20280
  }, speed);
19959
20281
  return () => clearInterval(timer);
19960
20282
  }, [totalChars, speed]);
19961
- const done = visibleChars >= totalChars && totalChars > 0;
20283
+ const done = measured !== null && visibleChars >= totalChars;
19962
20284
  useLayoutEffect(() => {
19963
20285
  const root = containerRef.current;
19964
20286
  const inner = innerRef.current;
@@ -19989,7 +20311,7 @@ defineMacro({
19989
20311
  class: "macro-type-inner",
19990
20312
  style: {
19991
20313
  display: "inline",
19992
- visibility: totalChars === 0 ? "hidden" : "visible",
20314
+ visibility: measured === null ? "hidden" : "visible",
19993
20315
  clipPath: clip
19994
20316
  },
19995
20317
  children: ctx.renderInlineNodes(children)