vuetify
Version:
Vue Material Component Framework
220 lines (186 loc) • 6.5 kB
text/typescript
// Styles
import './VRangeSlider.sass'
// Components
import VSlider from '../VSlider'
// Helpers
import {
createRange,
deepEqual,
} from '../../util/helpers'
// Types
import { PropValidator } from 'vue/types/options'
/* @vue/component */
export default VSlider.extend({
name: 'v-range-slider',
props: {
value: {
type: Array,
default: () => ([0, 0]),
} as unknown as PropValidator<[number, number]>,
},
data () {
return {
activeThumb: null as null | number,
lazyValue: this.value,
}
},
computed: {
classes (): object {
return {
...VSlider.options.computed.classes.call(this),
'v-input--range-slider': true,
}
},
internalValue: {
get (): number[] {
return this.lazyValue
},
set (val: number[]) {
// Round value to ensure the
// entire slider range can
// be selected with step
let value = val.map((v = 0) => this.roundValue(Math.min(Math.max(v, this.minValue), this.maxValue)))
// Switch values if range and wrong order
if (value[0] > value[1] || value[1] < value[0]) {
if (this.activeThumb !== null) {
const toFocus = this.activeThumb === 1 ? 0 : 1
const el = this.$refs[`thumb_${toFocus}`] as HTMLElement
el.focus()
}
value = [value[1], value[0]]
}
this.lazyValue = value
if (!deepEqual(value, this.value)) this.$emit('input', value)
this.validate()
},
},
inputWidth (): number[] {
return this.internalValue.map((v: number) => (
this.roundValue(v) - this.minValue) / (this.maxValue - this.minValue) * 100
)
},
},
methods: {
getTrackStyle (startLength: number, endLength: number, startPadding = 0, endPadding = 0) {
const startDir = this.vertical ? this.$vuetify.rtl ? 'top' : 'bottom' : this.$vuetify.rtl ? 'right' : 'left'
const endDir = this.vertical ? 'height' : 'width'
const start = `calc(${startLength}% + ${startPadding}px)`
const end = `calc(${endLength}% + ${endPadding}px)`
return {
transition: this.trackTransition,
[startDir]: start,
[endDir]: end,
}
},
getIndexOfClosestValue (arr: number[], v: number) {
if (Math.abs(arr[0] - v) < Math.abs(arr[1] - v)) return 0
else return 1
},
genInput () {
return createRange(2).map(i => {
const input = VSlider.options.methods.genInput.call(this)
input.data = input.data || {}
input.data.attrs = input.data.attrs || {}
input.data.attrs.value = this.internalValue[i]
input.data.attrs.id = `input-${i ? 'max' : 'min'}-${this._uid}`
return input
})
},
genTrackContainer () {
const children = []
const padding = this.isDisabled ? 10 : 0
const sections: { class: string, color: string | undefined, styles: [number, number, number, number] }[] = [
{
class: 'v-slider__track-background',
color: this.computedTrackColor,
styles: [0, this.inputWidth[0], 0, -padding],
},
{
class: this.isDisabled ? 'v-slider__track-background' : 'v-slider__track-fill',
color: this.isDisabled ? this.computedTrackColor : this.computedColor,
styles: [this.inputWidth[0], Math.abs(this.inputWidth[1] - this.inputWidth[0]), padding, padding * -2],
},
{
class: 'v-slider__track-background',
color: this.computedTrackColor,
styles: [this.inputWidth[1], Math.abs(100 - this.inputWidth[1]), padding, -padding],
},
]
if (this.$vuetify.rtl) sections.reverse()
children.push(...sections.map(section => this.$createElement('div', this.setBackgroundColor(section.color, {
staticClass: section.class,
style: this.getTrackStyle(...section.styles),
}))))
return this.$createElement('div', {
staticClass: 'v-slider__track-container',
ref: 'track',
}, children)
},
genChildren () {
return [
this.genInput(),
this.genTrackContainer(),
this.genSteps(),
createRange(2).map(index => {
const value = this.internalValue[index]
const onDrag = (e: MouseEvent) => {
this.isActive = true
this.activeThumb = index
this.onThumbMouseDown(e)
}
const onFocus = (e: Event) => {
this.isFocused = true
this.activeThumb = index
this.$emit('focus', e)
}
const onBlur = (e: Event) => {
this.isFocused = false
this.activeThumb = null
this.$emit('blur', e)
}
const valueWidth = this.inputWidth[index]
const isActive = this.isActive && this.activeThumb === index
const isFocused = this.isFocused && this.activeThumb === index
return this.genThumbContainer(value, valueWidth, isActive, isFocused, onDrag, onFocus, onBlur, `thumb_${index}`)
}),
]
},
onSliderClick (e: MouseEvent) {
if (!this.isActive) {
if (this.noClick) {
this.noClick = false
return
}
const { value, isInsideTrack } = this.parseMouseMove(e)
if (isInsideTrack) {
this.activeThumb = this.getIndexOfClosestValue(this.internalValue, value)
const refName = `thumb_${this.activeThumb}`
const thumbRef = this.$refs[refName] as HTMLElement
thumbRef.focus()
}
this.setInternalValue(value)
this.$emit('change', this.internalValue)
}
},
onMouseMove (e: MouseEvent) {
const { value, isInsideTrack } = this.parseMouseMove(e)
if (isInsideTrack && this.activeThumb === null) {
this.activeThumb = this.getIndexOfClosestValue(this.internalValue, value)
}
this.setInternalValue(value)
},
onKeyDown (e: KeyboardEvent) {
if (this.activeThumb === null) return
const value = this.parseKeyDown(e, this.internalValue[this.activeThumb])
if (value == null) return
this.setInternalValue(value)
this.$emit('change', this.internalValue)
},
setInternalValue (value: number) {
this.internalValue = this.internalValue.map((v: number, i: number) => {
if (i === this.activeThumb) return value
else return Number(v)
})
},
},
})