@hsu-react/ui 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
- var _excluded = ["prefix", "suffix", "className", "selectClassName", "onFocus", "onBlur", "onSearch", "disabled", "popupClassName", "filterOption", "onChange", "options", "placement", "onDropdownVisibleChange", "popupMatchSelectWidth", "popupMatchContentWidth", "optionFontSize"],
2
+ var _excluded = ["prefix", "suffix", "className", "selectClassName", "onFocus", "onBlur", "onSearch", "disabled", "popupClassName", "filterOption", "onChange", "options", "placement", "onDropdownVisibleChange", "popupMatchSelectWidth", "popupMatchContentWidth", "optionFontSize", "value"],
3
3
  _excluded2 = ["label", "value", "disabled"];
4
4
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5
5
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -15,7 +15,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
15
15
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
16
16
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
17
17
  import { AutoComplete } from "antd";
18
- import React, { useEffect, useMemo, useRef, useState } from "react";
18
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
19
19
  import classNames from "classnames";
20
20
  import styles from "./index.module.scss";
21
21
  import { generateRandomStr } from "hsu-utils";
@@ -48,6 +48,7 @@ var AutoCompleteSelect = function AutoCompleteSelect(props) {
48
48
  popupMatchContentWidth = props.popupMatchContentWidth,
49
49
  _props$optionFontSize = props.optionFontSize,
50
50
  optionFontSize = _props$optionFontSize === void 0 ? 14 : _props$optionFontSize,
51
+ value = props.value,
51
52
  antdAutoCompleteConfig = _objectWithoutProperties(props, _excluded);
52
53
  var _useState = useState(false),
53
54
  _useState2 = _slicedToArray(_useState, 2),
@@ -58,23 +59,33 @@ var AutoCompleteSelect = function AutoCompleteSelect(props) {
58
59
  _useState4 = _slicedToArray(_useState3, 2),
59
60
  open = _useState4[0],
60
61
  setOpen = _useState4[1];
61
- var _useState5 = useState(false),
62
+ var _useState5 = useState(""),
62
63
  _useState6 = _slicedToArray(_useState5, 2),
63
- legacyHasErrorStatus = _useState6[0],
64
- setLegacyHasErrorStatus = _useState6[1];
65
- var _useState7 = useState(true),
64
+ searchText = _useState6[0],
65
+ setSearchText = _useState6[1];
66
+ var _useState7 = useState(false),
66
67
  _useState8 = _slicedToArray(_useState7, 2),
67
- legacyHasArrowOrClear = _useState8[0],
68
- setLegacyHasArrowOrClear = _useState8[1];
68
+ legacyHasErrorStatus = _useState8[0],
69
+ setLegacyHasErrorStatus = _useState8[1];
70
+ var _useState9 = useState(true),
71
+ _useState10 = _slicedToArray(_useState9, 2),
72
+ legacyHasArrowOrClear = _useState10[0],
73
+ setLegacyHasArrowOrClear = _useState10[1];
69
74
  var legacyHasSelector = isLegacyHasSelectorBrowser();
70
75
  var cls = useMemo(function () {
71
76
  return generateRandomStr(10);
72
77
  }, []);
78
+ var handleSearch = useCallback(function (searchValue) {
79
+ setSearchText(searchValue);
80
+ onSearch === null || onSearch === void 0 || onSearch(searchValue);
81
+ }, [onSearch]);
82
+
83
+ // Always pass handleSearch so IME composition events stay observed
84
+ // (an in-progress pinyin input must not wipe out the option list)
73
85
  var _useSelectComposition = useSelectComposition({
74
- onSearch: onSearch
86
+ onSearch: handleSearch
75
87
  }),
76
88
  isComposing = _useSelectComposition.isComposing;
77
- useSelectPopupPosition(autoCompleteRef, open, cls);
78
89
  var autoCompleteWidth = autoCompleteRef.current ? autoCompleteRef.current.offsetWidth : 0;
79
90
  var calculatedPopupWidth = popupMatchContentWidth ? calculatePopupWidth({
80
91
  options: options === null || options === void 0 ? void 0 : options.map(function (opt) {
@@ -103,27 +114,51 @@ var AutoCompleteSelect = function AutoCompleteSelect(props) {
103
114
  });
104
115
  }, [options]);
105
116
 
106
- // Handle filterOption
107
- var handleFilterOption = useMemo(function () {
108
- // If a custom filterOption is provided, use it first
117
+ // Filter rule: prefer the custom filterOption, otherwise fall back to a
118
+ // case-insensitive substring match
119
+ var matchOption = useMemo(function () {
109
120
  if (customFilterOption) {
110
- return function (inputValue, option) {
111
- return customFilterOption(inputValue, option);
112
- };
121
+ return customFilterOption;
113
122
  }
114
-
115
- // Default filtering logic: case-insensitive substring match
116
123
  return function (inputValue, option) {
117
- if (!isComposing && option) {
118
- var _option$value, _ref, _option$label;
119
- var searchText = inputValue.toUpperCase();
120
- var optionValue = ((_option$value = option.value) !== null && _option$value !== void 0 ? _option$value : "").toString().toUpperCase();
121
- var optionLabel = ((_ref = (_option$label = option.label) !== null && _option$label !== void 0 ? _option$label : option.value) !== null && _ref !== void 0 ? _ref : "").toString().toUpperCase();
122
- return optionValue.includes(searchText) || optionLabel.includes(searchText);
124
+ var _option$value, _ref, _option$label;
125
+ if (!option) {
126
+ return true;
123
127
  }
124
- return true;
128
+ var keyword = inputValue.toUpperCase();
129
+ var optionValue = ((_option$value = option.value) !== null && _option$value !== void 0 ? _option$value : "").toString().toUpperCase();
130
+ var optionLabel = ((_ref = (_option$label = option.label) !== null && _option$label !== void 0 ? _option$label : option.value) !== null && _ref !== void 0 ? _ref : "").toString().toUpperCase();
131
+ return optionValue.includes(keyword) || optionLabel.includes(keyword);
125
132
  };
126
- }, [customFilterOption, isComposing]);
133
+ }, [customFilterOption]);
134
+
135
+ // Text the filter runs against: the controlled value wins, otherwise use the
136
+ // internally tracked search keyword
137
+ var filterText = useMemo(function () {
138
+ if (value !== undefined && value !== null) {
139
+ return String(value);
140
+ }
141
+ return searchText;
142
+ }, [value, searchText]);
143
+
144
+ // Filter inside the component (antd's own filterOption is turned off) so we
145
+ // know whether any candidate survives, keeping controlled open in sync
146
+ var filteredOptions = useMemo(function () {
147
+ // Skip filtering while composing so an in-progress pinyin input does not
148
+ // empty the candidate list
149
+ if (isComposing || !filterText) {
150
+ return autoCompleteOptions;
151
+ }
152
+ return autoCompleteOptions.filter(function (option) {
153
+ return matchOption(filterText, option);
154
+ });
155
+ }, [autoCompleteOptions, filterText, isComposing, matchOption]);
156
+
157
+ // In combobox mode antd hides the popup by itself when there is no candidate
158
+ // and skips onDropdownVisibleChange, so fold "no candidate" into open to stop
159
+ // the controlled state from drifting and leaving an empty popup behind
160
+ var mergedOpen = open && filteredOptions.length > 0;
161
+ useSelectPopupPosition(autoCompleteRef, mergedOpen, cls);
127
162
  useEffect(function () {
128
163
  if (!legacyHasSelector) {
129
164
  return;
@@ -163,7 +198,7 @@ var AutoCompleteSelect = function AutoCompleteSelect(props) {
163
198
  return function () {
164
199
  observer.disconnect();
165
200
  };
166
- }, [legacyHasSelector, open]);
201
+ }, [legacyHasSelector, mergedOpen]);
167
202
  return /*#__PURE__*/_jsxs("div", {
168
203
  className: classNames(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, styles.autoCompleteSelect, true), className !== null && className !== void 0 ? className : "", true), styles.focused, focused), styles.disabled, disabled), styles.popupMatchContentWidth, popupMatchContentWidth), styles.legacyHasErrorStatus, legacyHasSelector && legacyHasErrorStatus)),
169
204
  ref: autoCompleteRef,
@@ -172,19 +207,23 @@ var AutoCompleteSelect = function AutoCompleteSelect(props) {
172
207
  }), /*#__PURE__*/_jsx(AutoComplete, _objectSpread({}, _objectSpread(_objectSpread({
173
208
  allowClear: true
174
209
  }, antdAutoCompleteConfig), {}, {
210
+ value: value,
175
211
  onFocus: function onFocus(e) {
176
212
  setFocused(true);
177
213
  _onFocus === null || _onFocus === void 0 || _onFocus(e);
178
214
  },
179
215
  onBlur: function onBlur(e) {
180
216
  setFocused(false);
217
+ // antd skips onDropdownVisibleChange when there is no candidate,
218
+ // so collapse the controlled state manually on blur
219
+ setOpen(false);
181
220
  _onBlur === null || _onBlur === void 0 || _onBlur(e);
182
221
  },
183
- options: autoCompleteOptions,
184
- filterOption: handleFilterOption,
222
+ options: filteredOptions,
223
+ filterOption: false,
185
224
  onChange: onChange,
186
- onSearch: onSearch,
187
- open: open,
225
+ onSearch: handleSearch,
226
+ open: mergedOpen,
188
227
  onDropdownVisibleChange: function onDropdownVisibleChange(visible) {
189
228
  setOpen(visible);
190
229
  _onDropdownVisibleChange === null || _onDropdownVisibleChange === void 0 || _onDropdownVisibleChange(visible);
@@ -38,21 +38,29 @@ const AutoCompleteSelect = props => {
38
38
  popupMatchSelectWidth,
39
39
  popupMatchContentWidth,
40
40
  optionFontSize = 14,
41
+ value,
41
42
  ...antdAutoCompleteConfig
42
43
  } = props;
43
44
  const [focused, setFocused] = (0, _react.useState)(false);
44
45
  const autoCompleteRef = (0, _react.useRef)(null);
45
46
  const [open, setOpen] = (0, _react.useState)(false);
47
+ const [searchText, setSearchText] = (0, _react.useState)("");
46
48
  const [legacyHasErrorStatus, setLegacyHasErrorStatus] = (0, _react.useState)(false);
47
49
  const [legacyHasArrowOrClear, setLegacyHasArrowOrClear] = (0, _react.useState)(true);
48
50
  const legacyHasSelector = (0, _cssSupports.isLegacyHasSelectorBrowser)();
49
51
  const cls = (0, _react.useMemo)(() => (0, _hsuUtils.generateRandomStr)(10), []);
52
+ const handleSearch = (0, _react.useCallback)(searchValue => {
53
+ setSearchText(searchValue);
54
+ onSearch?.(searchValue);
55
+ }, [onSearch]);
56
+
57
+ // Always pass handleSearch so IME composition events stay observed
58
+ // (an in-progress pinyin input must not wipe out the option list)
50
59
  const {
51
60
  isComposing
52
61
  } = (0, _hooks.useSelectComposition)({
53
- onSearch
62
+ onSearch: handleSearch
54
63
  });
55
- (0, _hooks.useSelectPopupPosition)(autoCompleteRef, open, cls);
56
64
  const autoCompleteWidth = autoCompleteRef.current ? autoCompleteRef.current.offsetWidth : 0;
57
65
  const calculatedPopupWidth = popupMatchContentWidth ? (0, _utils.calculatePopupWidth)({
58
66
  options: options?.map(opt => ({
@@ -81,26 +89,48 @@ const AutoCompleteSelect = props => {
81
89
  });
82
90
  }, [options]);
83
91
 
84
- // Handle filterOption
85
- const handleFilterOption = (0, _react.useMemo)(() => {
86
- // If a custom filterOption is provided, use it first
92
+ // Filter rule: prefer the custom filterOption, otherwise fall back to a
93
+ // case-insensitive substring match
94
+ const matchOption = (0, _react.useMemo)(() => {
87
95
  if (customFilterOption) {
88
- return (inputValue, option) => {
89
- return customFilterOption(inputValue, option);
90
- };
96
+ return customFilterOption;
91
97
  }
92
-
93
- // Default filtering logic: case-insensitive substring match
94
98
  return (inputValue, option) => {
95
- if (!isComposing && option) {
96
- const searchText = inputValue.toUpperCase();
97
- const optionValue = (option.value ?? "").toString().toUpperCase();
98
- const optionLabel = (option.label ?? option.value ?? "").toString().toUpperCase();
99
- return optionValue.includes(searchText) || optionLabel.includes(searchText);
99
+ if (!option) {
100
+ return true;
100
101
  }
101
- return true;
102
+ const keyword = inputValue.toUpperCase();
103
+ const optionValue = (option.value ?? "").toString().toUpperCase();
104
+ const optionLabel = (option.label ?? option.value ?? "").toString().toUpperCase();
105
+ return optionValue.includes(keyword) || optionLabel.includes(keyword);
102
106
  };
103
- }, [customFilterOption, isComposing]);
107
+ }, [customFilterOption]);
108
+
109
+ // Text the filter runs against: the controlled value wins, otherwise use the
110
+ // internally tracked search keyword
111
+ const filterText = (0, _react.useMemo)(() => {
112
+ if (value !== undefined && value !== null) {
113
+ return String(value);
114
+ }
115
+ return searchText;
116
+ }, [value, searchText]);
117
+
118
+ // Filter inside the component (antd's own filterOption is turned off) so we
119
+ // know whether any candidate survives, keeping controlled open in sync
120
+ const filteredOptions = (0, _react.useMemo)(() => {
121
+ // Skip filtering while composing so an in-progress pinyin input does not
122
+ // empty the candidate list
123
+ if (isComposing || !filterText) {
124
+ return autoCompleteOptions;
125
+ }
126
+ return autoCompleteOptions.filter(option => matchOption(filterText, option));
127
+ }, [autoCompleteOptions, filterText, isComposing, matchOption]);
128
+
129
+ // In combobox mode antd hides the popup by itself when there is no candidate
130
+ // and skips onDropdownVisibleChange, so fold "no candidate" into open to stop
131
+ // the controlled state from drifting and leaving an empty popup behind
132
+ const mergedOpen = open && filteredOptions.length > 0;
133
+ (0, _hooks.useSelectPopupPosition)(autoCompleteRef, mergedOpen, cls);
104
134
  (0, _react.useEffect)(() => {
105
135
  if (!legacyHasSelector) {
106
136
  return;
@@ -140,7 +170,7 @@ const AutoCompleteSelect = props => {
140
170
  return () => {
141
171
  observer.disconnect();
142
172
  };
143
- }, [legacyHasSelector, open]);
173
+ }, [legacyHasSelector, mergedOpen]);
144
174
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
145
175
  className: (0, _classnames.default)({
146
176
  [_indexModule.default.autoCompleteSelect]: true,
@@ -156,19 +186,23 @@ const AutoCompleteSelect = props => {
156
186
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_antd.AutoComplete, {
157
187
  allowClear: true,
158
188
  ...antdAutoCompleteConfig,
189
+ value,
159
190
  onFocus: e => {
160
191
  setFocused(true);
161
192
  onFocus?.(e);
162
193
  },
163
194
  onBlur: e => {
164
195
  setFocused(false);
196
+ // antd skips onDropdownVisibleChange when there is no candidate,
197
+ // so collapse the controlled state manually on blur
198
+ setOpen(false);
165
199
  onBlur?.(e);
166
200
  },
167
- options: autoCompleteOptions,
168
- filterOption: handleFilterOption,
201
+ options: filteredOptions,
202
+ filterOption: false,
169
203
  onChange,
170
- onSearch,
171
- open,
204
+ onSearch: handleSearch,
205
+ open: mergedOpen,
172
206
  onDropdownVisibleChange: visible => {
173
207
  setOpen(visible);
174
208
  onDropdownVisibleChange?.(visible);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hsu-react/ui",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "一套基于 React + Ant Design 的中后台业务组件库",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,5 +1,12 @@
1
1
  import { AutoComplete, AutoCompleteProps } from "antd";
2
- import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react";
2
+ import React, {
3
+ ReactNode,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
3
10
  import classNames from "classnames";
4
11
  import styles from "./index.module.scss";
5
12
  import { generateRandomStr } from "hsu-utils";
@@ -52,11 +59,13 @@ const AutoCompleteSelect: React.FC<AutoCompleteSelectProps> = (props) => {
52
59
  popupMatchSelectWidth,
53
60
  popupMatchContentWidth,
54
61
  optionFontSize = 14,
62
+ value,
55
63
  ...antdAutoCompleteConfig
56
64
  } = props;
57
65
  const [focused, setFocused] = useState<boolean>(false);
58
66
  const autoCompleteRef = useRef<HTMLDivElement>(null);
59
67
  const [open, setOpen] = useState<boolean>(false);
68
+ const [searchText, setSearchText] = useState<string>("");
60
69
  const [legacyHasErrorStatus, setLegacyHasErrorStatus] =
61
70
  useState<boolean>(false);
62
71
  const [legacyHasArrowOrClear, setLegacyHasArrowOrClear] =
@@ -65,9 +74,17 @@ const AutoCompleteSelect: React.FC<AutoCompleteSelectProps> = (props) => {
65
74
 
66
75
  const cls = useMemo(() => generateRandomStr(10), []);
67
76
 
68
- const { isComposing } = useSelectComposition({ onSearch });
77
+ const handleSearch = useCallback(
78
+ (searchValue: string) => {
79
+ setSearchText(searchValue);
80
+ onSearch?.(searchValue);
81
+ },
82
+ [onSearch],
83
+ );
69
84
 
70
- useSelectPopupPosition(autoCompleteRef, open, cls);
85
+ // Always pass handleSearch so IME composition events stay observed
86
+ // (an in-progress pinyin input must not wipe out the option list)
87
+ const { isComposing } = useSelectComposition({ onSearch: handleSearch });
71
88
 
72
89
  const autoCompleteWidth = autoCompleteRef.current
73
90
  ? autoCompleteRef.current.offsetWidth
@@ -97,30 +114,54 @@ const AutoCompleteSelect: React.FC<AutoCompleteSelectProps> = (props) => {
97
114
  });
98
115
  }, [options]);
99
116
 
100
- // Handle filterOption
101
- const handleFilterOption = useMemo(() => {
102
- // If a custom filterOption is provided, use it first
117
+ // Filter rule: prefer the custom filterOption, otherwise fall back to a
118
+ // case-insensitive substring match
119
+ const matchOption = useMemo(() => {
103
120
  if (customFilterOption) {
104
- return (inputValue: string, option?: AutoCompleteOption) => {
105
- return customFilterOption(inputValue, option);
106
- };
121
+ return customFilterOption;
107
122
  }
108
123
 
109
- // Default filtering logic: case-insensitive substring match
110
124
  return (inputValue: string, option?: AutoCompleteOption): boolean => {
111
- if (!isComposing && option) {
112
- const searchText = inputValue.toUpperCase();
113
- const optionValue = (option.value ?? "").toString().toUpperCase();
114
- const optionLabel = (option.label ?? option.value ?? "")
115
- .toString()
116
- .toUpperCase();
117
- return (
118
- optionValue.includes(searchText) || optionLabel.includes(searchText)
119
- );
125
+ if (!option) {
126
+ return true;
120
127
  }
121
- return true;
128
+ const keyword = inputValue.toUpperCase();
129
+ const optionValue = (option.value ?? "").toString().toUpperCase();
130
+ const optionLabel = (option.label ?? option.value ?? "")
131
+ .toString()
132
+ .toUpperCase();
133
+ return optionValue.includes(keyword) || optionLabel.includes(keyword);
122
134
  };
123
- }, [customFilterOption, isComposing]);
135
+ }, [customFilterOption]);
136
+
137
+ // Text the filter runs against: the controlled value wins, otherwise use the
138
+ // internally tracked search keyword
139
+ const filterText = useMemo(() => {
140
+ if (value !== undefined && value !== null) {
141
+ return String(value);
142
+ }
143
+ return searchText;
144
+ }, [value, searchText]);
145
+
146
+ // Filter inside the component (antd's own filterOption is turned off) so we
147
+ // know whether any candidate survives, keeping controlled open in sync
148
+ const filteredOptions = useMemo(() => {
149
+ // Skip filtering while composing so an in-progress pinyin input does not
150
+ // empty the candidate list
151
+ if (isComposing || !filterText) {
152
+ return autoCompleteOptions;
153
+ }
154
+ return autoCompleteOptions.filter((option) =>
155
+ matchOption(filterText, option),
156
+ );
157
+ }, [autoCompleteOptions, filterText, isComposing, matchOption]);
158
+
159
+ // In combobox mode antd hides the popup by itself when there is no candidate
160
+ // and skips onDropdownVisibleChange, so fold "no candidate" into open to stop
161
+ // the controlled state from drifting and leaving an empty popup behind
162
+ const mergedOpen = open && filteredOptions.length > 0;
163
+
164
+ useSelectPopupPosition(autoCompleteRef, mergedOpen, cls);
124
165
 
125
166
  useEffect(() => {
126
167
  if (!legacyHasSelector) {
@@ -177,7 +218,7 @@ const AutoCompleteSelect: React.FC<AutoCompleteSelectProps> = (props) => {
177
218
  return () => {
178
219
  observer.disconnect();
179
220
  };
180
- }, [legacyHasSelector, open]);
221
+ }, [legacyHasSelector, mergedOpen]);
181
222
 
182
223
  return (
183
224
  <div
@@ -197,19 +238,23 @@ const AutoCompleteSelect: React.FC<AutoCompleteSelectProps> = (props) => {
197
238
  {...{
198
239
  allowClear: true,
199
240
  ...antdAutoCompleteConfig,
241
+ value,
200
242
  onFocus: (e) => {
201
243
  setFocused(true);
202
244
  onFocus?.(e);
203
245
  },
204
246
  onBlur: (e) => {
205
247
  setFocused(false);
248
+ // antd skips onDropdownVisibleChange when there is no candidate,
249
+ // so collapse the controlled state manually on blur
250
+ setOpen(false);
206
251
  onBlur?.(e);
207
252
  },
208
- options: autoCompleteOptions,
209
- filterOption: handleFilterOption,
253
+ options: filteredOptions,
254
+ filterOption: false,
210
255
  onChange,
211
- onSearch,
212
- open,
256
+ onSearch: handleSearch,
257
+ open: mergedOpen,
213
258
  onDropdownVisibleChange: (visible) => {
214
259
  setOpen(visible);
215
260
  onDropdownVisibleChange?.(visible);