1
1
import React , { Component , ReactNode , CSSProperties } from "react" ;
2
- import PropTypes from "prop-types" ;
3
2
import throttle from "./utils/throttle" ;
4
3
import { ThresholdUnits , parseThreshold } from "./utils/threshold" ;
4
+
5
5
type Fn = ( ) => any ;
6
6
interface Props {
7
7
next : Fn ;
8
- hasMore : ReactNode ;
8
+ hasMore : boolean ;
9
9
children : ReactNode ;
10
10
loader : ReactNode ;
11
11
scrollThreshold : number | string ;
12
12
endMessage : ReactNode ;
13
13
style : CSSProperties ;
14
14
height : number ;
15
15
scrollableTarget : ReactNode ;
16
- hasChildren : ReactNode ;
17
- pullDownToRefresh : ReactNode ;
16
+ hasChildren : boolean ;
17
+ pullDownToRefresh : boolean ;
18
18
pullDownToRefreshContent : ReactNode ;
19
19
releaseToRefreshContent : ReactNode ;
20
20
pullDownToRefreshThreshold : number ;
21
21
refreshFunction : Fn ;
22
- onScroll : Fn ;
22
+ onScroll : ( e : MouseEvent ) => any ;
23
23
dataLength : number ;
24
+ initialScrollY : number ;
25
+ key : string ;
26
+ className : string ;
24
27
}
25
28
26
29
interface State {
@@ -29,7 +32,6 @@ interface State {
29
32
}
30
33
31
34
export default class InfiniteScroll extends Component < Props , State > {
32
- private throttledOnScrollListener : ( ) => void ;
33
35
constructor ( props : Props ) {
34
36
super ( props ) ;
35
37
@@ -48,32 +50,43 @@ export default class InfiniteScroll extends Component<Props, State> {
48
50
this . getScrollableTarget = this . getScrollableTarget . bind ( this ) ;
49
51
}
50
52
53
+ private throttledOnScrollListener : ( ) => void ;
54
+ private _scrollableNode : HTMLElement | undefined | null ;
55
+ private el : HTMLElement | undefined | Window & typeof globalThis ;
56
+ private _infScroll : HTMLDivElement | undefined ;
51
57
private lastScrollTop = 0 ;
52
58
private actionTriggered = false ;
59
+ private _pullDown : HTMLDivElement | undefined ;
53
60
54
61
// variables to keep track of pull down behaviour
55
- private startY = 0 ;
56
- private currentY = 0 ;
57
- private dragging = false ;
62
+ private startY : number = 0 ;
63
+ private currentY : number = 0 ;
64
+ private dragging : boolean = false ;
58
65
59
66
// will be populated in componentDidMount
60
67
// based on the height of the pull down element
61
- private maxPullDownDistance = 0 ;
68
+ private maxPullDownDistance : number = 0 ;
69
+
62
70
componentDidMount ( ) {
63
71
this . _scrollableNode = this . getScrollableTarget ( ) ;
64
72
this . el = this . props . height
65
73
? this . _infScroll
66
74
: this . _scrollableNode || window ;
67
- this . el . addEventListener ( "scroll" , this . throttledOnScrollListener ) ;
75
+
76
+ if ( this . el ) {
77
+ this . el . addEventListener ( "scroll" , this . throttledOnScrollListener ) ;
78
+ }
68
79
69
80
if (
70
81
typeof this . props . initialScrollY === "number" &&
82
+ this . el &&
83
+ this . el instanceof HTMLElement &&
71
84
this . el . scrollHeight > this . props . initialScrollY
72
85
) {
73
86
this . el . scrollTo ( 0 , this . props . initialScrollY ) ;
74
87
}
75
88
76
- if ( this . props . pullDownToRefresh ) {
89
+ if ( this . props . pullDownToRefresh && this . el ) {
77
90
this . el . addEventListener ( "touchstart" , this . onStart ) ;
78
91
this . el . addEventListener ( "touchmove" , this . onMove ) ;
79
92
this . el . addEventListener ( "touchend" , this . onEnd ) ;
@@ -83,7 +96,12 @@ export default class InfiniteScroll extends Component<Props, State> {
83
96
this . el . addEventListener ( "mouseup" , this . onEnd ) ;
84
97
85
98
// get BCR of pullDown element to position it above
86
- this . maxPullDownDistance = this . _pullDown . firstChild . getBoundingClientRect ( ) . height ;
99
+ this . maxPullDownDistance =
100
+ ( this . _pullDown &&
101
+ this . _pullDown . firstChild &&
102
+ ( this . _pullDown . firstChild as HTMLDivElement ) . getBoundingClientRect ( )
103
+ . height ) ||
104
+ 0 ;
87
105
this . forceUpdate ( ) ;
88
106
89
107
if ( typeof this . props . refreshFunction !== "function" ) {
@@ -97,20 +115,22 @@ export default class InfiniteScroll extends Component<Props, State> {
97
115
}
98
116
99
117
componentWillUnmount ( ) {
100
- this . el . removeEventListener ( "scroll" , this . throttledOnScrollListener ) ;
118
+ if ( this . el ) {
119
+ this . el . removeEventListener ( "scroll" , this . throttledOnScrollListener ) ;
101
120
102
- if ( this . props . pullDownToRefresh ) {
103
- this . el . removeEventListener ( "touchstart" , this . onStart ) ;
104
- this . el . removeEventListener ( "touchmove" , this . onMove ) ;
105
- this . el . removeEventListener ( "touchend" , this . onEnd ) ;
121
+ if ( this . props . pullDownToRefresh ) {
122
+ this . el . removeEventListener ( "touchstart" , this . onStart ) ;
123
+ this . el . removeEventListener ( "touchmove" , this . onMove ) ;
124
+ this . el . removeEventListener ( "touchend" , this . onEnd ) ;
106
125
107
- this . el . removeEventListener ( "mousedown" , this . onStart ) ;
108
- this . el . removeEventListener ( "mousemove" , this . onMove ) ;
109
- this . el . removeEventListener ( "mouseup" , this . onEnd ) ;
126
+ this . el . removeEventListener ( "mousedown" , this . onStart ) ;
127
+ this . el . removeEventListener ( "mousemove" , this . onMove ) ;
128
+ this . el . removeEventListener ( "mouseup" , this . onEnd ) ;
129
+ }
110
130
}
111
131
}
112
132
113
- componentWillReceiveProps ( props ) {
133
+ componentWillReceiveProps ( props : Props ) {
114
134
// do nothing when dataLength and key are unchanged
115
135
if (
116
136
this . props . key === props . key &&
@@ -141,20 +161,32 @@ export default class InfiniteScroll extends Component<Props, State> {
141
161
return null ;
142
162
}
143
163
144
- onStart ( evt ) {
164
+ onStart : EventListener = ( evt : Event ) => {
145
165
if ( this . lastScrollTop ) return ;
146
166
147
167
this . dragging = true ;
148
- this . startY = evt . pageY || evt . touches [ 0 ] . pageY ;
168
+
169
+ if ( evt instanceof MouseEvent ) {
170
+ this . startY = evt . pageY ;
171
+ } else if ( evt instanceof TouchEvent ) {
172
+ this . startY = evt . touches [ 0 ] . pageY ;
173
+ }
149
174
this . currentY = this . startY ;
150
175
151
- this . _infScroll . style . willChange = "transform" ;
152
- this . _infScroll . style . transition = `transform 0.2s cubic-bezier(0,0,0.31,1)` ;
153
- }
176
+ if ( this . _infScroll ) {
177
+ this . _infScroll . style . willChange = "transform" ;
178
+ this . _infScroll . style . transition = `transform 0.2s cubic-bezier(0,0,0.31,1)` ;
179
+ }
180
+ } ;
154
181
155
- onMove ( evt ) {
182
+ onMove : EventListener = ( evt : Event ) => {
156
183
if ( ! this . dragging ) return ;
157
- this . currentY = evt . pageY || evt . touches [ 0 ] . pageY ;
184
+
185
+ if ( evt instanceof MouseEvent ) {
186
+ this . currentY = evt . pageY ;
187
+ } else if ( evt instanceof TouchEvent ) {
188
+ this . currentY = evt . touches [ 0 ] . pageY ;
189
+ }
158
190
159
191
// user is scrolling down to up
160
192
if ( this . currentY < this . startY ) return ;
@@ -168,12 +200,14 @@ export default class InfiniteScroll extends Component<Props, State> {
168
200
// so you can drag upto 1.5 times of the maxPullDownDistance
169
201
if ( this . currentY - this . startY > this . maxPullDownDistance * 1.5 ) return ;
170
202
171
- this . _infScroll . style . overflow = "visible" ;
172
- this . _infScroll . style . transform = `translate3d(0px, ${ this . currentY -
173
- this . startY } px, 0px)`;
174
- }
203
+ if ( this . _infScroll ) {
204
+ this . _infScroll . style . overflow = "visible" ;
205
+ this . _infScroll . style . transform = `translate3d(0px, ${ this . currentY -
206
+ this . startY } px, 0px)`;
207
+ }
208
+ } ;
175
209
176
- onEnd ( evt ) {
210
+ onEnd : EventListener = evt => {
177
211
this . startY = 0 ;
178
212
this . currentY = 0 ;
179
213
@@ -191,9 +225,12 @@ export default class InfiniteScroll extends Component<Props, State> {
191
225
this . _infScroll . style . willChange = "none" ;
192
226
}
193
227
} ) ;
194
- }
228
+ } ;
195
229
196
- isElementAtBottom ( target , scrollThreshold = 0.8 ) {
230
+ isElementAtBottom (
231
+ target : HTMLElement ,
232
+ scrollThreshold : string | number = 0.8
233
+ ) {
197
234
const clientHeight =
198
235
target === document . body || target === document . documentElement
199
236
? window . screen . availHeight
@@ -213,7 +250,7 @@ export default class InfiniteScroll extends Component<Props, State> {
213
250
) ;
214
251
}
215
252
216
- onScrollListener ( event ) {
253
+ onScrollListener ( event : MouseEvent ) {
217
254
if ( typeof this . props . onScroll === "function" ) {
218
255
// Execute this callback in next tick so that it does not affect the
219
256
// functionality of the library.
@@ -222,7 +259,7 @@ export default class InfiniteScroll extends Component<Props, State> {
222
259
223
260
let target =
224
261
this . props . height || this . _scrollableNode
225
- ? event . target
262
+ ? ( event . target as HTMLElement )
226
263
: document . documentElement . scrollTop
227
264
? document . documentElement
228
265
: document . body ;
@@ -249,10 +286,14 @@ export default class InfiniteScroll extends Component<Props, State> {
249
286
overflow : "auto" ,
250
287
WebkitOverflowScrolling : "touch" ,
251
288
...this . props . style
252
- } ;
289
+ } as CSSProperties ;
253
290
const hasChildren =
254
291
this . props . hasChildren ||
255
- ! ! ( this . props . children && this . props . children . length ) ;
292
+ ! ! (
293
+ this . props . children &&
294
+ this . props . children instanceof Array &&
295
+ this . props . children . length
296
+ ) ;
256
297
257
298
// because heighted infiniteScroll visualy breaks
258
299
// on drag down as overflow becomes visible
@@ -264,13 +305,13 @@ export default class InfiniteScroll extends Component<Props, State> {
264
305
< div style = { outerDivStyle } >
265
306
< div
266
307
className = { `infinite-scroll-component ${ this . props . className || "" } ` }
267
- ref = { infScroll => ( this . _infScroll = infScroll ) }
308
+ ref = { ( infScroll : HTMLDivElement ) => ( this . _infScroll = infScroll ) }
268
309
style = { style }
269
310
>
270
311
{ this . props . pullDownToRefresh && (
271
312
< div
272
313
style = { { position : "relative" } }
273
- ref = { pullDown => ( this . _pullDown = pullDown ) }
314
+ ref = { ( pullDown : HTMLDivElement ) => ( this . _pullDown = pullDown ) }
274
315
>
275
316
< div
276
317
style = { {
@@ -298,31 +339,3 @@ export default class InfiniteScroll extends Component<Props, State> {
298
339
) ;
299
340
}
300
341
}
301
-
302
- InfiniteScroll . defaultProps = {
303
- pullDownToRefreshContent : < h3 > Pull down to refresh</ h3 > ,
304
- releaseToRefreshContent : < h3 > Release to refresh</ h3 > ,
305
- pullDownToRefreshThreshold : 100 ,
306
- disableBrowserPullToRefresh : true
307
- } ;
308
-
309
- InfiniteScroll . propTypes = {
310
- next : PropTypes . func ,
311
- hasMore : PropTypes . bool ,
312
- children : PropTypes . node ,
313
- loader : PropTypes . node . isRequired ,
314
- scrollThreshold : PropTypes . oneOfType ( [ PropTypes . number , PropTypes . string ] ) ,
315
- endMessage : PropTypes . node ,
316
- style : PropTypes . object ,
317
- height : PropTypes . number ,
318
- scrollableTarget : PropTypes . node ,
319
- hasChildren : PropTypes . bool ,
320
- pullDownToRefresh : PropTypes . bool ,
321
- pullDownToRefreshContent : PropTypes . node ,
322
- releaseToRefreshContent : PropTypes . node ,
323
- pullDownToRefreshThreshold : PropTypes . number ,
324
- refreshFunction : PropTypes . func ,
325
- onScroll : PropTypes . func ,
326
- dataLength : PropTypes . number . isRequired ,
327
- key : PropTypes . string
328
- } ;
0 commit comments