diff --git a/docs/examples/basic.tsx b/docs/examples/basic.tsx
index f9012e1e6..e1357e44d 100644
--- a/docs/examples/basic.tsx
+++ b/docs/examples/basic.tsx
@@ -47,7 +47,7 @@ export default () => {
Basic
-
{...sharedProps} locale={zhCN} />
+ {...sharedProps} locale={zhCN} suffixIcon="SUFFIX" />
{...sharedProps} locale={enUS} />
@@ -79,6 +79,7 @@ export default () => {
}
return {};
}}
+ changeOnBlur
/>
diff --git a/docs/examples/range.tsx b/docs/examples/range.tsx
index 7e93df40e..3cb68d413 100644
--- a/docs/examples/range.tsx
+++ b/docs/examples/range.tsx
@@ -1,10 +1,10 @@
-import React from 'react';
import type { Moment } from 'moment';
import moment from 'moment';
-import RangePicker from '../../src/RangePicker';
+import React from 'react';
+import '../../assets/index.less';
import momentGenerateConfig from '../../src/generate/moment';
import zhCN from '../../src/locale/zh_CN';
-import '../../assets/index.less';
+import RangePicker from '../../src/RangePicker';
import './common.less';
const defaultStartValue = moment('2019-09-03 05:02:03');
@@ -70,13 +70,18 @@ export default () => {
ref={rangePickerRef}
showTime
style={{ width: 580 }}
- cellRender={(current, info) =>
{typeof current === "number" ? current : current.get("date")}
}
+ cellRender={(current, info) => (
+
+ {typeof current === 'number' ? current : current.get('date')}
+
+ )}
ranges={{
ranges: [moment(), moment().add(10, 'day')],
}}
onOk={(dates) => {
console.log('OK!!!', dates);
}}
+ changeOnBlur
/>
{...sharedProps}
diff --git a/package.json b/package.json
index 161a6e99b..c3ebeda81 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
"@babel/runtime": "^7.10.1",
"@rc-component/trigger": "^1.5.0",
"classnames": "^2.2.1",
- "rc-util": "^5.27.0"
+ "rc-util": "^5.30.0"
},
"engines": {
"node": ">=8.x"
diff --git a/src/Picker.tsx b/src/Picker.tsx
index d4a63b610..0a26e9b9f 100644
--- a/src/Picker.tsx
+++ b/src/Picker.tsx
@@ -11,8 +11,8 @@
* Tips: Should add faq about `datetime` mode with `defaultValue`
*/
-import classNames from 'classnames';
import type { AlignType } from '@rc-component/trigger/lib/interface';
+import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import warning from 'rc-util/lib/warning';
import * as React from 'react';
@@ -87,6 +87,12 @@ export type PickerSharedProps = {
onContextMenu?: React.MouseEventHandler;
onKeyDown?: (event: React.KeyboardEvent, preventDefault: () => void) => void;
+ /**
+ * Trigger `onChange` event when blur.
+ * If you don't want to user click `confirm` to trigger change, can use this.
+ */
+ changeOnBlur?: boolean;
+
// Internal
/** @private Internal usage, do not use in production mode!!! */
pickerRef?: React.MutableRefObject;
@@ -182,6 +188,7 @@ function InnerPicker(props: PickerProps) {
direction,
autoComplete = 'off',
inputRender,
+ changeOnBlur,
} = props as MergedPickerProps;
const inputRef = React.useRef(null);
@@ -301,6 +308,14 @@ function InnerPicker(props: PickerProps) {
};
// ============================= Input =============================
+ const onInternalBlur: React.FocusEventHandler = (e) => {
+ if (changeOnBlur) {
+ triggerChange(selectedValue);
+ }
+
+ onBlur?.(e);
+ };
+
const [inputProps, { focused, typing }] = usePickerInput({
blurToCancel: needConfirmButton,
open: mergedOpen,
@@ -336,7 +351,8 @@ function InnerPicker(props: PickerProps) {
onKeyDown?.(e, preventDefault);
},
onFocus,
- onBlur,
+ onBlur: onInternalBlur,
+ changeOnBlur,
});
// ============================= Sync ==============================
@@ -450,7 +466,17 @@ function InnerPicker(props: PickerProps) {
let suffixNode: React.ReactNode;
if (suffixIcon) {
- suffixNode = {suffixIcon};
+ suffixNode = (
+ {
+ // Not lost focus
+ e.preventDefault();
+ }}
+ >
+ {suffixIcon}
+
+ );
}
let clearNode: React.ReactNode;
diff --git a/src/RangePicker.tsx b/src/RangePicker.tsx
index fff3cfa91..5fbf0984d 100644
--- a/src/RangePicker.tsx
+++ b/src/RangePicker.tsx
@@ -1,5 +1,6 @@
import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
+import raf from 'rc-util/lib/raf';
import warning from 'rc-util/lib/warning';
import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
@@ -10,6 +11,7 @@ import useHoverValue from './hooks/useHoverValue';
import usePickerInput from './hooks/usePickerInput';
import usePresets from './hooks/usePresets';
import useRangeDisabled from './hooks/useRangeDisabled';
+import useRangeOpen from './hooks/useRangeOpen';
import useRangeViewDates from './hooks/useRangeViewDates';
import useTextValueMapping from './hooks/useTextValueMapping';
import useValueTexts from './hooks/useValueTexts';
@@ -130,6 +132,11 @@ export type RangePickerSharedProps = {
dateRender?: RangeDateRender;
cellRender?: CellRender;
panelRender?: (originPanel: React.ReactNode) => React.ReactNode;
+ /**
+ * Trigger `onChange` event when blur.
+ * If you don't want to user click `confirm` to trigger change, can use this.
+ */
+ changeOnBlur?: boolean;
};
type OmitPickerProps = Omit<
@@ -244,13 +251,11 @@ function InnerRangePicker(props: RangePickerProps) {
direction,
activePickerIndex,
autoComplete = 'off',
+ changeOnBlur,
} = props as MergedRangePickerProps;
const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time';
- // We record opened status here in case repeat open with picker
- const openRecordsRef = useRef>({});
-
const containerRef = useRef(null);
const panelDivRef = useRef(null);
const startInputDivRef = useRef(null);
@@ -268,11 +273,6 @@ function InnerRangePicker(props: RangePickerProps) {
// ============================= Misc ==============================
const formatList = toArray(getDefaultFormat(format, picker, showTime, use12Hours));
- // Active picker
- const [mergedActivePickerIndex, setMergedActivePickerIndex] = useMergedState<0 | 1>(0, {
- value: activePickerIndex,
- });
-
// Operation ref
const operationRef: React.MutableRefObject =
useRef(null);
@@ -343,6 +343,22 @@ function InnerRangePicker(props: RangePickerProps) {
}
};
+ // ============================= Open ==============================
+ const [mergedOpen, mergedActivePickerIndex, firstTimeOpen, triggerOpen] = useRangeOpen(
+ defaultOpen,
+ open,
+ activePickerIndex,
+ changeOnBlur,
+ startInputRef,
+ endInputRef,
+ getValue(selectedValue, 0),
+ getValue(selectedValue, 1),
+ onOpenChange,
+ );
+
+ const startOpen = mergedOpen && mergedActivePickerIndex === 0;
+ const endOpen = mergedOpen && mergedActivePickerIndex === 1;
+
// ========================= Disable Date ==========================
const [disabledStartDate, disabledEndDate] = useRangeDisabled(
{
@@ -353,29 +369,9 @@ function InnerRangePicker(props: RangePickerProps) {
disabledDate,
generateConfig,
},
- openRecordsRef.current[1],
- openRecordsRef.current[0],
+ firstTimeOpen,
);
- // ============================= Open ==============================
- const [mergedOpen, triggerInnerOpen] = useMergedState(false, {
- value: open,
- defaultValue: defaultOpen,
- postState: (postOpen) => (mergedDisabled[mergedActivePickerIndex] ? false : postOpen),
- onChange: (newOpen) => {
- if (onOpenChange) {
- onOpenChange(newOpen);
- }
-
- if (!newOpen && operationRef.current && operationRef.current.onClose) {
- operationRef.current.onClose();
- }
- },
- });
-
- const startOpen = mergedOpen && mergedActivePickerIndex === 0;
- const endOpen = mergedOpen && mergedActivePickerIndex === 1;
-
// ============================= Popup =============================
// Popup min width
const [popupMinWidth, setPopupMinWidth] = useState(0);
@@ -386,38 +382,10 @@ function InnerRangePicker(props: RangePickerProps) {
}, [mergedOpen]);
// ============================ Trigger ============================
- const triggerRef = React.useRef();
-
- function triggerOpen(newOpen: boolean, index: 0 | 1) {
- if (newOpen) {
- clearTimeout(triggerRef.current);
- openRecordsRef.current[index] = true;
-
- setMergedActivePickerIndex(index);
- triggerInnerOpen(newOpen);
-
- // Open to reset view date
- if (!mergedOpen) {
- setViewDate(null, index);
- }
- } else if (mergedActivePickerIndex === index) {
- triggerInnerOpen(newOpen);
-
- // Clean up async
- // This makes ref not quick refresh in case user open another input with blur trigger
- const openRecords = openRecordsRef.current;
- triggerRef.current = setTimeout(() => {
- if (openRecords === openRecordsRef.current) {
- openRecordsRef.current = {};
- }
- });
- }
- }
-
function triggerOpenAndFocus(index: 0 | 1) {
- triggerOpen(true, index);
+ triggerOpen(true, index, 'open');
// Use setTimeout to make sure panel DOM exists
- setTimeout(() => {
+ raf(() => {
const inputRef = [startInputRef, endInputRef][index];
if (inputRef.current) {
inputRef.current.focus();
@@ -451,11 +419,6 @@ function InnerRangePicker(props: RangePickerProps) {
startValue = null;
values = [null, endValue];
}
-
- // Clean up cache since invalidate
- openRecordsRef.current = {
- [sourceIndex]: true,
- };
} else if (picker !== 'time' || order !== false) {
// Reorder when in same date
values = reorderValues(values, generateConfig);
@@ -497,28 +460,6 @@ function InnerRangePicker(props: RangePickerProps) {
onChange(values, [startStr, endStr]);
}
}
-
- // >>>>> Open picker when
-
- // Always open another picker if possible
- let nextOpenIndex: 0 | 1 = null;
- if (sourceIndex === 0 && !mergedDisabled[1]) {
- nextOpenIndex = 1;
- } else if (sourceIndex === 1 && !mergedDisabled[0]) {
- nextOpenIndex = 0;
- }
-
- if (
- nextOpenIndex !== null &&
- nextOpenIndex !== mergedActivePickerIndex &&
- (!openRecordsRef.current[nextOpenIndex] || !getValue(values, nextOpenIndex)) &&
- getValue(values, sourceIndex)
- ) {
- // Delay to focus to avoid input blur trigger expired selectedValues
- triggerOpenAndFocus(nextOpenIndex);
- } else {
- triggerOpen(false, sourceIndex);
- }
}
const forwardKeyDown = (e: React.KeyboardEvent) => {
@@ -616,10 +557,20 @@ function InnerRangePicker(props: RangePickerProps) {
};
// ============================= Input =============================
+ const onInternalBlur: React.FocusEventHandler = (e) => {
+ if (changeOnBlur) {
+ const selectedIndexValue = getValue(selectedValue, mergedActivePickerIndex);
+ if (selectedIndexValue) {
+ triggerChange(selectedValue, mergedActivePickerIndex);
+ }
+ }
+ return onBlur?.(e);
+ };
+
const getSharedInputHookProps = (index: 0 | 1, resetText: () => void) => ({
- blurToCancel: needConfirmButton,
+ blurToCancel: !changeOnBlur && needConfirmButton,
forwardKeyDown,
- onBlur,
+ onBlur: onInternalBlur,
isClickOutside: (target: EventTarget | null) =>
!elementsContains(
[
@@ -631,13 +582,21 @@ function InnerRangePicker(props: RangePickerProps) {
target as HTMLElement,
),
onFocus: (e: React.FocusEvent) => {
- setMergedActivePickerIndex(index);
if (onFocus) {
onFocus(e);
}
},
triggerOpen: (newOpen: boolean) => {
- triggerOpen(newOpen, index);
+ if (newOpen) {
+ triggerOpen(newOpen, index, 'open');
+ } else {
+ triggerOpen(
+ newOpen,
+ // Close directly if no selected value provided
+ getValue(selectedValue, index) ? index : false,
+ 'blur',
+ );
+ }
},
onSubmit: () => {
if (
@@ -651,30 +610,36 @@ function InnerRangePicker(props: RangePickerProps) {
triggerChange(selectedValue, index);
resetText();
+
+ // Switch
+ triggerOpen(false, mergedActivePickerIndex, 'confirm');
},
onCancel: () => {
- triggerOpen(false, index);
+ triggerOpen(false, index, 'cancel');
setSelectedValue(mergedValue);
resetText();
},
});
+ const sharedPickerInput = {
+ onKeyDown: (e, preventDefault) => {
+ onKeyDown?.(e, preventDefault);
+ },
+ changeOnBlur,
+ };
+
const [startInputProps, { focused: startFocused, typing: startTyping }] = usePickerInput({
...getSharedInputHookProps(0, resetStartText),
open: startOpen,
value: startText,
- onKeyDown: (e, preventDefault) => {
- onKeyDown?.(e, preventDefault);
- },
+ ...sharedPickerInput,
});
const [endInputProps, { focused: endFocused, typing: endTyping }] = usePickerInput({
...getSharedInputHookProps(1, resetEndText),
open: endOpen,
value: endText,
- onKeyDown: (e, preventDefault) => {
- onKeyDown?.(e, preventDefault);
- },
+ ...sharedPickerInput,
});
// ========================== Click Picker ==========================
@@ -758,7 +723,7 @@ function InnerRangePicker(props: RangePickerProps) {
monthCellRender,
dateRender,
});
-
+
const panelDateRender = React.useMemo(() => {
if (!mergedCellRender) return undefined;
return (date: DateType, info: CellRenderInfo) =>
@@ -943,12 +908,13 @@ function InnerRangePicker(props: RangePickerProps) {
locale,
// rangeList,
onOk: () => {
- if (getValue(selectedValue, mergedActivePickerIndex)) {
- // triggerChangeOld(selectedValue);
+ const selectedIndexValue = getValue(selectedValue, mergedActivePickerIndex);
+ if (selectedIndexValue) {
triggerChange(selectedValue, mergedActivePickerIndex);
- if (onOk) {
- onOk(selectedValue);
- }
+ onOk?.(selectedValue);
+
+ // Switch
+ triggerOpen(false, mergedActivePickerIndex, 'confirm');
}
},
});
@@ -1001,7 +967,7 @@ function InnerRangePicker(props: RangePickerProps) {
presets={presetList}
onClick={(nextValue) => {
triggerChange(nextValue, null);
- triggerOpen(false, mergedActivePickerIndex);
+ triggerOpen(false, mergedActivePickerIndex, 'preset');
}}
onHover={(hoverValue) => {
setRangeHoverValue(hoverValue);
@@ -1051,7 +1017,17 @@ function InnerRangePicker(props: RangePickerProps) {
// ============================= Icons =============================
let suffixNode: React.ReactNode;
if (suffixIcon) {
- suffixNode = {suffixIcon};
+ suffixNode = (
+ {
+ // Not lost focus
+ e.preventDefault();
+ }}
+ >
+ {suffixIcon}
+
+ );
}
let clearNode: React.ReactNode;
@@ -1079,7 +1055,7 @@ function InnerRangePicker(props: RangePickerProps) {
}
triggerChange(values, null);
- triggerOpen(false, mergedActivePickerIndex);
+ triggerOpen(false, mergedActivePickerIndex, 'clear');
}}
className={`${prefixCls}-clear`}
>
@@ -1117,6 +1093,9 @@ function InnerRangePicker(props: RangePickerProps) {
} else {
onEndLeave();
}
+
+ // Switch
+ triggerOpen(false, mergedActivePickerIndex, 'confirm');
} else {
setSelectedValue(values);
}
diff --git a/src/hooks/usePickerInput.ts b/src/hooks/usePickerInput.ts
index d2663daec..5e0ffac65 100644
--- a/src/hooks/usePickerInput.ts
+++ b/src/hooks/usePickerInput.ts
@@ -1,4 +1,5 @@
import KeyCode from 'rc-util/lib/KeyCode';
+import raf from 'rc-util/lib/raf';
import type * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { addGlobalMouseDownEvent, getTargetFromEvent } from '../utils/uiUtil';
@@ -15,6 +16,7 @@ export default function usePickerInput({
onCancel,
onFocus,
onBlur,
+ changeOnBlur,
}: {
open: boolean;
value: string;
@@ -27,6 +29,7 @@ export default function usePickerInput({
onCancel: () => void;
onFocus?: React.FocusEventHandler;
onBlur?: React.FocusEventHandler;
+ changeOnBlur?: boolean;
}): [React.DOMAttributes, { focused: boolean; typing: boolean }] {
const [typing, setTyping] = useState(false);
const [focused, setFocused] = useState(false);
@@ -130,9 +133,7 @@ export default function usePickerInput({
}
setFocused(false);
- if (onBlur) {
- onBlur(e);
- }
+ onBlur?.(e);
},
};
@@ -156,14 +157,12 @@ export default function usePickerInput({
preventBlurRef.current = true;
// Always set back in case `onBlur` prevented by user
- requestAnimationFrame(() => {
+ raf(() => {
preventBlurRef.current = false;
});
- } else if (!focused || clickedOutside) {
+ } else if (!changeOnBlur && (!focused || clickedOutside)) {
triggerOpen(false);
}
- } else if (focused && !clickedOutside) {
- preventBlurRef.current = true;
}
}),
);
diff --git a/src/hooks/useRangeDisabled.ts b/src/hooks/useRangeDisabled.ts
index c01b81673..a201da5d7 100644
--- a/src/hooks/useRangeDisabled.ts
+++ b/src/hooks/useRangeDisabled.ts
@@ -1,8 +1,8 @@
import * as React from 'react';
-import type { RangeValue, PickerMode, Locale } from '../interface';
-import { getValue } from '../utils/miscUtil';
import type { GenerateConfig } from '../generate';
-import { isSameDate, getQuarter } from '../utils/dateUtil';
+import type { Locale, PickerMode, RangeValue } from '../interface';
+import { getQuarter, isSameDate } from '../utils/dateUtil';
+import { getValue } from '../utils/miscUtil';
export default function useRangeDisabled(
{
@@ -20,8 +20,7 @@ export default function useRangeDisabled(
locale: Locale;
generateConfig: GenerateConfig;
},
- disabledStart: boolean,
- disabledEnd: boolean,
+ firstTimeOpen: boolean,
) {
const startDate = getValue(selectedValue, 0);
const endDate = getValue(selectedValue, 1);
@@ -54,7 +53,7 @@ export default function useRangeDisabled(
}
// Disabled part
- if (disabledStart && endDate) {
+ if (!firstTimeOpen && endDate) {
switch (picker) {
case 'quarter':
return quarterNumber(date) > quarterNumber(endDate);
@@ -71,7 +70,7 @@ export default function useRangeDisabled(
return false;
},
- [disabledDate, disabled[1], endDate, disabledStart],
+ [disabledDate, disabled[1], endDate, firstTimeOpen],
);
const disabledEndDate = React.useCallback(
@@ -88,7 +87,7 @@ export default function useRangeDisabled(
}
// Disabled part
- if (disabledEnd && startDate) {
+ if (!firstTimeOpen && startDate) {
switch (picker) {
case 'quarter':
return quarterNumber(date) < quarterNumber(startDate);
@@ -106,7 +105,7 @@ export default function useRangeDisabled(
return false;
},
- [disabledDate, disabled[0], startDate, disabledEnd],
+ [disabledDate, disabled[0], startDate, firstTimeOpen],
);
return [disabledStartDate, disabledEndDate];
diff --git a/src/hooks/useRangeOpen.ts b/src/hooks/useRangeOpen.ts
new file mode 100644
index 000000000..f883e91a8
--- /dev/null
+++ b/src/hooks/useRangeOpen.ts
@@ -0,0 +1,129 @@
+import { useMergedState } from 'rc-util';
+import useEvent from 'rc-util/lib/hooks/useEvent';
+import raf from 'rc-util/lib/raf';
+import * as React from 'react';
+
+/**
+ * 1. Click input to show picker
+ * 2. Calculate next open index
+ *
+ * If click `confirm`:
+ * 3. Hide current picker
+ * 4. Open next index picker if exist
+ *
+ * If not `changeOnBlur` and click outside:
+ * 3. Hide picker
+ *
+ * If `changeOnBlur` and click outside:
+ * 3. Hide current picker
+ * 4. Open next index picker if exist
+ */
+
+export type SourceType = 'open' | 'blur' | 'confirm' | 'cancel' | 'clear' | 'preset';
+
+/**
+ * Auto control of open state
+ */
+export default function useRangeOpen(
+ defaultOpen: boolean,
+ open: boolean,
+ activePickerIndex: 0 | 1 | undefined,
+ changeOnBlur: boolean,
+ startInputRef: React.RefObject,
+ endInputRef: React.RefObject,
+ startSelectedValue: any,
+ endSelectedValue: any,
+ onOpenChange?: (open: boolean) => void,
+): [
+ open: boolean,
+ activeIndex: 0 | 1,
+ firstTimeOpen: boolean,
+ triggerOpen: (open: boolean, activeIndex: 0 | 1 | false, source: SourceType) => void,
+] {
+ // We record opened status here in case repeat open with picker
+ // const [openRecord, setOpenRecord] = React.useState<{
+ // 0?: boolean;
+ // 1?: boolean;
+ // }>({});
+
+ const [firstTimeOpen, setFirstTimeOpen] = React.useState(false);
+
+ const [mergedOpen, setMergedOpen] = useMergedState(defaultOpen || false, {
+ value: open,
+ onChange: (nextOpen) => {
+ onOpenChange?.(nextOpen);
+ },
+ });
+
+ const [mergedActivePickerIndex, setMergedActivePickerIndex] = useMergedState<0 | 1>(0, {
+ value: activePickerIndex,
+ });
+
+ const [nextActiveIndex, setNextActiveIndex] = React.useState<0 | 1>(null);
+
+ const triggerOpen = useEvent((nextOpen: boolean, index: 0 | 1 | false, source: SourceType) => {
+ // console.error('✅', nextOpen, index, source, startSelectedValue, endSelectedValue);
+
+ if (index === false) {
+ // Only when `nextOpen` is false and no need open to next index
+ setMergedOpen(nextOpen);
+ } else if (nextOpen) {
+ setMergedActivePickerIndex(index);
+ setMergedOpen(nextOpen);
+
+ const nextIndex = index === 0 ? 1 : 0;
+
+ // Record next open index
+ if (
+ !mergedOpen ||
+ // Also set next index if next is empty
+ ![startSelectedValue, endSelectedValue][nextIndex]
+ ) {
+ // Reset open record
+ // setOpenRecord({
+ // [index]: true,
+ // });
+ setFirstTimeOpen(true);
+ setNextActiveIndex(nextIndex);
+ } else {
+ // setOpenRecord((ori) => ({
+ // ...ori,
+ // [index]: true,
+ // }));
+ setFirstTimeOpen(false);
+
+ if (nextActiveIndex !== null) {
+ setNextActiveIndex(null);
+ }
+ }
+ } else if (source === 'confirm' || (source === 'blur' && changeOnBlur)) {
+ // Close if current value is empty
+ // const selectedValue = [startSelectedValue, endSelectedValue][index];
+
+ // if (!selectedValue) {
+ // setMergedOpen(false);
+ // return;
+ // }
+
+ if (nextActiveIndex !== null) {
+ setFirstTimeOpen(false);
+ setMergedActivePickerIndex(nextActiveIndex);
+ }
+ setNextActiveIndex(null);
+
+ // Focus back
+ if (nextActiveIndex !== null) {
+ raf(() => {
+ const ref = [startInputRef, endInputRef][nextActiveIndex];
+ ref.current?.focus();
+ });
+ } else {
+ setMergedOpen(false);
+ }
+ } else {
+ setMergedOpen(false);
+ }
+ });
+
+ return [mergedOpen, mergedActivePickerIndex, firstTimeOpen, triggerOpen];
+}
diff --git a/tests/blur.spec.tsx b/tests/blur.spec.tsx
new file mode 100644
index 000000000..65c4c926b
--- /dev/null
+++ b/tests/blur.spec.tsx
@@ -0,0 +1,70 @@
+import { fireEvent, render } from '@testing-library/react';
+import React from 'react';
+import { getMoment, MomentPicker, MomentRangePicker } from './util/commonUtil';
+
+describe('Picker.ChangeOnBlur', () => {
+ beforeEach(() => {
+ jest.useFakeTimers().setSystemTime(getMoment('1990-09-03 00:00:00').valueOf());
+ });
+
+ afterEach(() => {
+ jest.clearAllTimers();
+ jest.useRealTimers();
+ });
+
+ it('Picker', () => {
+ const onSelect = jest.fn();
+ const onChange = jest.fn();
+
+ const { container } = render(
+ <>
+
+
+ >,
+ );
+
+ // Open
+ fireEvent.click(container.querySelector('input'));
+ fireEvent.focus(container.querySelector('input'));
+
+ fireEvent.click(document.querySelector('.rc-picker-cell-inner'));
+ expect(onSelect).toHaveBeenCalled();
+ expect(onChange).not.toHaveBeenCalled();
+
+ container.querySelector('.outside').focus();
+ fireEvent.blur(container.querySelector('input'));
+ expect(onChange).toHaveBeenCalled();
+ });
+
+ it.only('RangePicker', () => {
+ const onChange = jest.fn();
+
+ const { container } = render(
+ <>
+
+
+ >,
+ );
+
+ // Open
+ fireEvent.mouseDown(container.querySelectorAll('input')[0]);
+ fireEvent.click(container.querySelectorAll('input')[0]);
+ fireEvent.focus(container.querySelectorAll('input')[0]);
+
+ fireEvent.click(document.querySelectorAll('.rc-picker-cell-inner')[0]);
+ expect(onChange).not.toHaveBeenCalled();
+
+ // Second Input
+ fireEvent.mouseDown(container.querySelectorAll('input')[1]);
+ fireEvent.click(container.querySelectorAll('input')[1]);
+ fireEvent.focus(container.querySelectorAll('input')[1]);
+
+ const cells = document.querySelectorAll('.rc-picker-time-panel-cell-inner');
+ fireEvent.click(cells[cells.length - 1]);
+
+ // Blur
+ container.querySelector('.outside').focus();
+ fireEvent.blur(container.querySelectorAll('input')[1]);
+ expect(onChange).toHaveBeenCalled();
+ });
+});
diff --git a/tests/picker.spec.tsx b/tests/picker.spec.tsx
index 7c4f0d2b8..34783e0de 100644
--- a/tests/picker.spec.tsx
+++ b/tests/picker.spec.tsx
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-loop-func */
import { act, createEvent, fireEvent, render } from '@testing-library/react';
-import moment, { Moment } from 'moment';
+import type { Moment } from 'moment';
+import moment from 'moment';
import KeyCode from 'rc-util/lib/KeyCode';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import { resetWarned } from 'rc-util/lib/warning';
@@ -341,19 +342,27 @@ describe('Picker.Basic', () => {
expect(mouseDownEvent.defaultPrevented).toBeTruthy();
});
- it('not fire blur when clickinside and is in focus ', () => {
+ it('not fire blur when clickinside and is in focus', () => {
const onBlur = jest.fn();
const { container } = render(
X} />,
);
+
+ const $input = container.querySelector('input');
+
openPicker(container);
+ $input.focus();
keyDown(KeyCode.ESC);
- fireEvent.mouseDown(container.querySelector('.suffix-icon'));
- fireEvent.blur(container.querySelector('input'));
- expect(onBlur).toHaveBeenCalledTimes(0);
- fireEvent.blur(container.querySelector('input'));
- expect(onBlur).toHaveBeenCalledTimes(1);
+ expect(document.activeElement).toBe($input);
+
+ // Click suffix should preventDefault
+ const $suffix = container.querySelector('.suffix-icon');
+ const mouseDownEvent = createEvent.mouseDown($suffix);
+ mouseDownEvent.preventDefault = jest.fn();
+ fireEvent($suffix, mouseDownEvent);
+
+ expect(mouseDownEvent.preventDefault).toHaveBeenCalled();
});
describe('full steps', () => {
@@ -637,7 +646,7 @@ describe('Picker.Basic', () => {
it(`should show integer when step is not integer (${unit})`, () => {
const props = {
[`${unit}Step`]: 5.5,
- }
+ };
const { container } = render(
);
openPicker(container);
expect(document.querySelectorAll('.rc-picker-time-panel-column')[index]).toMatchSnapshot();
diff --git a/tests/range.spec.tsx b/tests/range.spec.tsx
index 63869f5cc..e075fc61a 100644
--- a/tests/range.spec.tsx
+++ b/tests/range.spec.tsx
@@ -41,7 +41,7 @@ describe('Picker.Range', () => {
});
function keyDown(container: HTMLElement, index: number, keyCode: number) {
- fireEvent.keyDown(container.querySelectorAll('input')[0], {
+ fireEvent.keyDown(container.querySelectorAll('input')[index], {
keyCode,
which: keyCode,
charCode: keyCode,
@@ -92,6 +92,7 @@ describe('Picker.Range', () => {
onCalendarChange.mockReset();
selectCell(14);
+ expect(onChange).toHaveBeenCalled();
expect(isSame(onChange.mock.calls[0][0][0], '1990-09-13')).toBeTruthy();
expect(isSame(onChange.mock.calls[0][0][1], '1990-09-14')).toBeTruthy();
expect(onChange.mock.calls[0][1]).toEqual(['1990-09-13', '1990-09-14']);
@@ -577,7 +578,7 @@ describe('Picker.Range', () => {
// document.querySelector('input').last().simulate('keyDown', {
// which: KeyCode.ENTER,
// });
- keyDown(container, 1, KeyCode.ENTER);
+ keyDown(container, 0, KeyCode.ENTER);
expect(onChange).not.toHaveBeenCalled();
});
@@ -1152,11 +1153,13 @@ describe('Picker.Range', () => {
openPicker(container, 0);
inputValue('1990-11-28');
- closePicker(container, 0);
+ // closePicker(container, 0);
+ keyDown(container, 0, KeyCode.ENTER);
expect(isOpen()).toBeTruthy();
inputValue('1991-01-01');
- closePicker(container, 1);
+ // closePicker(container, 1);
+ keyDown(container, 1, KeyCode.ENTER);
expect(isOpen()).toBeFalsy();
});
@@ -1168,11 +1171,13 @@ describe('Picker.Range', () => {
openPicker(container, 0);
inputValue('1990-11-28');
- closePicker(container, 0);
+ keyDown(container, 0, KeyCode.ENTER);
+ // closePicker(container, 0);
expect(isOpen()).toBeTruthy();
inputValue('1990-12-23');
- closePicker(container, 1);
+ // closePicker(container, 1);
+ keyDown(container, 1, KeyCode.ENTER);
expect(isOpen()).toBeFalsy();
});
@@ -1183,11 +1188,13 @@ describe('Picker.Range', () => {
openPicker(container, 0);
inputValue('1989-01-20');
- closePicker(container, 0);
+ // closePicker(container, 0);
+ keyDown(container, 0, KeyCode.ENTER);
expect(isOpen()).toBeTruthy();
inputValue('1989-01-25');
- closePicker(container, 1);
+ // closePicker(container, 1);
+ keyDown(container, 1, KeyCode.ENTER);
expect(isOpen()).toBeFalsy();
});
});
@@ -1197,11 +1204,13 @@ describe('Picker.Range', () => {
openPicker(container, 1);
inputValue('1990-11-28', 1);
- closePicker(container, 1);
+ keyDown(container, 1, KeyCode.ENTER);
+ // closePicker(container, 1);
expect(isOpen()).toBeTruthy();
inputValue('1989-01-01');
- closePicker(container, 0);
+ // closePicker(container, 0);
+ keyDown(container, 0, KeyCode.ENTER);
expect(isOpen()).toBeFalsy();
});
@@ -1213,11 +1222,13 @@ describe('Picker.Range', () => {
openPicker(container, 1);
inputValue('1990-11-28', 1);
- closePicker(container, 1);
+ keyDown(container, 1, KeyCode.ENTER);
+ // closePicker(container, 1);
expect(isOpen()).toBeTruthy();
inputValue('1989-01-01');
- closePicker(container, 0);
+ keyDown(container, 0, KeyCode.ENTER);
+ // closePicker(container, 0);
expect(isOpen()).toBeFalsy();
});
@@ -1228,11 +1239,12 @@ describe('Picker.Range', () => {
openPicker(container, 1);
inputValue('1989-01-07', 1);
- closePicker(container, 1);
+ console.log('close!');
+ keyDown(container, 1, KeyCode.ENTER);
expect(isOpen()).toBeTruthy();
inputValue('1989-01-01');
- closePicker(container, 0);
+ keyDown(container, 0, KeyCode.ENTER);
expect(isOpen()).toBeFalsy();
});
});
@@ -1269,21 +1281,15 @@ describe('Picker.Range', () => {
});
it("shouldn't let mousedown blur the input", () => {
jest.useFakeTimers();
- // const preventDefault = jest.fn();
const { container } = render(
);
const node = container.querySelector('.rc-picker');
- // document.querySelector('.rc-picker').simulate('click');
fireEvent.click(node);
act(() => {
jest.runAllTimers();
});
- // document.querySelector('.rc-picker').simulate('mousedown', {
- // preventDefault,
- // });
const mouseDownEvent = createEvent.mouseDown(node);
fireEvent(node, mouseDownEvent);
expect(isOpen()).toBeTruthy();
- // expect(preventDefault).toHaveBeenCalled();
expect(mouseDownEvent.defaultPrevented).toBeTruthy();
jest.useRealTimers();
});
@@ -1514,9 +1520,12 @@ describe('Picker.Range', () => {
expect(document.querySelectorAll('input')[0].value).toBe('1990-09-07');
// back to first panel and clear input value
+ fireEvent.mouseDown(document.querySelectorAll('input')[0]);
fireEvent.focus(document.querySelectorAll('input')[0]);
inputValue('', 0);
+ console.log(container.querySelector('.rc-picker').innerHTML);
+
// reselect date
selectCell(9, 0);
expect(document.querySelectorAll('input')[0].value).toBe('1990-09-09');
@@ -1541,6 +1550,7 @@ describe('Picker.Range', () => {
selectCell(15);
fireEvent.click(document.querySelector('.rc-picker-month-btn'));
+
expect(findCell('Jan')).toHaveClass('rc-picker-cell-disabled');
expect(findCell('Dec')).not.toHaveClass('rc-picker-cell-disabled');
});
@@ -1785,7 +1795,7 @@ describe('Picker.Range', () => {
it('use dateRender and monthCellRender in month range picker', () => {
const { container, baseElement } = render(
{date.get('date')}
}
monthCellRender={(date) => {date.get('month') + 1}
}
/>,
@@ -1796,7 +1806,7 @@ describe('Picker.Range', () => {
it('use dateRender and monthCellRender in date range picker', () => {
const { container, baseElement } = render(
{date.get('date')}
}
monthCellRender={(date) => {date.get('month') + 1}
}
/>,