Merge grid geometry fixes into dnd kit

This commit is contained in:
Thomas Camlong committed 2026-08-09 18:03:37 +02:00
commit 63f82f1a35
13 files changed
+142 -57

No files matched your search

+2 -1
View File
@@ -38,7 +38,8 @@ Underscores and hyphen-minus are allowed.
The column count defines how many fixed-width cells are available in each row.
One cell contains a `200 × 200` logical-pixel tile.
Homarr scales the whole board uniformly instead of changing widget proportions, with horizontal scrolling when further shrinking would make controls too small.
Homarr scales the whole board uniformly instead of changing widget proportions, and always fits it to the available width without horizontal scrolling.
The main canvas stays at least one viewport tall and grows automatically while you drag or resize items near its lower edge.
### Public
@@ -10,7 +10,9 @@ The board uses fixed logical units:
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
widget's `clientWidth`, `clientHeight`, and `ResizeObserver` measurements stay
stable.
stable. The visual canvas always fits the available width, the main root keeps
at least one viewport of height, and edit-mode previews extend it downward as
needed.
The read-only renderer should:
@@ -5,6 +5,5 @@
export const LOGICAL_GRID_CELL_SIZE = 200;
export const LOGICAL_GRID_GAP = 24;
export const LOGICAL_GRID_PITCH = LOGICAL_GRID_CELL_SIZE + LOGICAL_GRID_GAP;
export const MIN_ACCESSIBLE_CANVAS_SCALE = 0.75;
export const COLLAPSED_SECTION_ROW_COUNT = 0.5;
@@ -21,6 +21,13 @@ export const getLogicalItemStyle = ({ x, y, w, h }: GridCoordinates): CSSPropert
export const getLayoutRowCount = (placements: readonly GridCoordinates[]) =>
placements.reduce((rowCount, placement) => Math.max(rowCount, placement.y + placement.h), 0);
export const getGridRowCountForVisualHeight = (visualHeight: number, visualScale: number) => {
if (!Number.isFinite(visualHeight) || visualHeight <= 0) return 1;
if (!Number.isFinite(visualScale) || visualScale <= 0) return 1;
return Math.max(1, Math.ceil((visualHeight / visualScale + LOGICAL_GRID_GAP) / LOGICAL_GRID_PITCH));
};
export const normalizeGridPlacement = <TPlacement extends GridPlacement>(
placement: TPlacement,
columnCount: number,
@@ -2,9 +2,7 @@
width: 100%;
max-width: 100%;
min-width: 0;
overflow: visible;
overflow-x: auto;
overscroll-behavior-inline: contain;
overflow: clip;
}
.viewport[data-board-hydrated="false"] {
@@ -5,11 +5,8 @@ import { createContext, useContext, useEffect, useMemo, useState } from "react";
import { Box } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { MIN_ACCESSIBLE_CANVAS_SCALE } from "./constants";
import classes from "./scaled-board-canvas.module.css";
export { MIN_ACCESSIBLE_CANVAS_SCALE };
const BoardCanvasScaleContext = createContext(1);
export const useBoardCanvasScale = () => useContext(BoardCanvasScaleContext);
@@ -18,7 +15,7 @@ export const calculateBoardCanvasScale = (availableWidth: number, logicalWidth:
if (!Number.isFinite(availableWidth) || !Number.isFinite(logicalWidth)) return 1;
if (availableWidth <= 0 || logicalWidth <= 0) return 1;
return Math.max(MIN_ACCESSIBLE_CANVAS_SCALE, availableWidth / logicalWidth);
return availableWidth / logicalWidth;
};
export const calculateBoardUiScale = (canvasScale: number) => {
@@ -36,9 +33,8 @@ interface ScaledBoardCanvasProps {
/**
* Scales the complete board as one surface while keeping widget layout in
* fixed logical pixels. The scale floor deliberately allows horizontal
* overflow so browser zoom still magnifies content instead of being cancelled
* by responsive downscaling.
* fixed logical pixels. The complete canvas always fits its viewport so board
* content can never widen the document or require horizontal scrolling.
*/
export const ScaledBoardCanvas = ({
logicalWidth,
@@ -62,6 +58,7 @@ export const ScaledBoardCanvas = ({
[logicalWidth, resolvedAvailableWidth],
);
const uiScale = calculateBoardUiScale(scale);
const inverseScale = scale > 0 ? 1 / scale : 1;
const visualWidth = logicalWidth * scale;
const hasHorizontalOverflow = resolvedAvailableWidth > 0 && visualWidth - resolvedAvailableWidth > 0.5;
@@ -84,6 +81,7 @@ export const ScaledBoardCanvas = ({
className={classes.canvas}
w={logicalWidth}
style={{
"--board-canvas-inverse-scale": inverseScale,
"--board-canvas-ui-scale": uiScale,
transform: `scale(${scale})`,
}}
@@ -1,6 +1,12 @@
import { describe, expect, test } from "vitest";
import { getLayoutRowCount, getLogicalItemStyle, getLogicalTrackSize, normalizeGridPlacement } from "../index";
import {
getGridRowCountForVisualHeight,
getLayoutRowCount,
getLogicalItemStyle,
getLogicalTrackSize,
normalizeGridPlacement,
} from "../index";
describe("fixed dashboard geometry", () => {
test("keeps a 1x1 item exactly 200px with a fixed 24px gap", () => {
@@ -24,4 +30,12 @@ describe("fixed dashboard geometry", () => {
expect(placement).toEqual({ id: "wide", x: 0, y: 0, w: 4, h: 2 });
expect(getLayoutRowCount([placement, { x: 0, y: 4, w: 1, h: 3 }])).toBe(7);
});
test("derives enough logical rows to fill the visual viewport at any canvas scale", () => {
expect(getGridRowCountForVisualHeight(1200, 1)).toBe(6);
expect(getLogicalTrackSize(getGridRowCountForVisualHeight(1200, 1))).toBeGreaterThanOrEqual(1200);
expect(getGridRowCountForVisualHeight(1200, 0.5)).toBe(11);
expect(getLogicalTrackSize(getGridRowCountForVisualHeight(1200, 0.5)) * 0.5).toBeGreaterThanOrEqual(1200);
});
});
@@ -1,9 +1,9 @@
import { describe, expect, test } from "vitest";
import { calculateBoardCanvasScale, calculateBoardUiScale, MIN_ACCESSIBLE_CANVAS_SCALE } from "../scaled-board-canvas";
import { calculateBoardCanvasScale, calculateBoardUiScale } from "../scaled-board-canvas";
describe("board canvas scale policy", () => {
test("fits the logical canvas exactly when the viewport can preserve the minimum scale", () => {
test("fits the logical canvas exactly", () => {
const scale = calculateBoardCanvasScale(840, 1000);
expect(scale).toBe(0.84);
@@ -14,16 +14,16 @@ describe("board canvas scale policy", () => {
expect(calculateBoardCanvasScale(1800, 1000)).toBe(1.8);
});
test("stops counter-scaling at 0.75 and lets the canvas overflow", () => {
test("keeps fitting narrow viewports without horizontal overflow", () => {
const availableWidth = 600;
const logicalWidth = 1000;
const scale = calculateBoardCanvasScale(availableWidth, logicalWidth);
expect(scale).toBe(MIN_ACCESSIBLE_CANVAS_SCALE);
expect(logicalWidth * scale).toBeGreaterThan(availableWidth);
expect(scale).toBe(0.6);
expect(logicalWidth * scale).toBe(availableWidth);
});
test("keeps content materially magnified at 200% effective zoom", () => {
test("keeps the board fitted at 200% effective zoom", () => {
const logicalWidth = 1696;
const normalAvailableWidth = 1872;
const normalScale = calculateBoardCanvasScale(normalAvailableWidth, logicalWidth);
@@ -31,8 +31,8 @@ describe("board canvas scale policy", () => {
const normalPaintedCellSize = 200 * normalScale;
const zoomedPaintedCellSize = 200 * zoomedScale * 2;
expect(zoomedScale).toBe(MIN_ACCESSIBLE_CANVAS_SCALE);
expect(zoomedPaintedCellSize).toBeGreaterThan(normalPaintedCellSize * 1.25);
expect(zoomedScale).toBeCloseTo(normalScale / 2);
expect(zoomedPaintedCellSize).toBeCloseTo(normalPaintedCellSize);
});
test.each([
@@ -18,7 +18,7 @@ export const BoardEmptySection = ({ section, columnCount, requestedRowCount, rai
const [isEditMode] = useEditMode();
const t = useScopedI18n("board.landmark");
if (totalLength === 0 && !isEditMode && requestedRowCount === 0) return null;
if (totalLength === 0 && !isEditMode && requestedRowCount === 0 && railPlacement !== "main") return null;
return (
<SectionGrid
@@ -86,6 +86,10 @@ body[data-board-grid-interacting="drag"] .board-grid-placeholder {
overflow: visible;
}
body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="true"] > .board-grid-content-mount {
overflow: clip;
}
.board-grid-placeholder {
z-index: 0;
background: color-mix(in srgb, var(--mantine-primary-color-filled) 16%, transparent);
@@ -10,6 +10,7 @@ import { CollisionPriority } from "@dnd-kit/abstract";
import { pointerIntersection } from "@dnd-kit/collision";
import type { DragDropManager, Draggable as DomDraggable } from "@dnd-kit/dom";
import { Accessibility, AutoScroller, Feedback, PointerActivationConstraints, PointerSensor } from "@dnd-kit/dom";
import { RestrictToElement } from "@dnd-kit/dom/modifiers";
import { getEventCoordinates, getFrameTransform, isPointerEvent } from "@dnd-kit/dom/utilities";
import type {
BeforeDragStartEvent,
@@ -720,6 +721,7 @@ export default function GridEditor({ sectionId, columnCount, rowCount, placement
if (!previewGrid || !interaction) return placements;
if (interaction.sourceGridId === sectionId) {
if (interaction.mode === "resize") return previewGrid.placements;
const original = controlledById.get(interaction.activeId);
if (!original) return previewGrid.placements;
return previewGrid.placements.some((placement) => placement.id === interaction.activeId)
@@ -734,9 +736,16 @@ export default function GridEditor({ sectionId, columnCount, rowCount, placement
);
const targetPlacement =
interaction?.valid && interaction.targetGridId === sectionId ? interaction.targetPlacement : null;
const growsDuringDrag =
interaction?.mode === "drag" &&
(interaction.targetGridId === sectionId ||
(interaction.targetGridId === null && interaction.sourceGridId === sectionId));
const participatesInInteraction =
interaction && (interaction.sourceGridId === sectionId || interaction.targetGridId === sectionId);
const previewRowCount = getLayoutRowCount(previewGrid?.placements ?? renderPlacements);
const renderedRowCount =
section.kind !== "container" && interaction?.mode === "resize"
? Math.max(rowCount, getLayoutRowCount(renderPlacements))
section.kind !== "container" && participatesInInteraction
? Math.max(rowCount, previewRowCount) + (growsDuringDrag ? 1 : 0)
: rowCount;
const { ref: droppableRef, isDropTarget } = useDroppable<GridTargetData>({
@@ -876,6 +885,14 @@ const DndGridEntry = ({ sectionId, placement, label, columnCount, maxRowCount }:
],
[],
);
const modifiers = useMemo(
() => [
RestrictToElement.configure({
element: () => shellRef.current?.closest('[data-testid="board-canvas"]') ?? null,
}),
],
[],
);
const {
ref: draggableRef,
handleRef,
@@ -886,6 +903,7 @@ const DndGridEntry = ({ sectionId, placement, label, columnCount, maxRowCount }:
type: GRID_ENTRY_TYPE,
data: sourceData,
sensors,
modifiers,
});
const setShellRef = useCallback(
(element: HTMLDivElement | null) => {
@@ -1,6 +1,6 @@
"use client";
import { useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import dynamic from "next/dynamic";
import { Box, LoadingOverlay } from "@mantine/core";
import combineClasses from "clsx";
@@ -13,12 +13,14 @@ import type { ContainerSectionItem, Section } from "~/app/[locale]/boards/_types
import {
getCollapsedDisplayLayout,
getEditableCanvasAttributes,
getGridRowCountForVisualHeight,
getLayoutRowCount,
getLogicalItemStyle,
getLogicalTrackSize,
getReadonlyCanvasAttributes,
normalizeGridPlacement,
} from "~/components/board/layout";
import { useBoardCanvasScale } from "~/components/board/layout/scaled-board-canvas";
import { loadGridEditorAsync } from "./grid-editor-loader";
import { useGridEditorRuntimeStatus } from "./grid-editor-runtime";
import type { SectionGridPlacement } from "./use-grid-layout-actions";
@@ -81,6 +83,7 @@ export const SectionGrid = ({
className,
}: SectionGridProps) => {
const [isEditMode] = useEditMode();
const canvasScale = useBoardCanvasScale();
const editorRuntimeStatus = useGridEditorRuntimeStatus();
const board = useRequiredBoard();
const currentLayoutId = useCurrentLayout();
@@ -144,8 +147,12 @@ export const SectionGrid = ({
);
const displayedItems = items.map((item) => withPlacement(item, placementById.get(item.id)));
const displayedInnerSections = innerSections.map((item) => withPlacement(item, placementById.get(item.id)));
const minimumViewportRowCount = useMinimumViewportRowCount(
section.kind === "empty" && railPlacement === "main",
canvasScale,
);
const contentRowCount = Math.max(1, getLayoutRowCount(displayPlacements));
const rowCount = Math.max(contentRowCount, requestedRowCount);
const rowCount = Math.max(contentRowCount, requestedRowCount, minimumViewportRowCount);
const logicalWidth = getLogicalTrackSize(columnCount);
const logicalHeight = getLogicalTrackSize(rowCount);
const canvasAttributes = isEditMode
@@ -199,6 +206,24 @@ export const SectionGrid = ({
);
};
const useMinimumViewportRowCount = (enabled: boolean, canvasScale: number) => {
const [visualHeight, setVisualHeight] = useState(0);
useEffect(() => {
if (!enabled) return;
const update = () => setVisualHeight(window.visualViewport?.height ?? window.innerHeight);
update();
window.addEventListener("resize", update, { passive: true });
window.visualViewport?.addEventListener("resize", update, { passive: true });
return () => {
window.removeEventListener("resize", update);
window.visualViewport?.removeEventListener("resize", update);
};
}, [enabled]);
return enabled ? getGridRowCountForVisualHeight(visualHeight, canvasScale) : 0;
};
const getContainerMinimumSize = (
board: ReturnType<typeof useRequiredBoard>,
layoutId: string,
+49 -30
View File
@@ -9,7 +9,6 @@ import { describe, test } from "vitest";
import {
LOGICAL_GRID_CELL_SIZE,
LOGICAL_GRID_GAP,
MIN_ACCESSIBLE_CANVAS_SCALE,
} from "../apps/nextjs/src/components/board/layout/constants";
import * as sqliteSchema from "../packages/db/schema/sqlite";
import { createHomarrContainer } from "./shared/create-homarr-container";
@@ -26,7 +25,6 @@ const gridRuntimeMarker = "homarr-dnd-kit-v1";
const dndKitPayloadMarker = "dnd-kit-description";
const logicalCellSize = LOGICAL_GRID_CELL_SIZE;
const logicalCellPitch = LOGICAL_GRID_CELL_SIZE + LOGICAL_GRID_GAP;
const minimumCanvasScale = MIN_ACCESSIBLE_CANVAS_SCALE;
describe("Board grid", () => {
test("supports fixed, nested, rail, pointer, keyboard and reconciliation workflows", async () => {
@@ -174,21 +172,17 @@ describe("Board grid", () => {
expect(await readLayoutShiftScoreAsync(page)).toBeLessThan(0.01);
// CSS zoom exercises Chromium's browser-zoom layout path without relying
// on desktop browser chrome. At 200% the scale floor must preserve
// magnification and expose horizontal scrolling instead of shrinking the
// board by the inverse zoom factor.
// on desktop browser chrome. The board must keep fitting without adding
// horizontal scrolling at any effective zoom.
await setDocumentZoomAsync(page, 2);
await expect(canvas).toHaveAttribute("data-canvas-scale", String(minimumCanvasScale));
await expect(canvas).toHaveAttribute("data-canvas-overflow", "true");
const zoomedCanvasScale = await readCanvasScaleAsync(canvas);
expect(zoomedCanvasScale).toBeCloseTo(normalCanvasScale / 2, 2);
await expect(canvas).toHaveAttribute("data-canvas-overflow", "false");
await expectFixedLogicalTileAsync(logicalTile);
await expectUniformVisualScaleAsync(logicalTile);
const fittedClockFontSize = await firstItem.locator(".clock-time-text").evaluate((element) => {
return Number.parseFloat(getComputedStyle(element).fontSize);
});
expect(fittedClockFontSize * minimumCanvasScale).toBeGreaterThanOrEqual(25);
const zoomedTileBox = await expectBoundingBoxAsync(logicalTile);
expect(zoomedTileBox.width).toBeGreaterThan(normalZoomTileBox.width * 1.25);
await expectHorizontalOverflowAsync(canvas);
expect(zoomedTileBox.width).toBeCloseTo(normalZoomTileBox.width, 1);
await expectNoHorizontalOverflowAsync(canvas);
await expectDocumentNotHorizontallyScrollableAsync(page);
const overflowRailY = (await expectBoundingBoxAsync(rail)).y;
await page.evaluate(() => window.scrollTo({ top: 300, behavior: "auto" }));
@@ -337,8 +331,11 @@ describe("Board grid", () => {
await expect(itemSelectDialog).toHaveCount(0);
const mainGrid = page.locator(`[data-grid-section-id="${fixture.sectionId}"]`);
const editorViewport = page.viewportSize();
if (!editorViewport) throw new Error("Expected a browser viewport");
expect((await expectBoundingBoxAsync(mainGrid)).height).toBeGreaterThanOrEqual(editorViewport.height - 1);
const interactionSnapshot = await readEditorGridSnapshotAsync(mainGrid);
await expectGridBoundaryRejectsOverflowAsync(page, mainGrid, firstItem);
await expectGridGrowsAtBoundaryAsync(page, canvas, mainGrid, firstItem);
await firstItem.locator("[data-grid-item-content]").evaluate((element) => {
const iframe = document.createElement("iframe");
iframe.dataset.e2eDragIframe = "true";
@@ -367,8 +364,8 @@ describe("Board grid", () => {
.evaluateAll((elements) => elements.forEach((element) => element.remove()));
await page.setViewportSize({ width: 600, height: 1200 });
await expect(canvas).toHaveAttribute("data-canvas-scale", String(minimumCanvasScale));
await expectHorizontalOverflowAsync(canvas);
await expect(canvas).toHaveAttribute("data-canvas-overflow", "false");
await expectNoHorizontalOverflowAsync(canvas);
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}"]`));
@@ -718,13 +715,12 @@ describe("Board grid", () => {
expect(continuousResizeWidth).toBeGreaterThan(logicalCellSize * canvasScale);
await page.mouse.move(eastResizeStart.x - logicalCellPitch * canvasScale, eastResizeStart.y, { steps: 6 });
await expect(containerSection).toHaveAttribute("data-grid-w", "2");
await expect(containerSection).toHaveAttribute("data-grid-w", "1");
await expect(resizePlaceholder).toHaveAttribute("data-grid-w", "1");
expect((await expectBoundingBoxAsync(resizeOutline)).width).toBeCloseTo(logicalCellSize * canvasScale, 1);
const liveNestedGridBox = await expectBoundingBoxAsync(nestedEditor);
const liveContainerBox = await expectBoundingBoxAsync(containerSection);
expect(liveNestedGridBox.width).toBeCloseTo(originalResizeWidth, 1);
expect(liveNestedGridBox.width).toBeLessThanOrEqual(liveContainerBox.width + 1);
expect(liveContainerBox.width).toBeCloseTo(logicalCellSize * canvasScale, 1);
await expect(containerSection.locator(":scope > .board-grid-content-mount")).toHaveCSS("overflow", "clip");
await page.mouse.up();
await expect(containerSection).toHaveAttribute("data-grid-w", "1");
await expect(resizeOutline).toHaveCount(0);
@@ -759,15 +755,26 @@ describe("Board grid", () => {
await expect(containerSection).toHaveAttribute("data-grid-h", "2");
const southResizeHandle = containerSection.locator(':scope > [data-grid-resize-handle="s"]');
await dragLocatorByAsync(page, southResizeHandle, 0, logicalCellPitch * canvasScale);
const gridHeightBeforeSouthResize = (await expectBoundingBoxAsync(mainGrid)).height;
const southResizeBox = await expectBoundingBoxAsync(southResizeHandle);
const southResizeStart = {
x: southResizeBox.x + southResizeBox.width / 2,
y: southResizeBox.y + southResizeBox.height / 2,
};
await page.mouse.move(southResizeStart.x, southResizeStart.y);
await page.mouse.down();
await page.mouse.move(southResizeStart.x, southResizeStart.y + logicalCellPitch * canvasScale, { steps: 8 });
await expect(containerSection).toHaveAttribute("data-grid-h", "3");
await expect(belowItem).toHaveAttribute("data-grid-y", "3");
await expect
.poll(async () => (await expectBoundingBoxAsync(mainGrid)).height)
.toBeGreaterThan(gridHeightBeforeSouthResize);
await page.mouse.up();
expect(
gridLayoutsOverlap(await readGridLayoutAsync(containerSection), await readGridLayoutAsync(belowItem)),
).toBe(false);
// Auto-scroll needs real document overflow. Grow the canvas through an
// explicit resize instead of relying on edit mode to add empty rows.
// Grow the canvas further to exercise sustained document auto-scroll.
await dragLocatorByAsync(page, southResizeHandle, 0, logicalCellPitch * canvasScale * 6);
await expect(containerSection).toHaveAttribute("data-grid-h", "9");
await expect(belowItem).toHaveAttribute("data-grid-y", "9");
@@ -1713,9 +1720,10 @@ const expectImmediateOutsideReleaseRestoresSnapshotAsync = async (page: Page, gr
await expect.poll(async () => await readEditorGridSnapshotAsync(grid)).toEqual(snapshot);
};
const expectGridBoundaryRejectsOverflowAsync = async (page: Page, grid: Locator, locator: Locator) => {
const expectGridGrowsAtBoundaryAsync = async (page: Page, canvas: Locator, grid: Locator, locator: Locator) => {
const snapshot = await readEditorGridSnapshotAsync(grid);
const gridBox = await expectBoundingBoxAsync(grid);
const canvasBox = await expectBoundingBoxAsync(canvas);
const itemBox = await expectBoundingBoxAsync(locator);
const start = { x: itemBox.x + itemBox.width * 0.63, y: itemBox.y + itemBox.height * 0.42 };
@@ -1723,11 +1731,22 @@ const expectGridBoundaryRejectsOverflowAsync = async (page: Page, grid: Locator,
await page.mouse.down();
await page.mouse.move(start.x + 8, start.y);
await expect(locator).toHaveAttribute("data-dnd-drag-source", "true");
await page.mouse.move(gridBox.x + gridBox.width / 2, gridBox.y + gridBox.height + logicalCellPitch, { steps: 12 });
await expect(page.locator("[data-grid-placeholder-for]")).toHaveCount(0);
const activeGridBox = await expectBoundingBoxAsync(grid);
expect(activeGridBox.height).toBeCloseTo(gridBox.height, 1);
await expect
.poll(async () => (await expectBoundingBoxAsync(grid)).height)
.toBeGreaterThan(gridBox.height);
await page.mouse.move(gridBox.x + gridBox.width / 2, gridBox.y + gridBox.height + logicalCellPitch / 2, {
steps: 12,
});
await expect(page.locator("[data-grid-placeholder-for]")).toHaveCount(1);
const activeGridBox = await expectBoundingBoxAsync(grid);
expect(activeGridBox.height).toBeGreaterThan(gridBox.height);
await page.mouse.move(canvasBox.x + canvasBox.width + 1000, start.y, { steps: 12 });
const overlayBox = await expectBoundingBoxAsync(page.locator(".board-grid-drag-overlay[data-dnd-dragging]"));
expect(overlayBox.x + overlayBox.width).toBeLessThanOrEqual(canvasBox.x + canvasBox.width + 1);
await page.keyboard.press("Escape");
await page.mouse.up();
await expect(page.locator("body")).not.toHaveAttribute("data-board-grid-interacting");
await expect.poll(async () => await readEditorGridSnapshotAsync(grid)).toEqual(snapshot);
@@ -2036,7 +2055,7 @@ const expectUniformVisualScaleAsync = async (locator: Locator) => {
expect(scale.horizontal).toBeCloseTo(scale.vertical, 3);
};
const expectHorizontalOverflowAsync = async (locator: Locator) => {
const expectNoHorizontalOverflowAsync = async (locator: Locator) => {
const dimensions = await locator.evaluate((element) => {
const htmlElement = element as HTMLElement;
return {
@@ -2045,7 +2064,7 @@ const expectHorizontalOverflowAsync = async (locator: Locator) => {
};
});
expect(dimensions.scrollWidth).toBeGreaterThan(dimensions.clientWidth);
expect(dimensions.scrollWidth - dimensions.clientWidth).toBeLessThanOrEqual(1);
};
const expectDocumentNotHorizontallyScrollableAsync = async (page: Page) => {