From 04ca5db4f5beb0fe5155baf4a5524d8e401a079d Mon Sep 17 00:00:00 2001 From: Weston Harper <13283469+wesharper@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:30:04 -0700 Subject: [PATCH] feat: add support for superforms {_errors: string[]} errors (#129) Co-authored-by: Weston Harper --- .changeset/kind-fireants-collect.md | 5 +++ package.json | 2 +- pnpm-lock.yaml | 42 ++++++++++-------- src/lib/components/form-field.svelte | 12 ++++-- src/lib/components/form-validation.svelte | 11 +++-- src/lib/helpers/get-cleansed-errors.ts | 5 +++ src/lib/helpers/index.ts | 1 + src/lib/internal/form-field/create-field.ts | 5 ++- src/lib/internal/form-field/types.ts | 4 +- src/routes/test/a/+page.svelte | 48 ++++++++++++++++++++- 10 files changed, 106 insertions(+), 29 deletions(-) create mode 100644 .changeset/kind-fireants-collect.md create mode 100644 src/lib/helpers/get-cleansed-errors.ts diff --git a/.changeset/kind-fireants-collect.md b/.changeset/kind-fireants-collect.md new file mode 100644 index 0000000..2d531d2 --- /dev/null +++ b/.changeset/kind-fireants-collect.md @@ -0,0 +1,5 @@ +--- +"formsnap": patch +--- + +Adds support for superforms {\_errors?: string[]} syntax diff --git a/package.json b/package.json index 6938e84..a7bce9b 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.14", - "bits-ui": "^0.9.0", + "bits-ui": "^0.17.0", "clsx": "^2.0.0", "concurrently": "^8.2.1", "contentlayer": "^0.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f41113..d4a730b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,8 +54,8 @@ devDependencies: specifier: ^10.4.14 version: 10.4.15(postcss@8.4.29) bits-ui: - specifier: ^0.9.0 - version: 0.9.0(svelte@4.2.0) + specifier: ^0.17.0 + version: 0.17.0(svelte@4.2.0) clsx: specifier: ^2.0.0 version: 2.0.0 @@ -902,6 +902,12 @@ packages: vfile-message: 2.0.4 dev: true + /@internationalized/date@3.5.1: + resolution: {integrity: sha512-LUQIfwU9e+Fmutc/DpRTGXSdgYZLBegi4wygCWDSVmUdLTaMHsQyASDiJtREwanwKuQLq0hY76fCJ9J/9I2xOQ==} + dependencies: + '@swc/helpers': 0.5.6 + dev: true + /@jest/schemas@29.6.3: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1022,16 +1028,17 @@ packages: - supports-color dev: true - /@melt-ui/svelte@0.57.2(svelte@4.2.0): - resolution: {integrity: sha512-Ul+pebT2wff5rp5xzlXwQ7W6z5YOS9oXsds21dqlE2gUAFBIfN1vIo4IZrTed/FkpCyey90CvHDQYfQvZiNvkA==} + /@melt-ui/svelte@0.71.2(svelte@4.2.0): + resolution: {integrity: sha512-GDUErhAphEoEOLpcBjQ84BgzRR6M3344fQE4QYFffwT7aedWak7CvNsECgeig1Y5xvfDmeEaFnGlOQXIBucJYw==} peerDependencies: svelte: '>=3 <5' dependencies: '@floating-ui/core': 1.4.1 '@floating-ui/dom': 1.5.1 + '@internationalized/date': 3.5.1 dequal: 2.0.3 focus-trap: 7.5.2 - nanoid: 4.0.2 + nanoid: 5.0.5 svelte: 4.2.0 dev: true @@ -1440,6 +1447,12 @@ packages: - encoding dev: true + /@swc/helpers@0.5.6: + resolution: {integrity: sha512-aYX01Ke9hunpoCexYAgQucEpARGQ5w/cqHFrIR+e9gdKb1QWTsVJuTJ2ozQzIAxLyRQe/m+2RqzkyOOGiMKRQA==} + dependencies: + tslib: 2.6.2 + dev: true + /@tailwindcss/typography@0.5.9(tailwindcss@3.3.3): resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==} peerDependencies: @@ -2092,13 +2105,14 @@ packages: file-uri-to-path: 1.0.0 dev: true - /bits-ui@0.9.0(svelte@4.2.0): - resolution: {integrity: sha512-wEVWMnR4or4t8dY+UP+G4jVugzsfhMcuIrTOoJh8V6t/JcHWne/z1nnBA/yD1E1cn391IUurSatRkv7qTJXMww==} + /bits-ui@0.17.0(svelte@4.2.0): + resolution: {integrity: sha512-K73jjco1qPmvGXMQtTkZG6K36UmNrPR21u+C1jzoRWmF3NnUfDP4hPJnAci0LosUycfvOxtaHB1M4awvLvQXyQ==} peerDependencies: svelte: ^4.0.0 dependencies: - '@melt-ui/svelte': 0.57.2(svelte@4.2.0) - nanoid: 5.0.2 + '@internationalized/date': 3.5.1 + '@melt-ui/svelte': 0.71.2(svelte@4.2.0) + nanoid: 5.0.5 svelte: 4.2.0 dev: true @@ -5128,14 +5142,8 @@ packages: hasBin: true dev: true - /nanoid@4.0.2: - resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} - engines: {node: ^14 || ^16 || >=18} - hasBin: true - dev: true - - /nanoid@5.0.2: - resolution: {integrity: sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==} + /nanoid@5.0.5: + resolution: {integrity: sha512-/Veqm+QKsyMY3kqi4faWplnY1u+VuKO3dD2binyPIybP31DRO29bPF+1mszgLnrR2KqSLceFLBNw0zmvDzN1QQ==} engines: {node: ^18 || >=20} hasBin: true dev: true diff --git a/src/lib/components/form-field.svelte b/src/lib/components/form-field.svelte index c2aedc7..60d5f72 100644 --- a/src/lib/components/form-field.svelte +++ b/src/lib/components/form-field.svelte @@ -1,4 +1,6 @@ - {#if $errors} - {$errors} + {#if internalErrors} + {internalErrors} {/if} diff --git a/src/lib/helpers/get-cleansed-errors.ts b/src/lib/helpers/get-cleansed-errors.ts new file mode 100644 index 0000000..e52e5d2 --- /dev/null +++ b/src/lib/helpers/get-cleansed-errors.ts @@ -0,0 +1,5 @@ +import type { SuperformsValidationError } from "../internal"; + +export function getCleansedErrors(errors: SuperformsValidationError): string[] | undefined { + return Array.isArray(errors) ? errors : errors?._errors ? errors._errors : undefined; +} diff --git a/src/lib/helpers/index.ts b/src/lib/helpers/index.ts index b703a34..e0384cd 100644 --- a/src/lib/helpers/index.ts +++ b/src/lib/helpers/index.ts @@ -2,3 +2,4 @@ export * from "./get-form-field.js"; export * from "./get-form.js"; export * from "./get-form-schema.js"; export * from "./get-form-control.js"; +export * from "./get-cleansed-errors.js"; diff --git a/src/lib/internal/form-field/create-field.ts b/src/lib/internal/form-field/create-field.ts index 7a03307..78ec615 100644 --- a/src/lib/internal/form-field/create-field.ts +++ b/src/lib/internal/form-field/create-field.ts @@ -5,6 +5,7 @@ import { createFieldActions, createFieldHandlers } from "@/lib/internal/index.js import type { ErrorsStore, GetFieldAttrsProps } from "@/lib/internal/index.js"; import type { CreateFormFieldReturn, FieldAttrStore, FieldContext, FieldIds } from "./types.js"; import { setContext } from "svelte"; +import { getCleansedErrors } from "@/lib/index.js"; export const FORM_FIELD_CONTEXT = "FormField"; @@ -52,7 +53,9 @@ export function createFormField< setContext(FORM_FIELD_CONTEXT, context); function getFieldAttrs(props: GetFieldAttrsProps) { - const { val, errors, constraints, hasValidation, hasDescription } = props; + const { val, errors: rawErrors, constraints, hasValidation, hasDescription } = props; + const errors = getCleansedErrors(rawErrors); + const $ids = get(ids); const describedBy = errors ? `${hasValidation ? $ids.validation : ""} ${hasDescription ? $ids.description : ""}` diff --git a/src/lib/internal/form-field/types.ts b/src/lib/internal/form-field/types.ts index fcc13a4..15c66f0 100644 --- a/src/lib/internal/form-field/types.ts +++ b/src/lib/internal/form-field/types.ts @@ -13,6 +13,8 @@ export type CreateFormFieldReturn = { context: FieldContext; }; +export type SuperformsValidationError = { _errors?: string[] } | string[] | undefined; + export type FieldContext = { /** * The name of the field in the form schema. @@ -32,7 +34,7 @@ export type FieldContext = { * if they exist, or undefined if they don't. Useful for displaying * errors in a custom validation message component. */ - errors: Writable; + errors: Writable; /** * A writable store containing the current value of the field. diff --git a/src/routes/test/a/+page.svelte b/src/routes/test/a/+page.svelte index 1605394..ba2ed7a 100644 --- a/src/routes/test/a/+page.svelte +++ b/src/routes/test/a/+page.svelte @@ -11,14 +11,23 @@ }), bio: z.string().max(250, "Bio must be at most 250 characters").optional(), website: z.string().url("Invalid URL").optional(), - usage: z.boolean().default(true) + usage: z.boolean().default(true), + multiSelect: z.array(z.string()).nonempty() }); + + const multiSelectItems = [ + { label: "First", value: "first" }, + { label: "Second", value: "second" }, + { label: "Third", value: "third" }, + { label: "Fourth", value: "fourth" } + ]; @@ -103,6 +112,43 @@ usage description + + Multi-select + { + setValue(selected?.map((opt) => opt.value)); + }} + > + + + + + ↓ + + + {#each multiSelectItems as option} + + {option.label} + + + {/each} + + + + +