@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.
- package/README.md +3 -1
- package/coverage.svg +1 -1
- package/lib/commonjs/DataGrid.js +490 -81
- package/lib/commonjs/DataGrid.js.map +1 -1
- package/lib/commonjs/DataGridFilterPanel.js +547 -0
- package/lib/commonjs/DataGridFilterPanel.js.map +1 -0
- package/lib/commonjs/adapter/columnTypes.js +256 -0
- package/lib/commonjs/adapter/columnTypes.js.map +1 -0
- package/lib/commonjs/adapter/columns.js +140 -4
- package/lib/commonjs/adapter/columns.js.map +1 -1
- package/lib/commonjs/adapter/filterOperators.js +422 -0
- package/lib/commonjs/adapter/filterOperators.js.map +1 -0
- package/lib/commonjs/adapter/pinning.js +105 -0
- package/lib/commonjs/adapter/pinning.js.map +1 -0
- package/lib/commonjs/adapter/selectColumn.js +11 -0
- package/lib/commonjs/adapter/selectColumn.js.map +1 -1
- package/lib/commonjs/adapter/treeData.js +195 -0
- package/lib/commonjs/adapter/treeData.js.map +1 -0
- package/lib/commonjs/index.js +26 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/numberCells.js +280 -0
- package/lib/commonjs/numberCells.js.map +1 -0
- package/lib/commonjs/styles.js +107 -1
- package/lib/commonjs/styles.js.map +1 -1
- package/lib/commonjs/types.js +39 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/useDataGridTable.js +234 -18
- package/lib/commonjs/useDataGridTable.js.map +1 -1
- package/lib/module/DataGrid.js +497 -88
- package/lib/module/DataGrid.js.map +1 -1
- package/lib/module/DataGridFilterPanel.js +540 -0
- package/lib/module/DataGridFilterPanel.js.map +1 -0
- package/lib/module/adapter/columnTypes.js +240 -0
- package/lib/module/adapter/columnTypes.js.map +1 -0
- package/lib/module/adapter/columns.js +137 -4
- package/lib/module/adapter/columns.js.map +1 -1
- package/lib/module/adapter/filterOperators.js +406 -0
- package/lib/module/adapter/filterOperators.js.map +1 -0
- package/lib/module/adapter/pinning.js +94 -0
- package/lib/module/adapter/pinning.js.map +1 -0
- package/lib/module/adapter/selectColumn.js +11 -1
- package/lib/module/adapter/selectColumn.js.map +1 -1
- package/lib/module/adapter/treeData.js +187 -0
- package/lib/module/adapter/treeData.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/numberCells.js +275 -0
- package/lib/module/numberCells.js.map +1 -0
- package/lib/module/styles.js +106 -0
- package/lib/module/styles.js.map +1 -1
- package/lib/module/types.js +38 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/useDataGridTable.js +238 -22
- package/lib/module/useDataGridTable.js.map +1 -1
- package/lib/typescript/DataGrid.d.ts.map +1 -1
- package/lib/typescript/DataGridFilterPanel.d.ts +52 -0
- package/lib/typescript/DataGridFilterPanel.d.ts.map +1 -0
- package/lib/typescript/adapter/columnTypes.d.ts +32 -0
- package/lib/typescript/adapter/columnTypes.d.ts.map +1 -0
- package/lib/typescript/adapter/columns.d.ts +14 -5
- package/lib/typescript/adapter/columns.d.ts.map +1 -1
- package/lib/typescript/adapter/filterOperators.d.ts +43 -0
- package/lib/typescript/adapter/filterOperators.d.ts.map +1 -0
- package/lib/typescript/adapter/pinning.d.ts +26 -0
- package/lib/typescript/adapter/pinning.d.ts.map +1 -0
- package/lib/typescript/adapter/selectColumn.d.ts +8 -0
- package/lib/typescript/adapter/selectColumn.d.ts.map +1 -1
- package/lib/typescript/adapter/treeData.d.ts +36 -0
- package/lib/typescript/adapter/treeData.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +4 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/numberCells.d.ts +46 -0
- package/lib/typescript/numberCells.d.ts.map +1 -0
- package/lib/typescript/styles.d.ts +32 -0
- package/lib/typescript/styles.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +154 -7
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/useDataGridTable.d.ts +40 -4
- package/lib/typescript/useDataGridTable.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/DataGrid.tsx +745 -95
- package/src/DataGridFilterPanel.tsx +648 -0
- package/src/adapter/columnTypes.ts +327 -0
- package/src/adapter/columns.test.ts +75 -1
- package/src/adapter/columns.ts +192 -3
- package/src/adapter/filterOperators.test.ts +292 -0
- package/src/adapter/filterOperators.ts +524 -0
- package/src/adapter/pinning.ts +134 -0
- package/src/adapter/selectColumn.ts +12 -1
- package/src/adapter/treeData.ts +228 -0
- package/src/index.ts +19 -0
- package/src/numberCells.tsx +379 -0
- package/src/styles.ts +131 -0
- package/src/types.ts +190 -7
- package/src/useDataGridTable.test.ts +69 -0
- package/src/useDataGridTable.ts +338 -17
package/src/DataGrid.tsx
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type CSSProperties,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { I18nManager, Platform, View, type TextInput, type ViewStyle } from 'react-native';
|
|
3
11
|
import {
|
|
4
12
|
Checkbox,
|
|
13
|
+
Icon,
|
|
5
14
|
IconButton,
|
|
6
15
|
Input,
|
|
7
16
|
Skeleton,
|
|
@@ -9,13 +18,43 @@ import {
|
|
|
9
18
|
TablePagination,
|
|
10
19
|
Text,
|
|
11
20
|
} from '@cdx-ui/components';
|
|
12
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
Check,
|
|
23
|
+
Close,
|
|
24
|
+
FilterList,
|
|
25
|
+
KeyboardArrowDown,
|
|
26
|
+
KeyboardArrowRight,
|
|
27
|
+
KeyboardArrowUp,
|
|
28
|
+
Search,
|
|
29
|
+
} from '@cdx-ui/icons';
|
|
13
30
|
import { dataAttributes, mergeDataAttributes } from '@cdx-ui/primitives';
|
|
14
31
|
import { cn, useControllableState } from '@cdx-ui/utils';
|
|
15
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
normalizePinnedColumns,
|
|
34
|
+
resolveColumnAlign,
|
|
35
|
+
resolveColumnHeaderAlign,
|
|
36
|
+
} from './adapter/columns';
|
|
37
|
+
import {
|
|
38
|
+
DEFAULT_PINNED_COLUMN_WIDTH,
|
|
39
|
+
DETAIL_PANEL_TOGGLE_WIDTH,
|
|
40
|
+
SELECT_COLUMN_WIDTH,
|
|
41
|
+
buildPinnedOffsets,
|
|
42
|
+
isColumnPinningActive,
|
|
43
|
+
pinnedCellStyle,
|
|
44
|
+
pinnedEdgeGuidesImage,
|
|
45
|
+
pinnedFieldSet,
|
|
46
|
+
warnPinnedFlexColumns,
|
|
47
|
+
type ColumnPinEdge,
|
|
48
|
+
type PinnedOffsetMaps,
|
|
49
|
+
} from './adapter/pinning';
|
|
50
|
+
import { getTypedCellText } from './adapter/columnTypes';
|
|
51
|
+
import { DataGridFilterPanel, useDataGridFilterPanel } from './DataGridFilterPanel';
|
|
16
52
|
import {
|
|
17
53
|
dataGridBodyVariants,
|
|
54
|
+
dataGridCellTextVariants,
|
|
18
55
|
dataGridCellVariants,
|
|
56
|
+
dataGridDetailPanelCellVariants,
|
|
57
|
+
dataGridDetailToggleCellVariants,
|
|
19
58
|
dataGridEmptyOverlayVariants,
|
|
20
59
|
dataGridFilterInputVariants,
|
|
21
60
|
dataGridFooterVariants,
|
|
@@ -27,6 +66,8 @@ import {
|
|
|
27
66
|
dataGridOverlayVariants,
|
|
28
67
|
dataGridPaginationRangeVariants,
|
|
29
68
|
dataGridPaginationVariants,
|
|
69
|
+
dataGridPinnedEdgeVariants,
|
|
70
|
+
dataGridPinnedSurfaceVariants,
|
|
30
71
|
dataGridRootVariants,
|
|
31
72
|
dataGridSelectCellVariants,
|
|
32
73
|
dataGridSelectHeadVariants,
|
|
@@ -35,22 +76,38 @@ import {
|
|
|
35
76
|
dataGridToolbarActionsVariants,
|
|
36
77
|
dataGridToolbarSearchVariants,
|
|
37
78
|
dataGridToolbarVariants,
|
|
79
|
+
dataGridTreeCellInnerVariants,
|
|
80
|
+
dataGridTreeContentVariants,
|
|
81
|
+
dataGridTreeToggleSlotVariants,
|
|
38
82
|
} from './styles';
|
|
39
83
|
import {
|
|
40
84
|
DEFAULT_PAGE_SIZE_OPTIONS,
|
|
41
85
|
DEFAULT_PAGINATION_MODEL,
|
|
86
|
+
DETAIL_PANEL_TOGGLE_ID,
|
|
87
|
+
EMPTY_ROW_ID_SET,
|
|
42
88
|
EMPTY_SELECTION,
|
|
43
89
|
SELECT_COLUMN_ID,
|
|
44
90
|
type DataGridProps,
|
|
45
91
|
type GridColDef,
|
|
92
|
+
type GridRowId,
|
|
46
93
|
type GridValidRowModel,
|
|
47
94
|
} from './types';
|
|
48
95
|
import { resolveRowId, useDataGridTable } from './useDataGridTable';
|
|
49
96
|
|
|
50
97
|
const MAX_SKELETON_ROW_COUNT = 10;
|
|
98
|
+
/** Matches the detail panel cell's `px-4`. */
|
|
99
|
+
const DETAIL_PANEL_INSET = 16;
|
|
100
|
+
|
|
101
|
+
const TREE_INDENT = 24;
|
|
51
102
|
const DEFAULT_WEB_COLUMN_WIDTH = 100;
|
|
52
|
-
const SELECT_COLUMN_WIDTH = 56;
|
|
53
103
|
const WEB_TABLE_CONTAINER_STYLE: ViewStyle = { overflow: 'visible' };
|
|
104
|
+
// Same token as the pinned cells' `border-stroke-secondary` divider.
|
|
105
|
+
const PINNED_EDGE_COLOR = 'var(--color-stroke-secondary)';
|
|
106
|
+
|
|
107
|
+
interface PinRenderMeta {
|
|
108
|
+
edge: ColumnPinEdge | false;
|
|
109
|
+
isEdgeDivider: boolean;
|
|
110
|
+
}
|
|
54
111
|
|
|
55
112
|
function cellStyle<R extends GridValidRowModel>(
|
|
56
113
|
col: GridColDef<R> | undefined,
|
|
@@ -78,16 +135,32 @@ function cellStyle<R extends GridValidRowModel>(
|
|
|
78
135
|
return Object.keys(style).length > 0 ? style : undefined;
|
|
79
136
|
}
|
|
80
137
|
|
|
138
|
+
function effectiveFlex<R extends GridValidRowModel>(
|
|
139
|
+
column: GridColDef<R>,
|
|
140
|
+
pinned: ReadonlySet<string>,
|
|
141
|
+
): number {
|
|
142
|
+
if (pinned.has(column.field)) {
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
return Math.max(column.flex ?? 0, 0);
|
|
146
|
+
}
|
|
147
|
+
|
|
81
148
|
function webColumnWidths<R extends GridValidRowModel>(
|
|
82
149
|
columns: readonly GridColDef<R>[],
|
|
83
|
-
|
|
150
|
+
leadingWidth: number,
|
|
151
|
+
pinned: ReadonlySet<string> = new Set(),
|
|
84
152
|
): Map<string, CSSProperties['width']> {
|
|
85
|
-
const totalFlex = columns.reduce((total, column) => total +
|
|
153
|
+
const totalFlex = columns.reduce((total, column) => total + effectiveFlex(column, pinned), 0);
|
|
86
154
|
const widths = new Map<string, CSSProperties['width']>();
|
|
87
155
|
|
|
88
156
|
if (totalFlex === 0) {
|
|
89
157
|
for (const column of columns) {
|
|
90
|
-
if (column.
|
|
158
|
+
if (pinned.has(column.field)) {
|
|
159
|
+
widths.set(
|
|
160
|
+
column.field,
|
|
161
|
+
Math.max(column.width ?? DEFAULT_PINNED_COLUMN_WIDTH, column.minWidth ?? 0),
|
|
162
|
+
);
|
|
163
|
+
} else if (column.width !== undefined) {
|
|
91
164
|
widths.set(column.field, column.width);
|
|
92
165
|
}
|
|
93
166
|
}
|
|
@@ -95,17 +168,21 @@ function webColumnWidths<R extends GridValidRowModel>(
|
|
|
95
168
|
}
|
|
96
169
|
|
|
97
170
|
const fixedWidth =
|
|
98
|
-
|
|
171
|
+
leadingWidth +
|
|
99
172
|
columns.reduce((total, column) => {
|
|
100
|
-
if ((column
|
|
173
|
+
if (effectiveFlex(column, pinned) > 0) {
|
|
101
174
|
return total;
|
|
102
175
|
}
|
|
103
|
-
|
|
176
|
+
const fallback = pinned.has(column.field)
|
|
177
|
+
? DEFAULT_PINNED_COLUMN_WIDTH
|
|
178
|
+
: DEFAULT_WEB_COLUMN_WIDTH;
|
|
179
|
+
return total + Math.max(column.width ?? fallback, column.minWidth ?? 0);
|
|
104
180
|
}, 0);
|
|
105
181
|
|
|
106
182
|
for (const column of columns) {
|
|
107
|
-
|
|
108
|
-
|
|
183
|
+
const flex = effectiveFlex(column, pinned);
|
|
184
|
+
if (flex > 0) {
|
|
185
|
+
const share = flex / totalFlex;
|
|
109
186
|
const percentage = share * 100;
|
|
110
187
|
const fixedShare = fixedWidth * share;
|
|
111
188
|
const width = `calc(${percentage.toString()}% - ${fixedShare.toString()}px)`;
|
|
@@ -114,16 +191,65 @@ function webColumnWidths<R extends GridValidRowModel>(
|
|
|
114
191
|
column.minWidth === undefined ? width : `max(${column.minWidth.toString()}px, ${width})`,
|
|
115
192
|
);
|
|
116
193
|
} else {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
);
|
|
194
|
+
const fallback = pinned.has(column.field)
|
|
195
|
+
? DEFAULT_PINNED_COLUMN_WIDTH
|
|
196
|
+
: DEFAULT_WEB_COLUMN_WIDTH;
|
|
197
|
+
widths.set(column.field, Math.max(column.width ?? fallback, column.minWidth ?? 0));
|
|
121
198
|
}
|
|
122
199
|
}
|
|
123
200
|
|
|
124
201
|
return widths;
|
|
125
202
|
}
|
|
126
203
|
|
|
204
|
+
function pinMetaFor(columnId: string, pinning: { start: string[]; end: string[] }): PinRenderMeta {
|
|
205
|
+
const startIndex = pinning.start.indexOf(columnId);
|
|
206
|
+
if (startIndex >= 0) {
|
|
207
|
+
return {
|
|
208
|
+
edge: 'start',
|
|
209
|
+
isEdgeDivider: startIndex === pinning.start.length - 1,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const endIndex = pinning.end.indexOf(columnId);
|
|
213
|
+
if (endIndex >= 0) {
|
|
214
|
+
return {
|
|
215
|
+
edge: 'end',
|
|
216
|
+
isEdgeDivider: endIndex === 0,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return { edge: false, isEdgeDivider: false };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function mergePinnedStyle(
|
|
223
|
+
base: ViewStyle | undefined,
|
|
224
|
+
pin: PinRenderMeta,
|
|
225
|
+
offsets: PinnedOffsetMaps | null,
|
|
226
|
+
columnId: string,
|
|
227
|
+
isHeader: boolean,
|
|
228
|
+
): ViewStyle | undefined {
|
|
229
|
+
if (!pin.edge || offsets == null) {
|
|
230
|
+
return base;
|
|
231
|
+
}
|
|
232
|
+
const offset =
|
|
233
|
+
pin.edge === 'start' ? (offsets.start.get(columnId) ?? 0) : (offsets.end.get(columnId) ?? 0);
|
|
234
|
+
return { ...base, ...pinnedCellStyle(pin.edge, offset, isHeader) };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function pinnedClassName(pin: PinRenderMeta, isHeader: boolean): string | undefined {
|
|
238
|
+
if (!pin.edge) {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
return cn(
|
|
242
|
+
dataGridPinnedSurfaceVariants({ header: isHeader }),
|
|
243
|
+
dataGridPinnedEdgeVariants({ edge: pin.isEdgeDivider ? pin.edge : 'none' }),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function BooleanCell({ value }: { value: boolean }) {
|
|
248
|
+
return (
|
|
249
|
+
<Icon as={value ? Check : Close} accessibilityLabel={value ? 'Yes' : 'No'} className="size-5" />
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
127
253
|
function defaultCellContent(value: unknown): ReactNode {
|
|
128
254
|
if (value == null) {
|
|
129
255
|
return '';
|
|
@@ -145,15 +271,24 @@ function defaultCellContent(value: unknown): ReactNode {
|
|
|
145
271
|
|
|
146
272
|
function DefaultLoadingRows<R extends GridValidRowModel>({
|
|
147
273
|
columns,
|
|
148
|
-
|
|
274
|
+
leadingColumnIds,
|
|
149
275
|
rowCount,
|
|
150
276
|
columnWidths,
|
|
277
|
+
orderedColumnIds,
|
|
278
|
+
pinning,
|
|
279
|
+
offsets,
|
|
151
280
|
}: {
|
|
152
281
|
columns: readonly GridColDef<R>[];
|
|
153
|
-
|
|
282
|
+
leadingColumnIds: readonly string[];
|
|
154
283
|
rowCount: number;
|
|
155
284
|
columnWidths: ReadonlyMap<string, CSSProperties['width']>;
|
|
285
|
+
orderedColumnIds?: readonly string[];
|
|
286
|
+
pinning: { start: string[]; end: string[] };
|
|
287
|
+
offsets: PinnedOffsetMaps | null;
|
|
156
288
|
}) {
|
|
289
|
+
const colByField = new Map(columns.map((column) => [column.field, column]));
|
|
290
|
+
const ids = orderedColumnIds ?? [...leadingColumnIds, ...columns.map((column) => column.field)];
|
|
291
|
+
|
|
157
292
|
return (
|
|
158
293
|
<>
|
|
159
294
|
{Array.from({ length: rowCount }, (_, rowIndex) => (
|
|
@@ -162,21 +297,50 @@ function DefaultLoadingRows<R extends GridValidRowModel>({
|
|
|
162
297
|
className="pointer-events-none web:hover:bg-surface-primary"
|
|
163
298
|
testID={rowIndex === 0 ? 'data-grid-default-loading-rows' : undefined}
|
|
164
299
|
>
|
|
165
|
-
{
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
300
|
+
{ids.map((columnId) => {
|
|
301
|
+
const pin = pinMetaFor(columnId, pinning);
|
|
302
|
+
if (columnId === SELECT_COLUMN_ID) {
|
|
303
|
+
return (
|
|
304
|
+
<Table.Cell
|
|
305
|
+
key={columnId}
|
|
306
|
+
align="center"
|
|
307
|
+
className={cn(dataGridSelectCellVariants(), pinnedClassName(pin, false))}
|
|
308
|
+
style={mergePinnedStyle(undefined, pin, offsets, columnId, false)}
|
|
309
|
+
>
|
|
310
|
+
<Skeleton className="size-5" />
|
|
311
|
+
</Table.Cell>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (columnId === DETAIL_PANEL_TOGGLE_ID) {
|
|
315
|
+
return (
|
|
316
|
+
<Table.Cell
|
|
317
|
+
key={columnId}
|
|
318
|
+
className={cn(dataGridDetailToggleCellVariants(), pinnedClassName(pin, false))}
|
|
319
|
+
style={mergePinnedStyle(undefined, pin, offsets, columnId, false)}
|
|
320
|
+
/>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
const column = colByField.get(columnId);
|
|
324
|
+
if (!column) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return (
|
|
328
|
+
<Table.Cell
|
|
329
|
+
key={column.field}
|
|
330
|
+
align={resolveColumnAlign(column)}
|
|
331
|
+
className={cn(dataGridCellVariants(), pinnedClassName(pin, false))}
|
|
332
|
+
style={mergePinnedStyle(
|
|
333
|
+
cellStyle(column, columnWidths.get(column.field)),
|
|
334
|
+
pin,
|
|
335
|
+
offsets,
|
|
336
|
+
column.field,
|
|
337
|
+
false,
|
|
338
|
+
)}
|
|
339
|
+
>
|
|
340
|
+
<Skeleton className="h-4 w-3/5" />
|
|
341
|
+
</Table.Cell>
|
|
342
|
+
);
|
|
343
|
+
})}
|
|
180
344
|
</Table.Row>
|
|
181
345
|
))}
|
|
182
346
|
</>
|
|
@@ -219,6 +383,8 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
219
383
|
rowCount,
|
|
220
384
|
showToolbar = false,
|
|
221
385
|
headerFilters = false,
|
|
386
|
+
pinnedColumns,
|
|
387
|
+
columnVisibilityModel,
|
|
222
388
|
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
|
|
223
389
|
noRowsOverlay,
|
|
224
390
|
loadingOverlay,
|
|
@@ -228,8 +394,66 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
228
394
|
label,
|
|
229
395
|
'aria-label': ariaLabel,
|
|
230
396
|
'aria-labelledby': ariaLabelledBy,
|
|
397
|
+
treeData = false,
|
|
398
|
+
getTreeDataPath,
|
|
399
|
+
groupingColDef,
|
|
400
|
+
defaultGroupingExpansionDepth,
|
|
401
|
+
isGroupExpandedByDefault,
|
|
402
|
+
rowSelectionPropagation,
|
|
403
|
+
getDetailPanelContent,
|
|
231
404
|
} = props;
|
|
232
405
|
|
|
406
|
+
const treeActive = treeData && getTreeDataPath !== undefined;
|
|
407
|
+
const isColumnVisible = useCallback(
|
|
408
|
+
(column: GridColDef<R>) => columnVisibilityModel?.[column.field] !== false,
|
|
409
|
+
[columnVisibilityModel],
|
|
410
|
+
);
|
|
411
|
+
const treeField = treeActive ? columns.find(isColumnVisible)?.field : undefined;
|
|
412
|
+
const gridColumns = useMemo((): readonly GridColDef<R>[] => {
|
|
413
|
+
if (treeField === undefined || groupingColDef === undefined) {
|
|
414
|
+
return columns;
|
|
415
|
+
}
|
|
416
|
+
return columns.map((column) =>
|
|
417
|
+
column.field === treeField
|
|
418
|
+
? ({ ...column, ...groupingColDef, field: column.field } as GridColDef<R>)
|
|
419
|
+
: column,
|
|
420
|
+
);
|
|
421
|
+
}, [columns, groupingColDef, treeField]);
|
|
422
|
+
const visibleColumns = useMemo(
|
|
423
|
+
() => gridColumns.filter(isColumnVisible),
|
|
424
|
+
[gridColumns, isColumnVisible],
|
|
425
|
+
);
|
|
426
|
+
const selectColumn = checkboxSelection && !treeActive;
|
|
427
|
+
const treeCheckbox = checkboxSelection && treeActive;
|
|
428
|
+
const detailPanel = getDetailPanelContent !== undefined;
|
|
429
|
+
const leadingColumnIds = useMemo(
|
|
430
|
+
() => [
|
|
431
|
+
...(selectColumn ? [SELECT_COLUMN_ID] : []),
|
|
432
|
+
...(detailPanel ? [DETAIL_PANEL_TOGGLE_ID] : []),
|
|
433
|
+
],
|
|
434
|
+
[detailPanel, selectColumn],
|
|
435
|
+
);
|
|
436
|
+
const leadingWidth =
|
|
437
|
+
(selectColumn ? SELECT_COLUMN_WIDTH : 0) + (detailPanel ? DETAIL_PANEL_TOGGLE_WIDTH : 0);
|
|
438
|
+
|
|
439
|
+
const [detailPanelExpandedRowIds, setDetailPanelExpandedRowIds] = useControllableState<
|
|
440
|
+
ReadonlySet<GridRowId>
|
|
441
|
+
>({
|
|
442
|
+
prop: props.detailPanelExpandedRowIds,
|
|
443
|
+
defaultProp: props.defaultDetailPanelExpandedRowIds ?? EMPTY_ROW_ID_SET,
|
|
444
|
+
onChange: (ids) => props.onDetailPanelExpandedRowIdsChange?.(new Set(ids)),
|
|
445
|
+
});
|
|
446
|
+
const resolvedDetailIds = detailPanelExpandedRowIds ?? EMPTY_ROW_ID_SET;
|
|
447
|
+
const toggleDetailPanel = (id: GridRowId) => {
|
|
448
|
+
const next = new Set(resolvedDetailIds);
|
|
449
|
+
if (next.has(id)) {
|
|
450
|
+
next.delete(id);
|
|
451
|
+
} else {
|
|
452
|
+
next.add(id);
|
|
453
|
+
}
|
|
454
|
+
setDetailPanelExpandedRowIds(next);
|
|
455
|
+
};
|
|
456
|
+
|
|
233
457
|
const [sortModel, setSortModel] = useControllableState({
|
|
234
458
|
prop: props.sortModel,
|
|
235
459
|
defaultProp: props.defaultSortModel ?? [],
|
|
@@ -245,6 +469,11 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
245
469
|
defaultProp: props.defaultFilterModel ?? [],
|
|
246
470
|
onChange: props.onFilterModelChange,
|
|
247
471
|
});
|
|
472
|
+
const [filterLogicOperator, setFilterLogicOperator] = useControllableState({
|
|
473
|
+
prop: props.filterLogicOperator,
|
|
474
|
+
defaultProp: props.defaultFilterLogicOperator ?? 'and',
|
|
475
|
+
onChange: props.onFilterLogicOperatorChange,
|
|
476
|
+
});
|
|
248
477
|
const [quickFilter, setQuickFilter] = useControllableState({
|
|
249
478
|
prop: props.quickFilter,
|
|
250
479
|
defaultProp: props.defaultQuickFilter ?? '',
|
|
@@ -259,17 +488,34 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
259
488
|
const resolvedSortModel = sortModel ?? [];
|
|
260
489
|
const resolvedPagination = paginationModel ?? DEFAULT_PAGINATION_MODEL;
|
|
261
490
|
const resolvedFilterModel = filterModel ?? [];
|
|
491
|
+
const resolvedFilterLogicOperator = filterLogicOperator ?? 'and';
|
|
262
492
|
const resolvedQuickFilter = quickFilter ?? '';
|
|
263
493
|
const resolvedSelection = rowSelectionModel ?? EMPTY_SELECTION;
|
|
264
494
|
|
|
495
|
+
const knownFields = useMemo(() => new Set(columns.map((column) => column.field)), [columns]);
|
|
496
|
+
const columnPinning = useMemo(
|
|
497
|
+
() => normalizePinnedColumns(pinnedColumns, knownFields, selectColumn, detailPanel),
|
|
498
|
+
[pinnedColumns, knownFields, selectColumn, detailPanel],
|
|
499
|
+
);
|
|
500
|
+
const pinningActive = isColumnPinningActive(columnPinning);
|
|
501
|
+
const pinnedFields = useMemo(() => pinnedFieldSet(columnPinning), [columnPinning]);
|
|
502
|
+
|
|
265
503
|
const {
|
|
266
504
|
table,
|
|
267
505
|
setQuickFilter: setQuickFilterWithPageReset,
|
|
268
506
|
setColumnFilter,
|
|
507
|
+
setFilterModel: setFilterModelWithPageReset,
|
|
508
|
+
tree,
|
|
269
509
|
} = useDataGridTable({
|
|
270
510
|
rows,
|
|
271
|
-
columns,
|
|
511
|
+
columns: gridColumns,
|
|
272
512
|
getRowId,
|
|
513
|
+
detailPanel,
|
|
514
|
+
treeData: treeActive,
|
|
515
|
+
getTreeDataPath,
|
|
516
|
+
defaultGroupingExpansionDepth,
|
|
517
|
+
isGroupExpandedByDefault,
|
|
518
|
+
rowSelectionPropagation,
|
|
273
519
|
checkboxSelection,
|
|
274
520
|
disableColumnSorting,
|
|
275
521
|
paginationMode,
|
|
@@ -282,48 +528,166 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
282
528
|
onPaginationModelChange: (model) => setPaginationModel(model),
|
|
283
529
|
filterModel: resolvedFilterModel,
|
|
284
530
|
onFilterModelChange: (model) => setFilterModel(model),
|
|
531
|
+
filterLogicOperator: resolvedFilterLogicOperator,
|
|
285
532
|
quickFilter: resolvedQuickFilter,
|
|
286
533
|
onQuickFilterChange: (value) => setQuickFilter(value),
|
|
287
534
|
rowSelectionModel: resolvedSelection,
|
|
288
535
|
onRowSelectionModelChange: (model) => setRowSelectionModel(model),
|
|
536
|
+
columnPinning,
|
|
537
|
+
columnVisibility: columnVisibilityModel,
|
|
289
538
|
});
|
|
290
539
|
|
|
291
540
|
const colByField = useMemo(() => {
|
|
292
541
|
const map = new Map<string, GridColDef<R>>();
|
|
293
|
-
for (const col of
|
|
542
|
+
for (const col of gridColumns) {
|
|
294
543
|
map.set(col.field, col);
|
|
295
544
|
}
|
|
296
545
|
return map;
|
|
297
|
-
}, [
|
|
546
|
+
}, [gridColumns]);
|
|
547
|
+
|
|
548
|
+
warnPinnedFlexColumns(visibleColumns, pinnedFields);
|
|
549
|
+
|
|
298
550
|
const columnWidths = useMemo(
|
|
299
|
-
() => webColumnWidths(
|
|
300
|
-
[
|
|
551
|
+
() => webColumnWidths(visibleColumns, leadingWidth, pinnedFields),
|
|
552
|
+
[leadingWidth, visibleColumns, pinnedFields],
|
|
553
|
+
);
|
|
554
|
+
const hasFlexColumns = useMemo(
|
|
555
|
+
() => visibleColumns.some((column) => effectiveFlex(column, pinnedFields) > 0),
|
|
556
|
+
[visibleColumns, pinnedFields],
|
|
301
557
|
);
|
|
302
|
-
const
|
|
558
|
+
const pinnedOffsets = useMemo(
|
|
559
|
+
() => (pinningActive ? buildPinnedOffsets(columnPinning, colByField) : null),
|
|
560
|
+
[pinningActive, columnPinning, colByField],
|
|
561
|
+
);
|
|
562
|
+
const orderedLeafColumnIds = useMemo(() => {
|
|
563
|
+
if (!pinningActive) {
|
|
564
|
+
return undefined;
|
|
565
|
+
}
|
|
566
|
+
return [
|
|
567
|
+
...table.getStartVisibleLeafColumns().map((column) => column.id),
|
|
568
|
+
...table.getCenterVisibleLeafColumns().map((column) => column.id),
|
|
569
|
+
...table.getEndVisibleLeafColumns().map((column) => column.id),
|
|
570
|
+
];
|
|
571
|
+
}, [pinningActive, table]);
|
|
572
|
+
const isEmpty = !loading && rows.length === 0;
|
|
573
|
+
const mainScrollStyle = useMemo((): ViewStyle | undefined => {
|
|
574
|
+
if (!pinningActive || pinnedOffsets == null) {
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
const guides =
|
|
578
|
+
Platform.OS === 'web' && !isEmpty
|
|
579
|
+
? pinnedEdgeGuidesImage(pinnedOffsets, PINNED_EDGE_COLOR, I18nManager.isRTL)
|
|
580
|
+
: undefined;
|
|
581
|
+
// Logical scroll-padding keeps focused cells out from under pinned edges (web).
|
|
582
|
+
return {
|
|
583
|
+
scrollPaddingInlineStart: pinnedOffsets.startTotal,
|
|
584
|
+
scrollPaddingInlineEnd: pinnedOffsets.endTotal,
|
|
585
|
+
...(guides ? { backgroundImage: guides, backgroundRepeat: 'no-repeat' } : {}),
|
|
586
|
+
} as ViewStyle;
|
|
587
|
+
}, [pinningActive, pinnedOffsets, isEmpty]);
|
|
303
588
|
|
|
304
589
|
const visibleRows = table.getRowModel().rows;
|
|
305
590
|
const pageCount = Math.max(table.getPageCount(), 1);
|
|
306
591
|
const selectedCount = table.getFilteredSelectedRowModel().rows.length;
|
|
307
592
|
const filterableCount = table.getFilteredRowModel().rows.length;
|
|
308
593
|
const headerSelectState =
|
|
309
|
-
|
|
594
|
+
tree?.headerSelection ??
|
|
595
|
+
(selectedCount === 0
|
|
310
596
|
? { checked: false, indeterminate: false }
|
|
311
597
|
: selectedCount === filterableCount
|
|
312
598
|
? { checked: true, indeterminate: false }
|
|
313
|
-
: { checked: false, indeterminate: true };
|
|
599
|
+
: { checked: false, indeterminate: true });
|
|
600
|
+
const leafColumnCount = table.getVisibleLeafColumns().length;
|
|
314
601
|
|
|
315
602
|
const accessibleName = ariaLabel ?? label;
|
|
316
|
-
const isEmpty = !loading && rows.length === 0;
|
|
317
603
|
const totalCount = paginationMode === 'server' ? (rowCount ?? rows.length) : filterableCount;
|
|
318
604
|
|
|
319
|
-
const filterValueFor = (field: string) =>
|
|
320
|
-
resolvedFilterModel.find((item) => item.field === field)?.value
|
|
605
|
+
const filterValueFor = (field: string) => {
|
|
606
|
+
const value = resolvedFilterModel.find((item) => item.field === field)?.value;
|
|
607
|
+
return typeof value === 'string' ? value : '';
|
|
608
|
+
};
|
|
321
609
|
|
|
322
610
|
const showToolbarBar = showToolbar || toolbarActions != null;
|
|
611
|
+
const filterPanelColumns = useMemo(
|
|
612
|
+
() => gridColumns.filter((column) => column.filterable !== false),
|
|
613
|
+
[gridColumns],
|
|
614
|
+
);
|
|
615
|
+
// Header filter cells and the column menu open the panel through `filterPanel.openForField`.
|
|
616
|
+
const filterPanel = useDataGridFilterPanel({
|
|
617
|
+
columns: filterPanelColumns,
|
|
618
|
+
filterModel: resolvedFilterModel,
|
|
619
|
+
onFilterModelChange: setFilterModel,
|
|
620
|
+
});
|
|
621
|
+
const filterPanelOpen = filterPanel.opener !== null && filterPanelColumns.length > 0;
|
|
622
|
+
const filterPanelAnchored = filterPanel.opener?.field !== undefined;
|
|
623
|
+
const filterPanelButtonRef = useRef<View>(null);
|
|
624
|
+
const showFilterPanelButton = showToolbar && filterPanelColumns.length > 0;
|
|
625
|
+
|
|
626
|
+
const mainRef = useRef<View>(null);
|
|
627
|
+
const [viewportWidth, setViewportWidth] = useState<number | undefined>(undefined);
|
|
628
|
+
useEffect(() => {
|
|
629
|
+
const main = mainRef.current as unknown as HTMLElement | null;
|
|
630
|
+
if (
|
|
631
|
+
Platform.OS !== 'web' ||
|
|
632
|
+
!detailPanel ||
|
|
633
|
+
main === null ||
|
|
634
|
+
typeof ResizeObserver === 'undefined'
|
|
635
|
+
) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
const observer = new ResizeObserver(() => setViewportWidth(main.clientWidth));
|
|
639
|
+
observer.observe(main);
|
|
640
|
+
return () => observer.disconnect();
|
|
641
|
+
}, [detailPanel]);
|
|
642
|
+
const detailPanelContentStyle = useMemo(
|
|
643
|
+
(): ViewStyle | undefined =>
|
|
644
|
+
viewportWidth === undefined
|
|
645
|
+
? undefined
|
|
646
|
+
: ({
|
|
647
|
+
position: 'sticky',
|
|
648
|
+
insetInlineStart: DETAIL_PANEL_INSET,
|
|
649
|
+
width: viewportWidth - DETAIL_PANEL_INSET * 2,
|
|
650
|
+
} as unknown as ViewStyle),
|
|
651
|
+
[viewportWidth],
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
const rootRef = useRef<View>(null);
|
|
655
|
+
const searchRef = useRef<TextInput>(null);
|
|
656
|
+
const bodyFocusRef = useRef<Element | null>(null);
|
|
657
|
+
useEffect(() => {
|
|
658
|
+
if (Platform.OS !== 'web') {
|
|
659
|
+
return undefined;
|
|
660
|
+
}
|
|
661
|
+
const root = rootRef.current as unknown as HTMLElement | null;
|
|
662
|
+
const onFocusIn = (event: FocusEvent) => {
|
|
663
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
664
|
+
bodyFocusRef.current =
|
|
665
|
+
target !== null && root?.contains(target.closest('tbody')) === true ? target : null;
|
|
666
|
+
};
|
|
667
|
+
document.addEventListener('focusin', onFocusIn);
|
|
668
|
+
return () => document.removeEventListener('focusin', onFocusIn);
|
|
669
|
+
}, []);
|
|
670
|
+
// Only when the focused row control was removed: focus that left the grid stays where it went.
|
|
671
|
+
useEffect(() => {
|
|
672
|
+
const focused = bodyFocusRef.current;
|
|
673
|
+
if (Platform.OS !== 'web' || focused === null || focused.isConnected) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
bodyFocusRef.current = null;
|
|
677
|
+
const active = document.activeElement;
|
|
678
|
+
if (active === null || active === document.body) {
|
|
679
|
+
searchRef.current?.focus();
|
|
680
|
+
}
|
|
681
|
+
}, [visibleRows]);
|
|
323
682
|
|
|
324
683
|
return (
|
|
325
684
|
<View
|
|
326
|
-
|
|
685
|
+
ref={rootRef}
|
|
686
|
+
className={cn(
|
|
687
|
+
dataGridRootVariants({ autoHeight }),
|
|
688
|
+
className,
|
|
689
|
+
filterPanelOpen && !filterPanelAnchored && 'overflow-visible',
|
|
690
|
+
)}
|
|
327
691
|
style={style}
|
|
328
692
|
{...mergeDataAttributes({}, dataAttributes({ slot: 'data-grid' }))}
|
|
329
693
|
>
|
|
@@ -333,6 +697,7 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
333
697
|
<Input size="small" className={dataGridToolbarSearchVariants()}>
|
|
334
698
|
<Input.Icon as={Search} />
|
|
335
699
|
<Input.Field
|
|
700
|
+
ref={searchRef}
|
|
336
701
|
value={resolvedQuickFilter}
|
|
337
702
|
onChangeText={setQuickFilterWithPageReset}
|
|
338
703
|
placeholder="Search"
|
|
@@ -340,17 +705,47 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
340
705
|
/>
|
|
341
706
|
</Input>
|
|
342
707
|
) : null}
|
|
708
|
+
{showFilterPanelButton ? (
|
|
709
|
+
<IconButton
|
|
710
|
+
ref={filterPanelButtonRef}
|
|
711
|
+
as={FilterList}
|
|
712
|
+
variant="ghost"
|
|
713
|
+
color="neutral"
|
|
714
|
+
size="small"
|
|
715
|
+
accessibilityLabel="Filters"
|
|
716
|
+
accessibilityState={{ expanded: filterPanelOpen }}
|
|
717
|
+
onPress={() => filterPanel.toggle(filterPanelButtonRef)}
|
|
718
|
+
/>
|
|
719
|
+
) : null}
|
|
343
720
|
{toolbarActions ? (
|
|
344
721
|
<View className={dataGridToolbarActionsVariants()}>{toolbarActions}</View>
|
|
345
722
|
) : null}
|
|
346
723
|
</View>
|
|
347
724
|
) : null}
|
|
725
|
+
{filterPanelOpen && filterPanel.opener ? (
|
|
726
|
+
<DataGridFilterPanel
|
|
727
|
+
columns={filterPanelColumns}
|
|
728
|
+
filterModel={resolvedFilterModel}
|
|
729
|
+
onFilterModelChange={setFilterModelWithPageReset}
|
|
730
|
+
logicOperator={resolvedFilterLogicOperator}
|
|
731
|
+
onLogicOperatorChange={(operator) => {
|
|
732
|
+
setFilterLogicOperator(operator);
|
|
733
|
+
if (resolvedPagination.page !== 0) {
|
|
734
|
+
setPaginationModel({ ...resolvedPagination, page: 0 });
|
|
735
|
+
}
|
|
736
|
+
}}
|
|
737
|
+
onClose={filterPanel.close}
|
|
738
|
+
opener={filterPanel.opener}
|
|
739
|
+
/>
|
|
740
|
+
) : null}
|
|
348
741
|
|
|
349
742
|
<View
|
|
743
|
+
ref={mainRef}
|
|
350
744
|
className={dataGridMainVariants({
|
|
351
745
|
autoHeight,
|
|
352
746
|
overlay: isEmpty || (loading && loadingOverlay != null),
|
|
353
747
|
})}
|
|
748
|
+
style={mainScrollStyle}
|
|
354
749
|
>
|
|
355
750
|
<Table
|
|
356
751
|
className={cn(
|
|
@@ -370,28 +765,52 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
370
765
|
{label ? <Table.Caption>{label}</Table.Caption> : null}
|
|
371
766
|
{Platform.OS === 'web' && hasFlexColumns ? (
|
|
372
767
|
<colgroup>
|
|
373
|
-
{
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}}
|
|
381
|
-
|
|
382
|
-
|
|
768
|
+
{(
|
|
769
|
+
orderedLeafColumnIds ?? [
|
|
770
|
+
...leadingColumnIds,
|
|
771
|
+
...visibleColumns.map((column) => column.field),
|
|
772
|
+
]
|
|
773
|
+
).map((columnId) =>
|
|
774
|
+
columnId === SELECT_COLUMN_ID ? (
|
|
775
|
+
<col key={columnId} style={{ width: SELECT_COLUMN_WIDTH }} />
|
|
776
|
+
) : columnId === DETAIL_PANEL_TOGGLE_ID ? (
|
|
777
|
+
<col key={columnId} style={{ width: DETAIL_PANEL_TOGGLE_WIDTH }} />
|
|
778
|
+
) : (
|
|
779
|
+
<col
|
|
780
|
+
key={columnId}
|
|
781
|
+
style={{
|
|
782
|
+
width: columnWidths.get(columnId),
|
|
783
|
+
minWidth: colByField.get(columnId)?.minWidth,
|
|
784
|
+
}}
|
|
785
|
+
/>
|
|
786
|
+
),
|
|
787
|
+
)}
|
|
383
788
|
</colgroup>
|
|
384
789
|
) : null}
|
|
385
790
|
<Table.Header className={dataGridHeaderVariants()}>
|
|
386
|
-
{
|
|
791
|
+
{(pinningActive
|
|
792
|
+
? [
|
|
793
|
+
{
|
|
794
|
+
id: 'pinned',
|
|
795
|
+
headers: [
|
|
796
|
+
...table.getStartLeafHeaders(),
|
|
797
|
+
...table.getCenterLeafHeaders(),
|
|
798
|
+
...table.getEndLeafHeaders(),
|
|
799
|
+
],
|
|
800
|
+
},
|
|
801
|
+
]
|
|
802
|
+
: table.getHeaderGroups()
|
|
803
|
+
).map((headerGroup) => (
|
|
387
804
|
<Table.Row key={headerGroup.id}>
|
|
388
805
|
{headerGroup.headers.map((header) => {
|
|
806
|
+
const pin = pinMetaFor(header.column.id, columnPinning);
|
|
389
807
|
if (header.id === SELECT_COLUMN_ID) {
|
|
390
808
|
return (
|
|
391
809
|
<Table.Head
|
|
392
810
|
key={header.id}
|
|
393
811
|
align="center"
|
|
394
|
-
className={dataGridSelectHeadVariants()}
|
|
812
|
+
className={cn(dataGridSelectHeadVariants(), pinnedClassName(pin, true))}
|
|
813
|
+
style={mergePinnedStyle(undefined, pin, pinnedOffsets, header.id, true)}
|
|
395
814
|
>
|
|
396
815
|
<Checkbox
|
|
397
816
|
value="select-all"
|
|
@@ -404,11 +823,25 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
404
823
|
</Table.Head>
|
|
405
824
|
);
|
|
406
825
|
}
|
|
826
|
+
if (header.id === DETAIL_PANEL_TOGGLE_ID) {
|
|
827
|
+
return (
|
|
828
|
+
<Table.Cell
|
|
829
|
+
key={header.id}
|
|
830
|
+
className={cn(
|
|
831
|
+
dataGridHeadVariants(),
|
|
832
|
+
dataGridDetailToggleCellVariants(),
|
|
833
|
+
pinnedClassName(pin, true),
|
|
834
|
+
)}
|
|
835
|
+
style={mergePinnedStyle(undefined, pin, pinnedOffsets, header.id, true)}
|
|
836
|
+
/>
|
|
837
|
+
);
|
|
838
|
+
}
|
|
407
839
|
|
|
408
840
|
const col = colByField.get(header.column.id);
|
|
841
|
+
const isTreeColumn = header.column.id === treeField;
|
|
409
842
|
const sorted = header.column.getIsSorted();
|
|
410
843
|
const canSort = !disableColumnSorting && header.column.getCanSort();
|
|
411
|
-
const headerAlign =
|
|
844
|
+
const headerAlign = resolveColumnHeaderAlign(col);
|
|
412
845
|
const headerName = col?.headerName ?? header.column.id;
|
|
413
846
|
const SortIcon = sorted === 'asc' ? KeyboardArrowUp : KeyboardArrowDown;
|
|
414
847
|
const sortButton = canSort ? (
|
|
@@ -428,13 +861,28 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
428
861
|
})}
|
|
429
862
|
/>
|
|
430
863
|
) : null;
|
|
864
|
+
const headerLabel = (
|
|
865
|
+
<>
|
|
866
|
+
{headerAlign === 'end' ? sortButton : null}
|
|
867
|
+
<Text numberOfLines={1} className={dataGridHeaderLabelVariants()}>
|
|
868
|
+
{col?.renderHeader ? col.renderHeader() : headerName}
|
|
869
|
+
</Text>
|
|
870
|
+
{headerAlign === 'end' ? null : sortButton}
|
|
871
|
+
</>
|
|
872
|
+
);
|
|
431
873
|
|
|
432
874
|
return (
|
|
433
875
|
<Table.Head
|
|
434
876
|
key={header.id}
|
|
435
877
|
align={headerAlign}
|
|
436
|
-
className={dataGridHeadVariants()}
|
|
437
|
-
style={
|
|
878
|
+
className={cn(dataGridHeadVariants(), pinnedClassName(pin, true))}
|
|
879
|
+
style={mergePinnedStyle(
|
|
880
|
+
cellStyle(col, columnWidths.get(header.column.id)),
|
|
881
|
+
pin,
|
|
882
|
+
pinnedOffsets,
|
|
883
|
+
header.column.id,
|
|
884
|
+
true,
|
|
885
|
+
)}
|
|
438
886
|
{...(Platform.OS === 'web'
|
|
439
887
|
? {
|
|
440
888
|
title: col?.renderHeader ? undefined : headerName,
|
|
@@ -451,18 +899,39 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
451
899
|
}
|
|
452
900
|
: {})}
|
|
453
901
|
>
|
|
454
|
-
|
|
455
|
-
className={
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
902
|
+
{isTreeColumn ? (
|
|
903
|
+
<View className={dataGridTreeCellInnerVariants()}>
|
|
904
|
+
<View className={dataGridTreeToggleSlotVariants()} />
|
|
905
|
+
{treeCheckbox ? (
|
|
906
|
+
<Checkbox
|
|
907
|
+
value="select-all"
|
|
908
|
+
isChecked={headerSelectState.checked}
|
|
909
|
+
isIndeterminate={headerSelectState.indeterminate}
|
|
910
|
+
onChange={() => tree?.toggleAllRowsSelected()}
|
|
911
|
+
accessibilityLabel="Select all"
|
|
912
|
+
aria-label="Select all"
|
|
913
|
+
/>
|
|
914
|
+
) : null}
|
|
915
|
+
<View
|
|
916
|
+
className={cn(
|
|
917
|
+
dataGridHeaderCellInnerVariants({ align: headerAlign }),
|
|
918
|
+
'flex-1',
|
|
919
|
+
)}
|
|
920
|
+
>
|
|
921
|
+
{headerLabel}
|
|
922
|
+
</View>
|
|
923
|
+
</View>
|
|
924
|
+
) : (
|
|
925
|
+
<View
|
|
926
|
+
className={dataGridHeaderCellInnerVariants({
|
|
927
|
+
align: headerAlign,
|
|
928
|
+
fitToColumn:
|
|
929
|
+
Platform.OS === 'web' && columnWidths.has(header.column.id),
|
|
930
|
+
})}
|
|
931
|
+
>
|
|
932
|
+
{headerLabel}
|
|
933
|
+
</View>
|
|
934
|
+
)}
|
|
466
935
|
</Table.Head>
|
|
467
936
|
);
|
|
468
937
|
})}
|
|
@@ -470,34 +939,58 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
470
939
|
))}
|
|
471
940
|
{headerFilters ? (
|
|
472
941
|
<Table.Row>
|
|
473
|
-
{
|
|
474
|
-
|
|
942
|
+
{(pinningActive
|
|
943
|
+
? [
|
|
944
|
+
...table.getStartVisibleLeafColumns(),
|
|
945
|
+
...table.getCenterVisibleLeafColumns(),
|
|
946
|
+
...table.getEndVisibleLeafColumns(),
|
|
947
|
+
]
|
|
948
|
+
: table.getVisibleLeafColumns()
|
|
949
|
+
).map((column) => {
|
|
950
|
+
const pin = pinMetaFor(column.id, columnPinning);
|
|
951
|
+
if (column.id === SELECT_COLUMN_ID || column.id === DETAIL_PANEL_TOGGLE_ID) {
|
|
475
952
|
return (
|
|
476
953
|
<Table.Cell
|
|
477
954
|
key={column.id}
|
|
478
955
|
align="center"
|
|
479
|
-
className={
|
|
956
|
+
className={cn(
|
|
957
|
+
dataGridSelectHeadVariants(),
|
|
958
|
+
column.id === DETAIL_PANEL_TOGGLE_ID && 'w-12 px-2',
|
|
959
|
+
pinnedClassName(pin, true),
|
|
960
|
+
)}
|
|
961
|
+
style={mergePinnedStyle(undefined, pin, pinnedOffsets, column.id, true)}
|
|
480
962
|
/>
|
|
481
963
|
);
|
|
482
964
|
}
|
|
483
965
|
const col = colByField.get(column.id);
|
|
484
|
-
const headerAlign = col?.headerAlign ?? col?.align;
|
|
485
966
|
if (col?.filterable === false) {
|
|
486
967
|
return (
|
|
487
968
|
<Table.Head
|
|
488
969
|
key={column.id}
|
|
489
|
-
align={
|
|
490
|
-
className={dataGridHeadVariants()}
|
|
491
|
-
style={
|
|
970
|
+
align={resolveColumnHeaderAlign(col)}
|
|
971
|
+
className={cn(dataGridHeadVariants(), pinnedClassName(pin, true))}
|
|
972
|
+
style={mergePinnedStyle(
|
|
973
|
+
cellStyle(col, columnWidths.get(column.id)),
|
|
974
|
+
pin,
|
|
975
|
+
pinnedOffsets,
|
|
976
|
+
column.id,
|
|
977
|
+
true,
|
|
978
|
+
)}
|
|
492
979
|
/>
|
|
493
980
|
);
|
|
494
981
|
}
|
|
495
982
|
return (
|
|
496
983
|
<Table.Head
|
|
497
984
|
key={column.id}
|
|
498
|
-
align={
|
|
499
|
-
className={dataGridHeadVariants()}
|
|
500
|
-
style={
|
|
985
|
+
align={resolveColumnHeaderAlign(col)}
|
|
986
|
+
className={cn(dataGridHeadVariants(), pinnedClassName(pin, true))}
|
|
987
|
+
style={mergePinnedStyle(
|
|
988
|
+
cellStyle(col, columnWidths.get(column.id)),
|
|
989
|
+
pin,
|
|
990
|
+
pinnedOffsets,
|
|
991
|
+
column.id,
|
|
992
|
+
true,
|
|
993
|
+
)}
|
|
501
994
|
>
|
|
502
995
|
<Input size="small" className={dataGridFilterInputVariants()}>
|
|
503
996
|
<Input.Field
|
|
@@ -516,23 +1009,86 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
516
1009
|
<Table.Body className={dataGridBodyVariants()}>
|
|
517
1010
|
{loading && loadingOverlay == null ? (
|
|
518
1011
|
<DefaultLoadingRows
|
|
519
|
-
columns={
|
|
520
|
-
|
|
1012
|
+
columns={visibleColumns}
|
|
1013
|
+
leadingColumnIds={leadingColumnIds}
|
|
521
1014
|
rowCount={Math.min(resolvedPagination.pageSize, MAX_SKELETON_ROW_COUNT)}
|
|
522
1015
|
columnWidths={columnWidths}
|
|
1016
|
+
orderedColumnIds={orderedLeafColumnIds}
|
|
1017
|
+
pinning={columnPinning}
|
|
1018
|
+
offsets={pinnedOffsets}
|
|
523
1019
|
/>
|
|
524
1020
|
) : isEmpty ? null : (
|
|
525
|
-
visibleRows.
|
|
1021
|
+
visibleRows.flatMap((row) => {
|
|
526
1022
|
const rowId = resolveRowId(row.original, getRowId);
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
1023
|
+
const cells = pinningActive
|
|
1024
|
+
? [
|
|
1025
|
+
...row.getStartVisibleCells(),
|
|
1026
|
+
...row.getCenterVisibleCells(),
|
|
1027
|
+
...row.getEndVisibleCells(),
|
|
1028
|
+
]
|
|
1029
|
+
: row.getVisibleCells();
|
|
1030
|
+
const detailContent = getDetailPanelContent?.({ id: rowId, row: row.original });
|
|
1031
|
+
const hasDetailPanel = detailContent != null;
|
|
1032
|
+
const detailOpen = hasDetailPanel && resolvedDetailIds.has(rowId);
|
|
1033
|
+
const treeLabel = treeActive
|
|
1034
|
+
? [...getTreeDataPath(row.original)].reverse().join(', ')
|
|
1035
|
+
: '';
|
|
1036
|
+
const dataRow = (
|
|
1037
|
+
<Table.Row
|
|
1038
|
+
key={row.id}
|
|
1039
|
+
className={detailOpen ? 'web:[&>*]:border-b-0' : undefined}
|
|
1040
|
+
>
|
|
1041
|
+
{cells.map((cell) => {
|
|
1042
|
+
const pin = pinMetaFor(cell.column.id, columnPinning);
|
|
1043
|
+
if (cell.column.id === DETAIL_PANEL_TOGGLE_ID) {
|
|
1044
|
+
return (
|
|
1045
|
+
<Table.Cell
|
|
1046
|
+
key={cell.id}
|
|
1047
|
+
align="center"
|
|
1048
|
+
className={cn(
|
|
1049
|
+
dataGridDetailToggleCellVariants(),
|
|
1050
|
+
pinnedClassName(pin, false),
|
|
1051
|
+
)}
|
|
1052
|
+
style={mergePinnedStyle(
|
|
1053
|
+
undefined,
|
|
1054
|
+
pin,
|
|
1055
|
+
pinnedOffsets,
|
|
1056
|
+
cell.column.id,
|
|
1057
|
+
false,
|
|
1058
|
+
)}
|
|
1059
|
+
>
|
|
1060
|
+
{hasDetailPanel ? (
|
|
1061
|
+
<IconButton
|
|
1062
|
+
as={detailOpen ? KeyboardArrowDown : KeyboardArrowRight}
|
|
1063
|
+
variant="ghost"
|
|
1064
|
+
color="neutral"
|
|
1065
|
+
size="small"
|
|
1066
|
+
iconClassName={detailOpen ? undefined : 'rtl:-scale-x-100'}
|
|
1067
|
+
accessibilityLabel={`${detailOpen ? 'Collapse' : 'Expand'} row ${String(rowId)}`}
|
|
1068
|
+
accessibilityState={{ expanded: detailOpen }}
|
|
1069
|
+
aria-expanded={detailOpen}
|
|
1070
|
+
onPress={() => toggleDetailPanel(rowId)}
|
|
1071
|
+
/>
|
|
1072
|
+
) : null}
|
|
1073
|
+
</Table.Cell>
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
530
1076
|
if (cell.column.id === SELECT_COLUMN_ID) {
|
|
531
1077
|
return (
|
|
532
1078
|
<Table.Cell
|
|
533
1079
|
key={cell.id}
|
|
534
1080
|
align="center"
|
|
535
|
-
className={
|
|
1081
|
+
className={cn(
|
|
1082
|
+
dataGridSelectCellVariants(),
|
|
1083
|
+
pinnedClassName(pin, false),
|
|
1084
|
+
)}
|
|
1085
|
+
style={mergePinnedStyle(
|
|
1086
|
+
undefined,
|
|
1087
|
+
pin,
|
|
1088
|
+
pinnedOffsets,
|
|
1089
|
+
cell.column.id,
|
|
1090
|
+
false,
|
|
1091
|
+
)}
|
|
536
1092
|
>
|
|
537
1093
|
<Checkbox
|
|
538
1094
|
value={String(rowId)}
|
|
@@ -554,26 +1110,120 @@ function DataGridInner<R extends GridValidRowModel>(props: DataGridProps<R>) {
|
|
|
554
1110
|
row: row.original,
|
|
555
1111
|
field: col.field,
|
|
556
1112
|
id: rowId,
|
|
1113
|
+
col,
|
|
557
1114
|
});
|
|
558
1115
|
} else if (col?.valueFormatter) {
|
|
559
1116
|
content = col.valueFormatter(value, row.original, col);
|
|
1117
|
+
} else if (col?.type === 'boolean' && typeof value === 'boolean') {
|
|
1118
|
+
content = <BooleanCell value={value} />;
|
|
560
1119
|
} else {
|
|
561
|
-
|
|
1120
|
+
const typedText = getTypedCellText(col, value, row.original);
|
|
1121
|
+
content = typedText !== undefined ? typedText : defaultCellContent(value);
|
|
562
1122
|
}
|
|
563
1123
|
|
|
1124
|
+
if (tree && cell.column.id === treeField) {
|
|
1125
|
+
const canExpand = row.subRows.length > 0;
|
|
1126
|
+
const expanded = canExpand && tree.isRowExpanded(row.id);
|
|
1127
|
+
const status = tree.selectionStatus(row.id);
|
|
1128
|
+
content = (
|
|
1129
|
+
<View
|
|
1130
|
+
className={dataGridTreeCellInnerVariants()}
|
|
1131
|
+
style={{ paddingStart: row.depth * TREE_INDENT }}
|
|
1132
|
+
>
|
|
1133
|
+
<View className={dataGridTreeToggleSlotVariants()}>
|
|
1134
|
+
{canExpand ? (
|
|
1135
|
+
<IconButton
|
|
1136
|
+
as={expanded ? KeyboardArrowDown : KeyboardArrowRight}
|
|
1137
|
+
variant="ghost"
|
|
1138
|
+
color="neutral"
|
|
1139
|
+
size="small"
|
|
1140
|
+
iconClassName={expanded ? undefined : 'rtl:-scale-x-100'}
|
|
1141
|
+
accessibilityLabel={`${expanded ? 'Collapse' : 'Expand'} ${treeLabel}`}
|
|
1142
|
+
accessibilityState={{ expanded }}
|
|
1143
|
+
aria-expanded={expanded}
|
|
1144
|
+
onPress={() => tree.toggleRowExpanded(row.id)}
|
|
1145
|
+
/>
|
|
1146
|
+
) : null}
|
|
1147
|
+
</View>
|
|
1148
|
+
{treeCheckbox ? (
|
|
1149
|
+
<Checkbox
|
|
1150
|
+
value={String(rowId)}
|
|
1151
|
+
isChecked={status === 'all'}
|
|
1152
|
+
isIndeterminate={status === 'some'}
|
|
1153
|
+
onChange={() => tree.toggleRowSelected(row)}
|
|
1154
|
+
accessibilityLabel={`Select ${treeLabel}`}
|
|
1155
|
+
aria-label={`Select ${treeLabel}`}
|
|
1156
|
+
/>
|
|
1157
|
+
) : null}
|
|
1158
|
+
<View className={dataGridTreeContentVariants()}>
|
|
1159
|
+
{typeof content === 'string' || typeof content === 'number' ? (
|
|
1160
|
+
<Text numberOfLines={1}>{content}</Text>
|
|
1161
|
+
) : (
|
|
1162
|
+
content
|
|
1163
|
+
)}
|
|
1164
|
+
</View>
|
|
1165
|
+
</View>
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
const align = resolveColumnAlign(col);
|
|
1170
|
+
const cellText =
|
|
1171
|
+
!col?.renderCell &&
|
|
1172
|
+
(typeof content === 'string' || typeof content === 'number')
|
|
1173
|
+
? String(content)
|
|
1174
|
+
: undefined;
|
|
1175
|
+
|
|
564
1176
|
return (
|
|
565
1177
|
<Table.Cell
|
|
566
1178
|
key={cell.id}
|
|
567
|
-
align={
|
|
568
|
-
|
|
569
|
-
|
|
1179
|
+
align={align}
|
|
1180
|
+
{...(Platform.OS === 'web' && cellText ? { title: cellText } : {})}
|
|
1181
|
+
className={cn(dataGridCellVariants(), pinnedClassName(pin, false))}
|
|
1182
|
+
style={mergePinnedStyle(
|
|
1183
|
+
cellStyle(col, columnWidths.get(cell.column.id)),
|
|
1184
|
+
pin,
|
|
1185
|
+
pinnedOffsets,
|
|
1186
|
+
cell.column.id,
|
|
1187
|
+
false,
|
|
1188
|
+
)}
|
|
570
1189
|
>
|
|
571
|
-
{
|
|
1190
|
+
{cellText !== undefined ? (
|
|
1191
|
+
<Text
|
|
1192
|
+
numberOfLines={1}
|
|
1193
|
+
className={dataGridCellTextVariants({
|
|
1194
|
+
align,
|
|
1195
|
+
fitToColumn:
|
|
1196
|
+
Platform.OS === 'web' && columnWidths.has(cell.column.id),
|
|
1197
|
+
})}
|
|
1198
|
+
>
|
|
1199
|
+
{cellText}
|
|
1200
|
+
</Text>
|
|
1201
|
+
) : (
|
|
1202
|
+
content
|
|
1203
|
+
)}
|
|
572
1204
|
</Table.Cell>
|
|
573
1205
|
);
|
|
574
1206
|
})}
|
|
575
1207
|
</Table.Row>
|
|
576
1208
|
);
|
|
1209
|
+
if (!detailOpen) {
|
|
1210
|
+
return [dataRow];
|
|
1211
|
+
}
|
|
1212
|
+
return [
|
|
1213
|
+
dataRow,
|
|
1214
|
+
<Table.Row
|
|
1215
|
+
key={`${row.id}:detail`}
|
|
1216
|
+
className="web:hover:bg-transparent"
|
|
1217
|
+
testID={`data-grid-detail-panel-${String(rowId)}`}
|
|
1218
|
+
>
|
|
1219
|
+
<Table.Cell
|
|
1220
|
+
colSpan={leafColumnCount}
|
|
1221
|
+
className={dataGridDetailPanelCellVariants()}
|
|
1222
|
+
>
|
|
1223
|
+
<View style={detailPanelContentStyle}>{detailContent}</View>
|
|
1224
|
+
</Table.Cell>
|
|
1225
|
+
</Table.Row>,
|
|
1226
|
+
];
|
|
577
1227
|
})
|
|
578
1228
|
)}
|
|
579
1229
|
</Table.Body>
|