@dynamic-field-kit/react 1.6.0 → 1.7.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 +58 -0
- package/README.md +101 -22
- package/dist/index.d.mts +37 -4
- package/dist/index.d.ts +37 -4
- package/dist/index.js +178 -55
- package/dist/index.mjs +191 -63
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @dynamic-field-kit/react
|
|
2
2
|
|
|
3
|
+
## 1.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Dirty-baseline rebasing, accessible validation errors, form-level message catalogs, and async field options across React, Vue, and Angular.
|
|
8
|
+
- 9b06e3f: Validation messages can be set once per form via `useDynamicForm({ messages })`,
|
|
9
|
+
or process-wide via `setDefaultMessages`, instead of passing a string to every
|
|
10
|
+
validator on every field. Built-in validators now resolve their message when
|
|
11
|
+
they run rather than when the field description is built, which is what made a
|
|
12
|
+
catalog impossible before. A message passed directly to a validator still wins,
|
|
13
|
+
and the English defaults are unchanged when no catalog is supplied.
|
|
14
|
+
|
|
15
|
+
`ValidationContext` - already `validate`'s fourth argument - gains an optional
|
|
16
|
+
`t` resolver, so a hand-written validator can translate its own messages too.
|
|
17
|
+
|
|
18
|
+
Adds `validators.matches(otherFieldName)` for confirm-password and
|
|
19
|
+
confirm-email fields, which every consumer was hand-writing.
|
|
20
|
+
|
|
21
|
+
No locale bundles ship: the mechanism is here, the translations are yours.
|
|
22
|
+
|
|
23
|
+
- a7358f9: Fix per-field `dirty`, which was measured against a baseline captured at mount
|
|
24
|
+
and never re-based - wrong after `reset(newValues)` on all three adapters, and
|
|
25
|
+
wrong on React and Vue for values that arrive after mount, where every field
|
|
26
|
+
reported dirty forever.
|
|
27
|
+
|
|
28
|
+
Adds `baselineValues` and `getDirtyValues()` to the form store on all three
|
|
29
|
+
adapters, and an `initialProperties` prop to `MultiFieldInput` for re-basing
|
|
30
|
+
without a store. Comparison moves from `!==` to `Object.is`, so a `NaN` numeric
|
|
31
|
+
field no longer reads as permanently dirty.
|
|
32
|
+
|
|
33
|
+
React's `useDynamicForm` no longer validates the same data twice per change.
|
|
34
|
+
|
|
35
|
+
- 53ed45a: `options` can now return a promise, covering both dependent selects
|
|
36
|
+
(`optionsDeps`) and search-remote pickers (`onOptionsQuery`). Renderers receive
|
|
37
|
+
`optionsStatus` and `optionsError` alongside `options`.
|
|
38
|
+
|
|
39
|
+
`debounceMs` was declared on `FieldDescription`, published in the `.d.ts` and
|
|
40
|
+
read by no implementation anywhere - setting it did nothing. It now debounces
|
|
41
|
+
these loads.
|
|
42
|
+
|
|
43
|
+
Debounce, abort of a superseded request, and discarding a response that lands
|
|
44
|
+
out of order all live in core's `createOptionsLoader`, so the three adapters
|
|
45
|
+
share one implementation. Synchronous and static options are untouched and never
|
|
46
|
+
enter a loading state.
|
|
47
|
+
|
|
48
|
+
- e35e876: `ariaDescribedBy` is now `${id}-error` when a field has an error instead of
|
|
49
|
+
being hard-coded `undefined`, and `makeErrorId` is exported so a custom renderer
|
|
50
|
+
can put the matching id on its message element. Without this,
|
|
51
|
+
`focusFirstInvalidField` had nothing to find for anyone following the official
|
|
52
|
+
renderer recipe.
|
|
53
|
+
|
|
54
|
+
Default renderers now render the validation message they were already being
|
|
55
|
+
handed - the one visible change in this release. Custom renderers are untouched,
|
|
56
|
+
so nobody gets two copies of their own message.
|
|
57
|
+
|
|
58
|
+
Development builds now warn when `FieldDescription.props` carries a key the
|
|
59
|
+
renderer prop contract owns, which 1.6.0 made possible to lose silently.
|
|
60
|
+
|
|
3
61
|
## 1.6.0
|
|
4
62
|
|
|
5
63
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -43,6 +43,11 @@ both packages:
|
|
|
43
43
|
- `validateFields` / `validateFieldsAsync` — a whole schema, returns `ValidationResult`
|
|
44
44
|
- `collectFieldPaths` — the leaf paths a schema actually has in the data (`contacts[0].email`)
|
|
45
45
|
- `indexGroupPathMap` — index an error or touched map by repeatable-group item
|
|
46
|
+
- `makeErrorId` — the id a renderer puts on its message element so
|
|
47
|
+
`aria-describedby` resolves
|
|
48
|
+
- `createOptionsLoader` / `isAsyncOptions` — the async options engine
|
|
49
|
+
- `createMessageResolver` / `setDefaultMessages` / `MessageCatalog` — validation
|
|
50
|
+
message catalog
|
|
46
51
|
- `resolveDisabled` / `resolveReadOnly` / `resolveOptions` — resolve a field's dynamic conditions and options
|
|
47
52
|
- `validators` — the built-in validator helpers (`required`, `email`, `minLength`, `compose`, …)
|
|
48
53
|
- `ValidationResult` / `ValidationContext`
|
|
@@ -140,6 +145,7 @@ const form = useDynamicForm({
|
|
|
140
145
|
initialValues: { country: 'VN' },
|
|
141
146
|
validateOnBlur: true, // default
|
|
142
147
|
validateOnChange: false, // default
|
|
148
|
+
messages: { required: 'Bắt buộc' }, // optional; see Validation & conditions
|
|
143
149
|
});
|
|
144
150
|
|
|
145
151
|
<form onSubmit={form.handleSubmit((data) => save(data))}>
|
|
@@ -163,6 +169,25 @@ const form = useDynamicForm({
|
|
|
163
169
|
/>
|
|
164
170
|
```
|
|
165
171
|
|
|
172
|
+
The `form` shorthand also carries `baselineValues`, which is what keeps
|
|
173
|
+
per-field `dirty` correct across a reset. Without a form store, pass the
|
|
174
|
+
baseline yourself:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
<MultiFieldInput
|
|
178
|
+
fieldDescriptions={fields}
|
|
179
|
+
properties={data}
|
|
180
|
+
initialProperties={original} // what `dirty` compares against
|
|
181
|
+
/>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Omit it and the baseline is the first non-`undefined` `properties` the
|
|
185
|
+
component sees — which is what an edit form wants when its values arrive from a
|
|
186
|
+
fetch after mount. Note that `{}` counts as a real value: a form
|
|
187
|
+
that opens blank cannot be told apart from one still waiting on a fetch, so
|
|
188
|
+
pass `initialProperties` when `properties` starts as `{}` rather than
|
|
189
|
+
`undefined`.
|
|
190
|
+
|
|
166
191
|
Passing `touched` and `errors` gives the form store ownership of renderer
|
|
167
192
|
metadata. `touched` is what makes an invalid submit visible: `handleSubmit`
|
|
168
193
|
marks every field touched before validating, so a renderer that gates its error
|
|
@@ -170,28 +195,30 @@ on `touched` shows it even for fields the user never focused. `reset()` clears
|
|
|
170
195
|
touched the same way. Individually passed props win over the ones `form`
|
|
171
196
|
derives, so you can pass `form` and still override one wire.
|
|
172
197
|
|
|
173
|
-
| Member | Description
|
|
174
|
-
| ----------------------------------- |
|
|
175
|
-
| `data` | Current form data, with `computeValue` fields applied
|
|
176
|
-
| `errors` | `Record<string, string[]>`, keyed like `validateFields`
|
|
177
|
-
| `isValid` / `isDirty` | Current synchronous validity / any value has changed
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
198
|
+
| Member | Description |
|
|
199
|
+
| ----------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
200
|
+
| `data` | Current form data, with `computeValue` fields applied |
|
|
201
|
+
| `errors` | `Record<string, string[]>`, keyed like `validateFields` |
|
|
202
|
+
| `isValid` / `isDirty` | Current synchronous validity / any value has changed |
|
|
203
|
+
| `baselineValues` | The values `dirty` is measured against — `initialValues` until `reset(newValues)` replaces them |
|
|
204
|
+
| `getDirtyValues()` | Only the entries differing from `baselineValues`, for PATCH-style submits |
|
|
205
|
+
| `isValidating` | An async validation pass is in flight |
|
|
206
|
+
| `isValidationComplete` | Every applicable validator finished and none is in flight |
|
|
207
|
+
| `validationStatus` | `'valid' | 'invalid' | 'pending'`— prefer it over`isValid`alone:`valid` cannot tell "nothing is wrong" from "nothing is wrong yet" |
|
|
208
|
+
| `isSubmitting` / `isSubmitted` | In-flight submit / at least one submit attempted |
|
|
209
|
+
| `touched` | Fields that have been blurred |
|
|
210
|
+
| `handleChange(data)` | Replace the whole form data — pass to `MultiFieldInput`'s `onChange` |
|
|
211
|
+
| `setFieldValue(name, value)` | Change one field |
|
|
212
|
+
| `handleBlur(name)` | Mark touched, and validate when `validateOnBlur` |
|
|
213
|
+
| `setFieldTouched(name, value?)` | Set touched explicitly |
|
|
214
|
+
| `touchAll()` | Mark every field touched — `handleSubmit` already calls it |
|
|
215
|
+
| `resetTouched()` | Clear touched only, leaving data/errors/dirty alone |
|
|
216
|
+
| `setTouched` | Raw setter for the whole touched map |
|
|
217
|
+
| `setData` | Raw state setter, for escape hatches |
|
|
218
|
+
| `validate()` | Validate now, returns a boolean |
|
|
219
|
+
| `validateAsync()` | Validate now, awaiting Promise-based rules |
|
|
220
|
+
| `reset(values?)` | Back to `initialValues` (or the values given), clearing errors/touched/submission |
|
|
221
|
+
| `handleSubmit(onValid, onInvalid?)` | Returns a submit handler; calls `preventDefault`, validates, then dispatches |
|
|
195
222
|
|
|
196
223
|
Leave `touched` off and `MultiFieldInput` falls back to tracking it internally
|
|
197
224
|
from blur alone, as it always did. In that mode nothing outside the component
|
|
@@ -245,6 +272,13 @@ const Base = getDefaultRenderer('date'); // undefined for an unknown type
|
|
|
245
272
|
`file` emits a `File` (or `File[]` when `multiple` is set), `range` and `number`
|
|
246
273
|
emit numbers, `checkbox` / `switch` emit booleans; everything else emits strings.
|
|
247
274
|
|
|
275
|
+
Since 1.7.0 a default renderer also renders its validation message, as
|
|
276
|
+
`<div id="{fieldId}-error" class="dfk-field-error" role="alert">`, which is what
|
|
277
|
+
`aria-describedby` points at. Before that they were handed `error` and dropped
|
|
278
|
+
it, so the form showed nothing. A registered custom renderer is unaffected — the
|
|
279
|
+
node is emitted only where a default was used, so you never get two copies. Hide
|
|
280
|
+
it with `.dfk-field-error { display: none }` if you want the old silence.
|
|
281
|
+
|
|
248
282
|
## DevTools
|
|
249
283
|
|
|
250
284
|
```tsx
|
|
@@ -350,6 +384,51 @@ fieldRegistry.register('text', ({ value, onValueChange, error, disabled }) => (
|
|
|
350
384
|
));
|
|
351
385
|
```
|
|
352
386
|
|
|
387
|
+
Forward `ariaInvalid`, `ariaRequired` and `ariaDescribedBy` too, and put
|
|
388
|
+
`makeErrorId(id)` on whatever element renders the message. `focusFirstInvalidField`
|
|
389
|
+
selects `[aria-invalid="true"]`, so a renderer that drops those props makes that
|
|
390
|
+
helper silently do nothing. See
|
|
391
|
+
[the recipes](../../docs/ui-kit-recipes.md#forward-the-aria-props).
|
|
392
|
+
|
|
393
|
+
### Validation messages
|
|
394
|
+
|
|
395
|
+
Set the built-in validators' messages once per form instead of on every field:
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
const form = useDynamicForm({
|
|
399
|
+
fields,
|
|
400
|
+
messages: { required: 'Bắt buộc', minLength: 'Tối thiểu {min} ký tự' },
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
A message passed straight to a validator still wins, and any key omitted falls
|
|
405
|
+
back to the English default. `setDefaultMessages(catalog)` sets a process-wide
|
|
406
|
+
one for code calling `validateFields` directly. Full key list in the
|
|
407
|
+
[core README](../core/README.md#validation-messages). **No locale bundles
|
|
408
|
+
ship** — the mechanism is here, the translations are yours.
|
|
409
|
+
|
|
410
|
+
### Async options
|
|
411
|
+
|
|
412
|
+
`options` may return a promise, and the renderer receives `optionsStatus`
|
|
413
|
+
(`'idle' | 'loading' | 'ready' | 'error'`), `optionsError` and `onOptionsQuery`:
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
{
|
|
417
|
+
name: 'assignee',
|
|
418
|
+
type: 'userPicker',
|
|
419
|
+
options: async (data, _rootData, ctx) =>
|
|
420
|
+
fetch(`/api/users?q=${ctx?.query ?? ''}`, { signal: ctx?.signal })
|
|
421
|
+
.then((r) => r.json()),
|
|
422
|
+
optionsDeps: (data) => [data.team], // reload when this changes; default []
|
|
423
|
+
debounceMs: 300, // collapses rapid reloads into one fetch
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Superseded requests are aborted and out-of-order responses discarded, so the
|
|
428
|
+
list always reflects the newest request. Static and synchronous options are
|
|
429
|
+
untouched and never enter a loading state. See the
|
|
430
|
+
[core README](../core/README.md#async-options).
|
|
431
|
+
|
|
353
432
|
## Repeatable field groups
|
|
354
433
|
|
|
355
434
|
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.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { ReactNode, ComponentType } from 'react';
|
|
2
|
-
import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldRendererProps, FieldTypeMap } from '@dynamic-field-kit/core';
|
|
3
|
-
export { FIELD_RENDERER_PROP_KEYS, FieldDescription, FieldRegistry, FieldRendererProps, FieldTypeKey, LayoutConfig, ValidationContext, ValidationResult, buildFieldRendererProps, collectFieldPaths, indexGroupPathMap, makeFieldId, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
|
|
2
|
+
import { FieldTypeKey, Properties, OptionsStatus, FieldDescription, LayoutConfig, ValidationResult, MessageCatalog, FieldRendererProps, FieldTypeMap } from '@dynamic-field-kit/core';
|
|
3
|
+
export { FIELD_RENDERER_PROP_KEYS, FieldDescription, FieldRegistry, FieldRendererProps, FieldTypeKey, LayoutConfig, ValidationContext, ValidationResult, buildFieldRendererProps, collectFieldPaths, indexGroupPathMap, makeErrorId, makeFieldId, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
|
|
4
4
|
|
|
5
5
|
type LayoutRenderer<C = unknown> = (props: {
|
|
6
6
|
children: React.ReactNode;
|
|
@@ -21,6 +21,8 @@ interface Props$2<T extends FieldTypeKey> {
|
|
|
21
21
|
label?: string;
|
|
22
22
|
placeholder?: string;
|
|
23
23
|
options?: Properties[];
|
|
24
|
+
optionsStatus?: OptionsStatus;
|
|
25
|
+
optionsError?: unknown;
|
|
24
26
|
className?: string;
|
|
25
27
|
description?: ReactNode;
|
|
26
28
|
disabled?: boolean;
|
|
@@ -40,8 +42,10 @@ interface Props$2<T extends FieldTypeKey> {
|
|
|
40
42
|
multiple?: boolean;
|
|
41
43
|
/** Extra, framework-agnostic props forwarded verbatim to the renderer. */
|
|
42
44
|
extraProps?: Properties;
|
|
45
|
+
/** Renderer-driven refetch for a search-remote field. */
|
|
46
|
+
onOptionsQuery?: (query: string) => void;
|
|
43
47
|
}
|
|
44
|
-
declare const DynamicInputInner: <T extends FieldTypeKey>({ type, onChange, onBlur, extraProps, ...rendererProps }: Props$2<T>) => React.JSX.Element;
|
|
48
|
+
declare const DynamicInputInner: <T extends FieldTypeKey>({ type, onChange, onBlur, extraProps, onOptionsQuery, ...rendererProps }: Props$2<T>) => React.JSX.Element;
|
|
45
49
|
declare const DynamicInput: typeof DynamicInputInner;
|
|
46
50
|
|
|
47
51
|
interface Props$1 {
|
|
@@ -67,6 +71,8 @@ interface DynamicFormBinding {
|
|
|
67
71
|
data: Properties;
|
|
68
72
|
errors: Record<string, string[]>;
|
|
69
73
|
touched: Record<string, boolean>;
|
|
74
|
+
/** The values per-field `dirty` is measured against. See `useDynamicForm`. */
|
|
75
|
+
baselineValues?: Properties;
|
|
70
76
|
handleChange: (data: Properties) => void;
|
|
71
77
|
handleBlur: (fieldName: string) => void;
|
|
72
78
|
}
|
|
@@ -95,6 +101,14 @@ interface Props {
|
|
|
95
101
|
* restores the pre-1.6 ids), or set `FieldDescription.id` per field.
|
|
96
102
|
*/
|
|
97
103
|
idPrefix?: string;
|
|
104
|
+
/**
|
|
105
|
+
* The values per-field `dirty` is measured against. Defaults to the first
|
|
106
|
+
* non-`undefined` `properties` this component sees - which is what an edit
|
|
107
|
+
* form wants when its values arrive from a fetch after mount. Supplied
|
|
108
|
+
* automatically by the `form` shorthand; pass it explicitly to re-base
|
|
109
|
+
* `dirty` without going through a form store.
|
|
110
|
+
*/
|
|
111
|
+
initialProperties?: Properties;
|
|
98
112
|
/**
|
|
99
113
|
* Top-level form data, threaded down through repeatable groups so a nested
|
|
100
114
|
* field's `appearCondition`/`computeValue` can read the root form. Omitted at
|
|
@@ -150,6 +164,13 @@ interface UseDynamicFormOptions {
|
|
|
150
164
|
initialValues?: Properties;
|
|
151
165
|
validateOnBlur?: boolean;
|
|
152
166
|
validateOnChange?: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Messages for the built-in validators, set once for the whole form instead
|
|
169
|
+
* of per field. A message passed directly to a validator still wins, and any
|
|
170
|
+
* key omitted here falls back to the validator's English default. See core's
|
|
171
|
+
* `MessageCatalog`.
|
|
172
|
+
*/
|
|
173
|
+
messages?: MessageCatalog;
|
|
153
174
|
}
|
|
154
175
|
interface UseDynamicFormResult {
|
|
155
176
|
data: Properties;
|
|
@@ -159,6 +180,18 @@ interface UseDynamicFormResult {
|
|
|
159
180
|
isValidationComplete: boolean;
|
|
160
181
|
validationStatus: ValidationResult['status'];
|
|
161
182
|
isDirty: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* The values `dirty` is measured against: the `initialValues` option until
|
|
185
|
+
* `reset(newValues)` replaces them. Distinct from that option, which never
|
|
186
|
+
* changes - pass this to `MultiFieldInput` (or use the `form` shorthand) so
|
|
187
|
+
* per-field `dirty` survives a reset.
|
|
188
|
+
*/
|
|
189
|
+
baselineValues: Properties;
|
|
190
|
+
/**
|
|
191
|
+
* The entries of `data` that differ from `baselineValues`. Intended for
|
|
192
|
+
* PATCH-style submits that should carry only what the user actually edited.
|
|
193
|
+
*/
|
|
194
|
+
getDirtyValues: () => Properties;
|
|
162
195
|
isSubmitting: boolean;
|
|
163
196
|
isSubmitted: boolean;
|
|
164
197
|
touched: Record<string, boolean>;
|
|
@@ -183,7 +216,7 @@ interface UseDynamicFormResult {
|
|
|
183
216
|
validateAsync: () => Promise<boolean>;
|
|
184
217
|
handleSubmit: (onValid: (data: Properties) => void | Promise<void>, onInvalid?: (errors: Record<string, string[]>) => void) => (e?: React.FormEvent) => Promise<void>;
|
|
185
218
|
}
|
|
186
|
-
declare function useDynamicForm({ fields, initialValues, validateOnBlur, validateOnChange, }: UseDynamicFormOptions): UseDynamicFormResult;
|
|
219
|
+
declare function useDynamicForm({ fields, initialValues, validateOnBlur, validateOnChange, messages, }: UseDynamicFormOptions): UseDynamicFormResult;
|
|
187
220
|
|
|
188
221
|
declare const defaultRenderersMap: Record<string, React.FC<FieldRendererProps>>;
|
|
189
222
|
declare function getDefaultRenderer(type: string): React.FC<FieldRendererProps> | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { ReactNode, ComponentType } from 'react';
|
|
2
|
-
import { FieldTypeKey, Properties, FieldDescription, LayoutConfig, ValidationResult, FieldRendererProps, FieldTypeMap } from '@dynamic-field-kit/core';
|
|
3
|
-
export { FIELD_RENDERER_PROP_KEYS, FieldDescription, FieldRegistry, FieldRendererProps, FieldTypeKey, LayoutConfig, ValidationContext, ValidationResult, buildFieldRendererProps, collectFieldPaths, indexGroupPathMap, makeFieldId, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
|
|
2
|
+
import { FieldTypeKey, Properties, OptionsStatus, FieldDescription, LayoutConfig, ValidationResult, MessageCatalog, FieldRendererProps, FieldTypeMap } from '@dynamic-field-kit/core';
|
|
3
|
+
export { FIELD_RENDERER_PROP_KEYS, FieldDescription, FieldRegistry, FieldRendererProps, FieldTypeKey, LayoutConfig, ValidationContext, ValidationResult, buildFieldRendererProps, collectFieldPaths, indexGroupPathMap, makeErrorId, makeFieldId, resolveDisabled, resolveOptions, resolveReadOnly, validateField, validateFieldAsync, validateFields, validateFieldsAsync, validators } from '@dynamic-field-kit/core';
|
|
4
4
|
|
|
5
5
|
type LayoutRenderer<C = unknown> = (props: {
|
|
6
6
|
children: React.ReactNode;
|
|
@@ -21,6 +21,8 @@ interface Props$2<T extends FieldTypeKey> {
|
|
|
21
21
|
label?: string;
|
|
22
22
|
placeholder?: string;
|
|
23
23
|
options?: Properties[];
|
|
24
|
+
optionsStatus?: OptionsStatus;
|
|
25
|
+
optionsError?: unknown;
|
|
24
26
|
className?: string;
|
|
25
27
|
description?: ReactNode;
|
|
26
28
|
disabled?: boolean;
|
|
@@ -40,8 +42,10 @@ interface Props$2<T extends FieldTypeKey> {
|
|
|
40
42
|
multiple?: boolean;
|
|
41
43
|
/** Extra, framework-agnostic props forwarded verbatim to the renderer. */
|
|
42
44
|
extraProps?: Properties;
|
|
45
|
+
/** Renderer-driven refetch for a search-remote field. */
|
|
46
|
+
onOptionsQuery?: (query: string) => void;
|
|
43
47
|
}
|
|
44
|
-
declare const DynamicInputInner: <T extends FieldTypeKey>({ type, onChange, onBlur, extraProps, ...rendererProps }: Props$2<T>) => React.JSX.Element;
|
|
48
|
+
declare const DynamicInputInner: <T extends FieldTypeKey>({ type, onChange, onBlur, extraProps, onOptionsQuery, ...rendererProps }: Props$2<T>) => React.JSX.Element;
|
|
45
49
|
declare const DynamicInput: typeof DynamicInputInner;
|
|
46
50
|
|
|
47
51
|
interface Props$1 {
|
|
@@ -67,6 +71,8 @@ interface DynamicFormBinding {
|
|
|
67
71
|
data: Properties;
|
|
68
72
|
errors: Record<string, string[]>;
|
|
69
73
|
touched: Record<string, boolean>;
|
|
74
|
+
/** The values per-field `dirty` is measured against. See `useDynamicForm`. */
|
|
75
|
+
baselineValues?: Properties;
|
|
70
76
|
handleChange: (data: Properties) => void;
|
|
71
77
|
handleBlur: (fieldName: string) => void;
|
|
72
78
|
}
|
|
@@ -95,6 +101,14 @@ interface Props {
|
|
|
95
101
|
* restores the pre-1.6 ids), or set `FieldDescription.id` per field.
|
|
96
102
|
*/
|
|
97
103
|
idPrefix?: string;
|
|
104
|
+
/**
|
|
105
|
+
* The values per-field `dirty` is measured against. Defaults to the first
|
|
106
|
+
* non-`undefined` `properties` this component sees - which is what an edit
|
|
107
|
+
* form wants when its values arrive from a fetch after mount. Supplied
|
|
108
|
+
* automatically by the `form` shorthand; pass it explicitly to re-base
|
|
109
|
+
* `dirty` without going through a form store.
|
|
110
|
+
*/
|
|
111
|
+
initialProperties?: Properties;
|
|
98
112
|
/**
|
|
99
113
|
* Top-level form data, threaded down through repeatable groups so a nested
|
|
100
114
|
* field's `appearCondition`/`computeValue` can read the root form. Omitted at
|
|
@@ -150,6 +164,13 @@ interface UseDynamicFormOptions {
|
|
|
150
164
|
initialValues?: Properties;
|
|
151
165
|
validateOnBlur?: boolean;
|
|
152
166
|
validateOnChange?: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Messages for the built-in validators, set once for the whole form instead
|
|
169
|
+
* of per field. A message passed directly to a validator still wins, and any
|
|
170
|
+
* key omitted here falls back to the validator's English default. See core's
|
|
171
|
+
* `MessageCatalog`.
|
|
172
|
+
*/
|
|
173
|
+
messages?: MessageCatalog;
|
|
153
174
|
}
|
|
154
175
|
interface UseDynamicFormResult {
|
|
155
176
|
data: Properties;
|
|
@@ -159,6 +180,18 @@ interface UseDynamicFormResult {
|
|
|
159
180
|
isValidationComplete: boolean;
|
|
160
181
|
validationStatus: ValidationResult['status'];
|
|
161
182
|
isDirty: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* The values `dirty` is measured against: the `initialValues` option until
|
|
185
|
+
* `reset(newValues)` replaces them. Distinct from that option, which never
|
|
186
|
+
* changes - pass this to `MultiFieldInput` (or use the `form` shorthand) so
|
|
187
|
+
* per-field `dirty` survives a reset.
|
|
188
|
+
*/
|
|
189
|
+
baselineValues: Properties;
|
|
190
|
+
/**
|
|
191
|
+
* The entries of `data` that differ from `baselineValues`. Intended for
|
|
192
|
+
* PATCH-style submits that should carry only what the user actually edited.
|
|
193
|
+
*/
|
|
194
|
+
getDirtyValues: () => Properties;
|
|
162
195
|
isSubmitting: boolean;
|
|
163
196
|
isSubmitted: boolean;
|
|
164
197
|
touched: Record<string, boolean>;
|
|
@@ -183,7 +216,7 @@ interface UseDynamicFormResult {
|
|
|
183
216
|
validateAsync: () => Promise<boolean>;
|
|
184
217
|
handleSubmit: (onValid: (data: Properties) => void | Promise<void>, onInvalid?: (errors: Record<string, string[]>) => void) => (e?: React.FormEvent) => Promise<void>;
|
|
185
218
|
}
|
|
186
|
-
declare function useDynamicForm({ fields, initialValues, validateOnBlur, validateOnChange, }: UseDynamicFormOptions): UseDynamicFormResult;
|
|
219
|
+
declare function useDynamicForm({ fields, initialValues, validateOnBlur, validateOnChange, messages, }: UseDynamicFormOptions): UseDynamicFormResult;
|
|
187
220
|
|
|
188
221
|
declare const defaultRenderersMap: Record<string, React.FC<FieldRendererProps>>;
|
|
189
222
|
declare function getDefaultRenderer(type: string): React.FC<FieldRendererProps> | undefined;
|