| <!-- |
| @license |
| Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
| The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
| Code distributed by Google as part of the polymer project is also |
| subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
| --> |
| |
| <link rel="import" href="../polymer/polymer.html"> |
| <link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html"> |
| <link rel="import" href="../iron-behaviors/iron-control-state.html"> |
| |
| <script> |
| |
| // Generate unique, monotonically increasing IDs for labels (needed by |
| // aria-labelledby) and add-ons. |
| Polymer.PaperInputHelper = {}; |
| Polymer.PaperInputHelper.NextLabelID = 1; |
| Polymer.PaperInputHelper.NextAddonID = 1; |
| |
| /** |
| * Use `Polymer.PaperInputBehavior` to implement inputs with `<paper-input-container>`. This |
| * behavior is implemented by `<paper-input>`. It exposes a number of properties from |
| * `<paper-input-container>` and `<input is="iron-input">` and they should be bound in your |
| * template. |
| * |
| * The input element can be accessed by the `inputElement` property if you need to access |
| * properties or methods that are not exposed. |
| * @polymerBehavior Polymer.PaperInputBehavior |
| */ |
| Polymer.PaperInputBehaviorImpl = { |
| |
| properties: { |
| /** |
| * Fired when the input changes due to user interaction. |
| * |
| * @event change |
| */ |
| |
| /** |
| * The label for this input. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * `<label>`'s content and `hidden` property, e.g. |
| * `<label hidden$="[[!label]]">[[label]]</label>` in your `template` |
| */ |
| label: { |
| type: String |
| }, |
| |
| /** |
| * The value for this input. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * the `<input is="iron-input">`'s `bindValue` |
| * property, or the value property of your input that is `notify:true`. |
| */ |
| value: { |
| notify: true, |
| type: String |
| }, |
| |
| /** |
| * Set to true to disable this input. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * both the `<paper-input-container>`'s and the input's `disabled` property. |
| */ |
| disabled: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * Returns true if the value is invalid. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to both the |
| * `<paper-input-container>`'s and the input's `invalid` property. |
| * |
| * If `autoValidate` is true, the `invalid` attribute is managed automatically, |
| * which can clobber attempts to manage it manually. |
| */ |
| invalid: { |
| type: Boolean, |
| value: false, |
| notify: true |
| }, |
| |
| /** |
| * Set to true to prevent the user from entering invalid input. If you're |
| * using PaperInputBehavior to implement your own paper-input-like element, |
| * bind this to `<input is="iron-input">`'s `preventInvalidInput` property. |
| */ |
| preventInvalidInput: { |
| type: Boolean |
| }, |
| |
| /** |
| * Set this to specify the pattern allowed by `preventInvalidInput`. If |
| * you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `allowedPattern` |
| * property. |
| */ |
| allowedPattern: { |
| type: String |
| }, |
| |
| /** |
| * The type of the input. The supported types are `text`, `number` and `password`. |
| * If you're using PaperInputBehavior to implement your own paper-input-like element, |
| * bind this to the `<input is="iron-input">`'s `type` property. |
| */ |
| type: { |
| type: String |
| }, |
| |
| /** |
| * The datalist of the input (if any). This should match the id of an existing `<datalist>`. |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `list` property. |
| */ |
| list: { |
| type: String |
| }, |
| |
| /** |
| * A pattern to validate the `input` with. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * the `<input is="iron-input">`'s `pattern` property. |
| */ |
| pattern: { |
| type: String |
| }, |
| |
| /** |
| * Set to true to mark the input as required. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * the `<input is="iron-input">`'s `required` property. |
| */ |
| required: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * The error message to display when the input is invalid. If you're using |
| * PaperInputBehavior to implement your own paper-input-like element, |
| * bind this to the `<paper-input-error>`'s content, if using. |
| */ |
| errorMessage: { |
| type: String |
| }, |
| |
| /** |
| * Set to true to show a character counter. |
| */ |
| charCounter: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * Set to true to disable the floating label. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * the `<paper-input-container>`'s `noLabelFloat` property. |
| */ |
| noLabelFloat: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * Set to true to always float the label. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * the `<paper-input-container>`'s `alwaysFloatLabel` property. |
| */ |
| alwaysFloatLabel: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * Set to true to auto-validate the input value. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * the `<paper-input-container>`'s `autoValidate` property. |
| */ |
| autoValidate: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * Name of the validator to use. If you're using PaperInputBehavior to |
| * implement your own paper-input-like element, bind this to |
| * the `<input is="iron-input">`'s `validator` property. |
| */ |
| validator: { |
| type: String |
| }, |
| |
| // HTMLInputElement attributes for binding if needed |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `autocomplete` property. |
| */ |
| autocomplete: { |
| type: String, |
| value: 'off' |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `autofocus` property. |
| */ |
| autofocus: { |
| type: Boolean, |
| observer: '_autofocusChanged' |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `inputmode` property. |
| */ |
| inputmode: { |
| type: String |
| }, |
| |
| /** |
| * The minimum length of the input value. |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `minlength` property. |
| */ |
| minlength: { |
| type: Number |
| }, |
| |
| /** |
| * The maximum length of the input value. |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `maxlength` property. |
| */ |
| maxlength: { |
| type: Number |
| }, |
| |
| /** |
| * The minimum (numeric or date-time) input value. |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `min` property. |
| */ |
| min: { |
| type: String |
| }, |
| |
| /** |
| * The maximum (numeric or date-time) input value. |
| * Can be a String (e.g. `"2000-01-01"`) or a Number (e.g. `2`). |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `max` property. |
| */ |
| max: { |
| type: String |
| }, |
| |
| /** |
| * Limits the numeric or date-time increments. |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `step` property. |
| */ |
| step: { |
| type: String |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `name` property. |
| */ |
| name: { |
| type: String |
| }, |
| |
| /** |
| * A placeholder string in addition to the label. If this is set, the label will always float. |
| */ |
| placeholder: { |
| type: String, |
| // need to set a default so _computeAlwaysFloatLabel is run |
| value: '' |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `readonly` property. |
| */ |
| readonly: { |
| type: Boolean, |
| value: false |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `size` property. |
| */ |
| size: { |
| type: Number |
| }, |
| |
| // Nonstandard attributes for binding if needed |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `autocapitalize` property. |
| */ |
| autocapitalize: { |
| type: String, |
| value: 'none' |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `autocorrect` property. |
| */ |
| autocorrect: { |
| type: String, |
| value: 'off' |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `autosave` property, |
| * used with type=search. |
| */ |
| autosave: { |
| type: String |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `results` property, |
| * used with type=search. |
| */ |
| results: { |
| type: Number |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the `<input is="iron-input">`'s `accept` property, |
| * used with type=file. |
| */ |
| accept: { |
| type: String |
| }, |
| |
| /** |
| * If you're using PaperInputBehavior to implement your own paper-input-like |
| * element, bind this to the`<input is="iron-input">`'s `multiple` property, |
| * used with type=file. |
| */ |
| multiple: { |
| type: Boolean |
| }, |
| |
| _ariaDescribedBy: { |
| type: String, |
| value: '' |
| }, |
| |
| _ariaLabelledBy: { |
| type: String, |
| value: '' |
| } |
| |
| }, |
| |
| listeners: { |
| 'addon-attached': '_onAddonAttached', |
| }, |
| |
| keyBindings: { |
| 'shift+tab:keydown': '_onShiftTabDown' |
| }, |
| |
| hostAttributes: { |
| tabindex: 0 |
| }, |
| |
| /** |
| * Returns a reference to the input element. |
| */ |
| get inputElement() { |
| return this.$.input; |
| }, |
| |
| /** |
| * Returns a reference to the focusable element. |
| */ |
| get _focusableElement() { |
| return this.inputElement; |
| }, |
| |
| registered: function() { |
| // These types have some default placeholder text; overlapping |
| // the label on top of it looks terrible. Auto-float the label in this case. |
| this._typesThatHaveText = ["date", "datetime", "datetime-local", "month", |
| "time", "week", "file"]; |
| }, |
| |
| attached: function() { |
| this._updateAriaLabelledBy(); |
| |
| if (this.inputElement && |
| this._typesThatHaveText.indexOf(this.inputElement.type) !== -1) { |
| this.alwaysFloatLabel = true; |
| } |
| }, |
| |
| _appendStringWithSpace: function(str, more) { |
| if (str) { |
| str = str + ' ' + more; |
| } else { |
| str = more; |
| } |
| return str; |
| }, |
| |
| _onAddonAttached: function(event) { |
| var target = event.path ? event.path[0] : event.target; |
| if (target.id) { |
| this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, target.id); |
| } else { |
| var id = 'paper-input-add-on-' + Polymer.PaperInputHelper.NextAddonID++; |
| target.id = id; |
| this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, id); |
| } |
| }, |
| |
| /** |
| * Validates the input element and sets an error style if needed. |
| * |
| * @return {boolean} |
| */ |
| validate: function() { |
| return this.inputElement.validate(); |
| }, |
| |
| /** |
| * Forward focus to inputElement. Overriden from IronControlState. |
| */ |
| _focusBlurHandler: function(event) { |
| Polymer.IronControlState._focusBlurHandler.call(this, event); |
| |
| // Forward the focus to the nested input. |
| if (this.focused && !this._shiftTabPressed) |
| this._focusableElement.focus(); |
| }, |
| |
| /** |
| * Handler that is called when a shift+tab keypress is detected by the menu. |
| * |
| * @param {CustomEvent} event A key combination event. |
| */ |
| _onShiftTabDown: function(event) { |
| var oldTabIndex = this.getAttribute('tabindex'); |
| this._shiftTabPressed = true; |
| this.setAttribute('tabindex', '-1'); |
| this.async(function() { |
| this.setAttribute('tabindex', oldTabIndex); |
| this._shiftTabPressed = false; |
| }, 1); |
| }, |
| |
| /** |
| * If `autoValidate` is true, then validates the element. |
| */ |
| _handleAutoValidate: function() { |
| if (this.autoValidate) |
| this.validate(); |
| }, |
| |
| /** |
| * Restores the cursor to its original position after updating the value. |
| * @param {string} newValue The value that should be saved. |
| */ |
| updateValueAndPreserveCaret: function(newValue) { |
| // Not all elements might have selection, and even if they have the |
| // right properties, accessing them might throw an exception (like for |
| // <input type=number>) |
| try { |
| var start = this.inputElement.selectionStart; |
| this.value = newValue; |
| |
| // The cursor automatically jumps to the end after re-setting the value, |
| // so restore it to its original position. |
| this.inputElement.selectionStart = start; |
| this.inputElement.selectionEnd = start; |
| } catch (e) { |
| // Just set the value and give up on the caret. |
| this.value = newValue; |
| } |
| }, |
| |
| _computeAlwaysFloatLabel: function(alwaysFloatLabel, placeholder) { |
| return placeholder || alwaysFloatLabel; |
| }, |
| |
| _updateAriaLabelledBy: function() { |
| var label = Polymer.dom(this.root).querySelector('label'); |
| if (!label) { |
| this._ariaLabelledBy = ''; |
| return; |
| } |
| var labelledBy; |
| if (label.id) { |
| labelledBy = label.id; |
| } else { |
| labelledBy = 'paper-input-label-' + Polymer.PaperInputHelper.NextLabelID++; |
| label.id = labelledBy; |
| } |
| this._ariaLabelledBy = labelledBy; |
| }, |
| |
| _onChange:function(event) { |
| // In the Shadow DOM, the `change` event is not leaked into the |
| // ancestor tree, so we must do this manually. |
| // See https://w3c.github.io/webcomponents/spec/shadow/#events-that-are-not-leaked-into-ancestor-trees. |
| if (this.shadowRoot) { |
| this.fire(event.type, {sourceEvent: event}, { |
| node: this, |
| bubbles: event.bubbles, |
| cancelable: event.cancelable |
| }); |
| } |
| }, |
| |
| _autofocusChanged: function() { |
| // Firefox doesn't respect the autofocus attribute if it's applied after |
| // the page is loaded (Chrome/WebKit do respect it), preventing an |
| // autofocus attribute specified in markup from taking effect when the |
| // element is upgraded. As a workaround, if the autofocus property is set, |
| // and the focus hasn't already been moved elsewhere, we take focus. |
| if (this.autofocus && this._focusableElement) { |
| |
| // In IE 11, the default document.activeElement can be the page's |
| // outermost html element, but there are also cases (under the |
| // polyfill?) in which the activeElement is not a real HTMLElement, but |
| // just a plain object. We identify the latter case as having no valid |
| // activeElement. |
| var activeElement = document.activeElement; |
| var isActiveElementValid = activeElement instanceof HTMLElement; |
| |
| // Has some other element has already taken the focus? |
| var isSomeElementActive = isActiveElementValid && |
| activeElement !== document.body && |
| activeElement !== document.documentElement; /* IE 11 */ |
| if (!isSomeElementActive) { |
| // No specific element has taken the focus yet, so we can take it. |
| this._focusableElement.focus(); |
| } |
| } |
| } |
| }; |
| |
| /** @polymerBehavior */ |
| Polymer.PaperInputBehavior = [ |
| Polymer.IronControlState, |
| Polymer.IronA11yKeysBehavior, |
| Polymer.PaperInputBehaviorImpl |
| ]; |
| </script> |