| Index: chrome/browser/resources/chromeos/quick_unlock/md_pin_keyboard.js
|
| diff --git a/chrome/browser/resources/chromeos/quick_unlock/md_pin_keyboard.js b/chrome/browser/resources/chromeos/quick_unlock/md_pin_keyboard.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d3740c76c39c4fdc94efc0eeaef7bc46903561c7
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/chromeos/quick_unlock/md_pin_keyboard.js
|
| @@ -0,0 +1,400 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview
|
| + * 'pin-keyboard' is a keyboard that can be used to enter PINs or more generally
|
| + * numeric values.
|
| + *
|
| + * Properties:
|
| + * value: The value of the PIN keyboard. Writing to this property will adjust
|
| + * the PIN keyboard's value.
|
| + *
|
| + * Events:
|
| + * 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
|
| + * event.detail.pin.
|
| + *
|
| + * Example:
|
| + * <pin-keyboard on-pin-change="onPinChange" on-submit="onPinSubmit">
|
| + * </pin-keyboard>
|
| + */
|
| +
|
| +(function() {
|
| +
|
| +/**
|
| + * Once auto backspace starts, the time between individual backspaces.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| +var REPEAT_BACKSPACE_DELAY_MS = 150;
|
| +
|
| +/**
|
| + * How long the backspace button must be held down before auto backspace
|
| + * starts.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| +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',
|
| +
|
| + behaviors: [
|
| + I18nBehavior,
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * Whether or not the keyboard's input element should be numerical
|
| + * or password.
|
| + * @private
|
| + */
|
| + enablePassword: {
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| +
|
| + /**
|
| + * The password element the pin keyboard is associated with. If this is not
|
| + * set, then a default input element is shown and used.
|
| + * @type {?Element}
|
| + * @private
|
| + */
|
| + passwordElement: {
|
| + type: Object,
|
| + value: function() {
|
| + return this.$.pinInput.inputElement;
|
| + },
|
| + observer: 'onPasswordElementAttached_',
|
| + },
|
| +
|
| + /**
|
| + * The intervalID used for the backspace button set/clear interval.
|
| + * @private
|
| + */
|
| + repeatBackspaceIntervalId_: {
|
| + type: Number,
|
| + value: 0,
|
| + },
|
| +
|
| + /**
|
| + * The timeoutID used for the auto backspace.
|
| + * @private
|
| + */
|
| + startAutoBackspaceId_: {
|
| + type: Number,
|
| + value: 0,
|
| + },
|
| +
|
| + /**
|
| + * Whether or not to show the default pin input.
|
| + * @private
|
| + */
|
| + showPinInput_: {
|
| + type: Boolean,
|
| + value: false,
|
| + },
|
| +
|
| + /**
|
| + * The value stored in the keyboard's input element.
|
| + * @private
|
| + */
|
| + value: {
|
| + type: String,
|
| + notify: true,
|
| + value: '',
|
| + observer: 'onPinValueChange_',
|
| + },
|
| + },
|
| +
|
| + /**
|
| + * Called when a password element is attached to the pin keyboard.
|
| + * @param {HTMLInputElement} inputElement The PIN keyboard's input element.
|
| + * @private
|
| + */
|
| + onPasswordElementAttached_: function(inputElement) {
|
| + this.showPinInput_ = inputElement == this.$.pinInput.inputElement;
|
| + 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
|
| + */
|
| + handleInputChanged_: function(event) {
|
| + this.value = event.target.value;
|
| + },
|
| +
|
| + /**
|
| + * Gets the selection start of the input field.
|
| + * @type {number}
|
| + * @private
|
| + */
|
| + get selectionStart_() {
|
| + return this.passwordElement.selectionStart;
|
| + },
|
| +
|
| + /**
|
| + * Gets the selection end of the input field.
|
| + * @type {number}
|
| + * @private
|
| + */
|
| + get selectionEnd_() {
|
| + return this.passwordElement.selectionEnd;
|
| + },
|
| +
|
| + /**
|
| + * Sets the selection start of the input field.
|
| + * @param {number} start The new selection start of the input element.
|
| + * @private
|
| + */
|
| + set selectionStart_(start) {
|
| + this.passwordElement.selectionStart = start;
|
| + },
|
| +
|
| + /**
|
| + * Sets the selection end of the input field.
|
| + * @param {number} end The new selection end of the input element.
|
| + * @private
|
| + */
|
| + set selectionEnd_(end) {
|
| + this.passwordElement.selectionEnd = end;
|
| + },
|
| +
|
| + /** Transfers focus to the input element. */
|
| + focus: function() {
|
| + this.passwordElement.focus();
|
| + },
|
| +
|
| + /**
|
| + * Called when a keypad number has been tapped.
|
| + * @param {Event} event The event object.
|
| + * @private
|
| + */
|
| + onNumberTap_: function(event) {
|
| + var numberValue = event.target.getAttribute('value');
|
| +
|
| + // Add the number where the caret is, then update the selection range of the
|
| + // input element.
|
| + var selectionStart = this.selectionStart_;
|
| + this.value = this.value.substring(0, this.selectionStart_) + numberValue +
|
| + this.value.substring(this.selectionEnd_);
|
| + this.selectionStart_ = selectionStart + 1;
|
| + this.selectionEnd_ = this.selectionStart_;
|
| +
|
| + // 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. */
|
| + firePinSubmitEvent_: function() {
|
| + this.fire('submit', { pin: this.value });
|
| + },
|
| +
|
| + /**
|
| + * Fires an update event with the current PIN value. The event will only be
|
| + * fired if the PIN value has actually changed.
|
| + * @param {string} value
|
| + * @param {string} previous
|
| + */
|
| + onPinValueChange_: function(value, previous) {
|
| + if (value != previous) {
|
| + this.passwordElement.value = this.value;
|
| + this.fire('pin-change', { pin: value });
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called when the user wants to erase the last character of the entered
|
| + * PIN value.
|
| + * @private
|
| + */
|
| + onPinClear_: function() {
|
| + // If the input is shown, clear the text based on the caret location or
|
| + // selected region of the input element. If it is just a caret, remove the
|
| + // character in front of the caret.
|
| + var selectionStart = this.selectionStart_;
|
| + var selectionEnd = this.selectionEnd_;
|
| + if (selectionStart == selectionEnd)
|
| + selectionStart--;
|
| +
|
| + this.value = this.value.substring(0, selectionStart) +
|
| + this.value.substring(selectionEnd);
|
| +
|
| + // Move the caret or selected region to the correct new place.
|
| + this.selectionStart_ = selectionStart;
|
| + this.selectionEnd_ = selectionStart;
|
| + },
|
| +
|
| + /**
|
| + * Called when the user presses or touches the backspace button. Starts a
|
| + * timer which starts an interval to repeatedly backspace the pin value until
|
| + * the interval is cleared.
|
| + * @param {Event} event The event object.
|
| + * @private
|
| + */
|
| + onBackspacePointerDown_: function(event) {
|
| + this.startAutoBackspaceId_ = setTimeout(function() {
|
| + 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();
|
| + },
|
| +
|
| + /**
|
| + * Helper function which clears the timer / interval ids and resets them.
|
| + * @private
|
| + */
|
| + clearAndReset_: function() {
|
| + clearInterval(this.repeatBackspaceIntervalId_);
|
| + this.repeatBackspaceIntervalId_ = 0;
|
| + clearTimeout(this.startAutoBackspaceId_);
|
| + this.startAutoBackspaceId_ = 0;
|
| + },
|
| +
|
| + /**
|
| + * Called when the user exits the backspace button. Stops the interval
|
| + * callback.
|
| + * @param {Event} event The event object.
|
| + * @private
|
| + */
|
| + onBackspacePointerOut_: function(event) {
|
| + this.clearAndReset_();
|
| +
|
| + if (!event.target.receivedFocusFromKeyboard)
|
| + this.focus();
|
| + event.preventDefault();
|
| + },
|
| +
|
| + /**
|
| + * Called when the user unpresses or untouches the backspace button. Stops the
|
| + * interval callback and fires a backspace event if there is no interval
|
| + * running.
|
| + * @param {Event} event The event object.
|
| + * @private
|
| + */
|
| + onBackspacePointerUp_: function(event) {
|
| + // If an interval has started, do not fire event on pointer up.
|
| + if (!this.repeatBackspaceIntervalId_)
|
| + this.onPinClear_();
|
| + this.clearAndReset_();
|
| +
|
| + if (!event.target.receivedFocusFromKeyboard)
|
| + this.focus();
|
| + event.preventDefault();
|
| + },
|
| +
|
| + /**
|
| + * Helper function to check whether a given |event| should be processed by
|
| + * the numeric only input.
|
| + * @param {Event} event The event object.
|
| + * @private
|
| + */
|
| + isValidEventForInput_: function(event) {
|
| + // Valid if the key is a number, and shift is not pressed.
|
| + if ((event.keyCode >= 48 && event.keyCode <= 57) && !event.shiftKey)
|
| + return true;
|
| +
|
| + // Valid if the key is one of the selected special keys defined in
|
| + // |PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES|.
|
| + if (PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES.indexOf(event.keyCode) > -1)
|
| + return true;
|
| +
|
| + // Valid if the key is CTRL+A to allow users to quickly select the entire
|
| + // PIN.
|
| + if (event.keyCode == 65 && event.ctrlKey)
|
| + return true;
|
| +
|
| + // The rest of the keys are invalid.
|
| + return false;
|
| + },
|
| +
|
| + /**
|
| + * 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.
|
| + if (event.keyCode == 38 || event.keyCode == 40) {
|
| + event.preventDefault();
|
| + return;
|
| + }
|
| +
|
| + // Enter pressed.
|
| + if (event.keyCode == 13) {
|
| + this.firePinSubmitEvent_();
|
| + event.preventDefault();
|
| + return;
|
| + }
|
| +
|
| + // 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.
|
| + if (!this.isValidEventForInput_(event)) {
|
| + event.preventDefault();
|
| + return;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Disables the backspace button if nothing is entered.
|
| + * @param {string} value
|
| + * @private
|
| + */
|
| + hasInput_: function(value) {
|
| + return value.length > 0;
|
| + },
|
| +
|
| + /**
|
| + * Computes the value of the pin input placeholder.
|
| + * @param {boolean} enablePassword
|
| + * @private
|
| + */
|
| + getInputPlaceholder_: function(enablePassword) {
|
| + return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') :
|
| + this.i18n('pinKeyboardPlaceholderPin');
|
| + },
|
| +
|
| + /**
|
| + * Computes the direction of the pin input.
|
| + * @param {string} password
|
| + * @private
|
| + */
|
| + isInputRtl_: function(password) {
|
| + // +password will convert a string to a number or to NaN if that's not
|
| + // possible. Number.isInteger will verify the value is not a NaN and that it
|
| + // does not contain decimals.
|
| + // This heuristic will fail for inputs like '1.0'.
|
| + //
|
| + // Since we still support users entering their passwords through the PIN
|
| + // keyboard, we swap the input box to rtl when we think it is a password
|
| + // (just numbers), if the document direction is rtl.
|
| + return (document.dir == 'rtl') && !Number.isInteger(+password);
|
| + },
|
| +});
|
| +})();
|
|
|