Skip to content

Commit 3ff731d

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

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
@@ -537,13 +537,13 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
537537
}
538538

539539
if (mergedOpen && (!isEnterKey || !keyLockRef.current)) {
540+
// Lock the Enter key after it is pressed to avoid repeated triggering of the onChange event.
541+
if (isEnterKey) {
542+
keyLockRef.current = true;
543+
}
540544
listRef.current?.onKeyDown(event, ...rest);
541545
}
542546

543-
if (isEnterKey) {
544-
keyLockRef.current = true;
545-
}
546-
547547
onKeyDown?.(event, ...rest);
548548
};
549549

@@ -568,6 +568,11 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
568568
});
569569
};
570570

571+
const onInputBlur = () => {
572+
// Unlock the Enter key after the input blur; otherwise, the Enter key needs to be pressed twice to trigger the correct effect.
573+
keyLockRef.current = false;
574+
};
575+
571576
// ========================== Focus / Blur ==========================
572577
/** Record real focus status */
573578
const focusRef = React.useRef<boolean>(false);
@@ -815,6 +820,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
815820
onSearchSubmit={onInternalSearchSubmit}
816821
onRemove={onSelectorRemove}
817822
tokenWithEnter={tokenWithEnter}
823+
onInputBlur={onInputBlur}
818824
/>
819825
)}
820826
</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);
@@ -231,6 +232,7 @@ const SelectSelector: React.FC<SelectorProps> = (props) => {
231232
onPaste={onInputPaste}
232233
onCompositionStart={onInputCompositionStart}
233234
onCompositionEnd={onInputCompositionEnd}
235+
onBlur={onInputBlur}
234236
tabIndex={tabIndex}
235237
attrs={pickAttrs(props, true)}
236238
/>

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)