Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(106)

Side by Side Diff: chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.js

Issue 2444443002: cros: Allow pin keyboard to keep focus without popping up the pin keyboard. (Closed)
Patch Set: Fixed patch set 5 errors. Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview 6 * @fileoverview
7 * 'pin-keyboard' is a keyboard that can be used to enter PINs or more generally 7 * 'pin-keyboard' is a keyboard that can be used to enter PINs or more generally
8 * numeric values. 8 * numeric values.
9 * 9 *
10 * Properties: 10 * Properties:
11 * value: The value of the PIN keyboard. Writing to this property will adjust 11 * value: The value of the PIN keyboard. Writing to this property will adjust
12 * the PIN keyboard's value. 12 * the PIN keyboard's value.
13 * 13 *
14 * Events: 14 * Events:
15 * pin-change: Fired when the PIN value has changed. The pin is available at 15 * pin-change: Fired when the PIN value has changed. The PIN is available at
16 * event.detail.pin. 16 * event.detail.pin.
17 * submit: Fired when the PIN is submitted. The pin is available at 17 * submit: Fired when the PIN is submitted. The PIN is available at
18 * event.detail.pin. 18 * event.detail.pin.
19 * 19 *
20 * Example: 20 * Example:
21 * <pin-keyboard on-pin-change="onPinChange" on-submit="onPinSubmit" 21 * <pin-keyboard on-pin-change="onPinChange" on-submit="onPinSubmit">
22 * value="{{pinValue}}">
23 * </pin-keyboard> 22 * </pin-keyboard>
24 */ 23 */
25 24
26 (function() { 25 (function() {
27 26
28 /** 27 /**
29 * Once auto backspace starts, the time between individual backspaces. 28 * Once auto backspace starts, the time between individual backspaces.
30 * @type {number} 29 * @type {number}
31 * @const 30 * @const
32 */ 31 */
33 var REPEAT_BACKSPACE_DELAY_MS = 150; 32 var REPEAT_BACKSPACE_DELAY_MS = 150;
34 33
35 /** 34 /**
36 * How long the backspace button must be held down before auto backspace 35 * How long the backspace button must be held down before auto backspace
37 * starts. 36 * starts.
38 * @type {number} 37 * @type {number}
39 * @const 38 * @const
40 */ 39 */
41 var INITIAL_BACKSPACE_DELAY_MS = 500; 40 var INITIAL_BACKSPACE_DELAY_MS = 500;
42 41
42 /**
43 * The key codes of the keys allowed to be used on the pin input, in addition to
44 * number keys. Currently we allow backspace(8), tab(9), left(37) and right(39).
45 * @type {Array<number>}
46 * @const
47 */
48 var PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES = [8, 9, 37, 39];
49
43 Polymer({ 50 Polymer({
44 is: 'pin-keyboard', 51 is: 'pin-keyboard',
45 52
46 behaviors: [ 53 behaviors: [
47 I18nBehavior, 54 I18nBehavior,
48 ], 55 ],
49 56
50 properties: { 57 properties: {
51 /** 58 /**
52 * Whether or not the keyboard's input element should be numerical 59 * Whether or not the keyboard's input element should be numerical
53 * or password. 60 * or password.
61 * @private
54 */ 62 */
55 enablePassword: { 63 enablePassword: {
56 type: Boolean, 64 type: Boolean,
57 value: false 65 value: false
58 }, 66 },
59 67
60 /** 68 /**
61 * Whether or not the keyboard's input should be shown. 69 * The password element the pin keyboard is associated with. If this is not
70 * set, then a default input element is shown and used.
71 * @type {HTMLInputElement}
72 * @private
62 */ 73 */
63 hideInput: { 74 passwordElement: {
64 type: Boolean, 75 type: Object,
65 value: false 76 value: function() { return this.$$('#pin-input'); },
77 observer: 'onPasswordElementAttached_'
66 }, 78 },
67 79
68 /** The value stored in the keyboard's input element. */ 80 /**
81 * The value stored in the keyboard's input element.
82 * @private
83 */
69 value: { 84 value: {
70 type: String, 85 type: String,
71 notify: true,
72 value: '', 86 value: '',
73 observer: 'onPinValueChange_' 87 observer: 'onPinValueChange_'
74 }, 88 },
75 89
76 /** 90 /**
77 * The intervalID used for the backspace button set/clear interval. 91 * The intervalID used for the backspace button set/clear interval.
78 * @private 92 * @private
79 */ 93 */
80 repeatBackspaceIntervalId_: { 94 repeatBackspaceIntervalId_: {
81 type: Number, 95 type: Number,
82 value: 0 96 value: 0
83 }, 97 },
84 98
85 /** 99 /**
86 * The timeoutID used for the auto backspace. 100 * The timeoutID used for the auto backspace.
87 * @private 101 * @private
88 */ 102 */
89 startAutoBackspaceId_: { 103 startAutoBackspaceId_: {
90 type: Number, 104 type: Number,
91 value: 0 105 value: 0
92 } 106 }
93 }, 107 },
94 108
95 /** 109 /**
96 * @override 110 * Called when a password element is attached to the pin keyboard.
111 * @param {HTMLInputElement} inputElement The PIN keyboard's input element.
112 * @private
97 */ 113 */
98 attached: function() { 114 onPasswordElementAttached_: function(inputElement) {
99 // Remove the space/enter key binds from the polymer 115 inputElement.addEventListener('input',
100 // iron-a11y-keys-behavior. 116 this.handleInputChanged_.bind(this));
101 var digitButtons = Polymer.dom(this.root).querySelectorAll('.digit-button');
102 for (var i = 0; i < digitButtons.length; ++i)
103 digitButtons[i].keyEventTarget = null;
104 }, 117 },
105 118
106 /** 119 /**
107 * Gets the container holding the password field. 120 * Called when the user uses the keyboard to enter a value into the input
108 * @type {!HTMLInputElement} 121 * element.
122 * @param {Event} event The event object.
123 * @private
109 */ 124 */
110 get inputElement() { 125 handleInputChanged_: function(event) {
111 return this.$$('#pin-input'); 126 this.value = event.target.value;
127 },
128
129 /**
130 * Gets the selection start of the input field.
131 * @type {number}
132 * @private
133 */
134 get selectionStart() {
jdufault 2016/11/03 17:42:41 Please append _ to the name of these four methods.
sammiequon 2016/11/03 22:26:55 Done.
135 return this.passwordElement.selectionStart;
136 },
137
138 /**
139 * Gets the selection end of the input field.
140 * @type {number}
141 * @private
142 */
143 get selectionEnd() {
144 return this.passwordElement.selectionEnd;
145 },
146
147 /**
148 * Sets the selection start of the input field.
149 * @param {number} start The new selection start of the input element.
150 * @private
151 */
152 set selectionStart(start) {
153 this.passwordElement.selectionStart = start;
154 },
155
156 /**
157 * Sets the selection end of the input field.
158 * @param {number} end The new selection end of the input element.
159 * @private
160 */
161 set selectionEnd(end) {
162 this.passwordElement.selectionEnd = end;
112 }, 163 },
113 164
114 /** Transfers focus to the input element. */ 165 /** Transfers focus to the input element. */
115 focus: function() { 166 focus: function() {
116 this.$$('#pin-input').focus(); 167 this.passwordElement.focus();
117 }, 168 },
118 169
119 /** 170 /**
120 * Called when a keypad number has been tapped. 171 * Called when a keypad number has been tapped.
121 * @param {!{target: !PaperButtonElement}} event 172 * @param {Event} event The event object.
122 * @private 173 * @private
123 */ 174 */
124 onNumberTap_: function(event, detail) { 175 onNumberTap_: function(event) {
125 var numberValue = event.target.getAttribute('value'); 176 var numberValue = event.target.getAttribute('value');
126 this.value += numberValue; 177
178 // Add the number where the caret is, then update the selection range of the
179 // input element.
180 var selectionStart = this.selectionStart;
181 var selectionEnd = this.selectionEnd;
jdufault 2016/11/03 17:42:41 Are selectionStart/selectionEnd used for caching t
sammiequon 2016/11/03 22:26:55 Done.
182 this.value = this.value.substring(0, selectionStart) + numberValue +
183 this.value.substring(selectionEnd);
184 this.selectionStart = selectionStart + 1;
185 this.selectionEnd = selectionStart + 1;
186
187 // If a number button is clicked, we do not want to switch focus to the
188 // button, therefore we transfer focus back to the input, but if a number
189 // button is tabbed into, it should keep focus, so users can use tab and
190 // spacebar/return to enter their PIN.
191 if (!event.target.receivedFocusFromKeyboard)
192 this.focus();
193 event.preventDefault();
127 }, 194 },
128 195
129 /** Fires a submit event with the current PIN value. */ 196 /** Fires a submit event with the current PIN value. */
130 firePinSubmitEvent_: function() { 197 firePinSubmitEvent_: function() {
131 this.fire('submit', { pin: this.value }); 198 this.fire('submit', { pin: this.value });
132 }, 199 },
133 200
134 /** 201 /**
135 * Fires an update event with the current PIN value. The event will only be 202 * Fires an update event with the current PIN value. The event will only be
136 * fired if the PIN value has actually changed. 203 * fired if the PIN value has actually changed.
137 * @param {string} value 204 * @param {string} value
138 * @param {string} previous 205 * @param {string} previous
139 */ 206 */
140 onPinValueChange_: function(value, previous) { 207 onPinValueChange_: function(value, previous) {
141 if (value != previous) 208 if (value != previous) {
209 // The selection caret gets placed at the end after altering the
210 // password element, so we store the previous location(s) and reapply
211 // them after the the new value is set.
212 var selectionStart = this.selectionStart;
213 var selectionEnd = this.selectionEnd;
214 this.passwordElement.value = this.value;
215 this.selectionStart = selectionStart;
216 this.selectionEnd = selectionEnd;
142 this.fire('pin-change', { pin: value }); 217 this.fire('pin-change', { pin: value });
218 }
143 }, 219 },
144 220
145 /** 221 /**
146 * Called when the user wants to erase the last character of the entered 222 * Called when the user wants to erase the last character of the entered
147 * PIN value. 223 * PIN value.
148 * @private 224 * @private
149 */ 225 */
150 onPinClear_: function() { 226 onPinClear_: function() {
151 this.value = this.value.substring(0, this.value.length - 1); 227 // If the input is shown, clear the text based on the caret location or
228 // selected region of the input element.
229 var selectionStart = this.selectionStart;
230 var selectionEnd = this.selectionEnd;
231
232 // If it is just a caret, remove the character in front of the caret.
233 if (selectionStart == selectionEnd)
234 selectionStart--;
235 this.value = this.value.substring(0, selectionStart) +
236 this.value.substring(selectionEnd);
237
238 // Move the caret or selected region to the correct new place.
239 this.selectionStart = selectionStart;
240 this.selectionEnd = selectionStart;
152 }, 241 },
153 242
154 /** 243 /**
155 * Called when the user presses or touches the backspace button. Starts a 244 * Called when the user presses or touches the backspace button. Starts a
156 * timer which starts an interval to repeatedly backspace the pin value until 245 * timer which starts an interval to repeatedly backspace the pin value until
157 * the interval is cleared. 246 * the interval is cleared.
158 * @param {Event} event The event object. 247 * @param {Event} event The event object.
159 * @private 248 * @private
160 */ 249 */
161 onBackspacePointerDown_: function(event) { 250 onBackspacePointerDown_: function(event) {
162 this.startAutoBackspaceId_ = setTimeout(function() { 251 this.startAutoBackspaceId_ = setTimeout(function() {
163 this.repeatBackspaceIntervalId_ = setInterval( 252 this.repeatBackspaceIntervalId_ = setInterval(
164 this.onPinClear_.bind(this), REPEAT_BACKSPACE_DELAY_MS); 253 this.onPinClear_.bind(this), REPEAT_BACKSPACE_DELAY_MS);
165 }.bind(this), INITIAL_BACKSPACE_DELAY_MS); 254 }.bind(this), INITIAL_BACKSPACE_DELAY_MS);
255
256 if (!event.target.receivedFocusFromKeyboard)
257 this.focus();
258 event.preventDefault();
166 }, 259 },
167 260
168 /** 261 /**
169 * Helper function which clears the timer / interval ids and resets them. 262 * Helper function which clears the timer / interval ids and resets them.
170 * @private 263 * @private
171 */ 264 */
172 clearAndReset_: function() { 265 clearAndReset_: function() {
173 clearInterval(this.repeatBackspaceIntervalId_); 266 clearInterval(this.repeatBackspaceIntervalId_);
174 this.repeatBackspaceIntervalId_ = 0; 267 this.repeatBackspaceIntervalId_ = 0;
175 clearTimeout(this.startAutoBackspaceId_); 268 clearTimeout(this.startAutoBackspaceId_);
176 this.startAutoBackspaceId_ = 0; 269 this.startAutoBackspaceId_ = 0;
177 }, 270 },
178 271
179 /** 272 /**
180 * Called when the user exits the backspace button. Stops the interval 273 * Called when the user exits the backspace button. Stops the interval
181 * callback. 274 * callback.
182 * @param {Event} event The event object. 275 * @param {Event} event The event object.
183 * @private 276 * @private
184 */ 277 */
185 onBackspacePointerOut_: function(event) { 278 onBackspacePointerOut_: function(event) {
186 this.clearAndReset_(); 279 this.clearAndReset_();
280
281 if (!event.target.receivedFocusFromKeyboard)
282 this.focus();
283 event.preventDefault();
187 }, 284 },
188 285
189 /** 286 /**
190 * Called when the user unpresses or untouches the backspace button. Stops the 287 * Called when the user unpresses or untouches the backspace button. Stops the
191 * interval callback and fires a backspace event if there is no interval 288 * interval callback and fires a backspace event if there is no interval
192 * running. 289 * running.
193 * @param {Event} event The event object. 290 * @param {Event} event The event object.
194 * @private 291 * @private
195 */ 292 */
196 onBackspacePointerUp_: function(event) { 293 onBackspacePointerUp_: function(event) {
197 // If an interval has started, do not fire event on pointer up. 294 // If an interval has started, do not fire event on pointer up.
198 if (!this.repeatBackspaceIntervalId_) 295 if (!this.repeatBackspaceIntervalId_)
199 this.onPinClear_(); 296 this.onPinClear_();
200 this.clearAndReset_(); 297 this.clearAndReset_();
298
299 if (!event.target.receivedFocusFromKeyboard)
300 this.focus();
301 event.preventDefault();
201 }, 302 },
202 303
203 /** Called when a key event is pressed while the input element has focus. */ 304 /**
305 * Helper function to check whether a given |event| should be processed by
306 * the numeric only input.
307 * @param {Event} event The event object.
308 * @private
309 */
310 isValidEventForInput_: function(event) {
311 // Valid if the key is a number, and shift is not pressed.
312 if ((event.keyCode >= 48 && event.keyCode <= 57) && !event.shiftKey)
313 return true;
314
315 // Valid if the key is one of the selected special keys defined in
316 // |PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES|.
317 if (PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES.indexOf(event.keyCode) > -1)
318 return true;
319
320 // Valid if a is pressed while the control key is pressed to allow users to
321 // quickly select the entire PIN.
322 if (event.keyCode == 65 && event.ctrlKey)
323 return true;
324
325 // The rest of the keys are invalid.
326 return false;
327 },
328
329 /**
330 * Called when a key event is pressed while the input element has focus.
331 * @param {Event} event The event object.
332 * @private
333 */
204 onInputKeyDown_: function(event) { 334 onInputKeyDown_: function(event) {
205 // Up/down pressed, swallow the event to prevent the input value from 335 // Up/down pressed, swallow the event to prevent the input value from
206 // being incremented or decremented. 336 // being incremented or decremented.
207 if (event.keyCode == 38 || event.keyCode == 40) { 337 if (event.keyCode == 38 || event.keyCode == 40) {
208 event.preventDefault(); 338 event.preventDefault();
209 return; 339 return;
210 } 340 }
211 341
212 // Enter pressed. 342 // Enter pressed.
213 if (event.keyCode == 13) { 343 if (event.keyCode == 13) {
214 this.firePinSubmitEvent_(); 344 this.firePinSubmitEvent_();
215 event.preventDefault(); 345 event.preventDefault();
216 return; 346 return;
217 } 347 }
218 },
219 348
220 /** 349 // Do not pass events that are not numbers or special keys we care about. We
221 * Keypress does not handle backspace but handles the char codes nicely, so we 350 // use this instead of input type number because there are several issues
222 * have a seperate event to process the backspaces. 351 // with input type number, such as no selectionStart/selectionEnd and
223 * @param {Event} event Keydown Event object. 352 // entered non numbers causes the caret to jump to the left.
224 * @private 353 if (!this.isValidEventForInput_(event)) {
225 */
226 onKeyDown_: function(event) {
227 // Backspace pressed.
228 if (event.keyCode == 8) {
229 this.onPinClear_();
230 event.preventDefault(); 354 event.preventDefault();
231 return; 355 return;
232 } 356 }
233 }, 357 },
234 358
235 /** 359 /**
236 * Called when a key press event is fired while the number button is focused. 360 * Disables the backspace button if nothing is entered.
237 * Ideally we would want to pass focus back to the input element, but the we
238 * cannot or the virtual keyboard will keep poping up.
239 * @param {Event} event Keypress Event object.
240 * @private
241 */
242 onKeyPress_: function(event) {
243 // If the active element is the input element, the input element itself will
244 // handle the keypresses, so we do not handle them here.
245 if (this.shadowRoot.activeElement == this.inputElement)
246 return;
247
248 var code = event.keyCode;
249
250 // Enter pressed.
251 if (code == 13) {
252 this.firePinSubmitEvent_();
253 event.preventDefault();
254 return;
255 }
256
257 // Space pressed. We want the old polymer function of space activating the
258 // button with focus.
259 if (code == 32) {
260 // Check if target was a number button.
261 if (event.target.hasAttribute('value')) {
262 this.value += event.target.getAttribute('value');
263 return;
264 }
265 // Check if target was backspace button.
266 if (event.target.classList.contains('backspace-button')) {
267 this.onPinClear_();
268 return;
269 }
270 }
271
272 this.value += String.fromCharCode(code);
273 },
274
275 /**
276 * Disables the submit and backspace button if nothing is entered.
277 * @param {string} value 361 * @param {string} value
278 * @private 362 * @private
279 */ 363 */
280 hasInput_: function(value) { 364 hasInput_: function(value) {
281 return value.length > 0; 365 return value.length > 0;
282 }, 366 },
283 367
284 /** 368 /**
285 * Computes whether the input type for the pin input should be password or
286 * numerical.
287 * @param {boolean} enablePassword
288 * @private
289 */
290 getInputType_: function(enablePassword) {
291 return enablePassword ? 'password' : 'number';
292 },
293
294 /**
295 * Computes the value of the pin input placeholder. 369 * Computes the value of the pin input placeholder.
296 * @param {boolean} enablePassword 370 * @param {boolean} enablePassword
297 * @private 371 * @private
298 */ 372 */
299 getInputPlaceholder_: function(enablePassword) { 373 getInputPlaceholder_: function(enablePassword) {
300 return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') : 374 return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') :
301 this.i18n('pinKeyboardPlaceholderPin'); 375 this.i18n('pinKeyboardPlaceholderPin');
302 }, 376 },
303 377
304 /** 378 /**
305 * Computes the direction of the pin input. 379 * Computes the direction of the pin input.
306 * @param {string} password 380 * @param {string} password
307 * @private 381 * @private
308 */ 382 */
309 isInputRtl_: function(password) { 383 isInputRtl_: function(password) {
310 // +password will convert a string to a number or to NaN if that's not 384 // +password will convert a string to a number or to NaN if that's not
311 // possible. Number.isInteger will verify the value is not a NaN and that it 385 // possible. Number.isInteger will verify the value is not a NaN and that it
312 // does not contain decimals. 386 // does not contain decimals.
313 // This heuristic will fail for inputs like '1.0'. 387 // This heuristic will fail for inputs like '1.0'.
314 // 388 //
315 // Since we still support users entering their passwords through the PIN 389 // Since we still support users entering their passwords through the PIN
316 // keyboard, we swap the input box to rtl when we think it is a password 390 // keyboard, we swap the input box to rtl when we think it is a password
317 // (just numbers), if the document direction is rtl. 391 // (just numbers), if the document direction is rtl.
318 return (document.dir == 'rtl') && !Number.isInteger(+password); 392 return (document.dir == 'rtl') && !Number.isInteger(+password);
319 }, 393 },
320 }); 394 });
321 })(); 395 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698