aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js')
-rw-r--r--src/js/captions.js2
-rw-r--r--src/js/controls.js182
-rw-r--r--src/js/defaults.js1
-rw-r--r--src/js/listeners.js106
-rw-r--r--src/js/plyr.js126
-rw-r--r--src/js/support.js1
-rw-r--r--src/js/ui.js173
-rw-r--r--src/js/utils.js30
8 files changed, 287 insertions, 334 deletions
diff --git a/src/js/captions.js b/src/js/captions.js
index e0692dcf..df717351 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -236,7 +236,7 @@ const captions = {
// Set the span content
if (utils.is.string(caption)) {
- content.textContent = caption.trim();
+ content.innerText = caption.trim();
} else {
content.appendChild(caption);
}
diff --git a/src/js/controls.js b/src/js/controls.js
index ec64977e..8a160864 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -6,34 +6,13 @@ import captions from './captions';
import html5 from './html5';
import i18n from './i18n';
import support from './support';
-import ui from './ui';
import utils from './utils';
// Sniff out the browser
const browser = utils.getBrowser();
const controls = {
- // Webkit polyfill for lower fill range
- updateRangeFill(target) {
- // Get range from event if event passed
- const range = utils.is.event(target) ? target.target : target;
-
- // Needs to be a valid <input type='range'>
- if (!utils.is.element(range) || range.getAttribute('type') !== 'range') {
- return;
- }
-
- // Set aria value for https://github.com/sampotts/plyr/issues/905
- range.setAttribute('aria-valuenow', range.value);
-
- // WebKit only
- if (!browser.isWebkit) {
- return;
- }
- // Set CSS custom property
- range.style.setProperty('--value', `${range.value / range.max * 100}%`);
- },
// Get icon URL
getIconUrl() {
@@ -373,7 +352,7 @@ const controls = {
break;
}
- progress.textContent = `% ${suffix.toLowerCase()}`;
+ progress.innerText = `% ${suffix.toLowerCase()}`;
}
this.elements.display[type] = progress;
@@ -429,6 +408,123 @@ const controls = {
list.appendChild(item);
},
+ // Update the displayed time
+ updateTimeDisplay(target = null, time = 0, inverted = false) {
+ // Bail if there's no element to display or the value isn't a number
+ if (!utils.is.element(target) || !utils.is.number(time)) {
+ return;
+ }
+
+ // Always display hours if duration is over an hour
+ const forceHours = utils.getHours(this.duration) > 0;
+
+ // eslint-disable-next-line no-param-reassign
+ target.innerText = utils.formatTime(time, forceHours, inverted);
+ },
+
+ // Update volume UI and storage
+ updateVolume() {
+ if (!this.supported.ui) {
+ return;
+ }
+
+ // Update range
+ if (utils.is.element(this.elements.inputs.volume)) {
+ controls.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume);
+ }
+
+ // Update mute state
+ if (utils.is.element(this.elements.buttons.mute)) {
+ utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0);
+ }
+ },
+
+ // Update seek value and lower fill
+ setRange(target, value = 0) {
+ if (!utils.is.element(target)) {
+ return;
+ }
+
+ // eslint-disable-next-line
+ target.value = value;
+
+ // Webkit range fill
+ controls.updateRangeFill.call(this, target);
+ },
+
+ // Update <progress> elements
+ updateProgress(event) {
+ if (!this.supported.ui || !utils.is.event(event)) {
+ return;
+ }
+
+ let value = 0;
+
+ const setProgress = (target, input) => {
+ const value = utils.is.number(input) ? input : 0;
+ const progress = utils.is.element(target) ? target : this.elements.display.buffer;
+
+ // Update value and label
+ if (utils.is.element(progress)) {
+ progress.value = value;
+
+ // Update text label inside
+ const label = progress.getElementsByTagName('span')[0];
+ if (utils.is.element(label)) {
+ label.childNodes[0].nodeValue = value;
+ }
+ }
+ };
+
+ if (event) {
+ switch (event.type) {
+ // Video playing
+ case 'timeupdate':
+ case 'seeking':
+ value = utils.getPercentage(this.currentTime, this.duration);
+
+ // Set seek range value only if it's a 'natural' time event
+ if (event.type === 'timeupdate') {
+ controls.setRange.call(this, this.elements.inputs.seek, value);
+ }
+
+ break;
+
+ // Check buffer status
+ case 'playing':
+ case 'progress':
+ setProgress(this.elements.display.buffer, this.buffered * 100);
+
+ break;
+
+ default:
+ break;
+ }
+ }
+ },
+
+ // Webkit polyfill for lower fill range
+ updateRangeFill(target) {
+ // Get range from event if event passed
+ const range = utils.is.event(target) ? target.target : target;
+
+ // Needs to be a valid <input type='range'>
+ if (!utils.is.element(range) || range.getAttribute('type') !== 'range') {
+ return;
+ }
+
+ // Set aria value for https://github.com/sampotts/plyr/issues/905
+ range.setAttribute('aria-valuenow', range.value);
+
+ // WebKit only
+ if (!browser.isWebkit) {
+ return;
+ }
+
+ // Set CSS custom property
+ range.style.setProperty('--value', `${range.value / range.max * 100}%`);
+ },
+
// Update hover tooltip for seeking
updateSeekTooltip(event) {
// Bail if setting not true
@@ -473,7 +569,7 @@ const controls = {
}
// Display the time a click would seek to
- ui.updateTimeDisplay.call(this, this.elements.display.seekTooltip, this.duration / 100 * percent);
+ controls.updateTimeDisplay.call(this, this.elements.display.seekTooltip, this.duration / 100 * percent);
// Set position
this.elements.display.seekTooltip.style.left = `${percent}%`;
@@ -488,6 +584,46 @@ const controls = {
}
},
+ // Handle time change event
+ timeUpdate(event) {
+ // Only invert if only one time element is displayed and used for both duration and currentTime
+ const invert = !utils.is.element(this.elements.display.duration) && this.config.invertTime;
+
+ // Duration
+ controls.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert);
+
+ // Ignore updates while seeking
+ if (event && event.type === 'timeupdate' && this.media.seeking) {
+ return;
+ }
+
+ // Playing progress
+ controls.updateProgress.call(this, event);
+ },
+
+ // Show the duration on metadataloaded
+ durationUpdate() {
+ if (!this.supported.ui) {
+ return;
+ }
+
+ // If there's a spot to display duration
+ const hasDuration = utils.is.element(this.elements.display.duration);
+
+ // If there's only one time display, display duration there
+ if (!hasDuration && this.config.displayDuration && this.paused) {
+ controls.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration);
+ }
+
+ // If there's a duration element, update content
+ if (hasDuration) {
+ controls.updateTimeDisplay.call(this, this.elements.display.duration, this.duration);
+ }
+
+ // Update the tooltip (if visible)
+ controls.updateSeekTooltip.call(this);
+ },
+
// Hide/show a tab
toggleTab(setting, toggle) {
utils.toggleHidden(this.elements.settings.tabs[setting], !toggle);
diff --git a/src/js/defaults.js b/src/js/defaults.js
index 7cc5c082..f66a7c2f 100644
--- a/src/js/defaults.js
+++ b/src/js/defaults.js
@@ -338,7 +338,6 @@ const defaults = {
paused: 'plyr--paused',
stopped: 'plyr--stopped',
loading: 'plyr--loading',
- error: 'plyr--has-error',
hover: 'plyr--hover',
tooltip: 'plyr__tooltip',
cues: 'plyr__cues',
diff --git a/src/js/listeners.js b/src/js/listeners.js
index f4e9ade3..5dd9d93f 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -238,22 +238,45 @@ class Listeners {
}, 0);
});
- // Toggle controls visibility based on mouse movement
- if (this.player.config.hideControls) {
- // Toggle controls on mouse events and entering fullscreen
- utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
- this.player.toggleControls(event);
- });
- }
+ // Toggle controls on mouse events and entering fullscreen
+ utils.on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', event => {
+ const { controls } = this.player.elements;
+
+ // Remove button states for fullscreen
+ if (event.type === 'enterfullscreen') {
+ controls.pressed = false;
+ controls.hover = false;
+ }
+
+ // Show, then hide after a timeout unless another control event occurs
+ const show = [
+ 'touchstart',
+ 'touchmove',
+ 'mousemove',
+ ].includes(event.type);
+
+ let delay = 0;
+
+ if (show) {
+ ui.toggleControls.call(this.player, true);
+ // Use longer timeout for touch devices
+ delay = this.player.touch ? 3000 : 2000;
+ }
+
+ // Clear timer
+ clearTimeout(this.player.timers.controls);
+ // Timer to prevent flicker when seeking
+ this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
+ });
}
// Listen for media events
media() {
// Time change on media
- utils.on(this.player.media, 'timeupdate seeking', event => ui.timeUpdate.call(this.player, event));
+ utils.on(this.player.media, 'timeupdate seeking', event => controls.timeUpdate.call(this.player, event));
// Display duration
- utils.on(this.player.media, 'durationchange loadeddata loadedmetadata', event => ui.durationUpdate.call(this.player, event));
+ utils.on(this.player.media, 'durationchange loadeddata loadedmetadata', event => controls.durationUpdate.call(this.player, event));
// Check for audio tracks on load
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
@@ -272,10 +295,10 @@ class Listeners {
});
// Check for buffer progress
- utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event));
+ utils.on(this.player.media, 'progress playing', event => controls.updateProgress.call(this.player, event));
// Handle volume changes
- utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event));
+ utils.on(this.player.media, 'volumechange', event => controls.updateVolume.call(this.player, event));
// Handle play/pause
utils.on(this.player.media, 'playing play pause ended emptied timeupdate', event => ui.checkPlaying.call(this.player, event));
@@ -283,9 +306,6 @@ class Listeners {
// Loading state
utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
- // Check if media failed to load
- // utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
-
// If autoplay, then load advertisement if required
// TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows
utils.on(this.player.media, 'playing', () => {
@@ -549,7 +569,8 @@ class Listeners {
}
this.player.config.invertTime = !this.player.config.invertTime;
- ui.timeUpdate.call(this.player);
+
+ controls.timeUpdate.call(this.player);
});
}
@@ -573,26 +594,45 @@ class Listeners {
// Seek tooltip
on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
- // Toggle controls visibility based on mouse movement
- if (this.player.config.hideControls) {
- // Watch for cursor over controls so they don't hide when trying to interact
- on(this.player.elements.controls, 'mouseenter mouseleave', event => {
- this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
- });
+ // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
+ on(this.player.elements.controls, 'mouseenter mouseleave', event => {
+ this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
+ });
- // Watch for cursor over controls so they don't hide when trying to interact
- on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
- this.player.elements.controls.pressed = [
- 'mousedown',
- 'touchstart',
- ].includes(event.type);
- });
+ // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
+ on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
+ this.player.elements.controls.pressed = [
+ 'mousedown',
+ 'touchstart',
+ ].includes(event.type);
+ });
- // Focus in/out on controls
- on(this.player.elements.controls, 'focusin focusout', event => {
- this.player.toggleControls(event);
- });
- }
+ // Focus in/out on controls
+ on(this.player.elements.controls, 'focusin focusout', event => {
+ const { config, elements, timers } = this.player;
+
+ // Skip transition to prevent focus from scrolling the parent element
+ utils.toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
+
+ // Toggle
+ ui.toggleControls.call(this.player, event.type === 'focusin');
+
+ // If focusin, hide again after delay
+ if (event.type === 'focusin') {
+ // Restore transition
+ setTimeout(() => {
+ utils.toggleClass(elements.controls, config.classNames.noTransition, false);
+ }, 0);
+
+ // Delay a little more for keyboard users
+ const delay = this.touch ? 3000 : 4000;
+
+ // Clear timer
+ clearTimeout(timers.controls);
+ // Hide
+ timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
+ }
+ });
// Mouse wheel for volume
on(
diff --git a/src/js/plyr.js b/src/js/plyr.js
index dee90dd2..4c569fec 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -55,6 +55,7 @@ class Plyr {
this.config = utils.extend(
{},
defaults,
+ Plyr.defaults,
options || {},
(() => {
try {
@@ -967,119 +968,32 @@ class Plyr {
/**
* Toggle the player controls
- * @param {boolean} toggle - Whether to show the controls
+ * @param {boolean} [toggle] - Whether to show the controls
*/
toggleControls(toggle) {
- // We need controls of course...
- if (!utils.is.element(this.elements.controls)) {
- return;
- }
+ // Don't toggle if missing UI support or if it's audio
+ if (this.supported.ui && !this.isAudio) {
+ // Get state before change
+ const isHidden = utils.hasClass(this.elements.container, this.config.classNames.hideControls);
- // Don't hide if no UI support or it's audio
- if (!this.supported.ui || this.isAudio) {
- return;
- }
+ // Negate the argument if not undefined since adding the class to hides the controls
+ const force = typeof toggle === 'undefined' ? undefined : !toggle;
- let delay = 0;
- let show = toggle;
- let isEnterFullscreen = false;
+ // Apply and get updated state
+ const hiding = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, force);
- // Get toggle state if not set
- if (!utils.is.boolean(toggle)) {
- if (utils.is.event(toggle)) {
- // Is the enter fullscreen event
- isEnterFullscreen = toggle.type === 'enterfullscreen';
-
- // Events that show the controls
- const showEvents = [
- 'touchstart',
- 'touchmove',
- 'mouseenter',
- 'mousemove',
- 'focusin',
- ];
-
- // Events that delay hiding
- const delayEvents = [
- 'touchmove',
- 'touchend',
- 'mousemove',
- ];
-
- // Whether to show controls
- show = showEvents.includes(toggle.type);
-
- // Delay hiding on move events
- if (delayEvents.includes(toggle.type)) {
- delay = 2000;
- }
-
- // Delay a little more for keyboard users
- if (!this.touch && toggle.type === 'focusin') {
- delay = 3000;
- utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
- }
- } else {
- show = utils.hasClass(this.elements.container, this.config.classNames.hideControls);
+ // Close menu
+ if (hiding && this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
+ controls.toggleMenu.call(this, false);
}
- }
-
- // Clear timer on every call
- clearTimeout(this.timers.controls);
-
- // If the mouse is not over the controls, set a timeout to hide them
- if (show || this.paused || this.loading) {
- // Check if controls toggled
- const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, false);
-
- // Trigger event
- if (toggled) {
- utils.dispatchEvent.call(this, this.media, 'controlsshown');
+ // Trigger event on change
+ if (hiding !== isHidden) {
+ const eventName = hiding ? 'controlshidden' : 'controlsshown';
+ utils.dispatchEvent.call(this, this.media, eventName);
}
-
- // Always show controls when paused or if touch
- if (this.paused || this.loading) {
- return;
- }
-
- // Delay for hiding on touch
- if (this.touch) {
- delay = 3000;
- }
- }
-
- // If toggle is false or if we're playing (regardless of toggle),
- // then set the timer to hide the controls
- if (!show || this.playing) {
- this.timers.controls = setTimeout(() => {
- // We need controls of course...
- if (!utils.is.element(this.elements.controls)) {
- return;
- }
-
- // If the mouse is over the controls (and not entering fullscreen), bail
- if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
- return;
- }
-
- // Restore transition behaviour
- if (!utils.hasClass(this.elements.container, this.config.classNames.hideControls)) {
- utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, false);
- }
-
- // Set hideControls class
- const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, this.config.hideControls);
-
- // Trigger event and close menu
- if (toggled) {
- utils.dispatchEvent.call(this, this.media, 'controlshidden');
-
- if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
- controls.toggleMenu.call(this, false);
- }
- }
- }, delay);
+ return !hiding;
}
+ return false;
}
/**
@@ -1266,4 +1180,6 @@ class Plyr {
}
}
+Plyr.defaults = utils.cloneDeep(defaults);
+
export default Plyr;
diff --git a/src/js/support.js b/src/js/support.js
index 5528e898..38212d9f 100644
--- a/src/js/support.js
+++ b/src/js/support.js
@@ -133,6 +133,7 @@ const support = {
},
});
window.addEventListener('test', null, options);
+ window.removeEventListener('test', null, options);
} catch (e) {
// Do nothing
}
diff --git a/src/js/ui.js b/src/js/ui.js
index f844f93c..3a8f2d05 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -74,10 +74,10 @@ const ui = {
this.quality = null;
// Reset volume display
- ui.updateVolume.call(this);
+ controls.updateVolume.call(this);
// Reset time display
- ui.timeUpdate.call(this);
+ controls.timeUpdate.call(this);
// Update the UI
ui.checkPlaying.call(this);
@@ -199,7 +199,7 @@ const ui = {
}
// Toggle controls
- this.toggleControls(!this.playing);
+ ui.toggleControls.call(this);
},
// Check if media is loading
@@ -214,171 +214,22 @@ const ui = {
// Timer to prevent flicker when seeking
this.timers.loading = setTimeout(() => {
- // Toggle container class hook
+ // Update progress bar loading class state
utils.toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
- // Show controls if loading, hide if done
- this.toggleControls(this.loading);
+ // Update controls visibility
+ ui.toggleControls.call(this);
}, this.loading ? 250 : 0);
},
- // Check if media failed to load
- checkFailed() {
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/networkState
- this.failed = this.media.networkState === 3;
+ // Toggle controls based on state and `force` argument
+ toggleControls(force) {
+ const { controls } = this.elements;
- if (this.failed) {
- utils.toggleClass(this.elements.container, this.config.classNames.loading, false);
- utils.toggleClass(this.elements.container, this.config.classNames.error, true);
+ if (controls && this.config.hideControls) {
+ // Show controls if force, loading, paused, or button interaction, otherwise hide
+ this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover));
}
-
- // Clear timer
- clearTimeout(this.timers.failed);
-
- // Timer to prevent flicker when seeking
- this.timers.loading = setTimeout(() => {
- // Toggle container class hook
- utils.toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
-
- // Show controls if loading, hide if done
- this.toggleControls(this.loading);
- }, this.loading ? 250 : 0);
- },
-
- // Update volume UI and storage
- updateVolume() {
- if (!this.supported.ui) {
- return;
- }
-
- // Update range
- if (utils.is.element(this.elements.inputs.volume)) {
- ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume);
- }
-
- // Update mute state
- if (utils.is.element(this.elements.buttons.mute)) {
- utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0);
- }
- },
-
- // Update seek value and lower fill
- setRange(target, value = 0) {
- if (!utils.is.element(target)) {
- return;
- }
-
- // eslint-disable-next-line
- target.value = value;
-
- // Webkit range fill
- controls.updateRangeFill.call(this, target);
- },
-
- // Set <progress> value
- setProgress(target, input) {
- const value = utils.is.number(input) ? input : 0;
- const progress = utils.is.element(target) ? target : this.elements.display.buffer;
-
- // Update value and label
- if (utils.is.element(progress)) {
- progress.value = value;
-
- // Update text label inside
- const label = progress.getElementsByTagName('span')[0];
- if (utils.is.element(label)) {
- label.childNodes[0].nodeValue = value;
- }
- }
- },
-
- // Update <progress> elements
- updateProgress(event) {
- if (!this.supported.ui || !utils.is.event(event)) {
- return;
- }
-
- let value = 0;
-
- if (event) {
- switch (event.type) {
- // Video playing
- case 'timeupdate':
- case 'seeking':
- value = utils.getPercentage(this.currentTime, this.duration);
-
- // Set seek range value only if it's a 'natural' time event
- if (event.type === 'timeupdate') {
- ui.setRange.call(this, this.elements.inputs.seek, value);
- }
-
- break;
-
- // Check buffer status
- case 'playing':
- case 'progress':
- ui.setProgress.call(this, this.elements.display.buffer, this.buffered * 100);
-
- break;
-
- default:
- break;
- }
- }
- },
-
- // Update the displayed time
- updateTimeDisplay(target = null, time = 0, inverted = false) {
- // Bail if there's no element to display or the value isn't a number
- if (!utils.is.element(target) || !utils.is.number(time)) {
- return;
- }
-
- // Always display hours if duration is over an hour
- const forceHours = utils.getHours(this.duration) > 0;
-
- // eslint-disable-next-line no-param-reassign
- target.textContent = utils.formatTime(time, forceHours, inverted);
- },
-
- // Handle time change event
- timeUpdate(event) {
- // Only invert if only one time element is displayed and used for both duration and currentTime
- const invert = !utils.is.element(this.elements.display.duration) && this.config.invertTime;
-
- // Duration
- ui.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert);
-
- // Ignore updates while seeking
- if (event && event.type === 'timeupdate' && this.media.seeking) {
- return;
- }
-
- // Playing progress
- ui.updateProgress.call(this, event);
- },
-
- // Show the duration on metadataloaded
- durationUpdate() {
- if (!this.supported.ui) {
- return;
- }
-
- // If there's a spot to display duration
- const hasDuration = utils.is.element(this.elements.display.duration);
-
- // If there's only one time display, display duration there
- if (!hasDuration && this.config.displayDuration && this.paused) {
- ui.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration);
- }
-
- // If there's a duration element, update content
- if (hasDuration) {
- ui.updateTimeDisplay.call(this, this.elements.display.duration, this.duration);
- }
-
- // Update the tooltip (if visible)
- controls.updateSeekTooltip.call(this);
},
};
diff --git a/src/js/utils.js b/src/js/utils.js
index 0cd332dd..0c5a28d7 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -3,6 +3,7 @@
// ==========================================================================
import loadjs from 'loadjs';
+import Storage from './storage';
import support from './support';
import { providers } from './types';
@@ -171,6 +172,8 @@ const utils = {
// Only load once if ID set
if (!hasId || !exists()) {
+ const useStorage = Storage.supported;
+
// Create container
const container = document.createElement('div');
utils.toggleHidden(container, true);
@@ -180,7 +183,7 @@ const utils = {
}
// Check in cache
- if (support.storage) {
+ if (useStorage) {
const cached = window.localStorage.getItem(prefix + id);
isCached = cached !== null;
@@ -199,7 +202,7 @@ const utils = {
return;
}
- if (support.storage) {
+ if (useStorage) {
window.localStorage.setItem(
prefix + id,
JSON.stringify({
@@ -262,7 +265,7 @@ const utils = {
// Add text node
if (utils.is.string(text)) {
- element.textContent = text;
+ element.innerText = text;
}
// Return built element
@@ -405,14 +408,16 @@ const utils = {
}
},
- // Toggle class on an element
- toggleClass(element, className, toggle) {
+ // Mirror Element.classList.toggle, with IE compatibility for "force" argument
+ toggleClass(element, className, force) {
if (utils.is.element(element)) {
- const contains = element.classList.contains(className);
-
- element.classList[toggle ? 'add' : 'remove'](className);
+ let method = 'toggle';
+ if (typeof force !== 'undefined') {
+ method = force ? 'add' : 'remove';
+ }
- return (toggle && !contains) || (!toggle && contains);
+ element.classList[method](className);
+ return element.classList.contains(className);
}
return null;
@@ -595,7 +600,7 @@ const utils = {
return input;
}
- return input.toString().replace(/{(\d+)}/g, (match, i) => utils.is.string(args[i]) ? args[i] : '');
+ return input.toString().replace(/{(\d+)}/g, (match, i) => (utils.is.string(args[i]) ? args[i] : ''));
},
// Get percentage
@@ -718,6 +723,11 @@ const utils = {
return array.filter((item, index) => array.indexOf(item) === index);
},
+ // Clone nested objects
+ cloneDeep(object) {
+ return JSON.parse(JSON.stringify(object));
+ },
+
// Get the closest value in an array
closest(array, value) {
if (!utils.is.array(array) || !array.length) {