@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
@@ -0,0 +1,379 @@
1
+ import type { ReactNode } from 'react';
2
+ import { View } from 'react-native';
3
+ import { Text } from '@cdx-ui/components';
4
+ import {
5
+ ArrowDropDown,
6
+ ArrowDropUp,
7
+ TrendingDown,
8
+ TrendingFlat,
9
+ TrendingUp,
10
+ type ForgeIcon,
11
+ } from '@cdx-ui/icons';
12
+ import { cn } from '@cdx-ui/utils';
13
+ import type { GridBaseColDef, GridColDef, GridValidRowModel } from './types';
14
+
15
+ /** Unicode minus (U+2212). The spec requires this character, not a hyphen. */
16
+ const MINUS = '\u2212';
17
+
18
+ const defaultNumberFormat = new Intl.NumberFormat(undefined, { maximumFractionDigits: 20 });
19
+
20
+ export type GridNumberCellDensity = 'default' | 'compact';
21
+
22
+ export type GridDeltaNumberSymbol = 'arrow' | 'trendline' | 'sign';
23
+
24
+ export interface GridPolarityNumberColDefOptions {
25
+ /**
26
+ * `default` uses body-md. `compact` uses body-sm.
27
+ * @default 'default'
28
+ */
29
+ density?: GridNumberCellDensity;
30
+ /**
31
+ * Formats the raw signed number. The cell turns a leading hyphen into −.
32
+ * Sorting uses the raw number. The header filter matches the number column's
33
+ * formatted value, not this string.
34
+ */
35
+ valueFormatter?: (value: number) => string;
36
+ }
37
+
38
+ export interface GridDeltaNumberColDefOptions {
39
+ /**
40
+ * Whether an increase is a favorable change. Color follows this together with
41
+ * the value's direction: an increase is success when this is true and danger
42
+ * when it is false; a decrease flips that. Zero stays primary either way.
43
+ * @default true
44
+ */
45
+ increaseIsGood?: boolean;
46
+ /**
47
+ * `arrow` uses drop arrows and leaves flat values blank. `trendline` uses
48
+ * trending icons, including a flat icon at zero. `sign` writes + or − in
49
+ * front of the value. Arrow and trendline hide the minus sign.
50
+ * @default 'arrow'
51
+ */
52
+ symbol?: GridDeltaNumberSymbol;
53
+ /**
54
+ * `default` uses body-md. `compact` uses body-sm.
55
+ * @default 'default'
56
+ */
57
+ density?: GridNumberCellDensity;
58
+ /**
59
+ * Formats the raw signed number. The cell applies the symbol's sign treatment
60
+ * on top of this string. Sorting uses the raw number. The header filter matches
61
+ * the number column's formatted value, not this string.
62
+ */
63
+ valueFormatter?: (value: number) => string;
64
+ }
65
+
66
+ type NumberTone = 'success' | 'danger' | 'neutral';
67
+ type DeltaDirection = 'increase' | 'decrease' | 'flat';
68
+
69
+ const TONE_CLASS: Record<NumberTone, string> = {
70
+ success: 'text-content-success',
71
+ danger: 'text-content-danger',
72
+ neutral: 'text-content-primary',
73
+ };
74
+
75
+ const ARROW_SLOT: Record<GridNumberCellDensity, string> = {
76
+ default: 'h-5 w-3.5 items-center justify-center overflow-hidden',
77
+ compact: 'h-4 w-3 items-center justify-center overflow-hidden',
78
+ };
79
+
80
+ const ARROW_SIZE: Record<GridNumberCellDensity, number> = {
81
+ default: 20,
82
+ compact: 16,
83
+ };
84
+
85
+ const TREND_SIZE: Record<GridNumberCellDensity, number> = {
86
+ default: 14,
87
+ compact: 12,
88
+ };
89
+
90
+ function toFiniteNumber(value: unknown): number | null {
91
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
92
+ }
93
+
94
+ function columnValueFormatter<R extends GridValidRowModel>(
95
+ format: ((value: number) => string) | undefined,
96
+ ): GridColDef<R>['valueFormatter'] {
97
+ if (!format) {
98
+ return undefined;
99
+ }
100
+ return (value) => {
101
+ const numeric = toFiniteNumber(value);
102
+ return numeric == null ? '' : format(numeric);
103
+ };
104
+ }
105
+
106
+ function formatRaw<R extends GridValidRowModel>(value: number, row: R, col: GridColDef<R>): string {
107
+ const formatted = col.valueFormatter?.(value, row, col);
108
+ if (typeof formatted === 'string' && formatted.length > 0) {
109
+ return formatted;
110
+ }
111
+ return defaultNumberFormat.format(value);
112
+ }
113
+
114
+ function unsigned(formatted: string): string {
115
+ const withoutSign = formatted.replace(/^[-−+]\s*/, '');
116
+ if (withoutSign.startsWith('(') && withoutSign.endsWith(')')) {
117
+ return withoutSign.slice(1, -1);
118
+ }
119
+ return withoutSign;
120
+ }
121
+
122
+ function polarityOf(value: number): 'positive' | 'negative' | 'neutral' {
123
+ if (value > 0) {
124
+ return 'positive';
125
+ }
126
+ if (value < 0) {
127
+ return 'negative';
128
+ }
129
+ return 'neutral';
130
+ }
131
+
132
+ function directionOf(value: number): DeltaDirection {
133
+ if (value > 0) {
134
+ return 'increase';
135
+ }
136
+ if (value < 0) {
137
+ return 'decrease';
138
+ }
139
+ return 'flat';
140
+ }
141
+
142
+ function polarityTone(polarity: 'positive' | 'negative' | 'neutral'): NumberTone {
143
+ if (polarity === 'positive') {
144
+ return 'success';
145
+ }
146
+ if (polarity === 'negative') {
147
+ return 'danger';
148
+ }
149
+ return 'neutral';
150
+ }
151
+
152
+ function deltaTone(direction: DeltaDirection, increaseIsGood: boolean): NumberTone {
153
+ if (direction === 'flat') {
154
+ return 'neutral';
155
+ }
156
+ const favorable = direction === 'increase' ? increaseIsGood : !increaseIsGood;
157
+ return favorable ? 'success' : 'danger';
158
+ }
159
+
160
+ function polarityLabel(polarity: 'positive' | 'negative' | 'neutral'): string {
161
+ if (polarity === 'positive') {
162
+ return 'Positive';
163
+ }
164
+ if (polarity === 'negative') {
165
+ return 'Negative';
166
+ }
167
+ return 'Neutral';
168
+ }
169
+
170
+ function directionLabel(direction: DeltaDirection): string {
171
+ if (direction === 'increase') {
172
+ return 'Increase';
173
+ }
174
+ if (direction === 'decrease') {
175
+ return 'Decrease';
176
+ }
177
+ return 'No change';
178
+ }
179
+
180
+ function NumberReadout({
181
+ label,
182
+ display,
183
+ tone,
184
+ density,
185
+ children,
186
+ }: {
187
+ label: string;
188
+ display: string;
189
+ tone: NumberTone;
190
+ density: GridNumberCellDensity;
191
+ children?: ReactNode;
192
+ }) {
193
+ return (
194
+ <View className="relative flex-row items-center gap-0.5">
195
+ <Text className="absolute h-px w-px overflow-hidden opacity-0">{label}</Text>
196
+ <View
197
+ aria-hidden
198
+ accessibilityElementsHidden
199
+ importantForAccessibility="no-hide-descendants"
200
+ className="flex-row items-center gap-0.5"
201
+ >
202
+ {children}
203
+ <Text
204
+ color={tone === 'neutral' ? 'primary' : tone}
205
+ size={density === 'compact' ? 'sm' : 'md'}
206
+ >
207
+ {display}
208
+ </Text>
209
+ </View>
210
+ </View>
211
+ );
212
+ }
213
+
214
+ function ToneIcon({
215
+ icon: Icon,
216
+ tone,
217
+ size,
218
+ slotClassName,
219
+ }: {
220
+ icon: ForgeIcon;
221
+ tone: NumberTone;
222
+ size: number;
223
+ slotClassName?: string;
224
+ }) {
225
+ return (
226
+ <View className={cn(TONE_CLASS[tone], slotClassName)}>
227
+ <Icon size={size} />
228
+ </View>
229
+ );
230
+ }
231
+
232
+ function PolarityNumberCell<R extends GridValidRowModel>({
233
+ value,
234
+ row,
235
+ col,
236
+ density,
237
+ }: {
238
+ value: unknown;
239
+ row: R;
240
+ col: GridColDef<R>;
241
+ density: GridNumberCellDensity;
242
+ }) {
243
+ const numeric = toFiniteNumber(value);
244
+ if (numeric == null) {
245
+ return null;
246
+ }
247
+
248
+ const polarity = polarityOf(numeric);
249
+ const magnitude = unsigned(formatRaw(numeric, row, col));
250
+ const display = polarity === 'negative' ? `${MINUS}${magnitude}` : magnitude;
251
+
252
+ return (
253
+ <NumberReadout
254
+ label={`${polarityLabel(polarity)} ${magnitude}`}
255
+ display={display}
256
+ tone={polarityTone(polarity)}
257
+ density={density}
258
+ />
259
+ );
260
+ }
261
+
262
+ function DeltaSymbol({
263
+ direction,
264
+ tone,
265
+ density,
266
+ symbol,
267
+ }: {
268
+ direction: DeltaDirection;
269
+ tone: NumberTone;
270
+ density: GridNumberCellDensity;
271
+ symbol: GridDeltaNumberSymbol;
272
+ }) {
273
+ if (symbol === 'sign') {
274
+ return null;
275
+ }
276
+
277
+ if (symbol === 'arrow') {
278
+ if (direction === 'flat') {
279
+ return <View className={ARROW_SLOT[density]} />;
280
+ }
281
+ return (
282
+ <ToneIcon
283
+ icon={direction === 'increase' ? ArrowDropUp : ArrowDropDown}
284
+ tone={tone}
285
+ size={ARROW_SIZE[density]}
286
+ slotClassName={ARROW_SLOT[density]}
287
+ />
288
+ );
289
+ }
290
+
291
+ const icon =
292
+ direction === 'increase' ? TrendingUp : direction === 'decrease' ? TrendingDown : TrendingFlat;
293
+ return <ToneIcon icon={icon} tone={tone} size={TREND_SIZE[density]} />;
294
+ }
295
+
296
+ function DeltaNumberCell<R extends GridValidRowModel>({
297
+ value,
298
+ row,
299
+ col,
300
+ density,
301
+ increaseIsGood,
302
+ symbol,
303
+ }: {
304
+ value: unknown;
305
+ row: R;
306
+ col: GridColDef<R>;
307
+ density: GridNumberCellDensity;
308
+ increaseIsGood: boolean;
309
+ symbol: GridDeltaNumberSymbol;
310
+ }) {
311
+ const numeric = toFiniteNumber(value);
312
+ if (numeric == null) {
313
+ return null;
314
+ }
315
+
316
+ const direction = directionOf(numeric);
317
+ const tone = deltaTone(direction, increaseIsGood);
318
+ const magnitude = unsigned(formatRaw(numeric, row, col));
319
+ let display = magnitude;
320
+ if (symbol === 'sign' && direction === 'increase') {
321
+ display = `+${magnitude}`;
322
+ } else if (symbol === 'sign' && direction === 'decrease') {
323
+ display = `${MINUS}${magnitude}`;
324
+ }
325
+
326
+ return (
327
+ <NumberReadout
328
+ label={`${directionLabel(direction)} ${magnitude}`}
329
+ display={display}
330
+ tone={tone}
331
+ density={density}
332
+ >
333
+ <DeltaSymbol direction={direction} tone={tone} density={density} symbol={symbol} />
334
+ </NumberReadout>
335
+ );
336
+ }
337
+
338
+ export function gridPolarityNumberColDef<R extends GridValidRowModel>(
339
+ options: GridPolarityNumberColDefOptions = {},
340
+ ): Pick<GridBaseColDef<R>, 'type' | 'align' | 'headerAlign' | 'renderCell' | 'valueFormatter'> {
341
+ const density = options.density ?? 'default';
342
+ return {
343
+ type: 'number',
344
+ align: 'end',
345
+ headerAlign: 'end',
346
+ ...(options.valueFormatter
347
+ ? { valueFormatter: columnValueFormatter<R>(options.valueFormatter) }
348
+ : {}),
349
+ renderCell: ({ value, row, col }) => (
350
+ <PolarityNumberCell value={value} row={row} col={col} density={density} />
351
+ ),
352
+ };
353
+ }
354
+
355
+ export function gridDeltaNumberColDef<R extends GridValidRowModel>(
356
+ options: GridDeltaNumberColDefOptions = {},
357
+ ): Pick<GridBaseColDef<R>, 'type' | 'align' | 'headerAlign' | 'renderCell' | 'valueFormatter'> {
358
+ const density = options.density ?? 'default';
359
+ const increaseIsGood = options.increaseIsGood ?? true;
360
+ const symbol = options.symbol ?? 'arrow';
361
+ return {
362
+ type: 'number',
363
+ align: 'end',
364
+ headerAlign: 'end',
365
+ ...(options.valueFormatter
366
+ ? { valueFormatter: columnValueFormatter<R>(options.valueFormatter) }
367
+ : {}),
368
+ renderCell: ({ value, row, col }) => (
369
+ <DeltaNumberCell
370
+ value={value}
371
+ row={row}
372
+ col={col}
373
+ density={density}
374
+ increaseIsGood={increaseIsGood}
375
+ symbol={symbol}
376
+ />
377
+ ),
378
+ };
379
+ }
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,36 @@ export const dataGridBodyVariants = cva(
135
180
 
136
181
  export const dataGridFilterInputVariants = cva('min-w-24');
137
182
 
183
+ export const dataGridFilterPanelVariants = cva(
184
+ 'absolute start-4 top-16 z-30 w-max max-w-[calc(100%-2rem)] gap-3 rounded-lg border border-stroke-secondary bg-surface-primary px-3 pb-2 pt-3 shadow-lg',
185
+ );
186
+
187
+ // The panel is as wide as one unwrapped row, capped at the grid's width. Below that, fields wrap
188
+ // onto new lines and grow to fill them, so the layout follows the grid's width, not the viewport.
189
+ export const dataGridFilterPanelRowVariants = cva('flex-row flex-wrap items-end gap-2');
190
+
191
+ export const dataGridFilterPanelLogicVariants = cva('min-h-8 w-24 shrink-0 justify-center');
192
+
193
+ export const dataGridFilterPanelFieldVariants = cva('min-w-0 shrink grow gap-1', {
194
+ variants: {
195
+ size: {
196
+ column: 'w-40',
197
+ operator: 'w-40',
198
+ value: 'w-56',
199
+ },
200
+ },
201
+ });
202
+
203
+ export const dataGridFilterPanelColumnVariants = cva('w-full');
204
+
205
+ export const dataGridFilterPanelOperatorVariants = cva('w-full');
206
+
207
+ export const dataGridFilterPanelValueVariants = cva('w-full');
208
+
209
+ export const dataGridFilterPanelFooterVariants = cva(
210
+ 'flex-row items-center justify-between gap-2 border-t border-stroke-secondary pt-2',
211
+ );
212
+
138
213
  export const dataGridHeadVariants = cva([
139
214
  'h-12 bg-neutral-50 px-4',
140
215
  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,24 +123,45 @@ 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;
@@ -114,13 +206,25 @@ export interface DataGridProps<R extends GridValidRowModel = GridValidRowModel>
114
206
  filterModel?: GridFilterModel;
115
207
  defaultFilterModel?: GridFilterModel;
116
208
  onFilterModelChange?: (model: GridFilterModel) => void;
209
+ /** Combines `filterModel` items. Defaults to `'and'`. The quick filter always applies on top. */
210
+ filterLogicOperator?: GridLogicOperator;
211
+ defaultFilterLogicOperator?: GridLogicOperator;
212
+ onFilterLogicOperatorChange?: (operator: GridLogicOperator) => void;
117
213
  quickFilter?: string;
118
214
  defaultQuickFilter?: string;
119
215
  onQuickFilterChange?: (value: string) => void;
120
- /** Shows the toolbar search field. Off by default, same as MUI Community. */
216
+ /** Shows the toolbar search field and filter button. Off by default, same as MUI Community. */
121
217
  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. */
218
+ /** Consumer-owned toolbar controls beside search and the filter button (export, density, toggles, etc.). Wire actions to app state that drives grid props. */
123
219
  toolbarActions?: ReactNode;
220
+ /**
221
+ * Pins columns to the logical start and/or end edge while the grid scrolls
222
+ * horizontally. Replaces MUI X Pro `pinnedColumns` (including deprecated
223
+ * `left` / `right` aliases). MUI X `initialState.pinnedColumns` maps here.
224
+ *
225
+ * MUI X Community ships no column pinning; this is a Pro feature.
226
+ */
227
+ pinnedColumns?: GridPinnedColumnFields;
124
228
  headerFilters?: boolean;
125
229
  /** Replaces the default centered "No rows" message. Shown when `rows` is empty and not loading. */
126
230
  noRowsOverlay?: ReactNode;
@@ -135,6 +239,9 @@ export interface DataGridProps<R extends GridValidRowModel = GridValidRowModel>
135
239
 
136
240
  export const SELECT_COLUMN_ID = '__select__';
137
241
 
242
+ /** MUI X Data Grid checkbox-column field; accepted as an alias for {@link SELECT_COLUMN_ID}. */
243
+ export const MUI_CHECKBOX_FIELD = '__check__';
244
+
138
245
  export const DEFAULT_PAGE_SIZE_OPTIONS = [25, 50, 100] as const;
139
246
 
140
247
  export const DEFAULT_PAGINATION_MODEL: GridPaginationModel = {