react-form-rewind 0.3.0 → 0.4.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/README.md CHANGED
@@ -2,16 +2,35 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/react-form-rewind.svg)](https://www.npmjs.com/package/react-form-rewind)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/react-form-rewind.svg)](https://www.npmjs.com/package/react-form-rewind)
5
- [![bundle size](https://img.shields.io/bundlephobia/minzip/react-form-rewind)](https://bundlephobia.com/package/react-form-rewind)
6
- [![license](https://img.shields.io/npm/l/react-form-rewind.svg)](LICENSE)
7
- [![react](https://img.shields.io/badge/react-%3E%3D18.0.0-blue.svg)](https://reactjs.org)
8
- [![typescript](https://img.shields.io/badge/typescript-5.3%2B-3178c6.svg)](https://www.typescriptlang.org/)
5
+ [![license](https://img.shields.io/npm/l/react-form-rewind.svg)](https://github.com/BazilSuhail/npm-react-form-rewind/blob/main/LICENSE)
6
+ [![types](https://img.shields.io/badge/types-typescript-blue.svg)](https://www.typescriptlang.org/)
7
+ [![zero deps](https://img.shields.io/badge/dependencies-zero-brightgreen.svg)]()
8
+ [![tree shakable](https://img.shields.io/badge/tree--shaking-yes-brightgreen.svg)]()
9
+ [![react](https://img.shields.io/badge/react-18%2B-61dafb.svg)](https://react.dev/)
10
+ [![bundle size](https://img.shields.io/bundlejs/size/react-form-rewind?label=min%2Bgzip)](https://bundlejs.com/?q=react-form-rewind)
11
+ [![github](https://img.shields.io/github/stars/BazilSuhail/npm-react-form-rewind?style=social)](https://github.com/BazilSuhail/npm-react-form-rewind)
9
12
 
10
13
  Zero-dependency, tree-shakable React state engine with auto-saved history stacks, time-traveling undo/redo, keyboard shortcuts, and draft persistence.
11
14
 
12
- ---
15
+ - **Undo/Redo** — full history stack with `Ctrl+Z` / `Ctrl+Shift+Z` keyboard shortcuts
16
+ - **Snapshot debouncing** — rapid keystrokes coalesced into logical history entries
17
+ - **Draft persistence** — auto-save to `localStorage` with schema versioning
18
+ - **Functional updates** — `setState(prev => prev + 1)` supported
19
+ - **History inspection** — access `past` and `future` arrays for custom UIs
20
+ - **Callbacks** — `onUndo`, `onRedo`, `onSnapshot` hooks
21
+ - Zero-config — no providers, no context, just a hook
22
+ - Tree-shakable — ESM + CJS with `sideEffects: false`
23
+ - TypeScript — full generics, all types exported
24
+
25
+ ## Install
13
26
 
14
- ## Why?
27
+ ```bash
28
+ npm install react-form-rewind
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ### Why?
15
34
 
16
35
  | Problem | Solution |
17
36
  |---------|----------|
@@ -20,7 +39,7 @@ Zero-dependency, tree-shakable React state engine with auto-saved history stacks
20
39
  | Manual debouncers for history snapshots | Automated keystroke coalescing into logical snapshots |
21
40
  | Heavy form libraries add validation bloat | Focused solely on history and state persistence |
22
41
 
23
- ## Quick Start
42
+ ## Usage
24
43
 
25
44
  ```bash
26
45
  npm install react-form-rewind
package/dist/index.js CHANGED
@@ -5,24 +5,77 @@ var react = require('react');
5
5
  // src/useFormHistory.ts
6
6
  var DEFAULT_MAX_HISTORY = 100;
7
7
  var DEFAULT_DEBOUNCE_MS = 300;
8
+ var DEFAULT_PERSIST_DEBOUNCE_MS = 500;
9
+ var DEFAULT_PERSIST_VERSION = 1;
10
+ function normalizePersist(persist) {
11
+ if (!persist) return null;
12
+ if (typeof persist === "boolean") return null;
13
+ return {
14
+ key: persist.key,
15
+ debounceMs: persist.debounceMs ?? DEFAULT_PERSIST_DEBOUNCE_MS,
16
+ version: persist.version ?? DEFAULT_PERSIST_VERSION
17
+ };
18
+ }
19
+ function readDraft(key, version) {
20
+ try {
21
+ if (typeof window === "undefined" || !window.localStorage) return null;
22
+ const raw = window.localStorage.getItem(key);
23
+ if (!raw) return null;
24
+ const parsed = JSON.parse(raw);
25
+ if (parsed.__version !== version) {
26
+ window.localStorage.removeItem(key);
27
+ return null;
28
+ }
29
+ return parsed.__state;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+ function writeDraft(key, state, version) {
35
+ try {
36
+ if (typeof window === "undefined" || !window.localStorage) return;
37
+ window.localStorage.setItem(key, JSON.stringify({ __state: state, __version: version }));
38
+ } catch {
39
+ }
40
+ }
41
+ function removeDraft(key) {
42
+ try {
43
+ if (typeof window === "undefined" || !window.localStorage) return;
44
+ window.localStorage.removeItem(key);
45
+ } catch {
46
+ }
47
+ }
8
48
  function useFormHistory(initialState, options = {}) {
9
49
  const {
10
50
  maxHistory = DEFAULT_MAX_HISTORY,
11
51
  debounceMs = DEFAULT_DEBOUNCE_MS,
52
+ persist: persistOption,
12
53
  onUndo,
13
54
  onRedo,
14
55
  onSnapshot
15
56
  } = options;
57
+ const persist = normalizePersist(persistOption);
58
+ const initializedRef = react.useRef(false);
59
+ const [state, setStateRaw] = react.useState(() => {
60
+ if (persist) {
61
+ const draft = readDraft(persist.key, persist.version);
62
+ if (draft !== null) {
63
+ initializedRef.current = true;
64
+ return draft;
65
+ }
66
+ }
67
+ return initialState;
68
+ });
69
+ const stateRef = react.useRef(state);
70
+ stateRef.current = state;
16
71
  const pastRef = react.useRef([]);
17
72
  const futureRef = react.useRef([]);
18
- const [state, setStateRaw] = react.useState(initialState);
19
- const stateRef = react.useRef(initialState);
20
- stateRef.current = state;
21
73
  const [past, setPast] = react.useState([]);
22
74
  const [future, setFuture] = react.useState([]);
23
75
  const debounceTimerRef = react.useRef(null);
24
76
  const pendingStateRef = react.useRef(null);
25
77
  const preDebounceStateRef = react.useRef(null);
78
+ const persistTimerRef = react.useRef(null);
26
79
  const pushToPast = react.useCallback(
27
80
  (entry) => {
28
81
  const next = [...pastRef.current, entry];
@@ -39,8 +92,35 @@ function useFormHistory(initialState, options = {}) {
39
92
  if (debounceTimerRef.current !== null) {
40
93
  clearTimeout(debounceTimerRef.current);
41
94
  }
95
+ if (persistTimerRef.current !== null) {
96
+ clearTimeout(persistTimerRef.current);
97
+ }
42
98
  };
43
99
  }, []);
100
+ const schedulePersist = react.useCallback(
101
+ (nextState) => {
102
+ if (!persist) return;
103
+ if (persistTimerRef.current !== null) {
104
+ clearTimeout(persistTimerRef.current);
105
+ }
106
+ persistTimerRef.current = setTimeout(() => {
107
+ writeDraft(persist.key, nextState, persist.version);
108
+ persistTimerRef.current = null;
109
+ }, persist.debounceMs);
110
+ },
111
+ [persist]
112
+ );
113
+ const persistNow = react.useCallback(
114
+ (nextState) => {
115
+ if (!persist) return;
116
+ if (persistTimerRef.current !== null) {
117
+ clearTimeout(persistTimerRef.current);
118
+ persistTimerRef.current = null;
119
+ }
120
+ writeDraft(persist.key, nextState, persist.version);
121
+ },
122
+ [persist]
123
+ );
44
124
  const setState = react.useCallback(
45
125
  (value, label) => {
46
126
  const prev = stateRef.current;
@@ -48,6 +128,7 @@ function useFormHistory(initialState, options = {}) {
48
128
  if (Object.is(prev, next)) return;
49
129
  setStateRaw(next);
50
130
  stateRef.current = next;
131
+ schedulePersist(next);
51
132
  if (debounceMs <= 0) {
52
133
  const entry = { state: prev, timestamp: Date.now(), label };
53
134
  pushToPast(entry);
@@ -76,7 +157,7 @@ function useFormHistory(initialState, options = {}) {
76
157
  preDebounceStateRef.current = null;
77
158
  }, debounceMs);
78
159
  },
79
- [debounceMs, pushToPast]
160
+ [debounceMs, pushToPast, schedulePersist]
80
161
  );
81
162
  const undo = react.useCallback(() => {
82
163
  if (debounceTimerRef.current !== null) {
@@ -107,8 +188,9 @@ function useFormHistory(initialState, options = {}) {
107
188
  setFuture(futureRef.current);
108
189
  setStateRaw(prev.state);
109
190
  stateRef.current = prev.state;
191
+ persistNow(prev.state);
110
192
  onUndo?.(prev.state);
111
- }, [pushToPast, onUndo]);
193
+ }, [pushToPast, onUndo, persistNow]);
112
194
  const redo = react.useCallback(() => {
113
195
  if (futureRef.current.length === 0) return;
114
196
  const next = futureRef.current[futureRef.current.length - 1];
@@ -123,8 +205,9 @@ function useFormHistory(initialState, options = {}) {
123
205
  setPast(pastRef.current);
124
206
  setStateRaw(next.state);
125
207
  stateRef.current = next.state;
208
+ persistNow(next.state);
126
209
  onRedo?.(next.state);
127
- }, [onRedo]);
210
+ }, [onRedo, persistNow]);
128
211
  const clearHistory = react.useCallback(() => {
129
212
  if (debounceTimerRef.current !== null) {
130
213
  clearTimeout(debounceTimerRef.current);
@@ -175,9 +258,12 @@ function useFormHistory(initialState, options = {}) {
175
258
  );
176
259
  const clearDraft = react.useCallback(() => {
177
260
  clearHistory();
261
+ if (persist) {
262
+ removeDraft(persist.key);
263
+ }
178
264
  setStateRaw(initialState);
179
265
  stateRef.current = initialState;
180
- }, [clearHistory, initialState]);
266
+ }, [clearHistory, initialState, persist]);
181
267
  return {
182
268
  state,
183
269
  setState,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useFormHistory.ts"],"names":["useRef","useState","useCallback","useEffect"],"mappings":";;;;;AAGA,IAAM,mBAAA,GAAsB,GAAA;AAC5B,IAAM,mBAAA,GAAsB,GAAA;AAErB,SAAS,cAAA,CACd,YAAA,EACA,OAAA,GAAoC,EAAC,EACZ;AACzB,EAAA,MAAM;AAAA,IACJ,UAAA,GAAa,mBAAA;AAAA,IACb,UAAA,GAAa,mBAAA;AAAA,IACb,MAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAEJ,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,gBAAA,GAAmBD,aAA6C,IAAI,CAAA;AAC1E,EAAA,MAAM,eAAA,GAAkBA,aAAiB,IAAI,CAAA;AAC7C,EAAA,MAAM,mBAAA,GAAsBA,aAAiB,IAAI,CAAA;AAEjD,EAAA,MAAM,UAAA,GAAaE,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,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,gBAAA,CAAiB,YAAY,IAAA,EAAM;AACrC,QAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AAAA,MACvC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,QAAA,GAAWD,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,WAAA,CAAY,IAAI,CAAA;AAChB,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AAEnB,MAAA,IAAI,cAAc,CAAA,EAAG;AACnB,QAAA,MAAM,KAAA,GAAyB,EAAE,KAAA,EAAO,IAAA,EAAM,WAAW,IAAA,CAAK,GAAA,IAAO,KAAA,EAAM;AAC3E,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA,SAAA,CAAU,UAAU,EAAC;AACrB,QAAA,SAAA,CAAU,EAAE,CAAA;AACZ,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,eAAA,CAAgB,YAAY,IAAA,EAAM;AACpC,QAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAAA,MAChC;AAEA,MAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAE1B,MAAA,IAAI,gBAAA,CAAiB,YAAY,IAAA,EAAM;AACrC,QAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AAAA,MACvC;AAEA,MAAA,gBAAA,CAAiB,OAAA,GAAU,WAAW,MAAM;AAC1C,QAAA,MAAM,KAAA,GAAyB;AAAA,UAC7B,OAAO,mBAAA,CAAoB,OAAA;AAAA,UAC3B,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB;AAAA,SACF;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA,SAAA,CAAU,UAAU,EAAC;AACrB,QAAA,SAAA,CAAU,EAAE,CAAA;AACZ,QAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAC3B,QAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAC1B,QAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAAA,MAChC,GAAG,UAAU,CAAA;AAAA,IACf,CAAA;AAAA,IACA,CAAC,YAAY,UAAU;AAAA,GACzB;AAEA,EAAA,MAAM,IAAA,GAAOA,kBAAY,MAAM;AAC7B,IAAA,IAAI,gBAAA,CAAiB,YAAY,IAAA,EAAM;AACrC,MAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AACrC,MAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAAA,IAC7B;AAEA,IAAA,IAAI,eAAA,CAAgB,OAAA,KAAY,IAAA,IAAQ,mBAAA,CAAoB,YAAY,IAAA,EAAM;AAC5E,MAAA,MAAM,KAAA,GAAyB;AAAA,QAC7B,OAAO,mBAAA,CAAoB,OAAA;AAAA,QAC3B,SAAA,EAAW,KAAK,GAAA;AAAI,OACtB;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA,SAAA,CAAU,UAAU,EAAC;AACrB,MAAA,SAAA,CAAU,EAAE,CAAA;AACZ,MAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAC1B,MAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAAA,IAChC;AAEA,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,UAAA,EAAY,MAAM,CAAC,CAAA;AAEvB,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,IAAI,gBAAA,CAAiB,YAAY,IAAA,EAAM;AACrC,MAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AACrC,MAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAAA,IAC7B;AACA,IAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAC1B,IAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAC9B,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,IAAI,QAAA,GAAW,KAAA;AAEf,MAAA,IAAI,gBAAA,CAAiB,YAAY,IAAA,EAAM;AACrC,QAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AACrC,QAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAAA,MAC7B;AAEA,MAAA,IAAI,eAAA,CAAgB,OAAA,KAAY,IAAA,IAAQ,mBAAA,CAAoB,YAAY,IAAA,EAAM;AAC5E,QAAA,MAAM,KAAA,GAAyB;AAAA,UAC7B,OAAO,mBAAA,CAAoB,OAAA;AAAA,UAC3B,SAAA,EAAW,KAAK,GAAA;AAAI,SACtB;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA,SAAA,CAAU,UAAU,EAAC;AACrB,QAAA,SAAA,CAAU,EAAE,CAAA;AACZ,QAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAC1B,QAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAC9B,QAAA,QAAA,GAAW,IAAA;AAAA,MACb;AAEA,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,MAAM,KAAA,GAAyB;AAAA,UAC7B,OAAO,QAAA,CAAS,OAAA;AAAA,UAChB,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB;AAAA,SACF;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA,SAAA,CAAU,UAAU,EAAC;AACrB,QAAA,SAAA,CAAU,EAAE,CAAA;AACZ,QAAA,UAAA,GAAa,KAAK,CAAA;AAAA,MACpB,CAAA,MAAA,IAAW,KAAA,IAAS,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAA,EAAG;AAC9C,QAAA,OAAA,CAAQ,QAAQ,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,EAAE,KAAA,GAAQ,KAAA;AACpD,QAAA,OAAA,CAAQ,CAAC,GAAG,OAAA,CAAQ,OAAO,CAAC,CAAA;AAAA,MAC9B;AAAA,IACF,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, useEffect, useRef, useState } from \"react\";\nimport type { HistoryEntry, UseFormHistoryOptions, UseFormHistoryReturn } from \"./types\";\n\nconst DEFAULT_MAX_HISTORY = 100;\nconst DEFAULT_DEBOUNCE_MS = 300;\n\nexport function useFormHistory<T>(\n initialState: T,\n options: UseFormHistoryOptions<T> = {},\n): UseFormHistoryReturn<T> {\n const {\n maxHistory = DEFAULT_MAX_HISTORY,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n onUndo,\n onRedo,\n onSnapshot,\n } = 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 debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingStateRef = useRef<T | null>(null);\n const preDebounceStateRef = useRef<T | null>(null);\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 useEffect(() => {\n return () => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n }\n };\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 setStateRaw(next);\n stateRef.current = next;\n\n if (debounceMs <= 0) {\n const entry: HistoryEntry<T> = { state: prev, timestamp: Date.now(), label };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n return;\n }\n\n if (pendingStateRef.current === null) {\n preDebounceStateRef.current = prev;\n }\n\n pendingStateRef.current = next;\n\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n }\n\n debounceTimerRef.current = setTimeout(() => {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current!,\n timestamp: Date.now(),\n label,\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n debounceTimerRef.current = null;\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n }, debounceMs);\n },\n [debounceMs, pushToPast],\n );\n\n const undo = useCallback(() => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n\n if (pendingStateRef.current !== null && preDebounceStateRef.current !== null) {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current,\n timestamp: Date.now(),\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n }\n\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 }, [pushToPast, 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 if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n pastRef.current = [];\n futureRef.current = [];\n setPast([]);\n setFuture([]);\n }, []);\n\n const snapshot = useCallback(\n (label?: string) => {\n let didFlush = false;\n\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n\n if (pendingStateRef.current !== null && preDebounceStateRef.current !== null) {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current,\n timestamp: Date.now(),\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n didFlush = true;\n }\n\n if (!didFlush) {\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 } else if (label && pastRef.current.length > 0) {\n pastRef.current[pastRef.current.length - 1].label = label;\n setPast([...pastRef.current]);\n }\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"]}
1
+ {"version":3,"sources":["../src/useFormHistory.ts"],"names":["useRef","useState","useCallback","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { HistoryEntry, PersistOptions, UseFormHistoryOptions, UseFormHistoryReturn } from \"./types\";\n\nconst DEFAULT_MAX_HISTORY = 100;\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst DEFAULT_PERSIST_DEBOUNCE_MS = 500;\nconst DEFAULT_PERSIST_VERSION = 1;\n\ninterface PersistStorage {\n key: string;\n debounceMs: number;\n version: number;\n}\n\nfunction normalizePersist(persist: boolean | PersistOptions | undefined): PersistStorage | null {\n if (!persist) return null;\n if (typeof persist === \"boolean\") return null;\n return {\n key: persist.key,\n debounceMs: persist.debounceMs ?? DEFAULT_PERSIST_DEBOUNCE_MS,\n version: persist.version ?? DEFAULT_PERSIST_VERSION,\n };\n}\n\nfunction readDraft<T>(key: string, version: number): T | null {\n try {\n if (typeof window === \"undefined\" || !window.localStorage) return null;\n const raw = window.localStorage.getItem(key);\n if (!raw) return null;\n const parsed = JSON.parse(raw);\n if (parsed.__version !== version) {\n window.localStorage.removeItem(key);\n return null;\n }\n return parsed.__state as T;\n } catch {\n return null;\n }\n}\n\nfunction writeDraft<T>(key: string, state: T, version: number): void {\n try {\n if (typeof window === \"undefined\" || !window.localStorage) return;\n window.localStorage.setItem(key, JSON.stringify({ __state: state, __version: version }));\n } catch {\n // localStorage full or disabled — silently ignore\n }\n}\n\nfunction removeDraft(key: string): void {\n try {\n if (typeof window === \"undefined\" || !window.localStorage) return;\n window.localStorage.removeItem(key);\n } catch {\n // silently ignore\n }\n}\n\nexport function useFormHistory<T>(\n initialState: T,\n options: UseFormHistoryOptions<T> = {},\n): UseFormHistoryReturn<T> {\n const {\n maxHistory = DEFAULT_MAX_HISTORY,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n persist: persistOption,\n onUndo,\n onRedo,\n onSnapshot,\n } = options;\n\n const persist = normalizePersist(persistOption);\n const initializedRef = useRef(false);\n\n // Hydrate from localStorage on first render\n const [state, setStateRaw] = useState<T>(() => {\n if (persist) {\n const draft = readDraft<T>(persist.key, persist.version);\n if (draft !== null) {\n initializedRef.current = true;\n return draft;\n }\n }\n return initialState;\n });\n\n const stateRef = useRef<T>(state);\n stateRef.current = state;\n\n const pastRef = useRef<HistoryEntry<T>[]>([]);\n const futureRef = useRef<HistoryEntry<T>[]>([]);\n\n const [past, setPast] = useState<HistoryEntry<T>[]>([]);\n const [future, setFuture] = useState<HistoryEntry<T>[]>([]);\n\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingStateRef = useRef<T | null>(null);\n const preDebounceStateRef = useRef<T | null>(null);\n\n // Persist debounce timer\n const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\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 // Cleanup timers on unmount\n useEffect(() => {\n return () => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n }\n if (persistTimerRef.current !== null) {\n clearTimeout(persistTimerRef.current);\n }\n };\n }, []);\n\n // Debounced persist helper\n const schedulePersist = useCallback(\n (nextState: T) => {\n if (!persist) return;\n if (persistTimerRef.current !== null) {\n clearTimeout(persistTimerRef.current);\n }\n persistTimerRef.current = setTimeout(() => {\n writeDraft(persist.key, nextState, persist.version);\n persistTimerRef.current = null;\n }, persist.debounceMs);\n },\n [persist],\n );\n\n // Persist immediately (used by undo/redo/snapshot/clearDraft which bypass debounce)\n const persistNow = useCallback(\n (nextState: T) => {\n if (!persist) return;\n if (persistTimerRef.current !== null) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n }\n writeDraft(persist.key, nextState, persist.version);\n },\n [persist],\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 setStateRaw(next);\n stateRef.current = next;\n schedulePersist(next);\n\n if (debounceMs <= 0) {\n const entry: HistoryEntry<T> = { state: prev, timestamp: Date.now(), label };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n return;\n }\n\n if (pendingStateRef.current === null) {\n preDebounceStateRef.current = prev;\n }\n\n pendingStateRef.current = next;\n\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n }\n\n debounceTimerRef.current = setTimeout(() => {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current!,\n timestamp: Date.now(),\n label,\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n debounceTimerRef.current = null;\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n }, debounceMs);\n },\n [debounceMs, pushToPast, schedulePersist],\n );\n\n const undo = useCallback(() => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n\n if (pendingStateRef.current !== null && preDebounceStateRef.current !== null) {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current,\n timestamp: Date.now(),\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n }\n\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 persistNow(prev.state);\n onUndo?.(prev.state);\n }, [pushToPast, onUndo, persistNow]);\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 persistNow(next.state);\n onRedo?.(next.state);\n }, [onRedo, persistNow]);\n\n const clearHistory = useCallback(() => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n pastRef.current = [];\n futureRef.current = [];\n setPast([]);\n setFuture([]);\n }, []);\n\n const snapshot = useCallback(\n (label?: string) => {\n let didFlush = false;\n\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n\n if (pendingStateRef.current !== null && preDebounceStateRef.current !== null) {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current,\n timestamp: Date.now(),\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n didFlush = true;\n }\n\n if (!didFlush) {\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 } else if (label && pastRef.current.length > 0) {\n pastRef.current[pastRef.current.length - 1].label = label;\n setPast([...pastRef.current]);\n }\n },\n [pushToPast, onSnapshot],\n );\n\n const clearDraft = useCallback(() => {\n clearHistory();\n if (persist) {\n removeDraft(persist.key);\n }\n setStateRaw(initialState);\n stateRef.current = initialState;\n }, [clearHistory, initialState, persist]);\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
@@ -3,24 +3,77 @@ import { useRef, useState, useCallback, useEffect } from 'react';
3
3
  // src/useFormHistory.ts
4
4
  var DEFAULT_MAX_HISTORY = 100;
5
5
  var DEFAULT_DEBOUNCE_MS = 300;
6
+ var DEFAULT_PERSIST_DEBOUNCE_MS = 500;
7
+ var DEFAULT_PERSIST_VERSION = 1;
8
+ function normalizePersist(persist) {
9
+ if (!persist) return null;
10
+ if (typeof persist === "boolean") return null;
11
+ return {
12
+ key: persist.key,
13
+ debounceMs: persist.debounceMs ?? DEFAULT_PERSIST_DEBOUNCE_MS,
14
+ version: persist.version ?? DEFAULT_PERSIST_VERSION
15
+ };
16
+ }
17
+ function readDraft(key, version) {
18
+ try {
19
+ if (typeof window === "undefined" || !window.localStorage) return null;
20
+ const raw = window.localStorage.getItem(key);
21
+ if (!raw) return null;
22
+ const parsed = JSON.parse(raw);
23
+ if (parsed.__version !== version) {
24
+ window.localStorage.removeItem(key);
25
+ return null;
26
+ }
27
+ return parsed.__state;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+ function writeDraft(key, state, version) {
33
+ try {
34
+ if (typeof window === "undefined" || !window.localStorage) return;
35
+ window.localStorage.setItem(key, JSON.stringify({ __state: state, __version: version }));
36
+ } catch {
37
+ }
38
+ }
39
+ function removeDraft(key) {
40
+ try {
41
+ if (typeof window === "undefined" || !window.localStorage) return;
42
+ window.localStorage.removeItem(key);
43
+ } catch {
44
+ }
45
+ }
6
46
  function useFormHistory(initialState, options = {}) {
7
47
  const {
8
48
  maxHistory = DEFAULT_MAX_HISTORY,
9
49
  debounceMs = DEFAULT_DEBOUNCE_MS,
50
+ persist: persistOption,
10
51
  onUndo,
11
52
  onRedo,
12
53
  onSnapshot
13
54
  } = options;
55
+ const persist = normalizePersist(persistOption);
56
+ const initializedRef = useRef(false);
57
+ const [state, setStateRaw] = useState(() => {
58
+ if (persist) {
59
+ const draft = readDraft(persist.key, persist.version);
60
+ if (draft !== null) {
61
+ initializedRef.current = true;
62
+ return draft;
63
+ }
64
+ }
65
+ return initialState;
66
+ });
67
+ const stateRef = useRef(state);
68
+ stateRef.current = state;
14
69
  const pastRef = useRef([]);
15
70
  const futureRef = useRef([]);
16
- const [state, setStateRaw] = useState(initialState);
17
- const stateRef = useRef(initialState);
18
- stateRef.current = state;
19
71
  const [past, setPast] = useState([]);
20
72
  const [future, setFuture] = useState([]);
21
73
  const debounceTimerRef = useRef(null);
22
74
  const pendingStateRef = useRef(null);
23
75
  const preDebounceStateRef = useRef(null);
76
+ const persistTimerRef = useRef(null);
24
77
  const pushToPast = useCallback(
25
78
  (entry) => {
26
79
  const next = [...pastRef.current, entry];
@@ -37,8 +90,35 @@ function useFormHistory(initialState, options = {}) {
37
90
  if (debounceTimerRef.current !== null) {
38
91
  clearTimeout(debounceTimerRef.current);
39
92
  }
93
+ if (persistTimerRef.current !== null) {
94
+ clearTimeout(persistTimerRef.current);
95
+ }
40
96
  };
41
97
  }, []);
98
+ const schedulePersist = useCallback(
99
+ (nextState) => {
100
+ if (!persist) return;
101
+ if (persistTimerRef.current !== null) {
102
+ clearTimeout(persistTimerRef.current);
103
+ }
104
+ persistTimerRef.current = setTimeout(() => {
105
+ writeDraft(persist.key, nextState, persist.version);
106
+ persistTimerRef.current = null;
107
+ }, persist.debounceMs);
108
+ },
109
+ [persist]
110
+ );
111
+ const persistNow = useCallback(
112
+ (nextState) => {
113
+ if (!persist) return;
114
+ if (persistTimerRef.current !== null) {
115
+ clearTimeout(persistTimerRef.current);
116
+ persistTimerRef.current = null;
117
+ }
118
+ writeDraft(persist.key, nextState, persist.version);
119
+ },
120
+ [persist]
121
+ );
42
122
  const setState = useCallback(
43
123
  (value, label) => {
44
124
  const prev = stateRef.current;
@@ -46,6 +126,7 @@ function useFormHistory(initialState, options = {}) {
46
126
  if (Object.is(prev, next)) return;
47
127
  setStateRaw(next);
48
128
  stateRef.current = next;
129
+ schedulePersist(next);
49
130
  if (debounceMs <= 0) {
50
131
  const entry = { state: prev, timestamp: Date.now(), label };
51
132
  pushToPast(entry);
@@ -74,7 +155,7 @@ function useFormHistory(initialState, options = {}) {
74
155
  preDebounceStateRef.current = null;
75
156
  }, debounceMs);
76
157
  },
77
- [debounceMs, pushToPast]
158
+ [debounceMs, pushToPast, schedulePersist]
78
159
  );
79
160
  const undo = useCallback(() => {
80
161
  if (debounceTimerRef.current !== null) {
@@ -105,8 +186,9 @@ function useFormHistory(initialState, options = {}) {
105
186
  setFuture(futureRef.current);
106
187
  setStateRaw(prev.state);
107
188
  stateRef.current = prev.state;
189
+ persistNow(prev.state);
108
190
  onUndo?.(prev.state);
109
- }, [pushToPast, onUndo]);
191
+ }, [pushToPast, onUndo, persistNow]);
110
192
  const redo = useCallback(() => {
111
193
  if (futureRef.current.length === 0) return;
112
194
  const next = futureRef.current[futureRef.current.length - 1];
@@ -121,8 +203,9 @@ function useFormHistory(initialState, options = {}) {
121
203
  setPast(pastRef.current);
122
204
  setStateRaw(next.state);
123
205
  stateRef.current = next.state;
206
+ persistNow(next.state);
124
207
  onRedo?.(next.state);
125
- }, [onRedo]);
208
+ }, [onRedo, persistNow]);
126
209
  const clearHistory = useCallback(() => {
127
210
  if (debounceTimerRef.current !== null) {
128
211
  clearTimeout(debounceTimerRef.current);
@@ -173,9 +256,12 @@ function useFormHistory(initialState, options = {}) {
173
256
  );
174
257
  const clearDraft = useCallback(() => {
175
258
  clearHistory();
259
+ if (persist) {
260
+ removeDraft(persist.key);
261
+ }
176
262
  setStateRaw(initialState);
177
263
  stateRef.current = initialState;
178
- }, [clearHistory, initialState]);
264
+ }, [clearHistory, initialState, persist]);
179
265
  return {
180
266
  state,
181
267
  setState,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useFormHistory.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { HistoryEntry, UseFormHistoryOptions, UseFormHistoryReturn } from \"./types\";\n\nconst DEFAULT_MAX_HISTORY = 100;\nconst DEFAULT_DEBOUNCE_MS = 300;\n\nexport function useFormHistory<T>(\n initialState: T,\n options: UseFormHistoryOptions<T> = {},\n): UseFormHistoryReturn<T> {\n const {\n maxHistory = DEFAULT_MAX_HISTORY,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n onUndo,\n onRedo,\n onSnapshot,\n } = 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 debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingStateRef = useRef<T | null>(null);\n const preDebounceStateRef = useRef<T | null>(null);\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 useEffect(() => {\n return () => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n }\n };\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 setStateRaw(next);\n stateRef.current = next;\n\n if (debounceMs <= 0) {\n const entry: HistoryEntry<T> = { state: prev, timestamp: Date.now(), label };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n return;\n }\n\n if (pendingStateRef.current === null) {\n preDebounceStateRef.current = prev;\n }\n\n pendingStateRef.current = next;\n\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n }\n\n debounceTimerRef.current = setTimeout(() => {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current!,\n timestamp: Date.now(),\n label,\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n debounceTimerRef.current = null;\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n }, debounceMs);\n },\n [debounceMs, pushToPast],\n );\n\n const undo = useCallback(() => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n\n if (pendingStateRef.current !== null && preDebounceStateRef.current !== null) {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current,\n timestamp: Date.now(),\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n }\n\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 }, [pushToPast, 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 if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n pastRef.current = [];\n futureRef.current = [];\n setPast([]);\n setFuture([]);\n }, []);\n\n const snapshot = useCallback(\n (label?: string) => {\n let didFlush = false;\n\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n\n if (pendingStateRef.current !== null && preDebounceStateRef.current !== null) {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current,\n timestamp: Date.now(),\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n didFlush = true;\n }\n\n if (!didFlush) {\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 } else if (label && pastRef.current.length > 0) {\n pastRef.current[pastRef.current.length - 1].label = label;\n setPast([...pastRef.current]);\n }\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"]}
1
+ {"version":3,"sources":["../src/useFormHistory.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { HistoryEntry, PersistOptions, UseFormHistoryOptions, UseFormHistoryReturn } from \"./types\";\n\nconst DEFAULT_MAX_HISTORY = 100;\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst DEFAULT_PERSIST_DEBOUNCE_MS = 500;\nconst DEFAULT_PERSIST_VERSION = 1;\n\ninterface PersistStorage {\n key: string;\n debounceMs: number;\n version: number;\n}\n\nfunction normalizePersist(persist: boolean | PersistOptions | undefined): PersistStorage | null {\n if (!persist) return null;\n if (typeof persist === \"boolean\") return null;\n return {\n key: persist.key,\n debounceMs: persist.debounceMs ?? DEFAULT_PERSIST_DEBOUNCE_MS,\n version: persist.version ?? DEFAULT_PERSIST_VERSION,\n };\n}\n\nfunction readDraft<T>(key: string, version: number): T | null {\n try {\n if (typeof window === \"undefined\" || !window.localStorage) return null;\n const raw = window.localStorage.getItem(key);\n if (!raw) return null;\n const parsed = JSON.parse(raw);\n if (parsed.__version !== version) {\n window.localStorage.removeItem(key);\n return null;\n }\n return parsed.__state as T;\n } catch {\n return null;\n }\n}\n\nfunction writeDraft<T>(key: string, state: T, version: number): void {\n try {\n if (typeof window === \"undefined\" || !window.localStorage) return;\n window.localStorage.setItem(key, JSON.stringify({ __state: state, __version: version }));\n } catch {\n // localStorage full or disabled — silently ignore\n }\n}\n\nfunction removeDraft(key: string): void {\n try {\n if (typeof window === \"undefined\" || !window.localStorage) return;\n window.localStorage.removeItem(key);\n } catch {\n // silently ignore\n }\n}\n\nexport function useFormHistory<T>(\n initialState: T,\n options: UseFormHistoryOptions<T> = {},\n): UseFormHistoryReturn<T> {\n const {\n maxHistory = DEFAULT_MAX_HISTORY,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n persist: persistOption,\n onUndo,\n onRedo,\n onSnapshot,\n } = options;\n\n const persist = normalizePersist(persistOption);\n const initializedRef = useRef(false);\n\n // Hydrate from localStorage on first render\n const [state, setStateRaw] = useState<T>(() => {\n if (persist) {\n const draft = readDraft<T>(persist.key, persist.version);\n if (draft !== null) {\n initializedRef.current = true;\n return draft;\n }\n }\n return initialState;\n });\n\n const stateRef = useRef<T>(state);\n stateRef.current = state;\n\n const pastRef = useRef<HistoryEntry<T>[]>([]);\n const futureRef = useRef<HistoryEntry<T>[]>([]);\n\n const [past, setPast] = useState<HistoryEntry<T>[]>([]);\n const [future, setFuture] = useState<HistoryEntry<T>[]>([]);\n\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingStateRef = useRef<T | null>(null);\n const preDebounceStateRef = useRef<T | null>(null);\n\n // Persist debounce timer\n const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\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 // Cleanup timers on unmount\n useEffect(() => {\n return () => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n }\n if (persistTimerRef.current !== null) {\n clearTimeout(persistTimerRef.current);\n }\n };\n }, []);\n\n // Debounced persist helper\n const schedulePersist = useCallback(\n (nextState: T) => {\n if (!persist) return;\n if (persistTimerRef.current !== null) {\n clearTimeout(persistTimerRef.current);\n }\n persistTimerRef.current = setTimeout(() => {\n writeDraft(persist.key, nextState, persist.version);\n persistTimerRef.current = null;\n }, persist.debounceMs);\n },\n [persist],\n );\n\n // Persist immediately (used by undo/redo/snapshot/clearDraft which bypass debounce)\n const persistNow = useCallback(\n (nextState: T) => {\n if (!persist) return;\n if (persistTimerRef.current !== null) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n }\n writeDraft(persist.key, nextState, persist.version);\n },\n [persist],\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 setStateRaw(next);\n stateRef.current = next;\n schedulePersist(next);\n\n if (debounceMs <= 0) {\n const entry: HistoryEntry<T> = { state: prev, timestamp: Date.now(), label };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n return;\n }\n\n if (pendingStateRef.current === null) {\n preDebounceStateRef.current = prev;\n }\n\n pendingStateRef.current = next;\n\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n }\n\n debounceTimerRef.current = setTimeout(() => {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current!,\n timestamp: Date.now(),\n label,\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n debounceTimerRef.current = null;\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n }, debounceMs);\n },\n [debounceMs, pushToPast, schedulePersist],\n );\n\n const undo = useCallback(() => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n\n if (pendingStateRef.current !== null && preDebounceStateRef.current !== null) {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current,\n timestamp: Date.now(),\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n }\n\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 persistNow(prev.state);\n onUndo?.(prev.state);\n }, [pushToPast, onUndo, persistNow]);\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 persistNow(next.state);\n onRedo?.(next.state);\n }, [onRedo, persistNow]);\n\n const clearHistory = useCallback(() => {\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n pastRef.current = [];\n futureRef.current = [];\n setPast([]);\n setFuture([]);\n }, []);\n\n const snapshot = useCallback(\n (label?: string) => {\n let didFlush = false;\n\n if (debounceTimerRef.current !== null) {\n clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = null;\n }\n\n if (pendingStateRef.current !== null && preDebounceStateRef.current !== null) {\n const entry: HistoryEntry<T> = {\n state: preDebounceStateRef.current,\n timestamp: Date.now(),\n };\n pushToPast(entry);\n futureRef.current = [];\n setFuture([]);\n pendingStateRef.current = null;\n preDebounceStateRef.current = null;\n didFlush = true;\n }\n\n if (!didFlush) {\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 } else if (label && pastRef.current.length > 0) {\n pastRef.current[pastRef.current.length - 1].label = label;\n setPast([...pastRef.current]);\n }\n },\n [pushToPast, onSnapshot],\n );\n\n const clearDraft = useCallback(() => {\n clearHistory();\n if (persist) {\n removeDraft(persist.key);\n }\n setStateRaw(initialState);\n stateRef.current = initialState;\n }, [clearHistory, initialState, persist]);\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.0",
3
+ "version": "0.4.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",
@@ -60,11 +60,11 @@
60
60
  ],
61
61
  "repository": {
62
62
  "type": "git",
63
- "url": "https://github.com/your-username/react-form-rewind.git"
63
+ "url": "https://github.com/BazilSuhail/npm-react-form-rewind.git"
64
64
  },
65
65
  "bugs": {
66
- "url": "https://github.com/your-username/react-form-rewind/issues"
66
+ "url": "https://github.com/BazilSuhail/npm-react-form-rewind/issues"
67
67
  },
68
- "homepage": "https://github.com/your-username/react-form-rewind#readme",
68
+ "homepage": "https://github.com/BazilSuhail/npm-react-form-rewind#readme",
69
69
  "license": "MIT"
70
70
  }