ResizeObserver.global.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global.ResizeObserver = factory());
  5. }(this, (function () { 'use strict';
  6. /**
  7. * A collection of shims that provide minimal functionality of the ES6 collections.
  8. *
  9. * These implementations are not meant to be used outside of the ResizeObserver
  10. * modules as they cover only a limited range of use cases.
  11. */
  12. /* eslint-disable require-jsdoc, valid-jsdoc */
  13. var MapShim = (function () {
  14. if (typeof Map !== 'undefined') {
  15. return Map;
  16. }
  17. /**
  18. * Returns index in provided array that matches the specified key.
  19. *
  20. * @param {Array<Array>} arr
  21. * @param {*} key
  22. * @returns {number}
  23. */
  24. function getIndex(arr, key) {
  25. var result = -1;
  26. arr.some(function (entry, index) {
  27. if (entry[0] === key) {
  28. result = index;
  29. return true;
  30. }
  31. return false;
  32. });
  33. return result;
  34. }
  35. return (function () {
  36. function anonymous() {
  37. this.__entries__ = [];
  38. }
  39. var prototypeAccessors = { size: { configurable: true } };
  40. /**
  41. * @returns {boolean}
  42. */
  43. prototypeAccessors.size.get = function () {
  44. return this.__entries__.length;
  45. };
  46. /**
  47. * @param {*} key
  48. * @returns {*}
  49. */
  50. anonymous.prototype.get = function (key) {
  51. var index = getIndex(this.__entries__, key);
  52. var entry = this.__entries__[index];
  53. return entry && entry[1];
  54. };
  55. /**
  56. * @param {*} key
  57. * @param {*} value
  58. * @returns {void}
  59. */
  60. anonymous.prototype.set = function (key, value) {
  61. var index = getIndex(this.__entries__, key);
  62. if (~index) {
  63. this.__entries__[index][1] = value;
  64. } else {
  65. this.__entries__.push([key, value]);
  66. }
  67. };
  68. /**
  69. * @param {*} key
  70. * @returns {void}
  71. */
  72. anonymous.prototype.delete = function (key) {
  73. var entries = this.__entries__;
  74. var index = getIndex(entries, key);
  75. if (~index) {
  76. entries.splice(index, 1);
  77. }
  78. };
  79. /**
  80. * @param {*} key
  81. * @returns {void}
  82. */
  83. anonymous.prototype.has = function (key) {
  84. return !!~getIndex(this.__entries__, key);
  85. };
  86. /**
  87. * @returns {void}
  88. */
  89. anonymous.prototype.clear = function () {
  90. this.__entries__.splice(0);
  91. };
  92. /**
  93. * @param {Function} callback
  94. * @param {*} [ctx=null]
  95. * @returns {void}
  96. */
  97. anonymous.prototype.forEach = function (callback, ctx) {
  98. var this$1 = this;
  99. if ( ctx === void 0 ) ctx = null;
  100. for (var i = 0, list = this$1.__entries__; i < list.length; i += 1) {
  101. var entry = list[i];
  102. callback.call(ctx, entry[1], entry[0]);
  103. }
  104. };
  105. Object.defineProperties( anonymous.prototype, prototypeAccessors );
  106. return anonymous;
  107. }());
  108. })();
  109. /**
  110. * Detects whether window and document objects are available in current environment.
  111. */
  112. var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && window.document === document;
  113. // Returns global object of a current environment.
  114. var global$1 = (function () {
  115. if (typeof global !== 'undefined' && global.Math === Math) {
  116. return global;
  117. }
  118. if (typeof self !== 'undefined' && self.Math === Math) {
  119. return self;
  120. }
  121. if (typeof window !== 'undefined' && window.Math === Math) {
  122. return window;
  123. }
  124. // eslint-disable-next-line no-new-func
  125. return Function('return this')();
  126. })();
  127. /**
  128. * A shim for the requestAnimationFrame which falls back to the setTimeout if
  129. * first one is not supported.
  130. *
  131. * @returns {number} Requests' identifier.
  132. */
  133. var requestAnimationFrame$1 = (function () {
  134. if (typeof requestAnimationFrame === 'function') {
  135. // It's required to use a bounded function because IE sometimes throws
  136. // an "Invalid calling object" error if rAF is invoked without the global
  137. // object on the left hand side.
  138. return requestAnimationFrame.bind(global$1);
  139. }
  140. return function (callback) { return setTimeout(function () { return callback(Date.now()); }, 1000 / 60); };
  141. })();
  142. // Defines minimum timeout before adding a trailing call.
  143. var trailingTimeout = 2;
  144. /**
  145. * Creates a wrapper function which ensures that provided callback will be
  146. * invoked only once during the specified delay period.
  147. *
  148. * @param {Function} callback - Function to be invoked after the delay period.
  149. * @param {number} delay - Delay after which to invoke callback.
  150. * @returns {Function}
  151. */
  152. var throttle = function (callback, delay) {
  153. var leadingCall = false,
  154. trailingCall = false,
  155. lastCallTime = 0;
  156. /**
  157. * Invokes the original callback function and schedules new invocation if
  158. * the "proxy" was called during current request.
  159. *
  160. * @returns {void}
  161. */
  162. function resolvePending() {
  163. if (leadingCall) {
  164. leadingCall = false;
  165. callback();
  166. }
  167. if (trailingCall) {
  168. proxy();
  169. }
  170. }
  171. /**
  172. * Callback invoked after the specified delay. It will further postpone
  173. * invocation of the original function delegating it to the
  174. * requestAnimationFrame.
  175. *
  176. * @returns {void}
  177. */
  178. function timeoutCallback() {
  179. requestAnimationFrame$1(resolvePending);
  180. }
  181. /**
  182. * Schedules invocation of the original function.
  183. *
  184. * @returns {void}
  185. */
  186. function proxy() {
  187. var timeStamp = Date.now();
  188. if (leadingCall) {
  189. // Reject immediately following calls.
  190. if (timeStamp - lastCallTime < trailingTimeout) {
  191. return;
  192. }
  193. // Schedule new call to be in invoked when the pending one is resolved.
  194. // This is important for "transitions" which never actually start
  195. // immediately so there is a chance that we might miss one if change
  196. // happens amids the pending invocation.
  197. trailingCall = true;
  198. } else {
  199. leadingCall = true;
  200. trailingCall = false;
  201. setTimeout(timeoutCallback, delay);
  202. }
  203. lastCallTime = timeStamp;
  204. }
  205. return proxy;
  206. };
  207. // Minimum delay before invoking the update of observers.
  208. var REFRESH_DELAY = 20;
  209. // A list of substrings of CSS properties used to find transition events that
  210. // might affect dimensions of observed elements.
  211. var transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight'];
  212. // Check if MutationObserver is available.
  213. var mutationObserverSupported = typeof MutationObserver !== 'undefined';
  214. /**
  215. * Singleton controller class which handles updates of ResizeObserver instances.
  216. */
  217. var ResizeObserverController = function() {
  218. this.connected_ = false;
  219. this.mutationEventsAdded_ = false;
  220. this.mutationsObserver_ = null;
  221. this.observers_ = [];
  222. this.onTransitionEnd_ = this.onTransitionEnd_.bind(this);
  223. this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);
  224. };
  225. /**
  226. * Adds observer to observers list.
  227. *
  228. * @param {ResizeObserverSPI} observer - Observer to be added.
  229. * @returns {void}
  230. */
  231. /**
  232. * Holds reference to the controller's instance.
  233. *
  234. * @private {ResizeObserverController}
  235. */
  236. /**
  237. * Keeps reference to the instance of MutationObserver.
  238. *
  239. * @private {MutationObserver}
  240. */
  241. /**
  242. * Indicates whether DOM listeners have been added.
  243. *
  244. * @private {boolean}
  245. */
  246. ResizeObserverController.prototype.addObserver = function (observer) {
  247. if (!~this.observers_.indexOf(observer)) {
  248. this.observers_.push(observer);
  249. }
  250. // Add listeners if they haven't been added yet.
  251. if (!this.connected_) {
  252. this.connect_();
  253. }
  254. };
  255. /**
  256. * Removes observer from observers list.
  257. *
  258. * @param {ResizeObserverSPI} observer - Observer to be removed.
  259. * @returns {void}
  260. */
  261. ResizeObserverController.prototype.removeObserver = function (observer) {
  262. var observers = this.observers_;
  263. var index = observers.indexOf(observer);
  264. // Remove observer if it's present in registry.
  265. if (~index) {
  266. observers.splice(index, 1);
  267. }
  268. // Remove listeners if controller has no connected observers.
  269. if (!observers.length && this.connected_) {
  270. this.disconnect_();
  271. }
  272. };
  273. /**
  274. * Invokes the update of observers. It will continue running updates insofar
  275. * it detects changes.
  276. *
  277. * @returns {void}
  278. */
  279. ResizeObserverController.prototype.refresh = function () {
  280. var changesDetected = this.updateObservers_();
  281. // Continue running updates if changes have been detected as there might
  282. // be future ones caused by CSS transitions.
  283. if (changesDetected) {
  284. this.refresh();
  285. }
  286. };
  287. /**
  288. * Updates every observer from observers list and notifies them of queued
  289. * entries.
  290. *
  291. * @private
  292. * @returns {boolean} Returns "true" if any observer has detected changes in
  293. * dimensions of it's elements.
  294. */
  295. ResizeObserverController.prototype.updateObservers_ = function () {
  296. // Collect observers that have active observations.
  297. var activeObservers = this.observers_.filter(function (observer) {
  298. return observer.gatherActive(), observer.hasActive();
  299. });
  300. // Deliver notifications in a separate cycle in order to avoid any
  301. // collisions between observers, e.g. when multiple instances of
  302. // ResizeObserver are tracking the same element and the callback of one
  303. // of them changes content dimensions of the observed target. Sometimes
  304. // this may result in notifications being blocked for the rest of observers.
  305. activeObservers.forEach(function (observer) { return observer.broadcastActive(); });
  306. return activeObservers.length > 0;
  307. };
  308. /**
  309. * Initializes DOM listeners.
  310. *
  311. * @private
  312. * @returns {void}
  313. */
  314. ResizeObserverController.prototype.connect_ = function () {
  315. // Do nothing if running in a non-browser environment or if listeners
  316. // have been already added.
  317. if (!isBrowser || this.connected_) {
  318. return;
  319. }
  320. // Subscription to the "Transitionend" event is used as a workaround for
  321. // delayed transitions. This way it's possible to capture at least the
  322. // final state of an element.
  323. document.addEventListener('transitionend', this.onTransitionEnd_);
  324. window.addEventListener('resize', this.refresh);
  325. if (mutationObserverSupported) {
  326. this.mutationsObserver_ = new MutationObserver(this.refresh);
  327. this.mutationsObserver_.observe(document, {
  328. attributes: true,
  329. childList: true,
  330. characterData: true,
  331. subtree: true
  332. });
  333. } else {
  334. document.addEventListener('DOMSubtreeModified', this.refresh);
  335. this.mutationEventsAdded_ = true;
  336. }
  337. this.connected_ = true;
  338. };
  339. /**
  340. * Removes DOM listeners.
  341. *
  342. * @private
  343. * @returns {void}
  344. */
  345. ResizeObserverController.prototype.disconnect_ = function () {
  346. // Do nothing if running in a non-browser environment or if listeners
  347. // have been already removed.
  348. if (!isBrowser || !this.connected_) {
  349. return;
  350. }
  351. document.removeEventListener('transitionend', this.onTransitionEnd_);
  352. window.removeEventListener('resize', this.refresh);
  353. if (this.mutationsObserver_) {
  354. this.mutationsObserver_.disconnect();
  355. }
  356. if (this.mutationEventsAdded_) {
  357. document.removeEventListener('DOMSubtreeModified', this.refresh);
  358. }
  359. this.mutationsObserver_ = null;
  360. this.mutationEventsAdded_ = false;
  361. this.connected_ = false;
  362. };
  363. /**
  364. * "Transitionend" event handler.
  365. *
  366. * @private
  367. * @param {TransitionEvent} event
  368. * @returns {void}
  369. */
  370. ResizeObserverController.prototype.onTransitionEnd_ = function (ref) {
  371. var propertyName = ref.propertyName; if ( propertyName === void 0 ) propertyName = '';
  372. // Detect whether transition may affect dimensions of an element.
  373. var isReflowProperty = transitionKeys.some(function (key) {
  374. return !!~propertyName.indexOf(key);
  375. });
  376. if (isReflowProperty) {
  377. this.refresh();
  378. }
  379. };
  380. /**
  381. * Returns instance of the ResizeObserverController.
  382. *
  383. * @returns {ResizeObserverController}
  384. */
  385. ResizeObserverController.getInstance = function () {
  386. if (!this.instance_) {
  387. this.instance_ = new ResizeObserverController();
  388. }
  389. return this.instance_;
  390. };
  391. ResizeObserverController.instance_ = null;
  392. /**
  393. * Defines non-writable/enumerable properties of the provided target object.
  394. *
  395. * @param {Object} target - Object for which to define properties.
  396. * @param {Object} props - Properties to be defined.
  397. * @returns {Object} Target object.
  398. */
  399. var defineConfigurable = (function (target, props) {
  400. for (var i = 0, list = Object.keys(props); i < list.length; i += 1) {
  401. var key = list[i];
  402. Object.defineProperty(target, key, {
  403. value: props[key],
  404. enumerable: false,
  405. writable: false,
  406. configurable: true
  407. });
  408. }
  409. return target;
  410. });
  411. /**
  412. * Returns the global object associated with provided element.
  413. *
  414. * @param {Object} target
  415. * @returns {Object}
  416. */
  417. var getWindowOf = (function (target) {
  418. // Assume that the element is an instance of Node, which means that it
  419. // has the "ownerDocument" property from which we can retrieve a
  420. // corresponding global object.
  421. var ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView;
  422. // Return the local global object if it's not possible extract one from
  423. // provided element.
  424. return ownerGlobal || global$1;
  425. });
  426. // Placeholder of an empty content rectangle.
  427. var emptyRect = createRectInit(0, 0, 0, 0);
  428. /**
  429. * Converts provided string to a number.
  430. *
  431. * @param {number|string} value
  432. * @returns {number}
  433. */
  434. function toFloat(value) {
  435. return parseFloat(value) || 0;
  436. }
  437. /**
  438. * Extracts borders size from provided styles.
  439. *
  440. * @param {CSSStyleDeclaration} styles
  441. * @param {...string} positions - Borders positions (top, right, ...)
  442. * @returns {number}
  443. */
  444. function getBordersSize(styles) {
  445. var positions = [], len = arguments.length - 1;
  446. while ( len-- > 0 ) positions[ len ] = arguments[ len + 1 ];
  447. return positions.reduce(function (size, position) {
  448. var value = styles['border-' + position + '-width'];
  449. return size + toFloat(value);
  450. }, 0);
  451. }
  452. /**
  453. * Extracts paddings sizes from provided styles.
  454. *
  455. * @param {CSSStyleDeclaration} styles
  456. * @returns {Object} Paddings box.
  457. */
  458. function getPaddings(styles) {
  459. var positions = ['top', 'right', 'bottom', 'left'];
  460. var paddings = {};
  461. for (var i = 0, list = positions; i < list.length; i += 1) {
  462. var position = list[i];
  463. var value = styles['padding-' + position];
  464. paddings[position] = toFloat(value);
  465. }
  466. return paddings;
  467. }
  468. /**
  469. * Calculates content rectangle of provided SVG element.
  470. *
  471. * @param {SVGGraphicsElement} target - Element content rectangle of which needs
  472. * to be calculated.
  473. * @returns {DOMRectInit}
  474. */
  475. function getSVGContentRect(target) {
  476. var bbox = target.getBBox();
  477. return createRectInit(0, 0, bbox.width, bbox.height);
  478. }
  479. /**
  480. * Calculates content rectangle of provided HTMLElement.
  481. *
  482. * @param {HTMLElement} target - Element for which to calculate the content rectangle.
  483. * @returns {DOMRectInit}
  484. */
  485. function getHTMLElementContentRect(target) {
  486. // Client width & height properties can't be
  487. // used exclusively as they provide rounded values.
  488. var clientWidth = target.clientWidth;
  489. var clientHeight = target.clientHeight;
  490. // By this condition we can catch all non-replaced inline, hidden and
  491. // detached elements. Though elements with width & height properties less
  492. // than 0.5 will be discarded as well.
  493. //
  494. // Without it we would need to implement separate methods for each of
  495. // those cases and it's not possible to perform a precise and performance
  496. // effective test for hidden elements. E.g. even jQuery's ':visible' filter
  497. // gives wrong results for elements with width & height less than 0.5.
  498. if (!clientWidth && !clientHeight) {
  499. return emptyRect;
  500. }
  501. var styles = getWindowOf(target).getComputedStyle(target);
  502. var paddings = getPaddings(styles);
  503. var horizPad = paddings.left + paddings.right;
  504. var vertPad = paddings.top + paddings.bottom;
  505. // Computed styles of width & height are being used because they are the
  506. // only dimensions available to JS that contain non-rounded values. It could
  507. // be possible to utilize the getBoundingClientRect if only it's data wasn't
  508. // affected by CSS transformations let alone paddings, borders and scroll bars.
  509. var width = toFloat(styles.width),
  510. height = toFloat(styles.height);
  511. // Width & height include paddings and borders when the 'border-box' box
  512. // model is applied (except for IE).
  513. if (styles.boxSizing === 'border-box') {
  514. // Following conditions are required to handle Internet Explorer which
  515. // doesn't include paddings and borders to computed CSS dimensions.
  516. //
  517. // We can say that if CSS dimensions + paddings are equal to the "client"
  518. // properties then it's either IE, and thus we don't need to subtract
  519. // anything, or an element merely doesn't have paddings/borders styles.
  520. if (Math.round(width + horizPad) !== clientWidth) {
  521. width -= getBordersSize(styles, 'left', 'right') + horizPad;
  522. }
  523. if (Math.round(height + vertPad) !== clientHeight) {
  524. height -= getBordersSize(styles, 'top', 'bottom') + vertPad;
  525. }
  526. }
  527. // Following steps can't be applied to the document's root element as its
  528. // client[Width/Height] properties represent viewport area of the window.
  529. // Besides, it's as well not necessary as the <html> itself neither has
  530. // rendered scroll bars nor it can be clipped.
  531. if (!isDocumentElement(target)) {
  532. // In some browsers (only in Firefox, actually) CSS width & height
  533. // include scroll bars size which can be removed at this step as scroll
  534. // bars are the only difference between rounded dimensions + paddings
  535. // and "client" properties, though that is not always true in Chrome.
  536. var vertScrollbar = Math.round(width + horizPad) - clientWidth;
  537. var horizScrollbar = Math.round(height + vertPad) - clientHeight;
  538. // Chrome has a rather weird rounding of "client" properties.
  539. // E.g. for an element with content width of 314.2px it sometimes gives
  540. // the client width of 315px and for the width of 314.7px it may give
  541. // 314px. And it doesn't happen all the time. So just ignore this delta
  542. // as a non-relevant.
  543. if (Math.abs(vertScrollbar) !== 1) {
  544. width -= vertScrollbar;
  545. }
  546. if (Math.abs(horizScrollbar) !== 1) {
  547. height -= horizScrollbar;
  548. }
  549. }
  550. return createRectInit(paddings.left, paddings.top, width, height);
  551. }
  552. /**
  553. * Checks whether provided element is an instance of the SVGGraphicsElement.
  554. *
  555. * @param {Element} target - Element to be checked.
  556. * @returns {boolean}
  557. */
  558. var isSVGGraphicsElement = (function () {
  559. // Some browsers, namely IE and Edge, don't have the SVGGraphicsElement
  560. // interface.
  561. if (typeof SVGGraphicsElement !== 'undefined') {
  562. return function (target) { return target instanceof getWindowOf(target).SVGGraphicsElement; };
  563. }
  564. // If it's so, then check that element is at least an instance of the
  565. // SVGElement and that it has the "getBBox" method.
  566. // eslint-disable-next-line no-extra-parens
  567. return function (target) { return target instanceof getWindowOf(target).SVGElement && typeof target.getBBox === 'function'; };
  568. })();
  569. /**
  570. * Checks whether provided element is a document element (<html>).
  571. *
  572. * @param {Element} target - Element to be checked.
  573. * @returns {boolean}
  574. */
  575. function isDocumentElement(target) {
  576. return target === getWindowOf(target).document.documentElement;
  577. }
  578. /**
  579. * Calculates an appropriate content rectangle for provided html or svg element.
  580. *
  581. * @param {Element} target - Element content rectangle of which needs to be calculated.
  582. * @returns {DOMRectInit}
  583. */
  584. function getContentRect(target) {
  585. if (!isBrowser) {
  586. return emptyRect;
  587. }
  588. if (isSVGGraphicsElement(target)) {
  589. return getSVGContentRect(target);
  590. }
  591. return getHTMLElementContentRect(target);
  592. }
  593. /**
  594. * Creates rectangle with an interface of the DOMRectReadOnly.
  595. * Spec: https://drafts.fxtf.org/geometry/#domrectreadonly
  596. *
  597. * @param {DOMRectInit} rectInit - Object with rectangle's x/y coordinates and dimensions.
  598. * @returns {DOMRectReadOnly}
  599. */
  600. function createReadOnlyRect(ref) {
  601. var x = ref.x;
  602. var y = ref.y;
  603. var width = ref.width;
  604. var height = ref.height;
  605. // If DOMRectReadOnly is available use it as a prototype for the rectangle.
  606. var Constr = typeof DOMRectReadOnly !== 'undefined' ? DOMRectReadOnly : Object;
  607. var rect = Object.create(Constr.prototype);
  608. // Rectangle's properties are not writable and non-enumerable.
  609. defineConfigurable(rect, {
  610. x: x, y: y, width: width, height: height,
  611. top: y,
  612. right: x + width,
  613. bottom: height + y,
  614. left: x
  615. });
  616. return rect;
  617. }
  618. /**
  619. * Creates DOMRectInit object based on the provided dimensions and the x/y coordinates.
  620. * Spec: https://drafts.fxtf.org/geometry/#dictdef-domrectinit
  621. *
  622. * @param {number} x - X coordinate.
  623. * @param {number} y - Y coordinate.
  624. * @param {number} width - Rectangle's width.
  625. * @param {number} height - Rectangle's height.
  626. * @returns {DOMRectInit}
  627. */
  628. function createRectInit(x, y, width, height) {
  629. return { x: x, y: y, width: width, height: height };
  630. }
  631. /**
  632. * Class that is responsible for computations of the content rectangle of
  633. * provided DOM element and for keeping track of it's changes.
  634. */
  635. var ResizeObservation = function(target) {
  636. this.broadcastWidth = 0;
  637. this.broadcastHeight = 0;
  638. this.contentRect_ = createRectInit(0, 0, 0, 0);
  639. this.target = target;
  640. };
  641. /**
  642. * Updates content rectangle and tells whether it's width or height properties
  643. * have changed since the last broadcast.
  644. *
  645. * @returns {boolean}
  646. */
  647. /**
  648. * Reference to the last observed content rectangle.
  649. *
  650. * @private {DOMRectInit}
  651. */
  652. /**
  653. * Broadcasted width of content rectangle.
  654. *
  655. * @type {number}
  656. */
  657. ResizeObservation.prototype.isActive = function () {
  658. var rect = getContentRect(this.target);
  659. this.contentRect_ = rect;
  660. return rect.width !== this.broadcastWidth || rect.height !== this.broadcastHeight;
  661. };
  662. /**
  663. * Updates 'broadcastWidth' and 'broadcastHeight' properties with a data
  664. * from the corresponding properties of the last observed content rectangle.
  665. *
  666. * @returns {DOMRectInit} Last observed content rectangle.
  667. */
  668. ResizeObservation.prototype.broadcastRect = function () {
  669. var rect = this.contentRect_;
  670. this.broadcastWidth = rect.width;
  671. this.broadcastHeight = rect.height;
  672. return rect;
  673. };
  674. var ResizeObserverEntry = function(target, rectInit) {
  675. var contentRect = createReadOnlyRect(rectInit);
  676. // According to the specification following properties are not writable
  677. // and are also not enumerable in the native implementation.
  678. //
  679. // Property accessors are not being used as they'd require to define a
  680. // private WeakMap storage which may cause memory leaks in browsers that
  681. // don't support this type of collections.
  682. defineConfigurable(this, { target: target, contentRect: contentRect });
  683. };
  684. var ResizeObserverSPI = function(callback, controller, callbackCtx) {
  685. this.activeObservations_ = [];
  686. this.observations_ = new MapShim();
  687. if (typeof callback !== 'function') {
  688. throw new TypeError('The callback provided as parameter 1 is not a function.');
  689. }
  690. this.callback_ = callback;
  691. this.controller_ = controller;
  692. this.callbackCtx_ = callbackCtx;
  693. };
  694. /**
  695. * Starts observing provided element.
  696. *
  697. * @param {Element} target - Element to be observed.
  698. * @returns {void}
  699. */
  700. /**
  701. * Registry of the ResizeObservation instances.
  702. *
  703. * @private {Map<Element, ResizeObservation>}
  704. */
  705. /**
  706. * Public ResizeObserver instance which will be passed to the callback
  707. * function and used as a value of it's "this" binding.
  708. *
  709. * @private {ResizeObserver}
  710. */
  711. /**
  712. * Collection of resize observations that have detected changes in dimensions
  713. * of elements.
  714. *
  715. * @private {Array<ResizeObservation>}
  716. */
  717. ResizeObserverSPI.prototype.observe = function (target) {
  718. if (!arguments.length) {
  719. throw new TypeError('1 argument required, but only 0 present.');
  720. }
  721. // Do nothing if current environment doesn't have the Element interface.
  722. if (typeof Element === 'undefined' || !(Element instanceof Object)) {
  723. return;
  724. }
  725. if (!(target instanceof getWindowOf(target).Element)) {
  726. throw new TypeError('parameter 1 is not of type "Element".');
  727. }
  728. var observations = this.observations_;
  729. // Do nothing if element is already being observed.
  730. if (observations.has(target)) {
  731. return;
  732. }
  733. observations.set(target, new ResizeObservation(target));
  734. this.controller_.addObserver(this);
  735. // Force the update of observations.
  736. this.controller_.refresh();
  737. };
  738. /**
  739. * Stops observing provided element.
  740. *
  741. * @param {Element} target - Element to stop observing.
  742. * @returns {void}
  743. */
  744. ResizeObserverSPI.prototype.unobserve = function (target) {
  745. if (!arguments.length) {
  746. throw new TypeError('1 argument required, but only 0 present.');
  747. }
  748. // Do nothing if current environment doesn't have the Element interface.
  749. if (typeof Element === 'undefined' || !(Element instanceof Object)) {
  750. return;
  751. }
  752. if (!(target instanceof getWindowOf(target).Element)) {
  753. throw new TypeError('parameter 1 is not of type "Element".');
  754. }
  755. var observations = this.observations_;
  756. // Do nothing if element is not being observed.
  757. if (!observations.has(target)) {
  758. return;
  759. }
  760. observations.delete(target);
  761. if (!observations.size) {
  762. this.controller_.removeObserver(this);
  763. }
  764. };
  765. /**
  766. * Stops observing all elements.
  767. *
  768. * @returns {void}
  769. */
  770. ResizeObserverSPI.prototype.disconnect = function () {
  771. this.clearActive();
  772. this.observations_.clear();
  773. this.controller_.removeObserver(this);
  774. };
  775. /**
  776. * Collects observation instances the associated element of which has changed
  777. * it's content rectangle.
  778. *
  779. * @returns {void}
  780. */
  781. ResizeObserverSPI.prototype.gatherActive = function () {
  782. var this$1 = this;
  783. this.clearActive();
  784. this.observations_.forEach(function (observation) {
  785. if (observation.isActive()) {
  786. this$1.activeObservations_.push(observation);
  787. }
  788. });
  789. };
  790. /**
  791. * Invokes initial callback function with a list of ResizeObserverEntry
  792. * instances collected from active resize observations.
  793. *
  794. * @returns {void}
  795. */
  796. ResizeObserverSPI.prototype.broadcastActive = function () {
  797. // Do nothing if observer doesn't have active observations.
  798. if (!this.hasActive()) {
  799. return;
  800. }
  801. var ctx = this.callbackCtx_;
  802. // Create ResizeObserverEntry instance for every active observation.
  803. var entries = this.activeObservations_.map(function (observation) {
  804. return new ResizeObserverEntry(observation.target, observation.broadcastRect());
  805. });
  806. this.callback_.call(ctx, entries, ctx);
  807. this.clearActive();
  808. };
  809. /**
  810. * Clears the collection of active observations.
  811. *
  812. * @returns {void}
  813. */
  814. ResizeObserverSPI.prototype.clearActive = function () {
  815. this.activeObservations_.splice(0);
  816. };
  817. /**
  818. * Tells whether observer has active observations.
  819. *
  820. * @returns {boolean}
  821. */
  822. ResizeObserverSPI.prototype.hasActive = function () {
  823. return this.activeObservations_.length > 0;
  824. };
  825. // Registry of internal observers. If WeakMap is not available use current shim
  826. // for the Map collection as it has all required methods and because WeakMap
  827. // can't be fully polyfilled anyway.
  828. var observers = typeof WeakMap !== 'undefined' ? new WeakMap() : new MapShim();
  829. /**
  830. * ResizeObserver API. Encapsulates the ResizeObserver SPI implementation
  831. * exposing only those methods and properties that are defined in the spec.
  832. */
  833. var ResizeObserver = function(callback) {
  834. if (!(this instanceof ResizeObserver)) {
  835. throw new TypeError('Cannot call a class as a function.');
  836. }
  837. if (!arguments.length) {
  838. throw new TypeError('1 argument required, but only 0 present.');
  839. }
  840. var controller = ResizeObserverController.getInstance();
  841. var observer = new ResizeObserverSPI(callback, controller, this);
  842. observers.set(this, observer);
  843. };
  844. // Expose public methods of ResizeObserver.
  845. ['observe', 'unobserve', 'disconnect'].forEach(function (method) {
  846. ResizeObserver.prototype[method] = function () {
  847. return (ref = observers.get(this))[method].apply(ref, arguments);
  848. var ref;
  849. };
  850. });
  851. var index = (function () {
  852. // Export existing implementation if available.
  853. if (typeof global$1.ResizeObserver !== 'undefined') {
  854. return global$1.ResizeObserver;
  855. }
  856. global$1.ResizeObserver = ResizeObserver;
  857. return ResizeObserver;
  858. })();
  859. return index;
  860. })));