UNPKG

quasar

Version:

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

501 lines (435 loc) 12.7 kB
import Vue from 'vue' import { getRatio, getModel, SliderMixin, keyCodes } from '../slider/slider-utils.js' import { stopAndPrevent } from '../../utils/event.js' import { between } from '../../utils/format.js' const dragType = { MIN: 0, RANGE: 1, MAX: 2 } export default Vue.extend({ name: 'QRange', mixins: [ SliderMixin ], props: { value: { type: Object, default: () => ({ min: null, max: null }), validator (val) { return 'min' in val && 'max' in val } }, dragRange: Boolean, dragOnlyRange: Boolean, leftLabelColor: String, leftLabelTextColor: String, rightLabelColor: String, rightLabelTextColor: String, leftLabelValue: [String, Number], rightLabelValue: [String, Number] }, data () { return { model: { min: this.value.min === null ? this.min : this.value.min, max: this.value.max === null ? this.max : this.value.max }, curMinRatio: 0, curMaxRatio: 0 } }, watch: { 'value.min' (val) { this.model.min = val === null ? this.min : val }, 'value.max' (val) { this.model.max = val === null ? this.max : val }, min (value) { if (this.model.min < value) { this.model.min = value } if (this.model.max < value) { this.model.max = value } }, max (value) { if (this.model.min > value) { this.model.min = value } if (this.model.max > value) { this.model.max = value } } }, computed: { ratioMin () { return this.active === true ? this.curMinRatio : this.modelMinRatio }, ratioMax () { return this.active === true ? this.curMaxRatio : this.modelMaxRatio }, modelMinRatio () { return (this.model.min - this.min) / (this.max - this.min) }, modelMaxRatio () { return (this.model.max - this.min) / (this.max - this.min) }, trackStyle () { return { [this.horizProp]: 100 * this.ratioMin + '%', width: 100 * (this.ratioMax - this.ratioMin) + '%' } }, minThumbStyle () { return { [this.horizProp]: (100 * this.ratioMin) + '%', 'z-index': this.__nextFocus === 'min' ? 2 : void 0 } }, maxThumbStyle () { return { [this.horizProp]: (100 * this.ratioMax) + '%' } }, minThumbClass () { return this.preventFocus === false && this.focus === 'min' ? 'q-slider--focus' : null }, maxThumbClass () { return this.preventFocus === false && this.focus === 'max' ? 'q-slider--focus' : null }, events () { if (this.editable === true) { if (this.$q.platform.is.mobile === true) { return { click: this.__mobileClick } } const evt = { mousedown: this.__activate } this.dragOnlyRange === true && Object.assign(evt, { focus: () => { this.__focus('both') }, blur: this.__blur, keydown: this.__keydown, keyup: this.__keyup }) return evt } }, minEvents () { if (this.editable && !this.$q.platform.is.mobile && this.dragOnlyRange !== true) { return { focus: () => { this.__focus('min') }, blur: this.__blur, keydown: this.__keydown, keyup: this.__keyup } } }, maxEvents () { if (this.editable && !this.$q.platform.is.mobile && this.dragOnlyRange !== true) { return { focus: () => { this.__focus('max') }, blur: this.__blur, keydown: this.__keydown, keyup: this.__keyup } } }, minPinClass () { const color = this.leftLabelColor || this.labelColor if (color) { return `text-${color}` } }, minPinTextClass () { const color = this.leftLabelTextColor || this.labelTextColor if (color) { return `text-${color}` } }, maxPinClass () { const color = this.rightLabelColor || this.labelColor if (color) { return `text-${color}` } }, maxPinTextClass () { const color = this.rightLabelTextColor || this.labelTextColor if (color) { return `text-${color}` } }, minLabel () { return this.leftLabelValue !== void 0 ? this.leftLabelValue : this.model.min }, maxLabel () { return this.rightLabelValue !== void 0 ? this.rightLabelValue : this.model.max } }, methods: { __updateValue (change) { if (this.model.min !== this.value.min || this.model.max !== this.value.max) { this.$emit('input', this.model) } change === true && this.$emit('change', this.model) }, __getDragging (event) { const { left, width } = this.$el.getBoundingClientRect(), sensitivity = this.dragOnlyRange ? 0 : this.$refs.minThumb.offsetWidth / (2 * width), diff = this.max - this.min let dragging = { left, width, valueMin: this.model.min, valueMax: this.model.max, ratioMin: (this.model.min - this.min) / diff, ratioMax: (this.model.max - this.min) / diff } let ratio = getRatio(event, dragging, this.$q.lang.rtl), type if (this.dragOnlyRange !== true && ratio < dragging.ratioMin + sensitivity) { type = dragType.MIN } else if (this.dragOnlyRange === true || ratio < dragging.ratioMax - sensitivity) { if (this.dragRange || this.dragOnlyRange) { type = dragType.RANGE Object.assign(dragging, { offsetRatio: ratio, offsetModel: getModel(ratio, this.min, this.max, this.step, this.decimals), rangeValue: dragging.valueMax - dragging.valueMin, rangeRatio: dragging.ratioMax - dragging.ratioMin }) } else { type = dragging.ratioMax - ratio < ratio - dragging.ratioMin ? dragType.MAX : dragType.MIN } } else { type = dragType.MAX } dragging.type = type this.__nextFocus = void 0 return dragging }, __updatePosition (event, dragging = this.dragging) { let ratio = getRatio(event, dragging, this.$q.lang.rtl), model = getModel(ratio, this.min, this.max, this.step, this.decimals), pos switch (dragging.type) { case dragType.MIN: if (ratio <= dragging.ratioMax) { pos = { minR: ratio, maxR: dragging.ratioMax, min: model, max: dragging.valueMax } this.__nextFocus = 'min' } else { pos = { minR: dragging.ratioMax, maxR: ratio, min: dragging.valueMax, max: model } this.__nextFocus = 'max' } break case dragType.MAX: if (ratio >= dragging.ratioMin) { pos = { minR: dragging.ratioMin, maxR: ratio, min: dragging.valueMin, max: model } this.__nextFocus = 'max' } else { pos = { minR: ratio, maxR: dragging.ratioMin, min: model, max: dragging.valueMin } this.__nextFocus = 'min' } break case dragType.RANGE: let ratioDelta = ratio - dragging.offsetRatio, minR = between(dragging.ratioMin + ratioDelta, 0, 1 - dragging.rangeRatio), modelDelta = model - dragging.offsetModel, min = between(dragging.valueMin + modelDelta, this.min, this.max - dragging.rangeValue) pos = { minR, maxR: minR + dragging.rangeRatio, min: parseFloat(min.toFixed(this.decimals)), max: parseFloat((min + dragging.rangeValue).toFixed(this.decimals)) } break } this.model = { min: pos.min, max: pos.max } // If either of the values to be emitted are null, set them to the defaults the user has entered. if (this.model.min === null || this.model.max === null) { this.model.min = pos.min || this.min this.model.max = pos.max || this.max } if (this.snap !== true || this.step === 0) { this.curMinRatio = pos.minR this.curMaxRatio = pos.maxR } else { const diff = this.max - this.min this.curMinRatio = (this.model.min - this.min) / diff this.curMaxRatio = (this.model.max - this.min) / diff } }, __focus (which) { this.focus = which }, __keydown (evt) { if (!keyCodes.includes(evt.keyCode)) { return } stopAndPrevent(evt) const step = ([34, 33].includes(evt.keyCode) ? 10 : 1) * this.computedStep, offset = [34, 37, 40].includes(evt.keyCode) ? -step : step if (this.dragOnlyRange) { const interval = this.dragOnlyRange ? this.model.max - this.model.min : 0 const min = between( parseFloat((this.model.min + offset).toFixed(this.decimals)), this.min, this.max - interval ) this.model = { min, max: parseFloat((min + interval).toFixed(this.decimals)) } } else if (this.focus === false) { return } else { const which = this.focus this.model = { ...this.model, [which]: between( parseFloat((this.model[which] + offset).toFixed(this.decimals)), which === 'min' ? this.min : this.model.min, which === 'max' ? this.max : this.model.max ) } } this.__updateValue() }, __getThumb (h, which) { return h('div', { ref: which + 'Thumb', staticClass: 'q-slider__thumb-container absolute non-selectable', style: this[which + 'ThumbStyle'], class: this[which + 'ThumbClass'], on: this[which + 'Events'], attrs: { tabindex: this.dragOnlyRange !== true ? this.computedTabindex : null } }, [ h('svg', { staticClass: 'q-slider__thumb absolute', attrs: { width: '21', height: '21' } }, [ h('circle', { attrs: { cx: '10.5', cy: '10.5', r: '7.875' } }) ]), this.label === true || this.labelAlways === true ? h('div', { staticClass: 'q-slider__pin absolute flex flex-center', class: this[which + 'PinClass'] }, [ h('div', { staticClass: 'q-slider__pin-value-marker' }, [ h('div', { staticClass: 'q-slider__pin-value-marker-bg' }), h('div', { staticClass: 'q-slider__pin-value-marker-text', class: this[which + 'PinTextClass'] }, [ this[which + 'Label'] ]) ]) ]) : null, h('div', { staticClass: 'q-slider__focus-ring' }) ]) } }, render (h) { return h('div', { staticClass: this.value.min === null || this.value.max === null ? 'q-slider--no-value' : void 0, attrs: { role: 'slider', 'aria-valuemin': this.min, 'aria-valuemax': this.max, 'data-step': this.step, 'aria-disabled': this.disable, tabindex: this.dragOnlyRange && !this.$q.platform.is.mobile ? this.computedTabindex : null }, class: this.classes, on: this.events, directives: this.editable ? [{ name: 'touch-pan', value: this.__pan, modifiers: { horizontal: true, prevent: true, stop: true, mouse: true, mouseAllDir: true } }] : null }, [ h('div', { staticClass: 'q-slider__track-container absolute overflow-hidden' }, [ h('div', { staticClass: 'q-slider__track absolute-full', style: this.trackStyle }), this.markers === true ? h('div', { staticClass: 'q-slider__track-markers absolute-full fit', style: this.markerStyle }) : null ]), this.__getThumb(h, 'min'), this.__getThumb(h, 'max') ]) } })