• 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      },
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