react-simplikit 0.0.29 → 0.0.31

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.
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/hooks/useDoubleClick/index.ts
21
+ var useDoubleClick_exports = {};
22
+ __export(useDoubleClick_exports, {
23
+ useDoubleClick: () => useDoubleClick
24
+ });
25
+ module.exports = __toCommonJS(useDoubleClick_exports);
26
+
27
+ // src/hooks/useDoubleClick/useDoubleClick.ts
28
+ var import_react2 = require("react");
29
+
30
+ // src/hooks/usePreservedCallback/usePreservedCallback.ts
31
+ var import_react = require("react");
32
+ function usePreservedCallback(callback) {
33
+ const callbackRef = (0, import_react.useRef)(callback);
34
+ (0, import_react.useEffect)(() => {
35
+ callbackRef.current = callback;
36
+ }, [callback]);
37
+ return (0, import_react.useCallback)((...args) => {
38
+ return callbackRef.current(...args);
39
+ }, []);
40
+ }
41
+
42
+ // src/hooks/useDoubleClick/useDoubleClick.ts
43
+ function useDoubleClick({
44
+ delay = 250,
45
+ click,
46
+ doubleClick
47
+ }) {
48
+ const clickTimeout = (0, import_react2.useRef)(null);
49
+ const clearClickTimeout = usePreservedCallback(() => {
50
+ if (clickTimeout.current != null) {
51
+ window.clearTimeout(clickTimeout.current);
52
+ clickTimeout.current = null;
53
+ }
54
+ });
55
+ (0, import_react2.useEffect)(() => () => clearClickTimeout(), [clearClickTimeout]);
56
+ const handleEvent = (0, import_react2.useCallback)(
57
+ (event) => {
58
+ clearClickTimeout();
59
+ if (click && event.detail === 1) {
60
+ clickTimeout.current = window.setTimeout(() => {
61
+ click(event);
62
+ }, delay);
63
+ }
64
+ if (event.detail === 2) {
65
+ doubleClick(event);
66
+ }
67
+ },
68
+ [click, doubleClick, delay, clearClickTimeout]
69
+ );
70
+ return handleEvent;
71
+ }
72
+ // Annotate the CommonJS export names for ESM import in node:
73
+ 0 && (module.exports = {
74
+ useDoubleClick
75
+ });
@@ -0,0 +1,42 @@
1
+ import { MouseEvent } from 'react';
2
+
3
+ type UseDoubleClickProps<E extends HTMLElement> = {
4
+ delay?: number;
5
+ click?: (event: MouseEvent<E>) => void;
6
+ doubleClick: (event: MouseEvent<E>) => void;
7
+ };
8
+ /**
9
+ * @description
10
+ * `useDoubleClick` is a React hook that differentiates between single and double click events.
11
+ * It delays the single click callback execution for a specified time, and cancels it if a second click (i.e. a double click) occurs within that time.
12
+ *
13
+ * @template {HTMLElement} E - The specific type of HTMLElement to be used with this hook (e.g., HTMLButtonElement, HTMLDivElement).
14
+ * @param {Object} params - Configuration options for click handling.
15
+ * @param {number} [params.delay=250] - The number of milliseconds to wait before triggering the single click callback. Defaults to 250ms.
16
+ * @param {(event: MouseEvent<E>) => void} [params.click] - The callback function to be executed on a single click.
17
+ * @param {(event: MouseEvent<E>) => void} params.doubleClick - The callback function to be executed on a double click. Required.
18
+ *
19
+ * @returns {(event: MouseEvent<E>) => void} A click handler function to attach to an element's `onClick` event.
20
+ *
21
+ * @example
22
+ * function GalleryCard() {
23
+ * const [selected, setSelected] = useState(false);
24
+ *
25
+ * const handleClick = () => setSelected((prev) => !prev);
26
+ * const handleDoubleClick = () => alert('Zoom in!');
27
+ *
28
+ * const handleEvent = useDoubleClick({
29
+ * click: handleClick,
30
+ * doubleClick: handleDoubleClick,
31
+ * });
32
+ *
33
+ * return (
34
+ * <div onClick={handleEvent}>
35
+ * {selected ? 'Selected' : 'Not selected'}
36
+ * </div>
37
+ * );
38
+ * }
39
+ */
40
+ declare function useDoubleClick<E extends HTMLElement = HTMLElement>({ delay, click, doubleClick, }: UseDoubleClickProps<E>): (event: MouseEvent<E>) => void;
41
+
42
+ export { useDoubleClick };
@@ -114,18 +114,41 @@ var listeners = /* @__PURE__ */ new Set();
114
114
  var emitListeners = () => {
115
115
  listeners.forEach((listener) => listener());
116
116
  };
117
- function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {}) {
117
+ function isPlainObject(value) {
118
+ if (typeof value !== "object") {
119
+ return false;
120
+ }
121
+ const proto = Object.getPrototypeOf(value);
122
+ const hasObjectPrototype = proto === Object.prototype;
123
+ if (!hasObjectPrototype) {
124
+ return false;
125
+ }
126
+ return Object.prototype.toString.call(value) === "[object Object]";
127
+ }
128
+ var ensureSerializable = (value) => {
129
+ if (value[0] != null && !["string", "number", "boolean"].includes(typeof value[0]) && !(isPlainObject(value[0]) || Array.isArray(value[0]))) {
130
+ throw new Error("Received a non-serializable value");
131
+ }
132
+ return value;
133
+ };
134
+ function useStorageState(key, {
135
+ storage = safeLocalStorage,
136
+ defaultValue,
137
+ ...options
138
+ } = {}) {
139
+ const serializedDefaultValue = defaultValue;
118
140
  const cache = (0, import_react.useRef)({
119
141
  data: null,
120
- parsed: defaultValue
142
+ parsed: serializedDefaultValue
121
143
  });
122
144
  const getSnapshot = (0, import_react.useCallback)(() => {
145
+ const deserializer = "deserializer" in options ? options.deserializer : JSON.parse;
123
146
  const data = storage.get(key);
124
147
  if (data !== cache.current.data) {
125
148
  try {
126
- cache.current.parsed = data != null ? JSON.parse(data) : defaultValue;
149
+ cache.current.parsed = data != null ? deserializer(data) : defaultValue;
127
150
  } catch {
128
- cache.current.parsed = defaultValue;
151
+ cache.current.parsed = serializedDefaultValue;
129
152
  }
130
153
  cache.current.data = data;
131
154
  }
@@ -146,21 +169,25 @@ function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {})
146
169
  };
147
170
  },
148
171
  () => getSnapshot(),
149
- () => defaultValue
172
+ () => serializedDefaultValue
150
173
  );
151
174
  const setStorageState = (0, import_react.useCallback)(
152
175
  (value) => {
176
+ const serializer = "serializer" in options ? options.serializer : JSON.stringify;
153
177
  const nextValue = typeof value === "function" ? value(getSnapshot()) : value;
154
178
  if (nextValue == null) {
155
179
  storage.remove(key);
156
180
  } else {
157
- storage.set(key, JSON.stringify(nextValue));
181
+ storage.set(key, serializer(nextValue));
158
182
  }
159
183
  emitListeners();
160
184
  },
161
185
  [getSnapshot, key, storage]
162
186
  );
163
- return [storageState, setStorageState];
187
+ const refreshStorageState = (0, import_react.useCallback)(() => {
188
+ setStorageState(getSnapshot());
189
+ }, [storage, getSnapshot, setStorageState]);
190
+ return ensureSerializable([storageState, setStorageState, refreshStorageState]);
164
191
  }
165
192
  // Annotate the CommonJS export names for ESM import in node:
166
193
  0 && (module.exports = {
@@ -7,16 +7,20 @@ type Storage = {
7
7
  clear(): void;
8
8
  };
9
9
 
10
- type ToPrimitive<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : never;
11
10
  type ToObject<T> = T extends unknown[] | Record<string, unknown> ? T : never;
12
- type Serializable<T> = T extends string | number | boolean ? ToPrimitive<T> : ToObject<T>;
11
+ type Serializable<T> = T extends string | number | boolean ? T : ToObject<T>;
13
12
  type StorageStateOptions<T> = {
14
13
  storage?: Storage;
15
- defaultValue?: Serializable<T>;
14
+ defaultValue?: T;
16
15
  };
17
16
  type StorageStateOptionsWithDefaultValue<T> = StorageStateOptions<T> & {
18
- defaultValue: Serializable<T>;
17
+ defaultValue: T;
19
18
  };
19
+ type StorageStateOptionsWithSerializer<T> = StorageStateOptions<T> & {
20
+ serializer: (value: Serializable<T>) => string;
21
+ deserializer: (value: string) => Serializable<T>;
22
+ };
23
+ type SerializableGuard<T extends readonly any[]> = T[0] extends any ? T : T[0] extends never ? 'Received a non-serializable value' : T;
20
24
  /**
21
25
  * @description
22
26
  * A React hook that functions like `useState` but persists the state value in browser storage.
@@ -26,11 +30,13 @@ type StorageStateOptionsWithDefaultValue<T> = StorageStateOptions<T> & {
26
30
  * @param {Object} [options] - Configuration options for storage behavior.
27
31
  * @param {Storage} [options.storage=localStorage] - The storage type (`localStorage` or `sessionStorage`). Defaults to `localStorage`.
28
32
  * @param {T} [options.defaultValue] - The initial value if no existing value is found.
33
+ * @param {Function} [options.serializer] - A function to serialize the state value to a string.
34
+ * @param {Function} [options.deserializer] - A function to deserialize the state value from a string.
29
35
  *
30
- * @returns {readonly [state: Serializable<T> | undefined, setState: (value: SetStateAction<Serializable<T> | undefined>) => void]} A tuple:
36
+ * @returns {readonly [state: Serializable<T> | undefined, setState: (value: SetStateAction<Serializable<T> | undefined>) => void, refreshState: () => void]} A tuple:
31
37
  * - state `Serializable<T> | undefined` - The current state value retrieved from storage;
32
38
  * - setState `(value: SetStateAction<Serializable<T> | undefined>) => void` - A function to update and persist the state;
33
- *
39
+ * - refreshState `() => void` - A function to refresh the state from storage;
34
40
  * @example
35
41
  * // Counter with persistent state
36
42
  * import { useStorageState } from 'react-simplikit';
@@ -43,8 +49,9 @@ type StorageStateOptionsWithDefaultValue<T> = StorageStateOptions<T> & {
43
49
  * return <button onClick={() => setCount(prev => prev + 1)}>Count: {count}</button>;
44
50
  * }
45
51
  */
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];
52
+ declare function useStorageState<T>(key: string): SerializableGuard<readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void]>;
53
+ declare function useStorageState<T>(key: string, options: StorageStateOptionsWithDefaultValue<T>): SerializableGuard<readonly [Serializable<T>, (value: SetStateAction<Serializable<T>>) => void, () => void]>;
54
+ declare function useStorageState<T>(key: string, options: StorageStateOptions<T>): SerializableGuard<readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void]>;
55
+ declare function useStorageState<T>(key: string, options: StorageStateOptionsWithSerializer<T>): SerializableGuard<readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void]>;
49
56
 
50
57
  export { type Serializable, useStorageState };
package/dist/index.cjs CHANGED
@@ -620,18 +620,41 @@ var listeners = /* @__PURE__ */ new Set();
620
620
  var emitListeners = () => {
621
621
  listeners.forEach((listener) => listener());
622
622
  };
623
- function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {}) {
623
+ function isPlainObject(value) {
624
+ if (typeof value !== "object") {
625
+ return false;
626
+ }
627
+ const proto = Object.getPrototypeOf(value);
628
+ const hasObjectPrototype = proto === Object.prototype;
629
+ if (!hasObjectPrototype) {
630
+ return false;
631
+ }
632
+ return Object.prototype.toString.call(value) === "[object Object]";
633
+ }
634
+ var ensureSerializable = (value) => {
635
+ if (value[0] != null && !["string", "number", "boolean"].includes(typeof value[0]) && !(isPlainObject(value[0]) || Array.isArray(value[0]))) {
636
+ throw new Error("Received a non-serializable value");
637
+ }
638
+ return value;
639
+ };
640
+ function useStorageState(key, {
641
+ storage = safeLocalStorage,
642
+ defaultValue,
643
+ ...options
644
+ } = {}) {
645
+ const serializedDefaultValue = defaultValue;
624
646
  const cache = (0, import_react19.useRef)({
625
647
  data: null,
626
- parsed: defaultValue
648
+ parsed: serializedDefaultValue
627
649
  });
628
650
  const getSnapshot = (0, import_react19.useCallback)(() => {
651
+ const deserializer = "deserializer" in options ? options.deserializer : JSON.parse;
629
652
  const data = storage.get(key);
630
653
  if (data !== cache.current.data) {
631
654
  try {
632
- cache.current.parsed = data != null ? JSON.parse(data) : defaultValue;
655
+ cache.current.parsed = data != null ? deserializer(data) : defaultValue;
633
656
  } catch {
634
- cache.current.parsed = defaultValue;
657
+ cache.current.parsed = serializedDefaultValue;
635
658
  }
636
659
  cache.current.data = data;
637
660
  }
@@ -652,21 +675,25 @@ function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {})
652
675
  };
653
676
  },
654
677
  () => getSnapshot(),
655
- () => defaultValue
678
+ () => serializedDefaultValue
656
679
  );
657
680
  const setStorageState = (0, import_react19.useCallback)(
658
681
  (value) => {
682
+ const serializer = "serializer" in options ? options.serializer : JSON.stringify;
659
683
  const nextValue = typeof value === "function" ? value(getSnapshot()) : value;
660
684
  if (nextValue == null) {
661
685
  storage.remove(key);
662
686
  } else {
663
- storage.set(key, JSON.stringify(nextValue));
687
+ storage.set(key, serializer(nextValue));
664
688
  }
665
689
  emitListeners();
666
690
  },
667
691
  [getSnapshot, key, storage]
668
692
  );
669
- return [storageState, setStorageState];
693
+ const refreshStorageState = (0, import_react19.useCallback)(() => {
694
+ setStorageState(getSnapshot());
695
+ }, [storage, getSnapshot, setStorageState]);
696
+ return ensureSerializable([storageState, setStorageState, refreshStorageState]);
670
697
  }
671
698
 
672
699
  // src/hooks/useThrottle/useThrottle.ts
@@ -0,0 +1,42 @@
1
+ import { MouseEvent } from 'react';
2
+
3
+ type UseDoubleClickProps<E extends HTMLElement> = {
4
+ delay?: number;
5
+ click?: (event: MouseEvent<E>) => void;
6
+ doubleClick: (event: MouseEvent<E>) => void;
7
+ };
8
+ /**
9
+ * @description
10
+ * `useDoubleClick` is a React hook that differentiates between single and double click events.
11
+ * It delays the single click callback execution for a specified time, and cancels it if a second click (i.e. a double click) occurs within that time.
12
+ *
13
+ * @template {HTMLElement} E - The specific type of HTMLElement to be used with this hook (e.g., HTMLButtonElement, HTMLDivElement).
14
+ * @param {Object} params - Configuration options for click handling.
15
+ * @param {number} [params.delay=250] - The number of milliseconds to wait before triggering the single click callback. Defaults to 250ms.
16
+ * @param {(event: MouseEvent<E>) => void} [params.click] - The callback function to be executed on a single click.
17
+ * @param {(event: MouseEvent<E>) => void} params.doubleClick - The callback function to be executed on a double click. Required.
18
+ *
19
+ * @returns {(event: MouseEvent<E>) => void} A click handler function to attach to an element's `onClick` event.
20
+ *
21
+ * @example
22
+ * function GalleryCard() {
23
+ * const [selected, setSelected] = useState(false);
24
+ *
25
+ * const handleClick = () => setSelected((prev) => !prev);
26
+ * const handleDoubleClick = () => alert('Zoom in!');
27
+ *
28
+ * const handleEvent = useDoubleClick({
29
+ * click: handleClick,
30
+ * doubleClick: handleDoubleClick,
31
+ * });
32
+ *
33
+ * return (
34
+ * <div onClick={handleEvent}>
35
+ * {selected ? 'Selected' : 'Not selected'}
36
+ * </div>
37
+ * );
38
+ * }
39
+ */
40
+ declare function useDoubleClick<E extends HTMLElement = HTMLElement>({ delay, click, doubleClick, }: UseDoubleClickProps<E>): (event: MouseEvent<E>) => void;
41
+
42
+ export { useDoubleClick };
@@ -0,0 +1,48 @@
1
+ // src/hooks/useDoubleClick/useDoubleClick.ts
2
+ import { useCallback as useCallback2, 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/useDoubleClick/useDoubleClick.ts
17
+ function useDoubleClick({
18
+ delay = 250,
19
+ click,
20
+ doubleClick
21
+ }) {
22
+ const clickTimeout = useRef2(null);
23
+ const clearClickTimeout = usePreservedCallback(() => {
24
+ if (clickTimeout.current != null) {
25
+ window.clearTimeout(clickTimeout.current);
26
+ clickTimeout.current = null;
27
+ }
28
+ });
29
+ useEffect2(() => () => clearClickTimeout(), [clearClickTimeout]);
30
+ const handleEvent = useCallback2(
31
+ (event) => {
32
+ clearClickTimeout();
33
+ if (click && event.detail === 1) {
34
+ clickTimeout.current = window.setTimeout(() => {
35
+ click(event);
36
+ }, delay);
37
+ }
38
+ if (event.detail === 2) {
39
+ doubleClick(event);
40
+ }
41
+ },
42
+ [click, doubleClick, delay, clearClickTimeout]
43
+ );
44
+ return handleEvent;
45
+ }
46
+ export {
47
+ useDoubleClick
48
+ };
@@ -7,16 +7,20 @@ type Storage = {
7
7
  clear(): void;
8
8
  };
9
9
 
10
- type ToPrimitive<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : never;
11
10
  type ToObject<T> = T extends unknown[] | Record<string, unknown> ? T : never;
12
- type Serializable<T> = T extends string | number | boolean ? ToPrimitive<T> : ToObject<T>;
11
+ type Serializable<T> = T extends string | number | boolean ? T : ToObject<T>;
13
12
  type StorageStateOptions<T> = {
14
13
  storage?: Storage;
15
- defaultValue?: Serializable<T>;
14
+ defaultValue?: T;
16
15
  };
17
16
  type StorageStateOptionsWithDefaultValue<T> = StorageStateOptions<T> & {
18
- defaultValue: Serializable<T>;
17
+ defaultValue: T;
19
18
  };
19
+ type StorageStateOptionsWithSerializer<T> = StorageStateOptions<T> & {
20
+ serializer: (value: Serializable<T>) => string;
21
+ deserializer: (value: string) => Serializable<T>;
22
+ };
23
+ type SerializableGuard<T extends readonly any[]> = T[0] extends any ? T : T[0] extends never ? 'Received a non-serializable value' : T;
20
24
  /**
21
25
  * @description
22
26
  * A React hook that functions like `useState` but persists the state value in browser storage.
@@ -26,11 +30,13 @@ type StorageStateOptionsWithDefaultValue<T> = StorageStateOptions<T> & {
26
30
  * @param {Object} [options] - Configuration options for storage behavior.
27
31
  * @param {Storage} [options.storage=localStorage] - The storage type (`localStorage` or `sessionStorage`). Defaults to `localStorage`.
28
32
  * @param {T} [options.defaultValue] - The initial value if no existing value is found.
33
+ * @param {Function} [options.serializer] - A function to serialize the state value to a string.
34
+ * @param {Function} [options.deserializer] - A function to deserialize the state value from a string.
29
35
  *
30
- * @returns {readonly [state: Serializable<T> | undefined, setState: (value: SetStateAction<Serializable<T> | undefined>) => void]} A tuple:
36
+ * @returns {readonly [state: Serializable<T> | undefined, setState: (value: SetStateAction<Serializable<T> | undefined>) => void, refreshState: () => void]} A tuple:
31
37
  * - state `Serializable<T> | undefined` - The current state value retrieved from storage;
32
38
  * - setState `(value: SetStateAction<Serializable<T> | undefined>) => void` - A function to update and persist the state;
33
- *
39
+ * - refreshState `() => void` - A function to refresh the state from storage;
34
40
  * @example
35
41
  * // Counter with persistent state
36
42
  * import { useStorageState } from 'react-simplikit';
@@ -43,8 +49,9 @@ type StorageStateOptionsWithDefaultValue<T> = StorageStateOptions<T> & {
43
49
  * return <button onClick={() => setCount(prev => prev + 1)}>Count: {count}</button>;
44
50
  * }
45
51
  */
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];
52
+ declare function useStorageState<T>(key: string): SerializableGuard<readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void]>;
53
+ declare function useStorageState<T>(key: string, options: StorageStateOptionsWithDefaultValue<T>): SerializableGuard<readonly [Serializable<T>, (value: SetStateAction<Serializable<T>>) => void, () => void]>;
54
+ declare function useStorageState<T>(key: string, options: StorageStateOptions<T>): SerializableGuard<readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void]>;
55
+ declare function useStorageState<T>(key: string, options: StorageStateOptionsWithSerializer<T>): SerializableGuard<readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void]>;
49
56
 
50
57
  export { type Serializable, useStorageState };
@@ -88,18 +88,41 @@ var listeners = /* @__PURE__ */ new Set();
88
88
  var emitListeners = () => {
89
89
  listeners.forEach((listener) => listener());
90
90
  };
91
- function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {}) {
91
+ function isPlainObject(value) {
92
+ if (typeof value !== "object") {
93
+ return false;
94
+ }
95
+ const proto = Object.getPrototypeOf(value);
96
+ const hasObjectPrototype = proto === Object.prototype;
97
+ if (!hasObjectPrototype) {
98
+ return false;
99
+ }
100
+ return Object.prototype.toString.call(value) === "[object Object]";
101
+ }
102
+ var ensureSerializable = (value) => {
103
+ if (value[0] != null && !["string", "number", "boolean"].includes(typeof value[0]) && !(isPlainObject(value[0]) || Array.isArray(value[0]))) {
104
+ throw new Error("Received a non-serializable value");
105
+ }
106
+ return value;
107
+ };
108
+ function useStorageState(key, {
109
+ storage = safeLocalStorage,
110
+ defaultValue,
111
+ ...options
112
+ } = {}) {
113
+ const serializedDefaultValue = defaultValue;
92
114
  const cache = useRef({
93
115
  data: null,
94
- parsed: defaultValue
116
+ parsed: serializedDefaultValue
95
117
  });
96
118
  const getSnapshot = useCallback(() => {
119
+ const deserializer = "deserializer" in options ? options.deserializer : JSON.parse;
97
120
  const data = storage.get(key);
98
121
  if (data !== cache.current.data) {
99
122
  try {
100
- cache.current.parsed = data != null ? JSON.parse(data) : defaultValue;
123
+ cache.current.parsed = data != null ? deserializer(data) : defaultValue;
101
124
  } catch {
102
- cache.current.parsed = defaultValue;
125
+ cache.current.parsed = serializedDefaultValue;
103
126
  }
104
127
  cache.current.data = data;
105
128
  }
@@ -120,21 +143,25 @@ function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {})
120
143
  };
121
144
  },
122
145
  () => getSnapshot(),
123
- () => defaultValue
146
+ () => serializedDefaultValue
124
147
  );
125
148
  const setStorageState = useCallback(
126
149
  (value) => {
150
+ const serializer = "serializer" in options ? options.serializer : JSON.stringify;
127
151
  const nextValue = typeof value === "function" ? value(getSnapshot()) : value;
128
152
  if (nextValue == null) {
129
153
  storage.remove(key);
130
154
  } else {
131
- storage.set(key, JSON.stringify(nextValue));
155
+ storage.set(key, serializer(nextValue));
132
156
  }
133
157
  emitListeners();
134
158
  },
135
159
  [getSnapshot, key, storage]
136
160
  );
137
- return [storageState, setStorageState];
161
+ const refreshStorageState = useCallback(() => {
162
+ setStorageState(getSnapshot());
163
+ }, [storage, getSnapshot, setStorageState]);
164
+ return ensureSerializable([storageState, setStorageState, refreshStorageState]);
138
165
  }
139
166
  export {
140
167
  useStorageState
package/esm/index.js CHANGED
@@ -571,18 +571,41 @@ var listeners = /* @__PURE__ */ new Set();
571
571
  var emitListeners = () => {
572
572
  listeners.forEach((listener) => listener());
573
573
  };
574
- function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {}) {
574
+ function isPlainObject(value) {
575
+ if (typeof value !== "object") {
576
+ return false;
577
+ }
578
+ const proto = Object.getPrototypeOf(value);
579
+ const hasObjectPrototype = proto === Object.prototype;
580
+ if (!hasObjectPrototype) {
581
+ return false;
582
+ }
583
+ return Object.prototype.toString.call(value) === "[object Object]";
584
+ }
585
+ var ensureSerializable = (value) => {
586
+ if (value[0] != null && !["string", "number", "boolean"].includes(typeof value[0]) && !(isPlainObject(value[0]) || Array.isArray(value[0]))) {
587
+ throw new Error("Received a non-serializable value");
588
+ }
589
+ return value;
590
+ };
591
+ function useStorageState(key, {
592
+ storage = safeLocalStorage,
593
+ defaultValue,
594
+ ...options
595
+ } = {}) {
596
+ const serializedDefaultValue = defaultValue;
575
597
  const cache = useRef10({
576
598
  data: null,
577
- parsed: defaultValue
599
+ parsed: serializedDefaultValue
578
600
  });
579
601
  const getSnapshot = useCallback8(() => {
602
+ const deserializer = "deserializer" in options ? options.deserializer : JSON.parse;
580
603
  const data = storage.get(key);
581
604
  if (data !== cache.current.data) {
582
605
  try {
583
- cache.current.parsed = data != null ? JSON.parse(data) : defaultValue;
606
+ cache.current.parsed = data != null ? deserializer(data) : defaultValue;
584
607
  } catch {
585
- cache.current.parsed = defaultValue;
608
+ cache.current.parsed = serializedDefaultValue;
586
609
  }
587
610
  cache.current.data = data;
588
611
  }
@@ -603,21 +626,25 @@ function useStorageState(key, { storage = safeLocalStorage, defaultValue } = {})
603
626
  };
604
627
  },
605
628
  () => getSnapshot(),
606
- () => defaultValue
629
+ () => serializedDefaultValue
607
630
  );
608
631
  const setStorageState = useCallback8(
609
632
  (value) => {
633
+ const serializer = "serializer" in options ? options.serializer : JSON.stringify;
610
634
  const nextValue = typeof value === "function" ? value(getSnapshot()) : value;
611
635
  if (nextValue == null) {
612
636
  storage.remove(key);
613
637
  } else {
614
- storage.set(key, JSON.stringify(nextValue));
638
+ storage.set(key, serializer(nextValue));
615
639
  }
616
640
  emitListeners();
617
641
  },
618
642
  [getSnapshot, key, storage]
619
643
  );
620
- return [storageState, setStorageState];
644
+ const refreshStorageState = useCallback8(() => {
645
+ setStorageState(getSnapshot());
646
+ }, [storage, getSnapshot, setStorageState]);
647
+ return ensureSerializable([storageState, setStorageState, refreshStorageState]);
621
648
  }
622
649
 
623
650
  // src/hooks/useThrottle/useThrottle.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-simplikit",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "main": "./dist/index.cjs",
5
5
  "type": "module",
6
6
  "sideEffects": false,