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.
@@ -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;AAEf,KAAK,UAAU,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAErH,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,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,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;IACF,YAAY,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,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,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,CA4CrB"}
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
- return superForm(defaults(defaultValue, valibot(schema)), {
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 * as Button from '$shadcn/button/index.js';
11
- export type FormButtonProps = Button.Props;
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 { ref = $bindable(null), children, class: className, ...restProps }: FormButtonProps = $props();
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 form = getFormContext();
21
- const { submitting, delayed } = form;
33
+ const { submitting, delayed } = $derived(form);
22
34
  </script>
23
35
 
24
- <Button.Root bind:ref type="submit" class={cn(className)} {...restProps}>
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.Root>
44
+ </Button>
@@ -1,6 +1,32 @@
1
- import * as Button from '$shadcn/button/index.js';
2
- export type FormButtonProps = Button.Props;
3
- declare const FormButton: import("svelte").Component<Button.Props, {}, "ref">;
4
- type FormButton = ReturnType<typeof FormButton>;
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;AAClD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC;AAgC7C,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"form-button.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/form/form-button.svelte.ts"],"names":[],"mappings":"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"}
@@ -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: HTMLInputAttributes;
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: HTMLInputAttributes;
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":"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"}
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
- }, "ref" | "value" | "perPage" | "trigger">;
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, {}, "ref" | "value">;
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" | "ref" | "value" | "searchWith">;
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
- }, {}, "ref" | "value">;
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,CAyH7F"}
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
- const outFile = opts.outputFile ?? 'component-sources.css';
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
- const outPath = resolve(config.root, outFile);
15
- componentFiles.add(relative(dirname(outPath), file));
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
- // console.log('writing', componentFiles.size);
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, outFile);
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 && shouldAdd(resolved.id)) {
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 (classRegex.test(code) && shouldAdd(id)) {
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.17",
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
- "bits-ui": "^2.15.8",
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.574.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
- "svelte": "^5.53.0",
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<ApiInput<E, P, M>>;
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
- return superForm<ValidInput<API, typeof path, typeof method>>(defaults(defaultValue, valibot(schema)), {
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 * as Button from '$shadcn/button/index.js';
11
- export type FormButtonProps = Button.Props;
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 { ref = $bindable(null), children, class: className, ...restProps }: FormButtonProps = $props();
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 form = getFormContext();
21
- const { submitting, delayed } = form;
33
+ const { submitting, delayed } = $derived(form);
22
34
  </script>
23
35
 
24
- <Button.Root bind:ref type="submit" class={cn(className)} {...restProps}>
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.Root>
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: HTMLInputAttributes;
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
- const outFile = opts.outputFile ?? 'component-sources.css';
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
- const outPath = resolve(config.root, outFile);
38
- componentFiles.add(relative(dirname(outPath), file));
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
- // console.log('writing', componentFiles.size);
52
- const outPath = resolve(config.root, outFile);
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, outFile);
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 && shouldAdd(resolved.id)) {
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 (classRegex.test(code) && shouldAdd(id)) {
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