@@ -83,7 +83,18 @@ const SyncStatusTable = ({
8383 const buildTreeRows = useMemo ( ( ) => {
8484 const rows = [ ] ;
8585
86- const addRow = ( node , level , parent , posinset , isHidden ) => {
86+ // Helper to check if a node will be visible (for setsize calculation)
87+ const isNodeVisible = ( node ) => {
88+ if ( ! showActiveOnly ) return true ;
89+ if ( node . type === 'repo' ) {
90+ const status = repoStatuses [ node . id ] ;
91+ return status ?. is_running ;
92+ }
93+ // For non-repo nodes, they're visible if shown
94+ return true ;
95+ } ;
96+
97+ const addRow = ( node , level , parent , posinset , ariaSetSize , isHidden ) => {
8798 const nodeId = `${ node . type } -${ node . id } ` ;
8899 const hasChildren = ( node . children && node . children . length > 0 ) ||
89100 ( node . repos && node . repos . length > 0 ) ;
@@ -95,6 +106,7 @@ const SyncStatusTable = ({
95106 level,
96107 parent,
97108 posinset,
109+ ariaSetSize,
98110 isHidden,
99111 hasChildren,
100112 isExpanded,
@@ -105,28 +117,58 @@ const SyncStatusTable = ({
105117 const reposToRender = node . repos || [ ] ;
106118 const allChildren = [ ...childrenToRender , ...reposToRender ] ;
107119
120+ // Calculate visible siblings for aria-setsize
121+ const visibleChildren = allChildren . filter ( isNodeVisible ) ;
122+ const visibleCount = visibleChildren . length ;
123+
108124 allChildren . forEach ( ( child , idx ) => {
109- addRow ( child , level + 1 , nodeId , idx + 1 , ! isExpanded || isHidden ) ;
125+ // Use position among all children for posinset, but visible count for setsize
126+ addRow ( child , level + 1 , nodeId , idx + 1 , visibleCount , ! isExpanded || isHidden ) ;
110127 } ) ;
111128 }
112129 } ;
113130
131+ // For root products, calculate visible products
132+ const visibleProducts = products . filter ( isNodeVisible ) ;
114133 products . forEach ( ( product , idx ) => {
115- addRow ( product , 1 , null , idx + 1 , false ) ;
134+ addRow ( product , 1 , null , idx + 1 , visibleProducts . length , false ) ;
116135 } ) ;
117136
118137 return rows ;
119- } , [ products , expandedNodeIds ] ) ;
138+ } , [ products , expandedNodeIds , showActiveOnly , repoStatuses ] ) ;
120139
121140 // Filter rows based on active only setting
122141 const visibleRows = useMemo ( ( ) => {
123142 if ( ! showActiveOnly ) return buildTreeRows ;
124143
125- return buildTreeRows . filter ( ( row ) => {
126- if ( row . type !== 'repo' ) return true ;
127- const status = repoStatuses [ row . id ] ;
128- return status ?. is_running ;
144+ // Build parent->children map
145+ const parentToChildren = { } ;
146+ buildTreeRows . forEach ( ( row ) => {
147+ if ( row . parent ) {
148+ if ( ! parentToChildren [ row . parent ] ) parentToChildren [ row . parent ] = [ ] ;
149+ parentToChildren [ row . parent ] . push ( row ) ;
150+ }
129151 } ) ;
152+
153+ // Recursive helper functions for visibility checking
154+ // hasVisibleChildren must be defined before isRowVisible due to ESLint rules
155+ function hasVisibleChildren ( row ) {
156+ const children = parentToChildren [ row . nodeId ] || [ ] ;
157+ // eslint-disable-next-line no-use-before-define
158+ return children . some ( child => isRowVisible ( child ) ) ;
159+ }
160+
161+ // Check if a row should be visible
162+ function isRowVisible ( row ) {
163+ if ( row . type === 'repo' ) {
164+ const status = repoStatuses [ row . id ] ;
165+ return status ?. is_running ;
166+ }
167+ // For non-repo nodes (product, minor, arch), visible if has visible children
168+ return hasVisibleChildren ( row ) ;
169+ }
170+
171+ return buildTreeRows . filter ( row => isRowVisible ( row ) ) ;
130172 } , [ buildTreeRows , showActiveOnly , repoStatuses ] ) ;
131173
132174 const toggleExpand = ( nodeId ) => {
@@ -150,21 +192,29 @@ const SyncStatusTable = ({
150192 // Get checkbox state for selectable nodes
151193 const nodeCheckboxState = isSelectableNode ? getNodeCheckboxState ( row ) : null ;
152194
153- const treeRow = {
154- onCollapse : row . hasChildren ? ( ) => toggleExpand ( row . nodeId ) : ( ) => { } ,
195+ // Build treeRow props - minimal for leaf nodes, full for expandable nodes
196+ const treeRow = row . hasChildren ? {
197+ onCollapse : ( ) => toggleExpand ( row . nodeId ) ,
155198 props : {
156199 'aria-level' : row . level ,
157200 'aria-posinset' : row . posinset ,
158- 'aria-setsize' : row . hasChildren ? ( row . children ?. length || 0 ) + ( row . repos ?. length || 0 ) : 0 ,
201+ 'aria-setsize' : row . ariaSetSize || 0 ,
159202 isExpanded : row . isExpanded ,
160203 isHidden : row . isHidden ,
161204 } ,
205+ } : {
206+ props : {
207+ 'aria-level' : row . level ,
208+ 'aria-posinset' : row . posinset ,
209+ 'aria-setsize' : 0 , // MUST be 0 for leaf nodes to hide expand button
210+ isHidden : row . isHidden ,
211+ } ,
162212 } ;
163213
164214 return (
165215 < TreeRowWrapper
166216 key = { row . nodeId }
167- row = { treeRow }
217+ row = { row . hasChildren ? treeRow : { props : treeRow . props } }
168218 >
169219 < Td dataLabel = { __ ( 'Name' ) } treeRow = { treeRow } >
170220 { isRepo && (
@@ -237,14 +287,13 @@ const SyncStatusTable = ({
237287 } ;
238288
239289 return (
240- < div style = { { paddingTop : '8px' } } >
241- < Table
242- aria-label = { __ ( 'Sync Status' ) }
243- variant = "compact"
244- isTreeTable
245- isStickyHeader
246- ouiaId = "sync-status-table"
247- >
290+ < Table
291+ aria-label = { __ ( 'Sync Status' ) }
292+ variant = "compact"
293+ isTreeTable
294+ isStickyHeader
295+ ouiaId = "sync-status-table"
296+ >
248297 < Thead >
249298 < Tr ouiaId = "sync-status-table-header" >
250299 < Th
@@ -273,7 +322,6 @@ const SyncStatusTable = ({
273322 { visibleRows . map ( row => renderRow ( row ) ) }
274323 </ Tbody >
275324 </ Table >
276- </ div >
277325 ) ;
278326} ;
279327
0 commit comments