@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.
- package/CHANGELOG.md +308 -0
- package/README.md +9 -9
- package/README.ru.md +115 -30
- package/analytics.js +3 -0
- package/analytics.js.map +1 -0
- package/chunk-2PSXYC3I.js +1782 -0
- package/chunk-2PSXYC3I.js.map +1 -0
- package/chunk-5D6S6EGF.js +206 -0
- package/chunk-5D6S6EGF.js.map +1 -0
- package/{chunk-6QOPSQ3Z.js → chunk-6E7VJAJT.js} +3 -3
- package/{chunk-6QOPSQ3Z.js.map → chunk-6E7VJAJT.js.map} +1 -1
- package/chunk-CGXKRCSM.js +117 -0
- package/chunk-CGXKRCSM.js.map +1 -0
- package/{chunk-M2PNAAIR.js → chunk-DQUVUMCX.js} +30 -19
- package/chunk-DQUVUMCX.js.map +1 -0
- package/chunk-K3J4L26K.js +345 -0
- package/chunk-K3J4L26K.js.map +1 -0
- package/{chunk-PJETA6YN.js → chunk-MAYUFA5K.js} +5 -4
- package/chunk-MAYUFA5K.js.map +1 -0
- package/{chunk-4V6WBJ76.js → chunk-MVGXZNHP.js} +2 -2
- package/{chunk-4V6WBJ76.js.map → chunk-MVGXZNHP.js.map} +1 -1
- package/{chunk-XKKJKYWZ.js → chunk-MZDTJSF7.js} +3 -3
- package/{chunk-XKKJKYWZ.js.map → chunk-MZDTJSF7.js.map} +1 -1
- package/{chunk-KUNT5MSU.js → chunk-Q5EOF36Y.js} +3 -3
- package/chunk-Q5EOF36Y.js.map +1 -0
- package/{chunk-7FEQFDJ7.js → chunk-R2RTCKXY.js} +2 -2
- package/{chunk-7FEQFDJ7.js.map → chunk-R2RTCKXY.js.map} +1 -1
- package/{chunk-HWVOFWAT.js → chunk-XFWLD5EO.js} +225 -26
- package/chunk-XFWLD5EO.js.map +1 -0
- package/fields/boolean.js +3 -3
- package/fields/datetime.js +3 -3
- package/fields/number.js +3 -3
- package/fields/selection.js +3 -3
- package/fields/specialized.js +3 -3
- package/fields/text.js +3 -3
- package/hcaptcha-U4XIT3HS.js +64 -0
- package/hcaptcha-U4XIT3HS.js.map +1 -0
- package/i18n.js +1 -1
- package/index.js +3268 -51
- package/index.js.map +1 -1
- package/offline.js +1 -1
- package/package.json +33 -4
- package/recaptcha-PKAUAY2S.js +56 -0
- package/recaptcha-PKAUAY2S.js.map +1 -0
- package/server-errors.js +3 -0
- package/server-errors.js.map +1 -0
- package/turnstile-7FXTBSLW.js +36 -0
- package/turnstile-7FXTBSLW.js.map +1 -0
- package/validators/ru.js +73 -0
- package/validators/ru.js.map +1 -0
- package/chunk-GOELIS6T.js +0 -849
- package/chunk-GOELIS6T.js.map +0 -1
- package/chunk-HWVOFWAT.js.map +0 -1
- package/chunk-KUNT5MSU.js.map +0 -1
- package/chunk-M2PNAAIR.js.map +0 -1
- 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-
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
export {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
export {
|
|
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,
|
|
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,
|
|
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 {
|
|
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
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
() =>
|
|
3215
|
-
[form,
|
|
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
|
-
|
|
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
|