fix(studio): stop popovers and tooltips clipping at panel edges (#2890)

The Renders tab format popover rendered as an in-flow absolute panel inside
the right panel, which is overflow-hidden, so it was sliced at the panel
edge. Portal it to the body and position it with the shared floating-panel
helper instead.

The ui/Tooltip bubble clamped only its centre point to the viewport, so a
wide bubble near an edge still hung off-screen (the timeline Selection tool
tooltip lost 32px on the left). Clamp with the measured bubble width.
This commit is contained in:
Miguel Ángel
2026-07-30 02:27:48 +02:00
committed by GitHub
parent cef3b86c95
commit f81ac74572
4 changed files with 114 additions and 31 deletions
@@ -1,5 +1,24 @@
import { describe, expect, it } from "vitest";
import { resolveFloatingPanelPosition } from "./floatingPanel";
import { clampCentredLeft, resolveFloatingPanelPosition } from "./floatingPanel";
describe("clampCentredLeft", () => {
it("leaves a bubble that already fits alone", () => {
expect(clampCentredLeft(400, 104, 800, 8)).toBe(400);
});
it("pushes a bubble whose left half would leave the viewport", () => {
// Trigger centred at x=20 with a 104px bubble would render at left=-32.
expect(clampCentredLeft(20, 104, 800, 8)).toBe(60);
});
it("pushes a bubble whose right half would leave the viewport", () => {
expect(clampCentredLeft(790, 104, 800, 8)).toBe(740);
});
it("keeps the left edge visible when the bubble is wider than the viewport", () => {
expect(clampCentredLeft(10, 900, 800, 8)).toBe(458);
});
});
describe("resolveFloatingPanelPosition", () => {
it("places the panel below the anchor when there is space", () => {
@@ -22,6 +22,21 @@ function clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
/**
* Clamp the centre point of a centred bubble so the whole bubble stays in the
* viewport: clamping the centre alone lets a wide bubble hang off the edge.
*/
export function clampCentredLeft(
centreX: number,
bubbleWidth: number,
viewportWidth: number,
margin: number,
): number {
const half = bubbleWidth / 2;
const min = half + margin;
return clamp(centreX, min, Math.max(min, viewportWidth - half - margin));
}
export function resolveFloatingPanelPosition(
anchor: FloatingRect,
viewport: FloatingSize,