1 <!-- 2 @license 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 Code distributed by Google as part of the polymer project is also 8 subject 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 }, 217 218 /** 219 * If you're using PaperInputBehavior to implement your own paper-input-like 220 * element, bind this to the `<input is="iron-input">`'s `inputmode` property. 221 */ 222 inputmode: { 223 type: String 224 }, 225 226 /** 227 * The minimum length of the input value. 228 * If you're using PaperInputBehavior to implement your own paper-input-like 229 * element, bind this to the `<input is="iron-input">`'s `minlength` property. 230 */ 231 minlength: { 232 type: Number 233 }, 234 235 /** 236 * The maximum length of the input value. 237 * If you're using PaperInputBehavior to implement your own paper-input-like 238 * element, bind this to the `<input is="iron-input">`'s `maxlength` property. 239 */ 240 maxlength: { 241 type: Number 242 }, 243 244 /** 245 * The minimum (numeric or date-time) input value. 246 * If you're using PaperInputBehavior to implement your own paper-input-like 247 * element, bind this to the `<input is="iron-input">`'s `min` property. 248 */ 249 min: { 250 type: String 251 }, 252 253 /** 254 * The maximum (numeric or date-time) input value. 255 * Can be a String (e.g. `"2000-1-1"`) or a Number (e.g. `2`). 256 * If you're using PaperInputBehavior to implement your own paper-input-like 257 * element, bind this to the `<input is="iron-input">`'s `max` property. 258 */ 259 max: { 260 type: String 261 }, 262 263 /** 264 * Limits the numeric or date-time increments. 265 * If you're using PaperInputBehavior to implement your own paper-input-like 266 * element, bind this to the `<input is="iron-input">`'s `step` property. 267 */ 268 step: { 269 type: String 270 }, 271 272 /** 273 * If you're using PaperInputBehavior to implement your own paper-input-like 274 * element, bind this to the `<input is="iron-input">`'s `name` property. 275 */ 276 name: { 277 type: String 278 }, 279 280 /** 281 * A placeholder string in addition to the label. If this is set, the label will always float. 282 */ 283 placeholder: { 284 type: String, 285 // need to set a default so _computeAlwaysFloatLabel is run 286 value: '' 287 }, 288 289 /** 290 * If you're using PaperInputBehavior to implement your own paper-input-like 291 * element, bind this to the `<input is="iron-input">`'s `readonly` property. 292 */ 293 readonly: { 294 type: Boolean, 295 value: false 296 }, 297 298 /** 299 * If you're using PaperInputBehavior to implement your own paper-input-like 300 * element, bind this to the `<input is="iron-input">`'s `size` property. 301 */ 302 size: { 303 type: Number 304 }, 305 306 // Nonstandard attributes for binding if needed 307 308 /** 309 * If you're using PaperInputBehavior to implement your own paper-input-like 310 * element, bind this to the `<input is="iron-input">`'s `autocapitalize` property. 311 */ 312 autocapitalize: { 313 type: String, 314 value: 'none' 315 }, 316 317 /** 318 * If you're using PaperInputBehavior to implement your own paper-input-like 319 * element, bind this to the `<input is="iron-input">`'s `autocorrect` property. 320 */ 321 autocorrect: { 322 type: String, 323 value: 'off' 324 }, 325 326 /** 327 * If you're using PaperInputBehavior to implement your own paper-input-like 328 * element, bind this to the `<input is="iron-input">`'s `autosave` property, 329 * used with type=search. 330 */ 331 autosave: { 332 type: String 333 }, 334 335 /** 336 * If you're using PaperInputBehavior to implement your own paper-input-like 337 * element, bind this to the `<input is="iron-input">`'s `results` property, 338 * used with type=search. 339 */ 340 results: { 341 type: Number 342 }, 343 344 /** 345 * If you're using PaperInputBehavior to implement your own paper-input-like 346 * element, bind this to the `<input is="iron-input">`'s `accept` property, 347 * used with type=file. 348 */ 349 accept: { 350 type: String 351 }, 352 353 /** 354 * If you're using PaperInputBehavior to implement your own paper-input-like 355 * element, bind this to the`<input is="iron-input">`'s `multiple` property, 356 * used with type=file. 357 */ 358 multiple: { 359 type: Boolean 360 }, 361 362 _ariaDescribedBy: { 363 type: String, 364 value: '' 365 }, 366 367 _ariaLabelledBy: { 368 type: String, 369 value: '' 370 } 371 372 }, 373 374 listeners: { 375 'addon-attached': '_onAddonAttached', 376 }, 377 378 keyBindings: { 379 'shift+tab:keydown': '_onShiftTabDown' 380 }, 381 382 hostAttributes: { 383 tabindex: 0 384 }, 385 386 /** 387 * Returns a reference to the input element. 388 */ 389 get inputElement() { 390 return this.$.input; 391 }, 392 393 /** 394 * Returns a reference to the focusable element. 395 */ 396 get _focusableElement() { 397 return this.inputElement; 398 }, 399 400 registered: function() { 401 // These types have some default placeholder text; overlapping 402 // the label on top of it looks terrible. Auto-float the label in this case. 403 this._typesThatHaveText = ["date", "datetime", "datetime-local", "month", 404 "time", "week", "file"]; 405 }, 406 407 attached: function() { 408 this._updateAriaLabelledBy(); 409 410 if (this.inputElement && 411 this._typesThatHaveText.indexOf(this.inputElement.type) !== -1) { 412 this.alwaysFloatLabel = true; 413 } 414 }, 415 416 _appendStringWithSpace: function(str, more) { 417 if (str) { 418 str = str + ' ' + more; 419 } else { 420 str = more; 421 } 422 return str; 423 }, 424 425 _onAddonAttached: function(event) { 426 var target = event.path ? event.path[0] : event.target; 427 if (target.id) { 428 this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, target.id); 429 } else { 430 var id = 'paper-input-add-on-' + Polymer.PaperInputHelper.NextAddonID++; 431 target.id = id; 432 this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, id); 433 } 434 }, 435 436 /** 437 * Validates the input element and sets an error style if needed. 438 * 439 * @return {boolean} 440 */ 441 validate: function() { 442 return this.inputElement.validate(); 443 }, 444 445 /** 446 * Forward focus to inputElement. Overriden from IronControlState. 447 */ 448 _focusBlurHandler: function(event) { 449 Polymer.IronControlState._focusBlurHandler.call(this, event); 450 451 // Forward the focus to the nested input. 452 if (this.focused && !this._shiftTabPressed) 453 this._focusableElement.focus(); 454 }, 455 456 /** 457 * Handler that is called when a shift+tab keypress is detected by the menu. 458 * 459 * @param {CustomEvent} event A key combination event. 460 */ 461 _onShiftTabDown: function(event) { 462 var oldTabIndex = this.getAttribute('tabindex'); 463 this._shiftTabPressed = true; 464 this.setAttribute('tabindex', '-1'); 465 this.async(function() { 466 this.setAttribute('tabindex', oldTabIndex); 467 this._shiftTabPressed = false; 468 }, 1); 469 }, 470 471 /** 472 * If `autoValidate` is true, then validates the element. 473 */ 474 _handleAutoValidate: function() { 475 if (this.autoValidate) 476 this.validate(); 477 }, 478 479 /** 480 * Restores the cursor to its original position after updating the value. 481 * @param {string} newValue The value that should be saved. 482 */ 483 updateValueAndPreserveCaret: function(newValue) { 484 // Not all elements might have selection, and even if they have the 485 // right properties, accessing them might throw an exception (like for 486 // <input type=number>) 487 try { 488 var start = this.inputElement.selectionStart; 489 this.value = newValue; 490 491 // The cursor automatically jumps to the end after re-setting the value, 492 // so restore it to its original position. 493 this.inputElement.selectionStart = start; 494 this.inputElement.selectionEnd = start; 495 } catch (e) { 496 // Just set the value and give up on the caret. 497 this.value = newValue; 498 } 499 }, 500 501 _computeAlwaysFloatLabel: function(alwaysFloatLabel, placeholder) { 502 return placeholder || alwaysFloatLabel; 503 }, 504 505 _updateAriaLabelledBy: function() { 506 var label = Polymer.dom(this.root).querySelector('label'); 507 if (!label) { 508 this._ariaLabelledBy = ''; 509 return; 510 } 511 var labelledBy; 512 if (label.id) { 513 labelledBy = label.id; 514 } else { 515 labelledBy = 'paper-input-label-' + Polymer.PaperInputHelper.NextLabelID++; 516 label.id = labelledBy; 517 } 518 this._ariaLabelledBy = labelledBy; 519 }, 520 521 _onChange:function(event) { 522 // In the Shadow DOM, the `change` event is not leaked into the 523 // ancestor tree, so we must do this manually. 524 // See https://w3c.github.io/webcomponents/spec/shadow/#events-that-are-not-leaked-into-ancestor-trees. 525 if (this.shadowRoot) { 526 this.fire(event.type, {sourceEvent: event}, { 527 node: this, 528 bubbles: event.bubbles, 529 cancelable: event.cancelable 530 }); 531 } 532 } 533 } 534 535 /** @polymerBehavior */ 536 Polymer.PaperInputBehavior = [ 537 Polymer.IronControlState, 538 Polymer.IronA11yKeysBehavior, 539 Polymer.PaperInputBehaviorImpl 540 ]; 541 </script> 542