Skip to content

Commit f76f763

Browse files
authored
Autocomplete: fix the focus of next element (#6477)
* tabindex * docs api * no js
1 parent b0a3784 commit f76f763

6 files changed

Lines changed: 89 additions & 5 deletions

File tree

Documentation/Blazorise.Docs/Resources/docs-api-index.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"generatedUtc": "2026-03-22T12:46:35.0517777Z",
2+
"generatedUtc": "2026-03-22T15:18:14.2066907Z",
33
"components": [
44
{
55
"type": "global::Blazorise.Abbreviation",
@@ -39617,6 +39617,14 @@
3961739617
"summary": "Custom inline styles to apply to the component.",
3961839618
"isBlazoriseEnum": false
3961939619
},
39620+
{
39621+
"name": "TabIndex",
39622+
"type": "int?",
39623+
"typeName": "int?",
39624+
"defaultValue": "null",
39625+
"summary": "Gets or sets the tabindex value for keyboard navigation.",
39626+
"isBlazoriseEnum": false
39627+
},
3962039628
{
3962139629
"name": "TextAlignment",
3962239630
"type": "global::Blazorise.TextAlignment",

Source/Blazorise/Components/Dropdown/DropdownItem.razor.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,18 @@ protected async Task CheckedChangedHandler( bool isChecked )
133133
/// <summary>
134134
/// Gets the tabindex value for keyboard navigation.
135135
/// </summary>
136-
protected int ComputedTabIndex => Disabled ? -1 : 0;
136+
protected int ComputedTabIndex => Disabled ? -1 : TabIndex ?? 0;
137137

138138
/// <summary>
139139
/// Holds the item value.
140140
/// </summary>
141141
[Parameter] public object Value { get; set; }
142142

143+
/// <summary>
144+
/// Gets or sets the tabindex value for keyboard navigation.
145+
/// </summary>
146+
[Parameter] public int? TabIndex { get; set; }
147+
143148
/// <summary>
144149
/// Indicate the currently active item.
145150
/// </summary>

Source/Extensions/Blazorise.Components/Autocomplete.razor

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@
7979
{
8080
var notFoundContext = GetNotFoundItemContext();
8181

82-
<DropdownItem Disabled CloseParentDropdowns="@CloseParentDropdowns" Class="@DropdownItemClassNames( notFoundContext )" Style="@DropdownItemStyleNames( notFoundContext )">
82+
<DropdownItem Disabled TabIndex="-1" CloseParentDropdowns="@CloseParentDropdowns" Class="@DropdownItemClassNames( notFoundContext )" Style="@DropdownItemStyleNames( notFoundContext )">
8383
@NotFoundTemplate( Search )
8484
</DropdownItem>
8585
}
8686
else if ( FreeTypingNotFoundVisible )
8787
{
8888
var notFoundContext = GetNotFoundItemContext();
8989

90-
<DropdownItem Disabled CloseParentDropdowns="@CloseParentDropdowns" Class="@DropdownItemClassNames( notFoundContext )" Style="@DropdownItemStyleNames( notFoundContext )">
90+
<DropdownItem Disabled TabIndex="-1" CloseParentDropdowns="@CloseParentDropdowns" Class="@DropdownItemClassNames( notFoundContext )" Style="@DropdownItemStyleNames( notFoundContext )">
9191
@FreeTypingNotFoundTemplate( Search )
9292
</DropdownItem>
9393
}
@@ -108,7 +108,7 @@
108108
var value = GetItemValue( item );
109109
var itemContext = CreateItemContext( item, value, text, itemIndex, isActiveItem, isFocusedItem, disabled );
110110

111-
<DropdownItem @key="@item" ElementId="@DropdownItemId( itemIndex )" Active="@isActiveItem" Checked=@isActiveItem ShowCheckbox=@isCheckbox Class="@DropdownItemClassNames( itemContext )" Style="@DropdownItemStyleNames( itemContext )" Value="@value"
111+
<DropdownItem @key="@item" ElementId="@DropdownItemId( itemIndex )" Active="@isActiveItem" Checked=@isActiveItem ShowCheckbox=@isCheckbox TabIndex="-1" Class="@DropdownItemClassNames( itemContext )" Style="@DropdownItemStyleNames( itemContext )" Value="@value"
112112
Disabled="@disabled"
113113
CloseParentDropdowns="@CloseParentDropdowns"
114114
@onmousedown="@(() => clickFromCheck = true)"

Source/Extensions/Blazorise.Components/Autocomplete.razor.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,35 @@ public async Task ScrollItemIntoView( int index )
530530
/// <returns>Returns awaitable task</returns>
531531
protected async Task OnTextKeyDownHandler( KeyboardEventArgs eventArgs )
532532
{
533+
bool isPlainTabNavigation = eventArgs.Code == "Tab" && !eventArgs.AltKey && !eventArgs.CtrlKey && !eventArgs.MetaKey;
534+
533535
await OnKeyDownHandler( eventArgs );
534536

537+
if ( isPlainTabNavigation )
538+
{
539+
if ( IsConfirmKey( eventArgs ) )
540+
{
541+
if ( IsMultiple && FreeTyping && !string.IsNullOrEmpty( Search ) && ActiveItemIndex < 0 )
542+
{
543+
await AddMultipleText( Search );
544+
545+
if ( CloseOnSelection )
546+
{
547+
await ResetCurrentSearch();
548+
}
549+
}
550+
else if ( SelectionMode != AutocompleteSelectionMode.Checkbox )
551+
{
552+
await SelectedOrResetOnCommit();
553+
}
554+
}
555+
556+
TextFocused = false;
557+
await Close();
558+
await SearchKeyDown.InvokeAsync( eventArgs );
559+
return;
560+
}
561+
535562
if ( eventArgs.Code == "Escape" )
536563
{
537564
await Close();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Autocomplete TItem="string"
2+
TValue="string"
3+
Data="@items"
4+
MinSearchLength="0"
5+
TextField="@(( item ) => item)"
6+
ValueField="@(( item ) => item)" />
7+
8+
<Button ElementId="nextButton" Outline>Next</Button>
9+
10+
@code {
11+
private readonly List<string> items = new()
12+
{
13+
"Alpha",
14+
"Beta",
15+
"Gamma"
16+
};
17+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Blazorise.E2E.Tests.Tests.Extensions.Autocomplete;
2+
3+
[Parallelizable( ParallelScope.Self )]
4+
[TestFixture]
5+
public class Autocomplete_5452_Tests : BlazorisePageTest
6+
{
7+
[SetUp]
8+
public async Task Init()
9+
{
10+
await SelectTestComponent<Autocomplete_5452Component>();
11+
}
12+
13+
[Test]
14+
public async Task TabbingOutWithOpenListFocusesNextElement()
15+
{
16+
var autocomplete = Page.Locator( ".b-is-autocomplete input[type=search]" );
17+
var dropdownMenu = Page.Locator( ".dropdown-menu.show" );
18+
var nextButton = Page.Locator( "#nextButton" );
19+
20+
await autocomplete.ClickAsync();
21+
await dropdownMenu.WaitForAsync();
22+
23+
await autocomplete.PressAsync( "Tab" );
24+
25+
await Expect( nextButton ).ToBeFocusedAsync();
26+
}
27+
}

0 commit comments

Comments
 (0)