Skip to content

Commit 8345e19

Browse files
LeeSSHH黎书行
authored andcommitted
fix: The Enter key lock cannot be released correctly when the custom input calls blur(). (#1113)
Co-authored-by: 黎书行 <[email protected]>
1 parent b24a464 commit 8345e19

File tree

7 files changed

+96
-5
lines changed

7 files changed

+96
-5
lines changed

src/BaseSelect/index.tsx

+10-4
Original file line numberDiff line numberDiff line change
@@ -534,13 +534,13 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
534534
}
535535

536536
if (mergedOpen && (!isEnterKey || !keyLockRef.current)) {
537+
// Lock the Enter key after it is pressed to avoid repeated triggering of the onChange event.
538+
if (isEnterKey) {
539+
keyLockRef.current = true;
540+
}
537541
listRef.current?.onKeyDown(event, ...rest);
538542
}
539543

540-
if (isEnterKey) {
541-
keyLockRef.current = true;
542-
}
543-
544544
onKeyDown?.(event, ...rest);
545545
};
546546

@@ -565,6 +565,11 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
565565
});
566566
};
567567

568+
const onInputBlur = () => {
569+
// Unlock the Enter key after the input blur; otherwise, the Enter key needs to be pressed twice to trigger the correct effect.
570+
keyLockRef.current = false;
571+
};
572+
568573
// ========================== Focus / Blur ==========================
569574
/** Record real focus status */
570575
const focusRef = React.useRef<boolean>(false);
@@ -812,6 +817,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
812817
onSearchSubmit={onInternalSearchSubmit}
813818
onRemove={onSelectorRemove}
814819
tokenWithEnter={tokenWithEnter}
820+
onInputBlur={onInputBlur}
815821
/>
816822
)}
817823
</SelectTrigger>

src/Selector/Input.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface InputProps {
2525
onMouseDown: React.MouseEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
2626
onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
2727
onPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
28+
onBlur: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
2829
onCompositionStart: React.CompositionEventHandler<
2930
HTMLInputElement | HTMLTextAreaElement | HTMLElement
3031
>;
@@ -52,6 +53,7 @@ const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = (props, ref)
5253
onPaste,
5354
onCompositionStart,
5455
onCompositionEnd,
56+
onBlur,
5557
open,
5658
attrs,
5759
} = props;
@@ -66,6 +68,7 @@ const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = (props, ref)
6668
onMouseDown: onOriginMouseDown,
6769
onCompositionStart: onOriginCompositionStart,
6870
onCompositionEnd: onOriginCompositionEnd,
71+
onBlur: onOriginBlur,
6972
style,
7073
} = originProps;
7174

@@ -134,6 +137,12 @@ const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = (props, ref)
134137
}
135138
},
136139
onPaste,
140+
onBlur(event: React.FocusEvent<HTMLElement>) {
141+
onBlur(event);
142+
if (onOriginBlur) {
143+
onOriginBlur(event);
144+
}
145+
},
137146
});
138147

139148
return inputNode;

src/Selector/MultipleSelector.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const SelectSelector: React.FC<SelectorProps> = (props) => {
7272
onInputMouseDown,
7373
onInputCompositionStart,
7474
onInputCompositionEnd,
75+
onInputBlur,
7576
} = props;
7677

7778
const measureRef = React.useRef<HTMLSpanElement>(null);
@@ -221,6 +222,7 @@ const SelectSelector: React.FC<SelectorProps> = (props) => {
221222
onPaste={onInputPaste}
222223
onCompositionStart={onInputCompositionStart}
223224
onCompositionEnd={onInputCompositionEnd}
225+
onBlur={onInputBlur}
224226
tabIndex={tabIndex}
225227
attrs={pickAttrs(props, true)}
226228
/>

src/Selector/SingleSelector.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const SingleSelector: React.FC<SelectorProps> = (props) => {
3636
onInputPaste,
3737
onInputCompositionStart,
3838
onInputCompositionEnd,
39+
onInputBlur,
3940
title,
4041
} = props;
4142

@@ -100,6 +101,7 @@ const SingleSelector: React.FC<SelectorProps> = (props) => {
100101
onPaste={onInputPaste}
101102
onCompositionStart={onInputCompositionStart}
102103
onCompositionEnd={onInputCompositionEnd}
104+
onBlur={onInputBlur}
103105
tabIndex={tabIndex}
104106
attrs={pickAttrs(props, true)}
105107
maxLength={combobox ? maxLength : undefined}

src/Selector/index.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface InnerSelectorProps {
4444
onInputPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
4545
onInputCompositionStart: React.CompositionEventHandler<HTMLInputElement | HTMLTextAreaElement>;
4646
onInputCompositionEnd: React.CompositionEventHandler<HTMLInputElement | HTMLTextAreaElement>;
47+
onInputBlur: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
4748
}
4849

4950
export interface RefSelectorProps {
@@ -92,7 +93,8 @@ export interface SelectorProps {
9293
onSearchSubmit?: (searchText: string) => void;
9394
onRemove: (value: DisplayValueType) => void;
9495
onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
95-
96+
// on inner input blur
97+
onInputBlur?: () => void;
9698
/**
9799
* @private get real dom for trigger align.
98100
* This may be removed after React provides replacement of `findDOMNode`
@@ -119,6 +121,7 @@ const Selector: React.ForwardRefRenderFunction<RefSelectorProps, SelectorProps>
119121
onSearchSubmit,
120122
onToggleOpen,
121123
onInputKeyDown,
124+
onInputBlur,
122125

123126
domRef,
124127
} = props;
@@ -270,6 +273,7 @@ const Selector: React.ForwardRefRenderFunction<RefSelectorProps, SelectorProps>
270273
onInputPaste,
271274
onInputCompositionStart,
272275
onInputCompositionEnd,
276+
onInputBlur,
273277
};
274278

275279
const selectNode =

tests/Select.test.tsx

+50
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
keyUp,
2929
selectItem,
3030
toggleOpen,
31+
waitFakeTimer,
3132
} from './utils/common';
3233

3334
describe('Select.Basic', () => {
@@ -2367,4 +2368,53 @@ describe('Select.Basic', () => {
23672368
expect(element[0]).not.toHaveClass('rc-select-item-option-disabled');
23682369
expect(element[1]).toHaveClass('rc-select-item-option-disabled');
23692370
});
2371+
2372+
it('release Enter key lock after customize input calls blur()', async () => {
2373+
let inputElem: HTMLInputElement | null = null;
2374+
const onBlur = jest.fn();
2375+
const Demo: React.FC = () => {
2376+
const ref = React.useRef<HTMLInputElement>(null);
2377+
const onSelect = () => {
2378+
ref.current!.blur();
2379+
fireEvent.blur(ref.current);
2380+
};
2381+
const getInputElement = () => {
2382+
return <input ref={ref} onBlur={onBlur} />;
2383+
};
2384+
return (
2385+
<Select
2386+
options={[{ value: 'aa' }, { value: 'bb' }]}
2387+
onSelect={onSelect}
2388+
mode="combobox"
2389+
getInputElement={getInputElement}
2390+
/>
2391+
);
2392+
};
2393+
const { container } = render(<Demo />);
2394+
inputElem = container.querySelector('input');
2395+
toggleOpen(container);
2396+
await waitFakeTimer();
2397+
expectOpen(container, true);
2398+
2399+
keyDown(inputElem!, 40);
2400+
keyUp(inputElem!, 40);
2401+
keyDown(inputElem!, 13);
2402+
2403+
await waitFakeTimer();
2404+
expect(onBlur).toHaveBeenCalledTimes(1);
2405+
expectOpen(container, false);
2406+
expect(inputElem.value).toEqual('aa');
2407+
2408+
toggleOpen(container);
2409+
await waitFakeTimer();
2410+
expectOpen(container, true);
2411+
2412+
keyDown(inputElem!, 40);
2413+
keyUp(inputElem!, 40);
2414+
keyDown(inputElem!, 13);
2415+
2416+
await waitFakeTimer();
2417+
expect(onBlur).toHaveBeenCalledTimes(2);
2418+
expect(inputElem.value).toEqual('bb');
2419+
});
23702420
});

tests/utils/common.ts

+18
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,21 @@ export function keyUp(element: HTMLElement, keyCode: number) {
117117
fireEvent(element, event);
118118
});
119119
}
120+
121+
/**
122+
* Wait for a time delay. Will wait `advanceTime * times` ms.
123+
*
124+
* @param advanceTime Default 1000
125+
* @param times Default 20
126+
*/
127+
export async function waitFakeTimer(advanceTime = 1000, times = 20) {
128+
for (let i = 0; i < times; i += 1) {
129+
await act(async () => {
130+
await Promise.resolve();
131+
132+
if (advanceTime > 0) {
133+
jest.advanceTimersByTime(advanceTime);
134+
}
135+
});
136+
}
137+
}

0 commit comments

Comments
 (0)