From e28d239aeb75ee5982889b1f5805348bd3db2b33 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 7 Aug 2026 10:33:28 +0800 Subject: [PATCH 1/2] fix(vtable): emit context menu click event Expose a typed event for ContextMenuPlugin menu item clicks. Consumers can observe plugin actions after built-in handling completes. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --- docs/assets/guide/en/plugin/context-menu.md | 47 ++++++++++ docs/assets/guide/zh/plugin/context-menu.md | 47 ++++++++++ .../context-menu/handle-menu-helper.test.ts | 39 +++++++++ .../issue-4655-context-menu-click.ts | 85 +++++++++++++++++++ packages/vtable-plugins/demo/menu.ts | 4 + packages/vtable-plugins/src/context-menu.ts | 14 +++ packages/vtable/src/core/TABLE_EVENT_TYPE.ts | 5 ++ packages/vtable/src/ts-types/events.ts | 4 +- packages/vtable/src/ts-types/menu.ts | 13 +++ 9 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 packages/vtable-plugins/demo/context-menu/issue-4655-context-menu-click.ts diff --git a/docs/assets/guide/en/plugin/context-menu.md b/docs/assets/guide/en/plugin/context-menu.md index 28bb38173b..43ebf5803f 100644 --- a/docs/assets/guide/en/plugin/context-menu.md +++ b/docs/assets/guide/en/plugin/context-menu.md @@ -11,6 +11,7 @@ ContextMenu provides rich right-click menu features for VTable tables, supportin - Support custom menu items and separators - Support menu item grouping and submenus - Support custom menu item click callbacks +- Support listening to menu item click events with menu item and cell position details - Support menu item icons and shortcut hints - Support numeric input in menu items (e.g., insert multiple rows/columns) @@ -132,6 +133,48 @@ const contextMenuPlugin = new ContextMenuPlugin({ }); ``` +## Listen to Menu Item Click Events + +If you need to observe user actions after a right-click menu item is clicked, listen to the `context_menu_click` event fired by `ContextMenuPlugin`. This event is only fired after the right-click menu plugin is installed and enabled. It is fired after the plugin finishes handling the menu item, so built-in operations such as freeze, insert, and delete have already taken effect. + +```typescript +import * as VTable from '@visactor/vtable'; +import { ContextMenuPlugin } from '@visactor/vtable-plugins'; + +const contextMenuPlugin = new ContextMenuPlugin(); + +const tableInstance = new VTable.ListTable({ + container: document.getElementById('container'), + columns, + records, + plugins: [contextMenuPlugin] +}); + +tableInstance.on(VTable.TABLE_EVENT_TYPE.CONTEXT_MENU_CLICK, args => { + const { col, row, contextMenu } = args; + + console.log('Context menu click position:', col, row); + console.log('Menu item:', contextMenu.menuKey, contextMenu.menuText); + + if (contextMenu.menuKey === 'freeze_to_this_row_and_column') { + console.log('Current frozen counts:', tableInstance.frozenRowCount, tableInstance.frozenColCount); + } +}); +``` + +Event argument description: + +| Parameter | Type | Description | +| --- | --- | --- | +| `col` | number | Column index of the menu trigger position; `-1` when there is no corresponding cell | +| `row` | number | Row index of the menu trigger position; `-1` when there is no corresponding cell | +| `contextMenu.menuKey` | string | Unique key of the clicked menu item | +| `contextMenu.menuText` | string | Display text of the clicked menu item | +| `contextMenu.rowIndex` | number | Row index of the menu trigger position | +| `contextMenu.colIndex` | number | Column index of the menu trigger position | +| `contextMenu.cellValue` | any | Cell value at the menu trigger position | +| `contextMenu.inputValue` | number \| string | Input value of an input menu item | + ## Complete Example Here is a complete example showing how to create a table with right-click menu functionality: @@ -244,6 +287,10 @@ const generateTestData = (count) => { // Create table instance const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + + tableInstance.on(VTable.TABLE_EVENT_TYPE.CONTEXT_MENU_CLICK, args => { + console.log('Context menu item clicked:', args.contextMenu); + }); ``` diff --git a/docs/assets/guide/zh/plugin/context-menu.md b/docs/assets/guide/zh/plugin/context-menu.md index 63289ec6f9..10f23b5fb9 100644 --- a/docs/assets/guide/zh/plugin/context-menu.md +++ b/docs/assets/guide/zh/plugin/context-menu.md @@ -11,6 +11,7 @@ - 支持自定义菜单项和分隔线 - 支持菜单项分组和子菜单 - 支持自定义菜单项点击回调 +- 支持监听菜单项点击事件,获取菜单项和单元格位置信息 - 支持菜单项图标和快捷键提示 - 支持菜单项中的数字输入(如插入多行/列) @@ -132,6 +133,48 @@ const contextMenuPlugin = new ContextMenuPlugin({ }); ``` +## 监听菜单项点击事件 + +如果需要在右键菜单项点击后统一感知用户操作,可以监听 `ContextMenuPlugin` 触发的 `context_menu_click` 事件。该事件只有安装并启用右键菜单插件后才会触发,并且会在插件完成菜单项处理后触发,因此内置的冻结、插入、删除等菜单操作已经生效。 + +```typescript +import * as VTable from '@visactor/vtable'; +import { ContextMenuPlugin } from '@visactor/vtable-plugins'; + +const contextMenuPlugin = new ContextMenuPlugin(); + +const tableInstance = new VTable.ListTable({ + container: document.getElementById('container'), + columns, + records, + plugins: [contextMenuPlugin] +}); + +tableInstance.on(VTable.TABLE_EVENT_TYPE.CONTEXT_MENU_CLICK, args => { + const { col, row, contextMenu } = args; + + console.log('右键菜单点击位置:', col, row); + console.log('菜单项信息:', contextMenu.menuKey, contextMenu.menuText); + + if (contextMenu.menuKey === 'freeze_to_this_row_and_column') { + console.log('当前冻结行列数:', tableInstance.frozenRowCount, tableInstance.frozenColCount); + } +}); +``` + +事件参数说明: + +| 参数 | 类型 | 描述 | +| --- | --- | --- | +| `col` | number | 菜单触发位置对应的列号;没有对应单元格时为 `-1` | +| `row` | number | 菜单触发位置对应的行号;没有对应单元格时为 `-1` | +| `contextMenu.menuKey` | string | 被点击菜单项的唯一标识 | +| `contextMenu.menuText` | string | 被点击菜单项的展示文本 | +| `contextMenu.rowIndex` | number | 菜单触发位置对应的行号 | +| `contextMenu.colIndex` | number | 菜单触发位置对应的列号 | +| `contextMenu.cellValue` | any | 菜单触发位置对应的单元格值 | +| `contextMenu.inputValue` | number \| string | 输入型菜单项中的输入值 | + ## 完整示例 以下是一个完整的示例,展示了如何创建具有右键菜单功能的表格: @@ -244,6 +287,10 @@ const generateTestData = (count) => { // 创建表格实例 const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + + tableInstance.on(VTable.TABLE_EVENT_TYPE.CONTEXT_MENU_CLICK, args => { + console.log('右键菜单项点击:', args.contextMenu); + }); ``` diff --git a/packages/vtable-plugins/__tests__/context-menu/handle-menu-helper.test.ts b/packages/vtable-plugins/__tests__/context-menu/handle-menu-helper.test.ts index b9579f9562..1e4f639506 100644 --- a/packages/vtable-plugins/__tests__/context-menu/handle-menu-helper.test.ts +++ b/packages/vtable-plugins/__tests__/context-menu/handle-menu-helper.test.ts @@ -134,6 +134,45 @@ describe('Context menu canvas option', () => { ); }); + test('ContextMenuPlugin emits context_menu_click after menu item click', () => { + const container = createDiv(); + const contextMenuPlugin = new ContextMenuPlugin(); + + table = new ListTable({ + container, + columns: [{ field: 'id', title: 'ID' }], + records: [{ id: 1 }], + plugins: [contextMenuPlugin] + }); + + const fireListeners = jest.spyOn(table, 'fireListeners'); + + contextMenuPlugin['handleMenuClickCallback']( + { + menuKey: 'freeze_to_this_row_and_column', + menuText: '冻结到本行本列', + rowIndex: 1, + colIndex: 0, + cellValue: 1 + }, + table + ); + + expect(table.frozenRowCount).toBe(2); + expect(table.frozenColCount).toBe(1); + expect(fireListeners).toHaveBeenCalledWith(ListTable.EVENT_TYPE.CONTEXT_MENU_CLICK, { + col: 0, + row: 1, + contextMenu: { + menuKey: 'freeze_to_this_row_and_column', + menuText: '冻结到本行本列', + rowIndex: 1, + colIndex: 0, + cellValue: 1 + } + }); + }); + test('ContextMenuPlugin init uses new options when added through updateOption', () => { const container = createDiv(); const contextMenuPlugin = new ContextMenuPlugin({ diff --git a/packages/vtable-plugins/demo/context-menu/issue-4655-context-menu-click.ts b/packages/vtable-plugins/demo/context-menu/issue-4655-context-menu-click.ts new file mode 100644 index 0000000000..cf5f5934ce --- /dev/null +++ b/packages/vtable-plugins/demo/context-menu/issue-4655-context-menu-click.ts @@ -0,0 +1,85 @@ +import * as VTable from '@visactor/vtable'; +import { ContextMenuPlugin } from '../../src/context-menu'; + +const CONTAINER_ID = 'vTable'; + +function createRecords() { + return Array.from({ length: 12 }, (_, index) => ({ + id: index + 1, + name: `name-${index + 1}`, + city: ['Beijing', 'Shanghai', 'Shenzhen'][index % 3], + value: Math.round((index + 1) * 12.6) + })); +} + +function updateStatus(table: VTable.ListTable, status: HTMLElement, eventArgs?: any) { + const lines = [ + `frozenRowCount: ${table.frozenRowCount}`, + `frozenColCount: ${table.frozenColCount}`, + eventArgs + ? `last context_menu_click: ${JSON.stringify(eventArgs.contextMenu)}` + : 'last context_menu_click: waiting for menu click' + ]; + status.textContent = lines.join('\n'); +} + +export function createTableInstance(status: HTMLElement) { + const plugin = new ContextMenuPlugin(); + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records: createRecords(), + columns: [ + { field: 'id', title: 'ID', width: 80 }, + { field: 'name', title: 'Name', width: 160 }, + { field: 'city', title: 'City', width: 160 }, + { field: 'value', title: 'Value', width: 120 } + ], + defaultRowHeight: 40, + defaultHeaderRowHeight: 40, + widthMode: 'standard', + heightMode: 'standard', + plugins: [plugin] + }; + + const tableInstance = new VTable.ListTable(option); + tableInstance.on(VTable.TABLE_EVENT_TYPE.CONTEXT_MENU_CLICK, args => { + updateStatus(tableInstance, status, args); + // eslint-disable-next-line no-console + console.log('context_menu_click', args); + }); + + updateStatus(tableInstance, status); + window.tableInstance = tableInstance; + return tableInstance; +} + +export function createTable() { + const info = document.createElement('div'); + info.style.margin = '10px'; + info.style.padding = '10px'; + info.style.border = '1px solid #ddd'; + info.style.borderRadius = '4px'; + info.style.backgroundColor = '#f9f9f9'; + info.innerHTML = ` +
Right-click a body cell, open the freeze submenu, then click freeze or unfreeze.
+The table should emit context_menu_click with menu details in contextMenu.