@versini/ui-hooks 5.3.2 → 6.0.1

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 (45) hide show
  1. package/README.md +35 -9
  2. package/dist/__tests__/__mocks__/ResizeObserver.d.ts +21 -0
  3. package/dist/__tests__/useClickOutside.test.d.ts +1 -0
  4. package/dist/__tests__/useHaptic.test.d.ts +1 -0
  5. package/dist/__tests__/useHotkeys.test.d.ts +1 -0
  6. package/dist/__tests__/useInterval.test.d.ts +1 -0
  7. package/dist/__tests__/useIsMounted.test.d.ts +1 -0
  8. package/dist/__tests__/useLocalStorage.test.d.ts +1 -0
  9. package/dist/__tests__/useMergeRefs.test.d.ts +1 -0
  10. package/dist/__tests__/useResizeObserver.test.d.ts +1 -0
  11. package/dist/__tests__/useUncontrolled.test.d.ts +1 -0
  12. package/dist/__tests__/useUniqueId.test.d.ts +1 -0
  13. package/dist/__tests__/useViewportSize.test.d.ts +1 -0
  14. package/dist/__tests__/utilities.test.d.ts +1 -0
  15. package/dist/useClickOutside/useClickOutside.d.ts +17 -0
  16. package/dist/useClickOutside/useClickOutside.js +68 -0
  17. package/dist/useHaptic/useHaptic.d.ts +24 -0
  18. package/dist/useHaptic/useHaptic.js +200 -0
  19. package/dist/useHotkeys/useHotkeys.d.ts +10 -0
  20. package/dist/useHotkeys/useHotkeys.js +65 -0
  21. package/dist/useHotkeys/utilities.d.ts +19 -0
  22. package/dist/useHotkeys/utilities.js +87 -0
  23. package/dist/useInViewport/useInViewport.d.ts +10 -0
  24. package/dist/useInViewport/useInViewport.js +52 -0
  25. package/dist/useInterval/useInterval.d.ts +21 -0
  26. package/dist/useInterval/useInterval.js +75 -0
  27. package/dist/useIsMounted/useIsMounted.d.ts +13 -0
  28. package/dist/useIsMounted/useIsMounted.js +46 -0
  29. package/dist/useLocalStorage/useLocalStorage.d.ts +82 -0
  30. package/dist/useLocalStorage/useLocalStorage.js +236 -0
  31. package/dist/useMergeRefs/useMergeRefs.d.ts +20 -0
  32. package/dist/useMergeRefs/useMergeRefs.js +62 -0
  33. package/dist/useResizeObserver/useResizeObserver.d.ts +17 -0
  34. package/dist/useResizeObserver/useResizeObserver.js +94 -0
  35. package/dist/useUncontrolled/useUncontrolled.d.ts +14 -0
  36. package/dist/useUncontrolled/useUncontrolled.js +76 -0
  37. package/dist/useUniqueId/useUniqueId.d.ts +36 -0
  38. package/dist/useUniqueId/useUniqueId.js +41 -0
  39. package/dist/useViewportSize/useViewportSize.d.ts +13 -0
  40. package/dist/useViewportSize/useViewportSize.js +60 -0
  41. package/dist/useVisualViewportSize/useVisualViewportSize.d.ts +14 -0
  42. package/dist/useVisualViewportSize/useVisualViewportSize.js +70 -0
  43. package/package.json +56 -4
  44. package/dist/index.d.ts +0 -316
  45. package/dist/index.js +0 -958
@@ -0,0 +1,94 @@
1
+ /*!
2
+ @versini/ui-hooks v6.0.1
3
+ © 2025 gizmette.com
4
+ */
5
+ try {
6
+ if (!window.__VERSINI_UI_HOOKS__) {
7
+ window.__VERSINI_UI_HOOKS__ = {
8
+ version: "6.0.1",
9
+ buildTime: "12/24/2025 09:19 AM EST",
10
+ homepage: "https://www.npmjs.com/package/@versini/ui-hooks",
11
+ license: "MIT",
12
+ };
13
+ }
14
+ } catch (error) {
15
+ // nothing to declare officer
16
+ }
17
+
18
+ import { useEffect, useMemo, useRef, useState } from "react";
19
+ import { useIsMounted } from "../useIsMounted/useIsMounted.js";
20
+
21
+ ;// CONCATENATED MODULE: external "react"
22
+
23
+ ;// CONCATENATED MODULE: external "../useIsMounted/useIsMounted.js"
24
+
25
+ ;// CONCATENATED MODULE: ./src/hooks/useResizeObserver/useResizeObserver.ts
26
+
27
+
28
+ const defaultState = {
29
+ x: 0,
30
+ y: 0,
31
+ width: 0,
32
+ height: 0,
33
+ top: 0,
34
+ left: 0,
35
+ bottom: 0,
36
+ right: 0
37
+ };
38
+ /**
39
+ * A custom hook that uses the ResizeObserver API to track the size changes of a DOM element.
40
+ *
41
+ * @template T - The type of the DOM element being observed.
42
+ * @param {ResizeObserverOptions} [options] - The options to configure the ResizeObserver.
43
+ * @returns {[React.RefObject<T>, ObserverRect]} - A tuple containing the ref object and
44
+ * the observed rectangle.
45
+ * @example
46
+ *
47
+ * const [rightElementRef, rect] = useResizeObserver<HTMLDivElement>();
48
+ * <div ref={componentRef}>
49
+ * Observed: <code>{JSON.stringify(rect)}</code>
50
+ * </div>
51
+ */ function useResizeObserver(options) {
52
+ const isMounted = useIsMounted();
53
+ const frameID = useRef(0);
54
+ const ref = useRef(null);
55
+ const [rect, setRect] = useState(defaultState);
56
+ const observer = useMemo(()=>{
57
+ /* v8 ignore start - SSR check for ResizeObserver */ if (typeof ResizeObserver === "undefined") {
58
+ return null;
59
+ }
60
+ /* v8 ignore stop */ return new ResizeObserver((entries)=>{
61
+ /* v8 ignore start - resize entry handling */ const entry = entries[0];
62
+ if (entry) {
63
+ cancelAnimationFrame(frameID.current);
64
+ frameID.current = requestAnimationFrame(()=>{
65
+ if (ref.current && isMounted()) {
66
+ setRect(entry.contentRect);
67
+ }
68
+ });
69
+ }
70
+ /* v8 ignore stop */ });
71
+ }, [
72
+ isMounted
73
+ ]);
74
+ useEffect(()=>{
75
+ /* v8 ignore start - ref.current check */ if (ref.current) {
76
+ observer?.observe(ref.current, options);
77
+ }
78
+ /* v8 ignore stop */ return ()=>{
79
+ observer?.disconnect();
80
+ /* v8 ignore start - frameID cleanup */ if (frameID.current) {
81
+ cancelAnimationFrame(frameID.current);
82
+ }
83
+ /* v8 ignore stop */ };
84
+ }, [
85
+ observer,
86
+ options
87
+ ]);
88
+ return [
89
+ ref,
90
+ rect
91
+ ];
92
+ }
93
+
94
+ export { useResizeObserver };
@@ -0,0 +1,14 @@
1
+ interface UseUncontrolledInput<T> {
2
+ /** Value for controlled state */
3
+ value?: T;
4
+ /** Initial value for uncontrolled state */
5
+ defaultValue?: T;
6
+ /** Final value for uncontrolled state when value and defaultValue are not provided */
7
+ finalValue?: T;
8
+ /** Controlled state onChange handler */
9
+ onChange?: (value: T) => void;
10
+ /** Initial delay for controlled state */
11
+ initialControlledDelay?: number;
12
+ }
13
+ export declare function useUncontrolled<T>({ value, defaultValue, finalValue, onChange, initialControlledDelay, }: UseUncontrolledInput<T>): [T, (value: T) => void, boolean];
14
+ export {};
@@ -0,0 +1,76 @@
1
+ /*!
2
+ @versini/ui-hooks v6.0.1
3
+ © 2025 gizmette.com
4
+ */
5
+ try {
6
+ if (!window.__VERSINI_UI_HOOKS__) {
7
+ window.__VERSINI_UI_HOOKS__ = {
8
+ version: "6.0.1",
9
+ buildTime: "12/24/2025 09:19 AM EST",
10
+ homepage: "https://www.npmjs.com/package/@versini/ui-hooks",
11
+ license: "MIT",
12
+ };
13
+ }
14
+ } catch (error) {
15
+ // nothing to declare officer
16
+ }
17
+
18
+ import { useEffect, useState } from "react";
19
+
20
+ ;// CONCATENATED MODULE: external "react"
21
+
22
+ ;// CONCATENATED MODULE: ./src/hooks/useUncontrolled/useUncontrolled.ts
23
+
24
+ function useUncontrolled({ value, defaultValue, finalValue, onChange = ()=>{}, initialControlledDelay = 0 }) {
25
+ const [initialDelayDone, setInitialDelayDone] = useState(false);
26
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue !== undefined ? defaultValue : finalValue);
27
+ const handleUncontrolledChange = (val)=>{
28
+ setUncontrolledValue(val);
29
+ onChange?.(val);
30
+ };
31
+ useEffect(()=>{
32
+ (async ()=>{
33
+ /**
34
+ * If initialControlledDelay is provided, wait for the delay.
35
+ */ if (value !== undefined) {
36
+ /* c8 ignore start */ if (!initialDelayDone && initialControlledDelay > 0) {
37
+ await new Promise((resolve)=>setTimeout(resolve, initialControlledDelay));
38
+ setInitialDelayDone(true);
39
+ }
40
+ /* c8 ignore end */ }
41
+ })();
42
+ }, [
43
+ value,
44
+ initialControlledDelay,
45
+ initialDelayDone
46
+ ]);
47
+ /**
48
+ * If value is provided, return the controlled value.
49
+ * If there is a delay, we need to wait for the delay: we need to first send
50
+ * back a value of an empty string, then after the delay
51
+ * we can send the actual value.
52
+ */ if (value !== undefined) {
53
+ if (!initialDelayDone && initialControlledDelay > 0) {
54
+ return [
55
+ "",
56
+ onChange,
57
+ true
58
+ ];
59
+ } else {
60
+ return [
61
+ value,
62
+ onChange,
63
+ true
64
+ ];
65
+ }
66
+ }
67
+ /**
68
+ * If value is not provided, return the uncontrolled value.
69
+ */ return [
70
+ uncontrolledValue,
71
+ handleUncontrolledChange,
72
+ false
73
+ ];
74
+ }
75
+
76
+ export { useUncontrolled };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Hook that generates a unique id that will retain its value
3
+ * during the lifecycle of the calling functional component.
4
+ *
5
+ * The parameters are either
6
+ * - nothing: a unique id will simply be generated with no prefix.
7
+ * - a string or a number: a prefix to prepend to the generated id.
8
+ * - an object with the following props:
9
+ * - id: if this is a number or a string, it will be returned as is
10
+ * - prefix: prefix to prepend to the generated id.
11
+ *
12
+ * @param {string | number | object} options
13
+ * @returns a unique id
14
+ *
15
+ * @examples
16
+ *
17
+ * const someId = useUniqueId();
18
+ * // -> someId = "1j3h4f5"
19
+ *
20
+ * const errorId = useUniqueId("av-text-input-error-");
21
+ * // -> errorId = "av-text-input-error-1j3h4f5"
22
+ *
23
+ * const inputId = useUniqueId({ id: 42, prefix: "av-text-input-" });
24
+ * // -> inputId = "av-text-input-42"
25
+ *
26
+ * const inputHintId = useUniqueId({
27
+ * prefix: "av-text-input-hint-",
28
+ * });
29
+ * // -> inputHintId = "av-text-input-hint-1j3h4f5"
30
+ *
31
+ */
32
+ export type UseUniqueIdOptions = string | number | {
33
+ id?: string | number;
34
+ prefix?: string;
35
+ };
36
+ export declare function useUniqueId(options?: UseUniqueIdOptions): string | undefined;
@@ -0,0 +1,41 @@
1
+ /*!
2
+ @versini/ui-hooks v6.0.1
3
+ © 2025 gizmette.com
4
+ */
5
+ try {
6
+ if (!window.__VERSINI_UI_HOOKS__) {
7
+ window.__VERSINI_UI_HOOKS__ = {
8
+ version: "6.0.1",
9
+ buildTime: "12/24/2025 09:19 AM EST",
10
+ homepage: "https://www.npmjs.com/package/@versini/ui-hooks",
11
+ license: "MIT",
12
+ };
13
+ }
14
+ } catch (error) {
15
+ // nothing to declare officer
16
+ }
17
+
18
+ import { useId } from "react";
19
+
20
+ ;// CONCATENATED MODULE: external "react"
21
+
22
+ ;// CONCATENATED MODULE: ./src/hooks/useUniqueId/useUniqueId.ts
23
+
24
+ function useUniqueId(options) {
25
+ const generatedId = useId();
26
+ if (!options) {
27
+ return generatedId;
28
+ }
29
+ if (typeof options === "number" || typeof options === "string") {
30
+ return `${options}${generatedId}`;
31
+ }
32
+ /* v8 ignore start - object options edge case */ if (typeof options === "object") {
33
+ const { id, prefix = "" } = options;
34
+ if (typeof id === "number" || typeof id === "string") {
35
+ return `${prefix}${id}`;
36
+ }
37
+ return `${prefix}${generatedId}`;
38
+ }
39
+ /* v8 ignore stop */ }
40
+
41
+ export { useUniqueId };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Custom hook that returns the current viewport size. It will update
3
+ * when the window is resized or the orientation changes.
4
+ *
5
+ * @returns The current viewport size
6
+ *
7
+ * @example
8
+ * const { width, height } = useViewportSize();
9
+ */
10
+ export declare function useViewportSize(): {
11
+ width: number;
12
+ height: number;
13
+ };
@@ -0,0 +1,60 @@
1
+ /*!
2
+ @versini/ui-hooks v6.0.1
3
+ © 2025 gizmette.com
4
+ */
5
+ try {
6
+ if (!window.__VERSINI_UI_HOOKS__) {
7
+ window.__VERSINI_UI_HOOKS__ = {
8
+ version: "6.0.1",
9
+ buildTime: "12/24/2025 09:19 AM EST",
10
+ homepage: "https://www.npmjs.com/package/@versini/ui-hooks",
11
+ license: "MIT",
12
+ };
13
+ }
14
+ } catch (error) {
15
+ // nothing to declare officer
16
+ }
17
+
18
+ import { useCallback, useEffect, useState } from "react";
19
+
20
+ ;// CONCATENATED MODULE: external "react"
21
+
22
+ ;// CONCATENATED MODULE: ./src/hooks/useViewportSize/useViewportSize.tsx
23
+
24
+ function useWindowEvent(type, listener) {
25
+ useEffect(()=>{
26
+ window.addEventListener(type, listener, {
27
+ passive: true
28
+ });
29
+ return ()=>window.removeEventListener(type, listener);
30
+ }, [
31
+ type,
32
+ listener
33
+ ]);
34
+ }
35
+ /**
36
+ * Custom hook that returns the current viewport size. It will update
37
+ * when the window is resized or the orientation changes.
38
+ *
39
+ * @returns The current viewport size
40
+ *
41
+ * @example
42
+ * const { width, height } = useViewportSize();
43
+ */ function useViewportSize() {
44
+ const [windowSize, setWindowSize] = useState({
45
+ width: 0,
46
+ height: 0
47
+ });
48
+ const setSize = useCallback(()=>{
49
+ setWindowSize({
50
+ width: window.innerWidth || 0,
51
+ height: window.innerHeight || 0
52
+ });
53
+ }, []);
54
+ useWindowEvent("resize", setSize);
55
+ useWindowEvent("orientationchange", setSize);
56
+ useEffect(setSize, []);
57
+ return windowSize;
58
+ }
59
+
60
+ export { useViewportSize };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Custom hook that returns the current visual viewport size. It will update
3
+ * when the window is resized (zoom, virtual keyboard displayed, etc.) or
4
+ * the orientation changes.
5
+ *
6
+ * @returns The current visual viewport size
7
+ *
8
+ * @example
9
+ * const { width, height } = useVisualViewportSize();
10
+ */
11
+ export declare function useVisualViewportSize(): {
12
+ width: number;
13
+ height: number;
14
+ };
@@ -0,0 +1,70 @@
1
+ /*!
2
+ @versini/ui-hooks v6.0.1
3
+ © 2025 gizmette.com
4
+ */
5
+ try {
6
+ if (!window.__VERSINI_UI_HOOKS__) {
7
+ window.__VERSINI_UI_HOOKS__ = {
8
+ version: "6.0.1",
9
+ buildTime: "12/24/2025 09:19 AM EST",
10
+ homepage: "https://www.npmjs.com/package/@versini/ui-hooks",
11
+ license: "MIT",
12
+ };
13
+ }
14
+ } catch (error) {
15
+ // nothing to declare officer
16
+ }
17
+
18
+ import { useCallback, useEffect, useState } from "react";
19
+
20
+ ;// CONCATENATED MODULE: external "react"
21
+
22
+ ;// CONCATENATED MODULE: ./src/hooks/useVisualViewportSize/useVisualViewportSize.tsx
23
+ /* v8 ignore start */
24
+ /**
25
+ * Custom hook that returns the current visual viewport size. It will update
26
+ * when the window is resized (zoom, virtual keyboard displayed, etc.) or
27
+ * the orientation changes.
28
+ *
29
+ * @returns The current visual viewport size
30
+ *
31
+ * @example
32
+ * const { width, height } = useVisualViewportSize();
33
+ */ function useVisualViewportSize() {
34
+ const [windowSize, setWindowSize] = useState({
35
+ width: 0,
36
+ height: 0
37
+ });
38
+ // Define setSize function once and never recreate it
39
+ const setSize = useCallback(()=>{
40
+ setWindowSize({
41
+ width: window?.visualViewport?.width || window.innerWidth || 0,
42
+ height: window?.visualViewport?.height || window.innerHeight || 0
43
+ });
44
+ }, []); // Empty dependency array is correct here
45
+ useEffect(()=>{
46
+ // Initial size setup
47
+ setSize();
48
+ // Set up event listeners
49
+ if (window.visualViewport) {
50
+ window.visualViewport.addEventListener("resize", setSize, {
51
+ passive: true
52
+ });
53
+ window.visualViewport.addEventListener("orientationchange", setSize, {
54
+ passive: true
55
+ });
56
+ }
57
+ // Cleanup
58
+ return ()=>{
59
+ if (window.visualViewport) {
60
+ window.visualViewport.removeEventListener("resize", setSize);
61
+ window.visualViewport.removeEventListener("orientationchange", setSize);
62
+ }
63
+ };
64
+ }, [
65
+ setSize
66
+ ]);
67
+ return windowSize;
68
+ } /* v8 ignore end */
69
+
70
+ export { useVisualViewportSize };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versini/ui-hooks",
3
- "version": "5.3.2",
3
+ "version": "6.0.1",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
@@ -12,8 +12,60 @@
12
12
  "url": "git@github.com:aversini/ui-components.git"
13
13
  },
14
14
  "type": "module",
15
- "main": "dist/index.js",
16
- "types": "dist/index.d.ts",
15
+ "exports": {
16
+ "./use-click-outside": {
17
+ "types": "./dist/useClickOutside/useClickOutside.d.ts",
18
+ "import": "./dist/useClickOutside/useClickOutside.js"
19
+ },
20
+ "./use-haptic": {
21
+ "types": "./dist/useHaptic/useHaptic.d.ts",
22
+ "import": "./dist/useHaptic/useHaptic.js"
23
+ },
24
+ "./use-hotkeys": {
25
+ "types": "./dist/useHotkeys/useHotkeys.d.ts",
26
+ "import": "./dist/useHotkeys/useHotkeys.js"
27
+ },
28
+ "./use-interval": {
29
+ "types": "./dist/useInterval/useInterval.d.ts",
30
+ "import": "./dist/useInterval/useInterval.js"
31
+ },
32
+ "./use-in-viewport": {
33
+ "types": "./dist/useInViewport/useInViewport.d.ts",
34
+ "import": "./dist/useInViewport/useInViewport.js"
35
+ },
36
+ "./use-is-mounted": {
37
+ "types": "./dist/useIsMounted/useIsMounted.d.ts",
38
+ "import": "./dist/useIsMounted/useIsMounted.js"
39
+ },
40
+ "./use-local-storage": {
41
+ "types": "./dist/useLocalStorage/useLocalStorage.d.ts",
42
+ "import": "./dist/useLocalStorage/useLocalStorage.js"
43
+ },
44
+ "./use-merge-refs": {
45
+ "types": "./dist/useMergeRefs/useMergeRefs.d.ts",
46
+ "import": "./dist/useMergeRefs/useMergeRefs.js"
47
+ },
48
+ "./use-resize-observer": {
49
+ "types": "./dist/useResizeObserver/useResizeObserver.d.ts",
50
+ "import": "./dist/useResizeObserver/useResizeObserver.js"
51
+ },
52
+ "./use-uncontrolled": {
53
+ "types": "./dist/useUncontrolled/useUncontrolled.d.ts",
54
+ "import": "./dist/useUncontrolled/useUncontrolled.js"
55
+ },
56
+ "./use-unique-id": {
57
+ "types": "./dist/useUniqueId/useUniqueId.d.ts",
58
+ "import": "./dist/useUniqueId/useUniqueId.js"
59
+ },
60
+ "./use-viewport-size": {
61
+ "types": "./dist/useViewportSize/useViewportSize.d.ts",
62
+ "import": "./dist/useViewportSize/useViewportSize.js"
63
+ },
64
+ "./use-visual-viewport-size": {
65
+ "types": "./dist/useVisualViewportSize/useVisualViewportSize.d.ts",
66
+ "import": "./dist/useVisualViewportSize/useVisualViewportSize.js"
67
+ }
68
+ },
17
69
  "files": [
18
70
  "dist",
19
71
  "README.md"
@@ -36,5 +88,5 @@
36
88
  "test:watch": "vitest",
37
89
  "test": "vitest run"
38
90
  },
39
- "gitHead": "ef4323fa1f14f9f2a5471d71f063519230762aec"
91
+ "gitHead": "7b2640a0650a4c3aa6ca078888f765cb400f9f13"
40
92
  }