routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+311
@@ -0,0 +1,311 @@
|
||||
/* eslint-disable max-statements */
|
||||
// Composables
|
||||
import { makeElevationProps } from "../../composables/elevation.js";
|
||||
import { useForm } from "../../composables/form.js";
|
||||
import { useRtl } from "../../composables/locale.js";
|
||||
import { makeRoundedProps } from "../../composables/rounded.js"; // Utilities
|
||||
import { computed, nextTick, onScopeDispose, provide, ref, shallowRef, toRef } from 'vue';
|
||||
import { clamp, createRange, getDecimals, IN_BROWSER, 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' || v === 'hover'
|
||||
},
|
||||
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,
|
||||
noKeyboard: Boolean,
|
||||
...makeRoundedProps(),
|
||||
...makeElevationProps({
|
||||
elevation: 1
|
||||
}),
|
||||
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 = ({
|
||||
props,
|
||||
steps,
|
||||
onSliderStart,
|
||||
onSliderMove,
|
||||
onSliderEnd,
|
||||
getActiveThumb
|
||||
}) => {
|
||||
const form = useForm(props);
|
||||
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 thumbColor = computed(() => props.error || form.isDisabled.value ? undefined : props.thumbColor ?? props.color);
|
||||
const thumbLabelColor = computed(() => props.error || form.isDisabled.value ? undefined : props.thumbColor);
|
||||
const trackColor = computed(() => props.error || form.isDisabled.value ? undefined : props.trackColor ?? props.color);
|
||||
const trackFillColor = computed(() => props.error || form.isDisabled.value ? 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
|
||||
});
|
||||
}
|
||||
onScopeDispose(() => {
|
||||
if (!IN_BROWSER) return;
|
||||
window.removeEventListener('touchmove', onMouseMove);
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
window.removeEventListener('mouseup', onSliderMouseUp);
|
||||
});
|
||||
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(({
|
||||
label
|
||||
}) => !!label));
|
||||
const data = {
|
||||
activeThumbRef,
|
||||
color: toRef(() => props.color),
|
||||
decimals,
|
||||
disabled: form.isDisabled,
|
||||
direction: toRef(() => props.direction),
|
||||
elevation: toRef(() => props.elevation),
|
||||
hasLabels,
|
||||
isReversed,
|
||||
indexFromEnd,
|
||||
min,
|
||||
max,
|
||||
mousePressed,
|
||||
noKeyboard: toRef(() => props.noKeyboard),
|
||||
numTicks,
|
||||
onSliderMousedown,
|
||||
onSliderTouchstart,
|
||||
parsedTicks,
|
||||
parseMouseMove,
|
||||
position,
|
||||
readonly: form.isReadonly,
|
||||
rounded: toRef(() => props.rounded),
|
||||
roundValue,
|
||||
showTicks,
|
||||
startOffset,
|
||||
step,
|
||||
thumbSize,
|
||||
thumbColor,
|
||||
thumbLabelColor,
|
||||
thumbLabel: toRef(() => props.thumbLabel),
|
||||
ticks: toRef(() => props.ticks),
|
||||
tickSize,
|
||||
trackColor,
|
||||
trackContainerRef,
|
||||
trackFillColor,
|
||||
trackSize,
|
||||
vertical
|
||||
};
|
||||
provide(VSliderSymbol, data);
|
||||
return data;
|
||||
};
|
||||
//# sourceMappingURL=slider.js.map
|
||||
Reference in New Issue
Block a user