fix(view): follow the keyboard through the navbar hiding behind it

Same two misses as the calendar's: the four-frame watch stopped before
Obsidian hid its navbar - a second move of the ground, after the keyboard's
own - and a trigger that arrived while a watch was running was dropped rather
than restarting it. The watch now needs twelve steady frames, restarts on
every trigger, and listens to Obsidian's Capacitor keyboard events too.
This commit is contained in:
hh
2026-09-05 01:15:11 +02:00
parent 70cf985aa1
commit 223560c8da
+25 -6
View File
@@ -13,9 +13,18 @@ export const VIEW_TYPE_PANEL = "beaver-panel";
// sits under every view, including the one filling the screen. // sits under every view, including the one filling the screen.
const BOTTOM_BARS = [".status-bar", ".mobile-navbar", ".mobile-toolbar"]; const BOTTOM_BARS = [".status-bar", ".mobile-navbar", ".mobile-toolbar"];
// The keyboard and the navbar both animate. Rather than guess how long that // The keyboard and the navbar both animate, and Obsidian hides the navbar
// takes, the inset is re-measured every frame until the reading holds still. // *after* the keyboard is up: the inset is re-measured every frame until the
const STEADY_FRAMES = 4; // reading has held still long enough to outlast both moves.
const STEADY_FRAMES = 12;
/** Obsidian's Capacitor keyboard events on mobile; absent on the desktop. */
const KEYBOARD_EVENTS = [
"keyboardWillShow",
"keyboardDidShow",
"keyboardWillHide",
"keyboardDidHide",
] as const;
interface SavedState { interface SavedState {
follow?: unknown; follow?: unknown;
@@ -106,6 +115,14 @@ export class PanelView extends ItemView {
// out the navbar comes back, so the leaf has to be re-measured after. // out the navbar comes back, so the leaf has to be re-measured after.
this.registerDomEvent(this.contentEl, "focusin", () => this.settle()); this.registerDomEvent(this.contentEl, "focusin", () => this.settle());
this.registerDomEvent(this.contentEl, "focusout", () => this.settle()); this.registerDomEvent(this.contentEl, "focusout", () => this.settle());
// Obsidian's own keyboard events (Capacitor, mobile only) fire before the
// animation and again as the navbar returns, which visualViewport alone
// does not always report.
for (const name of KEYBOARD_EVENTS) {
const onKeyboard = () => this.settle();
window.addEventListener(name, onKeyboard);
this.register(() => window.removeEventListener(name, onKeyboard));
}
this.follow(this.app.workspace.getActiveFile()); this.follow(this.app.workspace.getActiveFile());
this.plugin.views.add(this); this.plugin.views.add(this);
this.mountApp(); this.mountApp();
@@ -121,11 +138,13 @@ export class PanelView extends ItemView {
* which is the case in a sidedock drawer. * which is the case in a sidedock drawer.
*/ */
private settle(): void { private settle(): void {
if (this.settling !== null) {
return;
}
let last = Number.NaN; let last = Number.NaN;
let steady = 0; let steady = 0;
// A fresh trigger restarts the watch rather than being dropped: the navbar
// coming back is a second move, after the first one has settled.
if (this.settling !== null) {
cancelAnimationFrame(this.settling);
}
const tick = () => { const tick = () => {
const floor = this.inset(); const floor = this.inset();
steady = floor === last ? steady + 1 : 0; steady = floor === last ? steady + 1 : 0;