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 +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +111 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -2
package/dist/index.d.mts
CHANGED
|
@@ -35,4 +35,6 @@ interface UseFormHistoryReturn<T> {
|
|
|
35
35
|
future: HistoryEntry<T>[];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
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
|
-
|
|
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":"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","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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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.
|
|
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",
|