@opensite/hooks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +102 -0
- package/dist/browser/opensite-hooks.umd.cjs +2 -0
- package/dist/browser/opensite-hooks.umd.js +2 -0
- package/dist/browser/opensite-hooks.umd.js.map +1 -0
- package/dist/core/index.cjs +16 -0
- package/dist/core/index.d.ts +24 -0
- package/dist/core/index.js +16 -0
- package/dist/core/useBoolean.cjs +8 -0
- package/dist/core/useBoolean.d.ts +8 -0
- package/dist/core/useBoolean.js +8 -0
- package/dist/core/useCopyToClipboard.cjs +67 -0
- package/dist/core/useCopyToClipboard.d.ts +9 -0
- package/dist/core/useCopyToClipboard.js +67 -0
- package/dist/core/useDebounceCallback.cjs +83 -0
- package/dist/core/useDebounceCallback.d.ts +11 -0
- package/dist/core/useDebounceCallback.js +83 -0
- package/dist/core/useDebounceValue.cjs +13 -0
- package/dist/core/useDebounceValue.d.ts +2 -0
- package/dist/core/useDebounceValue.js +13 -0
- package/dist/core/useEventListener.cjs +40 -0
- package/dist/core/useEventListener.d.ts +6 -0
- package/dist/core/useEventListener.js +40 -0
- package/dist/core/useHover.cjs +14 -0
- package/dist/core/useHover.d.ts +1 -0
- package/dist/core/useHover.js +14 -0
- package/dist/core/useIsClient.cjs +8 -0
- package/dist/core/useIsClient.d.ts +1 -0
- package/dist/core/useIsClient.js +8 -0
- package/dist/core/useIsomorphicLayoutEffect.cjs +2 -0
- package/dist/core/useIsomorphicLayoutEffect.d.ts +2 -0
- package/dist/core/useIsomorphicLayoutEffect.js +2 -0
- package/dist/core/useLocalStorage.cjs +63 -0
- package/dist/core/useLocalStorage.d.ts +9 -0
- package/dist/core/useLocalStorage.js +63 -0
- package/dist/core/useMap.cjs +39 -0
- package/dist/core/useMap.d.ts +9 -0
- package/dist/core/useMap.js +39 -0
- package/dist/core/useMediaQuery.cjs +26 -0
- package/dist/core/useMediaQuery.d.ts +4 -0
- package/dist/core/useMediaQuery.js +26 -0
- package/dist/core/useOnClickOutside.cjs +30 -0
- package/dist/core/useOnClickOutside.d.ts +3 -0
- package/dist/core/useOnClickOutside.js +30 -0
- package/dist/core/usePrevious.cjs +8 -0
- package/dist/core/usePrevious.d.ts +1 -0
- package/dist/core/usePrevious.js +8 -0
- package/dist/core/useResizeObserver.cjs +37 -0
- package/dist/core/useResizeObserver.d.ts +3 -0
- package/dist/core/useResizeObserver.js +37 -0
- package/dist/core/useSessionStorage.cjs +63 -0
- package/dist/core/useSessionStorage.d.ts +9 -0
- package/dist/core/useSessionStorage.js +63 -0
- package/dist/core/useThrottle.cjs +65 -0
- package/dist/core/useThrottle.d.ts +5 -0
- package/dist/core/useThrottle.js +65 -0
- package/dist/hooks/index.cjs +16 -0
- package/dist/hooks/index.d.ts +24 -0
- package/dist/hooks/index.js +16 -0
- package/dist/hooks/useBoolean.cjs +1 -0
- package/dist/hooks/useBoolean.d.ts +2 -0
- package/dist/hooks/useBoolean.js +1 -0
- package/dist/hooks/useCopyToClipboard.cjs +1 -0
- package/dist/hooks/useCopyToClipboard.d.ts +2 -0
- package/dist/hooks/useCopyToClipboard.js +1 -0
- package/dist/hooks/useDebounceCallback.cjs +1 -0
- package/dist/hooks/useDebounceCallback.d.ts +2 -0
- package/dist/hooks/useDebounceCallback.js +1 -0
- package/dist/hooks/useDebounceValue.cjs +1 -0
- package/dist/hooks/useDebounceValue.d.ts +2 -0
- package/dist/hooks/useDebounceValue.js +1 -0
- package/dist/hooks/useEventListener.cjs +1 -0
- package/dist/hooks/useEventListener.d.ts +1 -0
- package/dist/hooks/useEventListener.js +1 -0
- package/dist/hooks/useHover.cjs +1 -0
- package/dist/hooks/useHover.d.ts +1 -0
- package/dist/hooks/useHover.js +1 -0
- package/dist/hooks/useIsClient.cjs +1 -0
- package/dist/hooks/useIsClient.d.ts +1 -0
- package/dist/hooks/useIsClient.js +1 -0
- package/dist/hooks/useIsomorphicLayoutEffect.cjs +1 -0
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts +1 -0
- package/dist/hooks/useIsomorphicLayoutEffect.js +1 -0
- package/dist/hooks/useLocalStorage.cjs +1 -0
- package/dist/hooks/useLocalStorage.d.ts +2 -0
- package/dist/hooks/useLocalStorage.js +1 -0
- package/dist/hooks/useMap.cjs +1 -0
- package/dist/hooks/useMap.d.ts +2 -0
- package/dist/hooks/useMap.js +1 -0
- package/dist/hooks/useMediaQuery.cjs +1 -0
- package/dist/hooks/useMediaQuery.d.ts +2 -0
- package/dist/hooks/useMediaQuery.js +1 -0
- package/dist/hooks/useOnClickOutside.cjs +1 -0
- package/dist/hooks/useOnClickOutside.d.ts +1 -0
- package/dist/hooks/useOnClickOutside.js +1 -0
- package/dist/hooks/usePrevious.cjs +1 -0
- package/dist/hooks/usePrevious.d.ts +1 -0
- package/dist/hooks/usePrevious.js +1 -0
- package/dist/hooks/useResizeObserver.cjs +1 -0
- package/dist/hooks/useResizeObserver.d.ts +1 -0
- package/dist/hooks/useResizeObserver.js +1 -0
- package/dist/hooks/useSessionStorage.cjs +1 -0
- package/dist/hooks/useSessionStorage.d.ts +2 -0
- package/dist/hooks/useSessionStorage.js +1 -0
- package/dist/hooks/useThrottle.cjs +1 -0
- package/dist/hooks/useThrottle.d.ts +2 -0
- package/dist/hooks/useThrottle.js +1 -0
- package/dist/index.cjs +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/package.json +252 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
|
|
3
|
+
export function useDebounceCallback(callback, delay, options = {}) {
|
|
4
|
+
const callbackRef = useRef(callback);
|
|
5
|
+
const timeoutRef = useRef(null);
|
|
6
|
+
const maxTimeoutRef = useRef(null);
|
|
7
|
+
const lastArgsRef = useRef(null);
|
|
8
|
+
const leading = options.leading ?? false;
|
|
9
|
+
const trailing = options.trailing ?? true;
|
|
10
|
+
const maxWait = options.maxWait;
|
|
11
|
+
const wait = Math.max(0, delay);
|
|
12
|
+
useIsomorphicLayoutEffect(() => {
|
|
13
|
+
callbackRef.current = callback;
|
|
14
|
+
}, [callback]);
|
|
15
|
+
const clearTimers = useCallback(() => {
|
|
16
|
+
if (timeoutRef.current) {
|
|
17
|
+
clearTimeout(timeoutRef.current);
|
|
18
|
+
timeoutRef.current = null;
|
|
19
|
+
}
|
|
20
|
+
if (maxTimeoutRef.current) {
|
|
21
|
+
clearTimeout(maxTimeoutRef.current);
|
|
22
|
+
maxTimeoutRef.current = null;
|
|
23
|
+
}
|
|
24
|
+
}, []);
|
|
25
|
+
const invoke = useCallback(() => {
|
|
26
|
+
if (!lastArgsRef.current) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const args = lastArgsRef.current;
|
|
30
|
+
lastArgsRef.current = null;
|
|
31
|
+
callbackRef.current(...args);
|
|
32
|
+
}, []);
|
|
33
|
+
const debouncedCallback = useCallback((...args) => {
|
|
34
|
+
lastArgsRef.current = args;
|
|
35
|
+
const shouldInvokeLeading = leading && timeoutRef.current === null && maxTimeoutRef.current === null;
|
|
36
|
+
if (shouldInvokeLeading) {
|
|
37
|
+
invoke();
|
|
38
|
+
}
|
|
39
|
+
if (timeoutRef.current) {
|
|
40
|
+
clearTimeout(timeoutRef.current);
|
|
41
|
+
}
|
|
42
|
+
if (trailing) {
|
|
43
|
+
timeoutRef.current = setTimeout(() => {
|
|
44
|
+
timeoutRef.current = null;
|
|
45
|
+
if (lastArgsRef.current) {
|
|
46
|
+
invoke();
|
|
47
|
+
}
|
|
48
|
+
if (maxTimeoutRef.current) {
|
|
49
|
+
clearTimeout(maxTimeoutRef.current);
|
|
50
|
+
maxTimeoutRef.current = null;
|
|
51
|
+
}
|
|
52
|
+
}, wait);
|
|
53
|
+
}
|
|
54
|
+
if (maxWait !== undefined && maxWait !== null && trailing) {
|
|
55
|
+
if (!maxTimeoutRef.current) {
|
|
56
|
+
const maxDelay = Math.max(0, maxWait);
|
|
57
|
+
maxTimeoutRef.current = setTimeout(() => {
|
|
58
|
+
maxTimeoutRef.current = null;
|
|
59
|
+
if (timeoutRef.current) {
|
|
60
|
+
clearTimeout(timeoutRef.current);
|
|
61
|
+
timeoutRef.current = null;
|
|
62
|
+
}
|
|
63
|
+
if (lastArgsRef.current) {
|
|
64
|
+
invoke();
|
|
65
|
+
}
|
|
66
|
+
}, maxDelay);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, [invoke, leading, trailing, maxWait, wait]);
|
|
70
|
+
const cancel = useCallback(() => {
|
|
71
|
+
clearTimers();
|
|
72
|
+
lastArgsRef.current = null;
|
|
73
|
+
}, [clearTimers]);
|
|
74
|
+
const flush = useCallback(() => {
|
|
75
|
+
if (!lastArgsRef.current) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
clearTimers();
|
|
79
|
+
invoke();
|
|
80
|
+
}, [clearTimers, invoke]);
|
|
81
|
+
useEffect(() => () => cancel(), [cancel]);
|
|
82
|
+
return { debouncedCallback, cancel, flush };
|
|
83
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useDebounceCallback } from "./useDebounceCallback.js";
|
|
3
|
+
export function useDebounceValue(value, delay, options) {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
5
|
+
const { debouncedCallback, cancel } = useDebounceCallback((next) => {
|
|
6
|
+
setDebouncedValue(next);
|
|
7
|
+
}, delay, options);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
debouncedCallback(value);
|
|
10
|
+
}, [debouncedCallback, value]);
|
|
11
|
+
useEffect(() => () => cancel(), [cancel]);
|
|
12
|
+
return debouncedValue;
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useDebounceCallback } from "./useDebounceCallback.js";
|
|
3
|
+
export function useDebounceValue(value, delay, options) {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
5
|
+
const { debouncedCallback, cancel } = useDebounceCallback((next) => {
|
|
6
|
+
setDebouncedValue(next);
|
|
7
|
+
}, delay, options);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
debouncedCallback(value);
|
|
10
|
+
}, [debouncedCallback, value]);
|
|
11
|
+
useEffect(() => () => cancel(), [cancel]);
|
|
12
|
+
return debouncedValue;
|
|
13
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
|
|
3
|
+
const isRefObject = (value) => !!value && typeof value === "object" && "current" in value;
|
|
4
|
+
export function useEventListener(eventName, handler, element, options) {
|
|
5
|
+
const savedHandler = useRef(handler);
|
|
6
|
+
useIsomorphicLayoutEffect(() => {
|
|
7
|
+
savedHandler.current = handler;
|
|
8
|
+
}, [handler]);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const isWindow = typeof Window !== "undefined" && element instanceof Window;
|
|
11
|
+
const isDocument = typeof Document !== "undefined" && element instanceof Document;
|
|
12
|
+
const target = element === undefined
|
|
13
|
+
? typeof window !== "undefined"
|
|
14
|
+
? window
|
|
15
|
+
: null
|
|
16
|
+
: isWindow || isDocument
|
|
17
|
+
? element
|
|
18
|
+
: typeof HTMLElement !== "undefined" && element instanceof HTMLElement
|
|
19
|
+
? element
|
|
20
|
+
: isRefObject(element)
|
|
21
|
+
? element.current
|
|
22
|
+
: null;
|
|
23
|
+
if (!target?.addEventListener) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const listener = (event) => {
|
|
27
|
+
const currentHandler = savedHandler.current;
|
|
28
|
+
if (typeof currentHandler === "function") {
|
|
29
|
+
currentHandler(event);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
currentHandler.handleEvent(event);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
target.addEventListener(eventName, listener, options);
|
|
36
|
+
return () => {
|
|
37
|
+
target.removeEventListener(eventName, listener, options);
|
|
38
|
+
};
|
|
39
|
+
}, [eventName, element, options]);
|
|
40
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type ElementRef = React.RefObject<HTMLElement>;
|
|
2
|
+
export declare function useEventListener<K extends keyof WindowEventMap>(eventName: K, handler: (event: WindowEventMap[K]) => void, element?: Window, options?: AddEventListenerOptions | boolean): void;
|
|
3
|
+
export declare function useEventListener<K extends keyof DocumentEventMap>(eventName: K, handler: (event: DocumentEventMap[K]) => void, element: Document, options?: AddEventListenerOptions | boolean): void;
|
|
4
|
+
export declare function useEventListener<K extends keyof HTMLElementEventMap>(eventName: K, handler: (event: HTMLElementEventMap[K]) => void, element: ElementRef, options?: AddEventListenerOptions | boolean): void;
|
|
5
|
+
export declare function useEventListener<K extends keyof HTMLElementEventMap>(eventName: K, handler: (event: HTMLElementEventMap[K]) => void, element: HTMLElement, options?: AddEventListenerOptions | boolean): void;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
|
|
3
|
+
const isRefObject = (value) => !!value && typeof value === "object" && "current" in value;
|
|
4
|
+
export function useEventListener(eventName, handler, element, options) {
|
|
5
|
+
const savedHandler = useRef(handler);
|
|
6
|
+
useIsomorphicLayoutEffect(() => {
|
|
7
|
+
savedHandler.current = handler;
|
|
8
|
+
}, [handler]);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const isWindow = typeof Window !== "undefined" && element instanceof Window;
|
|
11
|
+
const isDocument = typeof Document !== "undefined" && element instanceof Document;
|
|
12
|
+
const target = element === undefined
|
|
13
|
+
? typeof window !== "undefined"
|
|
14
|
+
? window
|
|
15
|
+
: null
|
|
16
|
+
: isWindow || isDocument
|
|
17
|
+
? element
|
|
18
|
+
: typeof HTMLElement !== "undefined" && element instanceof HTMLElement
|
|
19
|
+
? element
|
|
20
|
+
: isRefObject(element)
|
|
21
|
+
? element.current
|
|
22
|
+
: null;
|
|
23
|
+
if (!target?.addEventListener) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const listener = (event) => {
|
|
27
|
+
const currentHandler = savedHandler.current;
|
|
28
|
+
if (typeof currentHandler === "function") {
|
|
29
|
+
currentHandler(event);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
currentHandler.handleEvent(event);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
target.addEventListener(eventName, listener, options);
|
|
36
|
+
return () => {
|
|
37
|
+
target.removeEventListener(eventName, listener, options);
|
|
38
|
+
};
|
|
39
|
+
}, [eventName, element, options]);
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { useEventListener } from "./useEventListener.js";
|
|
3
|
+
export function useHover(ref) {
|
|
4
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
5
|
+
const handleEnter = useCallback(() => {
|
|
6
|
+
setIsHovered(true);
|
|
7
|
+
}, []);
|
|
8
|
+
const handleLeave = useCallback(() => {
|
|
9
|
+
setIsHovered(false);
|
|
10
|
+
}, []);
|
|
11
|
+
useEventListener("pointerenter", handleEnter, ref);
|
|
12
|
+
useEventListener("pointerleave", handleLeave, ref);
|
|
13
|
+
return isHovered;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useHover<T extends HTMLElement>(ref: React.RefObject<T>): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { useEventListener } from "./useEventListener.js";
|
|
3
|
+
export function useHover(ref) {
|
|
4
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
5
|
+
const handleEnter = useCallback(() => {
|
|
6
|
+
setIsHovered(true);
|
|
7
|
+
}, []);
|
|
8
|
+
const handleLeave = useCallback(() => {
|
|
9
|
+
setIsHovered(false);
|
|
10
|
+
}, []);
|
|
11
|
+
useEventListener("pointerenter", handleEnter, ref);
|
|
12
|
+
useEventListener("pointerleave", handleLeave, ref);
|
|
13
|
+
return isHovered;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useIsClient(): boolean;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
export function useLocalStorage(key, initialValue, options = {}) {
|
|
3
|
+
const { initializeWithValue = true, serialize = JSON.stringify, deserialize = JSON.parse, listenToStorageChanges = true, } = options;
|
|
4
|
+
const initialValueRef = useRef(initialValue);
|
|
5
|
+
const readValue = useCallback(() => {
|
|
6
|
+
if (typeof window === "undefined") {
|
|
7
|
+
return initialValueRef.current;
|
|
8
|
+
}
|
|
9
|
+
if (!initializeWithValue) {
|
|
10
|
+
return initialValueRef.current;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const item = window.localStorage.getItem(key);
|
|
14
|
+
return item ? deserialize(item) : initialValueRef.current;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return initialValueRef.current;
|
|
18
|
+
}
|
|
19
|
+
}, [deserialize, initializeWithValue, key]);
|
|
20
|
+
const [storedValue, setStoredValue] = useState(() => readValue());
|
|
21
|
+
const setValue = useCallback((value) => {
|
|
22
|
+
setStoredValue((current) => {
|
|
23
|
+
const valueToStore = typeof value === "function"
|
|
24
|
+
? value(current)
|
|
25
|
+
: value;
|
|
26
|
+
if (typeof window !== "undefined") {
|
|
27
|
+
try {
|
|
28
|
+
window.localStorage.setItem(key, serialize(valueToStore));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Ignore write errors (quota/security)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return valueToStore;
|
|
35
|
+
});
|
|
36
|
+
}, [key, serialize]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setStoredValue(readValue());
|
|
39
|
+
}, [readValue]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (typeof window === "undefined" || !listenToStorageChanges) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const handleStorageChange = (event) => {
|
|
45
|
+
if (event.key !== key) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (event.newValue === null) {
|
|
49
|
+
setStoredValue(initialValueRef.current);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
setStoredValue(deserialize(event.newValue));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
setStoredValue(initialValueRef.current);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
window.addEventListener("storage", handleStorageChange);
|
|
60
|
+
return () => window.removeEventListener("storage", handleStorageChange);
|
|
61
|
+
}, [deserialize, key, listenToStorageChanges]);
|
|
62
|
+
return [storedValue, setValue];
|
|
63
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface StorageOptions<T> {
|
|
2
|
+
initializeWithValue?: boolean;
|
|
3
|
+
serialize?: (value: T) => string;
|
|
4
|
+
deserialize?: (value: string) => T;
|
|
5
|
+
listenToStorageChanges?: boolean;
|
|
6
|
+
}
|
|
7
|
+
type StoredSetter<T> = (value: T | ((current: T) => T)) => void;
|
|
8
|
+
export declare function useLocalStorage<T>(key: string, initialValue: T, options?: StorageOptions<T>): [T, StoredSetter<T>];
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
export function useLocalStorage(key, initialValue, options = {}) {
|
|
3
|
+
const { initializeWithValue = true, serialize = JSON.stringify, deserialize = JSON.parse, listenToStorageChanges = true, } = options;
|
|
4
|
+
const initialValueRef = useRef(initialValue);
|
|
5
|
+
const readValue = useCallback(() => {
|
|
6
|
+
if (typeof window === "undefined") {
|
|
7
|
+
return initialValueRef.current;
|
|
8
|
+
}
|
|
9
|
+
if (!initializeWithValue) {
|
|
10
|
+
return initialValueRef.current;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const item = window.localStorage.getItem(key);
|
|
14
|
+
return item ? deserialize(item) : initialValueRef.current;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return initialValueRef.current;
|
|
18
|
+
}
|
|
19
|
+
}, [deserialize, initializeWithValue, key]);
|
|
20
|
+
const [storedValue, setStoredValue] = useState(() => readValue());
|
|
21
|
+
const setValue = useCallback((value) => {
|
|
22
|
+
setStoredValue((current) => {
|
|
23
|
+
const valueToStore = typeof value === "function"
|
|
24
|
+
? value(current)
|
|
25
|
+
: value;
|
|
26
|
+
if (typeof window !== "undefined") {
|
|
27
|
+
try {
|
|
28
|
+
window.localStorage.setItem(key, serialize(valueToStore));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Ignore write errors (quota/security)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return valueToStore;
|
|
35
|
+
});
|
|
36
|
+
}, [key, serialize]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setStoredValue(readValue());
|
|
39
|
+
}, [readValue]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (typeof window === "undefined" || !listenToStorageChanges) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const handleStorageChange = (event) => {
|
|
45
|
+
if (event.key !== key) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (event.newValue === null) {
|
|
49
|
+
setStoredValue(initialValueRef.current);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
setStoredValue(deserialize(event.newValue));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
setStoredValue(initialValueRef.current);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
window.addEventListener("storage", handleStorageChange);
|
|
60
|
+
return () => window.removeEventListener("storage", handleStorageChange);
|
|
61
|
+
}, [deserialize, key, listenToStorageChanges]);
|
|
62
|
+
return [storedValue, setValue];
|
|
63
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
export function useMap(initialState) {
|
|
3
|
+
const [map, setMap] = useState(() => {
|
|
4
|
+
if (initialState instanceof Map) {
|
|
5
|
+
return new Map(initialState);
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(initialState)) {
|
|
8
|
+
return new Map(initialState);
|
|
9
|
+
}
|
|
10
|
+
return new Map();
|
|
11
|
+
});
|
|
12
|
+
const mapRef = useRef(map);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
mapRef.current = map;
|
|
15
|
+
}, [map]);
|
|
16
|
+
const actions = useMemo(() => ({
|
|
17
|
+
set: (key, value) => {
|
|
18
|
+
setMap((prev) => {
|
|
19
|
+
const next = new Map(prev);
|
|
20
|
+
next.set(key, value);
|
|
21
|
+
return next;
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
setAll: (entries) => {
|
|
25
|
+
setMap(entries instanceof Map ? new Map(entries) : new Map(entries));
|
|
26
|
+
},
|
|
27
|
+
remove: (key) => {
|
|
28
|
+
setMap((prev) => {
|
|
29
|
+
const next = new Map(prev);
|
|
30
|
+
next.delete(key);
|
|
31
|
+
return next;
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
clear: () => setMap(new Map()),
|
|
35
|
+
get: (key) => mapRef.current.get(key),
|
|
36
|
+
has: (key) => mapRef.current.has(key),
|
|
37
|
+
}), []);
|
|
38
|
+
return [map, actions];
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface MapActions<K, V> {
|
|
2
|
+
set: (key: K, value: V) => void;
|
|
3
|
+
setAll: (entries: Map<K, V> | [K, V][]) => void;
|
|
4
|
+
remove: (key: K) => void;
|
|
5
|
+
clear: () => void;
|
|
6
|
+
get: (key: K) => V | undefined;
|
|
7
|
+
has: (key: K) => boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function useMap<K, V>(initialState?: Map<K, V> | [K, V][]): [Map<K, V>, MapActions<K, V>];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
export function useMap(initialState) {
|
|
3
|
+
const [map, setMap] = useState(() => {
|
|
4
|
+
if (initialState instanceof Map) {
|
|
5
|
+
return new Map(initialState);
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(initialState)) {
|
|
8
|
+
return new Map(initialState);
|
|
9
|
+
}
|
|
10
|
+
return new Map();
|
|
11
|
+
});
|
|
12
|
+
const mapRef = useRef(map);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
mapRef.current = map;
|
|
15
|
+
}, [map]);
|
|
16
|
+
const actions = useMemo(() => ({
|
|
17
|
+
set: (key, value) => {
|
|
18
|
+
setMap((prev) => {
|
|
19
|
+
const next = new Map(prev);
|
|
20
|
+
next.set(key, value);
|
|
21
|
+
return next;
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
setAll: (entries) => {
|
|
25
|
+
setMap(entries instanceof Map ? new Map(entries) : new Map(entries));
|
|
26
|
+
},
|
|
27
|
+
remove: (key) => {
|
|
28
|
+
setMap((prev) => {
|
|
29
|
+
const next = new Map(prev);
|
|
30
|
+
next.delete(key);
|
|
31
|
+
return next;
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
clear: () => setMap(new Map()),
|
|
35
|
+
get: (key) => mapRef.current.get(key),
|
|
36
|
+
has: (key) => mapRef.current.has(key),
|
|
37
|
+
}), []);
|
|
38
|
+
return [map, actions];
|
|
39
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
export function useMediaQuery(query, options = {}) {
|
|
3
|
+
const [matches, setMatches] = useState(() => {
|
|
4
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
5
|
+
return options.defaultValue ?? false;
|
|
6
|
+
}
|
|
7
|
+
return window.matchMedia(query).matches;
|
|
8
|
+
});
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const mediaQueryList = window.matchMedia(query);
|
|
14
|
+
const handler = (event) => {
|
|
15
|
+
setMatches(event.matches);
|
|
16
|
+
};
|
|
17
|
+
setMatches(mediaQueryList.matches);
|
|
18
|
+
if (mediaQueryList.addEventListener) {
|
|
19
|
+
mediaQueryList.addEventListener("change", handler);
|
|
20
|
+
return () => mediaQueryList.removeEventListener("change", handler);
|
|
21
|
+
}
|
|
22
|
+
mediaQueryList.addListener(handler);
|
|
23
|
+
return () => mediaQueryList.removeListener(handler);
|
|
24
|
+
}, [query]);
|
|
25
|
+
return matches;
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
export function useMediaQuery(query, options = {}) {
|
|
3
|
+
const [matches, setMatches] = useState(() => {
|
|
4
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
5
|
+
return options.defaultValue ?? false;
|
|
6
|
+
}
|
|
7
|
+
return window.matchMedia(query).matches;
|
|
8
|
+
});
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const mediaQueryList = window.matchMedia(query);
|
|
14
|
+
const handler = (event) => {
|
|
15
|
+
setMatches(event.matches);
|
|
16
|
+
};
|
|
17
|
+
setMatches(mediaQueryList.matches);
|
|
18
|
+
if (mediaQueryList.addEventListener) {
|
|
19
|
+
mediaQueryList.addEventListener("change", handler);
|
|
20
|
+
return () => mediaQueryList.removeEventListener("change", handler);
|
|
21
|
+
}
|
|
22
|
+
mediaQueryList.addListener(handler);
|
|
23
|
+
return () => mediaQueryList.removeListener(handler);
|
|
24
|
+
}, [query]);
|
|
25
|
+
return matches;
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
export function useOnClickOutside(ref, handler, eventType = "mousedown", options) {
|
|
3
|
+
const handlerRef = useRef(handler);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
handlerRef.current = handler;
|
|
6
|
+
}, [handler]);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (typeof document === "undefined") {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const refs = Array.isArray(ref) ? ref : [ref];
|
|
12
|
+
const listener = (event) => {
|
|
13
|
+
const target = event.target;
|
|
14
|
+
if (typeof Node === "undefined" || !(target instanceof Node)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const clickedInside = refs.some((currentRef) => {
|
|
18
|
+
const node = currentRef.current;
|
|
19
|
+
return node ? node.contains(target) : false;
|
|
20
|
+
});
|
|
21
|
+
if (!clickedInside) {
|
|
22
|
+
handlerRef.current(event);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
document.addEventListener(eventType, listener, options);
|
|
26
|
+
return () => {
|
|
27
|
+
document.removeEventListener(eventType, listener, options);
|
|
28
|
+
};
|
|
29
|
+
}, [eventType, options, ref]);
|
|
30
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
type PossibleRef<T extends HTMLElement> = React.RefObject<T>;
|
|
2
|
+
export declare function useOnClickOutside<T extends HTMLElement>(ref: PossibleRef<T> | Array<PossibleRef<T>>, handler: (event: MouseEvent | TouchEvent | PointerEvent) => void, eventType?: "mousedown" | "mouseup" | "click" | "touchstart" | "pointerdown", options?: AddEventListenerOptions | boolean): void;
|
|
3
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
export function useOnClickOutside(ref, handler, eventType = "mousedown", options) {
|
|
3
|
+
const handlerRef = useRef(handler);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
handlerRef.current = handler;
|
|
6
|
+
}, [handler]);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (typeof document === "undefined") {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const refs = Array.isArray(ref) ? ref : [ref];
|
|
12
|
+
const listener = (event) => {
|
|
13
|
+
const target = event.target;
|
|
14
|
+
if (typeof Node === "undefined" || !(target instanceof Node)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const clickedInside = refs.some((currentRef) => {
|
|
18
|
+
const node = currentRef.current;
|
|
19
|
+
return node ? node.contains(target) : false;
|
|
20
|
+
});
|
|
21
|
+
if (!clickedInside) {
|
|
22
|
+
handlerRef.current(event);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
document.addEventListener(eventType, listener, options);
|
|
26
|
+
return () => {
|
|
27
|
+
document.removeEventListener(eventType, listener, options);
|
|
28
|
+
};
|
|
29
|
+
}, [eventType, options, ref]);
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function usePrevious<T>(value: T): T | undefined;
|