@@ -6,6 +6,14 @@ import {FlattenedNode} from './shapes/nodeShapes';
66import TreeState , { State } from './state/TreeState' ;
77
88export default class Tree extends React . Component {
9+ constructor ( props ) {
10+ super ( props ) ;
11+ this . state = {
12+ topStickyHeader : null , // The header that should be sticky
13+ } ;
14+ this . _listRef = React . createRef ( ) ;
15+ }
16+
917 _cache = new CellMeasurerCache ( {
1018 fixedWidth : true ,
1119 minHeight : 20 ,
@@ -35,8 +43,80 @@ export default class Tree extends React.Component {
3543 : nodes [ index ] ;
3644 } ;
3745
46+ // Determine if a node is a group header
47+ isGroupHeader = node => {
48+ // Group headers are typically parent nodes with children
49+ // and deepness of 0 (root level)
50+ return node . children && node . children . length > 0 && node . deepness === 0 ;
51+ } ;
52+
53+ componentDidMount ( ) {
54+ // Initial check for headers after mounting
55+ if ( this . _listRef . current ) {
56+ const list = this . _listRef . current ;
57+ const grid = list && list . Grid ;
58+ if ( grid ) {
59+ this . handleScroll ( {
60+ scrollTop : grid . state . scrollTop ,
61+ scrollHeight : grid . state . scrollHeight ,
62+ clientHeight : grid . props . height ,
63+ } ) ;
64+ }
65+ }
66+ }
67+
68+ // Get all headers in the current data - simplified to reduce loops
69+ getAllHeaders = ( ) => {
70+ const rowCount = this . getRowCount ( ) ;
71+ const headers = [ ] ;
72+ let cumulativeHeight = 0 ;
73+
74+ for ( let i = 0 ; i < rowCount ; i ++ ) {
75+ const node = this . getNode ( i ) ;
76+
77+ if ( this . isGroupHeader ( node ) ) {
78+ headers . push ( {
79+ node,
80+ index : i ,
81+ top : cumulativeHeight ,
82+ } ) ;
83+ }
84+
85+ cumulativeHeight += this . _cache . rowHeight ( { index : i } ) ;
86+ }
87+
88+ return headers ;
89+ } ;
90+
91+ // Handle scroll events to update sticky headers - simplified logic
92+ handleScroll = ( { scrollTop, scrollHeight, clientHeight} ) => {
93+ if ( ! this . _listRef . current ) return ;
94+
95+ // Get all headers in the tree
96+ const allHeaders = this . getAllHeaders ( ) ;
97+
98+ // Find the header that should be sticky (last one whose top is <= scrollTop)
99+ const topStickyHeader = allHeaders
100+ . filter ( h => h . top <= scrollTop )
101+ . pop ( ) || null ;
102+
103+ // Only update state if something has changed
104+ const currentStickyId = this . state . topStickyHeader && this . state . topStickyHeader . node && this . state . topStickyHeader . node . id ;
105+ const newStickyId = topStickyHeader && topStickyHeader . node && topStickyHeader . node . id ;
106+
107+ if ( currentStickyId !== newStickyId ) {
108+ this . setState ( {
109+ topStickyHeader,
110+ } ) ;
111+ }
112+ } ;
113+
38114 rowRenderer = ( { node, key, measure, style, NodeRenderer, index} ) => {
39115 const { nodeMarginLeft} = this . props ;
116+ const isHeader = this . isGroupHeader ( node ) ;
117+
118+ // Add a class to identify group headers
119+ const className = isHeader ? 'tree-group-header' : '' ;
40120
41121 return (
42122 < NodeRenderer
@@ -47,10 +127,43 @@ export default class Tree extends React.Component {
47127 userSelect : 'none' ,
48128 cursor : 'pointer' ,
49129 } }
130+ className = { className }
50131 node = { node }
51132 onChange = { this . props . onChange }
52133 measure = { measure }
53134 index = { index }
135+ isGroupHeader = { isHeader }
136+ />
137+ ) ;
138+ } ;
139+
140+ // Render the sticky header - simplified render method
141+ renderStickyHeader = ( ) => {
142+ const { topStickyHeader} = this . state ;
143+ if ( ! topStickyHeader ) return null ;
144+
145+ const { NodeRenderer, nodeMarginLeft} = this . props ;
146+ // Always use the current node from the tree to ensure we have the latest state
147+ const index = topStickyHeader . index ;
148+ const currentNode = this . getNode ( index ) ;
149+
150+ return (
151+ < NodeRenderer
152+ key = { `sticky-header-${ currentNode . id } ` }
153+ style = { {
154+ marginLeft : currentNode . deepness * nodeMarginLeft ,
155+ userSelect : 'none' ,
156+ cursor : 'pointer' ,
157+ width : '100%' ,
158+ background : '#fff' ,
159+ zIndex : 10 ,
160+ } }
161+ className = "tree-group-header tree-sticky"
162+ node = { currentNode }
163+ onChange = { this . props . onChange }
164+ index = { index }
165+ isGroupHeader = { true }
166+ isSticky = { true }
54167 />
55168 ) ;
56169 } ;
@@ -66,25 +179,63 @@ export default class Tree extends React.Component {
66179 ) ;
67180 } ;
68181
182+ componentDidUpdate ( prevProps ) {
183+ // If nodes change, reset the cache
184+ if ( prevProps . nodes !== this . props . nodes ) {
185+ this . _cache . clearAll ( ) ;
186+ if ( this . _listRef . current ) {
187+ this . _listRef . current . recomputeRowHeights ( ) ;
188+ }
189+
190+ // Force rerender of sticky header when nodes change
191+ this . forceUpdate ( ) ;
192+ }
193+ }
194+
69195 render ( ) {
70196 const { nodes, width, scrollToIndex, scrollToAlignment} = this . props ;
197+ const { topStickyHeader} = this . state ;
198+
199+ // Calculate the height of the sticky header to properly offset the list
200+ const stickyHeaderHeight = topStickyHeader ? this . _cache . rowHeight ( { index : topStickyHeader . index } ) : 0 ;
71201
72202 return (
73- < AutoSizer disableWidth = { Boolean ( width ) } >
74- { ( { height, width : autoWidth } ) => (
75- < List
76- deferredMeasurementCache = { this . _cache }
77- ref = { r => ( this . _list = r ) }
78- height = { height }
79- rowCount = { this . getRowCount ( ) }
80- rowHeight = { this . _cache . rowHeight }
81- rowRenderer = { this . measureRowRenderer ( nodes ) }
82- width = { width || autoWidth }
83- scrollToIndex = { scrollToIndex }
84- scrollToAlignment = { scrollToAlignment }
85- />
203+ < div className = "tree-container" style = { { position : 'relative' , height : '100%' } } >
204+ { /* Sticky header container - simplified markup */ }
205+ { topStickyHeader && (
206+ < div
207+ className = "tree-sticky-header-container"
208+ style = { {
209+ position : 'absolute' ,
210+ top : 0 ,
211+ left : 0 ,
212+ right : 0 ,
213+ zIndex : 100 ,
214+ height : `${ stickyHeaderHeight } px` ,
215+ } }
216+ >
217+ { this . renderStickyHeader ( ) }
218+ </ div >
86219 ) }
87- </ AutoSizer >
220+
221+ < AutoSizer disableWidth = { Boolean ( width ) } >
222+ { ( { height, width : autoWidth } ) => (
223+ < List
224+ deferredMeasurementCache = { this . _cache }
225+ ref = { this . _listRef }
226+ height = { height }
227+ rowCount = { this . getRowCount ( ) }
228+ rowHeight = { this . _cache . rowHeight }
229+ rowRenderer = { this . measureRowRenderer ( nodes ) }
230+ width = { width || autoWidth }
231+ scrollToIndex = { scrollToIndex }
232+ scrollToAlignment = { scrollToAlignment }
233+ onScroll = { this . handleScroll }
234+ overscanRowCount = { 20 }
235+ />
236+ ) }
237+ </ AutoSizer >
238+ </ div >
88239 ) ;
89240 }
90241}
0 commit comments