@@ -5,10 +5,10 @@ import isHidden from 'licia/isHidden'
5
5
import now from 'licia/now'
6
6
import ResizeSensor from 'licia/ResizeSensor'
7
7
import isEmpty from 'licia/isEmpty'
8
- import unique from 'licia/unique'
9
8
import map from 'licia/map'
10
- import debounce from 'licia/debounce'
11
9
import each from 'licia/each'
10
+ import clone from 'licia/clone'
11
+ import some from 'licia/some'
12
12
13
13
/** IOptions */
14
14
export interface IOptions extends IComponentOptions {
@@ -34,6 +34,7 @@ export default class VirtualList extends Component<IOptions> {
34
34
private $space : $ . $
35
35
private space : HTMLElement
36
36
private spaceHeight = 0
37
+ private spaceWidth = 0
37
38
private topSpaceHeight = 0
38
39
// @ts -ignore
39
40
private bottomSpaceHeight = 0
@@ -45,7 +46,7 @@ export default class VirtualList extends Component<IOptions> {
45
46
private isAtBottom = true
46
47
private updateTimer : NodeJS . Timeout | null = null
47
48
private updateItems : Item [ ] = [ ]
48
- private resizeSensor : ResizeSensor
49
+ private displayItems : Item [ ] = [ ]
49
50
private scrollTimer : NodeJS . Timeout | null = null
50
51
constructor ( container : HTMLElement , options : IOptions = { } ) {
51
52
super ( container , { compName : 'virtual-list' } , options )
@@ -63,34 +64,35 @@ export default class VirtualList extends Component<IOptions> {
63
64
this . $space = this . find ( '.items-space' )
64
65
this . space = this . $space . get ( 0 ) as HTMLElement
65
66
66
- this . resizeSensor = new ResizeSensor ( this . space )
67
-
68
67
this . bindEvent ( )
69
68
}
69
+ /** Clear all items. */
70
70
clear ( ) {
71
71
this . items = [ ]
72
- this . render ( )
72
+ this . el . textContent = ''
73
73
}
74
+ /** Append item. */
74
75
append ( el : HTMLElement ) {
75
76
const item = new Item ( el , this . el )
76
77
this . items . push ( item )
77
78
this . updateSize ( item )
78
79
}
80
+ /** Set items. */
79
81
setItems ( els : HTMLElement [ ] ) {
80
82
each ( this . items , ( item ) => item . destroy ( ) )
81
83
this . items = map ( els , ( el ) => new Item ( el , this . el ) )
82
84
this . updateItems = [ ]
83
- this . updateAllSize ( )
84
85
}
85
- private updateAllSize = debounce ( ( ) => {
86
- this . updateItems . push ( ...this . items )
87
- this . updateItems = unique ( this . updateItems )
88
- if ( ! this . updateTimer ) {
89
- this . _updateSize ( )
86
+ /** Recalculate all heights. */
87
+ update ( ) {
88
+ this . updateSize ( )
89
+ }
90
+ private updateSize ( item ?: Item ) {
91
+ if ( item ) {
92
+ this . updateItems . push ( item )
93
+ } else {
94
+ this . updateItems = clone ( this . items )
90
95
}
91
- } , 1000 )
92
- private updateSize ( item : Item ) {
93
- this . updateItems . push ( item )
94
96
if ( ! this . updateTimer ) {
95
97
this . _updateSize ( )
96
98
}
@@ -135,17 +137,16 @@ export default class VirtualList extends Component<IOptions> {
135
137
private updateBottomSpace ( height : number ) {
136
138
this . bottomSpaceHeight = height
137
139
}
138
- private updateSpace ( height : number ) {
139
- if ( this . spaceHeight === height ) return
140
+ private updateSpace ( height : number , width : number ) {
141
+ if ( this . spaceHeight === height && this . spaceWidth === width ) {
142
+ return
143
+ }
140
144
this . spaceHeight = height
145
+ this . spaceWidth = width
141
146
this . space . style . height = height + 'px'
147
+ this . space . style . width = width + 'px'
142
148
}
143
149
private bindEvent ( ) {
144
- this . resizeSensor . addListener (
145
- throttle ( ( ) => {
146
- this . updateAllSize ( )
147
- } , 100 )
148
- )
149
150
this . $container . on ( 'scroll' , this . onScroll )
150
151
}
151
152
private render = throttle (
@@ -164,29 +165,43 @@ export default class VirtualList extends Component<IOptions> {
164
165
let topSpaceHeight = 0
165
166
let bottomSpaceHeight = 0
166
167
let currentHeight = 0
168
+ let currentWidth = this . spaceWidth
167
169
168
170
const len = items . length
169
171
170
- const frag = document . createDocumentFragment ( )
172
+ const displayItems = [ ]
171
173
for ( let i = 0 ; i < len ; i ++ ) {
172
174
const item = items [ i ]
173
- const { el , height } = item
175
+ const { height , width } = item
174
176
175
177
if ( currentHeight > bottom ) {
176
178
bottomSpaceHeight += height
177
179
} else if ( currentHeight + height > top ) {
178
- frag . appendChild ( el )
180
+ displayItems . push ( item )
179
181
} else if ( currentHeight < top ) {
180
182
topSpaceHeight += height
181
183
}
182
184
183
185
currentHeight += height
186
+
187
+ if ( currentWidth < width ) {
188
+ currentWidth = width
189
+ }
184
190
}
185
191
186
- this . updateSpace ( currentHeight )
192
+ this . updateSpace ( currentHeight , currentWidth )
187
193
this . updateTopSpace ( topSpaceHeight )
188
194
this . updateBottomSpace ( bottomSpaceHeight )
189
195
196
+ if ( ! some ( displayItems , ( item , idx ) => item !== this . displayItems [ idx ] ) ) {
197
+ return
198
+ }
199
+
200
+ const frag = document . createDocumentFragment ( )
201
+ for ( let i = 0 , len = displayItems . length ; i < len ; i ++ ) {
202
+ frag . appendChild ( displayItems [ i ] . el )
203
+ }
204
+
190
205
el . textContent = ''
191
206
el . appendChild ( frag )
192
207
@@ -278,7 +293,7 @@ class Item {
278
293
279
294
this . resizeSensor = new ResizeSensor ( el )
280
295
this . resizeSensor . addListener ( ( ) => {
281
- if ( el . parentNode === container && ! isHidden ( el ) ) {
296
+ if ( el . parentNode === container ) {
282
297
this . updateSize ( )
283
298
}
284
299
} )
@@ -287,6 +302,9 @@ class Item {
287
302
this . resizeSensor . destroy ( )
288
303
}
289
304
updateSize ( ) {
305
+ if ( isHidden ( this . el ) ) {
306
+ return
307
+ }
290
308
const { width, height } = this . el . getBoundingClientRect ( )
291
309
this . width = width
292
310
this . height = height
0 commit comments