@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.
- 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 +4 -0
- package/dist/cjs/segments/useSegmentedMask.js +155 -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 +4 -0
- package/dist/esm/segments/useSegmentedMask.js +155 -56
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +6 -6
- 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 +181 -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/README.md
CHANGED
|
@@ -435,9 +435,7 @@ export function Select() {
|
|
|
435
435
|
|
|
436
436
|
| Prop | Type | Default | Description |
|
|
437
437
|
|------|------|---------|-------------|
|
|
438
|
-
| `
|
|
439
|
-
| `addOptionByEnter` | `boolean` | `false` | Зафиксировать введённый текст как новый выбор по `Enter` (создание опции «на лету»). <br/> @deprecated Используйте `allowCustomOption`. `true` эквивалентен `allowCustomOption` с триггером `enter`. |
|
|
440
|
-
| `allowCustomOption` | `boolean` | `false` | Разрешить фиксировать произвольный ввод из строки поиска как выбранную опцию <br/> (значение, которого нет в `items`). |
|
|
438
|
+
| `addOptionByEnter` | `boolean` | `false` | Зафиксировать введённый текст как новый выбор по `Enter` (создание опции «на лету»). |
|
|
441
439
|
| `autoFocus` | `boolean` | — | Автофокус input при монтировании. На mobile выключается адаптивно (см. `layoutPresets`) |
|
|
442
440
|
| `autocomplete` | `boolean` | `false` | Не фильтровать список на клиенте — фильтрацию обеспечивает потребитель (серверный поиск). <br/> Введённый текст уходит в `search.onChange`, список берётся из `items` как есть. |
|
|
443
441
|
| `background` | `boolean` | `true` | Фон поля (acrylic) |
|
|
@@ -327,9 +327,9 @@ exports.FieldDate = (0, react_1.forwardRef)(function FieldDate(props, ref) {
|
|
|
327
327
|
(0, jsx_runtime_1.jsxs)("span", { className: styles_module_scss_2.default.rangeInputs, style: { '--field-date-mask-ch': placeholderMask.length }, children: [(0, jsx_runtime_1.jsx)(input_private_1.InputPrivate, { ...inputCommon, ref: (0, merge_refs_1.default)(ref, fromInputRef), className: (0, classnames_1.default)(styles_module_scss_1.default.fieldInput, styles_module_scss_2.default.rangeInputFrom), value: rangeInputValue[0], placeholder: placeholderMask, onChange: handleRangeInput(0), id: id, name: name, autoFocus: resolvedAutoFocus, "aria-label": labelFrom, "data-test-id": constants_1.TEST_IDS.fieldDateInputFrom }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_2.default.rangeSeparator, "aria-hidden": true, children: "\u2013" }), (0, jsx_runtime_1.jsx)(input_private_1.InputPrivate, { ...inputCommon, ref: toInputRef, className: styles_module_scss_1.default.fieldInput, value: rangeInputValue[1], placeholder: placeholderMask, onChange: handleRangeInput(1),
|
|
328
328
|
// «to» — последний инпут перед кнопками: на нём живёт roving-композит
|
|
329
329
|
// (ArrowRight в конце → clear/copy), keydown-цепочка с nav-handler'ом.
|
|
330
|
-
onKeyDown: handleToInputKeyDown, tabIndex: inputTabIndex, "aria-label": labelTo, "data-test-id": constants_1.TEST_IDS.fieldDateInputTo })] })) : ((0, jsx_runtime_1.jsx)(input_private_1.InputPrivate, { ref: (0, merge_refs_1.default)(ref, fromInputRef), className: styles_module_scss_1.default.fieldInput, value: singleInputValue, placeholder: placeholderMask,
|
|
331
|
-
// Single/date-time ведёт сегментный движок (
|
|
332
|
-
//
|
|
330
|
+
onKeyDown: handleToInputKeyDown, tabIndex: inputTabIndex, "aria-label": labelTo, "data-test-id": constants_1.TEST_IDS.fieldDateInputTo })] })) : ((0, jsx_runtime_1.jsx)(input_private_1.InputPrivate, { ref: (0, merge_refs_1.default)(ref, fromInputRef, singleSeg.nativeInputRef), className: styles_module_scss_1.default.fieldInput, value: singleInputValue, placeholder: placeholderMask,
|
|
331
|
+
// Single/date-time ведёт сегментный движок (useSegmentedMask), нативный onChange
|
|
332
|
+
// игнорируется. Range — обычный ввод-по-маске (formatMask в handleRangeInput).
|
|
333
333
|
onChange: () => { }, onKeyDown: handleSingleKeyDown, onClick: singleSeg.handleClick, onPaste: singleSeg.handlePaste, onFocus: handleSingleFocus, onBlur: handleSingleBlur, disabled: disabled, readonly: readOnly, inputMode: 'numeric', tabIndex: inputTabIndex, id: id, name: name, autoFocus: resolvedAutoFocus, "aria-haspopup": 'dialog', "aria-expanded": open, "data-test-id": constants_1.TEST_IDS.fieldDateInput })) }), postfixButtons && (
|
|
334
334
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
335
335
|
(0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_1.default.postfixButtonsSlot, onMouseDown: shared_1.preventSlotMouseDown, onClick: shared_1.stopSlotClickPropagation, children: postfixButtons }))] }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.iconSlot, "data-test-id": constants_1.TEST_IDS.fieldDateCalendar, "aria-hidden": true, children: (0, jsx_runtime_1.jsx)(system_1.CalendarSVG, {}) })] }) })] }));
|
|
@@ -10,8 +10,9 @@ export declare function getMaskString(mode: MaskMode, showSeconds?: boolean): st
|
|
|
10
10
|
* «десятку» (`day` → > 3, `month` → > 1, `hour` → > 2, `minute`/`second` → > 5), цифра трактуется
|
|
11
11
|
* как «единицы» и в начало сегмента добавляется ноль. Пример: `5` для day → `05.`,
|
|
12
12
|
* фокус автоматически уезжает на следующий сегмент.
|
|
13
|
-
* 2.
|
|
14
|
-
*
|
|
13
|
+
* 2. **Переполнение полной двойки**: если набрано две цифры и значение превышает `max` сегмента —
|
|
14
|
+
* сегмент начинается заново с последней цифры (как посегментный ввод в `useSegmentedMask`).
|
|
15
|
+
* Пример: `39` для day → `09`, `13` для month → `03`.
|
|
15
16
|
*/
|
|
16
17
|
export declare function formatMask(input: string, mode: MaskMode, showSeconds?: boolean): string;
|
|
17
18
|
/** Парсит `DD.MM.YYYY` или `DD.MM.YYYY, HH:mm[:ss]` в `Date`. Возвращает `undefined` при невалидной/неполной строке. */
|
|
@@ -48,8 +48,9 @@ function buildSegments(mode, showSeconds) {
|
|
|
48
48
|
* «десятку» (`day` → > 3, `month` → > 1, `hour` → > 2, `minute`/`second` → > 5), цифра трактуется
|
|
49
49
|
* как «единицы» и в начало сегмента добавляется ноль. Пример: `5` для day → `05.`,
|
|
50
50
|
* фокус автоматически уезжает на следующий сегмент.
|
|
51
|
-
* 2.
|
|
52
|
-
*
|
|
51
|
+
* 2. **Переполнение полной двойки**: если набрано две цифры и значение превышает `max` сегмента —
|
|
52
|
+
* сегмент начинается заново с последней цифры (как посегментный ввод в `useSegmentedMask`).
|
|
53
|
+
* Пример: `39` для day → `09`, `13` для month → `03`.
|
|
53
54
|
*/
|
|
54
55
|
function formatMask(input, mode, showSeconds = true) {
|
|
55
56
|
const maxLen = totalLen(mode, showSeconds);
|
|
@@ -75,7 +76,7 @@ function formatMask(input, mode, showSeconds = true) {
|
|
|
75
76
|
stream = stream.slice(seg.len);
|
|
76
77
|
if (raw.length === seg.len) {
|
|
77
78
|
const n = Number(raw);
|
|
78
|
-
out += n > seg.max ?
|
|
79
|
+
out += n > seg.max ? raw.slice(-1).padStart(seg.len, '0') : raw;
|
|
79
80
|
}
|
|
80
81
|
else {
|
|
81
82
|
out += raw;
|
|
@@ -20,7 +20,6 @@ const constants_1 = require("../../constants");
|
|
|
20
20
|
const hooks_1 = require("../../hooks");
|
|
21
21
|
const shared_1 = require("../shared");
|
|
22
22
|
const styles_module_scss_1 = __importDefault(require('../shared/styles.module.css'));
|
|
23
|
-
const hooks_2 = require("./hooks");
|
|
24
23
|
const styles_module_scss_2 = __importDefault(require('./styles.module.css'));
|
|
25
24
|
const utils_2 = require("./utils");
|
|
26
25
|
exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
@@ -29,8 +28,6 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
29
28
|
const chips = multiple ? (props.chips ?? true) : false;
|
|
30
29
|
const removeByBackspace = multiple ? (props.removeByBackspace ?? true) : false;
|
|
31
30
|
const closeDroplistOnItemClick = props.closeDroplistOnItemClick ?? !multiple;
|
|
32
|
-
const allowCustomOption = props.allowCustomOption === true;
|
|
33
|
-
const addCustomOptionTriggers = allowCustomOption ? props.addCustomOptionTriggers : undefined;
|
|
34
31
|
const resolvedAutoFocus = (0, hooks_1.useAdaptiveAutoFocus)(autoFocus, layoutPresets);
|
|
35
32
|
const inputRef = (0, react_1.useRef)(null);
|
|
36
33
|
// Отдельный ref на реальный <input>: `inputRef.current` перезаписывается span-обёрткой PopoverPrivate.
|
|
@@ -76,20 +73,16 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
76
73
|
}, [multiple, props.value, multipleLocal]);
|
|
77
74
|
const effectiveValidationState = (0, react_1.useMemo)(() => (error ? field_decorator_1.VALIDATION_STATE.Error : validationState), [error, validationState]);
|
|
78
75
|
const allItems = (0, react_1.useMemo)(() => [...(pinTop ?? []), ...items, ...(pinBottom ?? [])], [items, pinTop, pinBottom]);
|
|
79
|
-
const selectedIds = (0, react_1.useMemo)(() => (0, utils_2.getSelectedIds)({ multiple, multipleValue, singleValue }), [multiple, multipleValue, singleValue]);
|
|
80
|
-
// Выбранное, которого нет в `items` (кастом по allowCustomOption, ленивая подгрузка) — в список, не только в чип.
|
|
81
|
-
const missingSelectedItems = (0, react_1.useMemo)(() => (0, utils_2.getMissingSelectedItems)(selectedIds, allItems), [selectedIds, allItems]);
|
|
82
|
-
const itemsWithPlaceholders = (0, react_1.useMemo)(() => (missingSelectedItems.length > 0 ? [...missingSelectedItems, ...items] : items), [missingSelectedItems, items]);
|
|
83
76
|
// Выбранное значение переживает смену `items` (серверный поиск, ленивая подгрузка).
|
|
84
77
|
const seenItems = (0, react_1.useRef)(new Map());
|
|
85
78
|
const resolveItem = (0, react_1.useMemo)(() => {
|
|
86
|
-
for (const item of (0, utils_2.flatten)(
|
|
79
|
+
for (const item of (0, utils_2.flatten)(allItems)) {
|
|
87
80
|
if (item.id !== undefined) {
|
|
88
81
|
seenItems.current.set(item.id, item);
|
|
89
82
|
}
|
|
90
83
|
}
|
|
91
|
-
return (id) => (0, utils_2.findItem)(allItems, id) ??
|
|
92
|
-
}, [allItems
|
|
84
|
+
return (id) => (0, utils_2.findItem)(allItems, id) ?? seenItems.current.get(id);
|
|
85
|
+
}, [allItems]);
|
|
93
86
|
const selectedPairs = (0, react_1.useMemo)(() => {
|
|
94
87
|
if (!multiple || multipleValue.length === 0) {
|
|
95
88
|
return [];
|
|
@@ -144,13 +137,13 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
144
137
|
syncedLabelRef.current = selectedLabel;
|
|
145
138
|
setInputValue(selectedLabel);
|
|
146
139
|
}, [selectedLabel, typing, resetSearchOnOptionSelection, setInputValue]);
|
|
147
|
-
// При autocomplete фильтрует сервер — items берутся как
|
|
140
|
+
// При autocomplete фильтрует сервер — items берутся как есть.
|
|
148
141
|
const filteredItems = (0, react_1.useMemo)(() => {
|
|
149
142
|
if (!searchable || autocomplete || !typing) {
|
|
150
|
-
return
|
|
143
|
+
return items;
|
|
151
144
|
}
|
|
152
|
-
return (0, utils_2.filterItems)(
|
|
153
|
-
}, [searchable, autocomplete, typing,
|
|
145
|
+
return (0, utils_2.filterItems)(items, inputValue, enableFuzzySearch);
|
|
146
|
+
}, [searchable, autocomplete, typing, items, inputValue, enableFuzzySearch]);
|
|
154
147
|
const filteredPinTop = (0, react_1.useMemo)(() => {
|
|
155
148
|
if (!searchable || autocomplete || !typing || !pinTop) {
|
|
156
149
|
return pinTop;
|
|
@@ -163,7 +156,22 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
163
156
|
}
|
|
164
157
|
return (0, utils_2.filterItems)(pinBottom, inputValue, enableFuzzySearch);
|
|
165
158
|
}, [searchable, autocomplete, typing, pinBottom, inputValue, enableFuzzySearch]);
|
|
166
|
-
const
|
|
159
|
+
const handleOpenChange = (0, react_1.useCallback)((next) => {
|
|
160
|
+
// readonly/disabled поле открывать нельзя, закрывать — можно.
|
|
161
|
+
if (next && (disabled || readOnly))
|
|
162
|
+
return;
|
|
163
|
+
if (openProp === undefined) {
|
|
164
|
+
setOpenLocal(next);
|
|
165
|
+
}
|
|
166
|
+
onOpenChange?.(next);
|
|
167
|
+
if (!next) {
|
|
168
|
+
setTyping(false);
|
|
169
|
+
if (resetSearchOnOptionSelection) {
|
|
170
|
+
setInputValue(selectedLabel);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}, [openProp, onOpenChange, disabled, readOnly, resetSearchOnOptionSelection, setInputValue, selectedLabel]);
|
|
174
|
+
const handleSingleChange = (next) => {
|
|
167
175
|
if (multiple) {
|
|
168
176
|
return;
|
|
169
177
|
}
|
|
@@ -172,8 +180,8 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
172
180
|
}
|
|
173
181
|
props.onChange?.(next);
|
|
174
182
|
setTyping(false);
|
|
175
|
-
}
|
|
176
|
-
const handleMultipleChange = (
|
|
183
|
+
};
|
|
184
|
+
const handleMultipleChange = (next) => {
|
|
177
185
|
if (!multiple) {
|
|
178
186
|
return;
|
|
179
187
|
}
|
|
@@ -187,56 +195,8 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
187
195
|
setInputValue('');
|
|
188
196
|
}
|
|
189
197
|
}
|
|
190
|
-
}
|
|
191
|
-
const resetCustomOptionSearch = (0, react_1.useCallback)(() => {
|
|
192
|
-
setTyping(false);
|
|
193
|
-
if (resetSearchOnOptionSelection) {
|
|
194
|
-
setInputValue(multiple ? '' : selectedLabel);
|
|
195
|
-
}
|
|
196
|
-
}, [resetSearchOnOptionSelection, setInputValue, multiple, selectedLabel]);
|
|
197
|
-
const { listRef, suppressCloseCommit, enableCloseCommit, tryCommitOnClose, tryCommitOnBlur, tryCommitFromKeyboard, handleListSingleChange, handleListMultipleChange, } = (0, hooks_2.useCustomOption)({
|
|
198
|
-
allowCustomOption,
|
|
199
|
-
addCustomOptionTriggers,
|
|
200
|
-
addOptionByEnter,
|
|
201
|
-
multiple,
|
|
202
|
-
inputValue,
|
|
203
|
-
multipleValue,
|
|
204
|
-
commitSingle: handleSingleChange,
|
|
205
|
-
commitMultiple: handleMultipleChange,
|
|
206
|
-
resetSearch: resetCustomOptionSearch,
|
|
207
|
-
inputElementRef,
|
|
208
|
-
clearButtonRef,
|
|
209
|
-
copyButtonRef,
|
|
210
|
-
footerActiveElementsRefs,
|
|
211
|
-
});
|
|
212
|
-
const handleOpenChange = (0, react_1.useCallback)((next) => {
|
|
213
|
-
// readonly/disabled поле открывать нельзя, закрывать — можно.
|
|
214
|
-
if (next && (disabled || readOnly))
|
|
215
|
-
return;
|
|
216
|
-
if (openProp === undefined) {
|
|
217
|
-
setOpenLocal(next);
|
|
218
|
-
}
|
|
219
|
-
onOpenChange?.(next);
|
|
220
|
-
if (!next) {
|
|
221
|
-
setTyping(false);
|
|
222
|
-
const committed = tryCommitOnClose();
|
|
223
|
-
// Кастомная опция не зафиксирована — вернуть лейбл выбранного значения.
|
|
224
|
-
if (resetSearchOnOptionSelection && !committed) {
|
|
225
|
-
setInputValue(selectedLabel);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}, [
|
|
229
|
-
openProp,
|
|
230
|
-
onOpenChange,
|
|
231
|
-
disabled,
|
|
232
|
-
readOnly,
|
|
233
|
-
resetSearchOnOptionSelection,
|
|
234
|
-
setInputValue,
|
|
235
|
-
selectedLabel,
|
|
236
|
-
tryCommitOnClose,
|
|
237
|
-
]);
|
|
198
|
+
};
|
|
238
199
|
const handleInputChange = (next) => {
|
|
239
|
-
enableCloseCommit();
|
|
240
200
|
setInputValue(next);
|
|
241
201
|
setTyping(true);
|
|
242
202
|
if (!open) {
|
|
@@ -335,12 +295,19 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
335
295
|
}
|
|
336
296
|
if (event.key === 'Escape' && open) {
|
|
337
297
|
event.preventDefault();
|
|
338
|
-
suppressCloseCommit();
|
|
339
298
|
handleOpenChange(false);
|
|
340
299
|
}
|
|
341
|
-
if (
|
|
300
|
+
if (addOptionByEnter && event.key === 'Enter' && inputValue !== '') {
|
|
301
|
+
// Введённый текст становится новым значением (создание опции «на лету»).
|
|
342
302
|
event.preventDefault();
|
|
343
|
-
|
|
303
|
+
if (multiple) {
|
|
304
|
+
if (!multipleValue.includes(inputValue)) {
|
|
305
|
+
handleMultipleChange([...multipleValue, inputValue]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
handleSingleChange(inputValue);
|
|
310
|
+
}
|
|
344
311
|
return;
|
|
345
312
|
}
|
|
346
313
|
if (event.key === 'Enter' && !open) {
|
|
@@ -377,20 +344,13 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
377
344
|
}
|
|
378
345
|
onFocusProp?.(event);
|
|
379
346
|
}, [searchable, onFocusProp]);
|
|
380
|
-
const handleInputBlur = (event) => {
|
|
347
|
+
const handleInputBlur = (0, react_1.useCallback)((event) => {
|
|
381
348
|
setFocusVisible(false);
|
|
382
|
-
const blurResult = tryCommitOnBlur(event);
|
|
383
|
-
// Закрытый список не получит onOpenChange — вернуть лейбл выбранного значения.
|
|
384
|
-
// Клик по айтему открытого списка даёт relatedTarget === null, восстановление делает закрытие.
|
|
385
|
-
if (resetSearchOnOptionSelection && blurResult === 'skipped' && (!open || event.relatedTarget !== null)) {
|
|
386
|
-
setTyping(false);
|
|
387
|
-
setInputValue(selectedLabel);
|
|
388
|
-
}
|
|
389
349
|
onBlurProp?.(event);
|
|
390
|
-
};
|
|
350
|
+
}, [onBlurProp]);
|
|
391
351
|
const droplistSelection = multiple
|
|
392
|
-
? { mode: 'multiple', value: multipleValue, onChange:
|
|
393
|
-
: { mode: 'single', value: singleValue, onChange:
|
|
352
|
+
? { mode: 'multiple', value: multipleValue, onChange: handleMultipleChange }
|
|
353
|
+
: { mode: 'single', value: singleValue, onChange: handleSingleChange };
|
|
394
354
|
const hasChips = chips && selectedPairs.length > 0;
|
|
395
355
|
// minWidth поискового input'а = ширина введённого текста (`.inputPlug`), но не больше строки чипов.
|
|
396
356
|
// Замер до paint, иначе input мигает при вводе.
|
|
@@ -417,5 +377,5 @@ exports.FieldSelect = (0, react_1.forwardRef)(function FieldSelect(props, ref) {
|
|
|
417
377
|
}), children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.acrylicBg, "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.borderStateLayer, "data-state": 'borderOnBackground' }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.focusLayer })] }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.fieldContainer, children: (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.contentWrapper, children: [iconBefore && (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.iconSlot, children: iconBefore }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.inputLine, children: [prefix && (0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_1.default.prefix, children: prefix }), hasChips ? ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_2.default.chipsRow, ref: contentRef, "data-test-id": constants_1.TEST_IDS.fieldSelectChips, children: [selectedPairs.map(pair => ((0, jsx_runtime_1.jsx)(tag_1.Tag, { label: formatPair(pair), size: utils_2.TAG_SIZE_MAP[size], appearance: pair.appearance ?? 'neutral', onDelete: disabled || readOnly || pair.disabled ? undefined : handleRemoveChip(pair.id) }, String(pair.id)))), searchInput, (0, jsx_runtime_1.jsx)("span", { ref: inputPlugRef, className: styles_module_scss_2.default.inputPlug, "aria-hidden": true, children: inputValue })] })) : (searchInput), postfixButtons && (
|
|
418
378
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
419
379
|
(0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_1.default.postfixButtonsSlot, onMouseDown: shared_1.preventSlotMouseDown, onClick: shared_1.stopSlotClickPropagation, children: postfixButtons })), postfix && (0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_1.default.postfix, children: postfix })] }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.iconSlot, children: (0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_2.default.chevron, "data-open": open || undefined, "aria-hidden": true, children: (0, jsx_runtime_1.jsx)(system_1.ChevronDownSVG, {}) }) })] }) })] }));
|
|
420
|
-
return ((0, jsx_runtime_1.jsx)(field_decorator_1.FieldDecorator, { className: className, label: label, labelTooltip: labelTooltip, caption: caption, hint: hint, error: error, size: size, validationState: validationState, showHintIcon: showHintIcon, length: length, required: required, labelFor: id, disabled: disabled, readonly: readOnly, children: (0, jsx_runtime_1.jsx)(list_1.Droplist, { items: filteredItems, label: label, pinTop: filteredPinTop, pinBottom: filteredPinBottom, trigger: 'click', placement: placement, widthStrategy: widthStrategy, triggerElemRef: inputRef,
|
|
380
|
+
return ((0, jsx_runtime_1.jsx)(field_decorator_1.FieldDecorator, { className: className, label: label, labelTooltip: labelTooltip, caption: caption, hint: hint, error: error, size: size, validationState: validationState, showHintIcon: showHintIcon, length: length, required: required, labelFor: id, disabled: disabled, readonly: readOnly, children: (0, jsx_runtime_1.jsx)(list_1.Droplist, { items: filteredItems, label: label, pinTop: filteredPinTop, pinBottom: filteredPinBottom, trigger: 'click', placement: placement, widthStrategy: widthStrategy, triggerElemRef: inputRef, size: size, open: open, onOpenChange: handleOpenChange, selection: droplistSelection, closeDroplistOnItemClick: closeDroplistOnItemClick, footer: footer, footerActiveElementsRefs: footerActiveElementsRefs, loading: loading, noDataState: noDataState, noResultsState: noResultsState, errorDataState: errorDataState, dataError: dataError, dataFiltered: dataFiltered ?? (searchable && typing && inputValue !== ''), virtualized: virtualized, scroll: true, scrollToSelectedItem: scrollToSelectedItem, limitedScrollHeight: limitedScrollHeight, untouchableScrollbars: untouchableScrollbars, closeOnPopstate: closeOnPopstate, children: trigger }) }));
|
|
421
381
|
});
|
|
@@ -2,14 +2,3 @@ export declare const SELECTION_MODE: {
|
|
|
2
2
|
readonly Single: "single";
|
|
3
3
|
readonly Multiple: "multiple";
|
|
4
4
|
};
|
|
5
|
-
/** События, по которым произвольный ввод фиксируется как выбранная опция. */
|
|
6
|
-
export declare const ADD_CUSTOM_OPTION_TRIGGER: {
|
|
7
|
-
readonly Enter: "enter";
|
|
8
|
-
readonly Blur: "blur";
|
|
9
|
-
readonly Space: "space";
|
|
10
|
-
readonly Comma: "comma";
|
|
11
|
-
};
|
|
12
|
-
/** Триггеры по умолчанию для `selection='single'` при `allowCustomOption`. */
|
|
13
|
-
export declare const ADD_CUSTOM_OPTION_TRIGGERS_SINGLE: readonly ["enter", "blur"];
|
|
14
|
-
/** Триггеры по умолчанию для `selection='multiple'` при `allowCustomOption`. */
|
|
15
|
-
export declare const ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE: readonly ["enter", "blur", "space", "comma"];
|
|
@@ -1,26 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.SELECTION_MODE = void 0;
|
|
4
4
|
exports.SELECTION_MODE = {
|
|
5
5
|
Single: 'single',
|
|
6
6
|
Multiple: 'multiple',
|
|
7
7
|
};
|
|
8
|
-
/** События, по которым произвольный ввод фиксируется как выбранная опция. */
|
|
9
|
-
exports.ADD_CUSTOM_OPTION_TRIGGER = {
|
|
10
|
-
Enter: 'enter',
|
|
11
|
-
Blur: 'blur',
|
|
12
|
-
Space: 'space',
|
|
13
|
-
Comma: 'comma',
|
|
14
|
-
};
|
|
15
|
-
/** Триггеры по умолчанию для `selection='single'` при `allowCustomOption`. */
|
|
16
|
-
exports.ADD_CUSTOM_OPTION_TRIGGERS_SINGLE = [
|
|
17
|
-
exports.ADD_CUSTOM_OPTION_TRIGGER.Enter,
|
|
18
|
-
exports.ADD_CUSTOM_OPTION_TRIGGER.Blur,
|
|
19
|
-
];
|
|
20
|
-
/** Триггеры по умолчанию для `selection='multiple'` при `allowCustomOption`. */
|
|
21
|
-
exports.ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE = [
|
|
22
|
-
exports.ADD_CUSTOM_OPTION_TRIGGER.Enter,
|
|
23
|
-
exports.ADD_CUSTOM_OPTION_TRIGGER.Blur,
|
|
24
|
-
exports.ADD_CUSTOM_OPTION_TRIGGER.Space,
|
|
25
|
-
exports.ADD_CUSTOM_OPTION_TRIGGER.Comma,
|
|
26
|
-
];
|
|
@@ -4,30 +4,9 @@ import { Appearance as TagAppearance } from '@cloud-ru/ds-tag';
|
|
|
4
4
|
import { ValueOf } from '@cloud-ru/ds-utils';
|
|
5
5
|
import { FocusEvent, KeyboardEvent, ReactNode } from 'react';
|
|
6
6
|
import { FieldLayoutPresets } from '../../hooks';
|
|
7
|
-
import {
|
|
7
|
+
import { SELECTION_MODE } from './constants';
|
|
8
8
|
/** Режим выбора FieldSelect: одно значение или несколько. */
|
|
9
9
|
export type Selection = ValueOf<typeof SELECTION_MODE>;
|
|
10
|
-
/** Событие, по которому произвольный ввод фиксируется как выбранная опция. */
|
|
11
|
-
export type FieldSelectAddCustomOptionTrigger = ValueOf<typeof ADD_CUSTOM_OPTION_TRIGGER>;
|
|
12
|
-
/** Триггеры кастомной опции в режиме `single`. */
|
|
13
|
-
export type FieldSelectSingleAddCustomOptionTrigger = (typeof ADD_CUSTOM_OPTION_TRIGGERS_SINGLE)[number];
|
|
14
|
-
/** Триггеры кастомной опции в режиме `multiple`. */
|
|
15
|
-
export type FieldSelectMultipleAddCustomOptionTrigger = (typeof ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE)[number];
|
|
16
|
-
type CustomOptionProps<TTrigger extends FieldSelectAddCustomOptionTrigger> = {
|
|
17
|
-
/**
|
|
18
|
-
* Разрешить фиксировать произвольный ввод из строки поиска как выбранную опцию
|
|
19
|
-
* (значение, которого нет в `items`).
|
|
20
|
-
* @default false
|
|
21
|
-
*/
|
|
22
|
-
allowCustomOption?: boolean;
|
|
23
|
-
/**
|
|
24
|
-
* События, по которым произвольный ввод фиксируется как выбранная опция.
|
|
25
|
-
* Учитывается только при `allowCustomOption={true}`.
|
|
26
|
-
* Если не задан — используются все триггеры режима (`single`: enter, blur; `multiple`: enter, blur, space, comma).
|
|
27
|
-
* Триггеры, недоступные в текущем `selection`, игнорируются.
|
|
28
|
-
*/
|
|
29
|
-
addCustomOptionTriggers?: TTrigger[];
|
|
30
|
-
};
|
|
31
10
|
type WithTagAppearance<T> = T extends {
|
|
32
11
|
items: ItemProps[];
|
|
33
12
|
} ? Omit<T, 'items'> & {
|
|
@@ -119,8 +98,6 @@ type CommonSelectProps = FieldSelectDecoratorProps & DroplistPassthrough & {
|
|
|
119
98
|
autocomplete?: boolean;
|
|
120
99
|
/**
|
|
121
100
|
* Зафиксировать введённый текст как новый выбор по `Enter` (создание опции «на лету»).
|
|
122
|
-
*
|
|
123
|
-
* @deprecated Используйте `allowCustomOption`. `true` эквивалентен `allowCustomOption` с триггером `enter`.
|
|
124
101
|
* @default false
|
|
125
102
|
*/
|
|
126
103
|
addOptionByEnter?: boolean;
|
|
@@ -177,7 +154,7 @@ type CommonSelectProps = FieldSelectDecoratorProps & DroplistPassthrough & {
|
|
|
177
154
|
/** Тестовый id корня */
|
|
178
155
|
'data-test-id'?: string;
|
|
179
156
|
};
|
|
180
|
-
export type FieldSelectSingleProps = CommonSelectProps &
|
|
157
|
+
export type FieldSelectSingleProps = CommonSelectProps & {
|
|
181
158
|
/** Режим выбора. По умолчанию `'single'`. */
|
|
182
159
|
selection?: typeof SELECTION_MODE.Single;
|
|
183
160
|
/** Управляемое значение. Пустая строка трактуется как «значение не выбрано». */
|
|
@@ -192,7 +169,7 @@ export type FieldSelectSingleProps = CommonSelectProps & CustomOptionProps<Field
|
|
|
192
169
|
*/
|
|
193
170
|
closeDroplistOnItemClick?: boolean;
|
|
194
171
|
};
|
|
195
|
-
export type FieldSelectMultipleProps = CommonSelectProps &
|
|
172
|
+
export type FieldSelectMultipleProps = CommonSelectProps & {
|
|
196
173
|
/** Режим выбора */
|
|
197
174
|
selection: typeof SELECTION_MODE.Multiple;
|
|
198
175
|
/** Управляемые значения */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Size } from '@cloud-ru/ds-field-decorator';
|
|
2
|
+
import { ItemId } from '@cloud-ru/ds-list';
|
|
3
|
+
import { Appearance as TagAppearance, Size as TagSize } from '@cloud-ru/ds-tag';
|
|
4
|
+
import { FieldSelectItem, FieldSelectMultipleProps, FieldSelectProps } from './types';
|
|
5
|
+
export declare const TAG_SIZE_MAP: Record<Size, TagSize>;
|
|
6
|
+
export type WithIdContent = {
|
|
7
|
+
id?: ItemId;
|
|
8
|
+
content?: unknown;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
appearance?: TagAppearance;
|
|
11
|
+
};
|
|
12
|
+
export declare function extractLabel(item: WithIdContent): string;
|
|
13
|
+
export declare function extractSearchText(item: WithIdContent): string;
|
|
14
|
+
export declare function extractAppearance(item?: WithIdContent): TagAppearance | undefined;
|
|
15
|
+
export declare function flatten(items: FieldSelectItem[]): WithIdContent[];
|
|
16
|
+
export declare function findItem(items: FieldSelectItem[], id: ItemId): WithIdContent | undefined;
|
|
17
|
+
export declare function isFuzzyMatch(haystack: string, needle: string): boolean;
|
|
18
|
+
export declare function filterItems(items: FieldSelectItem[], query: string, fuzzy: boolean): FieldSelectItem[];
|
|
19
|
+
export declare function isMultiple(props: FieldSelectProps): props is FieldSelectMultipleProps;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TAG_SIZE_MAP = void 0;
|
|
4
|
+
exports.extractLabel = extractLabel;
|
|
5
|
+
exports.extractSearchText = extractSearchText;
|
|
6
|
+
exports.extractAppearance = extractAppearance;
|
|
7
|
+
exports.flatten = flatten;
|
|
8
|
+
exports.findItem = findItem;
|
|
9
|
+
exports.isFuzzyMatch = isFuzzyMatch;
|
|
10
|
+
exports.filterItems = filterItems;
|
|
11
|
+
exports.isMultiple = isMultiple;
|
|
12
|
+
const constants_1 = require("./constants");
|
|
13
|
+
exports.TAG_SIZE_MAP = { s: 'xs', m: 'xs', l: 's' };
|
|
14
|
+
function extractLabel(item) {
|
|
15
|
+
const { content, id } = item;
|
|
16
|
+
if (!content) {
|
|
17
|
+
return String(id ?? '');
|
|
18
|
+
}
|
|
19
|
+
if (typeof content === 'string' || typeof content === 'number') {
|
|
20
|
+
return String(content);
|
|
21
|
+
}
|
|
22
|
+
if (typeof content === 'object' && content !== null && 'label' in content) {
|
|
23
|
+
return String(content.label);
|
|
24
|
+
}
|
|
25
|
+
return String(id ?? '');
|
|
26
|
+
}
|
|
27
|
+
// Текст для поиска: лейбл (option) + caption + description — паритет с легаси `useSearch`,
|
|
28
|
+
// который матчил запрос по всем трём полям контента, а не только по лейблу.
|
|
29
|
+
function extractSearchText(item) {
|
|
30
|
+
const parts = [extractLabel(item)];
|
|
31
|
+
const { content } = item;
|
|
32
|
+
if (content && typeof content === 'object') {
|
|
33
|
+
const c = content;
|
|
34
|
+
if (typeof c.caption === 'string') {
|
|
35
|
+
parts.push(c.caption);
|
|
36
|
+
}
|
|
37
|
+
if (typeof c.description === 'string') {
|
|
38
|
+
parts.push(c.description);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return parts.join(' ');
|
|
42
|
+
}
|
|
43
|
+
// Цвет тега выбранного значения (multiple) — паритет с легаси `option.appearance`.
|
|
44
|
+
// Проверка типа нужна и при типизированном поле: items часто приходят из ответа бэкенда.
|
|
45
|
+
function extractAppearance(item) {
|
|
46
|
+
return typeof item?.appearance === 'string' ? item.appearance : undefined;
|
|
47
|
+
}
|
|
48
|
+
function flatten(items) {
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
out.push(item);
|
|
52
|
+
if ('items' in item && Array.isArray(item.items)) {
|
|
53
|
+
out.push(...flatten(item.items));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function findItem(items, id) {
|
|
59
|
+
for (const item of flatten(items)) {
|
|
60
|
+
if (item.id === id) {
|
|
61
|
+
return item;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
// Subsequence-fuzzy: символы запроса должны встречаться в строке в исходном порядке.
|
|
67
|
+
// Пример: query=`lge` matches `Large` (L → r → arg → e).
|
|
68
|
+
function isFuzzyMatch(haystack, needle) {
|
|
69
|
+
if (!needle) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
let i = 0;
|
|
73
|
+
for (const ch of haystack) {
|
|
74
|
+
if (ch === needle[i]) {
|
|
75
|
+
i += 1;
|
|
76
|
+
if (i === needle.length) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function filterItems(items, query, fuzzy) {
|
|
84
|
+
if (!query) {
|
|
85
|
+
return items;
|
|
86
|
+
}
|
|
87
|
+
const q = query.toLowerCase();
|
|
88
|
+
const matches = (item) => {
|
|
89
|
+
const text = extractSearchText(item).toLowerCase();
|
|
90
|
+
return fuzzy ? isFuzzyMatch(text, q) : text.includes(q);
|
|
91
|
+
};
|
|
92
|
+
const walk = (list) => list.flatMap(item => {
|
|
93
|
+
if ('items' in item && Array.isArray(item.items)) {
|
|
94
|
+
const filteredChildren = walk(item.items);
|
|
95
|
+
return filteredChildren.length > 0 ? [{ ...item, items: filteredChildren }] : [];
|
|
96
|
+
}
|
|
97
|
+
return matches(item) ? [item] : [];
|
|
98
|
+
});
|
|
99
|
+
return walk(items);
|
|
100
|
+
}
|
|
101
|
+
function isMultiple(props) {
|
|
102
|
+
return props.selection === constants_1.SELECTION_MODE.Multiple;
|
|
103
|
+
}
|
|
@@ -137,7 +137,7 @@ exports.FieldTime = (0, react_1.forwardRef)(function FieldTime({ label = '', lab
|
|
|
137
137
|
// clear/copy недостижимыми со стрелок — движок всегда делает preventDefault на ArrowRight.
|
|
138
138
|
onInputKeyDown(event);
|
|
139
139
|
}, [onInputKeyDown, timeSeg]);
|
|
140
|
-
// value ведёт сегментный движок (
|
|
140
|
+
// value ведёт сегментный движок (useSegmentedMask); нативный onChange input'а игнорируем.
|
|
141
141
|
const handleInputChange = (0, react_1.useCallback)(() => undefined, []);
|
|
142
142
|
const handleInputFocus = (0, react_1.useCallback)((event) => {
|
|
143
143
|
setFocusVisible(event.target.matches(':focus-visible'));
|
|
@@ -155,7 +155,7 @@ exports.FieldTime = (0, react_1.forwardRef)(function FieldTime({ label = '', lab
|
|
|
155
155
|
readonly: readOnly,
|
|
156
156
|
hover,
|
|
157
157
|
focusVisible: focusVisible || open,
|
|
158
|
-
}), children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.acrylicBg, "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.borderStateLayer, "data-state": 'borderOnBackground' }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.focusLayer })] }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.fieldContainer, children: (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.contentWrapper, children: [(0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.inputLine, children: (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.inputArea, children: [(0, jsx_runtime_1.jsx)(input_private_1.InputPrivate, { ref: (0, merge_refs_1.default)(ref, localRef), className: styles_module_scss_1.default.fieldInput, value: inputValue, onChange: handleInputChange, disabled: disabled, readonly: readOnly, placeholder: placeholderMask, inputMode: input_private_1.INPUT_MODE.Numeric, tabIndex: inputTabIndex, onClick: timeSeg.handleClick, onPaste: timeSeg.handlePaste, onFocus: handleInputFocus, onBlur: handleInputBlur, onKeyDown: handleInputKeyDown, id: id, name: name, autoFocus: resolvedAutoFocus, "aria-haspopup": 'dialog', "aria-expanded": open, ...(0, utils_1.extractSupportProps)(rest), "data-test-id": constants_1.TEST_IDS.fieldTimeInput }), postfixButtons && (0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_1.default.postfixButtonsSlot, children: postfixButtons })] }) }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.iconSlot, "data-test-id": constants_1.TEST_IDS.fieldTimeIcon, "aria-hidden": true, children: (0, jsx_runtime_1.jsx)(system_1.WatchSVG, {}) })] }) })] }));
|
|
158
|
+
}), children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.acrylicBg, "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.borderStateLayer, "data-state": 'borderOnBackground' }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.focusLayer })] }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.fieldContainer, children: (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.contentWrapper, children: [(0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.inputLine, children: (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.inputArea, children: [(0, jsx_runtime_1.jsx)(input_private_1.InputPrivate, { ref: (0, merge_refs_1.default)(ref, localRef, timeSeg.nativeInputRef), className: styles_module_scss_1.default.fieldInput, value: inputValue, onChange: handleInputChange, disabled: disabled, readonly: readOnly, placeholder: placeholderMask, inputMode: input_private_1.INPUT_MODE.Numeric, tabIndex: inputTabIndex, onClick: timeSeg.handleClick, onPaste: timeSeg.handlePaste, onFocus: handleInputFocus, onBlur: handleInputBlur, onKeyDown: handleInputKeyDown, id: id, name: name, autoFocus: resolvedAutoFocus, "aria-haspopup": 'dialog', "aria-expanded": open, ...(0, utils_1.extractSupportProps)(rest), "data-test-id": constants_1.TEST_IDS.fieldTimeInput }), postfixButtons && (0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_1.default.postfixButtonsSlot, children: postfixButtons })] }) }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.iconSlot, "data-test-id": constants_1.TEST_IDS.fieldTimeIcon, "aria-hidden": true, children: (0, jsx_runtime_1.jsx)(system_1.WatchSVG, {}) })] }) })] }));
|
|
159
159
|
return ((0, jsx_runtime_1.jsx)(field_decorator_1.FieldDecorator, { className: className, label: label, labelTooltip: labelTooltip, caption: caption, hint: hint, error: error, validationState: validationState, showHintIcon: showHintIcon, length: length, required: required, labelFor: id, disabled: disabled, readonly: readOnly, size: size, children: (0, jsx_runtime_1.jsx)(calendar_1.TimePickerDropdown
|
|
160
160
|
// Явный data-test-id на дропдауне → детерминированный id портального контента:
|
|
161
161
|
// `getTestIdBuilder` строит `content-${id}`, так контент адресуем для open-picker-снимка
|
|
@@ -29,6 +29,9 @@ type UseSegmentedMaskParams = {
|
|
|
29
29
|
* Сегментный ввод даты/времени (порт `useDateField` из @snack-uikit/fields). Каретка ходит по
|
|
30
30
|
* сегментам (`setSelectionRange` подсвечивает текущий), цифры заполняют сегмент с авто-переходом,
|
|
31
31
|
* стрелки двигают по сегментам, Backspace очищает сегмент до плейсхолдера.
|
|
32
|
+
*
|
|
33
|
+
* Навигация идёт через `handleKeyDown`, правка значения — через нативные `beforeinput`/`input`:
|
|
34
|
+
* `nativeInputRef` нужно передать в `ref` того же input'а.
|
|
32
35
|
*/
|
|
33
36
|
export declare function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue, onMaskedChange, onArrowDown, onArrowUp, onEscape, onEdit, }: UseSegmentedMaskParams): {
|
|
34
37
|
handleKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
@@ -36,6 +39,7 @@ export declare function useSegmentedMask({ inputRef, mask, slots, readonly, disa
|
|
|
36
39
|
handleFocus: () => void;
|
|
37
40
|
handleBlur: () => void;
|
|
38
41
|
handlePaste: (event: ClipboardEvent<HTMLInputElement>) => void;
|
|
42
|
+
nativeInputRef: (node: HTMLInputElement | null) => void;
|
|
39
43
|
selectSlot: (slot: SlotMeta) => void;
|
|
40
44
|
firstSlot: SlotMeta;
|
|
41
45
|
lastSlot: SlotMeta;
|