ResizeObserver.js 36 KB

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