From c7bf0c5c03a5c7716a39a0f2f5a11681eedbac7f Mon Sep 17 00:00:00 2001 From: Jesper Date: Tue, 10 Mar 2020 09:19:34 +0100 Subject: Fix prototype used for selector matcher function --- src/js/utils/elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/js/utils') diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js index b88aad0c..43f46416 100644 --- a/src/js/utils/elements.js +++ b/src/js/utils/elements.js @@ -221,7 +221,7 @@ export function hasClass(element, className) { // Element matches selector export function matches(element, selector) { - const prototype = { Element }; + const prototype = Element.prototype; function match() { return Array.from(document.querySelectorAll(selector)).includes(this); -- cgit v1.2.3 From 71928443f317e624ab94ff18e207447f06f745ad Mon Sep 17 00:00:00 2001 From: ydylla Date: Mon, 23 Mar 2020 22:50:19 +0100 Subject: silence all internal play promises --- src/js/utils/promise.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/js/utils/promise.js (limited to 'src/js/utils') diff --git a/src/js/utils/promise.js b/src/js/utils/promise.js new file mode 100644 index 00000000..42fcc2c3 --- /dev/null +++ b/src/js/utils/promise.js @@ -0,0 +1,27 @@ +/** + * Returns whether an object is `Promise`-like (i.e. has a `then` method). + * + * @param {Object} value + * An object that may or may not be `Promise`-like. + * + * @return {boolean} + * Whether or not the object is `Promise`-like. + */ +export function isPromise(value) { + return value !== undefined && value !== null && typeof value.then === 'function'; +} + +/** + * Silence a Promise-like object. + * + * This is useful for avoiding non-harmful, but potentially confusing "uncaught + * play promise" rejection error messages. + * + * @param {Object} value + * An object that may or may not be `Promise`-like. + */ +export function silencePromise(value) { + if (isPromise(value)) { + value.then(null, () => {}); + } +} -- cgit v1.2.3 From d06881783d7c8a9faa5d902da5ec33bc74f3aa38 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 30 Mar 2020 17:04:43 +1100 Subject: Formatting fixes --- src/js/utils/elements.js | 2 +- src/js/utils/events.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/js/utils') diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js index bdf18bfd..acff0dd9 100644 --- a/src/js/utils/elements.js +++ b/src/js/utils/elements.js @@ -221,7 +221,7 @@ export function hasClass(element, className) { // Element matches selector export function matches(element, selector) { - const {prototype} = Element; + const { prototype } = Element; function match() { return Array.from(document.querySelectorAll(selector)).includes(this); diff --git a/src/js/utils/events.js b/src/js/utils/events.js index 31571b2d..48300b6b 100644 --- a/src/js/utils/events.js +++ b/src/js/utils/events.js @@ -90,7 +90,7 @@ export function triggerEvent(element, type = '', bubbles = false, detail = {}) { // Create and dispatch the event const event = new CustomEvent(type, { bubbles, - detail: { ...detail, plyr: this,}, + detail: { ...detail, plyr: this }, }); // Dispatch the event -- cgit v1.2.3 From 49ed2cac4eff3ff3eae7a2c72e5280a302906f7d Mon Sep 17 00:00:00 2001 From: Som Meaden Date: Sat, 4 Apr 2020 13:43:51 +1000 Subject: This is a PR to allow for contextual content to be included in fullscreen (or fallback) mode. This means arbitrary elements (extensions to the basic player UI) can be overlaid and remain visible when the player switches to fullscreen. Example use-cases include: - display of video title or other metadata (see the included demo) - alternative access to menu items, such as a searchable captions list (in cases where many hundreds of languages are available) - custom share dialogs - integrated playlists with 'playing next' overlays This approach / PR is just an example of how this feature could work and aims to keep Plyr complexity to a minimum (while enabling some fairly interesting integrations). It utilises a single config option, and does away with the need for injecting bespoke APIs or elements into the player context on a per-project basis. Or trying to mess with what is a pretty slick, but tightly coupled system. For the user: A new `fullscreen.container` attribute is used to provide a container selector. The container must be an ancestor of the player, otherwise it's ignored. When toggling fullscreen mode, this container is now used in place of the player. Hovering over any children of the container is the same as hovering over the controls. The exception is where the player and the child share a common ancestor (that's not the fullscreen container) ... sounds complex but it's not. You can also gain pretty fine control this way with pointer events. Under the hood: it adds a `utils/elements/closest` helper method to find the right ancestor. If found this is returned as the fullscreen target in place of the player container. Fullscreen is instantiated slightly earlier in the setup so this container is available for the `listeners.controls` call. In here we add some more 'mouseenter/mouseleave' listeners to any direct descendants of the container, that aren't also ancestors of the player. And that's it. No extra classes, nothing else. There are some style changes to the demo (top margin on the player) but these would be project specific. Thanks for reading. --- src/js/utils/elements.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src/js/utils') diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js index bdf18bfd..f782fc3e 100644 --- a/src/js/utils/elements.js +++ b/src/js/utils/elements.js @@ -237,6 +237,28 @@ export function matches(element, selector) { return method.call(element, selector); } +// Closest ancestor element matching selector (also tests element itself) +export function closest(element, selector) { + const {prototype} = Element; + + // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill + function closestElement() { + let el = this; + + do { + if (matches.matches(el, selector)) return el; + el = el.parentElement || el.parentNode; + } while (el !== null && el.nodeType === 1); + return null; + } + + const method = + prototype.closest || + closestElement; + + return method.call(element, selector); +} + // Find all elements export function getElements(selector) { return this.elements.container.querySelectorAll(selector); -- cgit v1.2.3 From 502d5977d79148957828cbf313b7ef4c9f31973f Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sat, 11 Apr 2020 16:23:14 +1000 Subject: Converted to 2 space indentation --- src/js/utils/animation.js | 46 +++--- src/js/utils/arrays.js | 16 +-- src/js/utils/browser.js | 10 +- src/js/utils/elements.js | 336 ++++++++++++++++++++++---------------------- src/js/utils/events.js | 144 +++++++++---------- src/js/utils/fetch.js | 58 ++++---- src/js/utils/i18n.js | 50 +++---- src/js/utils/is.js | 78 +++++----- src/js/utils/load-image.js | 18 +-- src/js/utils/load-script.js | 10 +- src/js/utils/load-sprite.js | 104 +++++++------- src/js/utils/numbers.js | 2 +- src/js/utils/objects.js | 40 +++--- src/js/utils/promise.js | 6 +- src/js/utils/strings.js | 75 +++++----- src/js/utils/style.js | 108 +++++++------- src/js/utils/time.js | 36 ++--- src/js/utils/urls.js | 36 ++--- 18 files changed, 584 insertions(+), 589 deletions(-) (limited to 'src/js/utils') diff --git a/src/js/utils/animation.js b/src/js/utils/animation.js index 3f721b5a..d9e7615e 100644 --- a/src/js/utils/animation.js +++ b/src/js/utils/animation.js @@ -5,34 +5,34 @@ import is from './is'; export const transitionEndEvent = (() => { - const element = document.createElement('span'); + const element = document.createElement('span'); - const events = { - WebkitTransition: 'webkitTransitionEnd', - MozTransition: 'transitionend', - OTransition: 'oTransitionEnd otransitionend', - transition: 'transitionend', - }; + const events = { + WebkitTransition: 'webkitTransitionEnd', + MozTransition: 'transitionend', + OTransition: 'oTransitionEnd otransitionend', + transition: 'transitionend', + }; - const type = Object.keys(events).find(event => element.style[event] !== undefined); + const type = Object.keys(events).find(event => element.style[event] !== undefined); - return is.string(type) ? events[type] : false; + return is.string(type) ? events[type] : false; })(); // Force repaint of element export function repaint(element, delay) { - setTimeout(() => { - try { - // eslint-disable-next-line no-param-reassign - element.hidden = true; - - // eslint-disable-next-line no-unused-expressions - element.offsetHeight; - - // eslint-disable-next-line no-param-reassign - element.hidden = false; - } catch (e) { - // Do nothing - } - }, delay); + setTimeout(() => { + try { + // eslint-disable-next-line no-param-reassign + element.hidden = true; + + // eslint-disable-next-line no-unused-expressions + element.offsetHeight; + + // eslint-disable-next-line no-param-reassign + element.hidden = false; + } catch (e) { + // Do nothing + } + }, delay); } diff --git a/src/js/utils/arrays.js b/src/js/utils/arrays.js index 69ef242c..aed951cd 100644 --- a/src/js/utils/arrays.js +++ b/src/js/utils/arrays.js @@ -6,18 +6,18 @@ import is from './is'; // Remove duplicates in an array export function dedupe(array) { - if (!is.array(array)) { - return array; - } + if (!is.array(array)) { + return array; + } - return array.filter((item, index) => array.indexOf(item) === index); + return array.filter((item, index) => array.indexOf(item) === index); } // Get the closest value in an array export function closest(array, value) { - if (!is.array(array) || !array.length) { - return null; - } + if (!is.array(array) || !array.length) { + return null; + } - return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev)); + return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev)); } diff --git a/src/js/utils/browser.js b/src/js/utils/browser.js index 11705074..7b8fa5e2 100644 --- a/src/js/utils/browser.js +++ b/src/js/utils/browser.js @@ -4,11 +4,11 @@ // ========================================================================== const browser = { - isIE: /* @cc_on!@ */ false || !!document.documentMode, - isEdge: window.navigator.userAgent.includes('Edge'), - isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent), - isIPhone: /(iPhone|iPod)/gi.test(navigator.platform), - isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform), + isIE: /* @cc_on!@ */ false || !!document.documentMode, + isEdge: window.navigator.userAgent.includes('Edge'), + isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent), + isIPhone: /(iPhone|iPod)/gi.test(navigator.platform), + isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform), }; export default browser; diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js index acff0dd9..b48fa7e1 100644 --- a/src/js/utils/elements.js +++ b/src/js/utils/elements.js @@ -7,257 +7,257 @@ import { extend } from './objects'; // Wrap an element export function wrap(elements, wrapper) { - // Convert `elements` to an array, if necessary. - const targets = elements.length ? elements : [elements]; - - // Loops backwards to prevent having to clone the wrapper on the - // first element (see `child` below). - Array.from(targets) - .reverse() - .forEach((element, index) => { - const child = index > 0 ? wrapper.cloneNode(true) : wrapper; - // Cache the current parent and sibling. - const parent = element.parentNode; - const sibling = element.nextSibling; - - // Wrap the element (is automatically removed from its current - // parent). - child.appendChild(element); - - // If the element had a sibling, insert the wrapper before - // the sibling to maintain the HTML structure; otherwise, just - // append it to the parent. - if (sibling) { - parent.insertBefore(child, sibling); - } else { - parent.appendChild(child); - } - }); + // Convert `elements` to an array, if necessary. + const targets = elements.length ? elements : [elements]; + + // Loops backwards to prevent having to clone the wrapper on the + // first element (see `child` below). + Array.from(targets) + .reverse() + .forEach((element, index) => { + const child = index > 0 ? wrapper.cloneNode(true) : wrapper; + // Cache the current parent and sibling. + const parent = element.parentNode; + const sibling = element.nextSibling; + + // Wrap the element (is automatically removed from its current + // parent). + child.appendChild(element); + + // If the element had a sibling, insert the wrapper before + // the sibling to maintain the HTML structure; otherwise, just + // append it to the parent. + if (sibling) { + parent.insertBefore(child, sibling); + } else { + parent.appendChild(child); + } + }); } // Set attributes export function setAttributes(element, attributes) { - if (!is.element(element) || is.empty(attributes)) { - return; - } - - // Assume null and undefined attributes should be left out, - // Setting them would otherwise convert them to "null" and "undefined" - Object.entries(attributes) - .filter(([, value]) => !is.nullOrUndefined(value)) - .forEach(([key, value]) => element.setAttribute(key, value)); + if (!is.element(element) || is.empty(attributes)) { + return; + } + + // Assume null and undefined attributes should be left out, + // Setting them would otherwise convert them to "null" and "undefined" + Object.entries(attributes) + .filter(([, value]) => !is.nullOrUndefined(value)) + .forEach(([key, value]) => element.setAttribute(key, value)); } // Create a DocumentFragment export function createElement(type, attributes, text) { - // Create a new - const element = document.createElement(type); + // Create a new + const element = document.createElement(type); - // Set all passed attributes - if (is.object(attributes)) { - setAttributes(element, attributes); - } + // Set all passed attributes + if (is.object(attributes)) { + setAttributes(element, attributes); + } - // Add text node - if (is.string(text)) { - element.innerText = text; - } + // Add text node + if (is.string(text)) { + element.innerText = text; + } - // Return built element - return element; + // Return built element + return element; } // Inaert an element after another export function insertAfter(element, target) { - if (!is.element(element) || !is.element(target)) { - return; - } + if (!is.element(element) || !is.element(target)) { + return; + } - target.parentNode.insertBefore(element, target.nextSibling); + target.parentNode.insertBefore(element, target.nextSibling); } // Insert a DocumentFragment export function insertElement(type, parent, attributes, text) { - if (!is.element(parent)) { - return; - } + if (!is.element(parent)) { + return; + } - parent.appendChild(createElement(type, attributes, text)); + parent.appendChild(createElement(type, attributes, text)); } // Remove element(s) export function removeElement(element) { - if (is.nodeList(element) || is.array(element)) { - Array.from(element).forEach(removeElement); - return; - } + if (is.nodeList(element) || is.array(element)) { + Array.from(element).forEach(removeElement); + return; + } - if (!is.element(element) || !is.element(element.parentNode)) { - return; - } + if (!is.element(element) || !is.element(element.parentNode)) { + return; + } - element.parentNode.removeChild(element); + element.parentNode.removeChild(element); } // Remove all child elements export function emptyElement(element) { - if (!is.element(element)) { - return; - } + if (!is.element(element)) { + return; + } - let { length } = element.childNodes; + let { length } = element.childNodes; - while (length > 0) { - element.removeChild(element.lastChild); - length -= 1; - } + while (length > 0) { + element.removeChild(element.lastChild); + length -= 1; + } } // Replace element export function replaceElement(newChild, oldChild) { - if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) { - return null; - } + if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) { + return null; + } - oldChild.parentNode.replaceChild(newChild, oldChild); + oldChild.parentNode.replaceChild(newChild, oldChild); - return newChild; + return newChild; } // Get an attribute object from a string selector export function getAttributesFromSelector(sel, existingAttributes) { - // For example: - // '.test' to { class: 'test' } - // '#test' to { id: 'test' } - // '[data-test="test"]' to { 'data-test': 'test' } + // For example: + // '.test' to { class: 'test' } + // '#test' to { id: 'test' } + // '[data-test="test"]' to { 'data-test': 'test' } + + if (!is.string(sel) || is.empty(sel)) { + return {}; + } + + const attributes = {}; + const existing = extend({}, existingAttributes); + + sel.split(',').forEach(s => { + // Remove whitespace + const selector = s.trim(); + const className = selector.replace('.', ''); + const stripped = selector.replace(/[[\]]/g, ''); + // Get the parts and value + const parts = stripped.split('='); + const [key] = parts; + const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; + // Get the first character + const start = selector.charAt(0); + + switch (start) { + case '.': + // Add to existing classname + if (is.string(existing.class)) { + attributes.class = `${existing.class} ${className}`; + } else { + attributes.class = className; + } + break; - if (!is.string(sel) || is.empty(sel)) { - return {}; - } + case '#': + // ID selector + attributes.id = selector.replace('#', ''); + break; - const attributes = {}; - const existing = extend({}, existingAttributes); - - sel.split(',').forEach(s => { - // Remove whitespace - const selector = s.trim(); - const className = selector.replace('.', ''); - const stripped = selector.replace(/[[\]]/g, ''); - // Get the parts and value - const parts = stripped.split('='); - const [key] = parts; - const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; - // Get the first character - const start = selector.charAt(0); - - switch (start) { - case '.': - // Add to existing classname - if (is.string(existing.class)) { - attributes.class = `${existing.class} ${className}`; - } else { - attributes.class = className; - } - break; - - case '#': - // ID selector - attributes.id = selector.replace('#', ''); - break; - - case '[': - // Attribute selector - attributes[key] = value; - - break; - - default: - break; - } - }); + case '[': + // Attribute selector + attributes[key] = value; - return extend(existing, attributes); + break; + + default: + break; + } + }); + + return extend(existing, attributes); } // Toggle hidden export function toggleHidden(element, hidden) { - if (!is.element(element)) { - return; - } + if (!is.element(element)) { + return; + } - let hide = hidden; + let hide = hidden; - if (!is.boolean(hide)) { - hide = !element.hidden; - } + if (!is.boolean(hide)) { + hide = !element.hidden; + } - // eslint-disable-next-line no-param-reassign - element.hidden = hide; + // eslint-disable-next-line no-param-reassign + element.hidden = hide; } // Mirror Element.classList.toggle, with IE compatibility for "force" argument export function toggleClass(element, className, force) { - if (is.nodeList(element)) { - return Array.from(element).map(e => toggleClass(e, className, force)); + if (is.nodeList(element)) { + return Array.from(element).map(e => toggleClass(e, className, force)); + } + + if (is.element(element)) { + let method = 'toggle'; + if (typeof force !== 'undefined') { + method = force ? 'add' : 'remove'; } - if (is.element(element)) { - let method = 'toggle'; - if (typeof force !== 'undefined') { - method = force ? 'add' : 'remove'; - } - - element.classList[method](className); - return element.classList.contains(className); - } + element.classList[method](className); + return element.classList.contains(className); + } - return false; + return false; } // Has class name export function hasClass(element, className) { - return is.element(element) && element.classList.contains(className); + return is.element(element) && element.classList.contains(className); } // Element matches selector export function matches(element, selector) { - const { prototype } = Element; + const { prototype } = Element; - function match() { - return Array.from(document.querySelectorAll(selector)).includes(this); - } + function match() { + return Array.from(document.querySelectorAll(selector)).includes(this); + } - const method = - prototype.matches || - prototype.webkitMatchesSelector || - prototype.mozMatchesSelector || - prototype.msMatchesSelector || - match; + const method = + prototype.matches || + prototype.webkitMatchesSelector || + prototype.mozMatchesSelector || + prototype.msMatchesSelector || + match; - return method.call(element, selector); + return method.call(element, selector); } // Find all elements export function getElements(selector) { - return this.elements.container.querySelectorAll(selector); + return this.elements.container.querySelectorAll(selector); } // Find a single element export function getElement(selector) { - return this.elements.container.querySelector(selector); + return this.elements.container.querySelector(selector); } // Set focus and tab focus class export function setFocus(element = null, tabFocus = false) { - if (!is.element(element)) { - return; - } + if (!is.element(element)) { + return; + } - // Set regular focus - element.focus({ preventScroll: true }); + // Set regular focus + element.focus({ preventScroll: true }); - // If we want to mimic keyboard focus via tab - if (tabFocus) { - toggleClass(element, this.config.classNames.tabFocus); - } + // If we want to mimic keyboard focus via tab + if (tabFocus) { + toggleClass(element, this.config.classNames.tabFocus); + } } diff --git a/src/js/utils/events.js b/src/js/utils/events.js index 48300b6b..235eb629 100644 --- a/src/js/utils/events.js +++ b/src/js/utils/events.js @@ -8,110 +8,110 @@ import is from './is'; // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md // https://www.youtube.com/watch?v=NPM6172J22g const supportsPassiveListeners = (() => { - // Test via a getter in the options object to see if the passive property is accessed - let supported = false; - try { - const options = Object.defineProperty({}, 'passive', { - get() { - supported = true; - return null; - }, - }); - window.addEventListener('test', null, options); - window.removeEventListener('test', null, options); - } catch (e) { - // Do nothing - } + // Test via a getter in the options object to see if the passive property is accessed + let supported = false; + try { + const options = Object.defineProperty({}, 'passive', { + get() { + supported = true; + return null; + }, + }); + window.addEventListener('test', null, options); + window.removeEventListener('test', null, options); + } catch (e) { + // Do nothing + } - return supported; + return supported; })(); // Toggle event listener export function toggleListener(element, event, callback, toggle = false, passive = true, capture = false) { - // Bail if no element, event, or callback - if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) { - return; - } + // Bail if no element, event, or callback + if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) { + return; + } + + // Allow multiple events + const events = event.split(' '); + // Build options + // Default to just the capture boolean for browsers with no passive listener support + let options = capture; + + // If passive events listeners are supported + if (supportsPassiveListeners) { + options = { + // Whether the listener can be passive (i.e. default never prevented) + passive, + // Whether the listener is a capturing listener or not + capture, + }; + } - // Allow multiple events - const events = event.split(' '); - // Build options - // Default to just the capture boolean for browsers with no passive listener support - let options = capture; - - // If passive events listeners are supported - if (supportsPassiveListeners) { - options = { - // Whether the listener can be passive (i.e. default never prevented) - passive, - // Whether the listener is a capturing listener or not - capture, - }; + // If a single node is passed, bind the event listener + events.forEach(type => { + if (this && this.eventListeners && toggle) { + // Cache event listener + this.eventListeners.push({ element, type, callback, options }); } - // If a single node is passed, bind the event listener - events.forEach(type => { - if (this && this.eventListeners && toggle) { - // Cache event listener - this.eventListeners.push({ element, type, callback, options }); - } - - element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options); - }); + element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options); + }); } // Bind event handler export function on(element, events = '', callback, passive = true, capture = false) { - toggleListener.call(this, element, events, callback, true, passive, capture); + toggleListener.call(this, element, events, callback, true, passive, capture); } // Unbind event handler export function off(element, events = '', callback, passive = true, capture = false) { - toggleListener.call(this, element, events, callback, false, passive, capture); + toggleListener.call(this, element, events, callback, false, passive, capture); } // Bind once-only event handler export function once(element, events = '', callback, passive = true, capture = false) { - const onceCallback = (...args) => { - off(element, events, onceCallback, passive, capture); - callback.apply(this, args); - }; + const onceCallback = (...args) => { + off(element, events, onceCallback, passive, capture); + callback.apply(this, args); + }; - toggleListener.call(this, element, events, onceCallback, true, passive, capture); + toggleListener.call(this, element, events, onceCallback, true, passive, capture); } // Trigger event export function triggerEvent(element, type = '', bubbles = false, detail = {}) { - // Bail if no element - if (!is.element(element) || is.empty(type)) { - return; - } - - // Create and dispatch the event - const event = new CustomEvent(type, { - bubbles, - detail: { ...detail, plyr: this }, - }); - - // Dispatch the event - element.dispatchEvent(event); + // Bail if no element + if (!is.element(element) || is.empty(type)) { + return; + } + + // Create and dispatch the event + const event = new CustomEvent(type, { + bubbles, + detail: { ...detail, plyr: this }, + }); + + // Dispatch the event + element.dispatchEvent(event); } // Unbind all cached event listeners export function unbindListeners() { - if (this && this.eventListeners) { - this.eventListeners.forEach(item => { - const { element, type, callback, options } = item; - element.removeEventListener(type, callback, options); - }); + if (this && this.eventListeners) { + this.eventListeners.forEach(item => { + const { element, type, callback, options } = item; + element.removeEventListener(type, callback, options); + }); - this.eventListeners = []; - } + this.eventListeners = []; + } } // Run method when / if player is ready export function ready() { - return new Promise(resolve => - this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve), - ).then(() => {}); + return new Promise(resolve => + this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve), + ).then(() => {}); } diff --git a/src/js/utils/fetch.js b/src/js/utils/fetch.js index ee33ea7c..ef695193 100644 --- a/src/js/utils/fetch.js +++ b/src/js/utils/fetch.js @@ -4,39 +4,39 @@ // ========================================================================== export default function fetch(url, responseType = 'text') { - return new Promise((resolve, reject) => { - try { - const request = new XMLHttpRequest(); + return new Promise((resolve, reject) => { + try { + const request = new XMLHttpRequest(); - // Check for CORS support - if (!('withCredentials' in request)) { - return; - } + // Check for CORS support + if (!('withCredentials' in request)) { + return; + } - request.addEventListener('load', () => { - if (responseType === 'text') { - try { - resolve(JSON.parse(request.responseText)); - } catch (e) { - resolve(request.responseText); - } - } else { - resolve(request.response); - } - }); + request.addEventListener('load', () => { + if (responseType === 'text') { + try { + resolve(JSON.parse(request.responseText)); + } catch (e) { + resolve(request.responseText); + } + } else { + resolve(request.response); + } + }); - request.addEventListener('error', () => { - throw new Error(request.status); - }); + request.addEventListener('error', () => { + throw new Error(request.status); + }); - request.open('GET', url, true); + request.open('GET', url, true); - // Set the required response type - request.responseType = responseType; + // Set the required response type + request.responseType = responseType; - request.send(); - } catch (e) { - reject(e); - } - }); + request.send(); + } catch (e) { + reject(e); + } + }); } diff --git a/src/js/utils/i18n.js b/src/js/utils/i18n.js index 5eee5829..70868527 100644 --- a/src/js/utils/i18n.js +++ b/src/js/utils/i18n.js @@ -8,40 +8,40 @@ import { replaceAll } from './strings'; // Skip i18n for abbreviations and brand names const resources = { - pip: 'PIP', - airplay: 'AirPlay', - html5: 'HTML5', - vimeo: 'Vimeo', - youtube: 'YouTube', + pip: 'PIP', + airplay: 'AirPlay', + html5: 'HTML5', + vimeo: 'Vimeo', + youtube: 'YouTube', }; const i18n = { - get(key = '', config = {}) { - if (is.empty(key) || is.empty(config)) { - return ''; - } + get(key = '', config = {}) { + if (is.empty(key) || is.empty(config)) { + return ''; + } - let string = getDeep(config.i18n, key); + let string = getDeep(config.i18n, key); - if (is.empty(string)) { - if (Object.keys(resources).includes(key)) { - return resources[key]; - } + if (is.empty(string)) { + if (Object.keys(resources).includes(key)) { + return resources[key]; + } - return ''; - } + return ''; + } - const replace = { - '{seektime}': config.seekTime, - '{title}': config.title, - }; + const replace = { + '{seektime}': config.seekTime, + '{title}': config.title, + }; - Object.entries(replace).forEach(([k, v]) => { - string = replaceAll(string, k, v); - }); + Object.entries(replace).forEach(([k, v]) => { + string = replaceAll(string, k, v); + }); - return string; - }, + return string; + }, }; export default i18n; diff --git a/src/js/utils/is.js b/src/js/utils/is.js index 24f176cc..1cc33848 100644 --- a/src/js/utils/is.js +++ b/src/js/utils/is.js @@ -22,51 +22,51 @@ const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(inp const isPromise = input => instanceOf(input, Promise) && isFunction(input.then); const isEmpty = input => - isNullOrUndefined(input) || - ((isString(input) || isArray(input) || isNodeList(input)) && !input.length) || - (isObject(input) && !Object.keys(input).length); + isNullOrUndefined(input) || + ((isString(input) || isArray(input) || isNodeList(input)) && !input.length) || + (isObject(input) && !Object.keys(input).length); const isUrl = input => { - // Accept a URL object - if (instanceOf(input, window.URL)) { - return true; - } + // Accept a URL object + if (instanceOf(input, window.URL)) { + return true; + } - // Must be string from here - if (!isString(input)) { - return false; - } + // Must be string from here + if (!isString(input)) { + return false; + } - // Add the protocol if required - let string = input; - if (!input.startsWith('http://') || !input.startsWith('https://')) { - string = `http://${input}`; - } + // Add the protocol if required + let string = input; + if (!input.startsWith('http://') || !input.startsWith('https://')) { + string = `http://${input}`; + } - try { - return !isEmpty(new URL(string).hostname); - } catch (e) { - return false; - } + try { + return !isEmpty(new URL(string).hostname); + } catch (e) { + return false; + } }; export default { - nullOrUndefined: isNullOrUndefined, - object: isObject, - number: isNumber, - string: isString, - boolean: isBoolean, - function: isFunction, - array: isArray, - weakMap: isWeakMap, - nodeList: isNodeList, - element: isElement, - textNode: isTextNode, - event: isEvent, - keyboardEvent: isKeyboardEvent, - cue: isCue, - track: isTrack, - promise: isPromise, - url: isUrl, - empty: isEmpty, + nullOrUndefined: isNullOrUndefined, + object: isObject, + number: isNumber, + string: isString, + boolean: isBoolean, + function: isFunction, + array: isArray, + weakMap: isWeakMap, + nodeList: isNodeList, + element: isElement, + textNode: isTextNode, + event: isEvent, + keyboardEvent: isKeyboardEvent, + cue: isCue, + track: isTrack, + promise: isPromise, + url: isUrl, + empty: isEmpty, }; diff --git a/src/js/utils/load-image.js b/src/js/utils/load-image.js index 8acd2496..36d6ab81 100644 --- a/src/js/utils/load-image.js +++ b/src/js/utils/load-image.js @@ -5,15 +5,15 @@ // ========================================================================== export default function loadImage(src, minWidth = 1) { - return new Promise((resolve, reject) => { - const image = new Image(); + return new Promise((resolve, reject) => { + const image = new Image(); - const handler = () => { - delete image.onload; - delete image.onerror; - (image.naturalWidth >= minWidth ? resolve : reject)(image); - }; + const handler = () => { + delete image.onload; + delete image.onerror; + (image.naturalWidth >= minWidth ? resolve : reject)(image); + }; - Object.assign(image, { onload: handler, onerror: handler, src }); - }); + Object.assign(image, { onload: handler, onerror: handler, src }); + }); } diff --git a/src/js/utils/load-script.js b/src/js/utils/load-script.js index 81ae36f4..53b660c8 100644 --- a/src/js/utils/load-script.js +++ b/src/js/utils/load-script.js @@ -5,10 +5,10 @@ import loadjs from 'loadjs'; export default function loadScript(url) { - return new Promise((resolve, reject) => { - loadjs(url, { - success: resolve, - error: reject, - }); + return new Promise((resolve, reject) => { + loadjs(url, { + success: resolve, + error: reject, }); + }); } diff --git a/src/js/utils/load-sprite.js b/src/js/utils/load-sprite.js index fe4add00..0a4eff99 100644 --- a/src/js/utils/load-sprite.js +++ b/src/js/utils/load-sprite.js @@ -8,68 +8,68 @@ import is from './is'; // Load an external SVG sprite export default function loadSprite(url, id) { - if (!is.string(url)) { - return; + if (!is.string(url)) { + return; + } + + const prefix = 'cache'; + const hasId = is.string(id); + let isCached = false; + const exists = () => document.getElementById(id) !== null; + + const update = (container, data) => { + // eslint-disable-next-line no-param-reassign + container.innerHTML = data; + + // Check again incase of race condition + if (hasId && exists()) { + return; } - const prefix = 'cache'; - const hasId = is.string(id); - let isCached = false; - const exists = () => document.getElementById(id) !== null; + // Inject the SVG to the body + document.body.insertAdjacentElement('afterbegin', container); + }; - const update = (container, data) => { - // eslint-disable-next-line no-param-reassign - container.innerHTML = data; + // Only load once if ID set + if (!hasId || !exists()) { + const useStorage = Storage.supported; + // Create container + const container = document.createElement('div'); + container.setAttribute('hidden', ''); - // Check again incase of race condition - if (hasId && exists()) { - return; - } + if (hasId) { + container.setAttribute('id', id); + } - // Inject the SVG to the body - document.body.insertAdjacentElement('afterbegin', container); - }; + // Check in cache + if (useStorage) { + const cached = window.localStorage.getItem(`${prefix}-${id}`); + isCached = cached !== null; - // Only load once if ID set - if (!hasId || !exists()) { - const useStorage = Storage.supported; - // Create container - const container = document.createElement('div'); - container.setAttribute('hidden', ''); + if (isCached) { + const data = JSON.parse(cached); + update(container, data.content); + } + } - if (hasId) { - container.setAttribute('id', id); + // Get the sprite + fetch(url) + .then(result => { + if (is.empty(result)) { + return; } - // Check in cache if (useStorage) { - const cached = window.localStorage.getItem(`${prefix}-${id}`); - isCached = cached !== null; - - if (isCached) { - const data = JSON.parse(cached); - update(container, data.content); - } + window.localStorage.setItem( + `${prefix}-${id}`, + JSON.stringify({ + content: result, + }), + ); } - // Get the sprite - fetch(url) - .then(result => { - if (is.empty(result)) { - return; - } - - if (useStorage) { - window.localStorage.setItem( - `${prefix}-${id}`, - JSON.stringify({ - content: result, - }), - ); - } - - update(container, result); - }) - .catch(() => {}); - } + update(container, result); + }) + .catch(() => {}); + } } diff --git a/src/js/utils/numbers.js b/src/js/utils/numbers.js index f6eb65c8..87bbe7e2 100644 --- a/src/js/utils/numbers.js +++ b/src/js/utils/numbers.js @@ -11,7 +11,7 @@ * @type Number */ export function clamp(input = 0, min = 0, max = 255) { - return Math.min(Math.max(input, min), max); + return Math.min(Math.max(input, min), max); } export default { clamp }; diff --git a/src/js/utils/objects.js b/src/js/utils/objects.js index 225bb459..a327e488 100644 --- a/src/js/utils/objects.js +++ b/src/js/utils/objects.js @@ -6,37 +6,37 @@ import is from './is'; // Clone nested objects export function cloneDeep(object) { - return JSON.parse(JSON.stringify(object)); + return JSON.parse(JSON.stringify(object)); } // Get a nested value in an object export function getDeep(object, path) { - return path.split('.').reduce((obj, key) => obj && obj[key], object); + return path.split('.').reduce((obj, key) => obj && obj[key], object); } // Deep extend destination object with N more objects export function extend(target = {}, ...sources) { - if (!sources.length) { - return target; - } + if (!sources.length) { + return target; + } - const source = sources.shift(); + const source = sources.shift(); - if (!is.object(source)) { - return target; - } + if (!is.object(source)) { + return target; + } - Object.keys(source).forEach(key => { - if (is.object(source[key])) { - if (!Object.keys(target).includes(key)) { - Object.assign(target, { [key]: {} }); - } + Object.keys(source).forEach(key => { + if (is.object(source[key])) { + if (!Object.keys(target).includes(key)) { + Object.assign(target, { [key]: {} }); + } - extend(target[key], source[key]); - } else { - Object.assign(target, { [key]: source[key] }); - } - }); + extend(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + }); - return extend(target, ...sources); + return extend(target, ...sources); } diff --git a/src/js/utils/promise.js b/src/js/utils/promise.js index f45b46ab..4b59bba3 100644 --- a/src/js/utils/promise.js +++ b/src/js/utils/promise.js @@ -6,9 +6,9 @@ import is from './is'; * @param {Object} value An object that may or may not be `Promise`-like. */ export function silencePromise(value) { - if (is.promise(value)) { - value.then(null, () => {}); - } + if (is.promise(value)) { + value.then(null, () => {}); + } } export default { silencePromise }; diff --git a/src/js/utils/strings.js b/src/js/utils/strings.js index 6b9a65a2..b7de04c1 100644 --- a/src/js/utils/strings.js +++ b/src/js/utils/strings.js @@ -6,80 +6,75 @@ import is from './is'; // Generate a random ID export function generateId(prefix) { - return `${prefix}-${Math.floor(Math.random() * 10000)}`; + return `${prefix}-${Math.floor(Math.random() * 10000)}`; } // Format string export function format(input, ...args) { - if (is.empty(input)) { - return input; - } + if (is.empty(input)) { + return input; + } - return input.toString().replace(/{(\d+)}/g, (match, i) => args[i].toString()); + return input.toString().replace(/{(\d+)}/g, (match, i) => args[i].toString()); } // Get percentage export function getPercentage(current, max) { - if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) { - return 0; - } + if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) { + return 0; + } - return ((current / max) * 100).toFixed(2); + return ((current / max) * 100).toFixed(2); } // Replace all occurances of a string in a string -export function replaceAll(input = '', find = '', replace = '') { - return input.replace( - new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), - replace.toString(), - ); -} +export const replaceAll = (input = '', find = '', replace = '') => + input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString()); // Convert to title case -export function toTitleCase(input = '') { - return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase()); -} +export const toTitleCase = (input = '') => + input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase()); // Convert string to pascalCase export function toPascalCase(input = '') { - let string = input.toString(); + let string = input.toString(); - // Convert kebab case - string = replaceAll(string, '-', ' '); + // Convert kebab case + string = replaceAll(string, '-', ' '); - // Convert snake case - string = replaceAll(string, '_', ' '); + // Convert snake case + string = replaceAll(string, '_', ' '); - // Convert to title case - string = toTitleCase(string); + // Convert to title case + string = toTitleCase(string); - // Convert to pascal case - return replaceAll(string, ' ', ''); + // Convert to pascal case + return replaceAll(string, ' ', ''); } // Convert string to pascalCase export function toCamelCase(input = '') { - let string = input.toString(); + let string = input.toString(); - // Convert to pascal case - string = toPascalCase(string); + // Convert to pascal case + string = toPascalCase(string); - // Convert first character to lowercase - return string.charAt(0).toLowerCase() + string.slice(1); + // Convert first character to lowercase + return string.charAt(0).toLowerCase() + string.slice(1); } // Remove HTML from a string export function stripHTML(source) { - const fragment = document.createDocumentFragment(); - const element = document.createElement('div'); - fragment.appendChild(element); - element.innerHTML = source; - return fragment.firstChild.innerText; + const fragment = document.createDocumentFragment(); + const element = document.createElement('div'); + fragment.appendChild(element); + element.innerHTML = source; + return fragment.firstChild.innerText; } // Like outerHTML, but also works for DocumentFragment export function getHTML(element) { - const wrapper = document.createElement('div'); - wrapper.appendChild(element); - return wrapper.innerHTML; + const wrapper = document.createElement('div'); + wrapper.appendChild(element); + return wrapper.innerHTML; } diff --git a/src/js/utils/style.js b/src/js/utils/style.js index 17a033fe..0a37e595 100644 --- a/src/js/utils/style.js +++ b/src/js/utils/style.js @@ -5,74 +5,74 @@ import is from './is'; export function validateRatio(input) { - if (!is.array(input) && (!is.string(input) || !input.includes(':'))) { - return false; - } + if (!is.array(input) && (!is.string(input) || !input.includes(':'))) { + return false; + } - const ratio = is.array(input) ? input : input.split(':'); + const ratio = is.array(input) ? input : input.split(':'); - return ratio.map(Number).every(is.number); + return ratio.map(Number).every(is.number); } export function reduceAspectRatio(ratio) { - if (!is.array(ratio) || !ratio.every(is.number)) { - return null; - } + if (!is.array(ratio) || !ratio.every(is.number)) { + return null; + } - const [width, height] = ratio; - const getDivider = (w, h) => (h === 0 ? w : getDivider(h, w % h)); - const divider = getDivider(width, height); + const [width, height] = ratio; + const getDivider = (w, h) => (h === 0 ? w : getDivider(h, w % h)); + const divider = getDivider(width, height); - return [width / divider, height / divider]; + return [width / divider, height / divider]; } export function getAspectRatio(input) { - const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null); - // Try provided ratio - let ratio = parse(input); - - // Get from config - if (ratio === null) { - ratio = parse(this.config.ratio); - } - - // Get from embed - if (ratio === null && !is.empty(this.embed) && is.array(this.embed.ratio)) { - ({ ratio } = this.embed); - } - - // Get from HTML5 video - if (ratio === null && this.isHTML5) { - const { videoWidth, videoHeight } = this.media; - ratio = reduceAspectRatio([videoWidth, videoHeight]); - } - - return ratio; + const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null); + // Try provided ratio + let ratio = parse(input); + + // Get from config + if (ratio === null) { + ratio = parse(this.config.ratio); + } + + // Get from embed + if (ratio === null && !is.empty(this.embed) && is.array(this.embed.ratio)) { + ({ ratio } = this.embed); + } + + // Get from HTML5 video + if (ratio === null && this.isHTML5) { + const { videoWidth, videoHeight } = this.media; + ratio = reduceAspectRatio([videoWidth, videoHeight]); + } + + return ratio; } // Set aspect ratio for responsive container export function setAspectRatio(input) { - if (!this.isVideo) { - return {}; - } - - const { wrapper } = this.elements; - const ratio = getAspectRatio.call(this, input); - const [w, h] = is.array(ratio) ? ratio : [0, 0]; - const padding = (100 / w) * h; - - wrapper.style.paddingBottom = `${padding}%`; - - // For Vimeo we have an extra
to hide the standard controls and UI - if (this.isVimeo && this.supported.ui) { - const height = 240; - const offset = (height - padding) / (height / 50); - this.media.style.transform = `translateY(-${offset}%)`; - } else if (this.isHTML5) { - wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null); - } - - return { padding, ratio }; + if (!this.isVideo) { + return {}; + } + + const { wrapper } = this.elements; + const ratio = getAspectRatio.call(this, input); + const [w, h] = is.array(ratio) ? ratio : [0, 0]; + const padding = (100 / w) * h; + + wrapper.style.paddingBottom = `${padding}%`; + + // For Vimeo we have an extra
to hide the standard controls and UI + if (this.isVimeo && this.supported.ui) { + const height = 240; + const offset = (height - padding) / (height / 50); + this.media.style.transform = `translateY(-${offset}%)`; + } else if (this.isHTML5) { + wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null); + } + + return { padding, ratio }; } export default { setAspectRatio }; diff --git a/src/js/utils/time.js b/src/js/utils/time.js index 17228de5..31660c4a 100644 --- a/src/js/utils/time.js +++ b/src/js/utils/time.js @@ -11,25 +11,25 @@ export const getSeconds = value => Math.trunc(value % 60, 10); // Format time to UI friendly string export function formatTime(time = 0, displayHours = false, inverted = false) { - // Bail if the value isn't a number - if (!is.number(time)) { - return formatTime(undefined, displayHours, inverted); - } + // Bail if the value isn't a number + if (!is.number(time)) { + return formatTime(undefined, displayHours, inverted); + } - // Format time component to add leading zero - const format = value => `0${value}`.slice(-2); - // Breakdown to hours, mins, secs - let hours = getHours(time); - const mins = getMinutes(time); - const secs = getSeconds(time); + // Format time component to add leading zero + const format = value => `0${value}`.slice(-2); + // Breakdown to hours, mins, secs + let hours = getHours(time); + const mins = getMinutes(time); + const secs = getSeconds(time); - // Do we need to display hours? - if (displayHours || hours > 0) { - hours = `${hours}:`; - } else { - hours = ''; - } + // Do we need to display hours? + if (displayHours || hours > 0) { + hours = `${hours}:`; + } else { + hours = ''; + } - // Render - return `${inverted && time > 0 ? '-' : ''}${hours}${format(mins)}:${format(secs)}`; + // Render + return `${inverted && time > 0 ? '-' : ''}${hours}${format(mins)}:${format(secs)}`; } diff --git a/src/js/utils/urls.js b/src/js/utils/urls.js index 843c6aa6..ba264511 100644 --- a/src/js/utils/urls.js +++ b/src/js/utils/urls.js @@ -10,30 +10,30 @@ import is from './is'; * @param {Boolean} safe - failsafe parsing */ export function parseUrl(input, safe = true) { - let url = input; + let url = input; - if (safe) { - const parser = document.createElement('a'); - parser.href = url; - url = parser.href; - } + if (safe) { + const parser = document.createElement('a'); + parser.href = url; + url = parser.href; + } - try { - return new URL(url); - } catch (e) { - return null; - } + try { + return new URL(url); + } catch (e) { + return null; + } } // Convert object to URLSearchParams export function buildUrlParams(input) { - const params = new URLSearchParams(); + const params = new URLSearchParams(); - if (is.object(input)) { - Object.entries(input).forEach(([key, value]) => { - params.set(key, value); - }); - } + if (is.object(input)) { + Object.entries(input).forEach(([key, value]) => { + params.set(key, value); + }); + } - return params; + return params; } -- cgit v1.2.3 From 9c7e429b48320f6b021baa2ed23e35a6bd9ceae5 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 19 Apr 2020 19:51:06 +1000 Subject: Vimeo ratio fixes --- src/js/utils/style.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/js/utils') diff --git a/src/js/utils/style.js b/src/js/utils/style.js index 0a37e595..c2004fcb 100644 --- a/src/js/utils/style.js +++ b/src/js/utils/style.js @@ -64,9 +64,10 @@ export function setAspectRatio(input) { wrapper.style.paddingBottom = `${padding}%`; // For Vimeo we have an extra
to hide the standard controls and UI - if (this.isVimeo && this.supported.ui) { - const height = 240; + if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) { + const height = (100 / this.media.offsetWidth) * parseInt(window.getComputedStyle(this.media).paddingBottom, 10); const offset = (height - padding) / (height / 50); + this.media.style.transform = `translateY(-${offset}%)`; } else if (this.isHTML5) { wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null); -- cgit v1.2.3