@dynamic-field-kit/react 1.3.0 → 1.4.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 +55 -3
- package/dist/index.d.mts +43 -17
- package/dist/index.d.ts +43 -17
- package/dist/index.js +223 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +209 -43
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -12,9 +12,7 @@ Demo app: https://github.com/vannt-dev/dynamic-field-kit-demo
|
|
|
12
12
|
npm install @dynamic-field-kit/core @dynamic-field-kit/react react
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Note:
|
|
16
|
-
|
|
17
|
-
- Install with core: `npm install @dynamic-field-kit/core @dynamic-field-kit/react`
|
|
15
|
+
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
16
|
|
|
19
17
|
## Exports
|
|
20
18
|
|
|
@@ -23,11 +21,15 @@ Note: Core is shared runtime. Install core separately and ensure a single versio
|
|
|
23
21
|
- `MultiFieldInput`
|
|
24
22
|
- `layoutRegistry`
|
|
25
23
|
- `fieldRegistry`
|
|
24
|
+
- `FieldRegistry` (class, for scoped registries)
|
|
25
|
+
- `FieldRegistryProvider` / `useFieldRegistry` / `FieldRegistryProviderProps`
|
|
26
26
|
- `ReactFieldRenderer`
|
|
27
27
|
- `ReactFieldRegistry`
|
|
28
28
|
- `FieldDescription`
|
|
29
29
|
- `FieldTypeKey`
|
|
30
30
|
- `FieldRendererProps`
|
|
31
|
+
- `LayoutConfig`
|
|
32
|
+
- `validateField` / `validateFields` / `resolveDisabled` / `resolveReadOnly` / `ValidationResult`
|
|
31
33
|
|
|
32
34
|
`FieldGroupInput` (repeatable field groups) is used internally by `FieldInput` and doesn't need to be imported directly - see "Repeatable field groups" below.
|
|
33
35
|
|
|
@@ -151,6 +153,36 @@ const fields: FieldDescription[] = [
|
|
|
151
153
|
];
|
|
152
154
|
```
|
|
153
155
|
|
|
156
|
+
## Validation & conditions
|
|
157
|
+
|
|
158
|
+
Declare a `validate` hook and dynamic `disabledCondition`/`readOnlyCondition`;
|
|
159
|
+
your renderer receives `error`, `disabled`, and `readOnly`. `MultiFieldInput`
|
|
160
|
+
emits `onValidityChange`:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
<MultiFieldInput
|
|
164
|
+
fieldDescriptions={fields}
|
|
165
|
+
properties={data}
|
|
166
|
+
onChange={setData}
|
|
167
|
+
onValidityChange={({ valid, errors }) => setCanSubmit(valid)}
|
|
168
|
+
/>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Read the props inside a renderer:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
fieldRegistry.register('text', ({ value, onValueChange, error, disabled }) => (
|
|
175
|
+
<label>
|
|
176
|
+
<input
|
|
177
|
+
disabled={disabled}
|
|
178
|
+
value={value ?? ''}
|
|
179
|
+
onChange={(e) => onValueChange?.(e.target.value)}
|
|
180
|
+
/>
|
|
181
|
+
{error && <span className="error">{[].concat(error).join(', ')}</span>}
|
|
182
|
+
</label>
|
|
183
|
+
));
|
|
184
|
+
```
|
|
185
|
+
|
|
154
186
|
## Repeatable field groups
|
|
155
187
|
|
|
156
188
|
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 +198,7 @@ const fields: FieldDescription[] = [
|
|
|
166
198
|
{ name: 'phone', type: 'text', label: 'Phone' },
|
|
167
199
|
],
|
|
168
200
|
defaultItem: { email: '', phone: '' },
|
|
201
|
+
keyField: 'id', // optional: stable list key instead of the array index
|
|
169
202
|
minItems: 1,
|
|
170
203
|
maxItems: 5,
|
|
171
204
|
},
|
|
@@ -174,6 +207,25 @@ const fields: FieldDescription[] = [
|
|
|
174
207
|
<MultiFieldInput fieldDescriptions={fields} />;
|
|
175
208
|
```
|
|
176
209
|
|
|
210
|
+
## Scoped registries
|
|
211
|
+
|
|
212
|
+
`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.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import {
|
|
216
|
+
FieldRegistry,
|
|
217
|
+
FieldRegistryProvider,
|
|
218
|
+
MultiFieldInput,
|
|
219
|
+
} from '@dynamic-field-kit/react';
|
|
220
|
+
|
|
221
|
+
const registry = new FieldRegistry();
|
|
222
|
+
registry.register('text', MyTextRenderer);
|
|
223
|
+
|
|
224
|
+
<FieldRegistryProvider registry={registry}>
|
|
225
|
+
<MultiFieldInput fieldDescriptions={fields} />
|
|
226
|
+
</FieldRegistryProvider>;
|
|
227
|
+
```
|
|
228
|
+
|
|
177
229
|
## Type augmentation
|
|
178
230
|
|
|
179
231
|
Add your app's field types through module augmentation:
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldTypeMap, FieldRendererProps } 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';
|
|
2
4
|
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';
|
|
5
5
|
|
|
6
6
|
type LayoutRenderer<C = unknown> = (props: {
|
|
7
7
|
children: React.ReactNode;
|
|
@@ -14,43 +14,61 @@ declare class LayoutRegistry {
|
|
|
14
14
|
}
|
|
15
15
|
declare const layoutRegistry: LayoutRegistry;
|
|
16
16
|
|
|
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
17
|
interface Props$2<T extends FieldTypeKey> {
|
|
29
18
|
type: T;
|
|
30
19
|
value?: unknown;
|
|
31
20
|
onChange?: (value: unknown) => void;
|
|
21
|
+
onBlur?: () => void;
|
|
32
22
|
label?: string;
|
|
33
23
|
options?: Properties[];
|
|
34
24
|
className?: string;
|
|
35
25
|
description?: ReactNode;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
readOnly?: boolean;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
touched?: boolean;
|
|
30
|
+
dirty?: boolean;
|
|
31
|
+
error?: string | string[];
|
|
32
|
+
id?: string;
|
|
33
|
+
ariaInvalid?: boolean;
|
|
34
|
+
ariaDescribedBy?: string;
|
|
35
|
+
ariaRequired?: boolean;
|
|
36
|
+
/** Extra, framework-agnostic props forwarded verbatim to the renderer. */
|
|
37
|
+
extraProps?: Properties;
|
|
36
38
|
}
|
|
37
|
-
declare const DynamicInputInner: <T extends FieldTypeKey>({ type, value, onChange, label, options, className, description, }: Props$2<T>) => react_jsx_runtime.JSX.Element;
|
|
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
40
|
declare const DynamicInput: typeof DynamicInputInner;
|
|
39
41
|
|
|
40
42
|
interface Props$1 {
|
|
41
43
|
fieldDescription: FieldDescription;
|
|
42
44
|
renderInfos: Properties;
|
|
45
|
+
rootData?: Properties;
|
|
46
|
+
touched?: boolean;
|
|
47
|
+
dirty?: boolean;
|
|
48
|
+
onBlurField?: (key: string) => void;
|
|
43
49
|
onValueChangeField: (value: unknown, key: string) => void;
|
|
44
50
|
}
|
|
45
|
-
declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, onValueChangeField, }: Props$1) => react_jsx_runtime.JSX.Element>;
|
|
51
|
+
declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, rootData, touched, dirty, onBlurField, onValueChangeField, }: Props$1) => react_jsx_runtime.JSX.Element>;
|
|
46
52
|
|
|
47
53
|
interface Props {
|
|
48
54
|
fieldDescriptions: FieldDescription[];
|
|
49
55
|
properties?: Properties;
|
|
50
56
|
onChange?: (data: Properties) => void;
|
|
51
57
|
layout?: LayoutConfig;
|
|
58
|
+
/**
|
|
59
|
+
* Top-level form data, threaded down through repeatable groups so a nested
|
|
60
|
+
* field's `appearCondition`/`computeValue` can read the root form. Omitted at
|
|
61
|
+
* the top level, where the form's own data is the root.
|
|
62
|
+
*/
|
|
63
|
+
rootData?: Properties;
|
|
64
|
+
/**
|
|
65
|
+
* Called with the recursive validation result ({ valid, errors }) on every
|
|
66
|
+
* change. On the top-level component this covers the whole form (groups
|
|
67
|
+
* included).
|
|
68
|
+
*/
|
|
69
|
+
onValidityChange?: (result: ValidationResult) => void;
|
|
52
70
|
}
|
|
53
|
-
declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, }: Props) => react_jsx_runtime.JSX.Element;
|
|
71
|
+
declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, rootData, onValidityChange, }: Props) => react_jsx_runtime.JSX.Element;
|
|
54
72
|
|
|
55
73
|
type ReactFieldRenderer<T = unknown> = ComponentType<FieldRendererProps<T>>;
|
|
56
74
|
interface ReactFieldRegistry {
|
|
@@ -59,4 +77,12 @@ interface ReactFieldRegistry {
|
|
|
59
77
|
}
|
|
60
78
|
declare const fieldRegistry: ReactFieldRegistry;
|
|
61
79
|
|
|
62
|
-
|
|
80
|
+
interface FieldRegistryProviderProps {
|
|
81
|
+
registry: ReactFieldRegistry;
|
|
82
|
+
children: React.ReactNode;
|
|
83
|
+
}
|
|
84
|
+
declare const FieldRegistryProvider: ({ registry, children, }: FieldRegistryProviderProps) => React.ReactElement;
|
|
85
|
+
/** The registry for the nearest provider, or the global singleton. */
|
|
86
|
+
declare function useFieldRegistry(): ReactFieldRegistry;
|
|
87
|
+
|
|
88
|
+
export { DynamicInput, FieldInput, FieldRegistryProvider, type FieldRegistryProviderProps, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, fieldRegistry, layoutRegistry, useFieldRegistry };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldTypeMap, FieldRendererProps } 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';
|
|
2
4
|
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';
|
|
5
5
|
|
|
6
6
|
type LayoutRenderer<C = unknown> = (props: {
|
|
7
7
|
children: React.ReactNode;
|
|
@@ -14,43 +14,61 @@ declare class LayoutRegistry {
|
|
|
14
14
|
}
|
|
15
15
|
declare const layoutRegistry: LayoutRegistry;
|
|
16
16
|
|
|
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
17
|
interface Props$2<T extends FieldTypeKey> {
|
|
29
18
|
type: T;
|
|
30
19
|
value?: unknown;
|
|
31
20
|
onChange?: (value: unknown) => void;
|
|
21
|
+
onBlur?: () => void;
|
|
32
22
|
label?: string;
|
|
33
23
|
options?: Properties[];
|
|
34
24
|
className?: string;
|
|
35
25
|
description?: ReactNode;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
readOnly?: boolean;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
touched?: boolean;
|
|
30
|
+
dirty?: boolean;
|
|
31
|
+
error?: string | string[];
|
|
32
|
+
id?: string;
|
|
33
|
+
ariaInvalid?: boolean;
|
|
34
|
+
ariaDescribedBy?: string;
|
|
35
|
+
ariaRequired?: boolean;
|
|
36
|
+
/** Extra, framework-agnostic props forwarded verbatim to the renderer. */
|
|
37
|
+
extraProps?: Properties;
|
|
36
38
|
}
|
|
37
|
-
declare const DynamicInputInner: <T extends FieldTypeKey>({ type, value, onChange, label, options, className, description, }: Props$2<T>) => react_jsx_runtime.JSX.Element;
|
|
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
40
|
declare const DynamicInput: typeof DynamicInputInner;
|
|
39
41
|
|
|
40
42
|
interface Props$1 {
|
|
41
43
|
fieldDescription: FieldDescription;
|
|
42
44
|
renderInfos: Properties;
|
|
45
|
+
rootData?: Properties;
|
|
46
|
+
touched?: boolean;
|
|
47
|
+
dirty?: boolean;
|
|
48
|
+
onBlurField?: (key: string) => void;
|
|
43
49
|
onValueChangeField: (value: unknown, key: string) => void;
|
|
44
50
|
}
|
|
45
|
-
declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, onValueChangeField, }: Props$1) => react_jsx_runtime.JSX.Element>;
|
|
51
|
+
declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, rootData, touched, dirty, onBlurField, onValueChangeField, }: Props$1) => react_jsx_runtime.JSX.Element>;
|
|
46
52
|
|
|
47
53
|
interface Props {
|
|
48
54
|
fieldDescriptions: FieldDescription[];
|
|
49
55
|
properties?: Properties;
|
|
50
56
|
onChange?: (data: Properties) => void;
|
|
51
57
|
layout?: LayoutConfig;
|
|
58
|
+
/**
|
|
59
|
+
* Top-level form data, threaded down through repeatable groups so a nested
|
|
60
|
+
* field's `appearCondition`/`computeValue` can read the root form. Omitted at
|
|
61
|
+
* the top level, where the form's own data is the root.
|
|
62
|
+
*/
|
|
63
|
+
rootData?: Properties;
|
|
64
|
+
/**
|
|
65
|
+
* Called with the recursive validation result ({ valid, errors }) on every
|
|
66
|
+
* change. On the top-level component this covers the whole form (groups
|
|
67
|
+
* included).
|
|
68
|
+
*/
|
|
69
|
+
onValidityChange?: (result: ValidationResult) => void;
|
|
52
70
|
}
|
|
53
|
-
declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, }: Props) => react_jsx_runtime.JSX.Element;
|
|
71
|
+
declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, rootData, onValidityChange, }: Props) => react_jsx_runtime.JSX.Element;
|
|
54
72
|
|
|
55
73
|
type ReactFieldRenderer<T = unknown> = ComponentType<FieldRendererProps<T>>;
|
|
56
74
|
interface ReactFieldRegistry {
|
|
@@ -59,4 +77,12 @@ interface ReactFieldRegistry {
|
|
|
59
77
|
}
|
|
60
78
|
declare const fieldRegistry: ReactFieldRegistry;
|
|
61
79
|
|
|
62
|
-
|
|
80
|
+
interface FieldRegistryProviderProps {
|
|
81
|
+
registry: ReactFieldRegistry;
|
|
82
|
+
children: React.ReactNode;
|
|
83
|
+
}
|
|
84
|
+
declare const FieldRegistryProvider: ({ registry, children, }: FieldRegistryProviderProps) => React.ReactElement;
|
|
85
|
+
/** The registry for the nearest provider, or the global singleton. */
|
|
86
|
+
declare function useFieldRegistry(): ReactFieldRegistry;
|
|
87
|
+
|
|
88
|
+
export { DynamicInput, FieldInput, FieldRegistryProvider, type FieldRegistryProviderProps, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, fieldRegistry, layoutRegistry, useFieldRegistry };
|