@jielga/tmdatagrid 2.0.0-beta.13 → 2.0.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +967 -610
  3. package/dist/index.js +2944 -2327
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/anatomy.md +102 -0
  7. package/docs/cell-selection.md +154 -0
  8. package/docs/column-layout.md +204 -0
  9. package/docs/columns.md +262 -0
  10. package/docs/components.md +303 -0
  11. package/docs/editing.md +572 -0
  12. package/docs/editors.md +248 -0
  13. package/docs/export.md +326 -0
  14. package/docs/filtering.md +358 -0
  15. package/docs/getting-started.md +123 -0
  16. package/docs/grouping.md +165 -0
  17. package/docs/loading-and-empty.md +92 -0
  18. package/docs/localization.md +79 -0
  19. package/docs/menu.md +143 -0
  20. package/docs/pagination.md +144 -0
  21. package/docs/persistence.md +111 -0
  22. package/docs/portfolio-rebalancer.md +94 -0
  23. package/docs/query-builder.md +175 -0
  24. package/docs/quick-search.md +83 -0
  25. package/docs/row-details.md +111 -0
  26. package/docs/row-interaction.md +148 -0
  27. package/docs/row-pinning.md +132 -0
  28. package/docs/row-selection.md +134 -0
  29. package/docs/row-styling.md +133 -0
  30. package/docs/scrolling.md +111 -0
  31. package/docs/server-query.md +246 -0
  32. package/docs/server-side.md +206 -0
  33. package/docs/sorting.md +101 -0
  34. package/docs/styling.md +126 -0
  35. package/docs/summary-row.md +76 -0
  36. package/docs/testing.md +309 -0
  37. package/docs/toolbar.md +161 -0
  38. package/docs/use-tm-data-grid.md +361 -0
  39. package/package.json +21 -45
  40. package/skills/appearance/SKILL.md +31 -15
  41. package/skills/cell-selection/SKILL.md +44 -41
  42. package/skills/columns/SKILL.md +33 -32
  43. package/skills/data/SKILL.md +85 -15
  44. package/skills/editing/SKILL.md +6 -6
  45. package/skills/editing/references/common-mistakes.md +12 -12
  46. package/skills/filtering/SKILL.md +40 -18
  47. package/skills/getting-started/SKILL.md +5 -5
  48. package/skills/grouping/SKILL.md +11 -11
  49. package/skills/options/SKILL.md +5 -5
  50. package/skills/rows/SKILL.md +15 -15
  51. package/skills/server-side/SKILL.md +131 -7
  52. package/skills/testing/SKILL.md +4 -4
  53. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +28 -20
  54. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +5 -4
  55. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  56. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +40 -15
  57. package/src/components/TMDataGridExportPicker.module.css +77 -0
  58. package/src/components/TMDataGridExportPicker.tsx +234 -0
  59. package/src/{tmdatagrid/components → components}/TMDataGridFilterPanel.tsx +22 -6
  60. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +7 -5
  61. package/src/{tmdatagrid/components → components}/TMDataGridFilterSurface.tsx +8 -3
  62. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +20 -7
  63. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +16 -5
  64. package/src/{tmdatagrid/components → components}/TMDataGridHeaderFilterRow.tsx +2 -4
  65. package/src/{tmdatagrid/components → components}/TMDataGridMenu.tsx +115 -16
  66. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +2 -1
  67. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +74 -55
  68. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
  69. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.tsx +44 -8
  70. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +2 -1
  71. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  72. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  73. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  74. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  75. package/src/core/dom.ts +74 -0
  76. package/src/core/export.ts +840 -0
  77. package/src/{tmdatagrid/core → core}/filterControls.ts +10 -1
  78. package/src/{tmdatagrid/core → core}/labels.ts +40 -2
  79. package/src/{tmdatagrid/core → core}/labelsSv.ts +17 -1
  80. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  81. package/src/{tmdatagrid/core → core}/summary.ts +20 -4
  82. package/src/{tmdatagrid/index.ts → index.ts} +43 -4
  83. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +100 -45
  84. package/src/useTMDataGridExport.ts +78 -0
  85. package/src/tmdatagrid/core/cellExport.ts +0 -320
  86. /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
  87. /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
  88. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  89. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +0 -0
  90. /package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +0 -0
  91. /package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +0 -0
  92. /package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +0 -0
  93. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPanel.module.css +0 -0
  94. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  95. /package/src/{tmdatagrid/components → components}/TMDataGridFilterSurface.module.css +0 -0
  96. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  97. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  98. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +0 -0
  99. /package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +0 -0
  100. /package/src/{tmdatagrid/components → components}/TMDataGridHeaderFilterRow.module.css +0 -0
  101. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  102. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  103. /package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +0 -0
  104. /package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +0 -0
  105. /package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +0 -0
  106. /package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +0 -0
  107. /package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +0 -0
  108. /package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +0 -0
  109. /package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +0 -0
  110. /package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +0 -0
  111. /package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +0 -0
  112. /package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +0 -0
  113. /package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +0 -0
  114. /package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +0 -0
  115. /package/src/{tmdatagrid/components → components}/filters/controlLayout.ts +0 -0
  116. /package/src/{tmdatagrid/components → components}/filters/filterControlFor.ts +0 -0
  117. /package/src/{tmdatagrid/components → components}/sticky.module.css +0 -0
  118. /package/src/{tmdatagrid/components → components}/useHideableColumns.ts +0 -0
  119. /package/src/{tmdatagrid/core → core}/capabilities.ts +0 -0
  120. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  121. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  122. /package/src/{tmdatagrid/core → core}/columnOptions.ts +0 -0
  123. /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
  124. /package/src/{tmdatagrid/core → core}/controlledStateSync.ts +0 -0
  125. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  126. /package/src/{tmdatagrid/core → core}/editEngine.ts +0 -0
  127. /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
  128. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  129. /package/src/{tmdatagrid/core → core}/filterOperators.ts +0 -0
  130. /package/src/{tmdatagrid/core → core}/filterSurface.ts +0 -0
  131. /package/src/{tmdatagrid/core → core}/grouping.ts +0 -0
  132. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  133. /package/src/{tmdatagrid/core → core}/pageReset.ts +0 -0
  134. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  135. /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
  136. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  137. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  138. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
  139. /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
@@ -86,10 +86,19 @@ export type TMDataGridFilterControlComponent =
86
86
  * declaration of each feeds the filter panel and the cell editor alike.
87
87
  */
88
88
  export type TMDataGridColumnFilterOptions = {
89
+ /**
90
+ * The operators this column offers, a subset of the type's own. For a
91
+ * column backed by an endpoint that answers only some of them - `contains`
92
+ * and `equals`, say - so the panel and the header funnel never offer an
93
+ * operator the query cannot express. Kept in the type's order; one the type
94
+ * does not offer is ignored, and a list that leaves nothing falls back to
95
+ * the type's full set.
96
+ */
97
+ operators?: readonly TMDataGridFilterOperator[];
89
98
  /**
90
99
  * The operator a fresh filter on this column starts with, instead of the
91
100
  * type's default - a salary column can open on `"between"`. Must be one of
92
- * the type's own operators.
101
+ * the operators the column offers.
93
102
  */
94
103
  defaultOperator?: TMDataGridFilterOperator;
95
104
  /**
@@ -148,9 +148,27 @@ export type TMDataGridLabels = {
148
148
  // Cell selection menu
149
149
  cellCount: (count: number) => string;
150
150
  copy: string;
151
- exportCsv: string;
151
+ exportCells: string;
152
+ /** @deprecated Use `exportCells`. Read as its fallback for one beta. */
153
+ exportCsv?: string;
152
154
  includeHeaders: string;
153
155
 
156
+ // Export
157
+ exportAll: string;
158
+ exportSelected: (count: number) => string;
159
+ /** Title of the column picker `columns="custom"` opens; `format` is the file extension in upper case, "CSV". */
160
+ exportPickerTitle: (format: string) => string;
161
+ /** The line under the title; `selected` is the selected row count, or `null` for an export of all rows. */
162
+ exportPickerHint: (selected: number | null) => string;
163
+ exportPickerConfirm: string;
164
+ exportPickerCancel: string;
165
+ /** The select-all row over the picker's list. */
166
+ exportPickerSelectAll: string;
167
+ /** The "3 of 12" count beside it. */
168
+ exportPickerCount: (checked: number, total: number) => string;
169
+ /** Marks a column the grid hides at the moment. */
170
+ exportPickerHidden: string;
171
+
154
172
  // Generated checkbox lane
155
173
  selectColumnLabel: string;
156
174
  selectAllRows: string;
@@ -274,9 +292,25 @@ export const TMDATAGRID_LABELS_EN: TMDataGridLabels = {
274
292
 
275
293
  cellCount: (count) => (count === 1 ? "1 cell" : `${count} cells`),
276
294
  copy: "Copy",
277
- exportCsv: "Export as CSV for Excel",
295
+ exportCells: "Export cells",
278
296
  includeHeaders: "Include headers",
279
297
 
298
+ exportAll: "Export all rows",
299
+ exportSelected: (count) =>
300
+ count === 1 ? "Export 1 selected row" : `Export ${count} selected rows`,
301
+ exportPickerTitle: (format) => `Export as ${format}`,
302
+ exportPickerHint: (selected) =>
303
+ selected === null
304
+ ? "Select the columns to export"
305
+ : selected === 1
306
+ ? "Select the columns to export for the selected row"
307
+ : `Select the columns to export for the ${selected} selected rows`,
308
+ exportPickerConfirm: "Export",
309
+ exportPickerCancel: "Cancel",
310
+ exportPickerSelectAll: "Select all",
311
+ exportPickerCount: (checked, total) => `${checked} of ${total}`,
312
+ exportPickerHidden: "Hidden",
313
+
280
314
  selectColumnLabel: "Checkbox selection",
281
315
  selectAllRows: "Select all rows",
282
316
  selectRow: "Select row",
@@ -302,6 +336,10 @@ export function mergeLabels(
302
336
  return {
303
337
  ...TMDATAGRID_LABELS_EN,
304
338
  ...override,
339
+ exportCells:
340
+ override.exportCells ??
341
+ override.exportCsv ??
342
+ TMDATAGRID_LABELS_EN.exportCells,
305
343
  operators: {
306
344
  ...TMDATAGRID_LABELS_EN.operators,
307
345
  ...override.operators,
@@ -121,9 +121,25 @@ export const TMDATAGRID_LABELS_SV: TMDataGridLabels = {
121
121
 
122
122
  cellCount: (count) => (count === 1 ? "1 cell" : `${count} celler`),
123
123
  copy: "Kopiera",
124
- exportCsv: "Exportera som CSV för Excel",
124
+ exportCells: "Exportera celler",
125
125
  includeHeaders: "Inkludera rubriker",
126
126
 
127
+ exportAll: "Exportera alla rader",
128
+ exportSelected: (count) =>
129
+ count === 1 ? "Exportera 1 vald rad" : `Exportera ${count} valda rader`,
130
+ exportPickerTitle: (format) => `Exportera som ${format}`,
131
+ exportPickerHint: (selected) =>
132
+ selected === null
133
+ ? "Välj kolumner att exportera"
134
+ : selected === 1
135
+ ? "Välj kolumner att exportera för den valda raden"
136
+ : `Välj kolumner att exportera för de ${selected} valda raderna`,
137
+ exportPickerConfirm: "Exportera",
138
+ exportPickerCancel: "Avbryt",
139
+ exportPickerSelectAll: "Markera alla",
140
+ exportPickerCount: (checked, total) => `${checked} av ${total}`,
141
+ exportPickerHidden: "Dold",
142
+
127
143
  selectColumnLabel: "Kryssrutemarkering",
128
144
  selectAllRows: "Markera alla rader",
129
145
  selectRow: "Markera rad",
@@ -188,12 +188,26 @@ const SLICE_GUARDS: Record<
188
188
  isRecordOf(value, (entry) => typeof entry === "boolean"),
189
189
  columnSizing: (value) => isRecordOf(value, isFiniteNumber),
190
190
  columnOrder: (value) => isArrayOf(value, isString),
191
+ // Both the current logical shape and the physical one 1.x wrote.
191
192
  columnPinning: (value) =>
192
- isArrayOf(prop(value, "left"), isString) &&
193
- isArrayOf(prop(value, "right"), isString),
193
+ (isArrayOf(prop(value, "start"), isString) &&
194
+ isArrayOf(prop(value, "end"), isString)) ||
195
+ (isArrayOf(prop(value, "left"), isString) &&
196
+ isArrayOf(prop(value, "right"), isString)),
194
197
  grouping: (value) => isArrayOf(value, isString),
195
198
  };
196
199
 
200
+ /**
201
+ * Settings saved by 1.x used TanStack's old physical `columnPinning` keys.
202
+ * Rewrite them once on read, so nothing downstream sees `left` / `right`.
203
+ */
204
+ function normalizeColumnPinning(value: unknown): unknown {
205
+ const left = prop(value, "left");
206
+ const right = prop(value, "right");
207
+ if (left === undefined || right === undefined) return value;
208
+ return { start: left, end: right };
209
+ }
210
+
197
211
  function isValidSlice(slice: keyof GridState, value: unknown): boolean {
198
212
  const guard = SLICE_GUARDS[slice as keyof typeof SLICE_GUARDS];
199
213
  return guard === undefined || guard(value);
@@ -219,7 +233,10 @@ function readSlices(
219
233
  for (const slice of resolved.slices) {
220
234
  if (!(slice in record)) continue;
221
235
  if (!isValidSlice(slice, record[slice])) continue;
222
- restored[slice] = record[slice];
236
+ restored[slice] =
237
+ slice === "columnPinning"
238
+ ? normalizeColumnPinning(record[slice])
239
+ : record[slice];
223
240
  }
224
241
  return restored as Partial<GridState>;
225
242
  } catch {
@@ -323,8 +340,8 @@ function realignToColumns(
323
340
  }
324
341
  if (restored.columnPinning) {
325
342
  realigned.columnPinning = {
326
- left: keepIds(restored.columnPinning.left ?? []),
327
- right: keepIds(restored.columnPinning.right ?? []),
343
+ start: keepIds(restored.columnPinning.start ?? []),
344
+ end: keepIds(restored.columnPinning.end ?? []),
328
345
  };
329
346
  }
330
347
  if (restored.sorting) realigned.sorting = keepEntries(restored.sorting);
@@ -1,6 +1,10 @@
1
- import { aggregationFns, type RowData } from "@tanstack/react-table";
1
+ import {
2
+ type AggregationContext,
3
+ aggregationFns,
4
+ type RowData,
5
+ } from "@tanstack/react-table";
2
6
  import type { TMDataGridRowData } from "../TMDataGridContext";
3
- import type { TMDataGridTable } from "../useTMDataGrid";
7
+ import type { TMDataGridFeatures, TMDataGridTable } from "../useTMDataGrid";
4
8
 
5
9
  /** The registered aggregation names - the same set `aggregationFn` accepts. */
6
10
  export type TMDataGridAggregationName = keyof typeof aggregationFns;
@@ -33,11 +37,23 @@ export function aggregateColumn<TData extends RowData>({
33
37
  fn?: TMDataGridAggregationName;
34
38
  }): unknown {
35
39
  const erased = table as unknown as TMDataGridTable<TMDataGridRowData>;
40
+ const column = erased.getColumn(columnId);
41
+ // A summary for a column the table does not hold has no value.
42
+ if (!column) return undefined;
36
43
  // `flatRows`, not `rows`: the filtered model nests under `getSubRows`, and
37
44
  // the top-level array would total a tree's roots and drop every child.
38
45
  // Grouping does not nest it - the group rows are built from this model, not
39
46
  // held in it - so a grouped grid is unaffected either way.
40
47
  const rows = erased.getFilteredRowModel().flatRows;
41
- // Leaf and child rows are the same set here: this is already every row.
42
- return aggregationFns[fn](columnId, rows, rows);
48
+ // The rows are already every row, so there is nothing below them to select:
49
+ // `maxDepth: 0`, and no `subRows` or `groupingRow` - this is not a group.
50
+ const context: AggregationContext<TMDataGridFeatures, TMDataGridRowData> = {
51
+ column,
52
+ columnId,
53
+ maxDepth: 0,
54
+ getValue: (row) => row.getValue(columnId),
55
+ rows,
56
+ table: erased,
57
+ };
58
+ return aggregationFns[fn].aggregate(context);
43
59
  }
@@ -32,6 +32,10 @@ export {
32
32
  useTMDataGrid,
33
33
  type UseTMDataGridOptions,
34
34
  } from "./useTMDataGrid";
35
+ export {
36
+ useTMDataGridExport,
37
+ type TMDataGridExportApi,
38
+ } from "./useTMDataGridExport";
35
39
  export {
36
40
  TMDataGridFilterPills,
37
41
  type TMDataGridFilterPillsProps,
@@ -42,6 +46,7 @@ export {
42
46
  } from "./components/TMDataGridSearch";
43
47
  export type {
44
48
  TMDataGridMenuColumnsProps,
49
+ TMDataGridMenuExportProps,
45
50
  TMDataGridMenuProps,
46
51
  } from "./components/TMDataGridMenu";
47
52
  export {
@@ -126,6 +131,8 @@ export type {
126
131
  TMDataGridFilterSurface,
127
132
  } from "./core/filterSurface";
128
133
  export type { TMDataGridFilterPanelProps } from "./components/TMDataGridFilterPanel";
134
+ export type { TMDataGridColumnsPanelProps } from "./components/TMDataGridColumnsPanel";
135
+ export type { TMDataGridToolbarProps } from "./components/TMDataGridToolbar";
129
136
  export { TMDataGridFilterValueInput } from "./components/filters/TMDataGridFilterValueInput";
130
137
  export { DgRangeSliderFilter } from "./components/filters/DgRangeSliderFilter";
131
138
  export { DgDateRangeFilter } from "./components/filters/DgDateRangeFilter";
@@ -147,12 +154,14 @@ export { TMDATAGRID_LABELS_SV } from "./core/labelsSv";
147
154
  export {
148
155
  getColumnDefaultOperator,
149
156
  getColumnFilterControl,
157
+ getColumnOperators,
150
158
  getColumnLabel,
151
159
  getColumnType,
152
160
  isColumnEditableForRow,
153
161
  isColumnReorderable,
154
162
  isControlColumn,
155
163
  isGeneratedColumn,
164
+ type TMDataGridColumnSearchable,
156
165
  } from "./core/columnUtils";
157
166
  export { getGroupDataRows } from "./core/grouping";
158
167
  export {
@@ -177,19 +186,49 @@ export {
177
186
  type TMDataGridRangeBounds,
178
187
  } from "./core/cellRange";
179
188
  export {
189
+ buildExportData,
190
+ countSelectedExportRows,
191
+ csvExcelFormat,
192
+ csvFormat,
193
+ DEFAULT_EXPORT_OPTIONS,
194
+ downloadFile,
195
+ exportGrid,
196
+ formatExportValue,
197
+ getExportableColumns,
198
+ guardFormula,
199
+ jsonFormat,
200
+ resolveExportOptions,
201
+ toClipboardText,
202
+ tsvFormat,
203
+ writeClipboardText,
204
+ writeExportFile,
205
+ type BuildExportDataArgs,
206
+ type ExportGridArgs,
207
+ type TMDataGridClipboardTextOptions,
208
+ type TMDataGridCsvFormatOptions,
209
+ type TMDataGridExportColumns,
210
+ type TMDataGridExportData,
211
+ type TMDataGridExportFormat,
212
+ type TMDataGridExportOptions,
213
+ type TMDataGridExportPickerRequest,
214
+ type TMDataGridExportRows,
215
+ type TMDataGridExportSettings,
216
+ type TMDataGridExportValueGetter,
217
+ type TMDataGridExportWriteOptions,
218
+ type TMDataGridJsonFormatOptions,
219
+ type TMDataGridTsvFormatOptions,
220
+ // Deprecated, gone in the next beta.
180
221
  buildCellMatrix,
181
222
  buildGridCellMatrix,
182
223
  DEFAULT_CELL_EXPORT_OPTIONS,
183
224
  downloadTextFile,
184
225
  exportGridToCsv,
185
- formatExportValue,
186
- toClipboardText,
226
+ fromCellExportOptions,
187
227
  toExcelCsv,
188
- writeClipboardText,
189
228
  type BuildCellMatrixArgs,
190
229
  type TMDataGridCellExportOptions,
191
230
  type TMDataGridCellMatrix,
192
- } from "./core/cellExport";
231
+ } from "./core/export";
193
232
  export {
194
233
  areAllRowsExpanded,
195
234
  resolveExpandAll,
@@ -24,6 +24,7 @@ import {
24
24
  metaHelper,
25
25
  type Row,
26
26
  type RowData,
27
+ rowAggregationFeature,
27
28
  rowExpandingFeature,
28
29
  rowPaginationFeature,
29
30
  rowPinningFeature,
@@ -116,6 +117,13 @@ import {
116
117
  type TMDataGridQueryTable,
117
118
  } from "./core/pageReset";
118
119
  import type { TMDataGridCellRange } from "./core/cellRange";
120
+ import {
121
+ resolveExportOptions,
122
+ type TMDataGridExportOptions,
123
+ type TMDataGridExportPickerRequest,
124
+ type TMDataGridExportSettings,
125
+ type TMDataGridExportValueGetter,
126
+ } from "./core/export";
119
127
  import {
120
128
  createSelectColumn,
121
129
  SELECT_COLUMN_ID,
@@ -186,8 +194,8 @@ export type TMDataGridColumnMeta = {
186
194
  */
187
195
  enableOrdering?: boolean;
188
196
  /**
189
- * How this column filters: the operator a fresh filter starts with, and the
190
- * value control the filter panel renders for it.
197
+ * How this column filters: which operators it offers, the operator a fresh
198
+ * filter starts with, and the value control the filter panel renders for it.
191
199
  *
192
200
  * ```tsx
193
201
  * meta: {
@@ -214,6 +222,25 @@ export type TMDataGridColumnMeta = {
214
222
  * See {@link TMDataGridColumnEditOptions}.
215
223
  */
216
224
  edit?: TMDataGridColumnEditOptions;
225
+ /**
226
+ * `false` leaves the column out of every export and out of Ctrl+C - for a
227
+ * column of buttons, or one whose value means nothing outside the grid.
228
+ * Defaults to `true`.
229
+ */
230
+ enableExport?: boolean;
231
+ /**
232
+ * The value an export writes for this column, in place of
233
+ * `row.getValue(column.id)`. The export otherwise writes the value, never
234
+ * what the cell renders, so this is where a status code becomes its label
235
+ * or a nested object becomes one field.
236
+ *
237
+ * ```tsx
238
+ * meta: {
239
+ * exportValue: ({ value }) => STATUS_LABELS[value as Status],
240
+ * }
241
+ * ```
242
+ */
243
+ exportValue?: TMDataGridExportValueGetter;
217
244
  };
218
245
 
219
246
  /** Grid-wide configuration passed through `options.meta`. */
@@ -246,6 +273,7 @@ export const tmDataGridFeatures = tableFeatures({
246
273
  columnResizingFeature,
247
274
  columnFacetingFeature,
248
275
  columnGroupingFeature,
276
+ rowAggregationFeature,
249
277
  // Registered for grouping's sake rather than for tree data: the grouped row
250
278
  // model builds the parent rows, and this is what flattens the expanded ones
251
279
  // back into the flat list the body virtualizes.
@@ -371,11 +399,21 @@ export type TMDataGridUiState = {
371
399
  * describe different places.
372
400
  */
373
401
  cellRange: TMDataGridCellRange | null;
402
+ /**
403
+ * The export column picker, while it is open: which rows it exports and the
404
+ * options of the item that opened it. `null` while closed. Held here rather
405
+ * than in the menu item, which unmounts with the dropdown the moment it is
406
+ * clicked.
407
+ */
408
+ exportPicker: TMDataGridExportPickerRequest | null;
374
409
  };
375
410
 
376
411
  export type TMDataGridUiActions = {
377
412
  openFilterPanel: (columnId?: string | null) => void;
378
413
  closeFilterPanel: () => void;
414
+ /** Opens the export column picker for `request`. See `TMDataGrid.Menu.Export`'s `columns="custom"`. */
415
+ openExportPicker: (request: TMDataGridExportPickerRequest) => void;
416
+ closeExportPicker: () => void;
379
417
  /**
380
418
  * Points at a column's row in the filter panel without opening anything.
381
419
  * `openFilterPanel` does this as well as opening; this is the half a panel
@@ -446,6 +484,12 @@ export type TMDataGridApi<TData extends RowData> = {
446
484
  * table about which surface is on.
447
485
  */
448
486
  filters: TMDataGridFiltersSettings;
487
+ /**
488
+ * How the grid exports, the `exportOptions` option with its defaults filled
489
+ * in. Read by `useTMDataGridExport`, the `TMDataGrid.Menu.Export*` items and
490
+ * the cell-range menu, so every export of the grid agrees on the format.
491
+ */
492
+ exportOptions: TMDataGridExportSettings;
449
493
  /** Every string the chrome renders, `labels` merged over the English defaults. */
450
494
  labels: TMDataGridLabels;
451
495
  /** The detail renderer, when row details are on. See `renderDetails`. */
@@ -769,6 +813,20 @@ export type UseTMDataGridOptions<TData extends RowData> = Omit<
769
813
  * `persist`, this one does not have to be referentially stable.
770
814
  */
771
815
  filters?: TMDataGridFiltersOptions;
816
+ /**
817
+ * How the grid exports: the file format, the file name and whether the
818
+ * column labels go in as the first row. Defaults to `csvExcelFormat()`,
819
+ * `"export"` and `true`. See {@link TMDataGridExportOptions}.
820
+ *
821
+ * ```tsx
822
+ * useTMDataGrid({ data, columns, exportOptions: { format: csvFormat(), fileName: "employees" } });
823
+ * ```
824
+ *
825
+ * Read field by field like `filters`, so a literal is fine. A `format`
826
+ * built inline is rebuilt every render, which costs nothing but a small
827
+ * object; keep it at module scope when that bothers you.
828
+ */
829
+ exportOptions?: TMDataGridExportOptions;
772
830
  /**
773
831
  * How the quick search (`TMDataGrid.Search`) matches. `"fuzzy"` - the
774
832
  * default - forgives typos and skipped characters, and while it is the
@@ -1041,6 +1099,7 @@ export function useTMDataGrid<TData extends RowData>({
1041
1099
  cellSelection,
1042
1100
  onFocusedCellChange,
1043
1101
  filters: filterOptions,
1102
+ exportOptions: exportOptionsOverride,
1044
1103
  editing,
1045
1104
  renderDetails,
1046
1105
  renderDetailsEstHeight = DEFAULT_DETAILS_EST_HEIGHT,
@@ -1072,6 +1131,23 @@ export function useTMDataGrid<TData extends RowData>({
1072
1131
  // Resolved on the override's identity, so a module-scope dictionary costs one
1073
1132
  // merge for the lifetime of the grid.
1074
1133
  const labels = useMemo(() => mergeLabels(labelsOverride), [labelsOverride]);
1134
+ // Field by field for the same reason as `filters` below.
1135
+ const {
1136
+ format: exportFormat,
1137
+ fileName: exportFileName,
1138
+ includeHeaders: exportIncludeHeaders,
1139
+ columns: exportColumns,
1140
+ } = exportOptionsOverride ?? {};
1141
+ const exportOptions = useMemo(
1142
+ () =>
1143
+ resolveExportOptions({
1144
+ format: exportFormat,
1145
+ fileName: exportFileName,
1146
+ includeHeaders: exportIncludeHeaders,
1147
+ columns: exportColumns,
1148
+ }),
1149
+ [exportFormat, exportFileName, exportIncludeHeaders, exportColumns],
1150
+ );
1075
1151
  // Unpacked before the memo, so the api is keyed on the five fields rather
1076
1152
  // than on the object's identity - which is what lets `filters` be written as
1077
1153
  // a literal, the way it reads best, without republishing every render.
@@ -1476,14 +1552,14 @@ export function useTMDataGrid<TData extends RowData>({
1476
1552
  columnPinning: {
1477
1553
  // The generated columns are structurally pinned, so they are re-applied
1478
1554
  // on top of anything restored from storage.
1479
- left: [
1555
+ start: [
1480
1556
  ...(rowNumbersEnabled && pinningEnabled ? [ROW_NUMBER_COLUMN_ID] : []),
1481
1557
  ...(selectColumnEnabled && pinningEnabled ? [SELECT_COLUMN_ID] : []),
1482
1558
  ...(groupColumnEnabled && pinningEnabled ? [GROUP_COLUMN_ID] : []),
1483
1559
  ...(detailsColumnEnabled && pinningEnabled ? [DETAILS_COLUMN_ID] : []),
1484
1560
  ...(
1485
- persistedState.columnPinning?.left ??
1486
- options.initialState?.columnPinning?.left ??
1561
+ persistedState.columnPinning?.start ??
1562
+ options.initialState?.columnPinning?.start ??
1487
1563
  []
1488
1564
  ).filter(
1489
1565
  (id) =>
@@ -1495,10 +1571,10 @@ export function useTMDataGrid<TData extends RowData>({
1495
1571
  ],
1496
1572
  // The edit lane mirrors the generated columns on the left: structurally
1497
1573
  // pinned, outermost, re-applied over anything restored.
1498
- right: [
1574
+ end: [
1499
1575
  ...(
1500
- persistedState.columnPinning?.right ??
1501
- options.initialState?.columnPinning?.right ??
1576
+ persistedState.columnPinning?.end ??
1577
+ options.initialState?.columnPinning?.end ??
1502
1578
  []
1503
1579
  ).filter((id) => id !== EDIT_COLUMN_ID),
1504
1580
  ...(editColumnEnabled && pinningEnabled ? [EDIT_COLUMN_ID] : []),
@@ -1578,42 +1654,16 @@ export function useTMDataGrid<TData extends RowData>({
1578
1654
  // eslint-disable-next-line react-hooks/exhaustive-deps
1579
1655
  }, []);
1580
1656
 
1581
- // Two things have to happen whenever `grouping` changes.
1582
- //
1583
- // One: the tree column appears with the first grouped column and goes away
1584
- // with the last, so an ungrouped grid looks exactly as it did before grouping
1657
+ // The tree column appears with the first grouped column and goes away with
1658
+ // the last, so an ungrouped grid looks exactly as it did before grouping
1585
1659
  // existed. Driven from a subscription rather than by rebuilding the column
1586
1660
  // array, because the array is what the table is built from - deriving it from
1587
1661
  // table state would close the loop. Visibility is the one column property
1588
1662
  // that can be changed after the fact without touching the definitions.
1589
1663
  //
1590
- // Two, and this one is a workaround. In table-core 9.0.0-beta.21 the
1591
- // per-region column APIs do not list `grouping` among their memo
1592
- // dependencies, even though they all derive from `getAllLeafColumns()`, which
1593
- // does:
1594
- //
1595
- // | API | Declares |
1596
- // | --- | --- |
1597
- // | `getLeft/Center/RightVisibleLeafColumns` | columns, columnPinning, columnVisibility, columnOrder |
1598
- // | `getLeft/Center/RightHeaderGroups` | columnPinning, columnOrder |
1599
- // | `row.getLeft/Center/RightVisibleCells` | columnPinning, columnVisibility |
1600
- //
1601
- // So grouping a *second* column leaves every one of them returning the
1602
- // previous list: the column TanStack removed keeps its header and its grid
1603
- // track, and the row cells no longer line up with them. The first grouping
1604
- // appears to work only because the visibility write above happens to touch a
1605
- // dependency they share.
1606
- //
1607
- // Re-publishing `columnVisibility` and `columnOrder` - same contents, new
1608
- // identity - invalidates all three families. `columnOrder` is the only
1609
- // dependency the header groups declare, and `columnVisibility` the only one
1610
- // the cells do, so both are needed. Remove this once the deps are fixed
1611
- // upstream; the test that fails without it groups two columns and asserts the
1612
- // second one leaves the grid.
1613
- //
1614
1664
  // Writing back into the store from its own subscriber is safe: the guard is
1615
- // on `grouping`'s identity, and neither write touches it, so the callback
1616
- // these writes trigger short-circuits.
1665
+ // on `grouping`'s identity, and the write does not touch it, so the callback
1666
+ // it triggers short-circuits.
1617
1667
  useEffect(() => {
1618
1668
  if (!groupColumnEnabled) return;
1619
1669
  let previousGrouping = table.store.state.grouping;
@@ -1647,8 +1697,8 @@ export function useTMDataGrid<TData extends RowData>({
1647
1697
  // Keeps the entry injected into a controlled `columnVisibility` (see
1648
1698
  // requestedState) in sync with grouping.
1649
1699
  groupingActiveRef.current = state.grouping.length > 0;
1650
- // On a controlled slice the writes below round-trip through the
1651
- // consumer's handler; the next render must forward them unstabilized.
1700
+ // On a controlled slice the write below round-trips through the
1701
+ // consumer's handler; the next render must forward it unstabilized.
1652
1702
  // See republishControlledStateRef.
1653
1703
  republishControlledStateRef.current = true;
1654
1704
 
@@ -1656,7 +1706,6 @@ export function useTMDataGrid<TData extends RowData>({
1656
1706
  ...old,
1657
1707
  [GROUP_COLUMN_ID]: state.grouping.length > 0,
1658
1708
  }));
1659
- table.setColumnOrder((old) => [...old]);
1660
1709
  });
1661
1710
 
1662
1711
  return () => subscription.unsubscribe();
@@ -1713,6 +1762,7 @@ export function useTMDataGrid<TData extends RowData>({
1713
1762
  selectionAnchorRowId: null,
1714
1763
  focusedCell: null,
1715
1764
  cellRange: null,
1765
+ exportPicker: null,
1716
1766
  },
1717
1767
  ({ setState }) => ({
1718
1768
  openFilterPanel: (columnId = null) =>
@@ -1727,6 +1777,10 @@ export function useTMDataGrid<TData extends RowData>({
1727
1777
  filterPanelOpen: false,
1728
1778
  filterPanelColumnId: null,
1729
1779
  })),
1780
+ openExportPicker: (request) =>
1781
+ setState((prev) => ({ ...prev, exportPicker: request })),
1782
+ closeExportPicker: () =>
1783
+ setState((prev) => ({ ...prev, exportPicker: null })),
1730
1784
  focusPanelFilter: (columnId) =>
1731
1785
  setState((prev) => ({ ...prev, filterPanelColumnId: columnId })),
1732
1786
  focusHeaderFilter: (columnId) =>
@@ -1819,12 +1873,12 @@ export function useTMDataGrid<TData extends RowData>({
1819
1873
  table.setColumnSizing({ ...initial?.columnSizing });
1820
1874
  table.setColumnOrder([...(initial?.columnOrder ?? [])]);
1821
1875
  table.setColumnPinning({
1822
- left: [
1876
+ start: [
1823
1877
  ...(rowNumbersEnabled && pinningEnabled ? [ROW_NUMBER_COLUMN_ID] : []),
1824
1878
  ...(selectColumnEnabled && pinningEnabled ? [SELECT_COLUMN_ID] : []),
1825
1879
  ...(groupColumnEnabled && pinningEnabled ? [GROUP_COLUMN_ID] : []),
1826
1880
  ...(detailsColumnEnabled && pinningEnabled ? [DETAILS_COLUMN_ID] : []),
1827
- ...(initial?.columnPinning?.left ?? []).filter(
1881
+ ...(initial?.columnPinning?.start ?? []).filter(
1828
1882
  (id) =>
1829
1883
  id !== ROW_NUMBER_COLUMN_ID &&
1830
1884
  id !== SELECT_COLUMN_ID &&
@@ -1832,8 +1886,8 @@ export function useTMDataGrid<TData extends RowData>({
1832
1886
  id !== GROUP_COLUMN_ID,
1833
1887
  ),
1834
1888
  ],
1835
- right: [
1836
- ...(initial?.columnPinning?.right ?? []).filter(
1889
+ end: [
1890
+ ...(initial?.columnPinning?.end ?? []).filter(
1837
1891
  (id) => id !== EDIT_COLUMN_ID,
1838
1892
  ),
1839
1893
  ...(editColumnEnabled && pinningEnabled ? [EDIT_COLUMN_ID] : []),
@@ -1857,6 +1911,7 @@ export function useTMDataGrid<TData extends RowData>({
1857
1911
  edit,
1858
1912
  features,
1859
1913
  filters,
1914
+ exportOptions,
1860
1915
  labels,
1861
1916
  renderDetails,
1862
1917
  renderDetailsEstHeight,
@@ -0,0 +1,78 @@
1
+ import { useSelector } from "@tanstack/react-store";
2
+ import { useTMDataGridContext } from "./TMDataGridContext";
3
+ import {
4
+ buildExportData,
5
+ countSelectedExportRows,
6
+ resolveExportOptions,
7
+ writeExportFile,
8
+ type TMDataGridExportOptions,
9
+ } from "./core/export";
10
+
11
+ /** What {@link useTMDataGridExport} returns. */
12
+ export type TMDataGridExportApi = {
13
+ /**
14
+ * Downloads every filtered and sorted row, all pages. `overrides` are folded
15
+ * over the hook's options for this one call - the column picker's chosen
16
+ * columns, for instance.
17
+ */
18
+ exportAll: (overrides?: TMDataGridExportOptions) => Promise<void>;
19
+ /**
20
+ * Downloads the selected rows of the current view, in grid order. Resolves
21
+ * without a download when none is selected.
22
+ */
23
+ exportSelected: (overrides?: TMDataGridExportOptions) => Promise<void>;
24
+ /** How many rows `exportSelected` would write. Subscribes to the selection. */
25
+ selectedCount: number;
26
+ /**
27
+ * Whether row selection is on at all - `false` under
28
+ * `selectionMode: "highlight"` or `enableRowSelection: false`, where a
29
+ * "selected rows" control has nothing to offer.
30
+ */
31
+ canExportSelected: boolean;
32
+ };
33
+
34
+ /**
35
+ * The export as click handlers, for a button of your own anywhere inside the
36
+ * grid. The `TMDataGrid.Menu.Export*` items are this hook behind a
37
+ * `Menu.Item`.
38
+ *
39
+ * ```tsx
40
+ * function ExportButton() {
41
+ * const { exportAll } = useTMDataGridExport();
42
+ * return <Button onClick={() => void exportAll()}>Export</Button>;
43
+ * }
44
+ * ```
45
+ *
46
+ * `overrides` are folded over the grid's `exportOptions` for this caller:
47
+ * another format, another file name, another set of columns.
48
+ */
49
+ export function useTMDataGridExport(
50
+ overrides?: TMDataGridExportOptions,
51
+ ): TMDataGridExportApi {
52
+ const { table, features, exportOptions } = useTMDataGridContext();
53
+ const selectedCount = useSelector(table.store, () =>
54
+ countSelectedExportRows(table),
55
+ );
56
+
57
+ const run = async (
58
+ rows: "all" | "selected",
59
+ callOverrides?: TMDataGridExportOptions,
60
+ ) => {
61
+ const resolved = resolveExportOptions(
62
+ exportOptions,
63
+ overrides,
64
+ callOverrides,
65
+ );
66
+ const data = buildExportData({ table, rows, columns: resolved.columns });
67
+ if (data.columnIds.length === 0) return;
68
+ if (rows === "selected" && data.rows.length === 0) return;
69
+ await writeExportFile(data, resolved);
70
+ };
71
+
72
+ return {
73
+ exportAll: (callOverrides) => run("all", callOverrides),
74
+ exportSelected: (callOverrides) => run("selected", callOverrides),
75
+ selectedCount,
76
+ canExportSelected: features.rowSelection,
77
+ };
78
+ }