jotai-state-tree 1.4.1 → 1.4.2
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/{chunk-MSLAD5CJ.mjs → chunk-Q6QPBXHH.mjs} +88 -7
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +87 -7
- package/dist/index.mjs +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +96 -7
- package/dist/react.mjs +14 -2
- package/dist/{undo-BcBI_BQM.d.mts → undo-DL1pyOkT.d.mts} +2 -0
- package/dist/{undo-BcBI_BQM.d.ts → undo-DL1pyOkT.d.ts} +2 -0
- package/package.json +1 -1
- package/src/__tests__/app_example.test.tsx +1 -1
- package/src/__tests__/examples/dashboard-live-telemetry.test.tsx +107 -0
- package/src/__tests__/examples/form-builder-dynamic.test.tsx +187 -0
- package/src/__tests__/examples/kanban-board-references.test.tsx +130 -0
- package/src/__tests__/examples/note-taking-ssr.test.tsx +100 -0
- package/src/__tests__/examples/shopping-cart-views.test.tsx +181 -0
- package/src/__tests__/examples/todo-list-time-travel.test.tsx +223 -0
- package/src/__tests__/history_repro.test.ts +3 -4
- package/src/__tests__/index.test.ts +3 -2
- package/src/react.ts +15 -0
- package/src/tree.ts +67 -8
- package/src/undo.ts +42 -0
|
@@ -42,9 +42,12 @@ var identifierFinalizationRegistry = new FinalizationRegistry(
|
|
|
42
42
|
(info) => {
|
|
43
43
|
const typeMap = identifierRegistry.get(info.typeName);
|
|
44
44
|
if (typeMap) {
|
|
45
|
-
typeMap.
|
|
46
|
-
if (
|
|
47
|
-
|
|
45
|
+
const ref = typeMap.get(info.identifier);
|
|
46
|
+
if (!ref || ref.deref() === void 0) {
|
|
47
|
+
typeMap.delete(info.identifier);
|
|
48
|
+
if (typeMap.size === 0) {
|
|
49
|
+
identifierRegistry.delete(info.typeName);
|
|
50
|
+
}
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
}
|
|
@@ -71,6 +74,35 @@ function notifyLifecycleChange(node, isAlive2) {
|
|
|
71
74
|
listeners.forEach((listener) => listener(isAlive2));
|
|
72
75
|
}
|
|
73
76
|
}
|
|
77
|
+
function cloneAndSerialize(value) {
|
|
78
|
+
if (value === null || value === void 0) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
if (hasStateTreeNode(value)) {
|
|
82
|
+
return getSnapshotFromNode(getStateTreeNode(value));
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return value.map(cloneAndSerialize);
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === "object") {
|
|
88
|
+
if (value instanceof Map) {
|
|
89
|
+
const res = {};
|
|
90
|
+
for (const [k, v] of value.entries()) {
|
|
91
|
+
res[k] = cloneAndSerialize(v);
|
|
92
|
+
}
|
|
93
|
+
return res;
|
|
94
|
+
}
|
|
95
|
+
const proto = Object.getPrototypeOf(value);
|
|
96
|
+
if (proto === null || proto === Object.prototype) {
|
|
97
|
+
const res = {};
|
|
98
|
+
for (const [k, v] of Object.entries(value)) {
|
|
99
|
+
res[k] = cloneAndSerialize(v);
|
|
100
|
+
}
|
|
101
|
+
return res;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
74
106
|
var StateTreeNode = class {
|
|
75
107
|
constructor(type, initialValue, env, parent, pathSegment) {
|
|
76
108
|
this.$parent = null;
|
|
@@ -88,6 +120,8 @@ var StateTreeNode = class {
|
|
|
88
120
|
/** Cached snapshot for structural sharing optimization */
|
|
89
121
|
this.cachedSnapshot = void 0;
|
|
90
122
|
this.isSnapshotDirty = true;
|
|
123
|
+
/** Strong reference to the instance proxy to prevent GC of active nodes */
|
|
124
|
+
this.instance = null;
|
|
91
125
|
this.$id = generateNodeId();
|
|
92
126
|
this.$type = type;
|
|
93
127
|
this.$env = env ?? parent?.$env;
|
|
@@ -108,6 +142,7 @@ var StateTreeNode = class {
|
|
|
108
142
|
}
|
|
109
143
|
/** Set the instance reference */
|
|
110
144
|
setInstance(instance) {
|
|
145
|
+
this.instance = instance;
|
|
111
146
|
const entry = nodeRegistry.get(this.$id);
|
|
112
147
|
if (entry && instance && typeof instance === "object") {
|
|
113
148
|
entry.instance = new WeakRef(instance);
|
|
@@ -115,8 +150,7 @@ var StateTreeNode = class {
|
|
|
115
150
|
}
|
|
116
151
|
/** Get the instance */
|
|
117
152
|
getInstance() {
|
|
118
|
-
|
|
119
|
-
return entry?.instance?.deref() ?? null;
|
|
153
|
+
return this.instance;
|
|
120
154
|
}
|
|
121
155
|
/** Get current value from atom */
|
|
122
156
|
getValue() {
|
|
@@ -222,9 +256,27 @@ var StateTreeNode = class {
|
|
|
222
256
|
}
|
|
223
257
|
/** Notify patch listeners */
|
|
224
258
|
notifyPatch(patch, reversePatch) {
|
|
225
|
-
|
|
259
|
+
const serializedPatch = {
|
|
260
|
+
...patch,
|
|
261
|
+
value: "value" in patch ? cloneAndSerialize(patch.value) : void 0
|
|
262
|
+
};
|
|
263
|
+
if (!("value" in patch)) {
|
|
264
|
+
delete serializedPatch.value;
|
|
265
|
+
}
|
|
266
|
+
const serializedReversePatch = {
|
|
267
|
+
...reversePatch,
|
|
268
|
+
value: "value" in reversePatch ? cloneAndSerialize(reversePatch.value) : void 0,
|
|
269
|
+
oldValue: "oldValue" in reversePatch ? cloneAndSerialize(reversePatch.oldValue) : void 0
|
|
270
|
+
};
|
|
271
|
+
if (!("value" in reversePatch)) {
|
|
272
|
+
delete serializedReversePatch.value;
|
|
273
|
+
}
|
|
274
|
+
if (!("oldValue" in reversePatch)) {
|
|
275
|
+
delete serializedReversePatch.oldValue;
|
|
276
|
+
}
|
|
277
|
+
this.patchListeners.forEach((listener) => listener(serializedPatch, serializedReversePatch));
|
|
226
278
|
if (this.$parent) {
|
|
227
|
-
this.$parent.notifyPatch(
|
|
279
|
+
this.$parent.notifyPatch(serializedPatch, serializedReversePatch);
|
|
228
280
|
}
|
|
229
281
|
}
|
|
230
282
|
/** Notify snapshot listeners */
|
|
@@ -261,6 +313,7 @@ var StateTreeNode = class {
|
|
|
261
313
|
/** Destroy this node and all children */
|
|
262
314
|
destroy() {
|
|
263
315
|
if (!this.$isAlive) return;
|
|
316
|
+
this.instance = null;
|
|
264
317
|
lifecycleHookHandlers.runBeforeDestroy?.(this);
|
|
265
318
|
this.children.forEach((child) => child.destroy());
|
|
266
319
|
this.children.clear();
|
|
@@ -900,6 +953,8 @@ function unfreeze(target) {
|
|
|
900
953
|
}
|
|
901
954
|
|
|
902
955
|
// src/undo.ts
|
|
956
|
+
var undoManagersRegistry = /* @__PURE__ */ new WeakMap();
|
|
957
|
+
var timeTravelManagersRegistry = /* @__PURE__ */ new WeakMap();
|
|
903
958
|
var UndoManager = class {
|
|
904
959
|
constructor(target, options = {}) {
|
|
905
960
|
this.historyEntries = [];
|
|
@@ -920,6 +975,7 @@ var UndoManager = class {
|
|
|
920
975
|
groupByTime: options.groupByTime ?? false,
|
|
921
976
|
groupingWindow: options.groupingWindow ?? 200
|
|
922
977
|
};
|
|
978
|
+
undoManagersRegistry.set(target, this);
|
|
923
979
|
this.disposer = onPatch(target, (patch, reversePatch) => {
|
|
924
980
|
this.recordPatch(patch, reversePatch);
|
|
925
981
|
});
|
|
@@ -1013,6 +1069,13 @@ var UndoManager = class {
|
|
|
1013
1069
|
applyPatch(this.target, entry.patches[i]);
|
|
1014
1070
|
}
|
|
1015
1071
|
this.currentIndex--;
|
|
1072
|
+
const tt = timeTravelManagersRegistry.get(this.target);
|
|
1073
|
+
if (tt) {
|
|
1074
|
+
const N = tt.snapshots.length;
|
|
1075
|
+
const M = this.historyEntries.length;
|
|
1076
|
+
const ttIndex = this.currentIndex + 1 + (N - 1 - M);
|
|
1077
|
+
tt.index = Math.max(0, Math.min(N - 1, ttIndex));
|
|
1078
|
+
}
|
|
1016
1079
|
} finally {
|
|
1017
1080
|
this.isUndoing = false;
|
|
1018
1081
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -1033,6 +1096,13 @@ var UndoManager = class {
|
|
|
1033
1096
|
for (const patch of entry.inversePatches) {
|
|
1034
1097
|
applyPatch(this.target, patch);
|
|
1035
1098
|
}
|
|
1099
|
+
const tt = timeTravelManagersRegistry.get(this.target);
|
|
1100
|
+
if (tt) {
|
|
1101
|
+
const N = tt.snapshots.length;
|
|
1102
|
+
const M = this.historyEntries.length;
|
|
1103
|
+
const ttIndex = this.currentIndex + 1 + (N - 1 - M);
|
|
1104
|
+
tt.index = Math.max(0, Math.min(N - 1, ttIndex));
|
|
1105
|
+
}
|
|
1036
1106
|
} finally {
|
|
1037
1107
|
this.isRedoing = false;
|
|
1038
1108
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -1086,6 +1156,7 @@ var UndoManager = class {
|
|
|
1086
1156
|
}
|
|
1087
1157
|
}
|
|
1088
1158
|
dispose() {
|
|
1159
|
+
undoManagersRegistry.delete(this.target);
|
|
1089
1160
|
if (this.disposer) {
|
|
1090
1161
|
this.disposer();
|
|
1091
1162
|
this.disposer = null;
|
|
@@ -1112,6 +1183,7 @@ var TimeTravelManager = class {
|
|
|
1112
1183
|
this.target = target;
|
|
1113
1184
|
this.maxSnapshots = options.maxSnapshots ?? 50;
|
|
1114
1185
|
this.autoRecord = options.autoRecord ?? false;
|
|
1186
|
+
timeTravelManagersRegistry.set(target, this);
|
|
1115
1187
|
this.record();
|
|
1116
1188
|
if (this.autoRecord) {
|
|
1117
1189
|
this.disposer = onPatch(target, () => {
|
|
@@ -1193,6 +1265,13 @@ var TimeTravelManager = class {
|
|
|
1193
1265
|
try {
|
|
1194
1266
|
this.index = index;
|
|
1195
1267
|
applySnapshot(this.target, this.snapshots[index]);
|
|
1268
|
+
const undo = undoManagersRegistry.get(this.target);
|
|
1269
|
+
if (undo) {
|
|
1270
|
+
const N = this.snapshots.length;
|
|
1271
|
+
const M = undo.historyEntries.length;
|
|
1272
|
+
const undoIndex = this.index - 1 - (N - 1 - M);
|
|
1273
|
+
undo.currentIndex = Math.max(-1, Math.min(M - 1, undoIndex));
|
|
1274
|
+
}
|
|
1196
1275
|
} finally {
|
|
1197
1276
|
this.isApplying = false;
|
|
1198
1277
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -1210,6 +1289,7 @@ var TimeTravelManager = class {
|
|
|
1210
1289
|
this.record();
|
|
1211
1290
|
}
|
|
1212
1291
|
dispose() {
|
|
1292
|
+
timeTravelManagersRegistry.delete(this.target);
|
|
1213
1293
|
if (this.disposer) {
|
|
1214
1294
|
this.disposer();
|
|
1215
1295
|
this.disposer = null;
|
|
@@ -1302,6 +1382,7 @@ function createActionRecorder(target) {
|
|
|
1302
1382
|
export {
|
|
1303
1383
|
setLifecycleHookHandlers,
|
|
1304
1384
|
getIsApplyingSnapshotOrPatch,
|
|
1385
|
+
setIsApplyingSnapshotOrPatch,
|
|
1305
1386
|
getGlobalStore,
|
|
1306
1387
|
setGlobalStore,
|
|
1307
1388
|
resetGlobalStore,
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance } from './undo-
|
|
2
|
-
export { L as CustomTypeOptions, aG as IActionRecorder, aH as IActionRecording, C as IAnyComplexType, D as IAnyMixin, aE as IHistoryEntry, F as IJsonPatch, y as IMSTArray, z as IMSTMap, G as IReversibleJsonPatch, x as IStateTreeNode, aF as ITimeTravelManager, aC as IUndoManager, aD as IUndoManagerOptions, H as IValidationContext, K as IValidationError, J as IValidationResult, A as ModelInstance, E as ModelSelf, B as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, aB as createActionRecorder, aA as createTimeTravelManager, az as createUndoManager, a8 as destroy, a9 as detach, am as findAll, an as findFirst, as as freeze, a2 as getEnv, ag as getGlobalStore, a4 as getIdentifier, af as getMembers, ar as getOrCreatePath, Y as getParent, $ as getParentOfType, a0 as getPath, a1 as getPathParts, av as getRegistryStats, aj as getRelativePath, X as getRoot, N as getSnapshot, ap as getTreeStats, a3 as getType, _ as hasParent, al as haveSameRoot, a5 as isAlive, ak as isAncestor, at as isFrozen, a6 as isRoot, a7 as isStateTreeNode, ao as isValidReference, W as onAction, ay as onLifecycleChange, Q as onPatch, P as onSnapshot, V as recordPatches, ai as resetGlobalStore, ae as resolveIdentifier, ac as resolvePath, ah as setGlobalStore, Z as tryGetParent, ad as tryResolve, au as unfreeze, ab as walk } from './undo-
|
|
1
|
+
import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance } from './undo-DL1pyOkT.mjs';
|
|
2
|
+
export { L as CustomTypeOptions, aG as IActionRecorder, aH as IActionRecording, C as IAnyComplexType, D as IAnyMixin, aE as IHistoryEntry, F as IJsonPatch, y as IMSTArray, z as IMSTMap, G as IReversibleJsonPatch, x as IStateTreeNode, aF as ITimeTravelManager, aC as IUndoManager, aD as IUndoManagerOptions, H as IValidationContext, K as IValidationError, J as IValidationResult, A as ModelInstance, E as ModelSelf, B as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, aB as createActionRecorder, aA as createTimeTravelManager, az as createUndoManager, a8 as destroy, a9 as detach, am as findAll, an as findFirst, as as freeze, a2 as getEnv, ag as getGlobalStore, a4 as getIdentifier, af as getMembers, ar as getOrCreatePath, Y as getParent, $ as getParentOfType, a0 as getPath, a1 as getPathParts, av as getRegistryStats, aj as getRelativePath, X as getRoot, N as getSnapshot, ap as getTreeStats, a3 as getType, _ as hasParent, al as haveSameRoot, a5 as isAlive, ak as isAncestor, at as isFrozen, a6 as isRoot, a7 as isStateTreeNode, ao as isValidReference, W as onAction, ay as onLifecycleChange, Q as onPatch, P as onSnapshot, V as recordPatches, ai as resetGlobalStore, ae as resolveIdentifier, ac as resolvePath, ah as setGlobalStore, Z as tryGetParent, ad as tryResolve, au as unfreeze, ab as walk } from './undo-DL1pyOkT.mjs';
|
|
3
3
|
import 'jotai/vanilla/internals';
|
|
4
4
|
import 'jotai';
|
|
5
5
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance } from './undo-
|
|
2
|
-
export { L as CustomTypeOptions, aG as IActionRecorder, aH as IActionRecording, C as IAnyComplexType, D as IAnyMixin, aE as IHistoryEntry, F as IJsonPatch, y as IMSTArray, z as IMSTMap, G as IReversibleJsonPatch, x as IStateTreeNode, aF as ITimeTravelManager, aC as IUndoManager, aD as IUndoManagerOptions, H as IValidationContext, K as IValidationError, J as IValidationResult, A as ModelInstance, E as ModelSelf, B as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, aB as createActionRecorder, aA as createTimeTravelManager, az as createUndoManager, a8 as destroy, a9 as detach, am as findAll, an as findFirst, as as freeze, a2 as getEnv, ag as getGlobalStore, a4 as getIdentifier, af as getMembers, ar as getOrCreatePath, Y as getParent, $ as getParentOfType, a0 as getPath, a1 as getPathParts, av as getRegistryStats, aj as getRelativePath, X as getRoot, N as getSnapshot, ap as getTreeStats, a3 as getType, _ as hasParent, al as haveSameRoot, a5 as isAlive, ak as isAncestor, at as isFrozen, a6 as isRoot, a7 as isStateTreeNode, ao as isValidReference, W as onAction, ay as onLifecycleChange, Q as onPatch, P as onSnapshot, V as recordPatches, ai as resetGlobalStore, ae as resolveIdentifier, ac as resolvePath, ah as setGlobalStore, Z as tryGetParent, ad as tryResolve, au as unfreeze, ab as walk } from './undo-
|
|
1
|
+
import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance } from './undo-DL1pyOkT.js';
|
|
2
|
+
export { L as CustomTypeOptions, aG as IActionRecorder, aH as IActionRecording, C as IAnyComplexType, D as IAnyMixin, aE as IHistoryEntry, F as IJsonPatch, y as IMSTArray, z as IMSTMap, G as IReversibleJsonPatch, x as IStateTreeNode, aF as ITimeTravelManager, aC as IUndoManager, aD as IUndoManagerOptions, H as IValidationContext, K as IValidationError, J as IValidationResult, A as ModelInstance, E as ModelSelf, B as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, aB as createActionRecorder, aA as createTimeTravelManager, az as createUndoManager, a8 as destroy, a9 as detach, am as findAll, an as findFirst, as as freeze, a2 as getEnv, ag as getGlobalStore, a4 as getIdentifier, af as getMembers, ar as getOrCreatePath, Y as getParent, $ as getParentOfType, a0 as getPath, a1 as getPathParts, av as getRegistryStats, aj as getRelativePath, X as getRoot, N as getSnapshot, ap as getTreeStats, a3 as getType, _ as hasParent, al as haveSameRoot, a5 as isAlive, ak as isAncestor, at as isFrozen, a6 as isRoot, a7 as isStateTreeNode, ao as isValidReference, W as onAction, ay as onLifecycleChange, Q as onPatch, P as onSnapshot, V as recordPatches, ai as resetGlobalStore, ae as resolveIdentifier, ac as resolvePath, ah as setGlobalStore, Z as tryGetParent, ad as tryResolve, au as unfreeze, ab as walk } from './undo-DL1pyOkT.js';
|
|
3
3
|
import 'jotai/vanilla/internals';
|
|
4
4
|
import 'jotai';
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -473,9 +473,12 @@ var identifierFinalizationRegistry = new FinalizationRegistry(
|
|
|
473
473
|
(info) => {
|
|
474
474
|
const typeMap = identifierRegistry.get(info.typeName);
|
|
475
475
|
if (typeMap) {
|
|
476
|
-
typeMap.
|
|
477
|
-
if (
|
|
478
|
-
|
|
476
|
+
const ref = typeMap.get(info.identifier);
|
|
477
|
+
if (!ref || ref.deref() === void 0) {
|
|
478
|
+
typeMap.delete(info.identifier);
|
|
479
|
+
if (typeMap.size === 0) {
|
|
480
|
+
identifierRegistry.delete(info.typeName);
|
|
481
|
+
}
|
|
479
482
|
}
|
|
480
483
|
}
|
|
481
484
|
}
|
|
@@ -502,6 +505,35 @@ function notifyLifecycleChange(node, isAlive2) {
|
|
|
502
505
|
listeners.forEach((listener) => listener(isAlive2));
|
|
503
506
|
}
|
|
504
507
|
}
|
|
508
|
+
function cloneAndSerialize(value) {
|
|
509
|
+
if (value === null || value === void 0) {
|
|
510
|
+
return value;
|
|
511
|
+
}
|
|
512
|
+
if (hasStateTreeNode(value)) {
|
|
513
|
+
return getSnapshotFromNode(getStateTreeNode(value));
|
|
514
|
+
}
|
|
515
|
+
if (Array.isArray(value)) {
|
|
516
|
+
return value.map(cloneAndSerialize);
|
|
517
|
+
}
|
|
518
|
+
if (typeof value === "object") {
|
|
519
|
+
if (value instanceof Map) {
|
|
520
|
+
const res = {};
|
|
521
|
+
for (const [k, v] of value.entries()) {
|
|
522
|
+
res[k] = cloneAndSerialize(v);
|
|
523
|
+
}
|
|
524
|
+
return res;
|
|
525
|
+
}
|
|
526
|
+
const proto = Object.getPrototypeOf(value);
|
|
527
|
+
if (proto === null || proto === Object.prototype) {
|
|
528
|
+
const res = {};
|
|
529
|
+
for (const [k, v] of Object.entries(value)) {
|
|
530
|
+
res[k] = cloneAndSerialize(v);
|
|
531
|
+
}
|
|
532
|
+
return res;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return value;
|
|
536
|
+
}
|
|
505
537
|
var StateTreeNode = class {
|
|
506
538
|
constructor(type, initialValue, env, parent, pathSegment) {
|
|
507
539
|
this.$parent = null;
|
|
@@ -519,6 +551,8 @@ var StateTreeNode = class {
|
|
|
519
551
|
/** Cached snapshot for structural sharing optimization */
|
|
520
552
|
this.cachedSnapshot = void 0;
|
|
521
553
|
this.isSnapshotDirty = true;
|
|
554
|
+
/** Strong reference to the instance proxy to prevent GC of active nodes */
|
|
555
|
+
this.instance = null;
|
|
522
556
|
this.$id = generateNodeId();
|
|
523
557
|
this.$type = type;
|
|
524
558
|
this.$env = env ?? parent?.$env;
|
|
@@ -539,6 +573,7 @@ var StateTreeNode = class {
|
|
|
539
573
|
}
|
|
540
574
|
/** Set the instance reference */
|
|
541
575
|
setInstance(instance) {
|
|
576
|
+
this.instance = instance;
|
|
542
577
|
const entry = nodeRegistry.get(this.$id);
|
|
543
578
|
if (entry && instance && typeof instance === "object") {
|
|
544
579
|
entry.instance = new WeakRef(instance);
|
|
@@ -546,8 +581,7 @@ var StateTreeNode = class {
|
|
|
546
581
|
}
|
|
547
582
|
/** Get the instance */
|
|
548
583
|
getInstance() {
|
|
549
|
-
|
|
550
|
-
return entry?.instance?.deref() ?? null;
|
|
584
|
+
return this.instance;
|
|
551
585
|
}
|
|
552
586
|
/** Get current value from atom */
|
|
553
587
|
getValue() {
|
|
@@ -653,9 +687,27 @@ var StateTreeNode = class {
|
|
|
653
687
|
}
|
|
654
688
|
/** Notify patch listeners */
|
|
655
689
|
notifyPatch(patch, reversePatch) {
|
|
656
|
-
|
|
690
|
+
const serializedPatch = {
|
|
691
|
+
...patch,
|
|
692
|
+
value: "value" in patch ? cloneAndSerialize(patch.value) : void 0
|
|
693
|
+
};
|
|
694
|
+
if (!("value" in patch)) {
|
|
695
|
+
delete serializedPatch.value;
|
|
696
|
+
}
|
|
697
|
+
const serializedReversePatch = {
|
|
698
|
+
...reversePatch,
|
|
699
|
+
value: "value" in reversePatch ? cloneAndSerialize(reversePatch.value) : void 0,
|
|
700
|
+
oldValue: "oldValue" in reversePatch ? cloneAndSerialize(reversePatch.oldValue) : void 0
|
|
701
|
+
};
|
|
702
|
+
if (!("value" in reversePatch)) {
|
|
703
|
+
delete serializedReversePatch.value;
|
|
704
|
+
}
|
|
705
|
+
if (!("oldValue" in reversePatch)) {
|
|
706
|
+
delete serializedReversePatch.oldValue;
|
|
707
|
+
}
|
|
708
|
+
this.patchListeners.forEach((listener) => listener(serializedPatch, serializedReversePatch));
|
|
657
709
|
if (this.$parent) {
|
|
658
|
-
this.$parent.notifyPatch(
|
|
710
|
+
this.$parent.notifyPatch(serializedPatch, serializedReversePatch);
|
|
659
711
|
}
|
|
660
712
|
}
|
|
661
713
|
/** Notify snapshot listeners */
|
|
@@ -692,6 +744,7 @@ var StateTreeNode = class {
|
|
|
692
744
|
/** Destroy this node and all children */
|
|
693
745
|
destroy() {
|
|
694
746
|
if (!this.$isAlive) return;
|
|
747
|
+
this.instance = null;
|
|
695
748
|
lifecycleHookHandlers.runBeforeDestroy?.(this);
|
|
696
749
|
this.children.forEach((child) => child.destroy());
|
|
697
750
|
this.children.clear();
|
|
@@ -3716,6 +3769,8 @@ function createWithDefaults(type, snapshot = {}, env) {
|
|
|
3716
3769
|
}
|
|
3717
3770
|
|
|
3718
3771
|
// src/undo.ts
|
|
3772
|
+
var undoManagersRegistry = /* @__PURE__ */ new WeakMap();
|
|
3773
|
+
var timeTravelManagersRegistry = /* @__PURE__ */ new WeakMap();
|
|
3719
3774
|
var UndoManager = class {
|
|
3720
3775
|
constructor(target, options = {}) {
|
|
3721
3776
|
this.historyEntries = [];
|
|
@@ -3736,6 +3791,7 @@ var UndoManager = class {
|
|
|
3736
3791
|
groupByTime: options.groupByTime ?? false,
|
|
3737
3792
|
groupingWindow: options.groupingWindow ?? 200
|
|
3738
3793
|
};
|
|
3794
|
+
undoManagersRegistry.set(target, this);
|
|
3739
3795
|
this.disposer = onPatch(target, (patch, reversePatch) => {
|
|
3740
3796
|
this.recordPatch(patch, reversePatch);
|
|
3741
3797
|
});
|
|
@@ -3829,6 +3885,13 @@ var UndoManager = class {
|
|
|
3829
3885
|
applyPatch(this.target, entry.patches[i]);
|
|
3830
3886
|
}
|
|
3831
3887
|
this.currentIndex--;
|
|
3888
|
+
const tt = timeTravelManagersRegistry.get(this.target);
|
|
3889
|
+
if (tt) {
|
|
3890
|
+
const N = tt.snapshots.length;
|
|
3891
|
+
const M = this.historyEntries.length;
|
|
3892
|
+
const ttIndex = this.currentIndex + 1 + (N - 1 - M);
|
|
3893
|
+
tt.index = Math.max(0, Math.min(N - 1, ttIndex));
|
|
3894
|
+
}
|
|
3832
3895
|
} finally {
|
|
3833
3896
|
this.isUndoing = false;
|
|
3834
3897
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -3849,6 +3912,13 @@ var UndoManager = class {
|
|
|
3849
3912
|
for (const patch of entry.inversePatches) {
|
|
3850
3913
|
applyPatch(this.target, patch);
|
|
3851
3914
|
}
|
|
3915
|
+
const tt = timeTravelManagersRegistry.get(this.target);
|
|
3916
|
+
if (tt) {
|
|
3917
|
+
const N = tt.snapshots.length;
|
|
3918
|
+
const M = this.historyEntries.length;
|
|
3919
|
+
const ttIndex = this.currentIndex + 1 + (N - 1 - M);
|
|
3920
|
+
tt.index = Math.max(0, Math.min(N - 1, ttIndex));
|
|
3921
|
+
}
|
|
3852
3922
|
} finally {
|
|
3853
3923
|
this.isRedoing = false;
|
|
3854
3924
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -3902,6 +3972,7 @@ var UndoManager = class {
|
|
|
3902
3972
|
}
|
|
3903
3973
|
}
|
|
3904
3974
|
dispose() {
|
|
3975
|
+
undoManagersRegistry.delete(this.target);
|
|
3905
3976
|
if (this.disposer) {
|
|
3906
3977
|
this.disposer();
|
|
3907
3978
|
this.disposer = null;
|
|
@@ -3928,6 +3999,7 @@ var TimeTravelManager = class {
|
|
|
3928
3999
|
this.target = target;
|
|
3929
4000
|
this.maxSnapshots = options.maxSnapshots ?? 50;
|
|
3930
4001
|
this.autoRecord = options.autoRecord ?? false;
|
|
4002
|
+
timeTravelManagersRegistry.set(target, this);
|
|
3931
4003
|
this.record();
|
|
3932
4004
|
if (this.autoRecord) {
|
|
3933
4005
|
this.disposer = onPatch(target, () => {
|
|
@@ -4009,6 +4081,13 @@ var TimeTravelManager = class {
|
|
|
4009
4081
|
try {
|
|
4010
4082
|
this.index = index;
|
|
4011
4083
|
applySnapshot(this.target, this.snapshots[index]);
|
|
4084
|
+
const undo = undoManagersRegistry.get(this.target);
|
|
4085
|
+
if (undo) {
|
|
4086
|
+
const N = this.snapshots.length;
|
|
4087
|
+
const M = undo.historyEntries.length;
|
|
4088
|
+
const undoIndex = this.index - 1 - (N - 1 - M);
|
|
4089
|
+
undo.currentIndex = Math.max(-1, Math.min(M - 1, undoIndex));
|
|
4090
|
+
}
|
|
4012
4091
|
} finally {
|
|
4013
4092
|
this.isApplying = false;
|
|
4014
4093
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -4026,6 +4105,7 @@ var TimeTravelManager = class {
|
|
|
4026
4105
|
this.record();
|
|
4027
4106
|
}
|
|
4028
4107
|
dispose() {
|
|
4108
|
+
timeTravelManagersRegistry.delete(this.target);
|
|
4029
4109
|
if (this.disposer) {
|
|
4030
4110
|
this.disposer();
|
|
4031
4111
|
this.disposer = null;
|
package/dist/index.mjs
CHANGED
package/dist/react.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { ComponentType, FC, ReactNode } from 'react';
|
|
2
|
-
import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-
|
|
3
|
-
export { aI as hasStateTreeNode } from './undo-
|
|
2
|
+
import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-DL1pyOkT.mjs';
|
|
3
|
+
export { aI as hasStateTreeNode } from './undo-DL1pyOkT.mjs';
|
|
4
4
|
import 'jotai/vanilla/internals';
|
|
5
5
|
import 'jotai';
|
|
6
6
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { ComponentType, FC, ReactNode } from 'react';
|
|
2
|
-
import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-
|
|
3
|
-
export { aI as hasStateTreeNode } from './undo-
|
|
2
|
+
import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-DL1pyOkT.js';
|
|
3
|
+
export { aI as hasStateTreeNode } from './undo-DL1pyOkT.js';
|
|
4
4
|
import 'jotai/vanilla/internals';
|
|
5
5
|
import 'jotai';
|
|
6
6
|
|
package/dist/react.js
CHANGED
|
@@ -93,9 +93,12 @@ var identifierFinalizationRegistry = new FinalizationRegistry(
|
|
|
93
93
|
(info) => {
|
|
94
94
|
const typeMap = identifierRegistry.get(info.typeName);
|
|
95
95
|
if (typeMap) {
|
|
96
|
-
typeMap.
|
|
97
|
-
if (
|
|
98
|
-
|
|
96
|
+
const ref = typeMap.get(info.identifier);
|
|
97
|
+
if (!ref || ref.deref() === void 0) {
|
|
98
|
+
typeMap.delete(info.identifier);
|
|
99
|
+
if (typeMap.size === 0) {
|
|
100
|
+
identifierRegistry.delete(info.typeName);
|
|
101
|
+
}
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
}
|
|
@@ -122,6 +125,35 @@ function notifyLifecycleChange(node, isAlive) {
|
|
|
122
125
|
listeners.forEach((listener) => listener(isAlive));
|
|
123
126
|
}
|
|
124
127
|
}
|
|
128
|
+
function cloneAndSerialize(value) {
|
|
129
|
+
if (value === null || value === void 0) {
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
if (hasStateTreeNode(value)) {
|
|
133
|
+
return getSnapshotFromNode(getStateTreeNode(value));
|
|
134
|
+
}
|
|
135
|
+
if (Array.isArray(value)) {
|
|
136
|
+
return value.map(cloneAndSerialize);
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === "object") {
|
|
139
|
+
if (value instanceof Map) {
|
|
140
|
+
const res = {};
|
|
141
|
+
for (const [k, v] of value.entries()) {
|
|
142
|
+
res[k] = cloneAndSerialize(v);
|
|
143
|
+
}
|
|
144
|
+
return res;
|
|
145
|
+
}
|
|
146
|
+
const proto = Object.getPrototypeOf(value);
|
|
147
|
+
if (proto === null || proto === Object.prototype) {
|
|
148
|
+
const res = {};
|
|
149
|
+
for (const [k, v] of Object.entries(value)) {
|
|
150
|
+
res[k] = cloneAndSerialize(v);
|
|
151
|
+
}
|
|
152
|
+
return res;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
125
157
|
var StateTreeNode = class {
|
|
126
158
|
constructor(type, initialValue, env, parent, pathSegment) {
|
|
127
159
|
this.$parent = null;
|
|
@@ -139,6 +171,8 @@ var StateTreeNode = class {
|
|
|
139
171
|
/** Cached snapshot for structural sharing optimization */
|
|
140
172
|
this.cachedSnapshot = void 0;
|
|
141
173
|
this.isSnapshotDirty = true;
|
|
174
|
+
/** Strong reference to the instance proxy to prevent GC of active nodes */
|
|
175
|
+
this.instance = null;
|
|
142
176
|
this.$id = generateNodeId();
|
|
143
177
|
this.$type = type;
|
|
144
178
|
this.$env = env ?? parent?.$env;
|
|
@@ -159,6 +193,7 @@ var StateTreeNode = class {
|
|
|
159
193
|
}
|
|
160
194
|
/** Set the instance reference */
|
|
161
195
|
setInstance(instance) {
|
|
196
|
+
this.instance = instance;
|
|
162
197
|
const entry = nodeRegistry.get(this.$id);
|
|
163
198
|
if (entry && instance && typeof instance === "object") {
|
|
164
199
|
entry.instance = new WeakRef(instance);
|
|
@@ -166,8 +201,7 @@ var StateTreeNode = class {
|
|
|
166
201
|
}
|
|
167
202
|
/** Get the instance */
|
|
168
203
|
getInstance() {
|
|
169
|
-
|
|
170
|
-
return entry?.instance?.deref() ?? null;
|
|
204
|
+
return this.instance;
|
|
171
205
|
}
|
|
172
206
|
/** Get current value from atom */
|
|
173
207
|
getValue() {
|
|
@@ -273,9 +307,27 @@ var StateTreeNode = class {
|
|
|
273
307
|
}
|
|
274
308
|
/** Notify patch listeners */
|
|
275
309
|
notifyPatch(patch, reversePatch) {
|
|
276
|
-
|
|
310
|
+
const serializedPatch = {
|
|
311
|
+
...patch,
|
|
312
|
+
value: "value" in patch ? cloneAndSerialize(patch.value) : void 0
|
|
313
|
+
};
|
|
314
|
+
if (!("value" in patch)) {
|
|
315
|
+
delete serializedPatch.value;
|
|
316
|
+
}
|
|
317
|
+
const serializedReversePatch = {
|
|
318
|
+
...reversePatch,
|
|
319
|
+
value: "value" in reversePatch ? cloneAndSerialize(reversePatch.value) : void 0,
|
|
320
|
+
oldValue: "oldValue" in reversePatch ? cloneAndSerialize(reversePatch.oldValue) : void 0
|
|
321
|
+
};
|
|
322
|
+
if (!("value" in reversePatch)) {
|
|
323
|
+
delete serializedReversePatch.value;
|
|
324
|
+
}
|
|
325
|
+
if (!("oldValue" in reversePatch)) {
|
|
326
|
+
delete serializedReversePatch.oldValue;
|
|
327
|
+
}
|
|
328
|
+
this.patchListeners.forEach((listener) => listener(serializedPatch, serializedReversePatch));
|
|
277
329
|
if (this.$parent) {
|
|
278
|
-
this.$parent.notifyPatch(
|
|
330
|
+
this.$parent.notifyPatch(serializedPatch, serializedReversePatch);
|
|
279
331
|
}
|
|
280
332
|
}
|
|
281
333
|
/** Notify snapshot listeners */
|
|
@@ -312,6 +364,7 @@ var StateTreeNode = class {
|
|
|
312
364
|
/** Destroy this node and all children */
|
|
313
365
|
destroy() {
|
|
314
366
|
if (!this.$isAlive) return;
|
|
367
|
+
this.instance = null;
|
|
315
368
|
lifecycleHookHandlers.runBeforeDestroy?.(this);
|
|
316
369
|
this.children.forEach((child) => child.destroy());
|
|
317
370
|
this.children.clear();
|
|
@@ -560,6 +613,8 @@ function onAction(target, listener) {
|
|
|
560
613
|
}
|
|
561
614
|
|
|
562
615
|
// src/undo.ts
|
|
616
|
+
var undoManagersRegistry = /* @__PURE__ */ new WeakMap();
|
|
617
|
+
var timeTravelManagersRegistry = /* @__PURE__ */ new WeakMap();
|
|
563
618
|
var UndoManager = class {
|
|
564
619
|
constructor(target, options = {}) {
|
|
565
620
|
this.historyEntries = [];
|
|
@@ -580,6 +635,7 @@ var UndoManager = class {
|
|
|
580
635
|
groupByTime: options.groupByTime ?? false,
|
|
581
636
|
groupingWindow: options.groupingWindow ?? 200
|
|
582
637
|
};
|
|
638
|
+
undoManagersRegistry.set(target, this);
|
|
583
639
|
this.disposer = onPatch(target, (patch, reversePatch) => {
|
|
584
640
|
this.recordPatch(patch, reversePatch);
|
|
585
641
|
});
|
|
@@ -673,6 +729,13 @@ var UndoManager = class {
|
|
|
673
729
|
applyPatch(this.target, entry.patches[i]);
|
|
674
730
|
}
|
|
675
731
|
this.currentIndex--;
|
|
732
|
+
const tt = timeTravelManagersRegistry.get(this.target);
|
|
733
|
+
if (tt) {
|
|
734
|
+
const N = tt.snapshots.length;
|
|
735
|
+
const M = this.historyEntries.length;
|
|
736
|
+
const ttIndex = this.currentIndex + 1 + (N - 1 - M);
|
|
737
|
+
tt.index = Math.max(0, Math.min(N - 1, ttIndex));
|
|
738
|
+
}
|
|
676
739
|
} finally {
|
|
677
740
|
this.isUndoing = false;
|
|
678
741
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -693,6 +756,13 @@ var UndoManager = class {
|
|
|
693
756
|
for (const patch of entry.inversePatches) {
|
|
694
757
|
applyPatch(this.target, patch);
|
|
695
758
|
}
|
|
759
|
+
const tt = timeTravelManagersRegistry.get(this.target);
|
|
760
|
+
if (tt) {
|
|
761
|
+
const N = tt.snapshots.length;
|
|
762
|
+
const M = this.historyEntries.length;
|
|
763
|
+
const ttIndex = this.currentIndex + 1 + (N - 1 - M);
|
|
764
|
+
tt.index = Math.max(0, Math.min(N - 1, ttIndex));
|
|
765
|
+
}
|
|
696
766
|
} finally {
|
|
697
767
|
this.isRedoing = false;
|
|
698
768
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -746,6 +816,7 @@ var UndoManager = class {
|
|
|
746
816
|
}
|
|
747
817
|
}
|
|
748
818
|
dispose() {
|
|
819
|
+
undoManagersRegistry.delete(this.target);
|
|
749
820
|
if (this.disposer) {
|
|
750
821
|
this.disposer();
|
|
751
822
|
this.disposer = null;
|
|
@@ -772,6 +843,7 @@ var TimeTravelManager = class {
|
|
|
772
843
|
this.target = target;
|
|
773
844
|
this.maxSnapshots = options.maxSnapshots ?? 50;
|
|
774
845
|
this.autoRecord = options.autoRecord ?? false;
|
|
846
|
+
timeTravelManagersRegistry.set(target, this);
|
|
775
847
|
this.record();
|
|
776
848
|
if (this.autoRecord) {
|
|
777
849
|
this.disposer = onPatch(target, () => {
|
|
@@ -853,6 +925,13 @@ var TimeTravelManager = class {
|
|
|
853
925
|
try {
|
|
854
926
|
this.index = index;
|
|
855
927
|
applySnapshot(this.target, this.snapshots[index]);
|
|
928
|
+
const undo = undoManagersRegistry.get(this.target);
|
|
929
|
+
if (undo) {
|
|
930
|
+
const N = this.snapshots.length;
|
|
931
|
+
const M = undo.historyEntries.length;
|
|
932
|
+
const undoIndex = this.index - 1 - (N - 1 - M);
|
|
933
|
+
undo.currentIndex = Math.max(-1, Math.min(M - 1, undoIndex));
|
|
934
|
+
}
|
|
856
935
|
} finally {
|
|
857
936
|
this.isApplying = false;
|
|
858
937
|
rootNode.$isApplyingHistory = wasApplying;
|
|
@@ -870,6 +949,7 @@ var TimeTravelManager = class {
|
|
|
870
949
|
this.record();
|
|
871
950
|
}
|
|
872
951
|
dispose() {
|
|
952
|
+
timeTravelManagersRegistry.delete(this.target);
|
|
873
953
|
if (this.disposer) {
|
|
874
954
|
this.disposer();
|
|
875
955
|
this.disposer = null;
|
|
@@ -1229,6 +1309,15 @@ function useHydrateStore(target, snapshot, options) {
|
|
|
1229
1309
|
return;
|
|
1230
1310
|
}
|
|
1231
1311
|
const node = getStateTreeNode(target);
|
|
1312
|
+
(0, import_react.useMemo)(() => {
|
|
1313
|
+
const wasApplying = getIsApplyingSnapshotOrPatch();
|
|
1314
|
+
setIsApplyingSnapshotOrPatch(true);
|
|
1315
|
+
try {
|
|
1316
|
+
applySnapshotToNode(node, snapshot);
|
|
1317
|
+
} finally {
|
|
1318
|
+
setIsApplyingSnapshotOrPatch(wasApplying);
|
|
1319
|
+
}
|
|
1320
|
+
}, [node, snapshot]);
|
|
1232
1321
|
const pairs = (0, import_react.useMemo)(() => {
|
|
1233
1322
|
const collectedPairs = [];
|
|
1234
1323
|
function collect(n) {
|