routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+274
@@ -0,0 +1,274 @@
|
||||
import { createVNode as _createVNode, createTextVNode as _createTextVNode, createElementVNode as _createElementVNode, Fragment as _Fragment, normalizeClass as _normalizeClass } from "vue";
|
||||
// Styles
|
||||
import "./VTimePickerControls.css";
|
||||
|
||||
// Components
|
||||
import { VTimePickerField } from "./VTimePickerField.js";
|
||||
import { VBtn } from "../VBtn/index.js"; // Composables
|
||||
import { useLocale } from "../../composables/locale.js";
|
||||
import { useProxiedModel } from "../../composables/proxiedModel.js"; // Utilities
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { makeTimeValidationProps, useTimeValidation } from "./useTimeValidation.js";
|
||||
import { convert12to24, convert24to12, extractInteger, pad } from "./util.js";
|
||||
import { clamp, genericComponent, propsFactory, useRender } from "../../util/index.js"; // Types
|
||||
export const makeVTimePickerControlsProps = propsFactory({
|
||||
ampm: Boolean,
|
||||
color: String,
|
||||
disabled: Boolean,
|
||||
inputHints: Boolean,
|
||||
hour: [Number, String],
|
||||
minute: [Number, String],
|
||||
second: [Number, String],
|
||||
period: String,
|
||||
readonly: Boolean,
|
||||
useSeconds: Boolean,
|
||||
value: Number,
|
||||
viewMode: String,
|
||||
...makeTimeValidationProps()
|
||||
}, 'VTimePickerControls');
|
||||
export const VTimePickerControls = genericComponent()({
|
||||
name: 'VTimePickerControls',
|
||||
props: makeVTimePickerControlsProps(),
|
||||
emits: {
|
||||
'update:period': data => true,
|
||||
'update:viewMode': data => true,
|
||||
'update:hour': v => true,
|
||||
'update:minute': v => true,
|
||||
'update:second': v => true
|
||||
},
|
||||
setup(props, {
|
||||
emit
|
||||
}) {
|
||||
const {
|
||||
t
|
||||
} = useLocale();
|
||||
const {
|
||||
isAllowedHour,
|
||||
isAllowedMinute,
|
||||
isAllowedSecond,
|
||||
findNextAllowed
|
||||
} = useTimeValidation(props);
|
||||
const currentHour = computed(() => props.hour !== null ? props.ampm ? convert12to24(Number(props.hour), props.period ?? 'am') : Number(props.hour) : null);
|
||||
const currentMinute = computed(() => props.minute !== null ? Number(props.minute) : null);
|
||||
const isHourValid = computed(() => {
|
||||
if (props.hour === null) return true;
|
||||
return isAllowedHour.value?.(Number(currentHour.value)) ?? true;
|
||||
});
|
||||
const isMinuteValid = computed(() => {
|
||||
if (props.minute === null) return true;
|
||||
return isAllowedMinute.value?.(currentHour.value, Number(props.minute)) ?? true;
|
||||
});
|
||||
const isSecondValid = computed(() => {
|
||||
if (props.second === null) return true;
|
||||
return isAllowedSecond.value?.(currentHour.value, currentMinute.value, Number(props.second)) ?? true;
|
||||
});
|
||||
const transformHours = {
|
||||
in: v => {
|
||||
if (v == null || isNaN(Number(v))) return null;
|
||||
const val = Number(v);
|
||||
return props.ampm ? pad(convert24to12(val)) : pad(val);
|
||||
},
|
||||
out: v => {
|
||||
if (isNaN(Number(v)) || v == null || v === '') return null;
|
||||
const val = typeof v === 'string' ? extractInteger(v) : Number(v);
|
||||
if (val === null) return null;
|
||||
return props.ampm ? convert12to24(val, props.period ?? 'am') : clamp(val, 0, 23);
|
||||
}
|
||||
};
|
||||
const hour = useProxiedModel(props, 'hour', undefined, transformHours.in, transformHours.out);
|
||||
const transformMinutesOrSeconds = {
|
||||
in: v => v != null && !isNaN(Number(v)) ? pad(`${v}`) : null,
|
||||
out: v => {
|
||||
if (isNaN(Number(v)) || v == null || v === '') return null;
|
||||
const val = typeof v === 'string' ? extractInteger(v) : Number(v);
|
||||
return val !== null ? clamp(val, 0, 59) : null;
|
||||
}
|
||||
};
|
||||
const minute = useProxiedModel(props, 'minute', undefined, transformMinutesOrSeconds.in, transformMinutesOrSeconds.out);
|
||||
const second = useProxiedModel(props, 'second', undefined, transformMinutesOrSeconds.in, transformMinutesOrSeconds.out);
|
||||
function onHourFieldKeydown(e) {
|
||||
if (!['ArrowUp', 'ArrowDown'].includes(e.key)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isAm = props.period === 'am';
|
||||
const current = props.ampm ? convert12to24(Number(hour.value ?? 0), isAm ? 'am' : 'pm') : Number(hour.value ?? 0);
|
||||
const next = findNextAllowed('hour', current, e.key === 'ArrowUp');
|
||||
const togglePeriod = isAm && next >= 12 || !isAm && next < 12;
|
||||
if (props.ampm && togglePeriod) {
|
||||
emit('update:period', props.period === 'am' ? 'pm' : 'am');
|
||||
nextTick(() => hour.value = pad(next));
|
||||
} else {
|
||||
hour.value = pad(next);
|
||||
}
|
||||
}
|
||||
function onMinuteFieldKeydown(e) {
|
||||
if (!['ArrowUp', 'ArrowDown'].includes(e.key)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const current = Number(minute.value ?? 0);
|
||||
const next = findNextAllowed('minute', current, e.key === 'ArrowUp', currentHour.value);
|
||||
minute.value = pad(next);
|
||||
}
|
||||
function onSecondFieldKeydown(e) {
|
||||
if (!['ArrowUp', 'ArrowDown'].includes(e.key)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const current = Number(second.value ?? 0);
|
||||
const next = findNextAllowed('second', current, e.key === 'ArrowUp', currentHour.value, currentMinute.value);
|
||||
second.value = pad(next);
|
||||
}
|
||||
function createInputInterceptor(valueTransformOut, compare, apply) {
|
||||
return e => {
|
||||
if (!e.data) return;
|
||||
const inputElement = e.target;
|
||||
const {
|
||||
value: existingTxt,
|
||||
selectionStart,
|
||||
selectionEnd
|
||||
} = inputElement ?? {};
|
||||
if (extractInteger(e.data) === null) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
const potentialNewInputVal = existingTxt ? existingTxt.slice(0, selectionStart) + e.data + existingTxt.slice(selectionEnd) : e.data;
|
||||
if (potentialNewInputVal.length > 2) {
|
||||
if (selectionStart === selectionEnd && selectionEnd === 0 && e.data.trim().startsWith('0')) {
|
||||
e.preventDefault();
|
||||
inputElement.value = potentialNewInputVal.trim().substring(0, 2);
|
||||
apply(inputElement.value);
|
||||
if (e.data.trim().length === 1) {
|
||||
inputElement.setSelectionRange(1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (selectionStart === selectionEnd && selectionEnd === 1 && existingTxt.startsWith('0')) {
|
||||
e.preventDefault();
|
||||
inputElement.value = potentialNewInputVal.trim().substring(0, 2);
|
||||
apply(inputElement.value);
|
||||
return;
|
||||
}
|
||||
const maxValue = props.viewMode === 'hour' ? props.ampm ? 12 : 23 : 59;
|
||||
const value = extractInteger(potentialNewInputVal);
|
||||
if (value > maxValue) {
|
||||
e.preventDefault();
|
||||
inputElement.value = pad(String(extractInteger(e.data)).substring(0, 2));
|
||||
apply(inputElement.value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const potentialNewNumber = valueTransformOut(potentialNewInputVal);
|
||||
if (compare(potentialNewNumber)) {
|
||||
// ignore input when results in the same number
|
||||
// prevents typing more digits
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
}
|
||||
function setPeriod(val) {
|
||||
emit('update:period', val);
|
||||
}
|
||||
const hourInputRef = ref();
|
||||
const minuteInputRef = ref();
|
||||
const secondInputRef = ref();
|
||||
watch(() => props.viewMode, (_, old) => {
|
||||
switch (old) {
|
||||
case 'hour':
|
||||
hourInputRef.value.blur();
|
||||
break;
|
||||
case 'minute':
|
||||
minuteInputRef.value.blur();
|
||||
break;
|
||||
case 'second':
|
||||
secondInputRef.value.blur();
|
||||
break;
|
||||
}
|
||||
});
|
||||
const hourInputFilter = createInputInterceptor(transformHours.out, v => transformHours.in(v) === hour.value, v => hour.value = v);
|
||||
const minuteInputFilter = createInputInterceptor(transformMinutesOrSeconds.out, v => transformMinutesOrSeconds.in(v) === minute.value, v => minute.value = v);
|
||||
const secondInputFilter = createInputInterceptor(transformMinutesOrSeconds.out, v => transformMinutesOrSeconds.in(v) === second.value, v => second.value = v);
|
||||
useRender(() => {
|
||||
return _createElementVNode("div", {
|
||||
"class": "v-time-picker-controls"
|
||||
}, [_createElementVNode("div", {
|
||||
"class": _normalizeClass({
|
||||
'v-time-picker-controls__time': true,
|
||||
'v-time-picker-controls__time--with-ampm': props.ampm,
|
||||
'v-time-picker-controls__time--with-seconds': props.useSeconds
|
||||
})
|
||||
}, [_createVNode(VTimePickerField, {
|
||||
"ref": hourInputRef,
|
||||
"active": props.viewMode === 'hour',
|
||||
"color": props.color,
|
||||
"disabled": props.disabled,
|
||||
"label": t('$vuetify.timePicker.hour'),
|
||||
"showHint": props.inputHints,
|
||||
"error": isHourValid.value ? undefined : t('$vuetify.timePicker.notAllowed'),
|
||||
"modelValue": hour.value,
|
||||
"onUpdate:modelValue": v => hour.value = v,
|
||||
"onKeydown": onHourFieldKeydown,
|
||||
"onBeforeinput": hourInputFilter,
|
||||
"onFocus": () => emit('update:viewMode', 'hour')
|
||||
}, null), _createElementVNode("span", {
|
||||
"class": "v-time-picker-controls__time__separator"
|
||||
}, [_createTextVNode(":")]), _createVNode(VTimePickerField, {
|
||||
"ref": minuteInputRef,
|
||||
"active": props.viewMode === 'minute',
|
||||
"color": props.color,
|
||||
"disabled": props.disabled,
|
||||
"label": t('$vuetify.timePicker.minute'),
|
||||
"showHint": props.inputHints,
|
||||
"error": isMinuteValid.value ? undefined : t('$vuetify.timePicker.notAllowed'),
|
||||
"modelValue": minute.value,
|
||||
"onUpdate:modelValue": v => minute.value = v,
|
||||
"onKeydown": onMinuteFieldKeydown,
|
||||
"onBeforeinput": minuteInputFilter,
|
||||
"onFocus": () => emit('update:viewMode', 'minute')
|
||||
}, null), props.useSeconds && _createElementVNode("span", {
|
||||
"key": "secondsDivider",
|
||||
"class": "v-time-picker-controls__time__separator"
|
||||
}, [_createTextVNode(":")]), props.useSeconds && _createElementVNode(_Fragment, null, [_createVNode(VTimePickerField, {
|
||||
"key": "secondsVal",
|
||||
"ref": secondInputRef,
|
||||
"active": props.viewMode === 'second',
|
||||
"color": props.color,
|
||||
"disabled": props.disabled,
|
||||
"label": t('$vuetify.timePicker.second'),
|
||||
"showHint": props.inputHints,
|
||||
"error": isSecondValid.value ? undefined : t('$vuetify.timePicker.notAllowed'),
|
||||
"modelValue": second.value,
|
||||
"onUpdate:modelValue": v => second.value = v,
|
||||
"onKeydown": onSecondFieldKeydown,
|
||||
"onBeforeinput": secondInputFilter,
|
||||
"onFocus": () => emit('update:viewMode', 'second')
|
||||
}, null)]), props.ampm && _createElementVNode("div", {
|
||||
"class": "v-time-picker-controls__ampm"
|
||||
}, [_createVNode(VBtn, {
|
||||
"active": props.period === 'am',
|
||||
"color": props.period === 'am' ? props.color : undefined,
|
||||
"class": _normalizeClass({
|
||||
'v-time-picker-controls__ampm__am': true,
|
||||
'v-time-picker-controls__ampm__btn': true,
|
||||
'v-time-picker-controls__ampm__btn__active': props.period === 'am'
|
||||
}),
|
||||
"disabled": props.disabled,
|
||||
"text": t('$vuetify.timePicker.am'),
|
||||
"variant": props.disabled && props.period === 'am' ? 'elevated' : 'tonal',
|
||||
"onClick": () => props.period !== 'am' ? setPeriod('am') : null
|
||||
}, null), _createVNode(VBtn, {
|
||||
"active": props.period === 'pm',
|
||||
"color": props.period === 'pm' ? props.color : undefined,
|
||||
"class": _normalizeClass({
|
||||
'v-time-picker-controls__ampm__pm': true,
|
||||
'v-time-picker-controls__ampm__btn': true,
|
||||
'v-time-picker-controls__ampm__btn__active': props.period === 'pm'
|
||||
}),
|
||||
"disabled": props.disabled,
|
||||
"text": t('$vuetify.timePicker.pm'),
|
||||
"variant": props.disabled && props.period === 'pm' ? 'elevated' : 'tonal',
|
||||
"onClick": () => props.period !== 'pm' ? setPeriod('pm') : null
|
||||
}, null)])])]);
|
||||
});
|
||||
return {};
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=VTimePickerControls.js.map
|
||||
Reference in New Issue
Block a user