quasar-framework
Version:
Simultaneously build desktop/mobile SPA websites & phone/tablet apps with VueJS
322 lines (307 loc) • 9.63 kB
JavaScript
import { between } from '../../utils/format'
import extend from '../../utils/extend'
import {
getModel,
getPercentage,
notDivides,
SliderMixin
} from '../slider/slider-utils'
import { QChip } from '../chip'
const dragType = {
MIN: 0,
RANGE: 1,
MAX: 2
}
export default {
name: 'q-range',
mixins: [SliderMixin],
props: {
value: {
type: Object,
default: () => ({
min: 0,
max: 0
}),
validator (value) {
return value.hasOwnProperty('min') && value.hasOwnProperty('max')
}
},
dragRange: Boolean,
dragOnlyRange: Boolean,
leftLabelColor: String,
leftLabelValue: String,
rightLabelColor: String,
rightLabelValue: String
},
data () {
return {
model: extend({}, this.value),
dragging: false,
currentMinPercentage: (this.value.min - this.min) / (this.max - this.min),
currentMaxPercentage: (this.value.max - this.min) / (this.max - this.min)
}
},
computed: {
percentageMin () {
return this.snap ? (this.model.min - this.min) / (this.max - this.min) : this.currentMinPercentage
},
percentageMax () {
return this.snap ? (this.model.max - this.min) / (this.max - this.min) : this.currentMaxPercentage
},
activeTrackWidth () {
return 100 * (this.percentageMax - this.percentageMin) + '%'
},
leftDisplayValue () {
return this.leftLabelValue !== void 0
? this.leftLabelValue
: this.model.min
},
rightDisplayValue () {
return this.rightLabelValue !== void 0
? this.rightLabelValue
: this.model.max
},
leftTooltipColor () {
return this.leftLabelColor || this.labelColor
},
rightTooltipColor () {
return this.rightLabelColor || this.labelColor
}
},
watch: {
'value.min' (value) {
this.model.min = value
},
'value.max' (value) {
this.model.max = value
},
'model.min' (value) {
if (this.dragging) {
return
}
if (value > this.model.max) {
value = this.model.max
}
this.currentMinPercentage = (value - this.min) / (this.max - this.min)
},
'model.max' (value) {
if (this.dragging) {
return
}
if (value < this.model.min) {
value = this.model.min
}
this.currentMaxPercentage = (value - this.min) / (this.max - this.min)
},
min (value) {
if (this.model.min < value) {
this.__update({min: value})
}
if (this.model.max < value) {
this.__update({max: value})
}
this.$nextTick(this.__validateProps)
},
max (value) {
if (this.model.min > value) {
this.__update({min: value})
}
if (this.model.max > value) {
this.__update({max: value})
}
this.$nextTick(this.__validateProps)
},
step () {
this.$nextTick(this.__validateProps)
}
},
methods: {
__setActive (event) {
let
container = this.$refs.handle,
width = container.offsetWidth,
sensitivity = (this.dragOnlyRange ? -1 : 1) * this.$refs.handleMin.offsetWidth / (2 * width)
this.dragging = {
left: container.getBoundingClientRect().left,
width,
valueMin: this.model.min,
valueMax: this.model.max,
percentageMin: this.currentMinPercentage,
percentageMax: this.currentMaxPercentage
}
let
percentage = getPercentage(event, this.dragging),
type
if (percentage < this.currentMinPercentage + sensitivity) {
type = dragType.MIN
}
else if (percentage < this.currentMaxPercentage - sensitivity) {
if (this.dragRange || this.dragOnlyRange) {
type = dragType.RANGE
extend(this.dragging, {
offsetPercentage: percentage,
offsetModel: getModel(percentage, this.min, this.max, this.step, this.computedDecimals),
rangeValue: this.dragging.valueMax - this.dragging.valueMin,
rangePercentage: this.currentMaxPercentage - this.currentMinPercentage
})
}
else {
type = this.currentMaxPercentage - percentage < percentage - this.currentMinPercentage
? dragType.MAX
: dragType.MIN
}
}
else {
type = dragType.MAX
}
if (this.dragOnlyRange && type !== dragType.RANGE) {
this.dragging = false
return
}
this.dragging.type = type
this.__update(event)
},
__update (event) {
let
percentage = getPercentage(event, this.dragging),
model = getModel(percentage, this.min, this.max, this.step, this.computedDecimals),
pos
switch (this.dragging.type) {
case dragType.MIN:
if (percentage <= this.dragging.percentageMax) {
pos = {
minP: percentage,
maxP: this.dragging.percentageMax,
min: model,
max: this.dragging.valueMax
}
}
else {
pos = {
minP: this.dragging.percentageMax,
maxP: percentage,
min: this.dragging.valueMax,
max: model
}
}
break
case dragType.MAX:
if (percentage >= this.dragging.percentageMin) {
pos = {
minP: this.dragging.percentageMin,
maxP: percentage,
min: this.dragging.valueMin,
max: model
}
}
else {
pos = {
minP: percentage,
maxP: this.dragging.percentageMin,
min: model,
max: this.dragging.valueMin
}
}
break
case dragType.RANGE:
let
percentageDelta = percentage - this.dragging.offsetPercentage,
minP = between(this.dragging.percentageMin + percentageDelta, 0, 1 - this.dragging.rangePercentage),
modelDelta = model - this.dragging.offsetModel,
min = between(this.dragging.valueMin + modelDelta, this.min, this.max - this.dragging.rangeValue)
pos = {
minP,
maxP: minP + this.dragging.rangePercentage,
min: parseFloat(min.toFixed(this.computedDecimals)),
max: parseFloat((min + this.dragging.rangeValue).toFixed(this.computedDecimals))
}
break
}
this.currentMinPercentage = pos.minP
this.currentMaxPercentage = pos.maxP
this.__updateInput(pos)
},
__end () {
this.dragging = false
this.currentMinPercentage = (this.model.min - this.min) / (this.max - this.min)
this.currentMaxPercentage = (this.model.max - this.min) / (this.max - this.min)
this.$nextTick(() => {
if (JSON.stringify(this.model) !== JSON.stringify(this.value)) {
this.$emit('change', this.model)
}
this.$emit('dragend', this.model)
})
},
__updateInput ({min = this.model.min, max = this.model.max}) {
const model = {min, max}
this.model = model
this.$emit('input', model)
},
__validateProps () {
if (this.min >= this.max) {
console.error('Range error: min >= max', this.$el, this.min, this.max)
}
else if (notDivides((this.max - this.min) / this.step, this.computedDecimals)) {
console.error('Range error: step must be a divisor of max - min', this.min, this.max, this.step)
}
else if (notDivides((this.model.min - this.min) / this.step, this.computedDecimals)) {
console.error('Range error: step must be a divisor of initial value.min - min', this.model.min, this.min, this.step)
}
else if (notDivides((this.model.max - this.min) / this.step, this.computedDecimals)) {
console.error('Range error: step must be a divisor of initial value.max - min', this.model.max, this.max, this.step)
}
},
__getHandle (h, lower, upper, edge, percentage, color, label) {
return h('div', {
ref: `handle${upper}`,
staticClass: `q-slider-handle q-slider-handle-${lower}`,
style: {
left: `${percentage * 100}%`,
borderRadius: this.square ? '0' : '50%'
},
'class': [
edge ? 'handle-at-minimum' : null,
{ dragging: this.dragging }
]
}, [
this.label || this.labelAlways
? h(QChip, {
props: {
pointing: 'down',
square: true,
color
},
staticClass: 'q-slider-label no-pointer-events',
'class': { 'label-always': this.labelAlways }
}, [ label ])
: null,
__THEME__ !== 'ios'
? h('div', { staticClass: 'q-slider-ring' })
: null
])
},
__getContent (h) {
return [
h('div', {
staticClass: 'q-slider-track active-track',
style: {
left: `${this.percentageMin * 100}%`,
width: this.activeTrackWidth
},
'class': {
dragging: this.dragging,
'track-draggable': this.dragRange || this.dragOnlyRange
}
}),
this.__getHandle(
h, 'min', 'Min', !this.fillHandleAlways && this.model.min === this.min, this.percentageMin,
this.leftTooltipColor, this.leftDisplayValue
),
this.__getHandle(
h, 'max', 'Max', false, this.percentageMax,
this.rightTooltipColor, this.rightDisplayValue
)
]
}
}
}