diff --git a/README.md b/README.md
index 2bbaea6..31f3d8d 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,24 @@
# react-pullrefresh
-Pull to reflesh material design component.
+Pull to reflesh material design component.
react-native is supported.

-#### Demo
+## Demo
-[https://yusukeshibata.github.io/react-pullrefresh/](https://yusukeshibata.github.io/react-pullrefresh/)
+
+## Install
-#### Install
-
- ```sh
- npm install react-pullrefresh
- ```
+```sh
+npm install react-pullrefresh
+```
-#### Usage
+## Usage
- ```javascript
- import PullRefresh from 'react-pullrefresh'
+```javascript
+import PullRefresh from 'react-pullrefresh'
class App extends Component {
// onRefresh function canbe async/sync
@@ -44,22 +43,106 @@ react-native is supported.
export default App
```
+
+### HOC (High Order Component)
+
+```javascript
+import PullRefresh, { Indicator } from 'react-pullrefresh'
+
+const PortalIncidator = PortalHoc(Indicator)
+// control props namespace for List component.
+// add extra onRequestMore and pullFreshProps prop that not conflict with List Component.
+function PullRefreshHoc(AnotherComponent) {
+ return class _PullRefreshHoc extends React.Component {
+ handleRefresh = () => {
+ if (typeof this.props.onRequestMore === 'function') {
+ const reason = "pullRefresh"
+ return this.props.onRequestMore(reason)
+ }
+ }
+
+ render() {
+ // use pullFreshProps props namespace.
+ const { pullFreshProps, onRequestMore, ...otherProps } = this.props;
+ const defautWraperComponent = React.Fragment
+ const _pullFreshProps = Object.assign(
+ // change default wraperComponent to React.Fragment.
+ {
+ wraperComponent: defautWraperComponent
+ IndicatorComponent: PortalIncidator
+ },
+ pullFreshProps,
+ // pullFreshProps should never override AnotherComponent.
+ {
+ component: AnotherComponent,
+ onRefresh: this.handleRefresh
+ })
+ return (
+
+ )
+ }
+ }
+}
+
+
+// EnhancedList get extra two prop(onRequestMore, pullFreshProps)
+// for pull refresh feature.
+export const EnhancedList = PullRefreshHoc(List)
+
+// or more enhance
+const enhance = compose(FlipMoveHoc, InfiniteLoadHoc, ...OtherFeatureHocs)
+export const MulitiEnhancedList = enhance(EnhancedList)
+
+
+const handleRequestMore = async (reason) => {
+ if (reason === 'pullRefresh') {
+ await fetchData({page: 1})
+ } else if (reason === 'bottomInfiniteLoad') {
+ await fetchData({page: getNextPage()})
+ }
+}
+
+// List's prop disabled and component not conflict with pullRefresh props.
+const pullRefreshProps = {
+ color: "#ff0000",
+ disabled: false,
+ zIndex: 20
+}
+const list = (
+
+ {listItems}
+
+)
+```
+
#### Behaviour difference between v1/v2
TODO:
#### Props
-##### render
+##### render
TODO:
-
-##### color
+##### color
default: `#787878`
-##### bgColor
+##### bgColor
default: `#ffffff`
@@ -91,13 +174,13 @@ default: `undefined`
#### Removed props
-* size
-* offset
-* max
-* waitingComponent
-* pullingComponent
-* pulledComponent
-* supportDesktop
+- size
+- offset
+- max
+- waitingComponent
+- pullingComponent
+- pulledComponent
+- supportDesktop
#### License
diff --git a/src/component/index.js b/src/component/index.js
index 9b22c0a..65b48bd 100644
--- a/src/component/index.js
+++ b/src/component/index.js
@@ -30,7 +30,8 @@ const Component = styled.div`
border-radius: 20px;
width: 40px;
height: 40px;
- box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
+ 0 3px 1px -2px rgba(0, 0, 0, 0.2);
`
const RotatingSvg = styled.svg`
@@ -44,9 +45,8 @@ const DashedCircle = styled.circle`
animation: ${dashed} 1.4s ease-in-out infinite;
`
-export default (props, state) => {
- const { max, yRefreshing, y, phase } = state
- const { zIndex, color, bgColor } = props
+export default props => {
+ const { zIndex, color, bgColor, max, yRefreshing, y, phase } = props
const p = Math.atan(y / max)
const pMax = Math.atan(yRefreshing / max)
const r = Math.PI * 10 * 2
@@ -55,47 +55,47 @@ export default (props, state) => {
const refreshed = phase === 'refreshed'
return (
)
}
-
-
diff --git a/src/component/index.native.js b/src/component/index.native.js
index 650c9d2..871c153 100644
--- a/src/component/index.native.js
+++ b/src/component/index.native.js
@@ -1,7 +1,11 @@
import React, { Component } from 'react'
import styled from 'styled-components/native'
import { Easing, Animated, View } from 'react-native'
-import { Svg as NativeSvg, Circle as NativeCircle, Path as NativePath } from 'react-native-svg'
+import {
+ Svg as NativeSvg,
+ Circle as NativeCircle,
+ Path as NativePath
+} from 'react-native-svg'
class RotatingSvg extends Component {
constructor(props) {
@@ -12,14 +16,16 @@ class RotatingSvg extends Component {
}
}
componentDidMount() {
- this.state.r.addListener((r) => {
+ this.state.r.addListener(r => {
this.setState({ value: r.value })
})
- this._animated = Animated.loop(Animated.timing(this.state.r, {
- easing: Easing.linear,
- toValue: 270,
- duration: 1400,
- }))
+ this._animated = Animated.loop(
+ Animated.timing(this.state.r, {
+ easing: Easing.linear,
+ toValue: 270,
+ duration: 1400
+ })
+ )
this._animated.start()
}
componentWillUnmount() {
@@ -31,11 +37,8 @@ class RotatingSvg extends Component {
return (
@@ -48,7 +51,7 @@ class DashedCircle extends Component {
super(props)
this.state = {
rotate: new Animated.Value(0),
- dash: new Animated.Value(62),
+ dash: new Animated.Value(62)
}
}
componentDidMount() {
@@ -58,28 +61,30 @@ class DashedCircle extends Component {
this.state.dash.addListener(d => {
this.setState({ d: d.value })
})
- this._animated = Animated.loop(Animated.parallel([
- Animated.sequence([
- Animated.timing(this.state.rotate, {
- toValue: 135,
- duration: 700,
- }),
- Animated.timing(this.state.rotate, {
- toValue: 450,
- duration: 700
- }),
- ]),
- Animated.sequence([
- Animated.timing(this.state.dash, {
- toValue: 62/4,
- duration: 700
- }),
- Animated.timing(this.state.dash, {
- toValue: 62,
- duration: 700
- }),
+ this._animated = Animated.loop(
+ Animated.parallel([
+ Animated.sequence([
+ Animated.timing(this.state.rotate, {
+ toValue: 135,
+ duration: 700
+ }),
+ Animated.timing(this.state.rotate, {
+ toValue: 450,
+ duration: 700
+ })
+ ]),
+ Animated.sequence([
+ Animated.timing(this.state.dash, {
+ toValue: 62 / 4,
+ duration: 700
+ }),
+ Animated.timing(this.state.dash, {
+ toValue: 62,
+ duration: 700
+ })
+ ])
])
- ]))
+ )
this._animated.start()
}
componentWillUnmount() {
@@ -90,15 +95,12 @@ class DashedCircle extends Component {
const { strokeDasharray, style, ...props } = this.props
return (
)
@@ -117,9 +119,8 @@ const Container = styled(View)`
shadow-color: #000;
`
-export default (props, state, children) => {
- const { max, yRefreshing, y, phase } = state
- const { color, bgColor } = props
+export default (props, children) => {
+ const { color, bgColor, max, yRefreshing, y, phase } = props
const p = Math.atan(y / max)
const pMax = Math.atan(yRefreshing / max)
const r = Math.PI * 10 * 2
@@ -129,7 +130,7 @@ export default (props, state, children) => {
return [
children,
{
]
}
-
-
diff --git a/src/index.js b/src/index.js
index cf0c3c6..798d345 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,12 +1,13 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
+
import Spring from './spring'
-import renderDefault from './component'
+import Indicator from './component'
const MAX = 100
const sleep = msec => new Promise(resolve => setTimeout(resolve, msec))
-export default class PullRefresh extends Component {
+class PullRefresh extends Component {
constructor(props) {
super(props)
this.state = {
@@ -15,6 +16,14 @@ export default class PullRefresh extends Component {
max: MAX,
phase: ''
}
+ this.onScroll = this.onScroll.bind(this)
+ this.onMouseDown = this.onMouseDown.bind(this)
+ this.onMouseUp = this.onMouseUp.bind(this)
+ this.onMouseMove = this.onMouseMove.bind(this)
+ this.onTouchStart = this.onTouchStart.bind(this)
+ this.onTouchEnd = this.onTouchEnd.bind(this)
+ this.onTouchMove = this.onTouchMove.bind(this)
+ this.onSpringUpdate = this.onSpringUpdate.bind(this)
}
async refresh() {
this.setState({
@@ -26,7 +35,7 @@ export default class PullRefresh extends Component {
async _refresh() {
const { max, phase } = this.state
const { onRefresh } = this.props
- if(phase === 'willRefresh') {
+ if (phase === 'willRefresh') {
this._willRefresh = true
await this._spring.to(max)
this._spring.pause()
@@ -45,33 +54,104 @@ export default class PullRefresh extends Component {
this._spring.endValue = this._y
}
onScroll(evt) {
- this._scrollTop = evt.currentTarget.scrollTop !== undefined
- ? evt.currentTarget.scrollTop : evt.nativeEvent.contentOffset.y
+ if (this.props.onScroll) {
+ this.props.onScroll(evt)
+ }
+ if (!this.props.disabled) {
+ this._scrollTop =
+ evt.currentTarget.scrollTop !== undefined
+ ? evt.currentTarget.scrollTop
+ : evt.nativeEvent.contentOffset.y
+ }
+ }
+ onMouseDown(evt) {
+ if (this.props.onMouseDown) {
+ this.props.onMouseDown(evt)
+ }
+ if (!this.props.disabled && !this.props.disableMouse) {
+ this.onDown(evt)
+ }
+ }
+ onTouchStart(evt) {
+ if (this.props.onTouchStart) {
+ this.props.onTouchStart(evt)
+ }
+ if (!this.props.disabled && !this.props.disableTouch) {
+ this.onDown(evt)
+ }
}
onDown(evt) {
const { phase } = this.state
- if(this._willRefresh) return
- if(phase === 'refreshed' || phase === 'refreshing') return
+ if (this._willRefresh) return
+ if (phase === 'refreshed' || phase === 'refreshing') return
this._down = true
- const ey = evt.nativeEvent.touches ? evt.nativeEvent.touches[0].pageY : evt.pageY
+ const ey = evt.nativeEvent.touches
+ ? evt.nativeEvent.touches[0].pageY
+ : evt.pageY
+ const ex = evt.nativeEvent.touches
+ ? evt.nativeEvent.touches[0].pageX
+ : evt.pageX
this._py = ey
+ this._px = ex
+ }
+ async onMouseUp(evt) {
+ if (this.props.onMouseUp) {
+ this.props.onMouseUp(evt)
+ }
+ if (!this.props.disabled && !this.props.disableMouse) {
+ await this.onUp(evt)
+ }
+ }
+ async onTouchEnd(evt) {
+ if (this.props.onTouchEnd) {
+ this.props.onTouchEnd(evt)
+ }
+ if (!this.props.disabled && !this.props.disableTouch) {
+ await this.onUp(evt)
+ }
}
async onUp(evt) {
const { phase } = this.state
- if(phase === 'refreshed' || phase === 'refreshing') return
+ if (phase === 'refreshed' || phase === 'refreshing') return
this._down = false
await this._refresh()
}
+ onMouseMove(evt) {
+ if (this.props.onMouseMove) {
+ this.props.onMouseMove(evt)
+ }
+ if (!this.props.disabled && !this.props.disableMouse) {
+ this.onMove(evt)
+ }
+ }
+ onTouchMove(evt) {
+ if (this.props.onTouchMove) {
+ this.props.onTouchMove(evt)
+ }
+ if (!this.props.disabled && !this.props.disableTouch) {
+ this.onMove(evt)
+ }
+ }
onMove(evt) {
const { phase } = this.state
- if(this._willRefresh || !this._down) return
- if(phase === 'refreshed' || phase === 'refreshing') return
- const ey = evt.nativeEvent.touches ? evt.nativeEvent.touches[0].pageY : evt.pageY
- if(this._scrollTop <= 0) {
- this._y = this._y + ey - this._py
- this._spring.endValue = this._y
+ if (this._willRefresh || !this._down) return
+ if (phase === 'refreshed' || phase === 'refreshing') return
+ const ey = evt.nativeEvent.touches
+ ? evt.nativeEvent.touches[0].pageY
+ : evt.pageY
+ const ex = evt.nativeEvent.touches
+ ? evt.nativeEvent.touches[0].pageX
+ : evt.pageX
+ const vy = ey - this._py
+ const vx = ex - this._px
+ if (this._scrollTop <= 0 && Math.abs(vy) > Math.abs(vx)) {
+ if (vy >= 10 || vy < 0) {
+ this._y = this._y + vy
+ this._spring.endValue = this._y
+ }
}
this._py = ey
+ this._px = ex
}
onSpringUpdate(spring) {
const { max, yRefreshing, phase } = this.state
@@ -80,11 +160,11 @@ export default class PullRefresh extends Component {
y,
yRefreshing: this._willRefresh ? Math.max(y, yRefreshing) : y
})
- if(phase !== 'refreshed' && phase !== 'refreshing') {
- const newPhase = y >= max ? 'willRefresh' : ''
- if(phase !== newPhase) this.setState({ phase: newPhase })
+ if (phase !== 'refreshed' && phase !== 'refreshing') {
+ const newPhase = y >= max ? 'willRefresh' : ''
+ if (phase !== newPhase) this.setState({ phase: newPhase })
}
- if(phase === 'refreshed' && y === 0) {
+ if (phase === 'refreshed' && y === 0) {
this.setState({ phase: '' })
}
}
@@ -92,60 +172,173 @@ export default class PullRefresh extends Component {
this._y = 0
this._scrollTop = 0
this._spring = new Spring(60, 10)
- this._spring.onUpdate = ::this.onSpringUpdate
+ this._spring.onUpdate = this.onSpringUpdate
}
render() {
- const {
+ const { zIndex, color, bgColor } = this.props
+ const { max, yRefreshing, y, phase } = this.state
+ const indicatorProps = {
zIndex,
- render,
- bgColor,
color,
- onRefresh,
- disabled,
- as,
- children,
- ...props
- } = this.props
- const PullRefreshComponent = render
- const Container = as
- return (
-
-
- { children }
-
- { render(this.props, this.state) }
-
- )
+ bgColor,
+ max,
+ yRefreshing,
+ y,
+ phase
+ }
+ const { IndicatorComponent } = this.props
+ return
}
}
PullRefresh.propTypes = {
- as: PropTypes.oneOfType([ PropTypes.object, PropTypes.string ]),
+ wraperComponent: PropTypes.oneOfType([
+ PropTypes.func,
+ PropTypes.object,
+ PropTypes.string
+ ]),
+ component: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
onRefresh: PropTypes.func,
style: PropTypes.object,
disabled: PropTypes.bool,
+ disableMouse: PropTypes.bool,
+ disableTouch: PropTypes.bool,
color: PropTypes.string,
bgColor: PropTypes.string,
- render: PropTypes.func,
+ IndicatorComponent: PropTypes.func,
zIndex: PropTypes.number
}
PullRefresh.defaultProps = {
- as: 'div',
style: {},
disabled: false,
+ disableMouse: false,
+ disableTouch: false,
color: '#4285f4',
bgColor: '#fff',
- render: renderDefault,
+ IndicatorComponent: Indicator,
zIndex: undefined
}
+
+class PullRefreshConvertProps extends Component {
+ constructor(props) {
+ super(props)
+ this.setPullRefreshRef = this.setPullRefreshRef.bind(this)
+ this.onScroll = this.onScroll.bind(this)
+ this.onMouseDown = this.onMouseDown.bind(this)
+ this.onMouseUp = this.onMouseUp.bind(this)
+ this.onMouseMove = this.onMouseMove.bind(this)
+ this.onTouchStart = this.onTouchStart.bind(this)
+ this.onTouchEnd = this.onTouchEnd.bind(this)
+ this.onTouchMove = this.onTouchMove.bind(this)
+ }
+ setPullRefreshRef(pr) {
+ this.pr = pr
+ }
+ onScroll(e) {
+ this.pr.onScroll(e)
+ }
+ onMouseDown(e) {
+ this.pr.onMouseDown(e)
+ }
+ onMouseUp(e) {
+ this.pr.onMouseUp(e)
+ }
+ onMouseMove(e) {
+ this.pr.onMouseMove(e)
+ }
+ onTouchStart(e) {
+ this.pr.onTouchStart(e)
+ }
+ onTouchEnd(e) {
+ this.pr.onTouchEnd(e)
+ }
+ onTouchMove(e) {
+ this.pr.onTouchMove(e)
+ }
+ render() {
+ const {
+ onScroll,
+ onMouseDown,
+ onMouseUp,
+ onMouseMove,
+ onTouchStart,
+ onTouchEnd,
+ onTouchMove,
+ pullRefreshProps,
+ ...componentProps
+ } = this.props
+ const {
+ zIndex,
+ render,
+ bgColor,
+ color,
+ onRefresh,
+ disabled,
+ disableMouse,
+ disableTouch
+ } = this.props
+ const _pullRefreshProps =
+ pullRefreshProps !== null && typeof pullRefreshProps === 'object'
+ ? pullRefreshProps
+ : {
+ zIndex,
+ render,
+ bgColor,
+ color,
+ onRefresh,
+ disabled,
+ disableMouse,
+ disableTouch
+ }
+ const { wraperComponent, component } = _pullRefreshProps
+ const Component = component || 'div'
+ let Wraper
+ if (wraperComponent === null) {
+ Wraper = React.Fragment
+ } else if (!wraperComponent) {
+ Wraper = 'div'
+ } else {
+ Wraper = wraperComponent
+ }
+ return (
+
+
+
+
+ )
+ }
+}
+
+PullRefreshConvertProps.propTypes = {
+ pullRefreshProps: PropTypes.object
+}
+
+PullRefreshConvertProps.defaultProps = {
+ pullRefreshProps: null
+}
+
+export { PullRefreshConvertProps as default, Indicator, PullRefresh }
diff --git a/src/spring.js b/src/spring.js
index acd6984..b512847 100644
--- a/src/spring.js
+++ b/src/spring.js
@@ -2,7 +2,7 @@ import EventEmitter from 'event-emitter'
const sleep = msec => new Promise(resolve => setTimeout(resolve, msec))
const loop = async promise => {
- const proc = async () => await promise() && await proc()
+ const proc = async () => (await promise()) && (await proc())
await proc()
}
@@ -38,7 +38,7 @@ export default class Spring {
return this._value
}
setValue(value) {
- if(this._value !== value) {
+ if (this._value !== value) {
this._value = value
this._onUpdate(this)
}
@@ -49,23 +49,32 @@ export default class Spring {
async _wait(type) {
await new Promise(resolve => this._emitter.once(type, resolve))
}
- async loop() {
- if(this._loop) return
+ loop() {
+ if (this._loop) return
this._emit('start')
this._loop = true
- await loop(async () => {
- await sleep(1000 / 60)
- if(this._paused) return true
+ const _rafTimeout = (proc) => {
+ setTimeout(proc, 1000 / 60)
+ }
+
+ const raf = requestAnimationFrame ? requestAnimationFrame : _rafTimeout
+
+ const loop = () => {
+ if (this._paused) return true
// TODO: dummy -> use tention,friction
- const dv = (this._endValue - this._value) / 5
+ const dv = (this._endValue - this._value) / 2
this.setValue(this._value + dv)
- return Math.abs(dv) > 0.2
- })
- this.setValue(this._endValue)
+ if (Math.abs(dv) > 0.5) {
+ raf(loop)
+ } else {
+ this.setValue(this._endValue)
- this._loop = false
- this._emit('end')
+ this._loop = false
+ this._emit('end')
+ }
+ }
+ raf(loop)
}
}