Repository navigation
Commit 9844bbf
fix(components,plugin-list): inline edit marks a row modified only on a real change, draws one Actions column, and the Edit inline toggle reports aria-pressed (objectui#11816) (#11896)
Fixes #11816
Clause-②: no
Grid inline edit, the three defects the card measured on showcase Tasks
→ *Edit inline*:
## 1. A row is modified only by a real change
**Mechanism (measured).** `data-table`'s `saveEdit` and `stageEdit`
staged every committed value into `pendingChanges`. A cell opened and
left without a change still commits: a built-in editor commits on blur,
and a host-injected editor (the `@object-ui/fields` widget `ObjectGrid`
hands in) commits on the document pointerdown listener's click-outside.
So "1 row modified · Cancel All · Save All (1)" appeared for a row
nobody had changed.
**Fix.** Both staging paths now go through one pure helper,
`stageCellChange`. It stages the value only when `isUnchangedCellValue`
says it differs from the value the row loaded with. Otherwise it removes
the cell's entry, and the row's entry once no cell on it is staged. That
last step is what makes staging the loaded value back un-mark the row.
The equality rule covers only the round trips the editors make:
- the emptiness floor, `isEmptyValue` from `@object-ui/core` (`null`,
`undefined`, `''`, `[]`). `startEdit` seeds a `null` cell's editor with
`''`;
- a number against its decimal string. The built-in number editor stores
the input's string;
- a multi-value set in another order. `MultiSelectField`'s toggle
appends;
- two objects that serialize alike.
Nothing else is equal. The failure direction is asymmetric: calling a
change "unchanged" would drop an edit, as `isSameStoredValue` in
`@object-ui/plugin-form` also says of its own rule. The ref-based
staging is kept as it was: the `editingCellRef` re-entry guard, the
`editRevertRef` snapshot, and `stageEdit`'s functional update.
**PM hypothesis partly falsified: "a picklist code against its label".**
No producer puts a label in the loaded row. The showcase seeds store
codes (`priority: 'high'`). `SingleSelectField` and `MultiSelectField`
hand back `option.value`. The select cell renders by resolving the code.
The table also holds no options it could map a label through. Equating a
label with a code would be a consumer-side tolerance, so it is not
implemented.
## 2. One "Actions" column (the choice: a distinct header, not a merge)
**Mechanism (measured).** With inline edit on, `ObjectGrid` draws its
own row-menu column (`_actions`, headed by `grid.actions`). It also
passes `rowActions: true` to `data-table`, whose trailing column, headed
by `common.actions`, only ever holds a modified row's cancel/save pair
there. The result is two "Actions" headers. The pin below counts 2 on
the base. The second header was a bare string in the cell's larger
default type, while every data-column label sits in a `text-xs` muted
span.
**Choice: a distinct, consistently styled header.** When the trailing
column can only hold that pair, `data-table` heads it with a pencil
glyph. The column can only hold the pair when the table is editable, has
`onRowSave` or `onBatchSave`, and is given none of the menu inputs
`DataTableRowActionsMenu` reads (`onRowEdit`, `onRowDelete`, a non-empty
`rowActionDefs` with `onRowActionDef`). The pencil is the one the *Edit
inline* toggle carries, with the accessible name and tooltip
`table.edit` ("Edit", an existing key in every pack). A column that can
hold a row menu keeps "Actions", now in the data-column header type.
Why not merge:
- One column would need `ObjectGrid`'s `_actions` cell to render the
table's per-row save/cancel, or the table to render `ObjectGrid`'s
`RowActionMenu`. Either needs a new seam: a third `TableColumn.cell`
argument, an exported context, or a column-placement key. That is a
published-contract change, which the claim's stop conditions route to a
re-claim with `Clause-②: yes`.
- A text header would need a new `packages/i18n` key, the other stop
condition.
- Dropping the per-row save/cancel pair would remove an existing
affordance without a ruling.
## 3. The *Edit inline* toggle reports its state
The toggle is `ListView`'s toolbar button
(`data-testid="toolbar-inline-edit-toggle"`) in
`@object-ui/plugin-list`, not in `ObjectGrid`. It now sets
`aria-pressed` from the inline-edit state. The compact-toolbar entry in
`ViewSettingsPopover` is a native checkbox and already exposes its
state.
## Files, and the claim's surface
- In the claim:
`packages/components/src/renderers/complex/data-table.tsx`, its test
`packages/components/src/__tests__/data-table-noop-edit-11816.test.tsx`,
`.changeset/11816-inline-edit-noop.md`.
- **Outside the claim's list, landed on the producer side as the
dispatch directs:** `packages/plugin-list/src/ListView.tsx` (one
attribute) and one new case in
`packages/plugin-list/src/__tests__/ListView.test.tsx`. The toggle lives
there; `ObjectGrid.tsx` renders no toggle. Also new:
`packages/plugin-grid/src/__tests__/inlineEditNoopAndHeader-11816.test.tsx`,
a test only. It drives the real `ObjectGrid` with the real
`@object-ui/fields` select, the card's own shape.
- `packages/plugin-grid/src/ObjectGrid.tsx` is not edited, and nothing
in the claim's "Not on it" list is touched.
- No export, prop, type member or `packages/i18n` key is added:
`stageCellChange`, `isUnchangedCellValue` and `isSameCellScalar` are
module-private.
- Patch changeset for `@object-ui/components` and
`@object-ui/plugin-list`. `@object-ui/plugin-grid` gets no entry,
because only a test file changed there.
## Gates (final head `4bdd909`, except where a row names `bdbf14a`;
`bdbf14a`..`4bdd909` restores one dropped space in `data-table.tsx`)
- `pnpm exec vitest run
packages/components/src/__tests__/data-table-noop-edit-11816.test.tsx
packages/plugin-grid/src/__tests__/inlineEditNoopAndHeader-11816.test.tsx
packages/plugin-list/src/__tests__/ListView.test.tsx` at `4bdd909`:
`Test Files 3 passed (3)` · `Tests 160 passed (160)`.
- `pnpm --filter @object-ui/components type-check` at `4bdd909`: exit 0,
with the script name echoed. `pnpm --filter @object-ui/plugin-list
type-check` and `pnpm --filter @object-ui/plugin-grid type-check` at
`bdbf14a`: exit 0. Each package's `tsconfig.test.json` program lists the
touched test file (`tsc --listFilesOnly`, one hit each).
- Dependency closure, built first at `bdbf14a`: `pnpm
--workspace-concurrency=2 --filter '@object-ui/components^...' --filter
'@object-ui/plugin-list^...' --filter '@object-ui/plugin-grid^...'
build`, exit 0 (`Scope: 14 of 47 workspace projects`).
- The three packages' full suites at `bdbf14a`: `pnpm exec vitest run
--maxWorkers=2 packages/components/ packages/plugin-list/
packages/plugin-grid/`, giving `Test Files 671 passed | 1 skipped (672)`
· `Tests 6740 passed | 24 skipped (6764)`.
- `node scripts/check-changeset-presence.mjs`: `✅ 5 source file(s) of 3
released package(s) changed, and this change declares 1 changeset(s):
.changeset/11816-inline-edit-noop.md.`
- `pnpm check:new-line-citations`: `VERDICT
new-cross-file-line-citations: 0 new citation(s), enforcement
report-only -> exit 0`.
- `pnpm check:control-bytes`: `✅ check-control-bytes: OK`.
- `pnpm check:i18n-keys`: exit 0. `table.edit` resolves against the en
pack.
- `pnpm check:component-surface-parity`: `REPORT-ONLY -- exit 0`. No
`data-table` input or registration changed.
- `pnpm exec eslint --format json` on the 5 changed `.ts`/`.tsx` files:
5 files, 0 errors. This narrowing is a measurement, for three reasons:
- its population is the config's `**/*.{ts,tsx}` blocks applied to the
changed files;
- `eslint.config.js` sets no `parserOptions.project` or
`projectService`, so linting is per-file and this diff cannot move a
verdict on an untouched file;
- the three modified files carry the same warning counts as at `cef0eee`
(34, 188, 26). The two new test files carry `no-explicit-any` warnings,
as their siblings do.
**Reverse verification** (one-shot, committed first). `data-table.tsx`
and `ListView.tsx` were written back to `cef0eee`. The on-disk markers
read `stageCellChange` 0, `rowActionsColumnIsEditOnly` 0 and
`aria-pressed={inlineEdit}` 0. The same three test files then ran:
`Tests 12 failed | 148 passed (160)`. Every new pin goes red, except the
control "a column that can hold a row menu stays Actions", which is
green on both trees. The `ObjectGrid` pin reads `expected [ th, th ] to
have a length of 1 but got 2`. The files were restored with `git
checkout HEAD --`: both blob hashes match `HEAD` and `git diff HEAD` is
empty. Tests resolve `@object-ui/*` to `src` (`vitest.config.mts`
aliases), so no build sat between the edit and the run.
NOT MEASURED: a real-browser pass on the console against a showcase
backend; the repo-wide `pnpm lint` and `pnpm test` (CI).
## Acceptance notes (observed, not filed)
- `onCellChange` still fires on every commit, a no-op commit included.
Only the staging is gated, because gating the callback the same way
could leave a host that tracked an earlier staged value stale. Carrier:
none.
- Two equality rules now exist for "did this value change":
`isSameStoredValue` (plugin-form: `''` is not blank, numbers never equal
strings) and `isUnchangedCellValue` here. Each matches its own round
trip; unifying them is a design question. Carrier: none.
- Read from source, not reproduced: `SingleSelectField`'s cascade clear
compares the stored value with option codes exactly, while the select
cell resolves a case-variant code. A stored `Referral` against a
declared `referral` would be cleared to `null` when the editor mounts,
and that is staged, now as a real change. Carrier: none.
- The per-row cancel/save buttons carry English `title` strings ("Cancel
changes", "Save row") outside the language packs, unchanged here.
Carrier: none.
Session: `https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU`
---
_Generated by [Claude
Code](https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a476dee commit 9844bbf
7 files changed
Lines changed: 511 additions & 20 deletions
File tree
- .changeset
- content/docs/plugins
- packages
- components/src
- __tests__
- renderers/complex
- plugin-grid/src/__tests__
- plugin-list/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
776 | 776 | | |
777 | 777 | | |
778 | 778 | | |
779 | | - | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
780 | 782 | | |
781 | 783 | | |
782 | 784 | | |
783 | 785 | | |
784 | 786 | | |
785 | | - | |
| 787 | + | |
| 788 | + | |
786 | 789 | | |
787 | 790 | | |
788 | 791 | | |
| |||
| 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 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
0 commit comments