fix(layout): keep panel resizing smooth across plugin iframes

This commit is contained in:
t8y2 committed 2026-09-15 12:03:11 +08:00
1 parent c4952adb98
commit a9cf5822fa
8 files changed
+77 -31

No files matched your search

+35 -12
View File
@@ -25,6 +25,12 @@ function rect(left: number, width: number): DOMRect {
} as DOMRect;
}
function pointerEvent(type: string, clientX: number, pointerId = 1): MouseEvent {
const event = new MouseEvent(type, { bubbles: true, clientX });
Object.defineProperty(event, "pointerId", { value: pointerId });
return event;
}
describe("usePanelResize", () => {
beforeEach(() => {
vi.stubGlobal("localStorage", localStorageMock);
@@ -49,10 +55,10 @@ describe("usePanelResize", () => {
vi.spyOn(panel, "getBoundingClientRect").mockReturnValue(rect(1000, 360));
const { aiPanelWidth, startAiPanelResize } = usePanelResize();
handle.addEventListener("mousedown", startAiPanelResize);
handle.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, clientX: 1000 }));
document.dispatchEvent(new MouseEvent("mousemove", { clientX: -1000 }));
document.dispatchEvent(new MouseEvent("mouseup"));
handle.addEventListener("pointerdown", startAiPanelResize);
handle.dispatchEvent(pointerEvent("pointerdown", 1000));
document.dispatchEvent(pointerEvent("pointermove", -1000));
document.dispatchEvent(pointerEvent("pointerup", -1000));
expect(aiPanelWidth.value).toBe(1060);
expect(localStorage.getItem("dbx-ai-panel-width")).toBe("1060");
@@ -71,10 +77,10 @@ describe("usePanelResize", () => {
vi.spyOn(panel, "getBoundingClientRect").mockReturnValue(rect(300, 700));
const { aiPanelWidth, startAiPanelResize } = usePanelResize();
handle.addEventListener("mousedown", startAiPanelResize);
handle.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, clientX: 300 }));
document.dispatchEvent(new MouseEvent("mousemove", { clientX: 400 }));
document.dispatchEvent(new MouseEvent("mouseup"));
handle.addEventListener("pointerdown", startAiPanelResize);
handle.dispatchEvent(pointerEvent("pointerdown", 300));
document.dispatchEvent(pointerEvent("pointermove", 400));
document.dispatchEvent(pointerEvent("pointerup", 400));
expect(aiPanelWidth.value).toBe(600);
expect(localStorage.getItem("dbx-ai-panel-width")).toBe("600");
@@ -89,15 +95,32 @@ describe("usePanelResize", () => {
vi.spyOn(panel, "getBoundingClientRect").mockReturnValue(rect(0, 260));
const { sidebarWidth, startSidebarResize } = usePanelResize();
handle.addEventListener("mousedown", startSidebarResize);
handle.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, clientX: 260 }));
document.dispatchEvent(new MouseEvent("mousemove", { clientX: 0 }));
document.dispatchEvent(new MouseEvent("mouseup"));
handle.addEventListener("pointerdown", startSidebarResize);
handle.dispatchEvent(pointerEvent("pointerdown", 260));
document.dispatchEvent(pointerEvent("pointermove", 0));
expect(panel.style.width).toBe("240px");
document.dispatchEvent(pointerEvent("pointerup", 0));
expect(sidebarWidth.value).toBe(240);
expect(localStorage.getItem("dbx-sidebar-width")).toBe("240");
});
it("removes the drag overlay when the window loses focus", () => {
const panel = document.createElement("div");
const handle = document.createElement("div");
panel.append(handle);
document.body.append(panel);
const { startSidebarResize } = usePanelResize();
handle.addEventListener("pointerdown", startSidebarResize);
handle.dispatchEvent(pointerEvent("pointerdown", 260));
expect(document.body.querySelector('[aria-hidden="true"]')).not.toBeNull();
window.dispatchEvent(new Event("blur"));
expect(document.body.querySelector('[aria-hidden="true"]')).toBeNull();
});
it("raises persisted panel widths below the toolbar-safe minimum", () => {
localStorage.setItem("dbx-sidebar-width", "180");
localStorage.setItem("dbx-ai-panel-width", "200");
+30 -8
View File
@@ -29,30 +29,52 @@ export function usePanelResize() {
const tabBarCollapsed = ref(safeLocalStorageGet("dbx-tab-bar-collapsed") === "true");
function startPanelResize(widthRef: Ref<number>, storageKey: string, direction: "left" | "right", maxWidth: PanelMaxWidth = DEFAULT_PANEL_MAX_WIDTH) {
return (e: MouseEvent) => {
return (e: PointerEvent) => {
e.preventDefault();
const startX = e.clientX;
const resizeHandle = e.currentTarget as HTMLElement | null;
resizeHandle?.setPointerCapture(e.pointerId);
const resizeOverlay = document.createElement("div");
resizeOverlay.setAttribute("aria-hidden", "true");
Object.assign(resizeOverlay.style, {
position: "fixed",
inset: "0",
zIndex: "2147483647",
cursor: "col-resize",
userSelect: "none",
touchAction: "none",
});
document.body.append(resizeOverlay);
const resolvedMaxWidth = typeof maxWidth === "function" ? maxWidth(resizeHandle) : maxWidth;
const upperBound = Number.isFinite(resolvedMaxWidth) ? Math.max(PANEL_MIN_WIDTH, resolvedMaxWidth) : DEFAULT_PANEL_MAX_WIDTH;
const renderedWidth = resizeHandle?.parentElement?.getBoundingClientRect().width;
const requestedStartWidth = typeof renderedWidth === "number" && Number.isFinite(renderedWidth) && renderedWidth > 0 ? renderedWidth : widthRef.value;
const startWidth = Math.max(PANEL_MIN_WIDTH, Math.min(upperBound, requestedStartWidth));
widthRef.value = startWidth;
const panelElement = resizeHandle?.parentElement;
let currentWidth = startWidth;
const onMouseMove = (ev: MouseEvent) => {
const onPointerMove = (ev: PointerEvent) => {
const delta = ev.clientX - startX;
widthRef.value = Math.max(PANEL_MIN_WIDTH, Math.min(upperBound, startWidth + (direction === "right" ? delta : -delta)));
currentWidth = Math.max(PANEL_MIN_WIDTH, Math.min(upperBound, startWidth + (direction === "right" ? delta : -delta)));
panelElement?.style.setProperty("width", `${currentWidth}px`);
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
const finishResize = () => {
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", finishResize);
document.removeEventListener("pointercancel", finishResize);
window.removeEventListener("blur", finishResize);
if (resizeHandle?.hasPointerCapture(e.pointerId)) resizeHandle.releasePointerCapture(e.pointerId);
resizeOverlay.remove();
widthRef.value = currentWidth;
safeLocalStorageSet(storageKey, String(widthRef.value));
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
document.addEventListener("pointermove", onPointerMove);
document.addEventListener("pointerup", finishResize);
document.addEventListener("pointercancel", finishResize);
window.addEventListener("blur", finishResize, { once: true });
};
}