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/elements.js') 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 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/js/utils/elements.js') 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); -- 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/elements.js') 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/elements.js | 336 +++++++++++++++++++++++------------------------ 1 file changed, 168 insertions(+), 168 deletions(-) (limited to 'src/js/utils/elements.js') 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); + } } -- cgit v1.2.3