Choices-js / Choices

A vanilla JS customisable select box/text input plugin ⚡️
https://choices-js.github.io/Choices/
MIT License
6.19k stars 610 forks source link

Input search breaks when dropdown is opened by arrow keys #613

Open DarkMikey opened 5 years ago

DarkMikey commented 5 years ago

I recently implemented search to our single selects and came a across a bug, that occurs if you open the drop down by pressing arrow down. This leads to a broken search. You can't enter anything, cursor won't be displayed and if you click on the search input the drop down closes.

You can reproduce this at the first input under the headline "Single select input" at https://joshuajohnson.co.uk/Choices/

  1. Click on the first single select which displays "This is a placeholder"
  2. Click on any Choice (e.g. "choice 1")
  3. Press arrow-down-key -> drop down opens with broken search. Couldn't get it to work beside reloading the page.

Greetings

edit:

Choices-Google-Chrome-2019-11-06-13-56-03

jshjohnson commented 4 years ago

Closing as I cannot replicate this 👍

DarkMikey commented 4 years ago

@jshjohnson Not solved at all. I just followed my instructions again and the issue still persists. Please tell me if you need more info from my side.

jshjohnson commented 4 years ago

If you could provide a reduce test case highlighting the issue, that'd be great 👍

DarkMikey commented 4 years ago

Hope this helps. After reopening the dropdown with arrow-down key, search is not working anymore Choices-Google-Chrome-2019-11-06-13-56-03

jshjohnson commented 4 years ago

Which browser and OS are you using @DarkMikey ?

DarkMikey commented 4 years ago

Ah yeah sorry. Windows 10 64-Bit, Firefox 70.0.1 (64-Bit) and also Chrome Version 78.0.3904.87 (Official Build) (64-bit). Does this not happen for you?

jshjohnson commented 4 years ago

Yeah I cannot replicate this on Chrome (Mac OSX) - I'll be interested to know whether this is still an issue with the upcoming release as we made some changes to event listeners

sachicortes commented 4 years ago

The problem persists, I can confirm. I'm using Chrome Versión 76.0.3809.100 (Build oficial) (64 bits) in ubuntu 18.04. I have also tested it on browserstack with several other browsers (IE11, FF70 and others). It happens when you toggle the dropdown with the down arrow key while the focus is on the choices element. After this all event listeners attached to the input stop working and can't be focused. It happens on the single select input.

DarkMikey commented 4 years ago

So, can we re-open this?

dirkjf commented 1 year ago

The problem is String.fromCharCode(keyCode) is unreliable. For the arrowUp key (code 38) this returns & and for arrowDown (code 40) this returns (. This then returns true for wasPrintableChar which causes this bug.

dirkjf commented 1 year ago
const wasPrintableChar =
  (keyCode > 47 && keyCode < 58)   || // number keys
  (keyCode == 32)   ||                // spacebar
  (keyCode > 64 && keyCode < 91)   || // letter keys
  (keyCode > 95 && keyCode < 112)  || // numpad keys
  (keyCode > 185 && keyCode < 193) || // ;=,-./` (in order)
  (keyCode > 218 && keyCode < 223);   // [\]' (in order)

Something like this would solve this bug. But maybe opening the dropdown on focus and moving the focus to the input field would be a better solution? This would allow any character to be used. You could then use exceptions for keys like ArrowUp, ArrowDown, Tab, Delete and Enter for the required different behaviours.

ConceptImage commented 1 year ago

Any update on this ? I'm struggling with the same issue with arrow keys, or "Windows" keypress reopening the search input and writing "meta" on it, using choiceS.js 10.2.0

authanram commented 10 months ago

I can confirm with 10.2.0 on macos, Chrome Version 119.0.6045.199 (Official Build) (arm64).

It's pretty simple to reproduce. Just focus the field via "tab" and then press "arrowdown".

Screenshot 2023-12-04 at 09 29 04

Command key on mac does it also, it just fills in the key code "meta".

The field is not longer working afterwards. Clicking the input will close the field. So the page needs to be reloaded to reset it.

I noticed it while using https://filamentphp.com/.

RuzgarDogu commented 8 months ago

Hello, I encountered the same issue and struggled to find a solution. However, I managed to devise a workaround that may be helpful:

export const loadChoices = async (node) => {
    if (typeof window !== 'undefined') {
        const Choices = (await import('choices.js')).default;
        let c = new Choices(node, { 
            placeholder: false, 
            itemSelectText: '', 
            searchFields: ['label'], 
            searchPlaceholderValue: 'Search here...',
            callbackOnInit: function() {
                let container = this.containerOuter.element
                container.addEventListener('focus', () => {
                    this.showDropdown()
                }, true); // Use capture phase
            }
        });
        return c;
    }
};
dontWatchMeCode commented 4 weeks ago

Not the most elegant solution, but this worked for me:

window.addEventListener(
    'keydown',
    (e) => {
        /** @type {HTMLElement} */
        const target = e.target;

        /* @type {string} */
        const key = e.key;

        if (!target.classList.contains('choices')) return;
        if (key == 'Tab') return;

        e.stopPropagation();
        e.preventDefault();

        const selectId = String(target.querySelector('.choices__input')?.getAttribute('ID')).trim();
        if (!selectId) return console.warn('id not found no initial select item');

        const selectInstance = globalThis.vars.choices.get(selectId);
        if (!selectInstance) return console.warn('choices instance not found in global vars');

        selectInstance.showDropdown();
    },
    true,
);

globalThis.vars.choices is just a global ID map of the instances.

Solution by @RuzgarDogu worked but, I could still reproduce the error when focusing the Element by Shift+Tab / Tab.

This also solves Firefox opening the search Field when typing if the "search on type" setting is active.


Replacing the current keydown on the root choices element with something similar would also work, not 100% sure if this might break something else.

arcanefoam commented 2 weeks ago

I think it might be also related that in the _onDirectionKey method, the search is disabled:

  _onDirectionKey(event: KeyboardEvent, hasActiveDropdown: boolean): void {
    const { keyCode, metaKey } = event;
    const {
      DOWN_KEY: downKey,
      PAGE_UP_KEY: pageUpKey,
      PAGE_DOWN_KEY: pageDownKey,
    } = KEY_CODES;

    // If up or down key is pressed, traverse through options
    if (hasActiveDropdown || this._isSelectOneElement) {
      this.showDropdown();
      this._canSearch = false;
   ...
abbasmashaddy72 commented 2 weeks ago

Issue with Arrow Keys in Choices.js

I’m still facing the issue where the arrow keys (Up, Down, Left, Right) interfere with the behavior of Choices.js dropdowns while typing or interacting with the input. After reading suggestions from @dontWatchMeCode, I implemented the following workaround that resolves the problem by preventing the arrow keys from taking effect only while focused on the input field. This allows normal typing functionality while disabling arrow keys.

My Solution

Here’s the code I used to disable arrow keys while keeping everything else working, such as typing and opening the dropdown on focus:

window.addEventListener(
    "keydown",
    (e) => {
        /** @type {HTMLElement} */
        const target = e.target;

        const key = e.key;

        // Only target elements with the 'choices' class (related to Choices.js inputs)
        if (!target.classList.contains("choices")) return;

        // Disable arrow keys to prevent navigation while typing
        if (
            key === "ArrowUp" ||
            key === "ArrowDown" ||
            key === "ArrowLeft" ||
            key === "ArrowRight"
        ) {
            e.stopPropagation();
            e.preventDefault();  // Stop arrow key functionality
            return;
        }

        // Get the Choices.js input element
        const selectId = String(
            target.querySelector(".choices__input")?.getAttribute("ID")
        ).trim();
        if (!selectId)
            return console.warn("ID not found: no initial select item");

        // Fetch the Choices.js instance
        const selectInstance = globalThis.vars.choices.get(selectId);
        if (!selectInstance)
            return console.warn("Choices instance not found in global vars");

        // Ensure the dropdown is shown after keypress
        selectInstance.showDropdown();
    },
    true
);

How It Works:

Why This Helps:

By disabling only the arrow keys and allowing other keys to function normally, this solution maintains the behavior of Choices.js input fields while avoiding the issue where "arrowdown" or other key names get typed into the input field.