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 +21 -0
- package/package.json +1 -1
- package/src/css/a11y-offcanvas.theme.css +7 -6
- package/src/js/a11y-modal.js +28 -6
- package/src/js/a11y-offcanvas.js +32 -6
- package/src/js/index.js +1 -1
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
|
@@ -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-
|
|
155
|
+
[data-offcanvas-panel] :focus-visible {
|
|
155
156
|
outline-width: 3px;
|
|
156
157
|
}
|
|
157
158
|
}
|
package/src/js/a11y-modal.js
CHANGED
|
@@ -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
|
-
},
|
|
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
|
-
|
|
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;
|
package/src/js/a11y-offcanvas.js
CHANGED
|
@@ -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
|
-
},
|
|
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
|
-
|
|
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;
|