287 lines
8.3 KiB
JavaScript
287 lines
8.3 KiB
JavaScript
/* eslint-disable max-statements */
|
|
// Composables
|
|
import { makeElevationProps } from "../../composables/elevation.mjs";
|
|
import { useRtl } from "../../composables/locale.mjs";
|
|
import { makeRoundedProps } from "../../composables/rounded.mjs"; // Utilities
|
|
import { computed, provide, ref, shallowRef, toRef } from 'vue';
|
|
import { clamp, createRange, getDecimals, propsFactory } from "../../util/index.mjs"; // 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(() => +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;
|
|
const newValue = Math.round((clamped - offset) / step.value) * step.value + offset;
|
|
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 vertical = props.direction === 'vertical';
|
|
const start = vertical ? 'top' : 'left';
|
|
const length = vertical ? 'height' : 'width';
|
|
const position = vertical ? 'clientY' : 'clientX';
|
|
const {
|
|
[start]: trackStart,
|
|
[length]: trackLength
|
|
} = trackContainerRef.value?.$el.getBoundingClientRect();
|
|
const clickOffset = getPosition(e, position);
|
|
|
|
// It is possible for left to be NaN, force to number
|
|
let clickPos = Math.min(Math.max((clickOffset - trackStart - startOffset.value) / trackLength, 0), 1) || 0;
|
|
if (vertical ? indexFromEnd.value : indexFromEnd.value !== isRtl.value) clickPos = 1 - clickPos;
|
|
return roundValue(min.value + clickPos * (max.value - min.value));
|
|
}
|
|
const handleStop = e => {
|
|
onSliderEnd({
|
|
value: parseMouseMove(e)
|
|
});
|
|
mousePressed.value = false;
|
|
startOffset.value = 0;
|
|
};
|
|
const handleStart = e => {
|
|
activeThumbRef.value = getActiveThumb(e);
|
|
if (!activeThumbRef.value) return;
|
|
activeThumbRef.value.focus();
|
|
mousePressed.value = true;
|
|
if (activeThumbRef.value.contains(e.target)) {
|
|
startOffset.value = getOffset(e, activeThumbRef.value, props.direction);
|
|
} else {
|
|
startOffset.value = 0;
|
|
onSliderMove({
|
|
value: parseMouseMove(e)
|
|
});
|
|
}
|
|
onSliderStart({
|
|
value: parseMouseMove(e)
|
|
});
|
|
};
|
|
const moveListenerOptions = {
|
|
passive: true,
|
|
capture: true
|
|
};
|
|
function onMouseMove(e) {
|
|
onSliderMove({
|
|
value: parseMouseMove(e)
|
|
});
|
|
}
|
|
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) {
|
|
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.mjs.map
|