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); 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"); const strip = stripOf("design");
if (!strip) throw new Error("no design strip"); if (!strip) throw new Error("no design strip");
await activate("variables"); await activate("variables");
Object.defineProperty(strip, "clientWidth", { value: 250, configurable: true }); Object.defineProperty(strip, "clientWidth", { value: 250, configurable: true });
act(() => resizeAll()); 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 }); Object.defineProperty(strip, "clientWidth", { value: 0, configurable: true });
act(() => resizeAll()); 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; 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 }); Object.defineProperty(strip, "clientWidth", { value: 250, configurable: true });
act(() => resizeAll()); act(() => resizeAll());
expect(strip.scrollLeft).toBe(3 * TAB_STEP + TAB_WIDTH + 1 - 250); 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 }; 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. * 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) { function reshaped(before: StripShape | undefined, now: StripShape) {
if (!before || now.width === 0) return false; 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; 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, size: tab?.offsetWidth ?? 0,
}; };
const reveal = tab !== null && reshaped(shapes.get(list), shape); 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 scrollLeft = list.scrollLeft;
const target = reveal ? revealedScroll(shape, scrollLeft) : scrollLeft; const target = reveal ? revealedScroll(shape, scrollLeft) : scrollLeft;
return () => { return () => {