Repository navigation
feat(data-table): add declarative cell-editing errors - #635
Conversation
|
Since #583, components use the danger roles instead of the
|
interacsean
left a comment
There was a problem hiding this comment.
- If I enter 13, then blur, it takes the stated 600ms to show the error, if I re-enter the field and blur again, the error clears (even though I have left the 13 untouched). If the field hasn't changed since the last error, it can probably keep the error without revalidating... I guess there's some case where the validation may clear, based on updated data elsewhere though.
- The error information is not accessible via keyboard navigation (i.e. tab)
- I'd like to see the error popover show up when it first occurs — I think for both, because otherwise the user has to make a mouse/touch interaction to understand what is wrong. This would help with accessibility. It could have a 'x' to dismiss it (and then user can open again via "(!)")
- The style looks okay, although I think it can be improved: the alignment to the issue cell could be tighter to make it even more clear which cell the error is referring to.
- Showing the thrownError.message means that an error which is unexpected ends up showing text in the UI. Wdyt about creating an error class that extends error, and consumer code must throw one of these errors to show text. If any other error is thrown, UI just shows "An error occurred". I haven't thought through i18n: either you
throw CellValidationError(t("my error"))or perhapsthrow CellValidationError("my error")could try to pass "my error" to the i18n functions. Examples in the docs would be good for this too - inline-editing-demo.tsx still says a rejected value “does not block Tab navigation or create a cell error”
| typeOptions: { maxDecimals: 0 }, // Display precision, not an input rule. | ||
| edit: { | ||
| canEdit: (_row, { selected }) => selected, | ||
| onValueCommitted: (row, value) => { |
There was a problem hiding this comment.
We should update this example of error handling in here
There was a problem hiding this comment.
Updated in 6399348. This example now throws CellEditError instead of showing a toast and returning, so invalid quantities retain their draft and display cell-local feedback. Removed the unused useToast reference and regenerated the documentation.
* feat(data-table): give the default cell-error popover an error look and undo hint The standard popover body showed only the message, so it read like a neutral tooltip. It now leads with a destructive alert icon, shows the message in medium weight, and adds the existing "Press Esc to undo" hint. Custom error.render content is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(data-table): tighten the cell-error popover Drop the undo hint and the popup border, reduce padding and cap the width at max-w-xs so long messages wrap. The lab's Draft note column now throws a longer message on "?" to show wrapping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(data-table): refine cell-error popover placement and tint - 1px error ring on the cell, including while focused - popup opens directly under the cell, aligned to its left edge - light red tint (Alert's danger-surface) layered over the opaque popover colour so table lines don't show through; softer shadow-sm - icon centred on the first text line, at 70% of the text colour - lab: "!!" in Draft note throws a long message to show wrapping Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(data-table): 1.5px error outline, heavier error icons - cell error ring is 1.5px (resting and focused) - error icons in the cell and popover use strokeWidth 2.5 - drop the focus ring on the in-cell error trigger; it drew a second box around the icon when focus returned to it on popover close Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
In reply to the color-token feedback: Updated in 6399348. The cell error outline now uses ring-danger-solid, and the error icon uses text-danger-text. The latest popover UI refinements are preserved. |
|
In reply to this review: Thanks for the detailed feedback! Updated in 6399348:
|
|
@copilot resolve the merge conflicts in this pull request |
…errors # Conflicts: # docs-manifest.json Co-authored-by: IzumiSy <982850+IzumiSy@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
…errors # Conflicts: # docs-manifest.json # examples/vite-app/src/pages/showcase/data-table-lab/inline-editing-demo.tsx Co-authored-by: IzumiSy <982850+IzumiSy@users.noreply.github.com>
Resolved and pushed in b1a55bc. Merged the latest main, preserved both CellEditError and aggregation demo imports, and regenerated documentation metadata. All 791 targeted tests, type checks, lint, formatting, docs checks, and security scans passed. check-dts was blocked by npm React dependency resolution in its temporary consumer install. |
|
I picked up two more issues that fell out of the last round of edits cell-remaining.mp4
|
|
@interacsean Thanks for catching these! Updated in d77c7e1:
Refocusing does not reopen dismissed details; use the error icon or Alt+ArrowDown. Tab within interactive popover content still works normally. |

Motivation
DataTable's built-in editors already handle draft input, parsing, and keyboard navigation, but screens must build their own feedback for failures in editing callbacks. A screen can reject a proposed value by leaving
dataunchanged, yet that does not provide a consistent, accessible way to explain the failure beside the affected cell.The goal here is declarative error presentation, not a new validation API. Existing callbacks should remain responsible for application rules and persistence, while DataTable supplies a standard error popover and lets consumers customize its content.
An initial prototype exposed an important UX inconsistency: Enter discarded the rejected draft and restored the adopted value while leaving the cell marked invalid. The displayed value no longer matched the error, and correcting or dismissing that error was unclear. This change treats the draft and its failure as one editing session so they cannot become detached.
Keyboard navigation must not make an unchanged failed value appear accepted or trigger another save merely because the user moves to a different cell. Dismissing error details must remain distinct from resolving the error.
Screen shot
An error in cell
Error detail
Design Decision
Callback-driven errors with safe user-facing messages
By default, synchronous throws and returned Promise rejections from
onValueChangeandonValueCommitteduse the cell's error popover. The standard body displays a non-empty message only from the exportedCellEditError, marking it as safe to show to users. Consumers translate these messages before throwing. Ordinary errors, other thrown values, and empty messages use the localized fallback “Could not confirm this change” / 「変更を確定できませんでした」 in both the popover and its accessible description; internal messages and stack traces are not exposed.The fallback describes an unconfirmed change rather than assuming the input is invalid or claiming that DataTable performed a save. Application rules and persistence remain consumer-owned. Capturing a callback failure does not automatically log or report it; consumers remain responsible for diagnostics.
Consumers only configure exceptions to the default:
error: falsepreserves eager edit completion and leaves callback failures to the consumer: synchronous exceptions are not intercepted, and returned Promises are not observed.error.renderreceives the current row and original thrown value and replaces the popover body, including support for interactive React content. Custom content owns its message-safety policy. DataTable owns the indicator, positioning, keyboard access, and close button; it adds no reset or rollback action.There is no presentation discriminator because only popovers are supported. Inline presentation was left out because it introduces separate questions about replacing cell content, editor layout, and display while typing. There are also no new
validate, schema, or business-constraint options.Keep the failed draft until success or cancellation
Each cell has one internal session:
idle,editing,confirming, orfailed. A failed state always contains its target draft.onValueCommittedruns.onValueCommittedis the confirmation attempt. A normal return or fulfilled Promise completes the edit; a throw or rejection retains the original draft for correction or explicit retry.The public callback signatures remain
=> voidto preserve compatibility with existing handlers and successful return values. Only returned Promises are observed for the editing lifecycle; successful return values are ignored, not adopted as row data. Adoption, persistence, request cancellation, and rollback remain consumer-owned; suppressing stale UI results does not undo callback side effects or prevent stale consumer writes.Error inspection is part of the cell's focus group
New callback failures normally open their details without moving focus, including an asynchronous failure that arrives after the user has moved to another cell. Tab/Shift+Tab navigation dismisses the departing cell's details instead, including a failure thrown synchronously by that confirmation. The failed draft, error outline, icon, and accessible description remain intact. Returning to the cell does not reopen the same dismissed failure; the error icon or Alt+ArrowDown reopens it, and Alt+ArrowDown also focuses details that are already open.
Opening or interacting with the error popover must not accidentally confirm or discard a draft. The cell and its portaled inspector form one logical focus group, and Tab within interactive error content still moves between its controls. Closing details during navigation must not pull focus back into the failed cell, including at the first and last editable cells; ordinary outside-click dismissal also respects the chosen focus destination. Dismissing details and cancelling an edit remain separate operations: dismissal retains the draft and error, whereas cancellation clears them together.
Summary
CellEditErrorfor safe, translated user-facing messages; use localized confirmation-failure feedback for other thrown values.