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

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 4 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 value stored in the keyboard's input element.
70 * @private
62 */ 71 */
63 hideInput: { 72 value: {
64 type: Boolean, 73 type: String,
65 value: false 74 value: '',
75 observer: 'onPinValueChange_'
66 }, 76 },
67 77
68 /** The value stored in the keyboard's input element. */ 78 /**
69 value: { 79 * The password element the pin keyboard is associated with. If this is not
70 type: String, 80 * set, then a default input element is shown and used.
71 notify: true, 81 * @type {HTMLInputElement|undefined}
72 value: '', 82 * @private
73 observer: 'onPinValueChange_' 83 */
84 passwordElement: {
85 type: Object,
86 value: null,
87 observer: 'onPasswordElementAttached_'
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 * @private
97 */ 112 */
98 attached: function() { 113 onPasswordElementAttached_: function() {
99 // Remove the space/enter key binds from the polymer 114 this.inputElement.addEventListener('input',
100 // iron-a11y-keys-behavior. 115 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 }, 116 },
105 117
106 /** 118 /**
119 * Called when the user uses the keyboard to enter a value into the input
120 * element.
121 * @param {Event} event The event object.
122 * @private
123 */
124 handleInputChanged_: function(event) {
125 this.value = event.target.value;
126 },
127
128 /**
107 * Gets the container holding the password field. 129 * Gets the container holding the password field.
108 * @type {!HTMLInputElement} 130 * @type {!HTMLInputElement}
109 */ 131 */
110 get inputElement() { 132 get inputElement() {
jdufault 2016/11/02 22:23:16 What about using passwordElement directly? In att
sammiequon 2016/11/03 00:26:33 Done.
111 return this.$$('#pin-input'); 133 return this.passwordElement ? this.passwordElement : this.$$('#pin-input');
134 },
135
136 /**
137 * Gets the selection start of the input field.
138 * @type {number}
139 */
140 get selectionStart() {
jdufault 2016/11/02 22:23:17 Should these four methods be private?
sammiequon 2016/11/03 00:26:33 Done.
141 return this.inputElement.selectionStart;
142 },
143
144 /**
145 * Gets the selection end of the input field.
146 * @type {number}
147 */
148 get selectionEnd() {
149 return this.inputElement.selectionEnd;
150 },
151
152 /**
153 * Sets the selection start of the input field.
154 * @param {number} start The new selection start of the input element.
155 */
156 set selectionStart(start) {
157 this.inputElement.selectionStart = start;
158 },
159
160 /**
161 * Sets the selection end of the input field.
162 * @param {number} end The new selection end of the input element.
163 */
164 set selectionEnd(end) {
165 this.inputElement.selectionEnd = end;
112 }, 166 },
113 167
114 /** Transfers focus to the input element. */ 168 /** Transfers focus to the input element. */
115 focus: function() { 169 focus: function() {
116 this.$$('#pin-input').focus(); 170 this.inputElement.focus();
117 }, 171 },
118 172
119 /** 173 /**
120 * Called when a keypad number has been tapped. 174 * Called when a keypad number has been tapped.
121 * @param {!{target: !PaperButtonElement}} event 175 * @param {Event} event The event object.
122 * @private 176 * @private
123 */ 177 */
124 onNumberTap_: function(event, detail) { 178 onNumberTap_: function(event) {
125 var numberValue = event.target.getAttribute('value'); 179 var numberValue = event.target.getAttribute('value');
126 this.value += numberValue; 180
181 // Add the number where the caret is, then update the selection range of the
182 // input element.
183 var selectionStart = this.selectionStart;
184 var selectionEnd = this.selectionEnd;
185 this.value = this.value.substring(0, selectionStart) + numberValue +
186 this.value.substring(selectionEnd);
187 this.selectionStart = selectionStart + 1;
188 this.selectionEnd = selectionStart + 1;
189
190 // If a number button is clicked, we do not want to switch focus to the
191 // button, therefore we transfer focus back to the input, but if a number
192 // button is tabbed into, it should keep focus, so users can use tab and
193 // spacebar/return to enter their PIN.
194 if (!event.target.receivedFocusFromKeyboard)
195 this.focus();
196 event.preventDefault();
127 }, 197 },
128 198
129 /** Fires a submit event with the current PIN value. */ 199 /** Fires a submit event with the current PIN value. */
130 firePinSubmitEvent_: function() { 200 firePinSubmitEvent_: function() {
131 this.fire('submit', { pin: this.value }); 201 this.fire('submit', { pin: this.value });
132 }, 202 },
133 203
134 /** 204 /**
135 * Fires an update event with the current PIN value. The event will only be 205 * Fires an update event with the current PIN value. The event will only be
136 * fired if the PIN value has actually changed. 206 * fired if the PIN value has actually changed.
137 * @param {string} value 207 * @param {string} value
138 * @param {string} previous 208 * @param {string} previous
139 */ 209 */
140 onPinValueChange_: function(value, previous) { 210 onPinValueChange_: function(value, previous) {
141 if (value != previous) 211 if (value != previous) {
212 // The selection caret gets placed at the end after altering the
213 // password element, so we store the previous location(s) and reapply
214 // them after the the new value is set.
215 var selectionStart = this.selectionStart;
216 var selectionEnd = this.selectionEnd;
217 this.inputElement.value = this.value;
218 this.selectionStart = selectionStart;
219 this.selectionEnd = selectionEnd;
142 this.fire('pin-change', { pin: value }); 220 this.fire('pin-change', { pin: value });
221 }
143 }, 222 },
144 223
145 /** 224 /**
146 * Called when the user wants to erase the last character of the entered 225 * Called when the user wants to erase the last character of the entered
147 * PIN value. 226 * PIN value.
148 * @private 227 * @private
149 */ 228 */
150 onPinClear_: function() { 229 onPinClear_: function() {
151 this.value = this.value.substring(0, this.value.length - 1); 230 // If the input is shown, clear the text based on the caret location or
231 // selected region of the input element.
232 var selectionStart = this.selectionStart;
233 var selectionEnd = this.selectionEnd;
234
235 // If it is just a caret, remove the character in front of the caret.
236 if (selectionStart == selectionEnd)
237 selectionStart--;
238 this.value = this.value.substring(0, selectionStart) +
239 this.value.substring(selectionEnd);
240
241 // Move the caret or selected region to the correct new place.
242 this.selectionStart = selectionStart;
243 this.selectionEnd = selectionStart;
152 }, 244 },
153 245
154 /** 246 /**
155 * Called when the user presses or touches the backspace button. Starts a 247 * 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 248 * timer which starts an interval to repeatedly backspace the pin value until
157 * the interval is cleared. 249 * the interval is cleared.
158 * @param {Event} event The event object. 250 * @param {Event} event The event object.
159 * @private 251 * @private
160 */ 252 */
161 onBackspacePointerDown_: function(event) { 253 onBackspacePointerDown_: function(event) {
162 this.startAutoBackspaceId_ = setTimeout(function() { 254 this.startAutoBackspaceId_ = setTimeout(function() {
163 this.repeatBackspaceIntervalId_ = setInterval( 255 this.repeatBackspaceIntervalId_ = setInterval(
164 this.onPinClear_.bind(this), REPEAT_BACKSPACE_DELAY_MS); 256 this.onPinClear_.bind(this), REPEAT_BACKSPACE_DELAY_MS);
165 }.bind(this), INITIAL_BACKSPACE_DELAY_MS); 257 }.bind(this), INITIAL_BACKSPACE_DELAY_MS);
258
259 if (!event.target.receivedFocusFromKeyboard)
260 this.focus();
261 event.preventDefault();
166 }, 262 },
167 263
168 /** 264 /**
169 * Helper function which clears the timer / interval ids and resets them. 265 * Helper function which clears the timer / interval ids and resets them.
170 * @private 266 * @private
171 */ 267 */
172 clearAndReset_: function() { 268 clearAndReset_: function() {
173 clearInterval(this.repeatBackspaceIntervalId_); 269 clearInterval(this.repeatBackspaceIntervalId_);
174 this.repeatBackspaceIntervalId_ = 0; 270 this.repeatBackspaceIntervalId_ = 0;
175 clearTimeout(this.startAutoBackspaceId_); 271 clearTimeout(this.startAutoBackspaceId_);
176 this.startAutoBackspaceId_ = 0; 272 this.startAutoBackspaceId_ = 0;
177 }, 273 },
178 274
179 /** 275 /**
180 * Called when the user exits the backspace button. Stops the interval 276 * Called when the user exits the backspace button. Stops the interval
181 * callback. 277 * callback.
182 * @param {Event} event The event object. 278 * @param {Event} event The event object.
183 * @private 279 * @private
184 */ 280 */
185 onBackspacePointerOut_: function(event) { 281 onBackspacePointerOut_: function(event) {
186 this.clearAndReset_(); 282 this.clearAndReset_();
283
284 if (!event.target.receivedFocusFromKeyboard)
285 this.focus();
286 event.preventDefault();
187 }, 287 },
188 288
189 /** 289 /**
190 * Called when the user unpresses or untouches the backspace button. Stops the 290 * 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 291 * interval callback and fires a backspace event if there is no interval
192 * running. 292 * running.
193 * @param {Event} event The event object. 293 * @param {Event} event The event object.
194 * @private 294 * @private
195 */ 295 */
196 onBackspacePointerUp_: function(event) { 296 onBackspacePointerUp_: function(event) {
197 // If an interval has started, do not fire event on pointer up. 297 // If an interval has started, do not fire event on pointer up.
198 if (!this.repeatBackspaceIntervalId_) 298 if (!this.repeatBackspaceIntervalId_)
199 this.onPinClear_(); 299 this.onPinClear_();
200 this.clearAndReset_(); 300 this.clearAndReset_();
301
302 if (!event.target.receivedFocusFromKeyboard)
303 this.focus();
304 event.preventDefault();
201 }, 305 },
202 306
203 /** Called when a key event is pressed while the input element has focus. */ 307 /**
308 * Helper function to check whether a given |event| should be processed by
309 * the numeric only input.
310 * @param {Event} event The event object.
311 * @private
312 */
313 isValidKeyForInput_: function(event) {
jdufault 2016/11/02 22:23:16 Since this is taking an event instead of a keyCode
sammiequon 2016/11/03 00:26:33 Done.
314 // Valid if the key is a number, and shift is not pressed.
315 if ((event.keyCode >= 48 && event.keyCode <= 57) && !event.shiftKey)
316 return true;
317
318 // Valid if the key is one of the selected special keys defined in
319 // |PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES|.
320 if (PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES.indexOf(event.keyCode) > -1)
321 return true;
322
323 // Valid if a is pressed while the control key is pressed to allow users to
324 // quickly select the entire PIN.
325 if (event.keyCode == 65 && event.ctrlKey)
326 return true;
327
328 // The rest of the keys are invalid.
329 return false;
330 },
331
332 /**
333 * Called when a key event is pressed while the input element has focus.
334 * @param {Event} event The event object.
335 * @private
336 */
204 onInputKeyDown_: function(event) { 337 onInputKeyDown_: function(event) {
205 // Up/down pressed, swallow the event to prevent the input value from 338 // Up/down pressed, swallow the event to prevent the input value from
206 // being incremented or decremented. 339 // being incremented or decremented.
207 if (event.keyCode == 38 || event.keyCode == 40) { 340 if (event.keyCode == 38 || event.keyCode == 40) {
208 event.preventDefault(); 341 event.preventDefault();
209 return; 342 return;
210 } 343 }
211 344
212 // Enter pressed. 345 // Enter pressed.
213 if (event.keyCode == 13) { 346 if (event.keyCode == 13) {
214 this.firePinSubmitEvent_(); 347 this.firePinSubmitEvent_();
215 event.preventDefault(); 348 event.preventDefault();
216 return; 349 return;
217 } 350 }
218 },
219 351
220 /** 352 // 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 353 // use this instead of input type number because there are several issues
222 * have a seperate event to process the backspaces. 354 // with input type number, such as no selectionStart/selectionEnd and
223 * @param {Event} event Keydown Event object. 355 // entered non numbers causes the caret to jump to the left.
224 * @private 356 if (!isValidKeyForInput_(event)) {
jdufault 2016/11/02 22:23:16 This seems like it should be throwing errors? thi
sammiequon 2016/11/03 00:26:33 Thanks. It just lets everything through so I didn'
225 */
226 onKeyDown_: function(event) {
227 // Backspace pressed.
228 if (event.keyCode == 8) {
229 this.onPinClear_();
230 event.preventDefault(); 357 event.preventDefault();
231 return; 358 return;
232 } 359 }
233 }, 360 },
234 361
235 /** 362 /**
236 * Called when a key press event is fired while the number button is focused. 363 * 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 364 * @param {string} value
278 * @private 365 * @private
279 */ 366 */
280 hasInput_: function(value) { 367 hasInput_: function(value) {
281 return value.length > 0; 368 return value.length > 0;
282 }, 369 },
283 370
284 /** 371 /**
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. 372 * Computes the value of the pin input placeholder.
296 * @param {boolean} enablePassword 373 * @param {boolean} enablePassword
297 * @private 374 * @private
298 */ 375 */
299 getInputPlaceholder_: function(enablePassword) { 376 getInputPlaceholder_: function(enablePassword) {
300 return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') : 377 return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') :
301 this.i18n('pinKeyboardPlaceholderPin'); 378 this.i18n('pinKeyboardPlaceholderPin');
302 }, 379 },
303 380
304 /** 381 /**
305 * Computes the direction of the pin input. 382 * Computes the direction of the pin input.
306 * @param {string} password 383 * @param {string} password
307 * @private 384 * @private
308 */ 385 */
309 isInputRtl_: function(password) { 386 isInputRtl_: function(password) {
310 // +password will convert a string to a number or to NaN if that's not 387 // +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 388 // possible. Number.isInteger will verify the value is not a NaN and that it
312 // does not contain decimals. 389 // does not contain decimals.
313 // This heuristic will fail for inputs like '1.0'. 390 // This heuristic will fail for inputs like '1.0'.
314 // 391 //
315 // Since we still support users entering their passwords through the PIN 392 // 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 393 // keyboard, we swap the input box to rtl when we think it is a password
317 // (just numbers), if the document direction is rtl. 394 // (just numbers), if the document direction is rtl.
318 return (document.dir == 'rtl') && !Number.isInteger(+password); 395 return (document.dir == 'rtl') && !Number.isInteger(+password);
319 }, 396 },
320 }); 397 });
321 })(); 398 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698