@versini/ui-hooks 5.3.0 → 5.3.2

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/dist/index.d.ts CHANGED
@@ -55,39 +55,6 @@ export declare interface StorageProperties<T> {
55
55
  */
56
56
  export declare function useClickOutside<T extends HTMLElement = any>(handler: () => void, events?: string[] | null, nodes?: (HTMLElement | null)[]): RefObject<T | null>;
57
57
 
58
- /**
59
- * Custom hook for trapping focus within a container element. Implements W3C
60
- * WAI-ARIA dialog focus management patterns:
61
- * - Tab/Shift+Tab cycles through focusable elements within the container
62
- * - Focus is trapped and cannot escape to elements outside
63
- * - Initial focus is set based on the initialFocus option
64
- *
65
- * @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
66
- *
67
- */
68
- export declare function useFocusTrap({ enabled, initialFocus, }: UseFocusTrapOptions): UseFocusTrapReturn;
69
-
70
- declare interface UseFocusTrapOptions {
71
- /**
72
- * Whether the focus trap is active.
73
- */
74
- enabled: boolean;
75
- /**
76
- * Which element to initially focus when the trap activates. Can be a number
77
- * (tabbable index, 0 = first), a ref to an element, or -1 to disable initial
78
- * focus.
79
- * @default 0
80
- */
81
- initialFocus?: number | React.RefObject<HTMLElement | null>;
82
- }
83
-
84
- declare interface UseFocusTrapReturn {
85
- /**
86
- * Ref to attach to the container element that should trap focus.
87
- */
88
- containerRef: React.RefObject<HTMLDivElement | null>;
89
- }
90
-
91
58
  /**
92
59
  * Custom hook providing imperative haptic feedback for mobile devices. Uses
93
60
  * navigator.vibrate when available, falls back to iOS switch element trick for
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /*!
2
- @versini/ui-hooks v5.3.0
2
+ @versini/ui-hooks v5.3.2
3
3
  © 2025 gizmette.com
4
4
  */
5
5
  try {
6
6
  if (!window.__VERSINI_UI_HOOKS__) {
7
7
  window.__VERSINI_UI_HOOKS__ = {
8
- version: "5.3.0",
9
- buildTime: "12/14/2025 08:04 PM EST",
8
+ version: "5.3.2",
9
+ buildTime: "12/16/2025 01:48 PM EST",
10
10
  homepage: "https://www.npmjs.com/package/@versini/ui-hooks",
11
11
  license: "MIT",
12
12
  };
@@ -65,160 +65,6 @@ const DEFAULT_EVENTS = [
65
65
  return ref;
66
66
  }
67
67
 
68
- ;// CONCATENATED MODULE: ./src/hooks/useFocusTrap.ts
69
-
70
- /**
71
- * Selector for all focusable elements within a container. Based on W3C WAI-ARIA
72
- * practices for dialog focus management.
73
- */ const FOCUSABLE_SELECTOR = [
74
- 'a[href]:not([disabled]):not([tabindex="-1"])',
75
- 'button:not([disabled]):not([tabindex="-1"])',
76
- 'textarea:not([disabled]):not([tabindex="-1"])',
77
- 'input:not([disabled]):not([tabindex="-1"])',
78
- 'select:not([disabled]):not([tabindex="-1"])',
79
- '[tabindex]:not([tabindex="-1"]):not([disabled])',
80
- 'audio[controls]:not([tabindex="-1"])',
81
- 'video[controls]:not([tabindex="-1"])',
82
- 'details:not([tabindex="-1"])'
83
- ].join(", ");
84
- /**
85
- * Custom hook for trapping focus within a container element. Implements W3C
86
- * WAI-ARIA dialog focus management patterns:
87
- * - Tab/Shift+Tab cycles through focusable elements within the container
88
- * - Focus is trapped and cannot escape to elements outside
89
- * - Initial focus is set based on the initialFocus option
90
- *
91
- * @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
92
- *
93
- */ function useFocusTrap({ enabled, initialFocus = 0 }) {
94
- const containerRef = useRef(null);
95
- const previouslyFocusedRef = useRef(null);
96
- /**
97
- * Get all focusable elements within the container.
98
- */ const getFocusableElements = useCallback(()=>{
99
- /* c8 ignore next 3 - defensive check, containerRef is always set when enabled */ if (!containerRef.current) {
100
- return [];
101
- }
102
- const elements = containerRef.current.querySelectorAll(FOCUSABLE_SELECTOR);
103
- return Array.from(elements).filter((el)=>el.offsetParent !== null);
104
- }, []);
105
- /**
106
- * Focus a specific element by index, or the element referenced by a ref.
107
- */ const focusElement = useCallback((target)=>{
108
- if (typeof target === "number") {
109
- if (target === -1) {
110
- // -1 means don't focus anything.
111
- return;
112
- }
113
- const focusableElements = getFocusableElements();
114
- if (focusableElements.length > 0) {
115
- const index = Math.min(target, focusableElements.length - 1);
116
- focusableElements[index]?.focus();
117
- }
118
- } else if (target?.current) {
119
- target.current.focus();
120
- }
121
- }, [
122
- getFocusableElements
123
- ]);
124
- /**
125
- * Handle keyboard events for focus trapping. Tab cycles forward, Shift+Tab
126
- * cycles backward.
127
- */ const handleKeyDown = useCallback((event)=>{
128
- if (event.key !== "Tab" || !containerRef.current) {
129
- return;
130
- }
131
- const activeElement = document.activeElement;
132
- // If focus is in a nested dialog, let that dialog handle Tab.
133
- if (activeElement && !containerRef.current.contains(activeElement) && activeElement.closest('[role="dialog"]')) {
134
- return;
135
- }
136
- const focusableElements = getFocusableElements();
137
- if (focusableElements.length === 0) {
138
- event.preventDefault();
139
- return;
140
- }
141
- const firstElement = focusableElements[0];
142
- const lastElement = focusableElements[focusableElements.length - 1];
143
- // Shift+Tab from first element -> go to last.
144
- if (event.shiftKey && activeElement === firstElement) {
145
- event.preventDefault();
146
- lastElement?.focus();
147
- return;
148
- }
149
- // Tab from last element -> go to first.
150
- if (!event.shiftKey && activeElement === lastElement) {
151
- event.preventDefault();
152
- firstElement?.focus();
153
- return;
154
- }
155
- /* c8 ignore next 9 - defensive check for focus escaping, hard to trigger in tests */ // If focus is outside the container, bring it back.
156
- if (!containerRef.current.contains(activeElement)) {
157
- event.preventDefault();
158
- if (event.shiftKey) {
159
- lastElement?.focus();
160
- } else {
161
- firstElement?.focus();
162
- }
163
- }
164
- }, [
165
- getFocusableElements
166
- ]);
167
- /**
168
- * Handle focus events to ensure focus stays within the container. This catches
169
- * focus that escapes via mouse clicks or other means.
170
- */ const handleFocusIn = useCallback((event)=>{
171
- if (!containerRef.current || containerRef.current.contains(event.target)) {
172
- return;
173
- }
174
- // Allow focus to go to nested dialogs (child panels).
175
- const target = event.target;
176
- if (target?.closest('[role="dialog"]')) {
177
- return;
178
- }
179
- // Focus escaped the container, bring it back.
180
- const focusableElements = getFocusableElements();
181
- if (focusableElements.length > 0) {
182
- focusableElements[0]?.focus();
183
- }
184
- }, [
185
- getFocusableElements
186
- ]);
187
- // Set up focus trap when enabled.
188
- useEffect(()=>{
189
- if (!enabled) {
190
- return;
191
- }
192
- // Store the currently focused element to restore later.
193
- previouslyFocusedRef.current = document.activeElement;
194
- // Set initial focus after a small delay to ensure the DOM is ready.
195
- const focusTimer = setTimeout(()=>{
196
- focusElement(initialFocus);
197
- }, 0);
198
- // Add event listeners for focus trapping.
199
- document.addEventListener("keydown", handleKeyDown);
200
- document.addEventListener("focusin", handleFocusIn);
201
- return ()=>{
202
- clearTimeout(focusTimer);
203
- document.removeEventListener("keydown", handleKeyDown);
204
- document.removeEventListener("focusin", handleFocusIn);
205
- // Restore focus to the previously focused element if it's still in the DOM.
206
- if (previouslyFocusedRef.current?.isConnected) {
207
- previouslyFocusedRef.current.focus();
208
- }
209
- };
210
- }, [
211
- enabled,
212
- initialFocus,
213
- focusElement,
214
- handleKeyDown,
215
- handleFocusIn
216
- ]);
217
- return {
218
- containerRef
219
- };
220
- }
221
-
222
68
  ;// CONCATENATED MODULE: ./src/hooks/useHaptic.ts
223
69
 
224
70
  const HAPTIC_DURATION_MS = 50;
@@ -1109,5 +955,4 @@ function useWindowEvent(type, listener) {
1109
955
 
1110
956
 
1111
957
 
1112
-
1113
- export { getHotkeyHandler, shouldFireEvent, useClickOutside, useFocusTrap, useHaptic, useHotkeys, useInViewport, useInterval, useIsMounted, useLocalStorage, useMergeRefs, useResizeObserver, useUncontrolled, useUniqueId, useViewportSize, useVisualViewportSize };
958
+ export { getHotkeyHandler, shouldFireEvent, useClickOutside, useHaptic, useHotkeys, useInViewport, useInterval, useIsMounted, useLocalStorage, useMergeRefs, useResizeObserver, useUncontrolled, useUniqueId, useViewportSize, useVisualViewportSize };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versini/ui-hooks",
3
- "version": "5.3.0",
3
+ "version": "5.3.2",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
@@ -36,5 +36,5 @@
36
36
  "test:watch": "vitest",
37
37
  "test": "vitest run"
38
38
  },
39
- "gitHead": "15f3028acf21328c70773147a6bedfbafaa3a2a4"
39
+ "gitHead": "ef4323fa1f14f9f2a5471d71f063519230762aec"
40
40
  }