@cloud-ru/ds-fields 2.2.3-preview-a0540d7f.0 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- 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 +43 -98
- 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 +145 -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 +45 -100
- 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 +145 -56
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +20 -20
- package/src/components/FieldDate/FieldDate.tsx +3 -3
- package/src/components/FieldDate/mask.ts +4 -3
- package/src/components/FieldSelect/FieldSelect.tsx +63 -151
- 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 +171 -52
- package/dist/cjs/components/FieldSelect/hooks.d.ts +0 -33
- package/dist/cjs/components/FieldSelect/hooks.js +0 -95
- 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 -33
- package/dist/esm/components/FieldSelect/hooks.js +0 -92
- 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 -177
- 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
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useSegmentedMask = useSegmentedMask;
|
|
4
|
+
const utils_1 = require("@cloud-ru/ds-utils");
|
|
4
5
|
const react_1 = require("react");
|
|
6
|
+
const RESET_TYPED_KEYS = new Set([
|
|
7
|
+
'ArrowLeft',
|
|
8
|
+
'ArrowRight',
|
|
9
|
+
'ArrowUp',
|
|
10
|
+
'ArrowDown',
|
|
11
|
+
'Backspace',
|
|
12
|
+
'Delete',
|
|
13
|
+
'Enter',
|
|
14
|
+
'Tab',
|
|
15
|
+
'Escape',
|
|
16
|
+
]);
|
|
5
17
|
/**
|
|
6
18
|
* Сегментный ввод даты/времени (порт `useDateField` из @snack-uikit/fields). Каретка ходит по
|
|
7
19
|
* сегментам (`setSelectionRange` подсвечивает текущий), цифры заполняют сегмент с авто-переходом,
|
|
8
20
|
* стрелки двигают по сегментам, Backspace очищает сегмент до плейсхолдера.
|
|
21
|
+
* `nativeInputRef` передаётся в `ref` того же input'а: значение правится через нативные `beforeinput`/`input`.
|
|
9
22
|
*/
|
|
10
23
|
function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue, onMaskedChange, onArrowDown, onArrowUp, onEscape, onEdit, }) {
|
|
11
24
|
const firstSlot = slots[0];
|
|
@@ -110,70 +123,60 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
|
|
|
110
123
|
}
|
|
111
124
|
selectSlot(nextSlot(slot));
|
|
112
125
|
}, [clearSlot, isValidDate, lastSlot, nextSlot, selectSlot, slots, tryToComplete]);
|
|
126
|
+
// Цифры сегмента храним отдельно от текста: по `01` в поле не отличить `1` от `0` + `1`.
|
|
127
|
+
const typedRef = (0, react_1.useRef)(null);
|
|
128
|
+
const resetTyped = (0, react_1.useCallback)(() => {
|
|
129
|
+
typedRef.current = null;
|
|
130
|
+
}, []);
|
|
113
131
|
const typeDigit = (0, react_1.useCallback)((slot, key) => {
|
|
114
|
-
const text = getSlotText(slot);
|
|
115
|
-
const numberValue = Number(text) || 0;
|
|
116
|
-
const digit = Number(key);
|
|
117
|
-
const slotValue = parseInt(numberValue.toString() + key, 10) || 0;
|
|
118
|
-
const valueLength = slotValue.toString().length;
|
|
119
132
|
const maxLength = slot.max.toString().length;
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (digit * 10 > slot.max) {
|
|
133
|
-
updateSlot(slot, key);
|
|
134
|
-
checkInputAndGoNext(slot);
|
|
135
|
-
}
|
|
136
|
-
else if (digit || digit >= slot.min) {
|
|
137
|
-
updateSlot(slot, key);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
else if (slotValue <= slot.max) {
|
|
141
|
-
updateSlot(slot, slotValue);
|
|
142
|
-
checkInputAndGoNext(slot);
|
|
133
|
+
const prev = typedRef.current?.key === slot.key ? typedRef.current.digits : '';
|
|
134
|
+
const appended = prev + key;
|
|
135
|
+
const overflow = Number(appended) > slot.max;
|
|
136
|
+
const digits = overflow ? key : appended;
|
|
137
|
+
const value = Number(digits);
|
|
138
|
+
const complete = overflow || digits.length >= maxLength || value * 10 > slot.max;
|
|
139
|
+
const accepted = value >= slot.min;
|
|
140
|
+
if (accepted)
|
|
141
|
+
updateSlot(slot, value);
|
|
142
|
+
if (!complete) {
|
|
143
|
+
typedRef.current = { key: slot.key, digits };
|
|
144
|
+
return;
|
|
143
145
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
typedRef.current = null;
|
|
147
|
+
if (accepted)
|
|
146
148
|
checkInputAndGoNext(slot);
|
|
147
|
-
|
|
148
|
-
else if (digit || digit >= slot.min) {
|
|
149
|
-
updateSlot(slot, key);
|
|
150
|
-
}
|
|
151
|
-
}, [checkInputAndGoNext, getSlotText, updateSlot]);
|
|
149
|
+
}, [checkInputAndGoNext, updateSlot]);
|
|
152
150
|
const handleFocus = (0, react_1.useCallback)(() => {
|
|
153
151
|
if (readonly || disabled)
|
|
154
152
|
return;
|
|
153
|
+
resetTyped();
|
|
155
154
|
ensureMask();
|
|
156
155
|
// На фокус всегда выбираем ПЕРВЫЙ сегмент (паритет с легаси). При клике мышью `handleClick`
|
|
157
156
|
// отработает следом и перевыберет сегмент под кареткой; при Tab остаётся первый.
|
|
158
157
|
selectSlot(firstSlot);
|
|
159
|
-
}, [disabled, ensureMask, firstSlot, readonly, selectSlot]);
|
|
158
|
+
}, [disabled, ensureMask, firstSlot, readonly, resetTyped, selectSlot]);
|
|
160
159
|
const handleClick = (0, react_1.useCallback)((event) => {
|
|
161
160
|
if (readonly || disabled)
|
|
162
161
|
return;
|
|
162
|
+
resetTyped();
|
|
163
163
|
ensureMask();
|
|
164
164
|
selectSlot(slotFromIndex(event.currentTarget.selectionStart));
|
|
165
|
-
}, [disabled, ensureMask, readonly, selectSlot, slotFromIndex]);
|
|
165
|
+
}, [disabled, ensureMask, readonly, resetTyped, selectSlot, slotFromIndex]);
|
|
166
166
|
const handleBlur = (0, react_1.useCallback)(() => {
|
|
167
|
+
resetTyped();
|
|
167
168
|
const input = inputRef.current;
|
|
168
169
|
if (input && input.value === mask) {
|
|
169
170
|
input.value = '';
|
|
170
171
|
setValue('');
|
|
171
172
|
}
|
|
172
|
-
}, [inputRef, mask, setValue]);
|
|
173
|
+
}, [inputRef, mask, resetTyped, setValue]);
|
|
173
174
|
const handleKeyDown = (0, react_1.useCallback)((event) => {
|
|
174
175
|
const input = inputRef.current;
|
|
175
176
|
if (!input || readonly || disabled)
|
|
176
177
|
return;
|
|
178
|
+
if (RESET_TYPED_KEYS.has(event.key))
|
|
179
|
+
resetTyped();
|
|
177
180
|
// Tab уводит фокус из поля — не перехватываем.
|
|
178
181
|
if (event.key === 'Tab')
|
|
179
182
|
return;
|
|
@@ -188,7 +191,13 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
|
|
|
188
191
|
onArrowUp?.();
|
|
189
192
|
return;
|
|
190
193
|
}
|
|
194
|
+
// Копирование и вставку не блокируем: правку поля перехватит beforeinput.
|
|
195
|
+
if (event.ctrlKey || event.metaKey || event.altKey)
|
|
196
|
+
return;
|
|
191
197
|
onEdit?.();
|
|
198
|
+
// Экранные клавиатуры присылают `Unidentified` вместо цифры, её данные есть только в beforeinput.
|
|
199
|
+
if (/^\d$/.test(event.key) || event.key === 'Unidentified')
|
|
200
|
+
return;
|
|
192
201
|
if (event.key === 'Escape') {
|
|
193
202
|
event.preventDefault();
|
|
194
203
|
onEscape?.();
|
|
@@ -223,11 +232,6 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
|
|
|
223
232
|
if (event.key === 'Enter') {
|
|
224
233
|
tryToComplete();
|
|
225
234
|
emit();
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
if (/^\d$/.test(event.key)) {
|
|
229
|
-
typeDigit(slot, event.key);
|
|
230
|
-
emit();
|
|
231
235
|
}
|
|
232
236
|
}, [
|
|
233
237
|
clearSlot,
|
|
@@ -243,21 +247,13 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
|
|
|
243
247
|
onEscape,
|
|
244
248
|
prevSlot,
|
|
245
249
|
readonly,
|
|
250
|
+
resetTyped,
|
|
246
251
|
selectSlot,
|
|
247
252
|
slotFromIndex,
|
|
248
253
|
tryToComplete,
|
|
249
|
-
typeDigit,
|
|
250
254
|
]);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
// этого обработчика вставка не работала бы вовсе.
|
|
254
|
-
const handlePaste = (0, react_1.useCallback)((event) => {
|
|
255
|
-
if (readonly || disabled)
|
|
256
|
-
return;
|
|
257
|
-
event.preventDefault();
|
|
258
|
-
const digits = event.clipboardData.getData('text').replace(/\D/g, '');
|
|
259
|
-
if (!digits)
|
|
260
|
-
return;
|
|
255
|
+
const fillFromDigits = (0, react_1.useCallback)((digits) => {
|
|
256
|
+
resetTyped();
|
|
261
257
|
ensureMask();
|
|
262
258
|
let cursor = 0;
|
|
263
259
|
let lastFilled;
|
|
@@ -276,7 +272,100 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
|
|
|
276
272
|
else if (lastFilled) {
|
|
277
273
|
selectSlot(nextSlot(lastFilled));
|
|
278
274
|
}
|
|
275
|
+
}, [ensureMask, lastSlot, nextSlot, resetTyped, selectSlot, slots, tryToComplete, updateSlot]);
|
|
276
|
+
const handlePaste = (0, react_1.useCallback)((event) => {
|
|
277
|
+
if (readonly || disabled)
|
|
278
|
+
return;
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
const digits = event.clipboardData.getData('text').replace(/\D/g, '');
|
|
281
|
+
if (!digits)
|
|
282
|
+
return;
|
|
283
|
+
fillFromDigits(digits);
|
|
284
|
+
emit();
|
|
285
|
+
}, [disabled, emit, fillFromDigits, readonly]);
|
|
286
|
+
const applyEdit = (0, react_1.useCallback)((inputType, text) => {
|
|
287
|
+
const input = inputRef.current;
|
|
288
|
+
if (!input)
|
|
289
|
+
return;
|
|
290
|
+
onEdit?.();
|
|
291
|
+
if (inputType.startsWith('delete')) {
|
|
292
|
+
resetTyped();
|
|
293
|
+
clearSlot(slotFromIndex(input.selectionStart));
|
|
294
|
+
emit();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const digits = text.replace(/\D/g, '');
|
|
298
|
+
if (!inputType.startsWith('insert') || !digits)
|
|
299
|
+
return;
|
|
300
|
+
// Автозаполнение, `fill` и drop присылают строку целиком.
|
|
301
|
+
if (text.length > 1) {
|
|
302
|
+
fillFromDigits(digits);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
typeDigit(slotFromIndex(input.selectionStart), digits);
|
|
306
|
+
}
|
|
307
|
+
emit();
|
|
308
|
+
}, [clearSlot, emit, fillFromDigits, inputRef, onEdit, resetTyped, slotFromIndex, typeDigit]);
|
|
309
|
+
// Композицию IME браузер не даёт отменить — откатываем её в input.
|
|
310
|
+
const pendingEditRef = (0, react_1.useRef)(null);
|
|
311
|
+
const handleBeforeInput = (0, react_1.useCallback)((event) => {
|
|
312
|
+
const input = inputRef.current;
|
|
313
|
+
if (!input || readonly || disabled)
|
|
314
|
+
return;
|
|
315
|
+
ensureMask();
|
|
316
|
+
if (!event.cancelable) {
|
|
317
|
+
pendingEditRef.current = { value: input.value, start: input.selectionStart ?? 0, end: input.selectionEnd ?? 0 };
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
applyEdit(event.inputType, event.data ?? event.dataTransfer?.getData('text') ?? '');
|
|
322
|
+
}, [applyEdit, disabled, ensureMask, inputRef, readonly]);
|
|
323
|
+
const handleInput = (0, react_1.useCallback)((event) => {
|
|
324
|
+
const input = inputRef.current;
|
|
325
|
+
if (!input || readonly || disabled)
|
|
326
|
+
return;
|
|
327
|
+
const pending = pendingEditRef.current;
|
|
328
|
+
pendingEditRef.current = null;
|
|
329
|
+
if (pending) {
|
|
330
|
+
const inputType = event instanceof InputEvent ? event.inputType : '';
|
|
331
|
+
const inserted = input.value.slice(pending.start, input.value.length - (pending.value.length - pending.end));
|
|
332
|
+
input.value = pending.value;
|
|
333
|
+
setValue(pending.value);
|
|
334
|
+
input.setSelectionRange(pending.start, pending.end);
|
|
335
|
+
// Цифру композиции вводит её фиксация (insertText), иначе цифра запишется дважды.
|
|
336
|
+
if (!inputType.includes('Composition'))
|
|
337
|
+
applyEdit(inputType, inserted);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// Автозаполнение меняет значение без beforeinput.
|
|
341
|
+
const digits = input.value.replace(/\D/g, '');
|
|
342
|
+
input.value = mask;
|
|
343
|
+
setValue(mask);
|
|
344
|
+
fillFromDigits(digits);
|
|
279
345
|
emit();
|
|
280
|
-
}, [disabled, emit,
|
|
281
|
-
|
|
346
|
+
}, [applyEdit, disabled, emit, fillFromDigits, inputRef, mask, readonly, setValue]);
|
|
347
|
+
// React 18 не отдаёт `inputType` в `onBeforeInput`, поэтому слушаем нативные события.
|
|
348
|
+
const onNativeBeforeInput = (0, utils_1.useEventHandler)(handleBeforeInput);
|
|
349
|
+
const onNativeInput = (0, utils_1.useEventHandler)(handleInput);
|
|
350
|
+
const attachedInputRef = (0, react_1.useRef)(null);
|
|
351
|
+
const nativeInputRef = (0, react_1.useCallback)((node) => {
|
|
352
|
+
/* eslint-disable @cloud-ru/ssr-safe-react/domApi -- callback-ref вызывается только в браузере */
|
|
353
|
+
attachedInputRef.current?.removeEventListener('beforeinput', onNativeBeforeInput);
|
|
354
|
+
attachedInputRef.current?.removeEventListener('input', onNativeInput);
|
|
355
|
+
attachedInputRef.current = node;
|
|
356
|
+
node?.addEventListener('beforeinput', onNativeBeforeInput);
|
|
357
|
+
node?.addEventListener('input', onNativeInput);
|
|
358
|
+
/* eslint-enable @cloud-ru/ssr-safe-react/domApi */
|
|
359
|
+
}, [onNativeBeforeInput, onNativeInput]);
|
|
360
|
+
return {
|
|
361
|
+
handleKeyDown,
|
|
362
|
+
handleClick,
|
|
363
|
+
handleFocus,
|
|
364
|
+
handleBlur,
|
|
365
|
+
handlePaste,
|
|
366
|
+
nativeInputRef,
|
|
367
|
+
selectSlot,
|
|
368
|
+
firstSlot,
|
|
369
|
+
lastSlot,
|
|
370
|
+
};
|
|
282
371
|
}
|
|
@@ -321,9 +321,9 @@ export const FieldDate = forwardRef(function FieldDate(props, ref) {
|
|
|
321
321
|
_jsxs("span", { className: styles.rangeInputs, style: { '--field-date-mask-ch': placeholderMask.length }, children: [_jsx(InputPrivate, { ...inputCommon, ref: mergeRefs(ref, fromInputRef), className: cn(fieldStyles.fieldInput, styles.rangeInputFrom), value: rangeInputValue[0], placeholder: placeholderMask, onChange: handleRangeInput(0), id: id, name: name, autoFocus: resolvedAutoFocus, "aria-label": labelFrom, "data-test-id": TEST_IDS.fieldDateInputFrom }), _jsx("span", { className: styles.rangeSeparator, "aria-hidden": true, children: "\u2013" }), _jsx(InputPrivate, { ...inputCommon, ref: toInputRef, className: fieldStyles.fieldInput, value: rangeInputValue[1], placeholder: placeholderMask, onChange: handleRangeInput(1),
|
|
322
322
|
// «to» — последний инпут перед кнопками: на нём живёт roving-композит
|
|
323
323
|
// (ArrowRight в конце → clear/copy), keydown-цепочка с nav-handler'ом.
|
|
324
|
-
onKeyDown: handleToInputKeyDown, tabIndex: inputTabIndex, "aria-label": labelTo, "data-test-id": TEST_IDS.fieldDateInputTo })] })) : (_jsx(InputPrivate, { ref: mergeRefs(ref, fromInputRef), className: fieldStyles.fieldInput, value: singleInputValue, placeholder: placeholderMask,
|
|
325
|
-
// Single/date-time ведёт сегментный движок (
|
|
326
|
-
//
|
|
324
|
+
onKeyDown: handleToInputKeyDown, tabIndex: inputTabIndex, "aria-label": labelTo, "data-test-id": TEST_IDS.fieldDateInputTo })] })) : (_jsx(InputPrivate, { ref: mergeRefs(ref, fromInputRef, singleSeg.nativeInputRef), className: fieldStyles.fieldInput, value: singleInputValue, placeholder: placeholderMask,
|
|
325
|
+
// Single/date-time ведёт сегментный движок (useSegmentedMask), нативный onChange
|
|
326
|
+
// игнорируется. Range — обычный ввод-по-маске (formatMask в handleRangeInput).
|
|
327
327
|
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": TEST_IDS.fieldDateInput })) }), postfixButtons && (
|
|
328
328
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
329
329
|
_jsx("span", { className: fieldStyles.postfixButtonsSlot, onMouseDown: preventSlotMouseDown, onClick: stopSlotClickPropagation, children: postfixButtons }))] }), _jsx("div", { className: fieldStyles.iconSlot, "data-test-id": TEST_IDS.fieldDateCalendar, "aria-hidden": true, children: _jsx(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` при невалидной/неполной строке. */
|
|
@@ -42,8 +42,9 @@ function buildSegments(mode, showSeconds) {
|
|
|
42
42
|
* «десятку» (`day` → > 3, `month` → > 1, `hour` → > 2, `minute`/`second` → > 5), цифра трактуется
|
|
43
43
|
* как «единицы» и в начало сегмента добавляется ноль. Пример: `5` для day → `05.`,
|
|
44
44
|
* фокус автоматически уезжает на следующий сегмент.
|
|
45
|
-
* 2.
|
|
46
|
-
*
|
|
45
|
+
* 2. **Переполнение полной двойки**: если набрано две цифры и значение превышает `max` сегмента —
|
|
46
|
+
* сегмент начинается заново с последней цифры (как посегментный ввод в `useSegmentedMask`).
|
|
47
|
+
* Пример: `39` для day → `09`, `13` для month → `03`.
|
|
47
48
|
*/
|
|
48
49
|
export function formatMask(input, mode, showSeconds = true) {
|
|
49
50
|
const maxLen = totalLen(mode, showSeconds);
|
|
@@ -69,7 +70,7 @@ export function formatMask(input, mode, showSeconds = true) {
|
|
|
69
70
|
stream = stream.slice(seg.len);
|
|
70
71
|
if (raw.length === seg.len) {
|
|
71
72
|
const n = Number(raw);
|
|
72
|
-
out += n > seg.max ?
|
|
73
|
+
out += n > seg.max ? raw.slice(-1).padStart(seg.len, '0') : raw;
|
|
73
74
|
}
|
|
74
75
|
else {
|
|
75
76
|
out += raw;
|
|
@@ -3,7 +3,7 @@ import { ChevronDownSVG } from '@cloud-ru/ds-icons/interface/system';
|
|
|
3
3
|
import { InputPrivate, useButtonNavigation, useClearButton } from '@cloud-ru/ds-input-private';
|
|
4
4
|
import { Droplist } from '@cloud-ru/ds-list';
|
|
5
5
|
import { Tag } from '@cloud-ru/ds-tag';
|
|
6
|
-
import {
|
|
6
|
+
import { useLayoutEffect } from '@cloud-ru/ds-utils';
|
|
7
7
|
import cn from 'classnames';
|
|
8
8
|
import mergeRefs from 'merge-refs';
|
|
9
9
|
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
@@ -14,22 +14,18 @@ import { TEST_IDS } from '../../constants.js';
|
|
|
14
14
|
import { useAdaptiveAutoFocus } from '../../hooks/index.js';
|
|
15
15
|
import { copyTextToClipboard, getAcrylicProps, preventSlotMouseDown, stopSlotClickPropagation, toInputSize, useCopyButton, } from '../shared/index.js';
|
|
16
16
|
import fieldStyles from '../shared/styles.module.css';
|
|
17
|
-
import { useCustomOption } from './hooks.js';
|
|
18
17
|
import styles from './styles.module.css';
|
|
19
|
-
import { extractAppearance, extractLabel, filterItems, findItem, flatten,
|
|
18
|
+
import { extractAppearance, extractLabel, filterItems, findItem, flatten, isMultiple, TAG_SIZE_MAP, } from './utils.js';
|
|
20
19
|
export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
21
20
|
const { label = '', labelTooltip, caption, hint, error, validationState = VALIDATION_STATE.Default, showHintIcon, length, required, size = SIZE.M, className, fieldClassName, items, pinTop, pinBottom, placeholder = '', iconBefore, prefix, postfix, disabled, readonly: readOnly, background = true, open: openProp, onOpenChange, placement, widthStrategy = 'eq', searchable = true, search, autocomplete = false, addOptionByEnter = false, resetSearchOnOptionSelection = true, showClearButton = true, showCopyButton = true, onCopyButtonClick, id, name, autoFocus, layoutPresets, onFocus: onFocusProp, onBlur: onBlurProp, enableFuzzySearch = true, selectedOptionFormatter, footer, footerActiveElementsRefs, loading, noDataState, noResultsState, errorDataState, dataError, dataFiltered, virtualized, scrollToSelectedItem, limitedScrollHeight, untouchableScrollbars, closeOnPopstate, onKeyDown: onKeyDownProp, 'data-test-id': dataTestId = TEST_IDS.fieldSelect, } = props;
|
|
22
21
|
const multiple = isMultiple(props);
|
|
23
22
|
const chips = multiple ? (props.chips ?? true) : false;
|
|
24
23
|
const removeByBackspace = multiple ? (props.removeByBackspace ?? true) : false;
|
|
25
24
|
const closeDroplistOnItemClick = props.closeDroplistOnItemClick ?? !multiple;
|
|
26
|
-
const allowCustomOption = props.allowCustomOption === true;
|
|
27
|
-
const addCustomOptionTriggers = allowCustomOption ? props.addCustomOptionTriggers : undefined;
|
|
28
25
|
const resolvedAutoFocus = useAdaptiveAutoFocus(autoFocus, layoutPresets);
|
|
29
26
|
const inputRef = useRef(null);
|
|
30
27
|
// Отдельный ref на реальный <input>: `inputRef.current` перезаписывается span-обёрткой PopoverPrivate.
|
|
31
28
|
const inputElementRef = useRef(null);
|
|
32
|
-
const restoreInputFocusRef = useRef(false);
|
|
33
29
|
const clearButtonRef = useRef(null);
|
|
34
30
|
const copyButtonRef = useRef(null);
|
|
35
31
|
const contentRef = useRef(null);
|
|
@@ -71,20 +67,16 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
71
67
|
}, [multiple, props.value, multipleLocal]);
|
|
72
68
|
const effectiveValidationState = useMemo(() => (error ? VALIDATION_STATE.Error : validationState), [error, validationState]);
|
|
73
69
|
const allItems = useMemo(() => [...(pinTop ?? []), ...items, ...(pinBottom ?? [])], [items, pinTop, pinBottom]);
|
|
74
|
-
const selectedIds = useMemo(() => getSelectedIds({ multiple, multipleValue, singleValue }), [multiple, multipleValue, singleValue]);
|
|
75
|
-
// Выбранное, которого нет в `items` (кастом по allowCustomOption, ленивая подгрузка) — в список, не только в чип.
|
|
76
|
-
const missingSelectedItems = useMemo(() => getMissingSelectedItems(selectedIds, allItems), [selectedIds, allItems]);
|
|
77
|
-
const itemsWithPlaceholders = useMemo(() => (missingSelectedItems.length > 0 ? [...missingSelectedItems, ...items] : items), [missingSelectedItems, items]);
|
|
78
70
|
// Выбранное значение переживает смену `items` (серверный поиск, ленивая подгрузка).
|
|
79
71
|
const seenItems = useRef(new Map());
|
|
80
72
|
const resolveItem = useMemo(() => {
|
|
81
|
-
for (const item of flatten(
|
|
73
|
+
for (const item of flatten(allItems)) {
|
|
82
74
|
if (item.id !== undefined) {
|
|
83
75
|
seenItems.current.set(item.id, item);
|
|
84
76
|
}
|
|
85
77
|
}
|
|
86
|
-
return (id) => findItem(allItems, id) ??
|
|
87
|
-
}, [allItems
|
|
78
|
+
return (id) => findItem(allItems, id) ?? seenItems.current.get(id);
|
|
79
|
+
}, [allItems]);
|
|
88
80
|
const selectedPairs = useMemo(() => {
|
|
89
81
|
if (!multiple || multipleValue.length === 0) {
|
|
90
82
|
return [];
|
|
@@ -139,13 +131,13 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
139
131
|
syncedLabelRef.current = selectedLabel;
|
|
140
132
|
setInputValue(selectedLabel);
|
|
141
133
|
}, [selectedLabel, typing, resetSearchOnOptionSelection, setInputValue]);
|
|
142
|
-
// При autocomplete фильтрует сервер — items берутся как
|
|
134
|
+
// При autocomplete фильтрует сервер — items берутся как есть.
|
|
143
135
|
const filteredItems = useMemo(() => {
|
|
144
136
|
if (!searchable || autocomplete || !typing) {
|
|
145
|
-
return
|
|
137
|
+
return items;
|
|
146
138
|
}
|
|
147
|
-
return filterItems(
|
|
148
|
-
}, [searchable, autocomplete, typing,
|
|
139
|
+
return filterItems(items, inputValue, enableFuzzySearch);
|
|
140
|
+
}, [searchable, autocomplete, typing, items, inputValue, enableFuzzySearch]);
|
|
149
141
|
const filteredPinTop = useMemo(() => {
|
|
150
142
|
if (!searchable || autocomplete || !typing || !pinTop) {
|
|
151
143
|
return pinTop;
|
|
@@ -158,7 +150,22 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
158
150
|
}
|
|
159
151
|
return filterItems(pinBottom, inputValue, enableFuzzySearch);
|
|
160
152
|
}, [searchable, autocomplete, typing, pinBottom, inputValue, enableFuzzySearch]);
|
|
161
|
-
const
|
|
153
|
+
const handleOpenChange = useCallback((next) => {
|
|
154
|
+
// readonly/disabled поле открывать нельзя, закрывать — можно.
|
|
155
|
+
if (next && (disabled || readOnly))
|
|
156
|
+
return;
|
|
157
|
+
if (openProp === undefined) {
|
|
158
|
+
setOpenLocal(next);
|
|
159
|
+
}
|
|
160
|
+
onOpenChange?.(next);
|
|
161
|
+
if (!next) {
|
|
162
|
+
setTyping(false);
|
|
163
|
+
if (resetSearchOnOptionSelection) {
|
|
164
|
+
setInputValue(selectedLabel);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}, [openProp, onOpenChange, disabled, readOnly, resetSearchOnOptionSelection, setInputValue, selectedLabel]);
|
|
168
|
+
const handleSingleChange = (next) => {
|
|
162
169
|
if (multiple) {
|
|
163
170
|
return;
|
|
164
171
|
}
|
|
@@ -167,12 +174,11 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
167
174
|
}
|
|
168
175
|
props.onChange?.(next);
|
|
169
176
|
setTyping(false);
|
|
170
|
-
}
|
|
171
|
-
const handleMultipleChange =
|
|
177
|
+
};
|
|
178
|
+
const handleMultipleChange = (next) => {
|
|
172
179
|
if (!multiple) {
|
|
173
180
|
return;
|
|
174
181
|
}
|
|
175
|
-
restoreInputFocusRef.current = isBrowser() && inputElementRef.current === document.activeElement;
|
|
176
182
|
if (props.value === undefined) {
|
|
177
183
|
setMultipleLocal(next);
|
|
178
184
|
}
|
|
@@ -183,57 +189,8 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
183
189
|
setInputValue('');
|
|
184
190
|
}
|
|
185
191
|
}
|
|
186
|
-
}
|
|
187
|
-
const resetCustomOptionSearch = useCallback(() => {
|
|
188
|
-
setTyping(false);
|
|
189
|
-
if (resetSearchOnOptionSelection) {
|
|
190
|
-
setInputValue(multiple ? '' : selectedLabel);
|
|
191
|
-
}
|
|
192
|
-
}, [resetSearchOnOptionSelection, setInputValue, multiple, selectedLabel]);
|
|
193
|
-
const { listRef, suppressCloseCommit, enableCloseCommit, tryCommitOnClose, tryCommitOnBlur, tryCommitFromKeyboard, handleListSingleChange, handleListMultipleChange, } = useCustomOption({
|
|
194
|
-
allowCustomOption,
|
|
195
|
-
addCustomOptionTriggers,
|
|
196
|
-
addOptionByEnter,
|
|
197
|
-
multiple,
|
|
198
|
-
inputValue,
|
|
199
|
-
typing,
|
|
200
|
-
multipleValue,
|
|
201
|
-
commitSingle: handleSingleChange,
|
|
202
|
-
commitMultiple: handleMultipleChange,
|
|
203
|
-
resetSearch: resetCustomOptionSearch,
|
|
204
|
-
inputElementRef,
|
|
205
|
-
clearButtonRef,
|
|
206
|
-
copyButtonRef,
|
|
207
|
-
footerActiveElementsRefs,
|
|
208
|
-
});
|
|
209
|
-
const handleOpenChange = useCallback((next) => {
|
|
210
|
-
// readonly/disabled поле открывать нельзя, закрывать — можно.
|
|
211
|
-
if (next && (disabled || readOnly))
|
|
212
|
-
return;
|
|
213
|
-
if (openProp === undefined) {
|
|
214
|
-
setOpenLocal(next);
|
|
215
|
-
}
|
|
216
|
-
onOpenChange?.(next);
|
|
217
|
-
if (!next) {
|
|
218
|
-
setTyping(false);
|
|
219
|
-
const committed = tryCommitOnClose();
|
|
220
|
-
// Кастомная опция не зафиксирована — вернуть лейбл выбранного значения.
|
|
221
|
-
if (resetSearchOnOptionSelection && !committed) {
|
|
222
|
-
setInputValue(selectedLabel);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}, [
|
|
226
|
-
openProp,
|
|
227
|
-
onOpenChange,
|
|
228
|
-
disabled,
|
|
229
|
-
readOnly,
|
|
230
|
-
resetSearchOnOptionSelection,
|
|
231
|
-
setInputValue,
|
|
232
|
-
selectedLabel,
|
|
233
|
-
tryCommitOnClose,
|
|
234
|
-
]);
|
|
192
|
+
};
|
|
235
193
|
const handleInputChange = (next) => {
|
|
236
|
-
enableCloseCommit();
|
|
237
194
|
setInputValue(next);
|
|
238
195
|
setTyping(true);
|
|
239
196
|
if (!open) {
|
|
@@ -332,12 +289,19 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
332
289
|
}
|
|
333
290
|
if (event.key === 'Escape' && open) {
|
|
334
291
|
event.preventDefault();
|
|
335
|
-
suppressCloseCommit();
|
|
336
292
|
handleOpenChange(false);
|
|
337
293
|
}
|
|
338
|
-
if (
|
|
294
|
+
if (addOptionByEnter && event.key === 'Enter' && inputValue !== '') {
|
|
295
|
+
// Введённый текст становится новым значением (создание опции «на лету»).
|
|
339
296
|
event.preventDefault();
|
|
340
|
-
|
|
297
|
+
if (multiple) {
|
|
298
|
+
if (!multipleValue.includes(inputValue)) {
|
|
299
|
+
handleMultipleChange([...multipleValue, inputValue]);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
handleSingleChange(inputValue);
|
|
304
|
+
}
|
|
341
305
|
return;
|
|
342
306
|
}
|
|
343
307
|
if (event.key === 'Enter' && !open) {
|
|
@@ -350,11 +314,6 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
350
314
|
removeChip(last);
|
|
351
315
|
}
|
|
352
316
|
};
|
|
353
|
-
const handleTriggerKeyDownCapture = (event) => {
|
|
354
|
-
if (event.key === 'Escape') {
|
|
355
|
-
suppressCloseCommit();
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
317
|
const handleTriggerMouseEnter = useCallback(() => {
|
|
359
318
|
if (!readOnly && !disabled) {
|
|
360
319
|
setHover(true);
|
|
@@ -379,28 +338,14 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
379
338
|
}
|
|
380
339
|
onFocusProp?.(event);
|
|
381
340
|
}, [searchable, onFocusProp]);
|
|
382
|
-
const handleInputBlur = (event) => {
|
|
341
|
+
const handleInputBlur = useCallback((event) => {
|
|
383
342
|
setFocusVisible(false);
|
|
384
|
-
const blurResult = tryCommitOnBlur(event);
|
|
385
|
-
// Закрытый список не получит onOpenChange — вернуть лейбл выбранного значения.
|
|
386
|
-
// Клик по айтему открытого списка даёт relatedTarget === null, восстановление делает закрытие.
|
|
387
|
-
if (resetSearchOnOptionSelection && blurResult === 'skipped' && (!open || event.relatedTarget !== null)) {
|
|
388
|
-
setTyping(false);
|
|
389
|
-
setInputValue(selectedLabel);
|
|
390
|
-
}
|
|
391
343
|
onBlurProp?.(event);
|
|
392
|
-
};
|
|
344
|
+
}, [onBlurProp]);
|
|
393
345
|
const droplistSelection = multiple
|
|
394
|
-
? { mode: 'multiple', value: multipleValue, onChange:
|
|
395
|
-
: { mode: 'single', value: singleValue, onChange:
|
|
346
|
+
? { mode: 'multiple', value: multipleValue, onChange: handleMultipleChange }
|
|
347
|
+
: { mode: 'single', value: singleValue, onChange: handleSingleChange };
|
|
396
348
|
const hasChips = chips && selectedPairs.length > 0;
|
|
397
|
-
useLayoutEffect(() => {
|
|
398
|
-
if (!restoreInputFocusRef.current) {
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
restoreInputFocusRef.current = false;
|
|
402
|
-
inputElementRef.current?.focus();
|
|
403
|
-
}, [multipleValue.length]);
|
|
404
349
|
// minWidth поискового input'а = ширина введённого текста (`.inputPlug`), но не больше строки чипов.
|
|
405
350
|
// Замер до paint, иначе input мигает при вводе.
|
|
406
351
|
useLayoutEffect(() => {
|
|
@@ -413,11 +358,11 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
413
358
|
setChipInputMinWidth(Math.min(content, Math.max(plug, SEARCH_INPUT_PLUG_MIN_WIDTH)));
|
|
414
359
|
}, [hasChips, inputValue, selectedPairs.length]);
|
|
415
360
|
const searchInputField = (_jsx(InputPrivate, { ref: mergeRefs(ref, inputRef, inputElementRef), className: cn(fieldStyles.fieldInput, !searchable && styles.readonlyCursor), value: inputValue, onChange: searchable ? handleInputChange : undefined, placeholder: selectedLabel || hasChips ? '' : placeholder, disabled: disabled, readonly: !searchable || readOnly, id: id, name: name, autoFocus: resolvedAutoFocus, tabIndex: inputTabIndex, onFocus: handleInputFocus, onBlur: handleInputBlur, onKeyDown: handleTriggerKeyDown, "data-test-id": TEST_IDS.fieldSelectInput }));
|
|
416
|
-
const searchInput = hasChips ? (_jsx("div", { className: styles.inputWrapper, style: { minWidth: chipInputMinWidth }, children: searchInputField }
|
|
361
|
+
const searchInput = hasChips ? (_jsx("div", { className: styles.inputWrapper, style: { minWidth: chipInputMinWidth }, children: searchInputField })) : (_jsx("div", { className: fieldStyles.inputArea, children: searchInputField }));
|
|
417
362
|
const trigger = (
|
|
418
363
|
// Focusable-элемент — InputPrivate внутри, обёртка только собирает combobox.
|
|
419
364
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
420
|
-
_jsxs("div", { className: cn(fieldStyles.fieldWrapper, styles.trigger, fieldClassName), "data-size": size, "data-validation-state": effectiveValidationState, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-withbackground": background || undefined, "data-focusvisible": focusVisible || open || undefined, "data-hover": !readOnly && !disabled && hover ? true : undefined, "data-test-id": dataTestId, onMouseEnter: handleTriggerMouseEnter, onMouseLeave: handleTriggerMouseLeave,
|
|
365
|
+
_jsxs("div", { className: cn(fieldStyles.fieldWrapper, styles.trigger, fieldClassName), "data-size": size, "data-validation-state": effectiveValidationState, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-withbackground": background || undefined, "data-focusvisible": focusVisible || open || undefined, "data-hover": !readOnly && !disabled && hover ? true : undefined, "data-test-id": dataTestId, onMouseEnter: handleTriggerMouseEnter, onMouseLeave: handleTriggerMouseLeave, onClick: handleTriggerClick, children: [_jsxs("div", { className: fieldStyles.backgroundWrapper, children: [_jsx("div", { className: fieldStyles.materialLayer, ...getAcrylicProps({
|
|
421
366
|
validationState: effectiveValidationState,
|
|
422
367
|
disabled,
|
|
423
368
|
readonly: readOnly,
|
|
@@ -426,5 +371,5 @@ export const FieldSelect = forwardRef(function FieldSelect(props, ref) {
|
|
|
426
371
|
}), children: _jsx("div", { className: fieldStyles.acrylicBg, "aria-hidden": true }) }), _jsx("div", { className: fieldStyles.borderStateLayer, "data-state": 'borderOnBackground' }), _jsx("div", { className: fieldStyles.focusLayer })] }), _jsx("div", { className: fieldStyles.fieldContainer, children: _jsxs("div", { className: fieldStyles.contentWrapper, children: [iconBefore && _jsx("div", { className: fieldStyles.iconSlot, children: iconBefore }), _jsxs("div", { className: fieldStyles.inputLine, children: [prefix && _jsx("span", { className: fieldStyles.prefix, children: prefix }), hasChips ? (_jsxs("div", { className: styles.chipsRow, ref: contentRef, "data-test-id": TEST_IDS.fieldSelectChips, children: [selectedPairs.map(pair => (_jsx(Tag, { label: formatPair(pair), size: TAG_SIZE_MAP[size], appearance: pair.appearance ?? 'neutral', onDelete: disabled || readOnly || pair.disabled ? undefined : handleRemoveChip(pair.id) }, String(pair.id)))), searchInput, _jsx("span", { ref: inputPlugRef, className: styles.inputPlug, "aria-hidden": true, children: inputValue })] })) : (searchInput), postfixButtons && (
|
|
427
372
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
428
373
|
_jsx("span", { className: fieldStyles.postfixButtonsSlot, onMouseDown: preventSlotMouseDown, onClick: stopSlotClickPropagation, children: postfixButtons })), postfix && _jsx("span", { className: fieldStyles.postfix, children: postfix })] }), _jsx("div", { className: fieldStyles.iconSlot, children: _jsx("span", { className: styles.chevron, "data-open": open || undefined, "aria-hidden": true, children: _jsx(ChevronDownSVG, {}) }) })] }) })] }));
|
|
429
|
-
return (_jsx(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: _jsx(Droplist, { items: filteredItems, label: label, pinTop: filteredPinTop, pinBottom: filteredPinBottom, trigger: 'click', placement: placement, widthStrategy: widthStrategy, triggerElemRef: inputRef,
|
|
374
|
+
return (_jsx(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: _jsx(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 }) }));
|
|
430
375
|
});
|
|
@@ -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"];
|
|
@@ -2,22 +2,3 @@ export const SELECTION_MODE = {
|
|
|
2
2
|
Single: 'single',
|
|
3
3
|
Multiple: 'multiple',
|
|
4
4
|
};
|
|
5
|
-
/** События, по которым произвольный ввод фиксируется как выбранная опция. */
|
|
6
|
-
export const ADD_CUSTOM_OPTION_TRIGGER = {
|
|
7
|
-
Enter: 'enter',
|
|
8
|
-
Blur: 'blur',
|
|
9
|
-
Space: 'space',
|
|
10
|
-
Comma: 'comma',
|
|
11
|
-
};
|
|
12
|
-
/** Триггеры по умолчанию для `selection='single'` при `allowCustomOption`. */
|
|
13
|
-
export const ADD_CUSTOM_OPTION_TRIGGERS_SINGLE = [
|
|
14
|
-
ADD_CUSTOM_OPTION_TRIGGER.Enter,
|
|
15
|
-
ADD_CUSTOM_OPTION_TRIGGER.Blur,
|
|
16
|
-
];
|
|
17
|
-
/** Триггеры по умолчанию для `selection='multiple'` при `allowCustomOption`. */
|
|
18
|
-
export const ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE = [
|
|
19
|
-
ADD_CUSTOM_OPTION_TRIGGER.Enter,
|
|
20
|
-
ADD_CUSTOM_OPTION_TRIGGER.Blur,
|
|
21
|
-
ADD_CUSTOM_OPTION_TRIGGER.Space,
|
|
22
|
-
ADD_CUSTOM_OPTION_TRIGGER.Comma,
|
|
23
|
-
];
|