sveltekit-superforms
Advanced tools
Comparing version 0.5.8 to 0.5.9
import { type MaybePromise, type SubmitFunction } from '$app/forms'; | ||
import type { ActionResult } from '@sveltejs/kit'; | ||
import type { Page } from '@sveltejs/kit'; | ||
import { type Readable, type Writable } from 'svelte/store'; | ||
@@ -14,7 +15,2 @@ import type { Validation } from '..'; | ||
}>; | ||
/** | ||
* @param {string} taintedMessage If set, a confirm dialog will be shown when navigating away from the page if the form has been modified. | ||
* @param onUpdate Callback when the form is updated, by either a POST or a load function. | ||
* @param {boolean} applyAction If false, will not automatically update the form when $page updates. Useful for modal forms. | ||
*/ | ||
export type FormOptions<T extends AnyZodObject> = { | ||
@@ -53,7 +49,13 @@ applyAction?: boolean; | ||
multipleSubmits?: 'prevent' | 'allow' | 'abort'; | ||
flashMessage?: (errorResult: { | ||
type: 'error'; | ||
status: number; | ||
error: App.Error; | ||
}) => any; | ||
flashMessage?: { | ||
module: { | ||
getFlash(page: Readable<Page>): Writable<App.PageData['flash']>; | ||
updateFlash(page: Readable<Page>, update?: () => Promise<void>): Promise<void>; | ||
}; | ||
onError?: (errorResult: { | ||
type: 'error'; | ||
status: number; | ||
error: App.Error; | ||
}) => App.PageData['flash']; | ||
}; | ||
}; | ||
@@ -60,0 +62,0 @@ export type EnhancedForm<T extends AnyZodObject> = { |
@@ -10,3 +10,2 @@ import { enhance, applyAction } from '$app/forms'; | ||
import { deepEqual } from '..'; | ||
//import { getFlash, updateFlash } from './sveltekit-flash-message/client'; | ||
var FetchStatus; | ||
@@ -168,3 +167,3 @@ (function (FetchStatus) { | ||
typeof actionForm.form.valid !== 'boolean') { | ||
throw new Error("ActionData didn't return a Validation object. Make sure you return { form } from form actions."); | ||
throw new Error("ActionData didn't return a Validation object. Make sure you return { form } in the form actions."); | ||
} | ||
@@ -239,7 +238,2 @@ form = actionForm.form; | ||
} | ||
/* | ||
function _wipeForm() { | ||
rebind(emptyForm(), true); | ||
} | ||
*/ | ||
const Data_update = async (result, untaint) => { | ||
@@ -300,3 +294,3 @@ if (['error', 'redirect'].includes(result.type)) { | ||
if (!p.form.form) | ||
throw new Error("No form data found in $page.form (ActionData). Make sure you return { form } in this page's form actions."); | ||
throw new Error('No form data found in $page.form (ActionData). Make sure you return { form } in the form actions.'); | ||
await _update(p.form.form, p.status >= 200 && p.status < 400); | ||
@@ -306,3 +300,3 @@ } | ||
if (!p.data.form) { | ||
throw new Error("No form data found in $page.data (PageData). Make sure you return { form } in this page's load function."); | ||
throw new Error('No form data found in $page.data (PageData). Make sure you return { form } in the load function.'); | ||
} | ||
@@ -477,3 +471,3 @@ // It's a page reload, so don't trigger any update events. | ||
options.clearOnSubmit == 'message')) { | ||
//getFlash(page).set(undefined); | ||
options.flashMessage.module.getFlash(page).set(undefined); | ||
} | ||
@@ -549,3 +543,3 @@ //d('Submitting'); | ||
if (options.flashMessage) { | ||
if (result.type == 'error') { | ||
if (result.type == 'error' && options.flashMessage.onError) { | ||
if (errorMessage && | ||
@@ -555,15 +549,12 @@ result.error && | ||
'message' in result.error) { | ||
// Grab the modified error message from onError option | ||
result.error.message = errorMessage; | ||
} | ||
/* | ||
getFlash(page).set( | ||
options.flashMessage({ | ||
options.flashMessage.module.getFlash(page).set(options.flashMessage.onError({ | ||
...result, | ||
status: result.status ?? status | ||
}) | ||
); | ||
*/ | ||
})); | ||
} | ||
else { | ||
//updateFlash(page); | ||
else if (result.type != 'error') { | ||
options.flashMessage.module.updateFlash(page); | ||
} | ||
@@ -570,0 +561,0 @@ } |
import { fail, json } from '@sveltejs/kit'; | ||
import { parse, stringify } from 'devalue'; | ||
import { ZodAny, ZodDefault, ZodNullable, ZodOptional, ZodString, z, ZodNumber, ZodBoolean, ZodDate, ZodLiteral, ZodUnion, ZodArray, ZodEffects, ZodBigInt, ZodObject, ZodSymbol } from 'zod'; | ||
import { ZodAny, ZodDefault, ZodNullable, ZodOptional, ZodString, z, ZodNumber, ZodBoolean, ZodDate, ZodLiteral, ZodUnion, ZodArray, ZodEffects, ZodBigInt, ZodObject, ZodSymbol, ZodEnum } from 'zod'; | ||
function setValidationDefaults(data, fields) { | ||
@@ -132,3 +132,5 @@ for (const stringField of Object.keys(fields)) { | ||
} | ||
else if (zodType instanceof ZodUnion || zodType instanceof ZodAny) { | ||
else if (zodType instanceof ZodUnion || | ||
zodType instanceof ZodEnum || | ||
zodType instanceof ZodAny) { | ||
return value; | ||
@@ -217,3 +219,3 @@ } | ||
} | ||
throw new Error(`Unsupported type for ${strict ? 'strict' : 'falsy'} values on field "${String(field)}": ${zodType.constructor.name}. Add default, optional or nullable to the schema, or use the "defaults" option.`); | ||
throw new Error(`Unsupported type for ${strict ? 'strict' : 'falsy'} values on field "${String(field)}": ${zodType.constructor.name}. Add default, optional or nullable to the schema.`); | ||
} | ||
@@ -220,0 +222,0 @@ if (value) |
{ | ||
"name": "sveltekit-superforms", | ||
"version": "0.5.8", | ||
"version": "0.5.9", | ||
"author": "Andreas Söderlund <ciscoheat@gmail.com> (https://blog.encodeart.dev)", | ||
@@ -5,0 +5,0 @@ "description": "Supercharge your SvelteKit forms with this powerhouse of a library!", |
@@ -81,3 +81,3 @@ # sveltekit-superforms 💥 | ||
Optional: Add this to `<head>` for a much nicer visual experience: | ||
Optional: If you're starting from scratch, add this to `<head>` for a much nicer visual experience: | ||
@@ -90,3 +90,3 @@ **src/app.html** | ||
What we see now is rather basic, and there is no form action to submit to, but we can at least see that the form is populated. To get deeper insight, let's add the Super Form Debugging Svelte Component: | ||
Currently there is no form action to submit to, but we can at least see that the form is populated. To get deeper insight, let's add the Super Form Debugging Svelte Component: | ||
@@ -103,3 +103,3 @@ **src/routes/+page.svelte** | ||
Edit the fields and see how the `$form` store is automatically updated. It even displays the current page status in the right corner. | ||
Edit the fields and see how the `$form` store is automatically updated. The component also displays the current page status in the right corner. | ||
@@ -398,2 +398,17 @@ ## Posting - Without any bells and whistles | ||
## sveltekit-flash-message support | ||
The sister library to `sveltekit-superforms` is called [sveltekit-flash-message](https://github.com/ciscoheat/sveltekit-flash-message), a useful addon since the `message` property of `Validation<T>` doesn't persist when redirecting to a different page. If you have it installed and configured, you need to specify this option to make things work: | ||
```ts | ||
import * as flashModule from 'sveltekit-flash-message/client'; | ||
flashMessage: { | ||
module: flashModule, | ||
onError?: (errorResult: ActionResult<'error'>) => App.PageData['flash'] | ||
} | ||
``` | ||
The flash message is set automatically for every `ActionResult` except `error`, so the `onError` callback is needed to transform errors into your flash message type, or leave it out to disregard them. | ||
## The last one: Breaking free from FormData | ||
@@ -437,2 +452,6 @@ | ||
# Multiple and global forms | ||
There is only one `Page` and `ActionData` on a page, so multiple forms on the same page can cause trouble since it's hard to know which form updated `page`. An example is if you have a login form in the navigation that will be present on every page. Fortunately `sveltekit-superforms` has a simple solution for this, by setting `options.applyAction` to `false`, which will prevent it from updating `Page` and `ActionData`. If you want to make it completely self-contained, set `options.invalidateAll` to `false` as well. | ||
# Designing a CRUD interface | ||
@@ -501,3 +520,3 @@ | ||
Except for the `id`, it's worth noting that **Create** and **Update** can use the same schema, so they should naturally share the user interface. This is a fundamental idea in this library, so you can pass either `null/undefined` or an entity to `superValidate`, and it will generate default values in the first case: | ||
Note that **Create** and **Update** can now use the same schema, so they should naturally share the same user interface. This is a fundamental idea in this library, so you can pass either `null/undefined` or an entity to `superValidate`, and it will generate default values if the value passed to it is empty: | ||
@@ -578,3 +597,3 @@ **src/routes/+page.server.ts** | ||
We have prepared to display a status message, utilising `$page.status` to test for success or failure. And we're using the `empty` property of the form to display a "Create" or "Update" title. We shouldn't use the `empty` store returned from `superForm` here, since it will update even when validation fails, so we're using the initial data from the page load. | ||
We have prepared to display a status message, utilising `$page.status` to test for success or failure. And we're using the `empty` property of the form to display a "Create" or "Update" title. We shouldn't use the `$empty` store returned from `superForm` here, since it will update even when validation fails, changing the text, so we're using the initial data from the page load. | ||
@@ -600,3 +619,3 @@ The form action looks similar to before, but will branch after validation is successful: | ||
Here is where you should access your database API. Since we're using an array, the create and update logic is simple: | ||
This is where you should access your database API. Since we're using an array, the create and update logic is simple: | ||
@@ -628,5 +647,13 @@ ```ts | ||
In all examples, `T` represents the validation schema, a type that extends `AnyZodObject`. `z.infer<T>` refers to the underlying type of the schema (the actual data structure). | ||
Throughout the referenc, the following types are represented: | ||
```ts | ||
// T represents the validation schema | ||
T extends AnyZodObject | ||
// S refers to the underlying type of the schema, the actual data structure. | ||
S = z.infer<T> | ||
``` | ||
```ts | ||
export type ValidationErrors<T extends AnyZodObject> = Partial< | ||
@@ -658,3 +685,3 @@ Record<keyof z.infer<T>, string[] | undefined> | ||
**superValidate(data, schema, options?)** | ||
### **superValidate(data, schema, options?)** | ||
@@ -667,3 +694,3 @@ ```ts | ||
| FormData | ||
| Partial<Record<keyof z.infer<T>, unknown>> | ||
| Partial<Record<keyof S, unknown>> | ||
| null | ||
@@ -684,4 +711,5 @@ | undefined, | ||
valid: false; | ||
errors: {}; | ||
data: z.infer<T>; // See further down for default entity values. | ||
errors: { | ||
} | ||
data: S; // See further down for default entity values. | ||
empty: true; | ||
@@ -692,3 +720,3 @@ message: null; | ||
**setError(form, field, error)** | ||
### **setError(form, field, error)** | ||
@@ -698,3 +726,3 @@ ```ts | ||
form: Validation<T>, | ||
field: keyof z.infer<T>, | ||
field: keyof S, | ||
error: string | string[] | null | ||
@@ -706,5 +734,5 @@ ) : ActionFailure<{form: Validation<T>}> | ||
**noErrors(form)** | ||
### **noErrors(form)** | ||
If you want to return a form with no validation errors. Only the `errors` property will be modified, so `valid` still indicates the validation status. Useful for load functions where the entity is invalid, but as a initial state no errors should be displayed on the form. | ||
If you want to return a form with no validation errors. Only the `errors` property will be modified, so `valid` still indicates the validation status. Useful for load functions where the entity is invalid, but as an initial state no errors should be displayed on the form. | ||
@@ -715,3 +743,3 @@ ```ts | ||
**actionResult(type, data?, status?)** | ||
### **actionResult(type, data?, status?)** | ||
@@ -748,3 +776,3 @@ When not using form actions, this constructs an action result in a `Response` object, so you can return `Validation<T>` from your API/endpoints, for example in a login request: | ||
**superForm(form?, options?)** | ||
### **superForm(form?, options?)** | ||
@@ -805,2 +833,7 @@ ```ts | ||
| string; | ||
flashMessage?: { | ||
module: (import * as module from 'sveltekit-flash-message/client'), | ||
onError?: (errorResult: ActionResult<'error'>) => App.PageData['flash'] | ||
}; | ||
}; | ||
@@ -873,3 +906,3 @@ ``` | ||
const schema = z.object({ | ||
fish: z.enum(['Salmon', 'Tuna', 'Trout']).default('Salmon') | ||
fish: z.enum(['Salmon', 'Tuna', 'Trout']).default('Salmon') // or nullable/optional/nullish | ||
}); | ||
@@ -880,2 +913,20 @@ ``` | ||
# Default values | ||
When you start to configure the library to suit your stack, it's recommended to create an object with default values that you will refer to instead: | ||
```ts | ||
import { superForm } from 'sveltekit-superforms/client'; | ||
export function yourSuperForm( | ||
...params: Parameters<typeof superForm> | ||
): ReturnType<typeof superForm> { | ||
return superForm(params[0], { | ||
errorSelector: '.has-error', | ||
delayMs: 300, | ||
...params[1] | ||
}); | ||
} | ||
``` | ||
# Feedback wanted! | ||
@@ -882,0 +933,0 @@ |
Sorry, the diff of this file is not supported yet
917
81615
13
1105