Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
ac9129a
Add FluentAutocomplete component with basic functionality and documen…
dvoituron Jan 23, 2026
0bb01c8
Merge branch 'dev-v5' into users/dvoituron/dev-v5/autocomplete
dvoituron Jan 24, 2026
6425745
Enhance FluentAutocomplete with selected items rendering and input ch…
dvoituron Jan 24, 2026
5a35040
Merge branch 'dev-v5' into users/dvoituron/dev-v5/autocomplete
dvoituron Mar 4, 2026
b08171a
Format Autocomplete component for better readability
dvoituron Mar 4, 2026
0494f71
Merge branch 'dev-v5' into users/dvoituron/dev-v5/autocomplete
dvoituron Mar 10, 2026
79dbc48
feat: enhance FluentAutocomplete with search functionality and multip…
dvoituron Mar 10, 2026
5d5540c
fix: refactor event handling in FluentAutocomplete for improved text …
dvoituron Mar 10, 2026
bfe1206
fix: add missing OptionValue binding in FluentListbox for proper item…
dvoituron Mar 10, 2026
784cbed
Merge branch 'dev-v5' into users/dvoituron/dev-v5/autocomplete
dvoituron Mar 19, 2026
e5e74c5
Refactored FluentAutocomplete to use internal lists for selected and …
dvoituron Mar 19, 2026
9a94856
Enhance ListboxExtended to manage pending selected options changes an…
dvoituron Mar 20, 2026
95845df
Add FluentAutocomplete component and initialize method for keyboard n…
dvoituron Mar 20, 2026
706c118
Refactor FluentAutocomplete to integrate DropdownOption and enhance k…
dvoituron Mar 20, 2026
acddfdf
Refactor keyboard navigation in FluentAutocomplete to use hovered att…
dvoituron Mar 20, 2026
8ecaa5d
Fix popover handling in FluentAutocomplete to ensure proper error han…
dvoituron Mar 20, 2026
406f211
Enhance FluentAutocomplete and FluentListbox to improve popover handl…
dvoituron Mar 20, 2026
8faffb9
Add progress indicator to FluentAutocomplete and handle keyboard inpu…
dvoituron Mar 20, 2026
4c68656
Enhance documentation in FluentAutocomplete by adding summaries for e…
dvoituron Mar 25, 2026
9c0f63b
Refactor FluentAutocomplete to use TextInput type for input handling
dvoituron Mar 25, 2026
8ab3694
Refactor FluentAutocomplete to improve input handling and display fil…
dvoituron Mar 25, 2026
9eeb5e6
Enhance FluentIcon and FluentAutocomplete to support click event prop…
dvoituron Mar 25, 2026
2fbc84b
Enhance FluentAutocomplete to manage input focus on popup close and a…
dvoituron Mar 25, 2026
65fc4d2
Enhance FluentAutocomplete to improve option display logic and input …
dvoituron Mar 25, 2026
f3d5464
Refactor FluentAutocomplete to improve progress indicator visibility …
dvoituron Mar 25, 2026
84fb5ba
Enhance FluentAutocomplete to add clear selection functionality and u…
dvoituron Mar 25, 2026
6f3ff63
Enhance FluentAutocomplete to improve Enter key handling for option s…
dvoituron Mar 25, 2026
ea62ec5
Enhance FluentAutocomplete to add MaxAutoHeight functionality for sel…
dvoituron Mar 25, 2026
88196f1
Refactor FluentAutocomplete CSS to enhance MaxAutoHeight layout and i…
dvoituron Mar 26, 2026
d210075
Enhance FluentAutocomplete to add MaxSelectedWidth functionality for …
dvoituron Mar 26, 2026
2f7f5a6
Merge branch 'dev-v5' into users/dvoituron/dev-v5/autocomplete
dvoituron Mar 31, 2026
8797dc2
Enhance FluentAutocomplete with Placeholder and InputAppearance param…
dvoituron Mar 31, 2026
fe34483
Enhance FluentAutocomplete option template with avatar and name displ…
dvoituron Mar 31, 2026
bda2fc6
Enhance FluentAutocomplete with customizable icons for Clear and Sear…
dvoituron Mar 31, 2026
19eff31
Refactor FluentAutocomplete examples to streamline code and enhance d…
dvoituron Mar 31, 2026
c7afd8c
Add FluentAutocomplete documentation with keyboard interaction detail…
dvoituron Mar 31, 2026
aecb51f
Enhance FluentAutocomplete to detect wrapped items and adjust layout …
dvoituron Apr 1, 2026
fffd1e0
Update FluentAutocomplete to allow dynamic MaxAutoHeight and adjust I…
dvoituron Apr 1, 2026
77029d3
Refactor ItemsComparer usage in FluentAutocomplete to simplify item c…
dvoituron Apr 1, 2026
ec409ed
Enhance FluentAutocomplete with ShowDismiss option and update styles …
dvoituron Apr 2, 2026
2ff773e
Add documentation and example for single item selection in FluentAuto…
dvoituron Apr 2, 2026
564010d
Merge branch 'dev-v5' into users/dvoituron/dev-v5/autocomplete
dvoituron Apr 2, 2026
cde18e0
Refactor item comparison logic in FluentAutocomplete and add example …
dvoituron Apr 2, 2026
853fd78
Enhance option selection logic in FluentAutocomplete
dvoituron Apr 3, 2026
45865b2
Enhance FluentAutocomplete to support single selection with 'alone' a…
dvoituron Apr 3, 2026
8e19372
Refactor FluentAutocomplete to support single selection mode and upda…
dvoituron Apr 3, 2026
b016de6
Adjust FluentAutocomplete styles for improved layout and spacing in s…
dvoituron Apr 3, 2026
b5386f5
Enhance FluentAutocomplete component with multiple selection support …
dvoituron Apr 3, 2026
b6fd07b
Add header and footer content support to FluentAutocomplete component
dvoituron Apr 3, 2026
48e83d0
Refactor FluentAutocomplete component to conditionally display progre…
dvoituron Apr 3, 2026
215f155
Add maximum selected options feature to FluentAutocomplete component
dvoituron Apr 3, 2026
1d31f71
Enhance FluentAutocomplete component with improved item filtering and…
dvoituron Apr 3, 2026
b49f52b
Add MaximumOptionsSearch property and set max-height for FluentAutoco…
dvoituron Apr 3, 2026
f1ee645
Fix item removal logic in FluentAutocomplete component to use compare…
dvoituron Apr 3, 2026
a5df965
Enhance FluentAutocomplete component with custom option templates and…
dvoituron Apr 5, 2026
aa576b0
Fix default unit tests
dvoituron Apr 5, 2026
39c4f0e
Add tests for FluentAutocomplete component and fix max-height style h…
dvoituron Apr 5, 2026
e420c6a
Add tests for FluentAutocomplete component to verify item removal and…
dvoituron Apr 5, 2026
b769c38
Add test for FluentAutocomplete component to verify progress indicato…
dvoituron Apr 5, 2026
e8625a9
Add tests for FluentAutocomplete component to verify popover closes o…
dvoituron Apr 5, 2026
5a2ea30
Refactor FluentAutocomplete methods to internal visibility and enhanc…
dvoituron Apr 5, 2026
271af5e
Add test for FluentAutocomplete component to verify no action on null…
dvoituron Apr 5, 2026
ee25613
Update FluentAutocomplete documentation to clarify default comparison…
dvoituron Apr 6, 2026
17ad830
Add migration documentation for FluentAutocomplete component in v5
dvoituron Apr 6, 2026
6bac17f
Fix formatting in FluentListBase.razor for option template rendering
dvoituron Apr 6, 2026
6cedcc9
Merge remote-tracking branch 'origin/dev-v5' into users/dvoituron/dev…
dvoituron Apr 6, 2026
613b5b5
Fix PR comments
dvoituron Apr 7, 2026
c06d608
Merge branch 'dev-v5' into users/dvoituron/dev-v5/autocomplete
vnbaaij Apr 7, 2026
86cf3da
Fix doc
dvoituron Apr 7, 2026
63e321b
Merge branch 'dev-v5' into users/dvoituron/dev-v5/autocomplete
vnbaaij Apr 7, 2026
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
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ indent_size = 4
end_of_line = lf

[*.{razor,cshtml}]
indent_size = 4
indent_style = space
charset = utf-8-bom

[*.{cs,vb}]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
@page "/Lists/Autocomplete/Debug"

@using static FluentUI.Demo.SampleData.Olympics2024
Comment thread
dvoituron marked this conversation as resolved.

<div style="margin: 24px;">

<div>Selected items: <b>@string.Join("; ", SelectedCountries.Select(c => c.Name))</b></div>
<div>Search Text: <b>@SearchText</b></div>

<FluentAutocomplete TOption="Country"
TValue="string"
Width="100%"
Label="Select countries"
Placeholder="Type to search..."
ShowProgressIndicator="@ShowProgressIndicator"
MaxAutoHeight="@((MaxAutoHeight) ? "unset" : null)"
OnOptionsSearch="@OnSearchAsync"
Items="@Countries"
OptionText="@(item => item.Name)"
MaxSelectedWidth="@(MaxSelectedWidth ? "40px" : null)"
ShowDismiss="@ShowDismiss"
Multiple="@Multiple"
MaximumSelectedOptions="@(SetMaximumSelectedOptions ? 4 : (int?)null)"
@bind-Value="@SearchText"
@bind-SelectedItems="@SelectedCountries">

@* Drop-down item template *@
<OptionTemplate>
<FluentStack Style="pointer-events: none;" VerticalAlignment="VerticalAlignment.Center">
<FluentAvatar Image="@context.Flag()"
Name="@context.Name"
Size="AvatarSize.Size20" />
<FluentText Margin="@Margin.Left4">
@context.Name
</FluentText>
</FluentStack>
</OptionTemplate>

@* Content displayed at the top of the drop-down list *@
<HeaderContent>
<FluentText Size="TextSize.Size200" Color="Color.Primary" Align="TextAlign.Center">
Suggested contacts
</FluentText>
<FluentProgressBar Thickness="ProgressThickness.Large" Visible="@context.InProgress" />
</HeaderContent>

@* Content displayed at the bottom of the drop-down list *@
<FooterContent>
@if (!context.Items.Any())
{
<FluentText Size="TextSize.Size200" Align="TextAlign.Center" Color="Color.Error" Style="width: 100%;">
No results found
</FluentText>
}
</FooterContent>

</FluentAutocomplete>

<FluentStack Orientation="Orientation.Vertical">
<FluentSwitch @bind-Value="@ShowProgressIndicator" Label="Show progress indicator" />
<FluentSwitch @bind-Value="@MaxAutoHeight" Label="Auto height" />
<FluentSwitch @bind-Value="@MaxSelectedWidth" Label="Set a max selected width (40px)" />
<FluentSwitch @bind-Value="@ShowDismiss" Label="Show search or dismiss button" />
<FluentSwitch @bind-Value="@Multiple" Label="Multiple selection" />
<FluentSwitch @bind-Value="@SetMaximumSelectedOptions" Label="Set MaximumSelectedOptions to 4" />
<FluentButton OnClick="@SetSelectedCountriesAsync">
Set SelectedCountries to [be, fr]
</FluentButton>
</FluentStack>


<FluentAutocomplete TOption="Country"
TValue="string"
Width="100%"
MaximumOptionsSearch="int.MaxValue"
Label="Select countries from Items"
Items="@Countries"
OptionText="@(item => item.Name)"
@bind-SelectedItems="@SelectedCountries" />

</div>

@code
{
bool ShowProgressIndicator { get; set; }
bool MaxAutoHeight { get; set; }
bool MaxSelectedWidth { get; set; }
bool ShowDismiss { get; set; } = true;
bool Multiple { get; set; } = true;
bool SetMaximumSelectedOptions { get; set; }
string? SearchText { get; set; }
IEnumerable<Country> SelectedCountries { get; set; } = [];

async Task OnSearchAsync(OptionsSearchEventArgs<Country> e)
{
if (ShowProgressIndicator)
{
await Task.Delay(500); // Simulate async search
}

e.Items = Countries.Where(i => i.Name.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase))
.OrderBy(i => i.Name);
}

async Task SetSelectedCountriesAsync()
{
SelectedCountries = Countries.Where(i => i.Code == "be" || i.Code == "fr");
await Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<div>Selected: <b>@string.Join("; ", Selected.Select(x => x.Name))</b></div>

<FluentAutocomplete TOption="MyUser"
TValue="int"
Width="100%"
Label="Name"
Placeholder="Select a user"
OptionText="(option) => option.Name"
OptionValue="(option) => option.UserId"
OptionSelectedComparer="MyComparer.Instance"
OnOptionsSearch="@OnSearchAsync"
@bind-SelectedItems="@Selected" />

@code
{
IEnumerable<MyUser> Selected { get; set; } = [];

/// Search method called when the user types in the input or opens the options list.
/// A new list of MyUser is assigned with new object instances.
/// The component will use the OptionSelectedComparer to check if any of the new items are already selected.
Task OnSearchAsync(OptionsSearchEventArgs<MyUser> e)
{
e.Items = [
new MyUser(1, "Marvin Klein"),
new MyUser(2, "Denis Voituron"),
];

return Task.CompletedTask;
}

record MyUser(int UserId, string Name);

class MyComparer : IEqualityComparer<MyUser>
{
public static readonly MyComparer Instance = new();

public bool Equals(MyUser? x, MyUser? y) => x?.UserId == y?.UserId;

public int GetHashCode(MyUser obj) => obj.UserId.GetHashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@using static FluentUI.Demo.SampleData.Olympics2024

<div>Selected: <b>@string.Join("; ", SelectedCountries.Select(c => c.Name))</b></div>
<div>Search Text: <b>@SearchText</b></div>

<FluentAutocomplete TOption="Country"
TValue="string"
Width="100%"
Label="Select countries"
Placeholder="Type to search..."
ShowProgressIndicator="@ShowProgressIndicator"
MaxAutoHeight="@((MaxAutoHeight) ? "unset" : null)"
OnOptionsSearch="@OnSearchAsync"
OptionText="@(item => item.Name)"
MaxSelectedWidth="@(MaxSelectedWidth ? "40px" : null)"
ShowDismiss="@ShowDismiss"
@bind-Value="@SearchText"
@bind-SelectedItems="@SelectedCountries">

@* Template used with each Option items *@
<OptionTemplate>
<FluentStack Style="pointer-events: none;" VerticalAlignment="VerticalAlignment.Center">
<FluentAvatar Image="@context.Flag()"
Name="@context.Name"
Size="AvatarSize.Size20" />
<FluentText Margin="@Margin.Left4">
@context.Name
</FluentText>
</FluentStack>
</OptionTemplate>


@* Content display at the top of the Popup area *@
<HeaderContent>
<FluentText Size="TextSize.Size200" Color="Color.Primary" Align="TextAlign.Center">
Suggested contacts
</FluentText>
<FluentProgressBar Thickness="ProgressThickness.Large" Visible="@context.InProgress" />
</HeaderContent>

@* Content display at the bottom of the Popup area *@
<FooterContent>
@if (!context.InProgress && !context.Items.Any())
{
<FluentText Size="TextSize.Size200"
Align="TextAlign.Center"
Color="Color.Error"
Style="width: 100%;">
No results found
</FluentText>
}
</FooterContent>

</FluentAutocomplete>

<FluentStack Orientation="Orientation.Vertical">
<FluentSwitch @bind-Value="@ShowProgressIndicator" Label="Show progress indicator" />
<FluentSwitch @bind-Value="@MaxAutoHeight" Label="Auto height" />
<FluentSwitch @bind-Value="@MaxSelectedWidth" Label="Set a max selected width (40px)" />
<FluentSwitch @bind-Value="@ShowDismiss" Label="Show search or dismiss button" />
</FluentStack>

@code
{
bool ShowProgressIndicator { get; set; }
bool MaxAutoHeight { get; set; }
bool MaxSelectedWidth { get; set; }
bool ShowDismiss { get; set; } = true;
string? SearchText { get; set; }
IEnumerable<Country> SelectedCountries { get; set; } = [];

async Task OnSearchAsync(OptionsSearchEventArgs<Country> e)
{
if (ShowProgressIndicator)
{
await Task.Delay(500); // Simulate async search
}

e.Items = Countries.Where(i => i.Name.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase))
.OrderBy(i => i.Name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@using static FluentUI.Demo.SampleData.Olympics2024

<div>Selected: <b>@string.Join("; ", SelectedCountries.Select(c => c.Name))</b></div>

<FluentAutocomplete TOption="Country"
TValue="string"
Width="100%"
Label="Select countries"
Placeholder="Type to search..."
OnOptionsSearch="@OnSearchAsync"
OptionText="@(item => item.Name)"
OptionDisabled="@(e => e.Code == "au")"
@bind-SelectedItems="@SelectedCountries" />
@code
{
IEnumerable<Country> SelectedCountries { get; set; } = [];

Task OnSearchAsync(OptionsSearchEventArgs<Country> e)
{
e.Items = Countries.Where(i => i.Name.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase))
.OrderBy(i => i.Name);

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@using static FluentUI.Demo.SampleData.Olympics2024

<div>Selected: <b>@SelectedCountry?.Name</b></div>

<FluentAutocomplete TOption="Country"
TValue="string"
Label="Select a country"
Multiple="false"
Placeholder="Type to search..."
OnOptionsSearch="@OnSearchAsync"
OptionText="@(item => item.Name)"
OptionDisabled="@(e => e.Code == "au")"
@bind-SelectedItems="@SelectedCountries" />
@code
{
IEnumerable<Country> SelectedCountries { get; set; } = [];

Country? SelectedCountry
{
get => SelectedCountries.FirstOrDefault() ?? default;
set => SelectedCountries = value is not null ? [value] : [];
}

Task OnSearchAsync(OptionsSearchEventArgs<Country> e)
{
e.Items = Countries.Where(i => i.Name.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase))
.OrderBy(i => i.Name);

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: Autocomplete
route: /Lists/Autocomplete
---

# Autocomplete

An **Autocomplete** component is a text input that provides real-time suggestions as the user types.
It combines a free-text input with a filtered list of options, allowing users to either select from the suggestions or type their own value.

This is particularly useful when the list of options is large, as the user can narrow down the list op options without needing to scroll through all available items.

By default, the `FluentAutocomplete` component compares search results by instance with its internal selected items.
You can control this behavior by providing the `OptionSelectedComparer` parameter.

> **Note:** Accessibility requirements are not yet implemented for this component.

## Keyboard interaction

| Key | Behavior |
|---|---|
| **Type text** | Filters the list of options and triggers the `OnSearchAsync` method to fetch matching results. |
| **Arrow Down / Arrow Up** | Opens the suggestion list and navigates through the items in the suggestion list. |
| **Enter** | Selects the currently highlighted item. |
| **Backspace** | Deletes the most recently selected item (in multi-select mode). |
| **Escape** | Closes the suggestion list without selecting an item. |

<br /><br />

## Default

A basic autocomplete that filters a list of countries as the user types.
Multiple items can be selected, and one option is disabled (`OptionDisabled`).

{{ AutocompleteDefault }}

## Single item (Multiple=false)

Set the `Multiple` parameter to `false` to restrict the selection to a single item.
In this mode, the selected value replaces the input text and no tags are displayed.

{{ AutocompleteMultipleFalse }}

## Customized options

Demonstrates advanced features: a custom `OptionTemplate` to render each option with a flag, a progress indicator during async search,
a configurable max dropdown height, and a max width for selected items.

{{ AutocompleteCustomized }}

## Different object instances from search result

When the `OnOptionsSearch` method returns **new object instances** on each call (e.g. from an API or database query),
the component cannot match them to already-selected items by **reference**.

Use the `OptionSelectedComparer` parameter to provide a custom `IEqualityComparer<TOption>` that compares items by a unique key (such as an ID)
instead of by reference. Without this, previously selected items may not appear as checked in the refreshed list.

{{ AutocompleteComparer }}

## API FluentAutocomplete

{{ API Type=FluentAutocomplete<string,string> }}

## Migrating to v5

{{ INCLUDE File=MigrationFluentAutocomplete }}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
TOption="string"
TValue="string"
@bind-SelectedItems="@SelectedItems"
Multiple="true" />
Multiple="false" />

<FluentButton OnClick="@(e => Colors = new[] { "Yellow", "Purple", "Cyan" })">
Yellow, Purple, Cyan
Expand Down
Loading
Loading