Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/SelectInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ export default React.forwardRef<SelectInputRef, SelectInputProps>(function Selec
// ====================== Open ======================
const onInternalMouseDown: SelectInputProps['onMouseDown'] = useEvent((event) => {
if (!disabled) {
// https://github.com/ant-design/ant-design/issues/56002
// Tell `useSelectTriggerControl` to ignore this event
// When icon is dynamic render, the parentNode will miss
// so we need to mark the event directly
(event.nativeEvent as any)._ignore_global_close = true;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Directly modifying the native event object and casting it to any can lead to type safety issues and potential conflicts with other libraries that might also manipulate event objects. While this approach fixes the immediate bug, it relies on undocumented behavior and can be fragile. Consider using a more idiomatic React pattern, such as a context provider or a ref, to pass this _ignore_global_close flag to the useSelectTriggerControl hook.


const inputDOM = getDOM(inputRef.current);
if (inputDOM && event.target !== inputDOM && !inputDOM.contains(event.target as Node)) {
event.preventDefault();
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/useSelectTriggerControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default function useSelectTriggerControl(

if (
open &&
// Marked by SelectInput mouseDown event
!(event as any)._ignore_global_close &&

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Accessing a custom property _ignore_global_close on the event object with (event as any) reduces type safety and couples this hook tightly to the implementation details of SelectInput. If the structure of the native event or the SelectInput component changes, this could break unexpectedly. A more robust solution would involve a clear, type-safe communication channel between the components, such as a shared state or a dedicated prop.

elements()
.filter((element) => element)
.every((element) => !element.contains(target) && element !== target)
Expand Down
40 changes: 40 additions & 0 deletions tests/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,46 @@ describe('Select.Basic', () => {
expect(container.querySelector('input').getAttribute('readonly')).toBeFalsy();
});

it('dynamic change icon should not trigger close', () => {
const Demo = () => {
const [open, setOpen] = React.useState(false);

return (
<Select
open={open}
onPopupVisibleChange={setOpen}
showSearch
suffix={
open ? (
<span className="bamboo" key="bamboo" />
) : (
<span className="little" key="little" />
)
}
/>
);
};

const { container } = render(<Demo />);
const suffix = container.querySelector('.little');

const mouseDownEvent = createEvent.mouseDown(suffix);

// First click on the element and re-render to remove the node
fireEvent(suffix, mouseDownEvent);
act(() => {
jest.runAllTimers();
});

// Then popup to the window event (jsdom can not mock this. so we just fire directly)
fireEvent(window, mouseDownEvent);
act(() => {
jest.runAllTimers();
});

expectOpen(container);
});

it('should keep dropdown open when clicking inside popup to prevent accidental close', async () => {
const { container } = render(
<Select
Expand Down
Loading