aloha-vue 1.2.282 → 1.2.284

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
@@ -7,6 +7,14 @@
7
7
  ---
8
8
  # Versions
9
9
 
10
+ ## 1.2.284
11
+
12
+ - Improved `focus` management for the `AMenu2` component, ensuring better accessibility when the menu is closed.
13
+
14
+ ## 1.2.283
15
+
16
+ - Fixed the `HTML` structure of the component to improve accessibility and ensure compliance with accessibility standards.
17
+
10
18
  ## 1.2.282
11
19
 
12
20
  - Fixed the `ATable` component to ensure the `aria-labelledby` attribute is not used when the table has no header, improving accessibility compliance.
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "Vue.js"
15
15
  ],
16
16
  "homepage": "https://github.com/ilia-brykin/aloha/#README.md",
17
- "version": "1.2.282",
17
+ "version": "1.2.284",
18
18
  "author": {
19
19
  "name": "Ilia Brykin",
20
20
  "email": "brykin.ilia@gmail.com"
@@ -231,11 +231,19 @@ export default {
231
231
  } = ToggleAPI(props);
232
232
 
233
233
  const {
234
+ isDesktopSubMenuVisibleWhenMenuClosed,
234
235
  menuRef,
235
- removeListenerForKeydown,
236
- setListenerForKeydown,
236
+ removeListenerForKeydownDesktop,
237
+ removeListenerForKeydownMobile,
238
+ setListenerForKeydownDesktop,
239
+ setListenerForKeydownMobile,
237
240
  } = KeydownAPI(props, {
238
241
  closeMenu,
242
+ dataProParent,
243
+ isMenuOpen,
244
+ isSubMenuOpen,
245
+ panelParentsOpen,
246
+ togglePanel,
239
247
  });
240
248
 
241
249
  const {
@@ -262,7 +270,7 @@ export default {
262
270
  isMenuInitialized,
263
271
  isMobileWidth,
264
272
  } = ResizeAPI(props, {
265
- removeListenerForKeydown,
273
+ removeListenerForKeydownMobile,
266
274
  toggleMenu,
267
275
  });
268
276
 
@@ -335,9 +343,9 @@ export default {
335
343
  closeAllPanels();
336
344
  if (newValue) {
337
345
  destroyPopover();
338
- setListenerForKeydown();
346
+ setListenerForKeydownMobile();
339
347
  } else {
340
- removeListenerForKeydown();
348
+ removeListenerForKeydownMobile();
341
349
  }
342
350
  });
343
351
 
@@ -347,6 +355,14 @@ export default {
347
355
  deep: true,
348
356
  });
349
357
 
358
+ watch(isDesktopSubMenuVisibleWhenMenuClosed, newValue => {
359
+ if (newValue) {
360
+ setListenerForKeydownDesktop();
361
+ } else {
362
+ removeListenerForKeydownDesktop();
363
+ }
364
+ });
365
+
350
366
  provide("activeRoutesIds", computed(() => activeRoutesIds.value));
351
367
  provide("clickMenuLink", clickMenuLink);
352
368
  provide("isLinkTruncated", computed(() => isLinkTruncated.value));
@@ -363,7 +379,8 @@ export default {
363
379
  removeBodyClasses();
364
380
  destroyEventBusUpdateViewOnResize();
365
381
  destroyPopover();
366
- removeListenerForKeydown();
382
+ removeListenerForKeydownDesktop();
383
+ removeListenerForKeydownMobile();
367
384
  });
368
385
 
369
386
  return {
@@ -98,50 +98,56 @@ export default {
98
98
  "a_menu_2__breadcrumb__ul a_menu_2__breadcrumb__ul_truncated",
99
99
  ],
100
100
  }, [
101
- this.breadcrumbsItemsDropdown.length > 0 && h(ADropdown, {
102
- buttonClass: "a_btn a_btn_secondary a_btn_small a_menu_2__breadcrumb__ul_truncated__btn",
103
- buttonIconLeft: "ThreeDots",
104
- buttonTextScreenReader: "_A_MENU_2_BREADCRUMB_SHOW_BTN_",
105
- buttonTitle: "_A_MENU_2_BREADCRUMB_SHOW_BTN_",
106
- dropdownClass: "a_menu_2__breadcrumb__ul_truncated__dropdown",
107
- hasCaret: false,
108
- inBody: true,
109
- ...this.breadcrumbsTruncatedDropdownProps,
110
- }, {
111
- dropdown: () => [
112
- this.breadcrumbsItemsDropdown.map(breadcrumbsItem => {
113
- const ATTR = breadcrumbsItem.panelParentId ?
114
- {
115
- tag: "a",
116
- class: [
117
- "a_menu_2__breadcrumbs__link",
118
- this.breadcrumbsLinkClass,
119
- ],
120
- role: "button",
121
- tabindex: 0,
122
- onClick: () => this.goBack({ parentId: breadcrumbsItem.panelParentId }),
123
- onKeydown: $event => this.goBackKeydown({ $event, parentId: breadcrumbsItem.panelParentId }),
124
- } :
125
- {
126
- class: "a_menu_2__breadcrumbs__link",
127
- tag: "strong",
128
- };
129
- return h("li", {
130
- key: breadcrumbsItem.panelParentId,
131
- class: "a_menu_2__breadcrumbs__item",
132
- }, [
133
- h(AButton, {
134
- text: breadcrumbsItem.label,
135
- title: breadcrumbsItem.label,
136
- ...ATTR,
101
+ this.breadcrumbsItemsDropdown.length > 0 ?
102
+ h("li", {
103
+ class: "a_menu_2__breadcrumbs__item",
104
+ }, [
105
+ h(ADropdown, {
106
+ buttonClass: "a_btn a_btn_secondary a_btn_small a_menu_2__breadcrumb__ul_truncated__btn",
107
+ buttonIconLeft: "ThreeDots",
108
+ buttonTextScreenReader: "_A_MENU_2_BREADCRUMB_SHOW_BTN_",
109
+ buttonTitle: "_A_MENU_2_BREADCRUMB_SHOW_BTN_",
110
+ dropdownClass: "a_menu_2__breadcrumb__ul_truncated__dropdown",
111
+ hasCaret: false,
112
+ inBody: true,
113
+ ...this.breadcrumbsTruncatedDropdownProps,
114
+ }, {
115
+ dropdown: () => [
116
+ this.breadcrumbsItemsDropdown.map(breadcrumbsItem => {
117
+ const ATTR = breadcrumbsItem.panelParentId ?
118
+ {
119
+ tag: "a",
120
+ class: [
121
+ "a_menu_2__breadcrumbs__link",
122
+ this.breadcrumbsLinkClass,
123
+ ],
124
+ role: "button",
125
+ tabindex: 0,
126
+ onClick: () => this.goBack({ parentId: breadcrumbsItem.panelParentId }),
127
+ onKeydown: $event => this.goBackKeydown({ $event, parentId: breadcrumbsItem.panelParentId }),
128
+ } :
129
+ {
130
+ class: "a_menu_2__breadcrumbs__link",
131
+ tag: "strong",
132
+ };
133
+ return h("li", {
134
+ key: breadcrumbsItem.panelParentId,
135
+ class: "a_menu_2__breadcrumbs__item",
136
+ }, [
137
+ h(AButton, {
138
+ text: breadcrumbsItem.label,
139
+ title: breadcrumbsItem.label,
140
+ ...ATTR,
141
+ }),
142
+ h("span", {
143
+ class: "a_menu_2__breadcrumbs__item__divider",
144
+ }, "/"),
145
+ ]);
137
146
  }),
138
- h("span", {
139
- class: "a_menu_2__breadcrumbs__item__divider",
140
- }, "/"),
141
- ]);
147
+ ],
142
148
  }),
143
- ],
144
- }),
149
+ ]) :
150
+ "",
145
151
  this.breadcrumbsItemsTruncated.map(breadcrumbsItem => {
146
152
  const ATTR = breadcrumbsItem.panelParentId ?
147
153
  {
@@ -105,9 +105,7 @@ export default {
105
105
 
106
106
  const {
107
107
  tabindex,
108
- } = TabindexAPI(props, {
109
- isLinkOpen,
110
- });
108
+ } = TabindexAPI(props);
111
109
 
112
110
  const {
113
111
  countChildren,
@@ -4,9 +4,7 @@ import {
4
4
  toRef,
5
5
  } from "vue";
6
6
 
7
- export default function TabindexAPI(props, {
8
- isLinkOpen = computed(() => false),
9
- }) {
7
+ export default function TabindexAPI(props) {
10
8
  const isLinkInSearchPanel = toRef(props, "isLinkInSearchPanel");
11
9
  const isPanelOpen = toRef(props, "isPanelOpen");
12
10
 
@@ -18,24 +16,18 @@ export default function TabindexAPI(props, {
18
16
  if (isLinkInSearchPanel.value) {
19
17
  return 0;
20
18
  }
21
- if (!isMenuOpen.value) {
22
- if (isPanelMain.value && isPanelOpen.value) {
23
- return 0;
24
- }
25
- if (isPanelMain.value || isChildPanelOpen.value) {
26
- if (isLinkOpen.value) {
27
- return 0;
28
- }
29
- return -1;
30
- }
31
- if (isPanelOpen.value) {
32
- return 0;
33
- }
34
- return -1;
19
+ if (isPanelMain.value) {
20
+ return 0;
35
21
  }
36
22
  if (isPanelOpen.value) {
37
23
  return 0;
38
24
  }
25
+ if (!isMenuOpen.value) {
26
+ if (isChildPanelOpen.value) {
27
+ return 0;
28
+ }
29
+ }
30
+
39
31
  return -1;
40
32
  });
41
33
 
@@ -1,4 +1,5 @@
1
1
  import {
2
+ computed,
2
3
  ref,
3
4
  toRef,
4
5
  } from "vue";
@@ -6,13 +7,27 @@ import {
6
7
  import AMobileAPI from "../../compositionAPI/AMobileAPI";
7
8
 
8
9
  import AKeysCode from "../../const/AKeysCode";
10
+ import AKeyId from "../../const/AKeyId";
11
+ import {
12
+ getElementId,
13
+ } from "../utils/utils";
9
14
  import {
10
15
  focusableSelector,
11
16
  } from "../../const/AFocusableElements";
17
+ import {
18
+ findIndex,
19
+ forEach,
20
+ } from "lodash-es";
12
21
 
13
22
  export default function KeydownAPI(props, {
14
23
  closeMenu = () => {},
24
+ dataProParent = computed(() => ({})),
25
+ isMenuOpen = computed(() => false),
26
+ isSubMenuOpen = computed(() => false),
27
+ panelParentsOpen = ref([]),
28
+ togglePanel = () => {},
15
29
  }) {
30
+ const menuId = toRef(props, "menuId");
16
31
  const useEscapeForMobile = toRef(props, "useEscapeForMobile");
17
32
 
18
33
  const menuRef = ref(undefined);
@@ -21,6 +36,10 @@ export default function KeydownAPI(props, {
21
36
  isMobileWidth,
22
37
  } = AMobileAPI();
23
38
 
39
+ const isDesktopSubMenuVisibleWhenMenuClosed = computed(() => {
40
+ return !isMobileWidth.value && !isMenuOpen.value && isSubMenuOpen.value;
41
+ });
42
+
24
43
  const trapFocus = EVENT => {
25
44
  if (!menuRef.value) {
26
45
  return;
@@ -57,7 +76,7 @@ export default function KeydownAPI(props, {
57
76
  $event.stopPropagation();
58
77
  };
59
78
 
60
- const keydown = $event => {
79
+ const keydownMobile = $event => {
61
80
  const EVENT = $event || window.$event;
62
81
  if (EVENT.key === "Escape" || EVENT.keyCode === AKeysCode.escape) {
63
82
  pressEscape($event);
@@ -66,20 +85,138 @@ export default function KeydownAPI(props, {
66
85
  }
67
86
  };
68
87
 
69
- const setListenerForKeydown = () => {
88
+ const setFocusToLinkInPreviousSubPanel = ({ panelIndex, panelId }) => {
89
+ const LINK_HTML_ID = getElementId({
90
+ menuId: menuId.value,
91
+ id: panelId,
92
+ suffix: "link",
93
+ });
94
+ const LINK = document.getElementById(LINK_HTML_ID);
95
+ if (LINK) {
96
+ if (panelIndex > 0) {
97
+ const PANEL_PARENTS_OPEN_NEW = panelParentsOpen.value.slice(0, panelIndex);
98
+ togglePanel({ parentIds: PANEL_PARENTS_OPEN_NEW, withoutFocus: true });
99
+ }
100
+ LINK.focus();
101
+ }
102
+ };
103
+
104
+ const setFocusToLinkInNextSubPanel = ({ panelIndex }) => {
105
+ const PANEL_ID = panelParentsOpen.value[panelIndex + 1];
106
+ const PANEL_HTML_ID = getElementId({
107
+ menuId: menuId.value,
108
+ id: PANEL_ID,
109
+ suffix: "panel",
110
+ });
111
+ const LINKS = document.querySelectorAll(`#${ PANEL_HTML_ID } a.a_menu_2__link`);
112
+ if (LINKS.length) {
113
+ LINKS[0].focus();
114
+ }
115
+ };
116
+
117
+ const setFocusToParentLinkInMainPanel = ({ panelId }) => {
118
+ const LINK_ID = getElementId({
119
+ menuId: menuId.value,
120
+ id: panelId,
121
+ suffix: "link",
122
+ });
123
+ const LINK_ELEMENT = document.getElementById(LINK_ID);
124
+ if (LINK_ELEMENT) {
125
+ togglePanel({ parentIds: [], withoutFocus: true });
126
+ LINK_ELEMENT.focus();
127
+ }
128
+ };
129
+
130
+ const setFocusToNextParentLinkInMainPanel = ({ panelId }) => {
131
+ let linkNextId = panelId;
132
+ const INDEX_ACTIVE_LINK_IN_MAIN_PANEL = findIndex(dataProParent.value.main, [AKeyId, panelId]);
133
+ if (INDEX_ACTIVE_LINK_IN_MAIN_PANEL === -1) {
134
+ return;
135
+ }
136
+ if (INDEX_ACTIVE_LINK_IN_MAIN_PANEL !== dataProParent.value.main.length - 1) { // not last link
137
+ linkNextId = dataProParent.value.main?.[INDEX_ACTIVE_LINK_IN_MAIN_PANEL + 1]?.[AKeyId];
138
+ }
139
+
140
+ const LINK_ID = getElementId({
141
+ menuId: menuId.value,
142
+ id: linkNextId,
143
+ suffix: "link",
144
+ });
145
+ const LINK_ELEMENT = document.getElementById(LINK_ID);
146
+ if (LINK_ELEMENT) {
147
+ togglePanel({ parentIds: [], withoutFocus: true });
148
+ LINK_ELEMENT.focus();
149
+ }
150
+ };
151
+
152
+ const setFocus = ({ EVENT, PANEL_ELEMENT, panelIndex, panelId }) => {
153
+ const LINKS = PANEL_ELEMENT.querySelectorAll("a.a_menu_2__link");
154
+ if (EVENT.shiftKey) { // Shift + Tab
155
+ if (document.activeElement === LINKS[0]) {
156
+ if (panelIndex > 0) {
157
+ setFocusToLinkInPreviousSubPanel({ panelIndex, panelId });
158
+ } else {
159
+ setFocusToParentLinkInMainPanel({ panelId });
160
+ }
161
+ EVENT.preventDefault();
162
+ }
163
+ } else { // Tab
164
+ if (document.activeElement === LINKS[LINKS.length - 1]) {
165
+ if (panelIndex < panelParentsOpen.value.length - 1) {
166
+ setFocusToLinkInNextSubPanel({ panelIndex });
167
+ } else if (panelIndex > 0) {
168
+ setFocusToLinkInPreviousSubPanel({ panelIndex, panelId });
169
+ } else {
170
+ setFocusToNextParentLinkInMainPanel({ panelId });
171
+ }
172
+ EVENT.preventDefault();
173
+ }
174
+ }
175
+ };
176
+
177
+ const keydownDesktop = $event => {
178
+ const EVENT = $event || window.$event;
179
+ if (EVENT.key === "Tab" || EVENT.keyCode === AKeysCode.tab) {
180
+ forEach(panelParentsOpen.value, (panelId, panelIndex) => {
181
+ const PANEL_HTML_ID = getElementId({
182
+ menuId: menuId.value,
183
+ id: panelId,
184
+ suffix: "panel",
185
+ });
186
+ const PANEL_ELEMENT = document.getElementById(PANEL_HTML_ID);
187
+ if (PANEL_ELEMENT && PANEL_ELEMENT.contains(document.activeElement)) {
188
+ setFocus({ EVENT, PANEL_ELEMENT, panelIndex, panelId });
189
+
190
+ return false;
191
+ }
192
+ });
193
+ }
194
+ };
195
+
196
+ const setListenerForKeydownMobile = () => {
70
197
  if (isMobileWidth.value) {
71
- document.addEventListener("keydown", keydown);
198
+ document.addEventListener("keydown", keydownMobile);
72
199
  }
73
200
  };
74
201
 
202
+ const removeListenerForKeydownMobile = () => {
203
+ document.removeEventListener("keydown", keydownMobile);
204
+ };
205
+
206
+ const setListenerForKeydownDesktop = () => {
207
+ document.addEventListener("keydown", keydownDesktop);
208
+ };
75
209
 
76
- const removeListenerForKeydown = () => {
77
- document.removeEventListener("keydown", keydown);
210
+ const removeListenerForKeydownDesktop = () => {
211
+ document.removeEventListener("keydown", keydownDesktop);
78
212
  };
79
213
 
80
214
  return {
215
+ isDesktopSubMenuVisibleWhenMenuClosed,
81
216
  menuRef,
82
- removeListenerForKeydown,
83
- setListenerForKeydown,
217
+ removeListenerForKeydownDesktop,
218
+ removeListenerForKeydownMobile,
219
+ setListenerForKeydownDesktop,
220
+ setListenerForKeydownMobile,
84
221
  };
85
222
  }
@@ -38,7 +38,7 @@ export default function PanelsAPI(props, {
38
38
  resetSearch();
39
39
  };
40
40
 
41
- const togglePanel = ({ parentIds, parentId, isLinkInSearchPanel }) => {
41
+ const togglePanel = ({ parentIds, parentId, isLinkInSearchPanel, withoutFocus }) => {
42
42
  if (parentIds) {
43
43
  panelParentsOpen.value = parentIds;
44
44
  } else {
@@ -50,10 +50,12 @@ export default function PanelsAPI(props, {
50
50
  panelParentsOpen.value = PANEL_PARENTS_OPEN;
51
51
  }
52
52
 
53
- setFocusToFirstLinkInPanel({
54
- menuId: menuId.value,
55
- panelId: last(panelParentsOpen.value) || "",
56
- });
53
+ if (!withoutFocus) {
54
+ setFocusToFirstLinkInPanel({
55
+ menuId: menuId.value,
56
+ panelId: last(panelParentsOpen.value) || "",
57
+ });
58
+ }
57
59
  resetSearch();
58
60
  };
59
61
 
@@ -8,7 +8,7 @@ import AMobileAPI from "../../compositionAPI/AMobileAPI";
8
8
  import EventBus from "../../utils/EventBus";
9
9
 
10
10
  export default function ResizeAPI(props, {
11
- removeListenerForKeydown = () => {},
11
+ removeListenerForKeydownMobile = () => {},
12
12
  toggleMenu = () => {},
13
13
  }) {
14
14
  const isMenuOpenInitial = toRef(props, "isMenuOpenInitial");
@@ -24,7 +24,7 @@ export default function ResizeAPI(props, {
24
24
  toggleMenu({ isOpen: false });
25
25
  } else {
26
26
  toggleMenu({ isOpen: isMenuOpenInitial.value });
27
- removeListenerForKeydown();
27
+ removeListenerForKeydownMobile();
28
28
  }
29
29
  };
30
30