Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/node_modules/mdl-ext/src/lightboard/lightboard.js b/node_modules/mdl-ext/src/lightboard/lightboard.js
new file mode 100644
index 0000000..9a49559
--- /dev/null
+++ b/node_modules/mdl-ext/src/lightboard/lightboard.js
@@ -0,0 +1,332 @@
+/**
+ * @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.
+ *
+ * This code is built with Google Material Design Lite,
+ * which is Licensed under the Apache License, Version 2.0
+ */
+
+/**
+ * A lightboard is a translucent surface illuminated from behind, used for situations
+ * where a shape laid upon the surface needs to be seen with high contrast. In the "old days" of photography
+ * photograpers used a lightboard to get a quick view of their slides. The goal is to create a responsive lightbox
+ * design, based on flex layout, similar to what is used in Adobe LightRoom to browse images.
+ */
+
+import {
+  VK_ENTER,
+  VK_SPACE,
+  VK_END,
+  VK_HOME,
+  VK_ARROW_LEFT,
+  VK_ARROW_UP,
+  VK_ARROW_RIGHT,
+  VK_ARROW_DOWN,
+  IS_UPGRADED,
+  MDL_RIPPLE,
+  MDL_RIPPLE_COMPONENT,
+  MDL_RIPPLE_EFFECT,
+  MDL_RIPPLE_EFFECT_IGNORE_EVENTS
+} from '../utils/constants';
+
+const MDL_RIPPLE_CONTAINER = 'mdlext-lightboard__slide__ripple-container';
+
+(function() {
+  'use strict';
+
+  //const LIGHTBOARD = 'mdlext-lightboard';
+  const LIGHTBOARD_ROLE = 'grid';
+  const SLIDE = 'mdlext-lightboard__slide';
+  const SLIDE_ROLE  = 'gridcell';
+  const SLIDE_TABSTOP = 'mdlext-lightboard__slide__frame';
+  /**
+   * @constructor
+   * @param {Element} element The element that will be upgraded.
+   */
+  const MaterialExtLightboard = function MaterialExtLightboard(element) {
+    // Stores the element.
+    this.element_ = element;
+
+    // Initialize instance.
+    this.init();
+  };
+  window['MaterialExtLightboard'] = MaterialExtLightboard;
+
+
+  // Helpers
+  const getSlide = element => {
+    return element ? element.closest(`.${SLIDE}`) : null;
+  };
+
+
+
+  // Private methods.
+
+  /**
+   * Select a slide, i.e. set aria-selected="true"
+   * @param element
+   * @private
+   */
+  MaterialExtLightboard.prototype.selectSlide_ = function(element) {
+    const slide = getSlide(element);
+    if( slide && !slide.hasAttribute('aria-selected') ) {
+      [...this.element_.querySelectorAll(`.${SLIDE}[aria-selected="true"]`)]
+        .forEach(selectedSlide => selectedSlide.removeAttribute('aria-selected'));
+
+      slide.setAttribute('aria-selected', 'true');
+    }
+  };
+
+
+  /**
+   * Dispatch select event
+   * @param {Element} slide The slide that caused the event
+   * @private
+   */
+  MaterialExtLightboard.prototype.dispatchSelectEvent_ = function ( slide ) {
+    this.element_.dispatchEvent(
+      new CustomEvent('select', {
+        bubbles: true,
+        cancelable: true,
+        detail: { source: slide }
+      })
+    );
+  };
+
+  /**
+   * Handles custom command event, 'first', 'next', 'prev', 'last', 'select' or upgrade
+   * @param event. A custom event
+   * @private
+   */
+  MaterialExtLightboard.prototype.commandHandler_ = function( event ) {
+    event.preventDefault();
+    event.stopPropagation();
+
+    if(event && event.detail) {
+      this.command(event.detail);
+    }
+  };
+
+
+  // Public methods
+
+  /**
+   * Initialize lightboard slides
+   * @public
+   */
+  MaterialExtLightboard.prototype.upgradeSlides = function() {
+
+    const addRipple = slide => {
+      // Use slide frame as ripple container
+      if(!slide.querySelector(`.${MDL_RIPPLE_CONTAINER}`)) {
+        const a = slide.querySelector(`.${SLIDE_TABSTOP}`);
+        if(a) {
+          const rippleContainer = a;
+          rippleContainer.classList.add(MDL_RIPPLE_CONTAINER);
+          rippleContainer.classList.add(MDL_RIPPLE_EFFECT);
+          const ripple = document.createElement('span');
+          ripple.classList.add(MDL_RIPPLE);
+          rippleContainer.appendChild(ripple);
+          componentHandler.upgradeElement(rippleContainer, MDL_RIPPLE_COMPONENT);
+        }
+      }
+    };
+
+    const hasRippleEffect = this.element_.classList.contains(MDL_RIPPLE_EFFECT);
+
+    [...this.element_.querySelectorAll(`.${SLIDE}`)].forEach( slide => {
+
+      slide.setAttribute('role', SLIDE_ROLE);
+
+      if(!slide.querySelector('a')) {
+        slide.setAttribute('tabindex', '0');
+      }
+      if(hasRippleEffect) {
+        addRipple(slide);
+      }
+    });
+  };
+  MaterialExtLightboard.prototype['upgradeSlides'] = MaterialExtLightboard.prototype.upgradeSlides;
+
+
+  /**
+   * Execute command
+   * @param detail
+   * @public
+   */
+  MaterialExtLightboard.prototype.command = function( detail ) {
+
+    const firstSlide = () => {
+      return this.element_.querySelector(`.${SLIDE}:first-child`);
+    };
+
+    const lastSlide = () => {
+      return this.element_.querySelector(`.${SLIDE}:last-child`);
+    };
+
+    const nextSlide = () => {
+      const slide = this.element_.querySelector(`.${SLIDE}[aria-selected="true"]`).nextElementSibling;
+      return slide ? slide : firstSlide();
+    };
+
+    const prevSlide = () => {
+      const slide = this.element_.querySelector(`.${SLIDE}[aria-selected="true"]`).previousElementSibling;
+      return slide ? slide : lastSlide();
+    };
+
+    if(detail && detail.action) {
+
+      const { action, target } = detail;
+
+      let slide;
+      switch (action.toLowerCase()) {
+        case 'select':
+          slide = getSlide(target);
+          this.dispatchSelectEvent_(slide);
+          break;
+        case 'first':
+          slide = firstSlide();
+          break;
+        case 'next':
+          slide = nextSlide();
+          break;
+        case 'prev':
+          slide = prevSlide();
+          break;
+        case 'last':
+          slide = lastSlide();
+          break;
+        case 'upgrade':
+          this.upgradeSlides();
+          break;
+        default:
+          throw new Error(`Unknown action "${action}". Action must be one of "first", "next", "prev", "last", "select" or "upgrade"`);
+      }
+
+      if (slide) {
+        const a = slide.querySelector('a');
+        if (a) {
+          a.focus();
+        }
+        else {
+          slide.focus();
+        }
+
+        // Workaround for JSDom testing:
+        // In JsDom 'element.focus()' does not trigger any focus event
+        if(!slide.hasAttribute('aria-selected')) {
+          this.selectSlide_(slide);
+        }
+
+      }
+    }
+  };
+  MaterialExtLightboard.prototype['command'] = MaterialExtLightboard.prototype.command;
+
+
+  /**
+   * Initialize component
+   */
+  MaterialExtLightboard.prototype.init = function() {
+
+    const keydownHandler = event => {
+
+      if(event.target !== this.element_) {
+        let action;
+        let target;
+        switch (event.keyCode) {
+          case VK_HOME:
+            action = 'first';
+            break;
+          case VK_END:
+            action = 'last';
+            break;
+          case VK_ARROW_UP:
+          case VK_ARROW_LEFT:
+            action = 'prev';
+            break;
+          case VK_ARROW_DOWN:
+          case VK_ARROW_RIGHT:
+            action = 'next';
+            break;
+          case VK_ENTER:
+          case VK_SPACE:
+            action = 'select';
+            target = event.target;
+            break;
+        }
+        if(action)  {
+          event.preventDefault();
+          event.stopPropagation();
+          this.command( { action: action, target: target } );
+        }
+      }
+    };
+
+    const clickHandler = event => {
+      event.preventDefault();
+      event.stopPropagation();
+
+      if(event.target !== this.element_) {
+        this.command( { action: 'select', target: event.target } );
+      }
+    };
+
+    const focusHandler = event => {
+      event.preventDefault();
+      event.stopPropagation();
+
+      if(event.target !== this.element_) {
+        this.selectSlide_(event.target);
+      }
+    };
+
+
+    if (this.element_) {
+      this.element_.setAttribute('role', LIGHTBOARD_ROLE);
+
+      if (this.element_.classList.contains(MDL_RIPPLE_EFFECT)) {
+        this.element_.classList.add(MDL_RIPPLE_EFFECT_IGNORE_EVENTS);
+      }
+
+      // Remove listeners, just in case ...
+      this.element_.removeEventListener('command', this.commandHandler_);
+      this.element_.removeEventListener('keydown', keydownHandler);
+      this.element_.removeEventListener('click', clickHandler);
+      this.element_.removeEventListener('focus', focusHandler);
+
+      this.element_.addEventListener('command', this.commandHandler_.bind(this), false);
+      this.element_.addEventListener('keydown', keydownHandler, true);
+      this.element_.addEventListener('click', clickHandler, true);
+      this.element_.addEventListener('focus', focusHandler, true);
+
+      this.upgradeSlides();
+
+      this.element_.classList.add(IS_UPGRADED);
+    }
+  };
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  /* jshint undef:false */
+  componentHandler.register({
+    constructor: MaterialExtLightboard,
+    classAsString: 'MaterialExtLightboard',
+    cssClass: 'mdlext-js-lightboard',
+    widget: true
+  });
+
+})();