Skip to content

feat: add previewValue option #934

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ render(<Picker />, 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 |
Expand Down Expand Up @@ -102,7 +103,7 @@ render(<Picker />, 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 |
Expand All @@ -112,6 +113,7 @@ render(<Picker />, 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 |
Expand Down
4 changes: 4 additions & 0 deletions docs/examples/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ export default () => {
<h3>Keyboard event with prevent default behaviors</h3>
<Picker<Moment> {...sharedProps} locale={enUS} onKeyDown={keyDown} />
</div>
<div style={{ margin: '0 8px' }}>
<h3>PreviewValue is false</h3>
<Picker<Moment> {...sharedProps} locale={enUS} onKeyDown={keyDown} previewValue={false} />
</div>
</div>
</div>
);
Expand Down
11 changes: 11 additions & 0 deletions docs/examples/range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,17 @@ export default () => {
disabledDate={disabledDate}
/>
</div>
<div style={{ margin: '0 8px' }}>
<h3>PreviewValue is false</h3>
<RangePicker<Moment>
{...sharedProps}
previewValue={false}
value={undefined}
locale={zhCN}
placeholder={['start...', 'end...']}
disabledDate={disabledDate}
/>
</div>
</div>
</div>
);
Expand Down
14 changes: 13 additions & 1 deletion docs/examples/time.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const testClassNames = {
suffix: 'test-suffix',
popupContent: 'test-popup-content',
popupItem: 'test-popup-item',
}
};

export default () => {
return (
Expand Down Expand Up @@ -53,6 +53,18 @@ export default () => {
disabledHours: () => (type === 'start' ? [now.hours()] : [now.hours() - 5]),
})}
/>

<h3>PreviewValue is false</h3>
<RangePicker
defaultValue={[defaultValue, defaultValue]}
picker="time"
locale={zhCN}
previewValue={false}
generateConfig={momentGenerateConfig}
disabledTime={(now, type) => ({
disabledHours: () => (type === 'start' ? [now.hours()] : [now.hours() - 5]),
})}
/>
</div>
);
};
9 changes: 7 additions & 2 deletions src/PickerInput/RangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ function RangePicker<DateType extends object = any>(
styles: propStyles,
classNames: propClassNames,

previewValue,
// Value
defaultValue,
value,
Expand Down Expand Up @@ -487,7 +488,9 @@ function RangePicker<DateType extends object = any>(
const presetList = usePresets(presets, ranges);

const onPresetHover = (nextValues: RangeValueType<DateType> | null) => {
setInternalHoverValues(nextValues);
if (previewValue === 'hover') {
setInternalHoverValues(nextValues);
}
setHoverSource('preset');
};

Expand All @@ -505,7 +508,9 @@ function RangePicker<DateType extends object = any>(

// ======================== Panel =========================
const onPanelHover = (date: DateType) => {
setInternalHoverValues(date ? fillCalendarValue(date, activeIndex) : null);
if (previewValue === 'hover') {
setInternalHoverValues(date ? fillCalendarValue(date, activeIndex) : null);
}
setHoverSource('cell');
};

Expand Down
10 changes: 8 additions & 2 deletions src/PickerInput/SinglePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ function Picker<DateType extends object = any>(
styles: propStyles,
classNames: propClassNames,

previewValue,

// Value
order,
defaultValue,
Expand Down Expand Up @@ -413,7 +415,9 @@ function Picker<DateType extends object = any>(
const presetList = usePresets(presets);

const onPresetHover = (nextValue: DateType | null) => {
setInternalHoverValue(nextValue);
if (previewValue === 'hover') {
setInternalHoverValue(nextValue);
}
setHoverSource('preset');
};

Expand All @@ -433,7 +437,9 @@ function Picker<DateType extends object = any>(

// ======================== Panel =========================
const onPanelHover = (date: DateType | null) => {
setInternalHoverValue(date);
if (previewValue === 'hover') {
setInternalHoverValue(date);
}
setHoverSource('cell');
};

Expand Down
3 changes: 3 additions & 0 deletions src/PickerInput/hooks/useFilledProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type PickedProps<DateType extends object = any> = Pick<
| 'minDate'
| 'maxDate'
| 'defaultOpenValue'
| 'previewValue'
> & {
multiple?: boolean;
// RangePicker showTime definition is different with Picker
Expand Down Expand Up @@ -96,6 +97,7 @@ export default function useFilledProps<
locale,
picker = 'date',
prefixCls = 'rc-picker',
previewValue = 'hover',
styles = {},
classNames = {},
order = true,
Expand Down Expand Up @@ -161,6 +163,7 @@ export default function useFilledProps<
const filledProps = React.useMemo(
() => ({
...props,
previewValue,
prefixCls,
locale: mergedLocale,
picker,
Expand Down
9 changes: 9 additions & 0 deletions src/interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<DateType extends object = any>
Expand Down Expand Up @@ -425,6 +427,13 @@ export interface SharedPickerProps<DateType extends object = any>
*/
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;

Expand Down
12 changes: 11 additions & 1 deletion tests/generateWithTZ.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ describe('dayjs: getNow', () => {
const M_now = moment().tz(JP);

expect(D_now.format()).toEqual(M_now.format());
expect(D_now.get('hour') - D_now.utc().get('hour')).toEqual(9);

const expectedOffset = M_now.utcOffset() / 60;
const actualOffset = D_now.get('hour') - D_now.utc().get('hour');

let normalizedOffset = actualOffset;
if (actualOffset > 12) {
normalizedOffset = actualOffset - 24;
} else if (actualOffset < -12) {
normalizedOffset = actualOffset + 24;
}
expect(normalizedOffset).toEqual(expectedOffset);
});
});
28 changes: 28 additions & 0 deletions tests/range.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<DayRangePicker
minDate={dayjs('2024')}
open
mode={['year', 'year']}
showTime
previewValue={false}
needConfirm
value={[dayjs('2024-01-01'), dayjs('2025-01-01')]}
/>,
);

// 找到第一个输入框并保存初始值
const inputStart = container.querySelectorAll<HTMLInputElement>('.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);
});
});
48 changes: 47 additions & 1 deletion tests/time.spec.tsx
Original file line number Diff line number Diff line change
@@ -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(() => {
Expand Down Expand Up @@ -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(
<DayPicker
showTime={{
showMillisecond: true,
use12Hours: true,
}}
previewValue={false}
defaultValue={dayjs('1990-09-03 01:02:03')}
/>,
);
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');
});
});