From ca4a798545aef5ad360c7b717c36ea6ca2ea2a18 Mon Sep 17 00:00:00 2001
From: sea <45450994+warmthsea@users.noreply.github.com>
Date: Sat, 6 Sep 2025 07:46:11 +0800
Subject: [PATCH] fix(components): [date-picker]: use array modelValue
`datetime` type debacle (#22033)
fix(components): [date-picker]: use array value datetime type debacle
---
.../__tests__/date-picker-panel.test.tsx | 18 +++++++
.../src/date-picker-com/panel-date-pick.vue | 24 +++++-----
.../date-picker/__tests__/date-picker.test.ts | 48 +++++++++++++++++++
packages/utils/__tests__/arrays.test.ts | 12 ++++-
packages/utils/arrays.ts | 4 ++
5 files changed, 93 insertions(+), 13 deletions(-)
diff --git a/packages/components/date-picker-panel/__tests__/date-picker-panel.test.tsx b/packages/components/date-picker-panel/__tests__/date-picker-panel.test.tsx
index 91ae3d6953..dc750ad1db 100644
--- a/packages/components/date-picker-panel/__tests__/date-picker-panel.test.tsx
+++ b/packages/components/date-picker-panel/__tests__/date-picker-panel.test.tsx
@@ -461,6 +461,24 @@ describe('DatePickerPanel', () => {
'6',
])
})
+
+ it('should handle array value without errors', async () => {
+ const value = ref(['2025-09-01'])
+ const wrapper = mount(() => (
+
+ ))
+ await nextTick()
+
+ const dateInput = wrapper.find(
+ '.el-date-picker__time-header > span:nth-child(1) input'
+ ).element as HTMLInputElement
+ expect(dateInput.value).toBe('2025-09-01')
+
+ const timeInput = wrapper.find(
+ '.el-date-picker__time-header > span:nth-child(2) input'
+ ).element as HTMLInputElement
+ expect(timeInput.value).toBe('00:00:00')
+ })
})
describe(':type="datetimerange"', () => {
diff --git a/packages/components/date-picker-panel/src/date-picker-com/panel-date-pick.vue b/packages/components/date-picker-panel/src/date-picker-com/panel-date-pick.vue
index 8ed9e36be8..8c8848e947 100644
--- a/packages/components/date-picker-panel/src/date-picker-com/panel-date-pick.vue
+++ b/packages/components/date-picker-panel/src/date-picker-com/panel-date-pick.vue
@@ -236,7 +236,7 @@ import {
extractTimeFormat,
} from '@element-plus/components/time-picker'
import { ElIcon } from '@element-plus/components/icon'
-import { isArray, isFunction } from '@element-plus/utils'
+import { extractFirst, isArray, isFunction } from '@element-plus/utils'
import { EVENT_CODE } from '@element-plus/constants'
import {
ArrowLeft,
@@ -347,8 +347,9 @@ const emit = (value: Dayjs | Dayjs[], ...args: any[]) => {
const handleDatePick = async (value: DateTableEmits, keepOpen?: boolean) => {
if (selectionMode.value === 'date') {
value = value as Dayjs
- let newDate = props.parsedValue
- ? (props.parsedValue as Dayjs)
+ const parsedDateValue = extractFirst(props.parsedValue)
+ let newDate = parsedDateValue
+ ? parsedDateValue
.year(value.year())
.month(value.month())
.date(value.date())
@@ -547,7 +548,7 @@ const onConfirm = () => {
emit(props.parsedValue as Dayjs[])
} else {
// deal with the scenario where: user opens the date time picker, then confirm without doing anything
- let result = props.parsedValue as Dayjs
+ let result = extractFirst(props.parsedValue)
if (!result) {
const defaultTimeD = dayjs(defaultTime).locale(lang.value)
const defaultValueD = getDefaultValue()
@@ -595,17 +596,15 @@ const dateFormat = computed(() => {
const visibleTime = computed(() => {
if (userInputTime.value) return userInputTime.value
if (!props.parsedValue && !defaultValue.value) return
- return ((props.parsedValue || innerDate.value) as Dayjs).format(
- timeFormat.value
- )
+ const dateValue = extractFirst(props.parsedValue) || innerDate.value
+ return dateValue.format(timeFormat.value)
})
const visibleDate = computed(() => {
if (userInputDate.value) return userInputDate.value
if (!props.parsedValue && !defaultValue.value) return
- return ((props.parsedValue || innerDate.value) as Dayjs).format(
- dateFormat.value
- )
+ const dateValue = extractFirst(props.parsedValue) || innerDate.value
+ return dateValue.format(dateFormat.value)
})
const timePickerVisible = ref(false)
@@ -629,8 +628,9 @@ const getUnits = (date: Dayjs) => {
const handleTimePick = (value: Dayjs, visible: boolean, first: boolean) => {
const { hour, minute, second } = getUnits(value)
- const newDate = props.parsedValue
- ? (props.parsedValue as Dayjs).hour(hour).minute(minute).second(second)
+ const parsedDateValue = extractFirst(props.parsedValue)
+ const newDate = parsedDateValue
+ ? parsedDateValue.hour(hour).minute(minute).second(second)
: value
innerDate.value = newDate
emit(innerDate.value, true)
diff --git a/packages/components/date-picker/__tests__/date-picker.test.ts b/packages/components/date-picker/__tests__/date-picker.test.ts
index e4d0211676..69196255a0 100644
--- a/packages/components/date-picker/__tests__/date-picker.test.ts
+++ b/packages/components/date-picker/__tests__/date-picker.test.ts
@@ -1096,6 +1096,54 @@ describe('DatePicker', () => {
expect(changeHandler).toHaveBeenCalledTimes(1)
})
})
+
+ it('should handle array value for datetime type without errors', async () => {
+ const wrapper = _mount(
+ ``,
+ () => ({ value: ['2025-09-01'] })
+ )
+
+ await nextTick()
+ const input = wrapper.find('input')
+ expect(input.element.value).toBe('2025-09-01 00:00:00')
+
+ await input.trigger('focus')
+ await nextTick()
+
+ const dateInput = document.querySelector(
+ '.el-date-picker__time-header > span:nth-child(1) input'
+ ) as HTMLInputElement
+ const timeInput = document.querySelector(
+ '.el-date-picker__time-header > span:nth-child(2) input'
+ ) as HTMLInputElement
+
+ expect(dateInput?.value).toBe('2025-09-01')
+ expect(timeInput?.value).toBe('00:00:00')
+ })
+
+ it('should convert array value to proper format when changed', async () => {
+ const wrapper = _mount(
+ ``,
+ () => ({ value: ['2025-09-04'] })
+ )
+
+ const originalValue = wrapper.vm.value
+ expect(originalValue).toEqual(['2025-09-04'])
+
+ const input = wrapper.find('input')
+ await input.trigger('focus')
+ await nextTick()
+
+ const dateCell = document.querySelector('.el-date-table td.available')
+ await (dateCell as HTMLElement)?.click()
+ await nextTick()
+
+ expect(wrapper.vm.value).not.toEqual(['2025-09-04'])
+ expect(Array.isArray(wrapper.vm.value)).toBe(false)
+ })
})
describe('DatePicker Navigation', () => {
diff --git a/packages/utils/__tests__/arrays.test.ts b/packages/utils/__tests__/arrays.test.ts
index e18dd8f527..d51d0dd743 100644
--- a/packages/utils/__tests__/arrays.test.ts
+++ b/packages/utils/__tests__/arrays.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { castArray as lodashCastArray } from 'lodash-unified'
-import { castArray, ensureArray, unique } from '..'
+import { castArray, ensureArray, extractFirst, unique } from '..'
describe('arrays', () => {
it('unique should work', () => {
@@ -17,4 +17,14 @@ describe('arrays', () => {
it('re-export ensureArray', () => {
expect(ensureArray).toBe(lodashCastArray)
})
+
+ it('extractFirst should work', () => {
+ expect(extractFirst([1, 2, 3])).toBe(1)
+ expect(extractFirst(['a', 'b', 'c'])).toBe('a')
+ expect(extractFirst(42)).toBe(42)
+ expect(extractFirst('hello')).toBe('hello')
+ expect(extractFirst(null)).toBe(null)
+ expect(extractFirst(undefined)).toBe(undefined)
+ expect(extractFirst([])).toBe(undefined)
+ })
})
diff --git a/packages/utils/arrays.ts b/packages/utils/arrays.ts
index 892096434f..6eeddfa4c3 100644
--- a/packages/utils/arrays.ts
+++ b/packages/utils/arrays.ts
@@ -2,6 +2,10 @@ import { isArray } from './types'
export const unique = (arr: T[]) => [...new Set(arr)]
+export const extractFirst = (arr: T | T[]): T => {
+ return isArray(arr) ? arr[0] : arr
+}
+
type Many = T | ReadonlyArray
// TODO: rename to `ensureArray`
/** like `_.castArray`, except falsy value returns empty array. */