@cdx-ui/data-grid 0.3.10 → 0.3.12

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 (82) hide show
  1. package/README.md +3 -1
  2. package/coverage.svg +1 -1
  3. package/lib/commonjs/DataGrid.js +232 -62
  4. package/lib/commonjs/DataGrid.js.map +1 -1
  5. package/lib/commonjs/DataGridFilterPanel.js +420 -0
  6. package/lib/commonjs/DataGridFilterPanel.js.map +1 -0
  7. package/lib/commonjs/adapter/columnTypes.js +256 -0
  8. package/lib/commonjs/adapter/columnTypes.js.map +1 -0
  9. package/lib/commonjs/adapter/columns.js +132 -4
  10. package/lib/commonjs/adapter/columns.js.map +1 -1
  11. package/lib/commonjs/adapter/filterOperators.js +422 -0
  12. package/lib/commonjs/adapter/filterOperators.js.map +1 -0
  13. package/lib/commonjs/adapter/pinning.js +101 -0
  14. package/lib/commonjs/adapter/pinning.js.map +1 -0
  15. package/lib/commonjs/index.js +26 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/numberCells.js +280 -0
  18. package/lib/commonjs/numberCells.js.map +1 -0
  19. package/lib/commonjs/styles.js +61 -1
  20. package/lib/commonjs/styles.js.map +1 -1
  21. package/lib/commonjs/types.js +29 -1
  22. package/lib/commonjs/types.js.map +1 -1
  23. package/lib/commonjs/useDataGridTable.js +98 -13
  24. package/lib/commonjs/useDataGridTable.js.map +1 -1
  25. package/lib/module/DataGrid.js +237 -67
  26. package/lib/module/DataGrid.js.map +1 -1
  27. package/lib/module/DataGridFilterPanel.js +414 -0
  28. package/lib/module/DataGridFilterPanel.js.map +1 -0
  29. package/lib/module/adapter/columnTypes.js +240 -0
  30. package/lib/module/adapter/columnTypes.js.map +1 -0
  31. package/lib/module/adapter/columns.js +129 -4
  32. package/lib/module/adapter/columns.js.map +1 -1
  33. package/lib/module/adapter/filterOperators.js +406 -0
  34. package/lib/module/adapter/filterOperators.js.map +1 -0
  35. package/lib/module/adapter/pinning.js +90 -0
  36. package/lib/module/adapter/pinning.js.map +1 -0
  37. package/lib/module/index.js +2 -0
  38. package/lib/module/index.js.map +1 -1
  39. package/lib/module/numberCells.js +275 -0
  40. package/lib/module/numberCells.js.map +1 -0
  41. package/lib/module/styles.js +60 -0
  42. package/lib/module/styles.js.map +1 -1
  43. package/lib/module/types.js +28 -0
  44. package/lib/module/types.js.map +1 -1
  45. package/lib/module/useDataGridTable.js +99 -14
  46. package/lib/module/useDataGridTable.js.map +1 -1
  47. package/lib/typescript/DataGrid.d.ts.map +1 -1
  48. package/lib/typescript/DataGridFilterPanel.d.ts +17 -0
  49. package/lib/typescript/DataGridFilterPanel.d.ts.map +1 -0
  50. package/lib/typescript/adapter/columnTypes.d.ts +32 -0
  51. package/lib/typescript/adapter/columnTypes.d.ts.map +1 -0
  52. package/lib/typescript/adapter/columns.d.ts +14 -5
  53. package/lib/typescript/adapter/columns.d.ts.map +1 -1
  54. package/lib/typescript/adapter/filterOperators.d.ts +43 -0
  55. package/lib/typescript/adapter/filterOperators.d.ts.map +1 -0
  56. package/lib/typescript/adapter/pinning.d.ts +25 -0
  57. package/lib/typescript/adapter/pinning.d.ts.map +1 -0
  58. package/lib/typescript/index.d.ts +4 -1
  59. package/lib/typescript/index.d.ts.map +1 -1
  60. package/lib/typescript/numberCells.d.ts +46 -0
  61. package/lib/typescript/numberCells.d.ts.map +1 -0
  62. package/lib/typescript/styles.d.ts +21 -0
  63. package/lib/typescript/styles.d.ts.map +1 -1
  64. package/lib/typescript/types.d.ts +86 -7
  65. package/lib/typescript/types.d.ts.map +1 -1
  66. package/lib/typescript/useDataGridTable.d.ts +18 -4
  67. package/lib/typescript/useDataGridTable.d.ts.map +1 -1
  68. package/package.json +5 -5
  69. package/src/DataGrid.tsx +377 -67
  70. package/src/DataGridFilterPanel.tsx +477 -0
  71. package/src/adapter/columnTypes.ts +327 -0
  72. package/src/adapter/columns.test.ts +75 -1
  73. package/src/adapter/columns.ts +178 -3
  74. package/src/adapter/filterOperators.test.ts +292 -0
  75. package/src/adapter/filterOperators.ts +524 -0
  76. package/src/adapter/pinning.ts +125 -0
  77. package/src/index.ts +15 -0
  78. package/src/numberCells.tsx +379 -0
  79. package/src/styles.ts +75 -0
  80. package/src/types.ts +114 -7
  81. package/src/useDataGridTable.test.ts +69 -0
  82. package/src/useDataGridTable.ts +127 -8
@@ -2,10 +2,79 @@ import type { RowSelectionState } from '@tanstack/react-table';
2
2
  import type { GridRowId } from './types';
3
3
  import {
4
4
  columnFiltersToFilterModel,
5
+ filterModelToColumnFilters,
5
6
  rowSelectionToModel,
6
7
  selectionModelToRowSelection,
7
8
  } from './useDataGridTable';
8
9
 
10
+ describe('filterModelToColumnFilters', () => {
11
+ it('keeps items without an operator as text filters and drops blank ones', () => {
12
+ expect(
13
+ filterModelToColumnFilters([
14
+ { field: 'name', value: 'alice' },
15
+ { field: 'status', value: ' ' },
16
+ { field: 'age' },
17
+ ]),
18
+ ).toEqual([{ id: 'name', value: 'alice' }]);
19
+ });
20
+
21
+ it('groups several items on one field and round-trips them', () => {
22
+ const model = [
23
+ { field: 'amount', operator: '>' as const, value: 1 },
24
+ { field: 'amount', operator: '<' as const, value: 9 },
25
+ ];
26
+ const state = filterModelToColumnFilters(model);
27
+
28
+ expect(state).toEqual([
29
+ {
30
+ id: 'amount',
31
+ value: {
32
+ items: [
33
+ { operator: '>', value: 1 },
34
+ { operator: '<', value: 9 },
35
+ ],
36
+ },
37
+ },
38
+ ]);
39
+ expect(columnFiltersToFilterModel(state)).toEqual(model);
40
+ });
41
+
42
+ it('carries the operator and typed operand', () => {
43
+ expect(filterModelToColumnFilters([{ field: 'amount', operator: '>', value: 100 }])).toEqual([
44
+ { id: 'amount', value: { operator: '>', value: 100 } },
45
+ ]);
46
+ });
47
+
48
+ it('keeps isEmpty and isNotEmpty without a value', () => {
49
+ expect(
50
+ filterModelToColumnFilters([
51
+ { field: 'a', operator: 'isEmpty' },
52
+ { field: 'b', operator: 'isNotEmpty', value: '' },
53
+ ]),
54
+ ).toEqual([
55
+ { id: 'a', value: { operator: 'isEmpty', value: undefined } },
56
+ { id: 'b', value: { operator: 'isNotEmpty', value: '' } },
57
+ ]);
58
+ });
59
+
60
+ it('drops value operators whose operand is blank', () => {
61
+ expect(
62
+ filterModelToColumnFilters([
63
+ { field: 'a', operator: '>' },
64
+ { field: 'b', operator: 'isAnyOf', value: [] },
65
+ { field: 'c', operator: 'contains', value: ' ' },
66
+ ]),
67
+ ).toEqual([]);
68
+ });
69
+ });
70
+
71
+ describe('columnFiltersToFilterModel with operators', () => {
72
+ it('round-trips operator filters', () => {
73
+ const model = [{ field: 'amount', operator: '<=' as const, value: 5 }];
74
+ expect(columnFiltersToFilterModel(filterModelToColumnFilters(model))).toEqual(model);
75
+ });
76
+ });
77
+
9
78
  describe('columnFiltersToFilterModel', () => {
10
79
  it('keeps string filter values', () => {
11
80
  expect(columnFiltersToFilterModel([{ id: 'name', value: 'alice' }])).toEqual([
@@ -1,6 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import {
3
3
  columnFilteringFeature,
4
+ columnPinningFeature,
4
5
  createFilteredRowModel,
5
6
  createPaginatedRowModel,
6
7
  createSortedRowModel,
@@ -14,16 +15,37 @@ import {
14
15
  tableFeatures,
15
16
  useTable,
16
17
  type ColumnFiltersState,
18
+ type ColumnPinningState,
17
19
  type PaginationState,
18
20
  type RowSelectionState,
19
21
  type SortingState,
20
22
  } from '@tanstack/react-table';
21
23
  import { createSelectColumn } from './adapter/selectColumn';
22
24
  import { applyUpdater, gridColDefToColumnDef } from './adapter/columns';
25
+ import {
26
+ filterFn_displayText,
27
+ metaUsesDisplayFilter,
28
+ normalizeFilterQuery,
29
+ sortFn_boolean,
30
+ sortFn_datetime,
31
+ sortFn_number,
32
+ sortFn_selectLabel,
33
+ untypedCellIncludesQuery,
34
+ } from './adapter/columnTypes';
35
+ import {
36
+ filterOperatorNeedsValue,
37
+ isActiveFilterItem,
38
+ isBlankFilterValue,
39
+ isFilterValueGroup,
40
+ isOperatorFilterValue,
41
+ rowMatchesFilterItem,
42
+ } from './adapter/filterOperators';
23
43
  import type {
24
44
  GridColDef,
25
45
  GridFeatureMode,
46
+ GridFilterItem,
26
47
  GridFilterModel,
48
+ GridLogicOperator,
27
49
  GridPaginationModel,
28
50
  GridRowId,
29
51
  GridRowSelectionModel,
@@ -33,8 +55,24 @@ import type {
33
55
  } from './types';
34
56
  import { SELECT_COLUMN_ID } from './types';
35
57
 
58
+ function gridGlobalFilterFn(
59
+ row: Parameters<typeof filterFn_displayText>[0],
60
+ columnId: string,
61
+ filterValue: unknown,
62
+ ): boolean {
63
+ if (columnId === SELECT_COLUMN_ID) {
64
+ return false;
65
+ }
66
+ const meta = row.getAllCells().find((cell) => cell.column.id === columnId)?.column.columnDef.meta;
67
+ if (metaUsesDisplayFilter(meta)) {
68
+ return filterFn_displayText(row, columnId, filterValue);
69
+ }
70
+ return untypedCellIncludesQuery(row.getValue(columnId), normalizeFilterQuery(filterValue));
71
+ }
72
+
36
73
  const features = tableFeatures({
37
74
  columnFilteringFeature,
75
+ columnPinningFeature,
38
76
  globalFilteringFeature,
39
77
  rowSortingFeature,
40
78
  rowPaginationFeature,
@@ -43,7 +81,14 @@ const features = tableFeatures({
43
81
  sortedRowModel: createSortedRowModel(),
44
82
  paginatedRowModel: createPaginatedRowModel(),
45
83
  filterFns: { includesString: filterFn_includesString },
46
- sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
84
+ sortFns: {
85
+ alphanumeric: sortFn_alphanumeric,
86
+ text: sortFn_text,
87
+ number: sortFn_number,
88
+ datetime: sortFn_datetime,
89
+ boolean: sortFn_boolean,
90
+ selectLabel: sortFn_selectLabel,
91
+ },
47
92
  });
48
93
 
49
94
  export function sortModelToSortingState(model: GridSortModel): SortingState {
@@ -81,16 +126,55 @@ function columnFilterValueToString(value: unknown): string {
81
126
  return '';
82
127
  }
83
128
 
129
+ /**
130
+ * Items without an `operator` keep today's text filter. Items with one carry the operator and
131
+ * typed operand into the column filter. An item whose operator needs a value but has none is
132
+ * dropped, so `isEmpty` / `isNotEmpty` survive a blank `value` and nothing else does.
133
+ */
84
134
  export function filterModelToColumnFilters(model: GridFilterModel): ColumnFiltersState {
85
- return model
86
- .filter((item) => item.value.trim().length > 0)
87
- .map((item) => ({ id: item.field, value: item.value }));
135
+ const valuesByField = new Map<string, unknown[]>();
136
+ for (const item of model) {
137
+ let value: unknown;
138
+ if (item.operator !== undefined) {
139
+ if (filterOperatorNeedsValue(item.operator) && isBlankFilterValue(item.value)) {
140
+ continue;
141
+ }
142
+ value = { operator: item.operator, value: item.value };
143
+ } else {
144
+ const text = columnFilterValueToString(item.value);
145
+ if (text.trim().length === 0) {
146
+ continue;
147
+ }
148
+ value = text;
149
+ }
150
+ const values = valuesByField.get(item.field);
151
+ if (values) {
152
+ values.push(value);
153
+ } else {
154
+ valuesByField.set(item.field, [value]);
155
+ }
156
+ }
157
+ return [...valuesByField].map(([id, values]) => ({
158
+ id,
159
+ value: values.length === 1 ? values[0] : { items: values },
160
+ }));
161
+ }
162
+
163
+ function columnFilterValueToItem(field: string, value: unknown): GridFilterItem {
164
+ if (isOperatorFilterValue(value)) {
165
+ return { field, operator: value.operator, value: value.value };
166
+ }
167
+ return { field, value: columnFilterValueToString(value) };
88
168
  }
89
169
 
90
170
  export function columnFiltersToFilterModel(state: ColumnFiltersState): GridFilterModel {
91
171
  return state
92
172
  .filter((item) => item.id !== SELECT_COLUMN_ID)
93
- .map((item) => ({ field: item.id, value: columnFilterValueToString(item.value) }));
173
+ .flatMap((item) =>
174
+ isFilterValueGroup(item.value)
175
+ ? item.value.items.map((value) => columnFilterValueToItem(item.id, value))
176
+ : [columnFilterValueToItem(item.id, item.value)],
177
+ );
94
178
  }
95
179
 
96
180
  function selectionIdKey(id: GridRowId): string {
@@ -185,10 +269,12 @@ interface UseDataGridTableParams<R extends GridValidRowModel> {
185
269
  onPaginationModelChange: (model: GridPaginationModel) => void;
186
270
  filterModel: GridFilterModel;
187
271
  onFilterModelChange: (model: GridFilterModel) => void;
272
+ filterLogicOperator: GridLogicOperator;
188
273
  quickFilter: string;
189
274
  onQuickFilterChange: (value: string) => void;
190
275
  rowSelectionModel: GridRowSelectionModel;
191
276
  onRowSelectionModelChange: (model: GridRowSelectionModel) => void;
277
+ columnPinning: ColumnPinningState;
192
278
  }
193
279
 
194
280
  export function useDataGridTable<R extends GridValidRowModel>({
@@ -207,12 +293,35 @@ export function useDataGridTable<R extends GridValidRowModel>({
207
293
  onPaginationModelChange,
208
294
  filterModel,
209
295
  onFilterModelChange,
296
+ filterLogicOperator,
210
297
  quickFilter,
211
298
  onQuickFilterChange,
212
299
  rowSelectionModel,
213
300
  onRowSelectionModelChange,
301
+ columnPinning,
214
302
  }: UseDataGridTableParams<R>) {
215
- const data = useMemo(() => [...rows], [rows]);
303
+ const orFilterItems = useMemo(() => {
304
+ if (filterLogicOperator !== 'or' || filterMode === 'server') {
305
+ return undefined;
306
+ }
307
+ const active = filterModel.filter(isActiveFilterItem);
308
+ return active.length > 1 ? active : undefined;
309
+ }, [filterLogicOperator, filterMode, filterModel]);
310
+
311
+ const data = useMemo(() => {
312
+ if (!orFilterItems) {
313
+ return [...rows];
314
+ }
315
+ const filterableColumns = new Map(
316
+ columns.filter((col) => col.filterable !== false).map((col) => [col.field, col]),
317
+ );
318
+ return rows.filter((row) =>
319
+ orFilterItems.some((item) => {
320
+ const col = filterableColumns.get(item.field);
321
+ return col !== undefined && rowMatchesFilterItem(col, row, item);
322
+ }),
323
+ );
324
+ }, [columns, orFilterItems, rows]);
216
325
  const allIds = useMemo(() => data.map((row) => resolveRowId(row, getRowId)), [data, getRowId]);
217
326
 
218
327
  const columnDefs = useMemo(() => {
@@ -228,7 +337,10 @@ export function useDataGridTable<R extends GridValidRowModel>({
228
337
 
229
338
  const sortingState = useMemo(() => sortModelToSortingState(sortModel), [sortModel]);
230
339
  const paginationState = useMemo(() => paginationModelToState(paginationModel), [paginationModel]);
231
- const columnFilterState = useMemo(() => filterModelToColumnFilters(filterModel), [filterModel]);
340
+ const columnFilterState = useMemo(
341
+ () => (orFilterItems ? [] : filterModelToColumnFilters(filterModel)),
342
+ [filterModel, orFilterItems],
343
+ );
232
344
  const rowSelectionState = useMemo(
233
345
  () => selectionModelToRowSelection(rowSelectionModel, allIds),
234
346
  [rowSelectionModel, allIds],
@@ -261,6 +373,7 @@ export function useDataGridTable<R extends GridValidRowModel>({
261
373
  getRowId: (row) => selectionIdKey(resolveRowId(row, getRowId)),
262
374
  enableMultiSort: false,
263
375
  enableMultiRowSelection: true,
376
+ globalFilterFn: gridGlobalFilterFn,
264
377
  manualPagination: paginationMode === 'server',
265
378
  manualSorting: sortingMode === 'server',
266
379
  manualFiltering: filterMode === 'server',
@@ -272,6 +385,7 @@ export function useDataGridTable<R extends GridValidRowModel>({
272
385
  columnFilters: columnFilterState,
273
386
  globalFilter: quickFilter,
274
387
  rowSelection: rowSelectionState,
388
+ columnPinning,
275
389
  },
276
390
  onSortingChange: (updater) => {
277
391
  const next = applyUpdater(updater, sortingState);
@@ -297,5 +411,10 @@ export function useDataGridTable<R extends GridValidRowModel>({
297
411
  },
298
412
  });
299
413
 
300
- return { table, setQuickFilter, setColumnFilter };
414
+ const setFilterModel = (model: GridFilterModel) => {
415
+ onFilterModelChange(model);
416
+ resetPaginationPage();
417
+ };
418
+
419
+ return { table, setQuickFilter, setColumnFilter, setFilterModel };
301
420
  }