Skip to content

Commit c276ccd

Browse files
authored
fix: let parent rows unselect child rows when not itself selected (TanStack#6364)
- Updated checkbox selection logic to account for sub-row selections in various examples (Alpine, Angular, React, Preact, Solid, Svelte, Vue). - Refactored core row selection feature to include memoization for `getIsAllSubRowsSelected` and `getCanSelectSubRows` functions. - Improved performance by optimizing selection checks in utility functions.
1 parent b1a80cc commit c276ccd

12 files changed

Lines changed: 49 additions & 26 deletions

File tree

examples/alpine/expanding/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
<input
9898
type="checkbox"
9999
style="cursor: pointer"
100-
:checked="cell.row.getIsSelected()"
100+
:checked="cell.row.getIsSelected() || (cell.row.getCanSelectSubRows() && cell.row.getIsAllSubRowsSelected())"
101101
x-effect="$el.indeterminate = !cell.row.getIsSelected() && cell.row.getIsSomeSelected()"
102102
@change="cell.row.getToggleSelectedHandler()($event)"
103103
/>

examples/angular/expanding/src/app/expandable-cell/expandable-cell.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ export class ExpandableHeaderCell<T extends RowData> {
4343
<input
4444
type="checkbox"
4545
[indeterminate]="row.getIsSomeSelected()"
46-
[checked]="row.getIsSelected()"
46+
[checked]="
47+
row.getIsSelected() ||
48+
(row.getCanSelectSubRows() && row.getIsAllSubRowsSelected())
49+
"
4750
(change)="row.getToggleSelectedHandler()($event)"
4851
/>
4952
{{ ' ' }}

examples/lit/expanding/src/main.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ const columns: Array<ColumnDef<typeof features, Person>> = [
123123
cell: ({ row, getValue }) => html`
124124
<div style="padding-left:${row.depth * 2}rem">
125125
${indeterminateCheckbox({
126-
checked: row.getIsSelected(),
126+
checked:
127+
row.getIsSelected() ||
128+
(row.getCanSelectSubRows() && row.getIsAllSubRowsSelected()),
127129
indeterminate: row.getIsSomeSelected(),
128130
onChange: row.getToggleSelectedHandler(),
129131
})}

examples/preact/expanding/src/main.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ function App() {
6868
>
6969
<div>
7070
<IndeterminateCheckbox
71-
checked={row.getIsSelected()}
71+
checked={
72+
row.getIsSelected() ||
73+
(row.getCanSelectSubRows() && row.getIsAllSubRowsSelected())
74+
}
7275
indeterminate={row.getIsSomeSelected()}
7376
onChange={row.getToggleSelectedHandler()}
7477
/>{' '}

examples/react/expanding/src/main.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ function App() {
6969
>
7070
<div>
7171
<IndeterminateCheckbox
72-
checked={row.getIsSelected()}
72+
checked={
73+
row.getIsSelected() ||
74+
(row.getCanSelectSubRows() && row.getIsAllSubRowsSelected())
75+
}
7376
indeterminate={row.getIsSomeSelected()}
7477
onChange={row.getToggleSelectedHandler()}
7578
/>{' '}
@@ -129,6 +132,7 @@ function App() {
129132
// filterFromLeafRows: true,
130133
// maxLeafRowFilterDepth: 0,
131134
debugTable: true,
135+
debugRows: true,
132136
},
133137
(state) => state, // default selector
134138
)
@@ -343,7 +347,7 @@ function IndeterminateCheckbox({
343347
if (typeof indeterminate === 'boolean') {
344348
ref.current.indeterminate = !rest.checked && indeterminate
345349
}
346-
}, [ref, indeterminate])
350+
}, [ref, indeterminate, rest.checked])
347351

348352
return (
349353
<input

examples/solid/expanding/src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ function App() {
5959
<div style={{ 'padding-left': `${row.depth * 2}rem` }}>
6060
<div>
6161
<IndeterminateCheckbox
62-
checked={row.getIsSelected()}
62+
checked={
63+
row.getIsSelected() ||
64+
(row.getCanSelectSubRows() && row.getIsAllSubRowsSelected())
65+
}
6366
indeterminate={row.getIsSomeSelected()}
6467
onChange={row.getToggleSelectedHandler()}
6568
/>{' '}

examples/svelte/expanding/src/App.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@
156156
<div>
157157
<input
158158
type="checkbox"
159-
checked={row.getIsSelected()}
159+
checked={row.getIsSelected() ||
160+
(row.getCanSelectSubRows() &&
161+
row.getIsAllSubRowsSelected())}
160162
use:setIndeterminate={!row.getIsSelected() && row.getIsSomeSelected()}
161163
onchange={row.getToggleSelectedHandler()}
162164
class="sortable-header"

examples/vue/expanding/src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,10 @@ export default defineComponent({
156156
<div style={{ paddingLeft: `${row.depth * 2}rem` }}>
157157
<div>
158158
<IndeterminateCheckbox
159-
checked={row.getIsSelected()}
159+
checked={
160+
row.getIsSelected() ||
161+
(row.getCanSelectSubRows() && row.getIsAllSubRowsSelected())
162+
}
160163
indeterminate={row.getIsSomeSelected()}
161164
onChange={row.getToggleSelectedHandler()}
162165
/>{' '}

packages/table-core/src/features/row-selection/rowSelectionFeature.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,19 @@ export const rowSelectionFeature: TableFeature = {
6161
},
6262
row_getIsSomeSelected: {
6363
fn: (row) => row_getIsSomeSelected(row),
64+
memoDeps: (row) => [
65+
row.subRows,
66+
row.table.atoms.rowSelection?.get(),
67+
row.table.options.enableRowSelection,
68+
],
6469
},
6570
row_getIsAllSubRowsSelected: {
6671
fn: (row) => row_getIsAllSubRowsSelected(row),
72+
memoDeps: (row) => [
73+
row.subRows,
74+
row.table.atoms.rowSelection?.get(),
75+
row.table.options.enableRowSelection,
76+
],
6777
},
6878
row_getCanSelect: {
6979
fn: (row) => row_getCanSelect(row),

packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,6 @@ export function row_toggleSelected<
497497
table_setRowSelection(row.table, (old) => {
498498
value = typeof value !== 'undefined' ? value : !isSelected
499499

500-
if (row_getCanSelect(row) && isSelected === value) {
501-
return old
502-
}
503-
504500
const rowSelection = Object.assign(makeObjectMap<true>(), old)
505501

506502
mutateRowIsSelected(
@@ -787,13 +783,15 @@ export function isSubRowSelected<
787783

788784
const rowSelection = row.table.atoms.rowSelection?.get() ?? {}
789785

790-
let allChildrenSelected = true
791786
let someSelected = false
787+
let allChildrenSelected = true
788+
789+
for (let i = 0; i < row.subRows.length; i++) {
790+
const subRow = row.subRows[i]!
792791

793-
row.subRows.forEach((subRow) => {
794792
// Bail out early if we know both of these
795793
if (someSelected && !allChildrenSelected) {
796-
return
794+
break
797795
}
798796

799797
if (row_getCanSelect(subRow)) {
@@ -816,8 +814,7 @@ export function isSubRowSelected<
816814
allChildrenSelected = false
817815
}
818816
}
819-
})
817+
}
820818

821-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
822819
return allChildrenSelected ? 'all' : someSelected ? 'some' : false
823820
}

0 commit comments

Comments
 (0)