@rohal12/spindle 0.59.4 → 0.59.6
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 +342 -118
- package/package.json +1 -1
- package/src/components/macros/Unset.tsx +5 -1
- package/src/define-macro.ts +6 -3
- package/src/execute-mutation.ts +93 -39
- package/src/store.ts +82 -35
- package/src/story-api.ts +2 -2
- package/src/structural.ts +309 -77
- package/src/utils/object-path.ts +6 -3
package/dist/pkg/headless.js
CHANGED
|
@@ -13244,13 +13244,14 @@ function atomicName(value) {
|
|
|
13244
13244
|
* Members of Object.prototype (`constructor`, `toString`, `__proto__`, ...)
|
|
13245
13245
|
* are not story state: unless an object holds one as its own property, it
|
|
13246
13246
|
* reads as missing, as setByPath() treats it. Other inherited properties
|
|
13247
|
-
* (class getters, `size` of a Map) are read
|
|
13247
|
+
* (class getters, `size` of a Map) are read, and so are those of a string,
|
|
13248
|
+
* number or boolean (`length` of a string), as JavaScript boxes them.
|
|
13248
13249
|
*/
|
|
13249
13250
|
function getByPath(obj, segments) {
|
|
13250
13251
|
let current = obj;
|
|
13251
13252
|
for (const seg of segments) {
|
|
13252
|
-
if (current == null
|
|
13253
|
-
if (seg in Object.prototype && !hasOwn(current, seg)) return
|
|
13253
|
+
if (current == null) return void 0;
|
|
13254
|
+
if (seg in Object.prototype && !hasOwn(Object(current), seg)) return;
|
|
13254
13255
|
current = current[seg];
|
|
13255
13256
|
}
|
|
13256
13257
|
return current;
|
|
@@ -13407,14 +13408,21 @@ function deepClone(value, options = {}) {
|
|
|
13407
13408
|
if (isTemporal(val)) return val;
|
|
13408
13409
|
if (val instanceof Error) {
|
|
13409
13410
|
const copy = keep(val, Object.create(Object.getPrototypeOf(val)));
|
|
13410
|
-
for (const key of [...ERROR_HIDDEN_KEYS, "stack"]) if (hasOwn(val, key))
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13411
|
+
for (const key of [...ERROR_HIDDEN_KEYS, "stack"]) if (hasOwn(val, key)) {
|
|
13412
|
+
const held = val[key];
|
|
13413
|
+
Object.defineProperty(copy, key, {
|
|
13414
|
+
value: settled(clone(held), held),
|
|
13415
|
+
writable: true,
|
|
13416
|
+
configurable: true
|
|
13417
|
+
});
|
|
13418
|
+
}
|
|
13415
13419
|
return copyKeys(val, copy);
|
|
13416
13420
|
}
|
|
13417
13421
|
}
|
|
13422
|
+
function settled(copy, original) {
|
|
13423
|
+
const settle = options.settle;
|
|
13424
|
+
return settle && isObjectValue(copy) && isObjectValue(original) ? settle(copy, original) : copy;
|
|
13425
|
+
}
|
|
13418
13426
|
function clone(val) {
|
|
13419
13427
|
if (val === null || typeof val !== "object") return val;
|
|
13420
13428
|
const obj = val;
|
|
@@ -13426,12 +13434,12 @@ function deepClone(value, options = {}) {
|
|
|
13426
13434
|
}
|
|
13427
13435
|
if (val instanceof Map) {
|
|
13428
13436
|
const copy = keep(obj, /* @__PURE__ */ new Map());
|
|
13429
|
-
for (const [k, v] of val) copy.set(clone(k), clone(v));
|
|
13437
|
+
for (const [k, v] of val) copy.set(settled(clone(k), k), clone(v));
|
|
13430
13438
|
return copy;
|
|
13431
13439
|
}
|
|
13432
13440
|
if (val instanceof Set) {
|
|
13433
13441
|
const copy = keep(obj, /* @__PURE__ */ new Set());
|
|
13434
|
-
for (const v of val) copy.add(clone(v));
|
|
13442
|
+
for (const v of val) copy.add(settled(clone(v), v));
|
|
13435
13443
|
return copy;
|
|
13436
13444
|
}
|
|
13437
13445
|
const builtin = cloneBuiltin(obj);
|
|
@@ -13631,7 +13639,9 @@ function mergesWith(a, b, arrays) {
|
|
|
13631
13639
|
* is none either. Values that merge are reported as 'nested', to be
|
|
13632
13640
|
* compared key by key; they may still be equal. With `arrays`, arrays
|
|
13633
13641
|
* merge too: elements at the indices both hold are compared, and the
|
|
13634
|
-
* elements one has beyond the other's length are added or deleted.
|
|
13642
|
+
* elements one has beyond the other's length are added or deleted. An
|
|
13643
|
+
* index that holds no element (a hole) is no element, not an undefined one,
|
|
13644
|
+
* and holes the length gained at the end are the change of `length`, by how many.
|
|
13635
13645
|
*/
|
|
13636
13646
|
function keyChanges(before, after, arrays) {
|
|
13637
13647
|
const changes = [];
|
|
@@ -13652,16 +13662,41 @@ function keyChanges(before, after, arrays) {
|
|
|
13652
13662
|
if (Array.isArray(after)) {
|
|
13653
13663
|
const b = before;
|
|
13654
13664
|
const a = after;
|
|
13655
|
-
for (let i = 0; i < Math.min(b.length, a.length); i++)
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13665
|
+
for (let i = 0; i < Math.min(b.length, a.length); i++) {
|
|
13666
|
+
const key = String(i);
|
|
13667
|
+
if (i in b && i in a) compare(key, b[i], a[i]);
|
|
13668
|
+
else if (i in b) changes.push({
|
|
13669
|
+
key,
|
|
13670
|
+
kind: "deleted",
|
|
13671
|
+
hole: true
|
|
13672
|
+
});
|
|
13673
|
+
else if (i in a) changes.push({
|
|
13674
|
+
key,
|
|
13675
|
+
kind: "added",
|
|
13676
|
+
after: a[i]
|
|
13677
|
+
});
|
|
13678
|
+
}
|
|
13679
|
+
let next = b.length;
|
|
13680
|
+
for (let i = b.length; i < a.length; i++) {
|
|
13681
|
+
if (!(i in a)) continue;
|
|
13682
|
+
const gap = i - next;
|
|
13683
|
+
changes.push({
|
|
13684
|
+
key: String(i),
|
|
13685
|
+
kind: "added",
|
|
13686
|
+
after: a[i],
|
|
13687
|
+
...gap ? { gap } : {}
|
|
13688
|
+
});
|
|
13689
|
+
next = i + 1;
|
|
13690
|
+
}
|
|
13661
13691
|
for (let i = a.length; i < b.length; i++) changes.push({
|
|
13662
13692
|
key: String(i),
|
|
13663
13693
|
kind: "deleted"
|
|
13664
13694
|
});
|
|
13695
|
+
if (a.length > next) changes.push({
|
|
13696
|
+
key: "length",
|
|
13697
|
+
kind: "holes",
|
|
13698
|
+
after: a.length - next
|
|
13699
|
+
});
|
|
13665
13700
|
return changes;
|
|
13666
13701
|
}
|
|
13667
13702
|
for (const key of Object.keys(after)) if (hasOwn(before, key)) compare(key, before[key], after[key]);
|
|
@@ -13679,26 +13714,45 @@ function keyChanges(before, after, arrays) {
|
|
|
13679
13714
|
/**
|
|
13680
13715
|
* The property paths where `after` differs from `before`, two objects of
|
|
13681
13716
|
* one class: objects that merge (see isMergeable) are compared property by
|
|
13682
|
-
* property, any other value as a whole
|
|
13717
|
+
* property, any other value as a whole; with `arrays`, arrays are compared
|
|
13718
|
+
* by index too (see keyChanges).
|
|
13683
13719
|
*/
|
|
13684
|
-
function diffPaths(before, after) {
|
|
13720
|
+
function diffPaths(before, after, arrays = false) {
|
|
13685
13721
|
const changes = [];
|
|
13686
13722
|
const ancestors = /* @__PURE__ */ new Set();
|
|
13723
|
+
const unchanged = /* @__PURE__ */ new WeakMap();
|
|
13724
|
+
let cuts = 0;
|
|
13687
13725
|
(function walk(b, a, path) {
|
|
13688
|
-
if (ancestors.has(a))
|
|
13726
|
+
if (ancestors.has(a)) {
|
|
13727
|
+
cuts++;
|
|
13728
|
+
return;
|
|
13729
|
+
}
|
|
13730
|
+
if (unchanged.get(a)?.has(b)) return;
|
|
13689
13731
|
ancestors.add(a);
|
|
13690
|
-
|
|
13732
|
+
const found = changes.length;
|
|
13733
|
+
const cutsBefore = cuts;
|
|
13734
|
+
for (const change of keyChanges(b, a, arrays)) {
|
|
13691
13735
|
const at = [...path, change.key];
|
|
13692
13736
|
if (change.kind === "nested") walk(change.before, change.after, at);
|
|
13693
13737
|
else changes.push(change.kind === "deleted" ? {
|
|
13694
13738
|
path: at,
|
|
13695
|
-
deleted: true
|
|
13739
|
+
deleted: true,
|
|
13740
|
+
...change.hole ? { hole: true } : {}
|
|
13696
13741
|
} : {
|
|
13697
13742
|
path: at,
|
|
13698
13743
|
deleted: false,
|
|
13699
|
-
value: change.after
|
|
13744
|
+
value: change.after,
|
|
13745
|
+
...change.kind === "added" && Array.isArray(a) && Number(change.key) >= b.length ? {
|
|
13746
|
+
appended: true,
|
|
13747
|
+
...change.gap ? { gap: change.gap } : {}
|
|
13748
|
+
} : {}
|
|
13700
13749
|
});
|
|
13701
13750
|
}
|
|
13751
|
+
if (changes.length === found && cuts === cutsBefore) {
|
|
13752
|
+
const seen = unchanged.get(a) ?? /* @__PURE__ */ new Set();
|
|
13753
|
+
seen.add(b);
|
|
13754
|
+
unchanged.set(a, seen);
|
|
13755
|
+
}
|
|
13702
13756
|
ancestors.delete(a);
|
|
13703
13757
|
})(before, after, []);
|
|
13704
13758
|
return changes;
|
|
@@ -13710,34 +13764,103 @@ function underChange(path, keys) {
|
|
|
13710
13764
|
return false;
|
|
13711
13765
|
}
|
|
13712
13766
|
/**
|
|
13767
|
+
* The segment standing for the entry `index` of a Map or Set in a path, or
|
|
13768
|
+
* for the `cause` or `errors` of an Error ('c', 'e').
|
|
13769
|
+
*/
|
|
13770
|
+
var entryKey = (kind, index) => `\0${kind}${index}`;
|
|
13771
|
+
var isEntryKey = (segment) => segment.startsWith("\0");
|
|
13772
|
+
/**
|
|
13773
|
+
* What a Map, Set or Error holds in place of properties (keys and values,
|
|
13774
|
+
* members, `cause` and `errors`), each with the segment standing for it in
|
|
13775
|
+
* a path (see entryKey); undefined for any other value.
|
|
13776
|
+
*/
|
|
13777
|
+
function entryChildren(value) {
|
|
13778
|
+
const children = [];
|
|
13779
|
+
if (value instanceof Map) {
|
|
13780
|
+
let index = 0;
|
|
13781
|
+
for (const [k, v] of value) {
|
|
13782
|
+
children.push([entryKey("k", index), k], [entryKey("v", index), v]);
|
|
13783
|
+
index++;
|
|
13784
|
+
}
|
|
13785
|
+
} else if (value instanceof Set) {
|
|
13786
|
+
let index = 0;
|
|
13787
|
+
for (const member of value) children.push([entryKey("s", index++), member]);
|
|
13788
|
+
} else if (value instanceof Error) {
|
|
13789
|
+
for (const key of ["cause", "errors"]) if (hasOwn(value, key)) children.push([entryKey(key === "cause" ? "c" : "e", 0), value[key]]);
|
|
13790
|
+
} else return;
|
|
13791
|
+
return children;
|
|
13792
|
+
}
|
|
13793
|
+
/**
|
|
13794
|
+
* The value at `path` below `root`, where a path may go through the entries
|
|
13795
|
+
* of Maps, Sets and Errors (see objectPaths) as well as properties.
|
|
13796
|
+
*/
|
|
13797
|
+
function getByEntryPath(root, path) {
|
|
13798
|
+
let current = root;
|
|
13799
|
+
let start = 0;
|
|
13800
|
+
for (let i = 0; i < path.length; i++) {
|
|
13801
|
+
if (!isEntryKey(path[i])) continue;
|
|
13802
|
+
current = getByPath(current, path.slice(start, i));
|
|
13803
|
+
if (!isObjectValue(current)) return void 0;
|
|
13804
|
+
current = entryChildren(current)?.find(([at]) => at === path[i])?.[1];
|
|
13805
|
+
start = i + 1;
|
|
13806
|
+
}
|
|
13807
|
+
return start === path.length ? current : getByPath(current, path.slice(start));
|
|
13808
|
+
}
|
|
13809
|
+
/**
|
|
13810
|
+
* Write `value` at the path of an object a Map holds as a value (see
|
|
13811
|
+
* objectPaths); other entries are not replaced, as a key or member cannot
|
|
13812
|
+
* change without the entry moving. Returns whether it wrote.
|
|
13813
|
+
*/
|
|
13814
|
+
function setEntryValue(root, path, value) {
|
|
13815
|
+
const holder = getByEntryPath(root, path.slice(0, -1));
|
|
13816
|
+
const match = /^\0v(\d+)$/.exec(path[path.length - 1]);
|
|
13817
|
+
if (!(holder instanceof Map) || !match) return false;
|
|
13818
|
+
const key = [...holder.keys()][Number(match[1])];
|
|
13819
|
+
holder.set(key, value);
|
|
13820
|
+
return true;
|
|
13821
|
+
}
|
|
13822
|
+
/**
|
|
13713
13823
|
* Every path each object (or array, or other value) of `root` is at, the
|
|
13714
13824
|
* first appearance first, in depth-first order. Plain objects and arrays are
|
|
13715
13825
|
* entered, where first met only (an object held in an array is one object,
|
|
13716
13826
|
* however it was reached); other values are leaves. A path at or below one
|
|
13717
13827
|
* of `skip` is left out.
|
|
13828
|
+
*
|
|
13829
|
+
* With `entries`, the objects held in a Map or Set (as key, value or member)
|
|
13830
|
+
* are placed too, after everything else: one met nowhere else is at the path
|
|
13831
|
+
* of its Map or Set and the entry's segment (see entryKey), which is no path
|
|
13832
|
+
* a value can be read at (see getByEntryPath), so only to tell where an
|
|
13833
|
+
* object is from; so are the `cause` and `errors` of an Error.
|
|
13718
13834
|
*/
|
|
13719
|
-
function objectPaths(root, skip = /* @__PURE__ */ new Set()) {
|
|
13835
|
+
function objectPaths(root, skip = /* @__PURE__ */ new Set(), entries = false) {
|
|
13720
13836
|
const all = /* @__PURE__ */ new Map();
|
|
13721
|
-
|
|
13837
|
+
const collections = [];
|
|
13838
|
+
function place(value, at) {
|
|
13839
|
+
const known = all.get(value);
|
|
13840
|
+
if (known) {
|
|
13841
|
+
known.push(at);
|
|
13842
|
+
return;
|
|
13843
|
+
}
|
|
13844
|
+
all.set(value, [at]);
|
|
13845
|
+
if (isMergeable(value) || Array.isArray(value)) walk(value, at);
|
|
13846
|
+
else if (entries && entryChildren(value)) collections.push([value, at]);
|
|
13847
|
+
}
|
|
13848
|
+
function walk(node, path) {
|
|
13722
13849
|
for (const key of Object.keys(node)) {
|
|
13723
13850
|
const value = node[key];
|
|
13724
13851
|
if (!isObjectValue(value)) continue;
|
|
13725
13852
|
const at = [...path, key];
|
|
13726
13853
|
if (skip.has(pathKey(at))) continue;
|
|
13727
|
-
|
|
13728
|
-
if (known) {
|
|
13729
|
-
known.push(at);
|
|
13730
|
-
continue;
|
|
13731
|
-
}
|
|
13732
|
-
all.set(value, [at]);
|
|
13733
|
-
if (isMergeable(value) || Array.isArray(value)) walk(value, at);
|
|
13854
|
+
place(value, at);
|
|
13734
13855
|
}
|
|
13735
|
-
}
|
|
13856
|
+
}
|
|
13857
|
+
walk(root, []);
|
|
13858
|
+
for (const [collection, path] of collections) for (const [segment, value] of entryChildren(collection)) if (isObjectValue(value)) place(value, [...path, segment]);
|
|
13736
13859
|
return all;
|
|
13737
13860
|
}
|
|
13738
13861
|
/** Where each object of `root` first appears (see objectPaths). */
|
|
13739
|
-
function firstPaths(root, skip) {
|
|
13740
|
-
return new Map([...objectPaths(root, skip)].map(([object, paths]) => [object, paths[0]]));
|
|
13862
|
+
function firstPaths(root, skip, entries) {
|
|
13863
|
+
return new Map([...objectPaths(root, skip, entries)].map(([object, paths]) => [object, paths[0]]));
|
|
13741
13864
|
}
|
|
13742
13865
|
/**
|
|
13743
13866
|
* The objects `root` holds at more than one path (an object two variables
|
|
@@ -13745,7 +13868,44 @@ function firstPaths(root, skip) {
|
|
|
13745
13868
|
* parents before children.
|
|
13746
13869
|
*/
|
|
13747
13870
|
function sharedPaths(root) {
|
|
13748
|
-
return [...objectPaths(root).values()].filter((paths) => paths.length > 1);
|
|
13871
|
+
return [...objectPaths(root, void 0, true).values()].filter((paths) => paths.length > 1);
|
|
13872
|
+
}
|
|
13873
|
+
/**
|
|
13874
|
+
* Whether `y`, which takes the place of `x` at `at` (the first appearance of
|
|
13875
|
+
* both), holds an object elsewhere than `x` did, below an entry of a Map, Set
|
|
13876
|
+
* or Error (see aliasChanges): `$map.set("k", $a)` over an equal object, or
|
|
13877
|
+
* `$map.get("k").child = $a`. Entries are compared by position, and only
|
|
13878
|
+
* where both hold as many; other differences are changes of content. Below
|
|
13879
|
+
* an entry, objects that merge are compared key by key.
|
|
13880
|
+
*/
|
|
13881
|
+
function aliasMoved(x, y, at, earlier, later) {
|
|
13882
|
+
const pairs = [];
|
|
13883
|
+
const xs = entryChildren(x);
|
|
13884
|
+
const ys = entryChildren(y);
|
|
13885
|
+
if (xs && ys) {
|
|
13886
|
+
if (Object.getPrototypeOf(x) !== Object.getPrototypeOf(y)) return false;
|
|
13887
|
+
if (x instanceof Map || x instanceof Set) {
|
|
13888
|
+
if (x.size !== y.size) return false;
|
|
13889
|
+
}
|
|
13890
|
+
const held = new Map(ys);
|
|
13891
|
+
for (const [segment, a] of xs) if (held.has(segment)) pairs.push([
|
|
13892
|
+
segment,
|
|
13893
|
+
a,
|
|
13894
|
+
held.get(segment)
|
|
13895
|
+
]);
|
|
13896
|
+
} else if ((isMergeable(x) || Array.isArray(x)) && mergesWith(x, y, true)) {
|
|
13897
|
+
for (const key of Object.keys(y)) if (hasOwn(x, key)) pairs.push([
|
|
13898
|
+
key,
|
|
13899
|
+
x[key],
|
|
13900
|
+
y[key]
|
|
13901
|
+
]);
|
|
13902
|
+
}
|
|
13903
|
+
return pairs.some(([segment, a, b]) => {
|
|
13904
|
+
if (!isObjectValue(a) || !isObjectValue(b)) return false;
|
|
13905
|
+
const here = [...at, segment];
|
|
13906
|
+
const isAt = later.get(b);
|
|
13907
|
+
return pathKey(earlier.get(a)) !== pathKey(isAt) || pathKey(isAt) === pathKey(here) && aliasMoved(a, b, here, earlier, later);
|
|
13908
|
+
});
|
|
13749
13909
|
}
|
|
13750
13910
|
/**
|
|
13751
13911
|
* The paths whose object is another one in the references of `after` than in
|
|
@@ -13756,8 +13916,8 @@ function sharedPaths(root) {
|
|
|
13756
13916
|
* `after` holds.
|
|
13757
13917
|
*/
|
|
13758
13918
|
function aliasChanges(before, after) {
|
|
13759
|
-
const earlier = firstPaths(before);
|
|
13760
|
-
const later = firstPaths(after);
|
|
13919
|
+
const earlier = firstPaths(before, void 0, true);
|
|
13920
|
+
const later = firstPaths(after, void 0, true);
|
|
13761
13921
|
const changes = [];
|
|
13762
13922
|
(function walk(b, a, path) {
|
|
13763
13923
|
for (const key of Object.keys(a)) {
|
|
@@ -13775,17 +13935,41 @@ function aliasChanges(before, after) {
|
|
|
13775
13935
|
value: y,
|
|
13776
13936
|
alias: true
|
|
13777
13937
|
});
|
|
13938
|
+
else if (pathKey(isAt) === k && entryChildren(x) && aliasMoved(x, y, at, earlier, later)) changes.push({
|
|
13939
|
+
path: at,
|
|
13940
|
+
deleted: false,
|
|
13941
|
+
value: y,
|
|
13942
|
+
alias: true
|
|
13943
|
+
});
|
|
13778
13944
|
if ((isMergeable(x) || Array.isArray(x)) && mergesWith(x, y, true) && pathKey(isAt) === k && pathKey(wasAt) === k) walk(x, y, at);
|
|
13779
13945
|
}
|
|
13780
13946
|
})(before, after, []);
|
|
13781
13947
|
return changes;
|
|
13782
13948
|
}
|
|
13783
13949
|
/**
|
|
13950
|
+
* The changes from `before` to `after`, as paths below them: the property
|
|
13951
|
+
* paths that differ (see diffPaths), and those that hold equal content but
|
|
13952
|
+
* another object than they did (see aliasChanges), those first. Compared as
|
|
13953
|
+
* one object, so an object that two of their keys share is one object,
|
|
13954
|
+
* whichever of them it is reached through.
|
|
13955
|
+
*/
|
|
13956
|
+
function changesBetween(before, after, arrays = false) {
|
|
13957
|
+
const written = diffPaths(before, after, arrays);
|
|
13958
|
+
const keys = new Set(written.map((c) => pathKey(c.path)));
|
|
13959
|
+
const aliases = [];
|
|
13960
|
+
for (const change of aliasChanges(before, after)) {
|
|
13961
|
+
if (underChange(change.path, keys)) continue;
|
|
13962
|
+
keys.add(pathKey(change.path));
|
|
13963
|
+
aliases.push(change);
|
|
13964
|
+
}
|
|
13965
|
+
return [...aliases, ...written];
|
|
13966
|
+
}
|
|
13967
|
+
/**
|
|
13784
13968
|
* Where the objects of `source` are, other than at or below the paths of
|
|
13785
13969
|
* `changes` (which write something new there), for `existingObjects`.
|
|
13786
13970
|
*/
|
|
13787
13971
|
function locateObjects(source, changes) {
|
|
13788
|
-
return firstPaths(source, new Set(changes.map((c) => pathKey(c.path))));
|
|
13972
|
+
return firstPaths(source, new Set(changes.map((c) => pathKey(c.path))), true);
|
|
13789
13973
|
}
|
|
13790
13974
|
/**
|
|
13791
13975
|
* A map for `deepClone`'s `seen`: an object of the source `paths` locate
|
|
@@ -13800,7 +13984,7 @@ function existingObjects(paths, target) {
|
|
|
13800
13984
|
if (super.has(obj)) return true;
|
|
13801
13985
|
const path = paths.get(obj);
|
|
13802
13986
|
if (!path) return false;
|
|
13803
|
-
const found =
|
|
13987
|
+
const found = getByEntryPath(target, path);
|
|
13804
13988
|
if (!(isObjectValue(found) && (Array.isArray(found) ? Array.isArray(obj) : Object.getPrototypeOf(found) === Object.getPrototypeOf(obj)))) return false;
|
|
13805
13989
|
super.set(obj, found);
|
|
13806
13990
|
return true;
|
|
@@ -13815,32 +13999,20 @@ function isApplied(target, change) {
|
|
|
13815
13999
|
if (!hasOwn(parent, key)) return change.deleted;
|
|
13816
14000
|
return !change.deleted && deepEqual(parent[key], change.value);
|
|
13817
14001
|
}
|
|
14002
|
+
/**
|
|
14003
|
+
* Make `path` below `root` hold `object` (an object that `sharedPaths` found
|
|
14004
|
+
* at several paths): a property is written like setByPath() does, a Map's
|
|
14005
|
+
* value replaced; an entry that is a key or member is left as it is.
|
|
14006
|
+
*/
|
|
14007
|
+
function relinkPath(root, path, object) {
|
|
14008
|
+
if (path.some(isEntryKey)) setEntryValue(root, path, object);
|
|
14009
|
+
else setByPath(root, path, object);
|
|
14010
|
+
}
|
|
13818
14011
|
function applyChange(target, change) {
|
|
13819
14012
|
const root = target;
|
|
13820
14013
|
if (change.deleted) deleteByPath(root, change.path);
|
|
13821
14014
|
else setByPath(root, change.path, change.value);
|
|
13822
14015
|
}
|
|
13823
|
-
/**
|
|
13824
|
-
* Write how `after` differs from `before` (see keyChanges; arrays merge by
|
|
13825
|
-
* index) into `target`, another version of the same object or array.
|
|
13826
|
-
* Values that changed are written whole, as deep copies; for values that
|
|
13827
|
-
* merge, `mergeNested` may merge them into `target` instead, returning
|
|
13828
|
-
* whether it did. Into an array, elements removed from the end are removed
|
|
13829
|
-
* at the same indices and elements added are appended, and changes at
|
|
13830
|
-
* indices `target` lacks are dropped: where `target` was resized, its
|
|
13831
|
-
* indices do not line up with `before`'s.
|
|
13832
|
-
*/
|
|
13833
|
-
function mergeKeys(target, before, after, mergeNested) {
|
|
13834
|
-
const list = Array.isArray(target) ? target : void 0;
|
|
13835
|
-
for (const change of keyChanges(before, after, true)) {
|
|
13836
|
-
const { key } = change;
|
|
13837
|
-
if (change.kind === "deleted") if (list) list.length = Math.min(list.length, Number(key));
|
|
13838
|
-
else delete target[key];
|
|
13839
|
-
else if (list && change.kind === "added") list.push(deepClone(change.after));
|
|
13840
|
-
else if (list && Number(key) >= list.length) continue;
|
|
13841
|
-
else if (change.kind !== "nested" || !(mergeNested?.(key, change.before, change.after) || deepEqual(change.before, change.after))) target[key] = deepClone(change.after);
|
|
13842
|
-
}
|
|
13843
|
-
}
|
|
13844
14016
|
//#endregion
|
|
13845
14017
|
//#region src/event-emitter.ts
|
|
13846
14018
|
var VALID_EVENTS = new Set([
|
|
@@ -14600,9 +14772,18 @@ var cloneValue = (value) => deepClone(value, { keepUnregistered: true });
|
|
|
14600
14772
|
*/
|
|
14601
14773
|
function clonerFor(target, paths) {
|
|
14602
14774
|
const seen = paths ? existingObjects(paths, target) : /* @__PURE__ */ new Map();
|
|
14775
|
+
const settle = (copy, original) => {
|
|
14776
|
+
const path = paths?.get(original);
|
|
14777
|
+
if (!path || !isDraft(copy)) return copy;
|
|
14778
|
+
const plain = current(copy);
|
|
14779
|
+
if (!path.some(isEntryKey)) setByPath(target, path, plain);
|
|
14780
|
+
seen.set(original, plain);
|
|
14781
|
+
return plain;
|
|
14782
|
+
};
|
|
14603
14783
|
return (value) => deepClone(value, {
|
|
14604
14784
|
keepUnregistered: true,
|
|
14605
|
-
seen
|
|
14785
|
+
seen,
|
|
14786
|
+
settle
|
|
14606
14787
|
});
|
|
14607
14788
|
}
|
|
14608
14789
|
/** One cloner per target copy, made when first used. */
|
|
@@ -14751,7 +14932,8 @@ function routeStoreUpdate(recipe) {
|
|
|
14751
14932
|
path: [ns, ...change.path]
|
|
14752
14933
|
})) : [];
|
|
14753
14934
|
});
|
|
14754
|
-
const
|
|
14935
|
+
const held = locateObjects(namespacesOf(inner.work), changes);
|
|
14936
|
+
const cloners = clonersFor(() => held);
|
|
14755
14937
|
const owned = (change, target) => change.deleted ? change : {
|
|
14756
14938
|
...change,
|
|
14757
14939
|
value: cloners(target)(change.value)
|
|
@@ -14788,9 +14970,9 @@ function relink(draft, work, changes) {
|
|
|
14788
14970
|
const prefixes = [first, ...others].map((p) => pathKey(p).slice(0, -1));
|
|
14789
14971
|
if (!written.some((w) => prefixes.some((p) => w.startsWith(p)))) continue;
|
|
14790
14972
|
try {
|
|
14791
|
-
const object =
|
|
14973
|
+
const object = getByEntryPath(draft, first);
|
|
14792
14974
|
if (object === null || typeof object !== "object") continue;
|
|
14793
|
-
for (const path of others)
|
|
14975
|
+
for (const path of others) relinkPath(draft, path, object);
|
|
14794
14976
|
} catch {}
|
|
14795
14977
|
}
|
|
14796
14978
|
}
|
|
@@ -14806,6 +14988,12 @@ function cloneNamespaces(from) {
|
|
|
14806
14988
|
transient: deepClone(from.transient, { seen })
|
|
14807
14989
|
};
|
|
14808
14990
|
}
|
|
14991
|
+
/** The three variable namespaces of `state`, as an object to walk. */
|
|
14992
|
+
var namespacesOf = (state) => ({
|
|
14993
|
+
variables: state.variables,
|
|
14994
|
+
temporary: state.temporary,
|
|
14995
|
+
transient: state.transient
|
|
14996
|
+
});
|
|
14809
14997
|
/**
|
|
14810
14998
|
* Commit the property paths each mutation's code changed (work vs base) on
|
|
14811
14999
|
* top of the current store state, so writes made elsewhere during execution
|
|
@@ -14870,24 +15058,9 @@ function commitScopes(commits) {
|
|
|
14870
15058
|
}
|
|
14871
15059
|
/**
|
|
14872
15060
|
* The changes of the code to the namespaces, as paths below them (`['variables',
|
|
14873
|
-
* 'a']
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
* object, whichever of them it is reached through.
|
|
14877
|
-
*/
|
|
14878
|
-
function changesOf(base, work) {
|
|
14879
|
-
const before = base;
|
|
14880
|
-
const after = work;
|
|
14881
|
-
const written = diffPaths(before, after);
|
|
14882
|
-
const keys = new Set(written.map((c) => pathKey(c.path)));
|
|
14883
|
-
const aliases = [];
|
|
14884
|
-
for (const change of aliasChanges(before, after)) {
|
|
14885
|
-
if (underChange(change.path, keys)) continue;
|
|
14886
|
-
keys.add(pathKey(change.path));
|
|
14887
|
-
aliases.push(change);
|
|
14888
|
-
}
|
|
14889
|
-
return [...aliases, ...written];
|
|
14890
|
-
}
|
|
15061
|
+
* 'a']`; see changesBetween).
|
|
15062
|
+
*/
|
|
15063
|
+
var changesOf = (base, work) => changesBetween(base, work);
|
|
14891
15064
|
/** Every running mutation, to commit with its code going on. */
|
|
14892
15065
|
var running = () => activeScopes.map((scope) => ({
|
|
14893
15066
|
scope,
|
|
@@ -14896,12 +15069,26 @@ var running = () => activeScopes.map((scope) => ({
|
|
|
14896
15069
|
/**
|
|
14897
15070
|
* Bring a suspended mutation's copies up to the store after an action
|
|
14898
15071
|
* replaced or changed state under it (navigation clears temporaries, back
|
|
14899
|
-
* and restart replace variables).
|
|
14900
|
-
* the code
|
|
15072
|
+
* and restart replace variables). What the action changed (see
|
|
15073
|
+
* changesBetween) is written into the code's working copy, which keeps the
|
|
15074
|
+
* objects the code may hold where nothing changed, and the references among
|
|
15075
|
+
* the values written, and to the objects it holds already, as a commit does.
|
|
14901
15076
|
*/
|
|
14902
15077
|
function resync(scope) {
|
|
14903
15078
|
const state = useStoryStore.getState();
|
|
14904
|
-
|
|
15079
|
+
const now = namespacesOf(state);
|
|
15080
|
+
const changes = changesBetween(scope.base, now);
|
|
15081
|
+
const copy = clonerFor(scope.work, locateObjects(now, changes));
|
|
15082
|
+
for (const change of changes) try {
|
|
15083
|
+
applyChange(scope.work, change.deleted ? change : {
|
|
15084
|
+
...change,
|
|
15085
|
+
value: copy(change.value)
|
|
15086
|
+
});
|
|
15087
|
+
} catch {
|
|
15088
|
+
const [ns, root] = change.path;
|
|
15089
|
+
if (hasOwn(now[ns], root)) scope.work[ns][root] = copy(now[ns][root]);
|
|
15090
|
+
else delete scope.work[ns][root];
|
|
15091
|
+
}
|
|
14905
15092
|
scope.base = cloneNamespaces(state);
|
|
14906
15093
|
}
|
|
14907
15094
|
/**
|
|
@@ -14930,9 +15117,11 @@ function runWithCommittedMutations(action) {
|
|
|
14930
15117
|
*/
|
|
14931
15118
|
function mutateState(run) {
|
|
14932
15119
|
commitScopes(running());
|
|
15120
|
+
const held = locateObjects(namespacesOf(getActiveMutationScope() ?? useStoryStore.getState()), []);
|
|
14933
15121
|
const scope = startScope();
|
|
15122
|
+
const adopt = clonerFor(scope.work, held);
|
|
14934
15123
|
try {
|
|
14935
|
-
run(scope.work);
|
|
15124
|
+
run(scope.work, adopt);
|
|
14936
15125
|
} finally {
|
|
14937
15126
|
activeScopes.pop();
|
|
14938
15127
|
}
|
|
@@ -14963,7 +15152,11 @@ function executeMutation(code, mergedLocals, scopeUpdate) {
|
|
|
14963
15152
|
scope,
|
|
14964
15153
|
keepRunning: false
|
|
14965
15154
|
}]);
|
|
14966
|
-
|
|
15155
|
+
const changed = new Set(changesBetween(mergedLocals, localsClone, true).map((c) => c.path[0]));
|
|
15156
|
+
for (const group of sharedPaths(localsClone)) if (group.some((path) => changed.has(path[0]))) {
|
|
15157
|
+
for (const path of group) if (localsClone[path[0]] !== mergedLocals[path[0]]) changed.add(path[0]);
|
|
15158
|
+
}
|
|
15159
|
+
for (const key of Object.keys(localsClone)) if (changed.has(key)) scopeUpdate(key, localsClone[key]);
|
|
14967
15160
|
for (const key of Object.keys(mergedLocals)) if (!hasOwn(localsClone, key)) scopeUpdate(key, void 0);
|
|
14968
15161
|
}
|
|
14969
15162
|
//#endregion
|
|
@@ -15687,35 +15880,65 @@ function loadedEntryMoment(payload) {
|
|
|
15687
15880
|
* the `beforesave` hooks) into the payload's snapshot of the saved moment. A
|
|
15688
15881
|
* load restores that snapshot, the state on entering the passage, and runs
|
|
15689
15882
|
* the passage again, so data a hook adds to a save would otherwise be lost
|
|
15690
|
-
* on load (#227). Only the property paths the hooks changed are written (
|
|
15691
|
-
*
|
|
15692
|
-
*
|
|
15693
|
-
*
|
|
15694
|
-
*
|
|
15883
|
+
* on load (#227). Only the property paths the hooks changed are written (see
|
|
15884
|
+
* changesBetween): a whole variable would bring along what the passage did
|
|
15885
|
+
* to the rest of it, which the passage then does again on load (#232). The
|
|
15886
|
+
* references the hooks made stay: an object they put in two variables is one
|
|
15887
|
+
* object in the snapshot, and one they made another variable refer to is the
|
|
15888
|
+
* snapshot's own object of it (#302). Only the payload's copy changes: the
|
|
15889
|
+
* live history keeps the recorded snapshot (#159).
|
|
15695
15890
|
*/
|
|
15696
15891
|
function keepHookWrites(payload, before, after) {
|
|
15697
15892
|
const moment = payload.history[payload.historyIndex];
|
|
15698
15893
|
if (!moment) return;
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
}
|
|
15718
|
-
|
|
15894
|
+
const changes = changesBetween(before, after, true);
|
|
15895
|
+
if (changes.length === 0) return;
|
|
15896
|
+
const work = deepClone(moment.variables);
|
|
15897
|
+
const seen = existingObjects(locateObjects(after, changes), work);
|
|
15898
|
+
const own = (value) => deepClone(value, { seen });
|
|
15899
|
+
for (const change of changes) writeHookChange(work, after, change, own);
|
|
15900
|
+
moment.variables = shareEqual(moment.variables, work);
|
|
15901
|
+
}
|
|
15902
|
+
/**
|
|
15903
|
+
* Write `change` (of the hooks, to `after`) into `work`. The value goes in at
|
|
15904
|
+
* the first place `work` lacks the objects on the path of one of the kind
|
|
15905
|
+
* `after` holds (the passage created or replaced it): there, the hooks' whole
|
|
15906
|
+
* value of it is written. Into an array, elements removed from the end are
|
|
15907
|
+
* removed at the same indices and elements added are appended; changes at
|
|
15908
|
+
* indices the array lacks are dropped (it was resized, so its indices do not
|
|
15909
|
+
* line up with the live ones).
|
|
15910
|
+
*/
|
|
15911
|
+
function writeHookChange(work, after, change, own) {
|
|
15912
|
+
const { path } = change;
|
|
15913
|
+
let holder = work;
|
|
15914
|
+
let depth = 0;
|
|
15915
|
+
for (; depth < path.length - 1; depth++) {
|
|
15916
|
+
const key = path[depth];
|
|
15917
|
+
const child = hasOwn(holder, key) ? holder[key] : void 0;
|
|
15918
|
+
const held = getByPath(after, path.slice(0, depth + 1));
|
|
15919
|
+
if (!mergesWith(child, held, true)) {
|
|
15920
|
+
writeKey(holder, key, own(held));
|
|
15921
|
+
return;
|
|
15922
|
+
}
|
|
15923
|
+
holder = child;
|
|
15924
|
+
}
|
|
15925
|
+
const key = path[depth];
|
|
15926
|
+
if (change.deleted) writeKey(holder, key, void 0, change);
|
|
15927
|
+
else writeKey(holder, key, own(change.value), change);
|
|
15928
|
+
}
|
|
15929
|
+
function writeKey(holder, key, value, change) {
|
|
15930
|
+
const deleted = change?.deleted ?? false;
|
|
15931
|
+
if (Array.isArray(holder)) {
|
|
15932
|
+
const index = Number(key);
|
|
15933
|
+
if (deleted) if (change && "hole" in change) delete holder[index];
|
|
15934
|
+
else holder.length = Math.min(holder.length, index);
|
|
15935
|
+
else if (change && "appended" in change) {
|
|
15936
|
+
holder.length += change.gap ?? 0;
|
|
15937
|
+
holder.push(value);
|
|
15938
|
+
} else if (key === "length" && change) holder.length += value;
|
|
15939
|
+
else if (index < holder.length) holder[index] = value;
|
|
15940
|
+
} else if (deleted) delete holder[key];
|
|
15941
|
+
else setOwn(holder, key, value);
|
|
15719
15942
|
}
|
|
15720
15943
|
/** Restore the PRNG from a snapshot, or reset it without one. */
|
|
15721
15944
|
function restorePRNGFrom(prng) {
|
|
@@ -17289,8 +17512,8 @@ function defineMacro(config, source = "builtin") {
|
|
|
17289
17512
|
ctx.value = useStoryStore((s) => getByPath(s.variables, segments));
|
|
17290
17513
|
ctx.getValue = () => getByPath(readState().variables, segments);
|
|
17291
17514
|
ctx.setValue = (value) => {
|
|
17292
|
-
|
|
17293
|
-
setByPath(
|
|
17515
|
+
mutateState((work, adopt) => {
|
|
17516
|
+
setByPath(work.variables, segments, adopt(value), { createMissing: true });
|
|
17294
17517
|
});
|
|
17295
17518
|
};
|
|
17296
17519
|
}
|
|
@@ -17432,8 +17655,8 @@ function createStoryAPI() {
|
|
|
17432
17655
|
warnIfUndeclared(isTransient, key);
|
|
17433
17656
|
}
|
|
17434
17657
|
if (entries.some(([k]) => k.includes("."))) {
|
|
17435
|
-
mutateState((work) => {
|
|
17436
|
-
for (const [k, v] of entries) setOne(work, k, v);
|
|
17658
|
+
mutateState((work, adopt) => {
|
|
17659
|
+
for (const [k, v] of entries) setOne(work, k, adopt(v));
|
|
17437
17660
|
});
|
|
17438
17661
|
return;
|
|
17439
17662
|
}
|
|
@@ -19895,7 +20118,8 @@ defineMacro({
|
|
|
19895
20118
|
return useRunOnce("unset", rawArgs, () => {
|
|
19896
20119
|
const state = useStoryStore.getState();
|
|
19897
20120
|
const name = ctx.args.variable ?? "";
|
|
19898
|
-
if (name
|
|
20121
|
+
if (/^[$_%@][^.]*\./.test(name)) ctx.mutate(`delete ${name}`);
|
|
20122
|
+
else if (name.startsWith("$")) state.deleteVariable(name.slice(1));
|
|
19899
20123
|
else if (name.startsWith("_")) state.deleteTemporary(name.slice(1));
|
|
19900
20124
|
else if (name.startsWith("%")) state.deleteTransient(name.slice(1));
|
|
19901
20125
|
else if (name.startsWith("@")) ctx.update(name.slice(1), void 0);
|