Skip to content
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
1 change: 1 addition & 0 deletions src/PickerInput/RangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ function RangePicker<DateType extends object = any>(
setInnerValue,
getCalendarValue,
triggerCalendarChange,
true, // rangeValue
disabled,
formatList,
focused,
Expand Down
18 changes: 16 additions & 2 deletions src/PickerInput/SinglePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import useShowNow from './hooks/useShowNow';
import Popup from './Popup';
import SingleSelector from './Selector/SingleSelector';
import useSemantic from '../hooks/useSemantic';
import { isSameTimestamp } from '../utils/dateUtil';

// TODO: isInvalidateDate with showTime.disabledTime should not provide `range` prop

Expand Down Expand Up @@ -297,7 +298,8 @@ function Picker<DateType extends object = any>(
setInnerValue,
getCalendarValue,
triggerCalendarChange,
[], //disabled,
false, // rangeValue
[], // disabled
formatList,
focused,
mergedOpen,
Expand Down Expand Up @@ -451,6 +453,18 @@ function Picker<DateType extends object = any>(
onSetHover(date, 'cell');
};

const isPopupInvalidateDate = useEvent((date: DateType) => {
if (
multiple &&
Array.isArray(mergedValue) &&
mergedValue.some((valueDate) => isSameTimestamp(generateConfig, valueDate, date))
) {
return false;
}

return isInvalidateDate(date, { activeIndex: 0 });
});
Comment thread
QDyanbing marked this conversation as resolved.

// >>> Focus
const onPanelFocus: React.FocusEventHandler<HTMLElement> = (event) => {
triggerOpen(true);
Expand Down Expand Up @@ -527,7 +541,7 @@ function Picker<DateType extends object = any>(
// Value
format={maskFormat}
value={calendarValue}
isInvalid={isInvalidateDate}
isInvalid={isPopupInvalidateDate}
onChange={null}
onSelect={onPanelSelect}
// PickerValue
Expand Down
66 changes: 61 additions & 5 deletions src/PickerInput/hooks/useRangeValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ function orderDates<DateType extends object, DatesType extends DateType[]>(
return [...dates].sort((a, b) => (generateConfig.isAfter(a, b) ? 1 : -1)) as DatesType;
}

function includesTimestamp<DateType extends object>(
generateConfig: GenerateConfig<DateType>,
dates: DateType[],
date: DateType,
) {
return (
Array.isArray(dates) &&
dates.some((prevDate) => isSameTimestamp(generateConfig, prevDate, date))
);
}
Comment thread
QDyanbing marked this conversation as resolved.

/**
* Used for internal value management.
* It should always use `mergedValue` in render logic
Expand Down Expand Up @@ -171,6 +182,7 @@ export default function useRangeValue<ValueType extends DateType[], DateType ext
setInnerValue: (nextValue: ValueType) => void,
getCalendarValue: () => ValueType,
triggerCalendarChange: TriggerCalendarChange<ValueType>,
rangeValue: boolean,
disabled: ReplaceListType<Required<ValueType>, boolean>,
formatList: FormatType[],
focused: boolean,
Expand Down Expand Up @@ -263,11 +275,55 @@ export default function useRangeValue<ValueType extends DateType[], DateType ext
generateConfig.isAfter(end, start);

// >>> Invalid
const validateDates =
// Validate start
(disabled[0] || !start || !isInvalidateDate(start, { activeIndex: 0 })) &&
// Validate end
(disabled[1] || !end || !isInvalidateDate(end, { from: start, activeIndex: 1 }));
const prevStart = mergedValue[0] || null;
const prevEnd = mergedValue[1] || null;

const startChanged = !isSameTimestamp(generateConfig, prevStart, start || null);
const endChanged = !isSameTimestamp(generateConfig, prevEnd, end || null);

// `validateDates` negates this helper: only a changed/new invalid date
// should block submission, while an unchanged value that was already
// invalid can remain without blocking another field update.
const isInvalidateChangedDate = (
date: DateType,
prevDate: DateType,
changed: boolean,
prevInfo: { from?: DateType; activeIndex: number },
nextInfo: { from?: DateType; activeIndex: number },
) => {
const nextInvalidate = isInvalidateDate(date, nextInfo);

return nextInvalidate && (changed || !prevDate || !isInvalidateDate(prevDate, prevInfo));
};

const validateDates = rangeValue
? // Validate range dates. Existing invalid values should not block other updates.
(disabled[0] ||
!start ||
!isInvalidateChangedDate(
start,
prevStart,
startChanged,
{ activeIndex: 0 },
{ activeIndex: 0 },
)) &&
(disabled[1] ||
!end ||
!isInvalidateChangedDate(
end,
prevEnd,
endChanged,
{ from: prevStart, activeIndex: 1 },
{ from: start, activeIndex: 1 },
))
: // Validate single or multiple dates. Existing values may be disabled by updated `disabledDate`.
clone.every(
(date) =>
!date ||
includesTimestamp(generateConfig, mergedValue, date) ||
!isInvalidateDate(date, { activeIndex: 0 }),
);

// >>> Result
const allPassed =
// Null value is from clear button
Expand Down
38 changes: 38 additions & 0 deletions tests/multiple.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,44 @@ describe('Picker.Multiple', () => {
expect(onChange).toHaveBeenCalledWith(expect.anything(), ['1990-09-01', '1990-09-05']);
});

it('does not block change when existing value is disabled by disabledDate', () => {
const onChange = jest.fn();
const { container } = render(
<DayPicker
multiple
needConfirm
defaultValue={[getDay('1990-09-03')]}
disabledDate={(date) => date < getDay('1990-09-03').endOf('day')}
onChange={onChange}
/>,
);

openPicker(container);
selectCell(5);
fireEvent.click(document.querySelector('.rc-picker-ok button'));

expect(onChange).toHaveBeenCalledWith(expect.anything(), ['1990-09-03', '1990-09-05']);
});

it('blocks adding a preset value disabled by disabledDate', () => {
const onChange = jest.fn();
const { container } = render(
<DayPicker
multiple
needConfirm
defaultValue={[getDay('1990-09-03')]}
disabledDate={(date) => date.isSame(getDay('1990-09-05'), 'date')}
presets={[{ label: 'Disabled', value: getDay('1990-09-05') }]}
onChange={onChange}
/>,
);

openPicker(container);
fireEvent.click(document.querySelector('.rc-picker-presets li'));

expect(onChange).not.toHaveBeenCalled();
});

it('selector remove', () => {
const onChange = jest.fn();
const { container } = render(
Expand Down
93 changes: 93 additions & 0 deletions tests/range.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,99 @@ describe('Picker.Range', () => {
);
});

it('does not block change when existing value is disabled by disabledDate', () => {
const onChange = jest.fn();
const { container } = render(
<DayRangePicker
defaultValue={[getDay('1990-09-03'), getDay('1990-09-05')]}
disabledDate={(date) => date < getDay('1990-09-03').endOf('day')}
onChange={onChange}
/>,
);

openPicker(container, 1);
selectCell(6);
closePicker(container, 1);

expect(onChange).toHaveBeenCalledWith(
[expect.anything(), expect.anything()],
['1990-09-03', '1990-09-06'],
);
});

it('does not block start change when existing end value is disabled by disabledDate', () => {
const onChange = jest.fn();
const { container } = render(
<DayRangePicker
defaultValue={[getDay('1990-09-01'), getDay('1990-09-03')]}
disabledDate={(date) => date.isSame(getDay('1990-09-03'), 'date')}
onChange={onChange}
/>,
);

openPicker(container, 0);
selectCell(2);
closePicker(container, 1);

expect(onChange).toHaveBeenCalledWith(
[expect.anything(), expect.anything()],
['1990-09-02', '1990-09-03'],
);
});

it('blocks start change from disabled date to another disabled date', () => {
const onChange = jest.fn();
const { container } = render(
<DayRangePicker
defaultValue={[getDay('1990-09-01'), getDay('1990-09-05')]}
disabledDate={(date) => date.isBefore(getDay('1990-09-03'), 'date')}
onChange={onChange}
/>,
);

openPicker(container, 0);
inputValue('1990-09-02', 0);
keyDown(container, 0, KeyCode.ENTER);
closePicker(container, 0);

expect(onChange).not.toHaveBeenCalled();
});

it('blocks end change from disabled date to another disabled date', () => {
const onChange = jest.fn();
const { container } = render(
<DayRangePicker
defaultValue={[getDay('1990-09-01'), getDay('1990-09-03')]}
disabledDate={(date: Dayjs, { from }) => !!from && date.isAfter(from.add(1, 'day'))}
onChange={onChange}
/>,
);

openPicker(container, 1);
inputValue('1990-09-04', 1);
keyDown(container, 1, KeyCode.ENTER);
closePicker(container, 1);

expect(onChange).not.toHaveBeenCalled();
});

it('blocks change when existing end value becomes disabled by new start value', () => {
const onChange = jest.fn();
const { container } = render(
<DayRangePicker
defaultValue={[getDay('1990-09-03'), getDay('1990-09-04')]}
disabledDate={(date: Dayjs, { from }) => !!from && date.isAfter(from.add(1, 'day'))}
onChange={onChange}
/>,
);

openPicker(container, 0);
selectCell(2);
closePicker(container, 1);

expect(onChange).not.toHaveBeenCalled();
});

it('should close panel when finish first choose with showTime = true and disabled = [false, true]', () => {
const { baseElement } = render(<DayRangePicker showTime disabled={[false, true]} />);
expect(baseElement.querySelectorAll('.rc-picker-input')).toHaveLength(2);
Expand Down
Loading