@cdx-ui/data-grid 0.3.11 → 0.3.13

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 (96) hide show
  1. package/README.md +3 -1
  2. package/coverage.svg +1 -1
  3. package/lib/commonjs/DataGrid.js +490 -81
  4. package/lib/commonjs/DataGrid.js.map +1 -1
  5. package/lib/commonjs/DataGridFilterPanel.js +547 -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 +140 -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 +105 -0
  14. package/lib/commonjs/adapter/pinning.js.map +1 -0
  15. package/lib/commonjs/adapter/selectColumn.js +11 -0
  16. package/lib/commonjs/adapter/selectColumn.js.map +1 -1
  17. package/lib/commonjs/adapter/treeData.js +195 -0
  18. package/lib/commonjs/adapter/treeData.js.map +1 -0
  19. package/lib/commonjs/index.js +26 -0
  20. package/lib/commonjs/index.js.map +1 -1
  21. package/lib/commonjs/numberCells.js +280 -0
  22. package/lib/commonjs/numberCells.js.map +1 -0
  23. package/lib/commonjs/styles.js +107 -1
  24. package/lib/commonjs/styles.js.map +1 -1
  25. package/lib/commonjs/types.js +39 -1
  26. package/lib/commonjs/types.js.map +1 -1
  27. package/lib/commonjs/useDataGridTable.js +234 -18
  28. package/lib/commonjs/useDataGridTable.js.map +1 -1
  29. package/lib/module/DataGrid.js +497 -88
  30. package/lib/module/DataGrid.js.map +1 -1
  31. package/lib/module/DataGridFilterPanel.js +540 -0
  32. package/lib/module/DataGridFilterPanel.js.map +1 -0
  33. package/lib/module/adapter/columnTypes.js +240 -0
  34. package/lib/module/adapter/columnTypes.js.map +1 -0
  35. package/lib/module/adapter/columns.js +137 -4
  36. package/lib/module/adapter/columns.js.map +1 -1
  37. package/lib/module/adapter/filterOperators.js +406 -0
  38. package/lib/module/adapter/filterOperators.js.map +1 -0
  39. package/lib/module/adapter/pinning.js +94 -0
  40. package/lib/module/adapter/pinning.js.map +1 -0
  41. package/lib/module/adapter/selectColumn.js +11 -1
  42. package/lib/module/adapter/selectColumn.js.map +1 -1
  43. package/lib/module/adapter/treeData.js +187 -0
  44. package/lib/module/adapter/treeData.js.map +1 -0
  45. package/lib/module/index.js +2 -0
  46. package/lib/module/index.js.map +1 -1
  47. package/lib/module/numberCells.js +275 -0
  48. package/lib/module/numberCells.js.map +1 -0
  49. package/lib/module/styles.js +106 -0
  50. package/lib/module/styles.js.map +1 -1
  51. package/lib/module/types.js +38 -0
  52. package/lib/module/types.js.map +1 -1
  53. package/lib/module/useDataGridTable.js +238 -22
  54. package/lib/module/useDataGridTable.js.map +1 -1
  55. package/lib/typescript/DataGrid.d.ts.map +1 -1
  56. package/lib/typescript/DataGridFilterPanel.d.ts +52 -0
  57. package/lib/typescript/DataGridFilterPanel.d.ts.map +1 -0
  58. package/lib/typescript/adapter/columnTypes.d.ts +32 -0
  59. package/lib/typescript/adapter/columnTypes.d.ts.map +1 -0
  60. package/lib/typescript/adapter/columns.d.ts +14 -5
  61. package/lib/typescript/adapter/columns.d.ts.map +1 -1
  62. package/lib/typescript/adapter/filterOperators.d.ts +43 -0
  63. package/lib/typescript/adapter/filterOperators.d.ts.map +1 -0
  64. package/lib/typescript/adapter/pinning.d.ts +26 -0
  65. package/lib/typescript/adapter/pinning.d.ts.map +1 -0
  66. package/lib/typescript/adapter/selectColumn.d.ts +8 -0
  67. package/lib/typescript/adapter/selectColumn.d.ts.map +1 -1
  68. package/lib/typescript/adapter/treeData.d.ts +36 -0
  69. package/lib/typescript/adapter/treeData.d.ts.map +1 -0
  70. package/lib/typescript/index.d.ts +4 -1
  71. package/lib/typescript/index.d.ts.map +1 -1
  72. package/lib/typescript/numberCells.d.ts +46 -0
  73. package/lib/typescript/numberCells.d.ts.map +1 -0
  74. package/lib/typescript/styles.d.ts +32 -0
  75. package/lib/typescript/styles.d.ts.map +1 -1
  76. package/lib/typescript/types.d.ts +154 -7
  77. package/lib/typescript/types.d.ts.map +1 -1
  78. package/lib/typescript/useDataGridTable.d.ts +40 -4
  79. package/lib/typescript/useDataGridTable.d.ts.map +1 -1
  80. package/package.json +5 -5
  81. package/src/DataGrid.tsx +745 -95
  82. package/src/DataGridFilterPanel.tsx +648 -0
  83. package/src/adapter/columnTypes.ts +327 -0
  84. package/src/adapter/columns.test.ts +75 -1
  85. package/src/adapter/columns.ts +192 -3
  86. package/src/adapter/filterOperators.test.ts +292 -0
  87. package/src/adapter/filterOperators.ts +524 -0
  88. package/src/adapter/pinning.ts +134 -0
  89. package/src/adapter/selectColumn.ts +12 -1
  90. package/src/adapter/treeData.ts +228 -0
  91. package/src/index.ts +19 -0
  92. package/src/numberCells.tsx +379 -0
  93. package/src/styles.ts +131 -0
  94. package/src/types.ts +190 -7
  95. package/src/useDataGridTable.test.ts +69 -0
  96. package/src/useDataGridTable.ts +338 -17
package/src/styles.ts CHANGED
@@ -38,6 +38,51 @@ export const dataGridMainVariants = cva(
38
38
  },
39
39
  );
40
40
 
41
+ /**
42
+ * Opaque sticky fill so pinned cells cover scrolled content.
43
+ * Body hover matches the row's 50% secondary tint, mixed over the primary surface
44
+ * so the cell stays opaque while content scrolls underneath.
45
+ */
46
+ export const dataGridPinnedSurfaceVariants = cva(
47
+ Platform.select({
48
+ web: 'bg-surface-primary',
49
+ default: '',
50
+ }),
51
+ {
52
+ variants: {
53
+ header: {
54
+ true: 'bg-neutral-50',
55
+ false: Platform.select({
56
+ web: 'web:[tr:hover_&]:bg-[color-mix(in_srgb,var(--color-surface-secondary)_50%,var(--color-surface-primary))]',
57
+ default: '',
58
+ }),
59
+ },
60
+ },
61
+ defaultVariants: {
62
+ header: false,
63
+ },
64
+ },
65
+ );
66
+
67
+ export const dataGridPinnedEdgeVariants = cva('', {
68
+ variants: {
69
+ edge: {
70
+ start: Platform.select({
71
+ web: 'border-e border-stroke-secondary',
72
+ default: '',
73
+ }),
74
+ end: Platform.select({
75
+ web: 'border-s border-stroke-secondary',
76
+ default: '',
77
+ }),
78
+ none: '',
79
+ },
80
+ },
81
+ defaultVariants: {
82
+ edge: 'none',
83
+ },
84
+ });
85
+
41
86
  export const dataGridToolbarVariants = cva(
42
87
  'shrink-0 flex-row items-center gap-3 border-b border-stroke-secondary px-4 py-4',
43
88
  );
@@ -135,6 +180,49 @@ export const dataGridBodyVariants = cva(
135
180
 
136
181
  export const dataGridFilterInputVariants = cva('min-w-24');
137
182
 
183
+ // The toolbar panel sits at a fixed spot in the grid root. A panel opened from a column is anchored
184
+ // to its opener in a portal, so its position and width cap come from the anchored overlay instead.
185
+ export const dataGridFilterPanelVariants = cva(
186
+ 'w-max gap-3 rounded-lg border border-stroke-secondary bg-surface-primary px-3 pb-2 pt-3 shadow-lg',
187
+ {
188
+ variants: {
189
+ anchored: {
190
+ true: '',
191
+ false: 'absolute start-4 top-16 z-30 max-w-[calc(100%-2rem)]',
192
+ },
193
+ },
194
+ defaultVariants: {
195
+ anchored: false,
196
+ },
197
+ },
198
+ );
199
+
200
+ // The panel is as wide as one unwrapped row, capped at the grid's width. Below that, fields wrap
201
+ // onto new lines and grow to fill them, so the layout follows the grid's width, not the viewport.
202
+ export const dataGridFilterPanelRowVariants = cva('flex-row flex-wrap items-end gap-2');
203
+
204
+ export const dataGridFilterPanelLogicVariants = cva('min-h-8 w-24 shrink-0 justify-center');
205
+
206
+ export const dataGridFilterPanelFieldVariants = cva('min-w-0 shrink grow gap-1', {
207
+ variants: {
208
+ size: {
209
+ column: 'w-40',
210
+ operator: 'w-40',
211
+ value: 'w-56',
212
+ },
213
+ },
214
+ });
215
+
216
+ export const dataGridFilterPanelColumnVariants = cva('w-full');
217
+
218
+ export const dataGridFilterPanelOperatorVariants = cva('w-full');
219
+
220
+ export const dataGridFilterPanelValueVariants = cva('w-full');
221
+
222
+ export const dataGridFilterPanelFooterVariants = cva(
223
+ 'flex-row items-center justify-between gap-2 border-t border-stroke-secondary pt-2',
224
+ );
225
+
138
226
  export const dataGridHeadVariants = cva([
139
227
  'h-12 bg-neutral-50 px-4',
140
228
  Platform.select({
@@ -159,6 +247,49 @@ export const dataGridCellVariants = cva([
159
247
  }),
160
248
  ]);
161
249
 
250
+ export const dataGridDetailToggleCellVariants = cva([
251
+ 'w-12 px-2',
252
+ Platform.select({
253
+ web: 'border-b border-stroke-secondary',
254
+ default: '',
255
+ }),
256
+ ]);
257
+
258
+ // Header labels and depth-0 content line up because both reserve the toggle slot.
259
+ export const dataGridTreeCellInnerVariants = cva('min-w-0 flex-1 flex-row items-center gap-2');
260
+
261
+ export const dataGridTreeToggleSlotVariants = cva('size-8 shrink-0 items-center justify-center');
262
+
263
+ export const dataGridTreeContentVariants = cva('min-w-0 flex-1 flex-row items-center');
264
+
265
+ export const dataGridDetailPanelCellVariants = cva([
266
+ 'px-4 py-3',
267
+ Platform.select({
268
+ web: 'border-b border-stroke-secondary bg-surface-primary',
269
+ default: '',
270
+ }),
271
+ ]);
272
+
273
+ // Same `w-0 min-w-full` trick as the header label: a declared column width truncates the value
274
+ // with an ellipsis instead of wrapping it onto a second line and growing the fixed-height row.
275
+ export const dataGridCellTextVariants = cva('min-w-0', {
276
+ variants: {
277
+ align: {
278
+ start: 'text-start',
279
+ center: 'text-center',
280
+ end: 'text-end',
281
+ },
282
+ fitToColumn: {
283
+ true: 'w-0 min-w-full',
284
+ false: '',
285
+ },
286
+ },
287
+ defaultVariants: {
288
+ align: 'start',
289
+ fitToColumn: false,
290
+ },
291
+ });
292
+
162
293
  export const dataGridSelectCellVariants = cva([
163
294
  'w-14 px-4',
164
295
  Platform.select({
package/src/types.ts CHANGED
@@ -28,21 +28,92 @@ export interface GridRowSelectionModel {
28
28
  ids: Set<GridRowId>;
29
29
  }
30
30
 
31
+ /**
32
+ * Filter operators. Names match MUI Data Grid Community so server handlers written for
33
+ * MUI keep working. Which operators a column accepts depends on its `type`.
34
+ */
35
+ export type GridFilterOperator =
36
+ | 'contains'
37
+ | 'doesNotContain'
38
+ | 'equals'
39
+ | 'doesNotEqual'
40
+ | 'startsWith'
41
+ | 'endsWith'
42
+ | 'isEmpty'
43
+ | 'isNotEmpty'
44
+ | 'isAnyOf'
45
+ | '='
46
+ | '!='
47
+ | '>'
48
+ | '>='
49
+ | '<'
50
+ | '<='
51
+ | 'is'
52
+ | 'not'
53
+ | 'after'
54
+ | 'onOrAfter'
55
+ | 'before'
56
+ | 'onOrBefore';
57
+
31
58
  export interface GridFilterItem {
32
59
  field: string;
33
- value: string;
60
+ /** Defaults to `'contains'`, a case-insensitive substring match on the displayed text. */
61
+ operator?: GridFilterOperator;
62
+ /**
63
+ * Operand for `operator`: a string for text, a number for `number`, a `Date` for `date` /
64
+ * `dateTime`, a boolean for `boolean`, a `singleSelect` option value, or an array for
65
+ * `isAnyOf`. `isEmpty` and `isNotEmpty` ignore it.
66
+ */
67
+ value?: unknown;
34
68
  }
35
69
 
36
70
  export type GridFilterModel = GridFilterItem[];
37
71
 
72
+ /**
73
+ * Columns pinned to the logical start and end edges while the grid scrolls
74
+ * horizontally.
75
+ *
76
+ * `left` and `right` are deprecated compatibility aliases for `start` and `end`
77
+ * (same mapping as `GridColDef.align`).
78
+ */
79
+ export interface GridPinnedColumnFields {
80
+ start?: string[];
81
+ end?: string[];
82
+ /**
83
+ * @deprecated Use `start` instead.
84
+ */
85
+ left?: string[];
86
+ /**
87
+ * @deprecated Use `end` instead.
88
+ */
89
+ right?: string[];
90
+ }
91
+
92
+ /** How `filterModel` items combine: every item must match (`'and'`) or any one item (`'or'`). */
93
+ export type GridLogicOperator = 'and' | 'or';
94
+
38
95
  export interface GridRenderCellParams<R extends GridValidRowModel = GridValidRowModel> {
39
96
  value: unknown;
40
97
  row: R;
41
98
  field: string;
42
99
  id: GridRowId;
100
+ /** The column being rendered, including props spread after a column preset. */
101
+ col: GridColDef<R>;
43
102
  }
44
103
 
45
- export interface GridColDef<R extends GridValidRowModel = GridValidRowModel> {
104
+ /**
105
+ * Built-in column types. Names and defaults match MUI Data Grid Community.
106
+ * Omit `type` to keep untyped display, alignment, and sort.
107
+ */
108
+ export type GridColType = 'string' | 'number' | 'date' | 'dateTime' | 'boolean' | 'singleSelect';
109
+
110
+ /**
111
+ * A `singleSelect` choice: a primitive (value and label are the same) or an
112
+ * explicit `{ value, label }` pair.
113
+ */
114
+ export type GridValueOption = string | number | { value: string | number; label: string };
115
+
116
+ export interface GridBaseColDef<R extends GridValidRowModel = GridValidRowModel> {
46
117
  field: string;
47
118
  headerName?: string;
48
119
  width?: number;
@@ -52,28 +123,74 @@ export interface GridColDef<R extends GridValidRowModel = GridValidRowModel> {
52
123
  * Logical cell-content alignment.
53
124
  *
54
125
  * `left` and `right` are deprecated compatibility aliases for `start` and `end`.
55
- * @default 'start'
126
+ * When omitted, `number` defaults to `end`, `boolean` to `center`, and every
127
+ * other type (including omitted `type`) to `start`.
56
128
  */
57
129
  align?: GridAlignment;
58
130
  /**
59
- * Logical header-content alignment. Defaults to `align`.
131
+ * Logical header-content alignment. Defaults to `align`, then the type default.
60
132
  *
61
133
  * `left` and `right` are deprecated compatibility aliases for `start` and `end`.
62
134
  */
63
135
  headerAlign?: GridAlignment;
64
136
  sortable?: boolean;
65
137
  filterable?: boolean;
66
- type?: 'string';
138
+ /**
139
+ * Column value type. Drives default alignment, display formatting, and client
140
+ * sort/filter. Omitted `type` keeps the untyped defaults from the current release.
141
+ */
142
+ type?: Exclude<GridColType, 'singleSelect'>;
67
143
  valueGetter?: (value: unknown, row: R, col: GridColDef<R>) => unknown;
68
144
  valueFormatter?: (value: unknown, row: R, col: GridColDef<R>) => ReactNode | string;
69
145
  renderCell?: (params: GridRenderCellParams<R>) => ReactNode;
70
146
  renderHeader?: () => ReactNode;
71
147
  }
72
148
 
149
+ export interface GridSingleSelectColDef<
150
+ R extends GridValidRowModel = GridValidRowModel,
151
+ > extends Omit<GridBaseColDef<R>, 'type'> {
152
+ type: 'singleSelect';
153
+ /**
154
+ * Choices for a `singleSelect` column. Use `{ value, label }` when the stored
155
+ * value differs from the displayed text. A function receives the row when labels
156
+ * depend on other fields.
157
+ */
158
+ valueOptions: readonly GridValueOption[] | ((params: { row?: R }) => readonly GridValueOption[]);
159
+ }
160
+
161
+ export type GridColDef<R extends GridValidRowModel = GridValidRowModel> =
162
+ | GridBaseColDef<R>
163
+ | GridSingleSelectColDef<R>;
164
+
73
165
  export type GridFeatureMode = 'client' | 'server';
74
166
 
75
167
  export type GridPaginationMode = GridFeatureMode;
76
168
 
169
+ /** Maps a column `field` to `false` to hide it. Fields that are missing or `true` stay visible. */
170
+ export type GridColumnVisibilityModel = Record<string, boolean>;
171
+
172
+ export interface GridRowParams<R extends GridValidRowModel = GridValidRowModel> {
173
+ id: GridRowId;
174
+ row: R;
175
+ }
176
+
177
+ export interface GridTreeNodeParams<
178
+ R extends GridValidRowModel = GridValidRowModel,
179
+ > extends GridRowParams<R> {
180
+ /** `0` for top-level rows. */
181
+ depth: number;
182
+ }
183
+
184
+ /**
185
+ * How a selection change spreads through `treeData`. Both default to `true`, as in MUI.
186
+ */
187
+ export interface GridRowSelectionPropagation {
188
+ /** Selecting or deselecting a parent applies to its descendants that pass the current filter. */
189
+ descendants?: boolean;
190
+ /** A parent is selected exactly when all of its children are selected. */
191
+ parents?: boolean;
192
+ }
193
+
77
194
  export interface DataGridProps<R extends GridValidRowModel = GridValidRowModel> {
78
195
  rows: GridRowsProp<R>;
79
196
  columns: readonly GridColDef<R>[];
@@ -114,14 +231,72 @@ export interface DataGridProps<R extends GridValidRowModel = GridValidRowModel>
114
231
  filterModel?: GridFilterModel;
115
232
  defaultFilterModel?: GridFilterModel;
116
233
  onFilterModelChange?: (model: GridFilterModel) => void;
234
+ /** Combines `filterModel` items. Defaults to `'and'`. The quick filter always applies on top. */
235
+ filterLogicOperator?: GridLogicOperator;
236
+ defaultFilterLogicOperator?: GridLogicOperator;
237
+ onFilterLogicOperatorChange?: (operator: GridLogicOperator) => void;
117
238
  quickFilter?: string;
118
239
  defaultQuickFilter?: string;
119
240
  onQuickFilterChange?: (value: string) => void;
120
- /** Shows the toolbar search field. Off by default, same as MUI Community. */
241
+ /** Shows the toolbar search field and filter button. Off by default, same as MUI Community. */
121
242
  showToolbar?: boolean;
122
- /** Consumer-owned toolbar controls beside search (export, density, toggles, etc.). Wire actions to app state that drives grid props — DataGrid does not ship toolbar buttons. */
243
+ /** Consumer-owned toolbar controls beside search and the filter button (export, density, toggles, etc.). Wire actions to app state that drives grid props. */
123
244
  toolbarActions?: ReactNode;
245
+ /**
246
+ * Pins columns to the logical start and/or end edge while the grid scrolls
247
+ * horizontally. Replaces MUI X Pro `pinnedColumns` (including deprecated
248
+ * `left` / `right` aliases). MUI X `initialState.pinnedColumns` maps here.
249
+ *
250
+ * MUI X Community ships no column pinning; this is a Pro feature.
251
+ */
252
+ pinnedColumns?: GridPinnedColumnFields;
253
+ /**
254
+ * Hides columns without removing them from `columns`. Hidden columns still sort, filter, and
255
+ * match the quick filter, and the filter panel still lists them. The grid has no built-in
256
+ * control that changes visibility, so keep the model in your own state.
257
+ */
258
+ columnVisibilityModel?: GridColumnVisibilityModel;
124
259
  headerFilters?: boolean;
260
+ /**
261
+ * Renders `rows` as a tree. Each row's position comes from `getTreeDataPath`. The first column
262
+ * becomes the tree column: it indents by depth and holds the expand toggle and, with
263
+ * `checkboxSelection`, the row checkbox. Search and filters keep the ancestors of every match
264
+ * and expand them while the filter is active.
265
+ *
266
+ * Client mode only: server pagination, sorting, and filtering are not supported with tree data.
267
+ * Pagination counts top-level rows, so a group never splits across pages.
268
+ * @default false
269
+ */
270
+ treeData?: boolean;
271
+ /**
272
+ * Path from the root to the row, for example `['Accounts', 'OAuth V1', 'List accounts']`.
273
+ * Paths must be unique. A row whose parent path has no row attaches to its nearest existing
274
+ * ancestor. Keep the function reference stable.
275
+ */
276
+ getTreeDataPath?: (row: R) => readonly string[];
277
+ /**
278
+ * Overrides the tree column (the first column) when `treeData` is on: header name, width,
279
+ * `renderCell`, and so on. `field` stays the first column's field.
280
+ */
281
+ groupingColDef?: Partial<Omit<GridBaseColDef<R>, 'field'>>;
282
+ /** Depth expanded on first render. `0` collapses every group; `-1` expands all. @default 0 */
283
+ defaultGroupingExpansionDepth?: number;
284
+ /** Takes priority over `defaultGroupingExpansionDepth` for the initial state of a group. */
285
+ isGroupExpandedByDefault?: (node: GridTreeNodeParams<R>) => boolean;
286
+ /**
287
+ * How selection spreads between parents and children with `treeData`. Unlike MUI, rows hidden
288
+ * by a filter stay selected, so a search-then-pick form keeps earlier picks.
289
+ */
290
+ rowSelectionPropagation?: GridRowSelectionPropagation;
291
+ /**
292
+ * Content of the expandable panel under a row (master-detail). Adds a toggle column at the
293
+ * start. Rows for which this returns `null` or `undefined` get no toggle.
294
+ */
295
+ getDetailPanelContent?: (params: GridRowParams<R>) => ReactNode;
296
+ /** Ids of rows whose detail panel is open (controlled). */
297
+ detailPanelExpandedRowIds?: ReadonlySet<GridRowId>;
298
+ defaultDetailPanelExpandedRowIds?: ReadonlySet<GridRowId>;
299
+ onDetailPanelExpandedRowIdsChange?: (ids: Set<GridRowId>) => void;
125
300
  /** Replaces the default centered "No rows" message. Shown when `rows` is empty and not loading. */
126
301
  noRowsOverlay?: ReactNode;
127
302
  /** Replaces the default skeleton rows shown when `loading` is true. */
@@ -135,6 +310,14 @@ export interface DataGridProps<R extends GridValidRowModel = GridValidRowModel>
135
310
 
136
311
  export const SELECT_COLUMN_ID = '__select__';
137
312
 
313
+ /** Same id as MUI X's master-detail toggle column. */
314
+ export const DETAIL_PANEL_TOGGLE_ID = '__detail_panel_toggle__';
315
+
316
+ export const EMPTY_ROW_ID_SET: ReadonlySet<GridRowId> = new Set();
317
+
318
+ /** MUI X Data Grid checkbox-column field; accepted as an alias for {@link SELECT_COLUMN_ID}. */
319
+ export const MUI_CHECKBOX_FIELD = '__check__';
320
+
138
321
  export const DEFAULT_PAGE_SIZE_OPTIONS = [25, 50, 100] as const;
139
322
 
140
323
  export const DEFAULT_PAGINATION_MODEL: GridPaginationModel = {
@@ -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([