react-spectrum icon indicating copy to clipboard operation
react-spectrum copied to clipboard

ComboBox with autofocus doesn't show its dropdown auto focused with `menuTrigger='focus'`

Open LFDanLu opened this issue 2 years ago • 16 comments

Discussed in https://github.com/adobe/react-spectrum/discussions/5462

Originally posted by austincrim November 22, 2023 I'm trying to build a CMD + K-like interface using a ComboBox inside of a Dialog. I want the ListBox options to always be visible, regardless if the Input has been typed in or manually focused (using the ComboBox menuTrigger prop).

I couldn't find a way to do this in the docs. Any recommendations?

Here's how it looks now, but I want the list of options to be visible as soon as the dialog is opened.

Stackblitz link

https://github.com/adobe/react-spectrum/assets/32459922/1cc5dca5-8ba1-4293-8005-9820f9ba74b5

As an aside, the React Spectrum support page still links to creating a new GitHub Issue, when it looks like you've switched to Discussions.

See https://github.com/adobe/react-spectrum/discussions/5462#discussioncomment-7647429 for more info. The gist is that the collection for the ComboBox is undefined/empty when the input gets autofocused since RAC collections need two renders to fully generate.

LFDanLu avatar Nov 23 '23 00:11 LFDanLu

Exactly the same situation here ! Interested in any input from OP or anyone else on how to achieve this !

Thank you very much

Spriithy avatar Dec 09 '23 22:12 Spriithy

Could you expand on how much control over the open state you need for the ComboBox here?

LFDanLu avatar Dec 11 '23 17:12 LFDanLu

I think I would like to have something like menuTrigger="always" or something like this.

Le lun. 11 déc. 2023, 18:45, Daniel Lu @.***> a écrit :

Could you expand on how much control over the open state you need for the ComboBox here?

— Reply to this email directly, view it on GitHub https://github.com/adobe/react-spectrum/issues/5464#issuecomment-1850569378, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASG6H63COSDGYOYD2D25CLYI5BDVAVCNFSM6AAAAAA7XAZPECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNJQGU3DSMZXHA . You are receiving this because you commented.Message ID: @.***>

Spriithy avatar Dec 11 '23 17:12 Spriithy

So would that mean the combobox is open at all times? If that is the case, I think you'd be better off using a searchfield + listbox (which I now see you've mentioned in the discussion) since the ComboBox's open overlay would hide the rest of the page from screenreaders when open. Depending on how much you'd want the experience to match ComboBox (aka would focus remain in the searchfield and up/down arrow keys move virtual focusing in the ListBox?), it would take some work extracting what you need from the hooks. It the searchfield was more of a standalone experience from the listbox (aka typing in it would simply change what the listbox displays) then it would be more straightforward since you'd simply control the searchfield's input and use that to fetch a list of items for the listbox.

LFDanLu avatar Dec 11 '23 18:12 LFDanLu

What I really find difficult with the searchfield + listbox solution is that, being fairly new to react aria, I wouldn't know how to keep the focus on the input yet still be able to navigate options with the arrow keys.

Also, I'm facing a bug with combobox where, with a not so large list, typing then navigating with arrows then typing again makes the input lose focus. I should probably open an issue with this, but I can't reproduce the bug on a standalone page... (data is fetched from a React Context).

Le lun. 11 déc. 2023, 19:02, Daniel Lu @.***> a écrit :

So would that mean the combobox is open at all times? If that is the case, I think you'd be better off using a searchfield + listbox (which I now see you've mentioned in the discussion) since the ComboBox's open overlay would hide the rest of the page from screenreaders when open. Depending on how much you'd want the experience to match ComboBox (aka would focus remain in the searchfield and up/down arrow keys move virtual focusing in the ListBox?), it would take some work extracting what you need from the hooks. It the searchfield was more of a standalone experience from the listbox (aka typing in it would simply change what the listbox displays) then it would be more straightforward since you'd simply control the searchfield's input and use that to fetch a list of items for the listbox.

— Reply to this email directly, view it on GitHub https://github.com/adobe/react-spectrum/issues/5464#issuecomment-1850598763, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASG6H6KBG25GM4MX5IPB3LYI5DC7AVCNFSM6AAAAAA7XAZPECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNJQGU4TQNZWGM . You are receiving this because you commented.Message ID: @.***>

Spriithy avatar Dec 11 '23 18:12 Spriithy

What I really find difficult with the searchfield + listbox solution is that, being fairly new to react aria, I wouldn't know how to keep the focus on the input yet still be able to navigate options with the arrow keys.

So in short, the way this is handled is via tracking the "focusedKey" in state (aka the selectionManager), modifying it via the keyboard handlers attached to the input field (also see https://github.com/adobe/react-spectrum/blob/5ce9a29250e7cd009d73d9d1d36aa1010d7099b9/packages/%40react-aria/selection/src/useSelectableCollection.ts#L123-L189 for the specifics in useSelectableCollection), and then passing that same state (aka the collection + selection manager) with the appropriate options to the listbox so it will use virtual focus.

Also, I'm facing a bug with combobox where, with a not so large list, typing then navigating with arrows then typing again makes the input lose focus. I should probably open an issue with this, but I can't reproduce the bug on a standalone page... (data is fetched from a React Context).

Not sure what would be causing this, definitely file an issue once you get a base reproduction!

LFDanLu avatar Dec 11 '23 18:12 LFDanLu

Well thank you very much for the detailed answer and suggestions. This makes a lot of digging and internals comprehension for my use case. I'll make sure to give it a try anyways.

Spriithy avatar Dec 11 '23 19:12 Spriithy

Yeah, unfortunately the hooks as is come with a bit too much extra stuff, it would take some work pulling out the relevant code. Hopefully in the future we'll have a more base level useAutoComplete hook that only offers the collection filtering + focus handling without all the extra overlay open/closed state tracking

LFDanLu avatar Dec 12 '23 19:12 LFDanLu

Well that would be great ! as I believe many people must be reimplementing this themselves ?

On Tue, 12 Dec 2023 at 20:24, Daniel Lu @.***> wrote:

Yeah, unfortunately the hooks as is come with a bit too much extra stuff, it would take some more pulling out the relevant code. Hopefully in the future we'll have a more base level useAutoComplete hook that only offers the collection filtering + focus handling without all the extra overlay open/closed state tracking

— Reply to this email directly, view it on GitHub https://github.com/adobe/react-spectrum/issues/5464#issuecomment-1852669670, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASG6HYFJIEQOU7WXYOP7KTYJCVPZAVCNFSM6AAAAAA7XAZPECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNJSGY3DSNRXGA . You are receiving this because you commented.Message ID: @.***>

Spriithy avatar Dec 12 '23 19:12 Spriithy

So I've managed to control a ListBox using a search field like so :

But I cannot get the ListBox focus to wrap even though I've passed shouldFocusWrap: true. Also, how would I go setting up keyboard virtual focus between the input and the ListBox ?

function SearchableListBox() {
  const { globalSearch, setGlobalSearch } = useState("");
    const searchHistory = useState([
        { id: "a", search: "foo bar", date: new Date(), error: false },
        { id: "b", search: "foo baz", date: new Date(), error: false },
        { id: "c", search: "foo bar baz", date: new Date(), error: true },
        { id: "d", search: "bar baz", date: new Date(), error: false },
    ]); // replaced by data normally retrieved in IndexedDB using Dexie*/

    const getSearchEntryById = (id) => searchHistory.filter((query) => id === `${query.id}`).at(0);

    const listBoxItem = (query) => (
        <Item key={query.id} textValue={query.search} title={query.search} query={query} />
    );

    const listBoxState = useSingleSelectListState({
        onSelectionChange: (id) => setGlobalSearch(getSearchEntryById(id).search),
        items: searchHistory.filter((query) => query.search.indexOf(globalSearch) >= 0),
        children: listBoxItem,
    });

    // Get props for the listbox element
    let listBoxRef = React.useRef(null);
    let { listBoxProps } = useListBox({
        label: "Search history",
        shouldFocusWrap: true,
    }, listBoxState, listBoxRef);

    let inputRef = useRef(null);

    return (
        <>
                <input
                    value={globalSearch}
                    onChange={(e) => setGlobalSearch(e.target.value)}
                    ref={inputRef}
                    id="searchbar"
                    placeholder="Search"
                />

                <ListBox {...listBoxProps} listBoxRef={listBoxRef} state={listBoxState} />
        </>
    );
}

PS : the filter prop in the useSingleSelectListState is quite obscure to me since it doesn't match the ComboBox defaultFilter (this seems logical though, since the ListBox isn't related to a search field's input value).

Spriithy avatar Dec 12 '23 20:12 Spriithy

Just chiming in that I haven't forgotten about this, but we're pretty busy with an upcoming release so I haven't been able to look at this in-depth.

the keyboard virtual focus can be setup by doing something like https://github.com/adobe/react-spectrum/blob/2b05b6d984e1b37d4ac76f0bb305433d8f57d988/packages/%40react-aria/combobox/src/useComboBox.ts#L98-L108 and passing the keydown handlers to you input: https://github.com/adobe/react-spectrum/blob/2b05b6d984e1b37d4ac76f0bb305433d8f57d988/packages/%40react-aria/combobox/src/useComboBox.ts#L188. You may need to add/remove some additional onKeyDown behavior similar to what we do in useComboBox but not having a open state to flip on/off should make that easier. I'm not sure about the shouldFocusWrap issue, would need to dig.

LFDanLu avatar Dec 14 '23 21:12 LFDanLu

What I really find difficult with the searchfield + listbox solution is that, being fairly new to react aria, I wouldn't know how to keep the focus on the input yet still be able to navigate options with the arrow keys.

So in short, the way this is handled is via tracking the "focusedKey" in state (aka the selectionManager), modifying it via the keyboard handlers attached to the input field (also see

https://github.com/adobe/react-spectrum/blob/5ce9a29250e7cd009d73d9d1d36aa1010d7099b9/packages/%40react-aria/selection/src/useSelectableCollection.ts#L123-L189

for the specifics in useSelectableCollection), and then passing that same state (aka the collection + selection manager) with the appropriate options to the listbox so it will use virtual focus.

Also, I'm facing a bug with combobox where, with a not so large list, typing then navigating with arrows then typing again makes the input lose focus. I should probably open an issue with this, but I can't reproduce the bug on a standalone page... (data is fetched from a React Context).

Not sure what would be causing this, definitely file an issue once you get a base reproduction!

Hi! I tried to follow your suggestion and am running into an issue: it appears like the collection is always empty.

It was all pretty straightforward to put together, thanks to the amazing composition API you all designed, and I feel like I'm close, but I also feel like I'm missing something. Curious if anything comes to mind with what I've documented over at https://github.com/adobe/react-spectrum/discussions/6281?

apucacao avatar May 02 '24 19:05 apucacao

Just to double check, the code you added in https://github.com/adobe/react-spectrum/discussions/6281 your most up to date code? When you say the collection is always empty, I assume that means the ListBox itself isn't rendering anything at all? I assume the ListBox you are using is straight from RAC or is it a modified version?

EDIT: Also @devongovett pointed out that if you are copying Collection.tsx + the related contexts but are also using ListBox as is from RAC, then it won't work because the contexts like CollectionDocumentContext/etc will be different. You'll have to essentially copy over ListBox as well so that it uses your version of the context.

LFDanLu avatar May 02 '24 20:05 LFDanLu

Just to double check, the code you added in #6281 your most up to date code? When you say the collection is always empty, I assume that means the ListBox itself isn't rendering anything at all? I assume the ListBox you are using is straight from RAC or is it a modified version?

EDIT: Also @devongovett pointed out that if you are copying Collection.tsx + the related contexts but are also using ListBox as is from RAC, then it won't work because the contexts like CollectionDocumentContext/etc will be different. You'll have to essentially copy over ListBox as well so that it uses your version of the context.

Correct -- that's the most up-to-date code. The ListBox does indeed render empty and the underlying collection is empty (even though I can see items going into useListState). And the ListBox I'm was using is our version that adds custom styles to the one from RAC, like this.

The update to that post about copying Collection.tsx didn't solve it, and I can try your suggestion to ensure the contexts are identical. The thing is that before I reached for that, the collection was empty too.

apucacao avatar May 06 '24 13:05 apucacao

I take it that it should have worked as-is with the off-the-shelf RAC ListBox? Is the Collection.tsx stuff actually required for what I'm trying to do?

apucacao avatar May 08 '24 13:05 apucacao

The off the shelf ListBox should at the very least be able to render to items when used standalone so if that isn't working then that is unexpected. As for using RAC to build this autocomplete experience, yes you'll need useCollectionDocument so that the collection between the AutoCompleteListBox and the ListBox it wraps can be shared/filtered (similar to ComboBox's current implementation as you've noted already). However, still theoretical since I haven't tried building it with RAC yet, the conversation that happened early in the thread is using the old collection api prior to RAC that is still used in the hooks docs.

LFDanLu avatar May 08 '24 16:05 LFDanLu