react-form-rewind 0.1.0 → 0.2.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/dist/index.d.mts CHANGED
@@ -35,4 +35,6 @@ interface UseFormHistoryReturn<T> {
35
35
  future: HistoryEntry<T>[];
36
36
  }
37
37
 
38
- export type { HistoryEntry, HistoryStack, PersistOptions, UseFormHistoryOptions, UseFormHistoryReturn };
38
+ declare function useFormHistory<T>(initialState: T, options?: UseFormHistoryOptions<T>): UseFormHistoryReturn<T>;
39
+
40
+ export { type HistoryEntry, type HistoryStack, type PersistOptions, type UseFormHistoryOptions, type UseFormHistoryReturn, useFormHistory };
package/dist/index.d.ts CHANGED
@@ -35,4 +35,6 @@ interface UseFormHistoryReturn<T> {
35
35
  future: HistoryEntry<T>[];
36
36
  }
37
37
 
38
- export type { HistoryEntry, HistoryStack, PersistOptions, UseFormHistoryOptions, UseFormHistoryReturn };
38
+ declare function useFormHistory<T>(initialState: T, options?: UseFormHistoryOptions<T>): UseFormHistoryReturn<T>;
39
+
40
+ export { type HistoryEntry, type HistoryStack, type PersistOptions, type UseFormHistoryOptions, type UseFormHistoryReturn, useFormHistory };
package/dist/index.js CHANGED
@@ -1,4 +1,115 @@
1
1
  'use strict';
2
2
 
3
+ var react = require('react');
4
+
5
+ // src/useFormHistory.ts
6
+ var DEFAULT_MAX_HISTORY = 100;
7
+ function useFormHistory(initialState, options = {}) {
8
+ const { maxHistory = DEFAULT_MAX_HISTORY, onUndo, onRedo, onSnapshot } = options;
9
+ const pastRef = react.useRef([]);
10
+ const futureRef = react.useRef([]);
11
+ const [state, setStateRaw] = react.useState(initialState);
12
+ const stateRef = react.useRef(initialState);
13
+ stateRef.current = state;
14
+ const [past, setPast] = react.useState([]);
15
+ const [future, setFuture] = react.useState([]);
16
+ const pushToPast = react.useCallback(
17
+ (entry) => {
18
+ const next = [...pastRef.current, entry];
19
+ if (next.length > maxHistory) {
20
+ next.splice(0, next.length - maxHistory);
21
+ }
22
+ pastRef.current = next;
23
+ setPast(next);
24
+ },
25
+ [maxHistory]
26
+ );
27
+ const setState = react.useCallback(
28
+ (value, label) => {
29
+ const prev = stateRef.current;
30
+ const next = typeof value === "function" ? value(prev) : value;
31
+ if (Object.is(prev, next)) return;
32
+ const entry = { state: prev, timestamp: Date.now(), label };
33
+ pushToPast(entry);
34
+ futureRef.current = [];
35
+ setFuture([]);
36
+ setStateRaw(next);
37
+ stateRef.current = next;
38
+ },
39
+ [pushToPast]
40
+ );
41
+ const undo = react.useCallback(() => {
42
+ if (pastRef.current.length === 0) return;
43
+ const prev = pastRef.current[pastRef.current.length - 1];
44
+ const newPast = pastRef.current.slice(0, -1);
45
+ pastRef.current = newPast;
46
+ setPast(newPast);
47
+ const currentEntry = {
48
+ state: stateRef.current,
49
+ timestamp: Date.now()
50
+ };
51
+ futureRef.current = [...futureRef.current, currentEntry];
52
+ setFuture(futureRef.current);
53
+ setStateRaw(prev.state);
54
+ stateRef.current = prev.state;
55
+ onUndo?.(prev.state);
56
+ }, [onUndo]);
57
+ const redo = react.useCallback(() => {
58
+ if (futureRef.current.length === 0) return;
59
+ const next = futureRef.current[futureRef.current.length - 1];
60
+ const newFuture = futureRef.current.slice(0, -1);
61
+ futureRef.current = newFuture;
62
+ setFuture(newFuture);
63
+ const currentEntry = {
64
+ state: stateRef.current,
65
+ timestamp: Date.now()
66
+ };
67
+ pastRef.current = [...pastRef.current, currentEntry];
68
+ setPast(pastRef.current);
69
+ setStateRaw(next.state);
70
+ stateRef.current = next.state;
71
+ onRedo?.(next.state);
72
+ }, [onRedo]);
73
+ const clearHistory = react.useCallback(() => {
74
+ pastRef.current = [];
75
+ futureRef.current = [];
76
+ setPast([]);
77
+ setFuture([]);
78
+ }, []);
79
+ const snapshot = react.useCallback(
80
+ (label) => {
81
+ const entry = {
82
+ state: stateRef.current,
83
+ timestamp: Date.now(),
84
+ label
85
+ };
86
+ pushToPast(entry);
87
+ futureRef.current = [];
88
+ setFuture([]);
89
+ onSnapshot?.(entry);
90
+ },
91
+ [pushToPast, onSnapshot]
92
+ );
93
+ const clearDraft = react.useCallback(() => {
94
+ clearHistory();
95
+ setStateRaw(initialState);
96
+ stateRef.current = initialState;
97
+ }, [clearHistory, initialState]);
98
+ return {
99
+ state,
100
+ setState,
101
+ undo,
102
+ redo,
103
+ canUndo: past.length > 0,
104
+ canRedo: future.length > 0,
105
+ clearHistory,
106
+ clearDraft,
107
+ snapshot,
108
+ past,
109
+ future
110
+ };
111
+ }
112
+
113
+ exports.useFormHistory = useFormHistory;
3
114
  //# sourceMappingURL=index.js.map
4
115
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
1
+ {"version":3,"sources":["../src/useFormHistory.ts"],"names":["useRef","useState","useCallback"],"mappings":";;;;;AAGA,IAAM,mBAAA,GAAsB,GAAA;AAErB,SAAS,cAAA,CACd,YAAA,EACA,OAAA,GAAoC,EAAC,EACZ;AACzB,EAAA,MAAM,EAAE,UAAA,GAAa,mBAAA,EAAqB,MAAA,EAAQ,MAAA,EAAQ,YAAW,GAAI,OAAA;AAEzE,EAAA,MAAM,OAAA,GAAUA,YAAA,CAA0B,EAAE,CAAA;AAC5C,EAAA,MAAM,SAAA,GAAYA,YAAA,CAA0B,EAAE,CAAA;AAE9C,EAAA,MAAM,CAAC,KAAA,EAAO,WAAW,CAAA,GAAIC,eAAY,YAAY,CAAA;AACrD,EAAA,MAAM,QAAA,GAAWD,aAAU,YAAY,CAAA;AACvC,EAAA,QAAA,CAAS,OAAA,GAAU,KAAA;AAEnB,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIC,cAAA,CAA4B,EAAE,CAAA;AACtD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIA,cAAA,CAA4B,EAAE,CAAA;AAE1D,EAAA,MAAM,UAAA,GAAaC,iBAAA;AAAA,IACjB,CAAC,KAAA,KAA2B;AAC1B,MAAA,MAAM,IAAA,GAAO,CAAC,GAAG,OAAA,CAAQ,SAAS,KAAK,CAAA;AACvC,MAAA,IAAI,IAAA,CAAK,SAAS,UAAA,EAAY;AAC5B,QAAA,IAAA,CAAK,MAAA,CAAO,CAAA,EAAG,IAAA,CAAK,MAAA,GAAS,UAAU,CAAA;AAAA,MACzC;AACA,MAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,MAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,IACd,CAAA;AAAA,IACA,CAAC,UAAU;AAAA,GACb;AAEA,EAAA,MAAM,QAAA,GAAWA,iBAAA;AAAA,IACf,CAAC,OAA6B,KAAA,KAAmB;AAC/C,MAAA,MAAM,OAAO,QAAA,CAAS,OAAA;AACtB,MAAA,MAAM,OAAO,OAAO,KAAA,KAAU,UAAA,GAAc,KAAA,CAAyB,IAAI,CAAA,GAAI,KAAA;AAE7E,MAAA,IAAI,MAAA,CAAO,EAAA,CAAG,IAAA,EAAM,IAAI,CAAA,EAAG;AAE3B,MAAA,MAAM,KAAA,GAAyB,EAAE,KAAA,EAAO,IAAA,EAAM,WAAW,IAAA,CAAK,GAAA,IAAO,KAAA,EAAM;AAE3E,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA,SAAA,CAAU,UAAU,EAAC;AACrB,MAAA,SAAA,CAAU,EAAE,CAAA;AACZ,MAAA,WAAA,CAAY,IAAI,CAAA;AAChB,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AAAA,IACrB,CAAA;AAAA,IACA,CAAC,UAAU;AAAA,GACb;AAEA,EAAA,MAAM,IAAA,GAAOA,kBAAY,MAAM;AAC7B,IAAA,IAAI,OAAA,CAAQ,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG;AAElC,IAAA,MAAM,OAAO,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAC,CAAA;AACvD,IAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,EAAE,CAAA;AAC3C,IAAA,OAAA,CAAQ,OAAA,GAAU,OAAA;AAClB,IAAA,OAAA,CAAQ,OAAO,CAAA;AAEf,IAAA,MAAM,YAAA,GAAgC;AAAA,MACpC,OAAO,QAAA,CAAS,OAAA;AAAA,MAChB,SAAA,EAAW,KAAK,GAAA;AAAI,KACtB;AACA,IAAA,SAAA,CAAU,OAAA,GAAU,CAAC,GAAG,SAAA,CAAU,SAAS,YAAY,CAAA;AACvD,IAAA,SAAA,CAAU,UAAU,OAAO,CAAA;AAE3B,IAAA,WAAA,CAAY,KAAK,KAAK,CAAA;AACtB,IAAA,QAAA,CAAS,UAAU,IAAA,CAAK,KAAA;AACxB,IAAA,MAAA,GAAS,KAAK,KAAK,CAAA;AAAA,EACrB,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,IAAA,GAAOA,kBAAY,MAAM;AAC7B,IAAA,IAAI,SAAA,CAAU,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG;AAEpC,IAAA,MAAM,OAAO,SAAA,CAAU,OAAA,CAAQ,SAAA,CAAU,OAAA,CAAQ,SAAS,CAAC,CAAA;AAC3D,IAAA,MAAM,SAAA,GAAY,SAAA,CAAU,OAAA,CAAQ,KAAA,CAAM,GAAG,EAAE,CAAA;AAC/C,IAAA,SAAA,CAAU,OAAA,GAAU,SAAA;AACpB,IAAA,SAAA,CAAU,SAAS,CAAA;AAEnB,IAAA,MAAM,YAAA,GAAgC;AAAA,MACpC,OAAO,QAAA,CAAS,OAAA;AAAA,MAChB,SAAA,EAAW,KAAK,GAAA;AAAI,KACtB;AACA,IAAA,OAAA,CAAQ,OAAA,GAAU,CAAC,GAAG,OAAA,CAAQ,SAAS,YAAY,CAAA;AACnD,IAAA,OAAA,CAAQ,QAAQ,OAAO,CAAA;AAEvB,IAAA,WAAA,CAAY,KAAK,KAAK,CAAA;AACtB,IAAA,QAAA,CAAS,UAAU,IAAA,CAAK,KAAA;AACxB,IAAA,MAAA,GAAS,KAAK,KAAK,CAAA;AAAA,EACrB,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,YAAA,GAAeA,kBAAY,MAAM;AACrC,IAAA,OAAA,CAAQ,UAAU,EAAC;AACnB,IAAA,SAAA,CAAU,UAAU,EAAC;AACrB,IAAA,OAAA,CAAQ,EAAE,CAAA;AACV,IAAA,SAAA,CAAU,EAAE,CAAA;AAAA,EACd,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,QAAA,GAAWA,iBAAA;AAAA,IACf,CAAC,KAAA,KAAmB;AAClB,MAAA,MAAM,KAAA,GAAyB;AAAA,QAC7B,OAAO,QAAA,CAAS,OAAA;AAAA,QAChB,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,QACpB;AAAA,OACF;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA,SAAA,CAAU,UAAU,EAAC;AACrB,MAAA,SAAA,CAAU,EAAE,CAAA;AACZ,MAAA,UAAA,GAAa,KAAK,CAAA;AAAA,IACpB,CAAA;AAAA,IACA,CAAC,YAAY,UAAU;AAAA,GACzB;AAEA,EAAA,MAAM,UAAA,GAAaA,kBAAY,MAAM;AACnC,IAAA,YAAA,EAAa;AACb,IAAA,WAAA,CAAY,YAAY,CAAA;AACxB,IAAA,QAAA,CAAS,OAAA,GAAU,YAAA;AAAA,EACrB,CAAA,EAAG,CAAC,YAAA,EAAc,YAAY,CAAC,CAAA;AAE/B,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,QAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA;AAAA,IACA,OAAA,EAAS,KAAK,MAAA,GAAS,CAAA;AAAA,IACvB,OAAA,EAAS,OAAO,MAAA,GAAS,CAAA;AAAA,IACzB,YAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import { useCallback, useRef, useState } from \"react\";\nimport type { HistoryEntry, UseFormHistoryOptions, UseFormHistoryReturn } from \"./types\";\n\nconst DEFAULT_MAX_HISTORY = 100;\n\nexport function useFormHistory<T>(\n initialState: T,\n options: UseFormHistoryOptions<T> = {},\n): UseFormHistoryReturn<T> {\n const { maxHistory = DEFAULT_MAX_HISTORY, onUndo, onRedo, onSnapshot } = options;\n\n const pastRef = useRef<HistoryEntry<T>[]>([]);\n const futureRef = useRef<HistoryEntry<T>[]>([]);\n\n const [state, setStateRaw] = useState<T>(initialState);\n const stateRef = useRef<T>(initialState);\n stateRef.current = state;\n\n const [past, setPast] = useState<HistoryEntry<T>[]>([]);\n const [future, setFuture] = useState<HistoryEntry<T>[]>([]);\n\n const pushToPast = useCallback(\n (entry: HistoryEntry<T>) => {\n const next = [...pastRef.current, entry];\n if (next.length > maxHistory) {\n next.splice(0, next.length - maxHistory);\n }\n pastRef.current = next;\n setPast(next);\n },\n [maxHistory],\n );\n\n const setState = useCallback(\n (value: T | ((prev: T) => T), label?: string) => {\n const prev = stateRef.current;\n const next = typeof value === \"function\" ? (value as (prev: T) => T)(prev) : value;\n\n if (Object.is(prev, next)) return;\n\n const entry: HistoryEntry<T> = { state: prev, timestamp: Date.now(), label };\n\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n setStateRaw(next);\n stateRef.current = next;\n },\n [pushToPast],\n );\n\n const undo = useCallback(() => {\n if (pastRef.current.length === 0) return;\n\n const prev = pastRef.current[pastRef.current.length - 1];\n const newPast = pastRef.current.slice(0, -1);\n pastRef.current = newPast;\n setPast(newPast);\n\n const currentEntry: HistoryEntry<T> = {\n state: stateRef.current,\n timestamp: Date.now(),\n };\n futureRef.current = [...futureRef.current, currentEntry];\n setFuture(futureRef.current);\n\n setStateRaw(prev.state);\n stateRef.current = prev.state;\n onUndo?.(prev.state);\n }, [onUndo]);\n\n const redo = useCallback(() => {\n if (futureRef.current.length === 0) return;\n\n const next = futureRef.current[futureRef.current.length - 1];\n const newFuture = futureRef.current.slice(0, -1);\n futureRef.current = newFuture;\n setFuture(newFuture);\n\n const currentEntry: HistoryEntry<T> = {\n state: stateRef.current,\n timestamp: Date.now(),\n };\n pastRef.current = [...pastRef.current, currentEntry];\n setPast(pastRef.current);\n\n setStateRaw(next.state);\n stateRef.current = next.state;\n onRedo?.(next.state);\n }, [onRedo]);\n\n const clearHistory = useCallback(() => {\n pastRef.current = [];\n futureRef.current = [];\n setPast([]);\n setFuture([]);\n }, []);\n\n const snapshot = useCallback(\n (label?: string) => {\n const entry: HistoryEntry<T> = {\n state: stateRef.current,\n timestamp: Date.now(),\n label,\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n onSnapshot?.(entry);\n },\n [pushToPast, onSnapshot],\n );\n\n const clearDraft = useCallback(() => {\n clearHistory();\n setStateRaw(initialState);\n stateRef.current = initialState;\n }, [clearHistory, initialState]);\n\n return {\n state,\n setState,\n undo,\n redo,\n canUndo: past.length > 0,\n canRedo: future.length > 0,\n clearHistory,\n clearDraft,\n snapshot,\n past,\n future,\n };\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,3 +1,113 @@
1
+ import { useRef, useState, useCallback } from 'react';
1
2
 
3
+ // src/useFormHistory.ts
4
+ var DEFAULT_MAX_HISTORY = 100;
5
+ function useFormHistory(initialState, options = {}) {
6
+ const { maxHistory = DEFAULT_MAX_HISTORY, onUndo, onRedo, onSnapshot } = options;
7
+ const pastRef = useRef([]);
8
+ const futureRef = useRef([]);
9
+ const [state, setStateRaw] = useState(initialState);
10
+ const stateRef = useRef(initialState);
11
+ stateRef.current = state;
12
+ const [past, setPast] = useState([]);
13
+ const [future, setFuture] = useState([]);
14
+ const pushToPast = useCallback(
15
+ (entry) => {
16
+ const next = [...pastRef.current, entry];
17
+ if (next.length > maxHistory) {
18
+ next.splice(0, next.length - maxHistory);
19
+ }
20
+ pastRef.current = next;
21
+ setPast(next);
22
+ },
23
+ [maxHistory]
24
+ );
25
+ const setState = useCallback(
26
+ (value, label) => {
27
+ const prev = stateRef.current;
28
+ const next = typeof value === "function" ? value(prev) : value;
29
+ if (Object.is(prev, next)) return;
30
+ const entry = { state: prev, timestamp: Date.now(), label };
31
+ pushToPast(entry);
32
+ futureRef.current = [];
33
+ setFuture([]);
34
+ setStateRaw(next);
35
+ stateRef.current = next;
36
+ },
37
+ [pushToPast]
38
+ );
39
+ const undo = useCallback(() => {
40
+ if (pastRef.current.length === 0) return;
41
+ const prev = pastRef.current[pastRef.current.length - 1];
42
+ const newPast = pastRef.current.slice(0, -1);
43
+ pastRef.current = newPast;
44
+ setPast(newPast);
45
+ const currentEntry = {
46
+ state: stateRef.current,
47
+ timestamp: Date.now()
48
+ };
49
+ futureRef.current = [...futureRef.current, currentEntry];
50
+ setFuture(futureRef.current);
51
+ setStateRaw(prev.state);
52
+ stateRef.current = prev.state;
53
+ onUndo?.(prev.state);
54
+ }, [onUndo]);
55
+ const redo = useCallback(() => {
56
+ if (futureRef.current.length === 0) return;
57
+ const next = futureRef.current[futureRef.current.length - 1];
58
+ const newFuture = futureRef.current.slice(0, -1);
59
+ futureRef.current = newFuture;
60
+ setFuture(newFuture);
61
+ const currentEntry = {
62
+ state: stateRef.current,
63
+ timestamp: Date.now()
64
+ };
65
+ pastRef.current = [...pastRef.current, currentEntry];
66
+ setPast(pastRef.current);
67
+ setStateRaw(next.state);
68
+ stateRef.current = next.state;
69
+ onRedo?.(next.state);
70
+ }, [onRedo]);
71
+ const clearHistory = useCallback(() => {
72
+ pastRef.current = [];
73
+ futureRef.current = [];
74
+ setPast([]);
75
+ setFuture([]);
76
+ }, []);
77
+ const snapshot = useCallback(
78
+ (label) => {
79
+ const entry = {
80
+ state: stateRef.current,
81
+ timestamp: Date.now(),
82
+ label
83
+ };
84
+ pushToPast(entry);
85
+ futureRef.current = [];
86
+ setFuture([]);
87
+ onSnapshot?.(entry);
88
+ },
89
+ [pushToPast, onSnapshot]
90
+ );
91
+ const clearDraft = useCallback(() => {
92
+ clearHistory();
93
+ setStateRaw(initialState);
94
+ stateRef.current = initialState;
95
+ }, [clearHistory, initialState]);
96
+ return {
97
+ state,
98
+ setState,
99
+ undo,
100
+ redo,
101
+ canUndo: past.length > 0,
102
+ canRedo: future.length > 0,
103
+ clearHistory,
104
+ clearDraft,
105
+ snapshot,
106
+ past,
107
+ future
108
+ };
109
+ }
110
+
111
+ export { useFormHistory };
2
112
  //# sourceMappingURL=index.mjs.map
3
113
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
1
+ {"version":3,"sources":["../src/useFormHistory.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { useCallback, useRef, useState } from \"react\";\nimport type { HistoryEntry, UseFormHistoryOptions, UseFormHistoryReturn } from \"./types\";\n\nconst DEFAULT_MAX_HISTORY = 100;\n\nexport function useFormHistory<T>(\n initialState: T,\n options: UseFormHistoryOptions<T> = {},\n): UseFormHistoryReturn<T> {\n const { maxHistory = DEFAULT_MAX_HISTORY, onUndo, onRedo, onSnapshot } = options;\n\n const pastRef = useRef<HistoryEntry<T>[]>([]);\n const futureRef = useRef<HistoryEntry<T>[]>([]);\n\n const [state, setStateRaw] = useState<T>(initialState);\n const stateRef = useRef<T>(initialState);\n stateRef.current = state;\n\n const [past, setPast] = useState<HistoryEntry<T>[]>([]);\n const [future, setFuture] = useState<HistoryEntry<T>[]>([]);\n\n const pushToPast = useCallback(\n (entry: HistoryEntry<T>) => {\n const next = [...pastRef.current, entry];\n if (next.length > maxHistory) {\n next.splice(0, next.length - maxHistory);\n }\n pastRef.current = next;\n setPast(next);\n },\n [maxHistory],\n );\n\n const setState = useCallback(\n (value: T | ((prev: T) => T), label?: string) => {\n const prev = stateRef.current;\n const next = typeof value === \"function\" ? (value as (prev: T) => T)(prev) : value;\n\n if (Object.is(prev, next)) return;\n\n const entry: HistoryEntry<T> = { state: prev, timestamp: Date.now(), label };\n\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n setStateRaw(next);\n stateRef.current = next;\n },\n [pushToPast],\n );\n\n const undo = useCallback(() => {\n if (pastRef.current.length === 0) return;\n\n const prev = pastRef.current[pastRef.current.length - 1];\n const newPast = pastRef.current.slice(0, -1);\n pastRef.current = newPast;\n setPast(newPast);\n\n const currentEntry: HistoryEntry<T> = {\n state: stateRef.current,\n timestamp: Date.now(),\n };\n futureRef.current = [...futureRef.current, currentEntry];\n setFuture(futureRef.current);\n\n setStateRaw(prev.state);\n stateRef.current = prev.state;\n onUndo?.(prev.state);\n }, [onUndo]);\n\n const redo = useCallback(() => {\n if (futureRef.current.length === 0) return;\n\n const next = futureRef.current[futureRef.current.length - 1];\n const newFuture = futureRef.current.slice(0, -1);\n futureRef.current = newFuture;\n setFuture(newFuture);\n\n const currentEntry: HistoryEntry<T> = {\n state: stateRef.current,\n timestamp: Date.now(),\n };\n pastRef.current = [...pastRef.current, currentEntry];\n setPast(pastRef.current);\n\n setStateRaw(next.state);\n stateRef.current = next.state;\n onRedo?.(next.state);\n }, [onRedo]);\n\n const clearHistory = useCallback(() => {\n pastRef.current = [];\n futureRef.current = [];\n setPast([]);\n setFuture([]);\n }, []);\n\n const snapshot = useCallback(\n (label?: string) => {\n const entry: HistoryEntry<T> = {\n state: stateRef.current,\n timestamp: Date.now(),\n label,\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n onSnapshot?.(entry);\n },\n [pushToPast, onSnapshot],\n );\n\n const clearDraft = useCallback(() => {\n clearHistory();\n setStateRaw(initialState);\n stateRef.current = initialState;\n }, [clearHistory, initialState]);\n\n return {\n state,\n setState,\n undo,\n redo,\n canUndo: past.length > 0,\n canRedo: future.length > 0,\n clearHistory,\n clearDraft,\n snapshot,\n past,\n future,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-form-rewind",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Zero-dependency React state engine with auto-saved history stacks, time-traveling undo/redo, keyboard shortcuts, and draft persistence for forms.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -25,6 +25,8 @@
25
25
  "scripts": {
26
26
  "build": "tsup",
27
27
  "dev": "tsup --watch",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
28
30
  "lint": "tsc --noEmit",
29
31
  "prepublishOnly": "npm run build"
30
32
  },
@@ -32,11 +34,15 @@
32
34
  "react": ">=18.0.0"
33
35
  },
34
36
  "devDependencies": {
37
+ "@testing-library/jest-dom": "^7.0.1",
38
+ "@testing-library/react": "^16.3.3",
35
39
  "@types/react": "^18.2.0",
40
+ "jsdom": "^30.0.1",
36
41
  "react": "^18.2.0",
37
42
  "react-dom": "^18.2.0",
38
43
  "tsup": "^8.0.0",
39
- "typescript": "^5.3.0"
44
+ "typescript": "^5.3.0",
45
+ "vitest": "^5.0.0"
40
46
  },
41
47
  "keywords": [
42
48
  "react",