@tachui/forms 0.7.0-alpha1 → 0.8.0-alpha

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 (114) hide show
  1. package/README.md +136 -0
  2. package/dist/DatePicker-D5nRFTUm.js +475 -0
  3. package/dist/DatePicker-D5nRFTUm.js.map +1 -0
  4. package/dist/Select-yZyKooXk.js +945 -0
  5. package/dist/Select-yZyKooXk.js.map +1 -0
  6. package/dist/Slider-0-oal5YR.js +644 -0
  7. package/dist/Slider-0-oal5YR.js.map +1 -0
  8. package/dist/TextField-hX15dY3U.js +509 -0
  9. package/dist/TextField-hX15dY3U.js.map +1 -0
  10. package/dist/components/advanced/Slider.d.ts +190 -0
  11. package/dist/components/advanced/Slider.d.ts.map +1 -0
  12. package/dist/components/advanced/Stepper.d.ts +161 -0
  13. package/dist/components/advanced/Stepper.d.ts.map +1 -0
  14. package/dist/components/advanced/index.d.ts +15 -0
  15. package/dist/components/advanced/index.d.ts.map +1 -0
  16. package/dist/components/advanced/index.js +6 -0
  17. package/dist/{state → components/advanced}/index.js.map +1 -1
  18. package/dist/components/date-picker/DatePicker.d.ts +126 -0
  19. package/dist/components/date-picker/DatePicker.d.ts.map +1 -0
  20. package/dist/components/date-picker/index.d.ts +14 -0
  21. package/dist/components/date-picker/index.d.ts.map +1 -0
  22. package/dist/components/date-picker/index.js +5 -0
  23. package/dist/components/{index.js.map → date-picker/index.js.map} +1 -1
  24. package/dist/components/form-container/index.d.ts +58 -0
  25. package/dist/components/form-container/index.d.ts.map +1 -0
  26. package/dist/components/selection/Checkbox.d.ts.map +1 -0
  27. package/dist/components/selection/Radio.d.ts.map +1 -0
  28. package/dist/components/selection/Select.d.ts.map +1 -0
  29. package/dist/components/selection/index.d.ts +68 -0
  30. package/dist/components/selection/index.d.ts.map +1 -0
  31. package/dist/components/selection/index.js +12 -0
  32. package/dist/components/selection/index.js.map +1 -0
  33. package/dist/components/text-input/TextField.d.ts.map +1 -0
  34. package/dist/components/text-input/index.d.ts +8 -0
  35. package/dist/components/text-input/index.d.ts.map +1 -0
  36. package/dist/components/text-input/index.js +18 -0
  37. package/dist/components/text-input/index.js.map +1 -0
  38. package/dist/index-D3WfkqVv.js +249 -0
  39. package/dist/index-D3WfkqVv.js.map +1 -0
  40. package/dist/index.d.ts +10 -15
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +196 -376
  43. package/dist/index.js.map +1 -1
  44. package/dist/state/index.d.ts.map +1 -1
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/utils/index.d.ts +19 -0
  47. package/dist/utils/index.d.ts.map +1 -0
  48. package/dist/validation/component-validation.d.ts +11 -2
  49. package/dist/validation/component-validation.d.ts.map +1 -1
  50. package/dist/validation/index.d.ts.map +1 -1
  51. package/dist/validation/index.js +687 -17
  52. package/dist/validation/index.js.map +1 -1
  53. package/package.json +54 -41
  54. package/src/components/advanced/Slider.ts +722 -0
  55. package/src/components/advanced/Stepper.ts +715 -0
  56. package/src/components/advanced/index.ts +20 -0
  57. package/src/components/date-picker/DatePicker.ts +925 -0
  58. package/src/components/date-picker/index.ts +20 -0
  59. package/src/components/form-container/index.ts +266 -0
  60. package/src/components/selection/Checkbox.ts +478 -0
  61. package/src/components/selection/Radio.ts +470 -0
  62. package/src/components/selection/Select.ts +620 -0
  63. package/src/components/selection/index.ts +81 -0
  64. package/src/components/text-input/TextField.ts +728 -0
  65. package/src/components/text-input/index.ts +35 -0
  66. package/src/index.ts +48 -0
  67. package/src/state/index.ts +544 -0
  68. package/src/types/index.ts +579 -0
  69. package/src/utils/formatters.ts +184 -0
  70. package/src/utils/index.ts +57 -0
  71. package/src/validation/component-validation.ts +429 -0
  72. package/src/validation/index.ts +641 -0
  73. package/dist/Form-ueYEcSg1.cjs +0 -2
  74. package/dist/Form-ueYEcSg1.cjs.map +0 -1
  75. package/dist/Form-ylAr3o_e.js +0 -376
  76. package/dist/Form-ylAr3o_e.js.map +0 -1
  77. package/dist/components/Form.d.ts +0 -76
  78. package/dist/components/Form.d.ts.map +0 -1
  79. package/dist/components/index.cjs +0 -2
  80. package/dist/components/index.cjs.map +0 -1
  81. package/dist/components/index.d.ts +0 -9
  82. package/dist/components/index.d.ts.map +0 -1
  83. package/dist/components/index.js +0 -31
  84. package/dist/components/input/Checkbox.d.ts.map +0 -1
  85. package/dist/components/input/Radio.d.ts.map +0 -1
  86. package/dist/components/input/Select.d.ts.map +0 -1
  87. package/dist/components/input/TextField.d.ts.map +0 -1
  88. package/dist/components/input/index.d.ts +0 -11
  89. package/dist/components/input/index.d.ts.map +0 -1
  90. package/dist/forms-complex-BiQsZZlT.js +0 -361
  91. package/dist/forms-complex-BiQsZZlT.js.map +0 -1
  92. package/dist/forms-complex-DLEnXXJ5.cjs +0 -2
  93. package/dist/forms-complex-DLEnXXJ5.cjs.map +0 -1
  94. package/dist/forms-core-B1bx1drO.js +0 -839
  95. package/dist/forms-core-B1bx1drO.js.map +0 -1
  96. package/dist/forms-core-W_JGVLAI.cjs +0 -9
  97. package/dist/forms-core-W_JGVLAI.cjs.map +0 -1
  98. package/dist/forms-inputs-6QdeMWFk.js +0 -1075
  99. package/dist/forms-inputs-6QdeMWFk.js.map +0 -1
  100. package/dist/forms-inputs-DQ5QI_SU.cjs +0 -2
  101. package/dist/forms-inputs-DQ5QI_SU.cjs.map +0 -1
  102. package/dist/index.cjs +0 -2
  103. package/dist/index.cjs.map +0 -1
  104. package/dist/state/index.cjs +0 -2
  105. package/dist/state/index.cjs.map +0 -1
  106. package/dist/state/index.js +0 -9
  107. package/dist/utils/validators.d.ts +0 -101
  108. package/dist/utils/validators.d.ts.map +0 -1
  109. package/dist/validation/index.cjs +0 -2
  110. package/dist/validation/index.cjs.map +0 -1
  111. /package/dist/components/{input → selection}/Checkbox.d.ts +0 -0
  112. /package/dist/components/{input → selection}/Radio.d.ts +0 -0
  113. /package/dist/components/{input → selection}/Select.d.ts +0 -0
  114. /package/dist/components/{input → text-input}/TextField.d.ts +0 -0
@@ -0,0 +1,641 @@
1
+ /**
2
+ * TachUI Forms Validation System
3
+ *
4
+ * Comprehensive validation engine with built-in rules, custom validators,
5
+ * async validation support, and i18n-ready error messages.
6
+ */
7
+
8
+ import type {
9
+ CustomValidationRule,
10
+ FieldState,
11
+ FieldValidation,
12
+ ValidationResult,
13
+ ValidationRule,
14
+ } from '../types'
15
+
16
+ /**
17
+ * Built-in validation rules
18
+ */
19
+ const VALIDATION_RULES: Record<
20
+ string,
21
+ (value: any, options?: any) => ValidationResult
22
+ > = {
23
+ required: (value: any): ValidationResult => ({
24
+ valid: value !== null && value !== undefined && value !== '',
25
+ message: 'This field is required',
26
+ code: 'REQUIRED',
27
+ }),
28
+
29
+ email: (value: string): ValidationResult => {
30
+ if (!value) return { valid: true }
31
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
32
+ return {
33
+ valid: emailRegex.test(value),
34
+ message: 'Please enter a valid email address',
35
+ code: 'INVALID_EMAIL',
36
+ }
37
+ },
38
+
39
+ url: (value: string): ValidationResult => {
40
+ if (!value) return { valid: true }
41
+ try {
42
+ new URL(value)
43
+ return { valid: true }
44
+ } catch {
45
+ return {
46
+ valid: false,
47
+ message: 'Please enter a valid URL',
48
+ code: 'INVALID_URL',
49
+ }
50
+ }
51
+ },
52
+
53
+ number: (value: any): ValidationResult => {
54
+ if (!value) return { valid: true }
55
+ const num = Number(value)
56
+ return {
57
+ valid: !Number.isNaN(num) && Number.isFinite(num),
58
+ message: 'Please enter a valid number',
59
+ code: 'INVALID_NUMBER',
60
+ }
61
+ },
62
+
63
+ integer: (value: any): ValidationResult => {
64
+ if (!value) return { valid: true }
65
+ const num = Number(value)
66
+ return {
67
+ valid:
68
+ !Number.isNaN(num) && Number.isFinite(num) && Number.isInteger(num),
69
+ message: 'Please enter a valid integer',
70
+ code: 'INVALID_INTEGER',
71
+ }
72
+ },
73
+
74
+ min: (value: any, options: { min: number }): ValidationResult => {
75
+ if (!value) return { valid: true }
76
+ const num = Number(value)
77
+ if (Number.isNaN(num)) return { valid: true } // Let number validation handle this
78
+
79
+ return {
80
+ valid: num >= options.min,
81
+ message: `Value must be at least ${options.min}`,
82
+ code: 'MIN_VALUE',
83
+ }
84
+ },
85
+
86
+ max: (value: any, options: { max: number }): ValidationResult => {
87
+ if (!value) return { valid: true }
88
+ const num = Number(value)
89
+ if (Number.isNaN(num)) return { valid: true } // Let number validation handle this
90
+
91
+ return {
92
+ valid: num <= options.max,
93
+ message: `Value must be at most ${options.max}`,
94
+ code: 'MAX_VALUE',
95
+ }
96
+ },
97
+
98
+ minLength: (
99
+ value: string,
100
+ options: { minLength: number }
101
+ ): ValidationResult => {
102
+ if (!value) return { valid: true }
103
+ return {
104
+ valid: value.length >= options.minLength,
105
+ message: `Must be at least ${options.minLength} characters`,
106
+ code: 'MIN_LENGTH',
107
+ }
108
+ },
109
+
110
+ maxLength: (
111
+ value: string,
112
+ options: { maxLength: number }
113
+ ): ValidationResult => {
114
+ if (!value) return { valid: true }
115
+ return {
116
+ valid: value.length <= options.maxLength,
117
+ message: `Must be at most ${options.maxLength} characters`,
118
+ code: 'MAX_LENGTH',
119
+ }
120
+ },
121
+
122
+ pattern: (
123
+ value: string,
124
+ options: { pattern: string | RegExp; message?: string }
125
+ ): ValidationResult => {
126
+ if (!value) return { valid: true }
127
+ const regex =
128
+ typeof options.pattern === 'string'
129
+ ? new RegExp(options.pattern)
130
+ : options.pattern
131
+ return {
132
+ valid: regex.test(value),
133
+ message: options.message || 'Value does not match required pattern',
134
+ code: 'PATTERN_MISMATCH',
135
+ }
136
+ },
137
+
138
+ // Additional validation rules expected by tests
139
+ numeric: (value: any): ValidationResult => {
140
+ if (!value) return { valid: true }
141
+ const num = Number(value)
142
+ return {
143
+ valid: !Number.isNaN(num) && Number.isFinite(num),
144
+ message: 'Please enter a valid number',
145
+ code: 'INVALID_NUMERIC',
146
+ }
147
+ },
148
+
149
+ phone: (value: string): ValidationResult => {
150
+ if (!value) return { valid: true }
151
+ const phoneRegex = /^\+?[\d\s\-().]+$/
152
+ const digits = value.replace(/[\s\-().]/g, '')
153
+ return {
154
+ valid: phoneRegex.test(value) && digits.length >= 10,
155
+ message: 'Please enter a valid phone number',
156
+ code: 'INVALID_PHONE',
157
+ }
158
+ },
159
+
160
+ creditCard: (value: string): ValidationResult => {
161
+ if (!value) return { valid: true }
162
+ const digits = value.replace(/\s/g, '')
163
+ // Basic Luhn algorithm check
164
+ let sum = 0
165
+ let alternate = false
166
+ for (let i = digits.length - 1; i >= 0; i--) {
167
+ let n = parseInt(digits.charAt(i))
168
+ if (alternate) {
169
+ n *= 2
170
+ if (n > 9) n = (n % 10) + 1
171
+ }
172
+ sum += n
173
+ alternate = !alternate
174
+ }
175
+ return {
176
+ valid: /^\d{13,19}$/.test(digits) && sum % 10 === 0,
177
+ message: 'Please enter a valid credit card number',
178
+ code: 'INVALID_CREDIT_CARD',
179
+ }
180
+ },
181
+
182
+ ssn: (value: string): ValidationResult => {
183
+ if (!value) return { valid: true }
184
+ const digits = value.replace(/[-\s]/g, '')
185
+ return {
186
+ valid: /^\d{9}$/.test(digits),
187
+ message: 'Please enter a valid Social Security Number',
188
+ code: 'INVALID_SSN',
189
+ }
190
+ },
191
+
192
+ postalCode: (value: string): ValidationResult => {
193
+ if (!value) return { valid: true }
194
+ // US ZIP code (5 or 9 digits) or international postal codes
195
+ const usZip = /^\d{5}(-\d{4})?$/
196
+ const intlPostal = /^[A-Z0-9]{3,10}$/i
197
+ return {
198
+ valid: usZip.test(value) || intlPostal.test(value),
199
+ message: 'Please enter a valid postal code',
200
+ code: 'INVALID_POSTAL_CODE',
201
+ }
202
+ },
203
+
204
+ zipCode: (value: string): ValidationResult => {
205
+ if (!value) return { valid: true }
206
+ // US ZIP code (5 or 9 digits)
207
+ const usZip = /^\d{5}(-\d{4})?$/
208
+ return {
209
+ valid: usZip.test(value),
210
+ message: 'Please enter a valid ZIP code',
211
+ code: 'INVALID_ZIP_CODE',
212
+ }
213
+ },
214
+
215
+ date: (value: string): ValidationResult => {
216
+ if (!value) return { valid: true }
217
+ const date = new Date(value)
218
+ return {
219
+ valid:
220
+ !Number.isNaN(date.getTime()) && !!value.match(/^\d{4}-\d{2}-\d{2}$/),
221
+ message: 'Please enter a valid date (YYYY-MM-DD)',
222
+ code: 'INVALID_DATE',
223
+ }
224
+ },
225
+
226
+ time: (value: string): ValidationResult => {
227
+ if (!value) return { valid: true }
228
+ return {
229
+ valid: /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test(value),
230
+ message: 'Please enter a valid time (HH:MM)',
231
+ code: 'INVALID_TIME',
232
+ }
233
+ },
234
+
235
+ strongPassword: (value: string): ValidationResult => {
236
+ if (!value) return { valid: true }
237
+ const hasUpperCase = /[A-Z]/.test(value)
238
+ const hasLowerCase = /[a-z]/.test(value)
239
+ const hasNumbers = /\d/.test(value)
240
+ const hasNonalphas = /\W/.test(value)
241
+ const isLongEnough = value.length >= 8
242
+
243
+ const valid =
244
+ hasUpperCase && hasLowerCase && hasNumbers && hasNonalphas && isLongEnough
245
+ return {
246
+ valid,
247
+ message: valid
248
+ ? undefined
249
+ : 'Password must be at least 8 characters with uppercase, lowercase, number and special character',
250
+ code: 'WEAK_PASSWORD',
251
+ }
252
+ },
253
+ }
254
+
255
+ /**
256
+ * Custom validation rule registry
257
+ */
258
+ const customRules = new Map<string, CustomValidationRule>()
259
+
260
+ /**
261
+ * Register a custom validation rule
262
+ */
263
+ export function registerValidationRule(rule: CustomValidationRule): void {
264
+ customRules.set(rule.name, rule)
265
+ }
266
+
267
+ /**
268
+ * Unregister a custom validation rule
269
+ */
270
+ export function unregisterValidationRule(name: string): void {
271
+ customRules.delete(name)
272
+ }
273
+
274
+ /**
275
+ * Get all registered validation rules
276
+ */
277
+ export function getValidationRules(): string[] {
278
+ return [...Object.keys(VALIDATION_RULES), ...customRules.keys()]
279
+ }
280
+
281
+ /**
282
+ * Validate a single value against validation rules
283
+ */
284
+ export function validateValue(
285
+ value: any,
286
+ rules: ValidationRule[],
287
+ options?: Record<string, any>
288
+ ): ValidationResult {
289
+ for (const rule of rules) {
290
+ let result: ValidationResult
291
+
292
+ if (typeof rule === 'string') {
293
+ // Built-in rule
294
+ const validator = VALIDATION_RULES[rule]
295
+ if (validator) {
296
+ result = validator(value, options?.[rule])
297
+ } else {
298
+ // Check custom rules
299
+ const customRule = customRules.get(rule)
300
+ if (customRule) {
301
+ result = customRule.validate(value, customRule.options)
302
+ } else {
303
+ console.warn(`Unknown validation rule: ${rule}`)
304
+ continue
305
+ }
306
+ }
307
+ } else if ('validate' in rule) {
308
+ // Custom validation rule object
309
+ result = rule.validate(value, rule.options)
310
+ } else {
311
+ // Built-in validation rule object (BuiltInValidationRule)
312
+ const validator = VALIDATION_RULES[rule.name]
313
+ if (validator) {
314
+ result = validator(value, rule.options)
315
+ } else {
316
+ console.warn(`Unknown built-in validation rule: ${rule.name}`)
317
+ continue
318
+ }
319
+ }
320
+
321
+ if (!result.valid) {
322
+ return result
323
+ }
324
+ }
325
+
326
+ return { valid: true }
327
+ }
328
+
329
+ /**
330
+ * Validate a field state
331
+ */
332
+ export function validateField(
333
+ field: FieldState,
334
+ validation?: FieldValidation
335
+ ): ValidationResult {
336
+ if (!validation?.rules || validation.rules.length === 0) {
337
+ return { valid: true }
338
+ }
339
+
340
+ return validateValue(field.value, validation.rules)
341
+ }
342
+
343
+ /**
344
+ * Async validation function
345
+ */
346
+ export async function validateValueAsync(
347
+ value: any,
348
+ rules: ValidationRule[],
349
+ options?: Record<string, any>
350
+ ): Promise<ValidationResult> {
351
+ for (const rule of rules) {
352
+ let result: ValidationResult | Promise<ValidationResult>
353
+
354
+ if (typeof rule === 'string') {
355
+ const validator = VALIDATION_RULES[rule]
356
+ if (validator) {
357
+ result = validator(value, options?.[rule])
358
+ } else {
359
+ const customRule = customRules.get(rule)
360
+ if (customRule) {
361
+ result = customRule.validate(value, customRule.options)
362
+ } else {
363
+ continue
364
+ }
365
+ }
366
+ } else if ('validate' in rule) {
367
+ // Custom validation rule object
368
+ result = rule.validate(value, rule.options)
369
+ } else {
370
+ // Built-in validation rule object (BuiltInValidationRule)
371
+ const validator = VALIDATION_RULES[rule.name]
372
+ if (validator) {
373
+ result = validator(value, rule.options)
374
+ } else {
375
+ continue
376
+ }
377
+ }
378
+
379
+ // Handle async validation
380
+ const validationResult = await Promise.resolve(result)
381
+
382
+ if (!validationResult.valid) {
383
+ return validationResult
384
+ }
385
+ }
386
+
387
+ return { valid: true }
388
+ }
389
+
390
+ /**
391
+ * Debounced validation
392
+ */
393
+ export function createDebouncedValidator(
394
+ validator: (value: any) => ValidationResult | Promise<ValidationResult>,
395
+ delayMs: number = 300
396
+ ) {
397
+ let timeoutId: ReturnType<typeof setTimeout>
398
+ let lastValue: any
399
+ let lastResult: ValidationResult | Promise<ValidationResult>
400
+
401
+ return (value: any): Promise<ValidationResult> => {
402
+ return new Promise(resolve => {
403
+ // If value hasn't changed, return cached result
404
+ if (value === lastValue && lastResult) {
405
+ Promise.resolve(lastResult).then(resolve)
406
+ return
407
+ }
408
+
409
+ lastValue = value
410
+
411
+ // Clear previous timeout
412
+ clearTimeout(timeoutId)
413
+
414
+ // Set new timeout
415
+ timeoutId = setTimeout(async () => {
416
+ try {
417
+ lastResult = validator(value)
418
+ const result = await Promise.resolve(lastResult)
419
+ resolve(result)
420
+ } catch (_error) {
421
+ resolve({
422
+ valid: false,
423
+ message: 'Validation error occurred',
424
+ code: 'VALIDATION_ERROR',
425
+ })
426
+ }
427
+ }, delayMs)
428
+ })
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Cross-field validation utilities
434
+ */
435
+ export const CrossFieldValidators = {
436
+ /**
437
+ * Validate that two fields match (e.g., password confirmation)
438
+ */
439
+ fieldMatch:
440
+ (field1: string, field2: string, message = 'Fields must match') =>
441
+ (values: Record<string, any>): ValidationResult => {
442
+ const isValid = values[field1] === values[field2]
443
+ if (isValid) {
444
+ return { valid: true }
445
+ }
446
+ return {
447
+ valid: false,
448
+ message,
449
+ code: 'FIELD_MISMATCH',
450
+ }
451
+ },
452
+
453
+ /**
454
+ * Validate that at least one field in a group is filled
455
+ */
456
+ requireOneOf:
457
+ (fields: string[], message = 'At least one field is required') =>
458
+ (values: Record<string, any>): ValidationResult => {
459
+ const hasValue = fields.some(field => {
460
+ const value = values[field]
461
+ return value !== null && value !== undefined && value !== ''
462
+ })
463
+
464
+ if (hasValue) {
465
+ return { valid: true }
466
+ }
467
+ return {
468
+ valid: false,
469
+ message,
470
+ code: 'REQUIRE_ONE_OF',
471
+ }
472
+ },
473
+
474
+ /**
475
+ * Validate that a field is required when another field has a specific value
476
+ */
477
+ requiredWhen:
478
+ (
479
+ targetField: string,
480
+ conditionalField: string,
481
+ conditionalValue: any,
482
+ message = `${targetField} is required`
483
+ ) =>
484
+ (values: Record<string, any>): ValidationResult => {
485
+ if (values[conditionalField] === conditionalValue) {
486
+ const targetValue = values[targetField]
487
+ const isValid =
488
+ targetValue !== null &&
489
+ targetValue !== undefined &&
490
+ targetValue !== ''
491
+ if (isValid) {
492
+ return { valid: true }
493
+ }
494
+ return {
495
+ valid: false,
496
+ message,
497
+ code: 'REQUIRED_WHEN',
498
+ }
499
+ }
500
+ return { valid: true }
501
+ },
502
+
503
+ /**
504
+ * Validate date range (start date before end date)
505
+ */
506
+ dateRange:
507
+ (
508
+ startField: string,
509
+ endField: string,
510
+ message = 'End date must be after start date'
511
+ ) =>
512
+ (values: Record<string, any>): ValidationResult => {
513
+ const startDate = values[startField]
514
+ const endDate = values[endField]
515
+
516
+ if (!startDate || !endDate) return { valid: true }
517
+
518
+ const start = new Date(startDate)
519
+ const end = new Date(endDate)
520
+ const isValid = end >= start
521
+
522
+ if (isValid) {
523
+ return { valid: true }
524
+ }
525
+ return {
526
+ valid: false,
527
+ message,
528
+ code: 'INVALID_DATE_RANGE',
529
+ }
530
+ },
531
+ }
532
+
533
+ /**
534
+ * Built-in validation rule presets
535
+ */
536
+ export const ValidationPresets = {
537
+ email: ['required', 'email'],
538
+ password: ['required', { name: 'minLength', options: { minLength: 8 } }],
539
+ phone: [
540
+ 'required',
541
+ {
542
+ name: 'pattern',
543
+ options: { pattern: /^\+?[\d\s-()]+$/, message: 'Invalid phone number' },
544
+ },
545
+ ],
546
+ url: ['required', 'url'],
547
+ positiveNumber: ['required', 'number', { name: 'min', options: { min: 0 } }],
548
+ percentage: [
549
+ 'required',
550
+ 'number',
551
+ { name: 'min', options: { min: 0 } },
552
+ { name: 'max', options: { max: 100 } },
553
+ ],
554
+ }
555
+
556
+ /**
557
+ * Validation message formatter
558
+ */
559
+ export class ValidationMessageFormatter {
560
+ private messages: Record<string, string> = {}
561
+
562
+ constructor(messages: Record<string, string> = {}) {
563
+ this.messages = messages
564
+ }
565
+
566
+ setMessage(code: string, message: string): void {
567
+ this.messages[code] = message
568
+ }
569
+
570
+ setMessages(messages: Record<string, string>): void {
571
+ this.messages = { ...this.messages, ...messages }
572
+ }
573
+
574
+ format(result: ValidationResult, fieldName?: string): string {
575
+ if (result.code && this.messages[result.code]) {
576
+ return this.messages[result.code].replace(
577
+ `\${field}`,
578
+ fieldName || 'field'
579
+ )
580
+ }
581
+ return result.message || 'Validation failed'
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Default validation message formatter instance
587
+ */
588
+ export const defaultMessageFormatter = new ValidationMessageFormatter()
589
+
590
+ /**
591
+ * Validation utilities
592
+ */
593
+ export const ValidationUtils = {
594
+ /**
595
+ * Check if a validation result represents an error
596
+ */
597
+ isError: (result: ValidationResult): boolean => !result.valid,
598
+
599
+ /**
600
+ * Check if a validation result is valid
601
+ */
602
+ isValid: (result: ValidationResult): boolean => result.valid,
603
+
604
+ /**
605
+ * Combine multiple validation results
606
+ */
607
+ combineResults: (results: ValidationResult[]): ValidationResult => {
608
+ const errors = results.filter(r => !r.valid)
609
+ if (errors.length === 0) {
610
+ return { valid: true }
611
+ }
612
+
613
+ return {
614
+ valid: false,
615
+ message: errors.map(e => e.message).join(', '),
616
+ code: 'MULTIPLE_ERRORS',
617
+ }
618
+ },
619
+
620
+ /**
621
+ * Create a validation function from a rule configuration
622
+ */
623
+ createValidator:
624
+ (rules: ValidationRule[], options?: Record<string, any>) =>
625
+ (value: any): ValidationResult =>
626
+ validateValue(value, rules, options),
627
+
628
+ /**
629
+ * Create an async validation function
630
+ */
631
+ createAsyncValidator:
632
+ (rules: ValidationRule[], options?: Record<string, any>) =>
633
+ (value: any): Promise<ValidationResult> =>
634
+ validateValueAsync(value, rules, options),
635
+ }
636
+
637
+ // Export all validation functionality
638
+ export { VALIDATION_RULES }
639
+
640
+ // Export component validation for plugin registration
641
+ export * from './component-validation'
@@ -1,2 +0,0 @@
1
- "use strict";const a=require("@tachui/core"),k=require("./forms-core-W_JGVLAI.cjs");function v(t,r){return Array.isArray(t)?t.flatMap(o=>C(o,r)):C(t,r)}function C(t,r){if(t==null)return[];if(typeof t=="object"&&"type"in t&&t.type==="component"){const o={...t,props:{...t.props,_formContext:r}},i=t.render.bind(o)();return Array.isArray(i)?i:[i]}return a.defaultChildrenRenderer(t)}const R=t=>{const{onSubmit:r,onChange:o,validation:n={validateOn:"blur",stopOnFirstError:!1,debounceMs:300},initialValues:i={},resetOnSubmit:l=!1,preserveValues:y=!1,children:g,...m}=t,s=k.createFormState(i),[S,u]=a.createSignal("idle"),h=async c=>{if(c&&c.preventDefault(),!!r)try{if(u("idle"),await s.validateForm()){const d=s.watch();await r(d,s.state),u("success"),l&&s.resetForm()}else u("error")}catch(f){u("error"),console.error("Form submission error:",f)}};a.createEffect(()=>{if(o){const c=s.state,f=Object.keys(c.fields).filter(d=>c.fields[d].dirty);if(f.length>0){const d=f[f.length-1],b=c.fields[d];o(d,b.value,b)}}});const x={register:s.register,unregister:s.unregister,setValue:s.setValue,getValue:s.getValue,getError:s.getError,validateField:s.validateField,validation:n,onChange:o};return{type:"component",id:m.id||"form",render:()=>a.h("form",{...m,onsubmit:h,novalidate:!0,"data-tachui-form":!0,"data-form-state":s.state.valid?"valid":"invalid","data-form-submitting":s.state.submitting,"data-submission-result":S()},...v(g,x)),props:t,cleanup:[()=>{y||s.resetForm()}]}},B=t=>{const{title:r,description:o,header:n,footer:i,style:l="automatic",spacing:y=12,collapsible:g=!1,collapsed:m=!1,onToggle:s,accessibilityLabel:S,accessibilityRole:u="group",children:h,...x}=t,[p,c]=a.createSignal(m),f=()=>{const e=!p();c(e),s&&s(e)},d=e=>e?typeof e=="string"?e:typeof e=="function"?e():e:null,b=()=>{const e={marginBottom:"20px",border:"none",padding:"0",margin:"0 0 20px 0"};switch(l){case"grouped":return{...e,backgroundColor:"#ffffff",border:"1px solid #e0e0e0",borderRadius:"12px",overflow:"hidden"};case"inset":return{...e,backgroundColor:"#f8f9fa",border:"1px solid #e9ecef",borderRadius:"8px",margin:"0 16px 20px 16px"};case"sidebar":return{...e,borderLeft:"3px solid #007AFF",paddingLeft:"16px",backgroundColor:"#f8f9fa",borderRadius:"0 8px 8px 0"};case"plain":return e;default:return{...e,backgroundColor:"#ffffff",border:"1px solid #f0f0f0",borderRadius:"8px"}}},w=()=>{const e={fontSize:"16px",fontWeight:"600",color:"#1a1a1a",margin:"0 0 12px 0"};switch(l){case"grouped":case"inset":return{...e,padding:"12px 16px 0 16px",backgroundColor:"#f8f9fa",borderBottom:"1px solid #e9ecef"};case"sidebar":return{...e,fontSize:"14px",textTransform:"uppercase",letterSpacing:"0.5px",color:"#666",marginBottom:"8px"};default:return{...e,padding:"0 0 8px 0"}}},A=()=>{const e={display:"flex",flexDirection:"column",gap:`${y}px`};switch(l){case"grouped":case"inset":return{...e,padding:"16px"};case"sidebar":return{...e,padding:"8px 0"};default:return{...e,padding:"12px"}}},E=()=>{const e={fontSize:"14px",color:"#666",margin:"8px 0 0 0"};switch(l){case"grouped":case"inset":return{...e,padding:"0 16px 12px 16px",backgroundColor:"#f8f9fa",borderTop:"1px solid #e9ecef"};default:return{...e,padding:"0 0 4px 0"}}},O=()=>{const e=r||d(n);if(!e)return[];const F=w();if(g){const I=p()?"▶":"▼";return[a.h("legend",{style:{...F,cursor:"pointer",display:"flex",alignItems:"center",gap:"8px"},onclick:f},a.h("span",{style:{fontSize:"12px",color:"#666"}},a.text(I)),...typeof e=="string"?[a.text(e)]:[e.render()].flat())]}return[a.h("legend",{style:F},...typeof e=="string"?[a.text(e)]:[e.render()].flat())]},V=()=>!o||p()?[]:[a.h("div",{style:{fontSize:"14px",color:"#666",marginBottom:"12px",padding:l==="grouped"||l==="inset"?"0 16px":"0"}},a.text(o))],z=()=>g&&p()?[]:[a.h("div",{style:A()},...v(h,t._formContext))],D=()=>{const e=d(i);return!e||p()?[]:[a.h("div",{style:E()},...typeof e=="string"?[a.text(e)]:[e.render()].flat())]};return{type:"component",id:x.id||"form-section",render:()=>a.h("fieldset",{...x,style:b(),"aria-label":S,role:u,"data-tachui-form-section":!0,"data-collapsible":g,"data-collapsed":p(),"data-style":l},...O(),...V(),...z(),...D()),props:t}},L={createAutoForm:t=>(console.log("Auto form creation:",t),R),validation:{createSchema:t=>({fields:t,validate:async r=>({valid:!0,errors:{}})})},serialize:{toFormData:t=>{const r=new FormData;return Object.entries(t).forEach(([o,n])=>{n!=null&&(n instanceof File||n instanceof Blob?r.append(o,n):Array.isArray(n)?n.forEach((i,l)=>{r.append(`${o}[${l}]`,String(i))}):r.append(o,String(n)))}),r},toURLSearchParams:t=>{const r=new URLSearchParams;return Object.entries(t).forEach(([o,n])=>{n!=null&&(Array.isArray(n)?n.forEach(i=>r.append(o,String(i))):r.append(o,String(n)))}),r},toJSON:t=>JSON.stringify(t,null,2)}};exports.Form=R;exports.FormSection=B;exports.FormUtils=L;
2
- //# sourceMappingURL=Form-ueYEcSg1.cjs.map