react-essentials-functions 1.0.2 → 1.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/README.md +514 -3
- package/build/components/ConditionnalWrapper.d.ts +20 -3
- package/build/components/ConditionnalWrapper.js +19 -7
- package/build/hooks/index.d.ts +6 -0
- package/build/hooks/index.js +6 -0
- package/build/hooks/useClickOutside.d.ts +23 -0
- package/build/hooks/useClickOutside.js +42 -0
- package/build/hooks/useDebounce.d.ts +21 -0
- package/build/hooks/useDebounce.js +36 -0
- package/build/hooks/useDimensions.d.ts +22 -2
- package/build/hooks/useDimensions.js +53 -17
- package/build/hooks/useLocalStorage.d.ts +22 -0
- package/build/hooks/useLocalStorage.js +61 -0
- package/build/hooks/useMediaQuery.d.ts +17 -0
- package/build/hooks/useMediaQuery.js +43 -0
- package/build/hooks/usePrevious.d.ts +20 -0
- package/build/hooks/usePrevious.js +30 -0
- package/build/hooks/useSafeFetch.d.ts +26 -1
- package/build/hooks/useSafeFetch.js +43 -5
- package/build/hooks/useSafeState.d.ts +3 -1
- package/build/hooks/useSafeState.js +1 -2
- package/build/hooks/useScript.d.ts +36 -1
- package/build/hooks/useScript.js +55 -3
- package/build/hooks/useTheme.d.ts +24 -2
- package/build/hooks/useTheme.js +71 -9
- package/build/hooks/useToggle.d.ts +22 -0
- package/build/hooks/useToggle.js +38 -0
- package/build/hooks/useWindowDimensions.d.ts +22 -3
- package/build/hooks/useWindowDimensions.js +35 -16
- package/package.json +39 -59
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useDebounce = useDebounce;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
/**
|
|
6
|
+
* Hook that debounces a value by a given delay.
|
|
7
|
+
* The debounced value will only update after the specified delay has passed
|
|
8
|
+
* since the last change.
|
|
9
|
+
*
|
|
10
|
+
* @param value - The value to debounce
|
|
11
|
+
* @param delay - The debounce delay in milliseconds
|
|
12
|
+
* @returns The debounced value
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const [searchTerm, setSearchTerm] = useState('');
|
|
17
|
+
* const debouncedSearch = useDebounce(searchTerm, 300);
|
|
18
|
+
*
|
|
19
|
+
* useEffect(() => {
|
|
20
|
+
* // This will only fire 300ms after the user stops typing
|
|
21
|
+
* fetchResults(debouncedSearch);
|
|
22
|
+
* }, [debouncedSearch]);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function useDebounce(value, delay) {
|
|
26
|
+
const [debouncedValue, setDebouncedValue] = (0, react_1.useState)(value);
|
|
27
|
+
(0, react_1.useEffect)(() => {
|
|
28
|
+
const handler = setTimeout(() => {
|
|
29
|
+
setDebouncedValue(value);
|
|
30
|
+
}, delay);
|
|
31
|
+
return () => {
|
|
32
|
+
clearTimeout(handler);
|
|
33
|
+
};
|
|
34
|
+
}, [value, delay]);
|
|
35
|
+
return debouncedValue;
|
|
36
|
+
}
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
export
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
export type Dimensions = {
|
|
3
3
|
width: number;
|
|
4
4
|
height: number;
|
|
5
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* Hook to get the dimensions of a DOM element.
|
|
8
|
+
* Uses ResizeObserver for optimal performance.
|
|
9
|
+
*
|
|
10
|
+
* @param targetRef - React ref to the element to measure
|
|
11
|
+
* @returns Object containing width and height of the element
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const targetRef = useRef<HTMLDivElement>(null);
|
|
16
|
+
* const { width, height } = useDimensions(targetRef);
|
|
17
|
+
*
|
|
18
|
+
* return (
|
|
19
|
+
* <div ref={targetRef}>
|
|
20
|
+
* Size: {width} x {height}
|
|
21
|
+
* </div>
|
|
22
|
+
* );
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function useDimensions(targetRef: RefObject<HTMLElement>): Dimensions;
|
|
@@ -1,35 +1,71 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useDimensions =
|
|
3
|
+
exports.useDimensions = useDimensions;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
+
/**
|
|
6
|
+
* Hook to get the dimensions of a DOM element.
|
|
7
|
+
* Uses ResizeObserver for optimal performance.
|
|
8
|
+
*
|
|
9
|
+
* @param targetRef - React ref to the element to measure
|
|
10
|
+
* @returns Object containing width and height of the element
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const targetRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
* const { width, height } = useDimensions(targetRef);
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <div ref={targetRef}>
|
|
19
|
+
* Size: {width} x {height}
|
|
20
|
+
* </div>
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
5
24
|
function useDimensions(targetRef) {
|
|
6
|
-
const getDimensions = () => {
|
|
25
|
+
const getDimensions = (0, react_1.useCallback)(() => {
|
|
7
26
|
return {
|
|
8
27
|
width: targetRef.current ? targetRef.current.offsetWidth : 0,
|
|
9
28
|
height: targetRef.current ? targetRef.current.offsetHeight : 0,
|
|
10
29
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
30
|
+
// targetRef is a ref object - stable across renders, no need in deps
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
+
}, []);
|
|
33
|
+
const [dimensions, setDimensions] = (0, react_1.useState)({
|
|
34
|
+
width: 0,
|
|
35
|
+
height: 0,
|
|
36
|
+
});
|
|
37
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
38
|
+
if (targetRef.current) {
|
|
39
|
+
setDimensions(getDimensions());
|
|
40
|
+
}
|
|
41
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
+
}, []);
|
|
16
43
|
(0, react_1.useEffect)(() => {
|
|
44
|
+
const element = targetRef.current;
|
|
45
|
+
if (!element) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Use ResizeObserver for better performance than window resize events
|
|
49
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
50
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
51
|
+
setDimensions(getDimensions());
|
|
52
|
+
});
|
|
53
|
+
resizeObserver.observe(element);
|
|
54
|
+
return () => {
|
|
55
|
+
resizeObserver.disconnect();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Fallback for browsers that don't support ResizeObserver
|
|
59
|
+
const handleResize = () => {
|
|
60
|
+
setDimensions(getDimensions());
|
|
61
|
+
};
|
|
17
62
|
window.addEventListener('resize', handleResize);
|
|
18
63
|
window.addEventListener('scroll', handleResize);
|
|
19
|
-
window.addEventListener('load', handleResize);
|
|
20
64
|
return () => {
|
|
21
65
|
window.removeEventListener('resize', handleResize);
|
|
22
66
|
window.removeEventListener('scroll', handleResize);
|
|
23
|
-
window.addEventListener('load', handleResize);
|
|
24
67
|
};
|
|
25
|
-
|
|
26
|
-
(0, react_1.useLayoutEffect)(() => {
|
|
27
|
-
handleResize();
|
|
68
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
28
69
|
}, []);
|
|
29
70
|
return dimensions;
|
|
30
71
|
}
|
|
31
|
-
exports.useDimensions = useDimensions;
|
|
32
|
-
// const targetRef = useRef() // then set a ref to your component
|
|
33
|
-
// const size = useDimensions(targetRef)
|
|
34
|
-
// ....
|
|
35
|
-
// return (div ref={targetRef}> .... <p>{size.width} - {size.height} </p> .... </div>);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook that syncs state with localStorage.
|
|
3
|
+
* Handles serialization/deserialization automatically with JSON.
|
|
4
|
+
* Falls back gracefully when localStorage is unavailable.
|
|
5
|
+
*
|
|
6
|
+
* @param key - The localStorage key
|
|
7
|
+
* @param initialValue - The initial value if nothing is stored
|
|
8
|
+
* @returns A tuple of [storedValue, setValue, removeValue]
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const [name, setName, removeName] = useLocalStorage('user-name', '');
|
|
13
|
+
*
|
|
14
|
+
* return (
|
|
15
|
+
* <input
|
|
16
|
+
* value={name}
|
|
17
|
+
* onChange={(e) => setName(e.target.value)}
|
|
18
|
+
* />
|
|
19
|
+
* );
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void, () => void];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useLocalStorage = useLocalStorage;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
/**
|
|
6
|
+
* Hook that syncs state with localStorage.
|
|
7
|
+
* Handles serialization/deserialization automatically with JSON.
|
|
8
|
+
* Falls back gracefully when localStorage is unavailable.
|
|
9
|
+
*
|
|
10
|
+
* @param key - The localStorage key
|
|
11
|
+
* @param initialValue - The initial value if nothing is stored
|
|
12
|
+
* @returns A tuple of [storedValue, setValue, removeValue]
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const [name, setName, removeName] = useLocalStorage('user-name', '');
|
|
17
|
+
*
|
|
18
|
+
* return (
|
|
19
|
+
* <input
|
|
20
|
+
* value={name}
|
|
21
|
+
* onChange={(e) => setName(e.target.value)}
|
|
22
|
+
* />
|
|
23
|
+
* );
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function useLocalStorage(key, initialValue) {
|
|
27
|
+
const [storedValue, setStoredValue] = (0, react_1.useState)(() => {
|
|
28
|
+
if (typeof window === 'undefined') {
|
|
29
|
+
return initialValue;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const item = window.localStorage.getItem(key);
|
|
33
|
+
return item !== null ? JSON.parse(item) : initialValue;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return initialValue;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const setValue = (0, react_1.useCallback)((value) => {
|
|
40
|
+
setStoredValue((prev) => {
|
|
41
|
+
const valueToStore = value instanceof Function ? value(prev) : value;
|
|
42
|
+
try {
|
|
43
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// localStorage may not be available
|
|
47
|
+
}
|
|
48
|
+
return valueToStore;
|
|
49
|
+
});
|
|
50
|
+
}, [key]);
|
|
51
|
+
const removeValue = (0, react_1.useCallback)(() => {
|
|
52
|
+
try {
|
|
53
|
+
window.localStorage.removeItem(key);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// localStorage may not be available
|
|
57
|
+
}
|
|
58
|
+
setStoredValue(initialValue);
|
|
59
|
+
}, [key, initialValue]);
|
|
60
|
+
return [storedValue, setValue, removeValue];
|
|
61
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook that tracks whether a CSS media query matches.
|
|
3
|
+
* Listens for changes and updates automatically.
|
|
4
|
+
*
|
|
5
|
+
* @param query - The CSS media query string (e.g. '(min-width: 768px)')
|
|
6
|
+
* @returns Whether the media query currently matches
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const isMobile = useMediaQuery('(max-width: 767px)');
|
|
11
|
+
* const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
|
|
12
|
+
* const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
|
|
13
|
+
*
|
|
14
|
+
* return isMobile ? <MobileLayout /> : <DesktopLayout />;
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function useMediaQuery(query: string): boolean;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useMediaQuery = useMediaQuery;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
/**
|
|
6
|
+
* Hook that tracks whether a CSS media query matches.
|
|
7
|
+
* Listens for changes and updates automatically.
|
|
8
|
+
*
|
|
9
|
+
* @param query - The CSS media query string (e.g. '(min-width: 768px)')
|
|
10
|
+
* @returns Whether the media query currently matches
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const isMobile = useMediaQuery('(max-width: 767px)');
|
|
15
|
+
* const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
|
|
16
|
+
* const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
|
|
17
|
+
*
|
|
18
|
+
* return isMobile ? <MobileLayout /> : <DesktopLayout />;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function useMediaQuery(query) {
|
|
22
|
+
const [matches, setMatches] = (0, react_1.useState)(() => {
|
|
23
|
+
if (typeof window === 'undefined') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return window.matchMedia(query).matches;
|
|
27
|
+
});
|
|
28
|
+
(0, react_1.useEffect)(() => {
|
|
29
|
+
if (typeof window === 'undefined') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const mediaQuery = window.matchMedia(query);
|
|
33
|
+
setMatches(mediaQuery.matches);
|
|
34
|
+
const handleChange = (event) => {
|
|
35
|
+
setMatches(event.matches);
|
|
36
|
+
};
|
|
37
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
38
|
+
return () => {
|
|
39
|
+
mediaQuery.removeEventListener('change', handleChange);
|
|
40
|
+
};
|
|
41
|
+
}, [query]);
|
|
42
|
+
return matches;
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook that returns the previous value of a variable.
|
|
3
|
+
* Useful for comparing current and previous props or state.
|
|
4
|
+
*
|
|
5
|
+
* @param value - The value to track
|
|
6
|
+
* @returns The value from the previous render, or undefined on first render
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const [count, setCount] = useState(0);
|
|
11
|
+
* const previousCount = usePrevious(count);
|
|
12
|
+
*
|
|
13
|
+
* return (
|
|
14
|
+
* <div>
|
|
15
|
+
* Current: {count}, Previous: {previousCount ?? 'N/A'}
|
|
16
|
+
* </div>
|
|
17
|
+
* );
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function usePrevious<T>(value: T): T | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePrevious = usePrevious;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
/**
|
|
6
|
+
* Hook that returns the previous value of a variable.
|
|
7
|
+
* Useful for comparing current and previous props or state.
|
|
8
|
+
*
|
|
9
|
+
* @param value - The value to track
|
|
10
|
+
* @returns The value from the previous render, or undefined on first render
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const [count, setCount] = useState(0);
|
|
15
|
+
* const previousCount = usePrevious(count);
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <div>
|
|
19
|
+
* Current: {count}, Previous: {previousCount ?? 'N/A'}
|
|
20
|
+
* </div>
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function usePrevious(value) {
|
|
25
|
+
const ref = (0, react_1.useRef)(undefined);
|
|
26
|
+
(0, react_1.useEffect)(() => {
|
|
27
|
+
ref.current = value;
|
|
28
|
+
}, [value]);
|
|
29
|
+
return ref.current;
|
|
30
|
+
}
|
|
@@ -1 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Hook that provides a fetch function which automatically aborts previous
|
|
3
|
+
* requests and cleans up on unmount using AbortController.
|
|
4
|
+
*
|
|
5
|
+
* @returns A fetch function with automatic abort handling
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const safeFetch = useSafeFetch();
|
|
10
|
+
*
|
|
11
|
+
* useEffect(() => {
|
|
12
|
+
* const fetchData = async () => {
|
|
13
|
+
* try {
|
|
14
|
+
* const response = await safeFetch('https://api.example.com/data');
|
|
15
|
+
* const data = await response.json();
|
|
16
|
+
* } catch (error) {
|
|
17
|
+
* if (error.name !== 'AbortError') {
|
|
18
|
+
* console.error('Fetch error:', error);
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* };
|
|
22
|
+
* fetchData();
|
|
23
|
+
* }, [safeFetch]);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function useSafeFetch(): (url: string, options?: RequestInit) => Promise<Response>;
|
|
@@ -1,10 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useSafeFetch =
|
|
3
|
+
exports.useSafeFetch = useSafeFetch;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
+
/**
|
|
6
|
+
* Hook that provides a fetch function which automatically aborts previous
|
|
7
|
+
* requests and cleans up on unmount using AbortController.
|
|
8
|
+
*
|
|
9
|
+
* @returns A fetch function with automatic abort handling
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const safeFetch = useSafeFetch();
|
|
14
|
+
*
|
|
15
|
+
* useEffect(() => {
|
|
16
|
+
* const fetchData = async () => {
|
|
17
|
+
* try {
|
|
18
|
+
* const response = await safeFetch('https://api.example.com/data');
|
|
19
|
+
* const data = await response.json();
|
|
20
|
+
* } catch (error) {
|
|
21
|
+
* if (error.name !== 'AbortError') {
|
|
22
|
+
* console.error('Fetch error:', error);
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* };
|
|
26
|
+
* fetchData();
|
|
27
|
+
* }, [safeFetch]);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
5
30
|
function useSafeFetch() {
|
|
6
|
-
const
|
|
7
|
-
(0, react_1.useEffect)(() =>
|
|
8
|
-
|
|
31
|
+
const abortControllerRef = (0, react_1.useRef)(null);
|
|
32
|
+
(0, react_1.useEffect)(() => {
|
|
33
|
+
return () => {
|
|
34
|
+
abortControllerRef.current?.abort();
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
return (0, react_1.useCallback)((url, options = {}) => {
|
|
38
|
+
abortControllerRef.current?.abort();
|
|
39
|
+
const abortController = new AbortController();
|
|
40
|
+
abortControllerRef.current = abortController;
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
42
|
+
const { signal, ...restOptions } = options;
|
|
43
|
+
return fetch(url, {
|
|
44
|
+
...restOptions,
|
|
45
|
+
signal: abortController.signal,
|
|
46
|
+
});
|
|
47
|
+
}, []);
|
|
9
48
|
}
|
|
10
|
-
exports.useSafeFetch = useSafeFetch;
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
type SetStateAction<T> = T | ((prevState: T) => T);
|
|
2
|
+
export declare function useSafeState<T = unknown>(initialValue?: T | (() => T)): [T, (value: SetStateAction<T>) => void];
|
|
3
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useSafeState =
|
|
3
|
+
exports.useSafeState = useSafeState;
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
function useSafeState(initialValue = null) {
|
|
6
6
|
const isMounted = (0, react_1.useRef)(true);
|
|
@@ -17,4 +17,3 @@ function useSafeState(initialValue = null) {
|
|
|
17
17
|
}, []);
|
|
18
18
|
return [state, setStateSafe];
|
|
19
19
|
}
|
|
20
|
-
exports.useSafeState = useSafeState;
|
|
@@ -1 +1,36 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type UseScriptStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
2
|
+
export type UseScriptOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Callback called when script is loaded successfully
|
|
5
|
+
*/
|
|
6
|
+
onLoad?: () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Callback called when script fails to load
|
|
9
|
+
*/
|
|
10
|
+
onError?: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* Whether to remove the script tag when the component unmounts
|
|
13
|
+
* @default true
|
|
14
|
+
*/
|
|
15
|
+
removeOnUnmount?: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Hook to dynamically load external scripts.
|
|
19
|
+
* Returns the loading status of the script.
|
|
20
|
+
*
|
|
21
|
+
* @param url - The URL of the script to load
|
|
22
|
+
* @param options - Optional callbacks and configuration
|
|
23
|
+
* @returns The current status of the script: 'idle' | 'loading' | 'ready' | 'error'
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const status = useScript('https://example.com/script.js', {
|
|
28
|
+
* onLoad: () => console.log('Script loaded'),
|
|
29
|
+
* onError: () => console.error('Script failed to load'),
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* if (status === 'loading') return <div>Loading...</div>;
|
|
33
|
+
* if (status === 'error') return <div>Error loading script</div>;
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const useScript: (url: string, options?: UseScriptOptions) => UseScriptStatus;
|
package/build/hooks/useScript.js
CHANGED
|
@@ -2,15 +2,67 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useScript = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Hook to dynamically load external scripts.
|
|
7
|
+
* Returns the loading status of the script.
|
|
8
|
+
*
|
|
9
|
+
* @param url - The URL of the script to load
|
|
10
|
+
* @param options - Optional callbacks and configuration
|
|
11
|
+
* @returns The current status of the script: 'idle' | 'loading' | 'ready' | 'error'
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const status = useScript('https://example.com/script.js', {
|
|
16
|
+
* onLoad: () => console.log('Script loaded'),
|
|
17
|
+
* onError: () => console.error('Script failed to load'),
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* if (status === 'loading') return <div>Loading...</div>;
|
|
21
|
+
* if (status === 'error') return <div>Error loading script</div>;
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
const useScript = (url, options = {}) => {
|
|
25
|
+
const { onLoad, onError, removeOnUnmount = true } = options;
|
|
26
|
+
const [status, setStatus] = (0, react_1.useState)('idle');
|
|
27
|
+
const callbacksRef = (0, react_1.useRef)({ onLoad, onError });
|
|
28
|
+
// Keep callbacks ref updated
|
|
6
29
|
(0, react_1.useEffect)(() => {
|
|
30
|
+
callbacksRef.current = { onLoad, onError };
|
|
31
|
+
}, [onLoad, onError]);
|
|
32
|
+
(0, react_1.useEffect)(() => {
|
|
33
|
+
// Find existing script safely (avoid CSS selector injection)
|
|
34
|
+
const existingScript = Array.from(document.querySelectorAll('script')).find((s) => s.src === url || s.getAttribute('src') === url);
|
|
35
|
+
if (existingScript) {
|
|
36
|
+
const isLoaded = existingScript.readyState ===
|
|
37
|
+
'complete' ||
|
|
38
|
+
existingScript.readyState ===
|
|
39
|
+
'loaded';
|
|
40
|
+
setStatus(isLoaded ? 'ready' : 'loading');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
setStatus('loading');
|
|
7
44
|
const script = document.createElement('script');
|
|
8
45
|
script.src = url;
|
|
9
46
|
script.async = true;
|
|
47
|
+
const handleLoad = () => {
|
|
48
|
+
setStatus('ready');
|
|
49
|
+
callbacksRef.current.onLoad?.();
|
|
50
|
+
};
|
|
51
|
+
const handleError = () => {
|
|
52
|
+
setStatus('error');
|
|
53
|
+
callbacksRef.current.onError?.();
|
|
54
|
+
};
|
|
55
|
+
script.addEventListener('load', handleLoad);
|
|
56
|
+
script.addEventListener('error', handleError);
|
|
10
57
|
document.body.appendChild(script);
|
|
11
58
|
return () => {
|
|
12
|
-
|
|
59
|
+
script.removeEventListener('load', handleLoad);
|
|
60
|
+
script.removeEventListener('error', handleError);
|
|
61
|
+
if (removeOnUnmount && script.parentNode) {
|
|
62
|
+
script.parentNode.removeChild(script);
|
|
63
|
+
}
|
|
13
64
|
};
|
|
14
|
-
}, [url]);
|
|
65
|
+
}, [url, removeOnUnmount]);
|
|
66
|
+
return status;
|
|
15
67
|
};
|
|
16
68
|
exports.useScript = useScript;
|
|
@@ -1,2 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export type ThemeMode = 'light' | 'dark';
|
|
2
|
+
/**
|
|
3
|
+
* Hook to manage theme (light/dark) with localStorage persistence.
|
|
4
|
+
* Automatically detects system color scheme preference.
|
|
5
|
+
*
|
|
6
|
+
* @returns A tuple containing:
|
|
7
|
+
* - current theme mode ('light' | 'dark')
|
|
8
|
+
* - function to toggle between themes
|
|
9
|
+
* - boolean indicating if the component is mounted (useful for SSR)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const [theme, toggleTheme, mounted] = useTheme();
|
|
14
|
+
*
|
|
15
|
+
* if (!mounted) return null; // Avoid hydration mismatch
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <button onClick={toggleTheme}>
|
|
19
|
+
* Switch to {theme === 'light' ? 'dark' : 'light'} mode
|
|
20
|
+
* </button>
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const useTheme: () => [ThemeMode, () => void, boolean];
|