react-form-dto 1.0.3 → 1.0.5
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 +142 -0
- package/dist/components/FormBuilder.d.ts +17 -11
- package/dist/example/Demo.d.ts +1 -0
- package/dist/form/FormContext.d.ts +41 -0
- package/dist/form/FormProvider.d.ts +8 -0
- package/dist/form/useFieldArray.d.ts +10 -0
- package/dist/form/useForm.d.ts +10 -0
- package/dist/form/useWatch.d.ts +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useFormBuilderController.d.ts +16 -0
- package/dist/hooks/useFormDTO.d.ts +21 -0
- package/dist/index.d.ts +8 -1
- package/dist/react-form-dto.es.js +570 -362
- package/dist/react-form-dto.umd.js +2 -2
- package/dist/stories/Demo.stories.d.ts +16 -0
- package/dist/stories/FormBuilder.stories.d.ts +8 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -146,6 +146,148 @@ return (
|
|
|
146
146
|
|
|
147
147
|
---
|
|
148
148
|
|
|
149
|
+
## useFormBuilderController – Controlled, Auto‑Rendered Forms
|
|
150
|
+
|
|
151
|
+
`useFormBuilderController` is a hook that wraps `FormBuilder` and gives you:
|
|
152
|
+
|
|
153
|
+
- An auto-rendering `Form` component (no manual mapping of sections/fields)
|
|
154
|
+
- An imperative controller API for values, errors, and validation
|
|
155
|
+
|
|
156
|
+
It combines the convenience of `FormBuilder` with the programmatic control of `useFormBuilder`.
|
|
157
|
+
|
|
158
|
+
### When to Use
|
|
159
|
+
|
|
160
|
+
- You want the form UI to be generated entirely from a DTO
|
|
161
|
+
- You need to:
|
|
162
|
+
- Run validation on submit
|
|
163
|
+
- Read values/errors from outside the form
|
|
164
|
+
- Prefill fields programmatically
|
|
165
|
+
- Plug in custom field renderers
|
|
166
|
+
|
|
167
|
+
### Basic Example
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { useFormBuilderController } from 'react-form-dto';
|
|
171
|
+
import { myFormDTO } from './myFormDTO';
|
|
172
|
+
|
|
173
|
+
function MyFormWithController() {
|
|
174
|
+
const formController = useFormBuilderController({
|
|
175
|
+
dto: myFormDTO,
|
|
176
|
+
locale: 'en',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
|
|
182
|
+
const errors = formController.validateAll();
|
|
183
|
+
const hasErrors = Object.values(errors).some(
|
|
184
|
+
(err) => err && err.length > 0,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (hasErrors) {
|
|
188
|
+
console.log('Validation errors:', errors);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log('Form submitted:', formController.getValues());
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<form onSubmit={handleSubmit}>
|
|
197
|
+
{/* Auto-renders the full form from the DTO */}
|
|
198
|
+
<formController.Form />
|
|
199
|
+
<button type="submit">Submit</button>
|
|
200
|
+
</form>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### API Overview
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const controller = useFormBuilderController({
|
|
209
|
+
dto: myFormDTO,
|
|
210
|
+
locale: 'en',
|
|
211
|
+
renderers: {
|
|
212
|
+
// optional: override field types
|
|
213
|
+
text: MyTextField,
|
|
214
|
+
},
|
|
215
|
+
handleChangeCallback: (id, value) => {
|
|
216
|
+
// optional: side effects on every field change
|
|
217
|
+
console.log(id, value);
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Reading and validating
|
|
222
|
+
controller.getValues(); // Record<string, any>
|
|
223
|
+
controller.getErrors(); // Record<string, string | null>
|
|
224
|
+
controller.validateAll(); // Record<string, string[] | null>
|
|
225
|
+
controller.validateField('id'); // string[]
|
|
226
|
+
|
|
227
|
+
// Programmatic updates
|
|
228
|
+
controller.handleChange('firstName', 'Jane');
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Example with Prefill and Custom Renderers
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { useFormBuilderController } from 'react-form-dto';
|
|
235
|
+
import { myFormDTO } from './myFormDTO';
|
|
236
|
+
import { TextInput, SelectInput } from './customFields';
|
|
237
|
+
|
|
238
|
+
const renderers = {
|
|
239
|
+
text: TextInput,
|
|
240
|
+
select: SelectInput,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
function AdvancedForm() {
|
|
244
|
+
const formController = useFormBuilderController({
|
|
245
|
+
dto: myFormDTO,
|
|
246
|
+
locale: 'en',
|
|
247
|
+
renderers,
|
|
248
|
+
handleChangeCallback: (id, value) => {
|
|
249
|
+
// sync with external store / analytics, etc.
|
|
250
|
+
console.log('Changed:', id, value);
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const prefill = () => {
|
|
255
|
+
formController.handleChange('firstName', 'John');
|
|
256
|
+
formController.handleChange('lastName', 'Doe');
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<>
|
|
261
|
+
<button type="button" onClick={prefill}>
|
|
262
|
+
Prefill
|
|
263
|
+
</button>
|
|
264
|
+
|
|
265
|
+
<form
|
|
266
|
+
onSubmit={(e) => {
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
const errors = formController.validateAll();
|
|
269
|
+
const hasErrors = Object.values(errors).some(
|
|
270
|
+
(err) => err && err.length > 0,
|
|
271
|
+
);
|
|
272
|
+
if (!hasErrors) {
|
|
273
|
+
console.log('Values:', formController.getValues());
|
|
274
|
+
}
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
<formController.Form />
|
|
278
|
+
<button type="submit">Submit</button>
|
|
279
|
+
</form>
|
|
280
|
+
</>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
> For the full hook reference, see
|
|
286
|
+
> **Docs → Hooks → `useFormBuilderController`**:
|
|
287
|
+
> `docs/api/hooks/useFormBuilderController.md`
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
149
291
|
## 📋 Example Form rendered
|
|
150
292
|
|
|
151
293
|

|
|
@@ -29,20 +29,26 @@ type FormBuilderProps = {
|
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
31
|
* FormBuilder component that dynamically renders a form based on a FormDTO definition.
|
|
32
|
-
* Supports custom field renderers, validation, and programmatic access via ref.
|
|
33
32
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* **Standalone mode** (existing behaviour, unchanged):
|
|
34
|
+
* Manage state internally and expose `getValues`, `validateAll`, etc. via a ref.
|
|
35
|
+
*
|
|
36
|
+
* **Context mode** (new):
|
|
37
|
+
* When rendered inside a `<FormProvider>` the component reads values from
|
|
38
|
+
* context and writes back through `setValue`. Errors are also stored in
|
|
39
|
+
* context so they only appear after the field is touched or `trigger()` /
|
|
40
|
+
* `handleSubmit()` is called — matching react-hook-form UX conventions.
|
|
36
41
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* const values = formRef.current?.getValues();
|
|
41
|
-
* // Submit form values
|
|
42
|
-
* }
|
|
43
|
-
* };
|
|
42
|
+
* @example Standalone
|
|
43
|
+
* const formRef = useRef<FormBuilderHandle>(null);
|
|
44
|
+
* <FormBuilder ref={formRef} dto={myFormDTO} />
|
|
44
45
|
*
|
|
45
|
-
*
|
|
46
|
+
* @example With FormProvider (useFormDTO)
|
|
47
|
+
* const form = useFormDTO(myFormDTO);
|
|
48
|
+
* <FormProvider value={form}>
|
|
49
|
+
* <FormBuilder dto={myFormDTO} />
|
|
50
|
+
* <button onClick={form.handleSubmit(onSubmit)}>Submit</button>
|
|
51
|
+
* </FormProvider>
|
|
46
52
|
*/
|
|
47
53
|
export declare const FormBuilder: React.ForwardRefExoticComponent<FormBuilderProps & React.RefAttributes<FormBuilderHandle>>;
|
|
48
54
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ReactHookFormDemo(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export type FieldError = string | null | undefined;
|
|
3
|
+
export interface FieldArrayHelpers {
|
|
4
|
+
fields: any[];
|
|
5
|
+
append: (value: any) => void;
|
|
6
|
+
remove: (index: number) => void;
|
|
7
|
+
prepend: (value: any) => void;
|
|
8
|
+
swap: (a: number, b: number) => void;
|
|
9
|
+
insert: (index: number, value: any) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface FormContextType<T extends object = any> {
|
|
12
|
+
values: T;
|
|
13
|
+
errors: {
|
|
14
|
+
[K in keyof T]?: FieldError;
|
|
15
|
+
};
|
|
16
|
+
touchedFields: {
|
|
17
|
+
[K in keyof T]?: boolean;
|
|
18
|
+
};
|
|
19
|
+
register: (name: string, options?: {
|
|
20
|
+
validate?: (value: any) => FieldError;
|
|
21
|
+
}) => {
|
|
22
|
+
name: string;
|
|
23
|
+
ref: (el: HTMLInputElement | null) => void;
|
|
24
|
+
onChange: (e: React.ChangeEvent<any>) => void;
|
|
25
|
+
onBlur: () => void;
|
|
26
|
+
};
|
|
27
|
+
setValue: (name: string, value: any, shouldValidate?: boolean) => void;
|
|
28
|
+
getValues: () => T;
|
|
29
|
+
setError: (name: string, error: FieldError) => void;
|
|
30
|
+
clearError: (name: string) => void;
|
|
31
|
+
handleSubmit: (cb: (data: T, event?: React.BaseSyntheticEvent) => void) => (e?: React.SyntheticEvent) => void;
|
|
32
|
+
reset: (nextValues?: Partial<T>) => void;
|
|
33
|
+
trigger: (name?: string) => boolean;
|
|
34
|
+
/** Self-reference so child components can do `const { control } = useFormContext()` and spread `control.register(...)` */
|
|
35
|
+
control: FormContextType<T>;
|
|
36
|
+
watch: (name?: string | string[]) => any;
|
|
37
|
+
}
|
|
38
|
+
export declare const FormContext: React.Context<FormContextType<any> | undefined>;
|
|
39
|
+
export declare function useFormContext<T extends object = any>(): FormContextType<T>;
|
|
40
|
+
/** Returns the context value without throwing — useful for optional context bridges. */
|
|
41
|
+
export declare function useOptionalFormContext<T extends object = any>(): FormContextType<T> | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { FormContextType } from './FormContext';
|
|
3
|
+
type Props<T extends object> = {
|
|
4
|
+
value: FormContextType<T>;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare function FormProvider<T extends object = {}>({ value, children, }: Props<T>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function useFieldArray<T extends object = any>(props: {
|
|
2
|
+
name: keyof T;
|
|
3
|
+
}): {
|
|
4
|
+
fields: any[];
|
|
5
|
+
append: (value: any) => void;
|
|
6
|
+
prepend: (value: any) => void;
|
|
7
|
+
remove: (index: number) => void;
|
|
8
|
+
swap: (iA: number, iB: number) => void;
|
|
9
|
+
insert: (index: number, value: any) => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FormContextType, FieldError } from './FormContext';
|
|
2
|
+
type FieldValidation = (value: any) => FieldError;
|
|
3
|
+
type UseFormOptions<T> = {
|
|
4
|
+
defaultValues?: Partial<T>;
|
|
5
|
+
validate?: {
|
|
6
|
+
[K in keyof T]?: FieldValidation;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export declare function useForm<T extends object = any>(options?: UseFormOptions<T>): FormContextType<T>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useWatch<T extends object = any>(name: keyof T | (keyof T)[]): any;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FormDTO } from '../types';
|
|
2
|
+
type FormBuilderControllerProps = {
|
|
3
|
+
dto: FormDTO;
|
|
4
|
+
locale: string;
|
|
5
|
+
renderers?: Record<string, React.ComponentType<any>>;
|
|
6
|
+
handleChangeCallback?: (id: string, val: any) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare const useFormBuilderController: (props: FormBuilderControllerProps) => {
|
|
9
|
+
getValues: () => Record<string, any>;
|
|
10
|
+
getErrors: () => Record<string, string | null>;
|
|
11
|
+
validateAll: () => Record<string, string[]>;
|
|
12
|
+
validateField: (id: string) => string[];
|
|
13
|
+
handleChange: (id: string, val: any) => void | undefined;
|
|
14
|
+
Form: () => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FormDTO } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a react-hook-form–style form bound to a FormDTO.
|
|
4
|
+
*
|
|
5
|
+
* Default values are extracted from `field.defaultValue`.
|
|
6
|
+
* Validation rules come directly from `field.validations`.
|
|
7
|
+
*
|
|
8
|
+
* Use together with `<FormProvider>` and `<FormBuilder>` to get the
|
|
9
|
+
* full hook-form experience without managing a ref:
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const form = useFormDTO(myDTO);
|
|
13
|
+
*
|
|
14
|
+
* <FormProvider value={form}>
|
|
15
|
+
* <FormBuilder dto={myDTO} />
|
|
16
|
+
* <button onClick={form.handleSubmit(onSubmit)}>Submit</button>
|
|
17
|
+
* </FormProvider>
|
|
18
|
+
*/
|
|
19
|
+
export declare function useFormDTO<T extends object = Record<string, any>>(dto: FormDTO, options?: {
|
|
20
|
+
locale?: string;
|
|
21
|
+
}): import('..').FormContextType<T>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export { FormBuilder, Section, Field, TextAreaInput, AutoCompleteField, MultiAutoCompleteField, RadioInput, SelectInput, TextInput, CheckBoxInput, } from './components';
|
|
2
|
+
export type { FormBuilderHandle } from './components/FormBuilder';
|
|
3
|
+
export { useFormBuilder, useFormBuilderController, useFormDTO } from './hooks';
|
|
4
|
+
export { useForm } from './form/useForm';
|
|
5
|
+
export { useFieldArray } from './form/useFieldArray';
|
|
6
|
+
export { useWatch } from './form/useWatch';
|
|
7
|
+
export { FormProvider } from './form/FormProvider';
|
|
8
|
+
export { useFormContext, useOptionalFormContext, } from './form/FormContext';
|
|
9
|
+
export type { FormContextType, FieldError, FieldArrayHelpers } from './form/FormContext';
|
|
2
10
|
export { validateAll, validateField, validationRules } from './utils';
|
|
3
11
|
export type { FieldDTO, SectionDTO, FormDTO, InputType, Validations, Condition as VisibleWhenCondition, FieldCondition as VisibleWhenFieldCondition, ConditionGroup as VisibleWhenConditionGroup, } from './types';
|
|
4
|
-
export { useFormBuilder } from './hooks';
|