The sheet-backed ISelection: a thin adapter over the sdk IRangeSelection so the new
selection layers (and the editor, via ICellAnchor) drive the real sheet.getSelection()
instead of the dumb in-memory DefaultSelection.
Remarks
The architecture deliberately pushes the smart selection logic (merge / hidden / contentful
awareness, bounds clamping) into the model, so this adapter is almost pure delegation —
navigate / traverse / modify / append map straight onto the sdk's implementations. The two
non-trivial seams:
Reads are stable.IRangeSelection.getCoords() returns its internal _coords reference,
replaced only on a real change (its updateCoords short-circuits on isEqualSelectionCoords), so
getSelection() satisfies the useSyncExternalStore stable-snapshot invariant with no caching.
Subscription is ref-counted. A single sdk onChange listener is attached while any React
subscriber is present and detached when the last leaves — no dangling sdk listener, no dispose().
deselect is the one intent the sdk has no native method for; it is computed here as a pure range
split (mirroring DefaultSelection.deselect) and written back through updateCoords.
The Direction / RangeAxis unions are the same as const string literals the sdk's IRange.*
use, so they pass through without mapping.
The sheet-backed ISelection: a thin adapter over the sdk IRangeSelection so the new selection layers (and the editor, via ICellAnchor) drive the real
sheet.getSelection()instead of the dumb in-memoryDefaultSelection.Remarks
The architecture deliberately pushes the smart selection logic (merge / hidden / contentful awareness, bounds clamping) into the model, so this adapter is almost pure delegation —
navigate/traverse/modify/appendmap straight onto the sdk's implementations. The two non-trivial seams:IRangeSelection.getCoords()returns its internal_coordsreference, replaced only on a real change (itsupdateCoordsshort-circuits onisEqualSelectionCoords), sogetSelection()satisfies theuseSyncExternalStorestable-snapshot invariant with no caching.onChangelistener is attached while any React subscriber is present and detached when the last leaves — no dangling sdk listener, nodispose().deselectis the one intent the sdk has no native method for; it is computed here as a pure range split (mirroringDefaultSelection.deselect) and written back throughupdateCoords.The
Direction/RangeAxisunions are the sameas conststring literals the sdk'sIRange.*use, so they pass through without mapping.