@@ -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 ,
13+ } ;
14+ this . _listRef = React . createRef ( ) ;
15+ }
16+
917 _cache = new CellMeasurerCache ( {
1018 fixedWidth : true ,
1119 minHeight : 20 ,
@@ -35,8 +43,66 @@ export default class Tree extends React.Component {
3543 : nodes [ index ] ;
3644 } ;
3745
46+ isGroupHeader = node => {
47+ return node . children && node . children . length > 0 && node . deepness === 0 ;
48+ } ;
49+
50+ componentDidMount ( ) {
51+ if ( this . _listRef . current ) {
52+ const list = this . _listRef . current ;
53+ const grid = list && list . Grid ;
54+ if ( grid ) {
55+ this . handleScroll ( {
56+ scrollTop : grid . state . scrollTop ,
57+ } ) ;
58+ }
59+ }
60+ }
61+
62+ getAllHeaders = ( ) => {
63+ const rowCount = this . getRowCount ( ) ;
64+ const headers = [ ] ;
65+ let cumulativeHeight = 0 ;
66+
67+ for ( let i = 0 ; i < rowCount ; i ++ ) {
68+ const node = this . getNode ( i ) ;
69+
70+ if ( this . isGroupHeader ( node ) ) {
71+ headers . push ( {
72+ node,
73+ index : i ,
74+ top : cumulativeHeight ,
75+ } ) ;
76+ }
77+
78+ cumulativeHeight += this . _cache . rowHeight ( { index : i } ) ;
79+ }
80+
81+ return headers ;
82+ } ;
83+
84+ handleScroll = ( { scrollTop} ) => {
85+ if ( ! this . _listRef . current ) return ;
86+
87+ const allHeaders = this . getAllHeaders ( ) ;
88+
89+ const topStickyHeader = allHeaders . filter ( h => h . top <= scrollTop ) . pop ( ) || null ;
90+
91+ const currentStickyId =
92+ this . state . topStickyHeader && this . state . topStickyHeader . node && this . state . topStickyHeader . node . id ;
93+ const newStickyId = topStickyHeader && topStickyHeader . node && topStickyHeader . node . id ;
94+
95+ if ( currentStickyId !== newStickyId ) {
96+ this . setState ( {
97+ topStickyHeader,
98+ } ) ;
99+ }
100+ } ;
101+
38102 rowRenderer = ( { node, key, measure, style, NodeRenderer, index} ) => {
39103 const { nodeMarginLeft} = this . props ;
104+ const isHeader = this . isGroupHeader ( node ) ;
105+ const className = isHeader ? 'tree-group-header' : '' ;
40106
41107 return (
42108 < NodeRenderer
@@ -47,10 +113,41 @@ export default class Tree extends React.Component {
47113 userSelect : 'none' ,
48114 cursor : 'pointer' ,
49115 } }
116+ className = { className }
50117 node = { node }
51118 onChange = { this . props . onChange }
52119 measure = { measure }
53120 index = { index }
121+ isGroupHeader = { isHeader }
122+ />
123+ ) ;
124+ } ;
125+
126+ renderStickyHeader = ( ) => {
127+ const { topStickyHeader} = this . state ;
128+ if ( ! topStickyHeader ) return null ;
129+
130+ const { NodeRenderer, nodeMarginLeft} = this . props ;
131+ const index = topStickyHeader . index ;
132+ const currentNode = this . getNode ( index ) ;
133+
134+ return (
135+ < NodeRenderer
136+ key = { `sticky-header-${ currentNode . id } ` }
137+ style = { {
138+ marginLeft : currentNode . deepness * nodeMarginLeft ,
139+ userSelect : 'none' ,
140+ cursor : 'pointer' ,
141+ width : '100%' ,
142+ background : '#fff' ,
143+ zIndex : 10 ,
144+ } }
145+ className = "tree-group-header tree-sticky"
146+ node = { currentNode }
147+ onChange = { this . props . onChange }
148+ index = { index }
149+ isGroupHeader = { true }
150+ isSticky = { true }
54151 />
55152 ) ;
56153 } ;
@@ -66,25 +163,58 @@ export default class Tree extends React.Component {
66163 ) ;
67164 } ;
68165
166+ componentDidUpdate ( prevProps ) {
167+ if ( prevProps . nodes !== this . props . nodes ) {
168+ this . _cache . clearAll ( ) ;
169+ if ( this . _listRef . current ) {
170+ this . _listRef . current . recomputeRowHeights ( ) ;
171+ }
172+
173+ this . forceUpdate ( ) ;
174+ }
175+ }
176+
69177 render ( ) {
70178 const { nodes, width, scrollToIndex, scrollToAlignment} = this . props ;
179+ const { topStickyHeader} = this . state ;
180+ const stickyHeaderHeight = topStickyHeader ? this . _cache . rowHeight ( { index : topStickyHeader . index } ) : 0 ;
71181
72182 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- />
183+ < div className = "tree-container" style = { { position : 'relative' , height : '100%' } } >
184+ { topStickyHeader && (
185+ < div
186+ className = "tree-sticky-header-container"
187+ style = { {
188+ position : 'absolute' ,
189+ top : 0 ,
190+ left : 0 ,
191+ right : 0 ,
192+ zIndex : 100 ,
193+ height : `${ stickyHeaderHeight } px` ,
194+ } }
195+ >
196+ { this . renderStickyHeader ( ) }
197+ </ div >
86198 ) }
87- </ AutoSizer >
199+
200+ < AutoSizer disableWidth = { Boolean ( width ) } >
201+ { ( { height, width : autoWidth } ) => (
202+ < List
203+ deferredMeasurementCache = { this . _cache }
204+ ref = { this . _listRef }
205+ height = { height }
206+ rowCount = { this . getRowCount ( ) }
207+ rowHeight = { this . _cache . rowHeight }
208+ rowRenderer = { this . measureRowRenderer ( nodes ) }
209+ width = { width || autoWidth }
210+ scrollToIndex = { scrollToIndex }
211+ scrollToAlignment = { scrollToAlignment }
212+ onScroll = { this . handleScroll }
213+ overscanRowCount = { 20 }
214+ />
215+ ) }
216+ </ AutoSizer >
217+ </ div >
88218 ) ;
89219 }
90220}
0 commit comments