Index: chrome/browser/resources/chromeos/quick_unlock/md_pin_keyboard.js |
diff --git a/chrome/browser/resources/chromeos/quick_unlock/md_pin_keyboard.js b/chrome/browser/resources/chromeos/quick_unlock/md_pin_keyboard.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d3740c76c39c4fdc94efc0eeaef7bc46903561c7 |
--- /dev/null |
+++ b/chrome/browser/resources/chromeos/quick_unlock/md_pin_keyboard.js |
@@ -0,0 +1,400 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** |
+ * @fileoverview |
+ * 'pin-keyboard' is a keyboard that can be used to enter PINs or more generally |
+ * numeric values. |
+ * |
+ * Properties: |
+ * value: The value of the PIN keyboard. Writing to this property will adjust |
+ * the PIN keyboard's value. |
+ * |
+ * Events: |
+ * pin-change: Fired when the PIN value has changed. The PIN is available at |
+ * event.detail.pin. |
+ * submit: Fired when the PIN is submitted. The PIN is available at |
+ * event.detail.pin. |
+ * |
+ * Example: |
+ * <pin-keyboard on-pin-change="onPinChange" on-submit="onPinSubmit"> |
+ * </pin-keyboard> |
+ */ |
+ |
+(function() { |
+ |
+/** |
+ * Once auto backspace starts, the time between individual backspaces. |
+ * @type {number} |
+ * @const |
+ */ |
+var REPEAT_BACKSPACE_DELAY_MS = 150; |
+ |
+/** |
+ * How long the backspace button must be held down before auto backspace |
+ * starts. |
+ * @type {number} |
+ * @const |
+ */ |
+var INITIAL_BACKSPACE_DELAY_MS = 500; |
+ |
+/** |
+ * The key codes of the keys allowed to be used on the pin input, in addition to |
+ * number keys. Currently we allow backspace(8), tab(9), left(37) and right(39). |
+ * @type {Array<number>} |
+ * @const |
+ */ |
+var PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES = [8, 9, 37, 39]; |
+ |
+Polymer({ |
+ is: 'pin-keyboard', |
+ |
+ behaviors: [ |
+ I18nBehavior, |
+ ], |
+ |
+ properties: { |
+ /** |
+ * Whether or not the keyboard's input element should be numerical |
+ * or password. |
+ * @private |
+ */ |
+ enablePassword: { |
+ type: Boolean, |
+ value: false, |
+ }, |
+ |
+ /** |
+ * The password element the pin keyboard is associated with. If this is not |
+ * set, then a default input element is shown and used. |
+ * @type {?Element} |
+ * @private |
+ */ |
+ passwordElement: { |
+ type: Object, |
+ value: function() { |
+ return this.$.pinInput.inputElement; |
+ }, |
+ observer: 'onPasswordElementAttached_', |
+ }, |
+ |
+ /** |
+ * The intervalID used for the backspace button set/clear interval. |
+ * @private |
+ */ |
+ repeatBackspaceIntervalId_: { |
+ type: Number, |
+ value: 0, |
+ }, |
+ |
+ /** |
+ * The timeoutID used for the auto backspace. |
+ * @private |
+ */ |
+ startAutoBackspaceId_: { |
+ type: Number, |
+ value: 0, |
+ }, |
+ |
+ /** |
+ * Whether or not to show the default pin input. |
+ * @private |
+ */ |
+ showPinInput_: { |
+ type: Boolean, |
+ value: false, |
+ }, |
+ |
+ /** |
+ * The value stored in the keyboard's input element. |
+ * @private |
+ */ |
+ value: { |
+ type: String, |
+ notify: true, |
+ value: '', |
+ observer: 'onPinValueChange_', |
+ }, |
+ }, |
+ |
+ /** |
+ * Called when a password element is attached to the pin keyboard. |
+ * @param {HTMLInputElement} inputElement The PIN keyboard's input element. |
+ * @private |
+ */ |
+ onPasswordElementAttached_: function(inputElement) { |
+ this.showPinInput_ = inputElement == this.$.pinInput.inputElement; |
+ inputElement.addEventListener('input', |
+ this.handleInputChanged_.bind(this)); |
+ }, |
+ |
+ /** |
+ * Called when the user uses the keyboard to enter a value into the input |
+ * element. |
+ * @param {Event} event The event object. |
+ * @private |
+ */ |
+ handleInputChanged_: function(event) { |
+ this.value = event.target.value; |
+ }, |
+ |
+ /** |
+ * Gets the selection start of the input field. |
+ * @type {number} |
+ * @private |
+ */ |
+ get selectionStart_() { |
+ return this.passwordElement.selectionStart; |
+ }, |
+ |
+ /** |
+ * Gets the selection end of the input field. |
+ * @type {number} |
+ * @private |
+ */ |
+ get selectionEnd_() { |
+ return this.passwordElement.selectionEnd; |
+ }, |
+ |
+ /** |
+ * Sets the selection start of the input field. |
+ * @param {number} start The new selection start of the input element. |
+ * @private |
+ */ |
+ set selectionStart_(start) { |
+ this.passwordElement.selectionStart = start; |
+ }, |
+ |
+ /** |
+ * Sets the selection end of the input field. |
+ * @param {number} end The new selection end of the input element. |
+ * @private |
+ */ |
+ set selectionEnd_(end) { |
+ this.passwordElement.selectionEnd = end; |
+ }, |
+ |
+ /** Transfers focus to the input element. */ |
+ focus: function() { |
+ this.passwordElement.focus(); |
+ }, |
+ |
+ /** |
+ * Called when a keypad number has been tapped. |
+ * @param {Event} event The event object. |
+ * @private |
+ */ |
+ onNumberTap_: function(event) { |
+ var numberValue = event.target.getAttribute('value'); |
+ |
+ // Add the number where the caret is, then update the selection range of the |
+ // input element. |
+ var selectionStart = this.selectionStart_; |
+ this.value = this.value.substring(0, this.selectionStart_) + numberValue + |
+ this.value.substring(this.selectionEnd_); |
+ this.selectionStart_ = selectionStart + 1; |
+ this.selectionEnd_ = this.selectionStart_; |
+ |
+ // If a number button is clicked, we do not want to switch focus to the |
+ // button, therefore we transfer focus back to the input, but if a number |
+ // button is tabbed into, it should keep focus, so users can use tab and |
+ // spacebar/return to enter their PIN. |
+ if (!event.target.receivedFocusFromKeyboard) |
+ this.focus(); |
+ event.preventDefault(); |
+ }, |
+ |
+ /** Fires a submit event with the current PIN value. */ |
+ firePinSubmitEvent_: function() { |
+ this.fire('submit', { pin: this.value }); |
+ }, |
+ |
+ /** |
+ * Fires an update event with the current PIN value. The event will only be |
+ * fired if the PIN value has actually changed. |
+ * @param {string} value |
+ * @param {string} previous |
+ */ |
+ onPinValueChange_: function(value, previous) { |
+ if (value != previous) { |
+ this.passwordElement.value = this.value; |
+ this.fire('pin-change', { pin: value }); |
+ } |
+ }, |
+ |
+ /** |
+ * Called when the user wants to erase the last character of the entered |
+ * PIN value. |
+ * @private |
+ */ |
+ onPinClear_: function() { |
+ // If the input is shown, clear the text based on the caret location or |
+ // selected region of the input element. If it is just a caret, remove the |
+ // character in front of the caret. |
+ var selectionStart = this.selectionStart_; |
+ var selectionEnd = this.selectionEnd_; |
+ if (selectionStart == selectionEnd) |
+ selectionStart--; |
+ |
+ this.value = this.value.substring(0, selectionStart) + |
+ this.value.substring(selectionEnd); |
+ |
+ // Move the caret or selected region to the correct new place. |
+ this.selectionStart_ = selectionStart; |
+ this.selectionEnd_ = selectionStart; |
+ }, |
+ |
+ /** |
+ * Called when the user presses or touches the backspace button. Starts a |
+ * timer which starts an interval to repeatedly backspace the pin value until |
+ * the interval is cleared. |
+ * @param {Event} event The event object. |
+ * @private |
+ */ |
+ onBackspacePointerDown_: function(event) { |
+ this.startAutoBackspaceId_ = setTimeout(function() { |
+ this.repeatBackspaceIntervalId_ = setInterval( |
+ this.onPinClear_.bind(this), REPEAT_BACKSPACE_DELAY_MS); |
+ }.bind(this), INITIAL_BACKSPACE_DELAY_MS); |
+ |
+ if (!event.target.receivedFocusFromKeyboard) |
+ this.focus(); |
+ event.preventDefault(); |
+ }, |
+ |
+ /** |
+ * Helper function which clears the timer / interval ids and resets them. |
+ * @private |
+ */ |
+ clearAndReset_: function() { |
+ clearInterval(this.repeatBackspaceIntervalId_); |
+ this.repeatBackspaceIntervalId_ = 0; |
+ clearTimeout(this.startAutoBackspaceId_); |
+ this.startAutoBackspaceId_ = 0; |
+ }, |
+ |
+ /** |
+ * Called when the user exits the backspace button. Stops the interval |
+ * callback. |
+ * @param {Event} event The event object. |
+ * @private |
+ */ |
+ onBackspacePointerOut_: function(event) { |
+ this.clearAndReset_(); |
+ |
+ if (!event.target.receivedFocusFromKeyboard) |
+ this.focus(); |
+ event.preventDefault(); |
+ }, |
+ |
+ /** |
+ * Called when the user unpresses or untouches the backspace button. Stops the |
+ * interval callback and fires a backspace event if there is no interval |
+ * running. |
+ * @param {Event} event The event object. |
+ * @private |
+ */ |
+ onBackspacePointerUp_: function(event) { |
+ // If an interval has started, do not fire event on pointer up. |
+ if (!this.repeatBackspaceIntervalId_) |
+ this.onPinClear_(); |
+ this.clearAndReset_(); |
+ |
+ if (!event.target.receivedFocusFromKeyboard) |
+ this.focus(); |
+ event.preventDefault(); |
+ }, |
+ |
+ /** |
+ * Helper function to check whether a given |event| should be processed by |
+ * the numeric only input. |
+ * @param {Event} event The event object. |
+ * @private |
+ */ |
+ isValidEventForInput_: function(event) { |
+ // Valid if the key is a number, and shift is not pressed. |
+ if ((event.keyCode >= 48 && event.keyCode <= 57) && !event.shiftKey) |
+ return true; |
+ |
+ // Valid if the key is one of the selected special keys defined in |
+ // |PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES|. |
+ if (PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES.indexOf(event.keyCode) > -1) |
+ return true; |
+ |
+ // Valid if the key is CTRL+A to allow users to quickly select the entire |
+ // PIN. |
+ if (event.keyCode == 65 && event.ctrlKey) |
+ return true; |
+ |
+ // The rest of the keys are invalid. |
+ return false; |
+ }, |
+ |
+ /** |
+ * Called when a key event is pressed while the input element has focus. |
+ * @param {Event} event The event object. |
+ * @private |
+ */ |
+ onInputKeyDown_: function(event) { |
+ // Up/down pressed, swallow the event to prevent the input value from |
+ // being incremented or decremented. |
+ if (event.keyCode == 38 || event.keyCode == 40) { |
+ event.preventDefault(); |
+ return; |
+ } |
+ |
+ // Enter pressed. |
+ if (event.keyCode == 13) { |
+ this.firePinSubmitEvent_(); |
+ event.preventDefault(); |
+ return; |
+ } |
+ |
+ // Do not pass events that are not numbers or special keys we care about. We |
+ // use this instead of input type number because there are several issues |
+ // with input type number, such as no selectionStart/selectionEnd and |
+ // entered non numbers causes the caret to jump to the left. |
+ if (!this.isValidEventForInput_(event)) { |
+ event.preventDefault(); |
+ return; |
+ } |
+ }, |
+ |
+ /** |
+ * Disables the backspace button if nothing is entered. |
+ * @param {string} value |
+ * @private |
+ */ |
+ hasInput_: function(value) { |
+ return value.length > 0; |
+ }, |
+ |
+ /** |
+ * Computes the value of the pin input placeholder. |
+ * @param {boolean} enablePassword |
+ * @private |
+ */ |
+ getInputPlaceholder_: function(enablePassword) { |
+ return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') : |
+ this.i18n('pinKeyboardPlaceholderPin'); |
+ }, |
+ |
+ /** |
+ * Computes the direction of the pin input. |
+ * @param {string} password |
+ * @private |
+ */ |
+ isInputRtl_: function(password) { |
+ // +password will convert a string to a number or to NaN if that's not |
+ // possible. Number.isInteger will verify the value is not a NaN and that it |
+ // does not contain decimals. |
+ // This heuristic will fail for inputs like '1.0'. |
+ // |
+ // Since we still support users entering their passwords through the PIN |
+ // keyboard, we swap the input box to rtl when we think it is a password |
+ // (just numbers), if the document direction is rtl. |
+ return (document.dir == 'rtl') && !Number.isInteger(+password); |
+ }, |
+}); |
+})(); |