Skip to content

Commit e77a1af

Browse files
committed
[sveltekit] Add additionalPropertyKeyValidationError option
1 parent 5d84164 commit e77a1af

File tree

3 files changed

+106
-34
lines changed

3 files changed

+106
-34
lines changed

packages/sveltekit/src/lib/client/use-form.svelte.ts

Lines changed: 97 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ import { onDestroy } from 'svelte';
33
import type { AnyKey } from '@sjsf/form/lib/types';
44
import { isRecord } from '@sjsf/form/lib/object';
55
import {
6+
DEFAULT_ID_SEPARATOR,
7+
DEFAULT_PSEUDO_ID_SEPARATOR,
68
groupErrors,
7-
useForm,
9+
useForm2,
10+
type AdditionalPropertyKeyError,
11+
type AdditionalPropertyKeyValidator,
812
type Schema,
913
type SchemaValue,
10-
type UseFormOptions
14+
type UseFormOptions2
1115
} from '@sjsf/form';
1216

1317
import { page } from '$app/stores';
@@ -16,12 +20,14 @@ import type { InitialFormData, ValidatedFormData } from '../model';
1620

1721
import { useSvelteKitMutation, type SveltekitMutationOptions } from './use-mutation.svelte';
1822

19-
export type ValidatedFormDataFromActionDataBranch<ActionData, FormName extends keyof ActionData> =
20-
ActionData[FormName] extends ValidatedFormData<any, any> ? ActionData[FormName] : never;
23+
export type ValidatedFormDataFromActionDataBranch<ActionData, FormName extends AnyKey> =
24+
ActionData[keyof ActionData & FormName] extends ValidatedFormData<any, any>
25+
? ActionData[keyof ActionData & FormName]
26+
: never;
2127

2228
export type ValidatedFormDataFromActionDataUnion<
2329
ActionData,
24-
FormName extends keyof ActionData
30+
FormName extends AnyKey
2531
> = ActionData extends any ? ValidatedFormDataFromActionDataBranch<ActionData, FormName> : never;
2632

2733
export type FormNameFromActionDataBranch<ActionData> = keyof {
@@ -41,52 +47,94 @@ export type InitialFromDataFromPageData<PageData, FormName extends AnyKey> =
4147
: never;
4248

4349
export type FormValueFromInitialFormData<IFD, E, FallbackValue> =
44-
IFD extends InitialFormData<infer T, E, any> ? T : FallbackValue;
50+
IFD extends InitialFormData<infer T, E, any>
51+
? unknown extends T
52+
? FallbackValue
53+
: T
54+
: FallbackValue;
4555

4656
export type SendDataFromValidatedFormData<VFD, E> =
4757
VFD extends ValidatedFormData<E, infer SendData> ? SendData : false;
4858

4959
export type SendSchemaFromInitialFormData<IFD, V, E> =
5060
IFD extends InitialFormData<V, E, infer SendSchema> ? SendSchema : false;
5161

52-
export type UseSvelteKitFormOptions<ActionData, FormName, V, E, SendSchema extends boolean> = Omit<
53-
UseFormOptions<V, E>,
54-
'onSubmit' | (SendSchema extends true ? 'schema' : never)
55-
> &
56-
SveltekitMutationOptions<ActionData, V> & {
62+
type SvelteKitForm<
63+
ActionData,
64+
PageData,
65+
N extends FormNameFromActionDataUnion<ActionData>,
66+
FallbackValue = SchemaValue
67+
> = {
68+
name: N;
69+
__actionData: ActionData;
70+
__pageData: PageData;
71+
__fallbackValue: FallbackValue;
72+
};
73+
74+
type NameFromSvelteKitForm<F extends SvelteKitForm<any, any, any>> =
75+
F extends SvelteKitForm<any, any, infer Name> ? Name : never;
76+
77+
export function svelteKitForm<
78+
ActionData extends Record<AnyKey, any> | null,
79+
PageData,
80+
N extends FormNameFromActionDataUnion<ActionData> = FormNameFromActionDataUnion<ActionData>,
81+
FallbackValue = SchemaValue
82+
>(
83+
name: FormNameFromActionDataUnion<ActionData>
84+
): SvelteKitForm<ActionData, PageData, N, FallbackValue> {
85+
return { name } as SvelteKitForm<ActionData, PageData, N, FallbackValue>;
86+
}
87+
88+
export type AdditionalPropertyKeyValidationError =
89+
| string
90+
| ((ctx: { key: string; separator: string; separators: string[] }) => string);
91+
92+
export type UseSvelteKitFormOptions<
93+
F extends SvelteKitForm<any, any, any>,
94+
V,
95+
E,
96+
SendSchema extends boolean,
97+
AD = F['__actionData']
98+
> = Omit<UseFormOptions2<V, E>, 'onSubmit' | (SendSchema extends true ? 'schema' : never)> &
99+
SveltekitMutationOptions<AD, V> & {
57100
// Form options
58-
name: FormName;
101+
spec: F;
59102
/** @default false */
60103
forceDataInvalidation?: boolean;
61104
/** @default true */
62105
resetOnUpdate?: boolean;
106+
additionalPropertyKeyValidationError?: AdditionalPropertyKeyValidationError;
63107
} & (SendSchema extends true
64108
? {
65109
schema?: Schema;
66110
}
67111
: { schema: Schema });
68112

69113
export function useSvelteKitForm<
70-
ActionData extends Record<AnyKey, any> | null,
71-
PageData,
72-
FormName extends
73-
FormNameFromActionDataUnion<ActionData> = FormNameFromActionDataUnion<ActionData>,
74-
FallbackValue = SchemaValue,
114+
const O extends UseSvelteKitFormOptions<any, any, any, any, any>,
75115
// Local
76-
VFD = ValidatedFormDataFromActionDataUnion<ActionData, FormName>,
77-
IFD = InitialFromDataFromPageData<PageData, FormName>,
78-
E = ValidatorErrorFromValidatedFormData<VFD>,
116+
SKF extends SvelteKitForm<any, any, any> = O['spec'],
117+
ActionData = SKF['__actionData'],
118+
PageData = SKF['__pageData'],
119+
FallbackValue = SKF['__fallbackValue'],
120+
N extends AnyKey = NameFromSvelteKitForm<SKF>,
121+
VFD = ValidatedFormDataFromActionDataUnion<ActionData, N>,
122+
IFD = InitialFromDataFromPageData<PageData, N>,
123+
E = O extends
124+
| { additionalPropertyKeyValidationError: AdditionalPropertyKeyValidationError }
125+
| { additionalPropertyKeyValidator: AdditionalPropertyKeyValidator }
126+
? ValidatorErrorFromValidatedFormData<VFD> | AdditionalPropertyKeyError
127+
: ValidatorErrorFromValidatedFormData<VFD>,
79128
V = FormValueFromInitialFormData<IFD, E, FallbackValue>,
80129
SendSchema extends boolean = SendSchemaFromInitialFormData<IFD, V, E>,
81130
SendData extends boolean = SendDataFromValidatedFormData<VFD, E>
82-
>(options: UseSvelteKitFormOptions<ActionData, FormName, V, E, SendSchema>) {
131+
>(options: O) {
83132
let lastInitialFormData: InitialFormData<V, E, SendSchema> | undefined;
84133
let initialized = false;
134+
const spec: SKF = options.spec;
85135
const unsubscribe = page.subscribe((page) => {
86136
if (isRecord(page.form)) {
87-
const validationData = page.form[options.name] as
88-
| ValidatedFormData<E, SendData>
89-
| undefined;
137+
const validationData = page.form[spec.name] as ValidatedFormData<E, SendData> | undefined;
90138
if (validationData !== undefined) {
91139
if (initialized) {
92140
if (validationData.sendData) {
@@ -96,7 +144,7 @@ export function useSvelteKitForm<
96144
} else {
97145
initialized = true;
98146
lastInitialFormData = {
99-
schema: options.schema ?? page.data[options.name as string].schema,
147+
schema: options.schema ?? page.data[spec.name as string].schema,
100148
initialValue: validationData.data as V,
101149
initialErrors: validationData.errors
102150
};
@@ -106,18 +154,39 @@ export function useSvelteKitForm<
106154
}
107155
if (!initialized) {
108156
initialized = true;
109-
lastInitialFormData = page.data[options.name as string];
157+
lastInitialFormData = page.data[spec.name as string];
110158
return;
111159
}
112160
});
113161
onDestroy(unsubscribe);
114162

115163
const mutation = useSvelteKitMutation<ActionData, V>(options);
116164

117-
const form = useForm<V, E>(
165+
const separators = [
166+
options.idSeparator ?? DEFAULT_ID_SEPARATOR,
167+
options.pseudoIdSeparator ?? DEFAULT_PSEUDO_ID_SEPARATOR
168+
];
169+
const additionalPropertyKeyValidationError = $derived(
170+
options.additionalPropertyKeyValidationError
171+
);
172+
const form = useForm2<UseFormOptions2<V, E>>(
118173
Object.setPrototypeOf(options, {
119174
...lastInitialFormData,
120-
onSubmit: mutation.run
175+
onSubmit: mutation.run,
176+
additionalPropertyKeyValidator: additionalPropertyKeyValidationError && {
177+
validateAdditionalPropertyKey(key: string): string[] {
178+
for (const separator of separators) {
179+
if (key.includes(separator)) {
180+
return [
181+
typeof additionalPropertyKeyValidationError === 'string'
182+
? additionalPropertyKeyValidationError
183+
: additionalPropertyKeyValidationError({ key, separator, separators })
184+
];
185+
}
186+
}
187+
return [];
188+
}
189+
}
121190
})
122191
);
123192

packages/sveltekit/src/routes/+page.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const schema: Schema = {
3232
};
3333

3434
export const load = async () => {
35-
const form = initForm({ initialValue: {}, schema, validator, sendSchema: true });
35+
const form = initForm({ schema, validator, sendSchema: true });
3636
return { form };
3737
};
3838

packages/sveltekit/src/routes/+page.svelte

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,24 @@
44
import { theme } from '@sjsf/form/basic-theme';
55
import { translation } from '@sjsf/form/translations/en';
66
7-
import { useSvelteKitForm } from '$lib/client';
7+
import { useSvelteKitForm, svelteKitForm } from '$lib/client';
88
99
import type { PageData, ActionData } from './$types';
1010
11-
const { form } = useSvelteKitForm<ActionData, PageData>({
11+
const { form, enhance } = useSvelteKitForm({
1212
...theme,
13-
name: 'form',
13+
spec: svelteKitForm<ActionData, PageData>('form'),
1414
validator: createValidator(),
1515
translation,
1616
onSuccess: console.log,
17-
onFailure: console.warn
17+
onFailure: console.warn,
18+
additionalPropertyKeyValidationError({ separators }) {
19+
return `The content of these sequences ("${separators.join('", "')}") is prohibited`;
20+
}
1821
});
1922
</script>
2023

21-
<form method="POST" novalidate style="display: flex; flex-direction: column; gap: 1rem">
24+
<form use:enhance method="POST" novalidate style="display: flex; flex-direction: column; gap: 1rem">
2225
<FormContent bind:value={form.formValue} />
2326
<button type="submit" style="padding: 0.5rem;">Submit</button>
2427
</form>

0 commit comments

Comments
 (0)