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 |