Home | History | Annotate | Download | only in paper-input
      1 <!--
      2 Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
      3 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE
      4 The complete set of authors may be found at http://polymer.github.io/AUTHORS
      5 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS
      6 Code distributed by Google as part of the polymer project is also
      7 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS
      8 -->
      9 
     10 <!--
     11 `paper-input` is a single- or multi-line text field where user can enter input.
     12 It can optionally have a label.
     13 
     14 Example:
     15 
     16     <paper-input label="Your Name"></paper-input>
     17     <paper-input multiline label="Enter multiple lines here"></paper-input>
     18 
     19 Theming
     20 --------
     21 
     22 Set `CoreStyle.g.paperInput.focusedColor` and `CoreStyle.g.paperInput.invalidColor` to theme
     23 the focused and invalid states.
     24 
     25 @group Paper Elements
     26 @element paper-input
     27 @extends core-input
     28 @homepage github.io
     29 -->
     30 <link href="../polymer/polymer.html" rel="import">
     31 <link href="../core-input/core-input.html" rel="import">
     32 <link href="../core-style/core-style.html" rel="import">
     33 
     34 <core-style id="paper-input">
     35 
     36 #label.focused,
     37 #floatedLabel.focused {
     38   color: {{g.paperInput.focusedColor}};
     39 }
     40 
     41 #underlineHighlight.focused,
     42 #caretInner {
     43   background-color: {{g.paperInput.focusedColor}};
     44 }
     45 
     46 #error,
     47 :host(.invalid) #label.focused,
     48 :host(.invalid) #floatedLabel.focused {
     49   color: {{g.paperInput.invalidColor}};
     50 }
     51 :host(.invalid) #underlineHighlight.focused,
     52 :host(.invalid) #caretInner {
     53   background-color: {{g.paperInput.invalidColor}};
     54 }
     55 
     56 </core-style>
     57 
     58 <polymer-element name="paper-input" extends="core-input" attributes="label floatingLabel maxRows error" on-down="{{downAction}}" on-up="{{upAction}}">
     59 
     60   <template>
     61 
     62     <link href="paper-input.css" rel="stylesheet">
     63 
     64     <core-style ref="paper-input"></core-style>
     65 
     66     <div id="floatedLabel" class="hidden" hidden?="{{!floatingLabel}}"><span id="floatedLabelSpan">{{label}}</span></div>
     67 
     68     <div id="container" on-transitionend="{{transitionEndAction}}" on-webkitTransitionEnd="{{transitionEndAction}}">
     69 
     70       <div id="label"><span id="labelSpan">{{label}}</span></div>
     71 
     72       <div id="inputContainer">
     73 
     74         <div id="inputClone">
     75           <span id="inputCloneSpan" aria-hidden="true">&nbsp;</span>
     76         </div>
     77 
     78         <template if="{{multiline}}">
     79           <div id="minInputHeight"></div>
     80           <div id="maxInputHeight"></div>
     81         </template>
     82 
     83         <shadow></shadow>
     84 
     85       </div>
     86 
     87       <div id="underlineContainer">
     88         <div id="underline"></div>
     89         <div id="underlineHighlight" class="focusedColor"></div>
     90       </div>
     91 
     92       <div id="caret">
     93         <div id="caretInner" class="focusedColor"></div>
     94       </div>
     95 
     96     </div>
     97 
     98     <div id="errorContainer">
     99       <div id="error" role="alert" aria-hidden="{{!invalid}}">{{error || validationMessage}}</div>
    100       <div id="errorIcon"></div>
    101     </div>
    102 
    103   </template>
    104 
    105   <script>
    106 
    107   (function() {
    108 
    109     var paperInput = CoreStyle.g.paperInput = CoreStyle.g.paperInput || {};
    110     paperInput.focusedColor = '#4059a9';
    111     paperInput.invalidColor = '#d34336';
    112 
    113     Polymer('paper-input', {
    114 
    115       /**
    116        * The label for this input. It normally appears as grey text inside
    117        * the text input and disappears once the user enters text.
    118        *
    119        * @attribute label
    120        * @type string
    121        * @default ''
    122        */
    123       label: '',
    124 
    125       /**
    126        * If true, the label will "float" above the text input once the
    127        * user enters text instead of disappearing.
    128        *
    129        * @attribute floatingLabel
    130        * @type boolean
    131        * @default false
    132        */
    133       floatingLabel: false,
    134 
    135       /**
    136        * (multiline only) If set to a non-zero value, the height of this
    137        * text input will grow with the value changes until it is maxRows
    138        * rows tall. If the maximum size does not fit the value, the text
    139        * input will scroll internally.
    140        *
    141        * @attribute maxRows
    142        * @type number
    143        * @default 0
    144        */
    145       maxRows: 0,
    146 
    147       /**
    148        * The message to display if the input value fails validation. If this
    149        * is unset or the empty string, a default message is displayed depending
    150        * on the type of validation error.
    151        *
    152        * @attribute error
    153        * @type string
    154        */
    155       error: '',
    156 
    157       focused: false,
    158       pressed: false,
    159 
    160       attached: function() {
    161         if (this.multiline) {
    162           this.resizeInput();
    163           window.requestAnimationFrame(function() {
    164             this.$.underlineContainer.classList.add('animating');
    165           }.bind(this));
    166         }
    167       },
    168 
    169       resizeInput: function() {
    170         var height = this.$.inputClone.getBoundingClientRect().height;
    171         var bounded = this.maxRows > 0 || this.rows > 0;
    172         if (bounded) {
    173           var minHeight = this.$.minInputHeight.getBoundingClientRect().height;
    174           var maxHeight = this.$.maxInputHeight.getBoundingClientRect().height;
    175           height = Math.max(minHeight, Math.min(height, maxHeight));
    176         }
    177         this.$.inputContainer.style.height = height + 'px';
    178         this.$.underlineContainer.style.top = height + 'px';
    179       },
    180 
    181       prepareLabelTransform: function() {
    182         var toRect = this.$.floatedLabelSpan.getBoundingClientRect();
    183         var fromRect = this.$.labelSpan.getBoundingClientRect();
    184         if (toRect.width !== 0) {
    185           this.$.label.cachedTransform = 'scale(' + (toRect.width / fromRect.width) + ') ' +
    186             'translateY(' + (toRect.bottom - fromRect.bottom) + 'px)';
    187         }
    188       },
    189 
    190       toggleLabel: function(force) {
    191         var v = force !== undefined ? force : this.inputValue;
    192 
    193         if (!this.floatingLabel) {
    194           this.$.label.classList.toggle('hidden', v);
    195         }
    196 
    197         if (this.floatingLabel && !this.focused) {
    198           this.$.label.classList.toggle('hidden', v);
    199           this.$.floatedLabel.classList.toggle('hidden', !v);
    200         }
    201       },
    202 
    203       rowsChanged: function() {
    204         if (this.multiline && !isNaN(parseInt(this.rows))) {
    205           this.$.minInputHeight.innerHTML = '';
    206           for (var i = 0; i < this.rows; i++) {
    207             this.$.minInputHeight.appendChild(document.createElement('br'));
    208           }
    209           this.resizeInput();
    210         }
    211       },
    212 
    213       maxRowsChanged: function() {
    214         if (this.multiline && !isNaN(parseInt(this.maxRows))) {
    215           this.$.maxInputHeight.innerHTML = '';
    216           for (var i = 0; i < this.maxRows; i++) {
    217             this.$.maxInputHeight.appendChild(document.createElement('br'));
    218           }
    219           this.resizeInput();
    220         }
    221       },
    222 
    223       inputValueChanged: function() {
    224         this.super();
    225 
    226         if (this.multiline) {
    227           var escaped = this.inputValue.replace(/\n/gm, '<br>');
    228           if (!escaped || escaped.lastIndexOf('<br>') === escaped.length - 4) {
    229             escaped += '&nbsp';
    230           }
    231           this.$.inputCloneSpan.innerHTML = escaped;
    232           this.resizeInput();
    233         }
    234 
    235         this.toggleLabel();
    236       },
    237 
    238       labelChanged: function() {
    239         if (this.floatingLabel && this.$.floatedLabel && this.$.label) {
    240           // If the element is created programmatically, labelChanged is called before
    241           // binding. Run the measuring code in async so the DOM is ready.
    242           this.async(function() {
    243             this.prepareLabelTransform();
    244           });
    245         }
    246       },
    247 
    248       placeholderChanged: function() {
    249         this.label = this.placeholder;
    250       },
    251 
    252       inputFocusAction: function() {
    253         if (!this.pressed) {
    254           if (this.floatingLabel) {
    255             this.$.floatedLabel.classList.remove('hidden');
    256             this.$.floatedLabel.classList.add('focused');
    257             this.$.floatedLabel.classList.add('focusedColor');
    258           }
    259           this.$.label.classList.add('hidden');
    260           this.$.underlineHighlight.classList.add('focused');
    261           this.$.caret.classList.add('focused');
    262 
    263           this.super(arguments);
    264         }
    265         this.focused = true;
    266       },
    267 
    268       shouldFloatLabel: function() {
    269         // if type = number, the input value is the empty string until a valid number
    270         // is entered so we must do some hacks here
    271         return this.inputValue || (this.type === 'number' && !this.validity.valid);
    272       },
    273 
    274       inputBlurAction: function() {
    275         this.super(arguments);
    276 
    277         this.$.underlineHighlight.classList.remove('focused');
    278         this.$.caret.classList.remove('focused');
    279 
    280         if (this.floatingLabel) {
    281           this.$.floatedLabel.classList.remove('focused');
    282           this.$.floatedLabel.classList.remove('focusedColor');
    283           if (!this.shouldFloatLabel()) {
    284             this.$.floatedLabel.classList.add('hidden');
    285           }
    286         }
    287 
    288         // type = number hack. see core-input for more info
    289         if (!this.shouldFloatLabel()) {
    290           this.$.label.classList.remove('hidden');
    291           this.$.label.classList.add('animating');
    292           this.async(function() {
    293             this.$.label.style.webkitTransform = 'none';
    294             this.$.label.style.transform = 'none';
    295           });
    296         }
    297 
    298         this.focused = false;
    299       },
    300 
    301       downAction: function(e) {
    302         if (this.disabled) {
    303           return;
    304         }
    305 
    306         if (this.focused) {
    307           return;
    308         }
    309 
    310         this.pressed = true;
    311         var rect = this.$.underline.getBoundingClientRect();
    312         var right = e.x - rect.left;
    313         this.$.underlineHighlight.style.webkitTransformOriginX = right + 'px';
    314         this.$.underlineHighlight.style.transformOriginX = right + 'px';
    315         this.$.underlineHighlight.classList.remove('focused');
    316         this.underlineAsync = this.async(function() {
    317           this.$.underlineHighlight.classList.add('pressed');
    318         }, null, 200);
    319 
    320         // No caret animation if there is text in the input.
    321         if (!this.inputValue) {
    322           this.$.caret.classList.remove('focused');
    323         }
    324       },
    325 
    326       upAction: function(e) {
    327         if (this.disabled) {
    328           return;
    329         }
    330 
    331         if (!this.pressed) {
    332           return;
    333         }
    334 
    335         // if a touchevent caused the up, the synthentic mouseevents will blur
    336         // the input, make sure to prevent those from being generated.
    337         if (e._source === 'touch') {
    338           e.preventDefault();
    339         }
    340 
    341         if (this.underlineAsync) {
    342           clearTimeout(this.underlineAsync);
    343           this.underlineAsync = null;
    344         }
    345 
    346         // Focus the input here to bring up the virtual keyboard.
    347         this.$.input.focus();
    348         this.pressed = false;
    349         this.animating = true;
    350 
    351         this.$.underlineHighlight.classList.remove('pressed');
    352         this.$.underlineHighlight.classList.add('animating');
    353         this.async(function() {
    354           this.$.underlineHighlight.classList.add('focused');
    355         });
    356 
    357         // No caret animation if there is text in the input.
    358         if (!this.inputValue) {
    359           this.$.caret.classList.add('animating');
    360           this.async(function() {
    361             this.$.caret.classList.add('focused');
    362           }, null, 100);
    363         }
    364 
    365         if (this.floatingLabel) {
    366           this.$.label.classList.add('focusedColor');
    367           this.$.label.classList.add('animating');
    368           if (!this.$.label.cachedTransform) {
    369             this.prepareLabelTransform();
    370           }
    371           this.$.label.style.webkitTransform = this.$.label.cachedTransform;
    372           this.$.label.style.transform = this.$.label.cachedTransform;
    373         }
    374       },
    375 
    376       keydownAction: function() {
    377         this.super();
    378 
    379         // more type = number hacks. see core-input for more info
    380         if (this.type === 'number') {
    381           this.async(function() {
    382             if (!this.inputValue) {
    383               this.toggleLabel(!this.validity.valid);
    384             }
    385           });
    386         }
    387       },
    388 
    389       keypressAction: function() {
    390         if (this.animating) {
    391           this.transitionEndAction();
    392         }
    393       },
    394 
    395       transitionEndAction: function(e) {
    396         this.animating = false;
    397         if (this.pressed) {
    398           return;
    399         }
    400 
    401         if (this.focused) {
    402 
    403           if (this.floatingLabel || this.inputValue) {
    404             this.$.label.classList.add('hidden');
    405           }
    406 
    407           if (this.floatingLabel) {
    408             this.$.label.classList.remove('focusedColor');
    409             this.$.label.classList.remove('animating');
    410             this.$.floatedLabel.classList.remove('hidden');
    411             this.$.floatedLabel.classList.add('focused');
    412             this.$.floatedLabel.classList.add('focusedColor');
    413           }
    414 
    415           this.async(function() {
    416             this.$.underlineHighlight.classList.remove('animating');
    417             this.$.caret.classList.remove('animating');
    418           }, null, 100);
    419 
    420         } else {
    421 
    422           this.$.label.classList.remove('animating');
    423 
    424         }
    425       }
    426 
    427     });
    428 
    429   }());
    430 
    431   </script>
    432 
    433 </polymer-element>
    434