quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
211 lines (170 loc) • 5.49 kB
JavaScript
import { h, ref, computed, watch, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'
import QIcon from '../icon/QIcon.js'
import QSpinner from '../spinner/QSpinner.js'
import TouchPan from '../../directives/touch-pan/TouchPan.js'
import { createComponent } from '../../utils/private.create/create.js'
import { getScrollTarget, getVerticalScrollPosition, scrollTargetProp } from '../../utils/scroll/scroll.js'
import { between } from '../../utils/format/format.js'
import { prevent } from '../../utils/event/event.js'
import { hSlot, hDir } from '../../utils/private.render/render.js'
const
PULLER_HEIGHT = 40,
OFFSET_TOP = 20
export default createComponent({
name: 'QPullToRefresh',
props: {
color: String,
bgColor: String,
icon: String,
noMouse: Boolean,
disable: Boolean,
scrollTarget: scrollTargetProp
},
emits: [ 'refresh' ],
setup (props, { slots, emit }) {
const { proxy } = getCurrentInstance()
const { $q } = proxy
const state = ref('pull')
const pullRatio = ref(0)
const pulling = ref(false)
const pullPosition = ref(-PULLER_HEIGHT)
const animating = ref(false)
const positionCSS = ref({})
const style = computed(() => ({
opacity: pullRatio.value,
transform: `translateY(${ pullPosition.value }px) rotate(${ pullRatio.value * 360 }deg)`
}))
const classes = computed(() =>
'q-pull-to-refresh__puller row flex-center'
+ (animating.value === true ? ' q-pull-to-refresh__puller--animating' : '')
+ (props.bgColor !== void 0 ? ` bg-${ props.bgColor }` : '')
)
function pull (event) {
if (event.isFinal === true) {
if (pulling.value === true) {
pulling.value = false
if (state.value === 'pulled') {
state.value = 'refreshing'
animateTo({ pos: OFFSET_TOP })
trigger()
}
else if (state.value === 'pull') {
animateTo({ pos: -PULLER_HEIGHT, ratio: 0 })
}
}
return
}
if (animating.value === true || state.value === 'refreshing') {
return false
}
if (event.isFirst === true) {
if (getVerticalScrollPosition(localScrollTarget) !== 0 || event.direction !== 'down') {
if (pulling.value === true) {
pulling.value = false
state.value = 'pull'
animateTo({ pos: -PULLER_HEIGHT, ratio: 0 })
}
return false
}
pulling.value = true
const { top, left } = $el.getBoundingClientRect()
positionCSS.value = {
top: top + 'px',
left: left + 'px',
width: window.getComputedStyle($el).getPropertyValue('width')
}
}
prevent(event.evt)
const distance = Math.min(140, Math.max(0, event.distance.y))
pullPosition.value = distance - PULLER_HEIGHT
pullRatio.value = between(distance / (OFFSET_TOP + PULLER_HEIGHT), 0, 1)
const newState = pullPosition.value > OFFSET_TOP ? 'pulled' : 'pull'
if (state.value !== newState) {
state.value = newState
}
}
const directives = computed(() => {
// if props.disable === false
const modifiers = { down: true }
if (props.noMouse !== true) {
modifiers.mouse = true
}
return [ [
TouchPan,
pull,
void 0,
modifiers
] ]
})
const contentClass = computed(() =>
`q-pull-to-refresh__content${ pulling.value === true ? ' no-pointer-events' : '' }`
)
function trigger () {
emit('refresh', () => {
animateTo({ pos: -PULLER_HEIGHT, ratio: 0 }, () => {
state.value = 'pull'
})
})
}
let $el, localScrollTarget, timer = null
function animateTo ({ pos, ratio }, done) {
animating.value = true
pullPosition.value = pos
if (ratio !== void 0) {
pullRatio.value = ratio
}
timer !== null && clearTimeout(timer)
timer = setTimeout(() => {
timer = null
animating.value = false
done && done()
}, 300)
}
function updateScrollTarget () {
localScrollTarget = getScrollTarget($el, props.scrollTarget)
}
watch(() => props.scrollTarget, updateScrollTarget)
onMounted(() => {
$el = proxy.$el
updateScrollTarget()
})
onBeforeUnmount(() => {
timer !== null && clearTimeout(timer)
})
// expose public methods
Object.assign(proxy, { trigger, updateScrollTarget })
return () => {
const child = [
h('div', { class: contentClass.value }, hSlot(slots.default)),
h('div', {
class: 'q-pull-to-refresh__puller-container fixed row flex-center no-pointer-events z-top',
style: positionCSS.value
}, [
h('div', {
class: classes.value,
style: style.value
}, [
state.value !== 'refreshing'
? h(QIcon, {
name: props.icon || $q.iconSet.pullToRefresh.icon,
color: props.color,
size: '32px'
})
: h(QSpinner, {
size: '24px',
color: props.color
})
])
])
]
return hDir(
'div',
{ class: 'q-pull-to-refresh' },
child,
'main',
props.disable === false,
() => directives.value
)
}
}
})