• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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