@mailstep/design-system 0.13.0-beta.2 → 0.13.1-beta.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/package.json +1 -1
- package/ui/Elements/DatePicker/DatePicker.js +5 -1
- package/ui/Elements/DatePicker/Datetime/components/Timepicker/Timepicker.js +16 -6
- package/ui/Elements/DatePicker/Datetime/components/Timepicker/constants.d.ts +1 -0
- package/ui/Elements/DatePicker/Datetime/components/Timepicker/constants.js +2 -1
- package/ui/Elements/DatePicker/Datetime/components/Timepicker/utils/index.d.ts +1 -0
- package/ui/Elements/DatePicker/Datetime/components/Timepicker/utils/index.js +2 -1
- package/ui/Elements/DatePicker/Datetime/components/Timepicker/utils/isTimeFieldComplete.d.ts +3 -0
- package/ui/Elements/DatePicker/Datetime/components/Timepicker/utils/isTimeFieldComplete.js +5 -0
- package/ui/Elements/DatePicker/Datetime/components/Timepicker/utils/sanitizeTimeField.js +1 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/DatePickerInput.d.ts +6 -2
- package/ui/Elements/DatePicker/components/DatePickerInput/DatePickerInput.js +35 -6
- package/ui/Elements/DatePicker/components/DatePickerInput/constants.d.ts +5 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/constants.js +16 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/applyDateMask.d.ts +3 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/applyDateMask.js +19 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/countDigits.d.ts +3 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/countDigits.js +5 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/getMaskedCaretPosition.d.ts +3 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/getMaskedCaretPosition.js +14 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/index.d.ts +4 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/index.js +5 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/isMaskableFormat.d.ts +3 -0
- package/ui/Elements/DatePicker/components/DatePickerInput/utils/isMaskableFormat.js +9 -0
- package/ui/Elements/DatePicker/utils/getInputFormat.d.ts +3 -0
- package/ui/Elements/DatePicker/utils/getInputFormat.js +10 -0
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import SpaceAround_default from "../SpaceAround/index.js";
|
|
|
3
3
|
import Datetime from "./Datetime/DateTime.js";
|
|
4
4
|
import { DatePickerInput } from "./components/DatePickerInput/DatePickerInput.js";
|
|
5
5
|
import "./components/DatePickerInput/index.js";
|
|
6
|
+
import { getInputFormat } from "./utils/getInputFormat.js";
|
|
6
7
|
import { DatePickerBase } from "./styles.js";
|
|
7
8
|
import React, { useCallback, useContext } from "react";
|
|
8
9
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -16,9 +17,11 @@ import "moment/dist/locale/sk";
|
|
|
16
17
|
const defaultDateFormat = "DD.MM.YYYY";
|
|
17
18
|
const DatePicker = ({ name, value, initialViewDate, error, label, disabled, isValidDate, onChange, dateFormat = defaultDateFormat, timeFormat = false, open, secondValue = null, input, icon = true, showInputLabel = false, showCalendarLabel = true, spaceAround = false, className, onClear, onInputChange, ...passProps }) => {
|
|
18
19
|
const locale = useContext(LanguageContext);
|
|
20
|
+
const inputFormat = getInputFormat(dateFormat, timeFormat);
|
|
19
21
|
const handleRenderInput = useCallback((inputProps, openCalendar) => /* @__PURE__ */ jsx(DatePickerInput, {
|
|
20
22
|
inputProps,
|
|
21
23
|
openCalendar,
|
|
24
|
+
format: inputFormat,
|
|
22
25
|
name,
|
|
23
26
|
label,
|
|
24
27
|
error,
|
|
@@ -35,7 +38,8 @@ const DatePicker = ({ name, value, initialViewDate, error, label, disabled, isVa
|
|
|
35
38
|
icon,
|
|
36
39
|
showInputLabel,
|
|
37
40
|
onClear,
|
|
38
|
-
onInputChange
|
|
41
|
+
onInputChange,
|
|
42
|
+
inputFormat
|
|
39
43
|
]);
|
|
40
44
|
const handleRenderView = useCallback((_, renderCalendar) => {
|
|
41
45
|
if (label && !showInputLabel) return /* @__PURE__ */ jsx(x.div, {
|
|
@@ -4,8 +4,10 @@ import { FooterRowLabel } from "../FooterRowLabel.js";
|
|
|
4
4
|
import "./constants.js";
|
|
5
5
|
import { TimeButton, TimeField, TimeWrapper } from "./styles.js";
|
|
6
6
|
import { padTime } from "./utils/padTime.js";
|
|
7
|
+
import { isTimeFieldComplete } from "./utils/isTimeFieldComplete.js";
|
|
7
8
|
import { parseTimeField } from "./utils/parseTimeField.js";
|
|
8
9
|
import { sanitizeTimeField } from "./utils/sanitizeTimeField.js";
|
|
10
|
+
import "./utils/index.js";
|
|
9
11
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
10
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
13
|
import { x } from "@xstyled/styled-components";
|
|
@@ -18,6 +20,7 @@ const Timepicker = ({ setTime, value, disabled }) => {
|
|
|
18
20
|
const [minutesValue, setMinutesValue] = useState(padTime(minutes));
|
|
19
21
|
const isHoursEdited = useRef(false);
|
|
20
22
|
const isMinutesEdited = useRef(false);
|
|
23
|
+
const minutesRef = useRef(null);
|
|
21
24
|
useEffect(() => {
|
|
22
25
|
if (isHoursEdited.current) return;
|
|
23
26
|
setHoursValue(padTime(hours));
|
|
@@ -72,6 +75,8 @@ const Timepicker = ({ setTime, value, disabled }) => {
|
|
|
72
75
|
const parsed = parseTimeField(nextValue, NaN);
|
|
73
76
|
if (Number.isNaN(parsed) || parsed > 23) return;
|
|
74
77
|
commitTime("hours", parsed);
|
|
78
|
+
if (!isTimeFieldComplete(nextValue, parsed, 23)) return;
|
|
79
|
+
minutesRef.current?.focus();
|
|
75
80
|
}, [commitTime]);
|
|
76
81
|
const onMinutesChange = useCallback((e) => {
|
|
77
82
|
const nextValue = sanitizeTimeField(e.target.value);
|
|
@@ -93,14 +98,18 @@ const Timepicker = ({ setTime, value, disabled }) => {
|
|
|
93
98
|
}, []);
|
|
94
99
|
const onHoursBlur = useCallback(() => {
|
|
95
100
|
isHoursEdited.current = false;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
setHoursValue((prev) => {
|
|
102
|
+
const parsed = parseTimeField(prev, NaN);
|
|
103
|
+
return padTime(!Number.isNaN(parsed) && parsed <= 23 ? parsed : hours);
|
|
104
|
+
});
|
|
105
|
+
}, [hours]);
|
|
99
106
|
const onMinutesBlur = useCallback(() => {
|
|
100
107
|
isMinutesEdited.current = false;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
setMinutesValue((prev) => {
|
|
109
|
+
const parsed = parseTimeField(prev, NaN);
|
|
110
|
+
return padTime(!Number.isNaN(parsed) && parsed <= 59 ? parsed : minutes);
|
|
111
|
+
});
|
|
112
|
+
}, [minutes]);
|
|
104
113
|
return /* @__PURE__ */ jsxs(FooterRow, {
|
|
105
114
|
disabled,
|
|
106
115
|
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Icon, { icon: "clock" }), /* @__PURE__ */ jsx(FooterRowLabel, { children: i18n._({
|
|
@@ -147,6 +156,7 @@ const Timepicker = ({ setTime, value, disabled }) => {
|
|
|
147
156
|
children: ":"
|
|
148
157
|
}),
|
|
149
158
|
/* @__PURE__ */ jsx(TimeField, {
|
|
159
|
+
ref: minutesRef,
|
|
150
160
|
value: minutesValue,
|
|
151
161
|
disabled,
|
|
152
162
|
inputMode: "numeric",
|
|
@@ -4,5 +4,6 @@ const MINUTES_COUNT = 60;
|
|
|
4
4
|
const MINUTES_STEP = 5;
|
|
5
5
|
const MAX_HOURS = 23;
|
|
6
6
|
const MAX_MINUTES = 59;
|
|
7
|
+
const TIME_FIELD_LENGTH = 2;
|
|
7
8
|
//#endregion
|
|
8
|
-
export { HOURS_COUNT, MAX_HOURS, MAX_MINUTES, MINUTES_COUNT, MINUTES_STEP };
|
|
9
|
+
export { HOURS_COUNT, MAX_HOURS, MAX_MINUTES, MINUTES_COUNT, MINUTES_STEP, TIME_FIELD_LENGTH };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { padTime } from "./padTime.js";
|
|
2
|
+
import { isTimeFieldComplete } from "./isTimeFieldComplete.js";
|
|
2
3
|
import { parseTimeField } from "./parseTimeField.js";
|
|
3
4
|
import { sanitizeTimeField } from "./sanitizeTimeField.js";
|
|
4
|
-
export { padTime, parseTimeField, sanitizeTimeField };
|
|
5
|
+
export { isTimeFieldComplete, padTime, parseTimeField, sanitizeTimeField };
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { type FC, type InputHTMLAttributes } from 'react';
|
|
1
|
+
import { type ChangeEvent, type FC, type InputHTMLAttributes } from 'react';
|
|
2
|
+
type DatetimeInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
|
|
3
|
+
onChange?: (event: ChangeEvent<HTMLInputElement> | string) => void;
|
|
4
|
+
};
|
|
2
5
|
type DatePickerInputProps = {
|
|
3
|
-
inputProps:
|
|
6
|
+
inputProps: DatetimeInputProps;
|
|
4
7
|
openCalendar: () => void;
|
|
8
|
+
format: string;
|
|
5
9
|
name?: string;
|
|
6
10
|
label?: string;
|
|
7
11
|
error?: string;
|
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
import Input_default from "../../../../Forms/Input/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { applyDateMask } from "./utils/applyDateMask.js";
|
|
3
|
+
import { countDigits } from "./utils/countDigits.js";
|
|
4
|
+
import { getMaskedCaretPosition } from "./utils/getMaskedCaretPosition.js";
|
|
5
|
+
import { isMaskableFormat } from "./utils/isMaskableFormat.js";
|
|
6
|
+
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
3
7
|
import { jsx } from "react/jsx-runtime";
|
|
4
8
|
//#region packages/ui/Elements/DatePicker/components/DatePickerInput/DatePickerInput.tsx
|
|
5
|
-
const DatePickerInput = ({ inputProps, openCalendar, name, label, error, disabled, icon, showInputLabel, onClear, onInputChange }) => {
|
|
9
|
+
const DatePickerInput = ({ inputProps, openCalendar, format, name, label, error, disabled, icon, showInputLabel, onClear, onInputChange }) => {
|
|
6
10
|
const [typedValue, setTypedValue] = useState(null);
|
|
11
|
+
const inputRef = useRef(null);
|
|
12
|
+
const caretPosition = useRef(null);
|
|
13
|
+
const isMasked = isMaskableFormat(format);
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
if (caretPosition.current === null) return;
|
|
16
|
+
const caret = caretPosition.current;
|
|
17
|
+
caretPosition.current = null;
|
|
18
|
+
inputRef.current?.setSelectionRange(caret, caret);
|
|
19
|
+
});
|
|
7
20
|
const handleChange = useCallback((e) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
if (!isMasked) {
|
|
22
|
+
setTypedValue(e.target.value);
|
|
23
|
+
inputProps.onChange?.(e);
|
|
24
|
+
onInputChange?.(e.target.value);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const { value, selectionStart } = e.target;
|
|
28
|
+
const digitsBeforeCaret = countDigits(value.slice(0, selectionStart ?? value.length));
|
|
29
|
+
const maskedValue = applyDateMask(value, format);
|
|
30
|
+
caretPosition.current = getMaskedCaretPosition(maskedValue, digitsBeforeCaret);
|
|
31
|
+
setTypedValue(maskedValue);
|
|
32
|
+
inputProps.onChange?.(maskedValue);
|
|
33
|
+
onInputChange?.(maskedValue);
|
|
34
|
+
}, [
|
|
35
|
+
inputProps,
|
|
36
|
+
onInputChange,
|
|
37
|
+
isMasked,
|
|
38
|
+
format
|
|
39
|
+
]);
|
|
12
40
|
const handleBlur = useCallback(() => {
|
|
13
41
|
setTypedValue(null);
|
|
14
42
|
}, []);
|
|
@@ -19,6 +47,7 @@ const DatePickerInput = ({ inputProps, openCalendar, name, label, error, disable
|
|
|
19
47
|
error,
|
|
20
48
|
disabled,
|
|
21
49
|
isInvalid: !!error,
|
|
50
|
+
inputRef,
|
|
22
51
|
onChange: handleChange,
|
|
23
52
|
onBlur: handleBlur,
|
|
24
53
|
label: showInputLabel ? label : void 0,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region packages/ui/Elements/DatePicker/components/DatePickerInput/constants.ts
|
|
2
|
+
const MASKABLE_TOKENS = [
|
|
3
|
+
"DD",
|
|
4
|
+
"MM",
|
|
5
|
+
"YY",
|
|
6
|
+
"YYYY",
|
|
7
|
+
"HH",
|
|
8
|
+
"mm",
|
|
9
|
+
"ss"
|
|
10
|
+
];
|
|
11
|
+
const DIGIT_PATTERN = /\d/;
|
|
12
|
+
const FORMAT_SLOT_PATTERN = /[A-Za-z]/;
|
|
13
|
+
const NON_DIGIT_PATTERN = /\D/g;
|
|
14
|
+
const FORMAT_TOKEN_PATTERN = /[A-Za-z]+/g;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { DIGIT_PATTERN, FORMAT_SLOT_PATTERN, FORMAT_TOKEN_PATTERN, MASKABLE_TOKENS, NON_DIGIT_PATTERN };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FORMAT_SLOT_PATTERN, NON_DIGIT_PATTERN } from "../constants.js";
|
|
2
|
+
//#region packages/ui/Elements/DatePicker/components/DatePickerInput/utils/applyDateMask.ts
|
|
3
|
+
const applyDateMask = (value, format) => {
|
|
4
|
+
const digits = value.replace(NON_DIGIT_PATTERN, "");
|
|
5
|
+
let masked = "";
|
|
6
|
+
let digitIndex = 0;
|
|
7
|
+
for (const char of format) {
|
|
8
|
+
if (digitIndex >= digits.length) break;
|
|
9
|
+
if (FORMAT_SLOT_PATTERN.test(char)) {
|
|
10
|
+
masked += digits[digitIndex];
|
|
11
|
+
digitIndex += 1;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
masked += char;
|
|
15
|
+
}
|
|
16
|
+
return masked;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { applyDateMask };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DIGIT_PATTERN } from "../constants.js";
|
|
2
|
+
//#region packages/ui/Elements/DatePicker/components/DatePickerInput/utils/getMaskedCaretPosition.ts
|
|
3
|
+
const getMaskedCaretPosition = (maskedValue, digitsBeforeCaret) => {
|
|
4
|
+
if (digitsBeforeCaret <= 0) return 0;
|
|
5
|
+
let digits = 0;
|
|
6
|
+
for (let index = 0; index < maskedValue.length; index += 1) {
|
|
7
|
+
if (!DIGIT_PATTERN.test(maskedValue[index])) continue;
|
|
8
|
+
digits += 1;
|
|
9
|
+
if (digits === digitsBeforeCaret) return index + 1;
|
|
10
|
+
}
|
|
11
|
+
return maskedValue.length;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { getMaskedCaretPosition };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { applyDateMask } from "./applyDateMask.js";
|
|
2
|
+
import { countDigits } from "./countDigits.js";
|
|
3
|
+
import { getMaskedCaretPosition } from "./getMaskedCaretPosition.js";
|
|
4
|
+
import { isMaskableFormat } from "./isMaskableFormat.js";
|
|
5
|
+
export { applyDateMask, countDigits, getMaskedCaretPosition, isMaskableFormat };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FORMAT_TOKEN_PATTERN, MASKABLE_TOKENS } from "../constants.js";
|
|
2
|
+
//#region packages/ui/Elements/DatePicker/components/DatePickerInput/utils/isMaskableFormat.ts
|
|
3
|
+
const isMaskableFormat = (format) => {
|
|
4
|
+
const tokens = format.match(FORMAT_TOKEN_PATTERN);
|
|
5
|
+
if (!tokens?.length) return false;
|
|
6
|
+
return tokens.every((token) => MASKABLE_TOKENS.includes(token));
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { isMaskableFormat };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region packages/ui/Elements/DatePicker/utils/getInputFormat.ts
|
|
2
|
+
const getInputFormat = (dateFormat, timeFormat) => {
|
|
3
|
+
const date = typeof dateFormat === "string" ? dateFormat : "";
|
|
4
|
+
const time = typeof timeFormat === "string" ? timeFormat : "";
|
|
5
|
+
if (!date) return time;
|
|
6
|
+
if (!time) return date;
|
|
7
|
+
return `${date} ${time}`;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { getInputFormat };
|