diff --git a/docs/components/content/examples/FormExampleSuperstruct.vue b/docs/components/content/examples/FormExampleSuperstruct.vue new file mode 100644 index 00000000..3887a413 --- /dev/null +++ b/docs/components/content/examples/FormExampleSuperstruct.vue @@ -0,0 +1,36 @@ + + + diff --git a/docs/content/2.components/form.md b/docs/content/2.components/form.md index 53f4ba35..a39d0e32 100644 --- a/docs/content/2.components/form.md +++ b/docs/content/2.components/form.md @@ -8,13 +8,13 @@ links: ## Usage -Use the Form component to validate form data using schema libraries such as [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://github.com/fabian-hiller/valibot), or your own validation logic. +Use the Form component to validate form data using schema libraries such as [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://github.com/fabian-hiller/valibot), [Superstruct](https://github.com/ianstormtaylor/superstruct), or your own validation logic. It works with the [FormGroup](/components/form-group) component to display error messages around form elements automatically. The form component requires two props: - `state` - a reactive object holding the form's state. -- `schema` - a schema object from a validation library like [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi) or [Valibot](https://github.com/fabian-hiller/valibot). +- `schema` - a schema object from a validation library like [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://github.com/fabian-hiller/valibot) or [Superstruct](https://github.com/ianstormtaylor/superstruct). ::callout{icon="i-heroicons-light-bulb"} Note that **no validation library is included** by default, so ensure you **install the one you need**. @@ -52,6 +52,13 @@ Note that **no validation library is included** by default, so ensure you **inst class: 'w-60' --- :: + ::component-example{label="Superstruct"} + --- + component: 'form-example-superstruct' + componentProps: + class: 'w-60' + --- + :: :: ## Custom validation diff --git a/package.json b/package.json index 68ecfbf2..8071afb4 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "joi": "^17.13.3", "nuxt": "^3.13.2", "release-it": "^17.7.0", + "superstruct": "^2.0.2", "unbuild": "^2.0.0", "valibot": "^0.42.1", "valibot30": "npm:valibot@0.30.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b49a85b..06f58810 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,6 +109,9 @@ importers: release-it: specifier: ^17.7.0 version: 17.7.0(typescript@5.6.2) + superstruct: + specifier: ^2.0.2 + version: 2.0.2 unbuild: specifier: ^2.0.0 version: 2.0.0(typescript@5.6.2)(vue-tsc@2.1.6(typescript@5.6.2)) @@ -6078,6 +6081,10 @@ packages: resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} engines: {node: '>=16'} + superstruct@2.0.2: + resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} + engines: {node: '>=14.0.0'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -14318,6 +14325,8 @@ snapshots: dependencies: copy-anything: 3.0.5 + superstruct@2.0.2: {} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 diff --git a/src/runtime/components/forms/Form.vue b/src/runtime/components/forms/Form.vue index cf124a15..a61c60d1 100644 --- a/src/runtime/components/forms/Form.vue +++ b/src/runtime/components/forms/Form.vue @@ -13,6 +13,7 @@ import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } fro import type { BaseSchema as ValibotSchema30, BaseSchemaAsync as ValibotSchemaAsync30 } from 'valibot30' import type { GenericSchema as ValibotSchema31, GenericSchemaAsync as ValibotSchemaAsync31, SafeParser as ValibotSafeParser31, SafeParserAsync as ValibotSafeParserAsync31 } from 'valibot31' import type { GenericSchema as ValibotSchema, GenericSchemaAsync as ValibotSchemaAsync, SafeParser as ValibotSafeParser, SafeParserAsync as ValibotSafeParserAsync } from 'valibot' +import type { Struct } from 'superstruct' import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form } from '../../types/form' import { useId } from '#imports' @@ -35,7 +36,7 @@ export default defineComponent({ | PropType | PropType | ValibotSafeParserAsync31> | PropType - | PropType | ValibotSafeParserAsync>, + | PropType | ValibotSafeParserAsync> | PropType>, default: undefined }, state: { @@ -88,6 +89,8 @@ export default defineComponent({ errs = errs.concat(await getJoiErrors(props.state, props.schema)) } else if (isValibotSchema(props.schema)) { errs = errs.concat(await getValibotError(props.state, props.schema)) + } else if (isSuperStructSchema(props.schema)) { + errs = errs.concat(await getSuperStructErrors(props.state, props.schema)) } else { throw new Error('Form validation failed: Unsupported form schema') } @@ -195,6 +198,15 @@ function isYupError (error: any): error is YupError { return error.inner !== undefined } +function isSuperStructSchema (schema: any): schema is Struct { + return ( + 'schema' in schema && + typeof schema.coercer === 'function' && + typeof schema.validator === 'function' && + typeof schema.refiner === 'function' + ) +} + async function getYupErrors ( state: any, schema: YupObjectSchema @@ -218,6 +230,18 @@ function isZodSchema (schema: any): schema is ZodSchema { return schema.parse !== undefined } +async function getSuperStructErrors (state: any, schema: Struct): Promise { + const [err] = schema.validate(state) + if (err) { + const errors = err.failures() + return errors.map((error) => ({ + message: error.message, + path: error.path.join('.') + })) + } + return [] +} + async function getZodErrors ( state: any, schema: ZodSchema @@ -259,6 +283,7 @@ async function getJoiErrors ( } } + function isValibotSchema (schema: any): schema is ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema31 | ValibotSchemaAsync31 | ValibotSafeParser31 | ValibotSafeParserAsync31 | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser | ValibotSafeParserAsync { return '_parse' in schema || '_run' in schema || (typeof schema === 'function' && 'schema' in schema) }