@axenstudio/axen-form 0.1.10 → 0.1.11

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.
Files changed (85) hide show
  1. package/README.md +783 -270
  2. package/dist/core/dateUtils.cjs +7 -3
  3. package/dist/core/dateUtils.d.ts +1 -1
  4. package/dist/core/dateUtils.d.ts.map +1 -1
  5. package/dist/core/dateUtils.js +7 -3
  6. package/dist/core/validation.cjs +4 -1
  7. package/dist/core/validation.d.ts.map +1 -1
  8. package/dist/core/validation.js +4 -1
  9. package/dist/fields/AutocompleteField/AutocompleteField.cjs +32 -32
  10. package/dist/fields/AutocompleteField/AutocompleteField.js +32 -32
  11. package/dist/fields/AutocompleteField/AutocompleteField.module.css.cjs +34 -13
  12. package/dist/fields/AutocompleteField/AutocompleteField.module.css.js +34 -13
  13. package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.cjs +90 -88
  14. package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.d.ts.map +1 -1
  15. package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.js +90 -88
  16. package/dist/fields/AutocompletePredictField/AutocompletePredictField.cjs +81 -64
  17. package/dist/fields/AutocompletePredictField/AutocompletePredictField.d.ts.map +1 -1
  18. package/dist/fields/AutocompletePredictField/AutocompletePredictField.js +81 -64
  19. package/dist/fields/CheckboxField/CheckboxField.module.css.cjs +12 -0
  20. package/dist/fields/CheckboxField/CheckboxField.module.css.js +12 -0
  21. package/dist/fields/ColorField/ColorField.module.css.cjs +12 -0
  22. package/dist/fields/ColorField/ColorField.module.css.js +12 -0
  23. package/dist/fields/DateField/DateField.cjs +56 -4
  24. package/dist/fields/DateField/DateField.d.ts +1 -1
  25. package/dist/fields/DateField/DateField.d.ts.map +1 -1
  26. package/dist/fields/DateField/DateField.js +56 -4
  27. package/dist/fields/DateField/DatePicker.cjs +194 -0
  28. package/dist/fields/DateField/DatePicker.d.ts +11 -0
  29. package/dist/fields/DateField/DatePicker.d.ts.map +1 -0
  30. package/dist/fields/DateField/DatePicker.js +194 -0
  31. package/dist/fields/DateField/DatePicker.module.css.cjs +62 -0
  32. package/dist/fields/DateField/DatePicker.module.css.js +62 -0
  33. package/dist/fields/DateTimeField/DateTimeField.cjs +126 -8
  34. package/dist/fields/DateTimeField/DateTimeField.d.ts +2 -1
  35. package/dist/fields/DateTimeField/DateTimeField.d.ts.map +1 -1
  36. package/dist/fields/DateTimeField/DateTimeField.js +126 -8
  37. package/dist/fields/PasswordField/PasswordField.module.css.cjs +12 -0
  38. package/dist/fields/PasswordField/PasswordField.module.css.js +12 -0
  39. package/dist/fields/RadioField/RadioField.module.css.cjs +12 -0
  40. package/dist/fields/RadioField/RadioField.module.css.js +12 -0
  41. package/dist/fields/SliderField/SliderField.module.css.cjs +15 -3
  42. package/dist/fields/SliderField/SliderField.module.css.js +15 -3
  43. package/dist/fields/SwitchField/SwitchField.module.css.cjs +12 -0
  44. package/dist/fields/SwitchField/SwitchField.module.css.js +12 -0
  45. package/dist/fields/TimeField/TimeField.cjs +60 -5
  46. package/dist/fields/TimeField/TimeField.d.ts +2 -1
  47. package/dist/fields/TimeField/TimeField.d.ts.map +1 -1
  48. package/dist/fields/TimeField/TimeField.js +60 -5
  49. package/dist/fields/TimeField/TimePicker.cjs +182 -0
  50. package/dist/fields/TimeField/TimePicker.d.ts +11 -0
  51. package/dist/fields/TimeField/TimePicker.d.ts.map +1 -0
  52. package/dist/fields/TimeField/TimePicker.js +182 -0
  53. package/dist/fields/TimeField/TimePicker.module.css.cjs +50 -0
  54. package/dist/fields/TimeField/TimePicker.module.css.js +50 -0
  55. package/dist/fields/defaultPlaceholders.cjs +1 -0
  56. package/dist/fields/defaultPlaceholders.d.ts.map +1 -1
  57. package/dist/fields/defaultPlaceholders.js +1 -0
  58. package/dist/fields/fieldBase.module.css.cjs +366 -18
  59. package/dist/fields/fieldBase.module.css.js +366 -18
  60. package/dist/fields/icons.cjs +2 -0
  61. package/dist/fields/icons.d.ts +4 -0
  62. package/dist/fields/icons.d.ts.map +1 -1
  63. package/dist/fields/icons.js +2 -0
  64. package/dist/index.cjs +230 -3
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +230 -3
  68. package/dist/layout/Box/Box.module.css.cjs +12 -0
  69. package/dist/layout/Box/Box.module.css.js +12 -0
  70. package/dist/layout/Divider/Divider.module.css.cjs +12 -0
  71. package/dist/layout/Divider/Divider.module.css.js +12 -0
  72. package/dist/layout/Grid/Grid.module.css.cjs +12 -0
  73. package/dist/layout/Grid/Grid.module.css.js +12 -0
  74. package/dist/layout/Stack/Stack.module.css.cjs +12 -0
  75. package/dist/layout/Stack/Stack.module.css.js +12 -0
  76. package/dist/react/AxenForm.cjs +228 -3
  77. package/dist/react/AxenForm.js +228 -3
  78. package/dist/react/fieldGroup.module.css.cjs +12 -0
  79. package/dist/react/fieldGroup.module.css.js +12 -0
  80. package/dist/react/gridCell.module.css.cjs +12 -0
  81. package/dist/react/gridCell.module.css.js +12 -0
  82. package/dist/types.d.ts +1 -0
  83. package/dist/types.d.ts.map +1 -1
  84. package/package.json +4 -9
  85. package/dist/axen-form.css +0 -1057
@@ -1,3 +1,15 @@
1
+ (function() {
2
+ "use strict";
3
+ try {
4
+ if (typeof document != "undefined") {
5
+ var elementStyle = document.createElement("style");
6
+ elementStyle.appendChild(document.createTextNode("._switchLabel_1itxk_1 {\n cursor: pointer;\n}\n\n._switchTrack_1itxk_5 {\n position: relative;\n display: inline-flex;\n align-items: center;\n width: 44px;\n height: 24px;\n background-color: var(--axen-color-border);\n border-radius: var(--axen-border-radius-pill);\n transition: background-color var(--axen-transition-fast);\n flex-shrink: 0;\n}\n\n._switchInput_1itxk_17 {\n position: absolute;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n opacity: 0;\n cursor: pointer;\n}\n\n._switchInput_1itxk_17:checked + ._switchThumb_1itxk_27 {\n transform: translateX(20px);\n}\n\n._switchInput_1itxk_17:checked ~ ._switchThumb_1itxk_27 {\n transform: translateX(20px);\n}\n\n._switchTrack_1itxk_5:has(._switchInput_1itxk_17:checked) {\n background-color: var(--axen-color-primary);\n}\n\n._switchInput_1itxk_17:focus-visible ~ ._switchThumb_1itxk_27 {\n box-shadow: var(--axen-shadow-focus);\n}\n\n._switchInput_1itxk_17:disabled {\n cursor: not-allowed;\n}\n\n._switchTrack_1itxk_5:has(._switchInput_1itxk_17:disabled) {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n._switchThumb_1itxk_27 {\n position: absolute;\n left: 2px;\n width: 20px;\n height: 20px;\n background-color: var(--axen-color-bg);\n border-radius: 50%;\n box-shadow: var(--axen-shadow-sm);\n transition: transform var(--axen-transition-fast);\n pointer-events: none;\n}\n\n/* Mobile: larger touch target */\n@media (max-width: 599px) {\n ._switchTrack_1itxk_5 {\n width: 52px;\n height: 28px;\n }\n\n ._switchThumb_1itxk_27 {\n width: 24px;\n height: 24px;\n }\n\n ._switchInput_1itxk_17:checked ~ ._switchThumb_1itxk_27 {\n transform: translateX(24px);\n }\n}"));
7
+ document.head.appendChild(elementStyle);
8
+ }
9
+ } catch (e) {
10
+ console.error("vite-plugin-css-injected-by-js", e);
11
+ }
12
+ })();
1
13
  const switchLabel = "_switchLabel_1itxk_1";
2
14
  const switchTrack = "_switchTrack_1itxk_5";
3
15
  const switchInput = "_switchInput_1itxk_17";
@@ -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 TimePicker = require("./TimePicker.cjs");
8
10
  function TimeField({
9
11
  name,
10
12
  value,
@@ -19,16 +21,46 @@ function TimeField({
19
21
  required,
20
22
  locale,
21
23
  ampm,
22
- icon
24
+ icon,
25
+ form,
26
+ showSeconds
23
27
  }) {
28
+ const [isOpen, setIsOpen] = react.useState(false);
29
+ const wrapperRef = react.useRef(null);
24
30
  const aria = fieldUtils.ariaProps(name, { error, helperText, required, label });
25
31
  const resolvedLocale = locale ?? dateUtils.getDefaultLocale();
26
32
  const resolvedIcon = iconUtils.resolveFieldIcon(icon, "time");
33
+ const resolvedShowSeconds = showSeconds === true;
27
34
  const timeValue = typeof value === "string" ? value : "";
28
- const displayText = timeValue ? dateUtils.formatTimeLocale(timeValue, resolvedLocale, ampm) : "";
35
+ const displayText = timeValue ? dateUtils.formatTimeLocale(timeValue, resolvedLocale, ampm, resolvedShowSeconds) : "";
29
36
  const handleChange = (e) => {
30
37
  onChange(e);
31
38
  };
39
+ const openPicker = react.useCallback(() => {
40
+ if (!disabled && !readOnly) setIsOpen(true);
41
+ }, [disabled, readOnly]);
42
+ const handleApply = react.useCallback(
43
+ (isoTime) => {
44
+ form.setFieldValue(name, isoTime);
45
+ setIsOpen(false);
46
+ },
47
+ [form, name]
48
+ );
49
+ const handleClose = react.useCallback(() => {
50
+ setIsOpen(false);
51
+ }, []);
52
+ const handleBlur = react.useCallback(
53
+ (e) => {
54
+ setTimeout(() => {
55
+ var _a;
56
+ if (!((_a = wrapperRef.current) == null ? void 0 : _a.contains(document.activeElement))) {
57
+ setIsOpen(false);
58
+ onBlur(e);
59
+ }
60
+ }, 150);
61
+ },
62
+ [onBlur]
63
+ );
32
64
  const inputEl = /* @__PURE__ */ jsxRuntime.jsx(
33
65
  "input",
34
66
  {
@@ -36,8 +68,9 @@ function TimeField({
36
68
  type: "time",
37
69
  name,
38
70
  value: timeValue,
71
+ step: resolvedShowSeconds ? 1 : 60,
39
72
  onChange: handleChange,
40
- onBlur,
73
+ onBlur: handleBlur,
41
74
  placeholder,
42
75
  disabled,
43
76
  readOnly,
@@ -45,7 +78,7 @@ function TimeField({
45
78
  className: fieldUtils.cx(fieldBase_module.default.input, resolvedIcon && fieldBase_module.default.inputWithIcon, error && fieldBase_module.default.inputError)
46
79
  }
47
80
  );
48
- 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: [
49
82
  label && /* @__PURE__ */ jsxRuntime.jsx(
50
83
  "label",
51
84
  {
@@ -56,9 +89,31 @@ function TimeField({
56
89
  }
57
90
  ),
58
91
  resolvedIcon ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: fieldBase_module.default.inputIconWrapper, children: [
59
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: fieldBase_module.default.fieldIcon, children: resolvedIcon }),
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 time picker",
100
+ tabIndex: -1,
101
+ children: resolvedIcon
102
+ }
103
+ ),
60
104
  inputEl
61
105
  ] }) : inputEl,
106
+ isOpen && /* @__PURE__ */ jsxRuntime.jsx(
107
+ TimePicker.TimePicker,
108
+ {
109
+ value: timeValue,
110
+ showSeconds: resolvedShowSeconds,
111
+ ampm: ampm === true,
112
+ pickerId: `${name}-timepicker`,
113
+ onApply: handleApply,
114
+ onClose: handleClose
115
+ }
116
+ ),
62
117
  displayText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: fieldBase_module.default.helperText, "aria-hidden": "true", children: displayText }),
63
118
  helperText && /* @__PURE__ */ jsxRuntime.jsx(
64
119
  "span",
@@ -1,6 +1,7 @@
1
1
  import { FieldComponentProps } from '../../types';
2
- export declare function TimeField({ name, value, onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, locale, ampm, icon, }: Readonly<FieldComponentProps & {
2
+ export declare function TimeField({ name, value, onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, locale, ampm, icon, form, showSeconds, }: Readonly<FieldComponentProps & {
3
3
  locale?: string;
4
4
  ampm?: boolean;
5
+ showSeconds?: boolean;
5
6
  }>): import("react/jsx-runtime").JSX.Element;
6
7
  //# sourceMappingURL=TimeField.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TimeField.d.ts","sourceRoot":"","sources":["../../../src/fields/TimeField/TimeField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKvD,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,MAAM,EACN,IAAI,EACJ,IAAI,GACL,EAAE,QAAQ,CAAC,mBAAmB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,2CA+DrE"}
1
+ {"version":3,"file":"TimeField.d.ts","sourceRoot":"","sources":["../../../src/fields/TimeField/TimeField.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,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,WAAW,GACZ,EAAE,QAAQ,CAAC,mBAAmB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,2CAuH5F"}
@@ -1,8 +1,10 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useRef, useCallback } from "react";
2
3
  import { formatTimeLocale, 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 { TimePicker } from "./TimePicker.js";
6
8
  function TimeField({
7
9
  name,
8
10
  value,
@@ -17,16 +19,46 @@ function TimeField({
17
19
  required,
18
20
  locale,
19
21
  ampm,
20
- icon
22
+ icon,
23
+ form,
24
+ showSeconds
21
25
  }) {
26
+ const [isOpen, setIsOpen] = useState(false);
27
+ const wrapperRef = useRef(null);
22
28
  const aria = ariaProps(name, { error, helperText, required, label });
23
29
  const resolvedLocale = locale ?? getDefaultLocale();
24
30
  const resolvedIcon = resolveFieldIcon(icon, "time");
31
+ const resolvedShowSeconds = showSeconds === true;
25
32
  const timeValue = typeof value === "string" ? value : "";
26
- const displayText = timeValue ? formatTimeLocale(timeValue, resolvedLocale, ampm) : "";
33
+ const displayText = timeValue ? formatTimeLocale(timeValue, resolvedLocale, ampm, resolvedShowSeconds) : "";
27
34
  const handleChange = (e) => {
28
35
  onChange(e);
29
36
  };
37
+ const openPicker = useCallback(() => {
38
+ if (!disabled && !readOnly) setIsOpen(true);
39
+ }, [disabled, readOnly]);
40
+ const handleApply = useCallback(
41
+ (isoTime) => {
42
+ form.setFieldValue(name, isoTime);
43
+ setIsOpen(false);
44
+ },
45
+ [form, name]
46
+ );
47
+ const handleClose = useCallback(() => {
48
+ setIsOpen(false);
49
+ }, []);
50
+ const handleBlur = useCallback(
51
+ (e) => {
52
+ setTimeout(() => {
53
+ var _a;
54
+ if (!((_a = wrapperRef.current) == null ? void 0 : _a.contains(document.activeElement))) {
55
+ setIsOpen(false);
56
+ onBlur(e);
57
+ }
58
+ }, 150);
59
+ },
60
+ [onBlur]
61
+ );
30
62
  const inputEl = /* @__PURE__ */ jsx(
31
63
  "input",
32
64
  {
@@ -34,8 +66,9 @@ function TimeField({
34
66
  type: "time",
35
67
  name,
36
68
  value: timeValue,
69
+ step: resolvedShowSeconds ? 1 : 60,
37
70
  onChange: handleChange,
38
- onBlur,
71
+ onBlur: handleBlur,
39
72
  placeholder,
40
73
  disabled,
41
74
  readOnly,
@@ -43,7 +76,7 @@ function TimeField({
43
76
  className: cx(styles.input, resolvedIcon && styles.inputWithIcon, error && styles.inputError)
44
77
  }
45
78
  );
46
- return /* @__PURE__ */ jsxs("div", { className: styles.wrapper, children: [
79
+ return /* @__PURE__ */ jsxs("div", { className: styles.wrapper, ref: wrapperRef, style: { position: "relative" }, children: [
47
80
  label && /* @__PURE__ */ jsx(
48
81
  "label",
49
82
  {
@@ -54,9 +87,31 @@ function TimeField({
54
87
  }
55
88
  ),
56
89
  resolvedIcon ? /* @__PURE__ */ jsxs("div", { className: styles.inputIconWrapper, children: [
57
- /* @__PURE__ */ jsx("span", { className: styles.fieldIcon, children: resolvedIcon }),
90
+ /* @__PURE__ */ jsx(
91
+ "button",
92
+ {
93
+ type: "button",
94
+ className: styles.fieldIconClickable,
95
+ onClick: openPicker,
96
+ disabled: disabled || readOnly,
97
+ "aria-label": "Open time picker",
98
+ tabIndex: -1,
99
+ children: resolvedIcon
100
+ }
101
+ ),
58
102
  inputEl
59
103
  ] }) : inputEl,
104
+ isOpen && /* @__PURE__ */ jsx(
105
+ TimePicker,
106
+ {
107
+ value: timeValue,
108
+ showSeconds: resolvedShowSeconds,
109
+ ampm: ampm === true,
110
+ pickerId: `${name}-timepicker`,
111
+ onApply: handleApply,
112
+ onClose: handleClose
113
+ }
114
+ ),
60
115
  displayText && /* @__PURE__ */ jsx("span", { className: styles.helperText, "aria-hidden": "true", children: displayText }),
61
116
  helperText && /* @__PURE__ */ jsx(
62
117
  "span",
@@ -0,0 +1,182 @@
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 TimePicker_module = require("./TimePicker.module.css.cjs");
6
+ function pad2(n) {
7
+ return String(n).padStart(2, "0");
8
+ }
9
+ function parseTime(iso) {
10
+ if (!iso) {
11
+ const now = /* @__PURE__ */ new Date();
12
+ return { h: now.getHours(), m: now.getMinutes(), s: now.getSeconds() };
13
+ }
14
+ const parts = iso.split(":").map(Number);
15
+ return {
16
+ h: Number.isFinite(parts[0]) ? parts[0] : 0,
17
+ m: Number.isFinite(parts[1]) ? parts[1] : 0,
18
+ s: Number.isFinite(parts[2]) ? parts[2] : 0
19
+ };
20
+ }
21
+ function to12h(h24) {
22
+ const period = h24 >= 12 ? "PM" : "AM";
23
+ const h12 = h24 % 12 || 12;
24
+ return { h12, period };
25
+ }
26
+ function to24h(h12, period) {
27
+ if (period === "AM") return h12 === 12 ? 0 : h12;
28
+ return h12 === 12 ? 12 : h12 + 12;
29
+ }
30
+ function range(count) {
31
+ return Array.from({ length: count }, (_, i) => i);
32
+ }
33
+ function Column({ label, items, selected, onSelect, ariaLabel }) {
34
+ const columnRef = react.useRef(null);
35
+ const selectedRef = react.useRef(null);
36
+ react.useEffect(() => {
37
+ var _a;
38
+ (_a = selectedRef.current) == null ? void 0 : _a.scrollIntoView({ block: "nearest" });
39
+ }, [selected]);
40
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
41
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: TimePicker_module.default.columnLabel, children: label }),
42
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: TimePicker_module.default.column, ref: columnRef, role: "radiogroup", "aria-label": ariaLabel, children: items.map((n) => {
43
+ const isSelected = n === selected;
44
+ return /* @__PURE__ */ jsxRuntime.jsx(
45
+ "button",
46
+ {
47
+ ref: isSelected ? selectedRef : void 0,
48
+ type: "button",
49
+ role: "radio",
50
+ "aria-checked": isSelected,
51
+ className: `${TimePicker_module.default.cell} ${isSelected ? TimePicker_module.default.cellSelected : ""}`,
52
+ onClick: () => onSelect(n),
53
+ children: pad2(n)
54
+ },
55
+ n
56
+ );
57
+ }) })
58
+ ] });
59
+ }
60
+ function TimePicker({
61
+ value,
62
+ showSeconds,
63
+ ampm,
64
+ pickerId,
65
+ onApply,
66
+ onClose
67
+ }) {
68
+ const initial = parseTime(value);
69
+ const [hour24, setHour24] = react.useState(initial.h);
70
+ const [minute, setMinute] = react.useState(initial.m);
71
+ const [second, setSecond] = react.useState(initial.s);
72
+ const [period, setPeriod] = react.useState(() => to12h(initial.h).period);
73
+ const pickerRef = react.useRef(null);
74
+ const displayHour = ampm ? to12h(hour24).h12 : hour24;
75
+ const hourItems = ampm ? range(12).map((i) => i + 1) : range(24);
76
+ const handleHourSelect = react.useCallback(
77
+ (h) => {
78
+ if (ampm) {
79
+ setHour24(to24h(h, period));
80
+ } else {
81
+ setHour24(h);
82
+ }
83
+ },
84
+ [ampm, period]
85
+ );
86
+ const handlePeriodToggle = react.useCallback(
87
+ (p) => {
88
+ setPeriod(p);
89
+ const h12 = to12h(hour24).h12;
90
+ setHour24(to24h(h12, p));
91
+ },
92
+ [hour24]
93
+ );
94
+ const handleNow = react.useCallback(() => {
95
+ const now = /* @__PURE__ */ new Date();
96
+ setHour24(now.getHours());
97
+ setMinute(now.getMinutes());
98
+ setSecond(now.getSeconds());
99
+ setPeriod(now.getHours() >= 12 ? "PM" : "AM");
100
+ }, []);
101
+ const handleApply = react.useCallback(() => {
102
+ const parts = [pad2(hour24), pad2(minute)];
103
+ if (showSeconds) parts.push(pad2(second));
104
+ onApply(parts.join(":"));
105
+ }, [hour24, minute, second, showSeconds, onApply]);
106
+ react.useEffect(() => {
107
+ const handleKeyDown = (e) => {
108
+ if (e.key === "Escape") onClose();
109
+ };
110
+ document.addEventListener("keydown", handleKeyDown);
111
+ return () => document.removeEventListener("keydown", handleKeyDown);
112
+ }, [onClose]);
113
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
114
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: TimePicker_module.default.overlay, onClick: onClose, "aria-hidden": "true" }),
115
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: TimePicker_module.default.picker, ref: pickerRef, id: pickerId, "aria-label": "Time picker", children: [
116
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: TimePicker_module.default.columns, children: [
117
+ /* @__PURE__ */ jsxRuntime.jsx(
118
+ Column,
119
+ {
120
+ label: "HH",
121
+ items: hourItems,
122
+ selected: displayHour,
123
+ onSelect: handleHourSelect,
124
+ ariaLabel: "Hours"
125
+ }
126
+ ),
127
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: TimePicker_module.default.separator, "aria-hidden": "true", children: ":" }),
128
+ /* @__PURE__ */ jsxRuntime.jsx(
129
+ Column,
130
+ {
131
+ label: "MM",
132
+ items: range(60),
133
+ selected: minute,
134
+ onSelect: setMinute,
135
+ ariaLabel: "Minutes"
136
+ }
137
+ ),
138
+ showSeconds && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
139
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: TimePicker_module.default.separator, "aria-hidden": "true", children: ":" }),
140
+ /* @__PURE__ */ jsxRuntime.jsx(
141
+ Column,
142
+ {
143
+ label: "SS",
144
+ items: range(60),
145
+ selected: second,
146
+ onSelect: setSecond,
147
+ ariaLabel: "Seconds"
148
+ }
149
+ )
150
+ ] }),
151
+ ampm && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
152
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: TimePicker_module.default.columnLabel, children: "AP" }),
153
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: TimePicker_module.default.column, role: "radiogroup", "aria-label": "AM/PM", children: ["AM", "PM"].map((p) => /* @__PURE__ */ jsxRuntime.jsx(
154
+ "button",
155
+ {
156
+ type: "button",
157
+ role: "radio",
158
+ "aria-checked": p === period,
159
+ className: `${TimePicker_module.default.cell} ${p === period ? TimePicker_module.default.cellSelected : ""}`,
160
+ onClick: () => handlePeriodToggle(p),
161
+ children: p
162
+ },
163
+ p
164
+ )) })
165
+ ] })
166
+ ] }),
167
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: TimePicker_module.default.footer, children: [
168
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: TimePicker_module.default.footerBtn, onClick: handleNow, children: "Now" }),
169
+ /* @__PURE__ */ jsxRuntime.jsx(
170
+ "button",
171
+ {
172
+ type: "button",
173
+ className: `${TimePicker_module.default.footerBtn} ${TimePicker_module.default.footerBtnPrimary}`,
174
+ onClick: handleApply,
175
+ children: "Apply"
176
+ }
177
+ )
178
+ ] })
179
+ ] })
180
+ ] });
181
+ }
182
+ exports.TimePicker = TimePicker;
@@ -0,0 +1,11 @@
1
+ interface TimePickerProps {
2
+ value: string;
3
+ showSeconds: boolean;
4
+ ampm: boolean;
5
+ pickerId: string;
6
+ onApply: (isoTime: string) => void;
7
+ onClose: () => void;
8
+ }
9
+ export declare function TimePicker({ value, showSeconds, ampm, pickerId, onApply, onClose, }: Readonly<TimePickerProps>): import("react/jsx-runtime").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=TimePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimePicker.d.ts","sourceRoot":"","sources":["../../../src/fields/TimeField/TimePicker.tsx"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAmFD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,WAAW,EACX,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,OAAO,GACR,EAAE,QAAQ,CAAC,eAAe,CAAC,2CAiI3B"}
@@ -0,0 +1,182 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { useState, useRef, useCallback, useEffect } from "react";
3
+ import s from "./TimePicker.module.css.js";
4
+ function pad2(n) {
5
+ return String(n).padStart(2, "0");
6
+ }
7
+ function parseTime(iso) {
8
+ if (!iso) {
9
+ const now = /* @__PURE__ */ new Date();
10
+ return { h: now.getHours(), m: now.getMinutes(), s: now.getSeconds() };
11
+ }
12
+ const parts = iso.split(":").map(Number);
13
+ return {
14
+ h: Number.isFinite(parts[0]) ? parts[0] : 0,
15
+ m: Number.isFinite(parts[1]) ? parts[1] : 0,
16
+ s: Number.isFinite(parts[2]) ? parts[2] : 0
17
+ };
18
+ }
19
+ function to12h(h24) {
20
+ const period = h24 >= 12 ? "PM" : "AM";
21
+ const h12 = h24 % 12 || 12;
22
+ return { h12, period };
23
+ }
24
+ function to24h(h12, period) {
25
+ if (period === "AM") return h12 === 12 ? 0 : h12;
26
+ return h12 === 12 ? 12 : h12 + 12;
27
+ }
28
+ function range(count) {
29
+ return Array.from({ length: count }, (_, i) => i);
30
+ }
31
+ function Column({ label, items, selected, onSelect, ariaLabel }) {
32
+ const columnRef = useRef(null);
33
+ const selectedRef = useRef(null);
34
+ useEffect(() => {
35
+ var _a;
36
+ (_a = selectedRef.current) == null ? void 0 : _a.scrollIntoView({ block: "nearest" });
37
+ }, [selected]);
38
+ return /* @__PURE__ */ jsxs("div", { children: [
39
+ /* @__PURE__ */ jsx("div", { className: s.columnLabel, children: label }),
40
+ /* @__PURE__ */ jsx("div", { className: s.column, ref: columnRef, role: "radiogroup", "aria-label": ariaLabel, children: items.map((n) => {
41
+ const isSelected = n === selected;
42
+ return /* @__PURE__ */ jsx(
43
+ "button",
44
+ {
45
+ ref: isSelected ? selectedRef : void 0,
46
+ type: "button",
47
+ role: "radio",
48
+ "aria-checked": isSelected,
49
+ className: `${s.cell} ${isSelected ? s.cellSelected : ""}`,
50
+ onClick: () => onSelect(n),
51
+ children: pad2(n)
52
+ },
53
+ n
54
+ );
55
+ }) })
56
+ ] });
57
+ }
58
+ function TimePicker({
59
+ value,
60
+ showSeconds,
61
+ ampm,
62
+ pickerId,
63
+ onApply,
64
+ onClose
65
+ }) {
66
+ const initial = parseTime(value);
67
+ const [hour24, setHour24] = useState(initial.h);
68
+ const [minute, setMinute] = useState(initial.m);
69
+ const [second, setSecond] = useState(initial.s);
70
+ const [period, setPeriod] = useState(() => to12h(initial.h).period);
71
+ const pickerRef = useRef(null);
72
+ const displayHour = ampm ? to12h(hour24).h12 : hour24;
73
+ const hourItems = ampm ? range(12).map((i) => i + 1) : range(24);
74
+ const handleHourSelect = useCallback(
75
+ (h) => {
76
+ if (ampm) {
77
+ setHour24(to24h(h, period));
78
+ } else {
79
+ setHour24(h);
80
+ }
81
+ },
82
+ [ampm, period]
83
+ );
84
+ const handlePeriodToggle = useCallback(
85
+ (p) => {
86
+ setPeriod(p);
87
+ const h12 = to12h(hour24).h12;
88
+ setHour24(to24h(h12, p));
89
+ },
90
+ [hour24]
91
+ );
92
+ const handleNow = useCallback(() => {
93
+ const now = /* @__PURE__ */ new Date();
94
+ setHour24(now.getHours());
95
+ setMinute(now.getMinutes());
96
+ setSecond(now.getSeconds());
97
+ setPeriod(now.getHours() >= 12 ? "PM" : "AM");
98
+ }, []);
99
+ const handleApply = useCallback(() => {
100
+ const parts = [pad2(hour24), pad2(minute)];
101
+ if (showSeconds) parts.push(pad2(second));
102
+ onApply(parts.join(":"));
103
+ }, [hour24, minute, second, showSeconds, onApply]);
104
+ useEffect(() => {
105
+ const handleKeyDown = (e) => {
106
+ if (e.key === "Escape") onClose();
107
+ };
108
+ document.addEventListener("keydown", handleKeyDown);
109
+ return () => document.removeEventListener("keydown", handleKeyDown);
110
+ }, [onClose]);
111
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
112
+ /* @__PURE__ */ jsx("div", { className: s.overlay, onClick: onClose, "aria-hidden": "true" }),
113
+ /* @__PURE__ */ jsxs("div", { className: s.picker, ref: pickerRef, id: pickerId, "aria-label": "Time picker", children: [
114
+ /* @__PURE__ */ jsxs("div", { className: s.columns, children: [
115
+ /* @__PURE__ */ jsx(
116
+ Column,
117
+ {
118
+ label: "HH",
119
+ items: hourItems,
120
+ selected: displayHour,
121
+ onSelect: handleHourSelect,
122
+ ariaLabel: "Hours"
123
+ }
124
+ ),
125
+ /* @__PURE__ */ jsx("div", { className: s.separator, "aria-hidden": "true", children: ":" }),
126
+ /* @__PURE__ */ jsx(
127
+ Column,
128
+ {
129
+ label: "MM",
130
+ items: range(60),
131
+ selected: minute,
132
+ onSelect: setMinute,
133
+ ariaLabel: "Minutes"
134
+ }
135
+ ),
136
+ showSeconds && /* @__PURE__ */ jsxs(Fragment, { children: [
137
+ /* @__PURE__ */ jsx("div", { className: s.separator, "aria-hidden": "true", children: ":" }),
138
+ /* @__PURE__ */ jsx(
139
+ Column,
140
+ {
141
+ label: "SS",
142
+ items: range(60),
143
+ selected: second,
144
+ onSelect: setSecond,
145
+ ariaLabel: "Seconds"
146
+ }
147
+ )
148
+ ] }),
149
+ ampm && /* @__PURE__ */ jsxs("div", { children: [
150
+ /* @__PURE__ */ jsx("div", { className: s.columnLabel, children: "AP" }),
151
+ /* @__PURE__ */ jsx("div", { className: s.column, role: "radiogroup", "aria-label": "AM/PM", children: ["AM", "PM"].map((p) => /* @__PURE__ */ jsx(
152
+ "button",
153
+ {
154
+ type: "button",
155
+ role: "radio",
156
+ "aria-checked": p === period,
157
+ className: `${s.cell} ${p === period ? s.cellSelected : ""}`,
158
+ onClick: () => handlePeriodToggle(p),
159
+ children: p
160
+ },
161
+ p
162
+ )) })
163
+ ] })
164
+ ] }),
165
+ /* @__PURE__ */ jsxs("div", { className: s.footer, children: [
166
+ /* @__PURE__ */ jsx("button", { type: "button", className: s.footerBtn, onClick: handleNow, children: "Now" }),
167
+ /* @__PURE__ */ jsx(
168
+ "button",
169
+ {
170
+ type: "button",
171
+ className: `${s.footerBtn} ${s.footerBtnPrimary}`,
172
+ onClick: handleApply,
173
+ children: "Apply"
174
+ }
175
+ )
176
+ ] })
177
+ ] })
178
+ ] });
179
+ }
180
+ export {
181
+ TimePicker
182
+ };