svelte-ag 1.0.17 → 1.0.18
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/dist/lib/api/form.svelte.d.ts +66 -2
- package/dist/lib/api/form.svelte.d.ts.map +1 -1
- package/dist/lib/api/form.svelte.js +59 -2
- package/dist/lib/components/form/form-button.svelte +20 -8
- package/dist/lib/components/form/form-button.svelte.d.ts +30 -4
- package/dist/lib/components/form/form-button.svelte.d.ts.map +1 -1
- package/dist/lib/components/form/form-field-full.svelte +1 -1
- package/dist/lib/components/form/form-field-full.svelte.d.ts +1 -1
- package/dist/lib/components/form/form-field-full.svelte.d.ts.map +1 -1
- package/dist/lib/components/search/combinations/searchPopover.svelte.d.ts +1 -1
- package/dist/lib/components/search/components/search-input.svelte.d.ts +1 -1
- package/dist/lib/components/search/components/search.svelte.d.ts +1 -1
- package/dist/lib/components/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/lib/vite/vite-plugin-component-source-collector.d.ts.map +1 -1
- package/dist/lib/vite/vite-plugin-component-source-collector.js +20 -24
- package/package.json +8 -8
- package/src/lib/api/form.svelte.ts +137 -3
- package/src/lib/components/form/form-button.svelte +20 -8
- package/src/lib/components/form/form-field-full.svelte +1 -1
- package/src/lib/vite/vite-plugin-component-source-collector.ts +27 -30
|
@@ -1,18 +1,82 @@
|
|
|
1
1
|
import { superForm, type SuperForm, type SuperValidated } from 'sveltekit-superforms';
|
|
2
2
|
import type { ApiRequestFunction, HTTPMethod, ApiEndpoints, ApiInput, ApiSuccessBody, ApiErrorBody, ApiSchema } from 'ts-ag';
|
|
3
|
-
type ValidInput<E extends ApiEndpoints, P extends E['path'], M extends E['method']> = NonNullable<ApiInput<E, P, M>>;
|
|
3
|
+
export type ValidInput<E extends ApiEndpoints, P extends E['path'], M extends E['method']> = NonNullable<ApiInput<E, P, M>>;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a strongly-typed form factory for an API schema.
|
|
6
|
+
*
|
|
7
|
+
* Call the returned function with `{ path, method, ... }` to get a `SuperForm`
|
|
8
|
+
* that:
|
|
9
|
+
* - Validates using the Valibot schema for the given endpoint.
|
|
10
|
+
* - Submits via the provided `request` function on each valid update.
|
|
11
|
+
* - Maps API errors to `sveltekit-superforms` field errors / messages.
|
|
12
|
+
* - Optionally two-way binds external state through the `bind` adapter.
|
|
13
|
+
*/
|
|
4
14
|
export type ApiRequestForm<API extends ApiEndpoints> = <Path extends API['path'], Method extends Extract<API, {
|
|
5
15
|
path: Path;
|
|
6
16
|
}>['method']>(a: {
|
|
17
|
+
/** API path key used to select a schema and to call `request(path, method, data)` */
|
|
7
18
|
path: Path;
|
|
19
|
+
/** HTTP method used to select a schema and to call `request(path, method, data)` */
|
|
8
20
|
method: Method;
|
|
21
|
+
/**
|
|
22
|
+
* Optional lifecycle hooks for consumers.
|
|
23
|
+
* - `onSuccess`: called after a successful response body is parsed.
|
|
24
|
+
* - `onFail`: called after an error response body is parsed and mapped to form errors/messages.
|
|
25
|
+
*/
|
|
9
26
|
actions?: {
|
|
10
27
|
onSuccess?: (form: SuperValidated<ValidInput<API, Path, Method>>, response: ApiSuccessBody<API, Path, Method>) => void | Promise<void>;
|
|
11
28
|
onFail?: (form: SuperValidated<ValidInput<API, Path, Method>>, response: ApiErrorBody<API, Path, Method>) => void | Promise<void>;
|
|
12
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Partial initial values merged into schema defaults via `defaults(..., valibot(schema))`.
|
|
32
|
+
* Useful for edit forms where you only have a subset of fields initially.
|
|
33
|
+
*/
|
|
13
34
|
defaultValue?: Partial<ApiInput<API, Path, Method>>;
|
|
35
|
+
/**
|
|
36
|
+
* Two-way binding adapter to sync this form with external state.
|
|
37
|
+
*
|
|
38
|
+
* Use this when your app keeps the source-of-truth somewhere else (e.g. a store/box),
|
|
39
|
+
* but you still want Superforms handling validation + errors + submission.
|
|
40
|
+
*
|
|
41
|
+
* How it works:
|
|
42
|
+
* - Form -> external: on any form change, `bind.get(formData)` is validated against the schema,
|
|
43
|
+
* and if it differs from the current form value, `bind.set(formValue)` is called.
|
|
44
|
+
* - External -> form: whenever the external-derived value changes, the form store is updated
|
|
45
|
+
* to match (only if different).
|
|
46
|
+
*
|
|
47
|
+
* Important:
|
|
48
|
+
* - `get` should return an "input shape" object using the formData arg to populate fields that the
|
|
49
|
+
* external data store doesnt determine
|
|
50
|
+
* - `set` should update your external state based on the raw form data.
|
|
51
|
+
* - Keep `get` deterministic and free of side-effects; it is called frequently.
|
|
52
|
+
*/
|
|
53
|
+
bind?: {
|
|
54
|
+
/**
|
|
55
|
+
* Derives the schema-valid value from the current form data.
|
|
56
|
+
* This is where you transform/prune the form state into the exact shape your endpoint expects.
|
|
57
|
+
*
|
|
58
|
+
* Return value must validate to `ValidInput<API, Path, Method>`.
|
|
59
|
+
*/
|
|
60
|
+
get: (formData: ApiInput<API, Path, Method>) => ValidInput<API, Path, Method>;
|
|
61
|
+
/**
|
|
62
|
+
* Writes updated form data back to your external state.
|
|
63
|
+
* Called only when the derived value differs (deep) from the current form state.
|
|
64
|
+
*/
|
|
65
|
+
set: (formData: ApiInput<API, Path, Method>) => void;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Extra `superForm` options (merged last).
|
|
69
|
+
* If you pass `onSubmit` / `onUpdate` here it will override the defaults in this helper.
|
|
70
|
+
*/
|
|
14
71
|
formProps?: Parameters<typeof superForm<ValidInput<API, Path, Method>>>[1];
|
|
15
72
|
}) => SuperForm<ValidInput<API, Path, Method>>;
|
|
73
|
+
/**
|
|
74
|
+
* Build an endpoint-specific Superforms factory.
|
|
75
|
+
*
|
|
76
|
+
* @param schemas A `{[path]: {[method]: schema}}` mapping used to pick the Valibot schema for each endpoint.
|
|
77
|
+
* @param request An API request function that performs `(path, method, data)` and returns a fetch-like `Response`.
|
|
78
|
+
*
|
|
79
|
+
* @returns A function that creates a `SuperForm` for a particular `{path, method}` pair.
|
|
80
|
+
*/
|
|
16
81
|
export declare function createFormFunction<API extends ApiEndpoints>(schemas: Partial<Record<API['path'], Partial<Record<HTTPMethod, ApiSchema>>>>, request: ApiRequestFunction<API>): ApiRequestForm<API>;
|
|
17
|
-
export {};
|
|
18
82
|
//# sourceMappingURL=form.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/api/form.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkC,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtH,OAAO,KAAK,EACV,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,SAAS,EACV,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"form.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/api/form.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkC,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtH,OAAO,KAAK,EACV,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,SAAS,EACV,MAAM,OAAO,CAAC;AAMf,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAI,WAAW,CACtG,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAClB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,CAAC,GAAG,SAAS,YAAY,IAAI,CACrD,IAAI,SAAS,GAAG,CAAC,MAAM,CAAC,EACxB,MAAM,SAAS,OAAO,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,EACrD,CAAC,EAAE;IACH,qFAAqF;IACrF,IAAI,EAAE,IAAI,CAAC;IAEX,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,CACV,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,EACnD,QAAQ,EAAE,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,KACxC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,CAAC,EAAE,CACP,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,EACnD,QAAQ,EAAE,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,KACtC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC3B,CAAC;IAEF;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAEpD;;;;;;;;;;;;;;;;;OAiBG;IACH,IAAI,CAAC,EAAE;QACL;;;;;WAKG;QACH,GAAG,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAE9E;;;WAGG;QACH,GAAG,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;KACtD,CAAC;IAEF;;;OAGG;IACH,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5E,KAAK,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,SAAS,YAAY,EACzD,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,EAC7E,OAAO,EAAE,kBAAkB,CAAC,GAAG,CAAC,GAC/B,cAAc,CAAC,GAAG,CAAC,CAqGrB"}
|
|
@@ -1,19 +1,46 @@
|
|
|
1
1
|
import { superForm, defaults, setError, setMessage } from 'sveltekit-superforms';
|
|
2
2
|
import { valibot } from 'sveltekit-superforms/adapters';
|
|
3
|
+
import { watch } from 'runed';
|
|
4
|
+
import { dequal } from 'dequal';
|
|
5
|
+
import { get } from 'svelte/store';
|
|
6
|
+
import { safeParseAsync } from 'valibot';
|
|
7
|
+
/**
|
|
8
|
+
* Build an endpoint-specific Superforms factory.
|
|
9
|
+
*
|
|
10
|
+
* @param schemas A `{[path]: {[method]: schema}}` mapping used to pick the Valibot schema for each endpoint.
|
|
11
|
+
* @param request An API request function that performs `(path, method, data)` and returns a fetch-like `Response`.
|
|
12
|
+
*
|
|
13
|
+
* @returns A function that creates a `SuperForm` for a particular `{path, method}` pair.
|
|
14
|
+
*/
|
|
3
15
|
export function createFormFunction(schemas, request) {
|
|
4
|
-
return ({ path, method, actions, defaultValue, formProps }) => {
|
|
16
|
+
return ({ path, method, actions, defaultValue, formProps, bind }) => {
|
|
5
17
|
const schema = schemas[path]?.[method];
|
|
6
18
|
if (schema === undefined)
|
|
7
19
|
throw new Error('Invalid schema for form');
|
|
8
20
|
// if (typeof schema === 'function') {
|
|
9
21
|
// schema = schema();
|
|
10
22
|
// }
|
|
11
|
-
|
|
23
|
+
const form = superForm(defaults(defaultValue, valibot(schema)), {
|
|
12
24
|
SPA: true,
|
|
13
25
|
resetForm: true,
|
|
14
26
|
applyAction: false, // Prevents the form redirecting to the same page on submit
|
|
15
27
|
delayMs: 300,
|
|
16
28
|
validators: valibot(schema),
|
|
29
|
+
async onSubmit({ submitter }) {
|
|
30
|
+
// If a submit button has a name/value, include it in JSON forms (common for "intent" buttons).
|
|
31
|
+
if (submitter &&
|
|
32
|
+
'name' in submitter &&
|
|
33
|
+
typeof submitter.name === 'string' &&
|
|
34
|
+
'value' in submitter &&
|
|
35
|
+
typeof submitter.value === 'string') {
|
|
36
|
+
form.form.update((f) => {
|
|
37
|
+
if (submitter.name in f) {
|
|
38
|
+
f[submitter.name] = submitter.value;
|
|
39
|
+
}
|
|
40
|
+
return f;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
},
|
|
17
44
|
async onUpdate({ form }) {
|
|
18
45
|
if (!form.valid)
|
|
19
46
|
return;
|
|
@@ -42,5 +69,35 @@ export function createFormFunction(schemas, request) {
|
|
|
42
69
|
},
|
|
43
70
|
...formProps
|
|
44
71
|
});
|
|
72
|
+
if (bind !== undefined) {
|
|
73
|
+
/**
|
|
74
|
+
* Reads current form store, maps it through `bind.get`, and validates it against the endpoint schema.
|
|
75
|
+
* Returns the parsed (schema-valid) value.
|
|
76
|
+
*/
|
|
77
|
+
const bindGet = async () => {
|
|
78
|
+
const formData = get(form.form);
|
|
79
|
+
return (await safeParseAsync(schema, bind.get(formData))).output;
|
|
80
|
+
};
|
|
81
|
+
form.form.subscribe((v) => {
|
|
82
|
+
bindGet().then((bindValue) => {
|
|
83
|
+
// console.log('Updating binded value', bindValue, 'to', v);
|
|
84
|
+
if (!dequal(bindValue, v)) {
|
|
85
|
+
bind.set(v);
|
|
86
|
+
// bindGet().then((v) => {
|
|
87
|
+
// console.log('done update', bindGet());
|
|
88
|
+
// });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
watch(() => bindGet(), (newPromise) => {
|
|
93
|
+
newPromise.then((newValue) => {
|
|
94
|
+
// console.log('The state changed, updating the form from', get(form.form), 'to', newValue);
|
|
95
|
+
if (!dequal(get(form.form), newValue)) {
|
|
96
|
+
form.form.set(newValue);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return form;
|
|
45
102
|
};
|
|
46
103
|
}
|
|
@@ -7,21 +7,33 @@ Icons:
|
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
<script module lang="ts">
|
|
10
|
-
import
|
|
11
|
-
|
|
10
|
+
import type { FormFieldProps } from './form-field.svelte';
|
|
11
|
+
import type { FormPath } from 'sveltekit-superforms';
|
|
12
|
+
import type { Props as ButtonProps } from '$shadcn/button/index.js';
|
|
13
|
+
|
|
14
|
+
export type FormButtonProps<T extends Record<string, unknown>, U extends FormPath<T>> = Omit<ButtonProps, 'form'> & {
|
|
15
|
+
form?: FormFieldProps<T, U>['form'];
|
|
16
|
+
name?: U;
|
|
17
|
+
};
|
|
12
18
|
</script>
|
|
13
19
|
|
|
14
|
-
<script lang="ts">
|
|
20
|
+
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
|
21
|
+
import { Button } from '$shadcn/button/index.js';
|
|
15
22
|
import { cn, flyAndScale } from '../../utils/utils.js';
|
|
16
23
|
import { getFormContext } from './form.svelte';
|
|
17
24
|
|
|
18
|
-
let {
|
|
25
|
+
let {
|
|
26
|
+
ref = $bindable(null),
|
|
27
|
+
form = getFormContext<T, U>(),
|
|
28
|
+
children,
|
|
29
|
+
class: className,
|
|
30
|
+
...restProps
|
|
31
|
+
}: FormButtonProps<T, U> = $props();
|
|
19
32
|
|
|
20
|
-
const
|
|
21
|
-
const { submitting, delayed } = form;
|
|
33
|
+
const { submitting, delayed } = $derived(form);
|
|
22
34
|
</script>
|
|
23
35
|
|
|
24
|
-
<Button
|
|
36
|
+
<Button bind:ref type="submit" class={cn(className)} {...restProps}>
|
|
25
37
|
<span class={cn('flex transition-opacity', $submitting && !$delayed && 'opacity-0')}>
|
|
26
38
|
{#if $submitting && $delayed}
|
|
27
39
|
<span in:flyAndScale|global class="icon-loading"></span>
|
|
@@ -29,4 +41,4 @@ Icons:
|
|
|
29
41
|
{@render children?.()}
|
|
30
42
|
{/if}
|
|
31
43
|
</span>
|
|
32
|
-
</Button
|
|
44
|
+
</Button>
|
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type
|
|
1
|
+
import type { FormFieldProps } from './form-field.svelte';
|
|
2
|
+
import type { FormPath } from 'sveltekit-superforms';
|
|
3
|
+
import type { Props as ButtonProps } from '$shadcn/button/index.js';
|
|
4
|
+
export type FormButtonProps<T extends Record<string, unknown>, U extends FormPath<T>> = Omit<ButtonProps, 'form'> & {
|
|
5
|
+
form?: FormFieldProps<T, U>['form'];
|
|
6
|
+
name?: U;
|
|
7
|
+
};
|
|
8
|
+
declare function $$render<T extends Record<string, unknown>, U extends FormPath<T>>(): {
|
|
9
|
+
props: FormButtonProps<T, U>;
|
|
10
|
+
exports: {};
|
|
11
|
+
bindings: "ref";
|
|
12
|
+
slots: {};
|
|
13
|
+
events: {};
|
|
14
|
+
};
|
|
15
|
+
declare class __sveltets_Render<T extends Record<string, unknown>, U extends FormPath<T>> {
|
|
16
|
+
props(): ReturnType<typeof $$render<T, U>>['props'];
|
|
17
|
+
events(): ReturnType<typeof $$render<T, U>>['events'];
|
|
18
|
+
slots(): ReturnType<typeof $$render<T, U>>['slots'];
|
|
19
|
+
bindings(): "ref";
|
|
20
|
+
exports(): {};
|
|
21
|
+
}
|
|
22
|
+
interface $$IsomorphicComponent {
|
|
23
|
+
new <T extends Record<string, unknown>, U extends FormPath<T>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T, U>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T, U>['props']>, ReturnType<__sveltets_Render<T, U>['events']>, ReturnType<__sveltets_Render<T, U>['slots']>> & {
|
|
24
|
+
$$bindings?: ReturnType<__sveltets_Render<T, U>['bindings']>;
|
|
25
|
+
} & ReturnType<__sveltets_Render<T, U>['exports']>;
|
|
26
|
+
<T extends Record<string, unknown>, U extends FormPath<T>>(internal: unknown, props: ReturnType<__sveltets_Render<T, U>['props']> & {}): ReturnType<__sveltets_Render<T, U>['exports']>;
|
|
27
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
|
|
28
|
+
}
|
|
29
|
+
declare const FormButton: $$IsomorphicComponent;
|
|
30
|
+
type FormButton<T extends Record<string, unknown>, U extends FormPath<T>> = InstanceType<typeof FormButton<T, U>>;
|
|
5
31
|
export default FormButton;
|
|
6
32
|
//# sourceMappingURL=form-button.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-button.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/form/form-button.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,MAAM,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"form-button.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/form/form-button.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEpE,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG;IAClH,IAAI,CAAC,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,CAAC,CAAC;CACV,CAAC;AAMJ,iBAAS,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC;WAgC7C,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;;;;;EAA+E;AACjI,cAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC;IAC3E,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAClD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACpD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAClD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC9b,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACrL,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,EAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACrE;AACD,QAAA,MAAM,UAAU,EAAE,qBAAmC,CAAC;AACpC,KAAK,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,CAAC;AAClH,eAAe,UAAU,CAAC"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export type FormFullFieldProps<T extends Record<string, unknown>, U extends FormPath<T>> = FormFieldProps<T, U> & {
|
|
3
3
|
label: string;
|
|
4
4
|
description?: string;
|
|
5
|
-
inputProps
|
|
5
|
+
inputProps?: HTMLInputAttributes;
|
|
6
6
|
} & WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>>;
|
|
7
7
|
</script>
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type FormFullFieldProps<T extends Record<string, unknown>, U extends FormPath<T>> = FormFieldProps<T, U> & {
|
|
2
2
|
label: string;
|
|
3
3
|
description?: string;
|
|
4
|
-
inputProps
|
|
4
|
+
inputProps?: HTMLInputAttributes;
|
|
5
5
|
} & WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>>;
|
|
6
6
|
import { type FormFieldProps } from './form-field.svelte';
|
|
7
7
|
import { type FormPath } from 'sveltekit-superforms';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-field-full.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/form/form-field-full.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAChH,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"form-field-full.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/form/form-field-full.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ declare const SearchPopover: import("svelte").Component<SearchPopoverProps, {
|
|
|
18
18
|
score?: number;
|
|
19
19
|
})[];
|
|
20
20
|
}) | null;
|
|
21
|
-
}, "
|
|
21
|
+
}, "value" | "ref" | "perPage" | "trigger">;
|
|
22
22
|
type SearchPopover = ReturnType<typeof SearchPopover>;
|
|
23
23
|
export default SearchPopover;
|
|
24
24
|
//# sourceMappingURL=searchPopover.svelte.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SearchInputProps } from '../types';
|
|
2
|
-
declare const SearchInput: import("svelte").Component<SearchInputProps, {}, "
|
|
2
|
+
declare const SearchInput: import("svelte").Component<SearchInputProps, {}, "value" | "ref">;
|
|
3
3
|
type SearchInput = ReturnType<typeof SearchInput>;
|
|
4
4
|
export default SearchInput;
|
|
5
5
|
//# sourceMappingURL=search-input.svelte.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SearchProps } from '../types.js';
|
|
2
|
-
declare const Search: import("svelte").Component<SearchProps, {}, "items" | "
|
|
2
|
+
declare const Search: import("svelte").Component<SearchProps, {}, "items" | "value" | "ref" | "searchWith">;
|
|
3
3
|
type Search = ReturnType<typeof Search>;
|
|
4
4
|
export default Search;
|
|
5
5
|
//# sourceMappingURL=search.svelte.d.ts.map
|
|
@@ -6,7 +6,7 @@ declare const SidebarInput: import("svelte").Component<(Omit<import("svelte/elem
|
|
|
6
6
|
files?: undefined;
|
|
7
7
|
})) & {
|
|
8
8
|
ref?: HTMLElement | null | undefined;
|
|
9
|
-
}, {}, "
|
|
9
|
+
}, {}, "value" | "ref">;
|
|
10
10
|
type SidebarInput = ReturnType<typeof SidebarInput>;
|
|
11
11
|
export default SidebarInput;
|
|
12
12
|
//# sourceMappingURL=sidebar-input.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-component-source-collector.d.ts","sourceRoot":"","sources":["../../../src/lib/vite/vite-plugin-component-source-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAkB,MAAM,MAAM,CAAC;AAKnD,UAAU,OAAO;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B;;OAEG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAKD,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,IAAI,GAAE,OAA8B,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"vite-plugin-component-source-collector.d.ts","sourceRoot":"","sources":["../../../src/lib/vite/vite-plugin-component-source-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAkB,MAAM,MAAM,CAAC;AAKnD,UAAU,OAAO;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B;;OAEG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAKD,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,IAAI,GAAE,OAA8B,GAAG,MAAM,CAsH7F"}
|
|
@@ -4,15 +4,27 @@ import { resolve, relative, dirname } from 'path';
|
|
|
4
4
|
/** All unique component directories */
|
|
5
5
|
const componentFiles = new Set();
|
|
6
6
|
export default function componentSourceCollector(opts = { safePackages: [] }) {
|
|
7
|
-
|
|
7
|
+
// constants
|
|
8
|
+
const outFileName = opts.outputFile ?? 'component-sources.css';
|
|
9
|
+
const classRegex = /class(?:=|:)/;
|
|
10
|
+
const importRegex = /@import\s+['"]([^'"]+)['"]/g;
|
|
11
|
+
// state
|
|
8
12
|
let config;
|
|
9
13
|
let firstRound = true;
|
|
10
|
-
// ---- helpers ---- //
|
|
11
14
|
let initialTransformDone = false;
|
|
12
15
|
let initialTransformTimer = null;
|
|
16
|
+
function shouldAdd(code) {
|
|
17
|
+
return classRegex.test(code);
|
|
18
|
+
}
|
|
13
19
|
function addPath(file) {
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
if (file !== '' && // No nothing
|
|
21
|
+
!/\.svelte-kit/.test(file) && // No svelte-kit files
|
|
22
|
+
// No dep files unless marked as safe
|
|
23
|
+
(!/\.pnpm|.vite/.test(file) || opts.safePackages.some((p) => file.includes(`node_modules/${p}`)))) {
|
|
24
|
+
const outPath = resolve(config.root, outFileName);
|
|
25
|
+
const cleanedFileName = file.replace(/\?v=.*$/, '');
|
|
26
|
+
componentFiles.add(relative(dirname(outPath), cleanedFileName));
|
|
27
|
+
}
|
|
16
28
|
}
|
|
17
29
|
function scheduleInitialWrite() {
|
|
18
30
|
if (initialTransformTimer)
|
|
@@ -25,8 +37,7 @@ export default function componentSourceCollector(opts = { safePackages: [] }) {
|
|
|
25
37
|
}, 1000); // adjust delay as needed
|
|
26
38
|
}
|
|
27
39
|
const writeOutFile = async () => {
|
|
28
|
-
|
|
29
|
-
const outPath = resolve(config.root, outFile);
|
|
40
|
+
const outPath = resolve(config.root, outFileName);
|
|
30
41
|
const lines = Array.from(componentFiles)
|
|
31
42
|
.map((d) => `@source '${d}';`)
|
|
32
43
|
.sort();
|
|
@@ -34,18 +45,13 @@ export default function componentSourceCollector(opts = { safePackages: [] }) {
|
|
|
34
45
|
if (didWrite)
|
|
35
46
|
console.log('Wrote', lines.length);
|
|
36
47
|
};
|
|
37
|
-
const classRegex = /class(?:=|:)/;
|
|
38
|
-
const importRegex = /@import\s+['"]([^'"]+)['"]/g;
|
|
39
|
-
function shouldAdd(fileName) {
|
|
40
|
-
return !/\.pnpm|.vite/.test(fileName) || opts.safePackages.some((p) => fileName.includes(`node_modules/${p}`));
|
|
41
|
-
}
|
|
42
48
|
// ---- plugin ---- //
|
|
43
49
|
return {
|
|
44
50
|
name: 'vite-plugin-component-source-collector',
|
|
45
51
|
enforce: 'post',
|
|
46
52
|
async configResolved(resolved) {
|
|
47
53
|
config = resolved;
|
|
48
|
-
const outPath = resolve(config.root,
|
|
54
|
+
const outPath = resolve(config.root, outFileName);
|
|
49
55
|
if (config.command === 'build' && firstRound) {
|
|
50
56
|
componentFiles.clear();
|
|
51
57
|
firstRound = false;
|
|
@@ -71,30 +77,20 @@ export default function componentSourceCollector(opts = { safePackages: [] }) {
|
|
|
71
77
|
for (const match of matches) {
|
|
72
78
|
// console.log('MATching', match);
|
|
73
79
|
const resolved = await this.resolve(match[1], id);
|
|
74
|
-
if (resolved
|
|
80
|
+
if (resolved) {
|
|
75
81
|
addPath(resolved.id);
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
|
-
// TODO ignore .vite files
|
|
80
85
|
// Adds all other files with the classRegex
|
|
81
|
-
if (
|
|
86
|
+
if (shouldAdd(code)) {
|
|
82
87
|
addPath(id);
|
|
83
|
-
// if (config.command === 'serve') await writeOutFile();
|
|
84
88
|
}
|
|
85
89
|
if (!initialTransformDone) {
|
|
86
90
|
scheduleInitialWrite();
|
|
87
91
|
}
|
|
88
92
|
},
|
|
89
93
|
async handleHotUpdate(_ctx) {
|
|
90
|
-
// const output = await ctx.read();
|
|
91
|
-
// const id = ctx.file;
|
|
92
|
-
// console.log('Hot update sources', id, output, classRegex.test(output));
|
|
93
|
-
// if (classRegex.test(output)) {
|
|
94
|
-
// componentFiles.add(id);
|
|
95
|
-
// } else {
|
|
96
|
-
// componentFiles.delete(id);
|
|
97
|
-
// }
|
|
98
94
|
await writeOutFile();
|
|
99
95
|
},
|
|
100
96
|
async buildEnd() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-ag",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "Useful svelte components",
|
|
5
5
|
"bugs": "https://github.com/ageorgeh/svelte-ag/issues",
|
|
6
6
|
"repository": {
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"@dnd-kit/helpers": "^0.2.4",
|
|
50
50
|
"@floating-ui/dom": "^1.7.5",
|
|
51
51
|
"@rollup/pluginutils": "^5.3.0",
|
|
52
|
-
"
|
|
52
|
+
"@sveltejs/kit": "^2.52.2",
|
|
53
|
+
"bits-ui": "^2.16.1",
|
|
53
54
|
"bottleneck": "^2.19.5",
|
|
54
55
|
"clsx": "^2.1.1",
|
|
55
56
|
"dequal": "^2.0.3",
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
"formsnap": "2.0.1",
|
|
59
60
|
"radash": "12.1.1",
|
|
60
61
|
"runed": "0.37.1",
|
|
62
|
+
"svelte": "^5.53.0",
|
|
61
63
|
"svelte-toolbelt": "^0.10.6",
|
|
62
64
|
"sveltekit-superforms": "2.29.1",
|
|
63
65
|
"tailwind-merge": "^3.5.0",
|
|
@@ -72,9 +74,8 @@
|
|
|
72
74
|
"@iconify/tailwind4": "^1.2.1",
|
|
73
75
|
"@iconify/types": "^2.0.0",
|
|
74
76
|
"@internationalized/date": "^3.11.0",
|
|
75
|
-
"@lucide/svelte": "^0.
|
|
77
|
+
"@lucide/svelte": "^0.575.0",
|
|
76
78
|
"@playwright/test": "1.57.0",
|
|
77
|
-
"@sveltejs/kit": "^2.52.2",
|
|
78
79
|
"@sveltejs/package": "^2.5.7",
|
|
79
80
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
80
81
|
"@tailwindcss/typography": "^0.5.19",
|
|
@@ -93,7 +94,6 @@
|
|
|
93
94
|
"prettier-plugin-svelte": "^3.5.0",
|
|
94
95
|
"rollup": "^4.57.1",
|
|
95
96
|
"semantic-release": "^25.0.3",
|
|
96
|
-
"svelte": "^5.53.0",
|
|
97
97
|
"svelte-check": "^4.4.1",
|
|
98
98
|
"tailwindcss": "4.2.0",
|
|
99
99
|
"tw-animate-css": "^1.4.0",
|
|
@@ -104,8 +104,7 @@
|
|
|
104
104
|
"vitest": "^4.0.18"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
|
-
"
|
|
108
|
-
"tailwind-variants": "^1.0.0",
|
|
107
|
+
"tailwind-variants": "^3.2.2",
|
|
109
108
|
"tailwindcss": "^4.2.0",
|
|
110
109
|
"tw-animate-css": "^1.4.0"
|
|
111
110
|
},
|
|
@@ -113,6 +112,7 @@
|
|
|
113
112
|
"onlyBuiltDependencies": [
|
|
114
113
|
"esbuild",
|
|
115
114
|
"@tailwindcss/oxide"
|
|
116
|
-
]
|
|
115
|
+
],
|
|
116
|
+
"overrides": {}
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -9,15 +9,40 @@ import type {
|
|
|
9
9
|
ApiErrorBody,
|
|
10
10
|
ApiSchema
|
|
11
11
|
} from 'ts-ag';
|
|
12
|
+
import { watch } from 'runed';
|
|
13
|
+
import { dequal } from 'dequal';
|
|
14
|
+
import { get } from 'svelte/store';
|
|
15
|
+
import { safeParseAsync } from 'valibot';
|
|
12
16
|
|
|
13
|
-
type ValidInput<E extends ApiEndpoints, P extends E['path'], M extends E['method']> = NonNullable<
|
|
17
|
+
export type ValidInput<E extends ApiEndpoints, P extends E['path'], M extends E['method']> = NonNullable<
|
|
18
|
+
ApiInput<E, P, M>
|
|
19
|
+
>;
|
|
14
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Creates a strongly-typed form factory for an API schema.
|
|
23
|
+
*
|
|
24
|
+
* Call the returned function with `{ path, method, ... }` to get a `SuperForm`
|
|
25
|
+
* that:
|
|
26
|
+
* - Validates using the Valibot schema for the given endpoint.
|
|
27
|
+
* - Submits via the provided `request` function on each valid update.
|
|
28
|
+
* - Maps API errors to `sveltekit-superforms` field errors / messages.
|
|
29
|
+
* - Optionally two-way binds external state through the `bind` adapter.
|
|
30
|
+
*/
|
|
15
31
|
export type ApiRequestForm<API extends ApiEndpoints> = <
|
|
16
32
|
Path extends API['path'],
|
|
17
33
|
Method extends Extract<API, { path: Path }>['method']
|
|
18
34
|
>(a: {
|
|
35
|
+
/** API path key used to select a schema and to call `request(path, method, data)` */
|
|
19
36
|
path: Path;
|
|
37
|
+
|
|
38
|
+
/** HTTP method used to select a schema and to call `request(path, method, data)` */
|
|
20
39
|
method: Method;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Optional lifecycle hooks for consumers.
|
|
43
|
+
* - `onSuccess`: called after a successful response body is parsed.
|
|
44
|
+
* - `onFail`: called after an error response body is parsed and mapped to form errors/messages.
|
|
45
|
+
*/
|
|
21
46
|
actions?: {
|
|
22
47
|
onSuccess?: (
|
|
23
48
|
form: SuperValidated<ValidInput<API, Path, Method>>,
|
|
@@ -28,15 +53,67 @@ export type ApiRequestForm<API extends ApiEndpoints> = <
|
|
|
28
53
|
response: ApiErrorBody<API, Path, Method>
|
|
29
54
|
) => void | Promise<void>;
|
|
30
55
|
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Partial initial values merged into schema defaults via `defaults(..., valibot(schema))`.
|
|
59
|
+
* Useful for edit forms where you only have a subset of fields initially.
|
|
60
|
+
*/
|
|
31
61
|
defaultValue?: Partial<ApiInput<API, Path, Method>>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Two-way binding adapter to sync this form with external state.
|
|
65
|
+
*
|
|
66
|
+
* Use this when your app keeps the source-of-truth somewhere else (e.g. a store/box),
|
|
67
|
+
* but you still want Superforms handling validation + errors + submission.
|
|
68
|
+
*
|
|
69
|
+
* How it works:
|
|
70
|
+
* - Form -> external: on any form change, `bind.get(formData)` is validated against the schema,
|
|
71
|
+
* and if it differs from the current form value, `bind.set(formValue)` is called.
|
|
72
|
+
* - External -> form: whenever the external-derived value changes, the form store is updated
|
|
73
|
+
* to match (only if different).
|
|
74
|
+
*
|
|
75
|
+
* Important:
|
|
76
|
+
* - `get` should return an "input shape" object using the formData arg to populate fields that the
|
|
77
|
+
* external data store doesnt determine
|
|
78
|
+
* - `set` should update your external state based on the raw form data.
|
|
79
|
+
* - Keep `get` deterministic and free of side-effects; it is called frequently.
|
|
80
|
+
*/
|
|
81
|
+
bind?: {
|
|
82
|
+
/**
|
|
83
|
+
* Derives the schema-valid value from the current form data.
|
|
84
|
+
* This is where you transform/prune the form state into the exact shape your endpoint expects.
|
|
85
|
+
*
|
|
86
|
+
* Return value must validate to `ValidInput<API, Path, Method>`.
|
|
87
|
+
*/
|
|
88
|
+
get: (formData: ApiInput<API, Path, Method>) => ValidInput<API, Path, Method>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Writes updated form data back to your external state.
|
|
92
|
+
* Called only when the derived value differs (deep) from the current form state.
|
|
93
|
+
*/
|
|
94
|
+
set: (formData: ApiInput<API, Path, Method>) => void;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extra `superForm` options (merged last).
|
|
99
|
+
* If you pass `onSubmit` / `onUpdate` here it will override the defaults in this helper.
|
|
100
|
+
*/
|
|
32
101
|
formProps?: Parameters<typeof superForm<ValidInput<API, Path, Method>>>[1];
|
|
33
102
|
}) => SuperForm<ValidInput<API, Path, Method>>;
|
|
34
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Build an endpoint-specific Superforms factory.
|
|
106
|
+
*
|
|
107
|
+
* @param schemas A `{[path]: {[method]: schema}}` mapping used to pick the Valibot schema for each endpoint.
|
|
108
|
+
* @param request An API request function that performs `(path, method, data)` and returns a fetch-like `Response`.
|
|
109
|
+
*
|
|
110
|
+
* @returns A function that creates a `SuperForm` for a particular `{path, method}` pair.
|
|
111
|
+
*/
|
|
35
112
|
export function createFormFunction<API extends ApiEndpoints>(
|
|
36
113
|
schemas: Partial<Record<API['path'], Partial<Record<HTTPMethod, ApiSchema>>>>,
|
|
37
114
|
request: ApiRequestFunction<API>
|
|
38
115
|
): ApiRequestForm<API> {
|
|
39
|
-
return ({ path, method, actions, defaultValue, formProps }) => {
|
|
116
|
+
return ({ path, method, actions, defaultValue, formProps, bind }) => {
|
|
40
117
|
const schema = schemas[path]?.[method];
|
|
41
118
|
if (schema === undefined) throw new Error('Invalid schema for form');
|
|
42
119
|
|
|
@@ -44,12 +121,29 @@ export function createFormFunction<API extends ApiEndpoints>(
|
|
|
44
121
|
// schema = schema();
|
|
45
122
|
// }
|
|
46
123
|
|
|
47
|
-
|
|
124
|
+
const form = superForm<ValidInput<API, typeof path, typeof method>>(defaults(defaultValue, valibot(schema)), {
|
|
48
125
|
SPA: true,
|
|
49
126
|
resetForm: true,
|
|
50
127
|
applyAction: false, // Prevents the form redirecting to the same page on submit
|
|
51
128
|
delayMs: 300,
|
|
52
129
|
validators: valibot(schema),
|
|
130
|
+
async onSubmit({ submitter }) {
|
|
131
|
+
// If a submit button has a name/value, include it in JSON forms (common for "intent" buttons).
|
|
132
|
+
if (
|
|
133
|
+
submitter &&
|
|
134
|
+
'name' in submitter &&
|
|
135
|
+
typeof submitter.name === 'string' &&
|
|
136
|
+
'value' in submitter &&
|
|
137
|
+
typeof submitter.value === 'string'
|
|
138
|
+
) {
|
|
139
|
+
form.form.update((f) => {
|
|
140
|
+
if ((submitter.name as any) in f) {
|
|
141
|
+
f[submitter.name as any] = submitter.value;
|
|
142
|
+
}
|
|
143
|
+
return f;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
},
|
|
53
147
|
async onUpdate({ form }) {
|
|
54
148
|
if (!form.valid) return;
|
|
55
149
|
|
|
@@ -78,5 +172,45 @@ export function createFormFunction<API extends ApiEndpoints>(
|
|
|
78
172
|
},
|
|
79
173
|
...formProps
|
|
80
174
|
});
|
|
175
|
+
|
|
176
|
+
if (bind !== undefined) {
|
|
177
|
+
/**
|
|
178
|
+
* Reads current form store, maps it through `bind.get`, and validates it against the endpoint schema.
|
|
179
|
+
* Returns the parsed (schema-valid) value.
|
|
180
|
+
*/
|
|
181
|
+
const bindGet = async () => {
|
|
182
|
+
const formData = get(form.form);
|
|
183
|
+
return (await safeParseAsync(schema, bind.get(formData))).output as ValidInput<API, typeof path, typeof method>;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
form.form.subscribe((v) => {
|
|
187
|
+
bindGet().then((bindValue) => {
|
|
188
|
+
// console.log('Updating binded value', bindValue, 'to', v);
|
|
189
|
+
|
|
190
|
+
if (!dequal(bindValue, v)) {
|
|
191
|
+
bind.set(v);
|
|
192
|
+
|
|
193
|
+
// bindGet().then((v) => {
|
|
194
|
+
// console.log('done update', bindGet());
|
|
195
|
+
// });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
watch(
|
|
201
|
+
() => bindGet(),
|
|
202
|
+
(newPromise) => {
|
|
203
|
+
newPromise.then((newValue) => {
|
|
204
|
+
// console.log('The state changed, updating the form from', get(form.form), 'to', newValue);
|
|
205
|
+
|
|
206
|
+
if (!dequal(get(form.form), newValue)) {
|
|
207
|
+
form.form.set(newValue);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return form;
|
|
81
215
|
};
|
|
82
216
|
}
|
|
@@ -7,21 +7,33 @@ Icons:
|
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
<script module lang="ts">
|
|
10
|
-
import
|
|
11
|
-
|
|
10
|
+
import type { FormFieldProps } from './form-field.svelte';
|
|
11
|
+
import type { FormPath } from 'sveltekit-superforms';
|
|
12
|
+
import type { Props as ButtonProps } from '$shadcn/button/index.js';
|
|
13
|
+
|
|
14
|
+
export type FormButtonProps<T extends Record<string, unknown>, U extends FormPath<T>> = Omit<ButtonProps, 'form'> & {
|
|
15
|
+
form?: FormFieldProps<T, U>['form'];
|
|
16
|
+
name?: U;
|
|
17
|
+
};
|
|
12
18
|
</script>
|
|
13
19
|
|
|
14
|
-
<script lang="ts">
|
|
20
|
+
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
|
21
|
+
import { Button } from '$shadcn/button/index.js';
|
|
15
22
|
import { cn, flyAndScale } from '$utils/utils.js';
|
|
16
23
|
import { getFormContext } from './form.svelte';
|
|
17
24
|
|
|
18
|
-
let {
|
|
25
|
+
let {
|
|
26
|
+
ref = $bindable(null),
|
|
27
|
+
form = getFormContext<T, U>(),
|
|
28
|
+
children,
|
|
29
|
+
class: className,
|
|
30
|
+
...restProps
|
|
31
|
+
}: FormButtonProps<T, U> = $props();
|
|
19
32
|
|
|
20
|
-
const
|
|
21
|
-
const { submitting, delayed } = form;
|
|
33
|
+
const { submitting, delayed } = $derived(form);
|
|
22
34
|
</script>
|
|
23
35
|
|
|
24
|
-
<Button
|
|
36
|
+
<Button bind:ref type="submit" class={cn(className)} {...restProps}>
|
|
25
37
|
<span class={cn('flex transition-opacity', $submitting && !$delayed && 'opacity-0')}>
|
|
26
38
|
{#if $submitting && $delayed}
|
|
27
39
|
<span in:flyAndScale|global class="icon-loading"></span>
|
|
@@ -29,4 +41,4 @@ Icons:
|
|
|
29
41
|
{@render children?.()}
|
|
30
42
|
{/if}
|
|
31
43
|
</span>
|
|
32
|
-
</Button
|
|
44
|
+
</Button>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export type FormFullFieldProps<T extends Record<string, unknown>, U extends FormPath<T>> = FormFieldProps<T, U> & {
|
|
3
3
|
label: string;
|
|
4
4
|
description?: string;
|
|
5
|
-
inputProps
|
|
5
|
+
inputProps?: HTMLInputAttributes;
|
|
6
6
|
} & WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>>;
|
|
7
7
|
</script>
|
|
8
8
|
|
|
@@ -24,18 +24,33 @@ interface Options {
|
|
|
24
24
|
const componentFiles = new Set<string>();
|
|
25
25
|
|
|
26
26
|
export default function componentSourceCollector(opts: Options = { safePackages: [] }): Plugin {
|
|
27
|
-
|
|
27
|
+
// constants
|
|
28
|
+
const outFileName = opts.outputFile ?? 'component-sources.css';
|
|
29
|
+
const classRegex = /class(?:=|:)/;
|
|
30
|
+
const importRegex = /@import\s+['"]([^'"]+)['"]/g;
|
|
28
31
|
|
|
32
|
+
// state
|
|
29
33
|
let config: ResolvedConfig;
|
|
30
34
|
let firstRound = true;
|
|
31
|
-
|
|
32
|
-
// ---- helpers ---- //
|
|
33
35
|
let initialTransformDone = false;
|
|
34
36
|
let initialTransformTimer: NodeJS.Timeout | null = null;
|
|
35
37
|
|
|
38
|
+
function shouldAdd(code: string) {
|
|
39
|
+
return classRegex.test(code);
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
function addPath(file: string) {
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
if (
|
|
44
|
+
file !== '' && // No nothing
|
|
45
|
+
!/\.svelte-kit/.test(file) && // No svelte-kit files
|
|
46
|
+
// No dep files unless marked as safe
|
|
47
|
+
(!/\.pnpm|.vite/.test(file) || opts.safePackages.some((p) => file.includes(`node_modules/${p}`)))
|
|
48
|
+
) {
|
|
49
|
+
const outPath = resolve(config.root, outFileName);
|
|
50
|
+
const cleanedFileName = file.replace(/\?v=.*$/, '');
|
|
51
|
+
|
|
52
|
+
componentFiles.add(relative(dirname(outPath), cleanedFileName));
|
|
53
|
+
}
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
function scheduleInitialWrite() {
|
|
@@ -47,9 +62,10 @@ export default function componentSourceCollector(opts: Options = { safePackages:
|
|
|
47
62
|
}
|
|
48
63
|
}, 1000); // adjust delay as needed
|
|
49
64
|
}
|
|
65
|
+
|
|
50
66
|
const writeOutFile = async () => {
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
const outPath = resolve(config.root, outFileName);
|
|
68
|
+
|
|
53
69
|
const lines = Array.from(componentFiles)
|
|
54
70
|
.map((d) => `@source '${d}';`)
|
|
55
71
|
.sort();
|
|
@@ -58,14 +74,6 @@ export default function componentSourceCollector(opts: Options = { safePackages:
|
|
|
58
74
|
if (didWrite) console.log('Wrote', lines.length);
|
|
59
75
|
};
|
|
60
76
|
|
|
61
|
-
const classRegex = /class(?:=|:)/;
|
|
62
|
-
|
|
63
|
-
const importRegex = /@import\s+['"]([^'"]+)['"]/g;
|
|
64
|
-
|
|
65
|
-
function shouldAdd(fileName: string) {
|
|
66
|
-
return !/\.pnpm|.vite/.test(fileName) || opts.safePackages.some((p) => fileName.includes(`node_modules/${p}`));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
77
|
// ---- plugin ---- //
|
|
70
78
|
|
|
71
79
|
return {
|
|
@@ -74,7 +82,7 @@ export default function componentSourceCollector(opts: Options = { safePackages:
|
|
|
74
82
|
|
|
75
83
|
async configResolved(resolved) {
|
|
76
84
|
config = resolved;
|
|
77
|
-
const outPath = resolve(config.root,
|
|
85
|
+
const outPath = resolve(config.root, outFileName);
|
|
78
86
|
|
|
79
87
|
if (config.command === 'build' && firstRound) {
|
|
80
88
|
componentFiles.clear();
|
|
@@ -103,34 +111,23 @@ export default function componentSourceCollector(opts: Options = { safePackages:
|
|
|
103
111
|
for (const match of matches) {
|
|
104
112
|
// console.log('MATching', match);
|
|
105
113
|
const resolved = await this.resolve(match[1], id);
|
|
106
|
-
if (resolved
|
|
114
|
+
if (resolved) {
|
|
107
115
|
addPath(resolved.id);
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
|
-
// TODO ignore .vite files
|
|
112
119
|
|
|
113
120
|
// Adds all other files with the classRegex
|
|
114
|
-
if (
|
|
121
|
+
if (shouldAdd(code)) {
|
|
115
122
|
addPath(id);
|
|
116
|
-
// if (config.command === 'serve') await writeOutFile();
|
|
117
123
|
}
|
|
124
|
+
|
|
118
125
|
if (!initialTransformDone) {
|
|
119
126
|
scheduleInitialWrite();
|
|
120
127
|
}
|
|
121
128
|
},
|
|
122
129
|
|
|
123
130
|
async handleHotUpdate(_ctx) {
|
|
124
|
-
// const output = await ctx.read();
|
|
125
|
-
// const id = ctx.file;
|
|
126
|
-
|
|
127
|
-
// console.log('Hot update sources', id, output, classRegex.test(output));
|
|
128
|
-
|
|
129
|
-
// if (classRegex.test(output)) {
|
|
130
|
-
// componentFiles.add(id);
|
|
131
|
-
// } else {
|
|
132
|
-
// componentFiles.delete(id);
|
|
133
|
-
// }
|
|
134
131
|
await writeOutFile();
|
|
135
132
|
},
|
|
136
133
|
|