fix(client): drop the tooltip from row context-menu buttons

Session and workspace row ellipsis buttons gained a label tooltip when they
moved to the shared contextMenu.button factory. Keep the tooltip only on the
compact toolbar variant.

Co-authored-by: Yifffan <[email protected]>
This commit is contained in:
TurtleandYifffan committed 2026-11-04 18:32:25 +08:00
1 parent 50b159af26
commit a5b0afda88
6 files changed
+16 -8

No files matched your search

@@ -15,8 +15,8 @@
en: d152484eb41ac6b4
zh: 725b47765ee8f30b
/deepseek-ai-dsh-client-ui-context-menu/use-this-package:
en: 122f4cbfda90c063
zh: f88536b7641e445b
en: 892cfb46cfcddcc0
zh: c534b44e7ef53522
/deepseek-ai-dsh-client-ui-context-menu/use-this-package/register-actions:
en: 11bea09005de2678
zh: d56e25d2d93022aa
+1 -1
View File
@@ -29,7 +29,7 @@ The Web application bundle mounts this Client plugin. External Client plugins de
The plugin injects `useContextMenu` into every Slot scope through `ctx.slots.provideRoot`. Call `const { getTriggerProps } = useContextMenu()` once in the component and spread `getTriggerProps(target, handlers?)` onto each trigger. The factory is an ordinary function and can run in loops. It handles right-click, Shift+F10, the ContextMenu key, viewport coordinates, and focus without scrolling. Existing handlers run first; `preventDefault()` keeps their ownership and prevents duplicate handling by composed or nested bindings. Repeated, composing, and unrelated key events remain untouched. Pass `undefined` to retain native behavior, or a target resolver to read business state when the gesture begins. The resolved object is captured before focus changes. Plain children receive event props or a factory through props or an existing delegate; no additional React Context is needed. Special editors and canvases can call `ctx.contextMenus.open()` directly.
Use `renderFactorySlot('contextMenu.button', { target, label, className?, size? })` for an ellipsis trigger. `label` is localized by the caller; `size: 'sm'` selects the compact toolbar button, while omission retains caller-styled row geometry. The factory owns focus, pointer and click isolation, the bottom-edge anchor, and expanded state. Each rendered button toggles only the popup it opened: activating it again closes that popup even after the button moves or its target's display fields change, while another button for an equal target opens its own popup. Repeated right-click and keyboard gestures keep the 4-pixel rule. Target families need no identity rule here. It hides only when the target kind has no registered contribution, without resolving actions early. The stable `ctx.contextMenus.registeredKinds` observable publishes registration and withdrawal immediately, including providers activated after the button mounts. A registered provider returning no actions still makes its button available.
Use `renderFactorySlot('contextMenu.button', { target, label, className?, size? })` for an ellipsis trigger. `label` is localized by the caller; `size: 'sm'` selects the compact toolbar button with a label tooltip, while omission retains caller-styled row geometry without a tooltip. The factory owns focus, pointer and click isolation, the bottom-edge anchor, and expanded state. Each rendered button toggles only the popup it opened: activating it again closes that popup even after the button moves or its target's display fields change, while another button for an equal target opens its own popup. Repeated right-click and keyboard gestures keep the 4-pixel rule. Target families need no identity rule here. It hides only when the target kind has no registered contribution, without resolving actions early. The stable `ctx.contextMenus.registeredKinds` observable publishes registration and withdrawal immediately, including providers activated after the button mounts. A registered provider returning no actions still makes its button available.
### Register actions
+1 -1
View File
@@ -29,7 +29,7 @@ Web 应用 bundle 挂载此客户端插件。外部客户端插件在 `dsh.clien
插件通过 `ctx.slots.provideRoot` 向每个 Slot 作用域注入 `useContextMenu`。组件调用一次 `const { getTriggerProps } = useContextMenu()`,再将 `getTriggerProps(target, handlers?)` 展开到各个触发元素上。该工厂是普通函数,可以在循环中调用;它统一处理右键、Shift+F10、ContextMenu 键、视口坐标及无滚动聚焦。已有处理器先运行,`preventDefault()` 保留其处理权,并防止组合或嵌套绑定重复处理。重复、输入法组合及无关按键保持原状。传入 `undefined` 保留原生行为,也可传入目标解析函数,在手势开始时读取业务状态。解析后的对象会在焦点变化前捕获。普通子组件通过 props 或现有 delegate 接收事件 props 或工厂,无需额外 React Context。特殊编辑器和 Canvas 可直接调用 `ctx.contextMenus.open()`。
省略号触发器使用 `renderFactorySlot('contextMenu.button', { target, label, className?, size? })`。`label` 由调用方本地化;`size: 'sm'` 选择紧凑工具栏按钮,省略时保留调用方设置的行按钮尺寸。工厂统一管理焦点、指针和点击隔离、下边缘锚点与展开状态。每个已渲染的按钮只切换自己打开的弹出菜单:按钮移动或目标展示字段变化后,再次激活仍会关闭该菜单;另一个目标相同的按钮则打开自己的菜单。重复的右键和键盘手势仍沿用 4 像素规则。目标类别无需在此声明身份规则。仅当目标类别没有注册贡献时隐藏按钮,不提前解析操作。稳定的 `ctx.contextMenus.registeredKinds` observable 即时发布注册和注销,包括按钮挂载后才激活的提供方。已注册提供方即使返回空操作,按钮仍可用。
省略号触发器使用 `renderFactorySlot('contextMenu.button', { target, label, className?, size? })`。`label` 由调用方本地化;`size: 'sm'` 选择带标签提示的紧凑工具栏按钮,省略时保留调用方设置的行按钮尺寸,不显示提示。工厂统一管理焦点、指针和点击隔离、下边缘锚点与展开状态。每个已渲染的按钮只切换自己打开的弹出菜单:按钮移动或目标展示字段变化后,再次激活仍会关闭该菜单;另一个目标相同的按钮则打开自己的菜单。重复的右键和键盘手势仍沿用 4 像素规则。目标类别无需在此声明身份规则。仅当目标类别没有注册贡献时隐藏按钮,不提前解析操作。稳定的 `ctx.contextMenus.registeredKinds` observable 即时发布注册和注销,包括按钮挂载后才激活的提供方。已注册提供方即使返回空操作,按钮仍可用。
### 注册操作
@@ -46,9 +46,8 @@ export function ContextMenuButton({ target, label, className, size, useOpener, u
toggle(id, target, { x: rect.left, y: rect.bottom })
},
}
if (size !== 'sm') return <button {...attributes} type="button"><IconEllipsisOutlineRegular /></button>
return <Tooltip label={label} side="bottom" align="end" portal>
{size === 'sm'
? <Button {...attributes} size="sm" icon={<IconEllipsisOutlineRegular />} />
: <button {...attributes} type="button"><IconEllipsisOutlineRegular /></button>}
<Button {...attributes} size="sm" icon={<IconEllipsisOutlineRegular />} />
</Tooltip>
}
@@ -49,7 +49,7 @@ export interface ContextMenuButtonProps {
readonly target: ContextMenuTarget
readonly label: string
readonly className?: string | undefined
/** Use the compact toolbar control; omission preserves caller-styled row button geometry. */
/** Use the compact toolbar control with a label tooltip; omission preserves caller-styled row button geometry without a tooltip. */
readonly size?: 'sm'
}
@@ -47,6 +47,15 @@ it('shows late registered kinds without preparing actions and withdraws them wit
})) } })
expect(view.getByRole('button', { name: 'Header menu' }).getAttribute('aria-haspopup')).toBe('menu')
expect(resolve).not.toHaveBeenCalled()
vi.useFakeTimers()
fireEvent.pointerEnter(view.getByRole('button', { name: 'Row menu' }))
fireEvent.focus(view.getByRole('button', { name: 'Row menu' }))
await act(() => vi.advanceTimersByTimeAsync(1000))
expect(view.queryByRole('tooltip')).toBeNull()
fireEvent.focus(view.getByRole('button', { name: 'Header menu' }))
await act(() => vi.advanceTimersByTimeAsync(1000))
expect(view.getByRole('tooltip').textContent).toBe('Header menu')
vi.useRealTimers()
const source = runtime.ctx.contextMenus.registeredKinds
await contribution.dispose()
expect(view.queryByRole('button', { name: 'Header menu' })).toBeNull()