use-good-hooks 1.0.38 → 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,28 +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
- - `setDirect`: Function to update the state without recording history
266
- - `state`: The current state value
267
- - `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
+ ```
268
306
 
269
307
  ### `useDistinct`
270
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,164 +1,165 @@
1
- import { useRef as M, useReducer as T, useCallback as d } from "react";
2
- import H from "lodash/cloneDeep";
3
- import J from "lodash/isNil";
4
- import E from "lodash/size";
5
- import k from "./use-debounce-fn.js";
6
- const q = {
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
- }, l = (e, r) => r ? e : H(e), v = (e, r, y) => y ? e === r : JSON.stringify(e) === JSON.stringify(r), z = ({
11
- action: e,
12
- immutable: r,
13
- maxCapacity: y,
14
- onChange: t,
15
- state: f
16
- }) => {
17
- const { future: o, past: n, paused: S, present: p } = f;
18
- if (e.type === "CLEAR") {
19
- const s = {
20
- future: [],
21
- past: [],
22
- paused: S,
23
- present: l(e.initialState, r)
24
- };
25
- return t == null || t({
26
- action: e.type,
27
- state: s.present
28
- }), s;
29
- } else {
30
- if (e.type === "PAUSE")
31
- return {
32
- ...f,
33
- paused: !0
34
- };
35
- if (e.type === "REDO") {
36
- if (E(o) === 0)
37
- return f;
38
- const s = o[0], c = {
39
- future: o.slice(1),
40
- past: [...n, l(p, r)],
41
- paused: S,
42
- present: l(s, r)
43
- };
44
- return t == null || t({
45
- action: e.type,
46
- state: c.present
47
- }), c;
48
- } else {
49
- if (e.type === "RESUME")
50
- return {
51
- ...f,
52
- paused: !1
53
- };
54
- if (e.type === "REPLACE") {
55
- const { newPresent: s } = e, u = {
56
- ...f,
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 = {
57
19
  future: [],
58
20
  past: [],
59
- present: s
21
+ paused: d,
22
+ present: c(e.initialState, u)
60
23
  };
61
- return t == null || t({
24
+ return S({
62
25
  action: e.type,
63
- state: u.present
64
- }), u;
65
- } else if (e.type === "SET") {
66
- const { newPresent: s } = e;
67
- if (v(s, p, r))
68
- return f;
69
- if (S) {
70
- const w = {
71
- ...f,
72
- present: l(s, r)
26
+ state: s.present
27
+ }), s;
28
+ } else {
29
+ if (e.type === "PAUSE")
30
+ return {
31
+ ...n,
32
+ paused: !0
33
+ };
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)],
40
+ paused: d,
41
+ present: c(s, u)
73
42
  };
74
- return t == null || t({
43
+ return S({
75
44
  action: e.type,
76
- state: w.present
77
- }), w;
45
+ state: o.present
46
+ }), o;
47
+ } else {
48
+ if (e.type === "RESUME")
49
+ return {
50
+ ...n,
51
+ paused: !1
52
+ };
53
+ if (e.type === "REPLACE") {
54
+ const { newPresent: s } = e, t = {
55
+ ...n,
56
+ future: [],
57
+ past: [],
58
+ present: s
59
+ };
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;
68
+ if (d) {
69
+ const U = {
70
+ ...n,
71
+ present: c(s, u)
72
+ };
73
+ return S({
74
+ action: e.type,
75
+ state: U.present
76
+ }), U;
77
+ }
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 = {
81
+ future: [],
82
+ past: t,
83
+ paused: d,
84
+ present: c(s, u)
85
+ };
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,
96
+ paused: d,
97
+ present: c(s, u)
98
+ };
99
+ return S({
100
+ action: e.type,
101
+ state: o.present
102
+ }), o;
103
+ } else
104
+ throw new Error("Unsupported action type");
78
105
  }
79
- let u = [...n];
80
- p !== null && (u = [...u, l(p, r)]), !J(y) && y > 0 && E(u) > y && (u = u.slice(E(u) - y));
81
- const c = {
82
- future: [],
83
- past: u,
84
- paused: S,
85
- present: l(s, r)
86
- };
87
- return t == null || t({
88
- action: e.type,
89
- state: c.present
90
- }), c;
91
- } else if (e.type === "UNDO") {
92
- if (E(n) === 0)
93
- return f;
94
- const s = n[E(n) - 1], u = n.slice(0, E(n) - 1), c = {
95
- future: [l(p, r), ...o],
96
- past: u,
97
- paused: S,
98
- present: l(s, r)
99
- };
100
- return t == null || t({
101
- action: e.type,
102
- state: c.present
103
- }), c;
104
- } else
105
- throw new Error("Unsupported action type");
106
- }
107
- }
108
- }, G = (e, r) => {
109
- const { maxCapacity: y, debounceMs: t, debounceSettings: f, onChange: o, immutable: n } = r || {}, S = M(e), [p, s] = T(
110
- (i, F) => z({
111
- action: F,
112
- immutable: n,
113
- maxCapacity: y,
114
- onChange: o,
115
- state: i
116
- }),
106
+ }
107
+ },
117
108
  {
118
- ...q,
119
- paused: (r == null ? void 0 : r.paused) ?? !1,
120
- present: l(S.current, n)
109
+ ...L,
110
+ paused: E.current.paused ?? !1,
111
+ present: c(
112
+ w.current,
113
+ E.current.immutable
114
+ )
121
115
  }
122
- ), u = E(p.future) !== 0, c = E(p.past) !== 0, w = d(() => s({
123
- initialState: S.current,
124
- type: "CLEAR"
125
- }), []), U = d(() => {
126
- s({ type: "PAUSE" });
127
- }, []), P = d(() => {
128
- u && s({ type: "REDO" });
129
- }, [u]), A = d((i) => {
130
- s({ type: "REPLACE", newPresent: i });
131
- }, []), O = d(() => {
132
- s({ type: "RESUME" });
133
- }, []), D = k(
134
- (i) => s({ type: "SET", newPresent: i }),
135
- t,
136
- f
137
- ), R = d((i) => s({ type: "SET", newPresent: i }), []), N = d(
138
- (i) => {
139
- t ? D(i) : R(i);
140
- },
141
- [t, D, R]
142
- ), L = d(() => {
143
- c && s({ type: "UNDO" });
144
- }, [c]);
145
- return {
146
- canRedo: u,
147
- canUndo: c,
148
- clear: w,
149
- future: p.future,
150
- past: p.past,
151
- pause: U,
152
- paused: p.paused,
153
- redo: P,
154
- replace: A,
155
- resume: O,
156
- set: N,
157
- setDirect: R,
158
- state: p.present,
159
- undo: L
160
- };
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];
161
162
  };
162
163
  export {
163
- G as default
164
+ H as default
164
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.38"
53
+ "version": "1.0.40"
54
54
  }