uix-kit
Version:
A free web kits for fast web design and development, compatible with Bootstrap v5.
1,741 lines (1,514 loc) • 1.89 MB
JavaScript
/**
* @license
* Video.js 7.5.3 <http://videojs.com/>
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
* Available under Apache License Version 2.0
* <https://github.com/videojs/video.js/blob/master/LICENSE>
*
* Includes vtt.js <https://github.com/mozilla/vtt.js>
* Available under Apache License Version 2.0
* <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('global/window'), require('global/document')) :
typeof define === 'function' && define.amd ? define(['global/window', 'global/document'], factory) :
(global = global || self, global.videojs = factory(global.window, global.document));
}(this, function (window$1, document) {
window$1 = window$1 && window$1.hasOwnProperty('default') ? window$1['default'] : window$1;
document = document && document.hasOwnProperty('default') ? document['default'] : document;
var version = "7.5.3";
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _construct(Parent, args, Class) {
if (isNativeReflectConstruct()) {
_construct = Reflect.construct;
} else {
_construct = function _construct(Parent, args, Class) {
var a = [null];
a.push.apply(a, args);
var Constructor = Function.bind.apply(Parent, a);
var instance = new Constructor();
if (Class) _setPrototypeOf(instance, Class.prototype);
return instance;
};
}
return _construct.apply(null, arguments);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _taggedTemplateLiteralLoose(strings, raw) {
if (!raw) {
raw = strings.slice(0);
}
strings.raw = raw;
return strings;
}
/**
* @file create-logger.js
* @module create-logger
*/
var history = [];
/**
* Log messages to the console and history based on the type of message
*
* @private
* @param {string} type
* The name of the console method to use.
*
* @param {Array} args
* The arguments to be passed to the matching console method.
*/
var LogByTypeFactory = function LogByTypeFactory(name, log) {
return function (type, level, args) {
var lvl = log.levels[level];
var lvlRegExp = new RegExp("^(" + lvl + ")$");
if (type !== 'log') {
// Add the type to the front of the message when it's not "log".
args.unshift(type.toUpperCase() + ':');
} // Add console prefix after adding to history.
args.unshift(name + ':'); // Add a clone of the args at this point to history.
if (history) {
history.push([].concat(args));
} // If there's no console then don't try to output messages, but they will
// still be stored in history.
if (!window$1.console) {
return;
} // Was setting these once outside of this function, but containing them
// in the function makes it easier to test cases where console doesn't exist
// when the module is executed.
var fn = window$1.console[type];
if (!fn && type === 'debug') {
// Certain browsers don't have support for console.debug. For those, we
// should default to the closest comparable log.
fn = window$1.console.info || window$1.console.log;
} // Bail out if there's no console or if this type is not allowed by the
// current logging level.
if (!fn || !lvl || !lvlRegExp.test(type)) {
return;
}
fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
};
};
function createLogger(name) {
// This is the private tracking variable for logging level.
var level = 'info'; // the curried logByType bound to the specific log and history
var logByType;
/**
* Logs plain debug messages. Similar to `console.log`.
*
* Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
* of our JSDoc template, we cannot properly document this as both a function
* and a namespace, so its function signature is documented here.
*
* #### Arguments
* ##### *args
* Mixed[]
*
* Any combination of values that could be passed to `console.log()`.
*
* #### Return Value
*
* `undefined`
*
* @namespace
* @param {Mixed[]} args
* One or more messages or objects that should be logged.
*/
var log = function log() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
logByType('log', level, args);
}; // This is the logByType helper that the logging methods below use
logByType = LogByTypeFactory(name, log);
/**
* Create a new sublogger which chains the old name to the new name.
*
* For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
* ```js
* mylogger('foo');
* // > VIDEOJS: player: foo
* ```
*
* @param {string} name
* The name to add call the new logger
* @return {Object}
*/
log.createLogger = function (subname) {
return createLogger(name + ': ' + subname);
};
/**
* Enumeration of available logging levels, where the keys are the level names
* and the values are `|`-separated strings containing logging methods allowed
* in that logging level. These strings are used to create a regular expression
* matching the function name being called.
*
* Levels provided by Video.js are:
*
* - `off`: Matches no calls. Any value that can be cast to `false` will have
* this effect. The most restrictive.
* - `all`: Matches only Video.js-provided functions (`debug`, `log`,
* `log.warn`, and `log.error`).
* - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
* - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
* - `warn`: Matches `log.warn` and `log.error` calls.
* - `error`: Matches only `log.error` calls.
*
* @type {Object}
*/
log.levels = {
all: 'debug|log|warn|error',
off: '',
debug: 'debug|log|warn|error',
info: 'log|warn|error',
warn: 'warn|error',
error: 'error',
DEFAULT: level
};
/**
* Get or set the current logging level.
*
* If a string matching a key from {@link module:log.levels} is provided, acts
* as a setter.
*
* @param {string} [lvl]
* Pass a valid level to set a new logging level.
*
* @return {string}
* The current logging level.
*/
log.level = function (lvl) {
if (typeof lvl === 'string') {
if (!log.levels.hasOwnProperty(lvl)) {
throw new Error("\"" + lvl + "\" in not a valid log level");
}
level = lvl;
}
return level;
};
/**
* Returns an array containing everything that has been logged to the history.
*
* This array is a shallow clone of the internal history record. However, its
* contents are _not_ cloned; so, mutating objects inside this array will
* mutate them in history.
*
* @return {Array}
*/
log.history = function () {
return history ? [].concat(history) : [];
};
/**
* Allows you to filter the history by the given logger name
*
* @param {string} fname
* The name to filter by
*
* @return {Array}
* The filtered list to return
*/
log.history.filter = function (fname) {
return (history || []).filter(function (historyItem) {
// if the first item in each historyItem includes `fname`, then it's a match
return new RegExp(".*" + fname + ".*").test(historyItem[0]);
});
};
/**
* Clears the internal history tracking, but does not prevent further history
* tracking.
*/
log.history.clear = function () {
if (history) {
history.length = 0;
}
};
/**
* Disable history tracking if it is currently enabled.
*/
log.history.disable = function () {
if (history !== null) {
history.length = 0;
history = null;
}
};
/**
* Enable history tracking if it is currently disabled.
*/
log.history.enable = function () {
if (history === null) {
history = [];
}
};
/**
* Logs error messages. Similar to `console.error`.
*
* @param {Mixed[]} args
* One or more messages or objects that should be logged as an error
*/
log.error = function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return logByType('error', level, args);
};
/**
* Logs warning messages. Similar to `console.warn`.
*
* @param {Mixed[]} args
* One or more messages or objects that should be logged as a warning.
*/
log.warn = function () {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return logByType('warn', level, args);
};
/**
* Logs debug messages. Similar to `console.debug`, but may also act as a comparable
* log if `console.debug` is not available
*
* @param {Mixed[]} args
* One or more messages or objects that should be logged as debug.
*/
log.debug = function () {
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
return logByType('debug', level, args);
};
return log;
}
/**
* @file log.js
* @module log
*/
var log = createLogger('VIDEOJS');
var createLogger$1 = log.createLogger;
function clean(s) {
return s.replace(/\n\r?\s*/g, '');
}
var tsml = function tsml(sa) {
var s = '',
i = 0;
for (; i < arguments.length; i++) {
s += clean(sa[i]) + (arguments[i + 1] || '');
}
return s;
};
/**
* @file obj.js
* @module obj
*/
/**
* @callback obj:EachCallback
*
* @param {Mixed} value
* The current key for the object that is being iterated over.
*
* @param {string} key
* The current key-value for object that is being iterated over
*/
/**
* @callback obj:ReduceCallback
*
* @param {Mixed} accum
* The value that is accumulating over the reduce loop.
*
* @param {Mixed} value
* The current key for the object that is being iterated over.
*
* @param {string} key
* The current key-value for object that is being iterated over
*
* @return {Mixed}
* The new accumulated value.
*/
var toString = Object.prototype.toString;
/**
* Get the keys of an Object
*
* @param {Object}
* The Object to get the keys from
*
* @return {string[]}
* An array of the keys from the object. Returns an empty array if the
* object passed in was invalid or had no keys.
*
* @private
*/
var keys = function keys(object) {
return isObject(object) ? Object.keys(object) : [];
};
/**
* Array-like iteration for objects.
*
* @param {Object} object
* The object to iterate over
*
* @param {obj:EachCallback} fn
* The callback function which is called for each key in the object.
*/
function each(object, fn) {
keys(object).forEach(function (key) {
return fn(object[key], key);
});
}
/**
* Array-like reduce for objects.
*
* @param {Object} object
* The Object that you want to reduce.
*
* @param {Function} fn
* A callback function which is called for each key in the object. It
* receives the accumulated value and the per-iteration value and key
* as arguments.
*
* @param {Mixed} [initial = 0]
* Starting value
*
* @return {Mixed}
* The final accumulated value.
*/
function reduce(object, fn, initial) {
if (initial === void 0) {
initial = 0;
}
return keys(object).reduce(function (accum, key) {
return fn(accum, object[key], key);
}, initial);
}
/**
* Object.assign-style object shallow merge/extend.
*
* @param {Object} target
* @param {Object} ...sources
* @return {Object}
*/
function assign(target) {
for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
sources[_key - 1] = arguments[_key];
}
if (Object.assign) {
return Object.assign.apply(Object, [target].concat(sources));
}
sources.forEach(function (source) {
if (!source) {
return;
}
each(source, function (value, key) {
target[key] = value;
});
});
return target;
}
/**
* Returns whether a value is an object of any kind - including DOM nodes,
* arrays, regular expressions, etc. Not functions, though.
*
* This avoids the gotcha where using `typeof` on a `null` value
* results in `'object'`.
*
* @param {Object} value
* @return {boolean}
*/
function isObject(value) {
return !!value && typeof value === 'object';
}
/**
* Returns whether an object appears to be a "plain" object - that is, a
* direct instance of `Object`.
*
* @param {Object} value
* @return {boolean}
*/
function isPlain(value) {
return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
}
/**
* @file computed-style.js
* @module computed-style
*/
/**
* A safe getComputedStyle.
*
* This is needed because in Firefox, if the player is loaded in an iframe with
* `display:none`, then `getComputedStyle` returns `null`, so, we do a
* null-check to make sure that the player doesn't break in these cases.
*
* @function
* @param {Element} el
* The element you want the computed style of
*
* @param {string} prop
* The property name you want
*
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
*/
function computedStyle(el, prop) {
if (!el || !prop) {
return '';
}
if (typeof window$1.getComputedStyle === 'function') {
var cs = window$1.getComputedStyle(el);
return cs ? cs[prop] : '';
}
return '';
}
function _templateObject() {
var data = _taggedTemplateLiteralLoose(["Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ", " to ", "."]);
_templateObject = function _templateObject() {
return data;
};
return data;
}
/**
* Detect if a value is a string with any non-whitespace characters.
*
* @private
* @param {string} str
* The string to check
*
* @return {boolean}
* Will be `true` if the string is non-blank, `false` otherwise.
*
*/
function isNonBlankString(str) {
return typeof str === 'string' && /\S/.test(str);
}
/**
* Throws an error if the passed string has whitespace. This is used by
* class methods to be relatively consistent with the classList API.
*
* @private
* @param {string} str
* The string to check for whitespace.
*
* @throws {Error}
* Throws an error if there is whitespace in the string.
*/
function throwIfWhitespace(str) {
if (/\s/.test(str)) {
throw new Error('class has illegal whitespace characters');
}
}
/**
* Produce a regular expression for matching a className within an elements className.
*
* @private
* @param {string} className
* The className to generate the RegExp for.
*
* @return {RegExp}
* The RegExp that will check for a specific `className` in an elements
* className.
*/
function classRegExp(className) {
return new RegExp('(^|\\s)' + className + '($|\\s)');
}
/**
* Whether the current DOM interface appears to be real (i.e. not simulated).
*
* @return {boolean}
* Will be `true` if the DOM appears to be real, `false` otherwise.
*/
function isReal() {
// Both document and window will never be undefined thanks to `global`.
return document === window$1.document;
}
/**
* Determines, via duck typing, whether or not a value is a DOM element.
*
* @param {Mixed} value
* The value to check.
*
* @return {boolean}
* Will be `true` if the value is a DOM element, `false` otherwise.
*/
function isEl(value) {
return isObject(value) && value.nodeType === 1;
}
/**
* Determines if the current DOM is embedded in an iframe.
*
* @return {boolean}
* Will be `true` if the DOM is embedded in an iframe, `false`
* otherwise.
*/
function isInFrame() {
// We need a try/catch here because Safari will throw errors when attempting
// to get either `parent` or `self`
try {
return window$1.parent !== window$1.self;
} catch (x) {
return true;
}
}
/**
* Creates functions to query the DOM using a given method.
*
* @private
* @param {string} method
* The method to create the query with.
*
* @return {Function}
* The query method
*/
function createQuerier(method) {
return function (selector, context) {
if (!isNonBlankString(selector)) {
return document[method](null);
}
if (isNonBlankString(context)) {
context = document.querySelector(context);
}
var ctx = isEl(context) ? context : document;
return ctx[method] && ctx[method](selector);
};
}
/**
* Creates an element and applies properties, attributes, and inserts content.
*
* @param {string} [tagName='div']
* Name of tag to be created.
*
* @param {Object} [properties={}]
* Element properties to be applied.
*
* @param {Object} [attributes={}]
* Element attributes to be applied.
*
* @param {module:dom~ContentDescriptor} content
* A content descriptor object.
*
* @return {Element}
* The element that was created.
*/
function createEl(tagName, properties, attributes, content) {
if (tagName === void 0) {
tagName = 'div';
}
if (properties === void 0) {
properties = {};
}
if (attributes === void 0) {
attributes = {};
}
var el = document.createElement(tagName);
Object.getOwnPropertyNames(properties).forEach(function (propName) {
var val = properties[propName]; // See #2176
// We originally were accepting both properties and attributes in the
// same object, but that doesn't work so well.
if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
log.warn(tsml(_templateObject(), propName, val));
el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
// method for it.
} else if (propName === 'textContent') {
textContent(el, val);
} else {
el[propName] = val;
}
});
Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
el.setAttribute(attrName, attributes[attrName]);
});
if (content) {
appendContent(el, content);
}
return el;
}
/**
* Injects text into an element, replacing any existing contents entirely.
*
* @param {Element} el
* The element to add text content into
*
* @param {string} text
* The text content to add.
*
* @return {Element}
* The element with added text content.
*/
function textContent(el, text) {
if (typeof el.textContent === 'undefined') {
el.innerText = text;
} else {
el.textContent = text;
}
return el;
}
/**
* Insert an element as the first child node of another
*
* @param {Element} child
* Element to insert
*
* @param {Element} parent
* Element to insert child into
*/
function prependTo(child, parent) {
if (parent.firstChild) {
parent.insertBefore(child, parent.firstChild);
} else {
parent.appendChild(child);
}
}
/**
* Check if an element has a class name.
*
* @param {Element} element
* Element to check
*
* @param {string} classToCheck
* Class name to check for
*
* @return {boolean}
* Will be `true` if the element has a class, `false` otherwise.
*
* @throws {Error}
* Throws an error if `classToCheck` has white space.
*/
function hasClass(element, classToCheck) {
throwIfWhitespace(classToCheck);
if (element.classList) {
return element.classList.contains(classToCheck);
}
return classRegExp(classToCheck).test(element.className);
}
/**
* Add a class name to an element.
*
* @param {Element} element
* Element to add class name to.
*
* @param {string} classToAdd
* Class name to add.
*
* @return {Element}
* The DOM element with the added class name.
*/
function addClass(element, classToAdd) {
if (element.classList) {
element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
// in the case of classList not being supported.
} else if (!hasClass(element, classToAdd)) {
element.className = (element.className + ' ' + classToAdd).trim();
}
return element;
}
/**
* Remove a class name from an element.
*
* @param {Element} element
* Element to remove a class name from.
*
* @param {string} classToRemove
* Class name to remove
*
* @return {Element}
* The DOM element with class name removed.
*/
function removeClass(element, classToRemove) {
if (element.classList) {
element.classList.remove(classToRemove);
} else {
throwIfWhitespace(classToRemove);
element.className = element.className.split(/\s+/).filter(function (c) {
return c !== classToRemove;
}).join(' ');
}
return element;
}
/**
* The callback definition for toggleClass.
*
* @callback module:dom~PredicateCallback
* @param {Element} element
* The DOM element of the Component.
*
* @param {string} classToToggle
* The `className` that wants to be toggled
*
* @return {boolean|undefined}
* If `true` is returned, the `classToToggle` will be added to the
* `element`. If `false`, the `classToToggle` will be removed from
* the `element`. If `undefined`, the callback will be ignored.
*/
/**
* Adds or removes a class name to/from an element depending on an optional
* condition or the presence/absence of the class name.
*
* @param {Element} element
* The element to toggle a class name on.
*
* @param {string} classToToggle
* The class that should be toggled.
*
* @param {boolean|module:dom~PredicateCallback} [predicate]
* See the return value for {@link module:dom~PredicateCallback}
*
* @return {Element}
* The element with a class that has been toggled.
*/
function toggleClass(element, classToToggle, predicate) {
// This CANNOT use `classList` internally because IE11 does not support the
// second parameter to the `classList.toggle()` method! Which is fine because
// `classList` will be used by the add/remove functions.
var has = hasClass(element, classToToggle);
if (typeof predicate === 'function') {
predicate = predicate(element, classToToggle);
}
if (typeof predicate !== 'boolean') {
predicate = !has;
} // If the necessary class operation matches the current state of the
// element, no action is required.
if (predicate === has) {
return;
}
if (predicate) {
addClass(element, classToToggle);
} else {
removeClass(element, classToToggle);
}
return element;
}
/**
* Apply attributes to an HTML element.
*
* @param {Element} el
* Element to add attributes to.
*
* @param {Object} [attributes]
* Attributes to be applied.
*/
function setAttributes(el, attributes) {
Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
var attrValue = attributes[attrName];
if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
el.removeAttribute(attrName);
} else {
el.setAttribute(attrName, attrValue === true ? '' : attrValue);
}
});
}
/**
* Get an element's attribute values, as defined on the HTML tag.
*
* Attributes are not the same as properties. They're defined on the tag
* or with setAttribute.
*
* @param {Element} tag
* Element from which to get tag attributes.
*
* @return {Object}
* All attributes of the element. Boolean attributes will be `true` or
* `false`, others will be strings.
*/
function getAttributes(tag) {
var obj = {}; // known boolean attributes
// we can check for matching boolean properties, but not all browsers
// and not all tags know about these attributes, so, we still want to check them manually
var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
if (tag && tag.attributes && tag.attributes.length > 0) {
var attrs = tag.attributes;
for (var i = attrs.length - 1; i >= 0; i--) {
var attrName = attrs[i].name;
var attrVal = attrs[i].value; // check for known booleans
// the matching element property will return a value for typeof
if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
// the value of an included boolean attribute is typically an empty
// string ('') which would equal false if we just check for a false value.
// we also don't want support bad code like autoplay='false'
attrVal = attrVal !== null ? true : false;
}
obj[attrName] = attrVal;
}
}
return obj;
}
/**
* Get the value of an element's attribute.
*
* @param {Element} el
* A DOM element.
*
* @param {string} attribute
* Attribute to get the value of.
*
* @return {string}
* The value of the attribute.
*/
function getAttribute(el, attribute) {
return el.getAttribute(attribute);
}
/**
* Set the value of an element's attribute.
*
* @param {Element} el
* A DOM element.
*
* @param {string} attribute
* Attribute to set.
*
* @param {string} value
* Value to set the attribute to.
*/
function setAttribute(el, attribute, value) {
el.setAttribute(attribute, value);
}
/**
* Remove an element's attribute.
*
* @param {Element} el
* A DOM element.
*
* @param {string} attribute
* Attribute to remove.
*/
function removeAttribute(el, attribute) {
el.removeAttribute(attribute);
}
/**
* Attempt to block the ability to select text.
*/
function blockTextSelection() {
document.body.focus();
document.onselectstart = function () {
return false;
};
}
/**
* Turn off text selection blocking.
*/
function unblockTextSelection() {
document.onselectstart = function () {
return true;
};
}
/**
* Identical to the native `getBoundingClientRect` function, but ensures that
* the method is supported at all (it is in all browsers we claim to support)
* and that the element is in the DOM before continuing.
*
* This wrapper function also shims properties which are not provided by some
* older browsers (namely, IE8).
*
* Additionally, some browsers do not support adding properties to a
* `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
* properties (except `x` and `y` which are not widely supported). This helps
* avoid implementations where keys are non-enumerable.
*
* @param {Element} el
* Element whose `ClientRect` we want to calculate.
*
* @return {Object|undefined}
* Always returns a plain object - or `undefined` if it cannot.
*/
function getBoundingClientRect(el) {
if (el && el.getBoundingClientRect && el.parentNode) {
var rect = el.getBoundingClientRect();
var result = {};
['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
if (rect[k] !== undefined) {
result[k] = rect[k];
}
});
if (!result.height) {
result.height = parseFloat(computedStyle(el, 'height'));
}
if (!result.width) {
result.width = parseFloat(computedStyle(el, 'width'));
}
return result;
}
}
/**
* Represents the position of a DOM element on the page.
*
* @typedef {Object} module:dom~Position
*
* @property {number} left
* Pixels to the left.
*
* @property {number} top
* Pixels from the top.
*/
/**
* Get the position of an element in the DOM.
*
* Uses `getBoundingClientRect` technique from John Resig.
*
* @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
*
* @param {Element} el
* Element from which to get offset.
*
* @return {module:dom~Position}
* The position of the element that was passed in.
*/
function findPosition(el) {
var box;
if (el.getBoundingClientRect && el.parentNode) {
box = el.getBoundingClientRect();
}
if (!box) {
return {
left: 0,
top: 0
};
}
var docEl = document.documentElement;
var body = document.body;
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
var scrollLeft = window$1.pageXOffset || body.scrollLeft;
var left = box.left + scrollLeft - clientLeft;
var clientTop = docEl.clientTop || body.clientTop || 0;
var scrollTop = window$1.pageYOffset || body.scrollTop;
var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round
return {
left: Math.round(left),
top: Math.round(top)
};
}
/**
* Represents x and y coordinates for a DOM element or mouse pointer.
*
* @typedef {Object} module:dom~Coordinates
*
* @property {number} x
* x coordinate in pixels
*
* @property {number} y
* y coordinate in pixels
*/
/**
* Get the pointer position within an element.
*
* The base on the coordinates are the bottom left of the element.
*
* @param {Element} el
* Element on which to get the pointer position on.
*
* @param {EventTarget~Event} event
* Event object.
*
* @return {module:dom~Coordinates}
* A coordinates object corresponding to the mouse position.
*
*/
function getPointerPosition(el, event) {
var position = {};
var box = findPosition(el);
var boxW = el.offsetWidth;
var boxH = el.offsetHeight;
var boxY = box.top;
var boxX = box.left;
var pageY = event.pageY;
var pageX = event.pageX;
if (event.changedTouches) {
pageX = event.changedTouches[0].pageX;
pageY = event.changedTouches[0].pageY;
}
position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
return position;
}
/**
* Determines, via duck typing, whether or not a value is a text node.
*
* @param {Mixed} value
* Check if this value is a text node.
*
* @return {boolean}
* Will be `true` if the value is a text node, `false` otherwise.
*/
function isTextNode(value) {
return isObject(value) && value.nodeType === 3;
}
/**
* Empties the contents of an element.
*
* @param {Element} el
* The element to empty children from
*
* @return {Element}
* The element with no children
*/
function emptyEl(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
return el;
}
/**
* This is a mixed value that describes content to be injected into the DOM
* via some method. It can be of the following types:
*
* Type | Description
* -----------|-------------
* `string` | The value will be normalized into a text node.
* `Element` | The value will be accepted as-is.
* `TextNode` | The value will be accepted as-is.
* `Array` | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored).
* `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes.
*
* @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
*/
/**
* Normalizes content for eventual insertion into the DOM.
*
* This allows a wide range of content definition methods, but helps protect
* from falling into the trap of simply writing to `innerHTML`, which could
* be an XSS concern.
*
* The content for an element can be passed in multiple types and
* combinations, whose behavior is as follows:
*
* @param {module:dom~ContentDescriptor} content
* A content descriptor value.
*
* @return {Array}
* All of the content that was passed in, normalized to an array of
* elements or text nodes.
*/
function normalizeContent(content) {
// First, invoke content if it is a function. If it produces an array,
// that needs to happen before normalization.
if (typeof content === 'function') {
content = content();
} // Next up, normalize to an array, so one or many items can be normalized,
// filtered, and returned.
return (Array.isArray(content) ? content : [content]).map(function (value) {
// First, invoke value if it is a function to produce a new value,
// which will be subsequently normalized to a Node of some kind.
if (typeof value === 'function') {
value = value();
}
if (isEl(value) || isTextNode(value)) {
return value;
}
if (typeof value === 'string' && /\S/.test(value)) {
return document.createTextNode(value);
}
}).filter(function (value) {
return value;
});
}
/**
* Normalizes and appends content to an element.
*
* @param {Element} el
* Element to append normalized content to.
*
* @param {module:dom~ContentDescriptor} content
* A content descriptor value.
*
* @return {Element}
* The element with appended normalized content.
*/
function appendContent(el, content) {
normalizeContent(content).forEach(function (node) {
return el.appendChild(node);
});
return el;
}
/**
* Normalizes and inserts content into an element; this is identical to
* `appendContent()`, except it empties the element first.
*
* @param {Element} el
* Element to insert normalized content into.
*
* @param {module:dom~ContentDescriptor} content
* A content descriptor value.
*
* @return {Element}
* The element with inserted normalized content.
*/
function insertContent(el, content) {
return appendContent(emptyEl(el), content);
}
/**
* Check if an event was a single left click.
*
* @param {EventTarget~Event} event
* Event object.
*
* @return {boolean}
* Will be `true` if a single left click, `false` otherwise.
*/
function isSingleLeftClick(event) {
// Note: if you create something draggable, be sure to
// call it on both `mousedown` and `mousemove` event,
// otherwise `mousedown` should be enough for a button
if (event.button === undefined && event.buttons === undefined) {
// Why do we need `buttons` ?
// Because, middle mouse sometimes have this:
// e.button === 0 and e.buttons === 4
// Furthermore, we want to prevent combination click, something like
// HOLD middlemouse then left click, that would be
// e.button === 0, e.buttons === 5
// just `button` is not gonna work
// Alright, then what this block does ?
// this is for chrome `simulate mobile devices`
// I want to support this as well
return true;
}
if (event.button === 0 && event.buttons === undefined) {
// Touch screen, sometimes on some specific device, `buttons`
// doesn't have anything (safari on ios, blackberry...)
return true;
}
if (event.button !== 0 || event.buttons !== 1) {
// This is the reason we have those if else block above
// if any special case we can catch and let it slide
// we do it above, when get to here, this definitely
// is-not-left-click
return false;
}
return true;
}
/**
* Finds a single DOM element matching `selector` within the optional
* `context` of another DOM element (defaulting to `document`).
*
* @param {string} selector
* A valid CSS selector, which will be passed to `querySelector`.
*
* @param {Element|String} [context=document]
* A DOM element within which to query. Can also be a selector
* string in which case the first matching element will be used
* as context. If missing (or no element matches selector), falls
* back to `document`.
*
* @return {Element|null}
* The element that was found or null.
*/
var $ = createQuerier('querySelector');
/**
* Finds a all DOM elements matching `selector` within the optional
* `context` of another DOM element (defaulting to `document`).
*
* @param {string} selector
* A valid CSS selector, which will be passed to `querySelectorAll`.
*
* @param {Element|String} [context=document]
* A DOM element within which to query. Can also be a selector
* string in which case the first matching element will be used
* as context. If missing (or no element matches selector), falls
* back to `document`.
*
* @return {NodeList}
* A element list of elements that were found. Will be empty if none
* were found.
*
*/
var $$ = createQuerier('querySelectorAll');
var Dom = /*#__PURE__*/Object.freeze({
isReal: isReal,
isEl: isEl,
isInFrame: isInFrame,
createEl: createEl,
textContent: textContent,
prependTo: prependTo,
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
toggleClass: toggleClass,
setAttributes: setAttributes,
getAttributes: getAttributes,
getAttribute: getAttribute,
setAttribute: setAttribute,
removeAttribute: removeAttribute,
blockTextSelection: blockTextSelection,
unblockTextSelection: unblockTextSelection,
getBoundingClientRect: getBoundingClientRect,
findPosition: findPosition,
getPointerPosition: getPointerPosition,
isTextNode: isTextNode,
emptyEl: emptyEl,
normalizeContent: normalizeContent,
appendContent: appendContent,
insertContent: insertContent,
isSingleLeftClick: isSingleLeftClick,
$: $,
$$: $$
});
/**
* @file guid.js
* @module guid
*/
/**
* Unique ID for an element or function
* @type {Number}
*/
var _guid = 1;
/**
* Get a unique auto-incrementing ID by number that has not been returned before.
*
* @return {number}
* A new unique ID.
*/
function newGUID() {
return _guid++;
}
/**
* @file dom-data.js
* @module dom-data
*/
/**
* Element Data Store.
*
* Allows for binding data to an element without putting it directly on the
* element. Ex. Event listeners are stored here.
* (also from jsninja.com, slightly modified and updated for closure compiler)
*
* @type {Object}
* @private
*/
var elData = {};
/*
* Unique attribute name to store an element's guid in
*
* @type {String}
* @constant
* @private
*/
var elIdAttr = 'vdata' + new Date().getTime();
/**
* Returns the cache object where data for an element is stored
*
* @param {Element} el
* Element to store data for.
*
* @return {Object}
* The cache object for that el that was passed in.
*/
function getData(el) {
var id = el[elIdAttr];
if (!id) {
id = el[elIdAttr] = newGUID();
}
if (!elData[id]) {
elData[id] = {};
}
return elData[id];
}
/**
* Returns whether or not an element has cached data
*
* @param {Element} el
* Check if this element has cached data.
*
* @return {boolean}
* - True if the DOM element has cached data.
* - False otherwise.
*/
function hasData(el) {
var id = el[elIdAttr];
if (!id) {
return false;
}
return !!Object.getOwnPropertyNames(elData[id]).length;
}
/**
* Delete data for the element from the cache and the guid attr from getElementById
*
* @param {Element} el
* Remove cached data for this element.
*/
function removeData(el) {
var id = el[elIdAttr];
if (!id) {
return;
} // Remove all stored data
delete elData[id]; // Remove the elIdAttr property from the DOM node
try {
delete el[elIdAttr];
} catch (e) {
if (el.removeAttribute) {
el.removeAttribute(elIdAttr);
} else {
// IE doesn't appear to support removeAttribute on the document element
el[elIdAttr] = null;
}
}
}
/**
* @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
* (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
* This should work very similarly to jQuery's events, however it's based off the book version which isn't as
* robust as jquery's, so there's probably some differences.
*
* @file events.js
* @module events
*/
/**
* Clean up the listener cache and dispatchers
*
* @param {Element|Object} elem
* Element to clean up
*
* @param {string} type
* Type of event to clean up
*/
function _cleanUpEvents(elem, type) {
var data = getData(elem); // Remove the events of a particular type if there are none left
if (data.handlers[type].length === 0) {
delete data.handlers[type]; // data.handlers[type] = null;
// Setting to null was causing an error with data.handlers
// Remove the meta-handler from the element
if (elem.removeEventListener) {
elem.removeEventListener(type, data.dispatcher, false);
} else if (elem.detachEvent) {
elem.detachEvent('on' + type, data.dispatcher);
}
} // Remove the events object if there are no types left
if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
delete data.handlers;
delete data.dispatcher;
delete data.disabled;
} // Finally remove the element data if there is no data left
if (Object.getOwnPropertyNames(data).length === 0) {
removeData(elem);
}
}
/**
* Loops through an array of event types and calls the requested method for each type.
*
* @param {Function} fn
* The event method we want to use.
*
* @param {Element|Object} elem
* Element or object to bind listeners to
*
* @param {string} type
* Type of event to bind to.
*
* @param {EventTarget~EventListener} callback
* Event listener.
*/
function _handleMultipleEvents(fn, elem, types, callback) {
types.forEach(function (type) {
// Call the event method for each one of the types
fn(elem, type, callback);
});
}
/**
* Fix a native event to have standard property values
*
* @param {Object} event
* Event object to fix.
*
* @return {Object}
* Fixed event object.
*/
function fixEvent(event) {
function returnTrue() {
return true;
}
function returnFalse() {
return false;
} // Test if fixing up is needed
// Used to check if !event.stopPropagation instead of isPropagationStopped
// But native events return true for stopPropagation, but don't have
// other expected methods like isPropagationStopped. Seems to be a problem
// with the Javascript Ninja code. So we're just overriding all events now.
if (!event || !event.isPropagationStopped) {
var old = event || window$1.event;
event = {}; // Clone the old object so that we can modify the values event = {};
// IE8 Doesn't like when you mess with native event properties
// Firefox returns false for event.hasOwnProperty('type') and other props
// which makes copying more difficult.
// TODO: Probably best to create a whitelist of event props
for (var key in old) {
// Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
// Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
// and webkitMovementX/Y
if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
// Chrome 32+ warns if you try to copy deprecated returnValue, but
// we still want to if preventDefault isn't supported (IE8).
if (!(key === 'returnValue' && old.preventDefault)) {
event[key] = old[key];
}
}
} // The event occurred on this element
if (!event.target) {
event.target = event.srcElement || document;
} // Handle which other element the event is related to
if (!event.relatedTarget) {
event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
} // Stop the default browser action
event.preventDefault = function () {
if (old.preventDefault) {
old.preventDefault();
}
event.returnValue = false;
old.returnValue = false;
event.defaultPrevented = true;
};
event.defaultPrevented = false; // Stop the event from bubbling
event.stopPropagation = function () {
if (old.stopPropagation) {
old.stopPropagation();
}
event.cancelBubble = true;
old.cancelBubble = true;
event.isPropagationStopped = returnTrue;
};
event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
event.stopImmediatePropagation = function () {
if (old.stopImmediatePropagation) {
old.stopImmediatePropagation();
}
event.isImmediatePropagationStopped = returnTrue;
event.stopPropagation();
};
event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
if (event.clientX !== null && event.clientX !== undefined) {
var doc = document.documentElement;
var body = document.body;
event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
} // Handle key presses
event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
// 0 == left; 1 == middle; 2 == right
if (event.button !== null && event.button !== undefined) {
// The following is disabled because it does not pass videojs-standard
// and... yikes.
/* eslint-disable */
event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
/* eslint-enable */
}
} // Returns fixed-up instance
return event;
}
/**
* Whether passive event listeners are supported
*/
var _sup