根因:ResizeHandle 使用 document.addEventListener('mousemove') 监听拖拽,
当鼠标快速移到 iframe(HTML 预览)上方时,iframe 捕获了鼠标事件,
document 上的 mousemove 回调不再触发,导致宽度停止更新。
修复:将 mouse events 替换为 pointer events + setPointerCapture。
- onMouseDown → onPointerDown + setPointerCapture
- 在元素上直接监听 onPointerMove/onPointerUp(不再挂 document 事件)
- pointer capture 确保所有后续事件锁定到拖拽手柄,即使鼠标移到 iframe 上方
- 添加 touch-none CSS 防止触屏浏览器默认行为干扰拖拽
- 代码更简洁:不再需要手动 addEventListener/removeEventListener
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useRef } from "react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface ResizeHandleProps {
|
|
side: "left" | "right";
|
|
onResize: (delta: number) => void;
|
|
onResizeEnd?: () => void;
|
|
}
|
|
|
|
export function ResizeHandle({ side, onResize, onResizeEnd }: ResizeHandleProps) {
|
|
const isDragging = useRef(false);
|
|
const startX = useRef(0);
|
|
|
|
const handlePointerDown = useCallback(
|
|
(e: React.PointerEvent) => {
|
|
e.preventDefault();
|
|
isDragging.current = true;
|
|
startX.current = e.clientX;
|
|
// Capture pointer so all subsequent events route here, even over iframes
|
|
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
document.body.style.cursor = "col-resize";
|
|
document.body.style.userSelect = "none";
|
|
},
|
|
[]
|
|
);
|
|
|
|
const handlePointerMove = useCallback(
|
|
(e: React.PointerEvent) => {
|
|
if (!isDragging.current) return;
|
|
const delta = e.clientX - startX.current;
|
|
startX.current = e.clientX;
|
|
onResize(delta);
|
|
},
|
|
[onResize]
|
|
);
|
|
|
|
const handlePointerUp = useCallback(
|
|
(e: React.PointerEvent) => {
|
|
if (!isDragging.current) return;
|
|
isDragging.current = false;
|
|
(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
|
|
document.body.style.cursor = "";
|
|
document.body.style.userSelect = "";
|
|
onResizeEnd?.();
|
|
},
|
|
[onResizeEnd]
|
|
);
|
|
|
|
return (
|
|
<div
|
|
onPointerDown={handlePointerDown}
|
|
onPointerMove={handlePointerMove}
|
|
onPointerUp={handlePointerUp}
|
|
className={cn(
|
|
"group relative z-10 flex w-1 shrink-0 cursor-col-resize items-center justify-center touch-none",
|
|
side === "left" ? "-ml-0.5" : "-mr-0.5"
|
|
)}
|
|
>
|
|
<div className="h-full w-px bg-transparent transition-colors duration-150 group-hover:bg-border" />
|
|
</div>
|
|
);
|
|
}
|