fix(view,place): the boards view pads clear of the phone's navbar

The panel had an inset for this; the calendar never did. Its root filled the
leaf, and Obsidian's phone navbar floats over a leaf rather than shortening
it, so the scroll container ran to the bottom of the screen and the last row
of the list sat under the bar - unreachable.

`lib/inset.ts` is the same pure measurement the panel plugin grew, with the
same tests: the deeper of navbar and keyboard, never their sum, and a bar that
sits beside the view covers nothing of it. The view re-measures by frame until
it holds still and writes `--bcal-inset-bottom`.

`viewport()` subtracts the bar too, so anchored panels and the quick look are
placed against what is actually reachable instead of ending under it.
This commit is contained in:
hh committed 2026-09-05 01:05:14 +02:00
1 parent f7ccd21d7b
commit f980abcc1d
6 files changed
+267 -3

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
import { describe, expect, it } from "vitest";
import { BOTTOM_BARS, insetFor } from "../src/lib/inset";
import type { Rect } from "../src/lib/inset";
const rect = (over: Partial<Rect> = {}): Rect => ({
top: 0,
left: 0,
right: 390,
bottom: 780,
height: 780,
...over,
});
// A phone: 780 tall, a 56px navbar floating over the bottom, 340px of keyboard.
const NAVBAR = rect({ top: 724, bottom: 780, height: 56 });
describe("insetFor", () => {
it("clears nothing when the view stands alone", () => {
expect(insetFor(rect(), [], null)).toBe(0);
});
it("clears the navbar that paints over the view", () => {
expect(insetFor(rect(), [NAVBAR], null)).toBe(56);
});
it("clears the keyboard, which shortens no layout", () => {
expect(insetFor(rect(), [], { height: 440, offsetTop: 0 })).toBe(340);
});
it("takes the deeper of the two rather than their sum", () => {
// The navbar hides behind the keyboard while it is up.
expect(insetFor(rect(), [NAVBAR], { height: 440, offsetTop: 0 })).toBe(340);
});
it("comes back to the navbar once the keyboard goes", () => {
expect(insetFor(rect(), [NAVBAR], { height: 780, offsetTop: 0 })).toBe(56);
});
it("ignores a bar that sits beside the view, not over it", () => {
const sidedock = rect({ right: 300 });
const statusBar = rect({
top: 760,
bottom: 780,
height: 20,
left: 700,
right: 980,
});
expect(insetFor(sidedock, [statusBar], null)).toBe(0);
});
it("measures from where the visible area starts", () => {
expect(insetFor(rect(), [], { height: 440, offsetTop: 100 })).toBe(240);
});
it("says nothing about a view with no height yet", () => {
expect(insetFor(rect({ height: 0, bottom: 0 }), [NAVBAR], null)).toBe(0);
});
it("never returns a negative inset", () => {
const short = rect({ bottom: 300, height: 300 });
expect(insetFor(short, [], { height: 780, offsetTop: 0 })).toBe(0);
});
it("names the bars that float rather than make room", () => {
expect(BOTTOM_BARS).toContain(".mobile-navbar");
expect(BOTTOM_BARS).toContain(".status-bar");
});
});