@skbkontur/react-ui 6.1.7-c386f.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 +24 -23
- package/components/TimePicker/TimePicker.js +196 -132
- 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,33 +262,26 @@ 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
|
-
* элемент коммитится как есть, `null` очищает поле, а `undefined` оставляет значение прежним.
|
|
238
|
-
*/
|
|
239
279
|
var handleUnexpectedInput = useCallback(function (unexpectedValue) {
|
|
240
280
|
if (!onUnexpectedInput) {
|
|
241
|
-
|
|
281
|
+
blinkInput();
|
|
242
282
|
return;
|
|
243
283
|
}
|
|
244
|
-
var returnedValue = onUnexpectedInput(unexpectedValue,
|
|
284
|
+
var returnedValue = onUnexpectedInput(unexpectedValue, blinkInput);
|
|
245
285
|
if (returnedValue === undefined) {
|
|
246
286
|
return;
|
|
247
287
|
}
|
|
@@ -250,40 +290,31 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
250
290
|
return;
|
|
251
291
|
}
|
|
252
292
|
commitItem(returnedValue);
|
|
253
|
-
}, [
|
|
293
|
+
}, [blinkInput, commitItem, commitValue, onUnexpectedInput]);
|
|
294
|
+
/** Закрывает открытое меню: мобильный попап заодно завершает сессию ввода, как потеря фокуса. */
|
|
254
295
|
var closeOpenedDropdown = useCallback(function () {
|
|
255
|
-
var _a;
|
|
256
|
-
if (
|
|
296
|
+
var _a, _b;
|
|
297
|
+
if (isMobileDropdownActive) {
|
|
298
|
+
// Фокус может остаться в любом из двух полей: инпут попапа не всегда получает его —
|
|
299
|
+
// например мобильный браузер отказывается переводить фокус без жеста пользователя.
|
|
300
|
+
// Гасим оба, чтобы закрытие попапа всегда давало событие потери фокуса.
|
|
257
301
|
(_a = mobileInputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
302
|
+
(_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.blur();
|
|
258
303
|
setIsInputFocused(false);
|
|
259
304
|
resetMouseInteractionState();
|
|
260
305
|
closeDropdown();
|
|
261
306
|
commitEditingValue();
|
|
262
307
|
var blurEvent = mobileBlurEventRef.current;
|
|
263
308
|
mobileBlurEventRef.current = null;
|
|
264
|
-
|
|
265
|
-
onBlur === null || onBlur === void 0 ? void 0 : onBlur(blurEvent);
|
|
266
|
-
}
|
|
309
|
+
emitBlur(blurEvent);
|
|
267
310
|
return;
|
|
268
311
|
}
|
|
269
312
|
closeDropdown();
|
|
270
|
-
}, [
|
|
271
|
-
canUseMobileDropdownTimePicker,
|
|
272
|
-
closeDropdown,
|
|
273
|
-
commitEditingValue,
|
|
274
|
-
isDropdownOpened,
|
|
275
|
-
onBlur,
|
|
276
|
-
resetMouseInteractionState,
|
|
277
|
-
]);
|
|
313
|
+
}, [closeDropdown, commitEditingValue, emitBlur, isMobileDropdownActive, resetMouseInteractionState]);
|
|
278
314
|
var selectItem = useCallback(function (item) {
|
|
279
315
|
commitItem(item);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
closeDropdown();
|
|
285
|
-
}
|
|
286
|
-
}, [canUseMobileDropdownTimePicker, closeDropdown, closeOpenedDropdown, commitItem]);
|
|
316
|
+
closeOpenedDropdown();
|
|
317
|
+
}, [closeOpenedDropdown, commitItem]);
|
|
287
318
|
var isSelectHighlightedItem = useCallback(function (event) {
|
|
288
319
|
var _a, _b;
|
|
289
320
|
if (highlightedItemIndex === null) {
|
|
@@ -331,27 +362,20 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
331
362
|
}, [canUseMobileNativeTimePicker, disabled, getActiveInput, openDropdown, openNativeTimePicker]);
|
|
332
363
|
var blurInput = useCallback(function () {
|
|
333
364
|
var _a;
|
|
334
|
-
if (
|
|
365
|
+
if (isMobileDropdownActive) {
|
|
335
366
|
closeOpenedDropdown();
|
|
336
367
|
return;
|
|
337
368
|
}
|
|
338
369
|
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.blur();
|
|
339
|
-
}, [
|
|
340
|
-
var closeInputDropdown = useCallback(function () {
|
|
341
|
-
closeOpenedDropdown();
|
|
342
|
-
}, [closeOpenedDropdown]);
|
|
343
|
-
var blinkInput = useCallback(function () {
|
|
344
|
-
var _a;
|
|
345
|
-
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.blink();
|
|
346
|
-
}, [getActiveInput]);
|
|
370
|
+
}, [closeOpenedDropdown, getActiveInput, isMobileDropdownActive]);
|
|
347
371
|
useImperativeHandle(ref, function () { return ({
|
|
348
372
|
focus: focusInput,
|
|
349
373
|
blur: blurInput,
|
|
350
374
|
blink: blinkInput,
|
|
351
375
|
open: openInputDropdown,
|
|
352
|
-
close:
|
|
376
|
+
close: closeOpenedDropdown,
|
|
353
377
|
getRootNode: function () { return rootRef.current; },
|
|
354
|
-
}); }, [blinkInput, blurInput,
|
|
378
|
+
}); }, [blinkInput, blurInput, closeOpenedDropdown, focusInput, openInputDropdown]);
|
|
355
379
|
useLayoutEffect(function () {
|
|
356
380
|
if (!isDropdownOpened) {
|
|
357
381
|
return;
|
|
@@ -373,7 +397,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
373
397
|
if (!disabled) {
|
|
374
398
|
return;
|
|
375
399
|
}
|
|
376
|
-
if (
|
|
400
|
+
if (isMobileDropdownActive) {
|
|
377
401
|
closeOpenedDropdown();
|
|
378
402
|
return;
|
|
379
403
|
}
|
|
@@ -386,94 +410,145 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
386
410
|
commitEditingValue();
|
|
387
411
|
}
|
|
388
412
|
}, [
|
|
389
|
-
canUseMobileDropdownTimePicker,
|
|
390
413
|
closeDropdown,
|
|
391
414
|
closeOpenedDropdown,
|
|
392
415
|
commitEditingValue,
|
|
393
416
|
disabled,
|
|
394
417
|
isDropdownOpened,
|
|
395
418
|
isInputFocused,
|
|
419
|
+
isMobileDropdownActive,
|
|
396
420
|
resetMouseInteractionState,
|
|
397
421
|
]);
|
|
422
|
+
var previousIsMobileLayoutRef = useRef(isMobileLayout);
|
|
423
|
+
/**
|
|
424
|
+
* Смена верстки переносит открытое меню в нужную версию: как в ComboBox, меню не закрывается.
|
|
425
|
+
* Начатый ввод переезжает вместе с ним, а если переезжать некуда — коммитится, как при потере фокуса.
|
|
426
|
+
*/
|
|
398
427
|
useEffect(function () {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
+
};
|
|
405
473
|
var handleFocusBySource = function (event, isMobilePopupInput) {
|
|
406
474
|
var shouldSkipOpenDropdown = shouldSkipOpenOnFocusRef.current;
|
|
407
475
|
shouldSkipOpenOnFocusRef.current = false;
|
|
476
|
+
var isFocusMovedInsideControl = isControlFocusedRef.current;
|
|
408
477
|
if (canUseMobileNativeTimePicker) {
|
|
409
478
|
if (!shouldSkipOpenDropdown) {
|
|
410
479
|
openNativeTimePicker();
|
|
411
480
|
}
|
|
412
|
-
|
|
481
|
+
emitFocus(event);
|
|
413
482
|
return;
|
|
414
483
|
}
|
|
415
484
|
if (canUseMobileDropdownTimePicker && !isMobilePopupInput) {
|
|
416
485
|
if (!shouldSkipOpenDropdown) {
|
|
417
486
|
openDropdown();
|
|
418
487
|
}
|
|
488
|
+
// Дальше ввод идет в инпуте попапа, но фокус контрол получает здесь.
|
|
489
|
+
emitFocus(event);
|
|
419
490
|
return;
|
|
420
491
|
}
|
|
421
492
|
setIsInputFocused(true);
|
|
422
493
|
if (!shouldSkipOpenDropdown) {
|
|
423
494
|
openDropdown();
|
|
424
495
|
}
|
|
425
|
-
if (
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
else if (mouseDownSegmentRef.current) {
|
|
430
|
-
selectSegment(mouseDownSegmentRef.current);
|
|
431
|
-
}
|
|
496
|
+
if (mouseDownSegmentRef.current && !isInputFocused) {
|
|
497
|
+
// По пустому полю ввод всегда начинается с часов, а по заполненному — с сегмента под курсором.
|
|
498
|
+
selectSegment(isTimeDisplayEmpty(editingValueRef.current) ? 'hours' : mouseDownSegmentRef.current);
|
|
432
499
|
}
|
|
433
500
|
else {
|
|
434
|
-
|
|
501
|
+
// Переезд фокуса внутри контрола продолжает начатый ввод, поэтому сегмент сохраняется:
|
|
502
|
+
// иначе следующая цифра ушла бы в часы и затерла бы набранное время.
|
|
503
|
+
selectSegment(isFocusMovedInsideControl ? selectedSegment : 'hours');
|
|
435
504
|
}
|
|
436
|
-
|
|
505
|
+
emitFocus(event);
|
|
437
506
|
};
|
|
438
507
|
var handleFocus = function (event) { return handleFocusBySource(event, false); };
|
|
439
508
|
var handleMobileFocus = function (event) { return handleFocusBySource(event, true); };
|
|
440
509
|
var handleBlurBySource = function (event, isMobilePopupInput) {
|
|
441
|
-
|
|
442
|
-
|
|
510
|
+
/**
|
|
511
|
+
* Пока открыт мобильный попап, фокус ходит между полем и его инпутом: контрол остается активным,
|
|
512
|
+
* поэтому наружу о таком blur сообщать нечего. Событие запоминается на случай,
|
|
513
|
+
* если фокус в инпуте попапа так и не оказался — тогда именно оно уйдет в `onBlur` при закрытии.
|
|
514
|
+
*/
|
|
515
|
+
if (isMobileDropdownActive) {
|
|
516
|
+
event.persist();
|
|
517
|
+
mobileBlurEventRef.current = event;
|
|
443
518
|
return;
|
|
444
519
|
}
|
|
445
|
-
if (
|
|
520
|
+
if (canUseMobileNativeTimePicker) {
|
|
521
|
+
// Ввод в мобильной версии не идет через поле, но незакоммиченное значение могло остаться
|
|
522
|
+
// от десктопной версии, если верстка сменилась посреди ввода.
|
|
523
|
+
if (isInputFocused) {
|
|
524
|
+
setIsInputFocused(false);
|
|
525
|
+
resetMouseInteractionState();
|
|
526
|
+
commitEditingValue();
|
|
527
|
+
}
|
|
528
|
+
emitBlur(event);
|
|
446
529
|
return;
|
|
447
530
|
}
|
|
531
|
+
// Инпут закрытого попапа фокуса уже не держит, но его blur может прийти на размонтировании.
|
|
448
532
|
if (isMobilePopupInput) {
|
|
449
|
-
event.persist();
|
|
450
|
-
mobileBlurEventRef.current = event;
|
|
451
533
|
return;
|
|
452
534
|
}
|
|
453
535
|
setIsInputFocused(false);
|
|
454
536
|
resetMouseInteractionState();
|
|
455
537
|
closeDropdown();
|
|
456
538
|
commitEditingValue();
|
|
457
|
-
|
|
539
|
+
emitBlur(event);
|
|
458
540
|
};
|
|
459
541
|
var handleBlur = function (event) { return handleBlurBySource(event, false); };
|
|
460
542
|
var handleMobileBlur = function (event) { return handleBlurBySource(event, true); };
|
|
461
|
-
var
|
|
543
|
+
var handleClick = function (event) {
|
|
462
544
|
if (canUseMobileNativeTimePicker) {
|
|
463
545
|
openNativeTimePicker();
|
|
464
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
465
|
-
return;
|
|
466
546
|
}
|
|
467
|
-
|
|
547
|
+
else {
|
|
468
548
|
openDropdown();
|
|
469
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
470
|
-
return;
|
|
471
549
|
}
|
|
472
|
-
openDropdown();
|
|
473
550
|
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
474
551
|
};
|
|
475
|
-
var handleClick = function (event) { return handleClickBySource(event, false); };
|
|
476
|
-
var handleMobileClick = function (event) { return handleClickBySource(event, true); };
|
|
477
552
|
var handleMouseDownCaptureBySource = function (event, isMobilePopupInput) {
|
|
478
553
|
var _a, _b;
|
|
479
554
|
if (canUseMobileNativeTimePicker || (canUseMobileDropdownTimePicker && !isMobilePopupInput)) {
|
|
@@ -481,12 +556,10 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
481
556
|
}
|
|
482
557
|
var currentInputRef = isMobilePopupInput ? mobileInputRef : inputRef;
|
|
483
558
|
var segment = (_b = (_a = currentInputRef.current) === null || _a === void 0 ? void 0 : _a.getSegment(event.target)) !== null && _b !== void 0 ? _b : null;
|
|
484
|
-
|
|
485
|
-
if (isInputFocused && !isFragment) {
|
|
559
|
+
if (isInputFocused && segment === null) {
|
|
486
560
|
event.preventDefault();
|
|
487
561
|
}
|
|
488
562
|
isMouseFocusRef.current = !isInputFocused;
|
|
489
|
-
isMouseDownOnFragmentRef.current = isFragment;
|
|
490
563
|
mouseDownSegmentRef.current = segment;
|
|
491
564
|
};
|
|
492
565
|
var handleMouseDownCapture = function (event) {
|
|
@@ -647,12 +720,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
647
720
|
if (isKeyEscape(event)) {
|
|
648
721
|
if (isDropdownOpened) {
|
|
649
722
|
event.preventDefault();
|
|
650
|
-
|
|
651
|
-
closeOpenedDropdown();
|
|
652
|
-
}
|
|
653
|
-
else {
|
|
654
|
-
closeDropdown();
|
|
655
|
-
}
|
|
723
|
+
closeOpenedDropdown();
|
|
656
724
|
}
|
|
657
725
|
return;
|
|
658
726
|
}
|
|
@@ -661,15 +729,14 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
661
729
|
event.preventDefault();
|
|
662
730
|
return;
|
|
663
731
|
}
|
|
664
|
-
if (
|
|
732
|
+
if (isMobileDropdownActive) {
|
|
665
733
|
event.preventDefault();
|
|
666
734
|
closeOpenedDropdown();
|
|
667
735
|
return;
|
|
668
736
|
}
|
|
669
|
-
// Enter не выбирает элемент, а лишь подтверждает введенное значение,
|
|
670
|
-
// поэтому коммитим значение как при потере фокуса и подсвечиваем поле.
|
|
737
|
+
// Enter не выбирает элемент, а лишь подтверждает введенное значение, как потеря фокуса.
|
|
671
738
|
commitEditingValue();
|
|
672
|
-
|
|
739
|
+
blinkInput();
|
|
673
740
|
if (isDropdownOpened) {
|
|
674
741
|
event.preventDefault();
|
|
675
742
|
closeDropdown();
|
|
@@ -721,19 +788,16 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
721
788
|
};
|
|
722
789
|
var handlePaste = function (event) { return handlePasteBySource(event, false); };
|
|
723
790
|
var handleMobilePaste = function (event) { return handlePasteBySource(event, true); };
|
|
724
|
-
var handleNativeValueChange = useCallback(function (nextValue) {
|
|
725
|
-
commitValue(nextValue);
|
|
726
|
-
}, [commitValue]);
|
|
727
791
|
return (React.createElement(CommonWrapper, __assign({}, props),
|
|
728
792
|
React.createElement("span", { ref: rootRef, className: styles.root(), "data-tid": TimePickerDataTids.root },
|
|
729
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 })),
|
|
730
|
-
canUseMobileNativeTimePicker && (React.createElement(NativeTimeInput, { "data-tid": TimePickerDataTids.nativeInput, disabled: disabled, ref: nativeInputRef, format: format, minTime: minTime, maxTime: maxTime, value: editingValue, onValueChange:
|
|
731
|
-
|
|
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 })),
|
|
732
796
|
hasDropdown &&
|
|
733
797
|
!canUseMobileNativeTimePicker &&
|
|
734
798
|
!canUseMobileDropdownTimePicker &&
|
|
735
799
|
isDropdownOpened &&
|
|
736
800
|
hasDropdownContent &&
|
|
737
|
-
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 })))));
|
|
738
802
|
}), { validate: validateTimePicker });
|
|
739
803
|
//# sourceMappingURL=TimePicker.js.map
|