Skip to content

Commit

Permalink
fix: The Enter key lock cannot be released correctly when the custom …
Browse files Browse the repository at this point in the history
…input calls blur().
  • Loading branch information
黎书行 authored and LeeSSHH committed Jan 14, 2025
1 parent 4d77993 commit 9e20d1d
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 5 deletions.
14 changes: 10 additions & 4 deletions src/BaseSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -537,13 +537,13 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
}

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

if (isEnterKey) {
keyLockRef.current = true;
}

onKeyDown?.(event, ...rest);
};

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

const onInputBlur = () => {
// Unlock the Enter key after the input blur; otherwise, the Enter key needs to be pressed twice to trigger the correct effect.
keyLockRef.current = false;
};

// ========================== Focus / Blur ==========================
/** Record real focus status */
const focusRef = React.useRef<boolean>(false);
Expand Down Expand Up @@ -815,6 +820,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
onSearchSubmit={onInternalSearchSubmit}
onRemove={onSelectorRemove}
tokenWithEnter={tokenWithEnter}
onInputBlur={onInputBlur}
/>
)}
</SelectTrigger>
Expand Down
9 changes: 9 additions & 0 deletions src/Selector/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface InputProps {
onMouseDown: React.MouseEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
onPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
onBlur: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
onCompositionStart: React.CompositionEventHandler<
HTMLInputElement | HTMLTextAreaElement | HTMLElement
>;
Expand Down Expand Up @@ -52,6 +53,7 @@ const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = (props, ref)
onPaste,
onCompositionStart,
onCompositionEnd,
onBlur,
open,
attrs,
} = props;
Expand All @@ -66,6 +68,7 @@ const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = (props, ref)
onMouseDown: onOriginMouseDown,
onCompositionStart: onOriginCompositionStart,
onCompositionEnd: onOriginCompositionEnd,
onBlur: onOriginBlur,
style,
} = originProps;

Expand Down Expand Up @@ -134,6 +137,12 @@ const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = (props, ref)
}
},
onPaste,
onBlur(event: React.FocusEvent<HTMLElement>) {
onBlur(event);
if (onOriginBlur) {
onOriginBlur(event);
}
},
});

return inputNode;
Expand Down
2 changes: 2 additions & 0 deletions src/Selector/MultipleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const SelectSelector: React.FC<SelectorProps> = (props) => {
onInputMouseDown,
onInputCompositionStart,
onInputCompositionEnd,
onInputBlur,
} = props;

const measureRef = React.useRef<HTMLSpanElement>(null);
Expand Down Expand Up @@ -231,6 +232,7 @@ const SelectSelector: React.FC<SelectorProps> = (props) => {
onPaste={onInputPaste}
onCompositionStart={onInputCompositionStart}
onCompositionEnd={onInputCompositionEnd}
onBlur={onInputBlur}
tabIndex={tabIndex}
attrs={pickAttrs(props, true)}
/>
Expand Down
2 changes: 2 additions & 0 deletions src/Selector/SingleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const SingleSelector: React.FC<SelectorProps> = (props) => {
onInputPaste,
onInputCompositionStart,
onInputCompositionEnd,
onInputBlur,
title,
} = props;

Expand Down Expand Up @@ -100,6 +101,7 @@ const SingleSelector: React.FC<SelectorProps> = (props) => {
onPaste={onInputPaste}
onCompositionStart={onInputCompositionStart}
onCompositionEnd={onInputCompositionEnd}
onBlur={onInputBlur}
tabIndex={tabIndex}
attrs={pickAttrs(props, true)}
maxLength={combobox ? maxLength : undefined}
Expand Down
6 changes: 5 additions & 1 deletion src/Selector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface InnerSelectorProps {
onInputPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onInputCompositionStart: React.CompositionEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onInputCompositionEnd: React.CompositionEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onInputBlur: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
}

export interface RefSelectorProps {
Expand Down Expand Up @@ -92,7 +93,8 @@ export interface SelectorProps {
onSearchSubmit?: (searchText: string) => void;
onRemove: (value: DisplayValueType) => void;
onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;

// on inner input blur
onInputBlur?: () => void;
/**
* @private get real dom for trigger align.
* This may be removed after React provides replacement of `findDOMNode`
Expand All @@ -119,6 +121,7 @@ const Selector: React.ForwardRefRenderFunction<RefSelectorProps, SelectorProps>
onSearchSubmit,
onToggleOpen,
onInputKeyDown,
onInputBlur,

domRef,
} = props;
Expand Down Expand Up @@ -270,6 +273,7 @@ const Selector: React.ForwardRefRenderFunction<RefSelectorProps, SelectorProps>
onInputPaste,
onInputCompositionStart,
onInputCompositionEnd,
onInputBlur,
};

const selectNode =
Expand Down
50 changes: 50 additions & 0 deletions tests/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
keyUp,
selectItem,
toggleOpen,
waitFakeTimer,
} from './utils/common';

describe('Select.Basic', () => {
Expand Down Expand Up @@ -2367,4 +2368,53 @@ describe('Select.Basic', () => {
expect(element[0]).not.toHaveClass('rc-select-item-option-disabled');
expect(element[1]).toHaveClass('rc-select-item-option-disabled');
});

it('release Enter key lock after customize input calls blur()', async () => {
let inputElem: HTMLInputElement | null = null;
const onBlur = jest.fn();
const Demo: React.FC = () => {
const ref = React.useRef<HTMLInputElement>(null);
const onSelect = () => {
ref.current!.blur();
fireEvent.blur(ref.current);
};
const getInputElement = () => {
return <input ref={ref} onBlur={onBlur} />;
};
return (
<Select
options={[{ value: 'aa' }, { value: 'bb' }]}
onSelect={onSelect}
mode="combobox"
getInputElement={getInputElement}
/>
);
};
const { container } = render(<Demo />);
inputElem = container.querySelector('input');
toggleOpen(container);
await waitFakeTimer();
expectOpen(container, true);

keyDown(inputElem!, 40);
keyUp(inputElem!, 40);
keyDown(inputElem!, 13);

await waitFakeTimer();
expect(onBlur).toHaveBeenCalledTimes(1);
expectOpen(container, false);
expect(inputElem.value).toEqual('aa');

toggleOpen(container);
await waitFakeTimer();
expectOpen(container, true);

keyDown(inputElem!, 40);
keyUp(inputElem!, 40);
keyDown(inputElem!, 13);

await waitFakeTimer();
expect(onBlur).toHaveBeenCalledTimes(2);
expect(inputElem.value).toEqual('bb');
});
});
20 changes: 20 additions & 0 deletions tests/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,23 @@ export function keyUp(element: HTMLElement, keyCode: number) {
fireEvent(element, event);
});
}

/**
* Wait for a time delay. Will wait `advanceTime * times` ms.
*
* @param advanceTime Default 1000
* @param times Default 20
*/
export async function waitFakeTimer(advanceTime = 1000, times = 20) {
for (let i = 0; i < times; i += 1) {
await act(async () => {
await Promise.resolve();

if (advanceTime > 0) {
jest.advanceTimersByTime(advanceTime);
} else {
jest.runAllTimers();

Check warning on line 135 in tests/utils/common.ts

View check run for this annotation

Codecov / codecov/patch

tests/utils/common.ts#L134-L135

Added lines #L134 - L135 were not covered by tests
}
});
}
}

0 comments on commit 9e20d1d

Please sign in to comment.