jotai-state-tree 1.4.1 → 1.4.3
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-5OCZ6YLH.mjs} +340 -205
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +338 -205
- package/dist/index.mjs +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +347 -238
- package/dist/react.mjs +110 -32
- package/dist/{undo-BcBI_BQM.d.mts → undo-quCDYz_6.d.mts} +9 -5
- package/dist/{undo-BcBI_BQM.d.ts → undo-quCDYz_6.d.ts} +9 -5
- 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/__tests__/react.react.test.tsx +112 -0
- package/src/react.ts +111 -33
- package/src/tree.ts +67 -8
- package/src/undo.ts +398 -307
|
@@ -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,26 +953,32 @@ function unfreeze(target) {
|
|
|
900
953
|
}
|
|
901
954
|
|
|
902
955
|
// src/undo.ts
|
|
903
|
-
|
|
956
|
+
import { atom as atom2 } from "jotai";
|
|
957
|
+
var historyTrackersRegistry = /* @__PURE__ */ new WeakMap();
|
|
958
|
+
var HistoryTracker = class {
|
|
904
959
|
constructor(target, options = {}) {
|
|
905
|
-
|
|
906
|
-
this.
|
|
907
|
-
this.
|
|
908
|
-
this.isRedoing = false;
|
|
960
|
+
// Transient state
|
|
961
|
+
this.autoRecord = false;
|
|
962
|
+
this.isApplyingHistory = false;
|
|
909
963
|
this.skipRecording = false;
|
|
910
964
|
this.grouping = false;
|
|
911
965
|
this.actionGrouping = false;
|
|
912
966
|
this.currentGroup = [];
|
|
913
967
|
this.currentGroupInverse = [];
|
|
968
|
+
this.lastChangeTime = 0;
|
|
914
969
|
this.disposer = null;
|
|
915
970
|
this.actionDisposer = null;
|
|
916
|
-
this.lastChangeTime = 0;
|
|
917
971
|
this.target = target;
|
|
918
|
-
this.
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
972
|
+
this.maxHistoryLength = options.maxHistoryLength ?? options.maxSnapshots ?? 100;
|
|
973
|
+
this.groupByTime = options.groupByTime ?? false;
|
|
974
|
+
this.groupingWindow = options.groupingWindow ?? 200;
|
|
975
|
+
this.autoRecord = options.autoRecord ?? false;
|
|
976
|
+
const initialSnapshot = getSnapshot(target);
|
|
977
|
+
this.historyAtom = atom2({
|
|
978
|
+
entries: [],
|
|
979
|
+
currentIndex: -1,
|
|
980
|
+
initialSnapshot
|
|
981
|
+
});
|
|
923
982
|
this.disposer = onPatch(target, (patch, reversePatch) => {
|
|
924
983
|
this.recordPatch(patch, reversePatch);
|
|
925
984
|
});
|
|
@@ -932,32 +991,18 @@ var UndoManager = class {
|
|
|
932
991
|
}
|
|
933
992
|
});
|
|
934
993
|
}
|
|
935
|
-
get canUndo() {
|
|
936
|
-
return this.currentIndex >= 0;
|
|
937
|
-
}
|
|
938
|
-
get canRedo() {
|
|
939
|
-
return this.currentIndex < this.historyEntries.length - 1;
|
|
940
|
-
}
|
|
941
|
-
get undoLevels() {
|
|
942
|
-
return this.currentIndex + 1;
|
|
943
|
-
}
|
|
944
|
-
get redoLevels() {
|
|
945
|
-
return this.historyEntries.length - this.currentIndex - 1;
|
|
946
|
-
}
|
|
947
|
-
get history() {
|
|
948
|
-
return [...this.historyEntries];
|
|
949
|
-
}
|
|
950
|
-
get historyIndex() {
|
|
951
|
-
return this.currentIndex;
|
|
952
|
-
}
|
|
953
994
|
recordPatch(patch, reversePatch) {
|
|
954
|
-
if (this.
|
|
995
|
+
if (!this.autoRecord) {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (this.isApplyingHistory || this.skipRecording) {
|
|
955
999
|
return;
|
|
956
1000
|
}
|
|
957
1001
|
const node = getStateTreeNode(this.target);
|
|
958
1002
|
if (node.getRoot().$isApplyingHistory) {
|
|
959
1003
|
return;
|
|
960
1004
|
}
|
|
1005
|
+
const store = getGlobalStore();
|
|
961
1006
|
const now = Date.now();
|
|
962
1007
|
if (isActionRunning() && !this.grouping) {
|
|
963
1008
|
this.grouping = true;
|
|
@@ -975,72 +1020,172 @@ var UndoManager = class {
|
|
|
975
1020
|
this.currentGroupInverse.push({ ...patch });
|
|
976
1021
|
return;
|
|
977
1022
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1023
|
+
store.set(this.historyAtom, (prev) => {
|
|
1024
|
+
let entries = prev.currentIndex < prev.entries.length - 1 ? prev.entries.slice(0, prev.currentIndex + 1) : [...prev.entries];
|
|
1025
|
+
if (this.groupByTime && entries.length > 0 && now - this.lastChangeTime < this.groupingWindow && prev.currentIndex === prev.entries.length - 1) {
|
|
1026
|
+
const lastEntry = { ...entries[entries.length - 1] };
|
|
1027
|
+
lastEntry.patches = [...lastEntry.patches, reversePatch];
|
|
1028
|
+
lastEntry.inversePatches = [...lastEntry.inversePatches, { ...patch }];
|
|
1029
|
+
lastEntry.timestamp = now;
|
|
1030
|
+
lastEntry.snapshot = getSnapshot(this.target);
|
|
1031
|
+
entries[entries.length - 1] = lastEntry;
|
|
1032
|
+
this.lastChangeTime = now;
|
|
1033
|
+
return {
|
|
1034
|
+
...prev,
|
|
1035
|
+
entries
|
|
1036
|
+
};
|
|
1037
|
+
} else {
|
|
1038
|
+
const newEntry = {
|
|
1039
|
+
patches: [reversePatch],
|
|
1040
|
+
inversePatches: [{ ...patch }],
|
|
1041
|
+
timestamp: now,
|
|
1042
|
+
snapshot: getSnapshot(this.target)
|
|
1043
|
+
};
|
|
1044
|
+
entries.push(newEntry);
|
|
1045
|
+
let newIndex = entries.length - 1;
|
|
1046
|
+
if (entries.length > this.maxHistoryLength) {
|
|
1047
|
+
const excess = entries.length - this.maxHistoryLength;
|
|
1048
|
+
entries = entries.slice(excess);
|
|
1049
|
+
newIndex -= excess;
|
|
1050
|
+
}
|
|
1051
|
+
this.lastChangeTime = now;
|
|
1052
|
+
return {
|
|
1053
|
+
...prev,
|
|
1054
|
+
entries,
|
|
1055
|
+
currentIndex: newIndex
|
|
1056
|
+
};
|
|
997
1057
|
}
|
|
998
|
-
}
|
|
999
|
-
this.lastChangeTime = now;
|
|
1058
|
+
});
|
|
1000
1059
|
}
|
|
1001
1060
|
undo() {
|
|
1002
|
-
|
|
1061
|
+
const store = getGlobalStore();
|
|
1062
|
+
const state = store.get(this.historyAtom);
|
|
1063
|
+
if (state.currentIndex < 0) {
|
|
1003
1064
|
return;
|
|
1004
1065
|
}
|
|
1005
1066
|
const node = getStateTreeNode(this.target);
|
|
1006
1067
|
const rootNode = node.getRoot();
|
|
1007
1068
|
const wasApplying = rootNode.$isApplyingHistory;
|
|
1008
1069
|
rootNode.$isApplyingHistory = true;
|
|
1009
|
-
this.
|
|
1070
|
+
this.isApplyingHistory = true;
|
|
1010
1071
|
try {
|
|
1011
|
-
const entry =
|
|
1072
|
+
const entry = state.entries[state.currentIndex];
|
|
1012
1073
|
for (let i = entry.patches.length - 1; i >= 0; i--) {
|
|
1013
1074
|
applyPatch(this.target, entry.patches[i]);
|
|
1014
1075
|
}
|
|
1015
|
-
this.
|
|
1076
|
+
store.set(this.historyAtom, (prev) => ({
|
|
1077
|
+
...prev,
|
|
1078
|
+
currentIndex: prev.currentIndex - 1
|
|
1079
|
+
}));
|
|
1016
1080
|
} finally {
|
|
1017
|
-
this.
|
|
1081
|
+
this.isApplyingHistory = false;
|
|
1018
1082
|
rootNode.$isApplyingHistory = wasApplying;
|
|
1019
1083
|
}
|
|
1020
1084
|
}
|
|
1021
1085
|
redo() {
|
|
1022
|
-
|
|
1086
|
+
const store = getGlobalStore();
|
|
1087
|
+
const state = store.get(this.historyAtom);
|
|
1088
|
+
if (state.currentIndex >= state.entries.length - 1) {
|
|
1023
1089
|
return;
|
|
1024
1090
|
}
|
|
1025
1091
|
const node = getStateTreeNode(this.target);
|
|
1026
1092
|
const rootNode = node.getRoot();
|
|
1027
1093
|
const wasApplying = rootNode.$isApplyingHistory;
|
|
1028
1094
|
rootNode.$isApplyingHistory = true;
|
|
1029
|
-
this.
|
|
1095
|
+
this.isApplyingHistory = true;
|
|
1030
1096
|
try {
|
|
1031
|
-
|
|
1032
|
-
const entry =
|
|
1097
|
+
const nextIndex = state.currentIndex + 1;
|
|
1098
|
+
const entry = state.entries[nextIndex];
|
|
1033
1099
|
for (const patch of entry.inversePatches) {
|
|
1034
1100
|
applyPatch(this.target, patch);
|
|
1035
1101
|
}
|
|
1102
|
+
store.set(this.historyAtom, (prev) => ({
|
|
1103
|
+
...prev,
|
|
1104
|
+
currentIndex: nextIndex
|
|
1105
|
+
}));
|
|
1106
|
+
} finally {
|
|
1107
|
+
this.isApplyingHistory = false;
|
|
1108
|
+
rootNode.$isApplyingHistory = wasApplying;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
goTo(index) {
|
|
1112
|
+
const store = getGlobalStore();
|
|
1113
|
+
const state = store.get(this.historyAtom);
|
|
1114
|
+
const maxIdx = state.entries.length;
|
|
1115
|
+
if (index < 0 || index > maxIdx) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const node = getStateTreeNode(this.target);
|
|
1119
|
+
const rootNode = node.getRoot();
|
|
1120
|
+
const wasApplying = rootNode.$isApplyingHistory;
|
|
1121
|
+
rootNode.$isApplyingHistory = true;
|
|
1122
|
+
this.isApplyingHistory = true;
|
|
1123
|
+
try {
|
|
1124
|
+
const targetSnapshot = index === 0 ? state.initialSnapshot : state.entries[index - 1].snapshot;
|
|
1125
|
+
applySnapshot(this.target, targetSnapshot);
|
|
1126
|
+
store.set(this.historyAtom, (prev) => ({
|
|
1127
|
+
...prev,
|
|
1128
|
+
currentIndex: index - 1
|
|
1129
|
+
}));
|
|
1036
1130
|
} finally {
|
|
1037
|
-
this.
|
|
1131
|
+
this.isApplyingHistory = false;
|
|
1038
1132
|
rootNode.$isApplyingHistory = wasApplying;
|
|
1039
1133
|
}
|
|
1040
1134
|
}
|
|
1135
|
+
goBack() {
|
|
1136
|
+
const store = getGlobalStore();
|
|
1137
|
+
const state = store.get(this.historyAtom);
|
|
1138
|
+
const currentSnapshotIndex = state.currentIndex + 1;
|
|
1139
|
+
if (currentSnapshotIndex > 0) {
|
|
1140
|
+
this.goTo(currentSnapshotIndex - 1);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
goForward() {
|
|
1144
|
+
const store = getGlobalStore();
|
|
1145
|
+
const state = store.get(this.historyAtom);
|
|
1146
|
+
const currentSnapshotIndex = state.currentIndex + 1;
|
|
1147
|
+
if (currentSnapshotIndex < state.entries.length) {
|
|
1148
|
+
this.goTo(currentSnapshotIndex + 1);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
record() {
|
|
1152
|
+
const store = getGlobalStore();
|
|
1153
|
+
const state = store.get(this.historyAtom);
|
|
1154
|
+
let entries = state.currentIndex < state.entries.length - 1 ? state.entries.slice(0, state.currentIndex + 1) : [...state.entries];
|
|
1155
|
+
const newEntry = {
|
|
1156
|
+
patches: [],
|
|
1157
|
+
inversePatches: [],
|
|
1158
|
+
timestamp: Date.now(),
|
|
1159
|
+
snapshot: getSnapshot(this.target)
|
|
1160
|
+
};
|
|
1161
|
+
entries.push(newEntry);
|
|
1162
|
+
let newIndex = entries.length - 1;
|
|
1163
|
+
if (entries.length > this.maxHistoryLength) {
|
|
1164
|
+
const excess = entries.length - this.maxHistoryLength;
|
|
1165
|
+
entries = entries.slice(excess);
|
|
1166
|
+
newIndex -= excess;
|
|
1167
|
+
}
|
|
1168
|
+
store.set(this.historyAtom, {
|
|
1169
|
+
...state,
|
|
1170
|
+
entries,
|
|
1171
|
+
currentIndex: newIndex
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
getSnapshot(index) {
|
|
1175
|
+
const store = getGlobalStore();
|
|
1176
|
+
const state = store.get(this.historyAtom);
|
|
1177
|
+
if (index < 0 || index > state.entries.length) {
|
|
1178
|
+
throw new Error(`[jotai-state-tree] Invalid snapshot index: ${index}`);
|
|
1179
|
+
}
|
|
1180
|
+
return index === 0 ? state.initialSnapshot : state.entries[index - 1].snapshot;
|
|
1181
|
+
}
|
|
1041
1182
|
clear() {
|
|
1042
|
-
|
|
1043
|
-
this.
|
|
1183
|
+
const store = getGlobalStore();
|
|
1184
|
+
store.set(this.historyAtom, {
|
|
1185
|
+
entries: [],
|
|
1186
|
+
currentIndex: -1,
|
|
1187
|
+
initialSnapshot: getSnapshot(this.target)
|
|
1188
|
+
});
|
|
1044
1189
|
this.currentGroup = [];
|
|
1045
1190
|
this.currentGroupInverse = [];
|
|
1046
1191
|
this.grouping = false;
|
|
@@ -1059,20 +1204,28 @@ var UndoManager = class {
|
|
|
1059
1204
|
this.grouping = false;
|
|
1060
1205
|
this.actionGrouping = false;
|
|
1061
1206
|
if (this.currentGroup.length > 0) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1207
|
+
const store = getGlobalStore();
|
|
1208
|
+
store.set(this.historyAtom, (prev) => {
|
|
1209
|
+
let entries = prev.currentIndex < prev.entries.length - 1 ? prev.entries.slice(0, prev.currentIndex + 1) : [...prev.entries];
|
|
1210
|
+
const newEntry = {
|
|
1211
|
+
patches: [...this.currentGroup],
|
|
1212
|
+
inversePatches: [...this.currentGroupInverse],
|
|
1213
|
+
timestamp: Date.now(),
|
|
1214
|
+
snapshot: getSnapshot(this.target)
|
|
1215
|
+
};
|
|
1216
|
+
entries.push(newEntry);
|
|
1217
|
+
let newIndex = entries.length - 1;
|
|
1218
|
+
if (entries.length > this.maxHistoryLength) {
|
|
1219
|
+
const excess = entries.length - this.maxHistoryLength;
|
|
1220
|
+
entries = entries.slice(excess);
|
|
1221
|
+
newIndex -= excess;
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
...prev,
|
|
1225
|
+
entries,
|
|
1226
|
+
currentIndex: newIndex
|
|
1227
|
+
};
|
|
1069
1228
|
});
|
|
1070
|
-
this.currentIndex++;
|
|
1071
|
-
if (this.historyEntries.length > this.options.maxHistoryLength) {
|
|
1072
|
-
const excess = this.historyEntries.length - this.options.maxHistoryLength;
|
|
1073
|
-
this.historyEntries.splice(0, excess);
|
|
1074
|
-
this.currentIndex -= excess;
|
|
1075
|
-
}
|
|
1076
1229
|
}
|
|
1077
1230
|
this.currentGroup = [];
|
|
1078
1231
|
this.currentGroupInverse = [];
|
|
@@ -1086,6 +1239,7 @@ var UndoManager = class {
|
|
|
1086
1239
|
}
|
|
1087
1240
|
}
|
|
1088
1241
|
dispose() {
|
|
1242
|
+
historyTrackersRegistry.delete(this.target);
|
|
1089
1243
|
if (this.disposer) {
|
|
1090
1244
|
this.disposer();
|
|
1091
1245
|
this.disposer = null;
|
|
@@ -1094,134 +1248,113 @@ var UndoManager = class {
|
|
|
1094
1248
|
this.actionDisposer();
|
|
1095
1249
|
this.actionDisposer = null;
|
|
1096
1250
|
}
|
|
1097
|
-
this.clear();
|
|
1098
1251
|
}
|
|
1099
1252
|
};
|
|
1100
|
-
function
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
this.snapshots = [];
|
|
1106
|
-
this.index = -1;
|
|
1107
|
-
this.isApplying = false;
|
|
1108
|
-
this.disposer = null;
|
|
1109
|
-
this.actionDisposer = null;
|
|
1110
|
-
this.pendingRecord = false;
|
|
1111
|
-
this.actionGrouping = false;
|
|
1112
|
-
this.target = target;
|
|
1113
|
-
this.maxSnapshots = options.maxSnapshots ?? 50;
|
|
1114
|
-
this.autoRecord = options.autoRecord ?? false;
|
|
1115
|
-
this.record();
|
|
1116
|
-
if (this.autoRecord) {
|
|
1117
|
-
this.disposer = onPatch(target, () => {
|
|
1118
|
-
if (this.isApplying) return;
|
|
1119
|
-
const node = getStateTreeNode(this.target);
|
|
1120
|
-
if (node.getRoot().$isApplyingHistory) {
|
|
1121
|
-
return;
|
|
1122
|
-
}
|
|
1123
|
-
if (isActionRunning()) {
|
|
1124
|
-
this.pendingRecord = true;
|
|
1125
|
-
if (!this.actionGrouping) {
|
|
1126
|
-
this.actionGrouping = true;
|
|
1127
|
-
Promise.resolve().then(() => {
|
|
1128
|
-
if (this.actionGrouping) {
|
|
1129
|
-
this.commitPendingRecord();
|
|
1130
|
-
}
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
} else {
|
|
1134
|
-
this.record();
|
|
1135
|
-
}
|
|
1136
|
-
});
|
|
1137
|
-
this.actionDisposer = onAction(target, () => {
|
|
1138
|
-
const current = getCurrentAction();
|
|
1139
|
-
if (current && !current.parent) {
|
|
1140
|
-
if (this.actionGrouping) {
|
|
1141
|
-
this.commitPendingRecord();
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
commitPendingRecord() {
|
|
1148
|
-
this.actionGrouping = false;
|
|
1149
|
-
if (this.pendingRecord) {
|
|
1150
|
-
this.pendingRecord = false;
|
|
1151
|
-
this.record();
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
get currentIndex() {
|
|
1155
|
-
return this.index;
|
|
1156
|
-
}
|
|
1157
|
-
get snapshotCount() {
|
|
1158
|
-
return this.snapshots.length;
|
|
1159
|
-
}
|
|
1160
|
-
get canGoBack() {
|
|
1161
|
-
return this.index > 0;
|
|
1162
|
-
}
|
|
1163
|
-
get canGoForward() {
|
|
1164
|
-
return this.index < this.snapshots.length - 1;
|
|
1253
|
+
function getOrCreateHistoryTracker(target, options = {}) {
|
|
1254
|
+
let tracker = historyTrackersRegistry.get(target);
|
|
1255
|
+
if (!tracker) {
|
|
1256
|
+
tracker = new HistoryTracker(target, options);
|
|
1257
|
+
historyTrackersRegistry.set(target, tracker);
|
|
1165
1258
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1259
|
+
return tracker;
|
|
1260
|
+
}
|
|
1261
|
+
function createUndoManager(target, options) {
|
|
1262
|
+
const tracker = getOrCreateHistoryTracker(target, options);
|
|
1263
|
+
tracker.autoRecord = true;
|
|
1264
|
+
const store = getGlobalStore();
|
|
1265
|
+
return {
|
|
1266
|
+
get canUndo() {
|
|
1267
|
+
const state = store.get(tracker.historyAtom);
|
|
1268
|
+
return state.currentIndex >= 0;
|
|
1269
|
+
},
|
|
1270
|
+
get canRedo() {
|
|
1271
|
+
const state = store.get(tracker.historyAtom);
|
|
1272
|
+
return state.currentIndex < state.entries.length - 1;
|
|
1273
|
+
},
|
|
1274
|
+
get undoLevels() {
|
|
1275
|
+
const state = store.get(tracker.historyAtom);
|
|
1276
|
+
return state.currentIndex + 1;
|
|
1277
|
+
},
|
|
1278
|
+
get redoLevels() {
|
|
1279
|
+
const state = store.get(tracker.historyAtom);
|
|
1280
|
+
return state.entries.length - state.currentIndex - 1;
|
|
1281
|
+
},
|
|
1282
|
+
get history() {
|
|
1283
|
+
const state = store.get(tracker.historyAtom);
|
|
1284
|
+
return state.entries;
|
|
1285
|
+
},
|
|
1286
|
+
get historyIndex() {
|
|
1287
|
+
const state = store.get(tracker.historyAtom);
|
|
1288
|
+
return state.currentIndex;
|
|
1289
|
+
},
|
|
1290
|
+
undo() {
|
|
1291
|
+
tracker.undo();
|
|
1292
|
+
},
|
|
1293
|
+
redo() {
|
|
1294
|
+
tracker.redo();
|
|
1295
|
+
},
|
|
1296
|
+
clear() {
|
|
1297
|
+
tracker.clear();
|
|
1298
|
+
},
|
|
1299
|
+
startGroup() {
|
|
1300
|
+
tracker.startGroup();
|
|
1301
|
+
},
|
|
1302
|
+
endGroup() {
|
|
1303
|
+
tracker.endGroup();
|
|
1304
|
+
},
|
|
1305
|
+
withoutUndo(fn) {
|
|
1306
|
+
return tracker.withoutUndo(fn);
|
|
1307
|
+
},
|
|
1308
|
+
dispose() {
|
|
1309
|
+
tracker.dispose();
|
|
1204
1310
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
function createTimeTravelManager(target, options) {
|
|
1314
|
+
const tracker = getOrCreateHistoryTracker(target, options);
|
|
1315
|
+
if (options?.autoRecord) {
|
|
1316
|
+
tracker.autoRecord = true;
|
|
1211
1317
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1318
|
+
const store = getGlobalStore();
|
|
1319
|
+
return {
|
|
1320
|
+
get currentIndex() {
|
|
1321
|
+
const state = store.get(tracker.historyAtom);
|
|
1322
|
+
return state.currentIndex + 1;
|
|
1323
|
+
},
|
|
1324
|
+
get snapshotCount() {
|
|
1325
|
+
const state = store.get(tracker.historyAtom);
|
|
1326
|
+
return state.entries.length + 1;
|
|
1327
|
+
},
|
|
1328
|
+
get canGoBack() {
|
|
1329
|
+
const state = store.get(tracker.historyAtom);
|
|
1330
|
+
return state.currentIndex + 1 > 0;
|
|
1331
|
+
},
|
|
1332
|
+
get canGoForward() {
|
|
1333
|
+
const state = store.get(tracker.historyAtom);
|
|
1334
|
+
return state.currentIndex + 1 < state.entries.length + 1 - 1;
|
|
1335
|
+
},
|
|
1336
|
+
record() {
|
|
1337
|
+
tracker.record();
|
|
1338
|
+
},
|
|
1339
|
+
goBack() {
|
|
1340
|
+
tracker.goBack();
|
|
1341
|
+
},
|
|
1342
|
+
goForward() {
|
|
1343
|
+
tracker.goForward();
|
|
1344
|
+
},
|
|
1345
|
+
goTo(index) {
|
|
1346
|
+
tracker.goTo(index);
|
|
1347
|
+
},
|
|
1348
|
+
getSnapshot(index) {
|
|
1349
|
+
return tracker.getSnapshot(index);
|
|
1350
|
+
},
|
|
1351
|
+
clear() {
|
|
1352
|
+
tracker.clear();
|
|
1353
|
+
},
|
|
1354
|
+
dispose() {
|
|
1355
|
+
tracker.dispose();
|
|
1220
1356
|
}
|
|
1221
|
-
}
|
|
1222
|
-
};
|
|
1223
|
-
function createTimeTravelManager(target, options) {
|
|
1224
|
-
return new TimeTravelManager(target, options);
|
|
1357
|
+
};
|
|
1225
1358
|
}
|
|
1226
1359
|
var ActionRecorder = class {
|
|
1227
1360
|
constructor(target) {
|
|
@@ -1302,6 +1435,7 @@ function createActionRecorder(target) {
|
|
|
1302
1435
|
export {
|
|
1303
1436
|
setLifecycleHookHandlers,
|
|
1304
1437
|
getIsApplyingSnapshotOrPatch,
|
|
1438
|
+
setIsApplyingSnapshotOrPatch,
|
|
1305
1439
|
getGlobalStore,
|
|
1306
1440
|
setGlobalStore,
|
|
1307
1441
|
resetGlobalStore,
|
|
@@ -1362,6 +1496,7 @@ export {
|
|
|
1362
1496
|
freeze,
|
|
1363
1497
|
isFrozen,
|
|
1364
1498
|
unfreeze,
|
|
1499
|
+
getOrCreateHistoryTracker,
|
|
1365
1500
|
createUndoManager,
|
|
1366
1501
|
createTimeTravelManager,
|
|
1367
1502
|
createActionRecorder
|