@axenstudio/axen-form 0.1.10 → 0.1.12
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/README.md +783 -270
- package/dist/core/dateUtils.cjs +7 -3
- package/dist/core/dateUtils.d.ts +1 -1
- package/dist/core/dateUtils.d.ts.map +1 -1
- package/dist/core/dateUtils.js +7 -3
- package/dist/core/validation.cjs +4 -1
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +4 -1
- package/dist/fields/AutocompleteField/AutocompleteField.cjs +32 -32
- package/dist/fields/AutocompleteField/AutocompleteField.js +32 -32
- package/dist/fields/AutocompleteField/AutocompleteField.module.css.cjs +34 -13
- package/dist/fields/AutocompleteField/AutocompleteField.module.css.js +34 -13
- package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.cjs +90 -88
- package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.d.ts.map +1 -1
- package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.js +90 -88
- package/dist/fields/AutocompletePredictField/AutocompletePredictField.cjs +81 -64
- package/dist/fields/AutocompletePredictField/AutocompletePredictField.d.ts.map +1 -1
- package/dist/fields/AutocompletePredictField/AutocompletePredictField.js +81 -64
- package/dist/fields/CheckboxField/CheckboxField.module.css.cjs +12 -0
- package/dist/fields/CheckboxField/CheckboxField.module.css.js +12 -0
- package/dist/fields/ColorField/ColorField.module.css.cjs +12 -0
- package/dist/fields/ColorField/ColorField.module.css.js +12 -0
- package/dist/fields/DateField/DateField.cjs +56 -4
- package/dist/fields/DateField/DateField.d.ts +1 -1
- package/dist/fields/DateField/DateField.d.ts.map +1 -1
- package/dist/fields/DateField/DateField.js +56 -4
- package/dist/fields/DateField/DatePicker.cjs +194 -0
- package/dist/fields/DateField/DatePicker.d.ts +11 -0
- package/dist/fields/DateField/DatePicker.d.ts.map +1 -0
- package/dist/fields/DateField/DatePicker.js +194 -0
- package/dist/fields/DateField/DatePicker.module.css.cjs +62 -0
- package/dist/fields/DateField/DatePicker.module.css.js +62 -0
- package/dist/fields/DateTimeField/DateTimeField.cjs +126 -8
- package/dist/fields/DateTimeField/DateTimeField.d.ts +2 -1
- package/dist/fields/DateTimeField/DateTimeField.d.ts.map +1 -1
- package/dist/fields/DateTimeField/DateTimeField.js +126 -8
- package/dist/fields/PasswordField/PasswordField.module.css.cjs +12 -0
- package/dist/fields/PasswordField/PasswordField.module.css.js +12 -0
- package/dist/fields/RadioField/RadioField.module.css.cjs +12 -0
- package/dist/fields/RadioField/RadioField.module.css.js +12 -0
- package/dist/fields/SelectField/SelectField.cjs +6 -1
- package/dist/fields/SelectField/SelectField.d.ts.map +1 -1
- package/dist/fields/SelectField/SelectField.js +6 -1
- package/dist/fields/SliderField/SliderField.module.css.cjs +15 -3
- package/dist/fields/SliderField/SliderField.module.css.js +15 -3
- package/dist/fields/SwitchField/SwitchField.module.css.cjs +12 -0
- package/dist/fields/SwitchField/SwitchField.module.css.js +12 -0
- package/dist/fields/TimeField/TimeField.cjs +60 -5
- package/dist/fields/TimeField/TimeField.d.ts +2 -1
- package/dist/fields/TimeField/TimeField.d.ts.map +1 -1
- package/dist/fields/TimeField/TimeField.js +60 -5
- package/dist/fields/TimeField/TimePicker.cjs +182 -0
- package/dist/fields/TimeField/TimePicker.d.ts +11 -0
- package/dist/fields/TimeField/TimePicker.d.ts.map +1 -0
- package/dist/fields/TimeField/TimePicker.js +182 -0
- package/dist/fields/TimeField/TimePicker.module.css.cjs +50 -0
- package/dist/fields/TimeField/TimePicker.module.css.js +50 -0
- package/dist/fields/defaultPlaceholders.cjs +1 -0
- package/dist/fields/defaultPlaceholders.d.ts.map +1 -1
- package/dist/fields/defaultPlaceholders.js +1 -0
- package/dist/fields/fieldBase.module.css.cjs +373 -18
- package/dist/fields/fieldBase.module.css.js +373 -18
- package/dist/fields/icons.cjs +2 -0
- package/dist/fields/icons.d.ts +4 -0
- package/dist/fields/icons.d.ts.map +1 -1
- package/dist/fields/icons.js +2 -0
- package/dist/index.cjs +230 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -3
- package/dist/layout/Box/Box.module.css.cjs +12 -0
- package/dist/layout/Box/Box.module.css.js +12 -0
- package/dist/layout/Divider/Divider.module.css.cjs +12 -0
- package/dist/layout/Divider/Divider.module.css.js +12 -0
- package/dist/layout/Grid/Grid.module.css.cjs +12 -0
- package/dist/layout/Grid/Grid.module.css.js +12 -0
- package/dist/layout/Stack/Stack.module.css.cjs +12 -0
- package/dist/layout/Stack/Stack.module.css.js +12 -0
- package/dist/react/AxenForm.cjs +228 -3
- package/dist/react/AxenForm.js +228 -3
- package/dist/react/fieldGroup.module.css.cjs +12 -0
- package/dist/react/fieldGroup.module.css.js +12 -0
- package/dist/react/gridCell.module.css.cjs +12 -0
- package/dist/react/gridCell.module.css.js +12 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -9
- package/dist/axen-form.css +0 -1057
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
4
5
|
const dateUtils = require("../../core/dateUtils.cjs");
|
|
5
6
|
const fieldBase_module = require("../fieldBase.module.css.cjs");
|
|
6
7
|
const fieldUtils = require("../fieldUtils.cjs");
|
|
7
8
|
const iconUtils = require("../iconUtils.cjs");
|
|
9
|
+
const DatePicker = require("./DatePicker.cjs");
|
|
8
10
|
function DateField({
|
|
9
11
|
name,
|
|
10
12
|
value,
|
|
@@ -20,8 +22,11 @@ function DateField({
|
|
|
20
22
|
min,
|
|
21
23
|
max,
|
|
22
24
|
locale,
|
|
23
|
-
icon
|
|
25
|
+
icon,
|
|
26
|
+
form
|
|
24
27
|
}) {
|
|
28
|
+
const [isOpen, setIsOpen] = react.useState(false);
|
|
29
|
+
const wrapperRef = react.useRef(null);
|
|
25
30
|
const aria = fieldUtils.ariaProps(name, { error, helperText, required, label });
|
|
26
31
|
const resolvedLocale = locale ?? dateUtils.getDefaultLocale();
|
|
27
32
|
const resolvedIcon = iconUtils.resolveFieldIcon(icon, "date");
|
|
@@ -30,6 +35,31 @@ function DateField({
|
|
|
30
35
|
const handleChange = (e) => {
|
|
31
36
|
onChange(e);
|
|
32
37
|
};
|
|
38
|
+
const openPicker = react.useCallback(() => {
|
|
39
|
+
if (!disabled && !readOnly) setIsOpen(true);
|
|
40
|
+
}, [disabled, readOnly]);
|
|
41
|
+
const handleApply = react.useCallback(
|
|
42
|
+
(isoDate) => {
|
|
43
|
+
form.setFieldValue(name, isoDate);
|
|
44
|
+
setIsOpen(false);
|
|
45
|
+
},
|
|
46
|
+
[form, name]
|
|
47
|
+
);
|
|
48
|
+
const handleClose = react.useCallback(() => {
|
|
49
|
+
setIsOpen(false);
|
|
50
|
+
}, []);
|
|
51
|
+
const handleBlur = react.useCallback(
|
|
52
|
+
(e) => {
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
var _a;
|
|
55
|
+
if (!((_a = wrapperRef.current) == null ? void 0 : _a.contains(document.activeElement))) {
|
|
56
|
+
setIsOpen(false);
|
|
57
|
+
onBlur(e);
|
|
58
|
+
}
|
|
59
|
+
}, 150);
|
|
60
|
+
},
|
|
61
|
+
[onBlur]
|
|
62
|
+
);
|
|
33
63
|
const inputEl = /* @__PURE__ */ jsxRuntime.jsx(
|
|
34
64
|
"input",
|
|
35
65
|
{
|
|
@@ -38,7 +68,7 @@ function DateField({
|
|
|
38
68
|
name,
|
|
39
69
|
value: dateValue,
|
|
40
70
|
onChange: handleChange,
|
|
41
|
-
onBlur,
|
|
71
|
+
onBlur: handleBlur,
|
|
42
72
|
placeholder,
|
|
43
73
|
disabled,
|
|
44
74
|
readOnly,
|
|
@@ -48,7 +78,7 @@ function DateField({
|
|
|
48
78
|
className: fieldUtils.cx(fieldBase_module.default.input, resolvedIcon && fieldBase_module.default.inputWithIcon, error && fieldBase_module.default.inputError)
|
|
49
79
|
}
|
|
50
80
|
);
|
|
51
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: fieldBase_module.default.wrapper, children: [
|
|
81
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: fieldBase_module.default.wrapper, ref: wrapperRef, style: { position: "relative" }, children: [
|
|
52
82
|
label && /* @__PURE__ */ jsxRuntime.jsx(
|
|
53
83
|
"label",
|
|
54
84
|
{
|
|
@@ -59,9 +89,31 @@ function DateField({
|
|
|
59
89
|
}
|
|
60
90
|
),
|
|
61
91
|
resolvedIcon ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: fieldBase_module.default.inputIconWrapper, children: [
|
|
62
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
92
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
93
|
+
"button",
|
|
94
|
+
{
|
|
95
|
+
type: "button",
|
|
96
|
+
className: fieldBase_module.default.fieldIconClickable,
|
|
97
|
+
onClick: openPicker,
|
|
98
|
+
disabled: disabled || readOnly,
|
|
99
|
+
"aria-label": "Open date picker",
|
|
100
|
+
tabIndex: -1,
|
|
101
|
+
children: resolvedIcon
|
|
102
|
+
}
|
|
103
|
+
),
|
|
63
104
|
inputEl
|
|
64
105
|
] }) : inputEl,
|
|
106
|
+
isOpen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
107
|
+
DatePicker.DatePicker,
|
|
108
|
+
{
|
|
109
|
+
value: dateValue,
|
|
110
|
+
min,
|
|
111
|
+
max,
|
|
112
|
+
pickerId: `${name}-datepicker`,
|
|
113
|
+
onApply: handleApply,
|
|
114
|
+
onClose: handleClose
|
|
115
|
+
}
|
|
116
|
+
),
|
|
65
117
|
displayText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: fieldBase_module.default.helperText, "aria-hidden": "true", children: displayText }),
|
|
66
118
|
helperText && /* @__PURE__ */ jsxRuntime.jsx(
|
|
67
119
|
"span",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FieldComponentProps } from '../../types';
|
|
2
|
-
export declare function DateField({ name, value, onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, min, max, locale, icon, }: Readonly<FieldComponentProps & {
|
|
2
|
+
export declare function DateField({ name, value, onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, min, max, locale, icon, form, }: Readonly<FieldComponentProps & {
|
|
3
3
|
min?: string;
|
|
4
4
|
max?: string;
|
|
5
5
|
locale?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateField.d.ts","sourceRoot":"","sources":["../../../src/fields/DateField/DateField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"DateField.d.ts","sourceRoot":"","sources":["../../../src/fields/DateField/DateField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAMvD,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,MAAM,EACN,KAAK,EACL,UAAU,EACV,KAAK,EACL,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,GAAG,EACH,GAAG,EACH,MAAM,EACN,IAAI,EACJ,IAAI,GACL,EAAE,QAAQ,CAAC,mBAAmB,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,2CAwHjF"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useCallback } from "react";
|
|
2
3
|
import { formatDateLocale, getDefaultLocale } from "../../core/dateUtils.js";
|
|
3
4
|
import styles from "../fieldBase.module.css.js";
|
|
4
5
|
import { ariaProps, cx, labelId, helperId } from "../fieldUtils.js";
|
|
5
6
|
import { resolveFieldIcon } from "../iconUtils.js";
|
|
7
|
+
import { DatePicker } from "./DatePicker.js";
|
|
6
8
|
function DateField({
|
|
7
9
|
name,
|
|
8
10
|
value,
|
|
@@ -18,8 +20,11 @@ function DateField({
|
|
|
18
20
|
min,
|
|
19
21
|
max,
|
|
20
22
|
locale,
|
|
21
|
-
icon
|
|
23
|
+
icon,
|
|
24
|
+
form
|
|
22
25
|
}) {
|
|
26
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
27
|
+
const wrapperRef = useRef(null);
|
|
23
28
|
const aria = ariaProps(name, { error, helperText, required, label });
|
|
24
29
|
const resolvedLocale = locale ?? getDefaultLocale();
|
|
25
30
|
const resolvedIcon = resolveFieldIcon(icon, "date");
|
|
@@ -28,6 +33,31 @@ function DateField({
|
|
|
28
33
|
const handleChange = (e) => {
|
|
29
34
|
onChange(e);
|
|
30
35
|
};
|
|
36
|
+
const openPicker = useCallback(() => {
|
|
37
|
+
if (!disabled && !readOnly) setIsOpen(true);
|
|
38
|
+
}, [disabled, readOnly]);
|
|
39
|
+
const handleApply = useCallback(
|
|
40
|
+
(isoDate) => {
|
|
41
|
+
form.setFieldValue(name, isoDate);
|
|
42
|
+
setIsOpen(false);
|
|
43
|
+
},
|
|
44
|
+
[form, name]
|
|
45
|
+
);
|
|
46
|
+
const handleClose = useCallback(() => {
|
|
47
|
+
setIsOpen(false);
|
|
48
|
+
}, []);
|
|
49
|
+
const handleBlur = useCallback(
|
|
50
|
+
(e) => {
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
var _a;
|
|
53
|
+
if (!((_a = wrapperRef.current) == null ? void 0 : _a.contains(document.activeElement))) {
|
|
54
|
+
setIsOpen(false);
|
|
55
|
+
onBlur(e);
|
|
56
|
+
}
|
|
57
|
+
}, 150);
|
|
58
|
+
},
|
|
59
|
+
[onBlur]
|
|
60
|
+
);
|
|
31
61
|
const inputEl = /* @__PURE__ */ jsx(
|
|
32
62
|
"input",
|
|
33
63
|
{
|
|
@@ -36,7 +66,7 @@ function DateField({
|
|
|
36
66
|
name,
|
|
37
67
|
value: dateValue,
|
|
38
68
|
onChange: handleChange,
|
|
39
|
-
onBlur,
|
|
69
|
+
onBlur: handleBlur,
|
|
40
70
|
placeholder,
|
|
41
71
|
disabled,
|
|
42
72
|
readOnly,
|
|
@@ -46,7 +76,7 @@ function DateField({
|
|
|
46
76
|
className: cx(styles.input, resolvedIcon && styles.inputWithIcon, error && styles.inputError)
|
|
47
77
|
}
|
|
48
78
|
);
|
|
49
|
-
return /* @__PURE__ */ jsxs("div", { className: styles.wrapper, children: [
|
|
79
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.wrapper, ref: wrapperRef, style: { position: "relative" }, children: [
|
|
50
80
|
label && /* @__PURE__ */ jsx(
|
|
51
81
|
"label",
|
|
52
82
|
{
|
|
@@ -57,9 +87,31 @@ function DateField({
|
|
|
57
87
|
}
|
|
58
88
|
),
|
|
59
89
|
resolvedIcon ? /* @__PURE__ */ jsxs("div", { className: styles.inputIconWrapper, children: [
|
|
60
|
-
/* @__PURE__ */ jsx(
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
"button",
|
|
92
|
+
{
|
|
93
|
+
type: "button",
|
|
94
|
+
className: styles.fieldIconClickable,
|
|
95
|
+
onClick: openPicker,
|
|
96
|
+
disabled: disabled || readOnly,
|
|
97
|
+
"aria-label": "Open date picker",
|
|
98
|
+
tabIndex: -1,
|
|
99
|
+
children: resolvedIcon
|
|
100
|
+
}
|
|
101
|
+
),
|
|
61
102
|
inputEl
|
|
62
103
|
] }) : inputEl,
|
|
104
|
+
isOpen && /* @__PURE__ */ jsx(
|
|
105
|
+
DatePicker,
|
|
106
|
+
{
|
|
107
|
+
value: dateValue,
|
|
108
|
+
min,
|
|
109
|
+
max,
|
|
110
|
+
pickerId: `${name}-datepicker`,
|
|
111
|
+
onApply: handleApply,
|
|
112
|
+
onClose: handleClose
|
|
113
|
+
}
|
|
114
|
+
),
|
|
63
115
|
displayText && /* @__PURE__ */ jsx("span", { className: styles.helperText, "aria-hidden": "true", children: displayText }),
|
|
64
116
|
helperText && /* @__PURE__ */ jsx(
|
|
65
117
|
"span",
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const DatePicker_module = require("./DatePicker.module.css.cjs");
|
|
6
|
+
const WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
7
|
+
const MONTH_NAMES = [
|
|
8
|
+
"January",
|
|
9
|
+
"February",
|
|
10
|
+
"March",
|
|
11
|
+
"April",
|
|
12
|
+
"May",
|
|
13
|
+
"June",
|
|
14
|
+
"July",
|
|
15
|
+
"August",
|
|
16
|
+
"September",
|
|
17
|
+
"October",
|
|
18
|
+
"November",
|
|
19
|
+
"December"
|
|
20
|
+
];
|
|
21
|
+
function pad2(n) {
|
|
22
|
+
return String(n).padStart(2, "0");
|
|
23
|
+
}
|
|
24
|
+
function toISO(year, month, day) {
|
|
25
|
+
return `${year}-${pad2(month + 1)}-${pad2(day)}`;
|
|
26
|
+
}
|
|
27
|
+
function parseISO(iso) {
|
|
28
|
+
if (!iso) return null;
|
|
29
|
+
const parts = iso.split("-").map(Number);
|
|
30
|
+
if (parts.length < 3 || parts.some((p) => !Number.isFinite(p))) return null;
|
|
31
|
+
return { year: parts[0], month: parts[1] - 1, day: parts[2] };
|
|
32
|
+
}
|
|
33
|
+
function daysInMonth(year, month) {
|
|
34
|
+
return new Date(year, month + 1, 0).getDate();
|
|
35
|
+
}
|
|
36
|
+
function startDayOfWeek(year, month) {
|
|
37
|
+
return new Date(year, month, 1).getDay();
|
|
38
|
+
}
|
|
39
|
+
function isSameDay(a, b) {
|
|
40
|
+
return a.year === b.year && a.month === b.month && a.day === b.day;
|
|
41
|
+
}
|
|
42
|
+
function buildCalendarGrid(year, month) {
|
|
43
|
+
const cells = [];
|
|
44
|
+
const firstDow = startDayOfWeek(year, month);
|
|
45
|
+
const totalDays = daysInMonth(year, month);
|
|
46
|
+
if (firstDow > 0) {
|
|
47
|
+
const prevMonth = month === 0 ? 11 : month - 1;
|
|
48
|
+
const prevYear = month === 0 ? year - 1 : year;
|
|
49
|
+
const prevDays = daysInMonth(prevYear, prevMonth);
|
|
50
|
+
for (let i = firstDow - 1; i >= 0; i--) {
|
|
51
|
+
cells.push({ year: prevYear, month: prevMonth, day: prevDays - i, outside: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (let d = 1; d <= totalDays; d++) {
|
|
55
|
+
cells.push({ year, month, day: d, outside: false });
|
|
56
|
+
}
|
|
57
|
+
const nextMonth = month === 11 ? 0 : month + 1;
|
|
58
|
+
const nextYear = month === 11 ? year + 1 : year;
|
|
59
|
+
let nextDay = 1;
|
|
60
|
+
while (cells.length < 42) {
|
|
61
|
+
cells.push({ year: nextYear, month: nextMonth, day: nextDay++, outside: true });
|
|
62
|
+
}
|
|
63
|
+
return cells;
|
|
64
|
+
}
|
|
65
|
+
function DatePicker({
|
|
66
|
+
value,
|
|
67
|
+
min,
|
|
68
|
+
max,
|
|
69
|
+
pickerId,
|
|
70
|
+
onApply,
|
|
71
|
+
onClose
|
|
72
|
+
}) {
|
|
73
|
+
const parsed = parseISO(value);
|
|
74
|
+
const today = react.useMemo(() => {
|
|
75
|
+
const d = /* @__PURE__ */ new Date();
|
|
76
|
+
return { year: d.getFullYear(), month: d.getMonth(), day: d.getDate() };
|
|
77
|
+
}, []);
|
|
78
|
+
const [viewYear, setViewYear] = react.useState((parsed == null ? void 0 : parsed.year) ?? today.year);
|
|
79
|
+
const [viewMonth, setViewMonth] = react.useState((parsed == null ? void 0 : parsed.month) ?? today.month);
|
|
80
|
+
const minDate = react.useMemo(() => parseISO(min ?? ""), [min]);
|
|
81
|
+
const maxDate = react.useMemo(() => parseISO(max ?? ""), [max]);
|
|
82
|
+
const grid = react.useMemo(() => buildCalendarGrid(viewYear, viewMonth), [viewYear, viewMonth]);
|
|
83
|
+
const isDisabled = react.useCallback(
|
|
84
|
+
(cell) => {
|
|
85
|
+
const d = new Date(cell.year, cell.month, cell.day);
|
|
86
|
+
if (minDate) {
|
|
87
|
+
const minD = new Date(minDate.year, minDate.month, minDate.day);
|
|
88
|
+
if (d < minD) return true;
|
|
89
|
+
}
|
|
90
|
+
if (maxDate) {
|
|
91
|
+
const maxD = new Date(maxDate.year, maxDate.month, maxDate.day);
|
|
92
|
+
if (d > maxD) return true;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
},
|
|
96
|
+
[minDate, maxDate]
|
|
97
|
+
);
|
|
98
|
+
const prevMonth = react.useCallback(() => {
|
|
99
|
+
setViewMonth((m) => {
|
|
100
|
+
if (m === 0) {
|
|
101
|
+
setViewYear((y) => y - 1);
|
|
102
|
+
return 11;
|
|
103
|
+
}
|
|
104
|
+
return m - 1;
|
|
105
|
+
});
|
|
106
|
+
}, []);
|
|
107
|
+
const nextMonth = react.useCallback(() => {
|
|
108
|
+
setViewMonth((m) => {
|
|
109
|
+
if (m === 11) {
|
|
110
|
+
setViewYear((y) => y + 1);
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
return m + 1;
|
|
114
|
+
});
|
|
115
|
+
}, []);
|
|
116
|
+
const handleDayClick = react.useCallback(
|
|
117
|
+
(cell) => {
|
|
118
|
+
if (isDisabled(cell)) return;
|
|
119
|
+
onApply(toISO(cell.year, cell.month, cell.day));
|
|
120
|
+
},
|
|
121
|
+
[isDisabled, onApply]
|
|
122
|
+
);
|
|
123
|
+
const handleToday = react.useCallback(() => {
|
|
124
|
+
onApply(toISO(today.year, today.month, today.day));
|
|
125
|
+
}, [today, onApply]);
|
|
126
|
+
const handleClear = react.useCallback(() => {
|
|
127
|
+
onApply("");
|
|
128
|
+
}, [onApply]);
|
|
129
|
+
react.useEffect(() => {
|
|
130
|
+
const handleKeyDown = (e) => {
|
|
131
|
+
if (e.key === "Escape") onClose();
|
|
132
|
+
};
|
|
133
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
134
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
135
|
+
}, [onClose]);
|
|
136
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
137
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: DatePicker_module.default.overlay, onClick: onClose, "aria-hidden": "true" }),
|
|
138
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: DatePicker_module.default.picker, id: pickerId, "aria-label": "Date picker", children: [
|
|
139
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: DatePicker_module.default.header, children: [
|
|
140
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
141
|
+
"button",
|
|
142
|
+
{
|
|
143
|
+
type: "button",
|
|
144
|
+
className: DatePicker_module.default.navBtn,
|
|
145
|
+
onClick: prevMonth,
|
|
146
|
+
"aria-label": "Previous month",
|
|
147
|
+
children: "‹"
|
|
148
|
+
}
|
|
149
|
+
),
|
|
150
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: DatePicker_module.default.headerTitle, children: [
|
|
151
|
+
MONTH_NAMES[viewMonth],
|
|
152
|
+
" ",
|
|
153
|
+
viewYear
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: DatePicker_module.default.navBtn, onClick: nextMonth, "aria-label": "Next month", children: "›" })
|
|
156
|
+
] }),
|
|
157
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: DatePicker_module.default.weekdays, children: WEEKDAY_LABELS.map((wd) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: DatePicker_module.default.weekday, children: wd }, wd)) }),
|
|
158
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: DatePicker_module.default.grid, role: "radiogroup", "aria-label": "Calendar days", children: grid.map((cell) => {
|
|
159
|
+
const iso = toISO(cell.year, cell.month, cell.day);
|
|
160
|
+
const isSelected = parsed != null && isSameDay(cell, parsed);
|
|
161
|
+
const isToday = isSameDay(cell, today);
|
|
162
|
+
const dow = new Date(cell.year, cell.month, cell.day).getDay();
|
|
163
|
+
const isWeekend = dow === 0 || dow === 6;
|
|
164
|
+
const disabled = isDisabled(cell);
|
|
165
|
+
const cls = [
|
|
166
|
+
DatePicker_module.default.day,
|
|
167
|
+
cell.outside ? DatePicker_module.default.dayOutside : "",
|
|
168
|
+
isToday ? DatePicker_module.default.dayToday : "",
|
|
169
|
+
isSelected ? DatePicker_module.default.daySelected : "",
|
|
170
|
+
isWeekend && !isSelected ? DatePicker_module.default.dayWeekend : ""
|
|
171
|
+
].filter(Boolean).join(" ");
|
|
172
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
173
|
+
"button",
|
|
174
|
+
{
|
|
175
|
+
type: "button",
|
|
176
|
+
role: "radio",
|
|
177
|
+
"aria-checked": isSelected,
|
|
178
|
+
"aria-label": iso,
|
|
179
|
+
className: cls,
|
|
180
|
+
disabled,
|
|
181
|
+
onClick: () => handleDayClick(cell),
|
|
182
|
+
children: cell.day
|
|
183
|
+
},
|
|
184
|
+
iso + (cell.outside ? "-out" : "")
|
|
185
|
+
);
|
|
186
|
+
}) }),
|
|
187
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: DatePicker_module.default.footer, children: [
|
|
188
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: DatePicker_module.default.footerBtn, onClick: handleClear, children: "Clear" }),
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: DatePicker_module.default.footerBtn, onClick: handleToday, children: "Today" })
|
|
190
|
+
] })
|
|
191
|
+
] })
|
|
192
|
+
] });
|
|
193
|
+
}
|
|
194
|
+
exports.DatePicker = DatePicker;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface DatePickerProps {
|
|
2
|
+
value: string;
|
|
3
|
+
min?: string;
|
|
4
|
+
max?: string;
|
|
5
|
+
pickerId: string;
|
|
6
|
+
onApply: (isoDate: string) => void;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function DatePicker({ value, min, max, pickerId, onApply, onClose, }: Readonly<DatePickerProps>): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=DatePicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/fields/DateField/DatePicker.tsx"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAsFD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,GAAG,EACH,GAAG,EACH,QAAQ,EACR,OAAO,EACP,OAAO,GACR,EAAE,QAAQ,CAAC,eAAe,CAAC,2CA4J3B"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState, useCallback, useEffect } from "react";
|
|
3
|
+
import s from "./DatePicker.module.css.js";
|
|
4
|
+
const WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
5
|
+
const MONTH_NAMES = [
|
|
6
|
+
"January",
|
|
7
|
+
"February",
|
|
8
|
+
"March",
|
|
9
|
+
"April",
|
|
10
|
+
"May",
|
|
11
|
+
"June",
|
|
12
|
+
"July",
|
|
13
|
+
"August",
|
|
14
|
+
"September",
|
|
15
|
+
"October",
|
|
16
|
+
"November",
|
|
17
|
+
"December"
|
|
18
|
+
];
|
|
19
|
+
function pad2(n) {
|
|
20
|
+
return String(n).padStart(2, "0");
|
|
21
|
+
}
|
|
22
|
+
function toISO(year, month, day) {
|
|
23
|
+
return `${year}-${pad2(month + 1)}-${pad2(day)}`;
|
|
24
|
+
}
|
|
25
|
+
function parseISO(iso) {
|
|
26
|
+
if (!iso) return null;
|
|
27
|
+
const parts = iso.split("-").map(Number);
|
|
28
|
+
if (parts.length < 3 || parts.some((p) => !Number.isFinite(p))) return null;
|
|
29
|
+
return { year: parts[0], month: parts[1] - 1, day: parts[2] };
|
|
30
|
+
}
|
|
31
|
+
function daysInMonth(year, month) {
|
|
32
|
+
return new Date(year, month + 1, 0).getDate();
|
|
33
|
+
}
|
|
34
|
+
function startDayOfWeek(year, month) {
|
|
35
|
+
return new Date(year, month, 1).getDay();
|
|
36
|
+
}
|
|
37
|
+
function isSameDay(a, b) {
|
|
38
|
+
return a.year === b.year && a.month === b.month && a.day === b.day;
|
|
39
|
+
}
|
|
40
|
+
function buildCalendarGrid(year, month) {
|
|
41
|
+
const cells = [];
|
|
42
|
+
const firstDow = startDayOfWeek(year, month);
|
|
43
|
+
const totalDays = daysInMonth(year, month);
|
|
44
|
+
if (firstDow > 0) {
|
|
45
|
+
const prevMonth = month === 0 ? 11 : month - 1;
|
|
46
|
+
const prevYear = month === 0 ? year - 1 : year;
|
|
47
|
+
const prevDays = daysInMonth(prevYear, prevMonth);
|
|
48
|
+
for (let i = firstDow - 1; i >= 0; i--) {
|
|
49
|
+
cells.push({ year: prevYear, month: prevMonth, day: prevDays - i, outside: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (let d = 1; d <= totalDays; d++) {
|
|
53
|
+
cells.push({ year, month, day: d, outside: false });
|
|
54
|
+
}
|
|
55
|
+
const nextMonth = month === 11 ? 0 : month + 1;
|
|
56
|
+
const nextYear = month === 11 ? year + 1 : year;
|
|
57
|
+
let nextDay = 1;
|
|
58
|
+
while (cells.length < 42) {
|
|
59
|
+
cells.push({ year: nextYear, month: nextMonth, day: nextDay++, outside: true });
|
|
60
|
+
}
|
|
61
|
+
return cells;
|
|
62
|
+
}
|
|
63
|
+
function DatePicker({
|
|
64
|
+
value,
|
|
65
|
+
min,
|
|
66
|
+
max,
|
|
67
|
+
pickerId,
|
|
68
|
+
onApply,
|
|
69
|
+
onClose
|
|
70
|
+
}) {
|
|
71
|
+
const parsed = parseISO(value);
|
|
72
|
+
const today = useMemo(() => {
|
|
73
|
+
const d = /* @__PURE__ */ new Date();
|
|
74
|
+
return { year: d.getFullYear(), month: d.getMonth(), day: d.getDate() };
|
|
75
|
+
}, []);
|
|
76
|
+
const [viewYear, setViewYear] = useState((parsed == null ? void 0 : parsed.year) ?? today.year);
|
|
77
|
+
const [viewMonth, setViewMonth] = useState((parsed == null ? void 0 : parsed.month) ?? today.month);
|
|
78
|
+
const minDate = useMemo(() => parseISO(min ?? ""), [min]);
|
|
79
|
+
const maxDate = useMemo(() => parseISO(max ?? ""), [max]);
|
|
80
|
+
const grid = useMemo(() => buildCalendarGrid(viewYear, viewMonth), [viewYear, viewMonth]);
|
|
81
|
+
const isDisabled = useCallback(
|
|
82
|
+
(cell) => {
|
|
83
|
+
const d = new Date(cell.year, cell.month, cell.day);
|
|
84
|
+
if (minDate) {
|
|
85
|
+
const minD = new Date(minDate.year, minDate.month, minDate.day);
|
|
86
|
+
if (d < minD) return true;
|
|
87
|
+
}
|
|
88
|
+
if (maxDate) {
|
|
89
|
+
const maxD = new Date(maxDate.year, maxDate.month, maxDate.day);
|
|
90
|
+
if (d > maxD) return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
},
|
|
94
|
+
[minDate, maxDate]
|
|
95
|
+
);
|
|
96
|
+
const prevMonth = useCallback(() => {
|
|
97
|
+
setViewMonth((m) => {
|
|
98
|
+
if (m === 0) {
|
|
99
|
+
setViewYear((y) => y - 1);
|
|
100
|
+
return 11;
|
|
101
|
+
}
|
|
102
|
+
return m - 1;
|
|
103
|
+
});
|
|
104
|
+
}, []);
|
|
105
|
+
const nextMonth = useCallback(() => {
|
|
106
|
+
setViewMonth((m) => {
|
|
107
|
+
if (m === 11) {
|
|
108
|
+
setViewYear((y) => y + 1);
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
return m + 1;
|
|
112
|
+
});
|
|
113
|
+
}, []);
|
|
114
|
+
const handleDayClick = useCallback(
|
|
115
|
+
(cell) => {
|
|
116
|
+
if (isDisabled(cell)) return;
|
|
117
|
+
onApply(toISO(cell.year, cell.month, cell.day));
|
|
118
|
+
},
|
|
119
|
+
[isDisabled, onApply]
|
|
120
|
+
);
|
|
121
|
+
const handleToday = useCallback(() => {
|
|
122
|
+
onApply(toISO(today.year, today.month, today.day));
|
|
123
|
+
}, [today, onApply]);
|
|
124
|
+
const handleClear = useCallback(() => {
|
|
125
|
+
onApply("");
|
|
126
|
+
}, [onApply]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const handleKeyDown = (e) => {
|
|
129
|
+
if (e.key === "Escape") onClose();
|
|
130
|
+
};
|
|
131
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
132
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
133
|
+
}, [onClose]);
|
|
134
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
135
|
+
/* @__PURE__ */ jsx("div", { className: s.overlay, onClick: onClose, "aria-hidden": "true" }),
|
|
136
|
+
/* @__PURE__ */ jsxs("div", { className: s.picker, id: pickerId, "aria-label": "Date picker", children: [
|
|
137
|
+
/* @__PURE__ */ jsxs("div", { className: s.header, children: [
|
|
138
|
+
/* @__PURE__ */ jsx(
|
|
139
|
+
"button",
|
|
140
|
+
{
|
|
141
|
+
type: "button",
|
|
142
|
+
className: s.navBtn,
|
|
143
|
+
onClick: prevMonth,
|
|
144
|
+
"aria-label": "Previous month",
|
|
145
|
+
children: "‹"
|
|
146
|
+
}
|
|
147
|
+
),
|
|
148
|
+
/* @__PURE__ */ jsxs("span", { className: s.headerTitle, children: [
|
|
149
|
+
MONTH_NAMES[viewMonth],
|
|
150
|
+
" ",
|
|
151
|
+
viewYear
|
|
152
|
+
] }),
|
|
153
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: s.navBtn, onClick: nextMonth, "aria-label": "Next month", children: "›" })
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ jsx("div", { className: s.weekdays, children: WEEKDAY_LABELS.map((wd) => /* @__PURE__ */ jsx("div", { className: s.weekday, children: wd }, wd)) }),
|
|
156
|
+
/* @__PURE__ */ jsx("div", { className: s.grid, role: "radiogroup", "aria-label": "Calendar days", children: grid.map((cell) => {
|
|
157
|
+
const iso = toISO(cell.year, cell.month, cell.day);
|
|
158
|
+
const isSelected = parsed != null && isSameDay(cell, parsed);
|
|
159
|
+
const isToday = isSameDay(cell, today);
|
|
160
|
+
const dow = new Date(cell.year, cell.month, cell.day).getDay();
|
|
161
|
+
const isWeekend = dow === 0 || dow === 6;
|
|
162
|
+
const disabled = isDisabled(cell);
|
|
163
|
+
const cls = [
|
|
164
|
+
s.day,
|
|
165
|
+
cell.outside ? s.dayOutside : "",
|
|
166
|
+
isToday ? s.dayToday : "",
|
|
167
|
+
isSelected ? s.daySelected : "",
|
|
168
|
+
isWeekend && !isSelected ? s.dayWeekend : ""
|
|
169
|
+
].filter(Boolean).join(" ");
|
|
170
|
+
return /* @__PURE__ */ jsx(
|
|
171
|
+
"button",
|
|
172
|
+
{
|
|
173
|
+
type: "button",
|
|
174
|
+
role: "radio",
|
|
175
|
+
"aria-checked": isSelected,
|
|
176
|
+
"aria-label": iso,
|
|
177
|
+
className: cls,
|
|
178
|
+
disabled,
|
|
179
|
+
onClick: () => handleDayClick(cell),
|
|
180
|
+
children: cell.day
|
|
181
|
+
},
|
|
182
|
+
iso + (cell.outside ? "-out" : "")
|
|
183
|
+
);
|
|
184
|
+
}) }),
|
|
185
|
+
/* @__PURE__ */ jsxs("div", { className: s.footer, children: [
|
|
186
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: s.footerBtn, onClick: handleClear, children: "Clear" }),
|
|
187
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: s.footerBtn, onClick: handleToday, children: "Today" })
|
|
188
|
+
] })
|
|
189
|
+
] })
|
|
190
|
+
] });
|
|
191
|
+
}
|
|
192
|
+
export {
|
|
193
|
+
DatePicker
|
|
194
|
+
};
|