1- import React from 'react' ;
1+ import React , { useMemo } from 'react' ;
22import {
33 Table ,
44 TableProps ,
@@ -11,16 +11,34 @@ import { useInternalContext } from '../InternalContext';
1111import { DataViewTableHeader } from '../DataViewTableHeader' ;
1212import { DataViewTh , DataViewTrTree , isDataViewTdObject } from '../DataViewTable' ;
1313
14+ const getDescendants = ( node : DataViewTrTree ) : DataViewTrTree [ ] => ( ! node . children || ! node . children . length ) ? [ node ] : node . children . flatMap ( getDescendants ) ;
15+
16+ const isNodeChecked = ( node : DataViewTrTree , isSelected : ( node : DataViewTrTree ) => boolean ) => {
17+ let allSelected = true ;
18+ let someSelected = false ;
19+
20+ for ( const descendant of getDescendants ( node ) ) {
21+ const selected = ! ! isSelected ?.( descendant ) ;
22+
23+ someSelected ||= selected ;
24+ allSelected &&= selected ;
25+
26+ if ( ! allSelected && someSelected ) { return null }
27+ }
28+
29+ return allSelected ;
30+ } ;
31+
1432export interface DataViewTableTreeProps extends Omit < TableProps , 'onSelect' | 'rows' > {
1533 /** Columns definition */
1634 columns : DataViewTh [ ] ;
1735 /** Current page rows */
1836 rows : DataViewTrTree [ ] ;
19- /** Optinal icon for the leaf rows */
37+ /** Optional icon for the leaf rows */
2038 leafIcon ?: React . ReactNode ;
21- /** Optinal icon for the expanded parent rows */
39+ /** Optional icon for the expanded parent rows */
2240 expandedIcon ?: React . ReactNode ;
23- /** Optinal icon for the collapsed parent rows */
41+ /** Optional icon for the collapsed parent rows */
2442 collapsedIcon ?: React . ReactNode ;
2543 /** Custom OUIA ID */
2644 ouiaId ?: string ;
@@ -40,117 +58,85 @@ export const DataViewTableTree: React.FC<DataViewTableTreeProps> = ({
4058 const [ expandedNodeIds , setExpandedNodeIds ] = React . useState < string [ ] > ( [ ] ) ;
4159 const [ expandedDetailsNodeNames , setExpandedDetailsNodeIds ] = React . useState < string [ ] > ( [ ] ) ;
4260
43- const getDescendants = ( node : DataViewTrTree ) : DataViewTrTree [ ] => {
44- if ( ! node . children || ! node . children . length ) {
45- return [ node ] ;
46- } else {
47- let children : DataViewTrTree [ ] = [ ] ;
48- node . children . forEach ( ( child ) => {
49- children = [ ...children , ...getDescendants ( child ) ] ;
50- } ) ;
51- return children ;
52- }
53- } ;
54-
55- const areAllDescendantsSelected = ( node : DataViewTrTree ) => getDescendants ( node ) . every ( ( n ) => isSelected ?.( n ) ) ;
56- const areSomeDescendantsSelected = ( node : DataViewTrTree ) => getDescendants ( node ) . some ( ( n ) => isSelected ?.( n ) ) ;
57-
58- const isNodeChecked = ( node : DataViewTrTree ) => {
59- if ( areAllDescendantsSelected ( node ) ) {
60- return true ;
61- }
62- if ( areSomeDescendantsSelected ( node ) ) {
63- return null ;
64- }
65- return false ;
66- } ;
67-
68- /**
69- Recursive function which flattens the data into an array of flattened TreeRowWrapper components
70- params:
71- - nodes - array of a single level of tree nodes
72- - level - number representing how deeply nested the current row is
73- - posinset - position of the row relative to this row's siblings
74- - currentRowIndex - position of the row relative to the entire table
75- - isHidden - defaults to false, true if this row's parent is expanded
76- */
77- const renderRows = (
78- [ node , ...remainingNodes ] : DataViewTrTree [ ] ,
79- level = 1 ,
80- posinset = 1 ,
81- rowIndex = 0 ,
82- isHidden = false
83- ) : React . ReactNode [ ] => {
84- if ( ! node ) {
85- return [ ] ;
86- }
87- const isExpanded = expandedNodeIds . includes ( node . id ) ;
88- const isDetailsExpanded = expandedDetailsNodeNames . includes ( node . id ) ;
89- const isChecked = isNodeChecked ( node ) ;
90- let icon = leafIcon ;
91- if ( node . children ) {
92- icon = isExpanded ? expandedIcon : collapsedIcon ;
93- }
94-
95- const treeRow : TdProps [ 'treeRow' ] = {
96- onCollapse : ( ) =>
97- setExpandedNodeIds ( ( prevExpanded ) => {
98- const otherExpandedNodeIds = prevExpanded . filter ( ( id ) => id !== node . id ) ;
99- return isExpanded ? otherExpandedNodeIds : [ ...otherExpandedNodeIds , node . id ] ;
100- } ) ,
101- onToggleRowDetails : ( ) =>
102- setExpandedDetailsNodeIds ( ( prevDetailsExpanded ) => {
103- const otherDetailsExpandedNodeIds = prevDetailsExpanded . filter ( ( id ) => id !== node . id ) ;
104- return isDetailsExpanded ? otherDetailsExpandedNodeIds : [ ...otherDetailsExpandedNodeIds , node . id ] ;
105- } ) ,
106- onCheckChange : ( isSelectDisabled ?.( node ) || ! onSelect ) ? undefined : ( _event , isChecking ) => onSelect ?.( isChecking , getDescendants ( node ) ) ,
107- rowIndex,
108- props : {
109- isExpanded,
110- isDetailsExpanded,
111- isHidden,
112- 'aria-level' : level ,
113- 'aria-posinset' : posinset ,
114- 'aria-setsize' : node . children ?. length ?? 0 ,
115- isChecked,
116- ouiaId : `${ ouiaId } -tree-toggle-${ node . id } ` ,
117- checkboxId : `checkbox_id_${ node . id ?. toLowerCase ( ) . replace ( / \s + / g, '_' ) } ` ,
118- icon,
61+ const nodes = useMemo ( ( ) => {
62+
63+ const renderRows = (
64+ [ node , ...remainingNodes ] : DataViewTrTree [ ] ,
65+ level = 1 ,
66+ posinset = 1 ,
67+ rowIndex = 0 ,
68+ isHidden = false
69+ ) : React . ReactNode [ ] => {
70+ if ( ! node ) {
71+ return [ ] ;
11972 }
120- } ;
73+ const isExpanded = expandedNodeIds . includes ( node . id ) ;
74+ const isDetailsExpanded = expandedDetailsNodeNames . includes ( node . id ) ;
75+ const isChecked = isSelected && isNodeChecked ( node , isSelected ) ;
76+ let icon = leafIcon ;
77+ if ( node . children ) {
78+ icon = isExpanded ? expandedIcon : collapsedIcon ;
79+ }
80+
81+ const treeRow : TdProps [ 'treeRow' ] = {
82+ onCollapse : ( ) =>
83+ setExpandedNodeIds ( prevExpanded => {
84+ const otherExpandedNodeIds = prevExpanded . filter ( id => id !== node . id ) ;
85+ return isExpanded ? otherExpandedNodeIds : [ ...otherExpandedNodeIds , node . id ] ;
86+ } ) ,
87+ onToggleRowDetails : ( ) =>
88+ setExpandedDetailsNodeIds ( prevDetailsExpanded => {
89+ const otherDetailsExpandedNodeIds = prevDetailsExpanded . filter ( id => id !== node . id ) ;
90+ return isDetailsExpanded ? otherDetailsExpandedNodeIds : [ ...otherDetailsExpandedNodeIds , node . id ] ;
91+ } ) ,
92+ onCheckChange : ( isSelectDisabled ?.( node ) || ! onSelect ) ? undefined : ( _event , isChecking ) => onSelect ?.( isChecking , getDescendants ( node ) ) ,
93+ rowIndex,
94+ props : {
95+ isExpanded,
96+ isDetailsExpanded,
97+ isHidden,
98+ 'aria-level' : level ,
99+ 'aria-posinset' : posinset ,
100+ 'aria-setsize' : node . children ?. length ?? 0 ,
101+ isChecked,
102+ ouiaId : `${ ouiaId } -tree-toggle-${ node . id } ` ,
103+ checkboxId : `checkbox_id_${ node . id ?. toLowerCase ( ) . replace ( / \s + / g, '_' ) } ` ,
104+ icon,
105+ } ,
106+ } ;
121107
122- const childRows =
123- node . children && node . children . length
108+ const childRows = node . children ?. length
124109 ? renderRows ( node . children , level + 1 , 1 , rowIndex + 1 , ! isExpanded || isHidden )
125110 : [ ] ;
126111
127- return [
128- < TreeRowWrapper key = { node . id } row = { { props : treeRow . props } } >
129- { node . row . map ( ( cell , colIndex ) => {
130- const cellIsObject = isDataViewTdObject ( cell ) ;
131- return (
132- < Td
133- key = { colIndex }
134- treeRow = { colIndex === 0 ? treeRow : undefined }
135- { ...( cellIsObject && ( cell ?. props ?? { } ) ) }
136- data-ouia-component-id = { `${ ouiaId } -td-${ rowIndex } -${ colIndex } ` }
137- >
138- { cellIsObject ? cell . cell : cell }
139- </ Td >
140- )
141- } ) }
142- </ TreeRowWrapper > ,
143- ...childRows ,
144- ...renderRows ( remainingNodes , level , posinset + 1 , rowIndex + 1 + childRows . length , isHidden )
145- ] ;
146- } ;
112+ return [
113+ < TreeRowWrapper key = { node . id } row = { { props : treeRow . props } } >
114+ { node . row . map ( ( cell , colIndex ) => {
115+ const cellIsObject = isDataViewTdObject ( cell ) ;
116+ return (
117+ < Td
118+ key = { colIndex }
119+ treeRow = { colIndex === 0 ? treeRow : undefined }
120+ { ...( cellIsObject && ( cell ?. props ?? { } ) ) }
121+ data-ouia-component-id = { `${ ouiaId } -td-${ rowIndex } -${ colIndex } ` }
122+ >
123+ { cellIsObject ? cell . cell : cell }
124+ </ Td >
125+ ) ;
126+ } ) }
127+ </ TreeRowWrapper > ,
128+ ...childRows ,
129+ ...renderRows ( remainingNodes , level , posinset + 1 , rowIndex + 1 + childRows . length , isHidden ) ,
130+ ] ;
131+ } ;
132+
133+ return renderRows ( rows ) ;
134+ } , [ rows , expandedNodeIds , expandedDetailsNodeNames , leafIcon , expandedIcon , collapsedIcon , isSelected , onSelect , isSelectDisabled , ouiaId ] ) ;
147135
148136 return (
149137 < Table isTreeTable aria-label = "Data table" ouiaId = { ouiaId } { ...props } >
150138 < DataViewTableHeader isTreeTable columns = { columns } ouiaId = { ouiaId } />
151- < Tbody >
152- { renderRows ( rows ) }
153- </ Tbody >
139+ < Tbody > { nodes } </ Tbody >
154140 </ Table >
155141 ) ;
156142} ;
0 commit comments