@skbkontur/react-ui 6.1.7-1c902.0 → 6.1.7-c386f.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.
Files changed (40) hide show
  1. package/components/MenuItem/MenuItem.d.ts +1 -1
  2. package/components/MenuItem/MenuItem.js +0 -1
  3. package/components/MenuItem/MenuItem.js.map +1 -1
  4. package/components/MenuItem/MenuItem.styles.d.ts +0 -1
  5. package/components/MenuItem/MenuItem.styles.js +7 -10
  6. package/components/MenuItem/MenuItem.styles.js.map +1 -1
  7. package/components/TimePicker/TimePicker.d.ts +21 -5
  8. package/components/TimePicker/TimePicker.js +169 -78
  9. package/components/TimePicker/TimePicker.js.map +1 -1
  10. package/components/TimePicker/TimePicker.styles.d.ts +2 -0
  11. package/components/TimePicker/TimePicker.styles.js +7 -1
  12. package/components/TimePicker/TimePicker.styles.js.map +1 -1
  13. package/components/TimePicker/TimePickerItems.js +27 -20
  14. package/components/TimePicker/TimePickerItems.js.map +1 -1
  15. package/components/TimePicker/TimePickerMobilePopup.js +2 -2
  16. package/components/TimePicker/TimePickerMobilePopup.js.map +1 -1
  17. package/components/TimePicker/TimePickerPopup.js +2 -2
  18. package/components/TimePicker/TimePickerPopup.js.map +1 -1
  19. package/components/TimePicker/helpers/TimePicker.constants.d.ts +5 -0
  20. package/components/TimePicker/helpers/TimePicker.constants.js +5 -0
  21. package/components/TimePicker/helpers/TimePicker.constants.js.map +1 -1
  22. package/components/TimePicker/helpers/TimePicker.shared.d.ts +9 -0
  23. package/components/TimePicker/helpers/TimePicker.shared.js +17 -0
  24. package/components/TimePicker/helpers/TimePicker.shared.js.map +1 -1
  25. package/components/TimePicker/helpers/TimePicker.value.d.ts +21 -0
  26. package/components/TimePicker/helpers/TimePicker.value.js +67 -5
  27. package/components/TimePicker/helpers/TimePicker.value.js.map +1 -1
  28. package/components/TimePicker/hooks/useTimePickerDropdown.d.ts +6 -1
  29. package/components/TimePicker/hooks/useTimePickerDropdown.js +7 -9
  30. package/components/TimePicker/hooks/useTimePickerDropdown.js.map +1 -1
  31. package/components/TimePicker/hooks/useTimePickerSource.d.ts +27 -0
  32. package/components/TimePicker/hooks/useTimePickerSource.js +59 -0
  33. package/components/TimePicker/hooks/useTimePickerSource.js.map +1 -0
  34. package/components/TimePicker/hooks/useTimePickerValue.d.ts +6 -0
  35. package/components/TimePicker/hooks/useTimePickerValue.js +27 -6
  36. package/components/TimePicker/hooks/useTimePickerValue.js.map +1 -1
  37. package/components/TimePicker/index.d.ts +2 -0
  38. package/components/TimePicker/index.js +1 -0
  39. package/components/TimePicker/index.js.map +1 -1
  40. package/package.json +1 -1
@@ -37,26 +37,30 @@ import { DIGIT_REGEXP, EMPTY_SEGMENT, EMPTY_VALUE, TimePickerDataTids } from './
37
37
  import { commitTimeSegmentOnLeave, deleteTimeSegmentDigit, formatDigitToTimeSegment, shiftTimeSegmentValue, } from './helpers/TimePicker.editing.js';
38
38
  import { getTimePickerPopupMaxHeight } from './helpers/TimePicker.layout.js';
39
39
  import { getNextTimeSegment, getPreviousTimeSegment } from './helpers/TimePicker.selection.js';
40
- import { isTimeItem, isTimeItemObject, isTimeMenuItem, timeValueToItem, } from './helpers/TimePicker.shared.js';
40
+ import { isNavigableMenuElement, isTimeItem, isTimeItemObject, isTimeMenuItem, timeValueToItem, } from './helpers/TimePicker.shared.js';
41
41
  import { dedupeTimeItems, filterTimeItems, getEmptyDisplayValue, getTimeFilterQuery, getTimeValueString, isTimeDisplayEmpty, normalizeTimeValue, parsePastedTimeValue, replaceTimeSegment, resolveTimeItems, } from './helpers/TimePicker.value.js';
42
42
  import { validateTimePicker } from './helpers/validateTimePicker.js';
43
43
  import { useTimePickerDropdown } from './hooks/useTimePickerDropdown.js';
44
44
  import { useTimePickerSelection } from './hooks/useTimePickerSelection.js';
45
+ import { useTimePickerSource } from './hooks/useTimePickerSource.js';
45
46
  import { useTimePickerValue } from './hooks/useTimePickerValue.js';
46
47
  import { TimePickerLocaleHelper } from './locale/index.js';
47
48
  import { getStyles } from './TimePicker.styles.js';
48
49
  import { TimePickerMobilePopup } from './TimePickerMobilePopup.js';
49
50
  import { TimePickerPopup } from './TimePickerPopup.js';
51
+ /** Пустой список для клавиатуры: не создает новую ссылку на каждый рендер. */
52
+ var EMPTY_MENU_ITEMS = [];
50
53
  /** Поле с временем помогает пользователю быстро и удобно указать время в правильном формате.
51
54
  * В поле можно ввести время с клавиатуры либо выбрать из выпадающего списка,
52
55
  * передав массив или функцию в проп `source`.
53
56
  */
54
57
  export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (props, ref) {
55
- var disabled = props.disabled, _a = props.useMobileNativeTimePicker, useMobileNativeTimePicker = _a === void 0 ? false : _a, _b = props.format, format = _b === void 0 ? 'HH:mm' : _b, _c = props.size, size = _c === void 0 ? 'small' : _c, source = props.source, renderItem = props.renderItem, menuPos = props.menuPos, menuAlign = props.menuAlign, menuWidth = props.menuWidth, minTime = props.minTime, maxTime = props.maxTime, rightIcon = props.rightIcon, suffix = props.suffix, corners = props.corners, value = props.value, valueToItem = props.valueToItem, onValueChange = props.onValueChange, onInputValueChange = props.onInputValueChange, onUnexpectedInput = props.onUnexpectedInput, onFocus = props.onFocus, onBlur = props.onBlur, onClick = props.onClick, onKeyDown = props.onKeyDown, onPaste = props.onPaste, inputProps = __rest(props, ["disabled", "useMobileNativeTimePicker", "format", "size", "source", "renderItem", "menuPos", "menuAlign", "menuWidth", "minTime", "maxTime", "rightIcon", "suffix", "corners", "value", "valueToItem", "onValueChange", "onInputValueChange", "onUnexpectedInput", "onFocus", "onBlur", "onClick", "onKeyDown", "onPaste"]);
58
+ var _a;
59
+ var disabled = props.disabled, _b = props.useMobileNativeTimePicker, useMobileNativeTimePicker = _b === void 0 ? false : _b, _c = props.format, format = _c === void 0 ? 'HH:mm' : _c, _d = props.size, size = _d === void 0 ? 'small' : _d, source = props.source, renderItem = props.renderItem, menuPos = props.menuPos, menuAlign = props.menuAlign, menuWidth = props.menuWidth, minTime = props.minTime, maxTime = props.maxTime, rightIcon = props.rightIcon, suffix = props.suffix, corners = props.corners, value = props.value, valueToItem = props.valueToItem, onValueChange = props.onValueChange, onInputValueChange = props.onInputValueChange, onUnexpectedInput = props.onUnexpectedInput, onFocus = props.onFocus, onBlur = props.onBlur, onClick = props.onClick, onKeyDown = props.onKeyDown, onPaste = props.onPaste, inputProps = __rest(props, ["disabled", "useMobileNativeTimePicker", "format", "size", "source", "renderItem", "menuPos", "menuAlign", "menuWidth", "minTime", "maxTime", "rightIcon", "suffix", "corners", "value", "valueToItem", "onValueChange", "onInputValueChange", "onUnexpectedInput", "onFocus", "onBlur", "onClick", "onKeyDown", "onPaste"]);
56
60
  var theme = useContext(ThemeContext);
57
61
  var styles = useStyles(getStyles);
58
62
  var locale = useLocaleForControl('TimePicker', TimePickerLocaleHelper);
59
- var _d = useState(false), isInputFocused = _d[0], setIsInputFocused = _d[1];
63
+ var _e = useState(false), isInputFocused = _e[0], setIsInputFocused = _e[1];
60
64
  var inputRef = useRef(null);
61
65
  var mobileInputRef = useRef(null);
62
66
  var nativeInputRef = useRef(null);
@@ -66,28 +70,30 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
66
70
  var isMouseDownOnFragmentRef = useRef(false);
67
71
  var mouseDownSegmentRef = useRef(null);
68
72
  var mobileBlurEventRef = useRef(null);
73
+ var shouldSkipOpenOnFocusRef = useRef(false);
69
74
  var popupIdRef = useRef(TimePickerDataTids.popup + getRandomID());
70
- var _e = useState(false), isMobileDevice = _e[0], setIsMobileDevice = _e[1];
71
- var _f = useState(false), canUseMobileNativeTimePicker = _f[0], setCanUseMobileNativeTimePicker = _f[1];
75
+ var _f = useState(false), isMobileDevice = _f[0], setIsMobileDevice = _f[1];
76
+ var _g = useState(false), canUseMobileNativeTimePicker = _g[0], setCanUseMobileNativeTimePicker = _g[1];
72
77
  var isSourceFunction = typeof source === 'function';
73
- var sourceItems = useMemo(function () { return (Array.isArray(source) ? source : []); }, [source]);
74
- var hasDropdown = isSourceFunction || sourceItems.length > 0;
75
- var _g = useState([]), fetchedItems = _g[0], setFetchedItems = _g[1];
76
- var _h = useState(null), fetchedItemsQuery = _h[0], setFetchedItemsQuery = _h[1];
77
- var _j = useState(false), isLoadingItems = _j[0], setIsLoadingItems = _j[1];
78
- var _k = useState(false), isEditedAfterOpen = _k[0], setIsEditedAfterOpen = _k[1];
79
- var sourceValueItems = isSourceFunction ? fetchedItems : sourceItems;
78
+ var hasDropdown = isSourceFunction || (Array.isArray(source) && source.length > 0);
79
+ var _h = useState(false), isDropdownOpened = _h[0], setIsDropdownOpened = _h[1];
80
+ var _j = useState(false), isEditedAfterOpen = _j[0], setIsEditedAfterOpen = _j[1];
81
+ /**
82
+ * Форма элементов функции-источника: последняя, о которой компонент знает.
83
+ * Форма запоминается, потому что элементы функции очищаются при закрытии меню,
84
+ * а коммит значения после закрытия должен отдать наружу элемент той же формы.
85
+ * Для массива форма всегда считается по текущим элементам — запоминать нечего.
86
+ */
87
+ var lastSourceItemShapeRef = useRef(null);
80
88
  /**
81
89
  * Форма элемента, который компонент собирает для времени без элемента.
82
90
  * Повторяет форму элементов источника, а если их нет — форму внешнего `value`,
83
91
  * чтобы наружу уходило то же, что пришло внутрь.
92
+ * Значение читается при коммите, поэтому обновляется в эффекте: так его не меняет отброшенный рендер.
84
93
  */
85
- var shouldBuildItemAsObject = sourceValueItems.some(function (item) { return isTimeItem(item) && isTimeItemObject(item); }) ||
86
- (value !== null && value !== undefined && isTimeItemObject(value));
94
+ var shouldBuildItemAsObjectRef = useRef(false);
87
95
  var valueToItemRef = useRef(valueToItem);
88
96
  valueToItemRef.current = valueToItem;
89
- var shouldBuildItemAsObjectRef = useRef(shouldBuildItemAsObject);
90
- shouldBuildItemAsObjectRef.current = shouldBuildItemAsObject;
91
97
  var handleCommitValue = useCallback(function (committedValue, item) {
92
98
  var _a, _b;
93
99
  if (!onValueChange) {
@@ -100,14 +106,23 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
100
106
  var nextItem = (_b = item !== null && item !== void 0 ? item : (_a = valueToItemRef.current) === null || _a === void 0 ? void 0 : _a.call(valueToItemRef, committedValue)) !== null && _b !== void 0 ? _b : timeValueToItem(committedValue, shouldBuildItemAsObjectRef.current);
101
107
  onValueChange(nextItem, committedValue);
102
108
  }, [onValueChange]);
103
- var _l = useTimePickerValue({
109
+ var _k = useTimePickerValue({
104
110
  isInputFocused: isInputFocused,
105
111
  format: format,
106
112
  externalValue: getTimeValueString(value),
113
+ isValueControlled: value !== undefined,
107
114
  onCommitValue: handleCommitValue,
108
115
  onInputValueChange: onInputValueChange,
109
- }), editingValue = _l.editingValue, editingValueRef = _l.editingValueRef, updateEditingValue = _l.updateEditingValue, commitEditingValue = _l.commitEditingValue, commitValue = _l.commitValue, commitItem = _l.commitItem;
116
+ }), editingValue = _k.editingValue, editingValueRef = _k.editingValueRef, updateEditingValue = _k.updateEditingValue, commitEditingValue = _k.commitEditingValue, commitValue = _k.commitValue, commitItem = _k.commitItem;
110
117
  var filterQuery = isEditedAfterOpen ? getTimeFilterQuery(editingValue, format) : EMPTY_VALUE;
118
+ var _l = useTimePickerSource({ source: source, format: format, isDropdownOpened: isDropdownOpened, filterQuery: filterQuery }), sourceValueItems = _l.items, fetchedItemsQuery = _l.fetchedQuery, isLoadingItems = _l.isLoading;
119
+ useEffect(function () {
120
+ if (!isSourceFunction || sourceValueItems.some(isTimeItem)) {
121
+ lastSourceItemShapeRef.current = sourceValueItems.some(function (item) { return isTimeItem(item) && isTimeItemObject(item); });
122
+ }
123
+ shouldBuildItemAsObjectRef.current =
124
+ lastSourceItemShapeRef.current === true || (value !== null && value !== undefined && isTimeItemObject(value));
125
+ }, [isSourceFunction, sourceValueItems, value]);
111
126
  var filteredItems = useMemo(function () { return filterTimeItems(dedupeTimeItems(sourceValueItems, format), filterQuery, format); }, [filterQuery, format, sourceValueItems]);
112
127
  /**
113
128
  * Элементы для отрисовки: время и подпись достаются из элемента,
@@ -131,7 +146,17 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
131
146
  }
132
147
  }
133
148
  var canUseMobileDropdownTimePicker = hasDropdown && isMobileDevice && !canUseMobileNativeTimePicker;
134
- var hasDropdownContent = resolvedItems.length > 0 || isLoadingItems;
149
+ /**
150
+ * Меню с одними лишь заголовками и разделителями показывать не о чем,
151
+ * поэтому при активном фильтре нужен хотя бы один подошедший элемент со временем.
152
+ */
153
+ var hasDropdownContent = isLoadingItems || (filterQuery === EMPTY_VALUE ? resolvedItems.length > 0 : resolvedItems.some(isTimeMenuItem));
154
+ /**
155
+ * Элементы, с которыми работает клавиатура.
156
+ * Пока меню скрыто, их нет: иначе стрелки ходили бы по невидимому списку,
157
+ * а Enter выбирал бы невидимый элемент вместо коммита введенного времени.
158
+ */
159
+ var menuItems = hasDropdownContent ? resolvedItems : EMPTY_MENU_ITEMS;
135
160
  var inputRightIcon = isLoadingItems && resolvedItems.length > 0 ? (React.createElement("span", { "data-tid": TimePickerDataTids.inputLoading },
136
161
  React.createElement(LoadingIcon, { size: size }))) : (rightIcon);
137
162
  var displayValue = editingValue;
@@ -147,11 +172,11 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
147
172
  mouseDownSegmentRef.current = null;
148
173
  }, []);
149
174
  var updateEditingValueAfterUserEdit = useCallback(function (nextEditingValue) {
150
- if (editingValueRef.current !== nextEditingValue) {
175
+ if (hasDropdown && editingValueRef.current !== nextEditingValue) {
151
176
  setIsEditedAfterOpen(true);
152
177
  }
153
178
  updateEditingValue(nextEditingValue);
154
- }, [editingValueRef, updateEditingValue]);
179
+ }, [editingValueRef, hasDropdown, updateEditingValue]);
155
180
  var normalizeCurrentSegmentIfNeeded = useCallback(function (segment) {
156
181
  var nextDisplayValue = commitTimeSegmentOnLeave(editingValue === EMPTY_VALUE ? getEmptyDisplayValue(format) : editingValue, segment, format);
157
182
  updateEditingValueAfterUserEdit(nextDisplayValue);
@@ -171,46 +196,28 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
171
196
  var _m = useTimePickerDropdown({
172
197
  disabled: disabled,
173
198
  hasDropdown: hasDropdown,
174
- items: resolvedItems,
199
+ items: menuItems,
175
200
  selectedItemIndex: selectedItemIndex >= 0 ? selectedItemIndex : null,
176
201
  autoHighlightKey: autoHighlightKey,
177
- }), isDropdownOpened = _m.isDropdownOpened, highlightedItemIndex = _m.highlightedItemIndex, openDropdown = _m.openDropdown, closeDropdown = _m.closeDropdown, resetHighlightedItem = _m.resetHighlightedItem, tryNavigateItems = _m.tryNavigateItems;
178
- var sourceRef = useRef(source);
179
- sourceRef.current = source;
180
- useEffect(function () {
181
- var currentSource = sourceRef.current;
182
- if (typeof currentSource !== 'function' || !isDropdownOpened) {
183
- setIsLoadingItems(false);
184
- return;
185
- }
186
- var cancelled = false;
187
- setIsLoadingItems(true);
188
- new Promise(function (resolve) { return resolve(currentSource(filterQuery)); }).then(function (nextItems) {
189
- if (!cancelled) {
190
- setFetchedItems(nextItems);
191
- setFetchedItemsQuery(filterQuery);
192
- setIsLoadingItems(false);
193
- }
194
- }, function () {
195
- if (!cancelled) {
196
- setFetchedItems([]);
197
- setFetchedItemsQuery(filterQuery);
198
- setIsLoadingItems(false);
199
- }
200
- });
201
- return function () {
202
- cancelled = true;
203
- };
204
- }, [filterQuery, isDropdownOpened, isSourceFunction]);
202
+ isDropdownOpened: isDropdownOpened,
203
+ setIsDropdownOpened: setIsDropdownOpened,
204
+ }), highlightedItemIndex = _m.highlightedItemIndex, openDropdown = _m.openDropdown, closeDropdown = _m.closeDropdown, resetHighlightedItem = _m.resetHighlightedItem, tryNavigateItems = _m.tryNavigateItems;
205
+ /**
206
+ * Фильтрация начинается только после правки значения при открытом меню.
207
+ * Состояние живет ровно столько, сколько открыто меню: правка, которая заново открывает меню,
208
+ * сохраняет свой запрос, а правка при закрытом меню — например стрелками — на список не влияет.
209
+ */
205
210
  useEffect(function () {
206
- setIsEditedAfterOpen(false);
207
- if (!isDropdownOpened && isSourceFunction) {
208
- setFetchedItems([]);
209
- setFetchedItemsQuery(null);
211
+ if (!isDropdownOpened && isEditedAfterOpen) {
212
+ setIsEditedAfterOpen(false);
210
213
  }
211
- }, [isDropdownOpened, isSourceFunction]);
214
+ }, [isDropdownOpened, isEditedAfterOpen]);
212
215
  var popupId = popupIdRef.current;
213
- var activeDescendantId = hasDropdown && highlightedItemIndex !== null ? "".concat(popupId, "-item-").concat(highlightedItemIndex) : undefined;
216
+ var highlightedMenuItem = highlightedItemIndex === null ? undefined : menuItems[highlightedItemIndex];
217
+ /** Кастомный `MenuItem` может принести свой `id` — тогда указывать нужно именно на него. */
218
+ var activeDescendantId = hasDropdown && highlightedItemIndex !== null && highlightedMenuItem
219
+ ? ((_a = (isNavigableMenuElement(highlightedMenuItem) ? highlightedMenuItem.props.id : undefined)) !== null && _a !== void 0 ? _a : "".concat(popupId, "-item-").concat(highlightedItemIndex))
220
+ : undefined;
214
221
  var isMobileDropdownActive = canUseMobileDropdownTimePicker && isDropdownOpened;
215
222
  var getActiveInput = useCallback(function () { return (canUseMobileDropdownTimePicker && isDropdownOpened ? mobileInputRef.current : inputRef.current); }, [canUseMobileDropdownTimePicker, isDropdownOpened]);
216
223
  var _o = useTimePickerSelection({
@@ -226,14 +233,24 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
226
233
  /**
227
234
  * Сообщает о некорректном вводе: нажатой клавише или вставленном значении, которое не может быть временем.
228
235
  * Если обработчик не задан, поле вспыхивает само.
236
+ * Обработчик может подставить значение вместо некорректного ввода:
237
+ * элемент коммитится как есть, `null` очищает поле, а `undefined` оставляет значение прежним.
229
238
  */
230
239
  var handleUnexpectedInput = useCallback(function (unexpectedValue) {
231
- if (onUnexpectedInput) {
232
- onUnexpectedInput(unexpectedValue, blinkInputValue);
240
+ if (!onUnexpectedInput) {
241
+ blinkInputValue();
242
+ return;
243
+ }
244
+ var returnedValue = onUnexpectedInput(unexpectedValue, blinkInputValue);
245
+ if (returnedValue === undefined) {
246
+ return;
247
+ }
248
+ if (returnedValue === null) {
249
+ commitValue(EMPTY_VALUE);
233
250
  return;
234
251
  }
235
- blinkInputValue();
236
- }, [blinkInputValue, onUnexpectedInput]);
252
+ commitItem(returnedValue);
253
+ }, [blinkInputValue, commitItem, commitValue, onUnexpectedInput]);
237
254
  var closeOpenedDropdown = useCallback(function () {
238
255
  var _a;
239
256
  if (canUseMobileDropdownTimePicker && isDropdownOpened) {
@@ -267,21 +284,51 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
267
284
  closeDropdown();
268
285
  }
269
286
  }, [canUseMobileDropdownTimePicker, closeDropdown, closeOpenedDropdown, commitItem]);
270
- var isSelectHighlightedItem = useCallback(function () {
287
+ var isSelectHighlightedItem = useCallback(function (event) {
288
+ var _a, _b;
271
289
  if (highlightedItemIndex === null) {
272
290
  return false;
273
291
  }
274
- var resolvedItem = resolvedItems[highlightedItemIndex];
275
- if (!resolvedItem || !isTimeMenuItem(resolvedItem) || resolvedItem.disabled) {
292
+ var resolvedItem = menuItems[highlightedItemIndex];
293
+ if (!resolvedItem) {
294
+ return true;
295
+ }
296
+ if (isNavigableMenuElement(resolvedItem)) {
297
+ (_b = (_a = resolvedItem.props).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, event);
298
+ return true;
299
+ }
300
+ if (!isTimeMenuItem(resolvedItem) || resolvedItem.disabled) {
276
301
  return true;
277
302
  }
278
303
  selectItem(resolvedItem.item);
279
304
  return true;
280
- }, [highlightedItemIndex, resolvedItems, selectItem]);
281
- var focusInput = useCallback(function () {
305
+ }, [highlightedItemIndex, menuItems, selectItem]);
306
+ var focusInput = useCallback(function (options) {
282
307
  var _a;
308
+ if (disabled) {
309
+ return;
310
+ }
311
+ shouldSkipOpenOnFocusRef.current = Boolean(options === null || options === void 0 ? void 0 : options.withoutOpenDropdown);
283
312
  (_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.focus();
284
- }, [getActiveInput]);
313
+ shouldSkipOpenOnFocusRef.current = false;
314
+ }, [disabled, getActiveInput]);
315
+ /**
316
+ * Открывает выпадающее меню так же, как это делает пользователь: через фокус в поле.
317
+ * Меню закрывается по потере фокуса, поэтому открытое без фокуса меню было бы нечем закрыть.
318
+ * В режиме нативного пикера открывается системный пикер — своего меню в этом режиме нет.
319
+ */
320
+ var openInputDropdown = useCallback(function () {
321
+ var _a;
322
+ if (disabled) {
323
+ return;
324
+ }
325
+ if (canUseMobileNativeTimePicker) {
326
+ openNativeTimePicker();
327
+ return;
328
+ }
329
+ (_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.focus();
330
+ openDropdown();
331
+ }, [canUseMobileNativeTimePicker, disabled, getActiveInput, openDropdown, openNativeTimePicker]);
285
332
  var blurInput = useCallback(function () {
286
333
  var _a;
287
334
  if (canUseMobileDropdownTimePicker && isDropdownOpened) {
@@ -301,9 +348,10 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
301
348
  focus: focusInput,
302
349
  blur: blurInput,
303
350
  blink: blinkInput,
351
+ open: openInputDropdown,
304
352
  close: closeInputDropdown,
305
353
  getRootNode: function () { return rootRef.current; },
306
- }); }, [blinkInput, blurInput, closeInputDropdown, focusInput, getActiveInput]);
354
+ }); }, [blinkInput, blurInput, closeInputDropdown, focusInput, openInputDropdown]);
307
355
  useLayoutEffect(function () {
308
356
  if (!isDropdownOpened) {
309
357
  return;
@@ -317,11 +365,36 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
317
365
  scrollSelectedItemIntoView(targetNode);
318
366
  }
319
367
  }, [highlightedItemIndex, isDropdownOpened, selectedItemIndex]);
368
+ /**
369
+ * Блокировка контрола посреди ввода завершает его так же, как потеря фокуса:
370
+ * меню закрывается, а недоведенное значение коммитится, а не остается висеть в поле.
371
+ */
320
372
  useEffect(function () {
321
- if (disabled && isDropdownOpened) {
373
+ if (!disabled) {
374
+ return;
375
+ }
376
+ if (canUseMobileDropdownTimePicker && isDropdownOpened) {
322
377
  closeOpenedDropdown();
378
+ return;
379
+ }
380
+ if (isDropdownOpened) {
381
+ closeDropdown();
382
+ }
383
+ if (isInputFocused) {
384
+ setIsInputFocused(false);
385
+ resetMouseInteractionState();
386
+ commitEditingValue();
323
387
  }
324
- }, [closeOpenedDropdown, disabled, isDropdownOpened]);
388
+ }, [
389
+ canUseMobileDropdownTimePicker,
390
+ closeDropdown,
391
+ closeOpenedDropdown,
392
+ commitEditingValue,
393
+ disabled,
394
+ isDropdownOpened,
395
+ isInputFocused,
396
+ resetMouseInteractionState,
397
+ ]);
325
398
  useEffect(function () {
326
399
  setIsMobileDevice(Boolean(isMobile));
327
400
  setCanUseMobileNativeTimePicker(useMobileNativeTimePicker && Boolean(isMobile));
@@ -330,17 +403,25 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
330
403
  closeOpenedDropdown();
331
404
  }, [closeOpenedDropdown]);
332
405
  var handleFocusBySource = function (event, isMobilePopupInput) {
406
+ var shouldSkipOpenDropdown = shouldSkipOpenOnFocusRef.current;
407
+ shouldSkipOpenOnFocusRef.current = false;
333
408
  if (canUseMobileNativeTimePicker) {
334
- openNativeTimePicker();
409
+ if (!shouldSkipOpenDropdown) {
410
+ openNativeTimePicker();
411
+ }
335
412
  onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
336
413
  return;
337
414
  }
338
415
  if (canUseMobileDropdownTimePicker && !isMobilePopupInput) {
339
- openDropdown();
416
+ if (!shouldSkipOpenDropdown) {
417
+ openDropdown();
418
+ }
340
419
  return;
341
420
  }
342
421
  setIsInputFocused(true);
343
- openDropdown();
422
+ if (!shouldSkipOpenDropdown) {
423
+ openDropdown();
424
+ }
344
425
  if (isMouseDownOnFragmentRef.current && !isInputFocused) {
345
426
  if (isTimeDisplayEmpty(editingValueRef.current)) {
346
427
  selectSegment('hours');
@@ -477,20 +558,24 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
477
558
  }
478
559
  if (shouldHandleAsAllSelection && (isKeyBackspace(event) || isKeyDelete(event))) {
479
560
  event.preventDefault();
561
+ openDropdown();
480
562
  updateEditingValueAfterUserEdit(getEmptyDisplayValue(format));
481
563
  selectSegment('hours');
482
564
  return;
483
565
  }
484
566
  if (DIGIT_REGEXP.test(event.key)) {
485
567
  event.preventDefault();
486
- resetHighlightedItem();
568
+ openDropdown();
487
569
  var nextDigitInputValue = shouldHandleAsAllSelection ? getEmptyDisplayValue(format) : currentDisplayValue;
488
570
  var nextDigitInputSegment = shouldHandleAsAllSelection ? 'hours' : currentSegment;
489
571
  var result = formatDigitToTimeSegment(nextDigitInputValue, nextDigitInputSegment, event.key, format);
572
+ // Отклоненная цифра не меняет ни значение, ни список, поэтому и подсветку трогать нельзя:
573
+ // автоподсветка не восстановится, а Enter выбрал бы не тот элемент, который видит пользователь.
490
574
  if (result.shouldBlink) {
491
575
  handleUnexpectedInput(event.key);
492
576
  return;
493
577
  }
578
+ resetHighlightedItem();
494
579
  updateEditingValueAfterUserEdit(result.nextValue);
495
580
  selectSegment(result.selectedSegment);
496
581
  return;
@@ -538,6 +623,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
538
623
  }
539
624
  if (isKeyBackspace(event)) {
540
625
  event.preventDefault();
626
+ openDropdown();
541
627
  var nextDisplayValue = deleteTimeSegmentDigit(currentDisplayValue, currentSegment, format);
542
628
  var nextSegment = currentSegment;
543
629
  if (nextDisplayValue === currentDisplayValue) {
@@ -553,6 +639,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
553
639
  }
554
640
  if (isKeyDelete(event)) {
555
641
  event.preventDefault();
642
+ openDropdown();
556
643
  updateEditingValueAfterUserEdit(replaceTimeSegment(currentDisplayValue, currentSegment, EMPTY_SEGMENT, format));
557
644
  selectSegment(currentSegment);
558
645
  return;
@@ -570,7 +657,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
570
657
  return;
571
658
  }
572
659
  if (isKeyEnter(event)) {
573
- if (isSelectHighlightedItem()) {
660
+ if (isSelectHighlightedItem(event)) {
574
661
  event.preventDefault();
575
662
  return;
576
663
  }
@@ -610,7 +697,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
610
697
  var handleKeyDown = function (event) { return handleKeyDownBySource(event, false); };
611
698
  var handleMobileKeyDown = function (event) { return handleKeyDownBySource(event, true); };
612
699
  var handlePasteBySource = function (event, isMobilePopupInput) {
613
- var _a;
700
+ var _a, _b;
614
701
  onPaste === null || onPaste === void 0 ? void 0 : onPaste(event);
615
702
  if (event.defaultPrevented || disabled) {
616
703
  return;
@@ -618,13 +705,17 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
618
705
  if (canUseMobileNativeTimePicker || (canUseMobileDropdownTimePicker && !isMobilePopupInput)) {
619
706
  return;
620
707
  }
621
- var pastedValue = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text');
622
- var nextDisplayValue = parsePastedTimeValue(pastedValue, format);
708
+ var pastedValue = (_b = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text')) !== null && _b !== void 0 ? _b : EMPTY_VALUE;
623
709
  event.preventDefault();
710
+ if (pastedValue.trim() === EMPTY_VALUE) {
711
+ return;
712
+ }
713
+ var nextDisplayValue = parsePastedTimeValue(pastedValue, format);
624
714
  if (isTimeDisplayEmpty(nextDisplayValue)) {
625
715
  handleUnexpectedInput(pastedValue);
626
716
  return;
627
717
  }
718
+ openDropdown();
628
719
  updateEditingValueAfterUserEdit(nextDisplayValue);
629
720
  selectSegment('hours');
630
721
  };
@@ -637,7 +728,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
637
728
  React.createElement("span", { ref: rootRef, className: styles.root(), "data-tid": TimePickerDataTids.root },
638
729
  React.createElement(TimeInput, __assign({}, inputProps, { "data-tid": TimePickerDataTids.input, ref: inputRef, disabled: disabled, hasDropdown: hasDropdown, size: size, format: format, corners: corners, rightIcon: inputRightIcon, suffix: suffix, value: displayValue, "aria-haspopup": hasDropdown ? 'listbox' : undefined, "aria-expanded": !isMobileDropdownActive && hasDropdown && isDropdownOpened && hasDropdownContent ? true : undefined, "aria-controls": !isMobileDropdownActive && hasDropdown && isDropdownOpened && hasDropdownContent ? popupId : undefined, "aria-activedescendant": isMobileDropdownActive ? undefined : activeDescendantId, "aria-placeholder": ariaPlaceholder, "aria-busy": isLoadingItems || undefined, onClick: handleClick, onBlur: handleBlur, onFocus: handleFocus, onMouseDownCapture: handleMouseDownCapture, onMouseUp: handleMouseUp, onKeyDown: handleKeyDown, onSelectSegment: handleSelectSegmentByMouse, onPaste: handlePaste })),
639
730
  canUseMobileNativeTimePicker && (React.createElement(NativeTimeInput, { "data-tid": TimePickerDataTids.nativeInput, disabled: disabled, ref: nativeInputRef, format: format, minTime: minTime, maxTime: maxTime, value: editingValue, onValueChange: handleNativeValueChange })),
640
- canUseMobileDropdownTimePicker && isDropdownOpened && (React.createElement(TimePickerMobilePopup, { id: popupId, opened: isDropdownOpened, value: displayValue, inputRef: mobileInputRef, disabled: disabled, format: format, size: size, rightIcon: inputRightIcon, suffix: suffix, resolvedItems: resolvedItems, renderItem: renderItem, isLoading: isLoadingItems, highlightedItemIndex: highlightedItemIndex, normalizedValue: normalizedValue, itemRefs: itemRefs, error: props.error, warning: props.warning, "aria-describedby": props['aria-describedby'], "aria-label": props['aria-label'], "aria-placeholder": ariaPlaceholder, "aria-haspopup": 'listbox', "aria-expanded": hasDropdownContent ? true : undefined, "aria-controls": hasDropdownContent ? popupId : undefined, "aria-activedescendant": activeDescendantId, onFocus: handleMobileFocus, onBlur: handleMobileBlur, onClick: handleMobileClick, onKeyDown: handleMobileKeyDown, onMouseDownCapture: handleMobileMouseDownCapture, onMouseUp: handleMobileMouseUp, onPaste: handleMobilePaste, onSelectSegment: handleMobileSelectSegmentByMouse, onSelectItem: selectItem, onCloseRequest: handleCloseMobileDropdown })),
731
+ canUseMobileDropdownTimePicker && isDropdownOpened && (React.createElement(TimePickerMobilePopup, { id: popupId, opened: isDropdownOpened, value: displayValue, inputRef: mobileInputRef, disabled: disabled, format: format, size: size, rightIcon: inputRightIcon, suffix: suffix, resolvedItems: resolvedItems, renderItem: renderItem, isLoading: isLoadingItems, highlightedItemIndex: highlightedItemIndex, normalizedValue: normalizedValue, itemRefs: itemRefs, error: props.error, warning: props.warning, "aria-describedby": props['aria-describedby'], "aria-label": props['aria-label'], "aria-placeholder": ariaPlaceholder, "aria-haspopup": 'listbox', "aria-expanded": resolvedItems.length > 0 || isLoadingItems ? true : undefined, "aria-controls": resolvedItems.length > 0 || isLoadingItems ? popupId : undefined, "aria-activedescendant": activeDescendantId, onFocus: handleMobileFocus, onBlur: handleMobileBlur, onClick: handleMobileClick, onKeyDown: handleMobileKeyDown, onMouseDownCapture: handleMobileMouseDownCapture, onMouseUp: handleMobileMouseUp, onPaste: handleMobilePaste, onSelectSegment: handleMobileSelectSegmentByMouse, onSelectItem: selectItem, onCloseRequest: handleCloseMobileDropdown })),
641
732
  hasDropdown &&
642
733
  !canUseMobileNativeTimePicker &&
643
734
  !canUseMobileDropdownTimePicker &&