1<!-- 2@license 3Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7Code distributed by Google as part of the polymer project is also 8subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9--> 10 11<link rel="import" href="../polymer/polymer.html"> 12<link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html"> 13<link rel="import" href="../iron-behaviors/iron-control-state.html"> 14 15<script> 16 17 // Generate unique, monotonically increasing IDs for labels (needed by 18 // aria-labelledby) and add-ons. 19 Polymer.PaperInputHelper = {}; 20 Polymer.PaperInputHelper.NextLabelID = 1; 21 Polymer.PaperInputHelper.NextAddonID = 1; 22 23 /** 24 * Use `Polymer.PaperInputBehavior` to implement inputs with `<paper-input-container>`. This 25 * behavior is implemented by `<paper-input>`. It exposes a number of properties from 26 * `<paper-input-container>` and `<input is="iron-input">` and they should be bound in your 27 * template. 28 * 29 * The input element can be accessed by the `inputElement` property if you need to access 30 * properties or methods that are not exposed. 31 * @polymerBehavior Polymer.PaperInputBehavior 32 */ 33 Polymer.PaperInputBehaviorImpl = { 34 35 properties: { 36 /** 37 * Fired when the input changes due to user interaction. 38 * 39 * @event change 40 */ 41 42 /** 43 * The label for this input. If you're using PaperInputBehavior to 44 * implement your own paper-input-like element, bind this to 45 * `<label>`'s content and `hidden` property, e.g. 46 * `<label hidden$="[[!label]]">[[label]]</label>` in your `template` 47 */ 48 label: { 49 type: String 50 }, 51 52 /** 53 * The value for this input. If you're using PaperInputBehavior to 54 * implement your own paper-input-like element, bind this to 55 * the `<input is="iron-input">`'s `bindValue` 56 * property, or the value property of your input that is `notify:true`. 57 */ 58 value: { 59 notify: true, 60 type: String 61 }, 62 63 /** 64 * Set to true to disable this input. If you're using PaperInputBehavior to 65 * implement your own paper-input-like element, bind this to 66 * both the `<paper-input-container>`'s and the input's `disabled` property. 67 */ 68 disabled: { 69 type: Boolean, 70 value: false 71 }, 72 73 /** 74 * Returns true if the value is invalid. If you're using PaperInputBehavior to 75 * implement your own paper-input-like element, bind this to both the 76 * `<paper-input-container>`'s and the input's `invalid` property. 77 * 78 * If `autoValidate` is true, the `invalid` attribute is managed automatically, 79 * which can clobber attempts to manage it manually. 80 */ 81 invalid: { 82 type: Boolean, 83 value: false, 84 notify: true 85 }, 86 87 /** 88 * Set to true to prevent the user from entering invalid input. If you're 89 * using PaperInputBehavior to implement your own paper-input-like element, 90 * bind this to `<input is="iron-input">`'s `preventInvalidInput` property. 91 */ 92 preventInvalidInput: { 93 type: Boolean 94 }, 95 96 /** 97 * Set this to specify the pattern allowed by `preventInvalidInput`. If 98 * you're using PaperInputBehavior to implement your own paper-input-like 99 * element, bind this to the `<input is="iron-input">`'s `allowedPattern` 100 * property. 101 */ 102 allowedPattern: { 103 type: String 104 }, 105 106 /** 107 * The type of the input. The supported types are `text`, `number` and `password`. 108 * If you're using PaperInputBehavior to implement your own paper-input-like element, 109 * bind this to the `<input is="iron-input">`'s `type` property. 110 */ 111 type: { 112 type: String 113 }, 114 115 /** 116 * The datalist of the input (if any). This should match the id of an existing `<datalist>`. 117 * If you're using PaperInputBehavior to implement your own paper-input-like 118 * element, bind this to the `<input is="iron-input">`'s `list` property. 119 */ 120 list: { 121 type: String 122 }, 123 124 /** 125 * A pattern to validate the `input` with. If you're using PaperInputBehavior to 126 * implement your own paper-input-like element, bind this to 127 * the `<input is="iron-input">`'s `pattern` property. 128 */ 129 pattern: { 130 type: String 131 }, 132 133 /** 134 * Set to true to mark the input as required. If you're using PaperInputBehavior to 135 * implement your own paper-input-like element, bind this to 136 * the `<input is="iron-input">`'s `required` property. 137 */ 138 required: { 139 type: Boolean, 140 value: false 141 }, 142 143 /** 144 * The error message to display when the input is invalid. If you're using 145 * PaperInputBehavior to implement your own paper-input-like element, 146 * bind this to the `<paper-input-error>`'s content, if using. 147 */ 148 errorMessage: { 149 type: String 150 }, 151 152 /** 153 * Set to true to show a character counter. 154 */ 155 charCounter: { 156 type: Boolean, 157 value: false 158 }, 159 160 /** 161 * Set to true to disable the floating label. If you're using PaperInputBehavior to 162 * implement your own paper-input-like element, bind this to 163 * the `<paper-input-container>`'s `noLabelFloat` property. 164 */ 165 noLabelFloat: { 166 type: Boolean, 167 value: false 168 }, 169 170 /** 171 * Set to true to always float the label. If you're using PaperInputBehavior to 172 * implement your own paper-input-like element, bind this to 173 * the `<paper-input-container>`'s `alwaysFloatLabel` property. 174 */ 175 alwaysFloatLabel: { 176 type: Boolean, 177 value: false 178 }, 179 180 /** 181 * Set to true to auto-validate the input value. If you're using PaperInputBehavior to 182 * implement your own paper-input-like element, bind this to 183 * the `<paper-input-container>`'s `autoValidate` property. 184 */ 185 autoValidate: { 186 type: Boolean, 187 value: false 188 }, 189 190 /** 191 * Name of the validator to use. If you're using PaperInputBehavior to 192 * implement your own paper-input-like element, bind this to 193 * the `<input is="iron-input">`'s `validator` property. 194 */ 195 validator: { 196 type: String 197 }, 198 199 // HTMLInputElement attributes for binding if needed 200 201 /** 202 * If you're using PaperInputBehavior to implement your own paper-input-like 203 * element, bind this to the `<input is="iron-input">`'s `autocomplete` property. 204 */ 205 autocomplete: { 206 type: String, 207 value: 'off' 208 }, 209 210 /** 211 * If you're using PaperInputBehavior to implement your own paper-input-like 212 * element, bind this to the `<input is="iron-input">`'s `autofocus` property. 213 */ 214 autofocus: { 215 type: Boolean, 216 observer: '_autofocusChanged' 217 }, 218 219 /** 220 * If you're using PaperInputBehavior to implement your own paper-input-like 221 * element, bind this to the `<input is="iron-input">`'s `inputmode` property. 222 */ 223 inputmode: { 224 type: String 225 }, 226 227 /** 228 * The minimum length of the input value. 229 * If you're using PaperInputBehavior to implement your own paper-input-like 230 * element, bind this to the `<input is="iron-input">`'s `minlength` property. 231 */ 232 minlength: { 233 type: Number 234 }, 235 236 /** 237 * The maximum length of the input value. 238 * If you're using PaperInputBehavior to implement your own paper-input-like 239 * element, bind this to the `<input is="iron-input">`'s `maxlength` property. 240 */ 241 maxlength: { 242 type: Number 243 }, 244 245 /** 246 * The minimum (numeric or date-time) input value. 247 * If you're using PaperInputBehavior to implement your own paper-input-like 248 * element, bind this to the `<input is="iron-input">`'s `min` property. 249 */ 250 min: { 251 type: String 252 }, 253 254 /** 255 * The maximum (numeric or date-time) input value. 256 * Can be a String (e.g. `"2000-01-01"`) or a Number (e.g. `2`). 257 * If you're using PaperInputBehavior to implement your own paper-input-like 258 * element, bind this to the `<input is="iron-input">`'s `max` property. 259 */ 260 max: { 261 type: String 262 }, 263 264 /** 265 * Limits the numeric or date-time increments. 266 * If you're using PaperInputBehavior to implement your own paper-input-like 267 * element, bind this to the `<input is="iron-input">`'s `step` property. 268 */ 269 step: { 270 type: String 271 }, 272 273 /** 274 * If you're using PaperInputBehavior to implement your own paper-input-like 275 * element, bind this to the `<input is="iron-input">`'s `name` property. 276 */ 277 name: { 278 type: String 279 }, 280 281 /** 282 * A placeholder string in addition to the label. If this is set, the label will always float. 283 */ 284 placeholder: { 285 type: String, 286 // need to set a default so _computeAlwaysFloatLabel is run 287 value: '' 288 }, 289 290 /** 291 * If you're using PaperInputBehavior to implement your own paper-input-like 292 * element, bind this to the `<input is="iron-input">`'s `readonly` property. 293 */ 294 readonly: { 295 type: Boolean, 296 value: false 297 }, 298 299 /** 300 * If you're using PaperInputBehavior to implement your own paper-input-like 301 * element, bind this to the `<input is="iron-input">`'s `size` property. 302 */ 303 size: { 304 type: Number 305 }, 306 307 // Nonstandard attributes for binding if needed 308 309 /** 310 * If you're using PaperInputBehavior to implement your own paper-input-like 311 * element, bind this to the `<input is="iron-input">`'s `autocapitalize` property. 312 */ 313 autocapitalize: { 314 type: String, 315 value: 'none' 316 }, 317 318 /** 319 * If you're using PaperInputBehavior to implement your own paper-input-like 320 * element, bind this to the `<input is="iron-input">`'s `autocorrect` property. 321 */ 322 autocorrect: { 323 type: String, 324 value: 'off' 325 }, 326 327 /** 328 * If you're using PaperInputBehavior to implement your own paper-input-like 329 * element, bind this to the `<input is="iron-input">`'s `autosave` property, 330 * used with type=search. 331 */ 332 autosave: { 333 type: String 334 }, 335 336 /** 337 * If you're using PaperInputBehavior to implement your own paper-input-like 338 * element, bind this to the `<input is="iron-input">`'s `results` property, 339 * used with type=search. 340 */ 341 results: { 342 type: Number 343 }, 344 345 /** 346 * If you're using PaperInputBehavior to implement your own paper-input-like 347 * element, bind this to the `<input is="iron-input">`'s `accept` property, 348 * used with type=file. 349 */ 350 accept: { 351 type: String 352 }, 353 354 /** 355 * If you're using PaperInputBehavior to implement your own paper-input-like 356 * element, bind this to the`<input is="iron-input">`'s `multiple` property, 357 * used with type=file. 358 */ 359 multiple: { 360 type: Boolean 361 }, 362 363 _ariaDescribedBy: { 364 type: String, 365 value: '' 366 }, 367 368 _ariaLabelledBy: { 369 type: String, 370 value: '' 371 } 372 373 }, 374 375 listeners: { 376 'addon-attached': '_onAddonAttached', 377 }, 378 379 keyBindings: { 380 'shift+tab:keydown': '_onShiftTabDown' 381 }, 382 383 hostAttributes: { 384 tabindex: 0 385 }, 386 387 /** 388 * Returns a reference to the input element. 389 */ 390 get inputElement() { 391 return this.$.input; 392 }, 393 394 /** 395 * Returns a reference to the focusable element. 396 */ 397 get _focusableElement() { 398 return this.inputElement; 399 }, 400 401 registered: function() { 402 // These types have some default placeholder text; overlapping 403 // the label on top of it looks terrible. Auto-float the label in this case. 404 this._typesThatHaveText = ["date", "datetime", "datetime-local", "month", 405 "time", "week", "file"]; 406 }, 407 408 attached: function() { 409 this._updateAriaLabelledBy(); 410 411 if (this.inputElement && 412 this._typesThatHaveText.indexOf(this.inputElement.type) !== -1) { 413 this.alwaysFloatLabel = true; 414 } 415 }, 416 417 _appendStringWithSpace: function(str, more) { 418 if (str) { 419 str = str + ' ' + more; 420 } else { 421 str = more; 422 } 423 return str; 424 }, 425 426 _onAddonAttached: function(event) { 427 var target = event.path ? event.path[0] : event.target; 428 if (target.id) { 429 this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, target.id); 430 } else { 431 var id = 'paper-input-add-on-' + Polymer.PaperInputHelper.NextAddonID++; 432 target.id = id; 433 this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, id); 434 } 435 }, 436 437 /** 438 * Validates the input element and sets an error style if needed. 439 * 440 * @return {boolean} 441 */ 442 validate: function() { 443 return this.inputElement.validate(); 444 }, 445 446 /** 447 * Forward focus to inputElement. Overriden from IronControlState. 448 */ 449 _focusBlurHandler: function(event) { 450 Polymer.IronControlState._focusBlurHandler.call(this, event); 451 452 // Forward the focus to the nested input. 453 if (this.focused && !this._shiftTabPressed) 454 this._focusableElement.focus(); 455 }, 456 457 /** 458 * Handler that is called when a shift+tab keypress is detected by the menu. 459 * 460 * @param {CustomEvent} event A key combination event. 461 */ 462 _onShiftTabDown: function(event) { 463 var oldTabIndex = this.getAttribute('tabindex'); 464 this._shiftTabPressed = true; 465 this.setAttribute('tabindex', '-1'); 466 this.async(function() { 467 this.setAttribute('tabindex', oldTabIndex); 468 this._shiftTabPressed = false; 469 }, 1); 470 }, 471 472 /** 473 * If `autoValidate` is true, then validates the element. 474 */ 475 _handleAutoValidate: function() { 476 if (this.autoValidate) 477 this.validate(); 478 }, 479 480 /** 481 * Restores the cursor to its original position after updating the value. 482 * @param {string} newValue The value that should be saved. 483 */ 484 updateValueAndPreserveCaret: function(newValue) { 485 // Not all elements might have selection, and even if they have the 486 // right properties, accessing them might throw an exception (like for 487 // <input type=number>) 488 try { 489 var start = this.inputElement.selectionStart; 490 this.value = newValue; 491 492 // The cursor automatically jumps to the end after re-setting the value, 493 // so restore it to its original position. 494 this.inputElement.selectionStart = start; 495 this.inputElement.selectionEnd = start; 496 } catch (e) { 497 // Just set the value and give up on the caret. 498 this.value = newValue; 499 } 500 }, 501 502 _computeAlwaysFloatLabel: function(alwaysFloatLabel, placeholder) { 503 return placeholder || alwaysFloatLabel; 504 }, 505 506 _updateAriaLabelledBy: function() { 507 var label = Polymer.dom(this.root).querySelector('label'); 508 if (!label) { 509 this._ariaLabelledBy = ''; 510 return; 511 } 512 var labelledBy; 513 if (label.id) { 514 labelledBy = label.id; 515 } else { 516 labelledBy = 'paper-input-label-' + Polymer.PaperInputHelper.NextLabelID++; 517 label.id = labelledBy; 518 } 519 this._ariaLabelledBy = labelledBy; 520 }, 521 522 _onChange:function(event) { 523 // In the Shadow DOM, the `change` event is not leaked into the 524 // ancestor tree, so we must do this manually. 525 // See https://w3c.github.io/webcomponents/spec/shadow/#events-that-are-not-leaked-into-ancestor-trees. 526 if (this.shadowRoot) { 527 this.fire(event.type, {sourceEvent: event}, { 528 node: this, 529 bubbles: event.bubbles, 530 cancelable: event.cancelable 531 }); 532 } 533 }, 534 535 _autofocusChanged: function() { 536 // Firefox doesn't respect the autofocus attribute if it's applied after 537 // the page is loaded (Chrome/WebKit do respect it), preventing an 538 // autofocus attribute specified in markup from taking effect when the 539 // element is upgraded. As a workaround, if the autofocus property is set, 540 // and the focus hasn't already been moved elsewhere, we take focus. 541 if (this.autofocus && this._focusableElement) { 542 543 // In IE 11, the default document.activeElement can be the page's 544 // outermost html element, but there are also cases (under the 545 // polyfill?) in which the activeElement is not a real HTMLElement, but 546 // just a plain object. We identify the latter case as having no valid 547 // activeElement. 548 var activeElement = document.activeElement; 549 var isActiveElementValid = activeElement instanceof HTMLElement; 550 551 // Has some other element has already taken the focus? 552 var isSomeElementActive = isActiveElementValid && 553 activeElement !== document.body && 554 activeElement !== document.documentElement; /* IE 11 */ 555 if (!isSomeElementActive) { 556 // No specific element has taken the focus yet, so we can take it. 557 this._focusableElement.focus(); 558 } 559 } 560 } 561 }; 562 563 /** @polymerBehavior */ 564 Polymer.PaperInputBehavior = [ 565 Polymer.IronControlState, 566 Polymer.IronA11yKeysBehavior, 567 Polymer.PaperInputBehaviorImpl 568 ]; 569</script> 570