VirtualKeyboard
Show and hide the on-screen keyboard, and know exactly where it is - so a chat composer stays pinned above it instead of jumping.
@inject Bit.Butil.VirtualKeyboard virtualKeyboardMDN reference
SetOverlaysContent(true) switches that off: the keyboard is drawn
over the page, the viewport keeps its size, and the app becomes responsible for keeping its
content out from under it - through OnGeometryChange here, or the
keyboard-inset-* CSS environment variables.
IsSupported is false and every call is a no-op. Show also
needs a user gesture and a focused element whose virtualkeyboardpolicy is
manual - the attribute half of this API, set through
ElementReferenceStateExtensions.SetVirtualKeyboardPolicy.
True when the runtime exposes navigator.virtualKeyboard. During prerender/SSR the check returns false rather than throwing.
@inject Bit.Butil.VirtualKeyboard virtualKeyboard
var supported = await virtualKeyboard.IsSupported();Set this once at start-up, before anything can be focused - flipping it while the keyboard is up produces a visible jump. Until it is on, GetBoundingRect reports zeros, because the browser is resizing the viewport instead and has nothing to report.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) await virtualKeyboard.SetOverlaysContent(true);
}With virtualkeyboardpolicy set to manual on a contenteditable element, focusing it no longer raises the keyboard - Show and Hide do. That is what lets a custom editor decide when the keyboard is wanted.
Bit.Butil.VirtualKeyboard virtualKeyboard
<div @ref="_editor" contenteditable="true">...</div>
{
private ElementReference _editor;
// Manual policy stops the browser showing and hiding the keyboard on focus, which is the whole
// point: a custom editor that scrolls its own content wants to decide when it appears.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) await _editor.SetVirtualKeyboardPolicy(VirtualKeyboardPolicy.Manual);
}
// From a click handler, with the editor focused - it is refused otherwise.
private async Task ShowKeyboard()
{
await _editor.Focus();
await virtualKeyboard.Show();
}
}Fires when the keyboard appears, disappears or resizes, plus once immediately with the current geometry. The Y coordinate is the line your content has to stay above. Dispose the returned subscription.
_sub = await virtualKeyboard.OnGeometryChange(geometry =>
{
_composerBottomPadding = geometry.Height;
InvokeAsync(StateHasChanged);
});API reference
ValueTask<bool> IsSupported()ValueTask Show()ValueTask Hide()ValueTask<bool> GetOverlaysContent()ValueTask SetOverlaysContent(bool value)ValueTask<VirtualKeyboardGeometry> GetBoundingRect()Task<ButilSubscription> OnGeometryChange(Action<VirtualKeyboardGeometry> handler)ValueTask DisposeAsync()