@michalzard/svelte-forms 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/main.yml +28 -0
- package/.github/workflows/publish.yml +42 -0
- package/CHANGELOG.md +7 -0
- package/README.md +15 -0
- package/bun.lock +276 -0
- package/dist/index.js +3605 -0
- package/dist/index.mjs +3573 -0
- package/index.ts +93 -0
- package/package.json +25 -0
- package/tsconfig.json +21 -0
package/index.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { writable, get, type Writable } from "svelte/store";
|
|
2
|
+
import {
|
|
3
|
+
InferOutput,
|
|
4
|
+
safeParse,
|
|
5
|
+
type BaseSchema,
|
|
6
|
+
type BaseIssue,
|
|
7
|
+
} from "valibot";
|
|
8
|
+
type SubmitFn<T> = (values: T) => void | Promise<void>;
|
|
9
|
+
|
|
10
|
+
type UseFormParams<S extends BaseSchema<unknown, unknown, BaseIssue<unknown>>> =
|
|
11
|
+
{
|
|
12
|
+
initialValues: InferOutput<S>;
|
|
13
|
+
schema: S;
|
|
14
|
+
onSubmit: SubmitFn<InferOutput<S>>;
|
|
15
|
+
debounceInterval?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function useForm<
|
|
19
|
+
S extends BaseSchema<unknown, Record<string, any>, BaseIssue<unknown>>,
|
|
20
|
+
>(params: UseFormParams<S>) {
|
|
21
|
+
type T = InferOutput<S>;
|
|
22
|
+
const { initialValues, schema, onSubmit, debounceInterval = 50 } = params;
|
|
23
|
+
const values: Writable<T> = writable({ ...initialValues });
|
|
24
|
+
// only contains fields that currenlty have errors
|
|
25
|
+
const errors: Writable<Partial<Record<keyof T, string>>> = writable({});
|
|
26
|
+
const touched: Writable<Record<keyof T, boolean>> = writable(
|
|
27
|
+
Object.keys(initialValues).reduce((acc, key) => {
|
|
28
|
+
acc[key as keyof T] = false;
|
|
29
|
+
return acc;
|
|
30
|
+
}, {} as Record<keyof T, boolean>),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
function markTouched(field: keyof T) {
|
|
34
|
+
touched.update((t) => ({ ...t, [field]: true }));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mapIssues(
|
|
38
|
+
issues: BaseIssue<any>[],
|
|
39
|
+
): Partial<Record<keyof T, string>> {
|
|
40
|
+
const newErrors: Partial<Record<keyof T, string>> = {};
|
|
41
|
+
for (const issue of issues ?? []) {
|
|
42
|
+
const field = issue.path?.[0]?.key as keyof T;
|
|
43
|
+
if (field) newErrors[field] = issue.message;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return newErrors;
|
|
47
|
+
}
|
|
48
|
+
// Validation with debounce
|
|
49
|
+
let validationTimer: ReturnType<typeof setTimeout> | null = null;
|
|
50
|
+
values.subscribe(($values) => {
|
|
51
|
+
if (validationTimer) clearTimeout(validationTimer);
|
|
52
|
+
|
|
53
|
+
validationTimer = setTimeout(() => {
|
|
54
|
+
const result = safeParse(schema, $values);
|
|
55
|
+
errors.set(result.success ? {} : mapIssues(result.issues));
|
|
56
|
+
}, debounceInterval);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
async function handleSubmit(event: SubmitEvent) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
const currentValues = get(values);
|
|
62
|
+
errors.set({});
|
|
63
|
+
const result = safeParse(schema, currentValues);
|
|
64
|
+
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
errors.set(mapIssues(result.issues));
|
|
67
|
+
// mark all invalid fields as touched
|
|
68
|
+
touched.update((t) => {
|
|
69
|
+
for (const issue of result.issues ?? []) {
|
|
70
|
+
const field = issue.path?.[0]?.key as keyof T;
|
|
71
|
+
if (field) t[field] = true;
|
|
72
|
+
}
|
|
73
|
+
return t;
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await onSubmit(currentValues);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resetForm() {
|
|
82
|
+
values.set({ ...initialValues });
|
|
83
|
+
errors.set({});
|
|
84
|
+
touched.set(
|
|
85
|
+
Object.keys(initialValues).reduce((acc, key) => {
|
|
86
|
+
acc[key as keyof T] = false;
|
|
87
|
+
return acc;
|
|
88
|
+
}, {} as Record<keyof T, boolean>),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { values, errors, touched, markTouched, handleSubmit, resetForm };
|
|
93
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@michalzard/svelte-forms",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "bun build index.ts --format cjs --outdir dist/cjs && bun build index.ts --format esm --outdir dist/esm && mv dist/cjs/index.js dist/index.js && mv dist/esm/index.js dist/index.mjs",
|
|
9
|
+
"release": "bun run build && changeset publish"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@changesets/cli": "^2.29.8",
|
|
16
|
+
"@types/bun": "latest"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"typescript": "^5"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"svelte": "^5.53.6",
|
|
23
|
+
"valibot": "^1.2.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["ESNext"],
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "Preserve",
|
|
6
|
+
"moduleDetection": "force",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"noFallthroughCasesInSwitch": true,
|
|
15
|
+
"noUncheckedIndexedAccess": true,
|
|
16
|
+
"noImplicitOverride": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noPropertyAccessFromIndexSignature": true
|
|
20
|
+
}
|
|
21
|
+
}
|