@dynamic-field-kit/react 1.4.0 → 1.5.1

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,204 @@
1
+ # @dynamic-field-kit/react
2
+
3
+ ## 1.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Findable on npm: every package now carries real search keywords, and homepage points at the live demo instead of the README the npm page already renders.
8
+ - b22a6a1: Make the packages findable on npm, and point `homepage` at something worth
9
+ landing on.
10
+
11
+ The keyword lists were three entries long and two of those were the package's
12
+ own name — nobody searches `dynamic-field-kit/core`. npm ranks search partly on
13
+ keywords, so in practice these packages could only be found by someone who
14
+ already knew what they were called. The repository has carried the right
15
+ vocabulary as GitHub topics all along (`dynamic-forms`, `form-builder`,
16
+ `form-engine`, `form-validation`, `schema-driven`, `headless`, and the three
17
+ framework names); npm simply never saw any of it. Each package now carries that
18
+ vocabulary plus the terms its own users would type, including the schema
19
+ libraries it actually adapts — zod, yup, valibot and Standard Schema. Not JSON
20
+ Schema, which it does not support.
21
+
22
+ `homepage` pointed at the package's README on GitHub, which is the same text
23
+ npm already renders on the package page from the shipped README. It now points
24
+ at the live demo instead, where the forms actually run. The source stays one
25
+ click away in `repository`.
26
+
27
+ ## 1.5.0
28
+
29
+ ### Minor Changes
30
+
31
+ - 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.
32
+ - d91c737: Add form state hooks, schema adapters, wizard engine, DevTools and extended renderers.
33
+
34
+ **Core**
35
+
36
+ - `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`.
37
+ - 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.
38
+ - 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.
39
+ - Group array helpers: `moveGroupItem`, `swapGroupItems`, `insertGroupItem`, `focusFirstInvalidField`.
40
+ - `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.
41
+
42
+ **React / Vue / Angular**
43
+
44
+ - `useDynamicForm` (React, Vue) and `createDynamicFormStore` (Angular Signals) now expose the same surface, including `isSubmitting` and `isSubmitted`.
45
+ - `handleSubmit(onValid, onInvalid)` returns a submit handler in every framework and calls `preventDefault` on the event it receives.
46
+ - Default HTML5 renderers for `radio`, `range`, `file`, `date`, `time`, `datetime-local` and `switch`.
47
+ - `DynamicFormDevTools` overlay for inspecting form data, errors, metadata and field descriptions, with an error-count badge in all three frameworks.
48
+ - `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.
49
+
50
+ ### Patch Changes
51
+
52
+ - 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.
53
+ - 75447a6: Close the remaining documentation gaps in each package README, so every public
54
+ export is described somewhere. README ships in the npm tarball, so this reaches
55
+ the package pages only through a release.
56
+
57
+ - **Sync vs async validation** is now spelled out in core, with the consequence
58
+ that was previously implicit: `validateField` / `validateFields` cannot await,
59
+ so a `validate` hook returning a Promise is treated as valid on the sync path.
60
+ `useDynamicForm` and `createDynamicFormStore` validate synchronously
61
+ (including on submit), so async rules have to run through
62
+ `validateFieldsAsync` explicitly. Each adapter README repeats the caveat and
63
+ links to the core section.
64
+ - Document `validateFieldAsync`, `validateFieldsAsync`, `resolveOptions` and
65
+ `validators` in the react, vue and angular export lists, separated from each
66
+ adapter's own exports so it is clear they are core re-exports.
67
+ - Document the core types that appear in every signature but had no definition
68
+ in the README: `Properties`, `ValidatorFn`, `FieldValidatorResult`,
69
+ `FieldValidatorFunction`, and `FormStep` alongside `WizardState`.
70
+ - Angular: document `layoutRegistry` / `LayoutRegistry`, the `ColumnLayout` /
71
+ `RowLayout` / `GridLayout` components and `BaseInputComponent`, with a custom
72
+ layout example. Its layout registry holds standalone components rather than
73
+ render functions, which is the one place the three adapters genuinely differ,
74
+ and it was the only adapter not documenting its registry at all.
75
+ - Rename angular's `## What it exports` to `## Exports` to match react and vue.
76
+ - Link the demo sub-routes: react's enterprise-features and wizard pages, and
77
+ from core the per-framework demos plus the wizard it documents.
78
+
79
+ - e10044b: Fix the type declarations consumers get under `moduleResolution: node16` /
80
+ `nodenext`, and fill in the npm metadata the package pages never had.
81
+
82
+ Every package declared a single `types` target for both module formats while
83
+ shipping two sets of declarations. `arethetypeswrong` on the built tarballs
84
+ reported core and react "masquerading as CJS" and vue "masquerading as ESM":
85
+ an ESM import resolved to the CommonJS declaration file, and vice versa. The
86
+ `exports` maps now declare `types` per condition, so each format resolves to
87
+ the declarations that describe it.
88
+
89
+ Angular was worse than a mismatch - it did not resolve at all. Its entry is an
90
+ `.mjs` bundle, so TypeScript reads its declarations in ESM mode, where the
91
+ extensionless `export * from './public-api'` that ng-packagr generates is error
92
+ TS2834. The failure is in `dist/index.d.ts`, the first file a consumer reaches,
93
+ so the package was unusable on node16 resolution. A `postbuild` step now adds
94
+ the explicit `.js` extension to relative specifiers in the emitted `.d.ts`
95
+ files, resolving each against the build output so a directory import becomes
96
+ `./layout/index.js` rather than a broken `./layout.js`. Angular also gains an
97
+ `exports` map and `"type": "module"`, which it needs to describe itself
98
+ honestly. All four packages are clean on all four resolution modes now, except
99
+ angular's `require()`, which is ESM-only by nature - as `@angular/core` is.
100
+
101
+ Also in the tarballs: `CHANGELOG.md` now ships, so the npm page has release
102
+ history, and angular no longer ships a second copy of its README and LICENSE
103
+ that ng-packagr had copied into `dist`. Every package gains `homepage`, `bugs`
104
+ and `repository.directory`, so npm links to the right README and issue tracker
105
+ instead of nothing, and `publishConfig.provenance`, so each published tarball
106
+ is signed and linked back to the workflow run that built it.
107
+
108
+ - 75447a6: Let consumer bundlers drop the parts of the adapters an app does not use.
109
+
110
+ Components were declared as bare top-level calls — `defineComponent({...})` in
111
+ vue, `React.memo(...)` in react. A bundler cannot prove such a call is
112
+ side-effect free, so it has to evaluate it even when the result is unused, which
113
+ kept every default renderer and every component in an app's bundle no matter how
114
+ little of the package it imported. Marking those calls `/* @__PURE__ */` makes
115
+ them droppable. Measured with esbuild, minified, framework external:
116
+
117
+ | App imports | Before | After |
118
+ | -------------------------- | -------- | --------------- |
119
+ | react: one core helper | 11,149 B | 3,258 B (−71%) |
120
+ | react: `DynamicInput` only | 11,150 B | 6,830 B (−39%) |
121
+ | react: everything | 17,601 B | unchanged |
122
+ | vue: one core helper | 17,379 B | 13,934 B (−20%) |
123
+ | vue: `DynamicInput` only | 17,379 B | 13,934 B (−20%) |
124
+ | vue: everything | 20,530 B | unchanged |
125
+
126
+ Apps that use the whole surface are unchanged, which is the expected result —
127
+ there is nothing to drop. The shipped `dist` grows slightly (react +0.03 KB, vue
128
+ +0.29 KB) because the annotations are comments in the bundle; the trade is a
129
+ bigger published file for a smaller consumer bundle.
130
+
131
+ `@dynamic-field-kit/core` now declares `"sideEffects": false`. It has no
132
+ top-level execution at all — the only module-scope work is `new FieldRegistry()`
133
+ assigned to an export — so the claim is accurate, and it lets bundlers that rely
134
+ on the flag rather than their own analysis skip core entirely when it is unused.
135
+ The adapters deliberately do not set it: their entry side-effect-imports the
136
+ default layouts in order to register them.
137
+
138
+ - 75447a6: Stop publishing sourcemaps, roughly halving what each package installs.
139
+
140
+ tsup was emitting sourcemaps with `sourcesContent`, so every `.map` carried a
141
+ full copy of the TypeScript source. That is what made them work at all — `files`
142
+ only publishes `dist`, so a map referencing `../src/*.ts` would otherwise
143
+ resolve to nothing — but it also made them about half of each tarball, shipped
144
+ to every consumer on every install.
145
+
146
+ | Package | Unpacked | Tarball | Files |
147
+ | ------- | ----------------------- | --------------------- | ----- |
148
+ | core | 157.0 → 83.0 KB (−47%) | 33.3 → 17.5 KB (−47%) | 8 → 6 |
149
+ | react | 207.6 → 90.1 KB (−57%) | 46.8 → 19.2 KB (−59%) | 8 → 6 |
150
+ | vue | 250.4 → 109.4 KB (−56%) | 47.6 → 20.5 KB (−57%) | 8 → 6 |
151
+
152
+ Nothing that ends up in an application bundle changes — sourcemaps never do.
153
+ What changes is install size, and the ability to step into the library's
154
+ TypeScript source while debugging a consuming app.
155
+
156
+ This is a deliberate trade, not a free win: the maps worked. Each package's
157
+ `tsup.config.ts` carries the reasoning next to a `sourcemap: false` that is one
158
+ word away from restoring them.
159
+
160
+ `@dynamic-field-kit/angular` is unaffected; ng-packagr's published output does
161
+ not carry them.
162
+
163
+ - 244c3d4: Ship the MIT license text, and fix what the angular package tells npm.
164
+
165
+ Every package declared `"license": "MIT"` with no LICENSE file anywhere in the
166
+ repo, so the tarballs carried the claim and not the terms. npm includes a
167
+ LICENSE at the package root regardless of `files`, so all four now ship one.
168
+
169
+ The angular package's README was wrong in three places, all of them visible on
170
+ its npm page: the "pin versions explicitly" example named `core@^1.0.12` and
171
+ `angular@^1.2.3`, nine public exports (`FieldInputProps`, `DynamicFormOptions`,
172
+ `FieldTypeKey`, `LayoutConfig`, `ColumnLayoutConfig`, `RowLayoutConfig`,
173
+ `GridLayoutConfig`, `BaseLayoutConfig`, `ResponsiveLayoutConfig`) appeared
174
+ nowhere in it, and a section titled "Angular 14 and earlier" named versions the
175
+ package cannot run on — its peer floor is `>=14`.
176
+
177
+ Its `sideEffects: false` was also untrue: `defaultLayouts.ts` registers the
178
+ three layout components with a module-scope call, which is exactly the side
179
+ effect react and vue list their own layout modules for. The flag is now
180
+ `["**/fesm2022/*.mjs"]` — a glob because ng-packagr copies the field into
181
+ `dist/package.json`, where paths resolve one directory lower, and a bundler
182
+ reads whichever manifest is nearest the module. Nothing observable changes
183
+ today: bundling the published 1.4.0 with esbuild while importing only
184
+ `DynamicInput` already kept all three `register()` calls, because the fesm2022
185
+ bundle is a single module the app is using. The flag was a claim waiting to
186
+ break.
187
+
188
+ ## 1.4.0
189
+
190
+ ### Minor Changes
191
+
192
+ - 132f74b: Framework adapters: validation support, scoped field registries via dependency
193
+ injection, and unified cross-framework layout types.
194
+
195
+ - Validation wired through `DynamicInput` / `FieldInput` / `MultiFieldInput`.
196
+ - Scoped `FieldRegistry` injection (React context, Vue provide/inject, Angular
197
+ `FIELD_REGISTRY` token) so consumers can supply their own registry.
198
+ - Shared layout type definitions aligned across React, Vue, and Angular.
199
+
200
+ Requires `@dynamic-field-kit/core@^1.3.0` (peer dependency) for the new
201
+ validation and layout APIs.
202
+
203
+ Note: `@dynamic-field-kit/core` is a peer dependency (not bundled). Consumers
204
+ 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
 
@@ -19,6 +22,8 @@ Note: `@dynamic-field-kit/core`, `react`, and `react-dom` are **peer dependencie
19
22
  - `DynamicInput`
20
23
  - `FieldInput`
21
24
  - `MultiFieldInput`
25
+ - `useDynamicForm`
26
+ - `DynamicFormDevTools`
22
27
  - `layoutRegistry`
23
28
  - `fieldRegistry`
24
29
  - `FieldRegistry` (class, for scoped registries)
@@ -29,7 +34,22 @@ Note: `@dynamic-field-kit/core`, `react`, and `react-dom` are **peer dependencie
29
34
  - `FieldTypeKey`
30
35
  - `FieldRendererProps`
31
36
  - `LayoutConfig`
32
- - `validateField` / `validateFields` / `resolveDisabled` / `resolveReadOnly` / `ValidationResult`
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.
33
53
 
34
54
  `FieldGroupInput` (repeatable field groups) is used internally by `FieldInput` and doesn't need to be imported directly - see "Repeatable field groups" below.
35
55
 
@@ -96,6 +116,91 @@ export function Example() {
96
116
  }
97
117
  ```
98
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
+
99
204
  ## Layouts
100
205
 
101
206
  Use a layout name:
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import React, { ReactNode, ComponentType } from 'react';
2
- import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldTypeMap, FieldRendererProps } from '@dynamic-field-kit/core';
2
+ import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldRendererProps, FieldTypeMap } from '@dynamic-field-kit/core';
3
3
  export { FieldDescription, FieldRegistry, FieldRendererProps, FieldTypeKey, LayoutConfig, ValidationResult, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
4
- import * as react_jsx_runtime from 'react/jsx-runtime';
5
4
 
6
5
  type LayoutRenderer<C = unknown> = (props: {
7
6
  children: React.ReactNode;
@@ -36,7 +35,7 @@ interface Props$2<T extends FieldTypeKey> {
36
35
  /** Extra, framework-agnostic props forwarded verbatim to the renderer. */
37
36
  extraProps?: Properties;
38
37
  }
39
- 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_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;
40
39
  declare const DynamicInput: typeof DynamicInputInner;
41
40
 
42
41
  interface Props$1 {
@@ -48,7 +47,7 @@ interface Props$1 {
48
47
  onBlurField?: (key: string) => void;
49
48
  onValueChangeField: (value: unknown, key: string) => void;
50
49
  }
51
- declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, rootData, touched, dirty, onBlurField, 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>;
52
51
 
53
52
  interface Props {
54
53
  fieldDescriptions: FieldDescription[];
@@ -67,8 +66,53 @@ interface Props {
67
66
  * included).
68
67
  */
69
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';
70
86
  }
71
- declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, rootData, onValidityChange, }: Props) => react_jsx_runtime.JSX.Element;
87
+ declare const DynamicFormDevTools: React.FC<DynamicFormDevToolsProps>;
88
+
89
+ interface UseDynamicFormOptions {
90
+ fields: FieldDescription[];
91
+ initialValues?: Properties;
92
+ validateOnBlur?: boolean;
93
+ validateOnChange?: boolean;
94
+ }
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;
72
116
 
73
117
  type ReactFieldRenderer<T = unknown> = ComponentType<FieldRendererProps<T>>;
74
118
  interface ReactFieldRegistry {
@@ -85,4 +129,4 @@ declare const FieldRegistryProvider: ({ registry, children, }: FieldRegistryProv
85
129
  /** The registry for the nearest provider, or the global singleton. */
86
130
  declare function useFieldRegistry(): ReactFieldRegistry;
87
131
 
88
- export { DynamicInput, FieldInput, FieldRegistryProvider, type FieldRegistryProviderProps, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, fieldRegistry, layoutRegistry, useFieldRegistry };
132
+ export { DynamicFormDevTools, DynamicInput, FieldInput, FieldRegistryProvider, type FieldRegistryProviderProps, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, defaultRenderersMap, fieldRegistry, getDefaultRenderer, layoutRegistry, useDynamicForm, useFieldRegistry };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import React, { ReactNode, ComponentType } from 'react';
2
- import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldTypeMap, FieldRendererProps } from '@dynamic-field-kit/core';
2
+ import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldRendererProps, FieldTypeMap } from '@dynamic-field-kit/core';
3
3
  export { FieldDescription, FieldRegistry, FieldRendererProps, FieldTypeKey, LayoutConfig, ValidationResult, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
4
- import * as react_jsx_runtime from 'react/jsx-runtime';
5
4
 
6
5
  type LayoutRenderer<C = unknown> = (props: {
7
6
  children: React.ReactNode;
@@ -36,7 +35,7 @@ interface Props$2<T extends FieldTypeKey> {
36
35
  /** Extra, framework-agnostic props forwarded verbatim to the renderer. */
37
36
  extraProps?: Properties;
38
37
  }
39
- 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_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;
40
39
  declare const DynamicInput: typeof DynamicInputInner;
41
40
 
42
41
  interface Props$1 {
@@ -48,7 +47,7 @@ interface Props$1 {
48
47
  onBlurField?: (key: string) => void;
49
48
  onValueChangeField: (value: unknown, key: string) => void;
50
49
  }
51
- declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, rootData, touched, dirty, onBlurField, 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>;
52
51
 
53
52
  interface Props {
54
53
  fieldDescriptions: FieldDescription[];
@@ -67,8 +66,53 @@ interface Props {
67
66
  * included).
68
67
  */
69
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';
70
86
  }
71
- declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, rootData, onValidityChange, }: Props) => react_jsx_runtime.JSX.Element;
87
+ declare const DynamicFormDevTools: React.FC<DynamicFormDevToolsProps>;
88
+
89
+ interface UseDynamicFormOptions {
90
+ fields: FieldDescription[];
91
+ initialValues?: Properties;
92
+ validateOnBlur?: boolean;
93
+ validateOnChange?: boolean;
94
+ }
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;
72
116
 
73
117
  type ReactFieldRenderer<T = unknown> = ComponentType<FieldRendererProps<T>>;
74
118
  interface ReactFieldRegistry {
@@ -85,4 +129,4 @@ declare const FieldRegistryProvider: ({ registry, children, }: FieldRegistryProv
85
129
  /** The registry for the nearest provider, or the global singleton. */
86
130
  declare function useFieldRegistry(): ReactFieldRegistry;
87
131
 
88
- export { DynamicInput, FieldInput, FieldRegistryProvider, type FieldRegistryProviderProps, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, fieldRegistry, layoutRegistry, useFieldRegistry };
132
+ export { DynamicFormDevTools, DynamicInput, FieldInput, FieldRegistryProvider, type FieldRegistryProviderProps, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, defaultRenderersMap, fieldRegistry, getDefaultRenderer, layoutRegistry, useDynamicForm, useFieldRegistry };