@skbkontur/react-ui 6.1.6 → 6.1.7-41945.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/Checkbox/Checkbox.d.ts +5 -0
- package/components/Checkbox/Checkbox.js +42 -21
- package/components/Checkbox/Checkbox.js.map +1 -1
- package/components/Checkbox/Checkbox.mixins.d.ts +2 -0
- package/components/Checkbox/Checkbox.mixins.js +6 -0
- package/components/Checkbox/Checkbox.mixins.js.map +1 -1
- package/components/Checkbox/Checkbox.styles.d.ts +7 -0
- package/components/Checkbox/Checkbox.styles.js +23 -2
- package/components/Checkbox/Checkbox.styles.js.map +1 -1
- 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/Radio/Radio.d.ts +4 -0
- package/components/Radio/Radio.js +20 -1
- package/components/Radio/Radio.js.map +1 -1
- package/components/Radio/Radio.mixins.d.ts +2 -0
- package/components/Radio/Radio.mixins.js +6 -0
- package/components/Radio/Radio.mixins.js.map +1 -1
- package/components/Radio/Radio.styles.d.ts +6 -0
- package/components/Radio/Radio.styles.js +22 -4
- package/components/Radio/Radio.styles.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 +156 -0
- package/components/TimePicker/TimePicker.js +830 -0
- package/components/TimePicker/TimePicker.js.map +1 -0
- package/components/TimePicker/TimePicker.styles.d.ts +13 -0
- package/components/TimePicker/TimePicker.styles.js +42 -0
- package/components/TimePicker/TimePicker.styles.js.map +1 -0
- package/components/TimePicker/TimePickerItems.d.ts +24 -0
- package/components/TimePicker/TimePickerItems.js +118 -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 +27 -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 +80 -0
- package/components/TimePicker/helpers/TimePicker.shared.js +97 -0
- package/components/TimePicker/helpers/TimePicker.shared.js.map +1 -0
- package/components/TimePicker/helpers/TimePicker.value.d.ts +70 -0
- package/components/TimePicker/helpers/TimePicker.value.js +207 -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 +15 -0
- package/components/TimePicker/helpers/validateTimePicker.js.map +1 -0
- package/components/TimePicker/hooks/useTimePickerDropdown.d.ts +23 -0
- package/components/TimePicker/hooks/useTimePickerDropdown.js +67 -0
- package/components/TimePicker/hooks/useTimePickerDropdown.js.map +1 -0
- package/components/TimePicker/hooks/useTimePickerSelection.d.ts +18 -0
- package/components/TimePicker/hooks/useTimePickerSelection.js +66 -0
- package/components/TimePicker/hooks/useTimePickerSelection.js.map +1 -0
- 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 +38 -0
- package/components/TimePicker/hooks/useTimePickerValue.js +96 -0
- package/components/TimePicker/hooks/useTimePickerValue.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/components/Toggle/Toggle.d.ts +5 -0
- package/components/Toggle/Toggle.js +46 -22
- package/components/Toggle/Toggle.js.map +1 -1
- package/components/Toggle/Toggle.mixins.d.ts +2 -0
- package/components/Toggle/Toggle.mixins.js +6 -0
- package/components/Toggle/Toggle.mixins.js.map +1 -1
- package/components/Toggle/Toggle.styles.d.ts +9 -0
- package/components/Toggle/Toggle.styles.js +29 -2
- package/components/Toggle/Toggle.styles.js.map +1 -1
- 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 +27 -0
- package/internal/TimeInput/TimeFragments.styles.js +73 -0
- package/internal/TimeInput/TimeFragments.styles.js.map +1 -0
- package/internal/TimeInput/TimeInput.d.ts +21 -0
- package/internal/TimeInput/TimeInput.js +102 -0
- package/internal/TimeInput/TimeInput.js.map +1 -0
- package/internal/TimeInput/TimeInput.styles.d.ts +6 -0
- package/internal/TimeInput/TimeInput.styles.js +21 -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/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 +65 -0
- package/internal/themes/BasicTheme.js +253 -0
- package/internal/themes/BasicTheme.js.map +1 -1
- package/internal/themes/DarkTheme6_0.js +1 -0
- package/internal/themes/DarkTheme6_0.js.map +1 -1
- package/lib/locale/types.d.ts +2 -0
- package/lib/locale/types.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 +9 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type TimeFormat, type TimeItemValue, type TimePickerExtendedItem, type TimePickerMenuItem, type TimeSegment } from './TimePicker.shared.js';
|
|
2
|
+
/** Проверяет, что в display-значении нет ни одной введенной цифры. */
|
|
3
|
+
export declare const isTimeDisplayEmpty: (value: string) => boolean;
|
|
4
|
+
export declare const getTimeValueString: (value: TimeItemValue | null | undefined) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Разбирает элементы источника для отрисовки: достает время и подпись,
|
|
7
|
+
* а `disabled` дополняет проверкой диапазона `minTime`/`maxTime`.
|
|
8
|
+
* Исходный элемент сохраняется как есть — именно он уходит в `onValueChange`.
|
|
9
|
+
* React-элементы остаются на своих местах.
|
|
10
|
+
*/
|
|
11
|
+
export declare const resolveTimeItems: <T extends TimeItemValue>(items: Array<TimePickerExtendedItem<T>>, format: TimeFormat, minTime?: string, maxTime?: string) => Array<TimePickerMenuItem<T>>;
|
|
12
|
+
/**
|
|
13
|
+
* Оставляет только элементы, которые компонент умеет показать в меню.
|
|
14
|
+
* Пустые значения — результат условного рендера вроде `isVisible && item` или `items.length && item` —
|
|
15
|
+
* молча отбрасываются, а элементы, из которых не получается собрать время, отбрасываются с предупреждением.
|
|
16
|
+
*/
|
|
17
|
+
export declare const sanitizeTimeItems: <T extends TimeItemValue>(items: Array<TimePickerExtendedItem<T>>, format: TimeFormat) => Array<TimePickerExtendedItem<T>>;
|
|
18
|
+
/**
|
|
19
|
+
* Проверяет, выходит ли значение за диапазон `minTime`/`maxTime` после нормализации.
|
|
20
|
+
* Пустое значение не считается выходящим за диапазон. Если `minTime > maxTime`, диапазон трактуется как переходящий через полночь.
|
|
21
|
+
*/
|
|
22
|
+
export declare const isTimeValueOutOfRange: (value: string, format: TimeFormat, minTime?: string, maxTime?: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Нормализует display-значение в committed-форму `HH:mm[:ss]`.
|
|
25
|
+
* Частично заполненные сегменты дополняются и ограничиваются максимумами сегментов.
|
|
26
|
+
*/
|
|
27
|
+
export declare const normalizeTimeValue: (value: string, format: TimeFormat) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Преобразует display-значение в промежуточное значение для `onInputValueChange`.
|
|
30
|
+
* Заполненные сегменты дополняются до двух цифр, пропущенные сегменты до последнего заполненного становятся `00`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const serializeTimeValue: (value: string, format: TimeFormat) => string;
|
|
33
|
+
export declare const getTimeDisplayValue: (value: string, format: TimeFormat) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Возвращает display-значение внешнего `value`.
|
|
36
|
+
* Сегменты, записанные одной цифрой, дополняются нулем, поэтому `9:00` показывается как `09:00` —
|
|
37
|
+
* так же, как компонент показывает время элементов источника.
|
|
38
|
+
* Значение без разделителя остается частично введенным: `1` — это первая цифра часов, а не `01:00`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const getExternalTimeDisplayValue: (value: string, format: TimeFormat) => string;
|
|
41
|
+
/** Заменяет значение выбранного сегмента, сохраняя остальные сегменты display-значения без изменений. */
|
|
42
|
+
export declare const replaceTimeSegment: (value: string, segment: TimeSegment, nextSegmentValue: string, format: TimeFormat) => string;
|
|
43
|
+
/**
|
|
44
|
+
* Проверяет, что вставленное значение похоже на время:
|
|
45
|
+
* это либо сегменты из одной-двух цифр через любой разделитель — `12:30`, `9:5`, `1:23:45`, `12.30`, —
|
|
46
|
+
* либо цифры подряд — `9`, `1230`, `123045`.
|
|
47
|
+
* Строки, из которых время собралось бы случайно — `2026-08-01`, `hello 42`, — временем не считаются.
|
|
48
|
+
*/
|
|
49
|
+
export declare const isTimeLikePastedValue: (value: string) => boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Парсит вставленное пользователем значение и сразу преобразует его в committed-форму.
|
|
52
|
+
* Значение, не похожее на время, отдается как пустое: такую вставку компонент считает некорректным вводом.
|
|
53
|
+
*/
|
|
54
|
+
export declare const parsePastedTimeValue: (value: string, format: TimeFormat) => string;
|
|
55
|
+
/** Возвращает текущее display-значение сегмента или пустой сегмент, если индекс отсутствует. */
|
|
56
|
+
export declare const getTimeSegmentValue: (value: string, segment: TimeSegment, format: TimeFormat) => string;
|
|
57
|
+
export declare const getEmptyDisplayValue: (precision: TimeFormat) => string;
|
|
58
|
+
/**
|
|
59
|
+
* Преобразует display-значение в поисковый запрос для фильтрации элементов.
|
|
60
|
+
* Сегменты содержат только введенные цифры, незаполненный хвост отбрасывается: `12:3_` => `12:3`, `1_:__` => `1`.
|
|
61
|
+
* Позиции сегментов сохраняются, поэтому ведущие незаполненные сегменты остаются пустыми: `__:34` => `:34`.
|
|
62
|
+
* Это нужно для посегментного сравнения в `filterTimeItems` и передается как есть в пользовательскую `source`.
|
|
63
|
+
*/
|
|
64
|
+
export declare const getTimeFilterQuery: (value: string, format: TimeFormat) => string;
|
|
65
|
+
/**
|
|
66
|
+
* Фильтрует элементы по запросу из поля ввода.
|
|
67
|
+
* Сравнивает запрос посегментно: введенные цифры сегмента должны быть префиксом соответствующего сегмента элемента.
|
|
68
|
+
* Пустой запрос возвращает все элементы.
|
|
69
|
+
*/
|
|
70
|
+
export declare const filterTimeItems: <T extends TimeItemValue>(items: Array<TimePickerExtendedItem<T>>, query: string, format: TimeFormat) => Array<TimePickerExtendedItem<T>>;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import warning from 'warning';
|
|
2
|
+
import { DIGIT_REGEXP, EMPTY_SEGMENT, EMPTY_VALUE, NON_DIGIT_REGEXP, TIME_LIKE_DIGITS_REGEXP, TIME_LIKE_SEGMENTS_REGEXP, TIME_SEPARATOR, ZERO_PAD_CHAR, } from './TimePicker.constants.js';
|
|
3
|
+
import { getDigits, getDisplaySegments, getTimeSegments, normalizeTimeSegment, sanitizeSegment, getTimeItemLabel, getTimeItemValue, isTimeItemDisabled, isTimeItem, isTimeItemObject, } from './TimePicker.shared.js';
|
|
4
|
+
/** Проверяет, что в display-значении нет ни одной введенной цифры. */
|
|
5
|
+
export var isTimeDisplayEmpty = function (value) { return !DIGIT_REGEXP.test(value); };
|
|
6
|
+
export var getTimeValueString = function (value) {
|
|
7
|
+
if (value === null || value === undefined) {
|
|
8
|
+
return EMPTY_VALUE;
|
|
9
|
+
}
|
|
10
|
+
return getTimeItemValue(value);
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Разбирает элементы источника для отрисовки: достает время и подпись,
|
|
14
|
+
* а `disabled` дополняет проверкой диапазона `minTime`/`maxTime`.
|
|
15
|
+
* Исходный элемент сохраняется как есть — именно он уходит в `onValueChange`.
|
|
16
|
+
* React-элементы остаются на своих местах.
|
|
17
|
+
*/
|
|
18
|
+
export var resolveTimeItems = function (items, format, minTime, maxTime) {
|
|
19
|
+
return items.map(function (item) {
|
|
20
|
+
if (!isTimeItem(item)) {
|
|
21
|
+
return item;
|
|
22
|
+
}
|
|
23
|
+
var value = getTimeItemValue(item);
|
|
24
|
+
return {
|
|
25
|
+
item: item,
|
|
26
|
+
value: value,
|
|
27
|
+
label: getTimeItemLabel(item),
|
|
28
|
+
disabled: isTimeItemDisabled(item) || isTimeValueOutOfRange(value, format, minTime, maxTime),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Оставляет только элементы, которые компонент умеет показать в меню.
|
|
34
|
+
* Пустые значения — результат условного рендера вроде `isVisible && item` или `items.length && item` —
|
|
35
|
+
* молча отбрасываются, а элементы, из которых не получается собрать время, отбрасываются с предупреждением.
|
|
36
|
+
*/
|
|
37
|
+
export var sanitizeTimeItems = function (items, format) {
|
|
38
|
+
if (!Array.isArray(items)) {
|
|
39
|
+
warning(false, '[TimePicker]: the source function is expected to return an array or a Promise of an array.');
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
return items.filter(function (item) {
|
|
43
|
+
if (!item) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (!isTimeItem(item)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (isTimeItemObject(item) && typeof item.value !== 'string') {
|
|
50
|
+
warning(false, '[TimePicker]: a source item is expected to be a time string or an object with a time value.');
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (normalizeTimeValue(getTimeItemValue(item), format) === EMPTY_VALUE) {
|
|
54
|
+
warning(false, "[TimePicker]: the source item value \"".concat(getTimeItemValue(item), "\" is not a time."));
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Проверяет, выходит ли значение за диапазон `minTime`/`maxTime` после нормализации.
|
|
62
|
+
* Пустое значение не считается выходящим за диапазон. Если `minTime > maxTime`, диапазон трактуется как переходящий через полночь.
|
|
63
|
+
*/
|
|
64
|
+
export var isTimeValueOutOfRange = function (value, format, minTime, maxTime) {
|
|
65
|
+
var normalizedValue = normalizeTimeValue(value, format);
|
|
66
|
+
if (normalizedValue === EMPTY_VALUE) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
var normalizedMinTime = normalizeTimeRangeValue(minTime, format);
|
|
70
|
+
var normalizedMaxTime = normalizeTimeRangeValue(maxTime, format);
|
|
71
|
+
if (normalizedMinTime && normalizedMaxTime && normalizedMinTime > normalizedMaxTime) {
|
|
72
|
+
return normalizedValue < normalizedMinTime && normalizedValue > normalizedMaxTime;
|
|
73
|
+
}
|
|
74
|
+
return ((normalizedMinTime !== undefined && normalizedValue < normalizedMinTime) ||
|
|
75
|
+
(normalizedMaxTime !== undefined && normalizedValue > normalizedMaxTime));
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Нормализует display-значение в committed-форму `HH:mm[:ss]`.
|
|
79
|
+
* Частично заполненные сегменты дополняются и ограничиваются максимумами сегментов.
|
|
80
|
+
*/
|
|
81
|
+
export var normalizeTimeValue = function (value, format) {
|
|
82
|
+
if (isTimeDisplayEmpty(value)) {
|
|
83
|
+
return EMPTY_VALUE;
|
|
84
|
+
}
|
|
85
|
+
var displaySegments = getDisplaySegments(value, format);
|
|
86
|
+
return getTimeSegments(format)
|
|
87
|
+
.map(function (segment, index) { return normalizeTimeSegment(displaySegments[index], segment); })
|
|
88
|
+
.join(TIME_SEPARATOR);
|
|
89
|
+
};
|
|
90
|
+
var normalizeTimeRangeValue = function (value, format) {
|
|
91
|
+
if (!value) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
var normalizedValue = normalizeTimeValue(value, format);
|
|
95
|
+
return normalizedValue === EMPTY_VALUE ? undefined : normalizedValue;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Преобразует display-значение в промежуточное значение для `onInputValueChange`.
|
|
99
|
+
* Заполненные сегменты дополняются до двух цифр, пропущенные сегменты до последнего заполненного становятся `00`.
|
|
100
|
+
*/
|
|
101
|
+
export var serializeTimeValue = function (value, format) {
|
|
102
|
+
var serializedSegments = takeFilledSegments(getDisplaySegments(value, format).map(function (segmentValue) {
|
|
103
|
+
var digits = getDigits(segmentValue);
|
|
104
|
+
return digits ? digits.padStart(EMPTY_SEGMENT.length, ZERO_PAD_CHAR) : EMPTY_VALUE;
|
|
105
|
+
}));
|
|
106
|
+
return serializedSegments.map(function (segmentValue) { return segmentValue || '00'; }).join(TIME_SEPARATOR);
|
|
107
|
+
};
|
|
108
|
+
/** Отбрасывает незаполненный хвост сегментов: ведущие пустые сегменты остаются на своих местах. */
|
|
109
|
+
var takeFilledSegments = function (segments) {
|
|
110
|
+
var lastFilledIndex = segments.reduce(function (result, segmentValue, index) { return (segmentValue ? index : result); }, -1);
|
|
111
|
+
return segments.slice(0, lastFilledIndex + 1);
|
|
112
|
+
};
|
|
113
|
+
export var getTimeDisplayValue = function (value, format) {
|
|
114
|
+
if (value === EMPTY_VALUE) {
|
|
115
|
+
return EMPTY_VALUE;
|
|
116
|
+
}
|
|
117
|
+
return getDisplaySegments(value, format).join(TIME_SEPARATOR);
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Возвращает display-значение внешнего `value`.
|
|
121
|
+
* Сегменты, записанные одной цифрой, дополняются нулем, поэтому `9:00` показывается как `09:00` —
|
|
122
|
+
* так же, как компонент показывает время элементов источника.
|
|
123
|
+
* Значение без разделителя остается частично введенным: `1` — это первая цифра часов, а не `01:00`.
|
|
124
|
+
*/
|
|
125
|
+
export var getExternalTimeDisplayValue = function (value, format) {
|
|
126
|
+
if (!value.includes(TIME_SEPARATOR)) {
|
|
127
|
+
return getTimeDisplayValue(value, format);
|
|
128
|
+
}
|
|
129
|
+
var paddedSegments = value.split(TIME_SEPARATOR).map(function (segmentValue) {
|
|
130
|
+
var digits = getDigits(segmentValue);
|
|
131
|
+
return digits ? digits.padStart(EMPTY_SEGMENT.length, ZERO_PAD_CHAR) : EMPTY_VALUE;
|
|
132
|
+
});
|
|
133
|
+
return getTimeDisplayValue(paddedSegments.join(TIME_SEPARATOR), format);
|
|
134
|
+
};
|
|
135
|
+
/** Заменяет значение выбранного сегмента, сохраняя остальные сегменты display-значения без изменений. */
|
|
136
|
+
export var replaceTimeSegment = function (value, segment, nextSegmentValue, format) {
|
|
137
|
+
var segments = getDisplaySegments(value, format);
|
|
138
|
+
var index = getTimeSegments(format).indexOf(segment);
|
|
139
|
+
segments[index] = sanitizeSegment(nextSegmentValue);
|
|
140
|
+
return segments.join(TIME_SEPARATOR);
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Проверяет, что вставленное значение похоже на время:
|
|
144
|
+
* это либо сегменты из одной-двух цифр через любой разделитель — `12:30`, `9:5`, `1:23:45`, `12.30`, —
|
|
145
|
+
* либо цифры подряд — `9`, `1230`, `123045`.
|
|
146
|
+
* Строки, из которых время собралось бы случайно — `2026-08-01`, `hello 42`, — временем не считаются.
|
|
147
|
+
*/
|
|
148
|
+
export var isTimeLikePastedValue = function (value) {
|
|
149
|
+
var trimmedValue = value.trim();
|
|
150
|
+
return TIME_LIKE_SEGMENTS_REGEXP.test(trimmedValue) || TIME_LIKE_DIGITS_REGEXP.test(trimmedValue);
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Парсит вставленное пользователем значение и сразу преобразует его в committed-форму.
|
|
154
|
+
* Значение, не похожее на время, отдается как пустое: такую вставку компонент считает некорректным вводом.
|
|
155
|
+
*/
|
|
156
|
+
export var parsePastedTimeValue = function (value, format) {
|
|
157
|
+
var trimmedValue = value.trim();
|
|
158
|
+
if (!DIGIT_REGEXP.test(trimmedValue) || !isTimeLikePastedValue(trimmedValue)) {
|
|
159
|
+
return EMPTY_VALUE;
|
|
160
|
+
}
|
|
161
|
+
// Разделители сегментов приводятся к одному виду, чтобы `9.5` разбирался так же, как `9:5`.
|
|
162
|
+
var normalizedSeparators = TIME_LIKE_SEGMENTS_REGEXP.test(trimmedValue)
|
|
163
|
+
? trimmedValue.split(NON_DIGIT_REGEXP).join(TIME_SEPARATOR)
|
|
164
|
+
: trimmedValue;
|
|
165
|
+
var segments = getDisplaySegments(normalizedSeparators, format);
|
|
166
|
+
return normalizeTimeValue(segments.join(TIME_SEPARATOR), format);
|
|
167
|
+
};
|
|
168
|
+
/** Возвращает текущее display-значение сегмента или пустой сегмент, если индекс отсутствует. */
|
|
169
|
+
export var getTimeSegmentValue = function (value, segment, format) {
|
|
170
|
+
var _a;
|
|
171
|
+
var segments = getDisplaySegments(value, format);
|
|
172
|
+
var index = getTimeSegments(format).indexOf(segment);
|
|
173
|
+
return (_a = segments[index]) !== null && _a !== void 0 ? _a : EMPTY_SEGMENT;
|
|
174
|
+
};
|
|
175
|
+
export var getEmptyDisplayValue = function (precision) {
|
|
176
|
+
return getTimeSegments(precision)
|
|
177
|
+
.map(function () { return EMPTY_SEGMENT; })
|
|
178
|
+
.join(TIME_SEPARATOR);
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Преобразует display-значение в поисковый запрос для фильтрации элементов.
|
|
182
|
+
* Сегменты содержат только введенные цифры, незаполненный хвост отбрасывается: `12:3_` => `12:3`, `1_:__` => `1`.
|
|
183
|
+
* Позиции сегментов сохраняются, поэтому ведущие незаполненные сегменты остаются пустыми: `__:34` => `:34`.
|
|
184
|
+
* Это нужно для посегментного сравнения в `filterTimeItems` и передается как есть в пользовательскую `source`.
|
|
185
|
+
*/
|
|
186
|
+
export var getTimeFilterQuery = function (value, format) {
|
|
187
|
+
return takeFilledSegments(getDisplaySegments(value, format).map(getDigits)).join(TIME_SEPARATOR);
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Фильтрует элементы по запросу из поля ввода.
|
|
191
|
+
* Сравнивает запрос посегментно: введенные цифры сегмента должны быть префиксом соответствующего сегмента элемента.
|
|
192
|
+
* Пустой запрос возвращает все элементы.
|
|
193
|
+
*/
|
|
194
|
+
export var filterTimeItems = function (items, query, format) {
|
|
195
|
+
var querySegments = query.split(TIME_SEPARATOR).map(getDigits);
|
|
196
|
+
if (querySegments.every(function (segmentValue) { return !segmentValue; })) {
|
|
197
|
+
return items;
|
|
198
|
+
}
|
|
199
|
+
return items.filter(function (item) {
|
|
200
|
+
if (!isTimeItem(item)) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
var itemSegments = getDisplaySegments(normalizeTimeValue(getTimeItemValue(item), format), format);
|
|
204
|
+
return querySegments.every(function (segmentValue, index) { var _a; return !segmentValue || ((_a = itemSegments[index]) !== null && _a !== void 0 ? _a : EMPTY_VALUE).startsWith(segmentValue); });
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
//# sourceMappingURL=TimePicker.value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimePicker.value.js","sourceRoot":"","sources":["TimePicker.value.ts"],"names":[],"mappings":"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","sourcesContent":["import warning from 'warning';\n\nimport {\n DIGIT_REGEXP,\n EMPTY_SEGMENT,\n EMPTY_VALUE,\n NON_DIGIT_REGEXP,\n TIME_LIKE_DIGITS_REGEXP,\n TIME_LIKE_SEGMENTS_REGEXP,\n TIME_SEPARATOR,\n ZERO_PAD_CHAR,\n} from './TimePicker.constants.js';\nimport {\n getDigits,\n getDisplaySegments,\n getTimeSegments,\n normalizeTimeSegment,\n sanitizeSegment,\n getTimeItemLabel,\n getTimeItemValue,\n isTimeItemDisabled,\n type TimeFormat,\n type TimeItemValue,\n type TimePickerExtendedItem,\n type TimePickerMenuItem,\n type TimeSegment,\n isTimeItem,\n isTimeItemObject,\n} from './TimePicker.shared.js';\n\n/** Проверяет, что в display-значении нет ни одной введенной цифры. */\nexport const isTimeDisplayEmpty = (value: string): boolean => !DIGIT_REGEXP.test(value);\n\nexport const getTimeValueString = (value: TimeItemValue | null | undefined): string => {\n if (value === null || value === undefined) {\n return EMPTY_VALUE;\n }\n\n return getTimeItemValue(value);\n};\n\n/**\n * Разбирает элементы источника для отрисовки: достает время и подпись,\n * а `disabled` дополняет проверкой диапазона `minTime`/`maxTime`.\n * Исходный элемент сохраняется как есть — именно он уходит в `onValueChange`.\n * React-элементы остаются на своих местах.\n */\nexport const resolveTimeItems = <T extends TimeItemValue>(\n items: Array<TimePickerExtendedItem<T>>,\n format: TimeFormat,\n minTime?: string,\n maxTime?: string,\n): Array<TimePickerMenuItem<T>> =>\n items.map((item) => {\n if (!isTimeItem(item)) {\n return item;\n }\n\n const value = getTimeItemValue(item);\n\n return {\n item,\n value,\n label: getTimeItemLabel(item),\n disabled: isTimeItemDisabled(item) || isTimeValueOutOfRange(value, format, minTime, maxTime),\n };\n });\n\n/**\n * Оставляет только элементы, которые компонент умеет показать в меню.\n * Пустые значения — результат условного рендера вроде `isVisible && item` или `items.length && item` —\n * молча отбрасываются, а элементы, из которых не получается собрать время, отбрасываются с предупреждением.\n */\nexport const sanitizeTimeItems = <T extends TimeItemValue>(\n items: Array<TimePickerExtendedItem<T>>,\n format: TimeFormat,\n): Array<TimePickerExtendedItem<T>> => {\n if (!Array.isArray(items)) {\n warning(false, '[TimePicker]: the source function is expected to return an array or a Promise of an array.');\n\n return [];\n }\n\n return items.filter((item) => {\n if (!item) {\n return false;\n }\n\n if (!isTimeItem(item)) {\n return true;\n }\n\n if (isTimeItemObject(item) && typeof item.value !== 'string') {\n warning(false, '[TimePicker]: a source item is expected to be a time string or an object with a time value.');\n\n return false;\n }\n\n if (normalizeTimeValue(getTimeItemValue(item), format) === EMPTY_VALUE) {\n warning(false, `[TimePicker]: the source item value \"${getTimeItemValue(item)}\" is not a time.`);\n\n return false;\n }\n\n return true;\n });\n};\n\n/**\n * Проверяет, выходит ли значение за диапазон `minTime`/`maxTime` после нормализации.\n * Пустое значение не считается выходящим за диапазон. Если `minTime > maxTime`, диапазон трактуется как переходящий через полночь.\n */\nexport const isTimeValueOutOfRange = (\n value: string,\n format: TimeFormat,\n minTime?: string,\n maxTime?: string,\n): boolean => {\n const normalizedValue = normalizeTimeValue(value, format);\n\n if (normalizedValue === EMPTY_VALUE) {\n return false;\n }\n\n const normalizedMinTime = normalizeTimeRangeValue(minTime, format);\n const normalizedMaxTime = normalizeTimeRangeValue(maxTime, format);\n\n if (normalizedMinTime && normalizedMaxTime && normalizedMinTime > normalizedMaxTime) {\n return normalizedValue < normalizedMinTime && normalizedValue > normalizedMaxTime;\n }\n\n return (\n (normalizedMinTime !== undefined && normalizedValue < normalizedMinTime) ||\n (normalizedMaxTime !== undefined && normalizedValue > normalizedMaxTime)\n );\n};\n\n/**\n * Нормализует display-значение в committed-форму `HH:mm[:ss]`.\n * Частично заполненные сегменты дополняются и ограничиваются максимумами сегментов.\n */\nexport const normalizeTimeValue = (value: string, format: TimeFormat): string => {\n if (isTimeDisplayEmpty(value)) {\n return EMPTY_VALUE;\n }\n\n const displaySegments = getDisplaySegments(value, format);\n\n return getTimeSegments(format)\n .map((segment, index) => normalizeTimeSegment(displaySegments[index], segment))\n .join(TIME_SEPARATOR);\n};\n\nconst normalizeTimeRangeValue = (value: string | undefined, format: TimeFormat): string | undefined => {\n if (!value) {\n return undefined;\n }\n\n const normalizedValue = normalizeTimeValue(value, format);\n\n return normalizedValue === EMPTY_VALUE ? undefined : normalizedValue;\n};\n\n/**\n * Преобразует display-значение в промежуточное значение для `onInputValueChange`.\n * Заполненные сегменты дополняются до двух цифр, пропущенные сегменты до последнего заполненного становятся `00`.\n */\nexport const serializeTimeValue = (value: string, format: TimeFormat): string => {\n const serializedSegments = takeFilledSegments(\n getDisplaySegments(value, format).map((segmentValue) => {\n const digits = getDigits(segmentValue);\n\n return digits ? digits.padStart(EMPTY_SEGMENT.length, ZERO_PAD_CHAR) : EMPTY_VALUE;\n }),\n );\n\n return serializedSegments.map((segmentValue) => segmentValue || '00').join(TIME_SEPARATOR);\n};\n\n/** Отбрасывает незаполненный хвост сегментов: ведущие пустые сегменты остаются на своих местах. */\nconst takeFilledSegments = (segments: string[]): string[] => {\n const lastFilledIndex = segments.reduce((result, segmentValue, index) => (segmentValue ? index : result), -1);\n\n return segments.slice(0, lastFilledIndex + 1);\n};\n\nexport const getTimeDisplayValue = (value: string, format: TimeFormat): string => {\n if (value === EMPTY_VALUE) {\n return EMPTY_VALUE;\n }\n\n return getDisplaySegments(value, format).join(TIME_SEPARATOR);\n};\n\n/**\n * Возвращает display-значение внешнего `value`.\n * Сегменты, записанные одной цифрой, дополняются нулем, поэтому `9:00` показывается как `09:00` —\n * так же, как компонент показывает время элементов источника.\n * Значение без разделителя остается частично введенным: `1` — это первая цифра часов, а не `01:00`.\n */\nexport const getExternalTimeDisplayValue = (value: string, format: TimeFormat): string => {\n if (!value.includes(TIME_SEPARATOR)) {\n return getTimeDisplayValue(value, format);\n }\n\n const paddedSegments = value.split(TIME_SEPARATOR).map((segmentValue) => {\n const digits = getDigits(segmentValue);\n\n return digits ? digits.padStart(EMPTY_SEGMENT.length, ZERO_PAD_CHAR) : EMPTY_VALUE;\n });\n\n return getTimeDisplayValue(paddedSegments.join(TIME_SEPARATOR), format);\n};\n\n/** Заменяет значение выбранного сегмента, сохраняя остальные сегменты display-значения без изменений. */\nexport const replaceTimeSegment = (\n value: string,\n segment: TimeSegment,\n nextSegmentValue: string,\n format: TimeFormat,\n): string => {\n const segments = getDisplaySegments(value, format);\n const index = getTimeSegments(format).indexOf(segment);\n\n segments[index] = sanitizeSegment(nextSegmentValue);\n\n return segments.join(TIME_SEPARATOR);\n};\n\n/**\n * Проверяет, что вставленное значение похоже на время:\n * это либо сегменты из одной-двух цифр через любой разделитель — `12:30`, `9:5`, `1:23:45`, `12.30`, —\n * либо цифры подряд — `9`, `1230`, `123045`.\n * Строки, из которых время собралось бы случайно — `2026-08-01`, `hello 42`, — временем не считаются.\n */\nexport const isTimeLikePastedValue = (value: string): boolean => {\n const trimmedValue = value.trim();\n\n return TIME_LIKE_SEGMENTS_REGEXP.test(trimmedValue) || TIME_LIKE_DIGITS_REGEXP.test(trimmedValue);\n};\n\n/**\n * Парсит вставленное пользователем значение и сразу преобразует его в committed-форму.\n * Значение, не похожее на время, отдается как пустое: такую вставку компонент считает некорректным вводом.\n */\nexport const parsePastedTimeValue = (value: string, format: TimeFormat): string => {\n const trimmedValue = value.trim();\n\n if (!DIGIT_REGEXP.test(trimmedValue) || !isTimeLikePastedValue(trimmedValue)) {\n return EMPTY_VALUE;\n }\n\n // Разделители сегментов приводятся к одному виду, чтобы `9.5` разбирался так же, как `9:5`.\n const normalizedSeparators = TIME_LIKE_SEGMENTS_REGEXP.test(trimmedValue)\n ? trimmedValue.split(NON_DIGIT_REGEXP).join(TIME_SEPARATOR)\n : trimmedValue;\n\n const segments = getDisplaySegments(normalizedSeparators, format);\n\n return normalizeTimeValue(segments.join(TIME_SEPARATOR), format);\n};\n\n/** Возвращает текущее display-значение сегмента или пустой сегмент, если индекс отсутствует. */\nexport const getTimeSegmentValue = (value: string, segment: TimeSegment, format: TimeFormat): string => {\n const segments = getDisplaySegments(value, format);\n const index = getTimeSegments(format).indexOf(segment);\n\n return segments[index] ?? EMPTY_SEGMENT;\n};\n\nexport const getEmptyDisplayValue = (precision: TimeFormat): string =>\n getTimeSegments(precision)\n .map(() => EMPTY_SEGMENT)\n .join(TIME_SEPARATOR);\n\n/**\n * Преобразует display-значение в поисковый запрос для фильтрации элементов.\n * Сегменты содержат только введенные цифры, незаполненный хвост отбрасывается: `12:3_` => `12:3`, `1_:__` => `1`.\n * Позиции сегментов сохраняются, поэтому ведущие незаполненные сегменты остаются пустыми: `__:34` => `:34`.\n * Это нужно для посегментного сравнения в `filterTimeItems` и передается как есть в пользовательскую `source`.\n */\nexport const getTimeFilterQuery = (value: string, format: TimeFormat): string =>\n takeFilledSegments(getDisplaySegments(value, format).map(getDigits)).join(TIME_SEPARATOR);\n\n/**\n * Фильтрует элементы по запросу из поля ввода.\n * Сравнивает запрос посегментно: введенные цифры сегмента должны быть префиксом соответствующего сегмента элемента.\n * Пустой запрос возвращает все элементы.\n */\nexport const filterTimeItems = <T extends TimeItemValue>(\n items: Array<TimePickerExtendedItem<T>>,\n query: string,\n format: TimeFormat,\n): Array<TimePickerExtendedItem<T>> => {\n const querySegments = query.split(TIME_SEPARATOR).map(getDigits);\n\n if (querySegments.every((segmentValue) => !segmentValue)) {\n return items;\n }\n\n return items.filter((item) => {\n if (!isTimeItem(item)) {\n return true;\n }\n\n const itemSegments = getDisplaySegments(normalizeTimeValue(getTimeItemValue(item), format), format);\n\n return querySegments.every(\n (segmentValue, index) => !segmentValue || (itemSegments[index] ?? EMPTY_VALUE).startsWith(segmentValue),\n );\n });\n};\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Прокручивает ближайший scrollable-контейнер так, чтобы выбранный элемент оказался ближе к центру viewport контейнера.
|
|
3
|
+
* Отличается от библиотечной `scrollYCenterIntoNearestScrollable` тем, что смещение считается от текущего `scrollTop`:
|
|
4
|
+
* центр — это дельта относительно контейнера, поэтому библиотечная версия верна только для непрокрученного меню.
|
|
5
|
+
*/
|
|
6
|
+
export declare const scrollSelectedItemIntoView: (element: HTMLElement) => void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { findNearestScrollableParent } from '../../../lib/dom/scrollYCenterIntoNearestScrollable.js';
|
|
2
|
+
import { getOwnerGlobalObject, isBrowser } from '../../../lib/globalObject.js';
|
|
3
|
+
/**
|
|
4
|
+
* Прокручивает ближайший scrollable-контейнер так, чтобы выбранный элемент оказался ближе к центру viewport контейнера.
|
|
5
|
+
* Отличается от библиотечной `scrollYCenterIntoNearestScrollable` тем, что смещение считается от текущего `scrollTop`:
|
|
6
|
+
* центр — это дельта относительно контейнера, поэтому библиотечная версия верна только для непрокрученного меню.
|
|
7
|
+
*/
|
|
8
|
+
export var scrollSelectedItemIntoView = function (element) {
|
|
9
|
+
var globalObject = getOwnerGlobalObject(element);
|
|
10
|
+
if (!isBrowser(globalObject)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
var parent = findNearestScrollableParent(element);
|
|
14
|
+
if (!parent) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
var elementRect = element.getBoundingClientRect();
|
|
18
|
+
var parentRect = parent.getBoundingClientRect();
|
|
19
|
+
var scrollTopPos = elementRect.top - parentRect.top;
|
|
20
|
+
var scrollBottomPos = elementRect.bottom - parentRect.bottom;
|
|
21
|
+
var scrollCenterPos = (scrollTopPos + scrollBottomPos) / 2;
|
|
22
|
+
if (scrollCenterPos !== 0) {
|
|
23
|
+
parent.scrollTo({ top: parent.scrollTop + scrollCenterPos });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=scrollSelectedItemIntoView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrollSelectedItemIntoView.js","sourceRoot":"","sources":["scrollSelectedItemIntoView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,wDAAwD,CAAC;AACrG,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE/E;;;;GAIG;AACH,MAAM,CAAC,IAAM,0BAA0B,GAAG,UAAC,OAAoB;IAC7D,IAAM,YAAY,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAEnD,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,OAAO;IACT,CAAC;IAED,IAAM,MAAM,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IAEpD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,IAAM,WAAW,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;IACpD,IAAM,UAAU,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;IAElD,IAAM,YAAY,GAAG,WAAW,CAAC,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC;IACtD,IAAM,eAAe,GAAG,WAAW,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/D,IAAM,eAAe,GAAG,CAAC,YAAY,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAE7D,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,SAAS,GAAG,eAAe,EAAE,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC,CAAC","sourcesContent":["import { findNearestScrollableParent } from '../../../lib/dom/scrollYCenterIntoNearestScrollable.js';\nimport { getOwnerGlobalObject, isBrowser } from '../../../lib/globalObject.js';\n\n/**\n * Прокручивает ближайший scrollable-контейнер так, чтобы выбранный элемент оказался ближе к центру viewport контейнера.\n * Отличается от библиотечной `scrollYCenterIntoNearestScrollable` тем, что смещение считается от текущего `scrollTop`:\n * центр — это дельта относительно контейнера, поэтому библиотечная версия верна только для непрокрученного меню.\n */\nexport const scrollSelectedItemIntoView = (element: HTMLElement): void => {\n const globalObject = getOwnerGlobalObject(element);\n\n if (!isBrowser(globalObject)) {\n return;\n }\n\n const parent = findNearestScrollableParent(element);\n\n if (!parent) {\n return;\n }\n\n const elementRect = element.getBoundingClientRect();\n const parentRect = parent.getBoundingClientRect();\n\n const scrollTopPos = elementRect.top - parentRect.top;\n const scrollBottomPos = elementRect.bottom - parentRect.bottom;\n const scrollCenterPos = (scrollTopPos + scrollBottomPos) / 2;\n\n if (scrollCenterPos !== 0) {\n parent.scrollTo({ top: parent.scrollTop + scrollCenterPos });\n }\n};\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Nullable } from '../../../typings/utility-types.js';
|
|
2
|
+
import type { TimeFormat, TimeItem } from './TimePicker.shared.js';
|
|
3
|
+
export interface TimePickerValidationOptions {
|
|
4
|
+
format?: TimeFormat;
|
|
5
|
+
minTime?: string;
|
|
6
|
+
maxTime?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const validateTimePicker: (value: Nullable<string | TimeItem>, options?: TimePickerValidationOptions) => boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getTimeValueString, isTimeValueOutOfRange, normalizeTimeValue } from './TimePicker.value.js';
|
|
2
|
+
export var validateTimePicker = function (value, options) {
|
|
3
|
+
if (options === void 0) { options = {}; }
|
|
4
|
+
var timeValue = getTimeValueString(value);
|
|
5
|
+
if (!timeValue) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
var _a = options.format, format = _a === void 0 ? 'HH:mm' : _a, minTime = options.minTime, maxTime = options.maxTime;
|
|
9
|
+
// Нормализованное значение уже канонично, поэтому отдельная проверка display-формы не нужна.
|
|
10
|
+
if (normalizeTimeValue(timeValue, format) !== timeValue) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return !isTimeValueOutOfRange(timeValue, format, minTime, maxTime);
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=validateTimePicker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateTimePicker.js","sourceRoot":"","sources":["validateTimePicker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAQtG,MAAM,CAAC,IAAM,kBAAkB,GAAG,UAChC,KAAkC,EAClC,OAAyC;IAAzC,wBAAA,EAAA,YAAyC;IAEzC,IAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE5C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,IAAA,KAAuC,OAAO,OAA9B,EAAhB,MAAM,mBAAG,OAAO,KAAA,EAAE,OAAO,GAAc,OAAO,QAArB,EAAE,OAAO,GAAK,OAAO,QAAZ,CAAa;IAEvD,6FAA6F;IAC7F,IAAI,kBAAkB,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,SAAS,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CAAC,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACrE,CAAC,CAAC","sourcesContent":["import type { Nullable } from '../../../typings/utility-types.js';\nimport type { TimeFormat, TimeItem } from './TimePicker.shared.js';\nimport { getTimeValueString, isTimeValueOutOfRange, normalizeTimeValue } from './TimePicker.value.js';\n\nexport interface TimePickerValidationOptions {\n format?: TimeFormat;\n minTime?: string;\n maxTime?: string;\n}\n\nexport const validateTimePicker = (\n value: Nullable<string | TimeItem>,\n options: TimePickerValidationOptions = {},\n): boolean => {\n const timeValue = getTimeValueString(value);\n\n if (!timeValue) {\n return false;\n }\n\n const { format = 'HH:mm', minTime, maxTime } = options;\n\n // Нормализованное значение уже канонично, поэтому отдельная проверка display-формы не нужна.\n if (normalizeTimeValue(timeValue, format) !== timeValue) {\n return false;\n }\n\n return !isTimeValueOutOfRange(timeValue, format, minTime, maxTime);\n};\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type TimeItemValue, type TimePickerMenuItem } from '../helpers/TimePicker.shared.js';
|
|
2
|
+
interface UseTimePickerDropdownOptions<T extends TimeItemValue> {
|
|
3
|
+
hasDropdown: boolean;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
items: Array<TimePickerMenuItem<T>>;
|
|
6
|
+
selectedItemIndex: number | null;
|
|
7
|
+
autoHighlightKey: string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Состоянием открытости владеет компонент: от него зависит запрос элементов,
|
|
10
|
+
* а элементы, в свою очередь, нужны этому хуку для навигации.
|
|
11
|
+
*/
|
|
12
|
+
isDropdownOpened: boolean;
|
|
13
|
+
setIsDropdownOpened(isOpened: boolean): void;
|
|
14
|
+
}
|
|
15
|
+
interface UseTimePickerDropdownResult {
|
|
16
|
+
highlightedItemIndex: number | null;
|
|
17
|
+
openDropdown(): void;
|
|
18
|
+
closeDropdown(): void;
|
|
19
|
+
resetHighlightedItem(): void;
|
|
20
|
+
tryNavigateItems(step: 1 | -1): boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare const useTimePickerDropdown: <T extends TimeItemValue>(options: UseTimePickerDropdownOptions<T>) => UseTimePickerDropdownResult;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { isHighlightableMenuItem, isTimeMenuItem, } from '../helpers/TimePicker.shared.js';
|
|
3
|
+
export var useTimePickerDropdown = function (options) {
|
|
4
|
+
var hasDropdown = options.hasDropdown, disabled = options.disabled, items = options.items, selectedItemIndex = options.selectedItemIndex, autoHighlightKey = options.autoHighlightKey, isDropdownOpened = options.isDropdownOpened, setIsDropdownOpened = options.setIsDropdownOpened;
|
|
5
|
+
var _a = useState(null), highlightedItemIndex = _a[0], setHighlightedItemIndex = _a[1];
|
|
6
|
+
var lastAutoHighlightKeyRef = useRef(null);
|
|
7
|
+
var openDropdown = useCallback(function () {
|
|
8
|
+
if (!hasDropdown || disabled) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
setIsDropdownOpened(true);
|
|
12
|
+
}, [disabled, hasDropdown, setIsDropdownOpened]);
|
|
13
|
+
var closeDropdown = useCallback(function () {
|
|
14
|
+
setIsDropdownOpened(false);
|
|
15
|
+
setHighlightedItemIndex(null);
|
|
16
|
+
}, [setIsDropdownOpened]);
|
|
17
|
+
var resetHighlightedItem = useCallback(function () {
|
|
18
|
+
setHighlightedItemIndex(null);
|
|
19
|
+
}, []);
|
|
20
|
+
useEffect(function () {
|
|
21
|
+
if (lastAutoHighlightKeyRef.current === autoHighlightKey) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
lastAutoHighlightKeyRef.current = autoHighlightKey;
|
|
25
|
+
if (autoHighlightKey === null) {
|
|
26
|
+
setHighlightedItemIndex(null);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
var firstEnabledItemIndex = items.findIndex(function (item) { return isTimeMenuItem(item) && !item.disabled; });
|
|
30
|
+
setHighlightedItemIndex(firstEnabledItemIndex >= 0 ? firstEnabledItemIndex : null);
|
|
31
|
+
}, [autoHighlightKey, items]);
|
|
32
|
+
useEffect(function () {
|
|
33
|
+
setHighlightedItemIndex(function (current) {
|
|
34
|
+
if (current === null) {
|
|
35
|
+
return current;
|
|
36
|
+
}
|
|
37
|
+
var item = items[current];
|
|
38
|
+
return !item || !isHighlightableMenuItem(item) ? null : current;
|
|
39
|
+
});
|
|
40
|
+
}, [items]);
|
|
41
|
+
var tryNavigateItems = useCallback(function (step) {
|
|
42
|
+
var _a, _b;
|
|
43
|
+
if (!hasDropdown || !isDropdownOpened) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
var enabledIndexes = items.flatMap(function (item, index) { return (isHighlightableMenuItem(item) ? index : []); });
|
|
47
|
+
if (enabledIndexes.length === 0) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Навигация идет от выделенного элемента, а если его нет — от выбранного значения.
|
|
52
|
+
* Отсутствующий и заблокированный элементы дают `-1`, и тогда шаг вниз попадает на первый элемент,
|
|
53
|
+
* а шаг вверх выходит за начало и по фолбэку ниже уходит на последний.
|
|
54
|
+
*/
|
|
55
|
+
var currentIndex = enabledIndexes.indexOf((_a = highlightedItemIndex !== null && highlightedItemIndex !== void 0 ? highlightedItemIndex : selectedItemIndex) !== null && _a !== void 0 ? _a : -1);
|
|
56
|
+
setHighlightedItemIndex((_b = enabledIndexes[currentIndex + step]) !== null && _b !== void 0 ? _b : (step > 0 ? enabledIndexes[0] : enabledIndexes[enabledIndexes.length - 1]));
|
|
57
|
+
return true;
|
|
58
|
+
}, [hasDropdown, highlightedItemIndex, isDropdownOpened, selectedItemIndex, items]);
|
|
59
|
+
return {
|
|
60
|
+
highlightedItemIndex: highlightedItemIndex,
|
|
61
|
+
openDropdown: openDropdown,
|
|
62
|
+
closeDropdown: closeDropdown,
|
|
63
|
+
resetHighlightedItem: resetHighlightedItem,
|
|
64
|
+
tryNavigateItems: tryNavigateItems,
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=useTimePickerDropdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTimePickerDropdown.js","sourceRoot":"","sources":["useTimePickerDropdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjE,OAAO,EACL,uBAAuB,EACvB,cAAc,GAGf,MAAM,iCAAiC,CAAC;AAwBzC,MAAM,CAAC,IAAM,qBAAqB,GAAG,UACnC,OAAwC;IAEhC,IAAA,WAAW,GACjB,OAAO,YADU,EAAE,QAAQ,GAC3B,OAAO,SADoB,EAAE,KAAK,GAClC,OAAO,MAD2B,EAAE,iBAAiB,GACrD,OAAO,kBAD8C,EAAE,gBAAgB,GACvE,OAAO,iBADgE,EAAE,gBAAgB,GACzF,OAAO,iBADkF,EAAE,mBAAmB,GAC9G,OAAO,oBADuG,CACtG;IAEJ,IAAA,KAAkD,QAAQ,CAAgB,IAAI,CAAC,EAA9E,oBAAoB,QAAA,EAAE,uBAAuB,QAAiC,CAAC;IACtF,IAAM,uBAAuB,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAE5D,IAAM,YAAY,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,WAAW,IAAI,QAAQ,EAAE,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAEjD,IAAM,aAAa,GAAG,WAAW,CAAC;QAChC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC3B,uBAAuB,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAE1B,IAAM,oBAAoB,GAAG,WAAW,CAAC;QACvC,uBAAuB,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC;QACR,IAAI,uBAAuB,CAAC,OAAO,KAAK,gBAAgB,EAAE,CAAC;YACzD,OAAO;QACT,CAAC;QAED,uBAAuB,CAAC,OAAO,GAAG,gBAAgB,CAAC;QAEnD,IAAI,gBAAgB,KAAK,IAAI,EAAE,CAAC;YAC9B,uBAAuB,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO;QACT,CAAC;QAED,IAAM,qBAAqB,GAAG,KAAK,CAAC,SAAS,CAAC,UAAC,IAAI,IAAK,OAAA,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAtC,CAAsC,CAAC,CAAC;QAChG,uBAAuB,CAAC,qBAAqB,IAAI,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrF,CAAC,EAAE,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC;IAE9B,SAAS,CAAC;QACR,uBAAuB,CAAC,UAAC,OAAO;YAC9B,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,OAAO,OAAO,CAAC;YACjB,CAAC;YAED,IAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;YAE5B,OAAO,CAAC,IAAI,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;QAClE,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,IAAM,gBAAgB,GAAG,WAAW,CAClC,UAAC,IAAY;;QACX,IAAI,CAAC,WAAW,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,UAAC,IAAI,EAAE,KAAK,IAAK,OAAA,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAA5C,CAA4C,CAAC,CAAC;QAEpG,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACf,CAAC;QAED;;;;WAIG;QACH,IAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAC,MAAA,oBAAoB,aAApB,oBAAoB,cAApB,oBAAoB,GAAI,iBAAiB,mCAAI,CAAC,CAAC,CAAC,CAAC;QAE7F,uBAAuB,CACrB,MAAA,cAAc,CAAC,YAAY,GAAG,IAAI,CAAC,mCACjC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAC7E,CAAC;QAEF,OAAO,IAAI,CAAC;IACd,CAAC,EACD,CAAC,WAAW,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAChF,CAAC;IAEF,OAAO;QACL,oBAAoB,sBAAA;QACpB,YAAY,cAAA;QACZ,aAAa,eAAA;QACb,oBAAoB,sBAAA;QACpB,gBAAgB,kBAAA;KACjB,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport {\n isHighlightableMenuItem,\n isTimeMenuItem,\n type TimeItemValue,\n type TimePickerMenuItem,\n} from '../helpers/TimePicker.shared.js';\n\ninterface UseTimePickerDropdownOptions<T extends TimeItemValue> {\n hasDropdown: boolean;\n disabled?: boolean;\n items: Array<TimePickerMenuItem<T>>;\n selectedItemIndex: number | null;\n autoHighlightKey: string | null;\n /**\n * Состоянием открытости владеет компонент: от него зависит запрос элементов,\n * а элементы, в свою очередь, нужны этому хуку для навигации.\n */\n isDropdownOpened: boolean;\n setIsDropdownOpened(isOpened: boolean): void;\n}\n\ninterface UseTimePickerDropdownResult {\n highlightedItemIndex: number | null;\n openDropdown(): void;\n closeDropdown(): void;\n resetHighlightedItem(): void;\n tryNavigateItems(step: 1 | -1): boolean;\n}\n\nexport const useTimePickerDropdown = <T extends TimeItemValue>(\n options: UseTimePickerDropdownOptions<T>,\n): UseTimePickerDropdownResult => {\n const { hasDropdown, disabled, items, selectedItemIndex, autoHighlightKey, isDropdownOpened, setIsDropdownOpened } =\n options;\n\n const [highlightedItemIndex, setHighlightedItemIndex] = useState<number | null>(null);\n const lastAutoHighlightKeyRef = useRef<string | null>(null);\n\n const openDropdown = useCallback(() => {\n if (!hasDropdown || disabled) {\n return;\n }\n\n setIsDropdownOpened(true);\n }, [disabled, hasDropdown, setIsDropdownOpened]);\n\n const closeDropdown = useCallback(() => {\n setIsDropdownOpened(false);\n setHighlightedItemIndex(null);\n }, [setIsDropdownOpened]);\n\n const resetHighlightedItem = useCallback(() => {\n setHighlightedItemIndex(null);\n }, []);\n\n useEffect(() => {\n if (lastAutoHighlightKeyRef.current === autoHighlightKey) {\n return;\n }\n\n lastAutoHighlightKeyRef.current = autoHighlightKey;\n\n if (autoHighlightKey === null) {\n setHighlightedItemIndex(null);\n return;\n }\n\n const firstEnabledItemIndex = items.findIndex((item) => isTimeMenuItem(item) && !item.disabled);\n setHighlightedItemIndex(firstEnabledItemIndex >= 0 ? firstEnabledItemIndex : null);\n }, [autoHighlightKey, items]);\n\n useEffect(() => {\n setHighlightedItemIndex((current) => {\n if (current === null) {\n return current;\n }\n\n const item = items[current];\n\n return !item || !isHighlightableMenuItem(item) ? null : current;\n });\n }, [items]);\n\n const tryNavigateItems = useCallback(\n (step: 1 | -1) => {\n if (!hasDropdown || !isDropdownOpened) {\n return false;\n }\n\n const enabledIndexes = items.flatMap((item, index) => (isHighlightableMenuItem(item) ? index : []));\n\n if (enabledIndexes.length === 0) {\n return false;\n }\n\n /**\n * Навигация идет от выделенного элемента, а если его нет — от выбранного значения.\n * Отсутствующий и заблокированный элементы дают `-1`, и тогда шаг вниз попадает на первый элемент,\n * а шаг вверх выходит за начало и по фолбэку ниже уходит на последний.\n */\n const currentIndex = enabledIndexes.indexOf(highlightedItemIndex ?? selectedItemIndex ?? -1);\n\n setHighlightedItemIndex(\n enabledIndexes[currentIndex + step] ??\n (step > 0 ? enabledIndexes[0] : enabledIndexes[enabledIndexes.length - 1]),\n );\n\n return true;\n },\n [hasDropdown, highlightedItemIndex, isDropdownOpened, selectedItemIndex, items],\n );\n\n return {\n highlightedItemIndex,\n openDropdown,\n closeDropdown,\n resetHighlightedItem,\n tryNavigateItems,\n };\n};\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TimeInputRef } from '../../../internal/TimeInput/index.js';
|
|
2
|
+
import type { TimeFormat, TimeSegment } from '../helpers/TimePicker.shared.js';
|
|
3
|
+
type TimePickerSelection = TimeSegment | 'all';
|
|
4
|
+
interface UseTimePickerSelectionOptions {
|
|
5
|
+
isInputFocused: boolean;
|
|
6
|
+
format: TimeFormat;
|
|
7
|
+
getInput: () => TimeInputRef | null;
|
|
8
|
+
displayValue: string;
|
|
9
|
+
}
|
|
10
|
+
interface UseTimePickerSelectionResult {
|
|
11
|
+
selection: TimePickerSelection;
|
|
12
|
+
selectedSegment: TimeSegment;
|
|
13
|
+
selectSegment: (segment: TimeSegment) => void;
|
|
14
|
+
selectAll: () => void;
|
|
15
|
+
syncSelectionWithDOM: () => boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const useTimePickerSelection: (options: UseTimePickerSelectionOptions) => UseTimePickerSelectionResult;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useState } from 'react';
|
|
2
|
+
export var useTimePickerSelection = function (options) {
|
|
3
|
+
var isInputFocused = options.isInputFocused, format = options.format, displayValue = options.displayValue, getInput = options.getInput;
|
|
4
|
+
var _a = useState('hours'), selection = _a[0], setSelectionState = _a[1];
|
|
5
|
+
var selectedSegment = selection === 'all' ? 'hours' : selection;
|
|
6
|
+
var applySelectionToInput = useCallback(function (nextSelection) {
|
|
7
|
+
var currentInput = getInput();
|
|
8
|
+
if (!currentInput) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
var node = currentInput.getNode();
|
|
12
|
+
var isInputActive = (node === null || node === void 0 ? void 0 : node.ownerDocument.activeElement) === node;
|
|
13
|
+
var applySelection = function (input) {
|
|
14
|
+
if (nextSelection === 'all') {
|
|
15
|
+
input.selectAll();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
input.selectSegment(nextSelection);
|
|
19
|
+
};
|
|
20
|
+
if (isInputActive) {
|
|
21
|
+
applySelection(currentInput);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
setTimeout(function () {
|
|
25
|
+
var nextInput = getInput();
|
|
26
|
+
if (!nextInput) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
nextInput.focus();
|
|
30
|
+
applySelection(nextInput);
|
|
31
|
+
}, 0);
|
|
32
|
+
}, [getInput]);
|
|
33
|
+
var selectSegment = useCallback(function (segment) {
|
|
34
|
+
if (segment === 'seconds' && format === 'HH:mm') {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
setSelectionState(segment);
|
|
38
|
+
applySelectionToInput(segment);
|
|
39
|
+
}, [applySelectionToInput, format]);
|
|
40
|
+
var selectAll = useCallback(function () {
|
|
41
|
+
setSelectionState('all');
|
|
42
|
+
applySelectionToInput('all');
|
|
43
|
+
}, [applySelectionToInput]);
|
|
44
|
+
var syncSelectionWithDOM = useCallback(function () {
|
|
45
|
+
var _a;
|
|
46
|
+
if (!((_a = getInput()) === null || _a === void 0 ? void 0 : _a.isAllSelected())) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
setSelectionState('all');
|
|
50
|
+
return true;
|
|
51
|
+
}, [getInput]);
|
|
52
|
+
useLayoutEffect(function () {
|
|
53
|
+
if (!isInputFocused || !getInput()) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
applySelectionToInput(selection);
|
|
57
|
+
}, [applySelectionToInput, displayValue, getInput, isInputFocused, selection]);
|
|
58
|
+
return {
|
|
59
|
+
selection: selection,
|
|
60
|
+
selectedSegment: selectedSegment,
|
|
61
|
+
selectSegment: selectSegment,
|
|
62
|
+
selectAll: selectAll,
|
|
63
|
+
syncSelectionWithDOM: syncSelectionWithDOM,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=useTimePickerSelection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTimePickerSelection.js","sourceRoot":"","sources":["useTimePickerSelection.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useLayoutEffect, useState } from 'react';\n\nimport type { TimeInputRef } from '../../../internal/TimeInput/index.js';\nimport type { TimeFormat, TimeSegment } from '../helpers/TimePicker.shared.js';\n\ntype TimePickerSelection = TimeSegment | 'all';\n\ninterface UseTimePickerSelectionOptions {\n isInputFocused: boolean;\n format: TimeFormat;\n getInput: () => TimeInputRef | null;\n displayValue: string;\n}\ninterface UseTimePickerSelectionResult {\n selection: TimePickerSelection;\n selectedSegment: TimeSegment;\n selectSegment: (segment: TimeSegment) => void;\n selectAll: () => void;\n syncSelectionWithDOM: () => boolean;\n}\nexport const useTimePickerSelection = (options: UseTimePickerSelectionOptions): UseTimePickerSelectionResult => {\n const { isInputFocused, format, displayValue, getInput } = options;\n\n const [selection, setSelectionState] = useState<TimePickerSelection>('hours');\n\n const selectedSegment = selection === 'all' ? 'hours' : selection;\n\n const applySelectionToInput = useCallback(\n (nextSelection: TimePickerSelection) => {\n const currentInput = getInput();\n\n if (!currentInput) {\n return;\n }\n\n const node = currentInput.getNode();\n const isInputActive = node?.ownerDocument.activeElement === node;\n\n const applySelection = (input: TimeInputRef) => {\n if (nextSelection === 'all') {\n input.selectAll();\n return;\n }\n\n input.selectSegment(nextSelection);\n };\n\n if (isInputActive) {\n applySelection(currentInput);\n return;\n }\n\n setTimeout(() => {\n const nextInput = getInput();\n\n if (!nextInput) {\n return;\n }\n\n nextInput.focus();\n applySelection(nextInput);\n }, 0);\n },\n [getInput],\n );\n\n const selectSegment = useCallback(\n (segment: TimeSegment) => {\n if (segment === 'seconds' && format === 'HH:mm') {\n return;\n }\n\n setSelectionState(segment);\n applySelectionToInput(segment);\n },\n [applySelectionToInput, format],\n );\n\n const selectAll = useCallback(() => {\n setSelectionState('all');\n applySelectionToInput('all');\n }, [applySelectionToInput]);\n\n const syncSelectionWithDOM = useCallback(() => {\n if (!getInput()?.isAllSelected()) {\n return false;\n }\n\n setSelectionState('all');\n return true;\n }, [getInput]);\n\n useLayoutEffect(() => {\n if (!isInputFocused || !getInput()) {\n return;\n }\n\n applySelectionToInput(selection);\n }, [applySelectionToInput, displayValue, getInput, isInputFocused, selection]);\n\n return {\n selection,\n selectedSegment,\n selectSegment,\n selectAll,\n syncSelectionWithDOM,\n };\n};\n"]}
|