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
11 `paper-input` is a single- or multi-line text field where user can enter input.
12 It can optionally have a label.
16 <paper-input label="Your Name"></paper-input>
17 <paper-input multiline label="Enter multiple lines here"></paper-input>
22 Set `CoreStyle.g.paperInput.focusedColor` and `CoreStyle.g.paperInput.invalidColor` to theme
23 the focused and invalid states.
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">
34 <core-style id="paper-input">
37 #floatedLabel.focused {
38 color: {{g.paperInput.focusedColor}};
41 #underlineHighlight.focused,
43 background-color: {{g.paperInput.focusedColor}};
47 :host(.invalid) #label.focused,
48 :host(.invalid) #floatedLabel.focused {
49 color: {{g.paperInput.invalidColor}};
51 :host(.invalid) #underlineHighlight.focused,
52 :host(.invalid) #caretInner {
53 background-color: {{g.paperInput.invalidColor}};
58 <polymer-element name="paper-input" extends="core-input" attributes="label floatingLabel maxRows error" on-down="{{downAction}}" on-up="{{upAction}}">
62 <link href="paper-input.css" rel="stylesheet">
64 <core-style ref="paper-input"></core-style>
66 <div id="floatedLabel" class="hidden" hidden?="{{!floatingLabel}}"><span id="floatedLabelSpan">{{label}}</span></div>
68 <div id="container" on-transitionend="{{transitionEndAction}}" on-webkitTransitionEnd="{{transitionEndAction}}">
70 <div id="label"><span id="labelSpan">{{label}}</span></div>
72 <div id="inputContainer">
75 <span id="inputCloneSpan" aria-hidden="true"> </span>
78 <template if="{{multiline}}">
79 <div id="minInputHeight"></div>
80 <div id="maxInputHeight"></div>
87 <div id="underlineContainer">
88 <div id="underline"></div>
89 <div id="underlineHighlight" class="focusedColor"></div>
93 <div id="caretInner" class="focusedColor"></div>
98 <div id="errorContainer">
99 <div id="error" role="alert" aria-hidden="{{!invalid}}">{{error || validationMessage}}</div>
100 <div id="errorIcon"></div>
109 var paperInput = CoreStyle.g.paperInput = CoreStyle.g.paperInput || {};
110 paperInput.focusedColor = '#4059a9';
111 paperInput.invalidColor = '#d34336';
113 Polymer('paper-input', {
116 * The label for this input. It normally appears as grey text inside
117 * the text input and disappears once the user enters text.
126 * If true, the label will "float" above the text input once the
127 * user enters text instead of disappearing.
129 * @attribute floatingLabel
133 floatingLabel: false,
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.
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.
160 attached: function() {
161 if (this.multiline) {
163 window.requestAnimationFrame(function() {
164 this.$.underlineContainer.classList.add('animating');
169 resizeInput: function() {
170 var height = this.$.inputClone.getBoundingClientRect().height;
171 var bounded = this.maxRows > 0 || this.rows > 0;
173 var minHeight = this.$.minInputHeight.getBoundingClientRect().height;
174 var maxHeight = this.$.maxInputHeight.getBoundingClientRect().height;
175 height = Math.max(minHeight, Math.min(height, maxHeight));
177 this.$.inputContainer.style.height = height + 'px';
178 this.$.underlineContainer.style.top = height + 'px';
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)';
190 toggleLabel: function(force) {
191 var v = force !== undefined ? force : this.inputValue;
193 if (!this.floatingLabel) {
194 this.$.label.classList.toggle('hidden', v);
197 if (this.floatingLabel && !this.focused) {
198 this.$.label.classList.toggle('hidden', v);
199 this.$.floatedLabel.classList.toggle('hidden', !v);
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'));
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'));
223 inputValueChanged: function() {
226 if (this.multiline) {
227 var escaped = this.inputValue.replace(/\n/gm, '<br>');
228 if (!escaped || escaped.lastIndexOf('<br>') === escaped.length - 4) {
231 this.$.inputCloneSpan.innerHTML = escaped;
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();
248 placeholderChanged: function() {
249 this.label = this.placeholder;
252 inputFocusAction: function() {
254 if (this.floatingLabel) {
255 this.$.floatedLabel.classList.remove('hidden');
256 this.$.floatedLabel.classList.add('focused');
257 this.$.floatedLabel.classList.add('focusedColor');
259 this.$.label.classList.add('hidden');
260 this.$.underlineHighlight.classList.add('focused');
261 this.$.caret.classList.add('focused');
263 this.super(arguments);
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);
274 inputBlurAction: function() {
275 this.super(arguments);
277 this.$.underlineHighlight.classList.remove('focused');
278 this.$.caret.classList.remove('focused');
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');
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';
298 this.focused = false;
301 downAction: function(e) {
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');
320 // No caret animation if there is text in the input.
321 if (!this.inputValue) {
322 this.$.caret.classList.remove('focused');
326 upAction: function(e) {
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') {
341 if (this.underlineAsync) {
342 clearTimeout(this.underlineAsync);
343 this.underlineAsync = null;
346 // Focus the input here to bring up the virtual keyboard.
347 this.$.input.focus();
348 this.pressed = false;
349 this.animating = true;
351 this.$.underlineHighlight.classList.remove('pressed');
352 this.$.underlineHighlight.classList.add('animating');
353 this.async(function() {
354 this.$.underlineHighlight.classList.add('focused');
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');
365 if (this.floatingLabel) {
366 this.$.label.classList.add('focusedColor');
367 this.$.label.classList.add('animating');
368 if (!this.$.label.cachedTransform) {
369 this.prepareLabelTransform();
371 this.$.label.style.webkitTransform = this.$.label.cachedTransform;
372 this.$.label.style.transform = this.$.label.cachedTransform;
376 keydownAction: function() {
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);
389 keypressAction: function() {
390 if (this.animating) {
391 this.transitionEndAction();
395 transitionEndAction: function(e) {
396 this.animating = false;
403 if (this.floatingLabel || this.inputValue) {
404 this.$.label.classList.add('hidden');
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');
415 this.async(function() {
416 this.$.underlineHighlight.classList.remove('animating');
417 this.$.caret.classList.remove('animating');
422 this.$.label.classList.remove('animating');