Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/node_modules/mdl-ext/es/utils/constants.js b/node_modules/mdl-ext/es/utils/constants.js
new file mode 100644
index 0000000..2d063e2
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/constants.js
@@ -0,0 +1,63 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+var VK_TAB = 9;
+var VK_ENTER = 13;
+var VK_ESC = 27;
+var VK_SPACE = 32;
+var VK_PAGE_UP = 33;
+var VK_PAGE_DOWN = 34;
+var VK_END = 35;
+var VK_HOME = 36;
+var VK_ARROW_LEFT = 37;
+var VK_ARROW_UP = 38;
+var VK_ARROW_RIGHT = 39;
+var VK_ARROW_DOWN = 40;
+
+var ARIA_EXPANDED = 'aria-expanded';
+var ARIA_HIDDEN = 'aria-hidden';
+var ARIA_MULTISELECTABLE = 'aria-multiselectable';
+var ARIA_SELECTED = 'aria-selected';
+
+var IS_DIRTY = 'is-dirty';
+var IS_DISABLED = 'is-disabled';
+var IS_EXPANDED = 'is-expanded';
+var IS_FOCUSED = 'is-focused';
+var IS_INVALID = 'is-invalid';
+var IS_UPGRADED = 'is-upgraded';
+var DATA_UPGRADED = 'data-upgraded';
+
+var MDL_RIPPLE = 'mdl-ripple';
+var MDL_RIPPLE_COMPONENT = 'MaterialRipple';
+var MDL_RIPPLE_EFFECT = 'mdl-js-ripple-effect';
+var MDL_RIPPLE_EFFECT_IGNORE_EVENTS = 'mdl-js-ripple-effect--ignore-events';
+
+exports.VK_TAB = VK_TAB;
+exports.VK_ENTER = VK_ENTER;
+exports.VK_ESC = VK_ESC;
+exports.VK_SPACE = VK_SPACE;
+exports.VK_PAGE_UP = VK_PAGE_UP;
+exports.VK_PAGE_DOWN = VK_PAGE_DOWN;
+exports.VK_END = VK_END;
+exports.VK_HOME = VK_HOME;
+exports.VK_ARROW_LEFT = VK_ARROW_LEFT;
+exports.VK_ARROW_UP = VK_ARROW_UP;
+exports.VK_ARROW_RIGHT = VK_ARROW_RIGHT;
+exports.VK_ARROW_DOWN = VK_ARROW_DOWN;
+exports.ARIA_EXPANDED = ARIA_EXPANDED;
+exports.ARIA_HIDDEN = ARIA_HIDDEN;
+exports.ARIA_MULTISELECTABLE = ARIA_MULTISELECTABLE;
+exports.ARIA_SELECTED = ARIA_SELECTED;
+exports.IS_DIRTY = IS_DIRTY;
+exports.IS_DISABLED = IS_DISABLED;
+exports.IS_EXPANDED = IS_EXPANDED;
+exports.IS_FOCUSED = IS_FOCUSED;
+exports.IS_INVALID = IS_INVALID;
+exports.IS_UPGRADED = IS_UPGRADED;
+exports.DATA_UPGRADED = DATA_UPGRADED;
+exports.MDL_RIPPLE = MDL_RIPPLE;
+exports.MDL_RIPPLE_COMPONENT = MDL_RIPPLE_COMPONENT;
+exports.MDL_RIPPLE_EFFECT = MDL_RIPPLE_EFFECT;
+exports.MDL_RIPPLE_EFFECT_IGNORE_EVENTS = MDL_RIPPLE_EFFECT_IGNORE_EVENTS;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/debounce-function.js b/node_modules/mdl-ext/es/utils/debounce-function.js
new file mode 100644
index 0000000..27f977c
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/debounce-function.js
@@ -0,0 +1,89 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _apply = require("babel-runtime/core-js/reflect/apply");
+
+var _apply2 = _interopRequireDefault(_apply);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Debouncing enforces that a function not be called again until a certain
+ * amount of time has passed without it being called.
+ *
+ * @see https://css-tricks.com/the-difference-between-throttling-and-debouncing/
+ * @see https://github.com/webmodules/raf-debounce
+ * @see https://github.com/moszeed/es6-promise-debounce
+ * @see https://gist.github.com/philbirnie/893950093611d5c1dff4246a572cfbeb/
+ * @see https://github.com/SliceMeNice-ES6/event-utils/blob/master/debounce.js
+ * @see https://github.com/jeromedecoster/raf-funcs
+ * @see http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
+ * @see http://davidwalsh.name/javascript-debounce-function
+ *
+ * @param callback the callback
+ * @param threshold optional delay, default to 250 ms, min to 1000/60ms ms
+ * @param context  optional context of this, default to global
+ * @return {Function} reference to immediate and cancel
+ */
+var MIN_THRESHOLD = 1000 / 60;
+
+var debounceFunction = function debounceFunction(callback) {
+  var threshold = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 250;
+  var context = arguments[2];
+
+
+  if (threshold < MIN_THRESHOLD) {
+    threshold = MIN_THRESHOLD;
+  }
+
+  if (!context) {
+    context = this || window;
+  }
+
+  var next = null;
+  var start = 0;
+
+  return function () {
+    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+      args[_key] = arguments[_key];
+    }
+
+    var _cancel = function _cancel() {
+      if (next) {
+        window.cancelAnimationFrame(next);
+        next = null;
+      }
+    };
+
+    var execute = function execute() {
+      _cancel();
+      return (0, _apply2.default)(callback, context, args);
+    };
+
+    var later = function later() {
+      if (threshold - (Date.now() - start) <= 0) {
+        return execute();
+      }
+      next = window.requestAnimationFrame(later);
+    };
+
+    _cancel();
+    start = Date.now();
+    next = window.requestAnimationFrame(later);
+
+    return {
+      cancel: function cancel() {
+        return _cancel();
+      },
+      immediate: function immediate() {
+        return execute();
+      }
+    };
+  };
+};
+
+exports.default = debounceFunction;
+module.exports = exports["default"];
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/dom-utils.js b/node_modules/mdl-ext/es/utils/dom-utils.js
new file mode 100644
index 0000000..3f63f90
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/dom-utils.js
@@ -0,0 +1,424 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.tether = exports.removeChildElements = exports.moveElements = exports.isRectInsideWindowViewport = exports.isFocusable = exports.getScrollParents = exports.getParentElements = exports.getWindowViewport = undefined;
+
+var _isNan = require('babel-runtime/core-js/number/is-nan');
+
+var _isNan2 = _interopRequireDefault(_isNan);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Remove child element(s)
+ * element.innerHTNL = '' has a performance penality!
+ * @see http://jsperf.com/empty-an-element/16
+ * @see http://jsperf.com/force-reflow
+ * @param element
+ * @param forceReflow
+ */
+var removeChildElements = function removeChildElements(element) {
+  var forceReflow = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
+
+
+  // See: http://jsperf.com/empty-an-element/16
+  while (element.lastChild) {
+    element.removeChild(element.lastChild);
+  }
+  if (forceReflow) {
+    // See: http://jsperf.com/force-reflow
+    var d = element.style.display;
+
+    element.style.display = 'none';
+    element.style.display = d;
+  }
+};
+
+/**
+ * Moves child elements from a DOM node to another dom node.
+ * @param source {HTMLElement}
+ * @param target {HTMLElement} If the target parameter is ommited, a document fragment is created
+ * @return {HTMLElement} The target node
+ *
+ * @example
+ * // Moves child elements from a DOM node to another dom node.
+ * moveElements(source, destination);
+ *
+ * @example
+ * // If the second parameter is ommited, a document fragment is created:
+ * let fragment = moveElements(source);
+ *
+ * @See: https://github.com/webmodules/dom-move
+ */
+var moveElements = function moveElements(source, target) {
+  if (!target) {
+    target = source.ownerDocument.createDocumentFragment();
+  }
+  while (source.firstChild) {
+    target.appendChild(source.firstChild);
+  }
+  return target;
+};
+
+/**
+ * Get the browser viewport dimensions
+ * @see http://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript
+ * @return {{windowWidth: number, windowHeight: number}}
+ */
+var getWindowViewport = function getWindowViewport() {
+  return {
+    viewportWidth: Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0),
+    viewportHeight: Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
+  };
+};
+
+/**
+ * Check whether an element is in the window viewport
+ * @see http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport/
+ * @param top
+ * @param left
+ * @param bottom
+ * @param right
+ * @return {boolean} true if rectangle is inside window viewport, otherwise false
+ */
+var isRectInsideWindowViewport = function isRectInsideWindowViewport(_ref) {
+  var top = _ref.top,
+      left = _ref.left,
+      bottom = _ref.bottom,
+      right = _ref.right;
+
+  var _getWindowViewport = getWindowViewport(),
+      viewportWidth = _getWindowViewport.viewportWidth,
+      viewportHeight = _getWindowViewport.viewportHeight;
+
+  return top >= 0 && left >= 0 && bottom <= viewportHeight && right <= viewportWidth;
+};
+
+/**
+ * Get a list of parent elements that can possibly scroll
+ * @param el the element to get parents for
+ * @returns {Array}
+ */
+var getScrollParents = function getScrollParents(el) {
+  var elements = [];
+
+  /*
+  for (el = el.parentNode; el; el = el.parentNode) {
+    const cs = window.getComputedStyle(el);
+    if(!(cs.overflowY === 'hidden' && cs.overflowX === 'hidden')) {
+      elements.unshift(el);
+    }
+    if(el === document.body) {
+      break;
+    }
+  }
+  */
+
+  var element = el.parentNode;
+  while (element) {
+    var cs = window.getComputedStyle(element);
+    if (!(cs.overflowY === 'hidden' && cs.overflowX === 'hidden')) {
+      elements.unshift(element);
+    }
+    if (element === document.body) {
+      break;
+    }
+    element = element.parentNode;
+  }
+
+  return elements;
+};
+
+/**
+ * Get a list of parent elements, from a given element to a given element
+ * @param {HTMLElement} from
+ * @param {HTMLElement} to
+ * @return {Array<HTMLElement>} the parent elements, not including from and to
+ */
+var getParentElements = function getParentElements(from, to) {
+  var result = [];
+  var element = from.parentNode;
+  while (element) {
+    if (element === to) {
+      break;
+    }
+    result.unshift(element);
+    element = element.parentNode;
+  }
+  return result;
+};
+
+/**
+ * Position element next to button
+ *
+ * Positioning strategy
+ *  1. element.height > viewport.height
+ *     let element.height = viewport.heigt
+ *     let element.overflow-y = auto
+ *  2. element.width > viewport.width
+ *     let element.width = viewport.width
+ *  3. position element below button, align left edge of element with button left
+ *       done if element inside viewport
+ *  4. position element below button, align right edge of element with button right
+ *       done if element inside viewport
+ *  5. positions element above button, aligns left edge of element with button left
+ *       done if element inside viewport
+ *  6. position element above the control element, aligned to its right.
+ *       done if element inside viewport
+ *  7. position element at button right hand side, aligns element top with button top
+ *       done if element inside viewport
+ *  8. position element at button left hand side, aligns element top with button top
+ *       done if element inside viewport
+ *  9. position element inside viewport
+ *     1. position element at viewport bottom
+ *     2. position element at button right hand side
+ *        done if element inside viewport
+ *     3. position element at button left hand side
+ *       done if element inside viewport
+ *     4. position element at viewport right
+ * 10. done
+ *
+ */
+var tether = function tether(controlledBy, element) {
+  var controlRect = controlledBy.getBoundingClientRect();
+
+  // 1. will element height fit inside window viewport?
+
+  var _getWindowViewport2 = getWindowViewport(),
+      viewportWidth = _getWindowViewport2.viewportWidth,
+      viewportHeight = _getWindowViewport2.viewportHeight;
+
+  element.style.height = 'auto';
+  //element.style.overflowY = 'hidden';
+  if (element.offsetHeight > viewportHeight) {
+    element.style.height = viewportHeight + 'px';
+    element.style.overflowY = 'auto';
+  }
+
+  // 2. will element width fit inside window viewport?
+  element.style.width = 'auto';
+  if (element.offsetWidth > viewportWidth) {
+    element.style.width = viewportWidth + 'px';
+  }
+
+  var elementRect = element.getBoundingClientRect();
+
+  // element to control distance
+  var dy = controlRect.top - elementRect.top;
+  var dx = controlRect.left - elementRect.left;
+
+  // element rect, window coordinates relative to top,left of control
+  var top = elementRect.top + dy;
+  var left = elementRect.left + dx;
+  var bottom = top + elementRect.height;
+  var right = left + elementRect.width;
+
+  // Position relative to control
+  var ddy = dy;
+  var ddx = dx;
+
+  if (isRectInsideWindowViewport({
+    top: top + controlRect.height,
+    left: left,
+    bottom: bottom + controlRect.height,
+    right: right
+  })) {
+    // 3 position element below the control element, aligned to its left
+    ddy = controlRect.height + dy;
+    //console.log('***** 3');
+  } else if (isRectInsideWindowViewport({
+    top: top + controlRect.height,
+    left: left + controlRect.width - elementRect.width,
+    bottom: bottom + controlRect.height,
+    right: left + controlRect.width
+  })) {
+    // 4 position element below the control element, aligned to its right
+    ddy = controlRect.height + dy;
+    ddx = dx + controlRect.width - elementRect.width;
+    //console.log('***** 4');
+  } else if (isRectInsideWindowViewport({
+    top: top - elementRect.height,
+    left: left,
+    bottom: bottom - elementRect.height,
+    right: right
+  })) {
+    // 5. position element above the control element, aligned to its left.
+    ddy = dy - elementRect.height;
+    //console.log('***** 5');
+  } else if (isRectInsideWindowViewport({
+    top: top - elementRect.height,
+    left: left + controlRect.width - elementRect.width,
+    bottom: bottom - elementRect.height,
+    right: left + controlRect.width
+  })) {
+    // 6. position element above the control element, aligned to its right.
+    ddy = dy - elementRect.height;
+    ddx = dx + controlRect.width - elementRect.width;
+    //console.log('***** 6');
+  } else if (isRectInsideWindowViewport({
+    top: top,
+    left: left + controlRect.width,
+    bottom: bottom,
+    right: right + controlRect.width
+  })) {
+    // 7. position element at button right hand side
+    ddx = controlRect.width + dx;
+    //console.log('***** 7');
+  } else if (isRectInsideWindowViewport({
+    top: top,
+    left: left - controlRect.width,
+    bottom: bottom,
+    right: right - controlRect.width
+  })) {
+    // 8. position element at button left hand side
+    ddx = dx - elementRect.width;
+    //console.log('***** 8');
+  } else {
+    // 9. position element inside viewport, near controlrect if possible
+    //console.log('***** 9');
+
+    // 9.1 position element near controlrect bottom
+    ddy = dy - bottom + viewportHeight;
+    if (top + controlRect.height >= 0 && bottom + controlRect.height <= viewportHeight) {
+      ddy = controlRect.height + dy;
+    } else if (top - elementRect.height >= 0 && bottom - elementRect.height <= viewportHeight) {
+      ddy = dy - elementRect.height;
+    }
+
+    if (left + elementRect.width + controlRect.width <= viewportWidth) {
+      // 9.2 Position element at button right hand side
+      ddx = controlRect.width + dx;
+      //console.log('***** 9.2');
+    } else if (left - elementRect.width >= 0) {
+      // 9.3 Position element at button left hand side
+      ddx = dx - elementRect.width;
+      //console.log('***** 9.3');
+    } else {
+      // 9.4 position element at (near) viewport right
+      var r = left + elementRect.width - viewportWidth;
+      ddx = dx - r;
+      //console.log('***** 9.4');
+    }
+  }
+
+  // 10. done
+  element.style.top = element.offsetTop + ddy + 'px';
+  element.style.left = element.offsetLeft + ddx + 'px';
+  //console.log('***** 10. done');
+};
+
+/**
+ * Check if the given element can receive focus
+ * @param {HTMLElement} element the element to check
+ * @return {boolean} true if the element is focusable, otherwise false
+ */
+var isFocusable = function isFocusable(element) {
+  // https://github.com/stephenmathieson/is-focusable/blob/master/index.js
+  // http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
+
+  if (element.hasAttribute('tabindex')) {
+    var tabindex = element.getAttribute('tabindex');
+    if (!(0, _isNan2.default)(tabindex)) {
+      return parseInt(tabindex) > -1;
+    }
+  }
+
+  if (element.hasAttribute('contenteditable') && element.getAttribute('contenteditable') !== 'false') {
+    // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-contenteditable
+    return true;
+  }
+
+  // natively focusable, but only when enabled
+  var selector = /input|select|textarea|button|details/i;
+  var name = element.nodeName;
+  if (selector.test(name)) {
+    return element.type.toLowerCase() !== 'hidden' && !element.disabled;
+  }
+
+  // anchors and area must have an href
+  if (name === 'A' || name === 'AREA') {
+    return !!element.href;
+  }
+
+  if (name === 'IFRAME') {
+    // Check visible iframe
+    var cs = window.getComputedStyle(element);
+    return cs.getPropertyValue('display').toLowerCase() !== 'none';
+  }
+
+  return false;
+};
+
+/**
+ * Get a list of offset parents for given element
+ * @see https://www.benpickles.com/articles/51-finding-a-dom-nodes-common-ancestor-using-javascript
+ * @param el the element
+ * @return {Array} a list of offset parents
+ */
+/*
+const offsetParents = (el) => {
+  const elements = [];
+  for (; el; el = el.offsetParent) {
+    elements.unshift(el);
+  }
+  if(!elements.find(e => e === document.body)) {
+    elements.unshift(document.body);
+  }
+  return elements;
+};
+*/
+
+/**
+ * Finds the common offset ancestor of two DOM nodes
+ * @see https://www.benpickles.com/articles/51-finding-a-dom-nodes-common-ancestor-using-javascript
+ * @see https://gist.github.com/benpickles/4059636
+ * @param a
+ * @param b
+ * @return {Element} The common offset ancestor of a and b
+ */
+/*
+const commonOffsetAncestor = (a, b) => {
+  const parentsA = offsetParents(a);
+  const parentsB = offsetParents(b);
+
+  for (let i = 0; i < parentsA.length; i++) {
+    if (parentsA[i] !== parentsB[i]) return parentsA[i-1];
+  }
+};
+*/
+
+/**
+ * Calculate position relative to a target element
+ * @see http://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively
+ * @param target
+ * @param el
+ * @return {{top: number, left: number}}
+ */
+/*
+const calcPositionRelativeToTarget = (target, el) => {
+  let top = 0;
+  let left = 0;
+
+  while(el) {
+    top += (el.offsetTop - el.scrollTop + el.clientTop) || 0;
+    left += (el.offsetLeft - el.scrollLeft + el.clientLeft) || 0;
+    el = el.offsetParent;
+
+    if(el === target) {
+      break;
+    }
+  }
+  return { top: top, left: left };
+};
+*/
+
+exports.getWindowViewport = getWindowViewport;
+exports.getParentElements = getParentElements;
+exports.getScrollParents = getScrollParents;
+exports.isFocusable = isFocusable;
+exports.isRectInsideWindowViewport = isRectInsideWindowViewport;
+exports.moveElements = moveElements;
+exports.removeChildElements = removeChildElements;
+exports.tether = tether;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/easing.js b/node_modules/mdl-ext/es/utils/easing.js
new file mode 100644
index 0000000..95da687
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/easing.js
@@ -0,0 +1,22 @@
+'use strict';
+
+// See: http://robertpenner.com/easing/
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+var easeInOutQuad = function easeInOutQuad(t, b, c, d) {
+  t /= d / 2;
+  if (t < 1) return c / 2 * t * t + b;
+  t--;
+  return -c / 2 * (t * (t - 2) - 1) + b;
+};
+
+var inOutQuintic = function inOutQuintic(t, b, c, d) {
+  var ts = (t /= d) * t;
+  var tc = ts * t;
+  return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
+};
+
+exports.easeInOutQuad = easeInOutQuad;
+exports.inOutQuintic = inOutQuintic;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/full-throttle.js b/node_modules/mdl-ext/es/utils/full-throttle.js
new file mode 100644
index 0000000..da32123
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/full-throttle.js
@@ -0,0 +1,52 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _apply = require("babel-runtime/core-js/reflect/apply");
+
+var _apply2 = _interopRequireDefault(_apply);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Since some events can fire at a high rate, the event handler should be limited to execute computationally
+ * expensive operations, such as DOM modifications, inside a single rendered frame.
+ * When listening to e.g. scroll and resize events, the browser tends to fire off more events per
+ * second than are actually useful. For instance, if your event listener sets some element positions, then it
+ * is possible for those positions to be updated multiple times in a single rendered frame. In this case, all of
+ * the layout calculations triggered by setting the elements' positions will be wasted except for the one time that
+ * it runs immediately prior to the browser rendering the updated layout to the screen.
+ * To avoid wasting cycles, we can use requestAnimationFrame to only run the event listener once just before the page
+ * is rendered to the screen.
+ * *
+ * @param callback the function to throttle
+ * @param context  optional context of this, default to global
+ * @return {function(...[*])}
+ */
+var fullThrottle = function fullThrottle(callback, context) {
+
+  if (!context) {
+    context = undefined || window;
+  }
+
+  var throttling = false;
+
+  return function () {
+    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+      args[_key] = arguments[_key];
+    }
+
+    if (!throttling) {
+      throttling = true;
+      window.requestAnimationFrame(function () {
+        throttling = false;
+        return (0, _apply2.default)(callback, context, args);
+      });
+    }
+  };
+};
+
+exports.default = fullThrottle;
+module.exports = exports["default"];
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/index.js b/node_modules/mdl-ext/es/utils/index.js
new file mode 100644
index 0000000..4ef9db4
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/index.js
@@ -0,0 +1,15 @@
+'use strict';
+
+require('./constants');
+
+require('./dom-utils');
+
+require('./string-utils');
+
+require('./json-utils');
+
+require('./full-throttle');
+
+require('./easing');
+
+require('./interval-function');
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/interval-function.js b/node_modules/mdl-ext/es/utils/interval-function.js
new file mode 100644
index 0000000..abe7f9b
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/interval-function.js
@@ -0,0 +1,96 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+var MIN_INERVAL = 1000 / 60;
+
+/**
+ * Trigger a callback at a given interval
+ * @param interval defaults to 1000/60 ms
+ * @return {function()} reference to start, stop, immediate and started
+ */
+
+var intervalFunction = function intervalFunction() {
+  var interval = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : MIN_INERVAL;
+
+
+  var lapse = interval < MIN_INERVAL ? MIN_INERVAL : interval;
+  var cb = undefined;
+  var next = null;
+  var timeElapsed = 0;
+
+  var execute = function execute() {
+    var f = cb(timeElapsed);
+    if (!f) {
+      cancel();
+    }
+  };
+
+  var cancel = function cancel() {
+    if (next) {
+      window.cancelAnimationFrame(next);
+    }
+    next = null;
+    timeElapsed = 0;
+  };
+
+  var _start = function _start() {
+    var timeStart = Date.now();
+
+    var loop = function loop(now) {
+      if (next) {
+        next = window.requestAnimationFrame(function () {
+          return loop(Date.now());
+        });
+
+        timeElapsed += now - timeStart;
+
+        if (timeElapsed >= lapse) {
+          execute();
+          if ((timeElapsed -= lapse) > lapse) {
+            // time elapsed - interval_ > interval_ , indicates inactivity
+            // Could be due to browser minimized, tab changed, screen saver started, computer sleep, and so on
+            timeElapsed = 0;
+          }
+        }
+        timeStart = now;
+      }
+    };
+
+    next = 1; // a truthy value for first loop
+    loop(timeStart);
+  };
+
+  return {
+    get started() {
+      return next != null;
+    },
+    get interval() {
+      return lapse;
+    },
+    set interval(value) {
+      lapse = value < MIN_INERVAL ? MIN_INERVAL : value;
+    },
+    start: function start(callback) {
+      if (typeof callback !== 'function') {
+        throw new TypeError('callback parameter must be a function');
+      }
+      cb = callback;
+      _start();
+    },
+    immediate: function immediate() {
+      if (!cb) {
+        throw new ReferenceError('callback parameter is not defined. Call start before immediate.');
+      }
+      execute();
+    },
+
+    stop: function stop() {
+      return cancel();
+    }
+  };
+};
+
+exports.default = intervalFunction;
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/json-utils.js b/node_modules/mdl-ext/es/utils/json-utils.js
new file mode 100644
index 0000000..50be006
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/json-utils.js
@@ -0,0 +1,31 @@
+'use strict';
+
+/**
+ * Converts a JSON string to object
+ * @param jsonString
+ * @param source
+ */
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.jsonStringToObject = undefined;
+
+var _assign = require('babel-runtime/core-js/object/assign');
+
+var _assign2 = _interopRequireDefault(_assign);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var jsonStringToObject = function jsonStringToObject(jsonString) {
+  var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+  var s = jsonString.replace(/'/g, '"');
+  try {
+    return (0, _assign2.default)(source, JSON.parse(s));
+  } catch (e) {
+    throw new Error('Failed to parse json string: ' + s + '. Error: ' + e.message);
+  }
+};
+
+exports.jsonStringToObject = jsonStringToObject;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/resize-observer.js b/node_modules/mdl-ext/es/utils/resize-observer.js
new file mode 100644
index 0000000..37f0e0d
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/resize-observer.js
@@ -0,0 +1,377 @@
+'use strict';
+
+var _getIterator2 = require('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _intervalFunction = require('./interval-function');
+
+var _intervalFunction2 = _interopRequireDefault(_intervalFunction);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+(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
+   */
+  var getContentRect = function getContentRect(target) {
+    var cs = window.getComputedStyle(target);
+    var r = target.getBoundingClientRect();
+    var top = parseFloat(cs.paddingTop) || 0;
+    var left = parseFloat(cs.paddingLeft) || 0;
+    var width = r.width - ((parseFloat(cs.marginLeft) || 0) + (parseFloat(cs.marginRight) || 0) + (parseFloat(cs.borderLeftWidth) || 0) + (parseFloat(cs.borderRightWidth) || 0) + left + (parseFloat(cs.paddingRight) || 0));
+    var 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 };
+  };
+
+  var dimensionHasChanged = function dimensionHasChanged(target, lastWidth, lastHeight) {
+    var _getContentRect = getContentRect(target),
+        width = _getContentRect.width,
+        height = _getContentRect.height;
+
+    return width !== lastWidth || height !== lastHeight;
+  };
+
+  /**
+   * ResizeObservation holds observation information for a single Element.
+   * @param target
+   * @return {{target: *, broadcastWidth, broadcastHeight, isOrphan: (function()), isActive: (function())}}
+   * @constructor
+   */
+  var ResizeObservation = function ResizeObservation(target) {
+    var _getContentRect2 = getContentRect(target),
+        width = _getContentRect2.width,
+        height = _getContentRect2.height;
+
+    return {
+      target: target,
+      broadcastWidth: width,
+      broadcastHeight: height,
+
+      isOrphan: function isOrphan() {
+        return !this.target || !this.target.parentNode;
+      },
+      isActive: function isActive() {
+        return dimensionHasChanged(this.target, this.broadcastWidth, this.broadcastHeight);
+      }
+    };
+  };
+
+  /**
+   * A snapshot of the observed element
+   * @param target
+   * @param rect
+   * @return {{target: *, contentRect: *}}
+   * @constructor
+   */
+  var ResizeObserverEntry = function ResizeObserverEntry(target, rect) {
+    return {
+      target: target,
+      contentRect: rect
+    };
+  };
+
+  /**
+   * The ResizeObserver is used to observe changes to Element's content rect.
+   */
+
+  var ResizeObserver = function () {
+
+    /**
+     * Constructor for instantiating new Resize observers.
+     * @param callback void (sequence<ResizeObserverEntry> entries). The function which will be called on each resize.
+     * @throws {TypeError}
+     */
+    function ResizeObserver(callback) {
+      (0, _classCallCheck3.default)(this, ResizeObserver);
+
+
+      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}
+     */
+
+
+    (0, _createClass3.default)(ResizeObserver, [{
+      key: 'observe',
+
+
+      /**
+       * Adds target to the list of observed elements.
+       * @param {HTMLElement} target The target to observe
+       */
+      value: function observe(target) {
+        if (target) {
+          if (!(target instanceof HTMLElement)) {
+            throw new TypeError('target parameter must be an HTMLElement');
+          }
+          if (!this.observationTargets_.find(function (t) {
+            return t.target === target;
+          })) {
+            this.observationTargets_.push(ResizeObservation(target));
+            resizeController.start();
+          }
+        }
+      }
+
+      /**
+       * Removes target from the list of observed elements.
+       * @param target The target to remove
+       */
+
+    }, {
+      key: 'unobserve',
+      value: function unobserve(target) {
+        var i = this.observationTargets_.findIndex(function (t) {
+          return 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.
+       */
+
+    }, {
+      key: 'disconnect',
+      value: function disconnect() {
+        this.observationTargets_ = [];
+        this.activeTargets_ = [];
+      }
+
+      /**
+       * Removes the ResizeObserver from the list of observers
+       */
+
+    }, {
+      key: 'destroy',
+      value: function destroy() {
+        var _this = this;
+
+        this.disconnect();
+        var i = document.resizeObservers.findIndex(function (o) {
+          return o === _this;
+        });
+        if (i > -1) {
+          document.resizeObservers.splice(i, 1);
+        }
+      }
+    }, {
+      key: 'deleteOrphansAndPopulateActiveTargets_',
+      value: function 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(function (prev, resizeObservation, index, arr) {
+          if (resizeObservation.isOrphan()) {
+            arr.splice(index, 1);
+          } else if (resizeObservation.isActive()) {
+            prev.push(resizeObservation);
+          }
+          return prev;
+        }, []);
+      }
+    }, {
+      key: 'broadcast_',
+      value: function broadcast_() {
+        this.deleteOrphansAndPopulateActiveTargets_();
+        if (this.activeTargets_.length > 0) {
+          var entries = [];
+          var _iteratorNormalCompletion = true;
+          var _didIteratorError = false;
+          var _iteratorError = undefined;
+
+          try {
+            for (var _iterator = (0, _getIterator3.default)(this.activeTargets_), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+              var resizeObservation = _step.value;
+
+              var rect = getContentRect(resizeObservation.target);
+              resizeObservation.broadcastWidth = rect.width;
+              resizeObservation.broadcastHeight = rect.height;
+              entries.push(ResizeObserverEntry(resizeObservation.target, rect));
+            }
+          } catch (err) {
+            _didIteratorError = true;
+            _iteratorError = err;
+          } finally {
+            try {
+              if (!_iteratorNormalCompletion && _iterator.return) {
+                _iterator.return();
+              }
+            } finally {
+              if (_didIteratorError) {
+                throw _iteratorError;
+              }
+            }
+          }
+
+          this.callback_(entries);
+          this.activeTargets_ = [];
+        }
+      }
+    }, {
+      key: 'observationTargets',
+      get: function get() {
+        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}
+       */
+
+    }, {
+      key: 'activeTargets',
+      get: function get() {
+        return this.activeTargets_;
+      }
+    }]);
+    return ResizeObserver;
+  }();
+
+  //let interval = require('./interval-function');
+
+  /**
+   * Broadcasts Element.resize events
+   * @return {{start: (function()), stop: (function())}}
+   * @constructor
+   */
+
+
+  var ResizeController = function ResizeController() {
+
+    var shouldStop = function shouldStop() {
+      return document.resizeObservers.findIndex(function (resizeObserver) {
+        return resizeObserver.observationTargets.length > 0;
+      }) > -1;
+    };
+
+    var execute = function execute() {
+      //console.log('***** Execute');
+      var _iteratorNormalCompletion2 = true;
+      var _didIteratorError2 = false;
+      var _iteratorError2 = undefined;
+
+      try {
+        for (var _iterator2 = (0, _getIterator3.default)(document.resizeObservers), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+          var resizeObserver = _step2.value;
+
+          resizeObserver.broadcast_();
+        }
+      } catch (err) {
+        _didIteratorError2 = true;
+        _iteratorError2 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion2 && _iterator2.return) {
+            _iterator2.return();
+          }
+        } finally {
+          if (_didIteratorError2) {
+            throw _iteratorError2;
+          }
+        }
+      }
+
+      return shouldStop();
+    };
+
+    var interval = (0, _intervalFunction2.default)(200);
+
+    return {
+      start: function start() {
+        if (!interval.started) {
+          //console.log('***** Start poll');
+          interval.start(execute);
+        }
+      }
+    };
+  };
+
+  window.ResizeObserver = ResizeObserver;
+
+  var resizeController = ResizeController();
+  //console.log('***** ResizeObserver ready');
+})(window, document);
+/**
+ * An API for observing changes to Element’s size.
+ *
+ * @See https://wicg.github.io/ResizeObserver/
+ * @ee https://github.com/pelotoncycle/resize-observer
+ *
+ */
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/string-utils.js b/node_modules/mdl-ext/es/utils/string-utils.js
new file mode 100644
index 0000000..ab0045a
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/string-utils.js
@@ -0,0 +1,114 @@
+'use strict';
+
+/**
+ * @license
+ * Copyright 2016 Leif Olsen. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A javascript utility for conditionally creating a list of strings.
+ * The function takes any number of arguments which can be a string or object.
+ * Inspired by (but not copied from) JedWatson/classnames, https://github.com/JedWatson/classnames
+ *
+ * @param  {*} args the strings and/or objects to
+ * @return {Array} a list of strings
+ * @example
+ * // Returns ['foo', 'bar', 'baz', 'quux']
+ * stringList(', ', 'foo', { bar: true, duck: false }, 'baz', { quux: true });
+ * @example see the tests for more examples
+ */
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.stringList = exports.randomString = exports.joinStrings = undefined;
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var stringList = function stringList() {
+  for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+    args[_key] = arguments[_key];
+  }
+
+  var isString = function isString(str) {
+    return str != null && typeof str === 'string';
+  };
+
+  var flatten = function flatten(list) {
+    return list.reduce(function (a, b) {
+      return a.concat(Array.isArray(b) ? flatten(b) : b);
+    }, []);
+  };
+
+  var objectToStrings = function objectToStrings(arg) {
+    return (0, _keys2.default)(arg).filter(function (key) {
+      return arg[key];
+    }).map(function (key) {
+      return key;
+    });
+  };
+
+  return args.filter(function (arg) {
+    return !!arg;
+  }).map(function (arg) {
+    return isString(arg) ? arg : objectToStrings(arg);
+  }).reduce(function (result, arg) {
+    return result.concat(Array.isArray(arg) ? flatten(arg) : arg);
+  }, []);
+};
+
+/**
+ * A simple javascript utility for conditionally joining strings together.
+ * The function takes a delimiter string and any number of arguments which can be a string or object.
+ *
+ * @param delimiter delimiter to separate joined strings
+ * @param  {*} args the strings and/or objects to join
+ * @return {String} the joined strings
+ * @example
+ * // Returns 'foo, bar, baz, quux'
+ * joinStrings(', ', 'foo', { bar: true, duck: false }, 'baz', { quux: true });
+ * @example see the tests for more examples
+ */
+var joinStrings = function joinStrings() {
+  for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+    args[_key2 - 1] = arguments[_key2];
+  }
+
+  var delimiter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ' ';
+  return stringList.apply(undefined, args).join(delimiter);
+};
+
+/**
+ * Generates a random string with a given length
+ * @param n {Integer} length of generated string
+ * @see http://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript
+ * @return {String} the random string
+ * @example
+ * // Returns e.g. 'pd781w0y'
+ * randomString(8);
+ * @example see the tests for more examples
+ */
+var randomString = function randomString() {
+  var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 12;
+  return Array(n + 1).join((Math.random().toString(36) + '00000000000000000').slice(2, 18)).slice(0, n);
+};
+
+exports.joinStrings = joinStrings;
+exports.randomString = randomString;
+exports.stringList = stringList;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/throttle-function.js b/node_modules/mdl-ext/es/utils/throttle-function.js
new file mode 100644
index 0000000..03a5cea
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/throttle-function.js
@@ -0,0 +1,84 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _apply = require("babel-runtime/core-js/reflect/apply");
+
+var _apply2 = _interopRequireDefault(_apply);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Throttling enforces a maximum number of times a function can be called over time.
+ *
+ * @param callback the function to throttle
+ * @param delay optional delay, default to 1000/60ms
+ * @param context optional context of this, default to global window
+ * @returns {Function} reference to immediate and cancel functions
+ * @see https://developer.mozilla.org/en-US/docs/Web/Events/resize#Example
+ * @see https://gist.github.com/yoavniran/d1d33f278bb7744d55c3
+ * @see https://github.com/pelotoncycle/frame-throttle
+ * @see https://github.com/jeromedecoster/raf-funcs
+ */
+var MIN_DELAY = 1000 / 60;
+
+var throttleFunction = function throttleFunction(callback) {
+  var delay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : MIN_DELAY;
+  var context = arguments[2];
+
+
+  if (delay < MIN_DELAY) {
+    delay = MIN_DELAY;
+  }
+
+  if (!context) {
+    context = undefined || window;
+  }
+
+  var next = null;
+  var start = 0;
+
+  return function () {
+    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+      args[_key] = arguments[_key];
+    }
+
+    var _cancel = function _cancel() {
+      if (next !== null) {
+        window.cancelAnimationFrame(next);
+        next = null;
+      }
+    };
+
+    var execute = function execute() {
+      _cancel();
+      return (0, _apply2.default)(callback, context, args);
+    };
+
+    var later = function later() {
+      if (delay - (Date.now() - start) <= 0) {
+        return execute();
+      }
+      next = window.requestAnimationFrame(later);
+    };
+
+    if (next === null) {
+      start = Date.now();
+      next = window.requestAnimationFrame(later);
+    }
+
+    return {
+      cancel: function cancel() {
+        return _cancel();
+      },
+      immediate: function immediate() {
+        return execute();
+      }
+    };
+  };
+};
+
+exports.default = throttleFunction;
+module.exports = exports["default"];
\ No newline at end of file