<!-- @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> /** * 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 }, /** * 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-1-1"`) 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', 'focus': '_onFocus' }, observers: [ '_focusedControlStateChanged(focused)' ], 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-' + Math.floor((Math.random() * 100000)); 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 */ _onFocus: function() { if (!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; }, _focusedControlStateChanged: function(focused) { // IronControlState stops the focus and blur events in order to redispatch them on the host // element, but paper-input-container listens to those events. Since there are more // pending work on focus/blur in IronControlState, I'm putting in this hack to get the // input focus state working for now. if (!this.$.container) { this.$.container = Polymer.dom(this.root).querySelector('paper-input-container'); if (!this.$.container) { return; } } if (focused) { this.$.container._onFocus(); } else { this.$.container._onBlur(); } }, _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-' + new Date().getUTCMilliseconds(); 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 }); } } }; /** @polymerBehavior */ Polymer.PaperInputBehavior = [ Polymer.IronControlState, Polymer.IronA11yKeysBehavior, Polymer.PaperInputBehaviorImpl ]; </script>