From 866ef5a2b30229f669fbd9541dd1e5142cee0da1 Mon Sep 17 00:00:00 2001
From: TannerScadden <tanner@scaddenfamily.com>
Date: Mon, 13 Feb 2023 21:27:56 -0500
Subject: [PATCH 1/4] Saving a rough draft of a createForm

---
 packages/form/CHANGELOG.md        |  5 +++
 packages/form/LICENSE             | 21 ++++++++++++
 packages/form/README.md           | 24 ++++++++++++++
 packages/form/dev/index.html      | 35 ++++++++++++++++++++
 packages/form/dev/index.tsx       | 51 +++++++++++++++++++++++++++++
 packages/form/dev/vite.config.ts  |  2 ++
 packages/form/package.json        | 54 +++++++++++++++++++++++++++++++
 packages/form/src/index.ts        | 46 ++++++++++++++++++++++++++
 packages/form/test/index.test.ts  |  7 ++++
 packages/form/test/server.test.ts |  7 ++++
 packages/form/tsconfig.json       |  5 +++
 pnpm-lock.yaml                    |  6 ++++
 12 files changed, 263 insertions(+)
 create mode 100644 packages/form/CHANGELOG.md
 create mode 100644 packages/form/LICENSE
 create mode 100644 packages/form/README.md
 create mode 100644 packages/form/dev/index.html
 create mode 100644 packages/form/dev/index.tsx
 create mode 100644 packages/form/dev/vite.config.ts
 create mode 100644 packages/form/package.json
 create mode 100644 packages/form/src/index.ts
 create mode 100644 packages/form/test/index.test.ts
 create mode 100644 packages/form/test/server.test.ts
 create mode 100644 packages/form/tsconfig.json

diff --git a/packages/form/CHANGELOG.md b/packages/form/CHANGELOG.md
new file mode 100644
index 000000000..9feeb39d8
--- /dev/null
+++ b/packages/form/CHANGELOG.md
@@ -0,0 +1,5 @@
+# @solid-primitives/form
+
+0.0.100
+
+First commit of the form primitive.
diff --git a/packages/form/LICENSE b/packages/form/LICENSE
new file mode 100644
index 000000000..38b41d975
--- /dev/null
+++ b/packages/form/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Solid Primitives Working Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/packages/form/README.md b/packages/form/README.md
new file mode 100644
index 000000000..77703c32e
--- /dev/null
+++ b/packages/form/README.md
@@ -0,0 +1,24 @@
+<p>
+  <img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=I18n" alt="Solid Primitives I18n">
+</p>
+
+# @solid-primitives/form
+
+[![turborepo](https://img.shields.io/badge/built%20with-turborepo-cc00ff.svg?style=for-the-badge&logo=turborepo)](https://turborepo.org/)
+[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/i18n?style=for-the-badge)](https://bundlephobia.com/package/@solid-primitives/i18n)
+[![size](https://img.shields.io/npm/v/@solid-primitives/i18n?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/i18n)
+[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)
+
+Creates state and helpers for managing forms.
+
+## How to use it
+
+Install it:
+
+```bash
+yarn add @solid-primitives/form
+```
+
+## Changelog
+
+See [CHANGELOG.md](./CHANGELOG.md)
diff --git a/packages/form/dev/index.html b/packages/form/dev/index.html
new file mode 100644
index 000000000..65f53c4b6
--- /dev/null
+++ b/packages/form/dev/index.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <title>Solid App</title>
+    <style>
+      html {
+        font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
+      }
+
+      body {
+        padding: 0;
+        margin: 0;
+      }
+
+      a,
+      button {
+        cursor: pointer;
+      }
+
+      * {
+        margin: 0;
+      }
+    </style>
+  </head>
+
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+
+    <script src="/index.tsx" type="module"></script>
+  </body>
+</html>
diff --git a/packages/form/dev/index.tsx b/packages/form/dev/index.tsx
new file mode 100644
index 000000000..3a6732d5c
--- /dev/null
+++ b/packages/form/dev/index.tsx
@@ -0,0 +1,51 @@
+import { Component } from "solid-js";
+import { render } from "solid-js/web";
+import "uno.css";
+import { createForm } from "../src";
+
+const App: Component = () => {
+  const { updateForm, formData } = createForm({ name: "", food: "", email: "" });
+
+  const onSubmit = (e: any) => {
+    e.preventDefault();
+    console.log(formData);
+  };
+
+  return (
+    <form
+      class="p-24 box-border w-full min-h-screen flex flex-col justify-center items-center space-y-4 bg-gray-800 text-white"
+      onSubmit={onSubmit}
+    >
+      <label>Name</label>
+      <input
+        name="name"
+        required
+        value={formData.name}
+        onInput={e => updateForm("name", e.currentTarget.value)}
+      />
+      <label>Favorite Food</label>
+      <select
+        required
+        name="food"
+        value={formData.food}
+        onInput={e => updateForm("food", e.currentTarget.value)}
+      >
+        <option value="" />
+        <option value="apple">Apple</option>
+        <option value="pear">Pear</option>
+        <option value="banana">banana</option>
+      </select>
+      <label>Email</label>
+      <input
+        name="email"
+        type="email"
+        required
+        value={formData.email}
+        onInput={e => updateForm("email", e.currentTarget.value)}
+      />
+      <button type="submit">Submit</button>
+    </form>
+  );
+};
+
+render(() => <App />, document.getElementById("root")!);
diff --git a/packages/form/dev/vite.config.ts b/packages/form/dev/vite.config.ts
new file mode 100644
index 000000000..7ca66a520
--- /dev/null
+++ b/packages/form/dev/vite.config.ts
@@ -0,0 +1,2 @@
+import { viteConfig } from "../../../configs/vite.config";
+export default viteConfig;
diff --git a/packages/form/package.json b/packages/form/package.json
new file mode 100644
index 000000000..a2b264b83
--- /dev/null
+++ b/packages/form/package.json
@@ -0,0 +1,54 @@
+{
+  "name": "@solid-primitives/form",
+  "version": "1.1.4",
+  "description": "Primitive to create and compose forms",
+  "author": "Tanner Scadden <tanner@scaddenfamily.com>",
+  "license": "MIT",
+  "homepage": "https://github.com/solidjs-community/solid-primitives/tree/main/packages/form",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/solidjs-community/solid-primitives.git"
+  },
+  "primitive": {
+    "name": "form",
+    "stage": 3,
+    "list": [
+      "createForm"
+    ],
+    "category": "Utilities"
+  },
+  "files": [
+    "dist"
+  ],
+  "private": false,
+  "sideEffects": false,
+  "type": "module",
+  "main": "./dist/index.cjs",
+  "module": "./dist/index.js",
+  "browser": {},
+  "types": "./dist/index.d.ts",
+  "exports": {
+    "import": {
+      "types": "./dist/index.d.ts",
+      "default": "./dist/index.js"
+    },
+    "require": "./dist/index.cjs"
+  },
+  "scripts": {
+    "dev": "vite serve dev",
+    "page": "vite build dev",
+    "start": "vite -r ./dev/ -c ./dev/vite.config.ts",
+    "build": "jiti ../../scripts/build.ts",
+    "test": "vitest -c ../../configs/vitest.config.ts",
+    "test:ssr": "pnpm run test --mode ssr"
+  },
+  "keywords": [
+    "form",
+    "solid",
+    "primitives"
+  ],
+  "peerDependencies": {
+    "solid-js": "^1.6.0"
+  },
+  "typesVersions": {}
+}
diff --git a/packages/form/src/index.ts b/packages/form/src/index.ts
new file mode 100644
index 000000000..df601f07f
--- /dev/null
+++ b/packages/form/src/index.ts
@@ -0,0 +1,46 @@
+import { createStore } from "solid-js/store";
+
+// type FormSubmitEvent = Event & {
+//   submitter: HTMLElement;
+// } & {
+//   currentTarget: HTMLFormElement;
+//   target: Element;
+// };
+
+// export type FormErrors<F extends object> = { [K in keyof F]: boolean };
+// function createErrors<F extends object>(data: F): FormErrors<F> {
+//   const values = Object.keys(data) as Array<keyof F>;
+
+//   const errors = values.reduce((acc, val) => {
+//     acc[val] = false;
+//     return acc;
+//   }, {} as Partial<FormErrors<F>>);
+
+//   return errors as FormErrors<F>;
+// }
+
+export const createForm = <F extends object>(initialFormData: F) => {
+  const [formData, setFormData] = createStore<F>(initialFormData);
+
+  const updateForm = <K extends keyof F>(key: K, value: F[K]): void => {
+    setFormData(key as any, value);
+  };
+
+  const bulkUpdateForm = (bulkUpdateData: Partial<F>): void => {
+    setFormData(val => ({
+      ...val,
+      ...bulkUpdateData
+    }));
+  };
+
+  const overrideForm = (overrideData: F): void => {
+    setFormData(() => overrideData);
+  };
+
+  return {
+    formData,
+    updateForm,
+    bulkUpdateForm,
+    overrideForm
+  };
+};
diff --git a/packages/form/test/index.test.ts b/packages/form/test/index.test.ts
new file mode 100644
index 000000000..29e7016e7
--- /dev/null
+++ b/packages/form/test/index.test.ts
@@ -0,0 +1,7 @@
+import { describe, expect, it } from "vitest";
+
+describe("createForm", () => {
+  it("Should create a form", () => {
+    expect(false).toBe(true);
+  });
+});
diff --git a/packages/form/test/server.test.ts b/packages/form/test/server.test.ts
new file mode 100644
index 000000000..29e7016e7
--- /dev/null
+++ b/packages/form/test/server.test.ts
@@ -0,0 +1,7 @@
+import { describe, expect, it } from "vitest";
+
+describe("createForm", () => {
+  it("Should create a form", () => {
+    expect(false).toBe(true);
+  });
+});
diff --git a/packages/form/tsconfig.json b/packages/form/tsconfig.json
new file mode 100644
index 000000000..0f52db9f5
--- /dev/null
+++ b/packages/form/tsconfig.json
@@ -0,0 +1,5 @@
+{
+  "extends": "../../tsconfig.json",
+  "include": ["./src", "./test", "./dev", "./demo"],
+  "exclude": ["node_modules", "./dist"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c17d51acc..ff4dd4e67 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -225,6 +225,12 @@ importers:
       node-fetch: 3.3.0
       solid-js: 1.6.9
 
+  packages/form:
+    specifiers:
+      solid-js: ^1.6.0
+    dependencies:
+      solid-js: 1.6.9
+
   packages/fullscreen:
     specifiers:
       solid-js: ^1.6.0

From 7fde7e911b7463bc61506b6b61f0d453cdf817c0 Mon Sep 17 00:00:00 2001
From: TannerScadden <tanner@scaddenfamily.com>
Date: Wed, 15 Feb 2023 12:30:15 -0500
Subject: [PATCH 2/4] Initial form proposal. Handles the form submit event,
 gathers the data, and uses zod for validation

---
 packages/form/dev/index.tsx | 88 ++++++++++++++++++++-----------------
 packages/form/package.json  |  7 +++
 packages/form/src/index.ts  | 67 ++++++++++++++--------------
 pnpm-lock.yaml              | 25 +++++++++++
 4 files changed, 113 insertions(+), 74 deletions(-)

diff --git a/packages/form/dev/index.tsx b/packages/form/dev/index.tsx
index 3a6732d5c..573800613 100644
--- a/packages/form/dev/index.tsx
+++ b/packages/form/dev/index.tsx
@@ -1,49 +1,57 @@
 import { Component } from "solid-js";
 import { render } from "solid-js/web";
-import "uno.css";
+import { z } from "zod";
 import { createForm } from "../src";
 
-const App: Component = () => {
-  const { updateForm, formData } = createForm({ name: "", food: "", email: "" });
-
-  const onSubmit = (e: any) => {
-    e.preventDefault();
-    console.log(formData);
-  };
+const registerSchema = z.object({
+  password: z.string().min(8),
+  emergencyContact: z.array(z.object({ firstName: z.string() })).min(2),
+  favoriteNumber: z.number(),
+  user: z.object({
+    lastName: z.string(),
+    email: z.object({
+      random: z.array(
+        z.object({
+          keys: z.object({
+            email: z.string().email()
+          })
+        })
+      )
+    })
+  })
+});
 
+const App: Component = () => {
+  const { handleSubmit } = createForm({
+    schema: registerSchema,
+    onError(errors) {
+      console.log(errors);
+    },
+    onSubmit(data) {
+      console.log(data);
+    }
+  });
   return (
-    <form
-      class="p-24 box-border w-full min-h-screen flex flex-col justify-center items-center space-y-4 bg-gray-800 text-white"
-      onSubmit={onSubmit}
-    >
-      <label>Name</label>
-      <input
-        name="name"
-        required
-        value={formData.name}
-        onInput={e => updateForm("name", e.currentTarget.value)}
-      />
-      <label>Favorite Food</label>
-      <select
-        required
-        name="food"
-        value={formData.food}
-        onInput={e => updateForm("food", e.currentTarget.value)}
-      >
-        <option value="" />
-        <option value="apple">Apple</option>
-        <option value="pear">Pear</option>
-        <option value="banana">banana</option>
-      </select>
-      <label>Email</label>
-      <input
-        name="email"
-        type="email"
-        required
-        value={formData.email}
-        onInput={e => updateForm("email", e.currentTarget.value)}
-      />
-      <button type="submit">Submit</button>
+    <form onSubmit={handleSubmit}>
+      <label>1st Emergency Contact First Name</label>
+      <input name="emergencyContact[0].firstName" required type="text" />
+      <br />
+      <label>2nd Emergency Contact First Name</label>
+      <input name="emergencyContact[1].firstName" required type="text" />
+      <br />
+      <label>Your Favorite Number</label>
+      <input name="favoriteNumber" required type="number" />
+      <br />
+      <label>Your Last Name</label>
+      <input name="user.lastName" required type="text" />
+      <br />
+      <label>Crazy Set Path Email</label>
+      <input name="user.email.random[0].keys.email" required type="email" />
+      <br />
+      <label>Password</label>
+      <input name="password" required type="password" />
+      <br />
+      <button type="submit">Test</button>
     </form>
   );
 };
diff --git a/packages/form/package.json b/packages/form/package.json
index a2b264b83..e87064a2e 100644
--- a/packages/form/package.json
+++ b/packages/form/package.json
@@ -47,6 +47,13 @@
     "solid",
     "primitives"
   ],
+  "devDependencies": {
+    "@types/lodash.set": "4.3.7"
+  },
+  "dependencies": {
+    "zod": "3.20.6",
+    "lodash.set": "4.3.2"
+  },
   "peerDependencies": {
     "solid-js": "^1.6.0"
   },
diff --git a/packages/form/src/index.ts b/packages/form/src/index.ts
index df601f07f..5de979077 100644
--- a/packages/form/src/index.ts
+++ b/packages/form/src/index.ts
@@ -1,46 +1,45 @@
-import { createStore } from "solid-js/store";
+import set from "lodash.set";
+import { z, ZodError } from "zod";
 
-// type FormSubmitEvent = Event & {
-//   submitter: HTMLElement;
-// } & {
-//   currentTarget: HTMLFormElement;
-//   target: Element;
-// };
-
-// export type FormErrors<F extends object> = { [K in keyof F]: boolean };
-// function createErrors<F extends object>(data: F): FormErrors<F> {
-//   const values = Object.keys(data) as Array<keyof F>;
+export type FormError<T> = {
+  data: T;
+  error: ZodError;
+};
 
-//   const errors = values.reduce((acc, val) => {
-//     acc[val] = false;
-//     return acc;
-//   }, {} as Partial<FormErrors<F>>);
+export type CreateFormOptions<T> = {
+  schema: z.AnyZodObject;
+  onSubmit: (data: T) => Promise<void> | void;
+  onError: (errors: FormError<T>) => void | Promise<void>;
+};
 
-//   return errors as FormErrors<F>;
-// }
+type FormEvent = Event & {
+  submitter: HTMLElement;
+} & {
+  currentTarget: HTMLFormElement;
+  target: Element;
+};
 
-export const createForm = <F extends object>(initialFormData: F) => {
-  const [formData, setFormData] = createStore<F>(initialFormData);
+export const createForm = <T>(options: CreateFormOptions<T>) => {
+  const handleSubmit = (e: FormEvent) => {
+    e.preventDefault();
+    const formData = new FormData(e.currentTarget);
 
-  const updateForm = <K extends keyof F>(key: K, value: F[K]): void => {
-    setFormData(key as any, value);
-  };
+    let data: Partial<T> = {};
 
-  const bulkUpdateForm = (bulkUpdateData: Partial<F>): void => {
-    setFormData(val => ({
-      ...val,
-      ...bulkUpdateData
-    }));
-  };
+    for (const [name, value] of formData.entries()) {
+      const v = isNaN(value as any) ? value : Number(value);
+      set(data, name, v);
+    }
 
-  const overrideForm = (overrideData: F): void => {
-    setFormData(() => overrideData);
+    try {
+      options.schema.parse(data);
+      options.onSubmit(data as T);
+    } catch (e) {
+      options.onError({ data: data as T, error: e as ZodError });
+    }
   };
 
   return {
-    formData,
-    updateForm,
-    bulkUpdateForm,
-    overrideForm
+    handleSubmit
   };
 };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ff4dd4e67..c6a178a35 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -227,9 +227,16 @@ importers:
 
   packages/form:
     specifiers:
+      '@types/lodash.set': 4.3.7
+      lodash.set: 4.3.2
       solid-js: ^1.6.0
+      zod: 3.20.6
     dependencies:
+      lodash.set: 4.3.2
       solid-js: 1.6.9
+      zod: 3.20.6
+    devDependencies:
+      '@types/lodash.set': 4.3.7
 
   packages/fullscreen:
     specifiers:
@@ -2950,6 +2957,16 @@ packages:
       '@types/geojson': 7946.0.10
     dev: true
 
+  /@types/lodash.set/4.3.7:
+    resolution: {integrity: sha512-bS5Wkg/nrT82YUfkNYPSccFrNZRL+irl7Yt4iM6OTSQ0VZJED2oUIVm15NkNtUAQ8SRhCe+axqERUV6MJgkeEg==}
+    dependencies:
+      '@types/lodash': 4.14.191
+    dev: true
+
+  /@types/lodash/4.14.191:
+    resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
+    dev: true
+
   /@types/minimist/1.2.2:
     resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
     dev: true
@@ -6045,6 +6062,10 @@ packages:
     resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
     dev: true
 
+  /lodash.set/4.3.2:
+    resolution: {integrity: sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==}
+    dev: false
+
   /lodash.sortby/4.7.0:
     resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
     dev: true
@@ -8416,3 +8437,7 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
     dev: true
+
+  /zod/3.20.6:
+    resolution: {integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==}
+    dev: false

From 30fe9ccaeff63531c108e2d7f1cd38423fe5a60d Mon Sep 17 00:00:00 2001
From: TannerScadden <tanner@scaddenfamily.com>
Date: Wed, 15 Feb 2023 17:26:55 -0500
Subject: [PATCH 3/4] Removed lodash and zod as dependencies

---
 packages/form/package.json      |  6 +--
 packages/form/src/getParseFn.ts | 92 +++++++++++++++++++++++++++++++++
 packages/form/src/index.ts      | 28 +++++++---
 pnpm-lock.yaml                  | 22 +-------
 4 files changed, 117 insertions(+), 31 deletions(-)
 create mode 100644 packages/form/src/getParseFn.ts

diff --git a/packages/form/package.json b/packages/form/package.json
index e87064a2e..2c4dd9259 100644
--- a/packages/form/package.json
+++ b/packages/form/package.json
@@ -48,11 +48,7 @@
     "primitives"
   ],
   "devDependencies": {
-    "@types/lodash.set": "4.3.7"
-  },
-  "dependencies": {
-    "zod": "3.20.6",
-    "lodash.set": "4.3.2"
+    "zod": "3.20.6"
   },
   "peerDependencies": {
     "solid-js": "^1.6.0"
diff --git a/packages/form/src/getParseFn.ts b/packages/form/src/getParseFn.ts
new file mode 100644
index 000000000..6aa3c7150
--- /dev/null
+++ b/packages/form/src/getParseFn.ts
@@ -0,0 +1,92 @@
+// Credit to @trpc/server
+// https://github.com/trpc/trpc/blob/main/packages/server/src/core/parser.ts
+// https://github.com/trpc/trpc/blob/main/packages/server/src/core/internals/getParseFn.ts
+
+export type ParserZodEsque<TInput, TParsedInput> = {
+  _input: TInput;
+  _output: TParsedInput;
+};
+
+export type ParserMyZodEsque<TInput> = {
+  parse: (input: any) => TInput;
+};
+
+export type ParserSuperstructEsque<TInput> = {
+  create: (input: unknown) => TInput;
+};
+
+export type ParserCustomValidatorEsque<TInput> = (input: unknown) => TInput | Promise<TInput>;
+
+export type ParserYupEsque<TInput> = {
+  validateSync: (input: unknown) => TInput;
+};
+export type ParserWithoutInput<TInput> =
+  | ParserYupEsque<TInput>
+  | ParserSuperstructEsque<TInput>
+  | ParserCustomValidatorEsque<TInput>
+  | ParserMyZodEsque<TInput>;
+
+export type ParserWithInputOutput<TInput, TParsedInput> = ParserZodEsque<TInput, TParsedInput>;
+
+export type Parser = ParserWithoutInput<any> | ParserWithInputOutput<any, any>;
+
+export type inferParser<TParser extends Parser> = TParser extends ParserWithInputOutput<
+  infer $TIn,
+  infer $TOut
+>
+  ? {
+      in: $TIn;
+      out: $TOut;
+    }
+  : TParser extends ParserWithoutInput<infer $InOut>
+  ? {
+      in: $InOut;
+      out: $InOut;
+    }
+  : never;
+
+export type ParseFn<TType> = (value: unknown) => TType | Promise<TType>;
+
+export function getParseFn<TType>(procedureParser: Parser): ParseFn<TType> {
+  const parser = procedureParser as any;
+
+  if (typeof parser === "function") {
+    // ProcedureParserCustomValidatorEsque
+    return parser;
+  }
+
+  if (typeof parser.parseAsync === "function") {
+    // ProcedureParserZodEsque
+    return parser.parseAsync.bind(parser);
+  }
+
+  if (typeof parser.parse === "function") {
+    // ProcedureParserZodEsque
+    return parser.parse.bind(parser);
+  }
+
+  if (typeof parser.validateSync === "function") {
+    // ProcedureParserYupEsque
+    return parser.validateSync.bind(parser);
+  }
+
+  if (typeof parser.create === "function") {
+    // ProcedureParserSuperstructEsque
+    return parser.create.bind(parser);
+  }
+
+  throw new Error("Could not find a validator fn");
+}
+
+/**
+ * @deprecated only for backwards compat
+ * @internal
+ */
+export function getParseFnOrPassThrough<TType>(
+  procedureParser: Parser | undefined
+): ParseFn<TType> {
+  if (!procedureParser) {
+    return v => v as TType;
+  }
+  return getParseFn(procedureParser);
+}
diff --git a/packages/form/src/index.ts b/packages/form/src/index.ts
index 5de979077..59444dfc9 100644
--- a/packages/form/src/index.ts
+++ b/packages/form/src/index.ts
@@ -1,5 +1,5 @@
-import set from "lodash.set";
-import { z, ZodError } from "zod";
+import { ZodError } from "zod";
+import { getParseFn, Parser } from "./getParseFn";
 
 export type FormError<T> = {
   data: T;
@@ -7,9 +7,10 @@ export type FormError<T> = {
 };
 
 export type CreateFormOptions<T> = {
-  schema: z.AnyZodObject;
+  schema?: Parser;
   onSubmit: (data: T) => Promise<void> | void;
   onError: (errors: FormError<T>) => void | Promise<void>;
+  castNumbers?: boolean;
 };
 
 type FormEvent = Event & {
@@ -19,20 +20,35 @@ type FormEvent = Event & {
   target: Element;
 };
 
+// Taken from https://youmightnotneed.com/lodash#set
+const set = <T extends object>(obj: T, path: string | string[], value: string | number) => {
+  // Regex explained: https://regexr.com/58j0k
+  const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g)!;
+
+  pathArray.reduce((acc, key, i) => {
+    if (acc[key] === undefined) acc[key] = {};
+    if (i === pathArray.length - 1) acc[key] = value;
+    return acc[key];
+  }, obj as any);
+};
+
 export const createForm = <T>(options: CreateFormOptions<T>) => {
-  const handleSubmit = (e: FormEvent) => {
+  const handleSubmit = async (e: FormEvent) => {
     e.preventDefault();
     const formData = new FormData(e.currentTarget);
 
     let data: Partial<T> = {};
 
     for (const [name, value] of formData.entries()) {
-      const v = isNaN(value as any) ? value : Number(value);
+      const v = !options.castNumbers || isNaN(value as any) ? value.toString() : Number(value);
       set(data, name, v);
     }
 
     try {
-      options.schema.parse(data);
+      if (options.schema) {
+        const parser = getParseFn(options.schema);
+        await parser(data);
+      }
       options.onSubmit(data as T);
     } catch (e) {
       options.onError({ data: data as T, error: e as ZodError });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c6a178a35..260212598 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -227,16 +227,12 @@ importers:
 
   packages/form:
     specifiers:
-      '@types/lodash.set': 4.3.7
-      lodash.set: 4.3.2
       solid-js: ^1.6.0
       zod: 3.20.6
     dependencies:
-      lodash.set: 4.3.2
       solid-js: 1.6.9
-      zod: 3.20.6
     devDependencies:
-      '@types/lodash.set': 4.3.7
+      zod: 3.20.6
 
   packages/fullscreen:
     specifiers:
@@ -2957,16 +2953,6 @@ packages:
       '@types/geojson': 7946.0.10
     dev: true
 
-  /@types/lodash.set/4.3.7:
-    resolution: {integrity: sha512-bS5Wkg/nrT82YUfkNYPSccFrNZRL+irl7Yt4iM6OTSQ0VZJED2oUIVm15NkNtUAQ8SRhCe+axqERUV6MJgkeEg==}
-    dependencies:
-      '@types/lodash': 4.14.191
-    dev: true
-
-  /@types/lodash/4.14.191:
-    resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
-    dev: true
-
   /@types/minimist/1.2.2:
     resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
     dev: true
@@ -6062,10 +6048,6 @@ packages:
     resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
     dev: true
 
-  /lodash.set/4.3.2:
-    resolution: {integrity: sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==}
-    dev: false
-
   /lodash.sortby/4.7.0:
     resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
     dev: true
@@ -8440,4 +8422,4 @@ packages:
 
   /zod/3.20.6:
     resolution: {integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==}
-    dev: false
+    dev: true

From b78a704a564fdcbb8b48f07632c0d2aebf7ba41c Mon Sep 17 00:00:00 2001
From: TannerScadden <tanner@scaddenfamily.com>
Date: Wed, 15 Feb 2023 17:28:53 -0500
Subject: [PATCH 4/4] Changed error to be of type unknown

---
 packages/form/src/index.ts | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/packages/form/src/index.ts b/packages/form/src/index.ts
index 59444dfc9..f7c31da70 100644
--- a/packages/form/src/index.ts
+++ b/packages/form/src/index.ts
@@ -1,9 +1,8 @@
-import { ZodError } from "zod";
 import { getParseFn, Parser } from "./getParseFn";
 
 export type FormError<T> = {
   data: T;
-  error: ZodError;
+  error: unknown;
 };
 
 export type CreateFormOptions<T> = {
@@ -51,7 +50,7 @@ export const createForm = <T>(options: CreateFormOptions<T>) => {
       }
       options.onSubmit(data as T);
     } catch (e) {
-      options.onError({ data: data as T, error: e as ZodError });
+      options.onError({ data: data as T, error: e });
     }
   };