@@ -6,6 +6,14 @@ import {FlattenedNode} from './shapes/nodeShapes';
6
6
import TreeState , { State } from './state/TreeState' ;
7
7
8
8
export 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
+
9
17
_cache = new CellMeasurerCache ( {
10
18
fixedWidth : true ,
11
19
minHeight : 20 ,
@@ -35,8 +43,66 @@ export default class Tree extends React.Component {
35
43
: nodes [ index ] ;
36
44
} ;
37
45
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
+
38
102
rowRenderer = ( { node, key, measure, style, NodeRenderer, index} ) => {
39
103
const { nodeMarginLeft} = this . props ;
104
+ const isHeader = this . isGroupHeader ( node ) ;
105
+ const className = isHeader ? 'tree-group-header' : '' ;
40
106
41
107
return (
42
108
< NodeRenderer
@@ -47,10 +113,41 @@ export default class Tree extends React.Component {
47
113
userSelect : 'none' ,
48
114
cursor : 'pointer' ,
49
115
} }
116
+ className = { className }
50
117
node = { node }
51
118
onChange = { this . props . onChange }
52
119
measure = { measure }
53
120
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 }
54
151
/>
55
152
) ;
56
153
} ;
@@ -66,25 +163,58 @@ export default class Tree extends React.Component {
66
163
) ;
67
164
} ;
68
165
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
+
69
177
render ( ) {
70
178
const { nodes, width, scrollToIndex, scrollToAlignment} = this . props ;
179
+ const { topStickyHeader} = this . state ;
180
+ const stickyHeaderHeight = topStickyHeader ? this . _cache . rowHeight ( { index : topStickyHeader . index } ) : 0 ;
71
181
72
182
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 >
86
198
) }
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 >
88
218
) ;
89
219
}
90
220
}
0 commit comments