<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE
The complete set of authors may be found at http://polymer.github.io/AUTHORS
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS
-->

<!--
`paper-input` is a single- or multi-line text field where user can enter input.
It can optionally have a label.

Example:

    <paper-input label="Your Name"></paper-input>
    <paper-input multiline label="Enter multiple lines here"></paper-input>

Theming
--------

Set `CoreStyle.g.paperInput.focusedColor` and `CoreStyle.g.paperInput.invalidColor` to theme
the focused and invalid states.

To add custom styling to only some elements, use these selectors:

    html /deep/ paper-input[focused] .floated-label {
        /* floating label color when the input has focus */
        color: green;
    }

    html /deep/ paper-input .focused-underline,
    html /deep/ paper-input .cursor {
        /* line and cursor color when the input has focus */
        background-color: green;
    }

    html /deep/ paper-input.invalid[focused] .floated-label,
    html /deep/ paper-input[focused] .error-text,
    html /deep/ paper-input[focused] .error-icon {
        /* error text, icon, and floating label color when input is invalid */
        color: salmon;
    }

    html /deep/ paper-input.invalid .focused-underline,
    html /deep/ paper-input.invalid .cursor {
        /* line and cursor color when the input is invalid */
        background-color: salmon;
    }

@group Paper Elements
@element paper-input
@extends core-input
@homepage github.io
-->
<link href="../polymer/polymer.html" rel="import">
<link href="../core-icon/core-icon.html" rel="import">
<link href="../core-icons/core-icons.html" rel="import">
<link href="../core-input/core-input.html" rel="import">
<link href="../core-style/core-style.html" rel="import">

<core-style id="paper-input">

:host([focused]) .floated-label {
  color: {{g.paperInput.focusedColor}};
}

.focused-underline,
.cursor {
  background-color: {{g.paperInput.focusedColor}};
}


:host(.invalid[focused]) .floated-label,
:host([focused]) .error-text,
:host([focused]) .error-icon {
  color: {{g.paperInput.invalidColor}};
}

:host(.invalid) .focused-underline,
:host(.invalid) .cursor {
  background-color: {{g.paperInput.invalidColor}};
}

</core-style>

<polymer-element name="paper-input" extends="core-input" layout vertical attributes="label floatingLabel maxRows error" on-transitionEnd="{{transitionEndAction}}" on-webkitTransitionEnd="{{transitionEndAction}}">

  <template>

    <!--
    Input tests:

    - set value to integer 0
    - various html5 input types
    - sizing:
      - single-line: size with CSS
      - single-line: can fit to container
      - multi-line: size with CSS
      - multi-line: size with rows
      - multi-line: can fit to container
      - multi-line: grows with typing
      - multi-line: max rows
      - multi-line: max rows, scrolls after
    -->

    <link href="paper-input.css" rel="stylesheet">
    <core-style ref="paper-input"></core-style>

    <div class="floated-label" aria-hidden="true" hidden?="{{!floatingLabel}}" invisible?="{{!inputValue && !(type === 'number' && !validity.valid) || labelAnimated}}">
      <!-- needed for floating label animation measurement -->
      <span id="floatedLabelText" class="label-text">{{label}}</span>
    </div>

    <!-- <div class="input-body" flex auto relative on-down="{{downAction}}" on-up="{{upAction}}"> -->
    <div class="input-body" flex auto relative>

      <!-- the mirror sizes the input/textarea so it grows with typing -->
      <div id="mirror" class="mirror-text" invisible aria-hidden="true"></div>

      <div class="label" fit aria-hidden="true">
        <!-- needed for floating label animation measurement -->
        <span id="labelText" class="label-text" invisible?="{{inputValue || !inputValue && type === 'number' && !validity.valid}}" animated?=
        "{{labelAnimated}}">{{label}}</span>
      </div>

      <div class="cursor" invisible?="{{!cursorAnimated}}" animated?="{{cursorAnimated}}"></div>

      <!-- size the input/textarea with a div, because the textarea has intrinsic size in ff -->
      <div class="input-container" on-down="{{downAction}}" on-up="{{upAction}}">
        <shadow></shadow>
      </div>

    </div>

    <div id="underline" class="underline" relative>
      <div class="unfocused-underline" fit invisible?="{{disabled}}"></div>
      <div id="focusedUnderline" class="focused-underline" fit invisible?="{{!focused}}" animated?="{{underlineAnimated}}"></div>
    </div>

    <div layout horizontal center hidden?="{{!invalid}}">
      <div class="error-text" flex auto role="alert" aria-hidden="{{!invalid}}">{{error || validationMessage}}</div>
      <core-icon class="error-icon" icon="warning"></core-icon>
    </div>

  </template>

  <script>

  (function() {

    var paperInput = CoreStyle.g.paperInput = CoreStyle.g.paperInput || {};
    paperInput.focusedColor = '#4059a9';
    paperInput.invalidColor = '#d34336';

    Polymer('paper-input', {

      publish: {
        /**
         * The label for this input. It normally appears as grey text inside
         * the text input and disappears once the user enters text.
         *
         * @attribute label
         * @type string
         * @default ''
         */
        label: '',

        /**
         * If true, the label will "float" above the text input once the
         * user enters text instead of disappearing.
         *
         * @attribute floatingLabel
         * @type boolean
         * @default false
         */
        floatingLabel: false,

        /**
         * (multiline only) If set to a non-zero value, the height of this
         * text input will grow with the value changes until it is maxRows
         * rows tall. If the maximum size does not fit the value, the text
         * input will scroll internally.
         *
         * @attribute maxRows
         * @type number
         * @default 0
         */
        maxRows: 0,

        /**
         * The message to display if the input value fails validation. If this
         * is unset or the empty string, a default message is displayed depending
         * on the type of validation error.
         *
         * @attribute error
         * @type string
         */
        error: '',

        focused: {value: false, reflect: true}

      },

      get inputValueForMirror() {
        var tokens = this.inputValue ? String(this.inputValue).replace(/&/gm, '&amp;').replace(/"/gm, '&quot;').replace(/'/gm, '&#39;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;').split('\n') : [''];

        // Enforce the min and max heights for a multiline input here to
        // avoid measurement
        if (this.multiline) {
          if (this.maxRows && tokens.length > this.maxRows) {
            tokens = tokens.slice(0, this.maxRows);
          }
          while (this.rows && tokens.length < this.rows) {
            tokens.push('');
          }
        }

        return tokens.join('<br>') + '&nbsp;';
      },

      get inputHasValue() {
        // if type = number, the input value is the empty string until a valid number
        // is entered so we must do some hacks here
        return this.inputValue || (this.type === 'number' && !this.validity.valid);
      },

      syncInputValueToMirror: function() {
        this.$.mirror.innerHTML = this.inputValueForMirror;
      },

      ready: function() {
        this.syncInputValueToMirror();
      },

      prepareLabelTransform: function() {
        var toRect = this.$.floatedLabelText.getBoundingClientRect();
        var fromRect = this.$.labelText.getBoundingClientRect();
        if (toRect.width !== 0) {
          var sy = toRect.height / fromRect.height;
          this.$.labelText.cachedTransform =
            'scale3d(' + (toRect.width / fromRect.width) + ',' + sy + ',1) ' +
            'translate3d(0,' + (toRect.top - fromRect.top) / sy + 'px,0)';
        }
      },

      animateFloatingLabel: function() {
        if (!this.floatingLabel || this.labelAnimated) {
          return;
        }

        if (!this.$.labelText.cachedTransform) {
          this.prepareLabelTransform();
        }

        // If there's still no cached transform, the input is invisible so don't
        // do the animation.
        if (!this.$.labelText.cachedTransform) {
          return;
        }

        this.labelAnimated = true;
        // Handle interrupted animation
        this.async(function() {
          this.transitionEndAction();
        }, null, 250);

        if (this.inputHasValue) {
          this.$.labelText.style.webkitTransform = this.$.labelText.cachedTransform;
          this.$.labelText.style.transform = this.$.labelText.cachedTransform;
        } else {
          // Handle if the label started out floating
          if (!this.$.labelText.style.webkitTransform && !this.$.labelText.style.transform) {
            this.$.labelText.style.webkitTransform = this.$.labelText.cachedTransform;
            this.$.labelText.style.transform = this.$.labelText.cachedTransform;
            this.$.labelText.offsetTop;
          }
          this.$.labelText.style.webkitTransform = '';
          this.$.labelText.style.transform = '';
        }
      },

      inputValueChanged: function(old) {
        this.super();

        this.syncInputValueToMirror();
        if (old && !this.inputValue || !old && this.inputValue) {
          this.animateFloatingLabel();
        }
      },

      placeholderChanged: function() {
        this.label = this.placeholder;
      },

      inputFocusAction: function() {
        this.super(arguments);
        this.focused = true;
      },

      inputBlurAction: function(e) {
        this.super(arguments);
        this.focused = false;
      },

      downAction: function(e) {
        if (this.disabled) {
          return;
        }

        if (this.focused) {
          return;
        }

        // The underline spills from the tap location
        var rect = this.$.underline.getBoundingClientRect();
        var right = e.x - rect.left;
        this.$.focusedUnderline.style.mozTransformOrigin = right + 'px';
        this.$.focusedUnderline.style.webkitTransformOrigin = right + 'px ';
        this.$.focusedUnderline.style.transformOriginX = right + 'px';

        // Animations only run when the user interacts with the input
        this.underlineAnimated = true;

        // Cursor animation only runs if the input is empty
        if (!this.inputHasValue) {
          this.cursorAnimated = true;
        }
        // Handle interrupted animation
        this.async(function() {
          this.transitionEndAction();
        }, null, 250);
      },

      keydownAction: function() {
        this.super();

        // more type = number hacks. see core-input for more info
        if (this.type === 'number') {
          var valid = !this.inputValue && this.validity.valid;
          this.async(function() {
            if (valid !== (!this.inputValue && this.validity.valid)) {
              this.animateFloatingLabel();
            }
          });
        }
      },

      transitionEndAction: function() {
        this.underlineAnimated = false;
        this.cursorAnimated = false;
        this.labelAnimated = false;
      }

    });

  }());

  </script>

</polymer-element>
