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

<link rel=“import” href=“../polymer/polymer.html”> <link rel=“import” href=“../iron-behaviors/iron-control-state.html”> <link rel=“import” href=“../iron-flex-layout/iron-flex-layout.html”> <link rel=“import” href=“../iron-validatable-behavior/iron-validatable-behavior.html”> <link rel=“import” href=“../iron-form-element-behavior/iron-form-element-behavior.html”>

<!– `iron-autogrow-textarea` is an element containing a textarea that grows in height as more lines of input are entered. Unless an explicit height or the `maxRows` property is set, it will never scroll.

Example:

<iron-autogrow-textarea></iron-autogrow-textarea>

### Styling

The following custom properties and mixins are available for styling:

Custom property | Description | Default —————-|————-|———- `–iron-autogrow-textarea` | Mixin applied to the textarea | `{}` `–iron-autogrow-textarea-placeholder` | Mixin applied to the textarea placeholder | `{}`

@group Iron Elements @hero hero.svg @demo demo/index.html –>

<dom-module id=“iron-autogrow-textarea”>

<template>
  <style>
    :host {
      display: inline-block;
      position: relative;
      width: 400px;
      border: 1px solid;
      padding: 2px;
      -moz-appearance: textarea;
      -webkit-appearance: textarea;
      overflow: hidden;
    }

    .mirror-text {
      visibility: hidden;
      word-wrap: break-word;
    }

    .fit {
      @apply(--layout-fit);
    }

    textarea {
      position: relative;
      outline: none;
      border: none;
      resize: none;
      background: inherit;
      color: inherit;
      /* see comments in template */
      width: 100%;
      height: 100%;
      font-size: inherit;
      font-family: inherit;
      line-height: inherit;
      text-align: inherit;
      @apply(--iron-autogrow-textarea);
    }

    ::content textarea:invalid {
      box-shadow: none;
    }

    textarea::-webkit-input-placeholder {
      @apply(--iron-autogrow-textarea-placeholder);
    }

    textarea:-moz-placeholder {
      @apply(--iron-autogrow-textarea-placeholder);
    }

    textarea::-moz-placeholder {
      @apply(--iron-autogrow-textarea-placeholder);
    }

    textarea:-ms-input-placeholder {
      @apply(--iron-autogrow-textarea-placeholder);
    }
  </style>

  <!-- the mirror sizes the input/textarea so it grows with typing -->
  <!-- use &#160; instead &nbsp; of to allow this element to be used in XHTML -->
  <div id="mirror" class="mirror-text" aria-hidden="true">&#160;</div>

  <!-- size the input/textarea with a div, because the textarea has intrinsic size in ff -->
  <div class="textarea-container fit">
    <textarea id="textarea"
      name$="[[name]]"
      autocomplete$="[[autocomplete]]"
      autofocus$="[[autofocus]]"
      inputmode$="[[inputmode]]"
      placeholder$="[[placeholder]]"
      readonly$="[[readonly]]"
      required$="[[required]]"
      disabled$="[[disabled]]"
      rows$="[[rows]]"
      minlength$="[[minlength]]"
      maxlength$="[[maxlength]]"></textarea>
  </div>
</template>

</dom-module>

<script>

Polymer({

  is: 'iron-autogrow-textarea',

  behaviors: [
    Polymer.IronFormElementBehavior,
    Polymer.IronValidatableBehavior,
    Polymer.IronControlState
  ],

  properties: {

    /**
     * Use this property instead of `value` for two-way data binding.
     * This property will be deprecated in the future. Use `value` instead.
     * @type {string|number}
     */
    bindValue: {
      observer: '_bindValueChanged',
      type: String
    },

    /**
     * The initial number of rows.
     *
     * @attribute rows
     * @type number
     * @default 1
     */
    rows: {
      type: Number,
      value: 1,
      observer: '_updateCached'
    },

    /**
     * The maximum number of rows this element can grow to until it
     * scrolls. 0 means no maximum.
     *
     * @attribute maxRows
     * @type number
     * @default 0
     */
    maxRows: {
     type: Number,
     value: 0,
     observer: '_updateCached'
    },

    /**
     * Bound to the textarea's `autocomplete` attribute.
     */
    autocomplete: {
      type: String,
      value: 'off'
    },

    /**
     * Bound to the textarea's `autofocus` attribute.
     */
    autofocus: {
      type: Boolean,
      value: false
    },

    /**
     * Bound to the textarea's `inputmode` attribute.
     */
    inputmode: {
      type: String
    },

    /**
     * Bound to the textarea's `placeholder` attribute.
     */
    placeholder: {
      type: String
    },

    /**
     * Bound to the textarea's `readonly` attribute.
     */
    readonly: {
      type: String
    },

    /**
     * Set to true to mark the textarea as required.
     */
    required: {
      type: Boolean
    },

    /**
     * The minimum length of the input value.
     */
    minlength: {
      type: Number
    },

    /**
     * The maximum length of the input value.
     */
    maxlength: {
      type: Number
    }

  },

  listeners: {
    'input': '_onInput'
  },

  observers: [
    '_onValueChanged(value)'
  ],

  /**
   * Returns the underlying textarea.
   * @type HTMLTextAreaElement
   */
  get textarea() {
    return this.$.textarea;
  },

  /**
   * Returns textarea's selection start.
   * @type Number
   */
  get selectionStart() {
    return this.$.textarea.selectionStart;
  },

  /**
   * Returns textarea's selection end.
   * @type Number
   */
  get selectionEnd() {
    return this.$.textarea.selectionEnd;
  },

  /**
   * Sets the textarea's selection start.
   */
  set selectionStart(value) {
    this.$.textarea.selectionStart = value;
  },

  /**
   * Sets the textarea's selection end.
   */
  set selectionEnd(value) {
    this.$.textarea.selectionEnd = value;
  },

  /**
   * Returns true if `value` is valid. The validator provided in `validator`
   * will be used first, if it exists; otherwise, the `textarea`'s validity
   * is used.
   * @return {boolean} True if the value is valid.
   */
  validate: function() {
    // Empty, non-required input is valid.
    if (!this.required && this.value == '') {
      this.invalid = false;
      return true;
    }

    var valid;
    if (this.hasValidator()) {
      valid = Polymer.IronValidatableBehavior.validate.call(this, this.value);
    } else {
      valid = this.$.textarea.validity.valid;
      this.invalid = !valid;
    }
    this.fire('iron-input-validate');
    return valid;
  },

  _bindValueChanged: function() {
    var textarea = this.textarea;
    if (!textarea) {
      return;
    }

    // If the bindValue changed manually, then we need to also update
    // the underlying textarea's value. Otherwise this change was probably
    // generated from the _onInput handler, and the two values are already
    // the same.
    if (textarea.value !== this.bindValue) {
      textarea.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindValue;
    }

    this.value = this.bindValue;
    this.$.mirror.innerHTML = this._valueForMirror();
    // manually notify because we don't want to notify until after setting value
    this.fire('bind-value-changed', {value: this.bindValue});
  },

  _onInput: function(event) {
    this.bindValue = event.path ? event.path[0].value : event.target.value;
  },

  _constrain: function(tokens) {
    var _tokens;
    tokens = tokens || [''];
    // Enforce the min and max heights for a multiline input to avoid measurement
    if (this.maxRows > 0 && tokens.length > this.maxRows) {
      _tokens = tokens.slice(0, this.maxRows);
    } else {
      _tokens = tokens.slice(0);
    }
    while (this.rows > 0 && _tokens.length < this.rows) {
      _tokens.push('');
    }
    // Use &#160; instead &nbsp; of to allow this element to be used in XHTML.
    return _tokens.join('<br/>') + '&#160;';
  },

  _valueForMirror: function() {
    var input = this.textarea;
    if (!input) {
      return;
    }
    this.tokens = (input && input.value) ? input.value.replace(/&/gm, '&amp;').replace(/"/gm, '&quot;').replace(/'/gm, '&#39;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;').split('\n') : [''];
    return this._constrain(this.tokens);
  },

  _updateCached: function() {
    this.$.mirror.innerHTML = this._constrain(this.tokens);
  },

  _onValueChanged: function() {
    this.bindValue = this.value;
  }
});

</script>