@cloud-ru/ds-fields 2.2.3-preview-0c4119af.0 → 2.2.3-preview-a42433ce.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.
- package/README.md +1 -3
- package/dist/cjs/components/FieldDate/FieldDate.js +3 -3
- package/dist/cjs/components/FieldDate/mask.d.ts +3 -2
- package/dist/cjs/components/FieldDate/mask.js +4 -3
- package/dist/cjs/components/FieldSelect/FieldSelect.js +41 -81
- package/dist/cjs/components/FieldSelect/constants.d.ts +0 -11
- package/dist/cjs/components/FieldSelect/constants.js +1 -20
- package/dist/cjs/components/FieldSelect/types.d.ts +3 -26
- package/dist/cjs/components/FieldSelect/utils.d.ts +19 -0
- package/dist/cjs/components/FieldSelect/utils.js +103 -0
- package/dist/cjs/components/FieldTime/FieldTime.js +2 -2
- package/dist/cjs/segments/useSegmentedMask.d.ts +2 -0
- package/dist/cjs/segments/useSegmentedMask.js +146 -56
- package/dist/esm/components/FieldDate/FieldDate.js +3 -3
- package/dist/esm/components/FieldDate/mask.d.ts +3 -2
- package/dist/esm/components/FieldDate/mask.js +4 -3
- package/dist/esm/components/FieldSelect/FieldSelect.js +42 -82
- package/dist/esm/components/FieldSelect/constants.d.ts +0 -11
- package/dist/esm/components/FieldSelect/constants.js +0 -19
- package/dist/esm/components/FieldSelect/types.d.ts +3 -26
- package/dist/esm/components/FieldSelect/utils.d.ts +19 -0
- package/dist/esm/components/FieldSelect/utils.js +92 -0
- package/dist/esm/components/FieldTime/FieldTime.js +2 -2
- package/dist/esm/segments/useSegmentedMask.d.ts +2 -0
- package/dist/esm/segments/useSegmentedMask.js +146 -56
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/components/FieldDate/FieldDate.tsx +3 -3
- package/src/components/FieldDate/mask.ts +4 -3
- package/src/components/FieldSelect/FieldSelect.tsx +61 -129
- package/src/components/FieldSelect/constants.ts +0 -22
- package/src/components/FieldSelect/types.ts +48 -82
- package/src/components/FieldSelect/utils.ts +132 -0
- package/src/components/FieldTime/FieldTime.tsx +2 -2
- package/src/segments/useSegmentedMask.ts +172 -52
- package/dist/cjs/components/FieldSelect/hooks.d.ts +0 -32
- package/dist/cjs/components/FieldSelect/hooks.js +0 -83
- package/dist/cjs/components/FieldSelect/utils/customOption.d.ts +0 -21
- package/dist/cjs/components/FieldSelect/utils/customOption.js +0 -55
- package/dist/cjs/components/FieldSelect/utils/extract.d.ts +0 -11
- package/dist/cjs/components/FieldSelect/utils/extract.js +0 -39
- package/dist/cjs/components/FieldSelect/utils/filter.d.ts +0 -3
- package/dist/cjs/components/FieldSelect/utils/filter.js +0 -40
- package/dist/cjs/components/FieldSelect/utils/index.d.ts +0 -6
- package/dist/cjs/components/FieldSelect/utils/index.js +0 -22
- package/dist/cjs/components/FieldSelect/utils/items.d.ts +0 -9
- package/dist/cjs/components/FieldSelect/utils/items.js +0 -47
- package/dist/cjs/components/FieldSelect/utils/selection.d.ts +0 -11
- package/dist/cjs/components/FieldSelect/utils/selection.js +0 -18
- package/dist/cjs/components/FieldSelect/utils/tagSize.d.ts +0 -4
- package/dist/cjs/components/FieldSelect/utils/tagSize.js +0 -5
- package/dist/esm/components/FieldSelect/hooks.d.ts +0 -32
- package/dist/esm/components/FieldSelect/hooks.js +0 -80
- package/dist/esm/components/FieldSelect/utils/customOption.d.ts +0 -21
- package/dist/esm/components/FieldSelect/utils/customOption.js +0 -48
- package/dist/esm/components/FieldSelect/utils/extract.d.ts +0 -11
- package/dist/esm/components/FieldSelect/utils/extract.js +0 -34
- package/dist/esm/components/FieldSelect/utils/filter.d.ts +0 -3
- package/dist/esm/components/FieldSelect/utils/filter.js +0 -36
- package/dist/esm/components/FieldSelect/utils/index.d.ts +0 -6
- package/dist/esm/components/FieldSelect/utils/index.js +0 -6
- package/dist/esm/components/FieldSelect/utils/items.d.ts +0 -9
- package/dist/esm/components/FieldSelect/utils/items.js +0 -41
- package/dist/esm/components/FieldSelect/utils/selection.d.ts +0 -11
- package/dist/esm/components/FieldSelect/utils/selection.js +0 -14
- package/dist/esm/components/FieldSelect/utils/tagSize.d.ts +0 -4
- package/dist/esm/components/FieldSelect/utils/tagSize.js +0 -2
- package/src/components/FieldSelect/hooks.ts +0 -162
- package/src/components/FieldSelect/utils/customOption.ts +0 -77
- package/src/components/FieldSelect/utils/extract.ts +0 -49
- package/src/components/FieldSelect/utils/filter.ts +0 -51
- package/src/components/FieldSelect/utils/index.ts +0 -6
- package/src/components/FieldSelect/utils/items.ts +0 -58
- package/src/components/FieldSelect/utils/selection.ts +0 -25
- 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-
|
|
3
|
+
"version": "2.2.3-preview-a42433ce.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-
|
|
44
|
+
"@cloud-ru/ds-button": "1.0.2",
|
|
46
45
|
"@cloud-ru/ds-color-picker": "1.0.6",
|
|
46
|
+
"@cloud-ru/ds-calendar": "1.0.15-preview-a42433ce.0",
|
|
47
47
|
"@cloud-ru/ds-divider": "1.0.1",
|
|
48
|
+
"@cloud-ru/ds-dropdown": "1.0.8",
|
|
48
49
|
"@cloud-ru/ds-field-decorator": "1.0.5",
|
|
49
50
|
"@cloud-ru/ds-icons": "1.1.0",
|
|
50
|
-
"@cloud-ru/ds-dropdown": "1.0.8",
|
|
51
|
-
"@cloud-ru/ds-list": "2.1.4-preview-0c4119af.0",
|
|
52
51
|
"@cloud-ru/ds-input-private": "1.0.4",
|
|
53
52
|
"@cloud-ru/ds-loader": "1.0.1",
|
|
54
|
-
"@cloud-ru/ds-
|
|
53
|
+
"@cloud-ru/ds-list": "2.1.4-preview-a42433ce.0",
|
|
55
54
|
"@cloud-ru/ds-scroll": "1.0.2",
|
|
56
|
-
"@cloud-ru/ds-
|
|
57
|
-
"@cloud-ru/ds-slider": "1.0.4",
|
|
55
|
+
"@cloud-ru/ds-materials": "1.0.1",
|
|
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 ведёт сегментный движок (
|
|
528
|
-
//
|
|
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.
|
|
59
|
-
*
|
|
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 ?
|
|
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(
|
|
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) ??
|
|
209
|
-
}, [allItems
|
|
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
|
|
271
|
+
return items;
|
|
290
272
|
}
|
|
291
273
|
|
|
292
|
-
return filterItems(
|
|
293
|
-
}, [searchable, autocomplete, typing,
|
|
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
|
|
312
|
-
(next:
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
301
|
+
onOpenChange?.(next);
|
|
338
302
|
|
|
339
|
-
if (
|
|
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
|
-
[
|
|
311
|
+
[openProp, onOpenChange, disabled, readOnly, resetSearchOnOptionSelection, setInputValue, selectedLabel],
|
|
348
312
|
);
|
|
349
313
|
|
|
350
|
-
const
|
|
351
|
-
|
|
314
|
+
const handleSingleChange = (next: ItemId | undefined) => {
|
|
315
|
+
if (multiple) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
352
318
|
|
|
353
|
-
if (
|
|
354
|
-
|
|
319
|
+
if (props.value === undefined) {
|
|
320
|
+
setSingleLocal(next);
|
|
355
321
|
}
|
|
356
|
-
}, [resetSearchOnOptionSelection, setInputValue, multiple, selectedLabel]);
|
|
357
322
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
|
384
|
-
(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (openProp === undefined) {
|
|
388
|
-
setOpenLocal(next);
|
|
389
|
-
}
|
|
327
|
+
const handleMultipleChange = (next: ItemId[]) => {
|
|
328
|
+
if (!multiple) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
390
331
|
|
|
391
|
-
|
|
332
|
+
if (props.value === undefined) {
|
|
333
|
+
setMultipleLocal(next);
|
|
334
|
+
}
|
|
392
335
|
|
|
393
|
-
|
|
394
|
-
setTyping(false);
|
|
395
|
-
const committed = tryCommitOnClose();
|
|
336
|
+
props.onChange?.(next);
|
|
396
337
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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 (
|
|
477
|
+
if (addOptionByEnter && event.key === 'Enter' && inputValue !== '') {
|
|
478
|
+
// Введённый текст становится новым значением (создание опции «на лету»).
|
|
548
479
|
event.preventDefault();
|
|
549
|
-
|
|
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 = (
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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:
|
|
616
|
-
: { mode: 'single' as const, value: singleValue, onChange:
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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;
|