Project import generated by Copybara.
GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/node_modules/mdl-ext/src/utils/constants.js b/node_modules/mdl-ext/src/utils/constants.js
new file mode 100644
index 0000000..9e63ea7
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/constants.js
@@ -0,0 +1,62 @@
+'use strict';
+
+const VK_TAB = 9;
+const VK_ENTER = 13;
+const VK_ESC = 27;
+const VK_SPACE = 32;
+const VK_PAGE_UP = 33;
+const VK_PAGE_DOWN = 34;
+const VK_END = 35;
+const VK_HOME = 36;
+const VK_ARROW_LEFT = 37;
+const VK_ARROW_UP = 38;
+const VK_ARROW_RIGHT = 39;
+const VK_ARROW_DOWN = 40;
+
+const ARIA_EXPANDED = 'aria-expanded';
+const ARIA_HIDDEN = 'aria-hidden';
+const ARIA_MULTISELECTABLE = 'aria-multiselectable';
+const ARIA_SELECTED = 'aria-selected';
+
+const IS_DIRTY = 'is-dirty';
+const IS_DISABLED = 'is-disabled';
+const IS_EXPANDED = 'is-expanded';
+const IS_FOCUSED = 'is-focused';
+const IS_INVALID = 'is-invalid';
+const IS_UPGRADED = 'is-upgraded';
+const DATA_UPGRADED = 'data-upgraded';
+
+const MDL_RIPPLE = 'mdl-ripple';
+const MDL_RIPPLE_COMPONENT = 'MaterialRipple';
+const MDL_RIPPLE_EFFECT = 'mdl-js-ripple-effect';
+const MDL_RIPPLE_EFFECT_IGNORE_EVENTS = 'mdl-js-ripple-effect--ignore-events';
+
+export {
+ VK_TAB,
+ VK_ENTER,
+ VK_ESC,
+ VK_SPACE,
+ VK_PAGE_UP,
+ VK_PAGE_DOWN ,
+ VK_END,
+ VK_HOME,
+ VK_ARROW_LEFT,
+ VK_ARROW_UP,
+ VK_ARROW_RIGHT,
+ VK_ARROW_DOWN ,
+ ARIA_EXPANDED,
+ ARIA_HIDDEN,
+ ARIA_MULTISELECTABLE,
+ ARIA_SELECTED,
+ IS_DIRTY,
+ IS_DISABLED,
+ IS_EXPANDED,
+ IS_FOCUSED,
+ IS_INVALID,
+ IS_UPGRADED,
+ DATA_UPGRADED ,
+ MDL_RIPPLE,
+ MDL_RIPPLE_COMPONENT,
+ MDL_RIPPLE_EFFECT,
+ MDL_RIPPLE_EFFECT_IGNORE_EVENTS
+};
diff --git a/node_modules/mdl-ext/src/utils/debounce-function.js b/node_modules/mdl-ext/src/utils/debounce-function.js
new file mode 100644
index 0000000..68ce449
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/debounce-function.js
@@ -0,0 +1,66 @@
+/**
+ * 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
+ */
+const MIN_THRESHOLD = 1000/60;
+
+const debounceFunction = function(callback, threshold=250, context) {
+
+ if(threshold < MIN_THRESHOLD) {
+ threshold = MIN_THRESHOLD;
+ }
+
+ if (!context) {
+ context = this || window;
+ }
+
+ let next = null;
+ let start = 0;
+
+ return function (...args) {
+
+ const cancel = () => {
+ if(next) {
+ window.cancelAnimationFrame(next);
+ next = null;
+ }
+ };
+
+ const execute = () => {
+ cancel();
+ return Reflect.apply(callback, context, args);
+ };
+
+ const later = () => {
+ if (threshold - (Date.now() - start) <= 0) {
+ return execute();
+ }
+ next = window.requestAnimationFrame(later);
+ };
+
+ cancel();
+ start = Date.now();
+ next = window.requestAnimationFrame(later);
+
+ return {
+ cancel: () => cancel(),
+ immediate: () => execute()
+ };
+ };
+};
+
+export default debounceFunction;
diff --git a/node_modules/mdl-ext/src/utils/dom-utils.js b/node_modules/mdl-ext/src/utils/dom-utils.js
new file mode 100644
index 0000000..5aa2658
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/dom-utils.js
@@ -0,0 +1,418 @@
+/**
+ * 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
+ */
+const removeChildElements = (element, forceReflow = true) => {
+
+ // See: http://jsperf.com/empty-an-element/16
+ while (element.lastChild) {
+ element.removeChild(element.lastChild);
+ }
+ if(forceReflow) {
+ // See: http://jsperf.com/force-reflow
+ const 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
+ */
+const 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}}
+ */
+const 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
+ */
+const isRectInsideWindowViewport = ({ top, left, bottom, right }) => {
+ const { viewportWidth, viewportHeight } = getWindowViewport();
+ 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}
+ */
+const getScrollParents = el => {
+ const 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;
+ }
+ }
+ */
+
+ let element = el.parentNode;
+ while (element) {
+ const 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
+ */
+const getParentElements = (from, to) => {
+ const result = [];
+ let 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
+ *
+ */
+const tether = (controlledBy, element) => {
+ const controlRect = controlledBy.getBoundingClientRect();
+
+ // 1. will element height fit inside window viewport?
+ const { viewportWidth, viewportHeight } = getWindowViewport();
+
+ 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`;
+ }
+
+ const elementRect = element.getBoundingClientRect();
+
+ // element to control distance
+ const dy = controlRect.top - elementRect.top;
+ const dx = controlRect.left - elementRect.left;
+
+ // element rect, window coordinates relative to top,left of control
+ const top = elementRect.top + dy;
+ const left = elementRect.left + dx;
+ const bottom = top + elementRect.height;
+ const right = left + elementRect.width;
+
+ // Position relative to control
+ let ddy = dy;
+ let 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
+ const 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
+ */
+const 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')) {
+ const tabindex = element.getAttribute('tabindex');
+ if (!Number.isNaN(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
+ const selector = /input|select|textarea|button|details/i;
+ const 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
+ const 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 };
+};
+*/
+
+export {
+ getWindowViewport,
+ getParentElements,
+ getScrollParents,
+ isFocusable,
+ isRectInsideWindowViewport,
+ moveElements,
+ removeChildElements,
+ tether,
+};
+
diff --git a/node_modules/mdl-ext/src/utils/easing.js b/node_modules/mdl-ext/src/utils/easing.js
new file mode 100644
index 0000000..fea208f
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/easing.js
@@ -0,0 +1,18 @@
+'use strict';
+
+// See: http://robertpenner.com/easing/
+
+const 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;
+};
+
+const inOutQuintic = (t, b, c, d) => {
+ const ts = (t/=d)*t;
+ const tc = ts*t;
+ return b+c*(6*tc*ts + -15*ts*ts + 10*tc);
+};
+
+export { easeInOutQuad, inOutQuintic };
diff --git a/node_modules/mdl-ext/src/utils/full-throttle.js b/node_modules/mdl-ext/src/utils/full-throttle.js
new file mode 100644
index 0000000..7cac3d7
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/full-throttle.js
@@ -0,0 +1,35 @@
+/**
+ * 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(...[*])}
+ */
+const fullThrottle = (callback, context) => {
+
+ if (!context) {
+ context = this || window;
+ }
+
+ let throttling = false;
+
+ return (...args) => {
+ if(!throttling) {
+ throttling = true;
+ window.requestAnimationFrame( () => {
+ throttling = false;
+ return Reflect.apply(callback, context, args);
+ });
+ }
+ };
+};
+
+export default fullThrottle;
diff --git a/node_modules/mdl-ext/src/utils/index.js b/node_modules/mdl-ext/src/utils/index.js
new file mode 100644
index 0000000..c04972d
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/index.js
@@ -0,0 +1,7 @@
+import './constants';
+import './dom-utils';
+import './string-utils';
+import './json-utils';
+import './full-throttle';
+import './easing';
+import './interval-function';
diff --git a/node_modules/mdl-ext/src/utils/interval-function.js b/node_modules/mdl-ext/src/utils/interval-function.js
new file mode 100644
index 0000000..4c344e3
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/interval-function.js
@@ -0,0 +1,83 @@
+const 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
+ */
+
+const intervalFunction = ( interval = MIN_INERVAL ) => {
+
+ let lapse = interval < MIN_INERVAL ? MIN_INERVAL : interval;
+ let cb = undefined;
+ let next = null;
+ let timeElapsed = 0;
+
+ const execute = () => {
+ const f = cb(timeElapsed);
+ if (!f) {
+ cancel();
+ }
+ };
+
+ const cancel = () => {
+ if(next) {
+ window.cancelAnimationFrame(next);
+ }
+ next = null;
+ timeElapsed = 0;
+ };
+
+ const start = () => {
+ let timeStart = Date.now();
+
+ const loop = now => {
+ if (next) {
+ next = window.requestAnimationFrame( () => 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(callback) {
+ if(typeof callback !== 'function') {
+ throw new TypeError('callback parameter must be a function');
+ }
+ cb = callback;
+ start();
+ },
+ immediate() {
+ if(!cb) {
+ throw new ReferenceError('callback parameter is not defined. Call start before immediate.');
+ }
+ execute();
+ },
+ stop: () => cancel(),
+ };
+};
+
+export default intervalFunction;
diff --git a/node_modules/mdl-ext/src/utils/json-utils.js b/node_modules/mdl-ext/src/utils/json-utils.js
new file mode 100644
index 0000000..c6f6c77
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/json-utils.js
@@ -0,0 +1,18 @@
+'use strict';
+
+/**
+ * Converts a JSON string to object
+ * @param jsonString
+ * @param source
+ */
+const jsonStringToObject = (jsonString, source = {} ) => {
+ const s = jsonString.replace(/'/g, '"');
+ try {
+ return Object.assign(source, JSON.parse(s));
+ }
+ catch (e) {
+ throw new Error(`Failed to parse json string: ${s}. Error: ${e.message}`);
+ }
+};
+
+export { jsonStringToObject };
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);
diff --git a/node_modules/mdl-ext/src/utils/snippets/resize-observer.html b/node_modules/mdl-ext/src/utils/snippets/resize-observer.html
new file mode 100644
index 0000000..5d956c2
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/snippets/resize-observer.html
@@ -0,0 +1,221 @@
+<!-- See: https://github.com/WICG/ResizeObserver/blob/master/demo.html -->
+
+<style>
+ .resize {
+ border: 2em solid rgba(0,255,0, 0.5);
+ background-color: #DDD;
+ width: 90%; //300.49px;
+ height: 200.5px;
+ overflow: hidden;
+ position:relative;
+ display:inline-block;
+ }
+ .yellowBorder {
+ border-width: 1px;
+ border-style: solid;
+ }
+
+ .maptiles {
+ overflow: hidden;
+ }
+
+ .maptiles::after {
+ content: "map tiles";
+ position: relative;
+ top: 20px;
+ }
+
+ .elipse {
+ border: 20px solid rgba(100,255,100, 0.5);
+ }
+ .elipse::after {
+ content: "canvas";
+ position: relative;
+ top: -100px;
+ }
+ .domMunch {
+ background-color: orange;
+ }
+ .domMunch > div {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ border: 1px solid black;
+ }
+ .domMunch::after {
+ content: "domMunch";
+ position: relative;
+ top: 20px;
+ }
+
+ .toolbar {
+ margin: 16px 0;
+ position: relative;
+ display: flex;
+ align-items: center;
+ }
+
+ .toolbar > * {
+ margin-right: 8px;
+ min-width: 110px;
+ width: auto;
+ }
+
+ .toolbar > span {
+ flex-grow: 1;
+ text-align: right;
+ font-weight: 600;
+ font-size: 1.2em;
+ }
+</style>
+
+<h1 style="display:none">ResizeObserver unexpected error</h1>
+<pre id="log" style="display:none">log:</pre>
+
+<section class="toolbar">
+ <label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
+ <input id="cb-observe" type="checkbox" class="mdl-checkbox__input" checked>
+ <span class="mdl-checkbox__label">Observe</span>
+ </label>
+
+ <div id="input-inc-container" class="mdl-textfield mdl-textfield--floating-label mdl-js-textfield">
+ <input id="input-inc" class="mdl-textfield__input" type="number" min="1" max="40" value="10">
+ <label class="mdl-textfield__label">Size increment</label>
+ </div>
+
+ <button id="btn-grow" class="cmd-button mdl-button mdl-js-button mdl-button--raised">Grow</button>
+ <button id="btn-shrink" class="cmd-button mdl-button mdl-js-button mdl-button--raised">Shrink</button>
+ <button id="btn-delete" class="cmd-button mdl-button mdl-js-button mdl-button--raised">Delete observed element</button>
+
+ <span id="observe-notification">
+ </span>
+</section>
+
+
+<div id="observed-element" class="resize maptiles" ></div>
+
+<script>
+ 'use strict';
+
+ (function() {
+ 'use strict';
+ document.addEventListener('DOMContentLoaded', function() {
+
+ if (!window.ResizeObserver) {
+ throw new Error('no window.ResizeObserver');
+ }
+
+ function log(msg) {
+ var e = document.querySelector('#log');
+ e.textContent = e.innerText + '\n' + msg;
+ }
+
+ function nextColor() {
+ return '#'+Math.random().toString(16).substr(-6);
+ }
+
+ function grow() {
+ var inc = parseInt(document.querySelector('#input-inc').value);
+ var elements = document.querySelectorAll('.resize');
+ for (var i = 0; i < elements.length; i++) {
+ var el = elements[i];
+ var s = window.getComputedStyle(el);
+ var w = parseFloat(s.width);
+ var h = parseFloat(s.height);
+ el.style.width = (w + inc) + 'px';
+ el.style.height = (h + inc * 2 / 3) + 'px';
+ }
+ }
+
+ function shrink() {
+ var dec = parseInt(document.querySelector('#input-inc').value);
+ var elements = document.querySelectorAll('.resize');
+ for (var i = 0; i < elements.length; i++) {
+ var el = elements[i];
+ var s = window.getComputedStyle(el);
+ var w = parseFloat(s.width);
+ var h = parseFloat(s.height);
+ el.style.width = (w - dec) + 'px';
+ el.style.height = (h - dec * 2 / 3) + 'px';
+ }
+ }
+
+
+ function initResizeHandlers() {
+ var elements = document.querySelectorAll('.maptiles');
+ for (var i = 0; i < elements.length; i++) {
+ elements[i].onresize = function() { this.style.borderColor = nextColor() };
+ }
+ }
+
+ function displayNotification(value) {
+ var notification = document.querySelector("#observe-notification");
+ notification.style.color = nextColor();
+ if(Number.isInteger(value)) {
+ notification.innerHTML = '<span>Observed: ' + value + ' element' + (value > 1 ? 's' : '') + '</span>';
+ }
+ else {
+ notification.innerHTML = '<span>Observer: ' + value + '</span>';
+ }
+ }
+
+ //
+ // Init
+ //
+ window.addEventListener('error', function (e) {
+ var error = e.error;
+ console.log(error);
+ log(error);
+ });
+
+
+ var ro = new ResizeObserver( function(entries) {
+
+ displayNotification(entries.length);
+
+ for (var i = 0; i < entries.length; i++) {
+ var entry = entries[i];
+ if (!entry.target || !entry.target.parentNode) {
+ throw new Error("detected resize on orphan element");
+ }
+ if (entry.target.onresize) {
+ entry.target.onresize(entry);
+ }
+ }
+ });
+
+ function toggleObserve() {
+ var isObserve = document.querySelector('#cb-observe').checked;
+ displayNotification(isObserve ? 'on' : 'off');
+
+ var elements = document.querySelectorAll('.resize');
+ if (isObserve) {
+ for (var i = 0; i < elements.length; i++) {
+ ro.observe(elements[i]);
+ }
+ }
+ else {
+ for (var i = 0; i < elements.length; i++) {
+ ro.unobserve(elements[i]);
+ }
+ }
+ }
+
+ function deleteObservedElement() {
+ var el = document.querySelector('#observed-element');
+ el.parentNode.removeChild(el);
+ }
+
+
+ document.querySelector('#cb-observe').addEventListener('click', function() { toggleObserve() });
+ document.querySelector('#btn-grow').addEventListener('click', function() { grow() });
+ document.querySelector('#btn-shrink').addEventListener('click', function() { shrink() });
+ document.querySelector('#btn-delete').addEventListener('click', function() { deleteObservedElement() });
+
+ // Start
+ initResizeHandlers()
+ toggleObserve();
+ });
+
+ }());
+</script>
diff --git a/node_modules/mdl-ext/src/utils/string-utils.js b/node_modules/mdl-ext/src/utils/string-utils.js
new file mode 100644
index 0000000..38830cc
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/string-utils.js
@@ -0,0 +1,76 @@
+'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
+ */
+const stringList = (...args) => {
+
+ const isString = str => str != null && typeof str === 'string';
+
+ const flatten = list => list.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
+
+ const objectToStrings = arg =>
+ Object.keys(arg)
+ .filter(key => arg[key])
+ .map(key => key);
+
+ return args
+ .filter(arg => !!arg)
+ .map(arg => isString(arg) ? arg : objectToStrings(arg))
+ .reduce((result, arg) => 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
+ */
+const joinStrings = (delimiter = ' ', ...args) => stringList(...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
+ */
+const randomString = ( n=12 ) => Array( n+1 ).join((`${Math.random().toString(36)}00000000000000000`).slice(2, 18)).slice(0, n);
+
+export { joinStrings, randomString, stringList };
+
diff --git a/node_modules/mdl-ext/src/utils/throttle-function.js b/node_modules/mdl-ext/src/utils/throttle-function.js
new file mode 100644
index 0000000..f236ec3
--- /dev/null
+++ b/node_modules/mdl-ext/src/utils/throttle-function.js
@@ -0,0 +1,61 @@
+/**
+ * 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
+ */
+const MIN_DELAY = 1000/60;
+
+const throttleFunction = (callback, delay=MIN_DELAY, context) => {
+
+ if(delay < MIN_DELAY) {
+ delay = MIN_DELAY;
+ }
+
+ if (!context) {
+ context = this || window;
+ }
+
+ let next = null;
+ let start = 0;
+
+ return (...args) => {
+
+ const cancel = () => {
+ if(next !== null) {
+ window.cancelAnimationFrame(next);
+ next = null;
+ }
+ };
+
+ const execute = () => {
+ cancel();
+ return Reflect.apply(callback, context, args);
+ };
+
+ const 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: () => cancel(),
+ immediate: () => execute()
+ };
+ };
+};
+
+export default throttleFunction;