Repository navigation
Commit bede9de
feat(plugin-grid): right-clicking a list row opens its actions at the pointer; a row in a multi-row selection opens the bulk actions (#28457)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuF5qzfjTC1rAZK19osv9GA3)_
domain:ui#5 · os-sales · session_01L6bhgUp9U9hou7ccEhNS13
Fixes #28434
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- **改了什么**:列表行右键在指针处打开该行的操作菜单(Open、Open in new tab,再加上与 ⋯
同一解析器、同一谓词/权限门/顺序/确认的条目);在 2 行以上选区内右键,则打开选区的批量操作(与 `BulkActionBar`
同源)或「N selected + Clear」。
- **为什么改**:行操作此前只能点右侧小小的 ⋯ 按钮;右键是用户已有的习惯,且不新增任何声明,只是同一组已声明操作的第二个入口。
- **风险与回滚**:链接、编辑中的单元格、输入框、选中文字、触屏长按仍走浏览器原生菜单;`DataTableSchema` 新增运行时槽位
`renderRowContextMenu`(函数,zod 镜像按名拒绝作者写入),不动 spec、不加翻译键。回滚即 revert 本 PR。
- **先看**:`ui/packages/components/src/renderers/complex/data-table.tsx` 的
`openRowContextMenu` / `keepsBrowserContextMenu`
与菜单外壳;`ui/packages/plugin-grid/src/components/RowContextMenu.tsx`;`ObjectGrid.tsx`
的 `renderRowContextMenu`。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/plugin-grid/src/__tests__/rowContextMenu-28434.test.tsx
packages/components/src/renderers/complex/__tests__/data-table-row-context-menu-28434.test.tsx`
→ 23 个用例全绿。
- **需要你做的**:无,席位落地。
## What changes
- **One resolver per menu, read twice.** `RowActionMenu` is split into
`useRowActionPlan` (capability gate, switch-off, `planRowActionMenu`)
and `RowActionMenuItems`; the "⋮" cell and the context menu both render
from them. `BulkActionBar`'s gating moves into `useBulkActionItems`,
which the bar and the selection menu both read.
`objectGridActionsColumn` exposes the exact props the "⋮" cell renders
with (`rowActionMenuProps(row)`), so the context menu gets the same
handlers and therefore the same confirm dialogs.
- **`data-table` owns the menu.** A controlled, non-modal Radix
`DropdownMenu` anchored at the pointer (or below the focused cell for
the context-menu key / Shift+F10), mounted only while open. Esc and a
chosen item return focus to the cell it opened from. It keeps the
browser's menu on `a[href]`, inputs / contenteditable, a cell in edit
mode (`data-editing-cell`), a text selection inside the row, a touch
long-press, and portalled menus/dialogs; and when there is nothing to
offer.
- **New runtime slot `DataTableSchema.renderRowContextMenu(row, {
selected })`.** The host returns `DropdownMenuItem`s or `null` (browser
menu). The zod mirror refuses an authored value by name
(`handlerKeyRefusal`, `runtime-slot`), recorded in the KnownDrift ledger
(header pin 51 / 89).
- **`ObjectGrid` supplies the items.** A row in a selection of two or
more gets `SelectionContextMenuItems` (count, bulk actions, Clear); any
other row gets `RowContextMenuItems` (Open when the row click opens
something, Open in new tab when the host publishes a record URL through
`useRelatedRecordActions().recordHref`, then the row's actions).
- **Related-list tables** (no host renderer) get `data-table`'s own
menu: Open (the row click), then exactly that row's "⋮" items from the
extracted `useDataTableRowMenuPlan`. `plugin-detail` is not edited.
## Tests
- New `plugin-grid/src/__tests__/rowContextMenu-28434.test.tsx` (17), on
the real `ObjectGrid` with real `ActionProvider` and a related-record
host: per-row parity with the "⋮" menu (a row whose `visible` hides an
action lacks it in both), the inline primary listed, Open / Open in new
tab href, a custom action dispatching to its endpoint, no-host control;
selection branch (bulk defs + count 3, no bulk → count + Clear and Clear
clears, a row outside the selection and a selection of one get the row
menu); native exceptions (link, edit cell, text selection, touch) with
controls; Shift+F10, the context-menu key, Esc returns focus.
- New `components/.../data-table-row-context-menu-28434.test.tsx` (6):
built-in related-list menu parity and a predicate removing an item from
both, Open = row click, a 2+ selection and a table with no row menu keep
the browser's menu; host renderer decides alone, `null` keeps the
browser's menu, keyboard + focus return.
- Ablations via `scripts/ablation-replace.mjs`, each under
`os-verify-lock` and restored to blob == HEAD with `git diff HEAD`
empty: link exception removed (1 failed), touch exception removed (1
failed), focus return removed (1 failed), selection branch removed (2
failed), inline budget 0 → 1 (1 failed), built-in selection guard
removed (1 failed).
- `vitest run packages/plugin-grid/ packages/types/
packages/components/src/renderers/complex/`: 687 files, 13003 tests
passed. After merging `origin/main`: `packages/types/` + the two new
files + `ObjectGrid.declaredKeys-11068`: 388 files, 10475 passed.
## Screenshots (1440, app-crm Accounts)
Evidence commit `438798bb` (removed again in the next commit, so nothing
reaches `main`).
- Row menu, same items and order as the row's "⋮" (Edit, Delete): 
- Three rows selected, right-click on one: count, the bar's bulk Delete,
Clear: 
Read live in the same run: "⋮" items `["Edit","Delete"]`, context menu
`["Open","Open in new tab","Edit","Delete"]`, selection menu `3 selected
/ Delete / Clear`.
## Acceptance notes
- **Deviation (approved by the seat in the claim amendment 6546007567):
inline primary actions are listed.** The "⋮" cell shows up to one
primary action inline beside the trigger; a menu has no width budget, so
the context menu lists every action the cell offers, primaries first
where `planRowActionMenu` places them. On a row with an inline primary
the context menu therefore has one more item than the "⋮" popup, never a
different one.
- **Related-list tables have no Open in new tab and no selection
branch.** `data-table`'s built-in menu has no record URL to open (the
related-list host ignores the modifier payload) and no bulk actions, so
a row inside a selection of two or more keeps the browser's menu there.
- **Clause-②: yes (widening)** per the claim amendment:
`DataTableSchema` gains an optional runtime slot. No `packages/spec`
file is touched; i18n reuses `table.open`, `detail.openInNewTab`,
`grid.bulkSelected`, `grid.bulkClear` (present in all ten packs).
- A full-suite run first caught a regression in the byte ruler
(`ObjectGrid.declaredKeys-11068`): an always-mounted closed menu drew a
Radix trigger whose generated id differed between two identical renders.
The menu is now mounted only while open; the ruler passes.
## Verification
Local readings at head `8a537fb0` (merge of `origin/main` `776c1d87`;
source unchanged since `da55dc39` apart from the merge).
- `dispatch-gates --commands`: 35 derived, each run with its exit code
captured; all 35 exit 0. `--ran`: "35 derived, 35 run, 0 NOT-MEASURED, 0
UNRUN".
- `node scripts/check-sdui-manifest.mjs`: exit 0. `eslint` on each
changed file: 0 errors.
- `type-check` (tsc --noEmit + tsconfig.test.json) for
`@object-ui/types`, `@object-ui/components`, `@object-ui/plugin-grid`:
exit 0. Full turbo build: exit 0.
- No open PR touches any of these files (REST file lists of the open
PRs; `git merge-tree` where a merge base exists).
- Branch name is the harness-assigned `claude/project-thread-e7cfqb`.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes on the changed files.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to generated references, changelogs or release
notes.
- [x] 8. Title, body and comments are in English (the maintainer summary
is in Chinese by the seat's standing order).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgUp9U9hou7ccEhNS13)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgUp9U9hou7ccEhNS13
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 55a1feb commit bede9de
12 files changed
Lines changed: 1415 additions & 257 deletions
File tree
- .changeset
- ui/packages
- components/src/renderers/complex
- __tests__
- plugin-grid/src
- __tests__
- components
- types/src
- __tests__
- zod
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
0 commit comments