Skip to content

Commit 04b43d5

Browse files
authored
fix: Calendar should not trigger onChange when date is disabled (#183)
* fix: Not trigger onChange * fix: should pass onSelect * test: Add test case
1 parent 59f8f07 commit 04b43d5

File tree

6 files changed

+93
-46
lines changed

6 files changed

+93
-46
lines changed

examples/calendar.less

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.rc-picker-cell-selected {
2+
background: red;
3+
}

examples/calendar.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import PickerPanel from '../src/PickerPanel';
55
import momentGenerateConfig from '../src/generate/moment';
66
import zhCN from '../src/locale/zh_CN';
77
import '../assets/index.less';
8+
import './calendar.less';
89

910
function dateRender(date: Moment, today: Moment) {
1011
return (
@@ -21,6 +22,12 @@ function dateRender(date: Moment, today: Moment) {
2122
);
2223
}
2324

25+
const disabledProps = {
26+
disabledDate: (date) => date.date() === 10,
27+
onSelect: (d) => console.log('Select:', d.format('YYYY-MM-DD')),
28+
onChange: (d) => console.log('Change:', d.format('YYYY-MM-DD')),
29+
};
30+
2431
export default () => (
2532
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
2633
<div>
@@ -29,13 +36,15 @@ export default () => (
2936
// picker="month"
3037
generateConfig={momentGenerateConfig}
3138
dateRender={dateRender}
39+
{...disabledProps}
3240
/>
3341
</div>
3442
<div>
3543
<Picker<Moment>
3644
locale={zhCN}
3745
generateConfig={momentGenerateConfig}
3846
dateRender={dateRender}
47+
{...disabledProps}
3948
/>
4049
</div>
4150
</div>

src/Picker.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
170170
onMouseLeave,
171171
onContextMenu,
172172
onClick,
173+
onSelect,
173174
direction,
174175
autoComplete = 'off',
175176
} = props as MergedPickerProps<DateType>;
@@ -195,16 +196,16 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
195196
const [selectedValue, setSelectedValue] = React.useState<DateType | null>(mergedValue);
196197

197198
// Operation ref
198-
const operationRef: React.MutableRefObject<ContextOperationRefProps | null> = React.useRef<
199-
ContextOperationRefProps
200-
>(null);
199+
const operationRef: React.MutableRefObject<ContextOperationRefProps | null> = React.useRef<ContextOperationRefProps>(
200+
null,
201+
);
201202

202203
// Open
203204
const [mergedOpen, triggerInnerOpen] = useMergedState(false, {
204205
value: open,
205206
defaultValue: defaultOpen,
206-
postState: postOpen => (disabled ? false : postOpen),
207-
onChange: newOpen => {
207+
postState: (postOpen) => (disabled ? false : postOpen),
208+
onChange: (newOpen) => {
208209
if (onOpenChange) {
209210
onOpenChange(newOpen);
210211
}
@@ -224,7 +225,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
224225

225226
const [text, triggerTextChange, resetText] = useTextValueMapping({
226227
valueTexts,
227-
onTextChange: newText => {
228+
onTextChange: (newText) => {
228229
const inputDate = parseValue(newText, {
229230
locale,
230231
formatList,
@@ -292,7 +293,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
292293
value: text,
293294
triggerOpen,
294295
forwardKeyDown,
295-
isClickOutside: target =>
296+
isClickOutside: (target) =>
296297
!elementsContains([panelDivRef.current, inputDivRef.current], target as HTMLElement),
297298
onSubmit: () => {
298299
if (disabledDate && disabledDate(selectedValue)) {
@@ -364,6 +365,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
364365
style: undefined,
365366
pickerValue: undefined,
366367
onPickerValueChange: undefined,
368+
onChange: null,
367369
};
368370

369371
let panelNode: React.ReactNode = (
@@ -376,7 +378,10 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
376378
value={selectedValue}
377379
locale={locale}
378380
tabIndex={-1}
379-
onChange={setSelectedValue}
381+
onSelect={(date) => {
382+
onSelect?.(date);
383+
setSelectedValue(date);
384+
}}
380385
direction={direction}
381386
/>
382387
);
@@ -388,7 +393,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
388393
const panel = (
389394
<div
390395
className={`${prefixCls}-panel-container`}
391-
onMouseDown={e => {
396+
onMouseDown={(e) => {
392397
e.preventDefault();
393398
}}
394399
>
@@ -405,11 +410,11 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
405410
if (allowClear && mergedValue && !disabled) {
406411
clearNode = (
407412
<span
408-
onMouseDown={e => {
413+
onMouseDown={(e) => {
409414
e.preventDefault();
410415
e.stopPropagation();
411416
}}
412-
onMouseUp={e => {
417+
onMouseUp={(e) => {
413418
e.preventDefault();
414419
e.stopPropagation();
415420
triggerChange(null);
@@ -497,7 +502,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
497502
disabled={disabled}
498503
readOnly={inputReadOnly || typeof formatList[0] === 'function' || !typing}
499504
value={hoverValue || text}
500-
onChange={e => {
505+
onChange={(e) => {
501506
triggerTextChange(e.target.value);
502507
}}
503508
autoFocus={autoFocus}

src/PickerPanel.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
193193
const [mergedValue, setInnerValue] = useMergedState(null, {
194194
value,
195195
defaultValue,
196-
postState: val => {
196+
postState: (val) => {
197197
if (!val && defaultOpenValue && picker === 'time') {
198198
return defaultOpenValue;
199199
}
@@ -205,7 +205,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
205205
const [viewDate, setInnerViewDate] = useMergedState<DateType | null, DateType>(null, {
206206
value: pickerValue,
207207
defaultValue: defaultPickerValue || mergedValue,
208-
postState: date => date || generateConfig.getNow(),
208+
postState: (date) => date || generateConfig.getNow(),
209209
});
210210

211211
const setViewDate = (date: DateType) => {
@@ -270,7 +270,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
270270
onContextSelect(date, type);
271271
}
272272

273-
if (onChange && !isEqual(generateConfig, date, mergedValue)) {
273+
if (onChange && !isEqual(generateConfig, date, mergedValue) && !disabledDate?.(date)) {
274274
onChange(date);
275275
}
276276
}
@@ -307,7 +307,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
307307
/* eslint-enable no-lone-blocks */
308308
};
309309

310-
const onInternalBlur: React.FocusEventHandler<HTMLElement> = e => {
310+
const onInternalBlur: React.FocusEventHandler<HTMLElement> = (e) => {
311311
if (panelRef.current && panelRef.current.onBlur) {
312312
panelRef.current.onBlur(e);
313313
}

tests/keyboard.spec.tsx

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -477,35 +477,61 @@ describe('Picker.Keyboard', () => {
477477
expect(preventDefault).toHaveBeenCalled();
478478
});
479479

480-
it('keyboard should not trigger on disabledDate', () => {
481-
const onChange = jest.fn();
482-
const onSelect = jest.fn();
483-
const wrapper = mount(
484-
<MomentPicker
485-
showTime
486-
onSelect={onSelect}
487-
onChange={onChange}
488-
disabledDate={date => date.date() % 2 === 0}
489-
/>,
490-
);
491-
wrapper.find('input').simulate('focus');
492-
wrapper.keyDown(KeyCode.ENTER);
493-
wrapper.keyDown(KeyCode.TAB);
494-
wrapper.keyDown(KeyCode.TAB);
495-
wrapper.keyDown(KeyCode.DOWN);
496-
expect(isSame(onSelect.mock.calls[0][0], '1990-09-10')).toBeTruthy();
480+
describe('keyboard should not trigger on disabledDate', () => {
481+
it('picker', () => {
482+
const onChange = jest.fn();
483+
const onSelect = jest.fn();
484+
const wrapper = mount(
485+
<MomentPicker
486+
showTime
487+
onSelect={onSelect}
488+
onChange={onChange}
489+
disabledDate={date => date.date() % 2 === 0}
490+
/>,
491+
);
492+
wrapper.find('input').simulate('focus');
493+
wrapper.keyDown(KeyCode.ENTER);
494+
wrapper.keyDown(KeyCode.TAB);
495+
wrapper.keyDown(KeyCode.TAB);
496+
wrapper.keyDown(KeyCode.DOWN);
497+
expect(isSame(onSelect.mock.calls[0][0], '1990-09-10')).toBeTruthy();
497498

498-
// Not enter to change
499-
wrapper.keyDown(KeyCode.ENTER);
500-
expect(onChange).not.toHaveBeenCalled();
499+
// Not enter to change
500+
wrapper.keyDown(KeyCode.ENTER);
501+
expect(onChange).not.toHaveBeenCalled();
501502

502-
// Not button enabled
503-
expect(wrapper.find('.rc-picker-ok button').props().disabled).toBeTruthy();
503+
// Not button enabled
504+
expect(wrapper.find('.rc-picker-ok button').props().disabled).toBeTruthy();
504505

505-
// Another can be enter
506-
wrapper.keyDown(KeyCode.RIGHT);
507-
expect(wrapper.find('.rc-picker-ok button').props().disabled).toBeFalsy();
508-
wrapper.keyDown(KeyCode.ENTER);
509-
expect(onChange).toHaveBeenCalled();
506+
// Another can be enter
507+
wrapper.keyDown(KeyCode.RIGHT);
508+
expect(wrapper.find('.rc-picker-ok button').props().disabled).toBeFalsy();
509+
wrapper.keyDown(KeyCode.ENTER);
510+
expect(onChange).toHaveBeenCalled();
511+
});
512+
513+
it('panel', () => {
514+
const onChange = jest.fn();
515+
const onSelect = jest.fn();
516+
const wrapper = mount(
517+
<MomentPickerPanel
518+
onSelect={onSelect}
519+
onChange={onChange}
520+
disabledDate={date => date.date() % 2 === 0}
521+
/>,
522+
);
523+
524+
wrapper.find('.rc-picker-panel').simulate('focus');
525+
526+
// 9-10 is disabled
527+
wrapper.keyDown(KeyCode.DOWN);
528+
expect(isSame(onSelect.mock.calls[0][0], '1990-09-10')).toBeTruthy();
529+
expect(onChange).not.toHaveBeenCalled();
530+
531+
// 9-17 is enabled
532+
wrapper.keyDown(KeyCode.DOWN);
533+
expect(isSame(onSelect.mock.calls[1][0], '1990-09-17')).toBeTruthy();
534+
expect(isSame(onChange.mock.calls[0][0], '1990-09-17')).toBeTruthy();
535+
});
510536
});
511537
});

tests/setup.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ Object.assign(Enzyme.ReactWrapper.prototype, {
7070
this.find('.rc-picker-clear-btn').simulate('mouseUp');
7171
},
7272
keyDown(which, info = {}, index = 0) {
73-
this.find('input')
74-
.at(index)
75-
.simulate('keydown', { ...info, which });
73+
let component = this.find('input');
74+
75+
if (component.length === 0) {
76+
component = this.find('.rc-picker-panel');
77+
}
78+
79+
component.at(index).simulate('keydown', { ...info, which });
7680
},
7781
inputValue(text, index = 0) {
7882
this.find('input')

0 commit comments

Comments
 (0)