@ts-hooks-kit/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +146 -0
- package/dist/index.cjs +2574 -0
- package/dist/index.d.cts +1688 -0
- package/dist/index.d.ts +1688 -0
- package/dist/index.js +2499 -0
- package/package.json +78 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2574 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//#region rolldown:runtime
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
const react = __toESM(require("react"));
|
|
26
|
+
|
|
27
|
+
//#region src/useAsync/useAsync.ts
|
|
28
|
+
/**
|
|
29
|
+
* Custom hook that manages the state of an async function.
|
|
30
|
+
* @template T - The type of the resolved value.
|
|
31
|
+
* @param {() => Promise<T>} asyncFn - The async function to execute.
|
|
32
|
+
* @param {unknown[]} [deps] - Dependencies that trigger re-execution when changed.
|
|
33
|
+
* @returns {UseAsyncState<T>} The state of the async operation.
|
|
34
|
+
* @public
|
|
35
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-async)
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* const { value, error, loading, retry } = useAsync(async () => {
|
|
39
|
+
* const response = await fetch('/api/data');
|
|
40
|
+
* return response.json();
|
|
41
|
+
* }, []);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
function useAsync(asyncFn, deps = []) {
|
|
45
|
+
const [state, setState] = (0, react.useState)({
|
|
46
|
+
loading: true,
|
|
47
|
+
value: void 0,
|
|
48
|
+
error: void 0
|
|
49
|
+
});
|
|
50
|
+
const asyncFnRef = (0, react.useRef)(asyncFn);
|
|
51
|
+
const isMountedRef = (0, react.useRef)(true);
|
|
52
|
+
(0, react.useEffect)(() => {
|
|
53
|
+
asyncFnRef.current = asyncFn;
|
|
54
|
+
}, [asyncFn]);
|
|
55
|
+
const execute = (0, react.useCallback)(async () => {
|
|
56
|
+
setState((prev) => ({
|
|
57
|
+
...prev,
|
|
58
|
+
loading: true,
|
|
59
|
+
error: void 0
|
|
60
|
+
}));
|
|
61
|
+
try {
|
|
62
|
+
const result = await asyncFnRef.current();
|
|
63
|
+
if (isMountedRef.current) setState({
|
|
64
|
+
loading: false,
|
|
65
|
+
value: result,
|
|
66
|
+
error: void 0
|
|
67
|
+
});
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (isMountedRef.current) setState({
|
|
70
|
+
loading: false,
|
|
71
|
+
value: void 0,
|
|
72
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}, deps);
|
|
76
|
+
const retry = (0, react.useCallback)(() => {
|
|
77
|
+
execute();
|
|
78
|
+
}, [execute]);
|
|
79
|
+
(0, react.useEffect)(() => {
|
|
80
|
+
isMountedRef.current = true;
|
|
81
|
+
execute();
|
|
82
|
+
return () => {
|
|
83
|
+
isMountedRef.current = false;
|
|
84
|
+
};
|
|
85
|
+
}, [execute]);
|
|
86
|
+
return {
|
|
87
|
+
loading: state.loading,
|
|
88
|
+
value: state.value,
|
|
89
|
+
error: state.error,
|
|
90
|
+
retry
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/useBoolean/useBoolean.ts
|
|
96
|
+
/**
|
|
97
|
+
* Custom hook that handles boolean state with useful utility functions.
|
|
98
|
+
* @param {boolean} [defaultValue] - The initial value for the boolean state (default is `false`).
|
|
99
|
+
* @returns {UseBooleanReturn} An object containing the boolean state value and utility functions to manipulate the state.
|
|
100
|
+
* @throws Will throw an error if `defaultValue` is an invalid boolean value.
|
|
101
|
+
* @public
|
|
102
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-boolean)
|
|
103
|
+
* @example
|
|
104
|
+
* ```tsx
|
|
105
|
+
* const { value, setTrue, setFalse, toggle } = useBoolean(true);
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
function useBoolean(defaultValue = false) {
|
|
109
|
+
if (typeof defaultValue !== "boolean") throw new Error("defaultValue must be `true` or `false`");
|
|
110
|
+
const [value, setValue] = (0, react.useState)(defaultValue);
|
|
111
|
+
const setTrue = (0, react.useCallback)(() => {
|
|
112
|
+
setValue(true);
|
|
113
|
+
}, []);
|
|
114
|
+
const setFalse = (0, react.useCallback)(() => {
|
|
115
|
+
setValue(false);
|
|
116
|
+
}, []);
|
|
117
|
+
const toggle = (0, react.useCallback)(() => {
|
|
118
|
+
setValue((x) => !x);
|
|
119
|
+
}, []);
|
|
120
|
+
return {
|
|
121
|
+
value,
|
|
122
|
+
setValue,
|
|
123
|
+
setTrue,
|
|
124
|
+
setFalse,
|
|
125
|
+
toggle
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts
|
|
131
|
+
/**
|
|
132
|
+
* Custom hook that uses either `useLayoutEffect` or `useEffect` based on the environment (client-side or server-side).
|
|
133
|
+
* @param {Function} effect - The effect function to be executed.
|
|
134
|
+
* @param {Array<any>} [dependencies] - An array of dependencies for the effect (optional).
|
|
135
|
+
* @public
|
|
136
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-isomorphic-layout-effect)
|
|
137
|
+
* @example
|
|
138
|
+
* ```tsx
|
|
139
|
+
* useIsomorphicLayoutEffect(() => {
|
|
140
|
+
* // Code to be executed during the layout phase on the client side
|
|
141
|
+
* }, [dependency1, dependency2]);
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/useEventListener/useEventListener.ts
|
|
148
|
+
/**
|
|
149
|
+
* Custom hook that attaches event listeners to DOM elements, the window, or media query lists.
|
|
150
|
+
* @template KW - The type of event for window events.
|
|
151
|
+
* @template KH - The type of event for HTML or SVG element events.
|
|
152
|
+
* @template KM - The type of event for media query list events.
|
|
153
|
+
* @template T - The type of the DOM element (default is `HTMLElement`).
|
|
154
|
+
* @param {KW | KH | KM} eventName - The name of the event to listen for.
|
|
155
|
+
* @param {(event: WindowEventMap[KW] | HTMLElementEventMap[KH] | SVGElementEventMap[KH] | MediaQueryListEventMap[KM] | Event) => void} handler - The event handler function.
|
|
156
|
+
* @param {RefObject<T>} [element] - The DOM element or media query list to attach the event listener to (optional).
|
|
157
|
+
* @param {boolean | AddEventListenerOptions} [options] - An options object that specifies characteristics about the event listener (optional).
|
|
158
|
+
* @public
|
|
159
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-event-listener)
|
|
160
|
+
* @example
|
|
161
|
+
* ```tsx
|
|
162
|
+
* // Example 1: Attach a window event listener
|
|
163
|
+
* useEventListener('resize', handleResize);
|
|
164
|
+
* ```
|
|
165
|
+
* @example
|
|
166
|
+
* ```tsx
|
|
167
|
+
* // Example 2: Attach a document event listener with options
|
|
168
|
+
* const elementRef = useRef(document);
|
|
169
|
+
* useEventListener('click', handleClick, elementRef, { capture: true });
|
|
170
|
+
* ```
|
|
171
|
+
* @example
|
|
172
|
+
* ```tsx
|
|
173
|
+
* // Example 3: Attach an element event listener
|
|
174
|
+
* const buttonRef = useRef<HTMLButtonElement>(null);
|
|
175
|
+
* useEventListener('click', handleButtonClick, buttonRef);
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
function useEventListener(eventName, handler, element, options) {
|
|
179
|
+
const savedHandler = (0, react.useRef)(handler);
|
|
180
|
+
useIsomorphicLayoutEffect(() => {
|
|
181
|
+
savedHandler.current = handler;
|
|
182
|
+
}, [handler]);
|
|
183
|
+
(0, react.useEffect)(() => {
|
|
184
|
+
const targetElement = element?.current ?? window;
|
|
185
|
+
if (!(targetElement && targetElement.addEventListener)) return;
|
|
186
|
+
const listener = (event) => {
|
|
187
|
+
savedHandler.current(event);
|
|
188
|
+
};
|
|
189
|
+
targetElement.addEventListener(eventName, listener, options);
|
|
190
|
+
return () => {
|
|
191
|
+
targetElement.removeEventListener(eventName, listener, options);
|
|
192
|
+
};
|
|
193
|
+
}, [
|
|
194
|
+
eventName,
|
|
195
|
+
element,
|
|
196
|
+
options
|
|
197
|
+
]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/useClickAnyWhere/useClickAnyWhere.ts
|
|
202
|
+
/**
|
|
203
|
+
* Custom hook that handles click events anywhere on the document.
|
|
204
|
+
* @param {Function} handler - The function to be called when a click event is detected anywhere on the document.
|
|
205
|
+
* @public
|
|
206
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-click-any-where)
|
|
207
|
+
* @example
|
|
208
|
+
* ```tsx
|
|
209
|
+
* const handleClick = (event) => {
|
|
210
|
+
* console.log('Document clicked!', event);
|
|
211
|
+
* };
|
|
212
|
+
*
|
|
213
|
+
* // Attach click event handler to document
|
|
214
|
+
* useClickAnywhere(handleClick);
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
function useClickAnyWhere(handler) {
|
|
218
|
+
useEventListener("click", (event) => {
|
|
219
|
+
handler(event);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/useCopyToClipboard/useCopyToClipboard.ts
|
|
225
|
+
/**
|
|
226
|
+
* Custom hook that copies text to the clipboard using the [`Clipboard API`](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).
|
|
227
|
+
* @returns {[CopiedValue, CopyFn]} An tuple containing the copied text and a function to copy text to the clipboard.
|
|
228
|
+
* @public
|
|
229
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-copy-to-clipboard)
|
|
230
|
+
* @example
|
|
231
|
+
* ```tsx
|
|
232
|
+
* const [copiedText, copyToClipboard] = useCopyToClipboard();
|
|
233
|
+
* const textToCopy = 'Hello, world!';
|
|
234
|
+
*
|
|
235
|
+
* // Attempt to copy text to the clipboard
|
|
236
|
+
* copyToClipboard(textToCopy)
|
|
237
|
+
* .then(success => {
|
|
238
|
+
* if (success) {
|
|
239
|
+
* console.log(`Text "${textToCopy}" copied to clipboard successfully.`);
|
|
240
|
+
* } else {
|
|
241
|
+
* console.error('Failed to copy text to clipboard.');
|
|
242
|
+
* }
|
|
243
|
+
* });
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
function useCopyToClipboard() {
|
|
247
|
+
const [copiedText, setCopiedText] = (0, react.useState)(null);
|
|
248
|
+
const copy = (0, react.useCallback)(async (text) => {
|
|
249
|
+
if (!navigator?.clipboard) {
|
|
250
|
+
console.warn("Clipboard not supported");
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
await navigator.clipboard.writeText(text);
|
|
255
|
+
setCopiedText(text);
|
|
256
|
+
return true;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.warn("Copy failed", error);
|
|
259
|
+
setCopiedText(null);
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}, []);
|
|
263
|
+
return [copiedText, copy];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/useCounter/useCounter.ts
|
|
268
|
+
/**
|
|
269
|
+
* Custom hook that manages a counter with increment, decrement, reset, and setCount functionalities.
|
|
270
|
+
* @param {number} [initialValue] - The initial value for the counter.
|
|
271
|
+
* @returns {UseCounterReturn} An object containing the current count and functions to interact with the counter.
|
|
272
|
+
* @public
|
|
273
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-counter)
|
|
274
|
+
* @example
|
|
275
|
+
* ```tsx
|
|
276
|
+
* const { count, increment, decrement, reset, setCount } = useCounter(5);
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
function useCounter(initialValue) {
|
|
280
|
+
const [count, setCount] = (0, react.useState)(initialValue ?? 0);
|
|
281
|
+
const increment = (0, react.useCallback)(() => {
|
|
282
|
+
setCount((x) => x + 1);
|
|
283
|
+
}, []);
|
|
284
|
+
const decrement = (0, react.useCallback)(() => {
|
|
285
|
+
setCount((x) => x - 1);
|
|
286
|
+
}, []);
|
|
287
|
+
const reset = (0, react.useCallback)(() => {
|
|
288
|
+
setCount(initialValue ?? 0);
|
|
289
|
+
}, [initialValue]);
|
|
290
|
+
return {
|
|
291
|
+
count,
|
|
292
|
+
increment,
|
|
293
|
+
decrement,
|
|
294
|
+
reset,
|
|
295
|
+
setCount
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/useInterval/useInterval.ts
|
|
301
|
+
/**
|
|
302
|
+
* Custom hook that creates an interval that invokes a callback function at a specified delay using the [`setInterval API`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval).
|
|
303
|
+
* @param {() => void} callback - The function to be invoked at each interval.
|
|
304
|
+
* @param {number | null} delay - The time, in milliseconds, between each invocation of the callback. Use `null` to clear the interval.
|
|
305
|
+
* @public
|
|
306
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-interval)
|
|
307
|
+
* @example
|
|
308
|
+
* ```tsx
|
|
309
|
+
* const handleInterval = () => {
|
|
310
|
+
* // Code to be executed at each interval
|
|
311
|
+
* };
|
|
312
|
+
* useInterval(handleInterval, 1000);
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
function useInterval(callback, delay) {
|
|
316
|
+
const savedCallback = (0, react.useRef)(callback);
|
|
317
|
+
useIsomorphicLayoutEffect(() => {
|
|
318
|
+
savedCallback.current = callback;
|
|
319
|
+
}, [callback]);
|
|
320
|
+
(0, react.useEffect)(() => {
|
|
321
|
+
if (delay === null) return;
|
|
322
|
+
const id = setInterval(() => {
|
|
323
|
+
savedCallback.current();
|
|
324
|
+
}, delay);
|
|
325
|
+
return () => {
|
|
326
|
+
clearInterval(id);
|
|
327
|
+
};
|
|
328
|
+
}, [delay]);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/useCountdown/useCountdown.ts
|
|
333
|
+
/**
|
|
334
|
+
* Custom hook that manages countdown.
|
|
335
|
+
* @param {CountdownOptions} countdownOptions - The countdown's options.
|
|
336
|
+
* @returns {[number, CountdownControllers]} An array containing the countdown's count and its controllers.
|
|
337
|
+
* @public
|
|
338
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-countdown)
|
|
339
|
+
* @example
|
|
340
|
+
* ```tsx
|
|
341
|
+
* const [counter, { start, stop, reset }] = useCountdown({
|
|
342
|
+
* countStart: 10,
|
|
343
|
+
* intervalMs: 1000,
|
|
344
|
+
* isIncrement: false,
|
|
345
|
+
* });
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
function useCountdown({ countStart, countStop = 0, intervalMs = 1e3, isIncrement = false }) {
|
|
349
|
+
const { count, increment, decrement, reset: resetCounter } = useCounter(countStart);
|
|
350
|
+
const { value: isCountdownRunning, setTrue: startCountdown, setFalse: stopCountdown } = useBoolean(false);
|
|
351
|
+
const resetCountdown = (0, react.useCallback)(() => {
|
|
352
|
+
stopCountdown();
|
|
353
|
+
resetCounter();
|
|
354
|
+
}, [stopCountdown, resetCounter]);
|
|
355
|
+
const countdownCallback = (0, react.useCallback)(() => {
|
|
356
|
+
if (count === countStop) {
|
|
357
|
+
stopCountdown();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (isIncrement) increment();
|
|
361
|
+
else decrement();
|
|
362
|
+
}, [
|
|
363
|
+
count,
|
|
364
|
+
countStop,
|
|
365
|
+
decrement,
|
|
366
|
+
increment,
|
|
367
|
+
isIncrement,
|
|
368
|
+
stopCountdown
|
|
369
|
+
]);
|
|
370
|
+
useInterval(countdownCallback, isCountdownRunning ? intervalMs : null);
|
|
371
|
+
return [count, {
|
|
372
|
+
startCountdown,
|
|
373
|
+
stopCountdown,
|
|
374
|
+
resetCountdown
|
|
375
|
+
}];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/useEventCallback/useEventCallback.ts
|
|
380
|
+
function useEventCallback(fn) {
|
|
381
|
+
const ref = (0, react.useRef)(() => {
|
|
382
|
+
throw new Error("Cannot call an event handler while rendering.");
|
|
383
|
+
});
|
|
384
|
+
useIsomorphicLayoutEffect(() => {
|
|
385
|
+
ref.current = fn;
|
|
386
|
+
}, [fn]);
|
|
387
|
+
return (0, react.useCallback)((...args) => ref.current?.(...args), [ref]);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/useLocalStorage/useLocalStorage.ts
|
|
392
|
+
const IS_SERVER$6 = typeof window === "undefined";
|
|
393
|
+
/**
|
|
394
|
+
* Custom hook that uses the [`localStorage API`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to persist state across page reloads.
|
|
395
|
+
* @template T - The type of the state to be stored in local storage.
|
|
396
|
+
* @param {string} key - The key under which the value will be stored in local storage.
|
|
397
|
+
* @param {T | (() => T)} initialValue - The initial value of the state or a function that returns the initial value.
|
|
398
|
+
* @param {UseLocalStorageOptions<T>} [options] - Options for customizing the behavior of serialization and deserialization (optional).
|
|
399
|
+
* @returns {[T, Dispatch<SetStateAction<T>>, () => void]} A tuple containing the stored value, a function to set the value and a function to remove the key from storage.
|
|
400
|
+
* @public
|
|
401
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-local-storage)
|
|
402
|
+
* @example
|
|
403
|
+
* ```tsx
|
|
404
|
+
* const [count, setCount, removeCount] = useLocalStorage('count', 0);
|
|
405
|
+
* // Access the `count` value, the `setCount` function to update it and `removeCount` function to remove the key from storage.
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
function useLocalStorage(key, initialValue, options = {}) {
|
|
409
|
+
const { initializeWithValue = true } = options;
|
|
410
|
+
const serializer = (0, react.useCallback)((value) => {
|
|
411
|
+
if (options.serializer) return options.serializer(value);
|
|
412
|
+
return JSON.stringify(value);
|
|
413
|
+
}, [options]);
|
|
414
|
+
const deserializer = (0, react.useCallback)((value) => {
|
|
415
|
+
if (options.deserializer) return options.deserializer(value);
|
|
416
|
+
if (value === "undefined") return void 0;
|
|
417
|
+
const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;
|
|
418
|
+
let parsed;
|
|
419
|
+
try {
|
|
420
|
+
parsed = JSON.parse(value);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.error("Error parsing JSON:", error);
|
|
423
|
+
return defaultValue;
|
|
424
|
+
}
|
|
425
|
+
return parsed;
|
|
426
|
+
}, [options, initialValue]);
|
|
427
|
+
const readValue = (0, react.useCallback)(() => {
|
|
428
|
+
const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue;
|
|
429
|
+
if (IS_SERVER$6) return initialValueToUse;
|
|
430
|
+
try {
|
|
431
|
+
const raw = window.localStorage.getItem(key);
|
|
432
|
+
return raw ? deserializer(raw) : initialValueToUse;
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.warn(`Error reading localStorage key “${key}”:`, error);
|
|
435
|
+
return initialValueToUse;
|
|
436
|
+
}
|
|
437
|
+
}, [
|
|
438
|
+
initialValue,
|
|
439
|
+
key,
|
|
440
|
+
deserializer
|
|
441
|
+
]);
|
|
442
|
+
const [storedValue, setStoredValue] = (0, react.useState)(() => {
|
|
443
|
+
if (initializeWithValue) return readValue();
|
|
444
|
+
return initialValue instanceof Function ? initialValue() : initialValue;
|
|
445
|
+
});
|
|
446
|
+
const setValue = useEventCallback((value) => {
|
|
447
|
+
if (IS_SERVER$6) console.warn(`Tried setting localStorage key “${key}” even though environment is not a client`);
|
|
448
|
+
try {
|
|
449
|
+
const newValue = value instanceof Function ? value(readValue()) : value;
|
|
450
|
+
window.localStorage.setItem(key, serializer(newValue));
|
|
451
|
+
setStoredValue(newValue);
|
|
452
|
+
window.dispatchEvent(new StorageEvent("local-storage", { key }));
|
|
453
|
+
} catch (error) {
|
|
454
|
+
console.warn(`Error setting localStorage key “${key}”:`, error);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
const removeValue = useEventCallback(() => {
|
|
458
|
+
if (IS_SERVER$6) console.warn(`Tried removing localStorage key “${key}” even though environment is not a client`);
|
|
459
|
+
const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;
|
|
460
|
+
window.localStorage.removeItem(key);
|
|
461
|
+
setStoredValue(defaultValue);
|
|
462
|
+
window.dispatchEvent(new StorageEvent("local-storage", { key }));
|
|
463
|
+
});
|
|
464
|
+
(0, react.useEffect)(() => {
|
|
465
|
+
setStoredValue(readValue());
|
|
466
|
+
}, [key]);
|
|
467
|
+
const handleStorageChange = (0, react.useCallback)((event) => {
|
|
468
|
+
if (event.key && event.key !== key) return;
|
|
469
|
+
setStoredValue(readValue());
|
|
470
|
+
}, [key, readValue]);
|
|
471
|
+
useEventListener("storage", handleStorageChange);
|
|
472
|
+
useEventListener("local-storage", handleStorageChange);
|
|
473
|
+
return [
|
|
474
|
+
storedValue,
|
|
475
|
+
setValue,
|
|
476
|
+
removeValue
|
|
477
|
+
];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/useMediaQuery/useMediaQuery.ts
|
|
482
|
+
const IS_SERVER$5 = typeof window === "undefined";
|
|
483
|
+
/**
|
|
484
|
+
* Custom hook that tracks the state of a media query using the [`Match Media API`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia).
|
|
485
|
+
* @param {string} query - The media query to track.
|
|
486
|
+
* @param {?UseMediaQueryOptions} [options] - The options for customizing the behavior of the hook (optional).
|
|
487
|
+
* @returns {boolean} The current state of the media query (true if the query matches, false otherwise).
|
|
488
|
+
* @public
|
|
489
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-media-query)
|
|
490
|
+
* @example
|
|
491
|
+
* ```tsx
|
|
492
|
+
* const isSmallScreen = useMediaQuery('(max-width: 600px)');
|
|
493
|
+
* // Use `isSmallScreen` to conditionally apply styles or logic based on the screen size.
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
function useMediaQuery(query, { defaultValue = false, initializeWithValue = true } = {}) {
|
|
497
|
+
const getMatches = (query$1) => {
|
|
498
|
+
if (IS_SERVER$5) return defaultValue;
|
|
499
|
+
return window.matchMedia(query$1).matches;
|
|
500
|
+
};
|
|
501
|
+
const [matches, setMatches] = (0, react.useState)(() => {
|
|
502
|
+
if (initializeWithValue) return getMatches(query);
|
|
503
|
+
return defaultValue;
|
|
504
|
+
});
|
|
505
|
+
function handleChange() {
|
|
506
|
+
setMatches(getMatches(query));
|
|
507
|
+
}
|
|
508
|
+
useIsomorphicLayoutEffect(() => {
|
|
509
|
+
const matchMedia = window.matchMedia(query);
|
|
510
|
+
handleChange();
|
|
511
|
+
if (matchMedia.addListener) matchMedia.addListener(handleChange);
|
|
512
|
+
else matchMedia.addEventListener("change", handleChange);
|
|
513
|
+
return () => {
|
|
514
|
+
if (matchMedia.removeListener) matchMedia.removeListener(handleChange);
|
|
515
|
+
else matchMedia.removeEventListener("change", handleChange);
|
|
516
|
+
};
|
|
517
|
+
}, [query]);
|
|
518
|
+
return matches;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/useDarkMode/useDarkMode.ts
|
|
523
|
+
const COLOR_SCHEME_QUERY$1 = "(prefers-color-scheme: dark)";
|
|
524
|
+
const LOCAL_STORAGE_KEY$1 = "usehooks-ts-dark-mode";
|
|
525
|
+
/**
|
|
526
|
+
* Custom hook that returns the current state of the dark mode.
|
|
527
|
+
* @param {?DarkModeOptions} [options] - The initial value of the dark mode, default `false`.
|
|
528
|
+
* @returns {DarkModeReturn} An object containing the dark mode's state and its controllers.
|
|
529
|
+
* @public
|
|
530
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-dark-mode)
|
|
531
|
+
* @example
|
|
532
|
+
* ```tsx
|
|
533
|
+
* const { isDarkMode, toggle, enable, disable, set } = useDarkMode({ defaultValue: true });
|
|
534
|
+
* ```
|
|
535
|
+
*/
|
|
536
|
+
function useDarkMode(options = {}) {
|
|
537
|
+
const { defaultValue, localStorageKey = LOCAL_STORAGE_KEY$1, initializeWithValue = true } = options;
|
|
538
|
+
const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY$1, {
|
|
539
|
+
initializeWithValue,
|
|
540
|
+
defaultValue
|
|
541
|
+
});
|
|
542
|
+
const [isDarkMode, setDarkMode] = useLocalStorage(localStorageKey, defaultValue ?? isDarkOS ?? false, { initializeWithValue });
|
|
543
|
+
useIsomorphicLayoutEffect(() => {
|
|
544
|
+
if (isDarkOS !== isDarkMode) setDarkMode(isDarkOS);
|
|
545
|
+
}, [isDarkOS]);
|
|
546
|
+
return {
|
|
547
|
+
isDarkMode,
|
|
548
|
+
toggle: () => {
|
|
549
|
+
setDarkMode((prev) => !prev);
|
|
550
|
+
},
|
|
551
|
+
enable: () => {
|
|
552
|
+
setDarkMode(true);
|
|
553
|
+
},
|
|
554
|
+
disable: () => {
|
|
555
|
+
setDarkMode(false);
|
|
556
|
+
},
|
|
557
|
+
set: (value) => {
|
|
558
|
+
setDarkMode(value);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/utils/debounce.ts
|
|
565
|
+
/**
|
|
566
|
+
* Creates a debounced version of a function.
|
|
567
|
+
* @param func The function to debounce
|
|
568
|
+
* @param delay The delay in milliseconds
|
|
569
|
+
* @param options Debounce options
|
|
570
|
+
* @returns Debounced function with cancel and flush methods
|
|
571
|
+
*/
|
|
572
|
+
function debounce(func, delay, options = {}) {
|
|
573
|
+
const { leading = false, trailing = true, maxWait } = options;
|
|
574
|
+
let timeoutId = null;
|
|
575
|
+
let maxTimeoutId = null;
|
|
576
|
+
let lastArgs = null;
|
|
577
|
+
let lastResult;
|
|
578
|
+
let lastCallTime = null;
|
|
579
|
+
const clearTimeouts = () => {
|
|
580
|
+
if (timeoutId) {
|
|
581
|
+
clearTimeout(timeoutId);
|
|
582
|
+
timeoutId = null;
|
|
583
|
+
}
|
|
584
|
+
if (maxTimeoutId) {
|
|
585
|
+
clearTimeout(maxTimeoutId);
|
|
586
|
+
maxTimeoutId = null;
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
const invokeFunc = (args) => {
|
|
590
|
+
lastArgs = null;
|
|
591
|
+
lastCallTime = null;
|
|
592
|
+
lastResult = func(...args);
|
|
593
|
+
return lastResult;
|
|
594
|
+
};
|
|
595
|
+
const remainingWait = (time) => {
|
|
596
|
+
const timeSinceLastCall = time - (lastCallTime ?? 0);
|
|
597
|
+
return delay - timeSinceLastCall;
|
|
598
|
+
};
|
|
599
|
+
const shouldInvoke = (time) => {
|
|
600
|
+
const timeSinceLastCall = time - (lastCallTime ?? 0);
|
|
601
|
+
return lastCallTime === null || timeSinceLastCall >= delay || timeSinceLastCall < 0;
|
|
602
|
+
};
|
|
603
|
+
const trailingEdge = () => {
|
|
604
|
+
timeoutId = null;
|
|
605
|
+
if (trailing && lastArgs) return invokeFunc(lastArgs);
|
|
606
|
+
lastArgs = null;
|
|
607
|
+
lastCallTime = null;
|
|
608
|
+
return void 0;
|
|
609
|
+
};
|
|
610
|
+
const timerExpired = () => {
|
|
611
|
+
const time = Date.now();
|
|
612
|
+
if (shouldInvoke(time)) return trailingEdge();
|
|
613
|
+
const timeWaiting = remainingWait(time);
|
|
614
|
+
timeoutId = setTimeout(timerExpired, timeWaiting);
|
|
615
|
+
return void 0;
|
|
616
|
+
};
|
|
617
|
+
const debounced = (...args) => {
|
|
618
|
+
const time = Date.now();
|
|
619
|
+
const isInvoking = shouldInvoke(time);
|
|
620
|
+
lastArgs = args;
|
|
621
|
+
if (isInvoking) {
|
|
622
|
+
clearTimeouts();
|
|
623
|
+
if (lastCallTime === null) {
|
|
624
|
+
lastCallTime = time;
|
|
625
|
+
timeoutId = setTimeout(timerExpired, delay);
|
|
626
|
+
if (leading) return invokeFunc(args);
|
|
627
|
+
return void 0;
|
|
628
|
+
}
|
|
629
|
+
lastCallTime = time;
|
|
630
|
+
return lastResult;
|
|
631
|
+
}
|
|
632
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
633
|
+
if (maxWait && !maxTimeoutId && lastCallTime !== null) {
|
|
634
|
+
const timeSinceLastCall = time - lastCallTime;
|
|
635
|
+
const timeUntilMaxWait = maxWait - timeSinceLastCall;
|
|
636
|
+
maxTimeoutId = setTimeout(() => {
|
|
637
|
+
maxTimeoutId = null;
|
|
638
|
+
if (lastArgs) invokeFunc(lastArgs);
|
|
639
|
+
}, timeUntilMaxWait);
|
|
640
|
+
}
|
|
641
|
+
const timeWaiting = remainingWait(time);
|
|
642
|
+
timeoutId = setTimeout(timerExpired, timeWaiting);
|
|
643
|
+
return lastResult;
|
|
644
|
+
};
|
|
645
|
+
debounced.cancel = () => {
|
|
646
|
+
clearTimeouts();
|
|
647
|
+
lastArgs = null;
|
|
648
|
+
lastCallTime = null;
|
|
649
|
+
};
|
|
650
|
+
debounced.flush = () => {
|
|
651
|
+
if (timeoutId || maxTimeoutId) {
|
|
652
|
+
clearTimeouts();
|
|
653
|
+
if (lastArgs) return invokeFunc(lastArgs);
|
|
654
|
+
}
|
|
655
|
+
return lastResult;
|
|
656
|
+
};
|
|
657
|
+
return debounced;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region src/useUnmount/useUnmount.ts
|
|
662
|
+
/**
|
|
663
|
+
* Custom hook that runs a cleanup function when the component is unmounted.
|
|
664
|
+
* @param {() => void} func - The cleanup function to be executed on unmount.
|
|
665
|
+
* @public
|
|
666
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-unmount)
|
|
667
|
+
* @example
|
|
668
|
+
* ```tsx
|
|
669
|
+
* useUnmount(() => {
|
|
670
|
+
* // Cleanup logic here
|
|
671
|
+
* });
|
|
672
|
+
* ```
|
|
673
|
+
*/
|
|
674
|
+
function useUnmount(func) {
|
|
675
|
+
const funcRef = (0, react.useRef)(func);
|
|
676
|
+
funcRef.current = func;
|
|
677
|
+
(0, react.useEffect)(() => () => {
|
|
678
|
+
funcRef.current();
|
|
679
|
+
}, []);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/useDebounceCallback/useDebounceCallback.ts
|
|
684
|
+
/**
|
|
685
|
+
* Custom hook that creates a debounced version of a callback function.
|
|
686
|
+
* @template T - Type of the original callback function.
|
|
687
|
+
* @param {T} func - The callback function to be debounced.
|
|
688
|
+
* @param {number} delay - The delay in milliseconds before the callback is invoked (default is `500` milliseconds).
|
|
689
|
+
* @param {DebounceOptions} [options] - Options to control the behavior of the debounced function.
|
|
690
|
+
* @returns {DebouncedState<T>} A debounced version of the original callback along with control functions.
|
|
691
|
+
* @public
|
|
692
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-debounce-callback)
|
|
693
|
+
* @example
|
|
694
|
+
* ```tsx
|
|
695
|
+
* const debouncedCallback = useDebounceCallback(
|
|
696
|
+
* (searchTerm) => {
|
|
697
|
+
* // Perform search after user stops typing for 500 milliseconds
|
|
698
|
+
* searchApi(searchTerm);
|
|
699
|
+
* },
|
|
700
|
+
* 500
|
|
701
|
+
* );
|
|
702
|
+
*
|
|
703
|
+
* // Later in the component
|
|
704
|
+
* debouncedCallback('react hooks'); // Will invoke the callback after 500 milliseconds of inactivity.
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
function useDebounceCallback(func, delay = 500, options) {
|
|
708
|
+
const debouncedFunc = (0, react.useRef)(null);
|
|
709
|
+
useUnmount(() => {
|
|
710
|
+
if (debouncedFunc.current) debouncedFunc.current.cancel();
|
|
711
|
+
});
|
|
712
|
+
const debounced = (0, react.useMemo)(() => {
|
|
713
|
+
const debouncedFuncInstance = debounce(func, delay, options);
|
|
714
|
+
const wrappedFunc = (...args) => {
|
|
715
|
+
return debouncedFuncInstance(...args);
|
|
716
|
+
};
|
|
717
|
+
wrappedFunc.cancel = () => {
|
|
718
|
+
debouncedFuncInstance.cancel();
|
|
719
|
+
};
|
|
720
|
+
wrappedFunc.isPending = () => {
|
|
721
|
+
return !!debouncedFunc.current;
|
|
722
|
+
};
|
|
723
|
+
wrappedFunc.flush = () => {
|
|
724
|
+
return debouncedFuncInstance.flush();
|
|
725
|
+
};
|
|
726
|
+
return wrappedFunc;
|
|
727
|
+
}, [
|
|
728
|
+
func,
|
|
729
|
+
delay,
|
|
730
|
+
options
|
|
731
|
+
]);
|
|
732
|
+
(0, react.useEffect)(() => {
|
|
733
|
+
debouncedFunc.current = debounce(func, delay, options);
|
|
734
|
+
}, [
|
|
735
|
+
func,
|
|
736
|
+
delay,
|
|
737
|
+
options
|
|
738
|
+
]);
|
|
739
|
+
return debounced;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region src/useDebounceValue/useDebounceValue.ts
|
|
744
|
+
/**
|
|
745
|
+
* Custom hook that returns a debounced version of the provided value, along with a function to update it.
|
|
746
|
+
* @template T - The type of the value.
|
|
747
|
+
* @param {T | (() => T)} initialValue - The value to be debounced.
|
|
748
|
+
* @param {number} delay - The delay in milliseconds before the value is updated (default is 500ms).
|
|
749
|
+
* @param {object} [options] - Optional configurations for the debouncing behavior.
|
|
750
|
+
* @returns {[T, DebouncedState<(value: T) => void>]} An array containing the debounced value and the function to update it.
|
|
751
|
+
* @public
|
|
752
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-debounce-value)
|
|
753
|
+
* @example
|
|
754
|
+
* ```tsx
|
|
755
|
+
* const [debouncedValue, updateDebouncedValue] = useDebounceValue(inputValue, 500, { leading: true });
|
|
756
|
+
* ```
|
|
757
|
+
*/
|
|
758
|
+
function useDebounceValue(initialValue, delay, options) {
|
|
759
|
+
const eq = options?.equalityFn ?? ((left, right) => left === right);
|
|
760
|
+
const unwrappedInitialValue = initialValue instanceof Function ? initialValue() : initialValue;
|
|
761
|
+
const [debouncedValue, setDebouncedValue] = (0, react.useState)(unwrappedInitialValue);
|
|
762
|
+
const previousValueRef = (0, react.useRef)(unwrappedInitialValue);
|
|
763
|
+
const updateDebouncedValue = useDebounceCallback(setDebouncedValue, delay, options);
|
|
764
|
+
if (!eq(previousValueRef.current, unwrappedInitialValue)) {
|
|
765
|
+
updateDebouncedValue(unwrappedInitialValue);
|
|
766
|
+
previousValueRef.current = unwrappedInitialValue;
|
|
767
|
+
}
|
|
768
|
+
return [debouncedValue, updateDebouncedValue];
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region src/useDisclosure/useDisclosure.ts
|
|
773
|
+
/**
|
|
774
|
+
* Custom hook for managing boolean disclosure state (modals, popovers, drawers, etc.).
|
|
775
|
+
* @param {boolean} [initialState=false] - The initial open state.
|
|
776
|
+
* @param {UseDisclosureOptions} [options] - Optional callbacks for open/close events.
|
|
777
|
+
* @returns {UseDisclosureReturn} A tuple containing the open state and control actions.
|
|
778
|
+
* @public
|
|
779
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-disclosure)
|
|
780
|
+
* @example
|
|
781
|
+
* ```tsx
|
|
782
|
+
* const [opened, { open, close, toggle }] = useDisclosure(false, {
|
|
783
|
+
* onOpen: () => console.log('Opened'),
|
|
784
|
+
* onClose: () => console.log('Closed'),
|
|
785
|
+
* });
|
|
786
|
+
*
|
|
787
|
+
* return (
|
|
788
|
+
* <>
|
|
789
|
+
* <button onClick={open}>Open Modal</button>
|
|
790
|
+
* <Modal opened={opened} onClose={close}>
|
|
791
|
+
* <button onClick={close}>Close</button>
|
|
792
|
+
* </Modal>
|
|
793
|
+
* </>
|
|
794
|
+
* );
|
|
795
|
+
* ```
|
|
796
|
+
*/
|
|
797
|
+
function useDisclosure(initialState = false, options = {}) {
|
|
798
|
+
const { onOpen, onClose } = options;
|
|
799
|
+
const [opened, setOpened] = (0, react.useState)(initialState);
|
|
800
|
+
const open = (0, react.useCallback)(() => {
|
|
801
|
+
setOpened((isOpen) => {
|
|
802
|
+
if (!isOpen) {
|
|
803
|
+
onOpen?.();
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
return isOpen;
|
|
807
|
+
});
|
|
808
|
+
}, [onOpen]);
|
|
809
|
+
const close = (0, react.useCallback)(() => {
|
|
810
|
+
setOpened((isOpen) => {
|
|
811
|
+
if (isOpen) {
|
|
812
|
+
onClose?.();
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
return isOpen;
|
|
816
|
+
});
|
|
817
|
+
}, [onClose]);
|
|
818
|
+
const toggle = (0, react.useCallback)(() => {
|
|
819
|
+
setOpened((isOpen) => {
|
|
820
|
+
if (isOpen) onClose?.();
|
|
821
|
+
else onOpen?.();
|
|
822
|
+
return !isOpen;
|
|
823
|
+
});
|
|
824
|
+
}, [onClose, onOpen]);
|
|
825
|
+
return [opened, {
|
|
826
|
+
open,
|
|
827
|
+
close,
|
|
828
|
+
toggle
|
|
829
|
+
}];
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
//#endregion
|
|
833
|
+
//#region src/useDocumentTitle/useDocumentTitle.ts
|
|
834
|
+
/**
|
|
835
|
+
* Custom hook that sets the document title.
|
|
836
|
+
* @param {string} title - The title to set.
|
|
837
|
+
* @param {?UseDocumentTitleOptions} [options] - The options.
|
|
838
|
+
* @public
|
|
839
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-document-title)
|
|
840
|
+
* @example
|
|
841
|
+
* ```tsx
|
|
842
|
+
* useDocumentTitle('My new title');
|
|
843
|
+
* ```
|
|
844
|
+
*/
|
|
845
|
+
function useDocumentTitle(title, options = {}) {
|
|
846
|
+
const { preserveTitleOnUnmount = true } = options;
|
|
847
|
+
const defaultTitle = (0, react.useRef)(null);
|
|
848
|
+
useIsomorphicLayoutEffect(() => {
|
|
849
|
+
defaultTitle.current = window.document.title;
|
|
850
|
+
}, []);
|
|
851
|
+
useIsomorphicLayoutEffect(() => {
|
|
852
|
+
window.document.title = title;
|
|
853
|
+
}, [title]);
|
|
854
|
+
useUnmount(() => {
|
|
855
|
+
if (!preserveTitleOnUnmount && defaultTitle.current) window.document.title = defaultTitle.current;
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/useGeolocation/useGeolocation.ts
|
|
861
|
+
/**
|
|
862
|
+
* Custom hook that provides access to the browser's geolocation API.
|
|
863
|
+
* @param {UseGeolocationOptions} [options] - Options for geolocation and hook behavior.
|
|
864
|
+
* @returns {UseGeolocationState} The current geolocation state.
|
|
865
|
+
* @public
|
|
866
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-geolocation)
|
|
867
|
+
* @example
|
|
868
|
+
* ```tsx
|
|
869
|
+
* const { latitude, longitude, loading, error } = useGeolocation({
|
|
870
|
+
* enableHighAccuracy: true,
|
|
871
|
+
* timeout: 5000,
|
|
872
|
+
* });
|
|
873
|
+
*
|
|
874
|
+
* if (loading) return <div>Loading location...</div>;
|
|
875
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
876
|
+
* return <div>Location: {latitude}, {longitude}</div>;
|
|
877
|
+
* ```
|
|
878
|
+
*/
|
|
879
|
+
function useGeolocation(options = {}) {
|
|
880
|
+
const { enableHighAccuracy = false, timeout = Infinity, maximumAge = 0, enabled = true } = options;
|
|
881
|
+
const [state, setState] = (0, react.useState)({
|
|
882
|
+
latitude: void 0,
|
|
883
|
+
longitude: void 0,
|
|
884
|
+
accuracy: void 0,
|
|
885
|
+
altitude: void 0,
|
|
886
|
+
altitudeAccuracy: void 0,
|
|
887
|
+
heading: void 0,
|
|
888
|
+
speed: void 0,
|
|
889
|
+
timestamp: void 0,
|
|
890
|
+
loading: true,
|
|
891
|
+
error: void 0
|
|
892
|
+
});
|
|
893
|
+
(0, react.useEffect)(() => {
|
|
894
|
+
if (!enabled) {
|
|
895
|
+
setState((prev) => ({
|
|
896
|
+
...prev,
|
|
897
|
+
loading: false
|
|
898
|
+
}));
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (!navigator.geolocation) {
|
|
902
|
+
setState((prev) => ({
|
|
903
|
+
...prev,
|
|
904
|
+
loading: false,
|
|
905
|
+
error: new Error("Geolocation is not supported")
|
|
906
|
+
}));
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
let isMounted = true;
|
|
910
|
+
const onSuccess = (position) => {
|
|
911
|
+
if (!isMounted) return;
|
|
912
|
+
setState({
|
|
913
|
+
latitude: position.coords.latitude,
|
|
914
|
+
longitude: position.coords.longitude,
|
|
915
|
+
accuracy: position.coords.accuracy,
|
|
916
|
+
altitude: position.coords.altitude,
|
|
917
|
+
altitudeAccuracy: position.coords.altitudeAccuracy,
|
|
918
|
+
heading: position.coords.heading,
|
|
919
|
+
speed: position.coords.speed,
|
|
920
|
+
timestamp: position.timestamp,
|
|
921
|
+
loading: false,
|
|
922
|
+
error: void 0
|
|
923
|
+
});
|
|
924
|
+
};
|
|
925
|
+
const onError = (error) => {
|
|
926
|
+
if (!isMounted) return;
|
|
927
|
+
setState((prev) => ({
|
|
928
|
+
...prev,
|
|
929
|
+
loading: false,
|
|
930
|
+
error
|
|
931
|
+
}));
|
|
932
|
+
};
|
|
933
|
+
navigator.geolocation.getCurrentPosition(onSuccess, onError, {
|
|
934
|
+
enableHighAccuracy,
|
|
935
|
+
timeout,
|
|
936
|
+
maximumAge
|
|
937
|
+
});
|
|
938
|
+
return () => {
|
|
939
|
+
isMounted = false;
|
|
940
|
+
};
|
|
941
|
+
}, [
|
|
942
|
+
enableHighAccuracy,
|
|
943
|
+
enabled,
|
|
944
|
+
maximumAge,
|
|
945
|
+
timeout
|
|
946
|
+
]);
|
|
947
|
+
return state;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
//#endregion
|
|
951
|
+
//#region src/useHover/useHover.ts
|
|
952
|
+
/**
|
|
953
|
+
* Custom hook that tracks whether a DOM element is being hovered over.
|
|
954
|
+
* @template T - The type of the DOM element. Defaults to `HTMLElement`.
|
|
955
|
+
* @param {RefObject<T>} elementRef - The ref object for the DOM element to track.
|
|
956
|
+
* @returns {boolean} A boolean value indicating whether the element is being hovered over.
|
|
957
|
+
* @public
|
|
958
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-hover)
|
|
959
|
+
* @example
|
|
960
|
+
* ```tsx
|
|
961
|
+
* const buttonRef = useRef<HTMLButtonElement>(null);
|
|
962
|
+
* const isHovered = useHover(buttonRef);
|
|
963
|
+
* // Access the isHovered variable to determine if the button is being hovered over.
|
|
964
|
+
* ```
|
|
965
|
+
*/
|
|
966
|
+
function useHover(elementRef) {
|
|
967
|
+
const [value, setValue] = (0, react.useState)(false);
|
|
968
|
+
const handleMouseEnter = () => {
|
|
969
|
+
setValue(true);
|
|
970
|
+
};
|
|
971
|
+
const handleMouseLeave = () => {
|
|
972
|
+
setValue(false);
|
|
973
|
+
};
|
|
974
|
+
useEventListener("mouseenter", handleMouseEnter, elementRef);
|
|
975
|
+
useEventListener("mouseleave", handleMouseLeave, elementRef);
|
|
976
|
+
return value;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
//#endregion
|
|
980
|
+
//#region src/useIdle/useIdle.ts
|
|
981
|
+
/**
|
|
982
|
+
* Default events that indicate user activity.
|
|
983
|
+
*/
|
|
984
|
+
const DEFAULT_EVENTS = [
|
|
985
|
+
"mousemove",
|
|
986
|
+
"mousedown",
|
|
987
|
+
"resize",
|
|
988
|
+
"keydown",
|
|
989
|
+
"touchstart",
|
|
990
|
+
"wheel"
|
|
991
|
+
];
|
|
992
|
+
/**
|
|
993
|
+
* Custom hook that tracks whether the user is idle (no activity for a specified time).
|
|
994
|
+
* @param {number} timeout - The time in milliseconds of inactivity before considering the user idle.
|
|
995
|
+
* @param {UseIdleOptions} [options] - Options for customizing the hook behavior.
|
|
996
|
+
* @returns {IdleState} The current idle state and last activity timestamp.
|
|
997
|
+
* @public
|
|
998
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-idle)
|
|
999
|
+
* @example
|
|
1000
|
+
* ```tsx
|
|
1001
|
+
* const { idle, lastActive } = useIdle(5000);
|
|
1002
|
+
*
|
|
1003
|
+
* return (
|
|
1004
|
+
* <div>
|
|
1005
|
+
* <p>{idle ? 'User is idle' : 'User is active'}</p>
|
|
1006
|
+
* <p>Last active: {new Date(lastActive).toLocaleString()}</p>
|
|
1007
|
+
* </div>
|
|
1008
|
+
* );
|
|
1009
|
+
* ```
|
|
1010
|
+
*/
|
|
1011
|
+
function useIdle(timeout, options = {}) {
|
|
1012
|
+
const { events = DEFAULT_EVENTS, initialIdle = false } = options;
|
|
1013
|
+
const [state, setState] = (0, react.useState)({
|
|
1014
|
+
idle: initialIdle,
|
|
1015
|
+
lastActive: Date.now()
|
|
1016
|
+
});
|
|
1017
|
+
const timerRef = (0, react.useRef)(null);
|
|
1018
|
+
(0, react.useEffect)(() => {
|
|
1019
|
+
const resetIdle = () => {
|
|
1020
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1021
|
+
setState({
|
|
1022
|
+
idle: false,
|
|
1023
|
+
lastActive: Date.now()
|
|
1024
|
+
});
|
|
1025
|
+
timerRef.current = setTimeout(() => {
|
|
1026
|
+
setState((prev) => ({
|
|
1027
|
+
...prev,
|
|
1028
|
+
idle: true
|
|
1029
|
+
}));
|
|
1030
|
+
}, timeout);
|
|
1031
|
+
};
|
|
1032
|
+
events.forEach((event) => {
|
|
1033
|
+
window.addEventListener(event, resetIdle);
|
|
1034
|
+
});
|
|
1035
|
+
timerRef.current = setTimeout(() => {
|
|
1036
|
+
setState((prev) => ({
|
|
1037
|
+
...prev,
|
|
1038
|
+
idle: true
|
|
1039
|
+
}));
|
|
1040
|
+
}, timeout);
|
|
1041
|
+
return () => {
|
|
1042
|
+
events.forEach((event) => {
|
|
1043
|
+
window.removeEventListener(event, resetIdle);
|
|
1044
|
+
});
|
|
1045
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1046
|
+
};
|
|
1047
|
+
}, [timeout, events]);
|
|
1048
|
+
return state;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region src/useIntersectionObserver/useIntersectionObserver.ts
|
|
1053
|
+
/**
|
|
1054
|
+
* Custom hook that tracks the intersection of a DOM element with its containing element or the viewport using the [`Intersection Observer API`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
|
|
1055
|
+
* @param {UseIntersectionObserverOptions} options - The options for the Intersection Observer.
|
|
1056
|
+
* @returns {IntersectionReturn} The ref callback, a boolean indicating if the element is intersecting, and the intersection observer entry.
|
|
1057
|
+
* @public
|
|
1058
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-intersection-observer)
|
|
1059
|
+
* @example
|
|
1060
|
+
* ```tsx
|
|
1061
|
+
* // Example 1
|
|
1062
|
+
* const [ref, isIntersecting, entry] = useIntersectionObserver({ threshold: 0.5 });
|
|
1063
|
+
* ```
|
|
1064
|
+
*
|
|
1065
|
+
* ```tsx
|
|
1066
|
+
* // Example 2
|
|
1067
|
+
* const { ref, isIntersecting, entry } = useIntersectionObserver({ threshold: 0.5 });
|
|
1068
|
+
* ```
|
|
1069
|
+
*/
|
|
1070
|
+
function useIntersectionObserver({ threshold = 0, root = null, rootMargin = "0%", freezeOnceVisible = false, initialIsIntersecting = false, onChange } = {}) {
|
|
1071
|
+
const [ref, setRef] = (0, react.useState)(null);
|
|
1072
|
+
const [state, setState] = (0, react.useState)(() => ({
|
|
1073
|
+
isIntersecting: initialIsIntersecting,
|
|
1074
|
+
entry: void 0
|
|
1075
|
+
}));
|
|
1076
|
+
const callbackRef = (0, react.useRef)(void 0);
|
|
1077
|
+
callbackRef.current = onChange;
|
|
1078
|
+
const frozen = state.entry?.isIntersecting && freezeOnceVisible;
|
|
1079
|
+
(0, react.useEffect)(() => {
|
|
1080
|
+
if (!ref) return;
|
|
1081
|
+
if (!("IntersectionObserver" in window)) return;
|
|
1082
|
+
if (frozen) return;
|
|
1083
|
+
let unobserve;
|
|
1084
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1085
|
+
const thresholds = Array.isArray(observer.thresholds) ? observer.thresholds : [observer.thresholds];
|
|
1086
|
+
entries.forEach((entry) => {
|
|
1087
|
+
const isIntersecting = entry.isIntersecting && thresholds.some((threshold$1) => entry.intersectionRatio >= threshold$1);
|
|
1088
|
+
setState({
|
|
1089
|
+
isIntersecting,
|
|
1090
|
+
entry
|
|
1091
|
+
});
|
|
1092
|
+
if (callbackRef.current) callbackRef.current(isIntersecting, entry);
|
|
1093
|
+
if (isIntersecting && freezeOnceVisible && unobserve) {
|
|
1094
|
+
unobserve();
|
|
1095
|
+
unobserve = void 0;
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
}, {
|
|
1099
|
+
threshold,
|
|
1100
|
+
root,
|
|
1101
|
+
rootMargin
|
|
1102
|
+
});
|
|
1103
|
+
observer.observe(ref);
|
|
1104
|
+
return () => {
|
|
1105
|
+
observer.disconnect();
|
|
1106
|
+
};
|
|
1107
|
+
}, [
|
|
1108
|
+
ref,
|
|
1109
|
+
JSON.stringify(threshold),
|
|
1110
|
+
root,
|
|
1111
|
+
rootMargin,
|
|
1112
|
+
frozen,
|
|
1113
|
+
freezeOnceVisible
|
|
1114
|
+
]);
|
|
1115
|
+
const prevRef = (0, react.useRef)(null);
|
|
1116
|
+
(0, react.useEffect)(() => {
|
|
1117
|
+
if (!ref && state.entry?.target && !freezeOnceVisible && !frozen && prevRef.current !== state.entry.target) {
|
|
1118
|
+
prevRef.current = state.entry.target;
|
|
1119
|
+
setState({
|
|
1120
|
+
isIntersecting: initialIsIntersecting,
|
|
1121
|
+
entry: void 0
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
}, [
|
|
1125
|
+
ref,
|
|
1126
|
+
state.entry,
|
|
1127
|
+
freezeOnceVisible,
|
|
1128
|
+
frozen,
|
|
1129
|
+
initialIsIntersecting
|
|
1130
|
+
]);
|
|
1131
|
+
const result = [
|
|
1132
|
+
setRef,
|
|
1133
|
+
!!state.isIntersecting,
|
|
1134
|
+
state.entry
|
|
1135
|
+
];
|
|
1136
|
+
result.ref = result[0];
|
|
1137
|
+
result.isIntersecting = result[1];
|
|
1138
|
+
result.entry = result[2];
|
|
1139
|
+
return result;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
//#endregion
|
|
1143
|
+
//#region src/useIsClient/useIsClient.ts
|
|
1144
|
+
/**
|
|
1145
|
+
* Custom hook that determines if the code is running on the client side (in the browser).
|
|
1146
|
+
* @returns {boolean} A boolean value indicating whether the code is running on the client side.
|
|
1147
|
+
* @public
|
|
1148
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-is-client)
|
|
1149
|
+
* @example
|
|
1150
|
+
* ```tsx
|
|
1151
|
+
* const isClient = useIsClient();
|
|
1152
|
+
* // Use isClient to conditionally render or execute code specific to the client side.
|
|
1153
|
+
* ```
|
|
1154
|
+
*/
|
|
1155
|
+
function useIsClient() {
|
|
1156
|
+
const [isClient, setClient] = (0, react.useState)(false);
|
|
1157
|
+
(0, react.useEffect)(() => {
|
|
1158
|
+
setClient(true);
|
|
1159
|
+
}, []);
|
|
1160
|
+
return isClient;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
//#endregion
|
|
1164
|
+
//#region src/useIsMounted/useIsMounted.ts
|
|
1165
|
+
/**
|
|
1166
|
+
* Custom hook that determines if the component is currently mounted.
|
|
1167
|
+
* @returns {() => boolean} A function that returns a boolean value indicating whether the component is mounted.
|
|
1168
|
+
* @public
|
|
1169
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-is-mounted)
|
|
1170
|
+
* @example
|
|
1171
|
+
* ```tsx
|
|
1172
|
+
* const isComponentMounted = useIsMounted();
|
|
1173
|
+
* // Use isComponentMounted() to check if the component is currently mounted before performing certain actions.
|
|
1174
|
+
* ```
|
|
1175
|
+
*/
|
|
1176
|
+
function useIsMounted() {
|
|
1177
|
+
const isMounted = (0, react.useRef)(false);
|
|
1178
|
+
(0, react.useEffect)(() => {
|
|
1179
|
+
isMounted.current = true;
|
|
1180
|
+
return () => {
|
|
1181
|
+
isMounted.current = false;
|
|
1182
|
+
};
|
|
1183
|
+
}, []);
|
|
1184
|
+
return (0, react.useCallback)(() => isMounted.current, []);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
//#endregion
|
|
1188
|
+
//#region src/useList/useList.ts
|
|
1189
|
+
/**
|
|
1190
|
+
* Custom hook that manages a list state with setter actions.
|
|
1191
|
+
* @template T - The type of elements in the list.
|
|
1192
|
+
* @param {T[]} [initialValue] - The initial array of values for the list (optional).
|
|
1193
|
+
* @returns {UseListReturn<T>} A tuple containing the list state and actions to interact with the list.
|
|
1194
|
+
* @public
|
|
1195
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-list)
|
|
1196
|
+
* @example
|
|
1197
|
+
* ```tsx
|
|
1198
|
+
* const [list, listActions] = useList<number>([1, 2, 3]);
|
|
1199
|
+
* // Access the `list` array and use `listActions` to push, updateAt, removeAt, or reset values.
|
|
1200
|
+
* ```
|
|
1201
|
+
*/
|
|
1202
|
+
function useList(initialValue = []) {
|
|
1203
|
+
const initialListRef = (0, react.useRef)(initialValue);
|
|
1204
|
+
const [list, setList] = (0, react.useState)(initialValue);
|
|
1205
|
+
const set = (0, react.useCallback)((newList) => {
|
|
1206
|
+
setList(newList);
|
|
1207
|
+
}, []);
|
|
1208
|
+
const push = (0, react.useCallback)((...values) => {
|
|
1209
|
+
setList((prev) => [...prev, ...values]);
|
|
1210
|
+
}, []);
|
|
1211
|
+
const updateAt = (0, react.useCallback)((index, value) => {
|
|
1212
|
+
setList((prev) => {
|
|
1213
|
+
if (index < 0 || index >= prev.length) return prev;
|
|
1214
|
+
const copy = [...prev];
|
|
1215
|
+
copy[index] = value;
|
|
1216
|
+
return copy;
|
|
1217
|
+
});
|
|
1218
|
+
}, []);
|
|
1219
|
+
const insertAt = (0, react.useCallback)((index, value) => {
|
|
1220
|
+
setList((prev) => {
|
|
1221
|
+
if (index < 0 || index > prev.length) return prev;
|
|
1222
|
+
const copy = [...prev];
|
|
1223
|
+
copy.splice(index, 0, value);
|
|
1224
|
+
return copy;
|
|
1225
|
+
});
|
|
1226
|
+
}, []);
|
|
1227
|
+
const removeAt = (0, react.useCallback)((index) => {
|
|
1228
|
+
setList((prev) => {
|
|
1229
|
+
if (index < 0 || index >= prev.length) return prev;
|
|
1230
|
+
const copy = [...prev];
|
|
1231
|
+
copy.splice(index, 1);
|
|
1232
|
+
return copy;
|
|
1233
|
+
});
|
|
1234
|
+
}, []);
|
|
1235
|
+
const clear = (0, react.useCallback)(() => {
|
|
1236
|
+
setList([]);
|
|
1237
|
+
}, []);
|
|
1238
|
+
const reset = (0, react.useCallback)(() => {
|
|
1239
|
+
setList([...initialListRef.current]);
|
|
1240
|
+
}, []);
|
|
1241
|
+
const actions = {
|
|
1242
|
+
set,
|
|
1243
|
+
push,
|
|
1244
|
+
updateAt,
|
|
1245
|
+
insertAt,
|
|
1246
|
+
removeAt,
|
|
1247
|
+
clear,
|
|
1248
|
+
reset
|
|
1249
|
+
};
|
|
1250
|
+
return [list, actions];
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
//#endregion
|
|
1254
|
+
//#region src/useMap/useMap.ts
|
|
1255
|
+
/**
|
|
1256
|
+
* Custom hook that manages a key-value [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) state with setter actions.
|
|
1257
|
+
* @template K - The type of keys in the map.
|
|
1258
|
+
* @template V - The type of values in the map.
|
|
1259
|
+
* @param {MapOrEntries<K, V>} [initialState] - The initial state of the map as a Map or an array of key-value pairs (optional).
|
|
1260
|
+
* @returns {UseMapReturn<K, V>} A tuple containing the map state and actions to interact with the map.
|
|
1261
|
+
* @public
|
|
1262
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-map)
|
|
1263
|
+
* @example
|
|
1264
|
+
* ```tsx
|
|
1265
|
+
* const [map, mapActions] = useMap();
|
|
1266
|
+
* // Access the `map` state and use `mapActions` to set, remove, or reset entries.
|
|
1267
|
+
* ```
|
|
1268
|
+
*/
|
|
1269
|
+
function useMap(initialState = new Map()) {
|
|
1270
|
+
const [map, setMap] = (0, react.useState)(new Map(initialState));
|
|
1271
|
+
const actions = {
|
|
1272
|
+
set: (0, react.useCallback)((key, value) => {
|
|
1273
|
+
setMap((prev) => {
|
|
1274
|
+
const copy = new Map(prev);
|
|
1275
|
+
copy.set(key, value);
|
|
1276
|
+
return copy;
|
|
1277
|
+
});
|
|
1278
|
+
}, []),
|
|
1279
|
+
setAll: (0, react.useCallback)((entries) => {
|
|
1280
|
+
setMap(() => new Map(entries));
|
|
1281
|
+
}, []),
|
|
1282
|
+
remove: (0, react.useCallback)((key) => {
|
|
1283
|
+
setMap((prev) => {
|
|
1284
|
+
const copy = new Map(prev);
|
|
1285
|
+
copy.delete(key);
|
|
1286
|
+
return copy;
|
|
1287
|
+
});
|
|
1288
|
+
}, []),
|
|
1289
|
+
reset: (0, react.useCallback)(() => {
|
|
1290
|
+
setMap(() => new Map());
|
|
1291
|
+
}, [])
|
|
1292
|
+
};
|
|
1293
|
+
return [map, actions];
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
//#endregion
|
|
1297
|
+
//#region src/useMemoizedFn/useMemoizedFn.ts
|
|
1298
|
+
/**
|
|
1299
|
+
* Custom hook that returns a memoized version of a function that never changes reference.
|
|
1300
|
+
* The returned function will always call the latest version of the callback, but its
|
|
1301
|
+
* identity (reference) will remain stable across renders.
|
|
1302
|
+
*
|
|
1303
|
+
* This is useful when you need to pass a callback to a dependency array or child component
|
|
1304
|
+
* without causing unnecessary re-renders.
|
|
1305
|
+
*
|
|
1306
|
+
* @template T - The type of the function being memoized.
|
|
1307
|
+
* @param {T} fn - The function to memoize.
|
|
1308
|
+
* @returns {T} A memoized function with stable reference.
|
|
1309
|
+
* @public
|
|
1310
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-memoized-fn)
|
|
1311
|
+
* @example
|
|
1312
|
+
* ```tsx
|
|
1313
|
+
* const stableCallback = useMemoizedFn((value) => {
|
|
1314
|
+
* console.log(value);
|
|
1315
|
+
* });
|
|
1316
|
+
*
|
|
1317
|
+
* // stableCallback can be safely used in useEffect deps without causing infinite loops
|
|
1318
|
+
* useEffect(() => {
|
|
1319
|
+
* stableCallback(someValue);
|
|
1320
|
+
* }, [stableCallback]);
|
|
1321
|
+
* ```
|
|
1322
|
+
*/
|
|
1323
|
+
function useMemoizedFn(fn) {
|
|
1324
|
+
const fnRef = (0, react.useRef)(fn);
|
|
1325
|
+
fnRef.current = fn;
|
|
1326
|
+
const memoizedFn = (0, react.useCallback)((...args) => {
|
|
1327
|
+
return fnRef.current(...args);
|
|
1328
|
+
}, []);
|
|
1329
|
+
return memoizedFn;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
//#endregion
|
|
1333
|
+
//#region src/useNetwork/useNetwork.ts
|
|
1334
|
+
/**
|
|
1335
|
+
* Custom hook that tracks the browser's network connection status.
|
|
1336
|
+
* @returns {NetworkState} The current network state including online status and connection info.
|
|
1337
|
+
* @public
|
|
1338
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-network)
|
|
1339
|
+
* @example
|
|
1340
|
+
* ```tsx
|
|
1341
|
+
* const { online, effectiveType, downlink } = useNetwork();
|
|
1342
|
+
*
|
|
1343
|
+
* return (
|
|
1344
|
+
* <div>
|
|
1345
|
+
* <p>Status: {online ? 'Online' : 'Offline'}</p>
|
|
1346
|
+
* <p>Connection: {effectiveType}</p>
|
|
1347
|
+
* <p>Speed: {downlink} Mbps</p>
|
|
1348
|
+
* </div>
|
|
1349
|
+
* );
|
|
1350
|
+
* ```
|
|
1351
|
+
*/
|
|
1352
|
+
function useNetwork() {
|
|
1353
|
+
const [state, setState] = (0, react.useState)({
|
|
1354
|
+
online: typeof navigator !== "undefined" ? navigator.onLine : true,
|
|
1355
|
+
effectiveType: void 0,
|
|
1356
|
+
downlink: void 0,
|
|
1357
|
+
downlinkMax: void 0,
|
|
1358
|
+
type: void 0,
|
|
1359
|
+
rtt: void 0,
|
|
1360
|
+
saveData: void 0
|
|
1361
|
+
});
|
|
1362
|
+
(0, react.useEffect)(() => {
|
|
1363
|
+
const handleOnline = () => {
|
|
1364
|
+
setState((prev) => ({
|
|
1365
|
+
...prev,
|
|
1366
|
+
online: true
|
|
1367
|
+
}));
|
|
1368
|
+
};
|
|
1369
|
+
const handleOffline = () => {
|
|
1370
|
+
setState((prev) => ({
|
|
1371
|
+
...prev,
|
|
1372
|
+
online: false
|
|
1373
|
+
}));
|
|
1374
|
+
};
|
|
1375
|
+
const updateConnectionInfo = () => {
|
|
1376
|
+
const connection$1 = navigator.connection;
|
|
1377
|
+
if (connection$1) setState({
|
|
1378
|
+
online: navigator.onLine,
|
|
1379
|
+
effectiveType: connection$1.effectiveType,
|
|
1380
|
+
downlink: connection$1.downlink,
|
|
1381
|
+
downlinkMax: connection$1.downlinkMax,
|
|
1382
|
+
type: connection$1.type,
|
|
1383
|
+
rtt: connection$1.rtt,
|
|
1384
|
+
saveData: connection$1.saveData
|
|
1385
|
+
});
|
|
1386
|
+
};
|
|
1387
|
+
window.addEventListener("online", handleOnline);
|
|
1388
|
+
window.addEventListener("offline", handleOffline);
|
|
1389
|
+
const connection = navigator.connection;
|
|
1390
|
+
if (connection) {
|
|
1391
|
+
connection.addEventListener("change", updateConnectionInfo);
|
|
1392
|
+
updateConnectionInfo();
|
|
1393
|
+
}
|
|
1394
|
+
return () => {
|
|
1395
|
+
window.removeEventListener("online", handleOnline);
|
|
1396
|
+
window.removeEventListener("offline", handleOffline);
|
|
1397
|
+
if (connection) connection.removeEventListener("change", updateConnectionInfo);
|
|
1398
|
+
};
|
|
1399
|
+
}, []);
|
|
1400
|
+
return state;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
//#endregion
|
|
1404
|
+
//#region src/useOnClickOutside/useOnClickOutside.ts
|
|
1405
|
+
/**
|
|
1406
|
+
* Custom hook that handles clicks outside a specified element.
|
|
1407
|
+
* @template T - The type of the element's reference.
|
|
1408
|
+
* @param {RefObject<T | null> | RefObject<T | null>[]} ref - The React ref object(s) representing the element(s) to watch for outside clicks.
|
|
1409
|
+
* @param {(event: MouseEvent | TouchEvent | FocusEvent) => void} handler - The callback function to be executed when a click outside the element occurs.
|
|
1410
|
+
* @param {EventType} [eventType] - The mouse event type to listen for (optional, default is 'mousedown').
|
|
1411
|
+
* @param {?AddEventListenerOptions} [eventListenerOptions] - The options object to be passed to the `addEventListener` method (optional).
|
|
1412
|
+
* @returns {void}
|
|
1413
|
+
* @public
|
|
1414
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-on-click-outside)
|
|
1415
|
+
* @example
|
|
1416
|
+
* ```tsx
|
|
1417
|
+
* const containerRef = useRef(null);
|
|
1418
|
+
* useOnClickOutside([containerRef], () => {
|
|
1419
|
+
* // Handle clicks outside the container.
|
|
1420
|
+
* });
|
|
1421
|
+
* ```
|
|
1422
|
+
*/
|
|
1423
|
+
function useOnClickOutside(ref, handler, eventType = "mousedown", eventListenerOptions = {}) {
|
|
1424
|
+
useEventListener(eventType, (event) => {
|
|
1425
|
+
const target = event.target;
|
|
1426
|
+
if (!target || !target.isConnected) return;
|
|
1427
|
+
const isOutside = Array.isArray(ref) ? ref.filter((r) => Boolean(r.current)).every((r) => r.current && !r.current.contains(target)) : ref.current && !ref.current.contains(target);
|
|
1428
|
+
if (isOutside) handler(event);
|
|
1429
|
+
}, void 0, eventListenerOptions);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
//#endregion
|
|
1433
|
+
//#region src/usePageLeave/usePageLeave.ts
|
|
1434
|
+
/**
|
|
1435
|
+
* Custom hook that invokes a callback when the user's mouse leaves the page.
|
|
1436
|
+
* @param {() => void} handler - The callback function to invoke when the mouse leaves the page.
|
|
1437
|
+
* @public
|
|
1438
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-page-leave)
|
|
1439
|
+
* @example
|
|
1440
|
+
* ```tsx
|
|
1441
|
+
* usePageLeave(() => {
|
|
1442
|
+
* console.log('User is leaving the page');
|
|
1443
|
+
* // Show "Don't leave!" modal or save draft
|
|
1444
|
+
* });
|
|
1445
|
+
* ```
|
|
1446
|
+
*/
|
|
1447
|
+
function usePageLeave(handler) {
|
|
1448
|
+
(0, react.useEffect)(() => {
|
|
1449
|
+
if (!handler) return;
|
|
1450
|
+
const handleMouseLeave = () => {
|
|
1451
|
+
handler();
|
|
1452
|
+
};
|
|
1453
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
1454
|
+
return () => {
|
|
1455
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
1456
|
+
};
|
|
1457
|
+
}, [handler]);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
//#endregion
|
|
1461
|
+
//#region src/usePagination/usePagination.ts
|
|
1462
|
+
const DOTS = "dots";
|
|
1463
|
+
/**
|
|
1464
|
+
* Custom hook for managing pagination logic.
|
|
1465
|
+
* Returns page navigation functions and a range array suitable for pagination UI.
|
|
1466
|
+
* @param {UsePaginationOptions} options - Pagination configuration options.
|
|
1467
|
+
* @returns {UsePaginationReturn} The pagination state and navigation functions.
|
|
1468
|
+
* @public
|
|
1469
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-pagination)
|
|
1470
|
+
* @example
|
|
1471
|
+
* ```tsx
|
|
1472
|
+
* const { activePage, setPage, next, prev, range } = usePagination({
|
|
1473
|
+
* total: 20,
|
|
1474
|
+
* siblings: 1,
|
|
1475
|
+
* boundaries: 1,
|
|
1476
|
+
* });
|
|
1477
|
+
*
|
|
1478
|
+
* // range = [1, 'dots', 4, 5, 6, 'dots', 20]
|
|
1479
|
+
* ```
|
|
1480
|
+
*/
|
|
1481
|
+
function usePagination(options) {
|
|
1482
|
+
const { total, siblings = 1, boundaries = 1, initialPage = 1, page: controlledPage, onChange } = options;
|
|
1483
|
+
const [internalPage, setInternalPage] = (0, react.useState)(initialPage);
|
|
1484
|
+
const activePage = controlledPage ?? internalPage;
|
|
1485
|
+
const setPage = (0, react.useCallback)((page) => {
|
|
1486
|
+
const validPage = Math.max(1, Math.min(page, total));
|
|
1487
|
+
if (validPage !== activePage) {
|
|
1488
|
+
if (controlledPage === void 0) setInternalPage(validPage);
|
|
1489
|
+
onChange?.(validPage);
|
|
1490
|
+
}
|
|
1491
|
+
}, [
|
|
1492
|
+
activePage,
|
|
1493
|
+
controlledPage,
|
|
1494
|
+
onChange,
|
|
1495
|
+
total
|
|
1496
|
+
]);
|
|
1497
|
+
const next = (0, react.useCallback)(() => {
|
|
1498
|
+
setPage(activePage + 1);
|
|
1499
|
+
}, [activePage, setPage]);
|
|
1500
|
+
const prev = (0, react.useCallback)(() => {
|
|
1501
|
+
setPage(activePage - 1);
|
|
1502
|
+
}, [activePage, setPage]);
|
|
1503
|
+
const isFirst = activePage === 1;
|
|
1504
|
+
const isLast = activePage === total;
|
|
1505
|
+
const range = (0, react.useMemo)(() => {
|
|
1506
|
+
const range$1 = [];
|
|
1507
|
+
const totalVisible = siblings * 2 + 3 + boundaries * 2;
|
|
1508
|
+
if (totalVisible >= total) for (let i = 1; i <= total; i++) range$1.push(i);
|
|
1509
|
+
else {
|
|
1510
|
+
const leftBoundaryEnd = boundaries;
|
|
1511
|
+
for (let i = 1; i <= leftBoundaryEnd; i++) range$1.push(i);
|
|
1512
|
+
const leftSibling = Math.max(activePage - siblings, boundaries + 1);
|
|
1513
|
+
const rightSibling = Math.min(activePage + siblings, total - boundaries);
|
|
1514
|
+
const showLeftDots = leftSibling > boundaries + 1;
|
|
1515
|
+
const showRightDots = rightSibling < total - boundaries;
|
|
1516
|
+
if (showLeftDots) range$1.push(DOTS);
|
|
1517
|
+
for (let i = leftSibling; i <= rightSibling; i++) range$1.push(i);
|
|
1518
|
+
if (showRightDots) range$1.push(DOTS);
|
|
1519
|
+
const rightBoundaryStart = total - boundaries + 1;
|
|
1520
|
+
for (let i = rightBoundaryStart; i <= total; i++) range$1.push(i);
|
|
1521
|
+
}
|
|
1522
|
+
return range$1;
|
|
1523
|
+
}, [
|
|
1524
|
+
activePage,
|
|
1525
|
+
boundaries,
|
|
1526
|
+
siblings,
|
|
1527
|
+
total
|
|
1528
|
+
]);
|
|
1529
|
+
return {
|
|
1530
|
+
activePage,
|
|
1531
|
+
range,
|
|
1532
|
+
setPage,
|
|
1533
|
+
next,
|
|
1534
|
+
prev,
|
|
1535
|
+
first: isFirst,
|
|
1536
|
+
last: isLast
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
//#endregion
|
|
1541
|
+
//#region src/usePermission/usePermission.ts
|
|
1542
|
+
/**
|
|
1543
|
+
* Custom hook that tracks the state of a browser permission.
|
|
1544
|
+
* @param {PermissionName} permissionName - The name of the permission to track.
|
|
1545
|
+
* @returns {PermissionState} The current state of the permission.
|
|
1546
|
+
* @public
|
|
1547
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-permission)
|
|
1548
|
+
* @example
|
|
1549
|
+
* ```tsx
|
|
1550
|
+
* const { state, supported } = usePermission('camera');
|
|
1551
|
+
*
|
|
1552
|
+
* if (!supported) return <div>Permissions API not supported</div>;
|
|
1553
|
+
* if (state === 'denied') return <div>Camera access denied</div>;
|
|
1554
|
+
* return <Camera />;
|
|
1555
|
+
* ```
|
|
1556
|
+
*/
|
|
1557
|
+
function usePermission(permissionName) {
|
|
1558
|
+
const [state, setState] = (0, react.useState)({
|
|
1559
|
+
state: "prompt",
|
|
1560
|
+
supported: true
|
|
1561
|
+
});
|
|
1562
|
+
const permissionStatusRef = (0, react.useRef)(null);
|
|
1563
|
+
(0, react.useEffect)(() => {
|
|
1564
|
+
if (!navigator.permissions) {
|
|
1565
|
+
setState({
|
|
1566
|
+
state: "unsupported",
|
|
1567
|
+
supported: false
|
|
1568
|
+
});
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
let isMounted = true;
|
|
1572
|
+
const queryPermission = async () => {
|
|
1573
|
+
try {
|
|
1574
|
+
const status = await navigator.permissions.query({ name: permissionName });
|
|
1575
|
+
if (!isMounted) return;
|
|
1576
|
+
permissionStatusRef.current = status;
|
|
1577
|
+
const updateState = () => {
|
|
1578
|
+
if (isMounted) setState({
|
|
1579
|
+
state: status.state,
|
|
1580
|
+
supported: true
|
|
1581
|
+
});
|
|
1582
|
+
};
|
|
1583
|
+
updateState();
|
|
1584
|
+
status.addEventListener("change", updateState);
|
|
1585
|
+
return () => {
|
|
1586
|
+
status.removeEventListener("change", updateState);
|
|
1587
|
+
};
|
|
1588
|
+
} catch {
|
|
1589
|
+
if (isMounted) setState({
|
|
1590
|
+
state: "unsupported",
|
|
1591
|
+
supported: false
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
queryPermission();
|
|
1596
|
+
return () => {
|
|
1597
|
+
isMounted = false;
|
|
1598
|
+
};
|
|
1599
|
+
}, [permissionName]);
|
|
1600
|
+
return state;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
//#endregion
|
|
1604
|
+
//#region src/usePrevious/usePrevious.ts
|
|
1605
|
+
function usePrevious(value, initialValue) {
|
|
1606
|
+
const ref = (0, react.useRef)(initialValue);
|
|
1607
|
+
(0, react.useEffect)(() => {
|
|
1608
|
+
ref.current = value;
|
|
1609
|
+
}, [value]);
|
|
1610
|
+
return ref.current;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region src/useQueue/useQueue.ts
|
|
1615
|
+
/**
|
|
1616
|
+
* Custom hook that manages a FIFO (First In, First Out) queue state with setter actions.
|
|
1617
|
+
* @template T - The type of elements in the queue.
|
|
1618
|
+
* @param {T[]} [initialValue] - The initial array of values for the queue (optional).
|
|
1619
|
+
* @returns {UseQueueReturn<T>} A tuple containing the queue state and actions to interact with the queue.
|
|
1620
|
+
* @public
|
|
1621
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-queue)
|
|
1622
|
+
* @example
|
|
1623
|
+
* ```tsx
|
|
1624
|
+
* const [queue, queueActions] = useQueue<number>([1, 2, 3]);
|
|
1625
|
+
* // Access the `queue` array and use `queueActions` to add, remove, or clear values.
|
|
1626
|
+
* ```
|
|
1627
|
+
*/
|
|
1628
|
+
function useQueue(initialValue = []) {
|
|
1629
|
+
const queueRef = (0, react.useRef)(initialValue);
|
|
1630
|
+
const [, setTick] = (0, react.useState)(0);
|
|
1631
|
+
const update = (0, react.useCallback)(() => {
|
|
1632
|
+
setTick((t) => t + 1);
|
|
1633
|
+
}, []);
|
|
1634
|
+
const add = (0, react.useCallback)((value) => {
|
|
1635
|
+
queueRef.current = [...queueRef.current, value];
|
|
1636
|
+
update();
|
|
1637
|
+
}, [update]);
|
|
1638
|
+
const remove = (0, react.useCallback)(() => {
|
|
1639
|
+
if (queueRef.current.length === 0) return void 0;
|
|
1640
|
+
const [first, ...rest] = queueRef.current;
|
|
1641
|
+
queueRef.current = rest;
|
|
1642
|
+
update();
|
|
1643
|
+
return first;
|
|
1644
|
+
}, [update]);
|
|
1645
|
+
const clear = (0, react.useCallback)(() => {
|
|
1646
|
+
queueRef.current = [];
|
|
1647
|
+
update();
|
|
1648
|
+
}, [update]);
|
|
1649
|
+
const actions = (0, react.useMemo)(() => ({
|
|
1650
|
+
add,
|
|
1651
|
+
remove,
|
|
1652
|
+
clear,
|
|
1653
|
+
get first() {
|
|
1654
|
+
return queueRef.current[0];
|
|
1655
|
+
},
|
|
1656
|
+
get last() {
|
|
1657
|
+
return queueRef.current[queueRef.current.length - 1];
|
|
1658
|
+
},
|
|
1659
|
+
get size() {
|
|
1660
|
+
return queueRef.current.length;
|
|
1661
|
+
}
|
|
1662
|
+
}), [
|
|
1663
|
+
add,
|
|
1664
|
+
clear,
|
|
1665
|
+
remove
|
|
1666
|
+
]);
|
|
1667
|
+
return [queueRef.current, actions];
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
//#endregion
|
|
1671
|
+
//#region src/useReadLocalStorage/useReadLocalStorage.ts
|
|
1672
|
+
const IS_SERVER$4 = typeof window === "undefined";
|
|
1673
|
+
/**
|
|
1674
|
+
* Custom hook that reads a value from [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), closely related to [`useLocalStorage()`](https://usehooks-ts.com/react-hook/use-local-storage).
|
|
1675
|
+
* @template T - The type of the stored value.
|
|
1676
|
+
* @param {string} key - The key associated with the value in local storage.
|
|
1677
|
+
* @param {Options<T>} [options] - Additional options for reading the value (optional).
|
|
1678
|
+
* @returns {T | null | undefined} The stored value, or null if the key is not present or an error occurs.
|
|
1679
|
+
* @public
|
|
1680
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-read-local-storage)
|
|
1681
|
+
* @example
|
|
1682
|
+
* ```tsx
|
|
1683
|
+
* const storedData = useReadLocalStorage('myKey');
|
|
1684
|
+
* // Access the stored data from local storage.
|
|
1685
|
+
* ```
|
|
1686
|
+
*/
|
|
1687
|
+
function useReadLocalStorage(key, options = {}) {
|
|
1688
|
+
let { initializeWithValue = true } = options;
|
|
1689
|
+
if (IS_SERVER$4) initializeWithValue = false;
|
|
1690
|
+
const deserializer = (0, react.useCallback)((value) => {
|
|
1691
|
+
if (options.deserializer) return options.deserializer(value);
|
|
1692
|
+
if (value === "undefined") return void 0;
|
|
1693
|
+
let parsed;
|
|
1694
|
+
try {
|
|
1695
|
+
parsed = JSON.parse(value);
|
|
1696
|
+
} catch (error) {
|
|
1697
|
+
console.error("Error parsing JSON:", error);
|
|
1698
|
+
return null;
|
|
1699
|
+
}
|
|
1700
|
+
return parsed;
|
|
1701
|
+
}, [options]);
|
|
1702
|
+
const readValue = (0, react.useCallback)(() => {
|
|
1703
|
+
if (IS_SERVER$4) return null;
|
|
1704
|
+
try {
|
|
1705
|
+
const raw = window.localStorage.getItem(key);
|
|
1706
|
+
return raw ? deserializer(raw) : null;
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
console.warn(`Error reading localStorage key “${key}”:`, error);
|
|
1709
|
+
return null;
|
|
1710
|
+
}
|
|
1711
|
+
}, [key, deserializer]);
|
|
1712
|
+
const [storedValue, setStoredValue] = (0, react.useState)(() => {
|
|
1713
|
+
if (initializeWithValue) return readValue();
|
|
1714
|
+
return void 0;
|
|
1715
|
+
});
|
|
1716
|
+
(0, react.useEffect)(() => {
|
|
1717
|
+
setStoredValue(readValue());
|
|
1718
|
+
}, [key]);
|
|
1719
|
+
const handleStorageChange = (0, react.useCallback)((event) => {
|
|
1720
|
+
if (event.key && event.key !== key) return;
|
|
1721
|
+
setStoredValue(readValue());
|
|
1722
|
+
}, [key, readValue]);
|
|
1723
|
+
useEventListener("storage", handleStorageChange);
|
|
1724
|
+
useEventListener("local-storage", handleStorageChange);
|
|
1725
|
+
return storedValue;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
//#endregion
|
|
1729
|
+
//#region src/useResizeObserver/useResizeObserver.ts
|
|
1730
|
+
const initialSize = {
|
|
1731
|
+
width: void 0,
|
|
1732
|
+
height: void 0
|
|
1733
|
+
};
|
|
1734
|
+
/**
|
|
1735
|
+
* Custom hook that observes the size of an element using the [`ResizeObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
|
|
1736
|
+
* @template T - The type of the element to observe.
|
|
1737
|
+
* @param {UseResizeObserverOptions<T>} options - The options for the ResizeObserver.
|
|
1738
|
+
* @returns {Size} - The size of the observed element.
|
|
1739
|
+
* @public
|
|
1740
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-resize-observer)
|
|
1741
|
+
* @example
|
|
1742
|
+
* ```tsx
|
|
1743
|
+
* const myRef = useRef(null);
|
|
1744
|
+
* const { width = 0, height = 0 } = useResizeObserver({
|
|
1745
|
+
* ref: myRef,
|
|
1746
|
+
* box: 'content-box',
|
|
1747
|
+
* });
|
|
1748
|
+
*
|
|
1749
|
+
* <div ref={myRef}>Hello, world!</div>
|
|
1750
|
+
* ```
|
|
1751
|
+
*/
|
|
1752
|
+
function useResizeObserver(options) {
|
|
1753
|
+
const { ref, box = "content-box" } = options;
|
|
1754
|
+
const [{ width, height }, setSize] = (0, react.useState)(initialSize);
|
|
1755
|
+
const isMounted = useIsMounted();
|
|
1756
|
+
const previousSize = (0, react.useRef)({ ...initialSize });
|
|
1757
|
+
const onResize = (0, react.useRef)(void 0);
|
|
1758
|
+
onResize.current = options.onResize;
|
|
1759
|
+
(0, react.useEffect)(() => {
|
|
1760
|
+
if (!ref.current) return;
|
|
1761
|
+
if (typeof window === "undefined" || !("ResizeObserver" in window)) return;
|
|
1762
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
1763
|
+
if (!entry) return;
|
|
1764
|
+
const boxProp = box === "border-box" ? "borderBoxSize" : box === "device-pixel-content-box" ? "devicePixelContentBoxSize" : "contentBoxSize";
|
|
1765
|
+
const newWidth = extractSize(entry, boxProp, "inlineSize");
|
|
1766
|
+
const newHeight = extractSize(entry, boxProp, "blockSize");
|
|
1767
|
+
const hasChanged = previousSize.current.width !== newWidth || previousSize.current.height !== newHeight;
|
|
1768
|
+
if (hasChanged) {
|
|
1769
|
+
const newSize = {
|
|
1770
|
+
width: newWidth,
|
|
1771
|
+
height: newHeight
|
|
1772
|
+
};
|
|
1773
|
+
previousSize.current.width = newWidth;
|
|
1774
|
+
previousSize.current.height = newHeight;
|
|
1775
|
+
if (onResize.current) onResize.current(newSize);
|
|
1776
|
+
else if (isMounted()) setSize(newSize);
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
observer.observe(ref.current, { box });
|
|
1780
|
+
return () => {
|
|
1781
|
+
observer.disconnect();
|
|
1782
|
+
};
|
|
1783
|
+
}, [
|
|
1784
|
+
box,
|
|
1785
|
+
ref,
|
|
1786
|
+
isMounted
|
|
1787
|
+
]);
|
|
1788
|
+
return {
|
|
1789
|
+
width,
|
|
1790
|
+
height
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
function extractSize(entry, box, sizeType) {
|
|
1794
|
+
if (!entry[box]) {
|
|
1795
|
+
if (box === "contentBoxSize") return entry.contentRect[sizeType === "inlineSize" ? "width" : "height"];
|
|
1796
|
+
return void 0;
|
|
1797
|
+
}
|
|
1798
|
+
return Array.isArray(entry[box]) ? entry[box][0][sizeType] : entry[box][sizeType];
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
//#endregion
|
|
1802
|
+
//#region src/useScreen/useScreen.ts
|
|
1803
|
+
const IS_SERVER$3 = typeof window === "undefined";
|
|
1804
|
+
/**
|
|
1805
|
+
* Custom hook that tracks the [`screen`](https://developer.mozilla.org/en-US/docs/Web/API/Window/screen) dimensions and properties.
|
|
1806
|
+
* @param {?UseScreenOptions} [options] - The options for customizing the behavior of the hook (optional).
|
|
1807
|
+
* @returns {Screen | undefined} The current `Screen` object representing the screen dimensions and properties, or `undefined` if not available.
|
|
1808
|
+
* @public
|
|
1809
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-screen)
|
|
1810
|
+
* @example
|
|
1811
|
+
* ```tsx
|
|
1812
|
+
* const currentScreen = useScreen();
|
|
1813
|
+
* // Access properties of the current screen, such as width and height.
|
|
1814
|
+
* ```
|
|
1815
|
+
*/
|
|
1816
|
+
function useScreen(options = {}) {
|
|
1817
|
+
let { initializeWithValue = true } = options;
|
|
1818
|
+
if (IS_SERVER$3) initializeWithValue = false;
|
|
1819
|
+
const readScreen = () => {
|
|
1820
|
+
if (IS_SERVER$3) return void 0;
|
|
1821
|
+
return window.screen;
|
|
1822
|
+
};
|
|
1823
|
+
const [screen, setScreen] = (0, react.useState)(() => {
|
|
1824
|
+
if (initializeWithValue) return readScreen();
|
|
1825
|
+
return void 0;
|
|
1826
|
+
});
|
|
1827
|
+
const debouncedSetScreen = useDebounceCallback(setScreen, options.debounceDelay);
|
|
1828
|
+
function handleSize() {
|
|
1829
|
+
const newScreen = readScreen();
|
|
1830
|
+
const setSize = options.debounceDelay ? debouncedSetScreen : setScreen;
|
|
1831
|
+
if (newScreen) {
|
|
1832
|
+
const { width, height, availHeight, availWidth, colorDepth, orientation, pixelDepth } = newScreen;
|
|
1833
|
+
setSize({
|
|
1834
|
+
width,
|
|
1835
|
+
height,
|
|
1836
|
+
availHeight,
|
|
1837
|
+
availWidth,
|
|
1838
|
+
colorDepth,
|
|
1839
|
+
orientation,
|
|
1840
|
+
pixelDepth
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
useEventListener("resize", handleSize);
|
|
1845
|
+
useIsomorphicLayoutEffect(() => {
|
|
1846
|
+
handleSize();
|
|
1847
|
+
}, []);
|
|
1848
|
+
return screen;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
//#endregion
|
|
1852
|
+
//#region src/useScript/useScript.ts
|
|
1853
|
+
const cachedScriptStatuses = new Map();
|
|
1854
|
+
/**
|
|
1855
|
+
* Gets the script element with the specified source URL.
|
|
1856
|
+
* @param {string} src - The source URL of the script to get.
|
|
1857
|
+
* @returns {{ node: HTMLScriptElement | null, status: UseScriptStatus | undefined }} The script element and its loading status.
|
|
1858
|
+
* @public
|
|
1859
|
+
* @example
|
|
1860
|
+
* ```tsx
|
|
1861
|
+
* const script = getScriptNode(src);
|
|
1862
|
+
* ```
|
|
1863
|
+
*/
|
|
1864
|
+
function getScriptNode(src) {
|
|
1865
|
+
const node = document.querySelector(`script[src="${src}"]`);
|
|
1866
|
+
const status = node?.getAttribute("data-status");
|
|
1867
|
+
return {
|
|
1868
|
+
node,
|
|
1869
|
+
status
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Custom hook that dynamically loads scripts and tracking their loading status.
|
|
1874
|
+
* @param {string | null} src - The source URL of the script to load. Set to `null` or omit to prevent loading (optional).
|
|
1875
|
+
* @param {UseScriptOptions} [options] - Additional options for controlling script loading (optional).
|
|
1876
|
+
* @returns {UseScriptStatus} The status of the script loading, which can be one of 'idle', 'loading', 'ready', or 'error'.
|
|
1877
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-script)
|
|
1878
|
+
* @example
|
|
1879
|
+
* const scriptStatus = useScript('https://example.com/script.js', { removeOnUnmount: true });
|
|
1880
|
+
* // Access the status of the script loading (e.g., 'loading', 'ready', 'error').
|
|
1881
|
+
*/
|
|
1882
|
+
function useScript(src, options) {
|
|
1883
|
+
const [status, setStatus] = (0, react.useState)(() => {
|
|
1884
|
+
if (!src || options?.shouldPreventLoad) return "idle";
|
|
1885
|
+
if (typeof window === "undefined") return "loading";
|
|
1886
|
+
return cachedScriptStatuses.get(src) ?? "loading";
|
|
1887
|
+
});
|
|
1888
|
+
(0, react.useEffect)(() => {
|
|
1889
|
+
if (!src || options?.shouldPreventLoad) return;
|
|
1890
|
+
const cachedScriptStatus = cachedScriptStatuses.get(src);
|
|
1891
|
+
if (cachedScriptStatus === "ready" || cachedScriptStatus === "error") {
|
|
1892
|
+
setStatus(cachedScriptStatus);
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
const script = getScriptNode(src);
|
|
1896
|
+
let scriptNode = script.node;
|
|
1897
|
+
if (!scriptNode) {
|
|
1898
|
+
scriptNode = document.createElement("script");
|
|
1899
|
+
scriptNode.src = src;
|
|
1900
|
+
scriptNode.async = true;
|
|
1901
|
+
if (options?.id) scriptNode.id = options.id;
|
|
1902
|
+
scriptNode.setAttribute("data-status", "loading");
|
|
1903
|
+
document.body.appendChild(scriptNode);
|
|
1904
|
+
const setAttributeFromEvent = (event) => {
|
|
1905
|
+
const scriptStatus = event.type === "load" ? "ready" : "error";
|
|
1906
|
+
scriptNode?.setAttribute("data-status", scriptStatus);
|
|
1907
|
+
};
|
|
1908
|
+
scriptNode.addEventListener("load", setAttributeFromEvent);
|
|
1909
|
+
scriptNode.addEventListener("error", setAttributeFromEvent);
|
|
1910
|
+
} else setStatus(script.status ?? cachedScriptStatus ?? "loading");
|
|
1911
|
+
const setStateFromEvent = (event) => {
|
|
1912
|
+
const newStatus = event.type === "load" ? "ready" : "error";
|
|
1913
|
+
setStatus(newStatus);
|
|
1914
|
+
cachedScriptStatuses.set(src, newStatus);
|
|
1915
|
+
};
|
|
1916
|
+
scriptNode.addEventListener("load", setStateFromEvent);
|
|
1917
|
+
scriptNode.addEventListener("error", setStateFromEvent);
|
|
1918
|
+
return () => {
|
|
1919
|
+
if (scriptNode) {
|
|
1920
|
+
scriptNode.removeEventListener("load", setStateFromEvent);
|
|
1921
|
+
scriptNode.removeEventListener("error", setStateFromEvent);
|
|
1922
|
+
}
|
|
1923
|
+
if (scriptNode && options?.removeOnUnmount) {
|
|
1924
|
+
scriptNode.remove();
|
|
1925
|
+
cachedScriptStatuses.delete(src);
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
}, [
|
|
1929
|
+
src,
|
|
1930
|
+
options?.shouldPreventLoad,
|
|
1931
|
+
options?.removeOnUnmount,
|
|
1932
|
+
options?.id
|
|
1933
|
+
]);
|
|
1934
|
+
return status;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
//#endregion
|
|
1938
|
+
//#region src/useScrollLock/useScrollLock.ts
|
|
1939
|
+
const IS_SERVER$2 = typeof window === "undefined";
|
|
1940
|
+
/**
|
|
1941
|
+
* A custom hook that locks and unlocks scroll.
|
|
1942
|
+
* @param {UseScrollLockOptions} [options] - Options to configure the hook, by default it will lock the scroll automatically.
|
|
1943
|
+
* @returns {UseScrollLockReturn} - An object containing the lock and unlock functions.
|
|
1944
|
+
* @public
|
|
1945
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-scroll-lock)
|
|
1946
|
+
* @example
|
|
1947
|
+
* ```tsx
|
|
1948
|
+
* // Lock the scroll when the modal is mounted, and unlock it when it's unmounted
|
|
1949
|
+
* useScrollLock()
|
|
1950
|
+
* ```
|
|
1951
|
+
* @example
|
|
1952
|
+
* ```tsx
|
|
1953
|
+
* // Manually lock and unlock the scroll
|
|
1954
|
+
* const { lock, unlock } = useScrollLock({ autoLock: false })
|
|
1955
|
+
*
|
|
1956
|
+
* return (
|
|
1957
|
+
* <div>
|
|
1958
|
+
* <button onClick={lock}>Lock</button>
|
|
1959
|
+
* <button onClick={unlock}>Unlock</button>
|
|
1960
|
+
* </div>
|
|
1961
|
+
* )
|
|
1962
|
+
* ```
|
|
1963
|
+
*/
|
|
1964
|
+
function useScrollLock(options = {}) {
|
|
1965
|
+
const { autoLock = true, lockTarget, widthReflow = true } = options;
|
|
1966
|
+
const [isLocked, setIsLocked] = (0, react.useState)(false);
|
|
1967
|
+
const target = (0, react.useRef)(null);
|
|
1968
|
+
const originalStyle = (0, react.useRef)(null);
|
|
1969
|
+
const lock = () => {
|
|
1970
|
+
if (target.current) {
|
|
1971
|
+
const { overflow, paddingRight } = target.current.style;
|
|
1972
|
+
originalStyle.current = {
|
|
1973
|
+
overflow,
|
|
1974
|
+
paddingRight
|
|
1975
|
+
};
|
|
1976
|
+
if (widthReflow) {
|
|
1977
|
+
const offsetWidth = target.current === document.body ? window.innerWidth : target.current.offsetWidth;
|
|
1978
|
+
const currentPaddingRight = parseInt(window.getComputedStyle(target.current).paddingRight, 10) || 0;
|
|
1979
|
+
const scrollbarWidth = offsetWidth - target.current.scrollWidth;
|
|
1980
|
+
target.current.style.paddingRight = `${scrollbarWidth + currentPaddingRight}px`;
|
|
1981
|
+
}
|
|
1982
|
+
target.current.style.overflow = "hidden";
|
|
1983
|
+
setIsLocked(true);
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
const unlock = () => {
|
|
1987
|
+
if (target.current && originalStyle.current) {
|
|
1988
|
+
target.current.style.overflow = originalStyle.current.overflow;
|
|
1989
|
+
if (widthReflow) target.current.style.paddingRight = originalStyle.current.paddingRight;
|
|
1990
|
+
}
|
|
1991
|
+
setIsLocked(false);
|
|
1992
|
+
};
|
|
1993
|
+
useIsomorphicLayoutEffect(() => {
|
|
1994
|
+
if (IS_SERVER$2) return;
|
|
1995
|
+
if (lockTarget) target.current = typeof lockTarget === "string" ? document.querySelector(lockTarget) : lockTarget;
|
|
1996
|
+
if (!target.current) target.current = document.body;
|
|
1997
|
+
if (autoLock) lock();
|
|
1998
|
+
return () => {
|
|
1999
|
+
unlock();
|
|
2000
|
+
};
|
|
2001
|
+
}, [
|
|
2002
|
+
autoLock,
|
|
2003
|
+
lockTarget,
|
|
2004
|
+
widthReflow
|
|
2005
|
+
]);
|
|
2006
|
+
return {
|
|
2007
|
+
isLocked,
|
|
2008
|
+
lock,
|
|
2009
|
+
unlock
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
//#endregion
|
|
2014
|
+
//#region src/useSessionStorage/useSessionStorage.ts
|
|
2015
|
+
const IS_SERVER$1 = typeof window === "undefined";
|
|
2016
|
+
/**
|
|
2017
|
+
* Custom hook that uses the [`sessionStorage API`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to persist state across page reloads.
|
|
2018
|
+
* @template T - The type of the state to be stored in session storage.
|
|
2019
|
+
* @param {string} key - The key under which the value will be stored in session storage.
|
|
2020
|
+
* @param {T | (() => T)} initialValue - The initial value of the state or a function that returns the initial value.
|
|
2021
|
+
* @param {?UseSessionStorageOptions<T>} [options] - Options for customizing the behavior of serialization and deserialization (optional).
|
|
2022
|
+
* @returns {[T, Dispatch<SetStateAction<T>>, () => void]} A tuple containing the stored value, a function to set the value and a function to remove the key from storage.
|
|
2023
|
+
* @public
|
|
2024
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-session-storage)
|
|
2025
|
+
* @example
|
|
2026
|
+
* ```tsx
|
|
2027
|
+
* const [count, setCount, removeCount] = useSessionStorage('count', 0);
|
|
2028
|
+
* // Access the `count` value, the `setCount` function to update it and `removeCount` function to remove the key from storage.
|
|
2029
|
+
* ```
|
|
2030
|
+
*/
|
|
2031
|
+
function useSessionStorage(key, initialValue, options = {}) {
|
|
2032
|
+
const { initializeWithValue = true } = options;
|
|
2033
|
+
const serializer = (0, react.useCallback)((value) => {
|
|
2034
|
+
if (options.serializer) return options.serializer(value);
|
|
2035
|
+
return JSON.stringify(value);
|
|
2036
|
+
}, [options]);
|
|
2037
|
+
const deserializer = (0, react.useCallback)((value) => {
|
|
2038
|
+
if (options.deserializer) return options.deserializer(value);
|
|
2039
|
+
if (value === "undefined") return void 0;
|
|
2040
|
+
const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;
|
|
2041
|
+
let parsed;
|
|
2042
|
+
try {
|
|
2043
|
+
parsed = JSON.parse(value);
|
|
2044
|
+
} catch (error) {
|
|
2045
|
+
console.error("Error parsing JSON:", error);
|
|
2046
|
+
return defaultValue;
|
|
2047
|
+
}
|
|
2048
|
+
return parsed;
|
|
2049
|
+
}, [options, initialValue]);
|
|
2050
|
+
const readValue = (0, react.useCallback)(() => {
|
|
2051
|
+
const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue;
|
|
2052
|
+
if (IS_SERVER$1) return initialValueToUse;
|
|
2053
|
+
try {
|
|
2054
|
+
const raw = window.sessionStorage.getItem(key);
|
|
2055
|
+
return raw ? deserializer(raw) : initialValueToUse;
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
console.warn(`Error reading sessionStorage key “${key}”:`, error);
|
|
2058
|
+
return initialValueToUse;
|
|
2059
|
+
}
|
|
2060
|
+
}, [
|
|
2061
|
+
initialValue,
|
|
2062
|
+
key,
|
|
2063
|
+
deserializer
|
|
2064
|
+
]);
|
|
2065
|
+
const [storedValue, setStoredValue] = (0, react.useState)(() => {
|
|
2066
|
+
if (initializeWithValue) return readValue();
|
|
2067
|
+
return initialValue instanceof Function ? initialValue() : initialValue;
|
|
2068
|
+
});
|
|
2069
|
+
const setValue = useEventCallback((value) => {
|
|
2070
|
+
if (IS_SERVER$1) console.warn(`Tried setting sessionStorage key “${key}” even though environment is not a client`);
|
|
2071
|
+
try {
|
|
2072
|
+
const newValue = value instanceof Function ? value(readValue()) : value;
|
|
2073
|
+
window.sessionStorage.setItem(key, serializer(newValue));
|
|
2074
|
+
setStoredValue(newValue);
|
|
2075
|
+
window.dispatchEvent(new StorageEvent("session-storage", { key }));
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
console.warn(`Error setting sessionStorage key “${key}”:`, error);
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
const removeValue = useEventCallback(() => {
|
|
2081
|
+
if (IS_SERVER$1) console.warn(`Tried removing sessionStorage key “${key}” even though environment is not a client`);
|
|
2082
|
+
const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;
|
|
2083
|
+
window.sessionStorage.removeItem(key);
|
|
2084
|
+
setStoredValue(defaultValue);
|
|
2085
|
+
window.dispatchEvent(new StorageEvent("session-storage", { key }));
|
|
2086
|
+
});
|
|
2087
|
+
(0, react.useEffect)(() => {
|
|
2088
|
+
setStoredValue(readValue());
|
|
2089
|
+
}, [key]);
|
|
2090
|
+
const handleStorageChange = (0, react.useCallback)((event) => {
|
|
2091
|
+
if (event.key && event.key !== key) return;
|
|
2092
|
+
setStoredValue(readValue());
|
|
2093
|
+
}, [key, readValue]);
|
|
2094
|
+
useEventListener("storage", handleStorageChange);
|
|
2095
|
+
useEventListener("session-storage", handleStorageChange);
|
|
2096
|
+
return [
|
|
2097
|
+
storedValue,
|
|
2098
|
+
setValue,
|
|
2099
|
+
removeValue
|
|
2100
|
+
];
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
//#endregion
|
|
2104
|
+
//#region src/useSet/useSet.ts
|
|
2105
|
+
/**
|
|
2106
|
+
* Custom hook that manages a [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) state with setter actions.
|
|
2107
|
+
* @template T - The type of elements in the set.
|
|
2108
|
+
* @param {Set<T> | T[]} [initialValue] - The initial value of the set as a Set or an array of values (optional).
|
|
2109
|
+
* @returns {UseSetReturn<T>} A tuple containing the set state and actions to interact with the set.
|
|
2110
|
+
* @public
|
|
2111
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-set)
|
|
2112
|
+
* @example
|
|
2113
|
+
* ```tsx
|
|
2114
|
+
* const [set, setActions] = useSet<string>(['hello']);
|
|
2115
|
+
* // Access the `set` state and use `setActions` to add, remove, toggle, or reset values.
|
|
2116
|
+
* ```
|
|
2117
|
+
*/
|
|
2118
|
+
function useSet(initialValue = []) {
|
|
2119
|
+
const initialSet = initialValue instanceof Set ? initialValue : new Set(initialValue);
|
|
2120
|
+
const [set, setSet] = (0, react.useState)(initialSet);
|
|
2121
|
+
const actions = {
|
|
2122
|
+
add: (0, react.useCallback)((value) => {
|
|
2123
|
+
setSet((prev) => {
|
|
2124
|
+
const copy = new Set(prev);
|
|
2125
|
+
copy.add(value);
|
|
2126
|
+
return copy;
|
|
2127
|
+
});
|
|
2128
|
+
}, []),
|
|
2129
|
+
remove: (0, react.useCallback)((value) => {
|
|
2130
|
+
setSet((prev) => {
|
|
2131
|
+
const copy = new Set(prev);
|
|
2132
|
+
copy.delete(value);
|
|
2133
|
+
return copy;
|
|
2134
|
+
});
|
|
2135
|
+
}, []),
|
|
2136
|
+
toggle: (0, react.useCallback)((value) => {
|
|
2137
|
+
setSet((prev) => {
|
|
2138
|
+
const copy = new Set(prev);
|
|
2139
|
+
if (copy.has(value)) copy.delete(value);
|
|
2140
|
+
else copy.add(value);
|
|
2141
|
+
return copy;
|
|
2142
|
+
});
|
|
2143
|
+
}, []),
|
|
2144
|
+
has: (0, react.useCallback)((value) => set.has(value), [set]),
|
|
2145
|
+
clear: (0, react.useCallback)(() => {
|
|
2146
|
+
setSet(() => new Set());
|
|
2147
|
+
}, []),
|
|
2148
|
+
reset: (0, react.useCallback)(() => {
|
|
2149
|
+
setSet(() => new Set(initialSet));
|
|
2150
|
+
}, [])
|
|
2151
|
+
};
|
|
2152
|
+
return [set, actions];
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
//#endregion
|
|
2156
|
+
//#region src/useStateList/useStateList.ts
|
|
2157
|
+
/**
|
|
2158
|
+
* Custom hook for navigating through a list of states.
|
|
2159
|
+
* Useful for step-by-step flows, carousels, or multi-step forms.
|
|
2160
|
+
* @template T - The type of the state values.
|
|
2161
|
+
* @param {T[]} stateSet - The array of possible states.
|
|
2162
|
+
* @returns {UseStateListReturn<T>} The current state and navigation functions.
|
|
2163
|
+
* @public
|
|
2164
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-state-list)
|
|
2165
|
+
* @example
|
|
2166
|
+
* ```tsx
|
|
2167
|
+
* const states = ['step1', 'step2', 'step3'];
|
|
2168
|
+
* const { state, next, prev, isFirst, isLast } = useStateList(states);
|
|
2169
|
+
*
|
|
2170
|
+
* return (
|
|
2171
|
+
* <div>
|
|
2172
|
+
* <p>Current: {state}</p>
|
|
2173
|
+
* <button onClick={prev} disabled={isFirst}>Back</button>
|
|
2174
|
+
* <button onClick={next} disabled={isLast}>Next</button>
|
|
2175
|
+
* </div>
|
|
2176
|
+
* );
|
|
2177
|
+
* ```
|
|
2178
|
+
*/
|
|
2179
|
+
function useStateList(stateSet) {
|
|
2180
|
+
if (stateSet.length === 0) throw new Error("useStateList requires at least one state value");
|
|
2181
|
+
const stateSetRef = (0, react.useRef)(stateSet);
|
|
2182
|
+
const [currentIndex, setCurrentIndex] = (0, react.useState)(0);
|
|
2183
|
+
const state = stateSetRef.current[currentIndex];
|
|
2184
|
+
const isFirst = currentIndex === 0;
|
|
2185
|
+
const isLast = currentIndex === stateSetRef.current.length - 1;
|
|
2186
|
+
const prev = (0, react.useCallback)(() => {
|
|
2187
|
+
setCurrentIndex((index) => index > 0 ? index - 1 : index);
|
|
2188
|
+
}, []);
|
|
2189
|
+
const next = (0, react.useCallback)(() => {
|
|
2190
|
+
setCurrentIndex((index) => index < stateSetRef.current.length - 1 ? index + 1 : index);
|
|
2191
|
+
}, []);
|
|
2192
|
+
const setStateAt = (0, react.useCallback)((index) => {
|
|
2193
|
+
if (index >= 0 && index < stateSetRef.current.length) setCurrentIndex(index);
|
|
2194
|
+
}, []);
|
|
2195
|
+
const setState = (0, react.useCallback)((newState) => {
|
|
2196
|
+
const index = stateSetRef.current.indexOf(newState);
|
|
2197
|
+
if (index !== -1) setCurrentIndex(index);
|
|
2198
|
+
}, []);
|
|
2199
|
+
return {
|
|
2200
|
+
state,
|
|
2201
|
+
currentIndex,
|
|
2202
|
+
isFirst,
|
|
2203
|
+
isLast,
|
|
2204
|
+
prev,
|
|
2205
|
+
next,
|
|
2206
|
+
setStateAt,
|
|
2207
|
+
setState
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
//#endregion
|
|
2212
|
+
//#region src/useStep/useStep.ts
|
|
2213
|
+
/**
|
|
2214
|
+
* Custom hook that manages and navigates between steps in a multi-step process.
|
|
2215
|
+
* @param {number} maxStep - The maximum step in the process.
|
|
2216
|
+
* @returns {[number, UseStepActions]} An tuple containing the current step and helper functions for navigating steps.
|
|
2217
|
+
* @public
|
|
2218
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-step)
|
|
2219
|
+
* @example
|
|
2220
|
+
* ```tsx
|
|
2221
|
+
* const [currentStep, { goToNextStep, goToPrevStep, reset, canGoToNextStep, canGoToPrevStep, setStep }] = useStep(3);
|
|
2222
|
+
* // Access and use the current step and provided helper functions.
|
|
2223
|
+
* ```
|
|
2224
|
+
*/
|
|
2225
|
+
function useStep(maxStep) {
|
|
2226
|
+
const [currentStep, setCurrentStep] = (0, react.useState)(1);
|
|
2227
|
+
const canGoToNextStep = currentStep + 1 <= maxStep;
|
|
2228
|
+
const canGoToPrevStep = currentStep - 1 > 0;
|
|
2229
|
+
const setStep = (0, react.useCallback)((step) => {
|
|
2230
|
+
const newStep = step instanceof Function ? step(currentStep) : step;
|
|
2231
|
+
if (newStep >= 1 && newStep <= maxStep) {
|
|
2232
|
+
setCurrentStep(newStep);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
throw new Error("Step not valid");
|
|
2236
|
+
}, [maxStep, currentStep]);
|
|
2237
|
+
const goToNextStep = (0, react.useCallback)(() => {
|
|
2238
|
+
if (canGoToNextStep) setCurrentStep((step) => step + 1);
|
|
2239
|
+
}, [canGoToNextStep]);
|
|
2240
|
+
const goToPrevStep = (0, react.useCallback)(() => {
|
|
2241
|
+
if (canGoToPrevStep) setCurrentStep((step) => step - 1);
|
|
2242
|
+
}, [canGoToPrevStep]);
|
|
2243
|
+
const reset = (0, react.useCallback)(() => {
|
|
2244
|
+
setCurrentStep(1);
|
|
2245
|
+
}, []);
|
|
2246
|
+
return [currentStep, {
|
|
2247
|
+
goToNextStep,
|
|
2248
|
+
goToPrevStep,
|
|
2249
|
+
canGoToNextStep,
|
|
2250
|
+
canGoToPrevStep,
|
|
2251
|
+
setStep,
|
|
2252
|
+
reset
|
|
2253
|
+
}];
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
//#endregion
|
|
2257
|
+
//#region src/useTernaryDarkMode/useTernaryDarkMode.ts
|
|
2258
|
+
const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)";
|
|
2259
|
+
const LOCAL_STORAGE_KEY = "usehooks-ts-ternary-dark-mode";
|
|
2260
|
+
/**
|
|
2261
|
+
* Custom hook that manages ternary (system, dark, light) dark mode with local storage support.
|
|
2262
|
+
* @param {?TernaryDarkModeOptions | string} [options] - Options or the local storage key for the hook.
|
|
2263
|
+
* @returns {TernaryDarkModeReturn} An object containing the dark mode state and helper functions.
|
|
2264
|
+
* @public
|
|
2265
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-ternary-dark-mode)
|
|
2266
|
+
* @example
|
|
2267
|
+
* ```tsx
|
|
2268
|
+
* const { isDarkMode, ternaryDarkMode, setTernaryDarkMode, toggleTernaryDarkMode } = useTernaryDarkMode({ defaultValue: 'dark' });
|
|
2269
|
+
* // Access and use the dark mode state and provided helper functions.
|
|
2270
|
+
* ```
|
|
2271
|
+
*/
|
|
2272
|
+
function useTernaryDarkMode({ defaultValue = "system", localStorageKey = LOCAL_STORAGE_KEY, initializeWithValue = true } = {}) {
|
|
2273
|
+
const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY, { initializeWithValue });
|
|
2274
|
+
const [mode, setMode] = useLocalStorage(localStorageKey, defaultValue, { initializeWithValue });
|
|
2275
|
+
const isDarkMode = mode === "dark" || mode === "system" && isDarkOS;
|
|
2276
|
+
const toggleTernaryDarkMode = () => {
|
|
2277
|
+
const modes = [
|
|
2278
|
+
"light",
|
|
2279
|
+
"system",
|
|
2280
|
+
"dark"
|
|
2281
|
+
];
|
|
2282
|
+
setMode((prevMode) => {
|
|
2283
|
+
const prevIndex = modes.indexOf(prevMode);
|
|
2284
|
+
const nextIndex = (prevIndex + 1) % modes.length;
|
|
2285
|
+
return modes[nextIndex];
|
|
2286
|
+
});
|
|
2287
|
+
};
|
|
2288
|
+
return {
|
|
2289
|
+
isDarkMode,
|
|
2290
|
+
ternaryDarkMode: mode,
|
|
2291
|
+
setTernaryDarkMode: setMode,
|
|
2292
|
+
toggleTernaryDarkMode
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
//#endregion
|
|
2297
|
+
//#region src/useThrottle/useThrottle.ts
|
|
2298
|
+
/**
|
|
2299
|
+
* Custom hook that throttles a function, ensuring it is only called at most once per wait period.
|
|
2300
|
+
* @template T - The type of the function being throttled.
|
|
2301
|
+
* @param {T} fn - The function to throttle.
|
|
2302
|
+
* @param {number} wait - The number of milliseconds to throttle invocations to.
|
|
2303
|
+
* @returns {T} The throttled function.
|
|
2304
|
+
* @public
|
|
2305
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-throttle-fn)
|
|
2306
|
+
* @example
|
|
2307
|
+
* ```tsx
|
|
2308
|
+
* const throttledSave = useThrottleFn(saveToDatabase, 1000);
|
|
2309
|
+
* // Calling throttledSave() multiple times rapidly will only execute saveToDatabase once per second
|
|
2310
|
+
* ```
|
|
2311
|
+
*/
|
|
2312
|
+
function useThrottleFn(fn, wait) {
|
|
2313
|
+
const timeoutRef = (0, react.useRef)(null);
|
|
2314
|
+
const lastCalledRef = (0, react.useRef)(0);
|
|
2315
|
+
const throttledFn = (0, react.useCallback)((...args) => {
|
|
2316
|
+
const now = Date.now();
|
|
2317
|
+
const remaining = wait - (now - lastCalledRef.current);
|
|
2318
|
+
if (remaining <= 0 || remaining > wait) {
|
|
2319
|
+
if (timeoutRef.current) {
|
|
2320
|
+
clearTimeout(timeoutRef.current);
|
|
2321
|
+
timeoutRef.current = null;
|
|
2322
|
+
}
|
|
2323
|
+
lastCalledRef.current = now;
|
|
2324
|
+
return fn(...args);
|
|
2325
|
+
}
|
|
2326
|
+
}, [fn, wait]);
|
|
2327
|
+
return throttledFn;
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Custom hook that throttles a value, ensuring updates occur at most once per wait period.
|
|
2331
|
+
* @template T - The type of the value being throttled.
|
|
2332
|
+
* @param {T} value - The value to throttle.
|
|
2333
|
+
* @param {number} wait - The number of milliseconds to throttle updates to.
|
|
2334
|
+
* @returns {T} The throttled value.
|
|
2335
|
+
* @public
|
|
2336
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-throttle)
|
|
2337
|
+
* @example
|
|
2338
|
+
* ```tsx
|
|
2339
|
+
* const throttledSearchTerm = useThrottle(searchTerm, 300);
|
|
2340
|
+
* // throttledSearchTerm will only update 300ms after the last change to searchTerm
|
|
2341
|
+
* ```
|
|
2342
|
+
*/
|
|
2343
|
+
function useThrottle(value, wait) {
|
|
2344
|
+
const [throttledValue, setThrottledValue] = (0, react.useState)(value);
|
|
2345
|
+
const lastUpdatedRef = (0, react.useRef)(0);
|
|
2346
|
+
const previousValueRef = (0, react.useRef)(value);
|
|
2347
|
+
const now = Date.now();
|
|
2348
|
+
const remaining = wait - (now - lastUpdatedRef.current);
|
|
2349
|
+
if (remaining <= 0 || remaining > wait || previousValueRef.current !== value) {
|
|
2350
|
+
if (previousValueRef.current !== value) {
|
|
2351
|
+
setThrottledValue(value);
|
|
2352
|
+
lastUpdatedRef.current = now;
|
|
2353
|
+
previousValueRef.current = value;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
return throttledValue;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
//#endregion
|
|
2360
|
+
//#region src/useTimeout/useTimeout.ts
|
|
2361
|
+
/**
|
|
2362
|
+
* Custom hook that handles timeouts in React components using the [`setTimeout API`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout).
|
|
2363
|
+
* @param {() => void} callback - The function to be executed when the timeout elapses.
|
|
2364
|
+
* @param {number | null} delay - The duration (in milliseconds) for the timeout. Set to `null` to clear the timeout.
|
|
2365
|
+
* @returns {void} This hook does not return anything.
|
|
2366
|
+
* @public
|
|
2367
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-timeout)
|
|
2368
|
+
* @example
|
|
2369
|
+
* ```tsx
|
|
2370
|
+
* // Usage of useTimeout hook
|
|
2371
|
+
* useTimeout(() => {
|
|
2372
|
+
* // Code to be executed after the specified delay
|
|
2373
|
+
* }, 1000); // Set a timeout of 1000 milliseconds (1 second)
|
|
2374
|
+
* ```
|
|
2375
|
+
*/
|
|
2376
|
+
function useTimeout(callback, delay) {
|
|
2377
|
+
const savedCallback = (0, react.useRef)(callback);
|
|
2378
|
+
useIsomorphicLayoutEffect(() => {
|
|
2379
|
+
savedCallback.current = callback;
|
|
2380
|
+
}, [callback]);
|
|
2381
|
+
(0, react.useEffect)(() => {
|
|
2382
|
+
if (!delay && delay !== 0) return;
|
|
2383
|
+
const id = setTimeout(() => {
|
|
2384
|
+
savedCallback.current();
|
|
2385
|
+
}, delay);
|
|
2386
|
+
return () => {
|
|
2387
|
+
clearTimeout(id);
|
|
2388
|
+
};
|
|
2389
|
+
}, [delay]);
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
//#endregion
|
|
2393
|
+
//#region src/useToggle/useToggle.ts
|
|
2394
|
+
/**
|
|
2395
|
+
* Custom hook that manages a boolean toggle state in React components.
|
|
2396
|
+
* @param {boolean} [defaultValue] - The initial value for the toggle state.
|
|
2397
|
+
* @returns {[boolean, () => void, Dispatch<SetStateAction<boolean>>]} A tuple containing the current state,
|
|
2398
|
+
* a function to toggle the state, and a function to set the state explicitly.
|
|
2399
|
+
* @public
|
|
2400
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-toggle)
|
|
2401
|
+
* @example
|
|
2402
|
+
* ```tsx
|
|
2403
|
+
* const [isToggled, toggle, setToggle] = useToggle(); // Initial value is false
|
|
2404
|
+
* // OR
|
|
2405
|
+
* const [isToggled, toggle, setToggle] = useToggle(true); // Initial value is true
|
|
2406
|
+
* // Use isToggled in your component, toggle to switch the state, setToggle to set the state explicitly.
|
|
2407
|
+
* ```
|
|
2408
|
+
*/
|
|
2409
|
+
function useToggle(defaultValue) {
|
|
2410
|
+
const [value, setValue] = (0, react.useState)(!!defaultValue);
|
|
2411
|
+
const toggle = (0, react.useCallback)(() => {
|
|
2412
|
+
setValue((x) => !x);
|
|
2413
|
+
}, []);
|
|
2414
|
+
return [
|
|
2415
|
+
value,
|
|
2416
|
+
toggle,
|
|
2417
|
+
setValue
|
|
2418
|
+
];
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
//#endregion
|
|
2422
|
+
//#region src/useUpdate/useUpdate.ts
|
|
2423
|
+
/**
|
|
2424
|
+
* Custom hook that returns a function to force a component re-render.
|
|
2425
|
+
* @returns {() => void} A function that, when called, will force the component to re-render.
|
|
2426
|
+
* @public
|
|
2427
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-update)
|
|
2428
|
+
* @example
|
|
2429
|
+
* ```tsx
|
|
2430
|
+
* const update = useUpdate();
|
|
2431
|
+
*
|
|
2432
|
+
* return (
|
|
2433
|
+
* <div>
|
|
2434
|
+
* <p>Current time: {Date.now()}</p>
|
|
2435
|
+
* <button onClick={update}>Update</button>
|
|
2436
|
+
* </div>
|
|
2437
|
+
* );
|
|
2438
|
+
* ```
|
|
2439
|
+
*/
|
|
2440
|
+
function useUpdate() {
|
|
2441
|
+
const [, setTick] = (0, react.useState)(0);
|
|
2442
|
+
const update = (0, react.useCallback)(() => {
|
|
2443
|
+
setTick((tick) => tick + 1);
|
|
2444
|
+
}, []);
|
|
2445
|
+
return update;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
//#endregion
|
|
2449
|
+
//#region src/useUpdateEffect/useUpdateEffect.ts
|
|
2450
|
+
/**
|
|
2451
|
+
* A hook that runs an effect only when dependencies change, skipping the initial mount.
|
|
2452
|
+
* This is useful for responding to prop or state changes without running on first render.
|
|
2453
|
+
*
|
|
2454
|
+
* @param {() => void | (() => void)} effect - The effect function to run.
|
|
2455
|
+
* @param {unknown[]} deps - The dependency array.
|
|
2456
|
+
*
|
|
2457
|
+
* @example
|
|
2458
|
+
* ```tsx
|
|
2459
|
+
* function Component({ value }) {
|
|
2460
|
+
* useUpdateEffect(() => {
|
|
2461
|
+
* console.log('Value changed:', value);
|
|
2462
|
+
* }, [value]);
|
|
2463
|
+
*
|
|
2464
|
+
* return <div>{value}</div>;
|
|
2465
|
+
* }
|
|
2466
|
+
* ```
|
|
2467
|
+
*/
|
|
2468
|
+
function useUpdateEffect(effect, deps) {
|
|
2469
|
+
const isFirstMount = (0, react.useRef)(true);
|
|
2470
|
+
(0, react.useEffect)(() => {
|
|
2471
|
+
if (isFirstMount.current) {
|
|
2472
|
+
isFirstMount.current = false;
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
return effect();
|
|
2476
|
+
}, deps);
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
//#endregion
|
|
2480
|
+
//#region src/useWindowSize/useWindowSize.ts
|
|
2481
|
+
const IS_SERVER = typeof window === "undefined";
|
|
2482
|
+
/**
|
|
2483
|
+
* Custom hook that tracks the size of the window.
|
|
2484
|
+
* @param {?UseWindowSizeOptions} [options] - The options for customizing the behavior of the hook (optional).
|
|
2485
|
+
* @returns {object} An object containing the width and height of the window.
|
|
2486
|
+
* @public
|
|
2487
|
+
* @see [Documentation](https://usehooks-ts.com/react-hook/use-window-size)
|
|
2488
|
+
* @example
|
|
2489
|
+
* ```tsx
|
|
2490
|
+
* const { width = 0, height = 0 } = useWindowSize();
|
|
2491
|
+
* console.log(`Window size: ${width} x ${height}`);
|
|
2492
|
+
* ```
|
|
2493
|
+
*/
|
|
2494
|
+
function useWindowSize(options = {}) {
|
|
2495
|
+
let { initializeWithValue = true } = options;
|
|
2496
|
+
if (IS_SERVER) initializeWithValue = false;
|
|
2497
|
+
const [windowSize, setWindowSize] = (0, react.useState)(() => {
|
|
2498
|
+
if (initializeWithValue) return {
|
|
2499
|
+
width: window.innerWidth,
|
|
2500
|
+
height: window.innerHeight
|
|
2501
|
+
};
|
|
2502
|
+
return {
|
|
2503
|
+
width: void 0,
|
|
2504
|
+
height: void 0
|
|
2505
|
+
};
|
|
2506
|
+
});
|
|
2507
|
+
const debouncedSetWindowSize = useDebounceCallback(setWindowSize, options.debounceDelay);
|
|
2508
|
+
function handleSize() {
|
|
2509
|
+
const setSize = options.debounceDelay ? debouncedSetWindowSize : setWindowSize;
|
|
2510
|
+
setSize({
|
|
2511
|
+
width: window.innerWidth,
|
|
2512
|
+
height: window.innerHeight
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
useEventListener("resize", handleSize);
|
|
2516
|
+
useIsomorphicLayoutEffect(() => {
|
|
2517
|
+
handleSize();
|
|
2518
|
+
}, []);
|
|
2519
|
+
return windowSize;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
//#endregion
|
|
2523
|
+
exports.DOTS = DOTS
|
|
2524
|
+
exports.useAsync = useAsync
|
|
2525
|
+
exports.useBoolean = useBoolean
|
|
2526
|
+
exports.useClickAnyWhere = useClickAnyWhere
|
|
2527
|
+
exports.useCopyToClipboard = useCopyToClipboard
|
|
2528
|
+
exports.useCountdown = useCountdown
|
|
2529
|
+
exports.useCounter = useCounter
|
|
2530
|
+
exports.useDarkMode = useDarkMode
|
|
2531
|
+
exports.useDebounceCallback = useDebounceCallback
|
|
2532
|
+
exports.useDebounceValue = useDebounceValue
|
|
2533
|
+
exports.useDisclosure = useDisclosure
|
|
2534
|
+
exports.useDocumentTitle = useDocumentTitle
|
|
2535
|
+
exports.useEventCallback = useEventCallback
|
|
2536
|
+
exports.useEventListener = useEventListener
|
|
2537
|
+
exports.useGeolocation = useGeolocation
|
|
2538
|
+
exports.useHover = useHover
|
|
2539
|
+
exports.useIdle = useIdle
|
|
2540
|
+
exports.useIntersectionObserver = useIntersectionObserver
|
|
2541
|
+
exports.useInterval = useInterval
|
|
2542
|
+
exports.useIsClient = useIsClient
|
|
2543
|
+
exports.useIsMounted = useIsMounted
|
|
2544
|
+
exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect
|
|
2545
|
+
exports.useList = useList
|
|
2546
|
+
exports.useLocalStorage = useLocalStorage
|
|
2547
|
+
exports.useMap = useMap
|
|
2548
|
+
exports.useMediaQuery = useMediaQuery
|
|
2549
|
+
exports.useMemoizedFn = useMemoizedFn
|
|
2550
|
+
exports.useNetwork = useNetwork
|
|
2551
|
+
exports.useOnClickOutside = useOnClickOutside
|
|
2552
|
+
exports.usePageLeave = usePageLeave
|
|
2553
|
+
exports.usePagination = usePagination
|
|
2554
|
+
exports.usePermission = usePermission
|
|
2555
|
+
exports.usePrevious = usePrevious
|
|
2556
|
+
exports.useQueue = useQueue
|
|
2557
|
+
exports.useReadLocalStorage = useReadLocalStorage
|
|
2558
|
+
exports.useResizeObserver = useResizeObserver
|
|
2559
|
+
exports.useScreen = useScreen
|
|
2560
|
+
exports.useScript = useScript
|
|
2561
|
+
exports.useScrollLock = useScrollLock
|
|
2562
|
+
exports.useSessionStorage = useSessionStorage
|
|
2563
|
+
exports.useSet = useSet
|
|
2564
|
+
exports.useStateList = useStateList
|
|
2565
|
+
exports.useStep = useStep
|
|
2566
|
+
exports.useTernaryDarkMode = useTernaryDarkMode
|
|
2567
|
+
exports.useThrottle = useThrottle
|
|
2568
|
+
exports.useThrottleFn = useThrottleFn
|
|
2569
|
+
exports.useTimeout = useTimeout
|
|
2570
|
+
exports.useToggle = useToggle
|
|
2571
|
+
exports.useUnmount = useUnmount
|
|
2572
|
+
exports.useUpdate = useUpdate
|
|
2573
|
+
exports.useUpdateEffect = useUpdateEffect
|
|
2574
|
+
exports.useWindowSize = useWindowSize
|