diff --git a/README.md b/README.md index 16cb3e7f4..9d66c2562 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ render(, mountNode); | autoFocus | boolean | false | whether auto focus | | showTime | boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection | | picker | time \| date \| week \| month \| year | | control which kind of panel should be shown | +| previewValue | false \| hover | hover | When the user selects the date hover option, the value of the input field undergoes a temporary change | | format | String \| String[] | depends on whether you set timePicker and your locale | use to format/parse date(without time) value to/from input. When an array is provided, all values are used for parsing and first value for display | | use12Hours | boolean | false | 12 hours display mode | | value | moment | | current value like input's value | @@ -102,7 +103,7 @@ render(, mountNode); ### RangePicker | Property | Type | Default | Description | -| --- | --- | --- | --- | +| --- | --- | --- | --- | --- | | prefixCls | String | rc-picker | prefixCls of this component | | className | String | '' | additional css class of root dom | | style | React.CSSProperties | | additional style of root dom node | @@ -112,6 +113,7 @@ render(, mountNode); | defaultPickerValue | moment | | Set default display picker view date | | separator | String | '~' | set separator between inputs | | picker | time \| date \| week \| month \| year | | control which kind of panel | +| previewValue | false \| hover | hover | When the user selects the date hover option, the value of the input field undergoes a temporary change | | placeholder | [String, String] | | placeholder of date input | | showTime | boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection | | showTime.defaultValue | [moment, moment] | | to set default time of selected date | diff --git a/docs/examples/basic.tsx b/docs/examples/basic.tsx index 08d747111..acff81877 100644 --- a/docs/examples/basic.tsx +++ b/docs/examples/basic.tsx @@ -154,6 +154,10 @@ export default () => {

Keyboard event with prevent default behaviors

{...sharedProps} locale={enUS} onKeyDown={keyDown} /> +
+

PreviewValue is false

+ {...sharedProps} locale={enUS} onKeyDown={keyDown} previewValue={false} /> +
); diff --git a/docs/examples/range.tsx b/docs/examples/range.tsx index 1b137c8b0..23f50ea00 100644 --- a/docs/examples/range.tsx +++ b/docs/examples/range.tsx @@ -202,6 +202,17 @@ export default () => { disabledDate={disabledDate} /> +
+

PreviewValue is false

+ + {...sharedProps} + previewValue={false} + value={undefined} + locale={zhCN} + placeholder={['start...', 'end...']} + disabledDate={disabledDate} + /> +
); diff --git a/docs/examples/time.tsx b/docs/examples/time.tsx index d1332b99c..419b519c8 100644 --- a/docs/examples/time.tsx +++ b/docs/examples/time.tsx @@ -12,7 +12,7 @@ const testClassNames = { suffix: 'test-suffix', popupContent: 'test-popup-content', popupItem: 'test-popup-item', -} +}; export default () => { return ( @@ -53,6 +53,18 @@ export default () => { disabledHours: () => (type === 'start' ? [now.hours()] : [now.hours() - 5]), })} /> + +

PreviewValue is false

+ ({ + disabledHours: () => (type === 'start' ? [now.hours()] : [now.hours() - 5]), + })} + /> ); }; diff --git a/src/PickerInput/RangePicker.tsx b/src/PickerInput/RangePicker.tsx index 7a0b57839..97d4f335b 100644 --- a/src/PickerInput/RangePicker.tsx +++ b/src/PickerInput/RangePicker.tsx @@ -164,6 +164,7 @@ function RangePicker( styles: propStyles, classNames: propClassNames, + previewValue, // Value defaultValue, value, @@ -483,12 +484,19 @@ function RangePicker( [activeInputLeft: number, activeInputRight: number, selectorWidth: number] >([0, 0, 0]); + const onSetHover = (date: RangeValueType | null, source: 'cell' | 'preset') => { + if (previewValue !== 'hover') { + return; + } + setInternalHoverValues(date); + setHoverSource(source); + }; + // ======================= Presets ======================== const presetList = usePresets(presets, ranges); const onPresetHover = (nextValues: RangeValueType | null) => { - setInternalHoverValues(nextValues); - setHoverSource('preset'); + onSetHover(nextValues, 'preset'); }; const onPresetSubmit = (nextValues: RangeValueType) => { @@ -505,8 +513,7 @@ function RangePicker( // ======================== Panel ========================= const onPanelHover = (date: DateType) => { - setInternalHoverValues(date ? fillCalendarValue(date, activeIndex) : null); - setHoverSource('cell'); + onSetHover(date ? fillCalendarValue(date, activeIndex) : null, 'cell'); }; // >>> Focus diff --git a/src/PickerInput/SinglePicker.tsx b/src/PickerInput/SinglePicker.tsx index c773fbd09..09e5ca32e 100644 --- a/src/PickerInput/SinglePicker.tsx +++ b/src/PickerInput/SinglePicker.tsx @@ -128,6 +128,8 @@ function Picker( styles: propStyles, classNames: propClassNames, + previewValue, + // Value order, defaultValue, @@ -406,6 +408,14 @@ function Picker( } }, [mergedOpen]); + const onSetHover = (date: DateType | null, source: 'cell' | 'preset') => { + if (previewValue !== 'hover') { + return; + } + setInternalHoverValue(date); + setHoverSource(source); + }; + // ======================================================== // == Panels == // ======================================================== @@ -413,8 +423,7 @@ function Picker( const presetList = usePresets(presets); const onPresetHover = (nextValue: DateType | null) => { - setInternalHoverValue(nextValue); - setHoverSource('preset'); + onSetHover(nextValue, 'preset'); }; // TODO: handle this @@ -433,8 +442,7 @@ function Picker( // ======================== Panel ========================= const onPanelHover = (date: DateType | null) => { - setInternalHoverValue(date); - setHoverSource('cell'); + onSetHover(date, 'cell'); }; // >>> Focus diff --git a/src/PickerInput/hooks/useFilledProps.ts b/src/PickerInput/hooks/useFilledProps.ts index 26178c967..8cdd895cd 100644 --- a/src/PickerInput/hooks/useFilledProps.ts +++ b/src/PickerInput/hooks/useFilledProps.ts @@ -33,6 +33,7 @@ type PickedProps = Pick< | 'minDate' | 'maxDate' | 'defaultOpenValue' + | 'previewValue' > & { multiple?: boolean; // RangePicker showTime definition is different with Picker @@ -96,6 +97,7 @@ export default function useFilledProps< locale, picker = 'date', prefixCls = 'rc-picker', + previewValue = 'hover', styles = {}, classNames = {}, order = true, @@ -161,6 +163,7 @@ export default function useFilledProps< const filledProps = React.useMemo( () => ({ ...props, + previewValue, prefixCls, locale: mergedLocale, picker, diff --git a/src/interface.tsx b/src/interface.tsx index 2fe3dae7d..99f9b6767 100644 --- a/src/interface.tsx +++ b/src/interface.tsx @@ -313,6 +313,8 @@ export type LegacyOnKeyDown = ( export type SemanticName = 'root' | 'prefix' | 'input' | 'suffix'; +export type PreviewValueType = 'hover'; + export type PanelSemanticName = 'root' | 'header' | 'body' | 'content' | 'item' | 'footer'; export interface SharedPickerProps @@ -425,6 +427,13 @@ export interface SharedPickerProps */ preserveInvalidOnBlur?: boolean; + /** + * When the user selects the date hover option, the value of the input field undergoes a temporary change. + * `false` will not preview value. + * `hover` will preview value when hover. + */ + previewValue?: false | PreviewValueType; + // Motion transitionName?: string; diff --git a/tests/range.spec.tsx b/tests/range.spec.tsx index e584b75fc..b5173a656 100644 --- a/tests/range.spec.tsx +++ b/tests/range.spec.tsx @@ -2120,4 +2120,32 @@ describe('Picker.Range', () => { openPicker(container, 1); expect(container.querySelectorAll('.rc-picker-input')[0]).toHaveClass('rc-picker-input-active'); }); + + it('should not update preview value in input when previewValue is false', () => { + const { container } = render( + , + ); + + // 找到第一个输入框并保存初始值 + const inputStart = container.querySelectorAll('.rc-picker-input input')[0]; + const initialValueStart = inputStart.value; + + // 打开第一个面板并 hover 一个新值(例如 2028 年) + const targetCell = document.querySelector('[title="2028"]') as HTMLElement; + expect(targetCell).toBeTruthy(); // 确保存在 + + // 2. 模拟鼠标移入(hover) + fireEvent.mouseEnter(targetCell); + + // 确保值未更新(仍为原值) + expect(inputStart.value).toBe(initialValueStart); + }); }); diff --git a/tests/time.spec.tsx b/tests/time.spec.tsx index e19837d0d..afcd4841c 100644 --- a/tests/time.spec.tsx +++ b/tests/time.spec.tsx @@ -1,7 +1,8 @@ import { fireEvent, render } from '@testing-library/react'; import { resetWarned } from '@rc-component/util/lib/warning'; import React from 'react'; -import { DayPicker, getDay, openPicker, selectCell, findCell } from './util/commonUtil'; +import dayjs from 'dayjs'; +import { DayPicker, getDay, openPicker, selectCell } from './util/commonUtil'; describe('Picker.Time', () => { beforeEach(() => { @@ -68,4 +69,49 @@ describe('Picker.Time', () => { fireEvent.mouseEnter(getColCell(4, 1)); expect(container.querySelector('input')).toHaveValue('1990-09-03 12:00:00.000 PM'); }); + + it('hover should not update preview value in input when previewValue is false', async () => { + const { container } = render( + , + ); + openPicker(container); + + const getColCell = (colIndex: number, cellIndex: number) => { + const column = document.querySelectorAll('.rc-picker-time-panel-column')[colIndex]; + const cell = column.querySelectorAll('.rc-picker-time-panel-cell-inner')[cellIndex]; + + return cell; + }; + + // Hour + fireEvent.mouseEnter(getColCell(0, 3)); + expect(container.querySelector('input')).toHaveValue('1990-09-03 01:02:03.000 AM'); + + // Let test for mouse leave + fireEvent.mouseLeave(getColCell(0, 3)); + expect(container.querySelector('input')).toHaveValue('1990-09-03 01:02:03.000 AM'); + + // Minute + fireEvent.mouseEnter(getColCell(1, 2)); + expect(container.querySelector('input')).toHaveValue('1990-09-03 01:02:03.000 AM'); + + // Second + fireEvent.mouseEnter(getColCell(2, 1)); + expect(container.querySelector('input')).toHaveValue('1990-09-03 01:02:03.000 AM'); + + // Millisecond + fireEvent.mouseEnter(getColCell(3, 1)); + expect(container.querySelector('input')).toHaveValue('1990-09-03 01:02:03.000 AM'); + + // Meridiem + fireEvent.mouseEnter(getColCell(4, 1)); + expect(container.querySelector('input')).toHaveValue('1990-09-03 01:02:03.000 AM'); + }); });