@* Reusable confirmation dialog using Bootstrap modal. z-index ladder: Toast container 1090 > this modal 1055 > this backdrop 1040. *@ @inject IJSRuntime JS @implements IAsyncDisposable @if (_visible) { } @code { private bool _visible; private bool _bodyLocked; private TaskCompletionSource? _tcs; private ElementReference _modalRef; [Parameter] public string Title { get; set; } = "Confirm"; [Parameter] public string Message { get; set; } = "Are you sure?"; [Parameter] public string ConfirmText { get; set; } = "Confirm"; [Parameter] public string ConfirmButtonClass { get; set; } = "btn-primary"; public Task ShowAsync(string? message = null, string? title = null) { if (message != null) Message = message; if (title != null) Title = title; _visible = true; _tcs = new TaskCompletionSource(); StateHasChanged(); return _tcs.Task; } protected override async Task OnAfterRenderAsync(bool firstRender) { if (_visible && !_bodyLocked) { _bodyLocked = true; await TryLockBodyAsync(); // Focus the modal so the @onkeydown handler receives Escape. try { await _modalRef.FocusAsync(); } catch { /* prerender or detached: ignore */ } } } private async Task OnKeyDownAsync(KeyboardEventArgs e) { if (e.Key == "Escape") { await CancelAsync(); } } private void Confirm() { Close(true); } private void Cancel() { Close(false); } private Task CancelAsync() { Close(false); return Task.CompletedTask; } private void Close(bool result) { _visible = false; _ = TryUnlockBodyAsync(); _tcs?.TrySetResult(result); } private async Task TryLockBodyAsync() { try { await JS.InvokeVoidAsync("document.body.classList.add", "modal-open"); } catch { // Prerendering has no JS runtime; log only. try { await JS.InvokeVoidAsync("console.debug", "ConfirmDialog: JS interop unavailable for body lock."); } catch { /* swallow */ } } } private async Task TryUnlockBodyAsync() { _bodyLocked = false; try { await JS.InvokeVoidAsync("document.body.classList.remove", "modal-open"); } catch { try { await JS.InvokeVoidAsync("console.debug", "ConfirmDialog: JS interop unavailable for body unlock."); } catch { /* swallow */ } } } public async ValueTask DisposeAsync() { if (_bodyLocked) { await TryUnlockBodyAsync(); } } }