@cloudnest/redxplyr 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.editorconfig +10 -0
  2. package/.gitpod.yml +6 -0
  3. package/.node-version +1 -0
  4. package/.prettierrc +7 -0
  5. package/.stickler.yml +5 -0
  6. package/.stylelintrc.json +26 -0
  7. package/CHANGELOG.md +16 -0
  8. package/CONTRIBUTING.md +34 -0
  9. package/CONTROLS.md +49 -0
  10. package/Dockerfile +32 -0
  11. package/LICENSE.md +22 -0
  12. package/README.md +194 -0
  13. package/cspell.json +48 -0
  14. package/dist/redxplyr.css +1 -0
  15. package/dist/redxplyr.js +8801 -0
  16. package/dist/redxplyr.min.js +2 -0
  17. package/dist/redxplyr.min.js.map +1 -0
  18. package/dist/redxplyr.min.mjs +1 -0
  19. package/dist/redxplyr.min.mjs.map +1 -0
  20. package/dist/redxplyr.mjs +8793 -0
  21. package/dist/redxplyr.polyfilled.js +9294 -0
  22. package/dist/redxplyr.polyfilled.min.js +2 -0
  23. package/dist/redxplyr.polyfilled.min.js.map +1 -0
  24. package/dist/redxplyr.polyfilled.min.mjs +1 -0
  25. package/dist/redxplyr.polyfilled.min.mjs.map +1 -0
  26. package/dist/redxplyr.polyfilled.mjs +9286 -0
  27. package/dist/redxplyr.svg +1 -0
  28. package/eslint.config.mjs +39 -0
  29. package/gulpfile.js +8 -0
  30. package/package.json +114 -0
  31. package/pnpm-workspace.yaml +8 -0
  32. package/src/js/captions.js +411 -0
  33. package/src/js/config/defaults.js +459 -0
  34. package/src/js/config/states.js +10 -0
  35. package/src/js/config/types.js +34 -0
  36. package/src/js/console.js +28 -0
  37. package/src/js/controls.js +1870 -0
  38. package/src/js/fullscreen.js +305 -0
  39. package/src/js/html5.js +148 -0
  40. package/src/js/listeners.js +854 -0
  41. package/src/js/media.js +61 -0
  42. package/src/js/plugins/ads.js +647 -0
  43. package/src/js/plugins/preview-thumbnails.js +706 -0
  44. package/src/js/plugins/vimeo.js +443 -0
  45. package/src/js/plugins/youtube.js +451 -0
  46. package/src/js/plyr.d.ts +729 -0
  47. package/src/js/plyr.js +1291 -0
  48. package/src/js/plyr.polyfilled.js +13 -0
  49. package/src/js/source.js +155 -0
  50. package/src/js/storage.js +70 -0
  51. package/src/js/support.js +100 -0
  52. package/src/js/ui.js +297 -0
  53. package/src/js/utils/animation.js +33 -0
  54. package/src/js/utils/arrays.js +23 -0
  55. package/src/js/utils/browser.js +21 -0
  56. package/src/js/utils/elements.js +263 -0
  57. package/src/js/utils/events.js +116 -0
  58. package/src/js/utils/fetch.js +45 -0
  59. package/src/js/utils/i18n.js +47 -0
  60. package/src/js/utils/is.js +81 -0
  61. package/src/js/utils/load-image.js +19 -0
  62. package/src/js/utils/load-script.js +14 -0
  63. package/src/js/utils/load-sprite.js +77 -0
  64. package/src/js/utils/numbers.js +17 -0
  65. package/src/js/utils/objects.js +43 -0
  66. package/src/js/utils/promise.js +14 -0
  67. package/src/js/utils/strings.js +80 -0
  68. package/src/js/utils/style.js +148 -0
  69. package/src/js/utils/time.js +36 -0
  70. package/src/js/utils/urls.js +40 -0
  71. package/src/sass/base.scss +69 -0
  72. package/src/sass/components/badges.scss +12 -0
  73. package/src/sass/components/captions.scss +58 -0
  74. package/src/sass/components/control.scss +52 -0
  75. package/src/sass/components/controls.scss +65 -0
  76. package/src/sass/components/menus.scss +205 -0
  77. package/src/sass/components/poster.scss +27 -0
  78. package/src/sass/components/progress.scss +107 -0
  79. package/src/sass/components/sliders.scss +99 -0
  80. package/src/sass/components/times.scss +20 -0
  81. package/src/sass/components/tooltips.scss +91 -0
  82. package/src/sass/components/volume.scss +18 -0
  83. package/src/sass/lib/animation.scss +31 -0
  84. package/src/sass/lib/css-vars.scss +103 -0
  85. package/src/sass/lib/functions.scss +3 -0
  86. package/src/sass/lib/mixins.scss +82 -0
  87. package/src/sass/plugins/ads.scss +53 -0
  88. package/src/sass/plugins/preview-thumbnails/index.scss +121 -0
  89. package/src/sass/plugins/preview-thumbnails/settings.scss +17 -0
  90. package/src/sass/plyr.scss +46 -0
  91. package/src/sass/settings/badges.scss +7 -0
  92. package/src/sass/settings/breakpoints.scss +9 -0
  93. package/src/sass/settings/captions.scss +10 -0
  94. package/src/sass/settings/colors.scss +18 -0
  95. package/src/sass/settings/controls.scss +30 -0
  96. package/src/sass/settings/cosmetics.scss +5 -0
  97. package/src/sass/settings/helpers.scss +7 -0
  98. package/src/sass/settings/menus.scss +13 -0
  99. package/src/sass/settings/progress.scss +18 -0
  100. package/src/sass/settings/sliders.scss +39 -0
  101. package/src/sass/settings/tooltips.scss +11 -0
  102. package/src/sass/settings/type.scss +16 -0
  103. package/src/sass/states/fullscreen.scss +15 -0
  104. package/src/sass/types/audio.scss +61 -0
  105. package/src/sass/types/video.scss +170 -0
  106. package/src/sass/utils/animation.scss +7 -0
  107. package/src/sass/utils/hidden.scss +28 -0
  108. package/src/sprite/plyr-airplay.svg +8 -0
  109. package/src/sprite/plyr-captions-off.svg +7 -0
  110. package/src/sprite/plyr-captions-on.svg +7 -0
  111. package/src/sprite/plyr-download.svg +8 -0
  112. package/src/sprite/plyr-enter-fullscreen.svg +4 -0
  113. package/src/sprite/plyr-exit-fullscreen.svg +4 -0
  114. package/src/sprite/plyr-fast-forward.svg +3 -0
  115. package/src/sprite/plyr-logo-vimeo.svg +6 -0
  116. package/src/sprite/plyr-logo-youtube.svg +6 -0
  117. package/src/sprite/plyr-muted.svg +8 -0
  118. package/src/sprite/plyr-pause.svg +8 -0
  119. package/src/sprite/plyr-pip.svg +6 -0
  120. package/src/sprite/plyr-play.svg +5 -0
  121. package/src/sprite/plyr-restart.svg +5 -0
  122. package/src/sprite/plyr-rewind.svg +3 -0
  123. package/src/sprite/plyr-settings.svg +5 -0
  124. package/src/sprite/plyr-volume.svg +11 -0
  125. package/tasks/build.js +226 -0
  126. package/tasks/deploy.js +216 -0
  127. package/tasks/utils/publish.js +34 -0
@@ -0,0 +1,21 @@
1
+ // ==========================================================================
2
+ // Browser sniffing
3
+ // Unfortunately, due to mixed support, UA sniffing is required
4
+ // ==========================================================================
5
+
6
+ const isIE = Boolean(window.document.documentMode);
7
+ const isEdge = /Edge/.test(navigator.userAgent);
8
+ const isWebKit = 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent);
9
+ const isIPhone = /iPhone|iPod/i.test(navigator.userAgent) && navigator.maxTouchPoints > 1;
10
+ // navigator.platform may be deprecated but this check is still required
11
+ const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
12
+ const isIos = /iPad|iPhone|iPod/i.test(navigator.userAgent) && navigator.maxTouchPoints > 1;
13
+
14
+ export default {
15
+ isIE,
16
+ isEdge,
17
+ isWebKit,
18
+ isIPhone,
19
+ isIPadOS,
20
+ isIos,
21
+ };
@@ -0,0 +1,263 @@
1
+ // ==========================================================================
2
+ // Element utils
3
+ // ==========================================================================
4
+
5
+ import is from './is';
6
+ import { extend } from './objects';
7
+
8
+ // Wrap an element
9
+ export function wrap(elements, wrapper) {
10
+ // Convert `elements` to an array, if necessary.
11
+ const targets = elements.length ? elements : [elements];
12
+
13
+ // Loops backwards to prevent having to clone the wrapper on the
14
+ // first element (see `child` below).
15
+ Array.from(targets)
16
+ .reverse()
17
+ .forEach((element, index) => {
18
+ const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
19
+ // Cache the current parent and sibling.
20
+ const parent = element.parentNode;
21
+ const sibling = element.nextSibling;
22
+
23
+ // Wrap the element (is automatically removed from its current
24
+ // parent).
25
+ child.appendChild(element);
26
+
27
+ // If the element had a sibling, insert the wrapper before
28
+ // the sibling to maintain the HTML structure; otherwise, just
29
+ // append it to the parent.
30
+ if (sibling) {
31
+ parent.insertBefore(child, sibling);
32
+ }
33
+ else {
34
+ parent.appendChild(child);
35
+ }
36
+ });
37
+ }
38
+
39
+ // Set attributes
40
+ export function setAttributes(element, attributes) {
41
+ if (!is.element(element) || is.empty(attributes)) return;
42
+
43
+ // Assume null and undefined attributes should be left out,
44
+ // Setting them would otherwise convert them to "null" and "undefined"
45
+ Object.entries(attributes)
46
+ .filter(([, value]) => !is.nullOrUndefined(value))
47
+ .forEach(([key, value]) => element.setAttribute(key, value));
48
+ }
49
+
50
+ // Create a DocumentFragment
51
+ export function createElement(type, attributes, text) {
52
+ // Create a new <element>
53
+ const element = document.createElement(type);
54
+
55
+ // Set all passed attributes
56
+ if (is.object(attributes)) {
57
+ setAttributes(element, attributes);
58
+ }
59
+
60
+ // Add text node
61
+ if (is.string(text)) {
62
+ element.textContent = text;
63
+ }
64
+
65
+ // Return built element
66
+ return element;
67
+ }
68
+
69
+ // Insert an element after another
70
+ export function insertAfter(element, target) {
71
+ if (!is.element(element) || !is.element(target)) return;
72
+
73
+ target.parentNode.insertBefore(element, target.nextSibling);
74
+ }
75
+
76
+ // Insert a DocumentFragment
77
+ export function insertElement(type, parent, attributes, text) {
78
+ if (!is.element(parent)) return;
79
+
80
+ parent.appendChild(createElement(type, attributes, text));
81
+ }
82
+
83
+ // Remove element(s)
84
+ export function removeElement(element) {
85
+ if (is.nodeList(element) || is.array(element)) {
86
+ Array.from(element).forEach(removeElement);
87
+ return;
88
+ }
89
+
90
+ if (!is.element(element) || !is.element(element.parentNode)) {
91
+ return;
92
+ }
93
+
94
+ element.parentNode.removeChild(element);
95
+ }
96
+
97
+ // Remove all child elements
98
+ export function emptyElement(element) {
99
+ if (!is.element(element)) return;
100
+
101
+ let { length } = element.childNodes;
102
+
103
+ while (length > 0) {
104
+ element.removeChild(element.lastChild);
105
+ length -= 1;
106
+ }
107
+ }
108
+
109
+ // Replace element
110
+ export function replaceElement(newChild, oldChild) {
111
+ if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) return null;
112
+
113
+ oldChild.parentNode.replaceChild(newChild, oldChild);
114
+
115
+ return newChild;
116
+ }
117
+
118
+ // Get an attribute object from a string selector
119
+ export function getAttributesFromSelector(sel, existingAttributes) {
120
+ // For example:
121
+ // '.test' to { class: 'test' }
122
+ // '#test' to { id: 'test' }
123
+ // '[data-test="test"]' to { 'data-test': 'test' }
124
+
125
+ if (!is.string(sel) || is.empty(sel)) return {};
126
+
127
+ const attributes = {};
128
+ const existing = extend({}, existingAttributes);
129
+
130
+ sel.split(',').forEach((s) => {
131
+ // Remove whitespace
132
+ const selector = s.trim();
133
+ const className = selector.replace('.', '');
134
+ const stripped = selector.replace(/[[\]]/g, '');
135
+ // Get the parts and value
136
+ const parts = stripped.split('=');
137
+ const [key] = parts;
138
+ const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
139
+ // Get the first character
140
+ const start = selector.charAt(0);
141
+
142
+ switch (start) {
143
+ case '.':
144
+ // Add to existing classname
145
+ if (is.string(existing.class)) {
146
+ attributes.class = `${existing.class} ${className}`;
147
+ }
148
+ else {
149
+ attributes.class = className;
150
+ }
151
+ break;
152
+
153
+ case '#':
154
+ // ID selector
155
+ attributes.id = selector.replace('#', '');
156
+ break;
157
+
158
+ case '[':
159
+ // Attribute selector
160
+ attributes[key] = value;
161
+
162
+ break;
163
+
164
+ default:
165
+ break;
166
+ }
167
+ });
168
+
169
+ return extend(existing, attributes);
170
+ }
171
+
172
+ // Toggle hidden
173
+ export function toggleHidden(element, hidden) {
174
+ if (!is.element(element)) return;
175
+
176
+ let hide = hidden;
177
+
178
+ if (!is.boolean(hide)) {
179
+ hide = !element.hidden;
180
+ }
181
+
182
+ element.hidden = hide;
183
+ }
184
+
185
+ // Mirror Element.classList.toggle, with IE compatibility for "force" argument
186
+ export function toggleClass(element, className, force) {
187
+ if (is.nodeList(element)) {
188
+ return Array.from(element).map(e => toggleClass(e, className, force));
189
+ }
190
+
191
+ if (is.element(element)) {
192
+ let method = 'toggle';
193
+ if (typeof force !== 'undefined') {
194
+ method = force ? 'add' : 'remove';
195
+ }
196
+
197
+ element.classList[method](className);
198
+ return element.classList.contains(className);
199
+ }
200
+
201
+ return false;
202
+ }
203
+
204
+ // Has class name
205
+ export function hasClass(element, className) {
206
+ return is.element(element) && element.classList.contains(className);
207
+ }
208
+
209
+ // Element matches selector
210
+ export function matches(element, selector) {
211
+ const { prototype } = Element;
212
+
213
+ function match() {
214
+ return Array.from(document.querySelectorAll(selector)).includes(this);
215
+ }
216
+
217
+ const method
218
+ = prototype.matches
219
+ || prototype.webkitMatchesSelector
220
+ || prototype.mozMatchesSelector
221
+ || prototype.msMatchesSelector
222
+ || match;
223
+
224
+ return method.call(element, selector);
225
+ }
226
+
227
+ // Closest ancestor element matching selector (also tests element itself)
228
+ export function closest(element, selector) {
229
+ const { prototype } = Element;
230
+
231
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
232
+ function closestElement() {
233
+ let el = this;
234
+
235
+ do {
236
+ if (matches.matches(el, selector)) return el;
237
+ el = el.parentElement || el.parentNode;
238
+ } while (el !== null && el.nodeType === 1);
239
+ return null;
240
+ }
241
+
242
+ const method = prototype.closest || closestElement;
243
+
244
+ return method.call(element, selector);
245
+ }
246
+
247
+ // Find all elements
248
+ export function getElements(selector) {
249
+ return this.elements.container.querySelectorAll(selector);
250
+ }
251
+
252
+ // Find a single element
253
+ export function getElement(selector) {
254
+ return this.elements.container.querySelector(selector);
255
+ }
256
+
257
+ // Set focus and tab focus class
258
+ export function setFocus(element = null, focusVisible = false) {
259
+ if (!is.element(element)) return;
260
+
261
+ // Set regular focus
262
+ element.focus({ preventScroll: true, focusVisible });
263
+ }
@@ -0,0 +1,116 @@
1
+ // ==========================================================================
2
+ // Event utils
3
+ // ==========================================================================
4
+
5
+ import is from './is';
6
+
7
+ // Check for passive event listener support
8
+ // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
9
+ // https://www.youtube.com/watch?v=NPM6172J22g
10
+ const supportsPassiveListeners = (() => {
11
+ // Test via a getter in the options object to see if the passive property is accessed
12
+ let supported = false;
13
+ try {
14
+ const options = Object.defineProperty({}, 'passive', {
15
+ get() {
16
+ supported = true;
17
+ return null;
18
+ },
19
+ });
20
+ window.addEventListener('test', null, options);
21
+ window.removeEventListener('test', null, options);
22
+ }
23
+ catch {}
24
+
25
+ return supported;
26
+ })();
27
+
28
+ // Toggle event listener
29
+ export function toggleListener(element, event, callback, toggle = false, passive = true, capture = false) {
30
+ // Bail if no element, event, or callback
31
+ if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) {
32
+ return;
33
+ }
34
+
35
+ // Allow multiple events
36
+ const events = event.split(' ');
37
+ // Build options
38
+ // Default to just the capture boolean for browsers with no passive listener support
39
+ let options = capture;
40
+
41
+ // If passive events listeners are supported
42
+ if (supportsPassiveListeners) {
43
+ options = {
44
+ // Whether the listener can be passive (i.e. default never prevented)
45
+ passive,
46
+ // Whether the listener is a capturing listener or not
47
+ capture,
48
+ };
49
+ }
50
+
51
+ // If a single node is passed, bind the event listener
52
+ events.forEach((type) => {
53
+ if (this && this.eventListeners && toggle) {
54
+ // Cache event listener
55
+ this.eventListeners.push({ element, type, callback, options });
56
+ }
57
+
58
+ element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
59
+ });
60
+ }
61
+
62
+ // Bind event handler
63
+ export function on(element, events = '', callback, passive = true, capture = false) {
64
+ toggleListener.call(this, element, events, callback, true, passive, capture);
65
+ }
66
+
67
+ // Unbind event handler
68
+ export function off(element, events = '', callback, passive = true, capture = false) {
69
+ toggleListener.call(this, element, events, callback, false, passive, capture);
70
+ }
71
+
72
+ // Bind once-only event handler
73
+ export function once(element, events = '', callback, passive = true, capture = false) {
74
+ const onceCallback = (...args) => {
75
+ off(element, events, onceCallback, passive, capture);
76
+ callback.apply(this, args);
77
+ };
78
+
79
+ toggleListener.call(this, element, events, onceCallback, true, passive, capture);
80
+ }
81
+
82
+ // Trigger event
83
+ export function triggerEvent(element, type = '', bubbles = false, detail = {}) {
84
+ // Bail if no element
85
+ if (!is.element(element) || is.empty(type)) {
86
+ return;
87
+ }
88
+
89
+ // Create and dispatch the event
90
+ const event = new CustomEvent(type, {
91
+ bubbles,
92
+ detail: { ...detail, plyr: this },
93
+ });
94
+
95
+ // Dispatch the event
96
+ element.dispatchEvent(event);
97
+ }
98
+
99
+ // Unbind all cached event listeners
100
+ export function unbindListeners() {
101
+ if (this && this.eventListeners) {
102
+ this.eventListeners.forEach((item) => {
103
+ const { element, type, callback, options } = item;
104
+ element.removeEventListener(type, callback, options);
105
+ });
106
+
107
+ this.eventListeners = [];
108
+ }
109
+ }
110
+
111
+ // Run method when / if player is ready
112
+ export function ready() {
113
+ return new Promise(resolve =>
114
+ this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve),
115
+ ).then(() => {});
116
+ }
@@ -0,0 +1,45 @@
1
+ // ==========================================================================
2
+ // Fetch wrapper
3
+ // Using XHR to avoid issues with older browsers
4
+ // ==========================================================================
5
+
6
+ export default function fetch(url, responseType = 'text', withCredentials = false) {
7
+ return new Promise((resolve, reject) => {
8
+ try {
9
+ const request = new XMLHttpRequest();
10
+
11
+ // Check for CORS support
12
+ if (!('withCredentials' in request)) return;
13
+
14
+ // Set to true if needed for CORS
15
+ if (withCredentials) {
16
+ request.withCredentials = true;
17
+ }
18
+
19
+ request.addEventListener('load', () => {
20
+ if (responseType === 'text') {
21
+ try {
22
+ resolve(JSON.parse(request.responseText));
23
+ }
24
+ catch {
25
+ resolve(request.responseText);
26
+ }
27
+ }
28
+ else {
29
+ resolve(request.response);
30
+ }
31
+ });
32
+
33
+ request.addEventListener('error', () => {
34
+ throw new Error(request.status);
35
+ });
36
+
37
+ request.open('GET', url, true);
38
+ request.responseType = responseType;
39
+ request.send();
40
+ }
41
+ catch (error) {
42
+ reject(error);
43
+ }
44
+ });
45
+ }
@@ -0,0 +1,47 @@
1
+ // ==========================================================================
2
+ // Plyr internationalization
3
+ // ==========================================================================
4
+
5
+ import is from './is';
6
+ import { getDeep } from './objects';
7
+ import { replaceAll } from './strings';
8
+
9
+ // Skip i18n for abbreviations and brand names
10
+ const resources = {
11
+ pip: 'PIP',
12
+ airplay: 'AirPlay',
13
+ html5: 'HTML5',
14
+ vimeo: 'Vimeo',
15
+ youtube: 'YouTube',
16
+ };
17
+
18
+ const i18n = {
19
+ get(key = '', config = {}) {
20
+ if (is.empty(key) || is.empty(config)) {
21
+ return '';
22
+ }
23
+
24
+ let string = getDeep(config.i18n, key);
25
+
26
+ if (is.empty(string)) {
27
+ if (Object.keys(resources).includes(key)) {
28
+ return resources[key];
29
+ }
30
+
31
+ return '';
32
+ }
33
+
34
+ const replace = {
35
+ '{seektime}': config.seekTime,
36
+ '{title}': config.title,
37
+ };
38
+
39
+ Object.entries(replace).forEach(([k, v]) => {
40
+ string = replaceAll(string, k, v);
41
+ });
42
+
43
+ return string;
44
+ },
45
+ };
46
+
47
+ export default i18n;
@@ -0,0 +1,81 @@
1
+ // ==========================================================================
2
+ // Type checking utils
3
+ // ==========================================================================
4
+
5
+ const getConstructor = input => (input !== null && typeof input !== 'undefined' ? input.constructor : null);
6
+ const instanceOf = (input, constructor) => Boolean(input && constructor && input instanceof constructor);
7
+ const isNullOrUndefined = input => input === null || typeof input === 'undefined';
8
+ const isObject = input => getConstructor(input) === Object;
9
+ const isNumber = input => getConstructor(input) === Number && !Number.isNaN(input);
10
+ const isString = input => getConstructor(input) === String;
11
+ const isBoolean = input => getConstructor(input) === Boolean;
12
+ const isFunction = input => typeof input === 'function';
13
+ const isArray = input => Array.isArray(input);
14
+ const isWeakMap = input => instanceOf(input, WeakMap);
15
+ const isNodeList = input => instanceOf(input, NodeList);
16
+ const isTextNode = input => getConstructor(input) === Text;
17
+ const isEvent = input => instanceOf(input, Event);
18
+ const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);
19
+ const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
20
+ const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
21
+ const isPromise = input => instanceOf(input, Promise) && isFunction(input.then);
22
+
23
+ function isElement(input) {
24
+ return input !== null
25
+ && typeof input === 'object'
26
+ && input.nodeType === 1
27
+ && typeof input.style === 'object'
28
+ && typeof input.ownerDocument === 'object';
29
+ }
30
+
31
+ function isEmpty(input) {
32
+ return isNullOrUndefined(input)
33
+ || ((isString(input) || isArray(input) || isNodeList(input)) && !input.length)
34
+ || (isObject(input) && !Object.keys(input).length);
35
+ }
36
+
37
+ function isUrl(input) {
38
+ // Accept a URL object
39
+ if (instanceOf(input, window.URL)) {
40
+ return true;
41
+ }
42
+
43
+ // Must be string from here
44
+ if (!isString(input)) {
45
+ return false;
46
+ }
47
+
48
+ // Add the protocol if required
49
+ let string = input;
50
+ if (!input.startsWith('http://') || !input.startsWith('https://')) {
51
+ string = `http://${input}`;
52
+ }
53
+
54
+ try {
55
+ return !isEmpty(new URL(string).hostname);
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ export default {
63
+ nullOrUndefined: isNullOrUndefined,
64
+ object: isObject,
65
+ number: isNumber,
66
+ string: isString,
67
+ boolean: isBoolean,
68
+ function: isFunction,
69
+ array: isArray,
70
+ weakMap: isWeakMap,
71
+ nodeList: isNodeList,
72
+ element: isElement,
73
+ textNode: isTextNode,
74
+ event: isEvent,
75
+ keyboardEvent: isKeyboardEvent,
76
+ cue: isCue,
77
+ track: isTrack,
78
+ promise: isPromise,
79
+ url: isUrl,
80
+ empty: isEmpty,
81
+ };
@@ -0,0 +1,19 @@
1
+ // ==========================================================================
2
+ // Load image avoiding xhr/fetch CORS issues
3
+ // Server status can't be obtained this way unfortunately, so this uses "naturalWidth" to determine if the image has loaded
4
+ // By default it checks if it is at least 1px, but you can add a second argument to change this
5
+ // ==========================================================================
6
+
7
+ export default function loadImage(src, minWidth = 1) {
8
+ return new Promise((resolve, reject) => {
9
+ const image = new Image();
10
+
11
+ const handler = () => {
12
+ delete image.onload;
13
+ delete image.onerror;
14
+ (image.naturalWidth >= minWidth ? resolve : reject)(image);
15
+ };
16
+
17
+ Object.assign(image, { onload: handler, onerror: handler, src });
18
+ });
19
+ }
@@ -0,0 +1,14 @@
1
+ // ==========================================================================
2
+ // Load an external script
3
+ // ==========================================================================
4
+
5
+ import loadjs from 'loadjs';
6
+
7
+ export default function loadScript(url) {
8
+ return new Promise((resolve, reject) => {
9
+ loadjs(url, {
10
+ success: resolve,
11
+ error: reject,
12
+ });
13
+ });
14
+ }
@@ -0,0 +1,77 @@
1
+ // ==========================================================================
2
+ // Sprite loader
3
+ // ==========================================================================
4
+
5
+ import Storage from '../storage';
6
+ import fetch from './fetch';
7
+ import is from './is';
8
+
9
+ // Load an external SVG sprite
10
+ export default function loadSprite(url, id) {
11
+ if (!is.string(url)) {
12
+ return;
13
+ }
14
+
15
+ const prefix = 'cache';
16
+ const hasId = is.string(id);
17
+ let isCached = false;
18
+ const exists = () => document.getElementById(id) !== null;
19
+
20
+ const update = (container, data) => {
21
+ container.innerHTML = data;
22
+
23
+ // Check again incase of race condition
24
+ if (hasId && exists()) {
25
+ return;
26
+ }
27
+
28
+ // Inject the SVG to the body
29
+ document.body.insertAdjacentElement('afterbegin', container);
30
+ };
31
+
32
+ // Only load once if ID set
33
+ if (!hasId || !exists()) {
34
+ const useStorage = Storage.supported;
35
+ // Create container
36
+ const container = document.createElement('div');
37
+ container.setAttribute('hidden', '');
38
+
39
+ if (hasId) {
40
+ container.setAttribute('id', id);
41
+ }
42
+
43
+ // Check in cache
44
+ if (useStorage) {
45
+ const cached = window.localStorage.getItem(`${prefix}-${id}`);
46
+ isCached = cached !== null;
47
+
48
+ if (isCached) {
49
+ const data = JSON.parse(cached);
50
+ update(container, data.content);
51
+ }
52
+ }
53
+
54
+ // Get the sprite
55
+ fetch(url)
56
+ .then((result) => {
57
+ if (is.empty(result)) {
58
+ return;
59
+ }
60
+
61
+ if (useStorage) {
62
+ try {
63
+ window.localStorage.setItem(
64
+ `${prefix}-${id}`,
65
+ JSON.stringify({
66
+ content: result,
67
+ }),
68
+ );
69
+ }
70
+ catch {}
71
+ }
72
+
73
+ update(container, result);
74
+ })
75
+ .catch(() => {});
76
+ }
77
+ }