fix(studio): keep a hand-scrolled strip put through a maximise

A hidden strip keeps its scroll, so a strip is compared with its last visible
shape and a strip that comes back unchanged is left where the user put it.
This commit is contained in:
Miguel Ángel
2026-09-23 12:46:37 -04:00
parent 0fc5c4f4f8
commit 5fd6c7bdfb
2 changed files with 33 additions and 5 deletions
@@ -199,16 +199,43 @@ describe("dock tab fill", () => {
expect(strip.scrollLeft).toBe(0);
});
it("reveals the shown tab again when its strip comes back from hidden", async () => {
it("keeps a hand scroll through another group being maximised and restored", async () => {
const strip = stripOf("design");
if (!strip) throw new Error("no design strip");
await activate("variables");
Object.defineProperty(strip, "clientWidth", { value: 250, configurable: true });
act(() => resizeAll());
// Another group is maximised, hiding this strip; the browser drops a hidden strip's scroll.
strip.scrollLeft = 0;
// Hidden while another group is maximised; the browser keeps a hidden strip's scroll.
Object.defineProperty(strip, "clientWidth", { value: 0, configurable: true });
act(() => resizeAll());
Object.defineProperty(strip, "clientWidth", { value: 250, configurable: true });
act(() => resizeAll());
expect(strip.scrollLeft).toBe(0);
});
it("leaves a hand scroll alone when its strip widens", async () => {
const strip = stripOf("design");
if (!strip) throw new Error("no design strip");
await activate("variables");
Object.defineProperty(strip, "clientWidth", { value: 150, configurable: true });
act(() => resizeAll());
strip.scrollLeft = 0;
// Wider, but the shown tab is still out of view where the user left the strip.
Object.defineProperty(strip, "clientWidth", { value: 200, configurable: true });
act(() => resizeAll());
expect(strip.scrollLeft).toBe(0);
});
it("reveals the shown tab when its strip comes back narrower than it left", async () => {
const strip = stripOf("design");
if (!strip) throw new Error("no design strip");
await activate("variables");
strip.scrollLeft = 0;
Object.defineProperty(strip, "clientWidth", { value: 1000, configurable: true });
act(() => resizeAll());
Object.defineProperty(strip, "clientWidth", { value: 0, configurable: true });
act(() => resizeAll());
Object.defineProperty(strip, "clientWidth", { value: 250, configurable: true });
act(() => resizeAll());
expect(strip.scrollLeft).toBe(3 * TAB_STEP + TAB_WIDTH + 1 - 250);
@@ -19,12 +19,12 @@ function fillOf(list: HTMLElement): HTMLElement {
type StripShape = { width: number; tab: HTMLElement | null; left: number; size: number };
/**
* True when the strip narrowed, came back from hidden, or its shown tab moved or resized. dockview
* True when the strip narrowed or its shown tab moved or resized since it was last visible. dockview
* reveals a tab only when it is activated, and the user's own scrolling changes none of these.
*/
function reshaped(before: StripShape | undefined, now: StripShape) {
if (!before || now.width === 0) return false;
if (now.width < before.width || before.width === 0) return true;
if (now.width < before.width) return true;
return now.tab !== before.tab || now.left !== before.left || now.size !== before.size;
}
@@ -46,7 +46,8 @@ function measureFill(list: HTMLElement, shapes: WeakMap<HTMLElement, StripShape>
size: tab?.offsetWidth ?? 0,
};
const reveal = tab !== null && reshaped(shapes.get(list), shape);
shapes.set(list, shape);
// A hidden strip (another group maximised) keeps its scroll, so it is compared with its last visible shape.
if (shape.width > 0) shapes.set(list, shape);
const scrollLeft = list.scrollLeft;
const target = reveal ? revealedScroll(shape, scrollLeft) : scrollLeft;
return () => {