@cloud-ru/ds-fields 1.0.2-preview-7665e9c3.0 → 1.0.2-preview-ece1f136.0

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.
@@ -61,13 +61,23 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
61
61
  }, [multiple, props.value, multipleLocal]);
62
62
  const effectiveValidationState = (0, react_1.useMemo)(() => (error ? field_decorator_1.VALIDATION_STATE.Error : validationState), [error, validationState]);
63
63
  const allItems = (0, react_1.useMemo)(() => [...(pinTop ?? []), ...items, ...(pinBottom ?? [])], [items, pinTop, pinBottom]);
64
+ // Выбранное значение переживает смену `items` (серверный поиск, ленивая подгрузка).
65
+ const seenItems = (0, react_1.useRef)(new Map());
66
+ const resolveItem = (0, react_1.useMemo)(() => {
67
+ for (const item of (0, utils_2.flatten)(allItems)) {
68
+ if (item.id !== undefined) {
69
+ seenItems.current.set(item.id, item);
70
+ }
71
+ }
72
+ return (id) => (0, utils_2.findItem)(allItems, id) ?? seenItems.current.get(id);
73
+ }, [allItems]);
64
74
  const selectedPairs = (0, react_1.useMemo)(() => {
65
75
  if (!multiple || multipleValue.length === 0) {
66
76
  return [];
67
77
  }
68
78
  // Неизвестное значение (ещё не загружены опции) показываем по его id, не теряем (паритет с легаси).
69
79
  return multipleValue.map(id => {
70
- const item = (0, utils_2.findItem)(allItems, id);
80
+ const item = resolveItem(id);
71
81
  return {
72
82
  id,
73
83
  label: item ? (0, utils_2.extractLabel)(item) : String(id),
@@ -75,14 +85,14 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
75
85
  appearance: (0, utils_2.extractAppearance)(item),
76
86
  };
77
87
  });
78
- }, [allItems, multiple, multipleValue]);
88
+ }, [multiple, multipleValue, resolveItem]);
79
89
  const formatPair = (0, react_1.useCallback)((pair) => (selectedOptionFormatter ? selectedOptionFormatter(pair) : pair.label), [selectedOptionFormatter]);
80
90
  const selectedLabel = (0, react_1.useMemo)(() => {
81
91
  if (!multiple) {
82
92
  if (singleValue === undefined) {
83
93
  return '';
84
94
  }
85
- const item = (0, utils_2.findItem)(allItems, singleValue);
95
+ const item = resolveItem(singleValue);
86
96
  const label = item ? (0, utils_2.extractLabel)(item) : String(singleValue);
87
97
  return formatPair({ id: singleValue, label });
88
98
  }
@@ -93,7 +103,7 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
93
103
  return props.formatSelected(selectedPairs);
94
104
  }
95
105
  return selectedPairs.map(formatPair).join(', ');
96
- }, [allItems, multiple, singleValue, selectedPairs, chips, props, formatPair]);
106
+ }, [multiple, singleValue, selectedPairs, chips, props, formatPair, resolveItem]);
97
107
  // Строка поиска: controlled через `search.value`, иначе локальный state. Auto-sync к выбранному
98
108
  // значению делаем через СТАБИЛЬНЫЙ setLocalInput, а не через setInputValue в deps эффекта —
99
109
  // иначе нестабильная ссылка сеттера зацикливает эффект (сбрасывала бы значение постоянно).
@@ -16,7 +16,7 @@ import { useAdaptiveAutoFocus } from '../../hooks/index.js';
16
16
  import { copyTextToClipboard, getAcrylicProps, preventSlotMouseDown, stopSlotClickPropagation, toInputSize, useCopyButton, } from '../shared/index.js';
17
17
  import fieldStyles from '../shared/styles.module.css';
18
18
  import styles from './styles.module.css';
19
- import { extractAppearance, extractLabel, filterItems, findItem, isMultiple, TAG_SIZE_MAP } from './utils.js';
19
+ import { extractAppearance, extractLabel, filterItems, findItem, flatten, isMultiple, TAG_SIZE_MAP, } from './utils.js';
20
20
  export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
21
21
  const { label = '', labelTooltip, caption, hint, error, validationState = VALIDATION_STATE.Default, showHintIcon, length, required, size = SIZE.M, className, fieldClassName, items, pinTop, pinBottom, placeholder = '', iconBefore, prefix, postfix, disabled, readonly: readOnly, background = true, open: openProp, onOpenChange, placement, widthStrategy = 'eq', searchable = true, search, autocomplete = false, addOptionByEnter = false, resetSearchOnOptionSelection = true, showClearButton = true, showCopyButton = true, onCopyButtonClick, id, name, autoFocus, layoutPresets, onFocus: onFocusProp, onBlur: onBlurProp, enableFuzzySearch = true, selectedOptionFormatter, footer, footerActiveElementsRefs, loading, noDataState, noResultsState, errorDataState, dataError, dataFiltered, virtualized, scrollToSelectedItem, limitedScrollHeight, untouchableScrollbars, closeOnPopstate, onKeyDown: onKeyDownProp, 'data-test-id': dataTestId = TEST_IDS.fieldSelect, } = props;
22
22
  const multiple = isMultiple(props);
@@ -55,13 +55,23 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
55
55
  }, [multiple, props.value, multipleLocal]);
56
56
  const effectiveValidationState = useMemo(() => (error ? VALIDATION_STATE.Error : validationState), [error, validationState]);
57
57
  const allItems = useMemo(() => [...(pinTop ?? []), ...items, ...(pinBottom ?? [])], [items, pinTop, pinBottom]);
58
+ // Выбранное значение переживает смену `items` (серверный поиск, ленивая подгрузка).
59
+ const seenItems = useRef(new Map());
60
+ const resolveItem = useMemo(() => {
61
+ for (const item of flatten(allItems)) {
62
+ if (item.id !== undefined) {
63
+ seenItems.current.set(item.id, item);
64
+ }
65
+ }
66
+ return (id) => findItem(allItems, id) ?? seenItems.current.get(id);
67
+ }, [allItems]);
58
68
  const selectedPairs = useMemo(() => {
59
69
  if (!multiple || multipleValue.length === 0) {
60
70
  return [];
61
71
  }
62
72
  // Неизвестное значение (ещё не загружены опции) показываем по его id, не теряем (паритет с легаси).
63
73
  return multipleValue.map(id => {
64
- const item = findItem(allItems, id);
74
+ const item = resolveItem(id);
65
75
  return {
66
76
  id,
67
77
  label: item ? extractLabel(item) : String(id),
@@ -69,14 +79,14 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
69
79
  appearance: extractAppearance(item),
70
80
  };
71
81
  });
72
- }, [allItems, multiple, multipleValue]);
82
+ }, [multiple, multipleValue, resolveItem]);
73
83
  const formatPair = useCallback((pair) => (selectedOptionFormatter ? selectedOptionFormatter(pair) : pair.label), [selectedOptionFormatter]);
74
84
  const selectedLabel = useMemo(() => {
75
85
  if (!multiple) {
76
86
  if (singleValue === undefined) {
77
87
  return '';
78
88
  }
79
- const item = findItem(allItems, singleValue);
89
+ const item = resolveItem(singleValue);
80
90
  const label = item ? extractLabel(item) : String(singleValue);
81
91
  return formatPair({ id: singleValue, label });
82
92
  }
@@ -87,7 +97,7 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
87
97
  return props.formatSelected(selectedPairs);
88
98
  }
89
99
  return selectedPairs.map(formatPair).join(', ');
90
- }, [allItems, multiple, singleValue, selectedPairs, chips, props, formatPair]);
100
+ }, [multiple, singleValue, selectedPairs, chips, props, formatPair, resolveItem]);
91
101
  // Строка поиска: controlled через `search.value`, иначе локальный state. Auto-sync к выбранному
92
102
  // значению делаем через СТАБИЛЬНЫЙ setLocalInput, а не через setInputValue в deps эффекта —
93
103
  // иначе нестабильная ссылка сеттера зацикливает эффект (сбрасывала бы значение постоянно).