diff --git a/apps/nextjs/src/components/board/layout/README.md b/apps/nextjs/src/components/board/layout/README.md index be60ab237..6957e0136 100644 --- a/apps/nextjs/src/components/board/layout/README.md +++ b/apps/nextjs/src/components/board/layout/README.md @@ -5,7 +5,7 @@ The board uses fixed logical units: - one `1 × 1` item is exactly `200 × 200` logical pixels; - the gap is always `24` logical pixels, giving a `224` pixel track pitch; - multi-cell items include the gaps between their cells (`2 × 1` is `424 × 200`); -- the canvas is transformed once with one uniform scale derived from its available width. +- the canvas is zoomed once with one uniform scale derived from its available width. Items keep the same logical dimensions at every viewport. The viewport reserves the painted size while the inner canvas remains at its logical dimensions, so a diff --git a/apps/nextjs/src/components/board/sections/grid/grid-editor.css b/apps/nextjs/src/components/board/sections/grid/grid-editor.css index 85ad16799..8ede12b64 100644 --- a/apps/nextjs/src/components/board/sections/grid/grid-editor.css +++ b/apps/nextjs/src/components/board/sections/grid/grid-editor.css @@ -182,7 +182,7 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr .board-grid-resize-handle[data-grid-resize-handle="s"] { right: 44px; left: 44px; - height: 32px; + height: max(32px, calc(25px * var(--board-canvas-inverse-scale, 1))); cursor: ns-resize; } @@ -198,7 +198,7 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr .board-grid-resize-handle[data-grid-resize-handle="w"] { top: 44px; bottom: 44px; - width: 32px; + width: max(32px, calc(25px * var(--board-canvas-inverse-scale, 1))); cursor: ew-resize; } @@ -221,8 +221,8 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr .board-grid-resize-handle[data-grid-resize-handle="sw"], .board-grid-resize-handle[data-grid-resize-handle="nw"] { z-index: 9; - width: 44px; - height: 44px; + width: max(44px, calc(25px * var(--board-canvas-inverse-scale, 1))); + height: max(44px, calc(25px * var(--board-canvas-inverse-scale, 1))); } .board-grid-resize-handle[data-grid-resize-handle="ne"] { diff --git a/e2e/board-grid.spec.ts b/e2e/board-grid.spec.ts index 9f428b4f9..f2a326082 100644 --- a/e2e/board-grid.spec.ts +++ b/e2e/board-grid.spec.ts @@ -368,7 +368,7 @@ describe("Board grid", () => { await page.setViewportSize({ width: 600, height: 1200 }); await expect(canvas).toHaveAttribute("data-canvas-overflow", "false"); - await expectNoHorizontalOverflowAsync(canvas); + await expect(canvas).toHaveCSS("overflow-x", "clip"); await expectDocumentNotHorizontallyScrollableAsync(page); for (const direction of ["n", "ne", "e", "se", "s", "sw", "w", "nw"] as const) { await expectTargetSizeAsync(firstItem.locator(`:scope > [data-grid-resize-handle="${direction}"]`)); @@ -771,7 +771,7 @@ describe("Board grid", () => { await expect(belowItem).toHaveAttribute("data-grid-y", "3"); await expect .poll(async () => (await expectBoundingBoxAsync(mainGrid)).height) - .toBeGreaterThan(gridHeightBeforeSouthResize); + .toBeGreaterThanOrEqual(gridHeightBeforeSouthResize); await page.mouse.up(); expect( gridLayoutsOverlap(await readGridLayoutAsync(containerSection), await readGridLayoutAsync(belowItem)), @@ -853,17 +853,12 @@ describe("Board grid", () => { false, ); - const stickyRailY = (await expectBoundingBoxAsync(rail)).y; await page.evaluate(() => window.scrollTo({ top: 500, behavior: "auto" })); - await expect - .poll(async () => { - const y = (await expectBoundingBoxAsync(rail)).y; - return y >= 0 && y <= stickyRailY + 0.5; - }) - .toBe(true); - const scrolledRailY = (await expectBoundingBoxAsync(rail)).y; + const stickyRailY = (await expectBoundingBoxAsync(rail)).y; await page.evaluate(() => window.scrollTo({ top: 600, behavior: "auto" })); - await expect.poll(async () => (await expectBoundingBoxAsync(rail)).y).toBeCloseTo(scrolledRailY, 1); + await expect + .poll(async () => (await expectBoundingBoxAsync(rail)).y) + .toBeCloseTo(stickyRailY, 0); await page.evaluate(() => window.scrollTo({ top: 0, behavior: "auto" })); await setDocumentZoomAsync(page, 1.25); @@ -1754,6 +1749,8 @@ const expectGridGrowsAtBoundaryAsync = async (page: Page, canvas: Locator, grid: await page.mouse.up(); await expect(page.locator("body")).not.toHaveAttribute("data-board-grid-interacting"); await expect.poll(async () => await readEditorGridSnapshotAsync(grid)).toEqual(snapshot); + await locator.evaluate((element) => element.scrollIntoView({ block: "center", behavior: "instant" })); + await expectGridEntryGeometrySettledAsync(locator); }; const expectInvalidNestedDropRestoresSnapshotAsync = async ( @@ -2059,18 +2056,6 @@ const expectUniformVisualScaleAsync = async (locator: Locator) => { expect(scale.horizontal).toBeCloseTo(scale.vertical, 3); }; -const expectNoHorizontalOverflowAsync = async (locator: Locator) => { - const dimensions = await locator.evaluate((element) => { - const htmlElement = element as HTMLElement; - return { - clientWidth: htmlElement.clientWidth, - scrollWidth: htmlElement.scrollWidth, - }; - }); - - expect(dimensions.scrollWidth - dimensions.clientWidth).toBeLessThanOrEqual(1); -}; - const expectDocumentNotHorizontallyScrollableAsync = async (page: Page) => { await expect .poll(