@dynamic-field-kit/react 1.1.1 → 1.3.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 +77 -30
- package/dist/index.d.mts +12 -11
- package/dist/index.d.ts +12 -11
- package/dist/index.js +216 -97
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +232 -122
- package/dist/index.mjs.map +1 -1
- package/package.json +68 -47
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ 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: 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
|
+
|
|
15
19
|
## Exports
|
|
16
20
|
|
|
17
21
|
- `DynamicInput`
|
|
@@ -25,6 +29,8 @@ npm install @dynamic-field-kit/core @dynamic-field-kit/react react
|
|
|
25
29
|
- `FieldTypeKey`
|
|
26
30
|
- `FieldRendererProps`
|
|
27
31
|
|
|
32
|
+
`FieldGroupInput` (repeatable field groups) is used internally by `FieldInput` and doesn't need to be imported directly - see "Repeatable field groups" below.
|
|
33
|
+
|
|
28
34
|
Default layouts are registered automatically when you import the package root.
|
|
29
35
|
|
|
30
36
|
Built-in layouts:
|
|
@@ -39,44 +45,44 @@ Built-in layouts:
|
|
|
39
45
|
Register React components or function components through the React adapter:
|
|
40
46
|
|
|
41
47
|
```tsx
|
|
42
|
-
import { fieldRegistry } from
|
|
48
|
+
import { fieldRegistry } from '@dynamic-field-kit/react';
|
|
43
49
|
|
|
44
|
-
fieldRegistry.register(
|
|
45
|
-
<label style={{ display:
|
|
50
|
+
fieldRegistry.register('text', ({ value, onValueChange, label }) => (
|
|
51
|
+
<label style={{ display: 'grid', gap: 4 }}>
|
|
46
52
|
<span>{label}</span>
|
|
47
53
|
<input
|
|
48
|
-
value={value ??
|
|
54
|
+
value={value ?? ''}
|
|
49
55
|
onChange={(e) => onValueChange?.(e.target.value)}
|
|
50
56
|
/>
|
|
51
57
|
</label>
|
|
52
|
-
))
|
|
58
|
+
));
|
|
53
59
|
|
|
54
|
-
fieldRegistry.register(
|
|
55
|
-
<label style={{ display:
|
|
60
|
+
fieldRegistry.register('number', ({ value, onValueChange, label }) => (
|
|
61
|
+
<label style={{ display: 'grid', gap: 4 }}>
|
|
56
62
|
<span>{label}</span>
|
|
57
63
|
<input
|
|
58
64
|
type="number"
|
|
59
|
-
value={value ??
|
|
65
|
+
value={value ?? ''}
|
|
60
66
|
onChange={(e) => onValueChange?.(Number(e.target.value))}
|
|
61
67
|
/>
|
|
62
68
|
</label>
|
|
63
|
-
))
|
|
69
|
+
));
|
|
64
70
|
```
|
|
65
71
|
|
|
66
72
|
## Basic usage
|
|
67
73
|
|
|
68
74
|
```tsx
|
|
69
|
-
import { useState } from
|
|
70
|
-
import { MultiFieldInput } from
|
|
71
|
-
import type { FieldDescription } from
|
|
75
|
+
import { useState } from 'react';
|
|
76
|
+
import { MultiFieldInput } from '@dynamic-field-kit/react';
|
|
77
|
+
import type { FieldDescription } from '@dynamic-field-kit/core';
|
|
72
78
|
|
|
73
79
|
const fields: FieldDescription[] = [
|
|
74
|
-
{ name:
|
|
75
|
-
{ name:
|
|
76
|
-
]
|
|
80
|
+
{ name: 'name', type: 'text', label: 'Name' },
|
|
81
|
+
{ name: 'age', type: 'number', label: 'Age' },
|
|
82
|
+
];
|
|
77
83
|
|
|
78
84
|
export function Example() {
|
|
79
|
-
const [data, setData] = useState({})
|
|
85
|
+
const [data, setData] = useState({});
|
|
80
86
|
|
|
81
87
|
return (
|
|
82
88
|
<MultiFieldInput
|
|
@@ -84,7 +90,7 @@ export function Example() {
|
|
|
84
90
|
properties={data}
|
|
85
91
|
onChange={setData}
|
|
86
92
|
/>
|
|
87
|
-
)
|
|
93
|
+
);
|
|
88
94
|
}
|
|
89
95
|
```
|
|
90
96
|
|
|
@@ -101,7 +107,7 @@ Use a layout config object:
|
|
|
101
107
|
```tsx
|
|
102
108
|
<MultiFieldInput
|
|
103
109
|
fieldDescriptions={fields}
|
|
104
|
-
layout={{ type:
|
|
110
|
+
layout={{ type: 'grid', columns: 3, gap: 16 }}
|
|
105
111
|
/>
|
|
106
112
|
```
|
|
107
113
|
|
|
@@ -111,9 +117,9 @@ Use the built-in responsive layout:
|
|
|
111
117
|
<MultiFieldInput
|
|
112
118
|
fieldDescriptions={fields}
|
|
113
119
|
layout={{
|
|
114
|
-
type:
|
|
115
|
-
mobile:
|
|
116
|
-
desktop: { type:
|
|
120
|
+
type: 'responsive',
|
|
121
|
+
mobile: 'column',
|
|
122
|
+
desktop: { type: 'grid', columns: 2, gap: 12 },
|
|
117
123
|
}}
|
|
118
124
|
/>
|
|
119
125
|
```
|
|
@@ -121,11 +127,51 @@ Use the built-in responsive layout:
|
|
|
121
127
|
Register a custom layout:
|
|
122
128
|
|
|
123
129
|
```tsx
|
|
124
|
-
import { layoutRegistry } from
|
|
130
|
+
import { layoutRegistry } from '@dynamic-field-kit/react';
|
|
131
|
+
|
|
132
|
+
layoutRegistry.register('stack-tight', ({ children }) => (
|
|
133
|
+
<div style={{ display: 'grid', gap: 8 }}>{children}</div>
|
|
134
|
+
));
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Derived fields with `computeValue`
|
|
125
138
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
Give a field a `computeValue` to derive its value from the rest of the form data whenever any field changes:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
const fields: FieldDescription[] = [
|
|
143
|
+
{ name: 'firstName', type: 'text' },
|
|
144
|
+
{ name: 'lastName', type: 'text' },
|
|
145
|
+
{
|
|
146
|
+
name: 'fullName',
|
|
147
|
+
type: 'text',
|
|
148
|
+
computeValue: (data) =>
|
|
149
|
+
`${data.firstName ?? ''} ${data.lastName ?? ''}`.trim(),
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Repeatable field groups
|
|
155
|
+
|
|
156
|
+
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.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
const fields: FieldDescription[] = [
|
|
160
|
+
{
|
|
161
|
+
name: 'contacts',
|
|
162
|
+
type: 'group',
|
|
163
|
+
label: 'Contacts',
|
|
164
|
+
fields: [
|
|
165
|
+
{ name: 'email', type: 'text', label: 'Email' },
|
|
166
|
+
{ name: 'phone', type: 'text', label: 'Phone' },
|
|
167
|
+
],
|
|
168
|
+
defaultItem: { email: '', phone: '' },
|
|
169
|
+
minItems: 1,
|
|
170
|
+
maxItems: 5,
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
<MultiFieldInput fieldDescriptions={fields} />;
|
|
129
175
|
```
|
|
130
176
|
|
|
131
177
|
## Type augmentation
|
|
@@ -133,12 +179,12 @@ layoutRegistry.register("stack-tight", ({ children }) => (
|
|
|
133
179
|
Add your app's field types through module augmentation:
|
|
134
180
|
|
|
135
181
|
```ts
|
|
136
|
-
import
|
|
182
|
+
import '@dynamic-field-kit/core';
|
|
137
183
|
|
|
138
|
-
declare module
|
|
184
|
+
declare module '@dynamic-field-kit/core' {
|
|
139
185
|
interface FieldTypeMap {
|
|
140
|
-
text: string
|
|
141
|
-
number: number
|
|
186
|
+
text: string;
|
|
187
|
+
number: number;
|
|
142
188
|
}
|
|
143
189
|
}
|
|
144
190
|
```
|
|
@@ -147,8 +193,9 @@ declare module "@dynamic-field-kit/core" {
|
|
|
147
193
|
|
|
148
194
|
- `@dynamic-field-kit/core` stays framework-agnostic and does not export React-specific JSX types.
|
|
149
195
|
- `@dynamic-field-kit/react` narrows the shared registry to React component types so `fieldRegistry.get(type)` can be rendered safely in TSX.
|
|
150
|
-
- `MultiFieldInput` filters fields using `appearCondition`.
|
|
196
|
+
- `MultiFieldInput` filters fields using `appearCondition` and derives fields using `computeValue`.
|
|
151
197
|
- `DynamicInput` renders `Unknown field type: ...` when a renderer is missing.
|
|
198
|
+
- Fields with `fields` render as repeatable groups instead of going through `fieldRegistry`.
|
|
152
199
|
|
|
153
200
|
## License
|
|
154
201
|
|
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
3
3
|
import { FieldTypeKey, Properties, FieldDescription, FieldTypeMap, FieldRendererProps } from '@dynamic-field-kit/core';
|
|
4
4
|
export { FieldDescription, FieldRendererProps, FieldTypeKey } from '@dynamic-field-kit/core';
|
|
5
5
|
|
|
6
|
-
type LayoutRenderer<C =
|
|
6
|
+
type LayoutRenderer<C = unknown> = (props: {
|
|
7
7
|
children: React.ReactNode;
|
|
8
8
|
config?: C;
|
|
9
9
|
}) => React.ReactElement;
|
|
@@ -14,34 +14,35 @@ declare class LayoutRegistry {
|
|
|
14
14
|
}
|
|
15
15
|
declare const layoutRegistry: LayoutRegistry;
|
|
16
16
|
|
|
17
|
-
type BaseLayout =
|
|
18
|
-
type:
|
|
17
|
+
type BaseLayout = 'column' | 'row' | {
|
|
18
|
+
type: 'grid';
|
|
19
19
|
columns?: number;
|
|
20
20
|
gap?: number;
|
|
21
21
|
};
|
|
22
22
|
type LayoutConfig = BaseLayout | {
|
|
23
|
-
type:
|
|
23
|
+
type: 'responsive';
|
|
24
24
|
mobile: BaseLayout;
|
|
25
25
|
desktop: BaseLayout;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
interface Props$2<T extends FieldTypeKey> {
|
|
29
29
|
type: T;
|
|
30
|
-
value?:
|
|
31
|
-
onChange?: (value:
|
|
30
|
+
value?: unknown;
|
|
31
|
+
onChange?: (value: unknown) => void;
|
|
32
32
|
label?: string;
|
|
33
33
|
options?: Properties[];
|
|
34
34
|
className?: string;
|
|
35
35
|
description?: ReactNode;
|
|
36
36
|
}
|
|
37
|
-
declare const
|
|
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 DynamicInput: typeof DynamicInputInner;
|
|
38
39
|
|
|
39
40
|
interface Props$1 {
|
|
40
41
|
fieldDescription: FieldDescription;
|
|
41
42
|
renderInfos: Properties;
|
|
42
|
-
onValueChangeField: (value:
|
|
43
|
+
onValueChangeField: (value: unknown, key: string) => void;
|
|
43
44
|
}
|
|
44
|
-
declare const FieldInput: ({ fieldDescription, renderInfos, onValueChangeField }: Props$1) => react_jsx_runtime.JSX.Element
|
|
45
|
+
declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, onValueChangeField, }: Props$1) => react_jsx_runtime.JSX.Element>;
|
|
45
46
|
|
|
46
47
|
interface Props {
|
|
47
48
|
fieldDescriptions: FieldDescription[];
|
|
@@ -49,9 +50,9 @@ interface Props {
|
|
|
49
50
|
onChange?: (data: Properties) => void;
|
|
50
51
|
layout?: LayoutConfig;
|
|
51
52
|
}
|
|
52
|
-
declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout }: Props) => react_jsx_runtime.JSX.Element;
|
|
53
|
+
declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, }: Props) => react_jsx_runtime.JSX.Element;
|
|
53
54
|
|
|
54
|
-
type ReactFieldRenderer<T =
|
|
55
|
+
type ReactFieldRenderer<T = unknown> = ComponentType<FieldRendererProps<T>>;
|
|
55
56
|
interface ReactFieldRegistry {
|
|
56
57
|
register<K extends keyof FieldTypeMap>(type: K, renderer: ReactFieldRenderer<FieldTypeMap[K]>): void;
|
|
57
58
|
get<K extends keyof FieldTypeMap>(type: K): ReactFieldRenderer<FieldTypeMap[K]> | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
3
3
|
import { FieldTypeKey, Properties, FieldDescription, FieldTypeMap, FieldRendererProps } from '@dynamic-field-kit/core';
|
|
4
4
|
export { FieldDescription, FieldRendererProps, FieldTypeKey } from '@dynamic-field-kit/core';
|
|
5
5
|
|
|
6
|
-
type LayoutRenderer<C =
|
|
6
|
+
type LayoutRenderer<C = unknown> = (props: {
|
|
7
7
|
children: React.ReactNode;
|
|
8
8
|
config?: C;
|
|
9
9
|
}) => React.ReactElement;
|
|
@@ -14,34 +14,35 @@ declare class LayoutRegistry {
|
|
|
14
14
|
}
|
|
15
15
|
declare const layoutRegistry: LayoutRegistry;
|
|
16
16
|
|
|
17
|
-
type BaseLayout =
|
|
18
|
-
type:
|
|
17
|
+
type BaseLayout = 'column' | 'row' | {
|
|
18
|
+
type: 'grid';
|
|
19
19
|
columns?: number;
|
|
20
20
|
gap?: number;
|
|
21
21
|
};
|
|
22
22
|
type LayoutConfig = BaseLayout | {
|
|
23
|
-
type:
|
|
23
|
+
type: 'responsive';
|
|
24
24
|
mobile: BaseLayout;
|
|
25
25
|
desktop: BaseLayout;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
interface Props$2<T extends FieldTypeKey> {
|
|
29
29
|
type: T;
|
|
30
|
-
value?:
|
|
31
|
-
onChange?: (value:
|
|
30
|
+
value?: unknown;
|
|
31
|
+
onChange?: (value: unknown) => void;
|
|
32
32
|
label?: string;
|
|
33
33
|
options?: Properties[];
|
|
34
34
|
className?: string;
|
|
35
35
|
description?: ReactNode;
|
|
36
36
|
}
|
|
37
|
-
declare const
|
|
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 DynamicInput: typeof DynamicInputInner;
|
|
38
39
|
|
|
39
40
|
interface Props$1 {
|
|
40
41
|
fieldDescription: FieldDescription;
|
|
41
42
|
renderInfos: Properties;
|
|
42
|
-
onValueChangeField: (value:
|
|
43
|
+
onValueChangeField: (value: unknown, key: string) => void;
|
|
43
44
|
}
|
|
44
|
-
declare const FieldInput: ({ fieldDescription, renderInfos, onValueChangeField }: Props$1) => react_jsx_runtime.JSX.Element
|
|
45
|
+
declare const FieldInput: React.MemoExoticComponent<({ fieldDescription, renderInfos, onValueChangeField, }: Props$1) => react_jsx_runtime.JSX.Element>;
|
|
45
46
|
|
|
46
47
|
interface Props {
|
|
47
48
|
fieldDescriptions: FieldDescription[];
|
|
@@ -49,9 +50,9 @@ interface Props {
|
|
|
49
50
|
onChange?: (data: Properties) => void;
|
|
50
51
|
layout?: LayoutConfig;
|
|
51
52
|
}
|
|
52
|
-
declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout }: Props) => react_jsx_runtime.JSX.Element;
|
|
53
|
+
declare const MultiFieldInput: ({ fieldDescriptions, properties, onChange, layout, }: Props) => react_jsx_runtime.JSX.Element;
|
|
53
54
|
|
|
54
|
-
type ReactFieldRenderer<T =
|
|
55
|
+
type ReactFieldRenderer<T = unknown> = ComponentType<FieldRendererProps<T>>;
|
|
55
56
|
interface ReactFieldRegistry {
|
|
56
57
|
register<K extends keyof FieldTypeMap>(type: K, renderer: ReactFieldRenderer<FieldTypeMap[K]>): void;
|
|
57
58
|
get<K extends keyof FieldTypeMap>(type: K): ReactFieldRenderer<FieldTypeMap[K]> | undefined;
|