@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/index.js CHANGED
@@ -1,21 +1,25 @@
1
- import { FieldFileUpload, FieldColorPicker, FieldOTPInput, FieldPinInput, FieldCity, FieldAddress, FieldPhone } from './chunk-GOELIS6T.js';
2
- import { FormSyncStatus, FormOfflineIndicator, useOfflineForm } from './chunk-4V6WBJ76.js';
3
- export { FormOfflineIndicator, FormSyncStatus, useOfflineForm, useOfflineStatus, useSyncQueue } from './chunk-4V6WBJ76.js';
4
- import { FieldRichText, FieldEditable, FieldMaskedInput, FieldPasswordStrength, FieldPassword, FieldTextarea, FieldString } from './chunk-M2PNAAIR.js';
5
- export { FieldString } from './chunk-M2PNAAIR.js';
6
- import { FieldRating, FieldSlider, FieldPercentage, FieldCurrency, FieldNumberInput, FieldNumber } from './chunk-XKKJKYWZ.js';
7
- export { FieldNumber } from './chunk-XKKJKYWZ.js';
8
- import { FieldSchedule, FieldDuration, FieldTime, FieldDateTimePicker, FieldDateRange, FieldDate } from './chunk-KUNT5MSU.js';
9
- import { FieldTags, FieldSegmentedGroup, FieldRadioCard, FieldRadioGroup, FieldListbox, FieldAutocomplete, FieldCombobox, FieldCascadingSelect, FieldNativeSelect, FieldSelect, FieldCheckboxCard } from './chunk-PJETA6YN.js';
10
- export { FieldCombobox, FieldListbox, FieldSegmentedGroup, FieldSelect } from './chunk-PJETA6YN.js';
11
- import { FieldSwitch, FieldCheckbox } from './chunk-6QOPSQ3Z.js';
12
- import { useDeclarativeForm, useFormGroup, getZodConstraints, FormGroup, DeclarativeFormContext, unwrapSchema } from './chunk-HWVOFWAT.js';
13
- export { DeclarativeFormContext, FieldLabel, FieldTooltip, FormGroup, useAsyncSearch, useDebounce, useDeclarativeField, useDeclarativeForm, useDeclarativeFormOptional, useFormGroup } from './chunk-HWVOFWAT.js';
14
- export { FormI18nProvider, getLocalizedValue, useFormI18n, useLocalizedOptions } from './chunk-7FEQFDJ7.js';
1
+ import { CreditCardField, FieldSignature, FieldFileUpload, FieldColorPicker, FieldOTPInput, FieldPinInput, FieldCity, FieldAddress, FieldPhone } from './chunk-2PSXYC3I.js';
2
+ export { CardBrandIcon, CreditCardField, creditCardSchema, detectBrand, formatCardNumber, formatExpiry, isExpiryValid, luhn, parseFileSize, processFileWithSecurity, sanitizeFileName, validateMimeType } from './chunk-2PSXYC3I.js';
3
+ export { applyServerErrors, mapServerErrors } from './chunk-5D6S6EGF.js';
4
+ export { AnalyticsPanel, createGtagAdapter, createPostHogAdapter, createUmamiAdapter, createYandexMetrikaAdapter, useFormAnalytics } from './chunk-K3J4L26K.js';
5
+ import { validateSnils, validateBik, validateOgrn, validateKpp, validateInn10, validateInn12 } from './chunk-CGXKRCSM.js';
6
+ import { FormSyncStatus, FormOfflineIndicator, useOfflineForm } from './chunk-MVGXZNHP.js';
7
+ export { FormOfflineIndicator, FormSyncStatus, useOfflineForm, useOfflineStatus, useSyncQueue } from './chunk-MVGXZNHP.js';
8
+ import { FieldRichText, FieldEditable, FieldMaskedInput, FieldPasswordStrength, FieldPassword, FieldTextarea, FieldString } from './chunk-DQUVUMCX.js';
9
+ export { FieldString } from './chunk-DQUVUMCX.js';
10
+ import { FieldRating, FieldSlider, FieldPercentage, FieldCurrency, FieldNumberInput, FieldNumber } from './chunk-MZDTJSF7.js';
11
+ export { FieldNumber } from './chunk-MZDTJSF7.js';
12
+ import { FieldSchedule, FieldDuration, FieldTime, FieldDateTimePicker, FieldDateRange, FieldDate } from './chunk-Q5EOF36Y.js';
13
+ import { FieldTags, FieldSegmentedGroup, FieldRadioCard, FieldRadioGroup, FieldListbox, FieldAutocomplete, FieldCombobox, FieldCascadingSelect, FieldNativeSelect, FieldSelect, FieldCheckboxCard } from './chunk-MAYUFA5K.js';
14
+ export { FieldCombobox, FieldListbox, FieldSegmentedGroup, FieldSelect } from './chunk-MAYUFA5K.js';
15
+ import { FieldSwitch, FieldCheckbox } from './chunk-6E7VJAJT.js';
16
+ import { createField, FieldLabel, FieldError, useDeclarativeForm, useFormGroup, getZodConstraints, FormGroup, useDeclarativeFormOptional, useResolvedFieldProps, FieldWrapper, hasFieldErrors, formatFieldErrors, DeclarativeFormContext, unwrapSchema, useDebounce } from './chunk-XFWLD5EO.js';
17
+ export { DeclarativeFormContext, FieldLabel, FieldTooltip, FormGroup, useAsyncSearch, useDebounce, useDeclarativeField, useDeclarativeForm, useDeclarativeFormOptional, useFormGroup } from './chunk-XFWLD5EO.js';
18
+ export { FormI18nProvider, getLocalizedValue, useFormI18n, useLocalizedOptions } from './chunk-R2RTCKXY.js';
15
19
  import { createFormHookContexts, createFormHook } from '@tanstack/react-form';
16
- import { createContext, forwardRef, useContext, Fragment, useState, useRef, useMemo, useCallback, useEffect, Children, isValidElement, useSyncExternalStore, lazy, Suspense } from 'react';
20
+ import { createContext, forwardRef, lazy, useContext, useCallback, Fragment, useState, useRef, useMemo, useEffect, Suspense, useSyncExternalStore, Children, isValidElement, useId } from 'react';
17
21
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
18
- import { Field, IconButton, Button, Steps, ButtonGroup, Box, VStack, HStack, Heading, Alert, List, Text, Dialog, Portal, CloseButton, Skeleton } from '@chakra-ui/react';
22
+ import { Field, InputGroup, Input, Icon, IconButton, Button, Steps, ButtonGroup, Box, VStack, HStack, Separator, Flex, Text, Alert, Heading, List, Checkbox, Table, SimpleGrid, Image, RadioGroup, Circle, Dialog, Portal, CloseButton, NativeSelect, Progress, Spinner, Skeleton } from '@chakra-ui/react';
19
23
  import { useRouter } from 'next/navigation';
20
24
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from '@dnd-kit/core';
21
25
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
@@ -23,7 +27,10 @@ import { CSS } from '@dnd-kit/utilities';
23
27
  import JsonView from '@uiw/react-json-view';
24
28
  import { githubDarkTheme } from '@uiw/react-json-view/githubDark';
25
29
  import { githubLightTheme } from '@uiw/react-json-view/githubLight';
26
- import { LuCheck } from 'react-icons/lu';
30
+ import { withMask } from 'use-mask-input';
31
+ import { LuCheck, LuBookOpen, LuShield, LuLandmark, LuFileText } from 'react-icons/lu';
32
+ import { useReactTable, getPaginationRowModel, getFilteredRowModel, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
33
+ import { useVirtualizer } from '@tanstack/react-virtual';
27
34
  import { AnimatePresence, motion } from 'framer-motion';
28
35
  import { z } from 'zod/v4';
29
36
 
@@ -180,6 +187,80 @@ function useFormGroupList() {
180
187
  function useFormGroupListItem() {
181
188
  return useContext(FormGroupListItemContext);
182
189
  }
190
+ var CaptchaContext = createContext(void 0);
191
+ function useCaptchaConfig() {
192
+ return useContext(CaptchaContext);
193
+ }
194
+
195
+ // src/lib/captcha/types.ts
196
+ var CAPTCHA_TOKEN_FIELD = "__captchaToken";
197
+ var providers = {
198
+ turnstile: lazy(() => import('./turnstile-7FXTBSLW.js').then((m) => ({ default: m.TurnstileProvider }))),
199
+ recaptcha: lazy(() => import('./recaptcha-PKAUAY2S.js').then((m) => ({ default: m.RecaptchaProvider }))),
200
+ hcaptcha: lazy(() => import('./hcaptcha-U4XIT3HS.js').then((m) => ({ default: m.HcaptchaProvider })))
201
+ };
202
+ function CaptchaField(props) {
203
+ const config = useCaptchaConfig();
204
+ const formCtx = useDeclarativeFormOptional();
205
+ const provider = props.provider ?? config?.provider;
206
+ const siteKey = props.siteKey ?? config?.siteKey;
207
+ const theme = props.theme ?? config?.theme ?? "auto";
208
+ const size = props.size ?? config?.size ?? "normal";
209
+ const language = props.language ?? config?.language;
210
+ const [, setToken] = useState(null);
211
+ const handleSuccess = useCallback(
212
+ (captchaToken) => {
213
+ setToken(captchaToken);
214
+ if (formCtx?.form) {
215
+ formCtx.form.setFieldValue(CAPTCHA_TOKEN_FIELD, captchaToken);
216
+ }
217
+ props.onSuccess?.(captchaToken);
218
+ },
219
+ [formCtx, props.onSuccess]
220
+ );
221
+ const handleError = useCallback(
222
+ (error) => {
223
+ setToken(null);
224
+ if (formCtx?.form) {
225
+ formCtx.form.setFieldValue(CAPTCHA_TOKEN_FIELD, "");
226
+ }
227
+ props.onError?.(error);
228
+ },
229
+ [formCtx, props.onError]
230
+ );
231
+ const handleExpire = useCallback(() => {
232
+ setToken(null);
233
+ if (formCtx?.form) {
234
+ formCtx.form.setFieldValue(CAPTCHA_TOKEN_FIELD, "");
235
+ }
236
+ props.onExpire?.();
237
+ }, [formCtx, props.onExpire]);
238
+ if (!provider || !siteKey) {
239
+ if (process.env.NODE_ENV === "development") {
240
+ console.warn(
241
+ "[Form.Captcha] provider \u0438 siteKey \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B. \u041F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u043F\u0440\u043E\u043F\u0441\u044B \u0438\u043B\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u0442\u0435 captcha \u0432 createForm()."
242
+ );
243
+ }
244
+ return null;
245
+ }
246
+ const ProviderComponent = providers[provider];
247
+ if (!ProviderComponent) {
248
+ console.error(`[Form.Captcha] \u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440: ${provider}`);
249
+ return null;
250
+ }
251
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { style: { height: 65, width: 300 } }), children: /* @__PURE__ */ jsx(
252
+ ProviderComponent,
253
+ {
254
+ siteKey,
255
+ theme,
256
+ size,
257
+ language,
258
+ onSuccess: handleSuccess,
259
+ onError: handleError,
260
+ onExpire: handleExpire
261
+ }
262
+ ) });
263
+ }
183
264
  function DirtyGuard({
184
265
  message = "You have unsaved changes. Are you sure you want to leave?",
185
266
  dialogTitle = "Unsaved changes",
@@ -1364,6 +1445,635 @@ function ButtonSubmit({
1364
1445
  }
1365
1446
  ) });
1366
1447
  }
1448
+ function FieldImageChoice({
1449
+ name,
1450
+ label,
1451
+ helperText,
1452
+ required,
1453
+ disabled,
1454
+ readOnly,
1455
+ options,
1456
+ columns = 3,
1457
+ multiple = false
1458
+ }) {
1459
+ const { form } = useDeclarativeForm();
1460
+ useFormGroup();
1461
+ const {
1462
+ fullPath,
1463
+ label: resolvedLabel,
1464
+ helperText: resolvedHelperText,
1465
+ required: resolvedRequired,
1466
+ disabled: resolvedDisabled,
1467
+ readOnly: resolvedReadOnly
1468
+ } = useResolvedFieldProps(name, { label, helperText, required, disabled, readOnly });
1469
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
1470
+ const errors = field.state.meta.errors;
1471
+ const hasError = hasFieldErrors(errors);
1472
+ const value = field.state.value;
1473
+ const isSelected = (optValue) => {
1474
+ if (multiple) {
1475
+ return Array.isArray(value) && value.includes(optValue);
1476
+ }
1477
+ return value === optValue;
1478
+ };
1479
+ const handleSelect = (optValue) => {
1480
+ if (resolvedDisabled || resolvedReadOnly) return;
1481
+ if (multiple) {
1482
+ const current = Array.isArray(value) ? value : [];
1483
+ const next = current.includes(optValue) ? current.filter((v) => v !== optValue) : [...current, optValue];
1484
+ field.handleChange(next);
1485
+ } else {
1486
+ field.handleChange(optValue);
1487
+ }
1488
+ };
1489
+ return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, children: [
1490
+ resolvedLabel && /* @__PURE__ */ jsx(FieldLabel, { label: resolvedLabel, required: resolvedRequired }),
1491
+ /* @__PURE__ */ jsx(SimpleGrid, { columns: { base: 1, sm: 2, md: columns }, gap: 3, children: options.map((opt) => {
1492
+ const selected = isSelected(opt.value);
1493
+ return /* @__PURE__ */ jsxs(
1494
+ Box,
1495
+ {
1496
+ borderWidth: "2px",
1497
+ borderColor: selected ? "blue.500" : "border",
1498
+ borderRadius: "lg",
1499
+ overflow: "hidden",
1500
+ cursor: resolvedDisabled ? "default" : "pointer",
1501
+ onClick: () => handleSelect(opt.value),
1502
+ transition: "all 0.2s",
1503
+ _hover: resolvedDisabled ? void 0 : { borderColor: "blue.400", shadow: "md" },
1504
+ opacity: resolvedDisabled ? 0.5 : 1,
1505
+ position: "relative",
1506
+ children: [
1507
+ /* @__PURE__ */ jsx(
1508
+ Image,
1509
+ {
1510
+ src: opt.image,
1511
+ alt: opt.label,
1512
+ w: "100%",
1513
+ h: "120px",
1514
+ objectFit: "cover"
1515
+ }
1516
+ ),
1517
+ selected && /* @__PURE__ */ jsx(
1518
+ Box,
1519
+ {
1520
+ position: "absolute",
1521
+ top: "8px",
1522
+ right: "8px",
1523
+ bg: "blue.500",
1524
+ color: "white",
1525
+ borderRadius: "full",
1526
+ w: "24px",
1527
+ h: "24px",
1528
+ display: "flex",
1529
+ alignItems: "center",
1530
+ justifyContent: "center",
1531
+ fontSize: "sm",
1532
+ fontWeight: "bold",
1533
+ children: "\u2713"
1534
+ }
1535
+ ),
1536
+ /* @__PURE__ */ jsxs(Box, { p: 2, children: [
1537
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", fontWeight: "medium", children: opt.label }),
1538
+ opt.description && /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: opt.description })
1539
+ ] })
1540
+ ]
1541
+ },
1542
+ opt.value
1543
+ );
1544
+ }) }),
1545
+ /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage: formatFieldErrors(errors), helperText: resolvedHelperText })
1546
+ ] });
1547
+ } });
1548
+ }
1549
+ function FieldLikert({
1550
+ name,
1551
+ label,
1552
+ helperText,
1553
+ required,
1554
+ disabled,
1555
+ readOnly,
1556
+ anchors,
1557
+ showNumbers = false
1558
+ }) {
1559
+ const { form } = useDeclarativeForm();
1560
+ useFormGroup();
1561
+ const {
1562
+ fullPath,
1563
+ label: resolvedLabel,
1564
+ helperText: resolvedHelperText,
1565
+ required: resolvedRequired,
1566
+ disabled: resolvedDisabled,
1567
+ readOnly: resolvedReadOnly
1568
+ } = useResolvedFieldProps(name, { label, helperText, required, disabled, readOnly });
1569
+ anchors.length;
1570
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
1571
+ const errors = field.state.meta.errors;
1572
+ const hasError = hasFieldErrors(errors);
1573
+ const value = field.state.value;
1574
+ const handleSelect = (point) => {
1575
+ if (resolvedDisabled || resolvedReadOnly) return;
1576
+ field.handleChange(point);
1577
+ };
1578
+ return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, children: [
1579
+ resolvedLabel && /* @__PURE__ */ jsx(FieldLabel, { label: resolvedLabel, required: resolvedRequired }),
1580
+ /* @__PURE__ */ jsxs(Box, { display: { base: "none", md: "block" }, children: [
1581
+ /* @__PURE__ */ jsx(Flex, { justify: "space-between", align: "center", py: 2, children: anchors.map((anchor, i) => {
1582
+ const point = i + 1;
1583
+ const isSelected = value === point;
1584
+ return /* @__PURE__ */ jsxs(
1585
+ Flex,
1586
+ {
1587
+ direction: "column",
1588
+ align: "center",
1589
+ gap: 1,
1590
+ flex: 1,
1591
+ cursor: resolvedDisabled ? "default" : "pointer",
1592
+ onClick: () => handleSelect(point),
1593
+ opacity: resolvedDisabled ? 0.5 : 1,
1594
+ children: [
1595
+ showNumbers && /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: point }),
1596
+ /* @__PURE__ */ jsx(
1597
+ Box,
1598
+ {
1599
+ w: "32px",
1600
+ h: "32px",
1601
+ borderRadius: "full",
1602
+ borderWidth: "2px",
1603
+ borderColor: isSelected ? "blue.500" : "border",
1604
+ bg: isSelected ? "blue.500" : "transparent",
1605
+ transition: "all 0.15s",
1606
+ _hover: resolvedDisabled ? void 0 : { borderColor: "blue.400", transform: "scale(1.1)" }
1607
+ }
1608
+ ),
1609
+ /* @__PURE__ */ jsx(
1610
+ Text,
1611
+ {
1612
+ fontSize: "xs",
1613
+ textAlign: "center",
1614
+ color: isSelected ? "blue.600" : "fg.muted",
1615
+ fontWeight: isSelected ? "medium" : "normal",
1616
+ maxW: "80px",
1617
+ children: anchor
1618
+ }
1619
+ )
1620
+ ]
1621
+ },
1622
+ point
1623
+ );
1624
+ }) }),
1625
+ /* @__PURE__ */ jsx(Box, { h: "2px", bg: "border", mt: "-28px", mb: "24px", mx: "16px", borderRadius: "full" })
1626
+ ] }),
1627
+ /* @__PURE__ */ jsx(Box, { display: { base: "block", md: "none" }, children: anchors.map((anchor, i) => {
1628
+ const point = i + 1;
1629
+ const isSelected = value === point;
1630
+ return /* @__PURE__ */ jsxs(
1631
+ HStack,
1632
+ {
1633
+ p: 2,
1634
+ borderRadius: "md",
1635
+ cursor: resolvedDisabled ? "default" : "pointer",
1636
+ onClick: () => handleSelect(point),
1637
+ bg: isSelected ? "blue.50" : "transparent",
1638
+ _dark: isSelected ? { bg: "blue.900/20" } : void 0,
1639
+ _hover: resolvedDisabled ? void 0 : { bg: "bg.subtle" },
1640
+ gap: 3,
1641
+ children: [
1642
+ /* @__PURE__ */ jsx(
1643
+ Box,
1644
+ {
1645
+ w: "24px",
1646
+ h: "24px",
1647
+ borderRadius: "full",
1648
+ borderWidth: "2px",
1649
+ borderColor: isSelected ? "blue.500" : "border",
1650
+ bg: isSelected ? "blue.500" : "transparent",
1651
+ flexShrink: 0
1652
+ }
1653
+ ),
1654
+ /* @__PURE__ */ jsxs(Text, { fontSize: "sm", color: isSelected ? "blue.600" : "fg", children: [
1655
+ showNumbers && `${point}. `,
1656
+ anchor
1657
+ ] })
1658
+ ]
1659
+ },
1660
+ point
1661
+ );
1662
+ }) }),
1663
+ /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage: formatFieldErrors(errors), helperText: resolvedHelperText })
1664
+ ] });
1665
+ } });
1666
+ }
1667
+ function FieldMatrixChoice({
1668
+ name,
1669
+ label,
1670
+ helperText,
1671
+ required,
1672
+ disabled,
1673
+ readOnly,
1674
+ rows,
1675
+ columns,
1676
+ variant = "radio"
1677
+ }) {
1678
+ const { form } = useDeclarativeForm();
1679
+ const {
1680
+ fullPath,
1681
+ label: resolvedLabel,
1682
+ helperText: resolvedHelperText,
1683
+ required: resolvedRequired,
1684
+ disabled: resolvedDisabled,
1685
+ readOnly: resolvedReadOnly
1686
+ } = useResolvedFieldProps(name, { label, helperText, required, disabled, readOnly });
1687
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
1688
+ const errors = field.state.meta.errors;
1689
+ const hasError = hasFieldErrors(errors);
1690
+ const value = field.state.value ?? {};
1691
+ const setRowValue = (rowValue, colValue) => {
1692
+ if (resolvedDisabled || resolvedReadOnly) return;
1693
+ if (variant === "checkbox") {
1694
+ const current = value[rowValue] ?? [];
1695
+ const next = current.includes(colValue) ? current.filter((v) => v !== colValue) : [...current, colValue];
1696
+ field.handleChange({ ...value, [rowValue]: next });
1697
+ } else {
1698
+ field.handleChange({ ...value, [rowValue]: colValue });
1699
+ }
1700
+ };
1701
+ const isSelected = (rowValue, colValue) => {
1702
+ const rowVal = value[rowValue];
1703
+ if (variant === "checkbox") {
1704
+ return Array.isArray(rowVal) && rowVal.includes(colValue);
1705
+ }
1706
+ return rowVal === colValue;
1707
+ };
1708
+ const mobileView = /* @__PURE__ */ jsx(VStack, { gap: 4, align: "stretch", display: { base: "flex", md: "none" }, children: rows.map((row) => /* @__PURE__ */ jsxs(Box, { p: 3, borderWidth: "1px", borderRadius: "md", children: [
1709
+ /* @__PURE__ */ jsx(Text, { fontWeight: "medium", mb: 2, children: row.label }),
1710
+ variant === "checkbox" ? /* @__PURE__ */ jsx(VStack, { align: "start", gap: 1, children: columns.map((col) => /* @__PURE__ */ jsxs(
1711
+ Checkbox.Root,
1712
+ {
1713
+ checked: isSelected(row.value, col.value),
1714
+ onCheckedChange: () => setRowValue(row.value, col.value),
1715
+ disabled: !!resolvedDisabled,
1716
+ size: "sm",
1717
+ children: [
1718
+ /* @__PURE__ */ jsx(Checkbox.HiddenInput, {}),
1719
+ /* @__PURE__ */ jsx(Checkbox.Control, {}),
1720
+ /* @__PURE__ */ jsx(Checkbox.Label, { children: col.label })
1721
+ ]
1722
+ },
1723
+ col.value
1724
+ )) }) : /* @__PURE__ */ jsx(
1725
+ RadioGroup.Root,
1726
+ {
1727
+ value: String(value[row.value] ?? ""),
1728
+ onValueChange: (details) => {
1729
+ if (details.value) setRowValue(row.value, details.value);
1730
+ },
1731
+ disabled: !!resolvedDisabled,
1732
+ size: "sm",
1733
+ children: /* @__PURE__ */ jsx(VStack, { align: "start", gap: 1, children: columns.map((col) => /* @__PURE__ */ jsxs(RadioGroup.Item, { value: col.value, children: [
1734
+ /* @__PURE__ */ jsx(RadioGroup.ItemHiddenInput, {}),
1735
+ /* @__PURE__ */ jsx(RadioGroup.ItemIndicator, {}),
1736
+ /* @__PURE__ */ jsx(RadioGroup.ItemText, { children: col.label })
1737
+ ] }, col.value)) })
1738
+ }
1739
+ )
1740
+ ] }, row.value)) });
1741
+ const handleMatrixKeyDown = (e) => {
1742
+ const target = e.target;
1743
+ const cell = target.closest("[data-matrix-row][data-matrix-col]");
1744
+ if (!cell) return;
1745
+ const currentRow = cell.dataset.matrixRow;
1746
+ const currentCol = cell.dataset.matrixCol;
1747
+ const rowIdx = rows.findIndex((r) => r.value === currentRow);
1748
+ const colIdx = columns.findIndex((c) => c.value === currentCol);
1749
+ let nextRowIdx = rowIdx;
1750
+ let nextColIdx = colIdx;
1751
+ switch (e.key) {
1752
+ case "ArrowRight":
1753
+ nextColIdx = Math.min(colIdx + 1, columns.length - 1);
1754
+ break;
1755
+ case "ArrowLeft":
1756
+ nextColIdx = Math.max(colIdx - 1, 0);
1757
+ break;
1758
+ case "ArrowDown":
1759
+ nextRowIdx = Math.min(rowIdx + 1, rows.length - 1);
1760
+ break;
1761
+ case "ArrowUp":
1762
+ nextRowIdx = Math.max(rowIdx - 1, 0);
1763
+ break;
1764
+ case " ":
1765
+ case "Enter":
1766
+ e.preventDefault();
1767
+ setRowValue(rows[rowIdx].value, columns[colIdx].value);
1768
+ return;
1769
+ default:
1770
+ return;
1771
+ }
1772
+ e.preventDefault();
1773
+ const nextCell = document.querySelector(
1774
+ `[data-matrix-row="${rows[nextRowIdx].value}"][data-matrix-col="${columns[nextColIdx].value}"]`
1775
+ );
1776
+ nextCell?.focus();
1777
+ };
1778
+ const desktopView = /* @__PURE__ */ jsx(Box, { display: { base: "none", md: "block" }, overflowX: "auto", children: /* @__PURE__ */ jsxs(Table.Root, { size: "sm", variant: "outline", onKeyDown: handleMatrixKeyDown, children: [
1779
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
1780
+ /* @__PURE__ */ jsx(Table.ColumnHeader, { w: "40%" }),
1781
+ columns.map((col) => /* @__PURE__ */ jsx(Table.ColumnHeader, { textAlign: "center", children: col.label }, col.value))
1782
+ ] }) }),
1783
+ /* @__PURE__ */ jsx(Table.Body, { children: rows.map((row) => {
1784
+ const rowValue = value[row.value];
1785
+ const isRowEmpty = variant === "checkbox" ? !Array.isArray(rowValue) || rowValue.length === 0 : !rowValue;
1786
+ const showRowError = resolvedRequired && hasError && isRowEmpty;
1787
+ return /* @__PURE__ */ jsxs(
1788
+ Table.Row,
1789
+ {
1790
+ _hover: { bg: "bg.subtle" },
1791
+ bg: showRowError ? "red.50" : void 0,
1792
+ _dark: showRowError ? { bg: "red.900/10" } : void 0,
1793
+ children: [
1794
+ /* @__PURE__ */ jsx(Table.Cell, { fontWeight: "medium", color: showRowError ? "red.500" : void 0, children: row.label }),
1795
+ columns.map((col) => /* @__PURE__ */ jsx(
1796
+ Table.Cell,
1797
+ {
1798
+ textAlign: "center",
1799
+ "data-matrix-row": row.value,
1800
+ "data-matrix-col": col.value,
1801
+ tabIndex: 0,
1802
+ role: "gridcell",
1803
+ children: variant === "checkbox" ? /* @__PURE__ */ jsxs(
1804
+ Checkbox.Root,
1805
+ {
1806
+ checked: isSelected(row.value, col.value),
1807
+ onCheckedChange: () => setRowValue(row.value, col.value),
1808
+ disabled: !!resolvedDisabled,
1809
+ size: "sm",
1810
+ children: [
1811
+ /* @__PURE__ */ jsx(Checkbox.HiddenInput, {}),
1812
+ /* @__PURE__ */ jsx(Checkbox.Control, {})
1813
+ ]
1814
+ }
1815
+ ) : variant === "rating" ? /* @__PURE__ */ jsx(
1816
+ Box,
1817
+ {
1818
+ cursor: resolvedDisabled ? "default" : "pointer",
1819
+ onClick: () => setRowValue(row.value, col.value),
1820
+ fontSize: "lg",
1821
+ color: isSelected(row.value, col.value) ? "yellow.400" : "gray.300",
1822
+ _hover: resolvedDisabled ? void 0 : { color: "yellow.400" },
1823
+ children: "\u2605"
1824
+ }
1825
+ ) : /* @__PURE__ */ jsx(
1826
+ Circle,
1827
+ {
1828
+ size: "18px",
1829
+ borderWidth: "2px",
1830
+ borderColor: isSelected(row.value, col.value) ? "blue.500" : "border",
1831
+ bg: isSelected(row.value, col.value) ? "blue.500" : "transparent",
1832
+ cursor: resolvedDisabled ? "default" : "pointer",
1833
+ onClick: () => setRowValue(row.value, col.value),
1834
+ transition: "all 0.15s",
1835
+ _hover: resolvedDisabled ? void 0 : { borderColor: "blue.400" },
1836
+ "aria-checked": isSelected(row.value, col.value),
1837
+ role: "radio",
1838
+ children: isSelected(row.value, col.value) && /* @__PURE__ */ jsx(Circle, { size: "8px", bg: "white" })
1839
+ }
1840
+ )
1841
+ },
1842
+ col.value
1843
+ ))
1844
+ ]
1845
+ },
1846
+ row.value
1847
+ );
1848
+ }) })
1849
+ ] }) });
1850
+ return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, children: [
1851
+ resolvedLabel && /* @__PURE__ */ jsx(FieldLabel, { label: resolvedLabel, required: resolvedRequired }),
1852
+ desktopView,
1853
+ mobileView,
1854
+ /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage: formatFieldErrors(errors), helperText: resolvedHelperText })
1855
+ ] });
1856
+ } });
1857
+ }
1858
+ var VARIANT_CONTENT = {
1859
+ buttons: { yes: null, no: null },
1860
+ thumbs: { yes: "\u{1F44D}", no: "\u{1F44E}" },
1861
+ emoji: { yes: "\u{1F60A}", no: "\u{1F61E}" }
1862
+ };
1863
+ function FieldYesNo({
1864
+ name,
1865
+ label,
1866
+ helperText,
1867
+ required,
1868
+ disabled,
1869
+ readOnly,
1870
+ yesLabel = "\u0414\u0430",
1871
+ noLabel = "\u041D\u0435\u0442",
1872
+ variant = "buttons"
1873
+ }) {
1874
+ const { form } = useDeclarativeForm();
1875
+ useFormGroup();
1876
+ const {
1877
+ fullPath,
1878
+ label: resolvedLabel,
1879
+ helperText: resolvedHelperText,
1880
+ required: resolvedRequired,
1881
+ disabled: resolvedDisabled,
1882
+ readOnly: resolvedReadOnly
1883
+ } = useResolvedFieldProps(name, { label, helperText, required, disabled, readOnly });
1884
+ const icons = VARIANT_CONTENT[variant];
1885
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
1886
+ const errors = field.state.meta.errors;
1887
+ const hasError = hasFieldErrors(errors);
1888
+ const value = field.state.value;
1889
+ const handleSelect = (val) => {
1890
+ if (resolvedDisabled || resolvedReadOnly) return;
1891
+ field.handleChange(val);
1892
+ };
1893
+ return /* @__PURE__ */ jsxs(Field.Root, { invalid: hasError, children: [
1894
+ resolvedLabel && /* @__PURE__ */ jsx(FieldLabel, { label: resolvedLabel, required: resolvedRequired }),
1895
+ /* @__PURE__ */ jsxs(HStack, { gap: 3, children: [
1896
+ /* @__PURE__ */ jsxs(
1897
+ Box,
1898
+ {
1899
+ flex: 1,
1900
+ p: 4,
1901
+ borderWidth: "2px",
1902
+ borderColor: value === true ? "green.500" : "border",
1903
+ borderRadius: "lg",
1904
+ cursor: resolvedDisabled ? "default" : "pointer",
1905
+ onClick: () => handleSelect(true),
1906
+ bg: value === true ? "green.50" : "transparent",
1907
+ _dark: value === true ? { bg: "green.900/20" } : void 0,
1908
+ _hover: resolvedDisabled ? void 0 : { borderColor: "green.400", shadow: "sm" },
1909
+ transition: "all 0.2s",
1910
+ textAlign: "center",
1911
+ opacity: resolvedDisabled ? 0.5 : 1,
1912
+ role: "radio",
1913
+ "aria-checked": value === true,
1914
+ children: [
1915
+ icons.yes && /* @__PURE__ */ jsx(Text, { fontSize: "2xl", mb: 1, children: icons.yes }),
1916
+ /* @__PURE__ */ jsx(Text, { fontWeight: "medium", color: value === true ? "green.600" : "fg", children: yesLabel })
1917
+ ]
1918
+ }
1919
+ ),
1920
+ /* @__PURE__ */ jsxs(
1921
+ Box,
1922
+ {
1923
+ flex: 1,
1924
+ p: 4,
1925
+ borderWidth: "2px",
1926
+ borderColor: value === false ? "red.500" : "border",
1927
+ borderRadius: "lg",
1928
+ cursor: resolvedDisabled ? "default" : "pointer",
1929
+ onClick: () => handleSelect(false),
1930
+ bg: value === false ? "red.50" : "transparent",
1931
+ _dark: value === false ? { bg: "red.900/20" } : void 0,
1932
+ _hover: resolvedDisabled ? void 0 : { borderColor: "red.400", shadow: "sm" },
1933
+ transition: "all 0.2s",
1934
+ textAlign: "center",
1935
+ opacity: resolvedDisabled ? 0.5 : 1,
1936
+ role: "radio",
1937
+ "aria-checked": value === false,
1938
+ children: [
1939
+ icons.no && /* @__PURE__ */ jsx(Text, { fontSize: "2xl", mb: 1, children: icons.no }),
1940
+ /* @__PURE__ */ jsx(Text, { fontWeight: "medium", color: value === false ? "red.600" : "fg", children: noLabel })
1941
+ ]
1942
+ }
1943
+ )
1944
+ ] }),
1945
+ /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage: formatFieldErrors(errors), helperText: resolvedHelperText })
1946
+ ] });
1947
+ } });
1948
+ }
1949
+ var computingFields = /* @__PURE__ */ new Set();
1950
+ function getNestedValue(obj, path) {
1951
+ const parts = path.split(".");
1952
+ let result = obj;
1953
+ for (const part of parts) {
1954
+ if (result && typeof result === "object") {
1955
+ result = result[part];
1956
+ } else {
1957
+ return void 0;
1958
+ }
1959
+ }
1960
+ return result;
1961
+ }
1962
+ function getDepsSnapshot(values, deps) {
1963
+ return deps.map((dep) => getNestedValue(values, dep));
1964
+ }
1965
+ function areDepsEqual(a, b) {
1966
+ if (a.length !== b.length) return false;
1967
+ for (let i = 0; i < a.length; i++) {
1968
+ if (!Object.is(a[i], b[i])) return false;
1969
+ }
1970
+ return true;
1971
+ }
1972
+ function useComputedValue({
1973
+ form,
1974
+ compute,
1975
+ deps,
1976
+ debounce: debounceMs = 0,
1977
+ fieldPath
1978
+ }) {
1979
+ const prevDepsRef = useRef(null);
1980
+ const cachedResultRef = useRef(void 0);
1981
+ const subscribe = useCallback(
1982
+ (callback) => {
1983
+ const subscription = form.store.subscribe(callback);
1984
+ if (typeof subscription === "function") {
1985
+ return subscription;
1986
+ }
1987
+ return () => subscription.unsubscribe();
1988
+ },
1989
+ [form]
1990
+ );
1991
+ const getSnapshot = useCallback(() => {
1992
+ const values = form.state.values;
1993
+ if (deps && deps.length > 0) {
1994
+ const currentDeps = getDepsSnapshot(values, deps);
1995
+ if (prevDepsRef.current && areDepsEqual(prevDepsRef.current, currentDeps)) {
1996
+ return cachedResultRef.current;
1997
+ }
1998
+ prevDepsRef.current = currentDeps;
1999
+ }
2000
+ if (computingFields.has(fieldPath)) {
2001
+ if (process.env.NODE_ENV !== "production") {
2002
+ console.error(
2003
+ `[Form.Field.Calculated] \u041E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0430 \u0446\u0438\u043A\u043B\u0438\u0447\u0435\u0441\u043A\u0430\u044F \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C: \u043F\u043E\u043B\u0435 "${fieldPath}" \u0443\u0436\u0435 \u0432\u044B\u0447\u0438\u0441\u043B\u044F\u0435\u0442\u0441\u044F. \u0422\u0435\u043A\u0443\u0449\u0430\u044F \u0446\u0435\u043F\u043E\u0447\u043A\u0430: ${[...computingFields].join(" \u2192 ")} \u2192 ${fieldPath}`
2004
+ );
2005
+ }
2006
+ return cachedResultRef.current;
2007
+ }
2008
+ try {
2009
+ computingFields.add(fieldPath);
2010
+ const result = compute(values);
2011
+ cachedResultRef.current = result;
2012
+ return result;
2013
+ } finally {
2014
+ computingFields.delete(fieldPath);
2015
+ }
2016
+ }, [form, compute, deps, fieldPath]);
2017
+ const rawValue = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
2018
+ const debouncedValue = useDebounce(rawValue, debounceMs);
2019
+ return debounceMs > 0 ? debouncedValue : rawValue;
2020
+ }
2021
+ function CalculatedFieldInner({
2022
+ field,
2023
+ computedValue,
2024
+ format,
2025
+ hidden
2026
+ }) {
2027
+ useEffect(() => {
2028
+ if (!Object.is(field.state.value, computedValue)) {
2029
+ field.handleChange(computedValue);
2030
+ }
2031
+ }, [computedValue, field]);
2032
+ if (hidden) return null;
2033
+ const displayValue = format ? format(computedValue) : String(computedValue ?? "");
2034
+ return /* @__PURE__ */ jsx(Text, { fontSize: "md", fontWeight: "medium", py: "2", "data-testid": "calculated-value", children: displayValue });
2035
+ }
2036
+ function FieldCalculated({
2037
+ name,
2038
+ label,
2039
+ compute,
2040
+ format,
2041
+ deps,
2042
+ debounce = 0,
2043
+ hidden,
2044
+ helperText
2045
+ }) {
2046
+ const { form, fullPath, ...resolved } = useResolvedFieldProps(name, {
2047
+ label,
2048
+ helperText,
2049
+ readOnly: true
2050
+ });
2051
+ const computedValue = useComputedValue({
2052
+ form,
2053
+ compute,
2054
+ deps,
2055
+ debounce,
2056
+ fieldPath: fullPath
2057
+ });
2058
+ if (hidden) {
2059
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => /* @__PURE__ */ jsx(CalculatedFieldInner, { field, computedValue, hidden: true }) });
2060
+ }
2061
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => /* @__PURE__ */ jsx(FieldWrapper, { resolved, hasError: false, errorMessage: "", fullPath, children: /* @__PURE__ */ jsx(CalculatedFieldInner, { field, computedValue, format }) }) });
2062
+ }
2063
+ FieldCalculated.displayName = "FieldCalculated";
2064
+ function HiddenFieldInner({ field, value }) {
2065
+ useEffect(() => {
2066
+ if (value !== void 0 && !Object.is(field.state.value, value)) {
2067
+ field.handleChange(value);
2068
+ }
2069
+ }, [value, field]);
2070
+ return null;
2071
+ }
2072
+ function FieldHidden({ name, value }) {
2073
+ const { form, fullPath } = useResolvedFieldProps(name, {});
2074
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => /* @__PURE__ */ jsx(HiddenFieldInner, { field, value }) });
2075
+ }
2076
+ FieldHidden.displayName = "FieldHidden";
1367
2077
  function useIsDarkMode() {
1368
2078
  const [isDark, setIsDark] = useState(false);
1369
2079
  useEffect(() => {
@@ -1419,6 +2129,145 @@ function FormDebugValues({
1419
2129
  }
1420
2130
  ) });
1421
2131
  }
2132
+ function HoneypotField() {
2133
+ const id = useId();
2134
+ const nameRef = useRef(`hp_${id.replace(/:/g, "")}_${Math.random().toString(36).slice(2, 6)}`);
2135
+ return /* @__PURE__ */ jsxs(
2136
+ "div",
2137
+ {
2138
+ "aria-hidden": "true",
2139
+ style: {
2140
+ position: "absolute",
2141
+ left: "-9999px",
2142
+ width: 0,
2143
+ height: 0,
2144
+ overflow: "hidden",
2145
+ opacity: 0,
2146
+ pointerEvents: "none",
2147
+ tabIndex: -1
2148
+ },
2149
+ children: [
2150
+ /* @__PURE__ */ jsx("label", { htmlFor: nameRef.current, children: "Leave this field empty" }),
2151
+ /* @__PURE__ */ jsx("input", { type: "text", id: nameRef.current, name: nameRef.current, autoComplete: "off", tabIndex: -1 })
2152
+ ]
2153
+ }
2154
+ );
2155
+ }
2156
+ function useHoneypotCheck(enabled) {
2157
+ const checkBot = useCallback(() => {
2158
+ if (!enabled) return false;
2159
+ const honeypotInputs = document.querySelectorAll('input[name^="hp_"][tabindex="-1"]');
2160
+ for (const input of honeypotInputs) {
2161
+ if (input.value.trim() !== "") {
2162
+ return true;
2163
+ }
2164
+ }
2165
+ return false;
2166
+ }, [enabled]);
2167
+ return { isBot: checkBot };
2168
+ }
2169
+ var STORAGE_KEY_PREFIX = "form-rate-limit:";
2170
+ function useRateLimit(config, formId) {
2171
+ const [secondsLeft, setSecondsLeft] = useState(0);
2172
+ const [isBlocked, setIsBlocked] = useState(false);
2173
+ const [attemptVersion, setAttemptVersion] = useState(0);
2174
+ const timerRef = useRef(null);
2175
+ const storageKey = `${STORAGE_KEY_PREFIX}${formId ?? "default"}`;
2176
+ const getAttempts = useCallback(() => {
2177
+ if (!config) return [];
2178
+ try {
2179
+ const raw = sessionStorage.getItem(storageKey);
2180
+ if (!raw) return [];
2181
+ const attempts2 = JSON.parse(raw);
2182
+ const now = Date.now();
2183
+ return attempts2.filter((ts) => now - ts < config.windowMs);
2184
+ } catch {
2185
+ return [];
2186
+ }
2187
+ }, [config, storageKey]);
2188
+ const saveAttempts = useCallback(
2189
+ (attempts2) => {
2190
+ try {
2191
+ sessionStorage.setItem(storageKey, JSON.stringify(attempts2));
2192
+ } catch {
2193
+ }
2194
+ },
2195
+ [storageKey]
2196
+ );
2197
+ const startCountdown = useCallback(
2198
+ (blockUntil) => {
2199
+ if (timerRef.current) clearInterval(timerRef.current);
2200
+ const updateTimer = () => {
2201
+ const remaining = Math.ceil((blockUntil - Date.now()) / 1e3);
2202
+ if (remaining <= 0) {
2203
+ setIsBlocked(false);
2204
+ setSecondsLeft(0);
2205
+ if (timerRef.current) clearInterval(timerRef.current);
2206
+ timerRef.current = null;
2207
+ saveAttempts(getAttempts());
2208
+ } else {
2209
+ setSecondsLeft(remaining);
2210
+ }
2211
+ };
2212
+ updateTimer();
2213
+ timerRef.current = setInterval(updateTimer, 1e3);
2214
+ },
2215
+ [getAttempts, saveAttempts]
2216
+ );
2217
+ const recordAttempt = useCallback(() => {
2218
+ if (!config) return true;
2219
+ const attempts2 = getAttempts();
2220
+ if (attempts2.length >= config.maxSubmits) {
2221
+ setIsBlocked(true);
2222
+ const oldestAttempt = Math.min(...attempts2);
2223
+ const blockUntil = oldestAttempt + config.windowMs;
2224
+ startCountdown(blockUntil);
2225
+ return false;
2226
+ }
2227
+ attempts2.push(Date.now());
2228
+ saveAttempts(attempts2);
2229
+ setAttemptVersion((v) => v + 1);
2230
+ return true;
2231
+ }, [config, getAttempts, saveAttempts, startCountdown]);
2232
+ const reset = useCallback(() => {
2233
+ try {
2234
+ sessionStorage.removeItem(storageKey);
2235
+ } catch {
2236
+ }
2237
+ setIsBlocked(false);
2238
+ setSecondsLeft(0);
2239
+ if (timerRef.current) {
2240
+ clearInterval(timerRef.current);
2241
+ timerRef.current = null;
2242
+ }
2243
+ }, [storageKey]);
2244
+ useEffect(() => {
2245
+ return () => {
2246
+ if (timerRef.current) clearInterval(timerRef.current);
2247
+ };
2248
+ }, []);
2249
+ useEffect(() => {
2250
+ if (!config) return;
2251
+ const attempts2 = getAttempts();
2252
+ if (attempts2.length >= config.maxSubmits) {
2253
+ setIsBlocked(true);
2254
+ const oldestAttempt = Math.min(...attempts2);
2255
+ const blockUntil = oldestAttempt + config.windowMs;
2256
+ if (blockUntil > Date.now()) {
2257
+ startCountdown(blockUntil);
2258
+ }
2259
+ }
2260
+ }, [config, getAttempts, startCountdown]);
2261
+ if (!config) return null;
2262
+ const attempts = getAttempts();
2263
+ return {
2264
+ isBlocked,
2265
+ remaining: Math.max(0, config.maxSubmits - attempts.length),
2266
+ secondsLeft,
2267
+ recordAttempt,
2268
+ reset
2269
+ };
2270
+ }
1422
2271
 
1423
2272
  // src/lib/declarative/form-root/form-validators.ts
1424
2273
  function buildValidators(schema, validateOn) {
@@ -1448,6 +2297,67 @@ function buildValidators(schema, validateOn) {
1448
2297
  }
1449
2298
  return Object.keys(validators).length > 0 ? validators : void 0;
1450
2299
  }
2300
+ function getNestedValue2(obj, path) {
2301
+ const parts = path.split(".");
2302
+ let result = obj;
2303
+ for (const part of parts) {
2304
+ if (result && typeof result === "object") {
2305
+ result = result[part];
2306
+ } else {
2307
+ return void 0;
2308
+ }
2309
+ }
2310
+ return result;
2311
+ }
2312
+ function useFieldChangeListeners(form, onFieldChange) {
2313
+ const callbacksRef = useRef(onFieldChange);
2314
+ callbacksRef.current = onFieldChange;
2315
+ const prevValuesRef = useRef({});
2316
+ const fieldChangeApi = useMemo(
2317
+ () => ({
2318
+ setFieldValue: (name, value) => {
2319
+ form.setFieldValue(name, value);
2320
+ },
2321
+ getFieldValue: (name) => {
2322
+ return getNestedValue2(form.state.values, name);
2323
+ },
2324
+ getValues: () => {
2325
+ return form.state.values;
2326
+ }
2327
+ }),
2328
+ [form]
2329
+ );
2330
+ const initPrevValues = useCallback(() => {
2331
+ const callbacks = callbacksRef.current;
2332
+ if (!callbacks) return;
2333
+ const values = form.state.values;
2334
+ const snapshot = {};
2335
+ for (const key of Object.keys(callbacks)) {
2336
+ snapshot[key] = getNestedValue2(values, key);
2337
+ }
2338
+ prevValuesRef.current = snapshot;
2339
+ }, [form]);
2340
+ useEffect(() => {
2341
+ initPrevValues();
2342
+ const subscription = form.store.subscribe(() => {
2343
+ const callbacks = callbacksRef.current;
2344
+ if (!callbacks) return;
2345
+ const values = form.state.values;
2346
+ const prev = prevValuesRef.current;
2347
+ for (const key of Object.keys(callbacks)) {
2348
+ const currentValue = getNestedValue2(values, key);
2349
+ if (!Object.is(prev[key], currentValue)) {
2350
+ prev[key] = currentValue;
2351
+ callbacks[key](currentValue, fieldChangeApi);
2352
+ }
2353
+ }
2354
+ });
2355
+ if (typeof subscription === "function") {
2356
+ return subscription;
2357
+ }
2358
+ return () => subscription.unsubscribe();
2359
+ }, [form, fieldChangeApi, initPrevValues]);
2360
+ }
1451
2361
  var STORAGE_PREFIX = "form-persistence:";
1452
2362
  function useFormPersistence(config) {
1453
2363
  const {
@@ -1732,8 +2642,13 @@ function FormSimple({
1732
2642
  debug,
1733
2643
  middleware,
1734
2644
  addressProvider,
2645
+ onFieldChange,
2646
+ honeypot,
2647
+ rateLimit,
1735
2648
  children
1736
2649
  }) {
2650
+ const { isBot } = useHoneypotCheck(honeypot);
2651
+ const rateLimitState = useRateLimit(rateLimit);
1737
2652
  const features = useFormFeatures({
1738
2653
  persistence,
1739
2654
  offline,
@@ -1745,6 +2660,8 @@ function FormSimple({
1745
2660
  defaultValues: initialValue,
1746
2661
  validators: buildValidators(schema, validateOn),
1747
2662
  onSubmit: async ({ value, formApi }) => {
2663
+ if (isBot()) return;
2664
+ if (rateLimitState && !rateLimitState.recordAttempt()) return;
1748
2665
  let dataToSubmit = value;
1749
2666
  if (middleware?.beforeSubmit) {
1750
2667
  const transformed = await middleware.beforeSubmit(dataToSubmit);
@@ -1767,6 +2684,7 @@ function FormSimple({
1767
2684
  }
1768
2685
  }
1769
2686
  });
2687
+ useFieldChangeListeners(form, onFieldChange);
1770
2688
  useEffect(() => {
1771
2689
  return features.subscribeToFormChanges(form);
1772
2690
  }, [form, features]);
@@ -1804,6 +2722,12 @@ function FormSimple({
1804
2722
  form.handleSubmit();
1805
2723
  },
1806
2724
  children: [
2725
+ honeypot && /* @__PURE__ */ jsx(HoneypotField, {}),
2726
+ rateLimitState?.isBlocked && /* @__PURE__ */ jsxs("div", { role: "alert", style: { color: "var(--chakra-colors-fg-error, #e53e3e)", marginBottom: "1rem" }, children: [
2727
+ "Too many attempts. Try again in ",
2728
+ rateLimitState.secondsLeft,
2729
+ "s."
2730
+ ] }),
1807
2731
  children,
1808
2732
  debug && /* @__PURE__ */ jsx(FormDebugValues, { showInProduction: debug === "force" })
1809
2733
  ]
@@ -1880,8 +2804,13 @@ function FormWithApi({
1880
2804
  debug,
1881
2805
  middleware,
1882
2806
  addressProvider,
2807
+ onFieldChange,
2808
+ honeypot,
2809
+ rateLimit,
1883
2810
  children
1884
2811
  }) {
2812
+ const { isBot } = useHoneypotCheck(honeypot);
2813
+ const rateLimitState = useRateLimit(rateLimit);
1885
2814
  const formApi = useFormApi(api);
1886
2815
  const features = useFormFeatures({
1887
2816
  persistence,
@@ -1896,6 +2825,8 @@ function FormWithApi({
1896
2825
  defaultValues,
1897
2826
  validators: buildValidators(schema, validateOn),
1898
2827
  onSubmit: async ({ value, formApi: tanstackFormApi }) => {
2828
+ if (isBot()) return;
2829
+ if (rateLimitState && !rateLimitState.recordAttempt()) return;
1899
2830
  let dataToSubmit = value;
1900
2831
  if (middleware?.beforeSubmit) {
1901
2832
  const transformed = await middleware.beforeSubmit(dataToSubmit);
@@ -1918,6 +2849,7 @@ function FormWithApi({
1918
2849
  }
1919
2850
  }
1920
2851
  });
2852
+ useFieldChangeListeners(form, onFieldChange);
1921
2853
  useEffect(() => {
1922
2854
  return features.subscribeToFormChanges(form);
1923
2855
  }, [form, features]);
@@ -1964,6 +2896,12 @@ function FormWithApi({
1964
2896
  form.handleSubmit();
1965
2897
  },
1966
2898
  children: [
2899
+ honeypot && /* @__PURE__ */ jsx(HoneypotField, {}),
2900
+ rateLimitState?.isBlocked && /* @__PURE__ */ jsxs("div", { role: "alert", style: { color: "var(--chakra-colors-fg-error, #e53e3e)", marginBottom: "1rem" }, children: [
2901
+ "Too many attempts. Try again in ",
2902
+ rateLimitState.secondsLeft,
2903
+ "s."
2904
+ ] }),
1967
2905
  children,
1968
2906
  debug && /* @__PURE__ */ jsx(FormDebugValues, { showInProduction: debug === "force" })
1969
2907
  ]
@@ -1982,6 +2920,11 @@ function FormRoot({
1982
2920
  disabled,
1983
2921
  readOnly,
1984
2922
  debug,
2923
+ middleware,
2924
+ addressProvider,
2925
+ onFieldChange,
2926
+ honeypot,
2927
+ rateLimit,
1985
2928
  children
1986
2929
  }) {
1987
2930
  if (api) {
@@ -1998,6 +2941,11 @@ function FormRoot({
1998
2941
  disabled,
1999
2942
  readOnly,
2000
2943
  debug,
2944
+ middleware,
2945
+ addressProvider,
2946
+ onFieldChange,
2947
+ honeypot,
2948
+ rateLimit,
2001
2949
  children
2002
2950
  }
2003
2951
  );
@@ -2019,6 +2967,11 @@ function FormRoot({
2019
2967
  disabled,
2020
2968
  readOnly,
2021
2969
  debug,
2970
+ middleware,
2971
+ addressProvider,
2972
+ onFieldChange,
2973
+ honeypot,
2974
+ rateLimit,
2022
2975
  children
2023
2976
  }
2024
2977
  );
@@ -2123,6 +3076,25 @@ function FormBuilder({
2123
3076
  );
2124
3077
  }
2125
3078
  FormBuilder.displayName = "FormBuilder";
3079
+ function FormDivider({
3080
+ label,
3081
+ icon,
3082
+ variant = "solid",
3083
+ size = "xs",
3084
+ colorPalette = "gray"
3085
+ }) {
3086
+ if (!label && !icon) {
3087
+ return /* @__PURE__ */ jsx(Separator, { variant, size, colorPalette, my: 3 });
3088
+ }
3089
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, my: 3, children: [
3090
+ /* @__PURE__ */ jsx(Separator, { flex: "1", variant, size, colorPalette }),
3091
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 1.5, flexShrink: 0, children: [
3092
+ icon && /* @__PURE__ */ jsx(Box, { color: "fg.muted", children: icon }),
3093
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "fg.muted", fontWeight: "medium", whiteSpace: "nowrap", children: label })
3094
+ ] }),
3095
+ /* @__PURE__ */ jsx(Separator, { flex: "1", variant, size, colorPalette })
3096
+ ] });
3097
+ }
2126
3098
  function extractAllErrors(errors) {
2127
3099
  const messages = [];
2128
3100
  for (const error of errors) {
@@ -2187,26 +3159,1318 @@ function FormErrors({
2187
3159
  }
2188
3160
  );
2189
3161
  }
2190
- function FormFromSchema({
2191
- schema,
2192
- initialValue,
2193
- onSubmit,
2194
- submitLabel = "Save",
2195
- showReset = false,
2196
- resetLabel = "Reset",
2197
- exclude,
2198
- validateOn,
2199
- middleware,
2200
- disabled,
2201
- readOnly,
2202
- debug,
2203
- persistence,
2204
- offline,
2205
- beforeButtons,
2206
- afterButtons,
2207
- gap = 4
2208
- }) {
2209
- return /* @__PURE__ */ jsx(
3162
+ function createDocumentField(config) {
3163
+ return createField({
3164
+ displayName: config.displayName,
3165
+ render: ({ field, resolved, hasError, errorMessage }) => {
3166
+ const maskRef = useCallback((element) => {
3167
+ if (!element) return;
3168
+ withMask(config.mask, {
3169
+ showMaskOnFocus: false,
3170
+ clearIncomplete: true,
3171
+ autoUnmask: false
3172
+ })(element);
3173
+ }, []);
3174
+ const customError = config.validate ? config.validate(String(field.state.value ?? "")) : void 0;
3175
+ const showError = hasError || !!customError;
3176
+ const displayError = customError ?? errorMessage;
3177
+ return /* @__PURE__ */ jsxs(Field.Root, { invalid: showError, required: resolved.required, disabled: resolved.disabled, children: [
3178
+ /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
3179
+ /* @__PURE__ */ jsx(InputGroup, { startElement: /* @__PURE__ */ jsx(Icon, { color: "fg.muted", children: config.icon }), children: /* @__PURE__ */ jsx(
3180
+ Input,
3181
+ {
3182
+ ref: maskRef,
3183
+ value: String(field.state.value ?? ""),
3184
+ onChange: (e) => field.handleChange(e.target.value),
3185
+ onBlur: field.handleBlur,
3186
+ placeholder: config.placeholder
3187
+ }
3188
+ ) }),
3189
+ /* @__PURE__ */ jsx(FieldError, { hasError: showError, errorMessage: displayError, helperText: resolved.helperText })
3190
+ ] });
3191
+ }
3192
+ });
3193
+ }
3194
+ var FieldBankAccount = createDocumentField({
3195
+ displayName: "FieldBankAccount",
3196
+ mask: "99999999999999999999",
3197
+ placeholder: "40702810038000000001",
3198
+ icon: /* @__PURE__ */ jsx(LuLandmark, {}),
3199
+ validate: (value) => {
3200
+ const digits = value.replace(/\D/g, "");
3201
+ if (!digits) return void 0;
3202
+ if (digits.length !== 20) return "\u0420\u0430\u0441\u0447\u0451\u0442\u043D\u044B\u0439 \u0441\u0447\u0451\u0442 \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C 20 \u0446\u0438\u0444\u0440";
3203
+ return void 0;
3204
+ }
3205
+ });
3206
+ var FieldCorrAccount = createDocumentField({
3207
+ displayName: "FieldCorrAccount",
3208
+ mask: "99999999999999999999",
3209
+ placeholder: "30101810400000000225",
3210
+ icon: /* @__PURE__ */ jsx(LuLandmark, {}),
3211
+ validate: (value) => {
3212
+ const digits = value.replace(/\D/g, "");
3213
+ if (!digits) return void 0;
3214
+ if (digits.length !== 20) return "\u041A\u043E\u0440\u0440. \u0441\u0447\u0451\u0442 \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C 20 \u0446\u0438\u0444\u0440";
3215
+ if (!digits.startsWith("301")) return '\u041A\u043E\u0440\u0440. \u0441\u0447\u0451\u0442 \u0434\u043E\u043B\u0436\u0435\u043D \u043D\u0430\u0447\u0438\u043D\u0430\u0442\u044C\u0441\u044F \u0441 "301"';
3216
+ return void 0;
3217
+ }
3218
+ });
3219
+ var FieldBIK = createDocumentField({
3220
+ displayName: "FieldBIK",
3221
+ mask: "999999999",
3222
+ placeholder: "044525225",
3223
+ icon: /* @__PURE__ */ jsx(LuLandmark, {}),
3224
+ validate: (value) => {
3225
+ const digits = value.replace(/\D/g, "");
3226
+ if (!digits) return void 0;
3227
+ if (digits.length !== 9) return "\u0411\u0418\u041A \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C 9 \u0446\u0438\u0444\u0440";
3228
+ return validateBik(digits) ? void 0 : '\u0411\u0418\u041A \u0434\u043E\u043B\u0436\u0435\u043D \u043D\u0430\u0447\u0438\u043D\u0430\u0442\u044C\u0441\u044F \u0441 "04"';
3229
+ }
3230
+ });
3231
+ var FieldINN = createDocumentField({
3232
+ displayName: "FieldINN",
3233
+ mask: "999999999999",
3234
+ // 12 цифр максимум
3235
+ placeholder: "7707083893",
3236
+ icon: /* @__PURE__ */ jsx(LuFileText, {}),
3237
+ validate: (value) => {
3238
+ const digits = value.replace(/\D/g, "");
3239
+ if (!digits) return void 0;
3240
+ if (digits.length === 10) {
3241
+ return validateInn10(digits) ? void 0 : "\u041D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u0430\u044F \u0441\u0443\u043C\u043C\u0430 \u0418\u041D\u041D";
3242
+ }
3243
+ if (digits.length === 12) {
3244
+ return validateInn12(digits) ? void 0 : "\u041D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u0430\u044F \u0441\u0443\u043C\u043C\u0430 \u0418\u041D\u041D";
3245
+ }
3246
+ return "\u0418\u041D\u041D \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C 10 \u0438\u043B\u0438 12 \u0446\u0438\u0444\u0440";
3247
+ }
3248
+ });
3249
+ var FieldKPP = createDocumentField({
3250
+ displayName: "FieldKPP",
3251
+ mask: "*********",
3252
+ // 9 символов (цифры или буквы)
3253
+ placeholder: "770701001",
3254
+ icon: /* @__PURE__ */ jsx(LuFileText, {}),
3255
+ validate: (value) => {
3256
+ const clean = value.replace(/[\s-]/g, "").toUpperCase();
3257
+ if (!clean) return void 0;
3258
+ if (clean.length !== 9) return "\u041A\u041F\u041F \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C 9 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432";
3259
+ return validateKpp(clean) ? void 0 : "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u041A\u041F\u041F";
3260
+ }
3261
+ });
3262
+ var FieldOGRN = createDocumentField({
3263
+ displayName: "FieldOGRN",
3264
+ mask: "9999999999999",
3265
+ placeholder: "1027700132195",
3266
+ icon: /* @__PURE__ */ jsx(LuFileText, {}),
3267
+ validate: (value) => {
3268
+ const digits = value.replace(/\D/g, "");
3269
+ if (!digits) return void 0;
3270
+ if (digits.length !== 13) return "\u041E\u0413\u0420\u041D \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C 13 \u0446\u0438\u0444\u0440";
3271
+ return validateOgrn(digits) ? void 0 : "\u041D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u0430\u044F \u0441\u0443\u043C\u043C\u0430 \u041E\u0413\u0420\u041D";
3272
+ }
3273
+ });
3274
+ var FieldPassport = createDocumentField({
3275
+ displayName: "FieldPassport",
3276
+ mask: "99 99 999999",
3277
+ placeholder: "45 06 123456",
3278
+ icon: /* @__PURE__ */ jsx(LuBookOpen, {}),
3279
+ validate: (value) => {
3280
+ const digits = value.replace(/\D/g, "");
3281
+ if (!digits) return void 0;
3282
+ if (digits.length !== 10) return "\u041F\u0430\u0441\u043F\u043E\u0440\u0442: \u0441\u0435\u0440\u0438\u044F (4 \u0446\u0438\u0444\u0440\u044B) + \u043D\u043E\u043C\u0435\u0440 (6 \u0446\u0438\u0444\u0440)";
3283
+ return void 0;
3284
+ }
3285
+ });
3286
+ var FieldSNILS = createDocumentField({
3287
+ displayName: "FieldSNILS",
3288
+ mask: "999-999-999 99",
3289
+ placeholder: "123-456-789 00",
3290
+ icon: /* @__PURE__ */ jsx(LuShield, {}),
3291
+ validate: (value) => {
3292
+ const digits = value.replace(/\D/g, "");
3293
+ if (!digits) return void 0;
3294
+ if (digits.length !== 11) return "\u0421\u041D\u0418\u041B\u0421 \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C 11 \u0446\u0438\u0444\u0440";
3295
+ return validateSnils(digits) ? void 0 : "\u041D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u0430\u044F \u0441\u0443\u043C\u043C\u0430 \u0421\u041D\u0418\u041B\u0421";
3296
+ }
3297
+ });
3298
+ function mapZodType(zodType) {
3299
+ switch (zodType) {
3300
+ case "string":
3301
+ return "string";
3302
+ case "number":
3303
+ case "bigint":
3304
+ return "number";
3305
+ case "boolean":
3306
+ return "boolean";
3307
+ case "date":
3308
+ return "date";
3309
+ case "enum":
3310
+ case "literal":
3311
+ return "enum";
3312
+ default:
3313
+ return "unknown";
3314
+ }
3315
+ }
3316
+ function getArrayElementFields(schema, arrayPath) {
3317
+ const allFields = traverseSchema(schema);
3318
+ const field = findFieldByPath(allFields, arrayPath);
3319
+ if (field?.zodType === "array" && field.element?.children) {
3320
+ return field.element.children;
3321
+ }
3322
+ return [];
3323
+ }
3324
+ function findFieldByPath(fields, path) {
3325
+ const parts = path.split(".");
3326
+ let current = fields;
3327
+ for (let i = 0; i < parts.length; i++) {
3328
+ const found = current.find((f) => f.name === parts[i]);
3329
+ if (!found) return void 0;
3330
+ if (i === parts.length - 1) return found;
3331
+ if (found.children) {
3332
+ current = found.children;
3333
+ } else {
3334
+ return void 0;
3335
+ }
3336
+ }
3337
+ return void 0;
3338
+ }
3339
+ function fieldInfoToColumn(info) {
3340
+ return {
3341
+ name: info.name,
3342
+ label: info.ui?.title ?? camelToTitle(info.name),
3343
+ width: "auto",
3344
+ align: mapZodType(info.zodType) === "number" ? "right" : "left",
3345
+ fieldType: mapZodType(info.zodType),
3346
+ readOnly: false,
3347
+ required: info.required,
3348
+ enumValues: info.enumValues,
3349
+ placeholder: info.ui?.placeholder
3350
+ };
3351
+ }
3352
+ function mergeColumns(userColumns, schemaColumns) {
3353
+ return userColumns.filter((col) => !col.hidden).map((col) => {
3354
+ const schemaCol = schemaColumns.find((sc) => sc.name === col.name);
3355
+ if (col.computed) {
3356
+ return {
3357
+ name: col.name,
3358
+ label: col.label ?? camelToTitle(col.name),
3359
+ width: col.width ?? "auto",
3360
+ align: col.align ?? "right",
3361
+ fieldType: "number",
3362
+ computed: col.computed,
3363
+ format: col.format,
3364
+ readOnly: true,
3365
+ required: false
3366
+ };
3367
+ }
3368
+ return {
3369
+ name: col.name,
3370
+ label: col.label ?? schemaCol?.label ?? camelToTitle(col.name),
3371
+ width: col.width ?? schemaCol?.width ?? "auto",
3372
+ align: col.align ?? schemaCol?.align ?? "left",
3373
+ fieldType: schemaCol?.fieldType ?? "string",
3374
+ readOnly: col.readOnly ?? schemaCol?.readOnly ?? false,
3375
+ required: schemaCol?.required ?? false,
3376
+ enumValues: schemaCol?.enumValues,
3377
+ placeholder: schemaCol?.placeholder,
3378
+ format: col.format
3379
+ };
3380
+ });
3381
+ }
3382
+ function camelToTitle(str) {
3383
+ return str.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
3384
+ }
3385
+ function useTableColumns(schema, arrayPath, userColumns) {
3386
+ return useMemo(() => {
3387
+ const schemaFields = getArrayElementFields(schema, arrayPath);
3388
+ const autoColumns = schemaFields.map(fieldInfoToColumn);
3389
+ if (!userColumns || userColumns.length === 0) {
3390
+ return autoColumns;
3391
+ }
3392
+ return mergeColumns(userColumns, autoColumns);
3393
+ }, [schema, arrayPath, userColumns]);
3394
+ }
3395
+ function FieldDataGrid({
3396
+ name,
3397
+ label,
3398
+ columns: columnDefs,
3399
+ pageSize = 20,
3400
+ rowSelection = false,
3401
+ onRowSave,
3402
+ virtualized = false,
3403
+ virtualHeight = "500px",
3404
+ columnResizing = false,
3405
+ size = "sm",
3406
+ helperText,
3407
+ disabled = false
3408
+ }) {
3409
+ const { form, schema } = useDeclarativeForm();
3410
+ const parentGroup = useFormGroup();
3411
+ const fullPath = parentGroup ? `${parentGroup.name}.${name}` : name;
3412
+ const resolvedCols = useTableColumns(schema, fullPath);
3413
+ const [sorting, setSorting] = useState([]);
3414
+ const [columnFilters, setColumnFilters] = useState([]);
3415
+ const [columnOrder, setColumnOrder] = useState([]);
3416
+ const [rowSelectionState, setRowSelectionState] = useState({});
3417
+ const [editingCell, setEditingCell] = useState(null);
3418
+ const [modifiedCells, setModifiedCells] = useState(/* @__PURE__ */ new Set());
3419
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, mode: "array", children: (arrayField) => {
3420
+ const data = arrayField.state.value ?? [];
3421
+ const tableColumns = useMemo(() => {
3422
+ const cols = [];
3423
+ if (rowSelection) {
3424
+ cols.push({
3425
+ id: "select",
3426
+ header: ({ table: table2 }) => /* @__PURE__ */ jsxs(
3427
+ Checkbox.Root,
3428
+ {
3429
+ checked: table2.getIsAllPageRowsSelected() ? true : table2.getIsSomePageRowsSelected() ? "indeterminate" : false,
3430
+ onCheckedChange: () => table2.toggleAllPageRowsSelected(),
3431
+ size: "sm",
3432
+ children: [
3433
+ /* @__PURE__ */ jsx(Checkbox.HiddenInput, {}),
3434
+ /* @__PURE__ */ jsx(Checkbox.Control, {})
3435
+ ]
3436
+ }
3437
+ ),
3438
+ cell: ({ row }) => /* @__PURE__ */ jsxs(
3439
+ Checkbox.Root,
3440
+ {
3441
+ checked: row.getIsSelected(),
3442
+ onCheckedChange: () => row.toggleSelected(),
3443
+ size: "sm",
3444
+ children: [
3445
+ /* @__PURE__ */ jsx(Checkbox.HiddenInput, {}),
3446
+ /* @__PURE__ */ jsx(Checkbox.Control, {})
3447
+ ]
3448
+ }
3449
+ ),
3450
+ size: 40,
3451
+ enableSorting: false,
3452
+ enableColumnFilter: false
3453
+ });
3454
+ }
3455
+ for (const colDef of columnDefs) {
3456
+ const resolved = resolvedCols.find((r) => r.name === colDef.name);
3457
+ cols.push({
3458
+ id: colDef.name,
3459
+ accessorKey: colDef.name,
3460
+ header: () => colDef.label ?? resolved?.label ?? colDef.name,
3461
+ cell: ({ row, getValue }) => {
3462
+ const rowIndex = row.index;
3463
+ const value = getValue();
3464
+ const isEditing = editingCell?.row === rowIndex && editingCell?.col === colDef.name;
3465
+ if (isEditing && colDef.editable !== false) {
3466
+ return /* @__PURE__ */ jsx(
3467
+ EditableCell,
3468
+ {
3469
+ value,
3470
+ fieldType: resolved?.fieldType ?? "string",
3471
+ onSave: (newValue) => {
3472
+ const fieldPath = `${fullPath}[${rowIndex}].${colDef.name}`;
3473
+ form.setFieldValue(fieldPath, newValue);
3474
+ setEditingCell(null);
3475
+ const cellKey2 = `${rowIndex}:${colDef.name}`;
3476
+ setModifiedCells((prev) => new Set(prev).add(cellKey2));
3477
+ if (onRowSave) {
3478
+ const updatedRow = { ...data[rowIndex], [colDef.name]: newValue };
3479
+ onRowSave(updatedRow, rowIndex);
3480
+ }
3481
+ },
3482
+ onCancel: () => setEditingCell(null)
3483
+ }
3484
+ );
3485
+ }
3486
+ const cellKey = `${rowIndex}:${colDef.name}`;
3487
+ const isModified = modifiedCells.has(cellKey);
3488
+ return /* @__PURE__ */ jsx(
3489
+ Text,
3490
+ {
3491
+ cursor: colDef.editable !== false && !disabled ? "pointer" : "default",
3492
+ onClick: () => {
3493
+ if (colDef.editable !== false && !disabled) {
3494
+ setEditingCell({ row: rowIndex, col: colDef.name });
3495
+ }
3496
+ },
3497
+ textAlign: colDef.align,
3498
+ bg: isModified ? "yellow.100" : void 0,
3499
+ _dark: isModified ? { bg: "yellow.900/20" } : void 0,
3500
+ _hover: colDef.editable !== false && !disabled ? { bg: isModified ? "yellow.200" : "bg.subtle" } : void 0,
3501
+ px: 1,
3502
+ borderRadius: "sm",
3503
+ transition: "background 0.3s",
3504
+ children: value != null ? String(value) : "\u2014"
3505
+ }
3506
+ );
3507
+ },
3508
+ size: colDef.width ? parseInt(colDef.width) : void 0,
3509
+ enableColumnFilter: !!colDef.filter,
3510
+ enableSorting: true
3511
+ });
3512
+ }
3513
+ return cols;
3514
+ }, [columnDefs, resolvedCols, editingCell, disabled, fullPath, form, data, onRowSave, rowSelection]);
3515
+ const table = useReactTable({
3516
+ data,
3517
+ columns: tableColumns,
3518
+ state: {
3519
+ sorting,
3520
+ columnFilters,
3521
+ rowSelection: rowSelectionState,
3522
+ ...columnOrder.length > 0 ? { columnOrder } : {}
3523
+ },
3524
+ onSortingChange: setSorting,
3525
+ onColumnFiltersChange: setColumnFilters,
3526
+ onColumnOrderChange: setColumnOrder,
3527
+ onRowSelectionChange: setRowSelectionState,
3528
+ getCoreRowModel: getCoreRowModel(),
3529
+ getSortedRowModel: getSortedRowModel(),
3530
+ getFilteredRowModel: getFilteredRowModel(),
3531
+ // Пагинация отключается при виртуализации
3532
+ ...virtualized ? {} : { getPaginationRowModel: getPaginationRowModel() },
3533
+ initialState: virtualized ? {} : { pagination: { pageSize } },
3534
+ enableRowSelection: rowSelection,
3535
+ enableColumnResizing: columnResizing,
3536
+ columnResizeMode: "onChange"
3537
+ });
3538
+ const tableContainerRef = useRef(null);
3539
+ const { rows: tableRows } = table.getRowModel();
3540
+ const rowVirtualizer = useVirtualizer({
3541
+ count: tableRows.length,
3542
+ getScrollElement: () => tableContainerRef.current,
3543
+ estimateSize: () => size === "sm" ? 36 : size === "md" ? 44 : 52,
3544
+ overscan: 10,
3545
+ enabled: virtualized
3546
+ });
3547
+ return /* @__PURE__ */ jsxs(Field.Root, { children: [
3548
+ label && /* @__PURE__ */ jsx(Field.Label, { children: label }),
3549
+ columnDefs.some((c) => c.filter) && /* @__PURE__ */ jsx(HStack, { gap: 2, mb: 2, flexWrap: "wrap", children: columnDefs.filter((c) => c.filter).map((colDef) => {
3550
+ const column = table.getColumn(colDef.name);
3551
+ if (!column) return null;
3552
+ return /* @__PURE__ */ jsx(
3553
+ Input,
3554
+ {
3555
+ size: "xs",
3556
+ placeholder: `\u0424\u0438\u043B\u044C\u0442\u0440: ${colDef.label ?? colDef.name}`,
3557
+ value: column.getFilterValue() ?? "",
3558
+ onChange: (e) => column.setFilterValue(e.target.value || void 0),
3559
+ maxW: "200px"
3560
+ },
3561
+ colDef.name
3562
+ );
3563
+ }) }),
3564
+ /* @__PURE__ */ jsx(
3565
+ Box,
3566
+ {
3567
+ ref: virtualized ? tableContainerRef : void 0,
3568
+ overflowX: "auto",
3569
+ overflowY: virtualized ? "auto" : void 0,
3570
+ maxH: virtualized ? virtualHeight : void 0,
3571
+ borderWidth: "1px",
3572
+ borderRadius: "md",
3573
+ children: /* @__PURE__ */ jsxs(Table.Root, { size, interactive: true, variant: "outline", children: [
3574
+ /* @__PURE__ */ jsx(Table.Header, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(Table.Row, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsxs(
3575
+ Table.ColumnHeader,
3576
+ {
3577
+ cursor: header.column.getCanSort() ? "pointer" : "default",
3578
+ onClick: header.column.getToggleSortingHandler(),
3579
+ userSelect: "none",
3580
+ w: columnResizing ? `${header.getSize()}px` : void 0,
3581
+ position: "relative",
3582
+ draggable: header.column.id !== "select",
3583
+ onDragStart: (e) => {
3584
+ e.dataTransfer.setData("text/plain", header.column.id);
3585
+ e.dataTransfer.effectAllowed = "move";
3586
+ },
3587
+ onDragOver: (e) => e.preventDefault(),
3588
+ onDrop: (e) => {
3589
+ e.preventDefault();
3590
+ const fromId = e.dataTransfer.getData("text/plain");
3591
+ const toId = header.column.id;
3592
+ if (fromId === toId) return;
3593
+ const currentOrder = columnOrder.length > 0 ? columnOrder : table.getAllLeafColumns().map((c) => c.id);
3594
+ const fromIdx = currentOrder.indexOf(fromId);
3595
+ const toIdx = currentOrder.indexOf(toId);
3596
+ if (fromIdx === -1 || toIdx === -1) return;
3597
+ const next = [...currentOrder];
3598
+ next.splice(fromIdx, 1);
3599
+ next.splice(toIdx, 0, fromId);
3600
+ setColumnOrder(next);
3601
+ },
3602
+ children: [
3603
+ /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
3604
+ /* @__PURE__ */ jsx(Text, { children: flexRender(header.column.columnDef.header, header.getContext()) }),
3605
+ header.column.getIsSorted() === "asc" && /* @__PURE__ */ jsx(Text, { fontSize: "xs", children: "\u2191" }),
3606
+ header.column.getIsSorted() === "desc" && /* @__PURE__ */ jsx(Text, { fontSize: "xs", children: "\u2193" })
3607
+ ] }),
3608
+ columnResizing && /* @__PURE__ */ jsx(
3609
+ Box,
3610
+ {
3611
+ position: "absolute",
3612
+ right: "0",
3613
+ top: "0",
3614
+ bottom: "0",
3615
+ w: "4px",
3616
+ cursor: "col-resize",
3617
+ userSelect: "none",
3618
+ onMouseDown: header.getResizeHandler(),
3619
+ onTouchStart: header.getResizeHandler(),
3620
+ bg: header.column.getIsResizing() ? "blue.500" : "transparent",
3621
+ _hover: { bg: "blue.300" }
3622
+ }
3623
+ )
3624
+ ]
3625
+ },
3626
+ header.id
3627
+ )) }, headerGroup.id)) }),
3628
+ /* @__PURE__ */ jsx(Table.Body, { children: tableRows.length === 0 ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { colSpan: tableColumns.length, textAlign: "center", py: 8, children: /* @__PURE__ */ jsx(Text, { color: "fg.muted", children: "\u041D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445" }) }) }) : virtualized ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
3629
+ rowVirtualizer.getVirtualItems().length > 0 && /* @__PURE__ */ jsx(Table.Row, { style: { height: `${rowVirtualizer.getVirtualItems()[0]?.start ?? 0}px` }, children: /* @__PURE__ */ jsx(Table.Cell, { colSpan: tableColumns.length, p: "0" }) }),
3630
+ rowVirtualizer.getVirtualItems().map((virtualRow) => {
3631
+ const row = tableRows[virtualRow.index];
3632
+ return /* @__PURE__ */ jsx(
3633
+ Table.Row,
3634
+ {
3635
+ bg: row.getIsSelected() ? "blue.50" : void 0,
3636
+ _dark: row.getIsSelected() ? { bg: "blue.900/20" } : void 0,
3637
+ style: { height: `${virtualRow.size}px` },
3638
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(Table.Cell, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
3639
+ },
3640
+ row.id
3641
+ );
3642
+ }),
3643
+ /* @__PURE__ */ jsx(
3644
+ Table.Row,
3645
+ {
3646
+ style: {
3647
+ height: `${rowVirtualizer.getTotalSize() - (rowVirtualizer.getVirtualItems().at(-1)?.end ?? 0)}px`
3648
+ },
3649
+ children: /* @__PURE__ */ jsx(Table.Cell, { colSpan: tableColumns.length, p: "0" })
3650
+ }
3651
+ )
3652
+ ] }) : tableRows.map((row) => /* @__PURE__ */ jsx(
3653
+ Table.Row,
3654
+ {
3655
+ bg: row.getIsSelected() ? "blue.50" : void 0,
3656
+ _dark: row.getIsSelected() ? { bg: "blue.900/20" } : void 0,
3657
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(Table.Cell, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
3658
+ },
3659
+ row.id
3660
+ )) })
3661
+ ] })
3662
+ }
3663
+ ),
3664
+ /* @__PURE__ */ jsxs(HStack, { justify: "space-between", py: 2, children: [
3665
+ /* @__PURE__ */ jsxs(HStack, { gap: 2, children: [
3666
+ !virtualized && /* @__PURE__ */ jsxs(Fragment$1, { children: [
3667
+ /* @__PURE__ */ jsx(
3668
+ Button,
3669
+ {
3670
+ size: "xs",
3671
+ variant: "outline",
3672
+ onClick: () => table.previousPage(),
3673
+ disabled: !table.getCanPreviousPage(),
3674
+ children: "\u2190 \u041D\u0430\u0437\u0430\u0434"
3675
+ }
3676
+ ),
3677
+ /* @__PURE__ */ jsx(
3678
+ Button,
3679
+ {
3680
+ size: "xs",
3681
+ variant: "outline",
3682
+ onClick: () => table.nextPage(),
3683
+ disabled: !table.getCanNextPage(),
3684
+ children: "\u0414\u0430\u043B\u0435\u0435 \u2192"
3685
+ }
3686
+ )
3687
+ ] }),
3688
+ /* @__PURE__ */ jsx(
3689
+ Button,
3690
+ {
3691
+ size: "xs",
3692
+ variant: "ghost",
3693
+ onClick: () => {
3694
+ const headers = columnDefs.map((c) => c.label ?? c.name).join(",");
3695
+ const csvRows = data.map(
3696
+ (row) => columnDefs.map((c) => {
3697
+ const val = row[c.name];
3698
+ const str = String(val ?? "");
3699
+ return str.includes(",") ? `"${str}"` : str;
3700
+ }).join(",")
3701
+ );
3702
+ const csv = [headers, ...csvRows].join("\n");
3703
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
3704
+ const url = URL.createObjectURL(blob);
3705
+ const a = document.createElement("a");
3706
+ a.href = url;
3707
+ a.download = `${name}-export.csv`;
3708
+ a.click();
3709
+ URL.revokeObjectURL(url);
3710
+ },
3711
+ children: "\u2193 CSV"
3712
+ }
3713
+ )
3714
+ ] }),
3715
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: virtualized ? `${data.length} \u0437\u0430\u043F\u0438\u0441\u0435\u0439` : `\u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 ${table.getState().pagination.pageIndex + 1} \u0438\u0437 ${table.getPageCount()} (${data.length} \u0437\u0430\u043F\u0438\u0441\u0435\u0439)` })
3716
+ ] }),
3717
+ rowSelection && Object.keys(rowSelectionState).length > 0 && /* @__PURE__ */ jsx(HStack, { gap: 2, children: /* @__PURE__ */ jsxs(
3718
+ Button,
3719
+ {
3720
+ size: "xs",
3721
+ colorPalette: "red",
3722
+ variant: "ghost",
3723
+ onClick: () => {
3724
+ const indices = Object.keys(rowSelectionState).filter((k) => rowSelectionState[k]).map(Number).sort((a, b) => b - a);
3725
+ for (const idx of indices) {
3726
+ arrayField.removeValue(idx);
3727
+ }
3728
+ setRowSelectionState({});
3729
+ },
3730
+ disabled,
3731
+ children: [
3732
+ "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0435 (",
3733
+ Object.values(rowSelectionState).filter(Boolean).length,
3734
+ ")"
3735
+ ]
3736
+ }
3737
+ ) }),
3738
+ helperText && /* @__PURE__ */ jsx(Field.HelperText, { children: helperText })
3739
+ ] });
3740
+ } });
3741
+ }
3742
+ function EditableCell({
3743
+ value,
3744
+ fieldType,
3745
+ onSave,
3746
+ onCancel
3747
+ }) {
3748
+ const [localValue, setLocalValue] = useState(String(value ?? ""));
3749
+ const handleKeyDown = (e) => {
3750
+ if (e.key === "Enter") {
3751
+ const coerced = fieldType === "number" ? Number(localValue) || 0 : localValue;
3752
+ onSave(coerced);
3753
+ }
3754
+ if (e.key === "Escape") {
3755
+ onCancel();
3756
+ }
3757
+ };
3758
+ return /* @__PURE__ */ jsx(
3759
+ Input,
3760
+ {
3761
+ size: "xs",
3762
+ value: localValue,
3763
+ onChange: (e) => setLocalValue(e.target.value),
3764
+ onBlur: () => {
3765
+ const coerced = fieldType === "number" ? Number(localValue) || 0 : localValue;
3766
+ onSave(coerced);
3767
+ },
3768
+ onKeyDown: handleKeyDown,
3769
+ type: fieldType === "number" ? "number" : "text",
3770
+ autoFocus: true,
3771
+ borderRadius: "0"
3772
+ }
3773
+ );
3774
+ }
3775
+ var TableEditorContext = createContext(null);
3776
+ function useTableEditorContext() {
3777
+ const ctx = useContext(TableEditorContext);
3778
+ if (!ctx) {
3779
+ throw new Error("useTableEditorContext must be used inside <Form.Field.TableEditor>");
3780
+ }
3781
+ return ctx;
3782
+ }
3783
+
3784
+ // src/lib/declarative/form-fields/table/table-utils.ts
3785
+ function formatCellValue(value, column) {
3786
+ if (value === null || value === void 0 || value === "") return "";
3787
+ if (column.format) {
3788
+ return column.format(value);
3789
+ }
3790
+ if (column.fieldType === "boolean") {
3791
+ return value ? "\u2713" : "\u2717";
3792
+ }
3793
+ if (column.fieldType === "date" && value instanceof Date) {
3794
+ return value.toLocaleDateString();
3795
+ }
3796
+ if (column.fieldType === "number" && typeof value === "number") {
3797
+ return value.toLocaleString();
3798
+ }
3799
+ return String(value);
3800
+ }
3801
+ function getDefaultRow(columns) {
3802
+ const row = {};
3803
+ for (const col of columns) {
3804
+ if (col.computed) continue;
3805
+ switch (col.fieldType) {
3806
+ case "string":
3807
+ row[col.name] = "";
3808
+ break;
3809
+ case "number":
3810
+ row[col.name] = 0;
3811
+ break;
3812
+ case "boolean":
3813
+ row[col.name] = false;
3814
+ break;
3815
+ case "date":
3816
+ row[col.name] = "";
3817
+ break;
3818
+ case "enum":
3819
+ row[col.name] = col.enumValues?.[0] ?? "";
3820
+ break;
3821
+ default:
3822
+ row[col.name] = "";
3823
+ }
3824
+ }
3825
+ return row;
3826
+ }
3827
+ function coerceValue(raw, column) {
3828
+ const trimmed = raw.trim();
3829
+ switch (column.fieldType) {
3830
+ case "number": {
3831
+ const normalized = trimmed.replace(",", ".").replace(/\s/g, "");
3832
+ const num = Number(normalized);
3833
+ return Number.isNaN(num) ? 0 : num;
3834
+ }
3835
+ case "boolean":
3836
+ return ["true", "1", "\u0434\u0430", "yes", "\u2713"].includes(trimmed.toLowerCase());
3837
+ case "date":
3838
+ return trimmed;
3839
+ default:
3840
+ return trimmed;
3841
+ }
3842
+ }
3843
+ function parseTSV(text) {
3844
+ return text.split("\n").map((line) => line.split(" ")).filter((row) => row.some((cell) => cell.trim() !== ""));
3845
+ }
3846
+ function computeAggregate(rows, columnName, type, computeFn) {
3847
+ const values = rows.map((row) => {
3848
+ const raw = computeFn ? computeFn(row) : row[columnName];
3849
+ return typeof raw === "number" ? raw : Number(raw);
3850
+ }).filter((v) => !Number.isNaN(v));
3851
+ if (values.length === 0) return 0;
3852
+ switch (type) {
3853
+ case "sum":
3854
+ return values.reduce((a, b) => a + b, 0);
3855
+ case "avg":
3856
+ return values.reduce((a, b) => a + b, 0) / values.length;
3857
+ case "count":
3858
+ return values.length;
3859
+ case "min":
3860
+ return Math.min(...values);
3861
+ case "max":
3862
+ return Math.max(...values);
3863
+ }
3864
+ }
3865
+ function TableEditorFooter({ footerDefs, selectable, sortable }) {
3866
+ const { columns, rows, readOnly } = useTableEditorContext();
3867
+ if (footerDefs.length === 0 || rows.length === 0) return null;
3868
+ const aggregates = /* @__PURE__ */ new Map();
3869
+ for (const def of footerDefs) {
3870
+ const col = columns.find((c) => c.name === def.column);
3871
+ const value = computeAggregate(rows, def.column, def.aggregate, col?.computed);
3872
+ aggregates.set(def.column, { value, def });
3873
+ }
3874
+ return /* @__PURE__ */ jsx(Table.Footer, { children: /* @__PURE__ */ jsxs(Table.Row, { fontWeight: "bold", children: [
3875
+ sortable && !readOnly && /* @__PURE__ */ jsx(Table.Cell, {}),
3876
+ selectable && !readOnly && /* @__PURE__ */ jsx(Table.Cell, {}),
3877
+ columns.map((col) => {
3878
+ const agg = aggregates.get(col.name);
3879
+ return /* @__PURE__ */ jsx(Table.Cell, { textAlign: col.align, children: agg ? /* @__PURE__ */ jsxs(Text, { children: [
3880
+ agg.def.label && /* @__PURE__ */ jsx(Text, { as: "span", color: "fg.muted", mr: "1", children: agg.def.label }),
3881
+ agg.def.format ? agg.def.format(agg.value) : agg.value.toLocaleString()
3882
+ ] }) : null }, col.name);
3883
+ }),
3884
+ !readOnly && /* @__PURE__ */ jsx(Table.Cell, {})
3885
+ ] }) });
3886
+ }
3887
+ function TableEditorHeader({ selectable, sortable }) {
3888
+ const { columns, rows, selectedRows, toggleSelectAll, readOnly } = useTableEditorContext();
3889
+ const allSelected = rows.length > 0 && selectedRows.size === rows.length;
3890
+ const someSelected = selectedRows.size > 0 && !allSelected;
3891
+ return /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
3892
+ sortable && !readOnly && /* @__PURE__ */ jsx(Table.ColumnHeader, { w: "40px" }),
3893
+ selectable && !readOnly && /* @__PURE__ */ jsx(Table.ColumnHeader, { w: "40px", textAlign: "center", children: /* @__PURE__ */ jsxs(
3894
+ Checkbox.Root,
3895
+ {
3896
+ checked: allSelected ? true : someSelected ? "indeterminate" : false,
3897
+ onCheckedChange: () => toggleSelectAll(),
3898
+ size: "sm",
3899
+ children: [
3900
+ /* @__PURE__ */ jsx(Checkbox.HiddenInput, {}),
3901
+ /* @__PURE__ */ jsx(Checkbox.Control, {})
3902
+ ]
3903
+ }
3904
+ ) }),
3905
+ columns.map((col) => /* @__PURE__ */ jsxs(Table.ColumnHeader, { w: col.width, textAlign: col.align, children: [
3906
+ col.label,
3907
+ col.required && /* @__PURE__ */ jsx("span", { style: { color: "var(--chakra-colors-red-500)", marginLeft: "2px" }, children: "*" })
3908
+ ] }, col.name)),
3909
+ !readOnly && /* @__PURE__ */ jsx(Table.ColumnHeader, { w: "40px" })
3910
+ ] }) });
3911
+ }
3912
+ function TableMobileView() {
3913
+ const { columns, rows, addRow, removeRow, canAdd, canRemove, readOnly, disabled } = useTableEditorContext();
3914
+ return /* @__PURE__ */ jsxs(VStack, { gap: 3, align: "stretch", children: [
3915
+ rows.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "fg.muted", textAlign: "center", py: 6, children: "\u041D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445" }) : rows.map((rowData, rowIndex) => /* @__PURE__ */ jsxs(Box, { p: 3, borderWidth: "1px", borderRadius: "md", position: "relative", children: [
3916
+ /* @__PURE__ */ jsxs(HStack, { justify: "space-between", mb: 2, children: [
3917
+ /* @__PURE__ */ jsxs(Text, { fontSize: "xs", color: "fg.muted", fontWeight: "bold", children: [
3918
+ "#",
3919
+ rowIndex + 1
3920
+ ] }),
3921
+ !readOnly && /* @__PURE__ */ jsx(
3922
+ IconButton,
3923
+ {
3924
+ "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C",
3925
+ size: "xs",
3926
+ variant: "ghost",
3927
+ colorPalette: "red",
3928
+ onClick: () => removeRow(rowIndex),
3929
+ disabled: !canRemove || disabled,
3930
+ children: "\u2715"
3931
+ }
3932
+ )
3933
+ ] }),
3934
+ /* @__PURE__ */ jsx(VStack, { gap: 1, align: "stretch", children: columns.map((col) => {
3935
+ const value = col.computed ? col.computed(rowData) : rowData[col.name];
3936
+ return /* @__PURE__ */ jsxs(HStack, { justify: "space-between", fontSize: "sm", children: [
3937
+ /* @__PURE__ */ jsx(Text, { color: "fg.muted", fontWeight: "medium", minW: "80px", children: col.label }),
3938
+ /* @__PURE__ */ jsx(Text, { textAlign: "right", children: formatCellValue(value, col) || "\u2014" })
3939
+ ] }, col.name);
3940
+ }) })
3941
+ ] }, rowIndex)),
3942
+ !readOnly && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: addRow, disabled: !canAdd || disabled, children: "+ \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C" })
3943
+ ] });
3944
+ }
3945
+ function TableCell({ rowIndex, colIndex, column, rowData }) {
3946
+ const { form } = useDeclarativeForm();
3947
+ const { navigation, setEditingCell, setFocusedCell, fullPath, disabled, readOnly } = useTableEditorContext();
3948
+ const inputRef = useRef(null);
3949
+ const cellRef = useRef(null);
3950
+ const isEditing = navigation.editingCell?.row === rowIndex && navigation.editingCell?.col === colIndex;
3951
+ const isComputed = !!column.computed;
3952
+ const isReadOnly = readOnly || column.readOnly || isComputed || disabled;
3953
+ const fieldPath = `${fullPath}[${rowIndex}].${column.name}`;
3954
+ const startEdit = useCallback(() => {
3955
+ if (isReadOnly) return;
3956
+ setEditingCell({ row: rowIndex, col: colIndex });
3957
+ }, [isReadOnly, setEditingCell, rowIndex, colIndex]);
3958
+ const handleCellKeyDown = useCallback(
3959
+ (e) => {
3960
+ if (isReadOnly) return;
3961
+ if (e.key === "Enter" || e.key === "F2") {
3962
+ e.preventDefault();
3963
+ startEdit();
3964
+ }
3965
+ },
3966
+ [isReadOnly, startEdit]
3967
+ );
3968
+ if (isComputed) {
3969
+ const computedValue = column.computed(rowData);
3970
+ return /* @__PURE__ */ jsx(Table.Cell, { textAlign: column.align, "data-row": rowIndex, "data-col": colIndex, ref: cellRef, children: formatCellValue(computedValue, column) });
3971
+ }
3972
+ return /* @__PURE__ */ jsx(form.Field, { name: fieldPath, children: (field) => {
3973
+ const errors = field.state.meta.errors;
3974
+ const hasError = hasFieldErrors(errors);
3975
+ const value = field.state.value;
3976
+ if (isEditing) {
3977
+ return /* @__PURE__ */ jsx(
3978
+ EditingCell,
3979
+ {
3980
+ ref: inputRef,
3981
+ column,
3982
+ value,
3983
+ hasError,
3984
+ errors,
3985
+ onBlur: (newValue) => {
3986
+ field.handleChange(newValue);
3987
+ setEditingCell(null);
3988
+ },
3989
+ onChange: (newValue) => field.handleChange(newValue),
3990
+ rowIndex,
3991
+ colIndex
3992
+ }
3993
+ );
3994
+ }
3995
+ return /* @__PURE__ */ jsx(
3996
+ Table.Cell,
3997
+ {
3998
+ ref: cellRef,
3999
+ textAlign: column.align,
4000
+ "data-row": rowIndex,
4001
+ "data-col": colIndex,
4002
+ tabIndex: isReadOnly ? void 0 : 0,
4003
+ cursor: isReadOnly ? "default" : "pointer",
4004
+ onClick: startEdit,
4005
+ onKeyDown: handleCellKeyDown,
4006
+ onFocus: () => setFocusedCell({ row: rowIndex, col: colIndex }),
4007
+ borderColor: hasError ? "red.500" : void 0,
4008
+ borderWidth: hasError ? "1px" : void 0,
4009
+ title: hasError ? formatFieldErrors(errors) : void 0,
4010
+ _hover: isReadOnly ? void 0 : { bg: "bg.subtle" },
4011
+ children: formatCellValue(value, column) || /* @__PURE__ */ jsx("span", { style: { opacity: 0.4 }, children: column.placeholder ?? "\u2014" })
4012
+ }
4013
+ );
4014
+ } });
4015
+ }
4016
+ function EditingCell({
4017
+ column,
4018
+ value,
4019
+ hasError,
4020
+ errors,
4021
+ onBlur,
4022
+ onChange,
4023
+ rowIndex,
4024
+ colIndex,
4025
+ ref
4026
+ }) {
4027
+ const { setEditingCell } = useTableEditorContext();
4028
+ const [localValue, setLocalValue] = useState(String(value ?? ""));
4029
+ useEffect(() => {
4030
+ const el = ref.current;
4031
+ if (el) {
4032
+ el.focus();
4033
+ if ("select" in el) {
4034
+ el.select();
4035
+ }
4036
+ }
4037
+ }, [ref]);
4038
+ const handleKeyDown = useCallback(
4039
+ (e) => {
4040
+ if (e.key === "Escape") {
4041
+ e.preventDefault();
4042
+ setEditingCell(null);
4043
+ }
4044
+ if (e.key === "Tab" || e.key === "Enter") ;
4045
+ },
4046
+ [setEditingCell]
4047
+ );
4048
+ if (column.fieldType === "enum" && column.enumValues) {
4049
+ return /* @__PURE__ */ jsx(Table.Cell, { "data-row": rowIndex, "data-col": colIndex, p: "0", children: /* @__PURE__ */ jsx(NativeSelect.Root, { size: "sm", children: /* @__PURE__ */ jsxs(
4050
+ NativeSelect.Field,
4051
+ {
4052
+ ref,
4053
+ value: String(value ?? ""),
4054
+ onChange: (e) => onChange(e.target.value),
4055
+ onBlur: () => onBlur(value),
4056
+ onKeyDown: handleKeyDown,
4057
+ borderColor: hasError ? "red.500" : void 0,
4058
+ children: [
4059
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
4060
+ column.enumValues.map((v) => /* @__PURE__ */ jsx("option", { value: v, children: v }, v))
4061
+ ]
4062
+ }
4063
+ ) }) });
4064
+ }
4065
+ if (column.fieldType === "boolean") {
4066
+ return /* @__PURE__ */ jsx(Table.Cell, { "data-row": rowIndex, "data-col": colIndex, textAlign: "center", p: "0", children: /* @__PURE__ */ jsx(
4067
+ "input",
4068
+ {
4069
+ ref,
4070
+ type: "checkbox",
4071
+ checked: !!value,
4072
+ onChange: (e) => {
4073
+ onChange(e.target.checked);
4074
+ onBlur(e.target.checked);
4075
+ },
4076
+ onKeyDown: handleKeyDown
4077
+ }
4078
+ ) });
4079
+ }
4080
+ const inputType = column.fieldType === "number" ? "number" : "text";
4081
+ return /* @__PURE__ */ jsx(Table.Cell, { "data-row": rowIndex, "data-col": colIndex, p: "0", children: /* @__PURE__ */ jsx(
4082
+ Input,
4083
+ {
4084
+ ref,
4085
+ size: "sm",
4086
+ type: inputType,
4087
+ value: localValue,
4088
+ onChange: (e) => setLocalValue(e.target.value),
4089
+ onBlur: () => {
4090
+ const coerced = column.fieldType === "number" ? Number(localValue) || 0 : localValue;
4091
+ onBlur(coerced);
4092
+ },
4093
+ onKeyDown: handleKeyDown,
4094
+ textAlign: column.align,
4095
+ borderColor: hasError ? "red.500" : void 0,
4096
+ borderRadius: "0",
4097
+ title: hasError ? formatFieldErrors(errors) : void 0
4098
+ }
4099
+ ) });
4100
+ }
4101
+ function TableEditorRow({ rowIndex, rowData, selectable, sortable }) {
4102
+ const { columns, removeRow, canRemove, selectedRows, toggleRowSelection, readOnly, disabled } = useTableEditorContext();
4103
+ const isSelected = selectedRows.has(rowIndex);
4104
+ return /* @__PURE__ */ jsxs(
4105
+ Table.Row,
4106
+ {
4107
+ "data-row-index": rowIndex,
4108
+ bg: isSelected ? "blue.50" : void 0,
4109
+ _dark: isSelected ? { bg: "blue.900/20" } : void 0,
4110
+ children: [
4111
+ sortable && !readOnly && /* @__PURE__ */ jsx(Table.Cell, { w: "40px", cursor: "grab", textAlign: "center", color: "fg.muted", children: "\u283F" }),
4112
+ selectable && !readOnly && /* @__PURE__ */ jsx(Table.Cell, { w: "40px", textAlign: "center", children: /* @__PURE__ */ jsxs(Checkbox.Root, { checked: isSelected, onCheckedChange: () => toggleRowSelection(rowIndex), size: "sm", children: [
4113
+ /* @__PURE__ */ jsx(Checkbox.HiddenInput, {}),
4114
+ /* @__PURE__ */ jsx(Checkbox.Control, {})
4115
+ ] }) }),
4116
+ columns.map((col, colIndex) => /* @__PURE__ */ jsx(TableCell, { rowIndex, colIndex, column: col, rowData }, col.name)),
4117
+ !readOnly && /* @__PURE__ */ jsx(Table.Cell, { w: "40px", textAlign: "center", children: /* @__PURE__ */ jsx(
4118
+ IconButton,
4119
+ {
4120
+ "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443",
4121
+ size: "xs",
4122
+ variant: "ghost",
4123
+ colorPalette: "red",
4124
+ onClick: () => removeRow(rowIndex),
4125
+ disabled: !canRemove || disabled,
4126
+ children: "\u2715"
4127
+ }
4128
+ ) })
4129
+ ]
4130
+ }
4131
+ );
4132
+ }
4133
+ function TableEditorToolbar({ addLabel = "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443", actions }) {
4134
+ const { rows, canAdd, addRow, selectedRows, removeRow, readOnly, disabled } = useTableEditorContext();
4135
+ const handleBulkDelete = () => {
4136
+ const indices = [...selectedRows].sort((a, b) => b - a);
4137
+ for (const idx of indices) {
4138
+ removeRow(idx);
4139
+ }
4140
+ };
4141
+ return /* @__PURE__ */ jsxs(HStack, { justify: "space-between", py: "2", children: [
4142
+ /* @__PURE__ */ jsxs(HStack, { gap: "2", children: [
4143
+ !readOnly && /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: addRow, disabled: !canAdd || disabled, children: [
4144
+ "+ ",
4145
+ addLabel
4146
+ ] }),
4147
+ !readOnly && selectedRows.size > 0 && /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "ghost", colorPalette: "red", onClick: handleBulkDelete, disabled, children: [
4148
+ "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0435 (",
4149
+ selectedRows.size,
4150
+ ")"
4151
+ ] }),
4152
+ actions
4153
+ ] }),
4154
+ /* @__PURE__ */ jsxs(Text, { fontSize: "sm", color: "fg.muted", children: [
4155
+ rows.length,
4156
+ " ",
4157
+ rows.length === 1 ? "\u0441\u0442\u0440\u043E\u043A\u0430" : rows.length < 5 ? "\u0441\u0442\u0440\u043E\u043A\u0438" : "\u0441\u0442\u0440\u043E\u043A"
4158
+ ] })
4159
+ ] });
4160
+ }
4161
+ function getEditableColIndices(columns) {
4162
+ return columns.map((col, i) => !col.computed && !col.readOnly ? i : -1).filter((i) => i !== -1);
4163
+ }
4164
+ function useTableNavigation({
4165
+ columns,
4166
+ rowCount,
4167
+ editingCell,
4168
+ setEditingCell,
4169
+ addRow,
4170
+ canAdd,
4171
+ readOnly
4172
+ }) {
4173
+ const containerRef = useRef(null);
4174
+ const editableIndices = getEditableColIndices(columns);
4175
+ const focusCell = useCallback((row, col) => {
4176
+ const container = containerRef.current;
4177
+ if (!container) return;
4178
+ const cell = container.querySelector(`[data-row="${row}"][data-col="${col}"]`);
4179
+ if (cell instanceof HTMLElement) {
4180
+ const input = cell.querySelector("input, select");
4181
+ if (input) {
4182
+ input.focus();
4183
+ } else {
4184
+ cell.focus();
4185
+ }
4186
+ }
4187
+ }, []);
4188
+ const moveToNext = useCallback(
4189
+ (currentRow, currentCol, reverse = false) => {
4190
+ if (readOnly || editableIndices.length === 0) return;
4191
+ const currentEditIdx = editableIndices.indexOf(currentCol);
4192
+ if (currentEditIdx === -1) return;
4193
+ if (!reverse) {
4194
+ if (currentEditIdx < editableIndices.length - 1) {
4195
+ const nextCol = editableIndices[currentEditIdx + 1];
4196
+ setEditingCell({ row: currentRow, col: nextCol });
4197
+ requestAnimationFrame(() => focusCell(currentRow, nextCol));
4198
+ } else if (currentRow < rowCount - 1) {
4199
+ const nextCol = editableIndices[0];
4200
+ setEditingCell({ row: currentRow + 1, col: nextCol });
4201
+ requestAnimationFrame(() => focusCell(currentRow + 1, nextCol));
4202
+ } else if (canAdd) {
4203
+ addRow();
4204
+ const nextCol = editableIndices[0];
4205
+ requestAnimationFrame(() => {
4206
+ setEditingCell({ row: currentRow + 1, col: nextCol });
4207
+ requestAnimationFrame(() => focusCell(currentRow + 1, nextCol));
4208
+ });
4209
+ }
4210
+ } else {
4211
+ if (currentEditIdx > 0) {
4212
+ const prevCol = editableIndices[currentEditIdx - 1];
4213
+ setEditingCell({ row: currentRow, col: prevCol });
4214
+ requestAnimationFrame(() => focusCell(currentRow, prevCol));
4215
+ } else if (currentRow > 0) {
4216
+ const prevCol = editableIndices[editableIndices.length - 1];
4217
+ setEditingCell({ row: currentRow - 1, col: prevCol });
4218
+ requestAnimationFrame(() => focusCell(currentRow - 1, prevCol));
4219
+ }
4220
+ }
4221
+ },
4222
+ [readOnly, editableIndices, rowCount, canAdd, addRow, setEditingCell, focusCell]
4223
+ );
4224
+ const handleKeyDown = useCallback(
4225
+ (e) => {
4226
+ if (!editingCell) return;
4227
+ const { row, col } = editingCell;
4228
+ switch (e.key) {
4229
+ case "Tab":
4230
+ e.preventDefault();
4231
+ setEditingCell(null);
4232
+ requestAnimationFrame(() => moveToNext(row, col, e.shiftKey));
4233
+ break;
4234
+ case "Enter":
4235
+ e.preventDefault();
4236
+ setEditingCell(null);
4237
+ requestAnimationFrame(() => moveToNext(row, col, false));
4238
+ break;
4239
+ case "Escape":
4240
+ e.preventDefault();
4241
+ setEditingCell(null);
4242
+ requestAnimationFrame(() => focusCell(row, col));
4243
+ break;
4244
+ case "ArrowUp":
4245
+ if (row > 0) {
4246
+ e.preventDefault();
4247
+ setEditingCell(null);
4248
+ setEditingCell({ row: row - 1, col });
4249
+ requestAnimationFrame(() => focusCell(row - 1, col));
4250
+ }
4251
+ break;
4252
+ case "ArrowDown":
4253
+ if (row < rowCount - 1) {
4254
+ e.preventDefault();
4255
+ setEditingCell(null);
4256
+ setEditingCell({ row: row + 1, col });
4257
+ requestAnimationFrame(() => focusCell(row + 1, col));
4258
+ }
4259
+ break;
4260
+ }
4261
+ },
4262
+ [editingCell, setEditingCell, moveToNext, focusCell, rowCount]
4263
+ );
4264
+ return { containerRef, handleKeyDown, focusCell };
4265
+ }
4266
+ function FieldTableEditor({
4267
+ name,
4268
+ label,
4269
+ columns: columnDefs,
4270
+ addLabel,
4271
+ sortable = false,
4272
+ selectable = false,
4273
+ footer,
4274
+ maxRows: maxRowsProp,
4275
+ minRows: minRowsProp,
4276
+ clipboard = true,
4277
+ emptyText = '\u041D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445. \u041D\u0430\u0436\u043C\u0438\u0442\u0435 "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443"',
4278
+ size = "sm",
4279
+ striped = false,
4280
+ toolbarActions,
4281
+ helperText,
4282
+ disabled = false,
4283
+ readOnly = false
4284
+ }) {
4285
+ const { form, schema } = useDeclarativeForm();
4286
+ const parentGroup = useFormGroup();
4287
+ const fullPath = parentGroup ? `${parentGroup.name}.${name}` : name;
4288
+ const columns = useTableColumns(schema, fullPath, columnDefs);
4289
+ const constraints = getZodConstraints(schema, fullPath);
4290
+ const maxRows = maxRowsProp ?? constraints.array?.maxItems;
4291
+ const minRows = minRowsProp ?? constraints.array?.minItems;
4292
+ const [navigation, setNavigation] = useState({
4293
+ editingCell: null,
4294
+ focusedCell: null
4295
+ });
4296
+ const [selectedRows, setSelectedRows] = useState(/* @__PURE__ */ new Set());
4297
+ const setEditingCell = useCallback((cell) => {
4298
+ setNavigation((prev) => ({ ...prev, editingCell: cell }));
4299
+ }, []);
4300
+ const setFocusedCell = useCallback((cell) => {
4301
+ setNavigation((prev) => ({ ...prev, focusedCell: cell }));
4302
+ }, []);
4303
+ const toggleRowSelection = useCallback((index) => {
4304
+ setSelectedRows((prev) => {
4305
+ const next = new Set(prev);
4306
+ if (next.has(index)) {
4307
+ next.delete(index);
4308
+ } else {
4309
+ next.add(index);
4310
+ }
4311
+ return next;
4312
+ });
4313
+ }, []);
4314
+ const addRowRef = { current: () => {
4315
+ } };
4316
+ const rowCountRef = { current: 0 };
4317
+ const canAddRef = { current: false };
4318
+ const { containerRef, handleKeyDown } = useTableNavigation({
4319
+ columns,
4320
+ rowCount: rowCountRef.current,
4321
+ editingCell: navigation.editingCell,
4322
+ setEditingCell,
4323
+ addRow: () => addRowRef.current(),
4324
+ canAdd: canAddRef.current,
4325
+ readOnly
4326
+ });
4327
+ return /* @__PURE__ */ jsx(form.Field, { name: fullPath, mode: "array", children: (arrayField) => {
4328
+ const rows = arrayField.state.value ?? [];
4329
+ const canAdd = maxRows === void 0 || rows.length < maxRows;
4330
+ const canRemove = minRows === void 0 || rows.length > minRows;
4331
+ rowCountRef.current = rows.length;
4332
+ canAddRef.current = canAdd;
4333
+ const addRow = () => {
4334
+ if (!canAdd) return;
4335
+ arrayField.pushValue(getDefaultRow(columns));
4336
+ };
4337
+ addRowRef.current = addRow;
4338
+ const removeRow = (index) => {
4339
+ if (!canRemove) return;
4340
+ arrayField.removeValue(index);
4341
+ setSelectedRows((prev) => {
4342
+ const next = /* @__PURE__ */ new Set();
4343
+ for (const i of prev) {
4344
+ if (i < index) next.add(i);
4345
+ else if (i > index) next.add(i - 1);
4346
+ }
4347
+ return next;
4348
+ });
4349
+ };
4350
+ const moveRow = (from, to) => {
4351
+ arrayField.moveValue(from, to);
4352
+ };
4353
+ const toggleSelectAll = () => {
4354
+ if (selectedRows.size === rows.length) {
4355
+ setSelectedRows(/* @__PURE__ */ new Set());
4356
+ } else {
4357
+ setSelectedRows(new Set(rows.map((_, i) => i)));
4358
+ }
4359
+ };
4360
+ const contextValue = {
4361
+ columns,
4362
+ rows,
4363
+ fullPath,
4364
+ canAdd,
4365
+ canRemove,
4366
+ addRow,
4367
+ removeRow,
4368
+ moveRow,
4369
+ navigation,
4370
+ setEditingCell,
4371
+ setFocusedCell,
4372
+ selectedRows,
4373
+ toggleRowSelection,
4374
+ toggleSelectAll,
4375
+ disabled,
4376
+ readOnly,
4377
+ size
4378
+ };
4379
+ return /* @__PURE__ */ jsx(TableEditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(Field.Root, { children: [
4380
+ label && /* @__PURE__ */ jsx(Field.Label, { children: label }),
4381
+ /* @__PURE__ */ jsx(Box, { display: { base: "block", md: "none" }, children: /* @__PURE__ */ jsx(TableMobileView, {}) }),
4382
+ /* @__PURE__ */ jsx(
4383
+ Box,
4384
+ {
4385
+ ref: containerRef,
4386
+ display: { base: "none", md: "block" },
4387
+ overflowX: "auto",
4388
+ borderWidth: "1px",
4389
+ borderRadius: "md",
4390
+ onKeyDown: handleKeyDown,
4391
+ onPaste: clipboard ? (e) => {
4392
+ const target = e.target;
4393
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return;
4394
+ if (disabled || readOnly || !canAdd) return;
4395
+ const text = e.clipboardData?.getData("text/plain");
4396
+ if (!text) return;
4397
+ const parsed = parseTSV(text);
4398
+ if (parsed.length === 0) return;
4399
+ e.preventDefault();
4400
+ const editableCols = columns.filter((col) => !col.computed && !col.readOnly);
4401
+ for (const rawRow of parsed) {
4402
+ if (maxRows !== void 0 && rows.length >= maxRows) break;
4403
+ const row = {};
4404
+ for (let i = 0; i < editableCols.length && i < rawRow.length; i++) {
4405
+ row[editableCols[i].name] = coerceValue(rawRow[i], editableCols[i]);
4406
+ }
4407
+ arrayField.pushValue(row);
4408
+ }
4409
+ } : void 0,
4410
+ children: /* @__PURE__ */ jsxs(Table.Root, { size, striped, interactive: true, variant: "outline", children: [
4411
+ /* @__PURE__ */ jsx(TableEditorHeader, { selectable, sortable }),
4412
+ /* @__PURE__ */ jsx(Table.Body, { children: rows.length === 0 ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(
4413
+ Table.Cell,
4414
+ {
4415
+ colSpan: columns.length + (selectable && !readOnly ? 1 : 0) + (sortable && !readOnly ? 1 : 0) + (!readOnly ? 1 : 0),
4416
+ textAlign: "center",
4417
+ py: "8",
4418
+ children: /* @__PURE__ */ jsx(Text, { color: "fg.muted", children: emptyText })
4419
+ }
4420
+ ) }) : sortable && !readOnly ? /* @__PURE__ */ jsx(
4421
+ SortableWrapper,
4422
+ {
4423
+ items: rows.map((_, i) => `${fullPath}-${i}`),
4424
+ onReorder: (oldIdx, newIdx) => moveRow(oldIdx, newIdx),
4425
+ children: rows.map((rowData, rowIndex) => /* @__PURE__ */ jsx(SortableItem, { id: `${fullPath}-${rowIndex}`, children: /* @__PURE__ */ jsx(
4426
+ TableEditorRow,
4427
+ {
4428
+ rowIndex,
4429
+ rowData,
4430
+ selectable,
4431
+ sortable
4432
+ }
4433
+ ) }, `${fullPath}-${rowIndex}`))
4434
+ }
4435
+ ) : rows.map((rowData, rowIndex) => /* @__PURE__ */ jsx(
4436
+ TableEditorRow,
4437
+ {
4438
+ rowIndex,
4439
+ rowData,
4440
+ selectable,
4441
+ sortable
4442
+ },
4443
+ rowIndex
4444
+ )) }),
4445
+ footer && footer.length > 0 && /* @__PURE__ */ jsx(TableEditorFooter, { footerDefs: footer, selectable, sortable })
4446
+ ] })
4447
+ }
4448
+ ),
4449
+ /* @__PURE__ */ jsx(TableEditorToolbar, { addLabel, actions: toolbarActions }),
4450
+ helperText && /* @__PURE__ */ jsx(Field.HelperText, { children: helperText })
4451
+ ] }) });
4452
+ } });
4453
+ }
4454
+ function FormFromSchema({
4455
+ schema,
4456
+ initialValue,
4457
+ onSubmit,
4458
+ submitLabel = "Save",
4459
+ showReset = false,
4460
+ resetLabel = "Reset",
4461
+ exclude,
4462
+ validateOn,
4463
+ middleware,
4464
+ disabled,
4465
+ readOnly,
4466
+ debug,
4467
+ persistence,
4468
+ offline,
4469
+ beforeButtons,
4470
+ afterButtons,
4471
+ gap = 4
4472
+ }) {
4473
+ return /* @__PURE__ */ jsx(
2210
4474
  FormSimple,
2211
4475
  {
2212
4476
  schema,
@@ -2232,6 +4496,37 @@ function FormFromSchema({
2232
4496
  );
2233
4497
  }
2234
4498
  FormFromSchema.displayName = "FormFromSchema";
4499
+ var STATUS_MAP = {
4500
+ info: "info",
4501
+ warning: "warning",
4502
+ error: "error",
4503
+ success: "success",
4504
+ tip: "info"
4505
+ };
4506
+ var COLOR_MAP = {
4507
+ info: "blue",
4508
+ warning: "orange",
4509
+ error: "red",
4510
+ success: "green",
4511
+ tip: "teal"
4512
+ };
4513
+ function FormInfoBlock({
4514
+ variant = "info",
4515
+ title,
4516
+ children,
4517
+ appearance = "subtle",
4518
+ size = "md"
4519
+ }) {
4520
+ const status = STATUS_MAP[variant] ?? "info";
4521
+ const colorPalette = COLOR_MAP[variant] ?? "blue";
4522
+ return /* @__PURE__ */ jsxs(Alert.Root, { status, variant: appearance, size, colorPalette, children: [
4523
+ /* @__PURE__ */ jsx(Alert.Indicator, {}),
4524
+ title ? /* @__PURE__ */ jsxs(Box, { children: [
4525
+ /* @__PURE__ */ jsx(Alert.Title, { children: title }),
4526
+ /* @__PURE__ */ jsx(Alert.Description, { children })
4527
+ ] }) : /* @__PURE__ */ jsx(Alert.Description, { children })
4528
+ ] });
4529
+ }
2235
4530
  var FormStepsContext = createContext(null);
2236
4531
  function useFormStepsContext() {
2237
4532
  const context = useContext(FormStepsContext);
@@ -2827,7 +5122,7 @@ function evaluateWhenCondition(when, fieldValue) {
2827
5122
  }
2828
5123
  return Boolean(fieldValue);
2829
5124
  }
2830
- function getNestedValue(values, path) {
5125
+ function getNestedValue3(values, path) {
2831
5126
  const parts = path.split(".");
2832
5127
  let value = values;
2833
5128
  for (const part of parts) {
@@ -2857,7 +5152,7 @@ function FormStepsStep({
2857
5152
  if (!when) {
2858
5153
  return true;
2859
5154
  }
2860
- const fieldValue = getNestedValue(form.state.values, when.field);
5155
+ const fieldValue = getNestedValue3(form.state.values, when.field);
2861
5156
  return evaluateWhenCondition(when, fieldValue);
2862
5157
  });
2863
5158
  const indexRef = useRef(-1);
@@ -2867,7 +5162,7 @@ function FormStepsStep({
2867
5162
  return;
2868
5163
  }
2869
5164
  const subscription = form.store.subscribe(() => {
2870
- const fieldValue = getNestedValue(form.state.values, when.field);
5165
+ const fieldValue = getNestedValue3(form.state.values, when.field);
2871
5166
  const newIsVisible = evaluateWhenCondition(when, fieldValue);
2872
5167
  if (newIsVisible !== wasVisibleRef.current) {
2873
5168
  wasVisibleRef.current = newIsVisible;
@@ -2996,6 +5291,68 @@ function FormStepsStep({
2996
5291
  ) }) });
2997
5292
  }
2998
5293
  FormStepsStep.displayName = "FormStepsStep";
5294
+ function getNestedValue4(obj, path) {
5295
+ const parts = path.split(".");
5296
+ let result = obj;
5297
+ for (const part of parts) {
5298
+ if (result && typeof result === "object") {
5299
+ result = result[part];
5300
+ } else {
5301
+ return void 0;
5302
+ }
5303
+ }
5304
+ return result;
5305
+ }
5306
+ function FormWatch({ field, onChange }) {
5307
+ const { form } = useDeclarativeForm();
5308
+ const parentGroup = useFormGroup();
5309
+ const fullPath = parentGroup ? `${parentGroup.name}.${field}` : field;
5310
+ const onChangeRef = useRef(onChange);
5311
+ onChangeRef.current = onChange;
5312
+ const subscribe = useCallback(
5313
+ (callback) => {
5314
+ const subscription = form.store.subscribe(callback);
5315
+ if (typeof subscription === "function") {
5316
+ return subscription;
5317
+ }
5318
+ return () => subscription.unsubscribe();
5319
+ },
5320
+ [form]
5321
+ );
5322
+ const getValueSnapshot = useCallback(
5323
+ () => getNestedValue4(form.state.values, fullPath),
5324
+ [form, fullPath]
5325
+ );
5326
+ const value = useSyncExternalStore(subscribe, getValueSnapshot, getValueSnapshot);
5327
+ const fieldChangeApi = useMemo(
5328
+ () => ({
5329
+ setFieldValue: (name, val) => {
5330
+ form.setFieldValue(name, val);
5331
+ },
5332
+ getFieldValue: (name) => {
5333
+ return getNestedValue4(form.state.values, name);
5334
+ },
5335
+ getValues: () => {
5336
+ return form.state.values;
5337
+ }
5338
+ }),
5339
+ [form]
5340
+ );
5341
+ const prevValueRef = useRef(value);
5342
+ const isFirstRender = useRef(true);
5343
+ useEffect(() => {
5344
+ if (isFirstRender.current) {
5345
+ isFirstRender.current = false;
5346
+ prevValueRef.current = value;
5347
+ return;
5348
+ }
5349
+ if (!Object.is(prevValueRef.current, value)) {
5350
+ prevValueRef.current = value;
5351
+ onChangeRef.current(value, fieldChangeApi);
5352
+ }
5353
+ }, [value, fieldChangeApi]);
5354
+ return null;
5355
+ }
2999
5356
  function extractFieldNames2(children, parentPath = "") {
3000
5357
  const names = [];
3001
5358
  Children.forEach(children, (child) => {
@@ -3096,6 +5453,207 @@ function FormWhen({
3096
5453
  return /* @__PURE__ */ jsx(FormWhenContent, { shouldRender, fallback, parentPath, children });
3097
5454
  } });
3098
5455
  }
5456
+ function FormFromTemplate({
5457
+ template,
5458
+ onSubmit,
5459
+ initialValue,
5460
+ override,
5461
+ submitLabel = "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C",
5462
+ debug = false
5463
+ }) {
5464
+ const mergedInitialValue = initialValue ? { ...template.defaultValues, ...initialValue } : template.defaultValues;
5465
+ return /* @__PURE__ */ jsx(
5466
+ Form2.FromSchema,
5467
+ {
5468
+ schema: template.schema,
5469
+ initialValue: mergedInitialValue,
5470
+ onSubmit,
5471
+ exclude: override?.exclude,
5472
+ debug,
5473
+ submitLabel
5474
+ }
5475
+ );
5476
+ }
5477
+ var loginSchema = z.object({
5478
+ email: z.email("\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 email"),
5479
+ password: z.string().min(6, "\u041C\u0438\u043D\u0438\u043C\u0443\u043C 6 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432")
5480
+ }).strip();
5481
+ var loginForm = {
5482
+ name: "loginForm",
5483
+ title: "\u0412\u0445\u043E\u0434",
5484
+ description: "\u0424\u043E\u0440\u043C\u0430 \u0432\u0445\u043E\u0434\u0430: email + \u043F\u0430\u0440\u043E\u043B\u044C",
5485
+ category: "auth",
5486
+ schema: loginSchema,
5487
+ defaultValues: { email: "", password: "" },
5488
+ renderFields: () => null
5489
+ // Рендеринг через FormFromTemplate
5490
+ };
5491
+ var registerSchema = z.object({
5492
+ name: z.string().min(2, "\u041C\u0438\u043D\u0438\u043C\u0443\u043C 2 \u0441\u0438\u043C\u0432\u043E\u043B\u0430"),
5493
+ email: z.email("\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 email"),
5494
+ password: z.string().min(8, "\u041C\u0438\u043D\u0438\u043C\u0443\u043C 8 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432"),
5495
+ confirmPassword: z.string()
5496
+ }).strip().refine((d) => d.password === d.confirmPassword, {
5497
+ message: "\u041F\u0430\u0440\u043E\u043B\u0438 \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u044E\u0442",
5498
+ path: ["confirmPassword"]
5499
+ });
5500
+ var registerForm = {
5501
+ name: "registerForm",
5502
+ title: "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F",
5503
+ description: "\u0424\u043E\u0440\u043C\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438: \u0438\u043C\u044F, email, \u043F\u0430\u0440\u043E\u043B\u044C, \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435",
5504
+ category: "auth",
5505
+ schema: registerSchema,
5506
+ defaultValues: { name: "", email: "", password: "", confirmPassword: "" },
5507
+ renderFields: () => null
5508
+ };
5509
+ var forgotPasswordSchema = z.object({
5510
+ email: z.email("\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 email")
5511
+ }).strip();
5512
+ var forgotPasswordForm = {
5513
+ name: "forgotPasswordForm",
5514
+ title: "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043F\u0430\u0440\u043E\u043B\u044F",
5515
+ description: "\u0424\u043E\u0440\u043C\u0430 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u043F\u0430\u0440\u043E\u043B\u044F: email",
5516
+ category: "auth",
5517
+ schema: forgotPasswordSchema,
5518
+ defaultValues: { email: "" },
5519
+ renderFields: () => null
5520
+ };
5521
+ var contactSchema = z.object({
5522
+ name: z.string().min(2, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043C\u044F"),
5523
+ email: z.email("\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 email"),
5524
+ phone: z.string().optional(),
5525
+ message: z.string().min(10, "\u041C\u0438\u043D\u0438\u043C\u0443\u043C 10 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432")
5526
+ }).strip();
5527
+ var contactForm = {
5528
+ name: "contactForm",
5529
+ title: "\u041A\u043E\u043D\u0442\u0430\u043A\u0442\u043D\u0430\u044F \u0444\u043E\u0440\u043C\u0430",
5530
+ description: "\u041E\u0431\u0440\u0430\u0442\u043D\u0430\u044F \u0441\u0432\u044F\u0437\u044C: \u0438\u043C\u044F, email, \u0442\u0435\u043B\u0435\u0444\u043E\u043D, \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
5531
+ category: "feedback",
5532
+ schema: contactSchema,
5533
+ defaultValues: { name: "", email: "", phone: "", message: "" },
5534
+ renderFields: () => null
5535
+ };
5536
+ var feedbackSchema = z.object({
5537
+ rating: z.number().min(1).max(5),
5538
+ category: z.string().min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044E"),
5539
+ message: z.string().min(5, "\u041E\u043F\u0438\u0448\u0438\u0442\u0435 \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u0435\u0435"),
5540
+ email: z.email().optional()
5541
+ }).strip();
5542
+ var feedbackForm = {
5543
+ name: "feedbackForm",
5544
+ title: "\u041E\u0442\u0437\u044B\u0432",
5545
+ description: "\u0424\u043E\u0440\u043C\u0430 \u043E\u0442\u0437\u044B\u0432\u0430: \u0440\u0435\u0439\u0442\u0438\u043D\u0433, \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F, \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
5546
+ category: "feedback",
5547
+ schema: feedbackSchema,
5548
+ defaultValues: { rating: 0, category: "", message: "", email: "" },
5549
+ renderFields: () => null
5550
+ };
5551
+ var npsSchema = z.object({
5552
+ score: z.number().min(0).max(10),
5553
+ reason: z.string().optional(),
5554
+ email: z.email().optional()
5555
+ }).strip();
5556
+ var npsForm = {
5557
+ name: "npsForm",
5558
+ title: "NPS-\u043E\u043F\u0440\u043E\u0441",
5559
+ description: "Net Promoter Score: \u043E\u0446\u0435\u043D\u043A\u0430 0-10, \u043F\u0440\u0438\u0447\u0438\u043D\u0430, email",
5560
+ category: "survey",
5561
+ schema: npsSchema,
5562
+ defaultValues: { score: 0, reason: "", email: "" },
5563
+ renderFields: () => null
5564
+ };
5565
+ var companyRegistrationSchema = z.object({
5566
+ inn: z.string().min(10, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0418\u041D\u041D"),
5567
+ kpp: z.string().optional(),
5568
+ ogrn: z.string().optional(),
5569
+ name: z.string().min(2, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
5570
+ address: z.string().min(5, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441"),
5571
+ bik: z.string().optional(),
5572
+ account: z.string().optional(),
5573
+ corrAccount: z.string().optional()
5574
+ }).strip();
5575
+ var companyRegistrationForm = {
5576
+ name: "companyRegistration",
5577
+ title: "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F \u043A\u043E\u043C\u043F\u0430\u043D\u0438\u0438",
5578
+ description: "\u0420\u0435\u043A\u0432\u0438\u0437\u0438\u0442\u044B: \u0418\u041D\u041D, \u041A\u041F\u041F, \u041E\u0413\u0420\u041D, \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435, \u0430\u0434\u0440\u0435\u0441, \u0431\u0430\u043D\u043A\u043E\u0432\u0441\u043A\u0438\u0435 \u0440\u0435\u043A\u0432\u0438\u0437\u0438\u0442\u044B",
5579
+ category: "business",
5580
+ schema: companyRegistrationSchema,
5581
+ defaultValues: { inn: "", kpp: "", ogrn: "", name: "", address: "", bik: "", account: "", corrAccount: "" },
5582
+ renderFields: () => null
5583
+ };
5584
+ var orderItemSchema = z.object({
5585
+ product: z.string().min(1),
5586
+ qty: z.number().min(1),
5587
+ price: z.number().min(0)
5588
+ });
5589
+ var orderSchema = z.object({
5590
+ customer: z.string().min(2, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043C\u044F"),
5591
+ email: z.email("\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 email"),
5592
+ phone: z.string().optional(),
5593
+ address: z.string().min(5, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441"),
5594
+ items: z.array(orderItemSchema).min(1, "\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u0442\u043E\u0432\u0430\u0440"),
5595
+ comment: z.string().optional()
5596
+ }).strip();
5597
+ var orderForm = {
5598
+ name: "orderForm",
5599
+ title: "\u0417\u0430\u043A\u0430\u0437",
5600
+ description: "\u041E\u0444\u043E\u0440\u043C\u043B\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0430\u0437\u0430: \u043A\u043B\u0438\u0435\u043D\u0442, \u0430\u0434\u0440\u0435\u0441, \u0442\u043E\u0432\u0430\u0440\u044B",
5601
+ category: "ecommerce",
5602
+ schema: orderSchema,
5603
+ defaultValues: {
5604
+ customer: "",
5605
+ email: "",
5606
+ phone: "",
5607
+ address: "",
5608
+ items: [{ product: "", qty: 1, price: 0 }],
5609
+ comment: ""
5610
+ },
5611
+ renderFields: () => null
5612
+ };
5613
+ var profileSchema = z.object({
5614
+ firstName: z.string().min(2, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043C\u044F"),
5615
+ lastName: z.string().min(2, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0444\u0430\u043C\u0438\u043B\u0438\u044E"),
5616
+ email: z.email("\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 email"),
5617
+ phone: z.string().optional()
5618
+ }).strip();
5619
+ var profileForm = {
5620
+ name: "profileForm",
5621
+ title: "\u041F\u0440\u043E\u0444\u0438\u043B\u044C",
5622
+ description: "\u041B\u0438\u0447\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435: \u0438\u043C\u044F, \u0444\u0430\u043C\u0438\u043B\u0438\u044F, email, \u0442\u0435\u043B\u0435\u0444\u043E\u043D",
5623
+ category: "profile",
5624
+ schema: profileSchema,
5625
+ defaultValues: { firstName: "", lastName: "", email: "", phone: "" },
5626
+ renderFields: () => null
5627
+ };
5628
+ var addressSchema = z.object({
5629
+ country: z.string().min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043D\u0443"),
5630
+ city: z.string().min(2, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0433\u043E\u0440\u043E\u0434"),
5631
+ street: z.string().min(3, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0443\u043B\u0438\u0446\u0443"),
5632
+ building: z.string().min(1, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u043E\u043C"),
5633
+ apartment: z.string().optional(),
5634
+ zip: z.string().optional()
5635
+ }).strip();
5636
+ var addressForm = {
5637
+ name: "addressForm",
5638
+ title: "\u0410\u0434\u0440\u0435\u0441",
5639
+ description: "\u0410\u0434\u0440\u0435\u0441 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438: \u0441\u0442\u0440\u0430\u043D\u0430, \u0433\u043E\u0440\u043E\u0434, \u0443\u043B\u0438\u0446\u0430, \u0434\u043E\u043C, \u043A\u0432\u0430\u0440\u0442\u0438\u0440\u0430, \u0438\u043D\u0434\u0435\u043A\u0441",
5640
+ category: "address",
5641
+ schema: addressSchema,
5642
+ defaultValues: { country: "", city: "", street: "", building: "", apartment: "", zip: "" },
5643
+ renderFields: () => null
5644
+ };
5645
+ var templates = {
5646
+ loginForm,
5647
+ registerForm,
5648
+ forgotPasswordForm,
5649
+ contactForm,
5650
+ feedbackForm,
5651
+ npsForm,
5652
+ companyRegistration: companyRegistrationForm,
5653
+ orderForm,
5654
+ profileForm,
5655
+ addressForm
5656
+ };
3099
5657
  function createLazyComponent(importFn, fallbackHeight = "40px") {
3100
5658
  const LazyComponent = lazy(async () => {
3101
5659
  const module = await importFn();
@@ -3119,8 +5677,6 @@ function createLazyComponents(imports, fallbackHeight = "40px") {
3119
5677
  {}
3120
5678
  );
3121
5679
  }
3122
-
3123
- // src/lib/declarative/create-form.tsx
3124
5680
  function createForm(options = {}) {
3125
5681
  const {
3126
5682
  extraFields = {},
@@ -3131,7 +5687,8 @@ function createForm(options = {}) {
3131
5687
  lazySelects = {},
3132
5688
  lazyComboboxes = {},
3133
5689
  lazyListboxes = {},
3134
- addressProvider
5690
+ addressProvider,
5691
+ captcha
3135
5692
  } = options;
3136
5693
  const lazySelectComponents = createLazyComponents(lazySelects);
3137
5694
  const lazyComboboxComponents = createLazyComponents(lazyComboboxes);
@@ -3157,10 +5714,14 @@ function createForm(options = {}) {
3157
5714
  ...lazyListboxComponents
3158
5715
  };
3159
5716
  const ExtendedForm = Object.assign(
3160
- // Root component
5717
+ // Root component — оборачивает в CaptchaContext если captcha задан
3161
5718
  function ExtendedFormRoot(props) {
3162
5719
  const mergedProps = addressProvider && !props.addressProvider ? { ...props, addressProvider } : props;
3163
- return Form2(mergedProps);
5720
+ const formElement = Form2(mergedProps);
5721
+ if (captcha) {
5722
+ return /* @__PURE__ */ jsx(CaptchaContext, { value: captcha, children: formElement });
5723
+ }
5724
+ return formElement;
3164
5725
  },
3165
5726
  {
3166
5727
  Group: Form2.Group,
@@ -3172,6 +5733,7 @@ function createForm(options = {}) {
3172
5733
  Errors: Form2.Errors,
3173
5734
  DebugValues: Form2.DebugValues,
3174
5735
  DirtyGuard: Form2.DirtyGuard,
5736
+ Captcha: CaptchaField,
3175
5737
  When: Form2.When,
3176
5738
  Steps: Form2.Steps,
3177
5739
  AutoFields: Form2.AutoFields,
@@ -3185,7 +5747,7 @@ function useFieldActions(fieldName) {
3185
5747
  const { form } = useDeclarativeForm();
3186
5748
  const parentGroup = useFormGroup();
3187
5749
  const fullPath = parentGroup ? `${parentGroup.name}.${fieldName}` : fieldName;
3188
- const getNestedValue2 = useCallback(
5750
+ const getNestedValue5 = useCallback(
3189
5751
  (values) => {
3190
5752
  const parts = fullPath.split(".");
3191
5753
  let result = values;
@@ -3211,8 +5773,8 @@ function useFieldActions(fieldName) {
3211
5773
  [form]
3212
5774
  );
3213
5775
  const getValueSnapshot = useCallback(
3214
- () => getNestedValue2(form.state.values),
3215
- [form, getNestedValue2]
5776
+ () => getNestedValue5(form.state.values),
5777
+ [form, getNestedValue5]
3216
5778
  );
3217
5779
  const getMetaSnapshot = useCallback(() => {
3218
5780
  const meta = form.store.state.fieldMeta[fullPath];
@@ -3445,6 +6007,271 @@ function dateMeta(config) {
3445
6007
  }
3446
6008
  };
3447
6009
  }
6010
+ function useConversationalState(totalFields) {
6011
+ const [currentIndex, setCurrentIndex] = useState(0);
6012
+ const [isCompleted, setIsCompleted] = useState(false);
6013
+ const next = useCallback(() => {
6014
+ if (currentIndex < totalFields - 1) {
6015
+ setCurrentIndex((i) => i + 1);
6016
+ } else {
6017
+ setIsCompleted(true);
6018
+ }
6019
+ }, [currentIndex, totalFields]);
6020
+ const prev = useCallback(() => {
6021
+ if (currentIndex > 0) {
6022
+ setCurrentIndex((i) => i - 1);
6023
+ }
6024
+ }, [currentIndex]);
6025
+ const goTo = useCallback((index) => {
6026
+ if (index >= 0 && index < totalFields) {
6027
+ setCurrentIndex(index);
6028
+ setIsCompleted(false);
6029
+ }
6030
+ }, [totalFields]);
6031
+ return {
6032
+ currentIndex,
6033
+ totalFields,
6034
+ next,
6035
+ prev,
6036
+ goTo,
6037
+ isFirst: currentIndex === 0,
6038
+ isLast: currentIndex === totalFields - 1,
6039
+ isCompleted,
6040
+ progress: totalFields > 0 ? (currentIndex + 1) / totalFields : 0
6041
+ };
6042
+ }
6043
+ function ConversationalMode({
6044
+ children,
6045
+ welcomeScreen,
6046
+ completedScreen,
6047
+ nextLabel = "\u0414\u0430\u043B\u0435\u0435",
6048
+ prevLabel = "\u041D\u0430\u0437\u0430\u0434",
6049
+ submitLabel = "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C",
6050
+ showQuestionNumber = true,
6051
+ showProgress = true,
6052
+ onComplete
6053
+ }) {
6054
+ const childArray = Children.toArray(children);
6055
+ const state = useConversationalState(childArray.length);
6056
+ const containerRef = useRef(null);
6057
+ const handleKeyDown = useCallback(
6058
+ (e) => {
6059
+ if (e.key === "Enter" && !e.shiftKey) {
6060
+ const target = e.target;
6061
+ if (target.tagName === "TEXTAREA") return;
6062
+ e.preventDefault();
6063
+ if (state.isLast) {
6064
+ onComplete?.();
6065
+ } else {
6066
+ state.next();
6067
+ }
6068
+ }
6069
+ if (e.key === "ArrowUp" && e.altKey) {
6070
+ e.preventDefault();
6071
+ state.prev();
6072
+ }
6073
+ if (e.key === "ArrowDown" && e.altKey) {
6074
+ e.preventDefault();
6075
+ state.next();
6076
+ }
6077
+ },
6078
+ [state, onComplete]
6079
+ );
6080
+ if (state.isCompleted) {
6081
+ return /* @__PURE__ */ jsx(VStack, { minH: "300px", justify: "center", align: "center", gap: 4, py: 12, children: completedScreen ?? /* @__PURE__ */ jsx(Text, { fontSize: "lg", children: "\u0413\u043E\u0442\u043E\u0432\u043E!" }) });
6082
+ }
6083
+ const currentChild = childArray[state.currentIndex];
6084
+ return /* @__PURE__ */ jsxs(VStack, { gap: 6, align: "stretch", ref: containerRef, onKeyDown: handleKeyDown, children: [
6085
+ showProgress && /* @__PURE__ */ jsx(Progress.Root, { value: state.progress * 100, size: "xs", colorPalette: "blue", children: /* @__PURE__ */ jsx(Progress.Track, { children: /* @__PURE__ */ jsx(Progress.Range, {}) }) }),
6086
+ showQuestionNumber && /* @__PURE__ */ jsxs(Text, { fontSize: "sm", color: "fg.muted", children: [
6087
+ "\u0412\u043E\u043F\u0440\u043E\u0441 ",
6088
+ state.currentIndex + 1,
6089
+ " \u0438\u0437 ",
6090
+ state.totalFields
6091
+ ] }),
6092
+ /* @__PURE__ */ jsx(
6093
+ Box,
6094
+ {
6095
+ minH: "150px",
6096
+ py: 4,
6097
+ css: {
6098
+ animation: "fadeInUp 0.3s ease-out",
6099
+ "@keyframes fadeInUp": {
6100
+ from: { opacity: 0, transform: "translateY(20px)" },
6101
+ to: { opacity: 1, transform: "translateY(0)" }
6102
+ }
6103
+ },
6104
+ children: currentChild
6105
+ },
6106
+ state.currentIndex
6107
+ ),
6108
+ /* @__PURE__ */ jsxs(Flex, { justify: "space-between", pt: 2, children: [
6109
+ /* @__PURE__ */ jsxs(
6110
+ Button,
6111
+ {
6112
+ variant: "ghost",
6113
+ onClick: state.prev,
6114
+ disabled: state.isFirst,
6115
+ size: "sm",
6116
+ children: [
6117
+ "\u2190 ",
6118
+ prevLabel
6119
+ ]
6120
+ }
6121
+ ),
6122
+ /* @__PURE__ */ jsxs(HStack, { gap: 2, children: [
6123
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: "Enter \u21B5" }),
6124
+ state.isLast ? /* @__PURE__ */ jsx(Button, { colorPalette: "blue", onClick: onComplete, size: "sm", children: submitLabel }) : /* @__PURE__ */ jsxs(Button, { colorPalette: "blue", onClick: state.next, size: "sm", children: [
6125
+ nextLabel,
6126
+ " \u2192"
6127
+ ] })
6128
+ ] })
6129
+ ] })
6130
+ ] });
6131
+ }
6132
+ function useFormAutosave(form, config) {
6133
+ const {
6134
+ endpoint,
6135
+ interval = 5e3,
6136
+ debounce = 1e3,
6137
+ draftId,
6138
+ method = "POST",
6139
+ headers = {},
6140
+ onSave,
6141
+ onError
6142
+ } = config;
6143
+ const [status, setStatus] = useState("idle");
6144
+ const [lastSavedAt, setLastSavedAt] = useState(null);
6145
+ const [error, setError] = useState(null);
6146
+ const lastSavedDataRef = useRef("");
6147
+ const timerRef = useRef(null);
6148
+ const debounceRef = useRef(null);
6149
+ const save = useCallback(
6150
+ async (data) => {
6151
+ const serialized = JSON.stringify(data);
6152
+ if (serialized === lastSavedDataRef.current) return;
6153
+ if (typeof navigator !== "undefined" && !navigator.onLine) {
6154
+ if (draftId) {
6155
+ try {
6156
+ localStorage.setItem(`autosave:${draftId}`, serialized);
6157
+ } catch {
6158
+ }
6159
+ }
6160
+ return;
6161
+ }
6162
+ setStatus("saving");
6163
+ setError(null);
6164
+ try {
6165
+ const url = draftId ? `${endpoint}?draftId=${encodeURIComponent(draftId)}` : endpoint;
6166
+ const response = await fetch(url, {
6167
+ method,
6168
+ headers: {
6169
+ "Content-Type": "application/json",
6170
+ ...headers
6171
+ },
6172
+ body: serialized
6173
+ });
6174
+ if (!response.ok) {
6175
+ throw new Error(`Autosave failed: ${response.status}`);
6176
+ }
6177
+ const result = await response.json().catch(() => null);
6178
+ lastSavedDataRef.current = serialized;
6179
+ setStatus("saved");
6180
+ setLastSavedAt(/* @__PURE__ */ new Date());
6181
+ onSave?.(result);
6182
+ if (draftId) {
6183
+ try {
6184
+ localStorage.removeItem(`autosave:${draftId}`);
6185
+ } catch {
6186
+ }
6187
+ }
6188
+ } catch (err) {
6189
+ const errorMsg = err instanceof Error ? err.message : "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F";
6190
+ setStatus("error");
6191
+ setError(errorMsg);
6192
+ onError?.(err instanceof Error ? err : new Error(errorMsg));
6193
+ if (draftId) {
6194
+ try {
6195
+ localStorage.setItem(`autosave:${draftId}`, serialized);
6196
+ } catch {
6197
+ }
6198
+ }
6199
+ }
6200
+ },
6201
+ [endpoint, method, headers, draftId, onSave, onError]
6202
+ );
6203
+ const saveNow = useCallback(async () => {
6204
+ const values = form.state.values;
6205
+ await save(values);
6206
+ }, [form, save]);
6207
+ const loadDraft = useCallback(async () => {
6208
+ if (!draftId) return null;
6209
+ if (typeof navigator === "undefined" || navigator.onLine) {
6210
+ try {
6211
+ const url = `${endpoint}?draftId=${encodeURIComponent(draftId)}`;
6212
+ const response = await fetch(url);
6213
+ if (response.ok) {
6214
+ const data = await response.json();
6215
+ return data;
6216
+ }
6217
+ } catch {
6218
+ }
6219
+ }
6220
+ try {
6221
+ const stored = localStorage.getItem(`autosave:${draftId}`);
6222
+ if (stored) return JSON.parse(stored);
6223
+ } catch {
6224
+ }
6225
+ return null;
6226
+ }, [endpoint, draftId]);
6227
+ useEffect(() => {
6228
+ const tick = () => {
6229
+ if (debounceRef.current) clearTimeout(debounceRef.current);
6230
+ debounceRef.current = setTimeout(() => {
6231
+ const values = form.state.values;
6232
+ save(values);
6233
+ }, debounce);
6234
+ };
6235
+ timerRef.current = setInterval(tick, interval);
6236
+ return () => {
6237
+ if (timerRef.current) clearInterval(timerRef.current);
6238
+ if (debounceRef.current) clearTimeout(debounceRef.current);
6239
+ };
6240
+ }, [form, save, interval, debounce]);
6241
+ return { status, lastSavedAt, error, saveNow, loadDraft };
6242
+ }
6243
+ function AutosaveIndicator({
6244
+ status,
6245
+ lastSavedAt,
6246
+ error,
6247
+ labels
6248
+ }) {
6249
+ if (status === "idle") return null;
6250
+ const defaultLabels = {
6251
+ idle: "",
6252
+ saving: "\u0421\u043E\u0445\u0440\u0430\u043D\u044F\u044E...",
6253
+ saved: "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E",
6254
+ error: "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F"
6255
+ };
6256
+ const l = { ...defaultLabels, ...labels };
6257
+ return /* @__PURE__ */ jsxs(HStack, { gap: 2, fontSize: "xs", color: "fg.muted", children: [
6258
+ status === "saving" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
6259
+ /* @__PURE__ */ jsx(Spinner, { size: "xs" }),
6260
+ /* @__PURE__ */ jsx(Text, { children: l.saving })
6261
+ ] }),
6262
+ status === "saved" && /* @__PURE__ */ jsxs(Text, { color: "green.500", children: [
6263
+ "\u2713 ",
6264
+ l.saved,
6265
+ lastSavedAt && ` (${lastSavedAt.toLocaleTimeString()})`
6266
+ ] }),
6267
+ status === "error" && /* @__PURE__ */ jsxs(Text, { color: "red.500", children: [
6268
+ "\u2715 ",
6269
+ l.error,
6270
+ ": ",
6271
+ error
6272
+ ] })
6273
+ ] });
6274
+ }
3448
6275
 
3449
6276
  // src/lib/declarative/index.ts
3450
6277
  var ListButton = {
@@ -3505,7 +6332,31 @@ var FormField2 = {
3505
6332
  PinInput: FieldPinInput,
3506
6333
  OTPInput: FieldOTPInput,
3507
6334
  ColorPicker: FieldColorPicker,
3508
- FileUpload: FieldFileUpload
6335
+ FileUpload: FieldFileUpload,
6336
+ Signature: FieldSignature,
6337
+ // Поля для опросников
6338
+ MatrixChoice: FieldMatrixChoice,
6339
+ ImageChoice: FieldImageChoice,
6340
+ Likert: FieldLikert,
6341
+ YesNo: FieldYesNo,
6342
+ // Утилитарные поля
6343
+ Hidden: FieldHidden,
6344
+ Calculated: FieldCalculated,
6345
+ // Табличные редакторы
6346
+ TableEditor: FieldTableEditor,
6347
+ DataGrid: FieldDataGrid,
6348
+ // Банковская карта
6349
+ CreditCard: CreditCardField
6350
+ };
6351
+ var FormDocument = {
6352
+ INN: FieldINN,
6353
+ KPP: FieldKPP,
6354
+ OGRN: FieldOGRN,
6355
+ BIK: FieldBIK,
6356
+ BankAccount: FieldBankAccount,
6357
+ CorrAccount: FieldCorrAccount,
6358
+ SNILS: FieldSNILS,
6359
+ Passport: FieldPassport
3509
6360
  };
3510
6361
  var FormButton = {
3511
6362
  Submit: ButtonSubmit,
@@ -3524,15 +6375,23 @@ var Form2 = Object.assign(Form, {
3524
6375
  Errors: FormErrors,
3525
6376
  DebugValues: FormDebugValues,
3526
6377
  DirtyGuard,
6378
+ InfoBlock: FormInfoBlock,
6379
+ Divider: FormDivider,
6380
+ Watch: FormWatch,
3527
6381
  When: FormWhen,
3528
6382
  Steps: FormSteps2,
3529
6383
  Builder: FormBuilder,
3530
6384
  // Schema-based generation
3531
6385
  AutoFields: FormAutoFields,
3532
6386
  FromSchema: FormFromSchema,
6387
+ FromTemplate: FormFromTemplate,
3533
6388
  // Offline support
3534
6389
  OfflineIndicator: FormOfflineIndicator,
3535
- SyncStatus: FormSyncStatus
6390
+ SyncStatus: FormSyncStatus,
6391
+ // Russian documents (ИНН, ОГРН, БИК, СНИЛС, паспорт)
6392
+ Document: FormDocument,
6393
+ // CAPTCHA (Turnstile / reCAPTCHA / hCaptcha)
6394
+ Captcha: CaptchaField
3536
6395
  });
3537
6396
  function createSafeContext(contextName) {
3538
6397
  const Context = createContext(null);
@@ -3558,6 +6417,364 @@ function createNamedGroupContext(contextName) {
3558
6417
  return createSafeContext(contextName);
3559
6418
  }
3560
6419
 
3561
- export { ButtonSubmit, ChakraFormField, Form2 as Form, FormField, FormGroupDeclarative, FormGroupList, FormGroupListDeclarative, FormGroupListItem, RelationFieldProvider, TanStackFormField, booleanMeta, commonMeta, createForm, createNamedGroupContext, createSafeContext, dateMeta, enumMeta, fieldContext, formContext, numberMeta, relationMeta, textMeta, useAppForm, useFieldActions, useFieldContext, useFormApi, useFormContext, useFormField, useFormGroupList, useFormGroupListItem, useFormStepsContext, useRelationFieldContext, useRelationOptions, useTanStackFormField, useTypedFormContext, useTypedFormSubscribe, withForm, withRelations, withUIMeta, withUIMetaDeep };
6420
+ // src/lib/captcha/verify.ts
6421
+ var VERIFY_URLS = {
6422
+ turnstile: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
6423
+ recaptcha: "https://www.google.com/recaptcha/api/siteverify",
6424
+ hcaptcha: "https://api.hcaptcha.com/siteverify"
6425
+ };
6426
+ async function verifyCaptcha(token, options) {
6427
+ if (!token) {
6428
+ return { success: false, errorCodes: ["missing-input-response"] };
6429
+ }
6430
+ const { provider, secretKey, remoteIp } = options;
6431
+ const url = VERIFY_URLS[provider];
6432
+ if (!url) {
6433
+ return { success: false, errorCodes: ["unknown-provider"] };
6434
+ }
6435
+ const body = new URLSearchParams({
6436
+ secret: secretKey,
6437
+ response: token,
6438
+ ...remoteIp ? { remoteip: remoteIp } : {}
6439
+ });
6440
+ const response = await fetch(url, {
6441
+ method: "POST",
6442
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6443
+ body: body.toString()
6444
+ });
6445
+ if (!response.ok) {
6446
+ return { success: false, errorCodes: ["network-error"] };
6447
+ }
6448
+ const data = await response.json();
6449
+ return {
6450
+ success: Boolean(data.success),
6451
+ errorCodes: data["error-codes"] ?? data.errorCodes,
6452
+ hostname: data.hostname,
6453
+ challengeTs: data.challenge_ts ?? data.challengeTs
6454
+ };
6455
+ }
6456
+ function HistoryControls({
6457
+ history,
6458
+ showCounter = true,
6459
+ size = "sm"
6460
+ }) {
6461
+ return /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
6462
+ /* @__PURE__ */ jsx(
6463
+ IconButton,
6464
+ {
6465
+ "aria-label": "Undo (Ctrl+Z)",
6466
+ size,
6467
+ variant: "ghost",
6468
+ disabled: !history.canUndo,
6469
+ onClick: history.undo,
6470
+ title: "\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C (Ctrl+Z)",
6471
+ children: "\u21A9"
6472
+ }
6473
+ ),
6474
+ /* @__PURE__ */ jsx(
6475
+ IconButton,
6476
+ {
6477
+ "aria-label": "Redo (Ctrl+Y)",
6478
+ size,
6479
+ variant: "ghost",
6480
+ disabled: !history.canRedo,
6481
+ onClick: history.redo,
6482
+ title: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C (Ctrl+Y)",
6483
+ children: "\u21AA"
6484
+ }
6485
+ ),
6486
+ showCounter && /* @__PURE__ */ jsxs(Text, { fontSize: "xs", color: "fg.muted", minW: "40px", textAlign: "center", children: [
6487
+ history.currentIndex + 1,
6488
+ "/",
6489
+ history.historyLength
6490
+ ] })
6491
+ ] });
6492
+ }
6493
+ function useFormHistory(form, config) {
6494
+ const {
6495
+ maxHistory = 50,
6496
+ debounceMs = 500,
6497
+ persist = false,
6498
+ persistKey = "form-history",
6499
+ keyboard = true
6500
+ } = config ?? {};
6501
+ const [history, setHistory] = useState(() => {
6502
+ if (persist && typeof window !== "undefined") {
6503
+ try {
6504
+ const saved = sessionStorage.getItem(persistKey);
6505
+ if (saved) return JSON.parse(saved);
6506
+ } catch {
6507
+ }
6508
+ }
6509
+ return [{ values: form.state.values, timestamp: Date.now() }];
6510
+ });
6511
+ const [currentIndex, setCurrentIndex] = useState(() => {
6512
+ if (persist && typeof window !== "undefined") {
6513
+ try {
6514
+ const saved = sessionStorage.getItem(`${persistKey}-index`);
6515
+ if (saved) return parseInt(saved, 10);
6516
+ } catch {
6517
+ }
6518
+ }
6519
+ return 0;
6520
+ });
6521
+ const debounceRef = useRef(null);
6522
+ const isUndoRedoRef = useRef(false);
6523
+ const pushSnapshot = useCallback((values) => {
6524
+ setHistory((prev) => {
6525
+ const base = prev.slice(0, currentIndex + 1);
6526
+ const entry = { values: structuredClone(values), timestamp: Date.now() };
6527
+ const next = [...base, entry];
6528
+ const trimmed = next.length > maxHistory ? next.slice(next.length - maxHistory) : next;
6529
+ return trimmed;
6530
+ });
6531
+ setCurrentIndex((prev) => {
6532
+ const newIndex = Math.min(prev + 1, maxHistory - 1);
6533
+ return newIndex;
6534
+ });
6535
+ }, [currentIndex, maxHistory]);
6536
+ useEffect(() => {
6537
+ const unsub = form.store.subscribe(() => {
6538
+ if (isUndoRedoRef.current) return;
6539
+ if (debounceRef.current) clearTimeout(debounceRef.current);
6540
+ debounceRef.current = setTimeout(() => {
6541
+ pushSnapshot(form.state.values);
6542
+ }, debounceMs);
6543
+ });
6544
+ return () => {
6545
+ unsub();
6546
+ if (debounceRef.current) clearTimeout(debounceRef.current);
6547
+ };
6548
+ }, [form.store, form.state.values, debounceMs, pushSnapshot]);
6549
+ useEffect(() => {
6550
+ if (!persist) return;
6551
+ try {
6552
+ sessionStorage.setItem(persistKey, JSON.stringify(history));
6553
+ sessionStorage.setItem(`${persistKey}-index`, String(currentIndex));
6554
+ } catch {
6555
+ }
6556
+ }, [history, currentIndex, persist, persistKey]);
6557
+ const applySnapshot = useCallback((entry) => {
6558
+ isUndoRedoRef.current = true;
6559
+ const values = entry.values;
6560
+ for (const [key, value] of Object.entries(values)) {
6561
+ form.setFieldValue(key, value);
6562
+ }
6563
+ setTimeout(() => {
6564
+ isUndoRedoRef.current = false;
6565
+ }, 0);
6566
+ }, [form]);
6567
+ const undo = useCallback(() => {
6568
+ if (currentIndex <= 0) return;
6569
+ const newIndex = currentIndex - 1;
6570
+ setCurrentIndex(newIndex);
6571
+ applySnapshot(history[newIndex]);
6572
+ }, [currentIndex, history, applySnapshot]);
6573
+ const redo = useCallback(() => {
6574
+ if (currentIndex >= history.length - 1) return;
6575
+ const newIndex = currentIndex + 1;
6576
+ setCurrentIndex(newIndex);
6577
+ applySnapshot(history[newIndex]);
6578
+ }, [currentIndex, history, applySnapshot]);
6579
+ const clear = useCallback(() => {
6580
+ const initial = { values: form.state.values, timestamp: Date.now() };
6581
+ setHistory([initial]);
6582
+ setCurrentIndex(0);
6583
+ if (persist) {
6584
+ try {
6585
+ sessionStorage.removeItem(persistKey);
6586
+ sessionStorage.removeItem(`${persistKey}-index`);
6587
+ } catch {
6588
+ }
6589
+ }
6590
+ }, [form.state.values, persist, persistKey]);
6591
+ useEffect(() => {
6592
+ if (!keyboard || typeof window === "undefined") return;
6593
+ const handler = (e) => {
6594
+ const isCtrl = e.ctrlKey || e.metaKey;
6595
+ if (!isCtrl) return;
6596
+ if (e.key === "z" && !e.shiftKey) {
6597
+ e.preventDefault();
6598
+ undo();
6599
+ } else if (e.key === "z" && e.shiftKey || e.key === "y") {
6600
+ e.preventDefault();
6601
+ redo();
6602
+ }
6603
+ };
6604
+ window.addEventListener("keydown", handler);
6605
+ return () => window.removeEventListener("keydown", handler);
6606
+ }, [keyboard, undo, redo]);
6607
+ return {
6608
+ undo,
6609
+ redo,
6610
+ canUndo: currentIndex > 0,
6611
+ canRedo: currentIndex < history.length - 1,
6612
+ currentIndex,
6613
+ historyLength: history.length,
6614
+ clear,
6615
+ history
6616
+ };
6617
+ }
6618
+ function FormReadOnlyView({
6619
+ data,
6620
+ schema,
6621
+ labels = {},
6622
+ exclude = [],
6623
+ include,
6624
+ formatters = {},
6625
+ compact = false
6626
+ }) {
6627
+ const schemaLabels = {};
6628
+ if (schema?._def?.shape) {
6629
+ try {
6630
+ const shape = schema._def.shape();
6631
+ for (const [key, fieldSchema] of Object.entries(shape)) {
6632
+ const title = fieldSchema?._def?.meta?.ui?.title;
6633
+ if (title) schemaLabels[key] = title;
6634
+ }
6635
+ } catch {
6636
+ }
6637
+ }
6638
+ const entries = Object.entries(data).filter(([key]) => {
6639
+ if (exclude.includes(key)) return false;
6640
+ if (include && !include.includes(key)) return false;
6641
+ return true;
6642
+ });
6643
+ return /* @__PURE__ */ jsx(VStack, { gap: compact ? 2 : 4, align: "stretch", children: entries.map(([key, value], index) => {
6644
+ const label = labels[key] ?? schemaLabels[key] ?? humanizeKey(key);
6645
+ const formatter = formatters[key];
6646
+ const displayValue = formatter ? formatter(value) : formatValue(value);
6647
+ return compact ? /* @__PURE__ */ jsxs(HStack, { justify: "space-between", fontSize: "sm", children: [
6648
+ /* @__PURE__ */ jsx(Text, { color: "fg.muted", fontWeight: "medium", children: label }),
6649
+ /* @__PURE__ */ jsx(Text, { children: displayValue })
6650
+ ] }, key) : /* @__PURE__ */ jsxs(Box, { children: [
6651
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", fontWeight: "medium", mb: 1, children: label }),
6652
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", children: displayValue }),
6653
+ index < entries.length - 1 && /* @__PURE__ */ jsx(Separator, { mt: 3 })
6654
+ ] }, key);
6655
+ }) });
6656
+ }
6657
+ function humanizeKey(key) {
6658
+ return key.replace(/([A-Z])/g, " $1").replace(/[_-]/g, " ").trim().replace(/^\w/, (c) => c.toUpperCase());
6659
+ }
6660
+ function formatValue(value) {
6661
+ if (value == null) return "\u2014";
6662
+ if (typeof value === "boolean") return value ? "\u0414\u0430" : "\u041D\u0435\u0442";
6663
+ if (value instanceof Date) return value.toLocaleDateString("ru-RU");
6664
+ if (Array.isArray(value)) return value.join(", ");
6665
+ if (typeof value === "object") return JSON.stringify(value);
6666
+ return String(value);
6667
+ }
6668
+ function FormSkeleton({
6669
+ fields = 5,
6670
+ showSubmit = true,
6671
+ fieldHeight = "60px",
6672
+ gap = 4
6673
+ }) {
6674
+ let fieldCount;
6675
+ if (typeof fields === "number") {
6676
+ fieldCount = fields;
6677
+ } else if (fields?._def?.shape) {
6678
+ try {
6679
+ fieldCount = Object.keys(fields._def.shape()).length;
6680
+ } catch {
6681
+ fieldCount = 5;
6682
+ }
6683
+ } else {
6684
+ fieldCount = 5;
6685
+ }
6686
+ return /* @__PURE__ */ jsxs(VStack, { gap, align: "stretch", children: [
6687
+ Array.from({ length: fieldCount }, (_, i) => /* @__PURE__ */ jsxs(Box, { children: [
6688
+ /* @__PURE__ */ jsx(Skeleton, { height: "14px", width: `${60 + Math.random() * 40}%`, mb: 2 }),
6689
+ /* @__PURE__ */ jsx(Skeleton, { height: fieldHeight, borderRadius: "md" })
6690
+ ] }, i)),
6691
+ showSubmit && /* @__PURE__ */ jsx(Box, { pt: 2, children: /* @__PURE__ */ jsx(Skeleton, { height: "40px", width: "140px", borderRadius: "md" }) })
6692
+ ] });
6693
+ }
6694
+ function FormComparison({
6695
+ original,
6696
+ current,
6697
+ schema,
6698
+ labels = {},
6699
+ onlyChanged = false,
6700
+ exclude = []
6701
+ }) {
6702
+ const schemaLabels = {};
6703
+ if (schema?._def?.shape) {
6704
+ try {
6705
+ const shape = schema._def.shape();
6706
+ for (const [key, fieldSchema] of Object.entries(shape)) {
6707
+ const title = fieldSchema?._def?.meta?.ui?.title;
6708
+ if (title) schemaLabels[key] = title;
6709
+ }
6710
+ } catch {
6711
+ }
6712
+ }
6713
+ const allKeys = [.../* @__PURE__ */ new Set([...Object.keys(original), ...Object.keys(current)])].filter((key) => !exclude.includes(key));
6714
+ const entries = allKeys.map((key) => ({
6715
+ key,
6716
+ label: labels[key] ?? schemaLabels[key] ?? humanizeKey2(key),
6717
+ oldValue: original[key],
6718
+ newValue: current[key],
6719
+ changed: JSON.stringify(original[key]) !== JSON.stringify(current[key])
6720
+ })).filter((entry) => !onlyChanged || entry.changed);
6721
+ if (entries.length === 0) {
6722
+ return /* @__PURE__ */ jsx(Text, { color: "fg.muted", fontSize: "sm", children: "\u041D\u0435\u0442 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439" });
6723
+ }
6724
+ return /* @__PURE__ */ jsx(VStack, { gap: 3, align: "stretch", children: entries.map((entry, index) => /* @__PURE__ */ jsxs(
6725
+ Box,
6726
+ {
6727
+ p: 3,
6728
+ borderRadius: "md",
6729
+ bg: entry.changed ? "yellow.50" : "transparent",
6730
+ _dark: entry.changed ? { bg: "yellow.900/20" } : {},
6731
+ children: [
6732
+ /* @__PURE__ */ jsxs(Text, { fontSize: "xs", color: "fg.muted", fontWeight: "medium", mb: 1, children: [
6733
+ entry.label,
6734
+ entry.changed && /* @__PURE__ */ jsx(Text, { as: "span", color: "orange.500", ml: 1, children: "\u25CF" })
6735
+ ] }),
6736
+ entry.changed ? /* @__PURE__ */ jsxs(HStack, { gap: 4, fontSize: "sm", children: [
6737
+ /* @__PURE__ */ jsxs(Box, { flex: 1, children: [
6738
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "red.500", mb: 0.5, children: "\u0411\u044B\u043B\u043E:" }),
6739
+ /* @__PURE__ */ jsx(Text, { textDecoration: "line-through", color: "fg.muted", children: formatValue2(entry.oldValue) })
6740
+ ] }),
6741
+ /* @__PURE__ */ jsxs(Box, { flex: 1, children: [
6742
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "green.500", mb: 0.5, children: "\u0421\u0442\u0430\u043B\u043E:" }),
6743
+ /* @__PURE__ */ jsx(Text, { fontWeight: "medium", children: formatValue2(entry.newValue) })
6744
+ ] })
6745
+ ] }) : /* @__PURE__ */ jsx(Text, { fontSize: "sm", children: formatValue2(entry.newValue) }),
6746
+ index < entries.length - 1 && /* @__PURE__ */ jsx(Separator, { mt: 2 })
6747
+ ]
6748
+ },
6749
+ entry.key
6750
+ )) });
6751
+ }
6752
+ function humanizeKey2(key) {
6753
+ return key.replace(/([A-Z])/g, " $1").replace(/[_-]/g, " ").trim().replace(/^\w/, (c) => c.toUpperCase());
6754
+ }
6755
+ function formatValue2(value) {
6756
+ if (value == null) return "\u2014";
6757
+ if (typeof value === "boolean") return value ? "\u0414\u0430" : "\u041D\u0435\u0442";
6758
+ if (value instanceof Date) return value.toLocaleDateString("ru-RU");
6759
+ if (Array.isArray(value)) return value.join(", ");
6760
+ if (typeof value === "object") return JSON.stringify(value);
6761
+ return String(value);
6762
+ }
6763
+ function FormDependsOn({ field, cases, fallback }) {
6764
+ const { form } = useDeclarativeForm();
6765
+ return /* @__PURE__ */ jsx(form.Subscribe, { selector: (state) => state.values[field], children: (value) => {
6766
+ const key = String(value ?? "");
6767
+ const content = cases[key];
6768
+ if (content !== void 0) {
6769
+ return /* @__PURE__ */ jsx(Fragment$1, { children: content });
6770
+ }
6771
+ if (fallback !== void 0) {
6772
+ return /* @__PURE__ */ jsx(Fragment$1, { children: fallback });
6773
+ }
6774
+ return null;
6775
+ } });
6776
+ }
6777
+
6778
+ export { AutosaveIndicator, ButtonSubmit, CAPTCHA_TOKEN_FIELD, CaptchaContext, CaptchaField, ChakraFormField, ConversationalMode, FieldCalculated, FieldHidden, Form2 as Form, FormComparison, FormDependsOn, FormDivider, FormField, FormFromTemplate, FormGroupDeclarative, FormGroupList, FormGroupListDeclarative, FormGroupListItem, FormInfoBlock, FormReadOnlyView, FormSkeleton, FormWatch, HistoryControls, RelationFieldProvider, TanStackFormField, booleanMeta, commonMeta, createForm, createNamedGroupContext, createSafeContext, dateMeta, enumMeta, fieldContext, formContext, numberMeta, relationMeta, templates, textMeta, useAppForm, useCaptchaConfig, useConversationalState, useFieldActions, useFieldContext, useFormApi, useFormAutosave, useFormContext, useFormField, useFormGroupList, useFormGroupListItem, useFormHistory, useFormStepsContext, useRateLimit, useRelationFieldContext, useRelationOptions, useTanStackFormField, useTypedFormContext, useTypedFormSubscribe, verifyCaptcha, withForm, withRelations, withUIMeta, withUIMetaDeep };
3562
6779
  //# sourceMappingURL=index.js.map
3563
6780
  //# sourceMappingURL=index.js.map