fix: stabilize rail and resize interactions
This commit is contained in:
1 parent
d635e4d80f
commit
ab012b4a32
5 files changed
+101
-20
No files matched your search
@@ -21,10 +21,7 @@
|
||||
height: var(--board-gutter-viewport-height);
|
||||
padding-top: 24px;
|
||||
margin-top: -24px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-gutter: stable;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.gutter[data-board-editing="true"] {
|
||||
|
||||
@@ -116,11 +116,6 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.board-grid-resize-outline[data-grid-resize-valid="false"] {
|
||||
background: color-mix(in srgb, var(--mantine-color-red-filled) 8%, transparent);
|
||||
border-color: var(--mantine-color-red-filled);
|
||||
}
|
||||
|
||||
.board-grid-editor[data-dnd-drop-target="true"] {
|
||||
outline: 2px solid color-mix(in srgb, var(--mantine-primary-color-filled) 60%, transparent);
|
||||
outline-offset: 4px;
|
||||
|
||||
@@ -59,6 +59,7 @@ interface GridEditorProps {
|
||||
sectionId: string;
|
||||
columnCount: number;
|
||||
rowCount: number;
|
||||
maxRowCount: number | null;
|
||||
placements: readonly SectionGridPlacement[];
|
||||
className: string;
|
||||
}
|
||||
@@ -707,7 +708,14 @@ const GridCollisionSynchronizer = ({ interaction }: { interaction: GridInteracti
|
||||
};
|
||||
|
||||
/** A section-local dnd-kit canvas registered with the board transaction. */
|
||||
export default function GridEditor({ sectionId, columnCount, rowCount, placements, className }: GridEditorProps) {
|
||||
export default function GridEditor({
|
||||
sectionId,
|
||||
columnCount,
|
||||
rowCount,
|
||||
maxRowCount,
|
||||
placements,
|
||||
className,
|
||||
}: GridEditorProps) {
|
||||
const t = useI18n();
|
||||
const { section, items, innerSections } = useSectionContext();
|
||||
const { interaction, registerGrid, getDepth, isDropTargetEligible } = useBoardGridEditor();
|
||||
@@ -744,7 +752,7 @@ export default function GridEditor({ sectionId, columnCount, rowCount, placement
|
||||
interaction && (interaction.sourceGridId === sectionId || interaction.targetGridId === sectionId);
|
||||
const previewRowCount = getLayoutRowCount(previewGrid?.placements ?? renderPlacements);
|
||||
const renderedRowCount =
|
||||
section.kind !== "container" && participatesInInteraction
|
||||
maxRowCount === null && participatesInInteraction
|
||||
? Math.max(rowCount, previewRowCount) + (growsDuringDrag ? 1 : 0)
|
||||
: rowCount;
|
||||
|
||||
@@ -775,14 +783,14 @@ export default function GridEditor({ sectionId, columnCount, rowCount, placement
|
||||
return registerGrid({
|
||||
id: sectionId,
|
||||
columnCount,
|
||||
maxRowCount: section.kind === "container" ? rowCount : null,
|
||||
maxRowCount,
|
||||
parentGridId,
|
||||
ownerPlacementId,
|
||||
placements,
|
||||
depth,
|
||||
element,
|
||||
});
|
||||
}, [columnCount, depth, ownerPlacementId, parentGridId, placements, registerGrid, rowCount, section.kind, sectionId]);
|
||||
}, [columnCount, depth, maxRowCount, ownerPlacementId, parentGridId, placements, registerGrid, sectionId]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const viewport = gridRef.current?.parentElement;
|
||||
@@ -824,7 +832,7 @@ export default function GridEditor({ sectionId, columnCount, rowCount, placement
|
||||
placement={placement}
|
||||
label={label}
|
||||
columnCount={columnCount}
|
||||
maxRowCount={section.kind === "container" ? rowCount : null}
|
||||
maxRowCount={maxRowCount}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -1044,6 +1052,7 @@ const GridResizeHandles = ({
|
||||
y: number;
|
||||
direction: GridResizeDirection;
|
||||
visualScale: { x: number; y: number };
|
||||
placement: SectionGridPlacement;
|
||||
} | null>(null);
|
||||
|
||||
const start = (direction: GridResizeDirection) =>
|
||||
@@ -1077,7 +1086,14 @@ const GridResizeHandles = ({
|
||||
cancelResize();
|
||||
return;
|
||||
}
|
||||
pointerStartRef.current = { id: event.pointerId, x: event.clientX, y: event.clientY, direction, visualScale };
|
||||
pointerStartRef.current = {
|
||||
id: event.pointerId,
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
direction,
|
||||
visualScale,
|
||||
placement: { ...placement },
|
||||
};
|
||||
};
|
||||
|
||||
const handlePointerMove = (event: ReactPointerEvent<HTMLSpanElement>) => {
|
||||
@@ -1095,14 +1111,14 @@ const GridResizeHandles = ({
|
||||
const valid = update(started.direction, snappedDelta.x, snappedDelta.y);
|
||||
setContinuousResize({
|
||||
placement: getContinuousResizePlacement({
|
||||
placement,
|
||||
placement: started.placement,
|
||||
direction: started.direction,
|
||||
deltaColumns: continuousDelta.x,
|
||||
deltaRows: continuousDelta.y,
|
||||
columnCount,
|
||||
maxRowCount,
|
||||
minWidth: placement.minW ?? 1,
|
||||
minHeight: placement.minH ?? 1,
|
||||
minWidth: started.placement.minW ?? 1,
|
||||
minHeight: started.placement.minH ?? 1,
|
||||
}),
|
||||
valid,
|
||||
});
|
||||
|
||||
@@ -150,6 +150,7 @@ export const SectionGrid = ({
|
||||
const minimumViewportRowCount = useMinimumViewportRowCount(section.kind === "empty", canvasScale);
|
||||
const contentRowCount = Math.max(1, getLayoutRowCount(displayPlacements));
|
||||
const rowCount = Math.max(contentRowCount, requestedRowCount, minimumViewportRowCount);
|
||||
const maxRowCount = section.kind === "container" || railPlacement !== "main" ? rowCount : null;
|
||||
const logicalWidth = getLogicalTrackSize(columnCount);
|
||||
const logicalHeight = getLogicalTrackSize(rowCount);
|
||||
const canvasAttributes = isEditMode
|
||||
@@ -164,7 +165,7 @@ export const SectionGrid = ({
|
||||
innerSections: displayedInnerSections,
|
||||
integrations,
|
||||
columnCount,
|
||||
maxRowCount: section.kind === "container" ? rowCount : null,
|
||||
maxRowCount,
|
||||
announce,
|
||||
}}
|
||||
>
|
||||
@@ -182,6 +183,7 @@ export const SectionGrid = ({
|
||||
sectionId={section.id}
|
||||
columnCount={columnCount}
|
||||
rowCount={rowCount}
|
||||
maxRowCount={maxRowCount}
|
||||
placements={displayPlacements}
|
||||
className={combineClasses("board-grid-editor", classes.editorGrid)}
|
||||
/>
|
||||
|
||||
+72
-1
@@ -95,7 +95,8 @@ describe("Board grid", () => {
|
||||
await expect(rail).toBeVisible();
|
||||
await expect(rail).toHaveAttribute("data-board-gutter", "left");
|
||||
await expect(railRootSection).toHaveAttribute("data-rail-placement", "left");
|
||||
await expect(rail).toHaveCSS("overflow-x", "hidden");
|
||||
await expect(rail).toHaveCSS("overflow-x", "clip");
|
||||
await expect(rail).toHaveCSS("overflow-y", "clip");
|
||||
const initialRailBox = await expectBoundingBoxAsync(rail);
|
||||
const initialRailRootBox = await expectBoundingBoxAsync(railRootSection);
|
||||
expect(initialRailRootBox.width).toBeCloseTo(initialRailBox.width, 1);
|
||||
@@ -240,6 +241,7 @@ describe("Board grid", () => {
|
||||
|
||||
const viewMainSectionBox = await expectBoundingBoxAsync(mainSection);
|
||||
const viewFirstItemBox = await expectBoundingBoxAsync(logicalTile);
|
||||
const viewScrollY = await page.evaluate(() => window.scrollY);
|
||||
runtimeResources.enterEditMode();
|
||||
await editToggle.click();
|
||||
await expect(page.locator(`[data-grid-runtime="${gridRuntimeMarker}"]`)).toHaveCount(3);
|
||||
@@ -285,6 +287,39 @@ describe("Board grid", () => {
|
||||
await page.mouse.up();
|
||||
await expect.poll(async () => await readEditorGridSnapshotAsync(railGrid)).toEqual(railSnapshot);
|
||||
await rail.locator('[data-e2e-rail-overflow-fixture="true"]').evaluate((element) => element.remove());
|
||||
await rail.evaluate((element) => {
|
||||
const overflowFixture = document.createElement("div");
|
||||
overflowFixture.dataset.e2eRailOverflowFixture = "true";
|
||||
overflowFixture.style.position = "absolute";
|
||||
overflowFixture.style.insetBlockStart = "100%";
|
||||
overflowFixture.style.width = "1px";
|
||||
overflowFixture.style.height = "500px";
|
||||
overflowFixture.style.pointerEvents = "none";
|
||||
element.appendChild(overflowFixture);
|
||||
});
|
||||
await expect
|
||||
.poll(async () => await rail.evaluate((element) => element.scrollHeight - element.clientHeight))
|
||||
.toBeGreaterThan(0);
|
||||
const fixedRailGridHeight = (await expectBoundingBoxAsync(railGrid)).height;
|
||||
const fixedRailHeight = (await expectBoundingBoxAsync(rail)).height;
|
||||
const verticalRailItemBox = await expectBoundingBoxAsync(railItem);
|
||||
await page.mouse.move(
|
||||
verticalRailItemBox.x + verticalRailItemBox.width / 2,
|
||||
verticalRailItemBox.y + verticalRailItemBox.height / 2,
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(verticalRailItemBox.x + verticalRailItemBox.width / 2 + 8, verticalRailItemBox.y);
|
||||
await expect(railItem).toHaveAttribute("data-dnd-drag-source", "true");
|
||||
await page.mouse.move(railBox.x + railBox.width / 2, railBox.y + railBox.height - 2, { steps: 12 });
|
||||
await page.waitForTimeout(250);
|
||||
expect(await rail.evaluate((element) => element.scrollTop)).toBe(0);
|
||||
expect((await expectBoundingBoxAsync(rail)).height).toBeCloseTo(fixedRailHeight, 1);
|
||||
expect((await expectBoundingBoxAsync(railGrid)).height).toBeCloseTo(fixedRailGridHeight, 1);
|
||||
await page.keyboard.press("Escape");
|
||||
await page.mouse.up();
|
||||
await expect.poll(async () => await readEditorGridSnapshotAsync(railGrid)).toEqual(railSnapshot);
|
||||
await rail.locator('[data-e2e-rail-overflow-fixture="true"]').evaluate((element) => element.remove());
|
||||
await page.evaluate((scrollY) => window.scrollTo({ top: scrollY, behavior: "auto" }), viewScrollY);
|
||||
await expect(logicalTile).toHaveCSS("overflow-x", "hidden");
|
||||
await expect(logicalTile).toHaveCSS("overflow-y", "auto");
|
||||
await logicalTile.evaluate((element) => {
|
||||
@@ -767,6 +802,42 @@ describe("Board grid", () => {
|
||||
await expect(eastResizeHandle).not.toHaveAttribute("data-grid-resize-disabled");
|
||||
await resizeLocatorByImmediateReleaseAsync(page, eastResizeHandle, logicalCellPitch * canvasScale, 0);
|
||||
await expect(containerSection).toHaveAttribute("data-grid-w", "2");
|
||||
await expectGridEntryGeometrySettledAsync(containerSection);
|
||||
await expect(eastResizeHandle).not.toHaveAttribute("data-grid-resize-disabled");
|
||||
|
||||
const expandingEastResizeBox = await expectBoundingBoxAsync(eastResizeHandle);
|
||||
const expandingEastResizeStart = {
|
||||
x: expandingEastResizeBox.x + expandingEastResizeBox.width / 2,
|
||||
y: expandingEastResizeBox.y + expandingEastResizeBox.height / 2,
|
||||
};
|
||||
const expandingContainerBox = await expectBoundingBoxAsync(containerSection);
|
||||
const eastHandleGrabOffset = expandingContainerBox.x + expandingContainerBox.width - expandingEastResizeStart.x;
|
||||
const continuousExpansion = logicalCellPitch * canvasScale * 1.2;
|
||||
await page.mouse.move(expandingEastResizeStart.x, expandingEastResizeStart.y);
|
||||
await page.mouse.down();
|
||||
await expect(page.locator("body")).toHaveAttribute("data-board-grid-interacting", "resize");
|
||||
await page.mouse.move(expandingEastResizeStart.x + continuousExpansion, expandingEastResizeStart.y, {
|
||||
steps: 12,
|
||||
});
|
||||
await expect(resizeOutline).toBeVisible();
|
||||
const expandedOutlineBox = await expectBoundingBoxAsync(resizeOutline);
|
||||
expect(expandedOutlineBox.x + expandedOutlineBox.width).toBeCloseTo(
|
||||
expandingEastResizeStart.x + continuousExpansion + eastHandleGrabOffset,
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
await resizeOutline.evaluate((element) => {
|
||||
const probe = document.createElement("span");
|
||||
probe.style.color = "var(--mantine-primary-color-filled)";
|
||||
element.appendChild(probe);
|
||||
const usesPrimaryColor = getComputedStyle(element).borderTopColor === getComputedStyle(probe).color;
|
||||
probe.remove();
|
||||
return usesPrimaryColor;
|
||||
}),
|
||||
).toBe(true);
|
||||
await page.keyboard.press("Escape");
|
||||
await page.mouse.up();
|
||||
await expect(containerSection).toHaveAttribute("data-grid-w", "2");
|
||||
|
||||
const northResizeHandle = containerSection.locator(':scope > [data-grid-resize-handle="n"]');
|
||||
await expectTargetSizeAsync(northResizeHandle);
|
||||
|
||||
Reference in new issue
Block a user