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