Unverified Commit 8d80592d authored by Per Classon's avatar Per Classon Committed by GitHub

Add tristate to parent checkbox for DataTable (#67414)

* Add tristate to parent checkbox for DataTable to follow Material specifications
parent bbf1b557
...@@ -658,13 +658,16 @@ class DataTable extends StatelessWidget { ...@@ -658,13 +658,16 @@ class DataTable extends StatelessWidget {
static final LocalKey _headingRowKey = UniqueKey(); static final LocalKey _headingRowKey = UniqueKey();
void _handleSelectAll(bool? checked) { void _handleSelectAll(bool? checked, bool someChecked) {
// If some checkboxes are checked, all checkboxes are selected. Otherwise,
// use the new checked value but default to false if it's null.
final bool effectiveChecked = someChecked || (checked ?? false);
if (onSelectAll != null) { if (onSelectAll != null) {
onSelectAll!(checked); onSelectAll!(effectiveChecked);
} else { } else {
for (final DataRow row in rows) { for (final DataRow row in rows) {
if ((row.onSelectChanged != null) && (row.selected != checked)) if (row.onSelectChanged != null && row.selected != effectiveChecked)
row.onSelectChanged!(checked); row.onSelectChanged!(effectiveChecked);
} }
} }
} }
...@@ -692,10 +695,11 @@ class DataTable extends StatelessWidget { ...@@ -692,10 +695,11 @@ class DataTable extends StatelessWidget {
Widget _buildCheckbox({ Widget _buildCheckbox({
required BuildContext context, required BuildContext context,
required Color activeColor, required Color activeColor,
required bool checked, required bool? checked,
required VoidCallback? onRowTap, required VoidCallback? onRowTap,
required ValueChanged<bool?>? onCheckboxChanged, required ValueChanged<bool?>? onCheckboxChanged,
required MaterialStateProperty<Color?>? overlayColor, required MaterialStateProperty<Color?>? overlayColor,
required bool tristate,
}) { }) {
final ThemeData themeData = Theme.of(context)!; final ThemeData themeData = Theme.of(context)!;
final double effectiveHorizontalMargin = horizontalMargin final double effectiveHorizontalMargin = horizontalMargin
...@@ -713,6 +717,7 @@ class DataTable extends StatelessWidget { ...@@ -713,6 +717,7 @@ class DataTable extends StatelessWidget {
activeColor: activeColor, activeColor: activeColor,
value: checked, value: checked,
onChanged: onCheckboxChanged, onChanged: onCheckboxChanged,
tristate: tristate,
), ),
), ),
), ),
...@@ -869,7 +874,11 @@ class DataTable extends StatelessWidget { ...@@ -869,7 +874,11 @@ class DataTable extends StatelessWidget {
); );
final bool anyRowSelectable = rows.any((DataRow row) => row.onSelectChanged != null); final bool anyRowSelectable = rows.any((DataRow row) => row.onSelectChanged != null);
final bool displayCheckboxColumn = showCheckboxColumn && anyRowSelectable; final bool displayCheckboxColumn = showCheckboxColumn && anyRowSelectable;
final bool allChecked = displayCheckboxColumn && !rows.any((DataRow row) => row.onSelectChanged != null && !row.selected); final Iterable<DataRow> rowsChecked = displayCheckboxColumn ?
rows.where((DataRow row) => row.onSelectChanged != null && row.selected) : <DataRow>[];
final bool allChecked = displayCheckboxColumn && rowsChecked.length == rows.length;
final bool anyChecked = displayCheckboxColumn && rowsChecked.isNotEmpty;
final bool someChecked = anyChecked && !allChecked;
final double effectiveHorizontalMargin = horizontalMargin final double effectiveHorizontalMargin = horizontalMargin
?? theme.dataTableTheme.horizontalMargin ?? theme.dataTableTheme.horizontalMargin
?? _horizontalMargin; ?? _horizontalMargin;
...@@ -920,10 +929,11 @@ class DataTable extends StatelessWidget { ...@@ -920,10 +929,11 @@ class DataTable extends StatelessWidget {
tableRows[0].children![0] = _buildCheckbox( tableRows[0].children![0] = _buildCheckbox(
context: context, context: context,
activeColor: theme.accentColor, activeColor: theme.accentColor,
checked: allChecked, checked: someChecked ? null : allChecked,
onRowTap: null, onRowTap: null,
onCheckboxChanged: _handleSelectAll, onCheckboxChanged: (bool? checked) => _handleSelectAll(checked, someChecked),
overlayColor: null, overlayColor: null,
tristate: true,
); );
rowIndex = 1; rowIndex = 1;
for (final DataRow row in rows) { for (final DataRow row in rows) {
...@@ -934,6 +944,7 @@ class DataTable extends StatelessWidget { ...@@ -934,6 +944,7 @@ class DataTable extends StatelessWidget {
onRowTap: () => row.onSelectChanged != null ? row.onSelectChanged!(!row.selected) : null , onRowTap: () => row.onSelectChanged != null ? row.onSelectChanged!(!row.selected) : null ,
onCheckboxChanged: row.onSelectChanged, onCheckboxChanged: row.onSelectChanged,
overlayColor: row.color ?? effectiveDataRowColor, overlayColor: row.color ?? effectiveDataRowColor,
tristate: false,
); );
rowIndex += 1; rowIndex += 1;
} }
......
...@@ -104,6 +104,61 @@ void main() { ...@@ -104,6 +104,61 @@ void main() {
log.clear(); log.clear();
}); });
testWidgets('DataTable control test - tristate', (WidgetTester tester) async {
final List<String> log = <String>[];
const int numItems = 3;
Widget buildTable(List<bool> selected) {
return DataTable(
onSelectAll: (bool? value) {
log.add('select-all: $value');
},
columns: const <DataColumn>[
DataColumn(
label: Text('Name'),
tooltip: 'Name',
),
],
rows: List<DataRow>.generate(
numItems,
(int index) => DataRow(
cells: <DataCell>[DataCell(Text('Row $index'))],
selected: selected[index],
onSelectChanged: (bool? value) {
log.add('row-selected: $index');
},
),
),
);
}
// Tapping the parent checkbox when no rows are selected, selects all.
await tester.pumpWidget(MaterialApp(
home: Material(child: buildTable(<bool>[false, false, false])),
));
await tester.tap(find.byType(Checkbox).first);
expect(log, <String>['select-all: true']);
log.clear();
// Tapping the parent checkbox when some rows are selected, selects all.
await tester.pumpWidget(MaterialApp(
home: Material(child: buildTable(<bool>[true, false, true])),
));
await tester.tap(find.byType(Checkbox).first);
expect(log, <String>['select-all: true']);
log.clear();
// Tapping the parent checkbox when all rows are selected, deselects all.
await tester.pumpWidget(MaterialApp(
home: Material(child: buildTable(<bool>[true, true, true])),
));
await tester.tap(find.byType(Checkbox).first);
expect(log, <String>['select-all: false']);
log.clear();
});
testWidgets('DataTable control test - no checkboxes', (WidgetTester tester) async { testWidgets('DataTable control test - no checkboxes', (WidgetTester tester) async {
final List<String> log = <String>[]; final List<String> log = <String>[];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment