diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index f9c6c70614..fd33e4bec6 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -9813,6 +9813,15 @@ Removes all queued toasts with toast intent Custom + + + + + + + + + Gets or sets the toolbar's orentation. See @@ -9823,6 +9832,11 @@ Gets or sets the content to be rendered inside the component. + + + Gets or sets a value indicating whether arrow key navigation within text input fields is enabled. Default is false. + + Gets or sets a reference to the list of registered services. diff --git a/src/Core/Components/Toolbar/FluentToolbar.razor.cs b/src/Core/Components/Toolbar/FluentToolbar.razor.cs index 618ac03e94..cb63ffdf1b 100644 --- a/src/Core/Components/Toolbar/FluentToolbar.razor.cs +++ b/src/Core/Components/Toolbar/FluentToolbar.razor.cs @@ -1,9 +1,24 @@ using Microsoft.AspNetCore.Components; +using Microsoft.FluentUI.AspNetCore.Components.Extensions; +using Microsoft.JSInterop; namespace Microsoft.FluentUI.AspNetCore.Components; -public partial class FluentToolbar : FluentComponentBase +public partial class FluentToolbar : FluentComponentBase, IAsyncDisposable { + private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Toolbar/FluentToolbar.razor.js"; + + /// + [Inject] + private LibraryConfiguration LibraryConfiguration { get; set; } = default!; + + /// + [Inject] + private IJSRuntime JSRuntime { get; set; } = default!; + + /// + private IJSObjectReference Module { get; set; } = default!; + /// /// Gets or sets the toolbar's orentation. See /// @@ -15,4 +30,37 @@ public partial class FluentToolbar : FluentComponentBase /// [Parameter] public RenderFragment? ChildContent { get; set; } + + /// + /// Gets or sets a value indicating whether arrow key navigation within text input fields is enabled. Default is false. + /// + [Parameter] + public bool? EnableArrowKeyTextNavigation { get; set; } = false; + + public FluentToolbar() + { + Id = Identifier.NewId(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + Module ??= await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); + + if (EnableArrowKeyTextNavigation ?? false) + { + await Module.InvokeVoidAsync("preventArrowKeyNavigation", Id); + } + } + } + + public async ValueTask DisposeAsync() + { + if (Module is not null && !string.IsNullOrEmpty(Id)) + { + await Module.InvokeVoidAsync("removePreventArrowKeyNavigation", Id); + await Module.DisposeAsync(); + } + } } diff --git a/src/Core/Components/Toolbar/FluentToolbar.razor.js b/src/Core/Components/Toolbar/FluentToolbar.razor.js new file mode 100644 index 0000000000..fc39c53e61 --- /dev/null +++ b/src/Core/Components/Toolbar/FluentToolbar.razor.js @@ -0,0 +1,39 @@ +// Prevent fluent-toolbar from overriding arrow key functionality in input fields +const handlers = new Map(); + +export function preventArrowKeyNavigation(id) { + const toolbar = document.querySelector("#" + id); + if (!toolbar) return; + + const arrowKeyListener = (event) => { + if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { + const activeElement = document.activeElement; + if (activeElement && (activeElement.tagName === 'FLUENT-SEARCH' + || activeElement.tagName === 'FLUENT-TEXT-FIELD' + || activeElement.tagName === 'FLUENT-NUMBER-FIELD' + || activeElement.tagName === 'FLUENT-TEXT-AREA')) { + + const textLength = activeElement.value?.length || 0; + if (textLength > 0) { + event.stopPropagation(); + } + } + } + }; + + toolbar.addEventListener('keydown', arrowKeyListener, true); + handlers.set(id, { toolbar, arrowKeyListener }); +} + +export function removePreventArrowKeyNavigation(id) { + const handler = handlers.get(id); + if (handler) { + const { toolbar, arrowKeyListener } = handler; + + if (toolbar) { + toolbar.removeEventListener('keydown', arrowKeyListener, true); + } + + handlers.delete(id); + } +} diff --git a/tests/Core/_ToDo/Toolbar/FluentToolbarTests.cs b/tests/Core/_ToDo/Toolbar/FluentToolbarTests.cs index c98852c4bd..983a45fbaf 100644 --- a/tests/Core/_ToDo/Toolbar/FluentToolbarTests.cs +++ b/tests/Core/_ToDo/Toolbar/FluentToolbarTests.cs @@ -1,9 +1,20 @@ using Bunit; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Toolbar; public class FluentToolbarTests : TestBase { + [Inject] + private LibraryConfiguration LibraryConfiguration { get; set; } = new LibraryConfiguration(); + + public FluentToolbarTests() + { + TestContext.JSInterop.Mode = JSRuntimeMode.Loose; + TestContext.Services.AddSingleton(LibraryConfiguration); + } + [Fact] public void FluentToolbar_Default() {