sse-hooks 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,1324 @@
1
+ 'use strict';
2
+
3
+ const react = require('react');
4
+ const debounce = require('lodash.debounce');
5
+
6
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
7
+
8
+ const debounce__default = /*#__PURE__*/_interopDefaultCompat(debounce);
9
+
10
+ function useBoolean(defaultValue = false) {
11
+ if (typeof defaultValue !== "boolean") {
12
+ throw new Error("defaultValue must be `true` or `false`");
13
+ }
14
+ const [value, setValue] = react.useState(defaultValue);
15
+ const setTrue = react.useCallback(() => {
16
+ setValue(true);
17
+ }, []);
18
+ const setFalse = react.useCallback(() => {
19
+ setValue(false);
20
+ }, []);
21
+ const toggle = react.useCallback(() => {
22
+ setValue((x) => !x);
23
+ }, []);
24
+ return { value, setValue, setTrue, setFalse, toggle };
25
+ }
26
+
27
+ function useCopyToClipboard() {
28
+ const [copiedText, setCopiedText] = react.useState(null);
29
+ const copy = react.useCallback(async (text) => {
30
+ if (!navigator?.clipboard) {
31
+ console.warn("Clipboard not supported");
32
+ return false;
33
+ }
34
+ try {
35
+ await navigator.clipboard.writeText(text);
36
+ setCopiedText(text);
37
+ return true;
38
+ } catch (error) {
39
+ console.warn("Copy failed", error);
40
+ setCopiedText(null);
41
+ return false;
42
+ }
43
+ }, []);
44
+ return [copiedText, copy];
45
+ }
46
+
47
+ function useCounter(initialValue) {
48
+ const [count, setCount] = react.useState(initialValue ?? 0);
49
+ const increment = react.useCallback(() => {
50
+ setCount((x) => x + 1);
51
+ }, []);
52
+ const decrement = react.useCallback(() => {
53
+ setCount((x) => x - 1);
54
+ }, []);
55
+ const reset = react.useCallback(() => {
56
+ setCount(initialValue ?? 0);
57
+ }, [initialValue]);
58
+ return {
59
+ count,
60
+ increment,
61
+ decrement,
62
+ reset,
63
+ setCount
64
+ };
65
+ }
66
+
67
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
68
+
69
+ function useInterval(callback, delay) {
70
+ const savedCallback = react.useRef(callback);
71
+ useIsomorphicLayoutEffect(() => {
72
+ savedCallback.current = callback;
73
+ }, [callback]);
74
+ react.useEffect(() => {
75
+ if (delay === null) {
76
+ return;
77
+ }
78
+ const id = setInterval(() => {
79
+ savedCallback.current();
80
+ }, delay);
81
+ return () => {
82
+ clearInterval(id);
83
+ };
84
+ }, [delay]);
85
+ }
86
+
87
+ function useCountdown({
88
+ countStart,
89
+ countStop = 0,
90
+ intervalMs = 1e3,
91
+ isIncrement = false
92
+ }) {
93
+ const {
94
+ count,
95
+ increment,
96
+ decrement,
97
+ reset: resetCounter
98
+ } = useCounter(countStart);
99
+ const {
100
+ value: isCountdownRunning,
101
+ setTrue: startCountdown,
102
+ setFalse: stopCountdown
103
+ } = useBoolean(false);
104
+ const resetCountdown = react.useCallback(() => {
105
+ stopCountdown();
106
+ resetCounter();
107
+ }, [stopCountdown, resetCounter]);
108
+ const countdownCallback = react.useCallback(() => {
109
+ if (count === countStop) {
110
+ stopCountdown();
111
+ return;
112
+ }
113
+ if (isIncrement) {
114
+ increment();
115
+ } else {
116
+ decrement();
117
+ }
118
+ }, [count, countStop, decrement, increment, isIncrement, stopCountdown]);
119
+ useInterval(countdownCallback, isCountdownRunning ? intervalMs : null);
120
+ return [count, { startCountdown, stopCountdown, resetCountdown }];
121
+ }
122
+
123
+ function useEventCallback(fn) {
124
+ const ref = react.useRef(() => {
125
+ throw new Error("Cannot call an event handler while rendering.");
126
+ });
127
+ useIsomorphicLayoutEffect(() => {
128
+ ref.current = fn;
129
+ }, [fn]);
130
+ return react.useCallback((...args) => ref.current?.(...args), [ref]);
131
+ }
132
+
133
+ function useEventListener(eventName, handler, element, options) {
134
+ const savedHandler = react.useRef(handler);
135
+ useIsomorphicLayoutEffect(() => {
136
+ savedHandler.current = handler;
137
+ }, [handler]);
138
+ react.useEffect(() => {
139
+ const targetElement = element?.current ?? window;
140
+ if (!(targetElement && targetElement.addEventListener)) return;
141
+ const listener = (event) => {
142
+ savedHandler.current(event);
143
+ };
144
+ targetElement.addEventListener(eventName, listener, options);
145
+ return () => {
146
+ targetElement.removeEventListener(eventName, listener, options);
147
+ };
148
+ }, [eventName, element, options]);
149
+ }
150
+
151
+ const IS_SERVER$6 = typeof window === "undefined";
152
+ function useLocalStorage(key, initialValue, options = {}) {
153
+ const { initializeWithValue = true } = options;
154
+ const serializer = react.useCallback(
155
+ (value) => {
156
+ if (options.serializer) {
157
+ return options.serializer(value);
158
+ }
159
+ return JSON.stringify(value);
160
+ },
161
+ [options]
162
+ );
163
+ const deserializer = react.useCallback(
164
+ (value) => {
165
+ if (options.deserializer) {
166
+ return options.deserializer(value);
167
+ }
168
+ if (value === "undefined") {
169
+ return void 0;
170
+ }
171
+ const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;
172
+ let parsed;
173
+ try {
174
+ parsed = JSON.parse(value);
175
+ } catch (error) {
176
+ console.error("Error parsing JSON:", error);
177
+ return defaultValue;
178
+ }
179
+ return parsed;
180
+ },
181
+ [options, initialValue]
182
+ );
183
+ const readValue = react.useCallback(() => {
184
+ const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue;
185
+ if (IS_SERVER$6) {
186
+ return initialValueToUse;
187
+ }
188
+ try {
189
+ const raw = window.localStorage.getItem(key);
190
+ return raw ? deserializer(raw) : initialValueToUse;
191
+ } catch (error) {
192
+ console.warn(`Error reading localStorage key \u201C${key}\u201D:`, error);
193
+ return initialValueToUse;
194
+ }
195
+ }, [initialValue, key, deserializer]);
196
+ const [storedValue, setStoredValue] = react.useState(() => {
197
+ if (initializeWithValue) {
198
+ return readValue();
199
+ }
200
+ return initialValue instanceof Function ? initialValue() : initialValue;
201
+ });
202
+ const setValue = useEventCallback((value) => {
203
+ if (IS_SERVER$6) {
204
+ console.warn(
205
+ `Tried setting localStorage key \u201C${key}\u201D even though environment is not a client`
206
+ );
207
+ }
208
+ try {
209
+ const newValue = value instanceof Function ? value(readValue()) : value;
210
+ window.localStorage.setItem(key, serializer(newValue));
211
+ setStoredValue(newValue);
212
+ window.dispatchEvent(new StorageEvent("local-storage", { key }));
213
+ } catch (error) {
214
+ console.warn(`Error setting localStorage key \u201C${key}\u201D:`, error);
215
+ }
216
+ });
217
+ const removeValue = useEventCallback(() => {
218
+ if (IS_SERVER$6) {
219
+ console.warn(
220
+ `Tried removing localStorage key \u201C${key}\u201D even though environment is not a client`
221
+ );
222
+ }
223
+ const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;
224
+ window.localStorage.removeItem(key);
225
+ setStoredValue(defaultValue);
226
+ window.dispatchEvent(new StorageEvent("local-storage", { key }));
227
+ });
228
+ react.useEffect(() => {
229
+ setStoredValue(readValue());
230
+ }, [key]);
231
+ const handleStorageChange = react.useCallback(
232
+ (event) => {
233
+ if (event.key && event.key !== key) {
234
+ return;
235
+ }
236
+ setStoredValue(readValue());
237
+ },
238
+ [key, readValue]
239
+ );
240
+ useEventListener("storage", handleStorageChange);
241
+ useEventListener("local-storage", handleStorageChange);
242
+ return [storedValue, setValue, removeValue];
243
+ }
244
+
245
+ const IS_SERVER$5 = typeof window === "undefined";
246
+ function useMediaQuery(query, {
247
+ defaultValue = false,
248
+ initializeWithValue = true
249
+ } = {}) {
250
+ const getMatches = (query2) => {
251
+ if (IS_SERVER$5) {
252
+ return defaultValue;
253
+ }
254
+ return window.matchMedia(query2).matches;
255
+ };
256
+ const [matches, setMatches] = react.useState(() => {
257
+ if (initializeWithValue) {
258
+ return getMatches(query);
259
+ }
260
+ return defaultValue;
261
+ });
262
+ function handleChange() {
263
+ setMatches(getMatches(query));
264
+ }
265
+ useIsomorphicLayoutEffect(() => {
266
+ const matchMedia = window.matchMedia(query);
267
+ handleChange();
268
+ if (matchMedia.addListener) {
269
+ matchMedia.addListener(handleChange);
270
+ } else {
271
+ matchMedia.addEventListener("change", handleChange);
272
+ }
273
+ return () => {
274
+ if (matchMedia.removeListener) {
275
+ matchMedia.removeListener(handleChange);
276
+ } else {
277
+ matchMedia.removeEventListener("change", handleChange);
278
+ }
279
+ };
280
+ }, [query]);
281
+ return matches;
282
+ }
283
+
284
+ const COLOR_SCHEME_QUERY$1 = "(prefers-color-scheme: dark)";
285
+ const LOCAL_STORAGE_KEY$1 = "usehooks-ts-dark-mode";
286
+ function useDarkMode(options = {}) {
287
+ const {
288
+ defaultValue,
289
+ localStorageKey = LOCAL_STORAGE_KEY$1,
290
+ initializeWithValue = true
291
+ } = options;
292
+ const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY$1, {
293
+ initializeWithValue,
294
+ defaultValue
295
+ });
296
+ const [isDarkMode, setDarkMode] = useLocalStorage(
297
+ localStorageKey,
298
+ defaultValue ?? isDarkOS ?? false,
299
+ { initializeWithValue }
300
+ );
301
+ useIsomorphicLayoutEffect(() => {
302
+ if (isDarkOS !== isDarkMode) {
303
+ setDarkMode(isDarkOS);
304
+ }
305
+ }, [isDarkOS]);
306
+ return {
307
+ isDarkMode,
308
+ toggle: () => {
309
+ setDarkMode((prev) => !prev);
310
+ },
311
+ enable: () => {
312
+ setDarkMode(true);
313
+ },
314
+ disable: () => {
315
+ setDarkMode(false);
316
+ },
317
+ set: (value) => {
318
+ setDarkMode(value);
319
+ }
320
+ };
321
+ }
322
+
323
+ function useUnmount(func) {
324
+ const funcRef = react.useRef(func);
325
+ funcRef.current = func;
326
+ react.useEffect(
327
+ () => () => {
328
+ funcRef.current();
329
+ },
330
+ []
331
+ );
332
+ }
333
+
334
+ function useDebounceCallback(func, delay = 500, options) {
335
+ const debouncedFunc = react.useRef();
336
+ useUnmount(() => {
337
+ if (debouncedFunc.current) {
338
+ debouncedFunc.current.cancel();
339
+ }
340
+ });
341
+ const debounced = react.useMemo(() => {
342
+ const debouncedFuncInstance = debounce__default(func, delay, options);
343
+ const wrappedFunc = (...args) => {
344
+ return debouncedFuncInstance(...args);
345
+ };
346
+ wrappedFunc.cancel = () => {
347
+ debouncedFuncInstance.cancel();
348
+ };
349
+ wrappedFunc.isPending = () => {
350
+ return !!debouncedFunc.current;
351
+ };
352
+ wrappedFunc.flush = () => {
353
+ return debouncedFuncInstance.flush();
354
+ };
355
+ return wrappedFunc;
356
+ }, [func, delay, options]);
357
+ react.useEffect(() => {
358
+ debouncedFunc.current = debounce__default(func, delay, options);
359
+ }, [func, delay, options]);
360
+ return debounced;
361
+ }
362
+
363
+ function useDebounceValue(initialValue, delay, options) {
364
+ const eq = options?.equalityFn ?? ((left, right) => left === right);
365
+ const unwrappedInitialValue = initialValue instanceof Function ? initialValue() : initialValue;
366
+ const [debouncedValue, setDebouncedValue] = react.useState(
367
+ unwrappedInitialValue
368
+ );
369
+ const previousValueRef = react.useRef(unwrappedInitialValue);
370
+ const updateDebouncedValue = useDebounceCallback(
371
+ setDebouncedValue,
372
+ delay,
373
+ options
374
+ );
375
+ if (!eq(previousValueRef.current, unwrappedInitialValue)) {
376
+ updateDebouncedValue(unwrappedInitialValue);
377
+ previousValueRef.current = unwrappedInitialValue;
378
+ }
379
+ return [debouncedValue, updateDebouncedValue];
380
+ }
381
+
382
+ function useDocumentTitle(title, options = {}) {
383
+ const { preserveTitleOnUnmount = true } = options;
384
+ const defaultTitle = react.useRef(null);
385
+ useIsomorphicLayoutEffect(() => {
386
+ defaultTitle.current = window.document.title;
387
+ }, []);
388
+ useIsomorphicLayoutEffect(() => {
389
+ window.document.title = title;
390
+ }, [title]);
391
+ useUnmount(() => {
392
+ if (!preserveTitleOnUnmount && defaultTitle.current) {
393
+ window.document.title = defaultTitle.current;
394
+ }
395
+ });
396
+ }
397
+
398
+ function useFetch(url, options = {}) {
399
+ const [state, setState] = react.useState({
400
+ data: null,
401
+ loading: false,
402
+ error: null
403
+ });
404
+ const abortControllerRef = react.useRef(null);
405
+ const optionsRef = react.useRef(options);
406
+ react.useEffect(() => {
407
+ optionsRef.current = options;
408
+ }, [options]);
409
+ const execute = react.useCallback(
410
+ async (executeUrl, executeOptions) => {
411
+ const targetUrl = executeUrl || url;
412
+ if (!targetUrl) {
413
+ const error = new Error("No URL provided");
414
+ setState((prev) => ({ ...prev, error, loading: false }));
415
+ optionsRef.current.onError?.(error);
416
+ throw error;
417
+ }
418
+ if (abortControllerRef.current) {
419
+ abortControllerRef.current.abort();
420
+ }
421
+ abortControllerRef.current = new AbortController();
422
+ setState((prev) => ({ ...prev, loading: true, error: null }));
423
+ try {
424
+ const { immediate, onSuccess, onError, ...fetchOptions } = optionsRef.current;
425
+ const response = await fetch(targetUrl, {
426
+ ...fetchOptions,
427
+ ...executeOptions,
428
+ signal: abortControllerRef.current.signal
429
+ });
430
+ if (!response.ok) {
431
+ throw new Error(`HTTP error! status: ${response.status}`);
432
+ }
433
+ let data;
434
+ const contentType = response.headers.get("content-type");
435
+ if (contentType && contentType.includes("application/json")) {
436
+ data = await response.json();
437
+ } else {
438
+ data = await response.text();
439
+ }
440
+ setState({ data, loading: false, error: null });
441
+ onSuccess?.(data);
442
+ return data;
443
+ } catch (error) {
444
+ const fetchError = error;
445
+ if (fetchError.name !== "AbortError") {
446
+ setState((prev) => ({ ...prev, loading: false, error: fetchError }));
447
+ optionsRef.current.onError?.(fetchError);
448
+ }
449
+ throw fetchError;
450
+ }
451
+ },
452
+ [url]
453
+ );
454
+ const abort = react.useCallback(() => {
455
+ if (abortControllerRef.current) {
456
+ abortControllerRef.current.abort();
457
+ abortControllerRef.current = null;
458
+ }
459
+ }, []);
460
+ const reset = react.useCallback(() => {
461
+ abort();
462
+ setState({ data: null, loading: false, error: null });
463
+ }, [abort]);
464
+ react.useEffect(() => {
465
+ if (options.immediate && url) {
466
+ execute();
467
+ }
468
+ }, [url, options.immediate, execute]);
469
+ react.useEffect(() => {
470
+ return () => {
471
+ abort();
472
+ };
473
+ }, [abort]);
474
+ return {
475
+ ...state,
476
+ execute,
477
+ abort,
478
+ reset
479
+ };
480
+ }
481
+ function useGet(url, options = {}) {
482
+ return useFetch(url, { ...options, method: "GET" });
483
+ }
484
+ function usePost(url, options = {}) {
485
+ return useFetch(url, { ...options, method: "POST" });
486
+ }
487
+ function usePut(url, options = {}) {
488
+ return useFetch(url, { ...options, method: "PUT" });
489
+ }
490
+ function useDelete(url, options = {}) {
491
+ return useFetch(url, { ...options, method: "DELETE" });
492
+ }
493
+
494
+ function useHover(elementRef) {
495
+ const [value, setValue] = react.useState(false);
496
+ const handleMouseEnter = () => {
497
+ setValue(true);
498
+ };
499
+ const handleMouseLeave = () => {
500
+ setValue(false);
501
+ };
502
+ useEventListener("mouseenter", handleMouseEnter, elementRef);
503
+ useEventListener("mouseleave", handleMouseLeave, elementRef);
504
+ return value;
505
+ }
506
+
507
+ function useIndexedDB(databaseName, storeName, options = {}) {
508
+ const [data, setData] = react.useState(null);
509
+ const [error, setError] = react.useState(null);
510
+ const [loading, setLoading] = react.useState(false);
511
+ const [db, setDb] = react.useState(null);
512
+ const { version = 1, onUpgradeNeeded } = options;
513
+ react.useEffect(() => {
514
+ if (typeof window === "undefined") return;
515
+ const initDB = async () => {
516
+ try {
517
+ setLoading(true);
518
+ setError(null);
519
+ const request = indexedDB.open(databaseName, version);
520
+ request.onerror = () => {
521
+ setError(`Failed to open database: ${request.error?.message}`);
522
+ setLoading(false);
523
+ };
524
+ request.onsuccess = () => {
525
+ setDb(request.result);
526
+ setLoading(false);
527
+ };
528
+ request.onupgradeneeded = (event) => {
529
+ const database = request.result;
530
+ const oldVersion = event.oldVersion;
531
+ const newVersion = event.newVersion || version;
532
+ if (!database.objectStoreNames.contains(storeName)) {
533
+ database.createObjectStore(storeName);
534
+ }
535
+ if (onUpgradeNeeded) {
536
+ onUpgradeNeeded(database, oldVersion, newVersion);
537
+ }
538
+ };
539
+ } catch (err) {
540
+ setError(`IndexedDB initialization error: ${err}`);
541
+ setLoading(false);
542
+ }
543
+ };
544
+ initDB();
545
+ return () => {
546
+ if (db) {
547
+ db.close();
548
+ }
549
+ };
550
+ }, [databaseName, storeName, version, onUpgradeNeeded]);
551
+ const setItem = react.useCallback(
552
+ async (key, value) => {
553
+ if (!db) {
554
+ throw new Error("Database not initialized");
555
+ }
556
+ return new Promise((resolve, reject) => {
557
+ const transaction = db.transaction([storeName], "readwrite");
558
+ const store = transaction.objectStore(storeName);
559
+ const request = store.put(value, key);
560
+ request.onsuccess = () => {
561
+ setData(value);
562
+ resolve();
563
+ };
564
+ request.onerror = () => {
565
+ const errorMsg = `Failed to set item: ${request.error?.message}`;
566
+ setError(errorMsg);
567
+ reject(new Error(errorMsg));
568
+ };
569
+ });
570
+ },
571
+ [db, storeName]
572
+ );
573
+ const getItem = react.useCallback(
574
+ async (key) => {
575
+ if (!db) {
576
+ throw new Error("Database not initialized");
577
+ }
578
+ return new Promise((resolve, reject) => {
579
+ const transaction = db.transaction([storeName], "readonly");
580
+ const store = transaction.objectStore(storeName);
581
+ const request = store.get(key);
582
+ request.onsuccess = () => {
583
+ const result = request.result || null;
584
+ setData(result);
585
+ resolve(result);
586
+ };
587
+ request.onerror = () => {
588
+ const errorMsg = `Failed to get item: ${request.error?.message}`;
589
+ setError(errorMsg);
590
+ reject(new Error(errorMsg));
591
+ };
592
+ });
593
+ },
594
+ [db, storeName]
595
+ );
596
+ const removeItem = react.useCallback(
597
+ async (key) => {
598
+ if (!db) {
599
+ throw new Error("Database not initialized");
600
+ }
601
+ return new Promise((resolve, reject) => {
602
+ const transaction = db.transaction([storeName], "readwrite");
603
+ const store = transaction.objectStore(storeName);
604
+ const request = store.delete(key);
605
+ request.onsuccess = () => {
606
+ setData(null);
607
+ resolve();
608
+ };
609
+ request.onerror = () => {
610
+ const errorMsg = `Failed to remove item: ${request.error?.message}`;
611
+ setError(errorMsg);
612
+ reject(new Error(errorMsg));
613
+ };
614
+ });
615
+ },
616
+ [db, storeName]
617
+ );
618
+ const clear = react.useCallback(async () => {
619
+ if (!db) {
620
+ throw new Error("Database not initialized");
621
+ }
622
+ return new Promise((resolve, reject) => {
623
+ const transaction = db.transaction([storeName], "readwrite");
624
+ const store = transaction.objectStore(storeName);
625
+ const request = store.clear();
626
+ request.onsuccess = () => {
627
+ setData(null);
628
+ resolve();
629
+ };
630
+ request.onerror = () => {
631
+ const errorMsg = `Failed to clear store: ${request.error?.message}`;
632
+ setError(errorMsg);
633
+ reject(new Error(errorMsg));
634
+ };
635
+ });
636
+ }, [db, storeName]);
637
+ const getAllKeys = react.useCallback(async () => {
638
+ if (!db) {
639
+ throw new Error("Database not initialized");
640
+ }
641
+ return new Promise((resolve, reject) => {
642
+ const transaction = db.transaction([storeName], "readonly");
643
+ const store = transaction.objectStore(storeName);
644
+ const request = store.getAllKeys();
645
+ request.onsuccess = () => {
646
+ resolve(request.result);
647
+ };
648
+ request.onerror = () => {
649
+ const errorMsg = `Failed to get keys: ${request.error?.message}`;
650
+ setError(errorMsg);
651
+ reject(new Error(errorMsg));
652
+ };
653
+ });
654
+ }, [db, storeName]);
655
+ return {
656
+ data,
657
+ error,
658
+ loading,
659
+ setItem,
660
+ getItem,
661
+ removeItem,
662
+ clear,
663
+ getAllKeys
664
+ };
665
+ }
666
+
667
+ function useIntersectionObserver({
668
+ threshold = 0,
669
+ root = null,
670
+ rootMargin = "0%",
671
+ freezeOnceVisible = false,
672
+ initialIsIntersecting = false,
673
+ onChange
674
+ } = {}) {
675
+ const [ref, setRef] = react.useState(null);
676
+ const [state, setState] = react.useState(() => ({
677
+ isIntersecting: initialIsIntersecting,
678
+ entry: void 0
679
+ }));
680
+ const callbackRef = react.useRef();
681
+ callbackRef.current = onChange;
682
+ const frozen = state.entry?.isIntersecting && freezeOnceVisible;
683
+ react.useEffect(() => {
684
+ if (!ref) return;
685
+ if (!("IntersectionObserver" in window)) return;
686
+ if (frozen) return;
687
+ const observer = new IntersectionObserver(
688
+ (entries) => {
689
+ const thresholds = Array.isArray(observer.thresholds) ? observer.thresholds : [observer.thresholds];
690
+ entries.forEach((entry) => {
691
+ const isIntersecting = entry.isIntersecting && thresholds.some(
692
+ (threshold2) => entry.intersectionRatio >= threshold2
693
+ );
694
+ setState({ isIntersecting, entry });
695
+ if (callbackRef.current) {
696
+ callbackRef.current(isIntersecting, entry);
697
+ }
698
+ });
699
+ },
700
+ { threshold, root, rootMargin }
701
+ );
702
+ observer.observe(ref);
703
+ return () => {
704
+ observer.disconnect();
705
+ };
706
+ }, [
707
+ ref,
708
+ // eslint-disable-next-line react-hooks/exhaustive-deps
709
+ JSON.stringify(threshold),
710
+ root,
711
+ rootMargin,
712
+ frozen,
713
+ freezeOnceVisible
714
+ ]);
715
+ const prevRef = react.useRef(null);
716
+ react.useEffect(() => {
717
+ if (!ref && state.entry?.target && !freezeOnceVisible && !frozen && prevRef.current !== state.entry.target) {
718
+ prevRef.current = state.entry.target;
719
+ setState({ isIntersecting: initialIsIntersecting, entry: void 0 });
720
+ }
721
+ }, [ref, state.entry, freezeOnceVisible, frozen, initialIsIntersecting]);
722
+ const result = [
723
+ setRef,
724
+ !!state.isIntersecting,
725
+ state.entry
726
+ ];
727
+ result.ref = result[0];
728
+ result.isIntersecting = result[1];
729
+ result.entry = result[2];
730
+ return result;
731
+ }
732
+
733
+ function useIsClient() {
734
+ const [isClient, setClient] = react.useState(false);
735
+ react.useEffect(() => {
736
+ setClient(true);
737
+ }, []);
738
+ return isClient;
739
+ }
740
+
741
+ function useIsMounted() {
742
+ const isMounted = react.useRef(false);
743
+ react.useEffect(() => {
744
+ isMounted.current = true;
745
+ return () => {
746
+ isMounted.current = false;
747
+ };
748
+ }, []);
749
+ return react.useCallback(() => isMounted.current, []);
750
+ }
751
+
752
+ function useMap(initialState = /* @__PURE__ */ new Map()) {
753
+ const [map, setMap] = react.useState(new Map(initialState));
754
+ const actions = {
755
+ set: react.useCallback((key, value) => {
756
+ setMap((prev) => {
757
+ const copy = new Map(prev);
758
+ copy.set(key, value);
759
+ return copy;
760
+ });
761
+ }, []),
762
+ setAll: react.useCallback((entries) => {
763
+ setMap(() => new Map(entries));
764
+ }, []),
765
+ remove: react.useCallback((key) => {
766
+ setMap((prev) => {
767
+ const copy = new Map(prev);
768
+ copy.delete(key);
769
+ return copy;
770
+ });
771
+ }, []),
772
+ reset: react.useCallback(() => {
773
+ setMap(() => /* @__PURE__ */ new Map());
774
+ }, [])
775
+ };
776
+ return [map, actions];
777
+ }
778
+
779
+ const IS_SERVER$4 = typeof window === "undefined";
780
+ function useReadLocalStorage(key, options = {}) {
781
+ let { initializeWithValue = true } = options;
782
+ if (IS_SERVER$4) {
783
+ initializeWithValue = false;
784
+ }
785
+ const deserializer = react.useCallback(
786
+ (value) => {
787
+ if (options.deserializer) {
788
+ return options.deserializer(value);
789
+ }
790
+ if (value === "undefined") {
791
+ return void 0;
792
+ }
793
+ let parsed;
794
+ try {
795
+ parsed = JSON.parse(value);
796
+ } catch (error) {
797
+ console.error("Error parsing JSON:", error);
798
+ return null;
799
+ }
800
+ return parsed;
801
+ },
802
+ [options]
803
+ );
804
+ const readValue = react.useCallback(() => {
805
+ if (IS_SERVER$4) {
806
+ return null;
807
+ }
808
+ try {
809
+ const raw = window.localStorage.getItem(key);
810
+ return raw ? deserializer(raw) : null;
811
+ } catch (error) {
812
+ console.warn(`Error reading localStorage key \u201C${key}\u201D:`, error);
813
+ return null;
814
+ }
815
+ }, [key, deserializer]);
816
+ const [storedValue, setStoredValue] = react.useState(() => {
817
+ if (initializeWithValue) {
818
+ return readValue();
819
+ }
820
+ return void 0;
821
+ });
822
+ react.useEffect(() => {
823
+ setStoredValue(readValue());
824
+ }, [key]);
825
+ const handleStorageChange = react.useCallback(
826
+ (event) => {
827
+ if (event.key && event.key !== key) {
828
+ return;
829
+ }
830
+ setStoredValue(readValue());
831
+ },
832
+ [key, readValue]
833
+ );
834
+ useEventListener("storage", handleStorageChange);
835
+ useEventListener("local-storage", handleStorageChange);
836
+ return storedValue;
837
+ }
838
+
839
+ const initialSize = {
840
+ width: void 0,
841
+ height: void 0
842
+ };
843
+ function useResizeObserver(options) {
844
+ const { ref, box = "content-box" } = options;
845
+ const [{ width, height }, setSize] = react.useState(initialSize);
846
+ const isMounted = useIsMounted();
847
+ const previousSize = react.useRef({ ...initialSize });
848
+ const onResize = react.useRef(void 0);
849
+ onResize.current = options.onResize;
850
+ react.useEffect(() => {
851
+ if (!ref.current) return;
852
+ if (typeof window === "undefined" || !("ResizeObserver" in window)) return;
853
+ const observer = new ResizeObserver(([entry]) => {
854
+ const boxProp = box === "border-box" ? "borderBoxSize" : box === "device-pixel-content-box" ? "devicePixelContentBoxSize" : "contentBoxSize";
855
+ const newWidth = extractSize(entry, boxProp, "inlineSize");
856
+ const newHeight = extractSize(entry, boxProp, "blockSize");
857
+ const hasChanged = previousSize.current.width !== newWidth || previousSize.current.height !== newHeight;
858
+ if (hasChanged) {
859
+ const newSize = { width: newWidth, height: newHeight };
860
+ previousSize.current.width = newWidth;
861
+ previousSize.current.height = newHeight;
862
+ if (onResize.current) {
863
+ onResize.current(newSize);
864
+ } else {
865
+ if (isMounted()) {
866
+ setSize(newSize);
867
+ }
868
+ }
869
+ }
870
+ });
871
+ observer.observe(ref.current, { box });
872
+ return () => {
873
+ observer.disconnect();
874
+ };
875
+ }, [box, ref, isMounted]);
876
+ return { width, height };
877
+ }
878
+ function extractSize(entry, box, sizeType) {
879
+ if (!entry[box]) {
880
+ if (box === "contentBoxSize") {
881
+ return entry.contentRect[sizeType === "inlineSize" ? "width" : "height"];
882
+ }
883
+ return void 0;
884
+ }
885
+ return Array.isArray(entry[box]) ? entry[box][0][sizeType] : (
886
+ // @ts-ignore Support Firefox's non-standard behavior
887
+ entry[box][sizeType]
888
+ );
889
+ }
890
+
891
+ const IS_SERVER$3 = typeof window === "undefined";
892
+ function useScreen(options = {}) {
893
+ let { initializeWithValue = true } = options;
894
+ if (IS_SERVER$3) {
895
+ initializeWithValue = false;
896
+ }
897
+ const readScreen = () => {
898
+ if (IS_SERVER$3) {
899
+ return void 0;
900
+ }
901
+ return window.screen;
902
+ };
903
+ const [screen, setScreen] = react.useState(() => {
904
+ if (initializeWithValue) {
905
+ return readScreen();
906
+ }
907
+ return void 0;
908
+ });
909
+ const debouncedSetScreen = useDebounceCallback(
910
+ setScreen,
911
+ options.debounceDelay
912
+ );
913
+ function handleSize() {
914
+ const newScreen = readScreen();
915
+ const setSize = options.debounceDelay ? debouncedSetScreen : setScreen;
916
+ if (newScreen) {
917
+ const {
918
+ width,
919
+ height,
920
+ availHeight,
921
+ availWidth,
922
+ colorDepth,
923
+ orientation,
924
+ pixelDepth
925
+ } = newScreen;
926
+ setSize({
927
+ width,
928
+ height,
929
+ availHeight,
930
+ availWidth,
931
+ colorDepth,
932
+ orientation,
933
+ pixelDepth
934
+ });
935
+ }
936
+ }
937
+ useEventListener("resize", handleSize);
938
+ useIsomorphicLayoutEffect(() => {
939
+ handleSize();
940
+ }, []);
941
+ return screen;
942
+ }
943
+
944
+ const cachedScriptStatuses = /* @__PURE__ */ new Map();
945
+ function getScriptNode(src) {
946
+ const node = document.querySelector(
947
+ `script[src="${src}"]`
948
+ );
949
+ const status = node?.getAttribute("data-status");
950
+ return {
951
+ node,
952
+ status
953
+ };
954
+ }
955
+ function useScript(src, options) {
956
+ const [status, setStatus] = react.useState(() => {
957
+ if (!src || options?.shouldPreventLoad) {
958
+ return "idle";
959
+ }
960
+ if (typeof window === "undefined") {
961
+ return "loading";
962
+ }
963
+ return cachedScriptStatuses.get(src) ?? "loading";
964
+ });
965
+ react.useEffect(() => {
966
+ if (!src || options?.shouldPreventLoad) {
967
+ return;
968
+ }
969
+ const cachedScriptStatus = cachedScriptStatuses.get(src);
970
+ if (cachedScriptStatus === "ready" || cachedScriptStatus === "error") {
971
+ setStatus(cachedScriptStatus);
972
+ return;
973
+ }
974
+ const script = getScriptNode(src);
975
+ let scriptNode = script.node;
976
+ if (!scriptNode) {
977
+ scriptNode = document.createElement("script");
978
+ scriptNode.src = src;
979
+ scriptNode.async = true;
980
+ if (options?.id) {
981
+ scriptNode.id = options.id;
982
+ }
983
+ scriptNode.setAttribute("data-status", "loading");
984
+ document.body.appendChild(scriptNode);
985
+ const setAttributeFromEvent = (event) => {
986
+ const scriptStatus = event.type === "load" ? "ready" : "error";
987
+ scriptNode?.setAttribute("data-status", scriptStatus);
988
+ };
989
+ scriptNode.addEventListener("load", setAttributeFromEvent);
990
+ scriptNode.addEventListener("error", setAttributeFromEvent);
991
+ } else {
992
+ setStatus(script.status ?? cachedScriptStatus ?? "loading");
993
+ }
994
+ const setStateFromEvent = (event) => {
995
+ const newStatus = event.type === "load" ? "ready" : "error";
996
+ setStatus(newStatus);
997
+ cachedScriptStatuses.set(src, newStatus);
998
+ };
999
+ scriptNode.addEventListener("load", setStateFromEvent);
1000
+ scriptNode.addEventListener("error", setStateFromEvent);
1001
+ return () => {
1002
+ if (scriptNode) {
1003
+ scriptNode.removeEventListener("load", setStateFromEvent);
1004
+ scriptNode.removeEventListener("error", setStateFromEvent);
1005
+ }
1006
+ if (scriptNode && options?.removeOnUnmount) {
1007
+ scriptNode.remove();
1008
+ cachedScriptStatuses.delete(src);
1009
+ }
1010
+ };
1011
+ }, [src, options?.shouldPreventLoad, options?.removeOnUnmount, options?.id]);
1012
+ return status;
1013
+ }
1014
+
1015
+ const IS_SERVER$2 = typeof window === "undefined";
1016
+ function useScrollLock(options = {}) {
1017
+ const { autoLock = true, lockTarget, widthReflow = true } = options;
1018
+ const [isLocked, setIsLocked] = react.useState(false);
1019
+ const target = react.useRef(null);
1020
+ const originalStyle = react.useRef(null);
1021
+ const lock = () => {
1022
+ if (target.current) {
1023
+ const { overflow, paddingRight } = target.current.style;
1024
+ originalStyle.current = { overflow, paddingRight };
1025
+ if (widthReflow) {
1026
+ const offsetWidth = target.current === document.body ? window.innerWidth : target.current.offsetWidth;
1027
+ const currentPaddingRight = parseInt(window.getComputedStyle(target.current).paddingRight, 10) || 0;
1028
+ const scrollbarWidth = offsetWidth - target.current.scrollWidth;
1029
+ target.current.style.paddingRight = `${scrollbarWidth + currentPaddingRight}px`;
1030
+ }
1031
+ target.current.style.overflow = "hidden";
1032
+ setIsLocked(true);
1033
+ }
1034
+ };
1035
+ const unlock = () => {
1036
+ if (target.current && originalStyle.current) {
1037
+ target.current.style.overflow = originalStyle.current.overflow;
1038
+ if (widthReflow) {
1039
+ target.current.style.paddingRight = originalStyle.current.paddingRight;
1040
+ }
1041
+ }
1042
+ setIsLocked(false);
1043
+ };
1044
+ useIsomorphicLayoutEffect(() => {
1045
+ if (IS_SERVER$2) return;
1046
+ if (lockTarget) {
1047
+ target.current = typeof lockTarget === "string" ? document.querySelector(lockTarget) : lockTarget;
1048
+ }
1049
+ if (!target.current) {
1050
+ target.current = document.body;
1051
+ }
1052
+ if (autoLock) {
1053
+ lock();
1054
+ }
1055
+ return () => {
1056
+ unlock();
1057
+ };
1058
+ }, [autoLock, lockTarget, widthReflow]);
1059
+ return { isLocked, lock, unlock };
1060
+ }
1061
+
1062
+ const IS_SERVER$1 = typeof window === "undefined";
1063
+ function useSessionStorage(key, initialValue, options = {}) {
1064
+ const { initializeWithValue = true } = options;
1065
+ const serializer = react.useCallback(
1066
+ (value) => {
1067
+ if (options.serializer) {
1068
+ return options.serializer(value);
1069
+ }
1070
+ return JSON.stringify(value);
1071
+ },
1072
+ [options]
1073
+ );
1074
+ const deserializer = react.useCallback(
1075
+ (value) => {
1076
+ if (options.deserializer) {
1077
+ return options.deserializer(value);
1078
+ }
1079
+ if (value === "undefined") {
1080
+ return void 0;
1081
+ }
1082
+ const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;
1083
+ let parsed;
1084
+ try {
1085
+ parsed = JSON.parse(value);
1086
+ } catch (error) {
1087
+ console.error("Error parsing JSON:", error);
1088
+ return defaultValue;
1089
+ }
1090
+ return parsed;
1091
+ },
1092
+ [options, initialValue]
1093
+ );
1094
+ const readValue = react.useCallback(() => {
1095
+ const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue;
1096
+ if (IS_SERVER$1) {
1097
+ return initialValueToUse;
1098
+ }
1099
+ try {
1100
+ const raw = window.sessionStorage.getItem(key);
1101
+ return raw ? deserializer(raw) : initialValueToUse;
1102
+ } catch (error) {
1103
+ console.warn(`Error reading sessionStorage key \u201C${key}\u201D:`, error);
1104
+ return initialValueToUse;
1105
+ }
1106
+ }, [initialValue, key, deserializer]);
1107
+ const [storedValue, setStoredValue] = react.useState(() => {
1108
+ if (initializeWithValue) {
1109
+ return readValue();
1110
+ }
1111
+ return initialValue instanceof Function ? initialValue() : initialValue;
1112
+ });
1113
+ const setValue = useEventCallback(
1114
+ (value) => {
1115
+ if (IS_SERVER$1) {
1116
+ console.warn(
1117
+ `Tried setting sessionStorage key \u201C${key}\u201D even though environment is not a client`
1118
+ );
1119
+ }
1120
+ try {
1121
+ const newValue = value instanceof Function ? value(readValue()) : value;
1122
+ window.sessionStorage.setItem(key, serializer(newValue));
1123
+ setStoredValue(newValue);
1124
+ window.dispatchEvent(new StorageEvent("session-storage", { key }));
1125
+ } catch (error) {
1126
+ console.warn(`Error setting sessionStorage key \u201C${key}\u201D:`, error);
1127
+ }
1128
+ }
1129
+ );
1130
+ const removeValue = useEventCallback(() => {
1131
+ if (IS_SERVER$1) {
1132
+ console.warn(
1133
+ `Tried removing sessionStorage key \u201C${key}\u201D even though environment is not a client`
1134
+ );
1135
+ }
1136
+ const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;
1137
+ window.sessionStorage.removeItem(key);
1138
+ setStoredValue(defaultValue);
1139
+ window.dispatchEvent(new StorageEvent("session-storage", { key }));
1140
+ });
1141
+ react.useEffect(() => {
1142
+ setStoredValue(readValue());
1143
+ }, [key]);
1144
+ const handleStorageChange = react.useCallback(
1145
+ (event) => {
1146
+ if (event.key && event.key !== key) {
1147
+ return;
1148
+ }
1149
+ setStoredValue(readValue());
1150
+ },
1151
+ [key, readValue]
1152
+ );
1153
+ useEventListener("storage", handleStorageChange);
1154
+ useEventListener("session-storage", handleStorageChange);
1155
+ return [storedValue, setValue, removeValue];
1156
+ }
1157
+
1158
+ function useStep(maxStep) {
1159
+ const [currentStep, setCurrentStep] = react.useState(1);
1160
+ const canGoToNextStep = currentStep + 1 <= maxStep;
1161
+ const canGoToPrevStep = currentStep - 1 > 0;
1162
+ const setStep = react.useCallback(
1163
+ (step) => {
1164
+ const newStep = step instanceof Function ? step(currentStep) : step;
1165
+ if (newStep >= 1 && newStep <= maxStep) {
1166
+ setCurrentStep(newStep);
1167
+ return;
1168
+ }
1169
+ throw new Error("Step not valid");
1170
+ },
1171
+ [maxStep, currentStep]
1172
+ );
1173
+ const goToNextStep = react.useCallback(() => {
1174
+ if (canGoToNextStep) {
1175
+ setCurrentStep((step) => step + 1);
1176
+ }
1177
+ }, [canGoToNextStep]);
1178
+ const goToPrevStep = react.useCallback(() => {
1179
+ if (canGoToPrevStep) {
1180
+ setCurrentStep((step) => step - 1);
1181
+ }
1182
+ }, [canGoToPrevStep]);
1183
+ const reset = react.useCallback(() => {
1184
+ setCurrentStep(1);
1185
+ }, []);
1186
+ return [
1187
+ currentStep,
1188
+ {
1189
+ goToNextStep,
1190
+ goToPrevStep,
1191
+ canGoToNextStep,
1192
+ canGoToPrevStep,
1193
+ setStep,
1194
+ reset
1195
+ }
1196
+ ];
1197
+ }
1198
+
1199
+ const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)";
1200
+ const LOCAL_STORAGE_KEY = "usehooks-ts-ternary-dark-mode";
1201
+ function useTernaryDarkMode({
1202
+ defaultValue = "system",
1203
+ localStorageKey = LOCAL_STORAGE_KEY,
1204
+ initializeWithValue = true
1205
+ } = {}) {
1206
+ const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY, { initializeWithValue });
1207
+ const [mode, setMode] = useLocalStorage(localStorageKey, defaultValue, {
1208
+ initializeWithValue
1209
+ });
1210
+ const isDarkMode = mode === "dark" || mode === "system" && isDarkOS;
1211
+ const toggleTernaryDarkMode = () => {
1212
+ const modes = ["light", "system", "dark"];
1213
+ setMode((prevMode) => {
1214
+ const nextIndex = (modes.indexOf(prevMode) + 1) % modes.length;
1215
+ return modes[nextIndex];
1216
+ });
1217
+ };
1218
+ return {
1219
+ isDarkMode,
1220
+ ternaryDarkMode: mode,
1221
+ setTernaryDarkMode: setMode,
1222
+ toggleTernaryDarkMode
1223
+ };
1224
+ }
1225
+
1226
+ function useTimeout(callback, delay) {
1227
+ const savedCallback = react.useRef(callback);
1228
+ useIsomorphicLayoutEffect(() => {
1229
+ savedCallback.current = callback;
1230
+ }, [callback]);
1231
+ react.useEffect(() => {
1232
+ if (!delay && delay !== 0) {
1233
+ return;
1234
+ }
1235
+ const id = setTimeout(() => {
1236
+ savedCallback.current();
1237
+ }, delay);
1238
+ return () => {
1239
+ clearTimeout(id);
1240
+ };
1241
+ }, [delay]);
1242
+ }
1243
+
1244
+ function useToggle(defaultValue) {
1245
+ const [value, setValue] = react.useState(!!defaultValue);
1246
+ const toggle = react.useCallback(() => {
1247
+ setValue((x) => !x);
1248
+ }, []);
1249
+ return [value, toggle, setValue];
1250
+ }
1251
+
1252
+ const IS_SERVER = typeof window === "undefined";
1253
+ function useWindowSize(options = {}) {
1254
+ let { initializeWithValue = true } = options;
1255
+ if (IS_SERVER) {
1256
+ initializeWithValue = false;
1257
+ }
1258
+ const [windowSize, setWindowSize] = react.useState(() => {
1259
+ if (initializeWithValue) {
1260
+ return {
1261
+ width: window.innerWidth,
1262
+ height: window.innerHeight
1263
+ };
1264
+ }
1265
+ return {
1266
+ width: void 0,
1267
+ height: void 0
1268
+ };
1269
+ });
1270
+ const debouncedSetWindowSize = useDebounceCallback(
1271
+ setWindowSize,
1272
+ options.debounceDelay
1273
+ );
1274
+ function handleSize() {
1275
+ const setSize = options.debounceDelay ? debouncedSetWindowSize : setWindowSize;
1276
+ setSize({
1277
+ width: window.innerWidth,
1278
+ height: window.innerHeight
1279
+ });
1280
+ }
1281
+ useEventListener("resize", handleSize);
1282
+ useIsomorphicLayoutEffect(() => {
1283
+ handleSize();
1284
+ }, []);
1285
+ return windowSize;
1286
+ }
1287
+
1288
+ exports.useBoolean = useBoolean;
1289
+ exports.useCopyToClipboard = useCopyToClipboard;
1290
+ exports.useCountdown = useCountdown;
1291
+ exports.useCounter = useCounter;
1292
+ exports.useDarkMode = useDarkMode;
1293
+ exports.useDebounceCallback = useDebounceCallback;
1294
+ exports.useDebounceValue = useDebounceValue;
1295
+ exports.useDelete = useDelete;
1296
+ exports.useDocumentTitle = useDocumentTitle;
1297
+ exports.useEventCallback = useEventCallback;
1298
+ exports.useEventListener = useEventListener;
1299
+ exports.useFetch = useFetch;
1300
+ exports.useGet = useGet;
1301
+ exports.useHover = useHover;
1302
+ exports.useIndexedDB = useIndexedDB;
1303
+ exports.useIntersectionObserver = useIntersectionObserver;
1304
+ exports.useInterval = useInterval;
1305
+ exports.useIsClient = useIsClient;
1306
+ exports.useIsMounted = useIsMounted;
1307
+ exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
1308
+ exports.useLocalStorage = useLocalStorage;
1309
+ exports.useMap = useMap;
1310
+ exports.useMediaQuery = useMediaQuery;
1311
+ exports.usePost = usePost;
1312
+ exports.usePut = usePut;
1313
+ exports.useReadLocalStorage = useReadLocalStorage;
1314
+ exports.useResizeObserver = useResizeObserver;
1315
+ exports.useScreen = useScreen;
1316
+ exports.useScript = useScript;
1317
+ exports.useScrollLock = useScrollLock;
1318
+ exports.useSessionStorage = useSessionStorage;
1319
+ exports.useStep = useStep;
1320
+ exports.useTernaryDarkMode = useTernaryDarkMode;
1321
+ exports.useTimeout = useTimeout;
1322
+ exports.useToggle = useToggle;
1323
+ exports.useUnmount = useUnmount;
1324
+ exports.useWindowSize = useWindowSize;