@letar/forms 1.0.3 → 1.1.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.
@@ -0,0 +1,846 @@
1
+ import { useFormI18n, getLocalizedValue, useLocalizedOptions } from './chunk-7FEQFDJ7.js';
2
+ import { createContext, memo, useContext, useState, useEffect, useMemo, Component } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import { Field, HStack, Tooltip, Circle, Portal, VStack, Text, Box, createListCollection } from '@chakra-ui/react';
5
+ import { LuCircleHelp } from 'react-icons/lu';
6
+
7
+ var FormGroupContext = createContext(null);
8
+ function FormGroup({ name, children }) {
9
+ const parentContext = useContext(FormGroupContext);
10
+ const contextValue = {
11
+ originalName: name,
12
+ name: parentContext ? `${parentContext.name}.${name}` : name
13
+ };
14
+ return /* @__PURE__ */ jsx(FormGroupContext.Provider, { value: contextValue, children });
15
+ }
16
+ function useFormGroup() {
17
+ return useContext(FormGroupContext);
18
+ }
19
+ var DeclarativeFormContext = createContext(null);
20
+ function useDeclarativeForm() {
21
+ const context = useContext(DeclarativeFormContext);
22
+ if (!context) {
23
+ throw new Error("useDeclarativeForm must be used inside a Form component");
24
+ }
25
+ return context;
26
+ }
27
+ function useDeclarativeFormOptional() {
28
+ return useContext(DeclarativeFormContext);
29
+ }
30
+ function FieldTooltip({ title, description, example, impact }) {
31
+ return /* @__PURE__ */ jsxs(Tooltip.Root, { openDelay: 200, positioning: { placement: "top" }, children: [
32
+ /* @__PURE__ */ jsx(Tooltip.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
33
+ Circle,
34
+ {
35
+ size: "5",
36
+ cursor: "help",
37
+ color: "fg.muted",
38
+ _hover: { color: "colorPalette.fg" },
39
+ transition: "color 0.2s",
40
+ display: "inline-flex",
41
+ children: /* @__PURE__ */ jsx(LuCircleHelp, { size: 16 })
42
+ }
43
+ ) }),
44
+ /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Tooltip.Positioner, { children: /* @__PURE__ */ jsxs(Tooltip.Content, { children: [
45
+ /* @__PURE__ */ jsx(Tooltip.Arrow, { children: /* @__PURE__ */ jsx(Tooltip.ArrowTip, {}) }),
46
+ /* @__PURE__ */ jsxs(VStack, { align: "start", gap: 2, maxW: "280px", p: 1, children: [
47
+ title && /* @__PURE__ */ jsx(Text, { fontWeight: "semibold", fontSize: "sm", children: title }),
48
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", children: description }),
49
+ example && /* @__PURE__ */ jsxs(Box, { bg: "bg.emphasized", px: 2, py: 1, borderRadius: "md", w: "full", children: [
50
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "fg.muted", children: "Example:" }),
51
+ /* @__PURE__ */ jsxs(Text, { fontSize: "sm", fontStyle: "italic", children: [
52
+ '"',
53
+ example,
54
+ '"'
55
+ ] })
56
+ ] }),
57
+ impact && /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "green.fg", children: impact })
58
+ ] })
59
+ ] }) }) })
60
+ ] });
61
+ }
62
+ function FieldLabel({ label, tooltip, required }) {
63
+ if (!label) {
64
+ return null;
65
+ }
66
+ return /* @__PURE__ */ jsxs(Field.Label, { children: [
67
+ tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
68
+ /* @__PURE__ */ jsx("span", { children: label }),
69
+ /* @__PURE__ */ jsx(FieldTooltip, { ...tooltip })
70
+ ] }) : label,
71
+ required && /* @__PURE__ */ jsx(Field.RequiredIndicator, {})
72
+ ] });
73
+ }
74
+
75
+ // src/lib/declarative/zod-utils.ts
76
+ function unwrapSchema(schema) {
77
+ if (!schema?._zod?.def) {
78
+ return schema;
79
+ }
80
+ const type = schema._zod.def.type;
81
+ if (type === "optional" || type === "nullable" || type === "default") {
82
+ const inner = schema._zod.def.inner ?? schema._zod.def.innerType;
83
+ if (inner) {
84
+ return unwrapSchema(inner);
85
+ }
86
+ }
87
+ return schema;
88
+ }
89
+ function unwrapSchemaWithRequired(schema) {
90
+ if (!schema?._zod?.def) {
91
+ return { schema, required: true };
92
+ }
93
+ const type = schema._zod.def.type;
94
+ if (type === "optional" || type === "nullable") {
95
+ const inner = schema._zod.def.inner ?? schema._zod.def.innerType;
96
+ if (inner) {
97
+ const result = unwrapSchemaWithRequired(inner);
98
+ return { schema: result.schema, required: false };
99
+ }
100
+ }
101
+ if (type === "default") {
102
+ const inner = schema._zod.def.inner ?? schema._zod.def.innerType;
103
+ if (inner) {
104
+ const result = unwrapSchemaWithRequired(inner);
105
+ return { schema: result.schema, required: false };
106
+ }
107
+ }
108
+ return { schema, required: true };
109
+ }
110
+
111
+ // src/lib/declarative/schema-constraints.ts
112
+ function getZodConstraints(schema, path) {
113
+ if (!schema) {
114
+ return { schemaType: "unknown" };
115
+ }
116
+ const fieldSchema = getSchemaAtPath(schema, path);
117
+ if (!fieldSchema) {
118
+ return { schemaType: "unknown" };
119
+ }
120
+ const def = fieldSchema._zod?.def;
121
+ if (!def) {
122
+ return { schemaType: "unknown" };
123
+ }
124
+ const type = def.type;
125
+ const checks = def.checks || [];
126
+ switch (type) {
127
+ case "string":
128
+ return {
129
+ schemaType: "string",
130
+ string: extractStringConstraints(checks)
131
+ };
132
+ case "number":
133
+ return {
134
+ schemaType: "number",
135
+ number: extractNumberConstraints(checks)
136
+ };
137
+ case "date":
138
+ return {
139
+ schemaType: "date",
140
+ date: extractDateConstraints(checks)
141
+ };
142
+ case "array":
143
+ return {
144
+ schemaType: "array",
145
+ array: extractArrayConstraints(checks)
146
+ };
147
+ case "boolean":
148
+ return { schemaType: "boolean" };
149
+ case "enum":
150
+ return { schemaType: "enum" };
151
+ default:
152
+ return { schemaType: "unknown" };
153
+ }
154
+ }
155
+ function extractConstraints(checks, handlers) {
156
+ const constraints = {};
157
+ for (const check of checks) {
158
+ const checkDef = check._zod?.def;
159
+ if (!checkDef) {
160
+ continue;
161
+ }
162
+ const handler = handlers[checkDef.check];
163
+ if (handler) {
164
+ handler(constraints, checkDef);
165
+ }
166
+ }
167
+ return constraints;
168
+ }
169
+ var stringConstraintHandlers = {
170
+ min_length: (c, def) => {
171
+ c.minLength = def.minimum;
172
+ },
173
+ max_length: (c, def) => {
174
+ c.maxLength = def.maximum;
175
+ },
176
+ length_equals: (c, def) => {
177
+ c.minLength = def.length;
178
+ c.maxLength = def.length;
179
+ },
180
+ string_format: (c, def) => {
181
+ if (def.format === "email") {
182
+ c.inputType = "email";
183
+ } else if (def.format === "url") {
184
+ c.inputType = "url";
185
+ } else if (def.format === "regex" && def.pattern?.source) {
186
+ c.pattern = def.pattern.source;
187
+ }
188
+ }
189
+ };
190
+ function extractStringConstraints(checks) {
191
+ return extractConstraints(checks, stringConstraintHandlers);
192
+ }
193
+ var numberConstraintHandlers = {
194
+ greater_than: (c, def) => {
195
+ c.min = def.value;
196
+ },
197
+ less_than: (c, def) => {
198
+ c.max = def.value;
199
+ },
200
+ number_format: (c, def) => {
201
+ if (def.format === "safeint") {
202
+ c.isInteger = true;
203
+ c.step = 1;
204
+ }
205
+ },
206
+ multiple_of: (c, def) => {
207
+ c.step = def.value;
208
+ }
209
+ };
210
+ function extractNumberConstraints(checks) {
211
+ return extractConstraints(checks, numberConstraintHandlers);
212
+ }
213
+ var dateConstraintHandlers = {
214
+ greater_than: (c, def) => {
215
+ if (def.value) {
216
+ c.min = formatDateToISO(def.value);
217
+ }
218
+ },
219
+ less_than: (c, def) => {
220
+ if (def.value) {
221
+ c.max = formatDateToISO(def.value);
222
+ }
223
+ }
224
+ };
225
+ function extractDateConstraints(checks) {
226
+ return extractConstraints(checks, dateConstraintHandlers);
227
+ }
228
+ var arrayConstraintHandlers = {
229
+ min_length: (c, def) => {
230
+ c.minItems = def.minimum;
231
+ },
232
+ max_length: (c, def) => {
233
+ c.maxItems = def.maximum;
234
+ },
235
+ length: (c, def) => {
236
+ c.minItems = def.length;
237
+ c.maxItems = def.length;
238
+ }
239
+ };
240
+ function extractArrayConstraints(checks) {
241
+ return extractConstraints(checks, arrayConstraintHandlers);
242
+ }
243
+ function getSchemaAtPath(schema, path) {
244
+ if (!schema || !path) {
245
+ return schema;
246
+ }
247
+ const parts = path.split(".");
248
+ let current = schema;
249
+ for (const part of parts) {
250
+ current = unwrapSchema(current);
251
+ if (!current) {
252
+ return void 0;
253
+ }
254
+ if (/^\d+$/.test(part)) {
255
+ if (current._zod?.def?.type === "array") {
256
+ current = current._zod.def.element;
257
+ }
258
+ continue;
259
+ }
260
+ if (current._zod?.def?.type === "object") {
261
+ const shape = current._zod.def.shape;
262
+ if (shape && part in shape) {
263
+ current = shape[part];
264
+ } else {
265
+ return void 0;
266
+ }
267
+ } else {
268
+ return void 0;
269
+ }
270
+ }
271
+ return unwrapSchema(current);
272
+ }
273
+ function formatDateToISO(value) {
274
+ let date;
275
+ if (value instanceof Date) {
276
+ date = value;
277
+ } else if (typeof value === "string") {
278
+ date = new Date(value);
279
+ } else if (typeof value === "number") {
280
+ date = new Date(value);
281
+ } else {
282
+ return "";
283
+ }
284
+ if (isNaN(date.getTime())) {
285
+ return "";
286
+ }
287
+ const year = date.getFullYear();
288
+ const month = String(date.getMonth() + 1).padStart(2, "0");
289
+ const day = String(date.getDate()).padStart(2, "0");
290
+ return `${year}-${month}-${day}`;
291
+ }
292
+
293
+ // src/lib/declarative/schema-meta.ts
294
+ function getFieldMeta(schema, path) {
295
+ if (!schema) {
296
+ return { required: false };
297
+ }
298
+ const result = getSchemaAtPath2(schema, path);
299
+ if (!result.schema) {
300
+ return { required: false };
301
+ }
302
+ const fieldSchema = result.schema;
303
+ const meta = typeof fieldSchema?.meta === "function" ? fieldSchema.meta() : void 0;
304
+ return {
305
+ ui: meta?.ui,
306
+ required: result.required
307
+ };
308
+ }
309
+ function unwrapToBaseSchema(schema) {
310
+ if (!schema?._zod?.def) {
311
+ return schema;
312
+ }
313
+ const type = schema._zod.def.type;
314
+ if (type === "effects" || type === "transform" || type === "preprocess") {
315
+ const inner = schema._zod.def.inner ?? schema._zod.def.schema;
316
+ if (inner) {
317
+ return unwrapToBaseSchema(inner);
318
+ }
319
+ }
320
+ if (type === "pipeline") {
321
+ const inner = schema._zod.def.in;
322
+ if (inner) {
323
+ return unwrapToBaseSchema(inner);
324
+ }
325
+ }
326
+ return schema;
327
+ }
328
+ function getSchemaAtPath2(schema, path) {
329
+ if (!schema || !path) {
330
+ return { schema, required: true };
331
+ }
332
+ const parts = path.split(".");
333
+ let current = schema;
334
+ let isRequired = true;
335
+ for (const part of parts) {
336
+ current = unwrapToBaseSchema(current);
337
+ const unwrapped = unwrapSchemaWithRequired(current);
338
+ current = unwrapped.schema;
339
+ if (!unwrapped.required) {
340
+ isRequired = false;
341
+ }
342
+ if (!current) {
343
+ return { schema: void 0, required: false };
344
+ }
345
+ current = unwrapToBaseSchema(current);
346
+ if (/^\d+$/.test(part)) {
347
+ if (current._zod?.def?.type === "array") {
348
+ current = current._zod.def.element;
349
+ }
350
+ continue;
351
+ }
352
+ if (current._zod?.def?.type === "object") {
353
+ const shape = current._zod.def.shape;
354
+ if (shape && part in shape) {
355
+ current = shape[part];
356
+ } else {
357
+ return { schema: void 0, required: false };
358
+ }
359
+ } else {
360
+ return { schema: void 0, required: false };
361
+ }
362
+ }
363
+ current = unwrapToBaseSchema(current);
364
+ const finalUnwrap = unwrapSchemaWithRequired(current);
365
+ return {
366
+ // Возвращаем схему ДО unwrap — на ней can быть мета (.default().meta())
367
+ schema: current,
368
+ required: isRequired && finalUnwrap.required
369
+ };
370
+ }
371
+
372
+ // src/lib/declarative/form-fields/base/base-field.tsx
373
+ function useDeclarativeField(name) {
374
+ const { form, schema, primitiveArrayIndex, disabled, readOnly } = useDeclarativeForm();
375
+ const parentGroup = useFormGroup();
376
+ let fullPath;
377
+ if (name) {
378
+ fullPath = parentGroup ? `${parentGroup.name}.${name}` : name;
379
+ } else if (parentGroup) {
380
+ fullPath = parentGroup.name;
381
+ } else {
382
+ throw new Error("Field must have a name prop or be inside Form.Group.List for primitive arrays");
383
+ }
384
+ const schemaInfo = getFieldMeta(schema, fullPath);
385
+ const constraints = getZodConstraints(schema, fullPath);
386
+ return {
387
+ form,
388
+ fullPath,
389
+ name: name ?? String(primitiveArrayIndex),
390
+ meta: schemaInfo.ui,
391
+ required: schemaInfo.required,
392
+ formDisabled: disabled ?? false,
393
+ formReadOnly: readOnly ?? false,
394
+ constraints
395
+ };
396
+ }
397
+ function useDebounce(value, delay = 300) {
398
+ const [debouncedValue, setDebouncedValue] = useState(value);
399
+ useEffect(() => {
400
+ const timer = setTimeout(() => setDebouncedValue(value), delay);
401
+ return () => clearTimeout(timer);
402
+ }, [value, delay]);
403
+ return debouncedValue;
404
+ }
405
+ function useAsyncSearch(options = {}) {
406
+ const { useQuery, debounce = 300, minChars = 1, initialValue = "" } = options;
407
+ const [inputValue, setInputValue] = useState(initialValue);
408
+ const debouncedSearch = useDebounce(inputValue, debounce);
409
+ const shouldQuery = debouncedSearch.length >= minChars;
410
+ const queryResult = useQuery?.(shouldQuery ? debouncedSearch : "");
411
+ const { data, isLoading = false, error } = queryResult ?? {};
412
+ return {
413
+ inputValue,
414
+ setInputValue,
415
+ debouncedSearch,
416
+ shouldQuery,
417
+ isLoading,
418
+ data,
419
+ error
420
+ };
421
+ }
422
+ var FieldErrorBoundary = class extends Component {
423
+ constructor(props) {
424
+ super(props);
425
+ this.state = { hasError: false, error: null };
426
+ }
427
+ static getDerivedStateFromError(error) {
428
+ return { hasError: true, error };
429
+ }
430
+ componentDidCatch(error, errorInfo) {
431
+ console.error(`[Form] \u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043E\u043B\u0435 "${this.props.fieldName}":`, error, errorInfo);
432
+ }
433
+ render() {
434
+ if (this.state.hasError) {
435
+ return /* @__PURE__ */ jsx(Box, { p: 3, borderWidth: "1px", borderColor: "red.500", borderRadius: "md", bg: "red.50", _dark: { bg: "red.950" }, children: /* @__PURE__ */ jsxs(Text, { color: "red.600", _dark: { color: "red.300" }, fontSize: "sm", children: [
436
+ '\u041E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u043E\u043B\u0435 "',
437
+ this.props.fieldName,
438
+ '": ',
439
+ this.state.error?.message
440
+ ] }) });
441
+ }
442
+ return this.props.children;
443
+ }
444
+ };
445
+
446
+ // src/lib/declarative/form-fields/base/field-utils.ts
447
+ function formatFieldErrors(errors) {
448
+ return errors.map((e) => extractErrorMessage(e)).filter(Boolean).join(", ");
449
+ }
450
+ function extractErrorMessage(e) {
451
+ if (typeof e === "string") {
452
+ return e;
453
+ }
454
+ if (e === null || e === void 0) {
455
+ return "";
456
+ }
457
+ if (typeof e === "object") {
458
+ if (Array.isArray(e)) {
459
+ return e.map((item) => {
460
+ if (typeof item === "object" && item && "message" in item) {
461
+ return item.message;
462
+ }
463
+ if (typeof item === "string") {
464
+ return item;
465
+ }
466
+ return "";
467
+ }).filter(Boolean).join(", ");
468
+ }
469
+ if ("message" in e && typeof e.message === "string") {
470
+ return e.message;
471
+ }
472
+ if ("issues" in e && Array.isArray(e.issues)) {
473
+ return e.issues.map((issue) => {
474
+ if (typeof issue === "object" && issue && "message" in issue) {
475
+ return issue.message;
476
+ }
477
+ return "";
478
+ }).filter(Boolean).join(", ");
479
+ }
480
+ if ("fieldErrors" in e && typeof e.fieldErrors === "object" && e.fieldErrors) {
481
+ const fieldErrors = e.fieldErrors;
482
+ return Object.values(fieldErrors).flat().filter(Boolean).join(", ");
483
+ }
484
+ if ("_errors" in e && Array.isArray(e._errors)) {
485
+ return e._errors.filter(Boolean).join(", ");
486
+ }
487
+ try {
488
+ const json = JSON.stringify(e);
489
+ if (json === "{}" || json.length > 200) {
490
+ return "";
491
+ }
492
+ if (process.env.NODE_ENV === "development") {
493
+ console.warn("[form-components] Unknown error format:", e);
494
+ }
495
+ return json;
496
+ } catch {
497
+ return "";
498
+ }
499
+ }
500
+ return String(e);
501
+ }
502
+ function hasFieldErrors(errors) {
503
+ return Boolean(errors && errors.length > 0);
504
+ }
505
+ function getFieldErrors(field) {
506
+ const errors = field.state.meta.errors ?? [];
507
+ const hasError = hasFieldErrors(errors);
508
+ const errorMessage = hasError ? formatFieldErrors(errors) : "";
509
+ return { errors, hasError, errorMessage };
510
+ }
511
+
512
+ // src/lib/declarative/constraint-hints.ts
513
+ var EN_TRANSLATIONS = {
514
+ string_exact: "Exactly {n} {chars}",
515
+ string_range: "From {min} to {max} characters",
516
+ string_max: "Maximum {n} {chars}",
517
+ string_min: "Minimum {n} {chars}",
518
+ number_range: "From {min} to {max}{suffix}",
519
+ number_max: "Maximum {max}{suffix}",
520
+ number_min: "Minimum {min}{suffix}",
521
+ number_integer: "Integer",
522
+ number_integer_suffix: " (integer)",
523
+ date_range: "From {min} to {max}",
524
+ date_after: "Not before {min}",
525
+ date_before: "Not after {max}",
526
+ array_exact: "Exactly {n} {items}",
527
+ array_range: "From {min} to {max} items",
528
+ array_max: "Maximum {n} {items}",
529
+ array_min: "Minimum {n} {items}"
530
+ };
531
+ var RU_TRANSLATIONS = {
532
+ string_exact: "\u0420\u043E\u0432\u043D\u043E {n} {chars}",
533
+ string_range: "\u041E\u0442 {min} \u0434\u043E {max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
534
+ string_max: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {n} {chars}",
535
+ string_min: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {n} {chars}",
536
+ number_range: "\u041E\u0442 {min} \u0434\u043E {max}{suffix}",
537
+ number_max: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max}{suffix}",
538
+ number_min: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min}{suffix}",
539
+ number_integer: "\u0426\u0435\u043B\u043E\u0435 \u0447\u0438\u0441\u043B\u043E",
540
+ number_integer_suffix: " (\u0446\u0435\u043B\u043E\u0435)",
541
+ date_range: "\u0421 {min} \u043F\u043E {max}",
542
+ date_after: "\u041D\u0435 \u0440\u0430\u043D\u0435\u0435 {min}",
543
+ date_before: "\u041D\u0435 \u043F\u043E\u0437\u0434\u043D\u0435\u0435 {max}",
544
+ array_exact: "\u0420\u043E\u0432\u043D\u043E {n} {items}",
545
+ array_range: "\u041E\u0442 {min} \u0434\u043E {max} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
546
+ array_max: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {n} {items}",
547
+ array_min: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {n} {items}"
548
+ };
549
+ var BUILTIN_TRANSLATIONS = {
550
+ en: EN_TRANSLATIONS,
551
+ ru: RU_TRANSLATIONS
552
+ };
553
+ var CHAR_PLURALS = {
554
+ en: { one: "character", other: "characters" },
555
+ ru: { one: "\u0441\u0438\u043C\u0432\u043E\u043B", few: "\u0441\u0438\u043C\u0432\u043E\u043B\u0430", many: "\u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432", other: "\u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432" }
556
+ };
557
+ var ITEM_PLURALS = {
558
+ en: { one: "item", other: "items" },
559
+ ru: { one: "\u044D\u043B\u0435\u043C\u0435\u043D\u0442", few: "\u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0430", many: "\u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432", other: "\u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432" }
560
+ };
561
+ function pluralizeWord(n, locale, plurals) {
562
+ const lang = locale.split("-")[0];
563
+ const forms = plurals[lang] ?? plurals.en;
564
+ const rule = new Intl.PluralRules(locale).select(n);
565
+ return forms[rule] ?? forms.other;
566
+ }
567
+ function formatNumber(n, locale) {
568
+ if (Number.isInteger(n)) {
569
+ return new Intl.NumberFormat(locale).format(n);
570
+ }
571
+ return new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(n);
572
+ }
573
+ function formatDate(dateStr, locale) {
574
+ try {
575
+ const date = new Date(dateStr);
576
+ return new Intl.DateTimeFormat(locale, {
577
+ day: "numeric",
578
+ month: "long",
579
+ year: "numeric"
580
+ }).format(date);
581
+ } catch {
582
+ return dateStr;
583
+ }
584
+ }
585
+ function template(str, vars) {
586
+ return str.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? ""));
587
+ }
588
+ function getTranslations(locale, custom) {
589
+ const lang = locale.split("-")[0];
590
+ const base = BUILTIN_TRANSLATIONS[lang] ?? BUILTIN_TRANSLATIONS[locale] ?? EN_TRANSLATIONS;
591
+ return base;
592
+ }
593
+ function generateConstraintHint(constraints, locale = "en", customTranslations) {
594
+ if (!constraints) {
595
+ return void 0;
596
+ }
597
+ const t = getTranslations(locale);
598
+ switch (constraints.schemaType) {
599
+ case "string":
600
+ return generateStringHint(constraints.string, locale, t);
601
+ case "number":
602
+ return generateNumberHint(constraints.number, locale, t);
603
+ case "date":
604
+ return generateDateHint(constraints.date, locale, t);
605
+ case "array":
606
+ return generateArrayHint(constraints.array, locale, t);
607
+ default:
608
+ return void 0;
609
+ }
610
+ }
611
+ function generateStringHint(constraints, locale, t) {
612
+ if (!constraints) return void 0;
613
+ const { minLength, maxLength, inputType } = constraints;
614
+ if (inputType === "email" || inputType === "url") {
615
+ if (maxLength) {
616
+ return template(t.string_max, { n: maxLength, chars: pluralizeWord(maxLength, locale, CHAR_PLURALS) });
617
+ }
618
+ return void 0;
619
+ }
620
+ if (minLength !== void 0 && maxLength !== void 0) {
621
+ if (minLength === maxLength) {
622
+ return template(t.string_exact, { n: minLength, chars: pluralizeWord(minLength, locale, CHAR_PLURALS) });
623
+ }
624
+ return template(t.string_range, { min: minLength, max: maxLength });
625
+ }
626
+ if (maxLength !== void 0) {
627
+ return template(t.string_max, { n: maxLength, chars: pluralizeWord(maxLength, locale, CHAR_PLURALS) });
628
+ }
629
+ if (minLength !== void 0) {
630
+ return template(t.string_min, { n: minLength, chars: pluralizeWord(minLength, locale, CHAR_PLURALS) });
631
+ }
632
+ return void 0;
633
+ }
634
+ function generateNumberHint(constraints, locale, t) {
635
+ if (!constraints) return void 0;
636
+ const { min, max, isInteger } = constraints;
637
+ const suffix = isInteger ? t.number_integer_suffix : "";
638
+ if (min !== void 0 && max !== void 0) {
639
+ return template(t.number_range, { min: formatNumber(min, locale), max: formatNumber(max, locale), suffix });
640
+ }
641
+ if (max !== void 0) {
642
+ return template(t.number_max, { max: formatNumber(max, locale), suffix });
643
+ }
644
+ if (min !== void 0) {
645
+ return template(t.number_min, { min: formatNumber(min, locale), suffix });
646
+ }
647
+ if (isInteger) {
648
+ return t.number_integer;
649
+ }
650
+ return void 0;
651
+ }
652
+ function generateDateHint(constraints, locale, t) {
653
+ if (!constraints) return void 0;
654
+ const { min, max } = constraints;
655
+ if (min && max) {
656
+ return template(t.date_range, { min: formatDate(min, locale), max: formatDate(max, locale) });
657
+ }
658
+ if (min) {
659
+ return template(t.date_after, { min: formatDate(min, locale) });
660
+ }
661
+ if (max) {
662
+ return template(t.date_before, { max: formatDate(max, locale) });
663
+ }
664
+ return void 0;
665
+ }
666
+ function generateArrayHint(constraints, locale, t) {
667
+ if (!constraints) return void 0;
668
+ const { minItems, maxItems } = constraints;
669
+ if (minItems !== void 0 && maxItems !== void 0) {
670
+ if (minItems === maxItems) {
671
+ return template(t.array_exact, { n: minItems, items: pluralizeWord(minItems, locale, ITEM_PLURALS) });
672
+ }
673
+ return template(t.array_range, { min: minItems, max: maxItems });
674
+ }
675
+ if (maxItems !== void 0) {
676
+ return template(t.array_max, { n: maxItems, items: pluralizeWord(maxItems, locale, ITEM_PLURALS) });
677
+ }
678
+ if (minItems !== void 0) {
679
+ return template(t.array_min, { n: minItems, items: pluralizeWord(minItems, locale, ITEM_PLURALS) });
680
+ }
681
+ return void 0;
682
+ }
683
+
684
+ // src/lib/declarative/form-fields/base/use-resolved-field-props.ts
685
+ function useResolvedFieldProps(name, props) {
686
+ const {
687
+ form,
688
+ fullPath,
689
+ meta,
690
+ required: schemaRequired,
691
+ formDisabled,
692
+ formReadOnly,
693
+ constraints
694
+ } = useDeclarativeField(name);
695
+ const i18n = useFormI18n();
696
+ const i18nKey = meta?.i18nKey;
697
+ const resolvedTitle = getLocalizedValue(i18n, i18nKey, "title", meta?.title);
698
+ const resolvedPlaceholder = getLocalizedValue(i18n, i18nKey, "placeholder", meta?.placeholder);
699
+ const resolvedDescription = getLocalizedValue(i18n, i18nKey, "description", meta?.description);
700
+ const autoHint = generateConstraintHint(constraints, i18n?.locale ?? "en");
701
+ const helperText = props.helperText ?? resolvedDescription ?? autoHint;
702
+ const localizedOptions = useLocalizedOptions(meta?.options);
703
+ return {
704
+ form,
705
+ fullPath,
706
+ // Props override i18n override schema meta
707
+ label: props.label ?? resolvedTitle,
708
+ placeholder: props.placeholder ?? resolvedPlaceholder,
709
+ helperText,
710
+ // Tooltip from props or schema meta
711
+ tooltip: props.tooltip ?? meta?.tooltip,
712
+ // Required from schema or prop
713
+ required: props.required ?? schemaRequired,
714
+ // Form-level + local props (local wins)
715
+ disabled: props.disabled ?? formDisabled,
716
+ readOnly: props.readOnly ?? formReadOnly,
717
+ // Constraints for additional component configuration
718
+ constraints,
719
+ // Options with i18n translations
720
+ options: localizedOptions
721
+ };
722
+ }
723
+ function createField(options) {
724
+ const { displayName, render } = options;
725
+ const useFieldState = options.useFieldState ?? (() => ({}));
726
+ function FieldComponent(props) {
727
+ const { name, label, placeholder, helperText, required, disabled, readOnly, tooltip, ...componentProps } = props;
728
+ const { form, fullPath, ...resolvedRest } = useResolvedFieldProps(name, {
729
+ label,
730
+ placeholder,
731
+ helperText,
732
+ required,
733
+ disabled,
734
+ readOnly,
735
+ tooltip
736
+ });
737
+ const resolved = {
738
+ label: resolvedRest.label,
739
+ placeholder: resolvedRest.placeholder,
740
+ helperText: resolvedRest.helperText,
741
+ tooltip: resolvedRest.tooltip,
742
+ required: resolvedRest.required,
743
+ disabled: resolvedRest.disabled,
744
+ readOnly: resolvedRest.readOnly,
745
+ constraints: resolvedRest.constraints,
746
+ options: resolvedRest.options
747
+ };
748
+ const fieldState = useFieldState(componentProps, resolved);
749
+ return /* @__PURE__ */ jsx(FieldErrorBoundary, { fieldName: fullPath, children: /* @__PURE__ */ jsx(form.Field, { name: fullPath, children: (field) => {
750
+ const errors = field.state.meta.errors;
751
+ const isTouched = field.state.meta.isTouched;
752
+ const hasError = isTouched && hasFieldErrors(errors);
753
+ const errorMessage = hasError ? formatFieldErrors(errors) : "";
754
+ return render({
755
+ field,
756
+ value: field.state.value,
757
+ fullPath,
758
+ resolved,
759
+ hasError,
760
+ errorMessage,
761
+ fieldState,
762
+ componentProps
763
+ });
764
+ } }) });
765
+ }
766
+ FieldComponent.displayName = displayName;
767
+ return FieldComponent;
768
+ }
769
+ function FieldError({
770
+ hasError,
771
+ errorMessage,
772
+ helperText
773
+ }) {
774
+ if (hasError) {
775
+ return /* @__PURE__ */ jsx(Field.ErrorText, { children: errorMessage });
776
+ }
777
+ if (helperText) {
778
+ return /* @__PURE__ */ jsx(Field.HelperText, { children: helperText });
779
+ }
780
+ return null;
781
+ }
782
+ var FieldWrapper = memo(function FieldWrapper2({
783
+ resolved,
784
+ hasError,
785
+ errorMessage,
786
+ children
787
+ }) {
788
+ return /* @__PURE__ */ jsxs(
789
+ Field.Root,
790
+ {
791
+ invalid: hasError,
792
+ required: resolved.required,
793
+ disabled: resolved.disabled,
794
+ readOnly: resolved.readOnly,
795
+ children: [
796
+ /* @__PURE__ */ jsx(FieldLabel, { label: resolved.label, tooltip: resolved.tooltip, required: resolved.required }),
797
+ children,
798
+ /* @__PURE__ */ jsx(FieldError, { hasError, errorMessage, helperText: resolved.helperText })
799
+ ]
800
+ }
801
+ );
802
+ });
803
+ function SelectionFieldLabel({ label, tooltip, required }) {
804
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
805
+ tooltip ? /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
806
+ /* @__PURE__ */ jsx("span", { children: label }),
807
+ /* @__PURE__ */ jsx(FieldTooltip, { ...tooltip })
808
+ ] }) : label,
809
+ required && /* @__PURE__ */ jsx(Field.RequiredIndicator, {})
810
+ ] });
811
+ }
812
+ function getOptionLabel(item) {
813
+ return typeof item.label === "string" ? item.label : String(item.value);
814
+ }
815
+ function useGroupedOptions(options) {
816
+ const collection = useMemo(() => {
817
+ const hasGroups = options.some((opt) => opt.group);
818
+ return createListCollection({
819
+ items: options,
820
+ itemToString: getOptionLabel,
821
+ itemToValue: (item) => item.value,
822
+ isItemDisabled: (item) => item.disabled ?? false,
823
+ ...hasGroups && {
824
+ groupBy: (item) => item.group ?? ""
825
+ }
826
+ });
827
+ }, [options]);
828
+ const groups = useMemo(() => {
829
+ const hasGroups = options.some((opt) => opt.group);
830
+ if (!hasGroups) {
831
+ return null;
832
+ }
833
+ const groupMap = /* @__PURE__ */ new Map();
834
+ for (const opt of options) {
835
+ const group = opt.group ?? "";
836
+ const existing = groupMap.get(group) ?? [];
837
+ groupMap.set(group, [...existing, opt]);
838
+ }
839
+ return groupMap;
840
+ }, [options]);
841
+ return { collection, groups };
842
+ }
843
+
844
+ export { DeclarativeFormContext, FieldError, FieldLabel, FieldTooltip, FieldWrapper, FormGroup, SelectionFieldLabel, createField, getFieldErrors, getOptionLabel, getZodConstraints, unwrapSchema, useAsyncSearch, useDebounce, useDeclarativeField, useDeclarativeForm, useDeclarativeFormOptional, useFormGroup, useGroupedOptions, useResolvedFieldProps };
845
+ //# sourceMappingURL=chunk-HWVOFWAT.js.map
846
+ //# sourceMappingURL=chunk-HWVOFWAT.js.map