UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

224 lines (185 loc) 5.1 kB
import Vue from 'vue' import QIcon from '../icon/QIcon.js' import QSpinner from '../spinner/QSpinner.js' import TouchPan from '../../directives/TouchPan.js' import ListenersMixin from '../../mixins/listeners.js' import { getScrollTarget, getScrollPosition } from '../../utils/scroll.js' import { between } from '../../utils/format.js' import { prevent } from '../../utils/event.js' import { slot } from '../../utils/slot.js' const PULLER_HEIGHT = 40, OFFSET_TOP = 20 export default Vue.extend({ name: 'QPullToRefresh', mixins: [ ListenersMixin ], directives: { TouchPan }, props: { color: String, bgColor: String, icon: String, noMouse: Boolean, disable: Boolean, scrollTarget: { default: void 0 } }, data () { return { state: 'pull', pullRatio: 0, pulling: false, pullPosition: -PULLER_HEIGHT, animating: false, positionCSS: {} } }, computed: { style () { return { opacity: this.pullRatio, transform: `translateY(${this.pullPosition}px) rotate(${this.pullRatio * 360}deg)` } }, classes () { return 'q-pull-to-refresh__puller row flex-center' + (this.animating === true ? ' q-pull-to-refresh__puller--animating' : '') + (this.bgColor !== void 0 ? ` bg-${this.bgColor}` : '') }, directives () { if (this.disable !== true) { const modifiers = { down: true, mightPrevent: true } if (this.noMouse !== true) { modifiers.mouse = true } return [{ name: 'touch-pan', modifiers, value: this.__pull }] } }, contentClass () { return `q-pull-to-refresh__content${this.pulling === true ? ' no-pointer-events' : ''}` } }, watch: { scrollTarget () { this.updateScrollTarget() } }, methods: { trigger () { this.$emit('refresh', () => { this.__animateTo({ pos: -PULLER_HEIGHT, ratio: 0 }, () => { this.state = 'pull' }) }) }, updateScrollTarget () { this.__scrollTarget = getScrollTarget(this.$el, this.scrollTarget) }, __pull (event) { if (event.isFinal === true) { if (this.pulling === true) { this.pulling = false if (this.state === 'pulled') { this.state = 'refreshing' this.__animateTo({ pos: OFFSET_TOP }) this.trigger() } else if (this.state === 'pull') { this.__animateTo({ pos: -PULLER_HEIGHT, ratio: 0 }) } } return } if (this.animating === true || this.state === 'refreshing') { return false } if (event.isFirst === true) { if (getScrollPosition(this.__scrollTarget) !== 0 || event.direction !== 'down') { if (this.pulling === true) { this.pulling = false this.state = 'pull' this.__animateTo({ pos: -PULLER_HEIGHT, ratio: 0 }) } return false } this.pulling = true const { top, left } = this.$el.getBoundingClientRect() this.positionCSS = { top: top + 'px', left: left + 'px', width: window.getComputedStyle(this.$el).getPropertyValue('width') } } prevent(event.evt) const distance = Math.min(140, Math.max(0, event.distance.y)) this.pullPosition = distance - PULLER_HEIGHT this.pullRatio = between(distance / (OFFSET_TOP + PULLER_HEIGHT), 0, 1) const state = this.pullPosition > OFFSET_TOP ? 'pulled' : 'pull' if (this.state !== state) { this.state = state } }, __animateTo ({ pos, ratio }, done) { this.animating = true this.pullPosition = pos if (ratio !== void 0) { this.pullRatio = ratio } clearTimeout(this.timer) this.timer = setTimeout(() => { this.animating = false done && done() }, 300) } }, mounted () { this.updateScrollTarget() }, beforeDestroy () { clearTimeout(this.timer) }, render (h) { return h('div', { staticClass: 'q-pull-to-refresh', on: { ...this.qListeners }, directives: this.directives }, [ h('div', { class: this.contentClass }, slot(this, 'default')), h('div', { staticClass: 'q-pull-to-refresh__puller-container fixed row flex-center no-pointer-events z-top', style: this.positionCSS }, [ h('div', { style: this.style, class: this.classes }, [ this.state !== 'refreshing' ? h(QIcon, { props: { name: this.icon || this.$q.iconSet.pullToRefresh.icon, color: this.color, size: '32px' } }) : h(QSpinner, { props: { size: '24px', color: this.color } }) ]) ]) ]) } })