fix(components): [time-select] prevent freeze when step is invalid (#23635)

* fix(components): [time-select] prevent freeze when step is 00:00

* test: add test case

* fix: handle invalid step value formats "00:" and ":00"

* refactor: invalidStep use default value

* refactor: step use computed

* chore: typo

* refactor: step use computed
This commit is contained in:
snowbitx
2026-02-14 06:58:39 +08:00
committed by GitHub
parent 2d316c0e85
commit 5a997a62c5
3 changed files with 35 additions and 7 deletions

View File

@@ -269,6 +269,17 @@ describe('TimeSelect', () => {
expect(input.attributes('name')).toBe('timeSelectName')
})
it('should fallback to default step when step is 00:00', async () => {
const wrapper = mount(() => (
<TimeSelect start="09:00" end="10:00" step="00:00" />
))
const input = wrapper.find('input')
await input.trigger('click')
const items = document.querySelectorAll('.el-select-dropdown__item>span')
expect(items).toHaveLength(3)
expect([...items].at(-1)?.textContent).toBe('10:00')
})
describe('form item accessibility integration', () => {
it('automatic id attachment', async () => {
const wrapper = mount(() => (

View File

@@ -88,6 +88,8 @@ export interface TimeSelectProps extends UseEmptyValuesProps {
popperStyle?: string | CSSProperties
}
export const DEFAULT_STEP = '00:30'
/**
* @deprecated Removed after 3.0.0, Use `TimeSelectProps` instead.
*/
@@ -160,7 +162,7 @@ export const timeSelectProps = buildProps({
*/
step: {
type: String,
default: '00:30',
default: DEFAULT_STEP,
},
/**
* @description minimum time, any time before this time will be disabled

View File

@@ -47,6 +47,8 @@ import { useLocale, useNamespace } from '@element-plus/hooks'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { CircleClose, Clock } from '@element-plus/icons-vue'
import { compareTime, formatTime, nextTime, parseTime } from './utils'
import { debugWarn } from '@element-plus/utils'
import { DEFAULT_STEP } from './time-select'
import type { TimeSelectProps } from './time-select'
@@ -68,7 +70,7 @@ const props = withDefaults(defineProps<TimeSelectProps>(), {
clearable: true,
start: '09:00',
end: '18:00',
step: '00:30',
step: DEFAULT_STEP,
prefixIcon: () => Clock,
clearIcon: () => CircleClose,
popperClass: '',
@@ -93,11 +95,6 @@ const end = computed(() => {
return time ? formatTime(time) : null
})
const step = computed(() => {
const time = parseTime(props.step)
return time ? formatTime(time) : null
})
const minTime = computed(() => {
const time = parseTime(props.minTime || '')
return time ? formatTime(time) : null
@@ -108,6 +105,24 @@ const maxTime = computed(() => {
return time ? formatTime(time) : null
})
const step = computed(() => {
const time = parseTime(props.step)
const isInvalidStep =
!time ||
time.hours < 0 ||
time.minutes < 0 ||
Number.isNaN(time.hours) ||
Number.isNaN(time.minutes) ||
(time.hours === 0 && time.minutes === 0)
if (isInvalidStep) {
debugWarn(
'ElTimeSelect',
`invalid step, fallback to default step (${DEFAULT_STEP}).`
)
}
return !isInvalidStep ? formatTime(time) : DEFAULT_STEP
})
const items = computed(() => {
const result: { value: string; rawValue: string; disabled: boolean }[] = []
const push = (formattedValue: string, rawValue: string) => {