diff options
author | Sam Potts <sam@potts.es> | 2018-06-21 09:01:16 +1000 |
---|---|---|
committer | Sam Potts <sam@potts.es> | 2018-06-21 09:01:16 +1000 |
commit | 1f1d74ba50f2ac4113948a3b92a0b55f05b735c2 (patch) | |
tree | f62c9f4544562ef345f9a924e9052a0265e936bb /src | |
parent | bb546fe43fc6537a4dc0a350a7aa4260a3f97b1d (diff) | |
download | plyr-1f1d74ba50f2ac4113948a3b92a0b55f05b735c2.tar.lz plyr-1f1d74ba50f2ac4113948a3b92a0b55f05b735c2.tar.xz plyr-1f1d74ba50f2ac4113948a3b92a0b55f05b735c2.zip |
Work on menus
Diffstat (limited to 'src')
-rw-r--r-- | src/js/controls.js | 231 | ||||
-rw-r--r-- | src/js/listeners.js | 145 | ||||
-rw-r--r-- | src/sass/components/menus.scss | 2 | ||||
-rw-r--r-- | src/sass/utils/hidden.scss | 4 |
4 files changed, 188 insertions, 194 deletions
diff --git a/src/js/controls.js b/src/js/controls.js index b3435236..710ee93b 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -370,18 +370,22 @@ const controls = { type: 'button', role: 'menuitemradio', class: `${this.config.classNames.control} ${attributes.class ? attributes.class : ''}`.trim(), - value, 'aria-checked': checked, - }) + value, + }), ); + const flex = createElement('span'); + // We have to set as HTML incase of special characters - item.innerHTML = title; + flex.innerHTML = title; if (is.element(badge)) { - item.appendChild(badge); + flex.appendChild(badge); } + item.appendChild(flex); + Object.defineProperty(item, 'checked', { enumerable: true, get() { @@ -399,6 +403,34 @@ const controls = { }, }); + this.listeners.bind( + item, + 'click', + () => { + item.checked = true; + + switch (type) { + case 'language': + this.currentTrack = Number(value); + break; + + case 'quality': + this.quality = value; + break; + + case 'speed': + this.speed = parseFloat(value); + break; + + default: + break; + } + + controls.showMenuPanel.call(this, 'home'); + }, + type, + ); + list.appendChild(item); }, @@ -657,11 +689,88 @@ const controls = { toggleHidden(this.elements.settings.buttons[setting], !toggle); }, + // Update the selected setting + updateSetting(setting, container, input) { + const pane = this.elements.settings.panels[setting]; + let value = null; + let list = container; + + if (setting === 'captions') { + value = this.currentTrack; + } else { + value = !is.empty(input) ? input : this[setting]; + + // Get default + if (is.empty(value)) { + value = this.config[setting].default; + } + + // Unsupported value + if (!is.empty(this.options[setting]) && !this.options[setting].includes(value)) { + this.debug.warn(`Unsupported value of '${value}' for ${setting}`); + return; + } + + // Disabled value + if (!this.config[setting].options.includes(value)) { + this.debug.warn(`Disabled value of '${value}' for ${setting}`); + return; + } + } + + // Get the list if we need to + if (!is.element(list)) { + list = pane && pane.querySelector('[role="menu"]'); + } + + // If there's no list it means it's not been rendered... + if (!is.element(list)) { + return; + } + + // Update the label + const label = this.elements.settings.buttons[setting].querySelector(`.${this.config.classNames.menu.value}`); + label.innerHTML = controls.getLabel.call(this, setting, value); + + // Find the radio option and check it + const target = list && list.querySelector(`[value="${value}"]`); + + if (is.element(target)) { + target.checked = true; + } + }, + + // Translate a value into a nice label + getLabel(setting, value) { + switch (setting) { + case 'speed': + return value === 1 ? i18n.get('normal', this.config) : `${value}×`; + + case 'quality': + if (is.number(value)) { + const label = i18n.get(`qualityLabel.${value}`, this.config); + + if (!label.length) { + return `${value}p`; + } + + return label; + } + + return toTitleCase(value); + + case 'captions': + return captions.getLabel.call(this); + + default: + return null; + } + }, + // Set the quality menu setQualityMenu(options) { // Menu required if (!is.element(this.elements.settings.panels.quality)) { - console.warn('Not an element'); return; } @@ -674,10 +783,12 @@ const controls = { } // Toggle the pane and tab - console.warn(this.options.quality); const toggle = !is.empty(this.options.quality) && this.options.quality.length > 1; controls.toggleMenuButton.call(this, type, toggle); + // Empty the menu + emptyElement(list); + // Check if we need to toggle the parent controls.checkMenu.call(this); @@ -686,9 +797,6 @@ const controls = { return; } - // Empty the menu - emptyElement(list); - // Get the badge HTML for HD, 4K etc const getBadge = quality => { const label = i18n.get(`qualityBadge.${quality}`, this.config); @@ -719,84 +827,6 @@ const controls = { controls.updateSetting.call(this, type, list); }, - // Translate a value into a nice label - getLabel(setting, value) { - switch (setting) { - case 'speed': - return value === 1 ? i18n.get('normal', this.config) : `${value}×`; - - case 'quality': - if (is.number(value)) { - const label = i18n.get(`qualityLabel.${value}`, this.config); - - if (!label.length) { - return `${value}p`; - } - - return label; - } - - return toTitleCase(value); - - case 'captions': - return captions.getLabel.call(this); - - default: - return null; - } - }, - - // Update the selected setting - updateSetting(setting, container, input) { - const pane = this.elements.settings.panels[setting]; - let value = null; - let list = container; - - if (setting === 'captions') { - value = this.currentTrack; - } else { - value = !is.empty(input) ? input : this[setting]; - - // Get default - if (is.empty(value)) { - value = this.config[setting].default; - } - - // Unsupported value - if (!is.empty(this.options[setting]) && !this.options[setting].includes(value)) { - this.debug.warn(`Unsupported value of '${value}' for ${setting}`); - return; - } - - // Disabled value - if (!this.config[setting].options.includes(value)) { - this.debug.warn(`Disabled value of '${value}' for ${setting}`); - return; - } - } - - // Get the list if we need to - if (!is.element(list)) { - list = pane && pane.querySelector('[role="menu"]'); - } - - // If there's no list it means it's not been rendered... - if (!is.element(list)) { - return; - } - - // Update the label - const label = this.elements.settings.buttons[setting].querySelector(`.${this.config.classNames.menu.value}`); - label.innerHTML = controls.getLabel.call(this, setting, value); - - // Find the radio option and check it - const target = list && list.querySelector(`[value="${value}"]`); - - if (is.element(target)) { - target.checked = true; - } - }, - // Set the looping options /* setLoopMenu() { // Menu required @@ -846,13 +876,19 @@ const controls = { // Set a list of available captions languages setCaptionsMenu() { + // Menu required + if (!is.element(this.elements.settings.panels.captions)) { + return; + } + // TODO: Captions or language? Currently it's mixed const type = 'captions'; const list = this.elements.settings.panels.captions.querySelector('[role="menu"]'); const tracks = captions.getTracks.call(this); + const toggle = Boolean(tracks.length); // Toggle the pane and tab - controls.toggleMenuButton.call(this, type, tracks.length); + controls.toggleMenuButton.call(this, type, toggle); // Empty the menu emptyElement(list); @@ -861,7 +897,7 @@ const controls = { controls.checkMenu.call(this); // If there's no captions, bail - if (!tracks.length) { + if (!toggle) { return; } @@ -892,17 +928,13 @@ const controls = { // Set a list of available captions languages setSpeedMenu(options) { - // Do nothing if not selected - if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) { - return; - } - // Menu required if (!is.element(this.elements.settings.panels.speed)) { return; } const type = 'speed'; + const list = this.elements.settings.panels.speed.querySelector('[role="menu"]'); // Set the speed options if (is.array(options)) { @@ -918,6 +950,9 @@ const controls = { const toggle = !is.empty(this.options.speed) && this.options.speed.length > 1; controls.toggleMenuButton.call(this, type, toggle); + // Empty the menu + emptyElement(list); + // Check if we need to toggle the parent controls.checkMenu.call(this); @@ -926,12 +961,6 @@ const controls = { return; } - // Get the list to populate - const list = this.elements.settings.panels.speed.querySelector('[role="menu"]'); - - // Empty the menu - emptyElement(list); - // Create items this.options.speed.forEach(speed => { controls.createMenuItem.call(this, { @@ -1069,7 +1098,6 @@ const controls = { // Set attributes on current tab toggleHidden(current, true); - // current.setAttribute('tabindex', -1); // Set attributes on target toggleHidden(target, false); @@ -1238,6 +1266,7 @@ const controls = { class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`, role: 'menuitem', 'aria-haspopup': true, + hidden: '', }), ); diff --git a/src/js/listeners.js b/src/js/listeners.js index cc9d3889..d6786111 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -243,7 +243,8 @@ class Listeners { // Clear timer clearTimeout(this.player.timers.controls); - // Timer to prevent flicker when seeking + + // Set new timer to prevent flicker when seeking this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay); }, ); @@ -394,58 +395,58 @@ class Listeners { }); } - // Listen for control events - controls() { - // IE doesn't support input event, so we fallback to change - const inputEvent = browser.isIE ? 'change' : 'input'; + // Run default and custom handlers + proxy(event, defaultHandler, customHandlerKey) { + const customHandler = this.player.config.listeners[customHandlerKey]; + const hasCustomHandler = is.function(customHandler); + let returned = true; - // Run default and custom handlers - const proxy = (event, defaultHandler, customHandlerKey) => { - const customHandler = this.player.config.listeners[customHandlerKey]; - const hasCustomHandler = is.function(customHandler); - let returned = true; + // Execute custom handler + if (hasCustomHandler) { + returned = customHandler.call(this.player, event); + } - // Execute custom handler - if (hasCustomHandler) { - returned = customHandler.call(this.player, event); - } + // Only call default handler if not prevented in custom handler + if (returned && is.function(defaultHandler)) { + defaultHandler.call(this.player, event); + } + } - // Only call default handler if not prevented in custom handler - if (returned && is.function(defaultHandler)) { - defaultHandler.call(this.player, event); - } - }; + // Trigger custom and default handlers + bind(element, type, defaultHandler, customHandlerKey, passive = true) { + const customHandler = this.player.config.listeners[customHandlerKey]; + const hasCustomHandler = is.function(customHandler); - // Trigger custom and default handlers - const bind = (element, type, defaultHandler, customHandlerKey, passive = true) => { - const customHandler = this.player.config.listeners[customHandlerKey]; - const hasCustomHandler = is.function(customHandler); + on.call( + this.player, + element, + type, + event => this.proxy(event, defaultHandler, customHandlerKey), + passive && !hasCustomHandler, + ); + } - on.call( - this.player, - element, - type, - event => proxy(event, defaultHandler, customHandlerKey), - passive && !hasCustomHandler, - ); - }; + // Listen for control events + controls() { + // IE doesn't support input event, so we fallback to change + const inputEvent = browser.isIE ? 'change' : 'input'; // Play/pause toggle Array.from(this.player.elements.buttons.play).forEach(button => { - bind(button, 'click', this.player.togglePlay, 'play'); + this.bind(button, 'click', this.player.togglePlay, 'play'); }); // Pause - bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart'); + this.bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart'); // Rewind - bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind'); + this.bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind'); // Rewind - bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward'); + this.bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward'); // Mute toggle - bind( + this.bind( this.player.elements.buttons.mute, 'click', () => { @@ -455,10 +456,10 @@ class Listeners { ); // Captions toggle - bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions()); + this.bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions()); // Fullscreen toggle - bind( + this.bind( this.player.elements.buttons.fullscreen, 'click', () => { @@ -468,7 +469,7 @@ class Listeners { ); // Picture-in-Picture - bind( + this.bind( this.player.elements.buttons.pip, 'click', () => { @@ -478,62 +479,22 @@ class Listeners { ); // Airplay - bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay'); + this.bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay'); // Settings menu - bind(this.player.elements.buttons.settings, 'click', event => { + this.bind(this.player.elements.buttons.settings, 'click', event => { controls.toggleMenu.call(this.player, event); }); - // Settings menu - bind(this.player.elements.settings.popup, 'click', event => { - event.stopPropagation(); - - // Go back to home tab on click - const showHomeTab = () => { - controls.showMenuPanel.call(this.player, 'home'); - }; - - // Settings menu items - use event delegation as items are added/removed - if (matches(event.target, this.player.config.selectors.inputs.language)) { - proxy( - event, - () => { - this.player.currentTrack = Number(event.target.value); - showHomeTab(); - }, - 'language', - ); - } else if (matches(event.target, this.player.config.selectors.inputs.quality)) { - proxy( - event, - () => { - this.player.quality = event.target.value; - showHomeTab(); - }, - 'quality', - ); - } else if (matches(event.target, this.player.config.selectors.inputs.speed)) { - proxy( - event, - () => { - this.player.speed = parseFloat(event.target.value); - showHomeTab(); - }, - 'speed', - ); - } - }); - // Set range input alternative "value", which matches the tooltip time (#954) - bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => { + this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => { const clientRect = this.player.elements.progress.getBoundingClientRect(); const percent = 100 / clientRect.width * (event.pageX - clientRect.left); event.currentTarget.setAttribute('seek-value', percent); }); // Pause while seeking - bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => { + this.bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => { const seek = event.currentTarget; const code = event.keyCode ? event.keyCode : event.which; @@ -559,7 +520,7 @@ class Listeners { }); // Seek - bind( + this.bind( this.player.elements.inputs.seek, inputEvent, event => { @@ -582,7 +543,7 @@ class Listeners { // Current time invert // Only if one time element is used for both currentTime and duration if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) { - bind(this.player.elements.display.currentTime, 'click', () => { + this.bind(this.player.elements.display.currentTime, 'click', () => { // Do nothing if we're at the start if (this.player.currentTime === 0) { return; @@ -595,7 +556,7 @@ class Listeners { } // Volume - bind( + this.bind( this.player.elements.inputs.volume, inputEvent, event => { @@ -607,27 +568,27 @@ class Listeners { // Polyfill for lower fill in <input type="range"> for webkit if (browser.isWebkit) { Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(element => { - bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target)); + this.bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target)); }); } // Seek tooltip - bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => + this.bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event), ); // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) - bind(this.player.elements.controls, 'mouseenter mouseleave', event => { + this.bind(this.player.elements.controls, 'mouseenter mouseleave', event => { this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter'; }); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) - bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => { + this.bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => { this.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); }); // Focus in/out on controls - bind(this.player.elements.controls, 'focusin focusout', event => { + this.bind(this.player.elements.controls, 'focusin focusout', event => { const { config, elements, timers } = this.player; // Skip transition to prevent focus from scrolling the parent element @@ -654,7 +615,7 @@ class Listeners { }); // Mouse wheel for volume - bind( + this.bind( this.player.elements.inputs.volume, 'wheel', event => { diff --git a/src/sass/components/menus.scss b/src/sass/components/menus.scss index 35bfdeaa..be354e46 100644 --- a/src/sass/components/menus.scss +++ b/src/sass/components/menus.scss @@ -191,7 +191,7 @@ align-items: center; display: flex; margin-left: auto; - margin-right: -$plyr-control-padding; + margin-right: -($plyr-control-padding - 2); overflow: hidden; padding-left: ceil($plyr-control-padding * 3.5); pointer-events: none; diff --git a/src/sass/utils/hidden.scss b/src/sass/utils/hidden.scss index e4fa0aec..a42c3be8 100644 --- a/src/sass/utils/hidden.scss +++ b/src/sass/utils/hidden.scss @@ -22,3 +22,7 @@ width: 1px; } } + +.plyr [hidden] { + display: none !important; +} |