From 6d646a752b805f112972a28fe3a9a265d47e42f3 Mon Sep 17 00:00:00 2001 From: zwkang Date: Thu, 26 Oct 2023 19:13:59 +0800 Subject: [PATCH 1/3] fix: disabledTime validate user input --- src/Picker.tsx | 59 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/Picker.tsx b/src/Picker.tsx index 514d6088e..77fdf5a89 100644 --- a/src/Picker.tsx +++ b/src/Picker.tsx @@ -14,15 +14,16 @@ import type { AlignType } from '@rc-component/trigger/lib/interface'; import classNames from 'classnames'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; -import warning from 'rc-util/lib/warning'; import pickAttrs from 'rc-util/lib/pickAttrs'; +import warning from 'rc-util/lib/warning'; import * as React from 'react'; +import { GenerateConfig } from './generate'; import useHoverValue from './hooks/useHoverValue'; import usePickerInput from './hooks/usePickerInput'; import usePresets from './hooks/usePresets'; import useTextValueMapping from './hooks/useTextValueMapping'; import useValueTexts from './hooks/useValueTexts'; -import type { CustomFormat, PickerMode, PresetDate } from './interface'; +import type { CustomFormat, DisabledTime, PickerMode, PresetDate } from './interface'; import type { ContextOperationRefProps } from './PanelContext'; import PanelContext from './PanelContext'; import type { @@ -34,10 +35,10 @@ import PickerPanel from './PickerPanel'; import PickerTrigger from './PickerTrigger'; import PresetPanel from './PresetPanel'; import { formatValue, isEqual, parseValue } from './utils/dateUtil'; +import { getClearIcon } from './utils/getClearIcon'; import { toArray } from './utils/miscUtil'; import { elementsContains, getDefaultFormat, getInputSize } from './utils/uiUtil'; import { legacyPropsWarning } from './utils/warnUtil'; -import { getClearIcon } from './utils/getClearIcon'; export type PickerRefConfig = { focus: () => void; @@ -67,8 +68,8 @@ export type PickerSharedProps = { // Render suffixIcon?: React.ReactNode; - /** - * Clear all icon + /** + * Clear all icon * @deprecated Please use `allowClear` instead **/ clearIcon?: React.ReactNode; @@ -145,6 +146,32 @@ type MergedPickerProps = { picker?: PickerMode; } & OmitType; +function testValueInSet(num: number, range: number[]) { + if (typeof num === 'undefined' || typeof range === 'undefined') return; + const set = new Set(range); + return set.has(num); +} + +function validateTime( + picker: PickerMode, + disabledTime: DisabledTime, + date: DateType, + generateConfig: GenerateConfig, +) { + if (!disabledTime || picker !== 'date') return false; + const disabledTimes = disabledTime(date); + if (!disabledTimes) return false; + const { disabledHours, disabledMinutes, disabledSeconds } = disabledTimes; + const hour = generateConfig.getHour(date); + const minter = generateConfig.getMinute(date); + const second = generateConfig.getSecond(date); + + const validateHour = testValueInSet(hour, disabledHours?.()); + const validateMinute = testValueInSet(minter, disabledMinutes?.(hour)); + const validateSecond = testValueInSet(second, disabledSeconds?.(hour, minter)); + return validateHour || validateMinute || validateSecond; +} + function InnerPicker(props: PickerProps) { const { prefixCls = 'rc-picker', @@ -196,6 +223,7 @@ function InnerPicker(props: PickerProps) { autoComplete = 'off', inputRender, changeOnBlur, + disabledTime, } = props as MergedPickerProps; const inputRef = React.useRef(null); @@ -253,6 +281,8 @@ function InnerPicker(props: PickerProps) { locale, }); + const timeProps = typeof showTime === 'object' ? showTime : {}; + const [text, triggerTextChange, resetText] = useTextValueMapping({ valueTexts, onTextChange: (newText) => { @@ -261,7 +291,11 @@ function InnerPicker(props: PickerProps) { formatList, generateConfig, }); - if (inputDate && (!disabledDate || !disabledDate(inputDate))) { + if ( + inputDate && + (!disabledDate || !disabledDate(inputDate)) && + !validateTime(picker, disabledTime, inputDate || timeProps?.defaultValue, generateConfig) + ) { setSelectedValue(inputDate); } }, @@ -340,7 +374,8 @@ function InnerPicker(props: PickerProps) { // When user typing disabledDate with keyboard and enter, this value will be empty !selectedValue || // Normal disabled check - (disabledDate && disabledDate(selectedValue)) + (disabledDate && disabledDate(selectedValue)) || + validateTime(picker, disabledTime, selectedValue || timeProps?.defaultValue, generateConfig) ) { return false; } @@ -490,11 +525,7 @@ function InnerPicker(props: PickerProps) { ); } - const mergedClearIcon: React.ReactNode = getClearIcon( - prefixCls, - allowClear, - clearIcon, - ); + const mergedClearIcon: React.ReactNode = getClearIcon(prefixCls, allowClear, clearIcon); const clearNode: React.ReactNode = ( (props: PickerProps) { const mergedAllowClear = !!allowClear && mergedValue && !disabled; - const mergedInputProps: React.InputHTMLAttributes & { ref: React.MutableRefObject } = { + const mergedInputProps: React.InputHTMLAttributes & { + ref: React.MutableRefObject; + } = { id, tabIndex, disabled, From b26360eacc5c70021d002fc054dd03d05cbc2b41 Mon Sep 17 00:00:00 2001 From: zwkang Date: Thu, 26 Oct 2023 20:10:23 +0800 Subject: [PATCH 2/3] fix: add case test --- tests/disabledTime.spec.tsx | 95 +++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/tests/disabledTime.spec.tsx b/tests/disabledTime.spec.tsx index 46a4a056f..7b4f1f7fa 100644 --- a/tests/disabledTime.spec.tsx +++ b/tests/disabledTime.spec.tsx @@ -12,6 +12,19 @@ import { openPicker, } from './util/commonUtil'; +import dayjs from 'dayjs'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; +import KeyCode from 'rc-util/lib/KeyCode'; +dayjs.extend(customParseFormat); + +function keyDown(keyCode: number) { + fireEvent.keyDown(document.querySelector('input'), { + keyCode, + which: keyCode, + charCode: keyCode, + }); +} + describe('Picker.DisabledTime', () => { it('disabledTime on TimePicker', () => { render( @@ -153,11 +166,15 @@ describe('Picker.DisabledTime', () => { />, ); - expect(document.querySelector('.rc-picker-input > input').getAttribute('value')).toEqual('1989-11-28 00:00:00'); + expect(document.querySelector('.rc-picker-input > input').getAttribute('value')).toEqual( + '1989-11-28 00:00:00', + ); fireEvent.click(document.querySelectorAll('.rc-picker-cell-inner')[2]); - expect(document.querySelector('.rc-picker-input > input').getAttribute('value')).toEqual('1989-10-31 05:00:00'); + expect(document.querySelector('.rc-picker-input > input').getAttribute('value')).toEqual( + '1989-10-31 05:00:00', + ); }); it('disabledTime should reset correctly when date changed by click for no default value', function () { @@ -174,8 +191,12 @@ describe('Picker.DisabledTime', () => { const firstDayInMonth = now.startOf('month'); const firstDayInCalendar = firstDayInMonth.clone().subtract(firstDayInMonth.days(), 'days'); - const expected = firstDayInCalendar.clone().hour(h + 1 % 24).minute(m + 1 % 60).second(s + 1 % 60); - + const expected = firstDayInCalendar + .clone() + .hour(h + (1 % 24)) + .minute(m + (1 % 60)) + .second(s + (1 % 60)); + render(); fireEvent.click(document.querySelectorAll('.rc-picker-cell-inner')[0]); @@ -184,6 +205,72 @@ describe('Picker.DisabledTime', () => { expected.format('YYYY-MM-DD HH:mm:ss'), ); }); + // https://github.com/ant-design/ant-design/issues/45489 + it('disabledTime should reset correctly when date time change by input enter', () => { + resetWarned(); + const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const range = (start, end) => { + const result = []; + for (let i = start; i < end; i++) { + result.push(i); + } + return result; + }; + const disabledDate = (current) => { + return current && current.isBefore(Date.now()); + }; + const disabledDateTime = () => ({ + disabledHours: () => range(0, 24).splice(4, 20), + disabledMinutes: () => range(30, 60), + disabledSeconds: () => [55, 56], + }); + + const expectDate = '2023-10-26 00:00:00'; + const changingDate = '2023-10-26 03:00:00'; + const nowAllowDate = '2023-10-26 06:00:00'; + + const { container } = render( + , + ); + openPicker(container); + const yearPanel = document.querySelector('.rc-picker-time-panel-column'); + expect(yearPanel.querySelectorAll('li')[4]).toHaveClass('rc-picker-time-panel-cell-disabled'); + closePicker(container); + + expect(document.querySelector('.rc-picker-input input').getAttribute('value')).toEqual( + expectDate, + ); + + fireEvent.click(document.querySelector('.rc-picker-input > input')); + fireEvent.change(document.querySelector('.rc-picker-input > input'), { + target: { + value: changingDate, + }, + }); + keyDown(KeyCode.ENTER); + expect(document.querySelector('.rc-picker-input input').getAttribute('value')).toEqual( + changingDate, + ); + fireEvent.click(document.querySelector('.rc-picker-input > input')); + fireEvent.change(document.querySelector('.rc-picker-input > input'), { + target: { + value: nowAllowDate, + }, + }); + + keyDown(KeyCode.ENTER); + expect(document.querySelector('.rc-picker-input input').getAttribute('value')).toEqual( + changingDate, + ); + errSpy.mockRestore(); + }); describe('warning for legacy props', () => { it('single', () => { From da9e0a1f25846fa7b04b05cdbd24e2264147997a Mon Sep 17 00:00:00 2001 From: zwkang Date: Thu, 26 Oct 2023 20:14:21 +0800 Subject: [PATCH 3/3] fix: remove useless selectedValue --- src/Picker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Picker.tsx b/src/Picker.tsx index 77fdf5a89..f46a803db 100644 --- a/src/Picker.tsx +++ b/src/Picker.tsx @@ -375,7 +375,7 @@ function InnerPicker(props: PickerProps) { !selectedValue || // Normal disabled check (disabledDate && disabledDate(selectedValue)) || - validateTime(picker, disabledTime, selectedValue || timeProps?.defaultValue, generateConfig) + validateTime(picker, disabledTime, selectedValue, generateConfig) ) { return false; }