@@ -4,15 +4,17 @@ import { flushSync } from 'react-dom';
4
4
import clsx from 'clsx' ;
5
5
6
6
import {
7
+ HeaderRowSelectionChangeProvider ,
8
+ HeaderRowSelectionProvider ,
7
9
RowSelectionChangeProvider ,
8
- RowSelectionProvider ,
9
10
useCalculatedColumns ,
10
11
useColumnWidths ,
11
12
useGridDimensions ,
12
13
useLatestFunc ,
13
14
useLayoutEffect ,
14
15
useViewportColumns ,
15
- useViewportRows
16
+ useViewportRows ,
17
+ type HeaderRowSelectionContextValue
16
18
} from './hooks' ;
17
19
import {
18
20
abs ,
@@ -46,6 +48,7 @@ import type {
46
48
Position ,
47
49
Renderers ,
48
50
RowsChangeData ,
51
+ SelectHeaderRowEvent ,
49
52
SelectRowEvent ,
50
53
SortColumn
51
54
} from './types' ;
@@ -147,6 +150,8 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
147
150
*/
148
151
/** Set of selected row keys */
149
152
selectedRows ?: Maybe < ReadonlySet < K > > ;
153
+ /** Determines if row selection is disabled, per row */
154
+ isRowSelectionDisabled ?: Maybe < ( row : NoInfer < R > ) => boolean > ;
150
155
/** Function called whenever row selection is changed */
151
156
onSelectedRowsChange ?: Maybe < ( selectedRows : Set < NoInfer < K > > ) => void > ;
152
157
/** Used for multi column sorting */
@@ -225,6 +230,7 @@ function DataGrid<R, SR, K extends Key>(
225
230
summaryRowHeight : rawSummaryRowHeight ,
226
231
// Feature props
227
232
selectedRows,
233
+ isRowSelectionDisabled,
228
234
onSelectedRowsChange,
229
235
sortColumns,
230
236
onSortColumnsChange,
@@ -363,16 +369,27 @@ function DataGrid<R, SR, K extends Key>(
363
369
[ renderCheckbox , renderSortStatus ]
364
370
) ;
365
371
366
- const allRowsSelected = useMemo ( ( ) : boolean => {
372
+ const headerSelectionValue = useMemo ( ( ) : HeaderRowSelectionContextValue => {
367
373
// no rows to select = explicitely unchecked
368
- const { length } = rows ;
369
- return (
370
- length !== 0 &&
371
- selectedRows != null &&
372
- rowKeyGetter != null &&
373
- selectedRows . size >= length &&
374
- rows . every ( ( row ) => selectedRows . has ( rowKeyGetter ( row ) ) )
375
- ) ;
374
+ let hasSelectedRow = false ;
375
+ let hasUnselectedRow = false ;
376
+
377
+ if ( rowKeyGetter != null && selectedRows != null && selectedRows . size > 0 ) {
378
+ for ( const row of rows ) {
379
+ if ( selectedRows . has ( rowKeyGetter ( row ) ) ) {
380
+ hasSelectedRow = true ;
381
+ } else {
382
+ hasUnselectedRow = true ;
383
+ }
384
+
385
+ if ( hasSelectedRow && hasUnselectedRow ) break ;
386
+ }
387
+ }
388
+
389
+ return {
390
+ isRowSelected : hasSelectedRow && ! hasUnselectedRow ,
391
+ isIndeterminate : hasSelectedRow && hasUnselectedRow
392
+ } ;
376
393
} , [ rows , selectedRows , rowKeyGetter ] ) ;
377
394
378
395
const {
@@ -433,6 +450,7 @@ function DataGrid<R, SR, K extends Key>(
433
450
const onCellClickLatest = useLatestFunc ( onCellClick ) ;
434
451
const onCellDoubleClickLatest = useLatestFunc ( onCellDoubleClick ) ;
435
452
const onCellContextMenuLatest = useLatestFunc ( onCellContextMenu ) ;
453
+ const selectHeaderRowLatest = useLatestFunc ( selectHeaderRow ) ;
436
454
const selectRowLatest = useLatestFunc ( selectRow ) ;
437
455
const handleFormatterRowChangeLatest = useLatestFunc ( updateRow ) ;
438
456
const selectCellLatest = useLatestFunc ( selectCell ) ;
@@ -492,26 +510,30 @@ function DataGrid<R, SR, K extends Key>(
492
510
/**
493
511
* event handlers
494
512
*/
495
- function selectRow ( args : SelectRowEvent < R > ) {
513
+ function selectHeaderRow ( args : SelectHeaderRowEvent ) {
496
514
if ( ! onSelectedRowsChange ) return ;
497
515
498
516
assertIsValidKeyGetter < R , K > ( rowKeyGetter ) ;
499
517
500
- if ( args . type === 'HEADER' ) {
501
- const newSelectedRows = new Set ( selectedRows ) ;
502
- for ( const row of rows ) {
503
- const rowKey = rowKeyGetter ( row ) ;
504
- if ( args . checked ) {
505
- newSelectedRows . add ( rowKey ) ;
506
- } else {
507
- newSelectedRows . delete ( rowKey ) ;
508
- }
518
+ const newSelectedRows = new Set ( selectedRows ) ;
519
+ for ( const row of rows ) {
520
+ if ( isRowSelectionDisabled ?.( row ) === true ) continue ;
521
+ const rowKey = rowKeyGetter ( row ) ;
522
+ if ( args . checked ) {
523
+ newSelectedRows . add ( rowKey ) ;
524
+ } else {
525
+ newSelectedRows . delete ( rowKey ) ;
509
526
}
510
- onSelectedRowsChange ( newSelectedRows ) ;
511
- return ;
512
527
}
528
+ onSelectedRowsChange ( newSelectedRows ) ;
529
+ }
530
+
531
+ function selectRow ( args : SelectRowEvent < R > ) {
532
+ if ( ! onSelectedRowsChange ) return ;
513
533
534
+ assertIsValidKeyGetter < R , K > ( rowKeyGetter ) ;
514
535
const { row, checked, isShiftClick } = args ;
536
+ if ( isRowSelectionDisabled ?.( row ) === true ) return ;
515
537
const newSelectedRows = new Set ( selectedRows ) ;
516
538
const rowKey = rowKeyGetter ( row ) ;
517
539
const previousRowIdx = lastSelectedRowIdx . current ;
@@ -533,6 +555,7 @@ function DataGrid<R, SR, K extends Key>(
533
555
const step = sign ( rowIdx - previousRowIdx ) ;
534
556
for ( let i = previousRowIdx + step ; i !== rowIdx ; i += step ) {
535
557
const row = rows [ i ] ;
558
+ if ( isRowSelectionDisabled ?.( row ) === true ) continue ;
536
559
if ( checked ) {
537
560
newSelectedRows . add ( rowKeyGetter ( row ) ) ;
538
561
} else {
@@ -674,7 +697,7 @@ function DataGrid<R, SR, K extends Key>(
674
697
if ( isSelectable && shiftKey && key === ' ' ) {
675
698
assertIsValidKeyGetter < R , K > ( rowKeyGetter ) ;
676
699
const rowKey = rowKeyGetter ( row ) ;
677
- selectRow ( { type : 'ROW' , row, checked : ! selectedRows . has ( rowKey ) , isShiftClick : false } ) ;
700
+ selectRow ( { row, checked : ! selectedRows . has ( rowKey ) , isShiftClick : false } ) ;
678
701
// do not scroll
679
702
event . preventDefault ( ) ;
680
703
return ;
@@ -1008,6 +1031,7 @@ function DataGrid<R, SR, K extends Key>(
1008
1031
rowIdx,
1009
1032
row,
1010
1033
viewportColumns : rowColumns ,
1034
+ isRowSelectionDisabled : isRowSelectionDisabled ?.( row ) ?? false ,
1011
1035
isRowSelected,
1012
1036
onCellClick : onCellClickLatest ,
1013
1037
onCellDoubleClick : onCellDoubleClickLatest ,
@@ -1098,8 +1122,8 @@ function DataGrid<R, SR, K extends Key>(
1098
1122
data-testid = { testId }
1099
1123
>
1100
1124
< DataGridDefaultRenderersProvider value = { defaultGridComponents } >
1101
- < RowSelectionChangeProvider value = { selectRowLatest } >
1102
- < RowSelectionProvider value = { allRowsSelected } >
1125
+ < HeaderRowSelectionChangeProvider value = { selectHeaderRowLatest } >
1126
+ < HeaderRowSelectionProvider value = { headerSelectionValue } >
1103
1127
{ Array . from ( { length : groupedColumnHeaderRowsCount } , ( _ , index ) => (
1104
1128
< GroupedColumnHeaderRow
1105
1129
key = { index }
@@ -1127,68 +1151,70 @@ function DataGrid<R, SR, K extends Key>(
1127
1151
shouldFocusGrid = { ! selectedCellIsWithinSelectionBounds }
1128
1152
direction = { direction }
1129
1153
/>
1130
- </ RowSelectionProvider >
1131
- { rows . length === 0 && noRowsFallback ? (
1132
- noRowsFallback
1133
- ) : (
1134
- < >
1135
- { topSummaryRows ?. map ( ( row , rowIdx ) => {
1136
- const gridRowStart = headerRowsCount + 1 + rowIdx ;
1137
- const summaryRowIdx = mainHeaderRowIdx + 1 + rowIdx ;
1138
- const isSummaryRowSelected = selectedPosition . rowIdx === summaryRowIdx ;
1139
- const top = headerRowsHeight + summaryRowHeight * rowIdx ;
1140
-
1141
- return (
1142
- < SummaryRow
1143
- key = { rowIdx }
1144
- aria-rowindex = { gridRowStart }
1145
- rowIdx = { summaryRowIdx }
1146
- gridRowStart = { gridRowStart }
1147
- row = { row }
1148
- top = { top }
1149
- bottom = { undefined }
1150
- viewportColumns = { getRowViewportColumns ( summaryRowIdx ) }
1151
- lastFrozenColumnIndex = { lastFrozenColumnIndex }
1152
- selectedCellIdx = { isSummaryRowSelected ? selectedPosition . idx : undefined }
1153
- isTop
1154
- selectCell = { selectCellLatest }
1155
- />
1156
- ) ;
1157
- } ) }
1154
+ </ HeaderRowSelectionProvider >
1155
+ </ HeaderRowSelectionChangeProvider >
1156
+ { rows . length === 0 && noRowsFallback ? (
1157
+ noRowsFallback
1158
+ ) : (
1159
+ < >
1160
+ { topSummaryRows ?. map ( ( row , rowIdx ) => {
1161
+ const gridRowStart = headerRowsCount + 1 + rowIdx ;
1162
+ const summaryRowIdx = mainHeaderRowIdx + 1 + rowIdx ;
1163
+ const isSummaryRowSelected = selectedPosition . rowIdx === summaryRowIdx ;
1164
+ const top = headerRowsHeight + summaryRowHeight * rowIdx ;
1165
+
1166
+ return (
1167
+ < SummaryRow
1168
+ key = { rowIdx }
1169
+ aria-rowindex = { gridRowStart }
1170
+ rowIdx = { summaryRowIdx }
1171
+ gridRowStart = { gridRowStart }
1172
+ row = { row }
1173
+ top = { top }
1174
+ bottom = { undefined }
1175
+ viewportColumns = { getRowViewportColumns ( summaryRowIdx ) }
1176
+ lastFrozenColumnIndex = { lastFrozenColumnIndex }
1177
+ selectedCellIdx = { isSummaryRowSelected ? selectedPosition . idx : undefined }
1178
+ isTop
1179
+ selectCell = { selectCellLatest }
1180
+ />
1181
+ ) ;
1182
+ } ) }
1183
+ < RowSelectionChangeProvider value = { selectRowLatest } >
1158
1184
{ getViewportRows ( ) }
1159
- { bottomSummaryRows ?. map ( ( row , rowIdx ) => {
1160
- const gridRowStart = headerAndTopSummaryRowsCount + rows . length + rowIdx + 1 ;
1161
- const summaryRowIdx = rows . length + rowIdx ;
1162
- const isSummaryRowSelected = selectedPosition . rowIdx === summaryRowIdx ;
1163
- const top =
1164
- clientHeight > totalRowHeight
1165
- ? gridHeight - summaryRowHeight * ( bottomSummaryRows . length - rowIdx )
1166
- : undefined ;
1167
- const bottom =
1168
- top === undefined
1169
- ? summaryRowHeight * ( bottomSummaryRows . length - 1 - rowIdx )
1170
- : undefined ;
1171
-
1172
- return (
1173
- < SummaryRow
1174
- aria-rowindex = { ariaRowCount - bottomSummaryRowsCount + rowIdx + 1 }
1175
- key = { rowIdx }
1176
- rowIdx = { summaryRowIdx }
1177
- gridRowStart = { gridRowStart }
1178
- row = { row }
1179
- top = { top }
1180
- bottom = { bottom }
1181
- viewportColumns = { getRowViewportColumns ( summaryRowIdx ) }
1182
- lastFrozenColumnIndex = { lastFrozenColumnIndex }
1183
- selectedCellIdx = { isSummaryRowSelected ? selectedPosition . idx : undefined }
1184
- isTop = { false }
1185
- selectCell = { selectCellLatest }
1186
- />
1187
- ) ;
1188
- } ) }
1189
- </ >
1190
- ) }
1191
- </ RowSelectionChangeProvider >
1185
+ </ RowSelectionChangeProvider >
1186
+ { bottomSummaryRows ?. map ( ( row , rowIdx ) => {
1187
+ const gridRowStart = headerAndTopSummaryRowsCount + rows . length + rowIdx + 1 ;
1188
+ const summaryRowIdx = rows . length + rowIdx ;
1189
+ const isSummaryRowSelected = selectedPosition . rowIdx === summaryRowIdx ;
1190
+ const top =
1191
+ clientHeight > totalRowHeight
1192
+ ? gridHeight - summaryRowHeight * ( bottomSummaryRows . length - rowIdx )
1193
+ : undefined ;
1194
+ const bottom =
1195
+ top === undefined
1196
+ ? summaryRowHeight * ( bottomSummaryRows . length - 1 - rowIdx )
1197
+ : undefined ;
1198
+
1199
+ return (
1200
+ < SummaryRow
1201
+ aria-rowindex = { ariaRowCount - bottomSummaryRowsCount + rowIdx + 1 }
1202
+ key = { rowIdx }
1203
+ rowIdx = { summaryRowIdx }
1204
+ gridRowStart = { gridRowStart }
1205
+ row = { row }
1206
+ top = { top }
1207
+ bottom = { bottom }
1208
+ viewportColumns = { getRowViewportColumns ( summaryRowIdx ) }
1209
+ lastFrozenColumnIndex = { lastFrozenColumnIndex }
1210
+ selectedCellIdx = { isSummaryRowSelected ? selectedPosition . idx : undefined }
1211
+ isTop = { false }
1212
+ selectCell = { selectCellLatest }
1213
+ />
1214
+ ) ;
1215
+ } ) }
1216
+ </ >
1217
+ ) }
1192
1218
</ DataGridDefaultRenderersProvider >
1193
1219
1194
1220
{ renderDragHandle ( ) }
0 commit comments