Index: chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.js |
diff --git a/chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.js b/chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.js |
index d8b2762fb104855a57eb10cc72b4413c231f46ea..cbb0f164d2467b9cbefb58971df208b04159d755 100644 |
--- a/chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.js |
+++ b/chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.js |
@@ -12,9 +12,9 @@ |
* the PIN keyboard's value. |
* |
* Events: |
- * pin-change: Fired when the PIN value has changed. The pin is available at |
+ * 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 |
+ * submit: Fired when the PIN is submitted. The PIN is available at |
* event.detail.pin. |
* |
* Example: |
@@ -40,6 +40,14 @@ var REPEAT_BACKSPACE_DELAY_MS = 150; |
*/ |
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', |
@@ -51,6 +59,7 @@ Polymer({ |
/** |
* Whether or not the keyboard's input element should be numerical |
* or password. |
+ * @private |
*/ |
enablePassword: { |
type: Boolean, |
@@ -58,14 +67,9 @@ Polymer({ |
}, |
/** |
- * Whether or not the keyboard's input should be shown. |
+ * The value stored in the keyboard's input element. |
+ * @private |
*/ |
- hideInput: { |
- type: Boolean, |
- value: false |
- }, |
- |
- /** The value stored in the keyboard's input element. */ |
value: { |
type: String, |
notify: true, |
@@ -74,6 +78,17 @@ Polymer({ |
}, |
/** |
+ * The password element the pin keyboard is associated with. |
jdufault
2016/11/02 19:06:45
I would extend this comment to also say that a def
sammiequon
2016/11/02 21:31:41
Done.
|
+ * @type {HTMLInputElement|undefined} |
+ * @private |
+ */ |
+ passwordElement: { |
+ type: Object, |
+ value: null, |
+ observer: 'onPasswordElementAttached_' |
+ }, |
+ |
+ /** |
* The intervalID used for the backspace button set/clear interval. |
* @private |
*/ |
@@ -93,14 +108,22 @@ Polymer({ |
}, |
/** |
- * @override |
+ * Called when a password element is attached to the pin keyboard. |
+ * @private |
+ */ |
+ onPasswordElementAttached_: function() { |
+ this.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 |
*/ |
- attached: function() { |
- // Remove the space/enter key binds from the polymer |
- // iron-a11y-keys-behavior. |
- var digitButtons = Polymer.dom(this.root).querySelectorAll('.digit-button'); |
- for (var i = 0; i < digitButtons.length; ++i) |
- digitButtons[i].keyEventTarget = null; |
+ handleInputChanged_: function(event) { |
+ this.value = event.target.value; |
}, |
/** |
@@ -108,22 +131,55 @@ Polymer({ |
* @type {!HTMLInputElement} |
*/ |
get inputElement() { |
- return this.$$('#pin-input'); |
+ return this.passwordElement ? this.passwordElement : this.$$('#pin-input'); |
+ }, |
+ |
+ /** |
+ * Gets the selection range of the input field. |
+ * @type {Array<number>} |
+ */ |
+ get selectionRange() { |
+ return [this.inputElement.selectionStart, this.inputElement.selectionEnd]; |
+ }, |
+ |
+ /** |
+ * Sets the selection range of the input field. |
+ * @param {Array<number>} range The new range of the input element. |
+ */ |
+ set selectionRange(range) { |
+ this.inputElement.selectionStart = range[0]; |
+ this.inputElement.selectionEnd = range[1]; |
}, |
/** Transfers focus to the input element. */ |
focus: function() { |
- this.$$('#pin-input').focus(); |
+ this.inputElement.focus(); |
}, |
/** |
* Called when a keypad number has been tapped. |
- * @param {!{target: !PaperButtonElement}} event |
+ * @param {Event} event The event object. |
* @private |
*/ |
- onNumberTap_: function(event, detail) { |
+ onNumberTap_: function(event) { |
var numberValue = event.target.getAttribute('value'); |
- this.value += numberValue; |
+ |
+ // Add the number where the caret is, then update the selection range of the |
+ // input element. |
+ this.value = this.value.substring(0, this.selectionRange[0]) + numberValue + |
+ this.value.substring(this.selectionRange[1]); |
+ this.selectionRange = [this.selectionRange[0] + 1, |
+ this.selectionRange[0] == this.selectionRange[1] ? |
jdufault
2016/11/02 19:06:45
Can this be simplified to
this.selectionRange =
sammiequon
2016/11/02 21:31:41
Done.
|
+ this.selectionRange[1] + 1 : |
+ this.selectionRange[0] + 1]; |
jdufault
2016/11/02 19:06:45
I think having separate selectionStart/selectionEn
sammiequon
2016/11/02 21:31:41
Done.
|
+ |
+ // 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. */ |
@@ -138,8 +194,15 @@ Polymer({ |
* @param {string} previous |
*/ |
onPinValueChange_: function(value, previous) { |
- if (value != previous) |
+ if (value != previous) { |
+ // The selection caret gets placed at the end after altering the |
+ // password element, so we store the previous location(s) and reapply |
+ // them after the the new value is set. |
+ var selectionRange = this.selectionRange; |
+ this.inputElement.value = this.value; |
+ this.selectionRange = selectionRange; |
this.fire('pin-change', { pin: value }); |
+ } |
}, |
/** |
@@ -148,7 +211,18 @@ Polymer({ |
* @private |
*/ |
onPinClear_: function() { |
- this.value = this.value.substring(0, this.value.length - 1); |
+ // If the input is shown, clear the text based on the caret location or |
+ // selected region of the input element. |
+ var selectionRange = this.selectionRange; |
+ |
+ // If it is just a caret, remove the character in front of the caret. |
+ if (selectionRange[0] == selectionRange[1]) |
+ selectionRange[0]--; |
+ this.value = this.value.substring(0, selectionRange[0]) + |
+ this.value.substring(selectionRange[1]); |
+ |
+ // Move the caret or selected region to the correct new place. |
+ this.selectionRange = [selectionRange[0], selectionRange[0]]; |
}, |
/** |
@@ -163,6 +237,10 @@ Polymer({ |
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(); |
}, |
/** |
@@ -184,6 +262,10 @@ Polymer({ |
*/ |
onBackspacePointerOut_: function(event) { |
this.clearAndReset_(); |
+ |
+ if (!event.target.receivedFocusFromKeyboard) |
+ this.focus(); |
+ event.preventDefault(); |
}, |
/** |
@@ -198,9 +280,17 @@ Polymer({ |
if (!this.repeatBackspaceIntervalId_) |
this.onPinClear_(); |
this.clearAndReset_(); |
+ |
+ if (!event.target.receivedFocusFromKeyboard) |
+ this.focus(); |
+ event.preventDefault(); |
}, |
- /** Called when a key event is pressed while the input element has focus. */ |
+ /** |
+ * 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. |
@@ -215,65 +305,26 @@ Polymer({ |
event.preventDefault(); |
return; |
} |
- }, |
- /** |
- * Keypress does not handle backspace but handles the char codes nicely, so we |
- * have a seperate event to process the backspaces. |
- * @param {Event} event Keydown Event object. |
- * @private |
- */ |
- onKeyDown_: function(event) { |
- // Backspace pressed. |
- if (event.keyCode == 8) { |
- this.onPinClear_(); |
+ // 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. |
+ var isUsableKey = (event.keyCode >= 48 && event.keyCode <= 57) && |
jdufault
2016/11/02 19:06:45
I think this will be cleaner as a helper function.
sammiequon
2016/11/02 21:31:41
Done. Though I pass the event through to avoid too
|
+ !event.shiftKey; |
+ isUsableKey = isUsableKey || |
+ PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES.indexOf(event.keyCode) > -1; |
+ // Allow ctrl + a for users to quickly select the entire PIN. |
+ isUsableKey = isUsableKey || (event.keyCode == 65 && event.ctrlKey); |
+ |
+ if (!isUsableKey) { |
event.preventDefault(); |
return; |
} |
}, |
/** |
- * Called when a key press event is fired while the number button is focused. |
- * Ideally we would want to pass focus back to the input element, but the we |
- * cannot or the virtual keyboard will keep poping up. |
- * @param {Event} event Keypress Event object. |
- * @private |
- */ |
- onKeyPress_: function(event) { |
- // If the active element is the input element, the input element itself will |
- // handle the keypresses, so we do not handle them here. |
- if (this.shadowRoot.activeElement == this.inputElement) |
- return; |
- |
- var code = event.keyCode; |
- |
- // Enter pressed. |
- if (code == 13) { |
- this.firePinSubmitEvent_(); |
- event.preventDefault(); |
- return; |
- } |
- |
- // Space pressed. We want the old polymer function of space activating the |
- // button with focus. |
- if (code == 32) { |
- // Check if target was a number button. |
- if (event.target.hasAttribute('value')) { |
- this.value += event.target.getAttribute('value'); |
- return; |
- } |
- // Check if target was backspace button. |
- if (event.target.classList.contains('backspace-button')) { |
- this.onPinClear_(); |
- return; |
- } |
- } |
- |
- this.value += String.fromCharCode(code); |
- }, |
- |
- /** |
- * Disables the submit and backspace button if nothing is entered. |
+ * Disables the backspace button if nothing is entered. |
* @param {string} value |
* @private |
*/ |
@@ -282,16 +333,6 @@ Polymer({ |
}, |
/** |
- * Computes whether the input type for the pin input should be password or |
- * numerical. |
- * @param {boolean} enablePassword |
- * @private |
- */ |
- getInputType_: function(enablePassword) { |
- return enablePassword ? 'password' : 'number'; |
- }, |
- |
- /** |
* Computes the value of the pin input placeholder. |
* @param {boolean} enablePassword |
* @private |