@jbrowse/mobx-state-tree 6.6.0 → 6.7.0
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/CHANGELOG.md +24 -0
- package/dist/index.d.ts +7 -0
- package/dist/mobx-state-tree.cjs +199 -80
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +199 -80
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## [6.7.0](https://github.com/GMOD/mobx-state-tree/compare/v6.6.1...v6.7.0) (2026-09-30)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- A dead node's never-read child reads as a dead instance ([a6c430e](https://github.com/GMOD/mobx-state-tree/commit/a6c430e926ec31fe226a10cafff6e2ea0add4012))
|
|
6
|
+
- Exempt only never-created dead nodes; ADR 0008 ([d15fb5b](https://github.com/GMOD/mobx-state-tree/commit/d15fb5b99399d7844af910db689cbb5c403ddbc3))
|
|
7
|
+
- A never-read node dies as if it had been read ([3ffffae](https://github.com/GMOD/mobx-state-tree/commit/3ffffae9f5f44e7c5ea1573364da2ce6224ff288))
|
|
8
|
+
- Fail clearly, before damaging the tree, on impossible attaches ([25e0dff](https://github.com/GMOD/mobx-state-tree/commit/25e0dffd31b02c57ec31195685e3551de7167bb0))
|
|
9
|
+
- Destroy children that hooks add while a node is built at death ([743f887](https://github.com/GMOD/mobx-state-tree/commit/743f887f0a749f5dff727c86429c5ffb7ce4b726))
|
|
10
|
+
|
|
11
|
+
### Styling
|
|
12
|
+
|
|
13
|
+
- Format ADR 0008 ([4290662](https://github.com/GMOD/mobx-state-tree/commit/42906620c1c6fc58526251310468d0012f446159))
|
|
14
|
+
|
|
15
|
+
## [6.6.1](https://github.com/GMOD/mobx-state-tree/compare/v6.6.0...v6.6.1) (2026-09-25)
|
|
16
|
+
|
|
17
|
+
### Performance Improvements
|
|
18
|
+
|
|
19
|
+
- Reconcile children in linear time ([530e27f](https://github.com/GMOD/mobx-state-tree/commit/530e27f20046ba449d45c2724106fcb5b4133e06))
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
|
|
23
|
+
- Typecheck the reconcile tests; add pnpm typecheck to the verification norm ([a23e4f3](https://github.com/GMOD/mobx-state-tree/commit/a23e4f34f718dfd398b696ca5e36be88bbe52454))
|
|
24
|
+
|
|
1
25
|
## [6.6.0](https://github.com/GMOD/mobx-state-tree/compare/v6.5.2...v6.6.0) (2026-09-24)
|
|
2
26
|
|
|
3
27
|
### Bug Fixes
|
package/dist/index.d.ts
CHANGED
|
@@ -624,6 +624,13 @@ declare class ObjectNode<C, S, T> extends BaseNode<C, S, T> {
|
|
|
624
624
|
clearParent(): void;
|
|
625
625
|
notifyDetachOf(subject: AnyObjectNode): void;
|
|
626
626
|
private notifyChildrenOfDetach;
|
|
627
|
+
/**
|
|
628
|
+
* @internal
|
|
629
|
+
* @hidden
|
|
630
|
+
* A node that died inside a tree is its own root and has no identifier
|
|
631
|
+
* cache, so attaching it, or attaching anything to it, cannot work.
|
|
632
|
+
*/
|
|
633
|
+
assertAttachable(): IdentifierCache;
|
|
627
634
|
setParent(newParent: AnyObjectNode, subpath: string): void;
|
|
628
635
|
protected fireHook(name: Hook): void;
|
|
629
636
|
private _snapshotUponDeath?;
|
package/dist/mobx-state-tree.cjs
CHANGED
|
@@ -468,7 +468,7 @@ function resolveIdentifier(type, target, identifier) {
|
|
|
468
468
|
assertIsType(type, 1);
|
|
469
469
|
assertIsStateTreeNode(target, 2);
|
|
470
470
|
assertIsValidIdentifier(identifier, 3);
|
|
471
|
-
const node = getStateTreeNode(target).root.identifierCache
|
|
471
|
+
const node = getStateTreeNode(target).root.identifierCache?.resolve(type, normalizeIdentifier(identifier));
|
|
472
472
|
return node?.value;
|
|
473
473
|
}
|
|
474
474
|
/**
|
|
@@ -1211,7 +1211,7 @@ class ObjectNode extends BaseNode {
|
|
|
1211
1211
|
// addNodeToCache no-ops without an identifier attribute, and reaching the
|
|
1212
1212
|
// root to ask it is a walk up the whole parent chain
|
|
1213
1213
|
if (this.identifierAttribute) {
|
|
1214
|
-
const cache = parent ? parent.
|
|
1214
|
+
const cache = parent ? parent.assertAttachable() : this.identifierCache;
|
|
1215
1215
|
cache.addNodeToCache(this);
|
|
1216
1216
|
}
|
|
1217
1217
|
}
|
|
@@ -1222,8 +1222,11 @@ class ObjectNode extends BaseNode {
|
|
|
1222
1222
|
}
|
|
1223
1223
|
}
|
|
1224
1224
|
createObservableInstance(fireHooks = true) {
|
|
1225
|
+
const diedUncreated = this.state === NodeLifeCycle.DEAD &&
|
|
1226
|
+
this._observableInstanceState ===
|
|
1227
|
+
ObservableInstanceLifecycle.UNINITIALIZED;
|
|
1225
1228
|
if (devMode()) {
|
|
1226
|
-
if (this.state !== NodeLifeCycle.INITIALIZING) {
|
|
1229
|
+
if (!diedUncreated && this.state !== NodeLifeCycle.INITIALIZING) {
|
|
1227
1230
|
// istanbul ignore next
|
|
1228
1231
|
throw fail("assertion failed: the creation of the observable instance must be done on the initializing phase");
|
|
1229
1232
|
}
|
|
@@ -1276,6 +1279,9 @@ class ObjectNode extends BaseNode {
|
|
|
1276
1279
|
// subscribes to never pays for a full-tree snapshot at creation. keepAlive
|
|
1277
1280
|
// on _snapshotComputed preserves snapshot referential stability without it.
|
|
1278
1281
|
this._childNodes = EMPTY_OBJECT;
|
|
1282
|
+
if (diedUncreated) {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1279
1285
|
this.state = NodeLifeCycle.CREATED;
|
|
1280
1286
|
if (fireHooks) {
|
|
1281
1287
|
this.fireHook(Hook.afterCreate);
|
|
@@ -1330,6 +1336,19 @@ class ObjectNode extends BaseNode {
|
|
|
1330
1336
|
child.notifyDetachOf(subject);
|
|
1331
1337
|
}
|
|
1332
1338
|
}
|
|
1339
|
+
/**
|
|
1340
|
+
* @internal
|
|
1341
|
+
* @hidden
|
|
1342
|
+
* A node that died inside a tree is its own root and has no identifier
|
|
1343
|
+
* cache, so attaching it, or attaching anything to it, cannot work.
|
|
1344
|
+
*/
|
|
1345
|
+
assertAttachable() {
|
|
1346
|
+
const cache = this.root.identifierCache;
|
|
1347
|
+
if (!cache) {
|
|
1348
|
+
throw fail(`'${this}' is no longer part of a state tree, so it cannot be attached to one or have nodes attached to it.`);
|
|
1349
|
+
}
|
|
1350
|
+
return cache;
|
|
1351
|
+
}
|
|
1333
1352
|
setParent(newParent, subpath) {
|
|
1334
1353
|
const parentChanged = newParent !== this.parent;
|
|
1335
1354
|
const subpathChanged = subpath !== this.subpath;
|
|
@@ -1358,9 +1377,9 @@ class ObjectNode extends BaseNode {
|
|
|
1358
1377
|
}
|
|
1359
1378
|
}
|
|
1360
1379
|
if (parentChanged) {
|
|
1361
|
-
|
|
1380
|
+
this.assertAttachable();
|
|
1362
1381
|
this.environment = undefined; // will use root's
|
|
1363
|
-
newParent.
|
|
1382
|
+
newParent.assertAttachable().mergeCache(this);
|
|
1364
1383
|
this.baseSetParent(newParent, subpath);
|
|
1365
1384
|
this.fireHook(Hook.afterAttach);
|
|
1366
1385
|
}
|
|
@@ -1556,13 +1575,33 @@ class ObjectNode extends BaseNode {
|
|
|
1556
1575
|
addHiddenFinalProp(this.storedValue, "toJSON", toJSON);
|
|
1557
1576
|
}
|
|
1558
1577
|
aboutToDie() {
|
|
1578
|
+
const wasUninitialized = this._observableInstanceState ===
|
|
1579
|
+
ObservableInstanceLifecycle.UNINITIALIZED;
|
|
1580
|
+
const children = this.getChildren();
|
|
1581
|
+
if (wasUninitialized &&
|
|
1582
|
+
(this.hasSnapshotPostProcessor ||
|
|
1583
|
+
children.some(child => child instanceof ObjectNode &&
|
|
1584
|
+
child._observableInstanceState !==
|
|
1585
|
+
ObservableInstanceLifecycle.UNINITIALIZED))) {
|
|
1586
|
+
this.createObservableInstanceIfNeeded();
|
|
1587
|
+
}
|
|
1588
|
+
for (const node of children) {
|
|
1589
|
+
if (node.isAlive) {
|
|
1590
|
+
node.aboutToDie();
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1559
1593
|
if (this._observableInstanceState ===
|
|
1560
1594
|
ObservableInstanceLifecycle.UNINITIALIZED) {
|
|
1561
1595
|
return;
|
|
1562
1596
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1597
|
+
if (wasUninitialized) {
|
|
1598
|
+
const walked = new Set(children);
|
|
1599
|
+
for (const node of this.getChildren()) {
|
|
1600
|
+
if (node.isAlive && !walked.has(node)) {
|
|
1601
|
+
node.aboutToDie();
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1566
1605
|
// beforeDestroy should run before the disposers since else we could end up in a situation where
|
|
1567
1606
|
// a disposer added with addDisposer at this stage (beforeDestroy) is actually never released
|
|
1568
1607
|
this.baseAboutToDie();
|
|
@@ -1859,12 +1898,16 @@ class ComplexType extends BaseType {
|
|
|
1859
1898
|
current.setParent(parent, subpath);
|
|
1860
1899
|
return current;
|
|
1861
1900
|
}
|
|
1901
|
+
const newNode = getStateTreeNodeSafe(newValue);
|
|
1902
|
+
if (newNode) {
|
|
1903
|
+
newNode.assertAttachable();
|
|
1904
|
+
if (!current.isDetaching && newNode.isWithin(current)) {
|
|
1905
|
+
throw fail(`Cannot assign ${newNode} to '${parent.path}/${subpath}': it is part of ${current}, which it would replace.`);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1862
1908
|
// current node cannot be recycled in any way
|
|
1863
1909
|
current.die(); // noop if detaching
|
|
1864
|
-
|
|
1865
|
-
if (isStateTreeNode(newValue) && this.isAssignableFrom(getType(newValue))) {
|
|
1866
|
-
// newValue is a Node as well, move it here..
|
|
1867
|
-
const newNode = getStateTreeNode(newValue);
|
|
1910
|
+
if (newNode && this.isAssignableFrom(newNode.type)) {
|
|
1868
1911
|
newNode.setParent(parent, subpath);
|
|
1869
1912
|
return newNode;
|
|
1870
1913
|
}
|
|
@@ -4469,98 +4512,174 @@ function array(subtype) {
|
|
|
4469
4512
|
*/
|
|
4470
4513
|
function reconcileArrayChildren(parent, childType, oldNodes, newValues, firstNewPath) {
|
|
4471
4514
|
let nothingChanged = true;
|
|
4472
|
-
|
|
4473
|
-
//
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
childType instanceof ModelType &&
|
|
4484
|
-
childType.identifierAttribute) {
|
|
4485
|
-
const byId = new Map();
|
|
4486
|
-
for (const n of oldNodes) {
|
|
4487
|
-
if (n instanceof ObjectNode && n.identifier !== null) {
|
|
4488
|
-
byId.set(n.identifier, n);
|
|
4489
|
-
}
|
|
4490
|
-
}
|
|
4491
|
-
idIndex = { attr: childType.identifierAttribute, byId };
|
|
4492
|
-
}
|
|
4493
|
-
for (let i = 0;; i++) {
|
|
4494
|
-
const hasNewNode = i <= newValues.length - 1;
|
|
4495
|
-
const oldNode = oldNodes[i];
|
|
4496
|
-
let newValue = hasNewNode ? newValues[i] : undefined;
|
|
4515
|
+
const result = [];
|
|
4516
|
+
// every old node before `next` is claimed
|
|
4517
|
+
const claimed = new Uint8Array(oldNodes.length);
|
|
4518
|
+
let next = 0;
|
|
4519
|
+
let finder;
|
|
4520
|
+
for (let i = 0; i < newValues.length; i++) {
|
|
4521
|
+
while (claimed[next]) {
|
|
4522
|
+
next++;
|
|
4523
|
+
}
|
|
4524
|
+
const oldNode = oldNodes[next];
|
|
4525
|
+
let newValue = newValues[i];
|
|
4497
4526
|
const newPath = `${firstNewPath + i}`;
|
|
4498
4527
|
// for some reason, instead of newValue we got a node, fallback to the storedValue
|
|
4499
4528
|
// TODO: https://github.com/mobxjs/mobx-state-tree/issues/340#issuecomment-325581681
|
|
4500
4529
|
if (isNode(newValue)) {
|
|
4501
4530
|
newValue = newValue.storedValue;
|
|
4502
4531
|
}
|
|
4503
|
-
if (!oldNode
|
|
4504
|
-
|
|
4505
|
-
|
|
4532
|
+
if (!oldNode) {
|
|
4533
|
+
if (isStateTreeNode(newValue) &&
|
|
4534
|
+
getStateTreeNode(newValue).parent === parent) {
|
|
4535
|
+
// this node is owned by this parent, but not in the reconcilable set, so it must be double
|
|
4536
|
+
throw fail(`Cannot add an object to a state tree if it is already part of the same or another state tree. Tried to assign an object to '${parent.path}/${newPath}', but it lives already at '${getStateTreeNode(newValue).path}'`);
|
|
4537
|
+
}
|
|
4538
|
+
nothingChanged = false;
|
|
4539
|
+
result.push(valueAsNode(childType, parent, newPath, newValue));
|
|
4540
|
+
}
|
|
4541
|
+
else if (areSame(oldNode, newValue)) {
|
|
4542
|
+
claimed[next] = 1;
|
|
4543
|
+
result.push(valueAsNode(childType, parent, newPath, newValue, oldNode));
|
|
4506
4544
|
}
|
|
4507
|
-
else
|
|
4508
|
-
// new one does not exists
|
|
4545
|
+
else {
|
|
4509
4546
|
nothingChanged = false;
|
|
4510
|
-
|
|
4547
|
+
// a single-element write scans one node, cheaper than building an index
|
|
4548
|
+
const j = oldNodes.length > 1
|
|
4549
|
+
? (finder ??= new ReuseFinder(childType, oldNodes, claimed)).find(newValue, next)
|
|
4550
|
+
: scanForSame(oldNodes, claimed, next, newValue);
|
|
4551
|
+
let oldMatch;
|
|
4552
|
+
if (j >= 0) {
|
|
4553
|
+
claimed[j] = 1;
|
|
4554
|
+
oldMatch = oldNodes[j];
|
|
4555
|
+
}
|
|
4556
|
+
result.push(valueAsNode(childType, parent, newPath, newValue, oldMatch));
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
for (let j = next; j < oldNodes.length; j++) {
|
|
4560
|
+
if (!claimed[j]) {
|
|
4561
|
+
nothingChanged = false;
|
|
4562
|
+
const oldNode = oldNodes[j];
|
|
4511
4563
|
if (oldNode instanceof ObjectNode) {
|
|
4512
4564
|
// since it is going to be returned by pop/splice/shift better create it before killing it
|
|
4513
4565
|
// so it doesn't end up in an undead state
|
|
4514
4566
|
oldNode.createObservableInstanceIfNeeded();
|
|
4515
4567
|
}
|
|
4516
4568
|
oldNode.die();
|
|
4517
|
-
i--;
|
|
4518
4569
|
}
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
}
|
|
4527
|
-
nothingChanged = false;
|
|
4528
|
-
const newNode = valueAsNode(childType, parent, newPath, newValue);
|
|
4529
|
-
oldNodes.splice(i, 0, newNode);
|
|
4570
|
+
}
|
|
4571
|
+
return nothingChanged ? null : result;
|
|
4572
|
+
}
|
|
4573
|
+
function scanForSame(oldNodes, claimed, from, value) {
|
|
4574
|
+
for (let j = from; j < oldNodes.length; j++) {
|
|
4575
|
+
if (!claimed[j] && areSame(oldNodes[j], value)) {
|
|
4576
|
+
return j;
|
|
4530
4577
|
}
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4578
|
+
}
|
|
4579
|
+
return -1;
|
|
4580
|
+
}
|
|
4581
|
+
/**
|
|
4582
|
+
* Picks the old node the reorder branch reuses: the first unclaimed one
|
|
4583
|
+
* `areSame` holds for. `areSame` reduces to an equality — node identity for a
|
|
4584
|
+
* live node, the old node's snapshot for anything else — except where the
|
|
4585
|
+
* value is a plain object and the old node carries an identifier, where it
|
|
4586
|
+
* runs `is()` before matching ids. So only that case scans, and an identified
|
|
4587
|
+
* plain model looks its id up instead, as ADR 0001 records.
|
|
4588
|
+
*/
|
|
4589
|
+
class ReuseFinder {
|
|
4590
|
+
oldNodes;
|
|
4591
|
+
claimed;
|
|
4592
|
+
idAttribute;
|
|
4593
|
+
byId;
|
|
4594
|
+
byNode;
|
|
4595
|
+
bySnapshot;
|
|
4596
|
+
anyIdentified;
|
|
4597
|
+
constructor(childType, oldNodes, claimed) {
|
|
4598
|
+
this.oldNodes = oldNodes;
|
|
4599
|
+
this.claimed = claimed;
|
|
4600
|
+
this.idAttribute =
|
|
4601
|
+
childType instanceof ModelType ? childType.identifierAttribute : undefined;
|
|
4602
|
+
}
|
|
4603
|
+
find(value, next) {
|
|
4604
|
+
let j;
|
|
4605
|
+
if (this.idAttribute && isPlainObject(value)) {
|
|
4606
|
+
j =
|
|
4607
|
+
this.idIndex(next).get(normalizeIdentifier(value[this.idAttribute])) ??
|
|
4608
|
+
-1;
|
|
4609
|
+
}
|
|
4610
|
+
else if (isStateTreeNode(value)) {
|
|
4611
|
+
j = this.nodeIndex(next).get(getStateTreeNode(value)) ?? -1;
|
|
4612
|
+
}
|
|
4613
|
+
else if (!isPlainObject(value) || !this.hasIdentifiedNode(next)) {
|
|
4614
|
+
j = this.firstWithSnapshot(value, next);
|
|
4534
4615
|
}
|
|
4535
4616
|
else {
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4617
|
+
return scanForSame(this.oldNodes, this.claimed, next, value);
|
|
4618
|
+
}
|
|
4619
|
+
return j >= 0 && !this.claimed[j] && areSame(this.oldNodes[j], value)
|
|
4620
|
+
? j
|
|
4621
|
+
: -1;
|
|
4622
|
+
}
|
|
4623
|
+
// last one wins on a duplicate id, even when it is already claimed
|
|
4624
|
+
idIndex(next) {
|
|
4625
|
+
if (!this.byId) {
|
|
4626
|
+
this.byId = new Map();
|
|
4627
|
+
for (let j = next; j < this.oldNodes.length; j++) {
|
|
4628
|
+
const node = this.oldNodes[j];
|
|
4629
|
+
if (node instanceof ObjectNode && node.identifier !== null) {
|
|
4630
|
+
this.byId.set(node.identifier, j);
|
|
4547
4631
|
}
|
|
4548
4632
|
}
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4633
|
+
}
|
|
4634
|
+
return this.byId;
|
|
4635
|
+
}
|
|
4636
|
+
nodeIndex(next) {
|
|
4637
|
+
if (!this.byNode) {
|
|
4638
|
+
this.byNode = new Map();
|
|
4639
|
+
for (let j = next; j < this.oldNodes.length; j++) {
|
|
4640
|
+
this.byNode.set(this.oldNodes[j], j);
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
return this.byNode;
|
|
4644
|
+
}
|
|
4645
|
+
hasIdentifiedNode(next) {
|
|
4646
|
+
if (this.anyIdentified === undefined) {
|
|
4647
|
+
this.anyIdentified = this.oldNodes
|
|
4648
|
+
.slice(next)
|
|
4649
|
+
.some(node => node instanceof ObjectNode &&
|
|
4650
|
+
node.identifier !== null &&
|
|
4651
|
+
!!node.identifierAttribute);
|
|
4652
|
+
}
|
|
4653
|
+
return this.anyIdentified;
|
|
4654
|
+
}
|
|
4655
|
+
firstWithSnapshot(value, next) {
|
|
4656
|
+
if (!this.bySnapshot) {
|
|
4657
|
+
const first = new Map();
|
|
4658
|
+
const nextSame = new Int32Array(this.oldNodes.length);
|
|
4659
|
+
for (let j = this.oldNodes.length - 1; j >= next; j--) {
|
|
4660
|
+
const node = this.oldNodes[j];
|
|
4661
|
+
if (!this.claimed[j] && node.isAlive) {
|
|
4662
|
+
const snapshot = node.snapshot;
|
|
4663
|
+
nextSame[j] = first.get(snapshot) ?? -1;
|
|
4664
|
+
first.set(snapshot, j);
|
|
4556
4665
|
}
|
|
4557
4666
|
}
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4667
|
+
this.bySnapshot = { first, nextSame };
|
|
4668
|
+
}
|
|
4669
|
+
const { first, nextSame } = this.bySnapshot;
|
|
4670
|
+
const head = first.get(value);
|
|
4671
|
+
if (head === undefined) {
|
|
4672
|
+
return -1;
|
|
4673
|
+
}
|
|
4674
|
+
let j = head;
|
|
4675
|
+
while (j >= 0 && (this.claimed[j] || !this.oldNodes[j].isAlive)) {
|
|
4676
|
+
j = nextSame[j];
|
|
4677
|
+
}
|
|
4678
|
+
if (j !== head) {
|
|
4679
|
+
first.set(value, j);
|
|
4561
4680
|
}
|
|
4681
|
+
return j;
|
|
4562
4682
|
}
|
|
4563
|
-
return nothingChanged ? null : oldNodes;
|
|
4564
4683
|
}
|
|
4565
4684
|
/**
|
|
4566
4685
|
* Convert a value to a node at given parent and subpath. Attempts to reuse old node if possible and given.
|