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
12 Material Design: <a href="http://www.google.com/design/spec/components/text-fields.html">Text fields</a>
14 `paper-input-decorator` adds Material Design input field styling and animations to an element.
18 <paper-input-decorator label="Your Name">
19 <input is="core-input">
20 </paper-input-decorator>
22 <paper-input-decorator floatingLabel label="Your address">
24 </paper-input-decorator>
29 `paper-input-decorator` uses `core-style` for global theming. The following options are available:
31 - `CoreStyle.g.paperInput.labelColor` - The inline label, floating label, error message and error icon color when the input does not have focus.
32 - `CoreStyle.g.paperInput.focusedColor` - The floating label and the underline color when the input has focus.
33 - `CoreStyle.g.paperInput.invalidColor` - The error message, the error icon, the floating label and the underline's color when the input is invalid and has focus.
35 To add custom styling to only some elements, use these selectors:
37 paper-input-decorator /deep/ .label-text,
38 paper-input-decorator /deep/ .error {
39 /* inline label, floating label, error message and error icon color when the input is unfocused */
43 paper-input-decorator /deep/ ::-webkit-input-placeholder {
44 /* platform specific rules for placeholder text */
47 paper-input-decorator /deep/ ::-moz-placeholder {
50 paper-input-decorator /deep/ :-ms-input-placeholder {
54 paper-input-decorator /deep/ .unfocused-underline {
55 /* line color when the input is unfocused */
56 background-color: green;
59 paper-input-decorator[focused] /deep/ .floating-label .label-text {
60 /* floating label color when the input is focused */
64 paper-input-decorator /deep/ .focused-underline {
65 /* line color when the input is focused */
66 background-color: orange;
69 paper-input-decorator.invalid[focused] /deep/ .floated-label .label-text,
70 paper-input-decorator[focused] /deep/ .error {
71 /* floating label, error message nad error icon color when the input is invalid and focused */
75 paper-input-decorator.invalid /deep/ .focused-underline {
76 /* line and color when the input is invalid and focused */
77 background-color: salmon;
83 You can use inputs decorated with this element in a `form` as usual.
88 Because you provide the `input` element to `paper-input-decorator`, you can use any validation library
89 or the <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation">HTML5 Constraints Validation API</a>
90 to implement validation. Set the `isInvalid` attribute when the input is validated, and provide an
91 error message in the `error` attribute.
95 <paper-input-decorator id="paper1" error="Value must start with a number!">
96 <input id="input1" is="core-input" pattern="^[0-9].*">
97 </paper-input-decorator>
98 <button onclick="validate()"></button>
100 function validate() {
101 var decorator = document.getElementById('paper1');
102 var input = document.getElementById('input1');
103 decorator.isInvalid = !input.validity.valid;
107 Example to validate as the user types:
109 <template is="auto-binding">
110 <paper-input-decorator id="paper2" error="Value must start with a number!" isInvalid="{{!$.input2.validity.valid}}">
111 <input id="input2" is="core-input" pattern="^[0-9].*">
112 </paper-input-decorator>
118 `paper-input-decorator` will automatically set the `aria-label` attribute on the nested input
119 to the value of `label`. Do not set the `placeholder` attribute on the nested input, as it will
120 conflict with this element.
122 @group Paper Elements
123 @element paper-input-decorator
126 <link href="../polymer/polymer.html" rel="import">
127 <link href="../core-icon/core-icon.html" rel="import">
128 <link href="../core-icons/core-icons.html" rel="import">
129 <link href="../core-input/core-input.html" rel="import">
130 <link href="../core-style/core-style.html" rel="import">
132 <core-style id="paper-input-decorator">
136 color: {{g.paperInput.labelColor}};
139 ::-webkit-input-placeholder {
140 color: {{g.paperInput.labelColor}};
144 color: {{g.paperInput.labelColor}};
147 :-ms-input-placeholder {
148 color: {{g.paperInput.labelColor}};
151 .unfocused-underline {
152 background-color: {{g.paperInput.labelColor}};
155 :host([focused]) .floated-label .label-text {
156 color: {{g.paperInput.focusedColor}};
160 background-color: {{g.paperInput.focusedColor}};
163 :host(.invalid) .floated-label .label-text,
165 color: {{g.paperInput.invalidColor}};
168 :host(.invalid) .unfocused-underline,
169 :host(.invalid) .focused-underline {
170 background-color: {{g.paperInput.invalidColor}};
175 <polymer-element name="paper-input-decorator" layout vertical
176 on-transitionEnd="{{transitionEndAction}}" on-webkitTransitionEnd="{{transitionEndAction}}"
177 on-input="{{inputAction}}"
178 on-down="{{downAction}}">
182 <link href="paper-input-decorator.css" rel="stylesheet">
183 <core-style ref="paper-input-decorator"></core-style>
185 <div class="floated-label" aria-hidden="true" hidden?="{{!floatingLabel}}" invisible?="{{!floatingLabelVisible || labelAnimated}}">
186 <!-- needed for floating label animation measurement -->
187 <span id="floatedLabelText" class="label-text">{{label}}</span>
190 <div class="input-body" flex auto relative>
192 <div class="label" fit invisible aria-hidden="true">
193 <!-- needed for floating label animation measurement -->
194 <span id="labelText" class="label-text" invisible?="{{!_labelVisible}}" animated?="{{labelAnimated}}">{{label}}</span>
201 <div id="underline" class="underline" relative>
202 <div class="unfocused-underline" fit invisible?="{{disabled}}"></div>
203 <div id="focusedUnderline" class="focused-underline" fit invisible?="{{!focused}}" animated?="{{underlineAnimated}}"></div>
206 <div class="error" layout horizontal center hidden?="{{!isInvalid}}">
207 <div class="error-text" flex auto role="alert" aria-hidden="{{!isInvalid}}">{{error}}</div>
208 <core-icon class="error-icon" icon="warning"></core-icon>
217 var paperInput = CoreStyle.g.paperInput = CoreStyle.g.paperInput || {};
219 paperInput.labelColor = '#757575';
220 paperInput.focusedColor = '#4059a9';
221 paperInput.invalidColor = '#d34336';
228 * The label for this input. It normally appears as grey text inside
229 * the text input and disappears once the user enters text.
238 * If true, the label will "float" above the text input once the
239 * user enters text instead of disappearing.
241 * @attribute floatingLabel
245 floatingLabel: false,
248 * Set to true to style the element as disabled.
250 * @attribute disabled
254 disabled: {value: false, reflect: true},
257 * Use this property to override the automatic label visibility.
258 * If this property is set to `true` or `false`, the label visibility
259 * will respect this value instead of be based on whether there is
260 * a non-null value in the input.
262 * @attribute labelVisible
269 * Set this property to true to show the error message.
271 * @attribute isInvalid
278 * The message to display if the input value fails validation. If this
279 * is unset or the empty string, a default message is displayed depending
280 * on the type of validation error.
287 focused: {value: false, reflect: true}
292 floatingLabelVisible: 'floatingLabel && !_labelVisible',
293 _labelVisible: '(labelVisible === true || labelVisible === false) ? labelVisible : _autoLabelVisible'
297 // Delegate focus/blur events
298 Polymer.addEventListener(this, 'focus', this.focusAction.bind(this), true);
299 Polymer.addEventListener(this, 'blur', this.blurAction.bind(this), true);
302 attached: function() {
303 this.input = this.querySelector('input,textarea');
305 this.mo = new MutationObserver(function() {
306 this.input = this.querySelector('input,textarea');
308 this.mo.observe(this, {childList: true});
311 detached: function() {
312 this.mo.disconnect();
316 prepareLabelTransform: function() {
317 var toRect = this.$.floatedLabelText.getBoundingClientRect();
318 var fromRect = this.$.labelText.getBoundingClientRect();
319 if (toRect.width !== 0) {
320 var sy = toRect.height / fromRect.height;
321 this.$.labelText.cachedTransform =
322 'scale3d(' + (toRect.width / fromRect.width) + ',' + sy + ',1) ' +
323 'translate3d(0,' + (toRect.top - fromRect.top) / sy + 'px,0)';
327 animateFloatingLabel: function() {
328 if (!this.floatingLabel || this.labelAnimated) {
332 if (!this.$.labelText.cachedTransform) {
333 this.prepareLabelTransform();
336 // If there's still no cached transform, the input is invisible so don't
338 if (!this.$.labelText.cachedTransform) {
342 this.labelAnimated = true;
343 // Handle interrupted animation
344 this.async(function() {
345 this.transitionEndAction();
348 if (this._labelVisible) {
349 // Handle if the label started out floating
350 if (!this.$.labelText.style.webkitTransform && !this.$.labelText.style.transform) {
351 this.$.labelText.style.webkitTransform = this.$.labelText.cachedTransform;
352 this.$.labelText.style.transform = this.$.labelText.cachedTransform;
353 this.$.labelText.offsetTop;
355 this.$.labelText.style.webkitTransform = '';
356 this.$.labelText.style.transform = '';
358 this.$.labelText.style.webkitTransform = this.$.labelText.cachedTransform;
359 this.$.labelText.style.transform = this.$.labelText.cachedTransform;
360 this.input.placeholder = '';
366 _labelVisibleChanged: function(old) {
367 // do not do the animation on first render
368 if (old !== undefined) {
369 if (!this.animateFloatingLabel()) {
370 this.updateInputLabel(this.input, this.label);
375 labelVisibleChanged: function() {
376 if (this.labelVisible === 'true') {
377 this.labelVisible = true;
378 } else if (this.labelVisible === 'false') {
379 this.labelVisible = false;
383 labelChanged: function() {
385 this.updateInputLabel(this.input, this.label);
389 isInvalidChanged: function() {
390 this.classList.toggle('invalid', this.isInvalid);
393 focusedChanged: function() {
394 this.updateLabelVisibility(this.input && this.input.value);
397 inputChanged: function(old) {
399 this.updateLabelVisibility(this.input.value);
400 this.updateInputLabel(this.input, this.label);
403 this.updateInputLabel(old, '');
407 focusAction: function() {
411 blurAction: function(e) {
412 this.focused = false;
416 * Updates the label visibility based on a value. This is handled automatically
417 * if the user is typing, but if you imperatively set the input value you need
418 * to call this function.
420 * @method updateLabelVisibility
421 * @param {string} value
423 updateLabelVisibility: function(value) {
424 var v = (value !== null && value !== undefined) ? String(value) : value;
425 this._autoLabelVisible = (!this.focused && !v) || (!this.floatingLabel && !v);
428 updateInputLabel: function(input, label) {
429 if (this._labelVisible) {
430 this.input.placeholder = this.label;
432 this.input.placeholder = '';
435 input.setAttribute('aria-label', label);
437 input.removeAttribute('aria-label');
441 inputAction: function(e) {
442 this.updateLabelVisibility(e.target.value);
445 downAction: function(e) {
459 // The underline spills from the tap location
460 var rect = this.$.underline.getBoundingClientRect();
461 var right = e.x - rect.left;
462 this.$.focusedUnderline.style.mozTransformOrigin = right + 'px';
463 this.$.focusedUnderline.style.webkitTransformOrigin = right + 'px ';
464 this.$.focusedUnderline.style.transformOriginX = right + 'px';
466 // Animations only run when the user interacts with the input
467 this.underlineAnimated = true;
469 // Handle interrupted animation
470 this.async(function() {
471 this.transitionEndAction();
475 transitionEndAction: function() {
476 this.underlineAnimated = false;
477 this.labelAnimated = false;
478 if (this._labelVisible) {
479 this.input.placeholder = this.label;