@cloud-ru/ds-fields 2.1.2 → 2.1.3

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 2.1.3 (2026-09-02)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **FF-8959:** sync FieldSelect search to the selected value when search is controlled ([2f29e15](https://github.com/cloud-ru-tech/snack-v2/commit/2f29e15eeb3e1dd54bb119fd27ab68d5d84e383e))
11
+
6
12
  ## 2.1.2 (2026-09-02)
7
13
 
8
14
  ### Bug Fixes
@@ -13,8 +13,7 @@ const utils_1 = require("@cloud-ru/ds-utils");
13
13
  const classnames_1 = __importDefault(require("classnames"));
14
14
  const merge_refs_1 = __importDefault(require("merge-refs"));
15
15
  const react_1 = require("react");
16
- // Минимальная ширина поискового input'а в режиме чипов: при пустом вводе input «схлопывается»
17
- // к этому значению и прижимается к последнему чипу (паритет с легаси BASE_MIN_WIDTH).
16
+ // Минимальная ширина поискового input'а в режиме чипов (паритет с легаси BASE_MIN_WIDTH).
18
17
  const SEARCH_INPUT_PLUG_MIN_WIDTH = 4;
19
18
  const field_decorator_1 = require("@cloud-ru/ds-field-decorator");
20
19
  const constants_1 = require("../../constants");
@@ -31,14 +30,10 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
31
30
  const closeDroplistOnItemClick = props.closeDroplistOnItemClick ?? !multiple;
32
31
  const resolvedAutoFocus = (0, hooks_1.useAdaptiveAutoFocus)(autoFocus, layoutPresets);
33
32
  const inputRef = (0, react_1.useRef)(null);
34
- // Реальный <input>: `inputRef` уходит в Droplist как `triggerElemRef`, а `PopoverPrivate`
35
- // перезаписывает его `.current` своей span-обёрткой (см. MR!101). Отдельный ref на сам input
36
- // нужен для `useButtonNavigation` (курсор/фокус) и возврата фокуса после очистки.
33
+ // Отдельный ref на реальный <input>: `inputRef.current` перезаписывается span-обёрткой PopoverPrivate.
37
34
  const inputElementRef = (0, react_1.useRef)(null);
38
35
  const clearButtonRef = (0, react_1.useRef)(null);
39
36
  const copyButtonRef = (0, react_1.useRef)(null);
40
- // Контейнер чипов+input (замер доступной ширины) и скрытый плаг (замер ширины введённого текста)
41
- // для расчёта minWidth поискового input'а в режиме чипов — см. searchInput ниже.
42
37
  const contentRef = (0, react_1.useRef)(null);
43
38
  const inputPlugRef = (0, react_1.useRef)(null);
44
39
  const [chipInputMinWidth, setChipInputMinWidth] = (0, react_1.useState)(undefined);
@@ -48,13 +43,9 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
48
43
  const open = openProp ?? openLocal;
49
44
  const [singleLocal, setSingleLocal] = (0, react_1.useState)(!multiple ? props.defaultValue : undefined);
50
45
  const [multipleLocal, setMultipleLocal] = (0, react_1.useState)(
51
- // Array.isArray-страховка: при multiple defaultValue обязан быть массивом по типам,
52
- // но «расхлябанный» вызов (например, Storybook-spread, где single-default остаётся
53
- // строкой при переключении selection) не должен ронять компонент на `.map` строки.
46
+ // Страховка от вызова без типов (Storybook-spread): при multiple defaultValue может прийти строкой.
54
47
  multiple && Array.isArray(props.defaultValue) ? props.defaultValue : []);
55
- // Controlled-режим «залипает»: как только потребитель прислал `value`, локальный стейт больше
56
- // не читается. Иначе очистка (потребитель ставит `undefined`) откатывалась бы на устаревший
57
- // локальный выбор и воскрешала прошлое значение.
48
+ // Controlled-режим «залипает»: иначе очистка через `value: undefined` откатится на локальный стейт.
58
49
  const singleControlled = (0, react_1.useRef)(false);
59
50
  const multipleControlled = (0, react_1.useRef)(false);
60
51
  if (!multiple && props.value !== undefined) {
@@ -125,24 +116,28 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
125
116
  }
126
117
  return selectedPairs.map(formatPair).join(', ');
127
118
  }, [multiple, singleValue, selectedPairs, chips, props, formatPair, resolveItem]);
128
- // Строка поиска: controlled через `search.value`, иначе локальный state. Auto-sync к выбранному
129
- // значению делаем через СТАБИЛЬНЫЙ setLocalInput, а не через setInputValue в deps эффекта —
130
- // иначе нестабильная ссылка сеттера зацикливает эффект (сбрасывала бы значение постоянно).
131
119
  const [localInput, setLocalInput] = (0, react_1.useState)(search?.defaultValue ?? selectedLabel);
132
120
  const inputValue = search?.value !== undefined ? search.value : localInput;
133
121
  const [typing, setTyping] = (0, react_1.useState)(false);
122
+ // `search` — объектный литерал с новой идентичностью каждый рендер; ref делает `setInputValue` стабильным.
123
+ const searchRef = (0, react_1.useRef)(search);
124
+ searchRef.current = search;
134
125
  const setInputValue = (0, react_1.useCallback)((next) => {
135
- if (search?.value === undefined) {
126
+ const currentSearch = searchRef.current;
127
+ if (currentSearch?.value === undefined) {
136
128
  setLocalInput(next);
137
129
  }
138
- search?.onChange?.(next);
139
- }, [search]);
130
+ currentSearch?.onChange?.(next);
131
+ }, []);
132
+ const syncedLabelRef = (0, react_1.useRef)(selectedLabel);
140
133
  (0, react_1.useEffect)(() => {
141
- if (!typing && resetSearchOnOptionSelection && search?.value === undefined) {
142
- setLocalInput(selectedLabel);
134
+ if (typing || !resetSearchOnOptionSelection || syncedLabelRef.current === selectedLabel) {
135
+ return;
143
136
  }
144
- }, [selectedLabel, typing, resetSearchOnOptionSelection, search]);
145
- // autocomplete — клиентскую фильтрацию не делаем, список берётся из items как есть (серверный поиск).
137
+ syncedLabelRef.current = selectedLabel;
138
+ setInputValue(selectedLabel);
139
+ }, [selectedLabel, typing, resetSearchOnOptionSelection, setInputValue]);
140
+ // При autocomplete фильтрует сервер — items берутся как есть.
146
141
  const filteredItems = (0, react_1.useMemo)(() => {
147
142
  if (!searchable || autocomplete || !typing) {
148
143
  return items;
@@ -162,8 +157,7 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
162
157
  return (0, utils_2.filterItems)(pinBottom, inputValue, enableFuzzySearch);
163
158
  }, [searchable, autocomplete, typing, pinBottom, inputValue, enableFuzzySearch]);
164
159
  const handleOpenChange = (0, react_1.useCallback)((next) => {
165
- // Дроплист открывается кликом по полю (Droplist дёргает onOpenChange(true)).
166
- // readonly/disabled поле открывать нельзя — блокируем открытие, закрытие разрешаем.
160
+ // readonly/disabled поле открывать нельзя, закрывать можно.
167
161
  if (next && (disabled || readOnly))
168
162
  return;
169
163
  if (openProp === undefined) {
@@ -261,9 +255,7 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
261
255
  }
262
256
  return copied;
263
257
  }, [onCopyButtonClick, valueToCopy]);
264
- // Clear/Copy как roving-postfix: ArrowRight из input (курсор в конце или readonly) уводит на
265
- // кнопку, ArrowLeft — обратно в поле. Паритет с FieldCombo. `inputElementRef` — реальный input,
266
- // т.к. `inputRef` клобберится popover'ом.
258
+ // Clear/Copy roving-postfix (ArrowRight/ArrowLeft), паритет с FieldCombo.
267
259
  const clearButtonSettings = (0, input_private_1.useClearButton)({
268
260
  clearButtonRef,
269
261
  showClearButton: showClear,
@@ -288,9 +280,8 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
288
280
  submitKeys: ['Enter', 'Space', 'Tab'],
289
281
  });
290
282
  const handleTriggerKeyDown = (event) => {
291
- // Пробрасываем consumer onKeyDown до внутренней обработки (паритет с легаси useHandleOnKeyDown).
283
+ // Consumer-обработчик идёт до внутренней обработки (паритет с легаси useHandleOnKeyDown).
292
284
  onKeyDownProp?.(event);
293
- // Roving-навигация по clear/copy (ArrowRight/ArrowLeft). ArrowDown/Enter ниже не перехватываются.
294
285
  onInputKeyDown(event);
295
286
  if (disabled || readOnly) {
296
287
  return;
@@ -304,7 +295,7 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
304
295
  handleOpenChange(false);
305
296
  }
306
297
  if (addOptionByEnter && event.key === 'Enter' && inputValue !== '') {
307
- // Зафиксировать введённый текст как новый выбор (создание опции «на лету»).
298
+ // Введённый текст становится новым значением (создание опции «на лету»).
308
299
  event.preventDefault();
309
300
  if (multiple) {
310
301
  if (!multipleValue.includes(inputValue)) {
@@ -358,10 +349,8 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
358
349
  ? { mode: 'multiple', value: multipleValue, onChange: handleMultipleChange }
359
350
  : { mode: 'single', value: singleValue, onChange: handleSingleChange };
360
351
  const hasChips = chips && selectedPairs.length > 0;
361
- // minWidth поискового input'а в режиме чипов: ширина введённого текста (скрытый `.inputPlug`),
362
- // но не больше ширины строки чипов (`contentRef`). Замер в layout-эффекте (до paint, без мигания):
363
- // пустой ввод → ~4px (input прижат к последнему чипу), длинный ввод → перенос на новую строку
364
- // через flex-wrap. Без чипов minWidth не задаётся (input занимает всю строку как single-select).
352
+ // minWidth поискового input'а = ширина введённого текста (`.inputPlug`), но не больше строки чипов.
353
+ // Замер до paint, иначе input мигает при вводе.
365
354
  (0, utils_1.useLayoutEffect)(() => {
366
355
  if (!hasChips) {
367
356
  setChipInputMinWidth(undefined);
@@ -372,11 +361,9 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
372
361
  setChipInputMinWidth(Math.min(content, Math.max(plug, SEARCH_INPUT_PLUG_MIN_WIDTH)));
373
362
  }, [hasChips, inputValue, selectedPairs.length]);
374
363
  const searchInputField = ((0, jsx_runtime_1.jsx)(input_private_1.InputPrivate, { ref: (0, merge_refs_1.default)(ref, inputRef, inputElementRef), className: (0, classnames_1.default)(styles_module_scss_1.default.fieldInput, !searchable && styles_module_scss_2.default.readonlyCursor), value: inputValue, onChange: searchable ? handleInputChange : undefined, placeholder: selectedLabel || hasChips ? '' : placeholder, disabled: disabled, readonly: !searchable || readOnly, id: id, name: name, autoFocus: resolvedAutoFocus, tabIndex: inputTabIndex, onFocus: handleInputFocus, onBlur: handleInputBlur, onKeyDown: handleTriggerKeyDown, "data-test-id": constants_1.TEST_IDS.fieldSelectInput }));
375
- // В режиме чипов input заворачивается в `.inputWrapper` с динамической minWidth (флоу за чипами);
376
- // без чипов — обычный `inputArea` на всю строку.
377
364
  const searchInput = hasChips ? ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_2.default.inputWrapper, style: { minWidth: chipInputMinWidth }, children: searchInputField })) : ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.inputArea, children: searchInputField }));
378
365
  const trigger = (
379
- // Семантика: combobox-обёртка с InputPrivate внутри (input = focusable element).
366
+ // Focusable-элемент InputPrivate внутри, обёртка только собирает combobox.
380
367
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
381
368
  (0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(styles_module_scss_1.default.fieldWrapper, styles_module_scss_2.default.trigger, fieldClassName), "data-size": size, "data-validation-state": effectiveValidationState, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-withbackground": background || undefined, "data-focusvisible": focusVisible || open || undefined, "data-hover": !readOnly && !disabled && hover ? true : undefined, "data-test-id": dataTestId, onMouseEnter: handleTriggerMouseEnter, onMouseLeave: handleTriggerMouseLeave, onClick: handleTriggerClick, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.backgroundWrapper, children: [(0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.materialLayer, ...(0, shared_1.getAcrylicProps)({
382
369
  validationState: effectiveValidationState,
@@ -7,8 +7,7 @@ import { useLayoutEffect } from '@cloud-ru/ds-utils';
7
7
  import cn from 'classnames';
8
8
  import mergeRefs from 'merge-refs';
9
9
  import { forwardRef, useCallback, useEffect, useMemo, useRef, useState, } from 'react';
10
- // Минимальная ширина поискового input'а в режиме чипов: при пустом вводе input «схлопывается»
11
- // к этому значению и прижимается к последнему чипу (паритет с легаси BASE_MIN_WIDTH).
10
+ // Минимальная ширина поискового input'а в режиме чипов (паритет с легаси BASE_MIN_WIDTH).
12
11
  const SEARCH_INPUT_PLUG_MIN_WIDTH = 4;
13
12
  import { FieldDecorator, SIZE, VALIDATION_STATE } from '@cloud-ru/ds-field-decorator';
14
13
  import { TEST_IDS } from '../../constants.js';
@@ -25,14 +24,10 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
25
24
  const closeDroplistOnItemClick = props.closeDroplistOnItemClick ?? !multiple;
26
25
  const resolvedAutoFocus = useAdaptiveAutoFocus(autoFocus, layoutPresets);
27
26
  const inputRef = useRef(null);
28
- // Реальный <input>: `inputRef` уходит в Droplist как `triggerElemRef`, а `PopoverPrivate`
29
- // перезаписывает его `.current` своей span-обёрткой (см. MR!101). Отдельный ref на сам input
30
- // нужен для `useButtonNavigation` (курсор/фокус) и возврата фокуса после очистки.
27
+ // Отдельный ref на реальный <input>: `inputRef.current` перезаписывается span-обёрткой PopoverPrivate.
31
28
  const inputElementRef = useRef(null);
32
29
  const clearButtonRef = useRef(null);
33
30
  const copyButtonRef = useRef(null);
34
- // Контейнер чипов+input (замер доступной ширины) и скрытый плаг (замер ширины введённого текста)
35
- // для расчёта minWidth поискового input'а в режиме чипов — см. searchInput ниже.
36
31
  const contentRef = useRef(null);
37
32
  const inputPlugRef = useRef(null);
38
33
  const [chipInputMinWidth, setChipInputMinWidth] = useState(undefined);
@@ -42,13 +37,9 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
42
37
  const open = openProp ?? openLocal;
43
38
  const [singleLocal, setSingleLocal] = useState(!multiple ? props.defaultValue : undefined);
44
39
  const [multipleLocal, setMultipleLocal] = useState(
45
- // Array.isArray-страховка: при multiple defaultValue обязан быть массивом по типам,
46
- // но «расхлябанный» вызов (например, Storybook-spread, где single-default остаётся
47
- // строкой при переключении selection) не должен ронять компонент на `.map` строки.
40
+ // Страховка от вызова без типов (Storybook-spread): при multiple defaultValue может прийти строкой.
48
41
  multiple && Array.isArray(props.defaultValue) ? props.defaultValue : []);
49
- // Controlled-режим «залипает»: как только потребитель прислал `value`, локальный стейт больше
50
- // не читается. Иначе очистка (потребитель ставит `undefined`) откатывалась бы на устаревший
51
- // локальный выбор и воскрешала прошлое значение.
42
+ // Controlled-режим «залипает»: иначе очистка через `value: undefined` откатится на локальный стейт.
52
43
  const singleControlled = useRef(false);
53
44
  const multipleControlled = useRef(false);
54
45
  if (!multiple && props.value !== undefined) {
@@ -119,24 +110,28 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
119
110
  }
120
111
  return selectedPairs.map(formatPair).join(', ');
121
112
  }, [multiple, singleValue, selectedPairs, chips, props, formatPair, resolveItem]);
122
- // Строка поиска: controlled через `search.value`, иначе локальный state. Auto-sync к выбранному
123
- // значению делаем через СТАБИЛЬНЫЙ setLocalInput, а не через setInputValue в deps эффекта —
124
- // иначе нестабильная ссылка сеттера зацикливает эффект (сбрасывала бы значение постоянно).
125
113
  const [localInput, setLocalInput] = useState(search?.defaultValue ?? selectedLabel);
126
114
  const inputValue = search?.value !== undefined ? search.value : localInput;
127
115
  const [typing, setTyping] = useState(false);
116
+ // `search` — объектный литерал с новой идентичностью каждый рендер; ref делает `setInputValue` стабильным.
117
+ const searchRef = useRef(search);
118
+ searchRef.current = search;
128
119
  const setInputValue = useCallback((next) => {
129
- if (search?.value === undefined) {
120
+ const currentSearch = searchRef.current;
121
+ if (currentSearch?.value === undefined) {
130
122
  setLocalInput(next);
131
123
  }
132
- search?.onChange?.(next);
133
- }, [search]);
124
+ currentSearch?.onChange?.(next);
125
+ }, []);
126
+ const syncedLabelRef = useRef(selectedLabel);
134
127
  useEffect(() => {
135
- if (!typing && resetSearchOnOptionSelection && search?.value === undefined) {
136
- setLocalInput(selectedLabel);
128
+ if (typing || !resetSearchOnOptionSelection || syncedLabelRef.current === selectedLabel) {
129
+ return;
137
130
  }
138
- }, [selectedLabel, typing, resetSearchOnOptionSelection, search]);
139
- // autocomplete — клиентскую фильтрацию не делаем, список берётся из items как есть (серверный поиск).
131
+ syncedLabelRef.current = selectedLabel;
132
+ setInputValue(selectedLabel);
133
+ }, [selectedLabel, typing, resetSearchOnOptionSelection, setInputValue]);
134
+ // При autocomplete фильтрует сервер — items берутся как есть.
140
135
  const filteredItems = useMemo(() => {
141
136
  if (!searchable || autocomplete || !typing) {
142
137
  return items;
@@ -156,8 +151,7 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
156
151
  return filterItems(pinBottom, inputValue, enableFuzzySearch);
157
152
  }, [searchable, autocomplete, typing, pinBottom, inputValue, enableFuzzySearch]);
158
153
  const handleOpenChange = useCallback((next) => {
159
- // Дроплист открывается кликом по полю (Droplist дёргает onOpenChange(true)).
160
- // readonly/disabled поле открывать нельзя — блокируем открытие, закрытие разрешаем.
154
+ // readonly/disabled поле открывать нельзя, закрывать можно.
161
155
  if (next && (disabled || readOnly))
162
156
  return;
163
157
  if (openProp === undefined) {
@@ -255,9 +249,7 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
255
249
  }
256
250
  return copied;
257
251
  }, [onCopyButtonClick, valueToCopy]);
258
- // Clear/Copy как roving-postfix: ArrowRight из input (курсор в конце или readonly) уводит на
259
- // кнопку, ArrowLeft — обратно в поле. Паритет с FieldCombo. `inputElementRef` — реальный input,
260
- // т.к. `inputRef` клобберится popover'ом.
252
+ // Clear/Copy roving-postfix (ArrowRight/ArrowLeft), паритет с FieldCombo.
261
253
  const clearButtonSettings = useClearButton({
262
254
  clearButtonRef,
263
255
  showClearButton: showClear,
@@ -282,9 +274,8 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
282
274
  submitKeys: ['Enter', 'Space', 'Tab'],
283
275
  });
284
276
  const handleTriggerKeyDown = (event) => {
285
- // Пробрасываем consumer onKeyDown до внутренней обработки (паритет с легаси useHandleOnKeyDown).
277
+ // Consumer-обработчик идёт до внутренней обработки (паритет с легаси useHandleOnKeyDown).
286
278
  onKeyDownProp?.(event);
287
- // Roving-навигация по clear/copy (ArrowRight/ArrowLeft). ArrowDown/Enter ниже не перехватываются.
288
279
  onInputKeyDown(event);
289
280
  if (disabled || readOnly) {
290
281
  return;
@@ -298,7 +289,7 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
298
289
  handleOpenChange(false);
299
290
  }
300
291
  if (addOptionByEnter && event.key === 'Enter' && inputValue !== '') {
301
- // Зафиксировать введённый текст как новый выбор (создание опции «на лету»).
292
+ // Введённый текст становится новым значением (создание опции «на лету»).
302
293
  event.preventDefault();
303
294
  if (multiple) {
304
295
  if (!multipleValue.includes(inputValue)) {
@@ -352,10 +343,8 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
352
343
  ? { mode: 'multiple', value: multipleValue, onChange: handleMultipleChange }
353
344
  : { mode: 'single', value: singleValue, onChange: handleSingleChange };
354
345
  const hasChips = chips && selectedPairs.length > 0;
355
- // minWidth поискового input'а в режиме чипов: ширина введённого текста (скрытый `.inputPlug`),
356
- // но не больше ширины строки чипов (`contentRef`). Замер в layout-эффекте (до paint, без мигания):
357
- // пустой ввод → ~4px (input прижат к последнему чипу), длинный ввод → перенос на новую строку
358
- // через flex-wrap. Без чипов minWidth не задаётся (input занимает всю строку как single-select).
346
+ // minWidth поискового input'а = ширина введённого текста (`.inputPlug`), но не больше строки чипов.
347
+ // Замер до paint, иначе input мигает при вводе.
359
348
  useLayoutEffect(() => {
360
349
  if (!hasChips) {
361
350
  setChipInputMinWidth(undefined);
@@ -366,11 +355,9 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
366
355
  setChipInputMinWidth(Math.min(content, Math.max(plug, SEARCH_INPUT_PLUG_MIN_WIDTH)));
367
356
  }, [hasChips, inputValue, selectedPairs.length]);
368
357
  const searchInputField = (_jsx(InputPrivate, { ref: mergeRefs(ref, inputRef, inputElementRef), className: cn(fieldStyles.fieldInput, !searchable && styles.readonlyCursor), value: inputValue, onChange: searchable ? handleInputChange : undefined, placeholder: selectedLabel || hasChips ? '' : placeholder, disabled: disabled, readonly: !searchable || readOnly, id: id, name: name, autoFocus: resolvedAutoFocus, tabIndex: inputTabIndex, onFocus: handleInputFocus, onBlur: handleInputBlur, onKeyDown: handleTriggerKeyDown, "data-test-id": TEST_IDS.fieldSelectInput }));
369
- // В режиме чипов input заворачивается в `.inputWrapper` с динамической minWidth (флоу за чипами);
370
- // без чипов — обычный `inputArea` на всю строку.
371
358
  const searchInput = hasChips ? (_jsx("div", { className: styles.inputWrapper, style: { minWidth: chipInputMinWidth }, children: searchInputField })) : (_jsx("div", { className: fieldStyles.inputArea, children: searchInputField }));
372
359
  const trigger = (
373
- // Семантика: combobox-обёртка с InputPrivate внутри (input = focusable element).
360
+ // Focusable-элемент InputPrivate внутри, обёртка только собирает combobox.
374
361
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
375
362
  _jsxs("div", { className: cn(fieldStyles.fieldWrapper, styles.trigger, fieldClassName), "data-size": size, "data-validation-state": effectiveValidationState, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-withbackground": background || undefined, "data-focusvisible": focusVisible || open || undefined, "data-hover": !readOnly && !disabled && hover ? true : undefined, "data-test-id": dataTestId, onMouseEnter: handleTriggerMouseEnter, onMouseLeave: handleTriggerMouseLeave, onClick: handleTriggerClick, children: [_jsxs("div", { className: fieldStyles.backgroundWrapper, children: [_jsx("div", { className: fieldStyles.materialLayer, ...getAcrylicProps({
376
363
  validationState: effectiveValidationState,