LearnNewsExamplesServices
Frontmatter
id18838
titleA picker editor's trigger click ends the grid cell edit
stateClosed
labels
bugaigrid
assigneesneo-fable
createdAtSep 18, 2026, 12:30 PM
updatedAtSep 18, 2026, 2:19 PM
githubUrlhttps://github.com/neomjs/neo/issues/18838
authorneo-fable
commentsCount0
parentIssue18762
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 18, 2026, 2:19 PM

A picker editor's trigger click ends the grid cell edit

neo-fable
neo-fable commented on Sep 18, 2026, 12:30 PM

Context

Leaf of #18762, from the Fable evaluation round its Unresolved Liveness names. Measured on examples/grid/cellEditing at dev@3f859a4803 with a real pointer and keyboard. Text and number editors complete the whole loop; the date editor does not survive a pointer on its trigger.

Owner: @neo-fable (self-assigned 2026-09-18).

The Problem

Observed

Double-click a Random Date cell → the DateField editor mounts and takes focus. Click its picker trigger → the editor is destroyed, no picker stays, document.activeElement is body. A date can only be typed.

Measured mechanism (worker-side focus trace + a main-thread focusin/focusout/mutation trace)

The trigger click opens the picker, and the DateSelector takes focus. manager.Focus#focusMove reports the move as a leave for [datefield, row, body, view] and then an enter for [dateselector, picker]. The plugin commits on the editor's focusLeave, so completeEdit() destroys the editor and the picker with it, and focus falls to body.

A second defect sits behind the first. Once the session survives a picker visit, the editor's last focus enter came from the picker, so component.Base#revertFocus hands focus to a component that dies with the editor: ending the edit leaves focus on body instead of grid.View.

Corrected 2026-09-18: the first filing also listed "a ComboBox trigger click does not open the list". Under Playwright it does — 243 items, the input keeps focus, an item click selects, the session stays. That sighting was an artifact of the preview browser's click; the ComboBox keeps focus in its input, so it never crosses the island boundary. It stays here as a witness arm only.

The Architectural Reality

  • src/grid/plugin/CellEditing.mjs — editor.on('focusLeave', …) commits whenever the session is embodied.
  • src/manager/Focus.mjs — focusMove delivers the leave set before the enter set, synchronously; setComponentFocus writes containsFocus before it fires the events.
  • src/form/field/Picker.mjs — the picker container is parentId: 'document.body' with NO parentComponent. ComponentManager.getParentPath — the path manager.Focus works on — walks component.parent (parentComponent, else parentId), so the picker is a branch of its own, outside the editor's path. That is the root cause; button.Base, the tab overflow control and the grid component column give their floating parts parentComponent.
  • src/component/Base.mjs — revertFocus() reads focusEnterData.relatedTarget, overwritten on every focus enter.
  • D#17832 OQ5, resolved and assigned to the interaction-contract responsibility, never filed as a leaf: "Focus leaves the editor/picker island → commit-if-valid", "Escape with picker open → close picker only".

The Fix

Revised 2026-09-18, on @tobiu's question on the PR (would editors carrying parent ids simplify it): in src/form/field/Picker.mjs, createPicker gives the picker parentComponent: me. Focus moving between input and picker is then a move inside the field — no focusLeave for the field, the Row or the grid, and focusEnterData keeps its origin — so the grid plugin needs no change, and the field's two scans of the focus payload (for the picker's id, and for its own) are deleted. Every picker field gains it, not only grid editors.

First prescription, built and approved before the question: a plugin-side island — the leave terminal deferred by a microtask and asking containsFocus on editor and picker, a listener on the picker, and a restored focus origin on the session. It worked around a picker that was nobody's child.

Acceptance Criteria

  • Clicking a DateField editor's trigger opens the date picker and the session stays embodied; a picked day reaches the record when focus then leaves the island.
  • Escape with the picker open closes the picker and keeps the session; a second Escape cancels, and focus is on grid.View.
  • Focus leaving the editor AND its picker still commits a valid draft (the existing click-away and focus-leave arms stay green).
  • Witness: a ComboBox editor's trigger opens its list inside the session, and Escape closes the list before it cancels. Moved to #18839: any arm opening the Country editor can red on that ticket's renderer error (reading 'name' of null, 1 of 4 runs), so the witness lands with its fix.
  • CI-executed e2e arms on the public example (in #18837's spec), the two date arms red on dev first; each half of the fix killed by a named mutation.
  • Enter after a picker visit commits and leaves focus on grid.View (added 2026-09-18, once #18850's engine guard was on dev: the flow needs both fixes).

Out of Scope

Tab/Shift+Tab traversal, select-on-activation, S3 opt-out, the IME probe (remaining interaction-contract scope, #18861–#18865) · ComboBox on a bound store (#18839) · whether a picker field should open its picker on Enter while it is a grid editor — the picker still flashes into the DOM for ~9 ms on an Enter-commit.

Avoided Traps

  • A timer, or a plugin-side "picker is open" flag, to skip the commit: the focus manager already knows where focus is; a second notion of "inside" drifts from it.
  • Reading the leave event's oldPath: for a focus move it carries the path focus went TO, for a plain leave the path it came FROM — one name, two meanings. containsFocus after the move has landed means one thing.
  • Parenting the picker into the Row: the body clips it, and it dies with the pool slot.

Related

Parent #18762 · D#17832 (OQ5 terminal table) · #18764 · #18837 / PR #18842 (the spec these arms land in) · #17825 (outside-pointer dismissal for floating pickers, the neighbouring contract).

Live latest-open sweep: latest 20 open issues at 2026-09-18T10:27Z plus all-state searches for CellEditing / cell editing / picker — no equivalent. A2A sweep (latest 12, all read-states): no claim on this scope. Memory sweep: one hit, my own measurement checkpoint.

Origin Session ID: 323cb989-06a2-4b3f-8d70-912d67796c11

Retrieval Hint: "grid cell editing picker island focusLeave containsFocus DateField trigger destroys editor focus origin"

tobiu referenced in commit f44ddcf - "fix(form): a picker field owns its floating picker, so focus moving into it never leaves the field (#18838) (#18852) on Sep 18, 2026, 2:19 PM
tobiu closed this issue on Sep 18, 2026, 2:19 PM