UNPKG

@render-props/scrollable

Version:

A state container which provides an interface for listening to the scroll event of its child component and providing valuable data about direction, distance, and more. It also provides convenience functions for scrollTo with optional animation.

228 lines (210 loc) 5.17 kB
import _extends from '@babel/runtime/helpers/esm/extends' import React from 'react' import PropTypes from 'prop-types' import Events from '@render-props/events' import Throttle from '@render-props/throttle' import {callIfExists} from '@render-props/utils' import {scrollTo, getDistance, getDirection} from './utils' /** import Scrollable from '@render-props/scrollable' const ScrollableBox = props => ( <Scrollable> { ({ scrollRef, scrollToX, scrollToY, scrollTo, scrollHeight, scrollWidth, scrollY, scrollX, clientHeight, clientWidth, max, direction, distance, }) => ( <div ref={scrollRef} style={{ width: 300, height: 300, overflow: 'auto', background: '#000' }} > <div style={{width: 600, height: 16000, position: 'relative'}}> <pre style={{ position: 'absolute', top: scrollY + 10, left: scrollX + 10 }}> {JSON.stringify({ scrollRef, scrollToX, scrollToY, scrollTo, scrollHeight, scrollWidth, scrollY, scrollX, clientHeight, clientWidth, max, direction, distance, }, null, 2)} </pre> </div> </div> ) } </Scrollable> ) */ const initialState = { scrollHeight: 0, scrollWidth: 0, scrollY: 0, scrollX: 0, clientHeight: 0, clientWidth: 0, max: { x: 0, y: 0, }, direction: { x: 0, y: 0, }, distance: { x: 0, y: 0, }, } export class Scrollable_ extends React.Component { constructor(props) { super(props) this.scrollable = null this.scrollRef = e => { if (e === null) { return } const scrollableChanged = this.scrollable !== e if (this.scrollable !== e) { if (this.scrollable !== null) { this.props.removeAllEvents(this.scrollable) } this.scrollable = e this.props.addEvent(e, 'scroll', this.onScroll) this.onScroll() } } this.onScroll = e => { this.props.throttleState(prevState => { const { scrollWidth, scrollHeight, scrollLeft, scrollTop, clientWidth, clientHeight, } = this.scrollable const nextScroll = { scrollX: scrollLeft, scrollY: scrollTop, } const direction = getDirection(prevState, nextScroll) const distance = getDistance(prevState, nextScroll) return { scrollWidth: scrollWidth, scrollHeight: scrollHeight, scrollX: scrollLeft, scrollY: scrollTop, clientWidth, clientHeight, max: { x: scrollWidth - clientWidth, y: scrollHeight - clientHeight, }, direction, distance, } }) } this.scrollTo = (posX, posY, opt) => { if (typeof opt !== 'object' && opt !== null) { if (posY !== void 0 && posX !== null) { this.scrollable.scrollTop = posY } if (posX !== void 0 && posY !== null) { this.scrollable.scrollLeft = posX } } else { scrollTo( this.scrollable, { x: this.props.state.scrollX, y: this.props.state.scrollY, }, { x: posX, y: posY, }, opt ) } } this.scrollToX = (posX, opt) => this.scrollTo(posX, void 0, opt) this.scrollToY = (posY, opt) => this.scrollTo(void 0, posY, opt) this.scrollableContext = { scrollRef: this.scrollRef, scrollToX: this.scrollToX, scrollToY: this.scrollToY, scrollTo: this.scrollTo, } } componentDidMount() { const {initialX, initialY} = this.props if (initialX || initialY) { this.scrollTo(initialX, initialY) } } componentDidUpdate({state}) { if ( state.scrollX !== this.props.state.scrollX || state.scrollY !== this.props.state.scrollY ) { callIfExists(this.props.onScroll, this.props.state) } } render() { return this.props.children( _extends(this.scrollableContext, this.props.state) ) } } Scrollable_.displayName = 'Scrollable' Scrollable_.propTypes = { children: PropTypes.func.isRequired, onScroll: PropTypes.func, initialX: PropTypes.number, initialY: PropTypes.number, } export default function Scrollable(props) { return React.createElement(Events, null, function(eventContext) { return React.createElement( Throttle, { initialState: initialState, }, function(throttleContext) { return React.createElement( Scrollable_, _extends({}, eventContext, throttleContext, props) ) } ) }) }