Skip to content

feat(data-table): inline cell editing - #560

Open
itsprade wants to merge 12 commits into
mainfrom
feat/core/1750-datatable-inline-edit
Open

itsprade wants to merge 12 commits into
mainfrom
feat/core/1750-datatable-inline-edit

Conversation

@itsprade

@itsprade itsprade commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR adds inline cell editing to DataTable for every typed column, together with the decision record that proposes it (decisions/data-table-inline-editing.md). It's still a draft for team input: the "For the call" questions in the decision doc are open. Date and badge editing, first planned as a follow-up PR, are folded in, so the whole feature can be tried and reviewed at once.

Why

  • The Larson IMS team can't move about a dozen AG-Grid tables to DataTable without it. They're quantity-entry screens (POs, receipts, invoices, stock adjustments) that need typing into cells with rules, plus editability decided per row.
  • tailor-inc/platform-planning#1428 asks for one standard version.
  • tailor-inc/platform-planning#1750 asks whether this should be a first-class feature.

What's in it

An edit config on typed columns:

Column Editor
text, number, money, link Typed into
text / link with edit.options Dropdown
badge Dropdown of the same badges (choices from badgeLabelMap)
date Calendar; dateFormat: "datetime" adds a time field
  • Rules: min, max, maxDecimals and required, plus validate(value, row) for any rule of the screen's own. Date columns take min / max days.
    • Characters that can never be valid are blocked as the user types.
    • Other errors give the cell a red outline and a tooltip. Enter and Tab are blocked until the value is fixed, Esc reverts, and leaving an invalid cell reverts it.
  • Per row: canEdit(row, { selected }).
  • Keyboard: Enter moves down the column; Tab moves to the next editable cell, skipping read-only ones; Enter / Space / ↓ opens a dropdown or calendar. Japanese IME input is respected.
  • Saving: a value saves when the user leaves the cell, presses Enter or Tab, or picks a choice. If onCommit returns a promise, the cell keeps the new value while it's pending and reverts if it rejects. There's no saving indicator.
  • Look: spreadsheet-style, with no input boxes; the whole cell is the click target.
    • Cursors: a text cursor over typing cells, a pointer over dropdown and date cells, "not allowed" over read-only cells (unless rows are clickable).
    • The edited cell gets an inset primary ring, and an invalid one a destructive ring.
    • Dropdown and date icons appear on hover and focus, in a 20px space kept free at the right edge. Read-only rows of the column keep the space too.
    • Row height and column width never change while editing.
column({
  id: "received",
  label: "Received",
  type: "number",
  edit: {
    canEdit: (_row, { selected }) => selected,
    min: 0,
    maxDecimals: 0, // whole numbers only
    validate: (value, row) =>
      value !== null && value > row.ordered ? `Can't exceed ${row.ordered}` : undefined,
    onCommit: (row, value) => updateLine(row.id, { received: value }),
  },
});

Compatibility

  • No API change beyond columns: useDataTable options and DataTableContextValue are unchanged, and columns without edit behave as before.
  • Display fixes that apply to every table:
    • A date-only "YYYY-MM-DD" value shows that day in every time zone. It rendered a day early west of UTC.
    • Number and money cells show up to edit.maxDecimals decimals, and a maxDecimals below minDecimals no longer throws.
  • Shared Select: its dropdown gets the same 4px inner padding as Menu, Combobox and Autocomplete, so the highlighted option no longer touches the edges. It's a separate commit with its own patch changeset, easy to split out if preferred.

Also included

  • Docs: a new "Inline editing" section, written in docs-src/ and regenerated with pnpm docs:sync.
  • The list-dense-scan pattern: its "no inline editable cells" rule is replaced with guidance.
  • A 17-column goods-receipt demo in the DataTable lab.
  • Changesets: minor (inline editing) and patch (Select padding).

Not in this PR

  • A bring-your-own editor, and a boolean column type
  • Editing multi-value badge cells (they stay read-only)
  • Multi-cell paste, fill-down and undo
  • Moving between cells with the arrow keys
  • Comma decimal separators
  • Possible follow-ups if teams ask: an async validate for server checks with an in-cell message, and schema-based rules (e.g. Zod)

Try it

pnpm dev, then open /showcase/data-table-lab → Inline editing:

  • Tick a row to edit its Received quantity: whole numbers, never more than ordered.
  • Discount % is limited to 0–100 with one decimal; Weight takes three decimals, up to 500.
  • A unit price over 10,000 is rejected by the pretend server, and the cell reverts.
  • Supplier (pending lines only) and Warehouse are dropdowns, Status and QC are badge dropdowns, and Expected, Received at (with time) and Best before (Oct 2026–Dec 2027) are dates.

Test plan

  • pnpm type-check, pnpm lint, pnpm test (1,901 core tests, 82 of them new), check-dts, pnpm fmt:check, pnpm docs:check
  • Browser (vite example):
    • blocked keys, error tooltips, Enter blocked while invalid, and Esc revert
    • Enter and Tab movement
    • a failed autosave reverting the cell and showing a toast
    • dropdown, badge and date editors, bounded calendars, and date-time Done / Clear
    • icons never covering values, and row height and column width unchanged as rows become editable
  • Team decisions on the "For the call" questions in the decision doc

For reviewers

The open questions, with details in the decision doc:

  • Naming: edit / canEdit / onCommit, or Base UI's onValueCommitted wording?
  • Leaving an invalid cell reverts it (AG-Grid's default). Keep it?
  • Enter moves down, spreadsheet-style. OK?
  • One PR: review date and badge editing together with the rest, or split them back out?
  • #1115: ship the simple pending/revert behaviour now and let useOptimisticRows build on it later?
  • Catalogue pattern: move #1750 into docs-src/patterns/ and retire the popover version?
  • Bring-your-own editor: leave it out until a team asks?
  • Select padding: keep it here, or split it into its own PR?

Refs tailor-inc/platform-planning#1750 · tailor-inc/platform-planning#1428 · tailor-inc/platform-planning#1115 · tailor-inc/platform-planning#1161

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Code Metrics Report

main (28388de) #560 (54239f5) +/-
Coverage 87.4% 87.4% 0.0%
Test Execution Time 2m13s 2m21s +8s
Details
  |                     | main (28388de) | #560 (54239f5) | +/-  |
  |---------------------|----------------|----------------|------|
  | Coverage            |          87.4% |          87.4% | 0.0% |
  |   Files             |            204 |            204 |    0 |
  |   Lines             |           6014 |           6014 |    0 |
  |   Covered           |           5257 |           5257 |    0 |
- | Test Execution Time |          2m13s |          2m21s |  +8s |

Reported by octocov

@itsprade itsprade changed the title docs(data-table): propose inline cell editing feat(data-table): inline cell editing Sep 29, 2026
itsprade and others added 11 commits September 29, 2026 12:48
Decision record for building inline cell editing into DataTable: typing
into cells with rules (whole numbers, min/max, decimal limits), per-row
editability via canEdit(row, { selected }), keyboard entry, and autosave
on leave with revert on failure, across every built-in column type.

Open for team input; no package changes. Refs
tailor-inc/platform-planning#1750, tailor-inc/platform-planning#1428,
tailor-inc/platform-planning#1115.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pure helpers behind inline cell editing: which keystrokes a number cell
accepts, how pasted or IME text is cleaned up, the rule order (required,
number, decimals, min, max, custom), no-op equality (10 vs "10.00"),
currency decimal places, and a focus registry so Enter / Tab can move
between editable cells without querying the DOM.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k columns

Give a column an `edit` config and users can type straight into its
cells. The table never stores edits: `edit.onCommit(row, value)` runs
when the user leaves the cell (or presses Enter / Tab) with a changed
value that passes every rule, and the consumer updates `data`.

- Rules: `min`, `max`, `maxDecimals`, `required`, `validate`. Characters
  that can never be valid are blocked as typed; other errors show a red
  outline and a tooltip; Enter / Tab are blocked until fixed, Esc
  reverts, and leaving an invalid cell reverts it.
- Per-row control: `canEdit(row, { selected })`.
- Keyboard: Enter moves down the column, Tab to the next editable cell;
  IME composition is respected.
- Autosave: a promise returned from `onCommit` keeps the new value on
  screen while pending and reverts the cell if it rejects.
- The editor overlays the cell's normal content, so row height and
  column width never change. Number/money cells display as many
  decimals as their editor accepts.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs-src: new "Inline editing" section on the DataTable page (options,
  rules and errors, keyboard, saving, limits) and an `edit` column in the
  per-type field table; docs regenerated with `pnpm docs:sync`.
- list-dense-scan pattern: replace "no inline editable cells" with an
  inline-entry variant and guidance on when to use detail/form instead.
- vite example: goods-receipt table in the DataTable lab (received qty
  gated by selection, autosaving unit price with a failing save, notes,
  supplier link).
- Decision record: note that PR 1's scope is implemented here.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every typed column can now be edited in place:

- `text` / `link` with `edit.options`: a dropdown of choices. The column
  stores a choice's value and every cell shows its label.
- `badge`: a dropdown whose choices are the same badges; defaults to the
  entries of `typeOptions.badgeLabelMap`, else the enum filter options.
- `date`: a calendar popover (plus a time field for `datetime`), with
  `min` / `max` bounds and Clear unless `required`.

Picks commit straight away through the same `onCommit` / pending /
revert path as typed cells; `validate` can reject a pick with a tooltip.
Tab moves between all editable cells.

Also:
- Date-only "YYYY-MM-DD" values now render as that day in every time
  zone (they showed the previous day west of UTC).
- Navigation no longer relies on ref cleanups, which `Input` and
  `Select.Trigger` drop since #559 merged their refs; cells that left the
  page are skipped.
- Docs, demo (supplier dropdown, status badge dropdown, expected date)
  and changeset updated.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Date-only values: one parser for display and editing (cell-edit's
  `toLocalDate`), which also rejects impossible days — "2026-13-45" no
  longer rolls over into a real date.
- Date trigger: its accessible name now carries the current date
  ("Expected, Oct 2, 2026"); the visible date is hidden from AT.
- Badge columns: a cell holding several values stays read-only rather
  than having its list replaced by a single pick.
- Unit tests for the choice and date helpers.
- Demo: plausible supplier names.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Editable cells now look exactly like the rest of the table:

- No input boxes or outlines at rest. Each editor covers its whole cell,
  edge to edge (inheriting the cell's padding so text lines up), so a
  click anywhere in the cell starts editing.
- The cursor says what a click does: a text cursor over typing cells,
  a pointer over dropdown and date cells, and "not allowed" over every
  read-only cell in a table that has editable columns (unless rows are
  clickable).
- The cell being edited outlines its edges with a 2px inset primary
  ring; dropdown chevrons and calendar icons appear on hover, focus and
  while open, so they never cover text at rest.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A date column with `edit.min` / `edit.max` opened an empty cell's calendar
on today's month even when every day in it was disabled. It now opens on
the nearest day that can be picked.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds Warehouse (optional dropdown), Discount % (number limited to 0–100,
one decimal), Weight (three decimals, max 500), Lot no. (required text),
Received at (date-time), Best before (date bounded to Oct 2026–Dec 2027)
and QC (optional badge dropdown). Total now applies the discount.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dropdown chevron or calendar icon drew over the end of a long value.
Dropdown and date cells now keep a 20px space at their right edge for
the icon, so a value ends (or truncates) before it. Read-only cells of
the same column keep the space too, so the column's width doesn't shift
as rows become editable or not.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Select popup had no inner padding, so a highlighted option touched
its edges. It now has the same 4px inset as Menu, Combobox and
Autocomplete.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@itsprade
itsprade force-pushed the feat/core/1750-datatable-inline-edit branch from 581b087 to e7b094f Compare September 29, 2026 12:08
The "For the call" list still asked whether to ship in two PRs. Date and
badge editing are already in this one, so the question is now whether
reviewing them together is OK.

Refs tailor-inc/platform-planning#1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@itsprade
itsprade marked this pull request as ready for review September 29, 2026 12:13
@itsprade
itsprade requested a review from a team as a code owner September 29, 2026 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant