Revert "fix(resize): keep drag cursor consistent for table & chat resize (MUL…" (#5830)

This reverts commit 975cd49c3f.
This commit is contained in:
Naiyuan Qing
2026-07-23 16:30:37 +08:00
committed by GitHub
parent 975cd49c3f
commit 4bf17f5786
4 changed files with 24 additions and 191 deletions

View File

@@ -126,29 +126,27 @@ export function DataTable<TData>({
setResizingColumnId(header.column.id);
setColumnWidth(header, startWidth);
// Lock the resize cursor globally so it survives the pointer leaving the
// narrow handle; the matching rule lives in packages/ui/styles/base.css.
document.documentElement.setAttribute("data-table-resizing", "true");
const originalCursor = document.body.style.cursor;
const originalUserSelect = document.body.style.userSelect;
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
const handlePointerMove = (pointerEvent: PointerEvent) => {
setColumnWidth(header, startWidth + pointerEvent.clientX - startX);
};
// stopResize is idempotent; wire it to every drag-end path (including a
// window blur mid-drag) so the global resize-cursor lock can never strand.
const stopResize = () => {
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", stopResize);
window.removeEventListener("pointercancel", stopResize);
window.removeEventListener("blur", stopResize);
document.documentElement.removeAttribute("data-table-resizing");
document.body.style.cursor = originalCursor;
document.body.style.userSelect = originalUserSelect;
setResizingColumnId(null);
};
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", stopResize);
window.addEventListener("pointercancel", stopResize);
window.addEventListener("blur", stopResize);
},
[setColumnWidth],
);

View File

@@ -246,30 +246,6 @@ html:has(
user-select: none !important;
}
html[data-table-resizing="true"],
html[data-table-resizing="true"] * {
cursor: col-resize !important;
user-select: none !important;
}
html[data-chat-resizing="left"],
html[data-chat-resizing="left"] * {
cursor: col-resize !important;
user-select: none !important;
}
html[data-chat-resizing="top"],
html[data-chat-resizing="top"] * {
cursor: row-resize !important;
user-select: none !important;
}
html[data-chat-resizing="corner"],
html[data-chat-resizing="corner"] * {
cursor: nw-resize !important;
user-select: none !important;
}
[data-sidebar-resizing="true"] [data-slot="sidebar-gap"],
[data-sidebar-resizing="true"] [data-slot="sidebar-container"] {
transition: none !important;

View File

@@ -1,115 +0,0 @@
import { fireEvent, render } from "@testing-library/react";
import { useRef } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ChatResizeHandles } from "./chat-resize-handles";
import { useChatResize } from "./use-chat-resize";
// useChatResize only needs the size fields and setters from the chat store.
vi.mock("@multica/core/chat", () => {
const state = {
chatWidth: 360,
chatHeight: 480,
isExpanded: false,
setChatSize: vi.fn(),
setExpanded: vi.fn(),
};
const useChatStore = Object.assign(
(selector?: (s: typeof state) => unknown) =>
selector ? selector(state) : state,
{ getState: () => state },
);
return { CHAT_MIN_W: 320, CHAT_MIN_H: 400, useChatStore };
});
function Harness() {
const ref = useRef<HTMLDivElement>(null);
const { startDrag } = useChatResize(ref);
return (
<div ref={ref} style={{ position: "relative" }}>
<ChatResizeHandles onDragStart={startDrag} />
</div>
);
}
const POINTER_ID = 11;
function startLeftDrag(container: HTMLElement) {
// The left edge handle is the one with the col-resize cursor.
const handle = container.querySelector<HTMLElement>(".cursor-col-resize")!;
const setPointerCapture = vi.fn();
const releasePointerCapture = vi.fn();
// jsdom elements don't implement the pointer-capture API.
handle.setPointerCapture = setPointerCapture;
handle.hasPointerCapture = vi.fn(() => true);
handle.releasePointerCapture = releasePointerCapture;
fireEvent.pointerDown(handle, {
button: 0,
isPrimary: true,
pointerId: POINTER_ID,
clientX: 100,
clientY: 100,
});
return { handle, setPointerCapture, releasePointerCapture };
}
describe("chat resize cursor lock cleanup", () => {
beforeEach(() => {
document.documentElement.removeAttribute("data-chat-resizing");
});
afterEach(() => {
vi.restoreAllMocks();
document.documentElement.removeAttribute("data-chat-resizing");
});
it("locks the resize cursor on drag start and captures the pointer", () => {
const { container } = render(<Harness />);
const { setPointerCapture } = startLeftDrag(container);
expect(setPointerCapture).toHaveBeenCalledWith(POINTER_ID);
expect(document.documentElement).toHaveAttribute(
"data-chat-resizing",
"left",
);
});
it("clears the lock and releases capture on pointerup", () => {
const { container } = render(<Harness />);
const { releasePointerCapture } = startLeftDrag(container);
fireEvent.pointerUp(document, { pointerId: POINTER_ID });
expect(document.documentElement).not.toHaveAttribute("data-chat-resizing");
expect(releasePointerCapture).toHaveBeenCalledWith(POINTER_ID);
});
it("clears the lock on pointercancel (no pointerup)", () => {
const { container } = render(<Harness />);
startLeftDrag(container);
fireEvent.pointerCancel(document, { pointerId: POINTER_ID });
expect(document.documentElement).not.toHaveAttribute("data-chat-resizing");
});
it("clears the lock on lostpointercapture (no pointerup)", () => {
const { container } = render(<Harness />);
const { handle } = startLeftDrag(container);
fireEvent.lostPointerCapture(handle, { pointerId: POINTER_ID });
expect(document.documentElement).not.toHaveAttribute("data-chat-resizing");
});
it("clears the lock when the window loses focus mid-drag", () => {
const { container } = render(<Harness />);
startLeftDrag(container);
fireEvent.blur(window);
expect(document.documentElement).not.toHaveAttribute("data-chat-resizing");
});
});

View File

@@ -84,9 +84,7 @@ export function useChatResize(
const startDrag = useCallback(
(e: React.PointerEvent, dir: DragDir) => {
e.preventDefault();
const captureEl = e.currentTarget as HTMLElement;
const { pointerId } = e;
captureEl.setPointerCapture(pointerId);
(e.target as HTMLElement).setPointerCapture(e.pointerId);
dragRef.current = {
startX: e.clientX,
@@ -97,38 +95,9 @@ export function useChatResize(
};
setIsDragging(true);
// Lock the resize cursor globally so it survives the pointer leaving the
// narrow handle; per-direction rules live in packages/ui/styles/base.css.
document.documentElement.setAttribute("data-chat-resizing", dir);
// One idempotent cleanup wired to every way a pointer-capture drag can
// end. A drag started with setPointerCapture may terminate via
// pointercancel / lostpointercapture / window blur without a pointerup;
// missing any of those would strand data-chat-resizing and freeze the
// whole page in the resize cursor with text selection disabled.
let finished = false;
const finishDrag = () => {
if (finished) return;
finished = true;
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
document.removeEventListener("pointercancel", onPointerCancel);
captureEl.removeEventListener("lostpointercapture", onLostPointerCapture);
window.removeEventListener("blur", finishDrag);
dragRef.current = null;
setIsDragging(false);
document.documentElement.removeAttribute("data-chat-resizing");
if (captureEl.hasPointerCapture?.(pointerId)) {
captureEl.releasePointerCapture?.(pointerId);
}
};
const onPointerMove = (ev: PointerEvent) => {
const d = dragRef.current;
if (!d || ev.pointerId !== pointerId) return;
if (!d) return;
const { maxW: mw, maxH: mh } = boundsRef.current;
@@ -143,21 +112,26 @@ export function useChatResize(
setChatSize(clamp(rawW, CHAT_MIN_W, mw), clamp(rawH, CHAT_MIN_H, mh));
};
const onPointerUp = (ev: PointerEvent) => {
if (ev.pointerId === pointerId) finishDrag();
};
const onPointerCancel = (ev: PointerEvent) => {
if (ev.pointerId === pointerId) finishDrag();
};
const onLostPointerCapture = (ev: PointerEvent) => {
if (ev.pointerId === pointerId) finishDrag();
const onPointerUp = () => {
dragRef.current = null;
setIsDragging(false);
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
document.addEventListener("pointermove", onPointerMove);
document.addEventListener("pointerup", onPointerUp);
document.addEventListener("pointercancel", onPointerCancel);
captureEl.addEventListener("lostpointercapture", onLostPointerCapture);
window.addEventListener("blur", finishDrag);
const cursorMap: Record<DragDir, string> = {
left: "col-resize",
top: "row-resize",
corner: "nw-resize",
};
document.body.style.cursor = cursorMap[dir];
document.body.style.userSelect = "none";
},
[renderWidth, renderHeight, setChatSize],
);