accessible-kit 1.0.4 → 1.0.5

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/CHANGELOG.md CHANGED
@@ -5,15 +5,40 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.5] - 2025-12-21
9
+
10
+ ### Fixed
11
+ - **Focus Trap - Manual Tab Navigation**: Complete rewrite of Tab key handling in Offcanvas and Modal components
12
+ - **BREAKING CHANGE in behavior**: Focus trap now completely overrides native browser Tab behavior
13
+ - Always prevents default Tab action and manually controls focus movement
14
+ - Eliminates `aria-hidden` violations caused by browser trying to focus hidden elements
15
+ - `updateFocusableElements()` is called on every Tab keypress to catch dynamic DOM changes
16
+ - Focus only moves through elements in the filtered `focusableElements` list
17
+ - Properly handles nested collapse components - focus skips closed submenus and includes opened ones
18
+
19
+ ### Details
20
+ **Problem solved:**
21
+ When using Tab key in offcanvas/modal with dynamic content (e.g., collapse menus), the browser's native Tab behavior would attempt to focus elements with `aria-hidden="true"`, causing console warnings and accessibility violations.
22
+
23
+ **Solution:**
24
+ The focus trap now takes complete control of Tab navigation:
25
+ 1. Every Tab keypress prevents default browser behavior
26
+ 2. Updates the list of focusable elements to reflect current DOM state
27
+ 3. Manually calculates and focuses the next/previous element from the filtered list
28
+ 4. Elements inside `aria-hidden="true"` containers are never focused
29
+
30
+ This ensures perfect compatibility with dynamic components like animated collapse panels in navigation menus.
31
+
8
32
  ## [1.0.4] - 2025-12-21
9
33
 
10
34
  ### Fixed
11
- - **Focus Trap**: Fixed focus trap in Offcanvas and Modal components
35
+ - **Focus Trap - Initial Implementation**: Improved focus trap in Offcanvas and Modal components
12
36
  - Focus trap now correctly excludes elements with `aria-hidden="true"` and their children
13
37
  - Fixed timing issue where `updateFocusableElements()` was called before CSS visibility changes applied
14
- - Focus trap now properly skips hidden elements in collapsed/nested components (e.g., collapse submenus in navigation)
38
+ - Focus trap now properly skips hidden elements in collapsed/nested components
15
39
  - Added comprehensive filtering for hidden, invisible, and aria-hidden elements
16
40
  - Removed `visibility: hidden` check from filter to prevent false positives during panel opening
41
+ - Works correctly with both animated (CSS Grid) and non-animated collapse panels
17
42
 
18
43
  ### Added
19
44
  - Added `:focus-visible` styles to Offcanvas theme for better keyboard navigation visibility
@@ -109,6 +134,7 @@ document.addEventListener('DOMContentLoaded', () => {
109
134
  - Zero dependencies
110
135
  - Full TypeScript-ready exports
111
136
 
137
+ [1.0.5]: https://github.com/5ulo/accessible-kit/compare/v1.0.4...v1.0.5
112
138
  [1.0.4]: https://github.com/5ulo/accessible-kit/compare/v1.0.3...v1.0.4
113
139
  [1.0.3]: https://github.com/5ulo/accessible-kit/compare/v1.0.2...v1.0.3
114
140
  [1.0.2]: https://github.com/5ulo/accessible-kit/compare/v1.0.1...v1.0.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "accessible-kit",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Lightweight, accessible UI component library with full ARIA support. Zero dependencies, vanilla JavaScript.",
5
5
  "main": "src/js/index.js",
6
6
  "type": "module",
@@ -194,6 +194,10 @@ class AccessibleModal {
194
194
  el.getClientRects().length > 0
195
195
  );
196
196
 
197
+ if (!isVisible) {
198
+ return false;
199
+ }
200
+
197
201
  // Check if element or any parent has aria-hidden="true"
198
202
  let currentElement = el;
199
203
  while (currentElement && currentElement !== this.dialog) {
@@ -214,7 +218,7 @@ class AccessibleModal {
214
218
  return false;
215
219
  }
216
220
 
217
- return isVisible;
221
+ return true;
218
222
  });
219
223
 
220
224
  this.firstFocusable = this.focusableElements[0] || null;
@@ -225,24 +229,39 @@ class AccessibleModal {
225
229
  handleFocusTrap(e) {
226
230
  if (!this.isOpen || e.key !== "Tab") return;
227
231
 
232
+ // Update focusable elements before each Tab to catch dynamic changes (e.g., collapse panels)
233
+ this.updateFocusableElements();
234
+
228
235
  // If no focusable elements, prevent default
229
236
  if (this.focusableElements.length === 0) {
230
237
  e.preventDefault();
231
238
  return;
232
239
  }
233
240
 
241
+ // Always prevent default Tab behavior - we'll handle focus manually
242
+ e.preventDefault();
243
+
244
+ // Find current element index in focusable list
245
+ const currentIndex = this.focusableElements.indexOf(document.activeElement);
246
+
234
247
  // Shift + Tab (backward)
235
248
  if (e.shiftKey) {
236
- if (document.activeElement === this.firstFocusable) {
237
- e.preventDefault();
249
+ if (currentIndex <= 0) {
250
+ // At first element or not in list - go to last
238
251
  this.lastFocusable.focus();
252
+ } else {
253
+ // Go to previous element
254
+ this.focusableElements[currentIndex - 1].focus();
239
255
  }
240
256
  }
241
257
  // Tab (forward)
242
258
  else {
243
- if (document.activeElement === this.lastFocusable) {
244
- e.preventDefault();
259
+ if (currentIndex === -1 || currentIndex >= this.focusableElements.length - 1) {
260
+ // Not in list or at last element - go to first
245
261
  this.firstFocusable.focus();
262
+ } else {
263
+ // Go to next element
264
+ this.focusableElements[currentIndex + 1].focus();
246
265
  }
247
266
  }
248
267
  }
@@ -237,24 +237,39 @@ class AccessibleOffcanvas {
237
237
  handleFocusTrap(e) {
238
238
  if (!this.isOpen || e.key !== "Tab") return;
239
239
 
240
+ // Update focusable elements before each Tab to catch dynamic changes (e.g., collapse panels)
241
+ this.updateFocusableElements();
242
+
240
243
  // If no focusable elements, prevent default
241
244
  if (this.focusableElements.length === 0) {
242
245
  e.preventDefault();
243
246
  return;
244
247
  }
245
248
 
249
+ // Always prevent default Tab behavior - we'll handle focus manually
250
+ e.preventDefault();
251
+
252
+ // Find current element index in focusable list
253
+ const currentIndex = this.focusableElements.indexOf(document.activeElement);
254
+
246
255
  // Shift + Tab (backward)
247
256
  if (e.shiftKey) {
248
- if (document.activeElement === this.firstFocusable) {
249
- e.preventDefault();
257
+ if (currentIndex <= 0) {
258
+ // At first element or not in list - go to last
250
259
  this.lastFocusable.focus();
260
+ } else {
261
+ // Go to previous element
262
+ this.focusableElements[currentIndex - 1].focus();
251
263
  }
252
264
  }
253
265
  // Tab (forward)
254
266
  else {
255
- if (document.activeElement === this.lastFocusable) {
256
- e.preventDefault();
267
+ if (currentIndex === -1 || currentIndex >= this.focusableElements.length - 1) {
268
+ // Not in list or at last element - go to first
257
269
  this.firstFocusable.focus();
270
+ } else {
271
+ // Go to next element
272
+ this.focusableElements[currentIndex + 1].focus();
258
273
  }
259
274
  }
260
275
  }
package/src/js/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * a11y-kit
3
3
  * Lightweight, accessible UI component library with full ARIA support
4
4
  *
5
- * @version 1.0.4
5
+ * @version 1.0.5
6
6
  * @license MIT
7
7
  */
8
8