Capítulo 28 de 54
cellSelectionFeature tracks spreadsheet-style rectangular cell ranges as a compact ordered log of include/exclude operations (not a per-cell map), letting click, Shift-extend, drag, and Ctrl/Cmd multi-range selection all compose cheaply even over large tables.
CellSelectionState is Array<{ anchorRowId, anchorColumnId, focusRowId, focusColumnId, operation?: 'include' | 'exclude' }>. anchor is where a range started (fixed), focus is the corner that moves during drag/Shift-extend. Applied in order; an omitted operation means include. This is what makes "select all except these three cells" cheap instead of enumerating every included cell.table.state.cellSelection (reactive), table.getSelectedCellCount(), table.getSelectedCellIds(), getCellSelectionRowIds()/getCellSelectionColumnIds(), table.getSelectedCellRangesData() (each disjoint positive region as a row-major value grid). The expansion APIs are memoized and pull-based — a table that only highlights selected cells (via cell.getIsSelected()) never pays to actually enumerate a large selection.atoms: { cellSelection: atom } (recommended in v9) or classic state.cellSelection + onCellSelectionChange. Note: a drag fires one change per cell boundary crossed, so debounce/commit-on-mouseup before syncing to a server or URL.getRowId, same reasoning as row selection — selection is keyed by row id + column id.enableCellSelection on the table (boolean or per-cell function) or per-column-def enableCellSelection: false (column-level false always wins over the table option). A non-selectable cell is skipped even by a rectangle drawn straight through it; check cell.getCanSelect() before attaching handlers.cell.getSelectionStartHandler() on onMouseDown, cell.getSelectionExtendHandler() on onMouseEnter — no manual mouseup handling needed (the start handler self-attaches/detaches a document-level listener, even across iframe/popout boundaries via an optional target-document argument).enableCellSelectionDrag: false); Shift-click = extend the active range's focus, anchor fixed (enableCellRangeSelection: false to disable, isCellRangeSelectionEvent to swap the modifier); Ctrl/Cmd-click on an unselected cell = new inclusive range, on a selected cell = new exclusion (enableMultiCellRangeSelection: false to disable, isMultiCellRangeSelectionEvent to swap).table.selectCellRange(range, { mode: 'include' | 'exclude' }) replaces current selection (the older { additive: true } is a deprecated include-mode alias); table.getCellSelectionBounds() resolves the log into deterministic disjoint rectangles.cell.getIsSelected(), cell.getIsFocused() (the active cell — can be true even for an excluded anchor), cell.getSelectionEdges() → { top, right, bottom, left } (each true when that neighbor is not selected, which is exactly what's needed to draw one continuous outline around a union of rectangles without per-cell neighbor inspection), cell.getTabIndex() (roving tabindex: 0 for focused, -1 otherwise). Draw the outline with box-shadow: inset, not border — a border on a border-collapse table changes row height as selection changes; a box-shadow never affects layout.table.moveCellSelection(direction), extendCellSelection(direction), setFocusedCell(rowId, colId), selectAllCells(), resetCellSelection(true), direction ∈ 'up'|'down'|'left'|'right') for a dedicated library like TanStack Hotkeys to drive.cellSpanningFeature also registered, selection rectangles auto-expand to fully enclose any merged cell they touch — a merge is always entirely selected or entirely unselected, never partial.const features = tableFeatures({ cellSelectionFeature })
const table = useTable({ features, columns, data, getRowId: (r) => r.uuid })
<td onMouseDown={cell.getSelectionStartHandler()} onMouseEnter={cell.getSelectionExtendHandler()}>
<table.FlexRender cell={cell} />
</td>
<td>.getSelectionStartHandler/getSelectionExtendHandler) cover mouse drag/Shift/Ctrl entirely; keyboard needs a separate library wired to the imperative move/extend APIs.box-shadow: inset, never border, to avoid layout shift.getRowId dependency.getRowId matters for any id-keyed feature.