Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix various "first press" bugs on single select dropdowns #1104

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions public/assets/scripts/choices.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*! choices.js v10.2.0 | © 2022 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
/*! choices.js v10.2.0 | © 2023 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
Expand Down Expand Up @@ -1263,9 +1263,30 @@ var Choices = /** @class */function () {
var hasFocusedInput = this.input.isFocussed;
var hasActiveDropdown = this.dropdown.isActive;
var hasItems = this.itemList.hasChildren();
var keyString = String.fromCharCode(keyCode);
// eslint-disable-next-line no-control-regex
var wasPrintableChar = /[^\x00-\x1F]/.test(keyString);
/*
See:
https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs
https://stackoverflow.com/a/70866532 - "Unidentified" for mobile
http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7)
Logic: when a key event is sent, `event.key` represents its printable value _or_ one
of a large list of special values indicating meta keys/functionality. In addition,
key events for compose functionality contain a value of `Dead` when mid-composition.
I can't quite verify it, but non-English IMEs may also be able to generate key codes
for code points in the surrogate-pair range, which could potentially be seen as having
key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that
alone.
Here, key.length === 1 means we know for sure the input was printable and not a special
`key` value. When the length is greater than 1, it could be either a printable surrogate
pair or a special `key` value. We can tell the difference by checking if the _character
code_ value (not code point!) is in the "surrogate pair" range or not.
We don't use .codePointAt because an invalid code point would return 65535, which wouldn't
pass the >= 0x10000 check we would otherwise use.
> ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points
> of each plane are noncharacters: U+FFFE and U+FFFF on the BMP...
*/
var wasPrintableChar = event.key.length === 1 || event.key.length === 2 && event.key.charCodeAt(0) >= 0xD800 || event.key === 'Unidentified';
var BACK_KEY = constants_1.KEY_CODES.BACK_KEY,
DELETE_KEY = constants_1.KEY_CODES.DELETE_KEY,
ENTER_KEY = constants_1.KEY_CODES.ENTER_KEY,
Expand All @@ -1275,15 +1296,15 @@ var Choices = /** @class */function () {
DOWN_KEY = constants_1.KEY_CODES.DOWN_KEY,
PAGE_UP_KEY = constants_1.KEY_CODES.PAGE_UP_KEY,
PAGE_DOWN_KEY = constants_1.KEY_CODES.PAGE_DOWN_KEY;
if (!this._isTextElement && !hasActiveDropdown && wasPrintableChar) {
if (!this._isTextElement && !hasActiveDropdown) {
this.showDropdown();
if (!this.input.isFocussed) {
if (!this.input.isFocussed && wasPrintableChar) {
/*
We update the input value with the pressed key as
the input was not focussed at the time of key press
therefore does not have the value of the key.
*/
this.input.value += event.key.toLowerCase();
this.input.value += event.key;
}
}
switch (keyCode) {
Expand Down
2 changes: 1 addition & 1 deletion public/assets/scripts/choices.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/assets/scripts/choices.min.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
/*! choices.js v10.2.0 | © 2022 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
/*! choices.js v10.2.0 | © 2023 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
2 changes: 1 addition & 1 deletion public/types/src/scripts/choices.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 18 additions & 7 deletions src/scripts/choices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2196,16 +2196,17 @@ describe('choices', () => {

describe('direction key', () => {
const keyCodes = [
KEY_CODES.UP_KEY,
KEY_CODES.DOWN_KEY,
KEY_CODES.PAGE_UP_KEY,
KEY_CODES.PAGE_DOWN_KEY,
[KEY_CODES.UP_KEY, 'ArrowUp'],
[KEY_CODES.DOWN_KEY, 'ArrowDown'],
[KEY_CODES.PAGE_UP_KEY, 'PageUp'],
[KEY_CODES.PAGE_DOWN_KEY, 'PageDown'],
];

keyCodes.forEach((keyCode) => {
keyCodes.forEach(([keyCode, key]) => {
it(`calls _onDirectionKey with the expected arguments`, () => {
const event = {
keyCode,
key,
};

instance._onKeyDown(event);
Expand All @@ -2222,6 +2223,7 @@ describe('choices', () => {
it(`calls _onSelectKey with the expected arguments`, () => {
const event = {
keyCode: KEY_CODES.A_KEY,
key: 'A',
};

instance._onKeyDown(event);
Expand All @@ -2237,6 +2239,7 @@ describe('choices', () => {
it(`calls _onEnterKey with the expected arguments`, () => {
const event = {
keyCode: KEY_CODES.ENTER_KEY,
key: 'Enter',
};

instance._onKeyDown(event);
Expand All @@ -2250,12 +2253,20 @@ describe('choices', () => {
});

describe('delete key', () => {
const keyCodes = [KEY_CODES.DELETE_KEY, KEY_CODES.BACK_KEY];
// this is not an error; the constants are named the reverse of their assigned key names, according
// to their actual values, which appear to conform to the Windows VK mappings:
// 0x08 = 'Backspace', 0x2E = 'Delete'
// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#editing_keys
const keyCodes = [
[KEY_CODES.DELETE_KEY, 'Backspace'],
[KEY_CODES.BACK_KEY, 'Delete'],
];

keyCodes.forEach((keyCode) => {
keyCodes.forEach(([keyCode, key]) => {
it(`calls _onDeleteKey with the expected arguments`, () => {
const event = {
keyCode,
key
};

instance._onKeyDown(event);
Expand Down
Loading