diff --git a/examples/Demo/Shared/Pages/List/Autocomplete/Examples/AutoCompleteMaxSingleItem.razor b/examples/Demo/Shared/Pages/List/Autocomplete/Examples/AutoCompleteMaxSingleItem.razor
new file mode 100644
index 0000000000..4db11c271a
--- /dev/null
+++ b/examples/Demo/Shared/Pages/List/Autocomplete/Examples/AutoCompleteMaxSingleItem.razor
@@ -0,0 +1,29 @@
+@inject DataSource Data
+
+
+
+
+ Selected : @(SelectedItem?.Name)
+
+
+@code
+{
+ Country? SelectedItem = null;
+
+ private async Task OnSearchAsync(OptionsSearchEventArgs e)
+ {
+ var allCountries = await Data.GetCountriesAsync();
+ e.Items = allCountries.Where(i => i.Name.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase))
+ .OrderBy(i => i.Name);
+ }
+}
diff --git a/src/Core/Components/List/FluentAutocomplete.razor b/src/Core/Components/List/FluentAutocomplete.razor
index e7311d457d..3e52105360 100644
--- a/src/Core/Components/List/FluentAutocomplete.razor
+++ b/src/Core/Components/List/FluentAutocomplete.razor
@@ -6,6 +6,7 @@
@@ -31,7 +32,7 @@
autofocus="@Autofocus"
Style="@ComponentWidth">
@* Selected Items *@
- @if (this.SelectedOptions?.Any() == true)
+ @if (this.SelectedOptions?.Any() == true || this.SelectedOption is not null)
{
@* Normal (single) line height *@
if (string.IsNullOrEmpty(MaxAutoHeight))
@@ -67,36 +68,35 @@
@RenderSelectedOptions
}
-
}
@if (!Disabled && !ReadOnly)
{
- if (this.SelectedOptions?.Any() == true || !string.IsNullOrEmpty(ValueText))
+ if (this.SelectedOptions?.Any() == true || !string.IsNullOrEmpty(ValueText) || this.SelectedOption is not null)
{
if (IconDismiss != null)
{
- { IsReachedMaxItems = false; IsMultiSelectOpened = false; })"
- OnClick="@OnClearAsync" />
+ { IsReachedMaxItems = false; IsMultiSelectOpened = false; })"
+ OnClick="@OnClearAsync" />
}
}
else
{
if (IconSearch != null)
{
- { IsReachedMaxItems = false; IsMultiSelectOpened = false; })"
- OnClick="@OnDropDownExpandedAsync" />
+ { IsReachedMaxItems = false; IsMultiSelectOpened = false; })"
+ OnClick="@OnDropDownExpandedAsync" />
}
}
}
@@ -107,10 +107,10 @@
{
@if (SelectValueOnTab)
{
-
+
}
@@ -122,33 +122,33 @@
Shadow="ElevationShadow.Flyout">
@if (HeaderContent != null)
{
- @HeaderContent(Items ?? Array.Empty())
+ @HeaderContent(Items ?? Array.Empty())
}
@if (FooterContent != null)
{
- @FooterContent(Items ?? Array.Empty())
+ @FooterContent(Items ?? Array.Empty())
}
}
@@ -174,15 +174,15 @@
{
var optionValue = GetOptionValue(context.Item);
+ @key="@context.Item"
+ Value="@optionValue"
+ Style="@OptionStyle"
+ Class="@OptionClass"
+ Selected="@GetOptionSelected(context.Item)"
+ Disabled="@(GetOptionDisabled(context.Item) ?? false)"
+ OnSelect="@OnSelectCallback(context.Item)"
+ aria-selected="@(GetOptionSelected(context.Item) || optionValue == context.SelectableItem ? "true" : "false")"
+ selectable="@(optionValue == context.SelectableItem)">
@if (OptionTemplate == null)
{
@GetOptionText(context.Item)
@@ -196,27 +196,41 @@
private RenderFragment RenderSelectedOptions => __builder =>
{
- if (SelectedOptions != null)
+ var selectedOptions = new List();
+ if (Multiple && (SelectedOptions?.Any() ?? false))
+ {
+ selectedOptions.AddRange(SelectedOptions);
+ }
+ if (!Multiple && SelectedOption is not null)
{
- foreach (var item in SelectedOptions)
+ selectedOptions.Add(SelectedOption);
+ }
+
+ if (selectedOptions.Any())
+ {
+ foreach (var item in selectedOptions)
{
if (SelectedOptionTemplate == null)
{
var text = @GetOptionText(item);
- if (ReadOnly || Disabled)
+ if (Multiple == false)
+ {
+ @text
+ }
+ else if (ReadOnly || Disabled)
{
+ aria-label="@GetOptionText(item)">
@text
}
else
{
+ OnDismissClick="@(e => RemoveSelectedItemAsync(item))"
+ DismissTitle="@(string.Format(AccessibilityRemoveItem, text))"
+ aria-label="@GetOptionText(item)">
@text
}
diff --git a/src/Core/Components/List/FluentAutocomplete.razor.cs b/src/Core/Components/List/FluentAutocomplete.razor.cs
index 8361510426..12f4cd03f6 100644
--- a/src/Core/Components/List/FluentAutocomplete.razor.cs
+++ b/src/Core/Components/List/FluentAutocomplete.razor.cs
@@ -76,28 +76,6 @@ public override string? Value
set => base.Value = ValueText;
}
- ///
- /// For , this property must be True.
- /// Set the property to 1 to select just one item.
- ///
- public override bool Multiple
- {
- get
- {
- return base.Multiple;
- }
-
- set
- {
- if (value == false)
- {
- throw new ArgumentException("For FluentAutocomplete, this property must be True. Set the MaximumSelectedOptions property to 1 to select just one item.");
- }
-
- base.Multiple = true;
- }
- }
-
///
/// Gets or sets the visual appearance. See
///
@@ -245,6 +223,8 @@ public override bool Multiple
.AddStyle("display", "none", when: (Items == null || !Items.Any()) && (HeaderContent != null || FooterContent != null))
.Build();
+ private bool GetSingleSelect() => Multiple == false && SelectedOption is not null;
+
///
private string ComponentWidth
{
@@ -552,6 +532,7 @@ protected async Task OnClearAsync()
{
RemoveAllSelectedItems();
ValueText = string.Empty;
+ SelectedOption = default;
await RaiseValueTextChangedAsync(ValueText);
await RaiseChangedEventsAsync();
diff --git a/src/Core/Components/List/FluentAutocomplete.razor.css b/src/Core/Components/List/FluentAutocomplete.razor.css
index 55b6c4c17f..e9d653b1f2 100644
--- a/src/Core/Components/List/FluentAutocomplete.razor.css
+++ b/src/Core/Components/List/FluentAutocomplete.razor.css
@@ -52,15 +52,26 @@
margin-bottom: 2px;
}
+.fluent-autocomplete-multiselect[single-select] ::deep fluent-text-field::part(control) {
+ display: none;
+}
+
+.fluent-autocomplete-multiselect[single-select] ::deep fluent-text-field::part(start) {
+ max-width: calc(100% - 40px);
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
@media (forced-colors: active) {
.fluent-autocomplete-multiselect div[role=listbox] {
border: calc(var(--stroke-width)* 1px) solid transparent;
}
- .fluent-autocomplete-multiselect div[role=listbox] ::deep fluent-option:not([disabled]):not([selected])[selectable] {
- forced-color-adjust: none;
- background: highlight;
- color: highlighttext;
- }
+ .fluent-autocomplete-multiselect div[role=listbox] ::deep fluent-option:not([disabled]):not([selected])[selectable] {
+ forced-color-adjust: none;
+ background: highlight;
+ color: highlighttext;
+ }
}
diff --git a/src/Core/Components/List/ListComponentBase.razor.cs b/src/Core/Components/List/ListComponentBase.razor.cs
index 9e5fb3d364..b0f780d486 100644
--- a/src/Core/Components/List/ListComponentBase.razor.cs
+++ b/src/Core/Components/List/ListComponentBase.razor.cs
@@ -490,7 +490,7 @@ protected virtual bool GetOptionSelected(TOption item)
{
if (item != null)
{
- return OptionValue.Invoke(item) ?? OptionText.Invoke(item) ?? item.ToString();
+ return OptionValue?.Invoke(item) ?? OptionText?.Invoke(item) ?? item?.ToString();
}
else
{
diff --git a/tests/Core/List/FluentAutocompleteTests.FluentAutocomplete_MultipleEqualsFalse.verified.razor.html b/tests/Core/List/FluentAutocompleteTests.FluentAutocomplete_MultipleEqualsFalse.verified.razor.html
new file mode 100644
index 0000000000..bf55c5f1da
--- /dev/null
+++ b/tests/Core/List/FluentAutocompleteTests.FluentAutocomplete_MultipleEqualsFalse.verified.razor.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ 1-Denis Voituron
+
+
+ Remove 1-Denis Voituron
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/List/FluentAutocompleteTests.razor b/tests/Core/List/FluentAutocompleteTests.razor
index 58c10a2c83..f1e63cd587 100644
--- a/tests/Core/List/FluentAutocompleteTests.razor
+++ b/tests/Core/List/FluentAutocompleteTests.razor
@@ -21,10 +21,10 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.Items, Customers.Get());
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.Items, Customers.Get());
+ });
// Assert
cut.Verify();
@@ -35,11 +35,11 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.Items, Customers.Get());
- parameters.Add(p => p.Width, string.Empty);
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.Items, Customers.Get());
+ parameters.Add(p => p.Width, string.Empty);
+ });
// Assert
Assert.Contains("width: 250px", cut.Markup);
@@ -51,10 +51,10 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.Items, Customers.Get());
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.Items, Customers.Get());
+ });
// Act
cut.Find("fluent-text-field").Click();
@@ -81,11 +81,11 @@
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.Items, Customers.Get());
- parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.Items, Customers.Get());
+ parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
+ });
// Act
var input = cut.Find("fluent-text-field");
@@ -109,7 +109,7 @@
SelectValueOnTab="true"
@bind-SelectedOptions="@SelectedItems"
OnOptionsSearch="@OnSearchAsync" />
- );
+ );
// Act: click to open -> Tab to select
var input = cut.Find("fluent-text-field");
@@ -122,52 +122,35 @@
Assert.Single(SelectedItems);
}
- [Fact]
- public void FluentAutocomplete_MultipleFalse_Exception()
- {
- // Arrange & Act
- var ex = Assert.Throws(() =>
- {
- var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.Multiple, false);
- });
- });
-
- // Assert
- Assert.Equal("For FluentAutocomplete, this property must be True. Set the MaximumSelectedOptions property to 1 to select just one item.", ex.InnerException?.Message);
- }
-
[Fact]
public void FluentAutocomplete_Templates()
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.OptionValue, context => context.Id.ToString());
-
- // Add a HeaderContent template
- parameters.Add(p => p.HeaderContent, context =>
- {
- return $"";
- });
-
- // Add an Item template
- parameters.Add(p => p.OptionTemplate, context =>
- {
- return $"{context?.Id} {context?.Name}
";
- });
-
- // Add a FooterContent template
- parameters.Add(p => p.FooterContent, context =>
- {
- return $"{context.Count()} items found ";
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.OptionValue, context => context.Id.ToString());
+
+ // Add a HeaderContent template
+ parameters.Add(p => p.HeaderContent, context =>
+ {
+ return $"";
+ });
+
+ // Add an Item template
+ parameters.Add(p => p.OptionTemplate, context =>
+ {
+ return $"{context?.Id} {context?.Name}
";
+ });
+
+ // Add a FooterContent template
+ parameters.Add(p => p.FooterContent, context =>
+ {
+ return $"{context.Count()} items found ";
+ });
- parameters.Add(p => p.Items, Customers.Get());
- });
+ parameters.Add(p => p.Items, Customers.Get());
+ });
// Act
cut.Find("fluent-text-field").Click();
@@ -181,13 +164,13 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.OptionValue, context => context.Id.ToString());
- parameters.Add(p => p.OptionText, context => context.Name);
- parameters.Add(p => p.Items, Customers.Get());
- parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.OptionValue, context => context.Id.ToString());
+ parameters.Add(p => p.OptionText, context => context.Name);
+ parameters.Add(p => p.Items, Customers.Get());
+ parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
+ });
// Act
cut.Find("fluent-text-field").Click();
@@ -201,19 +184,19 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.OptionValue, context => context.Id.ToString());
- parameters.Add(p => p.OptionText, context => context.Name);
- parameters.Add(p => p.Items, Customers.Get());
- parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
-
- // Add an Item template
- parameters.Add(p => p.OptionTemplate, context =>
- {
- return $"{context?.Id} {context?.Name}
";
- });
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.OptionValue, context => context.Id.ToString());
+ parameters.Add(p => p.OptionText, context => context.Name);
+ parameters.Add(p => p.Items, Customers.Get());
+ parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
+
+ // Add an Item template
+ parameters.Add(p => p.OptionTemplate, context =>
+ {
+ return $"{context?.Id} {context?.Name}
";
+ });
+ });
// Act
cut.Find("fluent-text-field").Click();
@@ -227,13 +210,13 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.OptionValue, context => context.Id.ToString());
- parameters.Add(p => p.OptionText, context => context.Name);
- parameters.Add(p => p.Items, Customers.Get());
- parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.OptionValue, context => context.Id.ToString());
+ parameters.Add(p => p.OptionText, context => context.Name);
+ parameters.Add(p => p.Items, Customers.Get());
+ parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
+ });
// Act (click on the Dismiss button)
// The first SelectedOption is removed
@@ -248,10 +231,10 @@
{
// Arrange & Act
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.ValueText, "Preselected value");
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.ValueText, "Preselected value");
+ });
// Assert
var textField = cut.Find("fluent-text-field");
@@ -274,10 +257,10 @@
// Arrange
var valueText = "Preselected value";
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Bind(p => p.ValueText, valueText, x => valueText = x);
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Bind(p => p.ValueText, valueText, x => valueText = x);
+ });
Assert.False(string.IsNullOrEmpty(valueText));
@@ -295,10 +278,10 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.ValueText, "Some text here");
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.ValueText, "Some text here");
+ });
// Act
cut.Find("svg").Click(); // Clear button
@@ -313,11 +296,11 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.ValueText, "Some text here");
- parameters.Add(p => p.ShowOverlayOnEmptyResults, false);
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.ValueText, "Some text here");
+ parameters.Add(p => p.ShowOverlayOnEmptyResults, false);
+ });
// Act
cut.Find("svg").Click(); // Clear button
@@ -332,10 +315,10 @@
{
// Arrange && Act
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.Autofocus, true);
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.Autofocus, true);
+ });
// Assert
cut.Verify();
@@ -346,11 +329,11 @@
{
// Arrange
var cut = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Id, "myComponent");
- parameters.Add(p => p.MaxAutoHeight, "200px");
- parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
- });
+ {
+ parameters.Add(p => p.Id, "myComponent");
+ parameters.Add(p => p.MaxAutoHeight, "200px");
+ parameters.Add(p => p.SelectedOptions, Customers.Get().Take(2));
+ });
// Act
cut.Find("fluent-text-field").Click();
@@ -459,6 +442,29 @@
cut.Verify();
}
+ [Fact]
+ public async Task FluentAutocomplete_MultipleEqualsFalse()
+ {
+ Customer? SelectedItem = Customers.Get().First();
+
+ // Arrange
+ var cut = Render>(
+ @
+ );
+
+ // Assert (One item selected)
+ Assert.NotNull(SelectedItem);
+ Assert.Equal(1, SelectedItem.Id);
+
+ // Assert
+ cut.Verify();
+ }
+
// Send a key code
private async Task PressKeyAsync(IRenderedComponent> cut, KeyCode key, bool popoverOpened = false)
{
@@ -475,8 +481,4 @@
.OrderBy(i => i.Name);
return Task.CompletedTask;
}
-
-
-
-
}