quasar-framework
Version:
Build responsive SPA, SSR, PWA, Hybrid Mobile Apps and Electron apps, all simultaneously using the same codebase
186 lines (178 loc) • 4.66 kB
JavaScript
import { getScrollTarget, getScrollPosition } from '../../utils/scroll.js'
import QIcon from '../icon/QIcon.js'
import TouchPan from '../../directives/touch-pan.js'
const height = -65
export default {
name: 'QPullToRefresh',
directives: {
TouchPan
},
props: {
handler: {
type: Function,
required: true
},
color: {
type: String,
default: 'primary'
},
distance: {
type: Number,
default: 35
},
pullMessage: String,
releaseMessage: String,
refreshMessage: String,
refreshIcon: String,
inline: Boolean,
disable: Boolean
},
data () {
return {
state: 'pull',
pullPosition: height,
animating: false,
pulling: false,
scrolling: false
}
},
watch: {
inline (val) {
this.setScrollContainer(val)
}
},
computed: {
message () {
switch (this.state) {
case 'pulled':
return this.releaseMessage || this.$q.i18n.pullToRefresh.release
case 'refreshing':
return this.refreshMessage || this.$q.i18n.pullToRefresh.refresh
case 'pull':
default:
return this.pullMessage || this.$q.i18n.pullToRefresh.pull
}
},
style () {
return {
transform: `translateY(${this.pullPosition}px)`,
marginBottom: `${height}px`
}
},
messageClass () {
return `text-${this.color}`
}
},
methods: {
__pull (event) {
if (this.disable) {
return
}
if (event.isFinal) {
this.scrolling = false
this.pulling = false
if (this.state === 'pulled') {
this.state = 'refreshing'
this.__animateTo(0)
this.trigger()
}
else if (this.state === 'pull') {
this.__animateTo(height)
}
return
}
if (this.animating || this.scrolling || this.state === 'refreshing') {
return true
}
let top = getScrollPosition(this.scrollContainer)
if (top !== 0 || (top === 0 && event.direction !== 'down')) {
this.scrolling = true
if (this.pulling) {
this.pulling = false
this.state = 'pull'
this.__animateTo(height)
}
return true
}
event.evt.preventDefault()
this.pulling = true
this.pullPosition = height + Math.max(0, Math.pow(event.distance.y, 0.85))
this.state = this.pullPosition > this.distance ? 'pulled' : 'pull'
},
__animateTo (target, done, previousCall) {
if (!previousCall && this.animationId) {
cancelAnimationFrame(this.animating)
}
this.pullPosition -= (this.pullPosition - target) / 7
if (this.pullPosition - target > 1) {
this.animating = requestAnimationFrame(() => {
this.__animateTo(target, done, true)
})
}
else {
this.animating = requestAnimationFrame(() => {
this.pullPosition = target
this.animating = false
done && done()
})
}
},
trigger () {
this.handler(() => {
this.__animateTo(height, () => {
this.state = 'pull'
})
})
},
setScrollContainer (inline) {
this.$nextTick(() => {
this.scrollContainer = inline ? this.$el.parentNode : getScrollTarget(this.$el)
})
}
},
mounted () {
this.setScrollContainer(this.inline)
},
render (h) {
return h('div', { staticClass: 'pull-to-refresh overflow-hidden-y' }, [
h('div', {
staticClass: 'pull-to-refresh-container',
style: this.style,
directives: this.disable
? null
: [{
name: 'touch-pan',
modifiers: {
vertical: true,
mightPrevent: true
},
value: this.__pull
}]
}, [
h('div', {
staticClass: 'pull-to-refresh-message row flex-center',
'class': this.messageClass
}, [
h(QIcon, {
'class': { 'rotate-180': this.state === 'pulled' },
props: { name: this.$q.icon.pullToRefresh.arrow },
directives: [{
name: 'show',
value: this.state !== 'refreshing'
}]
}),
h(QIcon, {
staticClass: 'animate-spin',
props: { name: this.refreshIcon || this.$q.icon.pullToRefresh.refresh },
directives: [{
name: 'show',
value: this.state === 'refreshing'
}]
}),
` ${this.message}`
]),
this.$slots.default
])
])
}
}