diff --git a/.changeset/stale-poems-yawn.md b/.changeset/stale-poems-yawn.md
new file mode 100644
index 0000000..963704f
--- /dev/null
+++ b/.changeset/stale-poems-yawn.md
@@ -0,0 +1,5 @@
+---
+"simple-stack-form": patch
+---
+
+Set input type as email for inputs validating email values
diff --git a/examples/playground/src/components/react/Signup.tsx b/examples/playground/src/components/react/Signup.tsx
index 96f117d..65e3d44 100644
--- a/examples/playground/src/components/react/Signup.tsx
+++ b/examples/playground/src/components/react/Signup.tsx
@@ -13,6 +13,7 @@ export const signup = createForm({
await sleep(400);
return s !== "admin";
}),
+ email: z.string().email().optional().or(z.literal("")),
optIn: z.boolean().optional(),
});
@@ -30,6 +31,10 @@ export default function Signup({
+
+
+
+
diff --git a/packages/form/src/module.ts b/packages/form/src/module.ts
index 8547f5e..109443b 100644
--- a/packages/form/src/module.ts
+++ b/packages/form/src/module.ts
@@ -8,8 +8,11 @@ import {
ZodObject,
ZodOptional,
type ZodRawShape,
+ ZodString,
type ZodType,
z,
+ ZodUnion,
+ ZodLiteral,
} from "zod";
export type FormValidator = ZodRawShape;
@@ -20,7 +23,7 @@ export type FieldErrors<
export type InputProp = {
"aria-required": boolean;
name: string;
- type: "text" | "number" | "checkbox";
+ type: "text" | "number" | "checkbox" | "email";
};
export const formNameInputProps = {
@@ -213,6 +216,48 @@ function getInputProp(
return inputProp;
}
+function getInputType(fieldValidator: T): InputProp["type"] {
+ if (fieldValidator instanceof ZodBoolean) {
+ return "checkbox";
+ }
+
+ if (fieldValidator instanceof ZodNumber) {
+ return "number";
+ }
+
+ if (
+ fieldValidator instanceof ZodString &&
+ fieldValidator._def.checks.some((check) => check.kind === "email")
+ ) {
+ return "email";
+ }
+
+ if (fieldValidator instanceof ZodOptional) {
+ return getInputType(fieldValidator._def.innerType);
+ }
+
+ if (fieldValidator instanceof ZodUnion) {
+ const types: InputProp["type"][] =
+ fieldValidator._def.options.map(getInputType);
+ if (!types[0]) {
+ return "text";
+ }
+ if (types.every((type) => type === types[0])) {
+ return types[0];
+ }
+ if (
+ types.length === 2 &&
+ fieldValidator._def.options[1] instanceof ZodLiteral &&
+ !fieldValidator._def.options[1].value
+ ) {
+ // Handles specific case where email is optional. E.g.: `z.string().email().optional().or(z.literal(""))`
+ return types[0];
+ }
+ }
+
+ return "text";
+}
+
function getInputInfo(fieldValidator: T): {
type: InputProp["type"];
isArray: boolean;
@@ -236,15 +281,7 @@ function getInputInfo(fieldValidator: T): {
// TODO: respect preprocess() wrappers
- let type: InputProp["type"];
-
- if (resolvedType instanceof ZodBoolean) {
- type = "checkbox";
- } else if (resolvedType instanceof ZodNumber) {
- type = "number";
- } else {
- type = "text";
- }
+ const type = getInputType(resolvedType);
return { type, isArray, isOptional };
}