123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- import { createVNode as _createVNode } from "vue";
- // Styles
- import "./VTimePickerClock.css";
- // Composables
- import { useBackgroundColor, useTextColor } from "../../composables/color.mjs"; // Utilities
- import { computed, ref, toRef, watch } from 'vue';
- import { genericComponent, propsFactory, useRender } from "../../util/index.mjs"; // 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, _ref) {
- let {
- emit
- } = _ref;
- 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 {
- textColorClasses,
- textColorStyles
- } = useTextColor(toRef(props, 'color'));
- const {
- backgroundColorClasses,
- backgroundColorStyles
- } = useBackgroundColor(toRef(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);
- }
- }
- 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: `${50 + x * 50}%`,
- top: `${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();
- window.removeEventListener('mousemove', onDragMove);
- window.removeEventListener('touchmove', onDragMove);
- window.removeEventListener('mouseup', onMouseUp);
- window.removeEventListener('touchend', onMouseUp);
- isDragging.value = false;
- if (valueOnMouseUp.value !== null && isAllowed(valueOnMouseUp.value)) {
- emit('change', valueOnMouseUp.value);
- }
- }
- useRender(() => {
- return _createVNode("div", {
- "class": [{
- '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
- }, [_createVNode("div", {
- "class": "v-time-picker-clock__inner",
- "ref": innerClockRef
- }, [_createVNode("div", {
- "class": [{
- 'v-time-picker-clock__hand': true,
- 'v-time-picker-clock__hand--inner': isInner(props.modelValue)
- }, textColorClasses.value],
- "style": [{
- 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 _createVNode("div", {
- "class": [{
- '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": [getTransform(value), isActive && backgroundColorStyles.value]
- }, [_createVNode("span", null, [props.format(value)])]);
- })])]);
- });
- }
- });
- //# sourceMappingURL=VTimePickerClock.mjs.map
|