# Selection

`selectable: true` adds a checkbox column (`SelectableRows` plugin). Set
`singleSelect: true` for radio buttons instead (implies `selectable`).

## Selection state

Selection is server-first and lives in the core as a `SelectionState`:

```js
// explicit: the selected row keys are in `ids`
{ mode: "explicit", ids: Set(["1", "2"]), except: Set() }

// all: every matching row is selected except the ones in `except`
{ mode: "all", ids: Set(), except: Set(["3"]) }
```

`selectAll()` uses `mode: "all"` when `selectVisibleOnly` is `false`, so a
select-all across server pages only needs the keys of the rows you want to
exclude. With `selectVisibleOnly: true` (default) it only selects the visible
page as an explicit `ids` set.

Row keys come from the `rowKey` option (`"id"` by default), either a field name
or a function `(row) => key`.

## Accessible names

Every row control (checkbox or radio) is labelled `Select <row label>`, and the
header control is labelled with the `selectAll` label. The row label comes from
the `rowLabel` option, a field name or a function `(row, index) => string`,
falling back to the row key, then the row index:

```js
const grid = new DataGrid({
    selectable: true,
    rowLabel: "name", // -> "Select Alice Smith"
    // rowLabel: (row) => `${row.first} ${row.last}`,
});
```

`getRowLabel(row, index)` exposes the resolver publicly.

## API

```js
grid.isRowSelected(row, index)  // boolean
grid.getSelectionState()        // { mode, ids, except } snapshot
grid.selectRow(row, index)
grid.deselectRow(row, index)
grid.toggleRow(row, index)
grid.selectAll()                // visible page, or everything when selectVisibleOnly is false
grid.clearSelection()
```

`getSelection(...keys)` is a page-local convenience: with no keys it returns the
selected row objects of the current page, with one key an array of values, with
several keys an array of objects.

## Events

```js
grid.addEventListener("selectionChange", (ev) => {
    console.log(ev.detail.selectionState);
});
```

The core owns the `tr[data-selected]` state attribute; the plugin only renders
the checkboxes.

Selection survives pagination and sorting, but **any population change clears
it**: a `mode: "all"` selection only means something for the population it was
created on. Changing the global search or the column filters (and switching the
data source) invalidates the selection. The selection is also cleared as soon as
the search input value changes, even before the new search is committed, so a
bulk action never targets the population of a previous search.

## Row click

`rowClick: "select"` turns every data row into a click target that toggles its
selection:

```js
const grid = new DataGrid({
    selectable: true,
    rowClick: "select",
});
```

The whole row becomes a large mouse/touch target, while the native checkbox (or
radio with `singleSelect`) remains the keyboard and accessibility interface.
Rows only get the `dg-clickable-row` class (cursor) when `selectable` is on.
Interactive elements inside a row (`a, button, input, select, textarea`,
`[contenteditable]:not([contenteditable="false"])`, `[data-row-click-ignore]`)
and the selection control itself never trigger the toggle.

A cancelable `rowClick` event fires before the toggle and can veto it:

```js
grid.addEventListener("rowClick", (ev) => {
    if (ev.detail.row.protected) {
        ev.preventDefault();
    }
});
```

## Bulk actions

`bulkActions` adds a permanent bar with one button per action. A
`.dg-selection-count` badge shows the plain selected count and is hidden while
nothing is selected; the buttons are disabled when the selection is empty.
The badge announces `selectedCount` ("3 selected") through a live region while
staying visually language-neutral.

With `pager="more"` and an unknown total, an all-results selection keeps the
buttons enabled and hides the count badge, since its size cannot be computed.
Explicit selections still show their known count.

```js
const grid = new DataGrid({
    selectable: true,
    bulkActions: [
        { name: "archive", label: "Archive", intent: "danger" },
    ],
});

grid.addEventListener("bulkAction", (ev) => {
    // { action, selection, query }
    console.log(ev.detail.action, ev.detail.selection, ev.detail.query);
});
```

Unlike row actions, a bulk action receives the whole `SelectionState` and the
current `QueryState`, so it can act server-side on any number of rows.
