@@ -9,6 +9,10 @@ import PropTypes from 'prop-types';
99
1010const Context = React . createContext ( ) ;
1111
12+ export function useOverflow ( ) {
13+ return useContext ( Context ) ;
14+ }
15+
1216const containerStyle = {
1317 display : 'flex' ,
1418 flexDirection : 'column' ,
@@ -60,8 +64,6 @@ function getInitialState() {
6064 } ;
6165}
6266
63- const emptyStyle = { } ;
64-
6567/**
6668 * The overflow state provider. At a minimum it must contain an
6769 * `<Overflow.Content>` element, otherwise it will do nothing.
@@ -99,12 +101,13 @@ const emptyStyle = {};
99101export default function Overflow ( {
100102 children,
101103 onStateChange,
104+ style : styleProp ,
102105 tolerance = 0 ,
103106 ...rest
104107} ) {
105108 const [ state , dispatch ] = useReducer ( reducer , null , getInitialState ) ;
106109 const hidden = rest . hidden ;
107- const styleProp = rest . style || emptyStyle ;
110+ const viewportRef = useRef ( ) ;
108111
109112 const style = useMemo (
110113 ( ) => ( {
@@ -116,27 +119,33 @@ export default function Overflow({
116119 // `display: none` and allow that, otherwise ensure we use the value from
117120 // `containerStyle`.
118121 display :
119- hidden || styleProp . display === 'none' ? 'none' : containerStyle . display
122+ hidden || ( styleProp && styleProp . display === 'none' )
123+ ? 'none'
124+ : containerStyle . display
120125 } ) ,
121126 [ hidden , styleProp ]
122127 ) ;
123128
124- const context = useMemo ( ( ) => {
125- return {
129+ const refs = useMemo ( ( ) => ( { viewport : viewportRef } ) , [ ] ) ;
130+
131+ const context = useMemo (
132+ ( ) => ( {
126133 state,
127134 dispatch,
128- tolerance
129- } ;
130- } , [ state , tolerance ] ) ;
135+ tolerance,
136+ refs
137+ } ) ,
138+ [ refs , state , tolerance ]
139+ ) ;
131140
132141 useEffect ( ( ) => {
133142 if ( onStateChange ) {
134- onStateChange ( state ) ;
143+ onStateChange ( state , refs ) ;
135144 }
136- } , [ onStateChange , state ] ) ;
145+ } , [ onStateChange , refs , state ] ) ;
137146
138147 return (
139- < div data-overflow-wrapper = "" { ...rest } style = { style } >
148+ < div data-overflow-wrapper = "" style = { style } { ...rest } >
140149 < Context . Provider value = { context } > { children } </ Context . Provider >
141150 </ div >
142151 ) ;
@@ -150,8 +159,8 @@ Overflow.propTypes = {
150159 */
151160 children : PropTypes . node ,
152161 /**
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.
155164 */
156165 onStateChange : PropTypes . func ,
157166 /**
@@ -176,8 +185,8 @@ Overflow.propTypes = {
176185 * interfering with the styles this component needs to function.
177186 */
178187function 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 ;
181190 const contentRef = useRef ( ) ;
182191 const toleranceRef = useRef ( ) ;
183192 const watchRef = tolerance ? toleranceRef : contentRef ;
@@ -186,7 +195,7 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
186195 useEffect ( ( ) => {
187196 let ignore = false ;
188197
189- const root = rootRef . current ;
198+ const root = viewportRef . current ;
190199
191200 const createObserver = ( direction , rootMargin ) => {
192201 const threshold = 1e-12 ;
@@ -228,7 +237,7 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
228237 observers . right . disconnect ( ) ;
229238 observers . down . disconnect ( ) ;
230239 } ;
231- } , [ dispatch ] ) ;
240+ } , [ dispatch , viewportRef ] ) ;
232241
233242 useEffect ( ( ) => {
234243 const observers = observersRef . current ;
@@ -254,25 +263,31 @@ function OverflowContent({ children, style: styleProp, ...rest }) {
254263 } ;
255264 } , [ styleProp ] ) ;
256265
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+
257287 return (
258- < div ref = { rootRef } data-overflow-viewport = "" style = { viewportStyle } >
288+ < div ref = { viewportRef } data-overflow-viewport = "" style = { viewportStyle } >
259289 < 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 }
276291 { children }
277292 </ div >
278293 </ div >
@@ -330,18 +345,18 @@ OverflowContent.propTypes = {
330345 * ```
331346 */
332347function 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 ;
338353
339- let shouldRender = canScroll ;
354+ let shouldRender = isActive ;
340355
341356 if ( typeof children === 'function' ) {
342- const arg = direction ? canScroll : state ;
343357 shouldRender = true ;
344- children = children ( arg ) ;
358+ const stateArg = direction ? isActive : canScroll ;
359+ children = children ( stateArg , refs ) ;
345360 }
346361
347362 return shouldRender ? < > { children } </ > : null ;
@@ -352,8 +367,9 @@ OverflowIndicator.displayName = 'Overflow.Indicator';
352367OverflowIndicator . propTypes = {
353368 /**
354369 * 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).
357373 */
358374 children : PropTypes . oneOfType ( [ PropTypes . node , PropTypes . func ] ) ,
359375 /**
0 commit comments