From cd428a26a548c07b72771c1670264c02ee973df8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 17 Nov 2025 08:22:14 +0800 Subject: [PATCH 1/2] Fix dialog provider error when module hasn't finished loading --- .../Dialog/FluentDialogProvider.razor.cs | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/Core/Components/Dialog/FluentDialogProvider.razor.cs b/src/Core/Components/Dialog/FluentDialogProvider.razor.cs index 6718d4813f..c75554536d 100644 --- a/src/Core/Components/Dialog/FluentDialogProvider.razor.cs +++ b/src/Core/Components/Dialog/FluentDialogProvider.razor.cs @@ -2,6 +2,7 @@ // This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ +using System.Diagnostics; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; using Microsoft.FluentUI.AspNetCore.Components.Extensions; @@ -15,6 +16,8 @@ public partial class FluentDialogProvider : IAsyncDisposable private readonly InternalDialogContext _internalDialogContext; private readonly RenderFragment _renderDialogs; + // Don't set RunContinuationsAsynchronously to true because we want to stay in the same sync context. + private readonly TaskCompletionSource _moduleLoadedTcs = new(TaskCreationOptions.None); private IJSObjectReference? _module; [Inject] @@ -55,20 +58,44 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - _module ??= await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); + if (_module is null) + { + try + { + _module = await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); + _moduleLoadedTcs.TrySetResult(); + } + catch (Exception ex) + { + _moduleLoadedTcs.TrySetException(ex); + throw; + } + } } } - private void ShowDialog(IDialogReference dialogReference, Type? dialogComponent, DialogParameters parameters, object content) + /// + /// Wait for the module to be loaded and return it. + /// + private async Task GetLoadedModuleAsync() { if (_module is null) { - throw new InvalidOperationException("JS module is not loaded."); + await _moduleLoadedTcs.Task; } + // TCS either succeeds and the module is assigned, or it throws and we never get here. + Debug.Assert(_module is not null); + return _module; + } + + private void ShowDialog(IDialogReference dialogReference, Type? dialogComponent, DialogParameters parameters, object content) + { InvokeAsync(async () => { - var previouslyFocusedElement = await _module.InvokeAsync("getActiveElement"); + var module = await GetLoadedModuleAsync(); + + var previouslyFocusedElement = await module.InvokeAsync("getActiveElement"); DialogInstance dialog = new(dialogComponent, parameters, content, previouslyFocusedElement); dialogReference.Instance = dialog; @@ -78,14 +105,11 @@ private void ShowDialog(IDialogReference dialogReference, Type? dialogComponent, private async Task ShowDialogAsync(IDialogReference dialogReference, Type? dialogComponent, DialogParameters parameters, object content) { - if (_module is null) - { - throw new InvalidOperationException("JS module is not loaded."); - } - return await Task.Run(async () => { - var previouslyFocusedElement = await _module.InvokeAsync("getActiveElement"); + var module = await GetLoadedModuleAsync(); + + var previouslyFocusedElement = await module.InvokeAsync("getActiveElement"); DialogInstance dialog = new(dialogComponent, parameters, content, previouslyFocusedElement); dialogReference.Instance = dialog; @@ -166,12 +190,9 @@ internal void DismissInstance(string id, DialogResult result) internal async Task ReturnFocusAsync(IJSObjectReference element) { - if (_module is null) - { - throw new InvalidOperationException("JS module is not loaded."); - } + var module = await GetLoadedModuleAsync(); - await _module.InvokeVoidAsync("focusElement", element); + await module.InvokeVoidAsync("focusElement", element); await element.DisposeAsync(); } From 04c50bac4dbfab5b261520afde9b4ef15c0ba0b6 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 17 Nov 2025 08:49:53 +0800 Subject: [PATCH 2/2] Refactor --- .../Dialog/FluentDialogProvider.razor.cs | 58 +++++++------------ 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/src/Core/Components/Dialog/FluentDialogProvider.razor.cs b/src/Core/Components/Dialog/FluentDialogProvider.razor.cs index c75554536d..513cc39cc8 100644 --- a/src/Core/Components/Dialog/FluentDialogProvider.razor.cs +++ b/src/Core/Components/Dialog/FluentDialogProvider.razor.cs @@ -2,7 +2,6 @@ // This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ -using System.Diagnostics; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; using Microsoft.FluentUI.AspNetCore.Components.Extensions; @@ -16,8 +15,6 @@ public partial class FluentDialogProvider : IAsyncDisposable private readonly InternalDialogContext _internalDialogContext; private readonly RenderFragment _renderDialogs; - // Don't set RunContinuationsAsynchronously to true because we want to stay in the same sync context. - private readonly TaskCompletionSource _moduleLoadedTcs = new(TaskCreationOptions.None); private IJSObjectReference? _module; [Inject] @@ -58,44 +55,16 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - if (_module is null) - { - try - { - _module = await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); - _moduleLoadedTcs.TrySetResult(); - } - catch (Exception ex) - { - _moduleLoadedTcs.TrySetException(ex); - throw; - } - } + _module ??= await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); } } - /// - /// Wait for the module to be loaded and return it. - /// - private async Task GetLoadedModuleAsync() - { - if (_module is null) - { - await _moduleLoadedTcs.Task; - } - - // TCS either succeeds and the module is assigned, or it throws and we never get here. - Debug.Assert(_module is not null); - return _module; - } - private void ShowDialog(IDialogReference dialogReference, Type? dialogComponent, DialogParameters parameters, object content) { InvokeAsync(async () => { - var module = await GetLoadedModuleAsync(); + var previouslyFocusedElement = await GetPreviouslyFocusedElementAsync(); - var previouslyFocusedElement = await module.InvokeAsync("getActiveElement"); DialogInstance dialog = new(dialogComponent, parameters, content, previouslyFocusedElement); dialogReference.Instance = dialog; @@ -103,13 +72,23 @@ private void ShowDialog(IDialogReference dialogReference, Type? dialogComponent, }); } + private async Task GetPreviouslyFocusedElementAsync() + { + // If the module hasn't been loaded then the page hasn't rendered yet, so there is no previously focused element. + IJSObjectReference? previouslyFocusedElement = null; + if (_module is not null) + { + previouslyFocusedElement = await _module.InvokeAsync("getActiveElement"); + } + + return previouslyFocusedElement; + } + private async Task ShowDialogAsync(IDialogReference dialogReference, Type? dialogComponent, DialogParameters parameters, object content) { return await Task.Run(async () => { - var module = await GetLoadedModuleAsync(); - - var previouslyFocusedElement = await module.InvokeAsync("getActiveElement"); + var previouslyFocusedElement = await GetPreviouslyFocusedElementAsync(); DialogInstance dialog = new(dialogComponent, parameters, content, previouslyFocusedElement); dialogReference.Instance = dialog; @@ -190,9 +169,12 @@ internal void DismissInstance(string id, DialogResult result) internal async Task ReturnFocusAsync(IJSObjectReference element) { - var module = await GetLoadedModuleAsync(); + // Module should always be loaded here, but check just in case. + if (_module is not null) + { + await _module.InvokeVoidAsync("focusElement", element); + } - await module.InvokeVoidAsync("focusElement", element); await element.DisposeAsync(); }