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:
1 parent
f7ccd21d7b
commit
f980abcc1d
6 files changed
+267
-3
No files matched your search
@@ -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");
|
||||
});
|
||||
});
|
||||
+39
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { onScreen, place, roomFor } from "../src/lib/place";
|
||||
import { onScreen, place, roomFor, viewport } from "../src/lib/place";
|
||||
import type { Box } from "../src/lib/place";
|
||||
|
||||
const VIEW = { width: 1280, height: 800 };
|
||||
@@ -95,3 +95,41 @@ describe("roomFor", () => {
|
||||
expect(roomFor({ width: 390, height: 60 })).toBeGreaterThan(100);
|
||||
});
|
||||
});
|
||||
|
||||
// Obsidian's phone navbar is not part of the visual viewport - it floats over
|
||||
// it - so a panel sized to the viewport still ends up underneath it.
|
||||
describe("viewport minus the floating navbar", () => {
|
||||
function fakeWin(navbarTop: number | null, height = 780): Window {
|
||||
const bar = {
|
||||
getBoundingClientRect: () => ({
|
||||
top: navbarTop ?? 0,
|
||||
bottom: height,
|
||||
height: height - (navbarTop ?? 0),
|
||||
left: 0,
|
||||
right: 390,
|
||||
}),
|
||||
offsetParent: {},
|
||||
};
|
||||
return {
|
||||
innerWidth: 390,
|
||||
innerHeight: height,
|
||||
visualViewport: { width: 390, height, offsetTop: 0, offsetLeft: 0 },
|
||||
document: {
|
||||
querySelector: (selector: string) =>
|
||||
selector === ".mobile-navbar" && navbarTop !== null ? bar : null,
|
||||
},
|
||||
} as unknown as Window;
|
||||
}
|
||||
|
||||
it("takes the navbar off the bottom", () => {
|
||||
expect(viewport(fakeWin(724)).height).toBe(724);
|
||||
});
|
||||
|
||||
it("leaves the height alone with no navbar in the way", () => {
|
||||
expect(viewport(fakeWin(null)).height).toBe(780);
|
||||
});
|
||||
|
||||
it("still leaves a usable strip if a bar somehow covers everything", () => {
|
||||
expect(viewport(fakeWin(0)).height).toBeGreaterThanOrEqual(120);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user