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.
@@ -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.delete(info.identifier);
46
- if (typeMap.size === 0) {
47
- identifierRegistry.delete(info.typeName);
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
- const entry = nodeRegistry.get(this.$id);
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
- this.patchListeners.forEach((listener) => listener(patch, reversePatch));
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(patch, reversePatch);
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
- var UndoManager = class {
956
+ import { atom as atom2 } from "jotai";
957
+ var historyTrackersRegistry = /* @__PURE__ */ new WeakMap();
958
+ var HistoryTracker = class {
904
959
  constructor(target, options = {}) {
905
- this.historyEntries = [];
906
- this.currentIndex = -1;
907
- this.isUndoing = false;
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.options = {
919
- maxHistoryLength: options.maxHistoryLength ?? 100,
920
- groupByTime: options.groupByTime ?? false,
921
- groupingWindow: options.groupingWindow ?? 200
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.isUndoing || this.isRedoing || this.skipRecording) {
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
- if (this.options.groupByTime && this.historyEntries.length > 0 && now - this.lastChangeTime < this.options.groupingWindow && this.currentIndex === this.historyEntries.length - 1) {
979
- const lastEntry = this.historyEntries[this.currentIndex];
980
- lastEntry.patches.push(reversePatch);
981
- lastEntry.inversePatches.push({ ...patch });
982
- lastEntry.timestamp = now;
983
- } else {
984
- if (this.currentIndex < this.historyEntries.length - 1) {
985
- this.historyEntries.splice(this.currentIndex + 1);
986
- }
987
- this.historyEntries.push({
988
- patches: [reversePatch],
989
- inversePatches: [{ ...patch }],
990
- timestamp: now
991
- });
992
- this.currentIndex++;
993
- if (this.historyEntries.length > this.options.maxHistoryLength) {
994
- const excess = this.historyEntries.length - this.options.maxHistoryLength;
995
- this.historyEntries.splice(0, excess);
996
- this.currentIndex -= excess;
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
- if (!this.canUndo) {
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.isUndoing = true;
1070
+ this.isApplyingHistory = true;
1010
1071
  try {
1011
- const entry = this.historyEntries[this.currentIndex];
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.currentIndex--;
1076
+ store.set(this.historyAtom, (prev) => ({
1077
+ ...prev,
1078
+ currentIndex: prev.currentIndex - 1
1079
+ }));
1016
1080
  } finally {
1017
- this.isUndoing = false;
1081
+ this.isApplyingHistory = false;
1018
1082
  rootNode.$isApplyingHistory = wasApplying;
1019
1083
  }
1020
1084
  }
1021
1085
  redo() {
1022
- if (!this.canRedo) {
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.isRedoing = true;
1095
+ this.isApplyingHistory = true;
1030
1096
  try {
1031
- this.currentIndex++;
1032
- const entry = this.historyEntries[this.currentIndex];
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.isRedoing = false;
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
- this.historyEntries = [];
1043
- this.currentIndex = -1;
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
- if (this.currentIndex < this.historyEntries.length - 1) {
1063
- this.historyEntries.splice(this.currentIndex + 1);
1064
- }
1065
- this.historyEntries.push({
1066
- patches: this.currentGroup,
1067
- inversePatches: this.currentGroupInverse,
1068
- timestamp: Date.now()
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 createUndoManager(target, options) {
1101
- return new UndoManager(target, options);
1102
- }
1103
- var TimeTravelManager = class {
1104
- constructor(target, options = {}) {
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
- record() {
1167
- if (this.index < this.snapshots.length - 1) {
1168
- this.snapshots.splice(this.index + 1);
1169
- }
1170
- this.snapshots.push(getSnapshot(this.target));
1171
- this.index++;
1172
- if (this.snapshots.length > this.maxSnapshots) {
1173
- const excess = this.snapshots.length - this.maxSnapshots;
1174
- this.snapshots.splice(0, excess);
1175
- this.index -= excess;
1176
- }
1177
- }
1178
- goBack() {
1179
- if (!this.canGoBack) return;
1180
- this.goTo(this.index - 1);
1181
- }
1182
- goForward() {
1183
- if (!this.canGoForward) return;
1184
- this.goTo(this.index + 1);
1185
- }
1186
- goTo(index) {
1187
- if (index < 0 || index >= this.snapshots.length) return;
1188
- const node = getStateTreeNode(this.target);
1189
- const rootNode = node.getRoot();
1190
- const wasApplying = rootNode.$isApplyingHistory;
1191
- rootNode.$isApplyingHistory = true;
1192
- this.isApplying = true;
1193
- try {
1194
- this.index = index;
1195
- applySnapshot(this.target, this.snapshots[index]);
1196
- } finally {
1197
- this.isApplying = false;
1198
- rootNode.$isApplyingHistory = wasApplying;
1199
- }
1200
- }
1201
- getSnapshot(index) {
1202
- if (index < 0 || index >= this.snapshots.length) {
1203
- throw new Error(`[jotai-state-tree] Invalid snapshot index: ${index}`);
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
- return this.snapshots[index];
1206
- }
1207
- clear() {
1208
- this.snapshots = [];
1209
- this.index = -1;
1210
- this.record();
1311
+ };
1312
+ }
1313
+ function createTimeTravelManager(target, options) {
1314
+ const tracker = getOrCreateHistoryTracker(target, options);
1315
+ if (options?.autoRecord) {
1316
+ tracker.autoRecord = true;
1211
1317
  }
1212
- dispose() {
1213
- if (this.disposer) {
1214
- this.disposer();
1215
- this.disposer = null;
1216
- }
1217
- if (this.actionDisposer) {
1218
- this.actionDisposer();
1219
- this.actionDisposer = null;
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