diff --git a/packages/nc-gui/components/cell/DatePicker.vue b/packages/nc-gui/components/cell/DatePicker.vue index ab5b0cd2ca..a21fab44e8 100644 --- a/packages/nc-gui/components/cell/DatePicker.vue +++ b/packages/nc-gui/components/cell/DatePicker.vue @@ -43,6 +43,8 @@ const isClearedInputMode = ref(false) const open = ref(false) +const tempDate = ref() + const localState = computed({ get() { if (!modelValue || isClearedInputMode.value) { @@ -56,7 +58,9 @@ const localState = computed({ const format = picker.value === 'month' ? dateFormat : 'YYYY-MM-DD' - return dayjs(/^\d+$/.test(modelValue) ? +modelValue : modelValue, format) + const value = dayjs(/^\d+$/.test(modelValue) ? +modelValue : modelValue, format) + + return value }, set(val?: dayjs.Dayjs) { isClearedInputMode.value = false @@ -79,20 +83,36 @@ const localState = computed({ }, }) +watchEffect(() => { + if (localState.value) { + tempDate.value = localState.value + } +}) + const randomClass = `picker_${Math.floor(Math.random() * 99999)}` onClickOutside(datePickerRef, (e) => { - if ((e.target as HTMLElement)?.closest(`.${randomClass}`)) return + if ((e.target as HTMLElement)?.closest(`.${randomClass}, .nc-${randomClass}`)) return + datePickerRef.value?.blur?.() open.value = false }) const onBlur = (e) => { - if ((e?.relatedTarget as HTMLElement)?.closest(`.${randomClass}`)) return + if ( + (e?.relatedTarget as HTMLElement)?.closest(`.${randomClass}, .nc-${randomClass}`) || + (e?.target as HTMLElement)?.closest(`.${randomClass}, .nc-${randomClass}`) + ) { + return + } open.value = false } +const onFocus = () => { + open.value = true +} + watch( open, (next) => { @@ -165,14 +185,17 @@ const clickHandler = () => { cellClickHandler() } -const handleKeydown = (e: KeyboardEvent) => { - if (e.key !== 'Enter') { +const handleKeydown = (e: KeyboardEvent, _open?: boolean) => { + if (e.key !== 'Enter' && e.key !== 'Tab') { e.stopPropagation() } switch (e.key) { case 'Enter': - open.value = !open.value + e.preventDefault() + localState.value = tempDate.value + open.value = !_open + if (!open.value) { editable.value = false if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) { @@ -181,7 +204,7 @@ const handleKeydown = (e: KeyboardEvent) => { } return case 'Escape': - if (open.value) { + if (_open) { open.value = false editable.value = false if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) { @@ -193,9 +216,18 @@ const handleKeydown = (e: KeyboardEvent) => { datePickerRef.value?.blur?.() } + return + + case 'Tab': + open.value = false + if (isGrid.value) { + editable.value = false + datePickerRef.value?.blur?.() + } + return default: - if (!open.value && /^[0-9a-z]$/i.test(e.key)) { + if (!_open && /^[0-9a-z]$/i.test(e.key)) { open.value = true } } @@ -232,32 +264,87 @@ useEventListener(document, 'keydown', (e: KeyboardEvent) => { } } }) + +const handleUpdateValue = (e: Event) => { + const targetValue = (e.target as HTMLInputElement).value + if (!targetValue) { + tempDate.value = undefined + return + } + const value = dayjs(targetValue, dateFormat.value) + + if (value.isValid()) { + tempDate.value = value + } +} + +function handleSelectDate(value?: dayjs.Dayjs) { + tempDate.value = value + localState.value = value + open.value = false +} diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 7a5e3a542c..45fb5fb082 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -1,6 +1,6 @@ diff --git a/packages/nc-gui/components/cell/TimePicker.vue b/packages/nc-gui/components/cell/TimePicker.vue index d2b273099b..662303a163 100644 --- a/packages/nc-gui/components/cell/TimePicker.vue +++ b/packages/nc-gui/components/cell/TimePicker.vue @@ -1,6 +1,6 @@ - - diff --git a/packages/nc-gui/components/cell/YearPicker.vue b/packages/nc-gui/components/cell/YearPicker.vue index d2f3f50aca..9c3dd5e7d3 100644 --- a/packages/nc-gui/components/cell/YearPicker.vue +++ b/packages/nc-gui/components/cell/YearPicker.vue @@ -39,6 +39,8 @@ const { t } = useI18n() const open = ref(false) +const tempDate = ref() + const localState = computed({ get() { if (!modelValue || isClearedInputMode.value) { @@ -69,16 +71,27 @@ const localState = computed({ }, }) +watchEffect(() => { + if (localState.value) { + tempDate.value = localState.value + } +}) + const randomClass = `picker_${Math.floor(Math.random() * 99999)}` onClickOutside(datePickerRef, (e) => { - if ((e.target as HTMLElement)?.closest(`.${randomClass}`)) return + if ((e.target as HTMLElement)?.closest(`.${randomClass}, .nc-${randomClass}`)) return datePickerRef.value?.blur?.() open.value = false }) const onBlur = (e) => { - if ((e?.relatedTarget as HTMLElement)?.closest(`.${randomClass}`)) return + if ( + (e?.relatedTarget as HTMLElement)?.closest(`.${randomClass}, .nc-${randomClass}`) || + (e?.target as HTMLElement)?.closest(`.${randomClass}, .nc-${randomClass}`) + ) { + return + } open.value = false } @@ -136,14 +149,16 @@ const clickHandler = () => { open.value = active.value || editable.value } -const handleKeydown = (e: KeyboardEvent) => { - if (e.key !== 'Enter') { +const handleKeydown = (e: KeyboardEvent, _open?: boolean) => { + if (e.key !== 'Enter' && e.key !== 'Tab') { e.stopPropagation() } switch (e.key) { case 'Enter': - open.value = !open.value + e.preventDefault() + localState.value = tempDate.value + open.value = !_open if (!open.value) { editable.value = false if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) { @@ -151,9 +166,19 @@ const handleKeydown = (e: KeyboardEvent) => { } } + return + + case 'Tab': + open.value = false + + if (isGrid.value) { + editable.value = false + datePickerRef.value?.blur?.() + } + return case 'Escape': - if (open.value) { + if (_open) { open.value = false editable.value = false if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) { @@ -166,7 +191,7 @@ const handleKeydown = (e: KeyboardEvent) => { } return default: - if (!open.value && /^[0-9a-z]$/i.test(e.key)) { + if (!_open && /^[0-9a-z]$/i.test(e.key)) { open.value = true } } @@ -203,31 +228,77 @@ useEventListener(document, 'keydown', (e: KeyboardEvent) => { } } }) + +const handleUpdateValue = (e: Event) => { + const targetValue = (e.target as HTMLInputElement).value + if (!targetValue) { + tempDate.value = undefined + return + } + const value = dayjs(targetValue, 'YYYY') + + if (value.isValid()) { + tempDate.value = value + } +} + +function handleSelectDate(value?: dayjs.Dayjs) { + tempDate.value = value + localState.value = value + open.value = false +} diff --git a/packages/nc-gui/components/nc/DatePicker.vue b/packages/nc-gui/components/nc/DatePicker.vue new file mode 100644 index 0000000000..7ccc09b174 --- /dev/null +++ b/packages/nc-gui/components/nc/DatePicker.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/packages/nc-gui/components/nc/DateWeekSelector.vue b/packages/nc-gui/components/nc/DateWeekSelector.vue index 6e0b91b258..40505eb96c 100644 --- a/packages/nc-gui/components/nc/DateWeekSelector.vue +++ b/packages/nc-gui/components/nc/DateWeekSelector.vue @@ -13,19 +13,23 @@ interface Props { start: dayjs.Dayjs end: dayjs.Dayjs } | null + isCellInputField?: boolean + pickerType?: 'date' | 'time' | 'year' | 'month' } const props = withDefaults(defineProps(), { size: 'medium', selectedDate: null, isMondayFirst: true, - pageDate: dayjs(), + pageDate: () => dayjs(), isWeekPicker: false, - activeDates: [] as Array, + activeDates: () => [] as Array, selectedWeek: null, hideCalendar: false, + isCellInputField: false, + pickerType: 'date', }) -const emit = defineEmits(['update:selectedDate', 'update:pageDate', 'update:selectedWeek']) +const emit = defineEmits(['update:selectedDate', 'update:pageDate', 'update:selectedWeek', 'update:pickerType']) // Page date is the date we use to manage which month/date that is currently being displayed const pageDate = useVModel(props, 'pageDate', emit) @@ -35,6 +39,8 @@ const activeDates = useVModel(props, 'activeDates', emit) const selectedWeek = useVModel(props, 'selectedWeek', emit) +const pickerType = useVModel(props, 'pickerType', emit) + const days = computed(() => { if (props.isMondayFirst) { return ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] @@ -47,6 +53,14 @@ const currentMonthYear = computed(() => { return dayjs(pageDate.value).format('MMMM YYYY') }) +const currentMonth = computed(() => { + return dayjs(pageDate.value).format('MMMM') +}) + +const currentYear = computed(() => { + return dayjs(pageDate.value).format('YYYY') +}) + const selectWeek = (date: dayjs.Dayjs) => { const dayOffset = +props.isMondayFirst const dayOfWeek = (date.day() - dayOffset + 7) % 7 @@ -102,6 +116,9 @@ const isDayInPagedMonth = (date: dayjs.Dayjs) => { const handleSelectDate = (date: dayjs.Dayjs) => { if (props.isWeekPicker) { selectWeek(date) + } else if (props.isCellInputField) { + selectedDate.value = date + emit('update:selectedDate', date) } else { if (!isDayInPagedMonth(date)) { pageDate.value = date @@ -137,17 +154,30 @@ const paginate = (action: 'next' | 'prev') => { diff --git a/packages/nc-gui/components/nc/MonthYearSelector.vue b/packages/nc-gui/components/nc/MonthYearSelector.vue index 5ef4c7c397..c7823db432 100644 --- a/packages/nc-gui/components/nc/MonthYearSelector.vue +++ b/packages/nc-gui/components/nc/MonthYearSelector.vue @@ -6,20 +6,26 @@ interface Props { pageDate?: dayjs.Dayjs isYearPicker?: boolean hideCalendar?: boolean + isCellInputField?: boolean + pickerType?: 'date' | 'time' | 'year' | 'month' } const props = withDefaults(defineProps(), { selectedDate: null, - pageDate: dayjs(), + pageDate: () => dayjs(), isYearPicker: false, hideCalendar: false, + isCellInputField: false, + pickerType: 'date', }) -const emit = defineEmits(['update:selectedDate', 'update:pageDate']) +const emit = defineEmits(['update:selectedDate', 'update:pageDate', 'update:pickerType']) const pageDate = useVModel(props, 'pageDate', emit) const selectedDate = useVModel(props, 'selectedDate', emit) +const pickerType = useVModel(props, 'pickerType', emit) + const years = computed(() => { const date = pageDate.value const startOfYear = date.startOf('year') @@ -86,24 +92,41 @@ const compareYear = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => {