From fdecb34b8021a3306a71c7f7f286105b16c9bd32 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 25 Jul 2026 16:32:31 -0500 Subject: [PATCH 1/3] feat: cell selection feature --- _artifacts/domain_map.yaml | 17 + _artifacts/skill_tree.yaml | 13 + docs/config.json | 18 + docs/framework/alpine/guide/cell-selection.md | 367 ++++++ .../framework/angular/guide/cell-selection.md | 365 ++++++ docs/framework/angular/guide/migrating.md | 3 +- docs/framework/ember/guide/cell-selection.md | 342 +++++ docs/framework/lit/guide/cell-selection.md | 350 +++++ docs/framework/lit/guide/migrating.md | 3 +- docs/framework/preact/guide/cell-selection.md | 369 ++++++ docs/framework/preact/guide/migrating.md | 3 +- docs/framework/react/guide/cell-selection.md | 409 ++++++ docs/framework/react/guide/migrating.md | 3 +- docs/framework/solid/guide/cell-selection.md | 347 +++++ docs/framework/solid/guide/migrating.md | 3 +- docs/framework/svelte/guide/cell-selection.md | 357 ++++++ docs/framework/svelte/guide/migrating.md | 3 +- docs/framework/vue/guide/cell-selection.md | 343 +++++ docs/framework/vue/guide/migrating.md | 3 +- .../index/functions/createCoreRowModel.md | 2 +- docs/reference/index/functions/expandRows.md | 2 +- .../index/functions/makeStateUpdater.md | 3 +- docs/reference/index/index.md | 24 +- .../index/interfaces/CellSelectionBounds.md | 53 + .../index/interfaces/CellSelectionEdges.md | 50 + .../index/interfaces/CellSelectionRange.md | 56 + .../index/interfaces/Cell_CellSelection.md | 166 +++ docs/reference/index/interfaces/Cell_Core.md | 2 +- .../index/interfaces/Cell_FeatureMap.md | 18 +- ..._Aggregation.md => Cell_RowAggregation.md} | 6 +- .../interfaces/ColumnDef_CellSelection.md | 20 + .../index/interfaces/ColumnDef_FeatureMap.md | 32 +- ...egation.md => ColumnDef_RowAggregation.md} | 6 +- docs/reference/index/interfaces/ColumnMeta.md | 2 +- .../index/interfaces/Column_FeatureMap.md | 2 +- ...ggregation.md => Column_RowAggregation.md} | 6 +- .../index/interfaces/IdIdentifier.md | 6 +- .../index/interfaces/RowModelFns_All.md | 2 +- .../interfaces/RowModelFns_FeatureMap.md | 2 +- ...ation.md => RowModelFns_RowAggregation.md} | 6 +- .../index/interfaces/Row_FeatureMap.md | 2 +- ...w_Aggregation.md => Row_RowAggregation.md} | 6 +- .../interfaces/SelectCellRangeOptions.md | 21 + .../index/interfaces/StockFeatures.md | 42 +- .../interfaces/StringHeaderIdentifier.md | 6 +- .../index/interfaces/TableFeatures.md | 44 +- .../interfaces/TableOptions_CellSelection.md | 158 +++ .../index/interfaces/TableOptions_Core.md | 2 +- .../interfaces/TableOptions_FeatureMap.md | 42 +- ...tion.md => TableOptions_RowAggregation.md} | 6 +- .../index/interfaces/TableState_All.md | 18 +- .../interfaces/TableState_CellSelection.md | 18 + .../index/interfaces/TableState_FeatureMap.md | 38 +- .../index/interfaces/Table_CellSelection.md | 363 ++++++ docs/reference/index/interfaces/Table_Core.md | 2 +- .../index/interfaces/Table_FeatureMap.md | 40 +- .../index/interfaces/Table_Internal.md | 19 +- .../index/type-aliases/AccessorColumnDef.md | 2 +- .../index/type-aliases/AccessorFn.md | 2 +- .../index/type-aliases/AccessorFnColumnDef.md | 2 +- .../type-aliases/AccessorFnColumnDefBase.md | 2 +- .../type-aliases/AccessorKeyColumnDef.md | 2 +- .../type-aliases/AccessorKeyColumnDefBase.md | 2 +- docs/reference/index/type-aliases/Cell.md | 2 +- .../type-aliases/CellSelectionDirection.md | 12 + .../index/type-aliases/CellSelectionState.md | 19 + .../reference/index/type-aliases/ColumnDef.md | 2 +- .../index/type-aliases/ColumnDefBase.md | 2 +- .../index/type-aliases/ColumnDefBase_All.md | 4 +- .../index/type-aliases/ColumnDefResolved.md | 2 +- .../index/type-aliases/ColumnDefTemplate.md | 2 +- .../index/type-aliases/DebugOptions.md | 2 +- .../reference/index/type-aliases/DeepValue.md | 2 +- .../index/type-aliases/DisplayColumnDef.md | 2 +- .../index/type-aliases/ExtractColumnMeta.md | 2 +- docs/reference/index/type-aliases/Getter.md | 2 +- .../index/type-aliases/GroupColumnDef.md | 2 +- .../index/type-aliases/IdentifiedColumnDef.md | 2 +- docs/reference/index/type-aliases/NoInfer.md | 2 +- docs/reference/index/type-aliases/Prettify.md | 2 +- .../type-aliases/StringOrTemplateHeader.md | 2 +- docs/reference/index/type-aliases/Table.md | 2 +- .../index/type-aliases/TableOptions.md | 2 +- .../index/type-aliases/TableOptions_All.md | 4 +- .../index/type-aliases/TableState.md | 2 +- .../index/variables/cellSelectionFeature.md | 14 + .../index/variables/stockFeatures.md | 2 +- .../functions/cell_getCanSelect.md | 47 + .../functions/cell_getIsFocused.md | 44 + .../functions/cell_getIsSelected.md | 36 + .../functions/cell_getSelectionEdges.md | 48 + .../cell_getSelectionExtendHandler.md | 60 + .../cell_getSelectionStartHandler.md | 65 + .../functions/cell_getTabIndex.md | 44 + .../functions/column_getCanFilter.md | 2 +- .../functions/column_getFilterFn.md | 2 +- .../functions/column_getFilterIndex.md | 2 +- .../functions/column_getFilterValue.md | 2 +- .../functions/column_getIsFiltered.md | 2 +- .../column_getToggleSortingHandler.md | 2 +- .../functions/column_setFilterValue.md | 2 +- .../functions/getDefaultCellSelectionState.md | 27 + .../functions/isRowSelected.md | 2 +- .../functions/isSubRowSelected.md | 2 +- .../functions/isTouchStartEvent.md | 2 +- .../functions/passiveEventSupported.md | 2 +- .../functions/row_getAllCells.md | 2 +- .../functions/row_getAllCellsByColumnId.md | 2 +- .../functions/row_getCanExpand.md | 2 +- .../functions/row_getIsAllParentsExpanded.md | 2 +- .../functions/row_getIsExpanded.md | 2 +- .../functions/row_getParentRow.md | 4 +- .../functions/row_getParentRows.md | 2 +- .../functions/row_getToggleExpandedHandler.md | 2 +- .../functions/row_getToggleSelectedHandler.md | 2 +- .../functions/row_toggleExpanded.md | 2 +- .../functions/selectRowsFn.md | 2 +- .../functions/shouldAutoRemoveFilter.md | 2 +- .../functions/table_autoResetCellSelection.md | 48 + .../functions/table_extendCellSelection.md | 44 + .../functions/table_getCellSelectionBounds.md | 46 + .../table_getCellSelectionColumnIds.md | 40 + .../table_getCellSelectionColumnIndexes.md | 45 + .../functions/table_getCellSelectionRowIds.md | 40 + .../functions/table_getExpandedDepth.md | 2 +- .../functions/table_getFocusedCell.md | 47 + .../functions/table_getIsAllRowsExpanded.md | 2 +- .../functions/table_getIsSomeRowsExpanded.md | 2 +- .../functions/table_getRow.md | 2 +- .../functions/table_getRowId.md | 2 +- .../functions/table_getSelectedCellCount.md | 44 + .../functions/table_getSelectedCellIds.md | 40 + .../table_getSelectedCellRangesData.md | 44 + .../table_getToggleAllRowsExpandedHandler.md | 8 +- .../functions/table_moveCellSelection.md | 47 + .../functions/table_resetCellSelection.md | 47 + .../functions/table_resetColumnFilters.md | 2 +- .../functions/table_resetHeaderSizeInfo.md | 2 +- .../functions/table_selectAllCells.md | 40 + .../functions/table_selectCellRange.md | 51 + .../functions/table_setCellSelection.md | 44 + .../functions/table_setColumnFilters.md | 2 +- .../functions/table_setColumnResizing.md | 2 +- .../functions/table_setFocusedCell.md | 51 + docs/reference/static-functions/index.md | 24 + .../.devcontainer/devcontainer.json | 4 + examples/alpine/cell-selection/index.html | 231 ++++ examples/alpine/cell-selection/package.json | 25 + examples/alpine/cell-selection/src/index.css | 189 +++ examples/alpine/cell-selection/src/main.ts | 245 ++++ .../alpine/cell-selection/src/makeData.ts | 64 + .../alpine/cell-selection/src/vite-env.d.ts | 9 + .../cell-selection/tests/e2e/smoke.spec.ts | 104 ++ examples/alpine/cell-selection/tsconfig.json | 19 + examples/alpine/cell-selection/vite.config.js | 18 + .../.devcontainer/devcontainer.json | 4 + examples/angular/cell-selection/.gitignore | 44 + examples/angular/cell-selection/angular.json | 99 ++ examples/angular/cell-selection/package.json | 37 + .../angular/cell-selection/public/favicon.ico | Bin 0 -> 15086 bytes .../cell-selection/src/app/app.config.ts | 22 + .../angular/cell-selection/src/app/app.html | 179 +++ .../angular/cell-selection/src/app/app.ts | 267 ++++ .../cell-selection/src/app/makeData.ts | 64 + .../angular/cell-selection/src/app/table.ts | 38 + .../angular/cell-selection/src/index.html | 13 + examples/angular/cell-selection/src/main.ts | 5 + .../angular/cell-selection/src/styles.css | 421 ++++++ .../cell-selection/tests/e2e/smoke.spec.ts | 104 ++ .../angular/cell-selection/tsconfig.app.json | 11 + examples/angular/cell-selection/tsconfig.json | 23 + .../.devcontainer/devcontainer.json | 4 + examples/ember/cell-selection/app/app.css | 357 ++++++ examples/ember/cell-selection/app/app.ts | 22 + examples/ember/cell-selection/app/config.ts | 17 + examples/ember/cell-selection/app/router.ts | 51 + .../app/templates/application.gts | 553 ++++++++ .../cell-selection/app/utils/make-data.ts | 64 + examples/ember/cell-selection/babel.config.js | 49 + examples/ember/cell-selection/index.html | 17 + examples/ember/cell-selection/package.json | 59 + .../cell-selection/tests/e2e/smoke.spec.ts | 104 ++ examples/ember/cell-selection/tsconfig.json | 12 + examples/ember/cell-selection/vite.config.mjs | 9 + .../.devcontainer/devcontainer.json | 4 + examples/lit/cell-selection/.gitignore | 5 + examples/lit/cell-selection/index.html | 13 + examples/lit/cell-selection/package.json | 24 + examples/lit/cell-selection/src/main.ts | 531 ++++++++ examples/lit/cell-selection/src/makeData.ts | 64 + .../cell-selection/tests/e2e/smoke.spec.ts | 104 ++ examples/lit/cell-selection/tsconfig.json | 21 + examples/lit/cell-selection/vite.config.js | 18 + .../.devcontainer/devcontainer.json | 4 + examples/preact/cell-selection/index.html | 13 + examples/preact/cell-selection/package.json | 28 + examples/preact/cell-selection/src/index.css | 428 +++++++ examples/preact/cell-selection/src/main.tsx | 584 +++++++++ .../preact/cell-selection/src/makeData.ts | 64 + .../preact/cell-selection/src/vite-env.d.ts | 1 + .../cell-selection/tests/e2e/smoke.spec.ts | 104 ++ examples/preact/cell-selection/tsconfig.json | 26 + examples/preact/cell-selection/vite.config.ts | 10 + .../.devcontainer/devcontainer.json | 4 + examples/react/cell-selection/.gitignore | 5 + examples/react/cell-selection/index.html | 13 + examples/react/cell-selection/package.json | 33 + examples/react/cell-selection/src/index.css | 421 ++++++ examples/react/cell-selection/src/main.tsx | 583 +++++++++ examples/react/cell-selection/src/makeData.ts | 64 + .../react/cell-selection/src/vite-env.d.ts | 1 + .../cell-selection/tests/e2e/smoke.spec.ts | 86 ++ examples/react/cell-selection/tsconfig.json | 21 + examples/react/cell-selection/vite.config.js | 26 + .../mantine-react-table/fns/aggregationFns.ts | 2 +- .../hooks/useMRT_TableOptions.ts | 4 +- .../src/mantine-react-table/types.ts | 15 +- .../features/mrtFeatures.ts | 6 +- .../fns/aggregationFns.ts | 2 +- .../src/material-react-table/types.ts | 9 +- .../.devcontainer/devcontainer.json | 4 + examples/solid/cell-selection/index.html | 15 + examples/solid/cell-selection/package.json | 27 + examples/solid/cell-selection/src/App.tsx | 505 ++++++++ examples/solid/cell-selection/src/index.css | 421 ++++++ examples/solid/cell-selection/src/index.tsx | 16 + examples/solid/cell-selection/src/makeData.ts | 64 + .../solid/cell-selection/src/vite-env.d.ts | 1 + .../cell-selection/tests/e2e/smoke.spec.ts | 104 ++ examples/solid/cell-selection/tsconfig.json | 21 + examples/solid/cell-selection/vite.config.ts | 12 + .../.devcontainer/devcontainer.json | 4 + examples/svelte/cell-selection/index.html | 13 + examples/svelte/cell-selection/package.json | 25 + examples/svelte/cell-selection/src/App.svelte | 439 +++++++ examples/svelte/cell-selection/src/index.css | 421 ++++++ examples/svelte/cell-selection/src/main.ts | 9 + .../svelte/cell-selection/src/makeData.ts | 64 + .../svelte/cell-selection/src/vite-env.d.ts | 4 + .../svelte/cell-selection/svelte.config.js | 5 + .../cell-selection/tests/e2e/smoke.spec.ts | 132 ++ examples/svelte/cell-selection/tsconfig.json | 22 + examples/svelte/cell-selection/vite.config.js | 20 + .../.devcontainer/devcontainer.json | 4 + examples/vue/cell-selection/.gitignore | 24 + examples/vue/cell-selection/env.d.ts | 1 + examples/vue/cell-selection/index.html | 13 + examples/vue/cell-selection/package.json | 28 + .../vue/cell-selection/public/favicon.ico | Bin 0 -> 4286 bytes examples/vue/cell-selection/src/App.vue | 452 +++++++ examples/vue/cell-selection/src/env.d.ts | 8 + examples/vue/cell-selection/src/index.css | 421 ++++++ examples/vue/cell-selection/src/main.ts | 18 + examples/vue/cell-selection/src/makeData.ts | 64 + .../cell-selection/tests/e2e/smoke.spec.ts | 104 ++ examples/vue/cell-selection/tsconfig.json | 28 + examples/vue/cell-selection/vite.config.ts | 10 + .../table-core/skills/aggregation/SKILL.md | 2 +- .../table-core/skills/cell-selection/SKILL.md | 180 +++ .../src/core/row-models/createCoreRowModel.ts | 8 +- .../cell-selection/cellSelectionFeature.ts | 230 ++++ .../cellSelectionFeature.types.ts | 303 +++++ .../cellSelectionFeature.utils.ts | 1138 +++++++++++++++++ .../rowAggregationFeature.types.ts | 12 +- .../table-core/src/features/stockFeatures.ts | 7 +- packages/table-core/src/index.ts | 4 + packages/table-core/src/static-functions.ts | 1 + packages/table-core/src/types/Cell.ts | 6 +- packages/table-core/src/types/Column.ts | 4 +- packages/table-core/src/types/ColumnDef.ts | 19 +- packages/table-core/src/types/Row.ts | 4 +- packages/table-core/src/types/RowModelFns.ts | 6 +- packages/table-core/src/types/Table.ts | 4 +- packages/table-core/src/types/TableOptions.ts | 12 +- packages/table-core/src/types/TableState.ts | 5 +- .../cellSelectionFeature.test.ts | 736 +++++++++++ .../cell-selection/cellSelectionRange.test.ts | 350 +++++ perf-agg.md | 2 +- pnpm-lock.yaml | 469 +++++++ 279 files changed, 19556 insertions(+), 268 deletions(-) create mode 100644 docs/framework/alpine/guide/cell-selection.md create mode 100644 docs/framework/angular/guide/cell-selection.md create mode 100644 docs/framework/ember/guide/cell-selection.md create mode 100644 docs/framework/lit/guide/cell-selection.md create mode 100644 docs/framework/preact/guide/cell-selection.md create mode 100644 docs/framework/react/guide/cell-selection.md create mode 100644 docs/framework/solid/guide/cell-selection.md create mode 100644 docs/framework/svelte/guide/cell-selection.md create mode 100644 docs/framework/vue/guide/cell-selection.md create mode 100644 docs/reference/index/interfaces/CellSelectionBounds.md create mode 100644 docs/reference/index/interfaces/CellSelectionEdges.md create mode 100644 docs/reference/index/interfaces/CellSelectionRange.md create mode 100644 docs/reference/index/interfaces/Cell_CellSelection.md rename docs/reference/index/interfaces/{Cell_Aggregation.md => Cell_RowAggregation.md} (88%) create mode 100644 docs/reference/index/interfaces/ColumnDef_CellSelection.md rename docs/reference/index/interfaces/{ColumnDef_Aggregation.md => ColumnDef_RowAggregation.md} (95%) rename docs/reference/index/interfaces/{Column_Aggregation.md => Column_RowAggregation.md} (95%) rename docs/reference/index/interfaces/{RowModelFns_Aggregation.md => RowModelFns_RowAggregation.md} (85%) rename docs/reference/index/interfaces/{Row_Aggregation.md => Row_RowAggregation.md} (89%) create mode 100644 docs/reference/index/interfaces/SelectCellRangeOptions.md create mode 100644 docs/reference/index/interfaces/TableOptions_CellSelection.md rename docs/reference/index/interfaces/{TableOptions_Aggregation.md => TableOptions_RowAggregation.md} (87%) create mode 100644 docs/reference/index/interfaces/TableState_CellSelection.md create mode 100644 docs/reference/index/interfaces/Table_CellSelection.md create mode 100644 docs/reference/index/type-aliases/CellSelectionDirection.md create mode 100644 docs/reference/index/type-aliases/CellSelectionState.md create mode 100644 docs/reference/index/variables/cellSelectionFeature.md create mode 100644 docs/reference/static-functions/functions/cell_getCanSelect.md create mode 100644 docs/reference/static-functions/functions/cell_getIsFocused.md create mode 100644 docs/reference/static-functions/functions/cell_getIsSelected.md create mode 100644 docs/reference/static-functions/functions/cell_getSelectionEdges.md create mode 100644 docs/reference/static-functions/functions/cell_getSelectionExtendHandler.md create mode 100644 docs/reference/static-functions/functions/cell_getSelectionStartHandler.md create mode 100644 docs/reference/static-functions/functions/cell_getTabIndex.md create mode 100644 docs/reference/static-functions/functions/getDefaultCellSelectionState.md create mode 100644 docs/reference/static-functions/functions/table_autoResetCellSelection.md create mode 100644 docs/reference/static-functions/functions/table_extendCellSelection.md create mode 100644 docs/reference/static-functions/functions/table_getCellSelectionBounds.md create mode 100644 docs/reference/static-functions/functions/table_getCellSelectionColumnIds.md create mode 100644 docs/reference/static-functions/functions/table_getCellSelectionColumnIndexes.md create mode 100644 docs/reference/static-functions/functions/table_getCellSelectionRowIds.md create mode 100644 docs/reference/static-functions/functions/table_getFocusedCell.md create mode 100644 docs/reference/static-functions/functions/table_getSelectedCellCount.md create mode 100644 docs/reference/static-functions/functions/table_getSelectedCellIds.md create mode 100644 docs/reference/static-functions/functions/table_getSelectedCellRangesData.md create mode 100644 docs/reference/static-functions/functions/table_moveCellSelection.md create mode 100644 docs/reference/static-functions/functions/table_resetCellSelection.md create mode 100644 docs/reference/static-functions/functions/table_selectAllCells.md create mode 100644 docs/reference/static-functions/functions/table_selectCellRange.md create mode 100644 docs/reference/static-functions/functions/table_setCellSelection.md create mode 100644 docs/reference/static-functions/functions/table_setFocusedCell.md create mode 100644 examples/alpine/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/alpine/cell-selection/index.html create mode 100644 examples/alpine/cell-selection/package.json create mode 100644 examples/alpine/cell-selection/src/index.css create mode 100644 examples/alpine/cell-selection/src/main.ts create mode 100644 examples/alpine/cell-selection/src/makeData.ts create mode 100644 examples/alpine/cell-selection/src/vite-env.d.ts create mode 100644 examples/alpine/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/alpine/cell-selection/tsconfig.json create mode 100644 examples/alpine/cell-selection/vite.config.js create mode 100644 examples/angular/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/angular/cell-selection/.gitignore create mode 100644 examples/angular/cell-selection/angular.json create mode 100644 examples/angular/cell-selection/package.json create mode 100644 examples/angular/cell-selection/public/favicon.ico create mode 100644 examples/angular/cell-selection/src/app/app.config.ts create mode 100644 examples/angular/cell-selection/src/app/app.html create mode 100644 examples/angular/cell-selection/src/app/app.ts create mode 100644 examples/angular/cell-selection/src/app/makeData.ts create mode 100644 examples/angular/cell-selection/src/app/table.ts create mode 100644 examples/angular/cell-selection/src/index.html create mode 100644 examples/angular/cell-selection/src/main.ts create mode 100644 examples/angular/cell-selection/src/styles.css create mode 100644 examples/angular/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/angular/cell-selection/tsconfig.app.json create mode 100644 examples/angular/cell-selection/tsconfig.json create mode 100644 examples/ember/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/ember/cell-selection/app/app.css create mode 100644 examples/ember/cell-selection/app/app.ts create mode 100644 examples/ember/cell-selection/app/config.ts create mode 100644 examples/ember/cell-selection/app/router.ts create mode 100644 examples/ember/cell-selection/app/templates/application.gts create mode 100644 examples/ember/cell-selection/app/utils/make-data.ts create mode 100644 examples/ember/cell-selection/babel.config.js create mode 100644 examples/ember/cell-selection/index.html create mode 100644 examples/ember/cell-selection/package.json create mode 100644 examples/ember/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/ember/cell-selection/tsconfig.json create mode 100644 examples/ember/cell-selection/vite.config.mjs create mode 100644 examples/lit/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/lit/cell-selection/.gitignore create mode 100644 examples/lit/cell-selection/index.html create mode 100644 examples/lit/cell-selection/package.json create mode 100644 examples/lit/cell-selection/src/main.ts create mode 100644 examples/lit/cell-selection/src/makeData.ts create mode 100644 examples/lit/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/lit/cell-selection/tsconfig.json create mode 100644 examples/lit/cell-selection/vite.config.js create mode 100644 examples/preact/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/preact/cell-selection/index.html create mode 100644 examples/preact/cell-selection/package.json create mode 100644 examples/preact/cell-selection/src/index.css create mode 100644 examples/preact/cell-selection/src/main.tsx create mode 100644 examples/preact/cell-selection/src/makeData.ts create mode 100644 examples/preact/cell-selection/src/vite-env.d.ts create mode 100644 examples/preact/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/preact/cell-selection/tsconfig.json create mode 100644 examples/preact/cell-selection/vite.config.ts create mode 100644 examples/react/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/react/cell-selection/.gitignore create mode 100644 examples/react/cell-selection/index.html create mode 100644 examples/react/cell-selection/package.json create mode 100644 examples/react/cell-selection/src/index.css create mode 100644 examples/react/cell-selection/src/main.tsx create mode 100644 examples/react/cell-selection/src/makeData.ts create mode 100644 examples/react/cell-selection/src/vite-env.d.ts create mode 100644 examples/react/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/react/cell-selection/tsconfig.json create mode 100644 examples/react/cell-selection/vite.config.js create mode 100644 examples/solid/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/solid/cell-selection/index.html create mode 100644 examples/solid/cell-selection/package.json create mode 100644 examples/solid/cell-selection/src/App.tsx create mode 100644 examples/solid/cell-selection/src/index.css create mode 100644 examples/solid/cell-selection/src/index.tsx create mode 100644 examples/solid/cell-selection/src/makeData.ts create mode 100644 examples/solid/cell-selection/src/vite-env.d.ts create mode 100644 examples/solid/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/solid/cell-selection/tsconfig.json create mode 100644 examples/solid/cell-selection/vite.config.ts create mode 100644 examples/svelte/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/svelte/cell-selection/index.html create mode 100644 examples/svelte/cell-selection/package.json create mode 100644 examples/svelte/cell-selection/src/App.svelte create mode 100644 examples/svelte/cell-selection/src/index.css create mode 100644 examples/svelte/cell-selection/src/main.ts create mode 100644 examples/svelte/cell-selection/src/makeData.ts create mode 100644 examples/svelte/cell-selection/src/vite-env.d.ts create mode 100644 examples/svelte/cell-selection/svelte.config.js create mode 100644 examples/svelte/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/svelte/cell-selection/tsconfig.json create mode 100644 examples/svelte/cell-selection/vite.config.js create mode 100644 examples/vue/cell-selection/.devcontainer/devcontainer.json create mode 100644 examples/vue/cell-selection/.gitignore create mode 100644 examples/vue/cell-selection/env.d.ts create mode 100644 examples/vue/cell-selection/index.html create mode 100644 examples/vue/cell-selection/package.json create mode 100644 examples/vue/cell-selection/public/favicon.ico create mode 100644 examples/vue/cell-selection/src/App.vue create mode 100644 examples/vue/cell-selection/src/env.d.ts create mode 100644 examples/vue/cell-selection/src/index.css create mode 100644 examples/vue/cell-selection/src/main.ts create mode 100644 examples/vue/cell-selection/src/makeData.ts create mode 100644 examples/vue/cell-selection/tests/e2e/smoke.spec.ts create mode 100644 examples/vue/cell-selection/tsconfig.json create mode 100644 examples/vue/cell-selection/vite.config.ts create mode 100644 packages/table-core/skills/cell-selection/SKILL.md create mode 100644 packages/table-core/src/features/cell-selection/cellSelectionFeature.ts create mode 100644 packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts create mode 100644 packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts create mode 100644 packages/table-core/tests/implementation/features/cell-selection/cellSelectionFeature.test.ts create mode 100644 packages/table-core/tests/implementation/features/cell-selection/cellSelectionRange.test.ts diff --git a/_artifacts/domain_map.yaml b/_artifacts/domain_map.yaml index 491290ffc9..4c237fe0b7 100644 --- a/_artifacts/domain_map.yaml +++ b/_artifacts/domain_map.yaml @@ -528,6 +528,23 @@ skills: - 'Expecting row pinning to add sticky CSS or render top/center/bottom collections in the correct order automatically.' - 'Ignoring keepPinnedRows, so pinned rows remain visible outside the center row model when the desired product behavior was to filter or paginate them away.' + - slug: cell-selection + 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.' + sources: + [ + 'TanStack/table:docs/framework/react/guide/cell-selection.md', + 'TanStack/table:packages/table-core/src/features/cell-selection', + '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.' + - '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.' + - slug: row-selection package: '@tanstack/table-core' domain: feature-plugins diff --git a/_artifacts/skill_tree.yaml b/_artifacts/skill_tree.yaml index fee7c0075f..cce7a441b5 100644 --- a/_artifacts/skill_tree.yaml +++ b/_artifacts/skill_tree.yaml @@ -291,6 +291,19 @@ skills: - 'TanStack/table:packages/table-core/src/features/row-pinning' - 'TanStack/table:examples/react/row-pinning' + - name: 'Cell Selection' + slug: cell-selection + type: sub-skill + 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.' + requires: ['core', 'table-features'] + sources: + - 'TanStack/table:docs/framework/react/guide/cell-selection.md' + - 'TanStack/table:packages/table-core/src/features/cell-selection' + - 'TanStack/table:examples/react/cell-selection' + - name: 'Row Selection' slug: row-selection type: sub-skill diff --git a/docs/config.json b/docs/config.json index c447345253..3434e6e53b 100644 --- a/docs/config.json +++ b/docs/config.json @@ -215,6 +215,7 @@ { "label": "Expanding", "to": "framework/alpine/guide/expanding" }, { "label": "Pagination", "to": "framework/alpine/guide/pagination" }, { "label": "Row Pinning", "to": "framework/alpine/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/alpine/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/alpine/guide/row-selection" }, { "label": "Sorting", "to": "framework/alpine/guide/sorting" } ] @@ -236,6 +237,7 @@ { "label": "Expanding", "to": "framework/angular/guide/expanding" }, { "label": "Pagination", "to": "framework/angular/guide/pagination" }, { "label": "Row Pinning", "to": "framework/angular/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/angular/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/angular/guide/row-selection" }, { "label": "Sorting", "to": "framework/angular/guide/sorting" }, { "label": "Virtualization", "to": "framework/angular/guide/virtualization" } @@ -258,6 +260,7 @@ { "label": "Expanding", "to": "framework/ember/guide/expanding" }, { "label": "Pagination", "to": "framework/ember/guide/pagination" }, { "label": "Row Pinning", "to": "framework/ember/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/ember/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/ember/guide/row-selection" }, { "label": "Sorting", "to": "framework/ember/guide/sorting" } ] @@ -279,6 +282,7 @@ { "label": "Expanding", "to": "framework/lit/guide/expanding" }, { "label": "Pagination", "to": "framework/lit/guide/pagination" }, { "label": "Row Pinning", "to": "framework/lit/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/lit/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/lit/guide/row-selection" }, { "label": "Sorting", "to": "framework/lit/guide/sorting" }, { "label": "Virtualization", "to": "framework/lit/guide/virtualization" } @@ -301,6 +305,7 @@ { "label": "Expanding", "to": "framework/react/guide/expanding" }, { "label": "Pagination", "to": "framework/react/guide/pagination" }, { "label": "Row Pinning", "to": "framework/react/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/react/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/react/guide/row-selection" }, { "label": "Sorting", "to": "framework/react/guide/sorting" }, { "label": "Virtualization", "to": "framework/react/guide/virtualization" } @@ -323,6 +328,7 @@ { "label": "Expanding", "to": "framework/preact/guide/expanding" }, { "label": "Pagination", "to": "framework/preact/guide/pagination" }, { "label": "Row Pinning", "to": "framework/preact/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/preact/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/preact/guide/row-selection" }, { "label": "Sorting", "to": "framework/preact/guide/sorting" }, { "label": "Virtualization", "to": "framework/preact/guide/virtualization" } @@ -345,6 +351,7 @@ { "label": "Expanding", "to": "framework/solid/guide/expanding" }, { "label": "Pagination", "to": "framework/solid/guide/pagination" }, { "label": "Row Pinning", "to": "framework/solid/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/solid/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/solid/guide/row-selection" }, { "label": "Sorting", "to": "framework/solid/guide/sorting" }, { "label": "Virtualization", "to": "framework/solid/guide/virtualization" } @@ -367,6 +374,7 @@ { "label": "Expanding", "to": "framework/svelte/guide/expanding" }, { "label": "Pagination", "to": "framework/svelte/guide/pagination" }, { "label": "Row Pinning", "to": "framework/svelte/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/svelte/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/svelte/guide/row-selection" }, { "label": "Sorting", "to": "framework/svelte/guide/sorting" }, { "label": "Virtualization", "to": "framework/svelte/guide/virtualization" } @@ -389,6 +397,7 @@ { "label": "Expanding", "to": "framework/vue/guide/expanding" }, { "label": "Pagination", "to": "framework/vue/guide/pagination" }, { "label": "Row Pinning", "to": "framework/vue/guide/row-pinning" }, + { "label": "Cell Selection", "to": "framework/vue/guide/cell-selection" }, { "label": "Row Selection", "to": "framework/vue/guide/row-selection" }, { "label": "Sorting", "to": "framework/vue/guide/sorting" }, { "label": "Virtualization", "to": "framework/vue/guide/virtualization" } @@ -1079,6 +1088,7 @@ { "to": "framework/alpine/examples/pagination", "label": "Pagination" }, { "to": "framework/alpine/examples/row-pinning", "label": "Row Pinning" }, { "to": "framework/alpine/examples/row-selection", "label": "Row Selection" }, + { "label": "Cell Selection", "to": "framework/alpine/examples/cell-selection" }, { "to": "framework/alpine/examples/sorting", "label": "Sorting" }, { "to": "framework/alpine/examples/sorting-dynamic-data", "label": "Sorting (Dynamic Data)" } ] @@ -1107,6 +1117,7 @@ { "label": "Pagination", "to": "framework/angular/examples/pagination" }, { "label": "Row Pinning", "to": "framework/angular/examples/row-pinning" }, { "label": "Row Selection", "to": "framework/angular/examples/row-selection" }, + { "label": "Cell Selection", "to": "framework/angular/examples/cell-selection" }, { "label": "Sorting", "to": "framework/angular/examples/sorting" } ] }, @@ -1134,6 +1145,7 @@ { "label": "Pagination", "to": "framework/ember/examples/pagination" }, { "label": "Row Pinning", "to": "framework/ember/examples/row-pinning" }, { "label": "Row Selection", "to": "framework/ember/examples/row-selection" }, + { "label": "Cell Selection", "to": "framework/ember/examples/cell-selection" }, { "label": "Sorting", "to": "framework/ember/examples/sorting" } ] }, @@ -1161,6 +1173,7 @@ { "label": "Pagination", "to": "framework/lit/examples/pagination" }, { "label": "Row Pinning", "to": "framework/lit/examples/row-pinning" }, { "label": "Row Selection", "to": "framework/lit/examples/row-selection" }, + { "label": "Cell Selection", "to": "framework/lit/examples/cell-selection" }, { "label": "Sorting", "to": "framework/lit/examples/sorting" }, { "label": "Sorting (Dynamic Data)", "to": "framework/lit/examples/sorting-dynamic-data" } ] @@ -1191,6 +1204,7 @@ { "label": "Row DnD", "to": "framework/react/examples/row-dnd" }, { "label": "Row Pinning", "to": "framework/react/examples/row-pinning" }, { "label": "Row Selection", "to": "framework/react/examples/row-selection" }, + { "label": "Cell Selection", "to": "framework/react/examples/cell-selection" }, { "label": "Sorting", "to": "framework/react/examples/sorting" } ] }, @@ -1218,6 +1232,7 @@ { "label": "Pagination", "to": "framework/solid/examples/pagination" }, { "label": "Row Pinning", "to": "framework/solid/examples/row-pinning" }, { "label": "Row Selection", "to": "framework/solid/examples/row-selection" }, + { "label": "Cell Selection", "to": "framework/solid/examples/cell-selection" }, { "label": "Sorting", "to": "framework/solid/examples/sorting" } ] }, @@ -1245,6 +1260,7 @@ { "label": "Pagination", "to": "framework/svelte/examples/pagination" }, { "label": "Row Pinning", "to": "framework/svelte/examples/row-pinning" }, { "label": "Row Selection", "to": "framework/svelte/examples/row-selection" }, + { "label": "Cell Selection", "to": "framework/svelte/examples/cell-selection" }, { "label": "Sorting", "to": "framework/svelte/examples/sorting" } ] }, @@ -1272,6 +1288,7 @@ { "label": "Pagination", "to": "framework/vue/examples/pagination" }, { "label": "Row Pinning", "to": "framework/vue/examples/row-pinning" }, { "label": "Row Selection", "to": "framework/vue/examples/row-selection" }, + { "label": "Cell Selection", "to": "framework/vue/examples/cell-selection" }, { "label": "Sorting", "to": "framework/vue/examples/sorting" } ] }, @@ -1299,6 +1316,7 @@ { "label": "Pagination", "to": "framework/preact/examples/pagination" }, { "label": "Row Pinning", "to": "framework/preact/examples/row-pinning" }, { "label": "Row Selection", "to": "framework/preact/examples/row-selection" }, + { "label": "Cell Selection", "to": "framework/preact/examples/cell-selection" }, { "label": "Sorting", "to": "framework/preact/examples/sorting" } ] }, diff --git a/docs/framework/alpine/guide/cell-selection.md b/docs/framework/alpine/guide/cell-selection.md new file mode 100644 index 0000000000..f0dd6c8c48 --- /dev/null +++ b/docs/framework/alpine/guide/cell-selection.md @@ -0,0 +1,367 @@ +--- +title: Cell Selection (Alpine) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these Alpine examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```ts +import Alpine from 'alpinejs' +import { + createTable, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/alpine-table' + +const features = tableFeatures({ cellSelectionFeature }) + +Alpine.data('table', () => { + const local = Alpine.reactive({ data: defaultData }) + + const table = createTable( + { + features, + columns, + get data() { + return local.data + }, + }, + // the selector decides which slices bump Alpine's version counter; + // cellSelection has to be here or the highlight never moves + (state) => ({ cellSelection: state.cellSelection }), + ) + + return { 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.atoms.cellSelection.get()` - returns the current cell selection. Alpine re-renders from a version counter, so include `cellSelection` in the `createTable` selector for the DOM to update +- `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 + +```ts +console.log(table.atoms.cellSelection.get()) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +Reads of `table.atoms.cellSelection.get()` are tracked inside Alpine reactive contexts, so they stay fresh automatically. Outside one, the same call is a plain snapshot. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```ts +import { createAtom } from '@tanstack/store' +import { + createTable, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/alpine-table' + +const features = tableFeatures({ cellSelectionFeature }) +const cellSelectionAtom = createAtom([]) + +const table = createTable( + { + features, + columns, + get data() { + return local.data + }, + atoms: { cellSelection: cellSelectionAtom }, + }, + (state) => ({ cellSelection: state.cellSelection }), +) +``` + +The classic controlled-state pattern also works: + +```ts +const local = Alpine.reactive({ data, cellSelection: [] as CellSelectionState }) + +const table = createTable( + { + features, + columns, + get data() { + return local.data + }, + get state() { + return { cellSelection: local.cellSelection } + }, + onCellSelectionChange: (updater) => { + local.cellSelection = + typeof updater === 'function' ? updater(local.cellSelection) : updater + }, + }, + (state) => ({ cellSelection: state.cellSelection }), +) +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +const table = createTable({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +}) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +const table = createTable({ + features, + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +}) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```html + + + +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +const table = createTable({ + features, + //... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), +}) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```ts +import { createMultiHotkeyHandler } from '@tanstack/hotkeys' + +// Alpine has no hotkeys adapter, so the framework-agnostic core handler is used +onGridKeyDown: createMultiHotkeyHandler({ + ArrowUp: () => table.moveCellSelection('up'), + ArrowDown: () => table.moveCellSelection('down'), + 'Shift+ArrowDown': () => table.extendCellSelection('down'), + 'Mod+A': () => table.selectAllCells(), + Escape: () => table.resetCellSelection(true), +}), + +// then, in the markup: +//
...
+``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```ts +// compare a layout key, then reset when it changes +const layoutKey = JSON.stringify([ + table.atoms.columnOrder.get(), + table.atoms.columnPinning.get(), + table.atoms.columnVisibility.get(), +]) + +if (layoutKey !== lastLayoutKey) { + lastLayoutKey = layoutKey + queueMicrotask(() => table.resetCellSelection(true)) +} +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +const table = createTable({ + features, + //... + autoResetCellSelection: false, //keep ranges across data changes +}) +``` + +### Performance + +Alpine re-renders from a version counter rather than tracking individual +reads, so every selection change re-evaluates the `x-for` that renders the rows. +That makes cell selection the feature most likely to make a large Alpine table +feel slow. + +Measured on a table with a thousand rows and twelve columns, a drag updates in +roughly 160ms per move. The same table is around 12-19ms per move on the +signal-based adapters. Almost all of that difference is Alpine re-rendering rows, +not the selection reads themselves: `cell.getIsSelected()` resolves the cell's +row and column index and compares them against a memoized cache of the selection +bounds, which is a handful of integer comparisons. + +There is no fine-grained subscription primitive to reach for here, so the +practical fix is to render fewer rows. Paginate the table, or keep the rendered +row count small, and cell selection stays responsive. Ranges are stored as row +and column ids resolved against the pre-pagination order, so a selection can +still span pages. diff --git a/docs/framework/angular/guide/cell-selection.md b/docs/framework/angular/guide/cell-selection.md new file mode 100644 index 0000000000..415b461c68 --- /dev/null +++ b/docs/framework/angular/guide/cell-selection.md @@ -0,0 +1,365 @@ +--- +title: Cell Selection (Angular) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these Angular examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```ts +import { signal } from '@angular/core' +import { + injectTable, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/angular-table' + +const features = tableFeatures({ cellSelectionFeature }) + +export class App { + readonly data = signal(defaultData) + readonly table = injectTable(() => ({ + features, + columns, + data: this.data(), + })) +} +``` + +## 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.atoms.cellSelection.get()` - returns the current cell selection (a signal read, so it tracks automatically in templates, `computed(...)`, and `effect(...)`) +- `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 + +```ts +console.log(table.atoms.cellSelection.get()) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +Reads of `table.atoms.cellSelection.get()` are tracked inside Angular reactive contexts, so they stay fresh automatically. Outside one, the same call is a plain snapshot. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```ts +import { createAtom } from '@tanstack/angular-store' +import { + injectTable, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/angular-table' + +const features = tableFeatures({ cellSelectionFeature }) + +export class App { + readonly cellSelectionAtom = createAtom([]) + readonly table = injectTable(() => ({ + features, + columns, + data: this.data(), + atoms: { cellSelection: this.cellSelectionAtom }, + })) +} +``` + +The classic controlled-state pattern also works: + +```ts +export class App { + readonly cellSelection = signal([]) + readonly table = injectTable(() => ({ + features, + columns, + data: this.data(), + state: { cellSelection: this.cellSelection() }, + onCellSelectionChange: (updater) => { + this.cellSelection.update((old) => + typeof updater === 'function' ? updater(old) : updater, + ) + }, + })) +} +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +readonly table = injectTable(() => ({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +})) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +readonly table = injectTable(() => ({ + features, + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +})) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```html + + + {{ renderCell }} + + +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +readonly table = injectTable(() => ({ + features, + //... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), +})) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```ts +import { injectHotkeys } from '@tanstack/angular-hotkeys' + +export class App { + readonly grid = viewChild>('grid') + + constructor() { + injectHotkeys( + [ + { + hotkey: 'ArrowUp', + callback: () => this.table.moveCellSelection('up'), + }, + { + hotkey: 'ArrowDown', + callback: () => this.table.moveCellSelection('down'), + }, + { + hotkey: 'Shift+ArrowDown', + callback: () => this.table.extendCellSelection('down'), + }, + { hotkey: 'Mod+A', callback: () => this.table.selectAllCells() }, + { + hotkey: 'Escape', + callback: () => this.table.resetCellSelection(true), + }, + ], + () => ({ target: this.grid()?.nativeElement ?? null }), + ) + } +} +``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```ts +effect(() => { + // read the atoms so this tracks only the layout slices + this.table.atoms.columnOrder.get() + this.table.atoms.columnPinning.get() + this.table.atoms.columnVisibility.get() + + untracked(() => this.table.resetCellSelection(true)) +}) +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +readonly table = injectTable(() => ({ + features, + //... + autoResetCellSelection: false, //keep ranges across data changes +})) +``` + +### Performance + +Angular's signals track these reads, so a selection change marks only the +components that actually read it. There is no equivalent of React's per-row +`Subscribe` to reach for here, and the example renders its cells plainly. + +Measured on a table with a thousand rows and twelve columns, a drag updates in +roughly 15ms per move with plain reads and `ChangeDetectionStrategy.OnPush`. + +The per-cell reads are cheap by design. `cell.getIsSelected()` resolves the +cell's row and column index and compares them against a memoized cache of the +selection bounds, which is a handful of integer comparisons. If a very large +table does become a bottleneck, reach for +[virtualization](./virtualization) so that only visible rows exist in the DOM, +rather than for a subscription pattern. + +Note that Angular flushes change detection asynchronously, so a test that reads +the DOM synchronously after a click can beat the update. Poll the assertion +rather than reading once. diff --git a/docs/framework/angular/guide/migrating.md b/docs/framework/angular/guide/migrating.md index 1f5f0ad388..3c19301c04 100644 --- a/docs/framework/angular/guide/migrating.md +++ b/docs/framework/angular/guide/migrating.md @@ -50,8 +50,9 @@ TanStack Table V9 is a major release with significant internal architectural imp - **`tableOptions`**: Compose reusable table configuration, including features, row models, and default options. - **`createTableHook`**: Create reusable, strongly typed Angular table factories with pre-bound features, row models, default options, and component registries. -### 6. Refreshed Feature APIs +### 6. New and Refreshed Features +- **New features**: `cellSelectionFeature` adds spreadsheet-style rectangular cell range selection, with drag, Shift-extend, and multiple disjoint ranges. See the [Cell Selection Guide](./cell-selection.md). - **More capable features**: Aggregation, Row Selection, Column Pinning, and Column Resizing have all been made more feature rich (multiple aggregation definitions per column, Shift range selection, logical `start`/`end` pinning, and more). - **New core APIs**: New table and row APIs (like `table.getMaxSubRowDepth()`, `row.getDisplayIndex()`) round out the core feature set. diff --git a/docs/framework/ember/guide/cell-selection.md b/docs/framework/ember/guide/cell-selection.md new file mode 100644 index 0000000000..6e42134e34 --- /dev/null +++ b/docs/framework/ember/guide/cell-selection.md @@ -0,0 +1,342 @@ +--- +title: Cell Selection (Ember) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these Ember examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```ts +import { + useTable, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/ember-table' + +const features = tableFeatures({ cellSelectionFeature }) + +export default class MyTable extends Component { + @tracked data = defaultData + + table = useTable(() => ({ + features, + columns, + data: this.data, + })) +} +``` + +## 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.atoms.cellSelection.get()` - returns the current cell selection (a tracked read, so it invalidates templates and getters automatically) +- `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 + +```ts +console.log(table.atoms.cellSelection.get()) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +Reads of `table.atoms.cellSelection.get()` are tracked inside Ember reactive contexts, so they stay fresh automatically. Outside one, the same call is a plain snapshot. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```ts +import { createAtom } from '@tanstack/ember-table' +import { + useTable, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/ember-table' + +const features = tableFeatures({ cellSelectionFeature }) +const cellSelectionAtom = createAtom([]) + +table = useTable(() => ({ + features, + columns, + data: this.data, + atoms: { cellSelection: cellSelectionAtom }, +})) +``` + +The classic controlled-state pattern also works: + +```ts +@tracked cellSelection: CellSelectionState = [] + +table = useTable(() => ({ + features, + columns, + data: this.data, + state: { cellSelection: this.cellSelection }, + onCellSelectionChange: (updater) => { + this.cellSelection = + typeof updater === 'function' ? updater(this.cellSelection) : updater + }, +})) +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +table = useTable(() => ({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +})) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +table = useTable(() => ({ + features, + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +})) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```gts +{{! Ember templates extract function references without binding, so each + handler goes through a helper that calls it on the right cell }} + + + +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +table = useTable(() => ({ + features, + //... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), +})) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```ts +import { createMultiHotkeyHandler } from '@tanstack/hotkeys' + +// Ember has no hotkeys adapter, so the framework-agnostic core handler is used +onGridKeyDown = createMultiHotkeyHandler({ + ArrowUp: () => this.table.moveCellSelection('up'), + ArrowDown: () => this.table.moveCellSelection('down'), + 'Shift+ArrowDown': () => this.table.extendCellSelection('down'), + 'Mod+A': () => this.table.selectAllCells(), + Escape: () => this.table.resetCellSelection(true), +}) + +// then, in the template: +//
...
+``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```ts +// compare a layout key, then reset when it changes +const layoutKey = JSON.stringify([ + this.table.atoms.columnOrder.get(), + this.table.atoms.columnPinning.get(), + this.table.atoms.columnVisibility.get(), +]) + +if (layoutKey !== this.lastLayoutKey) { + this.lastLayoutKey = layoutKey + queueMicrotask(() => this.table.resetCellSelection(true)) +} +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +table = useTable(() => ({ + features, + //... + autoResetCellSelection: false, //keep ranges across data changes +})) +``` + +### Performance + +Ember's tracked signals invalidate only the getters and template sections that +actually read the selection, so the example renders its cells plainly. There is +no equivalent of React's per-row `Subscribe` to reach for here. + +Measured on a table with a thousand rows and twelve columns, a drag updates in +roughly 19ms per move with plain reads. + +The per-cell reads are cheap by design. `cell.getIsSelected()` resolves the +cell's row and column index and compares them against a memoized cache of the +selection bounds, which is a handful of integer comparisons. If a very large +table does become a bottleneck, paginate or virtualize so fewer rows exist in the +DOM, rather than reaching for a subscription pattern. diff --git a/docs/framework/lit/guide/cell-selection.md b/docs/framework/lit/guide/cell-selection.md new file mode 100644 index 0000000000..aac82e8f72 --- /dev/null +++ b/docs/framework/lit/guide/cell-selection.md @@ -0,0 +1,350 @@ +--- +title: Cell Selection (Lit) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these Lit examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```ts +import { LitElement, html } from 'lit' +import { + TableController, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/lit-table' + +const features = tableFeatures({ cellSelectionFeature }) + +class MyTable extends LitElement { + private tableController = new TableController(this) + + render() { + const table = this.tableController.table({ + features, + columns, + data: this._data, + }) + // ... + } +} +``` + +## 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.state.cellSelection` - returns the cell selection state reactively (selected by the second argument to `tableController.table(...)`) +- `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 + +```ts +console.log(table.state.cellSelection) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +In event handlers or other non-render code, you can also read the current snapshot with `table.atoms.cellSelection.get()`. This read does not subscribe a component to future changes, so prefer `table.state.cellSelection` in render positions. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```ts +import { createAtom } from '@tanstack/lit-store' +import { + TableController, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/lit-table' + +const features = tableFeatures({ cellSelectionFeature }) +const cellSelectionAtom = createAtom([]) + +const table = this.tableController.table({ + features, + columns, + data: this._data, + atoms: { cellSelection: cellSelectionAtom }, +}) +``` + +The classic controlled-state pattern also works: + +```ts +const table = this.tableController.table({ + features, + columns, + data: this._data, + state: { cellSelection: this._cellSelection }, + onCellSelectionChange: (updater) => { + this._cellSelection = + typeof updater === 'function' ? updater(this._cellSelection) : updater + }, +}) +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +const table = this.tableController.table({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +}) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +const table = this.tableController.table({ + features, + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +}) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```ts +html` + ${FlexRender({ cell })} +` +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +const table = this.tableController.table({ + features, + //... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), +}) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```ts +import { createMultiHotkeyHandler } from '@tanstack/lit-hotkeys' + +// one stateless handler is simpler here than a HotkeyController per binding +const onGridKeyDown = createMultiHotkeyHandler({ + ArrowUp: () => table.moveCellSelection('up'), + ArrowDown: () => table.moveCellSelection('down'), + 'Shift+ArrowDown': () => table.extendCellSelection('down'), + 'Mod+A': () => table.selectAllCells(), + Escape: () => table.resetCellSelection(true), +}) + +// then, in the template: +// html`
...
` +``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```ts +// compare a layout key across renders, then reset when it changes +const layoutKey = JSON.stringify([ + table.atoms.columnOrder.get(), + table.atoms.columnPinning.get(), + table.atoms.columnVisibility.get(), +]) + +if (layoutKey !== this._lastLayoutKey) { + this._lastLayoutKey = layoutKey + queueMicrotask(() => table.resetCellSelection(true)) +} +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +const table = this.tableController.table({ + features, + //... + autoResetCellSelection: false, //keep ranges across data changes +}) +``` + +### Performance + +The table controller requests a re-render on selection changes and lit-html +patches only the bindings that actually changed, so the example renders its cells +plainly. + +Measured on a table with a thousand rows and twelve columns, a drag updates in +roughly 13ms per move. + +The `subscribe` directive is available if you want an explicit fine-grained +subscription for one part of a template: + +```ts +subscribe( + table.atoms.cellSelection, + (ranges) => ranges.length, + (count) => html`${count} ranges`, +) +``` + +Note that Lit renders into shadow DOM, so the selection classes must be defined +in the component's `static styles` rather than a global stylesheet. diff --git a/docs/framework/lit/guide/migrating.md b/docs/framework/lit/guide/migrating.md index 07cd4e8da1..bba85c17b5 100644 --- a/docs/framework/lit/guide/migrating.md +++ b/docs/framework/lit/guide/migrating.md @@ -46,8 +46,9 @@ TanStack Table V9 is a major release with significant internal architectural imp - **`tableOptions()`**: Compose reusable table configuration, including features, row models, and default options. - **`createTableHook()`**: Define shared Lit table factories with pre-bound features, row models, defaults, and render helpers. -### 6. Refreshed Feature APIs +### 6. New and Refreshed Features +- **New features**: `cellSelectionFeature` adds spreadsheet-style rectangular cell range selection, with drag, Shift-extend, and multiple disjoint ranges. See the [Cell Selection Guide](./cell-selection.md). - **More capable features**: Aggregation, Row Selection, Column Pinning, and Column Resizing have all been made more feature rich (multiple aggregation definitions per column, Shift range selection, logical `start`/`end` pinning, and more). - **New core APIs**: New table and row APIs (like `table.getMaxSubRowDepth()`, `row.getDisplayIndex()`) round out the core feature set. diff --git a/docs/framework/preact/guide/cell-selection.md b/docs/framework/preact/guide/cell-selection.md new file mode 100644 index 0000000000..a1f1cdde5c --- /dev/null +++ b/docs/framework/preact/guide/cell-selection.md @@ -0,0 +1,369 @@ +--- +title: Cell Selection (Preact) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these Preact examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```ts +import { + useTable, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/preact-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const table = useTable({ + features, + columns, + data, +}) +``` + +## 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.state.cellSelection` - returns the cell selection state reactively (selected by the `useTable` selector) +- `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 + +```ts +console.log(table.state.cellSelection) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +In event handlers or other non-render code, you can also read the current snapshot with `table.atoms.cellSelection.get()`. This read does not subscribe a component to future changes, so prefer `table.state.cellSelection` in render positions. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```ts +import { useCreateAtom } from '@tanstack/preact-store' +import { + useTable, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/preact-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const cellSelectionAtom = useCreateAtom([]) + +const table = useTable({ + features, + columns, + data, + atoms: { cellSelection: cellSelectionAtom }, +}) +``` + +The classic controlled-state pattern also works: + +```ts +const [cellSelection, setCellSelection] = useState([]) + +const table = useTable({ + features, + columns, + data, + state: { cellSelection }, + onCellSelectionChange: setCellSelection, +}) +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +const table = useTable({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +}) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +const table = useTable({ + features, + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +}) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```tsx + + + +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +const table = useTable({ + features, + //... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), +}) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```ts +import { useHotkeys } from '@tanstack/preact-hotkeys' + +const gridRef = useRef(null) + +useHotkeys( + [ + { hotkey: 'ArrowUp', callback: () => table.moveCellSelection('up') }, + { hotkey: 'ArrowDown', callback: () => table.moveCellSelection('down') }, + { + hotkey: 'Shift+ArrowDown', + callback: () => table.extendCellSelection('down'), + }, + { hotkey: 'Mod+A', callback: () => table.selectAllCells() }, + { hotkey: 'Escape', callback: () => table.resetCellSelection(true) }, + ], + { target: gridRef }, +) +``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```ts +useEffect(() => { + table.resetCellSelection(true) +}, [ + table.state.columnOrder, + table.state.columnPinning, + table.state.columnVisibility, +]) +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +const table = useTable({ + features, + //... + autoResetCellSelection: false, //keep ranges across data changes +}) +``` + +### Performance with table.Subscribe + +Preact re-renders a component subtree when its state changes, so a drag that +updates on every cell boundary can reconcile the whole table body. `table.Subscribe` +is the tool for this, and where you put it is what matters. + +Put a subscription on each row with a selector that returns only what changes +that row's appearance. `Subscribe` uses `useSelector` under the hood, so a row +whose selected value is unchanged does not re-render: + +```tsx + + {table.getRowModel().rows.map((row) => ( + + rowSelectionKey( + ranges, + table.getCellSelectionBounds(), + row.getDisplayIndex(), + row.id, + ) + } + > + {() => ( + + {row.getVisibleCells().map((cell) => ( + + + + ))} + + )} + + ))} + +``` + +The selector needs to encode whether this row's cells fall inside a range, +whether the rows immediately above and below do (that decides its top and bottom +edges), and whether it owns the focused cell. +`table.getCellSelectionBounds()` is memoized, so it computes once per selection +change no matter how many rows call it. On a thousand-row table this keeps a drag +around 10ms per update instead of reconciling every cell. + +Leave column layout out of the selector: pinning, reordering, and hiding columns +are usually part of your `useTable` selector already, so the parent re-renders +and recreates these rows anyway. diff --git a/docs/framework/preact/guide/migrating.md b/docs/framework/preact/guide/migrating.md index 56699d0b80..d41d8ba7b2 100644 --- a/docs/framework/preact/guide/migrating.md +++ b/docs/framework/preact/guide/migrating.md @@ -50,8 +50,9 @@ TanStack Table V9 is a major release with significant internal architectural imp - **`tableOptions()`**: Compose reusable table configuration, including features, row models, and default options. - **`createTableHook()`**: Create app-level Preact table factories with shared features, row models, defaults, and component conventions. -### 6. Refreshed Feature APIs +### 6. New and Refreshed Features +- **New features**: `cellSelectionFeature` adds spreadsheet-style rectangular cell range selection, with drag, Shift-extend, and multiple disjoint ranges. See the [Cell Selection Guide](./cell-selection.md). - **More capable features**: Aggregation, Row Selection, Column Pinning, and Column Resizing have all been made more feature rich (multiple aggregation definitions per column, Shift range selection, logical `start`/`end` pinning, and more). - **New core APIs**: New table and row APIs (like `table.getMaxSubRowDepth()`, `row.getDisplayIndex()`) round out the core feature set. diff --git a/docs/framework/react/guide/cell-selection.md b/docs/framework/react/guide/cell-selection.md new file mode 100644 index 0000000000..393b03cd7e --- /dev/null +++ b/docs/framework/react/guide/cell-selection.md @@ -0,0 +1,409 @@ +--- +title: Cell Selection (React) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these React examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```tsx +import { + useTable, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/react-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const table = useTable({ + features, + columns, + data, +}) +``` + +## 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.state.cellSelection` - returns the cell selection state reactively (selected by the `useTable` selector) +- `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 + +```ts +console.log(table.state.cellSelection) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +In event handlers or other non-render code, you can also read the current snapshot with `table.atoms.cellSelection.get()`. This read does not subscribe a component to future changes, so prefer `table.state.cellSelection` (or `table.Subscribe`) in render positions. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```tsx +import { useCreateAtom } from '@tanstack/react-store' +import { + useTable, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/react-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const cellSelectionAtom = useCreateAtom([]) + +const table = useTable({ + features, + columns, + data, + atoms: { cellSelection: cellSelectionAtom }, +}) +``` + +The classic controlled-state pattern also works: + +```tsx +const [cellSelection, setCellSelection] = useState([]) + +const table = useTable({ + features, + columns, + data, + state: { cellSelection }, + onCellSelectionChange: setCellSelection, +}) +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +const table = useTable({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +}) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +const table = useTable({ + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +}) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```tsx + + + +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +const table = useTable({ + // ... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => + // Boolean((event as React.MouseEvent).metaKey), +}) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```tsx +import { useHotkeys } from '@tanstack/react-hotkeys' + +const gridRef = useRef(null) + +useHotkeys( + [ + { hotkey: 'ArrowUp', callback: () => table.moveCellSelection('up') }, + { hotkey: 'ArrowDown', callback: () => table.moveCellSelection('down') }, + { hotkey: 'ArrowLeft', callback: () => table.moveCellSelection('left') }, + { hotkey: 'ArrowRight', callback: () => table.moveCellSelection('right') }, + { + hotkey: 'Shift+ArrowUp', + callback: () => table.extendCellSelection('up'), + }, + { hotkey: 'Mod+A', callback: () => table.selectAllCells() }, + { hotkey: 'Escape', callback: () => table.resetCellSelection(true) }, + ], + { target: gridRef }, +) +``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```tsx +useEffect(() => { + table.resetCellSelection(true) +}, [ + table.state.columnOrder, + table.state.columnPinning, + table.state.columnVisibility, +]) +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +const table = useTable({ + //... + autoResetCellSelection: false, //keep ranges across data changes +}) +``` + +### Performance with table.Subscribe + +Cell selection is the feature most likely to make a large table feel slow, because a drag updates state on every cell boundary the pointer crosses. On a table with a thousand rows and a dozen columns, a naive setup re-renders twelve thousand cells per update. + +The per-cell reads themselves are cheap. `cell.getIsSelected()` resolves the cell's row and column index and compares them against a memoized cache of the selection's bounds, which is a handful of integer comparisons. The expensive part is React reconciling every cell in the table. + +`table.Subscribe` is the tool for this, and **where you put it is what matters**. The obvious placement is around the whole ``: + +```tsx +{ + /* every selection change re-renders every row */ +} +; + {() => ( + + {table.getRowModel().rows.map((row) => ( + ... + ))} + + )} + +``` + +That works, but it re-renders the entire table body on every drag update. Instead, put a subscription on each row with a selector that returns only what changes that row's appearance. `Subscribe` uses `useSelector` under the hood, so a row whose selected value is unchanged does not re-render: + +```tsx + + {table.getRowModel().rows.map((row) => ( + + rowSelectionKey( + ranges, + table.getCellSelectionBounds(), + row.getDisplayIndex(), + row.id, + ) + } + > + {() => ( + + {row.getVisibleCells().map((cell) => ( + + + + ))} + + )} + + ))} + +``` + +The selector needs to encode everything that changes how a row draws: whether its own cells fall inside a range, whether the rows immediately above and below do (that is what decides its top and bottom edges), and whether it owns the focused cell. + +```ts +function rowSelectionKey(ranges, bounds, rowIndex, rowId) { + const active = ranges[ranges.length - 1] + let key = + ranges.length > 0 && active.anchorRowId === rowId + ? `f${active.anchorColumnId}` + : '' + + for (const bound of bounds) { + const self = rowIndex >= bound.minRowIndex && rowIndex <= bound.maxRowIndex + const above = + rowIndex - 1 >= bound.minRowIndex && rowIndex - 1 <= bound.maxRowIndex + const below = + rowIndex + 1 >= bound.minRowIndex && rowIndex + 1 <= bound.maxRowIndex + + if (self || above || below) { + key += `|${self ? 1 : 0}${above ? 1 : 0}${below ? 1 : 0}:${bound.minColumnIndex}-${bound.maxColumnIndex}` + } + } + + return key +} +``` + +`table.getCellSelectionBounds()` is memoized, so it computes once per selection change no matter how many rows call it. Rows in the middle of a growing range produce an unchanged key, so extending a drag downward only re-renders the two rows at the moving boundary instead of all of them. On a thousand-row table this took a drag from roughly thirty-five milliseconds per update down to around ten. + +Two things worth knowing about this pattern: + +**Leave column layout out of the selector.** Pinning, reordering, and hiding columns change what a row renders, but those slices are usually part of your `useTable` selector already, so the parent re-renders and recreates these rows anyway. Adding them to the selection selector is redundant. + +**Prefer this over `React.memo`.** A memoized row component reads its cell classes through getters like `cell.getIsSelected()`, which hide their state dependency from the React Compiler. With the stable `row` object as its only visible input, the compiler can cache the row's cells and the highlight will never move, even though `React.memo` re-rendered correctly. `table.Subscribe` avoids the problem entirely because `useSelector` is a dependency the compiler recognizes. See [Subscribe for React Compiler Compatibility](./table-state#subscribe-for-react-compiler-compatibility) for more on this. + +If your table is large enough that this is not sufficient, reach for [virtualization](./virtualization) so that only visible rows exist in the DOM at all. diff --git a/docs/framework/react/guide/migrating.md b/docs/framework/react/guide/migrating.md index 4b8377f8fd..1776d6050f 100644 --- a/docs/framework/react/guide/migrating.md +++ b/docs/framework/react/guide/migrating.md @@ -50,8 +50,9 @@ TanStack Table V9 is a major release with significant internal architectural imp - `tableOptions`: Compose reusable table configuration, including features, row models, and default options. - `createTableHook`: Create custom table hooks with pre-bound features and components when you need a reusable app-level table pattern. See the [composable-tables (createTableHook) guide](./composable-tables.md). -### 6. Refreshed Feature APIs +### 6. New and Refreshed Features +- **New features**: `cellSelectionFeature` adds spreadsheet-style rectangular cell range selection, with drag, Shift-extend, and multiple disjoint ranges. See the [Cell Selection Guide](./cell-selection.md). - **More capable features**: Aggregation, Row Selection, Column Pinning, and Column Resizing have all been made more feature rich (multiple aggregation definitions per column, Shift range selection, logical `start`/`end` pinning, and more). - **New core APIs**: New table and row APIs (like `table.getMaxSubRowDepth()`, `row.getDisplayIndex()`) round out the core feature set. diff --git a/docs/framework/solid/guide/cell-selection.md b/docs/framework/solid/guide/cell-selection.md new file mode 100644 index 0000000000..f951edbe06 --- /dev/null +++ b/docs/framework/solid/guide/cell-selection.md @@ -0,0 +1,347 @@ +--- +title: Cell Selection (Solid) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these Solid examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```ts +import { + createTable, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/solid-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const table = createTable({ + features, + columns, + get data() { + return data() + }, +}) +``` + +## 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.atoms.cellSelection.get()` - returns the current cell selection (a tracked signal read inside JSX, memos, and effects; a plain snapshot elsewhere) +- `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 + +```ts +console.log(table.atoms.cellSelection.get()) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +Reads of `table.atoms.cellSelection.get()` are tracked inside Solid reactive contexts, so they stay fresh automatically. Outside one, the same call is a plain snapshot. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```ts +import { createAtom } from '@tanstack/solid-store' +import { + createTable, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/solid-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const cellSelectionAtom = createAtom([]) + +const table = createTable({ + features, + columns, + get data() { + return data() + }, + atoms: { cellSelection: cellSelectionAtom }, +}) +``` + +The classic controlled-state pattern also works: + +```ts +const [cellSelection, setCellSelection] = createSignal([]) + +const table = createTable({ + features, + columns, + get data() { + return data() + }, + get state() { + return { cellSelection: cellSelection() } + }, + onCellSelectionChange: setCellSelection, +}) +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +const table = createTable({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +}) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +const table = createTable({ + features, + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +}) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```tsx + + + +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +const table = createTable({ + features, + //... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), +}) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```ts +import { createHotkeys } from '@tanstack/solid-hotkeys' + +let gridRef!: HTMLDivElement + +createHotkeys( + [ + { hotkey: 'ArrowUp', callback: () => table.moveCellSelection('up') }, + { hotkey: 'ArrowDown', callback: () => table.moveCellSelection('down') }, + { + hotkey: 'Shift+ArrowDown', + callback: () => table.extendCellSelection('down'), + }, + { hotkey: 'Mod+A', callback: () => table.selectAllCells() }, + { hotkey: 'Escape', callback: () => table.resetCellSelection(true) }, + ], + () => ({ target: gridRef }), +) +``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```ts +createEffect( + on( + () => [ + table.atoms.columnOrder.get(), + table.atoms.columnPinning.get(), + table.atoms.columnVisibility.get(), + ], + () => table.resetCellSelection(true), + { defer: true }, // skip the first run so an initialState selection survives + ), +) +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +const table = createTable({ + features, + //... + autoResetCellSelection: false, //keep ranges across data changes +}) +``` + +### Performance + +Solid tracks atom reads natively, so a selection change re-runs only the memos +and JSX expressions that actually read it. There is no equivalent of React's +per-row `Subscribe` to reach for here, and the example renders its cells plainly. + +Measured on a table with a thousand rows and twelve columns, a drag updates in +roughly 13ms per move with plain reads and no subscription primitive at all. + +The per-cell reads are cheap by design. `cell.getIsSelected()` resolves the +cell's row and column index and compares them against a memoized cache of the +selection bounds, which is a handful of integer comparisons. If a very large +table does become a bottleneck, reach for +[virtualization](./virtualization) so that only visible rows exist in the DOM, +rather than for a subscription pattern. diff --git a/docs/framework/solid/guide/migrating.md b/docs/framework/solid/guide/migrating.md index 0a026217df..772af8b4c5 100644 --- a/docs/framework/solid/guide/migrating.md +++ b/docs/framework/solid/guide/migrating.md @@ -44,8 +44,9 @@ TanStack Table V9 is a major release with significant internal architectural imp - **`tableOptions()`**: Compose reusable table configuration, including features, row models, and default options. - **`createTableHook()`**: Define app-specific table factories with shared features, row models, defaults, and components. -### 6. Refreshed Feature APIs +### 6. New and Refreshed Features +- **New features**: `cellSelectionFeature` adds spreadsheet-style rectangular cell range selection, with drag, Shift-extend, and multiple disjoint ranges. See the [Cell Selection Guide](./cell-selection.md). - **More capable features**: Aggregation, Row Selection, Column Pinning, and Column Resizing have all been made more feature rich (multiple aggregation definitions per column, Shift range selection, logical `start`/`end` pinning, and more). - **New core APIs**: New table and row APIs (like `table.getMaxSubRowDepth()`, `row.getDisplayIndex()`) round out the core feature set. diff --git a/docs/framework/svelte/guide/cell-selection.md b/docs/framework/svelte/guide/cell-selection.md new file mode 100644 index 0000000000..445b9da6a1 --- /dev/null +++ b/docs/framework/svelte/guide/cell-selection.md @@ -0,0 +1,357 @@ +--- +title: Cell Selection (Svelte) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these Svelte examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```ts +import { + createTable, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/svelte-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const table = createTable({ + features, + columns, + get data() { + return data + }, +}) +``` + +## 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.state.cellSelection` - returns the cell selection state reactively (selected by the `createTable` selector) +- `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 + +```ts +console.log(table.state.cellSelection) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +In event handlers or other non-render code, you can also read the current snapshot with `table.atoms.cellSelection.get()`. This read does not subscribe a component to future changes, so prefer `table.state.cellSelection` in render positions. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```ts +import { createAtom } from '@tanstack/svelte-store' +import { + createTable, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/svelte-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const cellSelectionAtom = createAtom([]) + +const table = createTable({ + features, + columns, + get data() { + return data + }, + atoms: { cellSelection: cellSelectionAtom }, +}) +``` + +The classic controlled-state pattern also works: + +```ts +let cellSelection = $state([]) + +const table = createTable({ + features, + columns, + get data() { + return data + }, + get state() { + return { cellSelection } + }, + onCellSelectionChange: (updater) => { + cellSelection = + updater instanceof Function ? updater(cellSelection) : updater + }, +}) +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +const table = createTable({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +}) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +const table = createTable({ + features, + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +}) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```svelte + + + +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +const table = createTable({ + features, + //... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), +}) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```ts +import { createHotkeysAttachment } from '@tanstack/svelte-hotkeys' + +const gridKeys = createHotkeysAttachment([ + { hotkey: 'ArrowUp', callback: () => table.moveCellSelection('up') }, + { hotkey: 'ArrowDown', callback: () => table.moveCellSelection('down') }, + { + hotkey: 'Shift+ArrowDown', + callback: () => table.extendCellSelection('down'), + }, + { hotkey: 'Mod+A', callback: () => table.selectAllCells() }, + { hotkey: 'Escape', callback: () => table.resetCellSelection(true) }, +]) + +// then, in markup: +//
...
+``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```ts +let isFirstColumnLayout = true + +$effect(() => { + // read the atoms, not table.state: the selector rebuilds that object on every + // selected slice change, so reading it here would re-fire on every selection + void table.atoms.columnOrder.get() + void table.atoms.columnPinning.get() + void table.atoms.columnVisibility.get() + + if (isFirstColumnLayout) { + isFirstColumnLayout = false + return + } + table.resetCellSelection(true) +}) +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +const table = createTable({ + features, + //... + autoResetCellSelection: false, //keep ranges across data changes +}) +``` + +### Performance + +Svelte's runes track these reads and the compiler updates only the DOM nodes +that actually changed, so the example renders its cells plainly. + +Measured on a table with a thousand rows and twelve columns, a drag updates in +roughly 16ms per move with plain reads. + +`subscribeTable` is available if you want an explicit fine-grained subscription: + +```svelte +const selected = subscribeTable( table.atoms.cellSelection, (ranges) => +ranges.length, ) // read it as selected.current +``` + +One trap worth knowing: do not read `table.state` inside an `$effect` that also +writes selection state. The selector rebuilds that object whenever any selected +slice changes, so an effect meant to watch the column layout will re-fire on +every selection change and clear the selection you just made. Read +`table.atoms..get()` instead. diff --git a/docs/framework/svelte/guide/migrating.md b/docs/framework/svelte/guide/migrating.md index fc94c314ad..030ab394a9 100644 --- a/docs/framework/svelte/guide/migrating.md +++ b/docs/framework/svelte/guide/migrating.md @@ -44,8 +44,9 @@ TanStack Table V9 is a major release with significant internal architectural imp - **`tableOptions()`**: Compose reusable table configuration, including features, row models, and default options. - **`createTableHook()`**: Define shared Svelte table factories with pre-bound features, row models, defaults, and registered components. -### 6. Refreshed Feature APIs +### 6. New and Refreshed Features +- **New features**: `cellSelectionFeature` adds spreadsheet-style rectangular cell range selection, with drag, Shift-extend, and multiple disjoint ranges. See the [Cell Selection Guide](./cell-selection.md). - **More capable features**: Aggregation, Row Selection, Column Pinning, and Column Resizing have all been made more feature rich (multiple aggregation definitions per column, Shift range selection, logical `start`/`end` pinning, and more). - **New core APIs**: New table and row APIs (like `table.getMaxSubRowDepth()`, `row.getDisplayIndex()`) round out the core feature set. diff --git a/docs/framework/vue/guide/cell-selection.md b/docs/framework/vue/guide/cell-selection.md new file mode 100644 index 0000000000..cc24f17795 --- /dev/null +++ b/docs/framework/vue/guide/cell-selection.md @@ -0,0 +1,343 @@ +--- +title: Cell Selection (Vue) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these Vue examples: + +- [Cell Selection](../examples/cell-selection) + +### Cell Selection Setup + +Here's how you set up your table to use cell selection features. Adding the cell selection feature enables the related APIs. + +```ts +import { + useTable, + tableFeatures, + cellSelectionFeature, +} from '@tanstack/vue-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const table = useTable({ + features, + columns, + data, +}) +``` + +## 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. + +### Access Cell Selection State + +The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs. + +- `table.atoms.cellSelection.get()` - returns the current cell selection (reactive inside templates, `computed(...)`, `watch(...)`, and `table.Subscribe`; a plain snapshot elsewhere) +- `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 + +```ts +console.log(table.atoms.cellSelection.get()) //get the cell selection state +console.log(table.getSelectedCellCount()) //3 +console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName'] +console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]] +``` + +Reads of `table.atoms.cellSelection.get()` are tracked inside Vue reactive contexts, so they stay fresh automatically. Outside one, the same call is a plain snapshot. + +The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection. + +### Cell Selection State Shape + +`CellSelectionState` is an array of rectangles, each stored as its two defining corners: + +```ts +type CellSelectionRange = { + anchorRowId: string + anchorColumnId: string + focusRowId: string + focusColumnId: string +} + +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. + +### Manage Cell Selection State + +If you need access to the selection elsewhere in your application, you can own the state slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. + +```ts +import { createAtom } from '@tanstack/vue-store' +import { + useTable, + tableFeatures, + cellSelectionFeature, + type CellSelectionState, +} from '@tanstack/vue-table' + +const features = tableFeatures({ cellSelectionFeature }) + +const cellSelectionAtom = createAtom([]) + +const table = useTable({ + features, + columns, + data, + atoms: { cellSelection: cellSelectionAtom }, +}) +``` + +The classic controlled-state pattern also works: + +```ts +const cellSelection = ref([]) + +const table = useTable({ + features, + columns, + data, + onCellSelectionChange: (updater) => { + cellSelection.value = + updater instanceof Function ? updater(cellSelection.value) : updater + }, + state: { + get cellSelection() { + return cellSelection.value + }, + }, +}) +``` + +> Note: a drag emits one change per cell boundary the pointer crosses, so `onCellSelectionChange` fires repeatedly during a drag. If you are syncing selection to a server or a URL, debounce it or commit on `mouseup`. + +### Useful Row Ids + +Cell selection is keyed by row id and column id, so a meaningful row id matters here for the same reason it does with row selection. Use the `getRowId` table option to key selection by something stable from your data. + +```ts +const table = useTable({ + features, + //... + getRowId: (row) => row.uuid, // use the row's uuid from your database as the row id +}) +``` + +### Enable Cell Selection Conditionally + +Cell selection is enabled by default for every cell. Use the `enableCellSelection` table option to turn it off entirely, or pass a function for per-cell control. + +```ts +const table = useTable({ + features, + //... + enableCellSelection: (cell) => cell.row.original.age > 18, //only adults' cells are selectable +}) +``` + +A column def can also opt out, which is the common case for checkbox or action columns. A column-level `false` wins over the table option. + +```ts +columnHelper.accessor('actions', { + enableCellSelection: false, //this column can never be selected +}) +``` + +A cell that cannot be selected is skipped even when a rectangle is drawn straight through it, and `moveCellSelection` steps over its column rather than landing on it. Use `cell.getCanSelect()` to decide whether to attach selection handlers in your UI. + +### Mouse Interactions + +Two cell handlers drive every mouse interaction: + +- `cell.getSelectionStartHandler()` - bind to `onMouseDown` +- `cell.getSelectionExtendHandler()` - bind to `onMouseEnter` + +```vue + + + +``` + +You do not need to handle `mouseup` yourself. The start handler attaches its own document-level `mouseup` listener and removes it when the drag ends, so releasing the pointer outside the table still finishes the drag correctly. If your table renders into another document, such as an iframe or a popout window, pass that document in: `cell.getSelectionStartHandler(myDocument)`. + +#### Drag Selection + +Pressing down on a cell starts a new single-cell range, and every cell the pointer then enters moves that range's focus corner. Set `enableCellSelectionDrag: false` to require explicit clicks instead. + +#### Shift Range Selection + +Shift-clicking moves the active range's focus corner to the clicked cell, keeping its anchor fixed. The active cell therefore stays where the selection started, matching spreadsheet behavior. + +The handler recognizes Shift when the event exposes either `event.shiftKey` or `event.nativeEvent.shiftKey`. You can disable range behavior or replace the detection: + +```ts +const table = useTable({ + features, + //... + enableCellRangeSelection: false, + + // For example, use the platform modifier instead of Shift: + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), +}) +``` + +#### 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. + +### 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.getSelectionEdges()` - which sides sit on the selection boundary +- `cell.getTabIndex()` - `0` for the focused cell and `-1` otherwise, for roving tabindex + +`getSelectionEdges()` returns `{ top, right, bottom, left }`, where a side is `true` when the neighboring cell in that direction is not itself selected. That is what lets you draw a single continuous outline around a selection, including around a union of separate rectangles, without every cell inspecting its neighbors. + +```tsx +function getCellClassName(cell) { + // most cells are unselected, so bail before asking for edges + if (!cell.getIsSelected()) { + return cell.getIsFocused() ? 'cell cell-focused' : 'cell' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} +``` + +> Tip: draw the outline with `box-shadow: inset ...` rather than `border`. On a `border-collapse` table a thicker border widens the shared grid line, which makes rows change height as cells become selected. A box-shadow never affects layout. + +### Keyboard Navigation + +Cell selection ships no keyboard handling of its own. Instead it exposes imperative APIs so a dedicated library, such as [TanStack Hotkeys](https://tanstack.com/hotkeys), can drive it: + +- `table.moveCellSelection(direction)` - collapse the selection to a single cell one step away +- `table.extendCellSelection(direction)` - move the active range's focus corner, keeping its anchor +- `table.setFocusedCell(rowId, columnId)` - collapse the selection to one specific cell +- `table.selectAllCells()` - select every selectable cell +- `table.resetCellSelection(true)` - clear the selection + +`direction` is `'up'`, `'down'`, `'left'`, or `'right'`. + +```ts +import { useHotkeys } from '@tanstack/vue-hotkeys' + +const gridRef = useTemplateRef('grid') + +useHotkeys( + [ + { hotkey: 'ArrowUp', callback: () => table.moveCellSelection('up') }, + { hotkey: 'ArrowDown', callback: () => table.moveCellSelection('down') }, + { + hotkey: 'Shift+ArrowDown', + callback: () => table.extendCellSelection('down'), + }, + { hotkey: 'Mod+A', callback: () => table.selectAllCells() }, + { hotkey: 'Escape', callback: () => table.resetCellSelection(true) }, + ], + { target: gridRef }, +) +``` + +Scope the hotkeys to the grid element rather than the document, or arrow keys and Escape will hijack inputs elsewhere on the page. + +### 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. + +```ts +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + // spreadsheets expect a quoted field once it contains a delimiter, a newline, + // or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') +} + +navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) +``` + +### How Ranges Survive Table Changes + +Ranges store row and column ids, not positions, so they follow their corner cells rather than screen coordinates. + +- **Sorting, filtering, and column reordering** keep the corners pinned and recompute what sits between them. A range from "row A to row B" still runs from A to B after a sort, even though different rows now fall in between. +- **Column pinning** is accounted for in render order, so a rectangle stays visually contiguous when a column is pinned. +- **Hiding a column** that a corner sits on makes the range inert. Nothing renders as selected, but the range stays in state and comes back when the column is shown again. +- **Pagination** resolves against the pre-pagination order, so a range can span pages and lights up correctly on whichever page you are viewing. + +Because a reorder can widen a selection onto columns the user never picked, some applications prefer to clear the selection whenever the column layout changes. That is a userland decision, and one `useEffect` away: + +```ts +watch( + () => [ + table.atoms.columnOrder.get(), + table.atoms.columnPinning.get(), + table.atoms.columnVisibility.get(), + ], + () => table.resetCellSelection(true), +) +``` + +### Resetting Cell Selection + +`table.resetCellSelection()` restores `initialState.cellSelection`. Pass `true` to ignore initial state and clear the selection entirely. + +The selection also resets automatically whenever `data` changes, because new data can invalidate the row ids a range points at, or silently re-select cells if the new data happens to reuse ids. Turn that off with `autoResetCellSelection: false`, and note that `autoResetAll` overrides it. + +```ts +const table = useTable({ + features, + //... + autoResetCellSelection: false, //keep ranges across data changes +}) +``` + +### Performance + +Vue tracks atom reads natively, so a selection change invalidates only the +computed effects that actually read it. There is no equivalent of React's per-row +`Subscribe` to reach for here, and the example renders its cells plainly. + +Measured on a table with a thousand rows and twelve columns, a drag updates in +roughly 12ms per move with plain reads and no subscription primitive at all. + +The per-cell reads are cheap by design. `cell.getIsSelected()` resolves the +cell's row and column index and compares them against a memoized cache of the +selection bounds, which is a handful of integer comparisons. If a very large +table does become a bottleneck, reach for +[virtualization](./virtualization) so that only visible rows exist in the DOM, +rather than for a subscription pattern. diff --git a/docs/framework/vue/guide/migrating.md b/docs/framework/vue/guide/migrating.md index f9f1cb35c9..21358b7a47 100644 --- a/docs/framework/vue/guide/migrating.md +++ b/docs/framework/vue/guide/migrating.md @@ -50,8 +50,9 @@ TanStack Table V9 is a major release with significant internal architectural imp - **`tableOptions()`**: Compose reusable table configuration, including features, row models, and default options. - **`createTableHook()`**: Define shared Vue table factories with pre-bound features, row models, defaults, and components. -### 6. Refreshed Feature APIs +### 6. New and Refreshed Features +- **New features**: `cellSelectionFeature` adds spreadsheet-style rectangular cell range selection, with drag, Shift-extend, and multiple disjoint ranges. See the [Cell Selection Guide](./cell-selection.md). - **More capable features**: Aggregation, Row Selection, Column Pinning, and Column Resizing have all been made more feature rich (multiple aggregation definitions per column, Shift range selection, logical `start`/`end` pinning, and more). - **New core APIs**: New table and row APIs (like `table.getMaxSubRowDepth()`, `row.getDisplayIndex()`) round out the core feature set. diff --git a/docs/reference/index/functions/createCoreRowModel.md b/docs/reference/index/functions/createCoreRowModel.md index c7e9ee8ecc..669a215a0d 100644 --- a/docs/reference/index/functions/createCoreRowModel.md +++ b/docs/reference/index/functions/createCoreRowModel.md @@ -9,7 +9,7 @@ title: createCoreRowModel function createCoreRowModel(): (table) => () => RowModel; ``` -Defined in: [core/row-models/createCoreRowModel.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/createCoreRowModel.ts#L15) +Defined in: [core/row-models/createCoreRowModel.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/createCoreRowModel.ts#L16) Creates a memoized core row model factory. diff --git a/docs/reference/index/functions/expandRows.md b/docs/reference/index/functions/expandRows.md index dd38e8fbdb..1396582c84 100644 --- a/docs/reference/index/functions/expandRows.md +++ b/docs/reference/index/functions/expandRows.md @@ -9,7 +9,7 @@ title: expandRows function expandRows(rowModel): RowModel; ``` -Defined in: [features/row-expanding/createExpandedRowModel.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/createExpandedRowModel.ts#L61) +Defined in: [features/row-expanding/createExpandedRowModel.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/createExpandedRowModel.ts#L62) Expands a row model according to the current expanded row state. diff --git a/docs/reference/index/functions/makeStateUpdater.md b/docs/reference/index/functions/makeStateUpdater.md index 436b09ba16..b53ce80ad5 100644 --- a/docs/reference/index/functions/makeStateUpdater.md +++ b/docs/reference/index/functions/makeStateUpdater.md @@ -57,12 +57,13 @@ The updater writes through the table base atom for the slice and supports both v #### updater -[`Updater`](../type-aliases/Updater.md)\<`TableState_WorkerRowModels` & [`TableState_RowSorting`](../interfaces/TableState_RowSorting.md) & [`TableState_ColumnPinning`](../interfaces/TableState_ColumnPinning.md) & [`TableState_ColumnSizing`](../interfaces/TableState_ColumnSizing.md) & [`TableState_ColumnResizing`](../interfaces/TableState_ColumnResizing.md) & [`TableState_GlobalFiltering`](../interfaces/TableState_GlobalFiltering.md) & [`TableState_ColumnOrdering`](../interfaces/TableState_ColumnOrdering.md) & [`TableState_ColumnVisibility`](../interfaces/TableState_ColumnVisibility.md) & [`TableState_RowExpanding`](../interfaces/TableState_RowExpanding.md) & [`TableState_RowPinning`](../interfaces/TableState_RowPinning.md) & [`TableState_RowSelection`](../interfaces/TableState_RowSelection.md) & [`TableState_ColumnFiltering`](../interfaces/TableState_ColumnFiltering.md) & [`TableState_RowPagination`](../interfaces/TableState_RowPagination.md) & [`TableState_ColumnGrouping`](../interfaces/TableState_ColumnGrouping.md)\[`K` & +[`Updater`](../type-aliases/Updater.md)\<`TableState_WorkerRowModels` & [`TableState_RowSorting`](../interfaces/TableState_RowSorting.md) & [`TableState_ColumnPinning`](../interfaces/TableState_ColumnPinning.md) & [`TableState_ColumnSizing`](../interfaces/TableState_ColumnSizing.md) & [`TableState_ColumnResizing`](../interfaces/TableState_ColumnResizing.md) & [`TableState_GlobalFiltering`](../interfaces/TableState_GlobalFiltering.md) & [`TableState_ColumnOrdering`](../interfaces/TableState_ColumnOrdering.md) & [`TableState_ColumnVisibility`](../interfaces/TableState_ColumnVisibility.md) & [`TableState_RowExpanding`](../interfaces/TableState_RowExpanding.md) & [`TableState_RowPinning`](../interfaces/TableState_RowPinning.md) & [`TableState_RowSelection`](../interfaces/TableState_RowSelection.md) & [`TableState_ColumnFiltering`](../interfaces/TableState_ColumnFiltering.md) & [`TableState_RowPagination`](../interfaces/TableState_RowPagination.md) & [`TableState_ColumnGrouping`](../interfaces/TableState_ColumnGrouping.md) & [`TableState_CellSelection`](../interfaces/TableState_CellSelection.md)\[`K` & \| `"expanded"` \| `"columnFilters"` \| `"globalFilter"` \| `"grouping"` \| `"sorting"` + \| `"cellSelection"` \| `"columnOrder"` \| `"columnPinning"` \| `"columnResizing"` diff --git a/docs/reference/index/index.md b/docs/reference/index/index.md index e4be8ccf0b..bab42cfd74 100644 --- a/docs/reference/index/index.md +++ b/docs/reference/index/index.md @@ -25,14 +25,17 @@ title: index - [CachedRowModel\_Paginated](interfaces/CachedRowModel_Paginated.md) - [CachedRowModel\_Sorted](interfaces/CachedRowModel_Sorted.md) - [CachedRowModels\_FeatureMap](interfaces/CachedRowModels_FeatureMap.md) -- [Cell\_Aggregation](interfaces/Cell_Aggregation.md) - [Cell\_Cell](interfaces/Cell_Cell.md) +- [Cell\_CellSelection](interfaces/Cell_CellSelection.md) - [Cell\_ColumnGrouping](interfaces/Cell_ColumnGrouping.md) - [Cell\_Core](interfaces/Cell_Core.md) - [Cell\_CoreProperties](interfaces/Cell_CoreProperties.md) - [Cell\_FeatureMap](interfaces/Cell_FeatureMap.md) +- [Cell\_RowAggregation](interfaces/Cell_RowAggregation.md) - [CellContext](interfaces/CellContext.md) -- [Column\_Aggregation](interfaces/Column_Aggregation.md) +- [CellSelectionBounds](interfaces/CellSelectionBounds.md) +- [CellSelectionEdges](interfaces/CellSelectionEdges.md) +- [CellSelectionRange](interfaces/CellSelectionRange.md) - [Column\_Column](interfaces/Column_Column.md) - [Column\_ColumnFaceting](interfaces/Column_ColumnFaceting.md) - [Column\_ColumnFiltering](interfaces/Column_ColumnFiltering.md) @@ -47,8 +50,9 @@ title: index - [Column\_FeatureMap](interfaces/Column_FeatureMap.md) - [Column\_GlobalFiltering](interfaces/Column_GlobalFiltering.md) - [Column\_Internal](interfaces/Column_Internal.md) +- [Column\_RowAggregation](interfaces/Column_RowAggregation.md) - [Column\_RowSorting](interfaces/Column_RowSorting.md) -- [ColumnDef\_Aggregation](interfaces/ColumnDef_Aggregation.md) +- [ColumnDef\_CellSelection](interfaces/ColumnDef_CellSelection.md) - [ColumnDef\_ColumnFiltering](interfaces/ColumnDef_ColumnFiltering.md) - [ColumnDef\_ColumnGrouping](interfaces/ColumnDef_ColumnGrouping.md) - [ColumnDef\_ColumnPinning](interfaces/ColumnDef_ColumnPinning.md) @@ -57,6 +61,7 @@ title: index - [ColumnDef\_ColumnVisibility](interfaces/ColumnDef_ColumnVisibility.md) - [ColumnDef\_FeatureMap](interfaces/ColumnDef_FeatureMap.md) - [ColumnDef\_GlobalFiltering](interfaces/ColumnDef_GlobalFiltering.md) +- [ColumnDef\_RowAggregation](interfaces/ColumnDef_RowAggregation.md) - [ColumnDef\_RowSorting](interfaces/ColumnDef_RowSorting.md) - [ColumnDefaultOptions](interfaces/ColumnDefaultOptions.md) - [ColumnFilter](interfaces/ColumnFilter.md) @@ -95,7 +100,6 @@ title: index - [PrototypeAPI](interfaces/PrototypeAPI.md) - [ResolvedAggregationFn](interfaces/ResolvedAggregationFn.md) - [ResolvedColumnFilter](interfaces/ResolvedColumnFilter.md) -- [Row\_Aggregation](interfaces/Row_Aggregation.md) - [Row\_ColumnFiltering](interfaces/Row_ColumnFiltering.md) - [Row\_ColumnGrouping](interfaces/Row_ColumnGrouping.md) - [Row\_ColumnPinning](interfaces/Row_ColumnPinning.md) @@ -104,23 +108,26 @@ title: index - [Row\_CoreProperties](interfaces/Row_CoreProperties.md) - [Row\_FeatureMap](interfaces/Row_FeatureMap.md) - [Row\_Row](interfaces/Row_Row.md) +- [Row\_RowAggregation](interfaces/Row_RowAggregation.md) - [Row\_RowExpanding](interfaces/Row_RowExpanding.md) - [Row\_RowPinning](interfaces/Row_RowPinning.md) - [Row\_RowSelection](interfaces/Row_RowSelection.md) - [RowModel](interfaces/RowModel.md) -- [RowModelFns\_Aggregation](interfaces/RowModelFns_Aggregation.md) - [RowModelFns\_All](interfaces/RowModelFns_All.md) - [RowModelFns\_ColumnFiltering](interfaces/RowModelFns_ColumnFiltering.md) - [RowModelFns\_Core](interfaces/RowModelFns_Core.md) - [RowModelFns\_FeatureMap](interfaces/RowModelFns_FeatureMap.md) +- [RowModelFns\_RowAggregation](interfaces/RowModelFns_RowAggregation.md) - [RowModelFns\_RowSorting](interfaces/RowModelFns_RowSorting.md) - [RowPinningDefaultOptions](interfaces/RowPinningDefaultOptions.md) - [RowPinningState](interfaces/RowPinningState.md) +- [SelectCellRangeOptions](interfaces/SelectCellRangeOptions.md) - [SortFn](interfaces/SortFn.md) - [SortFnDef](interfaces/SortFnDef.md) - [SortFns](interfaces/SortFns.md) - [StockFeatures](interfaces/StockFeatures.md) - [StringHeaderIdentifier](interfaces/StringHeaderIdentifier.md) +- [Table\_CellSelection](interfaces/Table_CellSelection.md) - [Table\_ColumnFaceting](interfaces/Table_ColumnFaceting.md) - [Table\_ColumnFiltering](interfaces/Table_ColumnFiltering.md) - [Table\_ColumnGrouping](interfaces/Table_ColumnGrouping.md) @@ -153,8 +160,8 @@ title: index - [TableFeature](interfaces/TableFeature.md) - [TableFeatures](interfaces/TableFeatures.md) - [TableMeta](interfaces/TableMeta.md) -- [TableOptions\_Aggregation](interfaces/TableOptions_Aggregation.md) - [TableOptions\_Cell](interfaces/TableOptions_Cell.md) +- [TableOptions\_CellSelection](interfaces/TableOptions_CellSelection.md) - [TableOptions\_ColumnFiltering](interfaces/TableOptions_ColumnFiltering.md) - [TableOptions\_ColumnGrouping](interfaces/TableOptions_ColumnGrouping.md) - [TableOptions\_ColumnOrdering](interfaces/TableOptions_ColumnOrdering.md) @@ -166,6 +173,7 @@ title: index - [TableOptions\_Core](interfaces/TableOptions_Core.md) - [TableOptions\_FeatureMap](interfaces/TableOptions_FeatureMap.md) - [TableOptions\_GlobalFiltering](interfaces/TableOptions_GlobalFiltering.md) +- [TableOptions\_RowAggregation](interfaces/TableOptions_RowAggregation.md) - [TableOptions\_RowExpanding](interfaces/TableOptions_RowExpanding.md) - [TableOptions\_RowPagination](interfaces/TableOptions_RowPagination.md) - [TableOptions\_RowPinning](interfaces/TableOptions_RowPinning.md) @@ -174,6 +182,7 @@ title: index - [TableOptions\_RowSorting](interfaces/TableOptions_RowSorting.md) - [TableOptions\_Table](interfaces/TableOptions_Table.md) - [TableState\_All](interfaces/TableState_All.md) +- [TableState\_CellSelection](interfaces/TableState_CellSelection.md) - [TableState\_ColumnFiltering](interfaces/TableState_ColumnFiltering.md) - [TableState\_ColumnGrouping](interfaces/TableState_ColumnGrouping.md) - [TableState\_ColumnOrdering](interfaces/TableState_ColumnOrdering.md) @@ -214,6 +223,8 @@ title: index - [CachedRowModels](type-aliases/CachedRowModels.md) - [Cell](type-aliases/Cell.md) - [CellData](type-aliases/CellData.md) +- [CellSelectionDirection](type-aliases/CellSelectionDirection.md) +- [CellSelectionState](type-aliases/CellSelectionState.md) - [Column](type-aliases/Column.md) - [ColumnAggregationValue](type-aliases/ColumnAggregationValue.md) - [ColumnDef](type-aliases/ColumnDef.md) @@ -299,6 +310,7 @@ title: index - [aggregationFn\_unique](variables/aggregationFn_unique.md) - [aggregationFn\_uniqueCount](variables/aggregationFn_uniqueCount.md) - [~~aggregationFns~~](variables/aggregationFns.md) +- [cellSelectionFeature](variables/cellSelectionFeature.md) - [columnFacetingFeature](variables/columnFacetingFeature.md) - [columnFilteringFeature](variables/columnFilteringFeature.md) - [columnGroupingFeature](variables/columnGroupingFeature.md) diff --git a/docs/reference/index/interfaces/CellSelectionBounds.md b/docs/reference/index/interfaces/CellSelectionBounds.md new file mode 100644 index 0000000000..dc2309ac71 --- /dev/null +++ b/docs/reference/index/interfaces/CellSelectionBounds.md @@ -0,0 +1,53 @@ +--- +id: CellSelectionBounds +title: CellSelectionBounds +--- + +# Interface: CellSelectionBounds + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:46](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L46) + +A range resolved into inclusive display-order indexes. + +Ranges whose corners no longer resolve are omitted rather than clamped, so a +range with a filtered-out corner contributes nothing while remaining in state. + +## Properties + +### maxColumnIndex + +```ts +maxColumnIndex: number; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L47) + +*** + +### maxRowIndex + +```ts +maxRowIndex: number; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L48) + +*** + +### minColumnIndex + +```ts +minColumnIndex: number; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L49) + +*** + +### minRowIndex + +```ts +minRowIndex: number; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:50](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L50) diff --git a/docs/reference/index/interfaces/CellSelectionEdges.md b/docs/reference/index/interfaces/CellSelectionEdges.md new file mode 100644 index 0000000000..1ef9c8d0db --- /dev/null +++ b/docs/reference/index/interfaces/CellSelectionEdges.md @@ -0,0 +1,50 @@ +--- +id: CellSelectionEdges +title: CellSelectionEdges +--- + +# Interface: CellSelectionEdges + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L56) + +Which sides of a selected cell sit on the outer boundary of the selection. + +## Properties + +### bottom + +```ts +bottom: boolean; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L57) + +*** + +### left + +```ts +left: boolean; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L58) + +*** + +### right + +```ts +right: boolean; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L59) + +*** + +### top + +```ts +top: boolean; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L60) diff --git a/docs/reference/index/interfaces/CellSelectionRange.md b/docs/reference/index/interfaces/CellSelectionRange.md new file mode 100644 index 0000000000..1c3be55a5a --- /dev/null +++ b/docs/reference/index/interfaces/CellSelectionRange.md @@ -0,0 +1,56 @@ +--- +id: CellSelectionRange +title: CellSelectionRange +--- + +# Interface: CellSelectionRange + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L19) + +A single rectangular cell selection, stored as its two defining corners. + +The `anchor` corner stays put while the `focus` corner moves during a +shift-extend or a drag, so the pair carries strictly more information than a +normalized min/max rectangle would. Corners are stored as flat row and column +ids rather than nested objects or a packed `rowId_columnId` key, because +`getRowId` is user-supplied and may return ids containing any separator. + +## Properties + +### anchorColumnId + +```ts +anchorColumnId: string; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L20) + +*** + +### anchorRowId + +```ts +anchorRowId: string; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L21) + +*** + +### focusColumnId + +```ts +focusColumnId: string; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L22) + +*** + +### focusRowId + +```ts +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) diff --git a/docs/reference/index/interfaces/Cell_CellSelection.md b/docs/reference/index/interfaces/Cell_CellSelection.md new file mode 100644 index 0000000000..0878637e93 --- /dev/null +++ b/docs/reference/index/interfaces/Cell_CellSelection.md @@ -0,0 +1,166 @@ +--- +id: Cell_CellSelection +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) + +## Properties + +### getCanSelect() + +```ts +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) + +Checks whether this cell can currently be selected. + +#### Returns + +`boolean` + +*** + +### getIsFocused() + +```ts +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) + +Checks whether this cell is the active cell, i.e. the anchor of the most +recent range. + +#### Returns + +`boolean` + +*** + +### getIsSelected() + +```ts +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) + +Checks whether this cell falls inside any selected range. + +#### Returns + +`boolean` + +*** + +### getSelectionEdges() + +```ts +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) + +Returns which sides of this cell sit on the outer boundary of the +selection, for rendering a spreadsheet-style outline without each cell +inspecting its neighbours. + +All sides are `false` when the cell is not selected. + +#### Returns + +[`CellSelectionEdges`](CellSelectionEdges.md) + +*** + +### getSelectionExtendHandler() + +```ts +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) + +Creates a handler that extends the active range to this cell while a drag +is in progress. Bind it to `mouseenter`. + +The handler no-ops unless a drag is open, and skips redundant writes when +the active range already focuses this cell. + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` + +*** + +### getSelectionStartHandler() + +```ts +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) + +Creates a handler that begins a selection at this cell. Bind it to +`mousedown`. + +Pass the original mouse event, or a framework event whose `nativeEvent` is +that event, so modifier keys can be detected. The handler attaches its own +document-level `mouseup` listener so a drag released outside the table +still ends correctly; pass `contextDocument` when the table renders into +another document, such as an iframe or popout window. + +#### Parameters + +##### contextDocument? + +`Document` + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` + +*** + +### getTabIndex() + +```ts +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) + +Returns `0` for the focused cell and `-1` otherwise, for roving tabindex. + +#### Returns + +`number` diff --git a/docs/reference/index/interfaces/Cell_Core.md b/docs/reference/index/interfaces/Cell_Core.md index 3a29039890..3d3096313f 100644 --- a/docs/reference/index/interfaces/Cell_Core.md +++ b/docs/reference/index/interfaces/Cell_Core.md @@ -5,7 +5,7 @@ title: Cell_Core # Interface: Cell\_Core\ -Defined in: [types/Cell.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L7) +Defined in: [types/Cell.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L8) ## Extends diff --git a/docs/reference/index/interfaces/Cell_FeatureMap.md b/docs/reference/index/interfaces/Cell_FeatureMap.md index 2ef3cd52c3..8d39a9ca2b 100644 --- a/docs/reference/index/interfaces/Cell_FeatureMap.md +++ b/docs/reference/index/interfaces/Cell_FeatureMap.md @@ -5,24 +5,34 @@ title: Cell_FeatureMap # Interface: Cell\_FeatureMap -Defined in: [types/Cell.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L13) +Defined in: [types/Cell.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L14) ## Properties +### cellSelectionFeature + +```ts +cellSelectionFeature: Cell_CellSelection; +``` + +Defined in: [types/Cell.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L15) + +*** + ### columnGroupingFeature ```ts columnGroupingFeature: Cell_ColumnGrouping; ``` -Defined in: [types/Cell.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L15) +Defined in: [types/Cell.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L16) *** ### rowAggregationFeature ```ts -rowAggregationFeature: Cell_Aggregation; +rowAggregationFeature: Cell_RowAggregation; ``` -Defined in: [types/Cell.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L14) +Defined in: [types/Cell.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L17) diff --git a/docs/reference/index/interfaces/Cell_Aggregation.md b/docs/reference/index/interfaces/Cell_RowAggregation.md similarity index 88% rename from docs/reference/index/interfaces/Cell_Aggregation.md rename to docs/reference/index/interfaces/Cell_RowAggregation.md index c3ada522a0..0e1aeed2ee 100644 --- a/docs/reference/index/interfaces/Cell_Aggregation.md +++ b/docs/reference/index/interfaces/Cell_RowAggregation.md @@ -1,9 +1,9 @@ --- -id: Cell_Aggregation -title: Cell_Aggregation +id: Cell_RowAggregation +title: Cell_RowAggregation --- -# Interface: Cell\_Aggregation +# Interface: Cell\_RowAggregation Defined in: [features/row-aggregation/rowAggregationFeature.types.ts:298](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-aggregation/rowAggregationFeature.types.ts#L298) diff --git a/docs/reference/index/interfaces/ColumnDef_CellSelection.md b/docs/reference/index/interfaces/ColumnDef_CellSelection.md new file mode 100644 index 0000000000..7b486cc3f1 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_CellSelection.md @@ -0,0 +1,20 @@ +--- +id: ColumnDef_CellSelection +title: ColumnDef_CellSelection +--- + +# Interface: ColumnDef\_CellSelection + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:137](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L137) + +## Properties + +### enableCellSelection? + +```ts +optional enableCellSelection: boolean; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:141](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L141) + +Allows cells in this column to be selected. Defaults to `true`. diff --git a/docs/reference/index/interfaces/ColumnDef_FeatureMap.md b/docs/reference/index/interfaces/ColumnDef_FeatureMap.md index c55f10e3a8..2df41d19a8 100644 --- a/docs/reference/index/interfaces/ColumnDef_FeatureMap.md +++ b/docs/reference/index/interfaces/ColumnDef_FeatureMap.md @@ -5,7 +5,7 @@ title: ColumnDef_FeatureMap # Interface: ColumnDef\_FeatureMap\ -Defined in: [types/ColumnDef.ts:131](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L131) +Defined in: [types/ColumnDef.ts:132](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L132) ## Type Parameters @@ -23,13 +23,23 @@ Defined in: [types/ColumnDef.ts:131](https://github.com/TanStack/table/blob/main ## Properties +### cellSelectionFeature + +```ts +cellSelectionFeature: ColumnDef_CellSelection; +``` + +Defined in: [types/ColumnDef.ts:137](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L137) + +*** + ### columnFilteringFeature ```ts columnFilteringFeature: ColumnDef_ColumnFiltering; ``` -Defined in: [types/ColumnDef.ts:139](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L139) +Defined in: [types/ColumnDef.ts:138](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L138) *** @@ -39,7 +49,7 @@ Defined in: [types/ColumnDef.ts:139](https://github.com/TanStack/table/blob/main columnGroupingFeature: ColumnDef_ColumnGrouping; ``` -Defined in: [types/ColumnDef.ts:142](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L142) +Defined in: [types/ColumnDef.ts:139](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L139) *** @@ -49,7 +59,7 @@ Defined in: [types/ColumnDef.ts:142](https://github.com/TanStack/table/blob/main columnPinningFeature: ColumnDef_ColumnPinning; ``` -Defined in: [types/ColumnDef.ts:138](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L138) +Defined in: [types/ColumnDef.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L140) *** @@ -59,7 +69,7 @@ Defined in: [types/ColumnDef.ts:138](https://github.com/TanStack/table/blob/main columnResizingFeature: ColumnDef_ColumnResizing; ``` -Defined in: [types/ColumnDef.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L144) +Defined in: [types/ColumnDef.ts:141](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L141) *** @@ -69,7 +79,7 @@ Defined in: [types/ColumnDef.ts:144](https://github.com/TanStack/table/blob/main columnSizingFeature: ColumnDef_ColumnSizing; ``` -Defined in: [types/ColumnDef.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L143) +Defined in: [types/ColumnDef.ts:142](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L142) *** @@ -79,7 +89,7 @@ Defined in: [types/ColumnDef.ts:143](https://github.com/TanStack/table/blob/main columnVisibilityFeature: ColumnDef_ColumnVisibility; ``` -Defined in: [types/ColumnDef.ts:137](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L137) +Defined in: [types/ColumnDef.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L143) *** @@ -89,17 +99,17 @@ Defined in: [types/ColumnDef.ts:137](https://github.com/TanStack/table/blob/main globalFilteringFeature: ColumnDef_GlobalFiltering; ``` -Defined in: [types/ColumnDef.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L140) +Defined in: [types/ColumnDef.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L144) *** ### rowAggregationFeature ```ts -rowAggregationFeature: ColumnDef_Aggregation; +rowAggregationFeature: ColumnDef_RowAggregation; ``` -Defined in: [types/ColumnDef.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L136) +Defined in: [types/ColumnDef.ts:145](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L145) *** @@ -109,4 +119,4 @@ Defined in: [types/ColumnDef.ts:136](https://github.com/TanStack/table/blob/main rowSortingFeature: ColumnDef_RowSorting; ``` -Defined in: [types/ColumnDef.ts:141](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L141) +Defined in: [types/ColumnDef.ts:146](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L146) diff --git a/docs/reference/index/interfaces/ColumnDef_Aggregation.md b/docs/reference/index/interfaces/ColumnDef_RowAggregation.md similarity index 95% rename from docs/reference/index/interfaces/ColumnDef_Aggregation.md rename to docs/reference/index/interfaces/ColumnDef_RowAggregation.md index 7e5f8812b9..95560b4e4d 100644 --- a/docs/reference/index/interfaces/ColumnDef_Aggregation.md +++ b/docs/reference/index/interfaces/ColumnDef_RowAggregation.md @@ -1,9 +1,9 @@ --- -id: ColumnDef_Aggregation -title: ColumnDef_Aggregation +id: ColumnDef_RowAggregation +title: ColumnDef_RowAggregation --- -# Interface: ColumnDef\_Aggregation\ +# Interface: ColumnDef\_RowAggregation\ Defined in: [features/row-aggregation/rowAggregationFeature.types.ts:233](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-aggregation/rowAggregationFeature.types.ts#L233) diff --git a/docs/reference/index/interfaces/ColumnMeta.md b/docs/reference/index/interfaces/ColumnMeta.md index cd947daa4f..5397a1775d 100644 --- a/docs/reference/index/interfaces/ColumnMeta.md +++ b/docs/reference/index/interfaces/ColumnMeta.md @@ -5,7 +5,7 @@ title: ColumnMeta # Interface: ColumnMeta\ -Defined in: [types/ColumnDef.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L20) +Defined in: [types/ColumnDef.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L21) ## Type Parameters diff --git a/docs/reference/index/interfaces/Column_FeatureMap.md b/docs/reference/index/interfaces/Column_FeatureMap.md index 1298e789c7..a9e244ee60 100644 --- a/docs/reference/index/interfaces/Column_FeatureMap.md +++ b/docs/reference/index/interfaces/Column_FeatureMap.md @@ -112,7 +112,7 @@ Defined in: [types/Column.ts:37](https://github.com/TanStack/table/blob/main/pac ### rowAggregationFeature ```ts -rowAggregationFeature: Column_Aggregation; +rowAggregationFeature: Column_RowAggregation; ``` Defined in: [types/Column.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L28) diff --git a/docs/reference/index/interfaces/Column_Aggregation.md b/docs/reference/index/interfaces/Column_RowAggregation.md similarity index 95% rename from docs/reference/index/interfaces/Column_Aggregation.md rename to docs/reference/index/interfaces/Column_RowAggregation.md index 5e85cab81f..9dcb5d1643 100644 --- a/docs/reference/index/interfaces/Column_Aggregation.md +++ b/docs/reference/index/interfaces/Column_RowAggregation.md @@ -1,9 +1,9 @@ --- -id: Column_Aggregation -title: Column_Aggregation +id: Column_RowAggregation +title: Column_RowAggregation --- -# Interface: Column\_Aggregation\ +# Interface: Column\_RowAggregation\ Defined in: [features/row-aggregation/rowAggregationFeature.types.ts:264](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-aggregation/rowAggregationFeature.types.ts#L264) diff --git a/docs/reference/index/interfaces/IdIdentifier.md b/docs/reference/index/interfaces/IdIdentifier.md index ee15cbc3b6..409e84cd5d 100644 --- a/docs/reference/index/interfaces/IdIdentifier.md +++ b/docs/reference/index/interfaces/IdIdentifier.md @@ -5,7 +5,7 @@ title: IdIdentifier # Interface: IdIdentifier\ -Defined in: [types/ColumnDef.ts:83](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L83) +Defined in: [types/ColumnDef.ts:84](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L84) ## Type Parameters @@ -29,7 +29,7 @@ Defined in: [types/ColumnDef.ts:83](https://github.com/TanStack/table/blob/main/ optional header: ColumnDefTemplate>; ``` -Defined in: [types/ColumnDef.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L95) +Defined in: [types/ColumnDef.ts:96](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L96) Header text or template used to render this column's header. @@ -41,6 +41,6 @@ Header text or template used to render this column's header. id: string; ``` -Defined in: [types/ColumnDef.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L91) +Defined in: [types/ColumnDef.ts:92](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L92) Explicit stable column id. diff --git a/docs/reference/index/interfaces/RowModelFns_All.md b/docs/reference/index/interfaces/RowModelFns_All.md index 2fdfb05cab..9136037220 100644 --- a/docs/reference/index/interfaces/RowModelFns_All.md +++ b/docs/reference/index/interfaces/RowModelFns_All.md @@ -9,7 +9,7 @@ Defined in: [types/RowModelFns.ts:25](https://github.com/TanStack/table/blob/mai ## Extends -- `Partial`\<[`RowModelFns_ColumnFiltering`](RowModelFns_ColumnFiltering.md)\<`TFeatures`, `TData`\> & [`RowModelFns_Aggregation`](RowModelFns_Aggregation.md)\<`TFeatures`, `TData`\> & [`RowModelFns_RowSorting`](RowModelFns_RowSorting.md)\<`TFeatures`, `TData`\>\> +- `Partial`\<[`RowModelFns_ColumnFiltering`](RowModelFns_ColumnFiltering.md)\<`TFeatures`, `TData`\> & [`RowModelFns_RowAggregation`](RowModelFns_RowAggregation.md)\<`TFeatures`, `TData`\> & [`RowModelFns_RowSorting`](RowModelFns_RowSorting.md)\<`TFeatures`, `TData`\>\> ## Type Parameters diff --git a/docs/reference/index/interfaces/RowModelFns_FeatureMap.md b/docs/reference/index/interfaces/RowModelFns_FeatureMap.md index b02d3e6004..5de53b953c 100644 --- a/docs/reference/index/interfaces/RowModelFns_FeatureMap.md +++ b/docs/reference/index/interfaces/RowModelFns_FeatureMap.md @@ -32,7 +32,7 @@ Defined in: [types/RowModelFns.ts:14](https://github.com/TanStack/table/blob/mai ### rowAggregationFeature ```ts -rowAggregationFeature: RowModelFns_Aggregation; +rowAggregationFeature: RowModelFns_RowAggregation; ``` Defined in: [types/RowModelFns.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModelFns.ts#L13) diff --git a/docs/reference/index/interfaces/RowModelFns_Aggregation.md b/docs/reference/index/interfaces/RowModelFns_RowAggregation.md similarity index 85% rename from docs/reference/index/interfaces/RowModelFns_Aggregation.md rename to docs/reference/index/interfaces/RowModelFns_RowAggregation.md index 421a9eca0f..8b844f255b 100644 --- a/docs/reference/index/interfaces/RowModelFns_Aggregation.md +++ b/docs/reference/index/interfaces/RowModelFns_RowAggregation.md @@ -1,9 +1,9 @@ --- -id: RowModelFns_Aggregation -title: RowModelFns_Aggregation +id: RowModelFns_RowAggregation +title: RowModelFns_RowAggregation --- -# Interface: RowModelFns\_Aggregation\ +# Interface: RowModelFns\_RowAggregation\ Defined in: [features/row-aggregation/rowAggregationFeature.types.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-aggregation/rowAggregationFeature.types.ts#L95) diff --git a/docs/reference/index/interfaces/Row_FeatureMap.md b/docs/reference/index/interfaces/Row_FeatureMap.md index d84f6b937f..4889817d19 100644 --- a/docs/reference/index/interfaces/Row_FeatureMap.md +++ b/docs/reference/index/interfaces/Row_FeatureMap.md @@ -62,7 +62,7 @@ Defined in: [types/Row.ts:26](https://github.com/TanStack/table/blob/main/packag ### rowAggregationFeature ```ts -rowAggregationFeature: Row_Aggregation; +rowAggregationFeature: Row_RowAggregation; ``` Defined in: [types/Row.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L22) diff --git a/docs/reference/index/interfaces/Row_Aggregation.md b/docs/reference/index/interfaces/Row_RowAggregation.md similarity index 89% rename from docs/reference/index/interfaces/Row_Aggregation.md rename to docs/reference/index/interfaces/Row_RowAggregation.md index 7e0eab76e9..781a79c8ea 100644 --- a/docs/reference/index/interfaces/Row_Aggregation.md +++ b/docs/reference/index/interfaces/Row_RowAggregation.md @@ -1,9 +1,9 @@ --- -id: Row_Aggregation -title: Row_Aggregation +id: Row_RowAggregation +title: Row_RowAggregation --- -# Interface: Row\_Aggregation +# Interface: Row\_RowAggregation Defined in: [features/row-aggregation/rowAggregationFeature.types.ts:304](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-aggregation/rowAggregationFeature.types.ts#L304) diff --git a/docs/reference/index/interfaces/SelectCellRangeOptions.md b/docs/reference/index/interfaces/SelectCellRangeOptions.md new file mode 100644 index 0000000000..6eac5e51e8 --- /dev/null +++ b/docs/reference/index/interfaces/SelectCellRangeOptions.md @@ -0,0 +1,21 @@ +--- +id: SelectCellRangeOptions +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) + +## Properties + +### 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) + +Whether the range should be added alongside existing ranges rather than +replacing them. Defaults to `false`. diff --git a/docs/reference/index/interfaces/StockFeatures.md b/docs/reference/index/interfaces/StockFeatures.md index 0df976d4a5..0156d7b049 100644 --- a/docs/reference/index/interfaces/StockFeatures.md +++ b/docs/reference/index/interfaces/StockFeatures.md @@ -5,17 +5,27 @@ title: StockFeatures # Interface: StockFeatures -Defined in: [features/stockFeatures.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L17) +Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L18) ## Properties +### cellSelectionFeature + +```ts +cellSelectionFeature: TableFeature; +``` + +Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L19) + +*** + ### columnFacetingFeature ```ts columnFacetingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L19) +Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L20) *** @@ -25,7 +35,7 @@ Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blo columnFilteringFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L20) +Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L21) *** @@ -35,7 +45,7 @@ Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blo columnGroupingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L21) +Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L22) *** @@ -45,7 +55,7 @@ Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blo columnOrderingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L22) +Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L23) *** @@ -55,7 +65,7 @@ Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blo columnPinningFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L23) +Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L24) *** @@ -65,7 +75,7 @@ Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blo columnResizingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L24) +Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L25) *** @@ -75,7 +85,7 @@ Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blo columnSizingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L25) +Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L26) *** @@ -85,7 +95,7 @@ Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blo columnVisibilityFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L26) +Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L27) *** @@ -95,7 +105,7 @@ Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blo globalFilteringFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L27) +Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L28) *** @@ -105,7 +115,7 @@ Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blo rowAggregationFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L18) +Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L29) *** @@ -115,7 +125,7 @@ Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blo rowExpandingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L28) +Defined in: [features/stockFeatures.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L30) *** @@ -125,7 +135,7 @@ Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blo rowPaginationFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L29) +Defined in: [features/stockFeatures.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L31) *** @@ -135,7 +145,7 @@ Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blo rowPinningFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L30) +Defined in: [features/stockFeatures.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L32) *** @@ -145,7 +155,7 @@ Defined in: [features/stockFeatures.ts:30](https://github.com/TanStack/table/blo rowSelectionFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L31) +Defined in: [features/stockFeatures.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L33) *** @@ -155,4 +165,4 @@ Defined in: [features/stockFeatures.ts:31](https://github.com/TanStack/table/blo rowSortingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L32) +Defined in: [features/stockFeatures.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L34) diff --git a/docs/reference/index/interfaces/StringHeaderIdentifier.md b/docs/reference/index/interfaces/StringHeaderIdentifier.md index 556fe1ffb7..aa234ad7ef 100644 --- a/docs/reference/index/interfaces/StringHeaderIdentifier.md +++ b/docs/reference/index/interfaces/StringHeaderIdentifier.md @@ -5,7 +5,7 @@ title: StringHeaderIdentifier # Interface: StringHeaderIdentifier -Defined in: [types/ColumnDef.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L72) +Defined in: [types/ColumnDef.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L73) ## Properties @@ -15,7 +15,7 @@ Defined in: [types/ColumnDef.ts:72](https://github.com/TanStack/table/blob/main/ header: string; ``` -Defined in: [types/ColumnDef.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L76) +Defined in: [types/ColumnDef.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L77) Header text used both for rendering and as a fallback column id. @@ -27,6 +27,6 @@ Header text used both for rendering and as a fallback column id. optional id: string; ``` -Defined in: [types/ColumnDef.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L80) +Defined in: [types/ColumnDef.ts:81](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L81) Optional explicit id that overrides the header-derived id. diff --git a/docs/reference/index/interfaces/TableFeatures.md b/docs/reference/index/interfaces/TableFeatures.md index b29bf14067..2c88440ad8 100644 --- a/docs/reference/index/interfaces/TableFeatures.md +++ b/docs/reference/index/interfaces/TableFeatures.md @@ -40,13 +40,27 @@ every built-in aggregation function in your bundle. *** +### cellSelectionFeature? + +```ts +optional cellSelectionFeature: TableFeature; +``` + +Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L19) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`cellSelectionFeature`](StockFeatures.md#cellselectionfeature) + +*** + ### columnFacetingFeature? ```ts optional columnFacetingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L19) +Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L20) #### Inherited from @@ -60,7 +74,7 @@ Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blo optional columnFilteringFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L20) +Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L21) #### Inherited from @@ -74,7 +88,7 @@ Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blo optional columnGroupingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L21) +Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L22) #### Inherited from @@ -107,7 +121,7 @@ When omitted, the global declaration-merged `ColumnMeta` interface applies. optional columnOrderingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L22) +Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L23) #### Inherited from @@ -121,7 +135,7 @@ Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blo optional columnPinningFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L23) +Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L24) #### Inherited from @@ -135,7 +149,7 @@ Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blo optional columnResizingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L24) +Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L25) #### Inherited from @@ -149,7 +163,7 @@ Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blo optional columnSizingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L25) +Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L26) #### Inherited from @@ -163,7 +177,7 @@ Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blo optional columnVisibilityFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L26) +Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L27) #### Inherited from @@ -506,7 +520,7 @@ When omitted, the global declaration-merged `FilterMeta` interface applies. optional globalFilteringFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L27) +Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L28) #### Inherited from @@ -580,7 +594,7 @@ server-side pagination. optional rowAggregationFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L18) +Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L29) #### Inherited from @@ -594,7 +608,7 @@ Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blo optional rowExpandingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L28) +Defined in: [features/stockFeatures.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L30) #### Inherited from @@ -608,7 +622,7 @@ Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blo optional rowPaginationFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L29) +Defined in: [features/stockFeatures.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L31) #### Inherited from @@ -622,7 +636,7 @@ Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blo optional rowPinningFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L30) +Defined in: [features/stockFeatures.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L32) #### Inherited from @@ -636,7 +650,7 @@ Defined in: [features/stockFeatures.ts:30](https://github.com/TanStack/table/blo optional rowSelectionFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L31) +Defined in: [features/stockFeatures.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L33) #### Inherited from @@ -650,7 +664,7 @@ Defined in: [features/stockFeatures.ts:31](https://github.com/TanStack/table/blo optional rowSortingFeature: TableFeature; ``` -Defined in: [features/stockFeatures.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L32) +Defined in: [features/stockFeatures.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L34) #### Inherited from diff --git a/docs/reference/index/interfaces/TableOptions_CellSelection.md b/docs/reference/index/interfaces/TableOptions_CellSelection.md new file mode 100644 index 0000000000..42aef022f3 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_CellSelection.md @@ -0,0 +1,158 @@ +--- +id: TableOptions_CellSelection +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) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### autoResetCellSelection? + +```ts +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) + +Resets cell selection to `initialState.cellSelection` whenever `data` +changes. Defaults to `true`. + +Ranges are stored as row and column ids, so new data would otherwise leave +a selection pointing at rows that no longer exist, or silently re-select +cells if the new data happens to reuse ids. Set to `false` to keep ranges +across data changes, and note `autoResetAll` overrides this. + +*** + +### enableCellRangeSelection? + +```ts +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) + +Enables inclusive cell range selection through shift-click and drag. +Defaults to `true`. + +*** + +### enableCellSelection? + +```ts +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) + +Allows cells to be selected. + +Provide a predicate to decide per cell. A column def may also opt out with +its own `enableCellSelection: false`. Defaults to `true`. + +*** + +### enableCellSelectionDrag? + +```ts +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) + +Enables extending a selection by dragging across cells. Defaults to `true`. + +*** + +### enableMultiCellRangeSelection? + +```ts +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) + +Allows multiple disjoint rectangles to be selected at once. Defaults to +`true`. + +*** + +### isCellRangeSelectionEvent()? + +```ts +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) + +Determines whether a selection-start event should extend the active range +instead of replacing the selection. + +By default, events with `shiftKey` directly on the event or on +`event.nativeEvent` are treated as range-selection events. + +#### Parameters + +##### event + +`unknown` + +#### Returns + +`boolean` + +*** + +### isMultiCellRangeSelectionEvent()? + +```ts +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) + +Determines whether a selection-start event should add a new rectangle +alongside the existing ones. + +By default, events with `ctrlKey` or `metaKey` directly on the event or on +`event.nativeEvent` are treated as multi-range events. + +#### Parameters + +##### event + +`unknown` + +#### Returns + +`boolean` + +*** + +### onCellSelectionChange? + +```ts +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) + +Called with an updater when cell selection state changes. Pair this with +`state.cellSelection` when using external state; external atoms can own the +slice without this callback. + +A drag emits one change per cell crossed. Subscribe to +`table.atoms.cellSelection` for finer-grained reads when that matters. diff --git a/docs/reference/index/interfaces/TableOptions_Core.md b/docs/reference/index/interfaces/TableOptions_Core.md index e3e71d730d..47868b0456 100644 --- a/docs/reference/index/interfaces/TableOptions_Core.md +++ b/docs/reference/index/interfaces/TableOptions_Core.md @@ -5,7 +5,7 @@ title: TableOptions_Core # Interface: TableOptions\_Core\ -Defined in: [types/TableOptions.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L31) +Defined in: [types/TableOptions.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L32) Core options that are always available on a table, before optional feature options are mixed in. diff --git a/docs/reference/index/interfaces/TableOptions_FeatureMap.md b/docs/reference/index/interfaces/TableOptions_FeatureMap.md index c5945f93f4..cba74ae319 100644 --- a/docs/reference/index/interfaces/TableOptions_FeatureMap.md +++ b/docs/reference/index/interfaces/TableOptions_FeatureMap.md @@ -5,7 +5,7 @@ title: TableOptions_FeatureMap # Interface: TableOptions\_FeatureMap\ -Defined in: [types/TableOptions.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L58) +Defined in: [types/TableOptions.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L59) ## Type Parameters @@ -19,13 +19,23 @@ Defined in: [types/TableOptions.ts:58](https://github.com/TanStack/table/blob/ma ## Properties +### cellSelectionFeature + +```ts +cellSelectionFeature: TableOptions_CellSelection; +``` + +Defined in: [types/TableOptions.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L63) + +*** + ### columnFilteringFeature ```ts columnFilteringFeature: TableOptions_ColumnFiltering; ``` -Defined in: [types/TableOptions.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L63) +Defined in: [types/TableOptions.ts:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L64) *** @@ -35,7 +45,7 @@ Defined in: [types/TableOptions.ts:63](https://github.com/TanStack/table/blob/ma columnGroupingFeature: TableOptions_ColumnGrouping; ``` -Defined in: [types/TableOptions.ts:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L64) +Defined in: [types/TableOptions.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L65) *** @@ -45,7 +55,7 @@ Defined in: [types/TableOptions.ts:64](https://github.com/TanStack/table/blob/ma columnOrderingFeature: TableOptions_ColumnOrdering; ``` -Defined in: [types/TableOptions.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L65) +Defined in: [types/TableOptions.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L66) *** @@ -55,7 +65,7 @@ Defined in: [types/TableOptions.ts:65](https://github.com/TanStack/table/blob/ma columnPinningFeature: TableOptions_ColumnPinning; ``` -Defined in: [types/TableOptions.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L66) +Defined in: [types/TableOptions.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L67) *** @@ -65,7 +75,7 @@ Defined in: [types/TableOptions.ts:66](https://github.com/TanStack/table/blob/ma columnResizingFeature: TableOptions_ColumnResizing; ``` -Defined in: [types/TableOptions.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L67) +Defined in: [types/TableOptions.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L68) *** @@ -75,7 +85,7 @@ Defined in: [types/TableOptions.ts:67](https://github.com/TanStack/table/blob/ma columnSizingFeature: TableOptions_ColumnSizing; ``` -Defined in: [types/TableOptions.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L68) +Defined in: [types/TableOptions.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L69) *** @@ -85,7 +95,7 @@ Defined in: [types/TableOptions.ts:68](https://github.com/TanStack/table/blob/ma columnVisibilityFeature: TableOptions_ColumnVisibility; ``` -Defined in: [types/TableOptions.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L69) +Defined in: [types/TableOptions.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L70) *** @@ -95,17 +105,17 @@ Defined in: [types/TableOptions.ts:69](https://github.com/TanStack/table/blob/ma globalFilteringFeature: TableOptions_GlobalFiltering; ``` -Defined in: [types/TableOptions.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L70) +Defined in: [types/TableOptions.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L71) *** ### rowAggregationFeature ```ts -rowAggregationFeature: TableOptions_Aggregation; +rowAggregationFeature: TableOptions_RowAggregation; ``` -Defined in: [types/TableOptions.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L62) +Defined in: [types/TableOptions.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L72) *** @@ -115,7 +125,7 @@ Defined in: [types/TableOptions.ts:62](https://github.com/TanStack/table/blob/ma rowExpandingFeature: TableOptions_RowExpanding; ``` -Defined in: [types/TableOptions.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L71) +Defined in: [types/TableOptions.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L73) *** @@ -125,7 +135,7 @@ Defined in: [types/TableOptions.ts:71](https://github.com/TanStack/table/blob/ma rowPaginationFeature: TableOptions_RowPagination; ``` -Defined in: [types/TableOptions.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L72) +Defined in: [types/TableOptions.ts:74](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L74) *** @@ -135,7 +145,7 @@ Defined in: [types/TableOptions.ts:72](https://github.com/TanStack/table/blob/ma rowPinningFeature: TableOptions_RowPinning; ``` -Defined in: [types/TableOptions.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L73) +Defined in: [types/TableOptions.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L75) *** @@ -145,7 +155,7 @@ Defined in: [types/TableOptions.ts:73](https://github.com/TanStack/table/blob/ma rowSelectionFeature: TableOptions_RowSelection; ``` -Defined in: [types/TableOptions.ts:74](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L74) +Defined in: [types/TableOptions.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L76) *** @@ -155,4 +165,4 @@ Defined in: [types/TableOptions.ts:74](https://github.com/TanStack/table/blob/ma rowSortingFeature: TableOptions_RowSorting; ``` -Defined in: [types/TableOptions.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L75) +Defined in: [types/TableOptions.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L77) diff --git a/docs/reference/index/interfaces/TableOptions_Aggregation.md b/docs/reference/index/interfaces/TableOptions_RowAggregation.md similarity index 87% rename from docs/reference/index/interfaces/TableOptions_Aggregation.md rename to docs/reference/index/interfaces/TableOptions_RowAggregation.md index dc8b63caee..76a4bc4fce 100644 --- a/docs/reference/index/interfaces/TableOptions_Aggregation.md +++ b/docs/reference/index/interfaces/TableOptions_RowAggregation.md @@ -1,9 +1,9 @@ --- -id: TableOptions_Aggregation -title: TableOptions_Aggregation +id: TableOptions_RowAggregation +title: TableOptions_RowAggregation --- -# Interface: TableOptions\_Aggregation +# Interface: TableOptions\_RowAggregation Defined in: [features/row-aggregation/rowAggregationFeature.types.ts:332](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-aggregation/rowAggregationFeature.types.ts#L332) diff --git a/docs/reference/index/interfaces/TableState_All.md b/docs/reference/index/interfaces/TableState_All.md index 861b57d2ec..e93a3e3ac7 100644 --- a/docs/reference/index/interfaces/TableState_All.md +++ b/docs/reference/index/interfaces/TableState_All.md @@ -5,7 +5,7 @@ title: TableState_All # Interface: TableState\_All -Defined in: [types/TableState.ts:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L47) +Defined in: [types/TableState.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L49) Internal broad state shape containing every registered feature state slice. @@ -14,10 +14,24 @@ by other features. ## Extends -- `Partial`\<[`TableState_ColumnFiltering`](TableState_ColumnFiltering.md) & [`TableState_ColumnGrouping`](TableState_ColumnGrouping.md) & [`TableState_ColumnOrdering`](TableState_ColumnOrdering.md) & [`TableState_ColumnPinning`](TableState_ColumnPinning.md) & [`TableState_ColumnResizing`](TableState_ColumnResizing.md) & [`TableState_ColumnSizing`](TableState_ColumnSizing.md) & [`TableState_ColumnVisibility`](TableState_ColumnVisibility.md) & [`TableState_GlobalFiltering`](TableState_GlobalFiltering.md) & [`TableState_RowExpanding`](TableState_RowExpanding.md) & [`TableState_RowPagination`](TableState_RowPagination.md) & [`TableState_RowPinning`](TableState_RowPinning.md) & [`TableState_RowSelection`](TableState_RowSelection.md) & [`TableState_RowSorting`](TableState_RowSorting.md)\> +- `Partial`\<[`TableState_CellSelection`](TableState_CellSelection.md) & [`TableState_ColumnFiltering`](TableState_ColumnFiltering.md) & [`TableState_ColumnGrouping`](TableState_ColumnGrouping.md) & [`TableState_ColumnOrdering`](TableState_ColumnOrdering.md) & [`TableState_ColumnPinning`](TableState_ColumnPinning.md) & [`TableState_ColumnResizing`](TableState_ColumnResizing.md) & [`TableState_ColumnSizing`](TableState_ColumnSizing.md) & [`TableState_ColumnVisibility`](TableState_ColumnVisibility.md) & [`TableState_GlobalFiltering`](TableState_GlobalFiltering.md) & [`TableState_RowExpanding`](TableState_RowExpanding.md) & [`TableState_RowPagination`](TableState_RowPagination.md) & [`TableState_RowPinning`](TableState_RowPinning.md) & [`TableState_RowSelection`](TableState_RowSelection.md) & [`TableState_RowSorting`](TableState_RowSorting.md)\> ## Properties +### cellSelection? + +```ts +optional cellSelection: CellSelectionState; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L37) + +#### Inherited from + +[`TableState_CellSelection`](TableState_CellSelection.md).[`cellSelection`](TableState_CellSelection.md#cellselection) + +*** + ### columnFilters? ```ts diff --git a/docs/reference/index/interfaces/TableState_CellSelection.md b/docs/reference/index/interfaces/TableState_CellSelection.md new file mode 100644 index 0000000000..4dae993589 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_CellSelection.md @@ -0,0 +1,18 @@ +--- +id: TableState_CellSelection +title: TableState_CellSelection +--- + +# Interface: TableState\_CellSelection + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L36) + +## Properties + +### cellSelection + +```ts +cellSelection: CellSelectionState; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L37) diff --git a/docs/reference/index/interfaces/TableState_FeatureMap.md b/docs/reference/index/interfaces/TableState_FeatureMap.md index c67d09298d..023c56b255 100644 --- a/docs/reference/index/interfaces/TableState_FeatureMap.md +++ b/docs/reference/index/interfaces/TableState_FeatureMap.md @@ -5,17 +5,27 @@ title: TableState_FeatureMap # Interface: TableState\_FeatureMap -Defined in: [types/TableState.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L16) +Defined in: [types/TableState.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L17) ## Properties +### cellSelectionFeature + +```ts +cellSelectionFeature: TableState_CellSelection; +``` + +Defined in: [types/TableState.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L18) + +*** + ### columnFilteringFeature ```ts columnFilteringFeature: TableState_ColumnFiltering; ``` -Defined in: [types/TableState.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L17) +Defined in: [types/TableState.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L19) *** @@ -25,7 +35,7 @@ Defined in: [types/TableState.ts:17](https://github.com/TanStack/table/blob/main columnGroupingFeature: TableState_ColumnGrouping; ``` -Defined in: [types/TableState.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L18) +Defined in: [types/TableState.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L20) *** @@ -35,7 +45,7 @@ Defined in: [types/TableState.ts:18](https://github.com/TanStack/table/blob/main columnOrderingFeature: TableState_ColumnOrdering; ``` -Defined in: [types/TableState.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L19) +Defined in: [types/TableState.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L21) *** @@ -45,7 +55,7 @@ Defined in: [types/TableState.ts:19](https://github.com/TanStack/table/blob/main columnPinningFeature: TableState_ColumnPinning; ``` -Defined in: [types/TableState.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L20) +Defined in: [types/TableState.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L22) *** @@ -55,7 +65,7 @@ Defined in: [types/TableState.ts:20](https://github.com/TanStack/table/blob/main columnResizingFeature: TableState_ColumnResizing; ``` -Defined in: [types/TableState.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L21) +Defined in: [types/TableState.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L23) *** @@ -65,7 +75,7 @@ Defined in: [types/TableState.ts:21](https://github.com/TanStack/table/blob/main columnSizingFeature: TableState_ColumnSizing; ``` -Defined in: [types/TableState.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L22) +Defined in: [types/TableState.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L24) *** @@ -75,7 +85,7 @@ Defined in: [types/TableState.ts:22](https://github.com/TanStack/table/blob/main columnVisibilityFeature: TableState_ColumnVisibility; ``` -Defined in: [types/TableState.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L23) +Defined in: [types/TableState.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L25) *** @@ -85,7 +95,7 @@ Defined in: [types/TableState.ts:23](https://github.com/TanStack/table/blob/main globalFilteringFeature: TableState_GlobalFiltering; ``` -Defined in: [types/TableState.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L24) +Defined in: [types/TableState.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L26) *** @@ -95,7 +105,7 @@ Defined in: [types/TableState.ts:24](https://github.com/TanStack/table/blob/main rowExpandingFeature: TableState_RowExpanding; ``` -Defined in: [types/TableState.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L25) +Defined in: [types/TableState.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L27) *** @@ -105,7 +115,7 @@ Defined in: [types/TableState.ts:25](https://github.com/TanStack/table/blob/main rowPaginationFeature: TableState_RowPagination; ``` -Defined in: [types/TableState.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L26) +Defined in: [types/TableState.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L28) *** @@ -115,7 +125,7 @@ Defined in: [types/TableState.ts:26](https://github.com/TanStack/table/blob/main rowPinningFeature: TableState_RowPinning; ``` -Defined in: [types/TableState.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L27) +Defined in: [types/TableState.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L29) *** @@ -125,7 +135,7 @@ Defined in: [types/TableState.ts:27](https://github.com/TanStack/table/blob/main rowSelectionFeature: TableState_RowSelection; ``` -Defined in: [types/TableState.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L28) +Defined in: [types/TableState.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L30) *** @@ -135,7 +145,7 @@ Defined in: [types/TableState.ts:28](https://github.com/TanStack/table/blob/main rowSortingFeature: TableState_RowSorting; ``` -Defined in: [types/TableState.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L29) +Defined in: [types/TableState.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L31) *** diff --git a/docs/reference/index/interfaces/Table_CellSelection.md b/docs/reference/index/interfaces/Table_CellSelection.md new file mode 100644 index 0000000000..5e4b5cff47 --- /dev/null +++ b/docs/reference/index/interfaces/Table_CellSelection.md @@ -0,0 +1,363 @@ +--- +id: Table_CellSelection +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) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_isSelectingCells + +```ts +_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) + +**`Internal`** + +Whether a drag selection is currently open. + +Non-reactive instance data rather than state: only the mouse handlers read +it, nothing renders from it, and keeping it out of the slice means a +selection persisted mid-drag cannot rehydrate into a stuck drag. + +*** + +### autoResetCellSelection() + +```ts +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) + +Schedules a cell selection reset after `data` changes. + +Honors `autoResetAll` and `autoResetCellSelection`. Called by the core row +model; you rarely need to invoke it yourself. + +#### Returns + +`void` + +*** + +### extendCellSelection() + +```ts +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) + +Extends the active range one step in a direction, keeping its anchor fixed. + +#### Parameters + +##### direction + +[`CellSelectionDirection`](../type-aliases/CellSelectionDirection.md) + +#### Returns + +`void` + +*** + +### getCellSelectionBounds() + +```ts +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) + +Returns the selected ranges resolved into inclusive display-order indexes. + +This is the memoized cache every per-cell read goes through. Ranges whose +corners no longer resolve are omitted. + +#### Returns + +[`CellSelectionBounds`](CellSelectionBounds.md)[] + +*** + +### getCellSelectionColumnIds() + +```ts +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) + +Returns the ids of all columns intersected by the selection. + +#### Returns + +`string`[] + +*** + +### getCellSelectionColumnIndexes() + +```ts +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) + +**`Internal`** + +Returns a column id to display-index map for the current column order. + +Registered so the lookup stays memoized even when `columnOrderingFeature` +is absent, since its `getColumnIndexes` static rebuilds on every call. + +#### Returns + +`Record`\<`string`, `number`\> + +*** + +### getCellSelectionRowIds() + +```ts +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) + +Returns the ids of all rows intersected by the selection. + +#### Returns + +`string`[] + +*** + +### getFocusedCell() + +```ts +getFocusedCell: () => + | Cell + | 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) + +Returns the active cell, i.e. the anchor of the most recent range. + +#### Returns + + \| [`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `any`\> + \| `undefined` + +*** + +### getSelectedCellCount() + +```ts +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) + +Returns the number of selected cells. + +Computed as rectangle arithmetic. Falls back to enumerating cells when +`enableCellSelection` is a per-cell predicate. + +#### Returns + +`number` + +*** + +### getSelectedCellIds() + +```ts +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) + +Returns the ids of all selected cells, in row-major order per range. + +This expands the selection, so it costs one pass over the selected area. +It is memoized and never runs unless called. + +#### Returns + +`string`[] + +*** + +### getSelectedCellRangesData() + +```ts +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) + +Returns each selected range's values as a row-major grid. + +Indexed as `[rangeIndex][rowIndex][columnIndex]`. Serializing this to +clipboard text is left to userland, since the delimiter, the null +representation, and any quoting rules are application decisions. + +#### Returns + +`unknown`[][][] + +*** + +### moveCellSelection() + +```ts +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) + +Moves the selection one step in a direction, collapsing it to a single +cell. Columns that cannot be selected are skipped over. + +#### Parameters + +##### direction + +[`CellSelectionDirection`](../type-aliases/CellSelectionDirection.md) + +#### Returns + +`void` + +*** + +### resetCellSelection() + +```ts +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) + +Resets `cellSelection` to `initialState.cellSelection`. + +Pass `true` to ignore initial state and reset to an empty selection. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### selectAllCells() + +```ts +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) + +Selects every selectable cell in the table as one range. + +#### Returns + +`void` + +*** + +### selectCellRange() + +```ts +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) + +Selects a rectangle, replacing the current selection unless `additive`. + +#### Parameters + +##### range + +[`CellSelectionRange`](CellSelectionRange.md) + +##### opts? + +[`SelectCellRangeOptions`](SelectCellRangeOptions.md) + +#### Returns + +`void` + +*** + +### setCellSelection() + +```ts +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) + +Updates cell selection state with a next value or updater function. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`CellSelectionState`](../type-aliases/CellSelectionState.md)\> + +#### Returns + +`void` + +*** + +### setFocusedCell() + +```ts +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) + +Collapses the selection to a single cell at the given coordinates. + +#### Parameters + +##### rowId + +`string` + +##### columnId + +`string` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_Core.md b/docs/reference/index/interfaces/Table_Core.md index bb055303ca..744ad71ce9 100644 --- a/docs/reference/index/interfaces/Table_Core.md +++ b/docs/reference/index/interfaces/Table_Core.md @@ -5,7 +5,7 @@ title: Table_Core # Interface: Table\_Core\ -Defined in: [types/Table.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L39) +Defined in: [types/Table.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L40) The core table object that only includes the core table functionality such as column, header, row, and table APIS. No features are included. diff --git a/docs/reference/index/interfaces/Table_FeatureMap.md b/docs/reference/index/interfaces/Table_FeatureMap.md index b9a5d06866..b28da15e01 100644 --- a/docs/reference/index/interfaces/Table_FeatureMap.md +++ b/docs/reference/index/interfaces/Table_FeatureMap.md @@ -5,7 +5,7 @@ title: Table_FeatureMap # Interface: Table\_FeatureMap\ -Defined in: [types/Table.ts:50](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L50) +Defined in: [types/Table.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L51) ## Type Parameters @@ -19,13 +19,23 @@ Defined in: [types/Table.ts:50](https://github.com/TanStack/table/blob/main/pack ## Properties +### cellSelectionFeature + +```ts +cellSelectionFeature: Table_CellSelection; +``` + +Defined in: [types/Table.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L55) + +*** + ### columnFacetingFeature ```ts columnFacetingFeature: Table_ColumnFaceting; ``` -Defined in: [types/Table.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L61) +Defined in: [types/Table.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L56) *** @@ -35,7 +45,7 @@ Defined in: [types/Table.ts:61](https://github.com/TanStack/table/blob/main/pack columnFilteringFeature: Table_ColumnFiltering; ``` -Defined in: [types/Table.ts:54](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L54) +Defined in: [types/Table.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L57) *** @@ -45,7 +55,7 @@ Defined in: [types/Table.ts:54](https://github.com/TanStack/table/blob/main/pack columnGroupingFeature: Table_ColumnGrouping; ``` -Defined in: [types/Table.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L55) +Defined in: [types/Table.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L58) *** @@ -55,7 +65,7 @@ Defined in: [types/Table.ts:55](https://github.com/TanStack/table/blob/main/pack columnOrderingFeature: Table_ColumnOrdering; ``` -Defined in: [types/Table.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L56) +Defined in: [types/Table.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L59) *** @@ -65,7 +75,7 @@ Defined in: [types/Table.ts:56](https://github.com/TanStack/table/blob/main/pack columnPinningFeature: Table_ColumnPinning; ``` -Defined in: [types/Table.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L57) +Defined in: [types/Table.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L60) *** @@ -75,7 +85,7 @@ Defined in: [types/Table.ts:57](https://github.com/TanStack/table/blob/main/pack columnResizingFeature: Table_ColumnResizing; ``` -Defined in: [types/Table.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L58) +Defined in: [types/Table.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L61) *** @@ -85,7 +95,7 @@ Defined in: [types/Table.ts:58](https://github.com/TanStack/table/blob/main/pack columnSizingFeature: Table_ColumnSizing; ``` -Defined in: [types/Table.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L59) +Defined in: [types/Table.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L62) *** @@ -95,7 +105,7 @@ Defined in: [types/Table.ts:59](https://github.com/TanStack/table/blob/main/pack columnVisibilityFeature: Table_ColumnVisibility; ``` -Defined in: [types/Table.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L60) +Defined in: [types/Table.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L63) *** @@ -105,7 +115,7 @@ Defined in: [types/Table.ts:60](https://github.com/TanStack/table/blob/main/pack globalFilteringFeature: Table_GlobalFiltering; ``` -Defined in: [types/Table.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L62) +Defined in: [types/Table.ts:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L64) *** @@ -115,7 +125,7 @@ Defined in: [types/Table.ts:62](https://github.com/TanStack/table/blob/main/pack rowExpandingFeature: Table_RowExpanding; ``` -Defined in: [types/Table.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L63) +Defined in: [types/Table.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L65) *** @@ -125,7 +135,7 @@ Defined in: [types/Table.ts:63](https://github.com/TanStack/table/blob/main/pack rowPaginationFeature: Table_RowPagination; ``` -Defined in: [types/Table.ts:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L64) +Defined in: [types/Table.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L66) *** @@ -135,7 +145,7 @@ Defined in: [types/Table.ts:64](https://github.com/TanStack/table/blob/main/pack rowPinningFeature: Table_RowPinning; ``` -Defined in: [types/Table.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L65) +Defined in: [types/Table.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L67) *** @@ -145,7 +155,7 @@ Defined in: [types/Table.ts:65](https://github.com/TanStack/table/blob/main/pack rowSelectionFeature: Table_RowSelection; ``` -Defined in: [types/Table.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L66) +Defined in: [types/Table.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L68) *** @@ -155,4 +165,4 @@ Defined in: [types/Table.ts:66](https://github.com/TanStack/table/blob/main/pack rowSortingFeature: Table_RowSorting; ``` -Defined in: [types/Table.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L67) +Defined in: [types/Table.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L69) diff --git a/docs/reference/index/interfaces/Table_Internal.md b/docs/reference/index/interfaces/Table_Internal.md index 9fd49471da..16398441e5 100644 --- a/docs/reference/index/interfaces/Table_Internal.md +++ b/docs/reference/index/interfaces/Table_Internal.md @@ -5,7 +5,7 @@ title: Table_Internal # Interface: Table\_Internal\ -Defined in: [types/Table.ts:94](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L94) +Defined in: [types/Table.ts:96](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L96) Internal broad table shape used by feature implementations. @@ -315,7 +315,7 @@ Omit._rowInstanceInitFns _rowModelFns: RowModelFns_All; ``` -Defined in: [types/Table.ts:105](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L105) +Defined in: [types/Table.ts:107](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L107) *** @@ -325,7 +325,7 @@ Defined in: [types/Table.ts:105](https://github.com/TanStack/table/blob/main/pac _rowModels: CachedRowModel_All; ``` -Defined in: [types/Table.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L104) +Defined in: [types/Table.ts:106](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L106) *** @@ -351,7 +351,7 @@ Prototype cache for Row objects - shared by all rows in this table atoms: Atoms & Atoms_All; ``` -Defined in: [types/Table.ts:114](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L114) +Defined in: [types/Table.ts:116](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L116) The readonly derived atoms for each `TableState` slice. Each derives from its corresponding `baseAtom` plus, optionally, a per-slice external atom or @@ -371,7 +371,7 @@ Omit.atoms baseAtoms: BaseAtoms & BaseAtoms_All; ``` -Defined in: [types/Table.ts:113](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L113) +Defined in: [types/Table.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L115) The internal writable atoms for each `TableState` slice. This is the library's single write surface — all state mutations from features land here. @@ -1065,17 +1065,17 @@ Table_RowModels.getSortedRowModel initialState: ExtractFeatureMapTypes & TableState_All; ``` -Defined in: [types/Table.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L112) +Defined in: [types/Table.ts:114](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L114) *** ### options ```ts -options: object & DebugKeysFor & TableOptions_Core & Partial & TableOptions_ColumnGrouping & TableOptions_ColumnOrdering & TableOptions_ColumnPinning & TableOptions_ColumnResizing & TableOptions_ColumnSizing & TableOptions_ColumnVisibility & TableOptions_GlobalFiltering & TableOptions_RowExpanding & TableOptions_RowPagination & TableOptions_RowPinning & TableOptions_RowSelection & TableOptions_RowSorting> & object; +options: object & DebugKeysFor & TableOptions_Core & Partial & TableOptions_ColumnFiltering & TableOptions_ColumnGrouping & TableOptions_ColumnOrdering & TableOptions_ColumnPinning & TableOptions_ColumnResizing & TableOptions_ColumnSizing & TableOptions_ColumnVisibility & TableOptions_GlobalFiltering & TableOptions_RowExpanding & TableOptions_RowPagination & TableOptions_RowPinning & TableOptions_RowSelection & TableOptions_RowSorting> & object; ``` -Defined in: [types/Table.ts:106](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L106) +Defined in: [types/Table.ts:108](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L108) #### Type Declaration @@ -1127,6 +1127,7 @@ optional debugTable: boolean; ```ts optional atoms: Partial<{ + cellSelection?: Atom; columnFilters?: Atom; columnOrder?: Atom; columnPinning?: Atom; @@ -1239,4 +1240,4 @@ Omit.setOptions store: ReadonlyStore> & ReadonlyStore; ``` -Defined in: [types/Table.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L115) +Defined in: [types/Table.ts:117](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L117) diff --git a/docs/reference/index/type-aliases/AccessorColumnDef.md b/docs/reference/index/type-aliases/AccessorColumnDef.md index 42bd25e964..ff7224ad0b 100644 --- a/docs/reference/index/type-aliases/AccessorColumnDef.md +++ b/docs/reference/index/type-aliases/AccessorColumnDef.md @@ -11,7 +11,7 @@ type AccessorColumnDef = | AccessorFnColumnDef; ``` -Defined in: [types/ColumnDef.ts:235](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L235) +Defined in: [types/ColumnDef.ts:238](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L238) ## Type Parameters diff --git a/docs/reference/index/type-aliases/AccessorFn.md b/docs/reference/index/type-aliases/AccessorFn.md index 3efc0325f1..c6827091a3 100644 --- a/docs/reference/index/type-aliases/AccessorFn.md +++ b/docs/reference/index/type-aliases/AccessorFn.md @@ -9,7 +9,7 @@ title: AccessorFn type AccessorFn = (originalRow, index) => TValue; ``` -Defined in: [types/ColumnDef.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L51) +Defined in: [types/ColumnDef.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L52) Reads a cell value from an original row object. diff --git a/docs/reference/index/type-aliases/AccessorFnColumnDef.md b/docs/reference/index/type-aliases/AccessorFnColumnDef.md index 39f4a6a30e..d4a76d2935 100644 --- a/docs/reference/index/type-aliases/AccessorFnColumnDef.md +++ b/docs/reference/index/type-aliases/AccessorFnColumnDef.md @@ -9,7 +9,7 @@ title: AccessorFnColumnDef type AccessorFnColumnDef = AccessorFnColumnDefBase & ColumnIdentifiers; ``` -Defined in: [types/ColumnDef.ts:212](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L212) +Defined in: [types/ColumnDef.ts:215](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L215) ## Type Parameters diff --git a/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md b/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md index 5c996341b2..f975b7903d 100644 --- a/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md +++ b/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md @@ -9,7 +9,7 @@ title: AccessorFnColumnDefBase type AccessorFnColumnDefBase = ColumnDefBase & object; ``` -Defined in: [types/ColumnDef.ts:204](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L204) +Defined in: [types/ColumnDef.ts:207](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L207) ## Type Declaration diff --git a/docs/reference/index/type-aliases/AccessorKeyColumnDef.md b/docs/reference/index/type-aliases/AccessorKeyColumnDef.md index 7294b75bab..8bc2e60ff2 100644 --- a/docs/reference/index/type-aliases/AccessorKeyColumnDef.md +++ b/docs/reference/index/type-aliases/AccessorKeyColumnDef.md @@ -9,7 +9,7 @@ title: AccessorKeyColumnDef type AccessorKeyColumnDef = AccessorKeyColumnDefBase & Partial>; ``` -Defined in: [types/ColumnDef.ts:228](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L228) +Defined in: [types/ColumnDef.ts:231](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L231) ## Type Parameters diff --git a/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md b/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md index a6e8466864..40cdcc06e5 100644 --- a/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md +++ b/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md @@ -9,7 +9,7 @@ title: AccessorKeyColumnDefBase type AccessorKeyColumnDefBase = ColumnDefBase & object; ``` -Defined in: [types/ColumnDef.ts:219](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L219) +Defined in: [types/ColumnDef.ts:222](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L222) ## Type Declaration diff --git a/docs/reference/index/type-aliases/Cell.md b/docs/reference/index/type-aliases/Cell.md index 96042fd3b6..fb078091a6 100644 --- a/docs/reference/index/type-aliases/Cell.md +++ b/docs/reference/index/type-aliases/Cell.md @@ -9,7 +9,7 @@ title: Cell type Cell = Cell_Core & ExtractFeatureMapTypes; ``` -Defined in: [types/Cell.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L18) +Defined in: [types/Cell.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L20) ## Type Parameters diff --git a/docs/reference/index/type-aliases/CellSelectionDirection.md b/docs/reference/index/type-aliases/CellSelectionDirection.md new file mode 100644 index 0000000000..9293e6e8a5 --- /dev/null +++ b/docs/reference/index/type-aliases/CellSelectionDirection.md @@ -0,0 +1,12 @@ +--- +id: CellSelectionDirection +title: CellSelectionDirection +--- + +# Type Alias: CellSelectionDirection + +```ts +type CellSelectionDirection = "up" | "down" | "left" | "right"; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.types.ts#L63) diff --git a/docs/reference/index/type-aliases/CellSelectionState.md b/docs/reference/index/type-aliases/CellSelectionState.md new file mode 100644 index 0000000000..e2639ba564 --- /dev/null +++ b/docs/reference/index/type-aliases/CellSelectionState.md @@ -0,0 +1,19 @@ +--- +id: CellSelectionState +title: CellSelectionState +--- + +# Type Alias: CellSelectionState + +```ts +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) + +The selected rectangles. The last entry is the active one 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 +nothing here is transient and the whole slice is safe to persist. diff --git a/docs/reference/index/type-aliases/ColumnDef.md b/docs/reference/index/type-aliases/ColumnDef.md index 03fe75515d..c95a1abbed 100644 --- a/docs/reference/index/type-aliases/ColumnDef.md +++ b/docs/reference/index/type-aliases/ColumnDef.md @@ -12,7 +12,7 @@ type ColumnDef = | AccessorColumnDef; ``` -Defined in: [types/ColumnDef.ts:243](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L243) +Defined in: [types/ColumnDef.ts:246](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L246) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ColumnDefBase.md b/docs/reference/index/type-aliases/ColumnDefBase.md index dd701ad750..5a8b281dbd 100644 --- a/docs/reference/index/type-aliases/ColumnDefBase.md +++ b/docs/reference/index/type-aliases/ColumnDefBase.md @@ -9,7 +9,7 @@ title: ColumnDefBase type ColumnDefBase = ColumnDefBase_Core & ExtractFeatureMapTypes>; ``` -Defined in: [types/ColumnDef.ts:147](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L147) +Defined in: [types/ColumnDef.ts:149](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L149) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ColumnDefBase_All.md b/docs/reference/index/type-aliases/ColumnDefBase_All.md index 163842928d..628c5874ca 100644 --- a/docs/reference/index/type-aliases/ColumnDefBase_All.md +++ b/docs/reference/index/type-aliases/ColumnDefBase_All.md @@ -6,10 +6,10 @@ title: ColumnDefBase_All # Type Alias: ColumnDefBase\_All\ ```ts -type ColumnDefBase_All = ColumnDefBase_Core & Partial & ColumnDef_ColumnVisibility & ColumnDef_ColumnPinning & ColumnDef_ColumnFiltering & ColumnDef_GlobalFiltering & ColumnDef_RowSorting & ColumnDef_ColumnGrouping & ColumnDef_ColumnSizing & ColumnDef_ColumnResizing>; +type ColumnDefBase_All = ColumnDefBase_Core & Partial & ColumnDef_CellSelection & ColumnDef_ColumnVisibility & ColumnDef_ColumnPinning & ColumnDef_ColumnFiltering & ColumnDef_GlobalFiltering & ColumnDef_RowSorting & ColumnDef_ColumnGrouping & ColumnDef_ColumnSizing & ColumnDef_ColumnResizing>; ``` -Defined in: [types/ColumnDef.ts:157](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L157) +Defined in: [types/ColumnDef.ts:159](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L159) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ColumnDefResolved.md b/docs/reference/index/type-aliases/ColumnDefResolved.md index 0dfa25fb6c..301cf28891 100644 --- a/docs/reference/index/type-aliases/ColumnDefResolved.md +++ b/docs/reference/index/type-aliases/ColumnDefResolved.md @@ -9,7 +9,7 @@ title: ColumnDefResolved type ColumnDefResolved = Partial>> & object; ``` -Defined in: [types/ColumnDef.ts:252](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L252) +Defined in: [types/ColumnDef.ts:255](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L255) ## Type Declaration diff --git a/docs/reference/index/type-aliases/ColumnDefTemplate.md b/docs/reference/index/type-aliases/ColumnDefTemplate.md index 5dc24fca4e..32677765d3 100644 --- a/docs/reference/index/type-aliases/ColumnDefTemplate.md +++ b/docs/reference/index/type-aliases/ColumnDefTemplate.md @@ -9,7 +9,7 @@ title: ColumnDefTemplate type ColumnDefTemplate = string | (props) => any; ``` -Defined in: [types/ColumnDef.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L62) +Defined in: [types/ColumnDef.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L63) A renderable column template value. diff --git a/docs/reference/index/type-aliases/DebugOptions.md b/docs/reference/index/type-aliases/DebugOptions.md index 78adf506f7..7819ee894c 100644 --- a/docs/reference/index/type-aliases/DebugOptions.md +++ b/docs/reference/index/type-aliases/DebugOptions.md @@ -9,7 +9,7 @@ title: DebugOptions type DebugOptions = object & DebugKeysFor; ``` -Defined in: [types/TableOptions.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L48) +Defined in: [types/TableOptions.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L49) ## Type Declaration diff --git a/docs/reference/index/type-aliases/DeepValue.md b/docs/reference/index/type-aliases/DeepValue.md index e268f6dd83..f6f0473715 100644 --- a/docs/reference/index/type-aliases/DeepValue.md +++ b/docs/reference/index/type-aliases/DeepValue.md @@ -6,7 +6,7 @@ title: DeepValue # Type Alias: DeepValue\ ```ts -type DeepValue = T extends Record ? TProp extends `${infer TBranch}.${infer TDeepProp}` ? DeepValue : T[TProp & keyof T] : never; +type DeepValue = T extends null | undefined ? undefined : T extends Record ? TProp extends `${infer TBranch}.${infer TDeepProp}` ? DeepValue : T[TProp & keyof T] : never; ``` Defined in: [types/type-utils.ts:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L79) diff --git a/docs/reference/index/type-aliases/DisplayColumnDef.md b/docs/reference/index/type-aliases/DisplayColumnDef.md index fb2b5d8229..c58d8fe206 100644 --- a/docs/reference/index/type-aliases/DisplayColumnDef.md +++ b/docs/reference/index/type-aliases/DisplayColumnDef.md @@ -9,7 +9,7 @@ title: DisplayColumnDef type DisplayColumnDef = ColumnDefBase & ColumnIdentifiers; ``` -Defined in: [types/ColumnDef.ts:183](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L183) +Defined in: [types/ColumnDef.ts:186](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L186) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ExtractColumnMeta.md b/docs/reference/index/type-aliases/ExtractColumnMeta.md index e8e3ef4842..8572de59c5 100644 --- a/docs/reference/index/type-aliases/ExtractColumnMeta.md +++ b/docs/reference/index/type-aliases/ExtractColumnMeta.md @@ -9,7 +9,7 @@ title: ExtractColumnMeta type ExtractColumnMeta = IsAny extends true ? ColumnMeta : TFeatures extends object ? TMeta : ColumnMeta; ``` -Defined in: [types/ColumnDef.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L34) +Defined in: [types/ColumnDef.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L35) Resolves the type of `columnDef.meta` for a feature set. diff --git a/docs/reference/index/type-aliases/Getter.md b/docs/reference/index/type-aliases/Getter.md index 0001b0eec2..76b5d0c91d 100644 --- a/docs/reference/index/type-aliases/Getter.md +++ b/docs/reference/index/type-aliases/Getter.md @@ -9,7 +9,7 @@ title: Getter type Getter = () => NoInfer; ``` -Defined in: [types/type-utils.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L88) +Defined in: [types/type-utils.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L89) ## Type Parameters diff --git a/docs/reference/index/type-aliases/GroupColumnDef.md b/docs/reference/index/type-aliases/GroupColumnDef.md index 4a229873e0..041f4c42d3 100644 --- a/docs/reference/index/type-aliases/GroupColumnDef.md +++ b/docs/reference/index/type-aliases/GroupColumnDef.md @@ -9,7 +9,7 @@ title: GroupColumnDef type GroupColumnDef = GroupColumnDefBase & ColumnIdentifiers; ``` -Defined in: [types/ColumnDef.ts:197](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L197) +Defined in: [types/ColumnDef.ts:200](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L200) ## Type Parameters diff --git a/docs/reference/index/type-aliases/IdentifiedColumnDef.md b/docs/reference/index/type-aliases/IdentifiedColumnDef.md index 3e72c5364a..005f427309 100644 --- a/docs/reference/index/type-aliases/IdentifiedColumnDef.md +++ b/docs/reference/index/type-aliases/IdentifiedColumnDef.md @@ -9,7 +9,7 @@ title: IdentifiedColumnDef type IdentifiedColumnDef = ColumnDefBase & object; ``` -Defined in: [types/ColumnDef.ts:174](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L174) +Defined in: [types/ColumnDef.ts:177](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L177) ## Type Declaration diff --git a/docs/reference/index/type-aliases/NoInfer.md b/docs/reference/index/type-aliases/NoInfer.md index 48925eb990..4b3a015142 100644 --- a/docs/reference/index/type-aliases/NoInfer.md +++ b/docs/reference/index/type-aliases/NoInfer.md @@ -9,7 +9,7 @@ title: NoInfer type NoInfer = [T][T extends any ? 0 : never]; ``` -Defined in: [types/type-utils.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L86) +Defined in: [types/type-utils.ts:87](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L87) ## Type Parameters diff --git a/docs/reference/index/type-aliases/Prettify.md b/docs/reference/index/type-aliases/Prettify.md index bd22d7cbe7..41b0e32cc4 100644 --- a/docs/reference/index/type-aliases/Prettify.md +++ b/docs/reference/index/type-aliases/Prettify.md @@ -9,7 +9,7 @@ title: Prettify type Prettify = { [K in keyof T]: T[K] } & unknown; ``` -Defined in: [types/type-utils.ts:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L90) +Defined in: [types/type-utils.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L91) ## Type Parameters diff --git a/docs/reference/index/type-aliases/StringOrTemplateHeader.md b/docs/reference/index/type-aliases/StringOrTemplateHeader.md index 9f1ac9a3c0..dbd9378fc7 100644 --- a/docs/reference/index/type-aliases/StringOrTemplateHeader.md +++ b/docs/reference/index/type-aliases/StringOrTemplateHeader.md @@ -11,7 +11,7 @@ type StringOrTemplateHeader = | ColumnDefTemplate>; ``` -Defined in: [types/ColumnDef.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L66) +Defined in: [types/ColumnDef.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L67) ## Type Parameters diff --git a/docs/reference/index/type-aliases/Table.md b/docs/reference/index/type-aliases/Table.md index 63395042ed..2bbfee87c3 100644 --- a/docs/reference/index/type-aliases/Table.md +++ b/docs/reference/index/type-aliases/Table.md @@ -9,7 +9,7 @@ title: Table type Table = Table_Core & ExtractFeatureMapTypes>; ``` -Defined in: [types/Table.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L73) +Defined in: [types/Table.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L75) The table object that includes both the core table functionality and the features that are enabled via the `features` table option. diff --git a/docs/reference/index/type-aliases/TableOptions.md b/docs/reference/index/type-aliases/TableOptions.md index b170bea078..1c8b57bb5c 100644 --- a/docs/reference/index/type-aliases/TableOptions.md +++ b/docs/reference/index/type-aliases/TableOptions.md @@ -9,7 +9,7 @@ title: TableOptions type TableOptions = TableOptions_Core & ExtractFeatureMapTypes> & DebugOptions; ``` -Defined in: [types/TableOptions.ts:123](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L123) +Defined in: [types/TableOptions.ts:126](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L126) Complete table options for a specific feature set. diff --git a/docs/reference/index/type-aliases/TableOptions_All.md b/docs/reference/index/type-aliases/TableOptions_All.md index 3ccfa81774..128546cd7f 100644 --- a/docs/reference/index/type-aliases/TableOptions_All.md +++ b/docs/reference/index/type-aliases/TableOptions_All.md @@ -6,10 +6,10 @@ title: TableOptions_All # Type Alias: TableOptions\_All\ ```ts -type TableOptions_All = TableOptions_Core & Partial & TableOptions_ColumnGrouping & TableOptions_ColumnOrdering & TableOptions_ColumnPinning & TableOptions_ColumnResizing & TableOptions_ColumnSizing & TableOptions_ColumnVisibility & TableOptions_GlobalFiltering & TableOptions_RowExpanding & TableOptions_RowPagination & TableOptions_RowPinning & TableOptions_RowSelection & TableOptions_RowSorting & TableOptions_PluginFeatureMapTypes>; +type TableOptions_All = TableOptions_Core & Partial & TableOptions_ColumnFiltering & TableOptions_ColumnGrouping & TableOptions_ColumnOrdering & TableOptions_ColumnPinning & TableOptions_ColumnResizing & TableOptions_ColumnSizing & TableOptions_ColumnVisibility & TableOptions_GlobalFiltering & TableOptions_RowExpanding & TableOptions_RowPagination & TableOptions_RowPinning & TableOptions_RowSelection & TableOptions_RowSorting & TableOptions_PluginFeatureMapTypes>; ``` -Defined in: [types/TableOptions.ts:134](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L134) +Defined in: [types/TableOptions.ts:137](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L137) Internal broad option shape used where feature code may need to read options from features that are not present in the current generic feature set. diff --git a/docs/reference/index/type-aliases/TableState.md b/docs/reference/index/type-aliases/TableState.md index 90b749abf3..2117edf08d 100644 --- a/docs/reference/index/type-aliases/TableState.md +++ b/docs/reference/index/type-aliases/TableState.md @@ -9,7 +9,7 @@ title: TableState type TableState = ExtractFeatureMapTypes; ``` -Defined in: [types/TableState.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L38) +Defined in: [types/TableState.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L40) Complete table state for a specific feature set. diff --git a/docs/reference/index/variables/cellSelectionFeature.md b/docs/reference/index/variables/cellSelectionFeature.md new file mode 100644 index 0000000000..975f8f178f --- /dev/null +++ b/docs/reference/index/variables/cellSelectionFeature.md @@ -0,0 +1,14 @@ +--- +id: cellSelectionFeature +title: cellSelectionFeature +--- + +# Variable: cellSelectionFeature + +```ts +const cellSelectionFeature: TableFeature; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.ts#L38) + +Feature that adds spreadsheet-style cell range selection state and APIs. diff --git a/docs/reference/index/variables/stockFeatures.md b/docs/reference/index/variables/stockFeatures.md index 9ac61cfb9c..e646517db7 100644 --- a/docs/reference/index/variables/stockFeatures.md +++ b/docs/reference/index/variables/stockFeatures.md @@ -9,7 +9,7 @@ title: stockFeatures const stockFeatures: StockFeatures; ``` -Defined in: [features/stockFeatures.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L40) +Defined in: [features/stockFeatures.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L42) The complete set of stock optional table features. diff --git a/docs/reference/static-functions/functions/cell_getCanSelect.md b/docs/reference/static-functions/functions/cell_getCanSelect.md new file mode 100644 index 0000000000..95b40af5fb --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getCanSelect.md @@ -0,0 +1,47 @@ +--- +id: cell_getCanSelect +title: cell_getCanSelect +--- + +# Function: cell\_getCanSelect() + +```ts +function cell_getCanSelect(cell): boolean; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:322](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L322) + +Checks whether this cell can currently be selected. + +A column def opting out with `enableCellSelection: false` wins over the table +option, matching how the other per-column enable flags resolve. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const canSelect = cell_getCanSelect(cell) +``` diff --git a/docs/reference/static-functions/functions/cell_getIsFocused.md b/docs/reference/static-functions/functions/cell_getIsFocused.md new file mode 100644 index 0000000000..cf1787c7e3 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getIsFocused.md @@ -0,0 +1,44 @@ +--- +id: cell_getIsFocused +title: cell_getIsFocused +--- + +# Function: cell\_getIsFocused() + +```ts +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) + +Checks whether this cell is the active cell. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const isFocused = cell_getIsFocused(cell) +``` diff --git a/docs/reference/static-functions/functions/cell_getIsSelected.md b/docs/reference/static-functions/functions/cell_getIsSelected.md new file mode 100644 index 0000000000..645cbc9ce2 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getIsSelected.md @@ -0,0 +1,36 @@ +--- +id: cell_getIsSelected +title: cell_getIsSelected +--- + +# Function: cell\_getIsSelected() + +```ts +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) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` diff --git a/docs/reference/static-functions/functions/cell_getSelectionEdges.md b/docs/reference/static-functions/functions/cell_getSelectionEdges.md new file mode 100644 index 0000000000..de22f80db6 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getSelectionEdges.md @@ -0,0 +1,48 @@ +--- +id: cell_getSelectionEdges +title: cell_getSelectionEdges +--- + +# Function: cell\_getSelectionEdges() + +```ts +function cell_getSelectionEdges(cell): CellSelectionEdges; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:464](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L464) + +Returns which sides of this cell sit on the outer boundary of the selection. + +A side is an edge when the neighbouring cell in that direction is not itself +covered by a range, which is what lets a consumer draw a single outline +around an arbitrary union of rectangles. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +[`CellSelectionEdges`](../../index/interfaces/CellSelectionEdges.md) + +## Example + +```ts +const { top, right, bottom, left } = cell_getSelectionEdges(cell) +``` diff --git a/docs/reference/static-functions/functions/cell_getSelectionExtendHandler.md b/docs/reference/static-functions/functions/cell_getSelectionExtendHandler.md new file mode 100644 index 0000000000..300a82b078 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getSelectionExtendHandler.md @@ -0,0 +1,60 @@ +--- +id: cell_getSelectionExtendHandler +title: cell_getSelectionExtendHandler +--- + +# Function: cell\_getSelectionExtendHandler() + +```ts +function cell_getSelectionExtendHandler(cell): (_e) => void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:1098](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L1098) + +Creates a handler that extends the active range to this cell during a drag. + +No rAF coalescing is needed here, unlike the resize handler: `mouseenter` +fires once per cell boundary crossed rather than continuously, and deferring +it by a frame would only delay the highlight. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +```ts +(_e): void; +``` + +### Parameters + +#### \_e + +`unknown` + +### Returns + +`void` + +## Example + +```tsx + +``` diff --git a/docs/reference/static-functions/functions/cell_getSelectionStartHandler.md b/docs/reference/static-functions/functions/cell_getSelectionStartHandler.md new file mode 100644 index 0000000000..bcda7b78e5 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getSelectionStartHandler.md @@ -0,0 +1,65 @@ +--- +id: cell_getSelectionStartHandler +title: cell_getSelectionStartHandler +--- + +# Function: cell\_getSelectionStartHandler() + +```ts +function cell_getSelectionStartHandler(cell, _contextDocument?): (e) => void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:1023](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L1023) + +Creates a handler that begins a selection at this cell. + +Follows `header_getResizeHandler`: the enable check is resolved once outside +the returned closure and guarded again inside it, the document is injectable +for SSR and cross-document rendering, and the document-level `mouseup` +listener is attached here so a drag released outside the table still ends. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +### \_contextDocument? + +`Document` + +## Returns + +```ts +(e): void; +``` + +### Parameters + +#### e + +`unknown` + +### Returns + +`void` + +## Example + +```tsx + +``` diff --git a/docs/reference/static-functions/functions/cell_getTabIndex.md b/docs/reference/static-functions/functions/cell_getTabIndex.md new file mode 100644 index 0000000000..85df53852c --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getTabIndex.md @@ -0,0 +1,44 @@ +--- +id: cell_getTabIndex +title: cell_getTabIndex +--- + +# Function: cell\_getTabIndex() + +```ts +function cell_getTabIndex(cell): number; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:444](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L444) + +Returns `0` for the focused cell and `-1` otherwise, for roving tabindex. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`number` + +## Example + +```ts +const tabIndex = cell_getTabIndex(cell) +``` diff --git a/docs/reference/static-functions/functions/column_getCanFilter.md b/docs/reference/static-functions/functions/column_getCanFilter.md index 23d991e679..217daf2686 100644 --- a/docs/reference/static-functions/functions/column_getCanFilter.md +++ b/docs/reference/static-functions/functions/column_getCanFilter.md @@ -9,7 +9,7 @@ title: column_getCanFilter function column_getCanFilter(column): boolean; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:135](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L135) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L144) Checks whether column filtering is enabled for this accessor column. diff --git a/docs/reference/static-functions/functions/column_getFilterFn.md b/docs/reference/static-functions/functions/column_getFilterFn.md index e2d625f120..035655ef6b 100644 --- a/docs/reference/static-functions/functions/column_getFilterFn.md +++ b/docs/reference/static-functions/functions/column_getFilterFn.md @@ -11,7 +11,7 @@ function column_getFilterFn(column): | undefined; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L95) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L104) Resolves the filter function configured for a column. diff --git a/docs/reference/static-functions/functions/column_getFilterIndex.md b/docs/reference/static-functions/functions/column_getFilterIndex.md index 73848d4378..47962cb43a 100644 --- a/docs/reference/static-functions/functions/column_getFilterIndex.md +++ b/docs/reference/static-functions/functions/column_getFilterIndex.md @@ -9,7 +9,7 @@ title: column_getFilterIndex function column_getFilterIndex(column): number; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:197](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L197) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:206](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L206) Finds this column's position in the ordered `state.columnFilters` array. diff --git a/docs/reference/static-functions/functions/column_getFilterValue.md b/docs/reference/static-functions/functions/column_getFilterValue.md index 3d2eb7fd73..8443ea7412 100644 --- a/docs/reference/static-functions/functions/column_getFilterValue.md +++ b/docs/reference/static-functions/functions/column_getFilterValue.md @@ -9,7 +9,7 @@ title: column_getFilterValue function column_getFilterValue(column): unknown; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:177](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L177) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:186](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L186) Reads this column's current filter value from `state.columnFilters`. diff --git a/docs/reference/static-functions/functions/column_getIsFiltered.md b/docs/reference/static-functions/functions/column_getIsFiltered.md index 1de53cb98a..ff5dd51947 100644 --- a/docs/reference/static-functions/functions/column_getIsFiltered.md +++ b/docs/reference/static-functions/functions/column_getIsFiltered.md @@ -9,7 +9,7 @@ title: column_getIsFiltered function column_getIsFiltered(column): boolean; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:159](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L159) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:168](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L168) Checks whether this column currently has an entry in `state.columnFilters`. diff --git a/docs/reference/static-functions/functions/column_getToggleSortingHandler.md b/docs/reference/static-functions/functions/column_getToggleSortingHandler.md index 3b592724f2..5e5cbf5296 100644 --- a/docs/reference/static-functions/functions/column_getToggleSortingHandler.md +++ b/docs/reference/static-functions/functions/column_getToggleSortingHandler.md @@ -9,7 +9,7 @@ title: column_getToggleSortingHandler function column_getToggleSortingHandler(column): (e) => void; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:494](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L494) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:493](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L493) Creates a header event handler that toggles this column's sorting. diff --git a/docs/reference/static-functions/functions/column_setFilterValue.md b/docs/reference/static-functions/functions/column_setFilterValue.md index b9f04f7212..63e4690bc7 100644 --- a/docs/reference/static-functions/functions/column_setFilterValue.md +++ b/docs/reference/static-functions/functions/column_setFilterValue.md @@ -9,7 +9,7 @@ title: column_setFilterValue function column_setFilterValue(column, value): void; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:220](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L220) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:229](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L229) Adds, updates, or removes this column's filter value. diff --git a/docs/reference/static-functions/functions/getDefaultCellSelectionState.md b/docs/reference/static-functions/functions/getDefaultCellSelectionState.md new file mode 100644 index 0000000000..6119029f0e --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultCellSelectionState.md @@ -0,0 +1,27 @@ +--- +id: getDefaultCellSelectionState +title: getDefaultCellSelectionState +--- + +# Function: getDefaultCellSelectionState() + +```ts +function getDefaultCellSelectionState(): CellSelectionState; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L32) + +Creates the default cell selection state. + +The feature default is an empty selection. Reset APIs use this value when +`defaultState` is `true`. + +## Returns + +[`CellSelectionState`](../../index/type-aliases/CellSelectionState.md) + +## Example + +```ts +const selection = getDefaultCellSelectionState() +``` diff --git a/docs/reference/static-functions/functions/isRowSelected.md b/docs/reference/static-functions/functions/isRowSelected.md index 1139c47068..1a07476675 100644 --- a/docs/reference/static-functions/functions/isRowSelected.md +++ b/docs/reference/static-functions/functions/isRowSelected.md @@ -9,7 +9,7 @@ title: isRowSelected function isRowSelected(row, rowSelection): boolean; ``` -Defined in: [features/row-selection/rowSelectionFeature.utils.ts:869](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L869) +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:866](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L866) Returns whether a row id is selected in the current row selection state. diff --git a/docs/reference/static-functions/functions/isSubRowSelected.md b/docs/reference/static-functions/functions/isSubRowSelected.md index 7f76cf0efb..2c7e3c5417 100644 --- a/docs/reference/static-functions/functions/isSubRowSelected.md +++ b/docs/reference/static-functions/functions/isSubRowSelected.md @@ -9,7 +9,7 @@ title: isSubRowSelected function isSubRowSelected(row): boolean | "some" | "all"; ``` -Defined in: [features/row-selection/rowSelectionFeature.utils.ts:886](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L886) +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:883](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L883) Returns whether all, some, or none of a row's selectable descendants are selected. diff --git a/docs/reference/static-functions/functions/isTouchStartEvent.md b/docs/reference/static-functions/functions/isTouchStartEvent.md index 2d0b86595b..0557c6c2f1 100644 --- a/docs/reference/static-functions/functions/isTouchStartEvent.md +++ b/docs/reference/static-functions/functions/isTouchStartEvent.md @@ -9,7 +9,7 @@ title: isTouchStartEvent function isTouchStartEvent(e): e is TouchEvent; ``` -Defined in: [features/column-resizing/columnResizingFeature.utils.ts:430](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L430) +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:428](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L428) Narrows an unknown event to a `touchstart` event. diff --git a/docs/reference/static-functions/functions/passiveEventSupported.md b/docs/reference/static-functions/functions/passiveEventSupported.md index 5db4a1ddcf..77c532b492 100644 --- a/docs/reference/static-functions/functions/passiveEventSupported.md +++ b/docs/reference/static-functions/functions/passiveEventSupported.md @@ -9,7 +9,7 @@ title: passiveEventSupported function passiveEventSupported(): boolean; ``` -Defined in: [features/column-resizing/columnResizingFeature.utils.ts:396](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L396) +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:394](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L394) Detects whether the current environment supports passive event listeners. diff --git a/docs/reference/static-functions/functions/row_getAllCells.md b/docs/reference/static-functions/functions/row_getAllCells.md index 5ace4e5f69..f283936170 100644 --- a/docs/reference/static-functions/functions/row_getAllCells.md +++ b/docs/reference/static-functions/functions/row_getAllCells.md @@ -9,7 +9,7 @@ title: row_getAllCells function row_getAllCells(row): Cell[]; ``` -Defined in: [core/rows/coreRowsFeature.utils.ts:243](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L243) +Defined in: [core/rows/coreRowsFeature.utils.ts:250](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L250) Constructs one cell for each leaf column in this row. diff --git a/docs/reference/static-functions/functions/row_getAllCellsByColumnId.md b/docs/reference/static-functions/functions/row_getAllCellsByColumnId.md index fb963d2efc..db2d35d6f0 100644 --- a/docs/reference/static-functions/functions/row_getAllCellsByColumnId.md +++ b/docs/reference/static-functions/functions/row_getAllCellsByColumnId.md @@ -9,7 +9,7 @@ title: row_getAllCellsByColumnId function row_getAllCellsByColumnId(row): Record>; ``` -Defined in: [core/rows/coreRowsFeature.utils.ts:279](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L279) +Defined in: [core/rows/coreRowsFeature.utils.ts:286](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L286) Builds a lookup map of this row's cells keyed by column id. diff --git a/docs/reference/static-functions/functions/row_getCanExpand.md b/docs/reference/static-functions/functions/row_getCanExpand.md index 03e4c4580e..3f389cc354 100644 --- a/docs/reference/static-functions/functions/row_getCanExpand.md +++ b/docs/reference/static-functions/functions/row_getCanExpand.md @@ -9,7 +9,7 @@ title: row_getCanExpand function row_getCanExpand(row): boolean; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:344](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L344) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:340](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L340) Checks whether this row can be expanded. diff --git a/docs/reference/static-functions/functions/row_getIsAllParentsExpanded.md b/docs/reference/static-functions/functions/row_getIsAllParentsExpanded.md index 8cc1ebfadf..61c5dad14f 100644 --- a/docs/reference/static-functions/functions/row_getIsAllParentsExpanded.md +++ b/docs/reference/static-functions/functions/row_getIsAllParentsExpanded.md @@ -9,7 +9,7 @@ title: row_getIsAllParentsExpanded function row_getIsAllParentsExpanded(row): boolean; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:364](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L364) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:360](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L360) Checks whether every ancestor of this row is expanded. diff --git a/docs/reference/static-functions/functions/row_getIsExpanded.md b/docs/reference/static-functions/functions/row_getIsExpanded.md index 5fbf311893..3d756ce9e1 100644 --- a/docs/reference/static-functions/functions/row_getIsExpanded.md +++ b/docs/reference/static-functions/functions/row_getIsExpanded.md @@ -9,7 +9,7 @@ title: row_getIsExpanded function row_getIsExpanded(row): boolean; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:309](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L309) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:305](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L305) Checks whether this row is expanded. diff --git a/docs/reference/static-functions/functions/row_getParentRow.md b/docs/reference/static-functions/functions/row_getParentRow.md index c72a0c5ddf..0d576071cc 100644 --- a/docs/reference/static-functions/functions/row_getParentRow.md +++ b/docs/reference/static-functions/functions/row_getParentRow.md @@ -15,8 +15,8 @@ Defined in: [core/rows/coreRowsFeature.utils.ts:199](https://github.com/TanStack Looks up this row's direct parent, if it has one. -Parent lookup searches the pre-pagination row model so parent relationships -are available even when the parent is not on the current page. +Parent lookup prefers the core row model for structural parents, then falls +back to the pre-pagination row model for generated parent rows. ## Type Parameters diff --git a/docs/reference/static-functions/functions/row_getParentRows.md b/docs/reference/static-functions/functions/row_getParentRows.md index 02a23f54ba..9508797771 100644 --- a/docs/reference/static-functions/functions/row_getParentRows.md +++ b/docs/reference/static-functions/functions/row_getParentRows.md @@ -9,7 +9,7 @@ title: row_getParentRows function row_getParentRows(row): Row[]; ``` -Defined in: [core/rows/coreRowsFeature.utils.ts:216](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L216) +Defined in: [core/rows/coreRowsFeature.utils.ts:223](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L223) Collects this row's ancestor chain from root to direct parent. diff --git a/docs/reference/static-functions/functions/row_getToggleExpandedHandler.md b/docs/reference/static-functions/functions/row_getToggleExpandedHandler.md index 41f0847bde..c554723510 100644 --- a/docs/reference/static-functions/functions/row_getToggleExpandedHandler.md +++ b/docs/reference/static-functions/functions/row_getToggleExpandedHandler.md @@ -9,7 +9,7 @@ title: row_getToggleExpandedHandler function row_getToggleExpandedHandler(row): () => void; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:389](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L389) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:385](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L385) Creates a row control handler that toggles this row's expanded state. diff --git a/docs/reference/static-functions/functions/row_getToggleSelectedHandler.md b/docs/reference/static-functions/functions/row_getToggleSelectedHandler.md index 014fb6ba92..65756af575 100644 --- a/docs/reference/static-functions/functions/row_getToggleSelectedHandler.md +++ b/docs/reference/static-functions/functions/row_getToggleSelectedHandler.md @@ -9,7 +9,7 @@ title: row_getToggleSelectedHandler function row_getToggleSelectedHandler(row, opts?): (e) => void; ``` -Defined in: [features/row-selection/rowSelectionFeature.utils.ts:661](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L661) +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:660](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L660) Creates a checkbox-style handler that selects or deselects this row. diff --git a/docs/reference/static-functions/functions/row_toggleExpanded.md b/docs/reference/static-functions/functions/row_toggleExpanded.md index 91f783662a..365d59ba46 100644 --- a/docs/reference/static-functions/functions/row_toggleExpanded.md +++ b/docs/reference/static-functions/functions/row_toggleExpanded.md @@ -9,7 +9,7 @@ title: row_toggleExpanded function row_toggleExpanded(row, expanded?): void; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:258](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L258) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:254](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L254) Expands or collapses this row. diff --git a/docs/reference/static-functions/functions/selectRowsFn.md b/docs/reference/static-functions/functions/selectRowsFn.md index 3985c846f5..d509066a84 100644 --- a/docs/reference/static-functions/functions/selectRowsFn.md +++ b/docs/reference/static-functions/functions/selectRowsFn.md @@ -9,7 +9,7 @@ title: selectRowsFn function selectRowsFn(rowModel, table): RowModel; ``` -Defined in: [features/row-selection/rowSelectionFeature.utils.ts:810](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L810) +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:807](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L807) Builds a row model containing rows selected by the current row selection state. diff --git a/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md b/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md index 1571741b92..72886e0ec5 100644 --- a/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md +++ b/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md @@ -12,7 +12,7 @@ function shouldAutoRemoveFilter( column?): boolean; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:329](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L329) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:338](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L338) Returns whether a filter value should be removed from filter state. diff --git a/docs/reference/static-functions/functions/table_autoResetCellSelection.md b/docs/reference/static-functions/functions/table_autoResetCellSelection.md new file mode 100644 index 0000000000..322ba93b67 --- /dev/null +++ b/docs/reference/static-functions/functions/table_autoResetCellSelection.md @@ -0,0 +1,48 @@ +--- +id: table_autoResetCellSelection +title: table_autoResetCellSelection +--- + +# Function: table\_autoResetCellSelection() + +```ts +function table_autoResetCellSelection(table): void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:94](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L94) + +Schedules a cell selection reset after `data` changes. + +Ranges are stored as row and column ids, so without this a data swap would +leave a selection pointing at rows that no longer exist, or silently +re-select cells whenever new data reuses ids. The reset runs when +`autoResetAll` or `autoResetCellSelection` allows it, defaulting to on. + +Resetting to `initialState.cellSelection` rather than to empty means the +first row-model computation is a no-op, matching `table_autoResetExpanded`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_autoResetCellSelection(table) +``` diff --git a/docs/reference/static-functions/functions/table_extendCellSelection.md b/docs/reference/static-functions/functions/table_extendCellSelection.md new file mode 100644 index 0000000000..8f50f508f8 --- /dev/null +++ b/docs/reference/static-functions/functions/table_extendCellSelection.md @@ -0,0 +1,44 @@ +--- +id: table_extendCellSelection +title: table_extendCellSelection +--- + +# Function: table\_extendCellSelection() + +```ts +function table_extendCellSelection(table, direction): void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:725](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L725) + +Extends the active range one step in a direction, keeping its anchor fixed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +### direction + +[`CellSelectionDirection`](../../index/type-aliases/CellSelectionDirection.md) + +## Returns + +`void` + +## Example + +```ts +table_extendCellSelection(table, 'right') +``` diff --git a/docs/reference/static-functions/functions/table_getCellSelectionBounds.md b/docs/reference/static-functions/functions/table_getCellSelectionBounds.md new file mode 100644 index 0000000000..17968a8081 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCellSelectionBounds.md @@ -0,0 +1,46 @@ +--- +id: table_getCellSelectionBounds +title: table_getCellSelectionBounds +--- + +# Function: table\_getCellSelectionBounds() + +```ts +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) + +Resolves the selected ranges into inclusive 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 +no longer resolve, for example because its anchor row was filtered out, is +omitted rather than clamped, so it contributes nothing while remaining in +state and returns intact when the filter clears. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`CellSelectionBounds`](../../index/interfaces/CellSelectionBounds.md)[] + +## Example + +```ts +const bounds = table_getCellSelectionBounds(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCellSelectionColumnIds.md b/docs/reference/static-functions/functions/table_getCellSelectionColumnIds.md new file mode 100644 index 0000000000..618478d24d --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCellSelectionColumnIds.md @@ -0,0 +1,40 @@ +--- +id: table_getCellSelectionColumnIds +title: table_getCellSelectionColumnIds +--- + +# Function: table\_getCellSelectionColumnIds() + +```ts +function table_getCellSelectionColumnIds(table): string[]; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:973](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L973) + +Returns the ids of all columns intersected by the selection. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`string`[] + +## Example + +```ts +const columnIds = table_getCellSelectionColumnIds(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCellSelectionColumnIndexes.md b/docs/reference/static-functions/functions/table_getCellSelectionColumnIndexes.md new file mode 100644 index 0000000000..52a2a315a5 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCellSelectionColumnIndexes.md @@ -0,0 +1,45 @@ +--- +id: table_getCellSelectionColumnIndexes +title: table_getCellSelectionColumnIndexes +--- + +# Function: table\_getCellSelectionColumnIndexes() + +```ts +function table_getCellSelectionColumnIndexes(table): Record; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:187](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L187) + +Builds a column id to render-order index map. + +Registered by this feature so the lookup stays memoized even when +`columnOrderingFeature` is absent, since that feature's `getColumnIndexes` +static rebuilds all four maps on every call, which would make per-cell reads +O(columns). + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`Record`\<`string`, `number`\> + +## Example + +```ts +const index = table_getCellSelectionColumnIndexes(table)[columnId] +``` diff --git a/docs/reference/static-functions/functions/table_getCellSelectionRowIds.md b/docs/reference/static-functions/functions/table_getCellSelectionRowIds.md new file mode 100644 index 0000000000..c4e056dc2e --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCellSelectionRowIds.md @@ -0,0 +1,40 @@ +--- +id: table_getCellSelectionRowIds +title: table_getCellSelectionRowIds +--- + +# Function: table\_getCellSelectionRowIds() + +```ts +function table_getCellSelectionRowIds(table): string[]; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:936](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L936) + +Returns the ids of all rows intersected by the selection. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`string`[] + +## Example + +```ts +const rowIds = table_getCellSelectionRowIds(table) +``` diff --git a/docs/reference/static-functions/functions/table_getExpandedDepth.md b/docs/reference/static-functions/functions/table_getExpandedDepth.md index b5b6cc1077..9db93960e3 100644 --- a/docs/reference/static-functions/functions/table_getExpandedDepth.md +++ b/docs/reference/static-functions/functions/table_getExpandedDepth.md @@ -9,7 +9,7 @@ title: table_getExpandedDepth function table_getExpandedDepth(table): number; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:227](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L227) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:223](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L223) Computes the deepest expanded row id depth. diff --git a/docs/reference/static-functions/functions/table_getFocusedCell.md b/docs/reference/static-functions/functions/table_getFocusedCell.md new file mode 100644 index 0000000000..c6d7711f2c --- /dev/null +++ b/docs/reference/static-functions/functions/table_getFocusedCell.md @@ -0,0 +1,47 @@ +--- +id: table_getFocusedCell +title: table_getFocusedCell +--- + +# Function: table\_getFocusedCell() + +```ts +function table_getFocusedCell(table): + | Cell + | 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) + +Returns the active cell, i.e. the anchor of the most recent range. + +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 +is the active cell. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + + \| [`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `any`\> + \| `undefined` + +## Example + +```ts +const cell = table_getFocusedCell(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsAllRowsExpanded.md b/docs/reference/static-functions/functions/table_getIsAllRowsExpanded.md index d03d1cc578..abfabcc85a 100644 --- a/docs/reference/static-functions/functions/table_getIsAllRowsExpanded.md +++ b/docs/reference/static-functions/functions/table_getIsAllRowsExpanded.md @@ -9,7 +9,7 @@ title: table_getIsAllRowsExpanded function table_getIsAllRowsExpanded(table): boolean; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:192](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L192) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:188](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L188) Checks whether every row in the current row model is expanded. diff --git a/docs/reference/static-functions/functions/table_getIsSomeRowsExpanded.md b/docs/reference/static-functions/functions/table_getIsSomeRowsExpanded.md index 61d2f5bbb7..40633cde10 100644 --- a/docs/reference/static-functions/functions/table_getIsSomeRowsExpanded.md +++ b/docs/reference/static-functions/functions/table_getIsSomeRowsExpanded.md @@ -9,7 +9,7 @@ title: table_getIsSomeRowsExpanded function table_getIsSomeRowsExpanded(table): boolean; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:173](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L173) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:169](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L169) Checks whether any row is expanded. diff --git a/docs/reference/static-functions/functions/table_getRow.md b/docs/reference/static-functions/functions/table_getRow.md index 35ef0c743f..378ed7708c 100644 --- a/docs/reference/static-functions/functions/table_getRow.md +++ b/docs/reference/static-functions/functions/table_getRow.md @@ -12,7 +12,7 @@ function table_getRow( searchAll?): Row; ``` -Defined in: [core/rows/coreRowsFeature.utils.ts:329](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L329) +Defined in: [core/rows/coreRowsFeature.utils.ts:336](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L336) Looks up a row by id from the current or full row model. diff --git a/docs/reference/static-functions/functions/table_getRowId.md b/docs/reference/static-functions/functions/table_getRowId.md index aba1bf2648..495363bdc2 100644 --- a/docs/reference/static-functions/functions/table_getRowId.md +++ b/docs/reference/static-functions/functions/table_getRowId.md @@ -13,7 +13,7 @@ function table_getRowId( parent?): string; ``` -Defined in: [core/rows/coreRowsFeature.utils.ts:303](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L303) +Defined in: [core/rows/coreRowsFeature.utils.ts:310](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L310) Resolves the stable id for a row. diff --git a/docs/reference/static-functions/functions/table_getSelectedCellCount.md b/docs/reference/static-functions/functions/table_getSelectedCellCount.md new file mode 100644 index 0000000000..21cac9bee8 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getSelectedCellCount.md @@ -0,0 +1,44 @@ +--- +id: table_getSelectedCellCount +title: table_getSelectedCellCount +--- + +# Function: table\_getSelectedCellCount() + +```ts +function table_getSelectedCellCount(table): number; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:886](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L886) + +Returns the number of selected cells. + +Uses rectangle arithmetic, which needs no expansion. When +`enableCellSelection` is a per-cell predicate the cells have to be visited +individually, so that path falls back to enumeration. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`number` + +## Example + +```ts +const count = table_getSelectedCellCount(table) +``` diff --git a/docs/reference/static-functions/functions/table_getSelectedCellIds.md b/docs/reference/static-functions/functions/table_getSelectedCellIds.md new file mode 100644 index 0000000000..caf61c0f2b --- /dev/null +++ b/docs/reference/static-functions/functions/table_getSelectedCellIds.md @@ -0,0 +1,40 @@ +--- +id: table_getSelectedCellIds +title: table_getSelectedCellIds +--- + +# Function: table\_getSelectedCellIds() + +```ts +function table_getSelectedCellIds(table): string[]; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:838](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L838) + +Returns the ids of all selected cells, in row-major order per range. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`string`[] + +## Example + +```ts +const ids = table_getSelectedCellIds(table) +``` diff --git a/docs/reference/static-functions/functions/table_getSelectedCellRangesData.md b/docs/reference/static-functions/functions/table_getSelectedCellRangesData.md new file mode 100644 index 0000000000..d24c823750 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getSelectedCellRangesData.md @@ -0,0 +1,44 @@ +--- +id: table_getSelectedCellRangesData +title: table_getSelectedCellRangesData +--- + +# Function: table\_getSelectedCellRangesData() + +```ts +function table_getSelectedCellRangesData(table): unknown[][][]; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:859](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L859) + +Returns each selected range'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 +containing delimiters get quoted are all application decisions. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`unknown`[][][] + +## Example + +```ts +const [firstRange] = table_getSelectedCellRangesData(table) +``` diff --git a/docs/reference/static-functions/functions/table_getToggleAllRowsExpandedHandler.md b/docs/reference/static-functions/functions/table_getToggleAllRowsExpandedHandler.md index 2209b10493..f41d5e87f5 100644 --- a/docs/reference/static-functions/functions/table_getToggleAllRowsExpandedHandler.md +++ b/docs/reference/static-functions/functions/table_getToggleAllRowsExpandedHandler.md @@ -6,10 +6,10 @@ title: table_getToggleAllRowsExpandedHandler # Function: table\_getToggleAllRowsExpandedHandler() ```ts -function table_getToggleAllRowsExpandedHandler(table): (e) => void; +function table_getToggleAllRowsExpandedHandler(table): (_e) => void; ``` -Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:153](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L153) +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:150](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L150) Creates an event handler that toggles all rows expanded. @@ -32,12 +32,12 @@ Creates an event handler that toggles all rows expanded. ## Returns ```ts -(e): void; +(_e): void; ``` ### Parameters -#### e +#### \_e `unknown` diff --git a/docs/reference/static-functions/functions/table_moveCellSelection.md b/docs/reference/static-functions/functions/table_moveCellSelection.md new file mode 100644 index 0000000000..ae9b28ae39 --- /dev/null +++ b/docs/reference/static-functions/functions/table_moveCellSelection.md @@ -0,0 +1,47 @@ +--- +id: table_moveCellSelection +title: table_moveCellSelection +--- + +# Function: table\_moveCellSelection() + +```ts +function table_moveCellSelection(table, direction): void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:688](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L688) + +Moves the selection one step in a direction, collapsing it to a single cell. + +With nothing selected, this selects the first selectable cell so keyboard +navigation has somewhere to start. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +### direction + +[`CellSelectionDirection`](../../index/type-aliases/CellSelectionDirection.md) + +## Returns + +`void` + +## Example + +```ts +table_moveCellSelection(table, 'down') +``` diff --git a/docs/reference/static-functions/functions/table_resetCellSelection.md b/docs/reference/static-functions/functions/table_resetCellSelection.md new file mode 100644 index 0000000000..c4893cf216 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetCellSelection.md @@ -0,0 +1,47 @@ +--- +id: table_resetCellSelection +title: table_resetCellSelection +--- + +# Function: table\_resetCellSelection() + +```ts +function table_resetCellSelection(table, defaultState?): void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L65) + +Resets `cellSelection` to the configured initial state or feature default. + +With no argument, the reset clones `table.initialState.cellSelection` when it +exists. Passing `true` ignores initial state and resets to an empty selection. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetCellSelection(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetColumnFilters.md b/docs/reference/static-functions/functions/table_resetColumnFilters.md index 6be4c143cf..e4afcadfda 100644 --- a/docs/reference/static-functions/functions/table_resetColumnFilters.md +++ b/docs/reference/static-functions/functions/table_resetColumnFilters.md @@ -9,7 +9,7 @@ title: table_resetColumnFilters function table_resetColumnFilters(table, defaultState?): void; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:309](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L309) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:318](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L318) Resets `columnFilters` to the configured initial state or feature default. diff --git a/docs/reference/static-functions/functions/table_resetHeaderSizeInfo.md b/docs/reference/static-functions/functions/table_resetHeaderSizeInfo.md index cb2ac34f84..255bfde9bf 100644 --- a/docs/reference/static-functions/functions/table_resetHeaderSizeInfo.md +++ b/docs/reference/static-functions/functions/table_resetHeaderSizeInfo.md @@ -9,7 +9,7 @@ title: table_resetHeaderSizeInfo function table_resetHeaderSizeInfo(table, defaultState?): void; ``` -Defined in: [features/column-resizing/columnResizingFeature.utils.ts:370](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L370) +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:368](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L368) Resets `columnResizing` to the configured initial state or feature default. diff --git a/docs/reference/static-functions/functions/table_selectAllCells.md b/docs/reference/static-functions/functions/table_selectAllCells.md new file mode 100644 index 0000000000..36921c5814 --- /dev/null +++ b/docs/reference/static-functions/functions/table_selectAllCells.md @@ -0,0 +1,40 @@ +--- +id: table_selectAllCells +title: table_selectAllCells +--- + +# Function: table\_selectAllCells() + +```ts +function table_selectAllCells(table): void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:599](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L599) + +Selects every selectable cell in the table as one range. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_selectAllCells(table) +``` diff --git a/docs/reference/static-functions/functions/table_selectCellRange.md b/docs/reference/static-functions/functions/table_selectCellRange.md new file mode 100644 index 0000000000..5a70937fdc --- /dev/null +++ b/docs/reference/static-functions/functions/table_selectCellRange.md @@ -0,0 +1,51 @@ +--- +id: table_selectCellRange +title: table_selectCellRange +--- + +# Function: table\_selectCellRange() + +```ts +function table_selectCellRange( + table, + range, + 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) + +Selects a rectangle, replacing the current selection unless `additive`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +### range + +[`CellSelectionRange`](../../index/interfaces/CellSelectionRange.md) + +### opts? + +[`SelectCellRangeOptions`](../../index/interfaces/SelectCellRangeOptions.md) + +## Returns + +`void` + +## Example + +```ts +table_selectCellRange(table, range, { additive: true }) +``` diff --git a/docs/reference/static-functions/functions/table_setCellSelection.md b/docs/reference/static-functions/functions/table_setCellSelection.md new file mode 100644 index 0000000000..fd94322369 --- /dev/null +++ b/docs/reference/static-functions/functions/table_setCellSelection.md @@ -0,0 +1,44 @@ +--- +id: table_setCellSelection +title: table_setCellSelection +--- + +# Function: table\_setCellSelection() + +```ts +function table_setCellSelection(table, updater): void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L44) + +Routes a cell selection updater through the table's selection change handler. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`CellSelectionState`](../../index/type-aliases/CellSelectionState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setCellSelection(table, (old) => old.slice(0, -1)) +``` diff --git a/docs/reference/static-functions/functions/table_setColumnFilters.md b/docs/reference/static-functions/functions/table_setColumnFilters.md index 51bacf4bdd..f5f79a51a0 100644 --- a/docs/reference/static-functions/functions/table_setColumnFilters.md +++ b/docs/reference/static-functions/functions/table_setColumnFilters.md @@ -9,7 +9,7 @@ title: table_setColumnFilters function table_setColumnFilters(table, updater): void; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:269](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L269) +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:278](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L278) Routes a column filter updater through the table's filter change handler. diff --git a/docs/reference/static-functions/functions/table_setColumnResizing.md b/docs/reference/static-functions/functions/table_setColumnResizing.md index 1ff0934bfb..a99fb6c360 100644 --- a/docs/reference/static-functions/functions/table_setColumnResizing.md +++ b/docs/reference/static-functions/functions/table_setColumnResizing.md @@ -9,7 +9,7 @@ title: table_setColumnResizing function table_setColumnResizing(table, updater): void; ``` -Defined in: [features/column-resizing/columnResizingFeature.utils.ts:347](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L347) +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:345](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L345) Routes a transient column resizing updater through the table's resize handler. diff --git a/docs/reference/static-functions/functions/table_setFocusedCell.md b/docs/reference/static-functions/functions/table_setFocusedCell.md new file mode 100644 index 0000000000..8b3de469b1 --- /dev/null +++ b/docs/reference/static-functions/functions/table_setFocusedCell.md @@ -0,0 +1,51 @@ +--- +id: table_setFocusedCell +title: table_setFocusedCell +--- + +# Function: table\_setFocusedCell() + +```ts +function table_setFocusedCell( + table, + rowId, + columnId): void; +``` + +Defined in: [features/cell-selection/cellSelectionFeature.utils.ts:532](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts#L532) + +Collapses the selection to a single cell at the given coordinates. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/interfaces/Table_Internal.md)\<`TFeatures`, `TData`\> + +### rowId + +`string` + +### columnId + +`string` + +## Returns + +`void` + +## Example + +```ts +table_setFocusedCell(table, '3', 'firstName') +``` diff --git a/docs/reference/static-functions/index.md b/docs/reference/static-functions/index.md index 1f4da5647c..b5ca28f5cb 100644 --- a/docs/reference/static-functions/index.md +++ b/docs/reference/static-functions/index.md @@ -8,10 +8,17 @@ title: static-functions ## Functions - [aggregateColumnValue](functions/aggregateColumnValue.md) +- [cell\_getCanSelect](functions/cell_getCanSelect.md) - [cell\_getContext](functions/cell_getContext.md) - [cell\_getIsAggregated](functions/cell_getIsAggregated.md) +- [cell\_getIsFocused](functions/cell_getIsFocused.md) - [cell\_getIsGrouped](functions/cell_getIsGrouped.md) - [cell\_getIsPlaceholder](functions/cell_getIsPlaceholder.md) +- [cell\_getIsSelected](functions/cell_getIsSelected.md) +- [cell\_getSelectionEdges](functions/cell_getSelectionEdges.md) +- [cell\_getSelectionExtendHandler](functions/cell_getSelectionExtendHandler.md) +- [cell\_getSelectionStartHandler](functions/cell_getSelectionStartHandler.md) +- [cell\_getTabIndex](functions/cell_getTabIndex.md) - [cell\_getValue](functions/cell_getValue.md) - [cell\_renderValue](functions/cell_renderValue.md) - [column\_clearSorting](functions/column_clearSorting.md) @@ -65,6 +72,7 @@ title: static-functions - [column\_toggleSorting](functions/column_toggleSorting.md) - [column\_toggleVisibility](functions/column_toggleVisibility.md) - [formatAggregatedCellValue](functions/formatAggregatedCellValue.md) +- [getDefaultCellSelectionState](functions/getDefaultCellSelectionState.md) - [getDefaultColumnFiltersState](functions/getDefaultColumnFiltersState.md) - [getDefaultColumnOrderState](functions/getDefaultColumnOrderState.md) - [getDefaultColumnPinningState](functions/getDefaultColumnPinningState.md) @@ -125,8 +133,10 @@ title: static-functions - [row\_toggleSelected](functions/row_toggleSelected.md) - [selectRowsFn](functions/selectRowsFn.md) - [shouldAutoRemoveFilter](functions/shouldAutoRemoveFilter.md) +- [table\_autoResetCellSelection](functions/table_autoResetCellSelection.md) - [table\_autoResetExpanded](functions/table_autoResetExpanded.md) - [table\_autoResetPageIndex](functions/table_autoResetPageIndex.md) +- [table\_extendCellSelection](functions/table_extendCellSelection.md) - [table\_firstPage](functions/table_firstPage.md) - [table\_getAllColumns](functions/table_getAllColumns.md) - [table\_getAllFlatColumns](functions/table_getAllFlatColumns.md) @@ -137,6 +147,10 @@ title: static-functions - [table\_getCanNextPage](functions/table_getCanNextPage.md) - [table\_getCanPreviousPage](functions/table_getCanPreviousPage.md) - [table\_getCanSomeRowsExpand](functions/table_getCanSomeRowsExpand.md) +- [table\_getCellSelectionBounds](functions/table_getCellSelectionBounds.md) +- [table\_getCellSelectionColumnIds](functions/table_getCellSelectionColumnIds.md) +- [table\_getCellSelectionColumnIndexes](functions/table_getCellSelectionColumnIndexes.md) +- [table\_getCellSelectionRowIds](functions/table_getCellSelectionRowIds.md) - [table\_getCenterFlatHeaders](functions/table_getCenterFlatHeaders.md) - [table\_getCenterFooterGroups](functions/table_getCenterFooterGroups.md) - [table\_getCenterHeaderGroups](functions/table_getCenterHeaderGroups.md) @@ -162,6 +176,7 @@ title: static-functions - [table\_getFilteredRowModel](functions/table_getFilteredRowModel.md) - [table\_getFilteredSelectedRowModel](functions/table_getFilteredSelectedRowModel.md) - [table\_getFlatHeaders](functions/table_getFlatHeaders.md) +- [table\_getFocusedCell](functions/table_getFocusedCell.md) - [table\_getFooterGroups](functions/table_getFooterGroups.md) - [table\_getGlobalAutoFilterFn](functions/table_getGlobalAutoFilterFn.md) - [table\_getGlobalFacetedMinMaxValues](functions/table_getGlobalFacetedMinMaxValues.md) @@ -200,6 +215,9 @@ title: static-functions - [table\_getRowId](functions/table_getRowId.md) - [table\_getRowModel](functions/table_getRowModel.md) - [table\_getRowsInDisplayOrder](functions/table_getRowsInDisplayOrder.md) +- [table\_getSelectedCellCount](functions/table_getSelectedCellCount.md) +- [table\_getSelectedCellIds](functions/table_getSelectedCellIds.md) +- [table\_getSelectedCellRangesData](functions/table_getSelectedCellRangesData.md) - [table\_getSelectedRowIds](functions/table_getSelectedRowIds.md) - [table\_getSelectedRowModel](functions/table_getSelectedRowModel.md) - [table\_getSortedRowModel](functions/table_getSortedRowModel.md) @@ -220,9 +238,11 @@ title: static-functions - [table\_getVisibleLeafColumns](functions/table_getVisibleLeafColumns.md) - [table\_lastPage](functions/table_lastPage.md) - [table\_mergeOptions](functions/table_mergeOptions.md) +- [table\_moveCellSelection](functions/table_moveCellSelection.md) - [table\_nextPage](functions/table_nextPage.md) - [table\_previousPage](functions/table_previousPage.md) - [table\_reset](functions/table_reset.md) +- [table\_resetCellSelection](functions/table_resetCellSelection.md) - [table\_resetColumnFilters](functions/table_resetColumnFilters.md) - [table\_resetColumnOrder](functions/table_resetColumnOrder.md) - [table\_resetColumnPinning](functions/table_resetColumnPinning.md) @@ -238,6 +258,9 @@ title: static-functions - [table\_resetRowPinning](functions/table_resetRowPinning.md) - [table\_resetRowSelection](functions/table_resetRowSelection.md) - [table\_resetSorting](functions/table_resetSorting.md) +- [table\_selectAllCells](functions/table_selectAllCells.md) +- [table\_selectCellRange](functions/table_selectCellRange.md) +- [table\_setCellSelection](functions/table_setCellSelection.md) - [table\_setColumnFilters](functions/table_setColumnFilters.md) - [table\_setColumnOrder](functions/table_setColumnOrder.md) - [table\_setColumnPinning](functions/table_setColumnPinning.md) @@ -245,6 +268,7 @@ title: static-functions - [table\_setColumnSizing](functions/table_setColumnSizing.md) - [table\_setColumnVisibility](functions/table_setColumnVisibility.md) - [table\_setExpanded](functions/table_setExpanded.md) +- [table\_setFocusedCell](functions/table_setFocusedCell.md) - [table\_setGlobalFilter](functions/table_setGlobalFilter.md) - [table\_setGrouping](functions/table_setGrouping.md) - [table\_setOptions](functions/table_setOptions.md) diff --git a/examples/alpine/cell-selection/.devcontainer/devcontainer.json b/examples/alpine/cell-selection/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/alpine/cell-selection/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/alpine/cell-selection/index.html b/examples/alpine/cell-selection/index.html new file mode 100644 index 0000000000..b5e5a1e022 --- /dev/null +++ b/examples/alpine/cell-selection/index.html @@ -0,0 +1,231 @@ + + + + + + TanStack Alpine Table - Cell Selection + + +
+
+ + +
+
+

+ 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. +

+

+ Hiding, reordering, and pinning columns all keep a live selection + anchored to the same cell ids. Ranges are indexed in render order, so a + pinned column moves the rectangle with it rather than splitting it. +

+
+
+ +
+ +
+
+
+ + + + + +
+
+ +
+
+
+ + + + + + + + + + + + +
+ Rows () +
+
+
+
+ + + +
+
+
+
+ +
+
+ +

+      
+
+ + + +
+
+ + + diff --git a/examples/alpine/cell-selection/package.json b/examples/alpine/cell-selection/package.json new file mode 100644 index 0000000000..60553fc5a1 --- /dev/null +++ b/examples/alpine/cell-selection/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-alpine-table-example-cell-selection", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc --noEmit", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "dependencies": { + "@faker-js/faker": "^10.5.0", + "@tanstack/alpine-table": "^9.0.0-beta.56", + "@tanstack/hotkeys": "^0.8.0", + "alpinejs": "^3.15.12" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "@types/alpinejs": "^3.13.11", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/alpine/cell-selection/src/index.css b/examples/alpine/cell-selection/src/index.css new file mode 100644 index 0000000000..16c9b50401 --- /dev/null +++ b/examples/alpine/cell-selection/src/index.css @@ -0,0 +1,189 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +* { + box-sizing: border-box; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Demo layout helpers for the plain example UI. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.filter-row, +.page-controls { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.controls, +.page-controls { + gap: 0.5rem; + flex-wrap: wrap; +} + +.inline-controls { + gap: 0.25rem; +} + +.filter-row { + gap: 0.5rem; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row { + padding: 0 0.25rem; +} + +.demo-button, +.page-size-input, +.filter-input { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.filter-input { + width: 6rem; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +/* Cell selection: a dragged rectangle is tinted, and each cell draws only the + sides that getSelectionEdges() reports as the boundary of the selection, so + any union of rectangles ends up with a single continuous outline. + + The outline is drawn with inset box-shadows rather than borders. On a + border-collapse table a thicker border widens the shared grid line, which + would make rows change height as cells become selected; box-shadow never + affects layout. Each side is its own custom property so the four edge classes + compose, which they could not do if each set `box-shadow` directly. */ +.cell-selectable { + user-select: none; + cursor: cell; + --cell-edge-top: 0 0 0 0 transparent; + --cell-edge-right: 0 0 0 0 transparent; + --cell-edge-bottom: 0 0 0 0 transparent; + --cell-edge-left: 0 0 0 0 transparent; + box-shadow: + inset var(--cell-edge-top), + inset var(--cell-edge-right), + inset var(--cell-edge-bottom), + inset var(--cell-edge-left); +} + +.cell-selected { + background: #dbeafe; +} + +.cell-focused { + background: #bfdbfe; +} + +.cell-edge-top { + --cell-edge-top: 0 2px 0 0 #2563eb; +} + +.cell-edge-right { + --cell-edge-right: -2px 0 0 0 #2563eb; +} + +.cell-edge-bottom { + --cell-edge-bottom: 0 -2px 0 0 #2563eb; +} + +.cell-edge-left { + --cell-edge-left: 2px 0 0 0 #2563eb; +} diff --git a/examples/alpine/cell-selection/src/main.ts b/examples/alpine/cell-selection/src/main.ts new file mode 100644 index 0000000000..f5567f781c --- /dev/null +++ b/examples/alpine/cell-selection/src/main.ts @@ -0,0 +1,245 @@ +import Alpine from 'alpinejs' +import { faker } from '@faker-js/faker' +import { createMultiHotkeyHandler } from '@tanstack/hotkeys' +import { + FlexRender, + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createSortedRowModel, + createTable, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_datetime, + sortFn_text, + tableFeatures, +} from '@tanstack/alpine-table' +import { makeData } from './makeData' +import './index.css' +import type { Cell, ColumnDef, Header } from '@tanstack/alpine-table' +import type { Person } from './makeData' + +const features = tableFeatures({ + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + rowSortingFeature, + sortedRowModel: createSortedRowModel(), + sortFns: { + alphanumeric: sortFn_alphanumeric, + datetime: sortFn_datetime, + text: sortFn_text, + }, +}) + +const columns: Array> = [ + { accessorKey: 'firstName', header: 'First Name' }, + { accessorFn: (row) => row.lastName, id: 'lastName', header: 'Last Name' }, + { accessorKey: 'age', header: 'Age' }, + { accessorKey: 'visits', header: 'Visits' }, + { accessorKey: 'status', header: 'Status' }, + { + accessorKey: 'progress', + header: 'Profile Progress', + // enableCellSelection: false, // this column opts out of cell selection + }, + { accessorKey: 'email', header: 'Email' }, + { accessorKey: 'phone', header: 'Phone' }, + { accessorKey: 'city', header: 'City' }, + { accessorKey: 'country', header: 'Country' }, + { accessorKey: 'department', header: 'Department' }, + { + accessorKey: 'salary', + header: 'Salary', + cell: (info) => (info.getValue() as number).toLocaleString(), + }, +] + +// Serializing a selection for the clipboard is a userland concern: the table +// hands back `getSelectedCellRangesData()` as raw values, and the delimiter, +// the null representation, and the quoting rules are all yours to pick. This is +// the spreadsheet-flavored version. +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + + // spreadsheets expect a field to be quoted once it contains a delimiter, a + // newline, or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') // blank line between disjoint rectangles +} + +Alpine.data('table', () => { + const local = Alpine.reactive({ data: makeData(20) }) + + const table = createTable( + { + features, + columns, + get data() { + return local.data + }, + getRowId: (row: Person) => row.id, + enableCellSelection: true, // enable cell selection for all cells + // initialState: { cellSelection: [] }, // select cells on first render + // atoms: { cellSelection: cellSelectionAtom }, // own selection state with an external atom + // state: { cellSelection }, // classic controlled state; pair with onCellSelectionChange + // onCellSelectionChange: setCellSelection, + // enableCellRangeSelection: false, // disable Shift-click and drag ranges; default true + // enableMultiCellRangeSelection: false, // allow only one rectangle at a time; default true + // enableCellSelectionDrag: false, // disable drag-to-select; default true + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), // use Meta instead of Shift + debugTable: true, + }, + // Alpine re-renders from a version counter, and the selector decides which + // slices bump it. cellSelection has to be here or the highlight never moves. + (state) => ({ + cellSelection: state.cellSelection, + sorting: state.sorting, + columnOrder: state.columnOrder, + columnPinning: state.columnPinning, + columnVisibility: state.columnVisibility, + }), + ) + + // optionally, reset the cellSelection state not only when data changes, but + // also when column order, pinning, visibility, or sorting changes + // customize this to your needs + let isFirstColumnLayout = true + let lastLayoutKey = '' + + const resetOnLayoutChange = () => { + const layoutKey = JSON.stringify([ + table.atoms.columnOrder.get(), + table.atoms.columnPinning.get(), + table.atoms.columnVisibility.get(), + table.atoms.sorting.get(), + ]) + + if (isFirstColumnLayout) { + isFirstColumnLayout = false + lastLayoutKey = layoutKey + return + } + + if (layoutKey !== lastLayoutKey) { + lastLayoutKey = layoutKey + queueMicrotask(() => table.resetCellSelection(true)) + } + } + + const copySelection = () => + void navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) + + return { + table, + FlexRender, + + refreshData() { + local.data = makeData(20) + }, + stressTest() { + local.data = makeData(1_000) + }, + + // Alpine cannot call chained methods cleanly from directives, so the + // per-cell reads and handlers are exposed here. + cellClass(cell: Cell) { + // Most cells in a large grid are unselected, so bail before asking for + // edges. getSelectionEdges() would otherwise resolve the cell's position + // a second time just to discover it is not selected. + if (!cell.getIsSelected()) { + return cell.getIsFocused() + ? 'cell-selectable cell-focused' + : 'cell-selectable' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell-selectable', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') + }, + onCellMouseDown(cell: Cell, event: MouseEvent) { + cell.getSelectionStartHandler()(event) + }, + onCellMouseEnter(cell: Cell, event: MouseEvent) { + cell.getSelectionExtendHandler()(event) + }, + sortIndicator(header: Header) { + const sorted = header.column.getIsSorted() + return sorted === 'asc' ? ' 🔼' : sorted === 'desc' ? ' 🔽' : '' + }, + + selectionSummary() { + resetOnLayoutChange() + return `${table.getSelectedCellCount().toLocaleString()} cells selected across ${table + .getCellSelectionRowIds() + .length.toLocaleString()} rows and ${ + table.getCellSelectionColumnIds().length + } columns` + }, + + shuffleColumns() { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + }, + reverseColumns() { + table.setColumnOrder( + [...table.getAllLeafColumns().map((column) => column.id)].reverse(), + ) + }, + + copySelection, + + logRangesData() { + console.info( + 'table.getSelectedCellRangesData()', + table.getSelectedCellRangesData(), + ) + }, + + stringifiedState() { + return local.data.length < 1_001 + ? JSON.stringify(table.store.get(), null, 2) + : '' + }, + + // keyboard navigation is TanStack Hotkeys driving the table's imperative + // APIs; table-core ships no keydown handling of its own. Alpine has no + // hotkeys adapter, so the framework-agnostic core handler is used directly. + onGridKeyDown: createMultiHotkeyHandler({ + ArrowUp: () => table.moveCellSelection('up'), + ArrowDown: () => table.moveCellSelection('down'), + ArrowLeft: () => table.moveCellSelection('left'), + ArrowRight: () => table.moveCellSelection('right'), + 'Shift+ArrowUp': () => table.extendCellSelection('up'), + 'Shift+ArrowDown': () => table.extendCellSelection('down'), + 'Shift+ArrowLeft': () => table.extendCellSelection('left'), + 'Shift+ArrowRight': () => table.extendCellSelection('right'), + 'Mod+A': () => table.selectAllCells(), + Escape: () => table.resetCellSelection(true), + 'Mod+C': () => copySelection(), + }), + } +}) + +window.Alpine = Alpine +Alpine.start() diff --git a/examples/alpine/cell-selection/src/makeData.ts b/examples/alpine/cell-selection/src/makeData.ts new file mode 100644 index 0000000000..97fe7cd472 --- /dev/null +++ b/examples/alpine/cell-selection/src/makeData.ts @@ -0,0 +1,64 @@ +import { faker } from '@faker-js/faker' + +// twice the usual example's field count, so there are enough columns to +// meaningfully hide, reorder, and pin while a cell selection is active +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + email: string + phone: string + city: string + country: string + department: string + salary: number + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + email: faker.internet.email(), + phone: faker.phone.number(), + city: faker.location.city(), + country: faker.location.country(), + department: faker.commerce.department(), + salary: faker.number.int({ min: 40_000, max: 200_000 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/alpine/cell-selection/src/vite-env.d.ts b/examples/alpine/cell-selection/src/vite-env.d.ts new file mode 100644 index 0000000000..99f6333028 --- /dev/null +++ b/examples/alpine/cell-selection/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +import type Alpine from 'alpinejs' + +declare global { + interface Window { + Alpine: typeof Alpine + } +} diff --git a/examples/alpine/cell-selection/tests/e2e/smoke.spec.ts b/examples/alpine/cell-selection/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/alpine/cell-selection/tests/e2e/smoke.spec.ts @@ -0,0 +1,104 @@ +import { expect, test } from '@playwright/test' +import type { Locator, Page } from '@playwright/test' +import path from 'node:path' +import { startExampleServer } from '../../../../../tests/e2e/helpers/startExampleServer' + +const exampleDir = path.resolve() + +function collectPageErrors(page: Page) { + const errors: Array = [] + + page.on('pageerror', (error) => { + errors.push(error.message) + }) + + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()) + } + }) + + return errors +} + +async function openExample(page: Page) { + const server = await startExampleServer(exampleDir) + const errors = collectPageErrors(page) + + await page.route( + 'https://unpkg.com/react-scan/dist/auto.global.js', + (route) => + route.fulfill({ + contentType: 'application/javascript', + body: '', + }), + ) + + await page.goto(server.url) + + return { errors, server } +} + +function getTable(page: Page) { + return page + .locator('table:visible, .divTable:visible') + .filter({ has: page.locator('thead th, .thead .th') }) + .filter({ has: page.locator('tbody tr, .tbody .tr') }) + .first() +} + +function getHeaderCells(table: Locator) { + return table.locator('thead th, .thead .th') +} + +function getBodyRows(table: Locator) { + return table.locator('tbody tr, .tbody .tr') +} + +async function getFirstBodyRowData(table: Locator) { + const row = getBodyRows(table).first() + const firstInput = row + .locator('input:not([type="checkbox"]):not([type="radio"])') + .first() + + if ((await firstInput.count()) > 0 && (await firstInput.isVisible())) { + return firstInput.inputValue() + } + + const text = await row.textContent() + return text?.replace(/\s+/g, ' ').trim() ?? '' +} + +test('renders the table without crashing', async ({ page }) => { + const { errors, server } = await openExample(page) + + try { + const table = getTable(page) + const bodyRows = getBodyRows(table) + + await expect(table).toBeVisible() + await expect(getHeaderCells(table).first()).toBeVisible() + await expect(bodyRows.first()).toBeVisible() + + const regenerateButton = page + .getByRole('button', { name: /^Regenerate Data$/i }) + .first() + + if ((await regenerateButton.count()) > 0) { + await expect(regenerateButton).toBeVisible() + + const firstRowBefore = await getFirstBodyRowData(table) + + await regenerateButton.click() + + await expect + .poll(() => getFirstBodyRowData(table)) + .not.toBe(firstRowBefore) + await expect(bodyRows.first()).toBeVisible() + } + + expect(errors).toEqual([]) + } finally { + await server.close() + } +}) diff --git a/examples/alpine/cell-selection/tsconfig.json b/examples/alpine/cell-selection/tsconfig.json new file mode 100644 index 0000000000..8bd4c0cb56 --- /dev/null +++ b/examples/alpine/cell-selection/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "tests/e2e", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/alpine/cell-selection/vite.config.js b/examples/alpine/cell-selection/vite.config.js new file mode 100644 index 0000000000..9b5122fda5 --- /dev/null +++ b/examples/alpine/cell-selection/vite.config.js @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/angular/cell-selection/.devcontainer/devcontainer.json b/examples/angular/cell-selection/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/angular/cell-selection/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/angular/cell-selection/.gitignore b/examples/angular/cell-selection/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/cell-selection/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/cell-selection/angular.json b/examples/angular/cell-selection/angular.json new file mode 100644 index 0000000000..3809a09b69 --- /dev/null +++ b/examples/angular/cell-selection/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "cell-selection": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "cell-selection:build:production" + }, + "development": { + "buildTarget": "cell-selection:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/cell-selection/package.json b/examples/angular/cell-selection/package.json new file mode 100644 index 0000000000..4d37eac262 --- /dev/null +++ b/examples/angular/cell-selection/package.json @@ -0,0 +1,37 @@ +{ + "name": "tanstack-angular-table-example-cell-selection", + "scripts": { + "ng": "ng", + "start": "ng serve --port 6565", + "dev": "ng serve --port 6565", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "lint": "eslint ./src", + "test:types": "tsc -p tsconfig.app.json --noEmit", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "private": true, + "packageManager": "pnpm@11.9.0", + "dependencies": { + "@angular/cdk": "^22.0.2", + "@angular/common": "^22.0.2", + "@angular/compiler": "^22.0.2", + "@angular/core": "^22.0.2", + "@angular/forms": "^22.0.2", + "@angular/platform-browser": "^22.0.2", + "@angular/router": "^22.0.2", + "@faker-js/faker": "^10.5.0", + "@tanstack/angular-devtools": "^0.0.7", + "@tanstack/angular-hotkeys": "^0.10.0", + "@tanstack/angular-table": "^9.0.0-beta.56", + "@tanstack/angular-table-devtools": "^9.0.0-beta.56", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^22.0.0", + "@angular/cli": "^22.0.0", + "@angular/compiler-cli": "^22.0.2", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/cell-selection/public/favicon.ico b/examples/angular/cell-selection/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 zcmd^G33O9Omi+`8$@{|M-I6TH3wzF-p5CV8o}7f~KxR60LK+ApEFB<$bcciv%@SmA zV{n>g85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~ zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@ z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l* zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4 zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp| z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9 znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3 zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=? zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO zKO?E0 zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4 z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG` z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8 z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3 z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$ zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~ z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~ zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@ z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8 zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{ z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL` zt?XK-iTEOWV}f>Pz3H-s*>W z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/examples/angular/cell-selection/src/app/app.config.ts b/examples/angular/cell-selection/src/app/app.config.ts new file mode 100644 index 0000000000..00db520fb7 --- /dev/null +++ b/examples/angular/cell-selection/src/app/app.config.ts @@ -0,0 +1,22 @@ +import { isDevMode, provideBrowserGlobalErrorListeners } from '@angular/core' +import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + isDevMode() + ? provideTanStackDevtools(() => ({ + plugins: [ + { + name: 'TanStack Table', + render: () => + import('@tanstack/angular-table-devtools').then((m) => + m.TableDevtoolsPanel(), + ), + }, + ], + })) + : [], + ], +} diff --git a/examples/angular/cell-selection/src/app/app.html b/examples/angular/cell-selection/src/app/app.html new file mode 100644 index 0000000000..33809346d0 --- /dev/null +++ b/examples/angular/cell-selection/src/app/app.html @@ -0,0 +1,179 @@ +
+
+ + +
+
+

+ 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. +

+

+ Hiding, reordering, and pinning columns all keep a live selection anchored to the same cell ids. + Ranges are indexed in render order, so a pinned column moves the rectangle with it rather than + splitting it. +

+
+
+ +
+ @for (column of table.getAllLeafColumns(); track column.id) { +
+ +
+ } +
+
+
+ + + + + +
+
+ +
+ {{ table.getSelectedCellCount().toLocaleString() }} cells selected across + {{ table.getCellSelectionRowIds().length.toLocaleString() }} rows and + {{ table.getCellSelectionColumnIds().length }} columns +
+
+
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + @if (cell.getCanSelect()) { + + } @else { + + } + } + + } + + + + + + +
+ @if (!header.isPlaceholder) { +
+ + {{ headerCell }} + + {{ sortIndicator(header.column.getIsSorted()) }} +
+ @if (header.column.getCanPin()) { +
+ @if (header.column.getIsPinned() !== 'start') { + + } + @if (header.column.getIsPinned()) { + + } + @if (header.column.getIsPinned() !== 'end') { + + } +
+ } + } +
+ + {{ renderCell }} + + + + {{ renderCell }} + +
Rows ({{ table.getRowModel().rows.length.toLocaleString() }})
+
+
+
+ + + +
+
+
+
+ +
+
+ +
{{ stringifiedState() }}
+
+
+ + + +
+
diff --git a/examples/angular/cell-selection/src/app/app.ts b/examples/angular/cell-selection/src/app/app.ts new file mode 100644 index 0000000000..20b8b56cb5 --- /dev/null +++ b/examples/angular/cell-selection/src/app/app.ts @@ -0,0 +1,267 @@ +import { + ChangeDetectionStrategy, + Component, + Injector, + effect, + inject, + signal, + untracked, + viewChild, +} from '@angular/core' +import { faker } from '@faker-js/faker' +import { FlexRender, TanStackTable } from '@tanstack/angular-table' +import { injectTanStackTableDevtools } from '@tanstack/angular-table-devtools' +import { injectHotkeys } from '@tanstack/angular-hotkeys' +import { makeData } from './makeData' +import { createAppColumnHelper, injectTable } from './table' +import type { ElementRef } from '@angular/core' +import type { Cell } from '@tanstack/angular-table' +import type { Person } from './makeData' +import type { features } from './table' + +const columnHelper = createAppColumnHelper() + +// Serializing a selection for the clipboard is a userland concern: the table +// hands back `getSelectedCellRangesData()` as raw values, and the delimiter, +// the null representation, and the quoting rules are all yours to pick. This is +// the spreadsheet-flavored version. +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + + // spreadsheets expect a field to be quoted once it contains a delimiter, a + // newline, or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') // blank line between disjoint rectangles +} + +@Component({ + selector: 'app-root', + standalone: true, + imports: [FlexRender, TanStackTable], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + private readonly injector = inject(Injector) + readonly data = signal(makeData(20)) + readonly grid = viewChild>('grid') + + readonly columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: () => 'First Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor('lastName', { + header: () => 'Last Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => 'Visits', + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: () => 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: () => 'Profile Progress', + // enableCellSelection: false, // this column opts out of cell selection + footer: (props) => props.column.id, + }), + columnHelper.accessor('email', { + header: () => 'Email', + footer: (props) => props.column.id, + }), + columnHelper.accessor('phone', { + header: () => 'Phone', + footer: (props) => props.column.id, + }), + columnHelper.accessor('city', { + header: () => 'City', + footer: (props) => props.column.id, + }), + columnHelper.accessor('country', { + header: () => 'Country', + footer: (props) => props.column.id, + }), + columnHelper.accessor('department', { + header: () => 'Department', + footer: (props) => props.column.id, + }), + columnHelper.accessor('salary', { + header: () => 'Salary', + cell: (info) => info.getValue().toLocaleString(), + footer: (props) => props.column.id, + }), + ]) + + readonly table = injectTable(() => ({ + key: 'cell-selection', // needed for devtools + data: this.data(), + columns: this.columns, + getRowId: (row: Person) => row.id, + enableCellSelection: true, // enable cell selection for all cells + // initialState: { cellSelection: [] }, // select cells on first render + // atoms: { cellSelection: cellSelectionAtom }, // own selection state with an external atom + // state: { cellSelection }, // classic controlled state; pair with onCellSelectionChange + // onCellSelectionChange: (updater) => { ... }, + // enableCellRangeSelection: false, // disable Shift-click and drag ranges; default true + // enableMultiCellRangeSelection: false, // allow only one rectangle at a time; default true + // enableCellSelectionDrag: false, // disable drag-to-select; default true + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), // use Meta instead of Shift + debugTable: true, + })) + + constructor() { + // optionally, reset the cellSelection state not only when data changes, but + // also when column order, pinning, visibility, or sorting changes + // customize this to your needs + let isFirstColumnLayout = true + + effect(() => { + // read the atoms so this tracks only the layout slices + this.table.atoms.columnOrder.get() + this.table.atoms.columnPinning.get() + this.table.atoms.columnVisibility.get() + this.table.atoms.sorting.get() + + if (isFirstColumnLayout) { + isFirstColumnLayout = false + return + } + + untracked(() => this.table.resetCellSelection(true)) + }) + + // keyboard navigation is TanStack Hotkeys driving the table's imperative + // APIs; table-core ships no keydown handling of its own + injectHotkeys( + [ + { + hotkey: 'ArrowUp', + callback: () => this.table.moveCellSelection('up'), + }, + { + hotkey: 'ArrowDown', + callback: () => this.table.moveCellSelection('down'), + }, + { + hotkey: 'ArrowLeft', + callback: () => this.table.moveCellSelection('left'), + }, + { + hotkey: 'ArrowRight', + callback: () => this.table.moveCellSelection('right'), + }, + { + hotkey: 'Shift+ArrowUp', + callback: () => this.table.extendCellSelection('up'), + }, + { + hotkey: 'Shift+ArrowDown', + callback: () => this.table.extendCellSelection('down'), + }, + { + hotkey: 'Shift+ArrowLeft', + callback: () => this.table.extendCellSelection('left'), + }, + { + hotkey: 'Shift+ArrowRight', + callback: () => this.table.extendCellSelection('right'), + }, + { hotkey: 'Mod+A', callback: () => this.table.selectAllCells() }, + { + hotkey: 'Escape', + callback: () => this.table.resetCellSelection(true), + }, + { hotkey: 'Mod+C', callback: () => this.copySelection() }, + ], + () => ({ target: this.grid()?.nativeElement ?? null }), + ) + } + + ngOnInit() { + injectTanStackTableDevtools(() => ({ + table: this.table, + injector: this.injector, + })) + } + + // Angular's signals keep these reads fresh, so no Subscribe workaround is + // needed the way it is in React. + getCellClassName(cell: Cell) { + // Most cells in a large grid are unselected, so bail before asking for + // edges. getSelectionEdges() would otherwise resolve the cell's position a + // second time just to discover it is not selected. + if (!cell.getIsSelected()) { + return cell.getIsFocused() + ? 'cell-selectable cell-focused' + : 'cell-selectable' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell-selectable', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') + } + + sortIndicator(sorted: string | false) { + return sorted === 'asc' ? ' 🔼' : sorted === 'desc' ? ' 🔽' : '' + } + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) + + randomizeColumns() { + this.table.setColumnOrder( + faker.helpers.shuffle(this.table.getAllLeafColumns().map((d) => d.id)), + ) + } + + reverseColumns() { + this.table.setColumnOrder( + [...this.table.getAllLeafColumns().map((column) => column.id)].reverse(), + ) + } + + copySelection() { + void navigator.clipboard.writeText( + toTsv(this.table.getSelectedCellRangesData()), + ) + } + + logRangesData() { + console.info( + 'table.getSelectedCellRangesData()', + this.table.getSelectedCellRangesData(), + ) + } + + stringifiedState() { + return this.data().length < 1_001 + ? JSON.stringify(this.table.store.get(), null, 2) + : '' + } +} diff --git a/examples/angular/cell-selection/src/app/makeData.ts b/examples/angular/cell-selection/src/app/makeData.ts new file mode 100644 index 0000000000..97fe7cd472 --- /dev/null +++ b/examples/angular/cell-selection/src/app/makeData.ts @@ -0,0 +1,64 @@ +import { faker } from '@faker-js/faker' + +// twice the usual example's field count, so there are enough columns to +// meaningfully hide, reorder, and pin while a cell selection is active +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + email: string + phone: string + city: string + country: string + department: string + salary: number + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + email: faker.internet.email(), + phone: faker.phone.number(), + city: faker.location.city(), + country: faker.location.country(), + department: faker.commerce.department(), + salary: faker.number.int({ min: 40_000, max: 200_000 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/cell-selection/src/app/table.ts b/examples/angular/cell-selection/src/app/table.ts new file mode 100644 index 0000000000..a432e220f6 --- /dev/null +++ b/examples/angular/cell-selection/src/app/table.ts @@ -0,0 +1,38 @@ +import { + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createSortedRowModel, + createTableHook, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_datetime, + sortFn_text, + tableFeatures, +} from '@tanstack/angular-table' + +export const features = tableFeatures({ + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + rowSortingFeature, + sortedRowModel: createSortedRowModel(), + sortFns: { + alphanumeric: sortFn_alphanumeric, + datetime: sortFn_datetime, + text: sortFn_text, + }, +}) + +export const { + injectAppTable: injectTable, + injectTableContext, + createAppColumnHelper, + injectFlexRenderCellContext, + injectFlexRenderHeaderContext, +} = createTableHook({ + features, + debugTable: true, +}) diff --git a/examples/angular/cell-selection/src/index.html b/examples/angular/cell-selection/src/index.html new file mode 100644 index 0000000000..4f40d38cef --- /dev/null +++ b/examples/angular/cell-selection/src/index.html @@ -0,0 +1,13 @@ + + + + + Selection + + + + + + + + diff --git a/examples/angular/cell-selection/src/main.ts b/examples/angular/cell-selection/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/cell-selection/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/cell-selection/src/styles.css b/examples/angular/cell-selection/src/styles.css new file mode 100644 index 0000000000..24ed5d145b --- /dev/null +++ b/examples/angular/cell-selection/src/styles.css @@ -0,0 +1,421 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +/* Cell selection: a dragged rectangle is tinted, and each cell draws only the + sides that getSelectionEdges() reports as the boundary of the selection, so + any union of rectangles ends up with a single continuous outline. + + The outline is drawn with inset box-shadows rather than borders. On a + border-collapse table a thicker border widens the shared grid line, which + would make rows change height as cells become selected; box-shadow never + affects layout. Each side is its own custom property so the four edge classes + compose, which they could not do if each set `box-shadow` directly. */ +.cell-selectable { + user-select: none; + cursor: cell; + --cell-edge-top: 0 0 0 0 transparent; + --cell-edge-right: 0 0 0 0 transparent; + --cell-edge-bottom: 0 0 0 0 transparent; + --cell-edge-left: 0 0 0 0 transparent; + box-shadow: + inset var(--cell-edge-top), + inset var(--cell-edge-right), + inset var(--cell-edge-bottom), + inset var(--cell-edge-left); +} + +.cell-selected { + background: #dbeafe; +} + +.cell-focused { + background: #bfdbfe; +} + +.cell-edge-top { + --cell-edge-top: 0 2px 0 0 #2563eb; +} + +.cell-edge-right { + --cell-edge-right: -2px 0 0 0 #2563eb; +} + +.cell-edge-bottom { + --cell-edge-bottom: 0 -2px 0 0 #2563eb; +} + +.cell-edge-left { + --cell-edge-left: 2px 0 0 0 #2563eb; +} diff --git a/examples/angular/cell-selection/tests/e2e/smoke.spec.ts b/examples/angular/cell-selection/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/angular/cell-selection/tests/e2e/smoke.spec.ts @@ -0,0 +1,104 @@ +import { expect, test } from '@playwright/test' +import type { Locator, Page } from '@playwright/test' +import path from 'node:path' +import { startExampleServer } from '../../../../../tests/e2e/helpers/startExampleServer' + +const exampleDir = path.resolve() + +function collectPageErrors(page: Page) { + const errors: Array = [] + + page.on('pageerror', (error) => { + errors.push(error.message) + }) + + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()) + } + }) + + return errors +} + +async function openExample(page: Page) { + const server = await startExampleServer(exampleDir) + const errors = collectPageErrors(page) + + await page.route( + 'https://unpkg.com/react-scan/dist/auto.global.js', + (route) => + route.fulfill({ + contentType: 'application/javascript', + body: '', + }), + ) + + await page.goto(server.url) + + return { errors, server } +} + +function getTable(page: Page) { + return page + .locator('table:visible, .divTable:visible') + .filter({ has: page.locator('thead th, .thead .th') }) + .filter({ has: page.locator('tbody tr, .tbody .tr') }) + .first() +} + +function getHeaderCells(table: Locator) { + return table.locator('thead th, .thead .th') +} + +function getBodyRows(table: Locator) { + return table.locator('tbody tr, .tbody .tr') +} + +async function getFirstBodyRowData(table: Locator) { + const row = getBodyRows(table).first() + const firstInput = row + .locator('input:not([type="checkbox"]):not([type="radio"])') + .first() + + if ((await firstInput.count()) > 0 && (await firstInput.isVisible())) { + return firstInput.inputValue() + } + + const text = await row.textContent() + return text?.replace(/\s+/g, ' ').trim() ?? '' +} + +test('renders the table without crashing', async ({ page }) => { + const { errors, server } = await openExample(page) + + try { + const table = getTable(page) + const bodyRows = getBodyRows(table) + + await expect(table).toBeVisible() + await expect(getHeaderCells(table).first()).toBeVisible() + await expect(bodyRows.first()).toBeVisible() + + const regenerateButton = page + .getByRole('button', { name: /^Regenerate Data$/i }) + .first() + + if ((await regenerateButton.count()) > 0) { + await expect(regenerateButton).toBeVisible() + + const firstRowBefore = await getFirstBodyRowData(table) + + await regenerateButton.click() + + await expect + .poll(() => getFirstBodyRowData(table)) + .not.toBe(firstRowBefore) + await expect(bodyRows.first()).toBeVisible() + } + + expect(errors).toEqual([]) + } finally { + await server.close() + } +}) diff --git a/examples/angular/cell-selection/tsconfig.app.json b/examples/angular/cell-selection/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/cell-selection/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/cell-selection/tsconfig.json b/examples/angular/cell-selection/tsconfig.json new file mode 100644 index 0000000000..1ec24b3409 --- /dev/null +++ b/examples/angular/cell-selection/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "tests/e2e", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/ember/cell-selection/.devcontainer/devcontainer.json b/examples/ember/cell-selection/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/ember/cell-selection/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/ember/cell-selection/app/app.css b/examples/ember/cell-selection/app/app.css new file mode 100644 index 0000000000..0ff4763353 --- /dev/null +++ b/examples/ember/cell-selection/app/app.css @@ -0,0 +1,357 @@ +/* Shared demo styles for the TanStack Table Ember examples. */ + +html { + font-family: sans-serif; + font-size: 14px; +} + +* { + box-sizing: border-box; +} + +/* --- Base table --- */ + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + position: relative; + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* --- Div-based grid table (used by the performant resizing example) --- */ + +.divTable { + border: 1px solid lightgray; + width: fit-content; +} + +.divTable .tr { + display: flex; + width: fit-content; + height: 30px; +} + +.divTable .th, +.divTable .td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 2px 4px; + height: 30px; +} + +.divTable .th { + position: relative; + font-weight: bold; + text-align: center; +} + +/* --- Column resizing --- */ + +.resizer { + position: absolute; + top: 0; + right: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* --- Column pinning --- */ + +.pinned-rows-top, +.pinned-rows-bottom { + background: #f5f5f5; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +/* --- Demo layout helpers --- */ + +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.header-controls, +.pin-actions, +.filter-row, +.page-controls { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.controls, +.page-controls { + gap: 0.5rem; + flex-wrap: wrap; +} + +.inline-controls, +.header-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.nowrap { + white-space: nowrap; +} + +.scroll-container { + overflow-x: auto; +} + +.section-title { + font-size: 1.25rem; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +/* --- Column visibility toggle panel --- */ + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row { + padding: 0 0.25rem; +} + +/* --- Buttons and inputs --- */ + +.demo-button, +.demo-button-spaced, +.pin-button, +.filter-input, +.filter-select, +.page-size-input, +.column-size-input, +.outlined-control { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.outlined-control { + border-color: #000; +} + +.outlined-table { + border: 2px solid #000; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; + padding: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + padding: 0.25rem; +} + +.filter-input { + width: 6rem; +} + +.filter-select { + width: 9rem; +} + +/* --- Status cell demo (basic table) --- */ + +.status-bar { + font-size: 0.875rem; +} + +.status-relationship { + color: #16a34a; +} + +.status-complicated { + color: #ca8a04; +} + +.status-single { + color: #6b7280; +} + +/* --- Row selection --- */ + +.selected { + background: #e0f2fe; +} + +/* --- Code / state output --- */ + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +/* Cell selection: a dragged rectangle is tinted, and each cell draws only the + sides that getSelectionEdges() reports as the boundary of the selection, so + any union of rectangles ends up with a single continuous outline. + + The outline is drawn with inset box-shadows rather than borders. On a + border-collapse table a thicker border widens the shared grid line, which + would make rows change height as cells become selected; box-shadow never + affects layout. Each side is its own custom property so the four edge classes + compose, which they could not do if each set `box-shadow` directly. */ +.cell-selectable { + user-select: none; + cursor: cell; + --cell-edge-top: 0 0 0 0 transparent; + --cell-edge-right: 0 0 0 0 transparent; + --cell-edge-bottom: 0 0 0 0 transparent; + --cell-edge-left: 0 0 0 0 transparent; + box-shadow: + inset var(--cell-edge-top), + inset var(--cell-edge-right), + inset var(--cell-edge-bottom), + inset var(--cell-edge-left); +} + +.cell-selected { + background: #dbeafe; +} + +.cell-focused { + background: #bfdbfe; +} + +.cell-edge-top { + --cell-edge-top: 0 2px 0 0 #2563eb; +} + +.cell-edge-right { + --cell-edge-right: -2px 0 0 0 #2563eb; +} + +.cell-edge-bottom { + --cell-edge-bottom: 0 -2px 0 0 #2563eb; +} + +.cell-edge-left { + --cell-edge-left: 2px 0 0 0 #2563eb; +} diff --git a/examples/ember/cell-selection/app/app.ts b/examples/ember/cell-selection/app/app.ts new file mode 100644 index 0000000000..d5681b3c7a --- /dev/null +++ b/examples/ember/cell-selection/app/app.ts @@ -0,0 +1,22 @@ +/** + * Looking for services that come from addons? + * + * See: https://github.com/embroider-build/embroider/issues/2659 + * + * We currently don't support app-tree merging from libraries. + * + * For services, I highly recommend looking in to either of + * - https://github.com/chancancode/ember-polaris-service- + * - https://ember-primitives.pages.dev/6-utils/createService.md + * - https://ember-primitives.pages.dev/6-utils/createAsyncService.md + */ +import Application from 'ember-strict-application-resolver' +import './app.css' + +export default class App extends Application { + modules = { + ...import.meta.glob('./router.*', { eager: true }), + ...import.meta.glob('./templates/**/*', { eager: true }), + ...import.meta.glob('./services/**/*', { eager: true }), + } +} diff --git a/examples/ember/cell-selection/app/config.ts b/examples/ember/cell-selection/app/config.ts new file mode 100644 index 0000000000..8b0d33268d --- /dev/null +++ b/examples/ember/cell-selection/app/config.ts @@ -0,0 +1,17 @@ +interface Config { + environment: 'development' | 'production' + locationType: 'history' | 'hash' | 'none' | 'auto' + rootURL: string + EmberENV?: Record + APP: Record & { rootElement?: string; autoboot?: boolean } +} + +const ENV: Config = { + environment: import.meta.env.DEV ? 'development' : 'production', + rootURL: '/', + locationType: 'history', + EmberENV: {}, + APP: {}, +} + +export default ENV diff --git a/examples/ember/cell-selection/app/router.ts b/examples/ember/cell-selection/app/router.ts new file mode 100644 index 0000000000..eadbe60450 --- /dev/null +++ b/examples/ember/cell-selection/app/router.ts @@ -0,0 +1,51 @@ +import EmbroiderRouter from '@embroider/router' +import config from '#config' + +export default class Router extends EmbroiderRouter { + location = config.locationType + rootURL = config.rootURL +} + +Router.map(function () {}) + +/** + * Caveat: + * - https://github.com/embroider-build/embroider/issues/2521 + * We don't yet have a way to do this in a nice way + * + */ +// function bundle(name: string, loader: () => Promise<{ default: unknown }>[]) { +// return { +// names: [name], +// load: async () => { +// const [template, route, controller] = await Promise.all(loader()); +// let slashName = name.replaceAll(".", "/"); +// let results: Record = {}; + +// if (template) results[`./templates/${slashName}`] = template.default; +// if (route) results[`./routes/${slashName}`] = route.default; +// if (controller) results[`./controllers/${slashName}`] = controller.default; + +// return { +// default: results, +// }; +// }, +// }; +// } + +/** + * Examples from: + * - https://github.com/NullVoxPopuli/limber/blob/67e2f54bbe224052e38f9a9e566d704411e65e86/apps/repl/app/router.ts#L35 + */ +// (window as any)._embroiderRouteBundles_ = [ +// bundle("docs", () => [import("./templates/docs.gts")]), +// bundle("docs.repl-sdk", () => [import("./templates/docs/repl-sdk.gts")]), +// bundle("docs.ember-repl", () => [import("./templates/docs/ember-repl.gts")]), +// bundle("docs.embedding", () => [import("./templates/docs/embedding.gts")]), +// bundle("docs.editor", () => [import("./templates/docs/editor.gts")]), +// bundle("docs.whatever", () => [ +// import("./the/template.gts"), +// import("./the/route.ts"), +// import("./the/controller.ts"), +// ]), +// ]; diff --git a/examples/ember/cell-selection/app/templates/application.gts b/examples/ember/cell-selection/app/templates/application.gts new file mode 100644 index 0000000000..b65063efa0 --- /dev/null +++ b/examples/ember/cell-selection/app/templates/application.gts @@ -0,0 +1,553 @@ +import Component from '@glimmer/component' +import { tracked } from '@glimmer/tracking' +import { on } from '@ember/modifier' +import { faker } from '@faker-js/faker' +import { createMultiHotkeyHandler } from '@tanstack/hotkeys' +import { + FlexRenderCell, + FlexRenderHeader, + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createColumnHelper, + createSortedRowModel, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_datetime, + sortFn_text, + tableFeatures, + useTable, + type Cell, + type Column, + type Header, + type Row, + type Table, +} from '@tanstack/ember-table' + +import { makeData, type Person } from '../utils/make-data' + +const features = tableFeatures({ + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + rowSortingFeature, + sortedRowModel: createSortedRowModel(), + sortFns: { + alphanumeric: sortFn_alphanumeric, + datetime: sortFn_datetime, + text: sortFn_text, + }, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { header: 'First Name' }), + columnHelper.accessor('lastName', { header: 'Last Name' }), + columnHelper.accessor('age', { header: 'Age' }), + columnHelper.accessor('visits', { header: 'Visits' }), + columnHelper.accessor('status', { header: 'Status' }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + // enableCellSelection: false, // this column opts out of cell selection + }), + columnHelper.accessor('email', { header: 'Email' }), + columnHelper.accessor('phone', { header: 'Phone' }), + columnHelper.accessor('city', { header: 'City' }), + columnHelper.accessor('country', { header: 'Country' }), + columnHelper.accessor('department', { header: 'Department' }), + columnHelper.accessor('salary', { header: 'Salary' }), +]) + +// Serializing a selection for the clipboard is a userland concern: the table +// hands back `getSelectedCellRangesData()` as raw values, and the delimiter, +// the null representation, and the quoting rules are all yours to pick. This is +// the spreadsheet-flavored version. +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + + // spreadsheets expect a field to be quoted once it contains a delimiter, a + // newline, or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') // blank line between disjoint rectangles +} + +// Ember templates extract function references without binding, so every table +// or cell method used from the template needs a helper that calls it on the +// correct object. +const getVisibleCells = ( + row: Row, +): Array> => row.getVisibleCells() + +const canSelect = (cell: Cell): boolean => + cell.getCanSelect() + +const cellTabIndex = (cell: Cell): number => + cell.getTabIndex() + +const startHandler = (cell: Cell) => + cell.getSelectionStartHandler() + +const extendHandler = (cell: Cell) => + cell.getSelectionExtendHandler() + +const cellClass = (cell: Cell): string => { + // Most cells in a large grid are unselected, so bail before asking for edges. + // getSelectionEdges() would otherwise resolve the cell's position a second + // time just to discover it is not selected. + if (!cell.getIsSelected()) { + return cell.getIsFocused() + ? 'cell-selectable cell-focused' + : 'cell-selectable' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell-selectable', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} + +const isVisible = (column: Column): boolean => + column.getIsVisible() + +const toggleVisibility = (column: Column) => + column.getToggleVisibilityHandler() + +const canSort = (header: Header): boolean => + header.column.getCanSort() + +const toggleSorting = (header: Header) => + header.column.getToggleSortingHandler() ?? (() => {}) + +const sortIndicator = ( + header: Header, +): string => { + const sorted = header.column.getIsSorted() + return sorted === 'asc' ? ' 🔼' : sorted === 'desc' ? ' 🔽' : '' +} + +const canPin = (header: Header): boolean => + header.column.getCanPin() + +const pinnedAt = ( + header: Header, +): string | false => header.column.getIsPinned() + +const pinStart = (header: Header) => () => + header.column.pin('start') +const pinEnd = (header: Header) => () => + header.column.pin('end') +const unpin = (header: Header) => () => + header.column.pin(false) + +const not = (value: unknown): boolean => !value +const eq = (a: unknown, b: unknown): boolean => String(a) === String(b) + +export default class CellSelectionTable extends Component { + @tracked data: Array = makeData(20) + + private isFirstColumnLayout = true + private lastLayoutKey = '' + + table = useTable(() => ({ + features, + columns, + data: this.data, + getRowId: (row: Person) => row.id, + enableCellSelection: true, // enable cell selection for all cells + // initialState: { cellSelection: [] }, // select cells on first render + // atoms: { cellSelection: cellSelectionAtom }, // own selection state with an external atom + // state: { cellSelection }, // classic controlled state; pair with onCellSelectionChange + // onCellSelectionChange: setCellSelection, + // enableCellRangeSelection: false, // disable Shift-click and drag ranges; default true + // enableMultiCellRangeSelection: false, // allow only one rectangle at a time; default true + // enableCellSelectionDrag: false, // disable drag-to-select; default true + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), // use Meta instead of Shift + })) + + // Typed getters rather than reading this.table.* directly in the template: + // Glint loses feature-method inference through an inline useTable in a + // templated class, so hopping through a getter keeps the types. + private get tableInstance(): Table { + return this.table as unknown as Table + } + + get headerGroups() { + return this.tableInstance.getHeaderGroups() + } + + get rows() { + return this.tableInstance.getRowModel().rows + } + + get leafColumns() { + return this.tableInstance.getAllLeafColumns() + } + + get allColumnsVisible() { + return this.tableInstance.getIsAllColumnsVisible() + } + + // Ember's tracked signals keep these reads fresh, so there is no equivalent + // of React's per-row Subscribe to reach for here. + get selectedCellCount() { + this.resetOnLayoutChange() + return this.tableInstance.getSelectedCellCount().toLocaleString() + } + + get selectedRowCount() { + return this.tableInstance.getCellSelectionRowIds().length.toLocaleString() + } + + get selectedColumnCount() { + return this.tableInstance.getCellSelectionColumnIds().length + } + + get stringifiedState() { + return this.data.length < 1_001 + ? JSON.stringify(this.tableInstance.store.get(), null, 2) + : '' + } + + // optionally, reset the cellSelection state not only when data changes, but + // also when column order, pinning, visibility, or sorting changes + // customize this to your needs + private resetOnLayoutChange() { + const table = this.tableInstance + const layoutKey = JSON.stringify([ + table.atoms.columnOrder.get(), + table.atoms.columnPinning.get(), + table.atoms.columnVisibility.get(), + table.atoms.sorting.get(), + ]) + + if (this.isFirstColumnLayout) { + this.isFirstColumnLayout = false + this.lastLayoutKey = layoutKey + return + } + + if (layoutKey !== this.lastLayoutKey) { + this.lastLayoutKey = layoutKey + queueMicrotask(() => table.resetCellSelection(true)) + } + } + + toggleAllVisibility = (event: Event) => { + this.tableInstance.getToggleAllColumnsVisibilityHandler()(event) + } + + // keyboard navigation is TanStack Hotkeys driving the table's imperative + // APIs; table-core ships no keydown handling of its own. Ember has no hotkeys + // adapter, so the framework-agnostic core handler is used directly. + onGridKeyDown = createMultiHotkeyHandler({ + ArrowUp: () => this.tableInstance.moveCellSelection('up'), + ArrowDown: () => this.tableInstance.moveCellSelection('down'), + ArrowLeft: () => this.tableInstance.moveCellSelection('left'), + ArrowRight: () => this.tableInstance.moveCellSelection('right'), + 'Shift+ArrowUp': () => this.tableInstance.extendCellSelection('up'), + 'Shift+ArrowDown': () => this.tableInstance.extendCellSelection('down'), + 'Shift+ArrowLeft': () => this.tableInstance.extendCellSelection('left'), + 'Shift+ArrowRight': () => this.tableInstance.extendCellSelection('right'), + 'Mod+A': () => this.tableInstance.selectAllCells(), + Escape: () => this.tableInstance.resetCellSelection(true), + 'Mod+C': () => this.copySelection(), + }) as unknown as (event: KeyboardEvent) => void + + refreshData = () => { + this.data = makeData(20) + } + + stressTest = () => { + this.data = makeData(1_000) + } + + shuffleColumns = () => { + this.tableInstance.setColumnOrder( + faker.helpers.shuffle(this.leafColumns.map((column) => column.id)), + ) + } + + reverseColumns = () => { + this.tableInstance.setColumnOrder( + [...this.leafColumns.map((column) => column.id)].reverse(), + ) + } + + resetColumnOrder = () => this.tableInstance.resetColumnOrder() + resetColumnPinning = () => this.tableInstance.resetColumnPinning() + resetColumnVisibility = () => this.tableInstance.resetColumnVisibility() + selectAllCells = () => this.tableInstance.selectAllCells() + clearSelection = () => this.tableInstance.resetCellSelection(true) + + copySelection = () => { + void navigator.clipboard.writeText( + toTsv(this.tableInstance.getSelectedCellRangesData()), + ) + } + + logRangesData = () => { + console.info( + 'table.getSelectedCellRangesData()', + this.tableInstance.getSelectedCellRangesData(), + ) + } + + +} + +// `not` is exported so the template compiler keeps it referenced +export { not } diff --git a/examples/ember/cell-selection/app/utils/make-data.ts b/examples/ember/cell-selection/app/utils/make-data.ts new file mode 100644 index 0000000000..bf4fa83a77 --- /dev/null +++ b/examples/ember/cell-selection/app/utils/make-data.ts @@ -0,0 +1,64 @@ +import { faker } from '@faker-js/faker' + +// twice the usual example's field count, so there are enough columns to +// meaningfully hide, reorder, and pin while a cell selection is active +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + email: string + phone: string + city: string + country: string + department: string + salary: number + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0]!, + email: faker.internet.email(), + phone: faker.phone.number(), + city: faker.location.city(), + country: faker.location.country(), + department: faker.commerce.department(), + salary: faker.number.int({ min: 40_000, max: 200_000 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] ?? 0 + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/ember/cell-selection/babel.config.js b/examples/ember/cell-selection/babel.config.js new file mode 100644 index 0000000000..10915e4299 --- /dev/null +++ b/examples/ember/cell-selection/babel.config.js @@ -0,0 +1,49 @@ +import { buildMacros } from '@embroider/macros/babel' + +const macros = buildMacros({ + configure(config) { + if (process.env.EMBER_ENV === 'test') { + config.enableRuntimeMode() + } + }, +}) + +export default { + plugins: [ + [ + '@babel/plugin-transform-typescript', + { + allExtensions: true, + onlyRemoveTypeImports: true, + allowDeclareFields: true, + }, + ], + [ + 'babel-plugin-ember-template-compilation', + { + transforms: [...macros.templateMacros], + }, + ], + [ + 'module:decorator-transforms', + { + runtime: { + import: import.meta.resolve('decorator-transforms/runtime-esm'), + }, + }, + ], + [ + '@babel/plugin-transform-runtime', + { + absoluteRuntime: import.meta.dirname, + useESModules: true, + regenerator: false, + }, + ], + ...macros.babelMacros, + ], + + generatorOpts: { + compact: false, + }, +} diff --git a/examples/ember/cell-selection/index.html b/examples/ember/cell-selection/index.html new file mode 100644 index 0000000000..b9dbf7e836 --- /dev/null +++ b/examples/ember/cell-selection/index.html @@ -0,0 +1,17 @@ + + + + + Minimal App + + + + + + + diff --git a/examples/ember/cell-selection/package.json b/examples/ember/cell-selection/package.json new file mode 100644 index 0000000000..7ad42c4935 --- /dev/null +++ b/examples/ember/cell-selection/package.json @@ -0,0 +1,59 @@ +{ + "name": "tanstack-ember-table-example-cell-selection", + "version": "0.0.0", + "private": true, + "type": "module", + "imports": { + "#app/*": "./app/*", + "#config": "./app/config.ts", + "#components/*": "./app/components/*", + "#services/*": "./app/services/*", + "#test-helpers/*": "./tests/helpers/*", + "#utils/*": "./app/utils/*" + }, + "exports": { + "./tests/*": "./tests/*", + "./*": "./app/*" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts", + "test:types": "ember-tsc --noEmit" + }, + "dependencies": { + "@embroider/macros": "1.20.5", + "@embroider/router": "3.0.6", + "@faker-js/faker": "^10.5.0", + "@glimmer/component": "2.1.1", + "@tanstack/ember-table": "^9.0.0-beta.56", + "@tanstack/hotkeys": "^0.8.0", + "decorator-transforms": "2.4.0", + "ember-source": "7.0.0", + "ember-strict-application-resolver": "0.1.1" + }, + "devDependencies": { + "@babel/core": "7.29.7", + "@babel/plugin-transform-runtime": "7.29.7", + "@babel/plugin-transform-typescript": "7.29.7", + "@babel/runtime": "7.29.7", + "@ember/app-tsconfig": "2.0.0", + "@embroider/core": "4.6.2", + "@glint/ember-tsc": "1.8.11", + "@glint/template": "1.7.8", + "@glint/tsserver-plugin": "2.5.17", + "@nullvoxpopuli/ember-vite": "1.0.3", + "@rollup/plugin-babel": "7.1.0", + "babel-plugin-ember-template-compilation": "4.0.0", + "typescript": "6.0.3", + "vite": "^8.1.0" + }, + "engines": { + "node": ">= 24" + }, + "ember": { + "edition": "octane" + } +} diff --git a/examples/ember/cell-selection/tests/e2e/smoke.spec.ts b/examples/ember/cell-selection/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/ember/cell-selection/tests/e2e/smoke.spec.ts @@ -0,0 +1,104 @@ +import { expect, test } from '@playwright/test' +import type { Locator, Page } from '@playwright/test' +import path from 'node:path' +import { startExampleServer } from '../../../../../tests/e2e/helpers/startExampleServer' + +const exampleDir = path.resolve() + +function collectPageErrors(page: Page) { + const errors: Array = [] + + page.on('pageerror', (error) => { + errors.push(error.message) + }) + + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()) + } + }) + + return errors +} + +async function openExample(page: Page) { + const server = await startExampleServer(exampleDir) + const errors = collectPageErrors(page) + + await page.route( + 'https://unpkg.com/react-scan/dist/auto.global.js', + (route) => + route.fulfill({ + contentType: 'application/javascript', + body: '', + }), + ) + + await page.goto(server.url) + + return { errors, server } +} + +function getTable(page: Page) { + return page + .locator('table:visible, .divTable:visible') + .filter({ has: page.locator('thead th, .thead .th') }) + .filter({ has: page.locator('tbody tr, .tbody .tr') }) + .first() +} + +function getHeaderCells(table: Locator) { + return table.locator('thead th, .thead .th') +} + +function getBodyRows(table: Locator) { + return table.locator('tbody tr, .tbody .tr') +} + +async function getFirstBodyRowData(table: Locator) { + const row = getBodyRows(table).first() + const firstInput = row + .locator('input:not([type="checkbox"]):not([type="radio"])') + .first() + + if ((await firstInput.count()) > 0 && (await firstInput.isVisible())) { + return firstInput.inputValue() + } + + const text = await row.textContent() + return text?.replace(/\s+/g, ' ').trim() ?? '' +} + +test('renders the table without crashing', async ({ page }) => { + const { errors, server } = await openExample(page) + + try { + const table = getTable(page) + const bodyRows = getBodyRows(table) + + await expect(table).toBeVisible() + await expect(getHeaderCells(table).first()).toBeVisible() + await expect(bodyRows.first()).toBeVisible() + + const regenerateButton = page + .getByRole('button', { name: /^Regenerate Data$/i }) + .first() + + if ((await regenerateButton.count()) > 0) { + await expect(regenerateButton).toBeVisible() + + const firstRowBefore = await getFirstBodyRowData(table) + + await regenerateButton.click() + + await expect + .poll(() => getFirstBodyRowData(table)) + .not.toBe(firstRowBefore) + await expect(bodyRows.first()).toBeVisible() + } + + expect(errors).toEqual([]) + } finally { + await server.close() + } +}) diff --git a/examples/ember/cell-selection/tsconfig.json b/examples/ember/cell-selection/tsconfig.json new file mode 100644 index 0000000000..5aac8ced49 --- /dev/null +++ b/examples/ember/cell-selection/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@ember/app-tsconfig", + "compilerOptions": { + "allowJs": true, + "types": [ + "ember-source/types", + "@embroider/core/virtual", + "vite/client", + "@glint/ember-tsc/types" + ] + } +} diff --git a/examples/ember/cell-selection/vite.config.mjs b/examples/ember/cell-selection/vite.config.mjs new file mode 100644 index 0000000000..25d29c7c5e --- /dev/null +++ b/examples/ember/cell-selection/vite.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import { ember } from '@nullvoxpopuli/ember-vite' + +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [ember()], +}) diff --git a/examples/lit/cell-selection/.devcontainer/devcontainer.json b/examples/lit/cell-selection/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/lit/cell-selection/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/lit/cell-selection/.gitignore b/examples/lit/cell-selection/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/lit/cell-selection/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/lit/cell-selection/index.html b/examples/lit/cell-selection/index.html new file mode 100644 index 0000000000..1dccfa81b6 --- /dev/null +++ b/examples/lit/cell-selection/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + +
+ + + + diff --git a/examples/lit/cell-selection/package.json b/examples/lit/cell-selection/package.json new file mode 100644 index 0000000000..688a725b54 --- /dev/null +++ b/examples/lit/cell-selection/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-lit-table-example-cell-selection", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc --noEmit", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "dependencies": { + "@faker-js/faker": "^10.5.0", + "@tanstack/lit-hotkeys": "^0.11.0", + "@tanstack/lit-table": "^9.0.0-beta.56", + "lit": "^3.3.3" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/lit/cell-selection/src/main.ts b/examples/lit/cell-selection/src/main.ts new file mode 100644 index 0000000000..01f053e2da --- /dev/null +++ b/examples/lit/cell-selection/src/main.ts @@ -0,0 +1,531 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, css, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { faker } from '@faker-js/faker' +import { createMultiHotkeyHandler } from '@tanstack/lit-hotkeys' +import { + FlexRender, + TableController, + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createSortedRowModel, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_datetime, + sortFn_text, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { Cell, ColumnDef, LitTable } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const features = tableFeatures({ + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + rowSortingFeature, + sortedRowModel: createSortedRowModel(), + sortFns: { + alphanumeric: sortFn_alphanumeric, + datetime: sortFn_datetime, + text: sortFn_text, + }, +}) + +// Serializing a selection for the clipboard is a userland concern: the table +// hands back `getSelectedCellRangesData()` as raw values, and the delimiter, +// the null representation, and the quoting rules are all yours to pick. This is +// the spreadsheet-flavored version. +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + + // spreadsheets expect a field to be quoted once it contains a delimiter, a + // newline, or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') // blank line between disjoint rectangles +} + +function getCellClassName(cell: Cell) { + // Most cells in a large grid are unselected, so bail before asking for edges. + // getSelectionEdges() would otherwise resolve the cell's position a second + // time just to discover it is not selected. + if (!cell.getIsSelected()) { + return cell.getIsFocused() + ? 'cell-selectable cell-focused' + : 'cell-selectable' + } + + const edges = cell.getSelectionEdges() + + return [ + 'cell-selectable', + 'cell-selected', + cell.getIsFocused() && 'cell-focused', + edges.top && 'cell-edge-top', + edges.right && 'cell-edge-right', + edges.bottom && 'cell-edge-bottom', + edges.left && 'cell-edge-left', + ] + .filter(Boolean) + .join(' ') +} + +const columns: Array> = [ + { accessorKey: 'firstName', header: 'First Name' }, + { accessorFn: (row) => row.lastName, id: 'lastName', header: 'Last Name' }, + { accessorKey: 'age', header: 'Age' }, + { accessorKey: 'visits', header: 'Visits' }, + { accessorKey: 'status', header: 'Status' }, + { + accessorKey: 'progress', + header: 'Profile Progress', + // enableCellSelection: false, // this column opts out of cell selection + }, + { accessorKey: 'email', header: 'Email' }, + { accessorKey: 'phone', header: 'Phone' }, + { accessorKey: 'city', header: 'City' }, + { accessorKey: 'country', header: 'Country' }, + { accessorKey: 'department', header: 'Department' }, + { + accessorKey: 'salary', + header: 'Salary', + cell: (info) => (info.getValue() as number).toLocaleString(), + }, +] + +@customElement('lit-cell-selection-example') +export class LitCellSelectionExample extends LitElement { + private tableController = new TableController(this) + + @state() + private _data = makeData(20) + + // tracks whether the layout-change reset should skip its first run + private _isFirstColumnLayout = true + private _lastLayoutKey = '' + + // Lit renders into shadow DOM, so the demo styles live here rather than in a + // global stylesheet. The selection outline uses inset box-shadows: on a + // border-collapse table a thicker border widens the shared grid line, which + // would change row heights as cells become selected. + static styles = css` + :host { + display: block; + font-family: sans-serif; + font-size: 14px; + padding: 0.5rem; + } + + table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; + } + + th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + } + + td { + border-right: 1px solid lightgray; + padding: 2px 4px; + } + + td:last-child { + border-right: 0; + } + + tfoot { + color: gray; + } + + button { + margin-right: 0.5rem; + } + + .sortable-header { + cursor: pointer; + user-select: none; + } + + .pin-actions { + display: flex; + gap: 2px; + justify-content: center; + } + + .pin-button { + margin: 0; + padding: 0 4px; + } + + .column-toggle-panel { + display: inline-block; + border: 1px solid lightgray; + padding: 0.25rem 0.5rem; + } + + .cell-selectable { + user-select: none; + cursor: cell; + --cell-edge-top: 0 0 0 0 transparent; + --cell-edge-right: 0 0 0 0 transparent; + --cell-edge-bottom: 0 0 0 0 transparent; + --cell-edge-left: 0 0 0 0 transparent; + box-shadow: + inset var(--cell-edge-top), + inset var(--cell-edge-right), + inset var(--cell-edge-bottom), + inset var(--cell-edge-left); + } + + .cell-selected { + background: #dbeafe; + } + + .cell-focused { + background: #bfdbfe; + } + + .cell-edge-top { + --cell-edge-top: 0 2px 0 0 #2563eb; + } + + .cell-edge-right { + --cell-edge-right: -2px 0 0 0 #2563eb; + } + + .cell-edge-bottom { + --cell-edge-bottom: 0 -2px 0 0 #2563eb; + } + + .cell-edge-left { + --cell-edge-left: 2px 0 0 0 #2563eb; + } + ` + + private copySelection(table: LitTable) { + void navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData())) + } + + // optionally, reset the cellSelection state not only when data changes, but + // also when column order, pinning, visibility, or sorting changes + // customize this to your needs + private resetOnLayoutChange(table: LitTable) { + const layoutKey = JSON.stringify([ + table.atoms.columnOrder.get(), + table.atoms.columnPinning.get(), + table.atoms.columnVisibility.get(), + table.atoms.sorting.get(), + ]) + + if (this._isFirstColumnLayout) { + this._isFirstColumnLayout = false + this._lastLayoutKey = layoutKey + return + } + + if (layoutKey !== this._lastLayoutKey) { + this._lastLayoutKey = layoutKey + queueMicrotask(() => table.resetCellSelection(true)) + } + } + + protected render() { + const table = this.tableController.table( + { + features, + data: this._data, + columns, + getRowId: (row) => row.id, + enableCellSelection: true, // enable cell selection for all cells + // initialState: { cellSelection: [] }, // select cells on first render + // atoms: { cellSelection: cellSelectionAtom }, // own selection state with an external atom + // state: { cellSelection }, // classic controlled state; pair with onCellSelectionChange + // onCellSelectionChange: setCellSelection, + // enableCellRangeSelection: false, // disable Shift-click and drag ranges; default true + // enableMultiCellRangeSelection: false, // allow only one rectangle at a time; default true + // enableCellSelectionDrag: false, // disable drag-to-select; default true + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), // use Meta instead of Shift + debugTable: true, + }, + (state) => ({ + cellSelection: state.cellSelection, + sorting: state.sorting, + columnOrder: state.columnOrder, + columnPinning: state.columnPinning, + columnVisibility: state.columnVisibility, + }), + ) + + this.resetOnLayoutChange(table) + + // keyboard navigation is TanStack Hotkeys driving the table's imperative + // APIs; table-core ships no keydown handling of its own. One stateless + // handler is simpler here than eleven HotkeyControllers. + const onGridKeyDown = createMultiHotkeyHandler({ + ArrowUp: () => table.moveCellSelection('up'), + ArrowDown: () => table.moveCellSelection('down'), + ArrowLeft: () => table.moveCellSelection('left'), + ArrowRight: () => table.moveCellSelection('right'), + 'Shift+ArrowUp': () => table.extendCellSelection('up'), + 'Shift+ArrowDown': () => table.extendCellSelection('down'), + 'Shift+ArrowLeft': () => table.extendCellSelection('left'), + 'Shift+ArrowRight': () => table.extendCellSelection('right'), + 'Mod+A': () => table.selectAllCells(), + Escape: () => table.resetCellSelection(true), + 'Mod+C': () => this.copySelection(table), + }) + + return html` +
+ + +
+

+ 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. +

+

+ Hiding, reordering, and pinning columns all keep a live selection + anchored to the same cell ids. Ranges are indexed in render order, so a + pinned column moves the rectangle with it rather than splitting it. +

+
+
+ +
+ ${repeat( + table.getAllLeafColumns(), + (column) => column.id, + (column) => html` +
+ +
+ `, + )} +
+
+ + + + + +
+ +
+ ${table.getSelectedCellCount().toLocaleString()} cells selected across + ${table.getCellSelectionRowIds().length.toLocaleString()} rows and + ${table.getCellSelectionColumnIds().length} columns +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` + + ${row + .getVisibleCells() + .map((cell) => + cell.getCanSelect() + ? html`` + : html``, + )} + + `, + )} + + + + + + +
+ ${header.isPlaceholder + ? null + : html` +
+ ${FlexRender({ header })} + ${{ asc: ' 🔼', desc: ' 🔽' }[ + header.column.getIsSorted() as string + ] ?? ''} +
+ ${header.column.getCanPin() + ? html` +
+ ${header.column.getIsPinned() !== 'start' + ? html`` + : null} + ${header.column.getIsPinned() + ? html`` + : null} + ${header.column.getIsPinned() !== 'end' + ? html`` + : null} +
+ ` + : null} + `} +
+ ${FlexRender({ cell })} + ${FlexRender({ cell })}
+ Rows (${table.getRowModel().rows.length.toLocaleString()}) +
+
+
+ + + +
+
+
+ +
+
+ +
+${this._data.length < 1_001 ? JSON.stringify(table.state, null, 2) : ''}
+
+
+ + + +
+ ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'lit-cell-selection-example': LitCellSelectionExample + } +} diff --git a/examples/lit/cell-selection/src/makeData.ts b/examples/lit/cell-selection/src/makeData.ts new file mode 100644 index 0000000000..97fe7cd472 --- /dev/null +++ b/examples/lit/cell-selection/src/makeData.ts @@ -0,0 +1,64 @@ +import { faker } from '@faker-js/faker' + +// twice the usual example's field count, so there are enough columns to +// meaningfully hide, reorder, and pin while a cell selection is active +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + email: string + phone: string + city: string + country: string + department: string + salary: number + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + email: faker.internet.email(), + phone: faker.phone.number(), + city: faker.location.city(), + country: faker.location.country(), + department: faker.commerce.department(), + salary: faker.number.int({ min: 40_000, max: 200_000 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/lit/cell-selection/tests/e2e/smoke.spec.ts b/examples/lit/cell-selection/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/lit/cell-selection/tests/e2e/smoke.spec.ts @@ -0,0 +1,104 @@ +import { expect, test } from '@playwright/test' +import type { Locator, Page } from '@playwright/test' +import path from 'node:path' +import { startExampleServer } from '../../../../../tests/e2e/helpers/startExampleServer' + +const exampleDir = path.resolve() + +function collectPageErrors(page: Page) { + const errors: Array = [] + + page.on('pageerror', (error) => { + errors.push(error.message) + }) + + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()) + } + }) + + return errors +} + +async function openExample(page: Page) { + const server = await startExampleServer(exampleDir) + const errors = collectPageErrors(page) + + await page.route( + 'https://unpkg.com/react-scan/dist/auto.global.js', + (route) => + route.fulfill({ + contentType: 'application/javascript', + body: '', + }), + ) + + await page.goto(server.url) + + return { errors, server } +} + +function getTable(page: Page) { + return page + .locator('table:visible, .divTable:visible') + .filter({ has: page.locator('thead th, .thead .th') }) + .filter({ has: page.locator('tbody tr, .tbody .tr') }) + .first() +} + +function getHeaderCells(table: Locator) { + return table.locator('thead th, .thead .th') +} + +function getBodyRows(table: Locator) { + return table.locator('tbody tr, .tbody .tr') +} + +async function getFirstBodyRowData(table: Locator) { + const row = getBodyRows(table).first() + const firstInput = row + .locator('input:not([type="checkbox"]):not([type="radio"])') + .first() + + if ((await firstInput.count()) > 0 && (await firstInput.isVisible())) { + return firstInput.inputValue() + } + + const text = await row.textContent() + return text?.replace(/\s+/g, ' ').trim() ?? '' +} + +test('renders the table without crashing', async ({ page }) => { + const { errors, server } = await openExample(page) + + try { + const table = getTable(page) + const bodyRows = getBodyRows(table) + + await expect(table).toBeVisible() + await expect(getHeaderCells(table).first()).toBeVisible() + await expect(bodyRows.first()).toBeVisible() + + const regenerateButton = page + .getByRole('button', { name: /^Regenerate Data$/i }) + .first() + + if ((await regenerateButton.count()) > 0) { + await expect(regenerateButton).toBeVisible() + + const firstRowBefore = await getFirstBodyRowData(table) + + await regenerateButton.click() + + await expect + .poll(() => getFirstBodyRowData(table)) + .not.toBe(firstRowBefore) + await expect(bodyRows.first()).toBeVisible() + } + + expect(errors).toEqual([]) + } finally { + await server.close() + } +}) diff --git a/examples/lit/cell-selection/tsconfig.json b/examples/lit/cell-selection/tsconfig.json new file mode 100644 index 0000000000..b144fb1db2 --- /dev/null +++ b/examples/lit/cell-selection/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "tests/e2e", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/cell-selection/vite.config.js b/examples/lit/cell-selection/vite.config.js new file mode 100644 index 0000000000..9b5122fda5 --- /dev/null +++ b/examples/lit/cell-selection/vite.config.js @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/preact/cell-selection/.devcontainer/devcontainer.json b/examples/preact/cell-selection/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/preact/cell-selection/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/preact/cell-selection/index.html b/examples/preact/cell-selection/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/cell-selection/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/cell-selection/package.json b/examples/preact/cell-selection/package.json new file mode 100644 index 0000000000..a50d2e2357 --- /dev/null +++ b/examples/preact/cell-selection/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-preact-table-example-cell-selection", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "dependencies": { + "@faker-js/faker": "^10.5.0", + "@tanstack/preact-devtools": "^0.10.8", + "@tanstack/preact-hotkeys": "^0.10.0", + "@tanstack/preact-store": "^0.13.1", + "@tanstack/preact-table": "^9.0.0-beta.56", + "@tanstack/preact-table-devtools": "^9.0.0-beta.56", + "preact": "^10.29.2" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/preact/cell-selection/src/index.css b/examples/preact/cell-selection/src/index.css new file mode 100644 index 0000000000..8b2ba46a11 --- /dev/null +++ b/examples/preact/cell-selection/src/index.css @@ -0,0 +1,428 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled, +button[disabled] { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +/* Cell selection: a dragged rectangle is tinted, and each cell draws only the + sides that getSelectionEdges() reports as the boundary of the selection, so + any union of rectangles ends up with a single continuous outline. + + The outline is drawn with inset box-shadows rather than borders. On a + border-collapse table a thicker border widens the shared grid line, which + would make rows change height as cells become selected; box-shadow never + affects layout. Each side is its own custom property so the four edge classes + compose, which they could not do if each set `box-shadow` directly. */ +.cell-selectable { + user-select: none; + cursor: cell; + --cell-edge-top: 0 0 0 0 transparent; + --cell-edge-right: 0 0 0 0 transparent; + --cell-edge-bottom: 0 0 0 0 transparent; + --cell-edge-left: 0 0 0 0 transparent; + box-shadow: + inset var(--cell-edge-top), + inset var(--cell-edge-right), + inset var(--cell-edge-bottom), + inset var(--cell-edge-left); +} + +.cell-selected { + background: #dbeafe; +} + +.cell-focused { + background: #bfdbfe; +} + +.cell-edge-top { + --cell-edge-top: 0 2px 0 0 #2563eb; +} + +.cell-edge-right { + --cell-edge-right: -2px 0 0 0 #2563eb; +} + +.cell-edge-bottom { + --cell-edge-bottom: 0 -2px 0 0 #2563eb; +} + +.cell-edge-left { + --cell-edge-left: 2px 0 0 0 #2563eb; +} diff --git a/examples/preact/cell-selection/src/main.tsx b/examples/preact/cell-selection/src/main.tsx new file mode 100644 index 0000000000..e07fc309ef --- /dev/null +++ b/examples/preact/cell-selection/src/main.tsx @@ -0,0 +1,584 @@ +import { useEffect, useMemo, useRef, useState } from 'preact/hooks' +import { render } from 'preact' +import { faker } from '@faker-js/faker' +import { TanStackDevtools } from '@tanstack/preact-devtools' +import { + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createColumnHelper, + createSortedRowModel, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_datetime, + sortFn_text, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { + tableDevtoolsPlugin, + useTanStackTableDevtools, +} from '@tanstack/preact-table-devtools' +import { useHotkeys } from '@tanstack/preact-hotkeys' +import { useCreateAtom } from '@tanstack/preact-store' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { + Cell, + CellSelectionBounds, + CellSelectionState, +} from '@tanstack/preact-table' +import './index.css' + +const features = tableFeatures({ + cellSelectionFeature, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + rowSortingFeature, + sortedRowModel: createSortedRowModel(), + sortFns: { + alphanumeric: sortFn_alphanumeric, + datetime: sortFn_datetime, + text: sortFn_text, + }, +}) + +const columnHelper = createColumnHelper() + +// Serializing a selection for the clipboard is a userland concern: the table +// hands back `getSelectedCellRangesData()` as raw values, and the delimiter, +// the null representation, and the quoting rules are all yours to pick. This is +// the spreadsheet-flavored version. +function escapeTsvValue(value: unknown) { + const text = value == null ? '' : String(value) + + // spreadsheets expect a field to be quoted once it contains a delimiter, a + // newline, or a quote, with inner quotes doubled + return /["\t\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text +} + +function toTsv(ranges: Array>>) { + return ranges + .map((grid) => + grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), + ) + .join('\n\n') // blank line between disjoint rectangles +} + +function App() { + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + // enableCellSelection: false, // this column opts out of cell selection + footer: (props) => props.column.id, + }), + columnHelper.accessor('email', { + header: 'Email', + footer: (props) => props.column.id, + }), + columnHelper.accessor('phone', { + header: 'Phone', + footer: (props) => props.column.id, + }), + columnHelper.accessor('city', { + header: 'City', + footer: (props) => props.column.id, + }), + columnHelper.accessor('country', { + header: 'Country', + footer: (props) => props.column.id, + }), + columnHelper.accessor('department', { + header: 'Department', + footer: (props) => props.column.id, + }), + columnHelper.accessor('salary', { + header: 'Salary', + cell: (info) => info.getValue().toLocaleString(), + footer: (props) => props.column.id, + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + + // optionally, raise the selection state to your own atom + const cellSelectionAtom = useCreateAtom([]) + + const table = useTable( + { + key: 'cell-selection', // needed for devtools + features, + atoms: { + cellSelection: cellSelectionAtom, + }, + columns, + data, + getRowId: (row) => row.id, + enableCellSelection: true, // enable cell selection for all cells + // initialState: { cellSelection: [] }, // select cells on first render + // state: { cellSelection }, // classic controlled state; pair with onCellSelectionChange + // onCellSelectionChange: setCellSelection, + // enableCellRangeSelection: false, // disable Shift-click and drag ranges; default true + // enableMultiCellRangeSelection: false, // allow only one rectangle at a time; default true + // enableCellSelectionDrag: false, // disable drag-to-select; default true + // isCellRangeSelectionEvent: event => Boolean(event.metaKey), // use Meta instead of Shift + debugTable: true, + }, + // Every slice that changes the table's structure has to be here, or the + // headers and controls will not re-render when you pin, hide, or reorder. + // cellSelection is the deliberate exception: it is read through + // instead, so a drag does not re-render the whole example. + (state) => ({ + sorting: state.sorting, + columnVisibility: state.columnVisibility, + columnOrder: state.columnOrder, + columnPinning: state.columnPinning, + }), + ) + + useTanStackTableDevtools(table) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + // optionally, reset the cellSelection state not only when data changes, but also when column order, pinning, visibility, or sorting changes + // customize this to your needs + const isFirstColumnLayout = useRef(true) + useEffect(() => { + if (isFirstColumnLayout.current) { + isFirstColumnLayout.current = false + return + } + table.resetCellSelection(true) + }, [ + table.state.columnOrder, + table.state.columnPinning, + table.state.columnVisibility, + table.state.sorting, + ]) + + const gridRef = useRef(null) + + // keyboard navigation is TanStack Hotkeys driving the table's imperative APIs; + // table-core ships no keydown handling of its own + useHotkeys( + [ + { hotkey: 'ArrowUp', callback: () => table.moveCellSelection('up') }, + { hotkey: 'ArrowDown', callback: () => table.moveCellSelection('down') }, + { hotkey: 'ArrowLeft', callback: () => table.moveCellSelection('left') }, + { + hotkey: 'ArrowRight', + callback: () => table.moveCellSelection('right'), + }, + { + hotkey: 'Shift+ArrowUp', + callback: () => table.extendCellSelection('up'), + }, + { + hotkey: 'Shift+ArrowDown', + callback: () => table.extendCellSelection('down'), + }, + { + hotkey: 'Shift+ArrowLeft', + callback: () => table.extendCellSelection('left'), + }, + { + hotkey: 'Shift+ArrowRight', + callback: () => table.extendCellSelection('right'), + }, + { hotkey: 'Mod+A', callback: () => table.selectAllCells() }, + { hotkey: 'Escape', callback: () => table.resetCellSelection(true) }, + { + hotkey: 'Mod+C', + callback: () => { + void navigator.clipboard.writeText( + toTsv(table.getSelectedCellRangesData()), + ) + }, + }, + ], + { target: gridRef }, + ) + + return ( +
+
+ + +
+
+

+ 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. +

+

+ Hiding, reordering, and pinning columns all keep a live selection + anchored to the same cell ids. Ranges are indexed in render order, so a + pinned column moves the rectangle with it rather than splitting it. +

+
+
+ +
+ {table.getAllLeafColumns().map((column) => ( +
+ +
+ ))} +
+
+
+ + + + + +
+
+ + {() => ( +
+ {table.getSelectedCellCount().toLocaleString()} cells selected + across {table.getCellSelectionRowIds().length.toLocaleString()} rows + and {table.getCellSelectionColumnIds().length} columns +
+ )} +
+
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => ( + // one subscription per row rather than one around the whole + // tbody, so a drag only re-renders the rows whose key changed + + rowSelectionKey( + ranges, + table.getCellSelectionBounds(), + row.getDisplayIndex(), + row.id, + ) + } + > + {() => ( + + {row.getVisibleCells().map((cell) => + cell.getCanSelect() ? ( + + ) : ( + + ), + )} + + )} + + ))} + + + + + + +
+ {header.isPlaceholder ? null : ( + <> +
+ + {{ asc: ' 🔼', desc: ' 🔽' }[ + header.column.getIsSorted() as string + ] ?? null} +
+ {header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'start' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'end' ? ( + + ) : null} +
+ )} + + )} +
+ + + +
+ Rows ({table.getRowModel().rows.length.toLocaleString()}) +
+
+
+
+ + + +
+
+
+
+ +
+
+ + state}> + {(state) => ( +
{data.length < 1_001 && JSON.stringify(state, null, 2)}
+ )} +
+
+
+ + {/* scratch area for pasting a copied selection back in, to eyeball + the tab-separated shape. It sits outside the grid ref, so the + table hotkeys never intercept typing or Mod+V in here. */} + +
+
diff --git a/examples/svelte/cell-selection/src/index.css b/examples/svelte/cell-selection/src/index.css new file mode 100644 index 0000000000..24ed5d145b --- /dev/null +++ b/examples/svelte/cell-selection/src/index.css @@ -0,0 +1,421 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +/* Cell selection: a dragged rectangle is tinted, and each cell draws only the + sides that getSelectionEdges() reports as the boundary of the selection, so + any union of rectangles ends up with a single continuous outline. + + The outline is drawn with inset box-shadows rather than borders. On a + border-collapse table a thicker border widens the shared grid line, which + would make rows change height as cells become selected; box-shadow never + affects layout. Each side is its own custom property so the four edge classes + compose, which they could not do if each set `box-shadow` directly. */ +.cell-selectable { + user-select: none; + cursor: cell; + --cell-edge-top: 0 0 0 0 transparent; + --cell-edge-right: 0 0 0 0 transparent; + --cell-edge-bottom: 0 0 0 0 transparent; + --cell-edge-left: 0 0 0 0 transparent; + box-shadow: + inset var(--cell-edge-top), + inset var(--cell-edge-right), + inset var(--cell-edge-bottom), + inset var(--cell-edge-left); +} + +.cell-selected { + background: #dbeafe; +} + +.cell-focused { + background: #bfdbfe; +} + +.cell-edge-top { + --cell-edge-top: 0 2px 0 0 #2563eb; +} + +.cell-edge-right { + --cell-edge-right: -2px 0 0 0 #2563eb; +} + +.cell-edge-bottom { + --cell-edge-bottom: 0 -2px 0 0 #2563eb; +} + +.cell-edge-left { + --cell-edge-left: 2px 0 0 0 #2563eb; +} diff --git a/examples/svelte/cell-selection/src/main.ts b/examples/svelte/cell-selection/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/cell-selection/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/cell-selection/src/makeData.ts b/examples/svelte/cell-selection/src/makeData.ts new file mode 100644 index 0000000000..97fe7cd472 --- /dev/null +++ b/examples/svelte/cell-selection/src/makeData.ts @@ -0,0 +1,64 @@ +import { faker } from '@faker-js/faker' + +// twice the usual example's field count, so there are enough columns to +// meaningfully hide, reorder, and pin while a cell selection is active +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + email: string + phone: string + city: string + country: string + department: string + salary: number + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + email: faker.internet.email(), + phone: faker.phone.number(), + city: faker.location.city(), + country: faker.location.country(), + department: faker.commerce.department(), + salary: faker.number.int({ min: 40_000, max: 200_000 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/cell-selection/src/vite-env.d.ts b/examples/svelte/cell-selection/src/vite-env.d.ts new file mode 100644 index 0000000000..66addc6b05 --- /dev/null +++ b/examples/svelte/cell-selection/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module '*.css' {} diff --git a/examples/svelte/cell-selection/svelte.config.js b/examples/svelte/cell-selection/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/cell-selection/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/cell-selection/tests/e2e/smoke.spec.ts b/examples/svelte/cell-selection/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..4035d8450b --- /dev/null +++ b/examples/svelte/cell-selection/tests/e2e/smoke.spec.ts @@ -0,0 +1,132 @@ +import { expect, test } from '@playwright/test' +import type { Locator, Page } from '@playwright/test' +import path from 'node:path' +import { startExampleServer } from '../../../../../tests/e2e/helpers/startExampleServer' + +const exampleDir = path.resolve() + +function collectPageErrors(page: Page) { + const errors: Array = [] + + page.on('pageerror', (error) => { + errors.push(error.message) + }) + + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()) + } + }) + + return errors +} + +async function openExample(page: Page) { + const server = await startExampleServer(exampleDir) + const errors = collectPageErrors(page) + + await page.route( + 'https://unpkg.com/react-scan/dist/auto.global.js', + (route) => + route.fulfill({ + contentType: 'application/javascript', + body: '', + }), + ) + + await page.goto(server.url) + + return { errors, server } +} + +function getTable(page: Page) { + return page + .locator('table:visible, .divTable:visible') + .filter({ has: page.locator('thead th, .thead .th') }) + .filter({ has: page.locator('tbody tr, .tbody .tr') }) + .first() +} + +function getHeaderCells(table: Locator) { + return table.locator('thead th, .thead .th') +} + +function getBodyRows(table: Locator) { + return table.locator('tbody tr, .tbody .tr') +} + +async function getFirstBodyRowData(table: Locator) { + const row = getBodyRows(table).first() + const firstInput = row + .locator('input:not([type="checkbox"]):not([type="radio"])') + .first() + + if ((await firstInput.count()) > 0 && (await firstInput.isVisible())) { + return firstInput.inputValue() + } + + const text = await row.textContent() + return text?.replace(/\s+/g, ' ').trim() ?? '' +} + +test('renders the table without crashing', async ({ page }) => { + const { errors, server } = await openExample(page) + + try { + const table = getTable(page) + const bodyRows = getBodyRows(table) + + await expect(table).toBeVisible() + await expect(getHeaderCells(table).first()).toBeVisible() + await expect(bodyRows.first()).toBeVisible() + + const regenerateButton = page + .getByRole('button', { name: /^Regenerate Data$/i }) + .first() + + if ((await regenerateButton.count()) > 0) { + await expect(regenerateButton).toBeVisible() + + const firstRowBefore = await getFirstBodyRowData(table) + + await regenerateButton.click() + + await expect + .poll(() => getFirstBodyRowData(table)) + .not.toBe(firstRowBefore) + await expect(bodyRows.first()).toBeVisible() + } + + expect(errors).toEqual([]) + } finally { + await server.close() + } +}) + +test('selects an inclusive cell range with Shift-click', async ({ page }) => { + const { errors, server } = await openExample(page) + + try { + const table = getTable(page) + const rows = getBodyRows(table) + + 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'] }) + + // the rectangle covers 3 rows x 3 columns, and its anchor stays put + await expect(page.locator('td.cell-selected')).toHaveCount(9) + await expect(rows.nth(0).locator('td').nth(0)).toHaveClass(/cell-focused/) + await expect(rows.nth(3).locator('td').nth(0)).not.toHaveClass( + /cell-selected/, + ) + expect(errors).toEqual([]) + } finally { + await server.close() + } +}) diff --git a/examples/svelte/cell-selection/tsconfig.json b/examples/svelte/cell-selection/tsconfig.json new file mode 100644 index 0000000000..4a0b0b54b3 --- /dev/null +++ b/examples/svelte/cell-selection/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "tests/e2e", + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/cell-selection/vite.config.js b/examples/svelte/cell-selection/vite.config.js new file mode 100644 index 0000000000..3c3718bc18 --- /dev/null +++ b/examples/svelte/cell-selection/vite.config.js @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/vue/cell-selection/.devcontainer/devcontainer.json b/examples/vue/cell-selection/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/vue/cell-selection/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/vue/cell-selection/.gitignore b/examples/vue/cell-selection/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/examples/vue/cell-selection/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/vue/cell-selection/env.d.ts b/examples/vue/cell-selection/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/cell-selection/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/cell-selection/index.html b/examples/vue/cell-selection/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/cell-selection/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/cell-selection/package.json b/examples/vue/cell-selection/package.json new file mode 100644 index 0000000000..24ffaf3d96 --- /dev/null +++ b/examples/vue/cell-selection/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-vue-table-example-cell-selection", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "dependencies": { + "@faker-js/faker": "^10.5.0", + "@tanstack/vue-devtools": "^0.2.22", + "@tanstack/vue-hotkeys": "^0.10.0", + "@tanstack/vue-table": "^9.0.0-beta.56", + "@tanstack/vue-table-devtools": "^9.0.0-beta.56", + "vue": "^3.5.38" + }, + "devDependencies": { + "@types/node": "^26.0.0", + "@vitejs/plugin-vue": "^6.0.7", + "@vitejs/plugin-vue-jsx": "^5.1.6", + "typescript": "6.0.3", + "vite": "^8.1.0", + "vue-tsc": "^3.3.5" + } +} diff --git a/examples/vue/cell-selection/public/favicon.ico b/examples/vue/cell-selection/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/examples/vue/cell-selection/src/App.vue b/examples/vue/cell-selection/src/App.vue new file mode 100644 index 0000000000..e19ad54090 --- /dev/null +++ b/examples/vue/cell-selection/src/App.vue @@ -0,0 +1,452 @@ + + +