|
| 1 | +--- |
| 2 | +title: useFormKitOverlay Composable |
| 3 | +description: Promise-based modal/slideover forms - open FUDataEdit or FUAutoForm in a UModal/USlideover and await the saved data or null on cancel. |
| 4 | +--- |
| 5 | + |
| 6 | +## Overview |
| 7 | + |
| 8 | +`useFormKitOverlay()` composes `FUDataEdit`/`FUAutoForm`, `useFormKitForm`, and Nuxt UI's `useOverlay` into one call for the most common CRUD pattern: click Edit, fill in a dialog, save or cancel. |
| 9 | + |
| 10 | +```typescript |
| 11 | +const result = await overlay.edit({ data, schema, title }) |
| 12 | +``` |
| 13 | + |
| 14 | +`result` is the saved data on Save, or `null` on Cancel, <kbd>Esc</kbd>, or a backdrop click - there's no separate open/close state, mount timing, or FormKit submit plumbing to hand-assemble yourself. |
| 15 | + |
| 16 | +## Basic Usage |
| 17 | + |
| 18 | +```vue |
| 19 | +<script setup lang="ts"> |
| 20 | +const overlay = useFormKitOverlay() |
| 21 | +
|
| 22 | +const schema = [ |
| 23 | + { $formkit: 'nuxtUIInput', name: 'name', label: 'Name', validation: 'required' }, |
| 24 | + { $formkit: 'nuxtUIInput', name: 'email', label: 'Email', inputType: 'email', validation: 'required|email' }, |
| 25 | +] |
| 26 | +
|
| 27 | +const rows = ref([ |
| 28 | + { id: 1, name: 'Ada Lovelace', email: 'ada@example.com' }, |
| 29 | +]) |
| 30 | +
|
| 31 | +async function editRow(index: number) { |
| 32 | + const result = await overlay.edit({ |
| 33 | + data: rows.value[index], |
| 34 | + schema, |
| 35 | + title: `Edit ${rows.value[index]?.name}`, |
| 36 | + }) |
| 37 | +
|
| 38 | + if (result) |
| 39 | + rows.value[index] = result |
| 40 | +} |
| 41 | +</script> |
| 42 | +``` |
| 43 | + |
| 44 | +## Modal vs Slideover |
| 45 | + |
| 46 | +`as: 'modal' | 'slideover'` (default `'modal'`) chooses which Nuxt UI component wraps the form - one composable, not two: |
| 47 | + |
| 48 | +```typescript |
| 49 | +await overlay.edit({ data, schema, title, as: 'slideover' }) |
| 50 | +``` |
| 51 | + |
| 52 | +## Cancel and Dismissal Semantics |
| 53 | + |
| 54 | +Every way a user can close the overlay besides Save resolves the promise to `null`, never leaving it pending: the built-in Cancel button, <kbd>Esc</kbd>, and clicking outside the modal/slideover (backdrop click) all resolve the same way. |
| 55 | + |
| 56 | +## Auto-Inferred Schemas |
| 57 | + |
| 58 | +`auto()` opens the same modal/slideover, but infers the form from your data (or a [Valibot/Zod schema](/components/auto-form)) via `FUAutoForm` instead of requiring a hand-written FormKit schema: |
| 59 | + |
| 60 | +```typescript |
| 61 | +const result = await overlay.auto({ data: rows.value[index], title: `Edit ${rows.value[index]?.name}` }) |
| 62 | +``` |
| 63 | + |
| 64 | +```vue |
| 65 | +<script setup lang="ts"> |
| 66 | +import * as v from 'valibot' |
| 67 | +
|
| 68 | +const overlay = useFormKitOverlay() |
| 69 | +
|
| 70 | +const registrationSchema = v.object({ |
| 71 | + name: v.pipe(v.string(), v.minLength(2)), |
| 72 | + email: v.pipe(v.string(), v.email()), |
| 73 | +}) |
| 74 | +
|
| 75 | +async function editRegistration(data: object) { |
| 76 | + return overlay.auto({ data, valibotSchema: registrationSchema, title: 'Registration' }) |
| 77 | +} |
| 78 | +</script> |
| 79 | +``` |
| 80 | + |
| 81 | +`auto()` accepts the same `overrides`/`valibotSchema`/`zodSchema` options [`FUAutoForm`](/components/auto-form) does, plus this composable's own `title`/`as`. |
| 82 | + |
| 83 | +## Options |
| 84 | + |
| 85 | +### edit(options) |
| 86 | + |
| 87 | +| Option | Type | Default | Description | |
| 88 | +| --- | --- | --- | --- | |
| 89 | +| `data` | `unknown` | *(required)* | Data to pre-fill the form with | |
| 90 | +| `schema` | `FormKitSchemaDefinition` | *(required)* | Hand-written FormKit schema | |
| 91 | +| `title` | `string` | `undefined` | Modal/slideover header title | |
| 92 | +| `as` | `'modal' \| 'slideover'` | `'modal'` | Which Nuxt UI overlay component to render | |
| 93 | + |
| 94 | +### auto(options) |
| 95 | + |
| 96 | +| Option | Type | Default | Description | |
| 97 | +| --- | --- | --- | --- | |
| 98 | +| `data` | `unknown` | `undefined` | Data to infer the schema from and pre-fill the form with | |
| 99 | +| `valibotSchema` | `ValibotLikeSchema` | `undefined` | Infer inputs/validation from a Valibot object schema instead | |
| 100 | +| `zodSchema` | `ZodLikeSchema` | `undefined` | Infer inputs/validation from a Zod object schema instead | |
| 101 | +| `overrides` | `AutoFormOverrides` | `undefined` | Dot-path keyed map of partial schema nodes or `false` - see [`FUAutoForm`](/components/auto-form) | |
| 102 | +| `title` | `string` | `undefined` | Modal/slideover header title | |
| 103 | +| `as` | `'modal' \| 'slideover'` | `'modal'` | Which Nuxt UI overlay component to render | |
| 104 | + |
| 105 | +Both methods return `Promise<unknown>`, resolving to the saved data or `null`. |
| 106 | + |
| 107 | +## One Overlay Instance at a Time |
| 108 | + |
| 109 | +`edit()` and `auto()` each reuse a single registered overlay instance across calls, rather than stacking a new one per invocation. Calling either again before the previous call's promise has resolved doesn't open a second, independent overlay - it replaces the first one's data/title in place, and **the first call's promise never resolves** (its resolver is discarded, not rejected). Always `await` an `edit()`/`auto()` call (or otherwise ensure it has resolved) before starting another from the same composable instance. |
| 110 | + |
| 111 | +## Next Steps |
| 112 | + |
| 113 | +<CardGrid> |
| 114 | + <Card title="FUDataEdit" icon="i-lucide-file-edit" to="/components/data-edit"> |
| 115 | + The schema-driven form edit() opens |
| 116 | + </Card> |
| 117 | + |
| 118 | + <Card title="FUAutoForm" icon="i-lucide-wand-sparkles" to="/components/auto-form"> |
| 119 | + Schema-free forms auto() opens |
| 120 | + </Card> |
| 121 | + |
| 122 | + <Card title="API Reference" icon="i-lucide-book" to="/api/utilities"> |
| 123 | + Complete API documentation |
| 124 | + </Card> |
| 125 | +</CardGrid> |
0 commit comments