@astral/ui 4.52.0 → 4.52.1
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/DatePicker/hooks/useDatePickerOptions/useDatePickerOptions.d.ts +2 -0
- package/components/DatePicker/hooks/useDatePickerOptions/useDatePickerOptions.js +2 -1
- package/components/DatePicker/hooks/useMaskedValue/useMaskedValue.d.ts +4 -0
- package/components/DatePicker/hooks/useMaskedValue/useMaskedValue.js +37 -8
- package/components/DatePicker/useLogic/useLogic.d.ts +1 -0
- package/components/DatePicker/useLogic/useLogic.js +19 -13
- package/components/DateRangePicker/useLogic/useLogic.d.ts +2 -0
- package/components/DateRangePicker/useLogic/useLogic.js +27 -3
- package/components/DateRangePicker/useLogic/utils/index.d.ts +0 -1
- package/components/DateRangePicker/useLogic/utils/index.js +0 -1
- package/components/utils/date/format/index.d.ts +1 -0
- package/components/utils/date/format/index.js +1 -0
- package/{node/components/DateRangePicker/useLogic/utils → components/utils/date/format}/isMaskedDateSyntacticallyComplete/isMaskedDateSyntacticallyComplete.d.ts +1 -1
- package/components/utils/date/format/parseDate/parseDate.d.ts +1 -0
- package/components/utils/date/format/parseDate/parseDate.js +42 -13
- package/node/components/DatePicker/hooks/useDatePickerOptions/useDatePickerOptions.d.ts +2 -0
- package/node/components/DatePicker/hooks/useDatePickerOptions/useDatePickerOptions.js +2 -1
- package/node/components/DatePicker/hooks/useMaskedValue/useMaskedValue.d.ts +4 -0
- package/node/components/DatePicker/hooks/useMaskedValue/useMaskedValue.js +35 -6
- package/node/components/DatePicker/useLogic/useLogic.d.ts +1 -0
- package/node/components/DatePicker/useLogic/useLogic.js +19 -13
- package/node/components/DateRangePicker/useLogic/useLogic.d.ts +2 -0
- package/node/components/DateRangePicker/useLogic/useLogic.js +28 -4
- package/node/components/DateRangePicker/useLogic/utils/index.d.ts +0 -1
- package/node/components/DateRangePicker/useLogic/utils/index.js +0 -1
- package/node/components/utils/date/format/index.d.ts +1 -0
- package/node/components/utils/date/format/index.js +1 -0
- package/{components/DateRangePicker/useLogic/utils → node/components/utils/date/format}/isMaskedDateSyntacticallyComplete/isMaskedDateSyntacticallyComplete.d.ts +1 -1
- package/node/components/utils/date/format/parseDate/parseDate.d.ts +1 -0
- package/node/components/utils/date/format/parseDate/parseDate.js +45 -13
- package/package.json +1 -1
- /package/components/{DateRangePicker/useLogic/utils → utils/date/format}/isMaskedDateSyntacticallyComplete/index.d.ts +0 -0
- /package/components/{DateRangePicker/useLogic/utils → utils/date/format}/isMaskedDateSyntacticallyComplete/index.js +0 -0
- /package/components/{DateRangePicker/useLogic/utils → utils/date/format}/isMaskedDateSyntacticallyComplete/isMaskedDateSyntacticallyComplete.js +0 -0
- /package/node/components/{DateRangePicker/useLogic/utils → utils/date/format}/isMaskedDateSyntacticallyComplete/index.d.ts +0 -0
- /package/node/components/{DateRangePicker/useLogic/utils → utils/date/format}/isMaskedDateSyntacticallyComplete/index.js +0 -0
- /package/node/components/{DateRangePicker/useLogic/utils → utils/date/format}/isMaskedDateSyntacticallyComplete/isMaskedDateSyntacticallyComplete.js +0 -0
|
@@ -30,6 +30,8 @@ type UseMaskedValueAndSelectedBaseDateReturn = {
|
|
|
30
30
|
*/
|
|
31
31
|
value: string;
|
|
32
32
|
};
|
|
33
|
+
/** При blur неполной даты - в onChange уходит Invalid Date (тот же стейт, что у маски). */
|
|
34
|
+
onMaskedValueBlur: () => void;
|
|
33
35
|
};
|
|
34
36
|
/**
|
|
35
37
|
* хук объединяющий повторяющуюся логику в работе DatePicker и RangeDatePicker:
|
|
@@ -12,7 +12,7 @@ import { useSelectedBaseDate } from '../useSelectedBaseDate';
|
|
|
12
12
|
*/
|
|
13
13
|
export const useDatePickerOptions = ({ onChange, mask, currentValue, minDate, maxDate, monthOffset, onDatePick, }) => {
|
|
14
14
|
const baseDate = useBaseDateInRange({ minDate, maxDate, monthOffset });
|
|
15
|
-
const { maskedValue, onMaskedValueChange, onMaskedDateChange } = useMaskedValue({
|
|
15
|
+
const { maskedValue, onMaskedValueChange, onMaskedValueBlur, onMaskedDateChange, } = useMaskedValue({
|
|
16
16
|
currentValue,
|
|
17
17
|
mask,
|
|
18
18
|
onChangeValue: onChange,
|
|
@@ -39,5 +39,6 @@ export const useDatePickerOptions = ({ onChange, mask, currentValue, minDate, ma
|
|
|
39
39
|
onChange: handleDatePick,
|
|
40
40
|
date: selectedBaseDate || baseDate,
|
|
41
41
|
},
|
|
42
|
+
onMaskedValueBlur,
|
|
42
43
|
};
|
|
43
44
|
};
|
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
import { isDate } from '@astral/utils/date/isDate';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import { formatDate, parseDate } from '../../../utils/date';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { formatDate, isMaskedDateSyntacticallyComplete, parseDate, } from '../../../utils/date';
|
|
4
4
|
/**
|
|
5
5
|
* Хук для управления значением для MaskField
|
|
6
6
|
*/
|
|
7
7
|
export const useMaskedValue = ({ currentValue, mask, onChangeValue, }) => {
|
|
8
8
|
const [maskedValue, setMaskedValue] = useState(() => currentValue ? formatDate(currentValue, mask) : '');
|
|
9
|
+
const maskedValueRef = useRef(maskedValue);
|
|
10
|
+
/**
|
|
11
|
+
* Ввод в маску: синхронизируем локальное значение и ref (ref нужен на blur и после выбора даты из календаря,
|
|
12
|
+
* чтобы не читать устаревший state).
|
|
13
|
+
* `onChangeValue` вызываем только при пустой строке (`null`) или когда маска синтаксически полная —
|
|
14
|
+
* иначе partial не уходит наружу до завершения ввода или до blur (см. `handleMaskedValueBlur`).
|
|
15
|
+
*/
|
|
9
16
|
const handleMaskedValueChange = (value) => {
|
|
10
17
|
setMaskedValue(value);
|
|
11
|
-
|
|
18
|
+
maskedValueRef.current = value;
|
|
12
19
|
if (!value) {
|
|
13
20
|
onChangeValue?.(null, value);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!isMaskedDateSyntacticallyComplete(value, mask)) {
|
|
24
|
+
return;
|
|
14
25
|
}
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
onChangeValue?.(parseDate(value, mask), value);
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Blur поля: если осталась непустая неполная маска — один раз отдаём `Invalid Date` в `onChangeValue`,
|
|
30
|
+
* чтобы формы могли показать ошибку. Пустая строка и полная маска не дублируют уже отправленный `onChange`.
|
|
31
|
+
*/
|
|
32
|
+
const handleMaskedValueBlur = () => {
|
|
33
|
+
const currentMaskedValue = maskedValueRef.current;
|
|
34
|
+
if (!currentMaskedValue) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (isMaskedDateSyntacticallyComplete(currentMaskedValue, mask)) {
|
|
38
|
+
return;
|
|
17
39
|
}
|
|
40
|
+
onChangeValue?.(new Date(Number.NaN), currentMaskedValue);
|
|
18
41
|
};
|
|
19
42
|
const handleChangeMaskedDate = (date) => {
|
|
20
43
|
const formatted = formatDate(date, mask);
|
|
21
44
|
setMaskedValue(formatted);
|
|
45
|
+
maskedValueRef.current = formatted;
|
|
22
46
|
onChangeValue?.(date, formatted);
|
|
23
47
|
};
|
|
24
48
|
// здесь происходит реакция на изменение value из вне (управляемый компонент)
|
|
@@ -26,6 +50,7 @@ export const useMaskedValue = ({ currentValue, mask, onChangeValue, }) => {
|
|
|
26
50
|
// если новое значение пустое, то сбрасываем значение MaskField
|
|
27
51
|
if (!currentValue) {
|
|
28
52
|
setMaskedValue('');
|
|
53
|
+
maskedValueRef.current = '';
|
|
29
54
|
return;
|
|
30
55
|
}
|
|
31
56
|
// здесь обрабатывается сценарий, когда в инпут вводится невалидная дата и при этом currentValue становится Invalid Date
|
|
@@ -33,16 +58,20 @@ export const useMaskedValue = ({ currentValue, mask, onChangeValue, }) => {
|
|
|
33
58
|
if (!isDate(currentValue) && maskedValue) {
|
|
34
59
|
return;
|
|
35
60
|
}
|
|
36
|
-
//
|
|
37
|
-
const isEqualValueAndMaskedDate =
|
|
61
|
+
// сравнение только при полной маске, иначе getTime() от parseDate может быть NaN
|
|
62
|
+
const isEqualValueAndMaskedDate = isMaskedDateSyntacticallyComplete(maskedValue, mask) &&
|
|
63
|
+
currentValue.getTime() === parseDate(maskedValue, mask).getTime();
|
|
38
64
|
// если даты не равны, то значит изменился currentValue из вне и надо синхронизировать maskedValue
|
|
39
65
|
if (!isEqualValueAndMaskedDate) {
|
|
40
|
-
|
|
66
|
+
const formatted = formatDate(currentValue, mask);
|
|
67
|
+
setMaskedValue(formatted);
|
|
68
|
+
maskedValueRef.current = formatted;
|
|
41
69
|
}
|
|
42
70
|
}, [currentValue]);
|
|
43
71
|
return {
|
|
44
72
|
maskedValue,
|
|
45
73
|
onMaskedValueChange: handleMaskedValueChange,
|
|
74
|
+
onMaskedValueBlur: handleMaskedValueBlur,
|
|
46
75
|
onMaskedDateChange: handleChangeMaskedDate,
|
|
47
76
|
};
|
|
48
77
|
};
|
|
@@ -13,6 +13,7 @@ export declare const useLogic: ({ label, value, maxDate, minDate, mask, onOpen,
|
|
|
13
13
|
};
|
|
14
14
|
DatePickerInputProps: {
|
|
15
15
|
value: string;
|
|
16
|
+
onBlur: () => void;
|
|
16
17
|
};
|
|
17
18
|
onAccept: ((value: string, maskRef: import("imask").default.InputMask<import("imask").default.AnyMaskedOptions>, e?: InputEvent | undefined, onChange?: ((changeValue: string) => void) | undefined) => void) | undefined;
|
|
18
19
|
pickerProps: import("../types").PickerProps;
|
|
@@ -4,7 +4,6 @@ import { usePopover } from '../../usePopover';
|
|
|
4
4
|
import { useViewportType } from '../../useViewportType';
|
|
5
5
|
import { DEFAULT_DATE_MASK } from '../constants';
|
|
6
6
|
import { useDatePickerOptions } from '../hooks/useDatePickerOptions';
|
|
7
|
-
import { useMaskedValue } from '../hooks/useMaskedValue';
|
|
8
7
|
import { DEFAULT_MAX_DATE, DEFAULT_MIN_DATE } from '../MinMaxDateContext';
|
|
9
8
|
export const useLogic = ({ label, value, maxDate = DEFAULT_MAX_DATE, minDate = DEFAULT_MIN_DATE, mask = DEFAULT_DATE_MASK, onOpen, onClose, onBlur, onChange, forwardedRef, }) => {
|
|
10
9
|
const ref = useForwardedRef(forwardedRef);
|
|
@@ -12,21 +11,11 @@ export const useLogic = ({ label, value, maxDate = DEFAULT_MAX_DATE, minDate = D
|
|
|
12
11
|
const { isOpen, actions } = usePopover();
|
|
13
12
|
const { open, close } = actions;
|
|
14
13
|
const { isMobile } = useViewportType();
|
|
15
|
-
const { maskedValue } = useMaskedValue({
|
|
16
|
-
currentValue: value,
|
|
17
|
-
mask,
|
|
18
|
-
onChangeValue: onChange,
|
|
19
|
-
});
|
|
20
14
|
const isTitleShow = isMobile && typeof label === 'string';
|
|
21
15
|
const handleOpen = (event) => {
|
|
22
16
|
onOpen?.();
|
|
23
17
|
open(event);
|
|
24
18
|
};
|
|
25
|
-
const handleClose = () => {
|
|
26
|
-
onBlur?.();
|
|
27
|
-
onClose?.();
|
|
28
|
-
close();
|
|
29
|
-
};
|
|
30
19
|
const handleDayPick = (date) => {
|
|
31
20
|
if (isMobile) {
|
|
32
21
|
setSelectedDate(date);
|
|
@@ -39,7 +28,7 @@ export const useLogic = ({ label, value, maxDate = DEFAULT_MAX_DATE, minDate = D
|
|
|
39
28
|
onChange?.(date);
|
|
40
29
|
}
|
|
41
30
|
};
|
|
42
|
-
const { onAccept, inputProps: calculatedInputProps, pickerProps, } = useDatePickerOptions({
|
|
31
|
+
const { onAccept, inputProps: calculatedInputProps, pickerProps, onMaskedValueBlur, } = useDatePickerOptions({
|
|
43
32
|
currentValue: value,
|
|
44
33
|
maxDate,
|
|
45
34
|
minDate,
|
|
@@ -47,6 +36,22 @@ export const useLogic = ({ label, value, maxDate = DEFAULT_MAX_DATE, minDate = D
|
|
|
47
36
|
onDatePick: handleDayPick,
|
|
48
37
|
onChange: handleChange,
|
|
49
38
|
});
|
|
39
|
+
/** Blur: неполный ввод -Invalid Date в onChange (при isMobile onChange не шлём), потом внешний onBlur. */
|
|
40
|
+
const flushMaskedValueAndCallOnBlur = () => {
|
|
41
|
+
onMaskedValueBlur();
|
|
42
|
+
onBlur?.();
|
|
43
|
+
};
|
|
44
|
+
function handleClose() {
|
|
45
|
+
flushMaskedValueAndCallOnBlur();
|
|
46
|
+
onClose?.();
|
|
47
|
+
close();
|
|
48
|
+
}
|
|
49
|
+
const handleInputBlur = () => {
|
|
50
|
+
if (isOpen) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
flushMaskedValueAndCallOnBlur();
|
|
54
|
+
};
|
|
50
55
|
const handleConfirm = () => {
|
|
51
56
|
onChange?.(selectedDate);
|
|
52
57
|
handleClose();
|
|
@@ -58,7 +63,8 @@ export const useLogic = ({ label, value, maxDate = DEFAULT_MAX_DATE, minDate = D
|
|
|
58
63
|
title: isTitleShow ? label : undefined,
|
|
59
64
|
};
|
|
60
65
|
const DatePickerInputProps = {
|
|
61
|
-
value:
|
|
66
|
+
value: calculatedInputProps.value,
|
|
67
|
+
onBlur: handleInputBlur,
|
|
62
68
|
};
|
|
63
69
|
const confirmButtonProps = {
|
|
64
70
|
onClick: handleConfirm,
|
|
@@ -7,12 +7,14 @@ export declare const useLogic: ({ value, minDate, maxDate, mask, onChange, onOpe
|
|
|
7
7
|
ref: import("react").RefObject<HTMLInputElement | null>;
|
|
8
8
|
value: string;
|
|
9
9
|
onAccept: (_: string, maskRef: IMask.InputMask<IMask.AnyMaskedOptions>) => void;
|
|
10
|
+
onBlur: () => void;
|
|
10
11
|
onClick: (e: SyntheticEvent) => void;
|
|
11
12
|
};
|
|
12
13
|
endDatePickerInputProps: {
|
|
13
14
|
ref: import("react").RefObject<HTMLInputElement | null>;
|
|
14
15
|
value: string;
|
|
15
16
|
onAccept: (_: string, maskRef: IMask.InputMask<IMask.AnyMaskedOptions>) => void;
|
|
17
|
+
onBlur: () => void;
|
|
16
18
|
onClick: (e: SyntheticEvent) => void;
|
|
17
19
|
};
|
|
18
20
|
popoverHoveredContextProviderProps: {
|
|
@@ -2,8 +2,9 @@ import { useEffect, useRef, useState, } from 'react';
|
|
|
2
2
|
import { DEFAULT_DATE_MASK, DEFAULT_MAX_DATE, DEFAULT_MIN_DATE, useMaskedValue, useSelectedBaseDate, } from '../../DatePicker';
|
|
3
3
|
import { useForwardedRef } from '../../useForwardedRef';
|
|
4
4
|
import { usePopover } from '../../usePopover';
|
|
5
|
+
import { isMaskedDateSyntacticallyComplete } from '../../utils/date';
|
|
5
6
|
import { useBaseRangeDates } from './hooks';
|
|
6
|
-
import { getBoundaryDate
|
|
7
|
+
import { getBoundaryDate } from './utils';
|
|
7
8
|
export const useLogic = ({ value, minDate = DEFAULT_MIN_DATE, maxDate = DEFAULT_MAX_DATE, mask = DEFAULT_DATE_MASK, onChange, onOpen, onClose, onBlur, }, forwardedRef) => {
|
|
8
9
|
const ref = useForwardedRef(forwardedRef);
|
|
9
10
|
const startInputRef = useRef(null);
|
|
@@ -65,12 +66,12 @@ export const useLogic = ({ value, minDate = DEFAULT_MIN_DATE, maxDate = DEFAULT_
|
|
|
65
66
|
}
|
|
66
67
|
onChange?.({ ...value, end: endDateValue });
|
|
67
68
|
};
|
|
68
|
-
const { maskedValue: startMaskedValue, onMaskedValueChange: onMaskedStartValueChange, onMaskedDateChange: onMaskedStartDateChange, } = useMaskedValue({
|
|
69
|
+
const { maskedValue: startMaskedValue, onMaskedValueChange: onMaskedStartValueChange, onMaskedValueBlur: onMaskedStartValueBlur, onMaskedDateChange: onMaskedStartDateChange, } = useMaskedValue({
|
|
69
70
|
currentValue: value?.start,
|
|
70
71
|
mask,
|
|
71
72
|
onChangeValue: handleChangeStartDate,
|
|
72
73
|
});
|
|
73
|
-
const { maskedValue: endMaskedValue, onMaskedValueChange: onMaskedEndValueChange, onMaskedDateChange: onMaskedEndDateChange, } = useMaskedValue({
|
|
74
|
+
const { maskedValue: endMaskedValue, onMaskedValueChange: onMaskedEndValueChange, onMaskedValueBlur: onMaskedEndValueBlur, onMaskedDateChange: onMaskedEndDateChange, } = useMaskedValue({
|
|
74
75
|
currentValue: value?.end,
|
|
75
76
|
mask,
|
|
76
77
|
onChangeValue: handleChangeEndDate,
|
|
@@ -79,11 +80,32 @@ export const useLogic = ({ value, minDate = DEFAULT_MIN_DATE, maxDate = DEFAULT_
|
|
|
79
80
|
onOpen?.();
|
|
80
81
|
open(event);
|
|
81
82
|
};
|
|
83
|
+
const flushActiveMaskedValueOnClose = () => {
|
|
84
|
+
if (activeInput === 'startDate') {
|
|
85
|
+
onMaskedStartValueBlur();
|
|
86
|
+
}
|
|
87
|
+
if (activeInput === 'endDate') {
|
|
88
|
+
onMaskedEndValueBlur();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
82
91
|
const handleClose = () => {
|
|
92
|
+
flushActiveMaskedValueOnClose();
|
|
83
93
|
onBlur?.();
|
|
84
94
|
onClose?.();
|
|
85
95
|
close();
|
|
86
96
|
};
|
|
97
|
+
const handleStartInputBlur = () => {
|
|
98
|
+
if (isOpen) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
onMaskedStartValueBlur();
|
|
102
|
+
};
|
|
103
|
+
const handleEndInputBlur = () => {
|
|
104
|
+
if (isOpen) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
onMaskedEndValueBlur();
|
|
108
|
+
};
|
|
87
109
|
const handleClickStartInput = (e) => {
|
|
88
110
|
setActiveInput('startDate');
|
|
89
111
|
startInputRef.current?.focus();
|
|
@@ -138,12 +160,14 @@ export const useLogic = ({ value, minDate = DEFAULT_MIN_DATE, maxDate = DEFAULT_
|
|
|
138
160
|
ref: startInputRef,
|
|
139
161
|
value: startMaskedValue,
|
|
140
162
|
onAccept: handleAcceptStart,
|
|
163
|
+
onBlur: handleStartInputBlur,
|
|
141
164
|
onClick: handleClickStartInput,
|
|
142
165
|
},
|
|
143
166
|
endDatePickerInputProps: {
|
|
144
167
|
ref: endInputRef,
|
|
145
168
|
value: endMaskedValue,
|
|
146
169
|
onAccept: handleAcceptEnd,
|
|
170
|
+
onBlur: handleEndInputBlur,
|
|
147
171
|
onClick: handleClickEndInput,
|
|
148
172
|
},
|
|
149
173
|
popoverHoveredContextProviderProps: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DateMask } from '../maskDate';
|
|
2
2
|
/**
|
|
3
3
|
* утилита конвертации строковой даты созданной по маске обратно в Date
|
|
4
|
+
* Строгий разбор UTC через `dayjs` + `customParseFormat`; при невалидной строке — Invalid Date
|
|
4
5
|
*/
|
|
5
6
|
export declare const parseDate: (date: string, mask: DateMask, separator?: string) => Date;
|
|
@@ -1,21 +1,50 @@
|
|
|
1
|
-
import
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
3
|
+
import utc from 'dayjs/plugin/utc';
|
|
2
4
|
import { DateMaskElements } from '../maskDate';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
dayjs.extend(customParseFormat);
|
|
6
|
+
dayjs.extend(utc);
|
|
7
|
+
/**
|
|
8
|
+
* Токен маски UI → литерал формата `dayjs` (strict UTC).
|
|
9
|
+
* В маске час — `hh`, в дате используются сутки 0–23 как в `formatDate` (UTC), поэтому `HH`.
|
|
10
|
+
*/
|
|
11
|
+
const maskElementToDayjs = {
|
|
12
|
+
[DateMaskElements.Day]: 'DD',
|
|
13
|
+
[DateMaskElements.Month]: 'MM',
|
|
14
|
+
[DateMaskElements.Year]: 'YYYY',
|
|
15
|
+
[DateMaskElements.Hour]: 'HH',
|
|
16
|
+
[DateMaskElements.Minute]: 'mm',
|
|
17
|
+
[DateMaskElements.Second]: 'ss',
|
|
18
|
+
};
|
|
19
|
+
const buildDayjsFormat = (mask, separator) => {
|
|
20
|
+
const parts = mask.split(separator);
|
|
21
|
+
const tokens = [];
|
|
22
|
+
for (const part of parts) {
|
|
23
|
+
const t = maskElementToDayjs[part];
|
|
24
|
+
if (t == null) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
tokens.push(t);
|
|
28
|
+
}
|
|
29
|
+
return tokens.join(separator);
|
|
10
30
|
};
|
|
11
31
|
/**
|
|
12
32
|
* утилита конвертации строковой даты созданной по маске обратно в Date
|
|
33
|
+
* Строгий разбор UTC через `dayjs` + `customParseFormat`; при невалидной строке — Invalid Date
|
|
13
34
|
*/
|
|
14
35
|
export const parseDate = (date, mask, separator = '.') => {
|
|
15
36
|
const dateArr = date.split(separator);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
37
|
+
const maskSplit = mask.split(separator);
|
|
38
|
+
if (dateArr.length !== maskSplit.length) {
|
|
39
|
+
return new Date(Number.NaN);
|
|
40
|
+
}
|
|
41
|
+
const format = buildDayjsFormat(mask, separator);
|
|
42
|
+
if (format == null) {
|
|
43
|
+
return new Date(Number.NaN);
|
|
44
|
+
}
|
|
45
|
+
const parsed = dayjs.utc(date, format, true);
|
|
46
|
+
if (!parsed.isValid()) {
|
|
47
|
+
return new Date(Number.NaN);
|
|
48
|
+
}
|
|
49
|
+
return parsed.toDate();
|
|
21
50
|
};
|
|
@@ -30,6 +30,8 @@ type UseMaskedValueAndSelectedBaseDateReturn = {
|
|
|
30
30
|
*/
|
|
31
31
|
value: string;
|
|
32
32
|
};
|
|
33
|
+
/** При blur неполной даты - в onChange уходит Invalid Date (тот же стейт, что у маски). */
|
|
34
|
+
onMaskedValueBlur: () => void;
|
|
33
35
|
};
|
|
34
36
|
/**
|
|
35
37
|
* хук объединяющий повторяющуюся логику в работе DatePicker и RangeDatePicker:
|
|
@@ -15,7 +15,7 @@ const useSelectedBaseDate_1 = require("../useSelectedBaseDate");
|
|
|
15
15
|
*/
|
|
16
16
|
const useDatePickerOptions = ({ onChange, mask, currentValue, minDate, maxDate, monthOffset, onDatePick, }) => {
|
|
17
17
|
const baseDate = (0, useBaseDateInRange_1.useBaseDateInRange)({ minDate, maxDate, monthOffset });
|
|
18
|
-
const { maskedValue, onMaskedValueChange, onMaskedDateChange } = (0, useMaskedValue_1.useMaskedValue)({
|
|
18
|
+
const { maskedValue, onMaskedValueChange, onMaskedValueBlur, onMaskedDateChange, } = (0, useMaskedValue_1.useMaskedValue)({
|
|
19
19
|
currentValue,
|
|
20
20
|
mask,
|
|
21
21
|
onChangeValue: onChange,
|
|
@@ -42,6 +42,7 @@ const useDatePickerOptions = ({ onChange, mask, currentValue, minDate, maxDate,
|
|
|
42
42
|
onChange: handleDatePick,
|
|
43
43
|
date: selectedBaseDate || baseDate,
|
|
44
44
|
},
|
|
45
|
+
onMaskedValueBlur,
|
|
45
46
|
};
|
|
46
47
|
};
|
|
47
48
|
exports.useDatePickerOptions = useDatePickerOptions;
|
|
@@ -9,19 +9,43 @@ const date_1 = require("../../../utils/date");
|
|
|
9
9
|
*/
|
|
10
10
|
const useMaskedValue = ({ currentValue, mask, onChangeValue, }) => {
|
|
11
11
|
const [maskedValue, setMaskedValue] = (0, react_1.useState)(() => currentValue ? (0, date_1.formatDate)(currentValue, mask) : '');
|
|
12
|
+
const maskedValueRef = (0, react_1.useRef)(maskedValue);
|
|
13
|
+
/**
|
|
14
|
+
* Ввод в маску: синхронизируем локальное значение и ref (ref нужен на blur и после выбора даты из календаря,
|
|
15
|
+
* чтобы не читать устаревший state).
|
|
16
|
+
* `onChangeValue` вызываем только при пустой строке (`null`) или когда маска синтаксически полная —
|
|
17
|
+
* иначе partial не уходит наружу до завершения ввода или до blur (см. `handleMaskedValueBlur`).
|
|
18
|
+
*/
|
|
12
19
|
const handleMaskedValueChange = (value) => {
|
|
13
20
|
setMaskedValue(value);
|
|
14
|
-
|
|
21
|
+
maskedValueRef.current = value;
|
|
15
22
|
if (!value) {
|
|
16
23
|
onChangeValue?.(null, value);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!(0, date_1.isMaskedDateSyntacticallyComplete)(value, mask)) {
|
|
27
|
+
return;
|
|
17
28
|
}
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
onChangeValue?.((0, date_1.parseDate)(value, mask), value);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Blur поля: если осталась непустая неполная маска — один раз отдаём `Invalid Date` в `onChangeValue`,
|
|
33
|
+
* чтобы формы могли показать ошибку. Пустая строка и полная маска не дублируют уже отправленный `onChange`.
|
|
34
|
+
*/
|
|
35
|
+
const handleMaskedValueBlur = () => {
|
|
36
|
+
const currentMaskedValue = maskedValueRef.current;
|
|
37
|
+
if (!currentMaskedValue) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if ((0, date_1.isMaskedDateSyntacticallyComplete)(currentMaskedValue, mask)) {
|
|
41
|
+
return;
|
|
20
42
|
}
|
|
43
|
+
onChangeValue?.(new Date(Number.NaN), currentMaskedValue);
|
|
21
44
|
};
|
|
22
45
|
const handleChangeMaskedDate = (date) => {
|
|
23
46
|
const formatted = (0, date_1.formatDate)(date, mask);
|
|
24
47
|
setMaskedValue(formatted);
|
|
48
|
+
maskedValueRef.current = formatted;
|
|
25
49
|
onChangeValue?.(date, formatted);
|
|
26
50
|
};
|
|
27
51
|
// здесь происходит реакция на изменение value из вне (управляемый компонент)
|
|
@@ -29,6 +53,7 @@ const useMaskedValue = ({ currentValue, mask, onChangeValue, }) => {
|
|
|
29
53
|
// если новое значение пустое, то сбрасываем значение MaskField
|
|
30
54
|
if (!currentValue) {
|
|
31
55
|
setMaskedValue('');
|
|
56
|
+
maskedValueRef.current = '';
|
|
32
57
|
return;
|
|
33
58
|
}
|
|
34
59
|
// здесь обрабатывается сценарий, когда в инпут вводится невалидная дата и при этом currentValue становится Invalid Date
|
|
@@ -36,16 +61,20 @@ const useMaskedValue = ({ currentValue, mask, onChangeValue, }) => {
|
|
|
36
61
|
if (!(0, isDate_1.isDate)(currentValue) && maskedValue) {
|
|
37
62
|
return;
|
|
38
63
|
}
|
|
39
|
-
//
|
|
40
|
-
const isEqualValueAndMaskedDate =
|
|
64
|
+
// сравнение только при полной маске, иначе getTime() от parseDate может быть NaN
|
|
65
|
+
const isEqualValueAndMaskedDate = (0, date_1.isMaskedDateSyntacticallyComplete)(maskedValue, mask) &&
|
|
66
|
+
currentValue.getTime() === (0, date_1.parseDate)(maskedValue, mask).getTime();
|
|
41
67
|
// если даты не равны, то значит изменился currentValue из вне и надо синхронизировать maskedValue
|
|
42
68
|
if (!isEqualValueAndMaskedDate) {
|
|
43
|
-
|
|
69
|
+
const formatted = (0, date_1.formatDate)(currentValue, mask);
|
|
70
|
+
setMaskedValue(formatted);
|
|
71
|
+
maskedValueRef.current = formatted;
|
|
44
72
|
}
|
|
45
73
|
}, [currentValue]);
|
|
46
74
|
return {
|
|
47
75
|
maskedValue,
|
|
48
76
|
onMaskedValueChange: handleMaskedValueChange,
|
|
77
|
+
onMaskedValueBlur: handleMaskedValueBlur,
|
|
49
78
|
onMaskedDateChange: handleChangeMaskedDate,
|
|
50
79
|
};
|
|
51
80
|
};
|
|
@@ -13,6 +13,7 @@ export declare const useLogic: ({ label, value, maxDate, minDate, mask, onOpen,
|
|
|
13
13
|
};
|
|
14
14
|
DatePickerInputProps: {
|
|
15
15
|
value: string;
|
|
16
|
+
onBlur: () => void;
|
|
16
17
|
};
|
|
17
18
|
onAccept: ((value: string, maskRef: import("imask").default.InputMask<import("imask").default.AnyMaskedOptions>, e?: InputEvent | undefined, onChange?: ((changeValue: string) => void) | undefined) => void) | undefined;
|
|
18
19
|
pickerProps: import("../types").PickerProps;
|
|
@@ -7,7 +7,6 @@ const usePopover_1 = require("../../usePopover");
|
|
|
7
7
|
const useViewportType_1 = require("../../useViewportType");
|
|
8
8
|
const constants_1 = require("../constants");
|
|
9
9
|
const useDatePickerOptions_1 = require("../hooks/useDatePickerOptions");
|
|
10
|
-
const useMaskedValue_1 = require("../hooks/useMaskedValue");
|
|
11
10
|
const MinMaxDateContext_1 = require("../MinMaxDateContext");
|
|
12
11
|
const useLogic = ({ label, value, maxDate = MinMaxDateContext_1.DEFAULT_MAX_DATE, minDate = MinMaxDateContext_1.DEFAULT_MIN_DATE, mask = constants_1.DEFAULT_DATE_MASK, onOpen, onClose, onBlur, onChange, forwardedRef, }) => {
|
|
13
12
|
const ref = (0, useForwardedRef_1.useForwardedRef)(forwardedRef);
|
|
@@ -15,21 +14,11 @@ const useLogic = ({ label, value, maxDate = MinMaxDateContext_1.DEFAULT_MAX_DATE
|
|
|
15
14
|
const { isOpen, actions } = (0, usePopover_1.usePopover)();
|
|
16
15
|
const { open, close } = actions;
|
|
17
16
|
const { isMobile } = (0, useViewportType_1.useViewportType)();
|
|
18
|
-
const { maskedValue } = (0, useMaskedValue_1.useMaskedValue)({
|
|
19
|
-
currentValue: value,
|
|
20
|
-
mask,
|
|
21
|
-
onChangeValue: onChange,
|
|
22
|
-
});
|
|
23
17
|
const isTitleShow = isMobile && typeof label === 'string';
|
|
24
18
|
const handleOpen = (event) => {
|
|
25
19
|
onOpen?.();
|
|
26
20
|
open(event);
|
|
27
21
|
};
|
|
28
|
-
const handleClose = () => {
|
|
29
|
-
onBlur?.();
|
|
30
|
-
onClose?.();
|
|
31
|
-
close();
|
|
32
|
-
};
|
|
33
22
|
const handleDayPick = (date) => {
|
|
34
23
|
if (isMobile) {
|
|
35
24
|
setSelectedDate(date);
|
|
@@ -42,7 +31,7 @@ const useLogic = ({ label, value, maxDate = MinMaxDateContext_1.DEFAULT_MAX_DATE
|
|
|
42
31
|
onChange?.(date);
|
|
43
32
|
}
|
|
44
33
|
};
|
|
45
|
-
const { onAccept, inputProps: calculatedInputProps, pickerProps, } = (0, useDatePickerOptions_1.useDatePickerOptions)({
|
|
34
|
+
const { onAccept, inputProps: calculatedInputProps, pickerProps, onMaskedValueBlur, } = (0, useDatePickerOptions_1.useDatePickerOptions)({
|
|
46
35
|
currentValue: value,
|
|
47
36
|
maxDate,
|
|
48
37
|
minDate,
|
|
@@ -50,6 +39,22 @@ const useLogic = ({ label, value, maxDate = MinMaxDateContext_1.DEFAULT_MAX_DATE
|
|
|
50
39
|
onDatePick: handleDayPick,
|
|
51
40
|
onChange: handleChange,
|
|
52
41
|
});
|
|
42
|
+
/** Blur: неполный ввод -Invalid Date в onChange (при isMobile onChange не шлём), потом внешний onBlur. */
|
|
43
|
+
const flushMaskedValueAndCallOnBlur = () => {
|
|
44
|
+
onMaskedValueBlur();
|
|
45
|
+
onBlur?.();
|
|
46
|
+
};
|
|
47
|
+
function handleClose() {
|
|
48
|
+
flushMaskedValueAndCallOnBlur();
|
|
49
|
+
onClose?.();
|
|
50
|
+
close();
|
|
51
|
+
}
|
|
52
|
+
const handleInputBlur = () => {
|
|
53
|
+
if (isOpen) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
flushMaskedValueAndCallOnBlur();
|
|
57
|
+
};
|
|
53
58
|
const handleConfirm = () => {
|
|
54
59
|
onChange?.(selectedDate);
|
|
55
60
|
handleClose();
|
|
@@ -61,7 +66,8 @@ const useLogic = ({ label, value, maxDate = MinMaxDateContext_1.DEFAULT_MAX_DATE
|
|
|
61
66
|
title: isTitleShow ? label : undefined,
|
|
62
67
|
};
|
|
63
68
|
const DatePickerInputProps = {
|
|
64
|
-
value:
|
|
69
|
+
value: calculatedInputProps.value,
|
|
70
|
+
onBlur: handleInputBlur,
|
|
65
71
|
};
|
|
66
72
|
const confirmButtonProps = {
|
|
67
73
|
onClick: handleConfirm,
|
|
@@ -7,12 +7,14 @@ export declare const useLogic: ({ value, minDate, maxDate, mask, onChange, onOpe
|
|
|
7
7
|
ref: import("react").RefObject<HTMLInputElement | null>;
|
|
8
8
|
value: string;
|
|
9
9
|
onAccept: (_: string, maskRef: IMask.InputMask<IMask.AnyMaskedOptions>) => void;
|
|
10
|
+
onBlur: () => void;
|
|
10
11
|
onClick: (e: SyntheticEvent) => void;
|
|
11
12
|
};
|
|
12
13
|
endDatePickerInputProps: {
|
|
13
14
|
ref: import("react").RefObject<HTMLInputElement | null>;
|
|
14
15
|
value: string;
|
|
15
16
|
onAccept: (_: string, maskRef: IMask.InputMask<IMask.AnyMaskedOptions>) => void;
|
|
17
|
+
onBlur: () => void;
|
|
16
18
|
onClick: (e: SyntheticEvent) => void;
|
|
17
19
|
};
|
|
18
20
|
popoverHoveredContextProviderProps: {
|
|
@@ -5,6 +5,7 @@ const react_1 = require("react");
|
|
|
5
5
|
const DatePicker_1 = require("../../DatePicker");
|
|
6
6
|
const useForwardedRef_1 = require("../../useForwardedRef");
|
|
7
7
|
const usePopover_1 = require("../../usePopover");
|
|
8
|
+
const date_1 = require("../../utils/date");
|
|
8
9
|
const hooks_1 = require("./hooks");
|
|
9
10
|
const utils_1 = require("./utils");
|
|
10
11
|
const useLogic = ({ value, minDate = DatePicker_1.DEFAULT_MIN_DATE, maxDate = DatePicker_1.DEFAULT_MAX_DATE, mask = DatePicker_1.DEFAULT_DATE_MASK, onChange, onOpen, onClose, onBlur, }, forwardedRef) => {
|
|
@@ -45,7 +46,7 @@ const useLogic = ({ value, minDate = DatePicker_1.DEFAULT_MIN_DATE, maxDate = Da
|
|
|
45
46
|
const handleChangeStartDate = (startDateValue, startMaskString) => {
|
|
46
47
|
const isStartComplete = startDateValue != null &&
|
|
47
48
|
startMaskString != null &&
|
|
48
|
-
(0,
|
|
49
|
+
(0, date_1.isMaskedDateSyntacticallyComplete)(startMaskString, mask);
|
|
49
50
|
if (isStartComplete &&
|
|
50
51
|
value?.end &&
|
|
51
52
|
startDateValue &&
|
|
@@ -58,7 +59,7 @@ const useLogic = ({ value, minDate = DatePicker_1.DEFAULT_MIN_DATE, maxDate = Da
|
|
|
58
59
|
const handleChangeEndDate = (endDateValue, endMaskString) => {
|
|
59
60
|
const isEndComplete = endDateValue != null &&
|
|
60
61
|
endMaskString != null &&
|
|
61
|
-
(0,
|
|
62
|
+
(0, date_1.isMaskedDateSyntacticallyComplete)(endMaskString, mask);
|
|
62
63
|
if (isEndComplete &&
|
|
63
64
|
value?.start &&
|
|
64
65
|
endDateValue &&
|
|
@@ -68,12 +69,12 @@ const useLogic = ({ value, minDate = DatePicker_1.DEFAULT_MIN_DATE, maxDate = Da
|
|
|
68
69
|
}
|
|
69
70
|
onChange?.({ ...value, end: endDateValue });
|
|
70
71
|
};
|
|
71
|
-
const { maskedValue: startMaskedValue, onMaskedValueChange: onMaskedStartValueChange, onMaskedDateChange: onMaskedStartDateChange, } = (0, DatePicker_1.useMaskedValue)({
|
|
72
|
+
const { maskedValue: startMaskedValue, onMaskedValueChange: onMaskedStartValueChange, onMaskedValueBlur: onMaskedStartValueBlur, onMaskedDateChange: onMaskedStartDateChange, } = (0, DatePicker_1.useMaskedValue)({
|
|
72
73
|
currentValue: value?.start,
|
|
73
74
|
mask,
|
|
74
75
|
onChangeValue: handleChangeStartDate,
|
|
75
76
|
});
|
|
76
|
-
const { maskedValue: endMaskedValue, onMaskedValueChange: onMaskedEndValueChange, onMaskedDateChange: onMaskedEndDateChange, } = (0, DatePicker_1.useMaskedValue)({
|
|
77
|
+
const { maskedValue: endMaskedValue, onMaskedValueChange: onMaskedEndValueChange, onMaskedValueBlur: onMaskedEndValueBlur, onMaskedDateChange: onMaskedEndDateChange, } = (0, DatePicker_1.useMaskedValue)({
|
|
77
78
|
currentValue: value?.end,
|
|
78
79
|
mask,
|
|
79
80
|
onChangeValue: handleChangeEndDate,
|
|
@@ -82,11 +83,32 @@ const useLogic = ({ value, minDate = DatePicker_1.DEFAULT_MIN_DATE, maxDate = Da
|
|
|
82
83
|
onOpen?.();
|
|
83
84
|
open(event);
|
|
84
85
|
};
|
|
86
|
+
const flushActiveMaskedValueOnClose = () => {
|
|
87
|
+
if (activeInput === 'startDate') {
|
|
88
|
+
onMaskedStartValueBlur();
|
|
89
|
+
}
|
|
90
|
+
if (activeInput === 'endDate') {
|
|
91
|
+
onMaskedEndValueBlur();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
85
94
|
const handleClose = () => {
|
|
95
|
+
flushActiveMaskedValueOnClose();
|
|
86
96
|
onBlur?.();
|
|
87
97
|
onClose?.();
|
|
88
98
|
close();
|
|
89
99
|
};
|
|
100
|
+
const handleStartInputBlur = () => {
|
|
101
|
+
if (isOpen) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
onMaskedStartValueBlur();
|
|
105
|
+
};
|
|
106
|
+
const handleEndInputBlur = () => {
|
|
107
|
+
if (isOpen) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
onMaskedEndValueBlur();
|
|
111
|
+
};
|
|
90
112
|
const handleClickStartInput = (e) => {
|
|
91
113
|
setActiveInput('startDate');
|
|
92
114
|
startInputRef.current?.focus();
|
|
@@ -141,12 +163,14 @@ const useLogic = ({ value, minDate = DatePicker_1.DEFAULT_MIN_DATE, maxDate = Da
|
|
|
141
163
|
ref: startInputRef,
|
|
142
164
|
value: startMaskedValue,
|
|
143
165
|
onAccept: handleAcceptStart,
|
|
166
|
+
onBlur: handleStartInputBlur,
|
|
144
167
|
onClick: handleClickStartInput,
|
|
145
168
|
},
|
|
146
169
|
endDatePickerInputProps: {
|
|
147
170
|
ref: endInputRef,
|
|
148
171
|
value: endMaskedValue,
|
|
149
172
|
onAccept: handleAcceptEnd,
|
|
173
|
+
onBlur: handleEndInputBlur,
|
|
150
174
|
onClick: handleClickEndInput,
|
|
151
175
|
},
|
|
152
176
|
popoverHoveredContextProviderProps: {
|
|
@@ -15,4 +15,3 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./getBoundaryDate"), exports);
|
|
18
|
-
__exportStar(require("./isMaskedDateSyntacticallyComplete"), exports);
|
|
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./formatDate"), exports);
|
|
18
|
+
__exportStar(require("./isMaskedDateSyntacticallyComplete"), exports);
|
|
18
19
|
__exportStar(require("./maskDate"), exports);
|
|
19
20
|
__exportStar(require("./parseDate"), exports);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DateMask } from '../maskDate';
|
|
2
2
|
/**
|
|
3
3
|
* утилита конвертации строковой даты созданной по маске обратно в Date
|
|
4
|
+
* Строгий разбор UTC через `dayjs` + `customParseFormat`; при невалидной строке — Invalid Date
|
|
4
5
|
*/
|
|
5
6
|
export declare const parseDate: (date: string, mask: DateMask, separator?: string) => Date;
|
|
@@ -1,25 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.parseDate = void 0;
|
|
4
|
-
const
|
|
7
|
+
const dayjs_1 = __importDefault(require("dayjs"));
|
|
8
|
+
const customParseFormat_1 = __importDefault(require("dayjs/plugin/customParseFormat"));
|
|
9
|
+
const utc_1 = __importDefault(require("dayjs/plugin/utc"));
|
|
5
10
|
const maskDate_1 = require("../maskDate");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
dayjs_1.default.extend(customParseFormat_1.default);
|
|
12
|
+
dayjs_1.default.extend(utc_1.default);
|
|
13
|
+
/**
|
|
14
|
+
* Токен маски UI → литерал формата `dayjs` (strict UTC).
|
|
15
|
+
* В маске час — `hh`, в дате используются сутки 0–23 как в `formatDate` (UTC), поэтому `HH`.
|
|
16
|
+
*/
|
|
17
|
+
const maskElementToDayjs = {
|
|
18
|
+
[maskDate_1.DateMaskElements.Day]: 'DD',
|
|
19
|
+
[maskDate_1.DateMaskElements.Month]: 'MM',
|
|
20
|
+
[maskDate_1.DateMaskElements.Year]: 'YYYY',
|
|
21
|
+
[maskDate_1.DateMaskElements.Hour]: 'HH',
|
|
22
|
+
[maskDate_1.DateMaskElements.Minute]: 'mm',
|
|
23
|
+
[maskDate_1.DateMaskElements.Second]: 'ss',
|
|
24
|
+
};
|
|
25
|
+
const buildDayjsFormat = (mask, separator) => {
|
|
26
|
+
const parts = mask.split(separator);
|
|
27
|
+
const tokens = [];
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
const t = maskElementToDayjs[part];
|
|
30
|
+
if (t == null) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
tokens.push(t);
|
|
34
|
+
}
|
|
35
|
+
return tokens.join(separator);
|
|
13
36
|
};
|
|
14
37
|
/**
|
|
15
38
|
* утилита конвертации строковой даты созданной по маске обратно в Date
|
|
39
|
+
* Строгий разбор UTC через `dayjs` + `customParseFormat`; при невалидной строке — Invalid Date
|
|
16
40
|
*/
|
|
17
41
|
const parseDate = (date, mask, separator = '.') => {
|
|
18
42
|
const dateArr = date.split(separator);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
43
|
+
const maskSplit = mask.split(separator);
|
|
44
|
+
if (dateArr.length !== maskSplit.length) {
|
|
45
|
+
return new Date(Number.NaN);
|
|
46
|
+
}
|
|
47
|
+
const format = buildDayjsFormat(mask, separator);
|
|
48
|
+
if (format == null) {
|
|
49
|
+
return new Date(Number.NaN);
|
|
50
|
+
}
|
|
51
|
+
const parsed = dayjs_1.default.utc(date, format, true);
|
|
52
|
+
if (!parsed.isValid()) {
|
|
53
|
+
return new Date(Number.NaN);
|
|
54
|
+
}
|
|
55
|
+
return parsed.toDate();
|
|
24
56
|
};
|
|
25
57
|
exports.parseDate = parseDate;
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|