OLD | NEW |
| (Empty) |
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.g
ithub.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/CONTRI
BUTORS | |
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/PATEN
TS | |
8 --> | |
9 | |
10 <!-- | |
11 | |
12 Material Design: <a href="http://www.google.com/design/spec/components/text-fiel
ds.html">Text fields</a> | |
13 | |
14 `paper-input-decorator` adds Material Design input field styling and animations
to an element. | |
15 | |
16 Example: | |
17 | |
18 <paper-input-decorator label="Your Name"> | |
19 <input is="core-input"> | |
20 </paper-input-decorator> | |
21 | |
22 <paper-input-decorator floatingLabel label="Your address"> | |
23 <textarea></textarea> | |
24 </paper-input-decorator> | |
25 | |
26 Theming | |
27 ------- | |
28 | |
29 `paper-input-decorator` uses `core-style` for global theming. The following opti
ons are available: | |
30 | |
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 c
olor 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 focu
s. | |
34 | |
35 To add custom styling to only some elements, use these selectors: | |
36 | |
37 paper-input-decorator /deep/ .label-text, | |
38 paper-input-decorator /deep/ .error { | |
39 /* inline label, floating label, error message and error icon color whe
n the input is unfocused */ | |
40 color: green; | |
41 } | |
42 | |
43 paper-input-decorator /deep/ ::-webkit-input-placeholder { | |
44 /* platform specific rules for placeholder text */ | |
45 color: green; | |
46 } | |
47 paper-input-decorator /deep/ ::-moz-placeholder { | |
48 color: green; | |
49 } | |
50 paper-input-decorator /deep/ :-ms-input-placeholder { | |
51 color: green; | |
52 } | |
53 | |
54 paper-input-decorator /deep/ .unfocused-underline { | |
55 /* line color when the input is unfocused */ | |
56 background-color: green; | |
57 } | |
58 | |
59 paper-input-decorator[focused] /deep/ .floating-label .label-text { | |
60 /* floating label color when the input is focused */ | |
61 color: orange; | |
62 } | |
63 | |
64 paper-input-decorator /deep/ .focused-underline { | |
65 /* line color when the input is focused */ | |
66 background-color: orange; | |
67 } | |
68 | |
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 */ | |
72 color: salmon; | |
73 } | |
74 | |
75 paper-input-decorator.invalid /deep/ .focused-underline { | |
76 /* line and color when the input is invalid and focused */ | |
77 background-color: salmon; | |
78 } | |
79 | |
80 Form submission | |
81 --------------- | |
82 | |
83 You can use inputs decorated with this element in a `form` as usual. | |
84 | |
85 Validation | |
86 ---------- | |
87 | |
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/Co
nstraint_validation">HTML5 Constraints Validation API</a> | |
90 to implement validation. Set the `isInvalid` attribute when the input is validat
ed, and provide an | |
91 error message in the `error` attribute. | |
92 | |
93 Example: | |
94 | |
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> | |
99 <script> | |
100 function validate() { | |
101 var decorator = document.getElementById('paper1'); | |
102 var input = document.getElementById('input1'); | |
103 decorator.isInvalid = !input.validity.valid; | |
104 } | |
105 </script> | |
106 | |
107 Example to validate as the user types: | |
108 | |
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> | |
113 </template> | |
114 | |
115 Accessibility | |
116 ------------- | |
117 | |
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 in
put, as it will | |
120 conflict with this element. | |
121 | |
122 @group Paper Elements | |
123 @element paper-input-decorator | |
124 @homepage github.io | |
125 --> | |
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"> | |
131 | |
132 <core-style id="paper-input-decorator"> | |
133 | |
134 .label-text, | |
135 .error { | |
136 color: {{g.paperInput.labelColor}}; | |
137 } | |
138 | |
139 ::-webkit-input-placeholder { | |
140 color: {{g.paperInput.labelColor}}; | |
141 } | |
142 | |
143 ::-moz-placeholder { | |
144 color: {{g.paperInput.labelColor}}; | |
145 } | |
146 | |
147 :-ms-input-placeholder { | |
148 color: {{g.paperInput.labelColor}}; | |
149 } | |
150 | |
151 .unfocused-underline { | |
152 background-color: {{g.paperInput.labelColor}}; | |
153 } | |
154 | |
155 :host([focused]) .floated-label .label-text { | |
156 color: {{g.paperInput.focusedColor}}; | |
157 } | |
158 | |
159 .focused-underline { | |
160 background-color: {{g.paperInput.focusedColor}}; | |
161 } | |
162 | |
163 :host(.invalid) .floated-label .label-text, | |
164 .error { | |
165 color: {{g.paperInput.invalidColor}}; | |
166 } | |
167 | |
168 :host(.invalid) .unfocused-underline, | |
169 :host(.invalid) .focused-underline { | |
170 background-color: {{g.paperInput.invalidColor}}; | |
171 } | |
172 | |
173 </core-style> | |
174 | |
175 <polymer-element name="paper-input-decorator" layout vertical | |
176 on-transitionEnd="{{transitionEndAction}}" on-webkitTransitionEnd="{{transitio
nEndAction}}" | |
177 on-input="{{inputAction}}" | |
178 on-down="{{downAction}}"> | |
179 | |
180 <template> | |
181 | |
182 <link href="paper-input-decorator.css" rel="stylesheet"> | |
183 <core-style ref="paper-input-decorator"></core-style> | |
184 | |
185 <div class="floated-label" aria-hidden="true" hidden?="{{!floatingLabel}}" i
nvisible?="{{!floatingLabelVisible || labelAnimated}}"> | |
186 <!-- needed for floating label animation measurement --> | |
187 <span id="floatedLabelText" class="label-text">{{label}}</span> | |
188 </div> | |
189 | |
190 <div class="input-body" flex auto relative> | |
191 | |
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> | |
195 </div> | |
196 | |
197 <content></content> | |
198 | |
199 </div> | |
200 | |
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?="{{!fo
cused}}" animated?="{{underlineAnimated}}"></div> | |
204 </div> | |
205 | |
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> | |
209 </div> | |
210 | |
211 </template> | |
212 | |
213 <script> | |
214 | |
215 (function() { | |
216 | |
217 var paperInput = CoreStyle.g.paperInput = CoreStyle.g.paperInput || {}; | |
218 | |
219 paperInput.labelColor = '#757575'; | |
220 paperInput.focusedColor = '#4059a9'; | |
221 paperInput.invalidColor = '#d34336'; | |
222 | |
223 Polymer({ | |
224 | |
225 publish: { | |
226 | |
227 /** | |
228 * The label for this input. It normally appears as grey text inside | |
229 * the text input and disappears once the user enters text. | |
230 * | |
231 * @attribute label | |
232 * @type string | |
233 * @default '' | |
234 */ | |
235 label: '', | |
236 | |
237 /** | |
238 * If true, the label will "float" above the text input once the | |
239 * user enters text instead of disappearing. | |
240 * | |
241 * @attribute floatingLabel | |
242 * @type boolean | |
243 * @default false | |
244 */ | |
245 floatingLabel: false, | |
246 | |
247 /** | |
248 * Set to true to style the element as disabled. | |
249 * | |
250 * @attribute disabled | |
251 * @type boolean | |
252 * @default false | |
253 */ | |
254 disabled: {value: false, reflect: true}, | |
255 | |
256 /** | |
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. | |
261 * | |
262 * @attribute labelVisible | |
263 * @type boolean | |
264 * @default false | |
265 */ | |
266 labelVisible: null, | |
267 | |
268 /** | |
269 * Set this property to true to show the error message. | |
270 * | |
271 * @attribute isInvalid | |
272 * @type boolean | |
273 * @default false | |
274 */ | |
275 isInvalid: false, | |
276 | |
277 /** | |
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 dependin
g | |
280 * on the type of validation error. | |
281 * | |
282 * @attribute error | |
283 * @type string | |
284 */ | |
285 error: '', | |
286 | |
287 focused: {value: false, reflect: true} | |
288 | |
289 }, | |
290 | |
291 computed: { | |
292 floatingLabelVisible: 'floatingLabel && !_labelVisible', | |
293 _labelVisible: '(labelVisible === true || labelVisible === false) ? labe
lVisible : _autoLabelVisible' | |
294 }, | |
295 | |
296 ready: function() { | |
297 // Delegate focus/blur events | |
298 Polymer.addEventListener(this, 'focus', this.focusAction.bind(this), tru
e); | |
299 Polymer.addEventListener(this, 'blur', this.blurAction.bind(this), true)
; | |
300 }, | |
301 | |
302 attached: function() { | |
303 this.input = this.querySelector('input,textarea'); | |
304 | |
305 this.mo = new MutationObserver(function() { | |
306 this.input = this.querySelector('input,textarea'); | |
307 }.bind(this)); | |
308 this.mo.observe(this, {childList: true}); | |
309 }, | |
310 | |
311 detached: function() { | |
312 this.mo.disconnect(); | |
313 this.mo = null; | |
314 }, | |
315 | |
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)'; | |
324 } | |
325 }, | |
326 | |
327 animateFloatingLabel: function() { | |
328 if (!this.floatingLabel || this.labelAnimated) { | |
329 return false; | |
330 } | |
331 | |
332 if (!this.$.labelText.cachedTransform) { | |
333 this.prepareLabelTransform(); | |
334 } | |
335 | |
336 // If there's still no cached transform, the input is invisible so don't | |
337 // do the animation. | |
338 if (!this.$.labelText.cachedTransform) { | |
339 return false; | |
340 } | |
341 | |
342 this.labelAnimated = true; | |
343 // Handle interrupted animation | |
344 this.async(function() { | |
345 this.transitionEndAction(); | |
346 }, null, 250); | |
347 | |
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.cachedTran
sform; | |
352 this.$.labelText.style.transform = this.$.labelText.cachedTransform; | |
353 this.$.labelText.offsetTop; | |
354 } | |
355 this.$.labelText.style.webkitTransform = ''; | |
356 this.$.labelText.style.transform = ''; | |
357 } else { | |
358 this.$.labelText.style.webkitTransform = this.$.labelText.cachedTransf
orm; | |
359 this.$.labelText.style.transform = this.$.labelText.cachedTransform; | |
360 this.input.placeholder = ''; | |
361 } | |
362 | |
363 return true; | |
364 }, | |
365 | |
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); | |
371 } | |
372 } | |
373 }, | |
374 | |
375 labelVisibleChanged: function() { | |
376 if (this.labelVisible === 'true') { | |
377 this.labelVisible = true; | |
378 } else if (this.labelVisible === 'false') { | |
379 this.labelVisible = false; | |
380 } | |
381 }, | |
382 | |
383 labelChanged: function() { | |
384 if (this.input) { | |
385 this.updateInputLabel(this.input, this.label); | |
386 } | |
387 }, | |
388 | |
389 isInvalidChanged: function() { | |
390 this.classList.toggle('invalid', this.isInvalid); | |
391 }, | |
392 | |
393 focusedChanged: function() { | |
394 this.updateLabelVisibility(this.input && this.input.value); | |
395 }, | |
396 | |
397 inputChanged: function(old) { | |
398 if (this.input) { | |
399 this.updateLabelVisibility(this.input.value); | |
400 this.updateInputLabel(this.input, this.label); | |
401 } | |
402 if (old) { | |
403 this.updateInputLabel(old, ''); | |
404 } | |
405 }, | |
406 | |
407 focusAction: function() { | |
408 this.focused = true; | |
409 }, | |
410 | |
411 blurAction: function(e) { | |
412 this.focused = false; | |
413 }, | |
414 | |
415 /** | |
416 * Updates the label visibility based on a value. This is handled automati
cally | |
417 * if the user is typing, but if you imperatively set the input value you
need | |
418 * to call this function. | |
419 * | |
420 * @method updateLabelVisibility | |
421 * @param {string} value | |
422 */ | |
423 updateLabelVisibility: function(value) { | |
424 var v = (value !== null && value !== undefined) ? String(value) : value; | |
425 this._autoLabelVisible = (!this.focused && !v) || (!this.floatingLabel &
& !v); | |
426 }, | |
427 | |
428 updateInputLabel: function(input, label) { | |
429 if (this._labelVisible) { | |
430 this.input.placeholder = this.label; | |
431 } else { | |
432 this.input.placeholder = ''; | |
433 } | |
434 if (label) { | |
435 input.setAttribute('aria-label', label); | |
436 } else { | |
437 input.removeAttribute('aria-label'); | |
438 } | |
439 }, | |
440 | |
441 inputAction: function(e) { | |
442 this.updateLabelVisibility(e.target.value); | |
443 }, | |
444 | |
445 downAction: function(e) { | |
446 if (this.disabled) { | |
447 return; | |
448 } | |
449 | |
450 if (this.focused) { | |
451 return; | |
452 } | |
453 | |
454 if (this.input) { | |
455 this.input.focus(); | |
456 e.preventDefault(); | |
457 } | |
458 | |
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'; | |
465 | |
466 // Animations only run when the user interacts with the input | |
467 this.underlineAnimated = true; | |
468 | |
469 // Handle interrupted animation | |
470 this.async(function() { | |
471 this.transitionEndAction(); | |
472 }, null, 250); | |
473 }, | |
474 | |
475 transitionEndAction: function() { | |
476 this.underlineAnimated = false; | |
477 this.labelAnimated = false; | |
478 if (this._labelVisible) { | |
479 this.input.placeholder = this.label; | |
480 } | |
481 } | |
482 | |
483 }); | |
484 | |
485 }()); | |
486 | |
487 </script> | |
488 | |
489 </polymer-element> | |
OLD | NEW |