Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/node_modules/mdl-ext/src/collapsible/collapsible.js b/node_modules/mdl-ext/src/collapsible/collapsible.js
new file mode 100644
index 0000000..862fffb
--- /dev/null
+++ b/node_modules/mdl-ext/src/collapsible/collapsible.js
@@ -0,0 +1,437 @@
+/**
+ * @license
+ * Copyright 2016-2017 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.
+ *
+ * This code is built with Google Material Design Lite,
+ * which is Licensed under the Apache License, Version 2.0
+ */
+
+
+/**
+ * A collapsible is a component to mark expandable and collapsible regions.
+ * The component use the aria-expanded state to indicate whether regions of
+ * the content are collapsible, and to expose whether a region is currently
+ * expanded or collapsed.
+ * @see https://www.w3.org/WAI/GL/wiki/Using_the_WAI-ARIA_aria-expanded_state_to_mark_expandable_and_collapsible_regions
+ */
+
+import {
+  IS_UPGRADED,
+  VK_SPACE,
+  VK_ENTER,
+} from '../utils/constants';
+
+import { randomString } from '../utils/string-utils';
+import { getParentElements, isFocusable } from '../utils/dom-utils';
+
+const JS_COLLAPSIBLE = 'mdlext-js-collapsible';
+const COLLAPSIBLE_CONTROL_CLASS = 'mdlext-collapsible';
+const COLLAPSIBLE_GROUP_CLASS = 'mdlext-collapsible-group';
+const COLLAPSIBLE_REGION_CLASS = 'mdlext-collapsible-region';
+
+/**
+ * The collapsible component
+ */
+
+class Collapsible {
+  element_ = null;
+  controlElement_ = null;
+
+  /**
+   * @constructor
+   * @param {HTMLElement} element The element that this component is connected to.
+   */
+  constructor(element) {
+    this.element_ = element;
+    this.init();
+  }
+
+  keyDownHandler = event => {
+    if (event.keyCode === VK_ENTER || event.keyCode === VK_SPACE) {
+      event.preventDefault();
+
+      // Trigger click
+      (event.target || this.controlElement).dispatchEvent(
+        new MouseEvent('click', {
+          bubbles: true,
+          cancelable: true,
+          view: window
+        })
+      );
+    }
+  };
+
+  clickHandler = event => {
+    if(!this.isDisabled) {
+      if(event.target !== this.controlElement) {
+        // Do not toggle if a focusable element inside the control element triggered the event
+        const p = getParentElements(event.target, this.controlElement);
+        p.push(event.target);
+        if(p.find( el => isFocusable(el))) {
+          return;
+        }
+      }
+      this.toggle();
+    }
+  };
+
+  get element() {
+    return this.element_;
+  }
+
+  get controlElement() {
+    return this.controlElement_;
+  }
+
+  get isDisabled() {
+    return (this.controlElement.hasAttribute('disabled') &&
+      this.controlElement.getAttribute('disabled').toLowerCase() !== 'false') ||
+      (this.controlElement.hasAttribute('aria-disabled') &&
+      this.controlElement.getAttribute('aria-disabled').toLowerCase() !== 'false');
+  }
+
+  get isExpanded() {
+    return this.controlElement.hasAttribute('aria-expanded') &&
+      this.controlElement.getAttribute('aria-expanded').toLowerCase() === 'true';
+  }
+
+  get regionIds() {
+    return this.controlElement.hasAttribute('aria-controls')
+      ? this.controlElement.getAttribute('aria-controls').split(' ')
+      : [];
+  }
+
+  get regionElements() {
+    return this.regionIds
+      .map(id => document.querySelector(`#${id}`))
+      .filter( el => el != null);
+  }
+
+  collapse() {
+    if(!this.isDisabled && this.isExpanded) {
+      if(this.dispatchToggleEvent('collapse')) {
+        this.controlElement.setAttribute('aria-expanded', 'false');
+        const regions = this.regionElements.slice(0);
+        for (let i = regions.length - 1; i >= 0; --i) {
+          regions[i].setAttribute('hidden', '');
+        }
+      }
+    }
+  }
+
+  expand() {
+    if(!this.isDisabled && !this.isExpanded) {
+      if(this.dispatchToggleEvent('expand')) {
+        this.controlElement.setAttribute('aria-expanded', 'true');
+        this.regionElements.forEach(region => region.removeAttribute('hidden'));
+      }
+    }
+  }
+
+  toggle() {
+    if (this.isExpanded) {
+      this.collapse();
+    }
+    else {
+      this.expand();
+    }
+  }
+
+  dispatchToggleEvent(action) {
+    return this.element.dispatchEvent(
+      new CustomEvent('toggle', {
+        bubbles: true,
+        cancelable: true,
+        detail: {
+          action: action
+        }
+      })
+    );
+  }
+
+  disableToggle() {
+    this.controlElement.setAttribute('aria-disabled', true);
+  }
+
+  enableToggle() {
+    this.controlElement.removeAttribute('aria-disabled');
+  }
+
+  addRegionId(regionId) {
+    const ids = this.regionIds;
+    if(!ids.find(id => regionId === id)) {
+      ids.push(regionId);
+      this.controlElement.setAttribute('aria-controls', ids.join(' '));
+    }
+  }
+
+  addRegionElement(region) {
+    if(!(region.classList.contains(COLLAPSIBLE_GROUP_CLASS) ||
+      region.classList.contains(COLLAPSIBLE_REGION_CLASS))) {
+      region.classList.add(COLLAPSIBLE_GROUP_CLASS);
+    }
+
+    if(!region.hasAttribute('role')) {
+      const role = region.classList.contains(COLLAPSIBLE_GROUP_CLASS) ? 'group' : 'region';
+      region.setAttribute('role', role);
+    }
+
+    if(!region.hasAttribute('id')) {
+      region.id = `${region.getAttribute('role')}-${randomString()}`;
+    }
+
+    if(this.isExpanded) {
+      region.removeAttribute('hidden');
+    }
+    else {
+      region.setAttribute('hidden', '');
+    }
+    this.addRegionId(region.id);
+  }
+
+  removeRegionElement(region) {
+    if(region && region.id) {
+      const ids = this.regionIds.filter(id => id === region.id);
+      this.controlElement.setAttribute('aria-controls', ids.join(' '));
+    }
+  }
+
+  removeListeners() {
+    this.controlElement.removeEventListener('keydown', this.keyDownHandler);
+    this.controlElement.removeEventListener('click', this.clickHandler);
+  }
+
+  init() {
+    const initControl = () => {
+      // Find the button element
+      this.controlElement_ = this.element.querySelector(`.${COLLAPSIBLE_CONTROL_CLASS}`) || this.element;
+
+      // Add "aria-expanded" attribute if not present
+      if(!this.controlElement.hasAttribute('aria-expanded')) {
+        this.controlElement.setAttribute('aria-expanded', 'false');
+      }
+
+      // Add role=button if control != <button>
+      if(this.controlElement.nodeName.toLowerCase() !== 'button') {
+        this.controlElement.setAttribute('role', 'button');
+      }
+
+      // Add tabindex
+      if(!isFocusable(this.controlElement) && !this.controlElement.hasAttribute('tabindex')) {
+        this.controlElement.setAttribute('tabindex', '0');
+      }
+    };
+
+    const initRegions = () => {
+      let regions = [];
+      if(!this.controlElement.hasAttribute('aria-controls')) {
+        // Add siblings as collapsible region(s)
+        let r = this.element.nextElementSibling;
+        while(r) {
+          if(r.classList.contains(COLLAPSIBLE_GROUP_CLASS) ||
+            r.classList.contains(COLLAPSIBLE_REGION_CLASS)) {
+            regions.push(r);
+          }
+          else if(r.classList.contains(JS_COLLAPSIBLE)) {
+            // A new collapsible component
+            break;
+          }
+          r = r.nextElementSibling;
+        }
+      }
+      else {
+        regions = this.regionElements;
+      }
+      regions.forEach(region => this.addRegionElement(region));
+    };
+
+    const addListeners = () => {
+      this.controlElement.addEventListener('keydown', this.keyDownHandler);
+      this.controlElement.addEventListener('click', this.clickHandler);
+    };
+
+    initControl();
+    initRegions();
+    this.removeListeners();
+    addListeners();
+  }
+
+  downgrade() {
+    this.removeListeners();
+  }
+
+}
+
+(function() {
+  'use strict';
+
+  /**
+   * @constructor
+   * @param {HTMLElement} element The element that will be upgraded.
+   */
+  const MaterialExtCollapsible = function MaterialExtCollapsible(element) {
+    this.element_ = element;
+    this.collapsible = null;
+
+    // Initialize instance.
+    this.init();
+  };
+  window['MaterialExtCollapsible'] = MaterialExtCollapsible;
+
+  /**
+   * Initialize component
+   */
+  MaterialExtCollapsible.prototype.init = function() {
+    if (this.element_) {
+      this.collapsible = new Collapsible(this.element_);
+      this.element_.classList.add(IS_UPGRADED);
+
+      // Listen to 'mdl-componentdowngraded' event
+      this.element_.addEventListener('mdl-componentdowngraded', this.mdlDowngrade_.bind(this));
+    }
+  };
+
+  /*
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   */
+  MaterialExtCollapsible.prototype.mdlDowngrade_ = function() {
+    this.collapsible.downgrade();
+  };
+
+
+  // Public methods.
+
+  /**
+   * Get control element.
+   * @return {HTMLElement} element The element that controls the collapsible region.
+   * @public
+   */
+  MaterialExtCollapsible.prototype.getControlElement = function() {
+    return this.collapsible.controlElement;
+  };
+  MaterialExtCollapsible.prototype['getControlElement'] = MaterialExtCollapsible.prototype.getControlElement;
+
+  /**
+   * Get region elements controlled by this collapsible
+   * @returns {Array<HTMLElement>} the collapsible region elements
+   * @public
+   */
+  MaterialExtCollapsible.prototype.getRegionElements = function() {
+    return this.collapsible.regionElements;
+  };
+  MaterialExtCollapsible.prototype['getRegionElements'] = MaterialExtCollapsible.prototype.getRegionElements;
+
+  /**
+   * Add region elements.
+   * @param {Array<HTMLElement>} elements The element that will be upgraded.
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.addRegionElements = function(...elements) {
+    elements.forEach(element => this.collapsible.addRegionElement(element));
+  };
+  MaterialExtCollapsible.prototype['addRegionElements'] = MaterialExtCollapsible.prototype.addRegionElements;
+
+  /**
+   * Remove collapsible region(s) from component.
+   * Note: This operation does not delete the element from the DOM tree.
+   * @param {Array<HTMLElement>} elements The element that will be upgraded.
+   * @public
+   */
+  MaterialExtCollapsible.prototype.removeRegionElements = function(...elements) {
+    elements.forEach(element => this.collapsible.removeRegionElement(element));
+  };
+  MaterialExtCollapsible.prototype['removeRegionElements'] = MaterialExtCollapsible.prototype.removeRegionElements;
+
+  /**
+   * Expand collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.expand = function() {
+    this.collapsible.expand();
+  };
+  MaterialExtCollapsible.prototype['expand'] = MaterialExtCollapsible.prototype.expand;
+
+  /**
+   * Collapse collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.collapse = function() {
+    this.collapsible.collapse();
+  };
+  MaterialExtCollapsible.prototype['collapse'] = MaterialExtCollapsible.prototype.collapse;
+
+  /**
+   * Toggle collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.toggle = function() {
+    this.collapsible.toggle();
+  };
+  MaterialExtCollapsible.prototype['toggle'] = MaterialExtCollapsible.prototype.toggle;
+
+  /**
+   * Check whether component has aria-expanded state true
+   * @return {Boolean} true if aria-expanded="true", otherwise false
+   */
+  MaterialExtCollapsible.prototype.isExpanded = function() {
+    return this.collapsible.isExpanded;
+  };
+  MaterialExtCollapsible.prototype['isExpanded'] = MaterialExtCollapsible.prototype.isExpanded;
+
+  /**
+   * Check whether component has aria-disabled state set to true
+   * @return {Boolean} true if aria-disabled="true", otherwise false
+   */
+  MaterialExtCollapsible.prototype.isDisabled = function() {
+    return this.collapsible.isDisabled;
+  };
+  MaterialExtCollapsible.prototype['isDisabled'] = MaterialExtCollapsible.prototype.isDisabled;
+
+  /**
+   * Disables toggling of collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.disableToggle = function() {
+    this.collapsible.disableToggle();
+  };
+  MaterialExtCollapsible.prototype['disableToggle'] = MaterialExtCollapsible.prototype.disableToggle;
+
+  /**
+   * Enables toggling of collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.enableToggle = function() {
+    this.collapsible.enableToggle();
+  };
+  MaterialExtCollapsible.prototype['enableToggle'] = MaterialExtCollapsible.prototype.enableToggle;
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  componentHandler.register({
+    constructor: MaterialExtCollapsible,
+    classAsString: 'MaterialExtCollapsible',
+    cssClass: JS_COLLAPSIBLE,
+    widget: true
+  });
+
+})();