feat(@vben-core/form-ui): add typed form value codecs (#8189)
* feat(@vben-core/form-ui): add typed form value codecs * test(@vben-core/form-ui): cover codec value boundaries * refactor(project): propagate form codec generics * feat(@vben/plugins): propagate form codecs through vxe grids * refactor(@vben/playground): migrate form codec examples * refactor(@vben/playground): migrate query forms to codecs * docs(@vben/docs): document form value codecs
This commit is contained in:
@@ -207,7 +207,7 @@ Create the form through `useVbenForm`:
|
||||
|
||||
## Typed Values and Slots
|
||||
|
||||
Declare the value shape once with `useVbenForm<TValues>`. The same type flows through value APIs, callbacks, selectors, and field/default/action slots:
|
||||
Use `useVbenForm<TFormValues, TSubmitValues>` to declare component-facing form values and submission values separately. Schema, slots, selectors, and `setValues` use `TFormValues`; `getValues`, submit, and the first `handleSubmit` argument use `TSubmitValues`. Pass one generic when both shapes are identical.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
@@ -254,30 +254,47 @@ async function fillForm() {
|
||||
|
||||
Named field slots expose `field`, `componentField`, `modelValue`, `name`, `disabled`, `isInValid`, `values`, and `formApi`. The default slot exposes `shapes`, `values`, and `formApi`; action slots expose `values` and `formApi`. Forms without an explicit `TValues` remain compatible with arbitrary slot names and broad props.
|
||||
|
||||
## Value Formatting
|
||||
## Form Codec
|
||||
|
||||
Use `schema.valueFormat` when the component value is convenient for the UI but the final payload returned by `getValues()` should use a different shape.
|
||||
Use the form-level `codec` when component values and the backend payload have different shapes. `encode` converts the complete `TFormValues` object to `TSubmitValues`; `decode` performs the inverse conversion. Multi-field splits and merges are atomic and do not depend on schema order or string-path writes.
|
||||
|
||||
- return a value to write back to the current field
|
||||
- call `setValue(key, nextValue)` to write derived fields
|
||||
- return `undefined` to keep the original field removed after decomposition
|
||||
Define `codec` directly in the `useVbenForm` options. Annotate only the form-value input of `encode`; `TSubmitValues` is inferred from its return object and flows into `decode`, `getValues()`, and submit callbacks:
|
||||
|
||||
```ts
|
||||
const [Form, formApi] = useVbenForm({
|
||||
codec: {
|
||||
decode(values) {
|
||||
return { period: [values.startTime, values.endTime] };
|
||||
},
|
||||
encode(values: Readonly<FormValues>) {
|
||||
return {
|
||||
endTime: values.period[1],
|
||||
startTime: values.period[0],
|
||||
};
|
||||
},
|
||||
},
|
||||
schema,
|
||||
});
|
||||
```
|
||||
|
||||
<DemoPreview dir="demos/vben-form/value-format" />
|
||||
|
||||
`schema.valueFormat`, `fieldMappingTime`, and `arrayToStringFields` remain runtime-compatible but are deprecated. When a codec is configured it takes precedence and deprecated transforms are ignored.
|
||||
|
||||
## Key API Notes
|
||||
|
||||
- `useVbenForm` returns `[Form, formApi]`
|
||||
- `useVbenForm<TValues>` propagates values through APIs, callbacks, schema callbacks, and slots
|
||||
- `useVbenForm<TFormValues, TSubmitValues>` keeps component values and submission values distinct
|
||||
- prefer `reset`, `submit`, `validateAndSubmit`, and `clearValidation`
|
||||
- `resetForm`, `submitForm`, `validateAndSubmitForm`, and `resetValidate` remain deprecated aliases that warn once in development
|
||||
- `clearValidation` invalidates in-flight async results before clearing errors
|
||||
- `formApi.getFieldComponentRef()` and `formApi.getFocusedField()` are available in current versions
|
||||
- `handleValuesChange(values, fieldsChanged)` receives readonly raw form state before `valueFormat`, `fieldMappingTime`, or array-to-string conversion
|
||||
- `handleValuesChange(values, fieldsChanged)` receives readonly `TFormValues` before codec or legacy formatting
|
||||
- its third `getFormattedValues` argument formats lazily, so raw-only change handlers avoid clone and transform work
|
||||
- `getRawValues()` returns only an independent raw snapshot, `getValues()` returns only the formatted payload, and `getValueSnapshot()` returns both
|
||||
- `handleSubmit(values, rawValues)` receives the formatted payload and its corresponding raw snapshot
|
||||
- `fieldMappingTime` and `scrollToFirstError` are part of the current form props
|
||||
- `schema.valueFormat` lets `getValues()` transform UI values into backend-friendly payloads
|
||||
- `fieldMappingTime`, `arrayToStringFields`, and `schema.valueFormat` are deprecated compatibility options
|
||||
- `codec.encode` defines the `getValues()` payload and `codec.decode` powers complete `setSubmitValues()` fills
|
||||
- `formApi.form` is the stable `FormContextApi`; raw TanStack generics are intentionally not exposed
|
||||
- prefer `formApi.form.useFieldValue`, `useFieldValues`, and `useFieldError` for fine-grained subscriptions; use `useValues` only when the whole form is required
|
||||
- `useSelector` remains the compatibility selector for combined `{ values, errors, meta }` state
|
||||
|
||||
@@ -23,7 +23,7 @@ The following Vben APIs remain supported:
|
||||
|
||||
- `useVbenForm(options)` returning `[Form, formApi]`
|
||||
- existing `FormApi` methods for values, reset, validation, submission, schema updates, and component refs
|
||||
- existing `FormSchema` fields, dependencies, `valueFormat`, and array schema structure
|
||||
- existing `FormSchema` fields, dependencies, deprecated `valueFormat`, and array schema structure
|
||||
- application adapters and the re-exported `z` namespace
|
||||
- the existing `componentField` slot and binding shape
|
||||
|
||||
@@ -42,9 +42,9 @@ New code uses `reset`, `submit`, `validateAndSubmit`, and `clearValidation`. The
|
||||
| `useFieldValue(fieldName)` | `FormContextApi` | Subscribes to one field value without reacting to unrelated fields. |
|
||||
| `useFieldValues(fieldNames)` | `FormContextApi` | Subscribes to a declared group of field values. |
|
||||
| `useFieldError(fieldName)` | `FormContextApi` | Subscribes to one field error without consuming the full error object. |
|
||||
| `getRawValues()` | `FormApi` | Returns an independent raw snapshot before field mapping and `valueFormat`. |
|
||||
| `getRawValues()` | `FormApi` | Returns an independent form-value snapshot before codec or legacy formatting. |
|
||||
| `formatValues(rawValues)` | `FormApi` | Runs the unified formatting pipeline on a supplied raw snapshot. |
|
||||
| `getValueSnapshot()` | `FormApi` | Returns `{ rawValues, values }`, where `values` is the formatted payload. |
|
||||
| `getValueSnapshot()` | `FormApi` | Returns `{ rawValues, values }`, where `values` is `TSubmitValues`. |
|
||||
| `asyncDebounceMs` | `FormFieldOptions` | Configures TanStack Field async validation debounce. |
|
||||
| `changeEventFallback` | `FormCommonConfig` / adapter config | Enables fallback for legacy components that emit `change` without `update:*`; defaults to `false`. |
|
||||
|
||||
@@ -60,7 +60,7 @@ New code uses `reset`, `submit`, `validateAndSubmit`, and `clearValidation`. The
|
||||
| Change-event compatibility | `disabledOnChangeListener: false` enabled fallback | `changeEventFallback: true` enables fallback with positive semantics. |
|
||||
| Top-level render callbacks | `componentProps(values, actions, ctx)`, `help(values, actions, ctx)`, `renderComponentContent(values, actions, ctx)` | Receive only lightweight `FormSchemaContext`; value-dependent behavior moves to `dependencies.resolve`. |
|
||||
| `validateAndSubmit()` | Repeated low-level validation/scroll handling and could validate again during submit | Delegates to canonical `validate()` and shared submission logic; invalid forms do not submit. |
|
||||
| `getValues()` | Implicitly returned transformed values | Still returns the formatted payload; use `getRawValues()` for raw state. |
|
||||
| `getValues()` | Implicitly returned transformed values | Returns codec-encoded `TSubmitValues`; without a codec it preserves legacy formatting. |
|
||||
|
||||
### Removed APIs
|
||||
|
||||
@@ -86,7 +86,7 @@ New code uses `reset`, `submit`, `validateAndSubmit`, and `clearValidation`. The
|
||||
- Field components use fine-grained value/error selectors; full error aggregation is no longer on the normal input path.
|
||||
- Async validators discard stale Promises through a Vben generation without reading private TanStack AbortController or meta fields.
|
||||
- New and legacy dependencies share one atomic executor, so stale async results cannot overwrite newer state.
|
||||
- Formatting runs in a fixed array-to-string, range mapping, schema `valueFormat` order and performs one deep clone per formatted snapshot.
|
||||
- New code uses one form-level codec to encode the complete object atomically; legacy array-to-string, range mapping, and schema `valueFormat` remain compatible but deprecated.
|
||||
|
||||
## Typed Values and Slots
|
||||
|
||||
@@ -198,7 +198,7 @@ Required markers are derived from whether the schema accepts `undefined`.
|
||||
|
||||
Do not read `_def`, `_zod.def`, or `typeName`. Use public `.unwrap()` APIs and public pipe inputs. Delegate intersection defaults to the Zod 4-compatible `zod-defaults` package.
|
||||
|
||||
Standard Schema validation does not write transform/coerce output back into TanStack Form state. Keep using `valueFormat` for submission payload conversion, or explicitly call `parseAsync` at the submission boundary when transformed schema output is required.
|
||||
Standard Schema validation does not write transform/coerce output back into TanStack Form state. Use the form-level codec for submission payload conversion, or explicitly call `parseAsync` inside the codec `encode` boundary when transformed schema output is required.
|
||||
|
||||
Also review these changes:
|
||||
|
||||
@@ -220,7 +220,7 @@ The shadcn form primitives now use a Vben-owned field context. Labels, controls,
|
||||
|
||||
`dependencies.resolve(context)` is the recommended API: it evaluates once and atomically commits one dynamic-state patch, while stale async results are discarded as a unit. Legacy `if/show/disabled/required/rules/componentProps/trigger` callbacks remain supported through the same normalized executor, but are marked `@deprecated` and emit one development warning. Both APIs react only to declared `triggerFields`.
|
||||
|
||||
`handleValuesChange(rawValues, fieldsChanged, getFormattedValues)` receives readonly raw values and formats only when its third argument is called. `getRawValues()` and `getValues()` each create only the requested snapshot; use `getValueSnapshot()` when both are required. `handleSubmit(values, rawValues)` receives both forms at submission. The formatter performs one deep clone, then applies array-to-string, range mapping, and schema `valueFormat` in order. Array fields keep using TanStack push/remove operations and stable row identity.
|
||||
`handleValuesChange(rawValues, fieldsChanged, getFormattedValues)` receives readonly `TFormValues` and runs codec or legacy formatting only when its third argument is called. `getRawValues()` returns form values and `getValues()` returns `TSubmitValues`; use `getValueSnapshot()` when both are required. `handleSubmit(values, rawValues)` receives both forms at submission. Legacy array-to-string, range mapping, and schema `valueFormat` remain compatible but deprecated. Array fields keep using TanStack push/remove operations and stable row identity.
|
||||
|
||||
## Test and Acceptance Matrix
|
||||
|
||||
|
||||
Reference in New Issue
Block a user