@skbkontur/react-ui 6.1.7-1c902.0 → 6.1.7-4c72d.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/MenuItem/MenuItem.d.ts +1 -1
- package/components/MenuItem/MenuItem.js +0 -1
- package/components/MenuItem/MenuItem.js.map +1 -1
- package/components/MenuItem/MenuItem.styles.d.ts +0 -1
- package/components/MenuItem/MenuItem.styles.js +7 -10
- package/components/MenuItem/MenuItem.styles.js.map +1 -1
- package/components/TimePicker/TimePicker.d.ts +44 -25
- package/components/TimePicker/TimePicker.js +373 -191
- package/components/TimePicker/TimePicker.js.map +1 -1
- package/components/TimePicker/TimePicker.styles.d.ts +3 -1
- package/components/TimePicker/TimePicker.styles.js +9 -3
- package/components/TimePicker/TimePicker.styles.js.map +1 -1
- package/components/TimePicker/TimePickerItems.d.ts +5 -1
- package/components/TimePicker/TimePickerItems.js +39 -29
- package/components/TimePicker/TimePickerItems.js.map +1 -1
- package/components/TimePicker/TimePickerMobilePopup.d.ts +5 -2
- package/components/TimePicker/TimePickerMobilePopup.js +4 -4
- package/components/TimePicker/TimePickerMobilePopup.js.map +1 -1
- package/components/TimePicker/TimePickerPopup.d.ts +5 -1
- package/components/TimePicker/TimePickerPopup.js +4 -4
- package/components/TimePicker/TimePickerPopup.js.map +1 -1
- package/components/TimePicker/helpers/TimePicker.constants.d.ts +8 -1
- package/components/TimePicker/helpers/TimePicker.constants.js +8 -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 +20 -22
- package/components/TimePicker/helpers/TimePicker.shared.js +27 -24
- package/components/TimePicker/helpers/TimePicker.shared.js.map +1 -1
- package/components/TimePicker/helpers/TimePicker.value.d.ts +22 -11
- package/components/TimePicker/helpers/TimePicker.value.js +69 -37
- 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.d.ts +6 -1
- package/components/TimePicker/hooks/useTimePickerDropdown.js +15 -33
- package/components/TimePicker/hooks/useTimePickerDropdown.js.map +1 -1
- package/components/TimePicker/hooks/useTimePickerSource.d.ts +29 -0
- package/components/TimePicker/hooks/useTimePickerSource.js +67 -0
- package/components/TimePicker/hooks/useTimePickerSource.js.map +1 -0
- package/components/TimePicker/hooks/useTimePickerValue.d.ts +15 -4
- package/components/TimePicker/hooks/useTimePickerValue.js +40 -20
- package/components/TimePicker/hooks/useTimePickerValue.js.map +1 -1
- package/components/TimePicker/index.d.ts +3 -1
- package/components/TimePicker/index.js +1 -0
- package/components/TimePicker/index.js.map +1 -1
- package/components/TimePicker/locale/locales/en.js +2 -0
- package/components/TimePicker/locale/locales/en.js.map +1 -1
- package/components/TimePicker/locale/locales/ru.js +2 -0
- package/components/TimePicker/locale/locales/ru.js.map +1 -1
- package/components/TimePicker/locale/types.d.ts +2 -0
- package/components/TimePicker/locale/types.js.map +1 -1
- package/internal/NativeTimeInput/NativeTimeInput.d.ts +4 -0
- package/internal/NativeTimeInput/NativeTimeInput.js +7 -2
- package/internal/NativeTimeInput/NativeTimeInput.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 +30 -8
- package/internal/TimeInput/TimeFragments.js.map +1 -1
- package/internal/TimeInput/TimeFragments.styles.d.ts +15 -0
- package/internal/TimeInput/TimeFragments.styles.js +39 -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
|
@@ -22,103 +22,169 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
22
22
|
};
|
|
23
23
|
import React, { useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
24
24
|
import { CommonWrapper } from '../../internal/CommonWrapper/index.js';
|
|
25
|
+
import { extractCommonProps } from '../../internal/CommonWrapper/utils/extractCommonProps.js';
|
|
25
26
|
import { LoadingIcon } from '../../internal/icons2022/LoadingIcon.js';
|
|
26
27
|
import { NativeTimeInput } from '../../internal/NativeTimeInput/index.js';
|
|
27
28
|
import { TimeInput } from '../../internal/TimeInput/index.js';
|
|
28
|
-
import { isIOS
|
|
29
|
+
import { isIOS } from '../../lib/client.js';
|
|
29
30
|
import { isKeyArrowDown, isKeyArrowLeft, isKeyArrowRight, isKeyArrowUp, isKeyBackspace, isKeyChar, isKeyDelete, isKeyEnter, isKeyEscape, isKeySpace, isKeyTab, isModShift, isShortcutSelectAll, } from '../../lib/events/keyboard/identifiers.js';
|
|
30
31
|
import { forwardRefAndName } from '../../lib/forwardRefAndName.js';
|
|
31
32
|
import { useLocaleForControl } from '../../lib/locale/useLocaleForControl.js';
|
|
32
33
|
import { useStyles } from '../../lib/renderEnvironment/index.js';
|
|
33
34
|
import { ThemeContext } from '../../lib/theming/ThemeContext.js';
|
|
34
35
|
import { getRandomID } from '../../lib/utils.js';
|
|
36
|
+
import { useResponsiveLayout } from '../ResponsiveLayout/index.js';
|
|
35
37
|
import { scrollSelectedItemIntoView } from './helpers/scrollSelectedItemIntoView.js';
|
|
36
38
|
import { DIGIT_REGEXP, EMPTY_SEGMENT, EMPTY_VALUE, TimePickerDataTids } from './helpers/TimePicker.constants.js';
|
|
37
39
|
import { commitTimeSegmentOnLeave, deleteTimeSegmentDigit, formatDigitToTimeSegment, shiftTimeSegmentValue, } from './helpers/TimePicker.editing.js';
|
|
38
40
|
import { getTimePickerPopupMaxHeight } from './helpers/TimePicker.layout.js';
|
|
39
41
|
import { getNextTimeSegment, getPreviousTimeSegment } from './helpers/TimePicker.selection.js';
|
|
40
|
-
import { isTimeItem, isTimeItemObject, isTimeMenuItem, timeValueToItem, } from './helpers/TimePicker.shared.js';
|
|
41
|
-
import {
|
|
42
|
+
import { isNavigableMenuElement, isTimeItem, isTimeItemObject, isTimeMenuItem, timeValueToItem, } from './helpers/TimePicker.shared.js';
|
|
43
|
+
import { filterTimeItems, getEmptyDisplayValue, getTimeFilterQuery, getTimeValueString, isTimeDisplayEmpty, normalizeTimeValue, parsePastedTimeValue, replaceTimeSegment, resolveTimeItems, } from './helpers/TimePicker.value.js';
|
|
42
44
|
import { validateTimePicker } from './helpers/validateTimePicker.js';
|
|
43
45
|
import { useTimePickerDropdown } from './hooks/useTimePickerDropdown.js';
|
|
44
46
|
import { useTimePickerSelection } from './hooks/useTimePickerSelection.js';
|
|
47
|
+
import { useTimePickerSource } from './hooks/useTimePickerSource.js';
|
|
45
48
|
import { useTimePickerValue } from './hooks/useTimePickerValue.js';
|
|
46
49
|
import { TimePickerLocaleHelper } from './locale/index.js';
|
|
47
50
|
import { getStyles } from './TimePicker.styles.js';
|
|
48
51
|
import { TimePickerMobilePopup } from './TimePickerMobilePopup.js';
|
|
49
52
|
import { TimePickerPopup } from './TimePickerPopup.js';
|
|
53
|
+
/** Пустой список для клавиатуры: не создает новую ссылку на каждый рендер. */
|
|
54
|
+
var EMPTY_MENU_ITEMS = [];
|
|
50
55
|
/** Поле с временем помогает пользователю быстро и удобно указать время в правильном формате.
|
|
51
56
|
* В поле можно ввести время с клавиатуры либо выбрать из выпадающего списка,
|
|
52
57
|
* передав массив или функцию в проп `source`.
|
|
53
58
|
*/
|
|
54
59
|
export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (props, ref) {
|
|
55
|
-
var
|
|
60
|
+
var _a;
|
|
61
|
+
// `className`, `style` и data-атрибуты применяет `CommonWrapper` к корневому элементу,
|
|
62
|
+
// поэтому в поле ввода они не должны уехать повторно.
|
|
63
|
+
var _b = extractCommonProps(props), notCommonProps = _b[1];
|
|
64
|
+
var disabled = notCommonProps.disabled, _c = notCommonProps.useMobileNativeTimePicker, useMobileNativeTimePicker = _c === void 0 ? false : _c, _d = notCommonProps.format, format = _d === void 0 ? 'HH:mm' : _d, _e = notCommonProps.size, size = _e === void 0 ? 'small' : _e, source = notCommonProps.source, renderItem = notCommonProps.renderItem, menuPos = notCommonProps.menuPos, menuAlign = notCommonProps.menuAlign, menuWidth = notCommonProps.menuWidth, minTime = notCommonProps.minTime, maxTime = notCommonProps.maxTime, rightIcon = notCommonProps.rightIcon, suffix = notCommonProps.suffix, corners = notCommonProps.corners, value = notCommonProps.value, valueToItem = notCommonProps.valueToItem, onValueChange = notCommonProps.onValueChange, onInputValueChange = notCommonProps.onInputValueChange, onUnexpectedInput = notCommonProps.onUnexpectedInput, onFocus = notCommonProps.onFocus, onBlur = notCommonProps.onBlur, onClick = notCommonProps.onClick, onKeyDown = notCommonProps.onKeyDown, onPaste = notCommonProps.onPaste, inputProps = __rest(notCommonProps, ["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
65
|
var theme = useContext(ThemeContext);
|
|
57
66
|
var styles = useStyles(getStyles);
|
|
58
67
|
var locale = useLocaleForControl('TimePicker', TimePickerLocaleHelper);
|
|
59
|
-
var
|
|
68
|
+
var _f = useState(false), isInputFocused = _f[0], setIsInputFocused = _f[1];
|
|
60
69
|
var inputRef = useRef(null);
|
|
61
70
|
var mobileInputRef = useRef(null);
|
|
62
71
|
var nativeInputRef = useRef(null);
|
|
63
72
|
var rootRef = useRef(null);
|
|
64
73
|
var itemRefs = useRef(new Map());
|
|
65
74
|
var isMouseFocusRef = useRef(false);
|
|
66
|
-
|
|
75
|
+
/** Сегмент, по которому нажали мышью, или `null`, если нажали мимо сегментов. */
|
|
67
76
|
var mouseDownSegmentRef = useRef(null);
|
|
68
77
|
var mobileBlurEventRef = useRef(null);
|
|
78
|
+
var shouldSkipOpenOnFocusRef = useRef(false);
|
|
79
|
+
/**
|
|
80
|
+
* Контрол считает себя сфокусированным: о фокусе сообщено наружу, а сессия ввода не закончена.
|
|
81
|
+
* Внутри одной сессии фокус может переезжать между полем и инпутом мобильного попапа —
|
|
82
|
+
* например вместе со сменой верстки, — и такой переезд не начинает сессию заново.
|
|
83
|
+
*/
|
|
84
|
+
var isControlFocusedRef = useRef(false);
|
|
85
|
+
/** Фокус, который контрол сам переводит в скрытый нативный инпут, открывая системный пикер. */
|
|
86
|
+
var isMovingFocusToNativePickerRef = useRef(false);
|
|
69
87
|
var popupIdRef = useRef(TimePickerDataTids.popup + getRandomID());
|
|
70
|
-
|
|
71
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Мобильная версия включается медиавыражением темы, как в ComboBox:
|
|
90
|
+
* поле переключается между мобильной и десктопной версией вместе с версткой страницы.
|
|
91
|
+
*/
|
|
92
|
+
var isMobileLayout = useResponsiveLayout().isMobile;
|
|
93
|
+
var canUseMobileNativeTimePicker = useMobileNativeTimePicker && isMobileLayout;
|
|
72
94
|
var isSourceFunction = typeof source === 'function';
|
|
73
|
-
var
|
|
74
|
-
var
|
|
75
|
-
var
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
var hasDropdown = isSourceFunction || (Array.isArray(source) && source.length > 0);
|
|
96
|
+
var _g = useState(false), isDropdownOpened = _g[0], setIsDropdownOpened = _g[1];
|
|
97
|
+
var _h = useState(false), isEditedAfterOpen = _h[0], setIsEditedAfterOpen = _h[1];
|
|
98
|
+
/**
|
|
99
|
+
* Форма элементов функции-источника: последняя, о которой компонент знает.
|
|
100
|
+
* Форма запоминается, потому что элементы функции очищаются при закрытии меню,
|
|
101
|
+
* а коммит значения после закрытия должен отдать наружу элемент той же формы.
|
|
102
|
+
* Для массива форма всегда считается по текущим элементам — запоминать нечего.
|
|
103
|
+
*/
|
|
104
|
+
var lastSourceItemShapeRef = useRef(null);
|
|
80
105
|
/**
|
|
81
106
|
* Форма элемента, который компонент собирает для времени без элемента.
|
|
82
107
|
* Повторяет форму элементов источника, а если их нет — форму внешнего `value`,
|
|
83
108
|
* чтобы наружу уходило то же, что пришло внутрь.
|
|
109
|
+
* Значение читается при коммите, поэтому обновляется в эффекте: так его не меняет отброшенный рендер.
|
|
84
110
|
*/
|
|
85
|
-
var
|
|
86
|
-
(value !== null && value !== undefined && isTimeItemObject(value));
|
|
111
|
+
var shouldBuildItemAsObjectRef = useRef(false);
|
|
87
112
|
var valueToItemRef = useRef(valueToItem);
|
|
88
113
|
valueToItemRef.current = valueToItem;
|
|
89
|
-
var
|
|
90
|
-
shouldBuildItemAsObjectRef.current = shouldBuildItemAsObject;
|
|
91
|
-
var handleCommitValue = useCallback(function (committedValue, item) {
|
|
114
|
+
var handleCommitValue = useCallback(function (nextValue, item) {
|
|
92
115
|
var _a, _b;
|
|
93
116
|
if (!onValueChange) {
|
|
94
117
|
return;
|
|
95
118
|
}
|
|
96
|
-
if (
|
|
119
|
+
if (nextValue === EMPTY_VALUE) {
|
|
97
120
|
onValueChange(null, EMPTY_VALUE);
|
|
98
121
|
return;
|
|
99
122
|
}
|
|
100
|
-
var nextItem = (_b = item !== null && item !== void 0 ? item : (_a = valueToItemRef.current) === null || _a === void 0 ? void 0 : _a.call(valueToItemRef,
|
|
101
|
-
onValueChange(nextItem,
|
|
123
|
+
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);
|
|
124
|
+
onValueChange(nextItem, nextValue);
|
|
102
125
|
}, [onValueChange]);
|
|
103
|
-
var
|
|
126
|
+
var onBlurRef = useRef(onBlur);
|
|
127
|
+
onBlurRef.current = onBlur;
|
|
128
|
+
var pendingBlurEventRef = useRef(null);
|
|
129
|
+
var _j = useState(0), blurRequestId = _j[0], setBlurRequestId = _j[1];
|
|
130
|
+
/**
|
|
131
|
+
* Завершает сессию фокуса и откладывает `onBlur` до конца рендера, как это делает ComboBox.
|
|
132
|
+
* Значение коммитится синхронно с потерей фокуса, поэтому вызванный тут же `onBlur`
|
|
133
|
+
* попал бы в обработчик с состоянием родителя до изменения значения — например с пустым временем.
|
|
134
|
+
* Событие может отсутствовать: сессия все равно закрывается, но наружу ничего не уходит.
|
|
135
|
+
*/
|
|
136
|
+
var emitBlur = useCallback(function (event) {
|
|
137
|
+
if (!isControlFocusedRef.current) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
isControlFocusedRef.current = false;
|
|
141
|
+
if (!event || !onBlurRef.current) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
event.persist();
|
|
145
|
+
pendingBlurEventRef.current = event;
|
|
146
|
+
setBlurRequestId(function (currentId) { return currentId + 1; });
|
|
147
|
+
}, []);
|
|
148
|
+
var flushPendingBlur = useCallback(function () {
|
|
149
|
+
var _a;
|
|
150
|
+
var pendingBlurEvent = pendingBlurEventRef.current;
|
|
151
|
+
if (!pendingBlurEvent) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
pendingBlurEventRef.current = null;
|
|
155
|
+
(_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, pendingBlurEvent);
|
|
156
|
+
}, []);
|
|
157
|
+
useEffect(flushPendingBlur, [blurRequestId, flushPendingBlur]);
|
|
158
|
+
/** Родитель мог убрать поле в ответ на коммит значения — отложенное событие все равно должно уйти. */
|
|
159
|
+
useEffect(function () { return flushPendingBlur; }, [flushPendingBlur]);
|
|
160
|
+
var _k = useTimePickerValue({
|
|
104
161
|
isInputFocused: isInputFocused,
|
|
105
162
|
format: format,
|
|
106
163
|
externalValue: getTimeValueString(value),
|
|
164
|
+
isValueControlled: value !== undefined,
|
|
107
165
|
onCommitValue: handleCommitValue,
|
|
108
166
|
onInputValueChange: onInputValueChange,
|
|
109
|
-
}), editingValue =
|
|
167
|
+
}), editingValue = _k.editingValue, editingValueRef = _k.editingValueRef, committedValue = _k.committedValue, updateEditingValue = _k.updateEditingValue, commitEditingValue = _k.commitEditingValue, commitValue = _k.commitValue, commitItem = _k.commitItem;
|
|
110
168
|
var filterQuery = isEditedAfterOpen ? getTimeFilterQuery(editingValue, format) : EMPTY_VALUE;
|
|
111
|
-
var
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
169
|
+
var _l = useTimePickerSource({ source: source, format: format, isDropdownOpened: isDropdownOpened, filterQuery: filterQuery }), sourceValueItems = _l.items, fetchedItemsQuery = _l.fetchedQuery, isLoadingItems = _l.isLoading, isSourceFailed = _l.isFailed, retrySource = _l.retry;
|
|
170
|
+
useEffect(function () {
|
|
171
|
+
if (!isSourceFunction || sourceValueItems.some(isTimeItem)) {
|
|
172
|
+
lastSourceItemShapeRef.current = sourceValueItems.some(function (item) { return isTimeItem(item) && isTimeItemObject(item); });
|
|
173
|
+
}
|
|
174
|
+
shouldBuildItemAsObjectRef.current =
|
|
175
|
+
lastSourceItemShapeRef.current === true || (value !== null && value !== undefined && isTimeItemObject(value));
|
|
176
|
+
}, [isSourceFunction, sourceValueItems, value]);
|
|
177
|
+
var resolvedItems = useMemo(function () { return resolveTimeItems(filterTimeItems(sourceValueItems, filterQuery, format), format, minTime, maxTime); }, [filterQuery, format, maxTime, minTime, sourceValueItems]);
|
|
118
178
|
var popupMaxHeight = getTimePickerPopupMaxHeight(size, theme);
|
|
119
179
|
var ariaPlaceholder = format === 'HH:mm' ? locale.ariaPlaceholderHHMM : locale.ariaPlaceholderHHMMSS;
|
|
120
|
-
|
|
121
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Время, отмеченное в меню выбранным.
|
|
182
|
+
* Пока пользователь не начал править значение при открытом меню, это то же время, что в поле.
|
|
183
|
+
* С началом правки отмечено остается закоммиченное значение: набор фильтра — например `10` —
|
|
184
|
+
* не должен отмечать элемент `10:00` как уже выбранный.
|
|
185
|
+
*/
|
|
186
|
+
var selectedValue = normalizeTimeValue(isEditedAfterOpen ? committedValue : editingValue, format);
|
|
187
|
+
var selectedItemIndex = resolvedItems.findIndex(function (item) { return isTimeMenuItem(item) && normalizeTimeValue(item.value, format) === selectedValue; });
|
|
122
188
|
var autoHighlightKey = null;
|
|
123
189
|
if (filterQuery !== EMPTY_VALUE) {
|
|
124
190
|
if (isSourceFunction) {
|
|
@@ -130,28 +196,42 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
130
196
|
autoHighlightKey = "source-array:".concat(filterQuery);
|
|
131
197
|
}
|
|
132
198
|
}
|
|
133
|
-
var canUseMobileDropdownTimePicker = hasDropdown &&
|
|
134
|
-
|
|
199
|
+
var canUseMobileDropdownTimePicker = hasDropdown && isMobileLayout && !canUseMobileNativeTimePicker;
|
|
200
|
+
/** Ввод идет в мобильном попапе: он открыт, а нативного пикера в этом режиме нет. */
|
|
201
|
+
var isMobileDropdownActive = canUseMobileDropdownTimePicker && isDropdownOpened;
|
|
202
|
+
/**
|
|
203
|
+
* Меню с одними лишь заголовками и разделителями показывать не о чем,
|
|
204
|
+
* поэтому при активном фильтре нужен хотя бы один подошедший элемент со временем.
|
|
205
|
+
*/
|
|
206
|
+
var hasDropdownContent = isLoadingItems ||
|
|
207
|
+
isSourceFailed ||
|
|
208
|
+
(filterQuery === EMPTY_VALUE ? resolvedItems.length > 0 : resolvedItems.some(isTimeMenuItem));
|
|
209
|
+
/**
|
|
210
|
+
* Элементы, с которыми работает клавиатура.
|
|
211
|
+
* Пока меню скрыто, их нет: иначе стрелки ходили бы по невидимому списку,
|
|
212
|
+
* а Enter выбирал бы невидимый элемент вместо коммита введенного времени.
|
|
213
|
+
*/
|
|
214
|
+
var menuItems = hasDropdownContent ? resolvedItems : EMPTY_MENU_ITEMS;
|
|
135
215
|
var inputRightIcon = isLoadingItems && resolvedItems.length > 0 ? (React.createElement("span", { "data-tid": TimePickerDataTids.inputLoading },
|
|
136
216
|
React.createElement(LoadingIcon, { size: size }))) : (rightIcon);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
217
|
+
/** Сфокусированное поле всегда показывает сегменты, а пустое без фокуса — ничего. */
|
|
218
|
+
var getDisplayValue = function () {
|
|
219
|
+
if (isInputFocused) {
|
|
220
|
+
return editingValue || getEmptyDisplayValue(format);
|
|
221
|
+
}
|
|
222
|
+
return isTimeDisplayEmpty(editingValue) ? EMPTY_VALUE : editingValue;
|
|
223
|
+
};
|
|
224
|
+
var displayValue = getDisplayValue();
|
|
144
225
|
var resetMouseInteractionState = useCallback(function () {
|
|
145
226
|
isMouseFocusRef.current = false;
|
|
146
|
-
isMouseDownOnFragmentRef.current = false;
|
|
147
227
|
mouseDownSegmentRef.current = null;
|
|
148
228
|
}, []);
|
|
149
229
|
var updateEditingValueAfterUserEdit = useCallback(function (nextEditingValue) {
|
|
150
|
-
if (editingValueRef.current !== nextEditingValue) {
|
|
230
|
+
if (hasDropdown && editingValueRef.current !== nextEditingValue) {
|
|
151
231
|
setIsEditedAfterOpen(true);
|
|
152
232
|
}
|
|
153
233
|
updateEditingValue(nextEditingValue);
|
|
154
|
-
}, [editingValueRef, updateEditingValue]);
|
|
234
|
+
}, [editingValueRef, hasDropdown, updateEditingValue]);
|
|
155
235
|
var normalizeCurrentSegmentIfNeeded = useCallback(function (segment) {
|
|
156
236
|
var nextDisplayValue = commitTimeSegmentOnLeave(editingValue === EMPTY_VALUE ? getEmptyDisplayValue(format) : editingValue, segment, format);
|
|
157
237
|
updateEditingValueAfterUserEdit(nextDisplayValue);
|
|
@@ -161,149 +241,159 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
161
241
|
if (!canUseMobileNativeTimePicker || disabled) {
|
|
162
242
|
return;
|
|
163
243
|
}
|
|
244
|
+
// На iOS системный пикер открывается только фокусом, поэтому фокус уходит из поля в скрытый инпут.
|
|
245
|
+
isMovingFocusToNativePickerRef.current = true;
|
|
164
246
|
if (isIOS) {
|
|
165
247
|
(_a = nativeInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
166
248
|
}
|
|
167
249
|
else {
|
|
168
250
|
(_b = nativeInputRef.current) === null || _b === void 0 ? void 0 : _b.click();
|
|
169
251
|
}
|
|
252
|
+
isMovingFocusToNativePickerRef.current = false;
|
|
170
253
|
}, [canUseMobileNativeTimePicker, disabled]);
|
|
254
|
+
/** Фокус ушел в скрытый нативный инпут: для внешнего мира контрол остается активным. */
|
|
255
|
+
var isFocusMovedToNativePicker = function (event) {
|
|
256
|
+
return isMovingFocusToNativePickerRef.current ||
|
|
257
|
+
(nativeInputRef.current !== null && event.relatedTarget === nativeInputRef.current.getNode());
|
|
258
|
+
};
|
|
171
259
|
var _m = useTimePickerDropdown({
|
|
172
260
|
disabled: disabled,
|
|
173
261
|
hasDropdown: hasDropdown,
|
|
174
|
-
items:
|
|
262
|
+
items: menuItems,
|
|
175
263
|
selectedItemIndex: selectedItemIndex >= 0 ? selectedItemIndex : null,
|
|
176
264
|
autoHighlightKey: autoHighlightKey,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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]);
|
|
265
|
+
isDropdownOpened: isDropdownOpened,
|
|
266
|
+
setIsDropdownOpened: setIsDropdownOpened,
|
|
267
|
+
}), highlightedItemIndex = _m.highlightedItemIndex, openDropdown = _m.openDropdown, closeDropdown = _m.closeDropdown, resetHighlightedItem = _m.resetHighlightedItem, tryNavigateItems = _m.tryNavigateItems;
|
|
268
|
+
/**
|
|
269
|
+
* Фильтрация начинается только после правки значения при открытом меню.
|
|
270
|
+
* Состояние живет ровно столько, сколько открыто меню: правка, которая заново открывает меню,
|
|
271
|
+
* сохраняет свой запрос, а правка при закрытом меню — например стрелками — на список не влияет.
|
|
272
|
+
*/
|
|
205
273
|
useEffect(function () {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
setFetchedItems([]);
|
|
209
|
-
setFetchedItemsQuery(null);
|
|
274
|
+
if (!isDropdownOpened && isEditedAfterOpen) {
|
|
275
|
+
setIsEditedAfterOpen(false);
|
|
210
276
|
}
|
|
211
|
-
}, [isDropdownOpened,
|
|
277
|
+
}, [isDropdownOpened, isEditedAfterOpen]);
|
|
212
278
|
var popupId = popupIdRef.current;
|
|
213
|
-
var
|
|
214
|
-
|
|
215
|
-
var
|
|
279
|
+
var highlightedMenuItem = highlightedItemIndex === null ? undefined : menuItems[highlightedItemIndex];
|
|
280
|
+
/** Кастомный `MenuItem` может принести свой `id` — тогда указывать нужно именно на него. */
|
|
281
|
+
var activeDescendantId = highlightedMenuItem
|
|
282
|
+
? ((_a = (isNavigableMenuElement(highlightedMenuItem) ? highlightedMenuItem.props.id : undefined)) !== null && _a !== void 0 ? _a : "".concat(popupId, "-item-").concat(highlightedItemIndex))
|
|
283
|
+
: undefined;
|
|
284
|
+
var getActiveInput = useCallback(function () { return (isMobileDropdownActive ? mobileInputRef.current : inputRef.current); }, [isMobileDropdownActive]);
|
|
216
285
|
var _o = useTimePickerSelection({
|
|
217
286
|
isInputFocused: isInputFocused,
|
|
218
287
|
format: format,
|
|
219
288
|
displayValue: displayValue,
|
|
220
289
|
getInput: getActiveInput,
|
|
221
290
|
}), selection = _o.selection, selectedSegment = _o.selectedSegment, selectSegment = _o.selectSegment, selectAll = _o.selectAll, syncSelectionWithDOM = _o.syncSelectionWithDOM;
|
|
222
|
-
var
|
|
291
|
+
var blinkInput = useCallback(function () {
|
|
223
292
|
var _a;
|
|
224
293
|
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.blink();
|
|
225
294
|
}, [getActiveInput]);
|
|
226
|
-
/**
|
|
227
|
-
* Сообщает о некорректном вводе: нажатой клавише или вставленном значении, которое не может быть временем.
|
|
228
|
-
* Если обработчик не задан, поле вспыхивает само.
|
|
229
|
-
*/
|
|
230
295
|
var handleUnexpectedInput = useCallback(function (unexpectedValue) {
|
|
231
|
-
if (onUnexpectedInput) {
|
|
232
|
-
|
|
296
|
+
if (!onUnexpectedInput) {
|
|
297
|
+
blinkInput();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
var returnedValue = onUnexpectedInput(unexpectedValue, blinkInput);
|
|
301
|
+
if (returnedValue === undefined) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (returnedValue === null) {
|
|
305
|
+
commitValue(EMPTY_VALUE);
|
|
233
306
|
return;
|
|
234
307
|
}
|
|
235
|
-
|
|
236
|
-
}, [
|
|
308
|
+
commitItem(returnedValue);
|
|
309
|
+
}, [blinkInput, commitItem, commitValue, onUnexpectedInput]);
|
|
310
|
+
/** Закрывает открытое меню: мобильный попап заодно завершает сессию ввода, как потеря фокуса. */
|
|
237
311
|
var closeOpenedDropdown = useCallback(function () {
|
|
238
|
-
var _a;
|
|
239
|
-
if (
|
|
312
|
+
var _a, _b;
|
|
313
|
+
if (isMobileDropdownActive) {
|
|
314
|
+
// Фокус может остаться в любом из двух полей: инпут попапа не всегда получает его —
|
|
315
|
+
// например мобильный браузер отказывается переводить фокус без жеста пользователя.
|
|
316
|
+
// Гасим оба, чтобы закрытие попапа всегда давало событие потери фокуса.
|
|
240
317
|
(_a = mobileInputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
318
|
+
(_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.blur();
|
|
241
319
|
setIsInputFocused(false);
|
|
242
320
|
resetMouseInteractionState();
|
|
243
321
|
closeDropdown();
|
|
244
322
|
commitEditingValue();
|
|
245
323
|
var blurEvent = mobileBlurEventRef.current;
|
|
246
324
|
mobileBlurEventRef.current = null;
|
|
247
|
-
|
|
248
|
-
onBlur === null || onBlur === void 0 ? void 0 : onBlur(blurEvent);
|
|
249
|
-
}
|
|
325
|
+
emitBlur(blurEvent);
|
|
250
326
|
return;
|
|
251
327
|
}
|
|
252
328
|
closeDropdown();
|
|
253
|
-
}, [
|
|
254
|
-
canUseMobileDropdownTimePicker,
|
|
255
|
-
closeDropdown,
|
|
256
|
-
commitEditingValue,
|
|
257
|
-
isDropdownOpened,
|
|
258
|
-
onBlur,
|
|
259
|
-
resetMouseInteractionState,
|
|
260
|
-
]);
|
|
329
|
+
}, [closeDropdown, commitEditingValue, emitBlur, isMobileDropdownActive, resetMouseInteractionState]);
|
|
261
330
|
var selectItem = useCallback(function (item) {
|
|
262
331
|
commitItem(item);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
closeDropdown();
|
|
268
|
-
}
|
|
269
|
-
}, [canUseMobileDropdownTimePicker, closeDropdown, closeOpenedDropdown, commitItem]);
|
|
270
|
-
var isSelectHighlightedItem = useCallback(function () {
|
|
332
|
+
closeOpenedDropdown();
|
|
333
|
+
}, [closeOpenedDropdown, commitItem]);
|
|
334
|
+
var isSelectHighlightedItem = useCallback(function (event) {
|
|
335
|
+
var _a, _b;
|
|
271
336
|
if (highlightedItemIndex === null) {
|
|
272
337
|
return false;
|
|
273
338
|
}
|
|
274
|
-
var resolvedItem =
|
|
275
|
-
if (!resolvedItem
|
|
339
|
+
var resolvedItem = menuItems[highlightedItemIndex];
|
|
340
|
+
if (!resolvedItem) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
if (isNavigableMenuElement(resolvedItem)) {
|
|
344
|
+
(_b = (_a = resolvedItem.props).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, event);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
if (!isTimeMenuItem(resolvedItem) || resolvedItem.disabled) {
|
|
276
348
|
return true;
|
|
277
349
|
}
|
|
278
350
|
selectItem(resolvedItem.item);
|
|
279
351
|
return true;
|
|
280
|
-
}, [highlightedItemIndex,
|
|
281
|
-
var focusInput = useCallback(function () {
|
|
352
|
+
}, [highlightedItemIndex, menuItems, selectItem]);
|
|
353
|
+
var focusInput = useCallback(function (options) {
|
|
282
354
|
var _a;
|
|
355
|
+
if (disabled) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
shouldSkipOpenOnFocusRef.current = Boolean(options === null || options === void 0 ? void 0 : options.withoutOpenDropdown);
|
|
283
359
|
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.focus();
|
|
284
|
-
|
|
285
|
-
|
|
360
|
+
shouldSkipOpenOnFocusRef.current = false;
|
|
361
|
+
}, [disabled, getActiveInput]);
|
|
362
|
+
/**
|
|
363
|
+
* Открывает выпадающее меню так же, как это делает пользователь: через фокус в поле.
|
|
364
|
+
* Меню закрывается по потере фокуса, поэтому открытое без фокуса меню было бы нечем закрыть.
|
|
365
|
+
* В режиме нативного пикера открывается системный пикер — своего меню в этом режиме нет.
|
|
366
|
+
*/
|
|
367
|
+
var openInputDropdown = useCallback(function () {
|
|
286
368
|
var _a;
|
|
287
|
-
if (
|
|
369
|
+
if (disabled) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (canUseMobileNativeTimePicker) {
|
|
373
|
+
openNativeTimePicker();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.focus();
|
|
377
|
+
openDropdown();
|
|
378
|
+
}, [canUseMobileNativeTimePicker, disabled, getActiveInput, openDropdown, openNativeTimePicker]);
|
|
379
|
+
var blurInput = useCallback(function () {
|
|
380
|
+
var _a, _b;
|
|
381
|
+
if (isMobileDropdownActive) {
|
|
288
382
|
closeOpenedDropdown();
|
|
289
383
|
return;
|
|
290
384
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}, [closeOpenedDropdown]);
|
|
296
|
-
var blinkInput = useCallback(function () {
|
|
297
|
-
var _a;
|
|
298
|
-
(_a = getActiveInput()) === null || _a === void 0 ? void 0 : _a.blink();
|
|
299
|
-
}, [getActiveInput]);
|
|
385
|
+
// В нативном режиме фокус держит скрытый инпут системного пикера.
|
|
386
|
+
(_a = nativeInputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
387
|
+
(_b = getActiveInput()) === null || _b === void 0 ? void 0 : _b.blur();
|
|
388
|
+
}, [closeOpenedDropdown, getActiveInput, isMobileDropdownActive]);
|
|
300
389
|
useImperativeHandle(ref, function () { return ({
|
|
301
390
|
focus: focusInput,
|
|
302
391
|
blur: blurInput,
|
|
303
392
|
blink: blinkInput,
|
|
304
|
-
|
|
393
|
+
open: openInputDropdown,
|
|
394
|
+
close: closeOpenedDropdown,
|
|
305
395
|
getRootNode: function () { return rootRef.current; },
|
|
306
|
-
}); }, [blinkInput, blurInput,
|
|
396
|
+
}); }, [blinkInput, blurInput, closeOpenedDropdown, focusInput, openInputDropdown]);
|
|
307
397
|
useLayoutEffect(function () {
|
|
308
398
|
if (!isDropdownOpened) {
|
|
309
399
|
return;
|
|
@@ -317,82 +407,170 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
317
407
|
scrollSelectedItemIntoView(targetNode);
|
|
318
408
|
}
|
|
319
409
|
}, [highlightedItemIndex, isDropdownOpened, selectedItemIndex]);
|
|
410
|
+
/**
|
|
411
|
+
* Блокировка контрола посреди ввода завершает его так же, как потеря фокуса:
|
|
412
|
+
* меню закрывается, а недоведенное значение коммитится, а не остается висеть в поле.
|
|
413
|
+
*/
|
|
320
414
|
useEffect(function () {
|
|
321
|
-
if (disabled
|
|
415
|
+
if (!disabled) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (isMobileDropdownActive) {
|
|
322
419
|
closeOpenedDropdown();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (isDropdownOpened) {
|
|
423
|
+
closeDropdown();
|
|
424
|
+
}
|
|
425
|
+
if (isInputFocused) {
|
|
426
|
+
setIsInputFocused(false);
|
|
427
|
+
resetMouseInteractionState();
|
|
428
|
+
commitEditingValue();
|
|
323
429
|
}
|
|
324
|
-
}, [
|
|
430
|
+
}, [
|
|
431
|
+
closeDropdown,
|
|
432
|
+
closeOpenedDropdown,
|
|
433
|
+
commitEditingValue,
|
|
434
|
+
disabled,
|
|
435
|
+
isDropdownOpened,
|
|
436
|
+
isInputFocused,
|
|
437
|
+
isMobileDropdownActive,
|
|
438
|
+
resetMouseInteractionState,
|
|
439
|
+
]);
|
|
440
|
+
var previousIsMobileLayoutRef = useRef(isMobileLayout);
|
|
441
|
+
/**
|
|
442
|
+
* Смена верстки переносит открытое меню в нужную версию: как в ComboBox, меню не закрывается.
|
|
443
|
+
* Начатый ввод переезжает вместе с ним, а если переезжать некуда — коммитится, как при потере фокуса.
|
|
444
|
+
*/
|
|
325
445
|
useEffect(function () {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
446
|
+
if (previousIsMobileLayoutRef.current === isMobileLayout) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
previousIsMobileLayoutRef.current = isMobileLayout;
|
|
450
|
+
// Запомненный blur остался от прошлой верстки: как потеря фокуса контрола он больше не годится.
|
|
451
|
+
mobileBlurEventRef.current = null;
|
|
452
|
+
if (isMobileLayout) {
|
|
453
|
+
// Ввод продолжится в инпуте попапа, только если попап открывается вместе со сменой верстки.
|
|
454
|
+
if (isInputFocused && !isMobileDropdownActive) {
|
|
455
|
+
setIsInputFocused(false);
|
|
456
|
+
resetMouseInteractionState();
|
|
457
|
+
commitEditingValue();
|
|
458
|
+
}
|
|
459
|
+
// В режиме нативного пикера своего меню нет.
|
|
460
|
+
if (isDropdownOpened && canUseMobileNativeTimePicker) {
|
|
461
|
+
closeDropdown();
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Десктопное меню закрывается по потере фокуса поля, поэтому без фокуса в контроле
|
|
467
|
+
* его нечем было бы закрыть: мобильный попап мог быть открыт и без фокуса в своем инпуте.
|
|
468
|
+
* Если же фокус был, его вернет в поле `useTimePickerSelection`.
|
|
469
|
+
*/
|
|
470
|
+
if (isDropdownOpened && !isInputFocused) {
|
|
471
|
+
closeDropdown();
|
|
472
|
+
}
|
|
473
|
+
}, [
|
|
474
|
+
canUseMobileNativeTimePicker,
|
|
475
|
+
closeDropdown,
|
|
476
|
+
commitEditingValue,
|
|
477
|
+
isDropdownOpened,
|
|
478
|
+
isInputFocused,
|
|
479
|
+
isMobileDropdownActive,
|
|
480
|
+
isMobileLayout,
|
|
481
|
+
resetMouseInteractionState,
|
|
482
|
+
]);
|
|
483
|
+
/** Сообщает о фокусе один раз за сессию: переезд фокуса внутри контрола наружу не виден. */
|
|
484
|
+
var emitFocus = function (event) {
|
|
485
|
+
if (isControlFocusedRef.current) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
isControlFocusedRef.current = true;
|
|
489
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
490
|
+
};
|
|
332
491
|
var handleFocusBySource = function (event, isMobilePopupInput) {
|
|
492
|
+
var shouldSkipOpenDropdown = shouldSkipOpenOnFocusRef.current;
|
|
493
|
+
shouldSkipOpenOnFocusRef.current = false;
|
|
494
|
+
var isFocusMovedInsideControl = isControlFocusedRef.current;
|
|
333
495
|
if (canUseMobileNativeTimePicker) {
|
|
334
|
-
|
|
335
|
-
|
|
496
|
+
// Фокус сообщается до открытия пикера: иначе увод фокуса в нативный инпут закрыл бы сессию.
|
|
497
|
+
emitFocus(event);
|
|
498
|
+
if (!shouldSkipOpenDropdown) {
|
|
499
|
+
openNativeTimePicker();
|
|
500
|
+
}
|
|
336
501
|
return;
|
|
337
502
|
}
|
|
338
503
|
if (canUseMobileDropdownTimePicker && !isMobilePopupInput) {
|
|
339
|
-
|
|
504
|
+
if (!shouldSkipOpenDropdown) {
|
|
505
|
+
openDropdown();
|
|
506
|
+
}
|
|
507
|
+
// Дальше ввод идет в инпуте попапа, но фокус контрол получает здесь.
|
|
508
|
+
emitFocus(event);
|
|
340
509
|
return;
|
|
341
510
|
}
|
|
342
511
|
setIsInputFocused(true);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
selectSegment(mouseDownSegmentRef.current);
|
|
350
|
-
}
|
|
512
|
+
if (!shouldSkipOpenDropdown) {
|
|
513
|
+
openDropdown();
|
|
514
|
+
}
|
|
515
|
+
if (mouseDownSegmentRef.current && !isInputFocused) {
|
|
516
|
+
// По пустому полю ввод всегда начинается с часов, а по заполненному — с сегмента под курсором.
|
|
517
|
+
selectSegment(isTimeDisplayEmpty(editingValueRef.current) ? 'hours' : mouseDownSegmentRef.current);
|
|
351
518
|
}
|
|
352
519
|
else {
|
|
353
|
-
|
|
520
|
+
// Переезд фокуса внутри контрола продолжает начатый ввод, поэтому сегмент сохраняется:
|
|
521
|
+
// иначе следующая цифра ушла бы в часы и затерла бы набранное время.
|
|
522
|
+
selectSegment(isFocusMovedInsideControl ? selectedSegment : 'hours');
|
|
354
523
|
}
|
|
355
|
-
|
|
524
|
+
emitFocus(event);
|
|
356
525
|
};
|
|
357
526
|
var handleFocus = function (event) { return handleFocusBySource(event, false); };
|
|
358
527
|
var handleMobileFocus = function (event) { return handleFocusBySource(event, true); };
|
|
359
528
|
var handleBlurBySource = function (event, isMobilePopupInput) {
|
|
360
|
-
|
|
361
|
-
|
|
529
|
+
/**
|
|
530
|
+
* Пока открыт мобильный попап, фокус ходит между полем и его инпутом: контрол остается активным,
|
|
531
|
+
* поэтому наружу о таком blur сообщать нечего. Событие запоминается на случай,
|
|
532
|
+
* если фокус в инпуте попапа так и не оказался — тогда именно оно уйдет в `onBlur` при закрытии.
|
|
533
|
+
*/
|
|
534
|
+
if (isMobileDropdownActive) {
|
|
535
|
+
event.persist();
|
|
536
|
+
mobileBlurEventRef.current = event;
|
|
362
537
|
return;
|
|
363
538
|
}
|
|
364
|
-
if (
|
|
539
|
+
if (canUseMobileNativeTimePicker) {
|
|
540
|
+
if (isFocusMovedToNativePicker(event)) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
// Ввод в мобильной версии не идет через поле, но незакоммиченное значение могло остаться
|
|
544
|
+
// от десктопной версии, если верстка сменилась посреди ввода.
|
|
545
|
+
if (isInputFocused) {
|
|
546
|
+
setIsInputFocused(false);
|
|
547
|
+
resetMouseInteractionState();
|
|
548
|
+
commitEditingValue();
|
|
549
|
+
}
|
|
550
|
+
emitBlur(event);
|
|
365
551
|
return;
|
|
366
552
|
}
|
|
553
|
+
// Инпут закрытого попапа фокуса уже не держит, но его blur может прийти на размонтировании.
|
|
367
554
|
if (isMobilePopupInput) {
|
|
368
|
-
event.persist();
|
|
369
|
-
mobileBlurEventRef.current = event;
|
|
370
555
|
return;
|
|
371
556
|
}
|
|
372
557
|
setIsInputFocused(false);
|
|
373
558
|
resetMouseInteractionState();
|
|
374
559
|
closeDropdown();
|
|
375
560
|
commitEditingValue();
|
|
376
|
-
|
|
561
|
+
emitBlur(event);
|
|
377
562
|
};
|
|
378
563
|
var handleBlur = function (event) { return handleBlurBySource(event, false); };
|
|
379
564
|
var handleMobileBlur = function (event) { return handleBlurBySource(event, true); };
|
|
380
|
-
var
|
|
565
|
+
var handleClick = function (event) {
|
|
381
566
|
if (canUseMobileNativeTimePicker) {
|
|
382
567
|
openNativeTimePicker();
|
|
383
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
384
|
-
return;
|
|
385
568
|
}
|
|
386
|
-
|
|
569
|
+
else {
|
|
387
570
|
openDropdown();
|
|
388
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
389
|
-
return;
|
|
390
571
|
}
|
|
391
|
-
openDropdown();
|
|
392
572
|
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
393
573
|
};
|
|
394
|
-
var handleClick = function (event) { return handleClickBySource(event, false); };
|
|
395
|
-
var handleMobileClick = function (event) { return handleClickBySource(event, true); };
|
|
396
574
|
var handleMouseDownCaptureBySource = function (event, isMobilePopupInput) {
|
|
397
575
|
var _a, _b;
|
|
398
576
|
if (canUseMobileNativeTimePicker || (canUseMobileDropdownTimePicker && !isMobilePopupInput)) {
|
|
@@ -400,12 +578,10 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
400
578
|
}
|
|
401
579
|
var currentInputRef = isMobilePopupInput ? mobileInputRef : inputRef;
|
|
402
580
|
var segment = (_b = (_a = currentInputRef.current) === null || _a === void 0 ? void 0 : _a.getSegment(event.target)) !== null && _b !== void 0 ? _b : null;
|
|
403
|
-
|
|
404
|
-
if (isInputFocused && !isFragment) {
|
|
581
|
+
if (isInputFocused && segment === null) {
|
|
405
582
|
event.preventDefault();
|
|
406
583
|
}
|
|
407
584
|
isMouseFocusRef.current = !isInputFocused;
|
|
408
|
-
isMouseDownOnFragmentRef.current = isFragment;
|
|
409
585
|
mouseDownSegmentRef.current = segment;
|
|
410
586
|
};
|
|
411
587
|
var handleMouseDownCapture = function (event) {
|
|
@@ -477,20 +653,24 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
477
653
|
}
|
|
478
654
|
if (shouldHandleAsAllSelection && (isKeyBackspace(event) || isKeyDelete(event))) {
|
|
479
655
|
event.preventDefault();
|
|
656
|
+
openDropdown();
|
|
480
657
|
updateEditingValueAfterUserEdit(getEmptyDisplayValue(format));
|
|
481
658
|
selectSegment('hours');
|
|
482
659
|
return;
|
|
483
660
|
}
|
|
484
661
|
if (DIGIT_REGEXP.test(event.key)) {
|
|
485
662
|
event.preventDefault();
|
|
486
|
-
|
|
663
|
+
openDropdown();
|
|
487
664
|
var nextDigitInputValue = shouldHandleAsAllSelection ? getEmptyDisplayValue(format) : currentDisplayValue;
|
|
488
665
|
var nextDigitInputSegment = shouldHandleAsAllSelection ? 'hours' : currentSegment;
|
|
489
666
|
var result = formatDigitToTimeSegment(nextDigitInputValue, nextDigitInputSegment, event.key, format);
|
|
667
|
+
// Отклоненная цифра не меняет ни значение, ни список, поэтому и подсветку трогать нельзя:
|
|
668
|
+
// автоподсветка не восстановится, а Enter выбрал бы не тот элемент, который видит пользователь.
|
|
490
669
|
if (result.shouldBlink) {
|
|
491
670
|
handleUnexpectedInput(event.key);
|
|
492
671
|
return;
|
|
493
672
|
}
|
|
673
|
+
resetHighlightedItem();
|
|
494
674
|
updateEditingValueAfterUserEdit(result.nextValue);
|
|
495
675
|
selectSegment(result.selectedSegment);
|
|
496
676
|
return;
|
|
@@ -538,6 +718,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
538
718
|
}
|
|
539
719
|
if (isKeyBackspace(event)) {
|
|
540
720
|
event.preventDefault();
|
|
721
|
+
openDropdown();
|
|
541
722
|
var nextDisplayValue = deleteTimeSegmentDigit(currentDisplayValue, currentSegment, format);
|
|
542
723
|
var nextSegment = currentSegment;
|
|
543
724
|
if (nextDisplayValue === currentDisplayValue) {
|
|
@@ -553,6 +734,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
553
734
|
}
|
|
554
735
|
if (isKeyDelete(event)) {
|
|
555
736
|
event.preventDefault();
|
|
737
|
+
openDropdown();
|
|
556
738
|
updateEditingValueAfterUserEdit(replaceTimeSegment(currentDisplayValue, currentSegment, EMPTY_SEGMENT, format));
|
|
557
739
|
selectSegment(currentSegment);
|
|
558
740
|
return;
|
|
@@ -560,29 +742,28 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
560
742
|
if (isKeyEscape(event)) {
|
|
561
743
|
if (isDropdownOpened) {
|
|
562
744
|
event.preventDefault();
|
|
563
|
-
|
|
564
|
-
closeOpenedDropdown();
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
closeDropdown();
|
|
568
|
-
}
|
|
745
|
+
closeOpenedDropdown();
|
|
569
746
|
}
|
|
570
747
|
return;
|
|
571
748
|
}
|
|
572
749
|
if (isKeyEnter(event)) {
|
|
573
|
-
if (
|
|
750
|
+
if (isSourceFailed) {
|
|
574
751
|
event.preventDefault();
|
|
752
|
+
retrySource();
|
|
575
753
|
return;
|
|
576
754
|
}
|
|
577
|
-
if (
|
|
755
|
+
if (isSelectHighlightedItem(event)) {
|
|
756
|
+
event.preventDefault();
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (isMobileDropdownActive) {
|
|
578
760
|
event.preventDefault();
|
|
579
761
|
closeOpenedDropdown();
|
|
580
762
|
return;
|
|
581
763
|
}
|
|
582
|
-
// Enter не выбирает элемент, а лишь подтверждает введенное значение,
|
|
583
|
-
// поэтому коммитим значение как при потере фокуса и подсвечиваем поле.
|
|
764
|
+
// Enter не выбирает элемент, а лишь подтверждает введенное значение, как потеря фокуса.
|
|
584
765
|
commitEditingValue();
|
|
585
|
-
|
|
766
|
+
blinkInput();
|
|
586
767
|
if (isDropdownOpened) {
|
|
587
768
|
event.preventDefault();
|
|
588
769
|
closeDropdown();
|
|
@@ -610,7 +791,7 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
610
791
|
var handleKeyDown = function (event) { return handleKeyDownBySource(event, false); };
|
|
611
792
|
var handleMobileKeyDown = function (event) { return handleKeyDownBySource(event, true); };
|
|
612
793
|
var handlePasteBySource = function (event, isMobilePopupInput) {
|
|
613
|
-
var _a;
|
|
794
|
+
var _a, _b;
|
|
614
795
|
onPaste === null || onPaste === void 0 ? void 0 : onPaste(event);
|
|
615
796
|
if (event.defaultPrevented || disabled) {
|
|
616
797
|
return;
|
|
@@ -618,31 +799,32 @@ export var TimePicker = Object.assign(forwardRefAndName('TimePicker', function (
|
|
|
618
799
|
if (canUseMobileNativeTimePicker || (canUseMobileDropdownTimePicker && !isMobilePopupInput)) {
|
|
619
800
|
return;
|
|
620
801
|
}
|
|
621
|
-
var pastedValue = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text');
|
|
622
|
-
var nextDisplayValue = parsePastedTimeValue(pastedValue, format);
|
|
802
|
+
var pastedValue = (_b = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text')) !== null && _b !== void 0 ? _b : EMPTY_VALUE;
|
|
623
803
|
event.preventDefault();
|
|
804
|
+
if (pastedValue.trim() === EMPTY_VALUE) {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
var nextDisplayValue = parsePastedTimeValue(pastedValue, format);
|
|
624
808
|
if (isTimeDisplayEmpty(nextDisplayValue)) {
|
|
625
809
|
handleUnexpectedInput(pastedValue);
|
|
626
810
|
return;
|
|
627
811
|
}
|
|
812
|
+
openDropdown();
|
|
628
813
|
updateEditingValueAfterUserEdit(nextDisplayValue);
|
|
629
814
|
selectSegment('hours');
|
|
630
815
|
};
|
|
631
816
|
var handlePaste = function (event) { return handlePasteBySource(event, false); };
|
|
632
817
|
var handleMobilePaste = function (event) { return handlePasteBySource(event, true); };
|
|
633
|
-
var handleNativeValueChange = useCallback(function (nextValue) {
|
|
634
|
-
commitValue(nextValue);
|
|
635
|
-
}, [commitValue]);
|
|
636
818
|
return (React.createElement(CommonWrapper, __assign({}, props),
|
|
637
819
|
React.createElement("span", { ref: rootRef, className: styles.root(), "data-tid": TimePickerDataTids.root },
|
|
638
820
|
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
|
-
canUseMobileNativeTimePicker && (React.createElement(NativeTimeInput, { "data-tid": TimePickerDataTids.nativeInput, disabled: disabled, ref: nativeInputRef, format: format, minTime: minTime, maxTime: maxTime, value: editingValue, onValueChange:
|
|
640
|
-
|
|
821
|
+
canUseMobileNativeTimePicker && (React.createElement(NativeTimeInput, { "data-tid": TimePickerDataTids.nativeInput, disabled: disabled, ref: nativeInputRef, format: format, minTime: minTime, maxTime: maxTime, value: editingValue, onValueChange: commitValue, onBlur: emitBlur })),
|
|
822
|
+
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, isFailed: isSourceFailed, errorNetworkButton: locale.errorNetworkButton, errorNetworkMessage: locale.errorNetworkMessage, 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 || isSourceFailed ? true : undefined, "aria-controls": resolvedItems.length > 0 || isLoadingItems || isSourceFailed ? popupId : undefined, "aria-activedescendant": activeDescendantId, onFocus: handleMobileFocus, onBlur: handleMobileBlur, onClick: handleClick, onKeyDown: handleMobileKeyDown, onMouseDownCapture: handleMobileMouseDownCapture, onMouseUp: handleMobileMouseUp, onPaste: handleMobilePaste, onSelectSegment: handleMobileSelectSegmentByMouse, onRetry: retrySource, onSelectItem: selectItem, onCloseRequest: closeOpenedDropdown })),
|
|
641
823
|
hasDropdown &&
|
|
642
824
|
!canUseMobileNativeTimePicker &&
|
|
643
825
|
!canUseMobileDropdownTimePicker &&
|
|
644
826
|
isDropdownOpened &&
|
|
645
827
|
hasDropdownContent &&
|
|
646
|
-
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,
|
|
828
|
+
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, isFailed: isSourceFailed, errorNetworkButton: locale.errorNetworkButton, errorNetworkMessage: locale.errorNetworkMessage, highlightedItemIndex: highlightedItemIndex, selectedValue: selectedValue, itemRefs: itemRefs, onRetry: retrySource, onSelectItem: selectItem })))));
|
|
647
829
|
}), { validate: validateTimePicker });
|
|
648
830
|
//# sourceMappingURL=TimePicker.js.map
|