UNPKG

playable

Version:

Video player based on HTML5Video

1,173 lines (1,143 loc) 679 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.Playable = {}))); }(this, (function (exports) { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } var ExtendableError = /** @class */ (function (_super) { __extends(ExtendableError, _super); function ExtendableError(message) { var _this = _super.call(this, message) || this; Object.defineProperty(_this, 'message', { enumerable: false, value: message, }); Object.defineProperty(_this, 'name', { enumerable: false, value: _this.constructor.name, }); Error.captureStackTrace(_this, _this.constructor); return _this; } return ExtendableError; }(Error)); var NotAFunctionError = /** @class */ (function (_super) { __extends(NotAFunctionError, _super); function NotAFunctionError(functionName, expectedType, givenType) { return _super.call(this, "The function " + functionName + " expected a " + expectedType + ", " + givenType + " given.") || this; } return NotAFunctionError; }(ExtendableError)); var Lifetime; (function (Lifetime) { Lifetime["SINGLETON"] = "singleton"; Lifetime["TRANSIENT"] = "transient"; Lifetime["SCOPED"] = "scoped"; })(Lifetime || (Lifetime = {})); var Lifetime$1 = Lifetime; var PROPERTY_FOR_DEPENDENCIES = 'dependencies'; var makeFluidInterface = function (obj) { var setLifetime = function (value) { obj.lifetime = value; return obj; }; return { setLifetime: setLifetime, transient: function () { return setLifetime(Lifetime$1.TRANSIENT); }, scoped: function () { return setLifetime(Lifetime$1.SCOPED); }, singleton: function () { return setLifetime(Lifetime$1.SINGLETON); }, }; }; var asValue = function (value) { var resolve = function () { return value; }; return { resolve: resolve, lifetime: Lifetime$1.TRANSIENT, }; }; var asFunction = function (fn, options) { if (typeof fn !== 'function') { throw new NotAFunctionError('asFunction', 'function', typeof fn); } var defaults = { lifetime: Lifetime$1.TRANSIENT, }; options = __assign({}, defaults, options); var resolve = generateResolve(fn); var result = { resolve: resolve, lifetime: options.lifetime, }; result.resolve = resolve.bind(result); __assign(result, makeFluidInterface(result)); return result; }; var asClass = function (Type, options) { if (typeof Type !== 'function') { throw new NotAFunctionError('asClass', 'class', typeof Type); } var defaults = { lifetime: Lifetime$1.TRANSIENT, }; options = __assign({}, defaults, options); // A function to handle object construction for us, as to make the generateResolve more reusable var newClass = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return new (Type.bind.apply(Type, [void 0].concat(args)))(); }; var resolve = generateResolve(newClass, Type); var result = { resolve: resolve, lifetime: options.lifetime, }; result.resolve = resolve.bind(result); __assign(result, makeFluidInterface(result)); return result; }; function generateResolve(fn, dependencyParseTarget) { // If the function used for dependency parsing is falsy, use the supplied function if (!dependencyParseTarget) { dependencyParseTarget = fn; } // Try to resolve the dependencies var dependencies = dependencyParseTarget[PROPERTY_FOR_DEPENDENCIES] || []; // Use a regular function instead of an arrow function to facilitate binding to the registration. return function resolve(container) { if (dependencies.length > 0) { var wrappedModules = dependencies.reduce(function (wrapper, dependency) { wrapper[dependency] = container.resolve(dependency); return wrapper; }, {}); return fn(wrappedModules, container); } return fn(container); }; } var registrations = { asValue: asValue, asFunction: asFunction, asClass: asClass, }; var createErrorMessage = function (name, resolutionStack, message) { resolutionStack = resolutionStack.slice(); resolutionStack.push(name); var resolutionPathString = resolutionStack.join(' -> '); var msg = "Could not resolve '" + name + "'."; if (message) { msg += " " + message + " \n\n Resolution path: " + resolutionPathString; } return msg; }; var ResolutionError = /** @class */ (function (_super) { __extends(ResolutionError, _super); function ResolutionError(name, resolutionStack, message) { return _super.call(this, createErrorMessage(name, resolutionStack, message)) || this; } return ResolutionError; }(ExtendableError)); function nameValueToObject (name, value) { var _a; if (typeof name !== 'object') { return __assign((_a = {}, _a[name] = value, _a)); } return name; } var FAMILY_TREE = '__familyTree__'; var Container = /** @class */ (function () { function Container(options, _parentContainer) { this._registrations = {}; this._resolutionStack = []; this.options = __assign({}, options); this._parentContainer = _parentContainer || null; this[FAMILY_TREE] = this._parentContainer ? [this].concat(this._parentContainer[FAMILY_TREE]) : [this]; this.cache = {}; } Object.defineProperty(Container.prototype, "registrations", { get: function () { return __assign({}, this._parentContainer && this._parentContainer.registrations, this._registrations); }, enumerable: true, configurable: true }); Container.prototype._registerAs = function (fn, verbatimValue, name, value, options) { var _this = this; var registrations$$1 = nameValueToObject(name, value); Object.keys(registrations$$1).forEach(function (key) { var valueToRegister = registrations$$1[key]; // If we have options, copy them over. options = __assign({}, options); /* ignore coverage */ if (!verbatimValue && Array.isArray(valueToRegister)) { // The ('name', [value, options]) style options = __assign({}, options, valueToRegister[1]); valueToRegister = valueToRegister[0]; } _this.register(key, fn(valueToRegister, options)); }); // Chaining return this; }; Container.prototype.createScope = function () { return new Container(this.options, this); }; Container.prototype.register = function (name, registration) { var _this = this; var obj = nameValueToObject(name, registration); Object.keys(obj).forEach(function (key) { _this._registrations[key] = obj[key]; }); return this; }; Container.prototype.registerClass = function (name, value, options) { return this._registerAs(asClass, false, name, value, options); }; Container.prototype.registerFunction = function (name, value, options) { return this._registerAs(asFunction, false, name, value, options); }; Container.prototype.registerValue = function (name, value, options) { return this._registerAs(asValue, true, name, value, options); }; Container.prototype.resolve = function (name) { // We need a reference to the root container, // so we can retrieve and store singletons. var root = this[FAMILY_TREE][this[FAMILY_TREE].length - 1]; try { // Grab the registration by name. var registration = this.registrations[name]; if (this._resolutionStack.indexOf(name) > -1) { throw new ResolutionError(name, this._resolutionStack, 'Cyclic dependencies detected.'); } if (!registration) { throw new ResolutionError(name, this._resolutionStack); } // Pushes the currently-resolving module name onto the stack this._resolutionStack.push(name); // Do the thing var cached = void 0; var resolved = void 0; switch (registration.lifetime) { case Lifetime$1.TRANSIENT: // Transient lifetime means resolve every time. resolved = registration.resolve(this); break; case Lifetime$1.SINGLETON: // Singleton lifetime means cache at all times, regardless of scope. cached = root.cache[name]; if (cached === undefined) { resolved = registration.resolve(this); root.cache[name] = resolved; } else { resolved = cached; } break; case Lifetime$1.SCOPED: // Scoped lifetime means that the container // that resolves the registration also caches it. // When a registration is not found, we travel up // the family tree until we find one that is cached. // Note: The first element in the family tree is this container. for (var _i = 0, _a = this[FAMILY_TREE]; _i < _a.length; _i++) { var _containerFromFamiltyTree = _a[_i]; cached = _containerFromFamiltyTree.cache[name]; if (cached !== undefined) { // We found one! resolved = cached; break; } } // If we still have not found one, we need to resolve and cache it. if (cached === undefined) { resolved = registration.resolve(this); this.cache[name] = resolved; } break; default: throw new ResolutionError(name, this._resolutionStack, "Unknown lifetime \"" + registration.lifetime + "\""); } // Pop it from the stack again, ready for the next resolution this._resolutionStack.pop(); return resolved; } catch (err) { // When we get an error we need to reset the stack. this._resolutionStack = []; throw err; } }; return Container; }()); function createContainer(options, __parentContainer) { return new Container(options, __parentContainer); } var DependencyContainer = __assign({ createContainer: createContainer, Lifetime: Lifetime$1 }, registrations); var IPHONE_PATTERN = /iphone/i; var IPOD_PATTERN = /ipod/i; var IPAD_PATTERN = /ipad/i; var ANDROID_PATTERN = /(android)/i; var SAFARI_PATTERN = /^((?!chrome|android).)*safari/i; // There is some iPhone/iPad/iPod in Windows Phone... // https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx var isIE = function () { return !!window.MSStream; }; var getUserAgent = function () { return window.navigator && window.navigator.userAgent; }; var isIPhone = function () { return !isIE() && IPHONE_PATTERN.test(getUserAgent()); }; var isIPod = function () { return !isIE() && IPOD_PATTERN.test(getUserAgent()); }; var isIPad = function () { return !isIE() && IPAD_PATTERN.test(getUserAgent()); }; var isIOS = function () { return isIPhone() || isIPod() || isIPad(); }; var isAndroid = function () { return ANDROID_PATTERN.test(getUserAgent()); }; var isSafari = function () { return SAFARI_PATTERN.test(getUserAgent()); }; var convertUIConfigForIOS = function (params) { return (__assign({}, params, { disableControlWithClickOnPlayer: true, disableControlWithKeyboard: true, hideMainUI: true, nativeBrowserControls: true })); }; var convertUIConfigForAndroid = function (params) { return (__assign({}, params, { disableControlWithClickOnPlayer: true, disableControlWithKeyboard: true })); }; var convertToDeviceRelatedConfig = function (params) { if (isIOS()) { return convertUIConfigForIOS(params); } if (isAndroid()) { return convertUIConfigForAndroid(params); } return params; }; var PLAYER_API_PROPERTY = '___playerAPI'; var checkDescriptorsOnEquality = function (desc1, desc2) { return desc1.value === desc2.value && desc1.get === desc2.get && desc1.set === desc2.set; }; var playerAPI = function (name) { return function (target, property, descriptor) { var methodName = name || property; if (!target[PLAYER_API_PROPERTY]) { target[PLAYER_API_PROPERTY] = {}; } if (target[PLAYER_API_PROPERTY][methodName]) { if (!checkDescriptorsOnEquality(target[PLAYER_API_PROPERTY][methodName], descriptor)) { throw new Error("Method \"" + methodName + "\" for public API in " + target.constructor.name + " is already defined"); } } target[PLAYER_API_PROPERTY][methodName] = descriptor; }; }; var Player = /** @class */ (function () { function Player(params, scope, defaultModulesNames, additionalModuleNames, themeConfig) { if (defaultModulesNames === void 0) { defaultModulesNames = []; } if (additionalModuleNames === void 0) { additionalModuleNames = []; } this._scope = scope; this._scope.registerValue({ config: convertToDeviceRelatedConfig(params), }); this._scope.registerValue({ themeConfig: themeConfig, }); this._config = this._scope.resolve('config'); this._resolveAdditionalModules(additionalModuleNames); this._resolveDefaultModules(defaultModulesNames); } /* Separation for default and additional modules is needed for future implementation of public methods of resolved modules and could be abolished in future */ Player.prototype._resolveDefaultModules = function (modulesNames) { var _this = this; this._defaultModules = modulesNames.reduce(function (modules, moduleName) { if (_this._additionalModules[moduleName]) { return modules; } var resolvedModule = _this._scope.resolve(moduleName); _this._addPlayerAPIFromModule(resolvedModule); modules[moduleName] = resolvedModule; return modules; }, {}); }; Player.prototype._resolveAdditionalModules = function (modulesNames) { var _this = this; this._additionalModules = modulesNames.reduce(function (modules, moduleName) { var resolvedModule = _this._scope.resolve(moduleName); _this._addPlayerAPIFromModule(resolvedModule); modules[moduleName] = resolvedModule; return modules; }, {}); }; Player.prototype._getWrappedCallToModuleFunction = function (module, fn) { var _this = this; return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } if (_this._destroyed) { throw new Error('Player instance is destroyed'); } return fn.apply(module, args); }; }; Player.prototype._getPlayerAPIMethodDescriptor = function (module, descriptor) { var playerMethodDescriptor = { enumerable: true, configurable: true, }; var get = descriptor.get, set = descriptor.set, value = descriptor.value; if (get) { playerMethodDescriptor.get = this._getWrappedCallToModuleFunction(module, get); } if (set) { playerMethodDescriptor.set = this._getWrappedCallToModuleFunction(module, set); } if (value) { playerMethodDescriptor.value = this._getWrappedCallToModuleFunction(module, value); playerMethodDescriptor.writable = true; } return playerMethodDescriptor; }; Player.prototype._getModuleApi = function (module) { return module.getAPI ? module.getAPI() : module[PLAYER_API_PROPERTY]; }; Player.prototype._addPlayerAPIFromModule = function (module) { var _this = this; var moduleApi = this._getModuleApi(module); var getDescriptor = module.getAPI ? function (apiKey) { return Object.getOwnPropertyDescriptor(moduleApi, apiKey); } : function (apiKey) { return _this._getPlayerAPIMethodDescriptor(module, moduleApi[apiKey]); }; if (moduleApi) { Object.keys(moduleApi).forEach(function (apiKey) { if (_this[apiKey]) { throw new Error("API method " + apiKey + " is already defined in Player facade"); } Object.defineProperty(_this, apiKey, getDescriptor(apiKey)); }); } }; Player.prototype._clearPlayerAPIForModule = function (module) { var _this = this; var moduleApi = this._getModuleApi(module); if (moduleApi) { Object.keys(moduleApi).forEach(function (apiKey) { delete _this[apiKey]; }); } }; Player.prototype.destroy = function () { var _this = this; Object.keys(this._defaultModules).forEach(function (moduleName) { var module = _this._defaultModules[moduleName]; _this._clearPlayerAPIForModule(module); module.destroy(); }); Object.keys(this._additionalModules).forEach(function (moduleName) { var module = _this._additionalModules[moduleName]; _this._clearPlayerAPIForModule(module); if (module.destroy) { module.destroy(); } }); this._defaultModules = null; this._additionalModules = null; this._config = null; this._scope = null; this._destroyed = true; }; return Player; }()); /** * A collection of shims that provide minimal functionality of the ES6 collections. * * These implementations are not meant to be used outside of the ResizeObserver * modules as they cover only a limited range of use cases. */ /* eslint-disable require-jsdoc, valid-jsdoc */ var MapShim = (function () { if (typeof Map !== 'undefined') { return Map; } /** * Returns index in provided array that matches the specified key. * * @param {Array<Array>} arr * @param {*} key * @returns {number} */ function getIndex(arr, key) { var result = -1; arr.some(function (entry, index) { if (entry[0] === key) { result = index; return true; } return false; }); return result; } return /** @class */ (function () { function class_1() { this.__entries__ = []; } Object.defineProperty(class_1.prototype, "size", { /** * @returns {boolean} */ get: function () { return this.__entries__.length; }, enumerable: true, configurable: true }); /** * @param {*} key * @returns {*} */ class_1.prototype.get = function (key) { var index = getIndex(this.__entries__, key); var entry = this.__entries__[index]; return entry && entry[1]; }; /** * @param {*} key * @param {*} value * @returns {void} */ class_1.prototype.set = function (key, value) { var index = getIndex(this.__entries__, key); if (~index) { this.__entries__[index][1] = value; } else { this.__entries__.push([key, value]); } }; /** * @param {*} key * @returns {void} */ class_1.prototype.delete = function (key) { var entries = this.__entries__; var index = getIndex(entries, key); if (~index) { entries.splice(index, 1); } }; /** * @param {*} key * @returns {void} */ class_1.prototype.has = function (key) { return !!~getIndex(this.__entries__, key); }; /** * @returns {void} */ class_1.prototype.clear = function () { this.__entries__.splice(0); }; /** * @param {Function} callback * @param {*} [ctx=null] * @returns {void} */ class_1.prototype.forEach = function (callback, ctx) { if (ctx === void 0) { ctx = null; } for (var _i = 0, _a = this.__entries__; _i < _a.length; _i++) { var entry = _a[_i]; callback.call(ctx, entry[1], entry[0]); } }; return class_1; }()); })(); /** * Detects whether window and document objects are available in current environment. */ var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && window.document === document; // Returns global object of a current environment. var global$1 = (function () { if (typeof global !== 'undefined' && global.Math === Math) { return global; } if (typeof self !== 'undefined' && self.Math === Math) { return self; } if (typeof window !== 'undefined' && window.Math === Math) { return window; } // eslint-disable-next-line no-new-func return Function('return this')(); })(); /** * A shim for the requestAnimationFrame which falls back to the setTimeout if * first one is not supported. * * @returns {number} Requests' identifier. */ var requestAnimationFrame$1 = (function () { if (typeof requestAnimationFrame === 'function') { // It's required to use a bounded function because IE sometimes throws // an "Invalid calling object" error if rAF is invoked without the global // object on the left hand side. return requestAnimationFrame.bind(global$1); } return function (callback) { return setTimeout(function () { return callback(Date.now()); }, 1000 / 60); }; })(); // Defines minimum timeout before adding a trailing call. var trailingTimeout = 2; /** * Creates a wrapper function which ensures that provided callback will be * invoked only once during the specified delay period. * * @param {Function} callback - Function to be invoked after the delay period. * @param {number} delay - Delay after which to invoke callback. * @returns {Function} */ function throttle (callback, delay) { var leadingCall = false, trailingCall = false, lastCallTime = 0; /** * Invokes the original callback function and schedules new invocation if * the "proxy" was called during current request. * * @returns {void} */ function resolvePending() { if (leadingCall) { leadingCall = false; callback(); } if (trailingCall) { proxy(); } } /** * Callback invoked after the specified delay. It will further postpone * invocation of the original function delegating it to the * requestAnimationFrame. * * @returns {void} */ function timeoutCallback() { requestAnimationFrame$1(resolvePending); } /** * Schedules invocation of the original function. * * @returns {void} */ function proxy() { var timeStamp = Date.now(); if (leadingCall) { // Reject immediately following calls. if (timeStamp - lastCallTime < trailingTimeout) { return; } // Schedule new call to be in invoked when the pending one is resolved. // This is important for "transitions" which never actually start // immediately so there is a chance that we might miss one if change // happens amids the pending invocation. trailingCall = true; } else { leadingCall = true; trailingCall = false; setTimeout(timeoutCallback, delay); } lastCallTime = timeStamp; } return proxy; } // Minimum delay before invoking the update of observers. var REFRESH_DELAY = 20; // A list of substrings of CSS properties used to find transition events that // might affect dimensions of observed elements. var transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight']; // Check if MutationObserver is available. var mutationObserverSupported = typeof MutationObserver !== 'undefined'; /** * Singleton controller class which handles updates of ResizeObserver instances. */ var ResizeObserverController = /** @class */ (function () { /** * Creates a new instance of ResizeObserverController. * * @private */ function ResizeObserverController() { /** * Indicates whether DOM listeners have been added. * * @private {boolean} */ this.connected_ = false; /** * Tells that controller has subscribed for Mutation Events. * * @private {boolean} */ this.mutationEventsAdded_ = false; /** * Keeps reference to the instance of MutationObserver. * * @private {MutationObserver} */ this.mutationsObserver_ = null; /** * A list of connected observers. * * @private {Array<ResizeObserverSPI>} */ this.observers_ = []; this.onTransitionEnd_ = this.onTransitionEnd_.bind(this); this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY); } /** * Adds observer to observers list. * * @param {ResizeObserverSPI} observer - Observer to be added. * @returns {void} */ ResizeObserverController.prototype.addObserver = function (observer) { if (!~this.observers_.indexOf(observer)) { this.observers_.push(observer); } // Add listeners if they haven't been added yet. if (!this.connected_) { this.connect_(); } }; /** * Removes observer from observers list. * * @param {ResizeObserverSPI} observer - Observer to be removed. * @returns {void} */ ResizeObserverController.prototype.removeObserver = function (observer) { var observers = this.observers_; var index = observers.indexOf(observer); // Remove observer if it's present in registry. if (~index) { observers.splice(index, 1); } // Remove listeners if controller has no connected observers. if (!observers.length && this.connected_) { this.disconnect_(); } }; /** * Invokes the update of observers. It will continue running updates insofar * it detects changes. * * @returns {void} */ ResizeObserverController.prototype.refresh = function () { var changesDetected = this.updateObservers_(); // Continue running updates if changes have been detected as there might // be future ones caused by CSS transitions. if (changesDetected) { this.refresh(); } }; /** * Updates every observer from observers list and notifies them of queued * entries. * * @private * @returns {boolean} Returns "true" if any observer has detected changes in * dimensions of it's elements. */ ResizeObserverController.prototype.updateObservers_ = function () { // Collect observers that have active observations. var activeObservers = this.observers_.filter(function (observer) { return observer.gatherActive(), observer.hasActive(); }); // Deliver notifications in a separate cycle in order to avoid any // collisions between observers, e.g. when multiple instances of // ResizeObserver are tracking the same element and the callback of one // of them changes content dimensions of the observed target. Sometimes // this may result in notifications being blocked for the rest of observers. activeObservers.forEach(function (observer) { return observer.broadcastActive(); }); return activeObservers.length > 0; }; /** * Initializes DOM listeners. * * @private * @returns {void} */ ResizeObserverController.prototype.connect_ = function () { // Do nothing if running in a non-browser environment or if listeners // have been already added. if (!isBrowser || this.connected_) { return; } // Subscription to the "Transitionend" event is used as a workaround for // delayed transitions. This way it's possible to capture at least the // final state of an element. document.addEventListener('transitionend', this.onTransitionEnd_); window.addEventListener('resize', this.refresh); if (mutationObserverSupported) { this.mutationsObserver_ = new MutationObserver(this.refresh); this.mutationsObserver_.observe(document, { attributes: true, childList: true, characterData: true, subtree: true }); } else { document.addEventListener('DOMSubtreeModified', this.refresh); this.mutationEventsAdded_ = true; } this.connected_ = true; }; /** * Removes DOM listeners. * * @private * @returns {void} */ ResizeObserverController.prototype.disconnect_ = function () { // Do nothing if running in a non-browser environment or if listeners // have been already removed. if (!isBrowser || !this.connected_) { return; } document.removeEventListener('transitionend', this.onTransitionEnd_); window.removeEventListener('resize', this.refresh); if (this.mutationsObserver_) { this.mutationsObserver_.disconnect(); } if (this.mutationEventsAdded_) { document.removeEventListener('DOMSubtreeModified', this.refresh); } this.mutationsObserver_ = null; this.mutationEventsAdded_ = false; this.connected_ = false; }; /** * "Transitionend" event handler. * * @private * @param {TransitionEvent} event * @returns {void} */ ResizeObserverController.prototype.onTransitionEnd_ = function (_a) { var _b = _a.propertyName, propertyName = _b === void 0 ? '' : _b; // Detect whether transition may affect dimensions of an element. var isReflowProperty = transitionKeys.some(function (key) { return !!~propertyName.indexOf(key); }); if (isReflowProperty) { this.refresh(); } }; /** * Returns instance of the ResizeObserverController. * * @returns {ResizeObserverController} */ ResizeObserverController.getInstance = function () { if (!this.instance_) { this.instance_ = new ResizeObserverController(); } return this.instance_; }; /** * Holds reference to the controller's instance. * * @private {ResizeObserverController} */ ResizeObserverController.instance_ = null; return ResizeObserverController; }()); /** * Defines non-writable/enumerable properties of the provided target object. * * @param {Object} target - Object for which to define properties. * @param {Object} props - Properties to be defined. * @returns {Object} Target object. */ var defineConfigurable = (function (target, props) { for (var _i = 0, _a = Object.keys(props); _i < _a.length; _i++) { var key = _a[_i]; Object.defineProperty(target, key, { value: props[key], enumerable: false, writable: false, configurable: true }); } return target; }); /** * Returns the global object associated with provided element. * * @param {Object} target * @returns {Object} */ var getWindowOf = (function (target) { // Assume that the element is an instance of Node, which means that it // has the "ownerDocument" property from which we can retrieve a // corresponding global object. var ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView; // Return the local global object if it's not possible extract one from // provided element. return ownerGlobal || global$1; }); // Placeholder of an empty content rectangle. var emptyRect = createRectInit(0, 0, 0, 0); /** * Converts provided string to a number. * * @param {number|string} value * @returns {number} */ function toFloat(value) { return parseFloat(value) || 0; } /** * Extracts borders size from provided styles. * * @param {CSSStyleDeclaration} styles * @param {...string} positions - Borders positions (top, right, ...) * @returns {number} */ function getBordersSize(styles) { var positions = []; for (var _i = 1; _i < arguments.length; _i++) { positions[_i - 1] = arguments[_i]; } return positions.reduce(function (size, position) { var value = styles['border-' + position + '-width']; return size + toFloat(value); }, 0); } /** * Extracts paddings sizes from provided styles. * * @param {CSSStyleDeclaration} styles * @returns {Object} Paddings box. */ function getPaddings(styles) { var positions = ['top', 'right', 'bottom', 'left']; var paddings = {}; for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) { var position = positions_1[_i]; var value = styles['padding-' + position]; paddings[position] = toFloat(value); } return paddings; } /** * Calculates content rectangle of provided SVG element. * * @param {SVGGraphicsElement} target - Element content rectangle of which needs * to be calculated. * @returns {DOMRectInit} */ function getSVGContentRect(target) { var bbox = target.getBBox(); return createRectInit(0, 0, bbox.width, bbox.height); } /** * Calculates content rectangle of provided HTMLElement. * * @param {HTMLElement} target - Element for which to calculate the content rectangle. * @returns {DOMRectInit} */ function getHTMLElementContentRect(target) { // Client width & height properties can't be // used exclusively as they provide rounded values. var clientWidth = target.clientWidth, clientHeight = target.clientHeight; // By this condition we can catch all non-replaced inline, hidden and // detached elements. Though elements with width & height properties less // than 0.5 will be discarded as well. // // Without it we would need to implement separate methods for each of // those cases and it's not possible to perform a precise and performance // effective test for hidden elements. E.g. even jQuery's ':visible' filter // gives wrong results for elements with width & height less than 0.5. if (!clientWidth && !clientHeight) { return emptyRect; } var styles = getWindowOf(target).getComputedStyle(target); var paddings = getPaddings(styles); var horizPad = paddings.left + paddings.right; var vertPad = paddings.top + paddings.bottom; // Computed styles of width & height are being used because they are the // only dimensions available to JS that contain non-rounded values. It could // be possible to utilize the getBoundingClientRect if only it's data wasn't // affected by CSS transformations let alone paddings, borders and scroll bars. var width = toFloat(styles.width), height = toFloat(styles.height); // Width & height include paddings and borders when the 'border-box' box // model is applied (except for IE). if (styles.boxSizing === 'border-box') { // Following conditions are required to handle Internet Explorer which // doesn't include paddings and borders to computed CSS dimensions. // // We can say that if CSS dimensions + paddings are equal to the "client" // properties then it's either IE, and thus we don't need to subtract // anything, or an element merely doesn't have paddings/borders styles. if (Math.round(width + horizPad) !== clientWidth) { width -= getBordersSize(styles, 'left', 'right') + horizPad; } if (Math.round(height + vertPad) !== clientHeight) { height -= getBordersSize(styles, 'top', 'bottom') + vertPad; } } // Following steps can't be applied to the document's root element as its // client[Width/Height] properties represent viewport area of the window. // Besides, it's as well not necessary as the <html> itself neither has // rendered scroll bars nor it can be clipped. if (!isDocumentElement(target)) { // In some browsers (only in Firefox, actually) CSS width & height // include scroll bars size which can be removed at this step as scroll // bars are the only difference between rounded dimensions + paddings // and "client" properties, though that is not always true in Chrome. var vertScrollbar = Math.round(width + horizPad) - clientWidth; var horizScrollbar = Math.round(height + vertPad) - clientHeight; // Chrome has a rather weird rounding of "client" properties. // E.g. for an element with content width of 314.2px it sometimes gives // the client width of 315px and for the width of 314.7px it may give // 314px. And it doesn't happen all the time. So just ignore this delta // as a non-relevant. if (Math.abs(vertScrollbar) !== 1) { width -= vertScrollbar; } if (Math.abs(horizScrollbar) !== 1) { height -= horizScrollbar; } } return createRectInit(paddings.left, paddings.top, width, height); } /** * Checks whether provided element is an instance of the SVGGraphicsElement. * * @param {Element} target - Element to be checked. * @returns {boolean} */ var isSVGGraphicsElement = (function () { // Some browsers, namely IE and Edge, don't have the SVGGraphicsElement // interface. if (typeof SVGGraphicsElement !== 'undefined') { return function (target) { return target instanceof getWindowOf(target).SVGGraphicsElement; }; } // If it's so, then check that element is at least an instance of the // SVGElement and that it has the "getBBox" method. // eslint-disable-next-line no-extra-parens return function (target) { return (target instanceof getWindowOf(target).SVGElement && typeof target.getBBox === 'function'); }; })(); /** * Checks whether provided element is a document element (<html>). * * @param {Element} target - Element to be checked. * @returns {boolean} */ function isDocumentElement(target) { return target === getWindowOf(target).document.documentElement; } /** * Calculates an appropriate content rectangle for provided html or svg element. * * @param {Element} target - Element content rectangle of which needs to be calculated. * @returns {DOMRectInit} */ function getContentRect(target) { if (!isBrowser) { return emptyRect; } if (isSVGGraphicsElement(target)) { return getSVGContentRect(target); } return getHTMLElementContentRect(target); } /** * Creates rectangle with an interface of the DOMRectReadOnly. * Spec: https://drafts.fxtf.org/geometry/#domrectreadonly * * @param {DOMRectInit} rectInit - Object with rectangle's x/y coordinates and dimensions. * @returns {DOMRectReadOnly} */ function createReadOnlyRect(_a) { var x = _a.x, y = _a.y, width = _a.width, height = _a.height; // If DOMRectReadOnly is available use it as a prototype for the rectangle. var Constr = typeof DOMRectReadOnly !== 'undefined' ? DOMRectReadOnly : Object; var rect = Object.create(Constr.prototype); // Rectangle's properties are not writable and non-enumerable. defineConfigurable(rect, { x: x, y: y, width: width, height: height, top: y, right: x + width, bottom: height + y, left: x }); return rect; } /** * Creates DOMRectInit object based on the provided dimensions and the x/y coordinates. * Spec: https://drafts.fxtf.org/geometry/#dictdef-domrectinit * * @param {number} x - X coordinate. * @param {number} y - Y coordinate. * @param {number} width - Rectangle's width. * @param {number} height - Rectangle's height. * @returns {DOMRectInit} */ function createRectInit(x, y, width, height) { return { x: x, y: y, width: width, height: height }; } /** * Class that is responsible for computations of the content rectangle of * provided DOM element and for keeping track of it's changes. */ var ResizeObservation = /** @class */ (function () { /** * Creates an instance of ResizeObservation. * * @param {Element} target - Element to be observed. */ function ResizeObservation(target) { /** * Broadcasted width of content rectangle.