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

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

Powered by Google App Engine
This is Rietveld 408576698