diff --git a/.changeset/wizard-skip-required-fields.md b/.changeset/wizard-skip-required-fields.md new file mode 100644 index 000000000..ec9e6e860 --- /dev/null +++ b/.changeset/wizard-skip-required-fields.md @@ -0,0 +1,42 @@ +--- +"@object-ui/plugin-form": patch +--- + +fix(form): a wizard with `allowSkip` no longer submits past the required fields you skipped + +`allowSkip` let the user jump to any step from the indicator, and +`handleStepClick` did so without validating anything on the way. Since a wizard +mounts ONE step at a time and react-hook-form only validates the fields currently +**mounted**, a required field on a step nobody opened was never registered, never +validated, and simply absent from the payload. + +Measured against the unfixed component — 3 steps, required `owner` on step 2, +`allowSkip: true`, click step 3's indicator, fill it, hit Create: + + createCalls: 1 + payload: { subject: 'S1', notes: 'S3' } // `owner` missing entirely + UI mentions "required": false // nothing said so + +So an invalid create went out and the client said nothing about why — #2959's +validation half, wearing a wizard's clothes. + +The final submit now checks the WHOLE declared field set, and when something is +outstanding it returns the user to the first step that has one, marks that step's +indicator (`data-error="true"`, destructive circle + icon), names the fields in a +toast, and sends nothing. Conditional rules are honoured: the check runs on the +canonical `resolveFieldRuleState`, the same engine the form renderer and the +server's rule-validator use, so a field hidden by `visibleWhen` or not yet +required by `requiredWhen` is not demanded. The sequential path is unaffected — +a forward jump is refused without `allowSkip`, so Next already validated each step. + +Also in `WizardForm`: + +- `FormView.columns` is now honoured (spec key, previously dropped): the grid + width is the view's `columns`, else the step's own. Unlike the tabbed/split + hosts there is no widest-section fallback — wizard steps never share a viewport, + so each keeps its authored width. +- the root gained `@container`. The step grid is sized with container queries, and + without a container ancestor every `@md:`/`@2xl:` variant was inert — a step + declaring 2 columns rendered single-column. Found by running it in a browser; + the class was present all along, which is why asserting the class alone had + missed it. diff --git a/content/docs/plugins/plugin-form.mdx b/content/docs/plugins/plugin-form.mdx index 75beca9a2..38ed3a250 100644 --- a/content/docs/plugins/plugin-form.mdx +++ b/content/docs/plugins/plugin-form.mdx @@ -69,9 +69,11 @@ A sectioned form renders as ONE grid. The form view's `columns` (spec densely that section fills it. The view's value wins; without it the grid takes the widest section's density, and without either it is single-column. -`ObjectForm` (simple), `ModalForm`, `TabbedForm` and `SplitForm` all resolve it -that way, so the same metadata lays out identically in every host. The grid is -applied to the field container inside the form, never wrapped around the `