@@ -9,6 +9,10 @@ import PropTypes from 'prop-types';
9
9
10
10
const Context = React . createContext ( ) ;
11
11
12
+ export function useOverflow ( ) {
13
+ return useContext ( Context ) ;
14
+ }
15
+
12
16
const containerStyle = {
13
17
display : 'flex' ,
14
18
flexDirection : 'column' ,
@@ -60,8 +64,6 @@ function getInitialState() {
60
64
} ;
61
65
}
62
66
63
- const emptyStyle = { } ;
64
-
65
67
/**
66
68
* The overflow state provider. At a minimum it must contain an
67
69
* `<Overflow.Content>` element, otherwise it will do nothing.
@@ -99,12 +101,13 @@ const emptyStyle = {};
99
101
export default function Overflow ( {
100
102
children,
101
103
onStateChange,
104
+ style : styleProp ,
102
105
tolerance = 0 ,
103
106
...rest
104
107
} ) {
105
108
const [ state , dispatch ] = useReducer ( reducer , null , getInitialState ) ;
106
109
const hidden = rest . hidden ;
107
- const styleProp = rest . style || emptyStyle ;
110
+ const viewportRef = useRef ( ) ;
108
111
109
112
const style = useMemo (
110
113
( ) => ( {
@@ -116,27 +119,33 @@ export default function Overflow({
116
119
// `display: none` and allow that, otherwise ensure we use the value from
117
120
// `containerStyle`.
118
121
display :
119
- hidden || styleProp . display === 'none' ? 'none' : containerStyle . display
122
+ hidden || ( styleProp && styleProp . display === 'none' )
123
+ ? 'none'
124
+ : containerStyle . display
120
125
} ) ,
121
126
[ hidden , styleProp ]
122
127
) ;
123
128
124
- const context = useMemo ( ( ) => {
125
- return {
129
+ const refs = useMemo ( ( ) => ( { viewport : viewportRef } ) , [ ] ) ;
130
+
131
+ const context = useMemo (
132
+ ( ) => ( {
126
133
state,
127
134
dispatch,
128
- tolerance
129
- } ;
130
- } , [ state , tolerance ] ) ;
135
+ tolerance,
136
+ refs
137
+ } ) ,
138
+ [ refs , state , tolerance ]
139
+ ) ;
131
140
132
141
useEffect ( ( ) => {
133
142
if ( onStateChange ) {
134
- onStateChange ( state ) ;
143
+ onStateChange ( state , refs ) ;
135
144
}
136
- } , [ onStateChange , state ] ) ;
145
+ } , [ onStateChange , refs , state ] ) ;
137
146
138
147
return (
139
- < div data-overflow-wrapper = "" { ...rest } style = { style } >
148
+ < div data-overflow-wrapper = "" style = { style } { ...rest } >
140
149
< Context . Provider value = { context } > { children } </ Context . Provider >
141
150
</ div >
142
151
) ;
@@ -150,8 +159,8 @@ Overflow.propTypes = {
150
159
*/
151
160
children : PropTypes . node ,
152
161
/**
153
- * Callback that receives the latest overflow state, if you’d like to react
154
- * to scrollability in a custom way.
162
+ * Callback that receives the latest overflow state and an object of refs, if
163
+ * you’d like to react to overflow in a custom way.
155
164
*/
156
165
onStateChange : PropTypes . func ,
157
166
/**
@@ -176,8 +185,8 @@ Overflow.propTypes = {
176
185
* interfering with the styles this component needs to function.
177
186
*/
178
187
function OverflowContent ( { children, style : styleProp , ...rest } ) {
179
- const { dispatch, tolerance } = useContext ( Context ) ;
180
- const rootRef = useRef ( ) ;
188
+ const { dispatch, tolerance, refs } = useOverflow ( ) ;
189
+ const { viewport : viewportRef } = refs ;
181
190
const contentRef = useRef ( ) ;
182
191
const toleranceRef = useRef ( ) ;
183
192
const watchRef = tolerance ? toleranceRef : contentRef ;
@@ -186,7 +195,7 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
186
195
useEffect ( ( ) => {
187
196
let ignore = false ;
188
197
189
- const root = rootRef . current ;
198
+ const root = viewportRef . current ;
190
199
191
200
const createObserver = ( direction , rootMargin ) => {
192
201
const threshold = 1e-12 ;
@@ -228,7 +237,7 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
228
237
observers . right . disconnect ( ) ;
229
238
observers . down . disconnect ( ) ;
230
239
} ;
231
- } , [ dispatch ] ) ;
240
+ } , [ dispatch , viewportRef ] ) ;
232
241
233
242
useEffect ( ( ) => {
234
243
const observers = observersRef . current ;
@@ -254,25 +263,31 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
254
263
} ;
255
264
} , [ styleProp ] ) ;
256
265
266
+ const toleranceElement = useMemo (
267
+ ( ) =>
268
+ tolerance ? (
269
+ < div
270
+ data-overflow-tolerance
271
+ ref = { toleranceRef }
272
+ style = { {
273
+ position : 'absolute' ,
274
+ top : tolerance ,
275
+ left : tolerance ,
276
+ right : tolerance ,
277
+ bottom : tolerance ,
278
+ background : 'transparent' ,
279
+ pointerEvents : 'none' ,
280
+ zIndex : - 1
281
+ } }
282
+ />
283
+ ) : null ,
284
+ [ tolerance ]
285
+ ) ;
286
+
257
287
return (
258
- < div ref = { rootRef } data-overflow-viewport = "" style = { viewportStyle } >
288
+ < div ref = { viewportRef } data-overflow-viewport = "" style = { viewportStyle } >
259
289
< div ref = { contentRef } data-overflow-content = "" style = { style } { ...rest } >
260
- { tolerance ? (
261
- < div
262
- data-overflow-tolerance
263
- ref = { toleranceRef }
264
- style = { {
265
- position : 'absolute' ,
266
- top : tolerance ,
267
- left : tolerance ,
268
- right : tolerance ,
269
- bottom : tolerance ,
270
- background : 'transparent' ,
271
- pointerEvents : 'none' ,
272
- zIndex : - 1
273
- } }
274
- />
275
- ) : null }
290
+ { toleranceElement }
276
291
{ children }
277
292
</ div >
278
293
</ div >
@@ -330,18 +345,18 @@ OverflowContent.propTypes = {
330
345
* ```
331
346
*/
332
347
function OverflowIndicator ( { children, direction } ) {
333
- const context = useContext ( Context ) ;
334
- const { canScroll : state } = context . state ;
335
- const canScroll = direction
336
- ? state [ direction ]
337
- : state . up || state . left || state . right || state . down ;
348
+ const { state , refs } = useOverflow ( ) ;
349
+ const { canScroll } = state ;
350
+ const isActive = direction
351
+ ? canScroll [ direction ]
352
+ : canScroll . up || canScroll . left || canScroll . right || canScroll . down ;
338
353
339
- let shouldRender = canScroll ;
354
+ let shouldRender = isActive ;
340
355
341
356
if ( typeof children === 'function' ) {
342
- const arg = direction ? canScroll : state ;
343
357
shouldRender = true ;
344
- children = children ( arg ) ;
358
+ const stateArg = direction ? isActive : canScroll ;
359
+ children = children ( stateArg , refs ) ;
345
360
}
346
361
347
362
return shouldRender ? < > { children } </ > : null ;
@@ -352,8 +367,9 @@ OverflowIndicator.displayName = 'Overflow.Indicator';
352
367
OverflowIndicator . propTypes = {
353
368
/**
354
369
* Indicator to render when scrolling is allowed in the requested direction.
355
- * If given a function, it will be passed the overflow state and its result
356
- * will be rendered.
370
+ * If given a function, it will be passed the overflow state and and object
371
+ * containing the `viewport` ref (you can use the `refs` parameter to render
372
+ * an indicator that is also a button that scrolls the viewport).
357
373
*/
358
374
children : PropTypes . oneOfType ( [ PropTypes . node , PropTypes . func ] ) ,
359
375
/**
0 commit comments