@skbkontur/react-ui 6.1.7-65057.0 → 6.1.7-e032b.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.
- package/components/Button/Button.d.ts +1 -1
- package/components/Button/Button.js +4 -7
- package/components/Button/Button.js.map +1 -1
- package/components/FxInput/FxInputRestoreBtn.js +1 -1
- package/components/FxInput/FxInputRestoreBtn.js.map +1 -1
- package/components/Switcher/Switcher.js +0 -2
- package/components/Switcher/Switcher.js.map +1 -1
- package/components/TimePicker/TimePicker.d.ts +28 -24
- package/components/TimePicker/TimePicker.js +205 -131
- package/components/TimePicker/TimePicker.js.map +1 -1
- package/components/TimePicker/TimePickerItems.d.ts +1 -1
- package/components/TimePicker/TimePickerItems.js +5 -8
- package/components/TimePicker/TimePickerItems.js.map +1 -1
- package/components/TimePicker/TimePickerMobilePopup.d.ts +1 -2
- package/components/TimePicker/TimePickerMobilePopup.js +3 -3
- package/components/TimePicker/TimePickerMobilePopup.js.map +1 -1
- package/components/TimePicker/TimePickerPopup.d.ts +1 -1
- package/components/TimePicker/TimePickerPopup.js +2 -2
- package/components/TimePicker/TimePickerPopup.js.map +1 -1
- package/components/TimePicker/helpers/TimePicker.constants.d.ts +2 -1
- package/components/TimePicker/helpers/TimePicker.constants.js +2 -7
- package/components/TimePicker/helpers/TimePicker.constants.js.map +1 -1
- package/components/TimePicker/helpers/TimePicker.editing.d.ts +1 -5
- package/components/TimePicker/helpers/TimePicker.editing.js +8 -35
- package/components/TimePicker/helpers/TimePicker.editing.js.map +1 -1
- package/components/TimePicker/helpers/TimePicker.shared.d.ts +13 -24
- package/components/TimePicker/helpers/TimePicker.shared.js +13 -27
- package/components/TimePicker/helpers/TimePicker.shared.js.map +1 -1
- package/components/TimePicker/helpers/TimePicker.value.d.ts +3 -13
- package/components/TimePicker/helpers/TimePicker.value.js +13 -43
- package/components/TimePicker/helpers/TimePicker.value.js.map +1 -1
- package/components/TimePicker/helpers/scrollSelectedItemIntoView.d.ts +2 -0
- package/components/TimePicker/helpers/scrollSelectedItemIntoView.js +2 -0
- package/components/TimePicker/helpers/scrollSelectedItemIntoView.js.map +1 -1
- package/components/TimePicker/helpers/validateTimePicker.js +2 -4
- package/components/TimePicker/helpers/validateTimePicker.js.map +1 -1
- package/components/TimePicker/hooks/useTimePickerDropdown.js +9 -25
- package/components/TimePicker/hooks/useTimePickerDropdown.js.map +1 -1
- package/components/TimePicker/hooks/useTimePickerSource.js +2 -1
- package/components/TimePicker/hooks/useTimePickerSource.js.map +1 -1
- package/components/TimePicker/hooks/useTimePickerValue.d.ts +9 -4
- package/components/TimePicker/hooks/useTimePickerValue.js +22 -23
- package/components/TimePicker/hooks/useTimePickerValue.js.map +1 -1
- package/components/TimePicker/index.d.ts +1 -1
- package/components/TimePicker/index.js.map +1 -1
- package/internal/NativeTimeInput/NativeTimeInput.utils.d.ts +2 -0
- package/internal/NativeTimeInput/NativeTimeInput.utils.js +4 -8
- package/internal/NativeTimeInput/NativeTimeInput.utils.js.map +1 -1
- package/internal/TimeInput/TimeFragments.d.ts +0 -1
- package/internal/TimeInput/TimeFragments.js +27 -6
- package/internal/TimeInput/TimeFragments.js.map +1 -1
- package/internal/TimeInput/TimeFragments.styles.d.ts +14 -0
- package/internal/TimeInput/TimeFragments.styles.js +36 -9
- package/internal/TimeInput/TimeFragments.styles.js.map +1 -1
- package/internal/TimeInput/TimeInput.d.ts +0 -1
- package/internal/TimeInput/TimeInput.js +0 -1
- package/internal/TimeInput/TimeInput.js.map +1 -1
- package/package.json +1 -1
|
@@ -25,20 +25,21 @@ import { CommonWrapper } from '../../internal/CommonWrapper/index.js';
|
|
|
25
25
|
import { LoadingIcon } from '../../internal/icons2022/LoadingIcon.js';
|
|
26
26
|
import { NativeTimeInput } from '../../internal/NativeTimeInput/index.js';
|
|
27
27
|
import { TimeInput } from '../../internal/TimeInput/index.js';
|
|
28
|
-
import { isIOS
|
|
28
|
+
import { isIOS } from '../../lib/client.js';
|
|
29
29
|
import { isKeyArrowDown, isKeyArrowLeft, isKeyArrowRight, isKeyArrowUp, isKeyBackspace, isKeyChar, isKeyDelete, isKeyEnter, isKeyEscape, isKeySpace, isKeyTab, isModShift, isShortcutSelectAll, } from '../../lib/events/keyboard/identifiers.js';
|
|
30
30
|
import { forwardRefAndName } from '../../lib/forwardRefAndName.js';
|
|
31
31
|
import { useLocaleForControl } from '../../lib/locale/useLocaleForControl.js';
|
|
32
32
|
import { useStyles } from '../../lib/renderEnvironment/index.js';
|
|
33
33
|
import { ThemeContext } from '../../lib/theming/ThemeContext.js';
|
|
34
34
|
import { getRandomID } from '../../lib/utils.js';
|
|
35
|
+
import { useResponsiveLayout } from '../ResponsiveLayout/index.js';
|
|
35
36
|
import { scrollSelectedItemIntoView } from './helpers/scrollSelectedItemIntoView.js';
|
|
36
37
|
import { DIGIT_REGEXP, EMPTY_SEGMENT, EMPTY_VALUE, TimePickerDataTids } from './helpers/TimePicker.constants.js';
|
|
37
38
|
import { commitTimeSegmentOnLeave, deleteTimeSegmentDigit, formatDigitToTimeSegment, shiftTimeSegmentValue, } from './helpers/TimePicker.editing.js';
|
|
38
39
|
import { getTimePickerPopupMaxHeight } from './helpers/TimePicker.layout.js';
|
|
39
40
|
import { getNextTimeSegment, getPreviousTimeSegment } from './helpers/TimePicker.selection.js';
|
|
40
41
|
import { isNavigableMenuElement, isTimeItem, isTimeItemObject, isTimeMenuItem, timeValueToItem, } from './helpers/TimePicker.shared.js';
|
|
41
|
-
import {
|
|
42
|
+
import { filterTimeItems, getEmptyDisplayValue, getTimeFilterQuery, getTimeValueString, isTimeDisplayEmpty, normalizeTimeValue, parsePastedTimeValue, replaceTimeSegment, resolveTimeItems, } from './helpers/TimePicker.value.js';
|
|
42
43
|
import { validateTimePicker } from './helpers/validateTimePicker.js';
|
|
43
44
|
import { useTimePickerDropdown } from './hooks/useTimePickerDropdown.js';
|
|
44
45
|
import { useTimePickerSelection } from './hooks/useTimePickerSelection.js';
|
|
@@ -67,17 +68,27 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
67
68
|
var rootRef = useRef(null);
|
|
68
69
|
var itemRefs = useRef(new Map());
|
|
69
70
|
var isMouseFocusRef = useRef(false);
|
|
70
|
-
|
|
71
|
+
/** Сегмент, по которому нажали мышью, или `null`, если нажали мимо сегментов. */
|
|
71
72
|
var mouseDownSegmentRef = useRef(null);
|
|
72
73
|
var mobileBlurEventRef = useRef(null);
|
|
73
74
|
var shouldSkipOpenOnFocusRef = useRef(false);
|
|
75
|
+
/**
|
|
76
|
+
* Контрол считает себя сфокусированным: о фокусе сообщено наружу, а сессия ввода не закончена.
|
|
77
|
+
* Внутри одной сессии фокус может переезжать между полем и инпутом мобильного попапа —
|
|
78
|
+
* например вместе со сменой верстки, — и такой переезд не начинает сессию заново.
|
|
79
|
+
*/
|
|
80
|
+
var isControlFocusedRef = useRef(false);
|
|
74
81
|
var popupIdRef = useRef(TimePickerDataTids.popup + getRandomID());
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Мобильная версия включается медиавыражением темы, как в ComboBox:
|
|
84
|
+
* поле переключается между мобильной и десктопной версией вместе с версткой страницы.
|
|
85
|
+
*/
|
|
86
|
+
var isMobileLayout = useResponsiveLayout().isMobile;
|
|
87
|
+
var canUseMobileNativeTimePicker = useMobileNativeTimePicker && isMobileLayout;
|
|
77
88
|
var isSourceFunction = typeof source === 'function';
|
|
78
89
|
var hasDropdown = isSourceFunction || (Array.isArray(source) && source.length > 0);
|
|
79
|
-
var
|
|
80
|
-
var
|
|
90
|
+
var _f = useState(false), isDropdownOpened = _f[0], setIsDropdownOpened = _f[1];
|
|
91
|
+
var _g = useState(false), isEditedAfterOpen = _g[0], setIsEditedAfterOpen = _g[1];
|
|
81
92
|
/**
|
|
82
93
|
* Форма элементов функции-источника: последняя, о которой компонент знает.
|
|
83
94
|
* Форма запоминается, потому что элементы функции очищаются при закрытии меню,
|
|
@@ -94,28 +105,62 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
94
105
|
var shouldBuildItemAsObjectRef = useRef(false);
|
|
95
106
|
var valueToItemRef = useRef(valueToItem);
|
|
96
107
|
valueToItemRef.current = valueToItem;
|
|
97
|
-
var handleCommitValue = useCallback(function (
|
|
108
|
+
var handleCommitValue = useCallback(function (nextValue, item) {
|
|
98
109
|
var _a, _b;
|
|
99
110
|
if (!onValueChange) {
|
|
100
111
|
return;
|
|
101
112
|
}
|
|
102
|
-
if (
|
|
113
|
+
if (nextValue === EMPTY_VALUE) {
|
|
103
114
|
onValueChange(null, EMPTY_VALUE);
|
|
104
115
|
return;
|
|
105
116
|
}
|
|
106
|
-
var nextItem = (_b = item !== null && item !== void 0 ? item : (_a = valueToItemRef.current) === null || _a === void 0 ? void 0 : _a.call(valueToItemRef,
|
|
107
|
-
onValueChange(nextItem,
|
|
117
|
+
var nextItem = (_b = item !== null && item !== void 0 ? item : (_a = valueToItemRef.current) === null || _a === void 0 ? void 0 : _a.call(valueToItemRef, nextValue)) !== null && _b !== void 0 ? _b : timeValueToItem(nextValue, shouldBuildItemAsObjectRef.current);
|
|
118
|
+
onValueChange(nextItem, nextValue);
|
|
108
119
|
}, [onValueChange]);
|
|
109
|
-
var
|
|
120
|
+
var onBlurRef = useRef(onBlur);
|
|
121
|
+
onBlurRef.current = onBlur;
|
|
122
|
+
var pendingBlurEventRef = useRef(null);
|
|
123
|
+
var _h = useState(0), blurRequestId = _h[0], setBlurRequestId = _h[1];
|
|
124
|
+
/**
|
|
125
|
+
* Завершает сессию фокуса и откладывает `onBlur` до конца рендера, как это делает ComboBox.
|
|
126
|
+
* Значение коммитится синхронно с потерей фокуса, поэтому вызванный тут же `onBlur`
|
|
127
|
+
* попал бы в обработчик с состоянием родителя до изменения значения — например с пустым временем.
|
|
128
|
+
* Событие может отсутствовать: сессия все равно закрывается, но наружу ничего не уходит.
|
|
129
|
+
*/
|
|
130
|
+
var emitBlur = useCallback(function (event) {
|
|
131
|
+
if (!isControlFocusedRef.current) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
isControlFocusedRef.current = false;
|
|
135
|
+
if (!event || !onBlurRef.current) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
event.persist();
|
|
139
|
+
pendingBlurEventRef.current = event;
|
|
140
|
+
setBlurRequestId(function (currentId) { return currentId + 1; });
|
|
141
|
+
}, []);
|
|
142
|
+
var flushPendingBlur = useCallback(function () {
|
|
143
|
+
var _a;
|
|
144
|
+
var pendingBlurEvent = pendingBlurEventRef.current;
|
|
145
|
+
if (!pendingBlurEvent) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
pendingBlurEventRef.current = null;
|
|
149
|
+
(_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, pendingBlurEvent);
|
|
150
|
+
}, []);
|
|
151
|
+
useEffect(flushPendingBlur, [blurRequestId, flushPendingBlur]);
|
|
152
|
+
/** Родитель мог убрать поле в ответ на коммит значения — отложенное событие все равно должно уйти. */
|
|
153
|
+
useEffect(function () { return flushPendingBlur; }, [flushPendingBlur]);
|
|
154
|
+
var _j = useTimePickerValue({
|
|
110
155
|
isInputFocused: isInputFocused,
|
|
111
156
|
format: format,
|
|
112
157
|
externalValue: getTimeValueString(value),
|
|
113
158
|
isValueControlled: value !== undefined,
|
|
114
159
|
onCommitValue: handleCommitValue,
|
|
115
160
|
onInputValueChange: onInputValueChange,
|
|
116
|
-
}), editingValue =
|
|
161
|
+
}), editingValue = _j.editingValue, editingValueRef = _j.editingValueRef, committedValue = _j.committedValue, updateEditingValue = _j.updateEditingValue, commitEditingValue = _j.commitEditingValue, commitValue = _j.commitValue, commitItem = _j.commitItem;
|
|
117
162
|
var filterQuery = isEditedAfterOpen ? getTimeFilterQuery(editingValue, format) : EMPTY_VALUE;
|
|
118
|
-
var
|
|
163
|
+
var _k = useTimePickerSource({ source: source, format: format, isDropdownOpened: isDropdownOpened, filterQuery: filterQuery }), sourceValueItems = _k.items, fetchedItemsQuery = _k.fetchedQuery, isLoadingItems = _k.isLoading;
|
|
119
164
|
useEffect(function () {
|
|
120
165
|
if (!isSourceFunction || sourceValueItems.some(isTimeItem)) {
|
|
121
166
|
lastSourceItemShapeRef.current = sourceValueItems.some(function (item) { return isTimeItem(item) && isTimeItemObject(item); });
|
|
@@ -123,17 +168,17 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
123
168
|
shouldBuildItemAsObjectRef.current =
|
|
124
169
|
lastSourceItemShapeRef.current === true || (value !== null && value !== undefined && isTimeItemObject(value));
|
|
125
170
|
}, [isSourceFunction, sourceValueItems, value]);
|
|
126
|
-
var
|
|
127
|
-
/**
|
|
128
|
-
* Элементы для отрисовки: время и подпись достаются из элемента,
|
|
129
|
-
* а `disabled` дополняется проверкой диапазона `minTime`/`maxTime`.
|
|
130
|
-
* Исходный элемент сохраняется как есть — именно он уходит в `onValueChange`.
|
|
131
|
-
*/
|
|
132
|
-
var resolvedItems = useMemo(function () { return resolveTimeItems(filteredItems, format, minTime, maxTime); }, [maxTime, minTime, format, filteredItems]);
|
|
171
|
+
var resolvedItems = useMemo(function () { return resolveTimeItems(filterTimeItems(sourceValueItems, filterQuery, format), format, minTime, maxTime); }, [filterQuery, format, maxTime, minTime, sourceValueItems]);
|
|
133
172
|
var popupMaxHeight = getTimePickerPopupMaxHeight(size, theme);
|
|
134
173
|
var ariaPlaceholder = format === 'HH:mm' ? locale.ariaPlaceholderHHMM : locale.ariaPlaceholderHHMMSS;
|
|
135
|
-
|
|
136
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Время, отмеченное в меню выбранным.
|
|
176
|
+
* Пока пользователь не начал править значение при открытом меню, это то же время, что в поле.
|
|
177
|
+
* С началом правки отмечено остается закоммиченное значение: набор фильтра — например `10` —
|
|
178
|
+
* не должен отмечать элемент `10:00` как уже выбранный.
|
|
179
|
+
*/
|
|
180
|
+
var selectedValue = normalizeTimeValue(isEditedAfterOpen ? committedValue : editingValue, format);
|
|
181
|
+
var selectedItemIndex = resolvedItems.findIndex(function (item) { return isTimeMenuItem(item) && normalizeTimeValue(item.value, format) === selectedValue; });
|
|
137
182
|
var autoHighlightKey = null;
|
|
138
183
|
if (filterQuery !== EMPTY_VALUE) {
|
|
139
184
|
if (isSourceFunction) {
|
|
@@ -145,7 +190,9 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
145
190
|
autoHighlightKey = "source-array:".concat(filterQuery);
|
|
146
191
|
}
|
|
147
192
|
}
|
|
148
|
-
var canUseMobileDropdownTimePicker = hasDropdown &&
|
|
193
|
+
var canUseMobileDropdownTimePicker = hasDropdown && isMobileLayout && !canUseMobileNativeTimePicker;
|
|
194
|
+
/** Ввод идет в мобильном попапе: он открыт, а нативного пикера в этом режиме нет. */
|
|
195
|
+
var isMobileDropdownActive = canUseMobileDropdownTimePicker && isDropdownOpened;
|
|
149
196
|
/**
|
|
150
197
|
* Меню с одними лишь заголовками и разделителями показывать не о чем,
|
|
151
198
|
* поэтому при активном фильтре нужен хотя бы один подошедший элемент со временем.
|
|
@@ -159,16 +206,16 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
159
206
|
var menuItems = hasDropdownContent ? resolvedItems : EMPTY_MENU_ITEMS;
|
|
160
207
|
var inputRightIcon = isLoadingItems && resolvedItems.length > 0 ? (React.createElement("span", { "data-tid": TimePickerDataTids.inputLoading },
|
|
161
208
|
React.createElement(LoadingIcon, { size: size }))) : (rightIcon);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
209
|
+
/** Сфокусированное поле всегда показывает сегменты, а пустое без фокуса — ничего. */
|
|
210
|
+
var getDisplayValue = function () {
|
|
211
|
+
if (isInputFocused) {
|
|
212
|
+
return editingValue || getEmptyDisplayValue(format);
|
|
213
|
+
}
|
|
214
|
+
return isTimeDisplayEmpty(editingValue) ? EMPTY_VALUE : editingValue;
|
|
215
|
+
};
|
|
216
|
+
var displayValue = getDisplayValue();
|
|
169
217
|
var resetMouseInteractionState = useCallback(function () {
|
|
170
218
|
isMouseFocusRef.current = false;
|
|
171
|
-
isMouseDownOnFragmentRef.current = false;
|
|
172
219
|
mouseDownSegmentRef.current = null;
|
|
173
220
|
}, []);
|
|
174
221
|
var updateEditingValueAfterUserEdit = useCallback(function (nextEditingValue) {
|
|
@@ -193,7 +240,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
193
240
|
(_b = nativeInputRef.current) === null || _b === void 0 ? void 0 : _b.click();
|
|
194
241
|
}
|
|
195
242
|
}, [canUseMobileNativeTimePicker, disabled]);
|
|
196
|
-
var
|
|
243
|
+
var _l = useTimePickerDropdown({
|
|
197
244
|
disabled: disabled,
|
|
198
245
|
hasDropdown: hasDropdown,
|
|
199
246
|
items: menuItems,
|
|
@@ -201,7 +248,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
201
248
|
autoHighlightKey: autoHighlightKey,
|
|
202
249
|
isDropdownOpened: isDropdownOpened,
|
|
203
250
|
setIsDropdownOpened: setIsDropdownOpened,
|
|
204
|
-
}), highlightedItemIndex =
|
|
251
|
+
}), highlightedItemIndex = _l.highlightedItemIndex, openDropdown = _l.openDropdown, closeDropdown = _l.closeDropdown, resetHighlightedItem = _l.resetHighlightedItem, tryNavigateItems = _l.tryNavigateItems;
|
|
205
252
|
/**
|
|
206
253
|
* Фильтрация начинается только после правки значения при открытом меню.
|
|
207
254
|
* Состояние живет ровно столько, сколько открыто меню: правка, которая заново открывает меню,
|
|
@@ -215,65 +262,59 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
215
262
|
var popupId = popupIdRef.current;
|
|
216
263
|
var highlightedMenuItem = highlightedItemIndex === null ? undefined : menuItems[highlightedItemIndex];
|
|
217
264
|
/** Кастомный `MenuItem` может принести свой `id` — тогда указывать нужно именно на него. */
|
|
218
|
-
var activeDescendantId =
|
|
265
|
+
var activeDescendantId = highlightedMenuItem
|
|
219
266
|
? ((_a = (isNavigableMenuElement(highlightedMenuItem) ? highlightedMenuItem.props.id : undefined)) !== null && _a !== void 0 ? _a : "".concat(popupId, "-item-").concat(highlightedItemIndex))
|
|
220
267
|
: undefined;
|
|
221
|
-
var
|
|
222
|
-
var
|
|
223
|
-
var _o = useTimePickerSelection({
|
|
268
|
+
var getActiveInput = useCallback(function () { return (isMobileDropdownActive ? mobileInputRef.current : inputRef.current); }, [isMobileDropdownActive]);
|
|
269
|
+
var _m = useTimePickerSelection({
|
|
224
270
|
isInputFocused: isInputFocused,
|
|
225
271
|
format: format,
|
|
226
272
|
displayValue: displayValue,
|
|
227
273
|
getInput: getActiveInput,
|
|
228
|
-
}), selection =
|
|
229
|
-
var
|
|
274
|
+
}), selection = _m.selection, selectedSegment = _m.selectedSegment, selectSegment = _m.selectSegment, selectAll = _m.selectAll, syncSelectionWithDOM = _m.syncSelectionWithDOM;
|
|
275
|
+
var blinkInput = useCallback(function () {
|
|
230
276
|
var _a;
|
|
231
277
|
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.blink();
|
|
232
278
|
}, [getActiveInput]);
|
|
233
|
-
/**
|
|
234
|
-
* Сообщает о некорректном вводе: нажатой клавише или вставленном значении, которое не может быть временем.
|
|
235
|
-
* Если обработчик не задан, поле вспыхивает само.
|
|
236
|
-
*/
|
|
237
279
|
var handleUnexpectedInput = useCallback(function (unexpectedValue) {
|
|
238
|
-
if (onUnexpectedInput) {
|
|
239
|
-
|
|
280
|
+
if (!onUnexpectedInput) {
|
|
281
|
+
blinkInput();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
var returnedValue = onUnexpectedInput(unexpectedValue, blinkInput);
|
|
285
|
+
if (returnedValue === undefined) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (returnedValue === null) {
|
|
289
|
+
commitValue(EMPTY_VALUE);
|
|
240
290
|
return;
|
|
241
291
|
}
|
|
242
|
-
|
|
243
|
-
}, [
|
|
292
|
+
commitItem(returnedValue);
|
|
293
|
+
}, [blinkInput, commitItem, commitValue, onUnexpectedInput]);
|
|
294
|
+
/** Закрывает открытое меню: мобильный попап заодно завершает сессию ввода, как потеря фокуса. */
|
|
244
295
|
var closeOpenedDropdown = useCallback(function () {
|
|
245
|
-
var _a;
|
|
246
|
-
if (
|
|
296
|
+
var _a, _b;
|
|
297
|
+
if (isMobileDropdownActive) {
|
|
298
|
+
// Фокус может остаться в любом из двух полей: инпут попапа не всегда получает его —
|
|
299
|
+
// например мобильный браузер отказывается переводить фокус без жеста пользователя.
|
|
300
|
+
// Гасим оба, чтобы закрытие попапа всегда давало событие потери фокуса.
|
|
247
301
|
(_a = mobileInputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
302
|
+
(_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.blur();
|
|
248
303
|
setIsInputFocused(false);
|
|
249
304
|
resetMouseInteractionState();
|
|
250
305
|
closeDropdown();
|
|
251
306
|
commitEditingValue();
|
|
252
307
|
var blurEvent = mobileBlurEventRef.current;
|
|
253
308
|
mobileBlurEventRef.current = null;
|
|
254
|
-
|
|
255
|
-
onBlur === null || onBlur === void 0 ? void 0 : onBlur(blurEvent);
|
|
256
|
-
}
|
|
309
|
+
emitBlur(blurEvent);
|
|
257
310
|
return;
|
|
258
311
|
}
|
|
259
312
|
closeDropdown();
|
|
260
|
-
}, [
|
|
261
|
-
canUseMobileDropdownTimePicker,
|
|
262
|
-
closeDropdown,
|
|
263
|
-
commitEditingValue,
|
|
264
|
-
isDropdownOpened,
|
|
265
|
-
onBlur,
|
|
266
|
-
resetMouseInteractionState,
|
|
267
|
-
]);
|
|
313
|
+
}, [closeDropdown, commitEditingValue, emitBlur, isMobileDropdownActive, resetMouseInteractionState]);
|
|
268
314
|
var selectItem = useCallback(function (item) {
|
|
269
315
|
commitItem(item);
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
closeDropdown();
|
|
275
|
-
}
|
|
276
|
-
}, [canUseMobileDropdownTimePicker, closeDropdown, closeOpenedDropdown, commitItem]);
|
|
316
|
+
closeOpenedDropdown();
|
|
317
|
+
}, [closeOpenedDropdown, commitItem]);
|
|
277
318
|
var isSelectHighlightedItem = useCallback(function (event) {
|
|
278
319
|
var _a, _b;
|
|
279
320
|
if (highlightedItemIndex === null) {
|
|
@@ -321,27 +362,20 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
321
362
|
}, [canUseMobileNativeTimePicker, disabled, getActiveInput, openDropdown, openNativeTimePicker]);
|
|
322
363
|
var blurInput = useCallback(function () {
|
|
323
364
|
var _a;
|
|
324
|
-
if (
|
|
365
|
+
if (isMobileDropdownActive) {
|
|
325
366
|
closeOpenedDropdown();
|
|
326
367
|
return;
|
|
327
368
|
}
|
|
328
369
|
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.blur();
|
|
329
|
-
}, [
|
|
330
|
-
var closeInputDropdown = useCallback(function () {
|
|
331
|
-
closeOpenedDropdown();
|
|
332
|
-
}, [closeOpenedDropdown]);
|
|
333
|
-
var blinkInput = useCallback(function () {
|
|
334
|
-
var _a;
|
|
335
|
-
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.blink();
|
|
336
|
-
}, [getActiveInput]);
|
|
370
|
+
}, [closeOpenedDropdown, getActiveInput, isMobileDropdownActive]);
|
|
337
371
|
useImperativeHandle(ref, function () { return ({
|
|
338
372
|
focus: focusInput,
|
|
339
373
|
blur: blurInput,
|
|
340
374
|
blink: blinkInput,
|
|
341
375
|
open: openInputDropdown,
|
|
342
|
-
close:
|
|
376
|
+
close: closeOpenedDropdown,
|
|
343
377
|
getRootNode: function () { return rootRef.current; },
|
|
344
|
-
}); }, [blinkInput, blurInput,
|
|
378
|
+
}); }, [blinkInput, blurInput, closeOpenedDropdown, focusInput, openInputDropdown]);
|
|
345
379
|
useLayoutEffect(function () {
|
|
346
380
|
if (!isDropdownOpened) {
|
|
347
381
|
return;
|
|
@@ -363,7 +397,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
363
397
|
if (!disabled) {
|
|
364
398
|
return;
|
|
365
399
|
}
|
|
366
|
-
if (
|
|
400
|
+
if (isMobileDropdownActive) {
|
|
367
401
|
closeOpenedDropdown();
|
|
368
402
|
return;
|
|
369
403
|
}
|
|
@@ -376,94 +410,145 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
376
410
|
commitEditingValue();
|
|
377
411
|
}
|
|
378
412
|
}, [
|
|
379
|
-
canUseMobileDropdownTimePicker,
|
|
380
413
|
closeDropdown,
|
|
381
414
|
closeOpenedDropdown,
|
|
382
415
|
commitEditingValue,
|
|
383
416
|
disabled,
|
|
384
417
|
isDropdownOpened,
|
|
385
418
|
isInputFocused,
|
|
419
|
+
isMobileDropdownActive,
|
|
386
420
|
resetMouseInteractionState,
|
|
387
421
|
]);
|
|
422
|
+
var previousIsMobileLayoutRef = useRef(isMobileLayout);
|
|
423
|
+
/**
|
|
424
|
+
* Смена верстки переносит открытое меню в нужную версию: как в ComboBox, меню не закрывается.
|
|
425
|
+
* Начатый ввод переезжает вместе с ним, а если переезжать некуда — коммитится, как при потере фокуса.
|
|
426
|
+
*/
|
|
388
427
|
useEffect(function () {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
428
|
+
if (previousIsMobileLayoutRef.current === isMobileLayout) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
previousIsMobileLayoutRef.current = isMobileLayout;
|
|
432
|
+
// Запомненный blur остался от прошлой верстки: как потеря фокуса контрола он больше не годится.
|
|
433
|
+
mobileBlurEventRef.current = null;
|
|
434
|
+
if (isMobileLayout) {
|
|
435
|
+
// Ввод продолжится в инпуте попапа, только если попап открывается вместе со сменой верстки.
|
|
436
|
+
if (isInputFocused && !isMobileDropdownActive) {
|
|
437
|
+
setIsInputFocused(false);
|
|
438
|
+
resetMouseInteractionState();
|
|
439
|
+
commitEditingValue();
|
|
440
|
+
}
|
|
441
|
+
// В режиме нативного пикера своего меню нет.
|
|
442
|
+
if (isDropdownOpened && canUseMobileNativeTimePicker) {
|
|
443
|
+
closeDropdown();
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Десктопное меню закрывается по потере фокуса поля, поэтому без фокуса в контроле
|
|
449
|
+
* его нечем было бы закрыть: мобильный попап мог быть открыт и без фокуса в своем инпуте.
|
|
450
|
+
* Если же фокус был, его вернет в поле `useTimePickerSelection`.
|
|
451
|
+
*/
|
|
452
|
+
if (isDropdownOpened && !isInputFocused) {
|
|
453
|
+
closeDropdown();
|
|
454
|
+
}
|
|
455
|
+
}, [
|
|
456
|
+
canUseMobileNativeTimePicker,
|
|
457
|
+
closeDropdown,
|
|
458
|
+
commitEditingValue,
|
|
459
|
+
isDropdownOpened,
|
|
460
|
+
isInputFocused,
|
|
461
|
+
isMobileDropdownActive,
|
|
462
|
+
isMobileLayout,
|
|
463
|
+
resetMouseInteractionState,
|
|
464
|
+
]);
|
|
465
|
+
/** Сообщает о фокусе один раз за сессию: переезд фокуса внутри контрола наружу не виден. */
|
|
466
|
+
var emitFocus = function (event) {
|
|
467
|
+
if (isControlFocusedRef.current) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
isControlFocusedRef.current = true;
|
|
471
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
472
|
+
};
|
|
395
473
|
var handleFocusBySource = function (event, isMobilePopupInput) {
|
|
396
474
|
var shouldSkipOpenDropdown = shouldSkipOpenOnFocusRef.current;
|
|
397
475
|
shouldSkipOpenOnFocusRef.current = false;
|
|
476
|
+
var isFocusMovedInsideControl = isControlFocusedRef.current;
|
|
398
477
|
if (canUseMobileNativeTimePicker) {
|
|
399
478
|
if (!shouldSkipOpenDropdown) {
|
|
400
479
|
openNativeTimePicker();
|
|
401
480
|
}
|
|
402
|
-
|
|
481
|
+
emitFocus(event);
|
|
403
482
|
return;
|
|
404
483
|
}
|
|
405
484
|
if (canUseMobileDropdownTimePicker && !isMobilePopupInput) {
|
|
406
485
|
if (!shouldSkipOpenDropdown) {
|
|
407
486
|
openDropdown();
|
|
408
487
|
}
|
|
488
|
+
// Дальше ввод идет в инпуте попапа, но фокус контрол получает здесь.
|
|
489
|
+
emitFocus(event);
|
|
409
490
|
return;
|
|
410
491
|
}
|
|
411
492
|
setIsInputFocused(true);
|
|
412
493
|
if (!shouldSkipOpenDropdown) {
|
|
413
494
|
openDropdown();
|
|
414
495
|
}
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
else if (mouseDownSegmentRef.current) {
|
|
420
|
-
selectSegment(mouseDownSegmentRef.current);
|
|
421
|
-
}
|
|
496
|
+
if (mouseDownSegmentRef.current && !isInputFocused) {
|
|
497
|
+
// По пустому полю ввод всегда начинается с часов, а по заполненному — с сегмента под курсором.
|
|
498
|
+
selectSegment(isTimeDisplayEmpty(editingValueRef.current) ? 'hours' : mouseDownSegmentRef.current);
|
|
422
499
|
}
|
|
423
500
|
else {
|
|
424
|
-
|
|
501
|
+
// Переезд фокуса внутри контрола продолжает начатый ввод, поэтому сегмент сохраняется:
|
|
502
|
+
// иначе следующая цифра ушла бы в часы и затерла бы набранное время.
|
|
503
|
+
selectSegment(isFocusMovedInsideControl ? selectedSegment : 'hours');
|
|
425
504
|
}
|
|
426
|
-
|
|
505
|
+
emitFocus(event);
|
|
427
506
|
};
|
|
428
507
|
var handleFocus = function (event) { return handleFocusBySource(event, false); };
|
|
429
508
|
var handleMobileFocus = function (event) { return handleFocusBySource(event, true); };
|
|
430
509
|
var handleBlurBySource = function (event, isMobilePopupInput) {
|
|
431
|
-
|
|
432
|
-
|
|
510
|
+
/**
|
|
511
|
+
* Пока открыт мобильный попап, фокус ходит между полем и его инпутом: контрол остается активным,
|
|
512
|
+
* поэтому наружу о таком blur сообщать нечего. Событие запоминается на случай,
|
|
513
|
+
* если фокус в инпуте попапа так и не оказался — тогда именно оно уйдет в `onBlur` при закрытии.
|
|
514
|
+
*/
|
|
515
|
+
if (isMobileDropdownActive) {
|
|
516
|
+
event.persist();
|
|
517
|
+
mobileBlurEventRef.current = event;
|
|
433
518
|
return;
|
|
434
519
|
}
|
|
435
|
-
if (
|
|
520
|
+
if (canUseMobileNativeTimePicker) {
|
|
521
|
+
// Ввод в мобильной версии не идет через поле, но незакоммиченное значение могло остаться
|
|
522
|
+
// от десктопной версии, если верстка сменилась посреди ввода.
|
|
523
|
+
if (isInputFocused) {
|
|
524
|
+
setIsInputFocused(false);
|
|
525
|
+
resetMouseInteractionState();
|
|
526
|
+
commitEditingValue();
|
|
527
|
+
}
|
|
528
|
+
emitBlur(event);
|
|
436
529
|
return;
|
|
437
530
|
}
|
|
531
|
+
// Инпут закрытого попапа фокуса уже не держит, но его blur может прийти на размонтировании.
|
|
438
532
|
if (isMobilePopupInput) {
|
|
439
|
-
event.persist();
|
|
440
|
-
mobileBlurEventRef.current = event;
|
|
441
533
|
return;
|
|
442
534
|
}
|
|
443
535
|
setIsInputFocused(false);
|
|
444
536
|
resetMouseInteractionState();
|
|
445
537
|
closeDropdown();
|
|
446
538
|
commitEditingValue();
|
|
447
|
-
|
|
539
|
+
emitBlur(event);
|
|
448
540
|
};
|
|
449
541
|
var handleBlur = function (event) { return handleBlurBySource(event, false); };
|
|
450
542
|
var handleMobileBlur = function (event) { return handleBlurBySource(event, true); };
|
|
451
|
-
var
|
|
543
|
+
var handleClick = function (event) {
|
|
452
544
|
if (canUseMobileNativeTimePicker) {
|
|
453
545
|
openNativeTimePicker();
|
|
454
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
455
|
-
return;
|
|
456
546
|
}
|
|
457
|
-
|
|
547
|
+
else {
|
|
458
548
|
openDropdown();
|
|
459
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
460
|
-
return;
|
|
461
549
|
}
|
|
462
|
-
openDropdown();
|
|
463
550
|
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
464
551
|
};
|
|
465
|
-
var handleClick = function (event) { return handleClickBySource(event, false); };
|
|
466
|
-
var handleMobileClick = function (event) { return handleClickBySource(event, true); };
|
|
467
552
|
var handleMouseDownCaptureBySource = function (event, isMobilePopupInput) {
|
|
468
553
|
var _a, _b;
|
|
469
554
|
if (canUseMobileNativeTimePicker || (canUseMobileDropdownTimePicker && !isMobilePopupInput)) {
|
|
@@ -471,12 +556,10 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
471
556
|
}
|
|
472
557
|
var currentInputRef = isMobilePopupInput ? mobileInputRef : inputRef;
|
|
473
558
|
var segment = (_b = (_a = currentInputRef.current) === null || _a === void 0 ? void 0 : _a.getSegment(event.target)) !== null && _b !== void 0 ? _b : null;
|
|
474
|
-
|
|
475
|
-
if (isInputFocused && !isFragment) {
|
|
559
|
+
if (isInputFocused && segment === null) {
|
|
476
560
|
event.preventDefault();
|
|
477
561
|
}
|
|
478
562
|
isMouseFocusRef.current = !isInputFocused;
|
|
479
|
-
isMouseDownOnFragmentRef.current = isFragment;
|
|
480
563
|
mouseDownSegmentRef.current = segment;
|
|
481
564
|
};
|
|
482
565
|
var handleMouseDownCapture = function (event) {
|
|
@@ -637,12 +720,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
637
720
|
if (isKeyEscape(event)) {
|
|
638
721
|
if (isDropdownOpened) {
|
|
639
722
|
event.preventDefault();
|
|
640
|
-
|
|
641
|
-
closeOpenedDropdown();
|
|
642
|
-
}
|
|
643
|
-
else {
|
|
644
|
-
closeDropdown();
|
|
645
|
-
}
|
|
723
|
+
closeOpenedDropdown();
|
|
646
724
|
}
|
|
647
725
|
return;
|
|
648
726
|
}
|
|
@@ -651,15 +729,14 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
651
729
|
event.preventDefault();
|
|
652
730
|
return;
|
|
653
731
|
}
|
|
654
|
-
if (
|
|
732
|
+
if (isMobileDropdownActive) {
|
|
655
733
|
event.preventDefault();
|
|
656
734
|
closeOpenedDropdown();
|
|
657
735
|
return;
|
|
658
736
|
}
|
|
659
|
-
// Enter не выбирает элемент, а лишь подтверждает введенное значение,
|
|
660
|
-
// поэтому коммитим значение как при потере фокуса и подсвечиваем поле.
|
|
737
|
+
// Enter не выбирает элемент, а лишь подтверждает введенное значение, как потеря фокуса.
|
|
661
738
|
commitEditingValue();
|
|
662
|
-
|
|
739
|
+
blinkInput();
|
|
663
740
|
if (isDropdownOpened) {
|
|
664
741
|
event.preventDefault();
|
|
665
742
|
closeDropdown();
|
|
@@ -711,19 +788,16 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
711
788
|
};
|
|
712
789
|
var handlePaste = function (event) { return handlePasteBySource(event, false); };
|
|
713
790
|
var handleMobilePaste = function (event) { return handlePasteBySource(event, true); };
|
|
714
|
-
var handleNativeValueChange = useCallback(function (nextValue) {
|
|
715
|
-
commitValue(nextValue);
|
|
716
|
-
}, [commitValue]);
|
|
717
791
|
return (React.createElement(CommonWrapper, __assign({}, props),
|
|
718
792
|
React.createElement("span", { ref: rootRef, className: styles.root(), "data-tid": TimePickerDataTids.root },
|
|
719
793
|
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 })),
|
|
720
|
-
canUseMobileNativeTimePicker && (React.createElement(NativeTimeInput, { "data-tid": TimePickerDataTids.nativeInput, disabled: disabled, ref: nativeInputRef, format: format, minTime: minTime, maxTime: maxTime, value: editingValue, onValueChange:
|
|
721
|
-
|
|
794
|
+
canUseMobileNativeTimePicker && (React.createElement(NativeTimeInput, { "data-tid": TimePickerDataTids.nativeInput, disabled: disabled, ref: nativeInputRef, format: format, minTime: minTime, maxTime: maxTime, value: editingValue, onValueChange: commitValue })),
|
|
795
|
+
isMobileDropdownActive && (React.createElement(TimePickerMobilePopup, { id: popupId, value: displayValue, inputRef: mobileInputRef, disabled: disabled, format: format, size: size, rightIcon: inputRightIcon, suffix: suffix, resolvedItems: resolvedItems, renderItem: renderItem, isLoading: isLoadingItems, highlightedItemIndex: highlightedItemIndex, selectedValue: selectedValue, 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: handleClick, onKeyDown: handleMobileKeyDown, onMouseDownCapture: handleMobileMouseDownCapture, onMouseUp: handleMobileMouseUp, onPaste: handleMobilePaste, onSelectSegment: handleMobileSelectSegmentByMouse, onSelectItem: selectItem, onCloseRequest: closeOpenedDropdown })),
|
|
722
796
|
hasDropdown &&
|
|
723
797
|
!canUseMobileNativeTimePicker &&
|
|
724
798
|
!canUseMobileDropdownTimePicker &&
|
|
725
799
|
isDropdownOpened &&
|
|
726
800
|
hasDropdownContent &&
|
|
727
|
-
rootRef.current && (React.createElement(TimePickerPopup, { id: popupId, anchorElement: rootRef.current, menuPos: menuPos, menuAlign: menuAlign, menuWidth: menuWidth, popupMaxHeight: popupMaxHeight, format: format, size: size, resolvedItems: resolvedItems, renderItem: renderItem, isLoading: isLoadingItems, highlightedItemIndex: highlightedItemIndex,
|
|
801
|
+
rootRef.current && (React.createElement(TimePickerPopup, { id: popupId, anchorElement: rootRef.current, menuPos: menuPos, menuAlign: menuAlign, menuWidth: menuWidth, popupMaxHeight: popupMaxHeight, format: format, size: size, resolvedItems: resolvedItems, renderItem: renderItem, isLoading: isLoadingItems, highlightedItemIndex: highlightedItemIndex, selectedValue: selectedValue, itemRefs: itemRefs, onSelectItem: selectItem })))));
|
|
728
802
|
}), { validate: validateTimePicker });
|
|
729
803
|
//# sourceMappingURL=TimePicker.js.map
|