Skip to content

FluentAutoComplete: Single item enhancements #3571

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

Open
wants to merge 18 commits into
base: dev
Choose a base branch
from

Conversation

StevenRasmussen
Copy link
Contributor

Pull Request

📖 Description

When the MaximumSelectedOptions == 1, the control is rendered less than ideal due to the inherent nature that the control is expecting more than one item to be selected. This PR seeks to improve both the UI and UX of the FluentAutoComplete when only 1 item is desired.

🎫 Issues

Here is how the control renders like when the MaximumSelectedOptions == 1

image

Here is how the control renders with this PR:

image

✅ Checklist

General

  • I have added tests for my changes.
  • I have tested my changes.
  • I have updated the project documentation to reflect my changes.
  • I have read the CONTRIBUTING documentation and followed the standards for this project.

Component-specific

  • I have added a new component
  • I have added Unit Tests for my new component
  • I have modified an existing component
  • I have validated the Unit Tests for an existing component

@MarvinKlein1508
Copy link
Contributor

@StevenRasmussen wouldn't it make more sence to provide an itemprovider to the FluentCombobox ?
https://www.fluentui-blazor.net/Combobox

But I agree the current behaviour for single items within FluentAutocomplete could need some love :)

@StevenRasmussen
Copy link
Contributor Author

@MarvinKlein1508 - That might work if you only have a small list of items. However, when working with large datasets and you want to be able to search/filter items then the AutoComplete allows you to do this with the OnOptionsSearch callback. I don't think the ComboBox provides this type of functionality OOB.

@dvoituron
Copy link
Collaborator

Two important points:

  1. You cannot modify the code and adapt unit tests on existing components. Unit tests check that the result has not changed. For example, you may have modified the CSS class fluent-autocomplete-multiselect, which will impact hundreds of thousands of developers who may have customized the style. You can add features, but you can't remove or modify them without creating a breaking change.

  2. This visual change for MaximumSelectedOptions=“1” makes the code incompatible with Accessibility.
    Before
    image
    After
    image

@StevenRasmussen
Copy link
Contributor Author

@dvoituron - Thanks for the feedback :). Regarding your points:

  1. I will update the PR so that this will no longer be the case. The PR was aiming at "ideal" changes to the naming... however I will make it so that it will be backwards compatible and not break existing customizations of the class.
  2. I'm not really all that familiar with how to make the component compatible with Accessibility. Assuming I fix item 1 above, do you have any pointers and what needs to change to make it compatible?

@@ -1,5 +1,5 @@

<div class=" fluent-autocomplete-multiselect" style="width: 100%;" b-hg72r5b4ox="">
<div class=" fluent-autocomplete-multiselect item-selected" style="width: 100%;" b-hg72r5b4ox="">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dvoituron - Is this an acceptable modification to the logic and thus the unit test? A new class is being added if the count of the SelectedOptions > 0. This shouldn't break anyone since the main class name is un-changed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nop. It's not acceptable to change all Unit Tests.
You can do the "inverse": you can add an attribute single-selection only for you feature. And adapt the CSS in this case= .fluent-autocomplete-multiselect[single-selection] { ... }

<div class="fluent-autocomplete-multiselect" single-selection> ... 

if (ReadOnly || Disabled)
if (MaximumSelectedOptions == 1)
{
<FluentLabel aria-label="@GetOptionText(item)" role="checkbox" aria-checked="true">@text</FluentLabel>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dvoituron - I'm not sure if you think it appropriate to include a tab stop here or not. I left it out but could add it back in if you like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dvoituron - I believe adding the tab stop is what you are looking for as far as accessibility goes. I have added it so that you can see what the result is. Let me know if you still want other changes or if you still think we should pursue allowing Multiple = false;

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll check this out, but having a Multiple=false is still useful for people who want to keep the previous behavior.

@@ -79,7 +79,7 @@
Width="12px"
Style="cursor: pointer;"
Slot="end"
Title="@AccessibilityIconDismiss"
Title="@(MaximumSelectedOptions == 1 ? string.Format(AccessibilityRemoveItem, GetOptionText(SelectedOption)) : AccessibilityIconDismiss)"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dvoituron - When this is a single select (MaximumSelectedOptions == 1) then the accessibility text for the Clear button becomes the AccessibilityRemoveItem text which is something like Remove {0}.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did you change the current Title result: DismissTitle="@(string.Format(AccessibilityRemoveItem, text))" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was an attempt to improve the "accessibility" of the component. Since you can no longer remove the individual item, I felt it might be good for the 'Clear' button to include the name of the selected option and so I just updated it to use the same text as if you were removing a single item. I'm happy to change it back if you don't feel like this is a helpful update.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It's an option to add additional information in this case. But my biggest accessibility problem is that the component must “read” (with NVDA) all the elements present on the screen. This is still not the case when an item is selected, the user must be able to position himself on the component (focusable) and NVDA must announce something like “ selected”.

To move forward with this PR (and to be compatible with what already exists), it would probably be simpler to allow the Multiple=false parameter, which would activate your functionality. If Multiple=true, everything would work as it does now, and Accessibility would be compatbile.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you think about this idea?

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 tried this by making the following changes:

image

However, the control did not seem to work. It wouldn't allow me to select anything. I'm not opposed to this idea, I'm just not sure what else would be required to get it to work. At the end of the day, I'm simply looking for a way to only allow a single item to be selected and also have the UI styled in such a way that is optimal for a single item. As it stands, I believe this PR accomplishes that and does it in a way that does not break anything existing and is hopefully acceptable from an accessibility perspective with the latest change I pushed.

@dvoituron
Copy link
Collaborator

  1. I'm not really all that familiar with how to make the component compatible with Accessibility. Assuming I fix item 1 above, do you have any pointers and what needs to change to make it compatible?

Here are a few websites to explain Accessibility:

The best solution is to use a tool such as NVDA to validate the result.

@StevenRasmussen
Copy link
Contributor Author

@dvoituron - Thanks for the pointers regarding accessibility. I introduced changes that I believe have addressed your concern. That being said, I feel a bit like I'm blindfolded and trying to hit a dart board... I'm not quite sure what an acceptable solution is for accessibility. Please let me know if additional changes need to be made, otherwise I feel like the PR is ready for review again. Thanks!

@StevenRasmussen
Copy link
Contributor Author

@dvoituron - I wanted to check-in to see if you needed any additional changes to this PR. Thanks!

@@ -79,7 +79,7 @@
Width="12px"
Style="cursor: pointer;"
Slot="end"
Title="@AccessibilityIconDismiss"
Title="@(MaximumSelectedOptions == 1 ? string.Format(AccessibilityRemoveItem, GetOptionText(SelectedOption)) : AccessibilityIconDismiss)"
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's an option to add additional information in this case. But my biggest accessibility problem is that the component must “read” (with NVDA) all the elements present on the screen. This is still not the case when an item is selected, the user must be able to position himself on the component (focusable) and NVDA must announce something like “ selected”.

To move forward with this PR (and to be compatible with what already exists), it would probably be simpler to allow the Multiple=false parameter, which would activate your functionality. If Multiple=true, everything would work as it does now, and Accessibility would be compatbile.

&& SelectedOptions?.Count() > 0
&& !(AdditionalAttributes?.TryGetValue("single-select", out var isSingleSelect) ?? false))
{
var additionalAttributes = (AdditionalAttributes is not null)
Copy link
Collaborator

Choose a reason for hiding this comment

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

You don't need to update the AdditionalAttributes. You can add an attribute in the razor file like this:

<FluentTextField role="combobox" single-select="@GetSingleSelect()" ... >
private bool GetSingleSelect() => MaximumSelectedOptions == 1 && SelectedOptions?.Any() && Multiple == false;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@Trapulo
Copy link

Trapulo commented Apr 4, 2025

With this improvement (thanks! really important) Is there also a way to have a two-way simple binding to one single object it MaximumSelectedOptions = 1 to perform a simple two-way binding to an object's property referring to the selected object?

@dvoituron
Copy link
Collaborator

dvoituron commented Apr 13, 2025

With this improvement (thanks! really important) Is there also a way to have a two-way simple binding to one single object it MaximumSelectedOptions = 1 to perform a simple two-way binding to an object's property referring to the selected object?

No. By default, this component is dedicated to bind multiple items. Including when you set MaximumSelectedOptions=1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants