diff --git a/src/css/components/_volume.scss b/src/css/components/_volume.scss index cf3ecca923..5b7fdab0b7 100644 --- a/src/css/components/_volume.scss +++ b/src/css/components/_volume.scss @@ -32,35 +32,33 @@ width: 1px; height: 1px; margin-left: -1px; - } .video-js .vjs-volume-panel { - &:hover .vjs-volume-control, + &.vjs-hover .vjs-volume-control, &:active .vjs-volume-control, &:focus .vjs-volume-control, - & .vjs-volume-control:hover , - & .vjs-volume-control:active , - & .vjs-mute-control:hover ~ .vjs-volume-control, + & .vjs-volume-control:active, + &.vjs-hover .vjs-mute-control ~ .vjs-volume-control, & .vjs-volume-control.vjs-slider-active { &.vjs-volume-horizontal { width: 5em; height: 3em; } - visibility: visible; opacity: 1; position: relative; &.vjs-volume-vertical { left: -3.5em; + @include transition(left 0s); } $transition-property: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; @include transition($transition-property); } &.vjs-volume-panel-horizontal { - &:hover, + &.vjs-hover, &:active, &.vjs-slider-active { width: 9em; @@ -83,6 +81,7 @@ $transition-property: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; @include transition($transition-property) } + .video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal { $transition-property: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; @include transition($transition-property) diff --git a/src/css/components/menu/_menu-popup.scss b/src/css/components/menu/_menu-popup.scss index c09cb14151..dbc0435a48 100644 --- a/src/css/components/menu/_menu-popup.scss +++ b/src/css/components/menu/_menu-popup.scss @@ -38,7 +38,7 @@ max-height: 25em; } -.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu, +.vjs-workinghover .vjs-menu-button-popup.vjs-hover .vjs-menu, .vjs-menu-button-popup .vjs-menu.vjs-lock-showing { display: block; } diff --git a/src/js/clickable-component.js b/src/js/clickable-component.js index fd770f28e6..0259538d14 100644 --- a/src/js/clickable-component.js +++ b/src/js/clickable-component.js @@ -168,6 +168,8 @@ class ClickableComponent extends Component { if (typeof this.tabIndex_ !== 'undefined') { this.el_.removeAttribute('tabIndex'); } + this.off('mouseover', this.handleMouseOver); + this.off('mouseout', this.handleMouseOut); this.off(['tap', 'click'], this.handleClick); this.off('keydown', this.handleKeyDown); } diff --git a/src/js/control-bar/volume-panel.js b/src/js/control-bar/volume-panel.js index 7f58ab7f42..4d3885aaa2 100644 --- a/src/js/control-bar/volume-panel.js +++ b/src/js/control-bar/volume-panel.js @@ -3,6 +3,10 @@ */ import Component from '../component.js'; import {isPlain} from '../utils/obj'; +import * as Events from '../utils/events.js'; +import * as Fn from '../utils/fn.js'; +import keycode from 'keycode'; +import document from 'global/document'; // Required children import './volume-control/volume-control.js'; @@ -42,6 +46,11 @@ class VolumePanel extends Component { super(player, options); this.on(player, ['loadstart'], this.volumePanelState_); + this.on(this.muteToggle, 'keyup', this.handleKeyPress); + this.on(this.volumeControl, 'keyup', this.handleVolumeControlKeyUp); + this.on('keydown', this.handleKeyPress); + this.on('mouseover', this.handleMouseOver); + this.on('mouseout', this.handleMouseOut); // while the slider is active (the mouse has been pressed down and // is dragging) we do not want to hide the VolumeBar @@ -109,6 +118,73 @@ class VolumePanel extends Component { }); } + /** + * Dispose of the `volume-panel` and all child components. + */ + dispose() { + this.handleMouseOut(); + super.dispose(); + } + + /** + * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes + * the volume panel and sets focus on `MuteToggle`. + * + * @param {EventTarget~Event} event + * The `keyup` event that caused this function to be called. + * + * @listens keyup + */ + handleVolumeControlKeyUp(event) { + if (keycode.isEventKey(event, 'Esc')) { + this.muteToggle.focus(); + } + } + + /** + * This gets called when a `VolumePanel` gains hover via a `mouseover` event. + * Turns on listening for `mouseover` event. When they happen it + * calls `this.handleMouseOver`. + * + * @param {EventTarget~Event} event + * The `mouseover` event that caused this function to be called. + * + * @listens mouseover + */ + handleMouseOver(event) { + this.addClass('vjs-hover'); + Events.on(document, 'keyup', Fn.bind(this, this.handleKeyPress)); + } + + /** + * This gets called when a `VolumePanel` gains hover via a `mouseout` event. + * Turns on listening for `mouseout` event. When they happen it + * calls `this.handleMouseOut`. + * + * @param {EventTarget~Event} event + * The `mouseout` event that caused this function to be called. + * + * @listens mouseout + */ + handleMouseOut(event) { + this.removeClass('vjs-hover'); + Events.off(document, 'keyup', Fn.bind(this, this.handleKeyPress)); + } + + /** + * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`, + * looking for ESC, which hides the `VolumeControl`. + * + * @param {EventTarget~Event} event + * The keypress that triggered this event. + * + * @listens keydown | keyup + */ + handleKeyPress(event) { + if (keycode.isEventKey(event, 'Esc')) { + this.handleMouseOut(); + } + } } /** diff --git a/src/js/menu/menu-button.js b/src/js/menu/menu-button.js index c3ddc436eb..f4791f1ed9 100644 --- a/src/js/menu/menu-button.js +++ b/src/js/menu/menu-button.js @@ -5,8 +5,11 @@ import Button from '../button.js'; import Component from '../component.js'; import Menu from './menu.js'; import * as Dom from '../utils/dom.js'; +import * as Fn from '../utils/fn.js'; +import * as Events from '../utils/events.js'; import {toTitleCase} from '../utils/string-cases.js'; import { IS_IOS } from '../utils/browser.js'; +import document from 'global/document'; import keycode from 'keycode'; /** @@ -49,9 +52,11 @@ class MenuButton extends Component { this.on(this.menuButton_, 'click', this.handleClick); this.on(this.menuButton_, 'keydown', this.handleKeyDown); this.on(this.menuButton_, 'mouseenter', () => { + this.addClass('vjs-hover'); this.menu.show(); + Events.on(document, 'keyup', Fn.bind(this, this.handleMenuKeyUp)); }); - + this.on('mouseleave', this.handleMouseLeave); this.on('keydown', this.handleSubmenuKeyDown); } @@ -210,6 +215,14 @@ class MenuButton extends Component { return this.menuButton_.controlText(text, el); } + /** + * Dispose of the `menu-button` and all child components. + */ + dispose() { + this.handleMouseLeave(); + super.dispose(); + } + /** * Handle a click on a `MenuButton`. * See {@link ClickableComponent#handleClick} for instances where this is called. @@ -229,6 +242,19 @@ class MenuButton extends Component { } } + /** + * Handle `mouseleave` for `MenuButton`. + * + * @param {EventTarget~Event} event + * The `mouseleave` event that caused this function to be called. + * + * @listens mouseleave + */ + handleMouseLeave(event) { + this.removeClass('vjs-hover'); + Events.off(document, 'keyup', Fn.bind(this, this.handleMenuKeyUp)); + } + /** * Set the focus to the actual button, not to this element */ @@ -275,6 +301,22 @@ class MenuButton extends Component { } } + /** + * Handle a `keyup` event on a `MenuButton`. The listener for this is added in + * the constructor. + * + * @param {EventTarget~Event} event + * Key press event + * + * @listens keyup + */ + handleMenuKeyUp(event) { + // Escape hides popup menu + if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { + this.removeClass('vjs-hover'); + } + } + /** * This method name now delegates to `handleSubmenuKeyDown`. This means * anyone calling `handleSubmenuKeyPress` will not see their method calls