reactives-hooks 0.1.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/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/index.cjs +832 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +221 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.mjs +792 -0
- package/dist/index.mjs.map +1 -0
- package/dist/next/index.cjs +106 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +25 -0
- package/dist/next/index.d.ts +25 -0
- package/dist/next/index.mjs +101 -0
- package/dist/next/index.mjs.map +1 -0
- package/dist/utils/index.cjs +26 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.mjs +22 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +123 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import { useLayoutEffect, useEffect, useState, useCallback, useRef, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
function useToggle(initialValue = false) {
|
|
4
|
+
const [value, setValue] = useState(initialValue);
|
|
5
|
+
const toggle = useCallback((newValue) => {
|
|
6
|
+
setValue((prev) => typeof newValue === "boolean" ? newValue : !prev);
|
|
7
|
+
}, []);
|
|
8
|
+
return [value, toggle];
|
|
9
|
+
}
|
|
10
|
+
function useBoolean(initialValue = false) {
|
|
11
|
+
const [value, setValue] = useState(initialValue);
|
|
12
|
+
const setTrue = useCallback(() => setValue(true), []);
|
|
13
|
+
const setFalse = useCallback(() => setValue(false), []);
|
|
14
|
+
const toggle = useCallback(() => setValue((prev) => !prev), []);
|
|
15
|
+
return { value, setTrue, setFalse, toggle, setValue };
|
|
16
|
+
}
|
|
17
|
+
function useCounter(initialValue = 0, options = {}) {
|
|
18
|
+
const { min, max } = options;
|
|
19
|
+
const [count, setCount] = useState(initialValue);
|
|
20
|
+
const clamp = useCallback(
|
|
21
|
+
(value) => {
|
|
22
|
+
let clamped = value;
|
|
23
|
+
if (min !== void 0) clamped = Math.max(min, clamped);
|
|
24
|
+
if (max !== void 0) clamped = Math.min(max, clamped);
|
|
25
|
+
return clamped;
|
|
26
|
+
},
|
|
27
|
+
[min, max]
|
|
28
|
+
);
|
|
29
|
+
const increment = useCallback((delta = 1) => setCount((prev) => clamp(prev + delta)), [clamp]);
|
|
30
|
+
const decrement = useCallback((delta = 1) => setCount((prev) => clamp(prev - delta)), [clamp]);
|
|
31
|
+
const set = useCallback((value) => setCount(clamp(value)), [clamp]);
|
|
32
|
+
const reset = useCallback(() => setCount(initialValue), [initialValue]);
|
|
33
|
+
return [count, { increment, decrement, set, reset }];
|
|
34
|
+
}
|
|
35
|
+
function useMap(initialValue = []) {
|
|
36
|
+
const [map, setMap] = useState(() => new Map(initialValue));
|
|
37
|
+
const actions = {
|
|
38
|
+
set: useCallback((key, value) => {
|
|
39
|
+
setMap((prev) => {
|
|
40
|
+
const next = new Map(prev);
|
|
41
|
+
next.set(key, value);
|
|
42
|
+
return next;
|
|
43
|
+
});
|
|
44
|
+
}, []),
|
|
45
|
+
setAll: useCallback((entries) => {
|
|
46
|
+
setMap((prev) => {
|
|
47
|
+
const next = new Map(prev);
|
|
48
|
+
for (const [key, value] of entries) {
|
|
49
|
+
next.set(key, value);
|
|
50
|
+
}
|
|
51
|
+
return next;
|
|
52
|
+
});
|
|
53
|
+
}, []),
|
|
54
|
+
delete: useCallback((key) => {
|
|
55
|
+
setMap((prev) => {
|
|
56
|
+
const next = new Map(prev);
|
|
57
|
+
next.delete(key);
|
|
58
|
+
return next;
|
|
59
|
+
});
|
|
60
|
+
}, []),
|
|
61
|
+
clear: useCallback(() => {
|
|
62
|
+
setMap(/* @__PURE__ */ new Map());
|
|
63
|
+
}, []),
|
|
64
|
+
reset: useCallback(() => {
|
|
65
|
+
setMap(new Map(initialValue));
|
|
66
|
+
}, [])
|
|
67
|
+
};
|
|
68
|
+
return [map, actions];
|
|
69
|
+
}
|
|
70
|
+
function useSet(initialValue = []) {
|
|
71
|
+
const [set, setSet] = useState(() => new Set(initialValue));
|
|
72
|
+
const actions = {
|
|
73
|
+
add: useCallback((value) => {
|
|
74
|
+
setSet((prev) => /* @__PURE__ */ new Set([...prev, value]));
|
|
75
|
+
}, []),
|
|
76
|
+
delete: useCallback((value) => {
|
|
77
|
+
setSet((prev) => {
|
|
78
|
+
const next = new Set(prev);
|
|
79
|
+
next.delete(value);
|
|
80
|
+
return next;
|
|
81
|
+
});
|
|
82
|
+
}, []),
|
|
83
|
+
clear: useCallback(() => {
|
|
84
|
+
setSet(/* @__PURE__ */ new Set());
|
|
85
|
+
}, []),
|
|
86
|
+
reset: useCallback(() => {
|
|
87
|
+
setSet(new Set(initialValue));
|
|
88
|
+
}, []),
|
|
89
|
+
toggle: useCallback((value) => {
|
|
90
|
+
setSet((prev) => {
|
|
91
|
+
const next = new Set(prev);
|
|
92
|
+
if (next.has(value)) {
|
|
93
|
+
next.delete(value);
|
|
94
|
+
} else {
|
|
95
|
+
next.add(value);
|
|
96
|
+
}
|
|
97
|
+
return next;
|
|
98
|
+
});
|
|
99
|
+
}, [])
|
|
100
|
+
};
|
|
101
|
+
return [set, actions];
|
|
102
|
+
}
|
|
103
|
+
function usePrevious(value) {
|
|
104
|
+
const ref = useRef(void 0);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
ref.current = value;
|
|
107
|
+
}, [value]);
|
|
108
|
+
return ref.current;
|
|
109
|
+
}
|
|
110
|
+
function useStateHistory(initialValue) {
|
|
111
|
+
const [value, setValue] = useState(initialValue);
|
|
112
|
+
const historyRef = useRef([initialValue]);
|
|
113
|
+
const pointerRef = useRef(0);
|
|
114
|
+
const set = useCallback((newValue) => {
|
|
115
|
+
const pointer = pointerRef.current;
|
|
116
|
+
const newHistory = historyRef.current.slice(0, pointer + 1);
|
|
117
|
+
newHistory.push(newValue);
|
|
118
|
+
historyRef.current = newHistory;
|
|
119
|
+
pointerRef.current = newHistory.length - 1;
|
|
120
|
+
setValue(newValue);
|
|
121
|
+
}, []);
|
|
122
|
+
const undo = useCallback(() => {
|
|
123
|
+
if (pointerRef.current > 0) {
|
|
124
|
+
pointerRef.current -= 1;
|
|
125
|
+
setValue(historyRef.current[pointerRef.current]);
|
|
126
|
+
}
|
|
127
|
+
}, []);
|
|
128
|
+
const redo = useCallback(() => {
|
|
129
|
+
if (pointerRef.current < historyRef.current.length - 1) {
|
|
130
|
+
pointerRef.current += 1;
|
|
131
|
+
setValue(historyRef.current[pointerRef.current]);
|
|
132
|
+
}
|
|
133
|
+
}, []);
|
|
134
|
+
return {
|
|
135
|
+
value,
|
|
136
|
+
set,
|
|
137
|
+
undo,
|
|
138
|
+
redo,
|
|
139
|
+
canUndo: pointerRef.current > 0,
|
|
140
|
+
canRedo: pointerRef.current < historyRef.current.length - 1,
|
|
141
|
+
history: historyRef.current
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function getStoredValue(key, initialValue) {
|
|
145
|
+
if (typeof window === "undefined") return initialValue;
|
|
146
|
+
try {
|
|
147
|
+
const item = window.localStorage.getItem(key);
|
|
148
|
+
return item ? JSON.parse(item) : initialValue;
|
|
149
|
+
} catch {
|
|
150
|
+
return initialValue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function useLocalStorage(key, initialValue) {
|
|
154
|
+
const [storedValue, setStoredValue] = useState(() => getStoredValue(key, initialValue));
|
|
155
|
+
const setValue = useCallback(
|
|
156
|
+
(value) => {
|
|
157
|
+
setStoredValue((prev) => {
|
|
158
|
+
const nextValue = value instanceof Function ? value(prev) : value;
|
|
159
|
+
if (typeof window !== "undefined") {
|
|
160
|
+
window.localStorage.setItem(key, JSON.stringify(nextValue));
|
|
161
|
+
}
|
|
162
|
+
return nextValue;
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
[key]
|
|
166
|
+
);
|
|
167
|
+
const removeValue = useCallback(() => {
|
|
168
|
+
setStoredValue(initialValue);
|
|
169
|
+
if (typeof window !== "undefined") {
|
|
170
|
+
window.localStorage.removeItem(key);
|
|
171
|
+
}
|
|
172
|
+
}, [key, initialValue]);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
const handleStorageChange = (e) => {
|
|
175
|
+
if (e.key === key && e.newValue !== null) {
|
|
176
|
+
try {
|
|
177
|
+
setStoredValue(JSON.parse(e.newValue));
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
window.addEventListener("storage", handleStorageChange);
|
|
183
|
+
return () => window.removeEventListener("storage", handleStorageChange);
|
|
184
|
+
}, [key]);
|
|
185
|
+
return [storedValue, setValue, removeValue];
|
|
186
|
+
}
|
|
187
|
+
function getStoredValue2(key, initialValue) {
|
|
188
|
+
if (typeof window === "undefined") return initialValue;
|
|
189
|
+
try {
|
|
190
|
+
const item = window.sessionStorage.getItem(key);
|
|
191
|
+
return item ? JSON.parse(item) : initialValue;
|
|
192
|
+
} catch {
|
|
193
|
+
return initialValue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function useSessionStorage(key, initialValue) {
|
|
197
|
+
const [storedValue, setStoredValue] = useState(() => getStoredValue2(key, initialValue));
|
|
198
|
+
const setValue = useCallback(
|
|
199
|
+
(value) => {
|
|
200
|
+
setStoredValue((prev) => {
|
|
201
|
+
const nextValue = value instanceof Function ? value(prev) : value;
|
|
202
|
+
if (typeof window !== "undefined") {
|
|
203
|
+
window.sessionStorage.setItem(key, JSON.stringify(nextValue));
|
|
204
|
+
}
|
|
205
|
+
return nextValue;
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
[key]
|
|
209
|
+
);
|
|
210
|
+
const removeValue = useCallback(() => {
|
|
211
|
+
setStoredValue(initialValue);
|
|
212
|
+
if (typeof window !== "undefined") {
|
|
213
|
+
window.sessionStorage.removeItem(key);
|
|
214
|
+
}
|
|
215
|
+
}, [key, initialValue]);
|
|
216
|
+
return [storedValue, setValue, removeValue];
|
|
217
|
+
}
|
|
218
|
+
function useEventListener(eventName, handler, element, options) {
|
|
219
|
+
const savedHandler = useRef(handler);
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
savedHandler.current = handler;
|
|
222
|
+
}, [handler]);
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
const target = element?.current ?? window;
|
|
225
|
+
if (!target?.addEventListener) return;
|
|
226
|
+
const listener = (event) => savedHandler.current(event);
|
|
227
|
+
target.addEventListener(eventName, listener, options);
|
|
228
|
+
return () => target.removeEventListener(eventName, listener, options);
|
|
229
|
+
}, [eventName, element, options]);
|
|
230
|
+
}
|
|
231
|
+
function useClickOutside(handler) {
|
|
232
|
+
const ref = useRef(null);
|
|
233
|
+
const savedHandler = useRef(handler);
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
savedHandler.current = handler;
|
|
236
|
+
}, [handler]);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
const listener = (event) => {
|
|
239
|
+
if (!ref.current || ref.current.contains(event.target)) return;
|
|
240
|
+
savedHandler.current(event);
|
|
241
|
+
};
|
|
242
|
+
document.addEventListener("mousedown", listener);
|
|
243
|
+
document.addEventListener("touchstart", listener);
|
|
244
|
+
return () => {
|
|
245
|
+
document.removeEventListener("mousedown", listener);
|
|
246
|
+
document.removeEventListener("touchstart", listener);
|
|
247
|
+
};
|
|
248
|
+
}, []);
|
|
249
|
+
return ref;
|
|
250
|
+
}
|
|
251
|
+
function useHover() {
|
|
252
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
253
|
+
const ref = useRef(null);
|
|
254
|
+
const handleMouseEnter = useCallback(() => setIsHovered(true), []);
|
|
255
|
+
const handleMouseLeave = useCallback(() => setIsHovered(false), []);
|
|
256
|
+
const callbackRef = useCallback(
|
|
257
|
+
(node) => {
|
|
258
|
+
if (ref.current) {
|
|
259
|
+
ref.current.removeEventListener("mouseenter", handleMouseEnter);
|
|
260
|
+
ref.current.removeEventListener("mouseleave", handleMouseLeave);
|
|
261
|
+
}
|
|
262
|
+
ref.current = node;
|
|
263
|
+
if (node) {
|
|
264
|
+
node.addEventListener("mouseenter", handleMouseEnter);
|
|
265
|
+
node.addEventListener("mouseleave", handleMouseLeave);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
[handleMouseEnter, handleMouseLeave]
|
|
269
|
+
);
|
|
270
|
+
return [callbackRef, isHovered];
|
|
271
|
+
}
|
|
272
|
+
function useIntersectionObserver(options = {}) {
|
|
273
|
+
const { threshold = 0, root = null, rootMargin = "0px", freezeOnceVisible = false } = options;
|
|
274
|
+
const ref = useRef(null);
|
|
275
|
+
const [entry, setEntry] = useState(null);
|
|
276
|
+
const frozen = entry?.isIntersecting && freezeOnceVisible;
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
const node = ref.current;
|
|
279
|
+
if (!node || frozen) return;
|
|
280
|
+
const observer = new IntersectionObserver(
|
|
281
|
+
([observerEntry]) => {
|
|
282
|
+
if (observerEntry) setEntry(observerEntry);
|
|
283
|
+
},
|
|
284
|
+
{ threshold, root, rootMargin }
|
|
285
|
+
);
|
|
286
|
+
observer.observe(node);
|
|
287
|
+
return () => observer.disconnect();
|
|
288
|
+
}, [threshold, root, rootMargin, frozen]);
|
|
289
|
+
return {
|
|
290
|
+
ref,
|
|
291
|
+
isIntersecting: entry?.isIntersecting ?? false,
|
|
292
|
+
entry
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function useResizeObserver() {
|
|
296
|
+
const ref = useRef(null);
|
|
297
|
+
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
const node = ref.current;
|
|
300
|
+
if (!node) return;
|
|
301
|
+
const observer = new ResizeObserver(([resizeEntry]) => {
|
|
302
|
+
if (resizeEntry) {
|
|
303
|
+
const { width, height } = resizeEntry.contentRect;
|
|
304
|
+
setSize({ width, height });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
observer.observe(node);
|
|
308
|
+
return () => observer.disconnect();
|
|
309
|
+
}, []);
|
|
310
|
+
return [ref, size];
|
|
311
|
+
}
|
|
312
|
+
function useMutationObserver(callback, options = { childList: true, subtree: true }) {
|
|
313
|
+
const ref = useRef(null);
|
|
314
|
+
const savedCallback = useRef(callback);
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
savedCallback.current = callback;
|
|
317
|
+
}, [callback]);
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
const node = ref.current;
|
|
320
|
+
if (!node) return;
|
|
321
|
+
const observer = new MutationObserver((...args) => savedCallback.current(...args));
|
|
322
|
+
observer.observe(node, options);
|
|
323
|
+
return () => observer.disconnect();
|
|
324
|
+
}, [options]);
|
|
325
|
+
return ref;
|
|
326
|
+
}
|
|
327
|
+
function useMediaQuery(query) {
|
|
328
|
+
const [matches, setMatches] = useState(() => {
|
|
329
|
+
if (typeof window === "undefined") return false;
|
|
330
|
+
return window.matchMedia(query).matches;
|
|
331
|
+
});
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
const mql = window.matchMedia(query);
|
|
334
|
+
const handler = (e) => setMatches(e.matches);
|
|
335
|
+
setMatches(mql.matches);
|
|
336
|
+
mql.addEventListener("change", handler);
|
|
337
|
+
return () => mql.removeEventListener("change", handler);
|
|
338
|
+
}, [query]);
|
|
339
|
+
return matches;
|
|
340
|
+
}
|
|
341
|
+
function useWindowSize() {
|
|
342
|
+
const [size, setSize] = useState(() => ({
|
|
343
|
+
width: typeof window !== "undefined" ? window.innerWidth : 0,
|
|
344
|
+
height: typeof window !== "undefined" ? window.innerHeight : 0
|
|
345
|
+
}));
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
const handleResize = () => {
|
|
348
|
+
setSize({ width: window.innerWidth, height: window.innerHeight });
|
|
349
|
+
};
|
|
350
|
+
window.addEventListener("resize", handleResize);
|
|
351
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
352
|
+
}, []);
|
|
353
|
+
return size;
|
|
354
|
+
}
|
|
355
|
+
function useScroll(element) {
|
|
356
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
const target = element?.current ?? window;
|
|
359
|
+
const handleScroll = () => {
|
|
360
|
+
if ("scrollX" in target) {
|
|
361
|
+
setPosition({ x: target.scrollX ?? 0, y: target.scrollY ?? 0 });
|
|
362
|
+
} else if (target instanceof HTMLElement) {
|
|
363
|
+
setPosition({ x: target.scrollLeft, y: target.scrollTop });
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
handleScroll();
|
|
367
|
+
target.addEventListener("scroll", handleScroll, { passive: true });
|
|
368
|
+
return () => target.removeEventListener("scroll", handleScroll);
|
|
369
|
+
}, [element]);
|
|
370
|
+
return position;
|
|
371
|
+
}
|
|
372
|
+
function useMouse() {
|
|
373
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
const handleMouseMove = (e) => {
|
|
376
|
+
setPosition({ x: e.clientX, y: e.clientY });
|
|
377
|
+
};
|
|
378
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
379
|
+
return () => window.removeEventListener("mousemove", handleMouseMove);
|
|
380
|
+
}, []);
|
|
381
|
+
return position;
|
|
382
|
+
}
|
|
383
|
+
function useNetwork() {
|
|
384
|
+
const [state, setState] = useState(() => ({
|
|
385
|
+
online: typeof navigator !== "undefined" ? navigator.onLine : true
|
|
386
|
+
}));
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
const connection = navigator.connection;
|
|
389
|
+
const updateNetworkInfo = () => {
|
|
390
|
+
setState({
|
|
391
|
+
online: navigator.onLine,
|
|
392
|
+
downlink: connection?.downlink,
|
|
393
|
+
effectiveType: connection?.effectiveType,
|
|
394
|
+
rtt: connection?.rtt,
|
|
395
|
+
saveData: connection?.saveData
|
|
396
|
+
});
|
|
397
|
+
};
|
|
398
|
+
updateNetworkInfo();
|
|
399
|
+
window.addEventListener("online", updateNetworkInfo);
|
|
400
|
+
window.addEventListener("offline", updateNetworkInfo);
|
|
401
|
+
connection?.addEventListener?.("change", updateNetworkInfo);
|
|
402
|
+
return () => {
|
|
403
|
+
window.removeEventListener("online", updateNetworkInfo);
|
|
404
|
+
window.removeEventListener("offline", updateNetworkInfo);
|
|
405
|
+
connection?.removeEventListener?.("change", updateNetworkInfo);
|
|
406
|
+
};
|
|
407
|
+
}, []);
|
|
408
|
+
return state;
|
|
409
|
+
}
|
|
410
|
+
function useDebounceValue(value, delay) {
|
|
411
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
412
|
+
useEffect(() => {
|
|
413
|
+
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
|
414
|
+
return () => clearTimeout(timer);
|
|
415
|
+
}, [value, delay]);
|
|
416
|
+
return debouncedValue;
|
|
417
|
+
}
|
|
418
|
+
function useDebounceCallback(callback, delay) {
|
|
419
|
+
const callbackRef = useRef(callback);
|
|
420
|
+
const timerRef = useRef(null);
|
|
421
|
+
useEffect(() => {
|
|
422
|
+
callbackRef.current = callback;
|
|
423
|
+
}, [callback]);
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
return () => {
|
|
426
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
427
|
+
};
|
|
428
|
+
}, []);
|
|
429
|
+
return useCallback(
|
|
430
|
+
(...args) => {
|
|
431
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
432
|
+
timerRef.current = setTimeout(() => callbackRef.current(...args), delay);
|
|
433
|
+
},
|
|
434
|
+
[delay]
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
function useThrottleValue(value, interval) {
|
|
438
|
+
const [throttledValue, setThrottledValue] = useState(value);
|
|
439
|
+
const lastUpdated = useRef(Date.now());
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
const now = Date.now();
|
|
442
|
+
const elapsed = now - lastUpdated.current;
|
|
443
|
+
if (elapsed >= interval) {
|
|
444
|
+
lastUpdated.current = now;
|
|
445
|
+
setThrottledValue(value);
|
|
446
|
+
} else {
|
|
447
|
+
const timer = setTimeout(() => {
|
|
448
|
+
lastUpdated.current = Date.now();
|
|
449
|
+
setThrottledValue(value);
|
|
450
|
+
}, interval - elapsed);
|
|
451
|
+
return () => clearTimeout(timer);
|
|
452
|
+
}
|
|
453
|
+
}, [value, interval]);
|
|
454
|
+
return throttledValue;
|
|
455
|
+
}
|
|
456
|
+
function useThrottleCallback(callback, interval) {
|
|
457
|
+
const callbackRef = useRef(callback);
|
|
458
|
+
const lastCalledRef = useRef(0);
|
|
459
|
+
const timerRef = useRef(null);
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
callbackRef.current = callback;
|
|
462
|
+
}, [callback]);
|
|
463
|
+
useEffect(() => {
|
|
464
|
+
return () => {
|
|
465
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
466
|
+
};
|
|
467
|
+
}, []);
|
|
468
|
+
return useCallback(
|
|
469
|
+
(...args) => {
|
|
470
|
+
const now = Date.now();
|
|
471
|
+
const elapsed = now - lastCalledRef.current;
|
|
472
|
+
if (elapsed >= interval) {
|
|
473
|
+
lastCalledRef.current = now;
|
|
474
|
+
callbackRef.current(...args);
|
|
475
|
+
} else if (!timerRef.current) {
|
|
476
|
+
timerRef.current = setTimeout(() => {
|
|
477
|
+
lastCalledRef.current = Date.now();
|
|
478
|
+
timerRef.current = null;
|
|
479
|
+
callbackRef.current(...args);
|
|
480
|
+
}, interval - elapsed);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
[interval]
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
function useMount(callback) {
|
|
487
|
+
useEffect(() => {
|
|
488
|
+
callback();
|
|
489
|
+
}, []);
|
|
490
|
+
}
|
|
491
|
+
function useUnmount(callback) {
|
|
492
|
+
const callbackRef = useRef(callback);
|
|
493
|
+
callbackRef.current = callback;
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
return () => callbackRef.current();
|
|
496
|
+
}, []);
|
|
497
|
+
}
|
|
498
|
+
function useUpdateEffect(effect, deps) {
|
|
499
|
+
const isFirst = useRef(true);
|
|
500
|
+
useEffect(() => {
|
|
501
|
+
if (isFirst.current) {
|
|
502
|
+
isFirst.current = false;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
return effect();
|
|
506
|
+
}, deps);
|
|
507
|
+
}
|
|
508
|
+
function useIsMounted() {
|
|
509
|
+
const isMounted = useRef(false);
|
|
510
|
+
useEffect(() => {
|
|
511
|
+
isMounted.current = true;
|
|
512
|
+
return () => {
|
|
513
|
+
isMounted.current = false;
|
|
514
|
+
};
|
|
515
|
+
}, []);
|
|
516
|
+
return useCallback(() => isMounted.current, []);
|
|
517
|
+
}
|
|
518
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
519
|
+
function deepEqual(a, b) {
|
|
520
|
+
if (Object.is(a, b)) return true;
|
|
521
|
+
if (typeof a !== typeof b) return false;
|
|
522
|
+
if (a === null || b === null) return false;
|
|
523
|
+
if (typeof a !== "object") return false;
|
|
524
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
525
|
+
if (a.length !== b.length) return false;
|
|
526
|
+
return a.every((item, index) => deepEqual(item, b[index]));
|
|
527
|
+
}
|
|
528
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
529
|
+
const objA = a;
|
|
530
|
+
const objB = b;
|
|
531
|
+
const keysA = Object.keys(objA);
|
|
532
|
+
const keysB = Object.keys(objB);
|
|
533
|
+
if (keysA.length !== keysB.length) return false;
|
|
534
|
+
for (const key of keysA) {
|
|
535
|
+
if (!Object.prototype.hasOwnProperty.call(objB, key)) return false;
|
|
536
|
+
if (!deepEqual(objA[key], objB[key])) return false;
|
|
537
|
+
}
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
function useDeepCompareMemoize(deps) {
|
|
541
|
+
const ref = useRef(deps);
|
|
542
|
+
if (!deepEqual(deps, ref.current)) {
|
|
543
|
+
ref.current = deps;
|
|
544
|
+
}
|
|
545
|
+
return ref.current;
|
|
546
|
+
}
|
|
547
|
+
function useDeepCompareEffect(effect, deps) {
|
|
548
|
+
useEffect(effect, useDeepCompareMemoize(deps));
|
|
549
|
+
}
|
|
550
|
+
function useScrollLock(locked) {
|
|
551
|
+
useEffect(() => {
|
|
552
|
+
if (!locked) return;
|
|
553
|
+
const originalOverflow = document.body.style.overflow;
|
|
554
|
+
const originalPaddingRight = document.body.style.paddingRight;
|
|
555
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
556
|
+
document.body.style.overflow = "hidden";
|
|
557
|
+
if (scrollbarWidth > 0) {
|
|
558
|
+
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
|
559
|
+
}
|
|
560
|
+
return () => {
|
|
561
|
+
document.body.style.overflow = originalOverflow;
|
|
562
|
+
document.body.style.paddingRight = originalPaddingRight;
|
|
563
|
+
};
|
|
564
|
+
}, [locked]);
|
|
565
|
+
}
|
|
566
|
+
function useDarkMode(defaultTheme = "system") {
|
|
567
|
+
const [theme, setThemeState] = useState(() => {
|
|
568
|
+
if (typeof window === "undefined") return defaultTheme;
|
|
569
|
+
return localStorage.getItem("theme") ?? defaultTheme;
|
|
570
|
+
});
|
|
571
|
+
const [systemDark, setSystemDark] = useState(() => {
|
|
572
|
+
if (typeof window === "undefined") return false;
|
|
573
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
574
|
+
});
|
|
575
|
+
useEffect(() => {
|
|
576
|
+
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
577
|
+
const handler = (e) => setSystemDark(e.matches);
|
|
578
|
+
mql.addEventListener("change", handler);
|
|
579
|
+
return () => mql.removeEventListener("change", handler);
|
|
580
|
+
}, []);
|
|
581
|
+
const isDark = theme === "system" ? systemDark : theme === "dark";
|
|
582
|
+
const setTheme = useCallback((newTheme) => {
|
|
583
|
+
setThemeState(newTheme);
|
|
584
|
+
localStorage.setItem("theme", newTheme);
|
|
585
|
+
}, []);
|
|
586
|
+
const toggle = useCallback(() => {
|
|
587
|
+
setTheme(isDark ? "light" : "dark");
|
|
588
|
+
}, [isDark, setTheme]);
|
|
589
|
+
useEffect(() => {
|
|
590
|
+
document.documentElement.classList.toggle("dark", isDark);
|
|
591
|
+
}, [isDark]);
|
|
592
|
+
return { isDark, theme, setTheme, toggle };
|
|
593
|
+
}
|
|
594
|
+
function useFullscreen() {
|
|
595
|
+
const ref = useRef(null);
|
|
596
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
597
|
+
useEffect(() => {
|
|
598
|
+
const handler = () => setIsFullscreen(!!document.fullscreenElement);
|
|
599
|
+
document.addEventListener("fullscreenchange", handler);
|
|
600
|
+
return () => document.removeEventListener("fullscreenchange", handler);
|
|
601
|
+
}, []);
|
|
602
|
+
const enter = useCallback(async () => {
|
|
603
|
+
const element = ref.current;
|
|
604
|
+
if (element) await element.requestFullscreen();
|
|
605
|
+
}, []);
|
|
606
|
+
const exit = useCallback(async () => {
|
|
607
|
+
if (document.fullscreenElement) await document.exitFullscreen();
|
|
608
|
+
}, []);
|
|
609
|
+
const toggle = useCallback(async () => {
|
|
610
|
+
if (isFullscreen) await exit();
|
|
611
|
+
else await enter();
|
|
612
|
+
}, [isFullscreen, enter, exit]);
|
|
613
|
+
return { ref, isFullscreen, enter, exit, toggle };
|
|
614
|
+
}
|
|
615
|
+
function useCopyToClipboard() {
|
|
616
|
+
const [copiedText, setCopiedText] = useState(null);
|
|
617
|
+
const copy = useCallback(async (text) => {
|
|
618
|
+
if (!navigator?.clipboard) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
await navigator.clipboard.writeText(text);
|
|
623
|
+
setCopiedText(text);
|
|
624
|
+
return true;
|
|
625
|
+
} catch {
|
|
626
|
+
setCopiedText(null);
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
}, []);
|
|
630
|
+
return { copiedText, copy };
|
|
631
|
+
}
|
|
632
|
+
function parseKeyCombo(combo) {
|
|
633
|
+
const parts = combo.toLowerCase().split("+");
|
|
634
|
+
return {
|
|
635
|
+
key: parts[parts.length - 1],
|
|
636
|
+
ctrl: parts.includes("ctrl") || parts.includes("control"),
|
|
637
|
+
shift: parts.includes("shift"),
|
|
638
|
+
alt: parts.includes("alt"),
|
|
639
|
+
meta: parts.includes("meta") || parts.includes("cmd")
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function useHotkeys(keyCombo, callback, enabled = true) {
|
|
643
|
+
const callbackRef = useRef(callback);
|
|
644
|
+
callbackRef.current = callback;
|
|
645
|
+
useEffect(() => {
|
|
646
|
+
if (!enabled) return;
|
|
647
|
+
const parsed = parseKeyCombo(keyCombo);
|
|
648
|
+
const handler = (event) => {
|
|
649
|
+
const matches = event.key.toLowerCase() === parsed.key && event.ctrlKey === parsed.ctrl && event.shiftKey === parsed.shift && event.altKey === parsed.alt && event.metaKey === parsed.meta;
|
|
650
|
+
if (matches) {
|
|
651
|
+
event.preventDefault();
|
|
652
|
+
callbackRef.current(event);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
window.addEventListener("keydown", handler);
|
|
656
|
+
return () => window.removeEventListener("keydown", handler);
|
|
657
|
+
}, [keyCombo, enabled]);
|
|
658
|
+
}
|
|
659
|
+
function useAsync(asyncFunction, immediate = true) {
|
|
660
|
+
const [state, setState] = useState({
|
|
661
|
+
data: null,
|
|
662
|
+
loading: immediate,
|
|
663
|
+
error: null
|
|
664
|
+
});
|
|
665
|
+
const execute = useCallback(async () => {
|
|
666
|
+
setState({ data: null, loading: true, error: null });
|
|
667
|
+
try {
|
|
668
|
+
const data = await asyncFunction();
|
|
669
|
+
setState({ data, loading: false, error: null });
|
|
670
|
+
} catch (error) {
|
|
671
|
+
setState({ data: null, loading: false, error });
|
|
672
|
+
}
|
|
673
|
+
}, [asyncFunction]);
|
|
674
|
+
useEffect(() => {
|
|
675
|
+
if (immediate) {
|
|
676
|
+
execute();
|
|
677
|
+
}
|
|
678
|
+
}, [execute, immediate]);
|
|
679
|
+
return { ...state, execute };
|
|
680
|
+
}
|
|
681
|
+
function useFetch(url, options = {}) {
|
|
682
|
+
const { enabled = true, ...fetchOptions } = options;
|
|
683
|
+
const [data, setData] = useState(null);
|
|
684
|
+
const [loading, setLoading] = useState(enabled);
|
|
685
|
+
const [error, setError] = useState(null);
|
|
686
|
+
const abortControllerRef = useRef(null);
|
|
687
|
+
const fetchData = useCallback(async () => {
|
|
688
|
+
abortControllerRef.current?.abort();
|
|
689
|
+
abortControllerRef.current = new AbortController();
|
|
690
|
+
setLoading(true);
|
|
691
|
+
setError(null);
|
|
692
|
+
try {
|
|
693
|
+
const response = await fetch(url, {
|
|
694
|
+
...fetchOptions,
|
|
695
|
+
signal: abortControllerRef.current.signal
|
|
696
|
+
});
|
|
697
|
+
if (!response.ok) {
|
|
698
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
699
|
+
}
|
|
700
|
+
const json = await response.json();
|
|
701
|
+
setData(json);
|
|
702
|
+
} catch (err) {
|
|
703
|
+
if (err.name !== "AbortError") {
|
|
704
|
+
setError(err);
|
|
705
|
+
}
|
|
706
|
+
} finally {
|
|
707
|
+
setLoading(false);
|
|
708
|
+
}
|
|
709
|
+
}, [url]);
|
|
710
|
+
useEffect(() => {
|
|
711
|
+
if (!enabled) return;
|
|
712
|
+
fetchData();
|
|
713
|
+
return () => abortControllerRef.current?.abort();
|
|
714
|
+
}, [fetchData, enabled]);
|
|
715
|
+
return { data, loading, error, refetch: fetchData };
|
|
716
|
+
}
|
|
717
|
+
function useInfiniteScroll(loadMore, hasMore, options = {}) {
|
|
718
|
+
const { threshold = 0, rootMargin = "100px" } = options;
|
|
719
|
+
const sentinelRef = useRef(null);
|
|
720
|
+
const [loading, setLoading] = useState(false);
|
|
721
|
+
const loadingRef = useRef(false);
|
|
722
|
+
const handleLoadMore = useCallback(async () => {
|
|
723
|
+
if (loadingRef.current || !hasMore) return;
|
|
724
|
+
loadingRef.current = true;
|
|
725
|
+
setLoading(true);
|
|
726
|
+
try {
|
|
727
|
+
await loadMore();
|
|
728
|
+
} finally {
|
|
729
|
+
loadingRef.current = false;
|
|
730
|
+
setLoading(false);
|
|
731
|
+
}
|
|
732
|
+
}, [loadMore, hasMore]);
|
|
733
|
+
useEffect(() => {
|
|
734
|
+
const node = sentinelRef.current;
|
|
735
|
+
if (!node || !hasMore) return;
|
|
736
|
+
const observer = new IntersectionObserver(
|
|
737
|
+
([entry]) => {
|
|
738
|
+
if (entry?.isIntersecting) {
|
|
739
|
+
handleLoadMore();
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
{ threshold, rootMargin }
|
|
743
|
+
);
|
|
744
|
+
observer.observe(node);
|
|
745
|
+
return () => observer.disconnect();
|
|
746
|
+
}, [handleLoadMore, hasMore, threshold, rootMargin]);
|
|
747
|
+
const reset = useCallback(() => {
|
|
748
|
+
loadingRef.current = false;
|
|
749
|
+
setLoading(false);
|
|
750
|
+
}, []);
|
|
751
|
+
return { sentinelRef, loading, reset };
|
|
752
|
+
}
|
|
753
|
+
function usePagination(options) {
|
|
754
|
+
const { totalItems, pageSize: initialPageSize = 10, initialPage = 1 } = options;
|
|
755
|
+
const [page, setPageState] = useState(initialPage);
|
|
756
|
+
const [pageSize, setPageSizeState] = useState(initialPageSize);
|
|
757
|
+
const totalPages = useMemo(
|
|
758
|
+
() => Math.max(1, Math.ceil(totalItems / pageSize)),
|
|
759
|
+
[totalItems, pageSize]
|
|
760
|
+
);
|
|
761
|
+
const setPage = useCallback(
|
|
762
|
+
(newPage) => {
|
|
763
|
+
setPageState(Math.min(Math.max(1, newPage), totalPages));
|
|
764
|
+
},
|
|
765
|
+
[totalPages]
|
|
766
|
+
);
|
|
767
|
+
const setPageSize = useCallback((newSize) => {
|
|
768
|
+
setPageSizeState(newSize);
|
|
769
|
+
setPageState(1);
|
|
770
|
+
}, []);
|
|
771
|
+
const next = useCallback(() => setPage(page + 1), [page, setPage]);
|
|
772
|
+
const prev = useCallback(() => setPage(page - 1), [page, setPage]);
|
|
773
|
+
const startIndex = (page - 1) * pageSize;
|
|
774
|
+
const endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1);
|
|
775
|
+
return {
|
|
776
|
+
page,
|
|
777
|
+
pageSize,
|
|
778
|
+
totalPages,
|
|
779
|
+
hasNext: page < totalPages,
|
|
780
|
+
hasPrev: page > 1,
|
|
781
|
+
next,
|
|
782
|
+
prev,
|
|
783
|
+
setPage,
|
|
784
|
+
setPageSize,
|
|
785
|
+
startIndex,
|
|
786
|
+
endIndex
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export { useAsync, useBoolean, useClickOutside, useCopyToClipboard, useCounter, useDarkMode, useDebounceCallback, useDebounceValue, useDeepCompareEffect, useEventListener, useFetch, useFullscreen, useHotkeys, useHover, useInfiniteScroll, useIntersectionObserver, useIsMounted, useIsomorphicLayoutEffect, useLocalStorage, useMap, useMediaQuery, useMount, useMouse, useMutationObserver, useNetwork, usePagination, usePrevious, useResizeObserver, useScroll, useScrollLock, useSessionStorage, useSet, useStateHistory, useThrottleCallback, useThrottleValue, useToggle, useUnmount, useUpdateEffect, useWindowSize };
|
|
791
|
+
//# sourceMappingURL=index.mjs.map
|
|
792
|
+
//# sourceMappingURL=index.mjs.map
|