From 4d32fca650e03d51a4adc909962769386cd9e91f Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 14 Jul 2026 11:17:24 -0500 Subject: [PATCH 1/2] feat: aggregation overhaul as a standalone feature --- docs/config.json | 13 +- .../framework/alpine/guide/custom-features.md | 2 +- docs/framework/alpine/guide/grouping.md | 56 +- .../angular/guide/custom-features.md | 2 +- docs/framework/angular/guide/grouping.md | 55 +- docs/framework/angular/guide/migrating.md | 31 ++ docs/framework/ember/guide/custom-features.md | 2 +- docs/framework/ember/guide/grouping.md | 55 +- docs/framework/lit/guide/custom-features.md | 2 +- docs/framework/lit/guide/grouping.md | 54 +- docs/framework/lit/guide/migrating.md | 34 +- .../framework/preact/guide/custom-features.md | 2 +- docs/framework/preact/guide/grouping.md | 43 +- docs/framework/preact/guide/migrating.md | 34 +- docs/framework/react/guide/custom-features.md | 2 +- docs/framework/react/guide/grouping.md | 60 ++- docs/framework/react/guide/migrating.md | 33 ++ docs/framework/solid/guide/custom-features.md | 2 +- docs/framework/solid/guide/grouping.md | 56 +- docs/framework/solid/guide/migrating.md | 34 +- .../framework/svelte/guide/custom-features.md | 2 +- docs/framework/svelte/guide/grouping.md | 58 +-- docs/framework/svelte/guide/migrating.md | 34 +- docs/framework/vue/guide/custom-features.md | 2 +- docs/framework/vue/guide/grouping.md | 58 +-- docs/framework/vue/guide/migrating.md | 34 +- docs/guide/aggregation.md | 204 ++++++++ docs/guide/worker-row-models.md | 4 +- .../index/functions/aggregationFn_count.md | 42 -- .../index/functions/aggregationFn_first.md | 41 -- .../index/functions/aggregationFn_last.md | 41 -- .../index/functions/constructAggregationFn.md | 43 +- .../index/functions/constructFilterFn.md | 2 +- .../index/functions/constructSortFn.md | 2 +- .../index/functions/createGroupedRowModel.md | 10 +- .../index/functions/makeStateUpdater.md | 2 +- docs/reference/index/index.md | 29 +- .../index/interfaces/AggregationContext.md | 113 ++++ .../index/interfaces/AggregationFn.md | 59 --- .../index/interfaces/AggregationFnDef.md | 70 ++- .../interfaces/AggregationFnDescriptor.md | 52 ++ .../index/interfaces/AggregationFns.md | 4 +- .../interfaces/AggregationMergeContext.md | 165 ++++++ .../interfaces/AggregationValueContext.md | 60 +++ .../interfaces/AggregationValueResult.md | 28 + .../index/interfaces/CachedRowModel_All.md | 2 +- .../interfaces/CachedRowModel_Grouped.md | 4 +- .../index/interfaces/Cell_Aggregation.md | 26 + .../index/interfaces/Cell_ColumnGrouping.md | 22 +- docs/reference/index/interfaces/Cell_Core.md | 2 +- .../index/interfaces/Cell_FeatureMap.md | 14 +- .../index/interfaces/ColumnDef_Aggregation.md | 76 +++ .../interfaces/ColumnDef_ColumnGrouping.md | 36 +- .../index/interfaces/ColumnDef_FeatureMap.md | 30 +- .../index/interfaces/ColumnDefaultOptions.md | 6 +- docs/reference/index/interfaces/ColumnMeta.md | 2 +- .../index/interfaces/Column_Aggregation.md | 85 +++ .../index/interfaces/Column_ColumnGrouping.md | 56 +- .../reference/index/interfaces/Column_Core.md | 2 +- .../index/interfaces/Column_FeatureMap.md | 34 +- .../index/interfaces/Column_Internal.md | 6 +- .../index/interfaces/CreatedAggregationFn.md | 140 ----- .../index/interfaces/FeatureSlotPrereqs.md | 4 +- .../index/interfaces/IdIdentifier.md | 6 +- .../index/interfaces/ResolvedAggregationFn.md | 46 ++ .../interfaces/RowModelFns_Aggregation.md | 30 ++ .../index/interfaces/RowModelFns_All.md | 6 +- .../interfaces/RowModelFns_ColumnGrouping.md | 28 - .../interfaces/RowModelFns_FeatureMap.md | 8 +- .../index/interfaces/Row_Aggregation.md | 22 + .../index/interfaces/Row_ColumnGrouping.md | 12 +- docs/reference/index/interfaces/Row_Core.md | 2 +- .../index/interfaces/Row_FeatureMap.md | 26 +- .../index/interfaces/StockFeatures.md | 40 +- .../interfaces/StringHeaderIdentifier.md | 6 +- .../index/interfaces/TableFeatures.md | 44 +- .../interfaces/TableOptions_Aggregation.md | 24 + .../interfaces/TableOptions_ColumnGrouping.md | 10 +- .../index/interfaces/TableOptions_Core.md | 2 +- .../interfaces/TableOptions_FeatureMap.md | 38 +- .../index/interfaces/TableState_All.md | 2 +- .../interfaces/TableState_ColumnGrouping.md | 4 +- .../index/interfaces/Table_ColumnGrouping.md | 6 +- docs/reference/index/interfaces/Table_Core.md | 6 +- .../index/interfaces/Table_Internal.md | 8 +- .../interfaces/Table_RowModels_Grouped.md | 8 +- .../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 +- .../type-aliases/AggregationFnListItem.md | 31 ++ .../index/type-aliases/AggregationFnOption.md | 17 +- .../index/type-aliases/AggregationFnRef.md | 35 ++ .../index/type-aliases/AggregationResult.md | 24 + .../index/type-aliases/AggregationResultOf.md | 20 + .../type-aliases/BuiltInAggregationFn.md | 2 +- .../index/type-aliases/BuiltInFilterFn.md | 2 +- .../index/type-aliases/BuiltInSortFn.md | 2 +- docs/reference/index/type-aliases/Cell.md | 2 +- docs/reference/index/type-aliases/Column.md | 2 +- .../type-aliases/ColumnAggregationValue.md | 23 + .../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 +- .../type-aliases/CustomAggregationFns.md | 6 +- .../index/type-aliases/DebugOptions.md | 2 +- .../index/type-aliases/DisplayColumnDef.md | 2 +- .../type-aliases/ExtractAggregationFnKeys.md | 11 +- .../index/type-aliases/ExtractColumnMeta.md | 2 +- .../index/type-aliases/GroupColumnDef.md | 2 +- .../index/type-aliases/GroupingColumnMode.md | 2 +- .../index/type-aliases/GroupingState.md | 2 +- .../index/type-aliases/IdentifiedColumnDef.md | 2 +- docs/reference/index/type-aliases/Row.md | 2 +- .../type-aliases/StringOrTemplateHeader.md | 2 +- .../index/type-aliases/TableOptions.md | 2 +- .../index/type-aliases/TableOptions_All.md | 4 +- .../index/variables/aggregationFeature.md | 14 + .../index/variables/aggregationFn_count.md | 14 + .../index/variables/aggregationFn_extent.md | 10 +- .../index/variables/aggregationFn_first.md | 14 + .../index/variables/aggregationFn_last.md | 14 + .../index/variables/aggregationFn_max.md | 10 +- .../index/variables/aggregationFn_mean.md | 10 +- .../index/variables/aggregationFn_median.md | 10 +- .../index/variables/aggregationFn_min.md | 10 +- .../index/variables/aggregationFn_sum.md | 10 +- .../index/variables/aggregationFn_unique.md | 8 +- .../variables/aggregationFn_uniqueCount.md | 8 +- .../index/variables/aggregationFns.md | 132 +---- .../index/variables/columnGroupingFeature.md | 4 +- .../index/variables/filterFn_arrHas.md | 2 +- .../index/variables/filterFn_arrIncludes.md | 2 +- .../variables/filterFn_arrIncludesAll.md | 2 +- .../variables/filterFn_arrIncludesSome.md | 2 +- .../index/variables/filterFn_between.md | 16 + .../variables/filterFn_betweenInclusive.md | 16 + .../index/variables/filterFn_empty.md | 2 +- .../index/variables/filterFn_endsWith.md | 2 +- .../index/variables/filterFn_equals.md | 2 +- .../index/variables/filterFn_equalsString.md | 2 +- .../filterFn_equalsStringSensitive.md | 2 +- .../index/variables/filterFn_greaterThan.md | 2 +- .../filterFn_greaterThanOrEqualTo.md | 2 +- .../index/variables/filterFn_inDateRange.md | 2 +- .../index/variables/filterFn_inNumberRange.md | 2 +- .../variables/filterFn_includesString.md | 2 +- .../filterFn_includesStringSensitive.md | 2 +- .../index/variables/filterFn_lessThan.md | 2 +- .../variables/filterFn_lessThanOrEqualTo.md | 2 +- .../index/variables/filterFn_notEmpty.md | 2 +- .../index/variables/filterFn_startsWith.md | 2 +- .../index/variables/filterFn_weakEquals.md | 2 +- docs/reference/index/variables/filterFns.md | 2 +- .../index/variables/reSplitAlphaNumeric.md | 2 +- .../index/variables/sortFn_alphanumeric.md | 2 +- .../sortFn_alphanumericCaseSensitive.md | 2 +- .../reference/index/variables/sortFn_basic.md | 2 +- .../index/variables/sortFn_datetime.md | 2 +- docs/reference/index/variables/sortFn_text.md | 2 +- .../variables/sortFn_textCaseSensitive.md | 2 +- docs/reference/index/variables/sortFns.md | 2 +- .../index/variables/stockFeatures.md | 2 +- .../functions/aggregateColumnValue.md | 48 ++ .../functions/cell_getIsAggregated.md | 13 +- .../functions/cell_getIsGrouped.md | 2 +- .../functions/cell_getIsPlaceholder.md | 2 +- .../functions/column_clearSorting.md | 2 +- .../functions/column_getAggregationFn.md | 51 -- .../functions/column_getAggregationFns.md | 38 ++ .../functions/column_getAggregationValue.md | 42 ++ .../functions/column_getAutoAggregationFn.md | 22 +- .../functions/column_getCanFilter.md | 2 +- .../functions/column_getCanGroup.md | 2 +- .../functions/column_getCanMultiSort.md | 2 +- .../functions/column_getCanSort.md | 2 +- .../functions/column_getFilterIndex.md | 2 +- .../functions/column_getFilterValue.md | 2 +- .../functions/column_getFirstSortDir.md | 2 +- .../functions/column_getGroupedIndex.md | 2 +- .../functions/column_getIsFiltered.md | 2 +- .../functions/column_getIsGrouped.md | 2 +- .../functions/column_getIsSorted.md | 2 +- .../functions/column_getNextSortingOrder.md | 2 +- .../functions/column_getSortIndex.md | 2 +- .../column_getToggleGroupingHandler.md | 2 +- .../column_getToggleSortingHandler.md | 2 +- .../functions/column_setFilterValue.md | 2 +- .../functions/column_toggleGrouping.md | 2 +- .../functions/column_toggleSorting.md | 2 +- .../functions/formatAggregatedCellValue.md | 28 + .../functions/getDefaultGroupingState.md | 2 +- .../functions/normalizeAggregationRows.md | 35 ++ .../functions/row_getGroupingValue.md | 2 +- .../functions/row_getIsGrouped.md | 2 +- .../functions/shouldAutoRemoveFilter.md | 2 +- .../functions/table_getGroupedRowModel.md | 2 +- .../functions/table_resetColumnFilters.md | 2 +- .../functions/table_resetGlobalFilter.md | 2 +- .../functions/table_resetGrouping.md | 2 +- .../functions/table_setColumnFilters.md | 2 +- .../functions/table_setGlobalFilter.md | 2 +- .../functions/table_setGrouping.md | 2 +- docs/reference/static-functions/index.md | 6 +- examples/alpine/grouping/src/main.ts | 2 + examples/angular/grouping/src/app/columns.ts | 2 + examples/lit/grouping/src/main.ts | 2 + examples/preact/grouping/src/main.tsx | 2 + examples/react/aggregation/index.html | 12 + examples/react/aggregation/package.json | 28 + examples/react/aggregation/src/index.css | 87 ++++ examples/react/aggregation/src/main.tsx | 308 +++++++++++ examples/react/aggregation/src/makeData.ts | 21 + examples/react/aggregation/src/vite-env.d.ts | 1 + examples/react/aggregation/tsconfig.json | 21 + examples/react/aggregation/vite.config.js | 22 + examples/react/grouped-aggregation/index.html | 12 + .../react/grouped-aggregation/package.json | 28 + .../react/grouped-aggregation/src/index.css | 67 +++ .../react/grouped-aggregation/src/main.tsx | 313 +++++++++++ .../react/grouped-aggregation/src/makeData.ts | 48 ++ .../grouped-aggregation/src/vite-env.d.ts | 1 + .../react/grouped-aggregation/tsconfig.json | 21 + .../react/grouped-aggregation/vite.config.js | 22 + examples/react/grouping/src/main.tsx | 31 +- .../src/hooks/features.ts | 2 + .../src/hooks/features.ts | 2 + .../src/hooks/features.ts | 2 + .../src/hooks/features.ts | 2 + .../src/hooks/features.ts | 2 + .../src/hooks/features.ts | 2 + .../src/hooks/features.ts | 2 + .../react/mantine-react-table/src/main.tsx | 20 +- .../src/mantine-react-table/types.ts | 18 +- .../mantine-react-table/utils/column.utils.ts | 16 - .../react/material-react-table/src/main.tsx | 20 +- .../src/material-react-table/types.ts | 4 +- .../utils/column.utils.ts | 17 +- .../src/routes/index.tsx | 4 + .../web-worker-row-models/src/tableConfig.tsx | 25 +- .../tests/e2e/smoke.spec.ts | 35 +- examples/solid/grouping/src/App.tsx | 2 + examples/svelte/grouping/src/App.svelte | 2 + examples/vue/grouping/src/App.vue | 2 + .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- .../table-core/skills/aggregation/SKILL.md | 130 +++++ packages/table-core/skills/grouping/SKILL.md | 15 +- .../skills/migrate-v8-to-v9/SKILL.md | 15 +- .../table-core/skills/table-features/SKILL.md | 5 +- .../row-models/coreRowModelsFeature.utils.ts | 2 +- .../aggregation/aggregationFeature.ts | 59 +++ .../aggregation/aggregationFeature.types.ts | 312 +++++++++++ .../aggregation/aggregationFeature.utils.ts | 401 ++++++++++++++ .../features/aggregation/aggregationFns.ts | 336 ++++++++++++ .../columnFilteringFeature.types.ts | 2 +- .../column-filtering}/filterFns.ts | 8 +- .../column-grouping/columnGroupingFeature.ts | 21 +- .../columnGroupingFeature.types.ts | 145 +----- .../columnGroupingFeature.utils.ts | 123 +---- .../column-grouping/createGroupedRowModel.ts | 73 ++- .../globalFilteringFeature.utils.ts | 2 +- .../row-sorting/rowSortingFeature.types.ts | 2 +- .../row-sorting/rowSortingFeature.utils.ts | 2 +- .../{fns => features/row-sorting}/sortFns.ts | 11 +- .../table-core/src/features/stockFeatures.ts | 3 + packages/table-core/src/fns/aggregationFns.ts | 303 ----------- packages/table-core/src/index.ts | 15 +- packages/table-core/src/static-functions.ts | 1 + packages/table-core/src/types/Cell.ts | 2 + packages/table-core/src/types/Column.ts | 4 +- packages/table-core/src/types/ColumnDef.ts | 9 +- packages/table-core/src/types/Row.ts | 2 + packages/table-core/src/types/RowModelFns.ts | 6 +- .../table-core/src/types/TableFeatures.ts | 8 +- packages/table-core/src/types/TableOptions.ts | 6 +- .../table-core/src/worker/initTableWorker.ts | 2 +- .../aggregation/aggregationFeature.test.ts | 490 ++++++++++++++++++ .../columnGroupingFeature.test.ts | 2 + .../createGroupedRowModel.test.ts | 2 + .../row-selection/rowSelectionFeature.test.ts | 2 + .../prototypeSafeDictionaries.test.ts | 2 + .../columnGroupingFeature.test.ts | 2 + .../unit/core/table/rowModelSlots.test.ts | 91 +++- .../columnGroupingFeature.utils.test.ts | 53 +- .../features/fnRegistryResolution.test.ts | 6 +- .../tests/unit/fns/aggregationFns.test.ts | 195 +++---- .../unit/worker/serializeRebuild.test.ts | 10 +- .../src/components/FeaturesPanel.tsx | 24 +- .../skills/migrate-v8-to-v9/SKILL.md | 2 +- pnpm-lock.yaml | 80 +++ 299 files changed, 5543 insertions(+), 2279 deletions(-) create mode 100644 docs/guide/aggregation.md delete mode 100644 docs/reference/index/functions/aggregationFn_count.md delete mode 100644 docs/reference/index/functions/aggregationFn_first.md delete mode 100644 docs/reference/index/functions/aggregationFn_last.md create mode 100644 docs/reference/index/interfaces/AggregationContext.md delete mode 100644 docs/reference/index/interfaces/AggregationFn.md create mode 100644 docs/reference/index/interfaces/AggregationFnDescriptor.md create mode 100644 docs/reference/index/interfaces/AggregationMergeContext.md create mode 100644 docs/reference/index/interfaces/AggregationValueContext.md create mode 100644 docs/reference/index/interfaces/AggregationValueResult.md create mode 100644 docs/reference/index/interfaces/Cell_Aggregation.md create mode 100644 docs/reference/index/interfaces/ColumnDef_Aggregation.md create mode 100644 docs/reference/index/interfaces/Column_Aggregation.md delete mode 100644 docs/reference/index/interfaces/CreatedAggregationFn.md create mode 100644 docs/reference/index/interfaces/ResolvedAggregationFn.md create mode 100644 docs/reference/index/interfaces/RowModelFns_Aggregation.md delete mode 100644 docs/reference/index/interfaces/RowModelFns_ColumnGrouping.md create mode 100644 docs/reference/index/interfaces/Row_Aggregation.md create mode 100644 docs/reference/index/interfaces/TableOptions_Aggregation.md create mode 100644 docs/reference/index/type-aliases/AggregationFnListItem.md create mode 100644 docs/reference/index/type-aliases/AggregationFnRef.md create mode 100644 docs/reference/index/type-aliases/AggregationResult.md create mode 100644 docs/reference/index/type-aliases/AggregationResultOf.md create mode 100644 docs/reference/index/type-aliases/ColumnAggregationValue.md create mode 100644 docs/reference/index/variables/aggregationFeature.md create mode 100644 docs/reference/index/variables/aggregationFn_count.md create mode 100644 docs/reference/index/variables/aggregationFn_first.md create mode 100644 docs/reference/index/variables/aggregationFn_last.md create mode 100644 docs/reference/index/variables/filterFn_between.md create mode 100644 docs/reference/index/variables/filterFn_betweenInclusive.md create mode 100644 docs/reference/static-functions/functions/aggregateColumnValue.md delete mode 100644 docs/reference/static-functions/functions/column_getAggregationFn.md create mode 100644 docs/reference/static-functions/functions/column_getAggregationFns.md create mode 100644 docs/reference/static-functions/functions/column_getAggregationValue.md create mode 100644 docs/reference/static-functions/functions/formatAggregatedCellValue.md create mode 100644 docs/reference/static-functions/functions/normalizeAggregationRows.md create mode 100644 examples/react/aggregation/index.html create mode 100644 examples/react/aggregation/package.json create mode 100644 examples/react/aggregation/src/index.css create mode 100644 examples/react/aggregation/src/main.tsx create mode 100644 examples/react/aggregation/src/makeData.ts create mode 100644 examples/react/aggregation/src/vite-env.d.ts create mode 100644 examples/react/aggregation/tsconfig.json create mode 100644 examples/react/aggregation/vite.config.js create mode 100644 examples/react/grouped-aggregation/index.html create mode 100644 examples/react/grouped-aggregation/package.json create mode 100644 examples/react/grouped-aggregation/src/index.css create mode 100644 examples/react/grouped-aggregation/src/main.tsx create mode 100644 examples/react/grouped-aggregation/src/makeData.ts create mode 100644 examples/react/grouped-aggregation/src/vite-env.d.ts create mode 100644 examples/react/grouped-aggregation/tsconfig.json create mode 100644 examples/react/grouped-aggregation/vite.config.js create mode 100644 packages/table-core/skills/aggregation/SKILL.md create mode 100644 packages/table-core/src/features/aggregation/aggregationFeature.ts create mode 100644 packages/table-core/src/features/aggregation/aggregationFeature.types.ts create mode 100644 packages/table-core/src/features/aggregation/aggregationFeature.utils.ts create mode 100755 packages/table-core/src/features/aggregation/aggregationFns.ts rename packages/table-core/src/{fns => features/column-filtering}/filterFns.ts (98%) rename packages/table-core/src/{fns => features/row-sorting}/sortFns.ts (97%) delete mode 100755 packages/table-core/src/fns/aggregationFns.ts create mode 100644 packages/table-core/tests/implementation/features/aggregation/aggregationFeature.test.ts diff --git a/docs/config.json b/docs/config.json index 147bea5df6..cffe35d9f5 100644 --- a/docs/config.json +++ b/docs/config.json @@ -92,6 +92,7 @@ { "label": "Column Definitions", "to": "guide/column-defs" }, { "label": "Table Instance", "to": "guide/tables" }, { "label": "Row Models", "to": "guide/row-models" }, + { "label": "Aggregation", "to": "guide/aggregation" }, { "label": "Worker Row Models (Experimental)", "to": "guide/worker-row-models" }, { "label": "Rows", "to": "guide/rows" }, { "label": "Cells", "to": "guide/cells" }, @@ -827,6 +828,7 @@ { "label": "coreTablesFeature", "to": "reference/index/variables/coreTablesFeature" }, { "label": "columnFilteringFeature", "to": "reference/index/variables/columnFilteringFeature" }, { "label": "columnFacetingFeature", "to": "reference/index/variables/columnFacetingFeature" }, + { "label": "aggregationFeature", "to": "reference/index/variables/aggregationFeature" }, { "label": "columnGroupingFeature", "to": "reference/index/variables/columnGroupingFeature" }, { "label": "columnOrderingFeature", "to": "reference/index/variables/columnOrderingFeature" }, { "label": "columnPinningFeature", "to": "reference/index/variables/columnPinningFeature" }, @@ -854,6 +856,13 @@ { "label": "SortFn", "to": "reference/index/interfaces/SortFn" }, { "label": "SortFns", "to": "reference/index/interfaces/SortFns" }, { "label": "AggregationFns", "to": "reference/index/interfaces/AggregationFns" }, + { "label": "AggregationContext", "to": "reference/index/interfaces/AggregationContext" }, + { "label": "AggregationMergeContext", "to": "reference/index/interfaces/AggregationMergeContext" }, + { "label": "AggregationFnDef", "to": "reference/index/interfaces/AggregationFnDef" }, + { "label": "AggregationFnDescriptor", "to": "reference/index/interfaces/AggregationFnDescriptor" }, + { "label": "AggregationValueContext", "to": "reference/index/interfaces/AggregationValueContext" }, + { "label": "AggregationValueResult", "to": "reference/index/interfaces/AggregationValueResult" }, + { "label": "constructAggregationFn", "to": "reference/index/functions/constructAggregationFn" }, { "label": "ColumnFiltersState", "to": "reference/index/type-aliases/ColumnFiltersState" }, { "label": "ColumnOrderState", "to": "reference/index/type-aliases/ColumnOrderState" }, { "label": "ColumnPinningPosition", "to": "reference/index/type-aliases/ColumnPinningPosition" }, @@ -867,8 +876,8 @@ { "label": "RowSelectionState", "to": "reference/index/type-aliases/RowSelectionState" }, { "label": "SortDirection", "to": "reference/index/type-aliases/SortDirection" }, { "label": "SortingState", "to": "reference/index/type-aliases/SortingState" }, - { "label": "AggregationFn", "to": "reference/index/type-aliases/AggregationFn" }, { "label": "AggregationFnOption", "to": "reference/index/type-aliases/AggregationFnOption" }, + { "label": "AggregationResult", "to": "reference/index/type-aliases/AggregationResult" }, { "label": "FilterFnOption", "to": "reference/index/type-aliases/FilterFnOption" }, { "label": "SortFnOption", "to": "reference/index/type-aliases/SortFnOption" }, { "label": "BuiltInAggregationFn", "to": "reference/index/type-aliases/BuiltInAggregationFn" }, @@ -1148,6 +1157,8 @@ { "label": "Expanding", "to": "framework/react/examples/expanding" }, { "label": "Expanding Sub Components", "to": "framework/react/examples/sub-components" }, { "label": "Grouping", "to": "framework/react/examples/grouping" }, + { "label": "Aggregation", "to": "framework/react/examples/aggregation" }, + { "label": "Grouped Aggregation", "to": "framework/react/examples/grouped-aggregation" }, { "label": "Pagination", "to": "framework/react/examples/pagination" }, { "label": "Row DnD", "to": "framework/react/examples/row-dnd" }, { "label": "Row Pinning", "to": "framework/react/examples/row-pinning" }, diff --git a/docs/framework/alpine/guide/custom-features.md b/docs/framework/alpine/guide/custom-features.md index 0a49259022..b1a3dbbccf 100644 --- a/docs/framework/alpine/guide/custom-features.md +++ b/docs/framework/alpine/guide/custom-features.md @@ -147,7 +147,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met #### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ## Adding a Custom Feature diff --git a/docs/framework/alpine/guide/grouping.md b/docs/framework/alpine/guide/grouping.md index 5a3f78fd6f..632d51f348 100644 --- a/docs/framework/alpine/guide/grouping.md +++ b/docs/framework/alpine/guide/grouping.md @@ -8,6 +8,8 @@ Want to skip to the implementation? Check out these Alpine examples: - [Grouping](../examples/grouping) +Aggregation is an independent feature. See the core [Aggregation Guide](../../../guide/aggregation) for totals, multiple aggregations, custom definitions, and grouped aggregate values. + Read your reactive inputs such as `data` through a getter (for example backing them with `Alpine.reactive`) when creating the table, so the table sees updates. ### Grouping Setup @@ -16,6 +18,7 @@ Here's how you set up your table to use grouping features. Adding the grouping f ```ts import { + aggregationFeature, aggregationFn_sum, columnGroupingFeature, createGroupedRowModel, @@ -24,6 +27,7 @@ import { } from '@tanstack/alpine-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -54,6 +58,7 @@ To use the grouping feature, add the `columnGroupingFeature` and the `groupedRow ```ts import { + aggregationFeature, aggregationFn_sum, columnGroupingFeature, createGroupedRowModel, @@ -62,6 +67,7 @@ import { } from '@tanstack/alpine-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -78,6 +84,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```ts const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -117,7 +124,7 @@ const table = createTable({ ### Aggregations -When rows are grouped, you can aggregate the data in the grouped rows by columns using the `aggregationFn` column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the registry passed to `createGroupedRowModel`. +When both features are registered, grouped rows can aggregate data with the `aggregationFn` column option. It can be a registered name, an inline definition, or an array of aggregations. ```ts const column = columnHelper.accessor('key', { @@ -147,15 +154,19 @@ There are several built-in aggregation functions that you can use: You can define custom aggregation functions in the `aggregationFns` slot on `tableFeatures`. The slot is a record where the keys are the names of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. ```ts +const myCustomAggregation = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows + .map((row) => getValue(row)) + .filter(Boolean) + .join(', '), +}) + const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), - aggregationFns: { - sum: aggregationFn_sum, - myCustomAggregation: (columnId, leafRows, childRows) => { - // return the aggregated value - }, - }, + aggregationFns: { sum: aggregationFn_sum, myCustomAggregation }, }) const table = createTable({ @@ -164,7 +175,7 @@ const table = createTable({ }) ``` -In the above example, myCustomAggregation is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's aggregationFn option: +The context supplies normalized terminal `rows`, `getValue`, `column`, `table`, and an optional `groupingRow`. You can then use the definition in a column's `aggregationFn` option: ```ts const column = columnHelper.accessor('key', { @@ -174,27 +185,7 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** For `aggregationFn: 'myCustomAggregation'` string references to typecheck, register the function in the `aggregationFns` slot on `tableFeatures` (as shown above). The slot is the registry; no `declare module` augmentation is needed. Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```ts -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide `merge({ childResults, childRows })`. See the [Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -350,8 +341,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -363,14 +352,15 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```ts cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, `column.getAggregationFns()`, and `column.getAggregationValue()` belong to `aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```ts diff --git a/docs/framework/angular/guide/custom-features.md b/docs/framework/angular/guide/custom-features.md index 6d7335aa9f..19dd97dc60 100644 --- a/docs/framework/angular/guide/custom-features.md +++ b/docs/framework/angular/guide/custom-features.md @@ -153,7 +153,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met ##### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ### Adding a Custom Feature diff --git a/docs/framework/angular/guide/grouping.md b/docs/framework/angular/guide/grouping.md index f190cecd58..e9d1e52fcd 100644 --- a/docs/framework/angular/guide/grouping.md +++ b/docs/framework/angular/guide/grouping.md @@ -8,6 +8,8 @@ Want to skip to the implementation? Check out these Angular examples: - [Grouping](../examples/grouping) +Aggregation is an independent feature. See the core [Aggregation Guide](../../../guide/aggregation) for totals, multiple aggregations, custom definitions, and grouped aggregate values. + ### Grouping Setup Here's how you set up your table to use grouping features. Adding the grouping feature enables the related APIs. Additionally, if using client-side grouping, you also need to set up `groupedRowModel` after its associated feature because row model slots are type-checked. @@ -19,10 +21,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/angular-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -59,10 +63,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/angular-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -79,6 +85,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```ts const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -118,7 +125,7 @@ readonly table = injectTable(() => ({ ### Aggregations -When rows are grouped, you can aggregate the data in the grouped rows by columns using the `aggregationFn` column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the registry passed to `createGroupedRowModel`. +When both features are registered, grouped rows can aggregate data with the `aggregationFn` column option. It can be a registered name, an inline definition, or an array of aggregations. ```ts const column = columnHelper.accessor('key', { @@ -145,18 +152,19 @@ There are several built-in aggregation functions that you can use: #### Custom Aggregations -You can define custom aggregation functions in the registry that you pass to `createGroupedRowModel`. The registry is a record where the keys are the names of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. +You can register context-based custom definitions and reference them by name. ```ts +const myCustomAggregation = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.map((row) => getValue(row)).filter(Boolean).join(', '), +}) + const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), - aggregationFns: { - sum: aggregationFn_sum, - myCustomAggregation: (columnId, leafRows, childRows) => { - // return the aggregated value - }, - }, + aggregationFns: { sum: aggregationFn_sum, myCustomAggregation }, }) readonly table = injectTable(() => ({ @@ -165,7 +173,7 @@ readonly table = injectTable(() => ({ })) ``` -In the above example, myCustomAggregation is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's aggregationFn option: +The context supplies normalized terminal `rows`, `getValue`, `column`, `table`, and an optional `groupingRow`. You can then use the definition in a column's `aggregationFn` option: ```ts const column = columnHelper.accessor('key', { @@ -175,27 +183,7 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** For `aggregationFn: 'myCustomAggregation'` string references to typecheck, register the function in the `aggregationFns` slot on `tableFeatures` (as shown above). Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```ts -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide `merge({ childResults, childRows })`. See the [Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -264,8 +252,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -277,14 +263,15 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```ts cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, `column.getAggregationFns()`, and `column.getAggregationValue()` belong to `aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```ts diff --git a/docs/framework/angular/guide/migrating.md b/docs/framework/angular/guide/migrating.md index 622aec7083..8bd4faf75c 100644 --- a/docs/framework/angular/guide/migrating.md +++ b/docs/framework/angular/guide/migrating.md @@ -187,6 +187,35 @@ class TableCmp { } ``` +### Aggregation Feature Split + +Aggregation is now independent from column grouping. `stockFeatures` still includes both, so tables using it need no feature-registration change. If you declare features explicitly, add `aggregationFeature` whenever columns use `aggregationFn`, `aggregatedCell`, `getAggregationValue`, or `cell.getIsAggregated`. Add `columnGroupingFeature` and `groupedRowModel` only when you also group rows. Root totals can use aggregation without grouping. + +```ts +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, // only for grouped rows + groupedRowModel: createGroupedRowModel(), + aggregationFns: { sum: aggregationFn_sum }, +}) +``` + +Custom aggregation callables have changed to context-based definitions: + +```ts +// Table V8/earlier V9 betas +const total = (columnId, leafRows, childRows) => + leafRows.reduce((sum, row) => sum + row.getValue(columnId), 0) + +// Current V9 +const total = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.reduce((sum, row) => sum + Number(getValue(row)), 0), +}) +``` + +`column.getAggregationFn()` is now `column.getAggregationFns()` because a column can run multiple definitions. A single `aggregationFn` still returns a scalar; an array returns an object keyed by function name or descriptor `id`. The old callable `AggregationFn` and `CreatedAggregationFn` types are replaced by `AggregationFnDef`. + ### Available Features | Feature | Import Name | @@ -204,6 +233,7 @@ class TableCmp { | Column Sizing | `columnSizingFeature` | | Column Resizing | `columnResizingFeature` | | Column Grouping | `columnGroupingFeature` | +| Aggregation | `aggregationFeature` | | Column Faceting | `columnFacetingFeature` | --- @@ -245,6 +275,7 @@ import { const features = tableFeatures({ columnFilteringFeature, rowSortingFeature, + aggregationFeature, columnGroupingFeature, rowPaginationFeature, filteredRowModel: createFilteredRowModel(), diff --git a/docs/framework/ember/guide/custom-features.md b/docs/framework/ember/guide/custom-features.md index 5a09124440..1f58c552b9 100644 --- a/docs/framework/ember/guide/custom-features.md +++ b/docs/framework/ember/guide/custom-features.md @@ -153,7 +153,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met ##### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ### Adding a Custom Feature diff --git a/docs/framework/ember/guide/grouping.md b/docs/framework/ember/guide/grouping.md index c0ad3d930b..6d04cdf86e 100644 --- a/docs/framework/ember/guide/grouping.md +++ b/docs/framework/ember/guide/grouping.md @@ -8,6 +8,8 @@ Want to skip to the implementation? Check out these Ember examples: - [Grouping](../examples/grouping) +Aggregation is an independent feature. See the core [Aggregation Guide](../../../guide/aggregation) for totals, multiple aggregations, custom definitions, and grouped aggregate values. + ### Grouping Setup Here's how you set up your table to use grouping features. Adding the grouping feature enables the related APIs. Additionally, if using client-side grouping, you also need to set up `groupedRowModel` after its associated feature because row model slots are type-checked. @@ -18,10 +20,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/ember-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -54,10 +58,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/ember-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -74,6 +80,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```ts const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -113,7 +120,7 @@ const table = useTable(() => ({ ### Aggregations -When rows are grouped, you can aggregate the data in the grouped rows by columns using the `aggregationFn` column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the registry passed to `createGroupedRowModel`. +When both features are registered, grouped rows can aggregate data with the `aggregationFn` column option. It can be a registered name, an inline definition, or an array of aggregations. ```ts const column = columnHelper.accessor('key', { @@ -140,14 +147,19 @@ There are several built-in aggregation functions that you can use: #### Custom Aggregations -You can define custom aggregation functions in the registry that you pass to `createGroupedRowModel`. The registry is a record where the keys are the names of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. +You can register context-based custom definitions and reference them by name. ```ts -const myCustomAggregation = (columnId, leafRows, childRows) => { - // return the aggregated value -} +const myCustomAggregation = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows + .map((row) => getValue(row)) + .filter(Boolean) + .join(', '), +}) const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum, myCustomAggregation }, @@ -159,7 +171,7 @@ const table = useTable(() => ({ })) ``` -In the above example, `myCustomAggregation` is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's `aggregationFn` option: +The context supplies normalized terminal `rows`, `getValue`, `column`, `table`, and an optional `groupingRow`. You can then use the definition in a column's `aggregationFn` option: ```ts const column = columnHelper.accessor('key', { @@ -169,27 +181,7 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** For `aggregationFn: 'myCustomAggregation'` string references to typecheck, register the function in the `aggregationFns` slot on `tableFeatures` (as shown above). Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```ts -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide `merge({ childResults, childRows })`. See the [Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -256,8 +248,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -269,14 +259,15 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```ts cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, `column.getAggregationFns()`, and `column.getAggregationValue()` belong to `aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```ts @@ -285,5 +276,3 @@ table.getPreGroupedRowModel() ``` Use `table.setGrouping` and `table.resetGrouping` to update the grouping state directly. - - diff --git a/docs/framework/lit/guide/custom-features.md b/docs/framework/lit/guide/custom-features.md index 9ce634b682..a0367cb68c 100644 --- a/docs/framework/lit/guide/custom-features.md +++ b/docs/framework/lit/guide/custom-features.md @@ -145,7 +145,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met #### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ## Adding a Custom Feature diff --git a/docs/framework/lit/guide/grouping.md b/docs/framework/lit/guide/grouping.md index 56680e1fba..2dcd2824b4 100644 --- a/docs/framework/lit/guide/grouping.md +++ b/docs/framework/lit/guide/grouping.md @@ -8,6 +8,8 @@ Want to skip to the implementation? Check out these Lit examples: - [Grouping](../examples/grouping) +Aggregation is an independent feature. See the core [Aggregation Guide](../../../guide/aggregation) for totals, multiple aggregations, custom definitions, and grouped aggregate values. + ### Grouping Setup Here's how you set up your table to use grouping features. Adding the grouping feature enables the related APIs. Additionally, if using client-side grouping, you also need to set up `groupedRowModel` after its associated feature because row model slots are type-checked. @@ -20,10 +22,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/lit-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -68,10 +72,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/lit-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -88,6 +94,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```ts const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -157,15 +164,19 @@ There are several built-in aggregation functions that you can use: You can define custom aggregation functions in the `aggregationFns` slot on `tableFeatures`. The slot is a record where the keys are the names of the aggregation functions and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. ```ts +const myCustomAggregation = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows + .map((row) => getValue(row)) + .filter(Boolean) + .join(', '), +}) + const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), - aggregationFns: { - sum: aggregationFn_sum, - myCustomAggregation: (columnId, leafRows, childRows) => { - // return the aggregated value - }, - }, + aggregationFns: { sum: aggregationFn_sum, myCustomAggregation }, }) const table = this.tableController.table({ @@ -174,7 +185,7 @@ const table = this.tableController.table({ }) ``` -In the above example, myCustomAggregation is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's aggregationFn option: +The context supplies normalized terminal `rows`, `getValue`, `column`, `table`, and an optional `groupingRow`. You can then use the definition in a column's `aggregationFn` option: ```ts const column = columnHelper.accessor('key', { @@ -184,27 +195,7 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** String references like `aggregationFn: 'myCustomAggregation'` are automatically typed when the function is registered in the `aggregationFns` slot on `tableFeatures`. The registry slot replaces the old `declare module` augmentation approach. Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```ts -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide `merge({ childResults, childRows })`. See the [Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -272,8 +263,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -285,14 +274,15 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```ts cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, `column.getAggregationFns()`, and `column.getAggregationValue()` belong to `aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```ts diff --git a/docs/framework/lit/guide/migrating.md b/docs/framework/lit/guide/migrating.md index 3ce56b03c0..5134e020cf 100644 --- a/docs/framework/lit/guide/migrating.md +++ b/docs/framework/lit/guide/migrating.md @@ -198,6 +198,35 @@ const table = this.tableController.table({ Use it as a temporary migration shortcut. Explicit feature registration is the production target. +### Aggregation Feature Split + +Aggregation is now independent from column grouping. `stockFeatures` still includes both, so tables using it need no feature-registration change. If you declare features explicitly, add `aggregationFeature` whenever columns use `aggregationFn`, `aggregatedCell`, `getAggregationValue`, or `cell.getIsAggregated`. Add `columnGroupingFeature` and `groupedRowModel` only when you also group rows. Root totals can use aggregation without grouping. + +```ts +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, // only for grouped rows + groupedRowModel: createGroupedRowModel(), + aggregationFns: { sum: aggregationFn_sum }, +}) +``` + +Custom aggregation callables have changed to context-based definitions: + +```ts +// Table V8/earlier V9 betas +const total = (columnId, leafRows, childRows) => + leafRows.reduce((sum, row) => sum + row.getValue(columnId), 0) + +// Current V9 +const total = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.reduce((sum, row) => sum + Number(getValue(row)), 0), +}) +``` + +`column.getAggregationFn()` is now `column.getAggregationFns()` because a column can run multiple definitions. A single `aggregationFn` still returns a scalar; an array returns an object keyed by function name or descriptor `id`. The old callable `AggregationFn` and `CreatedAggregationFn` types are replaced by `AggregationFnDef`. + ### Available Features | Feature | Import Name | @@ -215,6 +244,7 @@ Use it as a temporary migration shortcut. Explicit feature registration is the p | Column Sizing | `columnSizingFeature` | | Column Resizing | `columnResizingFeature` | | Column Grouping | `columnGroupingFeature` | +| Aggregation | `aggregationFeature` | | Column Faceting | `columnFacetingFeature` | --- @@ -780,7 +810,9 @@ type Person = { - [ ] Replace controller construction with `new TableController(this)`. - [ ] Move table options from the controller constructor into `tableController.table(...)`. -- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`). +- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`) +- [ ] If aggregating, add `aggregationFeature`; add `columnGroupingFeature` separately only when grouping rows +- [ ] Convert custom aggregation callables to `constructAggregationFn({ aggregate, merge? })` definitions. - [ ] Move row model factories and function registries into `tableFeatures({...})` slots (remove the separate `rowModels` option). - [ ] Remove `getCoreRowModel`; the core row model is automatic. - [ ] Register `sortFns`, `filterFns`, and `aggregationFns` as slots on `tableFeatures({...})` (not as factory arguments). diff --git a/docs/framework/preact/guide/custom-features.md b/docs/framework/preact/guide/custom-features.md index d12982e12d..6219f20e7e 100644 --- a/docs/framework/preact/guide/custom-features.md +++ b/docs/framework/preact/guide/custom-features.md @@ -153,7 +153,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met ##### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ### Adding a Custom Feature diff --git a/docs/framework/preact/guide/grouping.md b/docs/framework/preact/guide/grouping.md index 26a1ab8418..a5d6836dd7 100644 --- a/docs/framework/preact/guide/grouping.md +++ b/docs/framework/preact/guide/grouping.md @@ -8,6 +8,8 @@ Want to skip to the implementation? Check out these Preact examples: - [Grouping](../examples/grouping) +Aggregation is an independent feature. See the core [Aggregation Guide](../../../guide/aggregation) for totals, multiple aggregations, custom definitions, and grouped aggregate values. + ### Grouping Setup Here's how you set up your table to use grouping features. Adding the grouping feature enables the related APIs. Additionally, if using client-side grouping, you also need to set up `groupedRowModel` after its associated feature because row model slots are type-checked. @@ -18,10 +20,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/preact-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -46,7 +50,7 @@ Grouping can also affect column order. There are 3 table features that can reord 2. Manual [Column Ordering](./column-ordering) - A manually specified column order is applied. 3. **Grouping** - If grouping is enabled, a grouping state is active, and `tableOptions.groupedColumnMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow. -To use the grouping feature, add the `columnGroupingFeature`, the `groupedRowModel` factory, and `aggregationFns` to your `tableFeatures` call. The grouped row model is responsible for grouping the rows based on the grouping state. +To group rows, add `columnGroupingFeature` and the `groupedRowModel` factory. If grouped rows should also calculate aggregate values, add the independent `aggregationFeature` and register the `aggregationFns` referenced by your columns. ```tsx import { @@ -54,10 +58,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/preact-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -74,6 +80,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```tsx const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -113,7 +120,7 @@ const table = useTable({ ### Aggregations -When rows are grouped, you can aggregate the data in the grouped rows by columns using the `aggregationFn` column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the registry passed to `createGroupedRowModel`. +When both features are registered, grouped rows can aggregate data with the `aggregationFn` column option. It can be a registered name, an inline definition, or an array of aggregations. ```tsx const column = columnHelper.accessor('key', { @@ -140,7 +147,7 @@ There are several built-in aggregation functions that you can use: #### Custom Aggregations -You can define custom aggregation functions in the registry that you pass to `createGroupedRowModel`. The registry is a record where the keys are the names of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. +You can register context-based custom definitions and reference them by name. ```tsx const myCustomAggregation: AggregationFn = ( @@ -152,6 +159,7 @@ const myCustomAggregation: AggregationFn = ( } const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { @@ -176,27 +184,7 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** For `aggregationFn: 'myCustomAggregation'` string references to typecheck, register the function in the `aggregationFns` slot on `tableFeatures` (as shown above). TypeScript infers the registered names from the slot automatically. Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```tsx -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide `merge({ childResults, childRows })`. See the [Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -261,8 +249,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -274,14 +260,15 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```tsx cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, `column.getAggregationFns()`, and `column.getAggregationValue()` belong to `aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```tsx diff --git a/docs/framework/preact/guide/migrating.md b/docs/framework/preact/guide/migrating.md index b3c131815d..423350b803 100644 --- a/docs/framework/preact/guide/migrating.md +++ b/docs/framework/preact/guide/migrating.md @@ -184,6 +184,35 @@ const table = useTable({ }) ``` +### Aggregation Feature Split + +Aggregation is now independent from column grouping. `stockFeatures` still includes both, so tables using it need no feature-registration change. If you declare features explicitly, add `aggregationFeature` whenever columns use `aggregationFn`, `aggregatedCell`, `getAggregationValue`, or `cell.getIsAggregated`. Add `columnGroupingFeature` and `groupedRowModel` only when you also group rows. Root totals can use aggregation without grouping. + +```ts +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, // only for grouped rows + groupedRowModel: createGroupedRowModel(), + aggregationFns: { sum: aggregationFn_sum }, +}) +``` + +Custom aggregation callables have changed to context-based definitions: + +```ts +// Table V8/earlier V9 betas +const total = (columnId, leafRows, childRows) => + leafRows.reduce((sum, row) => sum + row.getValue(columnId), 0) + +// Current V9 +const total = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.reduce((sum, row) => sum + Number(getValue(row)), 0), +}) +``` + +`column.getAggregationFn()` is now `column.getAggregationFns()` because a column can run multiple definitions. A single `aggregationFn` still returns a scalar; an array returns an object keyed by function name or descriptor `id`. The old callable `AggregationFn` and `CreatedAggregationFn` types are replaced by `AggregationFnDef`. + ### Available Features | Feature | Import Name | @@ -201,6 +230,7 @@ const table = useTable({ | Column Sizing | `columnSizingFeature` | | Column Resizing | `columnResizingFeature` | | Column Grouping | `columnGroupingFeature` | +| Aggregation | `aggregationFeature` | | Column Faceting | `columnFacetingFeature` | --- @@ -769,7 +799,9 @@ type Person = { - [ ] Replace `@tanstack/react-table` imports used through `preact/compat` with `@tanstack/preact-table`. - [ ] Replace `useReactTable` with `useTable`. -- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`). +- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`) +- [ ] If aggregating, add `aggregationFeature`; add `columnGroupingFeature` separately only when grouping rows +- [ ] Convert custom aggregation callables to `constructAggregationFn({ aggregate, merge? })` definitions. - [ ] Migrate `get*RowModel()` options (or earlier-beta `rowModels: {...}` entries) to `tableFeatures({...})` slots (e.g. `filteredRowModel: createFilteredRowModel()`). - [ ] Drop `getCoreRowModel`; the core row model is automatic. - [ ] Move `sortingFns`, `filterFns`, and `aggregationFns` into `tableFeatures` slots (not factory args). diff --git a/docs/framework/react/guide/custom-features.md b/docs/framework/react/guide/custom-features.md index 1a7bbac393..9884a75fa6 100644 --- a/docs/framework/react/guide/custom-features.md +++ b/docs/framework/react/guide/custom-features.md @@ -153,7 +153,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met ##### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ### Adding a Custom Feature diff --git a/docs/framework/react/guide/grouping.md b/docs/framework/react/guide/grouping.md index 17896f3f22..ffd6ae40bc 100644 --- a/docs/framework/react/guide/grouping.md +++ b/docs/framework/react/guide/grouping.md @@ -7,6 +7,11 @@ title: Grouping (React) Guide Want to skip to the implementation? Check out these React examples: - [Grouping](../examples/grouping) +- [Grouped Aggregation](../examples/grouped-aggregation) + +Aggregation is an independent feature. See the core +[Aggregation Guide](../../../guide/aggregation) for totals, multiple +aggregations, custom definitions, and grouped aggregate values. ### Grouping Setup @@ -16,12 +21,14 @@ Here's how you set up your table to use grouping features. Adding the grouping f import { useTable, tableFeatures, + aggregationFeature, columnGroupingFeature, createGroupedRowModel, aggregationFn_sum, } from '@tanstack/react-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -52,12 +59,14 @@ To use the grouping feature, add the `columnGroupingFeature` and the `groupedRow import { useTable, tableFeatures, + aggregationFeature, columnGroupingFeature, createGroupedRowModel, aggregationFn_sum, } from '@tanstack/react-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -74,6 +83,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```tsx const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -113,7 +123,7 @@ const table = useTable({ ### Aggregations -When rows are grouped, you can aggregate the data in the grouped rows by columns using the `aggregationFn` column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the registry passed to `createGroupedRowModel`. +When both features are registered, grouped rows can aggregate data with the `aggregationFn` column option. It can be a registered name, an inline definition, or an array of aggregations. ```tsx const column = columnHelper.accessor('key', { @@ -140,14 +150,19 @@ There are several built-in aggregation functions that you can use: #### Custom Aggregations -You can define custom aggregation functions in the registry that you pass to `createGroupedRowModel`. The registry is a record where the keys are the names of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. +You can register context-based custom definitions and reference them by name. ```tsx -const myCustomAggregation = (columnId, leafRows, childRows) => { - // return the aggregated value -} +const myCustomAggregation = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows + .map((row) => getValue(row)) + .filter(Boolean) + .join(', '), +}) const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum, myCustomAggregation }, @@ -159,7 +174,7 @@ const table = useTable({ }) ``` -In the above example, `myCustomAggregation` is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's `aggregationFn` option: +The context supplies normalized terminal `rows`, `getValue`, `column`, `table`, and an optional `groupingRow`. You can then use the definition in a column's `aggregationFn` option: ```tsx const column = columnHelper.accessor('key', { @@ -169,27 +184,9 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** For `aggregationFn: 'myCustomAggregation'` string references to typecheck, register the function in the `aggregationFns` slot on `tableFeatures` (as shown above). Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```tsx -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide +`merge({ childResults, childRows })`. See the +[Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -254,8 +251,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -267,14 +262,17 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```tsx cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, +`column.getAggregationFns()`, and `column.getAggregationValue()` belong to +`aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```tsx diff --git a/docs/framework/react/guide/migrating.md b/docs/framework/react/guide/migrating.md index 8f081fe47e..301e114ff4 100644 --- a/docs/framework/react/guide/migrating.md +++ b/docs/framework/react/guide/migrating.md @@ -204,6 +204,35 @@ const table = useTable({ }) ``` +### Aggregation Feature Split + +Aggregation is now independent from column grouping. `stockFeatures` still includes both, so tables using it need no feature-registration change. If you declare features explicitly, add `aggregationFeature` whenever columns use `aggregationFn`, `aggregatedCell`, `getAggregationValue`, or `cell.getIsAggregated`. Add `columnGroupingFeature` and `groupedRowModel` only when you also group rows. Root totals can use aggregation without grouping. + +```ts +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, // only for grouped rows + groupedRowModel: createGroupedRowModel(), + aggregationFns: { sum: aggregationFn_sum }, +}) +``` + +Custom aggregation callables have changed to context-based definitions: + +```ts +// Table V8/earlier V9 betas +const total = (columnId, leafRows, childRows) => + leafRows.reduce((sum, row) => sum + row.getValue(columnId), 0) + +// Current V9 +const total = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.reduce((sum, row) => sum + Number(getValue(row)), 0), +}) +``` + +`column.getAggregationFn()` is now `column.getAggregationFns()` because a column can run multiple definitions. A single `aggregationFn` still returns a scalar; an array returns an object keyed by function name or descriptor `id`. The old callable `AggregationFn` and `CreatedAggregationFn` types are replaced by `AggregationFnDef`. + ### Available Features | Feature | Import Name | @@ -221,6 +250,7 @@ const table = useTable({ | Column Sizing | `columnSizingFeature` | | Column Resizing | `columnResizingFeature` | | Column Grouping | `columnGroupingFeature` | +| Aggregation | `aggregationFeature` | | Column Faceting | `columnFacetingFeature` | --- @@ -261,6 +291,7 @@ import { const features = tableFeatures({ columnFilteringFeature, rowSortingFeature, + aggregationFeature, columnGroupingFeature, rowPaginationFeature, filteredRowModel: createFilteredRowModel(), @@ -1126,6 +1157,8 @@ This change improves type safety. If you were passing unusual data types, ensure - [ ] Update import: `useReactTable` โ†’ `useTable` - [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`) +- [ ] If aggregating, add `aggregationFeature`; add `columnGroupingFeature` separately only when grouping rows +- [ ] Convert custom aggregation callables to `constructAggregationFn({ aggregate, merge? })` definitions - [ ] Migrate `get*RowModel()` options to `tableFeatures` slots (e.g. `filteredRowModel: createFilteredRowModel()`) - [ ] Register `filterFns` / `sortFns` / `aggregationFns` registries as slots on `tableFeatures` (row model factories no longer take arguments) - [ ] Replace `declare module` augmentation of `FilterFns`/`SortFns`/`AggregationFns` with registry-slot registration, and `FilterMeta` augmentation with the `filterMeta` slot diff --git a/docs/framework/solid/guide/custom-features.md b/docs/framework/solid/guide/custom-features.md index 6f04b145f2..4e8e4093fe 100644 --- a/docs/framework/solid/guide/custom-features.md +++ b/docs/framework/solid/guide/custom-features.md @@ -147,7 +147,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met #### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ## Adding a Custom Feature diff --git a/docs/framework/solid/guide/grouping.md b/docs/framework/solid/guide/grouping.md index 14831f23d8..a3d783919e 100644 --- a/docs/framework/solid/guide/grouping.md +++ b/docs/framework/solid/guide/grouping.md @@ -8,6 +8,8 @@ Want to skip to the implementation? Check out these Solid examples: - [Grouping](../examples/grouping) +Aggregation is an independent feature. See the core [Aggregation Guide](../../../guide/aggregation) for totals, multiple aggregations, custom definitions, and grouped aggregate values. + Use getters for reactive inputs such as `data` when passing Solid signals to `createTable`. ### Grouping Setup @@ -20,10 +22,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/solid-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -58,10 +62,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/solid-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -78,6 +84,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```tsx const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -117,7 +124,7 @@ const table = createTable({ ### Aggregations -When rows are grouped, you can aggregate the data in the grouped rows by columns using the `aggregationFn` column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the registry passed to `createGroupedRowModel`. +When both features are registered, grouped rows can aggregate data with the `aggregationFn` column option. It can be a registered name, an inline definition, or an array of aggregations. ```tsx const column = columnHelper.accessor('key', { @@ -147,15 +154,19 @@ There are several built-in aggregation functions that you can use: You can define custom aggregation functions in the `aggregationFns` slot on `tableFeatures`. The slot is a record where the keys are the names of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. ```tsx +const myCustomAggregation = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows + .map((row) => getValue(row)) + .filter(Boolean) + .join(', '), +}) + const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), - aggregationFns: { - sum: aggregationFn_sum, - myCustomAggregation: (columnId, leafRows, childRows) => { - // return the aggregated value - }, - }, + aggregationFns: { sum: aggregationFn_sum, myCustomAggregation }, }) const table = createTable({ @@ -164,7 +175,7 @@ const table = createTable({ }) ``` -In the above example, myCustomAggregation is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's aggregationFn option: +The context supplies normalized terminal `rows`, `getValue`, `column`, `table`, and an optional `groupingRow`. You can then use the definition in a column's `aggregationFn` option: ```tsx const column = columnHelper.accessor('key', { @@ -174,27 +185,7 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** For `aggregationFn: 'myCustomAggregation'` string references to typecheck, register the function in the `aggregationFns` slot on `tableFeatures` (as shown above). The slot is the registry; no `declare module` augmentation is needed. Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```tsx -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide `merge({ childResults, childRows })`. See the [Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -261,8 +252,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -274,14 +263,15 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```tsx cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, `column.getAggregationFns()`, and `column.getAggregationValue()` belong to `aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```tsx diff --git a/docs/framework/solid/guide/migrating.md b/docs/framework/solid/guide/migrating.md index bf104da27d..e2f842ef33 100644 --- a/docs/framework/solid/guide/migrating.md +++ b/docs/framework/solid/guide/migrating.md @@ -184,6 +184,35 @@ const table = createTable({ Use it as a migration shortcut, not as the preferred production end state. +### Aggregation Feature Split + +Aggregation is now independent from column grouping. `stockFeatures` still includes both, so tables using it need no feature-registration change. If you declare features explicitly, add `aggregationFeature` whenever columns use `aggregationFn`, `aggregatedCell`, `getAggregationValue`, or `cell.getIsAggregated`. Add `columnGroupingFeature` and `groupedRowModel` only when you also group rows. Root totals can use aggregation without grouping. + +```ts +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, // only for grouped rows + groupedRowModel: createGroupedRowModel(), + aggregationFns: { sum: aggregationFn_sum }, +}) +``` + +Custom aggregation callables have changed to context-based definitions: + +```ts +// Table V8/earlier V9 betas +const total = (columnId, leafRows, childRows) => + leafRows.reduce((sum, row) => sum + row.getValue(columnId), 0) + +// Current V9 +const total = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.reduce((sum, row) => sum + Number(getValue(row)), 0), +}) +``` + +`column.getAggregationFn()` is now `column.getAggregationFns()` because a column can run multiple definitions. A single `aggregationFn` still returns a scalar; an array returns an object keyed by function name or descriptor `id`. The old callable `AggregationFn` and `CreatedAggregationFn` types are replaced by `AggregationFnDef`. + ### Available Features | Feature | Import Name | @@ -201,6 +230,7 @@ Use it as a migration shortcut, not as the preferred production end state. | Column Sizing | `columnSizingFeature` | | Column Resizing | `columnResizingFeature` | | Column Grouping | `columnGroupingFeature` | +| Aggregation | `aggregationFeature` | | Column Faceting | `columnFacetingFeature` | --- @@ -734,7 +764,9 @@ type Person = { ## Migration Checklist - [ ] Replace `createSolidTable` with `createTable`. -- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`). +- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`) +- [ ] If aggregating, add `aggregationFeature`; add `columnGroupingFeature` separately only when grouping rows +- [ ] Convert custom aggregation callables to `constructAggregationFn({ aggregate, merge? })` definitions. - [ ] Move every `get*RowModel` factory into `tableFeatures` as a slot (e.g. `sortedRowModel: createSortedRowModel()`). - [ ] Remove `getCoreRowModel`; the core row model is automatic. - [ ] Move `sortFns`, `filterFns`, and `aggregationFns` into `tableFeatures` as slots (not as factory arguments). diff --git a/docs/framework/svelte/guide/custom-features.md b/docs/framework/svelte/guide/custom-features.md index 8ceae7e174..48bee39a91 100644 --- a/docs/framework/svelte/guide/custom-features.md +++ b/docs/framework/svelte/guide/custom-features.md @@ -147,7 +147,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met #### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ## Adding a Custom Feature diff --git a/docs/framework/svelte/guide/grouping.md b/docs/framework/svelte/guide/grouping.md index 0b51c44f14..f3b0f09763 100644 --- a/docs/framework/svelte/guide/grouping.md +++ b/docs/framework/svelte/guide/grouping.md @@ -8,6 +8,8 @@ Want to skip to the implementation? Check out these Svelte examples: - [Grouping](../examples/grouping) +Aggregation is an independent feature. See the core [Aggregation Guide](../../../guide/aggregation) for totals, multiple aggregations, custom definitions, and grouped aggregate values. + Use getters for reactive inputs such as `data` when passing Svelte state to `createTable`. ### Grouping Setup @@ -20,10 +22,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/svelte-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -58,10 +62,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/svelte-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -78,6 +84,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```ts const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -117,7 +124,7 @@ const table = createTable({ ### Aggregations -When rows are grouped, you can aggregate the data in the grouped rows by columns using the `aggregationFn` column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the registry passed to `createGroupedRowModel`. +When both features are registered, grouped rows can aggregate data with the `aggregationFn` column option. It can be a registered name, an inline definition, or an array of aggregations. ```ts const column = columnHelper.accessor('key', { @@ -144,18 +151,22 @@ There are several built-in aggregation functions that you can use: #### Custom Aggregations -You can define custom aggregation functions in the registry that you pass to `createGroupedRowModel`. The registry is a record where the keys are the names of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. +You can register context-based custom definitions and reference them by name. ```ts +const myCustomAggregation = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows + .map((row) => getValue(row)) + .filter(Boolean) + .join(', '), +}) + const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), - aggregationFns: { - sum: aggregationFn_sum, - myCustomAggregation: (columnId, leafRows, childRows) => { - // return the aggregated value - }, - }, + aggregationFns: { sum: aggregationFn_sum, myCustomAggregation }, }) const table = createTable({ @@ -164,7 +175,7 @@ const table = createTable({ }) ``` -In the above example, myCustomAggregation is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's aggregationFn option: +The context supplies normalized terminal `rows`, `getValue`, `column`, `table`, and an optional `groupingRow`. You can then use the definition in a column's `aggregationFn` option: ```ts const column = columnHelper.accessor('key', { @@ -174,27 +185,7 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** For `aggregationFn: 'myCustomAggregation'` string references to typecheck, register the function in the `aggregationFns` slot of `tableFeatures({...})` as shown above. TypeScript infers the available aggregation function names from that registry. Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```ts -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide `merge({ childResults, childRows })`. See the [Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -266,8 +257,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -279,14 +268,15 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```ts cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, `column.getAggregationFns()`, and `column.getAggregationValue()` belong to `aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```ts diff --git a/docs/framework/svelte/guide/migrating.md b/docs/framework/svelte/guide/migrating.md index 433e292a1b..93c52c5823 100644 --- a/docs/framework/svelte/guide/migrating.md +++ b/docs/framework/svelte/guide/migrating.md @@ -195,6 +195,35 @@ const table = createTable({ Use it as a temporary migration shortcut. Explicit feature registration is the production target. +### Aggregation Feature Split + +Aggregation is now independent from column grouping. `stockFeatures` still includes both, so tables using it need no feature-registration change. If you declare features explicitly, add `aggregationFeature` whenever columns use `aggregationFn`, `aggregatedCell`, `getAggregationValue`, or `cell.getIsAggregated`. Add `columnGroupingFeature` and `groupedRowModel` only when you also group rows. Root totals can use aggregation without grouping. + +```ts +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, // only for grouped rows + groupedRowModel: createGroupedRowModel(), + aggregationFns: { sum: aggregationFn_sum }, +}) +``` + +Custom aggregation callables have changed to context-based definitions: + +```ts +// Table V8/earlier V9 betas +const total = (columnId, leafRows, childRows) => + leafRows.reduce((sum, row) => sum + row.getValue(columnId), 0) + +// Current V9 +const total = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.reduce((sum, row) => sum + Number(getValue(row)), 0), +}) +``` + +`column.getAggregationFn()` is now `column.getAggregationFns()` because a column can run multiple definitions. A single `aggregationFn` still returns a scalar; an array returns an object keyed by function name or descriptor `id`. The old callable `AggregationFn` and `CreatedAggregationFn` types are replaced by `AggregationFnDef`. + ### Available Features | Feature | Import Name | @@ -212,6 +241,7 @@ Use it as a temporary migration shortcut. Explicit feature registration is the p | Column Sizing | `columnSizingFeature` | | Column Resizing | `columnResizingFeature` | | Column Grouping | `columnGroupingFeature` | +| Aggregation | `aggregationFeature` | | Column Faceting | `columnFacetingFeature` | --- @@ -814,7 +844,9 @@ type Person = { - [ ] Upgrade the app to Svelte 5. - [ ] Replace `createSvelteTable` with `createTable`. - [ ] Replace Svelte 3/4 writable-store table patterns with runes and getters. -- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`). +- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`) +- [ ] If aggregating, add `aggregationFeature`; add `columnGroupingFeature` separately only when grouping rows +- [ ] Convert custom aggregation callables to `constructAggregationFn({ aggregate, merge? })` definitions. - [ ] Move row model factories into `tableFeatures({...})` as slots (e.g. `filteredRowModel: createFilteredRowModel()`). - [ ] Remove `getCoreRowModel`; the core row model is automatic. - [ ] Pass `sortFns`, `filterFns`, and `aggregationFns` as slots in `tableFeatures({...})` instead of as factory arguments (row model factories no longer take arguments). diff --git a/docs/framework/vue/guide/custom-features.md b/docs/framework/vue/guide/custom-features.md index 3899e7b94c..c908c5d87d 100644 --- a/docs/framework/vue/guide/custom-features.md +++ b/docs/framework/vue/guide/custom-features.md @@ -147,7 +147,7 @@ The `assignRowPrototype` method in a table feature is responsible for adding met #### assignCellPrototype -The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/beta/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, Column Grouping adds `getIsGrouped` and `getIsPlaceholder`, while Aggregation adds `getIsAggregated`. ## Adding a Custom Feature diff --git a/docs/framework/vue/guide/grouping.md b/docs/framework/vue/guide/grouping.md index 9d9ab018b9..2a83bb9f91 100644 --- a/docs/framework/vue/guide/grouping.md +++ b/docs/framework/vue/guide/grouping.md @@ -8,6 +8,8 @@ Want to skip to the implementation? Check out these Vue examples: - [Grouping](../examples/grouping) +Aggregation is an independent feature. See the core [Aggregation Guide](../../../guide/aggregation) for totals, multiple aggregations, custom definitions, and grouped aggregate values. + Vue refs can be passed directly where the adapter expects reactive table options. ### Grouping Setup @@ -20,10 +22,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/vue-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), // if using client-side grouping aggregationFns: { sum: aggregationFn_sum }, @@ -48,7 +52,7 @@ Grouping can also affect column order. There are 3 table features that can reord 2. Manual [Column Ordering](./column-ordering) - A manually specified column order is applied. 3. **Grouping** - If grouping is enabled, a grouping state is active, and `tableOptions.groupedColumnMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow. -To use the grouping feature, add the `columnGroupingFeature`, `groupedRowModel`, and `aggregationFns` to your `tableFeatures` call. The grouped row model is responsible for grouping the rows based on the grouping state. +To group rows, add `columnGroupingFeature` and `groupedRowModel`. If grouped rows should also calculate aggregate values, add the independent `aggregationFeature` and register the `aggregationFns` referenced by your columns. ```ts import { @@ -56,10 +60,12 @@ import { tableFeatures, columnGroupingFeature, createGroupedRowModel, + aggregationFeature, aggregationFn_sum, } from '@tanstack/vue-table' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum }, @@ -76,6 +82,7 @@ To allow the user to expand and collapse the grouped rows, you can use the expan ```ts const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowExpandingFeature, groupedRowModel: createGroupedRowModel(), @@ -115,7 +122,7 @@ const table = useTable({ ### Aggregations -When rows are grouped, you can aggregate the data in the grouped rows by columns using the `aggregationFn` column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the registry passed to `createGroupedRowModel`. +When both features are registered, grouped rows can aggregate data with the `aggregationFn` column option. It can be a registered name, an inline definition, or an array of aggregations. ```ts const column = columnHelper.accessor('key', { @@ -145,15 +152,19 @@ There are several built-in aggregation functions that you can use: You can define custom aggregation functions in the `aggregationFns` slot of `tableFeatures`. The slot is a record where the keys are the names of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's `aggregationFn` option. ```ts +const myCustomAggregation = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows + .map((row) => getValue(row)) + .filter(Boolean) + .join(', '), +}) + const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, groupedRowModel: createGroupedRowModel(), - aggregationFns: { - sum: aggregationFn_sum, - myCustomAggregation: (columnId, leafRows, childRows) => { - // return the aggregated value - }, - }, + aggregationFns: { sum: aggregationFn_sum, myCustomAggregation }, }) const table = useTable({ @@ -162,7 +173,7 @@ const table = useTable({ }) ``` -In the above example, myCustomAggregation is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's aggregationFn option: +The context supplies normalized terminal `rows`, `getValue`, `column`, `table`, and an optional `groupingRow`. You can then use the definition in a column's `aggregationFn` option: ```ts const column = columnHelper.accessor('key', { @@ -172,27 +183,7 @@ const column = columnHelper.accessor('key', { > **TypeScript Note:** For `aggregationFn: 'myCustomAggregation'` string references to typecheck, register the function in the `aggregationFns` slot of `tableFeatures`. When the function is registered there, TypeScript infers the available string keys from the registry, so no `declare module` augmentation is needed. Alternatively, skip the registry entirely by passing the function directly to the `aggregationFn` column option. -#### Customize Aggregation Function Behavior - -Aggregation functions support an optional "hanging" property: - -- `aggregationFn.resolveDataValue` - normalizes each row's value before it is aggregated. It is honored by every aggregation function built with the `constructAggregationFn` helper, which includes the value-based built-in aggregation functions (`count`, `first`, and `last` are plain row-level functions that never read every value). - -The `constructAggregationFn` helper builds an aggregation function from a value-level reducer (`aggregate`) plus that optional resolver and a `fromRows` setting that picks between the group's `leafRows` (the default) and its immediate `childRows`. The definition is attached to the returned function, so you can spread any constructed aggregation function and override only what differs. For example, a `min` that works on date columns: - -```ts -const earliest = constructAggregationFn({ - ...aggregationFn_min, // reuse the numeric reducer - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) - -const features = tableFeatures({ - columnGroupingFeature, - groupedRowModel: createGroupedRowModel(), - aggregationFns: { min: aggregationFn_min, earliest }, -}) -``` +For efficient nested grouping, a definition can also provide `merge({ childResults, childRows })`. See the [Aggregation Guide](../../../guide/aggregation#custom-aggregation-definitions). ### Manual Grouping @@ -264,8 +255,6 @@ column.getToggleGroupingHandler() column.getCanGroup() column.getIsGrouped() column.getGroupedIndex() -column.getAutoAggregationFn() -column.getAggregationFn() ``` Rows expose grouping helpers for grouped row rendering: @@ -277,14 +266,15 @@ row.groupingColumnId row.groupingValue ``` -Cells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering: +Cells expose grouping and placeholder helpers: ```ts cell.getIsGrouped() -cell.getIsAggregated() cell.getIsPlaceholder() ``` +`cell.getIsAggregated()`, `column.getAutoAggregationFn()`, `column.getAggregationFns()`, and `column.getAggregationValue()` belong to `aggregationFeature`. + The table instance exposes grouped and pre-grouped row models: ```ts diff --git a/docs/framework/vue/guide/migrating.md b/docs/framework/vue/guide/migrating.md index 6a4c465cfd..4da30b3cc1 100644 --- a/docs/framework/vue/guide/migrating.md +++ b/docs/framework/vue/guide/migrating.md @@ -178,6 +178,35 @@ const table = useTable({ Use it as a temporary migration shortcut. Explicit feature registration is the production target. +### Aggregation Feature Split + +Aggregation is now independent from column grouping. `stockFeatures` still includes both, so tables using it need no feature-registration change. If you declare features explicitly, add `aggregationFeature` whenever columns use `aggregationFn`, `aggregatedCell`, `getAggregationValue`, or `cell.getIsAggregated`. Add `columnGroupingFeature` and `groupedRowModel` only when you also group rows. Root totals can use aggregation without grouping. + +```ts +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, // only for grouped rows + groupedRowModel: createGroupedRowModel(), + aggregationFns: { sum: aggregationFn_sum }, +}) +``` + +Custom aggregation callables have changed to context-based definitions: + +```ts +// Table V8/earlier V9 betas +const total = (columnId, leafRows, childRows) => + leafRows.reduce((sum, row) => sum + row.getValue(columnId), 0) + +// Current V9 +const total = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.reduce((sum, row) => sum + Number(getValue(row)), 0), +}) +``` + +`column.getAggregationFn()` is now `column.getAggregationFns()` because a column can run multiple definitions. A single `aggregationFn` still returns a scalar; an array returns an object keyed by function name or descriptor `id`. The old callable `AggregationFn` and `CreatedAggregationFn` types are replaced by `AggregationFnDef`. + ### Available Features | Feature | Import Name | @@ -195,6 +224,7 @@ Use it as a temporary migration shortcut. Explicit feature registration is the p | Column Sizing | `columnSizingFeature` | | Column Resizing | `columnResizingFeature` | | Column Grouping | `columnGroupingFeature` | +| Aggregation | `aggregationFeature` | | Column Faceting | `columnFacetingFeature` | --- @@ -754,7 +784,9 @@ type Person = { ## Migration Checklist - [ ] Replace `useVueTable` with `useTable`. -- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`). +- [ ] Define `features` using `tableFeatures()` (or use `stockFeatures`) +- [ ] If aggregating, add `aggregationFeature`; add `columnGroupingFeature` separately only when grouping rows +- [ ] Convert custom aggregation callables to `constructAggregationFn({ aggregate, merge? })` definitions. - [ ] Move row model factories into `tableFeatures` as slots (e.g. `filteredRowModel: createFilteredRowModel()`). - [ ] Remove `getCoreRowModel`; the core row model is automatic. - [ ] Move `sortFns`, `filterFns`, and `aggregationFns` into `tableFeatures` as slots (row model factories no longer take arguments). diff --git a/docs/guide/aggregation.md b/docs/guide/aggregation.md new file mode 100644 index 0000000000..f95b5d2436 --- /dev/null +++ b/docs/guide/aggregation.md @@ -0,0 +1,204 @@ +--- +title: Aggregation Guide +--- + +Aggregation computes column values over sets of rows. It is independent from +column grouping: use it for table totals, filtered totals, selected-row totals, +or custom subsets without creating grouped rows. Register it alongside column +grouping when grouped rows should also expose aggregate values. + +## Setup + +Register `aggregationFeature` and the named definitions referenced by your +columns. Registering individual built-ins keeps unused functions out of the +bundle. + +```ts +import { + aggregationFeature, + aggregationFn_mean, + aggregationFn_sum, + tableFeatures, +} from '@tanstack/table-core' + +const features = tableFeatures({ + aggregationFeature, + aggregationFns: { + mean: aggregationFn_mean, + sum: aggregationFn_sum, + }, +}) +``` + +The full `aggregationFns` registry remains available for compatibility, but it +bundles every built-in. Tables using `stockFeatures` already include +`aggregationFeature`; they still need the definitions that named column options +should resolve. + +To combine aggregation with grouped rows, register both independent features: + +```ts +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, + groupedRowModel: createGroupedRowModel(), + aggregationFns: { sum: aggregationFn_sum }, +}) +``` + +## One Aggregation Per Column + +A scalar `aggregationFn` can be a registered name, `'auto'`, or an inline +definition. + +```ts +const amountColumn = columnHelper.accessor('amount', { + aggregationFn: 'sum', + footer: ({ column }) => column.getAggregationValue(), +}) +``` + +With no row argument, `column.getAggregationValue()` aggregates the table's +pre-grouped row model. In the normal client pipeline this includes filtering, +but precedes grouping, sorting, expansion, and pagination. + +## Choosing Which Rows To Aggregate + +Pass any row array to override the default: + +```ts +column.getAggregationValue(table.getCoreRowModel().rows) // all core rows +column.getAggregationValue(table.getRowModel().rows) // currently rendered model +column.getAggregationValue(table.getFilteredSelectedRowModel().rows) +column.getAggregationValue(customRows) +``` + +Hierarchical inputs are normalized to unique terminal leaves. Explicit row +calls are recomputed each time; the default call is cached against its row +model, registry, and column aggregation option. + +## Multiple Aggregations Per Column + +Use an array to return a keyed result object. Registered names become their own +keys. Use a descriptor when the result needs a different key or when an inline +definition appears in the array. + +```ts +const scoreColumn = columnHelper.accessor('score', { + aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }], + footer: ({ column }) => { + const result = column.getAggregationValue<{ + count: number + mean: number | undefined + range: [number | undefined, number | undefined] + }>() + + return `${result.count} values; mean ${result.mean}; range ${result.range}` + }, +}) +``` + +Every entry needs a unique stable id. Duplicate ids, missing descriptor ids, +and unregistered names warn in development and preserve the affected key with +an `undefined` value. + +## Custom Aggregation Definitions + +Custom aggregations are context-based definitions. `rows` contains normalized +terminal rows and `getValue(row)` reads the current column's value. + +```ts +const joined = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows + .map((row) => getValue(row)) + .filter(Boolean) + .join(', '), +}) +``` + +The context also includes `column`, `columnId`, and `table`. During grouped +aggregation it includes `groupingRow`; root and caller-supplied-row aggregation +omit that property. The grouping depth is `groupingRow.depth`. + +For a result that can be combined efficiently across nested groups, provide a +`merge` function: + +```ts +const sum = constructAggregationFn({ + aggregate: ({ rows, getValue }) => + rows.reduce((total, row) => { + const value = getValue(row) + return total + (typeof value === 'number' ? value : 0) + }, 0), + merge: ({ childResults }) => + childResults.reduce((total, value) => total + value, 0), +}) +``` + +Without `merge`, nested grouping calls `aggregate` over the group's terminal +rows. This replaces the previous callable aggregation signature and its +`fromRows` and `resolveDataValue` properties. + +## Grouped Cell Rendering + +`aggregatedCell` renders aggregate values on synthetic grouped rows. + +```ts +const amountColumn = columnHelper.accessor('amount', { + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), +}) +``` + +Use `cell.getIsAggregated()` to distinguish these cells. Grouping-only tables +do not expose this method; it belongs to `aggregationFeature`. + +## Providing Server or External Values + +A column can handle aggregation-value requests before local calculation: + +```ts +const amountColumn = columnHelper.accessor('amount', { + aggregationFn: 'sum', + getAggregationValue: ({ rows }) => { + if (rows !== undefined) return undefined // use local fallback for overrides + return { value: serverTotals.amount } + }, +}) +``` + +Returning `{ value }` marks the request as handled, including +`{ value: undefined }`. Returning `undefined` uses the local fallback. Put the +same provider on `defaultColumn` to share it across columns. + +Set `manualAggregation: true` to disable the local fallback for +`column.getAggregationValue()`. This is separate from `manualGrouping`, which +controls whether the grouped row model runs. + +## Built-in Definitions + +- `sum`: sums numeric values; non-numbers contribute zero. +- `count`: counts rows. +- `min` / `max`: find numeric or Date bounds. +- `extent`: returns `[min, max]`; an empty input returns + `[undefined, undefined]`. +- `mean`: averages numeric and number-like non-null values. +- `median`: requires every row value to be a number. +- `unique` / `uniqueCount`: use JavaScript `Set` semantics. +- `first` / `last`: return the positional value, including a nullish value. + +`aggregationFn: 'auto'` inspects the first core row value. Numbers resolve to a +registered `sum`, Dates resolve to a registered `extent`, and other values do +not resolve an aggregation. + +## Web Workers + +Worker-backed grouped row models eagerly compute explicitly configured grouped +aggregates in the worker. `column.getAggregationValue()` still executes its +final total on the main thread over the selected row model. Aggregation results +crossing the worker boundary must be structured-cloneable. + +See the React [Aggregation](../framework/react/examples/aggregation) and +[Grouped Aggregation](../framework/react/examples/grouped-aggregation) +examples for complete footer and grouped-cell rendering. diff --git a/docs/guide/worker-row-models.md b/docs/guide/worker-row-models.md index 135de570df..4a7e418a08 100644 --- a/docs/guide/worker-row-models.md +++ b/docs/guide/worker-row-models.md @@ -106,7 +106,9 @@ Offloaded stages must form a contiguous prefix of the pipeline (filtered, then g - **Flat source data only** for now (no `getSubRows`). - **Table options that affect processing** (a custom `globalFilterFn`, for example) must be passed to `initTableWorker` in the shared config, not just to your table hook. -- **Grouping aggregates** are computed eagerly in the worker, but only for columns with an explicit `aggregationFn` or `aggregatedCell`; group-row `getValue()` for other columns returns `undefined`. +- **Grouping aggregates** require both `aggregationFeature` and `columnGroupingFeature` in the shared feature set. They are computed eagerly in the worker, but only for columns with an explicit `aggregationFn` or `aggregatedCell`; group-row `getValue()` for other columns returns `undefined`. +- **Multiple aggregation results** are transferred as keyed objects. Custom aggregation results must be supported by the browser's structured-clone algorithm. +- **Grand totals and caller-selected row totals** from `column.getAggregationValue(rows?)` run on the main thread. The worker currently offloads row-model stages, not arbitrary aggregation requests. - **SSR** works out of the box: on the server (no `Worker` global) the table renders unprocessed rows, and the client takes over after hydration. - **If the worker fails** to load or throws, the plugin logs an error, keeps the last results on screen, and stops updating; un-resulted stages fall back to their pre-stage models. - **Nothing terminates the worker automatically** yet. `tableWorker.terminate()` exists as a manual escape hatch and self-heals on the next read. diff --git a/docs/reference/index/functions/aggregationFn_count.md b/docs/reference/index/functions/aggregationFn_count.md deleted file mode 100644 index d4e1fd4af8..0000000000 --- a/docs/reference/index/functions/aggregationFn_count.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -id: aggregationFn_count -title: aggregationFn_count ---- - -# Function: aggregationFn\_count() - -```ts -function aggregationFn_count(_columnId, leafRows): number; -``` - -Defined in: [fns/aggregationFns.ts:242](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L242) - -Counts the number of leaf rows in the group. - -The column id is ignored because the result is based only on group size. -This is a plain row-level function (not built with -`constructAggregationFn`) because it never reads row values. - -## Type Parameters - -### TFeatures - -`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) - -### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -## Parameters - -### \_columnId - -`string` - -### leafRows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -## Returns - -`number` diff --git a/docs/reference/index/functions/aggregationFn_first.md b/docs/reference/index/functions/aggregationFn_first.md deleted file mode 100644 index bda2ba51ab..0000000000 --- a/docs/reference/index/functions/aggregationFn_first.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -id: aggregationFn_first -title: aggregationFn_first ---- - -# Function: aggregationFn\_first() - -```ts -function aggregationFn_first(columnId, leafRows): unknown; -``` - -Defined in: [fns/aggregationFns.ts:255](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L255) - -Returns the first leaf-row value for a grouped column. - -This is a plain row-level function (not built with -`constructAggregationFn`) because it only reads one positional value. - -## Type Parameters - -### TFeatures - -`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) - -### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -## Parameters - -### columnId - -`string` - -### leafRows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -## Returns - -`unknown` diff --git a/docs/reference/index/functions/aggregationFn_last.md b/docs/reference/index/functions/aggregationFn_last.md deleted file mode 100644 index afe5638ddc..0000000000 --- a/docs/reference/index/functions/aggregationFn_last.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -id: aggregationFn_last -title: aggregationFn_last ---- - -# Function: aggregationFn\_last() - -```ts -function aggregationFn_last(columnId, leafRows): unknown; -``` - -Defined in: [fns/aggregationFns.ts:268](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L268) - -Returns the last leaf-row value for a grouped column. - -This is a plain row-level function (not built with -`constructAggregationFn`) because it only reads one positional value. - -## Type Parameters - -### TFeatures - -`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) - -### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -## Parameters - -### columnId - -`string` - -### leafRows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -## Returns - -`unknown` diff --git a/docs/reference/index/functions/constructAggregationFn.md b/docs/reference/index/functions/constructAggregationFn.md index 2d7f852b79..db6fb7823a 100644 --- a/docs/reference/index/functions/constructAggregationFn.md +++ b/docs/reference/index/functions/constructAggregationFn.md @@ -6,34 +6,13 @@ title: constructAggregationFn # Function: constructAggregationFn() ```ts -function constructAggregationFn(def): CreatedAggregationFn; +function constructAggregationFn(definition): AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L34) +Defined in: [features/aggregation/aggregationFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L75) -Builds an `AggregationFn` from a value-level reducer plus optional -`resolveDataValue` and `fromRows` settings. - -The `aggregate` reducer receives an array of the group rows' values, each -already passed through `resolveDataValue` when one is defined. Keeping -normalization in the resolver means a variant of an existing aggregation -function only has to swap the resolver, not re-implement the computation. - -The definition is attached to the returned function, so a variant can be -created by spreading a built-in aggregation function and overriding what -differs. For example, a `min` that works on date columns: - -```ts -const earliest = constructAggregationFn({ - ...aggregationFn_min, - resolveDataValue: (value) => - value instanceof Date ? value.getTime() : value, -}) -``` - -The built-in `count`, `first`, and `last` aggregation functions are plain -row-level functions instead: they read group position or size and never -look at every value, so materializing a values array would be wasted work. +Creates a typed context-based aggregation definition for a column or +aggregation-function registry. ## Type Parameters @@ -45,12 +24,20 @@ look at every value, so materializing a values array would be wasted work. `TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` +### TValue + +`TValue` = `unknown` + +### TResult + +`TResult` = `unknown` + ## Parameters -### def +### definition -[`AggregationFnDef`](../interfaces/AggregationFnDef.md)\<`TFeatures`, `TData`\> +[`AggregationFnDef`](../interfaces/AggregationFnDef.md)\<`TFeatures`, `TData`, `TValue`, `TResult`\> ## Returns -[`CreatedAggregationFn`](../interfaces/CreatedAggregationFn.md)\<`TFeatures`, `TData`\> +[`AggregationFnDef`](../interfaces/AggregationFnDef.md)\<`TFeatures`, `TData`, `TValue`, `TResult`\> diff --git a/docs/reference/index/functions/constructFilterFn.md b/docs/reference/index/functions/constructFilterFn.md index 7f510ef617..f14c7e2007 100644 --- a/docs/reference/index/functions/constructFilterFn.md +++ b/docs/reference/index/functions/constructFilterFn.md @@ -9,7 +9,7 @@ title: constructFilterFn function constructFilterFn(def): CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L40) +Defined in: [features/column-filtering/filterFns.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L40) Builds a `FilterFn` from a value-level comparator plus optional resolvers. diff --git a/docs/reference/index/functions/constructSortFn.md b/docs/reference/index/functions/constructSortFn.md index 2ce50e69df..dd72edae4d 100644 --- a/docs/reference/index/functions/constructSortFn.md +++ b/docs/reference/index/functions/constructSortFn.md @@ -9,7 +9,7 @@ title: constructSortFn function constructSortFn(def): CreatedSortFn; ``` -Defined in: [fns/sortFns.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L41) +Defined in: [features/row-sorting/sortFns.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L38) Builds a `SortFn` from a value-level comparator plus an optional `resolveDataValue` normalizer. diff --git a/docs/reference/index/functions/createGroupedRowModel.md b/docs/reference/index/functions/createGroupedRowModel.md index 2667411496..fddb2904ac 100644 --- a/docs/reference/index/functions/createGroupedRowModel.md +++ b/docs/reference/index/functions/createGroupedRowModel.md @@ -9,18 +9,14 @@ title: createGroupedRowModel function createGroupedRowModel(): (table) => () => RowModel; ``` -Defined in: [features/column-grouping/createGroupedRowModel.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/createGroupedRowModel.ts#L30) +Defined in: [features/column-grouping/createGroupedRowModel.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/createGroupedRowModel.ts#L26) Creates a memoized grouped row model factory. The factory reads the relevant table state atoms and options, then returns a row model function used by the table row-model pipeline. -Register the aggregation functions you use with the `aggregationFns` slot -on the `features` option: -`tableFeatures({ columnGroupingFeature, groupedRowModel: createGroupedRowModel(), aggregationFns: { sum: aggregationFn_sum } })`. -Importing individual `aggregationFn_*` functions keeps unused built-ins out -of your bundle; aggregation functions passed directly to the -`aggregationFn` column option need no registration at all. +When aggregationFeature is also registered, grouped rows use its shared +executor for non-group values. Grouping remains useful without aggregation. ## Type Parameters diff --git a/docs/reference/index/functions/makeStateUpdater.md b/docs/reference/index/functions/makeStateUpdater.md index ba8521bc1c..ad28b1308e 100644 --- a/docs/reference/index/functions/makeStateUpdater.md +++ b/docs/reference/index/functions/makeStateUpdater.md @@ -57,7 +57,7 @@ The updater writes through the table base atom for the slice and supports both v #### updater -[`Updater`](../type-aliases/Updater.md)\<`TableState_WorkerRowModels` & [`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_ColumnGrouping`](../interfaces/TableState_ColumnGrouping.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_RowSorting`](../interfaces/TableState_RowSorting.md) & [`TableState_ColumnOrdering`](../interfaces/TableState_ColumnOrdering.md)\[`K` & +[`Updater`](../type-aliases/Updater.md)\<`TableState_WorkerRowModels` & [`TableState_ColumnGrouping`](../interfaces/TableState_ColumnGrouping.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_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_RowSorting`](../interfaces/TableState_RowSorting.md) & [`TableState_ColumnOrdering`](../interfaces/TableState_ColumnOrdering.md)\[`K` & \| `"expanded"` \| `"columnFilters"` \| `"globalFilter"` diff --git a/docs/reference/index/index.md b/docs/reference/index/index.md index 57ec560013..11393af8fc 100644 --- a/docs/reference/index/index.md +++ b/docs/reference/index/index.md @@ -7,9 +7,13 @@ title: index ## Interfaces -- [AggregationFn](interfaces/AggregationFn.md) +- [AggregationContext](interfaces/AggregationContext.md) - [AggregationFnDef](interfaces/AggregationFnDef.md) +- [AggregationFnDescriptor](interfaces/AggregationFnDescriptor.md) - [AggregationFns](interfaces/AggregationFns.md) +- [AggregationMergeContext](interfaces/AggregationMergeContext.md) +- [AggregationValueContext](interfaces/AggregationValueContext.md) +- [AggregationValueResult](interfaces/AggregationValueResult.md) - [API](interfaces/API.md) - [CachedRowModel\_All](interfaces/CachedRowModel_All.md) - [CachedRowModel\_Core](interfaces/CachedRowModel_Core.md) @@ -20,12 +24,14 @@ 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\_ColumnGrouping](interfaces/Cell_ColumnGrouping.md) - [Cell\_Core](interfaces/Cell_Core.md) - [Cell\_CoreProperties](interfaces/Cell_CoreProperties.md) - [Cell\_FeatureMap](interfaces/Cell_FeatureMap.md) - [CellContext](interfaces/CellContext.md) +- [Column\_Aggregation](interfaces/Column_Aggregation.md) - [Column\_Column](interfaces/Column_Column.md) - [Column\_ColumnFaceting](interfaces/Column_ColumnFaceting.md) - [Column\_ColumnFiltering](interfaces/Column_ColumnFiltering.md) @@ -41,6 +47,7 @@ title: index - [Column\_GlobalFiltering](interfaces/Column_GlobalFiltering.md) - [Column\_Internal](interfaces/Column_Internal.md) - [Column\_RowSorting](interfaces/Column_RowSorting.md) +- [ColumnDef\_Aggregation](interfaces/ColumnDef_Aggregation.md) - [ColumnDef\_ColumnFiltering](interfaces/ColumnDef_ColumnFiltering.md) - [ColumnDef\_ColumnGrouping](interfaces/ColumnDef_ColumnGrouping.md) - [ColumnDef\_ColumnPinning](interfaces/ColumnDef_ColumnPinning.md) @@ -63,7 +70,6 @@ title: index - [columnResizingState](interfaces/columnResizingState.md) - [ColumnSort](interfaces/ColumnSort.md) - [CoreFeatures](interfaces/CoreFeatures.md) -- [CreatedAggregationFn](interfaces/CreatedAggregationFn.md) - [CreatedFilterFn](interfaces/CreatedFilterFn.md) - [CreatedSortFn](interfaces/CreatedSortFn.md) - [FeatureSlotPrereqs](interfaces/FeatureSlotPrereqs.md) @@ -86,7 +92,9 @@ title: index - [PaginationState](interfaces/PaginationState.md) - [Plugins](interfaces/Plugins.md) - [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) @@ -99,9 +107,9 @@ title: index - [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\_ColumnGrouping](interfaces/RowModelFns_ColumnGrouping.md) - [RowModelFns\_Core](interfaces/RowModelFns_Core.md) - [RowModelFns\_FeatureMap](interfaces/RowModelFns_FeatureMap.md) - [RowModelFns\_RowSorting](interfaces/RowModelFns_RowSorting.md) @@ -144,6 +152,7 @@ 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\_ColumnFiltering](interfaces/TableOptions_ColumnFiltering.md) - [TableOptions\_ColumnGrouping](interfaces/TableOptions_ColumnGrouping.md) @@ -188,7 +197,11 @@ title: index - [AccessorFnColumnDefBase](type-aliases/AccessorFnColumnDefBase.md) - [AccessorKeyColumnDef](type-aliases/AccessorKeyColumnDef.md) - [AccessorKeyColumnDefBase](type-aliases/AccessorKeyColumnDefBase.md) +- [AggregationFnListItem](type-aliases/AggregationFnListItem.md) - [AggregationFnOption](type-aliases/AggregationFnOption.md) +- [AggregationFnRef](type-aliases/AggregationFnRef.md) +- [AggregationResult](type-aliases/AggregationResult.md) +- [AggregationResultOf](type-aliases/AggregationResultOf.md) - [APIObject](type-aliases/APIObject.md) - [Atoms](type-aliases/Atoms.md) - [Atoms\_All](type-aliases/Atoms_All.md) @@ -201,6 +214,7 @@ title: index - [Cell](type-aliases/Cell.md) - [CellData](type-aliases/CellData.md) - [Column](type-aliases/Column.md) +- [ColumnAggregationValue](type-aliases/ColumnAggregationValue.md) - [ColumnDef](type-aliases/ColumnDef.md) - [ColumnDefBase](type-aliases/ColumnDefBase.md) - [ColumnDefBase\_All](type-aliases/ColumnDefBase_All.md) @@ -272,7 +286,11 @@ title: index ## Variables +- [aggregationFeature](variables/aggregationFeature.md) +- [aggregationFn\_count](variables/aggregationFn_count.md) - [aggregationFn\_extent](variables/aggregationFn_extent.md) +- [aggregationFn\_first](variables/aggregationFn_first.md) +- [aggregationFn\_last](variables/aggregationFn_last.md) - [aggregationFn\_max](variables/aggregationFn_max.md) - [aggregationFn\_mean](variables/aggregationFn_mean.md) - [aggregationFn\_median](variables/aggregationFn_median.md) @@ -300,6 +318,8 @@ title: index - [filterFn\_arrIncludes](variables/filterFn_arrIncludes.md) - [filterFn\_arrIncludesAll](variables/filterFn_arrIncludesAll.md) - [filterFn\_arrIncludesSome](variables/filterFn_arrIncludesSome.md) +- [filterFn\_between](variables/filterFn_between.md) +- [filterFn\_betweenInclusive](variables/filterFn_betweenInclusive.md) - [filterFn\_empty](variables/filterFn_empty.md) - [filterFn\_endsWith](variables/filterFn_endsWith.md) - [filterFn\_equals](variables/filterFn_equals.md) @@ -335,9 +355,6 @@ title: index ## Functions -- [aggregationFn\_count](functions/aggregationFn_count.md) -- [aggregationFn\_first](functions/aggregationFn_first.md) -- [aggregationFn\_last](functions/aggregationFn_last.md) - [assignPrototypeAPIs](functions/assignPrototypeAPIs.md) - [assignTableAPIs](functions/assignTableAPIs.md) - [buildHeaderGroups](functions/buildHeaderGroups.md) diff --git a/docs/reference/index/interfaces/AggregationContext.md b/docs/reference/index/interfaces/AggregationContext.md new file mode 100644 index 0000000000..53cd0d1c92 --- /dev/null +++ b/docs/reference/index/interfaces/AggregationContext.md @@ -0,0 +1,113 @@ +--- +id: AggregationContext +title: AggregationContext +--- + +# Interface: AggregationContext\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L14) + +Values and table objects available while one aggregation is evaluated. + +## Extended by + +- [`AggregationMergeContext`](AggregationMergeContext.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` = `unknown` + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L20) + +The column whose values are being aggregated. + +*** + +### columnId + +```ts +columnId: string; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L22) + +Convenience alias for `column.id`. + +*** + +### getValue() + +```ts +getValue: (row) => TValue; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L24) + +Reads this column's value from one of `rows`. + +#### Parameters + +##### row + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +#### Returns + +`TValue` + +*** + +### groupingRow? + +```ts +optional groupingRow: Row; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L30) + +The synthetic grouped row receiving this result. This property is omitted +for root or caller-supplied-row aggregation. Its `depth` identifies the +grouping level when grouped aggregation needs that distinction. + +*** + +### rows + +```ts +rows: readonly Row[]; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L35) + +Terminal leaf rows included in this aggregation. The executor normalizes +hierarchical and duplicate row inputs before invoking the definition. + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L37) + +The table that owns the column and rows. diff --git a/docs/reference/index/interfaces/AggregationFn.md b/docs/reference/index/interfaces/AggregationFn.md deleted file mode 100644 index 2a191660ca..0000000000 --- a/docs/reference/index/interfaces/AggregationFn.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -id: AggregationFn -title: AggregationFn ---- - -# Interface: AggregationFn()\ - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L30) - -## Type Parameters - -### TFeatures - -`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) - -### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -```ts -AggregationFn( - columnId, - leafRows, - childRows): any; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L34) - -## Parameters - -### columnId - -`string` - -### leafRows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -### childRows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -## Returns - -`any` - -## Properties - -### resolveDataValue? - -```ts -optional resolveDataValue: TransformDataValueFn; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L44) - -Normalizes each row's value before it is aggregated. Only honored by -aggregation functions built with `constructAggregationFn` (including the -value-based built-in aggregation functions). diff --git a/docs/reference/index/interfaces/AggregationFnDef.md b/docs/reference/index/interfaces/AggregationFnDef.md index fd3680752b..580b19edc3 100644 --- a/docs/reference/index/interfaces/AggregationFnDef.md +++ b/docs/reference/index/interfaces/AggregationFnDef.md @@ -3,79 +3,71 @@ id: AggregationFnDef title: AggregationFnDef --- -# Interface: AggregationFnDef\ +# Interface: AggregationFnDef\ -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:54](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L54) +Defined in: [features/aggregation/aggregationFeature.types.ts:54](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L54) -The definition object accepted by `constructAggregationFn`. - -`aggregate` is a value-level reducer: it receives the rows' (resolved) -values instead of the rows themselves, so normalization concerns stay in -`resolveDataValue`. - -## Extended by - -- [`CreatedAggregationFn`](CreatedAggregationFn.md) +A context-based aggregation definition and optional grouped-result merge. ## Type Parameters ### TFeatures -`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) = `any` ### TData -`TData` *extends* [`RowData`](../type-aliases/RowData.md) +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### TValue + +`TValue` = `unknown` + +### TResult + +`TResult` = `unknown` ## Properties ### aggregate() ```ts -aggregate: (values, rows, columnId) => any; +aggregate: (context) => TResult; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L58) - -#### Parameters - -##### values - -`any`[] +Defined in: [features/aggregation/aggregationFeature.types.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L61) -##### rows +Computes a result directly from normalized terminal rows. -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] +#### Parameters -##### columnId +##### context -`string` +[`AggregationContext`](AggregationContext.md)\<`TFeatures`, `TData`, `TValue`\> #### Returns -`any` +`TResult` *** -### fromRows? +### merge()? ```ts -optional fromRows: "leafRows" | "childRows"; +optional merge: (context) => TResult; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L69) +Defined in: [features/aggregation/aggregationFeature.types.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L66) -Which of the group's rows feed the aggregation: all of its `leafRows` -(the default) or its immediate `childRows`. For nested groups, child rows -expose already-aggregated values, which is faster for reaggregatable -computations like sums and extents. +Combines already-computed immediate child-group results. When omitted, +nested grouping falls back to `aggregate` over the group's terminal rows. -*** +#### Parameters -### resolveDataValue? +##### context -```ts -optional resolveDataValue: TransformDataValueFn; -``` +[`AggregationMergeContext`](AggregationMergeContext.md)\<`TFeatures`, `TData`, `TValue`, `TResult`\> + +#### Returns -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L70) +`TResult` diff --git a/docs/reference/index/interfaces/AggregationFnDescriptor.md b/docs/reference/index/interfaces/AggregationFnDescriptor.md new file mode 100644 index 0000000000..ab8b9981d0 --- /dev/null +++ b/docs/reference/index/interfaces/AggregationFnDescriptor.md @@ -0,0 +1,52 @@ +--- +id: AggregationFnDescriptor +title: AggregationFnDescriptor +--- + +# Interface: AggregationFnDescriptor\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L120) + +Gives an aggregation reference a stable key in a multiple result. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` = `unknown` + +### TResult + +`TResult` = `unknown` + +## Properties + +### aggregationFn + +```ts +aggregationFn: AggregationFnRef; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:127](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L127) + +The named, automatic, or inline definition to execute. + +*** + +### id + +```ts +id: string; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:129](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L129) + +Stable key used in the object returned by a multiple aggregation. diff --git a/docs/reference/index/interfaces/AggregationFns.md b/docs/reference/index/interfaces/AggregationFns.md index a342522625..b60705d3e2 100644 --- a/docs/reference/index/interfaces/AggregationFns.md +++ b/docs/reference/index/interfaces/AggregationFns.md @@ -5,4 +5,6 @@ title: AggregationFns # Interface: AggregationFns -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L28) +Defined in: [features/aggregation/aggregationFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L11) + +Declaration-merging fallback for named aggregation definitions. diff --git a/docs/reference/index/interfaces/AggregationMergeContext.md b/docs/reference/index/interfaces/AggregationMergeContext.md new file mode 100644 index 0000000000..3ed2dde8b9 --- /dev/null +++ b/docs/reference/index/interfaces/AggregationMergeContext.md @@ -0,0 +1,165 @@ +--- +id: AggregationMergeContext +title: AggregationMergeContext +--- + +# Interface: AggregationMergeContext\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L41) + +Additional values available when merging nested grouped results. + +## Extends + +- [`AggregationContext`](AggregationContext.md)\<`TFeatures`, `TData`, `TValue`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` + +### TResult + +`TResult` + +## Properties + +### childResults + +```ts +childResults: readonly TResult[]; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L48) + +Results produced for each immediate child group, in child-row order. + +*** + +### childRows + +```ts +childRows: readonly Row[]; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:50](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L50) + +Immediate child group rows corresponding to `childResults`. + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L20) + +The column whose values are being aggregated. + +#### Inherited from + +[`AggregationContext`](AggregationContext.md).[`column`](AggregationContext.md#column) + +*** + +### columnId + +```ts +columnId: string; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L22) + +Convenience alias for `column.id`. + +#### Inherited from + +[`AggregationContext`](AggregationContext.md).[`columnId`](AggregationContext.md#columnid) + +*** + +### getValue() + +```ts +getValue: (row) => TValue; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L24) + +Reads this column's value from one of `rows`. + +#### Parameters + +##### row + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +#### Returns + +`TValue` + +#### Inherited from + +[`AggregationContext`](AggregationContext.md).[`getValue`](AggregationContext.md#getvalue) + +*** + +### groupingRow? + +```ts +optional groupingRow: Row; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L30) + +The synthetic grouped row receiving this result. This property is omitted +for root or caller-supplied-row aggregation. Its `depth` identifies the +grouping level when grouped aggregation needs that distinction. + +#### Inherited from + +[`AggregationContext`](AggregationContext.md).[`groupingRow`](AggregationContext.md#groupingrow) + +*** + +### rows + +```ts +rows: readonly Row[]; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L35) + +Terminal leaf rows included in this aggregation. The executor normalizes +hierarchical and duplicate row inputs before invoking the definition. + +#### Inherited from + +[`AggregationContext`](AggregationContext.md).[`rows`](AggregationContext.md#rows) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L37) + +The table that owns the column and rows. + +#### Inherited from + +[`AggregationContext`](AggregationContext.md).[`table`](AggregationContext.md#table) diff --git a/docs/reference/index/interfaces/AggregationValueContext.md b/docs/reference/index/interfaces/AggregationValueContext.md new file mode 100644 index 0000000000..5e07ccef91 --- /dev/null +++ b/docs/reference/index/interfaces/AggregationValueContext.md @@ -0,0 +1,60 @@ +--- +id: AggregationValueContext +title: AggregationValueContext +--- + +# Interface: AggregationValueContext\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:285](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L285) + +Values passed to a column-level aggregation-value provider. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:291](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L291) + +The column whose value was requested. + +*** + +### rows? + +```ts +optional rows: readonly Row[]; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:293](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L293) + +Caller-provided rows, or `undefined` for the default row model. + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:295](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L295) + +The table that owns the column. diff --git a/docs/reference/index/interfaces/AggregationValueResult.md b/docs/reference/index/interfaces/AggregationValueResult.md new file mode 100644 index 0000000000..1e13229a44 --- /dev/null +++ b/docs/reference/index/interfaces/AggregationValueResult.md @@ -0,0 +1,28 @@ +--- +id: AggregationValueResult +title: AggregationValueResult +--- + +# Interface: AggregationValueResult\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:299](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L299) + +Marks an aggregation-value override as handled. + +## Type Parameters + +### TResult + +`TResult` = `unknown` + +## Properties + +### value + +```ts +value: TResult; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:301](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L301) + +The supplied value. `undefined` is still a handled result. diff --git a/docs/reference/index/interfaces/CachedRowModel_All.md b/docs/reference/index/interfaces/CachedRowModel_All.md index 5cff43a4ef..fbf5f136bd 100644 --- a/docs/reference/index/interfaces/CachedRowModel_All.md +++ b/docs/reference/index/interfaces/CachedRowModel_All.md @@ -197,7 +197,7 @@ Partial.globalFacetedUniqueValues optional groupedRowModel: () => RowModel; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:288](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L288) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:151](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L151) #### Returns diff --git a/docs/reference/index/interfaces/CachedRowModel_Grouped.md b/docs/reference/index/interfaces/CachedRowModel_Grouped.md index 174beb5271..a6ed56814a 100644 --- a/docs/reference/index/interfaces/CachedRowModel_Grouped.md +++ b/docs/reference/index/interfaces/CachedRowModel_Grouped.md @@ -5,7 +5,7 @@ title: CachedRowModel_Grouped # Interface: CachedRowModel\_Grouped\ -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:284](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L284) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:147](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L147) ## Type Parameters @@ -25,7 +25,7 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:284](https: groupedRowModel: () => RowModel; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:288](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L288) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:151](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L151) #### Returns diff --git a/docs/reference/index/interfaces/Cell_Aggregation.md b/docs/reference/index/interfaces/Cell_Aggregation.md new file mode 100644 index 0000000000..1579b3c135 --- /dev/null +++ b/docs/reference/index/interfaces/Cell_Aggregation.md @@ -0,0 +1,26 @@ +--- +id: Cell_Aggregation +title: Cell_Aggregation +--- + +# Interface: Cell\_Aggregation + +Defined in: [features/aggregation/aggregationFeature.types.ts:273](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L273) + +Cell instance APIs installed by `aggregationFeature`. + +## Properties + +### getIsAggregated() + +```ts +getIsAggregated: () => boolean; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:275](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L275) + +Whether this cell displays an aggregate on a synthetic grouped row. + +#### Returns + +`boolean` diff --git a/docs/reference/index/interfaces/Cell_ColumnGrouping.md b/docs/reference/index/interfaces/Cell_ColumnGrouping.md index 39e565a0ff..605c15d4ed 100644 --- a/docs/reference/index/interfaces/Cell_ColumnGrouping.md +++ b/docs/reference/index/interfaces/Cell_ColumnGrouping.md @@ -5,33 +5,17 @@ title: Cell_ColumnGrouping # Interface: Cell\_ColumnGrouping -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:211](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L211) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L78) ## Properties -### getIsAggregated() - -```ts -getIsAggregated: () => boolean; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:215](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L215) - -Checks whether this cell should render an aggregated value. - -#### Returns - -`boolean` - -*** - ### getIsGrouped() ```ts getIsGrouped: () => boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:219](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L219) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L82) Checks whether this cell represents the active grouping column. @@ -47,7 +31,7 @@ Checks whether this cell represents the active grouping column. getIsPlaceholder: () => boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:223](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L223) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L86) Checks whether this cell is hidden as a grouping placeholder. diff --git a/docs/reference/index/interfaces/Cell_Core.md b/docs/reference/index/interfaces/Cell_Core.md index 4ba49aab95..3a29039890 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:6](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L6) +Defined in: [types/Cell.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L7) ## Extends diff --git a/docs/reference/index/interfaces/Cell_FeatureMap.md b/docs/reference/index/interfaces/Cell_FeatureMap.md index 2d92ac0aa9..be71681a66 100644 --- a/docs/reference/index/interfaces/Cell_FeatureMap.md +++ b/docs/reference/index/interfaces/Cell_FeatureMap.md @@ -5,14 +5,24 @@ title: Cell_FeatureMap # Interface: Cell\_FeatureMap -Defined in: [types/Cell.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L12) +Defined in: [types/Cell.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L13) ## Properties +### aggregationFeature + +```ts +aggregationFeature: Cell_Aggregation; +``` + +Defined in: [types/Cell.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L14) + +*** + ### columnGroupingFeature ```ts columnGroupingFeature: Cell_ColumnGrouping; ``` -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:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L15) diff --git a/docs/reference/index/interfaces/ColumnDef_Aggregation.md b/docs/reference/index/interfaces/ColumnDef_Aggregation.md new file mode 100644 index 0000000000..edc8edd442 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_Aggregation.md @@ -0,0 +1,76 @@ +--- +id: ColumnDef_Aggregation +title: ColumnDef_Aggregation +--- + +# Interface: ColumnDef\_Aggregation\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:225](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L225) + +Column-definition options installed by `aggregationFeature`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### aggregatedCell? + +```ts +optional aggregatedCell: ColumnDefTemplate["getContext"]>>; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:231](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L231) + +Renderer used for a grouped row's aggregated cell. + +*** + +### aggregationFn? + +```ts +optional aggregationFn: AggregationFnOption; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:238](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L238) + +One aggregation reference for a scalar result, or an array for a keyed +result object. Inline definitions in an array require an explicit `id`. + +*** + +### getAggregationValue()? + +```ts +optional getAggregationValue: (context) => + | AggregationValueResult + | undefined; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:244](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L244) + +Optionally supplies a precomputed aggregation value for this column. +Return `{ value }` to handle the request, including `{ value: undefined }`; +return `undefined` to use the local aggregation fallback. + +#### Parameters + +##### context + +[`AggregationValueContext`](AggregationValueContext.md)\<`TFeatures`, `TData`, `TValue`\> + +#### Returns + + \| [`AggregationValueResult`](AggregationValueResult.md)\<`unknown`\> + \| `undefined` diff --git a/docs/reference/index/interfaces/ColumnDef_ColumnGrouping.md b/docs/reference/index/interfaces/ColumnDef_ColumnGrouping.md index c41866b4af..7a9eddc256 100644 --- a/docs/reference/index/interfaces/ColumnDef_ColumnGrouping.md +++ b/docs/reference/index/interfaces/ColumnDef_ColumnGrouping.md @@ -3,9 +3,9 @@ id: ColumnDef_ColumnGrouping title: ColumnDef_ColumnGrouping --- -# Interface: ColumnDef\_ColumnGrouping\ +# Interface: ColumnDef\_ColumnGrouping\ -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:123](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L123) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L12) ## Type Parameters @@ -17,43 +17,15 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:123](https: `TData` *extends* [`RowData`](../type-aliases/RowData.md) -### TValue - -`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) - ## Properties -### aggregatedCell? - -```ts -optional aggregatedCell: ColumnDefTemplate["getContext"]>>; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:131](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L131) - -The cell to display each row for the column if the cell is an aggregate. If a function is passed, it will be passed a props object with the context of the cell and should return the property type for your adapter (the exact type depends on the adapter being used). - -*** - -### aggregationFn? - -```ts -optional aggregationFn: AggregationFnOption; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:137](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L137) - -The resolved aggregation function for the column. - -*** - ### enableGrouping? ```ts optional enableGrouping: boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L143) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L21) Allows this column to be added to grouping state. @@ -67,7 +39,7 @@ Defaults to `true`; table-level `enableGrouping` must also allow grouping. optional getGroupingValue: (originalRow, index, row) => any; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:150](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L150) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L28) Returns the value used to group rows for this column. diff --git a/docs/reference/index/interfaces/ColumnDef_FeatureMap.md b/docs/reference/index/interfaces/ColumnDef_FeatureMap.md index b66e2e71c4..8184890c5c 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:130](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L130) +Defined in: [types/ColumnDef.ts:131](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L131) ## Type Parameters @@ -23,23 +23,33 @@ Defined in: [types/ColumnDef.ts:130](https://github.com/TanStack/table/blob/main ## Properties +### aggregationFeature + +```ts +aggregationFeature: ColumnDef_Aggregation; +``` + +Defined in: [types/ColumnDef.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L136) + +*** + ### columnFilteringFeature ```ts columnFilteringFeature: ColumnDef_ColumnFiltering; ``` -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:139](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L139) *** ### columnGroupingFeature ```ts -columnGroupingFeature: ColumnDef_ColumnGrouping; +columnGroupingFeature: ColumnDef_ColumnGrouping; ``` -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:142](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L142) *** @@ -49,7 +59,7 @@ Defined in: [types/ColumnDef.ts:140](https://github.com/TanStack/table/blob/main columnPinningFeature: ColumnDef_ColumnPinning; ``` -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:138](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L138) *** @@ -59,7 +69,7 @@ Defined in: [types/ColumnDef.ts:136](https://github.com/TanStack/table/blob/main columnResizingFeature: ColumnDef_ColumnResizing; ``` -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:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L144) *** @@ -69,7 +79,7 @@ Defined in: [types/ColumnDef.ts:142](https://github.com/TanStack/table/blob/main columnSizingFeature: ColumnDef_ColumnSizing; ``` -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:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L143) *** @@ -79,7 +89,7 @@ Defined in: [types/ColumnDef.ts:141](https://github.com/TanStack/table/blob/main columnVisibilityFeature: ColumnDef_ColumnVisibility; ``` -Defined in: [types/ColumnDef.ts:135](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L135) +Defined in: [types/ColumnDef.ts:137](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L137) *** @@ -89,7 +99,7 @@ Defined in: [types/ColumnDef.ts:135](https://github.com/TanStack/table/blob/main globalFilteringFeature: ColumnDef_GlobalFiltering; ``` -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) *** @@ -99,4 +109,4 @@ Defined in: [types/ColumnDef.ts:138](https://github.com/TanStack/table/blob/main rowSortingFeature: ColumnDef_RowSorting; ``` -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:141](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L141) diff --git a/docs/reference/index/interfaces/ColumnDefaultOptions.md b/docs/reference/index/interfaces/ColumnDefaultOptions.md index 899c6ec3b9..5e1806061c 100644 --- a/docs/reference/index/interfaces/ColumnDefaultOptions.md +++ b/docs/reference/index/interfaces/ColumnDefaultOptions.md @@ -5,7 +5,7 @@ title: ColumnDefaultOptions # Interface: ColumnDefaultOptions -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:226](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L226) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L89) ## Properties @@ -15,7 +15,7 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:226](https: enableGrouping: boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:227](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L227) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L90) *** @@ -25,4 +25,4 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:227](https: onGroupingChange: OnChangeFn; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:228](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L228) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L91) diff --git a/docs/reference/index/interfaces/ColumnMeta.md b/docs/reference/index/interfaces/ColumnMeta.md index bfc708c195..cd947daa4f 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:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L19) +Defined in: [types/ColumnDef.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L20) ## Type Parameters diff --git a/docs/reference/index/interfaces/Column_Aggregation.md b/docs/reference/index/interfaces/Column_Aggregation.md new file mode 100644 index 0000000000..8fb868302f --- /dev/null +++ b/docs/reference/index/interfaces/Column_Aggregation.md @@ -0,0 +1,85 @@ +--- +id: Column_Aggregation +title: Column_Aggregation +--- + +# Interface: Column\_Aggregation\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:250](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L250) + +Column instance APIs installed by `aggregationFeature`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getAggregationFns() + +```ts +getAggregationFns: () => readonly ResolvedAggregationFn[]; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:255](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L255) + +Resolves the configured scalar or multiple aggregation definitions. + +#### Returns + +readonly [`ResolvedAggregationFn`](ResolvedAggregationFn.md)\<`TFeatures`, `TData`\>[] + +*** + +### getAggregationValue() + +```ts +getAggregationValue: (rows?) => TResult; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:263](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L263) + +Aggregates this column over the default pre-grouped row model, or over a +caller-provided array of rows. Explicit rows are normalized to unique +terminal leaves and are intentionally not cached. + +#### Type Parameters + +##### TResult + +`TResult` = [`ColumnAggregationValue`](../type-aliases/ColumnAggregationValue.md)\<`TFeatures`\> + +#### Parameters + +##### rows? + +readonly [`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +#### Returns + +`TResult` + +*** + +### getAutoAggregationFn() + +```ts +getAutoAggregationFn: () => + | AggregationFnDef + | undefined; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:267](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L267) + +Infers `sum` for a numeric first row and `extent` for a Date first row. + +#### Returns + + \| [`AggregationFnDef`](AggregationFnDef.md)\<`TFeatures`, `TData`, `any`, `any`\> + \| `undefined` diff --git a/docs/reference/index/interfaces/Column_ColumnGrouping.md b/docs/reference/index/interfaces/Column_ColumnGrouping.md index 86da5fd020..ed08734eae 100644 --- a/docs/reference/index/interfaces/Column_ColumnGrouping.md +++ b/docs/reference/index/interfaces/Column_ColumnGrouping.md @@ -3,61 +3,19 @@ id: Column_ColumnGrouping title: Column_ColumnGrouping --- -# Interface: Column\_ColumnGrouping\ +# Interface: Column\_ColumnGrouping -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:157](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L157) - -## Type Parameters - -### TFeatures - -`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) - -### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L35) ## Properties -### getAggregationFn() - -```ts -getAggregationFn: () => AggregationFn | undefined; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:164](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L164) - -Returns the aggregation function for the column. - -#### Returns - -[`AggregationFn`](AggregationFn.md)\<`TFeatures`, `TData`\> \| `undefined` - -*** - -### getAutoAggregationFn() - -```ts -getAutoAggregationFn: () => AggregationFn | undefined; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:168](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L168) - -Returns the automatically inferred aggregation function for the column. - -#### Returns - -[`AggregationFn`](AggregationFn.md)\<`TFeatures`, `TData`\> \| `undefined` - -*** - ### getCanGroup() ```ts getCanGroup: () => boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:172](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L172) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L39) Checks whether this column can currently be grouped. @@ -73,7 +31,7 @@ Checks whether this column can currently be grouped. getGroupedIndex: () => number; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:176](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L176) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:43](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L43) Finds this column's position in the ordered grouping state. @@ -89,7 +47,7 @@ Finds this column's position in the ordered grouping state. getIsGrouped: () => boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:180](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L180) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L47) Checks whether this column id is present in grouping state. @@ -105,7 +63,7 @@ Checks whether this column id is present in grouping state. getToggleGroupingHandler: () => () => void; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:184](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L184) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L51) Returns a function that toggles the grouping state of the column. This is useful for passing to the `onClick` prop of a button. @@ -127,7 +85,7 @@ Returns a function that toggles the grouping state of the column. This is useful toggleGrouping: () => void; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:188](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L188) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L55) Toggles the grouping state of the column. diff --git a/docs/reference/index/interfaces/Column_Core.md b/docs/reference/index/interfaces/Column_Core.md index 39013de3ff..769f1fafe9 100644 --- a/docs/reference/index/interfaces/Column_Core.md +++ b/docs/reference/index/interfaces/Column_Core.md @@ -5,7 +5,7 @@ title: Column_Core # Interface: Column\_Core\ -Defined in: [types/Column.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L17) +Defined in: [types/Column.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L18) ## Extends diff --git a/docs/reference/index/interfaces/Column_FeatureMap.md b/docs/reference/index/interfaces/Column_FeatureMap.md index 6d45df5e64..c99c15e668 100644 --- a/docs/reference/index/interfaces/Column_FeatureMap.md +++ b/docs/reference/index/interfaces/Column_FeatureMap.md @@ -5,7 +5,7 @@ title: Column_FeatureMap # Interface: Column\_FeatureMap\ -Defined in: [types/Column.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L23) +Defined in: [types/Column.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L24) ## Type Parameters @@ -19,13 +19,23 @@ Defined in: [types/Column.ts:23](https://github.com/TanStack/table/blob/main/pac ## Properties +### aggregationFeature + +```ts +aggregationFeature: Column_Aggregation; +``` + +Defined in: [types/Column.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L28) + +*** + ### columnFacetingFeature ```ts columnFacetingFeature: Column_ColumnFaceting; ``` -Defined in: [types/Column.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L27) +Defined in: [types/Column.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L29) *** @@ -35,17 +45,17 @@ Defined in: [types/Column.ts:27](https://github.com/TanStack/table/blob/main/pac columnFilteringFeature: Column_ColumnFiltering; ``` -Defined in: [types/Column.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L28) +Defined in: [types/Column.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L30) *** ### columnGroupingFeature ```ts -columnGroupingFeature: Column_ColumnGrouping; +columnGroupingFeature: Column_ColumnGrouping; ``` -Defined in: [types/Column.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L29) +Defined in: [types/Column.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L31) *** @@ -55,7 +65,7 @@ Defined in: [types/Column.ts:29](https://github.com/TanStack/table/blob/main/pac columnOrderingFeature: Column_ColumnOrdering; ``` -Defined in: [types/Column.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L30) +Defined in: [types/Column.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L32) *** @@ -65,7 +75,7 @@ Defined in: [types/Column.ts:30](https://github.com/TanStack/table/blob/main/pac columnPinningFeature: Column_ColumnPinning; ``` -Defined in: [types/Column.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L31) +Defined in: [types/Column.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L33) *** @@ -75,7 +85,7 @@ Defined in: [types/Column.ts:31](https://github.com/TanStack/table/blob/main/pac columnResizingFeature: Column_ColumnResizing; ``` -Defined in: [types/Column.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L32) +Defined in: [types/Column.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L34) *** @@ -85,7 +95,7 @@ Defined in: [types/Column.ts:32](https://github.com/TanStack/table/blob/main/pac columnSizingFeature: Column_ColumnSizing; ``` -Defined in: [types/Column.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L33) +Defined in: [types/Column.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L35) *** @@ -95,7 +105,7 @@ Defined in: [types/Column.ts:33](https://github.com/TanStack/table/blob/main/pac columnVisibilityFeature: Column_ColumnVisibility; ``` -Defined in: [types/Column.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L34) +Defined in: [types/Column.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L36) *** @@ -105,7 +115,7 @@ Defined in: [types/Column.ts:34](https://github.com/TanStack/table/blob/main/pac globalFilteringFeature: Column_GlobalFiltering; ``` -Defined in: [types/Column.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L35) +Defined in: [types/Column.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L37) *** @@ -115,4 +125,4 @@ Defined in: [types/Column.ts:35](https://github.com/TanStack/table/blob/main/pac rowSortingFeature: Column_RowSorting; ``` -Defined in: [types/Column.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L36) +Defined in: [types/Column.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L38) diff --git a/docs/reference/index/interfaces/Column_Internal.md b/docs/reference/index/interfaces/Column_Internal.md index c1492df36c..3ee9f3bc1e 100644 --- a/docs/reference/index/interfaces/Column_Internal.md +++ b/docs/reference/index/interfaces/Column_Internal.md @@ -5,7 +5,7 @@ title: Column_Internal # Interface: Column\_Internal\ -Defined in: [types/Column.ts:46](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L46) +Defined in: [types/Column.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L48) ## Extends @@ -51,7 +51,7 @@ Omit.accessorFn columnDef: ColumnDefBase_All; ``` -Defined in: [types/Column.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L51) +Defined in: [types/Column.ts:53](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L53) *** @@ -177,4 +177,4 @@ Omit.parent table: Table_Internal; ``` -Defined in: [types/Column.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L52) +Defined in: [types/Column.ts:54](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L54) diff --git a/docs/reference/index/interfaces/CreatedAggregationFn.md b/docs/reference/index/interfaces/CreatedAggregationFn.md deleted file mode 100644 index 116141d4d0..0000000000 --- a/docs/reference/index/interfaces/CreatedAggregationFn.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -id: CreatedAggregationFn -title: CreatedAggregationFn ---- - -# Interface: CreatedAggregationFn()\ - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L82) - -The shape returned by `constructAggregationFn`: an `AggregationFn` with its -definition attached, so variants can be built by spreading it into another -`constructAggregationFn` call. - -The call signature is generic so a created aggregation function can be used -with any table's rows, regardless of the feature set it was defined -against. - -## Extends - -- [`AggregationFnDef`](AggregationFnDef.md)\<`TFeatures`, `TData`\> - -## Type Parameters - -### TFeatures - -`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) - -### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -```ts -CreatedAggregationFn( - columnId, - leafRows, - childRows): any; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L86) - -The shape returned by `constructAggregationFn`: an `AggregationFn` with its -definition attached, so variants can be built by spreading it into another -`constructAggregationFn` call. - -The call signature is generic so a created aggregation function can be used -with any table's rows, regardless of the feature set it was defined -against. - -## Type Parameters - -### TRowFeatures - -`TRowFeatures` *extends* [`TableFeatures`](TableFeatures.md) - -### TRowData - -`TRowData` *extends* [`RowData`](../type-aliases/RowData.md) - -## Parameters - -### columnId - -`string` - -### leafRows - -[`Row`](../type-aliases/Row.md)\<`TRowFeatures`, `TRowData`\>[] - -### childRows - -[`Row`](../type-aliases/Row.md)\<`TRowFeatures`, `TRowData`\>[] - -## Returns - -`any` - -## Properties - -### aggregate() - -```ts -aggregate: (values, rows, columnId) => any; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L58) - -#### Parameters - -##### values - -`any`[] - -##### rows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -##### columnId - -`string` - -#### Returns - -`any` - -#### Inherited from - -[`AggregationFnDef`](AggregationFnDef.md).[`aggregate`](AggregationFnDef.md#aggregate) - -*** - -### fromRows? - -```ts -optional fromRows: "leafRows" | "childRows"; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L69) - -Which of the group's rows feed the aggregation: all of its `leafRows` -(the default) or its immediate `childRows`. For nested groups, child rows -expose already-aggregated values, which is faster for reaggregatable -computations like sums and extents. - -#### Inherited from - -[`AggregationFnDef`](AggregationFnDef.md).[`fromRows`](AggregationFnDef.md#fromrows) - -*** - -### resolveDataValue? - -```ts -optional resolveDataValue: TransformDataValueFn; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L70) - -#### Inherited from - -[`AggregationFnDef`](AggregationFnDef.md).[`resolveDataValue`](AggregationFnDef.md#resolvedatavalue) diff --git a/docs/reference/index/interfaces/FeatureSlotPrereqs.md b/docs/reference/index/interfaces/FeatureSlotPrereqs.md index b09f429ea8..9a43f32f88 100644 --- a/docs/reference/index/interfaces/FeatureSlotPrereqs.md +++ b/docs/reference/index/interfaces/FeatureSlotPrereqs.md @@ -18,12 +18,12 @@ interface to get the same validation from `tableFeatures()`. ### aggregationFns ```ts -aggregationFns: "columnGroupingFeature"; +aggregationFns: "aggregationFeature"; ``` Defined in: [types/TableFeatures.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L95) -Named aggregation functions are only meaningful when grouping is enabled. +Named aggregation functions require the independent aggregation feature. *** diff --git a/docs/reference/index/interfaces/IdIdentifier.md b/docs/reference/index/interfaces/IdIdentifier.md index a20cefefee..ee15cbc3b6 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:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L82) +Defined in: [types/ColumnDef.ts:83](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L83) ## Type Parameters @@ -29,7 +29,7 @@ Defined in: [types/ColumnDef.ts:82](https://github.com/TanStack/table/blob/main/ optional header: ColumnDefTemplate>; ``` -Defined in: [types/ColumnDef.ts:94](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L94) +Defined in: [types/ColumnDef.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L95) 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:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L90) +Defined in: [types/ColumnDef.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L91) Explicit stable column id. diff --git a/docs/reference/index/interfaces/ResolvedAggregationFn.md b/docs/reference/index/interfaces/ResolvedAggregationFn.md new file mode 100644 index 0000000000..0552faae69 --- /dev/null +++ b/docs/reference/index/interfaces/ResolvedAggregationFn.md @@ -0,0 +1,46 @@ +--- +id: ResolvedAggregationFn +title: ResolvedAggregationFn +--- + +# Interface: ResolvedAggregationFn\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:214](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L214) + +A validated aggregation entry returned by `column.getAggregationFns()`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### aggregationFn + +```ts +aggregationFn: + | AggregationFnDef + | undefined; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:219](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L219) + +Resolved definition, or `undefined` when configuration is invalid. + +*** + +### id + +```ts +id: string | undefined; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:221](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L221) + +Key used for a multiple result; scalar inline definitions have no id. diff --git a/docs/reference/index/interfaces/RowModelFns_Aggregation.md b/docs/reference/index/interfaces/RowModelFns_Aggregation.md new file mode 100644 index 0000000000..84f365407d --- /dev/null +++ b/docs/reference/index/interfaces/RowModelFns_Aggregation.md @@ -0,0 +1,30 @@ +--- +id: RowModelFns_Aggregation +title: RowModelFns_Aggregation +--- + +# Interface: RowModelFns\_Aggregation\ + +Defined in: [features/aggregation/aggregationFeature.types.ts:87](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L87) + +Aggregation-definition registry carried by a table feature set. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### aggregationFns + +```ts +aggregationFns: Record>; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L91) diff --git a/docs/reference/index/interfaces/RowModelFns_All.md b/docs/reference/index/interfaces/RowModelFns_All.md index 045b77ed11..03db547701 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_ColumnGrouping`](RowModelFns_ColumnGrouping.md)\<`TFeatures`, `TData`\> & [`RowModelFns_RowSorting`](RowModelFns_RowSorting.md)\<`TFeatures`, `TData`\>\> +- `Partial`\<[`RowModelFns_ColumnFiltering`](RowModelFns_ColumnFiltering.md)\<`TFeatures`, `TData`\> & [`RowModelFns_Aggregation`](RowModelFns_Aggregation.md)\<`TFeatures`, `TData`\> & [`RowModelFns_RowSorting`](RowModelFns_RowSorting.md)\<`TFeatures`, `TData`\>\> ## Type Parameters @@ -26,10 +26,10 @@ Defined in: [types/RowModelFns.ts:25](https://github.com/TanStack/table/blob/mai ### aggregationFns? ```ts -optional aggregationFns: Record>; +optional aggregationFns: Record>; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L25) +Defined in: [features/aggregation/aggregationFeature.types.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L91) #### Inherited from diff --git a/docs/reference/index/interfaces/RowModelFns_ColumnGrouping.md b/docs/reference/index/interfaces/RowModelFns_ColumnGrouping.md deleted file mode 100644 index 4f85abfc53..0000000000 --- a/docs/reference/index/interfaces/RowModelFns_ColumnGrouping.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -id: RowModelFns_ColumnGrouping -title: RowModelFns_ColumnGrouping ---- - -# Interface: RowModelFns\_ColumnGrouping\ - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L21) - -## Type Parameters - -### TFeatures - -`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) - -### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -## Properties - -### aggregationFns - -```ts -aggregationFns: Record>; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L25) diff --git a/docs/reference/index/interfaces/RowModelFns_FeatureMap.md b/docs/reference/index/interfaces/RowModelFns_FeatureMap.md index 2762e3ce3b..1276bebb74 100644 --- a/docs/reference/index/interfaces/RowModelFns_FeatureMap.md +++ b/docs/reference/index/interfaces/RowModelFns_FeatureMap.md @@ -19,20 +19,20 @@ Defined in: [types/RowModelFns.ts:9](https://github.com/TanStack/table/blob/main ## Properties -### columnFilteringFeature +### aggregationFeature ```ts -columnFilteringFeature: RowModelFns_ColumnFiltering; +aggregationFeature: RowModelFns_Aggregation; ``` Defined in: [types/RowModelFns.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModelFns.ts#L13) *** -### columnGroupingFeature +### columnFilteringFeature ```ts -columnGroupingFeature: RowModelFns_ColumnGrouping; +columnFilteringFeature: RowModelFns_ColumnFiltering; ``` Defined in: [types/RowModelFns.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModelFns.ts#L14) diff --git a/docs/reference/index/interfaces/Row_Aggregation.md b/docs/reference/index/interfaces/Row_Aggregation.md new file mode 100644 index 0000000000..7a0af02a70 --- /dev/null +++ b/docs/reference/index/interfaces/Row_Aggregation.md @@ -0,0 +1,22 @@ +--- +id: Row_Aggregation +title: Row_Aggregation +--- + +# Interface: Row\_Aggregation + +Defined in: [features/aggregation/aggregationFeature.types.ts:279](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L279) + +Internal per-row cache used while grouped aggregates are evaluated. + +## Properties + +### \_aggregationValuesCache + +```ts +_aggregationValuesCache: Record; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:281](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L281) + +Cached aggregate results keyed by column id. diff --git a/docs/reference/index/interfaces/Row_ColumnGrouping.md b/docs/reference/index/interfaces/Row_ColumnGrouping.md index 65d438b0d0..f2ed593684 100644 --- a/docs/reference/index/interfaces/Row_ColumnGrouping.md +++ b/docs/reference/index/interfaces/Row_ColumnGrouping.md @@ -5,7 +5,7 @@ title: Row_ColumnGrouping # Interface: Row\_ColumnGrouping -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:191](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L191) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L58) ## Properties @@ -15,7 +15,7 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:191](https: _groupingValuesCache: Record; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:192](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L192) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L59) *** @@ -25,7 +25,7 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:192](https: getGroupingValue: (columnId) => unknown; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:196](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L196) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L63) Reads the value used to group this row for a column id. @@ -47,7 +47,7 @@ Reads the value used to group this row for a column id. getIsGrouped: () => boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:200](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L200) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L67) Checks whether this row represents a grouped row. @@ -63,7 +63,7 @@ Checks whether this row represents a grouped row. optional groupingColumnId: string; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:204](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L204) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L71) If this row is grouped, this is the id of the column that this row is grouped by. @@ -75,6 +75,6 @@ If this row is grouped, this is the id of the column that this row is grouped by optional groupingValue: unknown; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:208](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L208) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L75) If this row is grouped, this is the unique/shared value for the `groupingColumnId` for all of the rows in this group. diff --git a/docs/reference/index/interfaces/Row_Core.md b/docs/reference/index/interfaces/Row_Core.md index 9419241222..615d1a5d11 100644 --- a/docs/reference/index/interfaces/Row_Core.md +++ b/docs/reference/index/interfaces/Row_Core.md @@ -5,7 +5,7 @@ title: Row_Core # Interface: Row\_Core\ -Defined in: [types/Row.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L12) +Defined in: [types/Row.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L13) ## Extends diff --git a/docs/reference/index/interfaces/Row_FeatureMap.md b/docs/reference/index/interfaces/Row_FeatureMap.md index 46ba81add6..e97b54d9e2 100644 --- a/docs/reference/index/interfaces/Row_FeatureMap.md +++ b/docs/reference/index/interfaces/Row_FeatureMap.md @@ -5,7 +5,7 @@ title: Row_FeatureMap # Interface: Row\_FeatureMap\ -Defined in: [types/Row.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L17) +Defined in: [types/Row.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L18) ## Type Parameters @@ -19,13 +19,23 @@ Defined in: [types/Row.ts:17](https://github.com/TanStack/table/blob/main/packag ## Properties +### aggregationFeature + +```ts +aggregationFeature: Row_Aggregation; +``` + +Defined in: [types/Row.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L22) + +*** + ### columnFilteringFeature ```ts columnFilteringFeature: Row_ColumnFiltering; ``` -Defined in: [types/Row.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L21) +Defined in: [types/Row.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L23) *** @@ -35,7 +45,7 @@ Defined in: [types/Row.ts:21](https://github.com/TanStack/table/blob/main/packag columnGroupingFeature: Row_ColumnGrouping; ``` -Defined in: [types/Row.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L22) +Defined in: [types/Row.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L24) *** @@ -45,7 +55,7 @@ Defined in: [types/Row.ts:22](https://github.com/TanStack/table/blob/main/packag columnPinningFeature: Row_ColumnPinning; ``` -Defined in: [types/Row.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L23) +Defined in: [types/Row.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L25) *** @@ -55,7 +65,7 @@ Defined in: [types/Row.ts:23](https://github.com/TanStack/table/blob/main/packag columnVisibilityFeature: Row_ColumnVisibility; ``` -Defined in: [types/Row.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L24) +Defined in: [types/Row.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L26) *** @@ -65,7 +75,7 @@ Defined in: [types/Row.ts:24](https://github.com/TanStack/table/blob/main/packag rowExpandingFeature: Row_RowExpanding; ``` -Defined in: [types/Row.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L25) +Defined in: [types/Row.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L27) *** @@ -75,7 +85,7 @@ Defined in: [types/Row.ts:25](https://github.com/TanStack/table/blob/main/packag rowPinningFeature: Row_RowPinning; ``` -Defined in: [types/Row.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L26) +Defined in: [types/Row.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L28) *** @@ -85,4 +95,4 @@ Defined in: [types/Row.ts:26](https://github.com/TanStack/table/blob/main/packag rowSelectionFeature: Row_RowSelection; ``` -Defined in: [types/Row.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L27) +Defined in: [types/Row.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L29) diff --git a/docs/reference/index/interfaces/StockFeatures.md b/docs/reference/index/interfaces/StockFeatures.md index f1ecc0d2aa..db24168606 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:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L16) +Defined in: [features/stockFeatures.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L17) ## Properties +### aggregationFeature + +```ts +aggregationFeature: TableFeature; +``` + +Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L18) + +*** + ### columnFacetingFeature ```ts columnFacetingFeature: TableFeature; ``` -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:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L19) *** @@ -25,7 +35,7 @@ Defined in: [features/stockFeatures.ts:17](https://github.com/TanStack/table/blo columnFilteringFeature: 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:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L20) *** @@ -35,7 +45,7 @@ Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blo columnGroupingFeature: 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:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L21) *** @@ -45,7 +55,7 @@ Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blo columnOrderingFeature: 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:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L22) *** @@ -55,7 +65,7 @@ Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blo columnPinningFeature: 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:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L23) *** @@ -65,7 +75,7 @@ Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blo columnResizingFeature: 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:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L24) *** @@ -75,7 +85,7 @@ Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blo columnSizingFeature: 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:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L25) *** @@ -85,7 +95,7 @@ Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blo columnVisibilityFeature: 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:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L26) *** @@ -95,7 +105,7 @@ Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blo globalFilteringFeature: 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:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L27) *** @@ -105,7 +115,7 @@ Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blo rowExpandingFeature: 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:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L28) *** @@ -115,7 +125,7 @@ Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blo rowPaginationFeature: 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:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L29) *** @@ -125,7 +135,7 @@ Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blo rowPinningFeature: 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) *** @@ -135,7 +145,7 @@ Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blo rowSelectionFeature: 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) *** @@ -145,4 +155,4 @@ Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blo rowSortingFeature: 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) diff --git a/docs/reference/index/interfaces/StringHeaderIdentifier.md b/docs/reference/index/interfaces/StringHeaderIdentifier.md index 115c900c18..556fe1ffb7 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:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L71) +Defined in: [types/ColumnDef.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L72) ## Properties @@ -15,7 +15,7 @@ Defined in: [types/ColumnDef.ts:71](https://github.com/TanStack/table/blob/main/ header: string; ``` -Defined in: [types/ColumnDef.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L75) +Defined in: [types/ColumnDef.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L76) 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:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L79) +Defined in: [types/ColumnDef.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L80) 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 3e6bc81f0a..2fff0e39a7 100644 --- a/docs/reference/index/interfaces/TableFeatures.md +++ b/docs/reference/index/interfaces/TableFeatures.md @@ -21,10 +21,24 @@ options, and state types. ## Properties +### aggregationFeature? + +```ts +optional aggregationFeature: TableFeature; +``` + +Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L18) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`aggregationFeature`](StockFeatures.md#aggregationfeature) + +*** + ### aggregationFns? ```ts -optional aggregationFns: Record>; +optional aggregationFns: Record>; ``` Defined in: [types/TableFeatures.ts:193](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L193) @@ -46,7 +60,7 @@ every built-in aggregation function in your bundle. optional columnFacetingFeature: TableFeature; ``` -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:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L19) #### Inherited from @@ -60,7 +74,7 @@ Defined in: [features/stockFeatures.ts:17](https://github.com/TanStack/table/blo optional columnFilteringFeature: 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:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L20) #### Inherited from @@ -74,7 +88,7 @@ Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blo optional columnGroupingFeature: 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:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L21) #### Inherited from @@ -107,7 +121,7 @@ When omitted, the global declaration-merged `ColumnMeta` interface applies. optional columnOrderingFeature: 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:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L22) #### Inherited from @@ -121,7 +135,7 @@ Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blo optional columnPinningFeature: 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:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L23) #### Inherited from @@ -135,7 +149,7 @@ Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blo optional columnResizingFeature: 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:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L24) #### Inherited from @@ -149,7 +163,7 @@ Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blo optional columnSizingFeature: 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:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L25) #### Inherited from @@ -163,7 +177,7 @@ Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blo optional columnVisibilityFeature: 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:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L26) #### Inherited from @@ -506,7 +520,7 @@ When omitted, the global declaration-merged `FilterMeta` interface applies. optional globalFilteringFeature: 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:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L27) #### Inherited from @@ -580,7 +594,7 @@ server-side pagination. optional rowExpandingFeature: 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:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L28) #### Inherited from @@ -594,7 +608,7 @@ Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blo optional rowPaginationFeature: 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:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L29) #### Inherited from @@ -608,7 +622,7 @@ Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blo optional rowPinningFeature: 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 @@ -622,7 +636,7 @@ Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blo optional rowSelectionFeature: 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 @@ -636,7 +650,7 @@ Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blo optional rowSortingFeature: 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 diff --git a/docs/reference/index/interfaces/TableOptions_Aggregation.md b/docs/reference/index/interfaces/TableOptions_Aggregation.md new file mode 100644 index 0000000000..0e43b9f6ea --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_Aggregation.md @@ -0,0 +1,24 @@ +--- +id: TableOptions_Aggregation +title: TableOptions_Aggregation +--- + +# Interface: TableOptions\_Aggregation + +Defined in: [features/aggregation/aggregationFeature.types.ts:305](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L305) + +Table options installed by `aggregationFeature`. + +## Properties + +### manualAggregation? + +```ts +optional manualAggregation: boolean; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:311](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L311) + +Disables local `column.getAggregationValue()` calculation when a column +override does not handle the request. Group values supplied by manually +grouped rows remain the responsibility of the data owner. diff --git a/docs/reference/index/interfaces/TableOptions_ColumnGrouping.md b/docs/reference/index/interfaces/TableOptions_ColumnGrouping.md index c1cfaf2ad8..7fecb8eed3 100644 --- a/docs/reference/index/interfaces/TableOptions_ColumnGrouping.md +++ b/docs/reference/index/interfaces/TableOptions_ColumnGrouping.md @@ -5,7 +5,7 @@ title: TableOptions_ColumnGrouping # Interface: TableOptions\_ColumnGrouping -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:231](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L231) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:94](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L94) ## Properties @@ -15,7 +15,7 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:231](https: optional enableGrouping: boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:235](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L235) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:98](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L98) Allows columns to be grouped for this table. @@ -27,7 +27,7 @@ Allows columns to be grouped for this table. optional groupedColumnMode: false | "reorder" | "remove"; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:239](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L239) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L102) Grouping columns are automatically reordered by default to the start of the columns list. If you would rather remove them or leave them as-is, set the appropriate mode here. @@ -39,7 +39,7 @@ Grouping columns are automatically reordered by default to the start of the colu optional manualGrouping: boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:243](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L243) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:106](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L106) Enables manual grouping. If this option is set to `true`, the table will not automatically group rows using `getGroupedRowModel()` and instead will expect you to manually group the rows before passing them to the table. This is useful if you are doing server-side grouping and aggregation. @@ -51,7 +51,7 @@ Enables manual grouping. If this option is set to `true`, the table will not aut optional onGroupingChange: OnChangeFn; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:249](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L249) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L112) Called with an updater when grouping state changes. Pair this with `state.grouping` when using external state; external atoms can own the diff --git a/docs/reference/index/interfaces/TableOptions_Core.md b/docs/reference/index/interfaces/TableOptions_Core.md index dba585b96f..68bb5a4bcb 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:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L30) +Defined in: [types/TableOptions.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L31) 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 7c5420ea46..9e53d11f69 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:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L57) +Defined in: [types/TableOptions.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L58) ## Type Parameters @@ -19,13 +19,23 @@ Defined in: [types/TableOptions.ts:57](https://github.com/TanStack/table/blob/ma ## Properties +### aggregationFeature + +```ts +aggregationFeature: TableOptions_Aggregation; +``` + +Defined in: [types/TableOptions.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L62) + +*** + ### columnFilteringFeature ```ts columnFilteringFeature: TableOptions_ColumnFiltering; ``` -Defined in: [types/TableOptions.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L61) +Defined in: [types/TableOptions.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L63) *** @@ -35,7 +45,7 @@ Defined in: [types/TableOptions.ts:61](https://github.com/TanStack/table/blob/ma columnGroupingFeature: TableOptions_ColumnGrouping; ``` -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:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L64) *** @@ -45,7 +55,7 @@ Defined in: [types/TableOptions.ts:62](https://github.com/TanStack/table/blob/ma columnOrderingFeature: TableOptions_ColumnOrdering; ``` -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:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L65) *** @@ -55,7 +65,7 @@ Defined in: [types/TableOptions.ts:63](https://github.com/TanStack/table/blob/ma columnPinningFeature: TableOptions_ColumnPinning; ``` -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:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L66) *** @@ -65,7 +75,7 @@ Defined in: [types/TableOptions.ts:64](https://github.com/TanStack/table/blob/ma columnResizingFeature: TableOptions_ColumnResizing; ``` -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:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L67) *** @@ -75,7 +85,7 @@ Defined in: [types/TableOptions.ts:65](https://github.com/TanStack/table/blob/ma columnSizingFeature: TableOptions_ColumnSizing; ``` -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:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L68) *** @@ -85,7 +95,7 @@ Defined in: [types/TableOptions.ts:66](https://github.com/TanStack/table/blob/ma columnVisibilityFeature: TableOptions_ColumnVisibility; ``` -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:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L69) *** @@ -95,7 +105,7 @@ Defined in: [types/TableOptions.ts:67](https://github.com/TanStack/table/blob/ma globalFilteringFeature: TableOptions_GlobalFiltering; ``` -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:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L70) *** @@ -105,7 +115,7 @@ Defined in: [types/TableOptions.ts:68](https://github.com/TanStack/table/blob/ma rowExpandingFeature: TableOptions_RowExpanding; ``` -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:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L71) *** @@ -115,7 +125,7 @@ Defined in: [types/TableOptions.ts:69](https://github.com/TanStack/table/blob/ma rowPaginationFeature: TableOptions_RowPagination; ``` -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:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L72) *** @@ -125,7 +135,7 @@ Defined in: [types/TableOptions.ts:70](https://github.com/TanStack/table/blob/ma rowPinningFeature: TableOptions_RowPinning; ``` -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) *** @@ -135,7 +145,7 @@ Defined in: [types/TableOptions.ts:71](https://github.com/TanStack/table/blob/ma rowSelectionFeature: TableOptions_RowSelection; ``` -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) *** @@ -145,4 +155,4 @@ Defined in: [types/TableOptions.ts:72](https://github.com/TanStack/table/blob/ma rowSortingFeature: TableOptions_RowSorting; ``` -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) diff --git a/docs/reference/index/interfaces/TableState_All.md b/docs/reference/index/interfaces/TableState_All.md index c652f9e88d..861b57d2ec 100644 --- a/docs/reference/index/interfaces/TableState_All.md +++ b/docs/reference/index/interfaces/TableState_All.md @@ -136,7 +136,7 @@ Defined in: [features/global-filtering/globalFilteringFeature.types.ts:15](https optional grouping: GroupingState; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L18) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L9) #### Inherited from diff --git a/docs/reference/index/interfaces/TableState_ColumnGrouping.md b/docs/reference/index/interfaces/TableState_ColumnGrouping.md index ab7605d5ef..4891f3cf92 100644 --- a/docs/reference/index/interfaces/TableState_ColumnGrouping.md +++ b/docs/reference/index/interfaces/TableState_ColumnGrouping.md @@ -5,7 +5,7 @@ title: TableState_ColumnGrouping # Interface: TableState\_ColumnGrouping -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L17) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L8) ## Properties @@ -15,4 +15,4 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:17](https:/ grouping: GroupingState; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L18) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L9) diff --git a/docs/reference/index/interfaces/Table_ColumnGrouping.md b/docs/reference/index/interfaces/Table_ColumnGrouping.md index 65b8349d60..428a20a4e4 100644 --- a/docs/reference/index/interfaces/Table_ColumnGrouping.md +++ b/docs/reference/index/interfaces/Table_ColumnGrouping.md @@ -5,7 +5,7 @@ title: Table_ColumnGrouping # Interface: Table\_ColumnGrouping\<_TFeatures, _TData\> -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:254](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L254) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:117](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L117) ## Type Parameters @@ -25,7 +25,7 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:254](https: resetGrouping: (defaultState?) => void; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:263](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L263) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:126](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L126) Resets `grouping` to `initialState.grouping`. @@ -49,7 +49,7 @@ Pass `true` to ignore initial state and reset to `[]`. setGrouping: (updater) => void; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:267](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L267) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:130](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L130) Updates grouping state with a next ordered id array or updater function. diff --git a/docs/reference/index/interfaces/Table_Core.md b/docs/reference/index/interfaces/Table_Core.md index 3fab0987bb..d01de49646 100644 --- a/docs/reference/index/interfaces/Table_Core.md +++ b/docs/reference/index/interfaces/Table_Core.md @@ -523,9 +523,9 @@ Builds footer groups by reversing the current header group order. getGroupedRowModel: () => RowModel; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:277](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L277) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L140) -Resolves the row model after grouping and aggregation have been applied. +Resolves the row model after grouping has been applied. #### Returns @@ -652,7 +652,7 @@ Table_RowModels.getPreFilteredRowModel getPreGroupedRowModel: () => RowModel; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:281](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L281) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L144) Reads the row model immediately before grouping. diff --git a/docs/reference/index/interfaces/Table_Internal.md b/docs/reference/index/interfaces/Table_Internal.md index 72bc765b9c..ea45b6dca1 100644 --- a/docs/reference/index/interfaces/Table_Internal.md +++ b/docs/reference/index/interfaces/Table_Internal.md @@ -516,9 +516,9 @@ Builds footer groups by reversing the current header group order. getGroupedRowModel: () => RowModel; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:277](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L277) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L140) -Resolves the row model after grouping and aggregation have been applied. +Resolves the row model after grouping has been applied. #### Returns @@ -645,7 +645,7 @@ Table_RowModels.getPreFilteredRowModel getPreGroupedRowModel: () => RowModel; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:281](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L281) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L144) Reads the row model immediately before grouping. @@ -847,7 +847,7 @@ Defined in: [types/Table.ts:112](https://github.com/TanStack/table/blob/main/pac ### 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_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) diff --git a/docs/reference/index/interfaces/Table_RowModels_Grouped.md b/docs/reference/index/interfaces/Table_RowModels_Grouped.md index 33eacf4da1..b56159ec2c 100644 --- a/docs/reference/index/interfaces/Table_RowModels_Grouped.md +++ b/docs/reference/index/interfaces/Table_RowModels_Grouped.md @@ -5,7 +5,7 @@ title: Table_RowModels_Grouped # Interface: Table\_RowModels\_Grouped\ -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:270](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L270) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:133](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L133) ## Type Parameters @@ -25,9 +25,9 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:270](https: getGroupedRowModel: () => RowModel; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:277](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L277) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L140) -Resolves the row model after grouping and aggregation have been applied. +Resolves the row model after grouping has been applied. #### Returns @@ -41,7 +41,7 @@ Resolves the row model after grouping and aggregation have been applied. getPreGroupedRowModel: () => RowModel; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:281](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L281) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L144) Reads the row model immediately before grouping. diff --git a/docs/reference/index/type-aliases/AccessorColumnDef.md b/docs/reference/index/type-aliases/AccessorColumnDef.md index cc929db29b..42bd25e964 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:232](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L232) +Defined in: [types/ColumnDef.ts:235](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L235) ## Type Parameters diff --git a/docs/reference/index/type-aliases/AccessorFn.md b/docs/reference/index/type-aliases/AccessorFn.md index 5688982e2e..3efc0325f1 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:50](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L50) +Defined in: [types/ColumnDef.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L51) 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 a709444c94..39f4a6a30e 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:209](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L209) +Defined in: [types/ColumnDef.ts:212](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L212) ## Type Parameters diff --git a/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md b/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md index 8d33754ce5..5c996341b2 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:201](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L201) +Defined in: [types/ColumnDef.ts:204](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L204) ## Type Declaration diff --git a/docs/reference/index/type-aliases/AccessorKeyColumnDef.md b/docs/reference/index/type-aliases/AccessorKeyColumnDef.md index 09ecca041d..7294b75bab 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:225](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L225) +Defined in: [types/ColumnDef.ts:228](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L228) ## Type Parameters diff --git a/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md b/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md index 753ebe482a..a6e8466864 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:216](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L216) +Defined in: [types/ColumnDef.ts:219](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L219) ## Type Declaration diff --git a/docs/reference/index/type-aliases/AggregationFnListItem.md b/docs/reference/index/type-aliases/AggregationFnListItem.md new file mode 100644 index 0000000000..d9646f338b --- /dev/null +++ b/docs/reference/index/type-aliases/AggregationFnListItem.md @@ -0,0 +1,31 @@ +--- +id: AggregationFnListItem +title: AggregationFnListItem +--- + +# Type Alias: AggregationFnListItem\ + +```ts +type AggregationFnListItem = + | "auto" + | ExtractAggregationFnKeys +| AggregationFnDescriptor; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:133](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L133) + +One named or explicitly keyed entry in a multiple aggregation option. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` = `unknown` diff --git a/docs/reference/index/type-aliases/AggregationFnOption.md b/docs/reference/index/type-aliases/AggregationFnOption.md index 08698bea8d..2b469a3a62 100644 --- a/docs/reference/index/type-aliases/AggregationFnOption.md +++ b/docs/reference/index/type-aliases/AggregationFnOption.md @@ -3,16 +3,17 @@ id: AggregationFnOption title: AggregationFnOption --- -# Type Alias: AggregationFnOption\ +# Type Alias: AggregationFnOption\ ```ts -type AggregationFnOption = - | "auto" - | ExtractAggregationFnKeys -| AggregationFn; +type AggregationFnOption = + | AggregationFnRef +| ReadonlyArray>; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L115) +Defined in: [features/aggregation/aggregationFeature.types.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L143) + +A scalar aggregation reference or a list that produces a keyed object. ## Type Parameters @@ -23,3 +24,7 @@ Defined in: [features/column-grouping/columnGroupingFeature.types.ts:115](https: ### TData `TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` = `unknown` diff --git a/docs/reference/index/type-aliases/AggregationFnRef.md b/docs/reference/index/type-aliases/AggregationFnRef.md new file mode 100644 index 0000000000..45e7769558 --- /dev/null +++ b/docs/reference/index/type-aliases/AggregationFnRef.md @@ -0,0 +1,35 @@ +--- +id: AggregationFnRef +title: AggregationFnRef +--- + +# Type Alias: AggregationFnRef\ + +```ts +type AggregationFnRef = + | "auto" + | ExtractAggregationFnKeys +| AggregationFnDef; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L109) + +A registered name, automatic inference, or inline aggregation definition. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` = `unknown` + +### TResult + +`TResult` = `unknown` diff --git a/docs/reference/index/type-aliases/AggregationResult.md b/docs/reference/index/type-aliases/AggregationResult.md new file mode 100644 index 0000000000..436d3e0a0c --- /dev/null +++ b/docs/reference/index/type-aliases/AggregationResult.md @@ -0,0 +1,24 @@ +--- +id: AggregationResult +title: AggregationResult +--- + +# Type Alias: AggregationResult\ + +```ts +type AggregationResult = TOption extends ReadonlyArray ? { [TKey in AggregationEntryId]: AggregationResultOfRef>, TFeatures> } : AggregationResultOfRef; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:190](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L190) + +Infers the scalar or keyed result produced by an aggregation option. + +## Type Parameters + +### TOption + +`TOption` + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) = `any` diff --git a/docs/reference/index/type-aliases/AggregationResultOf.md b/docs/reference/index/type-aliases/AggregationResultOf.md new file mode 100644 index 0000000000..984b05164d --- /dev/null +++ b/docs/reference/index/type-aliases/AggregationResultOf.md @@ -0,0 +1,20 @@ +--- +id: AggregationResultOf +title: AggregationResultOf +--- + +# Type Alias: AggregationResultOf\ + +```ts +type AggregationResultOf = TDefinition extends AggregationFnDef ? TResult : unknown; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:152](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L152) + +Extracts the result type produced by an aggregation definition. + +## Type Parameters + +### TDefinition + +`TDefinition` diff --git a/docs/reference/index/type-aliases/BuiltInAggregationFn.md b/docs/reference/index/type-aliases/BuiltInAggregationFn.md index f85f0c1be5..66ecc6de69 100644 --- a/docs/reference/index/type-aliases/BuiltInAggregationFn.md +++ b/docs/reference/index/type-aliases/BuiltInAggregationFn.md @@ -9,4 +9,4 @@ title: BuiltInAggregationFn type BuiltInAggregationFn = keyof typeof aggregationFns; ``` -Defined in: [fns/aggregationFns.ts:303](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L303) +Defined in: [features/aggregation/aggregationFns.ts:336](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L336) diff --git a/docs/reference/index/type-aliases/BuiltInFilterFn.md b/docs/reference/index/type-aliases/BuiltInFilterFn.md index c9fe2ce8e1..233a47fcd0 100644 --- a/docs/reference/index/type-aliases/BuiltInFilterFn.md +++ b/docs/reference/index/type-aliases/BuiltInFilterFn.md @@ -9,4 +9,4 @@ title: BuiltInFilterFn type BuiltInFilterFn = keyof typeof filterFns; ``` -Defined in: [fns/filterFns.ts:455](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L455) +Defined in: [features/column-filtering/filterFns.ts:455](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L455) diff --git a/docs/reference/index/type-aliases/BuiltInSortFn.md b/docs/reference/index/type-aliases/BuiltInSortFn.md index ca0446f9cc..dd16d015ca 100644 --- a/docs/reference/index/type-aliases/BuiltInSortFn.md +++ b/docs/reference/index/type-aliases/BuiltInSortFn.md @@ -9,4 +9,4 @@ title: BuiltInSortFn type BuiltInSortFn = keyof typeof sortFns; ``` -Defined in: [fns/sortFns.ts:369](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L369) +Defined in: [features/row-sorting/sortFns.ts:366](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L366) diff --git a/docs/reference/index/type-aliases/Cell.md b/docs/reference/index/type-aliases/Cell.md index 52168082f7..96042fd3b6 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:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L16) +Defined in: [types/Cell.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L18) ## Type Parameters diff --git a/docs/reference/index/type-aliases/Column.md b/docs/reference/index/type-aliases/Column.md index 29bf73596a..285ad92513 100644 --- a/docs/reference/index/type-aliases/Column.md +++ b/docs/reference/index/type-aliases/Column.md @@ -9,7 +9,7 @@ title: Column type Column = Column_Core & ExtractFeatureMapTypes>; ``` -Defined in: [types/Column.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L39) +Defined in: [types/Column.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L41) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ColumnAggregationValue.md b/docs/reference/index/type-aliases/ColumnAggregationValue.md new file mode 100644 index 0000000000..ef8b3d4120 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnAggregationValue.md @@ -0,0 +1,23 @@ +--- +id: ColumnAggregationValue +title: ColumnAggregationValue +--- + +# Type Alias: ColumnAggregationValue\ + +```ts +type ColumnAggregationValue = + | RegisteredAggregationResult + | Record | undefined> + | undefined; +``` + +Defined in: [features/aggregation/aggregationFeature.types.ts:208](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L208) + +Default public result union when a column's precise option is not known. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) diff --git a/docs/reference/index/type-aliases/ColumnDef.md b/docs/reference/index/type-aliases/ColumnDef.md index af7d86dfdb..03fe75515d 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:240](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L240) +Defined in: [types/ColumnDef.ts:243](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L243) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ColumnDefBase.md b/docs/reference/index/type-aliases/ColumnDefBase.md index 8e0c300b97..dd701ad750 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:145](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L145) +Defined in: [types/ColumnDef.ts:147](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L147) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ColumnDefBase_All.md b/docs/reference/index/type-aliases/ColumnDefBase_All.md index 13a372b379..163842928d 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_GlobalFiltering & ColumnDef_RowSorting & ColumnDef_ColumnGrouping & ColumnDef_ColumnSizing & ColumnDef_ColumnResizing>; +type ColumnDefBase_All = ColumnDefBase_Core & Partial & ColumnDef_ColumnVisibility & ColumnDef_ColumnPinning & ColumnDef_ColumnFiltering & ColumnDef_GlobalFiltering & ColumnDef_RowSorting & ColumnDef_ColumnGrouping & ColumnDef_ColumnSizing & ColumnDef_ColumnResizing>; ``` -Defined in: [types/ColumnDef.ts:155](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L155) +Defined in: [types/ColumnDef.ts:157](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L157) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ColumnDefResolved.md b/docs/reference/index/type-aliases/ColumnDefResolved.md index caf4cff9e6..6ae3d58e84 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:249](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L249) +Defined in: [types/ColumnDef.ts:252](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L252) ## Type Declaration diff --git a/docs/reference/index/type-aliases/ColumnDefTemplate.md b/docs/reference/index/type-aliases/ColumnDefTemplate.md index fae53a3590..5dc24fca4e 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:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L61) +Defined in: [types/ColumnDef.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L62) A renderable column template value. diff --git a/docs/reference/index/type-aliases/CustomAggregationFns.md b/docs/reference/index/type-aliases/CustomAggregationFns.md index 6ff82f36d0..d4d26a9c2e 100644 --- a/docs/reference/index/type-aliases/CustomAggregationFns.md +++ b/docs/reference/index/type-aliases/CustomAggregationFns.md @@ -6,10 +6,12 @@ title: CustomAggregationFns # Type Alias: CustomAggregationFns\ ```ts -type CustomAggregationFns = Record>; +type CustomAggregationFns = Record>; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:93](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L93) +Defined in: [features/aggregation/aggregationFeature.types.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L95) + +Named context-based aggregation definitions registered on a feature set. ## Type Parameters diff --git a/docs/reference/index/type-aliases/DebugOptions.md b/docs/reference/index/type-aliases/DebugOptions.md index 0bbae09ea8..78adf506f7 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:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L47) +Defined in: [types/TableOptions.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L48) ## Type Declaration diff --git a/docs/reference/index/type-aliases/DisplayColumnDef.md b/docs/reference/index/type-aliases/DisplayColumnDef.md index 304f696e82..fb2b5d8229 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:180](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L180) +Defined in: [types/ColumnDef.ts:183](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L183) ## Type Parameters diff --git a/docs/reference/index/type-aliases/ExtractAggregationFnKeys.md b/docs/reference/index/type-aliases/ExtractAggregationFnKeys.md index b8173360fe..8ad4888d6d 100644 --- a/docs/reference/index/type-aliases/ExtractAggregationFnKeys.md +++ b/docs/reference/index/type-aliases/ExtractAggregationFnKeys.md @@ -11,16 +11,9 @@ type ExtractAggregationFnKeys = IsAny extends true ? | BuiltInAggregationFn : TFeatures extends object ? Extract : keyof AggregationFns; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:108](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L108) +Defined in: [features/aggregation/aggregationFeature.types.ts:101](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.types.ts#L101) -Resolves the valid string names for `columnDef.aggregationFn` for a feature -set. - -When the features object declares an `aggregationFns` registry -(`tableFeatures({ ..., aggregationFns })`), its keys are the only valid -names; a name is only assignable if an aggregation function is actually -registered for it. Otherwise this falls back to the global -declaration-merged `AggregationFns` interface. +String names available from a feature set's aggregation registry. ## Type Parameters diff --git a/docs/reference/index/type-aliases/ExtractColumnMeta.md b/docs/reference/index/type-aliases/ExtractColumnMeta.md index e120e7ab8a..e8e3ef4842 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:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L33) +Defined in: [types/ColumnDef.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L34) Resolves the type of `columnDef.meta` for a feature set. diff --git a/docs/reference/index/type-aliases/GroupColumnDef.md b/docs/reference/index/type-aliases/GroupColumnDef.md index 8965e42e06..4a229873e0 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:194](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L194) +Defined in: [types/ColumnDef.ts:197](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L197) ## Type Parameters diff --git a/docs/reference/index/type-aliases/GroupingColumnMode.md b/docs/reference/index/type-aliases/GroupingColumnMode.md index a5c20518f8..fa2c1b4fc6 100644 --- a/docs/reference/index/type-aliases/GroupingColumnMode.md +++ b/docs/reference/index/type-aliases/GroupingColumnMode.md @@ -9,4 +9,4 @@ title: GroupingColumnMode type GroupingColumnMode = false | "reorder" | "remove"; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:252](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L252) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L115) diff --git a/docs/reference/index/type-aliases/GroupingState.md b/docs/reference/index/type-aliases/GroupingState.md index 40690ac7cf..18a6a454a7 100644 --- a/docs/reference/index/type-aliases/GroupingState.md +++ b/docs/reference/index/type-aliases/GroupingState.md @@ -9,4 +9,4 @@ title: GroupingState type GroupingState = string[]; ``` -Defined in: [features/column-grouping/columnGroupingFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L15) +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:6](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L6) diff --git a/docs/reference/index/type-aliases/IdentifiedColumnDef.md b/docs/reference/index/type-aliases/IdentifiedColumnDef.md index 107856038c..3e72c5364a 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:171](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L171) +Defined in: [types/ColumnDef.ts:174](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L174) ## Type Declaration diff --git a/docs/reference/index/type-aliases/Row.md b/docs/reference/index/type-aliases/Row.md index 30c2955951..910bf660b9 100644 --- a/docs/reference/index/type-aliases/Row.md +++ b/docs/reference/index/type-aliases/Row.md @@ -9,7 +9,7 @@ title: Row type Row = Row_Core & ExtractFeatureMapTypes>; ``` -Defined in: [types/Row.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L30) +Defined in: [types/Row.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L32) ## Type Parameters diff --git a/docs/reference/index/type-aliases/StringOrTemplateHeader.md b/docs/reference/index/type-aliases/StringOrTemplateHeader.md index 3da1b1ccda..9f1ac9a3c0 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:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L65) +Defined in: [types/ColumnDef.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L66) ## Type Parameters diff --git a/docs/reference/index/type-aliases/TableOptions.md b/docs/reference/index/type-aliases/TableOptions.md index 01bd3ca06d..b170bea078 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:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L120) +Defined in: [types/TableOptions.ts:123](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L123) 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 55a367c2f5..3ccfa81774 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_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:131](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L131) +Defined in: [types/TableOptions.ts:134](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L134) 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/variables/aggregationFeature.md b/docs/reference/index/variables/aggregationFeature.md new file mode 100644 index 0000000000..6449e5fe2e --- /dev/null +++ b/docs/reference/index/variables/aggregationFeature.md @@ -0,0 +1,14 @@ +--- +id: aggregationFeature +title: aggregationFeature +--- + +# Variable: aggregationFeature + +```ts +const aggregationFeature: TableFeature; +``` + +Defined in: [features/aggregation/aggregationFeature.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.ts#L14) + +Independent aggregation feature for grouped values and root/custom-row totals. diff --git a/docs/reference/index/variables/aggregationFn_count.md b/docs/reference/index/variables/aggregationFn_count.md new file mode 100644 index 0000000000..4c5314a293 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_count.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_count +title: aggregationFn_count +--- + +# Variable: aggregationFn\_count + +```ts +const aggregationFn_count: AggregationFnDef; +``` + +Defined in: [features/aggregation/aggregationFns.ts:273](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L273) + +Counts rows, independently of the column's values. diff --git a/docs/reference/index/variables/aggregationFn_extent.md b/docs/reference/index/variables/aggregationFn_extent.md index b92be49bbc..e5dd1acb68 100644 --- a/docs/reference/index/variables/aggregationFn_extent.md +++ b/docs/reference/index/variables/aggregationFn_extent.md @@ -6,12 +6,10 @@ title: aggregationFn_extent # Variable: aggregationFn\_extent ```ts -const aggregationFn_extent: CreatedAggregationFn; +const aggregationFn_extent: AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L136) +Defined in: [features/aggregation/aggregationFns.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L143) -Finds the numeric extent for a grouped column. - -Returns `[min, max]`, where each entry is `undefined` when no numeric value is -present. +Finds the minimum and maximum numeric or Date values. Empty inputs return +`[undefined, undefined]`, preserving the previous built-in result shape. diff --git a/docs/reference/index/variables/aggregationFn_first.md b/docs/reference/index/variables/aggregationFn_first.md new file mode 100644 index 0000000000..1da1cbfa2e --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_first.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_first +title: aggregationFn_first +--- + +# Variable: aggregationFn\_first + +```ts +const aggregationFn_first: AggregationFnDef; +``` + +Defined in: [features/aggregation/aggregationFns.ts:291](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L291) + +Returns the first row's value, including a nullish value. diff --git a/docs/reference/index/variables/aggregationFn_last.md b/docs/reference/index/variables/aggregationFn_last.md new file mode 100644 index 0000000000..746e6138f3 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_last.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_last +title: aggregationFn_last +--- + +# Variable: aggregationFn\_last + +```ts +const aggregationFn_last: AggregationFnDef; +``` + +Defined in: [features/aggregation/aggregationFns.ts:303](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L303) + +Returns the last row's value, including a nullish value. diff --git a/docs/reference/index/variables/aggregationFn_max.md b/docs/reference/index/variables/aggregationFn_max.md index f3f576bd5d..fa92a6c7c0 100644 --- a/docs/reference/index/variables/aggregationFn_max.md +++ b/docs/reference/index/variables/aggregationFn_max.md @@ -6,12 +6,10 @@ title: aggregationFn_max # Variable: aggregationFn\_max ```ts -const aggregationFn_max: CreatedAggregationFn; +const aggregationFn_max: AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:111](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L111) +Defined in: [features/aggregation/aggregationFns.ts:108](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L108) -Finds the maximum numeric child-row value for a grouped column. - -Nullish and non-number values are ignored. Returns `undefined` when no -numeric value is found. +Finds the maximum numeric or Date value. Invalid value types are ignored; +`NaN` preserves the legacy numeric seeding behavior. diff --git a/docs/reference/index/variables/aggregationFn_mean.md b/docs/reference/index/variables/aggregationFn_mean.md index 177203757d..adb036cbdb 100644 --- a/docs/reference/index/variables/aggregationFn_mean.md +++ b/docs/reference/index/variables/aggregationFn_mean.md @@ -6,12 +6,10 @@ title: aggregationFn_mean # Variable: aggregationFn\_mean ```ts -const aggregationFn_mean: CreatedAggregationFn; +const aggregationFn_mean: AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:164](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L164) +Defined in: [features/aggregation/aggregationFns.ts:192](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L192) -Averages numeric leaf-row values for a grouped column. - -Number-like values are coerced with unary `+`; nullish and non-numeric values -are ignored. +Averages number and number-like row values. Nullish and non-numeric values +are ignored; other values retain the legacy unary-plus coercion behavior. diff --git a/docs/reference/index/variables/aggregationFn_median.md b/docs/reference/index/variables/aggregationFn_median.md index 8a0c96173f..126213feca 100644 --- a/docs/reference/index/variables/aggregationFn_median.md +++ b/docs/reference/index/variables/aggregationFn_median.md @@ -6,12 +6,10 @@ title: aggregationFn_median # Variable: aggregationFn\_median ```ts -const aggregationFn_median: CreatedAggregationFn; +const aggregationFn_median: AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:195](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L195) +Defined in: [features/aggregation/aggregationFns.ts:218](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L218) -Computes the median of numeric leaf-row values for a grouped column. - -All values must be numbers. If any value is non-numeric, or no leaf rows are -present, the result is `undefined`. +Computes the median when every row value is a number. Returns `undefined` +for empty inputs or when any value is non-numeric. diff --git a/docs/reference/index/variables/aggregationFn_min.md b/docs/reference/index/variables/aggregationFn_min.md index 3f8f707efe..11dad0586b 100644 --- a/docs/reference/index/variables/aggregationFn_min.md +++ b/docs/reference/index/variables/aggregationFn_min.md @@ -6,12 +6,10 @@ title: aggregationFn_min # Variable: aggregationFn\_min ```ts -const aggregationFn_min: CreatedAggregationFn; +const aggregationFn_min: AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L86) +Defined in: [features/aggregation/aggregationFns.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L73) -Finds the minimum numeric child-row value for a grouped column. - -Nullish and non-number values are ignored. Returns `undefined` when no -numeric value is found. +Finds the minimum numeric or Date value. Invalid value types are ignored; +`NaN` preserves the legacy numeric seeding behavior. diff --git a/docs/reference/index/variables/aggregationFn_sum.md b/docs/reference/index/variables/aggregationFn_sum.md index c55bfd38d9..d85f587a71 100644 --- a/docs/reference/index/variables/aggregationFn_sum.md +++ b/docs/reference/index/variables/aggregationFn_sum.md @@ -6,12 +6,10 @@ title: aggregationFn_sum # Variable: aggregationFn\_sum ```ts -const aggregationFn_sum: CreatedAggregationFn; +const aggregationFn_sum: AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L68) +Defined in: [features/aggregation/aggregationFns.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L45) -Sums numeric child-row values for a grouped column. - -Non-number values contribute `0`. Child rows are used so nested group totals -can reuse already aggregated values. +Sums numeric row values. Non-number values contribute zero. As in the +previous API, `NaN` is a number and therefore propagates through the sum. diff --git a/docs/reference/index/variables/aggregationFn_unique.md b/docs/reference/index/variables/aggregationFn_unique.md index 6d7f7be58e..6db3b38bd9 100644 --- a/docs/reference/index/variables/aggregationFn_unique.md +++ b/docs/reference/index/variables/aggregationFn_unique.md @@ -6,11 +6,9 @@ title: aggregationFn_unique # Variable: aggregationFn\_unique ```ts -const aggregationFn_unique: CreatedAggregationFn; +const aggregationFn_unique: AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:222](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L222) +Defined in: [features/aggregation/aggregationFns.ts:241](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L241) -Collects unique leaf-row values for a grouped column. - -Values are compared with JavaScript `Set` semantics. +Collects distinct row values using JavaScript `Set` semantics. diff --git a/docs/reference/index/variables/aggregationFn_uniqueCount.md b/docs/reference/index/variables/aggregationFn_uniqueCount.md index baa8906fed..b290fa7b12 100644 --- a/docs/reference/index/variables/aggregationFn_uniqueCount.md +++ b/docs/reference/index/variables/aggregationFn_uniqueCount.md @@ -6,11 +6,9 @@ title: aggregationFn_uniqueCount # Variable: aggregationFn\_uniqueCount ```ts -const aggregationFn_uniqueCount: CreatedAggregationFn; +const aggregationFn_uniqueCount: AggregationFnDef; ``` -Defined in: [fns/aggregationFns.ts:231](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L231) +Defined in: [features/aggregation/aggregationFns.ts:257](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L257) -Counts unique leaf-row values for a grouped column. - -Values are compared with JavaScript `Set` semantics. +Counts distinct row values using JavaScript `Set` semantics. diff --git a/docs/reference/index/variables/aggregationFns.md b/docs/reference/index/variables/aggregationFns.md index f20c2205d9..607abc59b5 100644 --- a/docs/reference/index/variables/aggregationFns.md +++ b/docs/reference/index/variables/aggregationFns.md @@ -9,175 +9,79 @@ title: aggregationFns const aggregationFns: object; ``` -Defined in: [fns/aggregationFns.ts:289](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L289) +Defined in: [features/aggregation/aggregationFns.ts:322](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFns.ts#L322) -The built-in aggregation function registry. - -Registering this full object opts out of tree-shaking: every built-in -aggregation function ends up in your bundle. Prefer importing the -`aggregationFn_*` functions you actually use and registering just those in -the `aggregationFns` slot, or passing them directly to the `aggregationFn` -column option. +Full built-in registry. Register individual definitions for tree-shaking. ## Type Declaration -### ~~count()~~ +### ~~count~~ ```ts -count: (_columnId, leafRows) => number = aggregationFn_count; +count: AggregationFnDef = aggregationFn_count; ``` -Counts the number of leaf rows in the group. - -The column id is ignored because the result is based only on group size. -This is a plain row-level function (not built with -`constructAggregationFn`) because it never reads row values. - -#### Type Parameters - -##### TFeatures - -`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) - -##### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -#### Parameters - -##### \_columnId - -`string` - -##### leafRows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -#### Returns - -`number` - ### ~~extent~~ ```ts -extent: CreatedAggregationFn = aggregationFn_extent; +extent: AggregationFnDef = aggregationFn_extent; ``` -### ~~first()~~ +### ~~first~~ ```ts -first: (columnId, leafRows) => unknown = aggregationFn_first; +first: AggregationFnDef = aggregationFn_first; ``` -Returns the first leaf-row value for a grouped column. - -This is a plain row-level function (not built with -`constructAggregationFn`) because it only reads one positional value. - -#### Type Parameters - -##### TFeatures - -`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) - -##### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -#### Parameters - -##### columnId - -`string` - -##### leafRows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -#### Returns - -`unknown` - -### ~~last()~~ +### ~~last~~ ```ts -last: (columnId, leafRows) => unknown = aggregationFn_last; +last: AggregationFnDef = aggregationFn_last; ``` -Returns the last leaf-row value for a grouped column. - -This is a plain row-level function (not built with -`constructAggregationFn`) because it only reads one positional value. - -#### Type Parameters - -##### TFeatures - -`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) - -##### TData - -`TData` *extends* [`RowData`](../type-aliases/RowData.md) - -#### Parameters - -##### columnId - -`string` - -##### leafRows - -[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] - -#### Returns - -`unknown` - ### ~~max~~ ```ts -max: CreatedAggregationFn = aggregationFn_max; +max: AggregationFnDef = aggregationFn_max; ``` ### ~~mean~~ ```ts -mean: CreatedAggregationFn = aggregationFn_mean; +mean: AggregationFnDef = aggregationFn_mean; ``` ### ~~median~~ ```ts -median: CreatedAggregationFn = aggregationFn_median; +median: AggregationFnDef = aggregationFn_median; ``` ### ~~min~~ ```ts -min: CreatedAggregationFn = aggregationFn_min; +min: AggregationFnDef = aggregationFn_min; ``` ### ~~sum~~ ```ts -sum: CreatedAggregationFn = aggregationFn_sum; +sum: AggregationFnDef = aggregationFn_sum; ``` ### ~~unique~~ ```ts -unique: CreatedAggregationFn = aggregationFn_unique; +unique: AggregationFnDef = aggregationFn_unique; ``` ### ~~uniqueCount~~ ```ts -uniqueCount: CreatedAggregationFn = aggregationFn_uniqueCount; +uniqueCount: AggregationFnDef = aggregationFn_uniqueCount; ``` ## Deprecated -Import individual `aggregationFn_*` functions instead for a -smaller bundle. This export still works and is not going away in v9, but -built-in name resolution (including `aggregationFn: 'auto'`) only finds -functions you register yourself. +Import individual `aggregationFn_*` definitions instead for a +smaller bundle. This registry remains available for compatibility. diff --git a/docs/reference/index/variables/columnGroupingFeature.md b/docs/reference/index/variables/columnGroupingFeature.md index d93579a4be..d14b0fda89 100644 --- a/docs/reference/index/variables/columnGroupingFeature.md +++ b/docs/reference/index/variables/columnGroupingFeature.md @@ -9,6 +9,6 @@ title: columnGroupingFeature const columnGroupingFeature: TableFeature; ``` -Defined in: [features/column-grouping/columnGroupingFeature.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts#L29) +Defined in: [features/column-grouping/columnGroupingFeature.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts#L26) -Feature that adds column grouping state, aggregation defaults, and grouped row APIs. +Feature that adds column grouping state and grouped row APIs. diff --git a/docs/reference/index/variables/filterFn_arrHas.md b/docs/reference/index/variables/filterFn_arrHas.md index a908304532..ebbd3495a2 100644 --- a/docs/reference/index/variables/filterFn_arrHas.md +++ b/docs/reference/index/variables/filterFn_arrHas.md @@ -9,6 +9,6 @@ title: filterFn_arrHas const filterFn_arrHas: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:356](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L356) +Defined in: [features/column-filtering/filterFns.ts:356](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L356) Keeps rows whose scalar column value equals at least one filter value. diff --git a/docs/reference/index/variables/filterFn_arrIncludes.md b/docs/reference/index/variables/filterFn_arrIncludes.md index 43ddcffdc3..136283cbb6 100644 --- a/docs/reference/index/variables/filterFn_arrIncludes.md +++ b/docs/reference/index/variables/filterFn_arrIncludes.md @@ -9,6 +9,6 @@ title: filterFn_arrIncludes const filterFn_arrIncludes: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:371](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L371) +Defined in: [features/column-filtering/filterFns.ts:371](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L371) Keeps rows whose array or string column value includes at least one filter value. diff --git a/docs/reference/index/variables/filterFn_arrIncludesAll.md b/docs/reference/index/variables/filterFn_arrIncludesAll.md index e9f4cdfb0e..f77d304550 100644 --- a/docs/reference/index/variables/filterFn_arrIncludesAll.md +++ b/docs/reference/index/variables/filterFn_arrIncludesAll.md @@ -9,6 +9,6 @@ title: filterFn_arrIncludesAll const filterFn_arrIncludesAll: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:390](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L390) +Defined in: [features/column-filtering/filterFns.ts:390](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L390) Keeps rows whose array column value includes every filter value. diff --git a/docs/reference/index/variables/filterFn_arrIncludesSome.md b/docs/reference/index/variables/filterFn_arrIncludesSome.md index c5c14f133a..e136ef40c1 100644 --- a/docs/reference/index/variables/filterFn_arrIncludesSome.md +++ b/docs/reference/index/variables/filterFn_arrIncludesSome.md @@ -9,6 +9,6 @@ title: filterFn_arrIncludesSome const filterFn_arrIncludesSome: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:406](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L406) +Defined in: [features/column-filtering/filterFns.ts:406](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L406) Keeps rows whose array column value includes at least one filter value. diff --git a/docs/reference/index/variables/filterFn_between.md b/docs/reference/index/variables/filterFn_between.md new file mode 100644 index 0000000000..7276b7fbde --- /dev/null +++ b/docs/reference/index/variables/filterFn_between.md @@ -0,0 +1,16 @@ +--- +id: filterFn_between +title: filterFn_between +--- + +# Variable: filterFn\_between + +```ts +const filterFn_between: CreatedFilterFn; +``` + +Defined in: [features/column-filtering/filterFns.ts:256](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L256) + +Keeps rows whose value falls between an exclusive min/max pair. + +Blank range endpoints are treated as open-ended. diff --git a/docs/reference/index/variables/filterFn_betweenInclusive.md b/docs/reference/index/variables/filterFn_betweenInclusive.md new file mode 100644 index 0000000000..b7c654e2be --- /dev/null +++ b/docs/reference/index/variables/filterFn_betweenInclusive.md @@ -0,0 +1,16 @@ +--- +id: filterFn_betweenInclusive +title: filterFn_betweenInclusive +--- + +# Variable: filterFn\_betweenInclusive + +```ts +const filterFn_betweenInclusive: CreatedFilterFn; +``` + +Defined in: [features/column-filtering/filterFns.ts:269](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L269) + +Keeps rows whose value falls between an inclusive min/max pair. + +Blank range endpoints are treated as open-ended. diff --git a/docs/reference/index/variables/filterFn_empty.md b/docs/reference/index/variables/filterFn_empty.md index f516234c75..f8fec96c8c 100644 --- a/docs/reference/index/variables/filterFn_empty.md +++ b/docs/reference/index/variables/filterFn_empty.md @@ -9,7 +9,7 @@ title: filterFn_empty const filterFn_empty: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:185](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L185) +Defined in: [features/column-filtering/filterFns.ts:185](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L185) Keeps rows whose column value is empty. diff --git a/docs/reference/index/variables/filterFn_endsWith.md b/docs/reference/index/variables/filterFn_endsWith.md index 97a05c2e79..b434c4599e 100644 --- a/docs/reference/index/variables/filterFn_endsWith.md +++ b/docs/reference/index/variables/filterFn_endsWith.md @@ -9,7 +9,7 @@ title: filterFn_endsWith const filterFn_endsWith: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:168](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L168) +Defined in: [features/column-filtering/filterFns.ts:168](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L168) Keeps rows whose stringified column value ends with the filter text. diff --git a/docs/reference/index/variables/filterFn_equals.md b/docs/reference/index/variables/filterFn_equals.md index 0d20997896..ff34d0faf7 100644 --- a/docs/reference/index/variables/filterFn_equals.md +++ b/docs/reference/index/variables/filterFn_equals.md @@ -9,7 +9,7 @@ title: filterFn_equals const filterFn_equals: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L77) +Defined in: [features/column-filtering/filterFns.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L77) Keeps rows whose column value is strictly equal to the filter value. diff --git a/docs/reference/index/variables/filterFn_equalsString.md b/docs/reference/index/variables/filterFn_equalsString.md index 1aa0e33d68..6b940cdd15 100644 --- a/docs/reference/index/variables/filterFn_equalsString.md +++ b/docs/reference/index/variables/filterFn_equalsString.md @@ -9,7 +9,7 @@ title: filterFn_equalsString const filterFn_equalsString: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:127](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L127) +Defined in: [features/column-filtering/filterFns.ts:127](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L127) Keeps rows whose stringified column value equals the filter text. diff --git a/docs/reference/index/variables/filterFn_equalsStringSensitive.md b/docs/reference/index/variables/filterFn_equalsStringSensitive.md index 455b800108..5acdbf2cfa 100644 --- a/docs/reference/index/variables/filterFn_equalsStringSensitive.md +++ b/docs/reference/index/variables/filterFn_equalsStringSensitive.md @@ -9,7 +9,7 @@ title: filterFn_equalsStringSensitive const filterFn_equalsStringSensitive: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L140) +Defined in: [features/column-filtering/filterFns.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L140) Keeps rows whose stringified column value exactly equals the filter text. diff --git a/docs/reference/index/variables/filterFn_greaterThan.md b/docs/reference/index/variables/filterFn_greaterThan.md index 17fa18d4f8..167a7ac9a5 100644 --- a/docs/reference/index/variables/filterFn_greaterThan.md +++ b/docs/reference/index/variables/filterFn_greaterThan.md @@ -9,7 +9,7 @@ title: filterFn_greaterThan const filterFn_greaterThan: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:210](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L210) +Defined in: [features/column-filtering/filterFns.ts:210](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L210) Keeps rows whose value is greater than the filter value. diff --git a/docs/reference/index/variables/filterFn_greaterThanOrEqualTo.md b/docs/reference/index/variables/filterFn_greaterThanOrEqualTo.md index 6f75052fd9..2b4b70c537 100644 --- a/docs/reference/index/variables/filterFn_greaterThanOrEqualTo.md +++ b/docs/reference/index/variables/filterFn_greaterThanOrEqualTo.md @@ -9,7 +9,7 @@ title: filterFn_greaterThanOrEqualTo const filterFn_greaterThanOrEqualTo: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:221](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L221) +Defined in: [features/column-filtering/filterFns.ts:221](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L221) Keeps rows whose value is greater than or equal to the filter value. diff --git a/docs/reference/index/variables/filterFn_inDateRange.md b/docs/reference/index/variables/filterFn_inDateRange.md index a08004161e..21f9db496b 100644 --- a/docs/reference/index/variables/filterFn_inDateRange.md +++ b/docs/reference/index/variables/filterFn_inDateRange.md @@ -9,7 +9,7 @@ title: filterFn_inDateRange const filterFn_inDateRange: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:323](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L323) +Defined in: [features/column-filtering/filterFns.ts:323](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L323) Keeps rows whose date value is inside an inclusive `[min, max]` date range. diff --git a/docs/reference/index/variables/filterFn_inNumberRange.md b/docs/reference/index/variables/filterFn_inNumberRange.md index bca6c3f449..832089f506 100644 --- a/docs/reference/index/variables/filterFn_inNumberRange.md +++ b/docs/reference/index/variables/filterFn_inNumberRange.md @@ -9,7 +9,7 @@ title: filterFn_inNumberRange const filterFn_inNumberRange: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:283](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L283) +Defined in: [features/column-filtering/filterFns.ts:283](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L283) Keeps rows whose numeric value is inside an inclusive `[min, max]` range. diff --git a/docs/reference/index/variables/filterFn_includesString.md b/docs/reference/index/variables/filterFn_includesString.md index 0d375724fd..f03a825c51 100644 --- a/docs/reference/index/variables/filterFn_includesString.md +++ b/docs/reference/index/variables/filterFn_includesString.md @@ -9,7 +9,7 @@ title: filterFn_includesString const filterFn_includesString: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:113](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L113) +Defined in: [features/column-filtering/filterFns.ts:113](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L113) Keeps rows whose stringified column value includes the filter text. diff --git a/docs/reference/index/variables/filterFn_includesStringSensitive.md b/docs/reference/index/variables/filterFn_includesStringSensitive.md index 49872f2c33..a7898836c1 100644 --- a/docs/reference/index/variables/filterFn_includesStringSensitive.md +++ b/docs/reference/index/variables/filterFn_includesStringSensitive.md @@ -9,7 +9,7 @@ title: filterFn_includesStringSensitive const filterFn_includesStringSensitive: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:100](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L100) +Defined in: [features/column-filtering/filterFns.ts:100](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L100) Keeps rows whose stringified column value includes the filter text. diff --git a/docs/reference/index/variables/filterFn_lessThan.md b/docs/reference/index/variables/filterFn_lessThan.md index f0b22efe58..ac8151a21f 100644 --- a/docs/reference/index/variables/filterFn_lessThan.md +++ b/docs/reference/index/variables/filterFn_lessThan.md @@ -9,7 +9,7 @@ title: filterFn_lessThan const filterFn_lessThan: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:232](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L232) +Defined in: [features/column-filtering/filterFns.ts:232](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L232) Keeps rows whose value is less than the filter value. diff --git a/docs/reference/index/variables/filterFn_lessThanOrEqualTo.md b/docs/reference/index/variables/filterFn_lessThanOrEqualTo.md index ce7bea7f40..c7d8b99346 100644 --- a/docs/reference/index/variables/filterFn_lessThanOrEqualTo.md +++ b/docs/reference/index/variables/filterFn_lessThanOrEqualTo.md @@ -9,7 +9,7 @@ title: filterFn_lessThanOrEqualTo const filterFn_lessThanOrEqualTo: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:243](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L243) +Defined in: [features/column-filtering/filterFns.ts:243](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L243) Keeps rows whose value is less than or equal to the filter value. diff --git a/docs/reference/index/variables/filterFn_notEmpty.md b/docs/reference/index/variables/filterFn_notEmpty.md index 639e51c0c6..90f448b626 100644 --- a/docs/reference/index/variables/filterFn_notEmpty.md +++ b/docs/reference/index/variables/filterFn_notEmpty.md @@ -9,7 +9,7 @@ title: filterFn_notEmpty const filterFn_notEmpty: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:197](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L197) +Defined in: [features/column-filtering/filterFns.ts:197](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L197) Keeps rows whose column value is not empty. diff --git a/docs/reference/index/variables/filterFn_startsWith.md b/docs/reference/index/variables/filterFn_startsWith.md index ff02cfe315..cdbbce90cb 100644 --- a/docs/reference/index/variables/filterFn_startsWith.md +++ b/docs/reference/index/variables/filterFn_startsWith.md @@ -9,7 +9,7 @@ title: filterFn_startsWith const filterFn_startsWith: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:153](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L153) +Defined in: [features/column-filtering/filterFns.ts:153](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L153) Keeps rows whose stringified column value starts with the filter text. diff --git a/docs/reference/index/variables/filterFn_weakEquals.md b/docs/reference/index/variables/filterFn_weakEquals.md index 3b8977c0e5..416d9fb25a 100644 --- a/docs/reference/index/variables/filterFn_weakEquals.md +++ b/docs/reference/index/variables/filterFn_weakEquals.md @@ -9,7 +9,7 @@ title: filterFn_weakEquals const filterFn_weakEquals: CreatedFilterFn; ``` -Defined in: [fns/filterFns.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L88) +Defined in: [features/column-filtering/filterFns.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L88) Keeps rows whose column value is loosely equal to the filter value. diff --git a/docs/reference/index/variables/filterFns.md b/docs/reference/index/variables/filterFns.md index cddd04886d..8550274b86 100644 --- a/docs/reference/index/variables/filterFns.md +++ b/docs/reference/index/variables/filterFns.md @@ -9,7 +9,7 @@ title: filterFns const filterFns: object; ``` -Defined in: [fns/filterFns.ts:434](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L434) +Defined in: [features/column-filtering/filterFns.ts:434](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/filterFns.ts#L434) The built-in filter function registry. diff --git a/docs/reference/index/variables/reSplitAlphaNumeric.md b/docs/reference/index/variables/reSplitAlphaNumeric.md index a95b118ca3..6bd9f71473 100644 --- a/docs/reference/index/variables/reSplitAlphaNumeric.md +++ b/docs/reference/index/variables/reSplitAlphaNumeric.md @@ -9,7 +9,7 @@ title: reSplitAlphaNumeric const reSplitAlphaNumeric: RegExp; ``` -Defined in: [fns/sortFns.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L15) +Defined in: [features/row-sorting/sortFns.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L12) Regular expression used to split mixed text and numeric chunks. diff --git a/docs/reference/index/variables/sortFn_alphanumeric.md b/docs/reference/index/variables/sortFn_alphanumeric.md index ee447de220..3cab158b22 100644 --- a/docs/reference/index/variables/sortFn_alphanumeric.md +++ b/docs/reference/index/variables/sortFn_alphanumeric.md @@ -9,7 +9,7 @@ title: sortFn_alphanumeric const sortFn_alphanumeric: CreatedSortFn; ``` -Defined in: [fns/sortFns.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L78) +Defined in: [features/row-sorting/sortFns.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L75) Sorts rows with the built-in alphanumeric strategy. diff --git a/docs/reference/index/variables/sortFn_alphanumericCaseSensitive.md b/docs/reference/index/variables/sortFn_alphanumericCaseSensitive.md index fa3d518304..e2cbabdc4f 100644 --- a/docs/reference/index/variables/sortFn_alphanumericCaseSensitive.md +++ b/docs/reference/index/variables/sortFn_alphanumericCaseSensitive.md @@ -9,7 +9,7 @@ title: sortFn_alphanumericCaseSensitive const sortFn_alphanumericCaseSensitive: CreatedSortFn; ``` -Defined in: [fns/sortFns.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L88) +Defined in: [features/row-sorting/sortFns.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L85) Sorts rows with the built-in alphanumeric case sensitive strategy. diff --git a/docs/reference/index/variables/sortFn_basic.md b/docs/reference/index/variables/sortFn_basic.md index ff3b38bec6..1e39a54192 100644 --- a/docs/reference/index/variables/sortFn_basic.md +++ b/docs/reference/index/variables/sortFn_basic.md @@ -9,7 +9,7 @@ title: sortFn_basic const sortFn_basic: CreatedSortFn; ``` -Defined in: [fns/sortFns.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L136) +Defined in: [features/row-sorting/sortFns.ts:133](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L133) Sorts rows with the built-in basic strategy. diff --git a/docs/reference/index/variables/sortFn_datetime.md b/docs/reference/index/variables/sortFn_datetime.md index 3896ab2bd6..fd42fdea9a 100644 --- a/docs/reference/index/variables/sortFn_datetime.md +++ b/docs/reference/index/variables/sortFn_datetime.md @@ -9,7 +9,7 @@ title: sortFn_datetime const sortFn_datetime: CreatedSortFn; ``` -Defined in: [fns/sortFns.ts:122](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L122) +Defined in: [features/row-sorting/sortFns.ts:119](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L119) Sorts rows with the built-in datetime strategy. diff --git a/docs/reference/index/variables/sortFn_text.md b/docs/reference/index/variables/sortFn_text.md index 88fc336e67..f8eff0dc0d 100644 --- a/docs/reference/index/variables/sortFn_text.md +++ b/docs/reference/index/variables/sortFn_text.md @@ -9,7 +9,7 @@ title: sortFn_text const sortFn_text: CreatedSortFn; ``` -Defined in: [fns/sortFns.ts:100](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L100) +Defined in: [features/row-sorting/sortFns.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L97) Sorts rows with the built-in text strategy. diff --git a/docs/reference/index/variables/sortFn_textCaseSensitive.md b/docs/reference/index/variables/sortFn_textCaseSensitive.md index 193eb78e40..4c9b8197f9 100644 --- a/docs/reference/index/variables/sortFn_textCaseSensitive.md +++ b/docs/reference/index/variables/sortFn_textCaseSensitive.md @@ -9,7 +9,7 @@ title: sortFn_textCaseSensitive const sortFn_textCaseSensitive: CreatedSortFn; ``` -Defined in: [fns/sortFns.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L112) +Defined in: [features/row-sorting/sortFns.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L109) Sorts rows with the built-in text case sensitive strategy. diff --git a/docs/reference/index/variables/sortFns.md b/docs/reference/index/variables/sortFns.md index e157510a31..a6946b42bb 100644 --- a/docs/reference/index/variables/sortFns.md +++ b/docs/reference/index/variables/sortFns.md @@ -9,7 +9,7 @@ title: sortFns const sortFns: object; ``` -Defined in: [fns/sortFns.ts:360](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L360) +Defined in: [features/row-sorting/sortFns.ts:357](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/sortFns.ts#L357) The built-in sorting function registry. diff --git a/docs/reference/index/variables/stockFeatures.md b/docs/reference/index/variables/stockFeatures.md index 7cc27311ff..9ac61cfb9c 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:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L38) +Defined in: [features/stockFeatures.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L40) The complete set of stock optional table features. diff --git a/docs/reference/static-functions/functions/aggregateColumnValue.md b/docs/reference/static-functions/functions/aggregateColumnValue.md new file mode 100644 index 0000000000..f6da9a4580 --- /dev/null +++ b/docs/reference/static-functions/functions/aggregateColumnValue.md @@ -0,0 +1,48 @@ +--- +id: aggregateColumnValue +title: aggregateColumnValue +--- + +# Function: aggregateColumnValue() + +```ts +function aggregateColumnValue(args): unknown; +``` + +Defined in: [features/aggregation/aggregationFeature.utils.ts:248](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.utils.ts#L248) + +Executes every configured aggregation for a column over normalized rows. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### args + +#### childRows? + +readonly [`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +#### column + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\> + +#### groupingRow? + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +#### rows + +readonly [`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +## Returns + +`unknown` diff --git a/docs/reference/static-functions/functions/cell_getIsAggregated.md b/docs/reference/static-functions/functions/cell_getIsAggregated.md index 5ce4d61a60..809a2fabee 100644 --- a/docs/reference/static-functions/functions/cell_getIsAggregated.md +++ b/docs/reference/static-functions/functions/cell_getIsAggregated.md @@ -9,12 +9,9 @@ title: cell_getIsAggregated function cell_getIsAggregated(cell): boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:384](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L384) +Defined in: [features/aggregation/aggregationFeature.utils.ts:364](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.utils.ts#L364) -Checks whether this cell should render an aggregated value. - -Aggregated cells are non-placeholder, non-grouped cells on rows that have -subRows. +Implements `cell.getIsAggregated()` for synthetic grouped rows. ## Type Parameters @@ -39,9 +36,3 @@ subRows. ## Returns `boolean` - -## Example - -```ts -const isAggregated = cell_getIsAggregated(cell) -``` diff --git a/docs/reference/static-functions/functions/cell_getIsGrouped.md b/docs/reference/static-functions/functions/cell_getIsGrouped.md index cbeade39b0..8a1b06d859 100644 --- a/docs/reference/static-functions/functions/cell_getIsGrouped.md +++ b/docs/reference/static-functions/functions/cell_getIsGrouped.md @@ -9,7 +9,7 @@ title: cell_getIsGrouped function cell_getIsGrouped(cell): boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:343](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L343) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:243](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L243) Checks whether this cell represents the grouped column for a grouped row. diff --git a/docs/reference/static-functions/functions/cell_getIsPlaceholder.md b/docs/reference/static-functions/functions/cell_getIsPlaceholder.md index 87e8e13faf..6714bec404 100644 --- a/docs/reference/static-functions/functions/cell_getIsPlaceholder.md +++ b/docs/reference/static-functions/functions/cell_getIsPlaceholder.md @@ -9,7 +9,7 @@ title: cell_getIsPlaceholder function cell_getIsPlaceholder(cell): boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:365](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L365) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:265](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L265) Checks whether this cell is a placeholder hidden by grouping. diff --git a/docs/reference/static-functions/functions/column_clearSorting.md b/docs/reference/static-functions/functions/column_clearSorting.md index 5ba08eb8b3..f47ab88d02 100644 --- a/docs/reference/static-functions/functions/column_clearSorting.md +++ b/docs/reference/static-functions/functions/column_clearSorting.md @@ -9,7 +9,7 @@ title: column_clearSorting function column_clearSorting(column): void; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:473](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L473) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:471](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L471) Removes this column from the sorting state. diff --git a/docs/reference/static-functions/functions/column_getAggregationFn.md b/docs/reference/static-functions/functions/column_getAggregationFn.md deleted file mode 100644 index 5bfebdd205..0000000000 --- a/docs/reference/static-functions/functions/column_getAggregationFn.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -id: column_getAggregationFn -title: column_getAggregationFn ---- - -# Function: column\_getAggregationFn() - -```ts -function column_getAggregationFn(column): - | AggregationFn - | undefined; -``` - -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:202](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L202) - -Resolves the aggregation function configured for a column. - -Function-valued `columnDef.aggregationFn` is returned directly, `'auto'` -delegates to `column_getAutoAggregationFn`, and string values are looked up in -the table's aggregation function registry. - -## 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 - -### column - -[`Column_Internal`](../../index/interfaces/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> - -## Returns - - \| [`AggregationFn`](../../index/interfaces/AggregationFn.md)\<`TFeatures`, `TData`\> - \| `undefined` - -## Example - -```ts -const aggregationFn = column_getAggregationFn(column) -``` diff --git a/docs/reference/static-functions/functions/column_getAggregationFns.md b/docs/reference/static-functions/functions/column_getAggregationFns.md new file mode 100644 index 0000000000..eba6cf7164 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getAggregationFns.md @@ -0,0 +1,38 @@ +--- +id: column_getAggregationFns +title: column_getAggregationFns +--- + +# Function: column\_getAggregationFns() + +```ts +function column_getAggregationFns(column): readonly ResolvedAggregationFn[]; +``` + +Defined in: [features/aggregation/aggregationFeature.utils.ts:142](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.utils.ts#L142) + +Resolves and validates a column's scalar or multiple aggregation option. + +## 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 + +### column + +[`Column_Internal`](../../index/interfaces/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +readonly [`ResolvedAggregationFn`](../../index/interfaces/ResolvedAggregationFn.md)\<`TFeatures`, `TData`\>[] diff --git a/docs/reference/static-functions/functions/column_getAggregationValue.md b/docs/reference/static-functions/functions/column_getAggregationValue.md new file mode 100644 index 0000000000..a2b7572d97 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getAggregationValue.md @@ -0,0 +1,42 @@ +--- +id: column_getAggregationValue +title: column_getAggregationValue +--- + +# Function: column\_getAggregationValue() + +```ts +function column_getAggregationValue(column, rows?): ColumnAggregationValue; +``` + +Defined in: [features/aggregation/aggregationFeature.utils.ts:310](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.utils.ts#L310) + +Implements `column.getAggregationValue(rows?)` and its default-value cache. + +## 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 + +### column + +[`Column_Internal`](../../index/interfaces/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### rows? + +readonly [`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +## Returns + +[`ColumnAggregationValue`](../../index/type-aliases/ColumnAggregationValue.md)\<`TFeatures`\> diff --git a/docs/reference/static-functions/functions/column_getAutoAggregationFn.md b/docs/reference/static-functions/functions/column_getAutoAggregationFn.md index 64a2a77905..3da3045d12 100644 --- a/docs/reference/static-functions/functions/column_getAutoAggregationFn.md +++ b/docs/reference/static-functions/functions/column_getAutoAggregationFn.md @@ -7,21 +7,13 @@ title: column_getAutoAggregationFn ```ts function column_getAutoAggregationFn(column): - | AggregationFn + | AggregationFnDef | undefined; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:154](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L154) +Defined in: [features/aggregation/aggregationFeature.utils.ts:100](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.utils.ts#L100) -Chooses a built-in aggregation function from the first core row value. - -Numeric columns default to `sum`, date-like values default to `extent`, and -other value types leave aggregation unspecified. - -The chosen aggregation function is looked up in the table's -`aggregationFns` registry. When it is not registered there, this returns -`undefined` and warns in development instead of substituting a different -aggregation function. +Resolves the `sum` or `extent` definition inferred from the first core row. ## Type Parameters @@ -45,11 +37,5 @@ aggregation function. ## Returns - \| [`AggregationFn`](../../index/interfaces/AggregationFn.md)\<`TFeatures`, `TData`\> + \| [`AggregationFnDef`](../../index/interfaces/AggregationFnDef.md)\<`TFeatures`, `TData`, `any`, `any`\> \| `undefined` - -## Example - -```ts -const aggregationFn = column_getAutoAggregationFn(column) -``` diff --git a/docs/reference/static-functions/functions/column_getCanFilter.md b/docs/reference/static-functions/functions/column_getCanFilter.md index 1b25b345bd..23d991e679 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:137](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L137) +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) Checks whether column filtering is enabled for this accessor column. diff --git a/docs/reference/static-functions/functions/column_getCanGroup.md b/docs/reference/static-functions/functions/column_getCanGroup.md index 45aef0d958..c4ba20869b 100644 --- a/docs/reference/static-functions/functions/column_getCanGroup.md +++ b/docs/reference/static-functions/functions/column_getCanGroup.md @@ -9,7 +9,7 @@ title: column_getCanGroup function column_getCanGroup(column): boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L66) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L65) Checks whether this column can be used for grouping. diff --git a/docs/reference/static-functions/functions/column_getCanMultiSort.md b/docs/reference/static-functions/functions/column_getCanMultiSort.md index 85f7690845..537236a0b0 100644 --- a/docs/reference/static-functions/functions/column_getCanMultiSort.md +++ b/docs/reference/static-functions/functions/column_getCanMultiSort.md @@ -9,7 +9,7 @@ title: column_getCanMultiSort function column_getCanMultiSort(column): boolean; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:408](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L408) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:406](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L406) Checks whether this column can be added to a multi-sort state. diff --git a/docs/reference/static-functions/functions/column_getCanSort.md b/docs/reference/static-functions/functions/column_getCanSort.md index f1bfb62a3f..6b74553334 100644 --- a/docs/reference/static-functions/functions/column_getCanSort.md +++ b/docs/reference/static-functions/functions/column_getCanSort.md @@ -9,7 +9,7 @@ title: column_getCanSort function column_getCanSort(column): boolean; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:385](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L385) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:383](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L383) Checks whether this accessor column can participate in sorting. diff --git a/docs/reference/static-functions/functions/column_getFilterIndex.md b/docs/reference/static-functions/functions/column_getFilterIndex.md index 4404c57d91..73848d4378 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:199](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L199) +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) 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 f7bfab5636..3d2eb7fd73 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:179](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L179) +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) Reads this column's current filter value from `state.columnFilters`. diff --git a/docs/reference/static-functions/functions/column_getFirstSortDir.md b/docs/reference/static-functions/functions/column_getFirstSortDir.md index 21dd3ad871..b00c455475 100644 --- a/docs/reference/static-functions/functions/column_getFirstSortDir.md +++ b/docs/reference/static-functions/functions/column_getFirstSortDir.md @@ -9,7 +9,7 @@ title: column_getFirstSortDir function column_getFirstSortDir(column): "asc" | "desc"; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:329](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L329) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:327](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L327) Resolves the first direction used when this column begins sorting. diff --git a/docs/reference/static-functions/functions/column_getGroupedIndex.md b/docs/reference/static-functions/functions/column_getGroupedIndex.md index 228288ca75..841b7f1c25 100644 --- a/docs/reference/static-functions/functions/column_getGroupedIndex.md +++ b/docs/reference/static-functions/functions/column_getGroupedIndex.md @@ -9,7 +9,7 @@ title: column_getGroupedIndex function column_getGroupedIndex(column): number; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:107](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L107) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:106](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L106) Finds this column's position in the ordered grouping state. diff --git a/docs/reference/static-functions/functions/column_getIsFiltered.md b/docs/reference/static-functions/functions/column_getIsFiltered.md index 548203ddc5..1de53cb98a 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:161](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L161) +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) Checks whether this column currently has an entry in `state.columnFilters`. diff --git a/docs/reference/static-functions/functions/column_getIsGrouped.md b/docs/reference/static-functions/functions/column_getIsGrouped.md index f5944cef53..8227f0b72b 100644 --- a/docs/reference/static-functions/functions/column_getIsGrouped.md +++ b/docs/reference/static-functions/functions/column_getIsGrouped.md @@ -9,7 +9,7 @@ title: column_getIsGrouped function column_getIsGrouped(column): boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L89) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L88) Checks whether this column id is present in `state.grouping`. diff --git a/docs/reference/static-functions/functions/column_getIsSorted.md b/docs/reference/static-functions/functions/column_getIsSorted.md index 1d4f43894a..52f98c5829 100644 --- a/docs/reference/static-functions/functions/column_getIsSorted.md +++ b/docs/reference/static-functions/functions/column_getIsSorted.md @@ -9,7 +9,7 @@ title: column_getIsSorted function column_getIsSorted(column): false | SortDirection; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:431](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L431) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:429](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L429) Reads this column's current sort direction. diff --git a/docs/reference/static-functions/functions/column_getNextSortingOrder.md b/docs/reference/static-functions/functions/column_getNextSortingOrder.md index 5e483e0a76..85be91cfad 100644 --- a/docs/reference/static-functions/functions/column_getNextSortingOrder.md +++ b/docs/reference/static-functions/functions/column_getNextSortingOrder.md @@ -9,7 +9,7 @@ title: column_getNextSortingOrder function column_getNextSortingOrder(column, multi?): false | "asc" | "desc"; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:352](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L352) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:350](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L350) Resolves the next sort order for this column's toggle cycle. diff --git a/docs/reference/static-functions/functions/column_getSortIndex.md b/docs/reference/static-functions/functions/column_getSortIndex.md index 661803c5b4..0470db21d7 100644 --- a/docs/reference/static-functions/functions/column_getSortIndex.md +++ b/docs/reference/static-functions/functions/column_getSortIndex.md @@ -9,7 +9,7 @@ title: column_getSortIndex function column_getSortIndex(column): number; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:452](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L452) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:450](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L450) Finds this column's position in the ordered `state.sorting` array. diff --git a/docs/reference/static-functions/functions/column_getToggleGroupingHandler.md b/docs/reference/static-functions/functions/column_getToggleGroupingHandler.md index 400af346d2..cd110d82ff 100644 --- a/docs/reference/static-functions/functions/column_getToggleGroupingHandler.md +++ b/docs/reference/static-functions/functions/column_getToggleGroupingHandler.md @@ -9,7 +9,7 @@ title: column_getToggleGroupingHandler function column_getToggleGroupingHandler(column): () => void; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:125](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L125) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:124](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L124) Creates a header/control handler that toggles grouping for this column. diff --git a/docs/reference/static-functions/functions/column_getToggleSortingHandler.md b/docs/reference/static-functions/functions/column_getToggleSortingHandler.md index 10b91fdee1..97e9007798 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:496](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L496) +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) 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 90f3573bb5..b9f04f7212 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:222](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L222) +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) Adds, updates, or removes this column's filter value. diff --git a/docs/reference/static-functions/functions/column_toggleGrouping.md b/docs/reference/static-functions/functions/column_toggleGrouping.md index 2bada6ef23..af3f0968d3 100644 --- a/docs/reference/static-functions/functions/column_toggleGrouping.md +++ b/docs/reference/static-functions/functions/column_toggleGrouping.md @@ -9,7 +9,7 @@ title: column_toggleGrouping function column_toggleGrouping(column): void; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L40) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L39) Adds or removes this column id from the grouping state. diff --git a/docs/reference/static-functions/functions/column_toggleSorting.md b/docs/reference/static-functions/functions/column_toggleSorting.md index 997443fb6d..213acbf530 100644 --- a/docs/reference/static-functions/functions/column_toggleSorting.md +++ b/docs/reference/static-functions/functions/column_toggleSorting.md @@ -12,7 +12,7 @@ function column_toggleSorting( multi?): void; ``` -Defined in: [features/row-sorting/rowSortingFeature.utils.ts:223](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L223) +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:221](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L221) Applies the next sorting state for this column. diff --git a/docs/reference/static-functions/functions/formatAggregatedCellValue.md b/docs/reference/static-functions/functions/formatAggregatedCellValue.md new file mode 100644 index 0000000000..a7dd338d93 --- /dev/null +++ b/docs/reference/static-functions/functions/formatAggregatedCellValue.md @@ -0,0 +1,28 @@ +--- +id: formatAggregatedCellValue +title: formatAggregatedCellValue +--- + +# Function: formatAggregatedCellValue() + +```ts +function formatAggregatedCellValue(value, option): string | null; +``` + +Defined in: [features/aggregation/aggregationFeature.utils.ts:385](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.utils.ts#L385) + +Formats the default scalar or keyed aggregated-cell representation. + +## Parameters + +### value + +`unknown` + +### option + +`unknown` + +## Returns + +`string` \| `null` diff --git a/docs/reference/static-functions/functions/getDefaultGroupingState.md b/docs/reference/static-functions/functions/getDefaultGroupingState.md index e318b9a7be..1657a95244 100644 --- a/docs/reference/static-functions/functions/getDefaultGroupingState.md +++ b/docs/reference/static-functions/functions/getDefaultGroupingState.md @@ -9,7 +9,7 @@ title: getDefaultGroupingState function getDefaultGroupingState(): GroupingState; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L25) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L24) Creates the default grouping state. diff --git a/docs/reference/static-functions/functions/normalizeAggregationRows.md b/docs/reference/static-functions/functions/normalizeAggregationRows.md new file mode 100644 index 0000000000..90a3933284 --- /dev/null +++ b/docs/reference/static-functions/functions/normalizeAggregationRows.md @@ -0,0 +1,35 @@ +--- +id: normalizeAggregationRows +title: normalizeAggregationRows +--- + +# Function: normalizeAggregationRows() + +```ts +function normalizeAggregationRows(rows): Row[]; +``` + +Defined in: [features/aggregation/aggregationFeature.utils.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/aggregation/aggregationFeature.utils.ts#L57) + +Flattens hierarchical row inputs to unique terminal leaves in encounter +order. This is the normalization used by public aggregation-value calls. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### rows + +readonly [`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +## Returns + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] diff --git a/docs/reference/static-functions/functions/row_getGroupingValue.md b/docs/reference/static-functions/functions/row_getGroupingValue.md index bc24be8e7a..33b1720b56 100644 --- a/docs/reference/static-functions/functions/row_getGroupingValue.md +++ b/docs/reference/static-functions/functions/row_getGroupingValue.md @@ -9,7 +9,7 @@ title: row_getGroupingValue function row_getGroupingValue(row, columnId): any; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:305](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L305) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:205](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L205) Reads and caches this row's grouping value for a column. diff --git a/docs/reference/static-functions/functions/row_getIsGrouped.md b/docs/reference/static-functions/functions/row_getIsGrouped.md index 773d0447ee..963c2dc60a 100644 --- a/docs/reference/static-functions/functions/row_getIsGrouped.md +++ b/docs/reference/static-functions/functions/row_getIsGrouped.md @@ -9,7 +9,7 @@ title: row_getIsGrouped function row_getIsGrouped(row): boolean; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:287](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L287) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:187](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L187) Checks whether this row was created as a grouped row. diff --git a/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md b/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md index 52ccfef782..1571741b92 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:331](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L331) +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) Returns whether a filter value should be removed from filter state. diff --git a/docs/reference/static-functions/functions/table_getGroupedRowModel.md b/docs/reference/static-functions/functions/table_getGroupedRowModel.md index bb4fa57c5e..ebeffb9426 100644 --- a/docs/reference/static-functions/functions/table_getGroupedRowModel.md +++ b/docs/reference/static-functions/functions/table_getGroupedRowModel.md @@ -11,7 +11,7 @@ function table_getGroupedRowModel(table): RowModel(table, defaultState?): void; ``` -Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:311](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L311) +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) Resets `columnFilters` to the configured initial state or feature default. diff --git a/docs/reference/static-functions/functions/table_resetGlobalFilter.md b/docs/reference/static-functions/functions/table_resetGlobalFilter.md index 1e371ebf2f..5f68207659 100644 --- a/docs/reference/static-functions/functions/table_resetGlobalFilter.md +++ b/docs/reference/static-functions/functions/table_resetGlobalFilter.md @@ -9,7 +9,7 @@ title: table_resetGlobalFilter function table_resetGlobalFilter(table, defaultState?): void; ``` -Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:122](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L122) +Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:118](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L118) Resets `globalFilter` to the configured initial state or feature default. diff --git a/docs/reference/static-functions/functions/table_resetGrouping.md b/docs/reference/static-functions/functions/table_resetGrouping.md index 025f95e395..6f4cbc9445 100644 --- a/docs/reference/static-functions/functions/table_resetGrouping.md +++ b/docs/reference/static-functions/functions/table_resetGrouping.md @@ -9,7 +9,7 @@ title: table_resetGrouping function table_resetGrouping(table, defaultState?): void; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:267](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L267) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:167](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L167) Resets `grouping` to the configured initial state or feature default. diff --git a/docs/reference/static-functions/functions/table_setColumnFilters.md b/docs/reference/static-functions/functions/table_setColumnFilters.md index abcc4415e7..51bacf4bdd 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:271](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L271) +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) Routes a column filter updater through the table's filter change handler. diff --git a/docs/reference/static-functions/functions/table_setGlobalFilter.md b/docs/reference/static-functions/functions/table_setGlobalFilter.md index d645e0803f..eeabba4044 100644 --- a/docs/reference/static-functions/functions/table_setGlobalFilter.md +++ b/docs/reference/static-functions/functions/table_setGlobalFilter.md @@ -9,7 +9,7 @@ title: table_setGlobalFilter function table_setGlobalFilter(table, updater): void; ``` -Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:103](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L103) +Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L99) Routes a global filter updater through the table's global filter handler. diff --git a/docs/reference/static-functions/functions/table_setGrouping.md b/docs/reference/static-functions/functions/table_setGrouping.md index c549c6bf06..f2ca98f4e3 100644 --- a/docs/reference/static-functions/functions/table_setGrouping.md +++ b/docs/reference/static-functions/functions/table_setGrouping.md @@ -9,7 +9,7 @@ title: table_setGrouping function table_setGrouping(table, updater): void; ``` -Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:248](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L248) +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:148](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L148) Routes a grouping updater through the table's grouping change handler. diff --git a/docs/reference/static-functions/index.md b/docs/reference/static-functions/index.md index 1e14141c26..6dc6bb20fc 100644 --- a/docs/reference/static-functions/index.md +++ b/docs/reference/static-functions/index.md @@ -7,6 +7,7 @@ title: static-functions ## Functions +- [aggregateColumnValue](functions/aggregateColumnValue.md) - [cell\_getContext](functions/cell_getContext.md) - [cell\_getIsAggregated](functions/cell_getIsAggregated.md) - [cell\_getIsGrouped](functions/cell_getIsGrouped.md) @@ -15,7 +16,8 @@ title: static-functions - [cell\_renderValue](functions/cell_renderValue.md) - [column\_clearSorting](functions/column_clearSorting.md) - [column\_getAfter](functions/column_getAfter.md) -- [column\_getAggregationFn](functions/column_getAggregationFn.md) +- [column\_getAggregationFns](functions/column_getAggregationFns.md) +- [column\_getAggregationValue](functions/column_getAggregationValue.md) - [column\_getAutoAggregationFn](functions/column_getAutoAggregationFn.md) - [column\_getAutoFilterFn](functions/column_getAutoFilterFn.md) - [column\_getAutoSortDir](functions/column_getAutoSortDir.md) @@ -62,6 +64,7 @@ title: static-functions - [column\_toggleGrouping](functions/column_toggleGrouping.md) - [column\_toggleSorting](functions/column_toggleSorting.md) - [column\_toggleVisibility](functions/column_toggleVisibility.md) +- [formatAggregatedCellValue](functions/formatAggregatedCellValue.md) - [getDefaultColumnFiltersState](functions/getDefaultColumnFiltersState.md) - [getDefaultColumnOrderState](functions/getDefaultColumnOrderState.md) - [getDefaultColumnPinningState](functions/getDefaultColumnPinningState.md) @@ -83,6 +86,7 @@ title: static-functions - [isRowSelected](functions/isRowSelected.md) - [isSubRowSelected](functions/isSubRowSelected.md) - [isTouchStartEvent](functions/isTouchStartEvent.md) +- [normalizeAggregationRows](functions/normalizeAggregationRows.md) - [orderColumns](functions/orderColumns.md) - [passiveEventSupported](functions/passiveEventSupported.md) - [row\_getAllCells](functions/row_getAllCells.md) diff --git a/examples/alpine/grouping/src/main.ts b/examples/alpine/grouping/src/main.ts index 144d3d5d73..666af13e91 100644 --- a/examples/alpine/grouping/src/main.ts +++ b/examples/alpine/grouping/src/main.ts @@ -1,6 +1,7 @@ import Alpine from 'alpinejs' import { FlexRender, + aggregationFeature, aggregationFn_mean, aggregationFn_median, aggregationFn_sum, @@ -28,6 +29,7 @@ import type { ColumnDef } from '@tanstack/alpine-table' import type { Person } from './makeData' const features = tableFeatures({ + aggregationFeature, columnFilteringFeature, columnGroupingFeature, rowExpandingFeature, diff --git a/examples/angular/grouping/src/app/columns.ts b/examples/angular/grouping/src/app/columns.ts index 16f2dd79ba..66417d5dfa 100644 --- a/examples/angular/grouping/src/app/columns.ts +++ b/examples/angular/grouping/src/app/columns.ts @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_median, aggregationFn_sum, @@ -16,6 +17,7 @@ import { import type { Person } from './makeData' const features = tableFeatures({ + aggregationFeature, columnGroupingFeature, rowPaginationFeature, columnFilteringFeature, diff --git a/examples/lit/grouping/src/main.ts b/examples/lit/grouping/src/main.ts index 6ef9296b8b..c2a87bf827 100644 --- a/examples/lit/grouping/src/main.ts +++ b/examples/lit/grouping/src/main.ts @@ -4,6 +4,7 @@ import { repeat } from 'lit/directives/repeat.js' import { FlexRender, TableController, + aggregationFeature, aggregationFn_mean, aggregationFn_median, aggregationFn_sum, @@ -29,6 +30,7 @@ import type { ColumnDef } from '@tanstack/lit-table' import type { Person } from './makeData' const features = tableFeatures({ + aggregationFeature, columnFilteringFeature, columnGroupingFeature, rowExpandingFeature, diff --git a/examples/preact/grouping/src/main.tsx b/examples/preact/grouping/src/main.tsx index d9ae8599b8..691237b6a9 100644 --- a/examples/preact/grouping/src/main.tsx +++ b/examples/preact/grouping/src/main.tsx @@ -2,6 +2,7 @@ import { useMemo, useState } from 'preact/hooks' import { render } from 'preact' import './index.css' import { + aggregationFeature, aggregationFn_mean, aggregationFn_median, aggregationFn_sum, @@ -27,6 +28,7 @@ import type { Person } from './makeData' // this example happens to use the createTableHook pattern, but it is not required const { useAppTable, createAppColumnHelper } = createTableHook({ features: { + aggregationFeature, columnFilteringFeature, columnGroupingFeature, rowExpandingFeature, diff --git a/examples/react/aggregation/index.html b/examples/react/aggregation/index.html new file mode 100644 index 0000000000..f05dde65e3 --- /dev/null +++ b/examples/react/aggregation/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Table Aggregation + + +
+ + + diff --git a/examples/react/aggregation/package.json b/examples/react/aggregation/package.json new file mode 100644 index 0000000000..0c132a61e2 --- /dev/null +++ b/examples/react/aggregation/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-react-table-example-aggregation", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.5.0", + "@tanstack/react-table": "^9.0.0-beta.47", + "react": "^19.2.7", + "react-dom": "^19.2.7" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/react/aggregation/src/index.css b/examples/react/aggregation/src/index.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/react/aggregation/src/index.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/react/aggregation/src/main.tsx b/examples/react/aggregation/src/main.tsx new file mode 100644 index 0000000000..20966700d9 --- /dev/null +++ b/examples/react/aggregation/src/main.tsx @@ -0,0 +1,308 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import { + aggregationFeature, + aggregationFn_count, + aggregationFn_extent, + aggregationFn_mean, + aggregationFn_sum, + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFn_includesString, + metaHelper, + rowPaginationFeature, + rowSelectionFeature, + useTable, +} from '@tanstack/react-table' +import { makeData } from './makeData' +import type { Table } from '@tanstack/react-table' +import type { Sale } from './makeData' + +type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected' + +type AggregationTableMeta = { + rowSource: RowSource +} + +const features = { + aggregationFeature, + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + filteredRowModel: createFilteredRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filterFns: { includesString: filterFn_includesString }, + aggregationFns: { + count: aggregationFn_count, + extent: aggregationFn_extent, + mean: aggregationFn_mean, + sum: aggregationFn_sum, + }, + tableMeta: metaHelper(), +} + +const columnHelper = createColumnHelper() + +// demo purposes - optionally pass in custom rows to aggregate +function getAggregationRows(table: Table) { + const rowSource = table.options.meta?.rowSource + if (rowSource === 'all') return table.getCoreRowModel().rows + if (rowSource === 'page') return table.getRowModel().rows + if (rowSource === 'selected') { + return table.getFilteredSelectedRowModel().rows + } + if (rowSource === 'custom') { + return table.getCoreRowModel().rows.slice(0, 3) + } + return undefined +} + +function formatValue(value: unknown): string { + if (Array.isArray(value)) return value.map(formatValue).join(' โ€“ ') + if (value && typeof value === 'object') { + return Object.entries(value) + .map(([key, entry]) => `${key}: ${formatValue(entry)}`) + .join(', ') + } + if (typeof value === 'number') + return value.toLocaleString(undefined, { maximumFractionDigits: 2 }) + return String(value ?? 'โ€”') +} + +function App() { + const [data, setData] = React.useState(() => makeData(10_000)) + const [rowSource, setRowSource] = React.useState('filtered') + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }), + columnHelper.accessor('category', { + header: 'Category', + filterFn: 'includesString', + }), + columnHelper.accessor('item', { + header: 'Item', + footer: ({ table }) => `${table.options.meta?.rowSource} total`, + }), + columnHelper.accessor('amount', { + header: 'Amount', + aggregationFn: 'sum', + cell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column, table }) => + // formatValue(column.getAggregationValue()), // default - accounts for filtering + formatValue(column.getAggregationValue(getAggregationRows(table))), // or pass in whatever array of rows you want to aggregate + }), + columnHelper.accessor('score', { + header: 'Score', + aggregationFn: [ + 'count', + 'mean', + { id: 'range', aggregationFn: 'extent' }, + ], + footer: ({ column, table }) => + // formatValue(column.getAggregationValue()), // default - accounts for filtering + formatValue(column.getAggregationValue(getAggregationRows(table))), // or pass in whatever array of rows you want to aggregate + }), + ]), + [], + ) + + const table = useTable( + { + features, + columns, + data, + meta: { rowSource }, + initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, + debugTable: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + return ( +
+

Aggregation without grouping

+

+ Amount uses a scalar sum. Score runs count, mean, and range + together and returns a keyed object. +

+
+ + +
+
+
+ + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = event.target.value + ? Number(event.target.value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/aggregation/src/makeData.ts b/examples/react/aggregation/src/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/react/aggregation/src/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/react/aggregation/src/vite-env.d.ts b/examples/react/aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/aggregation/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/aggregation/tsconfig.json b/examples/react/aggregation/tsconfig.json new file mode 100644 index 0000000000..585eb877a8 --- /dev/null +++ b/examples/react/aggregation/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/aggregation/vite.config.js b/examples/react/aggregation/vite.config.js new file mode 100644 index 0000000000..bf221a8f6c --- /dev/null +++ b/examples/react/aggregation/vite.config.js @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +export default defineConfig({ + server: { port: 6565 }, + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/grouped-aggregation/index.html b/examples/react/grouped-aggregation/index.html new file mode 100644 index 0000000000..5b156c5ebb --- /dev/null +++ b/examples/react/grouped-aggregation/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Table Grouped Aggregation + + +
+ + + diff --git a/examples/react/grouped-aggregation/package.json b/examples/react/grouped-aggregation/package.json new file mode 100644 index 0000000000..24fa4092ed --- /dev/null +++ b/examples/react/grouped-aggregation/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-react-table-example-grouped-aggregation", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.5.0", + "@tanstack/react-table": "^9.0.0-beta.47", + "react": "^19.2.7", + "react-dom": "^19.2.7" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/react/grouped-aggregation/src/index.css b/examples/react/grouped-aggregation/src/index.css new file mode 100644 index 0000000000..e93cfc85f6 --- /dev/null +++ b/examples/react/grouped-aggregation/src/index.css @@ -0,0 +1,67 @@ +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-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button { + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.page-size-input { + width: 4rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} diff --git a/examples/react/grouped-aggregation/src/main.tsx b/examples/react/grouped-aggregation/src/main.tsx new file mode 100644 index 0000000000..bfc5837d6d --- /dev/null +++ b/examples/react/grouped-aggregation/src/main.tsx @@ -0,0 +1,313 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import { + aggregationFeature, + aggregationFn_mean, + aggregationFn_median, + aggregationFn_sum, + columnFilteringFeature, + columnGroupingFeature, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFn_inNumberRange, + filterFn_includesString, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_text, +} from '@tanstack/react-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// this example happens to use the createTableHook pattern, but it is not required +const { useAppTable, createAppColumnHelper } = createTableHook({ + features: { + aggregationFeature, + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(), + groupedRowModel: createGroupedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(), + filterFns: { + includesString: filterFn_includesString, + inNumberRange: filterFn_inNumberRange, + }, + sortFns: { + alphanumeric: sortFn_alphanumeric, + text: sortFn_text, + }, + aggregationFns: { + mean: aggregationFn_mean, + median: aggregationFn_median, + sum: aggregationFn_sum, + }, + }, +}) + +const columnHelper = createAppColumnHelper() + +function App() { + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + footer: 'Grand Total', + cell: (info) => info.getValue(), + /** + * override the value used for row grouping + * (otherwise, defaults to the value derived from accessorKey / accessorFn) + */ + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? null : Math.round(value * 100) / 100 + }, + }), + columnHelper.accessor('visits', { + header: () => Visits, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column }) => + column.getAggregationValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined + ? null + : `${Math.round(value * 100) / 100}%` + }, + }), + ]), + [], + ) + + const [data, setData] = React.useState(() => makeData(10_000)) + const refreshData = () => setData(makeData(10_000)) + const stressTest = () => setData(makeData(200_000)) + + const table = useAppTable( + { + columns, + data, + // initialState: { grouping: ['status'] }, // group by a column on first render + // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom + // state: { grouping }, // classic controlled state; pair with onGroupingChange + // onGroupingChange: setGrouping, + // enableGrouping: false, // disable grouping for every column; default true + // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' + // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + debugTable: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ {header.column.getCanGroup() ? ( + // If the header can be grouped, let's add a toggle + + ) : null}{' '} + +
+ )} +
+ {cell.getIsGrouped() ? ( + // If it's a grouped cell, add an expander and row count + <> + + + ) : cell.getIsAggregated() ? ( + // If the cell is aggregated, use the Aggregated + // renderer for cell + + ) : cell.getIsPlaceholder() ? null : ( // For cells with repeated values, render null + // Otherwise, just render the regular cell + + )} +
+ {header.isPlaceholder ? null : ( + + )} +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/grouped-aggregation/src/makeData.ts b/examples/react/grouped-aggregation/src/makeData.ts new file mode 100644 index 0000000000..95302cdf16 --- /dev/null +++ b/examples/react/grouped-aggregation/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + 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 { + 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], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/grouped-aggregation/src/vite-env.d.ts b/examples/react/grouped-aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/grouped-aggregation/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/grouped-aggregation/tsconfig.json b/examples/react/grouped-aggregation/tsconfig.json new file mode 100644 index 0000000000..585eb877a8 --- /dev/null +++ b/examples/react/grouped-aggregation/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/grouped-aggregation/vite.config.js b/examples/react/grouped-aggregation/vite.config.js new file mode 100644 index 0000000000..bf221a8f6c --- /dev/null +++ b/examples/react/grouped-aggregation/vite.config.js @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +export default defineConfig({ + server: { port: 6565 }, + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/grouping/src/main.tsx b/examples/react/grouping/src/main.tsx index d42515016d..c78d2096b2 100644 --- a/examples/react/grouping/src/main.tsx +++ b/examples/react/grouping/src/main.tsx @@ -2,9 +2,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' import './index.css' import { - aggregationFn_mean, - aggregationFn_median, - aggregationFn_sum, columnFilteringFeature, columnGroupingFeature, createExpandedRowModel, @@ -45,11 +42,6 @@ const { useAppTable, createAppColumnHelper } = createTableHook({ alphanumeric: sortFn_alphanumeric, text: sortFn_text, }, - aggregationFns: { - mean: aggregationFn_mean, - median: aggregationFn_median, - sum: aggregationFn_sum, - }, }, }) @@ -75,14 +67,9 @@ function App() { }), columnHelper.accessor('age', { header: () => 'Age', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, - aggregationFn: 'median', }), columnHelper.accessor('visits', { header: () => Visits, - aggregationFn: 'sum', - aggregatedCell: ({ getValue }) => getValue().toLocaleString(), }), columnHelper.accessor('status', { header: 'Status', @@ -91,9 +78,6 @@ function App() { header: 'Profile Progress', cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', - aggregationFn: 'mean', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100 + '%', }), ]), [], @@ -166,11 +150,9 @@ function App() { style={{ background: cell.getIsGrouped() ? '#0aff0082' - : cell.getIsAggregated() - ? '#ffa50078' - : cell.getIsPlaceholder() - ? '#ff000042' - : 'white', + : cell.getIsPlaceholder() + ? '#ff000042' + : 'white', }} > {cell.getIsGrouped() ? ( @@ -187,12 +169,7 @@ function App() { {row.subRows.length.toLocaleString()}) - ) : cell.getIsAggregated() ? ( - // If the cell is aggregated, use the Aggregated - // renderer for cell - - ) : cell.getIsPlaceholder() ? null : ( // For cells with repeated values, render null - // Otherwise, just render the regular cell + ) : cell.getIsPlaceholder() ? null : row.getIsGrouped() ? null : ( // For cells with repeated values, render null )} diff --git a/examples/react/kitchen-sink-chakra-ui/src/hooks/features.ts b/examples/react/kitchen-sink-chakra-ui/src/hooks/features.ts index 8d94b9fbad..ffb5e86be1 100644 --- a/examples/react/kitchen-sink-chakra-ui/src/hooks/features.ts +++ b/examples/react/kitchen-sink-chakra-ui/src/hooks/features.ts @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_min, columnFacetingFeature, @@ -47,6 +48,7 @@ const fuzzyFilterFn = ( } export const features = tableFeatures({ + aggregationFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, diff --git a/examples/react/kitchen-sink-hero-ui/src/hooks/features.ts b/examples/react/kitchen-sink-hero-ui/src/hooks/features.ts index 8d94b9fbad..ffb5e86be1 100644 --- a/examples/react/kitchen-sink-hero-ui/src/hooks/features.ts +++ b/examples/react/kitchen-sink-hero-ui/src/hooks/features.ts @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_min, columnFacetingFeature, @@ -47,6 +48,7 @@ const fuzzyFilterFn = ( } export const features = tableFeatures({ + aggregationFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, diff --git a/examples/react/kitchen-sink-mantine/src/hooks/features.ts b/examples/react/kitchen-sink-mantine/src/hooks/features.ts index 8d94b9fbad..ffb5e86be1 100644 --- a/examples/react/kitchen-sink-mantine/src/hooks/features.ts +++ b/examples/react/kitchen-sink-mantine/src/hooks/features.ts @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_min, columnFacetingFeature, @@ -47,6 +48,7 @@ const fuzzyFilterFn = ( } export const features = tableFeatures({ + aggregationFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, diff --git a/examples/react/kitchen-sink-material-ui/src/hooks/features.ts b/examples/react/kitchen-sink-material-ui/src/hooks/features.ts index 8d94b9fbad..ffb5e86be1 100644 --- a/examples/react/kitchen-sink-material-ui/src/hooks/features.ts +++ b/examples/react/kitchen-sink-material-ui/src/hooks/features.ts @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_min, columnFacetingFeature, @@ -47,6 +48,7 @@ const fuzzyFilterFn = ( } export const features = tableFeatures({ + aggregationFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, diff --git a/examples/react/kitchen-sink-react-aria/src/hooks/features.ts b/examples/react/kitchen-sink-react-aria/src/hooks/features.ts index 8d94b9fbad..ffb5e86be1 100644 --- a/examples/react/kitchen-sink-react-aria/src/hooks/features.ts +++ b/examples/react/kitchen-sink-react-aria/src/hooks/features.ts @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_min, columnFacetingFeature, @@ -47,6 +48,7 @@ const fuzzyFilterFn = ( } export const features = tableFeatures({ + aggregationFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, diff --git a/examples/react/kitchen-sink-shadcn-base/src/hooks/features.ts b/examples/react/kitchen-sink-shadcn-base/src/hooks/features.ts index 8d94b9fbad..ffb5e86be1 100644 --- a/examples/react/kitchen-sink-shadcn-base/src/hooks/features.ts +++ b/examples/react/kitchen-sink-shadcn-base/src/hooks/features.ts @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_min, columnFacetingFeature, @@ -47,6 +48,7 @@ const fuzzyFilterFn = ( } export const features = tableFeatures({ + aggregationFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, diff --git a/examples/react/kitchen-sink-shadcn-radix/src/hooks/features.ts b/examples/react/kitchen-sink-shadcn-radix/src/hooks/features.ts index 8d94b9fbad..ffb5e86be1 100644 --- a/examples/react/kitchen-sink-shadcn-radix/src/hooks/features.ts +++ b/examples/react/kitchen-sink-shadcn-radix/src/hooks/features.ts @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_min, columnFacetingFeature, @@ -47,6 +48,7 @@ const fuzzyFilterFn = ( } export const features = tableFeatures({ + aggregationFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, diff --git a/examples/react/mantine-react-table/src/main.tsx b/examples/react/mantine-react-table/src/main.tsx index f60655f327..fa87361378 100644 --- a/examples/react/mantine-react-table/src/main.tsx +++ b/examples/react/mantine-react-table/src/main.tsx @@ -71,17 +71,21 @@ const columns = columnHelper.columns([ currency: 'USD', maximumFractionDigits: 0, }), - aggregationFn: 'mean', - AggregatedCell: ({ cell }) => ( - - Avg:{' '} - {cell.getValue().toLocaleString('en-US', { + aggregationFn: ['mean', 'min'], + AggregatedCell: ({ cell }) => { + const value = cell.getValue<{ mean: number; min: number }>() + const format = (amount: number) => + amount.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0, - })} - - ), + }) + return ( + + Avg: {format(value.mean)} ยท Min: {format(value.min)} + + ) + }, }), columnHelper.accessor('status', { header: 'Status', diff --git a/examples/react/mantine-react-table/src/mantine-react-table/types.ts b/examples/react/mantine-react-table/src/mantine-react-table/types.ts index 568d3f9105..86a909620e 100644 --- a/examples/react/mantine-react-table/src/mantine-react-table/types.ts +++ b/examples/react/mantine-react-table/src/mantine-react-table/types.ts @@ -9,7 +9,7 @@ import type { import type { AccessorFn, - AggregationFn, + AggregationFnDef, Cell, Column, ColumnDef, @@ -470,7 +470,12 @@ export type MRT_ColumnDef = { row: MRT_Row table: MRT_TableInstance }) => ReactNode - aggregationFn?: Array> | MRT_AggregationFn + aggregationFn?: + | Array< + | MRT_AggregationOption + | { aggregationFn: MRT_AggregationFn; id: string } + > + | MRT_AggregationFn Cell?: (props: { cell: MRT_Cell column: MRT_Column @@ -808,7 +813,7 @@ export type MRT_Cell = { export type MRT_AggregationOption = keyof typeof MRT_AggregationFns & string export type MRT_AggregationFn = - | AggregationFn + | AggregationFnDef | MRT_AggregationOption export type MRT_SortingOption = LiteralUnion @@ -859,9 +864,12 @@ export type MRT_TableOptions = { /** * Custom aggregation functions to apply to the table. These get merged with * MRT's built-ins (`mean`, `min`, `max`, etc.) and passed into - * `createGroupedRowModel(...)`. + * the aggregation feature registry. */ - aggregationFns?: Record> + aggregationFns?: Record< + string, + AggregationFnDef + > /** * Custom filter functions to apply to the table. These get merged with MRT's * built-ins (`fuzzy`, `contains`, `between`, etc.) and passed into diff --git a/examples/react/mantine-react-table/src/mantine-react-table/utils/column.utils.ts b/examples/react/mantine-react-table/src/mantine-react-table/utils/column.utils.ts index 69f666f48b..40cd10964d 100644 --- a/examples/react/mantine-react-table/src/mantine-react-table/utils/column.utils.ts +++ b/examples/react/mantine-react-table/src/mantine-react-table/utils/column.utils.ts @@ -1,5 +1,3 @@ -import type { Row, StockFeatures } from '@tanstack/react-table' - import type { MRT_Column, MRT_ColumnDef, @@ -40,7 +38,6 @@ export const prepareColumns = ({ tableOptions: MRT_DefinedTableOptions }): Array> => { const { - aggregationFns = {}, defaultDisplayColumn, filterFns = {}, sortFns = {}, @@ -59,19 +56,6 @@ export const prepareColumns = ({ tableOptions, }) } else if (columnDef.columnDefType === 'data') { - // assign aggregationFns if multiple aggregationFns are provided - if (Array.isArray(columnDef.aggregationFn)) { - const aggFns = columnDef.aggregationFn as Array - columnDef.aggregationFn = ( - columnId: string, - leafRows: Array>, - childRows: Array>, - ) => - aggFns.map((fn) => - aggregationFns[fn]?.(columnId, leafRows, childRows), - ) - } - // assign filterFns if (Object.keys(filterFns).includes(columnFilterFns[columnDef.id])) { columnDef.filterFn = diff --git a/examples/react/material-react-table/src/main.tsx b/examples/react/material-react-table/src/main.tsx index e4a0a2e2af..869f82f228 100644 --- a/examples/react/material-react-table/src/main.tsx +++ b/examples/react/material-react-table/src/main.tsx @@ -67,17 +67,21 @@ const columns = columnHelper.columns([ currency: 'USD', maximumFractionDigits: 0, }), - aggregationFn: 'mean', - AggregatedCell: ({ cell }) => ( - - Avg:{' '} - {cell.getValue().toLocaleString('en-US', { + aggregationFn: ['mean', 'min'], + AggregatedCell: ({ cell }) => { + const value = cell.getValue<{ mean: number; min: number }>() + const format = (amount: number) => + amount.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0, - })} - - ), + }) + return ( + + Avg: {format(value.mean)} ยท Min: {format(value.min)} + + ) + }, }), columnHelper.accessor('status', { header: 'Status', diff --git a/examples/react/material-react-table/src/material-react-table/types.ts b/examples/react/material-react-table/src/material-react-table/types.ts index 2ec73eb84e..91f6495fa0 100644 --- a/examples/react/material-react-table/src/material-react-table/types.ts +++ b/examples/react/material-react-table/src/material-react-table/types.ts @@ -1,6 +1,6 @@ import type { AccessorFn, - AggregationFn, + AggregationFnDef, Cell, Column, ColumnDef, @@ -388,7 +388,7 @@ export type MRT_Cell = Cell< export type MRT_AggregationOption = string & keyof typeof MRT_AggregationFns export type MRT_AggregationFn = - | AggregationFn + | AggregationFnDef | MRT_AggregationOption export type MRT_SortingOption = LiteralUnion diff --git a/examples/react/material-react-table/src/material-react-table/utils/column.utils.ts b/examples/react/material-react-table/src/material-react-table/utils/column.utils.ts index eb75aa9a82..40358d811c 100644 --- a/examples/react/material-react-table/src/material-react-table/utils/column.utils.ts +++ b/examples/react/material-react-table/src/material-react-table/utils/column.utils.ts @@ -1,5 +1,4 @@ import { useMemo } from 'react' -import type { Row, StockFeatures } from '@tanstack/react-table' import type { DropdownOption, MRT_Column, @@ -50,11 +49,10 @@ export const prepareColumns = ({ columnDefs: Array> tableOptions: MRT_DefinedTableOptions }): Array> => { - // `aggregationFns` / `filterFns` / `sortFns` are carried on the options object + // `filterFns` / `sortFns` are carried on the options object // at runtime (client-supplied custom fns), but aren't declared as core options // (they're feature-slot registries), so read them off a loose view. const { - aggregationFns = {}, defaultDisplayColumn, filterFns = {}, sortFns = {}, @@ -74,19 +72,6 @@ export const prepareColumns = ({ tableOptions, }) } else if (def.columnDefType === 'data') { - // assign aggregationFns if multiple aggregationFns are provided - if (Array.isArray(def.aggregationFn)) { - const aggFns = def.aggregationFn as Array - def.aggregationFn = ( - columnId: string, - leafRows: Array>, - childRows: Array>, - ) => - aggFns.map((fn) => - aggregationFns[fn]?.(columnId, leafRows, childRows), - ) - } - // assign filterFns if (Object.keys(filterFns).includes(columnFilterFns[def.id])) { def.filterFn = filterFns[columnFilterFns[def.id]] ?? filterFns.fuzzy diff --git a/examples/react/web-worker-row-models/src/routes/index.tsx b/examples/react/web-worker-row-models/src/routes/index.tsx index d20cf34eaf..172efc23f2 100644 --- a/examples/react/web-worker-row-models/src/routes/index.tsx +++ b/examples/react/web-worker-row-models/src/routes/index.tsx @@ -109,6 +109,10 @@ function JankMeter() { const stallRef = React.useRef(null) React.useEffect(() => { + if (dotRef.current) { + dotRef.current.dataset.hydrated = 'true' + } + let last = performance.now() let rafId = 0 const tick = () => { diff --git a/examples/react/web-worker-row-models/src/tableConfig.tsx b/examples/react/web-worker-row-models/src/tableConfig.tsx index 54510662e8..028d346e7f 100644 --- a/examples/react/web-worker-row-models/src/tableConfig.tsx +++ b/examples/react/web-worker-row-models/src/tableConfig.tsx @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_extent, aggregationFn_mean, aggregationFn_median, @@ -42,6 +43,7 @@ import type { Person } from './makeData' // crosses threads at all. export const sharedFeatures = tableFeatures({ + aggregationFeature, rowSortingFeature, columnFilteringFeature, globalFilteringFeature, @@ -87,6 +89,17 @@ const sortStatusFn: SortFn = ( ) } +// Multi-aggregation values are keyed objects on grouped rows, so define which +// result controls their order. Leaf rows still sort by their numeric age. +const sortAgeFn: SortFn = (rowA, rowB) => { + const getAge = (row: typeof rowA) => { + const value = row.getValue('age') + return typeof value === 'number' ? value : value.median + } + + return getAge(rowA) - getAge(rowB) +} + // The sorting example's column defs merged with the grouping example's // aggregation props, exercising both entirely off the main thread: default // string/number ordering, a named registry sortFn (alphanumeric), @@ -113,9 +126,15 @@ export const columns = columnHelper.columns([ }), columnHelper.accessor('age', { header: () => 'Age', - aggregationFn: 'median', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, + sortFn: sortAgeFn, + aggregationFn: ['median', { id: 'range', aggregationFn: 'extent' }], + aggregatedCell: ({ getValue }) => { + const value = getValue<{ + median: number + range: [number | undefined, number | undefined] + }>() + return `Median ${Math.round(value.median * 100) / 100}; Range ${value.range[0]}โ€“${value.range[1]}` + }, }), columnHelper.accessor('visits', { header: () => Visits, diff --git a/examples/react/web-worker-row-models/tests/e2e/smoke.spec.ts b/examples/react/web-worker-row-models/tests/e2e/smoke.spec.ts index 22f9a2eacf..a083c542c2 100644 --- a/examples/react/web-worker-row-models/tests/e2e/smoke.spec.ts +++ b/examples/react/web-worker-row-models/tests/e2e/smoke.spec.ts @@ -35,12 +35,33 @@ async function openExample(page: Page) { ) await page.goto(server.url) - // SSR: wait for hydration before interacting. The jank meter dot only gets - // an inline transform from its rAF loop after the client has hydrated. - await page.waitForFunction(() => { - const dot = document.querySelector('.jank-dot') - return Boolean(dot?.style.transform) - }) + await page.waitForTimeout(250) + if (errors.length) { + throw new Error(`Page failed during hydration:\n${errors.join('\n')}`) + } + + // SSR: wait for the jank meter's effect to prove hydration before interacting. + // Do not depend on requestAnimationFrame, which browsers may throttle. + try { + await page.waitForFunction( + () => { + const dot = document.querySelector('.jank-dot') + return dot?.dataset.hydrated === 'true' + }, + undefined, + { timeout: 5_000 }, + ) + } catch { + const diagnostics = await page.evaluate(() => ({ + console: (window as any).__consoleLog, + readyState: document.readyState, + scripts: Array.from(document.scripts).map((script) => ({ + src: script.src, + type: script.type, + })), + })) + throw new Error(`Hydration diagnostics: ${JSON.stringify(diagnostics)}`) + } return { errors, server } } @@ -122,6 +143,8 @@ test('groups via the worker when a grouping toggle is clicked', async ({ await expect.poll(() => getFirstBodyRowText(table)).toContain('๐Ÿ‘‰') await expect.poll(() => getFirstBodyRowText(table)).toMatch(/\(\d/) + await expect.poll(() => getFirstBodyRowText(table)).toContain('Median') + await expect.poll(() => getFirstBodyRowText(table)).toContain('Range') // Expand the first group: leaf rows appear beneath it. await table.locator('tbody button').first().click() diff --git a/examples/solid/grouping/src/App.tsx b/examples/solid/grouping/src/App.tsx index 4b6832a380..0dc7f0c532 100644 --- a/examples/solid/grouping/src/App.tsx +++ b/examples/solid/grouping/src/App.tsx @@ -1,4 +1,5 @@ import { + aggregationFeature, aggregationFn_mean, aggregationFn_median, aggregationFn_sum, @@ -24,6 +25,7 @@ import type { Person } from './makeData' const { createAppTable, createAppColumnHelper } = createTableHook({ features: { + aggregationFeature, columnFilteringFeature, columnGroupingFeature, rowExpandingFeature, diff --git a/examples/svelte/grouping/src/App.svelte b/examples/svelte/grouping/src/App.svelte index 55ad65bce6..0b25ba678e 100644 --- a/examples/svelte/grouping/src/App.svelte +++ b/examples/svelte/grouping/src/App.svelte @@ -1,5 +1,6 @@ + + diff --git a/examples/alpine/aggregation/package.json b/examples/alpine/aggregation/package.json new file mode 100644 index 0000000000..3e1ccd51a6 --- /dev/null +++ b/examples/alpine/aggregation/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-alpine-table-example-aggregation", + "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.47", + "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/aggregation/src/index.css b/examples/alpine/aggregation/src/index.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/alpine/aggregation/src/index.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/alpine/aggregation/src/main.ts b/examples/alpine/aggregation/src/main.ts new file mode 100644 index 0000000000..1a334cd948 --- /dev/null +++ b/examples/alpine/aggregation/src/main.ts @@ -0,0 +1,127 @@ +import Alpine from 'alpinejs' +import { + FlexRender, + aggregationFeature, + aggregationFn_count, + aggregationFn_extent, + aggregationFn_mean, + aggregationFn_sum, + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createTable, + filterFn_includesString, + metaHelper, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, +} from '@tanstack/alpine-table' +import { makeData } from './makeData' +import './index.css' +import type { Sale } from './makeData' +import type { Table } from '@tanstack/alpine-table' + +type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected' +type AggregationTableMeta = { rowSource: RowSource } +const features = tableFeatures({ + aggregationFeature, + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + filteredRowModel: createFilteredRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filterFns: { includesString: filterFn_includesString }, + aggregationFns: { + count: aggregationFn_count, + extent: aggregationFn_extent, + mean: aggregationFn_mean, + sum: aggregationFn_sum, + }, + tableMeta: metaHelper(), +}) +const columnHelper = createColumnHelper() +function getAggregationRows(table: Table) { + const source = table.options.meta?.rowSource + if (source === 'all') return table.getCoreRowModel().rows + if (source === 'page') return table.getRowModel().rows + if (source === 'selected') return table.getFilteredSelectedRowModel().rows + if (source === 'custom') return table.getCoreRowModel().rows.slice(0, 3) + return undefined +} +function formatValue(value: unknown): string { + if (Array.isArray(value)) return value.map(formatValue).join(' โ€“ ') + if (value && typeof value === 'object') + return Object.entries(value) + .map(([key, entry]) => `${key}: ${formatValue(entry)}`) + .join(', ') + if (typeof value === 'number') + return value.toLocaleString(undefined, { maximumFractionDigits: 2 }) + return String(value ?? 'โ€”') +} +const columns = columnHelper.columns([ + columnHelper.display({ id: 'select' }), + columnHelper.accessor('category', { + header: 'Category', + filterFn: 'includesString', + }), + columnHelper.accessor('item', { + header: 'Item', + footer: ({ table }) => `${table.options.meta?.rowSource} total`, + }), + columnHelper.accessor('amount', { + header: 'Amount', + aggregationFn: 'sum', + cell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column, table }) => + formatValue(column.getAggregationValue(getAggregationRows(table))), + }), + columnHelper.accessor('score', { + header: 'Score', + aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }], + footer: ({ column, table }) => + formatValue(column.getAggregationValue(getAggregationRows(table))), + }), +]) + +Alpine.data('table', () => { + const local = Alpine.reactive({ + data: makeData(10_000), + rowSource: 'filtered' as RowSource, + }) + const table = createTable({ + features, + columns, + get data() { + return local.data + }, + get meta() { + return { rowSource: local.rowSource } + }, + initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, + debugTable: true, + debugColumns: true, + }) + return { + table, + FlexRender, + pageSizes: [10, 20, 30, 40, 50], + get rowSource() { + return local.rowSource + }, + set rowSource(value: RowSource) { + local.rowSource = value + }, + goToPage(value: string) { + table.setPageIndex(value ? Number(value) - 1 : 0) + }, + refreshData() { + local.data = makeData(10_000) + }, + stressTest() { + local.data = makeData(200_000) + }, + } +}) +window.Alpine = Alpine +Alpine.start() diff --git a/examples/alpine/aggregation/src/makeData.ts b/examples/alpine/aggregation/src/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/alpine/aggregation/src/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/alpine/aggregation/src/vite-env.d.ts b/examples/alpine/aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..99f6333028 --- /dev/null +++ b/examples/alpine/aggregation/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/aggregation/tests/e2e/smoke.spec.ts b/examples/alpine/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/alpine/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/aggregation/tsconfig.json b/examples/alpine/aggregation/tsconfig.json new file mode 100644 index 0000000000..8bd4c0cb56 --- /dev/null +++ b/examples/alpine/aggregation/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/aggregation/vite.config.js b/examples/alpine/aggregation/vite.config.js new file mode 100644 index 0000000000..9b5122fda5 --- /dev/null +++ b/examples/alpine/aggregation/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/alpine/grouped-aggregation/.devcontainer/devcontainer.json b/examples/alpine/grouped-aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/alpine/grouped-aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/alpine/grouped-aggregation/index.html b/examples/alpine/grouped-aggregation/index.html new file mode 100644 index 0000000000..6823a6748e --- /dev/null +++ b/examples/alpine/grouped-aggregation/index.html @@ -0,0 +1,157 @@ + + + + + + TanStack Alpine Table - Grouped Aggregation + + +
+
+ + +
+
+ + + + + + + + + + +
+
+
+ + + + + + Page + + + of + + + + + | Go to page: + + + +
+
+ + Rows +
+
+ + + diff --git a/examples/alpine/grouped-aggregation/package.json b/examples/alpine/grouped-aggregation/package.json new file mode 100644 index 0000000000..ffcd7fac81 --- /dev/null +++ b/examples/alpine/grouped-aggregation/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-alpine-table-example-grouped-aggregation", + "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.47", + "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/grouped-aggregation/src/index.css b/examples/alpine/grouped-aggregation/src/index.css new file mode 100644 index 0000000000..de3acbaf1b --- /dev/null +++ b/examples/alpine/grouped-aggregation/src/index.css @@ -0,0 +1,133 @@ +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; +} diff --git a/examples/alpine/grouped-aggregation/src/main.ts b/examples/alpine/grouped-aggregation/src/main.ts new file mode 100644 index 0000000000..7b3a315a44 --- /dev/null +++ b/examples/alpine/grouped-aggregation/src/main.ts @@ -0,0 +1,151 @@ +import Alpine from 'alpinejs' +import { + FlexRender, + aggregationFeature, + aggregationFn_mean, + aggregationFn_median, + aggregationFn_sum, + columnFilteringFeature, + columnGroupingFeature, + createColumnHelper, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTable, + filterFn_inNumberRange, + filterFn_includesString, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_text, + tableFeatures, +} from '@tanstack/alpine-table' +import { makeData } from './makeData' +import './index.css' +import type { ColumnDef } from '@tanstack/alpine-table' +import type { Person } from './makeData' + +const features = tableFeatures({ + aggregationFeature, + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(), + groupedRowModel: createGroupedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(), + filterFns: { + includesString: filterFn_includesString, + inNumberRange: filterFn_inNumberRange, + }, + sortFns: { + alphanumeric: sortFn_alphanumeric, + text: sortFn_text, + }, + aggregationFns: { + mean: aggregationFn_mean, + median: aggregationFn_median, + sum: aggregationFn_sum, + }, +}) + +const columnHelper = createColumnHelper() + +// Grouping toggle buttons (header) and expander toggles (grouped cells) are +// interactive and rendered directly in `index.html`, because Alpine cannot +// process directives inside `x-html`. +const columns: Array> = columnHelper.columns( + [ + columnHelper.accessor('firstName', { + header: 'First Name', + footer: 'Grand Total', + cell: (info) => info.getValue(), + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? '' : Math.round(value * 100) / 100 + }, + }), + columnHelper.accessor('visits', { + header: () => 'Visits', + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column }) => + column.getAggregationValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? '' : `${Math.round(value * 100) / 100}%` + }, + }), + ], +) + +Alpine.data('table', () => { + const local = Alpine.reactive({ data: makeData(10_000) }) + + const table = createTable({ + features, + columns, + get data() { + return local.data + }, + // initialState: { grouping: ['status'] }, // group by a column on first render + // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom + // state: { grouping }, // classic controlled state; pair with onGroupingChange + // onGroupingChange: setGrouping, + // enableGrouping: false, // disable grouping for every column; default true + // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' + // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + debugTable: true, + }) + + return { + table, + FlexRender, + cellBackground(cell: any) { + if (cell.getIsGrouped()) return '#0aff0082' + if (cell.getIsAggregated()) return '#ffa50078' + if (cell.getIsPlaceholder()) return '#ff000042' + return 'white' + }, + goToPage(value: string) { + table.setPageIndex(value ? Number(value) - 1 : 0) + }, + refreshData() { + local.data = makeData(10_000) + }, + stressTest() { + local.data = makeData(200_000) + }, + } +}) + +window.Alpine = Alpine +Alpine.start() diff --git a/examples/alpine/grouped-aggregation/src/makeData.ts b/examples/alpine/grouped-aggregation/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/alpine/grouped-aggregation/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + 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], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/alpine/grouped-aggregation/src/vite-env.d.ts b/examples/alpine/grouped-aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..99f6333028 --- /dev/null +++ b/examples/alpine/grouped-aggregation/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/grouped-aggregation/tests/e2e/smoke.spec.ts b/examples/alpine/grouped-aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/alpine/grouped-aggregation/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/grouped-aggregation/tsconfig.json b/examples/alpine/grouped-aggregation/tsconfig.json new file mode 100644 index 0000000000..8bd4c0cb56 --- /dev/null +++ b/examples/alpine/grouped-aggregation/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/grouped-aggregation/vite.config.js b/examples/alpine/grouped-aggregation/vite.config.js new file mode 100644 index 0000000000..9b5122fda5 --- /dev/null +++ b/examples/alpine/grouped-aggregation/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/alpine/grouping/index.html b/examples/alpine/grouping/index.html index 7c9339cff9..2de7adc5b3 100644 --- a/examples/alpine/grouping/index.html +++ b/examples/alpine/grouping/index.html @@ -59,17 +59,13 @@ > - - diff --git a/examples/alpine/grouping/src/main.ts b/examples/alpine/grouping/src/main.ts index 666af13e91..de7584a9be 100644 --- a/examples/alpine/grouping/src/main.ts +++ b/examples/alpine/grouping/src/main.ts @@ -1,10 +1,6 @@ import Alpine from 'alpinejs' import { FlexRender, - aggregationFeature, - aggregationFn_mean, - aggregationFn_median, - aggregationFn_sum, columnFilteringFeature, columnGroupingFeature, createColumnHelper, @@ -29,7 +25,6 @@ import type { ColumnDef } from '@tanstack/alpine-table' import type { Person } from './makeData' const features = tableFeatures({ - aggregationFeature, columnFilteringFeature, columnGroupingFeature, rowExpandingFeature, @@ -48,11 +43,6 @@ const features = tableFeatures({ alphanumeric: sortFn_alphanumeric, text: sortFn_text, }, - aggregationFns: { - mean: aggregationFn_mean, - median: aggregationFn_median, - sum: aggregationFn_sum, - }, }) const columnHelper = createColumnHelper() @@ -74,14 +64,9 @@ const columns: Array> = columnHelper.columns( }), columnHelper.accessor('age', { header: () => 'Age', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, - aggregationFn: 'median', }), columnHelper.accessor('visits', { header: () => 'Visits', - aggregationFn: 'sum', - aggregatedCell: ({ getValue }) => getValue().toLocaleString(), }), columnHelper.accessor('status', { header: 'Status', @@ -89,9 +74,6 @@ const columns: Array> = columnHelper.columns( columnHelper.accessor('progress', { header: 'Profile Progress', cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', - aggregationFn: 'mean', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100 + '%', }), ], ) @@ -111,7 +93,7 @@ Alpine.data('table', () => { // onGroupingChange: setGrouping, // enableGrouping: false, // disable grouping for every column; default true // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' - // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + // manualGrouping: true, // pass rows that are already grouped, for example from a server debugTable: true, }) @@ -120,7 +102,6 @@ Alpine.data('table', () => { FlexRender, cellBackground(cell: any) { if (cell.getIsGrouped()) return '#0aff0082' - if (cell.getIsAggregated()) return '#ffa50078' if (cell.getIsPlaceholder()) return '#ff000042' return 'white' }, diff --git a/examples/alpine/grouping/tests/e2e/smoke.spec.ts b/examples/alpine/grouping/tests/e2e/smoke.spec.ts index 6188138993..275770642a 100644 --- a/examples/alpine/grouping/tests/e2e/smoke.spec.ts +++ b/examples/alpine/grouping/tests/e2e/smoke.spec.ts @@ -79,6 +79,8 @@ test('renders the table without crashing', async ({ page }) => { await expect(table).toBeVisible() await expect(getHeaderCells(table).first()).toBeVisible() await expect(bodyRows.first()).toBeVisible() + await expect(table.locator('tfoot')).toHaveCount(0) + await expect(table).not.toContainText('Grand Total') const regenerateButton = page .getByRole('button', { name: /^Regenerate Data$/i }) diff --git a/examples/angular/aggregation/.devcontainer/devcontainer.json b/examples/angular/aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/angular/aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/angular/aggregation/.gitignore b/examples/angular/aggregation/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/aggregation/.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/aggregation/angular.json b/examples/angular/aggregation/angular.json new file mode 100644 index 0000000000..5f6a237a2b --- /dev/null +++ b/examples/angular/aggregation/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": { + "aggregation": { + "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": "aggregation:build:production" + }, + "development": { + "buildTarget": "aggregation:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/aggregation/package.json b/examples/angular/aggregation/package.json new file mode 100644 index 0000000000..273d3bd8a9 --- /dev/null +++ b/examples/angular/aggregation/package.json @@ -0,0 +1,33 @@ +{ + "name": "tanstack-angular-table-example-aggregation", + "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/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-table": "^9.0.0-beta.47", + "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/aggregation/public/favicon.ico b/examples/angular/aggregation/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/aggregation/src/app/app.config.ts b/examples/angular/aggregation/src/app/app.config.ts new file mode 100644 index 0000000000..47544a0587 --- /dev/null +++ b/examples/angular/aggregation/src/app/app.config.ts @@ -0,0 +1,8 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import { provideRouter } from '@angular/router' +import { routes } from './app.routes' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners(), provideRouter(routes)], +} diff --git a/examples/angular/aggregation/src/app/app.html b/examples/angular/aggregation/src/app/app.html new file mode 100644 index 0000000000..e74ebd1895 --- /dev/null +++ b/examples/angular/aggregation/src/app/app.html @@ -0,0 +1,138 @@ +
+

Aggregation without grouping

+

+ Amount uses a scalar sum. Score runs count, mean, and range together and returns a + keyed object. +

+
+ +
+
+
+ + +
+
+ + + @for (group of table.getHeaderGroups(); track group.id) { + + @for (header of group.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + + + @for (group of table.getFooterGroups(); track group.id) { + + @for (footer of group.headers; track footer.id) { + + } + + } + +
+ @if (header.column.id === 'select') { + + } @else if (!header.isPlaceholder) { + {{ value }} + } +
+ @if (cell.column.id === 'select') { + + } @else { + {{ value }} + } +
+ @if (!footer.isPlaceholder) { + {{ value }} + } +
+
+
+ +
Page
+ {{ (table.atoms.pagination.get().pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }}
+ | Go to page: + +
+
+ Showing {{ table.getRowModel().rows.length.toLocaleString() }} of + {{ table.getRowCount().toLocaleString() }} Rows +
+
{{ stringifiedState() }}
+
diff --git a/examples/angular/aggregation/src/app/app.routes.ts b/examples/angular/aggregation/src/app/app.routes.ts new file mode 100644 index 0000000000..9a884b1131 --- /dev/null +++ b/examples/angular/aggregation/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import type { Routes } from '@angular/router' + +export const routes: Routes = [] diff --git a/examples/angular/aggregation/src/app/app.ts b/examples/angular/aggregation/src/app/app.ts new file mode 100644 index 0000000000..4bf2e85d4a --- /dev/null +++ b/examples/angular/aggregation/src/app/app.ts @@ -0,0 +1,123 @@ +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { + FlexRender, + aggregationFeature, + aggregationFn_count, + aggregationFn_extent, + aggregationFn_mean, + aggregationFn_sum, + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + filterFn_includesString, + injectTable, + metaHelper, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, +} from '@tanstack/angular-table' +import { makeData } from './makeData' +import type { ColumnDef, Table } from '@tanstack/angular-table' +import type { Sale } from './makeData' + +type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected' +type AggregationTableMeta = { rowSource: RowSource } +const features = tableFeatures({ + aggregationFeature, + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + filteredRowModel: createFilteredRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filterFns: { includesString: filterFn_includesString }, + aggregationFns: { + count: aggregationFn_count, + extent: aggregationFn_extent, + mean: aggregationFn_mean, + sum: aggregationFn_sum, + }, + tableMeta: metaHelper(), +}) +function getAggregationRows(table: Table) { + const source = table.options.meta?.rowSource + if (source === 'all') return table.getCoreRowModel().rows + if (source === 'page') return table.getRowModel().rows + if (source === 'selected') return table.getFilteredSelectedRowModel().rows + if (source === 'custom') return table.getCoreRowModel().rows.slice(0, 3) + return undefined +} +function formatValue(value: unknown): string { + if (Array.isArray(value)) return value.map(formatValue).join(' โ€“ ') + if (value && typeof value === 'object') + return Object.entries(value) + .map(([key, entry]) => `${key}: ${formatValue(entry)}`) + .join(', ') + if (typeof value === 'number') + return value.toLocaleString(undefined, { maximumFractionDigits: 2 }) + return String(value ?? 'โ€”') +} +const columns: Array> = [ + { id: 'select' }, + { accessorKey: 'category', header: 'Category', filterFn: 'includesString' }, + { + accessorKey: 'item', + header: 'Item', + footer: ({ table }) => `${table.options.meta?.rowSource} total`, + }, + { + accessorKey: 'amount', + header: 'Amount', + aggregationFn: 'sum', + cell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column, table }) => + formatValue(column.getAggregationValue(getAggregationRows(table))), + }, + { + accessorKey: 'score', + header: 'Score', + aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }], + footer: ({ column, table }) => + formatValue(column.getAggregationValue(getAggregationRows(table))), + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(10_000)) + readonly rowSource = signal('filtered') + readonly pageSizes = [10, 20, 30, 40, 50] + readonly table = injectTable(() => ({ + features, + columns, + data: this.data(), + meta: { rowSource: this.rowSource() }, + initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, + debugTable: true, + debugColumns: true, + })) + stringifiedState() { + return JSON.stringify(this.table.store.get(), null, 2) + } + refreshData = () => this.data.set(makeData(10_000)) + stressTest = () => this.data.set(makeData(200_000)) + onCategoryInput(event: Event) { + this.table + .getColumn('category') + ?.setFilterValue((event.target as HTMLInputElement).value) + } + onRowSourceChange(event: Event) { + this.rowSource.set((event.target as HTMLSelectElement).value as RowSource) + } + onPageInputChange(event: Event) { + const value = (event.target as HTMLInputElement).value + this.table.setPageIndex(value ? Number(value) - 1 : 0) + } + onPageSizeChange(event: Event) { + this.table.setPageSize(Number((event.target as HTMLSelectElement).value)) + } +} diff --git a/examples/angular/aggregation/src/app/makeData.ts b/examples/angular/aggregation/src/app/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/angular/aggregation/src/app/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/angular/aggregation/src/index.html b/examples/angular/aggregation/src/index.html new file mode 100644 index 0000000000..cc9641d9e8 --- /dev/null +++ b/examples/angular/aggregation/src/index.html @@ -0,0 +1,13 @@ + + + + + TanStack Angular Table - Aggregation + + + + + + + + diff --git a/examples/angular/aggregation/src/main.ts b/examples/angular/aggregation/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/aggregation/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/aggregation/src/styles.css b/examples/angular/aggregation/src/styles.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/angular/aggregation/src/styles.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/angular/aggregation/tests/e2e/smoke.spec.ts b/examples/angular/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/angular/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/aggregation/tsconfig.app.json b/examples/angular/aggregation/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/aggregation/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/aggregation/tsconfig.json b/examples/angular/aggregation/tsconfig.json new file mode 100644 index 0000000000..1ec24b3409 --- /dev/null +++ b/examples/angular/aggregation/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/angular/grouped-aggregation/.devcontainer/devcontainer.json b/examples/angular/grouped-aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/angular/grouped-aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/angular/grouped-aggregation/.gitignore b/examples/angular/grouped-aggregation/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/grouped-aggregation/.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/grouped-aggregation/angular.json b/examples/angular/grouped-aggregation/angular.json new file mode 100644 index 0000000000..fdc60ec309 --- /dev/null +++ b/examples/angular/grouped-aggregation/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": { + "grouped-aggregation": { + "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": "grouped-aggregation:build:production" + }, + "development": { + "buildTarget": "grouped-aggregation:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/grouped-aggregation/package.json b/examples/angular/grouped-aggregation/package.json new file mode 100644 index 0000000000..b81c6fee2d --- /dev/null +++ b/examples/angular/grouped-aggregation/package.json @@ -0,0 +1,33 @@ +{ + "name": "tanstack-angular-table-example-grouped-aggregation", + "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/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-table": "^9.0.0-beta.47", + "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/grouped-aggregation/public/favicon.ico b/examples/angular/grouped-aggregation/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/grouped-aggregation/src/app/app.config.ts b/examples/angular/grouped-aggregation/src/app/app.config.ts new file mode 100644 index 0000000000..cbb47d366c --- /dev/null +++ b/examples/angular/grouped-aggregation/src/app/app.config.ts @@ -0,0 +1,6 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners()], +} diff --git a/examples/angular/grouped-aggregation/src/app/app.html b/examples/angular/grouped-aggregation/src/app/app.html new file mode 100644 index 0000000000..4c2718436b --- /dev/null +++ b/examples/angular/grouped-aggregation/src/app/app.html @@ -0,0 +1,162 @@ +
+ + +
+ + + @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.getAllCells(); track cell.id) { + + } + + } + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (footer of footerGroup.headers; track footer.id) { + + } + + } + +
+ @if (header.isPlaceholder) { + + } @else { +
+ @if (header.column.getCanGroup()) { + + + } + + {{ header }} + +
+ } +
+ @if (cell.getIsGrouped()) { + + + + {{ cell }} + + {{ ' ' }}{{ row.subRows.length.toLocaleString() }} + } @else if (cell.getIsAggregated()) { + + + + {{ aggregatedCell }} + + } @else if (cell.getIsPlaceholder()) { + } @else { + + + + {{ cell }} + + } +
+ @if (!footer.isPlaceholder) { + {{ + footerCell + }} + } +
+ +
+
+ + + + + +
Page
+ + {{ (table.atoms.pagination.get().pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }} + +
+ + | Go to page: + + + + +
+
{{ table.getRowModel().rows.length.toLocaleString() }} Rows
+
+
{{ stringifiedState() }}
+
diff --git a/examples/angular/grouped-aggregation/src/app/app.ts b/examples/angular/grouped-aggregation/src/app/app.ts new file mode 100644 index 0000000000..605cc5fdf0 --- /dev/null +++ b/examples/angular/grouped-aggregation/src/app/app.ts @@ -0,0 +1,55 @@ +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { FlexRender, isFunction } from '@tanstack/angular-table' +import { columns, injectTable } from './columns' +import { makeData } from './makeData' +import type { GroupingState, Updater } from '@tanstack/angular-table' + +@Component({ + selector: 'app-root', + standalone: true, + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal(makeData(1_000)) + readonly grouping = signal([]) + + readonly table = injectTable(() => ({ + data: this.data(), + columns: columns, + initialState: { + pagination: { pageSize: 20, pageIndex: 0 }, + }, + state: { + grouping: this.grouping(), + }, + onGroupingChange: (updaterOrValue: Updater) => { + const groupingState = isFunction(updaterOrValue) + ? updaterOrValue([...this.grouping()]) + : updaterOrValue + this.grouping.set(groupingState) + }, + // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom + // enableGrouping: false, // disable grouping for every column; default true + // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' + // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + debugTable: true, + })) + + stringifiedState() { + return JSON.stringify(this.table.store.get(), null, 2) + } + + onPageInputChange(event: any): void { + const page = event.target.value ? Number(event.target.value) - 1 : 0 + this.table.setPageIndex(page) + } + + onPageSizeChange(event: any) { + this.table.setPageSize(Number(event.target.value)) + } + + refreshData = () => this.data.set(makeData(1_000)) + stressTest = () => this.data.set(makeData(200_000)) +} diff --git a/examples/angular/grouped-aggregation/src/app/columns.ts b/examples/angular/grouped-aggregation/src/app/columns.ts new file mode 100644 index 0000000000..974c154377 --- /dev/null +++ b/examples/angular/grouped-aggregation/src/app/columns.ts @@ -0,0 +1,103 @@ +import { + aggregationFeature, + aggregationFn_mean, + aggregationFn_median, + aggregationFn_sum, + columnFilteringFeature, + columnGroupingFeature, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createTableHook, + rowExpandingFeature, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/angular-table' +import type { Person } from './makeData' + +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, + rowPaginationFeature, + columnFilteringFeature, + rowExpandingFeature, + groupedRowModel: createGroupedRowModel(), + expandedRowModel: createExpandedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filteredRowModel: createFilteredRowModel(), + aggregationFns: { + mean: aggregationFn_mean, + median: aggregationFn_median, + sum: aggregationFn_sum, + }, +}) + +export const { createAppColumnHelper, injectAppTable: injectTable } = + createTableHook({ + features, + }) +const columnHelper = createAppColumnHelper() + +export const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + header: () => 'First Name', + footer: 'Grand Total', + cell: (info) => info.getValue(), + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => 'Last Name', + cell: (info) => info.getValue(), + }), + ]), + }), + columnHelper.group({ + header: 'Info', + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? '' : Math.round(value * 100) / 100 + }, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => `Visits`, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column }) => + column.getAggregationValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined + ? '' + : `${Math.round(value * 100) / 100}%` + }, + }), + ]), + }), + ]), + }), +]) diff --git a/examples/angular/grouped-aggregation/src/app/makeData.ts b/examples/angular/grouped-aggregation/src/app/makeData.ts new file mode 100644 index 0000000000..b393b01622 --- /dev/null +++ b/examples/angular/grouped-aggregation/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + 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 { + 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], + } +} + +export function makeData(...lens: Array): 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/grouped-aggregation/src/index.html b/examples/angular/grouped-aggregation/src/index.html new file mode 100644 index 0000000000..1367bf5ab5 --- /dev/null +++ b/examples/angular/grouped-aggregation/src/index.html @@ -0,0 +1,14 @@ + + + + + TanStack Angular Table - Grouped Aggregation + + + + + + + + + diff --git a/examples/angular/grouped-aggregation/src/main.ts b/examples/angular/grouped-aggregation/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/grouped-aggregation/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/grouped-aggregation/src/styles.css b/examples/angular/grouped-aggregation/src/styles.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/angular/grouped-aggregation/src/styles.css @@ -0,0 +1,365 @@ +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; +} diff --git a/examples/angular/grouped-aggregation/tests/e2e/smoke.spec.ts b/examples/angular/grouped-aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/angular/grouped-aggregation/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/grouped-aggregation/tsconfig.app.json b/examples/angular/grouped-aggregation/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/grouped-aggregation/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/grouped-aggregation/tsconfig.json b/examples/angular/grouped-aggregation/tsconfig.json new file mode 100644 index 0000000000..1ec24b3409 --- /dev/null +++ b/examples/angular/grouped-aggregation/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/angular/grouping/src/app/app.html b/examples/angular/grouping/src/app/app.html index 7a6a0ca2ac..bf5c641f26 100644 --- a/examples/angular/grouping/src/app/app.html +++ b/examples/angular/grouping/src/app/app.html @@ -40,13 +40,7 @@ @for (cell of row.getAllCells(); track cell.id) { @if (cell.getIsGrouped()) { @@ -61,19 +55,7 @@ {{ cell }} {{ ' ' }}{{ row.subRows.length.toLocaleString() }} - } @else if (cell.getIsAggregated()) { - - - - {{ aggregatedCell }} - - } @else if (cell.getIsPlaceholder()) { + } @else if (cell.getIsPlaceholder() || row.getIsGrouped()) { } @else { diff --git a/examples/angular/grouping/src/app/app.ts b/examples/angular/grouping/src/app/app.ts index 605cc5fdf0..679c379003 100644 --- a/examples/angular/grouping/src/app/app.ts +++ b/examples/angular/grouping/src/app/app.ts @@ -33,7 +33,7 @@ export class App { // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom // enableGrouping: false, // disable grouping for every column; default true // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' - // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + // manualGrouping: true, // pass rows that are already grouped, for example from a server debugTable: true, })) diff --git a/examples/angular/grouping/src/app/columns.ts b/examples/angular/grouping/src/app/columns.ts index 66417d5dfa..61744b2d38 100644 --- a/examples/angular/grouping/src/app/columns.ts +++ b/examples/angular/grouping/src/app/columns.ts @@ -1,8 +1,4 @@ import { - aggregationFeature, - aggregationFn_mean, - aggregationFn_median, - aggregationFn_sum, columnFilteringFeature, columnGroupingFeature, createExpandedRowModel, @@ -17,7 +13,6 @@ import { import type { Person } from './makeData' const features = tableFeatures({ - aggregationFeature, columnGroupingFeature, rowPaginationFeature, columnFilteringFeature, @@ -26,11 +21,6 @@ const features = tableFeatures({ expandedRowModel: createExpandedRowModel(), paginatedRowModel: createPaginatedRowModel(), filteredRowModel: createFilteredRowModel(), - aggregationFns: { - mean: aggregationFn_mean, - median: aggregationFn_median, - sum: aggregationFn_sum, - }, }) export const { createAppColumnHelper, injectAppTable: injectTable } = @@ -60,17 +50,12 @@ export const columns = columnHelper.columns([ columns: columnHelper.columns([ columnHelper.accessor('age', { header: () => 'Age', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, - aggregationFn: 'median', }), columnHelper.group({ header: 'More Info', columns: columnHelper.columns([ columnHelper.accessor('visits', { header: () => `Visits`, - aggregationFn: 'sum', - aggregatedCell: ({ getValue }) => getValue().toLocaleString(), }), columnHelper.accessor('status', { header: 'Status', @@ -79,9 +64,6 @@ export const columns = columnHelper.columns([ header: 'Profile Progress', cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', - aggregationFn: 'mean', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100 + '%', }), ]), }), diff --git a/examples/angular/grouping/tests/e2e/smoke.spec.ts b/examples/angular/grouping/tests/e2e/smoke.spec.ts index 6188138993..275770642a 100644 --- a/examples/angular/grouping/tests/e2e/smoke.spec.ts +++ b/examples/angular/grouping/tests/e2e/smoke.spec.ts @@ -79,6 +79,8 @@ test('renders the table without crashing', async ({ page }) => { await expect(table).toBeVisible() await expect(getHeaderCells(table).first()).toBeVisible() await expect(bodyRows.first()).toBeVisible() + await expect(table.locator('tfoot')).toHaveCount(0) + await expect(table).not.toContainText('Grand Total') const regenerateButton = page .getByRole('button', { name: /^Regenerate Data$/i }) diff --git a/examples/ember/aggregation/.devcontainer/devcontainer.json b/examples/ember/aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/ember/aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/ember/aggregation/app/app.css b/examples/ember/aggregation/app/app.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/ember/aggregation/app/app.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/ember/aggregation/app/app.ts b/examples/ember/aggregation/app/app.ts new file mode 100644 index 0000000000..d5681b3c7a --- /dev/null +++ b/examples/ember/aggregation/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/aggregation/app/config.ts b/examples/ember/aggregation/app/config.ts new file mode 100644 index 0000000000..8b0d33268d --- /dev/null +++ b/examples/ember/aggregation/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/aggregation/app/router.ts b/examples/ember/aggregation/app/router.ts new file mode 100644 index 0000000000..eadbe60450 --- /dev/null +++ b/examples/ember/aggregation/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/aggregation/app/templates/application.gts b/examples/ember/aggregation/app/templates/application.gts new file mode 100644 index 0000000000..e8d4ad805e --- /dev/null +++ b/examples/ember/aggregation/app/templates/application.gts @@ -0,0 +1,402 @@ +import Component from '@glimmer/component' +import { tracked } from '@glimmer/tracking' +import { on } from '@ember/modifier' +import { + useTable, + aggregationFeature, + aggregationFn_count, + aggregationFn_extent, + aggregationFn_mean, + aggregationFn_sum, + columnFilteringFeature, + FlexRenderCell, + FlexRenderHeader, + FlexRenderFooter, + tableFeatures, + rowPaginationFeature, + createPaginatedRowModel, + createFilteredRowModel, + filterFn_includesString, + metaHelper, + rowSelectionFeature, + createColumnHelper, + type Row, + type Cell, + type Table, +} from '@tanstack/ember-table' +import { makeData, type Sale } from '../utils/make-data' + +type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected' +type AggregationTableMeta = { rowSource: RowSource } + +const features = tableFeatures({ + aggregationFeature, + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + filteredRowModel: createFilteredRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filterFns: { includesString: filterFn_includesString }, + aggregationFns: { + count: aggregationFn_count, + extent: aggregationFn_extent, + mean: aggregationFn_mean, + sum: aggregationFn_sum, + }, + tableMeta: metaHelper(), +}) + +const columnHelper = createColumnHelper() + +const formatAggregationValue = (value: unknown): string => { + if (Array.isArray(value)) return value.map(formatAggregationValue).join(' โ€“ ') + if (value && typeof value === 'object') { + return Object.entries(value) + .map(([key, entry]) => `${key}: ${formatAggregationValue(entry)}`) + .join(', ') + } + return typeof value === 'number' + ? value.toLocaleString(undefined, { maximumFractionDigits: 2 }) + : String(value ?? 'โ€”') +} + +function getAggregationRows(table: Table) { + const source = table.options.meta?.rowSource + if (source === 'all') return table.getCoreRowModel().rows + if (source === 'page') return table.getRowModel().rows + if (source === 'selected') return table.getFilteredSelectedRowModel().rows + if (source === 'custom') return table.getCoreRowModel().rows.slice(0, 3) + return undefined +} + +const columns = columnHelper.columns([ + columnHelper.display({ id: 'select' }), + columnHelper.accessor('category', { + header: 'Category', + filterFn: 'includesString', + }), + columnHelper.accessor('item', { + header: 'Item', + footer: ({ table }) => `${table.options.meta?.rowSource} total`, + }), + columnHelper.accessor('amount', { + header: 'Amount', + aggregationFn: 'sum', + cell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column, table }) => + formatAggregationValue( + column.getAggregationValue(getAggregationRows(table)), + ), + }), + columnHelper.accessor('score', { + header: 'Score', + aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }], + footer: ({ column, table }) => + formatAggregationValue( + column.getAggregationValue(getAggregationRows(table)), + ), + }), +]) + +const PAGE_SIZES = [10, 20, 30, 40, 50] + +// TanStack Table v9 uses prototype-based methods that require `this` binding. +// Ember templates extract function references without binding, so we provide +// helpers that call methods on the correct object. +const getAllCells = ( + row: Row, +): Array> => row.getAllCells() +const not = (value: unknown): boolean => !value +const eq = (a: unknown, b: unknown): boolean => String(a) === String(b) +const isSelected = (row: Row): boolean => + row.getIsSelected() + +export default class PaginationTable extends Component { + @tracked data: Array = makeData(10_000) + @tracked rowSource: RowSource = 'filtered' + + table = useTable(() => ({ + features, + columns, + data: this.data, + meta: { rowSource: this.rowSource }, + initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, + // initialState: { pagination: { pageIndex: 1, pageSize: 20 } }, // set the initial page once + // atoms: { pagination: paginationAtom }, // preferred: own pagination state with an external atom + // state: { pagination }, // classic controlled state; pair with onPaginationChange + // onPaginationChange: setPagination, + // autoResetPageIndex: false, // keep the current page after page-altering changes; default true + // autoResetAll: false, // turn off every feature's automatic reset, including page index + // manualPagination: true, // pass data that is already paginated, for example from a server + // pageCount: 10, // total pages for manual pagination; use -1 when unknown + // rowCount: 1_000, // total rows for manual pagination; pageCount is calculated from this and pageSize + })) + + get headerGroups() { + return this.table.getHeaderGroups() + } + + get rows() { + return this.table.getRowModel().rows + } + + get footerGroups() { + return this.table.getFooterGroups() + } + + get pagination() { + return this.table.store.state.pagination + } + + get tableState() { + return JSON.stringify(this.pagination, null, 2) + } + + get canPreviousPage() { + return this.table.getCanPreviousPage() + } + + get canNextPage() { + return this.table.getCanNextPage() + } + + get allPageSelected() { + return this.table.getIsAllPageRowsSelected() + } + + get pageCount() { + return this.table.getPageCount() + } + + get currentPage() { + return (this.pagination.pageIndex + 1).toLocaleString() + } + + get pageCountDisplay() { + return this.table.getPageCount().toLocaleString() + } + + get currentPageInputValue() { + return String(this.pagination.pageIndex + 1) + } + + get pageSize() { + return this.pagination.pageSize + } + + get pageSizes() { + return PAGE_SIZES + } + + get categoryFilter() { + return String(this.table.getColumn('category')?.getFilterValue() ?? '') + } + + refreshData = () => { + this.data = makeData(10_000) + } + + stressTest = () => { + this.data = makeData(200_000) + } + + goToFirstPage = () => { + this.table.setPageIndex(0) + } + + goToPreviousPage = () => { + this.table.previousPage() + } + + goToNextPage = () => { + this.table.nextPage() + } + + goToLastPage = () => { + this.table.setPageIndex(this.table.getPageCount() - 1) + } + + handleGoToPage = (event: Event) => { + const target = event.currentTarget as HTMLInputElement + const page = target.value ? Number(target.value) - 1 : 0 + this.table.setPageIndex(page) + } + + handlePageSizeChange = (event: Event) => { + const target = event.currentTarget as HTMLSelectElement + this.table.setPageSize(Number(target.value)) + } + + handleCategoryFilter = (event: Event) => { + this.table + .getColumn('category') + ?.setFilterValue((event.currentTarget as HTMLInputElement).value) + } + + handleRowSource = (event: Event) => { + this.rowSource = (event.currentTarget as HTMLSelectElement) + .value as RowSource + } + + toggleAllPageRows = () => this.table.toggleAllPageRowsSelected() + + toggleRow = (event: Event) => { + const id = (event.currentTarget as HTMLInputElement).dataset.rowId + if (id) this.table.getRow(id).toggleSelected() + } + + +} diff --git a/examples/ember/aggregation/app/utils/make-data.ts b/examples/ember/aggregation/app/utils/make-data.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/ember/aggregation/app/utils/make-data.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/ember/aggregation/babel.config.js b/examples/ember/aggregation/babel.config.js new file mode 100644 index 0000000000..10915e4299 --- /dev/null +++ b/examples/ember/aggregation/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/aggregation/index.html b/examples/ember/aggregation/index.html new file mode 100644 index 0000000000..a879351cd8 --- /dev/null +++ b/examples/ember/aggregation/index.html @@ -0,0 +1,17 @@ + + + + + TanStack Ember Table - Aggregation + + + + + + + diff --git a/examples/ember/aggregation/package.json b/examples/ember/aggregation/package.json new file mode 100644 index 0000000000..16244ba6c0 --- /dev/null +++ b/examples/ember/aggregation/package.json @@ -0,0 +1,58 @@ +{ + "name": "tanstack-ember-table-example-aggregation", + "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.47", + "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/aggregation/tests/e2e/smoke.spec.ts b/examples/ember/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/ember/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/aggregation/tsconfig.json b/examples/ember/aggregation/tsconfig.json new file mode 100644 index 0000000000..5aac8ced49 --- /dev/null +++ b/examples/ember/aggregation/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/aggregation/vite.config.mjs b/examples/ember/aggregation/vite.config.mjs new file mode 100644 index 0000000000..25d29c7c5e --- /dev/null +++ b/examples/ember/aggregation/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/ember/grouped-aggregation/.devcontainer/devcontainer.json b/examples/ember/grouped-aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/ember/grouped-aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/ember/grouped-aggregation/app/app.css b/examples/ember/grouped-aggregation/app/app.css new file mode 100644 index 0000000000..acb93951cf --- /dev/null +++ b/examples/ember/grouped-aggregation/app/app.css @@ -0,0 +1,301 @@ +/* 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; +} diff --git a/examples/ember/grouped-aggregation/app/app.ts b/examples/ember/grouped-aggregation/app/app.ts new file mode 100644 index 0000000000..d5681b3c7a --- /dev/null +++ b/examples/ember/grouped-aggregation/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/grouped-aggregation/app/config.ts b/examples/ember/grouped-aggregation/app/config.ts new file mode 100644 index 0000000000..8b0d33268d --- /dev/null +++ b/examples/ember/grouped-aggregation/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/grouped-aggregation/app/router.ts b/examples/ember/grouped-aggregation/app/router.ts new file mode 100644 index 0000000000..eadbe60450 --- /dev/null +++ b/examples/ember/grouped-aggregation/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/grouped-aggregation/app/templates/application.gts b/examples/ember/grouped-aggregation/app/templates/application.gts new file mode 100644 index 0000000000..2b6efbce41 --- /dev/null +++ b/examples/ember/grouped-aggregation/app/templates/application.gts @@ -0,0 +1,480 @@ +import Component from '@glimmer/component' +import { tracked } from '@glimmer/tracking' +import { on } from '@ember/modifier' +import { + useTable, + aggregationFeature, + FlexRenderCell, + FlexRenderHeader, + FlexRenderFooter, + flexRender, + FlexRenderComponentConfig, + tableFeatures, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + columnFilteringFeature, + rowSortingFeature, + createGroupedRowModel, + createExpandedRowModel, + createPaginatedRowModel, + createFilteredRowModel, + createSortedRowModel, + aggregationFn_mean, + aggregationFn_median, + aggregationFn_sum, + filterFn_includesString, + filterFn_inNumberRange, + sortFn_alphanumeric, + sortFn_text, + createColumnHelper, + type Column, + type Row, + type Cell, + type CellContext, + type GroupingState, + type SortingState, +} from '@tanstack/ember-table' +import type { ComponentLike, ContentValue } from '@glint/template' +import { makeData, type Person } from '../utils/make-data' + +const features = tableFeatures({ + aggregationFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + columnFilteringFeature, + rowSortingFeature, + groupedRowModel: createGroupedRowModel(), + expandedRowModel: createExpandedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filteredRowModel: createFilteredRowModel(), + sortedRowModel: createSortedRowModel(), + aggregationFns: { + mean: aggregationFn_mean, + median: aggregationFn_median, + sum: aggregationFn_sum, + }, + filterFns: { + includesString: filterFn_includesString, + inNumberRange: filterFn_inNumberRange, + }, + sortFns: { + alphanumeric: sortFn_alphanumeric, + text: sortFn_text, + }, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + header: () => 'First Name', + footer: 'Grand Total', + cell: (info) => info.getValue(), + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => 'Last Name', + cell: (info) => info.getValue(), + }), + ]), + }), + columnHelper.group({ + header: 'Info', + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + aggregationFn: 'median', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? '' : Math.round(value * 100) / 100 + }, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => 'Visits', + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => + getValue().toLocaleString(), + footer: ({ column }) => + column.getAggregationValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => + `${Math.round(getValue() * 100) / 100}%`, + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + `${Math.round(getValue() * 100) / 100}%`, + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined + ? '' + : `${Math.round(value * 100) / 100}%` + }, + }), + ]), + }), + ]), + }), +]) + +const PAGE_SIZES = [10, 20, 30, 40, 50] + +// --- Aggregated cell renderer (renders aggregatedCell ?? cell) --- + +type RenderOptions = Record | undefined + +interface AggregatedCellSignature { + Args: { cell: Cell } + Element: null +} + +class FlexRenderAggregatedCell extends Component { + get result(): + | string + | number + | null + | FlexRenderComponentConfig { + const cell = this.args.cell + const def = + cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell + return flexRender(def, cell.getContext()) as + | string + | number + | null + | FlexRenderComponentConfig + } + + get resolvedContext(): CellContext { + return this.args.cell.getContext() + } + + get isComponent(): boolean { + return this.result instanceof FlexRenderComponentConfig + } + + get componentToRender(): + | ComponentLike<{ + Args: { + ctx: CellContext + options?: RenderOptions + } + }> + | undefined { + const result = this.result + if (result instanceof FlexRenderComponentConfig) { + return result.component as ComponentLike<{ + Args: { + ctx: CellContext + options?: RenderOptions + } + }> + } + return undefined + } + + get componentOptions(): RenderOptions { + const result = this.result + if (result instanceof FlexRenderComponentConfig) { + return result.options + } + return undefined + } + + get content(): ContentValue { + return this.result as ContentValue + } + + +} + +// --- Template helpers (v9 methods need explicit `this` binding) --- + +const getAllCells = ( + row: Row, +): Array> => row.getAllCells() +const getCanSort = (column: Column): boolean => + column.getCanSort() +const toggleSort = + (column: Column) => (event: Event) => { + column.getToggleSortingHandler()?.(event) + } +const lookup = (obj: Record, key: string): string => + obj[key] ?? '' +const getCanGroup = (column: Column): boolean => + column.getCanGroup() +const getIsGrouped = (column: Column): boolean => + column.getIsGrouped() +const getGroupedIndex = (column: Column): number => + column.getGroupedIndex() +const cellIsGrouped = (cell: Cell): boolean => + cell.getIsGrouped() +const cellIsAggregated = (cell: Cell): boolean => + cell.getIsAggregated() +const cellIsPlaceholder = (cell: Cell): boolean => + cell.getIsPlaceholder() +const rowIsExpanded = (row: Row): boolean => + row.getIsExpanded() +const rowCanExpand = (row: Row): boolean => + row.getCanExpand() +const subRowCount = (row: Row): string => + row.subRows.length.toLocaleString() + +const toggleGrouping = (column: Column) => () => + column.toggleGrouping() +const toggleExpanded = (row: Row) => () => + row.toggleExpanded() + +const not = (value: unknown): boolean => !value +const eq = (a: unknown, b: unknown): boolean => String(a) === String(b) + +export default class GroupingTable extends Component { + @tracked data: Array = makeData(1_000) + @tracked grouping: GroupingState = [] + @tracked sorting: SortingState = [] + + table = useTable(() => ({ + features, + columns, + data: this.data, + initialState: { pagination: { pageSize: 20, pageIndex: 0 } }, + state: { + grouping: this.grouping, + sorting: this.sorting, + }, + onGroupingChange: (updater) => { + this.grouping = + typeof updater === 'function' ? updater(this.grouping) : updater + }, + onSortingChange: (updater) => { + this.sorting = + typeof updater === 'function' ? updater(this.sorting) : updater + }, + // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom + // enableGrouping: false, // disable grouping for every column; default true + // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' + // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + })) + + get headerGroups() { + return this.table.getHeaderGroups() + } + + get rows() { + return this.table.getRowModel().rows + } + + get footerGroups() { + return this.table.getFooterGroups() + } + + get canPreviousPage() { + return this.table.getCanPreviousPage() + } + + get canNextPage() { + return this.table.getCanNextPage() + } + + get pagination() { + return this.table.store.state.pagination + } + + get pageSizes() { + return PAGE_SIZES + } + + get sortIndicators(): Record { + const indicators: Record = {} + for (const hg of this.table.getHeaderGroups()) { + for (const h of hg.headers) { + const sorted = h.column.getIsSorted() + indicators[h.column.id] = + sorted === 'asc' ? ' ๐Ÿ”ผ' : sorted === 'desc' ? ' ๐Ÿ”ฝ' : '' + } + } + return indicators + } + + get tableState() { + return JSON.stringify(this.table.store.state, null, 2) + } + + regenerateData = () => { + this.data = makeData(1_000) + } + + goToFirstPage = () => { + this.table.setPageIndex(0) + } + + goToPreviousPage = () => { + this.table.previousPage() + } + + goToNextPage = () => { + this.table.nextPage() + } + + goToLastPage = () => { + this.table.setPageIndex(this.table.getPageCount() - 1) + } + + handlePageSizeChange = (event: Event) => { + const target = event.currentTarget as HTMLSelectElement + this.table.setPageSize(Number(target.value)) + } + + +} diff --git a/examples/ember/grouped-aggregation/app/utils/make-data.ts b/examples/ember/grouped-aggregation/app/utils/make-data.ts new file mode 100644 index 0000000000..2c03d3da55 --- /dev/null +++ b/examples/ember/grouped-aggregation/app/utils/make-data.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + 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 { + 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]!, + } +} + +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/grouped-aggregation/babel.config.js b/examples/ember/grouped-aggregation/babel.config.js new file mode 100644 index 0000000000..10915e4299 --- /dev/null +++ b/examples/ember/grouped-aggregation/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/grouped-aggregation/index.html b/examples/ember/grouped-aggregation/index.html new file mode 100644 index 0000000000..981a32608e --- /dev/null +++ b/examples/ember/grouped-aggregation/index.html @@ -0,0 +1,17 @@ + + + + + TanStack Ember Table - Grouped Aggregation + + + + + + + diff --git a/examples/ember/grouped-aggregation/package.json b/examples/ember/grouped-aggregation/package.json new file mode 100644 index 0000000000..75a94f1162 --- /dev/null +++ b/examples/ember/grouped-aggregation/package.json @@ -0,0 +1,58 @@ +{ + "name": "tanstack-ember-table-example-grouped-aggregation", + "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.47", + "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/grouped-aggregation/tests/e2e/smoke.spec.ts b/examples/ember/grouped-aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/ember/grouped-aggregation/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/grouped-aggregation/tsconfig.json b/examples/ember/grouped-aggregation/tsconfig.json new file mode 100644 index 0000000000..5aac8ced49 --- /dev/null +++ b/examples/ember/grouped-aggregation/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/grouped-aggregation/vite.config.mjs b/examples/ember/grouped-aggregation/vite.config.mjs new file mode 100644 index 0000000000..25d29c7c5e --- /dev/null +++ b/examples/ember/grouped-aggregation/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/ember/grouping/app/templates/application.gts b/examples/ember/grouping/app/templates/application.gts index 3d4b123b85..8b5e347f14 100644 --- a/examples/ember/grouping/app/templates/application.gts +++ b/examples/ember/grouping/app/templates/application.gts @@ -5,8 +5,6 @@ import { useTable, FlexRenderCell, FlexRenderHeader, - flexRender, - FlexRenderComponentConfig, tableFeatures, columnGroupingFeature, rowExpandingFeature, @@ -18,9 +16,6 @@ import { createPaginatedRowModel, createFilteredRowModel, createSortedRowModel, - aggregationFn_mean, - aggregationFn_median, - aggregationFn_sum, filterFn_includesString, filterFn_inNumberRange, sortFn_alphanumeric, @@ -29,11 +24,9 @@ import { type Column, type Row, type Cell, - type CellContext, type GroupingState, type SortingState, } from '@tanstack/ember-table' -import type { ComponentLike, ContentValue } from '@glint/template' import { makeData, type Person } from '../utils/make-data' const features = tableFeatures({ @@ -47,11 +40,6 @@ const features = tableFeatures({ paginatedRowModel: createPaginatedRowModel(), filteredRowModel: createFilteredRowModel(), sortedRowModel: createSortedRowModel(), - aggregationFns: { - mean: aggregationFn_mean, - median: aggregationFn_median, - sum: aggregationFn_sum, - }, filterFns: { includesString: filterFn_includesString, inNumberRange: filterFn_inNumberRange, @@ -85,18 +73,12 @@ const columns = columnHelper.columns([ columns: columnHelper.columns([ columnHelper.accessor('age', { header: () => 'Age', - aggregationFn: 'median', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, }), columnHelper.group({ header: 'More Info', columns: columnHelper.columns([ columnHelper.accessor('visits', { header: () => 'Visits', - aggregationFn: 'sum', - aggregatedCell: ({ getValue }) => - getValue().toLocaleString(), }), columnHelper.accessor('status', { header: 'Status', @@ -105,9 +87,6 @@ const columns = columnHelper.columns([ header: 'Profile Progress', cell: ({ getValue }) => `${Math.round(getValue() * 100) / 100}%`, - aggregationFn: 'mean', - aggregatedCell: ({ getValue }) => - `${Math.round(getValue() * 100) / 100}%`, }), ]), }), @@ -117,83 +96,6 @@ const columns = columnHelper.columns([ const PAGE_SIZES = [10, 20, 30, 40, 50] -// --- Aggregated cell renderer (renders aggregatedCell ?? cell) --- - -type RenderOptions = Record | undefined - -interface AggregatedCellSignature { - Args: { cell: Cell } - Element: null -} - -class FlexRenderAggregatedCell extends Component { - get result(): - | string - | number - | null - | FlexRenderComponentConfig { - const cell = this.args.cell - const def = - cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell - return flexRender(def, cell.getContext()) as - | string - | number - | null - | FlexRenderComponentConfig - } - - get resolvedContext(): CellContext { - return this.args.cell.getContext() - } - - get isComponent(): boolean { - return this.result instanceof FlexRenderComponentConfig - } - - get componentToRender(): - | ComponentLike<{ - Args: { - ctx: CellContext - options?: RenderOptions - } - }> - | undefined { - const result = this.result - if (result instanceof FlexRenderComponentConfig) { - return result.component as ComponentLike<{ - Args: { - ctx: CellContext - options?: RenderOptions - } - }> - } - return undefined - } - - get componentOptions(): RenderOptions { - const result = this.result - if (result instanceof FlexRenderComponentConfig) { - return result.options - } - return undefined - } - - get content(): ContentValue { - return this.result as ContentValue - } - - -} - // --- Template helpers (v9 methods need explicit `this` binding) --- const getAllCells = ( @@ -215,12 +117,12 @@ const getGroupedIndex = (column: Column): number => column.getGroupedIndex() const cellIsGrouped = (cell: Cell): boolean => cell.getIsGrouped() -const cellIsAggregated = (cell: Cell): boolean => - cell.getIsAggregated() const cellIsPlaceholder = (cell: Cell): boolean => cell.getIsPlaceholder() const rowIsExpanded = (row: Row): boolean => row.getIsExpanded() +const rowIsGrouped = (row: Row): boolean => + row.getIsGrouped() const rowCanExpand = (row: Row): boolean => row.getCanExpand() const subRowCount = (row: Row): string => @@ -259,7 +161,7 @@ export default class GroupingTable extends Component { // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom // enableGrouping: false, // disable grouping for every column; default true // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' - // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + // manualGrouping: true, // pass rows that are already grouped, for example from a server })) get headerGroups() { @@ -374,7 +276,6 @@ export default class GroupingTable extends Component { {{#each (getAllCells row) as |cell|}} {{#if (cellIsGrouped cell)}} @@ -386,11 +287,9 @@ export default class GroupingTable extends Component { ({{subRowCount row}}) - {{else if (cellIsAggregated cell)}} - {{else if (cellIsPlaceholder cell)}} {{! placeholder cell - render nothing }} - {{else}} + {{else if (not (rowIsGrouped row))}} {{/if}} diff --git a/examples/ember/grouping/tests/e2e/smoke.spec.ts b/examples/ember/grouping/tests/e2e/smoke.spec.ts index 6188138993..275770642a 100644 --- a/examples/ember/grouping/tests/e2e/smoke.spec.ts +++ b/examples/ember/grouping/tests/e2e/smoke.spec.ts @@ -79,6 +79,8 @@ test('renders the table without crashing', async ({ page }) => { await expect(table).toBeVisible() await expect(getHeaderCells(table).first()).toBeVisible() await expect(bodyRows.first()).toBeVisible() + await expect(table.locator('tfoot')).toHaveCount(0) + await expect(table).not.toContainText('Grand Total') const regenerateButton = page .getByRole('button', { name: /^Regenerate Data$/i }) diff --git a/examples/lit/aggregation/.devcontainer/devcontainer.json b/examples/lit/aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/lit/aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/lit/aggregation/index.html b/examples/lit/aggregation/index.html new file mode 100644 index 0000000000..7bb0fed59a --- /dev/null +++ b/examples/lit/aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Aggregation + + +
+ + + + diff --git a/examples/lit/aggregation/package.json b/examples/lit/aggregation/package.json new file mode 100644 index 0000000000..894369fe45 --- /dev/null +++ b/examples/lit/aggregation/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-lit-table-example-aggregation", + "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-table": "^9.0.0-beta.47", + "lit": "^3.3.3" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/lit/aggregation/src/index.css b/examples/lit/aggregation/src/index.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/lit/aggregation/src/index.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/lit/aggregation/src/main.ts b/examples/lit/aggregation/src/main.ts new file mode 100644 index 0000000000..2704b569bd --- /dev/null +++ b/examples/lit/aggregation/src/main.ts @@ -0,0 +1,570 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + aggregationFeature, + aggregationFn_count, + aggregationFn_extent, + aggregationFn_mean, + aggregationFn_sum, + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFn_includesString, + metaHelper, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { Sale } from './makeData' +import type { Table } from '@tanstack/lit-table' + +type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected' +type AggregationTableMeta = { rowSource: RowSource } + +const features = tableFeatures({ + aggregationFeature, + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + filteredRowModel: createFilteredRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filterFns: { includesString: filterFn_includesString }, + aggregationFns: { + count: aggregationFn_count, + extent: aggregationFn_extent, + mean: aggregationFn_mean, + sum: aggregationFn_sum, + }, + tableMeta: metaHelper(), +}) + +const columnHelper = createColumnHelper() + +const formatAggregationValue = (value: unknown): string => { + if (Array.isArray(value)) return value.map(formatAggregationValue).join(' โ€“ ') + if (value && typeof value === 'object') { + return Object.entries(value) + .map(([key, entry]) => `${key}: ${formatAggregationValue(entry)}`) + .join(', ') + } + return typeof value === 'number' + ? value.toLocaleString(undefined, { maximumFractionDigits: 2 }) + : String(value ?? 'โ€”') +} + +function getAggregationRows(table: Table) { + const source = table.options.meta?.rowSource + if (source === 'all') return table.getCoreRowModel().rows + if (source === 'page') return table.getRowModel().rows + if (source === 'selected') return table.getFilteredSelectedRowModel().rows + if (source === 'custom') return table.getCoreRowModel().rows.slice(0, 3) + return undefined +} + +const columns = columnHelper.columns([ + columnHelper.display({ id: 'select' }), + columnHelper.accessor('category', { + header: 'Category', + filterFn: 'includesString', + }), + columnHelper.accessor('item', { + header: 'Item', + footer: ({ table }) => `${table.options.meta?.rowSource} total`, + }), + columnHelper.accessor('amount', { + header: 'Amount', + aggregationFn: 'sum', + cell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column, table }) => + formatAggregationValue( + column.getAggregationValue(getAggregationRows(table)), + ), + }), + columnHelper.accessor('score', { + header: 'Score', + aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }], + footer: ({ column, table }) => + formatAggregationValue( + column.getAggregationValue(getAggregationRows(table)), + ), + }), +]) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(10_000) + + @state() + private _rowSource: RowSource = 'filtered' + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + features, + columns, + data: this._data, + meta: { rowSource: this._rowSource }, + initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, + // initialState: { pagination: { pageIndex: 1, pageSize: 20 } }, // set the initial page once + // atoms: { pagination: paginationAtom }, // preferred: own pagination state with an external atom + // state: { pagination }, // classic controlled state; pair with onPaginationChange + // onPaginationChange: setPagination, + // autoResetPageIndex: false, // keep the current page after page-altering changes; default true + // autoResetAll: false, // turn off every feature's automatic reset, including page index + // manualPagination: true, // pass data that is already paginated, for example from a server + // pageCount: 10, // total pages for manual pagination; use -1 when unknown + // rowCount: 1_000, // total rows for manual pagination; pageCount is calculated from this and pageSize + debugTable: true, + }, + (state) => ({ pagination: state.pagination }), + ) + + return html` +
+

Aggregation without grouping

+

+ Amount uses a scalar sum. Score runs count, mean, and + range together and returns a keyed object. +

+
+ + +
+
+
+ + +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map( + (cell) => html` + + `, + )} + + `, + )} + + + ${table.getFooterGroups().map( + (footerGroup) => html` + + ${footerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + +
+ ${header.column.id === 'select' + ? html` table.toggleAllPageRowsSelected()} + />` + : header.isPlaceholder + ? null + : html`
${FlexRender({ header })}
`} +
+ ${cell.column.id === 'select' + ? html` row.toggleSelected()} + />` + : FlexRender({ cell })} +
+ ${header.isPlaceholder + ? null + : FlexRender({ footer: header })} +
+
+
+ + + + + +
Page
+ + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + +
+ + | Go to page: + + + +
+
+ Showing ${table.getRowModel().rows.length.toLocaleString()} of + ${table.getRowCount().toLocaleString()} Rows +
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/aggregation/src/makeData.ts b/examples/lit/aggregation/src/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/lit/aggregation/src/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/lit/aggregation/tests/e2e/smoke.spec.ts b/examples/lit/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/lit/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/aggregation/tsconfig.json b/examples/lit/aggregation/tsconfig.json new file mode 100644 index 0000000000..f24d433186 --- /dev/null +++ b/examples/lit/aggregation/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "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/aggregation/vite.config.js b/examples/lit/aggregation/vite.config.js new file mode 100644 index 0000000000..16eb98bb27 --- /dev/null +++ b/examples/lit/aggregation/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +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/lit/grouped-aggregation/.devcontainer/devcontainer.json b/examples/lit/grouped-aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/lit/grouped-aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/lit/grouped-aggregation/index.html b/examples/lit/grouped-aggregation/index.html new file mode 100644 index 0000000000..7f0b117d33 --- /dev/null +++ b/examples/lit/grouped-aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Grouped Aggregation + + +
+ + + + diff --git a/examples/lit/grouped-aggregation/package.json b/examples/lit/grouped-aggregation/package.json new file mode 100644 index 0000000000..730d1ece5a --- /dev/null +++ b/examples/lit/grouped-aggregation/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-lit-table-example-grouped-aggregation", + "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-table": "^9.0.0-beta.47", + "lit": "^3.3.3" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/lit/grouped-aggregation/src/main.ts b/examples/lit/grouped-aggregation/src/main.ts new file mode 100644 index 0000000000..cab89ec6fe --- /dev/null +++ b/examples/lit/grouped-aggregation/src/main.ts @@ -0,0 +1,555 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + aggregationFeature, + aggregationFn_mean, + aggregationFn_median, + aggregationFn_sum, + columnFilteringFeature, + columnGroupingFeature, + createColumnHelper, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFn_inNumberRange, + filterFn_includesString, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_text, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const features = tableFeatures({ + aggregationFeature, + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(), + groupedRowModel: createGroupedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(), + filterFns: { + includesString: filterFn_includesString, + inNumberRange: filterFn_inNumberRange, + }, + sortFns: { + alphanumeric: sortFn_alphanumeric, + text: sortFn_text, + }, + aggregationFns: { + mean: aggregationFn_mean, + median: aggregationFn_median, + sum: aggregationFn_sum, + }, +}) + +const columnHelper = createColumnHelper() + +const columns: Array> = columnHelper.columns( + [ + columnHelper.accessor('firstName', { + header: 'First Name', + footer: 'Grand Total', + cell: (info) => info.getValue(), + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => html`Last Name`, + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? '' : Math.round(value * 100) / 100 + }, + }), + columnHelper.accessor('visits', { + header: () => html`Visits`, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column }) => + column.getAggregationValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? '' : `${Math.round(value * 100) / 100}%` + }, + }), + ], +) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(10_000) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + features, + columns, + data: this._data, + // initialState: { grouping: ['status'] }, // group by a column on first render + // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom + // state: { grouping }, // classic controlled state; pair with onGroupingChange + // onGroupingChange: setGrouping, + // enableGrouping: false, // disable grouping for every column; default true + // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' + // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + debugTable: true, + }, + (state) => ({ + grouping: state.grouping, + expanded: state.expanded, + pagination: state.pagination, + }), + ) + + return html` +
+
+ + +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map( + (cell) => html` + + `, + )} + + `, + )} + + + ${table.getFooterGroups().map( + (footerGroup) => html` + + ${footerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html`
+ ${header.column.getCanGroup() + ? html`` + : null} + ${FlexRender({ header })} +
`} +
+ ${cell.getIsGrouped() + ? html`` + : cell.getIsAggregated() + ? FlexRender({ cell }) + : cell.getIsPlaceholder() + ? null + : FlexRender({ cell })} +
+ ${header.isPlaceholder + ? null + : FlexRender({ footer: header })} +
+
+
+ + + + + + Page + + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + + + + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + style="width: 64px; border: 1px solid gray; padding: 2px; border-radius: 4px" + /> + + +
+
${table.getRowModel().rows.length.toLocaleString()} Rows
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/grouped-aggregation/src/makeData.ts b/examples/lit/grouped-aggregation/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/grouped-aggregation/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + 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], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/grouped-aggregation/tests/e2e/smoke.spec.ts b/examples/lit/grouped-aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/lit/grouped-aggregation/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/grouped-aggregation/tsconfig.json b/examples/lit/grouped-aggregation/tsconfig.json new file mode 100644 index 0000000000..f24d433186 --- /dev/null +++ b/examples/lit/grouped-aggregation/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "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/grouped-aggregation/vite.config.js b/examples/lit/grouped-aggregation/vite.config.js new file mode 100644 index 0000000000..9b5122fda5 --- /dev/null +++ b/examples/lit/grouped-aggregation/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/lit/grouping/src/main.ts b/examples/lit/grouping/src/main.ts index c2a87bf827..8c532dc036 100644 --- a/examples/lit/grouping/src/main.ts +++ b/examples/lit/grouping/src/main.ts @@ -4,10 +4,6 @@ import { repeat } from 'lit/directives/repeat.js' import { FlexRender, TableController, - aggregationFeature, - aggregationFn_mean, - aggregationFn_median, - aggregationFn_sum, columnFilteringFeature, columnGroupingFeature, createColumnHelper, @@ -30,7 +26,6 @@ import type { ColumnDef } from '@tanstack/lit-table' import type { Person } from './makeData' const features = tableFeatures({ - aggregationFeature, columnFilteringFeature, columnGroupingFeature, rowExpandingFeature, @@ -49,11 +44,6 @@ const features = tableFeatures({ alphanumeric: sortFn_alphanumeric, text: sortFn_text, }, - aggregationFns: { - mean: aggregationFn_mean, - median: aggregationFn_median, - sum: aggregationFn_sum, - }, }) const columnHelper = createColumnHelper() @@ -72,14 +62,9 @@ const columns: Array> = columnHelper.columns( }), columnHelper.accessor('age', { header: () => 'Age', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, - aggregationFn: 'median', }), columnHelper.accessor('visits', { header: () => html`Visits`, - aggregationFn: 'sum', - aggregatedCell: ({ getValue }) => getValue().toLocaleString(), }), columnHelper.accessor('status', { header: 'Status', @@ -87,9 +72,6 @@ const columns: Array> = columnHelper.columns( columnHelper.accessor('progress', { header: 'Profile Progress', cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', - aggregationFn: 'mean', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100 + '%', }), ], ) @@ -113,7 +95,7 @@ class LitTableExample extends LitElement { // onGroupingChange: setGrouping, // enableGrouping: false, // disable grouping for every column; default true // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' - // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + // manualGrouping: true, // pass rows that are already grouped, for example from a server debugTable: true, }, (state) => ({ @@ -185,11 +167,9 @@ class LitTableExample extends LitElement { ${cell.getIsGrouped() ? html` + +
+
+
+ + +
+
+ + + {table.getHeaderGroups().map((group) => ( + + {group.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((group) => ( + + {group.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + + table.setPageIndex( + event.currentTarget.value + ? Number(event.currentTarget.value) - 1 + : 0, + ) + } + className="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') +render(, rootElement) diff --git a/examples/preact/aggregation/src/makeData.ts b/examples/preact/aggregation/src/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/preact/aggregation/src/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/preact/aggregation/src/vite-env.d.ts b/examples/preact/aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/aggregation/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/aggregation/tests/e2e/smoke.spec.ts b/examples/preact/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/preact/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/preact/aggregation/tsconfig.json b/examples/preact/aggregation/tsconfig.json new file mode 100644 index 0000000000..6b6ab1b40a --- /dev/null +++ b/examples/preact/aggregation/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "tests/e2e", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/aggregation/vite.config.ts b/examples/preact/aggregation/vite.config.ts new file mode 100644 index 0000000000..6b46e37cc9 --- /dev/null +++ b/examples/preact/aggregation/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [preact()], +}) diff --git a/examples/preact/grouped-aggregation/.devcontainer/devcontainer.json b/examples/preact/grouped-aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/preact/grouped-aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/preact/grouped-aggregation/index.html b/examples/preact/grouped-aggregation/index.html new file mode 100644 index 0000000000..2007797741 --- /dev/null +++ b/examples/preact/grouped-aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + + TanStack Preact Table - Grouped Aggregation + + +
+ + + diff --git a/examples/preact/grouped-aggregation/package.json b/examples/preact/grouped-aggregation/package.json new file mode 100644 index 0000000000..10f536c52a --- /dev/null +++ b/examples/preact/grouped-aggregation/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-preact-table-example-grouped-aggregation", + "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-table": "^9.0.0-beta.47", + "preact": "^10.29.2" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/preact/grouped-aggregation/src/index.css b/examples/preact/grouped-aggregation/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/grouped-aggregation/src/index.css @@ -0,0 +1,365 @@ +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; +} diff --git a/examples/preact/grouped-aggregation/src/main.tsx b/examples/preact/grouped-aggregation/src/main.tsx new file mode 100644 index 0000000000..cf6af58631 --- /dev/null +++ b/examples/preact/grouped-aggregation/src/main.tsx @@ -0,0 +1,310 @@ +import { useMemo, useState } from 'preact/hooks' +import { render } from 'preact' +import './index.css' +import { + aggregationFeature, + aggregationFn_mean, + aggregationFn_median, + aggregationFn_sum, + columnFilteringFeature, + columnGroupingFeature, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFn_inNumberRange, + filterFn_includesString, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_text, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// this example happens to use the createTableHook pattern, but it is not required +const { useAppTable, createAppColumnHelper } = createTableHook({ + features: { + aggregationFeature, + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(), + groupedRowModel: createGroupedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(), + filterFns: { + includesString: filterFn_includesString, + inNumberRange: filterFn_inNumberRange, + }, + sortFns: { + alphanumeric: sortFn_alphanumeric, + text: sortFn_text, + }, + aggregationFns: { + mean: aggregationFn_mean, + median: aggregationFn_median, + sum: aggregationFn_sum, + }, + }, +}) + +const columnHelper = createAppColumnHelper() + +function App() { + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + footer: 'Grand Total', + cell: (info) => info.getValue(), + /** + * override the value used for row grouping + * (otherwise, defaults to the value derived from accessorKey / accessorFn) + */ + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? null : Math.round(value * 100) / 100 + }, + }), + columnHelper.accessor('visits', { + header: () => Visits, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column }) => + column.getAggregationValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined + ? null + : `${Math.round(value * 100) / 100}%` + }, + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(10_000)) + const refreshData = () => setData(() => makeData(10_000)) + const stressTest = () => setData(() => makeData(200_000)) + + const table = useAppTable( + { + columns, + data, + // initialState: { grouping: ['status'] }, // group by a column on first render + // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom + // state: { grouping }, // classic controlled state; pair with onGroupingChange + // onGroupingChange: setGrouping, + // enableGrouping: false, // disable grouping for every column; default true + // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' + // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + debugTable: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ {header.column.getCanGroup() ? ( + // If the header can be grouped, let's add a toggle + + ) : null}{' '} + +
+ )} +
+ {cell.getIsGrouped() ? ( + // If it's a grouped cell, add an expander and row count + <> + + + ) : cell.getIsAggregated() ? ( + // If the cell is aggregated, use the Aggregated + // renderer for cell + + ) : cell.getIsPlaceholder() ? null : ( // For cells with repeated values, render null + // Otherwise, just render the regular cell + + )} +
+ {header.isPlaceholder ? null : ( + + )} +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/grouped-aggregation/src/makeData.ts b/examples/preact/grouped-aggregation/src/makeData.ts new file mode 100644 index 0000000000..95302cdf16 --- /dev/null +++ b/examples/preact/grouped-aggregation/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + 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 { + 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], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/grouped-aggregation/src/vite-env.d.ts b/examples/preact/grouped-aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/grouped-aggregation/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/grouped-aggregation/tests/e2e/smoke.spec.ts b/examples/preact/grouped-aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/preact/grouped-aggregation/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/preact/grouped-aggregation/tsconfig.json b/examples/preact/grouped-aggregation/tsconfig.json new file mode 100644 index 0000000000..6b6ab1b40a --- /dev/null +++ b/examples/preact/grouped-aggregation/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "tests/e2e", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/grouped-aggregation/vite.config.ts b/examples/preact/grouped-aggregation/vite.config.ts new file mode 100644 index 0000000000..6b46e37cc9 --- /dev/null +++ b/examples/preact/grouped-aggregation/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [preact()], +}) diff --git a/examples/preact/grouping/src/main.tsx b/examples/preact/grouping/src/main.tsx index 691237b6a9..10a127b651 100644 --- a/examples/preact/grouping/src/main.tsx +++ b/examples/preact/grouping/src/main.tsx @@ -2,10 +2,6 @@ import { useMemo, useState } from 'preact/hooks' import { render } from 'preact' import './index.css' import { - aggregationFeature, - aggregationFn_mean, - aggregationFn_median, - aggregationFn_sum, columnFilteringFeature, columnGroupingFeature, createExpandedRowModel, @@ -28,7 +24,6 @@ import type { Person } from './makeData' // this example happens to use the createTableHook pattern, but it is not required const { useAppTable, createAppColumnHelper } = createTableHook({ features: { - aggregationFeature, columnFilteringFeature, columnGroupingFeature, rowExpandingFeature, @@ -47,11 +42,6 @@ const { useAppTable, createAppColumnHelper } = createTableHook({ alphanumeric: sortFn_alphanumeric, text: sortFn_text, }, - aggregationFns: { - mean: aggregationFn_mean, - median: aggregationFn_median, - sum: aggregationFn_sum, - }, }, }) @@ -77,14 +67,9 @@ function App() { }), columnHelper.accessor('age', { header: () => 'Age', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, - aggregationFn: 'median', }), columnHelper.accessor('visits', { header: () => Visits, - aggregationFn: 'sum', - aggregatedCell: ({ getValue }) => getValue().toLocaleString(), }), columnHelper.accessor('status', { header: 'Status', @@ -93,9 +78,6 @@ function App() { header: 'Profile Progress', cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', - aggregationFn: 'mean', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100 + '%', }), ]), [], @@ -115,7 +97,7 @@ function App() { // onGroupingChange: setGrouping, // enableGrouping: false, // disable grouping for every column; default true // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' - // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + // manualGrouping: true, // pass rows that are already grouped, for example from a server debugTable: true, }, (state) => state, // default selector @@ -168,11 +150,9 @@ function App() { style={{ background: cell.getIsGrouped() ? '#0aff0082' - : cell.getIsAggregated() - ? '#ffa50078' - : cell.getIsPlaceholder() - ? '#ff000042' - : 'white', + : cell.getIsPlaceholder() + ? '#ff000042' + : 'white', }} > {cell.getIsGrouped() ? ( @@ -189,11 +169,7 @@ function App() { {row.subRows.length.toLocaleString()}) - ) : cell.getIsAggregated() ? ( - // If the cell is aggregated, use the Aggregated - // renderer for cell - - ) : cell.getIsPlaceholder() ? null : ( // For cells with repeated values, render null + ) : cell.getIsPlaceholder() ? null : row.getIsGrouped() ? null : ( // For cells with repeated values, render null // Otherwise, just render the regular cell )} diff --git a/examples/preact/grouping/tests/e2e/smoke.spec.ts b/examples/preact/grouping/tests/e2e/smoke.spec.ts index 6188138993..275770642a 100644 --- a/examples/preact/grouping/tests/e2e/smoke.spec.ts +++ b/examples/preact/grouping/tests/e2e/smoke.spec.ts @@ -79,6 +79,8 @@ test('renders the table without crashing', async ({ page }) => { await expect(table).toBeVisible() await expect(getHeaderCells(table).first()).toBeVisible() await expect(bodyRows.first()).toBeVisible() + await expect(table.locator('tfoot')).toHaveCount(0) + await expect(table).not.toContainText('Grand Total') const regenerateButton = page .getByRole('button', { name: /^Regenerate Data$/i }) diff --git a/examples/react/grouping/src/main.tsx b/examples/react/grouping/src/main.tsx index c78d2096b2..8c295e0032 100644 --- a/examples/react/grouping/src/main.tsx +++ b/examples/react/grouping/src/main.tsx @@ -97,7 +97,7 @@ function App() { // onGroupingChange: setGrouping, // enableGrouping: false, // disable grouping for every column; default true // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' - // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + // manualGrouping: true, // pass rows that are already grouped, for example from a server debugTable: true, }, (state) => state, // default selector diff --git a/examples/react/grouping/tests/e2e/smoke.spec.ts b/examples/react/grouping/tests/e2e/smoke.spec.ts index 2eb199bf28..51cf203b2f 100644 --- a/examples/react/grouping/tests/e2e/smoke.spec.ts +++ b/examples/react/grouping/tests/e2e/smoke.spec.ts @@ -71,6 +71,8 @@ test('regenerates table data', async ({ page }) => { await expect(table).toBeVisible() await expect(bodyRows.first()).toBeVisible() + await expect(table.locator('tfoot')).toHaveCount(0) + await expect(table).not.toContainText('Grand Total') await expect(regenerateButton).toBeVisible() const firstRowBefore = await getFirstBodyRowText(table) diff --git a/examples/solid/aggregation/.devcontainer/devcontainer.json b/examples/solid/aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/solid/aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/solid/aggregation/index.html b/examples/solid/aggregation/index.html new file mode 100644 index 0000000000..cd343fab0f --- /dev/null +++ b/examples/solid/aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Solid Table - Aggregation + + + +
+ + + diff --git a/examples/solid/aggregation/package.json b/examples/solid/aggregation/package.json new file mode 100644 index 0000000000..40bffee11c --- /dev/null +++ b/examples/solid/aggregation/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-solid-table-example-aggregation", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src", + "test:types": "tsc --noEmit", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.5.0", + "typescript": "6.0.3", + "vite": "^8.1.0", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-beta.47", + "solid-js": "^1.9.13" + } +} diff --git a/examples/solid/aggregation/src/App.tsx b/examples/solid/aggregation/src/App.tsx new file mode 100644 index 0000000000..8902d01187 --- /dev/null +++ b/examples/solid/aggregation/src/App.tsx @@ -0,0 +1,323 @@ +import { + aggregationFeature, + aggregationFn_count, + aggregationFn_extent, + aggregationFn_mean, + aggregationFn_sum, + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createTable, + filterFn_includesString, + metaHelper, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Sale } from './makeData' +import type { Table } from '@tanstack/solid-table' + +type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected' +type AggregationTableMeta = { rowSource: RowSource } +const features = tableFeatures({ + aggregationFeature, + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + filteredRowModel: createFilteredRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filterFns: { includesString: filterFn_includesString }, + aggregationFns: { + count: aggregationFn_count, + extent: aggregationFn_extent, + mean: aggregationFn_mean, + sum: aggregationFn_sum, + }, + tableMeta: metaHelper(), +}) +const columnHelper = createColumnHelper() +function getAggregationRows( + table: Table, + source = table.options.meta?.rowSource, +) { + if (source === 'all') return table.getCoreRowModel().rows + if (source === 'page') return table.getRowModel().rows + if (source === 'selected') return table.getFilteredSelectedRowModel().rows + if (source === 'custom') return table.getCoreRowModel().rows.slice(0, 3) + return undefined +} +function formatValue(value: unknown): string { + if (Array.isArray(value)) return value.map(formatValue).join(' โ€“ ') + if (value && typeof value === 'object') + return Object.entries(value) + .map(([key, entry]) => `${key}: ${formatValue(entry)}`) + .join(', ') + if (typeof value === 'number') + return value.toLocaleString(undefined, { maximumFractionDigits: 2 }) + return String(value ?? 'โ€”') +} + +function App() { + const [data, setData] = createSignal(makeData(10_000)) + const [rowSource, setRowSource] = createSignal('filtered') + const columns = columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }), + columnHelper.accessor('category', { + header: 'Category', + filterFn: 'includesString', + }), + columnHelper.accessor('item', { + header: 'Item', + footer: () => `${rowSource()} total`, + }), + columnHelper.accessor('amount', { + header: 'Amount', + aggregationFn: 'sum', + cell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column, table }) => + formatValue( + column.getAggregationValue(getAggregationRows(table, rowSource())), + ), + }), + columnHelper.accessor('score', { + header: 'Score', + aggregationFn: [ + 'count', + 'mean', + { id: 'range', aggregationFn: 'extent' }, + ], + footer: ({ column, table }) => + formatValue( + column.getAggregationValue(getAggregationRows(table, rowSource())), + ), + }), + ]) + const table = createTable({ + features, + columns, + get data() { + return data() + }, + get meta() { + return { rowSource: rowSource() } + }, + initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, + debugTable: true, + debugColumns: true, + }) + const changeRowSource = (value: RowSource) => { + setRowSource(value) + table.setOptions((previous) => ({ + ...previous, + meta: { rowSource: value }, + })) + } + const changeData = (value: Array) => { + setData(value) + table.setOptions((previous) => ({ ...previous, data: value })) + } + return ( +
+

Aggregation without grouping

+

+ Amount uses a scalar sum. Score runs count, mean, and range + together and returns a keyed object. +

+
+ + +
+
+
+ + +
+
+ + + + {(group) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + + + + {(group) => ( + + + {(header) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : header.column.id === + 'item' ? ( + `${rowSource()} total` + ) : header.column.id === 'amount' || + header.column.id === 'score' ? ( + formatValue( + header.column.getAggregationValue( + getAggregationRows(table, rowSource()), + ), + ) + ) : ( + + )} +
+
+
+ + + + + +
Page
+ + {(table.atoms.pagination.get().pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + + table.setPageIndex( + event.currentTarget.value + ? Number(event.currentTarget.value) - 1 + : 0, + ) + } + class="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
{JSON.stringify(table.store.get(), null, 2)}
+
+ ) +} +export default App diff --git a/examples/solid/aggregation/src/index.css b/examples/solid/aggregation/src/index.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/solid/aggregation/src/index.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/solid/aggregation/src/index.tsx b/examples/solid/aggregation/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/aggregation/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/aggregation/src/makeData.ts b/examples/solid/aggregation/src/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/solid/aggregation/src/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/solid/aggregation/src/vite-env.d.ts b/examples/solid/aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/solid/aggregation/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/solid/aggregation/tests/e2e/smoke.spec.ts b/examples/solid/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/solid/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/solid/aggregation/tsconfig.json b/examples/solid/aggregation/tsconfig.json new file mode 100644 index 0000000000..e408edcdc1 --- /dev/null +++ b/examples/solid/aggregation/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "tests/e2e", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/aggregation/vite.config.ts b/examples/solid/aggregation/vite.config.ts new file mode 100644 index 0000000000..ae07c754c1 --- /dev/null +++ b/examples/solid/aggregation/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/grouped-aggregation/.devcontainer/devcontainer.json b/examples/solid/grouped-aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/solid/grouped-aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/solid/grouped-aggregation/index.html b/examples/solid/grouped-aggregation/index.html new file mode 100644 index 0000000000..61753342f8 --- /dev/null +++ b/examples/solid/grouped-aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Solid Table - Grouped Aggregation + + + +
+ + + diff --git a/examples/solid/grouped-aggregation/package.json b/examples/solid/grouped-aggregation/package.json new file mode 100644 index 0000000000..42334e8352 --- /dev/null +++ b/examples/solid/grouped-aggregation/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-solid-table-example-grouped-aggregation", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src", + "test:types": "tsc --noEmit", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.5.0", + "typescript": "6.0.3", + "vite": "^8.1.0", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-beta.47", + "solid-js": "^1.9.13" + } +} diff --git a/examples/solid/grouped-aggregation/src/App.tsx b/examples/solid/grouped-aggregation/src/App.tsx new file mode 100644 index 0000000000..c0ca35bc58 --- /dev/null +++ b/examples/solid/grouped-aggregation/src/App.tsx @@ -0,0 +1,291 @@ +import { + aggregationFeature, + aggregationFn_mean, + aggregationFn_median, + aggregationFn_sum, + columnFilteringFeature, + columnGroupingFeature, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFn_inNumberRange, + filterFn_includesString, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_text, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const { createAppTable, createAppColumnHelper } = createTableHook({ + features: { + aggregationFeature, + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(), + groupedRowModel: createGroupedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(), + filterFns: { + includesString: filterFn_includesString, + inNumberRange: filterFn_inNumberRange, + }, + sortFns: { + alphanumeric: sortFn_alphanumeric, + text: sortFn_text, + }, + aggregationFns: { + mean: aggregationFn_mean, + median: aggregationFn_median, + sum: aggregationFn_sum, + }, + }, +}) + +const columnHelper = createAppColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + footer: 'Grand Total', + cell: (info) => info.getValue(), + /** + * override the value used for row grouping + * (otherwise, defaults to the value derived from accessorKey / accessorFn) + */ + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? null : Math.round(value * 100) / 100 + }, + }), + columnHelper.accessor('visits', { + header: () => Visits, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column }) => + column.getAggregationValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + footer: ({ column }) => { + const value = column.getAggregationValue() + return value === undefined ? null : `${Math.round(value * 100) / 100}%` + }, + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(10_000)) + const refreshData = () => setData(makeData(10_000)) + const stressTest = () => setData(makeData(200_000)) + + const table = createAppTable({ + columns, + get data() { + return data() + }, + // initialState: { grouping: ['status'] }, // group by a column on first render + // atoms: { grouping: groupingAtom }, // preferred: own grouping state with an external atom + // state: { grouping }, // classic controlled state; pair with onGroupingChange + // onGroupingChange: setGrouping, + // enableGrouping: false, // disable grouping for every column; default true + // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' + // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + debugTable: true, + }) + + return ( +
+
+ + +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + + + + {(footerGroup) => ( + + + {(header) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( +
+ {header.column.getCanGroup() ? ( + + ) : null}{' '} + +
+ )} +
+ {cell.getIsGrouped() ? ( + <> + + + ) : cell.getIsAggregated() ? ( + + ) : cell.getIsPlaceholder() ? null : ( + + )} +
+ {header.isPlaceholder ? null : ( + + )} +
+
+
+ + + + + +
Page
+ + {(table.atoms.pagination.get().pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
{JSON.stringify(table.store.get(), null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/grouped-aggregation/src/index.css b/examples/solid/grouped-aggregation/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/grouped-aggregation/src/index.css @@ -0,0 +1,365 @@ +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; +} diff --git a/examples/solid/grouped-aggregation/src/index.tsx b/examples/solid/grouped-aggregation/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/grouped-aggregation/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/grouped-aggregation/src/makeData.ts b/examples/solid/grouped-aggregation/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/solid/grouped-aggregation/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + 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], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/solid/grouped-aggregation/src/vite-env.d.ts b/examples/solid/grouped-aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/solid/grouped-aggregation/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/solid/grouped-aggregation/tests/e2e/smoke.spec.ts b/examples/solid/grouped-aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/solid/grouped-aggregation/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/solid/grouped-aggregation/tsconfig.json b/examples/solid/grouped-aggregation/tsconfig.json new file mode 100644 index 0000000000..e408edcdc1 --- /dev/null +++ b/examples/solid/grouped-aggregation/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "tests/e2e", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/grouped-aggregation/vite.config.ts b/examples/solid/grouped-aggregation/vite.config.ts new file mode 100644 index 0000000000..3d89efe64b --- /dev/null +++ b/examples/solid/grouped-aggregation/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/grouping/src/App.tsx b/examples/solid/grouping/src/App.tsx index 0dc7f0c532..3fefb18011 100644 --- a/examples/solid/grouping/src/App.tsx +++ b/examples/solid/grouping/src/App.tsx @@ -1,8 +1,4 @@ import { - aggregationFeature, - aggregationFn_mean, - aggregationFn_median, - aggregationFn_sum, columnFilteringFeature, columnGroupingFeature, createExpandedRowModel, @@ -25,7 +21,6 @@ import type { Person } from './makeData' const { createAppTable, createAppColumnHelper } = createTableHook({ features: { - aggregationFeature, columnFilteringFeature, columnGroupingFeature, rowExpandingFeature, @@ -44,11 +39,6 @@ const { createAppTable, createAppColumnHelper } = createTableHook({ alphanumeric: sortFn_alphanumeric, text: sortFn_text, }, - aggregationFns: { - mean: aggregationFn_mean, - median: aggregationFn_median, - sum: aggregationFn_sum, - }, }, }) @@ -71,14 +61,9 @@ const columns = columnHelper.columns([ }), columnHelper.accessor('age', { header: () => 'Age', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, - aggregationFn: 'median', }), columnHelper.accessor('visits', { header: () => Visits, - aggregationFn: 'sum', - aggregatedCell: ({ getValue }) => getValue().toLocaleString(), }), columnHelper.accessor('status', { header: 'Status', @@ -86,9 +71,6 @@ const columns = columnHelper.columns([ columnHelper.accessor('progress', { header: 'Profile Progress', cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', - aggregationFn: 'mean', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100 + '%', }), ]) @@ -108,7 +90,7 @@ function App() { // onGroupingChange: setGrouping, // enableGrouping: false, // disable grouping for every column; default true // groupedColumnMode: 'remove', // remove grouped columns instead of moving them to the start; default 'reorder' - // manualGrouping: true, // pass rows that are already grouped and aggregated, for example from a server + // manualGrouping: true, // pass rows that are already grouped, for example from a server debugTable: true, }) @@ -159,11 +141,9 @@ function App() { style={{ background: cell.getIsGrouped() ? '#0aff0082' - : cell.getIsAggregated() - ? '#ffa50078' - : cell.getIsPlaceholder() - ? '#ff000042' - : 'white', + : cell.getIsPlaceholder() + ? '#ff000042' + : 'white', }} > {cell.getIsGrouped() ? ( @@ -179,9 +159,7 @@ function App() { {row.subRows.length.toLocaleString()}) - ) : cell.getIsAggregated() ? ( - - ) : cell.getIsPlaceholder() ? null : ( + ) : cell.getIsPlaceholder() ? null : row.getIsGrouped() ? null : ( )} diff --git a/examples/solid/grouping/tests/e2e/smoke.spec.ts b/examples/solid/grouping/tests/e2e/smoke.spec.ts index 6188138993..275770642a 100644 --- a/examples/solid/grouping/tests/e2e/smoke.spec.ts +++ b/examples/solid/grouping/tests/e2e/smoke.spec.ts @@ -79,6 +79,8 @@ test('renders the table without crashing', async ({ page }) => { await expect(table).toBeVisible() await expect(getHeaderCells(table).first()).toBeVisible() await expect(bodyRows.first()).toBeVisible() + await expect(table.locator('tfoot')).toHaveCount(0) + await expect(table).not.toContainText('Grand Total') const regenerateButton = page .getByRole('button', { name: /^Regenerate Data$/i }) diff --git a/examples/svelte/aggregation/.devcontainer/devcontainer.json b/examples/svelte/aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/svelte/aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/svelte/aggregation/.gitignore b/examples/svelte/aggregation/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/aggregation/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/aggregation/index.html b/examples/svelte/aggregation/index.html new file mode 100644 index 0000000000..1b3241d10b --- /dev/null +++ b/examples/svelte/aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Svelte Table - Aggregation + + + +
+ + + diff --git a/examples/svelte/aggregation/package.json b/examples/svelte/aggregation/package.json new file mode 100644 index 0000000000..0226549b8a --- /dev/null +++ b/examples/svelte/aggregation/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-svelte-table-example-aggregation", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.5.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-beta.47", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.56.2", + "svelte-check": "^4.6.0", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/svelte/aggregation/src/App.svelte b/examples/svelte/aggregation/src/App.svelte new file mode 100644 index 0000000000..8d55707e4e --- /dev/null +++ b/examples/svelte/aggregation/src/App.svelte @@ -0,0 +1,74 @@ + + +
+

Aggregation without grouping

+

Amount uses a scalar sum. Score runs count, mean, and range together and returns a keyed object.

+
+
+
+ + +
+
+ + {#each table.getHeaderGroups() as group (group.id)}{#each group.headers as header (header.id)}{/each}{/each} + {#each table.getRowModel().rows as row (row.id)}{#each row.getAllCells() as cell (cell.id)}{/each}{/each} + {#each table.getFooterGroups() as group (group.id)}{#each group.headers as header (header.id)}{/each}{/each} +
{#if header.column.id === 'select'} table.toggleAllPageRowsSelected()} />{:else if !header.isPlaceholder}{/if}
{#if cell.column.id === 'select'} row.toggleSelected()} />{:else}{/if}
{#if !header.isPlaceholder}{/if}
+
+
+
Page
{(table.state.pagination.pageIndex + 1).toLocaleString()} of {table.getPageCount().toLocaleString()}
+ | Go to page: table.setPageIndex(event.currentTarget.value ? Number(event.currentTarget.value) - 1 : 0)} class="page-size-input" /> + +
+
Showing {table.getRowModel().rows.length.toLocaleString()} of {table.getRowCount().toLocaleString()} Rows
{JSON.stringify(table.state, null, 2)}
+
diff --git a/examples/svelte/aggregation/src/index.css b/examples/svelte/aggregation/src/index.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/svelte/aggregation/src/index.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/svelte/aggregation/src/main.ts b/examples/svelte/aggregation/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/aggregation/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/aggregation/src/makeData.ts b/examples/svelte/aggregation/src/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/svelte/aggregation/src/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/svelte/aggregation/svelte.config.js b/examples/svelte/aggregation/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/aggregation/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/aggregation/tests/e2e/smoke.spec.ts b/examples/svelte/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/svelte/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/svelte/aggregation/tsconfig.json b/examples/svelte/aggregation/tsconfig.json new file mode 100644 index 0000000000..4a0b0b54b3 --- /dev/null +++ b/examples/svelte/aggregation/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/aggregation/vite.config.js b/examples/svelte/aggregation/vite.config.js new file mode 100644 index 0000000000..3c3718bc18 --- /dev/null +++ b/examples/svelte/aggregation/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/svelte/grouped-aggregation/.devcontainer/devcontainer.json b/examples/svelte/grouped-aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/svelte/grouped-aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/svelte/grouped-aggregation/.gitignore b/examples/svelte/grouped-aggregation/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/grouped-aggregation/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/grouped-aggregation/index.html b/examples/svelte/grouped-aggregation/index.html new file mode 100644 index 0000000000..4991a46198 --- /dev/null +++ b/examples/svelte/grouped-aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Svelte Table - Grouped Aggregation + + + +
+ + + diff --git a/examples/svelte/grouped-aggregation/package.json b/examples/svelte/grouped-aggregation/package.json new file mode 100644 index 0000000000..ed08259c3f --- /dev/null +++ b/examples/svelte/grouped-aggregation/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-svelte-table-example-grouped-aggregation", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.5.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-beta.47", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.56.2", + "svelte-check": "^4.6.0", + "typescript": "6.0.3", + "vite": "^8.1.0" + } +} diff --git a/examples/svelte/grouped-aggregation/src/App.svelte b/examples/svelte/grouped-aggregation/src/App.svelte new file mode 100644 index 0000000000..13d3283dfa --- /dev/null +++ b/examples/svelte/grouped-aggregation/src/App.svelte @@ -0,0 +1,275 @@ + + +
+
+ + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + + + {#each table.getFooterGroups() as footerGroup (footerGroup.id)} + + {#each footerGroup.headers as header (header.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} +
+ {#if header.column.getCanGroup()} + + {/if} + {' '} + +
+ {/if} +
+ {#if cell.getIsGrouped()} + + {:else if cell.getIsAggregated()} + + {:else if !cell.getIsPlaceholder()} + + {/if} +
+ {#if !header.isPlaceholder} + + {/if} +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
{JSON.stringify(table.state, null, 2)}
+
diff --git a/examples/svelte/grouped-aggregation/src/index.css b/examples/svelte/grouped-aggregation/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/grouped-aggregation/src/index.css @@ -0,0 +1,365 @@ +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; +} diff --git a/examples/svelte/grouped-aggregation/src/main.ts b/examples/svelte/grouped-aggregation/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/grouped-aggregation/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/grouped-aggregation/src/makeData.ts b/examples/svelte/grouped-aggregation/src/makeData.ts new file mode 100644 index 0000000000..3f9a8b9181 --- /dev/null +++ b/examples/svelte/grouped-aggregation/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + 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 { + 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], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/svelte/grouped-aggregation/svelte.config.js b/examples/svelte/grouped-aggregation/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/grouped-aggregation/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/grouped-aggregation/tests/e2e/smoke.spec.ts b/examples/svelte/grouped-aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..6188138993 --- /dev/null +++ b/examples/svelte/grouped-aggregation/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/svelte/grouped-aggregation/tsconfig.json b/examples/svelte/grouped-aggregation/tsconfig.json new file mode 100644 index 0000000000..4a0b0b54b3 --- /dev/null +++ b/examples/svelte/grouped-aggregation/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/grouped-aggregation/vite.config.js b/examples/svelte/grouped-aggregation/vite.config.js new file mode 100644 index 0000000000..3c3718bc18 --- /dev/null +++ b/examples/svelte/grouped-aggregation/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/svelte/grouping/src/App.svelte b/examples/svelte/grouping/src/App.svelte index 0b25ba678e..6be0676559 100644 --- a/examples/svelte/grouping/src/App.svelte +++ b/examples/svelte/grouping/src/App.svelte @@ -1,9 +1,5 @@ + + diff --git a/examples/vanilla/aggregation/package.json b/examples/vanilla/aggregation/package.json new file mode 100644 index 0000000000..d4c0901277 --- /dev/null +++ b/examples/vanilla/aggregation/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-vanilla-table-example-aggregation", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "test:types": "tsc --noEmit", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "devDependencies": { + "@faker-js/faker": "^10.5.0", + "@rollup/plugin-replace": "^6.0.3", + "@types/node": "^26.0.0", + "typescript": "6.0.3", + "vite": "^8.1.0" + }, + "dependencies": { + "@tanstack/table-core": "^9.0.0-beta.47" + } +} diff --git a/examples/vanilla/aggregation/src/index.css b/examples/vanilla/aggregation/src/index.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/vanilla/aggregation/src/index.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/vanilla/aggregation/src/main.ts b/examples/vanilla/aggregation/src/main.ts new file mode 100644 index 0000000000..efbc02546e --- /dev/null +++ b/examples/vanilla/aggregation/src/main.ts @@ -0,0 +1,261 @@ +import './index.css' +import { + aggregationFeature, + aggregationFn_count, + aggregationFn_extent, + aggregationFn_mean, + aggregationFn_sum, + columnFilteringFeature, + constructTable, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFn_includesString, + metaHelper, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, +} from '@tanstack/table-core' +import { FlexRender } from '@tanstack/table-core/flex-render' +import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings' +import { makeData } from './makeData' +import type { Sale } from './makeData' +import type { Table } from '@tanstack/table-core' + +type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected' +type AggregationTableMeta = { rowSource: RowSource } +let data = makeData(10_000) +let rowSource: RowSource = 'filtered' +const features = tableFeatures({ + aggregationFeature, + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + coreReactivityFeature: storeReactivityBindings(), + filteredRowModel: createFilteredRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filterFns: { includesString: filterFn_includesString }, + aggregationFns: { + count: aggregationFn_count, + extent: aggregationFn_extent, + mean: aggregationFn_mean, + sum: aggregationFn_sum, + }, + tableMeta: metaHelper(), +}) +const columnHelper = createColumnHelper() +function getAggregationRows(table: Table) { + const source = table.options.meta?.rowSource + if (source === 'all') return table.getCoreRowModel().rows + if (source === 'page') return table.getRowModel().rows + if (source === 'selected') return table.getFilteredSelectedRowModel().rows + if (source === 'custom') return table.getCoreRowModel().rows.slice(0, 3) + return undefined +} +function formatValue(value: unknown): string { + if (Array.isArray(value)) return value.map(formatValue).join(' โ€“ ') + if (value && typeof value === 'object') + return Object.entries(value) + .map(([key, entry]) => `${key}: ${formatValue(entry)}`) + .join(', ') + if (typeof value === 'number') + return value.toLocaleString(undefined, { maximumFractionDigits: 2 }) + return String(value ?? 'โ€”') +} +const columns = columnHelper.columns([ + columnHelper.display({ id: 'select' }), + columnHelper.accessor('category', { + header: 'Category', + filterFn: 'includesString', + }), + columnHelper.accessor('item', { + header: 'Item', + footer: ({ table }) => `${table.options.meta?.rowSource} total`, + }), + columnHelper.accessor('amount', { + header: 'Amount', + aggregationFn: 'sum', + cell: ({ getValue }) => getValue().toLocaleString(), + footer: ({ column, table }) => + formatValue(column.getAggregationValue(getAggregationRows(table))), + }), + columnHelper.accessor('score', { + header: 'Score', + aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }], + footer: ({ column, table }) => + formatValue(column.getAggregationValue(getAggregationRows(table))), + }), +]) +const el = (tag: K, text?: string) => { + const node = document.createElement(tag) + if (text !== undefined) node.textContent = text + return node +} +function button(text: string, action: () => void, disabled = false) { + const node = el('button', text) + node.className = 'demo-button demo-button-sm' + node.disabled = disabled + node.onclick = action + return node +} +function renderTable(table: Table) { + const wrapper = document.getElementById('wrapper') as HTMLDivElement + wrapper.innerHTML = '' + wrapper.className = 'demo-root' + wrapper.append(el('h1', 'Aggregation without grouping')) + const description = el('p') + description.innerHTML = + 'Amount uses a scalar sum. Score runs count, mean, and range together and returns a keyed object.' + wrapper.append(description) + const actions = el('div') + actions.append( + button('Regenerate Data', () => { + data = makeData(10_000) + table.setOptions((prev) => ({ ...prev, data })) + renderTable(table) + }), + button('Stress Test (200k rows)', () => { + data = makeData(200_000) + table.setOptions((prev) => ({ ...prev, data })) + renderTable(table) + }), + ) + wrapper.append(actions, Object.assign(el('div'), { className: 'spacer-sm' })) + const controls = el('div') + controls.className = 'controls' + const filterLabel = el('label', 'Category filter: ') + const filter = el('input') + filter.value = String(table.getColumn('category')?.getFilterValue() ?? '') + filter.oninput = () => + table.getColumn('category')?.setFilterValue(filter.value) + filterLabel.append(filter) + const sourceLabel = el('label', 'Total rows: ') + const source = el('select') + const sources: Array<[RowSource, string]> = [ + ['filtered', 'Filtered rows'], + ['all', 'All rows'], + ['page', 'Visible page'], + ['selected', 'Filtered selected rows'], + ['custom', 'First three core rows'], + ] + for (const [value, label] of sources) { + const option = el('option', label) + option.value = value + option.selected = rowSource === value + source.append(option) + } + source.onchange = () => { + rowSource = source.value as RowSource + table.setOptions((prev) => ({ ...prev, meta: { rowSource } })) + renderTable(table) + } + sourceLabel.append(source) + controls.append(filterLabel, sourceLabel) + wrapper.append(controls, Object.assign(el('div'), { className: 'spacer-sm' })) + const tableElement = el('table'), + thead = el('thead'), + tbody = el('tbody'), + tfoot = el('tfoot') + tableElement.append(thead, tbody, tfoot) + for (const group of table.getHeaderGroups()) { + const tr = el('tr') + for (const header of group.headers) { + const th = el('th') + if (header.column.id === 'select') { + const input = el('input') + input.type = 'checkbox' + input.checked = table.getIsAllPageRowsSelected() + input.onchange = () => table.toggleAllPageRowsSelected() + th.append(input) + } else if (!header.isPlaceholder) + th.innerHTML = String(FlexRender({ header }) ?? '') + tr.append(th) + } + thead.append(tr) + } + for (const row of table.getRowModel().rows) { + const tr = el('tr') + for (const cell of row.getAllCells()) { + const td = el('td') + if (cell.column.id === 'amount') td.className = 'numeric' + if (cell.column.id === 'select') { + const input = el('input') + input.type = 'checkbox' + input.checked = row.getIsSelected() + input.onchange = () => row.toggleSelected() + td.append(input) + } else td.innerHTML = String(FlexRender({ cell }) ?? '') + tr.append(td) + } + tbody.append(tr) + } + for (const group of table.getFooterGroups()) { + const tr = el('tr') + for (const header of group.headers) { + const th = el('th') + th.colSpan = header.colSpan + if (!header.isPlaceholder) + th.innerHTML = String(FlexRender({ footer: header }) ?? '') + tr.append(th) + } + tfoot.append(tr) + } + wrapper.append( + tableElement, + Object.assign(el('div'), { className: 'spacer-sm' }), + ) + const pagination = el('div') + pagination.className = 'controls' + pagination.append( + button('<<', () => table.firstPage(), !table.getCanPreviousPage()), + button('<', () => table.previousPage(), !table.getCanPreviousPage()), + button('>', () => table.nextPage(), !table.getCanNextPage()), + button('>>', () => table.lastPage(), !table.getCanNextPage()), + ) + const info = el('span') + info.className = 'inline-controls' + info.innerHTML = `
Page
${table.store.state.pagination.pageIndex + 1} of ${table.getPageCount().toLocaleString()}` + pagination.append(info) + const pageLabel = el('span', '| Go to page:') + pageLabel.className = 'inline-controls' + const page = el('input') + page.type = 'number' + page.min = '1' + page.max = String(table.getPageCount()) + page.value = String(table.store.state.pagination.pageIndex + 1) + page.className = 'page-size-input' + page.oninput = () => + table.setPageIndex(page.value ? Number(page.value) - 1 : 0) + pageLabel.append(page) + pagination.append(pageLabel) + const size = el('select') + for (const value of [10, 20, 30, 40, 50]) { + const option = el('option', `Show ${value}`) + option.value = String(value) + option.selected = table.store.state.pagination.pageSize === value + size.append(option) + } + size.onchange = () => table.setPageSize(Number(size.value)) + pagination.append(size) + wrapper.append(pagination) + wrapper.append( + el( + 'div', + `Showing ${table.getRowModel().rows.length.toLocaleString()} of ${table.getRowCount().toLocaleString()} Rows`, + ), + Object.assign(el('pre'), { + textContent: JSON.stringify(table.store.state, null, 2), + }), + ) +} +const table = constructTable({ + features, + data, + columns, + meta: { rowSource }, + initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, + debugTable: true, + debugColumns: true, +}) +table.store.subscribe(() => renderTable(table)) +renderTable(table) diff --git a/examples/vanilla/aggregation/src/makeData.ts b/examples/vanilla/aggregation/src/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/vanilla/aggregation/src/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/vanilla/aggregation/src/vite-env.d.ts b/examples/vanilla/aggregation/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vanilla/aggregation/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vanilla/aggregation/tests/e2e/smoke.spec.ts b/examples/vanilla/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/vanilla/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/vanilla/aggregation/tsconfig.json b/examples/vanilla/aggregation/tsconfig.json new file mode 100644 index 0000000000..f24d433186 --- /dev/null +++ b/examples/vanilla/aggregation/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "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/vanilla/aggregation/vite.config.js b/examples/vanilla/aggregation/vite.config.js new file mode 100644 index 0000000000..9b5122fda5 --- /dev/null +++ b/examples/vanilla/aggregation/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/vue/aggregation/.devcontainer/devcontainer.json b/examples/vue/aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/vue/aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/vue/aggregation/.gitignore b/examples/vue/aggregation/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/examples/vue/aggregation/.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/aggregation/env.d.ts b/examples/vue/aggregation/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/aggregation/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/aggregation/index.html b/examples/vue/aggregation/index.html new file mode 100644 index 0000000000..1b06e67559 --- /dev/null +++ b/examples/vue/aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + + TanStack Vue Table - Aggregation + + +
+ + + diff --git a/examples/vue/aggregation/package.json b/examples/vue/aggregation/package.json new file mode 100644 index 0000000000..2514b25cc1 --- /dev/null +++ b/examples/vue/aggregation/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-vue-table-example-aggregation", + "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-table": "^9.0.0-beta.47", + "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/aggregation/src/App.vue b/examples/vue/aggregation/src/App.vue new file mode 100644 index 0000000000..a7aab75107 --- /dev/null +++ b/examples/vue/aggregation/src/App.vue @@ -0,0 +1,249 @@ + + + diff --git a/examples/vue/aggregation/src/env.d.ts b/examples/vue/aggregation/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/aggregation/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/aggregation/src/index.css b/examples/vue/aggregation/src/index.css new file mode 100644 index 0000000000..6aa3364a5e --- /dev/null +++ b/examples/vue/aggregation/src/index.css @@ -0,0 +1,87 @@ +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; + text-align: left; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +.demo-root { + padding: 0.5rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.controls, +.inline-controls { + display: flex; + align-items: center; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls { + gap: 0.25rem; +} + +.demo-button, +.page-size-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; +} + +td { + padding: 2px 4px; +} + +.numeric { + text-align: right; +} + +code { + background: #f0f0f0; + padding: 0.1rem 0.25rem; +} diff --git a/examples/vue/aggregation/src/main.ts b/examples/vue/aggregation/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/aggregation/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/aggregation/src/makeData.ts b/examples/vue/aggregation/src/makeData.ts new file mode 100644 index 0000000000..ff158ed6c6 --- /dev/null +++ b/examples/vue/aggregation/src/makeData.ts @@ -0,0 +1,21 @@ +import { faker } from '@faker-js/faker' + +export type Sale = { + category: string + item: string + amount: number + score: number +} + +const categories = ['Hardware', 'Software', 'Services'] as const + +const newSale = (): Sale => ({ + category: faker.helpers.arrayElement(categories), + item: faker.commerce.productName(), + amount: faker.number.int({ min: 25, max: 5_000 }), + score: faker.number.int({ min: 60, max: 100 }), +}) + +export function makeData(length: number): Array { + return Array.from({ length }, newSale) +} diff --git a/examples/vue/aggregation/tests/e2e/smoke.spec.ts b/examples/vue/aggregation/tests/e2e/smoke.spec.ts new file mode 100644 index 0000000000..783ec0e582 --- /dev/null +++ b/examples/vue/aggregation/tests/e2e/smoke.spec.ts @@ -0,0 +1,117 @@ +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() + await expect(page.getByLabel('Category filter:')).toBeVisible() + + const rowSource = page.getByLabel('Total rows:') + await expect(rowSource).toHaveValue('filtered') + await expect(rowSource.locator('option')).toHaveText([ + 'Filtered rows', + 'All rows', + 'Visible page', + 'Filtered selected rows', + 'First three core rows', + ]) + await rowSource.selectOption('all') + await expect(table.locator('tfoot')).toContainText('all total') + + 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/vue/aggregation/tsconfig.json b/examples/vue/aggregation/tsconfig.json new file mode 100644 index 0000000000..ab551f4269 --- /dev/null +++ b/examples/vue/aggregation/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "tests/e2e", + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/aggregation/vite.config.ts b/examples/vue/aggregation/vite.config.ts new file mode 100644 index 0000000000..0651f43b44 --- /dev/null +++ b/examples/vue/aggregation/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + server: { + port: 6565, + }, + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/grouped-aggregation/.devcontainer/devcontainer.json b/examples/vue/grouped-aggregation/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..12fea90a1f --- /dev/null +++ b/examples/vue/grouped-aggregation/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:24" +} diff --git a/examples/vue/grouped-aggregation/env.d.ts b/examples/vue/grouped-aggregation/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/grouped-aggregation/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/grouped-aggregation/index.html b/examples/vue/grouped-aggregation/index.html new file mode 100644 index 0000000000..9d2ee2c138 --- /dev/null +++ b/examples/vue/grouped-aggregation/index.html @@ -0,0 +1,13 @@ + + + + + + + TanStack Vue Table - Grouped Aggregation + + +
+ + + diff --git a/examples/vue/grouped-aggregation/package.json b/examples/vue/grouped-aggregation/package.json new file mode 100644 index 0000000000..0351ac2787 --- /dev/null +++ b/examples/vue/grouped-aggregation/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-vue-table-example-grouped-aggregation", + "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-table": "^9.0.0-beta.47", + "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/grouped-aggregation/src/App.vue b/examples/vue/grouped-aggregation/src/App.vue new file mode 100644 index 0000000000..72405ad9d7 --- /dev/null +++ b/examples/vue/grouped-aggregation/src/App.vue @@ -0,0 +1,319 @@ + + +