Skip to content

Commit b975d2a

Browse files
committed
Random id for default object forms.
1 parent 39cc111 commit b975d2a

File tree

8 files changed

+139
-17
lines changed

8 files changed

+139
-17
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- "SPA action mode", the `SPA` option can now take a string, corresponding to a form action, and it will post there, without the need for a html form on the page.
12+
- "SPA action mode", the `SPA` option can now take a string, corresponding to a form action, and it will post there, even without a html form on the page.
1313

1414
### Fixed
1515

1616
- Fixed types for constraints, tainted and errors when using intersections and unions in schemas.
1717
- Fixed SuperDebug collapsed height by preventing global css leak.
1818
- Redirect in SPA mode cancelled normal redirects, preventing applyAction.
19+
- Default objects sent to `superForm` returned a form with an empty id, causing collisions, it is now a random string.
1920

2021
## [2.8.1] - 2024-03-07
2122

src/lib/client/superForm.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import type {
3939
} from '$lib/adapters/adapters.js';
4040
import type { InputConstraints } from '$lib/jsonSchema/constraints.js';
4141
import { fieldProxy, type ProxyOptions } from './proxies.js';
42+
import { shapeFromObject } from '$lib/jsonSchema/schemaShape.js';
4243

4344
export type SuperFormEvents<T extends Record<string, unknown>, M> = Pick<
4445
FormOptions<T, M>,
@@ -395,9 +396,9 @@ try {
395396

396397
/**
397398
* Initializes a SvelteKit form, for convenient handling of values, errors and sumbitting data.
398-
* @param {SuperValidated} form Usually data.form from PageData.
399-
* @param {FormOptions} options Configuration for the form.
400-
* @returns {SuperForm} An object with properties for the form.
399+
* @param {SuperValidated} form Usually data.form from PageData or defaults, but can also be an object with default values, but then constraints won't be available.
400+
* @param {FormOptions} formOptions Configuration for the form.
401+
* @returns {SuperForm} A SuperForm object that can be used in a Svelte component.
401402
* @DCI-context
402403
*/
403404
export function superForm<
@@ -423,7 +424,6 @@ export function superForm<
423424
}
424425

425426
if (typeof options.SPA === 'string') {
426-
if (options.validators === undefined) options.validators = false;
427427
if (options.invalidateAll === undefined) options.invalidateAll = false;
428428
if (options.applyAction === undefined) options.applyAction = false;
429429
}
@@ -435,7 +435,7 @@ export function superForm<
435435
...options
436436
};
437437

438-
if (options.SPA && options.validators === undefined) {
438+
if (options.SPA === true && options.validators === undefined) {
439439
console.warn(
440440
'No validators set for superForm in SPA mode. ' +
441441
'Add a validation adapter to the validators option, or set it to false to disable this warning.'
@@ -452,11 +452,12 @@ export function superForm<
452452

453453
if (Context_isValidationObject(form) === false) {
454454
form = {
455-
id: options.id ?? '',
455+
id: options.id ?? Math.random().toString(36).slice(2, 10),
456456
valid: false,
457457
posted: false,
458458
errors: {},
459-
data: form as T
459+
data: form as T,
460+
shape: shapeFromObject(form)
460461
} satisfies SuperValidated<T, M, In>;
461462
}
462463

@@ -489,13 +490,6 @@ export function superForm<
489490
}
490491
initialForm = initialForms.get(form) as SuperValidated<T, M, In>;
491492

492-
if (typeof initialForm.valid !== 'boolean') {
493-
throw new SuperFormError(
494-
'A non-validation object was passed to superForm. ' +
495-
'It should be an object of type SuperValidated, usually returned from superValidate.'
496-
);
497-
}
498-
499493
// Detect if a form is posted without JavaScript.
500494
if (!browser && _currentPage.form && typeof _currentPage.form === 'object') {
501495
const postedData = _currentPage.form;

src/lib/jsonSchema/schemaShape.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,16 @@ function _schemaShape(schema: JSONSchema7Definition, path: string[]): SchemaShap
4949

5050
return info.types.includes('array') || info.types.includes('object') ? {} : undefined;
5151
}
52+
53+
export function shapeFromObject(obj: object): SchemaShape {
54+
let output: SchemaShape = {};
55+
const isArray = Array.isArray(obj);
56+
57+
for (const [key, value] of Object.entries(obj)) {
58+
if (!value || typeof value !== 'object') continue;
59+
if (isArray) output = { ...output, ...shapeFromObject(value) };
60+
else output[key] = shapeFromObject(value);
61+
}
62+
63+
return output;
64+
}

src/routes/(v2)/v2/Navigation.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
'zod-discriminated',
4848
'syncflash',
4949
'redirect-login',
50-
'debounce-username'
50+
'debounce-username',
51+
'mixed-forms'
5152
].sort();
5253
</script>
5354

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { zod } from '$lib/adapters/zod.js';
2+
import { message, superValidate } from '$lib/server/index.js';
3+
import { schema } from './schema.js';
4+
import { fail } from '@sveltejs/kit';
5+
6+
export const load = async () => {
7+
const form = await superValidate(zod(schema));
8+
return { form };
9+
};
10+
11+
export const actions = {
12+
default: async ({ request }) => {
13+
const formData = await request.formData();
14+
console.log(formData);
15+
16+
const form = await superValidate(formData, zod(schema));
17+
console.log(form);
18+
19+
if (!form.valid) return fail(400, { form });
20+
21+
return message(form, 'Posted OK!');
22+
}
23+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<script lang="ts">
2+
import { superForm } from '$lib/client/index.js';
3+
import SuperDebug from '$lib/client/SuperDebug.svelte';
4+
//import { zod } from '$lib/adapters/zod.js'
5+
//import { schema } from './schema.js';
6+
7+
export let data;
8+
9+
const { form, errors, tainted, message, enhance } = superForm(data.form, {
10+
taintedMessage: false
11+
});
12+
13+
const {
14+
form: formData,
15+
formId,
16+
enhance: enhance2,
17+
posted
18+
} = superForm({ name: '' }, { taintedMessage: false, invalidateAll: false });
19+
</script>
20+
21+
<SuperDebug data={{ $form, $errors, $tainted }} />
22+
23+
{#if $message}<h4>{$message}</h4>{/if}
24+
25+
<form method="POST" use:enhance>
26+
<label>
27+
Name: <input
28+
name="name"
29+
bind:value={$form.name}
30+
aria-invalid={$errors.name ? 'true' : undefined}
31+
/>
32+
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
33+
</label>
34+
<label>
35+
Email: <input
36+
name="email"
37+
bind:value={$form.email}
38+
aria-invalid={$errors.email ? 'true' : undefined}
39+
/>
40+
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
41+
</label>
42+
<div>
43+
<button>Submit</button>
44+
</div>
45+
</form>
46+
47+
<form method="POST" use:enhance2>
48+
<div>FORMID:{$formId}:</div>
49+
<div>POSTED:{$posted}:</div>
50+
<label>
51+
Name2: <input name="name" bind:value={$formData.name} />
52+
</label>
53+
<input type="hidden" name="email" value="[email protected]" />
54+
<div>
55+
<button>Submit</button>
56+
</div>
57+
</form>
58+
59+
<style lang="scss">
60+
form {
61+
margin: 2rem 0;
62+
63+
input {
64+
background-color: #dedede;
65+
}
66+
67+
.invalid {
68+
color: crimson;
69+
}
70+
}
71+
</style>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { z } from 'zod';
2+
3+
export const schema = z.object({
4+
name: z.string().min(2),
5+
email: z.string().email()
6+
});

src/tests/JSONSchema.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import type { JSONSchema7 } from 'json-schema';
33
import { schemaInfo } from '$lib/jsonSchema/schemaInfo.js';
44
import type { FromSchema } from 'json-schema-to-ts';
55
import { defaultValues, defaultTypes } from '$lib/jsonSchema/schemaDefaults.js';
6-
import { schemaShape } from '$lib/jsonSchema/schemaShape.js';
6+
import { schemaShape, shapeFromObject } from '$lib/jsonSchema/schemaShape.js';
77
import { z } from 'zod';
88
import { zod, zodToJSONSchema } from '$lib/adapters/zod.js';
99
import { schemaHash } from '$lib/jsonSchema/schemaHash.js';
1010
import { constraints } from '$lib/jsonSchema/constraints.js';
1111
import { SchemaError } from '$lib/errors.js';
12+
import type { Infer } from '$lib/index.js';
1213

1314
const schema = {
1415
type: 'object',
@@ -394,6 +395,18 @@ describe('Object shapes', () => {
394395
numberArray: {}
395396
});
396397
});
398+
399+
it('should return the same with the shapeFromObject function', () => {
400+
const data = {
401+
tags: [
402+
{ id: 1, names: ['aa', 'bb'], test: '' },
403+
{ id: 2, names: ['bb', 'cc'], test: ['aaa'] }
404+
]
405+
} satisfies Infer<typeof schema>;
406+
407+
assert(schema.safeParse(data).success);
408+
expect(shapeFromObject(data)).toEqual({ tags: { names: {}, test: {} } });
409+
});
397410
});
398411

399412
describe('Schema hash function', () => {

0 commit comments

Comments
 (0)