UNPKG

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