react-simplikit 0.0.2 → 0.0.4

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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -0
  3. package/dist/components/ImpressionArea/index.d.ts +46 -0
  4. package/dist/components/ImpressionArea/index.js +276 -0
  5. package/dist/components/Separated/index.d.ts +38 -0
  6. package/dist/components/Separated/index.js +40 -0
  7. package/dist/components/SwitchCase/index.d.ts +46 -0
  8. package/dist/components/SwitchCase/index.js +35 -0
  9. package/dist/hooks/useAsyncEffect/index.d.ts +25 -0
  10. package/dist/hooks/useAsyncEffect/index.js +43 -0
  11. package/dist/hooks/useBooleanState/index.d.ts +19 -0
  12. package/dist/hooks/useBooleanState/index.js +45 -0
  13. package/dist/hooks/useCallbackOncePerRender/index.d.ts +41 -0
  14. package/dist/hooks/useCallbackOncePerRender/index.js +59 -0
  15. package/dist/hooks/useDebounce/index.d.ts +48 -0
  16. package/dist/hooks/useDebounce/index.js +123 -0
  17. package/dist/hooks/useImpressionRef/index.d.ts +38 -0
  18. package/dist/hooks/useImpressionRef/index.js +237 -0
  19. package/dist/hooks/useInputState/index.d.ts +27 -0
  20. package/dist/hooks/useInputState/index.js +45 -0
  21. package/dist/hooks/useIntersectionObserver/index.d.ts +33 -0
  22. package/dist/hooks/useIntersectionObserver/index.js +91 -0
  23. package/dist/hooks/useInterval/index.d.ts +37 -0
  24. package/dist/hooks/useInterval/index.js +64 -0
  25. package/dist/hooks/useLoading/index.d.ts +37 -0
  26. package/dist/hooks/useLoading/index.js +61 -0
  27. package/dist/hooks/useOutsideClickEffect/index.d.ts +27 -0
  28. package/dist/hooks/useOutsideClickEffect/index.js +70 -0
  29. package/dist/hooks/usePreservedCallback/index.d.ts +30 -0
  30. package/dist/hooks/usePreservedCallback/index.js +41 -0
  31. package/dist/hooks/usePreservedReference/index.d.ts +42 -0
  32. package/dist/hooks/usePreservedReference/index.js +44 -0
  33. package/dist/hooks/usePrevious/index.d.ts +21 -0
  34. package/dist/hooks/usePrevious/index.js +47 -0
  35. package/dist/hooks/useRefEffect/index.d.ts +31 -0
  36. package/dist/hooks/useRefEffect/index.js +68 -0
  37. package/dist/hooks/useStorageState/index.d.ts +50 -0
  38. package/dist/hooks/useStorageState/index.js +168 -0
  39. package/dist/hooks/useThrottle/index.d.ts +47 -0
  40. package/dist/hooks/useThrottle/index.js +147 -0
  41. package/dist/hooks/useTimeout/index.d.ts +30 -0
  42. package/dist/hooks/useTimeout/index.js +53 -0
  43. package/dist/hooks/useToggle/index.d.ts +30 -0
  44. package/dist/hooks/useToggle/index.js +36 -0
  45. package/dist/hooks/useVisibilityEvent/index.d.ts +27 -0
  46. package/dist/hooks/useVisibilityEvent/index.js +46 -0
  47. package/dist/index.d.ts +26 -0
  48. package/dist/index.js +761 -0
  49. package/dist/utils/buildContext/index.d.ts +38 -0
  50. package/dist/utils/buildContext/index.js +55 -0
  51. package/dist/utils/mergeRefs/index.d.ts +37 -0
  52. package/dist/utils/mergeRefs/index.js +45 -0
  53. package/esm/components/ImpressionArea/index.d.mts +46 -0
  54. package/esm/components/ImpressionArea/index.mjs +249 -0
  55. package/esm/components/Separated/index.d.mts +38 -0
  56. package/esm/components/Separated/index.mjs +13 -0
  57. package/esm/components/SwitchCase/index.d.mts +46 -0
  58. package/esm/components/SwitchCase/index.mjs +8 -0
  59. package/esm/hooks/useAsyncEffect/index.d.mts +25 -0
  60. package/esm/hooks/useAsyncEffect/index.mjs +16 -0
  61. package/esm/hooks/useBooleanState/index.d.mts +19 -0
  62. package/esm/hooks/useBooleanState/index.mjs +18 -0
  63. package/esm/hooks/useCallbackOncePerRender/index.d.mts +41 -0
  64. package/esm/hooks/useCallbackOncePerRender/index.mjs +32 -0
  65. package/esm/hooks/useDebounce/index.d.mts +48 -0
  66. package/esm/hooks/useDebounce/index.mjs +96 -0
  67. package/esm/hooks/useImpressionRef/index.d.mts +38 -0
  68. package/esm/hooks/useImpressionRef/index.mjs +210 -0
  69. package/esm/hooks/useInputState/index.d.mts +27 -0
  70. package/esm/hooks/useInputState/index.mjs +18 -0
  71. package/esm/hooks/useIntersectionObserver/index.d.mts +33 -0
  72. package/esm/hooks/useIntersectionObserver/index.mjs +64 -0
  73. package/esm/hooks/useInterval/index.d.mts +37 -0
  74. package/esm/hooks/useInterval/index.mjs +37 -0
  75. package/esm/hooks/useLoading/index.d.mts +37 -0
  76. package/esm/hooks/useLoading/index.mjs +34 -0
  77. package/esm/hooks/useOutsideClickEffect/index.d.mts +27 -0
  78. package/esm/hooks/useOutsideClickEffect/index.mjs +43 -0
  79. package/esm/hooks/usePreservedCallback/index.d.mts +30 -0
  80. package/esm/hooks/usePreservedCallback/index.mjs +14 -0
  81. package/esm/hooks/usePreservedReference/index.d.mts +42 -0
  82. package/esm/hooks/usePreservedReference/index.mjs +17 -0
  83. package/esm/hooks/usePrevious/index.d.mts +21 -0
  84. package/esm/hooks/usePrevious/index.mjs +20 -0
  85. package/esm/hooks/useRefEffect/index.d.mts +31 -0
  86. package/esm/hooks/useRefEffect/index.mjs +41 -0
  87. package/esm/hooks/useStorageState/index.d.mts +50 -0
  88. package/esm/hooks/useStorageState/index.mjs +141 -0
  89. package/esm/hooks/useThrottle/index.d.mts +47 -0
  90. package/esm/hooks/useThrottle/index.mjs +120 -0
  91. package/esm/hooks/useTimeout/index.d.mts +30 -0
  92. package/esm/hooks/useTimeout/index.mjs +26 -0
  93. package/esm/hooks/useToggle/index.d.mts +30 -0
  94. package/esm/hooks/useToggle/index.mjs +9 -0
  95. package/esm/hooks/useVisibilityEvent/index.d.mts +27 -0
  96. package/esm/hooks/useVisibilityEvent/index.mjs +19 -0
  97. package/esm/index.d.mts +26 -0
  98. package/esm/index.mjs +711 -0
  99. package/esm/utils/buildContext/index.d.mts +38 -0
  100. package/esm/utils/buildContext/index.mjs +28 -0
  101. package/esm/utils/mergeRefs/index.d.mts +37 -0
  102. package/esm/utils/mergeRefs/index.mjs +18 -0
  103. package/package.json +87 -7
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @description
3
+ * `useLoading` is a React hook that simplifies managing the loading state of a `Promise`.
4
+ * It provides a state to track whether an asynchronous operation is in progress and a function to handle the loading state automatically.
5
+ *
6
+ * @returns A tuple `[boolean, <T>(promise: Promise<T>) => Promise<T>]`:
7
+ *
8
+ * - `boolean`: Represents the current loading state.
9
+ * - The initial value is `false`.
10
+ * - It is set to `true` when an asynchronous task is in progress.
11
+ *
12
+ * - `<T>(promise: Promise<T>) => Promise<T>`: A function that executes asynchronous tasks while managing the loading state.
13
+ * - This function takes a `Promise` as an argument and automatically resets the `isLoading` state to `false` when the `Promise` completes.
14
+ *
15
+ * @example
16
+ * function ConfirmButton() {
17
+ * const [loading, startLoading] = useLoading();
18
+ *
19
+ * const handleSubmit = useCallback(async () => {
20
+ * try {
21
+ * const result = await startLoading(postConfirmation());
22
+ * router.push(`/success?id=${result.id}`);
23
+ * } catch (error) {
24
+ * console.error('Error:', error);
25
+ * }
26
+ * }, [startLoading]);
27
+ *
28
+ * return (
29
+ * <button disabled={loading} onClick={handleSubmit}>
30
+ * {loading ? 'Loading...' : 'Confirm'}
31
+ * </button>
32
+ * );
33
+ * }
34
+ */
35
+ declare function useLoading(): [boolean, <T>(promise: Promise<T>) => Promise<T>];
36
+
37
+ export { useLoading };
@@ -0,0 +1,34 @@
1
+ // src/hooks/useLoading/useLoading.ts
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ function useLoading() {
4
+ const [loading, setLoading] = useState(false);
5
+ const ref = useIsMountedRef();
6
+ const startTransition = useCallback(
7
+ async (promise) => {
8
+ try {
9
+ setLoading(true);
10
+ const data = await promise;
11
+ return data;
12
+ } finally {
13
+ if (ref.isMounted) {
14
+ setLoading(false);
15
+ }
16
+ }
17
+ },
18
+ [ref.isMounted]
19
+ );
20
+ return useMemo(() => [loading, startTransition], [loading, startTransition]);
21
+ }
22
+ function useIsMountedRef() {
23
+ const ref = useRef({ isMounted: true }).current;
24
+ useEffect(() => {
25
+ ref.isMounted = true;
26
+ return () => {
27
+ ref.isMounted = false;
28
+ };
29
+ }, [ref]);
30
+ return ref;
31
+ }
32
+ export {
33
+ useLoading
34
+ };
@@ -0,0 +1,27 @@
1
+ type OneOrMore<T> = T | T[];
2
+ /**
3
+ * @description
4
+ * `useOutsideClickEffect` is a React hook that triggers a callback when a click event occurs outside the specified container(s).
5
+ * It is useful for closing modals, dropdowns, tooltips, and other UI components when clicking outside.
6
+ *
7
+ * @param {HTMLElement | HTMLElement[] | null} container - A single HTML element, an array of HTML elements, or `null`.
8
+ * If `null`, no event listener is attached.
9
+ * @param {() => void} callback - A function that is executed when clicking outside the specified container(s).
10
+ *
11
+ * @example
12
+ * import { useOutsideClickEffect } from 'react-simplikit';
13
+ * import { useState } from 'react';
14
+ *
15
+ * function Example() {
16
+ * const [wrapperEl, setWrapperEl] = useState<HTMLDivElement | null>(null);
17
+ *
18
+ * useOutsideClickEffect(wrapperEl, () => {
19
+ * console.log('Outside clicked!');
20
+ * });
21
+ *
22
+ * return <div ref={setWrapperEl}>Content</div>;
23
+ * }
24
+ */
25
+ declare function useOutsideClickEffect(container: OneOrMore<HTMLElement | null>, callback: () => void): void;
26
+
27
+ export { useOutsideClickEffect };
@@ -0,0 +1,43 @@
1
+ // src/hooks/useOutsideClickEffect/useOutsideClickEffect.ts
2
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
3
+
4
+ // src/hooks/usePreservedCallback/usePreservedCallback.ts
5
+ import { useCallback, useEffect, useRef } from "react";
6
+ function usePreservedCallback(callback) {
7
+ const callbackRef = useRef(callback);
8
+ useEffect(() => {
9
+ callbackRef.current = callback;
10
+ }, [callback]);
11
+ return useCallback((...args) => {
12
+ return callbackRef.current(...args);
13
+ }, []);
14
+ }
15
+
16
+ // src/hooks/useOutsideClickEffect/useOutsideClickEffect.ts
17
+ function useOutsideClickEffect(container, callback) {
18
+ const containers = useRef2([]);
19
+ const handleDocumentClick = usePreservedCallback(({ target }) => {
20
+ if (target === null) {
21
+ return;
22
+ }
23
+ if (containers.current.length === 0) {
24
+ return;
25
+ }
26
+ if (containers.current.some((x) => x.contains(target))) {
27
+ return;
28
+ }
29
+ callback();
30
+ });
31
+ useEffect2(() => {
32
+ containers.current = [container].flat(1).filter((item) => item != null);
33
+ }, [container]);
34
+ useEffect2(() => {
35
+ document.addEventListener("click", handleDocumentClick);
36
+ return () => {
37
+ document.removeEventListener("click", handleDocumentClick);
38
+ };
39
+ }, [handleDocumentClick]);
40
+ }
41
+ export {
42
+ useOutsideClickEffect
43
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @description
3
+ * `usePreservedCallback` is a React hook that maintains a stable reference to a callback function
4
+ * while ensuring it always has access to the latest state or props. This prevents unnecessary re-renders
5
+ * and simplifies dependency management when passing callbacks to child components or handling event listeners.
6
+ *
7
+ * @param {(...args: any[]) => any} callback - The function to preserve.
8
+ * It always references the latest state or props, even when the component re-renders.
9
+ *
10
+ * @returns {(...args: any[]) => any} A function with the same signature as the input callback.
11
+ * The returned function maintains a stable reference while accessing the latest state or props.
12
+ *
13
+ * @example
14
+ * import { usePreservedCallback } from 'react-simplikit';
15
+ * import { useState } from 'react';
16
+ *
17
+ * function Counter() {
18
+ * const [count, setCount] = useState(0);
19
+ *
20
+ * const handleClick = usePreservedCallback(() => {
21
+ * console.log(`Current count: ${count}`);
22
+ * setCount(prev => prev + 1);
23
+ * });
24
+ *
25
+ * return <button onClick={handleClick}>Click me</button>;
26
+ * }
27
+ */
28
+ declare function usePreservedCallback<Arguments extends any[] = any[], ReturnValue = unknown>(callback: (...args: Arguments) => ReturnValue): (...args: Arguments) => ReturnValue;
29
+
30
+ export { usePreservedCallback };
@@ -0,0 +1,14 @@
1
+ // src/hooks/usePreservedCallback/usePreservedCallback.ts
2
+ import { useCallback, useEffect, useRef } from "react";
3
+ function usePreservedCallback(callback) {
4
+ const callbackRef = useRef(callback);
5
+ useEffect(() => {
6
+ callbackRef.current = callback;
7
+ }, [callback]);
8
+ return useCallback((...args) => {
9
+ return callbackRef.current(...args);
10
+ }, []);
11
+ }
12
+ export {
13
+ usePreservedCallback
14
+ };
@@ -0,0 +1,42 @@
1
+ type NotNullishValue = {};
2
+ /**
3
+ * @description
4
+ * `usePreservedReference` is a React hook that helps maintain the reference of a value
5
+ * when it hasn't changed, while ensuring you can safely use the latest state.
6
+ * It prevents unnecessary re-renders while always allowing access to the latest data.
7
+ *
8
+ * @param {T} value - The value to maintain the reference for. It returns a new reference
9
+ * if the state value changes after comparison.
10
+ * @param {(a: T, b: T) => boolean} [areValuesEqual] - An optional function to determine
11
+ * if two values are equal. By default, it uses `JSON.stringify` for comparison.
12
+ *
13
+ * @returns {T} Returns the same reference if the value is considered equal to the previous one,
14
+ * otherwise returns a new reference.
15
+ *
16
+ * @example
17
+ * import { usePreservedReference } from 'react-simplikit';
18
+ * import { useState } from 'react';
19
+ *
20
+ * function ExampleComponent() {
21
+ * const [state, setState] = useState({ key: 'value' });
22
+ *
23
+ * const preservedState = usePreservedReference(state);
24
+ *
25
+ * return <div>{preservedState.key}</div>;
26
+ * }
27
+ *
28
+ * @example
29
+ * import { usePreservedReference } from 'react-simplikit';
30
+ * import { useState } from 'react';
31
+ *
32
+ * function ExampleComponent() {
33
+ * const [state, setState] = useState({ key: 'value' });
34
+ *
35
+ * const preservedState = usePreservedReference(state, (a, b) => a.key === b.key);
36
+ *
37
+ * return <div>{preservedState.key}</div>;
38
+ * }
39
+ */
40
+ declare function usePreservedReference<T extends NotNullishValue>(value: T, areValuesEqual?: (a: T, b: T) => boolean): T;
41
+
42
+ export { usePreservedReference };
@@ -0,0 +1,17 @@
1
+ // src/hooks/usePreservedReference/usePreservedReference.ts
2
+ import { useMemo, useRef } from "react";
3
+ function usePreservedReference(value, areValuesEqual = areDeeplyEqual) {
4
+ const ref = useRef(value);
5
+ return useMemo(() => {
6
+ if (!areValuesEqual(ref.current, value)) {
7
+ ref.current = value;
8
+ }
9
+ return ref.current;
10
+ }, [areValuesEqual, value]);
11
+ }
12
+ function areDeeplyEqual(x, y) {
13
+ return JSON.stringify(x) === JSON.stringify(y);
14
+ }
15
+ export {
16
+ usePreservedReference
17
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @description
3
+ * Returns the previous value of the input state.
4
+ * If a re-render occurs but the state value does not change, the previous value remains unchanged.
5
+ * If the state is an object or requires custom change detection, a `compare` function can be provided.
6
+ * By default, state changes are detected using `prev === next`.
7
+ *
8
+ * @template T - The type of the state.
9
+ * @param {T} state - The state whose previous value is to be tracked.
10
+ * @param {(prev: T | undefined, next: T) => boolean} [compare] - An optional comparison function to determine if the state has changed.
11
+ *
12
+ * @returns {T | undefined} The previous value of the state.
13
+ *
14
+ * @example
15
+ * const [count, setCount] = useState(0);
16
+ * // initial value of previousCount is `0`
17
+ * const previousCount = usePrevious(count);
18
+ */
19
+ declare function usePrevious<T>(state: T, compare?: (prev: T, next: T) => boolean): T;
20
+
21
+ export { usePrevious };
@@ -0,0 +1,20 @@
1
+ // src/hooks/usePrevious/usePrevious.ts
2
+ import { useEffect, useRef } from "react";
3
+ var strictEquals = (prev, next) => prev === next;
4
+ function usePrevious(state, compare = strictEquals) {
5
+ const prevRef = useRef(state);
6
+ const currentRef = useRef(state);
7
+ const isFirstRender = useRef(true);
8
+ useEffect(() => {
9
+ isFirstRender.current = false;
10
+ }, []);
11
+ if (isFirstRender.current || compare(currentRef.current, state)) {
12
+ return prevRef.current;
13
+ }
14
+ prevRef.current = currentRef.current;
15
+ currentRef.current = state;
16
+ return prevRef.current;
17
+ }
18
+ export {
19
+ usePrevious
20
+ };
@@ -0,0 +1,31 @@
1
+ import { DependencyList } from 'react';
2
+
3
+ type CleanupCallback = () => void;
4
+ /**
5
+ * @description
6
+ * `useRefEffect` is a custom hook that helps you set a reference to a specific DOM element and execute a callback whenever the element changes.
7
+ * This hook calls a cleanup function whenever the element changes to prevent memory leaks.
8
+ *
9
+ * @param callback - A callback function that is executed when the element is set. This function can return a cleanup function.
10
+ * @param deps - An array of dependencies that define when the callback should be re-executed. The `callback` is re-executed whenever the `deps` change.
11
+ *
12
+ * @returns A function to set the element. Pass this function to the `ref` attribute, and the `callback` will be called whenever the element changes.
13
+ *
14
+ * @example
15
+ * import { useRefEffect } from 'react-simplikit';
16
+ *
17
+ * function Component() {
18
+ * const ref = useRefEffect<HTMLDivElement>(element => {
19
+ * console.log('Element mounted:', element);
20
+ *
21
+ * return () => {
22
+ * console.log('Element unmounted:', element);
23
+ * };
24
+ * }, []);
25
+ *
26
+ * return <div ref={ref}>Basic Example</div>;
27
+ * }
28
+ */
29
+ declare function useRefEffect<Element extends HTMLElement = HTMLElement>(callback: (element: Element) => CleanupCallback | void, deps: DependencyList): (element: Element | null) => void;
30
+
31
+ export { useRefEffect };
@@ -0,0 +1,41 @@
1
+ // src/hooks/useRefEffect/useRefEffect.ts
2
+ import { useCallback as useCallback2, useRef as useRef2 } from "react";
3
+
4
+ // src/hooks/usePreservedCallback/usePreservedCallback.ts
5
+ import { useCallback, useEffect, useRef } from "react";
6
+ function usePreservedCallback(callback) {
7
+ const callbackRef = useRef(callback);
8
+ useEffect(() => {
9
+ callbackRef.current = callback;
10
+ }, [callback]);
11
+ return useCallback((...args) => {
12
+ return callbackRef.current(...args);
13
+ }, []);
14
+ }
15
+
16
+ // src/hooks/useRefEffect/useRefEffect.ts
17
+ function useRefEffect(callback, deps) {
18
+ const preservedCallback = usePreservedCallback(callback);
19
+ const cleanupCallbackRef = useRef2(() => {
20
+ });
21
+ const effect = useCallback2(
22
+ (element) => {
23
+ cleanupCallbackRef.current();
24
+ cleanupCallbackRef.current = () => {
25
+ };
26
+ if (element == null) {
27
+ return;
28
+ }
29
+ const cleanup = preservedCallback(element);
30
+ if (typeof cleanup === "function") {
31
+ cleanupCallbackRef.current = cleanup;
32
+ }
33
+ },
34
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
+ [preservedCallback, ...deps]
36
+ );
37
+ return effect;
38
+ }
39
+ export {
40
+ useRefEffect
41
+ };
@@ -0,0 +1,50 @@
1
+ import { SetStateAction } from 'react';
2
+
3
+ type Storage = {
4
+ get(key: string): string | null;
5
+ set(key: string, value: string): void;
6
+ remove(key: string): void;
7
+ clear(): void;
8
+ };
9
+
10
+ type ToPrimitive<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : never;
11
+ type ToObject<T> = T extends unknown[] | Record<string, unknown> ? T : never;
12
+ type Serializable<T> = T extends string | number | boolean ? ToPrimitive<T> : ToObject<T>;
13
+ type StorageStateOptions<T> = {
14
+ storage?: Storage;
15
+ defaultValue?: Serializable<T>;
16
+ };
17
+ type StorageStateOptionsWithDefaultValue<T> = StorageStateOptions<T> & {
18
+ defaultValue: Serializable<T>;
19
+ };
20
+ /**
21
+ * @description
22
+ * A React hook that functions like `useState` but persists the state value in browser storage.
23
+ * The value is retained across page reloads and can be shared between tabs when using `localStorage`.
24
+ *
25
+ * @param {string} key - The key used to store the value in storage.
26
+ * @param {object} [options] - Configuration options for storage behavior.
27
+ * @param {Storage} [options.storage=localStorage] - The storage type (`localStorage` or `sessionStorage`). Defaults to `localStorage`.
28
+ * @param {T} [options.defaultValue] - The initial value if no existing value is found.
29
+ *
30
+ * @returns {[Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void]} A tuple:
31
+ * - `state`: The current state value retrieved from storage.
32
+ * - `setState`: A function to update and persist the state.
33
+ *
34
+ * @example
35
+ * // Counter with persistent state
36
+ * import { useStorageState } from 'react-simplikit';
37
+ *
38
+ * function Counter() {
39
+ * const [count, setCount] = useStorageState<number>('counter', {
40
+ * defaultValue: 0,
41
+ * });
42
+ *
43
+ * return <button onClick={() => setCount(prev => prev + 1)}>Count: {count}</button>;
44
+ * }
45
+ */
46
+ declare function useStorageState<T>(key: string): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void];
47
+ declare function useStorageState<T>(key: string, { storage, defaultValue }: StorageStateOptionsWithDefaultValue<T>): readonly [Serializable<T>, (value: SetStateAction<Serializable<T>>) => void];
48
+ declare function useStorageState<T>(key: string, { storage, defaultValue }: StorageStateOptions<T>): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void];
49
+
50
+ export { type Serializable, useStorageState };
@@ -0,0 +1,141 @@
1
+ // src/hooks/useStorageState/useStorageState.ts
2
+ import { useCallback, useRef, useSyncExternalStore } from "react";
3
+
4
+ // src/hooks/useStorageState/storage.ts
5
+ var MemoStorage = class {
6
+ storage = /* @__PURE__ */ new Map();
7
+ get(key) {
8
+ return this.storage.get(key) ?? null;
9
+ }
10
+ set(key, value) {
11
+ this.storage.set(key, value);
12
+ }
13
+ remove(key) {
14
+ this.storage.delete(key);
15
+ }
16
+ clear() {
17
+ this.storage.clear();
18
+ }
19
+ };
20
+ var LocalStorage = class {
21
+ static canUse() {
22
+ const TEST_KEY = generateTestKey();
23
+ try {
24
+ localStorage.setItem(TEST_KEY, "test");
25
+ localStorage.removeItem(TEST_KEY);
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+ get(key) {
32
+ return localStorage.getItem(key);
33
+ }
34
+ set(key, value) {
35
+ localStorage.setItem(key, value);
36
+ }
37
+ remove(key) {
38
+ localStorage.removeItem(key);
39
+ }
40
+ clear() {
41
+ localStorage.clear();
42
+ }
43
+ };
44
+ var SessionStorage = class {
45
+ static canUse() {
46
+ const TEST_KEY = generateTestKey();
47
+ try {
48
+ sessionStorage.setItem(TEST_KEY, "test");
49
+ sessionStorage.removeItem(TEST_KEY);
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ get(key) {
56
+ return sessionStorage.getItem(key);
57
+ }
58
+ set(key, value) {
59
+ sessionStorage.setItem(key, value);
60
+ }
61
+ remove(key) {
62
+ sessionStorage.removeItem(key);
63
+ }
64
+ clear() {
65
+ sessionStorage.clear();
66
+ }
67
+ };
68
+ function generateTestKey() {
69
+ return new Array(4).fill(null).map(() => Math.random().toString(36).slice(2)).join("");
70
+ }
71
+ function generateStorage() {
72
+ if (LocalStorage.canUse()) {
73
+ return new LocalStorage();
74
+ }
75
+ return new MemoStorage();
76
+ }
77
+ function generateSessionStorage() {
78
+ if (SessionStorage.canUse()) {
79
+ return new SessionStorage();
80
+ }
81
+ return new MemoStorage();
82
+ }
83
+ var safeLocalStorage = generateStorage();
84
+ var safeSessionStorage = generateSessionStorage();
85
+
86
+ // src/hooks/useStorageState/useStorageState.ts
87
+ var listeners = /* @__PURE__ */ new Set();
88
+ var emitListeners = () => {
89
+ listeners.forEach((listener) => listener());
90
+ };
91
+ function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {}) {
92
+ const cache = useRef({
93
+ data: null,
94
+ parsed: defaultValue
95
+ });
96
+ const getSnapshot = useCallback(() => {
97
+ const data = storage.get(key);
98
+ if (data !== cache.current.data) {
99
+ try {
100
+ cache.current.parsed = data != null ? JSON.parse(data) : defaultValue;
101
+ } catch {
102
+ cache.current.parsed = defaultValue;
103
+ }
104
+ cache.current.data = data;
105
+ }
106
+ return cache.current.parsed;
107
+ }, [defaultValue, key, storage]);
108
+ const storageState = useSyncExternalStore(
109
+ (onStoreChange) => {
110
+ listeners.add(onStoreChange);
111
+ const handler = (event) => {
112
+ if (event.key === key) {
113
+ onStoreChange();
114
+ }
115
+ };
116
+ window.addEventListener("storage", handler);
117
+ return () => {
118
+ listeners.delete(onStoreChange);
119
+ window.removeEventListener("storage", handler);
120
+ };
121
+ },
122
+ () => getSnapshot(),
123
+ () => defaultValue
124
+ );
125
+ const setStorageState = useCallback(
126
+ (value) => {
127
+ const nextValue = typeof value === "function" ? value(getSnapshot()) : value;
128
+ if (nextValue == null) {
129
+ storage.remove(key);
130
+ } else {
131
+ storage.set(key, JSON.stringify(nextValue));
132
+ }
133
+ emitListeners();
134
+ },
135
+ [getSnapshot, key, storage]
136
+ );
137
+ return [storageState, setStorageState];
138
+ }
139
+ export {
140
+ useStorageState
141
+ };
@@ -0,0 +1,47 @@
1
+ type ThrottleOptions = {
2
+ /**
3
+ * An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
4
+ * If `edges` includes "leading", the function will be invoked at the start of the delay period.
5
+ * If `edges` includes "trailing", the function will be invoked at the end of the delay period.
6
+ * If both "leading" and "trailing" are included, the function will be invoked at both the start and end of the delay period.
7
+ * @default ["leading", "trailing"]
8
+ */
9
+ edges?: Array<'leading' | 'trailing'>;
10
+ };
11
+ type ThrottledFunction<F extends (...args: any[]) => void> = {
12
+ (...args: Parameters<F>): void;
13
+ cancel: () => void;
14
+ };
15
+ declare function throttle<F extends (...args: any[]) => void>(func: F, throttleMs: number, { edges }?: ThrottleOptions): ThrottledFunction<F>;
16
+
17
+ /**
18
+ * @description
19
+ * A React hook that creates a throttled version of a callback function.
20
+ * This is useful for limiting the rate at which a function can be called,
21
+ * such as when handling scroll or resize events.
22
+ *
23
+ * @template F - The type of the callback function.
24
+ * @param {F} callback - The function to be throttled.
25
+ * @param {number} wait - The number of milliseconds to throttle invocations to.
26
+ * @param {{ edges?: Array<'leading' | 'trailing'> }} [options] - Options to control the behavior of the throttle.
27
+ * @returns {F & { cancel: () => void }} - Returns the throttled function with a `cancel` method to cancel pending executions.
28
+ *
29
+ * @example
30
+ * const throttledScroll = useThrottle(() => {
31
+ * console.log('Scroll event');
32
+ * }, 200, { edges: ['leading', 'trailing'] });
33
+ *
34
+ * useEffect(() => {
35
+ * window.addEventListener('scroll', throttledScroll);
36
+ * return () => {
37
+ * window.removeEventListener('scroll', throttledScroll);
38
+ * throttledScroll.cancel();
39
+ * };
40
+ * }, [throttledScroll]);
41
+ */
42
+ declare function useThrottle<F extends (...args: any[]) => any>(callback: F, wait: number, options?: Parameters<typeof throttle>[2]): {
43
+ (...args: any[]): void;
44
+ cancel: () => void;
45
+ };
46
+
47
+ export { useThrottle };