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 `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.invalidCol
or` 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 float
ingLabel 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-webkitTran
sitionEnd="{{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"> </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 || validat
ionMessage}}</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.wid
th) + ') ' + | |
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 += ' '; | |
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 n
umber | |
270 // is entered so we must do some hacks here | |
271 return this.inputValue || (this.type === 'number' && !this.validity.vali
d); | |
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> | |
OLD | NEW |