quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
301 lines (249 loc) • 7.23 kB
JavaScript
import { between } from '../../utils/format.js'
import { position } from '../../utils/event.js'
import FormMixin from '../../mixins/form.js'
import DarkMixin from '../../mixins/dark.js'
import TouchPan from '../../directives/TouchPan.js'
// PGDOWN, LEFT, DOWN, PGUP, RIGHT, UP
export const keyCodes = [ 34, 37, 40, 33, 39, 38 ]
export function getRatio (evt, dragging, reverse, vertical) {
const
pos = position(evt),
val = vertical === true
? between((pos.top - dragging.top) / dragging.height, 0, 1)
: between((pos.left - dragging.left) / dragging.width, 0, 1)
return reverse === true ? 1.0 - val : val
}
export function getModel (ratio, min, max, step, decimals) {
let model = min + ratio * (max - min)
if (step > 0) {
const modulo = (model - min) % step
model += (Math.abs(modulo) >= step / 2 ? (modulo < 0 ? -1 : 1) * step : 0) - modulo
}
if (decimals > 0) {
model = parseFloat(model.toFixed(decimals))
}
return between(model, min, max)
}
export const SliderMixin = {
mixins: [ DarkMixin, FormMixin ],
directives: {
TouchPan
},
props: {
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1,
validator: v => v >= 0
},
color: String,
labelColor: String,
labelTextColor: String,
dense: Boolean,
label: Boolean,
labelAlways: Boolean,
markers: Boolean,
snap: Boolean,
vertical: Boolean,
reverse: Boolean,
disable: Boolean,
readonly: Boolean,
tabindex: [ String, Number ],
thumbPath: {
type: String,
default: 'M 4, 10 a 6,6 0 1,0 12,0 a 6,6 0 1,0 -12,0'
}
},
data () {
return {
active: false,
preventFocus: false,
focus: false
}
},
computed: {
axis () {
return this.vertical === true ? '--v' : '--h'
},
classes () {
return `q-slider q-slider${this.axis} q-slider--${this.active === true ? '' : 'in'}active` +
(this.isReversed === true ? ' q-slider--reversed' : '') +
(this.color !== void 0 ? ` text-${this.color}` : '') +
(this.disable === true ? ' disabled' : ' q-slider--enabled' + (this.editable === true ? ' q-slider--editable' : '')) +
(this.focus === 'both' ? ' q-slider--focus' : '') +
(this.label || this.labelAlways === true ? ' q-slider--label' : '') +
(this.labelAlways === true ? ' q-slider--label-always' : '') +
(this.isDark === true ? ' q-slider--dark' : '') +
(this.dense === true ? ' q-slider--dense q-slider--dense' + this.axis : '')
},
editable () {
return this.disable !== true && this.readonly !== true && this.min < this.max
},
decimals () {
return (String(this.step).trim('0').split('.')[1] || '').length
},
computedStep () {
return this.step === 0 ? 1 : this.step
},
minMaxDiff () {
return this.max - this.min
},
markerStyle () {
if (this.minMaxDiff !== 0) {
const size = 100 * this.computedStep / this.minMaxDiff
return {
backgroundSize: this.vertical === true
? '2px ' + size + '%'
: size + '% 2px'
}
}
},
computedTabindex () {
return this.editable === true ? this.tabindex || 0 : -1
},
isReversed () {
return this.vertical === true
? this.reverse === true
: this.reverse !== (this.$q.lang.rtl === true)
},
positionProp () {
if (this.vertical === true) {
return this.isReversed === true ? 'bottom' : 'top'
}
return this.isReversed === true ? 'right' : 'left'
},
sizeProp () {
return this.vertical === true ? 'height' : 'width'
},
orientation () {
return this.vertical === true ? 'vertical' : 'horizontal'
},
attrs () {
const attrs = {
role: 'slider',
'aria-valuemin': this.min,
'aria-valuemax': this.max,
'aria-orientation': this.orientation,
'data-step': this.step
}
if (this.disable === true) {
attrs['aria-disabled'] = 'true'
}
else if (this.readonly === true) {
attrs['aria-readonly'] = 'true'
}
return attrs
},
panDirectives () {
return this.editable === true
? [{
name: 'touch-pan',
value: this.__pan,
modifiers: {
[ this.orientation ]: true,
prevent: true,
stop: true,
mouse: true,
mouseAllDir: true
}
}]
: null
}
},
methods: {
__getThumbSvg (h) {
return h('svg', {
staticClass: 'q-slider__thumb absolute',
attrs: {
focusable: 'false', /* needed for IE11 */
viewBox: '0 0 20 20',
width: '20',
height: '20',
'aria-hidden': 'true'
}
}, [
h('path', {
attrs: {
d: this.thumbPath
}
})
])
},
__getPinStyle (percent, ratio) {
if (this.vertical === true) {
return {}
}
const offset = `${Math.ceil(20 * Math.abs(0.5 - ratio))}px`
return {
pin: {
transformOrigin: `${this.$q.lang.rtl === true ? offset : (this.$q.platform.is.ie === true ? '100%' : `calc(100% - ${offset})`)} 50%`
},
pinTextContainer: {
[this.$q.lang.rtl === true ? 'left' : 'right']: `${percent * 100}%`,
transform: `translateX(${Math.ceil((this.$q.lang.rtl === true ? -1 : 1) * 20 * percent)}px)`
}
}
},
__pan (event) {
if (event.isFinal) {
if (this.dragging !== void 0) {
this.__updatePosition(event.evt)
// only if touch, because we also have mousedown/up:
event.touch === true && this.__updateValue(true)
this.dragging = void 0
this.$emit('pan', 'end')
}
this.active = false
}
else if (event.isFirst) {
this.dragging = this.__getDragging(event.evt)
this.__updatePosition(event.evt)
this.__updateValue()
this.active = true
this.$emit('pan', 'start')
}
else {
this.__updatePosition(event.evt)
this.__updateValue()
}
},
__blur () {
this.focus = false
},
__activate (evt) {
this.__updatePosition(evt, this.__getDragging(evt))
this.__updateValue()
this.preventFocus = true
this.active = true
document.addEventListener('mouseup', this.__deactivate, true)
},
__deactivate () {
this.preventFocus = false
if (this.dragging === void 0) {
this.active = false
}
this.__updateValue(true)
this.__blur()
document.removeEventListener('mouseup', this.__deactivate, true)
},
__mobileClick (evt) {
this.__updatePosition(evt, this.__getDragging(evt))
this.__updateValue(true)
},
__keyup (evt) {
if (keyCodes.includes(evt.keyCode)) {
this.__updateValue(true)
}
}
},
beforeDestroy () {
document.removeEventListener('mouseup', this.__deactivate, true)
}
}