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 +1324 -0
- package/dist/index.d.cts +327 -0
- package/dist/index.d.mts +327 -0
- package/dist/index.d.ts +327 -0
- package/dist/index.mjs +1282 -0
- package/package.json +42 -0
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;
|