aboutsummaryrefslogtreecommitdiff
path: root/resources/assets
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2024-10-01 11:04:49 +0000
committerJan-Hendrik Willms <tleilax+studip@gmail.com>2024-10-01 11:04:49 +0000
commit8bc88f75ea82dcdcf218e5114d12f72d6b37cf3b (patch)
tree125a18b6b1f2759df30a6f2d8b54389f8aea9d49 /resources/assets
parentde136ea2be9eb2c7f10b65bc02c7ef8e1d2214cf (diff)
rework keyboard focus management for action menues, fixes #4641
Closes #4641 Merge request studip/studip!3457
Diffstat (limited to 'resources/assets')
-rw-r--r--resources/assets/javascripts/bootstrap/actionmenu.js15
-rw-r--r--resources/assets/javascripts/lib/actionmenu.js73
2 files changed, 44 insertions, 44 deletions
diff --git a/resources/assets/javascripts/bootstrap/actionmenu.js b/resources/assets/javascripts/bootstrap/actionmenu.js
index 5cc6021..bb6371d 100644
--- a/resources/assets/javascripts/bootstrap/actionmenu.js
+++ b/resources/assets/javascripts/bootstrap/actionmenu.js
@@ -34,19 +34,4 @@
STUDIP.ActionMenu.closeAll();
}
});
-
- // Close all action menus when the escape key is pressed and rotate through all its items
- // when TAB or SHIFT + TAB is pressed.
- $(document).on('keydown', function(event) {
- if (event.key === 'Escape') {
- STUDIP.ActionMenu.closeAll();
- } else if (event.key === 'Tab') {
- //Check if the focus is inside an action menu:
- let menu = $(event.target).closest('.action-menu');
- if (menu.hasClass('is-open') && STUDIP.ActionMenu.tabThroughItems(menu, event.shiftKey)) {
- event.preventDefault();
- }
- }
- });
-
}(jQuery));
diff --git a/resources/assets/javascripts/lib/actionmenu.js b/resources/assets/javascripts/lib/actionmenu.js
index 228d6c9..a81f78a 100644
--- a/resources/assets/javascripts/lib/actionmenu.js
+++ b/resources/assets/javascripts/lib/actionmenu.js
@@ -100,7 +100,7 @@ class ActionMenu
const additionalClasses = Object.values({ ...this.element[0].classList }).filter((item) => item != 'action-menu');
const menu_width = this.content.width();
const menu_height = this.content.height();
-
+
// Reposition the menu?
if (position) {
let parents = getScrollableParents(this.element, menu_width, menu_height);
@@ -122,9 +122,28 @@ class ActionMenu
this.position = false;
}
}
+
+ this.attachEventHandlers();
+
this.update();
}
+ // Close all action menus when the escape key is pressed and rotate through all its items
+ // when TAB or SHIFT + TAB is pressed.
+ attachEventHandlers() {
+ this.menu[0].addEventListener('keydown', (event) => {
+ if (event.key === 'Escape') {
+ this.close(true);
+ } else if (event.key === 'Tab' && this.is_open) {
+ this.tabThroughItems(event.shiftKey);
+ event.preventDefault();
+ } else if (event.key === 'Enter' && event.target.matches('label')) {
+ event.target.querySelector('button,input').click();
+ event.preventDefault();
+ }
+ });
+ }
+
toggleScrollHandler(active) {
if (ActionMenu.scrollHandlerState === active) {
return;
@@ -170,19 +189,23 @@ class ActionMenu
/**
* Toggle the menus display state. Pass a state to enforce it.
*/
- toggle(state = null) {
+ toggle(state = null, focus = false) {
this.is_open = state === null ? !this.is_open : state;
this.update();
if (this.is_open) {
this.reposition();
- this.menu.find('.action-menu-icon').focus();
ActionMenu.openMenus.push(this);
} else {
ActionMenu.openMenus = ActionMenu.openMenus.filter(menu => menu !== this);
}
+ // Always focus the toggle element
+ if (this.is_open || focus) {
+ this.menu.find('.action-menu-icon').focus();
+ }
+
this.toggleScrollHandler(ActionMenu.openMenus.filter(menu => menu.position).length > 0);
}
@@ -237,36 +260,28 @@ class ActionMenu
* Handles the rotation through the action menu items when the first
* or last element of the menu has been reached.
*
- * @param menu The menu whose items shall be rotated through.
- *
* @param reverse Whether to rotate in reverse (true) or not (false).
* Defaults to false.
*/
- static tabThroughItems(menu, reverse = false) {
- if (reverse) {
- //Put the focus on the last link in the menu, if the first link has the focus:
- if (jQuery(menu).find('a:first:focus').length > 0) {
- //Put the focus on the action menu button:
- jQuery(menu).find('button.action-menu-icon').focus();
- return true;
- } else if (jQuery(menu).find('button.action-menu-icon:focus').length > 0) {
- //Put the focus on the last action menu item:
- jQuery(menu).find('a:last').focus();
- return true;
- }
- } else {
- //Put the focus on the first link in the menu, if the last link has the focus:
- if (jQuery(menu).find('a:last:focus').length > 0) {
- //Put the focus on the action menu button:
- jQuery(menu).find('button.action-menu-icon').focus();
- return true;
- } else if (jQuery(menu).find('button.action-menu-icon:focus').length > 0) {
- //Put the focus on the first action menu item:
- jQuery(menu).find('a:first').focus();
- return true;
- }
+ tabThroughItems(reverse = false) {
+ const items = Array.from(this.menu[0].querySelectorAll([
+ '.action-menu-icon',
+ '.action-menu-item:not(.action-menu-item-disabled) a',
+ '.action-menu-item:not(.action-menu-item-disabled) button',
+ '.action-menu-item:not(.action-menu-item-disabled) label',
+ ].join(',')));
+
+ // Get index of currently focussed element
+ let index = items.findIndex(element => element === document.activeElement);
+ if (index === -1) {
+ index = 0;
}
- return false;
+
+ // Get new index based on direction
+ index = (index + (reverse ? -1 : 1) + items.length) % items.length;
+
+ // Focus element
+ items[index].focus();
}
}