From c04c0cc65113393710845e2a7b8240cd46bd761f Mon Sep 17 00:00:00 2001 From: Ewa Gasperowicz Date: Thu, 27 Apr 2017 00:41:31 +0200 Subject: [PATCH 1/2] Adding howto-menu-button element --- elements/howto-menu-button/demo.html | 32 +++++++ .../howto-menu-button.e2etest.js | 62 +++++++++++++ .../howto-menu-button/howto-menu-button.js | 88 +++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 elements/howto-menu-button/demo.html create mode 100644 elements/howto-menu-button/howto-menu-button.e2etest.js create mode 100644 elements/howto-menu-button/howto-menu-button.js diff --git a/elements/howto-menu-button/demo.html b/elements/howto-menu-button/demo.html new file mode 100644 index 00000000..096637de --- /dev/null +++ b/elements/howto-menu-button/demo.html @@ -0,0 +1,32 @@ + + + + +
+ Menu + + +
diff --git a/elements/howto-menu-button/howto-menu-button.e2etest.js b/elements/howto-menu-button/howto-menu-button.e2etest.js new file mode 100644 index 00000000..a9475f2b --- /dev/null +++ b/elements/howto-menu-button/howto-menu-button.e2etest.js @@ -0,0 +1,62 @@ +const helper = require('../../tools/selenium-helper.js'); +const expect = require('chai').expect; +const {Key} = require('selenium-webdriver'); + +describe('howto-menu-button', function() { + let success; + let driver; + beforeEach(function() { + driver = this.driver; + return this.driver.get(`${this.address}/howto-menu-button_demo.html`) + .then(_ => helper.waitForElement(this.driver, 'howto-menu-button')); + }); + + function _focusMenuBtn() { + return driver.executeScript(_ => { + window.menuBtn = document.querySelector('howto-menu-button'); + window.menuBtn.focus(); + }); + } + + async function _assessFirstItemFocused() { + success = await driver.executeScript(` + return document.activeElement === + document.querySelector('[role="menuitem"]'); + `); + expect(success).to.equal(true); + } + + async function _assessMenuOpened() { + success = await driver.executeScript(` + const menu = document.querySelector('[aria-labelledby="menu-btn"]'); + return menu.getAttribute('aria-hidden') === 'false'; + `); + expect(success).to.equal(true); + } + + let tests = [ + {key: 'ARROW_DOWN'}, + {key: 'ENTER'}, + {key: 'SPACE'}, + ]; + + tests.forEach(function(test) { + it('should open the menu on [' + test.key + ']', + async function() { + await _focusMenuBtn(); + await this.driver.actions().sendKeys(Key[test.key]).perform(); + await _assessMenuOpened(); + } + ); + }); + + tests.forEach(function(test) { + it('should focus the first menu item on [' + test.key + ']', + async function() { + await _focusMenuBtn(); + await this.driver.actions().sendKeys(Key[test.key]).perform(); + await _assessFirstItemFocused(); + } + ); + }); +}); diff --git a/elements/howto-menu-button/howto-menu-button.js b/elements/howto-menu-button/howto-menu-button.js new file mode 100644 index 00000000..3b1cf607 --- /dev/null +++ b/elements/howto-menu-button/howto-menu-button.js @@ -0,0 +1,88 @@ +/* + * A menu button is a button that opens a menu. + * It is referenced by the menu using aria-labelledby attribute. + * + * See: https://www.w3.org/TR/wai-aria-practices-1.1/#menubutton + */ +(function() { + /** + * Define key codes to help with handling keyboard events. + */ + const KEYCODE = { + DOWN: 40, + ENTER: 13, + SPACE: 32, + }; + + class HowtoMenuButton extends HTMLElement { + /** + * A getter for the first menuitem in the menu. + */ + get firstMenuItem() { + return this._menu.querySelector('[role^="menuitem"]:first-of-type'); + } + + /** + * Returns true if the menu is currently opened. + */ + _isMenuOpen() { + return !(this._menu.getAttribute('aria-hidden') === 'true'); + } + + /** + * Opens the menu if it was closed and vice versa. + */ + _toggleMenu() { + const isOpen = this._isMenuOpen(); + this._menu.setAttribute('aria-hidden', isOpen); + if (!isOpen) { + // Set focus on first menuitem. + this.firstMenuItem && this.firstMenuItem.focus(); + } + } + + /** + * Controls keyboard interactions. + */ + _handleKeydown(e) { + const triggers = [KEYCODE.DOWN, KEYCODE.ENTER, KEYCODE.SPACE]; + if (triggers.indexOf(e.keyCode) > -1) { + this._toggleMenu(); + } + } + + /** + * Sets up the menu button element. + */ + connectedCallback() { + this.setAttribute('role', 'button'); + this.setAttribute('aria-label', 'menu'); + this.setAttribute('aria-haspopup', true); + this.setAttribute('tabindex', 0); + + this.style.display = 'inline-block'; + this.style.width = parseInt(this.getAttribute('width'), 10) + 'px'; + this.style.height = parseInt(this.getAttribute('height'), 10) + 'px'; + this.style.overflow = 'hidden'; + this.style.color = 'transparent'; + this.style.background = ('linear-gradient(to bottom, black, black 20%,' + + ' white 20%, white 40%, black 40%, black 60%, white 60%, white 80%,' + + ' black 80%, black 100%)'); + + this._menu = document.querySelector('[aria-labelledby=' + this.id + ']'); + // TODO: Should this relationship use labelledby or aria-controls? + this.addEventListener('click', this._toggleMenu); + this.addEventListener('keydown', this._handleKeydown); + } + + /** + * Unregisters the event listeners that were set up in connectedCallback. + */ + disconnectedCallback() { + this.removeEventListener('click', this._toggleMenu); + this.removeEventListener('keydown', this._handleKeydown); + } + } + + window.customElements.define('howto-menu-button', HowtoMenuButton); +})(); From c6da114c5840df4c10e03e01d9d2238a288bd857 Mon Sep 17 00:00:00 2001 From: Surma Date: Thu, 4 May 2017 14:30:41 +0100 Subject: [PATCH 2/2] Fix build --- elements/howto-menu-button/README.md | 4 ++++ elements/howto-menu-button/howto-menu-button.e2etest.js | 2 +- elements/howto-menu-button/howto-menu-button.js | 6 ------ 3 files changed, 5 insertions(+), 7 deletions(-) create mode 100644 elements/howto-menu-button/README.md diff --git a/elements/howto-menu-button/README.md b/elements/howto-menu-button/README.md new file mode 100644 index 00000000..6d20020a --- /dev/null +++ b/elements/howto-menu-button/README.md @@ -0,0 +1,4 @@ +A menu button is a button that opens a menu. +It is referenced by the menu using aria-labelledby attribute. + +See: https://www.w3.org/TR/wai-aria-practices-1.1/#menubutton diff --git a/elements/howto-menu-button/howto-menu-button.e2etest.js b/elements/howto-menu-button/howto-menu-button.e2etest.js index a9475f2b..c6e84d67 100644 --- a/elements/howto-menu-button/howto-menu-button.e2etest.js +++ b/elements/howto-menu-button/howto-menu-button.e2etest.js @@ -7,7 +7,7 @@ describe('howto-menu-button', function() { let driver; beforeEach(function() { driver = this.driver; - return this.driver.get(`${this.address}/howto-menu-button_demo.html`) + return this.driver.get(`${this.address}/howto-menu-button/demo.html`) .then(_ => helper.waitForElement(this.driver, 'howto-menu-button')); }); diff --git a/elements/howto-menu-button/howto-menu-button.js b/elements/howto-menu-button/howto-menu-button.js index 3b1cf607..81dbc02c 100644 --- a/elements/howto-menu-button/howto-menu-button.js +++ b/elements/howto-menu-button/howto-menu-button.js @@ -1,9 +1,3 @@ -/* - * A menu button is a button that opens a menu. - * It is referenced by the menu using aria-labelledby attribute. - * - * See: https://www.w3.org/TR/wai-aria-practices-1.1/#menubutton - */ (function() { /** * Define key codes to help with handling keyboard events.