accessible-kit 1.0.3 → 1.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ 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.4] - 2025-12-21
9
+
10
+ ### Fixed
11
+ - **Focus Trap**: Fixed focus trap in Offcanvas and Modal components
12
+ - Focus trap now correctly excludes elements with `aria-hidden="true"` and their children
13
+ - 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)
15
+ - Added comprehensive filtering for hidden, invisible, and aria-hidden elements
16
+ - Removed `visibility: hidden` check from filter to prevent false positives during panel opening
17
+
18
+ ### Added
19
+ - Added `:focus-visible` styles to Offcanvas theme for better keyboard navigation visibility
20
+ - Added navigation demo with nested collapse submenus to Offcanvas demo page
21
+
22
+ ### Details
23
+ The focus trap improvements ensure that keyboard navigation works correctly in complex scenarios:
24
+ - When offcanvas/modal contains collapse components, Tab key properly skips hidden submenu items
25
+ - Focus is set after CSS transitions complete, preventing "no focusable elements" issue
26
+ - Users can now navigate nested menus with full accessibility support
27
+
8
28
  ## [1.0.3] - 2025-12-20
9
29
 
10
30
  ### Fixed
@@ -89,6 +109,7 @@ document.addEventListener('DOMContentLoaded', () => {
89
109
  - Zero dependencies
90
110
  - Full TypeScript-ready exports
91
111
 
112
+ [1.0.4]: https://github.com/5ulo/accessible-kit/compare/v1.0.3...v1.0.4
92
113
  [1.0.3]: https://github.com/5ulo/accessible-kit/compare/v1.0.2...v1.0.3
93
114
  [1.0.2]: https://github.com/5ulo/accessible-kit/compare/v1.0.1...v1.0.2
94
115
  [1.0.1]: https://github.com/5ulo/accessible-kit/compare/v1.0.0...v1.0.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "accessible-kit",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
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",
@@ -2,6 +2,12 @@
2
2
  /* Obsahuje vizualne nastavenia: farby, velkosti, bordery, radiusy, spacing */
3
3
  /* Pre zmenu vzhladu upravte tento subor */
4
4
 
5
+ /* Focus visible for all focusable elements inside offcanvas */
6
+ [data-offcanvas-panel] :focus-visible {
7
+ outline: 2px solid #3b82f6;
8
+ outline-offset: 2px;
9
+ }
10
+
5
11
  /* Offcanvas panel */
6
12
  [data-offcanvas-panel] {
7
13
  width: 320px;
@@ -70,11 +76,6 @@
70
76
  color: #111827;
71
77
  }
72
78
 
73
- [data-offcanvas-close]:focus {
74
- outline: 2px solid #3b82f6;
75
- outline-offset: 2px;
76
- }
77
-
78
79
  /* Backdrop */
79
80
  [data-offcanvas-backdrop] {
80
81
  background: rgba(0, 0, 0, 0.5);
@@ -151,7 +152,7 @@
151
152
  border: 2px solid currentColor;
152
153
  }
153
154
 
154
- [data-offcanvas-close]:focus {
155
+ [data-offcanvas-panel] :focus-visible {
155
156
  outline-width: 3px;
156
157
  }
157
158
  }
@@ -126,17 +126,16 @@ class AccessibleModal {
126
126
  document.body.classList.add("modal-open");
127
127
  }
128
128
 
129
- // Update focusable elements
130
- this.updateFocusableElements();
131
-
132
- // Focus first element
129
+ // Update focusable elements and focus - needs to wait for CSS to apply
133
130
  setTimeout(() => {
131
+ this.updateFocusableElements();
132
+
134
133
  if (this.firstFocusable) {
135
134
  this.firstFocusable.focus();
136
135
  } else {
137
136
  this.modal.focus();
138
137
  }
139
- }, 100);
138
+ }, 50);
140
139
 
141
140
  // Callback
142
141
  if (this.options.onOpen) {
@@ -188,11 +187,34 @@ class AccessibleModal {
188
187
  this.focusableElements = Array.from(
189
188
  this.dialog.querySelectorAll(focusableSelectors.join(","))
190
189
  ).filter((el) => {
191
- return (
190
+ // Check if element is visible
191
+ const isVisible = (
192
192
  el.offsetWidth > 0 ||
193
193
  el.offsetHeight > 0 ||
194
194
  el.getClientRects().length > 0
195
195
  );
196
+
197
+ // Check if element or any parent has aria-hidden="true"
198
+ let currentElement = el;
199
+ while (currentElement && currentElement !== this.dialog) {
200
+ if (currentElement.getAttribute('aria-hidden') === 'true') {
201
+ return false;
202
+ }
203
+ currentElement = currentElement.parentElement;
204
+ }
205
+
206
+ // Check if element has hidden attribute
207
+ if (el.hasAttribute('hidden')) {
208
+ return false;
209
+ }
210
+
211
+ // Check computed style for display (not visibility, as modal is opening)
212
+ const style = window.getComputedStyle(el);
213
+ if (style.display === 'none') {
214
+ return false;
215
+ }
216
+
217
+ return isVisible;
196
218
  });
197
219
 
198
220
  this.firstFocusable = this.focusableElements[0] || null;
@@ -131,17 +131,16 @@ class AccessibleOffcanvas {
131
131
  document.body.classList.add("offcanvas-open");
132
132
  }
133
133
 
134
- // Update focusable elements
135
- this.updateFocusableElements();
136
-
137
- // Focus first element
134
+ // Update focusable elements and focus - needs to wait for CSS to apply
138
135
  setTimeout(() => {
136
+ this.updateFocusableElements();
137
+
139
138
  if (this.firstFocusable) {
140
139
  this.firstFocusable.focus();
141
140
  } else {
142
141
  this.panel.focus();
143
142
  }
144
- }, 100);
143
+ }, 50);
145
144
 
146
145
  // Callback
147
146
  if (this.options.onOpen) {
@@ -196,11 +195,38 @@ class AccessibleOffcanvas {
196
195
  this.focusableElements = Array.from(
197
196
  this.panel.querySelectorAll(focusableSelectors.join(","))
198
197
  ).filter((el) => {
199
- return (
198
+ // Check if element is visible
199
+ const isVisible = (
200
200
  el.offsetWidth > 0 ||
201
201
  el.offsetHeight > 0 ||
202
202
  el.getClientRects().length > 0
203
203
  );
204
+
205
+ if (!isVisible) {
206
+ return false;
207
+ }
208
+
209
+ // Check if element or any parent has aria-hidden="true"
210
+ let currentElement = el;
211
+ while (currentElement && currentElement !== this.panel) {
212
+ if (currentElement.getAttribute('aria-hidden') === 'true') {
213
+ return false;
214
+ }
215
+ currentElement = currentElement.parentElement;
216
+ }
217
+
218
+ // Check if element has hidden attribute
219
+ if (el.hasAttribute('hidden')) {
220
+ return false;
221
+ }
222
+
223
+ // Check computed style for display (not visibility, as panel is opening)
224
+ const style = window.getComputedStyle(el);
225
+ if (style.display === 'none') {
226
+ return false;
227
+ }
228
+
229
+ return true;
204
230
  });
205
231
 
206
232
  this.firstFocusable = this.focusableElements[0] || null;
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.3
5
+ * @version 1.0.4
6
6
  * @license MIT
7
7
  */
8
8