UNPKG

quasar

Version:

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

394 lines (340 loc) 11.8 kB
import { h, ref, computed, watch, getCurrentInstance } from 'vue' import useSlider, { useSliderProps, useSliderEmits, keyCodes } from '../slider/use-slider.js' import { createComponent } from '../../utils/private/create.js' import { stopAndPrevent } from '../../utils/event.js' import { between } from '../../utils/format.js' const dragType = { MIN: 0, RANGE: 1, MAX: 2 } export default createComponent({ name: 'QRange', props: { ...useSliderProps, modelValue: { type: Object, default: () => ({ min: null, max: null }), validator: v => 'min' in v && 'max' in v }, dragRange: Boolean, dragOnlyRange: Boolean, leftLabelColor: String, leftLabelTextColor: String, rightLabelColor: String, rightLabelTextColor: String, leftLabelValue: [ String, Number ], rightLabelValue: [ String, Number ], leftThumbColor: String, rightThumbColor: String }, emits: useSliderEmits, setup (props, { emit }) { const { proxy: { $q } } = getCurrentInstance() const { state, methods } = useSlider({ updateValue, updatePosition, getDragging, formAttrs: computed(() => ({ type: 'hidden', name: props.name, value: `${ props.modelValue.min }|${ props.modelValue.max }` })) }) const rootRef = ref(null) const curMinRatio = ref(0) const curMaxRatio = ref(0) const model = ref({ min: 0, max: 0 }) function normalizeModel () { model.value.min = props.modelValue.min === null ? state.innerMin.value : between(props.modelValue.min, state.innerMin.value, state.innerMax.value) model.value.max = props.modelValue.max === null ? state.innerMax.value : between(props.modelValue.max, state.innerMin.value, state.innerMax.value) } watch( () => `${ props.modelValue.min }|${ props.modelValue.max }|${ state.innerMin.value }|${ state.innerMax.value }`, normalizeModel ) normalizeModel() const modelMinRatio = computed(() => methods.convertModelToRatio(model.value.min)) const modelMaxRatio = computed(() => methods.convertModelToRatio(model.value.max)) const ratioMin = computed(() => ( state.active.value === true ? curMinRatio.value : modelMinRatio.value )) const ratioMax = computed(() => ( state.active.value === true ? curMaxRatio.value : modelMaxRatio.value )) const selectionBarStyle = computed(() => { const acc = { [ state.positionProp.value ]: `${ 100 * ratioMin.value }%`, [ state.sizeProp.value ]: `${ 100 * (ratioMax.value - ratioMin.value) }%` } if (props.selectionImg !== void 0) { acc.backgroundImage = `url(${ props.selectionImg }) !important` } return acc }) const trackContainerEvents = computed(() => { if (state.editable.value !== true) { return {} } if ($q.platform.is.mobile === true) { return { onClick: methods.onMobileClick } } const evt = { onMousedown: methods.onActivate } if (props.dragRange === true || props.dragOnlyRange === true) { Object.assign(evt, { onFocus: () => { state.focus.value = 'both' }, onBlur: methods.onBlur, onKeydown, onKeyup: methods.onKeyup }) } return evt }) function getEvents (side) { return $q.platform.is.mobile !== true && state.editable.value === true && props.dragOnlyRange !== true ? { onFocus: () => { state.focus.value = side }, onBlur: methods.onBlur, onKeydown, onKeyup: methods.onKeyup } : {} } const thumbTabindex = computed(() => (props.dragOnlyRange !== true ? state.tabindex.value : null)) const trackContainerTabindex = computed(() => ( $q.platform.is.mobile !== true && (props.dragRange || props.dragOnlyRange === true) ? state.tabindex.value : null )) const minThumbRef = ref(null) const minEvents = computed(() => getEvents('min')) const getMinThumb = methods.getThumbRenderFn({ focusValue: 'min', getNodeData: () => ({ ref: minThumbRef, key: 'tmin', ...minEvents.value, tabindex: thumbTabindex.value }), ratio: ratioMin, label: computed(() => ( props.leftLabelValue !== void 0 ? props.leftLabelValue : model.value.min )), thumbColor: computed(() => props.leftThumbColor || props.thumbColor || props.color), labelColor: computed(() => props.leftLabelColor || props.labelColor), labelTextColor: computed(() => props.leftLabelTextColor || props.labelTextColor) }) const maxEvents = computed(() => getEvents('max')) const getMaxThumb = methods.getThumbRenderFn({ focusValue: 'max', getNodeData: () => ({ ...maxEvents.value, key: 'tmax', tabindex: thumbTabindex.value }), ratio: ratioMax, label: computed(() => ( props.rightLabelValue !== void 0 ? props.rightLabelValue : model.value.max )), thumbColor: computed(() => props.rightThumbColor || props.thumbColor || props.color), labelColor: computed(() => props.rightLabelColor || props.labelColor), labelTextColor: computed(() => props.rightLabelTextColor || props.labelTextColor) }) function updateValue (change) { if (model.value.min !== props.modelValue.min || model.value.max !== props.modelValue.max) { emit('update:modelValue', { ...model.value }) } change === true && emit('change', { ...model.value }) } function getDragging (event) { const { left, top, width, height } = rootRef.value.getBoundingClientRect(), sensitivity = props.dragOnlyRange === true ? 0 : (props.vertical === true ? minThumbRef.value.offsetHeight / (2 * height) : minThumbRef.value.offsetWidth / (2 * width) ) const dragging = { left, top, width, height, valueMin: model.value.min, valueMax: model.value.max, ratioMin: modelMinRatio.value, ratioMax: modelMaxRatio.value } const ratio = methods.getDraggingRatio(event, dragging) if (props.dragOnlyRange !== true && ratio < dragging.ratioMin + sensitivity) { dragging.type = dragType.MIN } else if (props.dragOnlyRange === true || ratio < dragging.ratioMax - sensitivity) { if (props.dragRange === true || props.dragOnlyRange === true) { dragging.type = dragType.RANGE Object.assign(dragging, { offsetRatio: ratio, offsetModel: methods.convertRatioToModel(ratio), rangeValue: dragging.valueMax - dragging.valueMin, rangeRatio: dragging.ratioMax - dragging.ratioMin }) } else { dragging.type = dragging.ratioMax - ratio < ratio - dragging.ratioMin ? dragType.MAX : dragType.MIN } } else { dragging.type = dragType.MAX } return dragging } function updatePosition (event, dragging = state.dragging.value) { let pos const ratio = methods.getDraggingRatio(event, dragging) const localModel = methods.convertRatioToModel(ratio) switch (dragging.type) { case dragType.MIN: if (ratio <= dragging.ratioMax) { pos = { minR: ratio, maxR: dragging.ratioMax, min: localModel, max: dragging.valueMax } state.focus.value = 'min' } else { pos = { minR: dragging.ratioMax, maxR: ratio, min: dragging.valueMax, max: localModel } state.focus.value = 'max' } break case dragType.MAX: if (ratio >= dragging.ratioMin) { pos = { minR: dragging.ratioMin, maxR: ratio, min: dragging.valueMin, max: localModel } state.focus.value = 'max' } else { pos = { minR: ratio, maxR: dragging.ratioMin, min: localModel, max: dragging.valueMin } state.focus.value = 'min' } break case dragType.RANGE: const ratioDelta = ratio - dragging.offsetRatio, minR = between(dragging.ratioMin + ratioDelta, 0, 1 - dragging.rangeRatio), modelDelta = localModel - dragging.offsetModel, min = between(dragging.valueMin + modelDelta, props.min, props.max - dragging.rangeValue) pos = { minR, maxR: minR + dragging.rangeRatio, min: parseFloat(min.toFixed(state.decimals.value)), max: parseFloat((min + dragging.rangeValue).toFixed(state.decimals.value)) } state.focus.value = 'both' break } // If either of the values to be emitted are null, set them to the defaults the user has entered. model.value = model.value.min === null || model.value.max === null ? { min: pos.min || props.min, max: pos.max || props.max } : { min: pos.min, max: pos.max } if (props.snap !== true || props.step === 0) { curMinRatio.value = pos.minR curMaxRatio.value = pos.maxR } else { curMinRatio.value = methods.convertModelToRatio(model.value.min) curMaxRatio.value = methods.convertModelToRatio(model.value.max) } } function onKeydown (evt) { if (!keyCodes.includes(evt.keyCode)) { return } stopAndPrevent(evt) const stepVal = ([ 34, 33 ].includes(evt.keyCode) ? 10 : 1) * state.step.value, offset = ( ([ 34, 37, 40 ].includes(evt.keyCode) ? -1 : 1) * (state.isReversed.value === true ? -1 : 1) * (props.vertical === true ? -1 : 1) * stepVal ) if (state.focus.value === 'both') { const interval = model.value.max - model.value.min const min = between( parseFloat((model.value.min + offset).toFixed(state.decimals.value)), state.innerMin.value, state.innerMax.value - interval ) model.value = { min, max: parseFloat((min + interval).toFixed(state.decimals.value)) } } else if (state.focus.value === false) { return } else { const which = state.focus.value model.value = { ...model.value, [ which ]: between( parseFloat((model.value[ which ] + offset).toFixed(state.decimals.value)), which === 'min' ? state.innerMin.value : model.value.min, which === 'max' ? state.innerMax.value : model.value.max ) } } updateValue() } return () => { const content = methods.getContent( selectionBarStyle, trackContainerTabindex, trackContainerEvents, node => { node.push( getMinThumb(), getMaxThumb() ) } ) return h('div', { ref: rootRef, class: 'q-range ' + state.classes.value + ( props.modelValue.min === null || props.modelValue.max === null ? ' q-slider--no-value' : '' ), ...state.attributes.value, 'aria-valuenow': props.modelValue.min + '|' + props.modelValue.max }, content) } } })