@letar/forms 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +308 -0
  2. package/README.md +9 -9
  3. package/README.ru.md +115 -30
  4. package/analytics.js +3 -0
  5. package/analytics.js.map +1 -0
  6. package/chunk-2PSXYC3I.js +1782 -0
  7. package/chunk-2PSXYC3I.js.map +1 -0
  8. package/chunk-5D6S6EGF.js +206 -0
  9. package/chunk-5D6S6EGF.js.map +1 -0
  10. package/{chunk-6QOPSQ3Z.js → chunk-6E7VJAJT.js} +3 -3
  11. package/{chunk-6QOPSQ3Z.js.map → chunk-6E7VJAJT.js.map} +1 -1
  12. package/chunk-CGXKRCSM.js +117 -0
  13. package/chunk-CGXKRCSM.js.map +1 -0
  14. package/{chunk-M2PNAAIR.js → chunk-DQUVUMCX.js} +30 -19
  15. package/chunk-DQUVUMCX.js.map +1 -0
  16. package/chunk-K3J4L26K.js +345 -0
  17. package/chunk-K3J4L26K.js.map +1 -0
  18. package/{chunk-PJETA6YN.js → chunk-MAYUFA5K.js} +5 -4
  19. package/chunk-MAYUFA5K.js.map +1 -0
  20. package/{chunk-4V6WBJ76.js → chunk-MVGXZNHP.js} +2 -2
  21. package/{chunk-4V6WBJ76.js.map → chunk-MVGXZNHP.js.map} +1 -1
  22. package/{chunk-XKKJKYWZ.js → chunk-MZDTJSF7.js} +3 -3
  23. package/{chunk-XKKJKYWZ.js.map → chunk-MZDTJSF7.js.map} +1 -1
  24. package/{chunk-KUNT5MSU.js → chunk-Q5EOF36Y.js} +3 -3
  25. package/chunk-Q5EOF36Y.js.map +1 -0
  26. package/{chunk-7FEQFDJ7.js → chunk-R2RTCKXY.js} +2 -2
  27. package/{chunk-7FEQFDJ7.js.map → chunk-R2RTCKXY.js.map} +1 -1
  28. package/{chunk-HWVOFWAT.js → chunk-XFWLD5EO.js} +225 -26
  29. package/chunk-XFWLD5EO.js.map +1 -0
  30. package/fields/boolean.js +3 -3
  31. package/fields/datetime.js +3 -3
  32. package/fields/number.js +3 -3
  33. package/fields/selection.js +3 -3
  34. package/fields/specialized.js +3 -3
  35. package/fields/text.js +3 -3
  36. package/hcaptcha-U4XIT3HS.js +64 -0
  37. package/hcaptcha-U4XIT3HS.js.map +1 -0
  38. package/i18n.js +1 -1
  39. package/index.js +3268 -51
  40. package/index.js.map +1 -1
  41. package/offline.js +1 -1
  42. package/package.json +33 -4
  43. package/recaptcha-PKAUAY2S.js +56 -0
  44. package/recaptcha-PKAUAY2S.js.map +1 -0
  45. package/server-errors.js +3 -0
  46. package/server-errors.js.map +1 -0
  47. package/turnstile-7FXTBSLW.js +36 -0
  48. package/turnstile-7FXTBSLW.js.map +1 -0
  49. package/validators/ru.js +73 -0
  50. package/validators/ru.js.map +1 -0
  51. package/chunk-GOELIS6T.js +0 -849
  52. package/chunk-GOELIS6T.js.map +0 -1
  53. package/chunk-HWVOFWAT.js.map +0 -1
  54. package/chunk-KUNT5MSU.js.map +0 -1
  55. package/chunk-M2PNAAIR.js.map +0 -1
  56. package/chunk-PJETA6YN.js.map +0 -1
package/offline.js CHANGED
@@ -1,3 +1,3 @@
1
- export { FormOfflineIndicator, FormSyncStatus, addToQueue, clearQueue, createSyncQueueStore, getOfflineStatus, getQueueFromStorage, processQueueItem, removeFromQueue, subscribeToStatusChanges, useOfflineForm, useOfflineStatus, useSyncQueue } from './chunk-4V6WBJ76.js';
1
+ export { FormOfflineIndicator, FormSyncStatus, addToQueue, clearQueue, createSyncQueueStore, getOfflineStatus, getQueueFromStorage, processQueueItem, removeFromQueue, subscribeToStatusChanges, useOfflineForm, useOfflineStatus, useSyncQueue } from './chunk-MVGXZNHP.js';
2
2
  //# sourceMappingURL=offline.js.map
3
3
  //# sourceMappingURL=offline.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@letar/forms",
3
- "version": "1.1.0",
4
- "description": "Declarative form components for React with 40+ field types, powered by TanStack Form and Chakra UI v3",
3
+ "version": "1.2.0",
4
+ "description": "Declarative form components for React with 50+ field types, built-in analytics, server error mapping, undo/redo — powered by TanStack Form and Chakra UI v3",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -42,6 +42,18 @@
42
42
  "types": "./fields/specialized.d.ts",
43
43
  "import": "./fields/specialized.js"
44
44
  },
45
+ "./server-errors": {
46
+ "types": "./server-errors.d.ts",
47
+ "import": "./server-errors.js"
48
+ },
49
+ "./analytics": {
50
+ "types": "./analytics.d.ts",
51
+ "import": "./analytics.js"
52
+ },
53
+ "./validators/ru": {
54
+ "types": "./validators/ru.d.ts",
55
+ "import": "./validators/ru.js"
56
+ },
45
57
  "./package.json": "./package.json"
46
58
  },
47
59
  "main": "./index.js",
@@ -68,7 +80,12 @@
68
80
  "validation",
69
81
  "multi-step",
70
82
  "offline",
71
- "i18n"
83
+ "i18n",
84
+ "analytics",
85
+ "undo-redo",
86
+ "server-errors",
87
+ "prisma",
88
+ "zenstack"
72
89
  ],
73
90
  "homepage": "https://forms.letar.best",
74
91
  "repository": {
@@ -89,7 +106,10 @@
89
106
  "react-icons": ">=5.0.0",
90
107
  "use-mask-input": ">=3.0.0",
91
108
  "zod": ">=3.24.0",
92
- "@uiw/react-json-view": ">=2.0.0"
109
+ "@uiw/react-json-view": ">=2.0.0",
110
+ "@tanstack/react-table": ">=8.0.0",
111
+ "@tanstack/react-virtual": ">=3.0.0",
112
+ "@marsidev/react-turnstile": ">=1.0.0"
93
113
  },
94
114
  "peerDependenciesMeta": {
95
115
  "@dnd-kit/core": {
@@ -115,6 +135,15 @@
115
135
  },
116
136
  "use-mask-input": {
117
137
  "optional": true
138
+ },
139
+ "@tanstack/react-table": {
140
+ "optional": true
141
+ },
142
+ "@tanstack/react-virtual": {
143
+ "optional": true
144
+ },
145
+ "@marsidev/react-turnstile": {
146
+ "optional": true
118
147
  }
119
148
  },
120
149
  "devDependencies": {
@@ -0,0 +1,56 @@
1
+ import { useRef, useCallback, useEffect } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ function RecaptchaProvider({
5
+ siteKey,
6
+ theme = "light",
7
+ size = "normal",
8
+ language,
9
+ onSuccess,
10
+ onError,
11
+ onExpire
12
+ }) {
13
+ const containerRef = useRef(null);
14
+ const widgetIdRef = useRef(null);
15
+ const handleCallback = useCallback(
16
+ (token) => {
17
+ onSuccess(token);
18
+ },
19
+ [onSuccess]
20
+ );
21
+ useEffect(() => {
22
+ const scriptId = "recaptcha-script";
23
+ if (!document.getElementById(scriptId)) {
24
+ const script = document.createElement("script");
25
+ script.id = scriptId;
26
+ script.src = `https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit${language ? `&hl=${language}` : ""}`;
27
+ script.async = true;
28
+ script.defer = true;
29
+ document.head.appendChild(script);
30
+ }
31
+ const renderWidget = () => {
32
+ if (containerRef.current && window.grecaptcha && widgetIdRef.current === null) {
33
+ widgetIdRef.current = window.grecaptcha.render(containerRef.current, {
34
+ sitekey: siteKey,
35
+ theme: theme === "auto" ? "light" : theme,
36
+ size: size === "invisible" ? "invisible" : size,
37
+ callback: handleCallback,
38
+ "error-callback": () => onError?.(new Error("reCAPTCHA error")),
39
+ "expired-callback": () => onExpire?.()
40
+ });
41
+ }
42
+ };
43
+ window.onRecaptchaLoad = renderWidget;
44
+ if (window.grecaptcha?.render) {
45
+ renderWidget();
46
+ }
47
+ return () => {
48
+ widgetIdRef.current = null;
49
+ };
50
+ }, [siteKey, theme, size, language, handleCallback, onError, onExpire]);
51
+ return /* @__PURE__ */ jsx("div", { ref: containerRef });
52
+ }
53
+
54
+ export { RecaptchaProvider };
55
+ //# sourceMappingURL=recaptcha-PKAUAY2S.js.map
56
+ //# sourceMappingURL=recaptcha-PKAUAY2S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/captcha/providers/recaptcha.tsx"],"names":[],"mappings":";;;AAqBO,SAAS,iBAAA,CAAkB;AAAA,EAChC,OAAA;AAAA,EACA,KAAA,GAAQ,OAAA;AAAA,EACR,IAAA,GAAO,QAAA;AAAA,EACP,QAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAA2B;AAEzB,EAAA,MAAM,YAAA,GAAe,OAAuB,IAAI,CAAA;AAChD,EAAA,MAAM,WAAA,GAAc,OAAsB,IAAI,CAAA;AAE9C,EAAA,MAAM,cAAA,GAAiB,WAAA;AAAA,IACrB,CAAC,KAAA,KAAkB;AACjB,MAAA,SAAA,CAAU,KAAK,CAAA;AAAA,IACjB,CAAA;AAAA,IACA,CAAC,SAAS;AAAA,GACZ;AAEA,EAAA,SAAA,CAAU,MAAM;AAEd,IAAA,MAAM,QAAA,GAAW,kBAAA;AACjB,IAAA,IAAI,CAAC,QAAA,CAAS,cAAA,CAAe,QAAQ,CAAA,EAAG;AACtC,MAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,MAAA,MAAA,CAAO,EAAA,GAAK,QAAA;AACZ,MAAA,MAAA,CAAO,MAAM,CAAA,8EAAA,EACX,QAAA,GAAW,CAAA,IAAA,EAAO,QAAQ,KAAK,EACjC,CAAA,CAAA;AACA,MAAA,MAAA,CAAO,KAAA,GAAQ,IAAA;AACf,MAAA,MAAA,CAAO,KAAA,GAAQ,IAAA;AACf,MAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,IAClC;AAGA,IAAA,MAAM,eAAe,MAAM;AACzB,MAAA,IAAI,aAAa,OAAA,IAAW,MAAA,CAAO,UAAA,IAAc,WAAA,CAAY,YAAY,IAAA,EAAM;AAC7E,QAAA,WAAA,CAAY,OAAA,GAAU,MAAA,CAAO,UAAA,CAAW,MAAA,CAAO,aAAa,OAAA,EAAS;AAAA,UACnE,OAAA,EAAS,OAAA;AAAA,UACT,KAAA,EAAO,KAAA,KAAU,MAAA,GAAS,OAAA,GAAU,KAAA;AAAA,UACpC,IAAA,EAAM,IAAA,KAAS,WAAA,GAAc,WAAA,GAAc,IAAA;AAAA,UAC3C,QAAA,EAAU,cAAA;AAAA,UACV,kBAAkB,MAAM,OAAA,GAAU,IAAI,KAAA,CAAM,iBAAiB,CAAC,CAAA;AAAA,UAC9D,kBAAA,EAAoB,MAAM,QAAA;AAAW,SACtC,CAAA;AAAA,MACH;AAAA,IACF,CAAA;AACC,IAAC,OAAe,eAAA,GAAkB,YAAA;AAGnC,IAAA,IAAI,MAAA,CAAO,YAAY,MAAA,EAAQ;AAC7B,MAAA,YAAA,EAAa;AAAA,IACf;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AAAA,IACxB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,EAAS,KAAA,EAAO,MAAM,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,QAAQ,CAAC,CAAA;AAEtE,EAAA,uBAAO,GAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,YAAA,EAAc,CAAA;AACjC","file":"recaptcha-PKAUAY2S.js","sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useRef } from 'react'\nimport type { CaptchaSize, CaptchaTheme } from '../types'\n\n/** Пропсы для reCAPTCHA провайдера */\ninterface RecaptchaProviderProps {\n siteKey: string\n theme?: CaptchaTheme\n size?: CaptchaSize\n language?: string\n onSuccess: (token: string) => void\n onError?: (error: unknown) => void\n onExpire?: () => void\n}\n\n/**\n * Провайдер Google reCAPTCHA v2.\n * Загружается лениво через dynamic import.\n * Требует установки react-google-recaptcha.\n */\nexport function RecaptchaProvider({\n siteKey,\n theme = 'light',\n size = 'normal',\n language,\n onSuccess,\n onError,\n onExpire,\n}: RecaptchaProviderProps) {\n // reCAPTCHA v2 — fallback на простой script-based подход\n const containerRef = useRef<HTMLDivElement>(null)\n const widgetIdRef = useRef<number | null>(null)\n\n const handleCallback = useCallback(\n (token: string) => {\n onSuccess(token)\n },\n [onSuccess]\n )\n\n useEffect(() => {\n // Загружаем reCAPTCHA API скрипт\n const scriptId = 'recaptcha-script'\n if (!document.getElementById(scriptId)) {\n const script = document.createElement('script')\n script.id = scriptId\n script.src = `https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit${\n language ? `&hl=${language}` : ''\n }`\n script.async = true\n script.defer = true\n document.head.appendChild(script)\n }\n\n // Ждём загрузки API\n const renderWidget = () => {\n if (containerRef.current && window.grecaptcha && widgetIdRef.current === null) {\n widgetIdRef.current = window.grecaptcha.render(containerRef.current, {\n sitekey: siteKey,\n theme: theme === 'auto' ? 'light' : theme,\n size: size === 'invisible' ? 'invisible' : size,\n callback: handleCallback,\n 'error-callback': () => onError?.(new Error('reCAPTCHA error')),\n 'expired-callback': () => onExpire?.(),\n })\n }\n } // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(window as any).onRecaptchaLoad = renderWidget\n\n // Если API уже загружено\n if (window.grecaptcha?.render) {\n renderWidget()\n }\n\n return () => {\n widgetIdRef.current = null\n }\n }, [siteKey, theme, size, language, handleCallback, onError, onExpire])\n\n return <div ref={containerRef} />\n}\n\n// Типизация window.grecaptcha\ndeclare global {\n interface Window {\n grecaptcha?: {\n render: (container: HTMLElement, params: Record<string, unknown>) => number\n reset: (widgetId?: number) => void\n getResponse: (widgetId?: number) => string\n }\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export { applyServerErrors, mapServerErrors, parseActionResultError, parseErrorObject, parsePrismaError, parseZenStackError, parseZodFlatError } from './chunk-5D6S6EGF.js';
2
+ //# sourceMappingURL=server-errors.js.map
3
+ //# sourceMappingURL=server-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"server-errors.js"}
@@ -0,0 +1,36 @@
1
+ import { lazy, Suspense } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ var TurnstileWidget = lazy(
5
+ () => import('@marsidev/react-turnstile').then((m) => ({
6
+ default: m.Turnstile
7
+ }))
8
+ );
9
+ function TurnstileProvider({
10
+ siteKey,
11
+ theme = "auto",
12
+ size = "normal",
13
+ language,
14
+ onSuccess,
15
+ onError,
16
+ onExpire
17
+ }) {
18
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { style: { height: 65, width: 300 } }), children: /* @__PURE__ */ jsx(
19
+ TurnstileWidget,
20
+ {
21
+ siteKey,
22
+ options: {
23
+ theme,
24
+ size,
25
+ language
26
+ },
27
+ onSuccess,
28
+ onError,
29
+ onExpire
30
+ }
31
+ ) });
32
+ }
33
+
34
+ export { TurnstileProvider };
35
+ //# sourceMappingURL=turnstile-7FXTBSLW.js.map
36
+ //# sourceMappingURL=turnstile-7FXTBSLW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/captcha/providers/turnstile.tsx"],"names":[],"mappings":";;;AAiBA,IAAM,eAAA,GAAkB,IAAA;AAAA,EAAK,MAC3B,OAAO,2BAA2B,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO;AAAA,IAC/C,SAAS,CAAA,CAAE;AAAA,GACb,CAAE;AACJ,CAAA;AAMO,SAAS,iBAAA,CAAkB;AAAA,EAChC,OAAA;AAAA,EACA,KAAA,GAAQ,MAAA;AAAA,EACR,IAAA,GAAO,QAAA;AAAA,EACP,QAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAA2B;AACzB,EAAA,uBACE,GAAA,CAAC,QAAA,EAAA,EAAS,QAAA,kBAAU,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,MAAA,EAAQ,EAAA,EAAI,KAAA,EAAO,GAAA,EAAI,EAAG,CAAA,EAC1D,QAAA,kBAAA,GAAA;AAAA,IAAC,eAAA;AAAA,IAAA;AAAA,MACC,OAAA;AAAA,MACA,OAAA,EAAS;AAAA,QACP,KAAA;AAAA,QACA,IAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,SAAA;AAAA,MACA,OAAA;AAAA,MACA;AAAA;AAAA,GACF,EACF,CAAA;AAEJ","file":"turnstile-7FXTBSLW.js","sourcesContent":["'use client'\n\nimport { lazy, Suspense } from 'react'\nimport type { CaptchaSize, CaptchaTheme } from '../types'\n\n/** Пропсы для Turnstile провайдера */\ninterface TurnstileProviderProps {\n siteKey: string\n theme?: CaptchaTheme\n size?: CaptchaSize\n language?: string\n onSuccess: (token: string) => void\n onError?: (error: unknown) => void\n onExpire?: () => void\n}\n\n// Lazy import @marsidev/react-turnstile — загружается только при использовании\nconst TurnstileWidget = lazy(() =>\n import('@marsidev/react-turnstile').then((m) => ({\n default: m.Turnstile,\n }))\n)\n\n/**\n * Провайдер Cloudflare Turnstile.\n * Загружает @marsidev/react-turnstile лениво при первом рендере.\n */\nexport function TurnstileProvider({\n siteKey,\n theme = 'auto',\n size = 'normal',\n language,\n onSuccess,\n onError,\n onExpire,\n}: TurnstileProviderProps) {\n return (\n <Suspense fallback={<div style={{ height: 65, width: 300 }} />}>\n <TurnstileWidget\n siteKey={siteKey}\n options={{\n theme,\n size,\n language,\n }}\n onSuccess={onSuccess}\n onError={onError}\n onExpire={onExpire}\n />\n </Suspense>\n )\n}\n"]}
@@ -0,0 +1,73 @@
1
+ import { innSchema, innIndividualSchema, innLegalSchema, isDigitsOfLength, snilsSchema, bikSchema, ogrnipSchema, ogrnSchema, kppSchema } from '../chunk-CGXKRCSM.js';
2
+ export { validateBik, validateInn10, validateInn12, validateKpp, validateOgrn, validateOgrnip, validateSnils } from '../chunk-CGXKRCSM.js';
3
+ import { z } from 'zod/v4';
4
+
5
+ var ACCOUNT_WEIGHTS = [7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1];
6
+ function validateBankAccountWithBik(account, bik, isCorrAccount = false) {
7
+ const accountDigits = account.replace(/\D/g, "");
8
+ const bikDigits = bik.replace(/\D/g, "");
9
+ if (!isDigitsOfLength(accountDigits, 20)) return false;
10
+ if (!isDigitsOfLength(bikDigits, 9)) return false;
11
+ let prefix;
12
+ if (isCorrAccount) {
13
+ prefix = `0${bikDigits.slice(0, 2)}`;
14
+ } else {
15
+ prefix = bikDigits.slice(6, 9);
16
+ }
17
+ const combined = `${prefix}${accountDigits}`;
18
+ const digits = combined.split("").map(Number);
19
+ const sum = ACCOUNT_WEIGHTS.reduce((acc, w, i) => acc + w * digits[i], 0);
20
+ return sum % 10 === 0;
21
+ }
22
+ function bankAccountSchema() {
23
+ return z.string().transform((v) => v.replace(/\D/g, "")).refine((v) => isDigitsOfLength(v, 20), { message: "\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" });
24
+ }
25
+ function bankAccountWithBikSchema(bikValue) {
26
+ return z.string().transform((v) => v.replace(/\D/g, "")).refine((v) => isDigitsOfLength(v, 20), { message: "\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" }).refine((v) => validateBankAccountWithBik(v, bikValue, false), {
27
+ message: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u044B\u0439 \u043A\u043B\u044E\u0447 \u0440\u0430\u0441\u0447\u0451\u0442\u043D\u043E\u0433\u043E \u0441\u0447\u0451\u0442\u0430"
28
+ });
29
+ }
30
+ function corrAccountSchema() {
31
+ return z.string().transform((v) => v.replace(/\D/g, "")).refine((v) => isDigitsOfLength(v, 20), { message: "\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" }).refine((v) => v.startsWith("301"), { message: '\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"' });
32
+ }
33
+ function corrAccountWithBikSchema(bikValue) {
34
+ return z.string().transform((v) => v.replace(/\D/g, "")).refine((v) => isDigitsOfLength(v, 20), { message: "\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" }).refine((v) => v.startsWith("301"), { message: '\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"' }).refine((v) => validateBankAccountWithBik(v, bikValue, true), { message: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u044B\u0439 \u043A\u043B\u044E\u0447 \u043A\u043E\u0440\u0440. \u0441\u0447\u0451\u0442\u0430" });
35
+ }
36
+ function validatePassport(value) {
37
+ const digits = value.replace(/\D/g, "");
38
+ return isDigitsOfLength(digits, 10);
39
+ }
40
+ function passportSchema() {
41
+ return z.string().transform((v) => v.replace(/\D/g, "")).refine((v) => isDigitsOfLength(v, 10), { message: "\u041F\u0430\u0441\u043F\u043E\u0440\u0442 \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C 10 \u0446\u0438\u0444\u0440 (\u0441\u0435\u0440\u0438\u044F + \u043D\u043E\u043C\u0435\u0440)" });
42
+ }
43
+
44
+ // src/lib/validators/ru/index.ts
45
+ var zRu = {
46
+ /** ИНН (10 или 12 цифр) */
47
+ inn: Object.assign(() => innSchema(), {
48
+ /** ИНН юрлица (10 цифр) */
49
+ legal: () => innLegalSchema(),
50
+ /** ИНН физлица (12 цифр) */
51
+ individual: () => innIndividualSchema()
52
+ }),
53
+ /** КПП (9 символов) */
54
+ kpp: () => kppSchema(),
55
+ /** ОГРН (13 цифр) */
56
+ ogrn: () => ogrnSchema(),
57
+ /** ОГРНИП (15 цифр) */
58
+ ogrnip: () => ogrnipSchema(),
59
+ /** БИК (9 цифр) */
60
+ bik: () => bikSchema(),
61
+ /** Расчётный счёт (20 цифр), опционально с проверкой по БИК */
62
+ bankAccount: (bikValue) => bikValue ? bankAccountWithBikSchema(bikValue) : bankAccountSchema(),
63
+ /** Корр. счёт (20 цифр, начинается с "301"), опционально с проверкой по БИК */
64
+ corrAccount: (bikValue) => bikValue ? corrAccountWithBikSchema(bikValue) : corrAccountSchema(),
65
+ /** СНИЛС (11 цифр) */
66
+ snils: () => snilsSchema(),
67
+ /** Паспорт (серия + номер, 10 цифр) */
68
+ passport: () => passportSchema()
69
+ };
70
+
71
+ export { validateBankAccountWithBik, validatePassport, zRu };
72
+ //# sourceMappingURL=ru.js.map
73
+ //# sourceMappingURL=ru.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/validators/ru/bank-account.ts","../../src/lib/validators/ru/passport.ts","../../src/lib/validators/ru/index.ts"],"names":["z"],"mappings":"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","file":"ru.js","sourcesContent":["/**\n * Валидация расчётного и корреспондентского счёта.\n *\n * Расчётный счёт — 20 цифр, контрольный ключ (3-я цифра) с учётом БИК.\n * Корр. счёт — 20 цифр, начинается с \"301\".\n */\nimport { z } from 'zod/v4'\nimport { isDigitsOfLength } from './checksum'\n\n// Веса для контрольного ключа банковского счёта\nconst ACCOUNT_WEIGHTS = [7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1]\n\n/**\n * Проверить контрольный ключ банковского счёта с учётом БИК.\n *\n * Алгоритм:\n * 1. Для расч. счёта: последние 3 цифры БИК + 20 цифр счёта = 23 цифры\n * 2. Для корр. счёта: \"0\" + первые 2 цифры БИК + 20 цифр счёта = 23 цифры\n */\nexport function validateBankAccountWithBik(account: string, bik: string, isCorrAccount = false): boolean {\n const accountDigits = account.replace(/\\D/g, '')\n const bikDigits = bik.replace(/\\D/g, '')\n\n if (!isDigitsOfLength(accountDigits, 20)) return false\n if (!isDigitsOfLength(bikDigits, 9)) return false\n\n // Формируем 23-значную строку для проверки\n let prefix: string\n if (isCorrAccount) {\n prefix = `0${bikDigits.slice(0, 2)}` // \"0\" + первые 2 цифры БИК\n } else {\n prefix = bikDigits.slice(6, 9) // Последние 3 цифры БИК\n }\n\n const combined = `${prefix}${accountDigits}`\n const digits = combined.split('').map(Number)\n\n // Контрольная сумма: сумма (digit[i] * weight[i]) mod 10 должна быть 0\n const sum = ACCOUNT_WEIGHTS.reduce((acc, w, i) => acc + w * digits[i], 0)\n return sum % 10 === 0\n}\n\n/**\n * Zod-схема расчётного счёта (20 цифр).\n * Без проверки контрольного ключа (нет БИК в контексте).\n */\nexport function bankAccountSchema() {\n return z\n .string()\n .transform((v) => v.replace(/\\D/g, ''))\n .refine((v) => isDigitsOfLength(v, 20), { message: 'Расчётный счёт должен содержать 20 цифр' })\n}\n\n/**\n * Zod-схема расчётного счёта с проверкой по БИК.\n */\nexport function bankAccountWithBikSchema(bikValue: string) {\n return z\n .string()\n .transform((v) => v.replace(/\\D/g, ''))\n .refine((v) => isDigitsOfLength(v, 20), { message: 'Расчётный счёт должен содержать 20 цифр' })\n .refine((v) => validateBankAccountWithBik(v, bikValue, false), {\n message: 'Неверный контрольный ключ расчётного счёта',\n })\n}\n\n/**\n * Zod-схема корреспондентского счёта (20 цифр, начинается с \"301\").\n */\nexport function corrAccountSchema() {\n return z\n .string()\n .transform((v) => v.replace(/\\D/g, ''))\n .refine((v) => isDigitsOfLength(v, 20), { message: 'Корр. счёт должен содержать 20 цифр' })\n .refine((v) => v.startsWith('301'), { message: 'Корр. счёт должен начинаться с \"301\"' })\n}\n\n/**\n * Zod-схема корр. счёта с проверкой по БИК.\n */\nexport function corrAccountWithBikSchema(bikValue: string) {\n return z\n .string()\n .transform((v) => v.replace(/\\D/g, ''))\n .refine((v) => isDigitsOfLength(v, 20), { message: 'Корр. счёт должен содержать 20 цифр' })\n .refine((v) => v.startsWith('301'), { message: 'Корр. счёт должен начинаться с \"301\"' })\n .refine((v) => validateBankAccountWithBik(v, bikValue, true), { message: 'Неверный контрольный ключ корр. счёта' })\n}\n","/**\n * Валидация российского паспорта.\n *\n * Серия: 4 цифры (2 цифры региона + 2 цифры года).\n * Номер: 6 цифр.\n * Итого: 10 цифр.\n */\nimport { z } from 'zod/v4'\nimport { isDigitsOfLength } from './checksum'\n\n/**\n * Проверить формат паспорта.\n */\nexport function validatePassport(value: string): boolean {\n const digits = value.replace(/\\D/g, '')\n return isDigitsOfLength(digits, 10)\n}\n\n/**\n * Zod-схема паспорта (серия + номер, 10 цифр).\n */\nexport function passportSchema() {\n return z\n .string()\n .transform((v) => v.replace(/\\D/g, ''))\n .refine((v) => isDigitsOfLength(v, 10), { message: 'Паспорт должен содержать 10 цифр (серия + номер)' })\n}\n","/**\n * zRu — Zod-валидаторы для российских документов.\n *\n * Headless: работают без UI, можно использовать на сервере.\n *\n * @example\n * ```typescript\n * import { zRu } from '@letar/forms/validators/ru'\n *\n * const CompanySchema = z.object({\n * inn: zRu.inn(), // 10 или 12 цифр + контрольная сумма\n * kpp: zRu.kpp(), // 9 символов\n * ogrn: zRu.ogrn(), // 13 цифр + контрольная сумма\n * bik: zRu.bik(), // 9 цифр, начинается с \"04\"\n * account: zRu.bankAccount(), // 20 цифр\n * snils: zRu.snils(), // 11 цифр + контрольная сумма\n * })\n *\n * // Варианты ИНН\n * zRu.inn.legal() // только юрлицо (10 цифр)\n * zRu.inn.individual() // только физлицо (12 цифр)\n * ```\n */\n\nimport {\n bankAccountSchema,\n bankAccountWithBikSchema,\n corrAccountSchema,\n corrAccountWithBikSchema,\n} from './bank-account'\nimport { bikSchema } from './bik'\nimport { innIndividualSchema, innLegalSchema, innSchema } from './inn'\nimport { kppSchema } from './kpp'\nimport { ogrnipSchema, ogrnSchema } from './ogrn'\nimport { passportSchema } from './passport'\nimport { snilsSchema } from './snils'\n\nexport const zRu = {\n /** ИНН (10 или 12 цифр) */\n inn: Object.assign(() => innSchema(), {\n /** ИНН юрлица (10 цифр) */\n legal: () => innLegalSchema(),\n /** ИНН физлица (12 цифр) */\n individual: () => innIndividualSchema(),\n }),\n /** КПП (9 символов) */\n kpp: () => kppSchema(),\n /** ОГРН (13 цифр) */\n ogrn: () => ogrnSchema(),\n /** ОГРНИП (15 цифр) */\n ogrnip: () => ogrnipSchema(),\n /** БИК (9 цифр) */\n bik: () => bikSchema(),\n /** Расчётный счёт (20 цифр), опционально с проверкой по БИК */\n bankAccount: (bikValue?: string) => (bikValue ? bankAccountWithBikSchema(bikValue) : bankAccountSchema()),\n /** Корр. счёт (20 цифр, начинается с \"301\"), опционально с проверкой по БИК */\n corrAccount: (bikValue?: string) => (bikValue ? corrAccountWithBikSchema(bikValue) : corrAccountSchema()),\n /** СНИЛС (11 цифр) */\n snils: () => snilsSchema(),\n /** Паспорт (серия + номер, 10 цифр) */\n passport: () => passportSchema(),\n}\n\n// Re-export для прямого использования\nexport { validateBankAccountWithBik } from './bank-account'\nexport { validateBik } from './bik'\nexport { validateInn10, validateInn12 } from './inn'\nexport { validateKpp } from './kpp'\nexport { validateOgrn, validateOgrnip } from './ogrn'\nexport { validatePassport } from './passport'\nexport { validateSnils } from './snils'\n"]}