@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.
- package/dist/pkg/format.js +1 -1
- package/dist/pkg/headless.js +416 -94
- package/dist/pkg/story-variables.js +19 -12
- package/package.json +1 -1
- package/src/class-registry.ts +133 -14
- package/src/components/macros/Computed.tsx +23 -3
- package/src/components/macros/For.tsx +6 -3
- package/src/components/macros/Type.tsx +8 -4
- package/src/components/macros/WidgetInvocation.tsx +7 -2
- package/src/define-macro.ts +1 -1
- package/src/execute-mutation.ts +36 -0
- package/src/hooks/use-modal-focus.ts +67 -17
- package/src/interpolation.ts +25 -5
- package/src/markup/render.tsx +39 -7
- package/src/story-api.ts +2 -1
- package/src/story-variables.ts +12 -2
- package/src/structural.ts +85 -21
- package/src/utils/control-edits.ts +36 -3
- package/src/utils/value-kinds.ts +14 -0
- package/src/widgets/widget-def.ts +27 -15
- package/types/tooling.d.ts +7 -1
package/dist/pkg/headless.js
CHANGED
|
@@ -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
|
-
|
|
10587
|
-
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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 `<
|
|
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([
|
|
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
|
-
|
|
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
|
|
12379
|
+
const children = useContext(WidgetChildrenContext);
|
|
12269
12380
|
const renderOptions = useRenderOptions();
|
|
12270
|
-
if (!
|
|
12271
|
-
return /* @__PURE__ */ jsx(
|
|
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
|
|
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 &&
|
|
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 (!
|
|
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 (
|
|
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
|
|
13504
|
+
throw malformedClass(name);
|
|
13283
13505
|
}
|
|
13284
|
-
const ctor =
|
|
13285
|
-
if (
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
14116
|
-
for (const [k, v] of val)
|
|
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 =
|
|
14121
|
-
for (const v of val)
|
|
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
|
-
|
|
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
|
|
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 =
|
|
14273
|
-
if (keys.length !==
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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)
|
|
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
|
|
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
|
|
17290
|
-
* scripted focus, and of a
|
|
17291
|
-
* member only (the first one
|
|
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 &&
|
|
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) =>
|
|
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
|
-
*
|
|
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
|
-
|
|
17826
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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] && !
|
|
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 [
|
|
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)
|
|
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 =
|
|
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:
|
|
20314
|
+
visibility: measured === null ? "hidden" : "visible",
|
|
19993
20315
|
clipPath: clip
|
|
19994
20316
|
},
|
|
19995
20317
|
children: ctx.renderInlineNodes(children)
|