diff --git a/src/workbench/Workbench.test.tsx b/src/workbench/Workbench.test.tsx index a3d8cda27..9d6e90541 100644 --- a/src/workbench/Workbench.test.tsx +++ b/src/workbench/Workbench.test.tsx @@ -279,8 +279,13 @@ describe("tool window split areas", () => { loadToolWindowLayout("/split"); const { container, unmount } = render(editor} />); const splitter = container.querySelector('[data-split-handle="left"]')!; + const grow = (slot: string) => container.querySelector(`[data-slot="${slot}"]`)!.style.flexGrow; expect(splitter).toHaveAttribute("hidden"); + act(() => showToolWindow("project")); + // Alone, the area fills its side whatever the stored ratio. + expect(grow("left")).toBe("1"); act(() => showToolWindow("structure")); + expect(Number(grow("left")) + Number(grow("left-split"))).toBeCloseTo(1); expect(screen.getByText("tree")).toBeVisible(); expect(screen.getByText("members")).toBeInTheDocument(); expect(splitter).not.toHaveAttribute("hidden"); diff --git a/src/workbench/toolwindows/ToolWindows.module.css b/src/workbench/toolwindows/ToolWindows.module.css index 02d0a6d97..6523b567b 100644 --- a/src/workbench/toolwindows/ToolWindows.module.css +++ b/src/workbench/toolwindows/ToolWindows.module.css @@ -12,6 +12,35 @@ .frameNoLeft { padding-left: var(--island-gap); } +/* + * Only the grid reads its track variables. Not inherited, so dragging a tool window + * edge restyles the grid alone instead of every node in the workbench below it. + */ +@property --tw-left { + syntax: "*"; + inherits: false; +} +@property --tw-right { + syntax: "*"; + inherits: false; +} +@property --tw-bottom { + syntax: "*"; + inherits: false; +} +@property --gap-left { + syntax: "*"; + inherits: false; +} +@property --gap-right { + syntax: "*"; + inherits: false; +} +@property --gap-bottom { + syntax: "*"; + inherits: false; +} + .grid { flex: 1; display: grid; @@ -56,13 +85,6 @@ .sideRow { flex-direction: row; } -/* --split-a / --split-b: the stored ratio (SidePanel), moved directly while the splitter is dragged. */ -.areaFirst { - flex: var(--split-a, 1) 1 0px; -} -.areaSecond { - flex: var(--split-b, 1) 1 0px; -} .island[hidden] { display: none; } diff --git a/src/workbench/toolwindows/ToolWindows.tsx b/src/workbench/toolwindows/ToolWindows.tsx index b17913311..3d874c903 100644 --- a/src/workbench/toolwindows/ToolWindows.tsx +++ b/src/workbench/toolwindows/ToolWindows.tsx @@ -696,24 +696,28 @@ const KEY_STEP = 10; /** * Splitter between the primary and split areas of a side: horizontal on the * left and right (areas stacked), vertical at the bottom (side by side). - * Moves CSS variables while dragging; the ratio is stored on release. + * Moves the areas' flex-grow while dragging; the ratio is stored on release. */ function SplitHandle({ anchor, hidden }: { anchor: ToolWindowAnchor; hidden: boolean }) { const stacked = anchor !== "bottom"; const measure = (el: HTMLElement) => { - const side = el.parentElement; const first = el.previousElementSibling as HTMLElement | null; const second = el.nextElementSibling as HTMLElement | null; - if (!side || !first || !second) return null; + if (!first || !second) return null; const size = (e: HTMLElement) => (stacked ? e.offsetHeight : e.offsetWidth); const total = size(first) + size(second); if (total <= 0) return null; const min = Math.min(stacked ? SPLIT_MIN.stacked : SPLIT_MIN.sideBySide, total / 2); - return { side, first: size(first), total, clamp: (px: number) => Math.min(total - min, Math.max(min, px)) }; + return { + areas: [first, second] as const, + first: size(first), + total, + clamp: (px: number) => Math.min(total - min, Math.max(min, px)), + }; }; - const apply = (side: HTMLElement, ratio: number) => { - side.style.setProperty("--split-a", String(ratio)); - side.style.setProperty("--split-b", String(1 - ratio)); + const apply = ([first, second]: readonly [HTMLElement, HTMLElement], ratio: number) => { + first.style.flexGrow = String(ratio); + second.style.flexGrow = String(1 - ratio); }; const ratioOf = (px: number, total: number) => Math.min(MAX_SPLIT, Math.max(MIN_SPLIT, px / total)); @@ -729,7 +733,7 @@ function SplitHandle({ anchor, hidden }: { anchor: ToolWindowAnchor; hidden: boo let ratio: number | null = null; const move = (ev: globalThis.PointerEvent) => { ratio = ratioOf(m.clamp(m.first + (stacked ? ev.clientY : ev.clientX) - origin), m.total); - apply(m.side, ratio); + apply(m.areas, ratio); }; const end = () => { el.removeEventListener("pointermove", move); @@ -738,7 +742,7 @@ function SplitHandle({ anchor, hidden }: { anchor: ToolWindowAnchor; hidden: boo document.body.style.cursor = cursor; if (ratio !== null) setToolWindowSplit(anchor, ratio); // Back to the store's value (unchanged when the ratio was clamped to it). - apply(m.side, useToolWindowLayout.getState().splits[anchor]); + apply(m.areas, useToolWindowLayout.getState().splits[anchor]); }; el.addEventListener("pointermove", move); el.addEventListener("pointerup", end); @@ -782,25 +786,32 @@ function SidePanel({ visible: SlotRecord; }) { const mounted = useToolWindowLayout((s) => s.mounted); + const split = !!visible[anchor] && !!visible[`${anchor}-split`]; + // A lone area takes the whole side: flex-grow below 1 would leave the rest empty. const ratio = useToolWindowLayout((s) => s.splits[anchor]); - const area = (slot: ToolWindowSlot, className: string | undefined) => { + const area = (slot: ToolWindowSlot, grow: number) => { const visibleId = visible[slot]; const list = windows.filter((w) => slotOf(w) === slot && (mounted.has(w.id) || w.id === visibleId)); if (!list.length) return null; return ( -