Skip to content

Inline edit cell does not exit edit mode for text/number/date/lookup fields — click-outside, Enter key, and Save button all fail to dismiss the editor #2321

Description

@hsynetal

bug

When inline editing a cell in the ObjectGrid list view, cells with non-discrete field types (text, number, email, date, datetime, currency, percent, lookup, user — essentially everything EXCEPT select/boolean/radio/rating) become permanently stuck in edit mode. Clicking outside the cell, pressing Enter, or even saving via the row-level Save button does not dismiss the editor.

Navigate to any object list with inlineEdit: true → click a cell to edit → type a value → click outside the grid. Expected: the cell exits edit mode and the staged value remains as a pending change (amber highlight). Actual: the cell stays in edit mode showing the widget editor, with no way to exit except clicking a different row's cell (which opens a NEW editor but leaves the old one still active). The only reliable escape is pressing Escape.

Root cause
In ObjectGrid.tsx, the renderCellEditor callback (line 1818) injects FieldEditWidget for every field type that has a registered edit widget. The onChange handler discriminates between discrete and non-discrete types:

ObjectGrid.tsx
Lines 1833-1838
return (
<FieldEditWidget
field={field}
value={ctx.value}
onChange={(v: any) => (discrete ? ctx.commit(v) : ctx.stage(v))}
/>
);
DISCRETE_EDIT_TYPES (defined in FieldEditWidget.tsx:112-114) includes only ['boolean', 'toggle', 'select', 'status', 'radio', 'rating'] — types whose value is chosen in one discrete gesture. For these, commit(v) calls saveEdit(true, v) in DataTable, which both persists the value AND sets editingCell to null (exits edit mode).

For all other types (text, number, email, date, datetime, currency, percent, lookup, user, phone, url, color, address, geolocation, code, qrcode, etc.), stage(v) is called instead:

data-table.tsx
Lines 751-766
const stageEdit = (value: any) => {
if (!editingCell) return;
const { rowIndex, columnKey } = editingCell;
setEditValue(value);
setPendingChanges((prev) => {
const next = new Map(prev);
const rowChanges = { ...(next.get(rowIndex) || {}) };
rowChanges[columnKey] = value;
next.set(rowIndex, rowChanges);
return next;
});
};
stageEdit updates editValue and pendingChanges but never sets editingCell to null. The editor stays open.

The DataTable's blur-commit handler (handleEditBlur, line 770) is only attached to its own built-in elements (lines 1389, 1410, 1424, 1464), not to the injected widget editors from renderCellEditor. There is no document-level click-outside listener for editing cells, and no keyboard handler on the injected widgets for Enter/Escape.

data-table.tsx
Lines 770-776
const handleEditBlur = () => {
if (skipBlurSaveRef.current) {
skipBlurSaveRef.current = false;
return;
}
saveEdit(true);
};
Consequently, the edit session remains open forever unless:

The field is a discrete type (select/boolean/radio/rating) — commit() closes it immediately on selection
The user presses Escape (keyboard event propagates from widget to DataTable cell)
The user double-clicks a different row's cell, opening a new edit session
Expected behavior
Click-outside: Clicking anywhere outside the editing cell should commit the staged value and exit edit mode (same behavior as built-in editors).
Enter key: Pressing Enter within the edit widget should commit and exit edit mode (same as built-in editors).
Save button: The row-level Save (floppy disk) button in the action column should persist and exit edit mode (this path DOES call saveRow which sets editingCell = null, but there may be a race condition — see observations below).
Affected field types
All non-discrete types registered in EDIT_WIDGETS (FieldEditWidget.tsx:54-90):

text, textarea, number, currency, percent, slider, progress, rating*,
email, phone, url,
date, datetime, time,
lookup, master_detail, user, owner,
color, address, location, geolocation, code, qrcode,
multiselect, tags, checkboxes, radio**

  • rating is discrete — already works via commit()
    ** radio is discrete — already works via commit()

The bug affects 24 out of 28 inline-editable field types.

Scope
File: vendor/objectui/packages/plugin-grid/src/ObjectGrid.tsx — renderCellEditor (line 1817)
File: vendor/objectui/packages/components/src/renderers/complex/data-table.tsx — no blur/click-outside on injected editors (line 1376)
File: vendor/objectui/packages/fields/src/FieldEditWidget.tsx — widget editor has no commit-on-blur wrapper (line 136)
Suggested fix options
Option A (minimal): Change the renderCellEditor to always call commit() instead of stage() for all types:

onChange={(v: any) => ctx.commit(v)}
This makes every field commit-and-exit on value change, matching the discrete-type behavior. The downside: for text/number fields, each keystroke would commit and close the editor, which is worse UX than staging.

Option B (preferred): Wrap injected widget editors in a container with a blur handler that calls commit():

// In renderCellEditor, wrap FieldEditWidget:
onChange={(v: any) => ctx.stage(v)}
onBlur={() => ctx.commit()} // <-- commit on blur
onKeyDown={(e) => { if (e.key === 'Enter') ctx.commit(); if (e.key === 'Escape') ctx.cancel(); }}
Option C: Add a document-level click-outside listener in DataTable that commits the edit when the user clicks outside the editing cell (matching the built-in handleEditBlur behavior but at the cell level rather than individual input level).

Reproduction
Open any object list view with inlineEdit: true (e.g., GRNs, Parts, Sales Orders)
Double-click any non-select cell (e.g., a text field like grn_number, a number field like quantity_received, a date field, a lookup field)
Type a value into the widget editor
Click anywhere outside the cell (e.g., another cell in the same row, the sidebar, the toolbar)
Observed: The edit widget stays open. The cell is still in edit mode.
Observed: Press Enter — no effect.
Observed: Click the row's Save (floppy disk) button — edit mode persists (may be a race condition or the button may not appear if rowHasChanges resolves to false before the staged pendingChanges state flush).
Expected: Any of the above actions should commit the staged value and exit edit mode.
Related
The InlineEditing component (plugin-grid/src/InlineEditing.tsx) has proper save/cancel buttons with Enter/Escape key handlers, but it is not used in the DataTable flow — it's only used in DetailView.tsx for record-detail inline field editing.
DISCRETE_EDIT_TYPES set is explicitly maintained in FieldEditWidget.tsx:112-114 — the bug is that non-discrete types must stage by design (to avoid committing on every keystroke), but the commit-on-blur/Enter pathway never fires for injected widgets.
DataTable's saveRow (line 778) and saveAllChanges (line 818) both DO exit edit mode for the affected row, but the save buttons may not be rendering or the save may be failing silently before reaching the exit-edit-mode code path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions