CloseWatcher
One event for every way a user asks to close something - the Escape key, the Android back gesture, and whatever else the platform offers - so a custom dialog behaves like a native one.
@inject Bit.Butil.CloseWatcher closeWatcherMDN reference
IsSupported is false, Create returns null - keep your own key handler as
the fallback.
True when the runtime exposes CloseWatcher. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.
@inject Bit.Butil.CloseWatcher closeWatcher
var supported = await closeWatcher.IsSupported();Open the panel below, then press Escape - or use the Android back gesture on a phone. The handler runs, the panel closes, and the gesture is consumed so nothing else reacts to it.
IAsyncDisposable
Bit.Butil.CloseWatcher closeWatcher
if (_open)
{
<div>...</div>
}
{
private bool _open;
private CloseWatcherHandle? _watcher;
// Created when the panel opens, so Escape, the Android back gesture and the browser's own close
// affordance all reach the same handler.
private async Task Open()
{
_open = true;
_watcher = await closeWatcher.Create(onClose: () =>
{
_open = false;
InvokeAsync(StateHasChanged);
});
}
// Closing by any other route - a button of your own - has to retire the watcher too, or the
// next Escape closes a panel that is already gone.
public async ValueTask DisposeAsync()
{
if (_watcher is not null) await _watcher.DisposeAsync();
}
}Passing an onCancel handler intercepts the close request first, so you can ask 'discard your changes?' and call Close() yourself if the answer is yes. The browser only offers the cancel step while there is a user activation to spend, so a close can still arrive without one - never rely on it firing.
Bit.Butil.CloseWatcher closeWatcher
{
private bool _open;
private bool _confirming;
private CloseWatcherHandle? _watcher;
private async Task Open()
{
_open = true;
// onCancel runs first and can stop the close; onClose is what actually closes. A cancel
// needs a prior user activation, so the very first close gesture cannot be intercepted.
_watcher = await closeWatcher.Create(
onClose: () => { _open = false; InvokeAsync(StateHasChanged); },
onCancel: () => { _confirming = true; InvokeAsync(StateHasChanged); });
}
// the user confirmed:
private async Task Confirm() => await _watcher!.Close();
}API reference
ValueTask<bool> IsSupported()ValueTask<CloseWatcherHandle?> Create(Action onClose, Action? onCancel = null)ValueTask RequestClose()ValueTask Close()ValueTask DisposeAsync()ValueTask DisposeAsync()