summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThibG <thib@sitedethib.com>2019-08-06 11:59:46 +0200
committerEugen Rochko <eugen@zeonfederated.com>2019-08-09 21:34:54 +0200
commitad6fcb2d9c791c9ac7c17b25221801f75dba644c (patch)
tree4c6248fc62e50074631aa8bca7703c4362906dd2
parentd8cf2a0fb69dc4c862921e497103ce8b02fab7fd (diff)
Improve dropdown menu keyboard navigation (#11491)
* Allow selecting menu items with the space bar in status dropdown menus * Fix modals opened by keyboard navigation being immediately closed * Fix menu items triggering modal actions * Add Tab trapping inside dropdown menu * Give focus back to last focused element when status dropdown menu closes
-rw-r--r--app/javascript/mastodon/actions/modal.js3
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js44
-rw-r--r--app/javascript/mastodon/containers/dropdown_menu_container.js2
-rw-r--r--app/javascript/mastodon/reducers/modal.js2
4 files changed, 30 insertions, 21 deletions
diff --git a/app/javascript/mastodon/actions/modal.js b/app/javascript/mastodon/actions/modal.js
index 80e15c28ec1..3d0299db58a 100644
--- a/app/javascript/mastodon/actions/modal.js
+++ b/app/javascript/mastodon/actions/modal.js
@@ -9,8 +9,9 @@ export function openModal(type, props) {
};
};
-export function closeModal() {
+export function closeModal(type) {
return {
type: MODAL_CLOSE,
+ modalType: type,
};
};
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index e122515c49d..9937d0f886c 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -45,7 +45,10 @@ class DropdownMenu extends React.PureComponent {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('keydown', this.handleKeyDown, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
- if (this.focusedItem && this.props.openedViaKeyboard) this.focusedItem.focus();
+ this.activeElement = document.activeElement;
+ if (this.focusedItem && this.props.openedViaKeyboard) {
+ this.focusedItem.focus();
+ }
this.setState({ mounted: true });
}
@@ -53,6 +56,9 @@ class DropdownMenu extends React.PureComponent {
document.removeEventListener('click', this.handleDocumentClick, false);
document.removeEventListener('keydown', this.handleKeyDown, false);
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
+ if (this.activeElement) {
+ this.activeElement.focus();
+ }
}
setRef = c => {
@@ -81,6 +87,18 @@ class DropdownMenu extends React.PureComponent {
element.focus();
}
break;
+ case 'Tab':
+ if (e.shiftKey) {
+ element = items[index-1] || items[items.length-1];
+ } else {
+ element = items[index+1] || items[0];
+ }
+ if (element) {
+ element.focus();
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ break;
case 'Home':
element = items[0];
if (element) {
@@ -93,11 +111,14 @@ class DropdownMenu extends React.PureComponent {
element.focus();
}
break;
+ case 'Escape':
+ this.props.onClose();
+ break;
}
}
- handleItemKeyDown = e => {
- if (e.key === 'Enter') {
+ handleItemKeyUp = e => {
+ if (e.key === 'Enter' || e.key === ' ') {
this.handleClick(e);
}
}
@@ -126,7 +147,7 @@ class DropdownMenu extends React.PureComponent {
return (
<li className='dropdown-menu__item' key={`${text}-${i}`}>
- <a href={href} target={target} data-method={method} rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyDown={this.handleItemKeyDown} data-index={i}>
+ <a href={href} target={target} data-method={method} rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyUp={this.handleItemKeyUp} data-index={i}>
{text}
</a>
</li>
@@ -202,19 +223,6 @@ export default class Dropdown extends React.PureComponent {
this.props.onClose(this.state.id);
}
- handleKeyDown = e => {
- switch(e.key) {
- case ' ':
- case 'Enter':
- this.handleClick(e);
- e.preventDefault();
- break;
- case 'Escape':
- this.handleClose();
- break;
- }
- }
-
handleItemClick = e => {
const i = Number(e.currentTarget.getAttribute('data-index'));
const { action, to } = this.props.items[i];
@@ -249,7 +257,7 @@ export default class Dropdown extends React.PureComponent {
const open = this.state.id === openDropdownId;
return (
- <div onKeyDown={this.handleKeyDown}>
+ <div>
<IconButton
icon={icon}
title={title}
diff --git a/app/javascript/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js
index 73c8a1e530e..f79b192029e 100644
--- a/app/javascript/mastodon/containers/dropdown_menu_container.js
+++ b/app/javascript/mastodon/containers/dropdown_menu_container.js
@@ -20,7 +20,7 @@ const mapDispatchToProps = (dispatch, { status, items }) => ({
}) : openDropdownMenu(id, dropdownPlacement, keyboard));
},
onClose(id) {
- dispatch(closeModal());
+ dispatch(closeModal('ACTIONS'));
dispatch(closeDropdownMenu(id));
},
});
diff --git a/app/javascript/mastodon/reducers/modal.js b/app/javascript/mastodon/reducers/modal.js
index 599a2443e0d..a30da2db1b4 100644
--- a/app/javascript/mastodon/reducers/modal.js
+++ b/app/javascript/mastodon/reducers/modal.js
@@ -10,7 +10,7 @@ export default function modal(state = initialState, action) {
case MODAL_OPEN:
return { modalType: action.modalType, modalProps: action.modalProps };
case MODAL_CLOSE:
- return initialState;
+ return (action.modalType === undefined || action.modalType === state.modalType) ? initialState : state;
default:
return state;
}