@harnessio/forms 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +404 -94
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +143 -130
- package/dist/index.js.map +1 -1
- package/dist/types/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,182 +1,492 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @harnessio/forms
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A type-safe, configuration-driven form library for React applications.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`@harnessio/forms` provides a declarative approach to building forms using configuration schemas. The library generates forms from type-safe definitions, handles validation, and manages form state through React Hook Form integration.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Key Features:**
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
11
|
+
- 🎯 Type-safe form definitions with TypeScript generics
|
|
12
|
+
- 📋 Configuration-driven form generation
|
|
13
|
+
- ✅ Built-in validation with Zod integration
|
|
14
|
+
- 🔢 Tuple support for fixed-position arrays
|
|
15
|
+
- 🎨 Flexible input component system
|
|
16
|
+
- 🔄 Conditional visibility and dynamic forms
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Core Principles
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
- Forms are generated from configuration
|
|
21
|
+
- Each input defines its own configuration interface
|
|
22
|
+
- Validation schemas are part of input configuration
|
|
23
|
+
- Type safety through generic type parameters
|
|
24
|
+
- Extensible through custom input components
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
## Type System
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
The library uses generic type parameters for type safety:
|
|
29
|
+
|
|
30
|
+
### `IInputDefinition<TConfig, TValue, TInputType>`
|
|
31
|
+
|
|
32
|
+
- `TConfig` - Input-specific configuration type
|
|
33
|
+
- `TValue` - Value type for the input
|
|
34
|
+
- `TInputType` - String literal type for input type
|
|
35
|
+
|
|
36
|
+
### `InputProps<TValue, TConfig>`
|
|
37
|
+
|
|
38
|
+
- `TValue` - Value type for the input
|
|
39
|
+
- `TConfig` - Input-specific configuration type
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### 1. Define Input Types
|
|
44
|
+
|
|
45
|
+
Each input has a unique type identifier:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
24
48
|
export enum InputType {
|
|
25
|
-
text =
|
|
26
|
-
number =
|
|
27
|
-
checkbox =
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
text = 'text',
|
|
50
|
+
number = 'number',
|
|
51
|
+
checkbox = 'checkbox',
|
|
52
|
+
array = 'array',
|
|
53
|
+
list = 'list'
|
|
54
|
+
// ... more types
|
|
30
55
|
}
|
|
31
56
|
```
|
|
32
57
|
|
|
33
|
-
|
|
58
|
+
### 2. Create Input Components
|
|
34
59
|
|
|
35
|
-
|
|
36
|
-
[Text input example](./playground/src/implementation/inputs/text-input.tsx)
|
|
37
|
-
|
|
38
|
-
Minimal implementation:
|
|
60
|
+
Define input configuration and component:
|
|
39
61
|
|
|
40
62
|
```typescript
|
|
41
|
-
import { InputComponent, InputProps, useController
|
|
63
|
+
import { InputComponent, InputProps, useController } from '@harnessio/forms'
|
|
42
64
|
|
|
65
|
+
// 1. Define input config type
|
|
43
66
|
export interface TextInputConfig {
|
|
44
|
-
|
|
67
|
+
placeholder?: string
|
|
45
68
|
}
|
|
46
69
|
|
|
47
|
-
|
|
70
|
+
// 2. Define value type
|
|
71
|
+
export type TextInputValueType = string
|
|
72
|
+
|
|
73
|
+
// 3. Create component with typed props
|
|
74
|
+
function TextInputInternal(
|
|
75
|
+
props: InputProps<TextInputValueType, TextInputConfig>
|
|
76
|
+
): JSX.Element {
|
|
48
77
|
const { readonly, path, input } = props
|
|
49
|
-
const { label
|
|
78
|
+
const { label, required, inputConfig } = input
|
|
50
79
|
|
|
51
|
-
const { field
|
|
52
|
-
name: path
|
|
53
|
-
})
|
|
80
|
+
const { field } = useController({ name: path })
|
|
54
81
|
|
|
55
82
|
return (
|
|
56
83
|
<>
|
|
57
84
|
<label>{label}</label>
|
|
58
|
-
<input
|
|
85
|
+
<input
|
|
86
|
+
placeholder={inputConfig?.placeholder}
|
|
87
|
+
{...field}
|
|
88
|
+
disabled={readonly}
|
|
89
|
+
/>
|
|
59
90
|
</>
|
|
60
91
|
)
|
|
61
92
|
}
|
|
62
93
|
|
|
63
|
-
|
|
94
|
+
// 4. Register as InputComponent
|
|
95
|
+
export class TextInput extends InputComponent<TextInputValueType, TextInputConfig> {
|
|
64
96
|
public internalType = InputType.text
|
|
65
97
|
|
|
66
|
-
renderComponent(props: InputProps<
|
|
98
|
+
renderComponent(props: InputProps<TextInputValueType, TextInputConfig>): JSX.Element {
|
|
67
99
|
return <TextInputInternal {...props} />
|
|
68
100
|
}
|
|
69
101
|
}
|
|
70
|
-
|
|
71
102
|
```
|
|
72
103
|
|
|
73
|
-
|
|
104
|
+
**See examples:** [playground/src/implementation/inputs/](./playground/src/implementation/inputs/)
|
|
105
|
+
|
|
106
|
+
### 3. Register Inputs
|
|
74
107
|
|
|
75
|
-
Use InputFactory to register
|
|
108
|
+
Use `InputFactory` to register your input components:
|
|
76
109
|
|
|
77
|
-
```
|
|
110
|
+
```typescript
|
|
78
111
|
import { InputFactory } from '@harnessio/forms'
|
|
79
112
|
|
|
80
|
-
import {
|
|
113
|
+
import { NumberInput } from './inputs/number-input'
|
|
114
|
+
import { TextInput } from './inputs/text-input'
|
|
81
115
|
|
|
82
116
|
const inputComponentFactory = new InputFactory()
|
|
83
117
|
inputComponentFactory.registerComponent(new TextInput())
|
|
118
|
+
inputComponentFactory.registerComponent(new NumberInput())
|
|
84
119
|
|
|
85
120
|
export default inputComponentFactory
|
|
86
121
|
```
|
|
87
122
|
|
|
88
|
-
|
|
123
|
+
### 4. Define Form Schema
|
|
124
|
+
|
|
125
|
+
Create a type-safe form definition using `IFormDefinition`:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { z } from 'zod'
|
|
89
129
|
|
|
90
|
-
|
|
130
|
+
import { IFormDefinition } from '@harnessio/forms'
|
|
91
131
|
|
|
92
|
-
|
|
93
|
-
export const
|
|
132
|
+
// Basic form without custom input configs
|
|
133
|
+
export const basicFormDefinition: IFormDefinition = {
|
|
94
134
|
inputs: [
|
|
95
135
|
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
inputType: 'text',
|
|
137
|
+
path: 'name',
|
|
138
|
+
label: 'Name',
|
|
139
|
+
required: true,
|
|
140
|
+
validation: {
|
|
141
|
+
schema: z.string().min(3, 'Name must be at least 3 characters')
|
|
142
|
+
}
|
|
99
143
|
},
|
|
100
144
|
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
145
|
+
inputType: 'number',
|
|
146
|
+
path: 'age',
|
|
147
|
+
label: 'Age',
|
|
148
|
+
validation: {
|
|
149
|
+
schema: z.coerce.number().min(18, 'Must be 18 or older')
|
|
150
|
+
}
|
|
104
151
|
}
|
|
105
152
|
]
|
|
106
153
|
}
|
|
107
154
|
```
|
|
108
155
|
|
|
109
|
-
|
|
156
|
+
**With custom input configs for type safety:**
|
|
110
157
|
|
|
111
158
|
```typescript
|
|
112
|
-
// 1. Define
|
|
159
|
+
// 1. Define config types for each custom input
|
|
113
160
|
export interface ListInputConfig {
|
|
114
|
-
inputType:
|
|
161
|
+
inputType: 'list'
|
|
115
162
|
inputConfig: {
|
|
116
|
-
inputs:
|
|
163
|
+
inputs: IInputDefinition[]
|
|
117
164
|
layout?: 'grid' | 'default'
|
|
118
165
|
}
|
|
119
166
|
}
|
|
120
167
|
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
export interface TextInputConfig {
|
|
169
|
+
inputType: 'text'
|
|
170
|
+
inputConfig?: {
|
|
171
|
+
placeholder?: string
|
|
172
|
+
}
|
|
173
|
+
}
|
|
123
174
|
|
|
124
|
-
//
|
|
125
|
-
export type InputConfigType =
|
|
126
|
-
| ListInputConfig
|
|
127
|
-
| TextInputConfig ...
|
|
175
|
+
// 2. Create union type
|
|
176
|
+
export type InputConfigType = ListInputConfig | TextInputConfig
|
|
128
177
|
|
|
129
|
-
//
|
|
178
|
+
// 3. Use union type for type-safe form definition
|
|
130
179
|
export const formDefinition: IFormDefinition<InputConfigType> = {
|
|
131
|
-
inputs: [
|
|
180
|
+
inputs: [
|
|
181
|
+
{
|
|
182
|
+
inputType: 'text',
|
|
183
|
+
path: 'username',
|
|
184
|
+
label: 'Username',
|
|
185
|
+
inputConfig: {
|
|
186
|
+
placeholder: 'Enter username' // ✅ Type-safe
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
inputType: 'list',
|
|
191
|
+
path: 'items',
|
|
192
|
+
label: 'Items',
|
|
193
|
+
inputConfig: {
|
|
194
|
+
inputs: [...], // ✅ Type-safe
|
|
195
|
+
layout: 'grid'
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
]
|
|
132
199
|
}
|
|
133
200
|
```
|
|
134
201
|
|
|
135
|
-
|
|
202
|
+
### 5. Render Form
|
|
203
|
+
|
|
204
|
+
Use `RootForm` and `RenderForm` components:
|
|
136
205
|
|
|
137
|
-
|
|
206
|
+
```typescript
|
|
207
|
+
import { RootForm, RenderForm, useZodValidationResolver } from '@harnessio/forms'
|
|
208
|
+
import { inputComponentFactory } from './factory'
|
|
209
|
+
import { formDefinition } from './form-definition'
|
|
210
|
+
|
|
211
|
+
function MyForm() {
|
|
212
|
+
const resolver = useZodValidationResolver(formDefinition)
|
|
138
213
|
|
|
139
|
-
|
|
214
|
+
const handleSubmit = (values: AnyFormValue) => {
|
|
215
|
+
console.log('Form values:', values)
|
|
216
|
+
}
|
|
140
217
|
|
|
141
|
-
|
|
142
|
-
<RootForm
|
|
143
|
-
|
|
144
|
-
|
|
218
|
+
return (
|
|
219
|
+
<RootForm
|
|
220
|
+
defaultValues={{}}
|
|
221
|
+
onSubmit={handleSubmit}
|
|
222
|
+
resolver={resolver}
|
|
223
|
+
>
|
|
224
|
+
{rootForm => (
|
|
225
|
+
<>
|
|
226
|
+
<RenderForm
|
|
227
|
+
factory={inputComponentFactory}
|
|
228
|
+
inputs={formDefinition}
|
|
229
|
+
/>
|
|
230
|
+
<button onClick={() => rootForm.submitForm()}>
|
|
231
|
+
Submit
|
|
232
|
+
</button>
|
|
233
|
+
</>
|
|
234
|
+
)}
|
|
235
|
+
</RootForm>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
145
238
|
```
|
|
146
239
|
|
|
147
|
-
|
|
240
|
+
## Tuple Support (Fixed-Position Arrays)
|
|
148
241
|
|
|
149
|
-
|
|
242
|
+
The library supports **tuple paths** using numeric indices for fixed-position arrays where each position can have its own schema.
|
|
150
243
|
|
|
151
|
-
When
|
|
244
|
+
### When to Use Tuples
|
|
152
245
|
|
|
153
|
-
|
|
154
|
-
- requiredSchema
|
|
155
|
-
- default - if validation is not found, it uses the default built-in validation.
|
|
246
|
+
✅ Use tuples for:
|
|
156
247
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
248
|
+
- Fixed number of elements (e.g., `[x, y]` coordinates)
|
|
249
|
+
- Position-specific schemas (e.g., `[primary, backup]` servers)
|
|
250
|
+
- Elements that shouldn't be added/removed dynamically
|
|
251
|
+
|
|
252
|
+
❌ Use `array` or `list` input types for:
|
|
253
|
+
|
|
254
|
+
- Variable number of items
|
|
255
|
+
- Add/remove functionality
|
|
256
|
+
- Same schema for all items
|
|
257
|
+
|
|
258
|
+
### Tuple Path Syntax
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// Coordinates: [10, 20]
|
|
262
|
+
{
|
|
263
|
+
inputs: [
|
|
264
|
+
{ inputType: 'number', path: 'coordinates.0', label: 'X' },
|
|
265
|
+
{ inputType: 'number', path: 'coordinates.1', label: 'Y' }
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Nested objects: [{ name: 'Primary', url: '...' }, { name: 'Backup', url: '...' }]
|
|
270
|
+
{
|
|
271
|
+
inputs: [
|
|
272
|
+
{ inputType: 'text', path: 'servers.0.name', label: 'Primary Server' },
|
|
273
|
+
{ inputType: 'text', path: 'servers.0.url', validation: { schema: z.string().url() } },
|
|
274
|
+
{ inputType: 'text', path: 'servers.1.name', label: 'Backup Server' },
|
|
275
|
+
{ inputType: 'text', path: 'servers.1.url', validation: { schema: z.string().url() } }
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Different schemas per position
|
|
280
|
+
{
|
|
281
|
+
inputs: [
|
|
282
|
+
{ inputType: 'text', path: 'owners.0.email', validation: { schema: z.string().email() } },
|
|
283
|
+
{ inputType: 'select', path: 'owners.1.role', inputConfig: { options: [...] } }
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Sparse indices: ['Critical', null, null, null, null, 'Low']
|
|
288
|
+
{
|
|
289
|
+
inputs: [
|
|
290
|
+
{ inputType: 'text', path: 'priorities.0', label: 'High Priority' },
|
|
291
|
+
{ inputType: 'text', path: 'priorities.5', label: 'Low Priority' }
|
|
292
|
+
]
|
|
293
|
+
}
|
|
167
294
|
```
|
|
168
295
|
|
|
169
|
-
|
|
170
|
-
|
|
296
|
+
**Example:** See [playground/src/examples/tuple-example/](./playground/src/examples/tuple-example/)
|
|
297
|
+
|
|
298
|
+
## Validation
|
|
171
299
|
|
|
172
|
-
|
|
173
|
-
// Required message config example
|
|
300
|
+
The library integrates with Zod for schema validation.
|
|
174
301
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
302
|
+
### Per-Input Validation
|
|
303
|
+
|
|
304
|
+
Define validation schemas directly in input definitions:
|
|
305
|
+
|
|
306
|
+
**Static validation:**
|
|
307
|
+
```typescript
|
|
308
|
+
{
|
|
309
|
+
inputType: 'text',
|
|
310
|
+
path: 'email',
|
|
311
|
+
label: 'Email',
|
|
312
|
+
required: true,
|
|
313
|
+
validation: {
|
|
314
|
+
schema: z.string().email('Invalid email format')
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Dynamic validation (depends on other form values):**
|
|
320
|
+
```typescript
|
|
321
|
+
{
|
|
322
|
+
inputType: 'text',
|
|
323
|
+
path: 'password',
|
|
324
|
+
label: 'Password',
|
|
325
|
+
required: true
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
inputType: 'text',
|
|
329
|
+
path: 'confirmPassword',
|
|
330
|
+
label: 'Confirm Password',
|
|
331
|
+
required: true,
|
|
332
|
+
validation: {
|
|
333
|
+
schema: (values) =>
|
|
334
|
+
z.string().refine(
|
|
335
|
+
(val) => val === values.password,
|
|
336
|
+
{ message: 'Passwords must match' }
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Global Validation Configuration
|
|
343
|
+
|
|
344
|
+
Configure validation globally using `useZodValidationResolver`:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const resolver = useZodValidationResolver(
|
|
348
|
+
formDefinition,
|
|
349
|
+
{
|
|
350
|
+
// Custom required message for all inputs
|
|
351
|
+
requiredMessage: 'This field is required',
|
|
352
|
+
|
|
353
|
+
// Custom required message per input type
|
|
354
|
+
requiredMessagePerInput: {
|
|
355
|
+
text: 'Text field is required',
|
|
356
|
+
number: 'Please enter a number'
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
// Custom required schema per input type
|
|
360
|
+
requiredSchemaPerInput: {
|
|
361
|
+
text: z.string().min(1),
|
|
362
|
+
number: z.number(),
|
|
363
|
+
myCustomInput: z.custom(...)
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
// Global validation function
|
|
367
|
+
globalValidation: (value, input, metadata) => {
|
|
368
|
+
// Custom validation logic
|
|
369
|
+
return { continue: true }
|
|
370
|
+
}
|
|
180
371
|
},
|
|
181
|
-
|
|
372
|
+
metadata
|
|
373
|
+
)
|
|
182
374
|
```
|
|
375
|
+
|
|
376
|
+
**Validation resolution order for required fields:**
|
|
377
|
+
|
|
378
|
+
1. `requiredSchemaPerInput[inputType]`
|
|
379
|
+
2. `requiredSchema`
|
|
380
|
+
3. Built-in default validation
|
|
381
|
+
|
|
382
|
+
## Additional Features
|
|
383
|
+
|
|
384
|
+
### Conditional Visibility
|
|
385
|
+
|
|
386
|
+
Control input visibility based on form values:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
{
|
|
390
|
+
inputType: 'select',
|
|
391
|
+
path: 'authType',
|
|
392
|
+
label: 'Authentication Type'
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
inputType: 'text',
|
|
396
|
+
path: 'apiToken',
|
|
397
|
+
label: 'API Token',
|
|
398
|
+
isVisible: (values) => values.authType === 'token'
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Default Values
|
|
403
|
+
|
|
404
|
+
Set default values using the `default` property or `collectDefaultValues`:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { collectDefaultValues } from '@harnessio/forms'
|
|
408
|
+
|
|
409
|
+
// In input definition
|
|
410
|
+
{
|
|
411
|
+
inputType: 'text',
|
|
412
|
+
path: 'name',
|
|
413
|
+
default: 'John Doe'
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Collect all defaults from form definition
|
|
417
|
+
const defaultValues = collectDefaultValues(formDefinition)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Value Transformers
|
|
421
|
+
|
|
422
|
+
Transform values between data model and form state:
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
{
|
|
426
|
+
inputType: 'text',
|
|
427
|
+
path: 'tags',
|
|
428
|
+
label: 'Tags',
|
|
429
|
+
// Transform incoming data (array) to form display (string)
|
|
430
|
+
inputTransform: (value: string[]) => ({
|
|
431
|
+
value: value.join(', ')
|
|
432
|
+
}),
|
|
433
|
+
// Transform form value (string) back to data model (array)
|
|
434
|
+
outputTransform: (value: string) => ({
|
|
435
|
+
value: value.split(',').map(s => s.trim())
|
|
436
|
+
})
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Transformer functions:**
|
|
441
|
+
- `inputTransform` - Converts data model → form state (runs when data is loaded)
|
|
442
|
+
- `outputTransform` - Converts form state → data model (runs on submit)
|
|
443
|
+
- Both must return `{ value: any }` or `undefined`
|
|
444
|
+
- Can chain multiple transformers by providing an array
|
|
445
|
+
|
|
446
|
+
## API Reference
|
|
447
|
+
|
|
448
|
+
### Core Types
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
// Input definition with generic parameters
|
|
452
|
+
IInputDefinition<TConfig = unknown, TValue = unknown, TInputType extends string = string>
|
|
453
|
+
|
|
454
|
+
// Input component props
|
|
455
|
+
InputProps<TValue = unknown, TConfig = unknown>
|
|
456
|
+
|
|
457
|
+
// Form definition
|
|
458
|
+
IFormDefinition<TConfig = unknown>
|
|
459
|
+
|
|
460
|
+
// Value types
|
|
461
|
+
AnyFormValue // any form value type
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Main Components
|
|
465
|
+
|
|
466
|
+
- `RootForm` - Root form component with React Hook Form integration
|
|
467
|
+
- `RenderForm` - Renders inputs from form definition
|
|
468
|
+
- `InputFactory` - Registry for input components
|
|
469
|
+
- `InputComponent<TValue, TConfig>` - Base class for input components
|
|
470
|
+
|
|
471
|
+
### Hooks
|
|
472
|
+
|
|
473
|
+
- `useZodValidationResolver(definition, config?, metadata?)` - Creates Zod validation resolver
|
|
474
|
+
- `useController(options)` - React Hook Form controller hook
|
|
475
|
+
|
|
476
|
+
### Utilities
|
|
477
|
+
|
|
478
|
+
- `collectDefaultValues(definition)` - Extracts default values from form definition
|
|
479
|
+
|
|
480
|
+
## Examples
|
|
481
|
+
|
|
482
|
+
Complete working examples can be found in [playground/src/examples/](./playground/src/examples/):
|
|
483
|
+
|
|
484
|
+
- [Basic Example](./playground/src/examples/basic-example/) - Simple form with text and number inputs
|
|
485
|
+
- [Tuple Example](./playground/src/examples/tuple-example/) - Fixed-position arrays
|
|
486
|
+
- [Array Example](./playground/src/examples/array-example/) - Dynamic arrays
|
|
487
|
+
- [List Example](./playground/src/examples/list-example/) - Dynamic object lists
|
|
488
|
+
- [Conditional Example](./playground/src/examples/conditional-example/) - Conditional visibility
|
|
489
|
+
|
|
490
|
+
## License
|
|
491
|
+
|
|
492
|
+
See the main repository license.
|