UNPKG

vuetify

Version:

Vue Material Component Framework

304 lines (303 loc) 8.94 kB
/* eslint-disable max-statements */ // Composables import { makeElevationProps } from "../../composables/elevation.js"; import { useRtl } from "../../composables/locale.js"; import { makeRoundedProps } from "../../composables/rounded.js"; // Utilities import { computed, nextTick, provide, ref, shallowRef, toRef } from 'vue'; import { clamp, createRange, getDecimals, propsFactory } from "../../util/index.js"; // Types export const VSliderSymbol = Symbol.for('vuetify:v-slider'); export function getOffset(e, el, direction) { const vertical = direction === 'vertical'; const rect = el.getBoundingClientRect(); const touch = 'touches' in e ? e.touches[0] : e; return vertical ? touch.clientY - (rect.top + rect.height / 2) : touch.clientX - (rect.left + rect.width / 2); } function getPosition(e, position) { if ('touches' in e && e.touches.length) return e.touches[0][position];else if ('changedTouches' in e && e.changedTouches.length) return e.changedTouches[0][position];else return e[position]; } export const makeSliderProps = propsFactory({ disabled: { type: Boolean, default: null }, error: Boolean, readonly: { type: Boolean, default: null }, max: { type: [Number, String], default: 100 }, min: { type: [Number, String], default: 0 }, step: { type: [Number, String], default: 0 }, thumbColor: String, thumbLabel: { type: [Boolean, String], default: undefined, validator: v => typeof v === 'boolean' || v === 'always' }, thumbSize: { type: [Number, String], default: 20 }, showTicks: { type: [Boolean, String], default: false, validator: v => typeof v === 'boolean' || v === 'always' }, ticks: { type: [Array, Object] }, tickSize: { type: [Number, String], default: 2 }, color: String, trackColor: String, trackFillColor: String, trackSize: { type: [Number, String], default: 4 }, direction: { type: String, default: 'horizontal', validator: v => ['vertical', 'horizontal'].includes(v) }, reverse: Boolean, ...makeRoundedProps(), ...makeElevationProps({ elevation: 2 }), ripple: { type: Boolean, default: true } }, 'Slider'); export const useSteps = props => { const min = computed(() => parseFloat(props.min)); const max = computed(() => parseFloat(props.max)); const step = computed(() => Number(props.step) > 0 ? parseFloat(props.step) : 0); const decimals = computed(() => Math.max(getDecimals(step.value), getDecimals(min.value))); function roundValue(value) { value = parseFloat(value); if (step.value <= 0) return value; const clamped = clamp(value, min.value, max.value); const offset = min.value % step.value; let newValue = Math.round((clamped - offset) / step.value) * step.value + offset; if (clamped > newValue && newValue + step.value > max.value) { newValue = max.value; } return parseFloat(Math.min(newValue, max.value).toFixed(decimals.value)); } return { min, max, step, decimals, roundValue }; }; export const useSlider = _ref => { let { props, steps, onSliderStart, onSliderMove, onSliderEnd, getActiveThumb } = _ref; const { isRtl } = useRtl(); const isReversed = toRef(() => props.reverse); const vertical = computed(() => props.direction === 'vertical'); const indexFromEnd = computed(() => vertical.value !== isReversed.value); const { min, max, step, decimals, roundValue } = steps; const thumbSize = computed(() => parseInt(props.thumbSize, 10)); const tickSize = computed(() => parseInt(props.tickSize, 10)); const trackSize = computed(() => parseInt(props.trackSize, 10)); const numTicks = computed(() => (max.value - min.value) / step.value); const disabled = toRef(() => props.disabled); const thumbColor = computed(() => props.error || props.disabled ? undefined : props.thumbColor ?? props.color); const trackColor = computed(() => props.error || props.disabled ? undefined : props.trackColor ?? props.color); const trackFillColor = computed(() => props.error || props.disabled ? undefined : props.trackFillColor ?? props.color); const mousePressed = shallowRef(false); const startOffset = shallowRef(0); const trackContainerRef = ref(); const activeThumbRef = ref(); function parseMouseMove(e) { const el = trackContainerRef.value?.$el; if (!el) return; const vertical = props.direction === 'vertical'; const start = vertical ? 'top' : 'left'; const length = vertical ? 'height' : 'width'; const position = vertical ? 'clientY' : 'clientX'; const { [start]: trackStart, [length]: trackLength } = el.getBoundingClientRect(); const clickOffset = getPosition(e, position); // It is possible for left to be NaN, force to number let clickPos = clamp((clickOffset - trackStart - startOffset.value) / trackLength) || 0; if (vertical ? indexFromEnd.value : indexFromEnd.value !== isRtl.value) clickPos = 1 - clickPos; return roundValue(min.value + clickPos * (max.value - min.value)); } const handleStop = e => { const value = parseMouseMove(e); if (value != null) { onSliderEnd({ value }); } mousePressed.value = false; startOffset.value = 0; }; const handleStart = e => { const value = parseMouseMove(e); activeThumbRef.value = getActiveThumb(e); if (!activeThumbRef.value) return; mousePressed.value = true; if (activeThumbRef.value.contains(e.target)) { startOffset.value = getOffset(e, activeThumbRef.value, props.direction); } else { startOffset.value = 0; if (value != null) { onSliderMove({ value }); } } if (value != null) { onSliderStart({ value }); } nextTick(() => activeThumbRef.value?.focus()); }; const moveListenerOptions = { passive: true, capture: true }; function onMouseMove(e) { const value = parseMouseMove(e); if (value != null) { onSliderMove({ value }); } } function onSliderMouseUp(e) { e.stopPropagation(); e.preventDefault(); handleStop(e); window.removeEventListener('mousemove', onMouseMove, moveListenerOptions); window.removeEventListener('mouseup', onSliderMouseUp); } function onSliderTouchend(e) { handleStop(e); window.removeEventListener('touchmove', onMouseMove, moveListenerOptions); e.target?.removeEventListener('touchend', onSliderTouchend); } function onSliderTouchstart(e) { handleStart(e); window.addEventListener('touchmove', onMouseMove, moveListenerOptions); e.target?.addEventListener('touchend', onSliderTouchend, { passive: false }); } function onSliderMousedown(e) { if (e.button !== 0) return; e.preventDefault(); handleStart(e); window.addEventListener('mousemove', onMouseMove, moveListenerOptions); window.addEventListener('mouseup', onSliderMouseUp, { passive: false }); } const position = val => { const percentage = (val - min.value) / (max.value - min.value) * 100; return clamp(isNaN(percentage) ? 0 : percentage, 0, 100); }; const showTicks = toRef(() => props.showTicks); const parsedTicks = computed(() => { if (!showTicks.value) return []; if (!props.ticks) { return numTicks.value !== Infinity ? createRange(numTicks.value + 1).map(t => { const value = min.value + t * step.value; return { value, position: position(value) }; }) : []; } if (Array.isArray(props.ticks)) return props.ticks.map(t => ({ value: t, position: position(t), label: t.toString() })); return Object.keys(props.ticks).map(key => ({ value: parseFloat(key), position: position(parseFloat(key)), label: props.ticks[key] })); }); const hasLabels = computed(() => parsedTicks.value.some(_ref2 => { let { label } = _ref2; return !!label; })); const data = { activeThumbRef, color: toRef(() => props.color), decimals, disabled, direction: toRef(() => props.direction), elevation: toRef(() => props.elevation), hasLabels, isReversed, indexFromEnd, min, max, mousePressed, numTicks, onSliderMousedown, onSliderTouchstart, parsedTicks, parseMouseMove, position, readonly: toRef(() => props.readonly), rounded: toRef(() => props.rounded), roundValue, showTicks, startOffset, step, thumbSize, thumbColor, thumbLabel: toRef(() => props.thumbLabel), ticks: toRef(() => props.ticks), tickSize, trackColor, trackContainerRef, trackFillColor, trackSize, vertical }; provide(VSliderSymbol, data); return data; }; //# sourceMappingURL=slider.js.map