More work on menus
This commit is contained in:
148
src/js/controls.js
vendored
148
src/js/controls.js
vendored
@ -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);
|
||||
|
Reference in New Issue
Block a user