aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js/controls.js101
-rw-r--r--src/js/listeners.js28
-rw-r--r--src/js/utils/elements.js15
-rw-r--r--src/sass/lib/mixins.scss2
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;
}