@via-profit/ui-kit 0.2.5 → 0.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/Autocomplete/AutocompleteContainer.js +47 -35
- package/Autocomplete/index.js +9 -7
- package/Autocomplete2/Autocomplete2.js +1 -1
- package/CHANGELOG.md +38 -0
- package/Calendar/CalendarComponent.js +89 -66
- package/Calendar/use-calendar.d.ts +3 -2
- package/Calendar/use-calendar.js +31 -24
- package/ClickOutside/index.js +12 -1
- package/Color/ColorParser.d.ts +9 -0
- package/Color/ColorParser.js +52 -12
- package/DatePicker/index.d.ts +4 -0
- package/DatePicker/index.js +28 -14
- package/DatePicker/useDatePickerFormat.js +20 -3
- package/MaskedField/useMasked.js +17 -6
- package/Menu/MenuContainer.d.ts +3 -3
- package/Menu/MenuContainer.js +26 -35
- package/Menu/VirtualizedList.js +1 -3
- package/Modal/BaseModal/ModalWrapper.js +65 -38
- package/Modal/BaseModal/context.js +6 -0
- package/Modal/BaseModal/index.js +0 -8
- package/Popper/Popper.d.ts +2 -2
- package/Popper/Popper.js +9 -7
- package/Popper/usePopper.js +79 -46
- package/Selectbox/SelectboxMenu.d.ts +1 -1
- package/Selectbox/index.js +11 -27
- package/Swiper/Swiper.js +17 -5
- package/TextArea/index.js +6 -14
- package/TextField/index.js +6 -18
- package/package.json +1 -1
- package/utils/TabManager.d.ts +7 -1
- package/utils/TabManager.js +39 -45
|
@@ -52,6 +52,34 @@ const Autocomplete = react_1.default.forwardRef((props, ref) => {
|
|
|
52
52
|
react_1.default.useImperativeHandle(ref, () => ({
|
|
53
53
|
clear: () => clear(),
|
|
54
54
|
}), [clear]);
|
|
55
|
+
const applyFilterForItems = react_1.default.useCallback((inputValue, itemList) => {
|
|
56
|
+
const query = inputValue.toLowerCase().trim();
|
|
57
|
+
// Apply filter
|
|
58
|
+
return typeof filterItems !== 'function' || query.length === 0
|
|
59
|
+
? itemList
|
|
60
|
+
: filterItems(itemList, { query, inputValue });
|
|
61
|
+
}, [filterItems]);
|
|
62
|
+
/**
|
|
63
|
+
* Restore the input text from the current value (used by clearOnBlur)
|
|
64
|
+
*/
|
|
65
|
+
const restoreInputValue = react_1.default.useCallback(() => {
|
|
66
|
+
const newInputValue = currentValue === null
|
|
67
|
+
? ''
|
|
68
|
+
: selectedItemToString(currentValue);
|
|
69
|
+
const newFilteredItems = multiple ? filteredItems : applyFilterForItems(newInputValue, items);
|
|
70
|
+
dispatch((0, context_1.actionSetPartial)({
|
|
71
|
+
filteredItems: newFilteredItems,
|
|
72
|
+
inputValue: newInputValue,
|
|
73
|
+
}));
|
|
74
|
+
}, [
|
|
75
|
+
currentValue,
|
|
76
|
+
selectedItemToString,
|
|
77
|
+
multiple,
|
|
78
|
+
filteredItems,
|
|
79
|
+
applyFilterForItems,
|
|
80
|
+
items,
|
|
81
|
+
dispatch,
|
|
82
|
+
]);
|
|
55
83
|
const inputKeydownEvent = react_1.default.useCallback((event) => {
|
|
56
84
|
var _a, _b, _c, _d, _e;
|
|
57
85
|
if (!isFocusedRef.current) {
|
|
@@ -98,8 +126,16 @@ const Autocomplete = react_1.default.forwardRef((props, ref) => {
|
|
|
98
126
|
// case 'PageDown':
|
|
99
127
|
// event.preventDefault();
|
|
100
128
|
// break;
|
|
101
|
-
case 'Escape':
|
|
102
129
|
case 'Tab':
|
|
130
|
+
// Focus leaves the field, so clearOnBlur applies as for the outside click
|
|
131
|
+
if (clearOnBlur) {
|
|
132
|
+
restoreInputValue();
|
|
133
|
+
}
|
|
134
|
+
if (currentOpen) {
|
|
135
|
+
onRequestClose(event);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case 'Escape':
|
|
103
139
|
if (currentOpen) {
|
|
104
140
|
onRequestClose(event);
|
|
105
141
|
}
|
|
@@ -108,14 +144,14 @@ const Autocomplete = react_1.default.forwardRef((props, ref) => {
|
|
|
108
144
|
// do nothing
|
|
109
145
|
break;
|
|
110
146
|
}
|
|
111
|
-
}, [
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
147
|
+
}, [
|
|
148
|
+
currentOpen,
|
|
149
|
+
onRequestClose,
|
|
150
|
+
onRequestOpen,
|
|
151
|
+
filteredItems.length,
|
|
152
|
+
clearOnBlur,
|
|
153
|
+
restoreInputValue,
|
|
154
|
+
]);
|
|
119
155
|
const renderChildren = react_1.default.useCallback(({ index, item }, itemProps) => children({ index, item: item, inputValue }, itemProps), [children, inputValue]);
|
|
120
156
|
/**
|
|
121
157
|
* Only affected value change action
|
|
@@ -178,16 +214,7 @@ const Autocomplete = react_1.default.forwardRef((props, ref) => {
|
|
|
178
214
|
// Click outside
|
|
179
215
|
if (needToClose && currentOpen) {
|
|
180
216
|
if (clearOnBlur) {
|
|
181
|
-
|
|
182
|
-
? ''
|
|
183
|
-
: selectedItemToString(currentValue);
|
|
184
|
-
const newFilteredItems = multiple
|
|
185
|
-
? filteredItems
|
|
186
|
-
: applyFilterForItems(newInputValue, items);
|
|
187
|
-
dispatch((0, context_1.actionSetPartial)({
|
|
188
|
-
filteredItems: newFilteredItems,
|
|
189
|
-
inputValue: newInputValue,
|
|
190
|
-
}));
|
|
217
|
+
restoreInputValue();
|
|
191
218
|
}
|
|
192
219
|
if (isOpen) {
|
|
193
220
|
onRequestClose(event);
|
|
@@ -198,22 +225,7 @@ const Autocomplete = react_1.default.forwardRef((props, ref) => {
|
|
|
198
225
|
return () => {
|
|
199
226
|
window.document.removeEventListener(ClickOutside_1.mouseEventMap.onMouseDown, mouseDownEvent);
|
|
200
227
|
};
|
|
201
|
-
}, [
|
|
202
|
-
onRequestClose,
|
|
203
|
-
anchorElement,
|
|
204
|
-
filteredItems,
|
|
205
|
-
items,
|
|
206
|
-
isOpen,
|
|
207
|
-
clearOnBlur,
|
|
208
|
-
currentOpen,
|
|
209
|
-
dispatch,
|
|
210
|
-
value,
|
|
211
|
-
selectedItemToString,
|
|
212
|
-
currentValue,
|
|
213
|
-
applyFilterForItems,
|
|
214
|
-
inputValue,
|
|
215
|
-
multiple,
|
|
216
|
-
]);
|
|
228
|
+
}, [onRequestClose, anchorElement, isOpen, clearOnBlur, currentOpen, restoreInputValue]);
|
|
217
229
|
const onSelectMenuItem = react_1.default.useCallback(item => {
|
|
218
230
|
if (!multiple) {
|
|
219
231
|
dispatch({
|
package/Autocomplete/index.js
CHANGED
|
@@ -35,13 +35,15 @@ __exportStar(require("./AutocompleteContainer"), exports);
|
|
|
35
35
|
__exportStar(require("./AutocompleteItem"), exports);
|
|
36
36
|
const Autocomplete = react_1.default.forwardRef((props, ref) => {
|
|
37
37
|
const { children, items, filterItems, isOpen, multiple, selectedItemToString, value } = props, restProps = __rest(props, ["children", "items", "filterItems", "isOpen", "multiple", "selectedItemToString", "value"]);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
// The initial state is used only on mount, so do not recompute it (and run filterItems) on every render
|
|
39
|
+
const [initialState] = react_1.default.useState(() => (0, context_1.createInitialState)({
|
|
40
|
+
items,
|
|
41
|
+
selectedItemToString,
|
|
42
|
+
filterItems,
|
|
43
|
+
value,
|
|
44
|
+
isOpen,
|
|
45
|
+
}));
|
|
46
|
+
return (react_1.default.createElement(context_1.ContextProvider, { initialState: initialState },
|
|
45
47
|
react_1.default.createElement(AutocompleteContainer_1.default, Object.assign({}, restProps, { items: items, filterItems: filterItems, isOpen: isOpen, multiple: multiple, selectedItemToString: selectedItemToString, value: value, ref: ref }), children)));
|
|
46
48
|
});
|
|
47
49
|
Autocomplete.displayName = 'Autocomplete';
|
|
@@ -39,7 +39,7 @@ const Autocomplete2 = React.forwardRef((props, ref) => {
|
|
|
39
39
|
* Filter items list by input value
|
|
40
40
|
*/
|
|
41
41
|
const filterItems = React.useCallback((items, data) => {
|
|
42
|
-
const
|
|
42
|
+
const query = data.query.toLocaleLowerCase();
|
|
43
43
|
return items.filter(item => item.label.toLocaleLowerCase().indexOf(query) !== -1);
|
|
44
44
|
}, []);
|
|
45
45
|
/**
|
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Список изменений
|
|
2
|
+
|
|
3
|
+
Все заметные изменения пакета `@via-profit/ui-kit`. Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/), версии следуют [семантическому версионированию](https://semver.org/lang/ru/).
|
|
4
|
+
|
|
5
|
+
## Не выпущено
|
|
6
|
+
|
|
7
|
+
### Изменено
|
|
8
|
+
|
|
9
|
+
- **Calendar**: номера недель при начале недели с понедельника считаются по ISO 8601; при другом первом дне недели первой считается неделя, содержащая 1 января.
|
|
10
|
+
- **Calendar**: свойство `markToday` по умолчанию `true`, как указано в документации.
|
|
11
|
+
- **Selectbox**: поле отображается и при пустом списке `items` (раньше компонент не рендерился вовсе).
|
|
12
|
+
- **DatePicker**: добавлено свойство `todayButtonLabel`; `toodayButtonLabel` оставлено как устаревший псевдоним.
|
|
13
|
+
|
|
14
|
+
### Исправлено
|
|
15
|
+
|
|
16
|
+
- **Selectbox**: типы в опубликованном пакете ссылались на несуществующий модуль `Selectbox`.
|
|
17
|
+
- **Selectbox**: `label` не был связан с кнопкой, `onFocus` не вызывался, звёздочка отображалась при `requiredAsterisk={false}`.
|
|
18
|
+
- **Popper**: фактическое положение после автоматического переворота сбрасывалось к `anchorPos`; поповер, открытый сразу при монтировании, оставался невидимым; положение не пересчитывалось при прокрутке вложенных контейнеров и изменении размеров.
|
|
19
|
+
- **Menu**: в режиме `multiple` нельзя было снять выбор, если элементы `value` — копии объектов из `items`; `onRequestClose` вызывался дважды.
|
|
20
|
+
- **ClickOutside**: собственный `ref` дочернего элемента перезаписывался.
|
|
21
|
+
- **TextField**, **TextArea**: идентификаторы генерировались случайно и не совпадали при серверном рендеринге; `TextArea` показывала звёздочку при `requiredAsterisk={false}`.
|
|
22
|
+
- **Autocomplete**: `clearOnBlur` не срабатывал при уходе из поля по Tab.
|
|
23
|
+
- **Calendar**: первый клик в режиме диапазона не вызывал `onChange`; кнопка «Сегодня» создавала неверный диапазон; ограничения `minDate`/`maxDate` сравнивались с точностью до миллисекунд (при `minDate={new Date()}` сегодняшний день был недоступен); список месяцев строился для текущего года вместо отображаемого.
|
|
24
|
+
- **DatePicker**: поле не очищалось при `value={null}`; без значения в календаре подсвечивалась сегодняшняя дата; свойства `overrides`, `markToday` и `weekDayLabelFormat` игнорировались; шаблоны с `D` и `Y` не разбирались, двузначный год превращался в 19xx, некорректные даты (31.02) принимались.
|
|
25
|
+
- **MaskedField**: курсор смещался при редактировании в середине значения, а часть введённого текста терялась; маски, заканчивающиеся литералом, никогда не считались заполненными.
|
|
26
|
+
- **Swiper**: после клика без перетаскивания переставали работать автопрокрутка и `goToIndex`.
|
|
27
|
+
- **Modal**: окно пропадало при повторном открытии во время анимации закрытия; использовались значения `onRequestClose` и других свойств из первого рендера; прокрутка страницы оставалась заблокированной после размонтирования открытого окна.
|
|
28
|
+
- **TabManager**: порядок перехода по Tab не совпадал с браузерным, учитывались отключённые элементы, не учитывался фокус, поставленный мышью.
|
|
29
|
+
- **Color**: парсер не поддерживал проценты, отрицательный оттенок и `transparent`.
|
|
30
|
+
|
|
31
|
+
## 0.2.6 — 2026-09-09
|
|
32
|
+
|
|
33
|
+
Основные изменения с версии 0.1.2 (подробный список не вёлся):
|
|
34
|
+
|
|
35
|
+
- **Calendar**: режим выбора диапазона дат, обработка `null` в режиме диапазона.
|
|
36
|
+
- **Swiper**: новый компонент, в том числе с бесконечной прокруткой.
|
|
37
|
+
- **Menu**: виртуализированный список.
|
|
38
|
+
- **Popper**: переработка позиционирования, исправления производительности и расчёта положения.
|
|
@@ -99,25 +99,43 @@ const computeView = (inputParams) => {
|
|
|
99
99
|
const computeCalendarDate = (params) => {
|
|
100
100
|
const { inputValue, defaultValue } = params;
|
|
101
101
|
const today = new Date();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
// Проверяем inputValue
|
|
103
|
+
if (inputValue !== null && inputValue !== undefined) {
|
|
104
|
+
if (Array.isArray(inputValue) && inputValue.length > 0) {
|
|
105
|
+
// Для range режима - берем первый элемент, если он есть
|
|
106
|
+
if (inputValue[0] instanceof Date) {
|
|
107
|
+
return inputValue[0];
|
|
108
|
+
}
|
|
109
|
+
// Если первый элемент null, пробуем второй
|
|
110
|
+
if (inputValue[1] instanceof Date) {
|
|
111
|
+
return inputValue[1];
|
|
112
|
+
}
|
|
105
113
|
}
|
|
106
|
-
if (
|
|
107
|
-
|
|
114
|
+
else if (inputValue instanceof Date) {
|
|
115
|
+
// Для non-range режима
|
|
116
|
+
return inputValue;
|
|
108
117
|
}
|
|
109
118
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
// Проверяем defaultValue
|
|
120
|
+
if (defaultValue !== null && defaultValue !== undefined) {
|
|
121
|
+
if (Array.isArray(defaultValue) && defaultValue.length > 0) {
|
|
122
|
+
if (defaultValue[0] instanceof Date) {
|
|
123
|
+
return defaultValue[0];
|
|
124
|
+
}
|
|
125
|
+
if (defaultValue[1] instanceof Date) {
|
|
126
|
+
return defaultValue[1];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (defaultValue instanceof Date) {
|
|
130
|
+
return defaultValue;
|
|
131
|
+
}
|
|
115
132
|
}
|
|
133
|
+
// Возвращаем сегодняшнюю дату, если ничего не подошло
|
|
116
134
|
return today;
|
|
117
135
|
};
|
|
118
136
|
const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
119
137
|
var _a;
|
|
120
|
-
const { minDate
|
|
138
|
+
const { minDate: inputMinDate, maxDate: inputMaxDate, weekStartDay = 'monday', locale = 'ru-RU', displayLeadingZero = false, onChange, badges = [], markToday = true, weekDayLabelFormat = 'short', accentColor = 'primary', defaultValue, value: inputValue, initialView: inputInitialView, view: inputView, views: inputViews, resetButtonLabel, todayButtonLabel, heading, subheading, footer, overrides, range = false, } = props;
|
|
121
139
|
/**
|
|
122
140
|
* Validations
|
|
123
141
|
*/
|
|
@@ -142,12 +160,21 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
142
160
|
throw new Error('The `weeks` view allowed only in range mode');
|
|
143
161
|
}
|
|
144
162
|
}
|
|
145
|
-
if (
|
|
163
|
+
if (typeof inputValue !== 'undefined' && typeof onChange !== 'function') {
|
|
146
164
|
throw new Error('You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange`.');
|
|
147
165
|
}
|
|
148
166
|
if (inputView && inputInitialView) {
|
|
149
167
|
throw new Error('You can not use view and initialView at the same time.');
|
|
150
168
|
}
|
|
169
|
+
// Default limits are created once, otherwise every render gets new Date instances
|
|
170
|
+
// and invalidates all memoized values depending on them
|
|
171
|
+
const minDate = react_1.default.useMemo(() => inputMinDate !== null && inputMinDate !== void 0 ? inputMinDate : new Date(new Date().getFullYear() - 100, 0, 1, 0, 0, 0), [inputMinDate]);
|
|
172
|
+
const maxDate = react_1.default.useMemo(() => inputMaxDate !== null && inputMaxDate !== void 0 ? inputMaxDate : new Date(new Date().getFullYear() + 100, 0, 1, 0, 0, 0), [inputMaxDate]);
|
|
173
|
+
const emitChange = react_1.default.useCallback((newValue) => {
|
|
174
|
+
if (typeof onChange === 'function') {
|
|
175
|
+
onChange(newValue);
|
|
176
|
+
}
|
|
177
|
+
}, [onChange]);
|
|
151
178
|
/**
|
|
152
179
|
* Overrides
|
|
153
180
|
*/
|
|
@@ -203,15 +230,26 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
203
230
|
const setViews = react_1.default.useCallback((variants) => setComputedViews(variants), []);
|
|
204
231
|
const selectView = react_1.default.useCallback((view) => {
|
|
205
232
|
var _a;
|
|
206
|
-
|
|
207
|
-
|
|
233
|
+
const nextViews = views.includes(view) ? views : [...views, view];
|
|
234
|
+
if (nextViews !== views) {
|
|
235
|
+
setViews(nextViews);
|
|
208
236
|
}
|
|
209
237
|
setView(view);
|
|
210
|
-
const index =
|
|
238
|
+
const index = nextViews.findIndex(v => v === view);
|
|
211
239
|
if (index > -1) {
|
|
212
240
|
(_a = swiperRef.current) === null || _a === void 0 ? void 0 : _a.goToIndex(index);
|
|
213
241
|
}
|
|
214
242
|
}, [setViews, views]);
|
|
243
|
+
/**
|
|
244
|
+
* Controlled `view` property was changed
|
|
245
|
+
*/
|
|
246
|
+
const inputViewRef = react_1.default.useRef(inputView);
|
|
247
|
+
react_1.default.useEffect(() => {
|
|
248
|
+
if (inputView && inputView !== inputViewRef.current) {
|
|
249
|
+
inputViewRef.current = inputView;
|
|
250
|
+
selectView(inputView);
|
|
251
|
+
}
|
|
252
|
+
}, [inputView, selectView]);
|
|
215
253
|
const resetVariablesRef = react_1.default.useRef({
|
|
216
254
|
calendarDate,
|
|
217
255
|
value,
|
|
@@ -252,11 +290,13 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
252
290
|
if (JSON.stringify(inputValue) !== JSON.stringify(inputValueRef.current)) {
|
|
253
291
|
inputValueRef.current = inputValue;
|
|
254
292
|
setValue(inputValue);
|
|
255
|
-
if (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
293
|
+
if (inputValue !== null && inputValue !== undefined) {
|
|
294
|
+
if (Array.isArray(inputValue) && inputValue[0] instanceof Date) {
|
|
295
|
+
setCalendarDate(inputValue[0]);
|
|
296
|
+
}
|
|
297
|
+
else if (inputValue instanceof Date) {
|
|
298
|
+
setCalendarDate(inputValue);
|
|
299
|
+
}
|
|
260
300
|
}
|
|
261
301
|
}
|
|
262
302
|
}, [inputValue]);
|
|
@@ -272,10 +312,10 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
272
312
|
}
|
|
273
313
|
if (view === 'months') {
|
|
274
314
|
if (type === 'prev') {
|
|
275
|
-
newCalendarDate.setFullYear(newCalendarDate.getFullYear()
|
|
315
|
+
newCalendarDate.setFullYear(newCalendarDate.getFullYear() - 1);
|
|
276
316
|
}
|
|
277
317
|
else {
|
|
278
|
-
newCalendarDate.setFullYear(newCalendarDate.getFullYear()
|
|
318
|
+
newCalendarDate.setFullYear(newCalendarDate.getFullYear() + 1);
|
|
279
319
|
}
|
|
280
320
|
}
|
|
281
321
|
if (view === 'days' || view === 'weeks') {
|
|
@@ -296,45 +336,29 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
296
336
|
}
|
|
297
337
|
// Single-date mode
|
|
298
338
|
if (!range) {
|
|
299
|
-
|
|
339
|
+
emitChange(selectedDate);
|
|
300
340
|
setValue(selectedDate);
|
|
301
341
|
return;
|
|
302
342
|
}
|
|
303
343
|
// Range-date mode
|
|
304
344
|
const [from, to] = (_a = value) !== null && _a !== void 0 ? _a : [];
|
|
305
|
-
//
|
|
306
|
-
if (from && to) {
|
|
307
|
-
setValue([selectedDate, null]);
|
|
308
|
-
onChange([selectedDate, null]);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
// если выбрана только первая дата или обе даты равны - завершение периода
|
|
312
|
-
if ((from && to === null) || (from && to && from.getTime() === to.getTime())) {
|
|
345
|
+
// выбрана только первая дата - завершение периода
|
|
346
|
+
if (from && !to) {
|
|
313
347
|
/// check to swap
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
else {
|
|
319
|
-
onChange([from, selectedDate]);
|
|
320
|
-
setValue([from, selectedDate]);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
// обе даты выбраны или ничего не выбрано ([Date, Date] | [null, null] | null) - начало нового периода
|
|
325
|
-
if ((from && to) || (from === null && to === null) || value === null) {
|
|
326
|
-
setValue([selectedDate, null]);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
348
|
+
const newValue = selectedDate.getTime() < from.getTime() ? [selectedDate, from] : [from, selectedDate];
|
|
349
|
+
emitChange(newValue);
|
|
350
|
+
setValue(newValue);
|
|
351
|
+
return;
|
|
329
352
|
}
|
|
330
|
-
|
|
353
|
+
// обе даты выбраны или ничего не выбрано ([Date, Date] | [null, null] | null) - начало нового периода
|
|
354
|
+
setValue([selectedDate, null]);
|
|
355
|
+
emitChange([selectedDate, null]);
|
|
356
|
+
}, [emitChange, getNextAvailableView, range, value]);
|
|
331
357
|
/**
|
|
332
358
|
* Handle of click on year item
|
|
333
359
|
*/
|
|
334
360
|
const handleYearSelected = react_1.default.useCallback((selectedYear) => () => {
|
|
335
|
-
const newDate = new Date(
|
|
336
|
-
newDate.setFullYear(selectedYear, 0, 1);
|
|
337
|
-
newDate.setHours(0, 0, 0);
|
|
361
|
+
const newDate = new Date(selectedYear, 0, 1, 0, 0, 0, 0);
|
|
338
362
|
setCalendarDate(newDate);
|
|
339
363
|
const nextView = getNextAvailableView();
|
|
340
364
|
if (nextView) {
|
|
@@ -342,9 +366,7 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
342
366
|
}
|
|
343
367
|
// if the next `view` does not exist
|
|
344
368
|
if (!nextView && typeof onChange === 'function') {
|
|
345
|
-
const a = new Date();
|
|
346
|
-
a.setFullYear(newDate.getFullYear(), 11, 31);
|
|
347
|
-
a.setHours(23, 59, 59);
|
|
369
|
+
const a = new Date(newDate.getFullYear(), 11, 31, 23, 59, 59, 999);
|
|
348
370
|
const selectedValue = range ? [newDate, a] : newDate;
|
|
349
371
|
onChange(selectedValue);
|
|
350
372
|
if (!inputValue) {
|
|
@@ -356,9 +378,7 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
356
378
|
* Handle of click on month item
|
|
357
379
|
*/
|
|
358
380
|
const handleMonthSelected = react_1.default.useCallback((monthIndex) => () => {
|
|
359
|
-
const newDate = new Date();
|
|
360
|
-
newDate.setFullYear(calendarDate.getFullYear(), monthIndex, 1);
|
|
361
|
-
newDate.setHours(0, 0, 0);
|
|
381
|
+
const newDate = new Date(calendarDate.getFullYear(), monthIndex, 1, 0, 0, 0, 0);
|
|
362
382
|
setCalendarDate(newDate);
|
|
363
383
|
const nextView = getNextAvailableView();
|
|
364
384
|
if (nextView) {
|
|
@@ -366,9 +386,8 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
366
386
|
}
|
|
367
387
|
// if the next `view` does not exist
|
|
368
388
|
if (!nextView && typeof onChange === 'function') {
|
|
369
|
-
|
|
370
|
-
a
|
|
371
|
-
a.setHours(0, 0, 0);
|
|
389
|
+
// The end of the last day of the month, the same way as for the year range
|
|
390
|
+
const a = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
372
391
|
const selectedValue = range ? [newDate, a] : newDate;
|
|
373
392
|
onChange(selectedValue);
|
|
374
393
|
if (!inputValue) {
|
|
@@ -391,12 +410,16 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
391
410
|
* Handle click on «Today» button
|
|
392
411
|
*/
|
|
393
412
|
const handleToday = react_1.default.useCallback(() => {
|
|
394
|
-
const
|
|
395
|
-
today.
|
|
413
|
+
const now = new Date();
|
|
414
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
|
|
415
|
+
const newValue = (range ? [today, today] : today);
|
|
396
416
|
setCalendarDate(today);
|
|
397
|
-
setValue(
|
|
398
|
-
|
|
399
|
-
|
|
417
|
+
setValue(newValue);
|
|
418
|
+
emitChange(newValue);
|
|
419
|
+
if (view !== 'days' && views.includes('days')) {
|
|
420
|
+
selectView('days');
|
|
421
|
+
}
|
|
422
|
+
}, [emitChange, range, selectView, view, views]);
|
|
400
423
|
const handleClickWeek = react_1.default.useCallback((week) => () => {
|
|
401
424
|
const days = week.days;
|
|
402
425
|
const dates = [days[0].date, days[days.length - 1].date];
|
|
@@ -425,7 +448,7 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
425
448
|
}, [views, range, selectView, onChange]);
|
|
426
449
|
const weeks = react_1.default.useMemo(() => getWeeks(calendarDate), [calendarDate, getWeeks]);
|
|
427
450
|
const yearsRange = react_1.default.useMemo(() => getYearsRange(minDate, maxDate), [minDate, maxDate, getYearsRange]);
|
|
428
|
-
const monthsRange = react_1.default.useMemo(() => getMonthsRange(minDate, maxDate), [minDate, maxDate, getMonthsRange]);
|
|
451
|
+
const monthsRange = react_1.default.useMemo(() => getMonthsRange(minDate, maxDate, calendarDate.getFullYear()), [minDate, maxDate, getMonthsRange, calendarDate]);
|
|
429
452
|
const renderViewDays = react_1.default.useCallback(() => (react_1.default.createElement(overridesMap.DateContainer, null, getWeeks(calendarDate).map(week => (react_1.default.createElement(overridesMap.WeekRow, { key: week.weekNumber }, week.days.map(day => {
|
|
430
453
|
if (day.date.getMonth() === calendarDate.getMonth()) {
|
|
431
454
|
const badge = badges.find(b => isSameDay(b.date, day.date));
|
|
@@ -461,7 +484,7 @@ const CalendarComponent = react_1.default.forwardRef((props, ref) => {
|
|
|
461
484
|
]);
|
|
462
485
|
const renderViewMonths = react_1.default.useCallback(() => (react_1.default.createElement(overridesMap.MonthsSelector, null, monthsRange.map(monthIndex => {
|
|
463
486
|
const isSelected = calendarDate.getMonth() === monthIndex;
|
|
464
|
-
return (react_1.default.createElement(overridesMap.MonthCell, { key: monthIndex
|
|
487
|
+
return (react_1.default.createElement(overridesMap.MonthCell, { key: monthIndex, accentColor: accentColor, isSelected: isSelected, onClick: handleMonthSelected(monthIndex) }, getMonthLabel(new Date(calendarDate.getFullYear(), monthIndex, 1, 0, 0, 0, 0))));
|
|
465
488
|
}))), [accentColor, calendarDate, getMonthLabel, handleMonthSelected, monthsRange, overridesMap]);
|
|
466
489
|
const renderViewYears = react_1.default.useCallback(() => (react_1.default.createElement(overridesMap.YearsSelector, { accentColor: accentColor, date: calendarDate, onChange: y => handleYearSelected(y)(), years: yearsRange, locale: locale, minDate: minDate, maxDate: maxDate })), [
|
|
467
490
|
accentColor,
|
|
@@ -61,9 +61,10 @@ export type UseCalendarPayload = {
|
|
|
61
61
|
*/
|
|
62
62
|
getYearsRange: (minDate: Date, maxDate: Date) => number[];
|
|
63
63
|
/**
|
|
64
|
-
* Returns range of monthes (array of month indexes)
|
|
64
|
+
* Returns range of monthes (array of month indexes) of the passed year (current year by default)
|
|
65
|
+
* which are intersected with the range between passed dates
|
|
65
66
|
*/
|
|
66
|
-
getMonthsRange: (minDate: Date, maxDate: Date) => number[];
|
|
67
|
+
getMonthsRange: (minDate: Date, maxDate: Date, year?: number) => number[];
|
|
67
68
|
};
|
|
68
69
|
export declare const useCalendar: (props: UseCalendarProps) => UseCalendarPayload;
|
|
69
70
|
export default useCalendar;
|
package/Calendar/use-calendar.js
CHANGED
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.useCalendar = void 0;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
8
|
const WHITESPACE = '\u{0020}';
|
|
9
|
-
const
|
|
9
|
+
const DAY_IN_MS = 86400000; // 24 * 60 * 60 * 1000
|
|
10
10
|
// Константа для маппинга дней недели
|
|
11
11
|
const WEEK_DAYS_MAP = {
|
|
12
12
|
sunday: 0,
|
|
@@ -19,9 +19,10 @@ const WEEK_DAYS_MAP = {
|
|
|
19
19
|
};
|
|
20
20
|
const useCalendar = (props) => {
|
|
21
21
|
const { minDate, maxDate, weekStartDay, displayLeadingZero, locale } = props;
|
|
22
|
-
// Мемоизируем timestamp граничных дат для оптимизации проверок isDisabled
|
|
23
|
-
|
|
24
|
-
const
|
|
22
|
+
// Мемоизируем timestamp граничных дат для оптимизации проверок isDisabled.
|
|
23
|
+
// Границы расширяются до целых дней, иначе при minDate={new Date()} сегодняшний день недоступен
|
|
24
|
+
const minTime = react_1.default.useMemo(() => new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate()).getTime(), [minDate]);
|
|
25
|
+
const maxTime = react_1.default.useMemo(() => new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 23, 59, 59, 999).getTime(), [maxDate]);
|
|
25
26
|
// Мемоизируем номер дня начала недели
|
|
26
27
|
const weekStartDayNumber = react_1.default.useMemo(() => WEEK_DAYS_MAP[weekStartDay], [weekStartDay]);
|
|
27
28
|
// Оптимизированная функция isSameDay
|
|
@@ -39,22 +40,26 @@ const useCalendar = (props) => {
|
|
|
39
40
|
return currentTime > maxTime || currentTime < minTime;
|
|
40
41
|
}, [maxTime, minTime]);
|
|
41
42
|
const calculateWeekNumber = react_1.default.useCallback((date) => {
|
|
42
|
-
// Используем UTC методы для избежания проблем с часовыми поясами
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const weekStart =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
// Используем UTC методы для избежания проблем с часовыми поясами (DST)
|
|
44
|
+
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
45
|
+
// Неделя с понедельника - ISO 8601: первая неделя года содержит первый четверг
|
|
46
|
+
if (weekStartDayNumber === WEEK_DAYS_MAP.monday) {
|
|
47
|
+
const isoDay = d.getUTCDay() || 7;
|
|
48
|
+
const thursday = new Date(d.getTime() + (4 - isoDay) * DAY_IN_MS);
|
|
49
|
+
const yearStart = Date.UTC(thursday.getUTCFullYear(), 0, 1);
|
|
50
|
+
return Math.ceil(((thursday.getTime() - yearStart) / DAY_IN_MS + 1) / 7);
|
|
51
|
+
}
|
|
52
|
+
// Остальные системы: первая неделя года содержит 1 января
|
|
53
|
+
const diff = (d.getUTCDay() - weekStartDayNumber + 7) % 7;
|
|
54
|
+
const weekStart = d.getTime() - diff * DAY_IN_MS;
|
|
55
|
+
const weekEnd = new Date(weekStart + 6 * DAY_IN_MS);
|
|
56
|
+
// Неделя содержит 1 января следующего года
|
|
57
|
+
if (weekEnd.getUTCFullYear() !== new Date(weekStart).getUTCFullYear()) {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
const yearStart = new Date(Date.UTC(new Date(weekStart).getUTCFullYear(), 0, 1));
|
|
61
|
+
const firstWeekStart = yearStart.getTime() - ((yearStart.getUTCDay() - weekStartDayNumber + 7) % 7) * DAY_IN_MS;
|
|
62
|
+
return Math.round((weekStart - firstWeekStart) / (7 * DAY_IN_MS)) + 1;
|
|
58
63
|
}, [weekStartDayNumber]);
|
|
59
64
|
const getDayLabel = react_1.default.useCallback((day) => {
|
|
60
65
|
const dateNum = day.getDate();
|
|
@@ -79,12 +84,14 @@ const useCalendar = (props) => {
|
|
|
79
84
|
}
|
|
80
85
|
return years;
|
|
81
86
|
}, []);
|
|
82
|
-
const getMonthsRange = react_1.default.useCallback((minDate, maxDate) => {
|
|
87
|
+
const getMonthsRange = react_1.default.useCallback((minDate, maxDate, year) => {
|
|
83
88
|
const m = [];
|
|
84
|
-
const y = new Date().getFullYear();
|
|
89
|
+
const y = typeof year === 'number' ? year : new Date().getFullYear();
|
|
85
90
|
for (let index = 0; index < 12; index++) {
|
|
86
|
-
const
|
|
87
|
-
|
|
91
|
+
const monthStart = new Date(y, index, 1, 0, 0, 0, 0);
|
|
92
|
+
const monthEnd = new Date(y, index + 1, 0, 23, 59, 59, 999);
|
|
93
|
+
// Месяц подходит, если хотя бы частично попадает в диапазон
|
|
94
|
+
if (monthStart.getTime() <= maxDate.getTime() && monthEnd.getTime() >= minDate.getTime()) {
|
|
88
95
|
m.push(index);
|
|
89
96
|
}
|
|
90
97
|
}
|
package/ClickOutside/index.js
CHANGED
|
@@ -37,6 +37,17 @@ const ClickOutside = props => {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
}, [onOutsideClick, mouseEvent]);
|
|
40
|
-
|
|
40
|
+
// Keep the child's own ref working alongside ours
|
|
41
|
+
const childRef = children.ref;
|
|
42
|
+
const setRef = react_1.default.useCallback((el) => {
|
|
43
|
+
ref.current = el;
|
|
44
|
+
if (typeof childRef === 'function') {
|
|
45
|
+
childRef(el);
|
|
46
|
+
}
|
|
47
|
+
else if (childRef && typeof childRef === 'object') {
|
|
48
|
+
childRef.current = el;
|
|
49
|
+
}
|
|
50
|
+
}, [childRef]);
|
|
51
|
+
return react_1.default.createElement(react_1.default.Fragment, null, react_1.default.cloneElement(children, { ref: setRef }));
|
|
41
52
|
};
|
|
42
53
|
exports.default = ClickOutside;
|
package/Color/ColorParser.d.ts
CHANGED
|
@@ -12,6 +12,15 @@ export declare class ColorParser {
|
|
|
12
12
|
*/
|
|
13
13
|
static parse(value: string): ParsedColor;
|
|
14
14
|
private static parseHex;
|
|
15
|
+
/**
|
|
16
|
+
* Returns function arguments: `rgb(10, 20 30 / 50%)` -> ['10', '20', '30', '50%']
|
|
17
|
+
*/
|
|
18
|
+
private static getArgs;
|
|
19
|
+
private static parseNumber;
|
|
20
|
+
/**
|
|
21
|
+
* Alpha may be a number (0..1) or a percentage (0%..100%)
|
|
22
|
+
*/
|
|
23
|
+
private static parseAlpha;
|
|
15
24
|
private static parseRgb;
|
|
16
25
|
private static parseHsl;
|
|
17
26
|
private static hslToRgb;
|