quasar-framework
Version:
Build responsive SPA, SSR, PWA, Hybrid Mobile Apps and Electron apps, all simultaneously using the same codebase
224 lines (214 loc) • 5.96 kB
JavaScript
import { between } from '../../utils/format.js'
import { getMouseWheelDistance } from '../../utils/event.js'
import { setScrollPosition } from '../../utils/scroll.js'
import QResizeObservable from '../observables/QResizeObservable.js'
import QScrollObservable from '../observables/QScrollObservable.js'
import TouchPan from '../../directives/touch-pan.js'
export default {
name: 'QScrollArea',
directives: {
TouchPan
},
props: {
thumbStyle: {
type: Object,
default: () => ({})
},
contentStyle: {
type: Object,
default: () => ({})
},
contentActiveStyle: {
type: Object,
default: () => ({})
},
delay: {
type: Number,
default: 1000
}
},
data () {
return {
active: false,
hover: false,
containerHeight: 0,
scrollPosition: 0,
scrollHeight: 0
}
},
computed: {
thumbHidden () {
return this.scrollHeight <= this.containerHeight || (!this.active && !this.hover)
},
thumbHeight () {
return Math.round(between(this.containerHeight * this.containerHeight / this.scrollHeight, 50, this.containerHeight))
},
style () {
const top = this.scrollPercentage * (this.containerHeight - this.thumbHeight)
return Object.assign({}, this.thumbStyle, {
top: `${top}px`,
height: `${this.thumbHeight}px`
})
},
mainStyle () {
return this.thumbHidden ? this.contentStyle : this.contentActiveStyle
},
scrollPercentage () {
const p = between(this.scrollPosition / (this.scrollHeight - this.containerHeight), 0, 1)
return Math.round(p * 10000) / 10000
}
},
methods: {
setScrollPosition (offset, duration) {
setScrollPosition(this.$refs.target, offset, duration)
},
__updateContainer ({ height }) {
if (this.containerHeight !== height) {
this.containerHeight = height
this.__setActive(true, true)
}
},
__updateScroll ({ position }) {
if (this.scrollPosition !== position) {
this.scrollPosition = position
this.__setActive(true, true)
}
},
__updateScrollHeight ({ height }) {
if (this.scrollHeight !== height) {
this.scrollHeight = height
this.__setActive(true, true)
}
},
__panThumb (e) {
if (e.isFirst) {
this.refPos = this.scrollPosition
this.__setActive(true, true)
document.body.classList.add('non-selectable')
if (document.selection) {
document.selection.empty()
}
else if (window.getSelection) {
window.getSelection().removeAllRanges()
}
}
if (e.isFinal) {
this.__setActive(false)
document.body.classList.remove('non-selectable')
}
const multiplier = (this.scrollHeight - this.containerHeight) / (this.containerHeight - this.thumbHeight)
this.$refs.target.scrollTop = this.refPos + (e.direction === 'down' ? 1 : -1) * e.distance.y * multiplier
},
__panContainer (e) {
if (e.isFirst) {
this.refPos = this.scrollPosition
this.__setActive(true, true)
}
if (e.isFinal) {
this.__setActive(false)
}
const pos = this.refPos + (e.direction === 'down' ? -1 : 1) * e.distance.y
this.$refs.target.scrollTop = pos
if (pos > 0 && pos + this.containerHeight < this.scrollHeight) {
e.evt.preventDefault()
}
},
__mouseWheel (e) {
const el = this.$refs.target
el.scrollTop += getMouseWheelDistance(e).y
if (el.scrollTop > 0 && el.scrollTop + this.containerHeight < this.scrollHeight) {
e.preventDefault()
}
},
__setActive (active, timer) {
clearTimeout(this.timer)
if (active === this.active) {
if (active && this.timer) {
this.__startTimer()
}
return
}
if (active) {
this.active = true
if (timer) {
this.__startTimer()
}
}
else {
this.active = false
}
},
__startTimer () {
this.timer = setTimeout(() => {
this.active = false
this.timer = null
}, this.delay)
}
},
render (h) {
if (!this.$q.platform.is.desktop) {
return h('div', {
staticClass: 'q-scroll-area relative-position',
style: this.contentStyle
}, [
h('div', {
ref: 'target',
staticClass: 'scroll relative-position fit'
}, this.$slots.default)
])
}
return h('div', {
staticClass: 'q-scrollarea relative-position',
on: {
mouseenter: () => { this.hover = true },
mouseleave: () => { this.hover = false }
}
}, [
h('div', {
ref: 'target',
staticClass: 'scroll relative-position overflow-hidden fit',
on: {
wheel: this.__mouseWheel
},
directives: [{
name: 'touch-pan',
modifiers: {
vertical: true,
noMouse: true,
mightPrevent: true
},
value: this.__panContainer
}]
}, [
h('div', {
staticClass: 'absolute full-width',
style: this.mainStyle
}, [
h(QResizeObservable, {
on: { resize: this.__updateScrollHeight }
}),
this.$slots.default
]),
h(QScrollObservable, {
on: { scroll: this.__updateScroll }
})
]),
h(QResizeObservable, {
on: { resize: this.__updateContainer }
}),
h('div', {
staticClass: 'q-scrollarea-thumb absolute-right',
style: this.style,
'class': { 'invisible-thumb': this.thumbHidden },
directives: [{
name: 'touch-pan',
modifiers: {
vertical: true,
prevent: true
},
value: this.__panThumb
}]
})
])
}
}