From e7c33bbfc2bf5ff67aa02cf651bbc828d4a4562c Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Wed, 18 Oct 2023 00:32:53 +0200 Subject: [PATCH] feat(MenuBar): Make the menu bar `role=toolbar` and add focus handling For accessibility the toolbar should be implemented using `role=toolbar`. This requires custom focus handling[1] this is done by making only one element tabable. [1] https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/toolbar_role#description Signed-off-by: Ferdinand Thiessen --- src/components/Menu/ActionEntry.js | 49 --------------- src/components/Menu/BaseActionEntry.js | 33 +++++++++++ src/components/Menu/MenuBar.vue | 49 +++++++++------ src/components/Menu/ReadonlyBar.vue | 27 ++++++--- src/components/Menu/ToolBarLogic.js | 82 ++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 74 deletions(-) delete mode 100644 src/components/Menu/ActionEntry.js create mode 100644 src/components/Menu/ToolBarLogic.js diff --git a/src/components/Menu/ActionEntry.js b/src/components/Menu/ActionEntry.js deleted file mode 100644 index a5666f4efbb..00000000000 --- a/src/components/Menu/ActionEntry.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * @copyright Copyright (c) 2022 Vinicius Reis - * - * @author Vinicius Reis - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -import ActionSingle from './ActionSingle.vue' -import ActionList from './ActionList.vue' - -export default { - name: 'ActionEntry', - functional: true, - render(h, ctx) { - const { actionEntry } = ctx.props - const { data, props, listeners } = ctx - const { key } = data - - const params = { - data, - key, - props, - on: listeners, - } - - if (actionEntry.component) { - return h(actionEntry.component, params) - } - - return actionEntry.children - ? h(ActionList, params) - : h(ActionSingle, params) - }, -} diff --git a/src/components/Menu/BaseActionEntry.js b/src/components/Menu/BaseActionEntry.js index c53d9e82336..fc675cfe678 100644 --- a/src/components/Menu/BaseActionEntry.js +++ b/src/components/Menu/BaseActionEntry.js @@ -45,6 +45,10 @@ const BaseActionEntry = { type: Object, required: true, }, + canBeFocussed: { + type: Boolean, + default: null, + }, }, data() { return { @@ -72,10 +76,20 @@ const BaseActionEntry = { ].join(' ') }, }, + watch: { + /** Handle tabindex for menu toolbar */ + canBeFocussed() { + this.setTabIndexOnButton() + }, + }, mounted() { this.$_updateState = debounce(this.updateState.bind(this), 50) this.$editor.on('update', this.$_updateState) this.$editor.on('selectionUpdate', this.$_updateState) + // Initially emit the disabled event to set the state in parent + this.$emit('disabled', this.state.disabled) + // Initially set the tabindex + this.setTabIndexOnButton() }, beforeDestroy() { this.$editor.off('update', this.$_updateState) @@ -84,6 +98,25 @@ const BaseActionEntry = { methods: { updateState() { this.state = getActionState(this.actionEntry, this.$editor) + this.$emit('disabled', this.state.disabled) + }, + setTabIndexOnButton() { + /** @type {HTMLButtonElement} */ + const button = this.$el.tagName.toLowerCase() === 'button' ? this.$el : this.$el.querySelector('button') + + if (this.canBeFocussed === null) { + button.removeAttribute('tabindex') + } else { + button.setAttribute('tabindex', this.canBeFocussed ? '0' : '-1') + } + }, + /** + * Focus the inner button of this action + */ + focusButton() { + /** @type {HTMLButtonElement} */ + const button = this.$el.tagName.toLowerCase() === 'button' ? this.$el : this.$el.querySelector('button') + button.focus() }, }, } diff --git a/src/components/Menu/MenuBar.vue b/src/components/Menu/MenuBar.vue index 16ebcea7cba..3d413030bce 100644 --- a/src/components/Menu/MenuBar.vue +++ b/src/components/Menu/MenuBar.vue @@ -25,8 +25,8 @@