Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 8 additions & 6 deletions examples/Demo/Shared/Shared/DemoNavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
<TitleTemplate><h3>Form & Inputs</h3></TitleTemplate>
<ChildContent>
<FluentNavLink Href="/Forms" Icon="@(new Icons.Regular.Size20.Form())">Overview</FluentNavLink>
<FluentNavLink Href="/Autocomplete" Icon="@(new Icons.Regular.Size20.ArrowAutofitContent())">Autocomplete</FluentNavLink>
<FluentNavLink Href="/Checkbox" Icon="@(new Icons.Regular.Size20.CheckboxChecked())">Checkbox</FluentNavLink>
<FluentNavLink Href="/InputFile" Icon="@(new Icons.Regular.Size20.ArrowUpload())">InputFile</FluentNavLink>
<FluentNavLink Href="/NumberField" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())">Number Field</FluentNavLink>
Expand All @@ -67,15 +66,14 @@
<FluentNavLink Href="/Accordion" Icon="@(new Icons.Regular.Size20.TextCollapse())">Accordion</FluentNavLink>
<FluentNavLink Href="/Anchor" Icon="@(new Icons.Regular.Size20.Link())">Anchor</FluentNavLink>
<FluentNavLink Href="/AnchoredRegion" Icon="@(new Icons.Regular.Size20.LinkSquare())">Anchored Region</FluentNavLink>
<FluentNavGroup Expanded="true" Title="Badge" Gap="10px">
<FluentNavGroup Expanded="true" Title="Badge" Gap="10px" Icon="@(new Icons.Regular.Size20.Tag())">
<FluentNavLink Href="/Badge" Icon="@(new Icons.Regular.Size20.Badge())">Badge</FluentNavLink>
<FluentNavLink Href="/CounterBadge" Icon="@(new Icons.Regular.Size20.NumberCircle1())">CounterBadge</FluentNavLink>
<FluentNavLink Href="/PresenceBadge" Icon="@(new Icons.Regular.Size20.PresenceAvailable())">PresenceBadge</FluentNavLink>
</FluentNavGroup>
<FluentNavLink Href="/Breadcrumb" Icon="@(new Icons.Regular.Size20.DocumentChevronDouble())">Breadcrumb</FluentNavLink>
<FluentNavLink Href="/Button" Icon="@(new Icons.Regular.Size20.ControlButton())">Button</FluentNavLink>
<FluentNavLink Href="/Card" Icon="@(new Icons.Regular.Size20.ContactCardGroup())">Card</FluentNavLink>
<FluentNavLink Href="/Combobox" Icon="@(new Icons.Regular.Size20.BoxEdit())">Combobox</FluentNavLink>
<FluentNavLink Href="/DataGrid" Icon="@(new Icons.Regular.Size20.Grid())">Data grid</FluentNavLink>
<FluentNavLink Href="/DateTime" Icon="@(new Icons.Regular.Size20.CalendarLtr())">Date & Time</FluentNavLink>
<FluentNavLink Href="/Dialog" Icon="@(new Icons.Regular.Size20.AppGeneric())">Dialog</FluentNavLink>
Expand All @@ -87,22 +85,26 @@
<FluentNavLink Href="/HorizontalScroll" Icon="@(new Icons.Regular.Size20.ArrowForward())">Horizontal Scroll</FluentNavLink>
<FluentNavLink Href="/Icon" Icon="@(new Icons.Regular.Size20.Symbols())">Icon</FluentNavLink>
<FluentNavLink Href="/Label" Icon="@(new Icons.Regular.Size20.DoorTag())">Label</FluentNavLink>
<FluentNavLink Href="/Listbox" Icon="@(new Icons.Regular.Size20.List())">Listbox</FluentNavLink>
<FluentNavGroup Expanded="true" Title="List" Gap="10px" Icon="@(new Icons.Regular.Size20.List())">
<FluentNavLink Href="/Autocomplete" Icon="@(new Icons.Regular.Size20.ArrowAutofitContent())">Autocomplete</FluentNavLink>
<FluentNavLink Href="/Combobox" Icon="@(new Icons.Regular.Size20.BoxEdit())">Combobox</FluentNavLink>
<FluentNavLink Href="/Listbox" Icon="@(new Icons.Regular.Size20.List())">Listbox</FluentNavLink>
<FluentNavLink Href="/Select" Icon="@(new Icons.Regular.Size20.GroupList())">Select</FluentNavLink>
<FluentNavLink Href="/Option" Icon="@(new Icons.Regular.Size20.MultiselectRtl())">Option</FluentNavLink>
</FluentNavGroup>
<FluentNavLink Href="/Menu" Icon="@(new Icons.Regular.Size20.Navigation())">Menu</FluentNavLink>
<FluentNavLink Href="/MenuButton" Icon="@(new Icons.Regular.Size20.ChevronCircleDown())">MenuButton</FluentNavLink>
<FluentNavLink Href="/MessageBar" Icon="@(new Icons.Regular.Size20.WindowHeaderHorizontal())">MessageBar</FluentNavLink>
<FluentNavLink Href="/MessageBox" Icon="@(new Icons.Regular.Size20.MegaphoneLoud())">MessageBox</FluentNavLink>
<FluentNavLink Href="/NavMenu" Icon="@(new Icons.Regular.Size20.Navigation())">NavMenu</FluentNavLink>
<FluentNavLink Href="/NavMenuTree" Icon="@(new Icons.Regular.Size20.Navigation())">NavMenuTree</FluentNavLink>
<FluentNavLink Href="/Option" Icon="@(new Icons.Regular.Size20.MultiselectRtl())">Option</FluentNavLink>
<FluentNavLink Href="/Overflow" Icon="@(new Icons.Regular.Size20.MultiselectRtl())">Overflow</FluentNavLink>
<FluentNavLink Href="/Overlay" Icon="@(new Icons.Regular.Size20.CursorHover())">Overlay</FluentNavLink>
<FluentNavLink Href="/Panel" Icon="@(new Icons.Regular.Size20.PanelRight())">Panel</FluentNavLink>
<FluentNavLink Href="/Persona" Icon="@(new Icons.Regular.Size20.PersonAvailable())">Persona</FluentNavLink>
<FluentNavLink Href="/Popover" Icon="@(new Icons.Regular.Size20.TooltipQuote())">Popover</FluentNavLink>
<FluentNavLink Href="/Progress" Icon="@(new Icons.Regular.Size20.SquareHint())">Progress</FluentNavLink>
<FluentNavLink Href="/ProgressRing" Icon="@(new Icons.Regular.Size20.ArrowClockwiseDashes())">Progress Ring</FluentNavLink>
<FluentNavLink Href="/Select" Icon="@(new Icons.Regular.Size20.GroupList())">Select</FluentNavLink>
<FluentNavLink Href="/Skeleton" Icon="@(new Icons.Regular.Size20.Shortpick())">Skeleton</FluentNavLink>
<FluentNavLink Href="/SplashScreen" Icon="@(new Icons.Regular.Size20.Drop())">SplashScreen</FluentNavLink>
<FluentNavLink Href="/Tabs" Icon="@(new Icons.Regular.Size20.TabDesktop())">Tabs</FluentNavLink>
Expand Down
1 change: 1 addition & 0 deletions src/Core/Components/List/FluentListbox.razor
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
required="@Required"
selectedOptions="@(SelectedOptions != null && SelectedOptions.Any() ? string.Join(',', SelectedOptions.Select(i => GetOptionValue(i))) : null)"
@onchange="@OnChangedHandlerAsync"
@onkeydown="@OnKeydownHandlerAsync"
@attributes="AdditionalAttributes">
@if (OptionTemplate == null || Items == null)
{
Expand Down
10 changes: 5 additions & 5 deletions src/Core/Components/List/InternalListContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,32 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
internal class InternalListContext<TOption>(ListComponentBase<TOption> listComponent) where TOption : notnull
{

private readonly List<FluentOption<TOption>> options = [];
private readonly ICollection<FluentOption<TOption>> options = [];

public ListComponentBase<TOption> ListComponent { get; } = listComponent;

/// <summary>
/// Gets the list of all select items inside of this select component.
/// </summary>
public IEnumerable<FluentOption<TOption>> Options => options;
public ICollection<FluentOption<TOption>> Options => options;

internal void Register(FluentOption<TOption> option)
{
if (option is null)
return;

if (!options.Contains(option))
{
options.Add(option);

}
}

internal void Unregister(FluentOption<TOption> option)
{
if (option is null)
return;

if (options.Contains(option))
options.Remove(option);
options.Remove(option);
}


Expand Down
71 changes: 71 additions & 0 deletions src/Core/Components/List/ListComponentBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.FluentUI.AspNetCore.Components.Utilities;

namespace Microsoft.FluentUI.AspNetCore.Components;
Expand Down Expand Up @@ -571,6 +572,47 @@ protected virtual async Task OnChangedHandlerAsync(ChangeEventArgs e)
}
}

protected virtual async Task OnKeydownHandlerAsync(KeyboardEventArgs e)
{
if (e is null || Multiple || Items is null)
{
return;
}


TOption? item = default;

// Get the previous and next options based on selected option
// Element at 0: Previous, 1: Current, 2: Next
List<TOption?>? sandwichedItems = FindPrevAndNextOptions().ToList();

switch (e.Key)
{
case "Home":
item = Items.FirstOrDefault();
break;
case "ArrowDown":
case "NumpadArrowDown":
item = sandwichedItems[2];
break;
case "ArrowUp":
case "NumpadArrowUp":
item = sandwichedItems[0];
break;
case "End":
item = Items.LastOrDefault();
break;
case "Enter":
case "Escape":
default:
break;
}

await OnSelectedItemChangedHandlerAsync(item);


}

/// <summary />
protected virtual bool RemoveSelectedItem(TOption? item)
{
Expand Down Expand Up @@ -609,4 +651,33 @@ protected EventCallback<string> OnSelectCallback(TOption? item)
return string.IsNullOrEmpty(AriaLabel) ? Title : AriaLabel;
#pragma warning restore CS0618 // Type or member is obsolete
}

private IEnumerable<TOption?> FindPrevAndNextOptions()
{
if (Items is null)
yield break;

using (IEnumerator<TOption> iter = Items.GetEnumerator())
{
TOption? previous = default;
while (iter.MoveNext())
{
if (iter.Current.Equals(SelectedOption))
{
yield return previous;
yield return iter.Current;
if (iter.MoveNext())
yield return iter.Current;
else
yield return default;
yield break;
}
previous = iter.Current;
}
}
// If we get here nothing has been found so return three default values
yield return default; // Previous
yield return default; // Current
yield return default; // Next
}
}