Skip to content

Commit 7c46d8d

Browse files
committed
WIP: Array paths only in FormPath-related types.
1 parent f74602f commit 7c46d8d

File tree

5 files changed

+149
-5
lines changed

5 files changed

+149
-5
lines changed

src/lib/stringPath.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export function mergePath(path: (string | number | symbol)[]) {
2121
/**
2222
* Lists all paths in an object as string accessors.
2323
*/
24-
export type FormPath<T extends object> =
25-
| (string & StringPath<T>)
24+
export type FormPath<T extends object, OnlyArrays extends boolean = false> =
25+
| (string & StringPath<T, OnlyArrays>)
2626
| FormPathLeaves<T>;
2727

2828
/**
@@ -31,7 +31,10 @@ export type FormPath<T extends object> =
3131
*/
3232
export type FormPathLeaves<T extends object> = string & StringPathLeaves<T>;
3333

34-
export type StringPath<T extends object> = NonNullable<T> extends (infer U)[]
34+
export type StringPath<
35+
T extends object,
36+
OnlyArrays extends boolean = false
37+
> = NonNullable<T> extends (infer U)[]
3538
? NonNullable<U> extends object
3639
?
3740
| `[${number}]`
@@ -40,10 +43,20 @@ export type StringPath<T extends object> = NonNullable<T> extends (infer U)[]
4043
: '.'}${NonNullable<U> extends Date | Set<unknown>
4144
? never
4245
: StringPath<NonNullable<U>> & string}`
43-
: `[${number}]` | `[${number}].${U & string}`
46+
: `[${number}]` /*| `[${number}].${U & string}`*/
4447
: NonNullable<T> extends object
4548
?
46-
| keyof T
49+
| (OnlyArrays extends false
50+
? keyof T
51+
: {
52+
[K in keyof T]-?: K extends string
53+
? NonNullable<T[K]> extends (infer U2)[]
54+
? U2 extends object
55+
? never
56+
: K
57+
: never
58+
: never;
59+
}[keyof T])
4760
| {
4861
[K in keyof T]-?: K extends string
4962
? NonNullable<T[K]> extends object
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { message, superValidate } from '$lib/server';
2+
import { schema } from './schema';
3+
import { fail } from '@sveltejs/kit';
4+
5+
export const load = async () => {
6+
const form = await superValidate(schema);
7+
return { form };
8+
};
9+
10+
export const actions = {
11+
default: async ({ request }) => {
12+
const formData = await request.formData();
13+
console.log(formData);
14+
15+
const form = await superValidate(formData, schema);
16+
console.log('POST', form);
17+
18+
if (!form.valid) return fail(400, { form });
19+
20+
return message(form, 'Posted OK!');
21+
}
22+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script lang="ts">
2+
import { superForm } from '$lib/client';
3+
import type { PageData } from './$types';
4+
import SuperDebug from '$lib/client/SuperDebug.svelte';
5+
import AutoComplete from './AutoComplete.svelte';
6+
7+
import type { FormPath, FormPathLeaves } from '$lib';
8+
import type { schema } from './schema';
9+
import type { z } from 'zod';
10+
11+
export let data: PageData;
12+
13+
type FP = FormPath<z.infer<typeof schema>, true>;
14+
type FPL = FormPathLeaves<z.infer<typeof schema>>;
15+
16+
type OK = Exclude<FP, FPL>;
17+
18+
const pageForm = superForm(data.form, {
19+
//dataType: 'json',
20+
//validators: schema
21+
});
22+
const { form, errors, message, enhance } = pageForm;
23+
24+
const options = [
25+
{ value: 'A', label: 'Aldebaran' },
26+
{ value: 'B', label: 'Betelgeuse' }
27+
];
28+
</script>
29+
30+
<SuperDebug data={{ $form, $errors }} />
31+
32+
{#if $message}<h4>{$message}</h4>{/if}
33+
34+
<form method="POST" use:enhance>
35+
<AutoComplete form={pageForm} field="tags2" {options} />
36+
<div>
37+
<button>Submit</button>
38+
</div>
39+
</form>
40+
41+
<style lang="scss">
42+
form {
43+
margin: 2rem 0;
44+
45+
input {
46+
background-color: #dedede;
47+
}
48+
49+
.invalid {
50+
color: crimson;
51+
}
52+
}
53+
</style>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<script lang="ts" context="module">
2+
import type { AnyZodObject } from 'zod';
3+
type T = AnyZodObject;
4+
</script>
5+
6+
<script lang="ts" generics="T extends AnyZodObject">
7+
import type { z } from 'zod';
8+
import type { ZodValidation, FormPath, FormPathLeaves } from '$lib';
9+
import type { Writable } from 'svelte/store';
10+
import { fieldProxy, type SuperForm } from '$lib/client';
11+
12+
export let form: SuperForm<ZodValidation<T>, unknown>;
13+
export let field: Exclude<
14+
FormPath<z.infer<T>>,
15+
FormPathLeaves<z.infer<T>>
16+
>;
17+
export let options: { value: string; label: string }[];
18+
export let label = '';
19+
20+
const value = fieldProxy(form.form, field) as Writable<unknown[]>;
21+
const errors = fieldProxy(
22+
form.errors,
23+
`${field}._errors` as FormPath<z.infer<T>>
24+
);
25+
</script>
26+
27+
{#if label}<label for={field}>{label}</label>{/if}
28+
29+
<!-- Note that the selected attribute is required for this to work without JS -->
30+
<select multiple name={field} bind:value={$value}>
31+
{#each options as option}
32+
<option value={option.value} selected={$value.includes(option.value)}
33+
>{option.label}</option
34+
>
35+
{/each}
36+
</select>
37+
38+
{#if $errors}<p class="invalid">{$errors}</p>{/if}
39+
40+
<style>
41+
.invalid {
42+
color: crimson;
43+
}
44+
</style>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { z } from 'zod';
2+
3+
export const schema = z.object({
4+
tags: z.string().min(1).array().min(1),
5+
tags2: z
6+
.object({
7+
id: z.number().int(),
8+
name: z.string().min(1)
9+
})
10+
.array()
11+
.min(1)
12+
});

0 commit comments

Comments
 (0)