use-good-hooks 1.0.39 → 1.0.40

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
@@ -210,30 +210,30 @@ Tracks the history of a state value, providing undo and redo capabilities. This
210
210
  import useHistoryState from 'use-good-hooks/use-history-state';
211
211
 
212
212
  const TextEditor = () => {
213
- const {
214
- canRedo,
215
- canUndo,
216
- history,
217
- redo,
218
- set,
219
- state,
220
- undo
221
- } = useHistoryState('', { maxCapacity: 10 });
213
+ const [state, actions] = useHistoryState('', {
214
+ maxCapacity: 10,
215
+ debounceMs: 0 // Disable debouncing for immediate updates
216
+ });
222
217
 
223
218
  return (
224
219
  <div>
225
220
  <textarea
226
- value={state}
227
- onChange={(e) => set(e.target.value)}
221
+ value={state.present}
222
+ onChange={(e) => actions.set(e.target.value)}
228
223
  rows={4}
229
224
  cols={50}
230
225
  />
231
226
  <div>
232
- <button onClick={undo} disabled={!canUndo}>Undo</button>
233
- <button onClick={redo} disabled={!canRedo}>Redo</button>
227
+ <button onClick={actions.undo} disabled={!state.canUndo}>
228
+ Undo
229
+ </button>
230
+ <button onClick={actions.redo} disabled={!state.canRedo}>
231
+ Redo
232
+ </button>
233
+ <button onClick={actions.clear}>Clear History</button>
234
234
  </div>
235
- <p>History (last {history.length} changes):</p>
236
- <pre>{JSON.stringify(history, null, 2)}</pre>
235
+ <p>Past states: {state.past.length}</p>
236
+ <p>Future states: {state.future.length}</p>
237
237
  </div>
238
238
  );
239
239
  };
@@ -243,27 +243,66 @@ const TextEditor = () => {
243
243
 
244
244
  - `initialState`: The initial state value
245
245
  - `options`: (Optional) Configuration options:
246
- - `debounceMs`: Time in milliseconds to debounce the state changes (default: 0)
247
- - `debounceSettings`: Debounce settings object (default: { leading: true, trailing: false })
248
- - `immutable`: Boolean indicating if the state should be treated as immutable (default: false)
246
+ - `debounceMs`: Time in milliseconds to debounce the state changes (default: 250)
247
+ - `debounceSettings`: Debounce settings object from Lodash
248
+ - `immutable`: Boolean indicating if the state should be treated as immutable (default: true)
249
249
  - `maxCapacity`: Maximum number of history entries to keep (default: 10)
250
- - `onChange`: Function to call when the state changes
250
+ - `onChange`: Function to call when the state changes, receives `{ action, state }`
251
251
  - `paused`: Boolean indicating if the history is paused (default: false)
252
252
 
253
253
  #### Returns
254
254
 
255
- - Object with:
256
- - `canRedo`: Boolean indicating if redo is possible
257
- - `canUndo`: Boolean indicating if undo is possible
258
- - `clear`: Function to clear the history
259
- - `future`: Array of future states
260
- - `past`: Array of past states
261
- - `pause`: Function to pause the history
262
- - `paused`: Boolean indicating if the history is paused
263
- - `redo`: Function to move to the next state (redo)
264
- - `set`: Function to update the state and record history
265
- - `state`: The current state value
266
- - `undo`: Function to move to the previous state (undo)
255
+ Returns a tuple `[historyState, historyActions]`:
256
+
257
+ **historyState** (Object): - `canRedo`: Boolean indicating if redo is possible - `canUndo`: Boolean indicating if undo is possible - `future`: Array of future states (for redo) - `past`: Array of past states (for undo) - `paused`: Boolean indicating if the history is paused - `present`: The current state value
258
+
259
+ **historyActions** (Object): - `clear`: Function to clear the history and reset to initial state - `pause`: Function to pause history tracking (updates won't be recorded) - `redo`: Function to move to the next state (redo) - `replace`: Function to replace the state without adding to history - `resume`: Function to resume history tracking - `set`: Function to update the state and record history (debounced by default) - `setDirect`: Function to update the state immediately, bypassing debounce - `undo`: Function to move to the previous state (undo)
260
+
261
+ #### Example with onChange callback
262
+
263
+ ```typescript
264
+ const Editor = () => {
265
+ const [state, actions] = useHistoryState('', {
266
+ onChange: ({ action, state }) => {
267
+ console.log(`Action: ${action}, State: ${state}`);
268
+ // Action can be: 'SET', 'UNDO', 'REDO', 'CLEAR', 'REPLACE'
269
+ }
270
+ });
271
+
272
+ return (
273
+ <textarea
274
+ value={state.present}
275
+ onChange={(e) => actions.set(e.target.value)}
276
+ />
277
+ );
278
+ };
279
+ ```
280
+
281
+ #### Example with pause/resume
282
+
283
+ ```typescript
284
+ const Form = () => {
285
+ const [state, actions] = useHistoryState({ name: '', email: '' });
286
+
287
+ const handleBulkUpdate = () => {
288
+ actions.pause(); // Pause history tracking
289
+ actions.set({ name: 'John', email: 'john@example.com' });
290
+ actions.set({ name: 'Jane', email: 'jane@example.com' });
291
+ actions.resume(); // Resume history tracking
292
+ // Only the final state will be in history
293
+ };
294
+
295
+ return (
296
+ <div>
297
+ <input
298
+ value={state.present.name}
299
+ onChange={(e) => actions.set({ ...state.present, name: e.target.value })}
300
+ />
301
+ <button onClick={handleBulkUpdate}>Bulk Update</button>
302
+ </div>
303
+ );
304
+ };
305
+ ```
267
306
 
268
307
  ### `useDistinct`
269
308
 
@@ -2,7 +2,7 @@ import { DebounceSettings } from 'lodash';
2
2
 
3
3
  export { DebounceSettings }
4
4
 
5
- declare type HistoryAction<T> = {
5
+ export declare type HistoryAction<T> = {
6
6
  type: 'CLEAR';
7
7
  initialState: T;
8
8
  } | {
@@ -26,38 +26,39 @@ declare type HistoryOnChange<T> = ({ action, state }: {
26
26
  state: HistoryState<T>['present'];
27
27
  }) => void;
28
28
 
29
- declare type HistoryState<T> = {
30
- future: T[];
31
- past: T[];
32
- paused: boolean;
33
- present: T | null;
34
- };
35
-
36
- declare type UseHistoryOptionsState<T> = {
37
- debounceSettings?: DebounceSettings;
29
+ export declare type HistoryOptions<T> = {
38
30
  debounceMs?: number;
31
+ debounceSettings?: DebounceSettings;
39
32
  immutable?: boolean;
40
33
  maxCapacity?: number;
41
34
  onChange?: HistoryOnChange<T>;
42
35
  paused?: boolean;
43
36
  };
44
37
 
45
- declare const useHistoryState: <T>(initialState: T, options?: UseHistoryOptionsState<T>) => {
38
+ export declare type HistoryState<T> = {
39
+ future: T[];
40
+ past: T[];
41
+ paused: boolean;
42
+ present: T | null;
43
+ };
44
+
45
+ declare const useHistoryState: <T>(initialState: T, options?: HistoryOptions<T>) => readonly [{
46
46
  canRedo: boolean;
47
47
  canUndo: boolean;
48
- clear: () => void;
49
48
  future: T[];
50
49
  past: T[];
51
- pause: () => void;
52
50
  paused: boolean;
51
+ present: T;
52
+ }, {
53
+ clear: () => void;
54
+ pause: () => void;
53
55
  redo: () => void;
54
56
  replace: (newPresent: T) => void;
55
57
  resume: () => void;
56
58
  set: (newPresent: T) => void;
57
59
  setDirect: (newPresent: T) => void;
58
- state: T;
59
60
  undo: () => void;
60
- };
61
+ }];
61
62
  export default useHistoryState;
62
63
 
63
64
  export { }
@@ -1,155 +1,165 @@
1
- import { useRef as k, useReducer as I, useCallback as l, useEffect as K } from "react";
2
- import Q from "lodash/cloneDeep";
3
- import W from "lodash/isNil";
4
- import o from "lodash/size";
5
- import X from "./use-debounce-fn.js";
6
- const Y = {
1
+ import { useRef as A, useReducer as O, useMemo as D, useEffect as b } from "react";
2
+ import h from "lodash/cloneDeep";
3
+ import g from "lodash/debounce";
4
+ import i from "lodash/size";
5
+ const L = {
7
6
  future: [],
8
7
  past: [],
9
8
  present: null
10
- }, c = (f, p) => p ? f : Q(f), Z = (f, p, S) => S ? f === p : JSON.stringify(f) === JSON.stringify(p), ee = (f, p) => {
11
- const { maxCapacity: S, debounceMs: D, debounceSettings: q, onChange: U, immutable: n } = p || {}, b = k(f), e = k(U), [E, i] = I(
12
- (t, r) => {
13
- var L, x, F, M, T, H;
14
- const { future: w, past: y, paused: d, present: m } = t;
15
- if (r.type === "CLEAR") {
16
- const u = {
9
+ }, c = (f, p) => p ? f : h(f), M = (f, p, w) => w ? f === p : JSON.stringify(f) === JSON.stringify(p), H = (f, p) => {
10
+ const w = A(f), E = A(p ?? {}), [l, a] = O(
11
+ (n, e) => {
12
+ const {
13
+ immutable: u = !0,
14
+ maxCapacity: r = 10,
15
+ onChange: S = () => null
16
+ } = E.current, { future: R, past: y, paused: d, present: m } = n;
17
+ if (e.type === "CLEAR") {
18
+ const s = {
17
19
  future: [],
18
20
  past: [],
19
21
  paused: d,
20
- present: c(r.initialState, n)
22
+ present: c(e.initialState, u)
21
23
  };
22
- return (L = e.current) == null || L.call(e, {
23
- action: r.type,
24
- state: u.present
25
- }), u;
24
+ return S({
25
+ action: e.type,
26
+ state: s.present
27
+ }), s;
26
28
  } else {
27
- if (r.type === "PAUSE")
29
+ if (e.type === "PAUSE")
28
30
  return {
29
- ...t,
31
+ ...n,
30
32
  paused: !0
31
33
  };
32
- if (r.type === "REDO") {
33
- if (o(w) === 0)
34
- return t;
35
- const u = w[0], a = {
36
- future: w.slice(1),
37
- past: [...y, c(m, n)],
34
+ if (e.type === "REDO") {
35
+ if (i(R) === 0)
36
+ return n;
37
+ const s = R[0], o = {
38
+ future: R.slice(1),
39
+ past: [...y, c(m, u)],
38
40
  paused: d,
39
- present: c(u, n)
41
+ present: c(s, u)
40
42
  };
41
- return (x = e.current) == null || x.call(e, {
42
- action: r.type,
43
- state: a.present
44
- }), a;
43
+ return S({
44
+ action: e.type,
45
+ state: o.present
46
+ }), o;
45
47
  } else {
46
- if (r.type === "RESUME")
48
+ if (e.type === "RESUME")
47
49
  return {
48
- ...t,
50
+ ...n,
49
51
  paused: !1
50
52
  };
51
- if (r.type === "REPLACE") {
52
- const { newPresent: u } = r, s = {
53
- ...t,
53
+ if (e.type === "REPLACE") {
54
+ const { newPresent: s } = e, t = {
55
+ ...n,
54
56
  future: [],
55
57
  past: [],
56
- present: u
58
+ present: s
57
59
  };
58
- return (F = e.current) == null || F.call(e, {
59
- action: r.type,
60
- state: s.present
61
- }), s;
62
- } else if (r.type === "SET") {
63
- const { newPresent: u } = r;
64
- if (Z(u, m, n))
65
- return t;
60
+ return S({
61
+ action: e.type,
62
+ state: t.present
63
+ }), t;
64
+ } else if (e.type === "SET") {
65
+ const { newPresent: s } = e;
66
+ if (M(s, m, u))
67
+ return n;
66
68
  if (d) {
67
- const J = {
68
- ...t,
69
- present: c(u, n)
69
+ const U = {
70
+ ...n,
71
+ present: c(s, u)
70
72
  };
71
- return (M = e.current) == null || M.call(e, {
72
- action: r.type,
73
- state: J.present
74
- }), J;
73
+ return S({
74
+ action: e.type,
75
+ state: U.present
76
+ }), U;
75
77
  }
76
- let s = [...y];
77
- m !== null && (s = [...s, c(m, n)]), !W(S) && S > 0 && o(s) > S && (s = s.slice(o(s) - S));
78
- const a = {
78
+ let t = [...y];
79
+ m !== null && (t = [...t, c(m, u)]), r > 0 && i(t) > r && (t = t.slice(i(t) - r));
80
+ const o = {
79
81
  future: [],
80
- past: s,
82
+ past: t,
81
83
  paused: d,
82
- present: c(u, n)
84
+ present: c(s, u)
83
85
  };
84
- return (T = e.current) == null || T.call(e, {
85
- action: r.type,
86
- state: a.present
87
- }), a;
88
- } else if (r.type === "UNDO") {
89
- if (o(y) === 0)
90
- return t;
91
- const u = y[o(y) - 1], s = y.slice(0, o(y) - 1), a = {
92
- future: [c(m, n), ...w],
93
- past: s,
86
+ return S({
87
+ action: e.type,
88
+ state: o.present
89
+ }), o;
90
+ } else if (e.type === "UNDO") {
91
+ if (i(y) === 0)
92
+ return n;
93
+ const s = y[i(y) - 1], t = y.slice(0, i(y) - 1), o = {
94
+ future: [c(m, u), ...R],
95
+ past: t,
94
96
  paused: d,
95
- present: c(u, n)
97
+ present: c(s, u)
96
98
  };
97
- return (H = e.current) == null || H.call(e, {
98
- action: r.type,
99
- state: a.present
100
- }), a;
99
+ return S({
100
+ action: e.type,
101
+ state: o.present
102
+ }), o;
101
103
  } else
102
104
  throw new Error("Unsupported action type");
103
105
  }
104
106
  }
105
107
  },
106
108
  {
107
- ...Y,
108
- paused: (p == null ? void 0 : p.paused) ?? !1,
109
- present: c(b.current, n)
109
+ ...L,
110
+ paused: E.current.paused ?? !1,
111
+ present: c(
112
+ w.current,
113
+ E.current.immutable
114
+ )
110
115
  }
111
- ), P = o(E.future) !== 0, A = o(E.past) !== 0, v = l(() => i({
112
- initialState: b.current,
113
- type: "CLEAR"
114
- }), []), z = l(() => {
115
- i({ type: "PAUSE" });
116
- }, []), R = l(() => {
117
- P && i({ type: "REDO" });
118
- }, [P]), V = l((t) => {
119
- i({ type: "REPLACE", newPresent: t });
120
- }, []), j = l(() => {
121
- i({ type: "RESUME" });
122
- }, []), N = X(
123
- (t) => i({ type: "SET", newPresent: t }),
124
- D,
125
- q
126
- ), O = l((t) => i({ type: "SET", newPresent: t }), []), B = l(
127
- (t) => {
128
- D ? N(t) : O(t);
129
- },
130
- [D, N, O]
131
- ), G = l(() => {
132
- A && i({ type: "UNDO" });
133
- }, [A]);
134
- return K(() => {
135
- e.current = U;
136
- }, [U]), {
137
- canRedo: P,
138
- canUndo: A,
139
- clear: v,
140
- future: E.future,
141
- past: E.past,
142
- pause: z,
143
- paused: E.paused,
144
- redo: R,
145
- replace: V,
146
- resume: j,
147
- set: B,
148
- setDirect: O,
149
- state: E.present,
150
- undo: G
151
- };
116
+ ), P = D(() => ({
117
+ canRedo: i(l.future) !== 0,
118
+ canUndo: i(l.past) !== 0,
119
+ future: l.future,
120
+ past: l.past,
121
+ paused: l.paused,
122
+ present: l.present
123
+ }), [l]), C = D(() => {
124
+ const { debounceMs: n = 250, debounceSettings: e } = E.current, u = g(
125
+ (r) => a({ type: "SET", newPresent: r }),
126
+ n,
127
+ e
128
+ );
129
+ return {
130
+ clear: () => {
131
+ a({
132
+ type: "CLEAR",
133
+ initialState: w.current
134
+ });
135
+ },
136
+ pause: () => {
137
+ a({ type: "PAUSE" });
138
+ },
139
+ redo: () => {
140
+ a({ type: "REDO" });
141
+ },
142
+ replace: (r) => {
143
+ a({ type: "REPLACE", newPresent: r });
144
+ },
145
+ resume: () => {
146
+ a({ type: "RESUME" });
147
+ },
148
+ set: (r) => {
149
+ n ? u(r) : a({ type: "SET", newPresent: r });
150
+ },
151
+ setDirect: (r) => {
152
+ a({ type: "SET", newPresent: r });
153
+ },
154
+ undo: () => {
155
+ a({ type: "UNDO" });
156
+ }
157
+ };
158
+ }, []);
159
+ return b(() => {
160
+ p && (E.current = p);
161
+ }, [p]), [P, C];
152
162
  };
153
163
  export {
154
- ee as default
164
+ H as default
155
165
  };
package/package.json CHANGED
@@ -50,5 +50,5 @@
50
50
  "test:coverage": "vitest --coverage --run",
51
51
  "test:watch": "vitest"
52
52
  },
53
- "version": "1.0.39"
53
+ "version": "1.0.40"
54
54
  }