Improved a11y of interactive drop-downs
authorMarcel Werk <burntime@woltlab.com>
Sat, 16 Mar 2019 18:47:16 +0000 (19:47 +0100)
committerMarcel Werk <burntime@woltlab.com>
Sat, 16 Mar 2019 18:47:16 +0000 (19:47 +0100)
See #2713

wcfsetup/install/files/js/WCF.User.js

index fa3b6ea187253d3b8354df993603c737ffe2f47e..052d4f961cb838d6dc7e1a7654cab4f746ed854e 100644 (file)
@@ -164,6 +164,21 @@ if (COMPILER_TARGET_DEFAULT) {
                 */
                _triggerElement: null,
                
+               /**
+                * @var        Element
+                */
+               _button: null,
+               
+               /**
+                * @var        Function
+                */
+                _callbackFocus: null,
+               
+               /**
+                * @var        boolean
+                */
+               _wasInsideDropdown: false,
+               
                /**
                 * Initializes the WCF.User.Panel.Abstract class.
                 *
@@ -177,6 +192,7 @@ if (COMPILER_TARGET_DEFAULT) {
                        this._identifier = identifier;
                        this._triggerElement = triggerElement;
                        this._options = options;
+                       this._callbackFocus = null;
                        
                        this._proxy = new WCF.Action.Proxy({
                                showLoadingOverlay: false,
@@ -184,6 +200,13 @@ if (COMPILER_TARGET_DEFAULT) {
                        });
                        
                        this._triggerElement.click($.proxy(this.toggle, this));
+                       this._button = elBySel('a', this._triggerElement[0]);
+                       if (this._button) {
+                               elAttr(this._button, 'role', 'button');
+                               elAttr(this._button, 'tabindex', '0');
+                               elAttr(this._button, 'aria-haspopup', true);
+                               elAttr(this._button, 'aria-expanded', false);
+                       }
                        
                        if (this._options.showAllLink) {
                                this._triggerElement.dblclick($.proxy(this._dblClick, this));
@@ -230,6 +253,16 @@ if (COMPILER_TARGET_DEFAULT) {
                                        this._loadData = false;
                                        this._load();
                                }
+                               
+                               elAttr(this._button, 'aria-expanded', true);
+                               if (this._callbackFocus === null) {
+                                       this._callbackFocus = this._maintainFocus.bind(this);
+                               }
+                               document.body.addEventListener('focus', this._callbackFocus, { capture: true });
+                       }
+                       else {
+                               elAttr(this._button, 'aria-expanded', false);
+                               document.body.removeEventListener('focus', this._callbackFocus);
                        }
                        
                        return false;
@@ -416,6 +449,26 @@ if (COMPILER_TARGET_DEFAULT) {
                                this._dropdown.resetItems();
                                this._loadData = true;
                        }
+               },
+               
+               /**
+                * @param {Event} event
+                */
+               _maintainFocus: function(event) {
+                       var dropdown = this._dropdown.getContainer()[0];
+                       
+                       if (!dropdown.contains(event.target)) {
+                               if (this._wasInsideDropdown) {
+                                       this._button.focus();
+                                       this._wasInsideDropdown = false;
+                               }
+                               else {
+                                       elBySel('a', dropdown).focus();
+                               }
+                       }
+                       else {
+                               this._wasInsideDropdown = true;
+                       }
                }
        });