Skip to content

<fmb-context-menu>

右键菜单。对当前选中的节点执行 Isolate / Hide / Show all / Copy reference 等动作,并把动作以事件形式通知宿主。组件只负责菜单本身——何时出现、出现在哪由宿主控制(经 x / y 定位、hidden 显隐)。

最小用法

html
<fmb-viewer-context id="ctx">
  <div id="viewport"><canvas id="canvas"></canvas></div>
  <!-- 初始隐藏,由宿主在 contextmenu 事件里定位并显示 -->
  <fmb-context-menu id="ctxmenu" hidden></fmb-context-menu>
</fmb-viewer-context>

属性

名称类型property / attribute默认值说明
viewerViewer | undefined仅 property(attribute: false)undefined通常经 <fmb-viewer-context> 注入
xnumberproperty + attribute(type: Number)0菜单左上角视口 X 坐标(px),写入 :hostleft
ynumberproperty + attribute(type: Number)0菜单左上角视口 Y 坐标(px),写入 :hosttop

显隐用 HTML 全局 hidden 属性即可(组件未自定义)。

事件

事件detail冒泡触发时机
fmb-ctx-action{ action: string; nodeIds: number[] }bubbles: true, composed: true点击 Isolate / Hide / Show all / Copy reference / Properties 时;action"isolate" | "hide" | "showAll" | "copy" | "properties",nodeIds 是动作针对的选中节点 id
fmb-ctx-closebubbles: true, composed: true任意菜单项执行完毕后(宿主据此隐藏菜单)

行为细节

  • 菜单项(固定清单):Isolate(I)、Hide(H)、Show all、Make transparent(禁用占位)、Measure from…(禁用占位)、Copy reference(⌘C)、Properties…(⌥↵)。快捷键仅为展示文案,组件不注册键盘监听。
  • 动作对象:执行时从 viewer.selection.getSelection() 取 Node 类型条目的 id 集合;选中为空时除 Show all 外的条目全部禁用(Show all 不依赖选中集,其 fmb-ctx-action 带空 nodeIds 发出)。
  • Hide:model.setNodesVisibility(ids, false)
  • Isolate:经共享 helper isolateZoom.isolate——model.isolateNodes(ids) 只留选中子树(装配自动展开,其余叶子隐藏),首次 isolate 保存相机位姿,然后 fit 到保留集。
  • Show all:经 isolateZoom.showAll——model.resetNodesVisibility() 全部复显;有保存的 isolate 前相机位姿则还原并清除。
  • Copy reference:把选中节点名称(逗号连接)写入剪贴板(navigator.clipboard,失败静默)。
  • Properties:组件自身不做任何事,只发 fmb-ctx-action——打开属性面板等响应由宿主实现。
  • 定位::hostposition: fixed(z-index: 1000),x / y 在挂载与每次更新时写入 left / top;坐标语义是视口坐标(配合 contextmenu 事件的 clientX/Y)。
  • 每个动作执行后都会发 fmb-ctx-close;菜单不自带外点/Escape 关闭逻辑,需宿主接线(见下)。

宿主接线示例

最小接线如下:右键打开并把坐标 clamp 进视口,外点 / Escape / fmb-ctx-close 关闭(同一份逻辑在完整集成示例的页面上下文里也有展示)。viewer-ui 另导出功能更全的共享接线 wireContextMenu(@fmb/viewer 阅读器实际所用),在此之上还做右键目标解析(资源管理器惯例:命中已选中者保持、未选中者替换选择、空白清空不弹)与右键拖拽分离:

ts
type MenuEl = HTMLElement & { x: number; y: number };

function clampMenuPosition(x: number, y: number, menuW: number, menuH: number, vw: number, vh: number) {
  return { x: Math.max(0, Math.min(x, vw - menuW)), y: Math.max(0, Math.min(y, vh - menuH)) };
}

export function wireMenu(triggers: Element[], menu: MenuEl): void {
  const open = (e: Event) => {
    const me = e as MouseEvent;
    e.preventDefault();
    const pos = clampMenuPosition(me.clientX, me.clientY, 220, 290, window.innerWidth, window.innerHeight);
    menu.x = pos.x;
    menu.y = pos.y;
    menu.hidden = false;
  };
  const close = () => {
    menu.hidden = true;
  };
  for (const t of triggers) t.addEventListener("contextmenu", open);
  document.addEventListener("click", (e) => {
    if (!menu.contains(e.target as Node)) close();
  });
  document.addEventListener("keydown", (e) => {
    if ((e as KeyboardEvent).key === "Escape") close();
  });
  menu.addEventListener("fmb-ctx-close", close);
}

// 视口与结构树都可唤出菜单
wireMenu([viewportEl, treeEl], document.getElementById("ctxmenu") as MenuEl);

可定制点

菜单表面/边框/圆角/阴影走 --fmb-surface--fmb-border--fmb-radius-lg--fmb-shadow-lg;hover 行底色走 --fmb-accent;文字走 --fmb-fg-1(禁用项 --fmb-fg-4,快捷键 --fmb-fg-3 + --fmb-mono)。全表见主题定制