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 +70 -32
- package/dist/use-history-state.d.ts +16 -15
- package/dist/use-history-state.js +150 -149
- package/package.json +1 -1
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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}>
|
|
233
|
-
|
|
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>
|
|
236
|
-
<
|
|
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:
|
|
247
|
-
- `debounceSettings`: Debounce settings object
|
|
248
|
-
- `immutable`: Boolean indicating if the state should be treated as immutable (default:
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
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
|
|
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
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
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
|
-
},
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
21
|
+
paused: d,
|
|
22
|
+
present: c(e.initialState, u)
|
|
60
23
|
};
|
|
61
|
-
return
|
|
24
|
+
return S({
|
|
62
25
|
action: e.type,
|
|
63
|
-
state:
|
|
64
|
-
}),
|
|
65
|
-
} else
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
43
|
+
return S({
|
|
75
44
|
action: e.type,
|
|
76
|
-
state:
|
|
77
|
-
}),
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
...
|
|
119
|
-
paused:
|
|
120
|
-
present:
|
|
109
|
+
...L,
|
|
110
|
+
paused: E.current.paused ?? !1,
|
|
111
|
+
present: c(
|
|
112
|
+
w.current,
|
|
113
|
+
E.current.immutable
|
|
114
|
+
)
|
|
121
115
|
}
|
|
122
|
-
),
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}, [
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
164
|
+
H as default
|
|
164
165
|
};
|
package/package.json
CHANGED