mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
fix(components): [date-picker] keep datetimerange close on value change (#21625)
* fix(components): [date-picker] keep datetimerange close on value change * chore: review past solution * test: add test case * docs: update onChange event * test: test
This commit is contained in:
@@ -201,7 +201,7 @@ Note, date time locale (month name, first day of the week ...) are also configur
|
||||
|
||||
| Name | Description | Type |
|
||||
| --------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| change | triggers when user confirms the value | ^[Function]`(val: typeof v-model) => void` |
|
||||
| change | triggers when user confirms the value or click outside | ^[Function]`(val: typeof v-model) => void` |
|
||||
| blur | triggers when Input blurs | ^[Function]`(e: FocusEvent) => void` |
|
||||
| focus | triggers when Input focuses | ^[Function]`(e: FocusEvent) => void` |
|
||||
| clear ^(2.7.7) | triggers when the clear icon is clicked in a clearable DatePicker | ^[Function]`() => void` |
|
||||
|
||||
@@ -122,7 +122,7 @@ datetime-picker/custom-icon
|
||||
|
||||
| Name | Description | Parameters |
|
||||
| --------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| change | triggers when user confirms the value | ^[Function]`(val: typeof v-model) => void` |
|
||||
| change | triggers when user confirms the value or click outside | ^[Function]`(val: typeof v-model) => void` |
|
||||
| blur | triggers when Input blurs | ^[Function]`(e: FocusEvent) => void` |
|
||||
| focus | triggers when Input focuses | ^[Function]`(e: FocusEvent) => void` |
|
||||
| clear ^(2.7.7) | triggers when the clear icon is clicked in a clearable DateTimePicker | ^[Function]`() => void` |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { nextTick } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import dayjs from 'dayjs'
|
||||
import { rAF } from '@element-plus/test-utils/tick'
|
||||
@@ -17,6 +17,8 @@ import { EVENT_CODE } from '@element-plus/constants'
|
||||
import { ElFormItem } from '@element-plus/components/form'
|
||||
import DatePicker from '../src/date-picker'
|
||||
|
||||
import type DatePickerRange from '../src/date-picker-com/panel-date-range.vue'
|
||||
|
||||
const _mount = (template: string, data = () => ({}), otherObj?) =>
|
||||
mount(
|
||||
{
|
||||
@@ -1970,7 +1972,7 @@ describe('DateRangePicker', () => {
|
||||
})
|
||||
|
||||
it('range, shows weekNumber', async () => {
|
||||
_mount(
|
||||
const wrapper = _mount(
|
||||
`<el-date-picker
|
||||
v-model="value"
|
||||
type="daterange"
|
||||
@@ -1978,6 +1980,10 @@ describe('DateRangePicker', () => {
|
||||
/>`,
|
||||
() => ({ value: [new Date(2025, 0, 1), new Date(2025, 1, 1)] })
|
||||
)
|
||||
const input = wrapper.find('input')
|
||||
input.trigger('blur')
|
||||
input.trigger('focus')
|
||||
|
||||
await nextTick()
|
||||
const weeks = document.querySelectorAll('td.week')
|
||||
expect(weeks.length).toBe(12)
|
||||
@@ -1996,6 +2002,85 @@ describe('DateRangePicker', () => {
|
||||
'10',
|
||||
])
|
||||
})
|
||||
it('should not be visible after input two dates', async () => {
|
||||
const onChange = vi.fn()
|
||||
const onUpdateModelValue = vi.fn()
|
||||
const wrapper = _mount(
|
||||
`<el-date-picker
|
||||
v-model="value"
|
||||
type="daterange"
|
||||
@change="onChange"
|
||||
@update:modelValue="onUpdateModelValue"
|
||||
/>`,
|
||||
() => ({
|
||||
value: [new Date(2025, 0, 1), new Date(2025, 1, 1)],
|
||||
onChange,
|
||||
onUpdateModelValue,
|
||||
})
|
||||
)
|
||||
const input = wrapper.find('input')
|
||||
await input.trigger('blur')
|
||||
await input.trigger('focus')
|
||||
|
||||
const rangePanelWrapper = wrapper.findComponent(
|
||||
'.el-date-range-picker'
|
||||
) as VueWrapper<InstanceType<typeof DatePickerRange>>
|
||||
expect(rangePanelWrapper.exists()).toBe(true)
|
||||
expect(rangePanelWrapper.vm.visible).toBe(true)
|
||||
const cells = document.querySelectorAll('.available .el-date-table-cell')
|
||||
;(cells[0] as HTMLElement).click()
|
||||
await flushPromises()
|
||||
expect(onChange).not.toHaveBeenCalled()
|
||||
expect(onUpdateModelValue).not.toHaveBeenCalled()
|
||||
;(cells[1] as HTMLElement).click()
|
||||
await flushPromises()
|
||||
|
||||
expect(onChange).toHaveBeenCalledOnce()
|
||||
expect(onUpdateModelValue).toHaveBeenCalledOnce()
|
||||
expect(rangePanelWrapper.vm.visible).toBe(false)
|
||||
})
|
||||
|
||||
it('should not trigger popper dropdown on dynamic assignment', async () => {
|
||||
const spy = vi.fn()
|
||||
const baseValue = [new Date(2025, 0, 1), new Date(2025, 0, 2)]
|
||||
const newVal = [new Date(2025, 0, 3), new Date(2025, 0, 4)]
|
||||
const wrapper = _mount(
|
||||
`<el-date-picker
|
||||
v-model="value"
|
||||
type="daterange"
|
||||
/>
|
||||
<button @click="changeDate">click</button>`,
|
||||
() => ({ value: baseValue }),
|
||||
{
|
||||
methods: {
|
||||
changeDate() {
|
||||
spy()
|
||||
this.value = newVal
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
await nextTick()
|
||||
const rangePanelWrapper = wrapper.findComponent(
|
||||
'.el-date-range-picker'
|
||||
) as VueWrapper<InstanceType<typeof DatePickerRange>>
|
||||
const inputRange = wrapper.findAll('.el-range-input')
|
||||
expect(rangePanelWrapper.exists()).toBe(true)
|
||||
expect(rangePanelWrapper.vm.visible).toBe(false)
|
||||
expect(inputRange[0].element.value).toBe('2025-01-01')
|
||||
expect(inputRange[1].element.value).toBe('2025-01-02')
|
||||
|
||||
await wrapper.find('button').trigger('click')
|
||||
await nextTick()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
expect(inputRange[0].element.value).toBe('2025-01-03')
|
||||
expect(inputRange[1].element.value).toBe('2025-01-04')
|
||||
expect(rangePanelWrapper.vm.parsedValue.map((s) => s.toDate())).toEqual(
|
||||
newVal
|
||||
)
|
||||
expect(rangePanelWrapper.vm.visible).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('MonthRange', () => {
|
||||
@@ -2035,6 +2120,9 @@ describe('MonthRange', () => {
|
||||
// input text is something like date string
|
||||
expect(inputs[0].element.value.length).toBe(7)
|
||||
expect(inputs[1].element.value.length).toBe(7)
|
||||
inputs[0].trigger('blur')
|
||||
inputs[0].trigger('focus')
|
||||
await nextTick()
|
||||
// reverse selection
|
||||
p1.click()
|
||||
await nextTick()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import dayjs from 'dayjs'
|
||||
import triggerEvent from '@element-plus/test-utils/trigger-event'
|
||||
@@ -958,9 +958,13 @@ describe('Datetimerange', () => {
|
||||
|
||||
it('shows weekNumber', async () => {
|
||||
const value = ref([new Date(2025, 0, 1), new Date(2025, 1, 1)])
|
||||
_mount(() => (
|
||||
const wrapper = _mount(() => (
|
||||
<DatePicker v-model={value.value} type="datetimerange" show-week-number />
|
||||
))
|
||||
const input = wrapper.find('input')
|
||||
input.trigger('blur')
|
||||
input.trigger('focus')
|
||||
|
||||
await nextTick()
|
||||
const weeks = document.querySelectorAll('td.week')
|
||||
expect(weeks.length).toBe(12)
|
||||
@@ -1033,4 +1037,46 @@ describe('Datetimerange', () => {
|
||||
expect(document.querySelector('.el-picker-panel__footer')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
it('should datetimerange visibility not be trapped by setting new values', async () => {
|
||||
const values = ref(['2025-08-02', '2025-08-02'])
|
||||
const spy = vi.fn()
|
||||
const onChange = () => {
|
||||
values.value = ['2025-08-02', '2025-08-02']
|
||||
spy()
|
||||
}
|
||||
const wrapper = _mount(() => (
|
||||
<DatePicker
|
||||
v-model={values.value}
|
||||
type="datetimerange"
|
||||
valueFormat="YYYY-MM-DD"
|
||||
//@ts-expect-error
|
||||
onChange={onChange}
|
||||
/>
|
||||
))
|
||||
const input = wrapper.find('input')
|
||||
await input.trigger('blur')
|
||||
await input.trigger('focus')
|
||||
|
||||
const rangePanelWrapper = wrapper.findComponent(
|
||||
'.el-date-range-picker'
|
||||
) as VueWrapper<InstanceType<typeof DatePickerRange>>
|
||||
expect(rangePanelWrapper.exists()).toBe(true)
|
||||
expect(rangePanelWrapper.vm.visible).toBe(true)
|
||||
|
||||
const cells = document.querySelectorAll('.available .el-date-table-cell')
|
||||
;(cells[0] as HTMLElement).click()
|
||||
await nextTick()
|
||||
;(cells[1] as HTMLElement).click()
|
||||
await nextTick()
|
||||
|
||||
const button = document.querySelectorAll(
|
||||
'.el-picker-panel__footer button'
|
||||
)![1] as HTMLButtonElement
|
||||
button.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(spy).toHaveBeenCalledOnce()
|
||||
expect(rangePanelWrapper.vm.visible).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -128,13 +128,23 @@ export const useRangePicker = (
|
||||
watch(
|
||||
() => props.parsedValue,
|
||||
(parsedValue) => {
|
||||
if (!props.visible || !parsedValue?.length) {
|
||||
if (!parsedValue?.length) {
|
||||
onReset(parsedValue)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
() => {
|
||||
if (props.visible) {
|
||||
onReset(props.parsedValue)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
return {
|
||||
minDate,
|
||||
maxDate,
|
||||
|
||||
@@ -438,6 +438,7 @@ const defaultValue = toRef(pickerBase.props, 'defaultValue')
|
||||
const { lang } = useLocale()
|
||||
const leftDate = ref<Dayjs>(dayjs().locale(lang.value))
|
||||
const rightDate = ref<Dayjs>(dayjs().locale(lang.value).add(1, unit))
|
||||
let shouldBeVisible = true
|
||||
|
||||
const {
|
||||
minDate,
|
||||
@@ -688,16 +689,17 @@ const handleRangePick = (
|
||||
maxDate.value = maxDate_
|
||||
minDate.value = minDate_
|
||||
|
||||
handleRangeConfirm(close)
|
||||
if (!showTime.value && close) {
|
||||
close = !minDate_ || !maxDate_
|
||||
}
|
||||
shouldBeVisible = close
|
||||
}
|
||||
|
||||
watch(
|
||||
[maxDate, minDate],
|
||||
([min, max]) => {
|
||||
if (min && max) handleRangeConfirm(true)
|
||||
},
|
||||
{ flush: 'post' }
|
||||
)
|
||||
watch([maxDate, minDate], ([max, min]) => {
|
||||
if (max && min) {
|
||||
handleRangeConfirm(shouldBeVisible)
|
||||
}
|
||||
})
|
||||
|
||||
const minTimePickerVisible = ref(false)
|
||||
const maxTimePickerVisible = ref(false)
|
||||
|
||||
Reference in New Issue
Block a user