Socket
Socket
Sign inDemoInstall

videojs-playlist-ui

Package Overview
Dependencies
Maintainers
178
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

videojs-playlist-ui - npm Package Compare versions

Comparing version 4.2.1 to 5.0.0

dist/videojs-playlist-ui.css.map

395

dist/videojs-playlist-ui.cjs.js

@@ -1,2 +0,2 @@

/*! @name videojs-playlist-ui @version 4.2.1 @license Apache-2.0 */
/*! @name videojs-playlist-ui @version 5.0.0 @license Apache-2.0 */
'use strict';

@@ -12,42 +12,5 @@

var version = "4.2.1";
var version = "5.0.0";
const dom = videojs__default["default"].dom;
const merge = videojs__default["default"].obj ? videojs__default["default"].obj.merge : videojs__default["default"].mergeOptions;
const formatTime = videojs__default["default"].time ? videojs__default["default"].time.formatTime : videojs__default["default"].formatTime; // see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document__default["default"].createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
}; // we don't add `vjs-playlist-now-playing` in addSelectedClass
// so it won't conflict with `vjs-icon-play
// since it'll get added when we mouse out
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component$1 = videojs__default["default"].getComponent('Component');
const createThumbnail = function (thumbnail) {

@@ -59,6 +22,4 @@ if (!thumbnail) {

}
const picture = document__default["default"].createElement('picture');
picture.className = 'vjs-playlist-thumbnail';
if (typeof thumbnail === 'string') {

@@ -73,2 +34,3 @@ // simple thumbnails

// responsive thumbnails
// additional variations of a <picture> are specified as

@@ -78,12 +40,12 @@ // <source> elements

const variant = thumbnail[i];
const source = document__default["default"].createElement('source'); // transfer the properties of each variant onto a <source>
const source = document__default["default"].createElement('source');
// transfer the properties of each variant onto a <source>
for (const prop in variant) {
source[prop] = variant[prop];
}
picture.appendChild(source);
} // the default version of a <picture> is specified by an <img>
}
// the default version of a <picture> is specified by an <img>
const variant = thumbnail[thumbnail.length - 1];

@@ -93,16 +55,10 @@ const img = document__default["default"].createElement('img');

img.alt = '';
for (const prop in variant) {
img[prop] = variant[prop];
}
picture.appendChild(img);
}
return picture;
};
const Component = videojs__default["default"].getComponent('Component');
class PlaylistMenuItem extends Component {
class PlaylistMenuItem extends Component$1 {
constructor(player, playlistItem, settings) {

@@ -112,3 +68,2 @@ if (!playlistItem.item) {

}
playlistItem.showDescription = settings.showDescription;

@@ -122,3 +77,2 @@ super(player, playlistItem);

}
handleKeyDown_(event) {

@@ -131,6 +85,4 @@ // keycode 13 is <Enter>

}
switchPlaylistItem_(event) {
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
if (this.playOnSelect) {

@@ -140,3 +92,2 @@ this.player_.play();

}
createEl() {

@@ -146,3 +97,2 @@ const li = document__default["default"].createElement('li');

const showDescription = this.options_.showDescription;
if (typeof item.data === 'object') {

@@ -155,12 +105,13 @@ const dataKeys = Object.keys(item.data);

}
li.className = 'vjs-playlist-item';
li.setAttribute('tabIndex', 0); // Thumbnail image
li.setAttribute('tabIndex', 0);
// Thumbnail image
this.thumbnail = createThumbnail(item.thumbnail);
li.appendChild(this.thumbnail); // Duration
li.appendChild(this.thumbnail);
// Duration
if (item.duration) {
const duration = document__default["default"].createElement('time');
const time = formatTime(item.duration);
const time = videojs__default["default"].time.formatTime(item.duration);
duration.className = 'vjs-playlist-duration';

@@ -170,5 +121,5 @@ duration.setAttribute('datetime', 'PT0H0M' + item.duration + 'S');

li.appendChild(duration);
} // Now playing
}
// Now playing
const nowPlayingEl = document__default["default"].createElement('span');

@@ -179,8 +130,10 @@ const nowPlayingText = this.localize('Now Playing');

nowPlayingEl.setAttribute('title', nowPlayingText);
this.thumbnail.appendChild(nowPlayingEl); // Title container contains title and "up next"
this.thumbnail.appendChild(nowPlayingEl);
// Title container contains title and "up next"
const titleContainerEl = document__default["default"].createElement('div');
titleContainerEl.className = 'vjs-playlist-title-container';
this.thumbnail.appendChild(titleContainerEl); // Up next
this.thumbnail.appendChild(titleContainerEl);
// Up next
const upNextEl = document__default["default"].createElement('span');

@@ -191,4 +144,5 @@ const upNextText = this.localize('Up Next');

upNextEl.setAttribute('title', upNextText);
titleContainerEl.appendChild(upNextEl); // Video title
titleContainerEl.appendChild(upNextEl);
// Video title
const titleEl = document__default["default"].createElement('cite');

@@ -199,6 +153,8 @@ const titleText = item.name || this.localize('Untitled Video');

titleEl.setAttribute('title', titleText);
titleContainerEl.appendChild(titleEl); // Populate thumbnails alt with the video title
titleContainerEl.appendChild(titleEl);
this.thumbnail.getElementsByTagName('img').alt = titleText; // We add thumbnail video description only if specified in playlist options
// Populate thumbnails alt with the video title
this.thumbnail.getElementsByTagName('img').alt = titleText;
// We add thumbnail video description only if specified in playlist options
if (showDescription) {

@@ -212,17 +168,30 @@ const descriptionEl = document__default["default"].createElement('div');

}
return li;
}
}
videojs__default["default"].registerComponent('PlaylistMenuItem', PlaylistMenuItem);
// we don't add `vjs-playlist-now-playing` in addSelectedClass
// so it won't conflict with `vjs-icon-play
// since it'll get added when we mouse out
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
videojs__default["default"].dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component = videojs__default["default"].getComponent('Component');
class PlaylistMenu extends Component {
constructor(player, options) {
if (!player.playlist) {
throw new Error('videojs-playlist is required for the playlist component');
}
super(player, options);
this.items = [];
if (options.horizontal) {

@@ -232,22 +201,28 @@ this.addClass('vjs-playlist-horizontal');

this.addClass('vjs-playlist-vertical');
} // If CSS pointer events aren't supported, we have to prevent
}
// If CSS pointer events aren't supported, we have to prevent
// clicking on playlist items during ads with slightly more
// invasive techniques. Details in the stylesheet.
if (options.supportsCssPointerEvents) {
this.addClass('vjs-csspointerevents');
}
this.createPlaylist_();
if (!videojs__default["default"].browser.TOUCH_ENABLED) {
this.addClass('vjs-mouse');
}
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], e => {
// The playlistadd and playlistremove events are handled separately. These
// also fire the playlistchange event with an `action` property, so can
// exclude them here.
if (e.type === 'playlistchange' && ['add', 'remove'].includes(e.action)) {
return;
}
this.update();
});
this.on(player, ['playlistadd'], e => this.addItems_(e.index, e.count));
this.on(player, ['playlistremove'], e => this.removeItems_(e.index, e.count));
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], event => {
this.update();
}); // Keep track of whether an ad is playing so that the menu
// Keep track of whether an ad is playing so that the menu
// appearance can be adapted appropriately
this.on(player, 'adstart', () => {

@@ -267,9 +242,7 @@ this.addClass('vjs-ad-playing');

}
createEl() {
return dom.createEl('div', {
return videojs__default["default"].dom.createEl('div', {
className: this.options_.className
});
}
empty_() {

@@ -281,3 +254,2 @@ if (this.items && this.items.length) {

}
createPlaylist_() {

@@ -287,3 +259,2 @@ const playlist = this.player_.playlist() || [];

let overlay = this.el_.querySelector('.vjs-playlist-ad-overlay');
if (!list) {

@@ -294,5 +265,5 @@ list = document__default["default"].createElement('ol');

}
this.empty_();
this.empty_(); // create new items
// create new items
for (let i = 0; i < playlist.length; i++) {

@@ -304,6 +275,6 @@ const item = new PlaylistMenuItem(this.player_, {

list.appendChild(item.el_);
} // Inject the ad overlay. We use this element to block clicks during ad
}
// Inject the ad overlay. We use this element to block clicks during ad
// playback and darken the menu to indicate inactivity
if (!overlay) {

@@ -316,13 +287,11 @@ overlay = document__default["default"].createElement('li');

list.appendChild(overlay);
} // select the current playlist item
}
// select the current playlist item
const selectedIndex = this.player_.playlist.currentItem();
if (this.items.length && selectedIndex >= 0) {
addSelectedClass(this.items[selectedIndex]);
const thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail');
if (thumbnail) {
dom.addClass(thumbnail, 'vjs-playlist-now-playing');
videojs__default["default"].dom.addClass(thumbnail, 'vjs-playlist-now-playing');
}

@@ -332,6 +301,58 @@ }

/**
* Adds a number of playlist items to the UI.
*
* Each item that was added to the underlying playlist in a certain range
* has a new PlaylistMenuItem created for it.
*
* @param {number} index
* The index at which to start adding items.
*
* @param {number} count
* The number of items to add.
*/
addItems_(index, count) {
const playlist = this.player_.playlist();
const items = playlist.slice(index, count + index);
if (!items.length) {
return;
}
const listEl = this.el_.querySelector('.vjs-playlist-item-list');
const listNodes = this.el_.querySelectorAll('.vjs-playlist-item');
// When appending to the list, `insertBefore` will only reliably accept
// `null` as the second argument, so we need to explicitly fall back to it.
const refNode = listNodes[index] || null;
const menuItems = items.map(item => {
const menuItem = new PlaylistMenuItem(this.player_, {
item
}, this.options_);
listEl.insertBefore(menuItem.el_, refNode);
return menuItem;
});
this.items.splice(index, 0, ...menuItems);
}
/**
* Removes a number of playlist items from the UI.
*
* Each PlaylistMenuItem component is disposed properly.
*
* @param {number} index
* The index at which to start removing items.
*
* @param {number} count
* The number of items to remove.
*/
removeItems_(index, count) {
const components = this.items.slice(index, count + index);
if (!components.length) {
return;
}
components.forEach(c => c.dispose());
this.items.splice(index, count);
}
update() {
// replace the playlist items being displayed, if necessary
const playlist = this.player_.playlist();
if (this.items.length !== playlist.length) {

@@ -343,3 +364,2 @@ // if the menu is currently empty or the state is obviously out

}
for (let i = 0; i < this.items.length; i++) {

@@ -352,17 +372,13 @@ if (this.items[i].item !== playlist[i]) {

}
} // the playlist itself is unchanged so just update the selection
}
// the playlist itself is unchanged so just update the selection
const currentItem = this.player_.playlist.currentItem();
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (i === currentItem) {
addSelectedClass(item);
if (document__default["default"].activeElement !== item.el()) {
dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
videojs__default["default"].dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
}
notUpNext(item);

@@ -378,50 +394,18 @@ } else if (i === currentItem + 1) {

}
}
/**
* Returns a boolean indicating whether an element has child elements.
*
* Note that this is distinct from whether it has child _nodes_.
*
* @param {HTMLElement} el
* A DOM element.
*
* @return {boolean}
* Whether the element has child elements.
*/
videojs__default["default"].registerComponent('PlaylistMenu', PlaylistMenu);
const hasChildEls = el => {
for (let i = 0; i < el.childNodes.length; i++) {
if (dom.isEl(el.childNodes[i])) {
return true;
}
}
return false;
// see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document__default["default"].createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
};
/**
* Finds the first empty root element.
*
* @param {string} className
* An HTML class name to search for.
*
* @return {HTMLElement}
* A DOM element to use as the root for a playlist.
*/
const Plugin = videojs__default["default"].getPlugin('plugin');
const findRoot = className => {
const all = document__default["default"].querySelectorAll('.' + className);
let el;
for (let i = 0; i < all.length; i++) {
if (!hasChildEls(all[i])) {
el = all[i];
break;
}
}
return el;
};
/**

@@ -443,58 +427,69 @@ * Initialize the plugin on a player.

*/
class PlaylistUI extends Plugin {
constructor(player, options) {
super(player, options);
if (!player.usingPlugin('playlist')) {
player.log.error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
return;
}
options = this.options_ = videojs__default["default"].obj.merge(defaults, options);
if (!videojs__default["default"].dom.isEl(options.el)) {
options.el = this.findRoot_(options.className);
}
const playlistUi = function (options) {
const player = this;
if (!player.playlist) {
throw new Error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
// Expose the playlist menu component on the player as well as the plugin
// This is a bit of an anti-pattern, but it's been that way forever and
// there are likely to be integrations relying on it.
this.playlistMenu = player.playlistMenu = new PlaylistMenu(player, options);
}
if (dom.isEl(options)) {
videojs__default["default"].log.warn('videojs-playlist-ui: Passing an element directly to playlistUi() is deprecated, use the "el" option instead!');
options = {
el: options
};
/**
* Dispose the plugin.
*/
dispose() {
super.dispose();
this.playlistMenu.dispose();
}
options = merge(defaults, options); // If the player is already using this plugin, remove the pre-existing
// PlaylistMenu, but retain the element and its location in the DOM because
// it will be re-used.
if (player.playlistMenu) {
const el = player.playlistMenu.el(); // Catch cases where the menu may have been disposed elsewhere or the
// element removed from the DOM.
if (el) {
const parentNode = el.parentNode;
const nextSibling = el.nextSibling; // Disposing the menu will remove `el` from the DOM, but we need to
// empty it ourselves to be sure.
player.playlistMenu.dispose();
dom.emptyEl(el); // Put the element back in its place.
if (nextSibling) {
parentNode.insertBefore(el, nextSibling);
} else {
parentNode.appendChild(el);
/**
* Returns a boolean indicating whether an element has child elements.
*
* Note that this is distinct from whether it has child _nodes_.
*
* @param {HTMLElement} el
* A DOM element.
*
* @return {boolean}
* Whether the element has child elements.
*/
hasChildEls_(el) {
for (let i = 0; i < el.childNodes.length; i++) {
if (videojs__default["default"].dom.isEl(el.childNodes[i])) {
return true;
}
options.el = el;
}
return false;
}
if (!dom.isEl(options.el)) {
options.el = findRoot(options.className);
/**
* Finds the first empty root element.
*
* @param {string} className
* An HTML class name to search for.
*
* @return {HTMLElement}
* A DOM element to use as the root for a playlist.
*/
findRoot_(className) {
const all = document__default["default"].querySelectorAll('.' + className);
for (let i = 0; i < all.length; i++) {
if (!this.hasChildEls_(all[i])) {
return all[i];
}
}
}
}
videojs__default["default"].registerPlugin('playlistUi', PlaylistUI);
PlaylistUI.VERSION = version;
player.playlistMenu = new PlaylistMenu(player, options);
}; // register components
videojs__default["default"].registerComponent('PlaylistMenu', PlaylistMenu);
videojs__default["default"].registerComponent('PlaylistMenuItem', PlaylistMenuItem); // register the plugin
videojs__default["default"].registerPlugin('playlistUi', playlistUi);
playlistUi.VERSION = version;
module.exports = playlistUi;
module.exports = PlaylistUI;

@@ -1,45 +0,8 @@

/*! @name videojs-playlist-ui @version 4.2.1 @license Apache-2.0 */
/*! @name videojs-playlist-ui @version 5.0.0 @license Apache-2.0 */
import document from 'global/document';
import videojs from 'video.js';
var version = "4.2.1";
var version = "5.0.0";
const dom = videojs.dom;
const merge = videojs.obj ? videojs.obj.merge : videojs.mergeOptions;
const formatTime = videojs.time ? videojs.time.formatTime : videojs.formatTime; // see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document.createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
}; // we don't add `vjs-playlist-now-playing` in addSelectedClass
// so it won't conflict with `vjs-icon-play
// since it'll get added when we mouse out
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component$1 = videojs.getComponent('Component');
const createThumbnail = function (thumbnail) {

@@ -51,6 +14,4 @@ if (!thumbnail) {

}
const picture = document.createElement('picture');
picture.className = 'vjs-playlist-thumbnail';
if (typeof thumbnail === 'string') {

@@ -65,2 +26,3 @@ // simple thumbnails

// responsive thumbnails
// additional variations of a <picture> are specified as

@@ -70,12 +32,12 @@ // <source> elements

const variant = thumbnail[i];
const source = document.createElement('source'); // transfer the properties of each variant onto a <source>
const source = document.createElement('source');
// transfer the properties of each variant onto a <source>
for (const prop in variant) {
source[prop] = variant[prop];
}
picture.appendChild(source);
} // the default version of a <picture> is specified by an <img>
}
// the default version of a <picture> is specified by an <img>
const variant = thumbnail[thumbnail.length - 1];

@@ -85,16 +47,10 @@ const img = document.createElement('img');

img.alt = '';
for (const prop in variant) {
img[prop] = variant[prop];
}
picture.appendChild(img);
}
return picture;
};
const Component = videojs.getComponent('Component');
class PlaylistMenuItem extends Component {
class PlaylistMenuItem extends Component$1 {
constructor(player, playlistItem, settings) {

@@ -104,3 +60,2 @@ if (!playlistItem.item) {

}
playlistItem.showDescription = settings.showDescription;

@@ -114,3 +69,2 @@ super(player, playlistItem);

}
handleKeyDown_(event) {

@@ -123,6 +77,4 @@ // keycode 13 is <Enter>

}
switchPlaylistItem_(event) {
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
if (this.playOnSelect) {

@@ -132,3 +84,2 @@ this.player_.play();

}
createEl() {

@@ -138,3 +89,2 @@ const li = document.createElement('li');

const showDescription = this.options_.showDescription;
if (typeof item.data === 'object') {

@@ -147,12 +97,13 @@ const dataKeys = Object.keys(item.data);

}
li.className = 'vjs-playlist-item';
li.setAttribute('tabIndex', 0); // Thumbnail image
li.setAttribute('tabIndex', 0);
// Thumbnail image
this.thumbnail = createThumbnail(item.thumbnail);
li.appendChild(this.thumbnail); // Duration
li.appendChild(this.thumbnail);
// Duration
if (item.duration) {
const duration = document.createElement('time');
const time = formatTime(item.duration);
const time = videojs.time.formatTime(item.duration);
duration.className = 'vjs-playlist-duration';

@@ -162,5 +113,5 @@ duration.setAttribute('datetime', 'PT0H0M' + item.duration + 'S');

li.appendChild(duration);
} // Now playing
}
// Now playing
const nowPlayingEl = document.createElement('span');

@@ -171,8 +122,10 @@ const nowPlayingText = this.localize('Now Playing');

nowPlayingEl.setAttribute('title', nowPlayingText);
this.thumbnail.appendChild(nowPlayingEl); // Title container contains title and "up next"
this.thumbnail.appendChild(nowPlayingEl);
// Title container contains title and "up next"
const titleContainerEl = document.createElement('div');
titleContainerEl.className = 'vjs-playlist-title-container';
this.thumbnail.appendChild(titleContainerEl); // Up next
this.thumbnail.appendChild(titleContainerEl);
// Up next
const upNextEl = document.createElement('span');

@@ -183,4 +136,5 @@ const upNextText = this.localize('Up Next');

upNextEl.setAttribute('title', upNextText);
titleContainerEl.appendChild(upNextEl); // Video title
titleContainerEl.appendChild(upNextEl);
// Video title
const titleEl = document.createElement('cite');

@@ -191,6 +145,8 @@ const titleText = item.name || this.localize('Untitled Video');

titleEl.setAttribute('title', titleText);
titleContainerEl.appendChild(titleEl); // Populate thumbnails alt with the video title
titleContainerEl.appendChild(titleEl);
this.thumbnail.getElementsByTagName('img').alt = titleText; // We add thumbnail video description only if specified in playlist options
// Populate thumbnails alt with the video title
this.thumbnail.getElementsByTagName('img').alt = titleText;
// We add thumbnail video description only if specified in playlist options
if (showDescription) {

@@ -204,17 +160,30 @@ const descriptionEl = document.createElement('div');

}
return li;
}
}
videojs.registerComponent('PlaylistMenuItem', PlaylistMenuItem);
// we don't add `vjs-playlist-now-playing` in addSelectedClass
// so it won't conflict with `vjs-icon-play
// since it'll get added when we mouse out
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
videojs.dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component = videojs.getComponent('Component');
class PlaylistMenu extends Component {
constructor(player, options) {
if (!player.playlist) {
throw new Error('videojs-playlist is required for the playlist component');
}
super(player, options);
this.items = [];
if (options.horizontal) {

@@ -224,22 +193,28 @@ this.addClass('vjs-playlist-horizontal');

this.addClass('vjs-playlist-vertical');
} // If CSS pointer events aren't supported, we have to prevent
}
// If CSS pointer events aren't supported, we have to prevent
// clicking on playlist items during ads with slightly more
// invasive techniques. Details in the stylesheet.
if (options.supportsCssPointerEvents) {
this.addClass('vjs-csspointerevents');
}
this.createPlaylist_();
if (!videojs.browser.TOUCH_ENABLED) {
this.addClass('vjs-mouse');
}
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], e => {
// The playlistadd and playlistremove events are handled separately. These
// also fire the playlistchange event with an `action` property, so can
// exclude them here.
if (e.type === 'playlistchange' && ['add', 'remove'].includes(e.action)) {
return;
}
this.update();
});
this.on(player, ['playlistadd'], e => this.addItems_(e.index, e.count));
this.on(player, ['playlistremove'], e => this.removeItems_(e.index, e.count));
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], event => {
this.update();
}); // Keep track of whether an ad is playing so that the menu
// Keep track of whether an ad is playing so that the menu
// appearance can be adapted appropriately
this.on(player, 'adstart', () => {

@@ -259,9 +234,7 @@ this.addClass('vjs-ad-playing');

}
createEl() {
return dom.createEl('div', {
return videojs.dom.createEl('div', {
className: this.options_.className
});
}
empty_() {

@@ -273,3 +246,2 @@ if (this.items && this.items.length) {

}
createPlaylist_() {

@@ -279,3 +251,2 @@ const playlist = this.player_.playlist() || [];

let overlay = this.el_.querySelector('.vjs-playlist-ad-overlay');
if (!list) {

@@ -286,5 +257,5 @@ list = document.createElement('ol');

}
this.empty_();
this.empty_(); // create new items
// create new items
for (let i = 0; i < playlist.length; i++) {

@@ -296,6 +267,6 @@ const item = new PlaylistMenuItem(this.player_, {

list.appendChild(item.el_);
} // Inject the ad overlay. We use this element to block clicks during ad
}
// Inject the ad overlay. We use this element to block clicks during ad
// playback and darken the menu to indicate inactivity
if (!overlay) {

@@ -308,13 +279,11 @@ overlay = document.createElement('li');

list.appendChild(overlay);
} // select the current playlist item
}
// select the current playlist item
const selectedIndex = this.player_.playlist.currentItem();
if (this.items.length && selectedIndex >= 0) {
addSelectedClass(this.items[selectedIndex]);
const thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail');
if (thumbnail) {
dom.addClass(thumbnail, 'vjs-playlist-now-playing');
videojs.dom.addClass(thumbnail, 'vjs-playlist-now-playing');
}

@@ -324,6 +293,58 @@ }

/**
* Adds a number of playlist items to the UI.
*
* Each item that was added to the underlying playlist in a certain range
* has a new PlaylistMenuItem created for it.
*
* @param {number} index
* The index at which to start adding items.
*
* @param {number} count
* The number of items to add.
*/
addItems_(index, count) {
const playlist = this.player_.playlist();
const items = playlist.slice(index, count + index);
if (!items.length) {
return;
}
const listEl = this.el_.querySelector('.vjs-playlist-item-list');
const listNodes = this.el_.querySelectorAll('.vjs-playlist-item');
// When appending to the list, `insertBefore` will only reliably accept
// `null` as the second argument, so we need to explicitly fall back to it.
const refNode = listNodes[index] || null;
const menuItems = items.map(item => {
const menuItem = new PlaylistMenuItem(this.player_, {
item
}, this.options_);
listEl.insertBefore(menuItem.el_, refNode);
return menuItem;
});
this.items.splice(index, 0, ...menuItems);
}
/**
* Removes a number of playlist items from the UI.
*
* Each PlaylistMenuItem component is disposed properly.
*
* @param {number} index
* The index at which to start removing items.
*
* @param {number} count
* The number of items to remove.
*/
removeItems_(index, count) {
const components = this.items.slice(index, count + index);
if (!components.length) {
return;
}
components.forEach(c => c.dispose());
this.items.splice(index, count);
}
update() {
// replace the playlist items being displayed, if necessary
const playlist = this.player_.playlist();
if (this.items.length !== playlist.length) {

@@ -335,3 +356,2 @@ // if the menu is currently empty or the state is obviously out

}
for (let i = 0; i < this.items.length; i++) {

@@ -344,17 +364,13 @@ if (this.items[i].item !== playlist[i]) {

}
} // the playlist itself is unchanged so just update the selection
}
// the playlist itself is unchanged so just update the selection
const currentItem = this.player_.playlist.currentItem();
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (i === currentItem) {
addSelectedClass(item);
if (document.activeElement !== item.el()) {
dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
videojs.dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
}
notUpNext(item);

@@ -370,50 +386,18 @@ } else if (i === currentItem + 1) {

}
}
/**
* Returns a boolean indicating whether an element has child elements.
*
* Note that this is distinct from whether it has child _nodes_.
*
* @param {HTMLElement} el
* A DOM element.
*
* @return {boolean}
* Whether the element has child elements.
*/
videojs.registerComponent('PlaylistMenu', PlaylistMenu);
const hasChildEls = el => {
for (let i = 0; i < el.childNodes.length; i++) {
if (dom.isEl(el.childNodes[i])) {
return true;
}
}
return false;
// see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document.createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
};
/**
* Finds the first empty root element.
*
* @param {string} className
* An HTML class name to search for.
*
* @return {HTMLElement}
* A DOM element to use as the root for a playlist.
*/
const Plugin = videojs.getPlugin('plugin');
const findRoot = className => {
const all = document.querySelectorAll('.' + className);
let el;
for (let i = 0; i < all.length; i++) {
if (!hasChildEls(all[i])) {
el = all[i];
break;
}
}
return el;
};
/**

@@ -435,58 +419,69 @@ * Initialize the plugin on a player.

*/
class PlaylistUI extends Plugin {
constructor(player, options) {
super(player, options);
if (!player.usingPlugin('playlist')) {
player.log.error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
return;
}
options = this.options_ = videojs.obj.merge(defaults, options);
if (!videojs.dom.isEl(options.el)) {
options.el = this.findRoot_(options.className);
}
const playlistUi = function (options) {
const player = this;
if (!player.playlist) {
throw new Error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
// Expose the playlist menu component on the player as well as the plugin
// This is a bit of an anti-pattern, but it's been that way forever and
// there are likely to be integrations relying on it.
this.playlistMenu = player.playlistMenu = new PlaylistMenu(player, options);
}
if (dom.isEl(options)) {
videojs.log.warn('videojs-playlist-ui: Passing an element directly to playlistUi() is deprecated, use the "el" option instead!');
options = {
el: options
};
/**
* Dispose the plugin.
*/
dispose() {
super.dispose();
this.playlistMenu.dispose();
}
options = merge(defaults, options); // If the player is already using this plugin, remove the pre-existing
// PlaylistMenu, but retain the element and its location in the DOM because
// it will be re-used.
if (player.playlistMenu) {
const el = player.playlistMenu.el(); // Catch cases where the menu may have been disposed elsewhere or the
// element removed from the DOM.
if (el) {
const parentNode = el.parentNode;
const nextSibling = el.nextSibling; // Disposing the menu will remove `el` from the DOM, but we need to
// empty it ourselves to be sure.
player.playlistMenu.dispose();
dom.emptyEl(el); // Put the element back in its place.
if (nextSibling) {
parentNode.insertBefore(el, nextSibling);
} else {
parentNode.appendChild(el);
/**
* Returns a boolean indicating whether an element has child elements.
*
* Note that this is distinct from whether it has child _nodes_.
*
* @param {HTMLElement} el
* A DOM element.
*
* @return {boolean}
* Whether the element has child elements.
*/
hasChildEls_(el) {
for (let i = 0; i < el.childNodes.length; i++) {
if (videojs.dom.isEl(el.childNodes[i])) {
return true;
}
options.el = el;
}
return false;
}
if (!dom.isEl(options.el)) {
options.el = findRoot(options.className);
/**
* Finds the first empty root element.
*
* @param {string} className
* An HTML class name to search for.
*
* @return {HTMLElement}
* A DOM element to use as the root for a playlist.
*/
findRoot_(className) {
const all = document.querySelectorAll('.' + className);
for (let i = 0; i < all.length; i++) {
if (!this.hasChildEls_(all[i])) {
return all[i];
}
}
}
}
videojs.registerPlugin('playlistUi', PlaylistUI);
PlaylistUI.VERSION = version;
player.playlistMenu = new PlaylistMenu(player, options);
}; // register components
videojs.registerComponent('PlaylistMenu', PlaylistMenu);
videojs.registerComponent('PlaylistMenuItem', PlaylistMenuItem); // register the plugin
videojs.registerPlugin('playlistUi', playlistUi);
playlistUi.VERSION = version;
export { playlistUi as default };
export { PlaylistUI as default };

@@ -1,2 +0,2 @@

/*! @name videojs-playlist-ui @version 4.2.1 @license Apache-2.0 */
/*! @name videojs-playlist-ui @version 5.0.0 @license Apache-2.0 */
(function (global, factory) {

@@ -12,42 +12,5 @@ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :

var version = "4.2.1";
var version = "5.0.0";
const dom = videojs__default["default"].dom;
const merge = videojs__default["default"].obj ? videojs__default["default"].obj.merge : videojs__default["default"].mergeOptions;
const formatTime = videojs__default["default"].time ? videojs__default["default"].time.formatTime : videojs__default["default"].formatTime; // see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document.createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
}; // we don't add `vjs-playlist-now-playing` in addSelectedClass
// so it won't conflict with `vjs-icon-play
// since it'll get added when we mouse out
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component$1 = videojs__default["default"].getComponent('Component');
const createThumbnail = function (thumbnail) {

@@ -59,6 +22,4 @@ if (!thumbnail) {

}
const picture = document.createElement('picture');
picture.className = 'vjs-playlist-thumbnail';
if (typeof thumbnail === 'string') {

@@ -73,2 +34,3 @@ // simple thumbnails

// responsive thumbnails
// additional variations of a <picture> are specified as

@@ -78,12 +40,12 @@ // <source> elements

const variant = thumbnail[i];
const source = document.createElement('source'); // transfer the properties of each variant onto a <source>
const source = document.createElement('source');
// transfer the properties of each variant onto a <source>
for (const prop in variant) {
source[prop] = variant[prop];
}
picture.appendChild(source);
} // the default version of a <picture> is specified by an <img>
}
// the default version of a <picture> is specified by an <img>
const variant = thumbnail[thumbnail.length - 1];

@@ -93,16 +55,10 @@ const img = document.createElement('img');

img.alt = '';
for (const prop in variant) {
img[prop] = variant[prop];
}
picture.appendChild(img);
}
return picture;
};
const Component = videojs__default["default"].getComponent('Component');
class PlaylistMenuItem extends Component {
class PlaylistMenuItem extends Component$1 {
constructor(player, playlistItem, settings) {

@@ -112,3 +68,2 @@ if (!playlistItem.item) {

}
playlistItem.showDescription = settings.showDescription;

@@ -122,3 +77,2 @@ super(player, playlistItem);

}
handleKeyDown_(event) {

@@ -131,6 +85,4 @@ // keycode 13 is <Enter>

}
switchPlaylistItem_(event) {
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
if (this.playOnSelect) {

@@ -140,3 +92,2 @@ this.player_.play();

}
createEl() {

@@ -146,3 +97,2 @@ const li = document.createElement('li');

const showDescription = this.options_.showDescription;
if (typeof item.data === 'object') {

@@ -155,12 +105,13 @@ const dataKeys = Object.keys(item.data);

}
li.className = 'vjs-playlist-item';
li.setAttribute('tabIndex', 0); // Thumbnail image
li.setAttribute('tabIndex', 0);
// Thumbnail image
this.thumbnail = createThumbnail(item.thumbnail);
li.appendChild(this.thumbnail); // Duration
li.appendChild(this.thumbnail);
// Duration
if (item.duration) {
const duration = document.createElement('time');
const time = formatTime(item.duration);
const time = videojs__default["default"].time.formatTime(item.duration);
duration.className = 'vjs-playlist-duration';

@@ -170,5 +121,5 @@ duration.setAttribute('datetime', 'PT0H0M' + item.duration + 'S');

li.appendChild(duration);
} // Now playing
}
// Now playing
const nowPlayingEl = document.createElement('span');

@@ -179,8 +130,10 @@ const nowPlayingText = this.localize('Now Playing');

nowPlayingEl.setAttribute('title', nowPlayingText);
this.thumbnail.appendChild(nowPlayingEl); // Title container contains title and "up next"
this.thumbnail.appendChild(nowPlayingEl);
// Title container contains title and "up next"
const titleContainerEl = document.createElement('div');
titleContainerEl.className = 'vjs-playlist-title-container';
this.thumbnail.appendChild(titleContainerEl); // Up next
this.thumbnail.appendChild(titleContainerEl);
// Up next
const upNextEl = document.createElement('span');

@@ -191,4 +144,5 @@ const upNextText = this.localize('Up Next');

upNextEl.setAttribute('title', upNextText);
titleContainerEl.appendChild(upNextEl); // Video title
titleContainerEl.appendChild(upNextEl);
// Video title
const titleEl = document.createElement('cite');

@@ -199,6 +153,8 @@ const titleText = item.name || this.localize('Untitled Video');

titleEl.setAttribute('title', titleText);
titleContainerEl.appendChild(titleEl); // Populate thumbnails alt with the video title
titleContainerEl.appendChild(titleEl);
this.thumbnail.getElementsByTagName('img').alt = titleText; // We add thumbnail video description only if specified in playlist options
// Populate thumbnails alt with the video title
this.thumbnail.getElementsByTagName('img').alt = titleText;
// We add thumbnail video description only if specified in playlist options
if (showDescription) {

@@ -212,17 +168,30 @@ const descriptionEl = document.createElement('div');

}
return li;
}
}
videojs__default["default"].registerComponent('PlaylistMenuItem', PlaylistMenuItem);
// we don't add `vjs-playlist-now-playing` in addSelectedClass
// so it won't conflict with `vjs-icon-play
// since it'll get added when we mouse out
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
videojs__default["default"].dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component = videojs__default["default"].getComponent('Component');
class PlaylistMenu extends Component {
constructor(player, options) {
if (!player.playlist) {
throw new Error('videojs-playlist is required for the playlist component');
}
super(player, options);
this.items = [];
if (options.horizontal) {

@@ -232,22 +201,28 @@ this.addClass('vjs-playlist-horizontal');

this.addClass('vjs-playlist-vertical');
} // If CSS pointer events aren't supported, we have to prevent
}
// If CSS pointer events aren't supported, we have to prevent
// clicking on playlist items during ads with slightly more
// invasive techniques. Details in the stylesheet.
if (options.supportsCssPointerEvents) {
this.addClass('vjs-csspointerevents');
}
this.createPlaylist_();
if (!videojs__default["default"].browser.TOUCH_ENABLED) {
this.addClass('vjs-mouse');
}
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], e => {
// The playlistadd and playlistremove events are handled separately. These
// also fire the playlistchange event with an `action` property, so can
// exclude them here.
if (e.type === 'playlistchange' && ['add', 'remove'].includes(e.action)) {
return;
}
this.update();
});
this.on(player, ['playlistadd'], e => this.addItems_(e.index, e.count));
this.on(player, ['playlistremove'], e => this.removeItems_(e.index, e.count));
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], event => {
this.update();
}); // Keep track of whether an ad is playing so that the menu
// Keep track of whether an ad is playing so that the menu
// appearance can be adapted appropriately
this.on(player, 'adstart', () => {

@@ -267,9 +242,7 @@ this.addClass('vjs-ad-playing');

}
createEl() {
return dom.createEl('div', {
return videojs__default["default"].dom.createEl('div', {
className: this.options_.className
});
}
empty_() {

@@ -281,3 +254,2 @@ if (this.items && this.items.length) {

}
createPlaylist_() {

@@ -287,3 +259,2 @@ const playlist = this.player_.playlist() || [];

let overlay = this.el_.querySelector('.vjs-playlist-ad-overlay');
if (!list) {

@@ -294,5 +265,5 @@ list = document.createElement('ol');

}
this.empty_();
this.empty_(); // create new items
// create new items
for (let i = 0; i < playlist.length; i++) {

@@ -304,6 +275,6 @@ const item = new PlaylistMenuItem(this.player_, {

list.appendChild(item.el_);
} // Inject the ad overlay. We use this element to block clicks during ad
}
// Inject the ad overlay. We use this element to block clicks during ad
// playback and darken the menu to indicate inactivity
if (!overlay) {

@@ -316,13 +287,11 @@ overlay = document.createElement('li');

list.appendChild(overlay);
} // select the current playlist item
}
// select the current playlist item
const selectedIndex = this.player_.playlist.currentItem();
if (this.items.length && selectedIndex >= 0) {
addSelectedClass(this.items[selectedIndex]);
const thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail');
if (thumbnail) {
dom.addClass(thumbnail, 'vjs-playlist-now-playing');
videojs__default["default"].dom.addClass(thumbnail, 'vjs-playlist-now-playing');
}

@@ -332,6 +301,58 @@ }

/**
* Adds a number of playlist items to the UI.
*
* Each item that was added to the underlying playlist in a certain range
* has a new PlaylistMenuItem created for it.
*
* @param {number} index
* The index at which to start adding items.
*
* @param {number} count
* The number of items to add.
*/
addItems_(index, count) {
const playlist = this.player_.playlist();
const items = playlist.slice(index, count + index);
if (!items.length) {
return;
}
const listEl = this.el_.querySelector('.vjs-playlist-item-list');
const listNodes = this.el_.querySelectorAll('.vjs-playlist-item');
// When appending to the list, `insertBefore` will only reliably accept
// `null` as the second argument, so we need to explicitly fall back to it.
const refNode = listNodes[index] || null;
const menuItems = items.map(item => {
const menuItem = new PlaylistMenuItem(this.player_, {
item
}, this.options_);
listEl.insertBefore(menuItem.el_, refNode);
return menuItem;
});
this.items.splice(index, 0, ...menuItems);
}
/**
* Removes a number of playlist items from the UI.
*
* Each PlaylistMenuItem component is disposed properly.
*
* @param {number} index
* The index at which to start removing items.
*
* @param {number} count
* The number of items to remove.
*/
removeItems_(index, count) {
const components = this.items.slice(index, count + index);
if (!components.length) {
return;
}
components.forEach(c => c.dispose());
this.items.splice(index, count);
}
update() {
// replace the playlist items being displayed, if necessary
const playlist = this.player_.playlist();
if (this.items.length !== playlist.length) {

@@ -343,3 +364,2 @@ // if the menu is currently empty or the state is obviously out

}
for (let i = 0; i < this.items.length; i++) {

@@ -352,17 +372,13 @@ if (this.items[i].item !== playlist[i]) {

}
} // the playlist itself is unchanged so just update the selection
}
// the playlist itself is unchanged so just update the selection
const currentItem = this.player_.playlist.currentItem();
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (i === currentItem) {
addSelectedClass(item);
if (document.activeElement !== item.el()) {
dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
videojs__default["default"].dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
}
notUpNext(item);

@@ -378,50 +394,18 @@ } else if (i === currentItem + 1) {

}
}
/**
* Returns a boolean indicating whether an element has child elements.
*
* Note that this is distinct from whether it has child _nodes_.
*
* @param {HTMLElement} el
* A DOM element.
*
* @return {boolean}
* Whether the element has child elements.
*/
videojs__default["default"].registerComponent('PlaylistMenu', PlaylistMenu);
const hasChildEls = el => {
for (let i = 0; i < el.childNodes.length; i++) {
if (dom.isEl(el.childNodes[i])) {
return true;
}
}
return false;
// see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document.createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
};
/**
* Finds the first empty root element.
*
* @param {string} className
* An HTML class name to search for.
*
* @return {HTMLElement}
* A DOM element to use as the root for a playlist.
*/
const Plugin = videojs__default["default"].getPlugin('plugin');
const findRoot = className => {
const all = document.querySelectorAll('.' + className);
let el;
for (let i = 0; i < all.length; i++) {
if (!hasChildEls(all[i])) {
el = all[i];
break;
}
}
return el;
};
/**

@@ -443,60 +427,71 @@ * Initialize the plugin on a player.

*/
class PlaylistUI extends Plugin {
constructor(player, options) {
super(player, options);
if (!player.usingPlugin('playlist')) {
player.log.error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
return;
}
options = this.options_ = videojs__default["default"].obj.merge(defaults, options);
if (!videojs__default["default"].dom.isEl(options.el)) {
options.el = this.findRoot_(options.className);
}
const playlistUi = function (options) {
const player = this;
if (!player.playlist) {
throw new Error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
// Expose the playlist menu component on the player as well as the plugin
// This is a bit of an anti-pattern, but it's been that way forever and
// there are likely to be integrations relying on it.
this.playlistMenu = player.playlistMenu = new PlaylistMenu(player, options);
}
if (dom.isEl(options)) {
videojs__default["default"].log.warn('videojs-playlist-ui: Passing an element directly to playlistUi() is deprecated, use the "el" option instead!');
options = {
el: options
};
/**
* Dispose the plugin.
*/
dispose() {
super.dispose();
this.playlistMenu.dispose();
}
options = merge(defaults, options); // If the player is already using this plugin, remove the pre-existing
// PlaylistMenu, but retain the element and its location in the DOM because
// it will be re-used.
if (player.playlistMenu) {
const el = player.playlistMenu.el(); // Catch cases where the menu may have been disposed elsewhere or the
// element removed from the DOM.
if (el) {
const parentNode = el.parentNode;
const nextSibling = el.nextSibling; // Disposing the menu will remove `el` from the DOM, but we need to
// empty it ourselves to be sure.
player.playlistMenu.dispose();
dom.emptyEl(el); // Put the element back in its place.
if (nextSibling) {
parentNode.insertBefore(el, nextSibling);
} else {
parentNode.appendChild(el);
/**
* Returns a boolean indicating whether an element has child elements.
*
* Note that this is distinct from whether it has child _nodes_.
*
* @param {HTMLElement} el
* A DOM element.
*
* @return {boolean}
* Whether the element has child elements.
*/
hasChildEls_(el) {
for (let i = 0; i < el.childNodes.length; i++) {
if (videojs__default["default"].dom.isEl(el.childNodes[i])) {
return true;
}
options.el = el;
}
return false;
}
if (!dom.isEl(options.el)) {
options.el = findRoot(options.className);
/**
* Finds the first empty root element.
*
* @param {string} className
* An HTML class name to search for.
*
* @return {HTMLElement}
* A DOM element to use as the root for a playlist.
*/
findRoot_(className) {
const all = document.querySelectorAll('.' + className);
for (let i = 0; i < all.length; i++) {
if (!this.hasChildEls_(all[i])) {
return all[i];
}
}
}
}
videojs__default["default"].registerPlugin('playlistUi', PlaylistUI);
PlaylistUI.VERSION = version;
player.playlistMenu = new PlaylistMenu(player, options);
}; // register components
return PlaylistUI;
videojs__default["default"].registerComponent('PlaylistMenu', PlaylistMenu);
videojs__default["default"].registerComponent('PlaylistMenuItem', PlaylistMenuItem); // register the plugin
videojs__default["default"].registerPlugin('playlistUi', playlistUi);
playlistUi.VERSION = version;
return playlistUi;
}));

@@ -1,2 +0,2 @@

/*! @name videojs-playlist-ui @version 4.2.1 @license Apache-2.0 */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("video.js")):"function"==typeof define&&define.amd?define(["video.js"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).videojsPlaylistUi=e(t.videojs)}(this,(function(t){"use strict";function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var s=e(t);const i=s.default.dom,l=s.default.obj?s.default.obj.merge:s.default.mergeOptions,a=s.default.time?s.default.time.formatTime:s.default.formatTime,n={className:"vjs-playlist",playOnSelect:!1,supportsCssPointerEvents:(()=>{const t=document.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents})()},o=function(t){t.addClass("vjs-selected")},d=function(t){t.removeClass("vjs-selected"),t.thumbnail&&i.removeClass(t.thumbnail,"vjs-playlist-now-playing")},r=function(t){t.removeClass("vjs-up-next")},c=s.default.getComponent("Component");class p extends c{constructor(t,e,s){if(!e.item)throw new Error("Cannot construct a PlaylistMenuItem without an item option");e.showDescription=s.showDescription,super(t,e),this.item=e.item,this.playOnSelect=s.playOnSelect,this.emitTapEvents(),this.on(["click","tap"],this.switchPlaylistItem_),this.on("keydown",this.handleKeyDown_)}handleKeyDown_(t){13!==t.which&&32!==t.which||this.switchPlaylistItem_()}switchPlaylistItem_(t){this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item)),this.playOnSelect&&this.player_.play()}createEl(){const t=document.createElement("li"),e=this.options_.item,s=this.options_.showDescription;if("object"==typeof e.data){Object.keys(e.data).forEach((s=>{const i=e.data[s];t.dataset[s]=i}))}if(t.className="vjs-playlist-item",t.setAttribute("tabIndex",0),this.thumbnail=function(t){if(!t){const t=document.createElement("div");return t.className="vjs-playlist-thumbnail vjs-playlist-thumbnail-placeholder",t}const e=document.createElement("picture");if(e.className="vjs-playlist-thumbnail","string"==typeof t){const s=document.createElement("img");s.loading="lazy",s.src=t,s.alt="",e.appendChild(s)}else{for(let s=0;s<t.length-1;s++){const i=t[s],l=document.createElement("source");for(const t in i)l[t]=i[t];e.appendChild(l)}const s=t[t.length-1],i=document.createElement("img");i.loading="lazy",i.alt="";for(const t in s)i[t]=s[t];e.appendChild(i)}return e}(e.thumbnail),t.appendChild(this.thumbnail),e.duration){const s=document.createElement("time"),i=a(e.duration);s.className="vjs-playlist-duration",s.setAttribute("datetime","PT0H0M"+e.duration+"S"),s.appendChild(document.createTextNode(i)),t.appendChild(s)}const i=document.createElement("span"),l=this.localize("Now Playing");i.className="vjs-playlist-now-playing-text",i.appendChild(document.createTextNode(l)),i.setAttribute("title",l),this.thumbnail.appendChild(i);const n=document.createElement("div");n.className="vjs-playlist-title-container",this.thumbnail.appendChild(n);const o=document.createElement("span"),d=this.localize("Up Next");o.className="vjs-up-next-text",o.appendChild(document.createTextNode(d)),o.setAttribute("title",d),n.appendChild(o);const r=document.createElement("cite"),c=e.name||this.localize("Untitled Video");if(r.className="vjs-playlist-name",r.appendChild(document.createTextNode(c)),r.setAttribute("title",c),n.appendChild(r),this.thumbnail.getElementsByTagName("img").alt=c,s){const t=document.createElement("div"),s=e.description||"";t.className="vjs-playlist-description",t.appendChild(document.createTextNode(s)),t.setAttribute("title",s),n.appendChild(t)}return t}}class h extends c{constructor(t,e){if(!t.playlist)throw new Error("videojs-playlist is required for the playlist component");super(t,e),this.items=[],e.horizontal?this.addClass("vjs-playlist-horizontal"):this.addClass("vjs-playlist-vertical"),e.supportsCssPointerEvents&&this.addClass("vjs-csspointerevents"),this.createPlaylist_(),s.default.browser.TOUCH_ENABLED||this.addClass("vjs-mouse"),this.on(t,["loadstart","playlistchange","playlistsorted"],(t=>{this.update()})),this.on(t,"adstart",(()=>{this.addClass("vjs-ad-playing")})),this.on(t,"adend",(()=>{this.removeClass("vjs-ad-playing")})),this.on("dispose",(()=>{this.empty_(),t.playlistMenu=null})),this.on(t,"dispose",(()=>{this.dispose()}))}createEl(){return i.createEl("div",{className:this.options_.className})}empty_(){this.items&&this.items.length&&(this.items.forEach((t=>t.dispose())),this.items.length=0)}createPlaylist_(){const t=this.player_.playlist()||[];let e=this.el_.querySelector(".vjs-playlist-item-list"),s=this.el_.querySelector(".vjs-playlist-ad-overlay");e||(e=document.createElement("ol"),e.className="vjs-playlist-item-list",this.el_.appendChild(e)),this.empty_();for(let s=0;s<t.length;s++){const i=new p(this.player_,{item:t[s]},this.options_);this.items.push(i),e.appendChild(i.el_)}s||(s=document.createElement("li"),s.className="vjs-playlist-ad-overlay"),e.appendChild(s);const l=this.player_.playlist.currentItem();if(this.items.length&&l>=0){o(this.items[l]);const t=this.items[l].$(".vjs-playlist-thumbnail");t&&i.addClass(t,"vjs-playlist-now-playing")}}update(){const t=this.player_.playlist();if(this.items.length!==t.length)return void this.createPlaylist_();for(let e=0;e<this.items.length;e++)if(this.items[e].item!==t[e])return void this.createPlaylist_();const e=this.player_.playlist.currentItem();for(let t=0;t<this.items.length;t++){const s=this.items[t];t===e?(o(s),document.activeElement!==s.el()&&i.addClass(s.thumbnail,"vjs-playlist-now-playing"),r(s)):t===e+1?(d(s),s.addClass("vjs-up-next")):(d(s),r(s))}}}const m=t=>{for(let e=0;e<t.childNodes.length;e++)if(i.isEl(t.childNodes[e]))return!0;return!1},u=function(t){const e=this;if(!e.playlist)throw new Error("videojs-playlist plugin is required by the videojs-playlist-ui plugin");if(i.isEl(t)&&(s.default.log.warn('videojs-playlist-ui: Passing an element directly to playlistUi() is deprecated, use the "el" option instead!'),t={el:t}),t=l(n,t),e.playlistMenu){const s=e.playlistMenu.el();if(s){const l=s.parentNode,a=s.nextSibling;e.playlistMenu.dispose(),i.emptyEl(s),a?l.insertBefore(s,a):l.appendChild(s),t.el=s}}i.isEl(t.el)||(t.el=(t=>{const e=document.querySelectorAll("."+t);let s;for(let t=0;t<e.length;t++)if(!m(e[t])){s=e[t];break}return s})(t.className)),e.playlistMenu=new h(e,t)};return s.default.registerComponent("PlaylistMenu",h),s.default.registerComponent("PlaylistMenuItem",p),s.default.registerPlugin("playlistUi",u),u.VERSION="4.2.1",u}));
/*! @name videojs-playlist-ui @version 5.0.0 @license Apache-2.0 */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("video.js")):"function"==typeof define&&define.amd?define(["video.js"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).videojsPlaylistUi=e(t.videojs)}(this,(function(t){"use strict";function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var s=e(t);const i=s.default.getComponent("Component");class l extends i{constructor(t,e,s){if(!e.item)throw new Error("Cannot construct a PlaylistMenuItem without an item option");e.showDescription=s.showDescription,super(t,e),this.item=e.item,this.playOnSelect=s.playOnSelect,this.emitTapEvents(),this.on(["click","tap"],this.switchPlaylistItem_),this.on("keydown",this.handleKeyDown_)}handleKeyDown_(t){13!==t.which&&32!==t.which||this.switchPlaylistItem_()}switchPlaylistItem_(t){this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item)),this.playOnSelect&&this.player_.play()}createEl(){const t=document.createElement("li"),e=this.options_.item,i=this.options_.showDescription;if("object"==typeof e.data){Object.keys(e.data).forEach((s=>{const i=e.data[s];t.dataset[s]=i}))}if(t.className="vjs-playlist-item",t.setAttribute("tabIndex",0),this.thumbnail=function(t){if(!t){const t=document.createElement("div");return t.className="vjs-playlist-thumbnail vjs-playlist-thumbnail-placeholder",t}const e=document.createElement("picture");if(e.className="vjs-playlist-thumbnail","string"==typeof t){const s=document.createElement("img");s.loading="lazy",s.src=t,s.alt="",e.appendChild(s)}else{for(let s=0;s<t.length-1;s++){const i=t[s],l=document.createElement("source");for(const t in i)l[t]=i[t];e.appendChild(l)}const s=t[t.length-1],i=document.createElement("img");i.loading="lazy",i.alt="";for(const t in s)i[t]=s[t];e.appendChild(i)}return e}(e.thumbnail),t.appendChild(this.thumbnail),e.duration){const i=document.createElement("time"),l=s.default.time.formatTime(e.duration);i.className="vjs-playlist-duration",i.setAttribute("datetime","PT0H0M"+e.duration+"S"),i.appendChild(document.createTextNode(l)),t.appendChild(i)}const l=document.createElement("span"),n=this.localize("Now Playing");l.className="vjs-playlist-now-playing-text",l.appendChild(document.createTextNode(n)),l.setAttribute("title",n),this.thumbnail.appendChild(l);const a=document.createElement("div");a.className="vjs-playlist-title-container",this.thumbnail.appendChild(a);const o=document.createElement("span"),d=this.localize("Up Next");o.className="vjs-up-next-text",o.appendChild(document.createTextNode(d)),o.setAttribute("title",d),a.appendChild(o);const r=document.createElement("cite"),c=e.name||this.localize("Untitled Video");if(r.className="vjs-playlist-name",r.appendChild(document.createTextNode(c)),r.setAttribute("title",c),a.appendChild(r),this.thumbnail.getElementsByTagName("img").alt=c,i){const t=document.createElement("div"),s=e.description||"";t.className="vjs-playlist-description",t.appendChild(document.createTextNode(s)),t.setAttribute("title",s),a.appendChild(t)}return t}}s.default.registerComponent("PlaylistMenuItem",l);const n=function(t){t.addClass("vjs-selected")},a=function(t){t.removeClass("vjs-selected"),t.thumbnail&&s.default.dom.removeClass(t.thumbnail,"vjs-playlist-now-playing")},o=function(t){t.removeClass("vjs-up-next")},d=s.default.getComponent("Component");class r extends d{constructor(t,e){super(t,e),this.items=[],e.horizontal?this.addClass("vjs-playlist-horizontal"):this.addClass("vjs-playlist-vertical"),e.supportsCssPointerEvents&&this.addClass("vjs-csspointerevents"),this.createPlaylist_(),s.default.browser.TOUCH_ENABLED||this.addClass("vjs-mouse"),this.on(t,["loadstart","playlistchange","playlistsorted"],(t=>{"playlistchange"===t.type&&["add","remove"].includes(t.action)||this.update()})),this.on(t,["playlistadd"],(t=>this.addItems_(t.index,t.count))),this.on(t,["playlistremove"],(t=>this.removeItems_(t.index,t.count))),this.on(t,"adstart",(()=>{this.addClass("vjs-ad-playing")})),this.on(t,"adend",(()=>{this.removeClass("vjs-ad-playing")})),this.on("dispose",(()=>{this.empty_(),t.playlistMenu=null})),this.on(t,"dispose",(()=>{this.dispose()}))}createEl(){return s.default.dom.createEl("div",{className:this.options_.className})}empty_(){this.items&&this.items.length&&(this.items.forEach((t=>t.dispose())),this.items.length=0)}createPlaylist_(){const t=this.player_.playlist()||[];let e=this.el_.querySelector(".vjs-playlist-item-list"),i=this.el_.querySelector(".vjs-playlist-ad-overlay");e||(e=document.createElement("ol"),e.className="vjs-playlist-item-list",this.el_.appendChild(e)),this.empty_();for(let s=0;s<t.length;s++){const i=new l(this.player_,{item:t[s]},this.options_);this.items.push(i),e.appendChild(i.el_)}i||(i=document.createElement("li"),i.className="vjs-playlist-ad-overlay"),e.appendChild(i);const a=this.player_.playlist.currentItem();if(this.items.length&&a>=0){n(this.items[a]);const t=this.items[a].$(".vjs-playlist-thumbnail");t&&s.default.dom.addClass(t,"vjs-playlist-now-playing")}}addItems_(t,e){const s=this.player_.playlist().slice(t,e+t);if(!s.length)return;const i=this.el_.querySelector(".vjs-playlist-item-list"),n=this.el_.querySelectorAll(".vjs-playlist-item")[t]||null,a=s.map((t=>{const e=new l(this.player_,{item:t},this.options_);return i.insertBefore(e.el_,n),e}));this.items.splice(t,0,...a)}removeItems_(t,e){const s=this.items.slice(t,e+t);s.length&&(s.forEach((t=>t.dispose())),this.items.splice(t,e))}update(){const t=this.player_.playlist();if(this.items.length!==t.length)return void this.createPlaylist_();for(let e=0;e<this.items.length;e++)if(this.items[e].item!==t[e])return void this.createPlaylist_();const e=this.player_.playlist.currentItem();for(let t=0;t<this.items.length;t++){const i=this.items[t];t===e?(n(i),document.activeElement!==i.el()&&s.default.dom.addClass(i.thumbnail,"vjs-playlist-now-playing"),o(i)):t===e+1?(a(i),i.addClass("vjs-up-next")):(a(i),o(i))}}}s.default.registerComponent("PlaylistMenu",r);const c={className:"vjs-playlist",playOnSelect:!1,supportsCssPointerEvents:(()=>{const t=document.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents})()},p=s.default.getPlugin("plugin");class h extends p{constructor(t,e){super(t,e),t.usingPlugin("playlist")?(e=this.options_=s.default.obj.merge(c,e),s.default.dom.isEl(e.el)||(e.el=this.findRoot_(e.className)),this.playlistMenu=t.playlistMenu=new r(t,e)):t.log.error("videojs-playlist plugin is required by the videojs-playlist-ui plugin")}dispose(){super.dispose(),this.playlistMenu.dispose()}hasChildEls_(t){for(let e=0;e<t.childNodes.length;e++)if(s.default.dom.isEl(t.childNodes[e]))return!0;return!1}findRoot_(t){const e=document.querySelectorAll("."+t);for(let t=0;t<e.length;t++)if(!this.hasChildEls_(e[t]))return e[t]}}return s.default.registerPlugin("playlistUi",h),h.VERSION="5.0.0",h}));
{
"name": "videojs-playlist-ui",
"version": "4.2.1",
"version": "5.0.0",
"author": "Brightcove, Inc.",

@@ -15,6 +15,3 @@ "description": "A user interface for the videojs-playlist API",

"build": "npm-run-all -p build:*",
"build:css": "npm-run-all build:css:sass build:css:copy-vertical build:css:copy-no-prefix",
"build:css:copy-no-prefix": "shx cp dist/videojs-playlist-ui.css dist/videojs-playlist-ui.vertical.no-prefix.css",
"build:css:copy-vertical": "shx cp dist/videojs-playlist-ui.css dist/videojs-playlist-ui.vertical.css",
"build:css:sass": "node-sass src/plugin.scss dist/videojs-playlist-ui.css --output-style=compressed --linefeed=lf",
"build:css": "sass src/plugin.scss dist/videojs-playlist-ui.css --style compressed",
"build:js": "rollup -c scripts/rollup.config.js",

@@ -31,6 +28,5 @@ "build:lang": "vjslang --dir dist/lang",

"update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
"preversion": "npm test",
"version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
"watch": "npm-run-all -p watch:*",
"watch:css": "npm run build:css:sass -- -w",
"watch:css": "npm run build:css -- -w",
"watch:js": "npm run build:js -- -w",

@@ -45,29 +41,31 @@ "posttest": "shx cat test/dist/coverage/text.txt",

"dependencies": {
"global": "^4.4.0",
"video.js": "^6 || ^7 || ^8"
"global": "^4.4.0"
},
"devDependencies": {
"conventional-changelog-cli": "^2.0.31",
"conventional-changelog-cli": "^2.2.2",
"conventional-changelog-videojs": "^3.0.2",
"doctoc": "^1.3.1",
"doctoc": "^2.2.1",
"husky": "^1.3.1",
"karma": "^4.4.1",
"lint-staged": "^8.2.1",
"node-sass": "^4.13.1",
"karma": "^6.4.2",
"lint-staged": "^13.2.2",
"not-prerelease": "^1.0.1",
"npm-merge-driver-install": "^1.0.0",
"npm-merge-driver-install": "^3.0.0",
"npm-run-all": "^4.1.5",
"pkg-ok": "^2.2.0",
"postcss-cli": "^6.1.3",
"rollup": "^2.61.1",
"sass": "^1.62.1",
"shx": "^0.3.2",
"sinon": "^6.1.5",
"videojs-generate-karma-config": "^5.3.1",
"videojs-generate-postcss-config": "~2.0.1",
"video.js": "^8.0.0",
"videojs-generate-karma-config": "^8.0.1",
"videojs-generate-rollup-config": "^7.0.0",
"videojs-generator-verify": "^4.0.1",
"videojs-languages": "^2.0.0",
"videojs-playlist": "^4.3.1",
"videojs-languages": "^1.0.0",
"videojs-playlist": "^5.1.0",
"videojs-standard": "^9.0.1"
},
"peerDependencies": {
"video.js": "^8.0.0",
"videojs-playlist": "^5.1.0"
},
"main": "dist/videojs-playlist-ui.cjs.js",

@@ -101,3 +99,3 @@ "module": "dist/videojs-playlist-ui.es.js",

"README.md": [
"npm run docs:toc",
"npm run docs",
"git add"

@@ -104,0 +102,0 @@ ]

# videojs-playlist-ui
[![Build Status](https://travis-ci.org/brightcove/videojs-playlist-ui.svg?branch=master)](https://travis-ci.org/brightcove/videojs-playlist-ui)
[![Greenkeeper badge](https://badges.greenkeeper.io/brightcove/videojs-playlist-ui.svg)](https://greenkeeper.io/)
[![Slack Status](http://slack.videojs.com/badge.svg)](http://slack.videojs.com)
[![NPM](https://nodei.co/npm/videojs-playlist-ui.png?downloads=true&downloadRank=true)](https://nodei.co/npm/videojs-playlist-ui/)

@@ -16,3 +12,2 @@

- [Getting Started](#getting-started)

@@ -35,9 +30,12 @@ - [Root Element](#root-element)

```html
<!-- Include the playlist menu styles somewhere in your page: -->
<link href="videojs-playlist-ui.vertical.css" rel="stylesheet">
<!-- Include the playlist menu styles somewhere in your page -->
<link href="videojs-playlist-ui.css" rel="stylesheet">
<!-- The playlist menu will be built automatically in here: -->
<!-- Your player will be created here: -->
<video-js data-setup='{}' controls></video-js>
<!-- The playlist menu will be built automatically in here -->
<div class="vjs-playlist"></div>
<!-- Include video.js, the videojs-playlist plugin and this plugin: -->
<!-- Include video.js, the videojs-playlist plugin and this plugin -->
<script src="video.js"></script>

@@ -48,4 +46,7 @@ <script src="videojs-playlist.js"></script>

<script>
// Initialize the plugin and build the playlist!
videojs(document.querySelector('video')).playlistUi();
// Initialize the player
const player = videojs(document.querySelector('video-js'));
// Initialize the plugin and render the playlist
player.playlistUi();
</script>

@@ -84,4 +85,2 @@ ```

> **NOTE:** Previously, the plugin supported passing the element in lieu of passing options. That feature is deprecated and will be removed in 4.0. Please use the `el` option going forward.
## Other Options

@@ -112,4 +111,4 @@

[components]: https://github.com/videojs/video.js/blob/master/docs/guides/components.md
[components-options]: https://github.com/videojs/video.js/blob/master/docs/guides/options.md#component-options
[components]: https://videojs.com/guides/components/
[components-options]: https://videojs.com/guides/components/#using-options
[contrib-ads]: https://github.com/videojs/videojs-contrib-ads
import document from 'global/document';
import videojs from 'video.js';
import {version as VERSION} from '../package.json';
import PlaylistMenu from './playlist-menu';
const dom = videojs.dom;
const merge = videojs.obj ? videojs.obj.merge : videojs.mergeOptions;
const formatTime = videojs.time ? videojs.time.formatTime : videojs.formatTime;
// see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js

@@ -23,393 +20,5 @@ const supportsCssPointerEvents = (() => {

// we don't add `vjs-playlist-now-playing` in addSelectedClass
// so it won't conflict with `vjs-icon-play
// since it'll get added when we mouse out
const addSelectedClass = function(el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function(el) {
el.removeClass('vjs-selected');
const Plugin = videojs.getPlugin('plugin');
if (el.thumbnail) {
dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function(el) {
el.addClass('vjs-up-next');
};
const notUpNext = function(el) {
el.removeClass('vjs-up-next');
};
const createThumbnail = function(thumbnail) {
if (!thumbnail) {
const placeholder = document.createElement('div');
placeholder.className = 'vjs-playlist-thumbnail vjs-playlist-thumbnail-placeholder';
return placeholder;
}
const picture = document.createElement('picture');
picture.className = 'vjs-playlist-thumbnail';
if (typeof thumbnail === 'string') {
// simple thumbnails
const img = document.createElement('img');
img.loading = 'lazy';
img.src = thumbnail;
img.alt = '';
picture.appendChild(img);
} else {
// responsive thumbnails
// additional variations of a <picture> are specified as
// <source> elements
for (let i = 0; i < thumbnail.length - 1; i++) {
const variant = thumbnail[i];
const source = document.createElement('source');
// transfer the properties of each variant onto a <source>
for (const prop in variant) {
source[prop] = variant[prop];
}
picture.appendChild(source);
}
// the default version of a <picture> is specified by an <img>
const variant = thumbnail[thumbnail.length - 1];
const img = document.createElement('img');
img.loading = 'lazy';
img.alt = '';
for (const prop in variant) {
img[prop] = variant[prop];
}
picture.appendChild(img);
}
return picture;
};
const Component = videojs.getComponent('Component');
class PlaylistMenuItem extends Component {
constructor(player, playlistItem, settings) {
if (!playlistItem.item) {
throw new Error('Cannot construct a PlaylistMenuItem without an item option');
}
playlistItem.showDescription = settings.showDescription;
super(player, playlistItem);
this.item = playlistItem.item;
this.playOnSelect = settings.playOnSelect;
this.emitTapEvents();
this.on(['click', 'tap'], this.switchPlaylistItem_);
this.on('keydown', this.handleKeyDown_);
}
handleKeyDown_(event) {
// keycode 13 is <Enter>
// keycode 32 is <Space>
if (event.which === 13 || event.which === 32) {
this.switchPlaylistItem_();
}
}
switchPlaylistItem_(event) {
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
if (this.playOnSelect) {
this.player_.play();
}
}
createEl() {
const li = document.createElement('li');
const item = this.options_.item;
const showDescription = this.options_.showDescription;
if (typeof item.data === 'object') {
const dataKeys = Object.keys(item.data);
dataKeys.forEach(key => {
const value = item.data[key];
li.dataset[key] = value;
});
}
li.className = 'vjs-playlist-item';
li.setAttribute('tabIndex', 0);
// Thumbnail image
this.thumbnail = createThumbnail(item.thumbnail);
li.appendChild(this.thumbnail);
// Duration
if (item.duration) {
const duration = document.createElement('time');
const time = formatTime(item.duration);
duration.className = 'vjs-playlist-duration';
duration.setAttribute('datetime', 'PT0H0M' + item.duration + 'S');
duration.appendChild(document.createTextNode(time));
li.appendChild(duration);
}
// Now playing
const nowPlayingEl = document.createElement('span');
const nowPlayingText = this.localize('Now Playing');
nowPlayingEl.className = 'vjs-playlist-now-playing-text';
nowPlayingEl.appendChild(document.createTextNode(nowPlayingText));
nowPlayingEl.setAttribute('title', nowPlayingText);
this.thumbnail.appendChild(nowPlayingEl);
// Title container contains title and "up next"
const titleContainerEl = document.createElement('div');
titleContainerEl.className = 'vjs-playlist-title-container';
this.thumbnail.appendChild(titleContainerEl);
// Up next
const upNextEl = document.createElement('span');
const upNextText = this.localize('Up Next');
upNextEl.className = 'vjs-up-next-text';
upNextEl.appendChild(document.createTextNode(upNextText));
upNextEl.setAttribute('title', upNextText);
titleContainerEl.appendChild(upNextEl);
// Video title
const titleEl = document.createElement('cite');
const titleText = item.name || this.localize('Untitled Video');
titleEl.className = 'vjs-playlist-name';
titleEl.appendChild(document.createTextNode(titleText));
titleEl.setAttribute('title', titleText);
titleContainerEl.appendChild(titleEl);
// Populate thumbnails alt with the video title
this.thumbnail.getElementsByTagName('img').alt = titleText;
// We add thumbnail video description only if specified in playlist options
if (showDescription) {
const descriptionEl = document.createElement('div');
const descriptionText = item.description || '';
descriptionEl.className = 'vjs-playlist-description';
descriptionEl.appendChild(document.createTextNode(descriptionText));
descriptionEl.setAttribute('title', descriptionText);
titleContainerEl.appendChild(descriptionEl);
}
return li;
}
}
class PlaylistMenu extends Component {
constructor(player, options) {
if (!player.playlist) {
throw new Error('videojs-playlist is required for the playlist component');
}
super(player, options);
this.items = [];
if (options.horizontal) {
this.addClass('vjs-playlist-horizontal');
} else {
this.addClass('vjs-playlist-vertical');
}
// If CSS pointer events aren't supported, we have to prevent
// clicking on playlist items during ads with slightly more
// invasive techniques. Details in the stylesheet.
if (options.supportsCssPointerEvents) {
this.addClass('vjs-csspointerevents');
}
this.createPlaylist_();
if (!videojs.browser.TOUCH_ENABLED) {
this.addClass('vjs-mouse');
}
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], (event) => {
this.update();
});
// Keep track of whether an ad is playing so that the menu
// appearance can be adapted appropriately
this.on(player, 'adstart', () => {
this.addClass('vjs-ad-playing');
});
this.on(player, 'adend', () => {
this.removeClass('vjs-ad-playing');
});
this.on('dispose', () => {
this.empty_();
player.playlistMenu = null;
});
this.on(player, 'dispose', () => {
this.dispose();
});
}
createEl() {
return dom.createEl('div', {className: this.options_.className});
}
empty_() {
if (this.items && this.items.length) {
this.items.forEach(i => i.dispose());
this.items.length = 0;
}
}
createPlaylist_() {
const playlist = this.player_.playlist() || [];
let list = this.el_.querySelector('.vjs-playlist-item-list');
let overlay = this.el_.querySelector('.vjs-playlist-ad-overlay');
if (!list) {
list = document.createElement('ol');
list.className = 'vjs-playlist-item-list';
this.el_.appendChild(list);
}
this.empty_();
// create new items
for (let i = 0; i < playlist.length; i++) {
const item = new PlaylistMenuItem(this.player_, {
item: playlist[i]
}, this.options_);
this.items.push(item);
list.appendChild(item.el_);
}
// Inject the ad overlay. We use this element to block clicks during ad
// playback and darken the menu to indicate inactivity
if (!overlay) {
overlay = document.createElement('li');
overlay.className = 'vjs-playlist-ad-overlay';
list.appendChild(overlay);
} else {
// Move overlay to end of list
list.appendChild(overlay);
}
// select the current playlist item
const selectedIndex = this.player_.playlist.currentItem();
if (this.items.length && selectedIndex >= 0) {
addSelectedClass(this.items[selectedIndex]);
const thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail');
if (thumbnail) {
dom.addClass(thumbnail, 'vjs-playlist-now-playing');
}
}
}
update() {
// replace the playlist items being displayed, if necessary
const playlist = this.player_.playlist();
if (this.items.length !== playlist.length) {
// if the menu is currently empty or the state is obviously out
// of date, rebuild everything.
this.createPlaylist_();
return;
}
for (let i = 0; i < this.items.length; i++) {
if (this.items[i].item !== playlist[i]) {
// if any of the playlist items have changed, rebuild the
// entire playlist
this.createPlaylist_();
return;
}
}
// the playlist itself is unchanged so just update the selection
const currentItem = this.player_.playlist.currentItem();
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (i === currentItem) {
addSelectedClass(item);
if (document.activeElement !== item.el()) {
dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
}
notUpNext(item);
} else if (i === currentItem + 1) {
removeSelectedClass(item);
upNext(item);
} else {
removeSelectedClass(item);
notUpNext(item);
}
}
}
}
/**
* Returns a boolean indicating whether an element has child elements.
*
* Note that this is distinct from whether it has child _nodes_.
*
* @param {HTMLElement} el
* A DOM element.
*
* @return {boolean}
* Whether the element has child elements.
*/
const hasChildEls = (el) => {
for (let i = 0; i < el.childNodes.length; i++) {
if (dom.isEl(el.childNodes[i])) {
return true;
}
}
return false;
};
/**
* Finds the first empty root element.
*
* @param {string} className
* An HTML class name to search for.
*
* @return {HTMLElement}
* A DOM element to use as the root for a playlist.
*/
const findRoot = (className) => {
const all = document.querySelectorAll('.' + className);
let el;
for (let i = 0; i < all.length; i++) {
if (!hasChildEls(all[i])) {
el = all[i];
break;
}
}
return el;
};
/**
* Initialize the plugin on a player.

@@ -430,60 +39,76 @@ *

*/
const playlistUi = function(options) {
const player = this;
class PlaylistUI extends Plugin {
if (!player.playlist) {
throw new Error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
}
constructor(player, options) {
super(player, options);
if (dom.isEl(options)) {
videojs.log.warn('videojs-playlist-ui: Passing an element directly to playlistUi() is deprecated, use the "el" option instead!');
options = {el: options};
}
if (!player.usingPlugin('playlist')) {
player.log.error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
return;
}
options = merge(defaults, options);
options = this.options_ = videojs.obj.merge(defaults, options);
// If the player is already using this plugin, remove the pre-existing
// PlaylistMenu, but retain the element and its location in the DOM because
// it will be re-used.
if (player.playlistMenu) {
const el = player.playlistMenu.el();
if (!videojs.dom.isEl(options.el)) {
options.el = this.findRoot_(options.className);
}
// Catch cases where the menu may have been disposed elsewhere or the
// element removed from the DOM.
if (el) {
const parentNode = el.parentNode;
const nextSibling = el.nextSibling;
// Expose the playlist menu component on the player as well as the plugin
// This is a bit of an anti-pattern, but it's been that way forever and
// there are likely to be integrations relying on it.
this.playlistMenu = player.playlistMenu = new PlaylistMenu(player, options);
}
// Disposing the menu will remove `el` from the DOM, but we need to
// empty it ourselves to be sure.
player.playlistMenu.dispose();
dom.emptyEl(el);
/**
* Dispose the plugin.
*/
dispose() {
super.dispose();
this.playlistMenu.dispose();
}
// Put the element back in its place.
if (nextSibling) {
parentNode.insertBefore(el, nextSibling);
} else {
parentNode.appendChild(el);
/**
* Returns a boolean indicating whether an element has child elements.
*
* Note that this is distinct from whether it has child _nodes_.
*
* @param {HTMLElement} el
* A DOM element.
*
* @return {boolean}
* Whether the element has child elements.
*/
hasChildEls_(el) {
for (let i = 0; i < el.childNodes.length; i++) {
if (videojs.dom.isEl(el.childNodes[i])) {
return true;
}
options.el = el;
}
return false;
}
if (!dom.isEl(options.el)) {
options.el = findRoot(options.className);
/**
* Finds the first empty root element.
*
* @param {string} className
* An HTML class name to search for.
*
* @return {HTMLElement}
* A DOM element to use as the root for a playlist.
*/
findRoot_(className) {
const all = document.querySelectorAll('.' + className);
for (let i = 0; i < all.length; i++) {
if (!this.hasChildEls_(all[i])) {
return all[i];
}
}
}
}
player.playlistMenu = new PlaylistMenu(player, options);
};
videojs.registerPlugin('playlistUi', PlaylistUI);
// register components
videojs.registerComponent('PlaylistMenu', PlaylistMenu);
videojs.registerComponent('PlaylistMenuItem', PlaylistMenuItem);
PlaylistUI.VERSION = VERSION;
// register the plugin
videojs.registerPlugin('playlistUi', playlistUi);
playlistUi.VERSION = VERSION;
export default playlistUi;
export default PlaylistUI;

@@ -5,2 +5,3 @@ /* eslint-disable no-console */

import QUnit from 'qunit';
import sinon from 'sinon';
import videojs from 'video.js';

@@ -38,3 +39,2 @@

const dom = videojs.dom || videojs;
const Html5 = videojs.getTech('Html5');

@@ -47,6 +47,4 @@

function setup() {
const merge = videojs.obj ? videojs.obj.merge : videojs.mergeOptions;
this.oldVideojsBrowser = videojs.browser;
videojs.browser = merge({}, videojs.browser);
videojs.browser = videojs.obj.merge({}, videojs.browser);

@@ -71,4 +69,4 @@ this.fixture = document.querySelector('#qunit-fixture');

// Create two playlist container elements.
this.fixture.appendChild(dom.createEl('div', {className: 'vjs-playlist'}));
this.fixture.appendChild(dom.createEl('div', {className: 'vjs-playlist'}));
this.fixture.appendChild(videojs.dom.createEl('div', {className: 'vjs-playlist'}));
this.fixture.appendChild(videojs.dom.createEl('div', {className: 'vjs-playlist'}));
}

@@ -81,3 +79,3 @@

this.player = null;
dom.emptyEl(this.fixture);
videojs.dom.emptyEl(this.fixture);
}

@@ -92,6 +90,8 @@

QUnit.test('errors if used without the playlist plugin', function(assert) {
assert.throws(function() {
this.player.playlist = null;
this.player.playlistUi();
}, 'threw on init');
sinon.spy(this.player.log, 'error');
this.player.playlist = null;
this.player.playlistUi();
assert.ok(this.player.log.error.calledOnce, 'player.log.error was called');
});

@@ -108,17 +108,4 @@

QUnit.test('can be initialized with an element (deprecated form)', function(assert) {
const elem = dom.createEl('div');
this.player.playlist(playlist);
this.player.playlistUi(elem);
assert.strictEqual(
elem.querySelectorAll('li.vjs-playlist-item').length,
playlist.length,
'created an element for each playlist item'
);
});
QUnit.test('can be initialized with an element', function(assert) {
const elem = dom.createEl('div');
const elem = videojs.dom.createEl('div');

@@ -141,3 +128,3 @@ this.player.playlist(playlist);

// to the next one.
firstEl.appendChild(dom.createEl('div'));
firstEl.appendChild(videojs.dom.createEl('div'));

@@ -156,8 +143,8 @@ this.player.playlist(playlist);

QUnit.test('can look for an element with a custom class that is not already in use', function(assert) {
const firstEl = dom.createEl('div', {className: 'super-playlist'});
const secondEl = dom.createEl('div', {className: 'super-playlist'});
const firstEl = videojs.dom.createEl('div', {className: 'super-playlist'});
const secondEl = videojs.dom.createEl('div', {className: 'super-playlist'});
// Give the firstEl a child, so the plugin thinks it is in use and moves on
// to the next one.
firstEl.appendChild(dom.createEl('div'));
firstEl.appendChild(videojs.dom.createEl('div'));

@@ -656,1 +643,237 @@ this.fixture.appendChild(firstEl);

});
QUnit.module('videojs-playlist-ui: add/remove', {beforeEach: setup, afterEach: teardown});
QUnit.test('adding zero items at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([], 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'correct number of items');
});
QUnit.test('adding one item at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
assert.strictEqual(items[0].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
});
QUnit.test('adding two items at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
assert.strictEqual(items[0].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
assert.strictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'has the correct name in the playlist DOM');
});
QUnit.test('adding one item in the middle of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
assert.strictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
});
QUnit.test('adding two items in the middle of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
assert.strictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
assert.strictEqual(items[2].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'has the correct name in the playlist DOM');
});
QUnit.test('adding one item at the end of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, playlist.length);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
assert.strictEqual(items[2].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
});
QUnit.test('adding two items at the end of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], playlist.length);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
assert.strictEqual(items[2].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
assert.strictEqual(items[3].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'has the correct name in the playlist DOM');
});
QUnit.test('removing zero items at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.remove(0, 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'correct number of items');
});
QUnit.test('removing one item at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
this.player.playlist.remove(0, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'correct number of items');
assert.notStrictEqual(items[0].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
});
QUnit.test('removing two items at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
this.player.playlist.remove(0, 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.notStrictEqual(items[0].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'the added item was properly removed from the DOM');
});
QUnit.test('removing one item in the middle of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
this.player.playlist.remove(1, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'correct number of items');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
});
QUnit.test('removing two items in the middle of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
this.player.playlist.remove(1, 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
assert.strictEqual(items[2], undefined, 'the added item was properly removed from the DOM');
});
QUnit.test('removing one item at the end of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
this.player.playlist.remove(2, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'correct number of items');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
});
QUnit.test('removing two items at the end of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
this.player.playlist.remove(2, 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'correct number of items');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'the added item was properly removed from the DOM');
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc