按右键按下时的命中目标决定是否弹节点菜单
PreviewWorkspace.tsx 在右键按下时记住命中元素,抬起时改成按它解析节点与树:指针被视口捕获后 pointerup 的 target 是捕获元素,原先按抬起目标找不到 [data-node-id],节点菜单永远打不开 指针取消时同步清掉记录的目标 tests/previewRightPanDrag.test.tsx 补「抬起被重定向到捕获元素仍弹菜单」用例
This commit is contained in:
+16
-4
@@ -118,6 +118,9 @@ export function PreviewWorkspace({
|
||||
const didInitialFitRef = useRef(false);
|
||||
const panRef = useRef<Extract<DragState, { kind: 'pan' }> | null>(null);
|
||||
const rightPanRef = useRef<PreviewRightPanGesture | null>(null);
|
||||
// 右键按下时的命中元素:指针被视口捕获后,pointerup 的 target 会变成捕获元素,
|
||||
// 必须在按下时把真正的节点/树目标记下来。
|
||||
const rightPanTargetRef = useRef<EventTarget | null>(null);
|
||||
const rightPressSequenceRef = useRef(false);
|
||||
const treeDragRef = useRef<{
|
||||
treeId: string;
|
||||
@@ -422,6 +425,7 @@ export function PreviewWorkspace({
|
||||
event.preventDefault();
|
||||
event.currentTarget.focus({ preventScroll: true });
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
rightPanTargetRef.current = event.target;
|
||||
rightPanRef.current = createPreviewRightPanGesture({
|
||||
pointerId: event.pointerId,
|
||||
pointer: { x: event.clientX, y: event.clientY },
|
||||
@@ -468,8 +472,11 @@ export function PreviewWorkspace({
|
||||
);
|
||||
};
|
||||
|
||||
const openNodeContextMenuAt = (event: ReactPointerEvent<HTMLDivElement>) => {
|
||||
const target = resolveRightClickMenuTarget(event.target);
|
||||
const openNodeContextMenuAt = (
|
||||
event: ReactPointerEvent<HTMLDivElement>,
|
||||
pressedTarget: EventTarget | null,
|
||||
) => {
|
||||
const target = resolveRightClickMenuTarget(pressedTarget);
|
||||
if (!target) return;
|
||||
const layout = treeLayouts.find(
|
||||
(item) => item.tree.src_ui_design === target.treeId,
|
||||
@@ -501,7 +508,9 @@ export function PreviewWorkspace({
|
||||
),
|
||||
});
|
||||
if (release.viewport) setViewport(release.viewport);
|
||||
if (release.openNodeMenu) openNodeContextMenuAt(event);
|
||||
const pressedTarget = rightPanTargetRef.current;
|
||||
rightPanTargetRef.current = null;
|
||||
if (release.openNodeMenu) openNodeContextMenuAt(event, pressedTarget);
|
||||
return;
|
||||
}
|
||||
if (panRef.current?.pointerId !== event.pointerId) return;
|
||||
@@ -516,7 +525,10 @@ export function PreviewWorkspace({
|
||||
const cancelsRightPan =
|
||||
rightPan !== null && rightPan.pointerId === event.pointerId;
|
||||
const cancelsPan = pan !== null && pan.pointerId === event.pointerId;
|
||||
if (cancelsRightPan) rightPanRef.current = null;
|
||||
if (cancelsRightPan) {
|
||||
rightPanRef.current = null;
|
||||
rightPanTargetRef.current = null;
|
||||
}
|
||||
if (cancelsPan) panRef.current = null;
|
||||
if (cancelsRightPan || cancelsPan) {
|
||||
setIsPanning(false);
|
||||
|
||||
@@ -292,6 +292,28 @@ describe('PreviewWorkspace right-button pan', () => {
|
||||
expect(screen.getByRole('menuitem', { name: '新增同级节点' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('opens the node menu when pointerup is retargeted to the capture element', () => {
|
||||
const { canvas, child, preview } = renderPreview();
|
||||
|
||||
dispatchPointer(child, 'pointerdown', {
|
||||
button: 2,
|
||||
clientX: 100,
|
||||
clientY: 100,
|
||||
pointerId: 7,
|
||||
});
|
||||
// 指针被视口捕获后,浏览器把后续指针事件派发给捕获元素:抬起目标不再是节点,
|
||||
// 菜单必须按按下时的命中目标裁决。
|
||||
dispatchPointer(preview, 'pointerup', {
|
||||
button: 2,
|
||||
clientX: 101,
|
||||
clientY: 100,
|
||||
pointerId: 7,
|
||||
});
|
||||
|
||||
expect(canvas.selectNode).toHaveBeenCalledWith('child');
|
||||
expect(screen.getByRole('menu')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('keeps an empty-grid right click silent, unlike the left button', () => {
|
||||
const { canvas, preview } = renderPreview();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user