@elcrm/form 0.0.63 → 0.0.65

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 (94) hide show
  1. package/README.md +139 -0
  2. package/dist/Form.d.ts +20 -0
  3. package/dist/Form.js +11 -0
  4. package/dist/FormInitContext.d.ts +25 -0
  5. package/dist/FormInitContext.js +20 -0
  6. package/dist/assets/styles/Field.module.css +1 -0
  7. package/dist/assets/styles/Form.module.css +1 -0
  8. package/dist/assets/styles/Select.module.css +1 -0
  9. package/dist/core/Field.d.ts +19 -0
  10. package/dist/core/Field.js +33 -0
  11. package/dist/events/onEvent.d.ts +55 -0
  12. package/dist/events/onEvent.js +67 -0
  13. package/dist/fields/Date.d.ts +13 -0
  14. package/dist/fields/Date.js +352 -0
  15. package/dist/fields/Input.d.ts +12 -0
  16. package/dist/fields/Input.js +127 -0
  17. package/dist/fields/Mask.d.ts +9 -0
  18. package/dist/fields/Mask.js +101 -0
  19. package/dist/fields/Modal.d.ts +25 -0
  20. package/dist/fields/Modal.js +62 -0
  21. package/dist/fields/Money.d.ts +14 -0
  22. package/dist/fields/Money.js +100 -0
  23. package/dist/fields/Numeric.d.ts +9 -0
  24. package/dist/fields/Numeric.js +73 -0
  25. package/dist/fields/Options.d.ts +32 -0
  26. package/dist/fields/Options.js +69 -0
  27. package/dist/fields/Password.d.ts +6 -0
  28. package/dist/fields/Password.js +153 -0
  29. package/dist/fields/Phone.d.ts +7 -0
  30. package/dist/fields/Phone.js +112 -0
  31. package/dist/fields/Range.d.ts +16 -0
  32. package/dist/fields/Range.js +103 -0
  33. package/dist/fields/Select.d.ts +21 -0
  34. package/dist/fields/Select.js +146 -0
  35. package/dist/fields/Textarea.d.ts +7 -0
  36. package/dist/fields/Textarea.js +52 -0
  37. package/dist/fields/Time.d.ts +26 -0
  38. package/dist/fields/Time.js +254 -0
  39. package/dist/fields/type.d.ts +77 -0
  40. package/dist/fields/visibility.d.ts +6 -0
  41. package/dist/fields/visibility.js +6 -0
  42. package/dist/hooks/use.d.ts +41 -0
  43. package/dist/hooks/use.js +57 -0
  44. package/dist/index.d.ts +26 -2
  45. package/dist/index.js +26 -0
  46. package/dist/mask/MaskPhone.d.ts +8 -0
  47. package/dist/mask/MaskPhone.js +1384 -0
  48. package/dist/package.js +96 -0
  49. package/dist/styles/Field.module.js +22 -0
  50. package/dist/styles/Form.module.js +12 -0
  51. package/dist/styles/Select.module.js +8 -0
  52. package/package.json +72 -11
  53. package/src/lib/styles/Field.module.scss +315 -0
  54. package/src/lib/styles/Form.module.scss +313 -0
  55. package/src/lib/styles/Select.module.scss +90 -0
  56. package/src/lib/styles/dark.css +47 -0
  57. package/src/lib/styles/light.css +51 -0
  58. package/dist/index.css +0 -1
  59. package/dist/index.es.js +0 -5045
  60. package/dist/index.umd.js +0 -89
  61. package/dist/src/lib/Captcha.d.ts +0 -1
  62. package/dist/src/lib/Check.d.ts +0 -13
  63. package/dist/src/lib/Code.d.ts +0 -22
  64. package/dist/src/lib/Color.d.ts +0 -22
  65. package/dist/src/lib/Field.d.ts +0 -13
  66. package/dist/src/lib/Files.d.ts +0 -20
  67. package/dist/src/lib/Generator.d.ts +0 -1
  68. package/dist/src/lib/Group.d.ts +0 -21
  69. package/dist/src/lib/Image.d.ts +0 -22
  70. package/dist/src/lib/Init.d.ts +0 -1
  71. package/dist/src/lib/MaskPhone.d.ts +0 -2
  72. package/dist/src/lib/Message.d.ts +0 -15
  73. package/dist/src/lib/Money.d.ts +0 -22
  74. package/dist/src/lib/Month.d.ts +0 -18
  75. package/dist/src/lib/Palette.d.ts +0 -9
  76. package/dist/src/lib/Progress.d.ts +0 -9
  77. package/dist/src/lib/Toogle.d.ts +0 -8
  78. package/dist/src/lib/Users.d.ts +0 -1
  79. package/dist/src/lib/_Time.d.ts +0 -0
  80. package/dist/src/lib/fields/Date.d.ts +0 -18
  81. package/dist/src/lib/fields/Input.d.ts +0 -27
  82. package/dist/src/lib/fields/Mask.d.ts +0 -19
  83. package/dist/src/lib/fields/Modal.d.ts +0 -19
  84. package/dist/src/lib/fields/Numeric.d.ts +0 -6
  85. package/dist/src/lib/fields/Options.d.ts +0 -22
  86. package/dist/src/lib/fields/Password.d.ts +0 -23
  87. package/dist/src/lib/fields/Phone.d.ts +0 -21
  88. package/dist/src/lib/fields/Select copy.d.ts +0 -23
  89. package/dist/src/lib/fields/Select.d.ts +0 -28
  90. package/dist/src/lib/fields/Textarea.d.ts +0 -23
  91. package/dist/src/lib/fields/type.d.ts +0 -30
  92. package/dist/src/lib/index.d.ts +0 -16
  93. package/dist/src/lib/onEvent.d.ts +0 -2
  94. package/dist/src/lib/use.d.ts +0 -10
@@ -0,0 +1,103 @@
1
+ import e from "../styles/Field.module.js";
2
+ import t from "../core/Field.js";
3
+ import { useFormFieldState as n } from "../hooks/use.js";
4
+ import r from "react";
5
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
6
+ //#region src/lib/fields/Range.tsx
7
+ function o(e, t, n) {
8
+ let r = e;
9
+ return Number.isFinite(r) || (r = t), Math.min(n, Math.max(t, r));
10
+ }
11
+ function s(e, t, n, r) {
12
+ return o(!Number.isFinite(r) || r <= 0 ? e : t + Math.round((e - t) / r) * r, t, n);
13
+ }
14
+ function c({ value: c = 0, form: l, onValue: u, onBlur: d, name: f = "", placeholder: p = "", label: m = "", error: h = "", hidden: g = !1, disabled: _ = !1, className: v = "", after: y = "", before: b = "", min: x = 0, max: S = 100, step: C = 1 }) {
15
+ let { value: w } = n(l, f, c), T = o(Number(w), x, S), E = S === x ? 0 : (T - x) / (S - x) * 100, D = E >= 99.999, O = !_, k = r.useRef(null), A = r.useCallback((e) => {
16
+ let t = {
17
+ value: s(e, x, S, C),
18
+ name: f || ""
19
+ };
20
+ l && f && l.setValue(t), u && u !== l?.onValue && u(t);
21
+ }, [
22
+ l,
23
+ f,
24
+ u,
25
+ x,
26
+ S,
27
+ C
28
+ ]), j = r.useCallback((e) => {
29
+ let t = k.current;
30
+ if (!t) return x;
31
+ let n = t.getBoundingClientRect(), r = n.width || 1;
32
+ return s(x + (e - n.left) / r * (S - x), x, S, C);
33
+ }, [
34
+ x,
35
+ S,
36
+ C
37
+ ]), M = (e) => {
38
+ O && (e.preventDefault(), e.currentTarget.setPointerCapture(e.pointerId), A(j(e.clientX)));
39
+ }, N = (e) => {
40
+ O && e.currentTarget.hasPointerCapture(e.pointerId) && A(j(e.clientX));
41
+ }, P = (e) => {
42
+ try {
43
+ e.currentTarget.releasePointerCapture(e.pointerId);
44
+ } catch {}
45
+ }, F = (e) => {
46
+ if (!O) return;
47
+ let t = T;
48
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") t = T + C;
49
+ else if (e.key === "ArrowLeft" || e.key === "ArrowDown") t = T - C;
50
+ else if (e.key === "Home") t = x;
51
+ else if (e.key === "End") t = S;
52
+ else if (e.key === "PageUp") t = T + C * 10;
53
+ else if (e.key === "PageDown") t = T - C * 10;
54
+ else return;
55
+ e.preventDefault(), A(s(t, x, S, C));
56
+ }, I = () => {
57
+ if (!d || !f) return;
58
+ let e = l && f ? l.getValue(f, c) : T;
59
+ d({
60
+ value: o(Number(e), x, S),
61
+ name: f
62
+ });
63
+ };
64
+ return g && (w == null || w === "") ? null : /* @__PURE__ */ a(t, {
65
+ type: "range",
66
+ label: m,
67
+ error: h,
68
+ element_blok: { className: [v || ""] },
69
+ element_input: {
70
+ className: [e.w, e.range],
71
+ props: { "data-disabled": _ || !O ? "true" : void 0 }
72
+ },
73
+ children: [
74
+ b,
75
+ /* @__PURE__ */ i("div", { children: /* @__PURE__ */ i("div", {
76
+ ref: k,
77
+ className: e.rangeTrack,
78
+ role: "slider",
79
+ tabIndex: O ? 0 : -1,
80
+ "aria-label": m || p || "Диапазон",
81
+ "aria-valuemin": x,
82
+ "aria-valuemax": S,
83
+ "aria-valuenow": T,
84
+ "data-disabled": _ || !O ? "true" : void 0,
85
+ onPointerDown: M,
86
+ onPointerMove: N,
87
+ onPointerUp: P,
88
+ onPointerCancel: P,
89
+ onKeyDown: F,
90
+ onBlur: I,
91
+ children: /* @__PURE__ */ i("div", {
92
+ className: D ? `${e.rangeFill} ${e.rangeFillFull}` : e.rangeFill,
93
+ style: { width: `${E}%` },
94
+ "aria-hidden": !0
95
+ })
96
+ }) }),
97
+ y
98
+ ]
99
+ });
100
+ }
101
+ c.displayName = "RangeField";
102
+ //#endregion
103
+ export { c as default };
@@ -0,0 +1,21 @@
1
+ import { TInput } from './type';
2
+ type SelectOptionObject = Record<number, {
3
+ n: string;
4
+ s: number;
5
+ }>;
6
+ type SelectOptionArrayItem = {
7
+ id?: number;
8
+ i?: number;
9
+ n: string;
10
+ s?: number;
11
+ };
12
+ type SelectOptions = SelectOptionObject | SelectOptionArrayItem[];
13
+ export type TSelect = TInput & {
14
+ options?: SelectOptions;
15
+ order?: number[];
16
+ };
17
+ declare function SelectField({ value, form, onValue, name, placeholder, label, error, hidden, disabled, className, after, before, options, order, }: TSelect): import("react/jsx-runtime").JSX.Element | "";
18
+ declare namespace SelectField {
19
+ var displayName: string;
20
+ }
21
+ export default SelectField;
@@ -0,0 +1,146 @@
1
+ import e from "../core/Field.js";
2
+ import { useFormFieldState as t } from "../hooks/use.js";
3
+ import n from "../styles/Select.module.js";
4
+ import r from "react";
5
+ import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
6
+ import { createPortal as s } from "react-dom";
7
+ //#region src/lib/fields/Select.tsx
8
+ function c({ value: c = 0, form: u, onValue: d, name: f = "", placeholder: p = "", label: m, error: h, hidden: g = !1, disabled: _ = !1, className: v, after: y = "", before: b = "", options: x, order: S }) {
9
+ let [C, w] = r.useState(!1), [T, E] = r.useState(0), [D, O] = r.useState(null), k = r.useId(), { value: A } = t(u, f, c ?? 0), j = Number(A), M = [];
10
+ x && (M = Array.isArray(x) ? x.map((e) => ({
11
+ id: e.id ?? e.i ?? 0,
12
+ label: e.n || "",
13
+ hidden: e.s === 0
14
+ })).filter((e) => e.id !== 0 && e.label.length > 0) : Object.keys(x).map((e) => {
15
+ let t = Number(e), n = x[t];
16
+ return {
17
+ id: t,
18
+ label: n?.n || "",
19
+ hidden: n?.s === 0
20
+ };
21
+ }).filter((e) => e.label.length > 0));
22
+ let N = (() => {
23
+ if (!S || S.length === 0) return M;
24
+ let e = new Map(M.map((e) => [e.id, e]));
25
+ return S.map((t) => e.get(t)).filter((e) => !!e);
26
+ })().filter((e) => !e.hidden), P = (e) => N.find((t) => t.id === e)?.label, F = C && N.length > 0 ? `${k}-opt-${T}` : void 0, I = (e) => {
27
+ let t = {
28
+ value: e,
29
+ name: f
30
+ };
31
+ u && f && u.onValue(t), d && d !== u?.onValue && d(t);
32
+ }, L = (e) => {
33
+ if (N.length === 0 || _) return;
34
+ let t = N.findIndex((e) => e.id === j);
35
+ O(e.getBoundingClientRect()), E(t >= 0 ? t : 0), w(!0);
36
+ }, R = (e) => {
37
+ L(e.currentTarget);
38
+ }, z = (e) => {
39
+ if (!_ && N.length !== 0) {
40
+ if (!C && (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ")) {
41
+ e.preventDefault(), L(e.currentTarget);
42
+ return;
43
+ }
44
+ if (C) {
45
+ if (e.key === "Escape") {
46
+ e.preventDefault(), w(!1);
47
+ return;
48
+ }
49
+ if (e.key === "ArrowDown") {
50
+ e.preventDefault(), E((e) => (e + 1) % N.length);
51
+ return;
52
+ }
53
+ if (e.key === "ArrowUp") {
54
+ e.preventDefault(), E((e) => e - 1 < 0 ? N.length - 1 : e - 1);
55
+ return;
56
+ }
57
+ if (e.key === "Enter") {
58
+ e.preventDefault();
59
+ let t = N[T];
60
+ t && I(t.id), w(!1);
61
+ }
62
+ }
63
+ }
64
+ }, B = {};
65
+ p && (B.placeholder = p);
66
+ let V = j === 0 || P(j) === void 0;
67
+ return g && V ? "" : /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ o(e, {
68
+ type: "select",
69
+ label: m,
70
+ element_blok: { className: [v || ""] },
71
+ error: h,
72
+ element_input: {
73
+ className: [n.f],
74
+ props: {
75
+ onClick: R,
76
+ onKeyDown: z,
77
+ tabIndex: _ ? -1 : 0,
78
+ role: "combobox",
79
+ "aria-expanded": C,
80
+ "aria-haspopup": "listbox",
81
+ "aria-controls": k,
82
+ ...F ? { "aria-activedescendant": F } : {},
83
+ "aria-disabled": _,
84
+ "data-disabled": _
85
+ }
86
+ },
87
+ children: [
88
+ b,
89
+ /* @__PURE__ */ a("div", {
90
+ ...B,
91
+ children: P(j) || p || ""
92
+ }),
93
+ y
94
+ ]
95
+ }), C && s(/* @__PURE__ */ a(l, {
96
+ parent: D || void 0,
97
+ onValue: I,
98
+ className: v,
99
+ items: N,
100
+ setOpen: w,
101
+ listId: k,
102
+ activeIndex: T,
103
+ currentValue: j
104
+ }), document.body)] });
105
+ }
106
+ c.displayName = "SelectField";
107
+ var l = ({ onValue: e, parent: t, className: r, items: s, setOpen: c, listId: l, activeIndex: d, currentValue: f }) => {
108
+ if (!t) return null;
109
+ let p = () => {
110
+ c(!1);
111
+ }, m = (t) => {
112
+ c(!1), e(t);
113
+ }, h = window.innerHeight - t.bottom < 306 ? Math.max(6, t.top - 300 - 6) : Math.min(window.innerHeight - 300 - 6, t.bottom + 6), g = Math.max(t.width, 260), _ = Math.min(Math.max(6, t.left), window.innerWidth - g - 6);
114
+ return /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a("div", {
115
+ className: n.o,
116
+ onClick: p,
117
+ "aria-hidden": !0
118
+ }), /* @__PURE__ */ a("div", {
119
+ className: [n.l, r].filter((e) => e !== void 0).join(" "),
120
+ style: {
121
+ top: h + "px",
122
+ left: _ + "px",
123
+ width: g
124
+ },
125
+ children: /* @__PURE__ */ a(u, {
126
+ listId: l,
127
+ items: s,
128
+ link: m,
129
+ activeIndex: d,
130
+ currentValue: f
131
+ })
132
+ })] });
133
+ }, u = ({ listId: e, items: t, link: n, activeIndex: r, currentValue: i }) => /* @__PURE__ */ a("ul", {
134
+ id: e,
135
+ role: "listbox",
136
+ children: t.map((t, o) => /* @__PURE__ */ a("li", {
137
+ id: `${e}-opt-${o}`,
138
+ role: "option",
139
+ "aria-selected": i === t.id,
140
+ "data-focus": r === o ? "true" : "false",
141
+ onClick: () => n(t.id),
142
+ children: t.label
143
+ }, t.id))
144
+ });
145
+ //#endregion
146
+ export { c as default };
@@ -0,0 +1,7 @@
1
+ import { TInput } from './type';
2
+ /** TextareaField — многострочное поле (contentEditable). */
3
+ declare function TextareaField({ value, form, onValue, name, placeholder, label, hidden, disabled, onBlur: onBlurField, className, after, before, error, inputmode, maxLength, spellCheck, }: TInput): import("react/jsx-runtime").JSX.Element;
4
+ declare namespace TextareaField {
5
+ var displayName: string;
6
+ }
7
+ export default TextareaField;
@@ -0,0 +1,52 @@
1
+ import e from "../styles/Field.module.js";
2
+ import t from "../core/Field.js";
3
+ import { useFormFieldState as n } from "../hooks/use.js";
4
+ import r, { asEditableClipboard as i } from "../events/onEvent.js";
5
+ import { isEmptyTextLike as a } from "./visibility.js";
6
+ import "react";
7
+ import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
8
+ //#region src/lib/fields/Textarea.tsx
9
+ function l({ value: l, form: u, onValue: d, name: f = "", placeholder: p = "", label: m = "", hidden: h = !1, disabled: g = !1, onBlur: _, className: v = "", after: y = "", before: b = "", error: x = "", inputmode: S = "text", maxLength: C, spellCheck: w = !0 }) {
10
+ let { value: T, version: E } = n(u, f || "", l), D = !g, O = (e) => {
11
+ let t = {
12
+ value: e.currentTarget.innerText,
13
+ name: f || ""
14
+ };
15
+ u && f && u.onValue(t), d && d !== u?.onValue && d(t);
16
+ }, k = (e) => {
17
+ let t = (e.currentTarget.textContent ?? "").trim();
18
+ C && t.length >= C && e.key !== "Backspace" && e.preventDefault(), e.key === "Enter" && !e.shiftKey && e.preventDefault();
19
+ }, A = (e) => r.Paste(C || 0, i(e));
20
+ if (h && a(T)) return /* @__PURE__ */ s(o, {});
21
+ let j = {};
22
+ return p && (j.placeholder = p), D && (j.edit = ""), j.inputMode = S || "text", /* @__PURE__ */ c(t, {
23
+ type: "note",
24
+ label: m,
25
+ error: x,
26
+ element_blok: { className: [v || ""] },
27
+ element_input: { className: [e.n] },
28
+ children: [
29
+ b,
30
+ /* @__PURE__ */ s("div", {
31
+ onPaste: A,
32
+ onInput: O,
33
+ onBlur: (e) => {
34
+ r.Blur(e), _?.({
35
+ value: (e.currentTarget.textContent ?? "").trim(),
36
+ name: f || ""
37
+ });
38
+ },
39
+ onKeyDown: k,
40
+ spellCheck: w,
41
+ contentEditable: D ? "plaintext-only" : !1,
42
+ suppressContentEditableWarning: !0,
43
+ ...j,
44
+ children: T
45
+ }, E),
46
+ y
47
+ ]
48
+ });
49
+ }
50
+ l.displayName = "TextareaField";
51
+ //#endregion
52
+ export { l as default };
@@ -0,0 +1,26 @@
1
+ import { TInput } from './type';
2
+ /** Значение в форме: строка `HH:mm` или пустая строка */
3
+ export type TTime = TInput & {
4
+ /** Шаг для минут в секундах (`60` — любая минута; `300` — кратно 5 мин) */
5
+ step?: number;
6
+ /** Минимальное время `HH:mm` */
7
+ min?: string;
8
+ /** Максимальное время `HH:mm` */
9
+ max?: string;
10
+ /**
11
+ * Выравнивание пары «часы : минуты» внутри поля:
12
+ * `start` — слева (как у даты), `center` — по центру капсулы.
13
+ * @default 'start'
14
+ */
15
+ align?: "start" | "center";
16
+ };
17
+ /**
18
+ * Время: два contentEditable по образцу Mask (каретка через queueMicrotask + setCursorPosition).
19
+ * Пока фокус в группе — в DOM только введённые цифры без ведущих нулей; в форму значение
20
+ * уходит при уходе фокуса с группы (snap минут, clamp, формат `HH:mm`).
21
+ */
22
+ declare function TimeField({ value, form, onValue: _onValue, onBlur: onBlurField, name, placeholder, label, hidden, disabled, error, className, after, before, step, min, max, id, spellCheck, align, }: TTime): import("react/jsx-runtime").JSX.Element | null;
23
+ declare namespace TimeField {
24
+ var displayName: string;
25
+ }
26
+ export default TimeField;
@@ -0,0 +1,254 @@
1
+ import e from "../styles/Field.module.js";
2
+ import t from "../core/Field.js";
3
+ import { useFormFieldState as n } from "../hooks/use.js";
4
+ import r from "../events/onEvent.js";
5
+ import i from "react";
6
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
7
+ //#region src/lib/fields/Time.tsx
8
+ function s(e) {
9
+ let t = window.getSelection();
10
+ if (!t || t.rangeCount === 0) return 0;
11
+ let n = t.getRangeAt(0);
12
+ if (!e.contains(n.startContainer)) return 0;
13
+ let r = document.createRange();
14
+ return r.selectNodeContents(e), r.setEnd(n.startContainer, n.startOffset), r.toString().length;
15
+ }
16
+ function c(e, t) {
17
+ let n = t.firstChild;
18
+ if (!n || n.nodeType !== Node.TEXT_NODE) return;
19
+ let r = n.textContent?.length ?? 0, i = Math.max(0, Math.min(e, r)), a = document.createRange(), o = window.getSelection();
20
+ o && (a.setStart(n, i), a.collapse(!0), o.removeAllRanges(), o.addRange(a), t.focus());
21
+ }
22
+ function l(e) {
23
+ return String(e).padStart(2, "0");
24
+ }
25
+ function u(e) {
26
+ return (e || "").replace(/\D/g, "");
27
+ }
28
+ function d(e) {
29
+ if (e == null || e === "" || typeof e != "string") return "";
30
+ let t = e.trim(), n = /^(\d{1,2}):(\d{2})(?::(\d{2}))?$/.exec(t);
31
+ if (!n) return "";
32
+ let r = Math.min(23, Math.max(0, parseInt(n[1], 10))), i = Math.min(59, Math.max(0, parseInt(n[2], 10)));
33
+ return `${l(r)}:${l(i)}`;
34
+ }
35
+ function f(e) {
36
+ let t = d(e);
37
+ if (!t) return {
38
+ h: null,
39
+ m: null
40
+ };
41
+ let [n, r] = t.split(":").map((e) => parseInt(e, 10));
42
+ return {
43
+ h: n,
44
+ m: r
45
+ };
46
+ }
47
+ function p(e, t) {
48
+ if (e === null && t === null) return "";
49
+ let n = e ?? 0, r = t ?? 0;
50
+ return `${l(n)}:${l(r)}`;
51
+ }
52
+ function m(e, t) {
53
+ let n = u(e).slice(0, 2);
54
+ if (n === "") return null;
55
+ let r = parseInt(n, 10);
56
+ return Number.isNaN(r) ? null : Math.min(t, Math.max(0, r));
57
+ }
58
+ function h(e) {
59
+ let t = d(e);
60
+ if (!t) return null;
61
+ let [n, r] = t.split(":").map((e) => parseInt(e, 10));
62
+ return n * 60 + r;
63
+ }
64
+ function g(e, t) {
65
+ let n = Math.max(1, Math.round(t / 60));
66
+ if (n <= 1) return e;
67
+ let r = Math.round(e / n) * n;
68
+ return Math.min(59, Math.max(0, r));
69
+ }
70
+ function _(e, t, n) {
71
+ let r = d(e);
72
+ if (!r) return "";
73
+ let i = h(r);
74
+ if (i === null) return "";
75
+ if (t) {
76
+ let e = h(t);
77
+ e !== null && (i = Math.max(i, e));
78
+ }
79
+ if (n) {
80
+ let e = h(n);
81
+ e !== null && (i = Math.min(i, e));
82
+ }
83
+ return p(Math.floor(i / 60), i % 60);
84
+ }
85
+ function v({ value: h = "", form: v, onValue: ee, onBlur: y, name: b = "", placeholder: x = "", label: S = "", hidden: C = !1, disabled: w = !1, error: T = "", className: E = "", after: D = "", before: te = "", step: O = 60, min: k, max: A, id: j, spellCheck: M = !1, align: N = "start" }) {
86
+ let { value: P, version: F } = n(v, b, h), I = d(P), L = !w, R = i.useRef(null), z = i.useRef(null), B = i.useRef(null), [V, H] = i.useState(!1);
87
+ i.useLayoutEffect(() => {
88
+ if (!V || !L) return;
89
+ let { h: e, m: t } = f(I), n = (e, t) => {
90
+ e && (t === null ? e.innerHTML = "<br>" : e.textContent = String(t));
91
+ };
92
+ n(R.current, e), n(z.current, t);
93
+ }, [
94
+ V,
95
+ I,
96
+ F,
97
+ L
98
+ ]);
99
+ let U = i.useCallback(() => {
100
+ let e = m(R.current?.textContent || "", 23), t = m(z.current?.textContent || "", 59);
101
+ if (e === null && t === null) return "";
102
+ let n = p(e, t);
103
+ if (!n) return "";
104
+ let { h: r, m: i } = f(n);
105
+ return i !== null && (n = p(r, g(i, O))), n &&= _(n, k, A), n || "";
106
+ }, [
107
+ k,
108
+ A,
109
+ O
110
+ ]), W = (e) => (t) => {
111
+ if (t.preventDefault(), !L) return;
112
+ let n = u(t.clipboardData?.getData("text/plain") ?? ""), r = e === "hour" ? 23 : 59;
113
+ if (n.length >= 4) {
114
+ let e = Math.min(23, Math.max(0, parseInt(n.slice(0, 2), 10))), t = Math.min(59, Math.max(0, parseInt(n.slice(2, 4), 10))), r = R.current, i = z.current;
115
+ r && (r.textContent = String(e), queueMicrotask(() => c(String(e).length, r))), i && (i.textContent = String(t), queueMicrotask(() => c(String(t).length, i)));
116
+ return;
117
+ }
118
+ let i = n.slice(0, 2), a = t.currentTarget;
119
+ if (i === "") {
120
+ a.innerHTML = "<br>", queueMicrotask(() => c(0, a));
121
+ return;
122
+ }
123
+ let o = parseInt(i, 10);
124
+ if (Number.isNaN(o)) return;
125
+ let s = String(Math.min(r, Math.max(0, o)));
126
+ a.textContent = s, queueMicrotask(() => c(s.length, a));
127
+ }, G = (e) => {
128
+ let t = e.target, n = t.textContent || "", r = s(t), i = n.slice(0, r).replace(/\D/g, "").length, a = u(n).slice(0, 2);
129
+ if (a === "") {
130
+ t.innerHTML = "<br>", queueMicrotask(() => c(0, t));
131
+ return;
132
+ }
133
+ let o = parseInt(a, 10);
134
+ if (Number.isNaN(o)) return;
135
+ let l = String(Math.min(23, Math.max(0, o)));
136
+ t.textContent = l;
137
+ let d = Math.min(i, l.length);
138
+ queueMicrotask(() => c(d, t));
139
+ }, K = (e) => {
140
+ let t = e.target, n = t.textContent || "", r = s(t), i = n.slice(0, r).replace(/\D/g, "").length, a = u(n).slice(0, 2);
141
+ if (a === "") {
142
+ t.innerHTML = "<br>", queueMicrotask(() => c(0, t));
143
+ return;
144
+ }
145
+ let o = parseInt(a, 10);
146
+ if (Number.isNaN(o)) return;
147
+ let l = String(Math.min(59, Math.max(0, o)));
148
+ t.textContent = l;
149
+ let d = Math.min(i, l.length);
150
+ queueMicrotask(() => c(d, t));
151
+ }, q = (e) => {
152
+ (e.key === "Enter" || e.key === ":") && (e.preventDefault(), z.current?.focus());
153
+ }, J = (e) => {
154
+ e.key === "Enter" && e.preventDefault();
155
+ }, Y = (e) => {
156
+ let t = e.currentTarget;
157
+ (t.textContent || "").trim().length || (t.innerHTML = "<br>"), L && H(!0);
158
+ }, X = (e) => {
159
+ r.Blur(e);
160
+ };
161
+ i.useEffect(() => {
162
+ let e = B.current;
163
+ if (!e) return;
164
+ let t = (t) => {
165
+ let n = t.relatedTarget;
166
+ if (n && e.contains(n)) return;
167
+ let r = U();
168
+ v && b && v.setValue({
169
+ name: b,
170
+ value: r
171
+ }), y && b && y({
172
+ value: r,
173
+ name: b
174
+ }), H(!1);
175
+ };
176
+ return e.addEventListener("focusout", t), () => e.removeEventListener("focusout", t);
177
+ }, [
178
+ v,
179
+ b,
180
+ y,
181
+ k,
182
+ A,
183
+ O,
184
+ U
185
+ ]);
186
+ let { h: Z, m: Q } = f(I), $ = {};
187
+ return x && ($.placeholder = x), L && ($.edit = ""), C && I === "" ? null : /* @__PURE__ */ o(t, {
188
+ type: "time",
189
+ label: S,
190
+ error: T,
191
+ element_blok: { className: [E || ""] },
192
+ element_input: {
193
+ className: [
194
+ e.w,
195
+ e.time,
196
+ N === "center" ? e.timeAlignCenter : ""
197
+ ].filter(Boolean),
198
+ props: { "data-disabled": w || !L ? "true" : void 0 }
199
+ },
200
+ children: [
201
+ te,
202
+ /* @__PURE__ */ o("div", {
203
+ ref: B,
204
+ className: e.timeInner,
205
+ role: "group",
206
+ "aria-label": S || "Время",
207
+ children: [
208
+ /* @__PURE__ */ a("div", {
209
+ id: j,
210
+ ref: R,
211
+ className: e.timePart,
212
+ contentEditable: L ? "plaintext-only" : !1,
213
+ suppressContentEditableWarning: !0,
214
+ spellCheck: M,
215
+ inputMode: "numeric",
216
+ onPaste: W("hour"),
217
+ onInput: G,
218
+ onKeyDown: q,
219
+ onFocus: Y,
220
+ onBlur: X,
221
+ "aria-label": "Часы, 0–23",
222
+ ...$,
223
+ children: !L || !V ? Z === null ? "" : l(Z) : null
224
+ }),
225
+ /* @__PURE__ */ a("span", {
226
+ className: e.timeSep,
227
+ "aria-hidden": "true",
228
+ children: ":"
229
+ }),
230
+ /* @__PURE__ */ a("div", {
231
+ ref: z,
232
+ className: e.timePart,
233
+ contentEditable: L ? "plaintext-only" : !1,
234
+ suppressContentEditableWarning: !0,
235
+ spellCheck: M,
236
+ inputMode: "numeric",
237
+ onPaste: W("minute"),
238
+ onInput: K,
239
+ onKeyDown: J,
240
+ onFocus: Y,
241
+ onBlur: X,
242
+ "aria-label": "Минуты, 0–59",
243
+ ...$,
244
+ children: !L || !V ? Q === null ? "" : l(Q) : null
245
+ })
246
+ ]
247
+ }, F),
248
+ D
249
+ ]
250
+ });
251
+ }
252
+ v.displayName = "TimeField";
253
+ //#endregion
254
+ export { v as default };
@@ -0,0 +1,77 @@
1
+ import { ReactNode } from 'react';
2
+ import { UseFormApi } from '../hooks/use';
3
+ /** Связь с формой и колбэки значения */
4
+ export type TFieldFormBindings = {
5
+ value?: unknown;
6
+ form?: UseFormApi;
7
+ onValue?: (data: TValue) => void | Promise<void>;
8
+ /** Колбэк при потере фокуса (коммит значения), не нативный `FocusEvent` */
9
+ onBlur?: (data: TValue) => void | Promise<void>;
10
+ name?: string;
11
+ };
12
+ /** Подпись, доступность, видимость */
13
+ export type TFieldPresentation = {
14
+ /** Подпись поля (визуально в `<dt>`) */
15
+ label?: string;
16
+ placeholder?: string;
17
+ /** true — только просмотр, без ввода (аналог нативного disabled) */
18
+ disabled?: boolean;
19
+ /**
20
+ * true — если значение поля **пустое**, поле не рендерится;
21
+ * при непустом значении поле показывается.
22
+ */
23
+ hidden?: boolean;
24
+ error?: string;
25
+ after?: ReactNode;
26
+ before?: ReactNode;
27
+ view?: string;
28
+ id?: string;
29
+ className?: string;
30
+ isCopy?: boolean;
31
+ maxLength?: number;
32
+ /**
33
+ * Проверка орфографии для **текстового** ввода (`Input`, `Textarea`).
34
+ * Для полей только с цифрами в компонентах выставляется **`false`**.
35
+ */
36
+ spellCheck?: boolean;
37
+ /**
38
+ * Подсказка клавиатуры на мобильных (`inputMode`).
39
+ * Для **чисел** — `numeric`, для **денег** — `decimal`, **телефон** — `tel` (по умолчанию в полях).
40
+ */
41
+ inputmode?: "text" | "email" | "none" | "tel" | "url" | "search" | "numeric" | "decimal";
42
+ /**
43
+ * Нативный `<input>` вместо **contentEditable** (`StringField`).
44
+ * Нужен для **автозаполнения логина/почты** (менеджеры паролей работают с нативными полями).
45
+ * У **`PasswordField`** свой проп **`native`** (см. **`TPasswordField`**).
46
+ */
47
+ native?: boolean;
48
+ /**
49
+ * Атрибут **`autocomplete`** (например **`username`**, **`email`**, **`off`**).
50
+ * Осмысленно при **`native`**; у `contentEditable` браузеры почти не применяют автозаполнение.
51
+ */
52
+ autoComplete?: string;
53
+ /**
54
+ * Тип нативного поля при **`native`** (по умолчанию **`text`**).
55
+ * Для почты удобно **`email`** + **`autoComplete="email"`**.
56
+ */
57
+ inputType?: "text" | "email" | "search" | "tel" | "url";
58
+ };
59
+ /** Базовые пропы текстовых и составных полей */
60
+ export type TInput = TFieldFormBindings & TFieldPresentation;
61
+ export type TValue = {
62
+ value: unknown;
63
+ name: string;
64
+ id?: string;
65
+ };
66
+ /**
67
+ * Поле пароля: **`native`** из `TInput` убран — здесь свой **`native`** = «не рендерить поле».
68
+ */
69
+ export type TPasswordField = Omit<TInput, "native"> & {
70
+ /** true — компонент не рендерится (пустая строка), пароль отдаётся нативному `<input>` снаружи */
71
+ native?: boolean;
72
+ /**
73
+ * Свои иконки переключателя **[показать пароль, скрыть пароль]** (когда ввод скрыт / виден).
74
+ * Если не задано — используются встроенные SVG.
75
+ */
76
+ eyes?: [React.ReactNode?, React.ReactNode?];
77
+ };