@letar/forms 1.1.0 → 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 +308 -0
  2. package/README.md +9 -9
  3. package/README.ru.md +115 -30
  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-6QOPSQ3Z.js → chunk-6E7VJAJT.js} +3 -3
  11. package/{chunk-6QOPSQ3Z.js.map → chunk-6E7VJAJT.js.map} +1 -1
  12. package/chunk-CGXKRCSM.js +117 -0
  13. package/chunk-CGXKRCSM.js.map +1 -0
  14. package/{chunk-M2PNAAIR.js → chunk-DQUVUMCX.js} +30 -19
  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-PJETA6YN.js → chunk-MAYUFA5K.js} +5 -4
  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-XKKJKYWZ.js → chunk-MZDTJSF7.js} +3 -3
  23. package/{chunk-XKKJKYWZ.js.map → chunk-MZDTJSF7.js.map} +1 -1
  24. package/{chunk-KUNT5MSU.js → chunk-Q5EOF36Y.js} +3 -3
  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-HWVOFWAT.js → chunk-XFWLD5EO.js} +225 -26
  29. package/chunk-XFWLD5EO.js.map +1 -0
  30. package/fields/boolean.js +3 -3
  31. package/fields/datetime.js +3 -3
  32. package/fields/number.js +3 -3
  33. package/fields/selection.js +3 -3
  34. package/fields/specialized.js +3 -3
  35. package/fields/text.js +3 -3
  36. package/hcaptcha-U4XIT3HS.js +64 -0
  37. package/hcaptcha-U4XIT3HS.js.map +1 -0
  38. package/i18n.js +1 -1
  39. package/index.js +3268 -51
  40. package/index.js.map +1 -1
  41. package/offline.js +1 -1
  42. package/package.json +33 -4
  43. package/recaptcha-PKAUAY2S.js +56 -0
  44. package/recaptcha-PKAUAY2S.js.map +1 -0
  45. package/server-errors.js +3 -0
  46. package/server-errors.js.map +1 -0
  47. package/turnstile-7FXTBSLW.js +36 -0
  48. package/turnstile-7FXTBSLW.js.map +1 -0
  49. package/validators/ru.js +73 -0
  50. package/validators/ru.js.map +1 -0
  51. package/chunk-GOELIS6T.js +0 -849
  52. package/chunk-GOELIS6T.js.map +0 -1
  53. package/chunk-HWVOFWAT.js.map +0 -1
  54. package/chunk-KUNT5MSU.js.map +0 -1
  55. package/chunk-M2PNAAIR.js.map +0 -1
  56. package/chunk-PJETA6YN.js.map +0 -1
package/chunk-GOELIS6T.js DELETED
@@ -1,849 +0,0 @@
1
- import { createField, FieldLabel, FieldError, useDebounce, FieldTooltip, FieldWrapper, useDeclarativeForm, useDeclarativeFormOptional } from './chunk-HWVOFWAT.js';
2
- import { Field, Box, Input, Spinner, List, Text, parseColor, ColorPicker, HStack, Portal, FileUpload, Button, Icon, PinInput, Group, useFileUploadContext, Float, IconButton } from '@chakra-ui/react';
3
- import { useState, useRef, useCallback, useEffect } from 'react';
4
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
- import { LuUpload, LuX, LuFile } from 'react-icons/lu';
6
- import { withMask } from 'use-mask-input';
7
-
8
- // src/lib/declarative/form-fields/specialized/providers/dadata.ts
9
- var DADATA_URL = "https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/address";
10
- function createDaDataProvider(config) {
11
- const { token, baseUrl = DADATA_URL } = config;
12
- return {
13
- async getSuggestions(query, options) {
14
- const body = {
15
- query,
16
- count: options?.count ?? 10
17
- };
18
- if (options?.bounds) {
19
- if (options.bounds.from) body.from_bound = { value: options.bounds.from };
20
- if (options.bounds.to) body.to_bound = { value: options.bounds.to };
21
- }
22
- if (options?.filters) {
23
- body.locations = [options.filters];
24
- }
25
- const response = await fetch(baseUrl, {
26
- method: "POST",
27
- headers: {
28
- "Content-Type": "application/json",
29
- Accept: "application/json",
30
- Authorization: `Token ${token}`
31
- },
32
- body: JSON.stringify(body)
33
- });
34
- if (!response.ok) {
35
- return [];
36
- }
37
- const data = await response.json();
38
- const suggestions = data.suggestions ?? [];
39
- return suggestions.map(
40
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
- (s) => ({
42
- label: s.value,
43
- value: s.value,
44
- data: s.data
45
- })
46
- );
47
- }
48
- };
49
- }
50
- function useAddressProvider(propProvider, token) {
51
- const formContext = useDeclarativeFormOptional();
52
- if (propProvider) return propProvider;
53
- if (formContext?.addressProvider) return formContext.addressProvider;
54
- if (token) return createDaDataProvider({ token });
55
- return null;
56
- }
57
- var FieldAddress = createField({
58
- displayName: "FieldAddress",
59
- useFieldState: (props) => {
60
- const { provider: propProvider, token, minChars = 3, debounceMs = 300, locations } = props;
61
- const provider = useAddressProvider(propProvider, token);
62
- const [inputValue, setInputValue] = useState("");
63
- const [suggestions, setSuggestions] = useState([]);
64
- const [isLoading, setIsLoading] = useState(false);
65
- const [isOpen, setIsOpen] = useState(false);
66
- const [highlightedIndex, setHighlightedIndex] = useState(-1);
67
- const containerRef = useRef(null);
68
- const initializedRef = useRef(false);
69
- const debouncedQuery = useDebounce(inputValue, debounceMs);
70
- const fetchSuggestions = useCallback(
71
- async (query) => {
72
- if (query.length < minChars || !provider) {
73
- setSuggestions([]);
74
- return;
75
- }
76
- setIsLoading(true);
77
- try {
78
- const results = await provider.getSuggestions(query, {
79
- count: 10,
80
- filters: locations ? Object.assign({}, ...locations) : void 0
81
- });
82
- setSuggestions(results);
83
- setIsOpen(true);
84
- } catch (error) {
85
- console.error("Error loading address suggestions:", error);
86
- setSuggestions([]);
87
- } finally {
88
- setIsLoading(false);
89
- }
90
- },
91
- [provider, minChars, locations]
92
- );
93
- useEffect(() => {
94
- if (debouncedQuery) {
95
- fetchSuggestions(debouncedQuery);
96
- } else {
97
- setSuggestions([]);
98
- setIsOpen(false);
99
- }
100
- }, [debouncedQuery, fetchSuggestions]);
101
- useEffect(() => {
102
- const handleClickOutside = (event) => {
103
- if (containerRef.current && !containerRef.current.contains(event.target)) {
104
- setIsOpen(false);
105
- }
106
- };
107
- document.addEventListener("mousedown", handleClickOutside);
108
- return () => document.removeEventListener("mousedown", handleClickOutside);
109
- }, []);
110
- return {
111
- inputValue,
112
- setInputValue,
113
- suggestions,
114
- setSuggestions,
115
- isLoading,
116
- setIsLoading,
117
- isOpen,
118
- setIsOpen,
119
- highlightedIndex,
120
- setHighlightedIndex,
121
- containerRef,
122
- debouncedQuery,
123
- fetchSuggestions,
124
- initializedRef
125
- };
126
- },
127
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
128
- const { valueOnly = false } = componentProps;
129
- const {
130
- inputValue,
131
- setInputValue,
132
- suggestions,
133
- setSuggestions,
134
- isLoading,
135
- isOpen,
136
- setIsOpen,
137
- highlightedIndex,
138
- setHighlightedIndex,
139
- containerRef,
140
- initializedRef
141
- } = fieldState;
142
- const fieldValue = field.state.value;
143
- if (!initializedRef.current && fieldValue) {
144
- const displayValue = typeof fieldValue === "string" ? fieldValue : fieldValue.value;
145
- if (displayValue && displayValue !== inputValue) {
146
- setInputValue(displayValue);
147
- }
148
- initializedRef.current = true;
149
- }
150
- const handleSelect = (suggestion) => {
151
- setInputValue(suggestion.value);
152
- setIsOpen(false);
153
- setSuggestions([]);
154
- if (valueOnly) {
155
- field.handleChange(suggestion.value);
156
- } else {
157
- const addressValue = {
158
- value: suggestion.value,
159
- data: suggestion.data
160
- };
161
- field.handleChange(addressValue);
162
- }
163
- };
164
- const handleKeyDown = (e) => {
165
- if (!isOpen || suggestions.length === 0) {
166
- return;
167
- }
168
- switch (e.key) {
169
- case "ArrowDown":
170
- e.preventDefault();
171
- setHighlightedIndex(highlightedIndex < suggestions.length - 1 ? highlightedIndex + 1 : 0);
172
- break;
173
- case "ArrowUp":
174
- e.preventDefault();
175
- setHighlightedIndex(highlightedIndex > 0 ? highlightedIndex - 1 : suggestions.length - 1);
176
- break;
177
- case "Enter":
178
- e.preventDefault();
179
- if (highlightedIndex >= 0) {
180
- handleSelect(suggestions[highlightedIndex]);
181
- }
182
- break;
183
- case "Escape":
184
- setIsOpen(false);
185
- break;
186
- }
187
- };
188
- return /* @__PURE__ */ jsxs(
189
- Field.Root,
190
- {
191
- invalid: hasError,
192
- required: resolved.required,
193
- disabled: resolved.disabled,
194
- readOnly: resolved.readOnly,
195
- children: [
196
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
197
- /* @__PURE__ */ jsxs(Box, { ref: containerRef, position: "relative", width: "100%", children: [
198
- /* @__PURE__ */ jsx(
199
- Input,
200
- {
201
- value: inputValue,
202
- onChange: (e) => {
203
- setInputValue(e.target.value);
204
- setHighlightedIndex(-1);
205
- },
206
- onFocus: () => {
207
- if (suggestions.length > 0) {
208
- setIsOpen(true);
209
- }
210
- },
211
- onBlur: field.handleBlur,
212
- onKeyDown: handleKeyDown,
213
- placeholder: resolved.placeholder ?? "Start typing address...",
214
- "data-field-name": fullPath
215
- }
216
- ),
217
- isLoading && /* @__PURE__ */ jsx(Box, { position: "absolute", right: 3, top: "50%", transform: "translateY(-50%)", children: /* @__PURE__ */ jsx(Spinner, { size: "sm" }) }),
218
- isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx(
219
- List.Root,
220
- {
221
- position: "absolute",
222
- zIndex: 10,
223
- width: "100%",
224
- bg: "bg.panel",
225
- borderWidth: "1px",
226
- borderRadius: "md",
227
- shadow: "md",
228
- maxH: "200px",
229
- overflowY: "auto",
230
- mt: 1,
231
- children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsx(
232
- List.Item,
233
- {
234
- px: 3,
235
- py: 2,
236
- cursor: "pointer",
237
- bg: highlightedIndex === index ? "bg.muted" : void 0,
238
- _hover: { bg: "bg.muted" },
239
- onClick: () => handleSelect(suggestion),
240
- onMouseEnter: () => setHighlightedIndex(index),
241
- children: /* @__PURE__ */ jsx(Text, { fontSize: "sm", children: suggestion.label })
242
- },
243
- suggestion.value + index
244
- ))
245
- }
246
- )
247
- ] }),
248
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
249
- ]
250
- }
251
- );
252
- }
253
- });
254
- var defaultSwatches = [
255
- "#000000",
256
- "#4A5568",
257
- "#F56565",
258
- "#ED64A6",
259
- "#9F7AEA",
260
- "#6B46C1",
261
- "#4299E1",
262
- "#0BC5EA",
263
- "#38B2AC",
264
- "#48BB78",
265
- "#ECC94B",
266
- "#DD6B20"
267
- ];
268
- var FieldColorPicker = createField({
269
- displayName: "FieldColorPicker",
270
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
271
- const {
272
- swatches = defaultSwatches,
273
- size = "md",
274
- showArea = true,
275
- showEyeDropper = true,
276
- showSliders = true,
277
- showInput = true
278
- } = componentProps;
279
- const currentValue = field.state.value || "#000000";
280
- let parsedColor;
281
- try {
282
- parsedColor = parseColor(currentValue);
283
- } catch {
284
- parsedColor = parseColor("#000000");
285
- }
286
- return /* @__PURE__ */ jsxs(
287
- Field.Root,
288
- {
289
- invalid: hasError,
290
- required: resolved.required,
291
- disabled: resolved.disabled,
292
- readOnly: resolved.readOnly,
293
- children: [
294
- /* @__PURE__ */ jsxs(
295
- ColorPicker.Root,
296
- {
297
- value: parsedColor,
298
- onValueChange: (details) => {
299
- field.handleChange(details.valueAsString);
300
- },
301
- disabled: resolved.disabled,
302
- readOnly: resolved.readOnly,
303
- size,
304
- children: [
305
- /* @__PURE__ */ jsx(ColorPicker.HiddenInput, { name: fullPath }),
306
- resolved.label && /* @__PURE__ */ jsxs(ColorPicker.Label, { children: [
307
- resolved.tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
308
- /* @__PURE__ */ jsx("span", { children: resolved.label }),
309
- /* @__PURE__ */ jsx(FieldTooltip, { ...resolved.tooltip })
310
- ] }) : resolved.label,
311
- resolved.required && /* @__PURE__ */ jsx(Field.RequiredIndicator, {})
312
- ] }),
313
- /* @__PURE__ */ jsxs(ColorPicker.Control, { children: [
314
- showInput && /* @__PURE__ */ jsx(ColorPicker.ChannelInput, { channel: "hex" }),
315
- /* @__PURE__ */ jsx(ColorPicker.Trigger, {})
316
- ] }),
317
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(ColorPicker.Positioner, { children: /* @__PURE__ */ jsxs(ColorPicker.Content, { children: [
318
- showArea && /* @__PURE__ */ jsx(ColorPicker.Area, {}),
319
- (showEyeDropper || showSliders) && /* @__PURE__ */ jsxs(HStack, { children: [
320
- showEyeDropper && /* @__PURE__ */ jsx(ColorPicker.EyeDropper, { size: "xs", variant: "outline" }),
321
- showSliders && /* @__PURE__ */ jsx(ColorPicker.Sliders, {})
322
- ] }),
323
- swatches.length > 0 && /* @__PURE__ */ jsx(ColorPicker.SwatchGroup, { children: swatches.map((swatch) => /* @__PURE__ */ jsx(ColorPicker.SwatchTrigger, { value: swatch, children: /* @__PURE__ */ jsx(ColorPicker.Swatch, { value: swatch, boxSize: "4.5" }) }, swatch)) })
324
- ] }) }) })
325
- ]
326
- }
327
- ),
328
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
329
- ]
330
- }
331
- );
332
- }
333
- });
334
- function FileImageList({ clearable }) {
335
- const fileUpload = useFileUploadContext();
336
- if (fileUpload.acceptedFiles.length === 0) {
337
- return null;
338
- }
339
- return /* @__PURE__ */ jsx(HStack, { wrap: "wrap", gap: "3", mt: "2", children: fileUpload.acceptedFiles.map((file) => /* @__PURE__ */ jsxs(FileUpload.Item, { file, p: "2", width: "auto", pos: "relative", children: [
340
- clearable && /* @__PURE__ */ jsx(Float, { placement: "top-end", children: /* @__PURE__ */ jsx(FileUpload.ItemDeleteTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { size: "2xs", variant: "solid", colorPalette: "red", rounded: "full", children: /* @__PURE__ */ jsx(LuX, {}) }) }) }),
341
- /* @__PURE__ */ jsx(FileUpload.ItemPreview, { type: "image/*", asChild: true, children: /* @__PURE__ */ jsx(FileUpload.ItemPreviewImage, { boxSize: "16", rounded: "md", objectFit: "cover" }) }),
342
- /* @__PURE__ */ jsx(FileUpload.ItemPreview, { type: ".*", asChild: true, children: /* @__PURE__ */ jsx(Icon, { fontSize: "4xl", color: "fg.muted", children: /* @__PURE__ */ jsx(LuFile, {}) }) })
343
- ] }, file.name)) });
344
- }
345
- function FileList({ showSize, clearable }) {
346
- const fileUpload = useFileUploadContext();
347
- if (fileUpload.acceptedFiles.length === 0) {
348
- return null;
349
- }
350
- return /* @__PURE__ */ jsx(FileUpload.ItemGroup, { mt: "2", children: fileUpload.acceptedFiles.map((file) => /* @__PURE__ */ jsxs(FileUpload.Item, { file, children: [
351
- /* @__PURE__ */ jsx(FileUpload.ItemPreview, { asChild: true, children: /* @__PURE__ */ jsx(Icon, { fontSize: "lg", color: "fg.muted", children: /* @__PURE__ */ jsx(LuFile, {}) }) }),
352
- showSize ? /* @__PURE__ */ jsxs(FileUpload.ItemContent, { children: [
353
- /* @__PURE__ */ jsx(FileUpload.ItemName, {}),
354
- /* @__PURE__ */ jsx(FileUpload.ItemSizeText, {})
355
- ] }) : /* @__PURE__ */ jsx(FileUpload.ItemName, { flex: "1" }),
356
- clearable && /* @__PURE__ */ jsx(FileUpload.ItemDeleteTrigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "ghost", color: "fg.muted", size: "xs", children: /* @__PURE__ */ jsx(LuX, {}) }) })
357
- ] }, file.name)) });
358
- }
359
- var FieldFileUpload = createField({
360
- displayName: "FieldFileUpload",
361
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
362
- const {
363
- accept,
364
- maxFileSize,
365
- maxFiles = 1,
366
- variant = "button",
367
- showSize = false,
368
- clearable = true,
369
- dropzoneLabel = "Drag and drop files here",
370
- dropzoneDescription,
371
- buttonText = "Upload file"
372
- } = componentProps;
373
- const placeholder = resolved.placeholder ?? "Select file(s)";
374
- const normalizedAccept = accept ? typeof accept === "string" ? accept.split(",").map((s) => s.trim()) : accept : void 0;
375
- const isImageUpload = normalizedAccept?.some((type) => type.startsWith("image/") || type === "image/*");
376
- return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, required: resolved.required, disabled: resolved.disabled, children: [
377
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
378
- /* @__PURE__ */ jsxs(
379
- FileUpload.Root,
380
- {
381
- maxFiles,
382
- maxFileSize,
383
- accept: normalizedAccept,
384
- disabled: resolved.disabled,
385
- onFileChange: (details) => {
386
- field.handleChange(details.acceptedFiles);
387
- },
388
- "data-field-name": fullPath,
389
- children: [
390
- /* @__PURE__ */ jsx(FileUpload.HiddenInput, { onBlur: field.handleBlur }),
391
- variant === "button" && /* @__PURE__ */ jsxs(Fragment, { children: [
392
- /* @__PURE__ */ jsx(FileUpload.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
393
- /* @__PURE__ */ jsx(LuUpload, {}),
394
- buttonText
395
- ] }) }),
396
- isImageUpload ? /* @__PURE__ */ jsx(FileImageList, { clearable }) : /* @__PURE__ */ jsx(FileList, { showSize, clearable })
397
- ] }),
398
- variant === "dropzone" && /* @__PURE__ */ jsxs(Fragment, { children: [
399
- /* @__PURE__ */ jsxs(FileUpload.Dropzone, { children: [
400
- /* @__PURE__ */ jsx(Icon, { size: "md", color: "fg.muted", children: /* @__PURE__ */ jsx(LuUpload, {}) }),
401
- /* @__PURE__ */ jsxs(FileUpload.DropzoneContent, { children: [
402
- /* @__PURE__ */ jsx(Box, { children: dropzoneLabel }),
403
- dropzoneDescription && /* @__PURE__ */ jsx(Text, { color: "fg.muted", children: dropzoneDescription })
404
- ] })
405
- ] }),
406
- isImageUpload ? /* @__PURE__ */ jsx(FileImageList, { clearable }) : /* @__PURE__ */ jsx(FileList, { showSize, clearable })
407
- ] }),
408
- variant === "input" && /* @__PURE__ */ jsx(Input, { asChild: true, children: /* @__PURE__ */ jsx(FileUpload.Trigger, { children: /* @__PURE__ */ jsx(FileUpload.Context, { children: ({ acceptedFiles }) => {
409
- if (acceptedFiles.length === 1) {
410
- return /* @__PURE__ */ jsx("span", { children: acceptedFiles[0].name });
411
- }
412
- if (acceptedFiles.length > 1) {
413
- return /* @__PURE__ */ jsxs("span", { children: [
414
- acceptedFiles.length,
415
- " files"
416
- ] });
417
- }
418
- return /* @__PURE__ */ jsx(Text, { color: "fg.subtle", children: placeholder });
419
- } }) }) })
420
- ]
421
- }
422
- ),
423
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
424
- ] });
425
- }
426
- });
427
- var FieldOTPInput = createField({
428
- displayName: "FieldOTPInput",
429
- useFieldState: (props) => {
430
- const [countdown, setCountdown] = useState(0);
431
- const [isResending, setIsResending] = useState(false);
432
- useEffect(() => {
433
- if (countdown <= 0) {
434
- return;
435
- }
436
- const timer = setInterval(() => {
437
- setCountdown((prev) => prev - 1);
438
- }, 1e3);
439
- return () => clearInterval(timer);
440
- }, [countdown]);
441
- const handleResend = useCallback(async () => {
442
- if (!props.onResend || countdown > 0) {
443
- return;
444
- }
445
- setIsResending(true);
446
- try {
447
- await props.onResend();
448
- setCountdown(props.resendTimeout ?? 60);
449
- } finally {
450
- setIsResending(false);
451
- }
452
- }, [props.onResend, countdown, props.resendTimeout]);
453
- const formatCountdown = (seconds) => {
454
- const mins = Math.floor(seconds / 60);
455
- const secs = seconds % 60;
456
- return `${mins}:${secs.toString().padStart(2, "0")}`;
457
- };
458
- const formContext = useDeclarativeForm();
459
- return { countdown, isResending, handleResend, formatCountdown, formContext };
460
- },
461
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
462
- const { length = 6, autoSubmit = false, type = "numeric", mask = false, onResend } = componentProps;
463
- const { countdown, isResending, handleResend, formatCountdown, formContext } = fieldState;
464
- const value = field.state.value ?? "";
465
- const handleValueComplete = (details) => {
466
- field.handleChange(details.valueAsString);
467
- if (autoSubmit && details.valueAsString.length === length) {
468
- formContext.form.handleSubmit();
469
- }
470
- };
471
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(Box, { children: [
472
- /* @__PURE__ */ jsxs(
473
- PinInput.Root,
474
- {
475
- value: value.split(""),
476
- onValueComplete: handleValueComplete,
477
- onValueChange: (details) => field.handleChange(details.valueAsString),
478
- count: length,
479
- type,
480
- mask,
481
- otp: true,
482
- children: [
483
- /* @__PURE__ */ jsx(PinInput.Control, { children: /* @__PURE__ */ jsx(HStack, { gap: 2, children: Array.from({ length }).map((_, index) => /* @__PURE__ */ jsx(PinInput.Input, { index, "data-field-name": index === 0 ? fullPath : void 0 }, index)) }) }),
484
- /* @__PURE__ */ jsx(PinInput.HiddenInput, {})
485
- ]
486
- }
487
- ),
488
- onResend && /* @__PURE__ */ jsx(HStack, { mt: 3, justify: "center", children: countdown > 0 ? /* @__PURE__ */ jsxs(Text, { fontSize: "sm", color: "fg.muted", children: [
489
- "Redo in ",
490
- formatCountdown(countdown)
491
- ] }) : /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: handleResend, disabled: isResending, loading: isResending, children: "Submit again" }) })
492
- ] }) });
493
- }
494
- });
495
- var PHONE_MASKS = {
496
- RU: "+7 (999) 999-99-99",
497
- US: "+1 (999) 999-9999",
498
- UK: "+44 9999 999999",
499
- DE: "+49 999 99999999",
500
- FR: "+33 9 99 99 99 99",
501
- IT: "+39 999 999 9999",
502
- ES: "+34 999 99 99 99",
503
- CN: "+86 999 9999 9999",
504
- JP: "+81 99 9999 9999",
505
- KR: "+82 99 9999 9999",
506
- BY: "+375 (99) 999-99-99",
507
- KZ: "+7 (999) 999-99-99",
508
- UA: "+380 (99) 999-99-99"
509
- };
510
- var COUNTRY_FLAGS = {
511
- RU: "\u{1F1F7}\u{1F1FA}",
512
- US: "\u{1F1FA}\u{1F1F8}",
513
- UK: "\u{1F1EC}\u{1F1E7}",
514
- DE: "\u{1F1E9}\u{1F1EA}",
515
- FR: "\u{1F1EB}\u{1F1F7}",
516
- IT: "\u{1F1EE}\u{1F1F9}",
517
- ES: "\u{1F1EA}\u{1F1F8}",
518
- CN: "\u{1F1E8}\u{1F1F3}",
519
- JP: "\u{1F1EF}\u{1F1F5}",
520
- KR: "\u{1F1F0}\u{1F1F7}",
521
- BY: "\u{1F1E7}\u{1F1FE}",
522
- KZ: "\u{1F1F0}\u{1F1FF}",
523
- UA: "\u{1F1FA}\u{1F1E6}"
524
- };
525
- var FieldPhone = createField({
526
- displayName: "FieldPhone",
527
- useFieldState: (props) => {
528
- const { country = "RU", autoUnmask = false } = props;
529
- const mask = PHONE_MASKS[country];
530
- const maskRef = useCallback(
531
- (element) => {
532
- if (element && mask) {
533
- const maskCallback = withMask(mask, {
534
- showMaskOnFocus: true,
535
- clearIncomplete: true,
536
- autoUnmask
537
- });
538
- maskCallback(element);
539
- }
540
- },
541
- [mask, autoUnmask]
542
- );
543
- return { maskRef };
544
- },
545
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps, fieldState }) => {
546
- const { country = "RU", showFlag = false } = componentProps;
547
- const flag = COUNTRY_FLAGS[country];
548
- const mask = PHONE_MASKS[country];
549
- const value = field.state.value ?? "";
550
- const resolvedPlaceholder = resolved.placeholder ?? mask?.toString().replace(/9/g, "_");
551
- return /* @__PURE__ */ jsxs(
552
- Field.Root,
553
- {
554
- invalid: hasError,
555
- required: resolved.required,
556
- disabled: resolved.disabled,
557
- readOnly: resolved.readOnly,
558
- children: [
559
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
560
- /* @__PURE__ */ jsxs(Group, { attached: true, children: [
561
- showFlag && /* @__PURE__ */ jsx(Text, { px: 3, display: "flex", alignItems: "center", bg: "bg.muted", borderWidth: "1px", borderRightWidth: "0", children: flag }),
562
- /* @__PURE__ */ jsx(
563
- Input,
564
- {
565
- ref: fieldState.maskRef,
566
- value,
567
- onChange: (e) => field.handleChange(e.target.value),
568
- onBlur: field.handleBlur,
569
- placeholder: resolvedPlaceholder,
570
- "data-field-name": fullPath,
571
- type: "tel",
572
- inputMode: "tel",
573
- autoComplete: "tel"
574
- }
575
- )
576
- ] }),
577
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
578
- ]
579
- }
580
- );
581
- }
582
- });
583
- var FieldPinInput = createField({
584
- displayName: "FieldPinInput",
585
- render: ({ field, fullPath, resolved, hasError, errorMessage, componentProps }) => {
586
- const {
587
- count = 4,
588
- mask,
589
- otp,
590
- type = "numeric",
591
- size = "md",
592
- variant = "outline",
593
- attached,
594
- onComplete
595
- } = componentProps;
596
- const stringValue = field.state.value ?? "";
597
- const arrayValue = stringValue.split("").slice(0, count);
598
- while (arrayValue.length < count) {
599
- arrayValue.push("");
600
- }
601
- const handleValueChange = (details) => {
602
- const newValue = details.value.join("");
603
- field.handleChange(newValue);
604
- };
605
- const handleValueComplete = (details) => {
606
- const completeValue = details.value.join("");
607
- onComplete?.(completeValue);
608
- };
609
- return /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError, errorMessage, fullPath, children: /* @__PURE__ */ jsxs(
610
- PinInput.Root,
611
- {
612
- value: arrayValue,
613
- onValueChange: handleValueChange,
614
- onValueComplete: handleValueComplete,
615
- placeholder: resolved.placeholder,
616
- mask,
617
- otp,
618
- type,
619
- size,
620
- variant,
621
- attached,
622
- disabled: resolved.disabled,
623
- readOnly: resolved.readOnly,
624
- invalid: hasError,
625
- count,
626
- onBlur: field.handleBlur,
627
- "data-field-name": fullPath,
628
- children: [
629
- /* @__PURE__ */ jsx(PinInput.HiddenInput, {}),
630
- /* @__PURE__ */ jsx(PinInput.Control, { children: Array.from({ length: count }).map((_, index) => /* @__PURE__ */ jsx(PinInput.Input, { index }, index)) })
631
- ]
632
- }
633
- ) });
634
- }
635
- });
636
- function useCityProvider(propProvider, token) {
637
- const formContext = useDeclarativeFormOptional();
638
- if (propProvider) return propProvider;
639
- if (formContext?.addressProvider) return formContext.addressProvider;
640
- if (token) return createDaDataProvider({ token });
641
- const envKey = typeof window !== "undefined" ? process.env.NEXT_PUBLIC_DADATA_API_KEY : "";
642
- if (envKey) return createDaDataProvider({ token: envKey });
643
- return null;
644
- }
645
- var FieldCity = createField({
646
- displayName: "FieldCity",
647
- useFieldState: (props) => {
648
- const { provider: propProvider, token, minChars = 2, debounceMs = 300 } = props;
649
- const provider = useCityProvider(propProvider, token);
650
- const [inputValue, setInputValue] = useState("");
651
- const [suggestions, setSuggestions] = useState([]);
652
- const [isLoading, setIsLoading] = useState(false);
653
- const [isOpen, setIsOpen] = useState(false);
654
- const [highlightedIndex, setHighlightedIndex] = useState(-1);
655
- const containerRef = useRef(null);
656
- const debouncedQuery = useDebounce(inputValue, debounceMs);
657
- const justSelectedRef = useRef(false);
658
- const initializedRef = useRef(false);
659
- const fetchSuggestions = useCallback(
660
- async (query) => {
661
- if (query.length < minChars || !provider) {
662
- setSuggestions([]);
663
- return;
664
- }
665
- setIsLoading(true);
666
- try {
667
- const results = await provider.getSuggestions(query, {
668
- count: 7,
669
- bounds: { from: "city", to: "settlement" }
670
- });
671
- setSuggestions(results);
672
- setIsOpen(results.length > 0);
673
- } catch (error) {
674
- console.error("Error loading city suggestions:", error);
675
- setSuggestions([]);
676
- } finally {
677
- setIsLoading(false);
678
- }
679
- },
680
- [provider, minChars]
681
- );
682
- useEffect(() => {
683
- if (justSelectedRef.current) {
684
- justSelectedRef.current = false;
685
- return;
686
- }
687
- if (debouncedQuery) {
688
- fetchSuggestions(debouncedQuery);
689
- } else {
690
- setSuggestions([]);
691
- setIsOpen(false);
692
- }
693
- }, [debouncedQuery, fetchSuggestions]);
694
- useEffect(() => {
695
- const handleClickOutside = (event) => {
696
- if (containerRef.current && !containerRef.current.contains(event.target)) {
697
- setIsOpen(false);
698
- }
699
- };
700
- document.addEventListener("mousedown", handleClickOutside);
701
- return () => document.removeEventListener("mousedown", handleClickOutside);
702
- }, []);
703
- return {
704
- inputValue,
705
- setInputValue,
706
- suggestions,
707
- setSuggestions,
708
- isLoading,
709
- setIsLoading,
710
- isOpen,
711
- setIsOpen,
712
- highlightedIndex,
713
- setHighlightedIndex,
714
- containerRef,
715
- debouncedQuery,
716
- justSelectedRef,
717
- initializedRef
718
- };
719
- },
720
- render: ({ field, fullPath, resolved, hasError, errorMessage, fieldState }) => {
721
- const {
722
- inputValue,
723
- setInputValue,
724
- suggestions,
725
- setSuggestions,
726
- isLoading,
727
- isOpen,
728
- setIsOpen,
729
- highlightedIndex,
730
- setHighlightedIndex,
731
- containerRef
732
- } = fieldState;
733
- const { justSelectedRef, initializedRef } = fieldState;
734
- const fieldValue = field.state.value;
735
- if (!initializedRef.current && fieldValue && fieldValue !== inputValue) {
736
- initializedRef.current = true;
737
- setInputValue(fieldValue);
738
- }
739
- const handleSelect = (suggestion) => {
740
- const cityName = suggestion.data?.city || suggestion.data?.settlement || suggestion.value;
741
- justSelectedRef.current = true;
742
- setInputValue(cityName);
743
- setIsOpen(false);
744
- setSuggestions([]);
745
- field.handleChange(cityName);
746
- };
747
- const handleKeyDown = (e) => {
748
- if (!isOpen || suggestions.length === 0) {
749
- return;
750
- }
751
- switch (e.key) {
752
- case "ArrowDown":
753
- e.preventDefault();
754
- setHighlightedIndex(highlightedIndex < suggestions.length - 1 ? highlightedIndex + 1 : 0);
755
- break;
756
- case "ArrowUp":
757
- e.preventDefault();
758
- setHighlightedIndex(highlightedIndex > 0 ? highlightedIndex - 1 : suggestions.length - 1);
759
- break;
760
- case "Enter":
761
- e.preventDefault();
762
- if (highlightedIndex >= 0) {
763
- handleSelect(suggestions[highlightedIndex]);
764
- }
765
- break;
766
- case "Escape":
767
- setIsOpen(false);
768
- break;
769
- }
770
- };
771
- return /* @__PURE__ */ jsxs(
772
- Field.Root,
773
- {
774
- invalid: hasError,
775
- required: resolved.required,
776
- disabled: resolved.disabled,
777
- readOnly: resolved.readOnly,
778
- children: [
779
- /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
780
- /* @__PURE__ */ jsxs(Box, { ref: containerRef, position: "relative", width: "100%", children: [
781
- /* @__PURE__ */ jsx(
782
- Input,
783
- {
784
- value: inputValue,
785
- onChange: (e) => {
786
- setInputValue(e.target.value);
787
- setHighlightedIndex(-1);
788
- if (!e.target.value) {
789
- field.handleChange("");
790
- }
791
- },
792
- onFocus: () => {
793
- if (suggestions.length > 0) {
794
- setIsOpen(true);
795
- }
796
- },
797
- onBlur: () => {
798
- if (inputValue && inputValue !== field.state.value) {
799
- field.handleChange(inputValue);
800
- }
801
- field.handleBlur();
802
- },
803
- onKeyDown: handleKeyDown,
804
- placeholder: resolved.placeholder ?? "Enter city",
805
- "data-field-name": fullPath
806
- }
807
- ),
808
- isLoading && /* @__PURE__ */ jsx(Box, { position: "absolute", right: 3, top: "50%", transform: "translateY(-50%)", children: /* @__PURE__ */ jsx(Spinner, { size: "sm" }) }),
809
- isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx(
810
- List.Root,
811
- {
812
- position: "absolute",
813
- zIndex: 10,
814
- width: "100%",
815
- bg: "bg.panel",
816
- borderWidth: "1px",
817
- borderRadius: "md",
818
- shadow: "md",
819
- maxH: "250px",
820
- overflowY: "auto",
821
- mt: 1,
822
- listStyle: "none",
823
- children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsx(
824
- List.Item,
825
- {
826
- px: 3,
827
- py: 2,
828
- cursor: "pointer",
829
- bg: highlightedIndex === index ? "bg.muted" : void 0,
830
- _hover: { bg: "bg.muted" },
831
- onClick: () => handleSelect(suggestion),
832
- onMouseEnter: () => setHighlightedIndex(index),
833
- children: /* @__PURE__ */ jsx(Text, { fontSize: "sm", children: suggestion.label })
834
- },
835
- `${suggestion.value}-${index}`
836
- ))
837
- }
838
- )
839
- ] }),
840
- /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
841
- ]
842
- }
843
- );
844
- }
845
- });
846
-
847
- export { FieldAddress, FieldCity, FieldColorPicker, FieldFileUpload, FieldOTPInput, FieldPhone, FieldPinInput };
848
- //# sourceMappingURL=chunk-GOELIS6T.js.map
849
- //# sourceMappingURL=chunk-GOELIS6T.js.map