blob: 37f0e0d98a7292b1b72681215067cdc434bb8e54 [file] [log] [blame]
'use strict';
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _intervalFunction = require('./interval-function');
var _intervalFunction2 = _interopRequireDefault(_intervalFunction);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
(function (window, document) {
'use strict';
if (typeof window.ResizeObserver !== 'undefined') {
return;
}
document.resizeObservers = [];
/**
* The content rect is defined in section 2.3 ResizeObserverEntry of the spec
* @param target the element to calculate the content rect for
* @return {{top: (Number|number), left: (Number|number), width: number, height: number}}
*
* Note:
* Avoid using margins on the observed element. The calculation can return incorrect values when margins are involved.
*
* The following CSS will report incorrect width (Chrome OSX):
*
* <div id="outer" style="width: 300px; height:300px; background-color: green;overflow:auto;">
* <div id="observed" style="width: 400px; height:400px; background-color: yellow; margin:30px; border: 20px solid red; padding:10px;">
* </div>
* </div>
*
* The calculated width is 280. The actual (correct) width is 340 since Chrome clips the margin.
*
* Use an outer container if you really need a "margin":
*
* <div id="outer" style="width: 300px; height:300px; background-color: green;overflow:auto; padding:30px;">
* <div id="observed" style="width: 400px; height:400px; background-color: yellow; margin: 0; border: 20px solid red; padding:10px;">
* </div>
* </div>
*
* A more detailed explanation can be fund here:
* http://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively
*/
var getContentRect = function getContentRect(target) {
var cs = window.getComputedStyle(target);
var r = target.getBoundingClientRect();
var top = parseFloat(cs.paddingTop) || 0;
var left = parseFloat(cs.paddingLeft) || 0;
var width = r.width - ((parseFloat(cs.marginLeft) || 0) + (parseFloat(cs.marginRight) || 0) + (parseFloat(cs.borderLeftWidth) || 0) + (parseFloat(cs.borderRightWidth) || 0) + left + (parseFloat(cs.paddingRight) || 0));
var height = r.height - ((parseFloat(cs.marginTop) || 0) + (parseFloat(cs.marginBottom) || 0) + (parseFloat(cs.borderTopWidth) || 0) + (parseFloat(cs.borderBottomWidth) || 0) + top + (parseFloat(cs.paddingBottom) || 0));
return { width: width, height: height, top: top, left: left };
};
var dimensionHasChanged = function dimensionHasChanged(target, lastWidth, lastHeight) {
var _getContentRect = getContentRect(target),
width = _getContentRect.width,
height = _getContentRect.height;
return width !== lastWidth || height !== lastHeight;
};
/**
* ResizeObservation holds observation information for a single Element.
* @param target
* @return {{target: *, broadcastWidth, broadcastHeight, isOrphan: (function()), isActive: (function())}}
* @constructor
*/
var ResizeObservation = function ResizeObservation(target) {
var _getContentRect2 = getContentRect(target),
width = _getContentRect2.width,
height = _getContentRect2.height;
return {
target: target,
broadcastWidth: width,
broadcastHeight: height,
isOrphan: function isOrphan() {
return !this.target || !this.target.parentNode;
},
isActive: function isActive() {
return dimensionHasChanged(this.target, this.broadcastWidth, this.broadcastHeight);
}
};
};
/**
* A snapshot of the observed element
* @param target
* @param rect
* @return {{target: *, contentRect: *}}
* @constructor
*/
var ResizeObserverEntry = function ResizeObserverEntry(target, rect) {
return {
target: target,
contentRect: rect
};
};
/**
* The ResizeObserver is used to observe changes to Element's content rect.
*/
var ResizeObserver = function () {
/**
* Constructor for instantiating new Resize observers.
* @param callback void (sequence<ResizeObserverEntry> entries). The function which will be called on each resize.
* @throws {TypeError}
*/
function ResizeObserver(callback) {
(0, _classCallCheck3.default)(this, ResizeObserver);
if (typeof callback !== 'function') {
throw new TypeError('callback parameter must be a function');
}
this.callback_ = callback;
this.observationTargets_ = [];
this.activeTargets_ = [];
document.resizeObservers.push(this);
}
/**
* A list of ResizeObservations. It represents all Elements being observed.
*
* @return {Array}
*/
(0, _createClass3.default)(ResizeObserver, [{
key: 'observe',
/**
* Adds target to the list of observed elements.
* @param {HTMLElement} target The target to observe
*/
value: function observe(target) {
if (target) {
if (!(target instanceof HTMLElement)) {
throw new TypeError('target parameter must be an HTMLElement');
}
if (!this.observationTargets_.find(function (t) {
return t.target === target;
})) {
this.observationTargets_.push(ResizeObservation(target));
resizeController.start();
}
}
}
/**
* Removes target from the list of observed elements.
* @param target The target to remove
*/
}, {
key: 'unobserve',
value: function unobserve(target) {
var i = this.observationTargets_.findIndex(function (t) {
return t.target === target;
});
if (i > -1) {
this.observationTargets_.splice(i, 1);
}
}
/**
* Stops the ResizeObserver instance from receiving notifications of resize changes.
* Until the observe() method is used again, observer's callback will not be invoked.
*/
}, {
key: 'disconnect',
value: function disconnect() {
this.observationTargets_ = [];
this.activeTargets_ = [];
}
/**
* Removes the ResizeObserver from the list of observers
*/
}, {
key: 'destroy',
value: function destroy() {
var _this = this;
this.disconnect();
var i = document.resizeObservers.findIndex(function (o) {
return o === _this;
});
if (i > -1) {
document.resizeObservers.splice(i, 1);
}
}
}, {
key: 'deleteOrphansAndPopulateActiveTargets_',
value: function deleteOrphansAndPopulateActiveTargets_() {
// Works, but two iterations
//this.observationTargets_ = this.observationTargets_.filter( resizeObervation => !resizeObervation.isOrphan());
//this.activeTargets_ = this.observationTargets_.filter( resizeObervation => resizeObervation.isActive());
// Same result as above, one iteration
/*
this.activeTargets_ = [];
let n = this.observationTargets_.length-1;
while(n >= 0) {
if(this.observationTargets_[n].isOrphan()) {
this.observationTargets_.splice(n, 1);
}
else if(this.observationTargets_[n].isActive()) {
this.activeTargets_.push(this.observationTargets_[n]);
}
n -= 1;
}
*/
// Same result as above - but reduce is cooler :-)
this.activeTargets_ = this.observationTargets_.reduceRight(function (prev, resizeObservation, index, arr) {
if (resizeObservation.isOrphan()) {
arr.splice(index, 1);
} else if (resizeObservation.isActive()) {
prev.push(resizeObservation);
}
return prev;
}, []);
}
}, {
key: 'broadcast_',
value: function broadcast_() {
this.deleteOrphansAndPopulateActiveTargets_();
if (this.activeTargets_.length > 0) {
var entries = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(this.activeTargets_), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var resizeObservation = _step.value;
var rect = getContentRect(resizeObservation.target);
resizeObservation.broadcastWidth = rect.width;
resizeObservation.broadcastHeight = rect.height;
entries.push(ResizeObserverEntry(resizeObservation.target, rect));
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
this.callback_(entries);
this.activeTargets_ = [];
}
}
}, {
key: 'observationTargets',
get: function get() {
return this.observationTargets_;
}
/**
* A list of ResizeObservations. It represents all Elements whose size has
* changed since last observation broadcast that are eligible for broadcast.
*
* @return {Array}
*/
}, {
key: 'activeTargets',
get: function get() {
return this.activeTargets_;
}
}]);
return ResizeObserver;
}();
//let interval = require('./interval-function');
/**
* Broadcasts Element.resize events
* @return {{start: (function()), stop: (function())}}
* @constructor
*/
var ResizeController = function ResizeController() {
var shouldStop = function shouldStop() {
return document.resizeObservers.findIndex(function (resizeObserver) {
return resizeObserver.observationTargets.length > 0;
}) > -1;
};
var execute = function execute() {
//console.log('***** Execute');
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = (0, _getIterator3.default)(document.resizeObservers), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var resizeObserver = _step2.value;
resizeObserver.broadcast_();
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return shouldStop();
};
var interval = (0, _intervalFunction2.default)(200);
return {
start: function start() {
if (!interval.started) {
//console.log('***** Start poll');
interval.start(execute);
}
}
};
};
window.ResizeObserver = ResizeObserver;
var resizeController = ResizeController();
//console.log('***** ResizeObserver ready');
})(window, document);
/**
* An API for observing changes to Element’s size.
*
* @See https://wicg.github.io/ResizeObserver/
* @ee https://github.com/pelotoncycle/resize-observer
*
*/