/*! @name videojs-contextmenu @version 2.0.0 @license Apache-2.0 */
(function (videojs,QUnit,sinon) {
'use strict';
videojs = videojs && videojs.hasOwnProperty('default') ? videojs['default'] : videojs;
QUnit = QUnit && QUnit.hasOwnProperty('default') ? QUnit['default'] : QUnit;
sinon = sinon && sinon.hasOwnProperty('default') ? sinon['default'] : sinon;
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
var empty = {};
var empty$1 = /*#__PURE__*/Object.freeze({
default: empty
});
var minDoc = ( empty$1 && empty ) || empty$1;
var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal :
typeof window !== 'undefined' ? window : {};
var doccy;
if (typeof document !== 'undefined') {
doccy = document;
} else {
doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
if (!doccy) {
doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
}
}
var document_1 = doccy;
/**
* @module plugin
*/
// vjs 5/6 cross compatibility.
var registerPlugin = videojs.registerPlugin || videojs.plugin;
/* eslint func-style: 0 */
var defaults = {
cancel: true,
sensitivity: 10,
wait: 500,
disabled: false
};
var EVENT_NAME = 'vjs-contextmenu';
/**
* Abstracts a DOM standard event into a vjs-contextmenu event.
*
* @private
* @param {Player} player
* @param {Event} event
* A triggering, native event.
* @return {Player}
*/
function sendAbstractedEvent(player, event) {
if (player.contextmenu.options.disabled) {
// videojs-contextmenu is disabled
return player;
}
var abstracted = {
target: player,
type: EVENT_NAME
};
['clientX', 'clientY', 'pageX', 'pageY', 'screenX', 'screenY'].forEach(function (k) {
abstracted[k] = event[k];
});
return player.trigger(abstracted);
}
/**
* Handles both touchcancel and touchend events.
*
* @private
* @param {Event} e
*/
function handleTouchEnd(e) {
var current = this.contextmenu.current;
if (!current) {
return;
}
var wait = this.contextmenu.options.wait;
if (e.type === 'touchend' && Number(new Date()) - current.time >= wait) {
sendAbstractedEvent(this, e);
}
this.contextmenu.current = null;
}
/**
* Handles touchmove events.
*
* @private
* @param {Event} e
*/
function handleTouchMove(e) {
var current = this.contextmenu.current;
if (!current) {
return;
}
var touch = e.touches[0];
var sensitivity = this.contextmenu.options.sensitivity;
// Cancel the current touch if the pointer has moved in either direction
// more than the sensitivity number of pixels.
if (touch.screenX - current.screenX > sensitivity || touch.screenY - current.screenY > sensitivity) {
this.contextmenu.current = null;
}
}
/**
* Handles touchstart events.
*
* @private
* @param {Event} e
*/
function handleTouchStart(e) {
// We only care about the first touch point.
if (this.contextmenu.current) {
return;
}
var touch = e.touches[0];
this.contextmenu.current = {
screenX: touch.screenX,
screenY: touch.screenY,
time: Number(new Date())
};
}
/**
* Handles contextmenu events.
*
* @private
* @param {Event} e
*/
function handleContextMenu(e) {
if (this.contextmenu.options.cancel && !this.contextmenu.options.disabled) {
e.preventDefault();
}
sendAbstractedEvent(this, e);
// If we get a "contextmenu" event, we can rely on that going forward
// because this client supports it; so, we can stop listening for
// touch events.
this.off(['touchcancel', 'touchend'], handleTouchEnd);
this.off('touchmove', handleTouchMove);
this.off('touchstart', handleTouchStart);
}
/**
* A cross-device context menu implementation for video.js players.
*
* @param {Object} [options={}]
* @param {Boolean} [cancel=true]
* Whether or not to cancel the native "contextmenu" event when
* it is seen.
*
* @param {Number} [sensitivity=10]
* The maximum number of pixels a finger can move because a touch
* is no longer considered to be "held".
*
* @param {Number} [wait=500]
* The minimum number of milliseconds a touch must be "held" before
* it registers.
*/
function contextmenu(options) {
var _this = this;
this.contextmenu.options = videojs.mergeOptions(defaults, options);
this.contextmenu.VERSION = '__VERSION__';
this.on('contextmenu', handleContextMenu);
this.on(['touchcancel', 'touchend'], handleTouchEnd);
this.on('touchmove', handleTouchMove);
this.on('touchstart', handleTouchStart);
this.ready(function () {
return _this.addClass(EVENT_NAME);
});
}
registerPlugin('contextmenu', contextmenu);
contextmenu.VERSION = '__VERSION__';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var Player = videojs.getComponent('Player');
QUnit.test('the environment is sane', function (assert) {
assert.strictEqual(_typeof(Array.isArray), 'function', 'es5 exists');
assert.strictEqual(typeof sinon === 'undefined' ? 'undefined' : _typeof(sinon), 'object', 'sinon exists');
assert.strictEqual(typeof videojs === 'undefined' ? 'undefined' : _typeof(videojs), 'function', 'videojs exists');
assert.strictEqual(typeof contextmenu === 'undefined' ? 'undefined' : _typeof(contextmenu), 'function', 'plugin is a function');
});
QUnit.module('videojs-contextmenu', {
beforeEach: function beforeEach() {
// Mock the environment's timers because certain things - particularly
// player readiness - are asynchronous in video.js 5. This MUST come
// before any player is created; otherwise, timers could get created
// with the actual timer methods!
this.clock = sinon.useFakeTimers();
this.fixture = document_1.getElementById('qunit-fixture');
this.video = document_1.createElement('video');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.contextmenu();
this.spy = sinon.spy();
this.player.on('vjs-contextmenu', this.spy);
// Tick the clock forward enough to trigger the player to be "ready".
this.clock.tick(1);
},
afterEach: function afterEach() {
this.player.dispose();
this.clock.restore();
}
});
QUnit.test('registers itself with video.js', function (assert) {
assert.strictEqual(_typeof(Player.prototype.contextmenu), 'function', 'videojs-contextmenu plugin was registered');
assert.ok(this.player.hasClass('vjs-contextmenu'), 'the plugin adds a class to the player');
});
QUnit.test('sends a "vjs-contextmenu" event when a native "contextmenu" event occurs', function (assert) {
assert.notOk(this.spy.called, '"vjs-contextmenu" has not been triggered yet');
this.player.trigger('contextmenu');
assert.ok(this.spy.calledOnce, '"contextmenu" triggered a "vjs-contextmenu"');
});
QUnit.test('sends a "vjs-contextmenu" on long touch', function (assert) {
this.player.trigger({
type: 'touchstart',
touches: [{
screenX: 1,
screenY: 1
}]
});
this.clock.tick(1000);
assert.notOk(this.spy.called, '"vjs-contextmenu" was not triggered between "touchstart" and "touchend"');
this.player.trigger({ type: 'touchend' });
assert.ok(this.spy.calledOnce, '"vjs-contextmenu" was triggered once a "touchend" triggered');
});
QUnit.test('stops listening for touches if it encounters a native "contextmenu" event', function (assert) {
assert.notOk(this.spy.called, '"vjs-contextmenu" has not been triggered yet');
this.player.trigger('contextmenu');
assert.ok(this.spy.calledOnce, '"contextmenu" triggered a "vjs-contextmenu"');
this.player.trigger({
type: 'touchstart',
touches: [{
screenX: 1,
screenY: 1
}]
});
this.clock.tick(1000);
this.player.trigger({
type: 'touchend'
});
assert.ok(this.spy.calledOnce, 'touches did not trigger a second "vjs-contextmenu"');
this.player.trigger('contextmenu');
assert.ok(this.spy.calledTwice, '"contextmenu" triggered a second "vjs-contextmenu"');
});
QUnit.test('will not fire "vjs-contextmenu" if the touch point has moved beyond the sensitivity range in either direction', function (assert) {
this.player.trigger({
type: 'touchstart',
touches: [{
screenX: 1,
screenY: 1
}]
});
this.player.trigger({
type: 'touchmove',
touches: [{
screenX: 12,
screenY: 1
}]
});
this.clock.tick(1000);
this.player.trigger({
type: 'touchend'
});
assert.notOk(this.spy.called, '"vjs-contextmenu" was not triggered because the touch point moved');
this.player.trigger({
type: 'touchstart',
touches: [{
screenX: 1,
screenY: 1
}]
});
this.player.trigger({
type: 'touchmove',
touches: [{
screenX: 1,
screenY: 12
}]
});
this.clock.tick(1000);
this.player.trigger({
type: 'touchend'
});
assert.notOk(this.spy.called, '"vjs-contextmenu" was not triggered because the touch point moved');
});
}(videojs,QUnit,sinon));