@rohal12/spindle 0.59.13 → 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 +320 -74
- package/dist/pkg/story-variables.js +17 -11
- package/package.json +1 -1
- package/src/class-registry.ts +133 -14
- package/src/components/macros/WidgetInvocation.tsx +7 -2
- package/src/execute-mutation.ts +10 -0
- package/src/hooks/use-modal-focus.ts +46 -16
- package/src/interpolation.ts +25 -5
- package/src/markup/render.tsx +11 -4
- package/src/structural.ts +85 -21
- package/src/utils/value-kinds.ts +14 -0
- package/src/widgets/widget-def.ts +27 -15
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++) {
|
|
@@ -12282,10 +12376,13 @@ function HtmlNodeRenderer({ node }) {
|
|
|
12282
12376
|
}) : element);
|
|
12283
12377
|
}
|
|
12284
12378
|
function ChildrenSlot() {
|
|
12285
|
-
const
|
|
12379
|
+
const children = useContext(WidgetChildrenContext);
|
|
12286
12380
|
const renderOptions = useRenderOptions();
|
|
12287
|
-
if (!
|
|
12288
|
-
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
|
+
});
|
|
12289
12386
|
}
|
|
12290
12387
|
/**
|
|
12291
12388
|
* Stable per-node keys. Keying rendered children by AST node identity (not
|
|
@@ -13194,6 +13291,39 @@ function stringify_primitive(thing) {
|
|
|
13194
13291
|
return String(thing);
|
|
13195
13292
|
}
|
|
13196
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
|
|
13197
13327
|
//#region src/class-registry.ts
|
|
13198
13328
|
var registry = /* @__PURE__ */ new Map();
|
|
13199
13329
|
var ctorToName = /* @__PURE__ */ new Map();
|
|
@@ -13233,6 +13363,9 @@ var ERROR_HIDDEN_KEYS$1 = [
|
|
|
13233
13363
|
"errors"
|
|
13234
13364
|
];
|
|
13235
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}"`);
|
|
13236
13369
|
var defineData = (target, key, value) => {
|
|
13237
13370
|
Object.defineProperty(target, key, {
|
|
13238
13371
|
value,
|
|
@@ -13252,15 +13385,49 @@ function refuseSymbolKeys(value) {
|
|
|
13252
13385
|
* Own enumerable keys of `value`, as a plain object; for an error also its
|
|
13253
13386
|
* message, cause and (AggregateError) errors, which are not enumerable.
|
|
13254
13387
|
*/
|
|
13255
|
-
function ownData(value) {
|
|
13388
|
+
function ownData(value, keys = Object.keys(value)) {
|
|
13256
13389
|
refuseSymbolKeys(value);
|
|
13257
13390
|
const data = {};
|
|
13258
13391
|
if (value instanceof Error) {
|
|
13259
13392
|
for (const key of ERROR_HIDDEN_KEYS$1) if (hasOwn(value, key)) defineData(data, key, value[key]);
|
|
13260
13393
|
}
|
|
13261
|
-
for (const key of
|
|
13394
|
+
for (const key of keys) defineData(data, key, value[key]);
|
|
13262
13395
|
return data;
|
|
13263
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
|
+
}
|
|
13264
13431
|
/** Make an error's message, cause and errors non-enumerable again. */
|
|
13265
13432
|
function hideErrorKeys(value) {
|
|
13266
13433
|
for (const key of ERROR_HIDDEN_KEYS$1) if (hasOwn(value, key)) Object.defineProperty(value, key, { enumerable: false });
|
|
@@ -13269,7 +13436,7 @@ function hideErrorKeys(value) {
|
|
|
13269
13436
|
var isEscapedOneCharKey = (key) => key.length === 1 && JSON.stringify(key).length > 3;
|
|
13270
13437
|
function reducers() {
|
|
13271
13438
|
const out = {};
|
|
13272
|
-
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);
|
|
13273
13440
|
for (const [proto, name] of BUILTIN_ERRORS) out[ERROR_PREFIX + name] = (v) => isObject(v) && Object.getPrototypeOf(v) === proto && ownData(v);
|
|
13274
13441
|
out[SYMBOL_TAG] = (v) => {
|
|
13275
13442
|
if (typeof v !== "symbol") return false;
|
|
@@ -13282,7 +13449,7 @@ function reducers() {
|
|
|
13282
13449
|
v.lastIndex
|
|
13283
13450
|
];
|
|
13284
13451
|
out[KEYS_TAG] = (v) => {
|
|
13285
|
-
if (!
|
|
13452
|
+
if (!isPlainData(v)) return false;
|
|
13286
13453
|
const keys = Object.keys(v);
|
|
13287
13454
|
if (!keys.some(isEscapedOneCharKey)) return false;
|
|
13288
13455
|
refuseSymbolKeys(v);
|
|
@@ -13291,27 +13458,71 @@ function reducers() {
|
|
|
13291
13458
|
};
|
|
13292
13459
|
return out;
|
|
13293
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
|
+
}
|
|
13294
13498
|
/** Restore a registered class instance in place, so cycles through it hold. */
|
|
13295
13499
|
function reviveClass(name) {
|
|
13296
13500
|
return (data) => {
|
|
13297
|
-
if (
|
|
13501
|
+
if (Array.isArray(data)) return reviveCollection(name, registeredCtor(name), data);
|
|
13502
|
+
if (!isPlainData(data)) {
|
|
13298
13503
|
if (isObject(data) && registeredClassName(data) === name) return data;
|
|
13299
|
-
throw
|
|
13504
|
+
throw malformedClass(name);
|
|
13300
13505
|
}
|
|
13301
|
-
const ctor =
|
|
13302
|
-
if (
|
|
13506
|
+
const ctor = registeredCtor(name);
|
|
13507
|
+
if (Object.values(COLLECTION_KINDS).some((c) => ctor.prototype instanceof c)) throw malformedClass(name);
|
|
13303
13508
|
Object.setPrototypeOf(data, ctor.prototype);
|
|
13304
13509
|
if (data instanceof Error) hideErrorKeys(data);
|
|
13305
13510
|
return data;
|
|
13306
13511
|
};
|
|
13307
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
|
+
}
|
|
13308
13519
|
/** Restore a built-in error in place, like a class instance. */
|
|
13309
13520
|
function reviveError(name) {
|
|
13310
13521
|
const ctor = ERROR_CTORS.get(name);
|
|
13311
13522
|
if (!ctor) return void 0;
|
|
13312
13523
|
return (data) => {
|
|
13313
13524
|
if (Object.getPrototypeOf(data) === ctor.prototype) return data;
|
|
13314
|
-
if (!
|
|
13525
|
+
if (!isPlainData(data)) throw new TypeError(`spindle: Malformed data for "${name}"`);
|
|
13315
13526
|
Object.setPrototypeOf(data, ctor.prototype);
|
|
13316
13527
|
hideErrorKeys(data);
|
|
13317
13528
|
return data;
|
|
@@ -13908,28 +14119,6 @@ function getBackendType() {
|
|
|
13908
14119
|
return _backend?.type ?? "memory";
|
|
13909
14120
|
}
|
|
13910
14121
|
//#endregion
|
|
13911
|
-
//#region src/utils/value-kinds.ts
|
|
13912
|
-
var toStringTag = (value) => Object.prototype.toString.call(value).slice(8, -1);
|
|
13913
|
-
/** Temporal values are immutable: copies may share them. */
|
|
13914
|
-
var isTemporal = (value) => toStringTag(value).startsWith("Temporal.");
|
|
13915
|
-
/** new Number(), new String(), new Boolean(), Object(1n). */
|
|
13916
|
-
var isBoxed = (value) => value instanceof Number || value instanceof String || value instanceof Boolean || toStringTag(value) === "BigInt";
|
|
13917
|
-
/**
|
|
13918
|
-
* Values compared and copied as a whole, by their content: they have no
|
|
13919
|
-
* own keys to merge by, or their keys are not the data (a property written
|
|
13920
|
-
* into one would be dropped by clones and saves, and by Immer for Map and
|
|
13921
|
-
* Set).
|
|
13922
|
-
*/
|
|
13923
|
-
function isAtomic(value) {
|
|
13924
|
-
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);
|
|
13925
|
-
}
|
|
13926
|
-
/** "a Map", "an Error", ... for an atomic value (see isAtomic), else undefined. */
|
|
13927
|
-
function atomicName(value) {
|
|
13928
|
-
if (!isAtomic(value)) return void 0;
|
|
13929
|
-
const tag = value instanceof Error ? "Error" : toStringTag(value);
|
|
13930
|
-
return `${/^[AEIOU]/.test(tag) ? "an" : "a"} ${tag}`;
|
|
13931
|
-
}
|
|
13932
|
-
//#endregion
|
|
13933
14122
|
//#region src/utils/object-path.ts
|
|
13934
14123
|
/**
|
|
13935
14124
|
* Traverse dot-path segments on an object and return the nested value.
|
|
@@ -14058,6 +14247,20 @@ function isPlainObject(value) {
|
|
|
14058
14247
|
const proto = Object.getPrototypeOf(value);
|
|
14059
14248
|
return proto === Object.prototype || proto === null;
|
|
14060
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
|
+
}
|
|
14061
14264
|
/** Own keys of an error that are not enumerable but are data. */
|
|
14062
14265
|
var ERROR_HIDDEN_KEYS = [
|
|
14063
14266
|
"message",
|
|
@@ -14119,23 +14322,25 @@ function deepClone(value, options = {}) {
|
|
|
14119
14322
|
const settle = options.settle;
|
|
14120
14323
|
return settle && isObjectValue(copy) && isObjectValue(original) ? settle(copy, original) : copy;
|
|
14121
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]));
|
|
14122
14327
|
function clone(val) {
|
|
14123
14328
|
if (val === null || typeof val !== "object") return val;
|
|
14124
14329
|
const obj = val;
|
|
14125
14330
|
if (seen.has(obj)) return seen.get(obj);
|
|
14126
14331
|
if (Array.isArray(val)) {
|
|
14127
|
-
const arr =
|
|
14332
|
+
const arr = collection(obj, new Array(val.length));
|
|
14128
14333
|
for (let i = 0; i < val.length; i++) if (i in val) arr[i] = clone(val[i]);
|
|
14129
14334
|
return arr;
|
|
14130
14335
|
}
|
|
14131
14336
|
if (val instanceof Map) {
|
|
14132
|
-
const copy =
|
|
14133
|
-
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));
|
|
14134
14339
|
return copy;
|
|
14135
14340
|
}
|
|
14136
14341
|
if (val instanceof Set) {
|
|
14137
|
-
const copy =
|
|
14138
|
-
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));
|
|
14139
14344
|
return copy;
|
|
14140
14345
|
}
|
|
14141
14346
|
const builtin = cloneBuiltin(obj);
|
|
@@ -14238,23 +14443,26 @@ function shareEqual(prev, curr) {
|
|
|
14238
14443
|
if (placed !== void 0) return placed;
|
|
14239
14444
|
if (!isObjectValue(p)) return fresh(c);
|
|
14240
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);
|
|
14241
14452
|
if (c instanceof Map && p instanceof Map) {
|
|
14242
|
-
const out = /* @__PURE__ */ new Map();
|
|
14243
|
-
|
|
14244
|
-
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));
|
|
14245
14455
|
return out;
|
|
14246
14456
|
}
|
|
14247
14457
|
if (Array.isArray(c) && Array.isArray(p)) {
|
|
14248
|
-
const out = new Array(c.length);
|
|
14249
|
-
done.set(c, out);
|
|
14458
|
+
const out = collection(new Array(c.length));
|
|
14250
14459
|
for (let i = 0; i < c.length; i++) if (i in c) out[i] = share(p[i], c[i]);
|
|
14251
14460
|
return out;
|
|
14252
14461
|
}
|
|
14253
14462
|
if (mergesWith(p, c, false)) {
|
|
14254
14463
|
const out = Object.create(Object.getPrototypeOf(c));
|
|
14255
14464
|
done.set(c, out);
|
|
14256
|
-
const cr
|
|
14257
|
-
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));
|
|
14258
14466
|
return out;
|
|
14259
14467
|
}
|
|
14260
14468
|
return fresh(c);
|
|
@@ -14283,14 +14491,27 @@ function equalBuiltin(a, b, assumed) {
|
|
|
14283
14491
|
if (isTemporal(a)) return String(a) === String(b);
|
|
14284
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);
|
|
14285
14493
|
}
|
|
14286
|
-
function equalKeys(a, b, assumed) {
|
|
14494
|
+
function equalKeys(a, b, assumed, keysOf = Object.keys) {
|
|
14287
14495
|
const ao = a;
|
|
14288
14496
|
const bo = b;
|
|
14289
|
-
const keys =
|
|
14290
|
-
if (keys.length !==
|
|
14497
|
+
const keys = keysOf(ao);
|
|
14498
|
+
if (keys.length !== keysOf(bo).length) return false;
|
|
14291
14499
|
for (const key of keys) if (!hasOwn(bo, key) || !equal(ao[key], bo[key], assumed)) return false;
|
|
14292
14500
|
return true;
|
|
14293
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
|
+
}
|
|
14294
14515
|
/** `assumed` tracks the pairs of objects met (see Pairs). */
|
|
14295
14516
|
function equal(a, b, assumed) {
|
|
14296
14517
|
if (Object.is(a, b) && !(assumed.strict && isObjectValue(a))) return true;
|
|
@@ -14304,7 +14525,7 @@ function equal(a, b, assumed) {
|
|
|
14304
14525
|
const ai = a.entries();
|
|
14305
14526
|
const bi = bc.entries();
|
|
14306
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;
|
|
14307
|
-
return
|
|
14528
|
+
return equalExtraKeys(a, b, assumed);
|
|
14308
14529
|
}
|
|
14309
14530
|
const builtin = equalBuiltin(a, b, assumed);
|
|
14310
14531
|
if (builtin !== void 0) return builtin;
|
|
@@ -14312,7 +14533,7 @@ function equal(a, b, assumed) {
|
|
|
14312
14533
|
const bc = b;
|
|
14313
14534
|
if (a.length !== bc.length) return false;
|
|
14314
14535
|
for (let i = 0; i < a.length; i++) if (i in a !== i in bc || !equal(a[i], bc[i], assumed)) return false;
|
|
14315
|
-
return
|
|
14536
|
+
return equalExtraKeys(a, b, assumed);
|
|
14316
14537
|
}
|
|
14317
14538
|
return equalKeys(a, b, assumed);
|
|
14318
14539
|
}
|
|
@@ -14331,7 +14552,7 @@ function isMergeable(value) {
|
|
|
14331
14552
|
* one class (see isMergeable), or, with `arrays`, both are arrays.
|
|
14332
14553
|
*/
|
|
14333
14554
|
function mergesWith(a, b, arrays) {
|
|
14334
|
-
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);
|
|
14335
14556
|
return isMergeable(a) && isMergeable(b) && Object.getPrototypeOf(a) === Object.getPrototypeOf(b);
|
|
14336
14557
|
}
|
|
14337
14558
|
/**
|
|
@@ -15605,6 +15826,11 @@ function deeplyFrozen(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
15605
15826
|
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return true;
|
|
15606
15827
|
if (!Object.isFrozen(value)) return false;
|
|
15607
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
|
+
}
|
|
15608
15834
|
return Reflect.ownKeys(value).every((key) => {
|
|
15609
15835
|
const desc = Object.getOwnPropertyDescriptor(value, key);
|
|
15610
15836
|
return !desc || !("value" in desc) || deeplyFrozen(desc.value, seen);
|
|
@@ -17307,15 +17533,34 @@ var FOCUSABLE = [
|
|
|
17307
17533
|
"[tabindex]"
|
|
17308
17534
|
].join(",");
|
|
17309
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
|
+
/**
|
|
17310
17547
|
* Whether CSS leaves `el` in the tab sequence: not `visibility: hidden`
|
|
17311
|
-
* (inherited, so the computed value covers ancestors)
|
|
17312
|
-
* `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>`.
|
|
17313
17551
|
*/
|
|
17314
17552
|
function isRendered(el) {
|
|
17315
17553
|
const { visibility } = getComputedStyle(el);
|
|
17316
17554
|
if (visibility === "hidden" || visibility === "collapse") return false;
|
|
17317
17555
|
for (let e = el; e; e = e.parentElement) if (getComputedStyle(e).display === "none") return false;
|
|
17318
|
-
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);
|
|
17319
17564
|
}
|
|
17320
17565
|
/** The `tabindex` of `el` as an integer, or null when absent or invalid. */
|
|
17321
17566
|
function tabIndexAttr(el) {
|
|
@@ -17324,9 +17569,10 @@ function tabIndexAttr(el) {
|
|
|
17324
17569
|
return parseInt(raw, 10);
|
|
17325
17570
|
}
|
|
17326
17571
|
/**
|
|
17327
|
-
* Whether the tab key can reach `el
|
|
17328
|
-
* scripted focus, and of a
|
|
17329
|
-
* 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.
|
|
17330
17576
|
*/
|
|
17331
17577
|
function isTabStop(el) {
|
|
17332
17578
|
const tabIndex = tabIndexAttr(el);
|
|
@@ -17337,7 +17583,7 @@ function isTabStop(el) {
|
|
|
17337
17583
|
if (details?.localName !== "details" || Array.from(details.children).find((c) => c.localName === "summary") !== el) return tabIndex !== null;
|
|
17338
17584
|
}
|
|
17339
17585
|
if (el instanceof HTMLInputElement && el.type === "radio" && el.name) {
|
|
17340
|
-
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));
|
|
17341
17587
|
return el === (group.find((r) => r.checked) ?? group[0]);
|
|
17342
17588
|
}
|
|
17343
17589
|
return true;
|
|
@@ -17347,7 +17593,7 @@ function isTabStop(el) {
|
|
|
17347
17593
|
* first, ascending, then the others in document order.
|
|
17348
17594
|
*/
|
|
17349
17595
|
function focusables(root) {
|
|
17350
|
-
const stops = Array.from(root.querySelectorAll(FOCUSABLE)).filter((el) =>
|
|
17596
|
+
const stops = Array.from(root.querySelectorAll(FOCUSABLE)).filter((el) => isAvailable(el) && isTabStop(el));
|
|
17351
17597
|
const positive = (el) => Math.max(tabIndexAttr(el) ?? 0, 0);
|
|
17352
17598
|
return [...stops.filter((el) => positive(el) > 0).sort((a, b) => positive(a) - positive(b)), ...stops.filter((el) => positive(el) === 0)];
|
|
17353
17599
|
}
|