UNPKG

quasar

Version:

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

327 lines (284 loc) 7.85 kB
import Vue from 'vue' import { between } from '../../utils/format.js' import { getMouseWheelDistance, prevent } from '../../utils/event.js' import { setScrollPosition, setHorizontalScrollPosition } from '../../utils/scroll.js' import slot from '../../utils/slot.js' import QResizeObserver from '../observer/QResizeObserver.js' import QScrollObserver from '../observer/QScrollObserver.js' import TouchPan from '../../directives/TouchPan.js' export default Vue.extend({ name: 'QScrollArea', directives: { TouchPan }, props: { thumbStyle: { type: Object, default: () => ({}) }, contentStyle: { type: Object, default: () => ({}) }, contentActiveStyle: { type: Object, default: () => ({}) }, delay: { type: [String, Number], default: 1000 }, horizontal: Boolean }, data () { return { active: false, hover: false, containerWidth: 0, containerHeight: 0, scrollPosition: 0, scrollSize: 0 } }, computed: { thumbHidden () { return this.scrollSize <= this.containerSize || (this.active === false && this.hover === false) }, thumbSize () { return Math.round( between( this.containerSize * this.containerSize / this.scrollSize, 50, this.containerSize ) ) }, style () { const pos = this.scrollPercentage * (this.containerSize - this.thumbSize) return Object.assign( {}, this.thumbStyle, this.horizontal === true ? { left: `${pos}px`, width: `${this.thumbSize}px` } : { top: `${pos}px`, height: `${this.thumbSize}px` } ) }, mainStyle () { return this.thumbHidden === true ? this.contentStyle : this.contentActiveStyle }, scrollPercentage () { const p = between(this.scrollPosition / (this.scrollSize - this.containerSize), 0, 1) return Math.round(p * 10000) / 10000 }, direction () { return this.horizontal === true ? 'right' : 'down' }, containerSize () { return this.horizontal === true ? this.containerWidth : this.containerHeight }, dirProps () { return this.horizontal === true ? { el: 'scrollLeft', wheel: 'x' } : { el: 'scrollTop', wheel: 'y' } }, thumbClass () { return `q-scrollarea__thumb--${this.horizontal === true ? 'h absolute-bottom' : 'v absolute-right'}` + (this.thumbHidden === true ? ' q-scrollarea__thumb--invisible' : '') } }, methods: { getScrollTarget () { return this.$refs.target }, getScrollPosition () { return this.$q.platform.is.desktop === true ? this.scrollPosition : this.$refs.target[this.dirProps.el] }, setScrollPosition (offset, duration) { const fn = this.horizontal === true ? setHorizontalScrollPosition : setScrollPosition fn(this.$refs.target, offset, duration) }, __updateContainer ({ height, width }) { if (this.containerWidth !== width) { this.containerWidth = width this.__setActive(true, true) } if (this.containerHeight !== height) { this.containerHeight = height this.__setActive(true, true) } }, __updateScroll ({ position }) { if (this.scrollPosition !== position) { this.scrollPosition = position this.__setActive(true, true) } }, __updateScrollSize ({ height, width }) { if (this.horizontal) { if (this.scrollSize !== width) { this.scrollSize = width this.__setActive(true, true) } } else { if (this.scrollSize !== height) { this.scrollSize = height this.__setActive(true, true) } } }, __panThumb (e) { if (e.isFirst === true) { this.refPos = this.scrollPosition this.__setActive(true, true) } if (e.isFinal === true) { this.__setActive(false) } const multiplier = (this.scrollSize - this.containerSize) / (this.containerSize - this.thumbSize) const distance = this.horizontal ? e.distance.x : e.distance.y const pos = this.refPos + (e.direction === this.direction ? 1 : -1) * distance * multiplier this.__setScroll(pos) }, __panContainer (e) { if (e.isFirst === true) { this.refPos = this.scrollPosition this.__setActive(true, true) } if (e.isFinal === true) { this.__setActive(false) } const distance = e.distance[this.horizontal === true ? 'x' : 'y'] const pos = this.refPos + (e.direction === this.direction ? -1 : 1) * distance this.__setScroll(pos) if (pos > 0 && pos + this.containerSize < this.scrollSize) { prevent(e.evt) } }, __mouseWheel (e) { const el = this.$refs.target el[this.dirProps.el] += getMouseWheelDistance(e)[this.dirProps.wheel] if ( el[this.dirProps.el] > 0 && el[this.dirProps.el] + this.containerSize < this.scrollSize ) { prevent(e) } }, __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) }, __setScroll (offset) { this.$refs.target[this.dirProps.el] = offset } }, render (h) { if (this.$q.platform.is.desktop !== true) { return h('div', { staticClass: 'q-scrollarea', style: this.contentStyle }, [ h('div', { ref: 'target', staticClass: 'scroll relative-position fit' }, slot(this, 'default')) ]) } return h('div', { staticClass: 'q-scrollarea', 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: !this.horizontal, horizontal: this.horizontal, mightPrevent: true }, value: this.__panContainer }] }, [ h('div', { staticClass: 'absolute', style: this.mainStyle, class: `full-${this.horizontal === true ? 'height' : 'width'}` }, [ h(QResizeObserver, { on: { resize: this.__updateScrollSize } }), slot(this, 'default') ]), h(QScrollObserver, { props: { horizontal: this.horizontal }, on: { scroll: this.__updateScroll } }) ]), h(QResizeObserver, { on: { resize: this.__updateContainer } }), h('div', { staticClass: 'q-scrollarea__thumb', style: this.style, class: this.thumbClass, directives: this.thumbHidden === true ? null : [{ name: 'touch-pan', modifiers: { vertical: !this.horizontal, horizontal: this.horizontal, prevent: true, mouse: true, mouseAllDir: true }, value: this.__panThumb }] }) ]) } })