resize-observer-polyfill
Advanced tools
Comparing version 1.4.1 to 1.4.2
/** | ||
* Exports global object for the 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 collection of shims that provide minimal functionality of the ES6 collections. | ||
@@ -27,8 +7,15 @@ * | ||
*/ | ||
/* eslint-disable require-jsdoc */ | ||
var Map = (function () { | ||
if (typeof global$1.Map === 'function') { | ||
return global$1.Map; | ||
/* 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) { | ||
@@ -57,2 +44,5 @@ var result = -1; | ||
/** | ||
* @returns {boolean} | ||
*/ | ||
prototypeAccessors.size.get = function () { | ||
@@ -62,2 +52,6 @@ return this.__entries__.length; | ||
/** | ||
* @param {*} key | ||
* @returns {*} | ||
*/ | ||
anonymous.prototype.get = function (key) { | ||
@@ -70,2 +64,7 @@ var index = getIndex(this.__entries__, key); | ||
/** | ||
* @param {*} key | ||
* @param {*} value | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.set = function (key, value) { | ||
@@ -81,2 +80,6 @@ var index = getIndex(this.__entries__, key); | ||
/** | ||
* @param {*} key | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.delete = function (key) { | ||
@@ -91,2 +94,6 @@ var entries = this.__entries__; | ||
/** | ||
* @param {*} key | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.has = function (key) { | ||
@@ -96,2 +103,5 @@ return !!~getIndex(this.__entries__, key); | ||
/** | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.clear = function () { | ||
@@ -101,7 +111,11 @@ this.__entries__.splice(0); | ||
/** | ||
* @param {Function} callback | ||
* @param {*} [ctx=null] | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.forEach = function (callback, ctx) { | ||
var this$1 = this; | ||
if ( ctx === void 0 ) ctx = null; | ||
for (var i = 0, list = this$1.__entries__; i < list.length; i += 1) { | ||
for (var i = 0, list = this.__entries__; i < list.length; i += 1) { | ||
var entry = list[i]; | ||
@@ -122,3 +136,3 @@ | ||
*/ | ||
var isBrowser = global$1.window === global$1 && typeof document != 'undefined'; | ||
var isBrowser = typeof window != 'undefined' && typeof document != 'undefined' && window.document === document; | ||
@@ -143,31 +157,10 @@ /** | ||
/** | ||
* Returns time stamp retrieved either from the "performance.now" or from | ||
* the "Date.now" method. | ||
* | ||
* @returns {DOMHighResTimeStamp|number} | ||
*/ | ||
var timeStamp = (function () { | ||
var host = Date; | ||
if (typeof performance === 'object' && typeof performance.now === 'function') { | ||
host = performance; | ||
} | ||
return function () { return host.now(); }; | ||
})(); | ||
/** | ||
* Creates a wrapper function which ensures that provided callback will be | ||
* invoked only once during the specified delay period. It also caches the last | ||
* call and re-invokes it after pending activation is resolved. | ||
* 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. | ||
* @param {boolean} [afterRAF = false] - Whether function needs to be invoked as | ||
* a requestAnimationFrame callback. | ||
* @returns {Function} | ||
*/ | ||
var throttle = function (callback, delay, afterRAF) { | ||
if ( afterRAF === void 0 ) afterRAF = false; | ||
var throttle = function (callback, delay) { | ||
var leadingCall = false, | ||
@@ -178,14 +171,14 @@ trailingCall = false, | ||
/** | ||
* Invokes the original callback function and schedules a new invocation if | ||
* the wrapper was called during current request. | ||
* Invokes the original callback function and schedules new invocation if | ||
* the "proxy" was called during current request. | ||
* | ||
* @returns {void} | ||
*/ | ||
function invokeCallback() { | ||
leadingCall = false; | ||
function resolvePending() { | ||
if (leadingCall) { | ||
leadingCall = false; | ||
// Invoke original function. | ||
callback(); | ||
callback(); | ||
} | ||
// Schedule new invocation if there has been a call during delay period. | ||
if (trailingCall) { | ||
@@ -197,5 +190,5 @@ proxy(); | ||
/** | ||
* Callback that will be invoked after the specified delay period. It will | ||
* delegate invocation of the original function to the requestAnimationFrame | ||
* if "afterRAF" parameter is set to "true". | ||
* Callback invoked after the specified delay. It will further postpone | ||
* invocation of the original function delegating it to the | ||
* requestAnimationFrame. | ||
* | ||
@@ -205,7 +198,7 @@ * @returns {void} | ||
function timeoutCallback() { | ||
afterRAF ? requestAnimationFrame$1(invokeCallback) : invokeCallback(); | ||
requestAnimationFrame$1(resolvePending); | ||
} | ||
/** | ||
* Schedules invocation of the initial function. | ||
* Schedules invocation of the original function. | ||
* | ||
@@ -215,11 +208,14 @@ * @returns {void} | ||
function proxy() { | ||
var callTime = timeStamp(); | ||
var timeStamp = Date.now(); | ||
// Postpone activation if there is already a pending call. | ||
if (leadingCall) { | ||
// Reject immediately following invocations. | ||
if (callTime - lastCallTime < trailingTimeout) { | ||
// 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; | ||
@@ -230,7 +226,6 @@ } else { | ||
// Schedule new invocation. | ||
setTimeout(timeoutCallback, delay); | ||
} | ||
lastCallTime = callTime; | ||
lastCallTime = timeStamp; | ||
} | ||
@@ -244,40 +239,33 @@ | ||
// Delay before the next iteration of the continuous cycle. | ||
var CONTINUOUS_DELAY = 80; | ||
// 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']; | ||
// Define whether the MutationObserver is supported. | ||
// eslint-disable-next-line no-extra-parens | ||
var mutationsSupported = typeof MutationObserver === 'function' && | ||
// MutationObserver should not be used if running in IE11 as it's | ||
// Detect whether running in IE 11 (facepalm). | ||
var isIE11 = typeof navigator != 'undefined' && /Trident\/.*rv:11/.test(navigator.userAgent); | ||
// MutationObserver should not be used if running in Internet Explorer 11 as it's | ||
// implementation is unreliable. Example: https://jsfiddle.net/x2r3jpuz/2/ | ||
// Unfortunately, there is no other way to check this issue but to use | ||
// userAgent's information. | ||
typeof navigator === 'object' && !(navigator.appName === 'Netscape' && navigator.userAgent.match(/Trident\/.*rv:11/)); | ||
// | ||
// It's a real bummer that there is no other way to check for this issue but to | ||
// use the UA information. | ||
var mutationObserverSupported = typeof MutationObserver != 'undefined' && !isIE11; | ||
/** | ||
* Controller class which handles updates of ResizeObserver instances. | ||
* It decides when and for how long it's necessary to run updates by listening | ||
* to the windows "resize" event along with a tracking of DOM mutations | ||
* (nodes removal, changes of attributes, etc.). | ||
* | ||
* Transitions and animations are handled by running a repeatable update cycle | ||
* until the dimensions of observed elements are changing. | ||
* | ||
* Continuous update cycle will be used automatically in case MutationObserver | ||
* is not supported. | ||
* Singleton controller class which handles updates of ResizeObserver instances. | ||
*/ | ||
var ResizeObserverController = function() { | ||
/** | ||
* Continuous updates must be enabled if MutationObserver is not supported. | ||
* Indicates whether DOM listeners have been added. | ||
* | ||
* @private {boolean} | ||
*/ | ||
this.isCycleContinuous_ = !mutationsSupported; | ||
this.connected_ = false; | ||
/** | ||
* Indicates whether DOM listeners have been added. | ||
* Tells that controller has subscribed for Mutation Events. | ||
* | ||
* @private {boolean} | ||
*/ | ||
this.listenersEnabled_ = false; | ||
this.mutationEventsAdded_ = false; | ||
@@ -298,8 +286,4 @@ /** | ||
// Make sure that the "refresh" method is invoked as a RAF callback and | ||
// that it happens only once during the provided period. | ||
this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY, true); | ||
// Additionally postpone invocation of the continuous updates. | ||
this.continuousUpdateHandler_ = throttle(this.refresh, CONTINUOUS_DELAY); | ||
this.onTransitionEnd_ = this.onTransitionEnd_.bind(this); | ||
this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY); | ||
}; | ||
@@ -313,4 +297,4 @@ | ||
*/ | ||
ResizeObserverController.prototype.connect = function (observer) { | ||
if (!this.isConnected(observer)) { | ||
ResizeObserverController.prototype.addObserver = function (observer) { | ||
if (!~this.observers_.indexOf(observer)) { | ||
this.observers_.push(observer); | ||
@@ -320,4 +304,4 @@ } | ||
// Add listeners if they haven't been added yet. | ||
if (!this.listenersEnabled_) { | ||
this.addListeners_(); | ||
if (!this.connected_) { | ||
this.connect_(); | ||
} | ||
@@ -332,3 +316,3 @@ }; | ||
*/ | ||
ResizeObserverController.prototype.disconnect = function (observer) { | ||
ResizeObserverController.prototype.removeObserver = function (observer) { | ||
var observers = this.observers_; | ||
@@ -343,4 +327,4 @@ var index = observers.indexOf(observer); | ||
// Remove listeners if controller has no connected observers. | ||
if (!observers.length && this.listenersEnabled_) { | ||
this.removeListeners_(); | ||
if (!observers.length && this.connected_) { | ||
this.disconnect_(); | ||
} | ||
@@ -350,14 +334,4 @@ }; | ||
/** | ||
* Tells whether the provided observer is connected to controller. | ||
* | ||
* @param {ResizeObserverSPI} observer - Observer to be checked. | ||
* @returns {boolean} | ||
*/ | ||
ResizeObserverController.prototype.isConnected = function (observer) { | ||
return !!~this.observers_.indexOf(observer); | ||
}; | ||
/** | ||
* Invokes the update of observers. It will continue running updates insofar | ||
* it detects changes or if continuous updates are enabled. | ||
* it detects changes. | ||
* | ||
@@ -367,11 +341,8 @@ * @returns {void} | ||
ResizeObserverController.prototype.refresh = function () { | ||
var hasChanges = this.updateObservers_(); | ||
var changesDetected = this.updateObservers_(); | ||
// Continue running updates if changes have been detected as there might | ||
// be future ones caused by CSS transitions. | ||
if (hasChanges) { | ||
if (changesDetected) { | ||
this.refresh(); | ||
} else if (this.isCycleContinuous_ && this.listenersEnabled_) { | ||
// Automatically repeat cycle if it's necessary. | ||
this.continuousUpdateHandler_(); | ||
} | ||
@@ -389,4 +360,4 @@ }; | ||
ResizeObserverController.prototype.updateObservers_ = function () { | ||
// Collect observers that have active entries. | ||
var active = this.observers_.filter(function (observer) { | ||
// Collect observers that have active observations. | ||
var activeObservers = this.observers_.filter(function (observer) { | ||
return observer.gatherActive(), observer.hasActive(); | ||
@@ -396,9 +367,9 @@ }); | ||
// Deliver notifications in a separate cycle in order to avoid any | ||
// collisions between observers. E.g. when multiple instances of | ||
// ResizeObserer are tracking the same element and the callback of one | ||
// 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. | ||
active.forEach(function (observer) { return observer.broadcastActive(); }); | ||
activeObservers.forEach(function (observer) { return observer.broadcastActive(); }); | ||
return active.length > 0; | ||
return activeObservers.length > 0; | ||
}; | ||
@@ -412,19 +383,17 @@ | ||
*/ | ||
ResizeObserverController.prototype.addListeners_ = function () { | ||
ResizeObserverController.prototype.connect_ = function () { | ||
// Do nothing if running in a non-browser environment or if listeners | ||
// have been already added. | ||
if (!isBrowser || this.listenersEnabled_) { | ||
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); | ||
// Subscription to the "Transitionend" event is used as a workaround for | ||
// delayed transitions. This way we can capture at least the final state | ||
// of an element. | ||
document.addEventListener('transitionend', this.refresh); | ||
// Subscribe to DOM mutations if it's possible as they may lead to | ||
// changes in the dimensions of elements. | ||
if (mutationsSupported) { | ||
if (mutationObserverSupported) { | ||
this.mutationsObserver_ = new MutationObserver(this.refresh); | ||
@@ -438,11 +407,9 @@ | ||
}); | ||
} else { | ||
document.addEventListener('DOMSubtreeModified', this.refresh); | ||
this.mutationEventsAdded_ = true; | ||
} | ||
this.listenersEnabled_ = true; | ||
// Don't wait for a possible event that might trigger the update of | ||
// observers and manually initiate the update process. | ||
if (this.isCycleContinuous_) { | ||
this.refresh(); | ||
} | ||
this.connected_ = true; | ||
}; | ||
@@ -456,11 +423,11 @@ | ||
*/ | ||
ResizeObserverController.prototype.removeListeners_ = function () { | ||
ResizeObserverController.prototype.disconnect_ = function () { | ||
// Do nothing if running in a non-browser environment or if listeners | ||
// have been already removed. | ||
if (!isBrowser || !this.listenersEnabled_) { | ||
if (!isBrowser || !this.connected_) { | ||
return; | ||
} | ||
document.removeEventListener('transitionend', this.onTransitionEnd_); | ||
window.removeEventListener('resize', this.refresh); | ||
document.removeEventListener('transitionend', this.refresh); | ||
@@ -471,7 +438,52 @@ if (this.mutationsObserver_) { | ||
if (this.mutationEventsAdded_) { | ||
document.removeEventListener('DOMSubtreeModified', this.refresh); | ||
} | ||
this.mutationsObserver_ = null; | ||
this.listenersEnabled_ = false; | ||
this.mutationEventsAdded_ = false; | ||
this.connected_ = false; | ||
}; | ||
/** | ||
* "Transitionend" event handler. | ||
* | ||
* @private | ||
* @param {TransitionEvent} event | ||
* @returns {void} | ||
*/ | ||
ResizeObserverController.prototype.onTransitionEnd_ = function (ref) { | ||
var propertyName = ref.propertyName; | ||
// 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; | ||
/** | ||
* Defines non-writable/enumerable properties of the provided target object. | ||
@@ -489,3 +501,3 @@ * | ||
value: props[key], | ||
enumerbale: false, | ||
enumerable: false, | ||
writable: false, | ||
@@ -522,4 +534,4 @@ configurable: true | ||
return positions.reduce(function (size, pos) { | ||
var value = styles['border-' + pos + '-width']; | ||
return positions.reduce(function (size, position) { | ||
var value = styles['border-' + position + '-width']; | ||
@@ -537,11 +549,11 @@ return size + toFloat(value); | ||
function getPaddings(styles) { | ||
var boxKeys = ['top', 'right', 'bottom', 'left']; | ||
var positions = ['top', 'right', 'bottom', 'left']; | ||
var paddings = {}; | ||
for (var i = 0, list = boxKeys; i < list.length; i += 1) { | ||
var key = list[i]; | ||
for (var i = 0, list = positions; i < list.length; i += 1) { | ||
var position = list[i]; | ||
var value = styles['padding-' + key]; | ||
var value = styles['padding-' + position]; | ||
paddings[key] = toFloat(value); | ||
paddings[position] = toFloat(value); | ||
} | ||
@@ -596,3 +608,3 @@ | ||
// only dimensions available to JS that contain non-rounded values. It could | ||
// be possible to utilize getBoundingClientRect if only it's data wasn't | ||
// be possible to utilize the getBoundingClientRect if only it's data wasn't | ||
// affected by CSS transformations let alone paddings, borders and scroll bars. | ||
@@ -602,4 +614,4 @@ var width = toFloat(styles.width), | ||
// Width & height include paddings and bord when 'border-box' box model is | ||
// applied (except for IE). | ||
// Width & height include paddings and borders when the 'border-box' box | ||
// model is applied (except for IE). | ||
if (styles.boxSizing === 'border-box') { | ||
@@ -621,3 +633,3 @@ // Following conditions are required to handle Internet Explorer which | ||
// Following steps can't applied to the document's root element as it's | ||
// Following steps can't be applied to the document's root element as its | ||
// client[Width/Height] properties represent viewport area of the window. | ||
@@ -660,8 +672,8 @@ // Besides, it's as well not necessary as the <html> itself neither has | ||
// interface. | ||
if (typeof SVGGraphicsElement === 'function') { | ||
if (typeof SVGGraphicsElement != 'undefined') { | ||
return function (target) { return target instanceof SVGGraphicsElement; }; | ||
} | ||
// If it's so, than check that element is at least an instance of the | ||
// SVGElement and that it has the "getBBox" method in the prototype chain. | ||
// 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 | ||
@@ -672,3 +684,3 @@ return function (target) { return target instanceof SVGElement && typeof target.getBBox === 'function'; }; | ||
/** | ||
* Checks whether provided element is a document element (root element of a document, i.e. <html>). | ||
* Checks whether provided element is a document element (<html>). | ||
* | ||
@@ -689,3 +701,2 @@ * @param {Element} target - Element to be checked. | ||
function getContentRect(target) { | ||
// Return empty rectangle if running in a non-browser environment. | ||
if (!isBrowser) { | ||
@@ -716,3 +727,3 @@ return emptyRect; | ||
// If DOMRectReadOnly is available use it as a prototype for the rectangle. | ||
var Constr = typeof DOMRectReadOnly === 'function' ? DOMRectReadOnly : Object; | ||
var Constr = typeof DOMRectReadOnly != 'undefined' ? DOMRectReadOnly : Object; | ||
var rect = Object.create(Constr.prototype); | ||
@@ -722,4 +733,3 @@ | ||
defineConfigurable(rect, { | ||
x: x, y: y, | ||
width: width, height: height, | ||
x: x, y: y, width: width, height: height, | ||
top: y, | ||
@@ -754,9 +764,2 @@ right: x + width, | ||
/** | ||
* Reference to the observed element. | ||
* | ||
* @type {Element} | ||
*/ | ||
this.target = target; | ||
/** | ||
* Broadcasted width of content rectangle. | ||
@@ -781,2 +784,9 @@ * | ||
this.contentRect_ = createRectInit(0, 0, 0, 0); | ||
/** | ||
* Reference to the observed element. | ||
* | ||
* @type {Element} | ||
*/ | ||
this.target = target; | ||
}; | ||
@@ -834,19 +844,15 @@ | ||
* | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-activetargets | ||
* | ||
* @private {Array<ResizeObservation>} | ||
*/ | ||
this.activeTargets_ = []; | ||
this.activeObservations_ = []; | ||
/** | ||
* Registry of the ResizeObservation instances. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-observationtargets | ||
* | ||
* @private {Map<Element, ResizeObservation>} | ||
*/ | ||
this.observationTargets_ = new Map(); | ||
this.observations_ = new MapShim(); | ||
/** | ||
* Reference to the callback function. | ||
* Spec: https://wicg.github.io/ResizeObserver/#resize-observer-callback | ||
* | ||
@@ -875,3 +881,2 @@ * @private {ResizeObserverCallback} | ||
* Starts observing provided element. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-observe | ||
* | ||
@@ -887,3 +892,3 @@ * @param {Element} target - Element to be observed. | ||
// Do nothing if current environment doesn't have the Element interface. | ||
if (!('Element' in global$1) || !(Element instanceof Object)) { | ||
if (typeof Element === 'undefined' || !(Element instanceof Object)) { | ||
return; | ||
@@ -896,16 +901,12 @@ } | ||
var targets = this.observationTargets_; | ||
var observations = this.observations_; | ||
// Do nothing if element is already being observed. | ||
if (targets.has(target)) { | ||
if (observations.has(target)) { | ||
return; | ||
} | ||
// Register new ResizeObservation instance. | ||
targets.set(target, new ResizeObservation(target)); | ||
observations.set(target, new ResizeObservation(target)); | ||
// Add observer to controller if it hasn't been connected yet. | ||
if (!this.controller_.isConnected(this)) { | ||
this.controller_.connect(this); | ||
} | ||
this.controller_.addObserver(this); | ||
@@ -918,3 +919,2 @@ // Force the update of observations. | ||
* Stops observing provided element. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-unobserve | ||
* | ||
@@ -930,3 +930,3 @@ * @param {Element} target - Element to stop observing. | ||
// Do nothing if current environment doesn't have the Element interface. | ||
if (!('Element' in global$1) || !(Element instanceof Object)) { | ||
if (typeof Element === 'undefined' || !(Element instanceof Object)) { | ||
return; | ||
@@ -939,16 +939,13 @@ } | ||
var targets = this.observationTargets_; | ||
var observations = this.observations_; | ||
// Do nothing if element is not being observed. | ||
if (!targets.has(target)) { | ||
if (!observations.has(target)) { | ||
return; | ||
} | ||
// Remove element and associated with it ResizeObsrvation instance from | ||
// registry. | ||
targets.delete(target); | ||
observations.delete(target); | ||
// Set back the initial state if there is nothing to observe. | ||
if (!targets.size) { | ||
this.controller_.disconnect(this); | ||
if (!observations.size) { | ||
this.controller_.removeObserver(this); | ||
} | ||
@@ -958,4 +955,3 @@ }; | ||
/** | ||
* Stops observing all elements and clears the observations list. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-disconnect | ||
* Stops observing all elements. | ||
* | ||
@@ -966,10 +962,9 @@ * @returns {void} | ||
this.clearActive(); | ||
this.observationTargets_.clear(); | ||
this.controller_.disconnect(this); | ||
this.observations_.clear(); | ||
this.controller_.removeObserver(this); | ||
}; | ||
/** | ||
* Clears an array of previously collected active observations and collects | ||
* observation instances which associated element has changed it's content | ||
* rectangle. | ||
* Collects observation instances the associated element of which has changed | ||
* it's content rectangle. | ||
* | ||
@@ -979,9 +974,9 @@ * @returns {void} | ||
ResizeObserverSPI.prototype.gatherActive = function () { | ||
var this$1 = this; | ||
this.clearActive(); | ||
var activeTargets = this.activeTargets_; | ||
this.observationTargets_.forEach(function (observation) { | ||
this.observations_.forEach(function (observation) { | ||
if (observation.isActive()) { | ||
activeTargets.push(observation); | ||
this$1.activeObservations_.push(observation); | ||
} | ||
@@ -1006,3 +1001,3 @@ }); | ||
// Create ResizeObserverEntry instance for every active observation. | ||
var entries = this.activeTargets_.map(function (observation) { | ||
var entries = this.activeObservations_.map(function (observation) { | ||
return new ResizeObserverEntry(observation.target, observation.broadcastRect()); | ||
@@ -1016,3 +1011,3 @@ }); | ||
/** | ||
* Clears the collection of pending/active observations. | ||
* Clears the collection of active observations. | ||
* | ||
@@ -1022,7 +1017,7 @@ * @returns {void} | ||
ResizeObserverSPI.prototype.clearActive = function () { | ||
this.activeTargets_.splice(0); | ||
this.activeObservations_.splice(0); | ||
}; | ||
/** | ||
* Tells whether observer has pending observations. | ||
* Tells whether observer has active observations. | ||
* | ||
@@ -1032,18 +1027,16 @@ * @returns {boolean} | ||
ResizeObserverSPI.prototype.hasActive = function () { | ||
return this.activeTargets_.length > 0; | ||
return this.activeObservations_.length > 0; | ||
}; | ||
// Controller that will be assigned to all instances of the ResizeObserver. | ||
var controller = new ResizeObserverController(); | ||
// Registry of internal observers. If WeakMap is not available use current shim | ||
// of the Map collection as the former one can't be polyfilled anyway. | ||
var observers = typeof WeakMap === 'function' ? new WeakMap() : new Map(); | ||
// for the Map collection as it has all required methods and because WeakMap | ||
// can't be fully polyfilled anyway. | ||
var observers = typeof WeakMap != 'undefined' ? new WeakMap() : new MapShim(); | ||
/** | ||
* ResizeObserver API. Encapsulates the ResizeObserver SPI implementation | ||
* providing only those methods properties that are define in the spec. | ||
* exposing only those methods and properties that are defined in the spec. | ||
*/ | ||
var ResizeObserver = function(callback) { | ||
if (!(this instanceof ResizeObserver)) { | ||
var ResizeObserver$1 = function(callback) { | ||
if (!(this instanceof ResizeObserver$1)) { | ||
throw new TypeError('Cannot call a class as a function'); | ||
@@ -1056,6 +1049,5 @@ } | ||
// Create a new instance of an internal ResizeObserver. | ||
var controller = ResizeObserverController.getInstance(); | ||
var observer = new ResizeObserverSPI(callback, controller, this); | ||
// Register internal observer. | ||
observers.set(this, observer); | ||
@@ -1066,6 +1058,5 @@ }; | ||
['observe', 'unobserve', 'disconnect'].forEach(function (method) { | ||
ResizeObserver.prototype[method] = function () { | ||
ResizeObserver$1.prototype[method] = function () { | ||
return (ref = observers.get(this))[method].apply(ref, arguments); | ||
var ref; | ||
return (ref = observers.get(this))[method].apply(ref, arguments); | ||
}; | ||
@@ -1075,10 +1066,11 @@ }); | ||
var index = (function () { | ||
// Export existing implementation if it's available. | ||
if (typeof global$1.ResizeObserver === 'function') { | ||
return global$1.ResizeObserver; | ||
// Export existing implementation if available. | ||
if (typeof ResizeObserver != 'undefined') { | ||
// eslint-disable-next-line no-undef | ||
return ResizeObserver; | ||
} | ||
return ResizeObserver; | ||
return ResizeObserver$1; | ||
})(); | ||
export default index; |
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global.ResizeObserver = factory()); | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global.ResizeObserver = factory()); | ||
}(this, (function () { | ||
@@ -34,8 +34,15 @@ 'use strict'; | ||
*/ | ||
/* eslint-disable require-jsdoc */ | ||
var Map = (function () { | ||
if (typeof global$1.Map === 'function') { | ||
return global$1.Map; | ||
/* 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) { | ||
@@ -64,2 +71,5 @@ var result = -1; | ||
/** | ||
* @returns {boolean} | ||
*/ | ||
prototypeAccessors.size.get = function () { | ||
@@ -69,2 +79,6 @@ return this.__entries__.length; | ||
/** | ||
* @param {*} key | ||
* @returns {*} | ||
*/ | ||
anonymous.prototype.get = function (key) { | ||
@@ -77,2 +91,7 @@ var index = getIndex(this.__entries__, key); | ||
/** | ||
* @param {*} key | ||
* @param {*} value | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.set = function (key, value) { | ||
@@ -88,2 +107,6 @@ var index = getIndex(this.__entries__, key); | ||
/** | ||
* @param {*} key | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.delete = function (key) { | ||
@@ -98,2 +121,6 @@ var entries = this.__entries__; | ||
/** | ||
* @param {*} key | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.has = function (key) { | ||
@@ -103,2 +130,5 @@ return !!~getIndex(this.__entries__, key); | ||
/** | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.clear = function () { | ||
@@ -108,7 +138,11 @@ this.__entries__.splice(0); | ||
/** | ||
* @param {Function} callback | ||
* @param {*} [ctx=null] | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.forEach = function (callback, ctx) { | ||
var this$1 = this; | ||
if ( ctx === void 0 ) ctx = null; | ||
for (var i = 0, list = this$1.__entries__; i < list.length; i += 1) { | ||
for (var i = 0, list = this.__entries__; i < list.length; i += 1) { | ||
var entry = list[i]; | ||
@@ -129,3 +163,3 @@ | ||
*/ | ||
var isBrowser = global$1.window === global$1 && typeof document != 'undefined'; | ||
var isBrowser = typeof window != 'undefined' && typeof document != 'undefined' && window.document === document; | ||
@@ -150,31 +184,10 @@ /** | ||
/** | ||
* Returns time stamp retrieved either from the "performance.now" or from | ||
* the "Date.now" method. | ||
* | ||
* @returns {DOMHighResTimeStamp|number} | ||
*/ | ||
var timeStamp = (function () { | ||
var host = Date; | ||
if (typeof performance === 'object' && typeof performance.now === 'function') { | ||
host = performance; | ||
} | ||
return function () { return host.now(); }; | ||
})(); | ||
/** | ||
* Creates a wrapper function which ensures that provided callback will be | ||
* invoked only once during the specified delay period. It also caches the last | ||
* call and re-invokes it after pending activation is resolved. | ||
* 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. | ||
* @param {boolean} [afterRAF = false] - Whether function needs to be invoked as | ||
* a requestAnimationFrame callback. | ||
* @returns {Function} | ||
*/ | ||
var throttle = function (callback, delay, afterRAF) { | ||
if ( afterRAF === void 0 ) afterRAF = false; | ||
var throttle = function (callback, delay) { | ||
var leadingCall = false, | ||
@@ -185,14 +198,14 @@ trailingCall = false, | ||
/** | ||
* Invokes the original callback function and schedules a new invocation if | ||
* the wrapper was called during current request. | ||
* Invokes the original callback function and schedules new invocation if | ||
* the "proxy" was called during current request. | ||
* | ||
* @returns {void} | ||
*/ | ||
function invokeCallback() { | ||
leadingCall = false; | ||
function resolvePending() { | ||
if (leadingCall) { | ||
leadingCall = false; | ||
// Invoke original function. | ||
callback(); | ||
callback(); | ||
} | ||
// Schedule new invocation if there has been a call during delay period. | ||
if (trailingCall) { | ||
@@ -204,5 +217,5 @@ proxy(); | ||
/** | ||
* Callback that will be invoked after the specified delay period. It will | ||
* delegate invocation of the original function to the requestAnimationFrame | ||
* if "afterRAF" parameter is set to "true". | ||
* Callback invoked after the specified delay. It will further postpone | ||
* invocation of the original function delegating it to the | ||
* requestAnimationFrame. | ||
* | ||
@@ -212,7 +225,7 @@ * @returns {void} | ||
function timeoutCallback() { | ||
afterRAF ? requestAnimationFrame$1(invokeCallback) : invokeCallback(); | ||
requestAnimationFrame$1(resolvePending); | ||
} | ||
/** | ||
* Schedules invocation of the initial function. | ||
* Schedules invocation of the original function. | ||
* | ||
@@ -222,11 +235,14 @@ * @returns {void} | ||
function proxy() { | ||
var callTime = timeStamp(); | ||
var timeStamp = Date.now(); | ||
// Postpone activation if there is already a pending call. | ||
if (leadingCall) { | ||
// Reject immediately following invocations. | ||
if (callTime - lastCallTime < trailingTimeout) { | ||
// 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; | ||
@@ -237,7 +253,6 @@ } else { | ||
// Schedule new invocation. | ||
setTimeout(timeoutCallback, delay); | ||
} | ||
lastCallTime = callTime; | ||
lastCallTime = timeStamp; | ||
} | ||
@@ -251,40 +266,33 @@ | ||
// Delay before the next iteration of the continuous cycle. | ||
var CONTINUOUS_DELAY = 80; | ||
// 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']; | ||
// Define whether the MutationObserver is supported. | ||
// eslint-disable-next-line no-extra-parens | ||
var mutationsSupported = typeof MutationObserver === 'function' && | ||
// MutationObserver should not be used if running in IE11 as it's | ||
// Detect whether running in IE 11 (facepalm). | ||
var isIE11 = typeof navigator != 'undefined' && /Trident\/.*rv:11/.test(navigator.userAgent); | ||
// MutationObserver should not be used if running in Internet Explorer 11 as it's | ||
// implementation is unreliable. Example: https://jsfiddle.net/x2r3jpuz/2/ | ||
// Unfortunately, there is no other way to check this issue but to use | ||
// userAgent's information. | ||
typeof navigator === 'object' && !(navigator.appName === 'Netscape' && navigator.userAgent.match(/Trident\/.*rv:11/)); | ||
// | ||
// It's a real bummer that there is no other way to check for this issue but to | ||
// use the UA information. | ||
var mutationObserverSupported = typeof MutationObserver != 'undefined' && !isIE11; | ||
/** | ||
* Controller class which handles updates of ResizeObserver instances. | ||
* It decides when and for how long it's necessary to run updates by listening | ||
* to the windows "resize" event along with a tracking of DOM mutations | ||
* (nodes removal, changes of attributes, etc.). | ||
* | ||
* Transitions and animations are handled by running a repeatable update cycle | ||
* until the dimensions of observed elements are changing. | ||
* | ||
* Continuous update cycle will be used automatically in case MutationObserver | ||
* is not supported. | ||
* Singleton controller class which handles updates of ResizeObserver instances. | ||
*/ | ||
var ResizeObserverController = function() { | ||
/** | ||
* Continuous updates must be enabled if MutationObserver is not supported. | ||
* Indicates whether DOM listeners have been added. | ||
* | ||
* @private {boolean} | ||
*/ | ||
this.isCycleContinuous_ = !mutationsSupported; | ||
this.connected_ = false; | ||
/** | ||
* Indicates whether DOM listeners have been added. | ||
* Tells that controller has subscribed for Mutation Events. | ||
* | ||
* @private {boolean} | ||
*/ | ||
this.listenersEnabled_ = false; | ||
this.mutationEventsAdded_ = false; | ||
@@ -305,8 +313,4 @@ /** | ||
// Make sure that the "refresh" method is invoked as a RAF callback and | ||
// that it happens only once during the provided period. | ||
this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY, true); | ||
// Additionally postpone invocation of the continuous updates. | ||
this.continuousUpdateHandler_ = throttle(this.refresh, CONTINUOUS_DELAY); | ||
this.onTransitionEnd_ = this.onTransitionEnd_.bind(this); | ||
this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY); | ||
}; | ||
@@ -320,4 +324,4 @@ | ||
*/ | ||
ResizeObserverController.prototype.connect = function (observer) { | ||
if (!this.isConnected(observer)) { | ||
ResizeObserverController.prototype.addObserver = function (observer) { | ||
if (!~this.observers_.indexOf(observer)) { | ||
this.observers_.push(observer); | ||
@@ -327,4 +331,4 @@ } | ||
// Add listeners if they haven't been added yet. | ||
if (!this.listenersEnabled_) { | ||
this.addListeners_(); | ||
if (!this.connected_) { | ||
this.connect_(); | ||
} | ||
@@ -339,3 +343,3 @@ }; | ||
*/ | ||
ResizeObserverController.prototype.disconnect = function (observer) { | ||
ResizeObserverController.prototype.removeObserver = function (observer) { | ||
var observers = this.observers_; | ||
@@ -350,4 +354,4 @@ var index = observers.indexOf(observer); | ||
// Remove listeners if controller has no connected observers. | ||
if (!observers.length && this.listenersEnabled_) { | ||
this.removeListeners_(); | ||
if (!observers.length && this.connected_) { | ||
this.disconnect_(); | ||
} | ||
@@ -357,14 +361,4 @@ }; | ||
/** | ||
* Tells whether the provided observer is connected to controller. | ||
* | ||
* @param {ResizeObserverSPI} observer - Observer to be checked. | ||
* @returns {boolean} | ||
*/ | ||
ResizeObserverController.prototype.isConnected = function (observer) { | ||
return !!~this.observers_.indexOf(observer); | ||
}; | ||
/** | ||
* Invokes the update of observers. It will continue running updates insofar | ||
* it detects changes or if continuous updates are enabled. | ||
* it detects changes. | ||
* | ||
@@ -374,11 +368,8 @@ * @returns {void} | ||
ResizeObserverController.prototype.refresh = function () { | ||
var hasChanges = this.updateObservers_(); | ||
var changesDetected = this.updateObservers_(); | ||
// Continue running updates if changes have been detected as there might | ||
// be future ones caused by CSS transitions. | ||
if (hasChanges) { | ||
if (changesDetected) { | ||
this.refresh(); | ||
} else if (this.isCycleContinuous_ && this.listenersEnabled_) { | ||
// Automatically repeat cycle if it's necessary. | ||
this.continuousUpdateHandler_(); | ||
} | ||
@@ -396,4 +387,4 @@ }; | ||
ResizeObserverController.prototype.updateObservers_ = function () { | ||
// Collect observers that have active entries. | ||
var active = this.observers_.filter(function (observer) { | ||
// Collect observers that have active observations. | ||
var activeObservers = this.observers_.filter(function (observer) { | ||
return observer.gatherActive(), observer.hasActive(); | ||
@@ -403,9 +394,9 @@ }); | ||
// Deliver notifications in a separate cycle in order to avoid any | ||
// collisions between observers. E.g. when multiple instances of | ||
// ResizeObserer are tracking the same element and the callback of one | ||
// 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. | ||
active.forEach(function (observer) { return observer.broadcastActive(); }); | ||
activeObservers.forEach(function (observer) { return observer.broadcastActive(); }); | ||
return active.length > 0; | ||
return activeObservers.length > 0; | ||
}; | ||
@@ -419,19 +410,17 @@ | ||
*/ | ||
ResizeObserverController.prototype.addListeners_ = function () { | ||
ResizeObserverController.prototype.connect_ = function () { | ||
// Do nothing if running in a non-browser environment or if listeners | ||
// have been already added. | ||
if (!isBrowser || this.listenersEnabled_) { | ||
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); | ||
// Subscription to the "Transitionend" event is used as a workaround for | ||
// delayed transitions. This way we can capture at least the final state | ||
// of an element. | ||
document.addEventListener('transitionend', this.refresh); | ||
// Subscribe to DOM mutations if it's possible as they may lead to | ||
// changes in the dimensions of elements. | ||
if (mutationsSupported) { | ||
if (mutationObserverSupported) { | ||
this.mutationsObserver_ = new MutationObserver(this.refresh); | ||
@@ -445,11 +434,9 @@ | ||
}); | ||
} else { | ||
document.addEventListener('DOMSubtreeModified', this.refresh); | ||
this.mutationEventsAdded_ = true; | ||
} | ||
this.listenersEnabled_ = true; | ||
// Don't wait for a possible event that might trigger the update of | ||
// observers and manually initiate the update process. | ||
if (this.isCycleContinuous_) { | ||
this.refresh(); | ||
} | ||
this.connected_ = true; | ||
}; | ||
@@ -463,11 +450,11 @@ | ||
*/ | ||
ResizeObserverController.prototype.removeListeners_ = function () { | ||
ResizeObserverController.prototype.disconnect_ = function () { | ||
// Do nothing if running in a non-browser environment or if listeners | ||
// have been already removed. | ||
if (!isBrowser || !this.listenersEnabled_) { | ||
if (!isBrowser || !this.connected_) { | ||
return; | ||
} | ||
document.removeEventListener('transitionend', this.onTransitionEnd_); | ||
window.removeEventListener('resize', this.refresh); | ||
document.removeEventListener('transitionend', this.refresh); | ||
@@ -478,7 +465,52 @@ if (this.mutationsObserver_) { | ||
if (this.mutationEventsAdded_) { | ||
document.removeEventListener('DOMSubtreeModified', this.refresh); | ||
} | ||
this.mutationsObserver_ = null; | ||
this.listenersEnabled_ = false; | ||
this.mutationEventsAdded_ = false; | ||
this.connected_ = false; | ||
}; | ||
/** | ||
* "Transitionend" event handler. | ||
* | ||
* @private | ||
* @param {TransitionEvent} event | ||
* @returns {void} | ||
*/ | ||
ResizeObserverController.prototype.onTransitionEnd_ = function (ref) { | ||
var propertyName = ref.propertyName; | ||
// 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; | ||
/** | ||
* Defines non-writable/enumerable properties of the provided target object. | ||
@@ -496,3 +528,3 @@ * | ||
value: props[key], | ||
enumerbale: false, | ||
enumerable: false, | ||
writable: false, | ||
@@ -529,4 +561,4 @@ configurable: true | ||
return positions.reduce(function (size, pos) { | ||
var value = styles['border-' + pos + '-width']; | ||
return positions.reduce(function (size, position) { | ||
var value = styles['border-' + position + '-width']; | ||
@@ -544,11 +576,11 @@ return size + toFloat(value); | ||
function getPaddings(styles) { | ||
var boxKeys = ['top', 'right', 'bottom', 'left']; | ||
var positions = ['top', 'right', 'bottom', 'left']; | ||
var paddings = {}; | ||
for (var i = 0, list = boxKeys; i < list.length; i += 1) { | ||
var key = list[i]; | ||
for (var i = 0, list = positions; i < list.length; i += 1) { | ||
var position = list[i]; | ||
var value = styles['padding-' + key]; | ||
var value = styles['padding-' + position]; | ||
paddings[key] = toFloat(value); | ||
paddings[position] = toFloat(value); | ||
} | ||
@@ -603,3 +635,3 @@ | ||
// only dimensions available to JS that contain non-rounded values. It could | ||
// be possible to utilize getBoundingClientRect if only it's data wasn't | ||
// be possible to utilize the getBoundingClientRect if only it's data wasn't | ||
// affected by CSS transformations let alone paddings, borders and scroll bars. | ||
@@ -609,4 +641,4 @@ var width = toFloat(styles.width), | ||
// Width & height include paddings and bord when 'border-box' box model is | ||
// applied (except for IE). | ||
// Width & height include paddings and borders when the 'border-box' box | ||
// model is applied (except for IE). | ||
if (styles.boxSizing === 'border-box') { | ||
@@ -628,3 +660,3 @@ // Following conditions are required to handle Internet Explorer which | ||
// Following steps can't applied to the document's root element as it's | ||
// Following steps can't be applied to the document's root element as its | ||
// client[Width/Height] properties represent viewport area of the window. | ||
@@ -667,8 +699,8 @@ // Besides, it's as well not necessary as the <html> itself neither has | ||
// interface. | ||
if (typeof SVGGraphicsElement === 'function') { | ||
if (typeof SVGGraphicsElement != 'undefined') { | ||
return function (target) { return target instanceof SVGGraphicsElement; }; | ||
} | ||
// If it's so, than check that element is at least an instance of the | ||
// SVGElement and that it has the "getBBox" method in the prototype chain. | ||
// 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 | ||
@@ -679,3 +711,3 @@ return function (target) { return target instanceof SVGElement && typeof target.getBBox === 'function'; }; | ||
/** | ||
* Checks whether provided element is a document element (root element of a document, i.e. <html>). | ||
* Checks whether provided element is a document element (<html>). | ||
* | ||
@@ -696,3 +728,2 @@ * @param {Element} target - Element to be checked. | ||
function getContentRect(target) { | ||
// Return empty rectangle if running in a non-browser environment. | ||
if (!isBrowser) { | ||
@@ -723,3 +754,3 @@ return emptyRect; | ||
// If DOMRectReadOnly is available use it as a prototype for the rectangle. | ||
var Constr = typeof DOMRectReadOnly === 'function' ? DOMRectReadOnly : Object; | ||
var Constr = typeof DOMRectReadOnly != 'undefined' ? DOMRectReadOnly : Object; | ||
var rect = Object.create(Constr.prototype); | ||
@@ -729,4 +760,3 @@ | ||
defineConfigurable(rect, { | ||
x: x, y: y, | ||
width: width, height: height, | ||
x: x, y: y, width: width, height: height, | ||
top: y, | ||
@@ -761,9 +791,2 @@ right: x + width, | ||
/** | ||
* Reference to the observed element. | ||
* | ||
* @type {Element} | ||
*/ | ||
this.target = target; | ||
/** | ||
* Broadcasted width of content rectangle. | ||
@@ -788,2 +811,9 @@ * | ||
this.contentRect_ = createRectInit(0, 0, 0, 0); | ||
/** | ||
* Reference to the observed element. | ||
* | ||
* @type {Element} | ||
*/ | ||
this.target = target; | ||
}; | ||
@@ -841,19 +871,15 @@ | ||
* | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-activetargets | ||
* | ||
* @private {Array<ResizeObservation>} | ||
*/ | ||
this.activeTargets_ = []; | ||
this.activeObservations_ = []; | ||
/** | ||
* Registry of the ResizeObservation instances. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-observationtargets | ||
* | ||
* @private {Map<Element, ResizeObservation>} | ||
*/ | ||
this.observationTargets_ = new Map(); | ||
this.observations_ = new MapShim(); | ||
/** | ||
* Reference to the callback function. | ||
* Spec: https://wicg.github.io/ResizeObserver/#resize-observer-callback | ||
* | ||
@@ -882,3 +908,2 @@ * @private {ResizeObserverCallback} | ||
* Starts observing provided element. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-observe | ||
* | ||
@@ -894,3 +919,3 @@ * @param {Element} target - Element to be observed. | ||
// Do nothing if current environment doesn't have the Element interface. | ||
if (!('Element' in global$1) || !(Element instanceof Object)) { | ||
if (typeof Element === 'undefined' || !(Element instanceof Object)) { | ||
return; | ||
@@ -903,16 +928,12 @@ } | ||
var targets = this.observationTargets_; | ||
var observations = this.observations_; | ||
// Do nothing if element is already being observed. | ||
if (targets.has(target)) { | ||
if (observations.has(target)) { | ||
return; | ||
} | ||
// Register new ResizeObservation instance. | ||
targets.set(target, new ResizeObservation(target)); | ||
observations.set(target, new ResizeObservation(target)); | ||
// Add observer to controller if it hasn't been connected yet. | ||
if (!this.controller_.isConnected(this)) { | ||
this.controller_.connect(this); | ||
} | ||
this.controller_.addObserver(this); | ||
@@ -925,3 +946,2 @@ // Force the update of observations. | ||
* Stops observing provided element. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-unobserve | ||
* | ||
@@ -937,3 +957,3 @@ * @param {Element} target - Element to stop observing. | ||
// Do nothing if current environment doesn't have the Element interface. | ||
if (!('Element' in global$1) || !(Element instanceof Object)) { | ||
if (typeof Element === 'undefined' || !(Element instanceof Object)) { | ||
return; | ||
@@ -946,16 +966,13 @@ } | ||
var targets = this.observationTargets_; | ||
var observations = this.observations_; | ||
// Do nothing if element is not being observed. | ||
if (!targets.has(target)) { | ||
if (!observations.has(target)) { | ||
return; | ||
} | ||
// Remove element and associated with it ResizeObsrvation instance from | ||
// registry. | ||
targets.delete(target); | ||
observations.delete(target); | ||
// Set back the initial state if there is nothing to observe. | ||
if (!targets.size) { | ||
this.controller_.disconnect(this); | ||
if (!observations.size) { | ||
this.controller_.removeObserver(this); | ||
} | ||
@@ -965,4 +982,3 @@ }; | ||
/** | ||
* Stops observing all elements and clears the observations list. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-disconnect | ||
* Stops observing all elements. | ||
* | ||
@@ -973,10 +989,9 @@ * @returns {void} | ||
this.clearActive(); | ||
this.observationTargets_.clear(); | ||
this.controller_.disconnect(this); | ||
this.observations_.clear(); | ||
this.controller_.removeObserver(this); | ||
}; | ||
/** | ||
* Clears an array of previously collected active observations and collects | ||
* observation instances which associated element has changed it's content | ||
* rectangle. | ||
* Collects observation instances the associated element of which has changed | ||
* it's content rectangle. | ||
* | ||
@@ -986,9 +1001,9 @@ * @returns {void} | ||
ResizeObserverSPI.prototype.gatherActive = function () { | ||
var this$1 = this; | ||
this.clearActive(); | ||
var activeTargets = this.activeTargets_; | ||
this.observationTargets_.forEach(function (observation) { | ||
this.observations_.forEach(function (observation) { | ||
if (observation.isActive()) { | ||
activeTargets.push(observation); | ||
this$1.activeObservations_.push(observation); | ||
} | ||
@@ -1013,3 +1028,3 @@ }); | ||
// Create ResizeObserverEntry instance for every active observation. | ||
var entries = this.activeTargets_.map(function (observation) { | ||
var entries = this.activeObservations_.map(function (observation) { | ||
return new ResizeObserverEntry(observation.target, observation.broadcastRect()); | ||
@@ -1023,3 +1038,3 @@ }); | ||
/** | ||
* Clears the collection of pending/active observations. | ||
* Clears the collection of active observations. | ||
* | ||
@@ -1029,7 +1044,7 @@ * @returns {void} | ||
ResizeObserverSPI.prototype.clearActive = function () { | ||
this.activeTargets_.splice(0); | ||
this.activeObservations_.splice(0); | ||
}; | ||
/** | ||
* Tells whether observer has pending observations. | ||
* Tells whether observer has active observations. | ||
* | ||
@@ -1039,18 +1054,16 @@ * @returns {boolean} | ||
ResizeObserverSPI.prototype.hasActive = function () { | ||
return this.activeTargets_.length > 0; | ||
return this.activeObservations_.length > 0; | ||
}; | ||
// Controller that will be assigned to all instances of the ResizeObserver. | ||
var controller = new ResizeObserverController(); | ||
// Registry of internal observers. If WeakMap is not available use current shim | ||
// of the Map collection as the former one can't be polyfilled anyway. | ||
var observers = typeof WeakMap === 'function' ? new WeakMap() : new Map(); | ||
// for the Map collection as it has all required methods and because WeakMap | ||
// can't be fully polyfilled anyway. | ||
var observers = typeof WeakMap != 'undefined' ? new WeakMap() : new MapShim(); | ||
/** | ||
* ResizeObserver API. Encapsulates the ResizeObserver SPI implementation | ||
* providing only those methods properties that are define in the spec. | ||
* exposing only those methods and properties that are defined in the spec. | ||
*/ | ||
var ResizeObserver = function(callback) { | ||
if (!(this instanceof ResizeObserver)) { | ||
var ResizeObserver$1 = function(callback) { | ||
if (!(this instanceof ResizeObserver$1)) { | ||
throw new TypeError('Cannot call a class as a function'); | ||
@@ -1063,6 +1076,5 @@ } | ||
// Create a new instance of an internal ResizeObserver. | ||
var controller = ResizeObserverController.getInstance(); | ||
var observer = new ResizeObserverSPI(callback, controller, this); | ||
// Register internal observer. | ||
observers.set(this, observer); | ||
@@ -1073,28 +1085,21 @@ }; | ||
['observe', 'unobserve', 'disconnect'].forEach(function (method) { | ||
ResizeObserver.prototype[method] = function () { | ||
ResizeObserver$1.prototype[method] = function () { | ||
return (ref = observers.get(this))[method].apply(ref, arguments); | ||
var ref; | ||
return (ref = observers.get(this))[method].apply(ref, arguments); | ||
}; | ||
}); | ||
/** | ||
* @deprecated Global version of the polyfill is deprecated and will be removed in the next major release. | ||
*/ | ||
if (typeof global$1.ResizeObserver !== 'function') { | ||
// ResizeObserver host property is not enumerable | ||
// in the native implementation. | ||
Object.defineProperty(global$1, 'ResizeObserver', { | ||
value: ResizeObserver, | ||
writable: true, | ||
configurable: true | ||
}); | ||
} | ||
var index = (function () { | ||
// Export existing implementation if available. | ||
if (typeof ResizeObserver != 'undefined') { | ||
// eslint-disable-next-line no-undef | ||
return ResizeObserver; | ||
} | ||
// Still export the constructor as for me it seems | ||
// awkward when a module doesn't export anything. | ||
var index_global = global$1.ResizeObserver; | ||
return ResizeObserver$1; | ||
})(); | ||
return index_global; | ||
global$1.ResizeObserver = index; | ||
return index; | ||
}))); |
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global.ResizeObserver = factory()); | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global.ResizeObserver = factory()); | ||
}(this, (function () { | ||
@@ -9,22 +9,2 @@ 'use strict'; | ||
/** | ||
* Exports global object for the 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 collection of shims that provide minimal functionality of the ES6 collections. | ||
@@ -35,8 +15,15 @@ * | ||
*/ | ||
/* eslint-disable require-jsdoc */ | ||
var Map = (function () { | ||
if (typeof global$1.Map === 'function') { | ||
return global$1.Map; | ||
/* 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) { | ||
@@ -65,2 +52,5 @@ var result = -1; | ||
/** | ||
* @returns {boolean} | ||
*/ | ||
prototypeAccessors.size.get = function () { | ||
@@ -70,2 +60,6 @@ return this.__entries__.length; | ||
/** | ||
* @param {*} key | ||
* @returns {*} | ||
*/ | ||
anonymous.prototype.get = function (key) { | ||
@@ -78,2 +72,7 @@ var index = getIndex(this.__entries__, key); | ||
/** | ||
* @param {*} key | ||
* @param {*} value | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.set = function (key, value) { | ||
@@ -89,2 +88,6 @@ var index = getIndex(this.__entries__, key); | ||
/** | ||
* @param {*} key | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.delete = function (key) { | ||
@@ -99,2 +102,6 @@ var entries = this.__entries__; | ||
/** | ||
* @param {*} key | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.has = function (key) { | ||
@@ -104,2 +111,5 @@ return !!~getIndex(this.__entries__, key); | ||
/** | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.clear = function () { | ||
@@ -109,7 +119,11 @@ this.__entries__.splice(0); | ||
/** | ||
* @param {Function} callback | ||
* @param {*} [ctx=null] | ||
* @returns {void} | ||
*/ | ||
anonymous.prototype.forEach = function (callback, ctx) { | ||
var this$1 = this; | ||
if ( ctx === void 0 ) ctx = null; | ||
for (var i = 0, list = this$1.__entries__; i < list.length; i += 1) { | ||
for (var i = 0, list = this.__entries__; i < list.length; i += 1) { | ||
var entry = list[i]; | ||
@@ -130,3 +144,3 @@ | ||
*/ | ||
var isBrowser = global$1.window === global$1 && typeof document != 'undefined'; | ||
var isBrowser = typeof window != 'undefined' && typeof document != 'undefined' && window.document === document; | ||
@@ -151,31 +165,10 @@ /** | ||
/** | ||
* Returns time stamp retrieved either from the "performance.now" or from | ||
* the "Date.now" method. | ||
* | ||
* @returns {DOMHighResTimeStamp|number} | ||
*/ | ||
var timeStamp = (function () { | ||
var host = Date; | ||
if (typeof performance === 'object' && typeof performance.now === 'function') { | ||
host = performance; | ||
} | ||
return function () { return host.now(); }; | ||
})(); | ||
/** | ||
* Creates a wrapper function which ensures that provided callback will be | ||
* invoked only once during the specified delay period. It also caches the last | ||
* call and re-invokes it after pending activation is resolved. | ||
* 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. | ||
* @param {boolean} [afterRAF = false] - Whether function needs to be invoked as | ||
* a requestAnimationFrame callback. | ||
* @returns {Function} | ||
*/ | ||
var throttle = function (callback, delay, afterRAF) { | ||
if ( afterRAF === void 0 ) afterRAF = false; | ||
var throttle = function (callback, delay) { | ||
var leadingCall = false, | ||
@@ -186,14 +179,14 @@ trailingCall = false, | ||
/** | ||
* Invokes the original callback function and schedules a new invocation if | ||
* the wrapper was called during current request. | ||
* Invokes the original callback function and schedules new invocation if | ||
* the "proxy" was called during current request. | ||
* | ||
* @returns {void} | ||
*/ | ||
function invokeCallback() { | ||
leadingCall = false; | ||
function resolvePending() { | ||
if (leadingCall) { | ||
leadingCall = false; | ||
// Invoke original function. | ||
callback(); | ||
callback(); | ||
} | ||
// Schedule new invocation if there has been a call during delay period. | ||
if (trailingCall) { | ||
@@ -205,5 +198,5 @@ proxy(); | ||
/** | ||
* Callback that will be invoked after the specified delay period. It will | ||
* delegate invocation of the original function to the requestAnimationFrame | ||
* if "afterRAF" parameter is set to "true". | ||
* Callback invoked after the specified delay. It will further postpone | ||
* invocation of the original function delegating it to the | ||
* requestAnimationFrame. | ||
* | ||
@@ -213,7 +206,7 @@ * @returns {void} | ||
function timeoutCallback() { | ||
afterRAF ? requestAnimationFrame$1(invokeCallback) : invokeCallback(); | ||
requestAnimationFrame$1(resolvePending); | ||
} | ||
/** | ||
* Schedules invocation of the initial function. | ||
* Schedules invocation of the original function. | ||
* | ||
@@ -223,11 +216,14 @@ * @returns {void} | ||
function proxy() { | ||
var callTime = timeStamp(); | ||
var timeStamp = Date.now(); | ||
// Postpone activation if there is already a pending call. | ||
if (leadingCall) { | ||
// Reject immediately following invocations. | ||
if (callTime - lastCallTime < trailingTimeout) { | ||
// 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; | ||
@@ -238,7 +234,6 @@ } else { | ||
// Schedule new invocation. | ||
setTimeout(timeoutCallback, delay); | ||
} | ||
lastCallTime = callTime; | ||
lastCallTime = timeStamp; | ||
} | ||
@@ -252,40 +247,33 @@ | ||
// Delay before the next iteration of the continuous cycle. | ||
var CONTINUOUS_DELAY = 80; | ||
// 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']; | ||
// Define whether the MutationObserver is supported. | ||
// eslint-disable-next-line no-extra-parens | ||
var mutationsSupported = typeof MutationObserver === 'function' && | ||
// MutationObserver should not be used if running in IE11 as it's | ||
// Detect whether running in IE 11 (facepalm). | ||
var isIE11 = typeof navigator != 'undefined' && /Trident\/.*rv:11/.test(navigator.userAgent); | ||
// MutationObserver should not be used if running in Internet Explorer 11 as it's | ||
// implementation is unreliable. Example: https://jsfiddle.net/x2r3jpuz/2/ | ||
// Unfortunately, there is no other way to check this issue but to use | ||
// userAgent's information. | ||
typeof navigator === 'object' && !(navigator.appName === 'Netscape' && navigator.userAgent.match(/Trident\/.*rv:11/)); | ||
// | ||
// It's a real bummer that there is no other way to check for this issue but to | ||
// use the UA information. | ||
var mutationObserverSupported = typeof MutationObserver != 'undefined' && !isIE11; | ||
/** | ||
* Controller class which handles updates of ResizeObserver instances. | ||
* It decides when and for how long it's necessary to run updates by listening | ||
* to the windows "resize" event along with a tracking of DOM mutations | ||
* (nodes removal, changes of attributes, etc.). | ||
* | ||
* Transitions and animations are handled by running a repeatable update cycle | ||
* until the dimensions of observed elements are changing. | ||
* | ||
* Continuous update cycle will be used automatically in case MutationObserver | ||
* is not supported. | ||
* Singleton controller class which handles updates of ResizeObserver instances. | ||
*/ | ||
var ResizeObserverController = function() { | ||
/** | ||
* Continuous updates must be enabled if MutationObserver is not supported. | ||
* Indicates whether DOM listeners have been added. | ||
* | ||
* @private {boolean} | ||
*/ | ||
this.isCycleContinuous_ = !mutationsSupported; | ||
this.connected_ = false; | ||
/** | ||
* Indicates whether DOM listeners have been added. | ||
* Tells that controller has subscribed for Mutation Events. | ||
* | ||
* @private {boolean} | ||
*/ | ||
this.listenersEnabled_ = false; | ||
this.mutationEventsAdded_ = false; | ||
@@ -306,8 +294,4 @@ /** | ||
// Make sure that the "refresh" method is invoked as a RAF callback and | ||
// that it happens only once during the provided period. | ||
this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY, true); | ||
// Additionally postpone invocation of the continuous updates. | ||
this.continuousUpdateHandler_ = throttle(this.refresh, CONTINUOUS_DELAY); | ||
this.onTransitionEnd_ = this.onTransitionEnd_.bind(this); | ||
this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY); | ||
}; | ||
@@ -321,4 +305,4 @@ | ||
*/ | ||
ResizeObserverController.prototype.connect = function (observer) { | ||
if (!this.isConnected(observer)) { | ||
ResizeObserverController.prototype.addObserver = function (observer) { | ||
if (!~this.observers_.indexOf(observer)) { | ||
this.observers_.push(observer); | ||
@@ -328,4 +312,4 @@ } | ||
// Add listeners if they haven't been added yet. | ||
if (!this.listenersEnabled_) { | ||
this.addListeners_(); | ||
if (!this.connected_) { | ||
this.connect_(); | ||
} | ||
@@ -340,3 +324,3 @@ }; | ||
*/ | ||
ResizeObserverController.prototype.disconnect = function (observer) { | ||
ResizeObserverController.prototype.removeObserver = function (observer) { | ||
var observers = this.observers_; | ||
@@ -351,4 +335,4 @@ var index = observers.indexOf(observer); | ||
// Remove listeners if controller has no connected observers. | ||
if (!observers.length && this.listenersEnabled_) { | ||
this.removeListeners_(); | ||
if (!observers.length && this.connected_) { | ||
this.disconnect_(); | ||
} | ||
@@ -358,14 +342,4 @@ }; | ||
/** | ||
* Tells whether the provided observer is connected to controller. | ||
* | ||
* @param {ResizeObserverSPI} observer - Observer to be checked. | ||
* @returns {boolean} | ||
*/ | ||
ResizeObserverController.prototype.isConnected = function (observer) { | ||
return !!~this.observers_.indexOf(observer); | ||
}; | ||
/** | ||
* Invokes the update of observers. It will continue running updates insofar | ||
* it detects changes or if continuous updates are enabled. | ||
* it detects changes. | ||
* | ||
@@ -375,11 +349,8 @@ * @returns {void} | ||
ResizeObserverController.prototype.refresh = function () { | ||
var hasChanges = this.updateObservers_(); | ||
var changesDetected = this.updateObservers_(); | ||
// Continue running updates if changes have been detected as there might | ||
// be future ones caused by CSS transitions. | ||
if (hasChanges) { | ||
if (changesDetected) { | ||
this.refresh(); | ||
} else if (this.isCycleContinuous_ && this.listenersEnabled_) { | ||
// Automatically repeat cycle if it's necessary. | ||
this.continuousUpdateHandler_(); | ||
} | ||
@@ -397,4 +368,4 @@ }; | ||
ResizeObserverController.prototype.updateObservers_ = function () { | ||
// Collect observers that have active entries. | ||
var active = this.observers_.filter(function (observer) { | ||
// Collect observers that have active observations. | ||
var activeObservers = this.observers_.filter(function (observer) { | ||
return observer.gatherActive(), observer.hasActive(); | ||
@@ -404,9 +375,9 @@ }); | ||
// Deliver notifications in a separate cycle in order to avoid any | ||
// collisions between observers. E.g. when multiple instances of | ||
// ResizeObserer are tracking the same element and the callback of one | ||
// 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. | ||
active.forEach(function (observer) { return observer.broadcastActive(); }); | ||
activeObservers.forEach(function (observer) { return observer.broadcastActive(); }); | ||
return active.length > 0; | ||
return activeObservers.length > 0; | ||
}; | ||
@@ -420,19 +391,17 @@ | ||
*/ | ||
ResizeObserverController.prototype.addListeners_ = function () { | ||
ResizeObserverController.prototype.connect_ = function () { | ||
// Do nothing if running in a non-browser environment or if listeners | ||
// have been already added. | ||
if (!isBrowser || this.listenersEnabled_) { | ||
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); | ||
// Subscription to the "Transitionend" event is used as a workaround for | ||
// delayed transitions. This way we can capture at least the final state | ||
// of an element. | ||
document.addEventListener('transitionend', this.refresh); | ||
// Subscribe to DOM mutations if it's possible as they may lead to | ||
// changes in the dimensions of elements. | ||
if (mutationsSupported) { | ||
if (mutationObserverSupported) { | ||
this.mutationsObserver_ = new MutationObserver(this.refresh); | ||
@@ -446,11 +415,9 @@ | ||
}); | ||
} else { | ||
document.addEventListener('DOMSubtreeModified', this.refresh); | ||
this.mutationEventsAdded_ = true; | ||
} | ||
this.listenersEnabled_ = true; | ||
// Don't wait for a possible event that might trigger the update of | ||
// observers and manually initiate the update process. | ||
if (this.isCycleContinuous_) { | ||
this.refresh(); | ||
} | ||
this.connected_ = true; | ||
}; | ||
@@ -464,11 +431,11 @@ | ||
*/ | ||
ResizeObserverController.prototype.removeListeners_ = function () { | ||
ResizeObserverController.prototype.disconnect_ = function () { | ||
// Do nothing if running in a non-browser environment or if listeners | ||
// have been already removed. | ||
if (!isBrowser || !this.listenersEnabled_) { | ||
if (!isBrowser || !this.connected_) { | ||
return; | ||
} | ||
document.removeEventListener('transitionend', this.onTransitionEnd_); | ||
window.removeEventListener('resize', this.refresh); | ||
document.removeEventListener('transitionend', this.refresh); | ||
@@ -479,7 +446,52 @@ if (this.mutationsObserver_) { | ||
if (this.mutationEventsAdded_) { | ||
document.removeEventListener('DOMSubtreeModified', this.refresh); | ||
} | ||
this.mutationsObserver_ = null; | ||
this.listenersEnabled_ = false; | ||
this.mutationEventsAdded_ = false; | ||
this.connected_ = false; | ||
}; | ||
/** | ||
* "Transitionend" event handler. | ||
* | ||
* @private | ||
* @param {TransitionEvent} event | ||
* @returns {void} | ||
*/ | ||
ResizeObserverController.prototype.onTransitionEnd_ = function (ref) { | ||
var propertyName = ref.propertyName; | ||
// 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; | ||
/** | ||
* Defines non-writable/enumerable properties of the provided target object. | ||
@@ -497,3 +509,3 @@ * | ||
value: props[key], | ||
enumerbale: false, | ||
enumerable: false, | ||
writable: false, | ||
@@ -530,4 +542,4 @@ configurable: true | ||
return positions.reduce(function (size, pos) { | ||
var value = styles['border-' + pos + '-width']; | ||
return positions.reduce(function (size, position) { | ||
var value = styles['border-' + position + '-width']; | ||
@@ -545,11 +557,11 @@ return size + toFloat(value); | ||
function getPaddings(styles) { | ||
var boxKeys = ['top', 'right', 'bottom', 'left']; | ||
var positions = ['top', 'right', 'bottom', 'left']; | ||
var paddings = {}; | ||
for (var i = 0, list = boxKeys; i < list.length; i += 1) { | ||
var key = list[i]; | ||
for (var i = 0, list = positions; i < list.length; i += 1) { | ||
var position = list[i]; | ||
var value = styles['padding-' + key]; | ||
var value = styles['padding-' + position]; | ||
paddings[key] = toFloat(value); | ||
paddings[position] = toFloat(value); | ||
} | ||
@@ -604,3 +616,3 @@ | ||
// only dimensions available to JS that contain non-rounded values. It could | ||
// be possible to utilize getBoundingClientRect if only it's data wasn't | ||
// be possible to utilize the getBoundingClientRect if only it's data wasn't | ||
// affected by CSS transformations let alone paddings, borders and scroll bars. | ||
@@ -610,4 +622,4 @@ var width = toFloat(styles.width), | ||
// Width & height include paddings and bord when 'border-box' box model is | ||
// applied (except for IE). | ||
// Width & height include paddings and borders when the 'border-box' box | ||
// model is applied (except for IE). | ||
if (styles.boxSizing === 'border-box') { | ||
@@ -629,3 +641,3 @@ // Following conditions are required to handle Internet Explorer which | ||
// Following steps can't applied to the document's root element as it's | ||
// Following steps can't be applied to the document's root element as its | ||
// client[Width/Height] properties represent viewport area of the window. | ||
@@ -668,8 +680,8 @@ // Besides, it's as well not necessary as the <html> itself neither has | ||
// interface. | ||
if (typeof SVGGraphicsElement === 'function') { | ||
if (typeof SVGGraphicsElement != 'undefined') { | ||
return function (target) { return target instanceof SVGGraphicsElement; }; | ||
} | ||
// If it's so, than check that element is at least an instance of the | ||
// SVGElement and that it has the "getBBox" method in the prototype chain. | ||
// 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 | ||
@@ -680,3 +692,3 @@ return function (target) { return target instanceof SVGElement && typeof target.getBBox === 'function'; }; | ||
/** | ||
* Checks whether provided element is a document element (root element of a document, i.e. <html>). | ||
* Checks whether provided element is a document element (<html>). | ||
* | ||
@@ -697,3 +709,2 @@ * @param {Element} target - Element to be checked. | ||
function getContentRect(target) { | ||
// Return empty rectangle if running in a non-browser environment. | ||
if (!isBrowser) { | ||
@@ -724,3 +735,3 @@ return emptyRect; | ||
// If DOMRectReadOnly is available use it as a prototype for the rectangle. | ||
var Constr = typeof DOMRectReadOnly === 'function' ? DOMRectReadOnly : Object; | ||
var Constr = typeof DOMRectReadOnly != 'undefined' ? DOMRectReadOnly : Object; | ||
var rect = Object.create(Constr.prototype); | ||
@@ -730,4 +741,3 @@ | ||
defineConfigurable(rect, { | ||
x: x, y: y, | ||
width: width, height: height, | ||
x: x, y: y, width: width, height: height, | ||
top: y, | ||
@@ -762,9 +772,2 @@ right: x + width, | ||
/** | ||
* Reference to the observed element. | ||
* | ||
* @type {Element} | ||
*/ | ||
this.target = target; | ||
/** | ||
* Broadcasted width of content rectangle. | ||
@@ -789,2 +792,9 @@ * | ||
this.contentRect_ = createRectInit(0, 0, 0, 0); | ||
/** | ||
* Reference to the observed element. | ||
* | ||
* @type {Element} | ||
*/ | ||
this.target = target; | ||
}; | ||
@@ -842,19 +852,15 @@ | ||
* | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-activetargets | ||
* | ||
* @private {Array<ResizeObservation>} | ||
*/ | ||
this.activeTargets_ = []; | ||
this.activeObservations_ = []; | ||
/** | ||
* Registry of the ResizeObservation instances. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-observationtargets | ||
* | ||
* @private {Map<Element, ResizeObservation>} | ||
*/ | ||
this.observationTargets_ = new Map(); | ||
this.observations_ = new MapShim(); | ||
/** | ||
* Reference to the callback function. | ||
* Spec: https://wicg.github.io/ResizeObserver/#resize-observer-callback | ||
* | ||
@@ -883,3 +889,2 @@ * @private {ResizeObserverCallback} | ||
* Starts observing provided element. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-observe | ||
* | ||
@@ -895,3 +900,3 @@ * @param {Element} target - Element to be observed. | ||
// Do nothing if current environment doesn't have the Element interface. | ||
if (!('Element' in global$1) || !(Element instanceof Object)) { | ||
if (typeof Element === 'undefined' || !(Element instanceof Object)) { | ||
return; | ||
@@ -904,16 +909,12 @@ } | ||
var targets = this.observationTargets_; | ||
var observations = this.observations_; | ||
// Do nothing if element is already being observed. | ||
if (targets.has(target)) { | ||
if (observations.has(target)) { | ||
return; | ||
} | ||
// Register new ResizeObservation instance. | ||
targets.set(target, new ResizeObservation(target)); | ||
observations.set(target, new ResizeObservation(target)); | ||
// Add observer to controller if it hasn't been connected yet. | ||
if (!this.controller_.isConnected(this)) { | ||
this.controller_.connect(this); | ||
} | ||
this.controller_.addObserver(this); | ||
@@ -926,3 +927,2 @@ // Force the update of observations. | ||
* Stops observing provided element. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-unobserve | ||
* | ||
@@ -938,3 +938,3 @@ * @param {Element} target - Element to stop observing. | ||
// Do nothing if current environment doesn't have the Element interface. | ||
if (!('Element' in global$1) || !(Element instanceof Object)) { | ||
if (typeof Element === 'undefined' || !(Element instanceof Object)) { | ||
return; | ||
@@ -947,16 +947,13 @@ } | ||
var targets = this.observationTargets_; | ||
var observations = this.observations_; | ||
// Do nothing if element is not being observed. | ||
if (!targets.has(target)) { | ||
if (!observations.has(target)) { | ||
return; | ||
} | ||
// Remove element and associated with it ResizeObsrvation instance from | ||
// registry. | ||
targets.delete(target); | ||
observations.delete(target); | ||
// Set back the initial state if there is nothing to observe. | ||
if (!targets.size) { | ||
this.controller_.disconnect(this); | ||
if (!observations.size) { | ||
this.controller_.removeObserver(this); | ||
} | ||
@@ -966,4 +963,3 @@ }; | ||
/** | ||
* Stops observing all elements and clears the observations list. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-disconnect | ||
* Stops observing all elements. | ||
* | ||
@@ -974,10 +970,9 @@ * @returns {void} | ||
this.clearActive(); | ||
this.observationTargets_.clear(); | ||
this.controller_.disconnect(this); | ||
this.observations_.clear(); | ||
this.controller_.removeObserver(this); | ||
}; | ||
/** | ||
* Clears an array of previously collected active observations and collects | ||
* observation instances which associated element has changed it's content | ||
* rectangle. | ||
* Collects observation instances the associated element of which has changed | ||
* it's content rectangle. | ||
* | ||
@@ -987,9 +982,9 @@ * @returns {void} | ||
ResizeObserverSPI.prototype.gatherActive = function () { | ||
var this$1 = this; | ||
this.clearActive(); | ||
var activeTargets = this.activeTargets_; | ||
this.observationTargets_.forEach(function (observation) { | ||
this.observations_.forEach(function (observation) { | ||
if (observation.isActive()) { | ||
activeTargets.push(observation); | ||
this$1.activeObservations_.push(observation); | ||
} | ||
@@ -1014,3 +1009,3 @@ }); | ||
// Create ResizeObserverEntry instance for every active observation. | ||
var entries = this.activeTargets_.map(function (observation) { | ||
var entries = this.activeObservations_.map(function (observation) { | ||
return new ResizeObserverEntry(observation.target, observation.broadcastRect()); | ||
@@ -1024,3 +1019,3 @@ }); | ||
/** | ||
* Clears the collection of pending/active observations. | ||
* Clears the collection of active observations. | ||
* | ||
@@ -1030,7 +1025,7 @@ * @returns {void} | ||
ResizeObserverSPI.prototype.clearActive = function () { | ||
this.activeTargets_.splice(0); | ||
this.activeObservations_.splice(0); | ||
}; | ||
/** | ||
* Tells whether observer has pending observations. | ||
* Tells whether observer has active observations. | ||
* | ||
@@ -1040,18 +1035,16 @@ * @returns {boolean} | ||
ResizeObserverSPI.prototype.hasActive = function () { | ||
return this.activeTargets_.length > 0; | ||
return this.activeObservations_.length > 0; | ||
}; | ||
// Controller that will be assigned to all instances of the ResizeObserver. | ||
var controller = new ResizeObserverController(); | ||
// Registry of internal observers. If WeakMap is not available use current shim | ||
// of the Map collection as the former one can't be polyfilled anyway. | ||
var observers = typeof WeakMap === 'function' ? new WeakMap() : new Map(); | ||
// for the Map collection as it has all required methods and because WeakMap | ||
// can't be fully polyfilled anyway. | ||
var observers = typeof WeakMap != 'undefined' ? new WeakMap() : new MapShim(); | ||
/** | ||
* ResizeObserver API. Encapsulates the ResizeObserver SPI implementation | ||
* providing only those methods properties that are define in the spec. | ||
* exposing only those methods and properties that are defined in the spec. | ||
*/ | ||
var ResizeObserver = function(callback) { | ||
if (!(this instanceof ResizeObserver)) { | ||
var ResizeObserver$1 = function(callback) { | ||
if (!(this instanceof ResizeObserver$1)) { | ||
throw new TypeError('Cannot call a class as a function'); | ||
@@ -1064,6 +1057,5 @@ } | ||
// Create a new instance of an internal ResizeObserver. | ||
var controller = ResizeObserverController.getInstance(); | ||
var observer = new ResizeObserverSPI(callback, controller, this); | ||
// Register internal observer. | ||
observers.set(this, observer); | ||
@@ -1074,6 +1066,5 @@ }; | ||
['observe', 'unobserve', 'disconnect'].forEach(function (method) { | ||
ResizeObserver.prototype[method] = function () { | ||
ResizeObserver$1.prototype[method] = function () { | ||
return (ref = observers.get(this))[method].apply(ref, arguments); | ||
var ref; | ||
return (ref = observers.get(this))[method].apply(ref, arguments); | ||
}; | ||
@@ -1083,12 +1074,12 @@ }); | ||
var index = (function () { | ||
// Export existing implementation if it's available. | ||
if (typeof global$1.ResizeObserver === 'function') { | ||
return global$1.ResizeObserver; | ||
// Export existing implementation if available. | ||
if (typeof ResizeObserver != 'undefined') { | ||
// eslint-disable-next-line no-undef | ||
return ResizeObserver; | ||
} | ||
return ResizeObserver; | ||
return ResizeObserver$1; | ||
})(); | ||
return index; | ||
}))); |
{ | ||
"name": "resize-observer-polyfill", | ||
"author": "Denis Rul <que.etc@gmail.com>", | ||
"version": "1.4.1", | ||
"version": "1.4.2", | ||
"description": "A polyfill for the Resize Observer API", | ||
@@ -12,10 +12,7 @@ "main": "dist/ResizeObserver.js", | ||
"test": "npm run test:lint && npm run test:spec", | ||
"test:ci": "npm run test:lint && npm run test:spec:sauce && npm run test:spec:node", | ||
"test:lint": "node ./node_modules/eslint/bin/eslint.js \"**/*.js\" --ignore-pattern \"/dist/\"", | ||
"test:spec": "npm run test:spec:ff && npm run test:spec:node", | ||
"test:spec:ff": "karma start --browsers Firefox", | ||
"test:spec:ch": "karma start --browsers Chrome", | ||
"test:spec:ie": "karma start --browsers IE", | ||
"test:spec:ie10": "karma start --browsers IE10", | ||
"test:spec": "karma start --browsers Chrome && npm run test:spec:node", | ||
"test:spec:sauce": "karma start --sauce=windows && karma start --sauce=linux && karma start --sauce=osx && karma start --sauce=ios && karma start --sauce=android", | ||
"test:spec:node": "babel-node --presets latest,stage-2 tests/node/index.js", | ||
"test:spec:all": "karma start --browsers Firefox,Chrome,IE,IE10 && npm run test:spec:node", | ||
"test:spec:custom": "karma start --no-browsers", | ||
@@ -32,10 +29,6 @@ "test:spec:native": "karma start --no-browsers --native" | ||
}, | ||
"types": "index.d.ts", | ||
"types": "src/index.d.ts", | ||
"files": [ | ||
"src/", | ||
"dist/", | ||
"index.js", | ||
"index.d.ts", | ||
"index.global.js", | ||
"index.global.d.ts" | ||
"dist/" | ||
], | ||
@@ -54,23 +47,22 @@ "keywords": [ | ||
"devDependencies": { | ||
"babel-cli": "^6.23.0", | ||
"babel-core": "^6.23.1", | ||
"babel-eslint": "^7.1.1", | ||
"babel-cli": "^6.24.0", | ||
"babel-core": "^6.24.0", | ||
"babel-eslint": "^7.2.1", | ||
"babel-plugin-transform-regenerator": "^6.22.0", | ||
"babel-preset-latest": "^6.22.0", | ||
"babel-preset-latest": "^6.24.0", | ||
"babel-preset-stage-2": "^6.22.0", | ||
"eslint": "^3.15.0", | ||
"eslint": "^3.19.0", | ||
"jasmine": "^2.5.3", | ||
"jasmine-core": "^2.5.2", | ||
"karma": "^1.4.1", | ||
"karma": "^1.5.0", | ||
"karma-chrome-launcher": "^2.0.0", | ||
"karma-firefox-launcher": "^1.0.0", | ||
"karma-ie-launcher": "^1.0.0", | ||
"karma-jasmine": "^1.1.0", | ||
"karma-jasmine-html-reporter": "^0.2.2", | ||
"karma-rollup-plugin": "^0.2.4", | ||
"karma-sauce-launcher": "^1.1.0", | ||
"karma-sourcemap-loader": "^0.3.7", | ||
"karma-spec-reporter": "0.0.26", | ||
"karma-spec-reporter": "0.0.30", | ||
"promise-polyfill": "^6.0.2", | ||
"regenerator-runtime": "^0.10.1", | ||
"rollup": "^0.41.4", | ||
"regenerator-runtime": "^0.10.3", | ||
"rollup": "^0.41.6", | ||
"rollup-plugin-babel": "^2.7.1", | ||
@@ -77,0 +69,0 @@ "rollup-plugin-buble": "^0.15.0" |
@@ -9,5 +9,5 @@ ResizeObserver Polyfill | ||
Implementation is based on the MutationObserver (no polling unless DOM changes) with a fall back to a continuous dirty checking cycle if the first one is not supported. Doesn't modify observed elements. Handles CSS transitions/animations, `<textarea>` resizes and can possibly observe changes caused by dynamic CSS pseudo-classes, e.g. by `:hover`. | ||
Implementation is based on the MutationObserver and uses Mutation Events as a fall back if the first one is not supported, so there will be no polling unless DOM changes. Doesn't modify observed elements. Handles CSS transitions/animations, `<textarea>` resizes and can possibly observe changes caused by dynamic CSS pseudo-classes, e.g. by `:hover`. | ||
Compliant with the [spec](http://rawgit.com/WICG/ResizeObserver/master/index.html) and the native implementation. The size is _2.6kb_ when minified and gzipped. | ||
Follows the [spec](http://rawgit.com/WICG/ResizeObserver/master/index.html) and the native implementation. The size is _2.44 KiB_ when minified and gzipped. | ||
@@ -30,18 +30,13 @@ [Live demo](http://que-etc.github.io/resize-observer-polyfill) (has style problems in IE10 and lower). | ||
Or just grab one of the pre-built versions from [`dist`](https://github.com/que-etc/resize-observer-polyfill/tree/master/dist). | ||
Or just [grab](https://github.com/que-etc/resize-observer-polyfill/tree/master/dist/ResizeObserver.js) the pre-built version. | ||
## Browser Support | ||
Polyfill has been tested and known to work in the following browsers: | ||
Polyfill has been tested in the following browsers: | ||
* Chrome 40+ , _native since v54_ | ||
* Firefox 37+ | ||
* Safari 9+, _including mobile_ | ||
* Opera 30+ | ||
* Edge 13+ | ||
* Internet Explorer 9+ | ||
[![Build Status](https://saucelabs.com/browser-matrix/que-etc.svg)](https://saucelabs.com/beta/builds/303f5344a7214ba5b62bc7079a15d376) | ||
**NOTE:** Internet Explorer 8 and its earlier versions are not supported. | ||
## Usage Examples | ||
## Usage Example | ||
@@ -65,22 +60,28 @@ It's recommended to use this library in the form of a [ponyfill](https://github.com/sindresorhus/ponyfill), which doesn't inflict modifications of the global object. | ||
``` | ||
Though you always can extend the global object if you need it. | ||
```javascript | ||
import ResizeObserver from 'resize-observer-polyfill'; | ||
Package's main file is a ES5 [UMD](https://github.com/umdjs/umd) bundle that will be swapped with the ES6 modules version for those bundlers that are aware of the [jnext:main](https://github.com/rollup/rollup/wiki/jsnext:main) field, e.g. for [Rollup](https://github.com/rollup/rollup) or webpack. | ||
window.ResizeObserver = ResizeObserver; | ||
``` | ||
**Note**: global version of the polyfill (`dist/ResizeObserver.global`) is deprecated and will be removed in the next major release. | ||
Package's main file is a ES5 [UMD](https://github.com/umdjs/umd) module and it will be dynamically substituted by the ES6 version for those bundlers that are aware of the [jnext:main](https://github.com/rollup/rollup/wiki/jsnext:main) field, e.g. for [Rollup](https://github.com/rollup/rollup). | ||
## Observation Strategy | ||
**Note**: global versions (`index.global` and `dist/ResizeObserver.global`) are deprecated and will be removed in the next major release. | ||
As mentioned above, this implementation primarily (but not solely) relies on Mutation Observer with a fallback to Mutation Events for IE 9, IE 10 and IE 11. It's important to notice that even though MO is available in Internet Explorer 11, it won't be used due to a very unreliable behavior mentioned in the issue #6 (run [this example](https://jsfiddle.net/x2r3jpuz/2/) in IE11). | ||
Speaking of Mutation Events as a fallback approach: they might not be as ugly as they are being rendered, particularly when their calls are batched, throttled and there is no need to analyze changes. Given that, they won't interrupt browser's reflow/repaint cycles (same for MutationObserver) and may even outperform Internet Explorer's implementation of MO causing little to no performance degradation. In contemporary browsers (Chrome, Firefox, etc.) Mutation Observer slows down [the suite](https://jsfiddle.net/que_etc/gaqLe8rn/) that includes 200 iterations of adding/removing elements, changing attributes and modifying text data by less than 1%. Internet Explorer gives different results with MO slowing down the same suite by 2-3% while Mutation Events show the difference of ~0.6%. | ||
As for the reasons why other approaches, namely the iframe/object and `scroll` strategies, were ruled out: | ||
* They require the observed element to be non-statically positioned. | ||
* You can't apply them directly to quite a number of elements: `<img>`, `<input>`, `<textarea>`, `<canvas>`, `<tr>`, `<tbody>`, `<thead>`, `<table>`, etc. For most of them you would need to keep an extra `<div>` wrapper and almost all instances of the SVGGraphicsElement will be out of scope. | ||
* The ResizeObserver spec requires to deliver notifications when a non-empty visible element becomes hidden, i.e. when either this element directly or one of its parent nodes receive the `display: none` state. Same goes for when it's being removed from or added to the DOM. It's not possible to handle these cases merely by using former approaches, so you'd still need to either subscribe for DOM mutations or to continuously check the element's state. | ||
And though every approach has its own limitations, I reckon that it'd be too much of a trade-off to have those constraints when building a polyfill. | ||
## Limitations | ||
* CSS changes caused by dynamic pseudo-classes, e.g. `:hover` and `:focus`, are not tracked. As a workaround you can add a short transition which would trigger the `transitionend` event when an element receives one of the former classes ([example](https://jsfiddle.net/que_etc/7fudzqng/)). | ||
* Notifications are delivered ~20ms after actual changes happen. | ||
* Changes caused by dynamic pseudo-classes, e.g. `:hover` and `:focus`, are not tracked. As a workaround you could add a short transition which would trigger the `transitionend` event when an element receives one of the former classes ([example](https://jsfiddle.net/que_etc/7fudzqng/)). | ||
* Delayed transitions will receive only one notification with the latest dimensions of an element. | ||
## Building and Testing | ||
## Building and testing | ||
To build polyfill. Creates UMD bundle in the `dist` folder: | ||
@@ -87,0 +88,0 @@ |
@@ -5,12 +5,10 @@ import {Map} from './shims/es6-collections'; | ||
// Controller that will be assigned to all instances of the ResizeObserver. | ||
const controller = new ResizeObserverController(); | ||
// Registry of internal observers. If WeakMap is not available use current shim | ||
// of the Map collection as the former one can't be polyfilled anyway. | ||
const observers = typeof WeakMap === 'function' ? new WeakMap() : new Map(); | ||
// for the Map collection as it has all required methods and because WeakMap | ||
// can't be fully polyfilled anyway. | ||
const observers = typeof WeakMap != 'undefined' ? new WeakMap() : new Map(); | ||
/** | ||
* ResizeObserver API. Encapsulates the ResizeObserver SPI implementation | ||
* providing only those methods properties that are define in the spec. | ||
* exposing only those methods and properties that are defined in the spec. | ||
*/ | ||
@@ -21,4 +19,4 @@ class ResizeObserver { | ||
* | ||
* @param {ResizeObserverCallback} callback - Callback that is invoked when dimensions of | ||
* the observed elements change. | ||
* @param {ResizeObserverCallback} callback - Callback that is invoked when | ||
* dimensions of the observed elements change. | ||
*/ | ||
@@ -30,6 +28,5 @@ constructor(callback) { | ||
// Create a new instance of an internal ResizeObserver. | ||
const controller = ResizeObserverController.getInstance(); | ||
const observer = new ResizeObserverSPI(callback, controller, this); | ||
// Register internal observer. | ||
observers.set(this, observer); | ||
@@ -36,0 +33,0 @@ } |
@@ -7,46 +7,33 @@ import isBrowser from './utils/isBrowser'; | ||
// Delay before the next iteration of the continuous cycle. | ||
const CONTINUOUS_DELAY = 80; | ||
// A list of substrings of CSS properties used to find transition events that | ||
// might affect dimensions of observed elements. | ||
const transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight']; | ||
// Define whether the MutationObserver is supported. | ||
// eslint-disable-next-line no-extra-parens | ||
const mutationsSupported = ( | ||
typeof MutationObserver == 'function' && | ||
// MutationObserver should not be used if running in IE11 as it's | ||
// implementation is unreliable. Example: https://jsfiddle.net/x2r3jpuz/2/ | ||
// Unfortunately, there is no other way to check this issue but to use | ||
// userAgent's information. | ||
typeof navigator == 'object' && | ||
!( | ||
navigator.appName === 'Netscape' && | ||
navigator.userAgent.match(/Trident\/.*rv:11/) | ||
) | ||
); | ||
// Detect whether running in IE 11 (facepalm). | ||
const isIE11 = typeof navigator != 'undefined' && (/Trident\/.*rv:11/).test(navigator.userAgent); | ||
// MutationObserver should not be used if running in Internet Explorer 11 as it's | ||
// implementation is unreliable. Example: https://jsfiddle.net/x2r3jpuz/2/ | ||
// | ||
// It's a real bummer that there is no other way to check for this issue but to | ||
// use the UA information. | ||
const mutationObserverSupported = typeof MutationObserver != 'undefined' && !isIE11; | ||
/** | ||
* Controller class which handles updates of ResizeObserver instances. | ||
* It decides when and for how long it's necessary to run updates by listening | ||
* to the windows "resize" event along with a tracking of DOM mutations | ||
* (nodes removal, changes of attributes, etc.). | ||
* | ||
* Transitions and animations are handled by running a repeatable update cycle | ||
* until the dimensions of observed elements are changing. | ||
* | ||
* Continuous update cycle will be used automatically in case MutationObserver | ||
* is not supported. | ||
* Singleton controller class which handles updates of ResizeObserver instances. | ||
*/ | ||
export default class ResizeObserverController { | ||
/** | ||
* Continuous updates must be enabled if MutationObserver is not supported. | ||
* Indicates whether DOM listeners have been added. | ||
* | ||
* @private {boolean} | ||
*/ | ||
isCycleContinuous_ = !mutationsSupported; | ||
connected_ = false; | ||
/** | ||
* Indicates whether DOM listeners have been added. | ||
* Tells that controller has subscribed for Mutation Events. | ||
* | ||
* @private {boolean} | ||
*/ | ||
listenersEnabled_ = false; | ||
mutationEventsAdded_ = false; | ||
@@ -68,11 +55,16 @@ /** | ||
/** | ||
* Holds reference to the controller's instance. | ||
* | ||
* @private {ResizeObserverController} | ||
*/ | ||
static instance_ = null; | ||
/** | ||
* Creates a new instance of ResizeObserverController. | ||
* | ||
* @private | ||
*/ | ||
constructor() { | ||
// Make sure that the "refresh" method is invoked as a RAF callback and | ||
// that it happens only once during the provided period. | ||
this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY, true); | ||
// Additionally postpone invocation of the continuous updates. | ||
this.continuousUpdateHandler_ = throttle(this.refresh, CONTINUOUS_DELAY); | ||
this.onTransitionEnd_ = this.onTransitionEnd_.bind(this); | ||
this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY); | ||
} | ||
@@ -86,4 +78,4 @@ | ||
*/ | ||
connect(observer) { | ||
if (!this.isConnected(observer)) { | ||
addObserver(observer) { | ||
if (!~this.observers_.indexOf(observer)) { | ||
this.observers_.push(observer); | ||
@@ -93,4 +85,4 @@ } | ||
// Add listeners if they haven't been added yet. | ||
if (!this.listenersEnabled_) { | ||
this.addListeners_(); | ||
if (!this.connected_) { | ||
this.connect_(); | ||
} | ||
@@ -105,3 +97,3 @@ } | ||
*/ | ||
disconnect(observer) { | ||
removeObserver(observer) { | ||
const observers = this.observers_; | ||
@@ -116,4 +108,4 @@ const index = observers.indexOf(observer); | ||
// Remove listeners if controller has no connected observers. | ||
if (!observers.length && this.listenersEnabled_) { | ||
this.removeListeners_(); | ||
if (!observers.length && this.connected_) { | ||
this.disconnect_(); | ||
} | ||
@@ -123,14 +115,4 @@ } | ||
/** | ||
* Tells whether the provided observer is connected to controller. | ||
* | ||
* @param {ResizeObserverSPI} observer - Observer to be checked. | ||
* @returns {boolean} | ||
*/ | ||
isConnected(observer) { | ||
return !!~this.observers_.indexOf(observer); | ||
} | ||
/** | ||
* Invokes the update of observers. It will continue running updates insofar | ||
* it detects changes or if continuous updates are enabled. | ||
* it detects changes. | ||
* | ||
@@ -140,11 +122,8 @@ * @returns {void} | ||
refresh() { | ||
const hasChanges = this.updateObservers_(); | ||
const changesDetected = this.updateObservers_(); | ||
// Continue running updates if changes have been detected as there might | ||
// be future ones caused by CSS transitions. | ||
if (hasChanges) { | ||
if (changesDetected) { | ||
this.refresh(); | ||
} else if (this.isCycleContinuous_ && this.listenersEnabled_) { | ||
// Automatically repeat cycle if it's necessary. | ||
this.continuousUpdateHandler_(); | ||
} | ||
@@ -162,4 +141,4 @@ } | ||
updateObservers_() { | ||
// Collect observers that have active entries. | ||
const active = this.observers_.filter(observer => { | ||
// Collect observers that have active observations. | ||
const activeObservers = this.observers_.filter(observer => { | ||
return observer.gatherActive(), observer.hasActive(); | ||
@@ -169,9 +148,9 @@ }); | ||
// Deliver notifications in a separate cycle in order to avoid any | ||
// collisions between observers. E.g. when multiple instances of | ||
// ResizeObserer are tracking the same element and the callback of one | ||
// 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. | ||
active.forEach(observer => observer.broadcastActive()); | ||
activeObservers.forEach(observer => observer.broadcastActive()); | ||
return active.length > 0; | ||
return activeObservers.length > 0; | ||
} | ||
@@ -185,19 +164,17 @@ | ||
*/ | ||
addListeners_() { | ||
connect_() { | ||
// Do nothing if running in a non-browser environment or if listeners | ||
// have been already added. | ||
if (!isBrowser || this.listenersEnabled_) { | ||
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); | ||
// Subscription to the "Transitionend" event is used as a workaround for | ||
// delayed transitions. This way we can capture at least the final state | ||
// of an element. | ||
document.addEventListener('transitionend', this.refresh); | ||
// Subscribe to DOM mutations if it's possible as they may lead to | ||
// changes in the dimensions of elements. | ||
if (mutationsSupported) { | ||
if (mutationObserverSupported) { | ||
this.mutationsObserver_ = new MutationObserver(this.refresh); | ||
@@ -211,11 +188,9 @@ | ||
}); | ||
} else { | ||
document.addEventListener('DOMSubtreeModified', this.refresh); | ||
this.mutationEventsAdded_ = true; | ||
} | ||
this.listenersEnabled_ = true; | ||
// Don't wait for a possible event that might trigger the update of | ||
// observers and manually initiate the update process. | ||
if (this.isCycleContinuous_) { | ||
this.refresh(); | ||
} | ||
this.connected_ = true; | ||
} | ||
@@ -229,11 +204,11 @@ | ||
*/ | ||
removeListeners_() { | ||
disconnect_() { | ||
// Do nothing if running in a non-browser environment or if listeners | ||
// have been already removed. | ||
if (!isBrowser || !this.listenersEnabled_) { | ||
if (!isBrowser || !this.connected_) { | ||
return; | ||
} | ||
document.removeEventListener('transitionend', this.onTransitionEnd_); | ||
window.removeEventListener('resize', this.refresh); | ||
document.removeEventListener('transitionend', this.refresh); | ||
@@ -244,5 +219,41 @@ if (this.mutationsObserver_) { | ||
if (this.mutationEventsAdded_) { | ||
document.removeEventListener('DOMSubtreeModified', this.refresh); | ||
} | ||
this.mutationsObserver_ = null; | ||
this.listenersEnabled_ = false; | ||
this.mutationEventsAdded_ = false; | ||
this.connected_ = false; | ||
} | ||
/** | ||
* "Transitionend" event handler. | ||
* | ||
* @private | ||
* @param {TransitionEvent} event | ||
* @returns {void} | ||
*/ | ||
onTransitionEnd_({propertyName}) { | ||
// Detect whether transition may affect dimensions of an element. | ||
const isReflowProperty = transitionKeys.some(key => { | ||
return !!~propertyName.indexOf(key); | ||
}); | ||
if (isReflowProperty) { | ||
this.refresh(); | ||
} | ||
} | ||
/** | ||
* Returns instance of the ResizeObserverController. | ||
* | ||
* @returns {ResizeObserverController} | ||
*/ | ||
static getInstance() { | ||
if (!this.instance_) { | ||
this.instance_ = new ResizeObserverController(); | ||
} | ||
return this.instance_; | ||
} | ||
} |
import {Map} from './shims/es6-collections'; | ||
import ResizeObservation from './ResizeObservation'; | ||
import ResizeObserverEntry from './ResizeObserverEntry'; | ||
import global from './shims/global'; | ||
@@ -11,11 +10,8 @@ export default class ResizeObserverSPI { | ||
* | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-activetargets | ||
* | ||
* @private {Array<ResizeObservation>} | ||
*/ | ||
activeTargets_ = []; | ||
activeObservations_ = []; | ||
/** | ||
* Reference to the callback function. | ||
* Spec: https://wicg.github.io/ResizeObserver/#resize-observer-callback | ||
* | ||
@@ -43,7 +39,6 @@ * @private {ResizeObserverCallback} | ||
* Registry of the ResizeObservation instances. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-observationtargets | ||
* | ||
* @private {Map<Element, ResizeObservation>} | ||
*/ | ||
observationTargets_ = new Map(); | ||
observations_ = new Map(); | ||
@@ -72,3 +67,2 @@ /** | ||
* Starts observing provided element. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-observe | ||
* | ||
@@ -84,3 +78,3 @@ * @param {Element} target - Element to be observed. | ||
// Do nothing if current environment doesn't have the Element interface. | ||
if (!('Element' in global) || !(Element instanceof Object)) { | ||
if (typeof Element === 'undefined' || !(Element instanceof Object)) { | ||
return; | ||
@@ -93,16 +87,12 @@ } | ||
const targets = this.observationTargets_; | ||
const observations = this.observations_; | ||
// Do nothing if element is already being observed. | ||
if (targets.has(target)) { | ||
if (observations.has(target)) { | ||
return; | ||
} | ||
// Register new ResizeObservation instance. | ||
targets.set(target, new ResizeObservation(target)); | ||
observations.set(target, new ResizeObservation(target)); | ||
// Add observer to controller if it hasn't been connected yet. | ||
if (!this.controller_.isConnected(this)) { | ||
this.controller_.connect(this); | ||
} | ||
this.controller_.addObserver(this); | ||
@@ -115,3 +105,2 @@ // Force the update of observations. | ||
* Stops observing provided element. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-unobserve | ||
* | ||
@@ -127,3 +116,3 @@ * @param {Element} target - Element to stop observing. | ||
// Do nothing if current environment doesn't have the Element interface. | ||
if (!('Element' in global) || !(Element instanceof Object)) { | ||
if (typeof Element === 'undefined' || !(Element instanceof Object)) { | ||
return; | ||
@@ -136,16 +125,13 @@ } | ||
const targets = this.observationTargets_; | ||
const observations = this.observations_; | ||
// Do nothing if element is not being observed. | ||
if (!targets.has(target)) { | ||
if (!observations.has(target)) { | ||
return; | ||
} | ||
// Remove element and associated with it ResizeObsrvation instance from | ||
// registry. | ||
targets.delete(target); | ||
observations.delete(target); | ||
// Set back the initial state if there is nothing to observe. | ||
if (!targets.size) { | ||
this.controller_.disconnect(this); | ||
if (!observations.size) { | ||
this.controller_.removeObserver(this); | ||
} | ||
@@ -155,4 +141,3 @@ } | ||
/** | ||
* Stops observing all elements and clears the observations list. | ||
* Spec: https://wicg.github.io/ResizeObserver/#dom-resizeobserver-disconnect | ||
* Stops observing all elements. | ||
* | ||
@@ -163,10 +148,9 @@ * @returns {void} | ||
this.clearActive(); | ||
this.observationTargets_.clear(); | ||
this.controller_.disconnect(this); | ||
this.observations_.clear(); | ||
this.controller_.removeObserver(this); | ||
} | ||
/** | ||
* Clears an array of previously collected active observations and collects | ||
* observation instances which associated element has changed it's content | ||
* rectangle. | ||
* Collects observation instances the associated element of which has changed | ||
* it's content rectangle. | ||
* | ||
@@ -178,7 +162,5 @@ * @returns {void} | ||
const activeTargets = this.activeTargets_; | ||
this.observationTargets_.forEach(observation => { | ||
this.observations_.forEach(observation => { | ||
if (observation.isActive()) { | ||
activeTargets.push(observation); | ||
this.activeObservations_.push(observation); | ||
} | ||
@@ -203,3 +185,3 @@ }); | ||
// Create ResizeObserverEntry instance for every active observation. | ||
const entries = this.activeTargets_.map(observation => { | ||
const entries = this.activeObservations_.map(observation => { | ||
return new ResizeObserverEntry( | ||
@@ -216,3 +198,3 @@ observation.target, | ||
/** | ||
* Clears the collection of pending/active observations. | ||
* Clears the collection of active observations. | ||
* | ||
@@ -222,7 +204,7 @@ * @returns {void} | ||
clearActive() { | ||
this.activeTargets_.splice(0); | ||
this.activeObservations_.splice(0); | ||
} | ||
/** | ||
* Tells whether observer has pending observations. | ||
* Tells whether observer has active observations. | ||
* | ||
@@ -232,4 +214,4 @@ * @returns {boolean} | ||
hasActive() { | ||
return this.activeTargets_.length > 0; | ||
return this.activeObservations_.length > 0; | ||
} | ||
} |
@@ -7,10 +7,15 @@ /** | ||
*/ | ||
import global from './global'; | ||
/* eslint-disable require-jsdoc */ | ||
export const Map = (() => { | ||
if (typeof global.Map === 'function') { | ||
return global.Map; | ||
/* eslint-disable require-jsdoc, valid-jsdoc */ | ||
const MapShim = (() => { | ||
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) { | ||
@@ -37,2 +42,5 @@ let result = -1; | ||
/** | ||
* @returns {boolean} | ||
*/ | ||
get size() { | ||
@@ -42,2 +50,6 @@ return this.__entries__.length; | ||
/** | ||
* @param {*} key | ||
* @returns {*} | ||
*/ | ||
get(key) { | ||
@@ -50,2 +62,7 @@ const index = getIndex(this.__entries__, key); | ||
/** | ||
* @param {*} key | ||
* @param {*} value | ||
* @returns {void} | ||
*/ | ||
set(key, value) { | ||
@@ -61,2 +78,6 @@ const index = getIndex(this.__entries__, key); | ||
/** | ||
* @param {*} key | ||
* @returns {void} | ||
*/ | ||
delete(key) { | ||
@@ -71,2 +92,6 @@ const entries = this.__entries__; | ||
/** | ||
* @param {*} key | ||
* @returns {void} | ||
*/ | ||
has(key) { | ||
@@ -76,2 +101,5 @@ return !!~getIndex(this.__entries__, key); | ||
/** | ||
* @returns {void} | ||
*/ | ||
clear() { | ||
@@ -81,2 +109,7 @@ this.__entries__.splice(0); | ||
/** | ||
* @param {Function} callback | ||
* @param {*} [ctx=null] | ||
* @returns {void} | ||
*/ | ||
forEach(callback, ctx = null) { | ||
@@ -89,1 +122,3 @@ for (const entry of this.__entries__) { | ||
})(); | ||
export {MapShim as Map}; |
@@ -12,3 +12,3 @@ /** | ||
value: props[key], | ||
enumerbale: false, | ||
enumerable: false, | ||
writable: false, | ||
@@ -15,0 +15,0 @@ configurable: true |
@@ -25,4 +25,4 @@ import defineConfigurable from './defineConfigurable'; | ||
function getBordersSize(styles, ...positions) { | ||
return positions.reduce((size, pos) => { | ||
const value = styles['border-' + pos + '-width']; | ||
return positions.reduce((size, position) => { | ||
const value = styles['border-' + position + '-width']; | ||
@@ -40,9 +40,9 @@ return size + toFloat(value); | ||
function getPaddings(styles) { | ||
const boxKeys = ['top', 'right', 'bottom', 'left']; | ||
const positions = ['top', 'right', 'bottom', 'left']; | ||
const paddings = {}; | ||
for (const key of boxKeys) { | ||
const value = styles['padding-' + key]; | ||
for (const position of positions) { | ||
const value = styles['padding-' + position]; | ||
paddings[key] = toFloat(value); | ||
paddings[position] = toFloat(value); | ||
} | ||
@@ -96,3 +96,3 @@ | ||
// only dimensions available to JS that contain non-rounded values. It could | ||
// be possible to utilize getBoundingClientRect if only it's data wasn't | ||
// be possible to utilize the getBoundingClientRect if only it's data wasn't | ||
// affected by CSS transformations let alone paddings, borders and scroll bars. | ||
@@ -102,4 +102,4 @@ let width = toFloat(styles.width), | ||
// Width & height include paddings and bord when 'border-box' box model is | ||
// applied (except for IE). | ||
// Width & height include paddings and borders when the 'border-box' box | ||
// model is applied (except for IE). | ||
if (styles.boxSizing === 'border-box') { | ||
@@ -121,3 +121,3 @@ // Following conditions are required to handle Internet Explorer which | ||
// Following steps can't applied to the document's root element as it's | ||
// Following steps can't be applied to the document's root element as its | ||
// client[Width/Height] properties represent viewport area of the window. | ||
@@ -160,8 +160,8 @@ // Besides, it's as well not necessary as the <html> itself neither has | ||
// interface. | ||
if (typeof SVGGraphicsElement === 'function') { | ||
if (typeof SVGGraphicsElement != 'undefined') { | ||
return target => target instanceof SVGGraphicsElement; | ||
} | ||
// If it's so, than check that element is at least an instance of the | ||
// SVGElement and that it has the "getBBox" method in the prototype chain. | ||
// 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 | ||
@@ -175,3 +175,3 @@ return target => ( | ||
/** | ||
* Checks whether provided element is a document element (root element of a document, i.e. <html>). | ||
* Checks whether provided element is a document element (<html>). | ||
* | ||
@@ -192,3 +192,2 @@ * @param {Element} target - Element to be checked. | ||
export function getContentRect(target) { | ||
// Return empty rectangle if running in a non-browser environment. | ||
if (!isBrowser) { | ||
@@ -214,3 +213,3 @@ return emptyRect; | ||
// If DOMRectReadOnly is available use it as a prototype for the rectangle. | ||
const Constr = typeof DOMRectReadOnly === 'function' ? DOMRectReadOnly : Object; | ||
const Constr = typeof DOMRectReadOnly != 'undefined' ? DOMRectReadOnly : Object; | ||
const rect = Object.create(Constr.prototype); | ||
@@ -220,4 +219,3 @@ | ||
defineConfigurable(rect, { | ||
x, y, | ||
width, height, | ||
x, y, width, height, | ||
top: y, | ||
@@ -224,0 +222,0 @@ right: x + width, |
@@ -1,6 +0,4 @@ | ||
import global from '../shims/global'; | ||
/** | ||
* Detects whether window and document objects are available in current environment. | ||
*/ | ||
export default global.window === global && typeof document != 'undefined'; | ||
export default typeof window != 'undefined' && typeof document != 'undefined' && window.document === document; |
@@ -7,29 +7,10 @@ import requestAnimationFrame from '../shims/requestAnimationFrame'; | ||
/** | ||
* Returns time stamp retrieved either from the "performance.now" or from | ||
* the "Date.now" method. | ||
* | ||
* @returns {DOMHighResTimeStamp|number} | ||
*/ | ||
const timeStamp = (() => { | ||
let host = Date; | ||
if (typeof performance === 'object' && typeof performance.now === 'function') { | ||
host = performance; | ||
} | ||
return () => host.now(); | ||
})(); | ||
/** | ||
* Creates a wrapper function which ensures that provided callback will be | ||
* invoked only once during the specified delay period. It also caches the last | ||
* call and re-invokes it after pending activation is resolved. | ||
* 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. | ||
* @param {boolean} [afterRAF = false] - Whether function needs to be invoked as | ||
* a requestAnimationFrame callback. | ||
* @returns {Function} | ||
*/ | ||
export default function (callback, delay, afterRAF = false) { | ||
export default function (callback, delay) { | ||
let leadingCall = false, | ||
@@ -40,14 +21,14 @@ trailingCall = false, | ||
/** | ||
* Invokes the original callback function and schedules a new invocation if | ||
* the wrapper was called during current request. | ||
* Invokes the original callback function and schedules new invocation if | ||
* the "proxy" was called during current request. | ||
* | ||
* @returns {void} | ||
*/ | ||
function invokeCallback() { | ||
leadingCall = false; | ||
function resolvePending() { | ||
if (leadingCall) { | ||
leadingCall = false; | ||
// Invoke original function. | ||
callback(); | ||
callback(); | ||
} | ||
// Schedule new invocation if there has been a call during delay period. | ||
if (trailingCall) { | ||
@@ -59,5 +40,5 @@ proxy(); | ||
/** | ||
* Callback that will be invoked after the specified delay period. It will | ||
* delegate invocation of the original function to the requestAnimationFrame | ||
* if "afterRAF" parameter is set to "true". | ||
* Callback invoked after the specified delay. It will further postpone | ||
* invocation of the original function delegating it to the | ||
* requestAnimationFrame. | ||
* | ||
@@ -67,7 +48,7 @@ * @returns {void} | ||
function timeoutCallback() { | ||
afterRAF ? requestAnimationFrame(invokeCallback) : invokeCallback(); | ||
requestAnimationFrame(resolvePending); | ||
} | ||
/** | ||
* Schedules invocation of the initial function. | ||
* Schedules invocation of the original function. | ||
* | ||
@@ -77,11 +58,14 @@ * @returns {void} | ||
function proxy() { | ||
const callTime = timeStamp(); | ||
const timeStamp = Date.now(); | ||
// Postpone activation if there is already a pending call. | ||
if (leadingCall) { | ||
// Reject immediately following invocations. | ||
if (callTime - lastCallTime < trailingTimeout) { | ||
// 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; | ||
@@ -92,7 +76,6 @@ } else { | ||
// Schedule new invocation. | ||
setTimeout(timeoutCallback, delay); | ||
} | ||
lastCallTime = callTime; | ||
lastCallTime = timeStamp; | ||
} | ||
@@ -99,0 +82,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
22
114
134582
19
3556