@cloud-ru/ds-fields 2.2.3 → 2.2.5
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/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/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/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 +14 -14
- package/src/components/FieldDate/FieldDate.tsx +3 -3
- package/src/components/FieldDate/mask.ts +4 -3
- package/src/components/FieldTime/FieldTime.tsx +2 -2
- package/src/segments/useSegmentedMask.ts +171 -52
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloud-ru/ds-fields",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
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-calendar": "^1.0.17",
|
|
44
|
+
"@cloud-ru/ds-bottom-sheet": "^1.1.2",
|
|
43
45
|
"@cloud-ru/ds-button": "^1.0.2",
|
|
44
|
-
"@cloud-ru/ds-
|
|
45
|
-
"@cloud-ru/ds-color-picker": "^1.0.6",
|
|
46
|
+
"@cloud-ru/ds-color-picker": "^1.0.7",
|
|
46
47
|
"@cloud-ru/ds-divider": "^1.0.1",
|
|
47
|
-
"@cloud-ru/ds-
|
|
48
|
-
"@cloud-ru/ds-
|
|
49
|
-
"@cloud-ru/ds-field-decorator": "^1.0.
|
|
50
|
-
"@cloud-ru/ds-
|
|
51
|
-
"@cloud-ru/ds-input-private": "^1.0.4",
|
|
52
|
-
"@cloud-ru/ds-list": "^2.1.3",
|
|
53
|
-
"@cloud-ru/ds-materials": "^1.0.1",
|
|
48
|
+
"@cloud-ru/ds-dropdown": "^1.0.9",
|
|
49
|
+
"@cloud-ru/ds-input-private": "^1.0.5",
|
|
50
|
+
"@cloud-ru/ds-field-decorator": "^1.0.6",
|
|
51
|
+
"@cloud-ru/ds-list": "^2.1.5",
|
|
54
52
|
"@cloud-ru/ds-loader": "^1.0.1",
|
|
53
|
+
"@cloud-ru/ds-materials": "^1.0.1",
|
|
54
|
+
"@cloud-ru/ds-icons": "^1.2.0",
|
|
55
55
|
"@cloud-ru/ds-scroll": "^1.0.2",
|
|
56
56
|
"@cloud-ru/ds-skeleton": "^1.0.1",
|
|
57
|
-
"@cloud-ru/ds-slider": "^1.0.
|
|
58
|
-
"@cloud-ru/ds-tag": "^1.1.
|
|
59
|
-
"@cloud-ru/ds-
|
|
60
|
-
"@cloud-ru/ds-
|
|
57
|
+
"@cloud-ru/ds-slider": "^1.0.5",
|
|
58
|
+
"@cloud-ru/ds-tag": "^1.1.2",
|
|
59
|
+
"@cloud-ru/ds-tooltip": "^1.0.5",
|
|
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
|
}
|
|
@@ -267,7 +267,7 @@ export const FieldTime = forwardRef<HTMLInputElement, FieldTimeProps>(function F
|
|
|
267
267
|
[onInputKeyDown, timeSeg],
|
|
268
268
|
);
|
|
269
269
|
|
|
270
|
-
// value ведёт сегментный движок (
|
|
270
|
+
// value ведёт сегментный движок (useSegmentedMask); нативный onChange input'а игнорируем.
|
|
271
271
|
const handleInputChange = useCallback(() => undefined, []);
|
|
272
272
|
|
|
273
273
|
const handleInputFocus = useCallback(
|
|
@@ -323,7 +323,7 @@ export const FieldTime = forwardRef<HTMLInputElement, FieldTimeProps>(function F
|
|
|
323
323
|
<div className={fieldStyles.inputLine}>
|
|
324
324
|
<div className={fieldStyles.inputArea}>
|
|
325
325
|
<InputPrivate
|
|
326
|
-
ref={mergeRefs(ref, localRef)}
|
|
326
|
+
ref={mergeRefs(ref, localRef, timeSeg.nativeInputRef)}
|
|
327
327
|
className={fieldStyles.fieldInput}
|
|
328
328
|
value={inputValue}
|
|
329
329
|
onChange={handleInputChange}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
import { useEventHandler } from '@cloud-ru/ds-utils';
|
|
1
2
|
import { ClipboardEvent, KeyboardEvent, MouseEvent, RefObject, useCallback, useRef } from 'react';
|
|
2
3
|
|
|
3
|
-
import { SlotMeta } from './segments';
|
|
4
|
+
import { SegmentKey, SlotMeta } from './segments';
|
|
5
|
+
|
|
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
|
+
]);
|
|
4
17
|
|
|
5
18
|
type UseSegmentedMaskParams = {
|
|
6
19
|
inputRef: RefObject<HTMLInputElement | null>;
|
|
@@ -32,6 +45,7 @@ type UseSegmentedMaskParams = {
|
|
|
32
45
|
* Сегментный ввод даты/времени (порт `useDateField` из @snack-uikit/fields). Каретка ходит по
|
|
33
46
|
* сегментам (`setSelectionRange` подсвечивает текущий), цифры заполняют сегмент с авто-переходом,
|
|
34
47
|
* стрелки двигают по сегментам, Backspace очищает сегмент до плейсхолдера.
|
|
48
|
+
* `nativeInputRef` передаётся в `ref` того же input'а: значение правится через нативные `beforeinput`/`input`.
|
|
35
49
|
*/
|
|
36
50
|
export function useSegmentedMask({
|
|
37
51
|
inputRef,
|
|
@@ -174,74 +188,71 @@ export function useSegmentedMask({
|
|
|
174
188
|
[clearSlot, isValidDate, lastSlot, nextSlot, selectSlot, slots, tryToComplete],
|
|
175
189
|
);
|
|
176
190
|
|
|
191
|
+
// Цифры сегмента храним отдельно от текста: по `01` в поле не отличить `1` от `0` + `1`.
|
|
192
|
+
const typedRef = useRef<{ key: SegmentKey; digits: string } | null>(null);
|
|
193
|
+
const resetTyped = useCallback(() => {
|
|
194
|
+
typedRef.current = null;
|
|
195
|
+
}, []);
|
|
196
|
+
|
|
177
197
|
const typeDigit = useCallback(
|
|
178
198
|
(slot: SlotMeta, key: string) => {
|
|
179
|
-
const text = getSlotText(slot);
|
|
180
|
-
const numberValue = Number(text) || 0;
|
|
181
|
-
const digit = Number(key);
|
|
182
|
-
const slotValue = parseInt(numberValue.toString() + key, 10) || 0;
|
|
183
|
-
const valueLength = slotValue.toString().length;
|
|
184
199
|
const maxLength = slot.max.toString().length;
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
checkInputAndGoNext(slot);
|
|
199
|
-
} else if (digit || digit >= slot.min) {
|
|
200
|
-
updateSlot(slot, key);
|
|
201
|
-
}
|
|
202
|
-
} else if (slotValue <= slot.max) {
|
|
203
|
-
updateSlot(slot, slotValue);
|
|
204
|
-
checkInputAndGoNext(slot);
|
|
205
|
-
} else if (digit * 10 > slot.max) {
|
|
206
|
-
updateSlot(slot, key);
|
|
207
|
-
checkInputAndGoNext(slot);
|
|
208
|
-
} else if (digit || digit >= slot.min) {
|
|
209
|
-
updateSlot(slot, key);
|
|
200
|
+
const prev = typedRef.current?.key === slot.key ? typedRef.current.digits : '';
|
|
201
|
+
const appended = prev + key;
|
|
202
|
+
const overflow = Number(appended) > slot.max;
|
|
203
|
+
const digits = overflow ? key : appended;
|
|
204
|
+
const value = Number(digits);
|
|
205
|
+
const complete = overflow || digits.length >= maxLength || value * 10 > slot.max;
|
|
206
|
+
const accepted = value >= slot.min;
|
|
207
|
+
|
|
208
|
+
if (accepted) updateSlot(slot, value);
|
|
209
|
+
|
|
210
|
+
if (!complete) {
|
|
211
|
+
typedRef.current = { key: slot.key, digits };
|
|
212
|
+
return;
|
|
210
213
|
}
|
|
214
|
+
|
|
215
|
+
typedRef.current = null;
|
|
216
|
+
if (accepted) checkInputAndGoNext(slot);
|
|
211
217
|
},
|
|
212
|
-
[checkInputAndGoNext,
|
|
218
|
+
[checkInputAndGoNext, updateSlot],
|
|
213
219
|
);
|
|
214
220
|
|
|
215
221
|
const handleFocus = useCallback(() => {
|
|
216
222
|
if (readonly || disabled) return;
|
|
223
|
+
resetTyped();
|
|
217
224
|
ensureMask();
|
|
218
225
|
// На фокус всегда выбираем ПЕРВЫЙ сегмент (паритет с легаси). При клике мышью `handleClick`
|
|
219
226
|
// отработает следом и перевыберет сегмент под кареткой; при Tab остаётся первый.
|
|
220
227
|
selectSlot(firstSlot);
|
|
221
|
-
}, [disabled, ensureMask, firstSlot, readonly, selectSlot]);
|
|
228
|
+
}, [disabled, ensureMask, firstSlot, readonly, resetTyped, selectSlot]);
|
|
222
229
|
|
|
223
230
|
const handleClick = useCallback(
|
|
224
231
|
(event: MouseEvent<HTMLInputElement>) => {
|
|
225
232
|
if (readonly || disabled) return;
|
|
233
|
+
resetTyped();
|
|
226
234
|
ensureMask();
|
|
227
235
|
selectSlot(slotFromIndex(event.currentTarget.selectionStart));
|
|
228
236
|
},
|
|
229
|
-
[disabled, ensureMask, readonly, selectSlot, slotFromIndex],
|
|
237
|
+
[disabled, ensureMask, readonly, resetTyped, selectSlot, slotFromIndex],
|
|
230
238
|
);
|
|
231
239
|
|
|
232
240
|
const handleBlur = useCallback(() => {
|
|
241
|
+
resetTyped();
|
|
233
242
|
const input = inputRef.current;
|
|
234
243
|
if (input && input.value === mask) {
|
|
235
244
|
input.value = '';
|
|
236
245
|
setValue('');
|
|
237
246
|
}
|
|
238
|
-
}, [inputRef, mask, setValue]);
|
|
247
|
+
}, [inputRef, mask, resetTyped, setValue]);
|
|
239
248
|
|
|
240
249
|
const handleKeyDown = useCallback(
|
|
241
250
|
(event: KeyboardEvent<HTMLInputElement>) => {
|
|
242
251
|
const input = inputRef.current;
|
|
243
252
|
if (!input || readonly || disabled) return;
|
|
244
253
|
|
|
254
|
+
if (RESET_TYPED_KEYS.has(event.key)) resetTyped();
|
|
255
|
+
|
|
245
256
|
// Tab уводит фокус из поля — не перехватываем.
|
|
246
257
|
if (event.key === 'Tab') return;
|
|
247
258
|
|
|
@@ -258,8 +269,14 @@ export function useSegmentedMask({
|
|
|
258
269
|
return;
|
|
259
270
|
}
|
|
260
271
|
|
|
272
|
+
// Копирование и вставку не блокируем: правку поля перехватит beforeinput.
|
|
273
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
274
|
+
|
|
261
275
|
onEdit?.();
|
|
262
276
|
|
|
277
|
+
// Экранные клавиатуры присылают `Unidentified` вместо цифры, её данные есть только в beforeinput.
|
|
278
|
+
if (/^\d$/.test(event.key) || event.key === 'Unidentified') return;
|
|
279
|
+
|
|
263
280
|
if (event.key === 'Escape') {
|
|
264
281
|
event.preventDefault();
|
|
265
282
|
onEscape?.();
|
|
@@ -297,11 +314,6 @@ export function useSegmentedMask({
|
|
|
297
314
|
if (event.key === 'Enter') {
|
|
298
315
|
tryToComplete();
|
|
299
316
|
emit();
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
if (/^\d$/.test(event.key)) {
|
|
303
|
-
typeDigit(slot, event.key);
|
|
304
|
-
emit();
|
|
305
317
|
}
|
|
306
318
|
},
|
|
307
319
|
[
|
|
@@ -318,22 +330,16 @@ export function useSegmentedMask({
|
|
|
318
330
|
onEscape,
|
|
319
331
|
prevSlot,
|
|
320
332
|
readonly,
|
|
333
|
+
resetTyped,
|
|
321
334
|
selectSlot,
|
|
322
335
|
slotFromIndex,
|
|
323
336
|
tryToComplete,
|
|
324
|
-
typeDigit,
|
|
325
337
|
],
|
|
326
338
|
);
|
|
327
339
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const handlePaste = useCallback(
|
|
332
|
-
(event: ClipboardEvent<HTMLInputElement>) => {
|
|
333
|
-
if (readonly || disabled) return;
|
|
334
|
-
event.preventDefault();
|
|
335
|
-
const digits = event.clipboardData.getData('text').replace(/\D/g, '');
|
|
336
|
-
if (!digits) return;
|
|
340
|
+
const fillFromDigits = useCallback(
|
|
341
|
+
(digits: string) => {
|
|
342
|
+
resetTyped();
|
|
337
343
|
ensureMask();
|
|
338
344
|
|
|
339
345
|
let cursor = 0;
|
|
@@ -352,10 +358,123 @@ export function useSegmentedMask({
|
|
|
352
358
|
} else if (lastFilled) {
|
|
353
359
|
selectSlot(nextSlot(lastFilled));
|
|
354
360
|
}
|
|
361
|
+
},
|
|
362
|
+
[ensureMask, lastSlot, nextSlot, resetTyped, selectSlot, slots, tryToComplete, updateSlot],
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const handlePaste = useCallback(
|
|
366
|
+
(event: ClipboardEvent<HTMLInputElement>) => {
|
|
367
|
+
if (readonly || disabled) return;
|
|
368
|
+
event.preventDefault();
|
|
369
|
+
const digits = event.clipboardData.getData('text').replace(/\D/g, '');
|
|
370
|
+
if (!digits) return;
|
|
371
|
+
fillFromDigits(digits);
|
|
372
|
+
emit();
|
|
373
|
+
},
|
|
374
|
+
[disabled, emit, fillFromDigits, readonly],
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
const applyEdit = useCallback(
|
|
378
|
+
(inputType: string, text: string) => {
|
|
379
|
+
const input = inputRef.current;
|
|
380
|
+
if (!input) return;
|
|
381
|
+
onEdit?.();
|
|
382
|
+
|
|
383
|
+
if (inputType.startsWith('delete')) {
|
|
384
|
+
resetTyped();
|
|
385
|
+
clearSlot(slotFromIndex(input.selectionStart));
|
|
386
|
+
emit();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const digits = text.replace(/\D/g, '');
|
|
391
|
+
if (!inputType.startsWith('insert') || !digits) return;
|
|
392
|
+
|
|
393
|
+
// Автозаполнение, `fill` и drop присылают строку целиком.
|
|
394
|
+
if (text.length > 1) {
|
|
395
|
+
fillFromDigits(digits);
|
|
396
|
+
} else {
|
|
397
|
+
typeDigit(slotFromIndex(input.selectionStart), digits);
|
|
398
|
+
}
|
|
355
399
|
emit();
|
|
356
400
|
},
|
|
357
|
-
[
|
|
401
|
+
[clearSlot, emit, fillFromDigits, inputRef, onEdit, resetTyped, slotFromIndex, typeDigit],
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// Композицию IME браузер не даёт отменить — откатываем её в input.
|
|
405
|
+
const pendingEditRef = useRef<{ value: string; start: number; end: number } | null>(null);
|
|
406
|
+
|
|
407
|
+
const handleBeforeInput = useCallback(
|
|
408
|
+
(event: InputEvent) => {
|
|
409
|
+
const input = inputRef.current;
|
|
410
|
+
if (!input || readonly || disabled) return;
|
|
411
|
+
ensureMask();
|
|
412
|
+
|
|
413
|
+
if (!event.cancelable) {
|
|
414
|
+
pendingEditRef.current = { value: input.value, start: input.selectionStart ?? 0, end: input.selectionEnd ?? 0 };
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
event.preventDefault();
|
|
419
|
+
applyEdit(event.inputType, event.data ?? event.dataTransfer?.getData('text') ?? '');
|
|
420
|
+
},
|
|
421
|
+
[applyEdit, disabled, ensureMask, inputRef, readonly],
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const handleInput = useCallback(
|
|
425
|
+
(event: Event) => {
|
|
426
|
+
const input = inputRef.current;
|
|
427
|
+
if (!input || readonly || disabled) return;
|
|
428
|
+
const pending = pendingEditRef.current;
|
|
429
|
+
pendingEditRef.current = null;
|
|
430
|
+
|
|
431
|
+
if (pending) {
|
|
432
|
+
const inputType = event instanceof InputEvent ? event.inputType : '';
|
|
433
|
+
const inserted = input.value.slice(pending.start, input.value.length - (pending.value.length - pending.end));
|
|
434
|
+
input.value = pending.value;
|
|
435
|
+
setValue(pending.value);
|
|
436
|
+
input.setSelectionRange(pending.start, pending.end);
|
|
437
|
+
// Цифру композиции вводит её фиксация (insertText), иначе цифра запишется дважды.
|
|
438
|
+
if (!inputType.includes('Composition')) applyEdit(inputType, inserted);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Автозаполнение меняет значение без beforeinput.
|
|
443
|
+
const digits = input.value.replace(/\D/g, '');
|
|
444
|
+
input.value = mask;
|
|
445
|
+
setValue(mask);
|
|
446
|
+
fillFromDigits(digits);
|
|
447
|
+
emit();
|
|
448
|
+
},
|
|
449
|
+
[applyEdit, disabled, emit, fillFromDigits, inputRef, mask, readonly, setValue],
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
// React 18 не отдаёт `inputType` в `onBeforeInput`, поэтому слушаем нативные события.
|
|
453
|
+
const onNativeBeforeInput = useEventHandler(handleBeforeInput);
|
|
454
|
+
const onNativeInput = useEventHandler(handleInput);
|
|
455
|
+
const attachedInputRef = useRef<HTMLInputElement | null>(null);
|
|
456
|
+
const nativeInputRef = useCallback(
|
|
457
|
+
(node: HTMLInputElement | null) => {
|
|
458
|
+
/* eslint-disable @cloud-ru/ssr-safe-react/domApi -- callback-ref вызывается только в браузере */
|
|
459
|
+
attachedInputRef.current?.removeEventListener('beforeinput', onNativeBeforeInput);
|
|
460
|
+
attachedInputRef.current?.removeEventListener('input', onNativeInput);
|
|
461
|
+
attachedInputRef.current = node;
|
|
462
|
+
node?.addEventListener('beforeinput', onNativeBeforeInput);
|
|
463
|
+
node?.addEventListener('input', onNativeInput);
|
|
464
|
+
/* eslint-enable @cloud-ru/ssr-safe-react/domApi */
|
|
465
|
+
},
|
|
466
|
+
[onNativeBeforeInput, onNativeInput],
|
|
358
467
|
);
|
|
359
468
|
|
|
360
|
-
return {
|
|
469
|
+
return {
|
|
470
|
+
handleKeyDown,
|
|
471
|
+
handleClick,
|
|
472
|
+
handleFocus,
|
|
473
|
+
handleBlur,
|
|
474
|
+
handlePaste,
|
|
475
|
+
nativeInputRef,
|
|
476
|
+
selectSlot,
|
|
477
|
+
firstSlot,
|
|
478
|
+
lastSlot,
|
|
479
|
+
};
|
|
361
480
|
}
|