From 1dc04ee49469936661acdeb3263705236a6c8ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 1 May 2023 21:59:46 +0800 Subject: [PATCH 01/19] add changeOnBlur --- src/RangePicker.tsx | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/RangePicker.tsx b/src/RangePicker.tsx index fff3cfa91..b26078e79 100644 --- a/src/RangePicker.tsx +++ b/src/RangePicker.tsx @@ -130,6 +130,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,6 +249,7 @@ function InnerRangePicker(props: RangePickerProps) { direction, activePickerIndex, autoComplete = 'off', + changeOnBlur, } = props as MergedRangePickerProps; const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time'; @@ -538,6 +544,17 @@ function InnerRangePicker(props: RangePickerProps) { } }; + // Submit with confirm button + function onInternalOk() { + if (getValue(selectedValue, mergedActivePickerIndex)) { + // triggerChangeOld(selectedValue); + triggerChange(selectedValue, mergedActivePickerIndex); + if (onOk) { + onOk(selectedValue); + } + } + } + // ============================= Text ============================== const sharedTextHooksProps = { formatList, @@ -616,10 +633,17 @@ function InnerRangePicker(props: RangePickerProps) { }; // ============================= Input ============================= + const onInternalBlur: React.FocusEventHandler = (e) => { + if (changeOnBlur) { + onInternalOk(); + } + return onBlur?.(e); + }; + const getSharedInputHookProps = (index: 0 | 1, resetText: () => void) => ({ blurToCancel: needConfirmButton, forwardKeyDown, - onBlur, + onBlur: onInternalBlur, isClickOutside: (target: EventTarget | null) => !elementsContains( [ @@ -758,7 +782,7 @@ function InnerRangePicker(props: RangePickerProps) { monthCellRender, dateRender, }); - + const panelDateRender = React.useMemo(() => { if (!mergedCellRender) return undefined; return (date: DateType, info: CellRenderInfo) => @@ -942,15 +966,7 @@ function InnerRangePicker(props: RangePickerProps) { (disabledDate && disabledDate(selectedValue[mergedActivePickerIndex])), locale, // rangeList, - onOk: () => { - if (getValue(selectedValue, mergedActivePickerIndex)) { - // triggerChangeOld(selectedValue); - triggerChange(selectedValue, mergedActivePickerIndex); - if (onOk) { - onOk(selectedValue); - } - } - }, + onOk: onInternalOk, }); if (picker !== 'time' && !showTime) { From 2760927bf9971f512eb97d71f912bc0f4c11d637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 1 May 2023 22:32:19 +0800 Subject: [PATCH 02/19] support Picker changeOnBlur --- src/Picker.tsx | 19 +++++++++++++++++-- src/RangePicker.tsx | 1 + src/hooks/usePickerInput.ts | 7 +++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Picker.tsx b/src/Picker.tsx index d4a63b610..017ac9cd2 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,7 @@ function InnerPicker(props: PickerProps) { onKeyDown?.(e, preventDefault); }, onFocus, - onBlur, + onBlur: onInternalBlur, }); // ============================= Sync ============================== diff --git a/src/RangePicker.tsx b/src/RangePicker.tsx index b26078e79..3e043b583 100644 --- a/src/RangePicker.tsx +++ b/src/RangePicker.tsx @@ -635,6 +635,7 @@ function InnerRangePicker(props: RangePickerProps) { // ============================= Input ============================= const onInternalBlur: React.FocusEventHandler = (e) => { if (changeOnBlur) { + console.log('???'); onInternalOk(); } return onBlur?.(e); diff --git a/src/hooks/usePickerInput.ts b/src/hooks/usePickerInput.ts index d2663daec..08300ab91 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'; @@ -156,11 +157,13 @@ 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) { - triggerOpen(false); + raf(() => { + triggerOpen(false); + }); } } else if (focused && !clickedOutside) { preventBlurRef.current = true; From 64c010dd190065fb1f0f5f4d1a0bbcbb6f2d07d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 1 May 2023 23:15:20 +0800 Subject: [PATCH 03/19] support RangePicker changeOnBlur --- src/Picker.tsx | 1 + src/RangePicker.tsx | 45 ++++++++++++++++++++----------------- src/hooks/usePickerInput.ts | 8 ++++--- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/Picker.tsx b/src/Picker.tsx index 017ac9cd2..b8f2c7426 100644 --- a/src/Picker.tsx +++ b/src/Picker.tsx @@ -352,6 +352,7 @@ function InnerPicker(props: PickerProps) { }, onFocus, onBlur: onInternalBlur, + changeOnBlur, }); // ============================= Sync ============================== diff --git a/src/RangePicker.tsx b/src/RangePicker.tsx index 3e043b583..5d9fb86a7 100644 --- a/src/RangePicker.tsx +++ b/src/RangePicker.tsx @@ -544,17 +544,6 @@ function InnerRangePicker(props: RangePickerProps) { } }; - // Submit with confirm button - function onInternalOk() { - if (getValue(selectedValue, mergedActivePickerIndex)) { - // triggerChangeOld(selectedValue); - triggerChange(selectedValue, mergedActivePickerIndex); - if (onOk) { - onOk(selectedValue); - } - } - } - // ============================= Text ============================== const sharedTextHooksProps = { formatList, @@ -635,14 +624,19 @@ function InnerRangePicker(props: RangePickerProps) { // ============================= Input ============================= const onInternalBlur: React.FocusEventHandler = (e) => { if (changeOnBlur) { - console.log('???'); - onInternalOk(); + const selectedIndexValue = getValue(selectedValue, mergedActivePickerIndex); + if (selectedIndexValue) { + triggerChange(selectedValue, mergedActivePickerIndex); + if (onOk) { + onOk(selectedValue); + } + } } return onBlur?.(e); }; const getSharedInputHookProps = (index: 0 | 1, resetText: () => void) => ({ - blurToCancel: needConfirmButton, + blurToCancel: !changeOnBlur && needConfirmButton, forwardKeyDown, onBlur: onInternalBlur, isClickOutside: (target: EventTarget | null) => @@ -684,22 +678,25 @@ function InnerRangePicker(props: RangePickerProps) { }, }); + 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 ========================== @@ -967,7 +964,13 @@ function InnerRangePicker(props: RangePickerProps) { (disabledDate && disabledDate(selectedValue[mergedActivePickerIndex])), locale, // rangeList, - onOk: onInternalOk, + onOk: () => { + const selectedIndexValue = getValue(selectedValue, mergedActivePickerIndex); + if (selectedIndexValue) { + triggerChange(selectedValue, mergedActivePickerIndex); + onOk?.(selectedValue); + } + }, }); if (picker !== 'time' && !showTime) { diff --git a/src/hooks/usePickerInput.ts b/src/hooks/usePickerInput.ts index 08300ab91..b61131cae 100644 --- a/src/hooks/usePickerInput.ts +++ b/src/hooks/usePickerInput.ts @@ -16,6 +16,7 @@ export default function usePickerInput({ onCancel, onFocus, onBlur, + changeOnBlur, }: { open: boolean; value: string; @@ -28,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); @@ -160,10 +162,10 @@ export default function usePickerInput({ raf(() => { preventBlurRef.current = false; }); - } else if (!focused || clickedOutside) { - raf(() => { + } else if (!changeOnBlur && (!focused || clickedOutside)) { + // raf(() => { triggerOpen(false); - }); + // }); } } else if (focused && !clickedOutside) { preventBlurRef.current = true; From 5756e855eef2a0324e8a9429beecfa863cbb9bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Tue, 2 May 2023 00:10:28 +0800 Subject: [PATCH 04/19] chore: test of it --- package.json | 2 +- src/hooks/usePickerInput.ts | 6 ++-- tests/blur.spec.tsx | 70 +++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 tests/blur.spec.tsx 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/hooks/usePickerInput.ts b/src/hooks/usePickerInput.ts index b61131cae..6559e096f 100644 --- a/src/hooks/usePickerInput.ts +++ b/src/hooks/usePickerInput.ts @@ -133,9 +133,7 @@ export default function usePickerInput({ } setFocused(false); - if (onBlur) { - onBlur(e); - } + onBlur?.(e); }, }; @@ -164,7 +162,7 @@ export default function usePickerInput({ }); } else if (!changeOnBlur && (!focused || clickedOutside)) { // raf(() => { - triggerOpen(false); + triggerOpen(false); // }); } } else if (focused && !clickedOutside) { 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( + <> + +