/**
* @file menu-button.js
*/
import Button from '../button.js' ;
import Component from '../component.js' ;
import Menu from './menu.js' ;
import * as Dom from '../utils/dom.js' ;
import * as Events from '../utils/events.js' ;
import {toTitleCase} de '../utils/string-cases.js' ;
import { IS_IOS } from '../utils/browser.js' ;
import document from 'global/document' ;
import keycode from 'keycode' ;
/**
* Une classe `MenuButton` pour n'importe quel {@link Menu} popup.
*
* @extends Component
*/
class MenuButton extends Component {
/**
* Crée une instance de cette classe.
*
* @param {Player} player
* Le `Player` auquel cette classe doit être attachée.
*
* @param {Objet} [options={}]
* La mémoire clé/valeur des options du lecteur.
*/
constructor(player, options = {}) {
super(player, options) ;
this.menuButton_ = new Button(player, options) ;
this.menuButton_.controlText(this.controlText_) ;
this.menuButton_.el_.setAttribute('aria-haspopup', 'true') ;
// Ajouter les valeurs de buildCSSClass au bouton, pas au wrapper
const buttonClass = Button.prototype.buildCSSClass() ;
this.menuButton_.el_.className = this.buildCSSClass() + ' ' + buttonClass ;
this.menuButton_.removeClass('vjs-control') ;
this.addChild(this.menuButton_) ;
this.update() ;
this.enabled_ = true ;
const handleClick = (e) => this.handleClick(e) ;
this.handleMenuKeyUp_ = (e) => this.handleMenuKeyUp(e) ;
this.on(this.menuButton_, 'tap', handleClick) ;
this.on(this.menuButton_, 'click', handleClick) ;
this.on(this.menuButton_, 'keydown', (e) => this.handleKeyDown(e)) ;
this.on(this.menuButton_, 'mouseenter', () => {
this.addClass('vjs-hover') ;
this.menu.show() ;
Events.on(document, 'keyup', this.handleMenuKeyUp_) ;
}) ;
this.on('mouseleave', (e) => this.handleMouseLeave(e)) ;
this.on('keydown', (e) => this.handleSubmenuKeyDown(e)) ;
}
/**
* Mettre à jour le menu en fonction de l'état actuel de ses éléments.
*/
update() {
const menu = this.createMenu() ;
if (this.menu) {
this.menu.dispose() ;
this.removeChild(this.menu) ;
}
this.menu = menu ;
this.addChild(menu) ;
/**
* Suivre l'état du bouton de menu
*
* @type {Booléen}
* @private
*/
this.buttonPressed_ = false ;
this.menuButton_.el_.setAttribute('aria-expanded', 'false') ;
if (this.items && this.items.length <= this.hideThreshold_) {
this.hide() ;
this.menu.contentEl_.removeAttribute('role') ;
} else {
this.show() ;
this.menu.contentEl_.setAttribute('role', 'menu') ;
}
}
/**
* Créez le menu et ajoutez-y tous les éléments.
*
* @return {Menu}
* Le menu construit
*/
createMenu() {
const menu = new Menu(this.player_, { menuButton : this }) ;
/**
* Masquer le menu si le nombre d'éléments est inférieur ou égal à ce seuil. Par défaut, il s'agit de
* à 0 et chaque fois que nous ajouterons des éléments qui peuvent être cachés dans le menu, nous l'incrémenterons. Nous listons
* car à chaque fois que nous lançons `createMenu`, nous devons réinitialiser la valeur.
*
* @protégé
* @type {Nombre}
*/
this.hideThreshold_ = 0 ;
// Ajouter un élément de la liste des titres en haut de la page
if (this.options_.title) {
const titleEl = Dom.createEl('li', {
className : 'vjs-menu-title',
textContent : toTitleCase(this.options_.title),
tabIndex : -1
}) ;
const titleComponent = new Component(this.player_, {el : titleEl}) ;
menu.addItem(titleComponent) ;
}
this.items = this.createItems() ;
if (this.items) {
// Ajouter des éléments de menu au menu
for (let i = 0 ; i < this.items.length ; i++) {
menu.addItem(this.items[i]) ;
}
}
menu de retour ;
}
/**
* Créez la liste des éléments du menu. Spécifique à chaque sous-classe.
*
* @abstract
*/
createItems() {}
/**
* Créez l'élément DOM `MenuButtons`.
*
* @return {Element}
* L'élément qui est créé.
*/
createEl() {
return super.createEl('div', {
className : this.buildWrapperCSSClass()
}, {
}) ;
}
/**
* Permettre aux sous-composants d'empiler les noms de classe CSS pour l'élément enveloppant
*
* @return {string}
* L'enveloppe DOM `className` construite
*/
buildWrapperCSSClass() {
let menuButtonClass = 'vjs-menu-button' ;
// Si l'option inline est passée, nous voulons utiliser des styles différents.
if (this.options_.inline === true) {
menuButtonClass += '-inline' ;
} else {
menuButtonClass += '-popup' ;
}
// TODO : Corrigez le CSS pour que cela ne soit pas nécessaire
const buttonClass = Button.prototype.buildCSSClass() ;
return `vjs-menu-button ${menuButtonClass} ${buttonClass} ${super.buildCSSClass()}` ;
}
/**
* Construit le DOM par défaut `className`.
*
* @return {string}
* Le `nom de classe` du DOM pour cet objet.
*/
buildCSSClass() {
let menuButtonClass = 'vjs-menu-button' ;
// Si l'option inline est passée, nous voulons utiliser des styles différents.
if (this.options_.inline === true) {
menuButtonClass += '-inline' ;
} else {
menuButtonClass += '-popup' ;
}
return `vjs-menu-button ${menuButtonClass} ${super.buildCSSClass()}` ;
}
/**
* Obtenir ou définir le texte de contrôle localisé qui sera utilisé pour l'accessibilité.
*
* > NOTE : Il provient de l'élément interne `menuButton_`.
*
* @param {string} [texte]
* Texte de contrôle pour l'élément.
*
* @param {Element} [el=this.menuButton_.el()]
* Élément sur lequel le titre doit être placé.
*
* @return {string}
* - Le texte du contrôle lors de l'obtention de
*/
controlText(text, el = this.menuButton_.el()) {
return this.menuButton_.controlText(text, el) ;
}
/**
* Se débarrasse du `menu-button` et de tous ses composants enfants.
*/
dispose() {
this.handleMouseLeave() ;
super.dispose() ;
}
/**
* Gère un clic sur un `MenuButton`.
* Voir {@link ClickableComponent#handleClick} pour les instances où cette fonction est appelée.
*
* @param {EventTarget~Event} event
* L'événement `keydown`, `tap` ou `click` qui a provoqué l'activation de cette fonction est le suivant
* appelé.
*
* @listens tap
* @listens click
*/
handleClick(event) {
if (this.buttonPressed_) {
this.unpressButton() ;
} else {
this.pressButton() ;
}
}
/**
* Gère le `mouseleave` pour le `MenuButton`.
*
* @param {EventTarget~Event} event
* L'événement `mouseleave` qui a provoqué l'appel de cette fonction.
*
* @listens mouseleave
*/
handleMouseLeave(event) {
this.removeClass('vjs-hover') ;
Events.off(document, 'keyup', this.handleMenuKeyUp_) ;
}
/**
* Mettre l'accent sur le bouton lui-même, et non sur cet élément
*/
focus() {
this.menuButton_.focus() ;
}
/**
* Retirer le focus du bouton actuel, et non de cet élément
*/
blur() {
this.menuButton_.blur() ;
}
/**
* Gère les touches de tabulation, d'échappement, de flèche vers le bas et de flèche vers le haut pour `MenuButton`. Voir
* {@link ClickableComponent#handleKeyDown} pour les cas où cette fonction est appelée.
*
* @param {EventTarget~Event} event
* L'événement `keydown` qui a provoqué l'appel de cette fonction.
*
* @listens keydown
*/
handleKeyDown(event) {
// La touche Escape ou Tab permet d'appuyer sur le "bouton"
if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
if (this.buttonPressed_) {
this.unpressButton() ;
}
// Ne pas utiliser preventDefault pour la touche Tab - nous voulons toujours perdre le focus
if (!keycode.isEventKey(event, 'Tab')) {
event.preventDefault() ;
// Remettre le focus sur le bouton du menu
this.menuButton_.focus() ;
}
// Flèche vers le haut ou Flèche vers le bas ; appuyez également sur le bouton pour ouvrir le menu
} else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
if (!this.buttonPressed_) {
event.preventDefault() ;
this.pressButton() ;
}
}
}
/**
* Gère un événement `keyup` sur un `MenuButton`. L'écouteur pour cela est ajouté dans
* le constructeur.
*
* @param {EventTarget~Event} event
* Événement presse-clé
*
* @listens keyup
*/
handleMenuKeyUp(event) {
// La touche Escape masque le menu contextuel
if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
this.removeClass('vjs-hover') ;
}
}
/**
* Le nom de cette méthode est désormais délégué à `handleSubmenuKeyDown`. Cela signifie que
* toute personne appelant `handleSubmenuKeyPress` ne verra pas ses appels de méthode
* cessent de fonctionner.
*
* @param {EventTarget~Event} event
* L'événement qui a provoqué l'appel de cette fonction.
*/
handleSubmenuKeyPress(event) {
this.handleSubmenuKeyDown(event) ;
}
/**
* Gérer un événement `keydown` sur un sous-menu. L'écouteur pour cela est ajouté dans
* le constructeur.
*
* @param {EventTarget~Event} event
* Événement presse-clé
*
* @listens keydown
*/
handleSubmenuKeyDown(event) {
// La touche Escape ou Tab permet d'appuyer sur le "bouton"
if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
if (this.buttonPressed_) {
this.unpressButton() ;
}
// Ne pas utiliser preventDefault pour la touche Tab - nous voulons toujours perdre le focus
if (!keycode.isEventKey(event, 'Tab')) {
event.preventDefault() ;
// Remettre le focus sur le bouton du menu
this.menuButton_.focus() ;
}
} else {
// NOTE : Il s'agit d'un cas particulier dans lequel nous ne transmettons pas les messages non traités de
// événements keydown jusqu'au gestionnaire de composants, parce qu'il est
// il s'agit simplement de gérer le keydown du `MenuItem`
// dans le `Menu` qui transmet déjà les touches inutilisées vers le haut.
}
}
/**
* Place le `MenuButton` actuel dans un état pressé.
*/
pressButton() {
if (this.enabled_) {
this.buttonPressed_ = true ;
this.menu.show() ;
this.menu.lockShowing() ;
this.menuButton_.el_.setAttribute('aria-expanded', 'true') ;
// place le focus dans le sous-menu, sauf sur iOS où cela se traduit par
// comportement de défilement indésirable lorsque le lecteur se trouve dans une iframe
if (IS_IOS && Dom.isInFrame()) {
// Retour anticipé pour que le menu ne soit pas focalisé
retour ;
}
ce.menu.focus() ;
}
}
/**
* Retire le `MenuButton` actuel de l'état pressé.
*/
unpressButton() {
if (this.enabled_) {
this.buttonPressed_ = false ;
this.menu.unlockShowing() ;
this.menu.hide() ;
this.menuButton_.el_.setAttribute('aria-expanded', 'false') ;
}
}
/**
* Désactive le bouton de menu. Ne permettez pas de cliquer dessus.
*/
disable() {
this.unpressButton() ;
this.enabled_ = false ;
this.addClass('vjs-disabled') ;
this.menuButton_.disable() ;
}
/**
* Active le `MenuButton`. Autorisez le clic dessus.
*/
enable() {
this.enabled_ = true ;
this.removeClass('vjs-disabled') ;
this.menuButton_.enable() ;
}
}
Component.registerComponent('MenuButton', MenuButton) ;
exporter le bouton de menu par défaut ;