@dynamic-field-kit/react 1.3.0 → 1.5.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/CHANGELOG.md ADDED
@@ -0,0 +1,180 @@
1
+ # @dynamic-field-kit/react
2
+
3
+ ## 1.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Form state hooks, schema adapters, wizard engine, DevTools and extended renderers across all four packages, with type declarations that now resolve correctly under node16 and nodenext.
8
+ - d91c737: Add form state hooks, schema adapters, wizard engine, DevTools and extended renderers.
9
+
10
+ **Core**
11
+
12
+ - `zodValidator`, `yupValidator`, `valibotValidator` / `standardSchemaValidator`. Adapters parse **synchronously** so their result is usable by the synchronous `validateFields`; schemas with async refinements or async `.test()` rules return a Promise and must be validated through `validateFieldsAsync`.
13
+ - Adapters take an explicit `{ target: 'form' | 'field' }` option. `'form'` (the default) parses the form data object; `'field'` parses a single scalar value. The field-name shorthand — `zodValidator(schema, 'email')` — is unchanged.
14
+ - Wizard engine: `createWizardState`, `validateStep`, `canGoNext`, `canGoPrev`, plus the navigation the engine needs to be usable - `goNext`, `goPrev`, `goToStep`, `markStepCompleted`, `isStepCompleted`. `goNext` records the step it leaves, so `completedSteps` is actually maintained.
15
+ - Group array helpers: `moveGroupItem`, `swapGroupItems`, `insertGroupItem`, `focusFirstInvalidField`.
16
+ - `switch` is a first-class field type: it had a shipped renderer in react and vue but no `FieldTypeMap` entry, so `type: 'switch'` did not typecheck.
17
+
18
+ **React / Vue / Angular**
19
+
20
+ - `useDynamicForm` (React, Vue) and `createDynamicFormStore` (Angular Signals) now expose the same surface, including `isSubmitting` and `isSubmitted`.
21
+ - `handleSubmit(onValid, onInvalid)` returns a submit handler in every framework and calls `preventDefault` on the event it receives.
22
+ - Default HTML5 renderers for `radio`, `range`, `file`, `date`, `time`, `datetime-local` and `switch`.
23
+ - `DynamicFormDevTools` overlay for inspecting form data, errors, metadata and field descriptions, with an error-count badge in all three frameworks.
24
+ - `MultiFieldInput` reports blur through `onBlurField` (an `@Output` in Angular), so a form store's `handleBlur` / `touched` / `validateOnBlur` can be wired to it. Vue and Angular previously had no blur plumbing at all.
25
+
26
+ ### Patch Changes
27
+
28
+ - c3faa51: Document the v1.5 APIs in each package README: form state, schema adapters, the wizard engine, default renderers, DevTools and blur wiring. README ships in the npm tarball, so this reaches package pages only through a release.
29
+ - 75447a6: Close the remaining documentation gaps in each package README, so every public
30
+ export is described somewhere. README ships in the npm tarball, so this reaches
31
+ the package pages only through a release.
32
+
33
+ - **Sync vs async validation** is now spelled out in core, with the consequence
34
+ that was previously implicit: `validateField` / `validateFields` cannot await,
35
+ so a `validate` hook returning a Promise is treated as valid on the sync path.
36
+ `useDynamicForm` and `createDynamicFormStore` validate synchronously
37
+ (including on submit), so async rules have to run through
38
+ `validateFieldsAsync` explicitly. Each adapter README repeats the caveat and
39
+ links to the core section.
40
+ - Document `validateFieldAsync`, `validateFieldsAsync`, `resolveOptions` and
41
+ `validators` in the react, vue and angular export lists, separated from each
42
+ adapter's own exports so it is clear they are core re-exports.
43
+ - Document the core types that appear in every signature but had no definition
44
+ in the README: `Properties`, `ValidatorFn`, `FieldValidatorResult`,
45
+ `FieldValidatorFunction`, and `FormStep` alongside `WizardState`.
46
+ - Angular: document `layoutRegistry` / `LayoutRegistry`, the `ColumnLayout` /
47
+ `RowLayout` / `GridLayout` components and `BaseInputComponent`, with a custom
48
+ layout example. Its layout registry holds standalone components rather than
49
+ render functions, which is the one place the three adapters genuinely differ,
50
+ and it was the only adapter not documenting its registry at all.
51
+ - Rename angular's `## What it exports` to `## Exports` to match react and vue.
52
+ - Link the demo sub-routes: react's enterprise-features and wizard pages, and
53
+ from core the per-framework demos plus the wizard it documents.
54
+
55
+ - e10044b: Fix the type declarations consumers get under `moduleResolution: node16` /
56
+ `nodenext`, and fill in the npm metadata the package pages never had.
57
+
58
+ Every package declared a single `types` target for both module formats while
59
+ shipping two sets of declarations. `arethetypeswrong` on the built tarballs
60
+ reported core and react "masquerading as CJS" and vue "masquerading as ESM":
61
+ an ESM import resolved to the CommonJS declaration file, and vice versa. The
62
+ `exports` maps now declare `types` per condition, so each format resolves to
63
+ the declarations that describe it.
64
+
65
+ Angular was worse than a mismatch - it did not resolve at all. Its entry is an
66
+ `.mjs` bundle, so TypeScript reads its declarations in ESM mode, where the
67
+ extensionless `export * from './public-api'` that ng-packagr generates is error
68
+ TS2834. The failure is in `dist/index.d.ts`, the first file a consumer reaches,
69
+ so the package was unusable on node16 resolution. A `postbuild` step now adds
70
+ the explicit `.js` extension to relative specifiers in the emitted `.d.ts`
71
+ files, resolving each against the build output so a directory import becomes
72
+ `./layout/index.js` rather than a broken `./layout.js`. Angular also gains an
73
+ `exports` map and `"type": "module"`, which it needs to describe itself
74
+ honestly. All four packages are clean on all four resolution modes now, except
75
+ angular's `require()`, which is ESM-only by nature - as `@angular/core` is.
76
+
77
+ Also in the tarballs: `CHANGELOG.md` now ships, so the npm page has release
78
+ history, and angular no longer ships a second copy of its README and LICENSE
79
+ that ng-packagr had copied into `dist`. Every package gains `homepage`, `bugs`
80
+ and `repository.directory`, so npm links to the right README and issue tracker
81
+ instead of nothing, and `publishConfig.provenance`, so each published tarball
82
+ is signed and linked back to the workflow run that built it.
83
+
84
+ - 75447a6: Let consumer bundlers drop the parts of the adapters an app does not use.
85
+
86
+ Components were declared as bare top-level calls — `defineComponent({...})` in
87
+ vue, `React.memo(...)` in react. A bundler cannot prove such a call is
88
+ side-effect free, so it has to evaluate it even when the result is unused, which
89
+ kept every default renderer and every component in an app's bundle no matter how
90
+ little of the package it imported. Marking those calls `/* @__PURE__ */` makes
91
+ them droppable. Measured with esbuild, minified, framework external:
92
+
93
+ | App imports | Before | After |
94
+ | -------------------------- | -------- | --------------- |
95
+ | react: one core helper | 11,149 B | 3,258 B (−71%) |
96
+ | react: `DynamicInput` only | 11,150 B | 6,830 B (−39%) |
97
+ | react: everything | 17,601 B | unchanged |
98
+ | vue: one core helper | 17,379 B | 13,934 B (−20%) |
99
+ | vue: `DynamicInput` only | 17,379 B | 13,934 B (−20%) |
100
+ | vue: everything | 20,530 B | unchanged |
101
+
102
+ Apps that use the whole surface are unchanged, which is the expected result —
103
+ there is nothing to drop. The shipped `dist` grows slightly (react +0.03 KB, vue
104
+ +0.29 KB) because the annotations are comments in the bundle; the trade is a
105
+ bigger published file for a smaller consumer bundle.
106
+
107
+ `@dynamic-field-kit/core` now declares `"sideEffects": false`. It has no
108
+ top-level execution at all — the only module-scope work is `new FieldRegistry()`
109
+ assigned to an export — so the claim is accurate, and it lets bundlers that rely
110
+ on the flag rather than their own analysis skip core entirely when it is unused.
111
+ The adapters deliberately do not set it: their entry side-effect-imports the
112
+ default layouts in order to register them.
113
+
114
+ - 75447a6: Stop publishing sourcemaps, roughly halving what each package installs.
115
+
116
+ tsup was emitting sourcemaps with `sourcesContent`, so every `.map` carried a
117
+ full copy of the TypeScript source. That is what made them work at all — `files`
118
+ only publishes `dist`, so a map referencing `../src/*.ts` would otherwise
119
+ resolve to nothing — but it also made them about half of each tarball, shipped
120
+ to every consumer on every install.
121
+
122
+ | Package | Unpacked | Tarball | Files |
123
+ | ------- | ----------------------- | --------------------- | ----- |
124
+ | core | 157.0 → 83.0 KB (−47%) | 33.3 → 17.5 KB (−47%) | 8 → 6 |
125
+ | react | 207.6 → 90.1 KB (−57%) | 46.8 → 19.2 KB (−59%) | 8 → 6 |
126
+ | vue | 250.4 → 109.4 KB (−56%) | 47.6 → 20.5 KB (−57%) | 8 → 6 |
127
+
128
+ Nothing that ends up in an application bundle changes — sourcemaps never do.
129
+ What changes is install size, and the ability to step into the library's
130
+ TypeScript source while debugging a consuming app.
131
+
132
+ This is a deliberate trade, not a free win: the maps worked. Each package's
133
+ `tsup.config.ts` carries the reasoning next to a `sourcemap: false` that is one
134
+ word away from restoring them.
135
+
136
+ `@dynamic-field-kit/angular` is unaffected; ng-packagr's published output does
137
+ not carry them.
138
+
139
+ - 244c3d4: Ship the MIT license text, and fix what the angular package tells npm.
140
+
141
+ Every package declared `"license": "MIT"` with no LICENSE file anywhere in the
142
+ repo, so the tarballs carried the claim and not the terms. npm includes a
143
+ LICENSE at the package root regardless of `files`, so all four now ship one.
144
+
145
+ The angular package's README was wrong in three places, all of them visible on
146
+ its npm page: the "pin versions explicitly" example named `core@^1.0.12` and
147
+ `angular@^1.2.3`, nine public exports (`FieldInputProps`, `DynamicFormOptions`,
148
+ `FieldTypeKey`, `LayoutConfig`, `ColumnLayoutConfig`, `RowLayoutConfig`,
149
+ `GridLayoutConfig`, `BaseLayoutConfig`, `ResponsiveLayoutConfig`) appeared
150
+ nowhere in it, and a section titled "Angular 14 and earlier" named versions the
151
+ package cannot run on — its peer floor is `>=14`.
152
+
153
+ Its `sideEffects: false` was also untrue: `defaultLayouts.ts` registers the
154
+ three layout components with a module-scope call, which is exactly the side
155
+ effect react and vue list their own layout modules for. The flag is now
156
+ `["**/fesm2022/*.mjs"]` — a glob because ng-packagr copies the field into
157
+ `dist/package.json`, where paths resolve one directory lower, and a bundler
158
+ reads whichever manifest is nearest the module. Nothing observable changes
159
+ today: bundling the published 1.4.0 with esbuild while importing only
160
+ `DynamicInput` already kept all three `register()` calls, because the fesm2022
161
+ bundle is a single module the app is using. The flag was a claim waiting to
162
+ break.
163
+
164
+ ## 1.4.0
165
+
166
+ ### Minor Changes
167
+
168
+ - 132f74b: Framework adapters: validation support, scoped field registries via dependency
169
+ injection, and unified cross-framework layout types.
170
+
171
+ - Validation wired through `DynamicInput` / `FieldInput` / `MultiFieldInput`.
172
+ - Scoped `FieldRegistry` injection (React context, Vue provide/inject, Angular
173
+ `FIELD_REGISTRY` token) so consumers can supply their own registry.
174
+ - Shared layout type definitions aligned across React, Vue, and Angular.
175
+
176
+ Requires `@dynamic-field-kit/core@^1.3.0` (peer dependency) for the new
177
+ validation and layout APIs.
178
+
179
+ Note: `@dynamic-field-kit/core` is a peer dependency (not bundled). Consumers
180
+ must install it alongside the adapter.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vannt-dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,7 +4,10 @@ React adapter for `@dynamic-field-kit/core`.
4
4
 
5
5
  This package provides React components for rendering `FieldDescription[]` and exports a React-typed `fieldRegistry`, so registered renderers can be used directly as JSX components.
6
6
 
7
- Demo app: https://github.com/vannt-dev/dynamic-field-kit-demo
7
+ Live demo: https://vannt-dev.github.io/dynamic-field-kit/react/ — plus
8
+ [enterprise features](https://vannt-dev.github.io/dynamic-field-kit/react/new-features/)
9
+ (`useDynamicForm`, HTML5 renderers, blur wiring, DevTools) and a
10
+ [multi-step wizard](https://vannt-dev.github.io/dynamic-field-kit/react/wizard/).
8
11
 
9
12
  ## Install
10
13
 
@@ -12,22 +15,41 @@ Demo app: https://github.com/vannt-dev/dynamic-field-kit-demo
12
15
  npm install @dynamic-field-kit/core @dynamic-field-kit/react react
13
16
  ```
14
17
 
15
- Note: Core is shared runtime. Install core separately and ensure a single version is used across adapters to avoid duplicate registries.
16
-
17
- - Install with core: `npm install @dynamic-field-kit/core @dynamic-field-kit/react`
18
+ Note: `@dynamic-field-kit/core`, `react`, and `react-dom` are **peer dependencies** this adapter does not bundle or auto-install them, so add them to your app explicitly (as shown above). Keep a single `@dynamic-field-kit/core` version across all adapters so they share one registry.
18
19
 
19
20
  ## Exports
20
21
 
21
22
  - `DynamicInput`
22
23
  - `FieldInput`
23
24
  - `MultiFieldInput`
25
+ - `useDynamicForm`
26
+ - `DynamicFormDevTools`
24
27
  - `layoutRegistry`
25
28
  - `fieldRegistry`
29
+ - `FieldRegistry` (class, for scoped registries)
30
+ - `FieldRegistryProvider` / `useFieldRegistry` / `FieldRegistryProviderProps`
26
31
  - `ReactFieldRenderer`
27
32
  - `ReactFieldRegistry`
28
33
  - `FieldDescription`
29
34
  - `FieldTypeKey`
30
35
  - `FieldRendererProps`
36
+ - `LayoutConfig`
37
+ - `defaultRenderersMap` / `getDefaultRenderer`
38
+
39
+ Re-exported from `@dynamic-field-kit/core` so a consumer app rarely has to import
40
+ both packages:
41
+
42
+ - `validateField` / `validateFieldAsync` — one field, returns `string[]`
43
+ - `validateFields` / `validateFieldsAsync` — a whole schema, returns `ValidationResult`
44
+ - `resolveDisabled` / `resolveReadOnly` / `resolveOptions` — resolve a field's dynamic conditions and options
45
+ - `validators` — the built-in validator helpers (`required`, `email`, `minLength`, `compose`, …)
46
+ - `ValidationResult`
47
+
48
+ `useDynamicForm` validates **synchronously** via `validateFields`, including on
49
+ submit. Fields whose `validate` hook returns a Promise are treated as valid on
50
+ that path, so run async rules through `validateFieldsAsync` yourself. See the
51
+ [core README](https://github.com/vannt-dev/dynamic-field-kit/tree/develop/packages/core#sync-vs-async-validation)
52
+ for the full rules.
31
53
 
32
54
  `FieldGroupInput` (repeatable field groups) is used internally by `FieldInput` and doesn't need to be imported directly - see "Repeatable field groups" below.
33
55
 
@@ -94,6 +116,91 @@ export function Example() {
94
116
  }
95
117
  ```
96
118
 
119
+ ## Form state (`useDynamicForm`)
120
+
121
+ Holds data, errors, touched and submission state for a set of fields. Vue's
122
+ composable and Angular's `createDynamicFormStore` expose the same surface.
123
+
124
+ ```tsx
125
+ import { useDynamicForm, MultiFieldInput } from '@dynamic-field-kit/react';
126
+
127
+ const form = useDynamicForm({
128
+ fields,
129
+ initialValues: { country: 'VN' },
130
+ validateOnBlur: true, // default
131
+ validateOnChange: false, // default
132
+ });
133
+
134
+ <form onSubmit={form.handleSubmit((data) => save(data))}>
135
+ <MultiFieldInput
136
+ fieldDescriptions={fields}
137
+ properties={form.data}
138
+ onChange={form.handleChange}
139
+ onBlurField={form.handleBlur} // wires touched + validateOnBlur
140
+ />
141
+ <button disabled={form.isSubmitting}>
142
+ {form.isSubmitting ? 'Saving…' : 'Save'}
143
+ </button>
144
+ </form>;
145
+ ```
146
+
147
+ | Member | Description |
148
+ | ----------------------------------- | --------------------------------------------------------------------------------- |
149
+ | `data` | Current form data, with `computeValue` fields applied |
150
+ | `errors` | `Record<string, string[]>`, keyed like `validateFields` |
151
+ | `isValid` / `isDirty` | No errors recorded / any value has changed |
152
+ | `isSubmitting` / `isSubmitted` | In-flight submit / at least one submit attempted |
153
+ | `touched` | Fields that have been blurred |
154
+ | `handleChange(data)` | Replace the whole form data — pass to `MultiFieldInput`'s `onChange` |
155
+ | `setFieldValue(name, value)` | Change one field |
156
+ | `handleBlur(name)` | Mark touched, and validate when `validateOnBlur` |
157
+ | `setFieldTouched(name, value?)` | Set touched explicitly |
158
+ | `setData` | Raw state setter, for escape hatches |
159
+ | `validate()` | Validate now, returns a boolean |
160
+ | `reset(values?)` | Back to `initialValues` (or the values given), clearing errors/touched/submission |
161
+ | `handleSubmit(onValid, onInvalid?)` | Returns a submit handler; calls `preventDefault`, validates, then dispatches |
162
+
163
+ `MultiFieldInput` tracks touched internally regardless; `onBlurField` is the
164
+ hook for driving an external store like this one.
165
+
166
+ ## Default renderers
167
+
168
+ `text` · `number` · `password` · `email` · `textarea` · `checkbox` · `select` ·
169
+ `radio` · `range` · `file` · `date` · `time` · `datetime-local` · `switch`
170
+
171
+ Any type you have not registered falls back to one of these. Reach the map
172
+ directly if you need to wrap or inspect a default:
173
+
174
+ ```ts
175
+ import {
176
+ defaultRenderersMap,
177
+ getDefaultRenderer,
178
+ } from '@dynamic-field-kit/react';
179
+
180
+ const Base = getDefaultRenderer('date'); // undefined for an unknown type
181
+ ```
182
+
183
+ `file` emits a `File` (or `File[]` when `multiple` is set), `range` and `number`
184
+ emit numbers, `checkbox` / `switch` emit booleans; everything else emits strings.
185
+
186
+ ## DevTools
187
+
188
+ ```tsx
189
+ import { DynamicFormDevTools } from '@dynamic-field-kit/react';
190
+
191
+ <DynamicFormDevTools
192
+ data={form.data}
193
+ errors={form.errors}
194
+ touched={form.touched}
195
+ isDirty={form.isDirty}
196
+ fields={fields}
197
+ position="bottom-right" // or "bottom-left"
198
+ />;
199
+ ```
200
+
201
+ A floating overlay with data / errors / meta / fields tabs. The collapsed
202
+ button carries a red badge with the number of fields in error.
203
+
97
204
  ## Layouts
98
205
 
99
206
  Use a layout name:
@@ -151,6 +258,36 @@ const fields: FieldDescription[] = [
151
258
  ];
152
259
  ```
153
260
 
261
+ ## Validation & conditions
262
+
263
+ Declare a `validate` hook and dynamic `disabledCondition`/`readOnlyCondition`;
264
+ your renderer receives `error`, `disabled`, and `readOnly`. `MultiFieldInput`
265
+ emits `onValidityChange`:
266
+
267
+ ```tsx
268
+ <MultiFieldInput
269
+ fieldDescriptions={fields}
270
+ properties={data}
271
+ onChange={setData}
272
+ onValidityChange={({ valid, errors }) => setCanSubmit(valid)}
273
+ />
274
+ ```
275
+
276
+ Read the props inside a renderer:
277
+
278
+ ```tsx
279
+ fieldRegistry.register('text', ({ value, onValueChange, error, disabled }) => (
280
+ <label>
281
+ <input
282
+ disabled={disabled}
283
+ value={value ?? ''}
284
+ onChange={(e) => onValueChange?.(e.target.value)}
285
+ />
286
+ {error && <span className="error">{[].concat(error).join(', ')}</span>}
287
+ </label>
288
+ ));
289
+ ```
290
+
154
291
  ## Repeatable field groups
155
292
 
156
293
  A field with `fields` renders as a repeatable group: `data[name]` becomes an array of items, each shaped by the nested `fields`, with "Add"/"Remove" controls rendered automatically.
@@ -166,6 +303,7 @@ const fields: FieldDescription[] = [
166
303
  { name: 'phone', type: 'text', label: 'Phone' },
167
304
  ],
168
305
  defaultItem: { email: '', phone: '' },
306
+ keyField: 'id', // optional: stable list key instead of the array index
169
307
  minItems: 1,
170
308
  maxItems: 5,
171
309
  },
@@ -174,6 +312,25 @@ const fields: FieldDescription[] = [
174
312
  <MultiFieldInput fieldDescriptions={fields} />;
175
313
  ```
176
314
 
315
+ ## Scoped registries
316
+
317
+ `fieldRegistry` is a process-wide singleton. To give a subtree its own renderers, create an isolated `FieldRegistry` and wrap the subtree in `FieldRegistryProvider`. Anything not wrapped keeps using the global singleton.
318
+
319
+ ```tsx
320
+ import {
321
+ FieldRegistry,
322
+ FieldRegistryProvider,
323
+ MultiFieldInput,
324
+ } from '@dynamic-field-kit/react';
325
+
326
+ const registry = new FieldRegistry();
327
+ registry.register('text', MyTextRenderer);
328
+
329
+ <FieldRegistryProvider registry={registry}>
330
+ <MultiFieldInput fieldDescriptions={fields} />
331
+ </FieldRegistryProvider>;
332
+ ```
333
+
177
334
  ## Type augmentation
178
335
 
179
336
  Add your app's field types through module augmentation:
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import React, { ReactNode, ComponentType } from 'react';
2
- import * as react_jsx_runtime from 'react/jsx-runtime';
3
- import { FieldTypeKey, Properties, FieldDescription, FieldTypeMap, FieldRendererProps } from '@dynamic-field-kit/core';
4
- export { FieldDescription, FieldRendererProps, FieldTypeKey } from '@dynamic-field-kit/core';
2
+ import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldRendererProps, FieldTypeMap } from '@dynamic-field-kit/core';
3
+ export { FieldDescription, FieldRegistry, FieldRendererProps, FieldTypeKey, LayoutConfig, ValidationResult, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
5
4
 
6
5
  type LayoutRenderer<C = unknown> = (props: {
7
6
  children: React.ReactNode;
@@ -14,43 +13,106 @@ declare class LayoutRegistry {
14
13
  }
15
14
  declare const layoutRegistry: LayoutRegistry;
16
15
 
17
- type BaseLayout = 'column' | 'row' | {
18
- type: 'grid';
19
- columns?: number;
20
- gap?: number;
21
- };
22
- type LayoutConfig = BaseLayout | {
23
- type: 'responsive';
24
- mobile: BaseLayout;
25
- desktop: BaseLayout;
26
- };
27
-
28
16
  interface Props$2<T extends FieldTypeKey> {
29
17
  type: T;
30
18
  value?: unknown;
31
19
  onChange?: (value: unknown) => void;
20
+ onBlur?: () => void;
32
21
  label?: string;
33
22
  options?: Properties[];
34
23
  className?: string;
35
24
  description?: ReactNode;
25
+ disabled?: boolean;
26
+ readOnly?: boolean;
27
+ required?: boolean;
28
+ touched?: boolean;
29
+ dirty?: boolean;
30
+ error?: string | string[];
31
+ id?: string;
32
+ ariaInvalid?: boolean;
33
+ ariaDescribedBy?: string;
34
+ ariaRequired?: boolean;
35
+ /** Extra, framework-agnostic props forwarded verbatim to the renderer. */
36
+ extraProps?: Properties;
36
37
  }
37
- declare const DynamicInputInner: <T extends FieldTypeKey>({ type, value, onChange, label, options, className, description, }: Props$2<T>) => react_jsx_runtime.JSX.Element;
38
+ declare const DynamicInputInner: <T extends FieldTypeKey>({ type, value, onChange, onBlur, label, options, className, description, disabled, readOnly, required, touched, dirty, error, id, ariaInvalid, ariaDescribedBy, ariaRequired, extraProps, }: Props$2<T>) => React.JSX.Element;
38
39
  declare const DynamicInput: typeof DynamicInputInner;
39
40
 
40
41
  interface Props$1 {
41
42
  fieldDescription: FieldDescription;
42
43
  renderInfos: Properties;
44
+ rootData?: Properties;
45
+ touched?: boolean;
46
+ dirty?: boolean;
47
+ onBlurField?: (key: string) => void;
43
48
  onValueChangeField: (value: unknown, key: string) => void;
44
49
  }
45
- declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, onValueChangeField, }: Props$1) => react_jsx_runtime.JSX.Element>;
50
+ declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, rootData, touched, dirty, onBlurField, onValueChangeField, }: Props$1) => React.JSX.Element>;
46
51
 
47
52
  interface Props {
48
53
  fieldDescriptions: FieldDescription[];
49
54
  properties?: Properties;
50
55
  onChange?: (data: Properties) => void;
51
56
  layout?: LayoutConfig;
57
+ /**
58
+ * Top-level form data, threaded down through repeatable groups so a nested
59
+ * field's `appearCondition`/`computeValue` can read the root form. Omitted at
60
+ * the top level, where the form's own data is the root.
61
+ */
62
+ rootData?: Properties;
63
+ /**
64
+ * Called with the recursive validation result ({ valid, errors }) on every
65
+ * change. On the top-level component this covers the whole form (groups
66
+ * included).
67
+ */
68
+ onValidityChange?: (result: ValidationResult) => void;
69
+ /**
70
+ * Called with a field's name when it loses focus. Touched state is still
71
+ * tracked internally either way; this is the hook for driving an external
72
+ * form store - pass `useDynamicForm`'s `handleBlur` to get its `touched`
73
+ * map and `validateOnBlur` behaviour.
74
+ */
75
+ onBlurField?: (fieldName: string) => void;
76
+ }
77
+ declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, rootData, onValidityChange, onBlurField, }: Props) => React.JSX.Element;
78
+
79
+ interface DynamicFormDevToolsProps {
80
+ data: Properties;
81
+ errors?: Record<string, string[]>;
82
+ touched?: Record<string, boolean>;
83
+ isDirty?: boolean;
84
+ fields?: FieldDescription[];
85
+ position?: 'bottom-right' | 'bottom-left';
86
+ }
87
+ declare const DynamicFormDevTools: React.FC<DynamicFormDevToolsProps>;
88
+
89
+ interface UseDynamicFormOptions {
90
+ fields: FieldDescription[];
91
+ initialValues?: Properties;
92
+ validateOnBlur?: boolean;
93
+ validateOnChange?: boolean;
52
94
  }
53
- declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, }: Props) => react_jsx_runtime.JSX.Element;
95
+ interface UseDynamicFormResult {
96
+ data: Properties;
97
+ errors: Record<string, string[]>;
98
+ isValid: boolean;
99
+ isDirty: boolean;
100
+ isSubmitting: boolean;
101
+ isSubmitted: boolean;
102
+ touched: Record<string, boolean>;
103
+ setData: React.Dispatch<React.SetStateAction<Properties>>;
104
+ setFieldValue: (name: string, value: unknown) => void;
105
+ setFieldTouched: (name: string, isTouched?: boolean) => void;
106
+ handleChange: (newData: Properties) => void;
107
+ handleBlur: (fieldName: string) => void;
108
+ reset: (newValues?: Properties) => void;
109
+ validate: () => boolean;
110
+ handleSubmit: (onValid: (data: Properties) => void | Promise<void>, onInvalid?: (errors: Record<string, string[]>) => void) => (e?: React.FormEvent) => Promise<void>;
111
+ }
112
+ declare function useDynamicForm({ fields, initialValues, validateOnBlur, validateOnChange, }: UseDynamicFormOptions): UseDynamicFormResult;
113
+
114
+ declare const defaultRenderersMap: Record<string, React.FC<FieldRendererProps>>;
115
+ declare function getDefaultRenderer(type: string): React.FC<FieldRendererProps> | undefined;
54
116
 
55
117
  type ReactFieldRenderer<T = unknown> = ComponentType<FieldRendererProps<T>>;
56
118
  interface ReactFieldRegistry {
@@ -59,4 +121,12 @@ interface ReactFieldRegistry {
59
121
  }
60
122
  declare const fieldRegistry: ReactFieldRegistry;
61
123
 
62
- export { DynamicInput, FieldInput, type LayoutConfig, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, fieldRegistry, layoutRegistry };
124
+ interface FieldRegistryProviderProps {
125
+ registry: ReactFieldRegistry;
126
+ children: React.ReactNode;
127
+ }
128
+ declare const FieldRegistryProvider: ({ registry, children, }: FieldRegistryProviderProps) => React.ReactElement;
129
+ /** The registry for the nearest provider, or the global singleton. */
130
+ declare function useFieldRegistry(): ReactFieldRegistry;
131
+
132
+ export { DynamicFormDevTools, DynamicInput, FieldInput, FieldRegistryProvider, type FieldRegistryProviderProps, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, defaultRenderersMap, fieldRegistry, getDefaultRenderer, layoutRegistry, useDynamicForm, useFieldRegistry };