Skip to content

导航与菜单:menubar 菜单栏 / 右键菜单 ​

稳定 · 弹层

自绘菜单栏 + 下拉菜单 + 子菜单。支持互斥展开、点击外部关闭、键盘导航,菜单项可挂全局快捷键。

元素Prop说明
menubar—普通容器:高度固定 26px,横向排列子项
menubar子节点<menu> 标题与任意其它元素(如右侧的 status 文本)
menulabel菜单标题(顶级)或多个菜单项的容器(子菜单)
menu子节点<menuitem> / <separator>
menuitemlabel菜单项文本
menuitemshortcut快捷键文本,如 "Ctrl+S"。显示在右侧,并全应用生效(无需展开菜单)
menuitemdisabled灰字,点了没反应
menuitemonClick点击触发;快捷键命中时收到 {x, y, shortcut}
js
<column>
  <menubar>
    <menu label="File">
      <menuitem label="New"  shortcut="Ctrl+N" onClick={() => say("New")} />
      <menuitem label="Open" shortcut="Ctrl+O" onClick={() => say("Open")} />
      <separator />
      <menuitem label="Save As" disabled={true} onClick={() => say("不该被触发")} />
    </menu>

    <menu label="View">
      <menuitem label="Zoom In" onClick={() => say("Zoom In")} />
      <!-- 子菜单:在 menuitem 里再嵌一个 menu -->
      <menuitem label="Theme">
        <menu>
          <menuitem label="Dark"  onClick={() => say("Dark")} />
          <menuitem label="Light" onClick={() => say("Light")} />
        </menu>
      </menuitem>
    </menu>

    <text>ready</text>    {/* menubar 是普通容器,右侧放什么都行 */}
  </menubar>

  <column gap={12} padding={16}>
    {/* 窗口主体内容 */}
  </column>
</column>

快捷键规则 ​

快捷键表在 Go 侧(Pump 层)匹配 —— 因为 JS 看不到已被输入框消费的按键。规则:

  • 只认带 Ctrl / Alt 的组合。shortcut="S" 这种裸字母键不会被受理(否则全应用都打不出 s)。
  • 修饰键全等比较:Ctrl+S 不会被 Ctrl+Shift+S 触发。
  • Cmd 自动归一到 Ctrl。
  • 菜单项没有 onClick 也可以挂 shortcut,只是命中后无事发生。

键盘导航 ​

焦点在菜单栏时:← / → 在标题间循环切换(换过去就展开), ↓ / Enter / Space 展开, Esc 收起当前层。

  • 无鼠标滑过切换标题、无悬停自动展开子菜单、无勾选项(checked menu item)。
  • 分隔线本身也能命中(命中测试只认带处理器的节点,所以每一行都挂了内置处理器)。

openContextMenu(x, y, items) ​

稳定 · 弹层 · 同步 API

在指定坐标就地弹出右键菜单。这是数据式 API,不是 contextMenu prop —— 理由见下方提示。

参数类型说明
x, ynumber弹出的屏幕坐标,通常来自 onContextMenu 的 e.x/e.y
itemsarray<menuitem> / <separator> 元素数组,与菜单栏里的写法完全一致
js
import { h, render, openContextMenu } from "gx/gfx";

<rect
  width={340}
  height={140}
  background="#e8eef7"
  onContextMenu={(e) =>
    openContextMenu(e.x, e.y, [
      <menuitem label="Copy"    shortcut="Ctrl+C" onClick={() => say("Copy")} />
      <menuitem label="Paste"   shortcut="Ctrl+V" onClick={() => say("Paste")} />
      <separator />
      <menuitem label="Inspect" onClick={() => say("Inspect")} />
    ])
  }
/>

为什么不做成 prop

JSX 元素是单次挂载的对象,一个节点只有一个 Parent 字段。 把同一个 <menu> 做成 prop 挂到多处会互相争抢宿主。做成"每次调用现构造一份"的数据式 API 就没有这个问题。

行为:靠近屏幕右下角时会自动向左上翻折保证整块可见;点选项或点外部关闭;在菜单上再点右键会被吞掉(不会误关)。

<tabs> / <tab> ​

稳定 · 容器

选项卡:顶部标签条(自绘,激活项主题绿 + 下划线)+ 内容区。<tab> 直接堆在 <tabs> 下即为页,页内容按 column 语义竖排(多元素不必再包一层容器)。

元素Prop说明
tabsvalue存在即受控:激活页 = value(数字,越界自动钳位),点击标签条不改内部状态,只派发 onChange 等脚本写回 signal —— 不回写就是切不动(受控的定义)
tabsonChange切页时收到 {index, title},受控/非受控都派发
tabtitle标签条文字,缺省兜底 Tab N
tab子节点页内容;全部页都是 keep-alive 的 —— 节点留树,输入框打的字、滚动位置切走再回来都还在,非激活页只是不布局、不绘制、不命中
js
import { createSignal } from "gx/solid";
import { h, render } from "gx/gfx";

const [tab, setTab] = createSignal(0);

<tabs value={() => tab()} onChange={(e) => setTab(e.index)}>
  <tab title="文件">
    <input width={240} placeholder="切走再回来,打的字还在" />
  </tab>
  <tab title="编辑">
    <text>编辑页</text>
  </tab>
</tabs>

keep-alive 与条件渲染的区别

<tabs> 的页语义与 show 指令、路由 keepAlive 是同一套哲学:隐藏 ≠ 销毁。想要"每次切来都全新构建",请在 value 驱动下用条件渲染自己写。

Apache License 2.0 · 用 ❤ 和 Go 编写