@letar/forms 1.0.3 → 1.2.0

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 (56) hide show
  1. package/CHANGELOG.md +331 -0
  2. package/README.md +9 -9
  3. package/README.ru.md +389 -0
  4. package/analytics.js +3 -0
  5. package/analytics.js.map +1 -0
  6. package/chunk-2PSXYC3I.js +1782 -0
  7. package/chunk-2PSXYC3I.js.map +1 -0
  8. package/chunk-5D6S6EGF.js +206 -0
  9. package/chunk-5D6S6EGF.js.map +1 -0
  10. package/chunk-6E7VJAJT.js +78 -0
  11. package/chunk-6E7VJAJT.js.map +1 -0
  12. package/chunk-CGXKRCSM.js +117 -0
  13. package/chunk-CGXKRCSM.js.map +1 -0
  14. package/chunk-DQUVUMCX.js +982 -0
  15. package/chunk-DQUVUMCX.js.map +1 -0
  16. package/chunk-K3J4L26K.js +345 -0
  17. package/chunk-K3J4L26K.js.map +1 -0
  18. package/chunk-MAYUFA5K.js +822 -0
  19. package/chunk-MAYUFA5K.js.map +1 -0
  20. package/{chunk-4V6WBJ76.js → chunk-MVGXZNHP.js} +2 -2
  21. package/{chunk-4V6WBJ76.js.map → chunk-MVGXZNHP.js.map} +1 -1
  22. package/chunk-MZDTJSF7.js +299 -0
  23. package/chunk-MZDTJSF7.js.map +1 -0
  24. package/chunk-Q5EOF36Y.js +709 -0
  25. package/chunk-Q5EOF36Y.js.map +1 -0
  26. package/{chunk-7FEQFDJ7.js → chunk-R2RTCKXY.js} +2 -2
  27. package/{chunk-7FEQFDJ7.js.map → chunk-R2RTCKXY.js.map} +1 -1
  28. package/chunk-XFWLD5EO.js +1045 -0
  29. package/chunk-XFWLD5EO.js.map +1 -0
  30. package/fields/boolean.js +5 -0
  31. package/fields/boolean.js.map +1 -0
  32. package/fields/datetime.js +5 -0
  33. package/fields/datetime.js.map +1 -0
  34. package/fields/number.js +5 -0
  35. package/fields/number.js.map +1 -0
  36. package/fields/selection.js +5 -0
  37. package/fields/selection.js.map +1 -0
  38. package/fields/specialized.js +5 -0
  39. package/fields/specialized.js.map +1 -0
  40. package/fields/text.js +5 -0
  41. package/fields/text.js.map +1 -0
  42. package/hcaptcha-U4XIT3HS.js +64 -0
  43. package/hcaptcha-U4XIT3HS.js.map +1 -0
  44. package/i18n.js +1 -1
  45. package/index.js +3736 -4962
  46. package/index.js.map +1 -1
  47. package/offline.js +1 -1
  48. package/package.json +59 -4
  49. package/recaptcha-PKAUAY2S.js +56 -0
  50. package/recaptcha-PKAUAY2S.js.map +1 -0
  51. package/server-errors.js +3 -0
  52. package/server-errors.js.map +1 -0
  53. package/turnstile-7FXTBSLW.js +36 -0
  54. package/turnstile-7FXTBSLW.js.map +1 -0
  55. package/validators/ru.js +73 -0
  56. package/validators/ru.js.map +1 -0
@@ -0,0 +1,982 @@
1
+ import { createField, FieldLabel, FieldError, FieldWrapper, useResolvedFieldProps, getFieldErrors } from './chunk-XFWLD5EO.js';
2
+ import { Field, Editable, IconButton, InputGroup, Input, VStack, HStack, Box, Text, Progress, List, Textarea, Popover, Portal, Button, Center, Icon, Image, Spinner } from '@chakra-ui/react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
5
+ import { LuEyeOff, LuEye, LuCheck, LuX, LuImage, LuRedo, LuUndo, LuLink, LuQuote, LuListOrdered, LuList, LuHeading3, LuHeading2, LuHeading1, LuCode, LuStrikethrough, LuUnderline, LuItalic, LuBold, LuUnlink, LuUpload } from 'react-icons/lu';
6
+ import TiptapImage from '@tiptap/extension-image';
7
+ import { Link } from '@tiptap/extension-link';
8
+ import Placeholder from '@tiptap/extension-placeholder';
9
+ import Underline from '@tiptap/extension-underline';
10
+ import { useEditor, EditorContent } from '@tiptap/react';
11
+ import StarterKit from '@tiptap/starter-kit';
12
+ import { withMask } from 'use-mask-input';
13
+
14
+ var FieldEditable = createField({
15
+ displayName: "FieldEditable",
16
+ render: ({ field, resolved, hasError, errorMessage, componentProps }) => {
17
+ const {
18
+ multiline = false,
19
+ activationMode = "click",
20
+ showControls = false,
21
+ editIcon,
22
+ cancelIcon,
23
+ submitIcon,
24
+ submitOnBlur = true
25
+ } = componentProps;
26
+ const currentValue = field.state.value || "";
27
+ return /* @__PURE__ */ jsxs(
28
+ Field.Root,
29
+ {
30
+ invalid: hasError,
31
+ required: resolved.required,
32
+ disabled: resolved.disabled,
33
+ readOnly: resolved.readOnly,
34
+ children: [
35
+ /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
36
+ /* @__PURE__ */ jsxs(
37
+ Editable.Root,
38
+ {
39
+ value: currentValue,
40
+ onValueChange: (details) => field.handleChange(details.value),
41
+ disabled: resolved.disabled,
42
+ readOnly: resolved.readOnly,
43
+ placeholder: resolved.placeholder ?? "Click to edit",
44
+ activationMode,
45
+ submitMode: submitOnBlur ? "blur" : "enter",
46
+ children: [
47
+ /* @__PURE__ */ jsx(
48
+ Editable.Preview,
49
+ {
50
+ minH: multiline ? "48px" : void 0,
51
+ alignItems: multiline ? "flex-start" : void 0,
52
+ width: "full"
53
+ }
54
+ ),
55
+ multiline ? /* @__PURE__ */ jsx(Editable.Textarea, {}) : /* @__PURE__ */ jsx(Editable.Input, {}),
56
+ showControls && /* @__PURE__ */ jsxs(Editable.Control, { children: [
57
+ /* @__PURE__ */ jsx(Editable.EditTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "xs", children: editIcon ?? "\u270F\uFE0F" }) }),
58
+ /* @__PURE__ */ jsx(Editable.CancelTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "xs", children: cancelIcon ?? "\u2715" }) }),
59
+ /* @__PURE__ */ jsx(Editable.SubmitTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "xs", children: submitIcon ?? "\u2713" }) })
60
+ ] })
61
+ ]
62
+ }
63
+ ),
64
+ /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
65
+ ]
66
+ }
67
+ );
68
+ }
69
+ });
70
+ var FieldPassword = createField({
71
+ displayName: "FieldPassword",
72
+ useFieldState: (props) => {
73
+ const [visible, setVisible] = useState(props.defaultVisible ?? false);
74
+ return {
75
+ visible,
76
+ toggle: () => setVisible((v) => !v)
77
+ };
78
+ },
79
+ render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
80
+ InputGroup,
81
+ {
82
+ endElement: /* @__PURE__ */ jsx(
83
+ IconButton,
84
+ {
85
+ tabIndex: -1,
86
+ me: "-2",
87
+ aspectRatio: "square",
88
+ size: "sm",
89
+ variant: "ghost",
90
+ height: "calc(100% - {spacing.2})",
91
+ "aria-label": "Toggle password visibility",
92
+ disabled: resolved.disabled,
93
+ onPointerDown: (e) => {
94
+ if (resolved.disabled) {
95
+ return;
96
+ }
97
+ if (e.button !== 0) {
98
+ return;
99
+ }
100
+ e.preventDefault();
101
+ fieldState.toggle();
102
+ },
103
+ children: fieldState.visible ? /* @__PURE__ */ jsx(LuEyeOff, {}) : /* @__PURE__ */ jsx(LuEye, {})
104
+ }
105
+ ),
106
+ children: /* @__PURE__ */ jsx(
107
+ Input,
108
+ {
109
+ type: fieldState.visible ? "text" : "password",
110
+ value: field.state.value ?? "",
111
+ onChange: (e) => field.handleChange(e.target.value),
112
+ onBlur: field.handleBlur,
113
+ placeholder: resolved.placeholder,
114
+ maxLength: componentProps.maxLength,
115
+ autoComplete: componentProps.autoComplete ?? resolved.autocomplete,
116
+ "data-field-name": fullPath
117
+ }
118
+ )
119
+ }
120
+ ) })
121
+ });
122
+ var DEFAULT_REQUIREMENTS = ["minLength:8", "uppercase", "lowercase", "number", "special"];
123
+ var REQUIREMENT_LABELS = {
124
+ "minLength:8": "Minimum 8 characters",
125
+ uppercase: "At least one uppercase letter",
126
+ lowercase: "At least one lowercase letter",
127
+ number: "At least one digit",
128
+ special: "At least one special character (!@#$%^&*)"
129
+ };
130
+ function checkRequirement(password, requirement) {
131
+ switch (requirement) {
132
+ case "minLength:8":
133
+ return password.length >= 8;
134
+ case "uppercase":
135
+ return /[A-Z]/.test(password);
136
+ case "lowercase":
137
+ return /[a-z]/.test(password);
138
+ case "number":
139
+ return /[0-9]/.test(password);
140
+ case "special":
141
+ return /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password);
142
+ default:
143
+ return false;
144
+ }
145
+ }
146
+ function calculateStrength(password, requirements) {
147
+ if (!password) {
148
+ return 0;
149
+ }
150
+ const metCount = requirements.filter((req) => checkRequirement(password, req)).length;
151
+ return Math.round(metCount / requirements.length * 100);
152
+ }
153
+ function getStrengthInfo(strength) {
154
+ if (strength < 25) {
155
+ return { label: "Weak", colorPalette: "red" };
156
+ }
157
+ if (strength < 50) {
158
+ return { label: "Medium", colorPalette: "orange" };
159
+ }
160
+ if (strength < 75) {
161
+ return { label: "Good", colorPalette: "yellow" };
162
+ }
163
+ return { label: "Strong", colorPalette: "green" };
164
+ }
165
+ var FieldPasswordStrength = createField({
166
+ displayName: "FieldPasswordStrength",
167
+ useFieldState: (props) => {
168
+ const [visible, setVisible] = useState(props.defaultVisible ?? false);
169
+ return { visible, toggle: () => setVisible((v) => !v) };
170
+ },
171
+ render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
172
+ const { requirements = DEFAULT_REQUIREMENTS, showRequirements = true } = componentProps;
173
+ const { visible, toggle } = fieldState;
174
+ const value = field.state.value ?? "";
175
+ const strength = calculateStrength(value, requirements);
176
+ const { label: strengthLabel, colorPalette } = getStrengthInfo(strength);
177
+ return /* @__PURE__ */ jsxs(
178
+ Field.Root,
179
+ {
180
+ invalid: hasError,
181
+ required: resolved.required,
182
+ disabled: resolved.disabled,
183
+ readOnly: resolved.readOnly,
184
+ children: [
185
+ /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
186
+ /* @__PURE__ */ jsxs(VStack, { gap: 2, align: "stretch", width: "100%", children: [
187
+ /* @__PURE__ */ jsxs(HStack, { children: [
188
+ /* @__PURE__ */ jsx(
189
+ Input,
190
+ {
191
+ type: visible ? "text" : "password",
192
+ value,
193
+ onChange: (e) => field.handleChange(e.target.value),
194
+ onBlur: field.handleBlur,
195
+ placeholder: resolved.placeholder ?? "Enter password",
196
+ "data-field-name": fullPath,
197
+ flex: 1
198
+ }
199
+ ),
200
+ /* @__PURE__ */ jsx(
201
+ IconButton,
202
+ {
203
+ "aria-label": visible ? "Hide password" : "Show password",
204
+ onClick: toggle,
205
+ variant: "ghost",
206
+ size: "sm",
207
+ children: visible ? /* @__PURE__ */ jsx(LuEyeOff, {}) : /* @__PURE__ */ jsx(LuEye, {})
208
+ }
209
+ )
210
+ ] }),
211
+ value && /* @__PURE__ */ jsxs(Box, { children: [
212
+ /* @__PURE__ */ jsxs(HStack, { justify: "space-between", mb: 1, children: [
213
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: "Strength" }),
214
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", fontWeight: "medium", color: `${colorPalette}.600`, children: strengthLabel })
215
+ ] }),
216
+ /* @__PURE__ */ jsx(Progress.Root, { value: strength, colorPalette, size: "xs", children: /* @__PURE__ */ jsx(Progress.Track, { children: /* @__PURE__ */ jsx(Progress.Range, {}) }) })
217
+ ] }),
218
+ showRequirements && value && /* @__PURE__ */ jsx(List.Root, { fontSize: "sm", gap: 1, children: requirements.map((req) => {
219
+ const met = checkRequirement(value, req);
220
+ return /* @__PURE__ */ jsxs(List.Item, { display: "flex", alignItems: "center", gap: 2, children: [
221
+ /* @__PURE__ */ jsx(Box, { color: met ? "green.500" : "gray.400", children: met ? /* @__PURE__ */ jsx(LuCheck, { size: 14 }) : /* @__PURE__ */ jsx(LuX, { size: 14 }) }),
222
+ /* @__PURE__ */ jsx(Text, { color: met ? "fg.default" : "fg.muted", children: REQUIREMENT_LABELS[req] })
223
+ ] }, req);
224
+ }) })
225
+ ] }),
226
+ /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
227
+ ]
228
+ }
229
+ );
230
+ }
231
+ });
232
+ var DEFAULT_TOOLBAR_BUTTONS = [
233
+ "bold",
234
+ "italic",
235
+ "underline",
236
+ "strike",
237
+ "code",
238
+ "heading1",
239
+ "heading2",
240
+ "heading3",
241
+ "bulletList",
242
+ "orderedList",
243
+ "blockquote",
244
+ "link",
245
+ "undo",
246
+ "redo"
247
+ ];
248
+ var TOOLBAR_CONFIG = {
249
+ bold: {
250
+ icon: /* @__PURE__ */ jsx(LuBold, {}),
251
+ label: "Bold",
252
+ action: (editor) => editor?.chain().focus().toggleBold().run(),
253
+ isActive: (editor) => editor?.isActive("bold") ?? false
254
+ },
255
+ italic: {
256
+ icon: /* @__PURE__ */ jsx(LuItalic, {}),
257
+ label: "Italic",
258
+ action: (editor) => editor?.chain().focus().toggleItalic().run(),
259
+ isActive: (editor) => editor?.isActive("italic") ?? false
260
+ },
261
+ underline: {
262
+ icon: /* @__PURE__ */ jsx(LuUnderline, {}),
263
+ label: "\u041F\u043E\u0434\u0447\u0451\u0440\u043A\u043D\u0443\u0442\u044B\u0439",
264
+ action: (editor) => editor?.chain().focus().toggleUnderline().run(),
265
+ isActive: (editor) => editor?.isActive("underline") ?? false
266
+ },
267
+ strike: {
268
+ icon: /* @__PURE__ */ jsx(LuStrikethrough, {}),
269
+ label: "Strikethrough",
270
+ action: (editor) => editor?.chain().focus().toggleStrike().run(),
271
+ isActive: (editor) => editor?.isActive("strike") ?? false
272
+ },
273
+ code: {
274
+ icon: /* @__PURE__ */ jsx(LuCode, {}),
275
+ label: "\u041A\u043E\u0434",
276
+ action: (editor) => editor?.chain().focus().toggleCode().run(),
277
+ isActive: (editor) => editor?.isActive("code") ?? false
278
+ },
279
+ heading1: {
280
+ icon: /* @__PURE__ */ jsx(LuHeading1, {}),
281
+ label: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 1",
282
+ action: (editor) => editor?.chain().focus().toggleHeading({ level: 1 }).run(),
283
+ isActive: (editor) => editor?.isActive("heading", { level: 1 }) ?? false
284
+ },
285
+ heading2: {
286
+ icon: /* @__PURE__ */ jsx(LuHeading2, {}),
287
+ label: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 2",
288
+ action: (editor) => editor?.chain().focus().toggleHeading({ level: 2 }).run(),
289
+ isActive: (editor) => editor?.isActive("heading", { level: 2 }) ?? false
290
+ },
291
+ heading3: {
292
+ icon: /* @__PURE__ */ jsx(LuHeading3, {}),
293
+ label: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 3",
294
+ action: (editor) => editor?.chain().focus().toggleHeading({ level: 3 }).run(),
295
+ isActive: (editor) => editor?.isActive("heading", { level: 3 }) ?? false
296
+ },
297
+ bulletList: {
298
+ icon: /* @__PURE__ */ jsx(LuList, {}),
299
+ label: "Bullet list",
300
+ action: (editor) => editor?.chain().focus().toggleBulletList().run(),
301
+ isActive: (editor) => editor?.isActive("bulletList") ?? false
302
+ },
303
+ orderedList: {
304
+ icon: /* @__PURE__ */ jsx(LuListOrdered, {}),
305
+ label: "Ordered list",
306
+ action: (editor) => editor?.chain().focus().toggleOrderedList().run(),
307
+ isActive: (editor) => editor?.isActive("orderedList") ?? false
308
+ },
309
+ blockquote: {
310
+ icon: /* @__PURE__ */ jsx(LuQuote, {}),
311
+ label: "Quote",
312
+ action: (editor) => editor?.chain().focus().toggleBlockquote().run(),
313
+ isActive: (editor) => editor?.isActive("blockquote") ?? false
314
+ },
315
+ link: {
316
+ icon: /* @__PURE__ */ jsx(LuLink, {}),
317
+ label: "\u0421\u0441\u044B\u043B\u043A\u0430",
318
+ action: (editor) => {
319
+ if (editor?.isActive("link")) {
320
+ editor.chain().focus().unsetLink().run();
321
+ } else {
322
+ const url = window.prompt("URL");
323
+ if (url) {
324
+ editor?.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
325
+ }
326
+ }
327
+ },
328
+ isActive: (editor) => editor?.isActive("link") ?? false
329
+ },
330
+ undo: {
331
+ icon: /* @__PURE__ */ jsx(LuUndo, {}),
332
+ label: "Undo",
333
+ action: (editor) => editor?.chain().focus().undo().run()
334
+ },
335
+ redo: {
336
+ icon: /* @__PURE__ */ jsx(LuRedo, {}),
337
+ label: "Redo",
338
+ action: (editor) => editor?.chain().focus().redo().run()
339
+ },
340
+ // Кнопка image processesся отдельно через ImagePopover (аналогично link)
341
+ image: {
342
+ icon: /* @__PURE__ */ jsx(LuImage, {}),
343
+ label: "Insert image",
344
+ action: () => {
345
+ }
346
+ }
347
+ };
348
+ function ImagePopover({ editor, config, disabled }) {
349
+ const [isOpen, setIsOpen] = useState(false);
350
+ const [uploadState, setUploadState] = useState("idle");
351
+ const [errorMessage, setErrorMessage] = useState(null);
352
+ const [previewUrl, setPreviewUrl] = useState(null);
353
+ const [isDragging, setIsDragging] = useState(false);
354
+ const fileInputRef = useRef(null);
355
+ const maxSize = config.maxSize ?? 10 * 1024 * 1024;
356
+ const acceptTypes = config.acceptTypes ?? ["image/*"];
357
+ const handleUpload = useCallback(
358
+ async (file) => {
359
+ if (!file.type.startsWith("image/")) {
360
+ setErrorMessage("File must be an image");
361
+ setUploadState("error");
362
+ return;
363
+ }
364
+ if (file.size > maxSize) {
365
+ const maxSizeMB = (maxSize / 1024 / 1024).toFixed(0);
366
+ setErrorMessage(`Size file\u0430 \u043D\u0435 must \u043F\u0440\u0435\u0432\u044B\u0448\u0430\u0442\u044C ${maxSizeMB}MB`);
367
+ setUploadState("error");
368
+ return;
369
+ }
370
+ const preview = URL.createObjectURL(file);
371
+ setPreviewUrl(preview);
372
+ setUploadState("uploading");
373
+ setErrorMessage(null);
374
+ try {
375
+ const formData = new FormData();
376
+ formData.append("file", file);
377
+ if (config.category) {
378
+ formData.append("category", config.category);
379
+ }
380
+ const response = await fetch(config.endpoint, {
381
+ method: "POST",
382
+ body: formData
383
+ });
384
+ const result = await response.json();
385
+ if (!response.ok) {
386
+ throw new Error(result.error || "Upload error");
387
+ }
388
+ if (result.url) {
389
+ ;
390
+ editor.chain().focus().setImage({ src: result.url }).run();
391
+ handleClose();
392
+ } else {
393
+ throw new Error("Image URL not received");
394
+ }
395
+ } catch (err) {
396
+ setErrorMessage(err instanceof Error ? err.message : "Upload error");
397
+ setUploadState("error");
398
+ } finally {
399
+ if (preview) {
400
+ URL.revokeObjectURL(preview);
401
+ }
402
+ }
403
+ },
404
+ [editor, config, maxSize]
405
+ );
406
+ const handleFileSelect = useCallback(
407
+ (e) => {
408
+ const file = e.target.files?.[0];
409
+ if (file) {
410
+ handleUpload(file);
411
+ }
412
+ e.target.value = "";
413
+ },
414
+ [handleUpload]
415
+ );
416
+ const handleDrop = useCallback(
417
+ (e) => {
418
+ e.preventDefault();
419
+ setIsDragging(false);
420
+ const file = e.dataTransfer.files[0];
421
+ if (file) {
422
+ handleUpload(file);
423
+ }
424
+ },
425
+ [handleUpload]
426
+ );
427
+ const handleClose = useCallback(() => {
428
+ setIsOpen(false);
429
+ setUploadState("idle");
430
+ setErrorMessage(null);
431
+ setPreviewUrl(null);
432
+ setIsDragging(false);
433
+ }, []);
434
+ const handleRetry = useCallback(() => {
435
+ setUploadState("idle");
436
+ setErrorMessage(null);
437
+ setPreviewUrl(null);
438
+ }, []);
439
+ return /* @__PURE__ */ jsxs(Popover.Root, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [
440
+ /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
441
+ IconButton,
442
+ {
443
+ "aria-label": "Insert image",
444
+ size: "sm",
445
+ variant: "ghost",
446
+ onClick: () => setIsOpen(true),
447
+ disabled,
448
+ children: /* @__PURE__ */ jsx(LuImage, {})
449
+ }
450
+ ) }),
451
+ /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, { children: /* @__PURE__ */ jsxs(Popover.Content, { width: "320px", children: [
452
+ /* @__PURE__ */ jsx(Popover.Arrow, { children: /* @__PURE__ */ jsx(Popover.ArrowTip, {}) }),
453
+ /* @__PURE__ */ jsxs(Popover.Body, { p: 3, children: [
454
+ uploadState === "idle" && /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
455
+ /* @__PURE__ */ jsx(
456
+ Box,
457
+ {
458
+ p: 6,
459
+ borderWidth: "2px",
460
+ borderStyle: "dashed",
461
+ borderColor: isDragging ? "colorPalette.500" : "border",
462
+ borderRadius: "md",
463
+ bg: isDragging ? "colorPalette.50" : "bg.subtle",
464
+ transition: "all 0.2s",
465
+ cursor: "pointer",
466
+ _hover: { borderColor: "colorPalette.400" },
467
+ onDragOver: (e) => {
468
+ e.preventDefault();
469
+ setIsDragging(true);
470
+ },
471
+ onDragLeave: (e) => {
472
+ e.preventDefault();
473
+ setIsDragging(false);
474
+ },
475
+ onDrop: handleDrop,
476
+ onClick: () => fileInputRef.current?.click(),
477
+ children: /* @__PURE__ */ jsx(Center, { children: /* @__PURE__ */ jsxs(VStack, { gap: 2, children: [
478
+ /* @__PURE__ */ jsx(Icon, { fontSize: "2xl", color: "fg.muted", children: /* @__PURE__ */ jsx(LuUpload, {}) }),
479
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", fontWeight: "medium", textAlign: "center", children: "Drag image here" }),
480
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: "or click to select" })
481
+ ] }) })
482
+ }
483
+ ),
484
+ /* @__PURE__ */ jsxs(Text, { fontSize: "xs", color: "fg.muted", textAlign: "center", children: [
485
+ "PNG, JPG, WEBP up to ",
486
+ (maxSize / 1024 / 1024).toFixed(0),
487
+ "MB"
488
+ ] }),
489
+ /* @__PURE__ */ jsx(
490
+ "input",
491
+ {
492
+ ref: fileInputRef,
493
+ type: "file",
494
+ accept: acceptTypes.join(","),
495
+ onChange: handleFileSelect,
496
+ style: { display: "none" }
497
+ }
498
+ ),
499
+ /* @__PURE__ */ jsx(HStack, { justify: "flex-end", children: /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: handleClose, children: "Cancel" }) })
500
+ ] }),
501
+ uploadState === "uploading" && /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
502
+ previewUrl && /* @__PURE__ */ jsx(Box, { borderRadius: "md", overflow: "hidden", bg: "bg.subtle", children: /* @__PURE__ */ jsx(Image, { src: previewUrl, alt: "Preview", maxH: "150px", w: "100%", objectFit: "contain" }) }),
503
+ /* @__PURE__ */ jsx(Center, { py: 2, children: /* @__PURE__ */ jsxs(HStack, { gap: 2, children: [
504
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", color: "colorPalette.500" }),
505
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "fg.muted", children: "Loading..." })
506
+ ] }) })
507
+ ] }),
508
+ uploadState === "error" && /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
509
+ previewUrl && /* @__PURE__ */ jsxs(Box, { borderRadius: "md", overflow: "hidden", bg: "bg.subtle", position: "relative", children: [
510
+ /* @__PURE__ */ jsx(Image, { src: previewUrl, alt: "Preview", maxH: "150px", w: "100%", objectFit: "contain", opacity: 0.5 }),
511
+ /* @__PURE__ */ jsx(Center, { position: "absolute", inset: 0, bg: "blackAlpha.500", borderRadius: "md", children: /* @__PURE__ */ jsx(Icon, { color: "red.400", fontSize: "2xl", children: /* @__PURE__ */ jsx(LuX, {}) }) })
512
+ ] }),
513
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "red.400", textAlign: "center", children: errorMessage }),
514
+ /* @__PURE__ */ jsxs(HStack, { justify: "center", gap: 2, children: [
515
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: handleClose, children: "Cancel" }),
516
+ /* @__PURE__ */ jsx(Button, { size: "sm", colorPalette: "brand", onClick: handleRetry, children: "Try again" })
517
+ ] })
518
+ ] })
519
+ ] })
520
+ ] }) }) })
521
+ ] });
522
+ }
523
+ function LinkPopover({ editor, disabled }) {
524
+ const [url, setUrl] = useState("");
525
+ const [isOpen, setIsOpen] = useState(false);
526
+ const isActive = editor.isActive("link");
527
+ const handleOpen = useCallback(() => {
528
+ if (isActive) {
529
+ editor.chain().focus().unsetLink().run();
530
+ } else {
531
+ const currentUrl = editor.getAttributes("link").href ?? "";
532
+ setUrl(currentUrl);
533
+ setIsOpen(true);
534
+ }
535
+ }, [editor, isActive]);
536
+ const handleClose = useCallback(() => {
537
+ setIsOpen(false);
538
+ setUrl("");
539
+ }, []);
540
+ const handleSubmit = useCallback(() => {
541
+ if (url) {
542
+ editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
543
+ }
544
+ handleClose();
545
+ }, [editor, url, handleClose]);
546
+ const handleRemove = useCallback(() => {
547
+ editor.chain().focus().unsetLink().run();
548
+ handleClose();
549
+ }, [editor, handleClose]);
550
+ const handleKeyDown = useCallback(
551
+ (e) => {
552
+ if (e.key === "Enter") {
553
+ e.preventDefault();
554
+ handleSubmit();
555
+ } else if (e.key === "Escape") {
556
+ handleClose();
557
+ }
558
+ },
559
+ [handleSubmit, handleClose]
560
+ );
561
+ return /* @__PURE__ */ jsxs(Popover.Root, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [
562
+ /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
563
+ IconButton,
564
+ {
565
+ "aria-label": isActive ? "Remove \u0441\u0441\u044B\u043B\u043A\u0443" : "Add \u0441\u0441\u044B\u043B\u043A\u0443",
566
+ size: "sm",
567
+ variant: isActive ? "solid" : "ghost",
568
+ colorPalette: isActive ? "brand" : void 0,
569
+ onClick: handleOpen,
570
+ disabled,
571
+ children: isActive ? /* @__PURE__ */ jsx(LuUnlink, {}) : /* @__PURE__ */ jsx(LuLink, {})
572
+ }
573
+ ) }),
574
+ /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, { children: /* @__PURE__ */ jsxs(Popover.Content, { width: "300px", children: [
575
+ /* @__PURE__ */ jsx(Popover.Arrow, { children: /* @__PURE__ */ jsx(Popover.ArrowTip, {}) }),
576
+ /* @__PURE__ */ jsx(Popover.Body, { p: 3, children: /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
577
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
578
+ Input,
579
+ {
580
+ placeholder: "https://example.com",
581
+ value: url,
582
+ onChange: (e) => setUrl(e.target.value),
583
+ onKeyDown: handleKeyDown,
584
+ size: "sm",
585
+ autoFocus: true
586
+ }
587
+ ) }),
588
+ /* @__PURE__ */ jsxs(HStack, { gap: 2, justify: "flex-end", children: [
589
+ editor.isActive("link") && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", colorPalette: "red", onClick: handleRemove, children: "Remove" }),
590
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: handleClose, children: "Cancel" }),
591
+ /* @__PURE__ */ jsx(Button, { size: "sm", colorPalette: "brand", onClick: handleSubmit, disabled: !url.trim(), children: "Apply" })
592
+ ] })
593
+ ] }) })
594
+ ] }) }) })
595
+ ] });
596
+ }
597
+ function safeParseJSON(value) {
598
+ try {
599
+ return JSON.parse(value);
600
+ } catch {
601
+ console.warn("RichText: Invalid JSON content, using empty document");
602
+ return "";
603
+ }
604
+ }
605
+ function FieldRichText({
606
+ name,
607
+ label,
608
+ placeholder,
609
+ helperText,
610
+ required,
611
+ disabled,
612
+ readOnly,
613
+ tooltip,
614
+ minHeight = "150px",
615
+ maxHeight,
616
+ showToolbar = true,
617
+ toolbarButtons = DEFAULT_TOOLBAR_BUTTONS,
618
+ outputFormat = "html",
619
+ imageUpload
620
+ }) {
621
+ const {
622
+ form,
623
+ fullPath,
624
+ label: resolvedLabel,
625
+ placeholder: resolvedPlaceholder,
626
+ helperText: resolvedHelperText,
627
+ tooltip: resolvedTooltip,
628
+ required: resolvedRequired,
629
+ disabled: resolvedDisabled,
630
+ readOnly: resolvedReadOnly
631
+ } = useResolvedFieldProps(name, { label, placeholder, helperText, required, disabled, readOnly, tooltip });
632
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
633
+ const { hasError, errorMessage } = getFieldErrors(field);
634
+ return /* @__PURE__ */ jsxs(
635
+ Field.Root,
636
+ {
637
+ invalid: hasError,
638
+ required: resolvedRequired,
639
+ disabled: resolvedDisabled,
640
+ readOnly: resolvedReadOnly,
641
+ children: [
642
+ /* @__PURE__ */ jsx(FieldLabel, { label: resolvedLabel, tooltip: resolvedTooltip, required: resolvedRequired }),
643
+ /* @__PURE__ */ jsx(
644
+ RichTextEditor,
645
+ {
646
+ value: field.state.value,
647
+ onChange: (value) => field.handleChange(value),
648
+ onBlur: field.handleBlur,
649
+ placeholder: resolvedPlaceholder,
650
+ minHeight,
651
+ maxHeight,
652
+ showToolbar,
653
+ toolbarButtons,
654
+ outputFormat,
655
+ disabled,
656
+ readOnly,
657
+ hasError,
658
+ fieldName: fullPath,
659
+ imageUpload
660
+ }
661
+ ),
662
+ /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolvedHelperText })
663
+ ]
664
+ }
665
+ );
666
+ } });
667
+ }
668
+ function RichTextEditor({
669
+ value,
670
+ onChange,
671
+ onBlur,
672
+ placeholder,
673
+ minHeight,
674
+ maxHeight,
675
+ showToolbar,
676
+ toolbarButtons,
677
+ outputFormat,
678
+ disabled,
679
+ readOnly,
680
+ hasError,
681
+ fieldName,
682
+ imageUpload
683
+ }) {
684
+ const extensions = useMemo(() => {
685
+ const baseExtensions = [
686
+ StarterKit,
687
+ Underline,
688
+ Link.configure({
689
+ openOnClick: false,
690
+ HTMLAttributes: {
691
+ rel: "noopener noreferrer",
692
+ target: "_blank"
693
+ }
694
+ }),
695
+ Placeholder.configure({
696
+ placeholder: placeholder ?? "Start typing..."
697
+ }),
698
+ // Add Image extension only if imageUpload is configured
699
+ ...imageUpload ? [
700
+ TiptapImage.configure({
701
+ inline: false,
702
+ allowBase64: false,
703
+ HTMLAttributes: {
704
+ class: "richtext-image"
705
+ }
706
+ })
707
+ ] : []
708
+ ];
709
+ return baseExtensions;
710
+ }, [placeholder, imageUpload]);
711
+ const editor = useEditor({
712
+ // Cast needed: minor @tiptap/core version drift (e.g. 3.20.0 vs 3.20.1) causes nominal type mismatch
713
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- @tiptap/core version incompatibility
714
+ extensions,
715
+ content: outputFormat === "json" && value ? safeParseJSON(value) : value || "",
716
+ editable: !disabled && !readOnly,
717
+ onUpdate: ({ editor: editor2 }) => {
718
+ if (outputFormat === "json") {
719
+ onChange(JSON.stringify(editor2.getJSON()));
720
+ } else {
721
+ onChange(editor2.getHTML());
722
+ }
723
+ },
724
+ onBlur: () => {
725
+ onBlur();
726
+ },
727
+ immediatelyRender: false
728
+ });
729
+ useEffect(() => {
730
+ if (!editor) {
731
+ return;
732
+ }
733
+ const currentContent = outputFormat === "json" ? JSON.stringify(editor.getJSON()) : editor.getHTML();
734
+ if (value !== currentContent) {
735
+ const content = outputFormat === "json" && value ? safeParseJSON(value) : value || "";
736
+ editor.commands.setContent(content, { emitUpdate: false });
737
+ }
738
+ }, [editor, value, outputFormat]);
739
+ useEffect(() => {
740
+ if (editor) {
741
+ editor.setEditable(!disabled && !readOnly);
742
+ }
743
+ }, [editor, disabled, readOnly]);
744
+ if (!editor) {
745
+ return null;
746
+ }
747
+ return /* @__PURE__ */ jsxs(
748
+ Box,
749
+ {
750
+ borderWidth: "1px",
751
+ borderRadius: "md",
752
+ borderColor: hasError ? "border.error" : "border",
753
+ overflow: "hidden",
754
+ "data-field-name": fieldName,
755
+ _focusWithin: {
756
+ borderColor: hasError ? "border.error" : "colorPalette.500",
757
+ boxShadow: hasError ? "0 0 0 1px var(--chakra-colors-border-error)" : "0 0 0 1px var(--chakra-colors-colorPalette-500)"
758
+ },
759
+ children: [
760
+ showToolbar && !readOnly && /* @__PURE__ */ jsx(HStack, { p: 1, gap: 0.5, borderBottomWidth: "1px", borderColor: "border", bg: "bg.subtle", flexWrap: "wrap", children: toolbarButtons.map((button) => {
761
+ if (button === "link") {
762
+ return /* @__PURE__ */ jsx(LinkPopover, { editor, disabled }, button);
763
+ }
764
+ if (button === "image") {
765
+ if (!imageUpload) {
766
+ return null;
767
+ }
768
+ return /* @__PURE__ */ jsx(ImagePopover, { editor, config: imageUpload, disabled }, button);
769
+ }
770
+ const config = TOOLBAR_CONFIG[button];
771
+ const isActive = config.isActive?.(editor) ?? false;
772
+ return /* @__PURE__ */ jsx(
773
+ IconButton,
774
+ {
775
+ "aria-label": config.label,
776
+ size: "sm",
777
+ variant: isActive ? "solid" : "ghost",
778
+ colorPalette: isActive ? "brand" : void 0,
779
+ onClick: () => config.action(editor),
780
+ disabled,
781
+ children: config.icon
782
+ },
783
+ button
784
+ );
785
+ }) }),
786
+ /* @__PURE__ */ jsx(
787
+ Box,
788
+ {
789
+ minHeight,
790
+ maxHeight,
791
+ overflowY: maxHeight ? "auto" : void 0,
792
+ p: 3,
793
+ css: {
794
+ "& .tiptap": {
795
+ outline: "none",
796
+ minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight
797
+ },
798
+ "& .tiptap p.is-editor-empty:first-child::before": {
799
+ color: "var(--chakra-colors-fg-muted)",
800
+ content: "attr(data-placeholder)",
801
+ float: "left",
802
+ height: 0,
803
+ pointerEvents: "none"
804
+ },
805
+ "& .tiptap h1": {
806
+ fontSize: "2xl",
807
+ fontWeight: "bold",
808
+ marginTop: "1em",
809
+ marginBottom: "0.5em"
810
+ },
811
+ "& .tiptap h2": {
812
+ fontSize: "xl",
813
+ fontWeight: "bold",
814
+ marginTop: "1em",
815
+ marginBottom: "0.5em"
816
+ },
817
+ "& .tiptap h3": {
818
+ fontSize: "lg",
819
+ fontWeight: "semibold",
820
+ marginTop: "1em",
821
+ marginBottom: "0.5em"
822
+ },
823
+ "& .tiptap ul, & .tiptap ol": {
824
+ paddingLeft: "1.5em",
825
+ marginTop: "0.5em",
826
+ marginBottom: "0.5em"
827
+ },
828
+ "& .tiptap blockquote": {
829
+ borderLeft: "3px solid var(--chakra-colors-border)",
830
+ paddingLeft: "1em",
831
+ marginLeft: 0,
832
+ marginTop: "0.5em",
833
+ marginBottom: "0.5em",
834
+ fontStyle: "italic",
835
+ color: "var(--chakra-colors-fg-muted)"
836
+ },
837
+ "& .tiptap code": {
838
+ backgroundColor: "var(--chakra-colors-bg-subtle)",
839
+ borderRadius: "3px",
840
+ padding: "0.2em 0.4em",
841
+ fontFamily: "mono",
842
+ fontSize: "0.9em"
843
+ },
844
+ "& .tiptap a": {
845
+ color: "var(--chakra-colors-colorPalette-500)",
846
+ textDecoration: "underline",
847
+ cursor: "pointer"
848
+ },
849
+ "& .tiptap p": {
850
+ marginTop: "0.25em",
851
+ marginBottom: "0.25em"
852
+ },
853
+ "& .tiptap img, & .tiptap .richtext-image": {
854
+ maxWidth: "100%",
855
+ height: "auto",
856
+ borderRadius: "4px",
857
+ marginTop: "0.5em",
858
+ marginBottom: "0.5em"
859
+ }
860
+ },
861
+ children: /* @__PURE__ */ jsx(EditorContent, { editor })
862
+ }
863
+ )
864
+ ]
865
+ }
866
+ );
867
+ }
868
+ function getInputModeFromType(type) {
869
+ switch (type) {
870
+ case "email":
871
+ return "email";
872
+ case "tel":
873
+ return "tel";
874
+ case "url":
875
+ return "url";
876
+ default:
877
+ return "text";
878
+ }
879
+ }
880
+ var FieldString = createField({
881
+ displayName: "FieldString",
882
+ render: ({ field, fullPath, resolved, hasError, errorMessage, isValidating, componentProps }) => {
883
+ const { constraints } = resolved;
884
+ const type = componentProps.type ?? constraints.string?.inputType ?? "text";
885
+ const maxLength = componentProps.maxLength ?? constraints.string?.maxLength;
886
+ const minLength = componentProps.minLength ?? constraints.string?.minLength;
887
+ const pattern = componentProps.pattern ?? constraints.string?.pattern;
888
+ const inputMode = componentProps.inputMode ?? getInputModeFromType(type);
889
+ return /* @__PURE__ */ jsx(
890
+ FieldWrapper,
891
+ {
892
+ resolved,
893
+ hasError,
894
+ errorMessage,
895
+ isValidating,
896
+ fullPath,
897
+ children: /* @__PURE__ */ jsx(
898
+ Input,
899
+ {
900
+ type,
901
+ inputMode,
902
+ value: field.state.value ?? "",
903
+ onChange: (e) => field.handleChange(e.target.value),
904
+ onBlur: field.handleBlur,
905
+ placeholder: resolved.placeholder,
906
+ maxLength,
907
+ minLength,
908
+ pattern,
909
+ autoComplete: componentProps.autoComplete ?? resolved.autocomplete,
910
+ "data-field-name": fullPath
911
+ }
912
+ )
913
+ }
914
+ );
915
+ }
916
+ });
917
+ var FieldTextarea = createField({
918
+ displayName: "FieldTextarea",
919
+ render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
920
+ const { constraints } = resolved;
921
+ const maxLength = componentProps.maxLength ?? constraints.string?.maxLength;
922
+ return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
923
+ Textarea,
924
+ {
925
+ value: field.state.value ?? "",
926
+ onChange: (e) => field.handleChange(e.target.value),
927
+ onBlur: field.handleBlur,
928
+ placeholder: resolved.placeholder,
929
+ rows: componentProps.rows,
930
+ autoresize: componentProps.autoresize,
931
+ resize: componentProps.resize ?? "vertical",
932
+ maxLength,
933
+ autoComplete: resolved.autocomplete,
934
+ "data-field-name": fullPath
935
+ }
936
+ ) });
937
+ }
938
+ });
939
+ var FieldMaskedInput = createField({
940
+ displayName: "FieldMaskedInput",
941
+ useFieldState: (props) => {
942
+ const {
943
+ mask,
944
+ placeholderChar = "_",
945
+ showMaskOnFocus = true,
946
+ showMaskOnHover = false,
947
+ clearIncomplete = false,
948
+ autoUnmask = false
949
+ } = props;
950
+ const maskRef = useCallback(
951
+ (element) => {
952
+ if (element && mask) {
953
+ const maskCallback = withMask(mask, {
954
+ placeholder: placeholderChar,
955
+ showMaskOnFocus,
956
+ showMaskOnHover,
957
+ clearIncomplete,
958
+ autoUnmask
959
+ });
960
+ maskCallback(element);
961
+ }
962
+ },
963
+ [mask, placeholderChar, showMaskOnFocus, showMaskOnHover, clearIncomplete, autoUnmask]
964
+ );
965
+ return { maskRef };
966
+ },
967
+ render: ({ field, fullPath, resolved, hasError, errorMessage, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsx(
968
+ Input,
969
+ {
970
+ ref: fieldState.maskRef,
971
+ value: field.state.value ?? "",
972
+ onChange: (e) => field.handleChange(e.target.value),
973
+ onBlur: field.handleBlur,
974
+ placeholder: resolved.placeholder,
975
+ "data-field-name": fullPath
976
+ }
977
+ ) })
978
+ });
979
+
980
+ export { DEFAULT_TOOLBAR_BUTTONS, FieldEditable, FieldMaskedInput, FieldPassword, FieldPasswordStrength, FieldRichText, FieldString, FieldTextarea, TOOLBAR_CONFIG };
981
+ //# sourceMappingURL=chunk-DQUVUMCX.js.map
982
+ //# sourceMappingURL=chunk-DQUVUMCX.js.map