From 52e590a8298d6a67ad7bfffdbffcf9d67aae4776 Mon Sep 17 00:00:00 2001 From: Lauri Eskola <lauri.eskola@acquia.com> Date: Tue, 8 Feb 2022 11:28:48 +0200 Subject: [PATCH] Issue #3231321 by bnjmnm, nod_, lauriii: Improve keyboard accessibility in a particular edge case --- .../ckeditor5/js/ckeditor5.admin.es6.js | 48 ++++++++---- core/modules/ckeditor5/js/ckeditor5.admin.js | 73 ++++++++++++------- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/core/modules/ckeditor5/js/ckeditor5.admin.es6.js b/core/modules/ckeditor5/js/ckeditor5.admin.es6.js index dfa136b9098f..e913b4d8ec16 100644 --- a/core/modules/ckeditor5/js/ckeditor5.admin.es6.js +++ b/core/modules/ckeditor5/js/ckeditor5.admin.es6.js @@ -3,7 +3,7 @@ * Provides admin UI for the CKEditor 5. */ -((Drupal, drupalSettings, $, JSON, once, Sortable) => { +((Drupal, drupalSettings, $, JSON, once, Sortable, { tabbable }) => { const toolbarHelp = [ { message: Drupal.t( @@ -584,10 +584,7 @@ // that can catch blur-causing events before the blur happens. If the // tooltip is hidden before the blur event, the outline will disappear // correctly. - once( - 'safari-focus-fix', - document.querySelectorAll('.ckeditor5-toolbar-item'), - ).forEach((item) => { + once('safari-focus-fix', '.ckeditor5-toolbar-item').forEach((item) => { item.addEventListener('keydown', (e) => { const keyCodeDirections = { 9: 'tab', @@ -679,9 +676,7 @@ // information can be retrieved after AJAX rebuilds. once( 'ui-state-storage', - document.querySelector( - '#filter-format-edit-form, #filter-format-add-form', - ), + '#filter-format-edit-form, #filter-format-add-form', ).forEach((form) => { form.setAttribute('data-drupal-ui-state', JSON.stringify({})); }); @@ -699,7 +694,32 @@ const activeTab = getUiStateStorage(`${id}-active-tab`); if (activeTab) { setTimeout(() => { - document.querySelector(activeTab).click(); + const activeTabLink = document.querySelector(activeTab); + activeTabLink.click(); + + // Only change focus on the plugin-settings-wrapper element. + if (id !== 'plugin-settings-wrapper') { + return; + } + // If the current focused element is not the body, then the user + // navigated away from the vertical tab area and is somewhere else + // within the form. Do not change the current focus. + if (document.activeElement !== document.body) { + return; + } + // If the active element is the body then we can assume that the + // focus was on an element that was replaced by an ajax command. + // If that is the case restore the focus to the active tab that + // was just rebuilt. + const targetTabPane = document.querySelector( + activeTabLink.getAttribute('href'), + ); + if (targetTabPane) { + const tabbableElements = tabbable(targetTabPane); + if (tabbableElements.length) { + tabbableElements[0].focus(); + } + } }); } @@ -718,12 +738,8 @@ }; once( - 'plugin-settings', - document.querySelector('#plugin-settings-wrapper'), - ).forEach(maintainActiveVerticalTab); - once( - 'filter-settings', - document.querySelector('#filter-settings-wrapper'), + 'maintainActiveVerticalTab', + '#plugin-settings-wrapper, #filter-settings-wrapper', ).forEach(maintainActiveVerticalTab); // Add listeners to maintain focus after AJAX rebuilds. @@ -1022,4 +1038,4 @@ }); }, }; -})(Drupal, drupalSettings, jQuery, JSON, once, Sortable); +})(Drupal, drupalSettings, jQuery, JSON, once, Sortable, tabbable); diff --git a/core/modules/ckeditor5/js/ckeditor5.admin.js b/core/modules/ckeditor5/js/ckeditor5.admin.js index 0c66220203eb..fe4b8ed7e2ff 100644 --- a/core/modules/ckeditor5/js/ckeditor5.admin.js +++ b/core/modules/ckeditor5/js/ckeditor5.admin.js @@ -37,7 +37,8 @@ function _defineProperties(target, props) { for (var i = 0; i < props.length; i+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } -(function (Drupal, drupalSettings, $, JSON, once, Sortable) { +(function (Drupal, drupalSettings, $, JSON, once, Sortable, _ref) { + var tabbable = _ref.tabbable; var toolbarHelp = [{ message: Drupal.t("The toolbar buttons that don't fit the user's browser window width will be grouped in a dropdown. If multiple toolbar rows are preferred, those can be configured by adding an explicit wrapping breakpoint wherever you want to start a new row.", null, { context: 'CKEditor 5 toolbar help text, default, no explicit wrapping breakpoint' @@ -325,10 +326,10 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d attach: function attach(context) { once('ckeditor5-admin-toolbar', '#ckeditor5-toolbar-app').forEach(function (container) { var selectedTextarea = context.querySelector('#ckeditor5-toolbar-buttons-selected'); - var available = Object.entries(JSON.parse(context.querySelector('#ckeditor5-toolbar-buttons-available').innerHTML)).map(function (_ref) { - var _ref2 = _slicedToArray(_ref, 2), - name = _ref2[0], - attrs = _ref2[1]; + var available = Object.entries(JSON.parse(context.querySelector('#ckeditor5-toolbar-buttons-available').innerHTML)).map(function (_ref2) { + var _ref3 = _slicedToArray(_ref2, 2), + name = _ref3[0], + attrs = _ref3[1]; return _objectSpread({ name: name, @@ -369,7 +370,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d }); render(container, selected, available, dividers); }); - once('safari-focus-fix', document.querySelectorAll('.ckeditor5-toolbar-item')).forEach(function (item) { + once('safari-focus-fix', '.ckeditor5-toolbar-item').forEach(function (item) { item.addEventListener('keydown', function (e) { var keyCodeDirections = { 9: 'tab', @@ -414,7 +415,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d return form.hasAttribute('data-drupal-ui-state') ? JSON.parse(form.getAttribute('data-drupal-ui-state'))[property] : null; }; - once('ui-state-storage', document.querySelector('#filter-format-edit-form, #filter-format-add-form')).forEach(function (form) { + once('ui-state-storage', '#filter-format-edit-form, #filter-format-add-form').forEach(function (form) { form.setAttribute('data-drupal-ui-state', JSON.stringify({})); }); @@ -424,7 +425,26 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d if (activeTab) { setTimeout(function () { - document.querySelector(activeTab).click(); + var activeTabLink = document.querySelector(activeTab); + activeTabLink.click(); + + if (id !== 'plugin-settings-wrapper') { + return; + } + + if (document.activeElement !== document.body) { + return; + } + + var targetTabPane = document.querySelector(activeTabLink.getAttribute('href')); + + if (targetTabPane) { + var tabbableElements = tabbable(targetTabPane); + + if (tabbableElements.length) { + tabbableElements[0].focus(); + } + } }); } @@ -438,8 +458,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d }); }; - once('plugin-settings', document.querySelector('#plugin-settings-wrapper')).forEach(maintainActiveVerticalTab); - once('filter-settings', document.querySelector('#filter-settings-wrapper')).forEach(maintainActiveVerticalTab); + once('maintainActiveVerticalTab', '#plugin-settings-wrapper, #filter-settings-wrapper').forEach(maintainActiveVerticalTab); var selectedButtons = document.querySelector('#ckeditor5-toolbar-buttons-selected'); once('textarea-listener', selectedButtons).forEach(function (textarea) { textarea.addEventListener('change', function (e) { @@ -507,8 +526,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d } }; - Drupal.theme.ckeditor5SelectedButtons = function (_ref3) { - var buttons = _ref3.buttons; + Drupal.theme.ckeditor5SelectedButtons = function (_ref4) { + var buttons = _ref4.buttons; return "\n <ul class=\"ckeditor5-toolbar-tray ckeditor5-toolbar-active__buttons\" data-button-list=\"ckeditor5-toolbar-active-buttons\" role=\"listbox\" aria-orientation=\"horizontal\" aria-labelledby=\"ckeditor5-toolbar-active-buttons-label\">\n ".concat(buttons.map(function (button) { return Drupal.theme.ckeditor5Button({ button: button, @@ -517,8 +536,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d }).join(''), "\n </ul>\n "); }; - Drupal.theme.ckeditor5DividerButtons = function (_ref4) { - var buttons = _ref4.buttons; + Drupal.theme.ckeditor5DividerButtons = function (_ref5) { + var buttons = _ref5.buttons; return "\n <ul class=\"ckeditor5-toolbar-tray ckeditor5-toolbar-divider__buttons\" data-button-list=\"ckeditor5-toolbar-divider-buttons\" role=\"listbox\" aria-orientation=\"horizontal\" aria-labelledby=\"ckeditor5-toolbar-divider-buttons-label\">\n ".concat(buttons.map(function (button) { return Drupal.theme.ckeditor5Button({ button: button, @@ -527,8 +546,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d }).join(''), "\n </ul>\n "); }; - Drupal.theme.ckeditor5AvailableButtons = function (_ref5) { - var buttons = _ref5.buttons; + Drupal.theme.ckeditor5AvailableButtons = function (_ref6) { + var buttons = _ref6.buttons; return "\n <ul class=\"ckeditor5-toolbar-tray ckeditor5-toolbar-available__buttons\" data-button-list=\"ckeditor5-toolbar-available-buttons\" role=\"listbox\" aria-orientation=\"horizontal\" aria-labelledby=\"ckeditor5-toolbar-available-buttons-label\">\n ".concat(buttons.map(function (button) { return Drupal.theme.ckeditor5Button({ button: button, @@ -537,11 +556,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d }).join(''), "\n </ul>\n "); }; - Drupal.theme.ckeditor5Button = function (_ref6) { - var _ref6$button = _ref6.button, - label = _ref6$button.label, - id = _ref6$button.id, - listType = _ref6.listType; + Drupal.theme.ckeditor5Button = function (_ref7) { + var _ref7$button = _ref7.button, + label = _ref7$button.label, + id = _ref7$button.id, + listType = _ref7.listType; var visuallyHiddenLabel = Drupal.t("@listType button @label", { '@listType': listType !== 'divider' ? listType : 'available', '@label': label @@ -549,11 +568,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d return "\n <li class=\"ckeditor5-toolbar-item ckeditor5-toolbar-item-".concat(id, "\" role=\"option\" tabindex=\"0\" data-drupal-selector=\"ckeditor5-toolbar-button\" data-id=\"").concat(id, "\" data-label=\"").concat(label, "\" data-divider=\"").concat(listType === 'divider', "\">\n <span class=\"ckeditor5-toolbar-button ckeditor5-toolbar-button-").concat(id, "\">\n <span class=\"visually-hidden\">").concat(visuallyHiddenLabel, "</span>\n </span>\n <span class=\"ckeditor5-toolbar-tooltip\" aria-hidden=\"true\">").concat(label, "</span>\n </li>\n "); }; - Drupal.theme.ckeditor5Admin = function (_ref7) { - var availableButtons = _ref7.availableButtons, - dividerButtons = _ref7.dividerButtons, - activeToolbar = _ref7.activeToolbar, - helpMessage = _ref7.helpMessage; + Drupal.theme.ckeditor5Admin = function (_ref8) { + var availableButtons = _ref8.availableButtons, + dividerButtons = _ref8.dividerButtons, + activeToolbar = _ref8.activeToolbar, + helpMessage = _ref8.helpMessage; return "\n <div aria-live=\"polite\" data-drupal-selector=\"ckeditor5-admin-help-message\">\n <p>".concat(helpMessage.join('</p><p>'), "</p>\n </div>\n <div class=\"ckeditor5-toolbar-disabled\">\n <div class=\"ckeditor5-toolbar-available\">\n <label id=\"ckeditor5-toolbar-available-buttons-label\">").concat(Drupal.t('Available buttons'), "</label>\n ").concat(availableButtons, "\n </div>\n <div class=\"ckeditor5-toolbar-divider\">\n <label id=\"ckeditor5-toolbar-divider-buttons-label\">").concat(Drupal.t('Button divider'), "</label>\n ").concat(dividerButtons, "\n </div>\n </div>\n <div class=\"ckeditor5-toolbar-active\">\n <label id=\"ckeditor5-toolbar-active-buttons-label\">").concat(Drupal.t('Active toolbar'), "</label>\n ").concat(activeToolbar, "\n </div>\n "); }; @@ -581,4 +600,4 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d }); } }; -})(Drupal, drupalSettings, jQuery, JSON, once, Sortable); \ No newline at end of file +})(Drupal, drupalSettings, jQuery, JSON, once, Sortable, tabbable); \ No newline at end of file -- GitLab