Project import generated by Copybara.

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