Skip to content

[bug] 画布:节点右键菜单打开后拖动画布,菜单无法关闭 #5

Closed

Description

@allendyu

问题与影响

选择节点后右键弹出上下文菜单,此时若按住鼠标左键拖动画布(平移视图),菜单不会关闭,并且在这之后继续点击空白处也无法关闭它,形成"消除不掉的悬浮菜单"。

菜单会一直遮挡画布,用户只能通过点选某一菜单项或执行删除等破坏性操作来让它消失,无法单纯取消。由于菜单覆盖在画布上方,还会挡住下方节点的点击与拖拽区域。

复现步骤

  1. 在画布上选中一个节点。
  2. 在该节点上右键,弹出节点上下文菜单(重命名 / 添加下游节点 / 复制 …)。
  3. 不点任何菜单项,按住鼠标左键在画布空白处拖动画布。
  4. 松开左键后,继续单击画布空白处。

预期与实际

  • 预期:开始拖动画布时应关闭上下文菜单(与单击空白处关闭的行为一致);至少在任何一次点击空白处后必须关闭。
  • 实际:拖动画布后菜单保持显示;之后单击空白处也不再关闭。菜单持续悬浮,遮挡画布。

定位与证据

菜单打开路径:节点右键走 actions.contextMenu(src/canvas/CanvasWorkspace.tsx:3075-3088),写入 menu 状态时只设置 nodeId,不设置 connected:

contextMenu: (id: string, event: React.MouseEvent) => {
  event.preventDefault();
  event.stopPropagation();
  if (!selected.includes(id)) setSelected([id]);
  setMenu({ x: event.clientX, y: event.clientY, world: ..., nodeId: id });
},

已确认的缺陷一:唯一的"点击外部关闭"监听器只服务于连线菜单

CanvasWorkspace.tsx:3203-3214 注册了 pointerdown 外部关闭逻辑,但被 menu?.connected 前置条件拦截,节点菜单永远不会注册该监听:

useEffect(() => {
  if (!menu?.connected) return;          // ← 节点菜单在此直接返回
  ...
  const close = (event: PointerEvent) => {
    if (!(event.target as Element).closest(".cw-context-menu")) setMenu(null);
  };
  document.addEventListener("pointerdown", close);
  return () => document.removeEventListener("pointerdown", close);
}, [menu?.connected]);

我已在仓库内检索确认:整个 CanvasWorkspace.tsx 中 document.addEventListener("pointerdown" | "mousedown" | "keydown") 仅此一处,因此节点菜单没有其他外部点击关闭路径。

已确认的缺陷二:画布空白处点击被拖拽手势提前拦截

onPaneClick(CanvasWorkspace.tsx:3455-3462)在判定为拖拽手势时直接 return,跳过了 setMenu(null):

onPaneClick={(event) => {
  if (panning || pointerGesture.current.blocksClick(1)) return;   // ← 拖拽后在此返回
  root.current?.focus({ preventScroll: true });
  setMenu(null);
  ...
}}

而 blocksClick(src/canvas/pointerGesture.ts:61-65)在指针位移超过 4px 时即返回 true。我用仓库内的 CanvasPointerGesture 做了隔离验证:

拖动 50px 后 release  -> blocksClick(1) = true   (onPaneClick 提前 return,setMenu(null) 不执行)
原地点击(无位移)    -> blocksClick(1) = false  (能正常关闭菜单)

这与用户观察到的"拖完关不掉、之后点空白也关不掉"一致——只要那一次手势被判为拖拽,关闭逻辑就被整体跳过。

已确认的缺陷三:键盘 Escape 对节点菜单同样失效

CanvasWorkspace.tsx:3157-3164 的菜单键盘处理同样以 menu?.connected 为前提,节点菜单按 Escape 不会关闭:

onKeyDown={(event) => {
  if (!menu?.connected) return;     // ← 节点菜单在此直接返回
  if (event.key === "Escape") { ... setMenu(null); return; }

(全局 onWorkspaceKey(CanvasWorkspace.tsx:2801-2819)在焦点位于画布内时也会 setMenu(null),因此 Escape 在焦点未转移到菜单时可能仍然有效;但一旦焦点落在菜单/其他面板内,这条兜底就被 :2791-2796 的排除列表拦截。此分支为代码阅读推断,未在浏览器中逐步确认。)

关键点:三处关闭路径都以 menu?.connected 为条件,而节点上下文菜单恰恰不满足该条件。CanvasWorkspace.tsx:3156 也体现了同样的区分:

className={menu?.connected ? "cw-canvas-menu" : undefined}

复现层级:用户现象来自真实界面操作(截图,展示节点上下文菜单悬浮于画布之上);blocksClick 在拖拽后返回 true 已通过隔离脚本验证;"节点菜单未注册外部点击监听"与"Escape 被 menu?.connected 拦截"通过源码阅读与全文件检索确认,未在浏览器中逐条触发验证。

验收标准

  • 节点上下文菜单打开后,在画布空白处按住左键拖动,菜单在拖拽开始时(或结束时)关闭。
  • 节点上下文菜单打开后,任意一次单击画布空白处都会关闭它,不受该次手势是否被判为拖拽影响。
  • 节点上下文菜单打开后,按 Escape 可关闭。
  • 回归:连线/端口菜单(menu.connected)原有的外部点击关闭、Escape 关闭、方向键导航行为不变。
  • 回归:拖拽画布本身仍正常平移,不因本次修复而被误判为点击而清空当前选中。

节点右键菜单打开后拖动画布,菜单仍悬浮且无法关闭

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:canvasCanvas, nodes and direct manipulationtype:bugSomething is broken

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions