@@ -31,6 +31,7 @@ export function createNextFlatCheckedItems(
3131 if ( data . selectionMode === 'single' ) {
3232 return ImmutableMap . from ( [ [ data . value , data . checked ] ] ) ;
3333 }
34+
3435 const treeItem = headlessTree . get ( data . value ) ;
3536 if ( ! treeItem ) {
3637 if ( process . env . NODE_ENV !== 'production' ) {
@@ -42,34 +43,50 @@ export function createNextFlatCheckedItems(
4243 }
4344 return previousCheckedItems ;
4445 }
45- let nextCheckedItems = previousCheckedItems ;
46+
47+ // Calling `ImmutableMap.set()` creates a new ImmutableMap - avoid this in loops.
48+ // Instead write all updates to a native Map and create a new ImmutableMap at the end.
49+ // Note that all descendants of the toggled item are processed even if they are collapsed,
50+ // making the choice of algorithm more important.
51+
52+ const nextCheckedItemsMap = new Map ( ImmutableMap . dangerouslyGetInternalMap ( previousCheckedItems ) ) ;
53+
54+ // The toggled item itself
55+ nextCheckedItemsMap . set ( data . value , data . checked ) ;
56+
57+ // Descendant updates
4658 for ( const children of headlessTree . subtree ( data . value ) ) {
47- nextCheckedItems = nextCheckedItems . set ( children . value , data . checked ) ;
59+ nextCheckedItemsMap . set ( children . value , data . checked ) ;
4860 }
49- nextCheckedItems = nextCheckedItems . set ( data . value , data . checked ) ;
5061
62+ // Ancestor updates - must be done after adding descendants and the toggle item.
63+ // If any ancestor is mixed, all ancestors above it are mixed too.
5164 let isAncestorsMixed = false ;
52- for ( const parent of headlessTree . ancestors ( treeItem . value ) ) {
53- // if one parent is mixed, all ancestors are mixed
65+
66+ for ( const ancestor of headlessTree . ancestors ( treeItem . value ) ) {
5467 if ( isAncestorsMixed ) {
55- nextCheckedItems = nextCheckedItems . set ( parent . value , 'mixed' ) ;
68+ nextCheckedItemsMap . set ( ancestor . value , 'mixed' ) ;
5669 continue ;
5770 }
58- let checkedChildrenAmount = 0 ;
59- for ( const child of headlessTree . children ( parent . value ) ) {
60- if ( ( nextCheckedItems . get ( child . value ) || false ) === data . checked ) {
61- checkedChildrenAmount ++ ;
71+
72+ // For each ancestor, if all of its children now have the same checked state as the toggled item,
73+ // set the ancestor to that checked state too. Otherwise it is 'mixed'.
74+ let childrenWithSameState = 0 ;
75+ for ( const child of headlessTree . children ( ancestor . value ) ) {
76+ if ( ( nextCheckedItemsMap . get ( child . value ) || false ) === data . checked ) {
77+ childrenWithSameState ++ ;
6278 }
6379 }
64- // if all children are checked, parent is checked
65- if ( checkedChildrenAmount === parent . childrenValues . length ) {
66- nextCheckedItems = nextCheckedItems . set ( parent . value , data . checked ) ;
80+
81+ if ( childrenWithSameState === ancestor . childrenValues . length ) {
82+ nextCheckedItemsMap . set ( ancestor . value , data . checked ) ;
6783 } else {
68- // if one parent is mixed, all ancestors are mixed
84+ nextCheckedItemsMap . set ( ancestor . value , ' mixed' ) ;
6985 isAncestorsMixed = true ;
70- nextCheckedItems = nextCheckedItems . set ( parent . value , 'mixed' ) ;
7186 }
7287 }
88+
89+ const nextCheckedItems = ImmutableMap . from ( nextCheckedItemsMap ) ;
7390 return nextCheckedItems ;
7491}
7592
0 commit comments