diff --git a/README.md b/README.md index 2f45e2e1e..4230d9c7c 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,9 @@ render(, mountNode); | onCalendarChange | Function(value:[moment], formatString: [string, string]) | | a callback function, can be executed when the start time or the end time of the range is changing | | direction | String: ltr or rtl | | Layout direction of picker component, it supports RTL direction too. | | order | Boolean | true | (TimeRangePicker only) `false` to disable auto order | +| order | Boolean | true | (TimeRangePicker only) `false` to disable auto order | +| disabledPickerStartDate | Function(startDate: moment):boolean | | disable start date | +| disabledPickerEndDate | Function(endDate: moment):boolean | | disable end date | ### showTime-options diff --git a/examples/range.tsx b/examples/range.tsx index 53a774d2f..843900070 100644 --- a/examples/range.tsx +++ b/examples/range.tsx @@ -14,14 +14,12 @@ function formatDate(date: Moment | null) { } export default () => { - const [value, setValue] = React.useState< - [Moment | null, Moment | null] | null - >([defaultStartValue, defaultEndValue]); + const [value, setValue] = React.useState<[Moment | null, Moment | null] | null>([ + defaultStartValue, + defaultEndValue, + ]); - const onChange = ( - newValue: [Moment | null, Moment | null] | null, - formatStrings?: string[], - ) => { + const onChange = (newValue: [Moment | null, Moment | null] | null, formatStrings?: string[]) => { console.log('Change:', newValue, formatStrings); setValue(newValue); }; @@ -33,6 +31,12 @@ export default () => { console.log('Calendar Change:', newValue, formatStrings); }; + const onChangeWithAllDatesEnabled = (newValue: [Moment | null, Moment | null] | null) => { + if (newValue[0] && newValue[1] && newValue[1].isBefore(newValue[0])) { + setValue([newValue[1], newValue[0]]); + } + }; + const sharedProps = { generateConfig: momentGenerateConfig, value, @@ -44,10 +48,7 @@ export default () => { return (
-

- Value:{' '} - {value ? `${formatDate(value[0])} ~ ${formatDate(value[1])}` : 'null'} -

+

Value: {value ? `${formatDate(value[0])} ~ ${formatDate(value[1])}` : 'null'}

@@ -132,21 +133,11 @@ export default () => {

Start disabled

- - {...sharedProps} - locale={zhCN} - allowClear - disabled={[true, false]} - /> + {...sharedProps} locale={zhCN} allowClear disabled={[true, false]} />

End disabled

- - {...sharedProps} - locale={zhCN} - allowClear - disabled={[false, true]} - /> + {...sharedProps} locale={zhCN} allowClear disabled={[false, true]} />
@@ -161,6 +152,17 @@ export default () => { renderExtraFooter={() =>
extra footer
} />
+
+

All dates enabled

+ + {...sharedProps} + onChange={onChangeWithAllDatesEnabled} + value={undefined} + locale={zhCN} + disabledPickerEndDate={() => false} + disabledPickerStartDate={() => false} + /> +
); diff --git a/src/RangePicker.tsx b/src/RangePicker.tsx index 09b9f17e2..27b667a47 100644 --- a/src/RangePicker.tsx +++ b/src/RangePicker.tsx @@ -78,6 +78,8 @@ export interface RangePickerSharedProps { direction?: 'ltr' | 'rtl'; /** @private Internal control of active picker. Do not use since it's private usage */ activePickerIndex?: 0 | 1; + disabledPickerStartDate?: (startDate: DateType) => boolean; + disabledPickerEndDate?: (endDate: DateType) => boolean; } type OmitPickerProps = Omit< @@ -182,6 +184,8 @@ function InnerRangePicker(props: RangePickerProps) { order, direction, activePickerIndex, + disabledPickerStartDate, + disabledPickerEndDate, } = props as MergedRangePickerProps; const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time'; @@ -288,6 +292,8 @@ function InnerRangePicker(props: RangePickerProps) { disabled: mergedDisabled, disabledDate, generateConfig, + disabledPickerStartDate, + disabledPickerEndDate, }); // ============================= Open ============================== @@ -332,13 +338,13 @@ function InnerRangePicker(props: RangePickerProps) { let values = newValue; const startValue = getValue(values, 0); - let endValue = getValue(values, 1); + const endValue = getValue(values, 1); if (startValue && endValue && generateConfig.isAfter(startValue, endValue)) { if (!isSameDate(generateConfig, startValue, endValue)) { - // Clean up end date when start date is after end date - values = [startValue, null]; - endValue = null; + // if user selects start date while end date picker is opened + // then set the start date at proper position + values = [startValue, endValue]; } else if (picker !== 'time' || order !== false) { // Reorder when in same date values = reorderValues(values, generateConfig); diff --git a/src/hooks/useRangeDisabled.ts b/src/hooks/useRangeDisabled.ts index 302b9342f..e61031975 100644 --- a/src/hooks/useRangeDisabled.ts +++ b/src/hooks/useRangeDisabled.ts @@ -12,6 +12,8 @@ export default function useRangeDisabled({ disabledDate, disabled, generateConfig, + disabledPickerStartDate, + disabledPickerEndDate, }: { picker: PickerMode; selectedValue: RangeValue; @@ -19,30 +21,35 @@ export default function useRangeDisabled({ disabled: [boolean, boolean]; locale: Locale; generateConfig: GenerateConfig; + disabledPickerStartDate?: (startDateToDisable: DateType) => boolean; + disabledPickerEndDate?: (endDateToDisable: DateType) => boolean; }) { const startDate = getValue(selectedValue, 0); const endDate = getValue(selectedValue, 1); const disabledStartDate = React.useCallback( (date: DateType) => { + if (disabledPickerStartDate) { + return disabledPickerStartDate(date); + } if (disabledDate && disabledDate(date)) { return true; } if (disabled[1] && endDate) { - return ( - !isSameDate(generateConfig, date, endDate) && - generateConfig.isAfter(date, endDate) - ); + return !isSameDate(generateConfig, date, endDate) && generateConfig.isAfter(date, endDate); } return false; }, - [disabledDate, disabled[1], endDate], + [disabledDate, disabled[1], endDate, disabledPickerStartDate], ); const disableEndDate = React.useCallback( (date: DateType) => { + if (disabledPickerEndDate) { + return disabledPickerEndDate(date); + } if (disabledDate && disabledDate(date)) { return true; } @@ -59,7 +66,7 @@ export default function useRangeDisabled({ return false; }, - [disabledDate, startDate, picker], + [disabledDate, startDate, picker, disabledPickerEndDate], ); // Handle week date disabled diff --git a/tests/range.spec.tsx b/tests/range.spec.tsx index e72ef9c5b..256af1661 100644 --- a/tests/range.spec.tsx +++ b/tests/range.spec.tsx @@ -173,7 +173,7 @@ describe('Picker.Range', () => { expect(wrapper.findCell(11).hasClass('rc-picker-cell-disabled')).toBeFalsy(); }); - it('Reset when startDate is after endDate', () => { + it('Set correct start and end date', () => { const onChange = jest.fn(); const wrapper = mount(); @@ -182,8 +182,8 @@ describe('Picker.Range', () => { wrapper.openPicker(0); wrapper.selectCell(23); - expect(onChange).not.toHaveBeenCalled(); - matchValues(wrapper, '1990-09-23', ''); + expect(onChange).toHaveBeenCalled(); + matchValues(wrapper, '1990-09-07', '1990-09-23'); }); it('allowEmpty', () => { @@ -1063,4 +1063,32 @@ describe('Picker.Range', () => { .props().id, ).toEqual('bamboo'); }); + + it('all dates inside endDate picker should be clickable', () => { + const onChange = jest.fn(); + + const wrapper = mount( + false} + disabledPickerStartDate={() => false} + />, + ); + + let cellNode: Wrapper; + + // Start date + wrapper.openPicker(); + wrapper.selectCell(23); + + // End date + cellNode = wrapper.selectCell(11); + expect(cellNode.hasClass('rc-picker-cell-disabled')).toBeFalsy(); + expect(onChange).toHaveBeenCalled(); + + // Click origin disabled date + cellNode = wrapper.selectCell(28); + expect(cellNode.hasClass('rc-picker-cell-disabled')).toBeFalsy(); + expect(onChange).toHaveBeenCalled(); + }); });