fix(sidebar): keep tooltips within the viewport

This commit is contained in:
zipg authored and GitHub committed 2026-07-30 18:15:41 +08:00
1 parent 661575227f
commit 7d1a72e074
3 files changed
+76 -3

No files matched your search

@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { floatingArrowOffset, floatingViewportShift } from "@/lib/common/floatingViewportPosition";
describe("floating viewport position", () => {
it("moves a floating element inside each viewport edge", () => {
expect(floatingViewportShift({ left: -24, right: 296, top: 40, bottom: 80 }, { width: 800, height: 600 })).toEqual({ x: 32, y: 0 });
expect(floatingViewportShift({ left: 600, right: 840, top: 40, bottom: 80 }, { width: 800, height: 600 })).toEqual({ x: -48, y: 0 });
expect(floatingViewportShift({ left: 40, right: 240, top: -12, bottom: 28 }, { width: 800, height: 600 })).toEqual({ x: 0, y: 20 });
expect(floatingViewportShift({ left: 40, right: 240, top: 570, bottom: 620 }, { width: 800, height: 600 })).toEqual({ x: 0, y: -28 });
});
it("does not move an element that already fits", () => {
expect(floatingViewportShift({ left: 8, right: 792, top: 8, bottom: 592 }, { width: 800, height: 600 })).toEqual({ x: 0, y: 0 });
});
it("keeps the arrow pointing at the trigger after shifting the floating element", () => {
expect(floatingArrowOffset(320, 32)).toBe(128);
expect(floatingArrowOffset(320, -48)).toBe(208);
expect(floatingArrowOffset(40, 100)).toBe(8);
expect(floatingArrowOffset(40, -100)).toBe(32);
});
});
@@ -0,0 +1,29 @@
export interface FloatingRect {
left: number;
right: number;
top: number;
bottom: number;
}
export interface FloatingViewport {
width: number;
height: number;
}
export function floatingViewportShift(rect: FloatingRect, viewport: FloatingViewport, margin = 8): { x: number; y: number } {
let x = 0;
let y = 0;
if (rect.left < margin) x = margin - rect.left;
else if (rect.right > viewport.width - margin) x = viewport.width - margin - rect.right;
if (rect.top < margin) y = margin - rect.top;
else if (rect.bottom > viewport.height - margin) y = viewport.height - margin - rect.bottom;
return { x, y };
}
export function floatingArrowOffset(length: number, shift: number, padding = 8): number {
const maximum = Math.max(padding, length - padding);
return Math.min(Math.max(padding, length / 2 - shift), maximum);
}