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

Add ComboboxControl and story #25442

Merged
merged 9 commits into from
Sep 28, 2020
Merged

Add ComboboxControl and story #25442

merged 9 commits into from
Sep 28, 2020

Conversation

youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Sep 18, 2020

This PR tries to add a Combobox/Autocomplete component to be used for PostAuthor/PageParent (selects where the number of entries can be very high). The idea is to replace the Downshift based one ultimately.

This component is just an adaptation of the existing FormTokenField component for a single value selection.

  • To test, run stroybook locally npm run storybook:dev
  • Open the FormTokenField -> Combobox story
  • Test :)

Thanks for your help.

@youknowriad youknowriad added Needs Design Feedback Needs general design feedback. [Feature] UI Components Impacts or related to the UI component system Needs Accessibility Feedback Need input from accessibility labels Sep 18, 2020
@github-actions
Copy link

github-actions bot commented Sep 18, 2020

Size Change: +2.93 kB (0%)

Total Size: 1.17 MB

Filename Size Change
build/api-fetch/index.js 3.35 kB +11 B (0%)
build/block-directory/index.js 8.53 kB +3 B (0%)
build/block-editor/index.js 128 kB +52 B (0%)
build/block-editor/style-rtl.css 11.1 kB -2 B (0%)
build/block-editor/style.css 11.1 kB -1 B
build/block-library/editor-rtl.css 8.57 kB -10 B (0%)
build/block-library/editor.css 8.58 kB -10 B (0%)
build/block-library/index.js 135 kB +6 B (0%)
build/block-library/style-rtl.css 7.61 kB +9 B (0%)
build/block-library/style.css 7.6 kB +12 B (0%)
build/blocks/index.js 47.5 kB -2 B (0%)
build/components/index.js 168 kB +972 B (0%)
build/components/style-rtl.css 15.4 kB -106 B (0%)
build/components/style.css 15.3 kB -105 B (0%)
build/core-data/index.js 12 kB -1 B
build/data-controls/index.js 1.27 kB +2 B (0%)
build/data/index.js 8.43 kB +5 B (0%)
build/edit-navigation/index.js 10.7 kB +239 B (2%)
build/edit-post/index.js 306 kB -1 B
build/edit-post/style-rtl.css 6.25 kB +10 B (0%)
build/edit-post/style.css 6.24 kB +10 B (0%)
build/edit-site/index.js 20.5 kB +876 B (4%)
build/edit-site/style-rtl.css 3.54 kB +239 B (6%) 🔍
build/edit-site/style.css 3.54 kB +237 B (6%) 🔍
build/edit-widgets/index.js 17.9 kB +316 B (1%)
build/edit-widgets/style-rtl.css 2.82 kB +24 B (0%)
build/edit-widgets/style.css 2.82 kB +24 B (0%)
build/editor/index.js 45.4 kB +91 B (0%)
build/editor/style-rtl.css 3.83 kB +26 B (0%)
build/editor/style.css 3.82 kB +24 B (0%)
build/element/index.js 4.44 kB -8 B (0%)
build/rich-text/index.js 13.7 kB -11 B (0%)
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/annotations/index.js 3.52 kB 0 B
build/autop/index.js 2.72 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/style-rtl.css 943 B 0 B
build/block-directory/style.css 942 B 0 B
build/block-library/theme-rtl.css 741 B 0 B
build/block-library/theme.css 741 B 0 B
build/block-serialization-default-parser/index.js 1.78 kB 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/compose/index.js 9.42 kB 0 B
build/date/index.js 31.9 kB 0 B
build/deprecated/index.js 772 B 0 B
build/dom-ready/index.js 568 B 0 B
build/dom/index.js 4.42 kB 0 B
build/edit-navigation/style-rtl.css 868 B 0 B
build/edit-navigation/style.css 871 B 0 B
build/editor/editor-styles-rtl.css 492 B 0 B
build/editor/editor-styles.css 493 B 0 B
build/escape-html/index.js 733 B 0 B
build/format-library/index.js 7.49 kB 0 B
build/format-library/style-rtl.css 547 B 0 B
build/format-library/style.css 548 B 0 B
build/hooks/index.js 1.74 kB 0 B
build/html-entities/index.js 621 B 0 B
build/i18n/index.js 3.55 kB 0 B
build/is-shallow-equal/index.js 709 B 0 B
build/keyboard-shortcuts/index.js 2.39 kB 0 B
build/keycodes/index.js 1.85 kB 0 B
build/list-reusable-blocks/index.js 3.02 kB 0 B
build/list-reusable-blocks/style-rtl.css 476 B 0 B
build/list-reusable-blocks/style.css 476 B 0 B
build/media-utils/index.js 5.12 kB 0 B
build/notices/index.js 1.69 kB 0 B
build/nux/index.js 3.27 kB 0 B
build/nux/style-rtl.css 671 B 0 B
build/nux/style.css 668 B 0 B
build/plugins/index.js 2.44 kB 0 B
build/primitives/index.js 1.34 kB 0 B
build/priority-queue/index.js 789 B 0 B
build/redux-routine/index.js 2.85 kB 0 B
build/server-side-render/index.js 2.61 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/token-list/index.js 1.24 kB 0 B
build/url/index.js 4.06 kB 0 B
build/viewport/index.js 1.74 kB 0 B
build/warning/index.js 1.13 kB 0 B
build/wordcount/index.js 1.17 kB 0 B

compressed-size-action

@adamsilverstein
Copy link
Member

@youknowriad Nice! I'll give this a test. Related is #25420 where I try using the FormTokenField for the Author selector. Working on that I was thinking we might go this direction.

suggestions={ availableContinents }
onChange={ ( tokens ) => setSelectedContinent( tokens ) }
onInputChange={ searchContinents }
placeholder="Type a continent"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Translate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's fine here, it's just a story.

@adamsilverstein
Copy link
Member

Overall this worked really well in my testing, nice work! A couple of questions/comments:

  • The passed placeholder never shows up in my testing, is that expected?
  • I would like to test against a much larger result set. Does the component display a spinner or loading state while the search is executing?
  • Got this error at some point: TypeError: Cannot read property 'length' of null

image

Steps to reproduce:

  • Load the component and type in a matching search, "as"
  • Click outside the results.

@youknowriad
Copy link
Contributor Author

The passed placeholder never shows up in my testing, is that expected?

this was inherited as is from FormTokenField so I'll have to check.

I would like to test against a much larger result set. Does the component display a spinner or loading state while the search is executing?

No it doesn't support a loading state, it's just like the "tags" input available today, it updates the suggestions when ready. I can see how some sense of "progress/loading" can be useful.

Base automatically changed from add/form-token-field-story to master September 21, 2020 14:32
@enriquesanchez
Copy link
Contributor

@youknowriad I'm getting a match is null error while trying to run and test. Am I perhaps missing a step? So far I tried:

  1. switch to try/autocomplete-combobox
  2. run npm run storybook:dev
  3. Open the FormTokenField -> Combobox story

Screen Shot 2020-09-21 at 15 44 46

@youknowriad
Copy link
Contributor Author

@enriquesanchez I can't reproduce your error 🤔. Do you have npm run dev running as well? stop it if it's the case.

Copy link
Member

@diegohaz diegohaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @youknowriad!

Tested it quickly with VoiceOver and NVDA. Aside from the points I mention below, which are not specific to screen readers, it's working well on VoiceOver. On NVDA, though, I noticed that "x results found" is announced twice the first time the combo box popup opens. Also, the combo box popup is announced just as list because it doesn't have an aria-label attribute.

Besides that, I found these interactions a bit weird. Not sure if they're expected or not:

  • Having to type two characters to see the suggestions is kind of annoying. I would expect it to show suggestions right away when I click on the input. It seems to be intentional though, so maybe I'm missing some context.

  • When you navigate to an option, there's no way to go back to the state where no option is selected. Usually, pressing ArrowUp on the first suggestion would unselect it so only the input would have focus. Typing additional characters on the input should also unselect any option.

  • I couldn't find a way to clear the input value.

  • Weird bug: type "oced", then delete "d". The popup will open, close and open again.

  • When I select an option, I expect the popup to close. But it keeps open.

  • When you have multiple items and the popup has a scrollbar, moving through suggestions don't affect scroll position. But it looks like it's a simple fix (just passing a prop to SuggestionsList).


FYI, there's a Combobox component in the works on Reakit. Here's an example: https://codesandbox.io/s/reakit-controlled-combobox-o1457

It's not quite ready yet. And it's still not optimized for big lists, but I've been exploring solutions for it, and it looks quite promising.

I'm currently testing with the Block Inserter.

@afercia
Copy link
Contributor

afercia commented Sep 23, 2020

Thanks @youknowriad, also for the ping in the Slack accessibility channel.

I tested the story extensively on:

  • macOS Safari + VoiceOver
  • Windows JAWS 2019 and NVDA with latest Firefox
  • Windows JAWS 2019 and NVDA with latest Chrome

It works as expected, meaning that the behavior and interaction aren't different from the existing Tags combobox.

As mentioned in #25267 (comment) the advantage of this implementation is that it sticks to the ARIA 1.0 combobox pattern. Downshift implements the ARIA 1.1 combobox pattern, which is different from the ARIA 1.0 one. The ARIA 1.1 pattern is buggy and undergoing revision. It doesn't work with Safari + VoiceOver.

I guess one more plus of this implementation would be reducing the usage of (maybe entirely removing?) Downshift.

That said, I noticed some of the things reported also by @diegohaz but they happen also on the existing Tags combobox so they're unrelated to this exploration. Specifically:

"x results found" is announced twice

Yes, twice with NVDA and multiple times with JAWS 2019. Maybe the aria-live message is sent multiple times or the component does a full re-render.

Regarding other considerations:

Also, the combo box popup is announced just as list because it doesn't have an aria-label attribute.

I don't think screen readers would ever announce the aria-label of the listbox because the real focus stays on the input field and it's not possible to really navigate to the listbox. However, if we want to be strict and follow the ARIA Authoring Practices examples, it should preferably use an aria-labelledby attribute pointing at the label element of the input. See details and examples on https://www.w3.org/TR/wai-aria-practices-1.1/#combobox

Having to type two characters to see the suggestions is kind of annoying. I would expect it to show suggestions right away when I click on the input. It seems to be intentional though, so maybe I'm missing some context.

I don't think we would want to hammer the REST API for just one typed character. Also, I'm not sure it would be a good user experience. In core, the de facto standard for many "live search" fields is a minimum of two characters.

When you navigate to an option, there's no way to go back to the state where no option is selected. Usually, pressing ArrowUp on the first suggestion would unselect it so only the input would have focus.

I'm not sure that's the expected interaction described in the ARIA Authoring Practices. Arrow Up and Down should loop through the suggestions. To clear the input field users can just delete what they entered. Optionally the delete key can be used to clear the field in one go, see:

Delete (Optional): Returns focus to the textbox, removes the selected state if a suggestion was selected, and removes the inline autocomplete string if present.

However, I wouldn't recommend to implement this optional behavior. IMHO it's best to keep things closer to a more native behavior.

Typing additional characters on the input should also unselect any option.

Not sure I understand this point. When typing additional characters and there's no match the suggestions listbox disappears, as expected.

I couldn't find a way to clear the input value.

Hm, I just used the delete key and I think that the expected behavior for all input fields? Also selecting the entered text and cutting works.

Btw, all these points are unrelated to this exploration. If there are things to fix (most notably the multiple announcement of the search results) these should be addressed separately.

@diegohaz
Copy link
Member

diegohaz commented Sep 23, 2020

I don't think screen readers would ever announce the aria-label of the listbox because the real focus stays on the input field and it's not possible to really navigate to the listbox.

In my tests, NVDA does announce the listbox label when the virtual focus is moved onto the suggestions for the first time. VoiceOver announces it if you navigate using VO + ArrowRight.

I don't think we would want to hammer the REST API for just one typed character. Also, I'm not sure it would be a good user experience. In core, the de facto standard for many "live search" fields is a minimum of two characters.

I see! I didn't assume that suggestions would be populated by a REST API as it seems to be a very generic component. The way I was expecting it was something similar to the Google Search combo box, which should be familiar to most parts of the internet. That's also how the WAI-ARIA example works.

For network requests, I think it would be better to rely on a sort of debounce method instead.

I'm not sure that's the expected interaction described in the ARIA Authoring Practices. Arrow Up and Down should loop through the suggestions.

In the example, it does loop through suggestions without focusing on the combobox. But the pattern describes it differently:

  • Down Arrow: Moves focus to and selects the next option. If focus is on the last option, either returns focus to the combobox or does nothing.
  • Up Arrow: Moves focus to and selects the previous option. If focus is on the first option, either returns focus to the combobox or does nothing.

Typing additional characters on the input should also unselect any option.

Not sure I understand this point. When typing additional characters and there's no match the suggestions listbox disappears, as expected.

I mean this behavior:

Typing a character on a combobox unselects the selected option and returns focus to the combobox

The WAI-ARIA pattern also covers this:

  • Any printable character: If the combobox is editable, returns the focus to the combobox without closing the popup and types the character. Otherwise, moves focus to the next option with a name that starts with the characters typed.

That's also how I've seen this working on other sites.

Hm, I just used the delete key and I think that the expected behavior for all input fields? Also selecting the entered text and cutting works.

Looks like the problem happens only when you already have selected an item.

Trying to clear the value of the combobox after selecting an option. It's always set back to the selected value

Btw, all these points are unrelated to this exploration. If there are things to fix (most notably the multiple announcement of the search results) these should be addressed separately.

I'm sorry! I wasn't aware of the context on this PR, so I just tested everything related to interactions and the a11y semantics. Feel free to ignore my points.

@enriquesanchez
Copy link
Contributor

Do you have npm run dev running as well? stop it if it's the case.

Thanks @youknowriad! I think that was my issue 😃


Tested on VoiceOver and works as expected most of the time. The component feels a bit buggy but I assume that's because it's still a WIP?

  • Sometimes I wasn't able to select a result. My virtual focus was on the element but I wasn't able to select it with Enter. This was very random and couldn't consistently replicate it.
  • Sometimes my focus was not constrained to the list of results, so if I pressed ArrowDown more than once it would jump to an unexpected place outside the component. This will also happen even if I had two results displayed. I'm not sure if this is a VoiceOver-only issue.

2020-09-23 16-30-11 2020-09-23 16_31_21

It'd be great to be able to test with a larger set of results to get a better feel of the interaction with more results.

@youknowriad
Copy link
Contributor Author

@haz I think I understand the issues you raise about the value reset behavior, it's fixed for me in the last commit.

@youknowriad
Copy link
Contributor Author

I don't think we would want to hammer the REST API for just one typed character. Also, I'm not sure it would be a good user experience. In core, the de facto standard for many "live search" fields is a minimum of two characters.
I see! I didn't assume that suggestions would be populated by a REST API as it seems to be a very generic component. The way I was expecting it was something similar to the Google Search combo box, which should be familiar to most parts of the internet. That's also how the WAI-ARIA example works.

For network requests, I think it would be better to rely on a sort of debounce method instead.

I agree with this as well, I feel it shouldn't be the responsibility of the component itself but more the caller.

@youknowriad
Copy link
Contributor Author

Thanks for the reviews everyone, I believe some of the issues are related to race conditions, refactoring the component to use hooks should make these easier to reason about and might solve some of these.

I'm happy to see that in general, this is behavior properly in terms of a11y.

I'll start the refactoring and polish and I'd like to leave the existing issues that are common with tags input to a separate PR (I'll create an issue for these later).

@afercia
Copy link
Contributor

afercia commented Sep 24, 2020

In my tests, NVDA does announce the listbox label when the virtual focus is moved onto the suggestions for the first time. VoiceOver announces it if you navigate using VO + ArrowRight.

I'm not sure why your NVDA is in "virtual focus" mode because as soon as you land on the input field it should switch to "focus mode". In focus mode, you're only editing the field so you can't navigate to the listbox.

In the example, it does loop through suggestions without focusing on the combobox. But the pattern describes it differently:

  • Down Arrow: Moves focus to and selects the next option. If focus is on the last option, either returns focus to the combobox or does nothing.
  • Up Arrow: Moves focus to and selects the previous option. If focus is on the first option, either returns focus to the combobox or does nothing.

@diegohaz I think this comes from the ARIA Authoring Practices 1.2, which are still a "Working Draft". As said above, the ARIA 1.2 combobox pattern is undergoing revision and we should not take this pattern into consideration. The ARIA Authoring Practices 1.2 reflect the ARIA 1.2 spec.

Instead, we should stick to the markup and interaction of the 1.0 pattern.

Same applies for some of your following points, that I think refers to 1.2

The whole purpose of this PR is to stick to the ARIA 1.0 pattern so it's important to pick up the correct specification and refer to the correct Authoring Practices details and examples.

@youknowriad
Copy link
Contributor Author

@diegohaz I noticed your emoji on my comment, is my fix incorrect? (I'm talking about the fact to type extra characters or empty the field to unset the value)

@diegohaz
Copy link
Member

@youknowriad It's not me! I think that's because you mentioned the wrong Haz. 😅

@youknowriad
Copy link
Contributor Author

Sorry @haz wrong ping :P

@haz
Copy link

haz commented Sep 24, 2020

Aye, I haven't done WP hacking in many years now. Carry on... ;)

@diegohaz
Copy link
Member

diegohaz commented Sep 24, 2020

I'm not sure why your NVDA is in "virtual focus" mode because as soon as you land on the input field it should switch to "focus mode". In focus mode, you're only editing the field so you can't navigate to the listbox.

NVDA does switch to focus mode. By "virtual focus" (which is the term MDN uses), I mean "visual focus" or the focus that is defined by the aria-activedescendant attribute.

I can record a GIF later, but the reproduction is pretty simple. I've upgraded NVDA recently, so that might be the reason.

@diegohaz I think this comes from the ARIA Authoring Practices 1.2, which are still a "Working Draft". As said above, the ARIA 1.2 combobox pattern is undergoing revision and we should not take this pattern into consideration. The ARIA Authoring Practices 1.2 reflect the ARIA 1.2 spec.

As far as I know, the problems around the combobox 1.1 patterns are related to the document structure, not the recommended keyboard interactions. The combobox 1.0 pattern has also structural problems because of the use of aria-owns. As far as I know, the only difference between combobox 1.0 and 1.2 is that the latter uses aria-controls instead of aria-owns.

Both Authoring Practices 1.1 and 1.2 recommend the same interactions regarding ArrowUp and ArrowDown. I couldn't find an equivalent document for 1.0 aside from the examples, which just describe how the examples work.

This is not a big deal for one-dimensional comboboxes as we should be able to move the caret on the input using horizontal arrow keys. But this becomes a bigger problem on tree/grid comboboxes where horizontal arrow keys move the visual focus within the popup. Without a clear way to return visual focus to the input, users can't move the caret. It's important to standardize as much as possible the interaction model across different types of comboboxes so we don't confuse users. That's why I recommend we follow 1.1 and 1.2 recommendations here.

Same applies for some of your following points, that I think refers to 1.2

The only other reference I made to the Authoring Practices are related to printable characters. It's still valid if you consider 1.0.

Pinging @jeryj who has done a lot of research on this topic.

@afercia
Copy link
Contributor

afercia commented Sep 24, 2020

As far as I know, the problems around the combobox 1.1 patterns are related to the document structure, not the recommended keyboard interactions. The combobox 1.0 pattern has also structural problems because of the use of aria-owns. As far as I know, the only difference between combobox 1.0 and 1.2 is that the latter uses aria-controls instead of aria-owns.

Both Authoring Practices 1.1 and 1.2 recommend the same interactions regarding ArrowUp and ArrowDown.

It is possible, however the point is that we should not look at 1.2 as the combobox pattern is undergoing substantial revision.

After some more digging, it appears there's something not well described in the ARIA Authoring Practices. On one hand, the expected interaction for the listbox https://www.w3.org/TR/wai-aria-practices-1.1/#listbox-popup-keyboard-interaction uses the words "... either ... or ..." thus suggesting that authors can choose between one of the following two options:

Up Arrow: Moves focus to and selects the previous option. If focus is on the first option, either returns focus to the textbox or does nothing.

But then, in the examples, there's one more pattern described 🎉 and it's the "looping" behavior:

https://www.w3.org/TR/wai-aria-practices-1.1/examples/combobox/aria1.1pattern/listbox-combo.html

"Listbox Popup" section

Down Arrow

If focus is on the last option, moves focus to the first option.
Note: This wrapping behavior is useful when Home and End move the editing cursor as described below.

Up Arrow

If focus is on the first option, moves focus to the last option.
Note: This wrapping behavior is useful when Home and End move the editing cursor as described below.

Home

Moves focus to the textbox and places the editing cursor at the beginning of the field.

End

Moves focus to the textbox and places the editing cursor at the end of the field.

So the W3C could improve clarity but there are actually 3 patterns that are valid. The "wrapping" (looping) behavior is valid and, to me, is the most intuitive one. Since this is already implemented, I don't see a good reason to change it.

@diegohaz
Copy link
Member

@youknowriad Tested again with NVDA, and now also with JAWS. I can confirm that the following issues have been fixed with the latest commits:

  • I noticed that "x results found" is announced twice the first time the combo box popup opens. ✅
  • I couldn't find a way to clear the input value. ✅
  • Weird bug: type "oced", then delete "d". The popup will open, close and open again. ✅
  • When you have multiple items and the popup has a scrollbar, moving through suggestions don't affect scroll position. But it looks like it's a simple fix (just passing a prop to SuggestionsList). ✅

Regarding the use of the ComboboxControl component, isn't this component used in other places? I'm a bit surprised that it wasn't even exported.

@youknowriad
Copy link
Contributor Author

yeah, I was surprised too, I saw it used on PRs but apparently it was not used anywhere.

@youknowriad youknowriad marked this pull request as ready for review September 25, 2020 10:37
@youknowriad youknowriad changed the title Try autocomplete combobox control Add ComboboxControl and story Sep 25, 2020
@youknowriad youknowriad self-assigned this Sep 25, 2020
@youknowriad
Copy link
Contributor Author

I think we're reaching a good state for initial merge. WDYT. I'd like to move forward and use this in PostAuthor and ParentPage

@enriquesanchez
Copy link
Contributor

This is so much better, @youknowriad 👍

Tested it again and all the buginness and issues with Up and Down I found before are gone.

I just came across one issue: When selecting an option from the results, VoiceOver announces "1 result found", as if its performing a search with the selected option:

Screen Shot 2020-09-25 at 15 33 12

Is this expected? I understand this is doing a live search and it's why this is happening, but it threw me off a little.

@youknowriad
Copy link
Contributor Author

@enriquesanchez I think it's fixed with a few other tweaks too.

@youknowriad youknowriad merged commit 58c0aff into master Sep 28, 2020
@youknowriad youknowriad deleted the try/autocomplete-combobox branch September 28, 2020 09:07
@youknowriad youknowriad added the [Type] New API New API to be used by plugin developers or package users. label Sep 28, 2020
@github-actions github-actions bot added this to the Gutenberg 9.1 milestone Sep 28, 2020
@adamsilverstein
Copy link
Member

@youknowriad Excellent work here! I will work on using this for the author/parent page selectors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] UI Components Impacts or related to the UI component system Needs Accessibility Feedback Need input from accessibility Needs Design Feedback Needs general design feedback. [Type] New API New API to be used by plugin developers or package users.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants