feat(accessibility): add native keyboard dialogs and semantic account links (U08)
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
@implements IAsyncDisposable
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<dialog @ref="element" class="dialog-content app-modal" aria-labelledby="@titleId">
|
||||
<ToastHost InDialog="true" />
|
||||
<h2 id="@titleId" class="visually-hidden">@Title</h2>
|
||||
<div inert="@discardPrompt">@ChildContent</div>
|
||||
@if (discardPrompt)
|
||||
{
|
||||
<section role="alertdialog" aria-label="Ungespeicherte Eingaben">
|
||||
<h3>Ungespeicherte Eingaben</h3>
|
||||
<p>Die Änderungen wurden noch nicht gespeichert. Möchtest du weiter bearbeiten oder die Eingaben verwerfen?</p>
|
||||
<div class="dialog-actions">
|
||||
<button type="button" class="btn btn-primary" data-discard-continue @onclick="ContinueEditing">Weiter bearbeiten</button>
|
||||
<button type="button" class="btn btn-outline-danger" @onclick="Discard">Eingaben verwerfen</button>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</dialog>
|
||||
|
||||
@code {
|
||||
[Parameter] public string Title { get; set; } = "Dialog";
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
[Parameter] public EventCallback OnCancel { get; set; }
|
||||
[Parameter] public bool Dirty { get; set; }
|
||||
[Parameter] public bool Busy { get; set; }
|
||||
private readonly string titleId = "dialog-" + Guid.NewGuid().ToString("N");
|
||||
private ElementReference element;
|
||||
private DotNetObjectReference<ModalDialog>? reference;
|
||||
private bool discardPrompt;
|
||||
private bool focusPrompt;
|
||||
private bool disposed;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
reference = DotNetObjectReference.Create(this);
|
||||
await JS.InvokeVoidAsync("duempelkasDialog.open", element, reference);
|
||||
}
|
||||
if (focusPrompt)
|
||||
{
|
||||
focusPrompt = false;
|
||||
await JS.InvokeVoidAsync("duempelkasDialog.focus", element, discardPrompt ? "[data-discard-continue]" : "[data-initial-focus],input,select,textarea,button");
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task RequestCloseAsync()
|
||||
{
|
||||
if (Busy || disposed) return;
|
||||
if (Dirty && !discardPrompt)
|
||||
{
|
||||
discardPrompt = true;
|
||||
focusPrompt = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else if (discardPrompt) await ContinueEditing();
|
||||
else await OnCancel.InvokeAsync();
|
||||
}
|
||||
|
||||
private Task ContinueEditing()
|
||||
{
|
||||
discardPrompt = false;
|
||||
focusPrompt = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
private Task Discard() => OnCancel.InvokeAsync();
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
disposed = true;
|
||||
try { await JS.InvokeVoidAsync("duempelkasDialog.close", element); }
|
||||
catch (Exception ex) when (ex is JSException or JSDisconnectedException or ObjectDisposedException or TaskCanceledException) { }
|
||||
reference?.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user