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

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 3 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"
jdufault 2016/11/02 19:06:45 Update example / docs.
sammiequon 2016/11/02 21:31:41 Done.
22 * value="{{pinValue}}"> 22 * value="{{pinValue}}">
23 * </pin-keyboard> 23 * </pin-keyboard>
24 */ 24 */
25 25
26 (function() { 26 (function() {
27 27
28 /** 28 /**
29 * Once auto backspace starts, the time between individual backspaces. 29 * Once auto backspace starts, the time between individual backspaces.
30 * @type {number} 30 * @type {number}
31 * @const 31 * @const
32 */ 32 */
33 var REPEAT_BACKSPACE_DELAY_MS = 150; 33 var REPEAT_BACKSPACE_DELAY_MS = 150;
34 34
35 /** 35 /**
36 * How long the backspace button must be held down before auto backspace 36 * How long the backspace button must be held down before auto backspace
37 * starts. 37 * starts.
38 * @type {number} 38 * @type {number}
39 * @const 39 * @const
40 */ 40 */
41 var INITIAL_BACKSPACE_DELAY_MS = 500; 41 var INITIAL_BACKSPACE_DELAY_MS = 500;
42 42
43 /**
44 * The key codes of the keys allowed to be used on the pin input, in addition to
45 * number keys. Currently we allow backspace(8), tab(9), left(37) and right(39).
46 * @type {Array<number>}
47 * @const
48 */
49 var PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES = [8, 9, 37, 39];
50
43 Polymer({ 51 Polymer({
44 is: 'pin-keyboard', 52 is: 'pin-keyboard',
45 53
46 behaviors: [ 54 behaviors: [
47 I18nBehavior, 55 I18nBehavior,
48 ], 56 ],
49 57
50 properties: { 58 properties: {
51 /** 59 /**
52 * Whether or not the keyboard's input element should be numerical 60 * Whether or not the keyboard's input element should be numerical
53 * or password. 61 * or password.
62 * @private
54 */ 63 */
55 enablePassword: { 64 enablePassword: {
56 type: Boolean, 65 type: Boolean,
57 value: false 66 value: false
58 }, 67 },
59 68
60 /** 69 /**
61 * Whether or not the keyboard's input should be shown. 70 * The value stored in the keyboard's input element.
71 * @private
62 */ 72 */
63 hideInput: {
64 type: Boolean,
65 value: false
66 },
67
68 /** The value stored in the keyboard's input element. */
69 value: { 73 value: {
70 type: String, 74 type: String,
71 notify: true, 75 notify: true,
72 value: '', 76 value: '',
73 observer: 'onPinValueChange_' 77 observer: 'onPinValueChange_'
74 }, 78 },
75 79
76 /** 80 /**
81 * 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.
82 * @type {HTMLInputElement|undefined}
83 * @private
84 */
85 passwordElement: {
86 type: Object,
87 value: null,
88 observer: 'onPasswordElementAttached_'
89 },
90
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 * @private
97 */ 113 */
98 attached: function() { 114 onPasswordElementAttached_: function() {
99 // Remove the space/enter key binds from the polymer 115 this.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 /**
120 * Called when the user uses the keyboard to enter a value into the input
121 * element.
122 * @param {Event} event The event object.
123 * @private
124 */
125 handleInputChanged_: function(event) {
126 this.value = event.target.value;
127 },
128
129 /**
107 * Gets the container holding the password field. 130 * Gets the container holding the password field.
108 * @type {!HTMLInputElement} 131 * @type {!HTMLInputElement}
109 */ 132 */
110 get inputElement() { 133 get inputElement() {
111 return this.$$('#pin-input'); 134 return this.passwordElement ? this.passwordElement : this.$$('#pin-input');
135 },
136
137 /**
138 * Gets the selection range of the input field.
139 * @type {Array<number>}
140 */
141 get selectionRange() {
142 return [this.inputElement.selectionStart, this.inputElement.selectionEnd];
143 },
144
145 /**
146 * Sets the selection range of the input field.
147 * @param {Array<number>} range The new range of the input element.
148 */
149 set selectionRange(range) {
150 this.inputElement.selectionStart = range[0];
151 this.inputElement.selectionEnd = range[1];
112 }, 152 },
113 153
114 /** Transfers focus to the input element. */ 154 /** Transfers focus to the input element. */
115 focus: function() { 155 focus: function() {
116 this.$$('#pin-input').focus(); 156 this.inputElement.focus();
117 }, 157 },
118 158
119 /** 159 /**
120 * Called when a keypad number has been tapped. 160 * Called when a keypad number has been tapped.
121 * @param {!{target: !PaperButtonElement}} event 161 * @param {Event} event The event object.
122 * @private 162 * @private
123 */ 163 */
124 onNumberTap_: function(event, detail) { 164 onNumberTap_: function(event) {
125 var numberValue = event.target.getAttribute('value'); 165 var numberValue = event.target.getAttribute('value');
126 this.value += numberValue; 166
167 // Add the number where the caret is, then update the selection range of the
168 // input element.
169 this.value = this.value.substring(0, this.selectionRange[0]) + numberValue +
170 this.value.substring(this.selectionRange[1]);
171 this.selectionRange = [this.selectionRange[0] + 1,
172 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.
173 this.selectionRange[1] + 1 :
174 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.
175
176 // If a number button is clicked, we do not want to switch focus to the
177 // button, therefore we transfer focus back to the input, but if a number
178 // button is tabbed into, it should keep focus, so users can use tab and
179 // spacebar/return to enter their PIN.
180 if (!event.target.receivedFocusFromKeyboard)
181 this.focus();
182 event.preventDefault();
127 }, 183 },
128 184
129 /** Fires a submit event with the current PIN value. */ 185 /** Fires a submit event with the current PIN value. */
130 firePinSubmitEvent_: function() { 186 firePinSubmitEvent_: function() {
131 this.fire('submit', { pin: this.value }); 187 this.fire('submit', { pin: this.value });
132 }, 188 },
133 189
134 /** 190 /**
135 * Fires an update event with the current PIN value. The event will only be 191 * Fires an update event with the current PIN value. The event will only be
136 * fired if the PIN value has actually changed. 192 * fired if the PIN value has actually changed.
137 * @param {string} value 193 * @param {string} value
138 * @param {string} previous 194 * @param {string} previous
139 */ 195 */
140 onPinValueChange_: function(value, previous) { 196 onPinValueChange_: function(value, previous) {
141 if (value != previous) 197 if (value != previous) {
198 // The selection caret gets placed at the end after altering the
199 // password element, so we store the previous location(s) and reapply
200 // them after the the new value is set.
201 var selectionRange = this.selectionRange;
202 this.inputElement.value = this.value;
203 this.selectionRange = selectionRange;
142 this.fire('pin-change', { pin: value }); 204 this.fire('pin-change', { pin: value });
205 }
143 }, 206 },
144 207
145 /** 208 /**
146 * Called when the user wants to erase the last character of the entered 209 * Called when the user wants to erase the last character of the entered
147 * PIN value. 210 * PIN value.
148 * @private 211 * @private
149 */ 212 */
150 onPinClear_: function() { 213 onPinClear_: function() {
151 this.value = this.value.substring(0, this.value.length - 1); 214 // If the input is shown, clear the text based on the caret location or
215 // selected region of the input element.
216 var selectionRange = this.selectionRange;
217
218 // If it is just a caret, remove the character in front of the caret.
219 if (selectionRange[0] == selectionRange[1])
220 selectionRange[0]--;
221 this.value = this.value.substring(0, selectionRange[0]) +
222 this.value.substring(selectionRange[1]);
223
224 // Move the caret or selected region to the correct new place.
225 this.selectionRange = [selectionRange[0], selectionRange[0]];
152 }, 226 },
153 227
154 /** 228 /**
155 * Called when the user presses or touches the backspace button. Starts a 229 * 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 230 * timer which starts an interval to repeatedly backspace the pin value until
157 * the interval is cleared. 231 * the interval is cleared.
158 * @param {Event} event The event object. 232 * @param {Event} event The event object.
159 * @private 233 * @private
160 */ 234 */
161 onBackspacePointerDown_: function(event) { 235 onBackspacePointerDown_: function(event) {
162 this.startAutoBackspaceId_ = setTimeout(function() { 236 this.startAutoBackspaceId_ = setTimeout(function() {
163 this.repeatBackspaceIntervalId_ = setInterval( 237 this.repeatBackspaceIntervalId_ = setInterval(
164 this.onPinClear_.bind(this), REPEAT_BACKSPACE_DELAY_MS); 238 this.onPinClear_.bind(this), REPEAT_BACKSPACE_DELAY_MS);
165 }.bind(this), INITIAL_BACKSPACE_DELAY_MS); 239 }.bind(this), INITIAL_BACKSPACE_DELAY_MS);
240
241 if (!event.target.receivedFocusFromKeyboard)
242 this.focus();
243 event.preventDefault();
166 }, 244 },
167 245
168 /** 246 /**
169 * Helper function which clears the timer / interval ids and resets them. 247 * Helper function which clears the timer / interval ids and resets them.
170 * @private 248 * @private
171 */ 249 */
172 clearAndReset_: function() { 250 clearAndReset_: function() {
173 clearInterval(this.repeatBackspaceIntervalId_); 251 clearInterval(this.repeatBackspaceIntervalId_);
174 this.repeatBackspaceIntervalId_ = 0; 252 this.repeatBackspaceIntervalId_ = 0;
175 clearTimeout(this.startAutoBackspaceId_); 253 clearTimeout(this.startAutoBackspaceId_);
176 this.startAutoBackspaceId_ = 0; 254 this.startAutoBackspaceId_ = 0;
177 }, 255 },
178 256
179 /** 257 /**
180 * Called when the user exits the backspace button. Stops the interval 258 * Called when the user exits the backspace button. Stops the interval
181 * callback. 259 * callback.
182 * @param {Event} event The event object. 260 * @param {Event} event The event object.
183 * @private 261 * @private
184 */ 262 */
185 onBackspacePointerOut_: function(event) { 263 onBackspacePointerOut_: function(event) {
186 this.clearAndReset_(); 264 this.clearAndReset_();
265
266 if (!event.target.receivedFocusFromKeyboard)
267 this.focus();
268 event.preventDefault();
187 }, 269 },
188 270
189 /** 271 /**
190 * Called when the user unpresses or untouches the backspace button. Stops the 272 * 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 273 * interval callback and fires a backspace event if there is no interval
192 * running. 274 * running.
193 * @param {Event} event The event object. 275 * @param {Event} event The event object.
194 * @private 276 * @private
195 */ 277 */
196 onBackspacePointerUp_: function(event) { 278 onBackspacePointerUp_: function(event) {
197 // If an interval has started, do not fire event on pointer up. 279 // If an interval has started, do not fire event on pointer up.
198 if (!this.repeatBackspaceIntervalId_) 280 if (!this.repeatBackspaceIntervalId_)
199 this.onPinClear_(); 281 this.onPinClear_();
200 this.clearAndReset_(); 282 this.clearAndReset_();
283
284 if (!event.target.receivedFocusFromKeyboard)
285 this.focus();
286 event.preventDefault();
201 }, 287 },
202 288
203 /** Called when a key event is pressed while the input element has focus. */ 289 /**
290 * Called when a key event is pressed while the input element has focus.
291 * @param {Event} event The event object.
292 * @private
293 */
204 onInputKeyDown_: function(event) { 294 onInputKeyDown_: function(event) {
205 // Up/down pressed, swallow the event to prevent the input value from 295 // Up/down pressed, swallow the event to prevent the input value from
206 // being incremented or decremented. 296 // being incremented or decremented.
207 if (event.keyCode == 38 || event.keyCode == 40) { 297 if (event.keyCode == 38 || event.keyCode == 40) {
208 event.preventDefault(); 298 event.preventDefault();
209 return; 299 return;
210 } 300 }
211 301
212 // Enter pressed. 302 // Enter pressed.
213 if (event.keyCode == 13) { 303 if (event.keyCode == 13) {
214 this.firePinSubmitEvent_(); 304 this.firePinSubmitEvent_();
215 event.preventDefault(); 305 event.preventDefault();
216 return; 306 return;
217 } 307 }
308
309 // Do not pass events that are not numbers or special keys we care about. We
310 // use this instead of input type number because there are several issues
311 // with input type number, such as no selectionStart/selectionEnd and
312 // entered non numbers causes the caret to jump to the left.
313 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
314 !event.shiftKey;
315 isUsableKey = isUsableKey ||
316 PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES.indexOf(event.keyCode) > -1;
317 // Allow ctrl + a for users to quickly select the entire PIN.
318 isUsableKey = isUsableKey || (event.keyCode == 65 && event.ctrlKey);
319
320 if (!isUsableKey) {
321 event.preventDefault();
322 return;
323 }
218 }, 324 },
219 325
220 /** 326 /**
221 * Keypress does not handle backspace but handles the char codes nicely, so we 327 * Disables the backspace button if nothing is entered.
222 * have a seperate event to process the backspaces.
223 * @param {Event} event Keydown Event object.
224 * @private
225 */
226 onKeyDown_: function(event) {
227 // Backspace pressed.
228 if (event.keyCode == 8) {
229 this.onPinClear_();
230 event.preventDefault();
231 return;
232 }
233 },
234
235 /**
236 * Called when a key press event is fired while the number button is focused.
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 328 * @param {string} value
278 * @private 329 * @private
279 */ 330 */
280 hasInput_: function(value) { 331 hasInput_: function(value) {
281 return value.length > 0; 332 return value.length > 0;
282 }, 333 },
283 334
284 /** 335 /**
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. 336 * Computes the value of the pin input placeholder.
296 * @param {boolean} enablePassword 337 * @param {boolean} enablePassword
297 * @private 338 * @private
298 */ 339 */
299 getInputPlaceholder_: function(enablePassword) { 340 getInputPlaceholder_: function(enablePassword) {
300 return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') : 341 return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') :
301 this.i18n('pinKeyboardPlaceholderPin'); 342 this.i18n('pinKeyboardPlaceholderPin');
302 }, 343 },
303 344
304 /** 345 /**
305 * Computes the direction of the pin input. 346 * Computes the direction of the pin input.
306 * @param {string} password 347 * @param {string} password
307 * @private 348 * @private
308 */ 349 */
309 isInputRtl_: function(password) { 350 isInputRtl_: function(password) {
310 // +password will convert a string to a number or to NaN if that's not 351 // +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 352 // possible. Number.isInteger will verify the value is not a NaN and that it
312 // does not contain decimals. 353 // does not contain decimals.
313 // This heuristic will fail for inputs like '1.0'. 354 // This heuristic will fail for inputs like '1.0'.
314 // 355 //
315 // Since we still support users entering their passwords through the PIN 356 // 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 357 // keyboard, we swap the input box to rtl when we think it is a password
317 // (just numbers), if the document direction is rtl. 358 // (just numbers), if the document direction is rtl.
318 return (document.dir == 'rtl') && !Number.isInteger(+password); 359 return (document.dir == 'rtl') && !Number.isInteger(+password);
319 }, 360 },
320 }); 361 });
321 })(); 362 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698