| 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 |