diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/js/controls.js | 101 | ||||
-rw-r--r-- | src/js/listeners.js | 28 | ||||
-rw-r--r-- | src/js/utils/elements.js | 15 | ||||
-rw-r--r-- | src/sass/lib/mixins.scss | 2 |
4 files changed, 121 insertions, 25 deletions
diff --git a/src/js/controls.js b/src/js/controls.js index 710ee93b..0f2db4f3 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -9,7 +9,7 @@ import support from './support'; import { repaint, transitionEndEvent } from './utils/animation'; import { dedupe } from './utils/arrays'; import browser from './utils/browser'; -import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, matches, removeElement, setAttributes, toggleClass, toggleHidden } from './utils/elements'; +import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, matches, removeElement, setAttributes, setFocus, toggleClass, toggleHidden } from './utils/elements'; import { off, on } from './utils/events'; import is from './utils/is'; import loadSprite from './utils/loadSprite'; @@ -175,7 +175,7 @@ const controls = { } if ('class' in attributes) { - if (attributes.class.includes(this.config.classNames.control)) { + if (!attributes.class.includes(this.config.classNames.control)) { attributes.class += ` ${this.config.classNames.control}`; } } else { @@ -1016,12 +1016,20 @@ const controls = { button.setAttribute('aria-expanded', show); } + // Show the actual popup if (is.element(popup)) { toggleHidden(popup, !show); toggleClass(this.elements.container, this.config.classNames.menu.open, show); if (show) { popup.removeAttribute('tabindex'); + + // Focus the first item if key interaction + if (event.type === 'keydown') { + const pane = Object.values(this.elements.settings.panels).find(pane => !pane.hidden); + const firstItem = pane.querySelector('[role^="menuitem"]'); + setFocus.call(this, firstItem, true); + } } else { popup.setAttribute('tabindex', -1); } @@ -1104,9 +1112,7 @@ const controls = { // Focus the first item const firstItem = target.querySelector('[role^="menuitem"]'); - if (firstItem) { - firstItem.focus(); - } + setFocus.call(this, firstItem, true); }, // Build the default HTML @@ -1257,6 +1263,9 @@ const controls = { role: 'menu', }); + home.appendChild(menu); + inner.appendChild(home); + // Build the menu items this.config.settings.forEach(type => { const menuItem = createElement( @@ -1270,6 +1279,26 @@ const controls = { }), ); + // Handle space or -> to open menu + on(menuItem, 'keydown', event => { + // We only care about space and -> + if (![32,39].includes(event.which)) { + return; + } + + // Prevent play / seek + event.preventDefault(); + event.stopPropagation(); + + // Show the respective menu + controls.showMenuPanel.call(this, type); + }, false); + + // Show menu on click + on(menuItem, 'click', () => { + controls.showMenuPanel.call(this, type); + }); + const flex = createElement('span', null, i18n.get(type, this.config)); const value = createElement('span', { @@ -1290,18 +1319,55 @@ const controls = { }); // Back button - const back = createElement( - 'button', - { - type: 'button', - class: `${this.config.classNames.control} ${this.config.classNames.control}--back`, - }, - i18n.get(type, this.config), + const backButton = createElement('button', { + type: 'button', + class: `${this.config.classNames.control} ${this.config.classNames.control}--back`, + }); + + // Visible label + backButton.appendChild( + createElement( + 'span', + { + 'aria-hidden': true, + }, + i18n.get(type, this.config), + ), + ); + + // Screen reader label + backButton.appendChild( + createElement( + 'span', + { + class: this.config.classNames.hidden, + }, + i18n.get('menuBack', this.config), + ), ); - back.addEventListener('click', () => { + + // Handle space or -> to open menu + on(backButton, 'keydown', event => { + // We only care about <- + if (event.which !== 37) { + return; + } + + // Prevent seek + event.preventDefault(); + event.stopPropagation(); + + // Show the respective menu + controls.showMenuPanel.call(this, 'home'); + }, false); + + // Go back + on(backButton, 'click', () => { controls.showMenuPanel.call(this, 'home'); }); - pane.appendChild(back); + + // Add to pane + pane.appendChild(backButton); // Menu pane.appendChild( @@ -1312,17 +1378,10 @@ const controls = { inner.appendChild(pane); - menuItem.addEventListener('click', () => { - controls.showMenuPanel.call(this, type); - }); - this.elements.settings.buttons[type] = menuItem; this.elements.settings.panels[type] = pane; }); - home.appendChild(menu); - inner.appendChild(home); - popup.appendChild(inner); control.appendChild(popup); container.appendChild(control); diff --git a/src/js/listeners.js b/src/js/listeners.js index 05a12147..ffcd81fa 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -483,11 +483,34 @@ class Listeners { // Airplay this.bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay'); - // Settings menu + // Settings menu - click toggle this.bind(this.player.elements.buttons.settings, 'click', event => { controls.toggleMenu.call(this.player, event); }); + // Settings menu - keyboard toggle + this.bind( + this.player.elements.buttons.settings, + 'keydown', + event => { + // We only care about space + if (event.which !== 32) { + return; + } + + // Prevent scroll + event.preventDefault(); + + // Prevent playing video + event.stopPropagation(); + + // Toggle menu + controls.toggleMenu.call(this.player, event); + }, + null, + false, + ); + // Set range input alternative "value", which matches the tooltip time (#954) this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => { const clientRect = this.player.elements.progress.getBoundingClientRect(); @@ -626,8 +649,7 @@ class Listeners { const inverted = event.webkitDirectionInvertedFromDevice; // Get delta from event. Invert if `inverted` is true - const [x, y] = [event.deltaX, -event.deltaY] - .map(value => inverted ? -value : value); + const [x, y] = [event.deltaX, -event.deltaY].map(value => (inverted ? -value : value)); // Using the biggest delta, normalize to 1 or -1 (or 0 if no delta) const direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y); diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js index 7b58c9ff..e7e17041 100644 --- a/src/js/utils/elements.js +++ b/src/js/utils/elements.js @@ -294,3 +294,18 @@ export function trapFocus(element = null, toggle = false) { toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false); } + +// Set focus and tab focus class +export function setFocus(element = null, tabFocus = false) { + if (!is.element(element)) { + return; + } + + // Set regular focus + element.focus(); + + // If we want to mimic keyboard focus via tab + if (tabFocus) { + toggleClass(element, this.config.classNames.tabFocus); + } +} diff --git a/src/sass/lib/mixins.scss b/src/sass/lib/mixins.scss index 8b333f65..7d7d66b0 100644 --- a/src/sass/lib/mixins.scss +++ b/src/sass/lib/mixins.scss @@ -5,7 +5,7 @@ // Nicer focus styles // --------------------------------------- @mixin plyr-tab-focus($color: $plyr-tab-focus-default-color) { - box-shadow: 0 0 0 3px rgba($color, 0.35); + box-shadow: 0 0 0 5px rgba($color, 0.5); outline: 0; } |