aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSam Potts <sam@potts.es>2018-06-18 23:13:40 +1000
committerSam Potts <sam@potts.es>2018-06-18 23:13:40 +1000
commit7b9ef7d757811cb7c855856ef091061d315b13c4 (patch)
tree5665e788abac87953a9f7ddaf29ed01c6a4f7adf /src
parentd64ed4ba5a53547e255eda85d8ab7801298921fc (diff)
downloadplyr-7b9ef7d757811cb7c855856ef091061d315b13c4.tar.lz
plyr-7b9ef7d757811cb7c855856ef091061d315b13c4.tar.xz
plyr-7b9ef7d757811cb7c855856ef091061d315b13c4.zip
More work on menus
Diffstat (limited to 'src')
-rw-r--r--src/js/config/defaults.js3
-rw-r--r--src/js/controls.js148
-rw-r--r--src/js/listeners.js2
-rw-r--r--src/js/plyr.js2
-rw-r--r--src/sass/components/menus.scss53
5 files changed, 99 insertions, 109 deletions
diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js
index 1e90a4f0..a898755d 100644
--- a/src/js/config/defaults.js
+++ b/src/js/config/defaults.js
@@ -354,6 +354,9 @@ const defaults = {
isTouch: 'plyr--is-touch',
uiSupported: 'plyr--full-ui',
noTransition: 'plyr--no-transition',
+ display: {
+ time: 'plyr__time',
+ },
menu: {
value: 'plyr__menu__value',
badge: 'plyr__badge',
diff --git a/src/js/controls.js b/src/js/controls.js
index 57e5bd3f..607511cb 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -9,19 +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,
- removeElement,
- setAttributes,
- toggleClass,
- toggleHidden,
- matches,
-} from './utils/elements';
+import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, matches, removeElement, setAttributes, toggleClass, toggleHidden } from './utils/elements';
import { off, on } from './utils/events';
import is from './utils/is';
import loadSprite from './utils/loadSprite';
@@ -360,7 +348,7 @@ const controls = {
const container = createElement(
'div',
extend(attributes, {
- class: `plyr__time ${attributes.class}`,
+ class: `${this.config.classNames.display.time} ${attributes.class ? attributes.class : ''}`.trim(),
'aria-label': i18n.get(type, this.config),
}),
'00:00',
@@ -374,20 +362,43 @@ const controls = {
// Create a settings menu item
createMenuItem({ value, list, type, title, badge = null, checked = false }) {
+ const attributes = getAttributesFromSelector(this.config.selectors.inputs[type]);
+
const item = createElement(
'button',
- extend(getAttributesFromSelector(this.config.selectors.inputs[type]), {
+ extend(attributes, {
type: 'button',
+ role: 'menuitemradio',
+ class: `${this.config.classNames.control} ${attributes.class ? attributes.class : ''}`.trim(),
value,
'aria-checked': checked,
- }),
- title,
+ })
);
+ // We have to set as HTML incase of special characters
+ item.innerHTML = title;
+
if (is.element(badge)) {
item.appendChild(badge);
}
+ Object.defineProperty(item, 'checked', {
+ enumerable: true,
+ get() {
+ return item.getAttribute('aria-checked') === 'true';
+ },
+ set(checked) {
+ // Ensure exclusivity
+ if (checked) {
+ Array.from(item.parentNode.children)
+ .filter(node => matches(node, '[role="menuitemradio"]'))
+ .forEach(node => node.setAttribute('aria-checked', 'false'));
+ }
+
+ item.setAttribute('aria-checked', checked ? 'true' : 'false');
+ },
+ });
+
list.appendChild(item);
},
@@ -649,12 +660,12 @@ const controls = {
// Set the quality menu
setQualityMenu(options) {
// Menu required
- if (!is.element(this.elements.settings.menus.quality)) {
+ if (!is.element(this.elements.settings.panels.quality)) {
return;
}
const type = 'quality';
- const list = this.elements.settings.menus.quality.querySelector('[role="menu"]');
+ const list = this.elements.settings.panels.quality.querySelector('[role="menu"]');
// Set options if passed and filter based on uniqueness and config
if (is.array(options)) {
@@ -735,7 +746,7 @@ const controls = {
// Update the selected setting
updateSetting(setting, container, input) {
- const pane = this.elements.settings.menus[setting];
+ const pane = this.elements.settings.panels[setting];
let value = null;
let list = container;
@@ -777,26 +788,26 @@ const controls = {
label.innerHTML = controls.getLabel.call(this, setting, value);
// Find the radio option and check it
- const target = list && list.querySelector(`button[value="${value}"]`);
+ const target = list && list.querySelector(`[value="${value}"]`);
if (is.element(target)) {
- target.setAttribute('aria-checked', true);
+ target.checked = true;
}
},
// Set the looping options
/* setLoopMenu() {
// Menu required
- if (!is.element(this.elements.settings.menus.loop)) {
+ if (!is.element(this.elements.settings.panels.loop)) {
return;
}
const options = ['start', 'end', 'all', 'reset'];
- const list = this.elements.settings.menus.loop.querySelector('[role="menu"]');
+ const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
// Show the pane and tab
toggleHidden(this.elements.settings.buttons.loop, false);
- toggleHidden(this.elements.settings.menus.loop, false);
+ toggleHidden(this.elements.settings.panels.loop, false);
// Toggle the pane and tab
const toggle = !is.empty(this.loop.options);
@@ -835,7 +846,7 @@ const controls = {
setCaptionsMenu() {
// TODO: Captions or language? Currently it's mixed
const type = 'captions';
- const list = this.elements.settings.menus.captions.querySelector('[role="menu"]');
+ const list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
const tracks = captions.getTracks.call(this);
// Toggle the pane and tab
@@ -885,7 +896,7 @@ const controls = {
}
// Menu required
- if (!is.element(this.elements.settings.menus.speed)) {
+ if (!is.element(this.elements.settings.panels.speed)) {
return;
}
@@ -914,7 +925,7 @@ const controls = {
}
// Get the list to populate
- const list = this.elements.settings.menus.speed.querySelector('[role="menu"]');
+ const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Empty the menu
emptyElement(list);
@@ -986,19 +997,13 @@ const controls = {
}
},
- // Get the natural size of a tab
- getTabSize(tab) {
+ // Get the natural size of a menu panel
+ getMenuSize(tab) {
const clone = tab.cloneNode(true);
clone.style.position = 'absolute';
clone.style.opacity = 0;
clone.removeAttribute('hidden');
- // Prevent input's being unchecked due to the name being identical
- /* Array.from(clone.querySelectorAll('input[name]')).forEach(input => {
- const name = input.getAttribute('name');
- input.setAttribute('name', `${name}-clone`);
- }); */
-
// Append to parent so we get the "real" size
tab.parentNode.appendChild(clone);
@@ -1015,34 +1020,18 @@ const controls = {
};
},
- // Toggle Menu
- showMenu(type = '') {
- const { menu } = this.elements.settings;
- const pane = document.getElementById(`plyr-settings-${this.id}-${type}`);
-
- console.warn(`plyr-settings-${this.id}-${type}`);
+ // Show a panel in the menu
+ showMenuPanel(type = '') {
+ const target = document.getElementById(`plyr-settings-${this.id}-${type}`);
// Nothing to show, bail
- if (!is.element(pane)) {
- console.warn('No pane found');
+ if (!is.element(target)) {
return;
}
- // Are we targeting a tab? If not, bail
- /* const isTab = pane.getAttribute('role') === 'tabpanel';
- if (!isTab) {
- return;
- } */
-
- // Hide all other tabs
- // Get other tabs
- const current = menu.querySelector(`[id^=plyr-settings-${this.id}]:not([hidden])`);
- const container = current.parentNode;
-
- // Set other toggles to be expanded false
- /* Array.from(menu.querySelectorAll(`[aria-controls="${current.getAttribute('id')}"]`)).forEach(toggle => {
- toggle.setAttribute('aria-expanded', false);
- }); */
+ // Hide all other panels
+ const container = target.parentNode;
+ const current = Array.from(container.children).find(node => !node.hidden);
// If we can do fancy animations, we'll animate the height/width
if (support.transitions && !support.reducedMotion) {
@@ -1051,12 +1040,12 @@ const controls = {
container.style.height = `${current.scrollHeight}px`;
// Get potential sizes
- const size = controls.getTabSize.call(this, pane);
+ const size = controls.getMenuSize.call(this, target);
// Restore auto height/width
- const restore = e => {
+ const restore = event => {
// We're only bothered about height and width on the container
- if (e.target !== container || !['width', 'height'].includes(e.propertyName)) {
+ if (event.target !== container || !['width', 'height'].includes(event.propertyName)) {
return;
}
@@ -1081,16 +1070,10 @@ const controls = {
// current.setAttribute('tabindex', -1);
// Set attributes on target
- toggleHidden(pane, false);
-
- /* const tabs = getElements.call(this, `[aria-controls="${target}"]`);
- Array.from(tabs).forEach(tab => {
- tab.setAttribute('aria-expanded', true);
- });
- pane.removeAttribute('tabindex'); */
+ toggleHidden(target, false);
// Focus the first item
- pane.querySelectorAll('button:not(:disabled), input:not(:disabled), [tabindex]')[0].focus();
+ target.querySelectorAll('[role^="menuitem"]')[0].focus();
},
// Build the default HTML
@@ -1248,12 +1231,12 @@ const controls = {
extend(getAttributesFromSelector(this.config.selectors.buttons.settings), {
type: 'button',
class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,
- 'role': 'menuitem',
+ role: 'menuitem',
'aria-haspopup': true,
}),
);
- const flex = createElement('span', null, i18n.get(type, this.config))
+ const flex = createElement('span', null, i18n.get(type, this.config));
const value = createElement('span', {
class: this.config.classNames.menu.value,
@@ -1266,7 +1249,6 @@ const controls = {
menuItem.appendChild(flex);
menu.appendChild(menuItem);
-
// Build the panes
const pane = createElement('div', {
id: `plyr-settings-${data.id}-${type}`,
@@ -1274,28 +1256,34 @@ const controls = {
});
// Back button
- pane.appendChild(createElement(
+ const back = createElement(
'button',
{
type: 'button',
class: `${this.config.classNames.control} ${this.config.classNames.control}--back`,
},
i18n.get(type, this.config),
- ));
+ );
+ back.addEventListener('click', () => {
+ controls.showMenuPanel.call(this, 'home');
+ });
+ pane.appendChild(back);
// Menu
- pane.appendChild(createElement('div', {
- role: 'menu',
- }));
+ pane.appendChild(
+ createElement('div', {
+ role: 'menu',
+ }),
+ );
inner.appendChild(pane);
menuItem.addEventListener('click', () => {
- controls.showMenu.call(this, type);
+ controls.showMenuPanel.call(this, type);
});
this.elements.settings.buttons[type] = menuItem;
- this.elements.settings.menus[type] = pane;
+ this.elements.settings.panels[type] = pane;
});
home.appendChild(menu);
diff --git a/src/js/listeners.js b/src/js/listeners.js
index 68a38af2..cc9d3889 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -491,7 +491,7 @@ class Listeners {
// Go back to home tab on click
const showHomeTab = () => {
- controls.showMenu.call(this.player, 'home');
+ controls.showMenuPanel.call(this.player, 'home');
};
// Settings menu items - use event delegation as items are added/removed
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 249c296f..2b65cc1d 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -83,7 +83,7 @@ class Plyr {
settings: {
popup: null,
menu: null,
- menus: {},
+ panels: {},
buttons: {},
},
};
diff --git a/src/sass/components/menus.scss b/src/sass/components/menus.scss
index b8dbf5e7..35bfdeaa 100644
--- a/src/sass/components/menus.scss
+++ b/src/sass/components/menus.scss
@@ -139,50 +139,49 @@
}
}
- label.plyr__control {
+ .plyr__control[role='menuitemradio'] {
padding-left: $plyr-control-padding;
- input[type='radio'] + span {
- background: rgba(#000, 0.1);
+ &::before,
+ &::after {
border-radius: 100%;
+ }
+
+ &::before {
+ background: rgba(#000, 0.1);
+ content: '';
display: block;
flex-shrink: 0;
height: 16px;
margin-right: $plyr-control-spacing;
- position: relative;
transition: all 0.3s ease;
width: 16px;
-
- &::after {
- background: #fff;
- border-radius: 100%;
- content: '';
- height: 6px;
- left: 5px;
- opacity: 0;
- position: absolute;
- top: 5px;
- transform: scale(0);
- transition: transform 0.3s ease, opacity 0.3s ease;
- width: 6px;
- }
}
- input[type='radio']:checked + span {
- background: $plyr-color-main;
+ &::after {
+ background: #fff;
+ border: 0;
+ height: 6px;
+ left: 12px;
+ opacity: 0;
+ top: 50%;
+ transform: translateY(-50%) scale(0);
+ transition: transform 0.3s ease, opacity 0.3s ease;
+ width: 6px;
+ }
+ &[aria-checked='true'] {
+ &::before {
+ background: $plyr-color-main;
+ }
&::after {
opacity: 1;
- transform: scale(1);
+ transform: translateY(-50%) scale(1);
}
}
- input[type='radio']:focus + span {
- @include plyr-tab-focus();
- }
-
- &.plyr__tab-focus input[type='radio'] + span,
- &:hover input[type='radio'] + span {
+ &.plyr__tab-focus::before,
+ &:hover::before {
background: rgba(#000, 0.1);
}
}