@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 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: 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`
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
- export { DynamicInput, FieldInput, type LayoutConfig, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, fieldRegistry, layoutRegistry };
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
- export { DynamicInput, FieldInput, type LayoutConfig, MultiFieldInput, type ReactFieldRegistry, type ReactFieldRenderer, fieldRegistry, layoutRegistry };
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 };