@cloud-ru/ds-fields 2.2.3-preview-0c4119af.0 → 2.2.3-preview-7425cb27.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.
Files changed (76) hide show
  1. package/README.md +1 -3
  2. package/dist/cjs/components/FieldDate/FieldDate.js +3 -3
  3. package/dist/cjs/components/FieldDate/mask.d.ts +3 -2
  4. package/dist/cjs/components/FieldDate/mask.js +4 -3
  5. package/dist/cjs/components/FieldSelect/FieldSelect.js +41 -81
  6. package/dist/cjs/components/FieldSelect/constants.d.ts +0 -11
  7. package/dist/cjs/components/FieldSelect/constants.js +1 -20
  8. package/dist/cjs/components/FieldSelect/types.d.ts +3 -26
  9. package/dist/cjs/components/FieldSelect/utils.d.ts +19 -0
  10. package/dist/cjs/components/FieldSelect/utils.js +103 -0
  11. package/dist/cjs/components/FieldTime/FieldTime.js +2 -2
  12. package/dist/cjs/segments/useSegmentedMask.d.ts +4 -0
  13. package/dist/cjs/segments/useSegmentedMask.js +155 -56
  14. package/dist/esm/components/FieldDate/FieldDate.js +3 -3
  15. package/dist/esm/components/FieldDate/mask.d.ts +3 -2
  16. package/dist/esm/components/FieldDate/mask.js +4 -3
  17. package/dist/esm/components/FieldSelect/FieldSelect.js +42 -82
  18. package/dist/esm/components/FieldSelect/constants.d.ts +0 -11
  19. package/dist/esm/components/FieldSelect/constants.js +0 -19
  20. package/dist/esm/components/FieldSelect/types.d.ts +3 -26
  21. package/dist/esm/components/FieldSelect/utils.d.ts +19 -0
  22. package/dist/esm/components/FieldSelect/utils.js +92 -0
  23. package/dist/esm/components/FieldTime/FieldTime.js +2 -2
  24. package/dist/esm/segments/useSegmentedMask.d.ts +4 -0
  25. package/dist/esm/segments/useSegmentedMask.js +155 -56
  26. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  27. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  28. package/package.json +6 -6
  29. package/src/components/FieldDate/FieldDate.tsx +3 -3
  30. package/src/components/FieldDate/mask.ts +4 -3
  31. package/src/components/FieldSelect/FieldSelect.tsx +61 -129
  32. package/src/components/FieldSelect/constants.ts +0 -22
  33. package/src/components/FieldSelect/types.ts +48 -82
  34. package/src/components/FieldSelect/utils.ts +132 -0
  35. package/src/components/FieldTime/FieldTime.tsx +2 -2
  36. package/src/segments/useSegmentedMask.ts +181 -52
  37. package/dist/cjs/components/FieldSelect/hooks.d.ts +0 -32
  38. package/dist/cjs/components/FieldSelect/hooks.js +0 -83
  39. package/dist/cjs/components/FieldSelect/utils/customOption.d.ts +0 -21
  40. package/dist/cjs/components/FieldSelect/utils/customOption.js +0 -55
  41. package/dist/cjs/components/FieldSelect/utils/extract.d.ts +0 -11
  42. package/dist/cjs/components/FieldSelect/utils/extract.js +0 -39
  43. package/dist/cjs/components/FieldSelect/utils/filter.d.ts +0 -3
  44. package/dist/cjs/components/FieldSelect/utils/filter.js +0 -40
  45. package/dist/cjs/components/FieldSelect/utils/index.d.ts +0 -6
  46. package/dist/cjs/components/FieldSelect/utils/index.js +0 -22
  47. package/dist/cjs/components/FieldSelect/utils/items.d.ts +0 -9
  48. package/dist/cjs/components/FieldSelect/utils/items.js +0 -47
  49. package/dist/cjs/components/FieldSelect/utils/selection.d.ts +0 -11
  50. package/dist/cjs/components/FieldSelect/utils/selection.js +0 -18
  51. package/dist/cjs/components/FieldSelect/utils/tagSize.d.ts +0 -4
  52. package/dist/cjs/components/FieldSelect/utils/tagSize.js +0 -5
  53. package/dist/esm/components/FieldSelect/hooks.d.ts +0 -32
  54. package/dist/esm/components/FieldSelect/hooks.js +0 -80
  55. package/dist/esm/components/FieldSelect/utils/customOption.d.ts +0 -21
  56. package/dist/esm/components/FieldSelect/utils/customOption.js +0 -48
  57. package/dist/esm/components/FieldSelect/utils/extract.d.ts +0 -11
  58. package/dist/esm/components/FieldSelect/utils/extract.js +0 -34
  59. package/dist/esm/components/FieldSelect/utils/filter.d.ts +0 -3
  60. package/dist/esm/components/FieldSelect/utils/filter.js +0 -36
  61. package/dist/esm/components/FieldSelect/utils/index.d.ts +0 -6
  62. package/dist/esm/components/FieldSelect/utils/index.js +0 -6
  63. package/dist/esm/components/FieldSelect/utils/items.d.ts +0 -9
  64. package/dist/esm/components/FieldSelect/utils/items.js +0 -41
  65. package/dist/esm/components/FieldSelect/utils/selection.d.ts +0 -11
  66. package/dist/esm/components/FieldSelect/utils/selection.js +0 -14
  67. package/dist/esm/components/FieldSelect/utils/tagSize.d.ts +0 -4
  68. package/dist/esm/components/FieldSelect/utils/tagSize.js +0 -2
  69. package/src/components/FieldSelect/hooks.ts +0 -162
  70. package/src/components/FieldSelect/utils/customOption.ts +0 -77
  71. package/src/components/FieldSelect/utils/extract.ts +0 -49
  72. package/src/components/FieldSelect/utils/filter.ts +0 -51
  73. package/src/components/FieldSelect/utils/index.ts +0 -6
  74. package/src/components/FieldSelect/utils/items.ts +0 -58
  75. package/src/components/FieldSelect/utils/selection.ts +0 -25
  76. package/src/components/FieldSelect/utils/tagSize.ts +0 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud-ru/ds-fields",
3
- "version": "2.2.3-preview-0c4119af.0",
3
+ "version": "2.2.3-preview-7425cb27.0",
4
4
  "description": "Поля ввода с единой обёрткой label/caption/hint/error и общими осями size/validationState — текст, пароль, число, ползунок, дата, время, выбор, цвет.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,23 +40,23 @@
40
40
  "merge-refs": "^2.0.0",
41
41
  "react-textarea-autosize": "^8.5.9",
42
42
  "@cloud-ru/ds-adaptive": "1.0.1",
43
- "@cloud-ru/ds-button": "1.0.2",
44
43
  "@cloud-ru/ds-bottom-sheet": "1.1.1",
45
- "@cloud-ru/ds-calendar": "1.0.15-preview-0c4119af.0",
44
+ "@cloud-ru/ds-button": "1.0.2",
45
+ "@cloud-ru/ds-calendar": "1.0.14",
46
46
  "@cloud-ru/ds-color-picker": "1.0.6",
47
47
  "@cloud-ru/ds-divider": "1.0.1",
48
48
  "@cloud-ru/ds-field-decorator": "1.0.5",
49
49
  "@cloud-ru/ds-icons": "1.1.0",
50
+ "@cloud-ru/ds-list": "2.1.3",
50
51
  "@cloud-ru/ds-dropdown": "1.0.8",
51
- "@cloud-ru/ds-list": "2.1.4-preview-0c4119af.0",
52
52
  "@cloud-ru/ds-input-private": "1.0.4",
53
53
  "@cloud-ru/ds-loader": "1.0.1",
54
54
  "@cloud-ru/ds-materials": "1.0.1",
55
55
  "@cloud-ru/ds-scroll": "1.0.2",
56
- "@cloud-ru/ds-tooltip": "1.0.4",
57
- "@cloud-ru/ds-slider": "1.0.4",
58
56
  "@cloud-ru/ds-skeleton": "1.0.1",
57
+ "@cloud-ru/ds-slider": "1.0.4",
59
58
  "@cloud-ru/ds-tag": "1.1.1",
59
+ "@cloud-ru/ds-tooltip": "1.0.4",
60
60
  "@cloud-ru/ds-utils": "1.1.0"
61
61
  }
62
62
  }
@@ -520,12 +520,12 @@ export const FieldDate = forwardRef<HTMLInputElement, FieldDateProps>(function F
520
520
  </span>
521
521
  ) : (
522
522
  <InputPrivate
523
- ref={mergeRefs(ref, fromInputRef)}
523
+ ref={mergeRefs(ref, fromInputRef, singleSeg.nativeInputRef)}
524
524
  className={fieldStyles.fieldInput}
525
525
  value={singleInputValue}
526
526
  placeholder={placeholderMask}
527
- // Single/date-time ведёт сегментный движок (keydown, useSegmentedMask), нативный
528
- // onChange игнорируется. Range — обычный ввод-по-маске (formatMask в handleRangeInput).
527
+ // Single/date-time ведёт сегментный движок (useSegmentedMask), нативный onChange
528
+ // игнорируется. Range — обычный ввод-по-маске (formatMask в handleRangeInput).
529
529
  onChange={() => {}}
530
530
  onKeyDown={handleSingleKeyDown}
531
531
  onClick={singleSeg.handleClick}
@@ -55,8 +55,9 @@ function buildSegments(mode: MaskMode, showSeconds: boolean): Segment[] {
55
55
  * «десятку» (`day` → > 3, `month` → > 1, `hour` → > 2, `minute`/`second` → > 5), цифра трактуется
56
56
  * как «единицы» и в начало сегмента добавляется ноль. Пример: `5` для day → `05.`,
57
57
  * фокус автоматически уезжает на следующий сегмент.
58
- * 2. **Clamp полной двойки**: если набрано две цифры и значение превышает `max` сегмента —
59
- * значение прижимается к верхней границе. Пример: `55` для day → `31`, `99` для month → `12`.
58
+ * 2. **Переполнение полной двойки**: если набрано две цифры и значение превышает `max` сегмента —
59
+ * сегмент начинается заново с последней цифры (как посегментный ввод в `useSegmentedMask`).
60
+ * Пример: `39` для day → `09`, `13` для month → `03`.
60
61
  */
61
62
  export function formatMask(input: string, mode: MaskMode, showSeconds = true): string {
62
63
  const maxLen = totalLen(mode, showSeconds);
@@ -81,7 +82,7 @@ export function formatMask(input: string, mode: MaskMode, showSeconds = true): s
81
82
  stream = stream.slice(seg.len);
82
83
  if (raw.length === seg.len) {
83
84
  const n = Number(raw);
84
- out += n > seg.max ? String(seg.max).padStart(seg.len, '0') : raw;
85
+ out += n > seg.max ? raw.slice(-1).padStart(seg.len, '0') : raw;
85
86
  } else {
86
87
  out += raw;
87
88
  }
@@ -34,7 +34,6 @@ import {
34
34
  useCopyButton,
35
35
  } from '../shared';
36
36
  import fieldStyles from '../shared/styles.module.scss';
37
- import { useCustomOption } from './hooks';
38
37
  import styles from './styles.module.scss';
39
38
  import { FieldSelectProps } from './types';
40
39
  import {
@@ -43,8 +42,6 @@ import {
43
42
  filterItems,
44
43
  findItem,
45
44
  flatten,
46
- getMissingSelectedItems,
47
- getSelectedIds,
48
45
  isMultiple,
49
46
  TAG_SIZE_MAP,
50
47
  WithIdContent,
@@ -114,8 +111,6 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
114
111
  const chips = multiple ? (props.chips ?? true) : false;
115
112
  const removeByBackspace = multiple ? (props.removeByBackspace ?? true) : false;
116
113
  const closeDroplistOnItemClick = props.closeDroplistOnItemClick ?? !multiple;
117
- const allowCustomOption = props.allowCustomOption === true;
118
- const addCustomOptionTriggers = allowCustomOption ? props.addCustomOptionTriggers : undefined;
119
114
 
120
115
  const resolvedAutoFocus = useAdaptiveAutoFocus(autoFocus, layoutPresets);
121
116
 
@@ -182,31 +177,18 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
182
177
 
183
178
  const allItems = useMemo(() => [...(pinTop ?? []), ...items, ...(pinBottom ?? [])], [items, pinTop, pinBottom]);
184
179
 
185
- const selectedIds = useMemo(
186
- () => getSelectedIds({ multiple, multipleValue, singleValue }),
187
- [multiple, multipleValue, singleValue],
188
- );
189
-
190
- // Выбранное, которого нет в `items` (кастом по allowCustomOption, ленивая подгрузка) — в список, не только в чип.
191
- const missingSelectedItems = useMemo(() => getMissingSelectedItems(selectedIds, allItems), [selectedIds, allItems]);
192
-
193
- const itemsWithPlaceholders = useMemo(
194
- () => (missingSelectedItems.length > 0 ? [...missingSelectedItems, ...items] : items),
195
- [missingSelectedItems, items],
196
- );
197
-
198
180
  // Выбранное значение переживает смену `items` (серверный поиск, ленивая подгрузка).
199
181
  const seenItems = useRef(new Map<ItemId, WithIdContent>());
200
182
 
201
183
  const resolveItem = useMemo(() => {
202
- for (const item of flatten([...missingSelectedItems, ...allItems])) {
184
+ for (const item of flatten(allItems)) {
203
185
  if (item.id !== undefined) {
204
186
  seenItems.current.set(item.id, item);
205
187
  }
206
188
  }
207
189
 
208
- return (id: ItemId) => findItem(allItems, id) ?? findItem(missingSelectedItems, id) ?? seenItems.current.get(id);
209
- }, [allItems, missingSelectedItems]);
190
+ return (id: ItemId) => findItem(allItems, id) ?? seenItems.current.get(id);
191
+ }, [allItems]);
210
192
 
211
193
  const selectedPairs = useMemo(() => {
212
194
  if (!multiple || multipleValue.length === 0) {
@@ -283,14 +265,14 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
283
265
  setInputValue(selectedLabel);
284
266
  }, [selectedLabel, typing, resetSearchOnOptionSelection, setInputValue]);
285
267
 
286
- // При autocomplete фильтрует сервер — items берутся как есть, плюс выбранное вне списка.
268
+ // При autocomplete фильтрует сервер — items берутся как есть.
287
269
  const filteredItems = useMemo(() => {
288
270
  if (!searchable || autocomplete || !typing) {
289
- return itemsWithPlaceholders;
271
+ return items;
290
272
  }
291
273
 
292
- return filterItems(itemsWithPlaceholders, inputValue, enableFuzzySearch);
293
- }, [searchable, autocomplete, typing, itemsWithPlaceholders, inputValue, enableFuzzySearch]);
274
+ return filterItems(items, inputValue, enableFuzzySearch);
275
+ }, [searchable, autocomplete, typing, items, inputValue, enableFuzzySearch]);
294
276
 
295
277
  const filteredPinTop = useMemo(() => {
296
278
  if (!searchable || autocomplete || !typing || !pinTop) {
@@ -308,112 +290,61 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
308
290
  return filterItems(pinBottom, inputValue, enableFuzzySearch);
309
291
  }, [searchable, autocomplete, typing, pinBottom, inputValue, enableFuzzySearch]);
310
292
 
311
- const handleSingleChange = useCallback(
312
- (next: ItemId | undefined) => {
313
- if (multiple) {
314
- return;
315
- }
316
-
317
- if (props.value === undefined) {
318
- setSingleLocal(next);
319
- }
320
-
321
- props.onChange?.(next);
322
- setTyping(false);
323
- },
324
- [multiple, props],
325
- );
326
-
327
- const handleMultipleChange = useCallback(
328
- (next: ItemId[]) => {
329
- if (!multiple) {
330
- return;
331
- }
332
-
333
- if (props.value === undefined) {
334
- setMultipleLocal(next);
293
+ const handleOpenChange = useCallback(
294
+ (next: boolean) => {
295
+ // readonly/disabled поле открывать нельзя, закрывать — можно.
296
+ if (next && (disabled || readOnly)) return;
297
+ if (openProp === undefined) {
298
+ setOpenLocal(next);
335
299
  }
336
300
 
337
- props.onChange?.(next);
301
+ onOpenChange?.(next);
338
302
 
339
- if (searchable) {
303
+ if (!next) {
340
304
  setTyping(false);
341
305
 
342
306
  if (resetSearchOnOptionSelection) {
343
- setInputValue('');
307
+ setInputValue(selectedLabel);
344
308
  }
345
309
  }
346
310
  },
347
- [multiple, props, searchable, resetSearchOnOptionSelection, setInputValue],
311
+ [openProp, onOpenChange, disabled, readOnly, resetSearchOnOptionSelection, setInputValue, selectedLabel],
348
312
  );
349
313
 
350
- const resetCustomOptionSearch = useCallback(() => {
351
- setTyping(false);
314
+ const handleSingleChange = (next: ItemId | undefined) => {
315
+ if (multiple) {
316
+ return;
317
+ }
352
318
 
353
- if (resetSearchOnOptionSelection) {
354
- setInputValue(multiple ? '' : selectedLabel);
319
+ if (props.value === undefined) {
320
+ setSingleLocal(next);
355
321
  }
356
- }, [resetSearchOnOptionSelection, setInputValue, multiple, selectedLabel]);
357
322
 
358
- const {
359
- listRef,
360
- suppressCloseCommit,
361
- enableCloseCommit,
362
- tryCommitOnClose,
363
- tryCommitOnBlur,
364
- tryCommitFromKeyboard,
365
- handleListSingleChange,
366
- handleListMultipleChange,
367
- } = useCustomOption({
368
- allowCustomOption,
369
- addCustomOptionTriggers,
370
- addOptionByEnter,
371
- multiple,
372
- inputValue,
373
- multipleValue,
374
- commitSingle: handleSingleChange,
375
- commitMultiple: handleMultipleChange,
376
- resetSearch: resetCustomOptionSearch,
377
- inputElementRef,
378
- clearButtonRef,
379
- copyButtonRef,
380
- footerActiveElementsRefs,
381
- });
323
+ props.onChange?.(next);
324
+ setTyping(false);
325
+ };
382
326
 
383
- const handleOpenChange = useCallback(
384
- (next: boolean) => {
385
- // readonly/disabled поле открывать нельзя, закрывать — можно.
386
- if (next && (disabled || readOnly)) return;
387
- if (openProp === undefined) {
388
- setOpenLocal(next);
389
- }
327
+ const handleMultipleChange = (next: ItemId[]) => {
328
+ if (!multiple) {
329
+ return;
330
+ }
390
331
 
391
- onOpenChange?.(next);
332
+ if (props.value === undefined) {
333
+ setMultipleLocal(next);
334
+ }
392
335
 
393
- if (!next) {
394
- setTyping(false);
395
- const committed = tryCommitOnClose();
336
+ props.onChange?.(next);
396
337
 
397
- // Кастомная опция не зафиксирована — вернуть лейбл выбранного значения.
398
- if (resetSearchOnOptionSelection && !committed) {
399
- setInputValue(selectedLabel);
400
- }
338
+ if (searchable) {
339
+ setTyping(false);
340
+
341
+ if (resetSearchOnOptionSelection) {
342
+ setInputValue('');
401
343
  }
402
- },
403
- [
404
- openProp,
405
- onOpenChange,
406
- disabled,
407
- readOnly,
408
- resetSearchOnOptionSelection,
409
- setInputValue,
410
- selectedLabel,
411
- tryCommitOnClose,
412
- ],
413
- );
344
+ }
345
+ };
414
346
 
415
347
  const handleInputChange = (next: string) => {
416
- enableCloseCommit();
417
348
  setInputValue(next);
418
349
  setTyping(true);
419
350
 
@@ -540,13 +471,21 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
540
471
 
541
472
  if (event.key === 'Escape' && open) {
542
473
  event.preventDefault();
543
- suppressCloseCommit();
544
474
  handleOpenChange(false);
545
475
  }
546
476
 
547
- if (tryCommitFromKeyboard(event)) {
477
+ if (addOptionByEnter && event.key === 'Enter' && inputValue !== '') {
478
+ // Введённый текст становится новым значением (создание опции «на лету»).
548
479
  event.preventDefault();
549
- event.stopPropagation();
480
+
481
+ if (multiple) {
482
+ if (!multipleValue.includes(inputValue)) {
483
+ handleMultipleChange([...multipleValue, inputValue]);
484
+ }
485
+ } else {
486
+ handleSingleChange(inputValue);
487
+ }
488
+
550
489
  return;
551
490
  }
552
491
 
@@ -597,23 +536,17 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
597
536
  [searchable, onFocusProp],
598
537
  );
599
538
 
600
- const handleInputBlur = (event: FocusEvent<HTMLInputElement>) => {
601
- setFocusVisible(false);
602
- const blurResult = tryCommitOnBlur(event);
603
-
604
- // Закрытый список не получит onOpenChange — вернуть лейбл выбранного значения.
605
- // Клик по айтему открытого списка даёт relatedTarget === null, восстановление делает закрытие.
606
- if (resetSearchOnOptionSelection && blurResult === 'skipped' && (!open || event.relatedTarget !== null)) {
607
- setTyping(false);
608
- setInputValue(selectedLabel);
609
- }
610
-
611
- onBlurProp?.(event);
612
- };
539
+ const handleInputBlur = useCallback(
540
+ (event: FocusEvent<HTMLInputElement>) => {
541
+ setFocusVisible(false);
542
+ onBlurProp?.(event);
543
+ },
544
+ [onBlurProp],
545
+ );
613
546
 
614
547
  const droplistSelection = multiple
615
- ? { mode: 'multiple' as const, value: multipleValue, onChange: handleListMultipleChange }
616
- : { mode: 'single' as const, value: singleValue, onChange: handleListSingleChange };
548
+ ? { mode: 'multiple' as const, value: multipleValue, onChange: handleMultipleChange }
549
+ : { mode: 'single' as const, value: singleValue, onChange: handleSingleChange };
617
550
 
618
551
  const hasChips = chips && selectedPairs.length > 0;
619
552
 
@@ -765,7 +698,6 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
765
698
  placement={placement}
766
699
  widthStrategy={widthStrategy}
767
700
  triggerElemRef={inputRef}
768
- listRef={listRef}
769
701
  size={size}
770
702
  open={open}
771
703
  onOpenChange={handleOpenChange}
@@ -2,25 +2,3 @@ export const SELECTION_MODE = {
2
2
  Single: 'single',
3
3
  Multiple: 'multiple',
4
4
  } as const;
5
-
6
- /** События, по которым произвольный ввод фиксируется как выбранная опция. */
7
- export const ADD_CUSTOM_OPTION_TRIGGER = {
8
- Enter: 'enter',
9
- Blur: 'blur',
10
- Space: 'space',
11
- Comma: 'comma',
12
- } as const;
13
-
14
- /** Триггеры по умолчанию для `selection='single'` при `allowCustomOption`. */
15
- export const ADD_CUSTOM_OPTION_TRIGGERS_SINGLE = [
16
- ADD_CUSTOM_OPTION_TRIGGER.Enter,
17
- ADD_CUSTOM_OPTION_TRIGGER.Blur,
18
- ] as const;
19
-
20
- /** Триггеры по умолчанию для `selection='multiple'` при `allowCustomOption`. */
21
- export const ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE = [
22
- ADD_CUSTOM_OPTION_TRIGGER.Enter,
23
- ADD_CUSTOM_OPTION_TRIGGER.Blur,
24
- ADD_CUSTOM_OPTION_TRIGGER.Space,
25
- ADD_CUSTOM_OPTION_TRIGGER.Comma,
26
- ] as const;
@@ -5,41 +5,11 @@ import { ValueOf } from '@cloud-ru/ds-utils';
5
5
  import { FocusEvent, KeyboardEvent, ReactNode } from 'react';
6
6
 
7
7
  import { FieldLayoutPresets } from '../../hooks';
8
- import {
9
- ADD_CUSTOM_OPTION_TRIGGER,
10
- ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE,
11
- ADD_CUSTOM_OPTION_TRIGGERS_SINGLE,
12
- SELECTION_MODE,
13
- } from './constants';
8
+ import { SELECTION_MODE } from './constants';
14
9
 
15
10
  /** Режим выбора FieldSelect: одно значение или несколько. */
16
11
  export type Selection = ValueOf<typeof SELECTION_MODE>;
17
12
 
18
- /** Событие, по которому произвольный ввод фиксируется как выбранная опция. */
19
- export type FieldSelectAddCustomOptionTrigger = ValueOf<typeof ADD_CUSTOM_OPTION_TRIGGER>;
20
-
21
- /** Триггеры кастомной опции в режиме `single`. */
22
- export type FieldSelectSingleAddCustomOptionTrigger = (typeof ADD_CUSTOM_OPTION_TRIGGERS_SINGLE)[number];
23
-
24
- /** Триггеры кастомной опции в режиме `multiple`. */
25
- export type FieldSelectMultipleAddCustomOptionTrigger = (typeof ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE)[number];
26
-
27
- type CustomOptionProps<TTrigger extends FieldSelectAddCustomOptionTrigger> = {
28
- /**
29
- * Разрешить фиксировать произвольный ввод из строки поиска как выбранную опцию
30
- * (значение, которого нет в `items`).
31
- * @default false
32
- */
33
- allowCustomOption?: boolean;
34
- /**
35
- * События, по которым произвольный ввод фиксируется как выбранная опция.
36
- * Учитывается только при `allowCustomOption={true}`.
37
- * Если не задан — используются все триггеры режима (`single`: enter, blur; `multiple`: enter, blur, space, comma).
38
- * Триггеры, недоступные в текущем `selection`, игнорируются.
39
- */
40
- addCustomOptionTriggers?: TTrigger[];
41
- };
42
-
43
13
  // ItemProps — union с рекурсивными группами, поэтому раздаём поле по всем веткам.
44
14
  type WithTagAppearance<T> = T extends { items: ItemProps[] }
45
15
  ? Omit<T, 'items'> & { appearance?: TagAppearance; items: WithTagAppearance<ItemProps>[] }
@@ -145,8 +115,6 @@ type CommonSelectProps = FieldSelectDecoratorProps &
145
115
  autocomplete?: boolean;
146
116
  /**
147
117
  * Зафиксировать введённый текст как новый выбор по `Enter` (создание опции «на лету»).
148
- *
149
- * @deprecated Используйте `allowCustomOption`. `true` эквивалентен `allowCustomOption` с триггером `enter`.
150
118
  * @default false
151
119
  */
152
120
  addOptionByEnter?: boolean;
@@ -201,55 +169,53 @@ type CommonSelectProps = FieldSelectDecoratorProps &
201
169
  'data-test-id'?: string;
202
170
  };
203
171
 
204
- export type FieldSelectSingleProps = CommonSelectProps &
205
- CustomOptionProps<FieldSelectSingleAddCustomOptionTrigger> & {
206
- /** Режим выбора. По умолчанию `'single'`. */
207
- selection?: typeof SELECTION_MODE.Single;
208
- /** Управляемое значение. Пустая строка трактуется как «значение не выбрано». */
209
- value?: ItemId;
210
- /** Неуправляемое значение по умолчанию. Пустая строка трактуется как «значение не выбрано». */
211
- defaultValue?: ItemId;
212
- /** Колбек смены значения */
213
- onChange?(value: ItemId | undefined): void;
214
- /**
215
- * Закрывать дроплист после клика на айтем.
216
- * @default true
217
- */
218
- closeDroplistOnItemClick?: boolean;
219
- };
172
+ export type FieldSelectSingleProps = CommonSelectProps & {
173
+ /** Режим выбора. По умолчанию `'single'`. */
174
+ selection?: typeof SELECTION_MODE.Single;
175
+ /** Управляемое значение. Пустая строка трактуется как «значение не выбрано». */
176
+ value?: ItemId;
177
+ /** Неуправляемое значение по умолчанию. Пустая строка трактуется как «значение не выбрано». */
178
+ defaultValue?: ItemId;
179
+ /** Колбек смены значения */
180
+ onChange?(value: ItemId | undefined): void;
181
+ /**
182
+ * Закрывать дроплист после клика на айтем.
183
+ * @default true
184
+ */
185
+ closeDroplistOnItemClick?: boolean;
186
+ };
220
187
 
221
- export type FieldSelectMultipleProps = CommonSelectProps &
222
- CustomOptionProps<FieldSelectMultipleAddCustomOptionTrigger> & {
223
- /** Режим выбора */
224
- selection: typeof SELECTION_MODE.Multiple;
225
- /** Управляемые значения */
226
- value?: ItemId[];
227
- /** Неуправляемые значения по умолчанию */
228
- defaultValue?: ItemId[];
229
- /** Колбек смены значений */
230
- onChange?(value: ItemId[]): void;
231
- /**
232
- * Форматтер строки выбранных значений (используется, если `chips=false`).
233
- * @default — список лейблов через `, `
234
- */
235
- formatSelected?(selected: { id: ItemId; label: string }[]): string;
236
- /**
237
- * Отображать выбранные значения как чипы (`@cloud-ru/ds-tag`) внутри поля. Если `false`,
238
- * показывает строку из `formatSelected` либо comma-joined.
239
- * @default true
240
- */
241
- chips?: boolean;
242
- /**
243
- * Удалять последний чип по `Backspace`, когда строка ввода пустая.
244
- * Работает только при `chips=true` и `searchable=true`.
245
- * @default true
246
- */
247
- removeByBackspace?: boolean;
248
- /**
249
- * Закрывать дроплист после клика на айтем.
250
- * @default false
251
- */
252
- closeDroplistOnItemClick?: boolean;
253
- };
188
+ export type FieldSelectMultipleProps = CommonSelectProps & {
189
+ /** Режим выбора */
190
+ selection: typeof SELECTION_MODE.Multiple;
191
+ /** Управляемые значения */
192
+ value?: ItemId[];
193
+ /** Неуправляемые значения по умолчанию */
194
+ defaultValue?: ItemId[];
195
+ /** Колбек смены значений */
196
+ onChange?(value: ItemId[]): void;
197
+ /**
198
+ * Форматтер строки выбранных значений (используется, если `chips=false`).
199
+ * @default — список лейблов через `, `
200
+ */
201
+ formatSelected?(selected: { id: ItemId; label: string }[]): string;
202
+ /**
203
+ * Отображать выбранные значения как чипы (`@cloud-ru/ds-tag`) внутри поля. Если `false`,
204
+ * показывает строку из `formatSelected` либо comma-joined.
205
+ * @default true
206
+ */
207
+ chips?: boolean;
208
+ /**
209
+ * Удалять последний чип по `Backspace`, когда строка ввода пустая.
210
+ * Работает только при `chips=true` и `searchable=true`.
211
+ * @default true
212
+ */
213
+ removeByBackspace?: boolean;
214
+ /**
215
+ * Закрывать дроплист после клика на айтем.
216
+ * @default false
217
+ */
218
+ closeDroplistOnItemClick?: boolean;
219
+ };
254
220
 
255
221
  export type FieldSelectProps = FieldSelectSingleProps | FieldSelectMultipleProps;