accessible-kit 1.0.2 → 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,50 @@ 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
+
28
+ ## [1.0.3] - 2025-12-20
29
+
30
+ ### Fixed
31
+ - **CSS Architecture**: Fixed inconsistent placement of animations in Offcanvas and Modal components
32
+ - Moved `transition` properties from `theme.css` to `core.css` for Offcanvas component
33
+ - Moved `transition` properties from `theme.css` to `core.css` for Modal component
34
+ - Animations now work out-of-the-box even when using custom themes without importing `theme.css`
35
+
36
+ ### Changed
37
+ - Updated `package.json` exports to include `style` condition for better CSS import compatibility
38
+ - Improved CSS imports documentation for Tailwind CSS and other bundlers
39
+
40
+ ### Details
41
+ All components now follow consistent CSS architecture:
42
+ - **Core CSS** (`*.core.css`): Contains layout, positioning, behavior, and **animations**
43
+ - **Theme CSS** (`*.theme.css`): Contains only visual styling (colors, spacing, borders, shadows)
44
+
45
+ This ensures animations work correctly even when developers create custom themes.
46
+
47
+ ## [1.0.2] - 2025-12-20
48
+
49
+ ### Changed
50
+ - Minor version bump for package.json exports improvements
51
+
8
52
  ## [1.0.1] - 2025-12-20
9
53
 
10
54
  ### Changed
@@ -65,5 +109,8 @@ document.addEventListener('DOMContentLoaded', () => {
65
109
  - Zero dependencies
66
110
  - Full TypeScript-ready exports
67
111
 
112
+ [1.0.4]: https://github.com/5ulo/accessible-kit/compare/v1.0.3...v1.0.4
113
+ [1.0.3]: https://github.com/5ulo/accessible-kit/compare/v1.0.2...v1.0.3
114
+ [1.0.2]: https://github.com/5ulo/accessible-kit/compare/v1.0.1...v1.0.2
68
115
  [1.0.1]: https://github.com/5ulo/accessible-kit/compare/v1.0.0...v1.0.1
69
116
  [1.0.0]: https://github.com/5ulo/accessible-kit/releases/tag/v1.0.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "accessible-kit",
3
- "version": "1.0.2",
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",
@@ -15,6 +15,8 @@
15
15
  justify-content: center;
16
16
  visibility: hidden;
17
17
  opacity: 0;
18
+ transition: opacity var(--a11y-modal-duration, 0.2s) var(--a11y-modal-easing, ease),
19
+ visibility var(--a11y-modal-duration, 0.2s) var(--a11y-modal-easing, ease);
18
20
  }
19
21
 
20
22
  [data-modal][aria-hidden="false"] {
@@ -31,6 +33,7 @@
31
33
  max-width: 90vw;
32
34
  overflow: hidden;
33
35
  transform: scale(0.95);
36
+ transition: transform var(--a11y-modal-duration, 0.2s) var(--a11y-modal-easing, ease);
34
37
  }
35
38
 
36
39
  [data-modal][aria-hidden="false"] [data-modal-dialog] {
@@ -5,8 +5,6 @@
5
5
  /* Modal wrapper */
6
6
  [data-modal] {
7
7
  padding: 1rem;
8
- transition: opacity var(--a11y-modal-duration, 0.2s) var(--a11y-modal-easing, ease),
9
- visibility var(--a11y-modal-duration, 0.2s) var(--a11y-modal-easing, ease);
10
8
  }
11
9
 
12
10
  /* Modal dialog */
@@ -16,7 +14,6 @@
16
14
  border-radius: 0.5rem;
17
15
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
18
16
  0 10px 10px -5px rgba(0, 0, 0, 0.04);
19
- transition: transform var(--a11y-modal-duration, 0.2s) var(--a11y-modal-easing, ease);
20
17
  }
21
18
 
22
19
  /* Modal header */
@@ -208,10 +205,8 @@
208
205
  }
209
206
  }
210
207
 
211
- /* Reduced motion support */
208
+ /* Reduced motion support for theme transitions */
212
209
  @media (prefers-reduced-motion: reduce) {
213
- [data-modal],
214
- [data-modal-dialog],
215
210
  [data-modal-close] {
216
211
  transition: none !important;
217
212
  }
@@ -9,6 +9,8 @@
9
9
  visibility: hidden;
10
10
  overflow-y: auto;
11
11
  -webkit-overflow-scrolling: touch;
12
+ transition: transform var(--a11y-offcanvas-duration, 0.3s) var(--a11y-offcanvas-easing, ease),
13
+ visibility var(--a11y-offcanvas-duration, 0.3s) var(--a11y-offcanvas-easing, ease);
12
14
  }
13
15
 
14
16
  [data-offcanvas-panel][aria-hidden="false"] {
@@ -96,6 +98,8 @@
96
98
  z-index: 999;
97
99
  visibility: hidden;
98
100
  opacity: 0;
101
+ transition: opacity var(--a11y-offcanvas-duration, 0.3s) var(--a11y-offcanvas-easing, ease),
102
+ visibility var(--a11y-offcanvas-duration, 0.3s) var(--a11y-offcanvas-easing, ease);
99
103
  }
100
104
 
101
105
  [data-offcanvas-backdrop][aria-hidden="false"] {
@@ -2,14 +2,18 @@
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;
8
14
  max-width: 100%;
9
15
  background: #fff;
10
16
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
11
- transition: transform var(--a11y-offcanvas-duration, 0.3s) var(--a11y-offcanvas-easing, ease),
12
- visibility var(--a11y-offcanvas-duration, 0.3s) var(--a11y-offcanvas-easing, ease);
13
17
  display: flex;
14
18
  flex-direction: column;
15
19
  }
@@ -72,16 +76,9 @@
72
76
  color: #111827;
73
77
  }
74
78
 
75
- [data-offcanvas-close]:focus {
76
- outline: 2px solid #3b82f6;
77
- outline-offset: 2px;
78
- }
79
-
80
79
  /* Backdrop */
81
80
  [data-offcanvas-backdrop] {
82
81
  background: rgba(0, 0, 0, 0.5);
83
- transition: opacity var(--a11y-offcanvas-duration, 0.3s) var(--a11y-offcanvas-easing, ease),
84
- visibility var(--a11y-offcanvas-duration, 0.3s) var(--a11y-offcanvas-easing, ease);
85
82
  }
86
83
 
87
84
  /* Wide variant */
@@ -155,15 +152,13 @@
155
152
  border: 2px solid currentColor;
156
153
  }
157
154
 
158
- [data-offcanvas-close]:focus {
155
+ [data-offcanvas-panel] :focus-visible {
159
156
  outline-width: 3px;
160
157
  }
161
158
  }
162
159
 
163
- /* Reduced motion support */
160
+ /* Reduced motion support for theme transitions */
164
161
  @media (prefers-reduced-motion: reduce) {
165
- [data-offcanvas-panel],
166
- [data-offcanvas-backdrop],
167
162
  [data-offcanvas-close] {
168
163
  transition: none !important;
169
164
  }
@@ -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.2
5
+ * @version 1.0.4
6
6
  * @license MIT
7
7
  */
8
8