text: /**
   * Copyright 2016 Google Inc. 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.
   */
  
  import {MDCFoundation} from '@material/base/index';
  import {cssClasses, strings} from './constants';
  import {MDCMenuFoundation} from '@material/menu/index';
  
  const OPENER_KEYS = [
    {key: 'ArrowUp', keyCode: 38, forType: 'keydown'},
    {key: 'ArrowDown', keyCode: 40, forType: 'keydown'},
    {key: 'Space', keyCode: 32, forType: 'keyup'},
  ];
  
  export default class MDCSelectFoundation extends MDCFoundation {
    static get cssClasses() {
      return cssClasses;
    }
  
    static get strings() {
      return strings;
    }
  
    static get defaultAdapter() {
      return {
        addClass: (/* className: string */) => {},
        removeClass: (/* className: string */) => {},
        floatLabel: (/* value: boolean */) => {},
        activateBottomLine: () => {},
        deactivateBottomLine: () => {},
        addBodyClass: (/* className: string */) => {},
        removeBodyClass: (/* className: string */) => {},
        setAttr: (/* attr: string, value: string */) => {},
        rmAttr: (/* attr: string */) => {},
        computeBoundingRect: () => /* {left: number, top: number} */ ({left: 0, top: 0}),
        registerInteractionHandler: (/* type: string, handler: EventListener */) => {},
        deregisterInteractionHandler: (/* type: string, handler: EventListener */) => {},
        focus: () => {},
        makeTabbable: () => {},
        makeUntabbable: () => {},
        getComputedStyleValue: (/* propertyName: string */) => /* string */ '',
        setStyle: (/* propertyName: string, value: string */) => {},
        create2dRenderingContext: () => /* {font: string, measureText: (string) => {width: number}} */ ({
          font: '',
          measureText: () => ({width: 0}),
        }),
        setMenuElStyle: (/* propertyName: string, value: string */) => {},
        setMenuElAttr: (/* attr: string, value: string */) => {},
        rmMenuElAttr: (/* attr: string */) => {},
        getMenuElOffsetHeight: () => /* number */ 0,
        openMenu: (/* focusIndex: number */) => {},
        isMenuOpen: () => /* boolean */ false,
        setSelectedTextContent: (/* textContent: string */) => {},
        getNumberOfOptions: () => /* number */ 0,
        getTextForOptionAtIndex: (/* index: number */) => /* string */ '',
        getValueForOptionAtIndex: (/* index: number */) => /* string */ '',
        setAttrForOptionAtIndex: (/* index: number, attr: string, value: string */) => {},
        rmAttrForOptionAtIndex: (/* index: number, attr: string */) => {},
        getOffsetTopForOptionAtIndex: (/* index: number */) => /* number */ 0,
        registerMenuInteractionHandler: (/* type: string, handler: EventListener */) => {},
        deregisterMenuInteractionHandler: (/* type: string, handler: EventListener */) => {},
        notifyChange: () => {},
        getWindowInnerHeight: () => /* number */ 0,
      };
    }
  
    constructor(adapter) {
      super(Object.assign(MDCSelectFoundation.defaultAdapter, adapter));
      this.ctx_ = null;
      this.selectedIndex_ = -1;
      this.disabled_ = false;
      this.isFocused_ = false;
  
      /** @private {number} */
      this.animationRequestId_ = 0;
  
      this.displayHandler_ = (evt) => {
        evt.preventDefault();
        if (!this.adapter_.isMenuOpen()) {
          this.open_();
        }
      };
      this.displayViaKeyboardHandler_ = (evt) => this.handleDisplayViaKeyboard_(evt);
      this.selectionHandler_ = ({detail}) => {
        const {index} = detail;
  
        if (index !== this.selectedIndex_) {
          this.setSelectedIndex(index);
          this.adapter_.notifyChange();
        }
        this.close_();
      };
      this.cancelHandler_ = () => {
        this.close_();
        if (this.selectedIndex_ === -1) {
          this.adapter_.floatLabel(false);
        }
      };
    }
  
    init() {
      this.ctx_ = this.adapter_.create2dRenderingContext();
      this.adapter_.registerInteractionHandler('click', this.displayHandler_);
      this.adapter_.registerInteractionHandler('keydown', this.displayViaKeyboardHandler_);
      this.adapter_.registerInteractionHandler('keyup', this.displayViaKeyboardHandler_);
      this.adapter_.registerMenuInteractionHandler(
        MDCMenuFoundation.strings.SELECTED_EVENT, this.selectionHandler_);
      this.adapter_.registerMenuInteractionHandler(
        MDCMenuFoundation.strings.CANCEL_EVENT, this.cancelHandler_);
      this.resize();
    }
  
    destroy() {
      // Drop reference to context object to prevent potential leaks
      this.ctx_ = null;
      cancelAnimationFrame(this.animationRequestId_);
      this.adapter_.deregisterInteractionHandler('click', this.displayHandler_);
      this.adapter_.deregisterInteractionHandler('keydown', this.displayViaKeyboardHandler_);
      this.adapter_.deregisterInteractionHandler('keyup', this.displayViaKeyboardHandler_);
      this.adapter_.deregisterMenuInteractionHandler(
        MDCMenuFoundation.strings.SELECTED_EVENT, this.selectionHandler_);
      this.adapter_.deregisterMenuInteractionHandler(
        MDCMenuFoundation.strings.CANCEL_EVENT, this.cancelHandler_);
    }
  
    getValue() {
      return this.selectedIndex_ >= 0 ? this.adapter_.getValueForOptionAtIndex(this.selectedIndex_) : '';
    }
  
    getSelectedIndex() {
      return this.selectedIndex_;
    }
  
    setSelectedIndex(index) {
      const prevSelectedIndex = this.selectedIndex_;
      if (prevSelectedIndex >= 0) {
        this.adapter_.rmAttrForOptionAtIndex(this.selectedIndex_, 'aria-selected');
      }
  
      this.selectedIndex_ = index >= 0 && index < this.adapter_.getNumberOfOptions() ? index : -1;
      let selectedTextContent = '';
      if (this.selectedIndex_ >= 0) {
        selectedTextContent = this.adapter_.getTextForOptionAtIndex(this.selectedIndex_).trim();
        this.adapter_.setAttrForOptionAtIndex(this.selectedIndex_, 'aria-selected', 'true');
        this.adapter_.floatLabel(true);
      } else {
        if (!this.adapter_.isMenuOpen()) {
          this.adapter_.floatLabel(false);
        }
      }
      this.adapter_.setSelectedTextContent(selectedTextContent);
    }
  
    isDisabled() {
      return this.disabled_;
    }
  
    setDisabled(disabled) {
      const {DISABLED} = MDCSelectFoundation.cssClasses;
      this.disabled_ = disabled;
      if (this.disabled_) {
        this.adapter_.addClass(DISABLED);
        this.adapter_.setAttr('aria-disabled', 'true');
        this.adapter_.makeUntabbable();
      } else {
        this.adapter_.removeClass(DISABLED);
        this.adapter_.rmAttr('aria-disabled');
        this.adapter_.makeTabbable();
      }
    }
  
    resize() {
      const font = this.adapter_.getComputedStyleValue('font');
      const letterSpacing = parseFloat(this.adapter_.getComputedStyleValue('letter-spacing'));
  
      if (font) {
        this.ctx_.font = font;
      } else {
        const primaryFontFamily = this.adapter_.getComputedStyleValue('font-family').split(',')[0];
        const fontSize = this.adapter_.getComputedStyleValue('font-size');
        this.ctx_.font = `${fontSize} ${primaryFontFamily}`;
      }
  
      let maxTextLength = 0;
  
      for (let i = 0, l = this.adapter_.getNumberOfOptions(); i < l; i++) {
        const surfacePaddingRight = parseInt(this.adapter_.getComputedStyleValue('padding-right'), 10);
        const surfacePaddingLeft = parseInt(this.adapter_.getComputedStyleValue('padding-left'), 10);
        const selectBoxAddedPadding = surfacePaddingRight + surfacePaddingLeft;
        const txt = this.adapter_.getTextForOptionAtIndex(i).trim();
        const {width} = this.ctx_.measureText(txt);
        const addedSpace = letterSpacing * txt.length;
  
        maxTextLength =
          Math.max(maxTextLength, Math.ceil(width + addedSpace + selectBoxAddedPadding));
      }
  
      this.adapter_.setStyle('width', `${maxTextLength}px`);
    }
  
    open_() {
      this.disableScroll_();
      const {OPEN} = MDCSelectFoundation.cssClasses;
      const focusIndex = this.selectedIndex_ < 0 ? 0 : this.selectedIndex_;
  
      this.setMenuStylesForOpenAtIndex_(focusIndex);
      this.adapter_.floatLabel(true);
      this.adapter_.activateBottomLine();
      this.adapter_.addClass(OPEN);
      this.animationRequestId_ = requestAnimationFrame(() => {
        this.adapter_.openMenu(focusIndex);
        this.isFocused_ = true;
      });
    }
  
    setMenuStylesForOpenAtIndex_(index) {
      const innerHeight = this.adapter_.getWindowInnerHeight();
      const {left, top} = this.adapter_.computeBoundingRect();
  
      this.adapter_.setMenuElAttr('aria-hidden', 'true');
      this.adapter_.setMenuElStyle('display', 'block');
      const menuHeight = this.adapter_.getMenuElOffsetHeight();
      const itemOffsetTop = this.adapter_.getOffsetTopForOptionAtIndex(index);
      this.adapter_.setMenuElStyle('display', '');
      this.adapter_.rmMenuElAttr('aria-hidden');
  
      let adjustedTop = top - itemOffsetTop;
      const overflowsTop = adjustedTop < 0;
      const overflowsBottom = adjustedTop + menuHeight > innerHeight;
      if (overflowsTop) {
        adjustedTop = 0;
      } else if (overflowsBottom) {
        adjustedTop = Math.max(0, innerHeight - menuHeight);
      };
  
      this.adapter_.setMenuElStyle('left', `${left}px`);
      this.adapter_.setMenuElStyle('top', `${adjustedTop}px`);
      this.adapter_.setMenuElStyle('transform-origin', `center ${itemOffsetTop}px`);
    }
  
    close_() {
      const {OPEN} = MDCSelectFoundation.cssClasses;
      this.adapter_.removeClass(OPEN);
      this.adapter_.deactivateBottomLine();
      this.adapter_.focus();
      this.enableScroll_();
    }
  
    handleDisplayViaKeyboard_(evt) {
      // We use a hard-coded 2 instead of Event.AT_TARGET to avoid having to reference a browser
      // global.
      const EVENT_PHASE_AT_TARGET = 2;
      if (evt.eventPhase !== EVENT_PHASE_AT_TARGET) {
        return;
      }
  
      // Prevent pressing space down from scrolling the page
      const isSpaceDown = evt.type === 'keydown' && (evt.key === 'Space' || evt.keyCode === 32);
      if (isSpaceDown) {
        evt.preventDefault();
      }
  
      const isOpenerKey = OPENER_KEYS.some(({key, keyCode, forType}) => {
        return evt.type === forType && (evt.key === key || evt.keyCode === keyCode);
      });
  
      if (isOpenerKey) {
        this.displayHandler_(evt);
      }
    }
  
    disableScroll_() {
      this.adapter_.addBodyClass(cssClasses.SCROLL_LOCK);
    }
  
    enableScroll_() {
      this.adapter_.removeBodyClass(cssClasses.SCROLL_LOCK);
    }
  }
  