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 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
  ![Form Example](./example.png)
@@ -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
- * @example
35
- * const formRef = useRef<FormBuilderHandle>(null);
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
- * const handleSubmit = () => {
38
- * const errors = formRef.current?.validateAll();
39
- * if (Object.keys(errors || {}).length === 0) {
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
- * <FormBuilder ref={formRef} dto={myFormDTO} renderers={customRenderers} />
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;
@@ -1 +1,3 @@
1
1
  export * from './useFormBuilder';
2
+ export * from './useFormBuilderController';
3
+ export * from './useFormDTO';
@@ -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';