2016-01-25 18:30:12 -05:00
|
|
|
/**
|
2018-04-05 03:50:55 +08:00
|
|
|
* @file clickable-component.js
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
|
|
|
import Component from './component';
|
|
|
|
import * as Dom from './utils/dom.js';
|
|
|
|
import * as Events from './utils/events.js';
|
|
|
|
import * as Fn from './utils/fn.js';
|
|
|
|
import log from './utils/log.js';
|
|
|
|
import document from 'global/document';
|
2016-12-02 15:44:57 -05:00
|
|
|
import {assign} from './utils/obj';
|
2016-01-25 18:30:12 -05:00
|
|
|
|
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Clickable Component which is clickable or keyboard actionable,
|
|
|
|
* but is not a native HTML button.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
|
|
|
* @extends Component
|
|
|
|
*/
|
|
|
|
class ClickableComponent extends Component {
|
|
|
|
|
2016-12-02 15:07:19 -05:00
|
|
|
/**
|
|
|
|
* Creates an instance of this class.
|
|
|
|
*
|
|
|
|
* @param {Player} player
|
|
|
|
* The `Player` that this class should be attached to.
|
|
|
|
*
|
|
|
|
* @param {Object} [options]
|
|
|
|
* The key/value store of player options.
|
|
|
|
*/
|
2016-01-25 18:30:12 -05:00
|
|
|
constructor(player, options) {
|
|
|
|
super(player, options);
|
|
|
|
|
|
|
|
this.emitTapEvents();
|
|
|
|
|
2016-11-03 19:43:15 +00:00
|
|
|
this.enable();
|
2016-01-25 18:30:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Create the `Component`s DOM element.
|
|
|
|
*
|
|
|
|
* @param {string} [tag=div]
|
|
|
|
* The element's node type.
|
|
|
|
*
|
|
|
|
* @param {Object} [props={}]
|
|
|
|
* An object of properties that should be set on the element.
|
|
|
|
*
|
|
|
|
* @param {Object} [attributes={}]
|
|
|
|
* An object of attributes that should be set on the element.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
|
|
|
* @return {Element}
|
2016-12-02 15:07:19 -05:00
|
|
|
* The element that gets created.
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
2016-07-25 09:49:38 -04:00
|
|
|
createEl(tag = 'div', props = {}, attributes = {}) {
|
2016-01-25 18:30:12 -05:00
|
|
|
props = assign({
|
2017-02-01 14:53:43 -05:00
|
|
|
innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
|
2016-01-25 18:30:12 -05:00
|
|
|
className: this.buildCSSClass(),
|
|
|
|
tabIndex: 0
|
|
|
|
}, props);
|
|
|
|
|
|
|
|
if (tag === 'button') {
|
|
|
|
log.error(`Creating a ClickableComponent with an HTML element of ${tag} is not supported; use a Button instead.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add ARIA attributes for clickable element which is not a native HTML button
|
|
|
|
attributes = assign({
|
2018-05-11 11:05:29 -07:00
|
|
|
role: 'button'
|
2016-01-25 18:30:12 -05:00
|
|
|
}, attributes);
|
|
|
|
|
2016-11-03 19:43:15 +00:00
|
|
|
this.tabIndex_ = props.tabIndex;
|
|
|
|
|
2016-08-03 15:29:12 -04:00
|
|
|
const el = super.createEl(tag, props, attributes);
|
2016-01-25 18:30:12 -05:00
|
|
|
|
|
|
|
this.createControlTextEl(el);
|
|
|
|
|
|
|
|
return el;
|
|
|
|
}
|
|
|
|
|
2017-11-16 11:19:47 -05:00
|
|
|
dispose() {
|
2018-04-05 03:50:55 +08:00
|
|
|
// remove controlTextEl_ on dispose
|
2017-11-16 11:19:47 -05:00
|
|
|
this.controlTextEl_ = null;
|
|
|
|
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
2016-01-25 18:30:12 -05:00
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Create a control text element on this `Component`
|
|
|
|
*
|
|
|
|
* @param {Element} [el]
|
|
|
|
* Parent element for the control text.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
|
|
|
* @return {Element}
|
2016-12-02 15:07:19 -05:00
|
|
|
* The control text element that gets created.
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
|
|
|
createControlTextEl(el) {
|
|
|
|
this.controlTextEl_ = Dom.createEl('span', {
|
|
|
|
className: 'vjs-control-text'
|
2018-05-11 11:05:29 -07:00
|
|
|
}, {
|
|
|
|
// let the screen reader user know that the text of the element may change
|
|
|
|
'aria-live': 'polite'
|
2016-01-25 18:30:12 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
if (el) {
|
|
|
|
el.appendChild(this.controlTextEl_);
|
|
|
|
}
|
|
|
|
|
2016-05-17 10:17:12 +02:00
|
|
|
this.controlText(this.controlText_, el);
|
2016-01-25 18:30:12 -05:00
|
|
|
|
|
|
|
return this.controlTextEl_;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Get or set the localize text to use for the controls on the `Component`.
|
|
|
|
*
|
|
|
|
* @param {string} [text]
|
|
|
|
* Control text for element.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
2016-12-02 15:07:19 -05:00
|
|
|
* @param {Element} [el=this.el()]
|
|
|
|
* Element to set the title on.
|
|
|
|
*
|
2017-01-18 01:50:22 -05:00
|
|
|
* @return {string}
|
2016-12-02 15:07:19 -05:00
|
|
|
* - The control text when getting
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
2016-07-25 09:49:38 -04:00
|
|
|
controlText(text, el = this.el()) {
|
2017-11-16 16:27:19 +00:00
|
|
|
if (text === undefined) {
|
2016-07-25 09:49:38 -04:00
|
|
|
return this.controlText_ || 'Need Text';
|
|
|
|
}
|
|
|
|
|
2016-05-17 10:17:12 +02:00
|
|
|
const localizedText = this.localize(text);
|
2016-01-25 18:30:12 -05:00
|
|
|
|
|
|
|
this.controlText_ = text;
|
2017-06-28 02:32:58 -04:00
|
|
|
Dom.textContent(this.controlTextEl_, localizedText);
|
2017-02-02 11:45:39 -08:00
|
|
|
if (!this.nonIconControl) {
|
|
|
|
// Set title attribute if only an icon is shown
|
|
|
|
el.setAttribute('title', localizedText);
|
|
|
|
}
|
2016-01-25 18:30:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Builds the default DOM `className`.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
2016-12-02 15:07:19 -05:00
|
|
|
* @return {string}
|
|
|
|
* The DOM `className` for this object.
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
|
|
|
buildCSSClass() {
|
|
|
|
return `vjs-control vjs-button ${super.buildCSSClass()}`;
|
|
|
|
}
|
|
|
|
|
2016-03-25 14:16:56 -04:00
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Enable this `Component`s element.
|
2016-03-25 14:16:56 -04:00
|
|
|
*/
|
|
|
|
enable() {
|
2017-05-11 22:32:20 +01:00
|
|
|
if (!this.enabled_) {
|
|
|
|
this.enabled_ = true;
|
|
|
|
this.removeClass('vjs-disabled');
|
|
|
|
this.el_.setAttribute('aria-disabled', 'false');
|
|
|
|
if (typeof this.tabIndex_ !== 'undefined') {
|
|
|
|
this.el_.setAttribute('tabIndex', this.tabIndex_);
|
|
|
|
}
|
|
|
|
this.on(['tap', 'click'], this.handleClick);
|
|
|
|
this.on('focus', this.handleFocus);
|
|
|
|
this.on('blur', this.handleBlur);
|
2016-11-03 19:43:15 +00:00
|
|
|
}
|
2016-03-25 14:16:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Disable this `Component`s element.
|
2016-03-25 14:16:56 -04:00
|
|
|
*/
|
|
|
|
disable() {
|
2017-05-11 22:32:20 +01:00
|
|
|
this.enabled_ = false;
|
2016-03-25 14:16:56 -04:00
|
|
|
this.addClass('vjs-disabled');
|
|
|
|
this.el_.setAttribute('aria-disabled', 'true');
|
2016-11-03 19:43:15 +00:00
|
|
|
if (typeof this.tabIndex_ !== 'undefined') {
|
|
|
|
this.el_.removeAttribute('tabIndex');
|
|
|
|
}
|
2017-05-11 22:32:20 +01:00
|
|
|
this.off(['tap', 'click'], this.handleClick);
|
2016-11-03 19:43:15 +00:00
|
|
|
this.off('focus', this.handleFocus);
|
|
|
|
this.off('blur', this.handleBlur);
|
2016-03-25 14:16:56 -04:00
|
|
|
}
|
|
|
|
|
2016-01-25 18:30:12 -05:00
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* This gets called when a `ClickableComponent` gets:
|
|
|
|
* - Clicked (via the `click` event, listening starts in the constructor)
|
|
|
|
* - Tapped (via the `tap` event, listening starts in the constructor)
|
|
|
|
* - The following things happen in order:
|
|
|
|
* 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
|
|
|
|
* `ClickableComponent`.
|
|
|
|
* 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
|
|
|
|
* {@link ClickableComponent#handleKeyPress}.
|
|
|
|
* 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
|
|
|
|
* the space or enter key.
|
|
|
|
* 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
|
|
|
|
* event as a parameter.
|
|
|
|
*
|
|
|
|
* @param {EventTarget~Event} event
|
|
|
|
* The `keydown`, `tap`, or `click` event that caused this function to be
|
|
|
|
* called.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
2016-12-02 15:07:19 -05:00
|
|
|
* @listens tap
|
|
|
|
* @listens click
|
|
|
|
* @abstract
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
2016-12-02 15:07:19 -05:00
|
|
|
handleClick(event) {}
|
2016-01-25 18:30:12 -05:00
|
|
|
|
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* This gets called when a `ClickableComponent` gains focus via a `focus` event.
|
|
|
|
* Turns on listening for `keydown` events. When they happen it
|
|
|
|
* calls `this.handleKeyPress`.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
2016-12-02 15:07:19 -05:00
|
|
|
* @param {EventTarget~Event} event
|
|
|
|
* The `focus` event that caused this function to be called.
|
|
|
|
*
|
|
|
|
* @listens focus
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
2016-12-02 15:07:19 -05:00
|
|
|
handleFocus(event) {
|
2016-01-25 18:30:12 -05:00
|
|
|
Events.on(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Called when this ClickableComponent has focus and a key gets pressed down. By
|
|
|
|
* default it will call `this.handleClick` when the key is space or enter.
|
|
|
|
*
|
|
|
|
* @param {EventTarget~Event} event
|
|
|
|
* The `keydown` event that caused this function to be called.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
2016-12-02 15:07:19 -05:00
|
|
|
* @listens keydown
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
|
|
|
handleKeyPress(event) {
|
2016-07-25 09:49:38 -04:00
|
|
|
|
2016-01-25 18:30:12 -05:00
|
|
|
// Support Space (32) or Enter (13) key operation to fire a click event
|
|
|
|
if (event.which === 32 || event.which === 13) {
|
|
|
|
event.preventDefault();
|
2017-02-07 22:24:30 -08:00
|
|
|
this.trigger('click');
|
2016-01-25 18:30:12 -05:00
|
|
|
} else if (super.handleKeyPress) {
|
2016-07-25 09:49:38 -04:00
|
|
|
|
|
|
|
// Pass keypress handling up for unsupported keys
|
|
|
|
super.handleKeyPress(event);
|
2016-01-25 18:30:12 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-12-02 15:07:19 -05:00
|
|
|
* Called when a `ClickableComponent` loses focus. Turns off the listener for
|
|
|
|
* `keydown` events. Which Stops `this.handleKeyPress` from getting called.
|
|
|
|
*
|
|
|
|
* @param {EventTarget~Event} event
|
|
|
|
* The `blur` event that caused this function to be called.
|
2016-01-25 18:30:12 -05:00
|
|
|
*
|
2016-12-02 15:07:19 -05:00
|
|
|
* @listens blur
|
2016-01-25 18:30:12 -05:00
|
|
|
*/
|
2016-12-02 15:07:19 -05:00
|
|
|
handleBlur(event) {
|
2016-01-25 18:30:12 -05:00
|
|
|
Events.off(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Component.registerComponent('ClickableComponent', ClickableComponent);
|
|
|
|
export default ClickableComponent;
|