mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-20 09:44:26 +08:00
redesign time picker in date time select for desktop device
This commit is contained in:
@@ -0,0 +1,113 @@
|
|||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
|
import { useUserStore } from '@/stores/user.ts';
|
||||||
|
|
||||||
|
import { type NameValue } from '@/core/base.ts';
|
||||||
|
import { type WeekDayValue } from '@/core/datetime.ts';
|
||||||
|
import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
|
||||||
|
import { getCurrentYear } from '@/lib/datetime.ts';
|
||||||
|
|
||||||
|
export interface TimePickerValue {
|
||||||
|
value: string;
|
||||||
|
itemsIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDateTimeSelectionBase() {
|
||||||
|
const {
|
||||||
|
getAllMinWeekdayNames,
|
||||||
|
getAllMeridiemIndicators,
|
||||||
|
isLongDateMonthAfterYear,
|
||||||
|
isLongTime24HourFormat,
|
||||||
|
isLongTimeMeridiemIndicatorFirst,
|
||||||
|
isLongTimeHourTwoDigits,
|
||||||
|
isLongTimeMinuteTwoDigits,
|
||||||
|
isLongTimeSecondTwoDigits
|
||||||
|
} = useI18n();
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const is24Hour = ref<boolean>(isLongTime24HourFormat());
|
||||||
|
const isHourTwoDigits = ref<boolean>(isLongTimeHourTwoDigits());
|
||||||
|
const isMinuteTwoDigits = ref<boolean>(isLongTimeMinuteTwoDigits());
|
||||||
|
const isSecondTwoDigits = ref<boolean>(isLongTimeSecondTwoDigits());
|
||||||
|
const isMeridiemIndicatorFirst = ref<boolean>(isLongTimeMeridiemIndicatorFirst() || false);
|
||||||
|
|
||||||
|
const yearRange = ref<number[]>([
|
||||||
|
2000,
|
||||||
|
getCurrentYear() + 1
|
||||||
|
]);
|
||||||
|
|
||||||
|
const meridiemItems = computed<NameValue[]>(() => getAllMeridiemIndicators());
|
||||||
|
|
||||||
|
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
|
||||||
|
const dayNames = computed<string[]>(() => arrangeArrayWithNewStartIndex(getAllMinWeekdayNames(), firstDayOfWeek.value));
|
||||||
|
const isYearFirst = computed<boolean>(() => isLongDateMonthAfterYear());
|
||||||
|
|
||||||
|
function getDisplayTimeValue(value: number, forceTwoDigits: boolean): string {
|
||||||
|
if (forceTwoDigits && value < 10) {
|
||||||
|
return `0${value}`;
|
||||||
|
} else {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateAllHours(count: number, forceTwoDigits: boolean): TimePickerValue[] {
|
||||||
|
const ret: TimePickerValue[] = [];
|
||||||
|
const startHour = is24Hour.value ? 0 : 1;
|
||||||
|
const endHour = is24Hour.value ? 23 : 11;
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
if (!is24Hour.value) {
|
||||||
|
ret.push({
|
||||||
|
value: '12',
|
||||||
|
itemsIndex: i
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = startHour; j <= endHour; j++) {
|
||||||
|
ret.push({
|
||||||
|
value: getDisplayTimeValue(j, forceTwoDigits),
|
||||||
|
itemsIndex: i
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateAllMinutesOrSeconds(count: number, forceTwoDigits: boolean): TimePickerValue[] {
|
||||||
|
const ret: TimePickerValue[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
for (let j = 0; j < 60; j++) {
|
||||||
|
ret.push({
|
||||||
|
value: getDisplayTimeValue(j, forceTwoDigits),
|
||||||
|
itemsIndex: i
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// states
|
||||||
|
is24Hour,
|
||||||
|
isHourTwoDigits,
|
||||||
|
isMinuteTwoDigits,
|
||||||
|
isSecondTwoDigits,
|
||||||
|
isMeridiemIndicatorFirst,
|
||||||
|
yearRange,
|
||||||
|
// computed
|
||||||
|
meridiemItems,
|
||||||
|
firstDayOfWeek,
|
||||||
|
dayNames,
|
||||||
|
isYearFirst,
|
||||||
|
// functions
|
||||||
|
getDisplayTimeValue,
|
||||||
|
generateAllHours,
|
||||||
|
generateAllMinutesOrSeconds
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,10 +12,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #no-data>
|
<template #no-data>
|
||||||
<vue-date-picker inline vertical time-picker-inline enable-seconds auto-apply
|
<vue-date-picker inline vertical enable-seconds auto-apply
|
||||||
ref="datepicker"
|
ref="datepicker"
|
||||||
month-name-format="long"
|
month-name-format="long"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
|
:enable-time-picker="false"
|
||||||
:dark="isDarkMode"
|
:dark="isDarkMode"
|
||||||
:week-start="firstDayOfWeek"
|
:week-start="firstDayOfWeek"
|
||||||
:year-range="yearRange"
|
:year-range="yearRange"
|
||||||
@@ -33,29 +34,82 @@
|
|||||||
<button class="dp__pm_am_button" tabindex="0" @click="toggle">{{ tt(`datetime.${value}.content`) }}</button>
|
<button class="dp__pm_am_button" tabindex="0" @click="toggle">{{ tt(`datetime.${value}.content`) }}</button>
|
||||||
</template>
|
</template>
|
||||||
</vue-date-picker>
|
</vue-date-picker>
|
||||||
|
<div class="date-time-select-time-picker-container">
|
||||||
|
<v-btn class="px-3" color="primary" variant="flat"
|
||||||
|
v-if="!is24Hour && isMeridiemIndicatorFirst"
|
||||||
|
@click="toggleMeridiemIndicator">
|
||||||
|
{{ tt(`datetime.${currentMeridiemIndicator}.content`) }}
|
||||||
|
</v-btn>
|
||||||
|
<v-autocomplete eager ref="hourInput"
|
||||||
|
density="compact"
|
||||||
|
max-width="70px"
|
||||||
|
item-title="value"
|
||||||
|
item-value="value"
|
||||||
|
auto-select-first="exact"
|
||||||
|
:items="hourItems"
|
||||||
|
:hide-no-data="true"
|
||||||
|
v-model="currentHour"
|
||||||
|
@update:focused="onFocused(hourInput, $event)"
|
||||||
|
@keydown="onKeyDown('hour', $event)"
|
||||||
|
/>
|
||||||
|
<span>:</span>
|
||||||
|
<v-autocomplete eager ref="minuteInput"
|
||||||
|
density="compact"
|
||||||
|
max-width="70px"
|
||||||
|
item-title="value"
|
||||||
|
item-value="value"
|
||||||
|
auto-select-first="exact"
|
||||||
|
:items="minuteItems"
|
||||||
|
:hide-no-data="true"
|
||||||
|
v-model="currentMinute"
|
||||||
|
@update:focused="onFocused(minuteInput, $event)"
|
||||||
|
@keydown="onKeyDown('minute', $event)"
|
||||||
|
/>
|
||||||
|
<span>:</span>
|
||||||
|
<v-autocomplete eager ref="secondInput"
|
||||||
|
density="compact"
|
||||||
|
max-width="70px"
|
||||||
|
item-title="value"
|
||||||
|
item-value="value"
|
||||||
|
auto-select-first="exact"
|
||||||
|
:items="secondItems"
|
||||||
|
:hide-no-data="true"
|
||||||
|
v-model="currentSecond"
|
||||||
|
@update:focused="onFocused(secondInput, $event)"
|
||||||
|
@keydown="onKeyDown('second', $event)"
|
||||||
|
/>
|
||||||
|
<v-btn class="px-3" color="primary" variant="flat"
|
||||||
|
v-if="!is24Hour && !isMeridiemIndicatorFirst"
|
||||||
|
@click="toggleMeridiemIndicator">
|
||||||
|
{{ tt(`datetime.${currentMeridiemIndicator}.content`) }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</v-select>
|
</v-select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { VAutocomplete } from 'vuetify/components/VAutocomplete';
|
||||||
|
|
||||||
|
import { computed, useTemplateRef, nextTick } from 'vue';
|
||||||
import { useTheme } from 'vuetify';
|
import { useTheme } from 'vuetify';
|
||||||
|
|
||||||
import { useI18n } from '@/locales/helpers.ts';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
import { type TimePickerValue, useDateTimeSelectionBase } from '@/components/base/DateTimeSelectionBase.ts';
|
||||||
|
|
||||||
import { useUserStore } from '@/stores/user.ts';
|
|
||||||
|
|
||||||
import { type WeekDayValue } from '@/core/datetime.ts';
|
|
||||||
import { ThemeType } from '@/core/theme.ts';
|
import { ThemeType } from '@/core/theme.ts';
|
||||||
import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
|
import { MeridiemIndicator } from '@/core/datetime.ts';
|
||||||
import {
|
import {
|
||||||
getCurrentYear,
|
getHourIn12HourFormat,
|
||||||
getTimezoneOffsetMinutes,
|
getTimezoneOffsetMinutes,
|
||||||
getBrowserTimezoneOffsetMinutes,
|
getBrowserTimezoneOffsetMinutes,
|
||||||
getLocalDatetimeFromUnixTime,
|
getLocalDatetimeFromUnixTime,
|
||||||
getActualUnixTimeForStore,
|
getActualUnixTimeForStore,
|
||||||
getUnixTime
|
getUnixTime,
|
||||||
|
getAMOrPM,
|
||||||
|
getCombinedDateAndTimeValues
|
||||||
} from '@/lib/datetime.ts';
|
} from '@/lib/datetime.ts';
|
||||||
|
import { setChildInputFocus } from '@/lib/ui/desktop.ts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: number;
|
modelValue: number;
|
||||||
@@ -70,14 +124,30 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { tt, getAllMinWeekdayNames, getMonthShortName, formatUnixTimeToLongDateTime, isLongDateMonthAfterYear, isLongTime24HourFormat } = useI18n();
|
const {
|
||||||
|
tt,
|
||||||
|
getMonthShortName,
|
||||||
|
formatUnixTimeToLongDateTime
|
||||||
|
} = useI18n();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const {
|
||||||
|
is24Hour,
|
||||||
|
isHourTwoDigits,
|
||||||
|
isMinuteTwoDigits,
|
||||||
|
isSecondTwoDigits,
|
||||||
|
isMeridiemIndicatorFirst,
|
||||||
|
yearRange,
|
||||||
|
firstDayOfWeek,
|
||||||
|
dayNames,
|
||||||
|
isYearFirst,
|
||||||
|
getDisplayTimeValue,
|
||||||
|
generateAllHours,
|
||||||
|
generateAllMinutesOrSeconds
|
||||||
|
} = useDateTimeSelectionBase();
|
||||||
|
|
||||||
const yearRange = ref<number[]>([
|
const hourInput = useTemplateRef<VAutocomplete>('hourInput');
|
||||||
2000,
|
const minuteInput = useTemplateRef<VAutocomplete>('minuteInput');
|
||||||
getCurrentYear() + 1
|
const secondInput = useTemplateRef<VAutocomplete>('secondInput');
|
||||||
]);
|
|
||||||
|
|
||||||
const dateTime = computed<Date>({
|
const dateTime = computed<Date>({
|
||||||
get: () => {
|
get: () => {
|
||||||
@@ -95,12 +165,155 @@ const dateTime = computed<Date>({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hourItems = computed<TimePickerValue[]>(() => generateAllHours(1, isHourTwoDigits.value));
|
||||||
|
const minuteItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(1, isMinuteTwoDigits.value));
|
||||||
|
const secondItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(1, isSecondTwoDigits.value));
|
||||||
|
|
||||||
|
const currentMeridiemIndicator = computed<string>({
|
||||||
|
get: () => {
|
||||||
|
return getAMOrPM(dateTime.value.getHours())
|
||||||
|
},
|
||||||
|
set: (value: string) => {
|
||||||
|
if (value !== MeridiemIndicator.AM.name && value !== MeridiemIndicator.PM.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, currentMinute.value, currentSecond.value, value, is24Hour.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const currentHour = computed<string>({
|
||||||
|
get: () => {
|
||||||
|
return getDisplayTimeValue(is24Hour.value ? dateTime.value.getHours() : getHourIn12HourFormat(dateTime.value.getHours()), isHourTwoDigits.value);
|
||||||
|
},
|
||||||
|
set: (value: string) => {
|
||||||
|
const hour = parseInt(value);
|
||||||
|
|
||||||
|
if (isNaN(hour) || hour < 0 || (is24Hour.value ? hour > 23 : hour > 12)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, value, currentMinute.value, currentSecond.value, currentMeridiemIndicator.value, is24Hour.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const currentMinute = computed<string>({
|
||||||
|
get: () => {
|
||||||
|
return getDisplayTimeValue(dateTime.value.getMinutes(), isMinuteTwoDigits.value);
|
||||||
|
},
|
||||||
|
set: (value: string) => {
|
||||||
|
const minute = parseInt(value);
|
||||||
|
|
||||||
|
if (isNaN(minute) || minute < 0 || minute > 59) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, value, currentSecond.value, currentMeridiemIndicator.value, is24Hour.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const currentSecond = computed<string>({
|
||||||
|
get: () => {
|
||||||
|
return getDisplayTimeValue(dateTime.value.getSeconds(), isSecondTwoDigits.value);
|
||||||
|
},
|
||||||
|
set: (value: string) => {
|
||||||
|
const second = parseInt(value);
|
||||||
|
|
||||||
|
if (isNaN(second) || second < 0 || second > 59) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, currentMinute.value, value, currentMeridiemIndicator.value, is24Hour.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
|
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
|
||||||
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
|
|
||||||
const dayNames = computed<string[]>(() => arrangeArrayWithNewStartIndex(getAllMinWeekdayNames(), firstDayOfWeek.value));
|
|
||||||
const isYearFirst = computed<boolean>(() => isLongDateMonthAfterYear());
|
|
||||||
const is24Hour = computed<boolean>(() => isLongTime24HourFormat());
|
|
||||||
const displayTime = computed<string>(() => formatUnixTimeToLongDateTime(getActualUnixTimeForStore(getUnixTime(dateTime.value), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())));
|
const displayTime = computed<string>(() => formatUnixTimeToLongDateTime(getActualUnixTimeForStore(getUnixTime(dateTime.value), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())));
|
||||||
|
|
||||||
|
function toggleMeridiemIndicator(): void {
|
||||||
|
if (currentMeridiemIndicator.value === MeridiemIndicator.AM.name) {
|
||||||
|
currentMeridiemIndicator.value = MeridiemIndicator.PM.name;
|
||||||
|
} else {
|
||||||
|
currentMeridiemIndicator.value = MeridiemIndicator.AM.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFocused(input: VAutocomplete | null | undefined, focused: boolean): void {
|
||||||
|
if (input && focused) {
|
||||||
|
nextTick(() => {
|
||||||
|
setChildInputFocus(input?.$el, 'input');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(type: string, e: KeyboardEvent): void {
|
||||||
|
if (e.altKey || e.ctrlKey || e.metaKey || (e.key.indexOf('F') === 0 && (e.key.length === 2 || e.key.length === 3))
|
||||||
|
|| e.key === 'ArrowLeft' || e.key === 'ArrowRight'
|
||||||
|
|| e.key === 'Home' || e.key === 'End'
|
||||||
|
|| e.key === 'Backspace' || e.key === 'Delete' || e.key === 'Del') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key.length === 1 && '0' <= e.key && e.key <= '9') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = '';
|
||||||
|
|
||||||
|
if (e.target instanceof HTMLInputElement) {
|
||||||
|
const input = e.target as HTMLInputElement;
|
||||||
|
value = input.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Tab' || e.key === 'Enter') {
|
||||||
|
if (type === 'hour') {
|
||||||
|
currentHour.value = value;
|
||||||
|
} else if (type === 'minute') {
|
||||||
|
currentMinute.value = value;
|
||||||
|
} else if (type === 'second') {
|
||||||
|
currentSecond.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.shiftKey && e.key === 'Tab') {
|
||||||
|
if (type === 'minute') {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setChildInputFocus(hourInput.value?.$el, 'input');
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
} else if (type === 'second') {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setChildInputFocus(minuteInput.value?.$el, 'input');
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!e.shiftKey && (e.key === 'Tab' || e.key === 'Enter')) {
|
||||||
|
if (type === 'hour') {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setChildInputFocus(minuteInput.value?.$el, 'input');
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
} else if (type === 'minute') {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setChildInputFocus(secondInput.value?.$el, 'input');
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -111,4 +324,36 @@ const displayTime = computed<string>(() => formatUnixTimeToLongDateTime(getActua
|
|||||||
.date-time-select-menu .dp__menu {
|
.date-time-select-menu .dp__menu {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date-time-select-time-picker-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--dp-menu-padding);
|
||||||
|
padding-bottom: 0;
|
||||||
|
column-gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-time-select-time-picker-container .v-autocomplete.v-input--density-compact {
|
||||||
|
--v-input-control-height: 38px;
|
||||||
|
--v-field-input-padding-top: 4px;
|
||||||
|
--v-field-input-padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-time-select-time-picker-container .v-autocomplete.v-input--density-compact .v-field {
|
||||||
|
--v-field-padding-start: 12px;
|
||||||
|
--v-field-padding-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-time-select-time-picker-container .v-autocomplete.v-input--density-compact .v-field__input {
|
||||||
|
min-height: 38px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-time-select-time-picker-container .v-autocomplete.v-input--density-compact .v-field__append-inner .v-autocomplete__menu-icon {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-time-select-time-picker-container .v-autocomplete .v-field--appended {
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -120,13 +120,11 @@ import VueDatePicker from '@vuepic/vue-datepicker';
|
|||||||
|
|
||||||
import { useI18n } from '@/locales/helpers.ts';
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
import { useI18nUIComponents } from '@/lib/ui/mobile.ts';
|
import { useI18nUIComponents } from '@/lib/ui/mobile.ts';
|
||||||
|
import { type TimePickerValue, useDateTimeSelectionBase } from '@/components/base/DateTimeSelectionBase.ts';
|
||||||
|
|
||||||
import { useEnvironmentsStore } from '@/stores/environment.ts';
|
import { useEnvironmentsStore } from '@/stores/environment.ts';
|
||||||
import { useUserStore } from '@/stores/user.ts';
|
|
||||||
|
|
||||||
import { type NameValue } from '@/core/base.ts';
|
import { isDefined } from '@/lib/common.ts';
|
||||||
import { type WeekDayValue } from '@/core/datetime.ts';
|
|
||||||
import { isDefined, arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
|
|
||||||
import {
|
import {
|
||||||
getHourIn12HourFormat,
|
getHourIn12HourFormat,
|
||||||
getTimezoneOffsetMinutes,
|
getTimezoneOffsetMinutes,
|
||||||
@@ -134,7 +132,6 @@ import {
|
|||||||
getLocalDatetimeFromUnixTime,
|
getLocalDatetimeFromUnixTime,
|
||||||
getActualUnixTimeForStore,
|
getActualUnixTimeForStore,
|
||||||
getCurrentUnixTime,
|
getCurrentUnixTime,
|
||||||
getCurrentYear,
|
|
||||||
getUnixTime,
|
getUnixTime,
|
||||||
getAMOrPM,
|
getAMOrPM,
|
||||||
getCombinedDateAndTimeValues
|
getCombinedDateAndTimeValues
|
||||||
@@ -142,11 +139,6 @@ import {
|
|||||||
|
|
||||||
type VueDatePickerType = InstanceType<typeof VueDatePicker>;
|
type VueDatePickerType = InstanceType<typeof VueDatePicker>;
|
||||||
|
|
||||||
interface TimePickerValue {
|
|
||||||
value: string;
|
|
||||||
itemsIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: number;
|
modelValue: number;
|
||||||
initMode?: string;
|
initMode?: string;
|
||||||
@@ -160,85 +152,79 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
tt,
|
tt,
|
||||||
getAllMinWeekdayNames,
|
|
||||||
getAllMeridiemIndicators,
|
|
||||||
getMonthShortName,
|
getMonthShortName,
|
||||||
formatUnixTimeToLongDateTime,
|
formatUnixTimeToLongDateTime
|
||||||
isLongDateMonthAfterYear,
|
|
||||||
isLongTime24HourFormat,
|
|
||||||
isLongTimeMeridiemIndicatorFirst,
|
|
||||||
isLongTimeHourTwoDigits,
|
|
||||||
isLongTimeMinuteTwoDigits,
|
|
||||||
isLongTimeSecondTwoDigits
|
|
||||||
} = useI18n();
|
} = useI18n();
|
||||||
const { showToast } = useI18nUIComponents();
|
const { showToast } = useI18nUIComponents();
|
||||||
|
|
||||||
const environmentsStore = useEnvironmentsStore();
|
const {
|
||||||
const userStore = useUserStore();
|
is24Hour,
|
||||||
|
isHourTwoDigits,
|
||||||
|
isMinuteTwoDigits,
|
||||||
|
isSecondTwoDigits,
|
||||||
|
isMeridiemIndicatorFirst,
|
||||||
|
yearRange,
|
||||||
|
meridiemItems,
|
||||||
|
firstDayOfWeek,
|
||||||
|
dayNames,
|
||||||
|
isYearFirst,
|
||||||
|
getDisplayTimeValue,
|
||||||
|
generateAllHours,
|
||||||
|
generateAllMinutesOrSeconds
|
||||||
|
} = useDateTimeSelectionBase();
|
||||||
|
|
||||||
|
const environmentsStore = useEnvironmentsStore();
|
||||||
|
|
||||||
|
const datetimepicker = useTemplateRef<VueDatePickerType>('datetimepicker');
|
||||||
|
|
||||||
const is24Hour: boolean = isLongTime24HourFormat();
|
|
||||||
const isHourTwoDigits: boolean = isLongTimeHourTwoDigits();
|
|
||||||
const isMinuteTwoDigits: boolean = isLongTimeMinuteTwoDigits();
|
|
||||||
const isSecondTwoDigits: boolean = isLongTimeSecondTwoDigits();
|
|
||||||
const isMeridiemIndicatorFirst: boolean = isLongTimeMeridiemIndicatorFirst() || false;
|
|
||||||
let resetTimePickerItemPositionItemsClass: string | undefined = undefined;
|
let resetTimePickerItemPositionItemsClass: string | undefined = undefined;
|
||||||
let resetTimePickerItemPositionItemClass: string | undefined = undefined;
|
let resetTimePickerItemPositionItemClass: string | undefined = undefined;
|
||||||
let resetTimePickerItemPositionItemsLastOffsetTop: number | undefined = undefined;
|
let resetTimePickerItemPositionItemsLastOffsetTop: number | undefined = undefined;
|
||||||
let resetTimePickerItemPositionCheckedFrames: number | undefined = undefined;
|
let resetTimePickerItemPositionCheckedFrames: number | undefined = undefined;
|
||||||
|
|
||||||
const mode = ref<string>(props.initMode || 'time');
|
const mode = ref<string>(props.initMode || 'time');
|
||||||
const yearRange = ref<number[]>([
|
|
||||||
2000,
|
|
||||||
getCurrentYear() + 1
|
|
||||||
]);
|
|
||||||
const dateTime = ref<Date>(getLocalDatetimeFromUnixTime(props.modelValue || getCurrentUnixTime()));
|
const dateTime = ref<Date>(getLocalDatetimeFromUnixTime(props.modelValue || getCurrentUnixTime()));
|
||||||
const timePickerContainerHeight = ref<number | undefined>(undefined);
|
const timePickerContainerHeight = ref<number | undefined>(undefined);
|
||||||
const timePickerItemHeight = ref<number | undefined>(undefined);
|
const timePickerItemHeight = ref<number | undefined>(undefined);
|
||||||
|
|
||||||
const datetimepicker = useTemplateRef<VueDatePickerType>('datetimepicker');
|
const hourItems = computed<TimePickerValue[]>(() => generateAllHours(3, isHourTwoDigits.value));
|
||||||
|
const minuteItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(3, isMinuteTwoDigits.value));
|
||||||
|
const secondItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(3, isSecondTwoDigits.value));
|
||||||
|
|
||||||
const currentMeridiemIndicator = computed<string>({
|
const currentMeridiemIndicator = computed<string>({
|
||||||
get: () => {
|
get: () => {
|
||||||
return getAMOrPM(dateTime.value.getHours())
|
return getAMOrPM(dateTime.value.getHours())
|
||||||
},
|
},
|
||||||
set: (value: string) => {
|
set: (value: string) => {
|
||||||
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, currentMinute.value, currentSecond.value, value, is24Hour);
|
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, currentMinute.value, currentSecond.value, value, is24Hour.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const currentHour = computed<string>({
|
const currentHour = computed<string>({
|
||||||
get: () => {
|
get: () => {
|
||||||
return getDisplayTimeValue(is24Hour ? dateTime.value.getHours() : getHourIn12HourFormat(dateTime.value.getHours()), isHourTwoDigits);
|
return getDisplayTimeValue(is24Hour.value ? dateTime.value.getHours() : getHourIn12HourFormat(dateTime.value.getHours()), isHourTwoDigits.value);
|
||||||
},
|
},
|
||||||
set: (value: string) => {
|
set: (value: string) => {
|
||||||
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, value, currentMinute.value, currentSecond.value, currentMeridiemIndicator.value, is24Hour);
|
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, value, currentMinute.value, currentSecond.value, currentMeridiemIndicator.value, is24Hour.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const currentMinute = computed<string>({
|
const currentMinute = computed<string>({
|
||||||
get: () => {
|
get: () => {
|
||||||
return getDisplayTimeValue(dateTime.value.getMinutes(), isMinuteTwoDigits);
|
return getDisplayTimeValue(dateTime.value.getMinutes(), isMinuteTwoDigits.value);
|
||||||
},
|
},
|
||||||
set: (value: string) => {
|
set: (value: string) => {
|
||||||
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, value, currentSecond.value, currentMeridiemIndicator.value, is24Hour);
|
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, value, currentSecond.value, currentMeridiemIndicator.value, is24Hour.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const currentSecond = computed<string>({
|
const currentSecond = computed<string>({
|
||||||
get: () => {
|
get: () => {
|
||||||
return getDisplayTimeValue(dateTime.value.getSeconds(), isSecondTwoDigits);
|
return getDisplayTimeValue(dateTime.value.getSeconds(), isSecondTwoDigits.value);
|
||||||
},
|
},
|
||||||
set: (value: string) => {
|
set: (value: string) => {
|
||||||
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, currentMinute.value, value, currentMeridiemIndicator.value, is24Hour);
|
dateTime.value = getCombinedDateAndTimeValues(dateTime.value, currentHour.value, currentMinute.value, value, currentMeridiemIndicator.value, is24Hour.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const hourItems = computed<TimePickerValue[]>(() => generateAllHours(3, isHourTwoDigits));
|
|
||||||
const minuteItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(3, isMinuteTwoDigits));
|
|
||||||
const secondItems = computed<TimePickerValue[]>(() => generateAllMinutesOrSeconds(3, isSecondTwoDigits));
|
|
||||||
const meridiemItems = computed<NameValue[]>(() => getAllMeridiemIndicators());
|
|
||||||
|
|
||||||
const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false);
|
const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false);
|
||||||
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
|
|
||||||
const dayNames = computed<string[]>(() => arrangeArrayWithNewStartIndex(getAllMinWeekdayNames(), firstDayOfWeek.value));
|
|
||||||
const isYearFirst = computed<boolean>(() => isLongDateMonthAfterYear());
|
|
||||||
const displayTime = computed<string>(() => formatUnixTimeToLongDateTime(getActualUnixTimeForStore(getUnixTime(dateTime.value), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())));
|
const displayTime = computed<string>(() => formatUnixTimeToLongDateTime(getActualUnixTimeForStore(getUnixTime(dateTime.value), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())));
|
||||||
const switchButtonTitle = computed<string>(() => mode.value === 'time' ? 'Date' : 'Time');
|
const switchButtonTitle = computed<string>(() => mode.value === 'time' ? 'Date' : 'Time');
|
||||||
|
|
||||||
@@ -274,53 +260,6 @@ function confirm(): void {
|
|||||||
emit('update:show', false);
|
emit('update:show', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayTimeValue(value: number, forceTwoDigits: boolean): string {
|
|
||||||
if (forceTwoDigits && value < 10) {
|
|
||||||
return `0${value}`;
|
|
||||||
} else {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateAllHours(count: number, forceTwoDigits: boolean): TimePickerValue[] {
|
|
||||||
const ret: TimePickerValue[] = [];
|
|
||||||
const startHour = is24Hour ? 0 : 1;
|
|
||||||
const endHour = is24Hour ? 23 : 11;
|
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
if (!is24Hour) {
|
|
||||||
ret.push({
|
|
||||||
value: '12',
|
|
||||||
itemsIndex: i
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = startHour; j <= endHour; j++) {
|
|
||||||
ret.push({
|
|
||||||
value: getDisplayTimeValue(j, forceTwoDigits),
|
|
||||||
itemsIndex: i
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateAllMinutesOrSeconds(count: number, forceTwoDigits: boolean): TimePickerValue[] {
|
|
||||||
const ret: TimePickerValue[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
for (let j = 0; j < 60; j++) {
|
|
||||||
ret.push({
|
|
||||||
value: getDisplayTimeValue(j, forceTwoDigits),
|
|
||||||
itemsIndex: i
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTimerPickerItemStyle(textualValue: string, textualCurrentValue: string, itemsIndex: number, values: TimePickerValue[]): string {
|
function getTimerPickerItemStyle(textualValue: string, textualCurrentValue: string, itemsIndex: number, values: TimePickerValue[]): string {
|
||||||
if (!timePickerContainerHeight.value || !timePickerItemHeight.value) {
|
if (!timePickerContainerHeight.value || !timePickerItemHeight.value) {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -48,6 +48,22 @@ export function getCssValue(element: HTMLElement | null, name: string): string {
|
|||||||
return computedStyle.getPropertyValue(name);
|
return computedStyle.getPropertyValue(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setChildInputFocus(parentEl: HTMLElement | undefined, childSelector: string): void {
|
||||||
|
if (!parentEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childElement = parentEl.querySelector(childSelector);
|
||||||
|
|
||||||
|
if (!childElement || !(childElement as HTMLInputElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childInput = (childElement as HTMLInputElement);
|
||||||
|
childInput.focus();
|
||||||
|
childInput.select();
|
||||||
|
}
|
||||||
|
|
||||||
export function scrollToSelectedItem(parentEl: HTMLElement | null | undefined, containerSelector: string | null, selectedItemSelector: string): void {
|
export function scrollToSelectedItem(parentEl: HTMLElement | null | undefined, containerSelector: string | null, selectedItemSelector: string): void {
|
||||||
if (!parentEl) {
|
if (!parentEl) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user