@cloud-ru/ds-fields 2.2.3-preview-7425cb27.0 → 2.2.3-preview-a42433ce.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,9 +29,7 @@ type UseSegmentedMaskParams = {
29
29
  * Сегментный ввод даты/времени (порт `useDateField` из @snack-uikit/fields). Каретка ходит по
30
30
  * сегментам (`setSelectionRange` подсвечивает текущий), цифры заполняют сегмент с авто-переходом,
31
31
  * стрелки двигают по сегментам, Backspace очищает сегмент до плейсхолдера.
32
- *
33
- * Навигация идёт через `handleKeyDown`, правка значения — через нативные `beforeinput`/`input`:
34
- * `nativeInputRef` нужно передать в `ref` того же input'а.
32
+ * `nativeInputRef` передаётся в `ref` того же input'а: значение правится через нативные `beforeinput`/`input`.
35
33
  */
36
34
  export declare function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue, onMaskedChange, onArrowDown, onArrowUp, onEscape, onEdit, }: UseSegmentedMaskParams): {
37
35
  handleKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
@@ -18,9 +18,7 @@ const RESET_TYPED_KEYS = new Set([
18
18
  * Сегментный ввод даты/времени (порт `useDateField` из @snack-uikit/fields). Каретка ходит по
19
19
  * сегментам (`setSelectionRange` подсвечивает текущий), цифры заполняют сегмент с авто-переходом,
20
20
  * стрелки двигают по сегментам, Backspace очищает сегмент до плейсхолдера.
21
- *
22
- * Навигация идёт через `handleKeyDown`, правка значения — через нативные `beforeinput`/`input`:
23
- * `nativeInputRef` нужно передать в `ref` того же input'а.
21
+ * `nativeInputRef` передаётся в `ref` того же input'а: значение правится через нативные `beforeinput`/`input`.
24
22
  */
25
23
  function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue, onMaskedChange, onArrowDown, onArrowUp, onEscape, onEdit, }) {
26
24
  const firstSlot = slots[0];
@@ -125,8 +123,7 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
125
123
  }
126
124
  selectSlot(nextSlot(slot));
127
125
  }, [clearSlot, isValidDate, lastSlot, nextSlot, selectSlot, slots, tryToComplete]);
128
- // Цифры, набранные в текущий сегмент. Храним отдельно от текста: по `01` в поле нельзя понять,
129
- // набрали `1` или `0` + `1`, а от этого зависит, ждать ли вторую цифру.
126
+ // Цифры сегмента храним отдельно от текста: по `01` в поле не отличить `1` от `0` + `1`.
130
127
  const typedRef = (0, react_1.useRef)(null);
131
128
  const resetTyped = (0, react_1.useCallback)(() => {
132
129
  typedRef.current = null;
@@ -135,12 +132,11 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
135
132
  const maxLength = slot.max.toString().length;
136
133
  const prev = typedRef.current?.key === slot.key ? typedRef.current.digits : '';
137
134
  const appended = prev + key;
138
- // Цифра выводит сегмент за max — сегмент начинается с неё заново (как React Aria и MUI X): `13` в месяце → `03`.
135
+ // Как в React Aria и MUI X: `13` в месяце → `03`.
139
136
  const overflow = Number(appended) > slot.max;
140
137
  const digits = overflow ? key : appended;
141
138
  const value = Number(digits);
142
139
  const complete = overflow || digits.length >= maxLength || value * 10 > slot.max;
143
- // Значение ниже min (`0` в дне) не показываем, но помним цифру до следующей.
144
140
  const accepted = value >= slot.min;
145
141
  if (accepted)
146
142
  updateSlot(slot, value);
@@ -180,7 +176,6 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
180
176
  const input = inputRef.current;
181
177
  if (!input || readonly || disabled)
182
178
  return;
183
- // Навигация и правка начинают набор сегмента заново. Цифры, `Unidentified` (Android) и модификаторы — нет.
184
179
  if (RESET_TYPED_KEYS.has(event.key))
185
180
  resetTyped();
186
181
  // Tab уводит фокус из поля — не перехватываем.
@@ -197,11 +192,11 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
197
192
  onArrowUp?.();
198
193
  return;
199
194
  }
200
- // Сочетания (копирование, вставка, выделение) отдаём браузеру: правку поля перехватит beforeinput.
195
+ // Копирование и вставку не блокируем: правку поля перехватит beforeinput.
201
196
  if (event.ctrlKey || event.metaKey || event.altKey)
202
197
  return;
203
198
  onEdit?.();
204
- // Цифры и `Unidentified` (экранные клавиатуры Android, IME) вводятся через beforeinput.
199
+ // Экранные клавиатуры присылают `Unidentified` вместо цифры, её данные есть только в beforeinput.
205
200
  if (/^\d$/.test(event.key) || event.key === 'Unidentified')
206
201
  return;
207
202
  if (event.key === 'Escape') {
@@ -258,7 +253,6 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
258
253
  slotFromIndex,
259
254
  tryToComplete,
260
255
  ]);
261
- // Раскладывает цифры по сегментам слева направо полными кусками — для вставки и автозаполнения.
262
256
  const fillFromDigits = (0, react_1.useCallback)((digits) => {
263
257
  resetTyped();
264
258
  ensureMask();
@@ -280,8 +274,6 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
280
274
  selectSlot(nextSlot(lastFilled));
281
275
  }
282
276
  }, [ensureMask, lastSlot, nextSlot, resetTyped, selectSlot, slots, tryToComplete, updateSlot]);
283
- // Вставка (Ctrl+V) полной строки даты/времени. Нативный onChange у поля игнорируется (движок
284
- // controlled), поэтому без этого обработчика вставка не работала бы вовсе.
285
277
  const handlePaste = (0, react_1.useCallback)((event) => {
286
278
  if (readonly || disabled)
287
279
  return;
@@ -306,7 +298,7 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
306
298
  const digits = text.replace(/\D/g, '');
307
299
  if (!inputType.startsWith('insert') || !digits)
308
300
  return;
309
- // Строка целиком (автозаполнение, `fill`, drop) раскладывается с начала маски, одиночная цифра — в текущий сегмент.
301
+ // Автозаполнение, `fill` и drop присылают строку целиком.
310
302
  if (text.length > 1) {
311
303
  fillFromDigits(digits);
312
304
  }
@@ -315,7 +307,7 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
315
307
  }
316
308
  emit();
317
309
  }, [clearSlot, emit, fillFromDigits, inputRef, onEdit, resetTyped, slotFromIndex, typeDigit]);
318
- // Значение и выделение до правки, которую браузер не дал отменить (композиция IME).
310
+ // Композицию IME браузер не даёт отменить — откатываем её в input.
319
311
  const pendingEditRef = (0, react_1.useRef)(null);
320
312
  const handleBeforeInput = (0, react_1.useCallback)((event) => {
321
313
  const input = inputRef.current;
@@ -336,18 +328,17 @@ function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue,
336
328
  const pending = pendingEditRef.current;
337
329
  pendingEditRef.current = null;
338
330
  if (pending) {
339
- // Браузер уже изменил значение: вырезаем вставленное, возвращаем прежнее и применяем как обычный ввод.
340
331
  const inputType = event instanceof InputEvent ? event.inputType : '';
341
332
  const inserted = input.value.slice(pending.start, input.value.length - (pending.value.length - pending.end));
342
333
  input.value = pending.value;
343
334
  setValue(pending.value);
344
335
  input.setSelectionRange(pending.start, pending.end);
345
- // Цифру из композиции IME применит её фиксация (отменяемый insertText), как в React Aria.
336
+ // Цифру композиции вводит её фиксация (insertText), иначе цифра запишется дважды.
346
337
  if (!inputType.includes('Composition'))
347
338
  applyEdit(inputType, inserted);
348
339
  return;
349
340
  }
350
- // Значение сменили без beforeinput (автозаполнение браузера).
341
+ // Автозаполнение меняет значение без beforeinput.
351
342
  const digits = input.value.replace(/\D/g, '');
352
343
  input.value = mask;
353
344
  setValue(mask);
@@ -29,9 +29,7 @@ type UseSegmentedMaskParams = {
29
29
  * Сегментный ввод даты/времени (порт `useDateField` из @snack-uikit/fields). Каретка ходит по
30
30
  * сегментам (`setSelectionRange` подсвечивает текущий), цифры заполняют сегмент с авто-переходом,
31
31
  * стрелки двигают по сегментам, Backspace очищает сегмент до плейсхолдера.
32
- *
33
- * Навигация идёт через `handleKeyDown`, правка значения — через нативные `beforeinput`/`input`:
34
- * `nativeInputRef` нужно передать в `ref` того же input'а.
32
+ * `nativeInputRef` передаётся в `ref` того же input'а: значение правится через нативные `beforeinput`/`input`.
35
33
  */
36
34
  export declare function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue, onMaskedChange, onArrowDown, onArrowUp, onEscape, onEdit, }: UseSegmentedMaskParams): {
37
35
  handleKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
@@ -15,9 +15,7 @@ const RESET_TYPED_KEYS = new Set([
15
15
  * Сегментный ввод даты/времени (порт `useDateField` из @snack-uikit/fields). Каретка ходит по
16
16
  * сегментам (`setSelectionRange` подсвечивает текущий), цифры заполняют сегмент с авто-переходом,
17
17
  * стрелки двигают по сегментам, Backspace очищает сегмент до плейсхолдера.
18
- *
19
- * Навигация идёт через `handleKeyDown`, правка значения — через нативные `beforeinput`/`input`:
20
- * `nativeInputRef` нужно передать в `ref` того же input'а.
18
+ * `nativeInputRef` передаётся в `ref` того же input'а: значение правится через нативные `beforeinput`/`input`.
21
19
  */
22
20
  export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, setValue, onMaskedChange, onArrowDown, onArrowUp, onEscape, onEdit, }) {
23
21
  const firstSlot = slots[0];
@@ -122,8 +120,7 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
122
120
  }
123
121
  selectSlot(nextSlot(slot));
124
122
  }, [clearSlot, isValidDate, lastSlot, nextSlot, selectSlot, slots, tryToComplete]);
125
- // Цифры, набранные в текущий сегмент. Храним отдельно от текста: по `01` в поле нельзя понять,
126
- // набрали `1` или `0` + `1`, а от этого зависит, ждать ли вторую цифру.
123
+ // Цифры сегмента храним отдельно от текста: по `01` в поле не отличить `1` от `0` + `1`.
127
124
  const typedRef = useRef(null);
128
125
  const resetTyped = useCallback(() => {
129
126
  typedRef.current = null;
@@ -132,12 +129,11 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
132
129
  const maxLength = slot.max.toString().length;
133
130
  const prev = typedRef.current?.key === slot.key ? typedRef.current.digits : '';
134
131
  const appended = prev + key;
135
- // Цифра выводит сегмент за max — сегмент начинается с неё заново (как React Aria и MUI X): `13` в месяце → `03`.
132
+ // Как в React Aria и MUI X: `13` в месяце → `03`.
136
133
  const overflow = Number(appended) > slot.max;
137
134
  const digits = overflow ? key : appended;
138
135
  const value = Number(digits);
139
136
  const complete = overflow || digits.length >= maxLength || value * 10 > slot.max;
140
- // Значение ниже min (`0` в дне) не показываем, но помним цифру до следующей.
141
137
  const accepted = value >= slot.min;
142
138
  if (accepted)
143
139
  updateSlot(slot, value);
@@ -177,7 +173,6 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
177
173
  const input = inputRef.current;
178
174
  if (!input || readonly || disabled)
179
175
  return;
180
- // Навигация и правка начинают набор сегмента заново. Цифры, `Unidentified` (Android) и модификаторы — нет.
181
176
  if (RESET_TYPED_KEYS.has(event.key))
182
177
  resetTyped();
183
178
  // Tab уводит фокус из поля — не перехватываем.
@@ -194,11 +189,11 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
194
189
  onArrowUp?.();
195
190
  return;
196
191
  }
197
- // Сочетания (копирование, вставка, выделение) отдаём браузеру: правку поля перехватит beforeinput.
192
+ // Копирование и вставку не блокируем: правку поля перехватит beforeinput.
198
193
  if (event.ctrlKey || event.metaKey || event.altKey)
199
194
  return;
200
195
  onEdit?.();
201
- // Цифры и `Unidentified` (экранные клавиатуры Android, IME) вводятся через beforeinput.
196
+ // Экранные клавиатуры присылают `Unidentified` вместо цифры, её данные есть только в beforeinput.
202
197
  if (/^\d$/.test(event.key) || event.key === 'Unidentified')
203
198
  return;
204
199
  if (event.key === 'Escape') {
@@ -255,7 +250,6 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
255
250
  slotFromIndex,
256
251
  tryToComplete,
257
252
  ]);
258
- // Раскладывает цифры по сегментам слева направо полными кусками — для вставки и автозаполнения.
259
253
  const fillFromDigits = useCallback((digits) => {
260
254
  resetTyped();
261
255
  ensureMask();
@@ -277,8 +271,6 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
277
271
  selectSlot(nextSlot(lastFilled));
278
272
  }
279
273
  }, [ensureMask, lastSlot, nextSlot, resetTyped, selectSlot, slots, tryToComplete, updateSlot]);
280
- // Вставка (Ctrl+V) полной строки даты/времени. Нативный onChange у поля игнорируется (движок
281
- // controlled), поэтому без этого обработчика вставка не работала бы вовсе.
282
274
  const handlePaste = useCallback((event) => {
283
275
  if (readonly || disabled)
284
276
  return;
@@ -303,7 +295,7 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
303
295
  const digits = text.replace(/\D/g, '');
304
296
  if (!inputType.startsWith('insert') || !digits)
305
297
  return;
306
- // Строка целиком (автозаполнение, `fill`, drop) раскладывается с начала маски, одиночная цифра — в текущий сегмент.
298
+ // Автозаполнение, `fill` и drop присылают строку целиком.
307
299
  if (text.length > 1) {
308
300
  fillFromDigits(digits);
309
301
  }
@@ -312,7 +304,7 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
312
304
  }
313
305
  emit();
314
306
  }, [clearSlot, emit, fillFromDigits, inputRef, onEdit, resetTyped, slotFromIndex, typeDigit]);
315
- // Значение и выделение до правки, которую браузер не дал отменить (композиция IME).
307
+ // Композицию IME браузер не даёт отменить — откатываем её в input.
316
308
  const pendingEditRef = useRef(null);
317
309
  const handleBeforeInput = useCallback((event) => {
318
310
  const input = inputRef.current;
@@ -333,18 +325,17 @@ export function useSegmentedMask({ inputRef, mask, slots, readonly, disabled, se
333
325
  const pending = pendingEditRef.current;
334
326
  pendingEditRef.current = null;
335
327
  if (pending) {
336
- // Браузер уже изменил значение: вырезаем вставленное, возвращаем прежнее и применяем как обычный ввод.
337
328
  const inputType = event instanceof InputEvent ? event.inputType : '';
338
329
  const inserted = input.value.slice(pending.start, input.value.length - (pending.value.length - pending.end));
339
330
  input.value = pending.value;
340
331
  setValue(pending.value);
341
332
  input.setSelectionRange(pending.start, pending.end);
342
- // Цифру из композиции IME применит её фиксация (отменяемый insertText), как в React Aria.
333
+ // Цифру композиции вводит её фиксация (insertText), иначе цифра запишется дважды.
343
334
  if (!inputType.includes('Composition'))
344
335
  applyEdit(inputType, inserted);
345
336
  return;
346
337
  }
347
- // Значение сменили без beforeinput (автозаполнение браузера).
338
+ // Автозаполнение меняет значение без beforeinput.
348
339
  const digits = input.value.replace(/\D/g, '');
349
340
  input.value = mask;
350
341
  setValue(mask);