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
8 changes: 8 additions & 0 deletions src/Core/Components/Dialog/FluentDialog.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,14 @@ public async Task CloseAsync(DialogResult dialogResult)
{
await Instance.Parameters.OnDialogResult.InvokeAsync(dialogResult);
}

if (DialogContext is not null && Instance.PreviouslyFocusedElement is not null)
{
// Dialog does not close instantly, wait a little while to ensure that it has closed
// before trying to set focus. If dialog is not closed, focus cannot be set.
await Task.Delay(50);
await DialogContext.DialogContainer.ReturnFocusAsync(Instance.PreviouslyFocusedElement);
}
}
else
{
Expand Down
75 changes: 67 additions & 8 deletions src/Core/Components/Dialog/FluentDialogProvider.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,31 @@

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.FluentUI.AspNetCore.Components.Extensions;
using Microsoft.JSInterop;

namespace Microsoft.FluentUI.AspNetCore.Components;

public partial class FluentDialogProvider : IDisposable
public partial class FluentDialogProvider : IAsyncDisposable
{
private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Dialog/FluentDialogProvider.razor.js";

private readonly InternalDialogContext _internalDialogContext;
private readonly RenderFragment _renderDialogs;
private IJSObjectReference? _module;

[Inject]
private IDialogService DialogService { get; set; } = default!;

[Inject]
private NavigationManager NavigationManager { get; set; } = default!;

[Inject]
private IJSRuntime JSRuntime { get; set; } = default!;

[Inject]
private LibraryConfiguration LibraryConfiguration { get; set; } = default!;

/// <summary>
/// Constructs an instance of <see cref="FluentToastProvider"/>.
/// </summary>
Expand All @@ -40,24 +51,47 @@
DialogService.OnDialogCloseRequested += DismissInstance;
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_module ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration));
}
}

private void ShowDialog(IDialogReference dialogReference, Type? dialogComponent, DialogParameters parameters, object content)
{
DialogInstance dialog = new(dialogComponent, parameters, content);
dialogReference.Instance = dialog;
if (_module is null)
{
throw new InvalidOperationException("JS module is not loaded.");
}

_internalDialogContext.References.Add(dialogReference);
InvokeAsync(StateHasChanged);
InvokeAsync(async () =>
{
var previouslyFocusedElement = await _module.InvokeAsync<IJSObjectReference>("getActiveElement");
DialogInstance dialog = new(dialogComponent, parameters, content, previouslyFocusedElement);
dialogReference.Instance = dialog;

_internalDialogContext.References.Add(dialogReference);
});
}

private async Task<IDialogReference> ShowDialogAsync(IDialogReference dialogReference, Type? dialogComponent, DialogParameters parameters, object content)
{
return await Task.Run(() =>
if (_module is null)
{
DialogInstance dialog = new(dialogComponent, parameters, content);
throw new InvalidOperationException("JS module is not loaded.");
}

return await Task.Run(async () =>
{
var previouslyFocusedElement = await _module.InvokeAsync<IJSObjectReference>("getActiveElement");

DialogInstance dialog = new(dialogComponent, parameters, content, previouslyFocusedElement);
dialogReference.Instance = dialog;

_internalDialogContext.References.Add(dialogReference);
InvokeAsync(StateHasChanged);

Check warning on line 94 in src/Core/Components/Dialog/FluentDialogProvider.razor.cs

View workflow job for this annotation

GitHub Actions / Build and Test Core Lib

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 94 in src/Core/Components/Dialog/FluentDialogProvider.razor.cs

View workflow job for this annotation

GitHub Actions / Build and Test Core Lib

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 94 in src/Core/Components/Dialog/FluentDialogProvider.razor.cs

View workflow job for this annotation

GitHub Actions / Build and Deploy Demo site

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 94 in src/Core/Components/Dialog/FluentDialogProvider.razor.cs

View workflow job for this annotation

GitHub Actions / Build and Deploy Demo site

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 94 in src/Core/Components/Dialog/FluentDialogProvider.razor.cs

View workflow job for this annotation

GitHub Actions / Build and Test Core Lib

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 94 in src/Core/Components/Dialog/FluentDialogProvider.razor.cs

View workflow job for this annotation

GitHub Actions / Build and Test Core Lib

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

return dialogReference;
});
Expand Down Expand Up @@ -130,6 +164,17 @@
}
}

internal async Task ReturnFocusAsync(IJSObjectReference element)
{
if (_module is null)
{
throw new InvalidOperationException("JS module is not loaded.");
}

await _module.InvokeVoidAsync("focusElement", element);
await element.DisposeAsync();
}

internal IDialogReference? GetDialogReference(string id)
{
return _internalDialogContext.References.SingleOrDefault(x => x.Id == id);
Expand Down Expand Up @@ -183,11 +228,25 @@
}
}

public void Dispose()
public async ValueTask DisposeAsync()
{
if (NavigationManager != null)
{
NavigationManager.LocationChanged -= LocationChanged;
}

try
{
if (_module is not null)
{
await _module.DisposeAsync();
}
}
catch (Exception ex) when (ex is JSDisconnectedException ||
ex is OperationCanceledException)
{
// The JSRuntime side may routinely be gone already if the reason we're disposing is that
// the client disconnected. This is not an error.
}
}
}
9 changes: 9 additions & 0 deletions src/Core/Components/Dialog/FluentDialogProvider.razor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function getActiveElement() {
return document.activeElement
}

export function focusElement(element) {
if (!!element) {
element.focus();
}
}
7 changes: 6 additions & 1 deletion src/Core/Components/Dialog/Services/DialogInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
// This file is licensed to you under the MIT License.
// ------------------------------------------------------------------------

using Microsoft.JSInterop;

namespace Microsoft.FluentUI.AspNetCore.Components;

public sealed class DialogInstance
{
public DialogInstance(Type? type, DialogParameters parameters, object content)
public DialogInstance(Type? type, DialogParameters parameters, object content, IJSObjectReference? previouslyFocusedElement)
{
ContentType = type;
Parameters = parameters;
Content = content;
Id = Parameters.Id ?? Identifier.NewId();
PreviouslyFocusedElement = previouslyFocusedElement;
}

public string Id { get; }
Expand All @@ -22,6 +25,8 @@ public DialogInstance(Type? type, DialogParameters parameters, object content)

public DialogParameters Parameters { get; internal set; }

internal IJSObjectReference? PreviouslyFocusedElement { get; }

internal Dictionary<string, object>? GetParameterDictionary()
{
if (Content is null)
Expand Down
3 changes: 3 additions & 0 deletions tests/Core/Dialog/FluentDialogServiceTests.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

public FluentDialogServiceTests()
{
JSInterop.Mode = JSRuntimeMode.Loose;
Services.AddSingleton(LibraryConfiguration);
var dialogUtilsModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Dialog/FluentDialogProvider.razor.js");
dialogUtilsModule.SetupModule("getActiveElement", _ => true);
}

[Fact]
Expand Down
Loading