Work on key bindings for menu

This commit is contained in:
Sam Potts 2018-06-28 23:44:07 +10:00
parent e59fe1aacf
commit 3bf1c59bd6
20 changed files with 310 additions and 80 deletions

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

11
demo/dist/demo.js vendored
View File

@ -1874,7 +1874,7 @@ typeof navigator === "object" && (function () {
// webpack (using a build step causes webpack #1617). Grunt verifies that
// this value matches package.json during build.
// See: https://github.com/getsentry/raven-js/issues/465
VERSION: '3.26.2',
VERSION: '3.26.3',
debug: false,
@ -2351,7 +2351,9 @@ typeof navigator === "object" && (function () {
return;
}
if (this._globalOptions.stacktrace || (options && options.stacktrace)) {
// Always attempt to get stacktrace if message is empty.
// It's the only way to provide any helpful information to the user.
if (this._globalOptions.stacktrace || options.stacktrace || data.message === '') {
// fingerprint on msg, not stack trace (legacy behavior, could be revisited)
data.fingerprint = data.fingerprint == null ? msg : data.fingerprint;
@ -3508,6 +3510,11 @@ typeof navigator === "object" && (function () {
options
);
var ex = data.exception.values[0];
if (ex.type == null && ex.value === '') {
ex.value = 'Unrecoverable error caught';
}
// Move mechanism from options to exception interface
// We do this, as requiring user to pass `{exception:{mechanism:{ ... }}}` would be
// too much

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

118
dist/plyr.js vendored
View File

@ -638,6 +638,24 @@ typeof navigator === "object" && (function (global, factory) {
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
}
// Set focus and tab focus class
function setFocus() {
var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var tabFocus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 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);
}
}
// ==========================================================================
var transitionEndEvent = function () {
@ -2351,12 +2369,22 @@ typeof navigator === "object" && (function (global, factory) {
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') {
var pane = Object.values(this.elements.settings.panels).find(function (pane) {
return !pane.hidden;
});
var firstItem = pane.querySelector('[role^="menuitem"]');
setFocus.call(this, firstItem, true);
}
} else {
popup.setAttribute('tabindex', -1);
}
@ -2447,9 +2475,7 @@ typeof navigator === "object" && (function (global, factory) {
// Focus the first item
var firstItem = target.querySelector('[role^="menuitem"]');
if (firstItem) {
firstItem.focus();
}
setFocus.call(this, firstItem, true);
},
@ -2589,6 +2615,9 @@ typeof navigator === "object" && (function (global, factory) {
role: 'menu'
});
home.appendChild(menu);
inner.appendChild(home);
// Build the menu items
this.config.settings.forEach(function (type) {
var menuItem = createElement('button', extend(getAttributesFromSelector(_this8.config.selectors.buttons.settings), {
@ -2599,6 +2628,26 @@ typeof navigator === "object" && (function (global, factory) {
hidden: ''
}));
// Handle space or -> to open menu
on(menuItem, 'keydown', function (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(_this8, type);
}, false);
// Show menu on click
on(menuItem, 'click', function () {
controls.showMenuPanel.call(_this8, type);
});
var flex = createElement('span', null, i18n.get(type, _this8.config));
var value = createElement('span', {
@ -2619,14 +2668,43 @@ typeof navigator === "object" && (function (global, factory) {
});
// Back button
var back = createElement('button', {
var backButton = createElement('button', {
type: 'button',
class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--back'
}, i18n.get(type, _this8.config));
back.addEventListener('click', function () {
});
// Visible label
backButton.appendChild(createElement('span', {
'aria-hidden': true
}, i18n.get(type, _this8.config)));
// Screen reader label
backButton.appendChild(createElement('span', {
class: _this8.config.classNames.hidden
}, i18n.get('menuBack', _this8.config)));
// Handle space or -> to open menu
on(backButton, 'keydown', function (event) {
// We only care about <-
if (event.which !== 37) {
return;
}
// Prevent seek
event.preventDefault();
event.stopPropagation();
// Show the respective menu
controls.showMenuPanel.call(_this8, 'home');
}, false);
// Go back
on(backButton, 'click', function () {
controls.showMenuPanel.call(_this8, 'home');
});
pane.appendChild(back);
// Add to pane
pane.appendChild(backButton);
// Menu
pane.appendChild(createElement('div', {
@ -2635,17 +2713,10 @@ typeof navigator === "object" && (function (global, factory) {
inner.appendChild(pane);
menuItem.addEventListener('click', function () {
controls.showMenuPanel.call(_this8, type);
});
_this8.elements.settings.buttons[type] = menuItem;
_this8.elements.settings.panels[type] = pane;
});
home.appendChild(menu);
inner.appendChild(home);
popup.appendChild(inner);
control.appendChild(popup);
container.appendChild(control);
@ -4678,11 +4749,28 @@ typeof navigator === "object" && (function (global, factory) {
// 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', function (event) {
controls.toggleMenu.call(_this5.player, event);
});
// Settings menu - keyboard toggle
this.bind(this.player.elements.buttons.settings, 'keydown', function (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(_this5.player, event);
}, null, false);
// Set range input alternative "value", which matches the tooltip time (#954)
this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this5.player.elements.progress.getBoundingClientRect();

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7737,12 +7737,20 @@ typeof navigator === "object" && (function (global, factory) {
button.setAttribute('aria-expanded', show);
}
// Show the actual popup
if (is$1.element(popup)) {
toggleHidden(popup, !show);
toggleClass(this.elements.container, this.config.classNames.menu.open, show);
if (show) {
popup.removeAttribute('tabindex');
// Focus the first item
var firstItem = popup.querySelector('[role^="menuitem"]');
console.warn(firstItem);
if (firstItem) {
firstItem.focus();
}
} else {
popup.setAttribute('tabindex', -1);
}
@ -8005,14 +8013,28 @@ typeof navigator === "object" && (function (global, factory) {
});
// Back button
var back = createElement('button', {
var backButton = createElement('button', {
type: 'button',
class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--back'
}, i18n.get(type, _this8.config));
back.addEventListener('click', function () {
});
// Visible label
backButton.appendChild(createElement('span', {
'aria-hidden': true
}, i18n.get(type, _this8.config)));
// Screen reader label
backButton.appendChild(createElement('span', {
class: _this8.config.classNames.hidden
}, i18n.get('menuBack', _this8.config)));
// Bind listener
backButton.addEventListener('click', function () {
controls.showMenuPanel.call(_this8, 'home');
});
pane.appendChild(back);
// Add to pane
pane.appendChild(backButton);
// Menu
pane.appendChild(createElement('div', {
@ -10064,11 +10086,28 @@ typeof navigator === "object" && (function (global, factory) {
// 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', function (event) {
controls.toggleMenu.call(_this5.player, event);
});
// Settings menu - keyboard toggle
this.bind(this.player.elements.buttons.settings, 'keydown', function (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(_this5.player, event);
}, null, false);
// Set range input alternative "value", which matches the tooltip time (#954)
this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this5.player.elements.progress.getBoundingClientRect();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,14 +9,14 @@
"style": "./dist/plyr.css",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.4",
"babel-eslint": "^8.2.5",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.7.0",
"del": "^3.0.0",
"eslint": "^5.0.0",
"eslint-config-airbnb-base": "^13.0.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-import": "^2.13.0",
"git-branch": "^2.0.1",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^5.0.0",
@ -40,16 +40,16 @@
"postcss-custom-properties": "^7.0.0",
"prettier-eslint": "^8.8.2",
"prettier-stylelint": "^0.4.2",
"rollup-plugin-babel": "^3.0.4",
"rollup-plugin-babel": "^3.0.5",
"rollup-plugin-commonjs": "^9.1.3",
"rollup-plugin-node-resolve": "^3.3.0",
"run-sequence": "^2.2.1",
"stylelint": "^9.3.0",
"stylelint-config-prettier": "^3.2.0",
"stylelint-config-prettier": "^3.3.0",
"stylelint-config-recommended": "^2.1.0",
"stylelint-config-sass-guidelines": "^5.0.0",
"stylelint-order": "^0.8.1",
"stylelint-scss": "^3.1.2",
"stylelint-scss": "^3.1.3",
"stylelint-selector-bem-pattern": "^2.0.0"
},
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
@ -74,7 +74,7 @@
"babel-polyfill": "^6.26.0",
"custom-event-polyfill": "^0.3.0",
"loadjs": "^3.5.4",
"raven-js": "^3.26.2",
"raven-js": "^3.26.3",
"url-polyfill": "^1.0.13"
}
}

101
src/js/controls.js vendored
View File

@ -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),
),
);
back.addEventListener('click', () => {
// Screen reader label
backButton.appendChild(
createElement(
'span',
{
class: this.config.classNames.hidden,
},
i18n.get('menuBack', this.config),
),
);
// 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);

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -464,7 +464,7 @@ babel-core@^6.26.3:
slash "^1.0.0"
source-map "^0.5.7"
babel-eslint@^8.2.4:
babel-eslint@^8.2.5:
version "8.2.5"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.5.tgz#dc2331c259d36782aa189da510c43dedd5adc7a3"
dependencies:
@ -1901,9 +1901,9 @@ eslint-module-utils@^2.2.0:
debug "^2.6.8"
pkg-dir "^1.0.0"
eslint-plugin-import@^2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.12.0.tgz#dad31781292d6664b25317fd049d2e2b2f02205d"
eslint-plugin-import@^2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.13.0.tgz#df24f241175e312d91662dc91ca84064caec14ed"
dependencies:
contains-path "^0.1.0"
debug "^2.6.8"
@ -5178,9 +5178,9 @@ randomatic@^1.1.3:
is-number "^3.0.0"
kind-of "^4.0.0"
raven-js@^3.26.2:
version "3.26.2"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.26.2.tgz#9153af2416e96ccf4e0b9cbc6c90c34dda0d7e88"
raven-js@^3.26.3:
version "3.26.3"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.26.3.tgz#0efb49969b5b11ab965f7b0d6da4ca102b763cb0"
rc@^1.0.1, rc@^1.1.6:
version "1.2.6"
@ -5629,9 +5629,9 @@ rimraf@2, rimraf@^2.2.8:
dependencies:
glob "^7.0.5"
rollup-plugin-babel@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-3.0.4.tgz#41b3e762fe64450dd61da3105a2cf7ad76be4edc"
rollup-plugin-babel@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-3.0.5.tgz#9769a7977098da1dce5b5888fe38dfd8666bf08d"
dependencies:
rollup-pluginutils "^1.5.0"
@ -6103,9 +6103,9 @@ style-search@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
stylelint-config-prettier@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-3.2.0.tgz#af32b7845adeeddbf0a0bd642ace4ca1e68958e2"
stylelint-config-prettier@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-3.3.0.tgz#cc22a4b5310c1919cee77131d6e220c60a62a480"
dependencies:
stylelint "^9.1.1"
@ -6138,9 +6138,9 @@ stylelint-scss@^2.0.0:
postcss-selector-parser "^3.1.1"
postcss-value-parser "^3.3.0"
stylelint-scss@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.1.2.tgz#3257c0600d197fe7642f3698944b47c91567f379"
stylelint-scss@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.1.3.tgz#28f881ae298c3f5db667b10b6cf94a1a219001d6"
dependencies:
lodash "^4.17.10"
postcss-media-query-parser "^0.2.3"