@tachui/forms 0.7.1-alpha → 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 (90) hide show
  1. package/README.md +87 -272
  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/components/advanced/index.js.map +1 -0
  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/date-picker/index.js.map +1 -0
  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/{state/index.js → index-D3WfkqVv.js} +15 -8
  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 +198 -376
  43. package/dist/index.js.map +1 -0
  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 +282 -191
  52. package/dist/validation/index.js.map +1 -0
  53. package/package.json +53 -39
  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/TextField-CGBM3x7K.js +0 -1799
  74. package/dist/components/Form.d.ts +0 -76
  75. package/dist/components/Form.d.ts.map +0 -1
  76. package/dist/components/index.d.ts +0 -9
  77. package/dist/components/index.d.ts.map +0 -1
  78. package/dist/components/index.js +0 -28
  79. package/dist/components/input/Checkbox.d.ts.map +0 -1
  80. package/dist/components/input/Radio.d.ts.map +0 -1
  81. package/dist/components/input/Select.d.ts.map +0 -1
  82. package/dist/components/input/TextField.d.ts.map +0 -1
  83. package/dist/components/input/index.d.ts +0 -11
  84. package/dist/components/input/index.d.ts.map +0 -1
  85. package/dist/utils/validators.d.ts +0 -101
  86. package/dist/utils/validators.d.ts.map +0 -1
  87. /package/dist/components/{input → selection}/Checkbox.d.ts +0 -0
  88. /package/dist/components/{input → selection}/Radio.d.ts +0 -0
  89. /package/dist/components/{input → selection}/Select.d.ts +0 -0
  90. /package/dist/components/{input → text-input}/TextField.d.ts +0 -0
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Checkbox Component
3
+ *
4
+ * SwiftUI-inspired checkbox with support for indeterminate state,
5
+ * validation, and accessibility features.
6
+ */
7
+
8
+ import type { Component, ComponentInstance } from '@tachui/core'
9
+ import { createEffect, createSignal, h, text } from '@tachui/core'
10
+ import { createField } from '../../state'
11
+ import type { CheckboxProps } from '../../types'
12
+
13
+ /**
14
+ * Checkbox component implementation
15
+ */
16
+ export const Checkbox: Component<CheckboxProps> = props => {
17
+ const {
18
+ name,
19
+ label,
20
+ disabled = false,
21
+ required = false,
22
+ checked: controlledChecked,
23
+ defaultChecked = false,
24
+ indeterminate = false,
25
+ validation,
26
+ onChange,
27
+ onBlur,
28
+ onFocus,
29
+ error: externalError,
30
+ helperText,
31
+ ...restProps
32
+ } = props
33
+
34
+ // Get form context if available
35
+ const formContext = (props as any)._formContext
36
+
37
+ // Create field state
38
+ const field = createField(
39
+ name,
40
+ controlledChecked ?? defaultChecked,
41
+ validation
42
+ )
43
+
44
+ // Register field with form if form context exists
45
+ if (formContext) {
46
+ formContext.register(name, validation)
47
+ }
48
+
49
+ const [focused, setFocused] = createSignal(false)
50
+
51
+ // Sync with controlled value
52
+ if (controlledChecked !== undefined) {
53
+ createEffect(() => {
54
+ if (field.value() !== controlledChecked) {
55
+ field.setValue(controlledChecked)
56
+ }
57
+ })
58
+ }
59
+
60
+ // Handle checkbox change
61
+ const handleChange = (event: Event) => {
62
+ const target = event.target as HTMLInputElement
63
+ const newChecked = target.checked
64
+
65
+ field.setValue(newChecked)
66
+
67
+ if (formContext) {
68
+ formContext.setValue(name, newChecked)
69
+ }
70
+
71
+ if (onChange) {
72
+ onChange(name, newChecked, field as any)
73
+ }
74
+ }
75
+
76
+ // Handle focus
77
+ const handleFocus = (_event: Event) => {
78
+ setFocused(true)
79
+ field.onFocus()
80
+
81
+ if (onFocus) {
82
+ onFocus(name, field.value())
83
+ }
84
+ }
85
+
86
+ // Handle blur
87
+ const handleBlur = (_event: Event) => {
88
+ setFocused(false)
89
+ field.onBlur()
90
+
91
+ if (onBlur) {
92
+ onBlur(name, field.value())
93
+ }
94
+ }
95
+
96
+ // Determine error message
97
+ const errorMessage =
98
+ externalError || field.error() || formContext?.getError(name)
99
+
100
+ // Handle keyboard interaction for custom styling
101
+ const handleKeyDown = (event: KeyboardEvent) => {
102
+ if (event.key === ' ' || event.key === 'Enter') {
103
+ event.preventDefault()
104
+ const checkbox = event.target as HTMLInputElement
105
+ checkbox.checked = !checkbox.checked
106
+ handleChange(event)
107
+ }
108
+ }
109
+
110
+ const componentInstance: ComponentInstance = {
111
+ type: 'component',
112
+ id: restProps.id || `checkbox-${name}`,
113
+ render: () =>
114
+ h(
115
+ 'div',
116
+ {
117
+ ...restProps,
118
+ class: `tachui-checkbox ${restProps.class || ''}`.trim(),
119
+ 'data-tachui-checkbox-container': true,
120
+ 'data-field-state': errorMessage
121
+ ? 'error'
122
+ : field.validating()
123
+ ? 'validating'
124
+ : 'valid',
125
+ 'data-checked': field.value(),
126
+ 'data-indeterminate': indeterminate,
127
+ 'data-disabled': disabled,
128
+ },
129
+ // Checkbox input and label wrapper
130
+ h(
131
+ 'label',
132
+ {
133
+ 'data-tachui-checkbox-label': true,
134
+ 'data-focused': focused(),
135
+ 'data-disabled': disabled,
136
+ },
137
+ // Hidden native checkbox for accessibility
138
+ h('input', {
139
+ type: 'checkbox',
140
+ id: restProps.id || name,
141
+ name,
142
+ checked: field.value(),
143
+ disabled,
144
+ required,
145
+ onchange: handleChange,
146
+ onfocus: handleFocus,
147
+ onblur: handleBlur,
148
+ onkeydown: handleKeyDown,
149
+ 'aria-invalid': !!errorMessage,
150
+ 'aria-describedby':
151
+ [
152
+ errorMessage ? `${name}-error` : null,
153
+ helperText ? `${name}-helper` : null,
154
+ ]
155
+ .filter(Boolean)
156
+ .join(' ') || undefined,
157
+ 'data-tachui-checkbox-input': true,
158
+ style: {
159
+ position: 'absolute',
160
+ opacity: '0',
161
+ width: '1px',
162
+ height: '1px',
163
+ margin: '-1px',
164
+ padding: '0',
165
+ border: '0',
166
+ clip: 'rect(0,0,0,0)',
167
+ },
168
+ }),
169
+
170
+ // Custom checkbox visual
171
+ h(
172
+ 'div',
173
+ {
174
+ 'data-tachui-checkbox-visual': true,
175
+ 'data-checked': field.value(),
176
+ 'data-indeterminate': indeterminate,
177
+ 'data-focused': focused(),
178
+ 'data-disabled': disabled,
179
+ 'data-error': !!errorMessage,
180
+ 'aria-hidden': 'true',
181
+ role: 'presentation',
182
+ },
183
+ // Checkmark or indeterminate indicator
184
+ ...(field.value() || indeterminate
185
+ ? [
186
+ h(
187
+ 'div',
188
+ {
189
+ 'data-tachui-checkbox-indicator': true,
190
+ 'data-type': indeterminate ? 'indeterminate' : 'checked',
191
+ },
192
+ text(indeterminate ? '−' : '✓')
193
+ ),
194
+ ]
195
+ : [])
196
+ ),
197
+
198
+ // Label text
199
+ ...(label
200
+ ? [
201
+ h(
202
+ 'span',
203
+ {
204
+ 'data-tachui-checkbox-text': true,
205
+ 'data-disabled': disabled,
206
+ },
207
+ text(label),
208
+ ...(required
209
+ ? [
210
+ h(
211
+ 'span',
212
+ {
213
+ 'aria-label': 'required',
214
+ 'data-required-indicator': true,
215
+ },
216
+ text(' *')
217
+ ),
218
+ ]
219
+ : [])
220
+ ),
221
+ ]
222
+ : [])
223
+ ),
224
+
225
+ // Error message
226
+ ...(errorMessage
227
+ ? [
228
+ h(
229
+ 'div',
230
+ {
231
+ id: `${name}-error`,
232
+ role: 'alert',
233
+ 'aria-live': 'polite',
234
+ 'data-tachui-error': true,
235
+ },
236
+ text(errorMessage)
237
+ ),
238
+ ]
239
+ : []),
240
+
241
+ // Helper text
242
+ ...(helperText && !errorMessage
243
+ ? [
244
+ h(
245
+ 'div',
246
+ {
247
+ id: `${name}-helper`,
248
+ 'data-tachui-helper': true,
249
+ },
250
+ text(helperText)
251
+ ),
252
+ ]
253
+ : []),
254
+
255
+ // Validation indicator
256
+ ...(field.validating()
257
+ ? [
258
+ h(
259
+ 'div',
260
+ {
261
+ 'data-tachui-validation-spinner': true,
262
+ 'aria-label': 'Validating...',
263
+ },
264
+ text('⏳')
265
+ ),
266
+ ]
267
+ : [])
268
+ ),
269
+ props: props,
270
+ cleanup: [
271
+ () => {
272
+ if (formContext) {
273
+ formContext.unregister(name)
274
+ }
275
+ },
276
+ ],
277
+ }
278
+
279
+ return componentInstance
280
+ }
281
+
282
+ /**
283
+ * Switch/Toggle component variant of Checkbox
284
+ */
285
+ export const Switch: Component<
286
+ CheckboxProps & {
287
+ size?: 'small' | 'medium' | 'large'
288
+ }
289
+ > = props => {
290
+ const { size = 'medium', ...checkboxProps } = props
291
+
292
+ // Add switch-specific props
293
+ const switchProps = {
294
+ ...checkboxProps,
295
+ class:
296
+ `tachui-switch tachui-switch-${size} ${checkboxProps.class || ''}`.trim(),
297
+ }
298
+
299
+ const checkbox = Checkbox(switchProps)
300
+ return {
301
+ ...checkbox,
302
+ render: () => {
303
+ const result = checkbox.render()
304
+ if (Array.isArray(result)) {
305
+ return result.map(node => ({
306
+ ...node,
307
+ props: {
308
+ ...node.props,
309
+ 'data-tachui-switch': true,
310
+ 'data-switch-size': size,
311
+ },
312
+ }))
313
+ }
314
+ return {
315
+ ...result,
316
+ props: {
317
+ ...result.props,
318
+ 'data-tachui-switch': true,
319
+ 'data-switch-size': size,
320
+ },
321
+ }
322
+ },
323
+ }
324
+ }
325
+
326
+ /**
327
+ * CheckboxGroup component for managing multiple related checkboxes
328
+ */
329
+ export const CheckboxGroup: Component<{
330
+ name: string
331
+ label?: string
332
+ options: Array<{
333
+ value: any
334
+ label: string
335
+ disabled?: boolean
336
+ }>
337
+ value?: any[]
338
+ defaultValue?: any[]
339
+ onChange?: (name: string, value: any[], selected: any) => void
340
+ validation?: any
341
+ error?: string
342
+ helperText?: string
343
+ disabled?: boolean
344
+ required?: boolean
345
+ direction?: 'horizontal' | 'vertical'
346
+ id?: string
347
+ [key: string]: any
348
+ }> = props => {
349
+ const {
350
+ name,
351
+ label,
352
+ options,
353
+ value: controlledValue,
354
+ defaultValue = [],
355
+ onChange,
356
+ validation,
357
+ error,
358
+ helperText,
359
+ disabled = false,
360
+ required = false,
361
+ direction = 'vertical',
362
+ ...restProps
363
+ } = props
364
+
365
+ // Create field state for the group
366
+ const field = createField(name, controlledValue ?? defaultValue, validation)
367
+
368
+ // Handle individual checkbox changes
369
+ const handleCheckboxChange = (optionValue: any, checked: boolean) => {
370
+ const currentValue = field.value() || []
371
+ let newValue: any[]
372
+
373
+ if (checked) {
374
+ newValue = [...currentValue, optionValue]
375
+ } else {
376
+ newValue = currentValue.filter((v: any) => v !== optionValue)
377
+ }
378
+
379
+ field.setValue(newValue)
380
+
381
+ if (onChange) {
382
+ onChange(name, newValue, optionValue)
383
+ }
384
+ }
385
+
386
+ const componentInstance: ComponentInstance = {
387
+ type: 'component',
388
+ id: restProps.id || `checkbox-group-${name}`,
389
+ render: () =>
390
+ h(
391
+ 'fieldset',
392
+ {
393
+ ...restProps,
394
+ 'data-tachui-checkbox-group': true,
395
+ 'data-direction': direction,
396
+ 'data-disabled': disabled,
397
+ },
398
+ // Group label
399
+ ...(label
400
+ ? [
401
+ h(
402
+ 'legend',
403
+ {
404
+ 'data-tachui-group-label': true,
405
+ },
406
+ text(label),
407
+ ...(required
408
+ ? [
409
+ h(
410
+ 'span',
411
+ {
412
+ 'aria-label': 'required',
413
+ 'data-required-indicator': true,
414
+ },
415
+ text(' *')
416
+ ),
417
+ ]
418
+ : [])
419
+ ),
420
+ ]
421
+ : []),
422
+
423
+ // Checkbox options
424
+ h(
425
+ 'div',
426
+ {
427
+ 'data-tachui-checkbox-options': true,
428
+ 'data-direction': direction,
429
+ },
430
+ ...options.flatMap((option, index) => {
431
+ const checkbox = Checkbox({
432
+ name: `${name}-${index}`,
433
+ label: option.label,
434
+ checked: (field.value() || []).includes(option.value),
435
+ disabled: disabled || option.disabled,
436
+ onChange: (_, checked) =>
437
+ handleCheckboxChange(option.value, checked),
438
+ })
439
+ const result = checkbox.render()
440
+ return Array.isArray(result) ? result : [result]
441
+ })
442
+ ),
443
+
444
+ // Error message
445
+ ...(error
446
+ ? [
447
+ h(
448
+ 'div',
449
+ {
450
+ id: `${name}-error`,
451
+ role: 'alert',
452
+ 'aria-live': 'polite',
453
+ 'data-tachui-error': true,
454
+ },
455
+ text(error)
456
+ ),
457
+ ]
458
+ : []),
459
+
460
+ // Helper text
461
+ ...(helperText && !error
462
+ ? [
463
+ h(
464
+ 'div',
465
+ {
466
+ id: `${name}-helper`,
467
+ 'data-tachui-helper': true,
468
+ },
469
+ text(helperText)
470
+ ),
471
+ ]
472
+ : [])
473
+ ),
474
+ props: props,
475
+ }
476
+
477
+ return componentInstance
478
+ }