@snack-uikit/fields 1.0.1-preview-acb79d0b.0 → 1.0.2

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 CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 1.0.2 (2026-09-29)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **FF-9222:** allow leading zeros in date/time field input ([9499820](https://github.com/cloud-ru-tech/snack-uikit/commit/9499820c3a1a0c7130e4ac471aed436062a090e3))
12
+
13
+
14
+
15
+
16
+
17
+ ## 1.0.1 (2026-09-21)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **FF-8693:** adopt workspace and catalog protocols and raise 0.x packages to 1.0.0 ([579989e](https://github.com/cloud-ru-tech/snack-uikit/commit/579989ec78845b4161627bc688c9b1b57abc9dac))
23
+
24
+
25
+
26
+
27
+
6
28
  ## 0.57.5 (2026-09-15)
7
29
 
8
30
  ### Only dependencies have been changed
package/README.md CHANGED
@@ -963,8 +963,8 @@ FieldStepper в основном предназначен для работы с
963
963
  ### Props
964
964
  | name | type | default value | description |
965
965
  |------|------|---------------|-------------|
966
- | __@metadata@6615* | `DecoratorMetadataObject` | - | |
967
- | __@hasInstance@6613* | `(value: any) => boolean` | - | Determines whether the given value inherits from this function if this function was used as a constructor function. A constructor function can control which objects are recognized as its instances by 'instanceof' by overriding this method. |
966
+ | __@metadata@1231* | `DecoratorMetadataObject` | - | |
967
+ | __@hasInstance@1229* | `(value: any) => boolean` | - | Determines whether the given value inherits from this function if this function was used as a constructor function. A constructor function can control which objects are recognized as its instances by 'instanceof' by overriding this method. |
968
968
  | name* | `string` | - | Returns the name of the function. Function names are read-only and can not be changed. |
969
969
  | caller* | `Function` | - | |
970
970
  | arguments* | `any` | - | |
@@ -32,7 +32,6 @@ function useDateField(_ref) {
32
32
  getSlotKeyFromIndex = _ref2.getSlotKeyFromIndex,
33
33
  setFocus = _ref2.setFocus,
34
34
  updateSlot = _ref2.updateSlot,
35
- getSlot = _ref2.getSlot,
36
35
  isLikeDate = _ref2.isLikeDate,
37
36
  isAllSelected = _ref2.isAllSelected,
38
37
  tryToCompleteInput = _ref2.tryToCompleteInput,
@@ -40,7 +39,10 @@ function useDateField(_ref) {
40
39
  parseDate = _ref2.parseDate;
41
40
  const focusSlotKey = (0, react_1.useMemo)(() => slotOrder[0], [slotOrder]);
42
41
  const focusSlotRef = (0, react_1.useRef)(focusSlotKey);
42
+ /** Цифры, введённые в текущий слот с момента его выбора */
43
+ const slotInputRef = (0, react_1.useRef)();
43
44
  const setInputFocus = (0, react_1.useCallback)(focusSlot => {
45
+ slotInputRef.current = undefined;
44
46
  if (!inputRef.current || readonly) {
45
47
  return;
46
48
  }
@@ -103,7 +105,7 @@ function useDateField(_ref) {
103
105
  }
104
106
  }, [slotOrder, tryToCompleteInput, isValidInput, setFocus, getNextSlotKey, updateSlot, slotsPlaceholder]);
105
107
  const handleKeyDown = (0, react_1.useCallback)(e => {
106
- var _a;
108
+ var _a, _b, _c;
107
109
  if (inputRef.current && !readonly) {
108
110
  if (e.key !== 'Tab') {
109
111
  e.preventDefault();
@@ -122,11 +124,9 @@ function useDateField(_ref) {
122
124
  const clickIndex = inputRef.current.selectionStart;
123
125
  const slotKey = getSlotKeyFromIndex(clickIndex);
124
126
  if (slotKey) {
125
- const value = getSlot(slotKey);
126
127
  const _slotsInfo$slotKey2 = slotsInfo[slotKey],
127
128
  max = _slotsInfo$slotKey2.max,
128
129
  min = _slotsInfo$slotKey2.min;
129
- const numberValue = Number(value) || 0;
130
130
  if (e.key === 'ArrowRight') {
131
131
  if (isAllSelected() || slotKey === slotOrder[slotOrder.length - 1]) {
132
132
  inputRef.current.selectionStart = inputRef.current.value.length;
@@ -147,53 +147,43 @@ function useDateField(_ref) {
147
147
  updateSlot(slotKey, (_a = slotsPlaceholder[slotKey]) !== null && _a !== void 0 ? _a : '');
148
148
  }
149
149
  }
150
- if (/^\d+$/.test(e.key)) {
151
- const digit = Number(e.key);
152
- const slotValue = parseInt(numberValue.toString() + e.key, 10) || 0;
153
- const valueLength = slotValue.toString().length;
150
+ if (/^\d$/.test(e.key)) {
154
151
  const maxLength = max.toString().length;
155
- const isTheLastInput = value.match(/^0+$/) && maxLength === 2 && digit === 0;
156
- if (valueLength < maxLength) {
157
- if (slotValue || slotValue >= min) {
158
- updateSlot(slotKey, slotValue);
159
- if (isTheLastInput) checkInputAndGoNext(slotKey);
160
- }
161
- if (slotValue * 10 > max) {
162
- checkInputAndGoNext(slotKey);
163
- }
164
- } else if (valueLength > maxLength) {
165
- if (digit * 10 > max) {
166
- updateSlot(slotKey, e.key);
167
- checkInputAndGoNext(slotKey);
168
- } else if (digit || digit >= min) {
169
- updateSlot(slotKey, e.key);
170
- }
152
+ const prevDigits = ((_b = slotInputRef.current) === null || _b === void 0 ? void 0 : _b.slotKey) === slotKey ? slotInputRef.current.digits : '';
153
+ let digits = prevDigits + e.key;
154
+ // при выходе за границы слота начинаем ввод заново с текущей цифры
155
+ if (Number(digits) > max || digits.length === maxLength && Number(digits) < min) {
156
+ digits = e.key;
157
+ }
158
+ const slotValue = Number(digits);
159
+ const isSlotCompleted = digits.length === maxLength || slotValue * 10 > max;
160
+ if (isSlotCompleted) {
161
+ slotInputRef.current = undefined;
162
+ updateSlot(slotKey, slotValue);
163
+ checkInputAndGoNext(slotKey);
171
164
  } else {
172
- if (slotValue <= max) {
173
- updateSlot(slotKey, slotValue);
174
- checkInputAndGoNext(slotKey);
175
- } else {
176
- if (digit * 10 > max) {
177
- updateSlot(slotKey, e.key);
178
- checkInputAndGoNext(slotKey);
179
- } else if (digit || digit >= min) {
180
- updateSlot(slotKey, e.key);
181
- }
182
- }
165
+ slotInputRef.current = {
166
+ slotKey,
167
+ digits
168
+ };
169
+ updateSlot(slotKey, slotValue < min ? digits + ((_c = slotsPlaceholder[slotKey]) !== null && _c !== void 0 ? _c : '').slice(digits.length) : slotValue);
183
170
  }
171
+ } else {
172
+ slotInputRef.current = undefined;
184
173
  }
185
174
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
186
175
  // @ts-ignore
187
176
  onChange === null || onChange === void 0 ? void 0 : onChange(parseDate(isLikeDate() ? inputRef.current.value : ''));
188
177
  }
189
178
  }
190
- }, [inputRef, readonly, getSlotKeyFromIndex, setIsOpen, tryToCompleteInput, getSlot, slotsInfo, parseDate, isLikeDate, onChange, isAllSelected, slotOrder, setFocus, getNextSlotKey, getPrevSlotKey, mask, focusSlotKey, updateSlot, slotsPlaceholder, checkInputAndGoNext]);
179
+ }, [inputRef, readonly, getSlotKeyFromIndex, setIsOpen, tryToCompleteInput, slotsInfo, parseDate, isLikeDate, onChange, isAllSelected, slotOrder, setFocus, getNextSlotKey, getPrevSlotKey, mask, focusSlotKey, updateSlot, slotsPlaceholder, checkInputAndGoNext]);
191
180
  const handleBlur = (0, react_1.useCallback)(() => {
192
181
  var _a;
193
182
  if (!readonly && ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.value) === mask) {
194
183
  inputRef.current.value = '';
195
184
  }
196
185
  focusSlotRef.current = focusSlotKey;
186
+ slotInputRef.current = undefined;
197
187
  }, [inputRef, mask, readonly, focusSlotKey]);
198
188
  return {
199
189
  handleKeyDown,
@@ -9,10 +9,13 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
9
9
  const mask = MASK[dateTimeMode][locale.baseName] || MASK[dateTimeMode][DEFAULT_LOCALE.baseName];
10
10
  const slotsPlaceholder = SLOTS_PLACEHOLDER[dateTimeMode][locale.baseName] || SLOTS_PLACEHOLDER[dateTimeMode][DEFAULT_LOCALE.baseName];
11
11
  const slotOrder = SLOT_ORDER[dateTimeMode];
12
- const { getNextSlotKey, getPrevSlotKey, getSlotKeyFromIndex, setFocus, updateSlot, getSlot, isLikeDate, isAllSelected, tryToCompleteInput, isValidInput, parseDate, } = useDateFieldHelpersForMode({ inputRef, mode: dateTimeMode });
12
+ const { getNextSlotKey, getPrevSlotKey, getSlotKeyFromIndex, setFocus, updateSlot, isLikeDate, isAllSelected, tryToCompleteInput, isValidInput, parseDate, } = useDateFieldHelpersForMode({ inputRef, mode: dateTimeMode });
13
13
  const focusSlotKey = useMemo(() => slotOrder[0], [slotOrder]);
14
14
  const focusSlotRef = useRef(focusSlotKey);
15
+ /** Цифры, введённые в текущий слот с момента его выбора */
16
+ const slotInputRef = useRef();
15
17
  const setInputFocus = useCallback((focusSlot) => {
18
+ slotInputRef.current = undefined;
16
19
  if (!inputRef.current || readonly) {
17
20
  return;
18
21
  }
@@ -73,7 +76,7 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
73
76
  }
74
77
  }, [slotOrder, tryToCompleteInput, isValidInput, setFocus, getNextSlotKey, updateSlot, slotsPlaceholder]);
75
78
  const handleKeyDown = useCallback((e) => {
76
- var _a;
79
+ var _a, _b, _c;
77
80
  if (inputRef.current && !readonly) {
78
81
  if (e.key !== 'Tab') {
79
82
  e.preventDefault();
@@ -92,9 +95,7 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
92
95
  const clickIndex = inputRef.current.selectionStart;
93
96
  const slotKey = getSlotKeyFromIndex(clickIndex);
94
97
  if (slotKey) {
95
- const value = getSlot(slotKey);
96
98
  const { max, min } = slotsInfo[slotKey];
97
- const numberValue = Number(value) || 0;
98
99
  if (e.key === 'ArrowRight') {
99
100
  if (isAllSelected() || slotKey === slotOrder[slotOrder.length - 1]) {
100
101
  inputRef.current.selectionStart = inputRef.current.value.length;
@@ -116,47 +117,29 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
116
117
  updateSlot(slotKey, (_a = slotsPlaceholder[slotKey]) !== null && _a !== void 0 ? _a : '');
117
118
  }
118
119
  }
119
- if (/^\d+$/.test(e.key)) {
120
- const digit = Number(e.key);
121
- const slotValue = parseInt(numberValue.toString() + e.key, 10) || 0;
122
- const valueLength = slotValue.toString().length;
120
+ if (/^\d$/.test(e.key)) {
123
121
  const maxLength = max.toString().length;
124
- const isTheLastInput = value.match(/^0+$/) && maxLength === 2 && digit === 0;
125
- if (valueLength < maxLength) {
126
- if (slotValue || slotValue >= min) {
127
- updateSlot(slotKey, slotValue);
128
- if (isTheLastInput)
129
- checkInputAndGoNext(slotKey);
130
- }
131
- if (slotValue * 10 > max) {
132
- checkInputAndGoNext(slotKey);
133
- }
122
+ const prevDigits = ((_b = slotInputRef.current) === null || _b === void 0 ? void 0 : _b.slotKey) === slotKey ? slotInputRef.current.digits : '';
123
+ let digits = prevDigits + e.key;
124
+ // при выходе за границы слота начинаем ввод заново с текущей цифры
125
+ if (Number(digits) > max || (digits.length === maxLength && Number(digits) < min)) {
126
+ digits = e.key;
134
127
  }
135
- else if (valueLength > maxLength) {
136
- if (digit * 10 > max) {
137
- updateSlot(slotKey, e.key);
138
- checkInputAndGoNext(slotKey);
139
- }
140
- else if (digit || digit >= min) {
141
- updateSlot(slotKey, e.key);
142
- }
128
+ const slotValue = Number(digits);
129
+ const isSlotCompleted = digits.length === maxLength || slotValue * 10 > max;
130
+ if (isSlotCompleted) {
131
+ slotInputRef.current = undefined;
132
+ updateSlot(slotKey, slotValue);
133
+ checkInputAndGoNext(slotKey);
143
134
  }
144
135
  else {
145
- if (slotValue <= max) {
146
- updateSlot(slotKey, slotValue);
147
- checkInputAndGoNext(slotKey);
148
- }
149
- else {
150
- if (digit * 10 > max) {
151
- updateSlot(slotKey, e.key);
152
- checkInputAndGoNext(slotKey);
153
- }
154
- else if (digit || digit >= min) {
155
- updateSlot(slotKey, e.key);
156
- }
157
- }
136
+ slotInputRef.current = { slotKey, digits };
137
+ updateSlot(slotKey, slotValue < min ? digits + ((_c = slotsPlaceholder[slotKey]) !== null && _c !== void 0 ? _c : '').slice(digits.length) : slotValue);
158
138
  }
159
139
  }
140
+ else {
141
+ slotInputRef.current = undefined;
142
+ }
160
143
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
161
144
  // @ts-ignore
162
145
  onChange === null || onChange === void 0 ? void 0 : onChange(parseDate(isLikeDate() ? inputRef.current.value : ''));
@@ -168,7 +151,6 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
168
151
  getSlotKeyFromIndex,
169
152
  setIsOpen,
170
153
  tryToCompleteInput,
171
- getSlot,
172
154
  slotsInfo,
173
155
  parseDate,
174
156
  isLikeDate,
@@ -190,6 +172,7 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
190
172
  inputRef.current.value = '';
191
173
  }
192
174
  focusSlotRef.current = focusSlotKey;
175
+ slotInputRef.current = undefined;
193
176
  }, [inputRef, mask, readonly, focusSlotKey]);
194
177
  return {
195
178
  handleKeyDown,
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Fields",
7
- "version": "1.0.1-preview-acb79d0b.0",
7
+ "version": "1.0.2",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -42,21 +42,21 @@
42
42
  "merge-refs": "^1.3.0",
43
43
  "react-textarea-autosize": "^8.5.3",
44
44
  "uncontrollable": "^8.0.4",
45
- "@snack-uikit/button": "1.0.1-preview-acb79d0b.0",
46
- "@snack-uikit/calendar": "1.0.1-preview-acb79d0b.0",
47
- "@snack-uikit/color-picker": "1.0.1-preview-acb79d0b.0",
48
- "@snack-uikit/divider": "3.2.13-preview-acb79d0b.0",
49
- "@snack-uikit/dropdown": "1.0.1-preview-acb79d0b.0",
50
- "@snack-uikit/icons": "1.0.1-preview-acb79d0b.0",
51
- "@snack-uikit/scroll": "1.0.1-preview-acb79d0b.0",
52
- "@snack-uikit/list": "1.0.1-preview-acb79d0b.0",
53
- "@snack-uikit/input-private": "4.8.11-preview-acb79d0b.0",
54
- "@snack-uikit/skeleton": "1.0.1-preview-acb79d0b.0",
55
- "@snack-uikit/slider": "1.0.1-preview-acb79d0b.0",
56
- "@snack-uikit/truncate-string": "1.0.1-preview-acb79d0b.0",
57
- "@snack-uikit/tooltip": "1.0.1-preview-acb79d0b.0",
58
- "@snack-uikit/tag": "1.0.1-preview-acb79d0b.0",
59
- "@snack-uikit/utils": "4.0.3-preview-acb79d0b.0"
45
+ "@snack-uikit/color-picker": "^1.0.1",
46
+ "@snack-uikit/divider": "^4.0.0",
47
+ "@snack-uikit/button": "^1.0.1",
48
+ "@snack-uikit/calendar": "^1.0.2",
49
+ "@snack-uikit/icons": "^1.0.1",
50
+ "@snack-uikit/dropdown": "^1.0.1",
51
+ "@snack-uikit/scroll": "^1.0.1",
52
+ "@snack-uikit/input-private": "^5.0.0",
53
+ "@snack-uikit/list": "^1.0.1",
54
+ "@snack-uikit/slider": "^1.0.1",
55
+ "@snack-uikit/tag": "^1.0.1",
56
+ "@snack-uikit/tooltip": "^1.0.1",
57
+ "@snack-uikit/skeleton": "^1.0.1",
58
+ "@snack-uikit/truncate-string": "^1.0.1",
59
+ "@snack-uikit/utils": "^5.0.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/fuzzy-search": "2.1.5",
@@ -56,7 +56,6 @@ export function useDateField({
56
56
  getSlotKeyFromIndex,
57
57
  setFocus,
58
58
  updateSlot,
59
- getSlot,
60
59
  isLikeDate,
61
60
  isAllSelected,
62
61
  tryToCompleteInput,
@@ -66,9 +65,13 @@ export function useDateField({
66
65
 
67
66
  const focusSlotKey = useMemo(() => slotOrder[0], [slotOrder]);
68
67
  const focusSlotRef = useRef<FocusSlot>(focusSlotKey);
68
+ /** Цифры, введённые в текущий слот с момента его выбора */
69
+ const slotInputRef = useRef<{ slotKey: SlotKey; digits: string }>();
69
70
 
70
71
  const setInputFocus = useCallback(
71
72
  (focusSlot?: FocusSlot) => {
73
+ slotInputRef.current = undefined;
74
+
72
75
  if (!inputRef.current || readonly) {
73
76
  return;
74
77
  }
@@ -169,11 +172,8 @@ export function useDateField({
169
172
  const slotKey = getSlotKeyFromIndex(clickIndex);
170
173
 
171
174
  if (slotKey) {
172
- const value = getSlot(slotKey);
173
175
  const { max, min } = slotsInfo[slotKey];
174
176
 
175
- const numberValue = Number(value) || 0;
176
-
177
177
  if (e.key === 'ArrowRight') {
178
178
  if (isAllSelected() || slotKey === slotOrder[slotOrder.length - 1]) {
179
179
  inputRef.current.selectionStart = inputRef.current.value.length;
@@ -197,43 +197,32 @@ export function useDateField({
197
197
  }
198
198
  }
199
199
 
200
- if (/^\d+$/.test(e.key)) {
201
- const digit = Number(e.key);
202
- const slotValue = parseInt(numberValue.toString() + e.key, 10) || 0;
203
-
204
- const valueLength = slotValue.toString().length;
200
+ if (/^\d$/.test(e.key)) {
205
201
  const maxLength = max.toString().length;
206
- const isTheLastInput = value.match(/^0+$/) && maxLength === 2 && digit === 0;
207
-
208
- if (valueLength < maxLength) {
209
- if (slotValue || slotValue >= min) {
210
- updateSlot(slotKey, slotValue);
211
- if (isTheLastInput) checkInputAndGoNext(slotKey);
212
- }
213
-
214
- if (slotValue * 10 > max) {
215
- checkInputAndGoNext(slotKey);
216
- }
217
- } else if (valueLength > maxLength) {
218
- if (digit * 10 > max) {
219
- updateSlot(slotKey, e.key);
220
- checkInputAndGoNext(slotKey);
221
- } else if (digit || digit >= min) {
222
- updateSlot(slotKey, e.key);
223
- }
202
+ const prevDigits = slotInputRef.current?.slotKey === slotKey ? slotInputRef.current.digits : '';
203
+ let digits = prevDigits + e.key;
204
+
205
+ // при выходе за границы слота начинаем ввод заново с текущей цифры
206
+ if (Number(digits) > max || (digits.length === maxLength && Number(digits) < min)) {
207
+ digits = e.key;
208
+ }
209
+
210
+ const slotValue = Number(digits);
211
+ const isSlotCompleted = digits.length === maxLength || slotValue * 10 > max;
212
+
213
+ if (isSlotCompleted) {
214
+ slotInputRef.current = undefined;
215
+ updateSlot(slotKey, slotValue);
216
+ checkInputAndGoNext(slotKey);
224
217
  } else {
225
- if (slotValue <= max) {
226
- updateSlot(slotKey, slotValue);
227
- checkInputAndGoNext(slotKey);
228
- } else {
229
- if (digit * 10 > max) {
230
- updateSlot(slotKey, e.key);
231
- checkInputAndGoNext(slotKey);
232
- } else if (digit || digit >= min) {
233
- updateSlot(slotKey, e.key);
234
- }
235
- }
218
+ slotInputRef.current = { slotKey, digits };
219
+ updateSlot(
220
+ slotKey,
221
+ slotValue < min ? digits + (slotsPlaceholder[slotKey] ?? '').slice(digits.length) : slotValue,
222
+ );
236
223
  }
224
+ } else {
225
+ slotInputRef.current = undefined;
237
226
  }
238
227
 
239
228
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -248,7 +237,6 @@ export function useDateField({
248
237
  getSlotKeyFromIndex,
249
238
  setIsOpen,
250
239
  tryToCompleteInput,
251
- getSlot,
252
240
  slotsInfo,
253
241
  parseDate,
254
242
  isLikeDate,
@@ -271,6 +259,7 @@ export function useDateField({
271
259
  inputRef.current.value = '';
272
260
  }
273
261
  focusSlotRef.current = focusSlotKey;
262
+ slotInputRef.current = undefined;
274
263
  }, [inputRef, mask, readonly, focusSlotKey]);
275
264
 
276
265
  return {