Skip to content

Commit a81d188

Browse files
committed
Added taint option to formFieldProxy.
1 parent 8119f01 commit a81d188

File tree

5 files changed

+95
-34
lines changed

5 files changed

+95
-34
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ Headlines: Added, Changed, Deprecated, Removed, Fixed, Security
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [1.10.0] - 2023-11-07
99

1010
### Added
1111

1212
- Added `arrayProxy`, for proxying arrays and their errors in the form data.
1313
- Added `FormPathArrays` type, enumerating all arrays in an object as string accessors. Used to access `arrayProxy` in a type-safe manner.
14+
- `formFieldProxy` now has a `taint` option, in case a modification should not taint the form.
1415

1516
## [1.9.0] - 2023-10-31
1617

src/lib/client/index.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,24 +203,26 @@ export type TaintOption<T extends AnyZodObject = AnyZodObject> =
203203
| 'untaint-all'
204204
| { fields: FormPathLeaves<z.infer<T>> | FormPathLeaves<z.infer<T>>[] };
205205

206+
type SuperFormData<T extends ZodValidation<AnyZodObject>> = {
207+
subscribe: Readable<z.infer<UnwrapEffects<T>>>['subscribe'];
208+
set(
209+
this: void,
210+
value: z.infer<UnwrapEffects<T>>,
211+
options?: { taint?: TaintOption<UnwrapEffects<T>> }
212+
): void;
213+
update(
214+
this: void,
215+
updater: Updater<z.infer<UnwrapEffects<T>>>,
216+
options?: { taint?: TaintOption<UnwrapEffects<T>> }
217+
): void;
218+
};
219+
206220
export type SuperForm<
207221
T extends ZodValidation<AnyZodObject>,
208222
// eslint-disable-next-line @typescript-eslint/no-explicit-any
209223
M = App.Superforms.Message extends never ? any : App.Superforms.Message
210224
> = {
211-
form: {
212-
subscribe: Readable<z.infer<UnwrapEffects<T>>>['subscribe'];
213-
set(
214-
this: void,
215-
value: z.infer<UnwrapEffects<T>>,
216-
options?: { taint?: TaintOption<UnwrapEffects<T>> }
217-
): void;
218-
update(
219-
this: void,
220-
updater: Updater<z.infer<UnwrapEffects<T>>>,
221-
options?: { taint?: TaintOption<UnwrapEffects<T>> }
222-
): void;
223-
};
225+
form: SuperFormData<T>;
224226
formId: Writable<string | undefined>;
225227
errors: Writable<SuperValidated<T, M>['errors']> & {
226228
clear: () => void;

src/lib/client/proxies.ts

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { derived, type Updater, type Writable } from 'svelte/store';
1+
import {
2+
derived,
3+
type Readable,
4+
type Updater,
5+
type Writable
6+
} from 'svelte/store';
27
import {
38
SuperFormError,
49
type InputConstraint,
@@ -39,6 +44,8 @@ const defaultOptions: DefaultOptions = {
3944
emptyIfZero: true
4045
};
4146

47+
export type TaintOptions = boolean | 'untaint' | 'untaint-all';
48+
4249
///// Proxy functions ///////////////////////////////////////////////
4350

4451
export function booleanProxy<
@@ -239,32 +246,35 @@ export function arrayProxy<
239246
>(
240247
// eslint-disable-next-line @typescript-eslint/no-explicit-any
241248
superForm: SuperForm<T, any>,
242-
path: Path
249+
path: Path,
250+
options?: { taint?: TaintOptions }
243251
): {
244252
path: Path;
245253
values: Writable<FormPathType<z.infer<UnwrapEffects<T>>, Path>>;
246254
errors: Writable<string[] | undefined>;
247255
} {
248256
return {
249257
path,
250-
values: fieldProxy(superForm.form, path),
258+
values: superFieldProxy(superForm, path, options),
251259
errors: fieldProxy(
252260
superForm.errors,
253261
// eslint-disable-next-line @typescript-eslint/no-explicit-any
254262
`${path}._errors` as any
255263
) as Writable<string[] | undefined>
256264
};
257265
}
266+
258267
export function formFieldProxy<
259268
T extends ZodValidation<AnyZodObject>,
260269
Path extends FormPathLeaves<z.infer<UnwrapEffects<T>>>
261270
>(
262271
// eslint-disable-next-line @typescript-eslint/no-explicit-any
263272
superForm: SuperForm<T, any>,
264-
path: Path
273+
path: Path,
274+
options?: { taint?: TaintOptions }
265275
): {
266276
path: Path;
267-
value: Writable<FormPathType<z.infer<UnwrapEffects<T>>, Path>>;
277+
value: SuperFieldProxy<FormPathType<z.infer<UnwrapEffects<T>>, Path>>;
268278
errors: Writable<string[] | undefined>;
269279
constraints: Writable<InputConstraint | undefined>;
270280
tainted: Writable<boolean | undefined>;
@@ -312,7 +322,7 @@ export function formFieldProxy<
312322

313323
return {
314324
path,
315-
value: fieldProxy(superForm.form, path),
325+
value: superFieldProxy(superForm, path, options),
316326
errors: fieldProxy(superForm.errors, path) as unknown as Writable<
317327
string[] | undefined
318328
>,
@@ -324,6 +334,60 @@ export function formFieldProxy<
324334
};
325335
}
326336

337+
type SuperFieldProxy<T extends object> = /*Writable<T> &*/ {
338+
subscribe: Readable<T>['subscribe'];
339+
set(this: void, value: T, options?: { taint?: TaintOptions }): void;
340+
update(
341+
this: void,
342+
updater: Updater<T>,
343+
options?: { taint?: TaintOptions }
344+
): void;
345+
};
346+
347+
function superFieldProxy<
348+
T extends ZodValidation<AnyZodObject>,
349+
Path extends FormPath<z.infer<T>>
350+
>(
351+
superForm: SuperForm<T>,
352+
path: Path,
353+
baseOptions?: { taint?: TaintOptions }
354+
): SuperFieldProxy<FormPathType<z.infer<T>, Path>> {
355+
const form = superForm.form;
356+
const path2 = splitPath<z.infer<T>>(path);
357+
358+
const proxy = derived(form, ($form) => {
359+
const data = traversePath($form, path2);
360+
return data?.value;
361+
});
362+
363+
return {
364+
subscribe(...params: Parameters<typeof proxy.subscribe>) {
365+
const unsub = proxy.subscribe(...params);
366+
return () => unsub();
367+
},
368+
update(
369+
upd: Updater<FormPathType<z.infer<T>, Path>>,
370+
options?: { taint?: TaintOptions }
371+
) {
372+
form.update((f) => {
373+
const output = traversePath(f, path2);
374+
if (output) output.parent[output.key] = upd(output.value);
375+
return f;
376+
}, options ?? baseOptions);
377+
},
378+
set(
379+
value: FormPathType<z.infer<T>, Path>,
380+
options?: { taint?: TaintOptions }
381+
) {
382+
form.update((f) => {
383+
const output = traversePath(f, path2);
384+
if (output) output.parent[output.key] = value;
385+
return f;
386+
}, options ?? baseOptions);
387+
}
388+
};
389+
}
390+
327391
export function fieldProxy<T extends object, Path extends FormPath<T>>(
328392
form: Writable<T>,
329393
path: Path
@@ -337,30 +401,21 @@ export function fieldProxy<T extends object, Path extends FormPath<T>>(
337401

338402
return {
339403
subscribe(...params: Parameters<typeof proxy.subscribe>) {
340-
//console.log('~ fieldproxy ~ subscribe', path);
341404
const unsub = proxy.subscribe(...params);
342405

343-
return () => {
344-
//console.log('~ fieldproxy ~ unsubscribe', field);
345-
unsub();
346-
};
406+
return () => unsub();
347407
},
348-
//subscribe: proxy.subscribe,
349408
update(upd: Updater<FormPathType<T, Path>>) {
350-
//console.log('~ fieldStore ~ update value for', path);
351409
form.update((f) => {
352410
const output = traversePath(f, path2);
353411
if (output) output.parent[output.key] = upd(output.value);
354-
//else console.log('[update] Not found:', path, 'in', f);
355412
return f;
356413
});
357414
},
358415
set(value: FormPathType<T, Path>) {
359-
//console.log('~ fieldStore ~ set value for', path, value);
360416
form.update((f) => {
361417
const output = traversePath(f, path2);
362418
if (output) output.parent[output.key] = value;
363-
//else console.log('[set] Not found:', path, 'in', f);
364419
return f;
365420
});
366421
}

src/routes/tests/array-component/+page.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
dataType: 'json'
1111
//validators: schema
1212
});
13-
const { form, errors, message, enhance } = pageForm;
13+
const { form, errors, message, enhance, tainted } = pageForm;
1414
1515
const options = [
1616
{ value: 'A', label: 'Aldebaran' },
@@ -20,10 +20,12 @@
2020

2121
<SuperDebug data={{ $form, $errors }} />
2222

23+
{#if $tainted}<h3>FORM IS TAINTED</h3>{/if}
24+
2325
{#if $message}<h4>{$message}</h4>{/if}
2426

2527
<form method="POST" use:enhance>
26-
<AutoComplete form={pageForm} field="sub.tags" {options} />
28+
<AutoComplete form={pageForm} field="sub.tags" {options} taint={false} />
2729
<div>
2830
<button>Submit</button>
2931
</div>

src/routes/tests/array-component/AutoComplete.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
import type { z } from 'zod';
88
import type { ZodValidation, FormPathArrays } from '$lib';
99
import type { SuperForm } from '$lib/client';
10-
import { arrayProxy } from '$lib/client/proxies';
10+
import { arrayProxy, type TaintOptions } from '$lib/client/proxies';
1111
1212
export let form: SuperForm<ZodValidation<T>, unknown>;
1313
export let field: FormPathArrays<z.infer<T>>;
1414
export let options: { value: string; label: string }[];
1515
export let label = '';
16+
export let taint: TaintOptions = true;
1617
17-
const { values, errors } = arrayProxy(form, field);
18+
const { values, errors } = arrayProxy(form, field, { taint });
1819
</script>
1920

2021
{#if label}<label for={field}>{label}</label>{/if}

0 commit comments

Comments
 (0)