UNPKG

78.2 kBJavaScriptView Raw
1(function webpackUniversalModuleDefinition(root, factory) {
2 if(typeof exports === 'object' && typeof module === 'object')
3 module.exports = factory(require("react"), require("react-dom"));
4 else if(typeof define === 'function' && define.amd)
5 define(["react", "react-dom"], factory);
6 else if(typeof exports === 'object')
7 exports["ReactContainerQuery"] = factory(require("react"), require("react-dom"));
8 else
9 root["ReactContainerQuery"] = factory(root["React"], root["ReactDOM"]);
10})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_5__, __WEBPACK_EXTERNAL_MODULE_6__) {
11return /******/ (function(modules) { // webpackBootstrap
12/******/ // The module cache
13/******/ var installedModules = {};
14/******/
15/******/ // The require function
16/******/ function __webpack_require__(moduleId) {
17/******/
18/******/ // Check if module is in cache
19/******/ if(installedModules[moduleId]) {
20/******/ return installedModules[moduleId].exports;
21/******/ }
22/******/ // Create a new module (and put it into the cache)
23/******/ var module = installedModules[moduleId] = {
24/******/ i: moduleId,
25/******/ l: false,
26/******/ exports: {}
27/******/ };
28/******/
29/******/ // Execute the module function
30/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31/******/
32/******/ // Flag the module as loaded
33/******/ module.l = true;
34/******/
35/******/ // Return the exports of the module
36/******/ return module.exports;
37/******/ }
38/******/
39/******/
40/******/ // expose the modules object (__webpack_modules__)
41/******/ __webpack_require__.m = modules;
42/******/
43/******/ // expose the module cache
44/******/ __webpack_require__.c = installedModules;
45/******/
46/******/ // define getter function for harmony exports
47/******/ __webpack_require__.d = function(exports, name, getter) {
48/******/ if(!__webpack_require__.o(exports, name)) {
49/******/ Object.defineProperty(exports, name, {
50/******/ configurable: false,
51/******/ enumerable: true,
52/******/ get: getter
53/******/ });
54/******/ }
55/******/ };
56/******/
57/******/ // getDefaultExport function for compatibility with non-harmony modules
58/******/ __webpack_require__.n = function(module) {
59/******/ var getter = module && module.__esModule ?
60/******/ function getDefault() { return module['default']; } :
61/******/ function getModuleExports() { return module; };
62/******/ __webpack_require__.d(getter, 'a', getter);
63/******/ return getter;
64/******/ };
65/******/
66/******/ // Object.prototype.hasOwnProperty.call
67/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
68/******/
69/******/ // __webpack_public_path__
70/******/ __webpack_require__.p = "";
71/******/
72/******/ // Load entry module and return exports
73/******/ return __webpack_require__(__webpack_require__.s = 4);
74/******/ })
75/************************************************************************/
76/******/ ([
77/* 0 */
78/***/ (function(module, exports, __webpack_require__) {
79
80"use strict";
81
82Object.defineProperty(exports, "__esModule", { value: true });
83function matchQueries(rules) {
84 var entries = [];
85 for (var _i = 0, _a = Object.keys(rules); _i < _a.length; _i++) {
86 var className = _a[_i];
87 var rule = rules[className];
88 entries.push({
89 minWidth: rule.minWidth != null ? rule.minWidth : 0,
90 maxWidth: rule.maxWidth != null ? rule.maxWidth : Infinity,
91 minHeight: rule.minHeight != null ? rule.minHeight : 0,
92 maxHeight: rule.maxHeight != null ? rule.maxHeight : Infinity,
93 className: className,
94 });
95 }
96 return function (_a) {
97 var height = _a.height, width = _a.width;
98 var classNameMap = {};
99 for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
100 var _b = entries_1[_i], className = _b.className, minWidth = _b.minWidth, maxWidth = _b.maxWidth, minHeight = _b.minHeight, maxHeight = _b.maxHeight;
101 if (height != null && width != null) {
102 classNameMap[className] = (minWidth <= width && width <= maxWidth &&
103 minHeight <= height && height <= maxHeight);
104 }
105 else if (height == null && width != null) {
106 classNameMap[className] = minWidth <= width && width <= maxWidth;
107 }
108 else if (height != null && width == null) {
109 classNameMap[className] = minHeight <= height && height <= maxHeight;
110 }
111 else {
112 classNameMap[className] = true;
113 }
114 }
115 return classNameMap;
116 };
117}
118exports.default = matchQueries;
119//# sourceMappingURL=matchQueries.js.map
120
121/***/ }),
122/* 1 */
123/***/ (function(module, exports, __webpack_require__) {
124
125"use strict";
126
127
128var utils = module.exports = {};
129
130/**
131 * Loops through the collection and calls the callback for each element. if the callback returns truthy, the loop is broken and returns the same value.
132 * @public
133 * @param {*} collection The collection to loop through. Needs to have a length property set and have indices set from 0 to length - 1.
134 * @param {function} callback The callback to be called for each element. The element will be given as a parameter to the callback. If this callback returns truthy, the loop is broken and the same value is returned.
135 * @returns {*} The value that a callback has returned (if truthy). Otherwise nothing.
136 */
137utils.forEach = function(collection, callback) {
138 for(var i = 0; i < collection.length; i++) {
139 var result = callback(collection[i]);
140 if(result) {
141 return result;
142 }
143 }
144};
145
146
147/***/ }),
148/* 2 */
149/***/ (function(module, exports, __webpack_require__) {
150
151"use strict";
152
153
154var detector = module.exports = {};
155
156detector.isIE = function(version) {
157 function isAnyIeVersion() {
158 var agent = navigator.userAgent.toLowerCase();
159 return agent.indexOf("msie") !== -1 || agent.indexOf("trident") !== -1 || agent.indexOf(" edge/") !== -1;
160 }
161
162 if(!isAnyIeVersion()) {
163 return false;
164 }
165
166 if(!version) {
167 return true;
168 }
169
170 //Shamelessly stolen from https://gist.github.com/padolsey/527683
171 var ieVersion = (function(){
172 var undef,
173 v = 3,
174 div = document.createElement("div"),
175 all = div.getElementsByTagName("i");
176
177 do {
178 div.innerHTML = "<!--[if gt IE " + (++v) + "]><i></i><![endif]-->";
179 }
180 while (all[0]);
181
182 return v > 4 ? v : undef;
183 }());
184
185 return version === ieVersion;
186};
187
188detector.isLegacyOpera = function() {
189 return !!window.opera;
190};
191
192
193/***/ }),
194/* 3 */
195/***/ (function(module, exports, __webpack_require__) {
196
197"use strict";
198
199
200Object.defineProperty(exports, "__esModule", { value: true });
201var hasOwnProperty = Object.prototype.hasOwnProperty;
202function isShallowEqual(paramA, paramB) {
203 var keysA = Object.keys(paramA);
204 var keysB = Object.keys(paramB);
205 if (keysA.length !== keysB.length) {
206 return false;
207 }
208 for (var i = 0; i < keysA.length; i++) {
209 if (!hasOwnProperty.call(paramB, keysA[i]) || paramA[keysA[i]] !== paramB[keysA[i]]) {
210 return false;
211 }
212 }
213 return true;
214}
215exports.default = isShallowEqual;
216//# sourceMappingURL=isShallowEqual.js.map
217
218/***/ }),
219/* 4 */
220/***/ (function(module, exports, __webpack_require__) {
221
222"use strict";
223
224
225var __extends = undefined && undefined.__extends || function () {
226 var extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) {
227 d.__proto__ = b;
228 } || function (d, b) {
229 for (var p in b) {
230 if (b.hasOwnProperty(p)) d[p] = b[p];
231 }
232 };
233 return function (d, b) {
234 extendStatics(d, b);
235 function __() {
236 this.constructor = d;
237 }
238 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
239 };
240}();
241var __assign = undefined && undefined.__assign || Object.assign || function (t) {
242 for (var s, i = 1, n = arguments.length; i < n; i++) {
243 s = arguments[i];
244 for (var p in s) {
245 if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
246 }
247 }
248 return t;
249};
250Object.defineProperty(exports, "__esModule", { value: true });
251var React = __webpack_require__(5);
252var ReactDOM = __webpack_require__(6);
253var matchQueries_1 = __webpack_require__(0);
254var ContainerQueryCore_1 = __webpack_require__(7);
255var isShallowEqual_1 = __webpack_require__(3);
256/**
257 * <ContainerQuery query={query} initialSize={{width: 123, height: 456}}>
258 * {(params) => {
259 * <div className={classname(params)}></div>
260 * }}
261 * </ContainerQuery>
262 */
263var ContainerQuery = /** @class */function (_super) {
264 __extends(ContainerQuery, _super);
265 function ContainerQuery(props) {
266 var _this = _super.call(this, props) || this;
267 _this.cqCore = null;
268 _this.state = {
269 params: props.initialSize ? matchQueries_1.default(props.query)(props.initialSize) : {}
270 };
271 return _this;
272 }
273 ContainerQuery.prototype.componentDidMount = function () {
274 this._startObserving(this.props.query);
275 };
276 ContainerQuery.prototype.componentWillReceiveProps = function (nextProps) {
277 // componentWillReceiveProps and componentDidMount can potentially run out of order,
278 // so we need to consider the case where cqCore is not initialized yet.
279 if (this.cqCore && !isQueriesEqual(this.props.query, nextProps.query)) {
280 this.cqCore.disconnect();
281 this.cqCore = null;
282 this._startObserving(nextProps.query);
283 }
284 };
285 ContainerQuery.prototype.componentDidUpdate = function () {
286 this.cqCore.observe(ReactDOM.findDOMNode(this));
287 };
288 ContainerQuery.prototype.componentWillUnmount = function () {
289 this.cqCore.disconnect();
290 this.cqCore = null;
291 };
292 ContainerQuery.prototype.render = function () {
293 return this.props.children(this.state.params);
294 };
295 ContainerQuery.prototype._startObserving = function (query) {
296 var _this = this;
297 this.cqCore = new ContainerQueryCore_1.default(query, function (params) {
298 _this.setState({ params: params });
299 });
300 this.cqCore.observe(ReactDOM.findDOMNode(this));
301 };
302 return ContainerQuery;
303}(React.Component);
304exports.ContainerQuery = ContainerQuery;
305;
306function applyContainerQuery(Component, query, initialSize) {
307 return _a = /** @class */function (_super) {
308 __extends(ContainerQuery, _super);
309 function ContainerQuery(props) {
310 var _this = _super.call(this, props) || this;
311 _this.cqCore = null;
312 _this.state = {
313 params: initialSize ? matchQueries_1.default(query)(initialSize) : {}
314 };
315 return _this;
316 }
317 ContainerQuery.prototype.componentDidMount = function () {
318 var _this = this;
319 this.cqCore = new ContainerQueryCore_1.default(query, function (params) {
320 _this.setState({ params: params });
321 });
322 this.cqCore.observe(ReactDOM.findDOMNode(this));
323 };
324 ContainerQuery.prototype.componentDidUpdate = function () {
325 this.cqCore.observe(ReactDOM.findDOMNode(this));
326 };
327 ContainerQuery.prototype.componentWillUnmount = function () {
328 this.cqCore.disconnect();
329 this.cqCore = null;
330 };
331 ContainerQuery.prototype.render = function () {
332 return React.createElement(Component, __assign({}, this.props, { containerQuery: this.state.params }));
333 };
334 return ContainerQuery;
335 }(React.Component), _a.displayName = Component.displayName ? "ContainerQuery(" + Component.displayName + ")" : 'ContainerQuery', _a;
336 var _a;
337}
338exports.applyContainerQuery = applyContainerQuery;
339var hasOwnProperty = Object.prototype.hasOwnProperty;
340function isQueriesEqual(queryA, queryB) {
341 var keysA = Object.keys(queryA);
342 var keysB = Object.keys(queryB);
343 if (keysA.length !== keysB.length) {
344 return false;
345 }
346 for (var i = 0; i < keysA.length; i++) {
347 if (!hasOwnProperty.call(queryB, keysA[i]) || !isShallowEqual_1.default(queryA[keysA[i]], queryB[keysA[i]])) {
348 return false;
349 }
350 }
351 return true;
352}
353//# sourceMappingURL=index.js.map
354
355/***/ }),
356/* 5 */
357/***/ (function(module, exports) {
358
359module.exports = __WEBPACK_EXTERNAL_MODULE_5__;
360
361/***/ }),
362/* 6 */
363/***/ (function(module, exports) {
364
365module.exports = __WEBPACK_EXTERNAL_MODULE_6__;
366
367/***/ }),
368/* 7 */
369/***/ (function(module, exports, __webpack_require__) {
370
371"use strict";
372
373
374Object.defineProperty(exports, "__esModule", { value: true });
375var resize_observer_lite_1 = __webpack_require__(8);
376var matchQueries_1 = __webpack_require__(0);
377var isShallowEqual_1 = __webpack_require__(3);
378var ContainerQueryCore = /** @class */function () {
379 function ContainerQueryCore(query, callback) {
380 var _this = this;
381 this.result = {};
382 this.rol = new resize_observer_lite_1.default(function (size) {
383 var result = matchQueries_1.default(query)(size);
384 if (!isShallowEqual_1.default(_this.result, result)) {
385 callback(result);
386 _this.result = result;
387 }
388 });
389 }
390 ContainerQueryCore.prototype.observe = function (element) {
391 this.rol.observe(element);
392 };
393 ContainerQueryCore.prototype.disconnect = function () {
394 this.rol.disconnect();
395 };
396 return ContainerQueryCore;
397}();
398exports.default = ContainerQueryCore;
399//# sourceMappingURL=ContainerQueryCore.js.map
400
401/***/ }),
402/* 8 */
403/***/ (function(module, exports, __webpack_require__) {
404
405"use strict";
406
407Object.defineProperty(exports, "__esModule", { value: true });
408var elementResizeDetectorMaker = __webpack_require__(9);
409var ResizeObserverLite = /** @class */ (function () {
410 function ResizeObserverLite(handler) {
411 var _this = this;
412 this.handler = handler;
413 this.listenedElement = null;
414 this.hasResizeObserver = typeof window.ResizeObserver !== 'undefined';
415 if (this.hasResizeObserver) {
416 this.rz = new ResizeObserver(function (entries) {
417 _this.handler(getSize(entries[0].target));
418 });
419 }
420 else {
421 this.erd = elementResizeDetectorMaker({ strategy: 'scroll' });
422 }
423 }
424 ResizeObserverLite.prototype.observe = function (element) {
425 var _this = this;
426 if (this.listenedElement !== element) {
427 if (this.listenedElement) {
428 this.disconnect();
429 }
430 if (element) {
431 if (this.hasResizeObserver) {
432 this.rz.observe(element);
433 }
434 else {
435 this.erd.listenTo(element, function (element) {
436 _this.handler(getSize(element));
437 });
438 }
439 }
440 this.listenedElement = element;
441 }
442 };
443 ResizeObserverLite.prototype.disconnect = function () {
444 if (this.listenedElement) {
445 if (this.hasResizeObserver) {
446 this.rz.disconnect();
447 }
448 else {
449 this.erd.uninstall(this.listenedElement);
450 }
451 this.listenedElement = null;
452 }
453 };
454 return ResizeObserverLite;
455}());
456exports.default = ResizeObserverLite;
457function getSize(element) {
458 return {
459 width: getNumber(window.getComputedStyle(element)['width']),
460 height: getNumber(window.getComputedStyle(element)['height'])
461 };
462}
463function getNumber(str) {
464 var m = /^([0-9\.]+)px$/.exec(str);
465 return m ? parseFloat(m[1]) : 0;
466}
467//# sourceMappingURL=index.js.map
468
469/***/ }),
470/* 9 */
471/***/ (function(module, exports, __webpack_require__) {
472
473"use strict";
474
475
476var forEach = __webpack_require__(1).forEach;
477var elementUtilsMaker = __webpack_require__(10);
478var listenerHandlerMaker = __webpack_require__(11);
479var idGeneratorMaker = __webpack_require__(12);
480var idHandlerMaker = __webpack_require__(13);
481var reporterMaker = __webpack_require__(14);
482var browserDetector = __webpack_require__(2);
483var batchProcessorMaker = __webpack_require__(15);
484var stateHandler = __webpack_require__(17);
485
486//Detection strategies.
487var objectStrategyMaker = __webpack_require__(18);
488var scrollStrategyMaker = __webpack_require__(19);
489
490function isCollection(obj) {
491 return Array.isArray(obj) || obj.length !== undefined;
492}
493
494function toArray(collection) {
495 if (!Array.isArray(collection)) {
496 var array = [];
497 forEach(collection, function (obj) {
498 array.push(obj);
499 });
500 return array;
501 } else {
502 return collection;
503 }
504}
505
506function isElement(obj) {
507 return obj && obj.nodeType === 1;
508}
509
510/**
511 * @typedef idHandler
512 * @type {object}
513 * @property {function} get Gets the resize detector id of the element.
514 * @property {function} set Generate and sets the resize detector id of the element.
515 */
516
517/**
518 * @typedef Options
519 * @type {object}
520 * @property {boolean} callOnAdd Determines if listeners should be called when they are getting added.
521 Default is true. If true, the listener is guaranteed to be called when it has been added.
522 If false, the listener will not be guarenteed to be called when it has been added (does not prevent it from being called).
523 * @property {idHandler} idHandler A custom id handler that is responsible for generating, setting and retrieving id's for elements.
524 If not provided, a default id handler will be used.
525 * @property {reporter} reporter A custom reporter that handles reporting logs, warnings and errors.
526 If not provided, a default id handler will be used.
527 If set to false, then nothing will be reported.
528 * @property {boolean} debug If set to true, the the system will report debug messages as default for the listenTo method.
529 */
530
531/**
532 * Creates an element resize detector instance.
533 * @public
534 * @param {Options?} options Optional global options object that will decide how this instance will work.
535 */
536module.exports = function(options) {
537 options = options || {};
538
539 //idHandler is currently not an option to the listenTo function, so it should not be added to globalOptions.
540 var idHandler;
541
542 if (options.idHandler) {
543 // To maintain compatability with idHandler.get(element, readonly), make sure to wrap the given idHandler
544 // so that readonly flag always is true when it's used here. This may be removed next major version bump.
545 idHandler = {
546 get: function (element) { return options.idHandler.get(element, true); },
547 set: options.idHandler.set
548 };
549 } else {
550 var idGenerator = idGeneratorMaker();
551 var defaultIdHandler = idHandlerMaker({
552 idGenerator: idGenerator,
553 stateHandler: stateHandler
554 });
555 idHandler = defaultIdHandler;
556 }
557
558 //reporter is currently not an option to the listenTo function, so it should not be added to globalOptions.
559 var reporter = options.reporter;
560
561 if(!reporter) {
562 //If options.reporter is false, then the reporter should be quiet.
563 var quiet = reporter === false;
564 reporter = reporterMaker(quiet);
565 }
566
567 //batchProcessor is currently not an option to the listenTo function, so it should not be added to globalOptions.
568 var batchProcessor = getOption(options, "batchProcessor", batchProcessorMaker({ reporter: reporter }));
569
570 //Options to be used as default for the listenTo function.
571 var globalOptions = {};
572 globalOptions.callOnAdd = !!getOption(options, "callOnAdd", true);
573 globalOptions.debug = !!getOption(options, "debug", false);
574
575 var eventListenerHandler = listenerHandlerMaker(idHandler);
576 var elementUtils = elementUtilsMaker({
577 stateHandler: stateHandler
578 });
579
580 //The detection strategy to be used.
581 var detectionStrategy;
582 var desiredStrategy = getOption(options, "strategy", "object");
583 var strategyOptions = {
584 reporter: reporter,
585 batchProcessor: batchProcessor,
586 stateHandler: stateHandler,
587 idHandler: idHandler
588 };
589
590 if(desiredStrategy === "scroll") {
591 if (browserDetector.isLegacyOpera()) {
592 reporter.warn("Scroll strategy is not supported on legacy Opera. Changing to object strategy.");
593 desiredStrategy = "object";
594 } else if (browserDetector.isIE(9)) {
595 reporter.warn("Scroll strategy is not supported on IE9. Changing to object strategy.");
596 desiredStrategy = "object";
597 }
598 }
599
600 if(desiredStrategy === "scroll") {
601 detectionStrategy = scrollStrategyMaker(strategyOptions);
602 } else if(desiredStrategy === "object") {
603 detectionStrategy = objectStrategyMaker(strategyOptions);
604 } else {
605 throw new Error("Invalid strategy name: " + desiredStrategy);
606 }
607
608 //Calls can be made to listenTo with elements that are still being installed.
609 //Also, same elements can occur in the elements list in the listenTo function.
610 //With this map, the ready callbacks can be synchronized between the calls
611 //so that the ready callback can always be called when an element is ready - even if
612 //it wasn't installed from the function itself.
613 var onReadyCallbacks = {};
614
615 /**
616 * Makes the given elements resize-detectable and starts listening to resize events on the elements. Calls the event callback for each event for each element.
617 * @public
618 * @param {Options?} options Optional options object. These options will override the global options. Some options may not be overriden, such as idHandler.
619 * @param {element[]|element} elements The given array of elements to detect resize events of. Single element is also valid.
620 * @param {function} listener The callback to be executed for each resize event for each element.
621 */
622 function listenTo(options, elements, listener) {
623 function onResizeCallback(element) {
624 var listeners = eventListenerHandler.get(element);
625 forEach(listeners, function callListenerProxy(listener) {
626 listener(element);
627 });
628 }
629
630 function addListener(callOnAdd, element, listener) {
631 eventListenerHandler.add(element, listener);
632
633 if(callOnAdd) {
634 listener(element);
635 }
636 }
637
638 //Options object may be omitted.
639 if(!listener) {
640 listener = elements;
641 elements = options;
642 options = {};
643 }
644
645 if(!elements) {
646 throw new Error("At least one element required.");
647 }
648
649 if(!listener) {
650 throw new Error("Listener required.");
651 }
652
653 if (isElement(elements)) {
654 // A single element has been passed in.
655 elements = [elements];
656 } else if (isCollection(elements)) {
657 // Convert collection to array for plugins.
658 // TODO: May want to check so that all the elements in the collection are valid elements.
659 elements = toArray(elements);
660 } else {
661 return reporter.error("Invalid arguments. Must be a DOM element or a collection of DOM elements.");
662 }
663
664 var elementsReady = 0;
665
666 var callOnAdd = getOption(options, "callOnAdd", globalOptions.callOnAdd);
667 var onReadyCallback = getOption(options, "onReady", function noop() {});
668 var debug = getOption(options, "debug", globalOptions.debug);
669
670 forEach(elements, function attachListenerToElement(element) {
671 if (!stateHandler.getState(element)) {
672 stateHandler.initState(element);
673 idHandler.set(element);
674 }
675
676 var id = idHandler.get(element);
677
678 debug && reporter.log("Attaching listener to element", id, element);
679
680 if(!elementUtils.isDetectable(element)) {
681 debug && reporter.log(id, "Not detectable.");
682 if(elementUtils.isBusy(element)) {
683 debug && reporter.log(id, "System busy making it detectable");
684
685 //The element is being prepared to be detectable. Do not make it detectable.
686 //Just add the listener, because the element will soon be detectable.
687 addListener(callOnAdd, element, listener);
688 onReadyCallbacks[id] = onReadyCallbacks[id] || [];
689 onReadyCallbacks[id].push(function onReady() {
690 elementsReady++;
691
692 if(elementsReady === elements.length) {
693 onReadyCallback();
694 }
695 });
696 return;
697 }
698
699 debug && reporter.log(id, "Making detectable...");
700 //The element is not prepared to be detectable, so do prepare it and add a listener to it.
701 elementUtils.markBusy(element, true);
702 return detectionStrategy.makeDetectable({ debug: debug }, element, function onElementDetectable(element) {
703 debug && reporter.log(id, "onElementDetectable");
704
705 if (stateHandler.getState(element)) {
706 elementUtils.markAsDetectable(element);
707 elementUtils.markBusy(element, false);
708 detectionStrategy.addListener(element, onResizeCallback);
709 addListener(callOnAdd, element, listener);
710
711 // Since the element size might have changed since the call to "listenTo", we need to check for this change,
712 // so that a resize event may be emitted.
713 // Having the startSize object is optional (since it does not make sense in some cases such as unrendered elements), so check for its existance before.
714 // Also, check the state existance before since the element may have been uninstalled in the installation process.
715 var state = stateHandler.getState(element);
716 if (state && state.startSize) {
717 var width = element.offsetWidth;
718 var height = element.offsetHeight;
719 if (state.startSize.width !== width || state.startSize.height !== height) {
720 onResizeCallback(element);
721 }
722 }
723
724 if(onReadyCallbacks[id]) {
725 forEach(onReadyCallbacks[id], function(callback) {
726 callback();
727 });
728 }
729 } else {
730 // The element has been unisntalled before being detectable.
731 debug && reporter.log(id, "Element uninstalled before being detectable.");
732 }
733
734 delete onReadyCallbacks[id];
735
736 elementsReady++;
737 if(elementsReady === elements.length) {
738 onReadyCallback();
739 }
740 });
741 }
742
743 debug && reporter.log(id, "Already detecable, adding listener.");
744
745 //The element has been prepared to be detectable and is ready to be listened to.
746 addListener(callOnAdd, element, listener);
747 elementsReady++;
748 });
749
750 if(elementsReady === elements.length) {
751 onReadyCallback();
752 }
753 }
754
755 function uninstall(elements) {
756 if(!elements) {
757 return reporter.error("At least one element is required.");
758 }
759
760 if (isElement(elements)) {
761 // A single element has been passed in.
762 elements = [elements];
763 } else if (isCollection(elements)) {
764 // Convert collection to array for plugins.
765 // TODO: May want to check so that all the elements in the collection are valid elements.
766 elements = toArray(elements);
767 } else {
768 return reporter.error("Invalid arguments. Must be a DOM element or a collection of DOM elements.");
769 }
770
771 forEach(elements, function (element) {
772 eventListenerHandler.removeAllListeners(element);
773 detectionStrategy.uninstall(element);
774 stateHandler.cleanState(element);
775 });
776 }
777
778 return {
779 listenTo: listenTo,
780 removeListener: eventListenerHandler.removeListener,
781 removeAllListeners: eventListenerHandler.removeAllListeners,
782 uninstall: uninstall
783 };
784};
785
786function getOption(options, name, defaultValue) {
787 var value = options[name];
788
789 if((value === undefined || value === null) && defaultValue !== undefined) {
790 return defaultValue;
791 }
792
793 return value;
794}
795
796
797/***/ }),
798/* 10 */
799/***/ (function(module, exports, __webpack_require__) {
800
801"use strict";
802
803
804module.exports = function(options) {
805 var getState = options.stateHandler.getState;
806
807 /**
808 * Tells if the element has been made detectable and ready to be listened for resize events.
809 * @public
810 * @param {element} The element to check.
811 * @returns {boolean} True or false depending on if the element is detectable or not.
812 */
813 function isDetectable(element) {
814 var state = getState(element);
815 return state && !!state.isDetectable;
816 }
817
818 /**
819 * Marks the element that it has been made detectable and ready to be listened for resize events.
820 * @public
821 * @param {element} The element to mark.
822 */
823 function markAsDetectable(element) {
824 getState(element).isDetectable = true;
825 }
826
827 /**
828 * Tells if the element is busy or not.
829 * @public
830 * @param {element} The element to check.
831 * @returns {boolean} True or false depending on if the element is busy or not.
832 */
833 function isBusy(element) {
834 return !!getState(element).busy;
835 }
836
837 /**
838 * Marks the object is busy and should not be made detectable.
839 * @public
840 * @param {element} element The element to mark.
841 * @param {boolean} busy If the element is busy or not.
842 */
843 function markBusy(element, busy) {
844 getState(element).busy = !!busy;
845 }
846
847 return {
848 isDetectable: isDetectable,
849 markAsDetectable: markAsDetectable,
850 isBusy: isBusy,
851 markBusy: markBusy
852 };
853};
854
855
856/***/ }),
857/* 11 */
858/***/ (function(module, exports, __webpack_require__) {
859
860"use strict";
861
862
863module.exports = function(idHandler) {
864 var eventListeners = {};
865
866 /**
867 * Gets all listeners for the given element.
868 * @public
869 * @param {element} element The element to get all listeners for.
870 * @returns All listeners for the given element.
871 */
872 function getListeners(element) {
873 var id = idHandler.get(element);
874
875 if (id === undefined) {
876 return [];
877 }
878
879 return eventListeners[id] || [];
880 }
881
882 /**
883 * Stores the given listener for the given element. Will not actually add the listener to the element.
884 * @public
885 * @param {element} element The element that should have the listener added.
886 * @param {function} listener The callback that the element has added.
887 */
888 function addListener(element, listener) {
889 var id = idHandler.get(element);
890
891 if(!eventListeners[id]) {
892 eventListeners[id] = [];
893 }
894
895 eventListeners[id].push(listener);
896 }
897
898 function removeListener(element, listener) {
899 var listeners = getListeners(element);
900 for (var i = 0, len = listeners.length; i < len; ++i) {
901 if (listeners[i] === listener) {
902 listeners.splice(i, 1);
903 break;
904 }
905 }
906 }
907
908 function removeAllListeners(element) {
909 var listeners = getListeners(element);
910 if (!listeners) { return; }
911 listeners.length = 0;
912 }
913
914 return {
915 get: getListeners,
916 add: addListener,
917 removeListener: removeListener,
918 removeAllListeners: removeAllListeners
919 };
920};
921
922
923/***/ }),
924/* 12 */
925/***/ (function(module, exports, __webpack_require__) {
926
927"use strict";
928
929
930module.exports = function() {
931 var idCount = 1;
932
933 /**
934 * Generates a new unique id in the context.
935 * @public
936 * @returns {number} A unique id in the context.
937 */
938 function generate() {
939 return idCount++;
940 }
941
942 return {
943 generate: generate
944 };
945};
946
947
948/***/ }),
949/* 13 */
950/***/ (function(module, exports, __webpack_require__) {
951
952"use strict";
953
954
955module.exports = function(options) {
956 var idGenerator = options.idGenerator;
957 var getState = options.stateHandler.getState;
958
959 /**
960 * Gets the resize detector id of the element.
961 * @public
962 * @param {element} element The target element to get the id of.
963 * @returns {string|number|null} The id of the element. Null if it has no id.
964 */
965 function getId(element) {
966 var state = getState(element);
967
968 if (state && state.id !== undefined) {
969 return state.id;
970 }
971
972 return null;
973 }
974
975 /**
976 * Sets the resize detector id of the element. Requires the element to have a resize detector state initialized.
977 * @public
978 * @param {element} element The target element to set the id of.
979 * @returns {string|number|null} The id of the element.
980 */
981 function setId(element) {
982 var state = getState(element);
983
984 if (!state) {
985 throw new Error("setId required the element to have a resize detection state.");
986 }
987
988 var id = idGenerator.generate();
989
990 state.id = id;
991
992 return id;
993 }
994
995 return {
996 get: getId,
997 set: setId
998 };
999};
1000
1001
1002/***/ }),
1003/* 14 */
1004/***/ (function(module, exports, __webpack_require__) {
1005
1006"use strict";
1007
1008
1009/* global console: false */
1010
1011/**
1012 * Reporter that handles the reporting of logs, warnings and errors.
1013 * @public
1014 * @param {boolean} quiet Tells if the reporter should be quiet or not.
1015 */
1016module.exports = function(quiet) {
1017 function noop() {
1018 //Does nothing.
1019 }
1020
1021 var reporter = {
1022 log: noop,
1023 warn: noop,
1024 error: noop
1025 };
1026
1027 if(!quiet && window.console) {
1028 var attachFunction = function(reporter, name) {
1029 //The proxy is needed to be able to call the method with the console context,
1030 //since we cannot use bind.
1031 reporter[name] = function reporterProxy() {
1032 var f = console[name];
1033 if (f.apply) { //IE9 does not support console.log.apply :)
1034 f.apply(console, arguments);
1035 } else {
1036 for (var i = 0; i < arguments.length; i++) {
1037 f(arguments[i]);
1038 }
1039 }
1040 };
1041 };
1042
1043 attachFunction(reporter, "log");
1044 attachFunction(reporter, "warn");
1045 attachFunction(reporter, "error");
1046 }
1047
1048 return reporter;
1049};
1050
1051/***/ }),
1052/* 15 */
1053/***/ (function(module, exports, __webpack_require__) {
1054
1055"use strict";
1056
1057
1058var utils = __webpack_require__(16);
1059
1060module.exports = function batchProcessorMaker(options) {
1061 options = options || {};
1062 var reporter = options.reporter;
1063 var asyncProcess = utils.getOption(options, "async", true);
1064 var autoProcess = utils.getOption(options, "auto", true);
1065
1066 if(autoProcess && !asyncProcess) {
1067 reporter && reporter.warn("Invalid options combination. auto=true and async=false is invalid. Setting async=true.");
1068 asyncProcess = true;
1069 }
1070
1071 var batch = Batch();
1072 var asyncFrameHandler;
1073 var isProcessing = false;
1074
1075 function addFunction(level, fn) {
1076 if(!isProcessing && autoProcess && asyncProcess && batch.size() === 0) {
1077 // Since this is async, it is guaranteed to be executed after that the fn is added to the batch.
1078 // This needs to be done before, since we're checking the size of the batch to be 0.
1079 processBatchAsync();
1080 }
1081
1082 batch.add(level, fn);
1083 }
1084
1085 function processBatch() {
1086 // Save the current batch, and create a new batch so that incoming functions are not added into the currently processing batch.
1087 // Continue processing until the top-level batch is empty (functions may be added to the new batch while processing, and so on).
1088 isProcessing = true;
1089 while (batch.size()) {
1090 var processingBatch = batch;
1091 batch = Batch();
1092 processingBatch.process();
1093 }
1094 isProcessing = false;
1095 }
1096
1097 function forceProcessBatch(localAsyncProcess) {
1098 if (isProcessing) {
1099 return;
1100 }
1101
1102 if(localAsyncProcess === undefined) {
1103 localAsyncProcess = asyncProcess;
1104 }
1105
1106 if(asyncFrameHandler) {
1107 cancelFrame(asyncFrameHandler);
1108 asyncFrameHandler = null;
1109 }
1110
1111 if(localAsyncProcess) {
1112 processBatchAsync();
1113 } else {
1114 processBatch();
1115 }
1116 }
1117
1118 function processBatchAsync() {
1119 asyncFrameHandler = requestFrame(processBatch);
1120 }
1121
1122 function clearBatch() {
1123 batch = {};
1124 batchSize = 0;
1125 topLevel = 0;
1126 bottomLevel = 0;
1127 }
1128
1129 function cancelFrame(listener) {
1130 // var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.clearTimeout;
1131 var cancel = clearTimeout;
1132 return cancel(listener);
1133 }
1134
1135 function requestFrame(callback) {
1136 // var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || function(fn) { return window.setTimeout(fn, 20); };
1137 var raf = function(fn) { return setTimeout(fn, 0); };
1138 return raf(callback);
1139 }
1140
1141 return {
1142 add: addFunction,
1143 force: forceProcessBatch
1144 };
1145};
1146
1147function Batch() {
1148 var batch = {};
1149 var size = 0;
1150 var topLevel = 0;
1151 var bottomLevel = 0;
1152
1153 function add(level, fn) {
1154 if(!fn) {
1155 fn = level;
1156 level = 0;
1157 }
1158
1159 if(level > topLevel) {
1160 topLevel = level;
1161 } else if(level < bottomLevel) {
1162 bottomLevel = level;
1163 }
1164
1165 if(!batch[level]) {
1166 batch[level] = [];
1167 }
1168
1169 batch[level].push(fn);
1170 size++;
1171 }
1172
1173 function process() {
1174 for(var level = bottomLevel; level <= topLevel; level++) {
1175 var fns = batch[level];
1176
1177 for(var i = 0; i < fns.length; i++) {
1178 var fn = fns[i];
1179 fn();
1180 }
1181 }
1182 }
1183
1184 function getSize() {
1185 return size;
1186 }
1187
1188 return {
1189 add: add,
1190 process: process,
1191 size: getSize
1192 };
1193}
1194
1195
1196/***/ }),
1197/* 16 */
1198/***/ (function(module, exports, __webpack_require__) {
1199
1200"use strict";
1201
1202
1203var utils = module.exports = {};
1204
1205utils.getOption = getOption;
1206
1207function getOption(options, name, defaultValue) {
1208 var value = options[name];
1209
1210 if((value === undefined || value === null) && defaultValue !== undefined) {
1211 return defaultValue;
1212 }
1213
1214 return value;
1215}
1216
1217
1218/***/ }),
1219/* 17 */
1220/***/ (function(module, exports, __webpack_require__) {
1221
1222"use strict";
1223
1224
1225var prop = "_erd";
1226
1227function initState(element) {
1228 element[prop] = {};
1229 return getState(element);
1230}
1231
1232function getState(element) {
1233 return element[prop];
1234}
1235
1236function cleanState(element) {
1237 delete element[prop];
1238}
1239
1240module.exports = {
1241 initState: initState,
1242 getState: getState,
1243 cleanState: cleanState
1244};
1245
1246
1247/***/ }),
1248/* 18 */
1249/***/ (function(module, exports, __webpack_require__) {
1250
1251"use strict";
1252/**
1253 * Resize detection strategy that injects objects to elements in order to detect resize events.
1254 * Heavily inspired by: http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/
1255 */
1256
1257
1258
1259var browserDetector = __webpack_require__(2);
1260
1261module.exports = function(options) {
1262 options = options || {};
1263 var reporter = options.reporter;
1264 var batchProcessor = options.batchProcessor;
1265 var getState = options.stateHandler.getState;
1266
1267 if(!reporter) {
1268 throw new Error("Missing required dependency: reporter.");
1269 }
1270
1271 /**
1272 * Adds a resize event listener to the element.
1273 * @public
1274 * @param {element} element The element that should have the listener added.
1275 * @param {function} listener The listener callback to be called for each resize event of the element. The element will be given as a parameter to the listener callback.
1276 */
1277 function addListener(element, listener) {
1278 if(!getObject(element)) {
1279 throw new Error("Element is not detectable by this strategy.");
1280 }
1281
1282 function listenerProxy() {
1283 listener(element);
1284 }
1285
1286 if(browserDetector.isIE(8)) {
1287 //IE 8 does not support object, but supports the resize event directly on elements.
1288 getState(element).object = {
1289 proxy: listenerProxy
1290 };
1291 element.attachEvent("onresize", listenerProxy);
1292 } else {
1293 var object = getObject(element);
1294 object.contentDocument.defaultView.addEventListener("resize", listenerProxy);
1295 }
1296 }
1297
1298 /**
1299 * Makes an element detectable and ready to be listened for resize events. Will call the callback when the element is ready to be listened for resize changes.
1300 * @private
1301 * @param {object} options Optional options object.
1302 * @param {element} element The element to make detectable
1303 * @param {function} callback The callback to be called when the element is ready to be listened for resize changes. Will be called with the element as first parameter.
1304 */
1305 function makeDetectable(options, element, callback) {
1306 if (!callback) {
1307 callback = element;
1308 element = options;
1309 options = null;
1310 }
1311
1312 options = options || {};
1313 var debug = options.debug;
1314
1315 function injectObject(element, callback) {
1316 var OBJECT_STYLE = "display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding: 0; margin: 0; opacity: 0; z-index: -1000; pointer-events: none;";
1317
1318 //The target element needs to be positioned (everything except static) so the absolute positioned object will be positioned relative to the target element.
1319
1320 // Position altering may be performed directly or on object load, depending on if style resolution is possible directly or not.
1321 var positionCheckPerformed = false;
1322
1323 // The element may not yet be attached to the DOM, and therefore the style object may be empty in some browsers.
1324 // Since the style object is a reference, it will be updated as soon as the element is attached to the DOM.
1325 var style = window.getComputedStyle(element);
1326 var width = element.offsetWidth;
1327 var height = element.offsetHeight;
1328
1329 getState(element).startSize = {
1330 width: width,
1331 height: height
1332 };
1333
1334 function mutateDom() {
1335 function alterPositionStyles() {
1336 if(style.position === "static") {
1337 element.style.position = "relative";
1338
1339 var removeRelativeStyles = function(reporter, element, style, property) {
1340 function getNumericalValue(value) {
1341 return value.replace(/[^-\d\.]/g, "");
1342 }
1343
1344 var value = style[property];
1345
1346 if(value !== "auto" && getNumericalValue(value) !== "0") {
1347 reporter.warn("An element that is positioned static has style." + property + "=" + value + " which is ignored due to the static positioning. The element will need to be positioned relative, so the style." + property + " will be set to 0. Element: ", element);
1348 element.style[property] = 0;
1349 }
1350 };
1351
1352 //Check so that there are no accidental styles that will make the element styled differently now that is is relative.
1353 //If there are any, set them to 0 (this should be okay with the user since the style properties did nothing before [since the element was positioned static] anyway).
1354 removeRelativeStyles(reporter, element, style, "top");
1355 removeRelativeStyles(reporter, element, style, "right");
1356 removeRelativeStyles(reporter, element, style, "bottom");
1357 removeRelativeStyles(reporter, element, style, "left");
1358 }
1359 }
1360
1361 function onObjectLoad() {
1362 // The object has been loaded, which means that the element now is guaranteed to be attached to the DOM.
1363 if (!positionCheckPerformed) {
1364 alterPositionStyles();
1365 }
1366
1367 /*jshint validthis: true */
1368
1369 function getDocument(element, callback) {
1370 //Opera 12 seem to call the object.onload before the actual document has been created.
1371 //So if it is not present, poll it with an timeout until it is present.
1372 //TODO: Could maybe be handled better with object.onreadystatechange or similar.
1373 if(!element.contentDocument) {
1374 setTimeout(function checkForObjectDocument() {
1375 getDocument(element, callback);
1376 }, 100);
1377
1378 return;
1379 }
1380
1381 callback(element.contentDocument);
1382 }
1383
1384 //Mutating the object element here seems to fire another load event.
1385 //Mutating the inner document of the object element is fine though.
1386 var objectElement = this;
1387
1388 //Create the style element to be added to the object.
1389 getDocument(objectElement, function onObjectDocumentReady(objectDocument) {
1390 //Notify that the element is ready to be listened to.
1391 callback(element);
1392 });
1393 }
1394
1395 // The element may be detached from the DOM, and some browsers does not support style resolving of detached elements.
1396 // The alterPositionStyles needs to be delayed until we know the element has been attached to the DOM (which we are sure of when the onObjectLoad has been fired), if style resolution is not possible.
1397 if (style.position !== "") {
1398 alterPositionStyles(style);
1399 positionCheckPerformed = true;
1400 }
1401
1402 //Add an object element as a child to the target element that will be listened to for resize events.
1403 var object = document.createElement("object");
1404 object.style.cssText = OBJECT_STYLE;
1405 object.tabIndex = -1;
1406 object.type = "text/html";
1407 object.onload = onObjectLoad;
1408
1409 //Safari: This must occur before adding the object to the DOM.
1410 //IE: Does not like that this happens before, even if it is also added after.
1411 if(!browserDetector.isIE()) {
1412 object.data = "about:blank";
1413 }
1414
1415 element.appendChild(object);
1416 getState(element).object = object;
1417
1418 //IE: This must occur after adding the object to the DOM.
1419 if(browserDetector.isIE()) {
1420 object.data = "about:blank";
1421 }
1422 }
1423
1424 if(batchProcessor) {
1425 batchProcessor.add(mutateDom);
1426 } else {
1427 mutateDom();
1428 }
1429 }
1430
1431 if(browserDetector.isIE(8)) {
1432 //IE 8 does not support objects properly. Luckily they do support the resize event.
1433 //So do not inject the object and notify that the element is already ready to be listened to.
1434 //The event handler for the resize event is attached in the utils.addListener instead.
1435 callback(element);
1436 } else {
1437 injectObject(element, callback);
1438 }
1439 }
1440
1441 /**
1442 * Returns the child object of the target element.
1443 * @private
1444 * @param {element} element The target element.
1445 * @returns The object element of the target.
1446 */
1447 function getObject(element) {
1448 return getState(element).object;
1449 }
1450
1451 function uninstall(element) {
1452 if(browserDetector.isIE(8)) {
1453 element.detachEvent("onresize", getState(element).object.proxy);
1454 } else {
1455 element.removeChild(getObject(element));
1456 }
1457 delete getState(element).object;
1458 }
1459
1460 return {
1461 makeDetectable: makeDetectable,
1462 addListener: addListener,
1463 uninstall: uninstall
1464 };
1465};
1466
1467
1468/***/ }),
1469/* 19 */
1470/***/ (function(module, exports, __webpack_require__) {
1471
1472"use strict";
1473/**
1474 * Resize detection strategy that injects divs to elements in order to detect resize events on scroll events.
1475 * Heavily inspired by: https://github.com/marcj/css-element-queries/blob/master/src/ResizeSensor.js
1476 */
1477
1478
1479
1480var forEach = __webpack_require__(1).forEach;
1481
1482module.exports = function(options) {
1483 options = options || {};
1484 var reporter = options.reporter;
1485 var batchProcessor = options.batchProcessor;
1486 var getState = options.stateHandler.getState;
1487 var hasState = options.stateHandler.hasState;
1488 var idHandler = options.idHandler;
1489
1490 if (!batchProcessor) {
1491 throw new Error("Missing required dependency: batchProcessor");
1492 }
1493
1494 if (!reporter) {
1495 throw new Error("Missing required dependency: reporter.");
1496 }
1497
1498 //TODO: Could this perhaps be done at installation time?
1499 var scrollbarSizes = getScrollbarSizes();
1500
1501 // Inject the scrollbar styling that prevents them from appearing sometimes in Chrome.
1502 // The injected container needs to have a class, so that it may be styled with CSS (pseudo elements).
1503 var styleId = "erd_scroll_detection_scrollbar_style";
1504 var detectionContainerClass = "erd_scroll_detection_container";
1505 injectScrollStyle(styleId, detectionContainerClass);
1506
1507 function getScrollbarSizes() {
1508 var width = 500;
1509 var height = 500;
1510
1511 var child = document.createElement("div");
1512 child.style.cssText = "position: absolute; width: " + width*2 + "px; height: " + height*2 + "px; visibility: hidden; margin: 0; padding: 0;";
1513
1514 var container = document.createElement("div");
1515 container.style.cssText = "position: absolute; width: " + width + "px; height: " + height + "px; overflow: scroll; visibility: none; top: " + -width*3 + "px; left: " + -height*3 + "px; visibility: hidden; margin: 0; padding: 0;";
1516
1517 container.appendChild(child);
1518
1519 document.body.insertBefore(container, document.body.firstChild);
1520
1521 var widthSize = width - container.clientWidth;
1522 var heightSize = height - container.clientHeight;
1523
1524 document.body.removeChild(container);
1525
1526 return {
1527 width: widthSize,
1528 height: heightSize
1529 };
1530 }
1531
1532 function injectScrollStyle(styleId, containerClass) {
1533 function injectStyle(style, method) {
1534 method = method || function (element) {
1535 document.head.appendChild(element);
1536 };
1537
1538 var styleElement = document.createElement("style");
1539 styleElement.innerHTML = style;
1540 styleElement.id = styleId;
1541 method(styleElement);
1542 return styleElement;
1543 }
1544
1545 if (!document.getElementById(styleId)) {
1546 var containerAnimationClass = containerClass + "_animation";
1547 var containerAnimationActiveClass = containerClass + "_animation_active";
1548 var style = "/* Created by the element-resize-detector library. */\n";
1549 style += "." + containerClass + " > div::-webkit-scrollbar { display: none; }\n\n";
1550 style += "." + containerAnimationActiveClass + " { -webkit-animation-duration: 0.1s; animation-duration: 0.1s; -webkit-animation-name: " + containerAnimationClass + "; animation-name: " + containerAnimationClass + "; }\n";
1551 style += "@-webkit-keyframes " + containerAnimationClass + " { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }\n";
1552 style += "@keyframes " + containerAnimationClass + " { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }";
1553 injectStyle(style);
1554 }
1555 }
1556
1557 function addAnimationClass(element) {
1558 element.className += " " + detectionContainerClass + "_animation_active";
1559 }
1560
1561 function addEvent(el, name, cb) {
1562 if (el.addEventListener) {
1563 el.addEventListener(name, cb);
1564 } else if(el.attachEvent) {
1565 el.attachEvent("on" + name, cb);
1566 } else {
1567 return reporter.error("[scroll] Don't know how to add event listeners.");
1568 }
1569 }
1570
1571 function removeEvent(el, name, cb) {
1572 if (el.removeEventListener) {
1573 el.removeEventListener(name, cb);
1574 } else if(el.detachEvent) {
1575 el.detachEvent("on" + name, cb);
1576 } else {
1577 return reporter.error("[scroll] Don't know how to remove event listeners.");
1578 }
1579 }
1580
1581 function getExpandElement(element) {
1582 return getState(element).container.childNodes[0].childNodes[0].childNodes[0];
1583 }
1584
1585 function getShrinkElement(element) {
1586 return getState(element).container.childNodes[0].childNodes[0].childNodes[1];
1587 }
1588
1589 /**
1590 * Adds a resize event listener to the element.
1591 * @public
1592 * @param {element} element The element that should have the listener added.
1593 * @param {function} listener The listener callback to be called for each resize event of the element. The element will be given as a parameter to the listener callback.
1594 */
1595 function addListener(element, listener) {
1596 var listeners = getState(element).listeners;
1597
1598 if (!listeners.push) {
1599 throw new Error("Cannot add listener to an element that is not detectable.");
1600 }
1601
1602 getState(element).listeners.push(listener);
1603 }
1604
1605 /**
1606 * Makes an element detectable and ready to be listened for resize events. Will call the callback when the element is ready to be listened for resize changes.
1607 * @private
1608 * @param {object} options Optional options object.
1609 * @param {element} element The element to make detectable
1610 * @param {function} callback The callback to be called when the element is ready to be listened for resize changes. Will be called with the element as first parameter.
1611 */
1612 function makeDetectable(options, element, callback) {
1613 if (!callback) {
1614 callback = element;
1615 element = options;
1616 options = null;
1617 }
1618
1619 options = options || {};
1620
1621 function debug() {
1622 if (options.debug) {
1623 var args = Array.prototype.slice.call(arguments);
1624 args.unshift(idHandler.get(element), "Scroll: ");
1625 if (reporter.log.apply) {
1626 reporter.log.apply(null, args);
1627 } else {
1628 for (var i = 0; i < args.length; i++) {
1629 reporter.log(args[i]);
1630 }
1631 }
1632 }
1633 }
1634
1635 function isDetached(element) {
1636 function isInDocument(element) {
1637 return element === element.ownerDocument.body || element.ownerDocument.body.contains(element);
1638 }
1639
1640 if (!isInDocument(element)) {
1641 return true;
1642 }
1643
1644 // FireFox returns null style in hidden iframes. See https://github.com/wnr/element-resize-detector/issues/68 and https://bugzilla.mozilla.org/show_bug.cgi?id=795520
1645 if (getComputedStyle(element) === null) {
1646 return true;
1647 }
1648
1649 return false;
1650 }
1651
1652 function isUnrendered(element) {
1653 // Check the absolute positioned container since the top level container is display: inline.
1654 var container = getState(element).container.childNodes[0];
1655 var style = getComputedStyle(container);
1656 return !style.width || style.width.indexOf("px") === -1; //Can only compute pixel value when rendered.
1657 }
1658
1659 function getStyle() {
1660 // Some browsers only force layouts when actually reading the style properties of the style object, so make sure that they are all read here,
1661 // so that the user of the function can be sure that it will perform the layout here, instead of later (important for batching).
1662 var elementStyle = getComputedStyle(element);
1663 var style = {};
1664 style.position = elementStyle.position;
1665 style.width = element.offsetWidth;
1666 style.height = element.offsetHeight;
1667 style.top = elementStyle.top;
1668 style.right = elementStyle.right;
1669 style.bottom = elementStyle.bottom;
1670 style.left = elementStyle.left;
1671 style.widthCSS = elementStyle.width;
1672 style.heightCSS = elementStyle.height;
1673 return style;
1674 }
1675
1676 function storeStartSize() {
1677 var style = getStyle();
1678 getState(element).startSize = {
1679 width: style.width,
1680 height: style.height
1681 };
1682 debug("Element start size", getState(element).startSize);
1683 }
1684
1685 function initListeners() {
1686 getState(element).listeners = [];
1687 }
1688
1689 function storeStyle() {
1690 debug("storeStyle invoked.");
1691 if (!getState(element)) {
1692 debug("Aborting because element has been uninstalled");
1693 return;
1694 }
1695
1696 var style = getStyle();
1697 getState(element).style = style;
1698 }
1699
1700 function storeCurrentSize(element, width, height) {
1701 getState(element).lastWidth = width;
1702 getState(element).lastHeight = height;
1703 }
1704
1705 function getExpandChildElement(element) {
1706 return getExpandElement(element).childNodes[0];
1707 }
1708
1709 function getWidthOffset() {
1710 return 2 * scrollbarSizes.width + 1;
1711 }
1712
1713 function getHeightOffset() {
1714 return 2 * scrollbarSizes.height + 1;
1715 }
1716
1717 function getExpandWidth(width) {
1718 return width + 10 + getWidthOffset();
1719 }
1720
1721 function getExpandHeight(height) {
1722 return height + 10 + getHeightOffset();
1723 }
1724
1725 function getShrinkWidth(width) {
1726 return width * 2 + getWidthOffset();
1727 }
1728
1729 function getShrinkHeight(height) {
1730 return height * 2 + getHeightOffset();
1731 }
1732
1733 function positionScrollbars(element, width, height) {
1734 var expand = getExpandElement(element);
1735 var shrink = getShrinkElement(element);
1736 var expandWidth = getExpandWidth(width);
1737 var expandHeight = getExpandHeight(height);
1738 var shrinkWidth = getShrinkWidth(width);
1739 var shrinkHeight = getShrinkHeight(height);
1740 expand.scrollLeft = expandWidth;
1741 expand.scrollTop = expandHeight;
1742 shrink.scrollLeft = shrinkWidth;
1743 shrink.scrollTop = shrinkHeight;
1744 }
1745
1746 function injectContainerElement() {
1747 var container = getState(element).container;
1748
1749 if (!container) {
1750 container = document.createElement("div");
1751 container.className = detectionContainerClass;
1752 container.style.cssText = "visibility: hidden; display: inline; width: 0px; height: 0px; z-index: -1; overflow: hidden; margin: 0; padding: 0;";
1753 getState(element).container = container;
1754 addAnimationClass(container);
1755 element.appendChild(container);
1756
1757 var onAnimationStart = function () {
1758 getState(element).onRendered && getState(element).onRendered();
1759 };
1760
1761 addEvent(container, "animationstart", onAnimationStart);
1762
1763 // Store the event handler here so that they may be removed when uninstall is called.
1764 // See uninstall function for an explanation why it is needed.
1765 getState(element).onAnimationStart = onAnimationStart;
1766 }
1767
1768 return container;
1769 }
1770
1771 function injectScrollElements() {
1772 function alterPositionStyles() {
1773 var style = getState(element).style;
1774
1775 if(style.position === "static") {
1776 element.style.position = "relative";
1777
1778 var removeRelativeStyles = function(reporter, element, style, property) {
1779 function getNumericalValue(value) {
1780 return value.replace(/[^-\d\.]/g, "");
1781 }
1782
1783 var value = style[property];
1784
1785 if(value !== "auto" && getNumericalValue(value) !== "0") {
1786 reporter.warn("An element that is positioned static has style." + property + "=" + value + " which is ignored due to the static positioning. The element will need to be positioned relative, so the style." + property + " will be set to 0. Element: ", element);
1787 element.style[property] = 0;
1788 }
1789 };
1790
1791 //Check so that there are no accidental styles that will make the element styled differently now that is is relative.
1792 //If there are any, set them to 0 (this should be okay with the user since the style properties did nothing before [since the element was positioned static] anyway).
1793 removeRelativeStyles(reporter, element, style, "top");
1794 removeRelativeStyles(reporter, element, style, "right");
1795 removeRelativeStyles(reporter, element, style, "bottom");
1796 removeRelativeStyles(reporter, element, style, "left");
1797 }
1798 }
1799
1800 function getLeftTopBottomRightCssText(left, top, bottom, right) {
1801 left = (!left ? "0" : (left + "px"));
1802 top = (!top ? "0" : (top + "px"));
1803 bottom = (!bottom ? "0" : (bottom + "px"));
1804 right = (!right ? "0" : (right + "px"));
1805
1806 return "left: " + left + "; top: " + top + "; right: " + right + "; bottom: " + bottom + ";";
1807 }
1808
1809 debug("Injecting elements");
1810
1811 if (!getState(element)) {
1812 debug("Aborting because element has been uninstalled");
1813 return;
1814 }
1815
1816 alterPositionStyles();
1817
1818 var rootContainer = getState(element).container;
1819
1820 if (!rootContainer) {
1821 rootContainer = injectContainerElement();
1822 }
1823
1824 // Due to this WebKit bug https://bugs.webkit.org/show_bug.cgi?id=80808 (currently fixed in Blink, but still present in WebKit browsers such as Safari),
1825 // we need to inject two containers, one that is width/height 100% and another that is left/top -1px so that the final container always is 1x1 pixels bigger than
1826 // the targeted element.
1827 // When the bug is resolved, "containerContainer" may be removed.
1828
1829 // The outer container can occasionally be less wide than the targeted when inside inline elements element in WebKit (see https://bugs.webkit.org/show_bug.cgi?id=152980).
1830 // This should be no problem since the inner container either way makes sure the injected scroll elements are at least 1x1 px.
1831
1832 var scrollbarWidth = scrollbarSizes.width;
1833 var scrollbarHeight = scrollbarSizes.height;
1834 var containerContainerStyle = "position: absolute; flex: none; overflow: hidden; z-index: -1; visibility: hidden; width: 100%; height: 100%; left: 0px; top: 0px;";
1835 var containerStyle = "position: absolute; flex: none; overflow: hidden; z-index: -1; visibility: hidden; " + getLeftTopBottomRightCssText(-(1 + scrollbarWidth), -(1 + scrollbarHeight), -scrollbarHeight, -scrollbarWidth);
1836 var expandStyle = "position: absolute; flex: none; overflow: scroll; z-index: -1; visibility: hidden; width: 100%; height: 100%;";
1837 var shrinkStyle = "position: absolute; flex: none; overflow: scroll; z-index: -1; visibility: hidden; width: 100%; height: 100%;";
1838 var expandChildStyle = "position: absolute; left: 0; top: 0;";
1839 var shrinkChildStyle = "position: absolute; width: 200%; height: 200%;";
1840
1841 var containerContainer = document.createElement("div");
1842 var container = document.createElement("div");
1843 var expand = document.createElement("div");
1844 var expandChild = document.createElement("div");
1845 var shrink = document.createElement("div");
1846 var shrinkChild = document.createElement("div");
1847
1848 // Some browsers choke on the resize system being rtl, so force it to ltr. https://github.com/wnr/element-resize-detector/issues/56
1849 // However, dir should not be set on the top level container as it alters the dimensions of the target element in some browsers.
1850 containerContainer.dir = "ltr";
1851
1852 containerContainer.style.cssText = containerContainerStyle;
1853 containerContainer.className = detectionContainerClass;
1854 container.className = detectionContainerClass;
1855 container.style.cssText = containerStyle;
1856 expand.style.cssText = expandStyle;
1857 expandChild.style.cssText = expandChildStyle;
1858 shrink.style.cssText = shrinkStyle;
1859 shrinkChild.style.cssText = shrinkChildStyle;
1860
1861 expand.appendChild(expandChild);
1862 shrink.appendChild(shrinkChild);
1863 container.appendChild(expand);
1864 container.appendChild(shrink);
1865 containerContainer.appendChild(container);
1866 rootContainer.appendChild(containerContainer);
1867
1868 function onExpandScroll() {
1869 getState(element).onExpand && getState(element).onExpand();
1870 }
1871
1872 function onShrinkScroll() {
1873 getState(element).onShrink && getState(element).onShrink();
1874 }
1875
1876 addEvent(expand, "scroll", onExpandScroll);
1877 addEvent(shrink, "scroll", onShrinkScroll);
1878
1879 // Store the event handlers here so that they may be removed when uninstall is called.
1880 // See uninstall function for an explanation why it is needed.
1881 getState(element).onExpandScroll = onExpandScroll;
1882 getState(element).onShrinkScroll = onShrinkScroll;
1883 }
1884
1885 function registerListenersAndPositionElements() {
1886 function updateChildSizes(element, width, height) {
1887 var expandChild = getExpandChildElement(element);
1888 var expandWidth = getExpandWidth(width);
1889 var expandHeight = getExpandHeight(height);
1890 expandChild.style.width = expandWidth + "px";
1891 expandChild.style.height = expandHeight + "px";
1892 }
1893
1894 function updateDetectorElements(done) {
1895 var width = element.offsetWidth;
1896 var height = element.offsetHeight;
1897
1898 debug("Storing current size", width, height);
1899
1900 // Store the size of the element sync here, so that multiple scroll events may be ignored in the event listeners.
1901 // Otherwise the if-check in handleScroll is useless.
1902 storeCurrentSize(element, width, height);
1903
1904 // Since we delay the processing of the batch, there is a risk that uninstall has been called before the batch gets to execute.
1905 // Since there is no way to cancel the fn executions, we need to add an uninstall guard to all fns of the batch.
1906
1907 batchProcessor.add(0, function performUpdateChildSizes() {
1908 if (!getState(element)) {
1909 debug("Aborting because element has been uninstalled");
1910 return;
1911 }
1912
1913 if (!areElementsInjected()) {
1914 debug("Aborting because element container has not been initialized");
1915 return;
1916 }
1917
1918 if (options.debug) {
1919 var w = element.offsetWidth;
1920 var h = element.offsetHeight;
1921
1922 if (w !== width || h !== height) {
1923 reporter.warn(idHandler.get(element), "Scroll: Size changed before updating detector elements.");
1924 }
1925 }
1926
1927 updateChildSizes(element, width, height);
1928 });
1929
1930 batchProcessor.add(1, function updateScrollbars() {
1931 if (!getState(element)) {
1932 debug("Aborting because element has been uninstalled");
1933 return;
1934 }
1935
1936 if (!areElementsInjected()) {
1937 debug("Aborting because element container has not been initialized");
1938 return;
1939 }
1940
1941 positionScrollbars(element, width, height);
1942 });
1943
1944 if (done) {
1945 batchProcessor.add(2, function () {
1946 if (!getState(element)) {
1947 debug("Aborting because element has been uninstalled");
1948 return;
1949 }
1950
1951 if (!areElementsInjected()) {
1952 debug("Aborting because element container has not been initialized");
1953 return;
1954 }
1955
1956 done();
1957 });
1958 }
1959 }
1960
1961 function areElementsInjected() {
1962 return !!getState(element).container;
1963 }
1964
1965 function notifyListenersIfNeeded() {
1966 function isFirstNotify() {
1967 return getState(element).lastNotifiedWidth === undefined;
1968 }
1969
1970 debug("notifyListenersIfNeeded invoked");
1971
1972 var state = getState(element);
1973
1974 // Don't notify the if the current size is the start size, and this is the first notification.
1975 if (isFirstNotify() && state.lastWidth === state.startSize.width && state.lastHeight === state.startSize.height) {
1976 return debug("Not notifying: Size is the same as the start size, and there has been no notification yet.");
1977 }
1978
1979 // Don't notify if the size already has been notified.
1980 if (state.lastWidth === state.lastNotifiedWidth && state.lastHeight === state.lastNotifiedHeight) {
1981 return debug("Not notifying: Size already notified");
1982 }
1983
1984
1985 debug("Current size not notified, notifying...");
1986 state.lastNotifiedWidth = state.lastWidth;
1987 state.lastNotifiedHeight = state.lastHeight;
1988 forEach(getState(element).listeners, function (listener) {
1989 listener(element);
1990 });
1991 }
1992
1993 function handleRender() {
1994 debug("startanimation triggered.");
1995
1996 if (isUnrendered(element)) {
1997 debug("Ignoring since element is still unrendered...");
1998 return;
1999 }
2000
2001 debug("Element rendered.");
2002 var expand = getExpandElement(element);
2003 var shrink = getShrinkElement(element);
2004 if (expand.scrollLeft === 0 || expand.scrollTop === 0 || shrink.scrollLeft === 0 || shrink.scrollTop === 0) {
2005 debug("Scrollbars out of sync. Updating detector elements...");
2006 updateDetectorElements(notifyListenersIfNeeded);
2007 }
2008 }
2009
2010 function handleScroll() {
2011 debug("Scroll detected.");
2012
2013 if (isUnrendered(element)) {
2014 // Element is still unrendered. Skip this scroll event.
2015 debug("Scroll event fired while unrendered. Ignoring...");
2016 return;
2017 }
2018
2019 var width = element.offsetWidth;
2020 var height = element.offsetHeight;
2021
2022 if (width !== getState(element).lastWidth || height !== getState(element).lastHeight) {
2023 debug("Element size changed.");
2024 updateDetectorElements(notifyListenersIfNeeded);
2025 } else {
2026 debug("Element size has not changed (" + width + "x" + height + ").");
2027 }
2028 }
2029
2030 debug("registerListenersAndPositionElements invoked.");
2031
2032 if (!getState(element)) {
2033 debug("Aborting because element has been uninstalled");
2034 return;
2035 }
2036
2037 getState(element).onRendered = handleRender;
2038 getState(element).onExpand = handleScroll;
2039 getState(element).onShrink = handleScroll;
2040
2041 var style = getState(element).style;
2042 updateChildSizes(element, style.width, style.height);
2043 }
2044
2045 function finalizeDomMutation() {
2046 debug("finalizeDomMutation invoked.");
2047
2048 if (!getState(element)) {
2049 debug("Aborting because element has been uninstalled");
2050 return;
2051 }
2052
2053 var style = getState(element).style;
2054 storeCurrentSize(element, style.width, style.height);
2055 positionScrollbars(element, style.width, style.height);
2056 }
2057
2058 function ready() {
2059 callback(element);
2060 }
2061
2062 function install() {
2063 debug("Installing...");
2064 initListeners();
2065 storeStartSize();
2066
2067 batchProcessor.add(0, storeStyle);
2068 batchProcessor.add(1, injectScrollElements);
2069 batchProcessor.add(2, registerListenersAndPositionElements);
2070 batchProcessor.add(3, finalizeDomMutation);
2071 batchProcessor.add(4, ready);
2072 }
2073
2074 debug("Making detectable...");
2075
2076 if (isDetached(element)) {
2077 debug("Element is detached");
2078
2079 injectContainerElement();
2080
2081 debug("Waiting until element is attached...");
2082
2083 getState(element).onRendered = function () {
2084 debug("Element is now attached");
2085 install();
2086 };
2087 } else {
2088 install();
2089 }
2090 }
2091
2092 function uninstall(element) {
2093 var state = getState(element);
2094
2095 if (!state) {
2096 // Uninstall has been called on a non-erd element.
2097 return;
2098 }
2099
2100 // Uninstall may have been called in the following scenarios:
2101 // (1) Right between the sync code and async batch (here state.busy = true, but nothing have been registered or injected).
2102 // (2) In the ready callback of the last level of the batch by another element (here, state.busy = true, but all the stuff has been injected).
2103 // (3) After the installation process (here, state.busy = false and all the stuff has been injected).
2104 // So to be on the safe side, let's check for each thing before removing.
2105
2106 // We need to remove the event listeners, because otherwise the event might fire on an uninstall element which results in an error when trying to get the state of the element.
2107 state.onExpandScroll && removeEvent(getExpandElement(element), "scroll", state.onExpandScroll);
2108 state.onShrinkScroll && removeEvent(getShrinkElement(element), "scroll", state.onShrinkScroll);
2109 state.onAnimationStart && removeEvent(state.container, "animationstart", state.onAnimationStart);
2110
2111 state.container && element.removeChild(state.container);
2112 }
2113
2114 return {
2115 makeDetectable: makeDetectable,
2116 addListener: addListener,
2117 uninstall: uninstall
2118 };
2119};
2120
2121
2122/***/ })
2123/******/ ]);
2124});
2125//# sourceMappingURL=react-container-query.js.map
\No newline at end of file