@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.
250 lines (220 loc) • 5.66 kB
JavaScript
'use strict'
var _interopRequireDefault = require('@babel/runtime/helpers/interopRequireDefault')
exports.__esModule = true
exports.default = Scrollable
exports.Scrollable_ = void 0
var _extends2 = _interopRequireDefault(
require('@babel/runtime/helpers/extends')
)
var _react = _interopRequireDefault(require('react'))
var _propTypes = _interopRequireDefault(require('prop-types'))
var _events = _interopRequireDefault(require('@render-props/events'))
var _throttle = _interopRequireDefault(require('@render-props/throttle'))
var _utils = require('@render-props/utils')
var _utils2 = require('./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,
},
}
class Scrollable_ extends _react.default.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 = (0, _utils2.getDirection)(prevState, nextScroll)
const distance = (0, _utils2.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 {
;(0, _utils2.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
) {
;(0, _utils.callIfExists)(this.props.onScroll, this.props.state)
}
}
render() {
return this.props.children(
(0, _extends2.default)(this.scrollableContext, this.props.state)
)
}
}
exports.Scrollable_ = Scrollable_
Scrollable_.displayName = 'Scrollable'
Scrollable_.propTypes = {
children: _propTypes.default.func.isRequired,
onScroll: _propTypes.default.func,
initialX: _propTypes.default.number,
initialY: _propTypes.default.number,
}
function Scrollable(props) {
return _react.default.createElement(_events.default, null, function(
eventContext
) {
return _react.default.createElement(
_throttle.default,
{
initialState: initialState,
},
function(throttleContext) {
return _react.default.createElement(
Scrollable_,
(0, _extends2.default)({}, eventContext, throttleContext, props)
)
}
)
})
}