From 9e47a4f8c52cd3f5890762f8145004e9e516cdd6 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Fri, 31 Jul 2026 08:21:46 -0500 Subject: [PATCH 1/7] feat: support subtractive cell selection --- docs/framework/alpine/guide/cell-selection.md | 19 +- .../framework/angular/guide/cell-selection.md | 19 +- docs/framework/ember/guide/cell-selection.md | 19 +- docs/framework/lit/guide/cell-selection.md | 19 +- docs/framework/preact/guide/cell-selection.md | 19 +- docs/framework/react/guide/cell-selection.md | 19 +- docs/framework/solid/guide/cell-selection.md | 19 +- docs/framework/svelte/guide/cell-selection.md | 19 +- docs/framework/vue/guide/cell-selection.md | 19 +- docs/reference/index/index.md | 2 + .../index/interfaces/CellSelectionRange.md | 13 ++ .../index/interfaces/Cell_CellSelection.md | 18 +- .../interfaces/SelectCellRangeOptions.md | 23 ++- .../interfaces/TableOptions_CellSelection.md | 25 ++- .../index/interfaces/Table_CellSelection.md | 52 +++--- .../type-aliases/CellSelectionRangeMode.md | 12 ++ .../CellSelectionRangeOperation.md | 12 ++ .../index/type-aliases/CellSelectionState.md | 6 +- .../functions/cell_getIsFocused.md | 2 +- .../functions/cell_getIsSelected.md | 4 +- .../functions/table_getCellSelectionBounds.md | 5 +- .../functions/table_getFocusedCell.md | 4 +- .../functions/table_getSelectedCellCount.md | 7 +- .../table_getSelectedCellRangesData.md | 4 +- .../functions/table_selectCellRange.md | 6 +- examples/alpine/cell-selection/src/main.ts | 2 +- .../angular/cell-selection/src/app/app.html | 6 +- .../angular/cell-selection/src/app/app.ts | 2 +- examples/lit/cell-selection/src/main.ts | 8 +- examples/preact/cell-selection/src/main.tsx | 10 +- examples/react/cell-selection/src/main.tsx | 10 +- .../cell-selection/tests/e2e/smoke.spec.ts | 27 +++ .../spreadsheet/tests/e2e/spreadsheet.spec.ts | 23 +++ examples/solid/cell-selection/src/App.tsx | 10 +- examples/svelte/cell-selection/src/App.svelte | 4 +- examples/vue/cell-selection/src/App.vue | 6 +- .../cellSelectionFeature.types.ts | 45 +++-- .../cellSelectionFeature.utils.ts | 73 ++++---- .../cell-selection/cellSelectionGeometry.ts | 169 ++++++++++++++++++ .../cellSelectionFeature.test.ts | 100 +++++++++++ .../cellSelectionGeometry.test.ts | 85 +++++++++ .../cell-selection/cellSelectionRange.test.ts | 66 +++++++ 42 files changed, 806 insertions(+), 206 deletions(-) create mode 100644 docs/reference/index/type-aliases/CellSelectionRangeMode.md create mode 100644 docs/reference/index/type-aliases/CellSelectionRangeOperation.md create mode 100644 packages/table-core/src/features/cell-selection/cellSelectionGeometry.ts create mode 100644 packages/table-core/tests/implementation/features/cell-selection/cellSelectionGeometry.test.ts diff --git a/docs/framework/alpine/guide/cell-selection.md b/docs/framework/alpine/guide/cell-selection.md index 4ec8ccf546..4b4e3d66ac 100644 --- a/docs/framework/alpine/guide/cell-selection.md +++ b/docs/framework/alpine/guide/cell-selection.md @@ -44,7 +44,7 @@ Alpine.data('table', () => { ## Cell Selection (Alpine) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -54,7 +54,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.atoms.cellSelection.get()) //get the cell selection state @@ -69,7 +69,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -77,6 +77,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -84,7 +85,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -216,14 +217,18 @@ const table = createTable({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -286,7 +291,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/framework/angular/guide/cell-selection.md b/docs/framework/angular/guide/cell-selection.md index b1d3a808cd..97a11dc914 100644 --- a/docs/framework/angular/guide/cell-selection.md +++ b/docs/framework/angular/guide/cell-selection.md @@ -34,7 +34,7 @@ export class App { ## Cell Selection (Angular) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -44,7 +44,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.atoms.cellSelection.get()) //get the cell selection state @@ -59,7 +59,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -67,6 +67,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -74,7 +75,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -200,14 +201,18 @@ readonly table = injectTable(() => ({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -287,7 +292,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/framework/ember/guide/cell-selection.md b/docs/framework/ember/guide/cell-selection.md index e53c2d9802..f7724593ed 100644 --- a/docs/framework/ember/guide/cell-selection.md +++ b/docs/framework/ember/guide/cell-selection.md @@ -34,7 +34,7 @@ export default class MyTable extends Component { ## Cell Selection (Ember) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -44,7 +44,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.atoms.cellSelection.get()) //get the cell selection state @@ -59,7 +59,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -67,6 +67,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -74,7 +75,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -196,14 +197,18 @@ table = useTable(() => ({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -266,7 +271,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/framework/lit/guide/cell-selection.md b/docs/framework/lit/guide/cell-selection.md index 16ce895920..7aed3bb8d8 100644 --- a/docs/framework/lit/guide/cell-selection.md +++ b/docs/framework/lit/guide/cell-selection.md @@ -38,7 +38,7 @@ class MyTable extends LitElement { ## Cell Selection (Lit) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -48,7 +48,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.state.cellSelection) //get the cell selection state @@ -63,7 +63,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -71,6 +71,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -78,7 +79,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -196,14 +197,18 @@ const table = this.tableController.table({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -266,7 +271,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/framework/preact/guide/cell-selection.md b/docs/framework/preact/guide/cell-selection.md index d283614889..823f429638 100644 --- a/docs/framework/preact/guide/cell-selection.md +++ b/docs/framework/preact/guide/cell-selection.md @@ -30,7 +30,7 @@ const table = useTable({ ## Cell Selection (Preact) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -40,7 +40,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.state.cellSelection) //get the cell selection state @@ -55,7 +55,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -63,6 +63,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -70,7 +71,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -188,14 +189,18 @@ const table = useTable({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -262,7 +267,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/framework/react/guide/cell-selection.md b/docs/framework/react/guide/cell-selection.md index 2ba28d2167..5ffa0d0cd8 100644 --- a/docs/framework/react/guide/cell-selection.md +++ b/docs/framework/react/guide/cell-selection.md @@ -30,7 +30,7 @@ const table = useTable({ ## Cell Selection (React) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -40,7 +40,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.state.cellSelection) //get the cell selection state @@ -55,7 +55,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -63,6 +63,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -70,7 +71,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -187,14 +188,18 @@ const table = useTable({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -263,7 +268,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/framework/solid/guide/cell-selection.md b/docs/framework/solid/guide/cell-selection.md index ba5dc2664d..dc292f7ffb 100644 --- a/docs/framework/solid/guide/cell-selection.md +++ b/docs/framework/solid/guide/cell-selection.md @@ -32,7 +32,7 @@ const table = createTable({ ## Cell Selection (Solid) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -42,7 +42,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.atoms.cellSelection.get()) //get the cell selection state @@ -57,7 +57,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -65,6 +65,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -72,7 +73,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -196,14 +197,18 @@ const table = createTable({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -270,7 +275,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/framework/svelte/guide/cell-selection.md b/docs/framework/svelte/guide/cell-selection.md index 03e47b3681..de2cb3ae7c 100644 --- a/docs/framework/svelte/guide/cell-selection.md +++ b/docs/framework/svelte/guide/cell-selection.md @@ -32,7 +32,7 @@ const table = createTable({ ## Cell Selection (Svelte) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -42,7 +42,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.atoms.cellSelection.get()) //get the cell selection state @@ -64,7 +64,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -72,6 +72,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -79,7 +80,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -206,14 +207,18 @@ const table = createTable({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -278,7 +283,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/framework/vue/guide/cell-selection.md b/docs/framework/vue/guide/cell-selection.md index f202ae4d33..8081c76264 100644 --- a/docs/framework/vue/guide/cell-selection.md +++ b/docs/framework/vue/guide/cell-selection.md @@ -30,7 +30,7 @@ const table = useTable({ ## Cell Selection (Vue) Guide -The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-click to add a second rectangle. Let's take a look at some common use cases. +The cell selection feature keeps track of spreadsheet-style rectangular selections. A user can click a cell, drag across a block of cells, Shift-click to extend, and Ctrl/Cmd-drag to add or subtract a rectangle based on whether the starting cell is selected. Let's take a look at some common use cases. ### Access Cell Selection State @@ -40,7 +40,7 @@ The table instance already manages the cell selection state for you. You can acc - `getSelectedCellCount()` - returns how many cells are selected - `getSelectedCellIds()` - returns the ids of every selected cell - `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches -- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid +- `getSelectedCellRangesData()` - returns each final positive selection region's values as a row-major grid ```ts console.log(table.atoms.cellSelection.get()) //get the cell selection state @@ -55,7 +55,7 @@ The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoi ### Cell Selection State Shape -`CellSelectionState` is an array of rectangles, each stored as its two defining corners: +`CellSelectionState` is an ordered array of range operations, each stored as its two defining corners: ```ts type CellSelectionRange = { @@ -63,6 +63,7 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array @@ -70,7 +71,7 @@ type CellSelectionState = Array The `anchor` corner is where the selection started and stays put. The `focus` corner is the one that moves while dragging or Shift-extending. Storing both corners, rather than a normalized min/max rectangle, is what makes Shift-extend and "collapse back to the active cell" possible. -Because ranges are only two corners, a drag across thousands of cells updates two strings rather than building a map with one entry per selected cell. +Ranges are applied in order. An omitted `operation` is an inclusion for backward compatibility; an `exclude` range subtracts its rectangle from the selection produced so far. This compact operation log means a “select all except these cells” interaction does not build a map with one entry per selected cell. ### Manage Cell Selection State @@ -195,14 +196,18 @@ const table = useTable({ #### Multiple Ranges -Ctrl-clicking or Cmd-clicking pushes an additional rectangle onto the selection instead of replacing it. Set `enableMultiCellRangeSelection: false` to allow only one rectangle at a time, or override `isMultiCellRangeSelectionEvent` to change the modifier. +Ctrl-clicking or Cmd-clicking an unselected cell adds a new inclusive rectangle. Starting the same modified interaction on a selected cell adds an exclusion instead, so clicking removes that cell and dragging subtracts the whole rectangle. Whether the drag includes or excludes is fixed when it starts; shrinking an exclusion drag restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both behaviors, or override `isMultiCellRangeSelectionEvent` to change the modifier. + +#### Programmatic Range Operations + +`table.selectCellRange(range)` replaces the current selection. Pass `{ mode: 'include' }` to append an inclusion or `{ mode: 'exclude' }` to append an exclusion. The older `{ additive: true }` option remains as a deprecated alias for include mode; `mode` wins if both options are supplied. `table.getCellSelectionBounds()` resolves the operation log into deterministic, disjoint positive rectangles. ### Render Cell Selection UI TanStack Table does not dictate how you render selected cells. These cell APIs give you everything you need: - `cell.getIsSelected()` - whether this cell falls inside any range -- `cell.getIsFocused()` - whether this is the active cell +- `cell.getIsFocused()` - whether this is the active cell (an excluded anchor can be focused without being selected) - `cell.getSelectionEdges()` - which sides sit on the selection boundary - `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex @@ -269,7 +274,7 @@ Scope the hotkeys to the grid element rather than the document, or arrow keys an ### Copying a Selection -`getSelectedCellRangesData()` returns raw values indexed as `[rangeIndex][rowIndex][columnIndex]`. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. +`getSelectedCellRangesData()` returns raw values indexed as `[regionIndex][rowIndex][columnIndex]`. A region is one of the final disjoint positive rectangles after all include and exclude operations are applied, so it does not necessarily correspond one-to-one with stored state. Turning that into clipboard text is left to your application, because the delimiter, the representation of `null`, and any quoting rules are decisions only you can make. ```ts function escapeTsvValue(value: unknown) { diff --git a/docs/reference/index/index.md b/docs/reference/index/index.md index bab42cfd74..e048bc3c17 100644 --- a/docs/reference/index/index.md +++ b/docs/reference/index/index.md @@ -224,6 +224,8 @@ title: index - [Cell](type-aliases/Cell.md) - [CellData](type-aliases/CellData.md) - [CellSelectionDirection](type-aliases/CellSelectionDirection.md) +- [CellSelectionRangeMode](type-aliases/CellSelectionRangeMode.md) +- [CellSelectionRangeOperation](type-aliases/CellSelectionRangeOperation.md) - [CellSelectionState](type-aliases/CellSelectionState.md) - [Column](type-aliases/Column.md) - [ColumnAggregationValue](type-aliases/ColumnAggregationValue.md) diff --git a/docs/reference/index/interfaces/CellSelectionRange.md b/docs/reference/index/interfaces/CellSelectionRange.md index 1c3be55a5a..f208f600c7 100644 --- a/docs/reference/index/interfaces/CellSelectionRange.md +++ b/docs/reference/index/interfaces/CellSelectionRange.md @@ -54,3 +54,16 @@ focusRowId: string; ``` Defined in: [features/cell-selection/cellSelectionFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L23) + +*** + +### operation? + +```ts +optional operation: CellSelectionRangeOperation; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L28) + +How this range changes the selection produced by the ranges before it. +Defaults to `include`. diff --git a/docs/reference/index/interfaces/Cell_CellSelection.md b/docs/reference/index/interfaces/Cell_CellSelection.md index 0878637e93..a1628a5666 100644 --- a/docs/reference/index/interfaces/Cell_CellSelection.md +++ b/docs/reference/index/interfaces/Cell_CellSelection.md @@ -5,7 +5,7 @@ title: Cell_CellSelection # Interface: Cell\_CellSelection -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L144) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:159](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L159) ## Properties @@ -15,7 +15,7 @@ Defined in: [features/cell-selection/cellSelectionFeature.types.ts:144](https:// getCanSelect: () => boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:148](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L148) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:163](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L163) Checks whether this cell can currently be selected. @@ -31,7 +31,7 @@ Checks whether this cell can currently be selected. getIsFocused: () => boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:153](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L153) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:168](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L168) Checks whether this cell is the active cell, i.e. the anchor of the most recent range. @@ -48,9 +48,9 @@ recent range. getIsSelected: () => boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:157](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L157) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:172](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L172) -Checks whether this cell falls inside any selected range. +Checks whether this cell falls inside the final positive selection. #### Returns @@ -64,7 +64,7 @@ Checks whether this cell falls inside any selected range. getSelectionEdges: () => CellSelectionEdges; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:165](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L165) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:180](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L180) Returns which sides of this cell sit on the outer boundary of the selection, for rendering a spreadsheet-style outline without each cell @@ -84,7 +84,7 @@ All sides are `false` when the cell is not selected. getSelectionExtendHandler: () => (event) => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:173](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L173) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:188](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L188) Creates a handler that extends the active range to this cell while a drag is in progress. Bind it to `mouseenter`. @@ -116,7 +116,7 @@ the active range already focuses this cell. getSelectionStartHandler: (contextDocument?) => (event) => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:184](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L184) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:199](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L199) Creates a handler that begins a selection at this cell. Bind it to `mousedown`. @@ -157,7 +157,7 @@ another document, such as an iframe or popout window. getTabIndex: () => number; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:190](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L190) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:205](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L205) Returns `0` for the focused cell and `-1` otherwise, for roving tabindex. diff --git a/docs/reference/index/interfaces/SelectCellRangeOptions.md b/docs/reference/index/interfaces/SelectCellRangeOptions.md index 6eac5e51e8..8c23b34251 100644 --- a/docs/reference/index/interfaces/SelectCellRangeOptions.md +++ b/docs/reference/index/interfaces/SelectCellRangeOptions.md @@ -5,17 +5,34 @@ title: SelectCellRangeOptions # Interface: SelectCellRangeOptions -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L65) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:74](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L74) ## Properties -### additive? +### ~~additive?~~ ```ts optional additive: boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L70) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L86) Whether the range should be added alongside existing ranges rather than replacing them. Defaults to `false`. + +#### Deprecated + +Use `mode: 'include'` instead. + +*** + +### mode? + +```ts +optional mode: CellSelectionRangeMode; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L79) + +Whether to replace the selection, add the range, or subtract the range. +Defaults to `replace`. diff --git a/docs/reference/index/interfaces/TableOptions_CellSelection.md b/docs/reference/index/interfaces/TableOptions_CellSelection.md index 42aef022f3..58dbdccfba 100644 --- a/docs/reference/index/interfaces/TableOptions_CellSelection.md +++ b/docs/reference/index/interfaces/TableOptions_CellSelection.md @@ -5,7 +5,7 @@ title: TableOptions_CellSelection # Interface: TableOptions\_CellSelection\ -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L73) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L89) ## Type Parameters @@ -25,7 +25,7 @@ Defined in: [features/cell-selection/cellSelectionFeature.types.ts:73](https://g optional autoResetCellSelection: boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L86) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L102) Resets cell selection to `initialState.cellSelection` whenever `data` changes. Defaults to `true`. @@ -43,7 +43,7 @@ across data changes, and note `autoResetAll` overrides this. optional enableCellRangeSelection: boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L91) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:107](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L107) Enables inclusive cell range selection through shift-click and drag. Defaults to `true`. @@ -56,7 +56,7 @@ Defaults to `true`. optional enableCellSelection: boolean | (cell) => boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:98](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L98) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:114](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L114) Allows cells to be selected. @@ -71,7 +71,7 @@ its own `enableCellSelection: false`. Defaults to `true`. optional enableCellSelectionDrag: boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L104) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L120) Enables extending a selection by dragging across cells. Defaults to `true`. @@ -83,10 +83,9 @@ Enables extending a selection by dragging across cells. Defaults to `true`. optional enableMultiCellRangeSelection: boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L109) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:124](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L124) -Allows multiple disjoint rectangles to be selected at once. Defaults to -`true`. +Allows modifier interactions to add or subtract ranges. Defaults to `true`. *** @@ -96,7 +95,7 @@ Allows multiple disjoint rectangles to be selected at once. Defaults to optional isCellRangeSelectionEvent: (event) => boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:117](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L117) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:132](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L132) Determines whether a selection-start event should extend the active range instead of replacing the selection. @@ -122,10 +121,10 @@ By default, events with `shiftKey` directly on the event or on optional isMultiCellRangeSelectionEvent: (event) => boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:125](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L125) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L140) -Determines whether a selection-start event should add a new rectangle -alongside the existing ones. +Determines whether a selection-start event should add or subtract a new +rectangle. The operation depends on whether the starting cell is selected. By default, events with `ctrlKey` or `metaKey` directly on the event or on `event.nativeEvent` are treated as multi-range events. @@ -148,7 +147,7 @@ By default, events with `ctrlKey` or `metaKey` directly on the event or on optional onCellSelectionChange: OnChangeFn; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:134](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L134) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:149](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L149) Called with an updater when cell selection state changes. Pair this with `state.cellSelection` when using external state; external atoms can own the diff --git a/docs/reference/index/interfaces/Table_CellSelection.md b/docs/reference/index/interfaces/Table_CellSelection.md index aad0a48f68..b27d20139f 100644 --- a/docs/reference/index/interfaces/Table_CellSelection.md +++ b/docs/reference/index/interfaces/Table_CellSelection.md @@ -5,7 +5,7 @@ title: Table_CellSelection # Interface: Table\_CellSelection\ -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:193](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L193) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:208](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L208) ## Type Parameters @@ -25,7 +25,7 @@ Defined in: [features/cell-selection/cellSelectionFeature.types.ts:193](https:// _isSelectingCells: boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:206](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L206) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:221](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L221) **`Internal`** @@ -43,7 +43,7 @@ selection persisted mid-drag cannot rehydrate into a stuck drag. autoResetCellSelection: () => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:213](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L213) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:228](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L228) Schedules a cell selection reset after `data` changes. @@ -62,7 +62,7 @@ model; you rarely need to invoke it yourself. extendCellSelection: (direction) => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:217](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L217) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:232](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L232) Extends the active range one step in a direction, keeping its anchor fixed. @@ -84,9 +84,10 @@ Extends the active range one step in a direction, keeping its anchor fixed. getCellSelectionBounds: () => CellSelectionBounds[]; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:224](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L224) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:240](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L240) -Returns the selected ranges resolved into inclusive display-order indexes. +Returns the final positive selection as disjoint, inclusive display-order +index rectangles after all include and exclude operations are applied. This is the memoized cache every per-cell read goes through. Ranges whose corners no longer resolve are omitted. @@ -103,7 +104,7 @@ corners no longer resolve are omitted. getCellSelectionColumnIds: () => string[]; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:228](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L228) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:244](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L244) Returns the ids of all columns intersected by the selection. @@ -119,7 +120,7 @@ Returns the ids of all columns intersected by the selection. getCellSelectionColumnIndexes: () => Record; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:237](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L237) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:253](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L253) **`Internal`** @@ -140,7 +141,7 @@ is absent, since its `getColumnIndexes` static rebuilds on every call. getCellSelectionRowIds: () => string[]; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:241](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L241) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:257](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L257) Returns the ids of all rows intersected by the selection. @@ -158,9 +159,10 @@ getFocusedCell: () => | undefined; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:245](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L245) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:262](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L262) -Returns the active cell, i.e. the anchor of the most recent range. +Returns the active cell, i.e. the anchor of the most recent operation. +An exclusion's active cell is focused even though it is not selected. #### Returns @@ -175,12 +177,12 @@ Returns the active cell, i.e. the anchor of the most recent range. getSelectedCellCount: () => number; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:252](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L252) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:269](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L269) Returns the number of selected cells. -Computed as rectangle arithmetic for one range. Falls back to enumerating -cells for overlapping ranges or a per-cell `enableCellSelection` predicate. +Computed with rectangle arithmetic unless a per-cell +`enableCellSelection` predicate requires enumeration. #### Returns @@ -194,7 +196,7 @@ cells for overlapping ranges or a per-cell `enableCellSelection` predicate. getSelectedCellIds: () => string[]; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:259](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L259) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:276](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L276) Returns the unique ids of all selected cells, in row-major order. @@ -213,11 +215,11 @@ It is memoized and never runs unless called. getSelectedCellRangesData: () => unknown[][][]; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:267](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L267) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:284](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L284) -Returns each selected range's values as a row-major grid. +Returns each final positive selection region's values as a row-major grid. -Indexed as `[rangeIndex][rowIndex][columnIndex]`. Serializing this to +Indexed as `[regionIndex][rowIndex][columnIndex]`. Serializing this to clipboard text is left to userland, since the delimiter, the null representation, and any quoting rules are application decisions. @@ -233,7 +235,7 @@ representation, and any quoting rules are application decisions. moveCellSelection: (direction) => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:272](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L272) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:289](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L289) Moves the selection one step in a direction, collapsing it to a single cell. Columns that cannot be selected are skipped over. @@ -256,7 +258,7 @@ cell. Columns that cannot be selected are skipped over. resetCellSelection: (defaultState?) => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:278](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L278) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:295](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L295) Resets `cellSelection` to `initialState.cellSelection`. @@ -280,7 +282,7 @@ Pass `true` to ignore initial state and reset to an empty selection. selectAllCells: () => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:282](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L282) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:299](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L299) Selects every selectable cell in the table as one range. @@ -296,9 +298,9 @@ Selects every selectable cell in the table as one range. selectCellRange: (range, opts?) => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:286](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L286) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:303](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L303) -Selects a rectangle, replacing the current selection unless `additive`. +Selects a rectangle using the requested replace/include/exclude mode. #### Parameters @@ -322,7 +324,7 @@ Selects a rectangle, replacing the current selection unless `additive`. setCellSelection: (updater) => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:293](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L293) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:310](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L310) Updates cell selection state with a next value or updater function. @@ -344,7 +346,7 @@ Updates cell selection state with a next value or updater function. setFocusedCell: (rowId, columnId) => void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:297](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L297) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:314](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L314) Collapses the selection to a single cell at the given coordinates. diff --git a/docs/reference/index/type-aliases/CellSelectionRangeMode.md b/docs/reference/index/type-aliases/CellSelectionRangeMode.md new file mode 100644 index 0000000000..bc7c3f3395 --- /dev/null +++ b/docs/reference/index/type-aliases/CellSelectionRangeMode.md @@ -0,0 +1,12 @@ +--- +id: CellSelectionRangeMode +title: CellSelectionRangeMode +--- + +# Type Alias: CellSelectionRangeMode + +```ts +type CellSelectionRangeMode = "replace" | "include" | "exclude"; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L72) diff --git a/docs/reference/index/type-aliases/CellSelectionRangeOperation.md b/docs/reference/index/type-aliases/CellSelectionRangeOperation.md new file mode 100644 index 0000000000..0d6d8949b8 --- /dev/null +++ b/docs/reference/index/type-aliases/CellSelectionRangeOperation.md @@ -0,0 +1,12 @@ +--- +id: CellSelectionRangeOperation +title: CellSelectionRangeOperation +--- + +# Type Alias: CellSelectionRangeOperation + +```ts +type CellSelectionRangeOperation = "include" | "exclude"; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L31) diff --git a/docs/reference/index/type-aliases/CellSelectionState.md b/docs/reference/index/type-aliases/CellSelectionState.md index e2639ba564..e2d824aa33 100644 --- a/docs/reference/index/type-aliases/CellSelectionState.md +++ b/docs/reference/index/type-aliases/CellSelectionState.md @@ -9,10 +9,10 @@ title: CellSelectionState type CellSelectionState = CellSelectionRange[]; ``` -Defined in: [features/cell-selection/cellSelectionFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L34) +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L41) -The selected rectangles. The last entry is the active one that shift-extend -and drag operate on. +Ordered range operations that produce the final selection. The last entry is +the active operation that shift-extend and drag operate on. A bare array, matching `SortingState` and `ColumnFiltersState`. Drag session state deliberately lives outside this slice as non-reactive instance data, so diff --git a/docs/reference/static-functions/functions/cell_getIsFocused.md b/docs/reference/static-functions/functions/cell_getIsFocused.md index cf1787c7e3..47f407d0ab 100644 --- a/docs/reference/static-functions/functions/cell_getIsFocused.md +++ b/docs/reference/static-functions/functions/cell_getIsFocused.md @@ -9,7 +9,7 @@ title: cell_getIsFocused function cell_getIsFocused(cell): boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:420](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L420) +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:424](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L424) Checks whether this cell is the active cell. diff --git a/docs/reference/static-functions/functions/cell_getIsSelected.md b/docs/reference/static-functions/functions/cell_getIsSelected.md index 204ee18839..cd0c242835 100644 --- a/docs/reference/static-functions/functions/cell_getIsSelected.md +++ b/docs/reference/static-functions/functions/cell_getIsSelected.md @@ -9,9 +9,9 @@ title: cell_getIsSelected function cell_getIsSelected(cell): boolean; ``` -Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:396](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L396) +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:400](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L400) -Checks whether this cell falls inside any selected range. +Checks whether this cell falls inside the final positive selection. Deliberately not memoized. Registering this through `assignPrototypeAPIs` with `memoDeps` would allocate a memo closure and dependency array per cell, diff --git a/docs/reference/static-functions/functions/table_getCellSelectionBounds.md b/docs/reference/static-functions/functions/table_getCellSelectionBounds.md index 17968a8081..1b6d027855 100644 --- a/docs/reference/static-functions/functions/table_getCellSelectionBounds.md +++ b/docs/reference/static-functions/functions/table_getCellSelectionBounds.md @@ -9,9 +9,10 @@ title: table_getCellSelectionBounds function table_getCellSelectionBounds(table): CellSelectionBounds[]; ``` -Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:243](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L243) +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:246](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L246) -Resolves the selected ranges into inclusive display-order index rectangles. +Resolves ordered range operations into disjoint, positive display-order +index rectangles. This is the single cache every per-cell read goes through, so index lookups happen once per invalidation rather than once per cell. A range whose corners diff --git a/docs/reference/static-functions/functions/table_getFocusedCell.md b/docs/reference/static-functions/functions/table_getFocusedCell.md index c6d7711f2c..b8c197cb9d 100644 --- a/docs/reference/static-functions/functions/table_getFocusedCell.md +++ b/docs/reference/static-functions/functions/table_getFocusedCell.md @@ -11,9 +11,9 @@ function table_getFocusedCell(table): | undefined; ``` -Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:506](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L506) +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:510](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L510) -Returns the active cell, i.e. the anchor of the most recent range. +Returns the active cell, i.e. the anchor of the most recent operation. Focus is derived rather than stored: in spreadsheet semantics, dragging from A1 to C5 leaves the active cell at A1, so the active range's anchor already diff --git a/docs/reference/static-functions/functions/table_getSelectedCellCount.md b/docs/reference/static-functions/functions/table_getSelectedCellCount.md index 55b2e31856..484354f3e9 100644 --- a/docs/reference/static-functions/functions/table_getSelectedCellCount.md +++ b/docs/reference/static-functions/functions/table_getSelectedCellCount.md @@ -9,13 +9,12 @@ title: table_getSelectedCellCount function table_getSelectedCellCount(table): number; ``` -Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:932](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L932) +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:940](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L940) Returns the number of selected cells. -Uses rectangle arithmetic for a single range, which needs no expansion. -Multiple ranges are enumerated so overlapping cells are counted once. A -per-cell `enableCellSelection` predicate also requires enumeration. +Uses rectangle arithmetic over the normalized, disjoint positive regions. +A per-cell `enableCellSelection` predicate requires enumeration. ## Type Parameters diff --git a/docs/reference/static-functions/functions/table_getSelectedCellRangesData.md b/docs/reference/static-functions/functions/table_getSelectedCellRangesData.md index 60d667abb6..d9f3204175 100644 --- a/docs/reference/static-functions/functions/table_getSelectedCellRangesData.md +++ b/docs/reference/static-functions/functions/table_getSelectedCellRangesData.md @@ -9,9 +9,9 @@ title: table_getSelectedCellRangesData function table_getSelectedCellRangesData(table): unknown[][][]; ``` -Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:905](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L905) +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:914](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L914) -Returns each selected range's values as a row-major grid. +Returns each final positive region's values as a row-major grid. This is the raw material for clipboard export. Serializing it to text is left to userland, since the delimiter, the null representation, and whether values diff --git a/docs/reference/static-functions/functions/table_selectCellRange.md b/docs/reference/static-functions/functions/table_selectCellRange.md index 5a70937fdc..e573036123 100644 --- a/docs/reference/static-functions/functions/table_selectCellRange.md +++ b/docs/reference/static-functions/functions/table_selectCellRange.md @@ -12,9 +12,9 @@ function table_selectCellRange( opts?): void; ``` -Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:554](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L554) +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:558](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L558) -Selects a rectangle, replacing the current selection unless `additive`. +Selects a rectangle using replace, include, or exclude semantics. ## Type Parameters @@ -47,5 +47,5 @@ Selects a rectangle, replacing the current selection unless `additive`. ## Example ```ts -table_selectCellRange(table, range, { additive: true }) +table_selectCellRange(table, range, { mode: 'exclude' }) ``` diff --git a/examples/alpine/cell-selection/src/main.ts b/examples/alpine/cell-selection/src/main.ts index 02caf63dc4..d55af147fe 100644 --- a/examples/alpine/cell-selection/src/main.ts +++ b/examples/alpine/cell-selection/src/main.ts @@ -80,7 +80,7 @@ function toTsv(ranges: Array>>) { .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) - .join('\n\n') // blank line between disjoint rectangles + .join('\n\n') // blank line between final selected regions } Alpine.data('table', () => { diff --git a/examples/angular/cell-selection/src/app/app.html b/examples/angular/cell-selection/src/app/app.html index b9af69373d..a760dcaf4c 100644 --- a/examples/angular/cell-selection/src/app/app.html +++ b/examples/angular/cell-selection/src/app/app.html @@ -8,9 +8,9 @@

Click and drag to select a range of cells. Hold Shift while clicking to extend the selection, or - Ctrl/Cmd to add a second rectangle. Arrow keys move the selection, Shift+Arrow extends it, Mod+A - selects all, Mod+C copies, and Escape clears. Uncomment `enableCellSelection: false` on a column - def to opt that column out of selection. + Ctrl/Cmd to add or subtract a rectangle. Arrow keys move the selection, Shift+Arrow extends it, + Mod+A selects all, Mod+C copies, and Escape clears. Uncomment `enableCellSelection: false` on a + column def to opt that column out of selection.

Hiding, reordering, and pinning columns all keep a live selection anchored to the same cell ids. diff --git a/examples/angular/cell-selection/src/app/app.ts b/examples/angular/cell-selection/src/app/app.ts index d8ca73e2b1..87ffcdc599 100644 --- a/examples/angular/cell-selection/src/app/app.ts +++ b/examples/angular/cell-selection/src/app/app.ts @@ -44,7 +44,7 @@ function toTsv(ranges: Array>>) { .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) - .join('\n\n') // blank line between disjoint rectangles + .join('\n\n') // blank line between final selected regions } @Component({ diff --git a/examples/lit/cell-selection/src/main.ts b/examples/lit/cell-selection/src/main.ts index df27b2ec5d..c960eb7c98 100644 --- a/examples/lit/cell-selection/src/main.ts +++ b/examples/lit/cell-selection/src/main.ts @@ -58,7 +58,7 @@ function toTsv(ranges: Array>>) { .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) - .join('\n\n') // blank line between disjoint rectangles + .join('\n\n') // blank line between final selected regions } function getCellClassName(cell: Cell) { @@ -321,9 +321,9 @@ export class LitCellSelectionExample extends LitElement {

Click and drag to select a range of cells. Hold Shift while clicking to - extend the selection, or Ctrl/Cmd to add a second rectangle. Arrow keys - move the selection, Shift+Arrow extends it, Mod+A selects all, Mod+C - copies, and Escape clears. Uncomment + extend the selection, or Ctrl/Cmd to add or subtract a rectangle. Arrow + keys move the selection, Shift+Arrow extends it, Mod+A selects all, + Mod+C copies, and Escape clears. Uncomment enableCellSelection: false on a column def to opt that column out of selection.

diff --git a/examples/preact/cell-selection/src/main.tsx b/examples/preact/cell-selection/src/main.tsx index 96c3bcd6c0..9ae701d18b 100644 --- a/examples/preact/cell-selection/src/main.tsx +++ b/examples/preact/cell-selection/src/main.tsx @@ -70,7 +70,7 @@ function toTsv(ranges: Array>>) { .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) - .join('\n\n') // blank line between disjoint rectangles + .join('\n\n') // blank line between final selected regions } function App() { @@ -259,10 +259,10 @@ function App() {

Click and drag to select a range of cells. Hold Shift while clicking to - extend the selection, or Ctrl/Cmd to add a second rectangle. Arrow keys - move the selection, Shift+Arrow extends it, Mod+A selects all, Mod+C - copies, and Escape clears. Uncomment `enableCellSelection: false` on a - column def to opt that column out of selection. + extend the selection, or Ctrl/Cmd to add or subtract a rectangle. Arrow + keys move the selection, Shift+Arrow extends it, Mod+A selects all, + Mod+C copies, and Escape clears. Uncomment `enableCellSelection: false` + on a column def to opt that column out of selection.

Hiding, reordering, and pinning columns all keep a live selection diff --git a/examples/react/cell-selection/src/main.tsx b/examples/react/cell-selection/src/main.tsx index 8fc05047a1..ca96dcbe1b 100644 --- a/examples/react/cell-selection/src/main.tsx +++ b/examples/react/cell-selection/src/main.tsx @@ -70,7 +70,7 @@ function toTsv(ranges: Array>>) { .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) - .join('\n\n') // blank line between disjoint rectangles + .join('\n\n') // blank line between final selected regions } function App() { @@ -259,10 +259,10 @@ function App() {

Click and drag to select a range of cells. Hold Shift while clicking to - extend the selection, or Ctrl/Cmd to add a second rectangle. Arrow keys - move the selection, Shift+Arrow extends it, Mod+A selects all, Mod+C - copies, and Escape clears. Uncomment `enableCellSelection: false` on a - column def to opt that column out of selection. + extend the selection, or Ctrl/Cmd to add or subtract a rectangle. Arrow + keys move the selection, Shift+Arrow extends it, Mod+A selects all, + Mod+C copies, and Escape clears. Uncomment `enableCellSelection: false` + on a column def to opt that column out of selection.

Hiding, reordering, and pinning columns all keep a live selection diff --git a/examples/react/cell-selection/tests/e2e/smoke.spec.ts b/examples/react/cell-selection/tests/e2e/smoke.spec.ts index b66bd38b7a..06dd600505 100644 --- a/examples/react/cell-selection/tests/e2e/smoke.spec.ts +++ b/examples/react/cell-selection/tests/e2e/smoke.spec.ts @@ -112,3 +112,30 @@ test('selects an inclusive cell range with Shift-click', async ({ page }) => { await server.close() } }) + +test('subtracts a selected cell with Ctrl/Cmd-click', async ({ page }) => { + const { errors, server } = await openExample(page) + + try { + const rows = page.locator('table').first().locator('tbody tr') + const center = rows.nth(1).locator('td').nth(1) + await expect(rows.first()).toBeVisible() + + await rows.nth(0).locator('td').nth(0).click() + await rows + .nth(2) + .locator('td') + .nth(2) + .click({ modifiers: ['Shift'] }) + await expect(page.locator('td.cell-selected')).toHaveCount(9) + + await center.click({ modifiers: ['ControlOrMeta'] }) + + await expect(page.locator('td.cell-selected')).toHaveCount(8) + await expect(center).not.toHaveClass(/cell-selected/) + await expect(center).toHaveClass(/cell-focused/) + expect(errors).toEqual([]) + } finally { + await server.close() + } +}) diff --git a/examples/react/spreadsheet/tests/e2e/spreadsheet.spec.ts b/examples/react/spreadsheet/tests/e2e/spreadsheet.spec.ts index 13bd49d530..1a799a6666 100644 --- a/examples/react/spreadsheet/tests/e2e/spreadsheet.spec.ts +++ b/examples/react/spreadsheet/tests/e2e/spreadsheet.spec.ts @@ -73,6 +73,29 @@ test('renders a virtualized spreadsheet without page errors', async ({ } }) +test('subtracts cells from a selection with Ctrl/Cmd', async ({ page }) => { + const { errors, server } = await openExample(page) + + try { + const start = cell(page, 1, 0) + const end = cell(page, 3, 2) + const center = cell(page, 2, 1) + + await start.click() + await end.click({ modifiers: ['Shift'] }) + await expect(page.locator('[data-sheet-cell].cell-selected')).toHaveCount(9) + + await center.click({ modifiers: ['ControlOrMeta'] }) + + await expect(page.locator('[data-sheet-cell].cell-selected')).toHaveCount(8) + await expect(center).not.toHaveClass(/cell-selected/) + await expect(center).toHaveClass(/cell-focused/) + expect(errors).toEqual([]) + } finally { + await server.close() + } +}) + test('edits a cell and supports atomic undo and redo', async ({ page }) => { const { errors, server } = await openExample(page) diff --git a/examples/solid/cell-selection/src/App.tsx b/examples/solid/cell-selection/src/App.tsx index 97dfe82268..67c6b9a2dc 100644 --- a/examples/solid/cell-selection/src/App.tsx +++ b/examples/solid/cell-selection/src/App.tsx @@ -60,7 +60,7 @@ function toTsv(ranges: Array>>) { .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) - .join('\n\n') // blank line between disjoint rectangles + .join('\n\n') // blank line between final selected regions } function getCellClassName(cell: Cell) { @@ -255,10 +255,10 @@ function App() {

Click and drag to select a range of cells. Hold Shift while clicking to - extend the selection, or Ctrl/Cmd to add a second rectangle. Arrow keys - move the selection, Shift+Arrow extends it, Mod+A selects all, Mod+C - copies, and Escape clears. Uncomment `enableCellSelection: false` on a - column def to opt that column out of selection. + extend the selection, or Ctrl/Cmd to add or subtract a rectangle. Arrow + keys move the selection, Shift+Arrow extends it, Mod+A selects all, + Mod+C copies, and Escape clears. Uncomment `enableCellSelection: false` + on a column def to opt that column out of selection.

Hiding, reordering, and pinning columns all keep a live selection diff --git a/examples/svelte/cell-selection/src/App.svelte b/examples/svelte/cell-selection/src/App.svelte index 945dacabbc..675cabda69 100644 --- a/examples/svelte/cell-selection/src/App.svelte +++ b/examples/svelte/cell-selection/src/App.svelte @@ -61,7 +61,7 @@ .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) - .join('\n\n') // blank line between disjoint rectangles + .join('\n\n') // blank line between final selected regions } function getCellClassName(cell: Cell) { @@ -238,7 +238,7 @@

Click and drag to select a range of cells. Hold Shift while clicking to - extend the selection, or Ctrl/Cmd to add a second rectangle. Arrow keys move + extend the selection, or Ctrl/Cmd to add or subtract a rectangle. Arrow keys move the selection, Shift+Arrow extends it, Mod+A selects all, Mod+C copies, and Escape clears. Uncomment `enableCellSelection: false` on a column def to opt that column out of selection. diff --git a/examples/vue/cell-selection/src/App.vue b/examples/vue/cell-selection/src/App.vue index fde8165c2b..e6a8a87439 100644 --- a/examples/vue/cell-selection/src/App.vue +++ b/examples/vue/cell-selection/src/App.vue @@ -61,7 +61,7 @@ function toTsv(ranges: Array>>) { .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) - .join('\n\n') // blank line between disjoint rectangles + .join('\n\n') // blank line between final selected regions } function getCellClassName(cell: Cell) { @@ -250,8 +250,8 @@ useHotkeys(

Click and drag to select a range of cells. Hold Shift while clicking to - extend the selection, or Ctrl/Cmd to add a second rectangle. Arrow keys - move the selection, Shift+Arrow extends it, Mod+A selects all, Mod+C + extend the selection, or Ctrl/Cmd to add or subtract a rectangle. Arrow + keys move the selection, Shift+Arrow extends it, Mod+A selects all, Mod+C copies, and Escape clears. Uncomment `enableCellSelection: false` on a column def to opt that column out of selection.

diff --git a/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts b/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts index 97f2140e9c..60c49b3d52 100644 --- a/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts +++ b/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts @@ -21,11 +21,18 @@ export interface CellSelectionRange { anchorRowId: string focusColumnId: string focusRowId: string + /** + * How this range changes the selection produced by the ranges before it. + * Defaults to `include`. + */ + operation?: CellSelectionRangeOperation } +export type CellSelectionRangeOperation = 'include' | 'exclude' + /** - * The selected rectangles. The last entry is the active one that shift-extend - * and drag operate on. + * Ordered range operations that produce the final selection. The last entry is + * the active operation that shift-extend and drag operate on. * * A bare array, matching `SortingState` and `ColumnFiltersState`. Drag session * state deliberately lives outside this slice as non-reactive instance data, so @@ -62,10 +69,19 @@ export interface CellSelectionEdges { export type CellSelectionDirection = 'up' | 'down' | 'left' | 'right' +export type CellSelectionRangeMode = 'replace' | 'include' | 'exclude' + export interface SelectCellRangeOptions { + /** + * Whether to replace the selection, add the range, or subtract the range. + * Defaults to `replace`. + */ + mode?: CellSelectionRangeMode /** * Whether the range should be added alongside existing ranges rather than * replacing them. Defaults to `false`. + * + * @deprecated Use `mode: 'include'` instead. */ additive?: boolean } @@ -103,8 +119,7 @@ export interface TableOptions_CellSelection< */ enableCellSelectionDrag?: boolean /** - * Allows multiple disjoint rectangles to be selected at once. Defaults to - * `true`. + * Allows modifier interactions to add or subtract ranges. Defaults to `true`. */ enableMultiCellRangeSelection?: boolean /** @@ -116,8 +131,8 @@ export interface TableOptions_CellSelection< */ isCellRangeSelectionEvent?: (event: unknown) => boolean /** - * Determines whether a selection-start event should add a new rectangle - * alongside the existing ones. + * Determines whether a selection-start event should add or subtract a new + * rectangle. The operation depends on whether the starting cell is selected. * * By default, events with `ctrlKey` or `metaKey` directly on the event or on * `event.nativeEvent` are treated as multi-range events. @@ -152,7 +167,7 @@ export interface Cell_CellSelection { */ getIsFocused: () => boolean /** - * Checks whether this cell falls inside any selected range. + * Checks whether this cell falls inside the final positive selection. */ getIsSelected: () => boolean /** @@ -216,7 +231,8 @@ export interface Table_CellSelection< */ extendCellSelection: (direction: CellSelectionDirection) => void /** - * Returns the selected ranges resolved into inclusive display-order indexes. + * Returns the final positive selection as disjoint, inclusive display-order + * index rectangles after all include and exclude operations are applied. * * This is the memoized cache every per-cell read goes through. Ranges whose * corners no longer resolve are omitted. @@ -240,14 +256,15 @@ export interface Table_CellSelection< */ getCellSelectionRowIds: () => Array /** - * Returns the active cell, i.e. the anchor of the most recent range. + * Returns the active cell, i.e. the anchor of the most recent operation. + * An exclusion's active cell is focused even though it is not selected. */ getFocusedCell: () => Cell | undefined /** * Returns the number of selected cells. * - * Computed as rectangle arithmetic for one range. Falls back to enumerating - * cells for overlapping ranges or a per-cell `enableCellSelection` predicate. + * Computed with rectangle arithmetic unless a per-cell + * `enableCellSelection` predicate requires enumeration. */ getSelectedCellCount: () => number /** @@ -258,9 +275,9 @@ export interface Table_CellSelection< */ getSelectedCellIds: () => Array /** - * Returns each selected range's values as a row-major grid. + * Returns each final positive selection region's values as a row-major grid. * - * Indexed as `[rangeIndex][rowIndex][columnIndex]`. Serializing this to + * Indexed as `[regionIndex][rowIndex][columnIndex]`. Serializing this to * clipboard text is left to userland, since the delimiter, the null * representation, and any quoting rules are application decisions. */ @@ -281,7 +298,7 @@ export interface Table_CellSelection< */ selectAllCells: () => void /** - * Selects a rectangle, replacing the current selection unless `additive`. + * Selects a rectangle using the requested replace/include/exclude mode. */ selectCellRange: ( range: CellSelectionRange, diff --git a/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts b/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts index 41b2a0c897..0c9ffcf7b7 100644 --- a/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts +++ b/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts @@ -1,5 +1,7 @@ import { callMemoOrStaticFn, cloneState, makeObjectMap } from '../../utils' import { table_getVisibleLeafColumns } from '../column-visibility/columnVisibilityFeature.utils' +import { applyCellSelectionBoundsOperations } from './cellSelectionGeometry' +import type { CellSelectionBoundsOperation } from './cellSelectionGeometry' import type { CellData, RowData, Updater } from '../../types/type-utils' import type { TableFeatures } from '../../types/TableFeatures' import type { Table_Internal } from '../../types/Table' @@ -227,7 +229,8 @@ function resolveRowIndex< } /** - * Resolves the selected ranges into inclusive display-order index rectangles. + * Resolves ordered range operations into disjoint, positive display-order + * index rectangles. * * This is the single cache every per-cell read goes through, so index lookups * happen once per invalidation rather than once per cell. A range whose corners @@ -255,7 +258,7 @@ export function table_getCellSelectionBounds< table_getCellSelectionColumnIndexes, ) - const bounds: Array = [] + const operations: Array = [] for (let i = 0; i < ranges.length; i++) { const range = ranges[i]! @@ -273,15 +276,16 @@ export function table_getCellSelectionBounds< continue } - bounds.push({ + operations.push({ minRowIndex: Math.min(anchorRowIndex, focusRowIndex), maxRowIndex: Math.max(anchorRowIndex, focusRowIndex), minColumnIndex: Math.min(anchorColumnIndex, focusColumnIndex), maxColumnIndex: Math.max(anchorColumnIndex, focusColumnIndex), + operation: range.operation ?? 'include', }) } - return bounds + return applyCellSelectionBoundsOperations(operations) } /** @@ -382,7 +386,7 @@ function resolveCellPosition< } /** - * Checks whether this cell falls inside any selected range. + * Checks whether this cell falls inside the final positive selection. * * Deliberately not memoized. Registering this through `assignPrototypeAPIs` * with `memoDeps` would allocate a memo closure and dependency array per cell, @@ -492,7 +496,7 @@ export function cell_getSelectionEdges< // Focus APIs /** - * Returns the active cell, i.e. the anchor of the most recent range. + * Returns the active cell, i.e. the anchor of the most recent operation. * * Focus is derived rather than stored: in spreadsheet semantics, dragging from * A1 to C5 leaves the active cell at A1, so the active range's anchor already @@ -544,11 +548,11 @@ export function table_setFocusedCell< // Selection writes /** - * Selects a rectangle, replacing the current selection unless `additive`. + * Selects a rectangle using replace, include, or exclude semantics. * * @example * ```ts - * table_selectCellRange(table, range, { additive: true }) + * table_selectCellRange(table, range, { mode: 'exclude' }) * ``` */ export function table_selectCellRange< @@ -559,8 +563,13 @@ export function table_selectCellRange< range: CellSelectionRange, opts?: SelectCellRangeOptions, ) { + const mode = opts?.mode ?? (opts?.additive ? 'include' : 'replace') + const { operation: _operation, ...coordinates } = range + const nextRange: CellSelectionRange = + mode === 'exclude' ? { ...coordinates, operation: 'exclude' } : coordinates + table_setCellSelection(table, (old) => - opts?.additive ? [...old, range] : [range], + mode === 'replace' ? [nextRange] : [...old, nextRange], ) } @@ -797,7 +806,7 @@ export function table_extendCellSelection< // Derived selection data /** - * Walks each resolved rectangle, invoking a visitor per selectable cell. + * Walks each final positive region, invoking a visitor per selectable cell. * * Every expansion API shares this so the per-cell enable predicate is applied * in exactly one place. @@ -891,7 +900,7 @@ export function table_getSelectedCellIds< } /** - * Returns each selected range's values as a row-major grid. + * Returns each final positive region's values as a row-major grid. * * This is the raw material for clipboard export. Serializing it to text is left * to userland, since the delimiter, the null representation, and whether values @@ -920,9 +929,8 @@ export function table_getSelectedCellRangesData< /** * Returns the number of selected cells. * - * Uses rectangle arithmetic for a single range, which needs no expansion. - * Multiple ranges are enumerated so overlapping cells are counted once. A - * per-cell `enableCellSelection` predicate also requires enumeration. + * Uses rectangle arithmetic over the normalized, disjoint positive regions. + * A per-cell `enableCellSelection` predicate requires enumeration. * * @example * ```ts @@ -943,31 +951,30 @@ export function table_getSelectedCellCount< if (!bounds.length) return 0 - if ( - bounds.length > 1 || - typeof table.options.enableCellSelection === 'function' - ) { + if (typeof table.options.enableCellSelection === 'function') { const ids = new Set() forEachSelectedCell(table, (cell) => ids.add(cell.id)) return ids.size } const columns = getDisplayOrderedColumns(table) - const bound = bounds[0]! - let selectableColumns = 0 - - for ( - let columnIndex = bound.minColumnIndex; - columnIndex <= bound.maxColumnIndex; - columnIndex++ - ) { - const column = columns[columnIndex] - if (!column) continue - const columnDef = column.columnDef as Partial - if (columnDef.enableCellSelection !== false) selectableColumns++ + let count = 0 + for (const bound of bounds) { + let selectableColumns = 0 + for ( + let columnIndex = bound.minColumnIndex; + columnIndex <= bound.maxColumnIndex; + columnIndex++ + ) { + const column = columns[columnIndex] + if (!column) continue + const columnDef = column.columnDef as Partial + if (columnDef.enableCellSelection !== false) selectableColumns++ + } + count += (bound.maxRowIndex - bound.minRowIndex + 1) * selectableColumns } - return (bound.maxRowIndex - bound.minRowIndex + 1) * selectableColumns + return count } /** @@ -1105,6 +1112,9 @@ export function cell_getSelectionStartHandler< const rowId = cell.row.id const columnId = cell.column.id + const shouldExclude = + isMultiRangeEvent && + callMemoOrStaticFn(cell, 'getIsSelected', cell_getIsSelected) table_setCellSelection(table, (old) => { const active = old[old.length - 1] @@ -1121,6 +1131,7 @@ export function cell_getSelectionStartHandler< anchorColumnId: columnId, focusRowId: rowId, focusColumnId: columnId, + ...(shouldExclude ? { operation: 'exclude' as const } : {}), } return isMultiRangeEvent ? [...old, range] : [range] diff --git a/packages/table-core/src/features/cell-selection/cellSelectionGeometry.ts b/packages/table-core/src/features/cell-selection/cellSelectionGeometry.ts new file mode 100644 index 0000000000..f6f9a0955a --- /dev/null +++ b/packages/table-core/src/features/cell-selection/cellSelectionGeometry.ts @@ -0,0 +1,169 @@ +import type { + CellSelectionBounds, + CellSelectionRangeOperation, +} from './cellSelectionFeature.types' + +export interface CellSelectionBoundsOperation extends CellSelectionBounds { + operation: CellSelectionRangeOperation +} + +function compareBounds(a: CellSelectionBounds, b: CellSelectionBounds) { + return ( + a.minRowIndex - b.minRowIndex || + a.minColumnIndex - b.minColumnIndex || + a.maxRowIndex - b.maxRowIndex || + a.maxColumnIndex - b.maxColumnIndex + ) +} + +export function intersectCellSelectionBounds( + a: CellSelectionBounds, + b: CellSelectionBounds, +): CellSelectionBounds | undefined { + const intersection = { + minRowIndex: Math.max(a.minRowIndex, b.minRowIndex), + maxRowIndex: Math.min(a.maxRowIndex, b.maxRowIndex), + minColumnIndex: Math.max(a.minColumnIndex, b.minColumnIndex), + maxColumnIndex: Math.min(a.maxColumnIndex, b.maxColumnIndex), + } + + return intersection.minRowIndex <= intersection.maxRowIndex && + intersection.minColumnIndex <= intersection.maxColumnIndex + ? intersection + : undefined +} + +export function subtractCellSelectionBounds( + source: CellSelectionBounds, + excluded: CellSelectionBounds, +): Array { + const intersection = intersectCellSelectionBounds(source, excluded) + if (!intersection) return [source] + + const result: Array = [] + + if (source.minRowIndex < intersection.minRowIndex) { + result.push({ + ...source, + maxRowIndex: intersection.minRowIndex - 1, + }) + } + if (intersection.maxRowIndex < source.maxRowIndex) { + result.push({ + ...source, + minRowIndex: intersection.maxRowIndex + 1, + }) + } + if (source.minColumnIndex < intersection.minColumnIndex) { + result.push({ + minRowIndex: intersection.minRowIndex, + maxRowIndex: intersection.maxRowIndex, + minColumnIndex: source.minColumnIndex, + maxColumnIndex: intersection.minColumnIndex - 1, + }) + } + if (intersection.maxColumnIndex < source.maxColumnIndex) { + result.push({ + minRowIndex: intersection.minRowIndex, + maxRowIndex: intersection.maxRowIndex, + minColumnIndex: intersection.maxColumnIndex + 1, + maxColumnIndex: source.maxColumnIndex, + }) + } + + return result +} + +function mergePair( + a: CellSelectionBounds, + b: CellSelectionBounds, +): CellSelectionBounds | undefined { + if ( + a.minRowIndex === b.minRowIndex && + a.maxRowIndex === b.maxRowIndex && + (a.maxColumnIndex + 1 === b.minColumnIndex || + b.maxColumnIndex + 1 === a.minColumnIndex) + ) { + return { + minRowIndex: a.minRowIndex, + maxRowIndex: a.maxRowIndex, + minColumnIndex: Math.min(a.minColumnIndex, b.minColumnIndex), + maxColumnIndex: Math.max(a.maxColumnIndex, b.maxColumnIndex), + } + } + + if ( + a.minColumnIndex === b.minColumnIndex && + a.maxColumnIndex === b.maxColumnIndex && + (a.maxRowIndex + 1 === b.minRowIndex || b.maxRowIndex + 1 === a.minRowIndex) + ) { + return { + minRowIndex: Math.min(a.minRowIndex, b.minRowIndex), + maxRowIndex: Math.max(a.maxRowIndex, b.maxRowIndex), + minColumnIndex: a.minColumnIndex, + maxColumnIndex: a.maxColumnIndex, + } + } + + return undefined +} + +export function mergeAdjacentCellSelectionBounds( + input: ReadonlyArray, +): Array { + const result = input.slice() + + for (let i = 0; i < result.length; i++) { + for (let j = i + 1; j < result.length; j++) { + const merged = mergePair(result[i]!, result[j]!) + if (!merged) continue + + result.splice(j, 1) + result[i] = merged + i = -1 + break + } + } + + return result.sort(compareBounds) +} + +export function addCellSelectionBounds( + selected: ReadonlyArray, + included: CellSelectionBounds, +): Array { + let fragments = [included] + + for (const existing of selected) { + fragments = fragments.flatMap((fragment) => + subtractCellSelectionBounds(fragment, existing), + ) + if (!fragments.length) return selected.slice() + } + + return mergeAdjacentCellSelectionBounds([...selected, ...fragments]) +} + +export function applyCellSelectionBoundsOperations( + operations: ReadonlyArray, +): Array { + let selected: Array = [] + + for (const operation of operations) { + const bounds: CellSelectionBounds = { + minRowIndex: operation.minRowIndex, + maxRowIndex: operation.maxRowIndex, + minColumnIndex: operation.minColumnIndex, + maxColumnIndex: operation.maxColumnIndex, + } + if (operation.operation === 'exclude') { + selected = mergeAdjacentCellSelectionBounds( + selected.flatMap((bound) => subtractCellSelectionBounds(bound, bounds)), + ) + } else { + selected = addCellSelectionBounds(selected, bounds) + } + } + + return selected.sort(compareBounds) +} diff --git a/packages/table-core/tests/implementation/features/cell-selection/cellSelectionFeature.test.ts b/packages/table-core/tests/implementation/features/cell-selection/cellSelectionFeature.test.ts index 05369ca956..3ee4cd0c9d 100644 --- a/packages/table-core/tests/implementation/features/cell-selection/cellSelectionFeature.test.ts +++ b/packages/table-core/tests/implementation/features/cell-selection/cellSelectionFeature.test.ts @@ -733,6 +733,106 @@ describe('cellSelectionFeature', () => { expect(table.getSelectedCellCount()).toBe(2) }) + it('ctrl-mousedown on a selected cell subtracts it', () => { + const table = makeTable() + const fake = makeFakeDocument() + + table.selectCellRange(rangeOf('r0', 'a', 'r2', 'c')) + getCell(table, 'r1', 'b').getSelectionStartHandler(fake.document)({ + ctrlKey: true, + }) + + expect(table.atoms.cellSelection.get()).toEqual([ + rangeOf('r0', 'a', 'r2', 'c'), + { ...rangeOf('r1', 'b', 'r1', 'b'), operation: 'exclude' }, + ]) + expect(table.getSelectedCellCount()).toBe(8) + expect(getCell(table, 'r1', 'b').getIsSelected()).toBe(false) + expect(getCell(table, 'r1', 'b').getIsFocused()).toBe(true) + }) + + it('ctrl-drag subtracts cells and shrinking the drag restores them', () => { + const table = makeTable() + const fake = makeFakeDocument() + + table.selectCellRange(rangeOf('r0', 'a', 'r2', 'c')) + getCell(table, 'r1', 'a').getSelectionStartHandler(fake.document)({ + ctrlKey: true, + }) + getCell(table, 'r1', 'c').getSelectionExtendHandler()({}) + expect(table.getSelectedCellCount()).toBe(6) + + getCell(table, 'r1', 'b').getSelectionExtendHandler()({}) + expect(table.getSelectedCellCount()).toBe(7) + expect(table.atoms.cellSelection.get().at(-1)).toEqual({ + ...rangeOf('r1', 'a', 'r1', 'b'), + operation: 'exclude', + }) + }) + + it('shift and keyboard extension preserve an active exclusion', () => { + const table = makeTable() + const fake = makeFakeDocument() + + table.selectCellRange(rangeOf('r0', 'a', 'r2', 'c')) + getCell(table, 'r1', 'a').getSelectionStartHandler(fake.document)({ + ctrlKey: true, + }) + fake.fire('mouseup') + getCell(table, 'r1', 'b').getSelectionStartHandler(fake.document)({ + shiftKey: true, + }) + + expect(table.getSelectedCellCount()).toBe(7) + expect(table.atoms.cellSelection.get().at(-1)).toEqual({ + ...rangeOf('r1', 'a', 'r1', 'b'), + operation: 'exclude', + }) + + table.extendCellSelection('right') + + expect(table.getSelectedCellCount()).toBe(6) + expect(table.atoms.cellSelection.get().at(-1)).toEqual({ + ...rangeOf('r1', 'a', 'r1', 'c'), + operation: 'exclude', + }) + }) + + it('ctrl-drag beginning on an unselected cell only includes the rectangle', () => { + const table = makeTable() + const fake = makeFakeDocument() + + table.selectCellRange(rangeOf('r0', 'a', 'r0', 'a')) + getCell(table, 'r1', 'b').getSelectionStartHandler(fake.document)({ + ctrlKey: true, + }) + getCell(table, 'r2', 'c').getSelectionExtendHandler()({}) + + expect(table.getSelectedCellIds()).toEqual([ + 'r0_a', + 'r1_b', + 'r1_c', + 'r2_b', + 'r2_c', + ]) + expect(table.atoms.cellSelection.get().at(-1)?.operation).toBeUndefined() + }) + + it('ignores subtractive modifiers when multi-range selection is disabled', () => { + const table = makeTable({ enableMultiCellRangeSelection: false }) + const fake = makeFakeDocument() + + table.selectCellRange(rangeOf('r0', 'a', 'r2', 'c')) + getCell(table, 'r1', 'b').getSelectionStartHandler(fake.document)({ + ctrlKey: true, + }) + + expect(table.atoms.cellSelection.get()).toEqual([ + rangeOf('r1', 'b', 'r1', 'b'), + ]) + expect(table.getSelectedCellCount()).toBe(1) + }) + it('metaKey works for multi-range as well', () => { const table = makeTable() const fake = makeFakeDocument() diff --git a/packages/table-core/tests/implementation/features/cell-selection/cellSelectionGeometry.test.ts b/packages/table-core/tests/implementation/features/cell-selection/cellSelectionGeometry.test.ts new file mode 100644 index 0000000000..53f8cc5df7 --- /dev/null +++ b/packages/table-core/tests/implementation/features/cell-selection/cellSelectionGeometry.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest' +import { + addCellSelectionBounds, + applyCellSelectionBoundsOperations, + intersectCellSelectionBounds, + mergeAdjacentCellSelectionBounds, + subtractCellSelectionBounds, +} from '../../../../src/features/cell-selection/cellSelectionGeometry' +import type { CellSelectionBounds } from '../../../../src' + +const bounds = ( + minRowIndex: number, + maxRowIndex: number, + minColumnIndex: number, + maxColumnIndex: number, +): CellSelectionBounds => ({ + minRowIndex, + maxRowIndex, + minColumnIndex, + maxColumnIndex, +}) + +describe('cell selection geometry', () => { + it('intersects inclusive rectangles', () => { + expect( + intersectCellSelectionBounds(bounds(0, 2, 0, 2), bounds(1, 3, 2, 4)), + ).toEqual(bounds(1, 2, 2, 2)) + expect( + intersectCellSelectionBounds(bounds(0, 0, 0, 0), bounds(1, 1, 1, 1)), + ).toBeUndefined() + }) + + it('cuts an interior rectangle into four disjoint pieces', () => { + expect( + subtractCellSelectionBounds(bounds(0, 4, 0, 4), bounds(1, 3, 1, 3)), + ).toEqual([ + bounds(0, 0, 0, 4), + bounds(4, 4, 0, 4), + bounds(1, 3, 0, 0), + bounds(1, 3, 4, 4), + ]) + }) + + it('leaves a rectangle unchanged when the exclusion does not overlap', () => { + const source = bounds(0, 1, 0, 1) + expect(subtractCellSelectionBounds(source, bounds(3, 4, 3, 4))).toEqual([ + source, + ]) + }) + + it('adds only the uncovered fragments of overlapping rectangles', () => { + expect( + addCellSelectionBounds([bounds(0, 1, 0, 1)], bounds(1, 2, 1, 2)), + ).toEqual([bounds(0, 1, 0, 1), bounds(1, 1, 2, 2), bounds(2, 2, 1, 2)]) + }) + + it('merges adjacent rectangles with identical spans', () => { + expect( + mergeAdjacentCellSelectionBounds([ + bounds(2, 2, 0, 2), + bounds(0, 0, 0, 2), + bounds(1, 1, 0, 2), + ]), + ).toEqual([bounds(0, 2, 0, 2)]) + }) + + it('applies include and exclude operations in order', () => { + expect( + applyCellSelectionBoundsOperations([ + { ...bounds(0, 2, 0, 2), operation: 'include' }, + { ...bounds(1, 1, 1, 1), operation: 'exclude' }, + { ...bounds(1, 1, 1, 1), operation: 'include' }, + ]), + ).toEqual([bounds(0, 2, 0, 2)]) + }) + + it('ignores an exclusion before any matching inclusion', () => { + expect( + applyCellSelectionBoundsOperations([ + { ...bounds(0, 2, 0, 2), operation: 'exclude' }, + { ...bounds(1, 1, 1, 1), operation: 'include' }, + ]), + ).toEqual([bounds(1, 1, 1, 1)]) + }) +}) diff --git a/packages/table-core/tests/implementation/features/cell-selection/cellSelectionRange.test.ts b/packages/table-core/tests/implementation/features/cell-selection/cellSelectionRange.test.ts index 3659344478..86b34b4078 100644 --- a/packages/table-core/tests/implementation/features/cell-selection/cellSelectionRange.test.ts +++ b/packages/table-core/tests/implementation/features/cell-selection/cellSelectionRange.test.ts @@ -129,6 +129,72 @@ describe('cell selection ranges', () => { expect(table.getSelectedCellCount()).toBe(7) }) + it('subtracts a range from the positive selection geometry', () => { + const table = makeTable() + table.selectCellRange(rangeOf('r0', 'a', 'r2', 'c')) + table.selectCellRange(rangeOf('r1', 'b', 'r1', 'b'), { + mode: 'exclude', + }) + + expect(table.getSelectedCellCount()).toBe(8) + expect(table.getSelectedCellIds()).not.toContain('r1_b') + expect(table.getCellSelectionBounds()).toEqual([ + { + minRowIndex: 0, + maxRowIndex: 0, + minColumnIndex: 0, + maxColumnIndex: 2, + }, + { + minRowIndex: 1, + maxRowIndex: 1, + minColumnIndex: 0, + maxColumnIndex: 0, + }, + { + minRowIndex: 1, + maxRowIndex: 1, + minColumnIndex: 2, + maxColumnIndex: 2, + }, + { + minRowIndex: 2, + maxRowIndex: 2, + minColumnIndex: 0, + maxColumnIndex: 2, + }, + ]) + }) + + it('can include cells again after excluding them', () => { + const table = makeTable() + const center = rangeOf('r1', 'b', 'r1', 'b') + table.selectCellRange(rangeOf('r0', 'a', 'r2', 'c')) + table.selectCellRange(center, { mode: 'exclude' }) + table.selectCellRange(center, { mode: 'include' }) + + expect(table.getSelectedCellCount()).toBe(9) + expect(table.getCellSelectionBounds()).toEqual([ + { + minRowIndex: 0, + maxRowIndex: 2, + minColumnIndex: 0, + maxColumnIndex: 2, + }, + ]) + }) + + it('lets mode override the deprecated additive option', () => { + const table = makeTable() + table.selectCellRange(rangeOf('r0', 'a', 'r1', 'b')) + table.selectCellRange(rangeOf('r0', 'a', 'r0', 'a'), { + additive: true, + mode: 'replace', + }) + + expect(table.getSelectedCellIds()).toEqual(['r0_a']) + }) + it('is memoized between reads', () => { const table = makeTable() table.selectCellRange(rangeOf('r0', 'a', 'r2', 'b')) From 2526b4727f2f1ec76b74aacea2ec6a93a40b6951 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:27:08 +0000 Subject: [PATCH 2/7] ci: apply automated fixes --- docs/framework/lit/quick-start.md | 14 +-- examples/lit/basic-app-table/src/main.ts | 34 +++--- .../lit/basic-dynamic-columns/src/main.ts | 43 ++++---- examples/lit/basic-external-atoms/src/main.ts | 19 ++-- examples/lit/basic-external-state/src/main.ts | 19 ++-- examples/lit/basic-subscribe/src/main.ts | 10 +- examples/lit/cell-selection/src/main.ts | 97 ++++++++--------- .../lit/column-pinning-sticky/src/main.ts | 72 ++++++------- examples/lit/column-pinning/src/main.ts | 72 ++++++------- examples/lit/column-sizing/src/main.ts | 10 +- .../src/components/products-table.ts | 54 +++++----- .../src/components/users-table.ts | 56 +++++----- examples/lit/expanding/src/main.ts | 12 +-- .../lit/filters-faceted-bucketed/src/main.ts | 18 ++-- examples/lit/filters-faceted/src/main.ts | 18 ++-- examples/lit/filters-fuzzy/src/main.ts | 32 +++--- examples/lit/filters/src/main.ts | 16 +-- examples/lit/grouped-aggregation/src/main.ts | 30 +++--- examples/lit/grouping/src/main.ts | 30 +++--- examples/lit/kitchen-sink/src/main.ts | 102 +++++++++--------- examples/lit/row-pinning/src/main.ts | 12 +-- examples/lit/sorting-dynamic-data/src/main.ts | 35 +++--- examples/lit/sorting/src/main.ts | 43 ++++---- examples/lit/virtualized-columns/src/main.ts | 24 ++--- 24 files changed, 444 insertions(+), 428 deletions(-) diff --git a/docs/framework/lit/quick-start.md b/docs/framework/lit/quick-start.md index e95805bc28..7c033e33a8 100644 --- a/docs/framework/lit/quick-start.md +++ b/docs/framework/lit/quick-start.md @@ -181,15 +181,17 @@ export class PersonTable extends LitElement { : html`
${FlexRender({ header })} ${ - { asc: ' 🔼', desc: ' 🔽' }[ - header.column.getIsSorted() as string - ] ?? null - } + { asc: ' 🔼', desc: ' 🔽' }[ + header.column.getIsSorted() as string + ] ?? null + }
` } diff --git a/examples/lit/basic-app-table/src/main.ts b/examples/lit/basic-app-table/src/main.ts index eb271df06b..87d7b5b03f 100644 --- a/examples/lit/basic-app-table/src/main.ts +++ b/examples/lit/basic-app-table/src/main.ts @@ -131,28 +131,28 @@ class LitTableExample extends LitElement { ? null : html`
${h.FlexRender()} ${ - { asc: ' \u{1F53C}', desc: ' \u{1F53D}' }[ - h.column.getIsSorted() as string - ] ?? null - } + { asc: ' \u{1F53C}', desc: ' \u{1F53D}' }[ + h.column.getIsSorted() as string + ] ?? null + }
` } diff --git a/examples/lit/basic-dynamic-columns/src/main.ts b/examples/lit/basic-dynamic-columns/src/main.ts index 492c7344ed..a29999047c 100644 --- a/examples/lit/basic-dynamic-columns/src/main.ts +++ b/examples/lit/basic-dynamic-columns/src/main.ts @@ -419,33 +419,34 @@ class LitTableExample extends LitElement { : html`
${FlexRender({ header })}${ - { - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? - '' - } + { + asc: ' 🔼', + desc: ' 🔽', + }[ + header.column.getIsSorted() as string + ] ?? '' + }
${ - header.column.getCanFilter() - ? html`` - : null - } + header.column.getCanFilter() + ? html`` + : null + } ` } diff --git a/examples/lit/basic-external-atoms/src/main.ts b/examples/lit/basic-external-atoms/src/main.ts index 094ec862b2..f975d6eb8e 100644 --- a/examples/lit/basic-external-atoms/src/main.ts +++ b/examples/lit/basic-external-atoms/src/main.ts @@ -136,19 +136,20 @@ class LitTableExample extends LitElement { ? null : html`
${FlexRender({ header })} ${ - { - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null - } + { + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? + null + }
` } diff --git a/examples/lit/basic-external-state/src/main.ts b/examples/lit/basic-external-state/src/main.ts index cc27a35d7d..552e05cbac 100644 --- a/examples/lit/basic-external-state/src/main.ts +++ b/examples/lit/basic-external-state/src/main.ts @@ -133,19 +133,20 @@ class LitTableExample extends LitElement { ? null : html`
${FlexRender({ header })} ${ - { - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null - } + { + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? + null + }
` } diff --git a/examples/lit/basic-subscribe/src/main.ts b/examples/lit/basic-subscribe/src/main.ts index d2875e6a26..6c20871fab 100644 --- a/examples/lit/basic-subscribe/src/main.ts +++ b/examples/lit/basic-subscribe/src/main.ts @@ -271,10 +271,12 @@ class LitTableExample extends LitElement { : html`
${FlexRender({ header })}
${ - header.column.getCanFilter() - ? this.renderColumnFilter(header.getContext()) - : null - } + header.column.getCanFilter() + ? this.renderColumnFilter( + header.getContext(), + ) + : null + } ` } diff --git a/examples/lit/cell-selection/src/main.ts b/examples/lit/cell-selection/src/main.ts index 43aae19043..5675730e58 100644 --- a/examples/lit/cell-selection/src/main.ts +++ b/examples/lit/cell-selection/src/main.ts @@ -419,61 +419,62 @@ export class LitCellSelectionExample extends LitElement { ${ - header.column.getCanPin() - ? html` -
- ${ - header.column.getIsPinned() !== 'start' - ? html`` - : null - } - ${ - header.column.getIsPinned() - ? html`` - : null - } - ${ - header.column.getIsPinned() !== 'end' - ? html`` - : null - } -
- ` - : null - } + header.column.getCanPin() + ? html` +
+ ${ + header.column.getIsPinned() !== + 'start' + ? html`` + : null + } + ${ + header.column.getIsPinned() + ? html`` + : null + } + ${ + header.column.getIsPinned() !== 'end' + ? html`` + : null + } +
+ ` + : null + } ` } diff --git a/examples/lit/column-pinning-sticky/src/main.ts b/examples/lit/column-pinning-sticky/src/main.ts index 76e6545429..78166da06a 100644 --- a/examples/lit/column-pinning-sticky/src/main.ts +++ b/examples/lit/column-pinning-sticky/src/main.ts @@ -261,44 +261,44 @@ class LitTableExample extends LitElement { ? html`
${ - header.column.getIsPinned() !== 'start' - ? html` - - ` - : null - } + header.column.getIsPinned() !== 'start' + ? html` + + ` + : null + } ${ - header.column.getIsPinned() - ? html` - - ` - : null - } + header.column.getIsPinned() + ? html` + + ` + : null + } ${ - header.column.getIsPinned() !== 'end' - ? html` - - ` - : null - } + header.column.getIsPinned() !== 'end' + ? html` + + ` + : null + }
` : null diff --git a/examples/lit/column-pinning/src/main.ts b/examples/lit/column-pinning/src/main.ts index 2cda19eaaa..33429ae964 100644 --- a/examples/lit/column-pinning/src/main.ts +++ b/examples/lit/column-pinning/src/main.ts @@ -228,44 +228,44 @@ class LitTableExample extends LitElement { ? html`
${ - header.column.getIsPinned() !== 'start' - ? html` - - ` - : null - } + header.column.getIsPinned() !== 'start' + ? html` + + ` + : null + } ${ - header.column.getIsPinned() - ? html` - - ` - : null - } + header.column.getIsPinned() + ? html` + + ` + : null + } ${ - header.column.getIsPinned() !== 'end' - ? html` - - ` - : null - } + header.column.getIsPinned() !== 'end' + ? html` + + ` + : null + }
` : null diff --git a/examples/lit/column-sizing/src/main.ts b/examples/lit/column-sizing/src/main.ts index 919f1dbafa..0479eb7e83 100644 --- a/examples/lit/column-sizing/src/main.ts +++ b/examples/lit/column-sizing/src/main.ts @@ -118,10 +118,12 @@ class LitTableExample extends LitElement { ? null : html`
1 && - sorting.findIndex( - (s) => s.id === header.column.id, - ) > -1 - ? html`${ - sorting.findIndex( - (s) => s.id === header.column.id, - ) + 1 - }` - : nothing - } + sorting.length > 1 && + sorting.findIndex( + (s) => s.id === header.column.id, + ) > -1 + ? html`${ + sorting.findIndex( + (s) => s.id === header.column.id, + ) + 1 + }` + : nothing + } ` } @@ -188,26 +188,28 @@ export class ProductsTable extends LitElement { ? html` ${footer.FooterSum()} ${ - hasFilter - ? html` - (filtered)` - : nothing - } - ` - : columnId === 'select' - ? nothing - : html` - ${footer.FooterColumnId()} - ${ hasFilter ? html` - ✓` : nothing } + ` + : columnId === 'select' + ? nothing + : html` + ${footer.FooterColumnId()} + ${ + hasFilter + ? html` + ✓` + : nothing + } ` } diff --git a/examples/lit/composable-tables/src/components/users-table.ts b/examples/lit/composable-tables/src/components/users-table.ts index 0ee295dc47..9bc0a89f2b 100644 --- a/examples/lit/composable-tables/src/components/users-table.ts +++ b/examples/lit/composable-tables/src/components/users-table.ts @@ -142,19 +142,19 @@ export class UsersTable extends LitElement { ${header.SortIndicator()} ${header.ColumnFilter()} ${ - sorting.length > 1 && - sorting.findIndex( - (s) => s.id === header.column.id, - ) > -1 - ? html`${ - sorting.findIndex( - (s) => s.id === header.column.id, - ) + 1 - }` - : nothing - } + sorting.length > 1 && + sorting.findIndex( + (s) => s.id === header.column.id, + ) > -1 + ? html`${ + sorting.findIndex( + (s) => s.id === header.column.id, + ) + 1 + }` + : nothing + } ` } @@ -202,27 +202,29 @@ export class UsersTable extends LitElement { ? html` ${footer.FooterSum()} ${ - hasFilter - ? html` - (filtered)` - : nothing - } - ` - : columnId === 'actions' || - columnId === 'select' - ? nothing - : html` - ${footer.FooterColumnId()} - ${ hasFilter ? html` - ✓` : nothing } + ` + : columnId === 'actions' || + columnId === 'select' + ? nothing + : html` + ${footer.FooterColumnId()} + ${ + hasFilter + ? html` + ✓` + : nothing + } ` } diff --git a/examples/lit/expanding/src/main.ts b/examples/lit/expanding/src/main.ts index e2a27879b0..f245f9024d 100644 --- a/examples/lit/expanding/src/main.ts +++ b/examples/lit/expanding/src/main.ts @@ -262,12 +262,12 @@ class LitTableExample extends LitElement {
${FlexRender({ header })} ${ - header.column.getCanFilter() - ? html`
- ${renderFilter(header.column, table)} -
` - : null - } + header.column.getCanFilter() + ? html`
+ ${renderFilter(header.column, table)} +
` + : null + }
` } diff --git a/examples/lit/filters-faceted-bucketed/src/main.ts b/examples/lit/filters-faceted-bucketed/src/main.ts index c75567c41a..8effb0c8e6 100644 --- a/examples/lit/filters-faceted-bucketed/src/main.ts +++ b/examples/lit/filters-faceted-bucketed/src/main.ts @@ -196,15 +196,15 @@ class LitTableExample extends LitElement { : html` ${FlexRender({ header })} ${ - header.column.getCanFilter() - ? html`
- -
` - : null - } + header.column.getCanFilter() + ? html`
+ +
` + : null + } ` } diff --git a/examples/lit/filters-faceted/src/main.ts b/examples/lit/filters-faceted/src/main.ts index a6ce047ab2..5102b1d0dc 100644 --- a/examples/lit/filters-faceted/src/main.ts +++ b/examples/lit/filters-faceted/src/main.ts @@ -274,15 +274,15 @@ class LitTableExample extends LitElement { : html` ${FlexRender({ header })} ${ - header.column.getCanFilter() - ? html`
- -
` - : null - } + header.column.getCanFilter() + ? html`
+ +
` + : null + } ` } diff --git a/examples/lit/filters-fuzzy/src/main.ts b/examples/lit/filters-fuzzy/src/main.ts index 5b6d009c40..aa0d654e4c 100644 --- a/examples/lit/filters-fuzzy/src/main.ts +++ b/examples/lit/filters-fuzzy/src/main.ts @@ -256,27 +256,27 @@ class LitTableExample extends LitElement {
${FlexRender({ header })} ${ - { asc: ' 🔼', desc: ' 🔽' }[ - header.column.getIsSorted() as string - ] ?? null - } + { asc: ' 🔼', desc: ' 🔽' }[ + header.column.getIsSorted() as string + ] ?? null + }
${ - header.column.getCanFilter() - ? html`
- -
` - : null - } + header.column.getCanFilter() + ? html`
+ +
` + : null + } ` } diff --git a/examples/lit/filters/src/main.ts b/examples/lit/filters/src/main.ts index b088959630..eb31aaf749 100644 --- a/examples/lit/filters/src/main.ts +++ b/examples/lit/filters/src/main.ts @@ -217,14 +217,14 @@ class LitTableExample extends LitElement { ? null : html`
${FlexRender({ header })}
${ - header.column.getCanFilter() - ? html`
- -
` - : null - } ` + header.column.getCanFilter() + ? html`
+ +
` + : null + } ` } `, diff --git a/examples/lit/grouped-aggregation/src/main.ts b/examples/lit/grouped-aggregation/src/main.ts index 457b83afb0..abada796d4 100644 --- a/examples/lit/grouped-aggregation/src/main.ts +++ b/examples/lit/grouped-aggregation/src/main.ts @@ -168,19 +168,19 @@ class LitTableExample extends LitElement { ? null : html`
${ - header.column.getCanGroup() - ? html`` - : null - } + header.column.getCanGroup() + ? html`` + : null + } ${FlexRender({ header })}
` } @@ -215,8 +215,8 @@ class LitTableExample extends LitElement { ? html`` - : null - } + header.column.getCanGroup() + ? html`` + : null + } ${FlexRender({ header })}
` } @@ -184,8 +184,8 @@ class LitTableExample extends LitElement { ? html`` - : null - } + column.getIsPinned() !== 'start' + ? html`` + : null + } ${ - column.getIsPinned() - ? html`` - : null - } + column.getIsPinned() + ? html`` + : null + } ${ - column.getIsPinned() !== 'end' - ? html`` - : null - } + column.getIsPinned() !== 'end' + ? html`` + : null + } ` : null @@ -482,10 +482,10 @@ class LitTableExample extends LitElement { @click=${column.getToggleGroupingHandler()} > ${ - column.getIsGrouped() - ? `Stop (${column.getGroupedIndex()})` - : 'Group' - } + column.getIsGrouped() + ? `Stop (${column.getGroupedIndex()})` + : 'Group' + } ` : null } @@ -505,12 +505,12 @@ class LitTableExample extends LitElement { > ${FlexRender({ header })} ${ - column.getIsSorted() === 'asc' - ? ' ▲' - : column.getIsSorted() === 'desc' - ? ' ▼' - : '' - } + column.getIsSorted() === 'asc' + ? ' ▲' + : column.getIsSorted() === 'desc' + ? ' ▼' + : '' + } ` : FlexRender({ header }) } @@ -653,9 +653,9 @@ class LitTableExample extends LitElement { @@ -663,23 +663,23 @@ class LitTableExample extends LitElement { : cell.column.id === 'firstName' ? html`
${ - cell.row.getCanExpand() - ? html`` - : html`-` - } + cell.row.getCanExpand() + ? html`` + : html`-` + } ${FlexRender({ cell })}
` : cell.getIsGrouped() ? html`` - : null - } + header.column.getIsPinned() !== + 'start' + ? html`` + : null + } ${ - header.column.getIsPinned() - ? html`` - : null - } + header.column.getIsPinned() + ? html`` + : null + } ${ - header.column.getIsPinned() !== 'end' - ? html`` - : null - } + header.column.getIsPinned() !== + 'end' + ? html`` + : null + }
` : null diff --git a/examples/lit/column-pinning-sticky/src/main.ts b/examples/lit/column-pinning-sticky/src/main.ts index 78166da06a..2f43811b9e 100644 --- a/examples/lit/column-pinning-sticky/src/main.ts +++ b/examples/lit/column-pinning-sticky/src/main.ts @@ -266,7 +266,7 @@ class LitTableExample extends LitElement { @@ -279,7 +279,7 @@ class LitTableExample extends LitElement { @@ -292,7 +292,7 @@ class LitTableExample extends LitElement { diff --git a/examples/lit/column-pinning/src/main.ts b/examples/lit/column-pinning/src/main.ts index 33429ae964..d7a05d67eb 100644 --- a/examples/lit/column-pinning/src/main.ts +++ b/examples/lit/column-pinning/src/main.ts @@ -233,7 +233,7 @@ class LitTableExample extends LitElement { @@ -246,7 +246,7 @@ class LitTableExample extends LitElement { @@ -259,7 +259,7 @@ class LitTableExample extends LitElement { diff --git a/examples/lit/composable-tables/src/components/products-table.ts b/examples/lit/composable-tables/src/components/products-table.ts index b209bb7c09..2b69fd20f1 100644 --- a/examples/lit/composable-tables/src/components/products-table.ts +++ b/examples/lit/composable-tables/src/components/products-table.ts @@ -134,10 +134,11 @@ export class ProductsTable extends LitElement { ) > -1 ? html`${ - sorting.findIndex( - (s) => s.id === header.column.id, - ) + 1 - } + s.id === header.column.id, + ) + 1 + }` : nothing } diff --git a/examples/lit/composable-tables/src/components/users-table.ts b/examples/lit/composable-tables/src/components/users-table.ts index 9bc0a89f2b..253a18488c 100644 --- a/examples/lit/composable-tables/src/components/users-table.ts +++ b/examples/lit/composable-tables/src/components/users-table.ts @@ -148,10 +148,11 @@ export class UsersTable extends LitElement { ) > -1 ? html`${ - sorting.findIndex( - (s) => s.id === header.column.id, - ) + 1 - } + s.id === header.column.id, + ) + 1 + }` : nothing } diff --git a/examples/lit/grouped-aggregation/src/main.ts b/examples/lit/grouped-aggregation/src/main.ts index abada796d4..9d77397d30 100644 --- a/examples/lit/grouped-aggregation/src/main.ts +++ b/examples/lit/grouped-aggregation/src/main.ts @@ -174,10 +174,10 @@ class LitTableExample extends LitElement { style="cursor: pointer" > ${ - header.column.getIsGrouped() - ? `🛑(${header.column.getGroupedIndex()}) ` - : '👊 ' - } + header.column.getIsGrouped() + ? `🛑(${header.column.getGroupedIndex()}) ` + : '👊 ' + } ` : null } diff --git a/examples/lit/grouping/src/main.ts b/examples/lit/grouping/src/main.ts index f93a917a64..5701226da7 100644 --- a/examples/lit/grouping/src/main.ts +++ b/examples/lit/grouping/src/main.ts @@ -145,10 +145,10 @@ class LitTableExample extends LitElement { style="cursor: pointer" > ${ - header.column.getIsGrouped() - ? `🛑(${header.column.getGroupedIndex()}) ` - : '👊 ' - } + header.column.getIsGrouped() + ? `🛑(${header.column.getGroupedIndex()}) ` + : '👊 ' + } ` : null } From 8b72f6fc4c695e76a19d1a4b167ed5b027f2eded Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:30:38 +0000 Subject: [PATCH 4/7] ci: apply automated fixes (attempt 3/3) --- examples/lit/cell-selection/src/main.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/lit/cell-selection/src/main.ts b/examples/lit/cell-selection/src/main.ts index a0209bc1f1..2fb34d8319 100644 --- a/examples/lit/cell-selection/src/main.ts +++ b/examples/lit/cell-selection/src/main.ts @@ -443,7 +443,7 @@ export class LitCellSelectionExample extends LitElement { ? html`` @@ -454,7 +454,7 @@ export class LitCellSelectionExample extends LitElement { ? html`` @@ -466,7 +466,7 @@ export class LitCellSelectionExample extends LitElement { ? html`` From e877d0f1313bce142881dee856ea66cd22234851 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 1 Aug 2026 07:05:46 -0500 Subject: [PATCH 5/7] docs: update cell selection skills --- _artifacts/domain_map.yaml | 17 ++++---- _artifacts/skill_spec.md | 10 +++-- _artifacts/skill_tree.yaml | 21 ++++++++-- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../table-core/skills/cell-selection/SKILL.md | 40 +++++++++++++++---- .../skills/migrate-v8-to-v9/SKILL.md | 3 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- 12 files changed, 75 insertions(+), 30 deletions(-) diff --git a/_artifacts/domain_map.yaml b/_artifacts/domain_map.yaml index e19dab5561..f74ebe9c27 100644 --- a/_artifacts/domain_map.yaml +++ b/_artifacts/domain_map.yaml @@ -12,7 +12,7 @@ library: meta: generated_by: '@tanstack/intent scaffold domain discovery' - date: '2026-07-31' + date: '2026-08-01' status: reviewed maintainer_review_pending: false phase_4_date: '2026-07-10' @@ -25,7 +25,7 @@ scope: - 'Teach headless rendering, feature registration, row-model ownership, framework state, and v8-to-v9 changes as foundations.' included: - 'All 18 public workspace packages.' - - 'All 14 stock Table v9 features, custom features, TypeScript helpers, migrations, framework state, composable table hooks, Query, Virtual, Devtools, and fuzzy ranking.' + - 'All 16 stock Table v9 features, custom features, TypeScript helpers, migrations, framework state, composable table hooks, Query, Virtual, Devtools, and fuzzy ranking.' - 'Renderer-owned CSS edge cases for sticky positioning, column widths, resizing, layout, and virtualization.' excluded: - 'Component-library-specific integrations such as shadcn, Material UI, Mantine, and equivalent design systems.' @@ -47,7 +47,7 @@ migration_depth_contract: - 'Framework package and construction entrypoint changes, including framework-version prerequisites.' - 'Logical start/end column-pinning rename and the full old-to-new API mapping.' - 'Prototype-bound row, cell, column, and header methods; object-spread/Object.keys/JSON implications.' - - 'Required tableFeatures registration, stockFeatures audit guidance, all 14 feature imports, and feature-gated state/APIs.' + - 'Required tableFeatures registration, stockFeatures audit guidance, all 16 feature imports, and feature-gated state/APIs.' - 'Core row model removal plus every get*RowModel to create*RowModel feature-slot mapping.' - 'filterFns, sortFns, aggregationFns, and filterMeta registry-slot migration.' - 'Adapter state access, selectors/subscriptions, controlled slices, external atoms, precedence, and onStateChange removal.' @@ -156,7 +156,7 @@ domains: description: 'Headless philosophy, feature registration, client/server ownership, TypeScript inference, API discovery, custom features, and v8 migration.' - slug: feature-plugins name: 'Feature plugins' - description: 'The 14 stock optional features, their prerequisites, state, row-model participation, and UI responsibilities.' + description: 'The 16 stock optional features, their prerequisites, state, row-model participation, and UI responsibilities.' - slug: framework-adapters name: 'Framework adapters' description: 'Per-framework setup, reactive table state, v8 migration, reusable createTableHook patterns, and supported Query/Virtual composition.' @@ -533,7 +533,7 @@ skills: package: '@tanstack/table-core' domain: feature-plugins type: feature - purpose: 'Maintain spreadsheet-style rectangular cell ranges anchored to row and column ids across sorting, filtering, pagination, and column layout changes.' + purpose: 'Maintain spreadsheet-style rectangular selections as ordered include/exclude operations anchored to row and column ids, resolving them into disjoint positive regions across sorting, filtering, pagination, and column layout changes.' sources: [ 'TanStack/table:docs/framework/react/guide/cell-selection.md', @@ -541,7 +541,10 @@ skills: 'TanStack/table:examples/react/cell-selection', ] failure_modes: - - 'Expecting cellSelection to be a per-cell map; it is an array of two-corner rectangles, so a range widens onto new cells when sorting or column order changes what sits between its corners.' + - 'Expecting cellSelection to be a per-cell map or a list of final positive regions; it is an ordered log of two-corner include/exclude operations, and derived bounds may split one stored range into several regions.' + - 'Reordering, deduplicating, or serializing only the corners of controlled cellSelection state and thereby changing or losing subtraction semantics.' + - 'Using additive when the intended operation is explicit subtraction; selectCellRange mode is replace, include, or exclude, while additive is only a deprecated include alias.' + - 'Assuming Ctrl/Cmd always adds a range; a modified interaction excludes when it starts on a selected cell and includes when it starts on an unselected cell.' - 'Binding only the mousedown handler and expecting drag selection, or reimplementing mouseup even though the start handler owns its own document listener.' - 'Drawing the selection outline with borders on a border-collapse table, which changes row heights as cells become selected.' - 'Re-rendering every cell on each drag update instead of subscribing per row to table.atoms.cellSelection.' @@ -1656,7 +1659,7 @@ documentation_read: narrative_docs: 'All 187 narrative Markdown documents were inventoried by title, headings, and admonitions; foundational, feature, state, migration, composable, and virtualization guides were deep-read.' generated_references: 'All 828 generated reference documents were inventoried for exports and API categories; exact API truth is delegated to installed dist declarations (.d.ts) in skills.' examples: 'All 277 example directories were inventoried; basic, state, composable, Query, Virtual, feature, and Devtools-relevant examples were sampled or deep-read by skill.' - source: 'All public package entrypoints, tableFeatures prerequisites, core construction/state precedence, all 14 feature implementations/defaults, createTableHook implementations, Devtools registration, and match-sorter-utils source were inspected.' + source: 'All public package entrypoints, tableFeatures prerequisites, core construction/state precedence, all 16 feature implementations/defaults, createTableHook implementations, Devtools registration, and match-sorter-utils source were inspected.' community: 'Recent v9 issues plus recurring high-signal issues and GitHub discussions were reviewed for failure modes and misconceptions.' open_gaps: diff --git a/_artifacts/skill_spec.md b/_artifacts/skill_spec.md index cb08151978..00de2f975d 100644 --- a/_artifacts/skill_spec.md +++ b/_artifacts/skill_spec.md @@ -9,7 +9,7 @@ This specification is the generation contract for a deliberately smaller, foot-g ## Outcome -Generate 78 short package-local skills across all 18 public packages. A loaded skill should quickly do three things: +Generate 80 short package-local skills across all 18 public packages. A loaded skill should quickly do three things: 1. Correct the user or agent mental model. 2. Show the smallest reliable setup or decision pattern. @@ -183,8 +183,10 @@ Skill versions ship with package versions. After release tooling calculates pack - custom-features — plugin lifecycle after exhausting built-in APIs and meta. - migrate-v8-to-v9 — shared breaking changes and adapter migration routing. -### Stock feature plugins — @tanstack/table-core (14) +### Stock feature plugins — @tanstack/table-core (16) +- aggregation +- cell-selection - column-faceting - column-filtering - grouping @@ -271,7 +273,7 @@ All Devtools skills must emphasize the required non-empty table options.key, lif | Package | Skills | | -------------------------------- | -----: | -| @tanstack/table-core | 21 | +| @tanstack/table-core | 23 | | @tanstack/react-table | 6 | | @tanstack/preact-table | 6 | | @tanstack/octane-table | 3 | @@ -289,7 +291,7 @@ All Devtools skills must emphasize the required non-empty table options.key, lif | @tanstack/vue-table-devtools | 1 | | @tanstack/angular-table-devtools | 1 | | @tanstack/match-sorter-utils | 1 | -| Total | 78 | +| Total | 80 | ## Framework distinctions that must survive generation diff --git a/_artifacts/skill_tree.yaml b/_artifacts/skill_tree.yaml index 790aeddb3f..a5c477aa17 100644 --- a/_artifacts/skill_tree.yaml +++ b/_artifacts/skill_tree.yaml @@ -9,7 +9,7 @@ library: generated_from: domain_map: '_artifacts/domain_map.yaml' skill_spec: '_artifacts/skill_spec.md' -generated_at: '2026-07-31' +generated_at: '2026-08-01' status: reviewed reviewed_at: '2026-07-29' batch_review: true @@ -119,7 +119,7 @@ skills: domain: foundations path: 'packages/table-core/skills/migrate-v8-to-v9/SKILL.md' package: 'packages/table-core' - description: 'Perform a complete TanStack Table v8-to-v9 migration audit: all 14 feature registrations, every row-model and registry slot, state/store changes, prototype methods, full pinning and resizing mappings, sorting and selection semantics, removed internals, helpers, meta typing, and generic changes. useLegacyTable is only a deprecated temporary bridge when already encountered.' + description: 'Perform a complete TanStack Table v8-to-v9 migration audit: all 16 feature registrations, every row-model and registry slot, state/store changes, prototype methods, full pinning and resizing mappings, sorting and selection semantics, removed internals, helpers, meta typing, and generic changes. useLegacyTable is only a deprecated temporary bridge when already encountered.' requires: ['core', 'table-features', 'typescript'] sources: - 'TanStack/table:docs/framework/react/guide/migrating.md' @@ -174,6 +174,21 @@ skills: - 'TanStack/table:packages/table-core/src/features/column-grouping' - 'TanStack/table:examples/react/grouping' + - name: 'Aggregation' + slug: aggregation + type: sub-skill + domain: feature-plugins + path: 'packages/table-core/skills/aggregation/SKILL.md' + package: 'packages/table-core' + description: 'Aggregate columns independently of grouping with rowAggregationFeature, built-in or custom aggregation functions, caller-selected row scopes, keyed results, grouped merges, and manual values.' + requires: ['core', 'table-features'] + sources: + - 'TanStack/table:docs/guide/aggregation.md' + - 'TanStack/table:docs/framework/react/guide/aggregation.md' + - 'TanStack/table:packages/table-core/src/features/row-aggregation' + - 'TanStack/table:examples/react/aggregation' + - 'TanStack/table:examples/react/grouped-aggregation' + - name: 'Column Ordering' slug: column-ordering type: sub-skill @@ -297,7 +312,7 @@ skills: domain: feature-plugins path: 'packages/table-core/skills/cell-selection/SKILL.md' package: 'packages/table-core' - description: 'Select rectangular cell ranges with cellSelectionFeature: two-corner range state, drag and Shift and Ctrl handlers, selection edges, and render-order resolution under pinning. Load when ranges widen unexpectedly after sorting or reordering, or when drag re-renders the whole table.' + description: 'Select, add, and subtract rectangular cell ranges with cellSelectionFeature: ordered include/exclude operations, modifier dragging, final positive bounds, selection edges, and render-order resolution under pinning. Load for spreadsheet-style or “select all except” behavior.' requires: ['core', 'table-features'] sources: - 'TanStack/table:docs/framework/react/guide/cell-selection.md' diff --git a/packages/angular-table/skills/migrate-v8-to-v9/SKILL.md b/packages/angular-table/skills/migrate-v8-to-v9/SKILL.md index 5a5a195aa6..bef7d9d896 100644 --- a/packages/angular-table/skills/migrate-v8-to-v9/SKILL.md +++ b/packages/angular-table/skills/migrate-v8-to-v9/SKILL.md @@ -59,7 +59,7 @@ class TableCmp { The initializer reruns when signals read inside it change and calls `setOptions`; do not rebuild columns or features there. -Feature imports are `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. APIs are feature-gated. Put a feature before its dependent slot in the same `tableFeatures` call. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. +Feature imports are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. APIs are feature-gated. Put a feature before its dependent slot in the same `tableFeatures` call. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. ### Row-model mapping diff --git a/packages/lit-table/skills/migrate-v8-to-v9/SKILL.md b/packages/lit-table/skills/migrate-v8-to-v9/SKILL.md index b95bad13fc..d30c41c951 100644 --- a/packages/lit-table/skills/migrate-v8-to-v9/SKILL.md +++ b/packages/lit-table/skills/migrate-v8-to-v9/SKILL.md @@ -60,7 +60,7 @@ class PeopleTable extends LitElement { | `sortingFns` table option | `sortFns` feature slot | | Top-level `onStateChange` | Per-slice callbacks, external atoms, or store subscription | -Feature imports are `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. APIs are feature-gated. Put a feature before its dependent slot in one `tableFeatures` call. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. +Feature imports are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. APIs are feature-gated. Put a feature before its dependent slot in one `tableFeatures` call. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. ### Row-model mapping diff --git a/packages/preact-table/skills/migrate-v8-to-v9/SKILL.md b/packages/preact-table/skills/migrate-v8-to-v9/SKILL.md index ba60383f75..2f18d5e2fe 100644 --- a/packages/preact-table/skills/migrate-v8-to-v9/SKILL.md +++ b/packages/preact-table/skills/migrate-v8-to-v9/SKILL.md @@ -63,7 +63,7 @@ V8 did not have a first-party Preact adapter; many Preact apps used `@tanstack/r In the registry slots, register individually imported built-ins (`filterFn_includesString`, `sortFn_alphanumeric`, `aggregationFn_sum`, and so on) under their conventional keys alongside custom functions; the full `filterFns`/`sortFns`/`aggregationFns` registry objects still work but bundle every built-in. -Register each prerequisite feature before its row-model slot. Stock features are `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. +Register each prerequisite feature before its row-model slot. Stock features are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. ### Preact state and subscriptions diff --git a/packages/react-table/skills/migrate-v8-to-v9/SKILL.md b/packages/react-table/skills/migrate-v8-to-v9/SKILL.md index 034a39ad3b..210b8a3704 100644 --- a/packages/react-table/skills/migrate-v8-to-v9/SKILL.md +++ b/packages/react-table/skills/migrate-v8-to-v9/SKILL.md @@ -74,7 +74,7 @@ Prefer explicit features as the end state. `stockFeatures` is a useful kitchen-s In the registry slots, register individually imported built-ins (`filterFn_includesString`, `sortFn_alphanumeric`, `aggregationFn_sum`, and so on) under their conventional keys alongside custom functions; the full `filterFns`/`sortFns`/`aggregationFns` registry objects still work but bundle every built-in. -Declare each prerequisite feature before its row-model slot in the same `tableFeatures()` call. Available stock features are `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. +Declare each prerequisite feature before its row-model slot in the same `tableFeatures()` call. Available stock features are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. ### State and React subscriptions diff --git a/packages/solid-table/skills/migrate-v8-to-v9/SKILL.md b/packages/solid-table/skills/migrate-v8-to-v9/SKILL.md index 2c9743306c..090e73d7f5 100644 --- a/packages/solid-table/skills/migrate-v8-to-v9/SKILL.md +++ b/packages/solid-table/skills/migrate-v8-to-v9/SKILL.md @@ -69,7 +69,7 @@ Keep static features and columns outside reactive component work. Prefer explici In the registry slots, register individually imported built-ins (`filterFn_includesString`, `sortFn_alphanumeric`, `aggregationFn_sum`, and so on) under their conventional keys alongside custom functions; the full `filterFns`/`sortFns`/`aggregationFns` registry objects still work but bundle every built-in. -Place each prerequisite feature before its row-model slot. Stock features are `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. +Place each prerequisite feature before its row-model slot. Stock features are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. ### Solid state and reactivity diff --git a/packages/svelte-table/skills/migrate-v8-to-v9/SKILL.md b/packages/svelte-table/skills/migrate-v8-to-v9/SKILL.md index 66526f89b5..2602b48c82 100644 --- a/packages/svelte-table/skills/migrate-v8-to-v9/SKILL.md +++ b/packages/svelte-table/skills/migrate-v8-to-v9/SKILL.md @@ -58,7 +58,7 @@ const table = createTable({ | `filterFns` / `aggregationFns` table options | Same-named feature slots | | Top-level `onStateChange` | Per-slice callbacks, external atoms, or store subscription | -Available feature imports are `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. An API does not exist unless its feature is registered. Put a feature before its dependent slot in the same `tableFeatures` call. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. +Available feature imports are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. An API does not exist unless its feature is registered. Put a feature before its dependent slot in the same `tableFeatures` call. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. ### Row-model mapping diff --git a/packages/table-core/skills/cell-selection/SKILL.md b/packages/table-core/skills/cell-selection/SKILL.md index ebe123cf25..51cd6808a7 100644 --- a/packages/table-core/skills/cell-selection/SKILL.md +++ b/packages/table-core/skills/cell-selection/SKILL.md @@ -1,7 +1,7 @@ --- name: cell-selection description: > - Select rectangular cell ranges with cellSelectionFeature: two-corner range state keyed by row and column id, mousedown/mouseenter handlers, selection edges, render-order resolution under pinning, and autoResetCellSelection. Load when ranges widen unexpectedly after sorting or column reordering, when a drag re-renders the whole table, or when building copy-to-clipboard from a selection. + Select, add, and subtract rectangular cell ranges with cellSelectionFeature: ordered include/exclude operations keyed by row and column id, modifier dragging, final positive bounds, selection edges, render-order resolution under pinning, and autoResetCellSelection. Load for spreadsheet-style selection, “select all except” behavior, unexpected range changes after sorting or reordering, drag performance, or copy-to-clipboard. metadata: { type: sub-skill, @@ -15,7 +15,7 @@ sources: - 'TanStack/table:examples/react/cell-selection' --- -This skill builds on `core` and `table-features`. `cellSelection` is an array of rectangles, each stored as two corner cells identified by row and column id. It is not a per-cell map, and it is not positional. +This skill builds on `core` and `table-features`. `cellSelection` is an ordered operation log of rectangles, each stored as two corner cells identified by row and column id. It is not a per-cell map, and it is not positional. Table resolves the log into disjoint positive rectangles for membership and derived reads. ## Setup @@ -37,11 +37,12 @@ type CellSelectionRange = { anchorColumnId: string focusRowId: string focusColumnId: string + operation?: 'include' | 'exclude' } type CellSelectionState = Array ``` -The `anchor` corner stays put; the `focus` corner moves during a drag or Shift-extend. Two corners are what make Shift-extend possible, and they keep a drag across thousands of cells to a two-field write. +The `anchor` corner stays put; the `focus` corner moves during a drag or Shift-extend. Two corners are what make Shift-extend possible, and they keep a drag across thousands of cells to a two-field write. Operations apply in array order. An omitted `operation` means `include` for backward compatibility; `exclude` subtracts from the selection produced by preceding entries. ## Core Patterns @@ -54,14 +55,27 @@ const onMouseEnter = cell.getSelectionExtendHandler() The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so a pointer released outside the table still finishes correctly. Pass a document explicitly (`cell.getSelectionStartHandler(iframeDocument)`) only when the table renders into another document. +With the default event predicates, Shift extends the active operation. Ctrl/Cmd starts an inclusion when the starting cell is unselected and an exclusion when it is selected. That choice remains fixed for the drag, so shrinking an exclusion restores cells that leave its rectangle. Set `enableMultiCellRangeSelection: false` to disable both modifier behaviors. + +### Apply ranges programmatically + +```ts +table.selectCellRange(range) // replace +table.selectCellRange(range, { mode: 'include' }) +table.selectCellRange(range, { mode: 'exclude' }) +``` + +Use `mode` when the operation is known. The deprecated `{ additive: true }` option is only an alias for include mode; an explicit `mode` wins. + ### Read the selection ```ts const count = table.getSelectedCellCount() +const bounds = table.getCellSelectionBounds() const grids = table.getSelectedCellRangesData() // [range][row][column] ``` -Expansion APIs are memoized and pull-based, so a table that only highlights cells never pays to enumerate a large selection. +`bounds` and `grids` describe the final disjoint positive regions after all operations, not one entry per stored state operation. Expansion APIs are memoized and pull-based, so a table that only highlights cells never pays to enumerate a large selection. Cell count uses rectangle arithmetic unless a per-cell `enableCellSelection` predicate requires enumeration. ### Draw the outline from edges @@ -69,7 +83,7 @@ Expansion APIs are memoized and pull-based, so a table that only highlights cell ### Drive keyboard navigation externally -The feature ships no keydown handling. Call `table.moveCellSelection(direction)`, `table.extendCellSelection(direction)`, `table.setFocusedCell(rowId, columnId)`, `table.selectAllCells()`, and `table.resetCellSelection(true)` from a hotkey library such as `@tanstack/react-hotkeys`, scoped to the grid element rather than the document. +The feature ships no keydown handling. Call `table.moveCellSelection(direction)`, `table.extendCellSelection(direction)`, `table.setFocusedCell(rowId, columnId)`, `table.selectAllCells()`, and `table.resetCellSelection(true)` from a hotkey library such as `@tanstack/react-hotkeys`, scoped to the grid element rather than the document. Extending preserves the active operation. `getFocusedCell()` follows the latest anchor, so an excluded cell can remain focused without being selected. ## Common Mistakes @@ -79,10 +93,20 @@ Wrong: `const isSelected = table.state.cellSelection[cell.id]` Correct: `const isSelected = cell.getIsSelected()` -`cellSelection` holds rectangles, not cell keys. Membership is resolved by comparing the cell's row and column index against the memoized bounds. +`cellSelection` holds ordered rectangle operations, not cell keys. Membership is resolved against the memoized final positive bounds. Source: `packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts` +### [HIGH] Treating stored operations as final selected regions + +Wrong: serialize or render each `table.state.cellSelection` entry as a selected rectangle. + +Correct: use `table.getCellSelectionBounds()`, `cell.getIsSelected()`, or `table.getSelectedCellRangesData()` for the resolved selection. + +An exclusion is an instruction, not a selected region, and a subtraction can split one included rectangle into four disjoint positive regions. Re-including a later rectangle applies after the exclusion because state order is significant. + +Source: `packages/table-core/src/features/cell-selection/cellSelectionGeometry.ts` + ### [HIGH] Assuming a range is frozen to the cells it originally covered Ranges are anchored to corner ids, so sorting, filtering, and column reordering keep the corners and recompute what sits between them. A range can therefore widen onto columns or rows the user never selected. Reset in userland when the product needs stricter behavior: @@ -159,7 +183,7 @@ Source: `docs/framework/react/guide/cell-selection.md#copying-a-selection` ### [MEDIUM] Persisting a selection and expecting drag state with it -`cellSelection` is safe to persist because drag session state is deliberately non-reactive instance data, not part of the slice. Do not add an `isSelecting` field to the persisted state; a stored `true` would rehydrate into a drag that hovering extends and nothing ever ends. +`cellSelection` is safe to persist because drag session state is deliberately non-reactive instance data, not part of the slice. Preserve array order and each `operation`; sorting or deduplicating the entries changes the resolved selection. Do not add an `isSelecting` field to the persisted state; a stored `true` would rehydrate into a drag that hovering extends and nothing ever ends. Source: `packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts` @@ -177,4 +201,4 @@ Source: `packages/table-core/src/features/cell-selection/cellSelectionFeature.ts ## API Discovery -Inspect `node_modules/@tanstack/table-core/dist/features/cell-selection/` for `CellSelectionRange`, `CellSelectionState`, `CellSelectionBounds`, `CellSelectionEdges`, the enablement and `is*Event` options, and the cell and table instance APIs. +Inspect `node_modules/@tanstack/table-core/dist/features/cell-selection/` for `CellSelectionRange`, `CellSelectionRangeOperation`, `CellSelectionRangeMode`, `CellSelectionState`, `CellSelectionBounds`, `SelectCellRangeOptions`, the enablement and `is*Event` options, and the cell and table instance APIs. diff --git a/packages/table-core/skills/migrate-v8-to-v9/SKILL.md b/packages/table-core/skills/migrate-v8-to-v9/SKILL.md index b65c1a6a5c..f50a18ad8d 100644 --- a/packages/table-core/skills/migrate-v8-to-v9/SKILL.md +++ b/packages/table-core/skills/migrate-v8-to-v9/SKILL.md @@ -69,6 +69,7 @@ V8 bundled all stock features. V9 exposes an API only when its feature is presen | Capability | V9 feature | | --------------------------- | ------------------------- | | Aggregation | `rowAggregationFeature` | +| Cell selection | `cellSelectionFeature` | | Column faceting | `columnFacetingFeature` | | Column filtering | `columnFilteringFeature` | | Column ordering | `columnOrderingFeature` | @@ -294,7 +295,7 @@ Do not confuse new capabilities with required breakages. After the table works, - [ ] Load the installed framework adapter's migration and table-state skills. - [ ] Replace the v8 adapter constructor/hook/controller with its v9 entrypoint. - [ ] Add a stable `features` object to every table. -- [ ] Inventory every feature API used by table, row, column, cell, and header code; register all 14 required stock features. +- [ ] Inventory every feature API used by table, row, column, cell, and header code; register all 16 required stock features. - [ ] Use `stockFeatures` only as a temporary parity aid and record an explicit-feature follow-up. - [ ] Remove `getCoreRowModel()` unless supplying a deliberate custom `coreRowModel` slot. - [ ] Move all remaining `get*RowModel()` options or earlier-beta `rowModels` entries to `create*RowModel()` feature slots. diff --git a/packages/vue-table/skills/migrate-v8-to-v9/SKILL.md b/packages/vue-table/skills/migrate-v8-to-v9/SKILL.md index 538377f6eb..91845149f9 100644 --- a/packages/vue-table/skills/migrate-v8-to-v9/SKILL.md +++ b/packages/vue-table/skills/migrate-v8-to-v9/SKILL.md @@ -51,7 +51,7 @@ const table = useTable({ features, columns, data }) | `filterFns` / `aggregationFns` table options | Same-named feature slots | | Top-level `onStateChange` | Per-slice callbacks, external atoms, or store subscription | -Available feature imports are `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. APIs are feature-gated. Put every feature before its dependent slot in the same `tableFeatures` call. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. +Available feature imports are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. APIs are feature-gated. Put every feature before its dependent slot in the same `tableFeatures` call. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows. ### Row-model mapping From 059fec600fee0cdb0dce53c3faeb6c58c9471cf2 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 1 Aug 2026 07:10:06 -0500 Subject: [PATCH 6/7] vite type module --- examples/alpine/aggregation/package.json | 1 + examples/alpine/basic-app-table/package.json | 1 + examples/alpine/basic-create-table/package.json | 1 + examples/alpine/basic-dynamic-columns/package.json | 1 + examples/alpine/basic-external-atoms/package.json | 1 + examples/alpine/basic-external-state/package.json | 1 + examples/alpine/cell-selection/package.json | 1 + examples/alpine/column-ordering/package.json | 1 + examples/alpine/column-pinning-split/package.json | 1 + examples/alpine/column-pinning-sticky/package.json | 1 + examples/alpine/column-pinning/package.json | 1 + examples/alpine/column-resizing-performant/package.json | 1 + examples/alpine/column-resizing/package.json | 1 + examples/alpine/column-sizing/package.json | 1 + examples/alpine/column-visibility/package.json | 1 + examples/alpine/custom-plugin/package.json | 1 + examples/alpine/expanding/package.json | 1 + examples/alpine/filters-faceted-bucketed/package.json | 1 + examples/alpine/filters-faceted/package.json | 1 + examples/alpine/filters/package.json | 1 + examples/alpine/grouped-aggregation/package.json | 1 + examples/alpine/grouping/package.json | 1 + examples/alpine/header-groups/package.json | 1 + examples/alpine/pagination/package.json | 1 + examples/alpine/row-pinning/package.json | 1 + examples/alpine/row-selection/package.json | 1 + examples/alpine/sorting-dynamic-data/package.json | 1 + examples/alpine/sorting/package.json | 1 + examples/alpine/sub-components/package.json | 1 + examples/lit/aggregation/package.json | 1 + examples/lit/basic-app-table/package.json | 1 + examples/lit/basic-dynamic-columns/package.json | 1 + examples/lit/basic-external-atoms/package.json | 1 + examples/lit/basic-external-state/package.json | 1 + examples/lit/basic-subscribe/package.json | 1 + examples/lit/basic-table-controller/package.json | 1 + examples/lit/cell-selection/package.json | 1 + examples/lit/column-ordering/package.json | 1 + examples/lit/column-pinning-split/package.json | 1 + examples/lit/column-pinning-sticky/package.json | 1 + examples/lit/column-pinning/package.json | 1 + examples/lit/column-resizing-performant/package.json | 1 + examples/lit/column-resizing/package.json | 1 + examples/lit/column-visibility/package.json | 1 + examples/lit/composable-tables/package.json | 1 + examples/lit/expanding/package.json | 1 + examples/lit/filters-faceted-bucketed/package.json | 1 + examples/lit/filters-faceted/package.json | 1 + examples/lit/filters-fuzzy/package.json | 1 + examples/lit/filters/package.json | 1 + examples/lit/grouped-aggregation/package.json | 1 + examples/lit/grouping/package.json | 1 + examples/lit/header-groups/package.json | 1 + examples/lit/kitchen-sink/package.json | 1 + examples/lit/pagination/package.json | 1 + examples/lit/row-pinning/package.json | 1 + examples/lit/row-selection/package.json | 1 + examples/lit/sorting-dynamic-data/package.json | 1 + examples/lit/sorting/package.json | 1 + examples/lit/sub-components/package.json | 1 + examples/lit/virtualized-columns/package.json | 1 + examples/lit/virtualized-infinite-scrolling/package.json | 1 + examples/lit/virtualized-rows/package.json | 1 + examples/react/aggregation/package.json | 1 + examples/react/basic-dynamic-columns/package.json | 1 + examples/react/basic-external-atoms/package.json | 1 + examples/react/basic-external-state/package.json | 1 + examples/react/basic-subscribe/package.json | 1 + examples/react/basic-use-app-table/package.json | 1 + examples/react/basic-use-legacy-table/package.json | 1 + examples/react/basic-use-table/package.json | 1 + examples/react/cell-selection/package.json | 1 + examples/react/column-dnd/package.json | 1 + examples/react/column-ordering/package.json | 1 + examples/react/column-pinning-split/package.json | 1 + examples/react/column-pinning-sticky/package.json | 1 + examples/react/column-pinning/package.json | 1 + examples/react/column-resizing-performant/package.json | 1 + examples/react/column-resizing/package.json | 1 + examples/react/column-sizing/package.json | 1 + examples/react/column-visibility/package.json | 1 + examples/react/composable-tables/package.json | 1 + examples/react/custom-plugin/package.json | 1 + examples/react/expanding/package.json | 1 + examples/react/filters-faceted-bucketed/package.json | 1 + examples/react/filters-faceted/package.json | 1 + examples/react/filters-fuzzy/package.json | 1 + examples/react/filters/package.json | 1 + examples/react/grouped-aggregation/package.json | 1 + examples/react/grouping/package.json | 1 + examples/react/header-groups/package.json | 1 + examples/react/kitchen-sink-chakra-ui/package.json | 1 + examples/react/kitchen-sink-mantine/package.json | 1 + examples/react/kitchen-sink-material-ui/package.json | 1 + examples/react/mantine-react-table/package.json | 1 + examples/react/pagination/package.json | 1 + examples/react/row-dnd/package.json | 1 + examples/react/row-pinning/package.json | 1 + examples/react/row-selection/package.json | 1 + examples/react/sorting/package.json | 1 + examples/react/spreadsheet/package.json | 1 + examples/react/sub-components/package.json | 1 + examples/react/virtualized-columns-experimental/package.json | 1 + examples/react/virtualized-columns/package.json | 1 + examples/react/virtualized-infinite-scrolling/package.json | 1 + examples/react/virtualized-rows-experimental/package.json | 1 + examples/react/virtualized-rows/package.json | 1 + examples/react/with-tanstack-form/package.json | 1 + examples/react/with-tanstack-query/package.json | 1 + examples/react/with-tanstack-router/package.json | 1 + examples/vanilla/aggregation/package.json | 1 + examples/vanilla/basic/package.json | 1 + examples/vanilla/pagination/package.json | 1 + examples/vanilla/sorting/package.json | 1 + test-results/.last-run.json | 4 ---- 115 files changed, 114 insertions(+), 4 deletions(-) delete mode 100644 test-results/.last-run.json diff --git a/examples/alpine/aggregation/package.json b/examples/alpine/aggregation/package.json index a221271e85..3ba59554c3 100644 --- a/examples/alpine/aggregation/package.json +++ b/examples/alpine/aggregation/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-aggregation", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/basic-app-table/package.json b/examples/alpine/basic-app-table/package.json index 234d943888..e67ab6906b 100644 --- a/examples/alpine/basic-app-table/package.json +++ b/examples/alpine/basic-app-table/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-basic-app-table", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/basic-create-table/package.json b/examples/alpine/basic-create-table/package.json index 54d9d9f8f6..37f1f88902 100644 --- a/examples/alpine/basic-create-table/package.json +++ b/examples/alpine/basic-create-table/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-basic-create-table", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/basic-dynamic-columns/package.json b/examples/alpine/basic-dynamic-columns/package.json index 5f0be179b4..1c16624a85 100644 --- a/examples/alpine/basic-dynamic-columns/package.json +++ b/examples/alpine/basic-dynamic-columns/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-basic-dynamic-columns", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/basic-external-atoms/package.json b/examples/alpine/basic-external-atoms/package.json index 8e59ebb13a..818e11320f 100644 --- a/examples/alpine/basic-external-atoms/package.json +++ b/examples/alpine/basic-external-atoms/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-basic-external-atoms", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/basic-external-state/package.json b/examples/alpine/basic-external-state/package.json index bce09b2ea0..6e79a3d273 100644 --- a/examples/alpine/basic-external-state/package.json +++ b/examples/alpine/basic-external-state/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-basic-external-state", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/cell-selection/package.json b/examples/alpine/cell-selection/package.json index 5409d011ee..0782fb6c33 100644 --- a/examples/alpine/cell-selection/package.json +++ b/examples/alpine/cell-selection/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-cell-selection", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/column-ordering/package.json b/examples/alpine/column-ordering/package.json index 9004cae6e5..7a2625c065 100644 --- a/examples/alpine/column-ordering/package.json +++ b/examples/alpine/column-ordering/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-column-ordering", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/column-pinning-split/package.json b/examples/alpine/column-pinning-split/package.json index 6f36a6b504..43b0c94342 100644 --- a/examples/alpine/column-pinning-split/package.json +++ b/examples/alpine/column-pinning-split/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-column-pinning-split", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/column-pinning-sticky/package.json b/examples/alpine/column-pinning-sticky/package.json index 3c083b0098..5d695366ff 100644 --- a/examples/alpine/column-pinning-sticky/package.json +++ b/examples/alpine/column-pinning-sticky/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-column-pinning-sticky", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/column-pinning/package.json b/examples/alpine/column-pinning/package.json index 6e02161871..8402cbde21 100644 --- a/examples/alpine/column-pinning/package.json +++ b/examples/alpine/column-pinning/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-column-pinning", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/column-resizing-performant/package.json b/examples/alpine/column-resizing-performant/package.json index bd245b594f..90f763f71a 100644 --- a/examples/alpine/column-resizing-performant/package.json +++ b/examples/alpine/column-resizing-performant/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-column-resizing-performant", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/column-resizing/package.json b/examples/alpine/column-resizing/package.json index cd0ccb466f..990a1484c7 100644 --- a/examples/alpine/column-resizing/package.json +++ b/examples/alpine/column-resizing/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-column-resizing", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/column-sizing/package.json b/examples/alpine/column-sizing/package.json index de5d6c9998..452b933086 100644 --- a/examples/alpine/column-sizing/package.json +++ b/examples/alpine/column-sizing/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-column-sizing", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/column-visibility/package.json b/examples/alpine/column-visibility/package.json index 2e83ab93f1..a1dde7cfbb 100644 --- a/examples/alpine/column-visibility/package.json +++ b/examples/alpine/column-visibility/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-column-visibility", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/custom-plugin/package.json b/examples/alpine/custom-plugin/package.json index 7d0a638f5d..6e1e124059 100644 --- a/examples/alpine/custom-plugin/package.json +++ b/examples/alpine/custom-plugin/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-custom-plugin", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/expanding/package.json b/examples/alpine/expanding/package.json index 8290a76a49..e45ea4404a 100644 --- a/examples/alpine/expanding/package.json +++ b/examples/alpine/expanding/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-expanding", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/filters-faceted-bucketed/package.json b/examples/alpine/filters-faceted-bucketed/package.json index 23aa262027..cbc1a2bb19 100644 --- a/examples/alpine/filters-faceted-bucketed/package.json +++ b/examples/alpine/filters-faceted-bucketed/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-filters-faceted-bucketed", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/filters-faceted/package.json b/examples/alpine/filters-faceted/package.json index e586bd29a8..89ee22cb4e 100644 --- a/examples/alpine/filters-faceted/package.json +++ b/examples/alpine/filters-faceted/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-filters-faceted", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/filters/package.json b/examples/alpine/filters/package.json index 90949b1ce5..dac3dc114a 100644 --- a/examples/alpine/filters/package.json +++ b/examples/alpine/filters/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-filters", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/grouped-aggregation/package.json b/examples/alpine/grouped-aggregation/package.json index c3ec2144f1..128b01bdd2 100644 --- a/examples/alpine/grouped-aggregation/package.json +++ b/examples/alpine/grouped-aggregation/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-grouped-aggregation", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/grouping/package.json b/examples/alpine/grouping/package.json index b456f3c379..821414d2e7 100644 --- a/examples/alpine/grouping/package.json +++ b/examples/alpine/grouping/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-grouping", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/header-groups/package.json b/examples/alpine/header-groups/package.json index 8a878aab08..7138ca7f47 100644 --- a/examples/alpine/header-groups/package.json +++ b/examples/alpine/header-groups/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-header-groups", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/pagination/package.json b/examples/alpine/pagination/package.json index b2241fffbb..8dbc2803d7 100644 --- a/examples/alpine/pagination/package.json +++ b/examples/alpine/pagination/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-pagination", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/row-pinning/package.json b/examples/alpine/row-pinning/package.json index 612819c81c..e60725f5be 100644 --- a/examples/alpine/row-pinning/package.json +++ b/examples/alpine/row-pinning/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-row-pinning", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/row-selection/package.json b/examples/alpine/row-selection/package.json index 2811546da0..87e800c12c 100644 --- a/examples/alpine/row-selection/package.json +++ b/examples/alpine/row-selection/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-row-selection", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/sorting-dynamic-data/package.json b/examples/alpine/sorting-dynamic-data/package.json index 5071718a2f..ed54cc96dc 100644 --- a/examples/alpine/sorting-dynamic-data/package.json +++ b/examples/alpine/sorting-dynamic-data/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-sorting-dynamic-data", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/sorting/package.json b/examples/alpine/sorting/package.json index abf7625478..564b3e2e0c 100644 --- a/examples/alpine/sorting/package.json +++ b/examples/alpine/sorting/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-sorting", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/alpine/sub-components/package.json b/examples/alpine/sub-components/package.json index 4976c3f3f0..3b61362b12 100644 --- a/examples/alpine/sub-components/package.json +++ b/examples/alpine/sub-components/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-alpine-table-example-sub-components", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/aggregation/package.json b/examples/lit/aggregation/package.json index 5428c15311..a80f99e422 100644 --- a/examples/lit/aggregation/package.json +++ b/examples/lit/aggregation/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-aggregation", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/basic-app-table/package.json b/examples/lit/basic-app-table/package.json index 0c661100e3..2c7c0910fa 100644 --- a/examples/lit/basic-app-table/package.json +++ b/examples/lit/basic-app-table/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-basic-app-table", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/basic-dynamic-columns/package.json b/examples/lit/basic-dynamic-columns/package.json index 386adb045e..e96f59341c 100644 --- a/examples/lit/basic-dynamic-columns/package.json +++ b/examples/lit/basic-dynamic-columns/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-basic-dynamic-columns", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/basic-external-atoms/package.json b/examples/lit/basic-external-atoms/package.json index 323a901b16..4ad5a024cc 100644 --- a/examples/lit/basic-external-atoms/package.json +++ b/examples/lit/basic-external-atoms/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-basic-external-atoms", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/basic-external-state/package.json b/examples/lit/basic-external-state/package.json index d225096fea..01d14c0985 100644 --- a/examples/lit/basic-external-state/package.json +++ b/examples/lit/basic-external-state/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-basic-external-state", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/basic-subscribe/package.json b/examples/lit/basic-subscribe/package.json index c4a2247eae..fa56161766 100644 --- a/examples/lit/basic-subscribe/package.json +++ b/examples/lit/basic-subscribe/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-basic-subscribe", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/basic-table-controller/package.json b/examples/lit/basic-table-controller/package.json index 00c5906e36..d00e49d471 100644 --- a/examples/lit/basic-table-controller/package.json +++ b/examples/lit/basic-table-controller/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-basic-table-controller", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/cell-selection/package.json b/examples/lit/cell-selection/package.json index 52ae7f1824..cd40d6d545 100644 --- a/examples/lit/cell-selection/package.json +++ b/examples/lit/cell-selection/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-cell-selection", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/column-ordering/package.json b/examples/lit/column-ordering/package.json index fc80c5eba9..0427eecde1 100644 --- a/examples/lit/column-ordering/package.json +++ b/examples/lit/column-ordering/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-column-ordering", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/column-pinning-split/package.json b/examples/lit/column-pinning-split/package.json index 4795020923..7b87918a6d 100644 --- a/examples/lit/column-pinning-split/package.json +++ b/examples/lit/column-pinning-split/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-column-pinning-split", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/column-pinning-sticky/package.json b/examples/lit/column-pinning-sticky/package.json index 336d600478..b71114ffa3 100644 --- a/examples/lit/column-pinning-sticky/package.json +++ b/examples/lit/column-pinning-sticky/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-column-pinning-sticky", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/column-pinning/package.json b/examples/lit/column-pinning/package.json index 07e6b627e6..9a91b512b4 100644 --- a/examples/lit/column-pinning/package.json +++ b/examples/lit/column-pinning/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-column-pinning", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/column-resizing-performant/package.json b/examples/lit/column-resizing-performant/package.json index 633cea443a..6c4c5596b1 100644 --- a/examples/lit/column-resizing-performant/package.json +++ b/examples/lit/column-resizing-performant/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-column-resizing-performant", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/column-resizing/package.json b/examples/lit/column-resizing/package.json index 951fa0ab58..166618bd56 100644 --- a/examples/lit/column-resizing/package.json +++ b/examples/lit/column-resizing/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-column-resizing", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/column-visibility/package.json b/examples/lit/column-visibility/package.json index 9236f38d88..f164a7ea85 100644 --- a/examples/lit/column-visibility/package.json +++ b/examples/lit/column-visibility/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-column-visibility", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/composable-tables/package.json b/examples/lit/composable-tables/package.json index 0d19c84861..3390307067 100644 --- a/examples/lit/composable-tables/package.json +++ b/examples/lit/composable-tables/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-composable-tables", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/expanding/package.json b/examples/lit/expanding/package.json index d333c18f88..3d8dcbebec 100644 --- a/examples/lit/expanding/package.json +++ b/examples/lit/expanding/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-expanding", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/filters-faceted-bucketed/package.json b/examples/lit/filters-faceted-bucketed/package.json index 99ac4ce2aa..cbe9cd1f35 100644 --- a/examples/lit/filters-faceted-bucketed/package.json +++ b/examples/lit/filters-faceted-bucketed/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-filters-faceted-bucketed", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/filters-faceted/package.json b/examples/lit/filters-faceted/package.json index 316f4f55a3..ec52956251 100644 --- a/examples/lit/filters-faceted/package.json +++ b/examples/lit/filters-faceted/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-filters-faceted", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/filters-fuzzy/package.json b/examples/lit/filters-fuzzy/package.json index dee8cc5470..7648332396 100644 --- a/examples/lit/filters-fuzzy/package.json +++ b/examples/lit/filters-fuzzy/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-filters-fuzzy", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/filters/package.json b/examples/lit/filters/package.json index c72cabcbb8..0b49aa5a75 100644 --- a/examples/lit/filters/package.json +++ b/examples/lit/filters/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-filters", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/grouped-aggregation/package.json b/examples/lit/grouped-aggregation/package.json index 690c3be4b0..3ff515a80e 100644 --- a/examples/lit/grouped-aggregation/package.json +++ b/examples/lit/grouped-aggregation/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-grouped-aggregation", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/grouping/package.json b/examples/lit/grouping/package.json index 79a80f581b..ad37876fa3 100644 --- a/examples/lit/grouping/package.json +++ b/examples/lit/grouping/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-grouping", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/header-groups/package.json b/examples/lit/header-groups/package.json index c0a2944fdf..b265dc3fc5 100644 --- a/examples/lit/header-groups/package.json +++ b/examples/lit/header-groups/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-header-groups", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/kitchen-sink/package.json b/examples/lit/kitchen-sink/package.json index e5d95953af..56ef5bd149 100644 --- a/examples/lit/kitchen-sink/package.json +++ b/examples/lit/kitchen-sink/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-kitchen-sink", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/pagination/package.json b/examples/lit/pagination/package.json index bee5885e54..4ddaffc3f3 100644 --- a/examples/lit/pagination/package.json +++ b/examples/lit/pagination/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-pagination", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/row-pinning/package.json b/examples/lit/row-pinning/package.json index 2a88a68328..1d1f6f184a 100644 --- a/examples/lit/row-pinning/package.json +++ b/examples/lit/row-pinning/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-row-pinning", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/row-selection/package.json b/examples/lit/row-selection/package.json index cd546a8833..0f9ec438ef 100644 --- a/examples/lit/row-selection/package.json +++ b/examples/lit/row-selection/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-row-selection", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/sorting-dynamic-data/package.json b/examples/lit/sorting-dynamic-data/package.json index f5e6ab7dc8..cd96e14022 100644 --- a/examples/lit/sorting-dynamic-data/package.json +++ b/examples/lit/sorting-dynamic-data/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-sorting-dynamic-data", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/sorting/package.json b/examples/lit/sorting/package.json index a7926be049..d08792d472 100644 --- a/examples/lit/sorting/package.json +++ b/examples/lit/sorting/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-sorting", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/sub-components/package.json b/examples/lit/sub-components/package.json index 97834387d8..21a63b488e 100644 --- a/examples/lit/sub-components/package.json +++ b/examples/lit/sub-components/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-sub-components", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/virtualized-columns/package.json b/examples/lit/virtualized-columns/package.json index 77c660b143..62ddaa72a6 100644 --- a/examples/lit/virtualized-columns/package.json +++ b/examples/lit/virtualized-columns/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-virtualized-columns", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/virtualized-infinite-scrolling/package.json b/examples/lit/virtualized-infinite-scrolling/package.json index 0f71458038..df09ab40e0 100644 --- a/examples/lit/virtualized-infinite-scrolling/package.json +++ b/examples/lit/virtualized-infinite-scrolling/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-virtualized-infinite-scrolling", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/lit/virtualized-rows/package.json b/examples/lit/virtualized-rows/package.json index 33bd85fe3f..162804b2d1 100644 --- a/examples/lit/virtualized-rows/package.json +++ b/examples/lit/virtualized-rows/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-lit-table-example-virtualized-rows", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/aggregation/package.json b/examples/react/aggregation/package.json index 936daffb09..b4410eeaad 100644 --- a/examples/react/aggregation/package.json +++ b/examples/react/aggregation/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-aggregation", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/basic-dynamic-columns/package.json b/examples/react/basic-dynamic-columns/package.json index 3001e55c4f..6d501ef4ec 100644 --- a/examples/react/basic-dynamic-columns/package.json +++ b/examples/react/basic-dynamic-columns/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-basic-dynamic-columns", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/basic-external-atoms/package.json b/examples/react/basic-external-atoms/package.json index 33df47bbf5..ca6bdf02f0 100644 --- a/examples/react/basic-external-atoms/package.json +++ b/examples/react/basic-external-atoms/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-basic-external-atoms", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/basic-external-state/package.json b/examples/react/basic-external-state/package.json index 254ea774c3..9abb5452ed 100644 --- a/examples/react/basic-external-state/package.json +++ b/examples/react/basic-external-state/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-basic-external-state", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/basic-subscribe/package.json b/examples/react/basic-subscribe/package.json index c55b4db0b0..a6c8eb5faf 100644 --- a/examples/react/basic-subscribe/package.json +++ b/examples/react/basic-subscribe/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-basic-subscribe", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/basic-use-app-table/package.json b/examples/react/basic-use-app-table/package.json index 919d58e0ae..51f2d1045f 100644 --- a/examples/react/basic-use-app-table/package.json +++ b/examples/react/basic-use-app-table/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-basic-use-app-table", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/basic-use-legacy-table/package.json b/examples/react/basic-use-legacy-table/package.json index ddd75c5c72..daa2e71c6c 100644 --- a/examples/react/basic-use-legacy-table/package.json +++ b/examples/react/basic-use-legacy-table/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-basic-use-legacy-table", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/basic-use-table/package.json b/examples/react/basic-use-table/package.json index b484fafcbe..1ecf227e19 100644 --- a/examples/react/basic-use-table/package.json +++ b/examples/react/basic-use-table/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-basic-use-table", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/cell-selection/package.json b/examples/react/cell-selection/package.json index c31f141b1c..5b5b2373ff 100644 --- a/examples/react/cell-selection/package.json +++ b/examples/react/cell-selection/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-cell-selection", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-dnd/package.json b/examples/react/column-dnd/package.json index 5bb6305453..ab2627fcd3 100644 --- a/examples/react/column-dnd/package.json +++ b/examples/react/column-dnd/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-dnd", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-ordering/package.json b/examples/react/column-ordering/package.json index ff707e791d..ad7b841f4d 100644 --- a/examples/react/column-ordering/package.json +++ b/examples/react/column-ordering/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-ordering", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-pinning-split/package.json b/examples/react/column-pinning-split/package.json index 527734ea87..5b0e392ef5 100644 --- a/examples/react/column-pinning-split/package.json +++ b/examples/react/column-pinning-split/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-pinning-split", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-pinning-sticky/package.json b/examples/react/column-pinning-sticky/package.json index c2b561e500..1d70f5a365 100644 --- a/examples/react/column-pinning-sticky/package.json +++ b/examples/react/column-pinning-sticky/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-pinning-sticky", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-pinning/package.json b/examples/react/column-pinning/package.json index 1bd60c198d..d81d725541 100644 --- a/examples/react/column-pinning/package.json +++ b/examples/react/column-pinning/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-pinning", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-resizing-performant/package.json b/examples/react/column-resizing-performant/package.json index fb7c1d2771..59b36dd277 100644 --- a/examples/react/column-resizing-performant/package.json +++ b/examples/react/column-resizing-performant/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-resizing-performant", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-resizing/package.json b/examples/react/column-resizing/package.json index 8c5ed032f7..fea3204bab 100644 --- a/examples/react/column-resizing/package.json +++ b/examples/react/column-resizing/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-resizing", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-sizing/package.json b/examples/react/column-sizing/package.json index bf5d89d0b9..45e6472326 100644 --- a/examples/react/column-sizing/package.json +++ b/examples/react/column-sizing/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-sizing", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/column-visibility/package.json b/examples/react/column-visibility/package.json index 838645dfba..da87210baa 100644 --- a/examples/react/column-visibility/package.json +++ b/examples/react/column-visibility/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-column-visibility", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/composable-tables/package.json b/examples/react/composable-tables/package.json index e15aeaeb8e..c54e9569cd 100644 --- a/examples/react/composable-tables/package.json +++ b/examples/react/composable-tables/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-composable-tables", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/custom-plugin/package.json b/examples/react/custom-plugin/package.json index 47f8cb9333..8ba218137f 100644 --- a/examples/react/custom-plugin/package.json +++ b/examples/react/custom-plugin/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-custom-plugin", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/expanding/package.json b/examples/react/expanding/package.json index efdb099608..262d88d527 100644 --- a/examples/react/expanding/package.json +++ b/examples/react/expanding/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-expanding", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/filters-faceted-bucketed/package.json b/examples/react/filters-faceted-bucketed/package.json index 7d3bb50f9e..7f999a56cf 100644 --- a/examples/react/filters-faceted-bucketed/package.json +++ b/examples/react/filters-faceted-bucketed/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-filters-faceted-bucketed", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/filters-faceted/package.json b/examples/react/filters-faceted/package.json index 339e5f97e5..b21f627ce0 100644 --- a/examples/react/filters-faceted/package.json +++ b/examples/react/filters-faceted/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-filters-faceted", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/filters-fuzzy/package.json b/examples/react/filters-fuzzy/package.json index 2758785d47..ffa398024f 100644 --- a/examples/react/filters-fuzzy/package.json +++ b/examples/react/filters-fuzzy/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-filters-fuzzy", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/filters/package.json b/examples/react/filters/package.json index f8fe367fe4..f98ef54bc3 100644 --- a/examples/react/filters/package.json +++ b/examples/react/filters/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-filters", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/grouped-aggregation/package.json b/examples/react/grouped-aggregation/package.json index 424e94096d..ba5df352d0 100644 --- a/examples/react/grouped-aggregation/package.json +++ b/examples/react/grouped-aggregation/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-grouped-aggregation", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/grouping/package.json b/examples/react/grouping/package.json index 33fc5908f6..f56b4d1912 100644 --- a/examples/react/grouping/package.json +++ b/examples/react/grouping/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-grouping", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/header-groups/package.json b/examples/react/header-groups/package.json index e49d6764cc..09e0c8a148 100644 --- a/examples/react/header-groups/package.json +++ b/examples/react/header-groups/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-header-groups", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/kitchen-sink-chakra-ui/package.json b/examples/react/kitchen-sink-chakra-ui/package.json index 8d716ff262..77644e7a61 100644 --- a/examples/react/kitchen-sink-chakra-ui/package.json +++ b/examples/react/kitchen-sink-chakra-ui/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-kitchen-sink-chakra-ui", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/kitchen-sink-mantine/package.json b/examples/react/kitchen-sink-mantine/package.json index 379b840f89..12d23591d7 100644 --- a/examples/react/kitchen-sink-mantine/package.json +++ b/examples/react/kitchen-sink-mantine/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-kitchen-sink-mantine", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/kitchen-sink-material-ui/package.json b/examples/react/kitchen-sink-material-ui/package.json index a1061f47bf..2c05075e37 100644 --- a/examples/react/kitchen-sink-material-ui/package.json +++ b/examples/react/kitchen-sink-material-ui/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-kitchen-sink-material-ui", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/mantine-react-table/package.json b/examples/react/mantine-react-table/package.json index c332e9929f..a110713324 100644 --- a/examples/react/mantine-react-table/package.json +++ b/examples/react/mantine-react-table/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-mantine-react-table", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/pagination/package.json b/examples/react/pagination/package.json index e231d7ca49..24fd0fd433 100644 --- a/examples/react/pagination/package.json +++ b/examples/react/pagination/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-pagination", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/row-dnd/package.json b/examples/react/row-dnd/package.json index 07f0473c30..82c1449e77 100644 --- a/examples/react/row-dnd/package.json +++ b/examples/react/row-dnd/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-row-dnd", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/row-pinning/package.json b/examples/react/row-pinning/package.json index 45de1b6bd5..f7fe340c56 100644 --- a/examples/react/row-pinning/package.json +++ b/examples/react/row-pinning/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-row-pinning", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/row-selection/package.json b/examples/react/row-selection/package.json index 421f060c0b..7ee8ddf698 100644 --- a/examples/react/row-selection/package.json +++ b/examples/react/row-selection/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-row-selection", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/sorting/package.json b/examples/react/sorting/package.json index d3ec66d886..e089eebef3 100644 --- a/examples/react/sorting/package.json +++ b/examples/react/sorting/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-sorting", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/spreadsheet/package.json b/examples/react/spreadsheet/package.json index 33014b400b..9f86104221 100644 --- a/examples/react/spreadsheet/package.json +++ b/examples/react/spreadsheet/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-spreadsheet", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/sub-components/package.json b/examples/react/sub-components/package.json index 7d8ec12442..4b2ae3c62b 100644 --- a/examples/react/sub-components/package.json +++ b/examples/react/sub-components/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-sub-components", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/virtualized-columns-experimental/package.json b/examples/react/virtualized-columns-experimental/package.json index 3d0f77cf9f..c866b6c82a 100644 --- a/examples/react/virtualized-columns-experimental/package.json +++ b/examples/react/virtualized-columns-experimental/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-virtualized-columns-experimental", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/virtualized-columns/package.json b/examples/react/virtualized-columns/package.json index b12f8b3849..9a831a2615 100644 --- a/examples/react/virtualized-columns/package.json +++ b/examples/react/virtualized-columns/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-virtualized-columns", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/virtualized-infinite-scrolling/package.json b/examples/react/virtualized-infinite-scrolling/package.json index d8ea393f7c..0c7669659c 100644 --- a/examples/react/virtualized-infinite-scrolling/package.json +++ b/examples/react/virtualized-infinite-scrolling/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-virtualized-infinite-scrolling", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/virtualized-rows-experimental/package.json b/examples/react/virtualized-rows-experimental/package.json index 410e0b0e8a..d9bb814161 100644 --- a/examples/react/virtualized-rows-experimental/package.json +++ b/examples/react/virtualized-rows-experimental/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-virtualized-rows-experimental", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/virtualized-rows/package.json b/examples/react/virtualized-rows/package.json index 74b700437e..81e1ba0473 100644 --- a/examples/react/virtualized-rows/package.json +++ b/examples/react/virtualized-rows/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-virtualized-rows", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/with-tanstack-form/package.json b/examples/react/with-tanstack-form/package.json index 7f2ca4a2a2..ac0561968b 100644 --- a/examples/react/with-tanstack-form/package.json +++ b/examples/react/with-tanstack-form/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-with-tanstack-form", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/with-tanstack-query/package.json b/examples/react/with-tanstack-query/package.json index 23b571d62f..6039cd6a14 100644 --- a/examples/react/with-tanstack-query/package.json +++ b/examples/react/with-tanstack-query/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-with-tanstack-query", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/react/with-tanstack-router/package.json b/examples/react/with-tanstack-router/package.json index d4c063cb25..f83e501a06 100644 --- a/examples/react/with-tanstack-router/package.json +++ b/examples/react/with-tanstack-router/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-react-table-example-with-tanstack-router", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/vanilla/aggregation/package.json b/examples/vanilla/aggregation/package.json index a15bec18ba..f9f9f26836 100644 --- a/examples/vanilla/aggregation/package.json +++ b/examples/vanilla/aggregation/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-vanilla-table-example-aggregation", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/vanilla/basic/package.json b/examples/vanilla/basic/package.json index fb8bd425a9..a3a2445bf8 100644 --- a/examples/vanilla/basic/package.json +++ b/examples/vanilla/basic/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-vanilla-table-example-basic", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/vanilla/pagination/package.json b/examples/vanilla/pagination/package.json index caf0c81da0..b0d846da6f 100644 --- a/examples/vanilla/pagination/package.json +++ b/examples/vanilla/pagination/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-vanilla-table-example-pagination", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/examples/vanilla/sorting/package.json b/examples/vanilla/sorting/package.json index 9e76d739bb..755663c24e 100644 --- a/examples/vanilla/sorting/package.json +++ b/examples/vanilla/sorting/package.json @@ -1,6 +1,7 @@ { "name": "tanstack-vanilla-table-example-sorting", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", diff --git a/test-results/.last-run.json b/test-results/.last-run.json deleted file mode 100644 index cbcc1fbac1..0000000000 --- a/test-results/.last-run.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "passed", - "failedTests": [] -} \ No newline at end of file From 55b025f55e85dc8c80769084a93fa8458716a975 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 1 Aug 2026 07:20:20 -0500 Subject: [PATCH 7/7] remove test results --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index df459c9799..484cfce297 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ es artifacts .rpt2_cache coverage +test-results *.tgz # misc