routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+252
@@ -0,0 +1,252 @@
|
||||
import { normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode } from "vue";
|
||||
// Styles
|
||||
import "./VTimePickerClock.css";
|
||||
|
||||
// Composables
|
||||
import { useBackgroundColor, useTextColor } from "../../composables/color.js"; // Utilities
|
||||
import { computed, onScopeDispose, ref, watch } from 'vue';
|
||||
import { debounce, genericComponent, IN_BROWSER, propsFactory, useRender } from "../../util/index.js"; // Types
|
||||
export const makeVTimePickerClockProps = propsFactory({
|
||||
allowedValues: Function,
|
||||
ampm: Boolean,
|
||||
color: String,
|
||||
disabled: Boolean,
|
||||
displayedValue: null,
|
||||
double: Boolean,
|
||||
format: {
|
||||
type: Function,
|
||||
default: val => val
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
scrollable: Boolean,
|
||||
readonly: Boolean,
|
||||
rotate: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
modelValue: {
|
||||
type: Number
|
||||
}
|
||||
}, 'VTimePickerClock');
|
||||
export const VTimePickerClock = genericComponent()({
|
||||
name: 'VTimePickerClock',
|
||||
props: makeVTimePickerClockProps(),
|
||||
emits: {
|
||||
change: val => true,
|
||||
input: val => true
|
||||
},
|
||||
setup(props, {
|
||||
emit
|
||||
}) {
|
||||
const clockRef = ref(null);
|
||||
const innerClockRef = ref(null);
|
||||
const inputValue = ref(undefined);
|
||||
const isDragging = ref(false);
|
||||
const valueOnMouseDown = ref(null);
|
||||
const valueOnMouseUp = ref(null);
|
||||
const emitChangeDebounced = debounce(value => emit('change', value), 750);
|
||||
const {
|
||||
textColorClasses,
|
||||
textColorStyles
|
||||
} = useTextColor(() => props.color);
|
||||
const {
|
||||
backgroundColorClasses,
|
||||
backgroundColorStyles
|
||||
} = useBackgroundColor(() => props.color);
|
||||
const count = computed(() => props.max - props.min + 1);
|
||||
const roundCount = computed(() => props.double ? count.value / 2 : count.value);
|
||||
const degreesPerUnit = computed(() => 360 / roundCount.value);
|
||||
const degrees = computed(() => degreesPerUnit.value * Math.PI / 180);
|
||||
const displayedValue = computed(() => props.modelValue == null ? props.min : props.modelValue);
|
||||
const innerRadiusScale = computed(() => 0.62);
|
||||
const genChildren = computed(() => {
|
||||
const children = [];
|
||||
for (let value = props.min; value <= props.max; value = value + props.step) {
|
||||
children.push(value);
|
||||
}
|
||||
return children;
|
||||
});
|
||||
watch(() => props.modelValue, val => {
|
||||
inputValue.value = val;
|
||||
});
|
||||
function update(value) {
|
||||
if (inputValue.value !== value) {
|
||||
inputValue.value = value;
|
||||
}
|
||||
emit('input', value);
|
||||
}
|
||||
function isAllowed(value) {
|
||||
return !props.allowedValues || props.allowedValues(value);
|
||||
}
|
||||
function wheel(e) {
|
||||
if (!props.scrollable || props.disabled) return;
|
||||
e.preventDefault();
|
||||
const delta = Math.sign(-e.deltaY || 1);
|
||||
let value = displayedValue.value;
|
||||
do {
|
||||
value = value + delta;
|
||||
value = (value - props.min + count.value) % count.value + props.min;
|
||||
} while (!isAllowed(value) && value !== displayedValue.value);
|
||||
if (value !== props.displayedValue) {
|
||||
update(value);
|
||||
}
|
||||
emitChangeDebounced(value);
|
||||
}
|
||||
function isInner(value) {
|
||||
return props.double && value - props.min >= roundCount.value;
|
||||
}
|
||||
function handScale(value) {
|
||||
return isInner(value) ? innerRadiusScale.value : 1;
|
||||
}
|
||||
function getPosition(value) {
|
||||
const rotateRadians = props.rotate * Math.PI / 180;
|
||||
return {
|
||||
x: Math.sin((value - props.min) * degrees.value + rotateRadians) * handScale(value),
|
||||
y: -Math.cos((value - props.min) * degrees.value + rotateRadians) * handScale(value)
|
||||
};
|
||||
}
|
||||
function angleToValue(angle, insideClick) {
|
||||
const value = (Math.round(angle / degreesPerUnit.value) + (insideClick ? roundCount.value : 0)) % count.value + props.min;
|
||||
|
||||
// Necessary to fix edge case when selecting left part of the value(s) at 12 o'clock
|
||||
if (angle < 360 - degreesPerUnit.value / 2) return value;
|
||||
return insideClick ? props.max - roundCount.value + 1 : props.min;
|
||||
}
|
||||
function getTransform(i) {
|
||||
const {
|
||||
x,
|
||||
y
|
||||
} = getPosition(i);
|
||||
return {
|
||||
left: `${Math.round(50 + x * 50)}%`,
|
||||
top: `${Math.round(50 + y * 50)}%`
|
||||
};
|
||||
}
|
||||
function euclidean(p0, p1) {
|
||||
const dx = p1.x - p0.x;
|
||||
const dy = p1.y - p0.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
function angle(center, p1) {
|
||||
const value = 2 * Math.atan2(p1.y - center.y - euclidean(center, p1), p1.x - center.x);
|
||||
return Math.abs(value * 180 / Math.PI);
|
||||
}
|
||||
function setMouseDownValue(value) {
|
||||
if (valueOnMouseDown.value === null) {
|
||||
valueOnMouseDown.value = value;
|
||||
}
|
||||
valueOnMouseUp.value = value;
|
||||
update(value);
|
||||
}
|
||||
function onDragMove(e) {
|
||||
e.preventDefault();
|
||||
if (!isDragging.value && e.type !== 'click' || !clockRef.value) return;
|
||||
const {
|
||||
width,
|
||||
top,
|
||||
left
|
||||
} = clockRef.value?.getBoundingClientRect();
|
||||
const {
|
||||
width: innerWidth
|
||||
} = innerClockRef.value?.getBoundingClientRect() ?? {
|
||||
width: 0
|
||||
};
|
||||
const {
|
||||
clientX,
|
||||
clientY
|
||||
} = 'touches' in e ? e.touches[0] : e;
|
||||
const center = {
|
||||
x: width / 2,
|
||||
y: -width / 2
|
||||
};
|
||||
const coords = {
|
||||
x: clientX - left,
|
||||
y: top - clientY
|
||||
};
|
||||
const handAngle = Math.round(angle(center, coords) - props.rotate + 360) % 360;
|
||||
const insideClick = props.double && euclidean(center, coords) < (innerWidth + innerWidth * innerRadiusScale.value) / 4;
|
||||
const checksCount = Math.ceil(15 / degreesPerUnit.value);
|
||||
let value;
|
||||
for (let i = 0; i < checksCount; i++) {
|
||||
value = angleToValue(handAngle + i * degreesPerUnit.value, insideClick);
|
||||
if (isAllowed(value)) return setMouseDownValue(value);
|
||||
value = angleToValue(handAngle - i * degreesPerUnit.value, insideClick);
|
||||
if (isAllowed(value)) return setMouseDownValue(value);
|
||||
}
|
||||
}
|
||||
function onMouseDown(e) {
|
||||
if (props.disabled) return;
|
||||
e.preventDefault();
|
||||
window.addEventListener('mousemove', onDragMove);
|
||||
window.addEventListener('touchmove', onDragMove);
|
||||
window.addEventListener('mouseup', onMouseUp);
|
||||
window.addEventListener('touchend', onMouseUp);
|
||||
valueOnMouseDown.value = null;
|
||||
valueOnMouseUp.value = null;
|
||||
isDragging.value = true;
|
||||
onDragMove(e);
|
||||
}
|
||||
function onMouseUp(e) {
|
||||
e.stopPropagation();
|
||||
removeListeners();
|
||||
isDragging.value = false;
|
||||
if (valueOnMouseUp.value !== null && isAllowed(valueOnMouseUp.value)) {
|
||||
emit('change', valueOnMouseUp.value);
|
||||
}
|
||||
}
|
||||
function removeListeners() {
|
||||
if (!IN_BROWSER) return;
|
||||
window.removeEventListener('mousemove', onDragMove);
|
||||
window.removeEventListener('touchmove', onDragMove);
|
||||
window.removeEventListener('mouseup', onMouseUp);
|
||||
window.removeEventListener('touchend', onMouseUp);
|
||||
}
|
||||
onScopeDispose(removeListeners);
|
||||
useRender(() => {
|
||||
return _createElementVNode("div", {
|
||||
"class": _normalizeClass([{
|
||||
'v-time-picker-clock': true,
|
||||
'v-time-picker-clock--indeterminate': props.modelValue == null,
|
||||
'v-time-picker-clock--readonly': props.readonly
|
||||
}]),
|
||||
"onMousedown": onMouseDown,
|
||||
"onTouchstart": onMouseDown,
|
||||
"onWheel": wheel,
|
||||
"ref": clockRef
|
||||
}, [_createElementVNode("div", {
|
||||
"class": "v-time-picker-clock__inner",
|
||||
"ref": innerClockRef
|
||||
}, [_createElementVNode("div", {
|
||||
"class": _normalizeClass([{
|
||||
'v-time-picker-clock__hand': true,
|
||||
'v-time-picker-clock__hand--inner': isInner(props.modelValue)
|
||||
}, textColorClasses.value]),
|
||||
"style": _normalizeStyle([{
|
||||
transform: `rotate(${props.rotate + degreesPerUnit.value * (displayedValue.value - props.min)}deg) scaleY(${handScale(displayedValue.value)})`
|
||||
}, textColorStyles.value])
|
||||
}, null), genChildren.value.map(value => {
|
||||
const isActive = value === displayedValue.value;
|
||||
return _createElementVNode("div", {
|
||||
"class": _normalizeClass([{
|
||||
'v-time-picker-clock__item': true,
|
||||
'v-time-picker-clock__item--active': isActive,
|
||||
'v-time-picker-clock__item--disabled': props.disabled || !isAllowed(value)
|
||||
}, isActive && backgroundColorClasses.value]),
|
||||
"style": _normalizeStyle([getTransform(value), isActive && backgroundColorStyles.value])
|
||||
}, [_createElementVNode("span", null, [props.format(value)])]);
|
||||
})])]);
|
||||
});
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=VTimePickerClock.js.map
|
||||
Reference in New Issue
Block a user