@formisch/preact 0.1.0 → 0.3.0

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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Formisch is a schema-based, headless form library for Preact. It manages form state and validation. It is type-safe, fast by default and its bundle size is small due to its modular design. Try it out in our [playground](https://stackblitz.com/edit/formisch-playground-preact)!
4
4
 
5
+ Formisch is also available for [Qwik][formisch-qwik], [SolidJS][formisch-solid], and [Vue][formisch-vue]. Svelte will follow soon.
6
+
5
7
  ## Highlights
6
8
 
7
9
  - Small bundle size starting at 2.5 kB
@@ -13,10 +15,10 @@ Formisch is a schema-based, headless form library for Preact. It manages form st
13
15
 
14
16
  ## Example
15
17
 
16
- Every form starts with the `useForm$` hook. It initializes your form's store based on the provided Valibot schema and infers its types. Next, wrap your form in the `<Form />` component. It's a thin layer around the native `<form />` element that handles form validation and submission. Then, you can access the state of a field with the `useField` hook or the `<Field />` component to connect your inputs.
18
+ Every form starts with the `useForm` hook. It initializes your form's store based on the provided Valibot schema and infers its types. Next, wrap your form in the `<Form />` component. It's a thin layer around the native `<form />` element that handles form validation and submission. Then, you can access the state of a field with the `useField` hook or the `<Field />` component to connect your inputs.
17
19
 
18
20
  ```tsx
19
- import { Field, Form, useForm$ } from '@formisch/preact';
21
+ import { Field, Form, useForm } from '@formisch/preact';
20
22
  import * as v from 'valibot';
21
23
 
22
24
  const LoginSchema = v.object({
@@ -31,26 +33,22 @@ export default function LoginPage() {
31
33
 
32
34
  return (
33
35
  <Form of={loginForm} onSubmit={(output) => console.log(output)}>
34
- <Field
35
- of={loginForm}
36
- path={['email']}
37
- render={(field) => (
36
+ <Field of={loginForm} path={['email']}>
37
+ {(field) => (
38
38
  <div>
39
39
  <input {...field.props} value={field.input} type="email" />
40
40
  {field.errors.value && <div>{field.errors.value[0]}</div>}
41
41
  </div>
42
42
  )}
43
- />
44
- <Field
45
- of={loginForm}
46
- path={['password']}
47
- render={(field) => (
43
+ </Field>
44
+ <Field of={loginForm} path={['password']}>
45
+ {(field) => (
48
46
  <div>
49
47
  <input {...field.props} value={field.input} type="password" />
50
48
  {field.errors.value && <div>{field.errors.value[0]}</div>}
51
49
  </div>
52
50
  )}
53
- />
51
+ </Field>
54
52
  <button type="submit">Login</button>
55
53
  </Form>
56
54
  );
@@ -59,6 +57,16 @@ export default function LoginPage() {
59
57
 
60
58
  In addition, Formisch offers several functions (we call them "methods") that can be used to read and manipulate the form state. These include `focus`, `getErrors`, `getAllErrors`, `getInput`, `insert`, `move`, `remove`, `replace`, `reset`, `setErrors`, `setInput`, `submit`, `swap` and `validate`. These methods allow you to control the form programmatically.
61
59
 
60
+ ## Comparison
61
+
62
+ What makes Formisch unique is its framework-agnostic core, which is fully native to the framework you are using. It works by inserting framework-specific reactivity blocks when the core package is built. The result is a small bundle size and native performance for any UI update. This feature, along with a few others, distinguishes Formisch from other form libraries. My vision for Formisch is to create a framework-agnostic platform similar to [Vite](https://vite.dev/), but for forms.
63
+
64
+ ## Partners
65
+
66
+ Thanks to our partners who support the development! [Join them](https://github.com/sponsors/fabian-hiller) and contribute to the sustainability of open source software!
67
+
68
+ ![Partners of Formisch](https://github.com/fabian-hiller/formisch/blob/main/partners.webp?raw=true)
69
+
62
70
  ## Feedback
63
71
 
64
72
  Find a bug or have an idea how to improve the library? Please fill out an [issue](https://github.com/fabian-hiller/formisch/issues/new). Together we can make forms even better!
@@ -66,3 +74,7 @@ Find a bug or have an idea how to improve the library? Please fill out an [issue
66
74
  ## License
67
75
 
68
76
  This project is available free of charge and licensed under the [MIT license](https://github.com/fabian-hiller/formisch/blob/main/LICENSE.md).
77
+
78
+ [formisch-qwik]: https://github.com/fabian-hiller/formisch/tree/main/frameworks/qwik
79
+ [formisch-solid]: https://github.com/fabian-hiller/formisch/tree/main/frameworks/solid
80
+ [formisch-vue]: https://github.com/fabian-hiller/formisch/tree/main/frameworks/vue
package/dist/index.d.ts CHANGED
@@ -77,15 +77,15 @@ type ValidationMode = "initial" | "touch" | "input" | "change" | "blur" | "submi
77
77
  interface FormConfig<TSchema extends Schema = Schema> {
78
78
  readonly schema: TSchema;
79
79
  readonly initialInput?: DeepPartial<v.InferInput<TSchema>> | undefined;
80
- readonly validateOn?: ValidationMode | undefined;
81
- readonly revalidateOn?: Exclude<ValidationMode, "initial"> | undefined;
80
+ readonly validate?: ValidationMode | undefined;
81
+ readonly revalidate?: Exclude<ValidationMode, "initial"> | undefined;
82
82
  }
83
83
  interface InternalFormStore<TSchema extends Schema = Schema> extends InternalObjectStore {
84
84
  element?: HTMLFormElement;
85
85
  validators: number;
86
- validateOn: ValidationMode;
87
- revalidateOn: Exclude<ValidationMode, "initial">;
88
- validate: (input: unknown) => Promise<v.SafeParseResult<TSchema>>;
86
+ validate: ValidationMode;
87
+ revalidate: Exclude<ValidationMode, "initial">;
88
+ parse: (input: unknown) => Promise<v.SafeParseResult<TSchema>>;
89
89
  isSubmitting: Signal<boolean>;
90
90
  isSubmitted: Signal<boolean>;
91
91
  isValidating: Signal<boolean>;
@@ -93,7 +93,7 @@ interface InternalFormStore<TSchema extends Schema = Schema> extends InternalObj
93
93
  interface BaseFormStore<TSchema extends Schema = Schema> {
94
94
  [INTERNAL]: InternalFormStore<TSchema>;
95
95
  }
96
- type SubmitHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>, event: SubmitEvent) => MaybePromise<void>;
96
+ type SubmitHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>, event: SubmitEvent) => MaybePromise<unknown>;
97
97
  //#endregion
98
98
  //#region src/types/path.d.ts
99
99
  /**
@@ -123,16 +123,16 @@ type MergeUnion<T> = { [K in KeyOf<T>]: T extends Record<K, infer V> ? V : never
123
123
  /**
124
124
  * Lazily evaluate only the first valid path segment based on the given value.
125
125
  */
126
- type LazyPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValidPath : TPathToCheck extends readonly [infer TFirstKey extends KeyOf<TValue>, ...infer TPathRest extends Path] ? LazyPath<MergeUnion<TValue>[TFirstKey], TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<KeyOf<TValue>> extends false ? readonly [...TValidPath, KeyOf<TValue>] : TValidPath;
126
+ type LazyPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValidPath : TPathToCheck extends readonly [infer TFirstKey extends KeyOf<TValue>, ...infer TPathRest extends Path] ? LazyPath<Required<MergeUnion<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<KeyOf<TValue>> extends false ? readonly [...TValidPath, KeyOf<TValue>] : TValidPath;
127
127
  /**
128
128
  * Returns the path if valid, otherwise the first possible valid path based on
129
129
  * the given value.
130
130
  */
131
- type ValidPath<TValue, TPath extends RequiredPath> = TPath extends LazyPath<TValue, TPath> ? TPath : LazyPath<TValue, TPath>;
131
+ type ValidPath<TValue, TPath extends RequiredPath> = TPath extends LazyPath<Required<TValue>, TPath> ? TPath : LazyPath<Required<TValue>, TPath>;
132
132
  /**
133
133
  * Extracts the value type at the given path.
134
134
  */
135
- type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey, ...infer TRest extends Path] ? TKey extends KeyOf<TValue> ? PathValue<MergeUnion<TValue>[TKey], TRest> : unknown : TValue;
135
+ type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey, ...infer TRest extends Path] ? TKey extends KeyOf<Required<TValue>> ? PathValue<MergeUnion<Required<TValue>>[TKey], TRest> : unknown : TValue;
136
136
  /**
137
137
  * Checks if a value is an array or contains one.
138
138
  */
@@ -140,16 +140,16 @@ type IsOrHasArray<TValue> = IsAny<TValue> extends true ? false : TValue extends
140
140
  /**
141
141
  * Extracts the exact keys of a tuple, array or object that contain arrays.
142
142
  */
143
- type KeyOfArrayPath<TValue> = IsAny<TValue> extends true ? never : TValue extends readonly (infer TItem)[] ? number extends TValue["length"] ? IsOrHasArray<TItem> extends true ? number : never : { [TKey in keyof TValue]: TKey extends `${infer TIndex extends number}` ? IsOrHasArray<TValue[TKey]> extends true ? TIndex : never : never }[number] : TValue extends Record<string, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> extends true ? TKey : never }[keyof TValue] & PathKey : never;
143
+ type KeyOfArrayPath<TValue> = IsAny<TValue> extends true ? never : TValue extends readonly (infer TItem)[] ? number extends TValue["length"] ? IsOrHasArray<TItem> extends true ? number : never : { [TKey in keyof TValue]: TKey extends `${infer TIndex extends number}` ? IsOrHasArray<NonNullable<TValue[TKey]>> extends true ? TIndex : never : never }[number] : TValue extends Record<string, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<NonNullable<TValue[TKey]>> extends true ? TKey : never }[keyof TValue] & PathKey : never;
144
144
  /**
145
145
  * Lazily evaluate only the first valid array path segment based on the given value.
146
146
  */
147
- type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? TValidPath : readonly [...TValidPath, KeyOfArrayPath<TValue>] : TPathToCheck extends readonly [infer TFirstKey extends KeyOfArrayPath<TValue>, ...infer TPathRest extends Path] ? LazyArrayPath<MergeUnion<TValue>[TFirstKey], TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<KeyOfArrayPath<TValue>> extends false ? readonly [...TValidPath, KeyOfArrayPath<TValue>] : never;
147
+ type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? TValidPath : readonly [...TValidPath, KeyOfArrayPath<TValue>] : TPathToCheck extends readonly [infer TFirstKey extends KeyOfArrayPath<TValue>, ...infer TPathRest extends Path] ? LazyArrayPath<Required<MergeUnion<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<KeyOfArrayPath<TValue>> extends false ? readonly [...TValidPath, KeyOfArrayPath<TValue>] : never;
148
148
  /**
149
149
  * Returns the path if valid, otherwise the first possible valid array path based on
150
150
  * the given value.
151
151
  */
152
- type ValidArrayPath<TValue, TPath extends RequiredPath> = TPath extends LazyArrayPath<TValue, TPath> ? TPath : LazyArrayPath<TValue, TPath>;
152
+ type ValidArrayPath<TValue, TPath extends RequiredPath> = TPath extends LazyArrayPath<Required<TValue>, TPath> ? TPath : LazyArrayPath<Required<TValue>, TPath>;
153
153
  //#endregion
154
154
  //#region src/array/copyItemState/copyItemState.d.ts
155
155
  /**
@@ -193,6 +193,9 @@ interface GetFieldInputConfig<TSchema extends Schema, TFieldPath extends Require
193
193
  declare function getInput<TSchema extends Schema>(form: BaseFormStore<TSchema>): PartialValues<v.InferInput<TSchema>>;
194
194
  declare function getInput<TSchema extends Schema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? GetFieldInputConfig<TSchema, TFieldPath> : GetFormInputConfig): PartialValues<TFieldPath extends RequiredPath ? PathValue<v.InferInput<TSchema>, TFieldPath> : v.InferInput<TSchema>>;
195
195
  //#endregion
196
+ //#region src/handleSubmit/handleSubmit.d.ts
197
+ declare function handleSubmit<TSchema extends Schema>(form: BaseFormStore<TSchema>, handler: SubmitHandler<TSchema>): (event: SubmitEvent) => void;
198
+ //#endregion
196
199
  //#region src/insert/insert.d.ts
197
200
  interface InsertConfig<TSchema extends Schema, TFieldArrayPath extends RequiredPath> {
198
201
  readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
@@ -339,7 +342,7 @@ interface FormStore<TSchema extends Schema = Schema> extends BaseFormStore<TSche
339
342
  interface FieldProps<TSchema extends Schema = Schema, TFieldPath extends RequiredPath = RequiredPath> {
340
343
  readonly of: FormStore<TSchema>;
341
344
  readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
342
- readonly render: (store: FieldStore<TSchema, TFieldPath>) => JSX.Element;
345
+ readonly children: (store: FieldStore<TSchema, TFieldPath>) => JSX.Element;
343
346
  }
344
347
  /**
345
348
  * Headless form field that provides reactive properties and state.
@@ -347,7 +350,7 @@ interface FieldProps<TSchema extends Schema = Schema, TFieldPath extends Require
347
350
  declare function Field<TSchema extends Schema, TFieldPath extends RequiredPath>({
348
351
  of,
349
352
  path,
350
- render
353
+ children
351
354
  }: FieldProps<TSchema, TFieldPath>): JSX.Element;
352
355
  //#endregion
353
356
  //#region src/components/FieldArray/FieldArray.d.ts
@@ -357,7 +360,7 @@ declare function Field<TSchema extends Schema, TFieldPath extends RequiredPath>(
357
360
  interface FieldArrayProps<TSchema extends Schema = Schema, TFieldArrayPath extends RequiredPath = RequiredPath> {
358
361
  readonly of: FormStore<TSchema>;
359
362
  readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
360
- readonly render: (store: FieldArrayStore<TSchema, TFieldArrayPath>) => JSX.Element;
363
+ readonly children: (store: FieldArrayStore<TSchema, TFieldArrayPath>) => JSX.Element;
361
364
  }
362
365
  /**
363
366
  * Headless field array that provides reactive properties and state.
@@ -365,7 +368,7 @@ interface FieldArrayProps<TSchema extends Schema = Schema, TFieldArrayPath exten
365
368
  declare function FieldArray<TSchema extends Schema, TFieldArrayPath extends RequiredPath>({
366
369
  of,
367
370
  path,
368
- render
371
+ children
369
372
  }: FieldArrayProps<TSchema, TFieldArrayPath>): JSX.Element;
370
373
  //#endregion
371
374
  //#region src/components/Form/Form.d.ts
@@ -390,4 +393,4 @@ declare function useFieldArray<TSchema extends Schema, TFieldArrayPath extends R
390
393
  //#region src/hooks/useForm/useForm.d.ts
391
394
  declare function useForm<TSchema extends Schema>(config: FormConfig<TSchema>): FormStore<TSchema>;
392
395
  //#endregion
393
- export { Field, FieldArray, FieldArrayProps, FieldArrayStore, FieldElementProps, FieldProps, FieldStore, FocusFieldConfig, Form, FormProps, FormStore, GetFieldErrorsConfig, GetFieldInputConfig, GetFormErrorsConfig, GetFormInputConfig, InsertConfig, MoveConfig, RemoveConfig, ReplaceConfig, ResetFieldConfig, ResetFormConfig, SetFieldErrorsConfig, SetFieldInputConfig, SetFormErrorsConfig, SetFormInputConfig, SwapConfig, UseFieldArrayConfig, UseFieldConfig, ValidateFormConfig, focus, getAllErrors, getErrors, getInput, insert, move, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };
396
+ export { Field, FieldArray, FieldArrayProps, FieldArrayStore, FieldElementProps, FieldProps, FieldStore, FocusFieldConfig, Form, FormProps, FormStore, GetFieldErrorsConfig, GetFieldInputConfig, GetFormErrorsConfig, GetFormInputConfig, InsertConfig, MoveConfig, RemoveConfig, ReplaceConfig, ResetFieldConfig, ResetFormConfig, SetFieldErrorsConfig, SetFieldInputConfig, SetFormErrorsConfig, SetFormInputConfig, type SubmitHandler, SwapConfig, UseFieldArrayConfig, UseFieldConfig, ValidateFormConfig, focus, getAllErrors, getErrors, getInput, handleSubmit, insert, move, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };
package/dist/index.js CHANGED
@@ -121,6 +121,7 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
121
121
  */
122
122
  function resetItemState(internalFieldStore, initialInput) {
123
123
  batch(() => {
124
+ internalFieldStore.errors.value = null;
124
125
  if (internalFieldStore.kind === "array") {
125
126
  internalFieldStore.isTouched.value = false;
126
127
  internalFieldStore.isDirty.value = false;
@@ -293,6 +294,7 @@ function setFieldInput(internalFieldStore, input) {
293
294
  } else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldInput(internalFieldStore.children[key], input[key]);
294
295
  else {
295
296
  internalFieldStore.input.value = input;
297
+ internalFieldStore.isTouched.value = true;
296
298
  const startInput = untrack(() => internalFieldStore.startInput.value);
297
299
  internalFieldStore.isDirty.value = startInput !== input && (startInput !== void 0 || input !== "" && !Number.isNaN(input));
298
300
  }
@@ -321,12 +323,12 @@ function walkFieldStore(internalFieldStore, callback) {
321
323
  if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) walkFieldStore(internalFieldStore.children[index], callback);
322
324
  else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) walkFieldStore(internalFieldStore.children[key], callback);
323
325
  }
324
- function createFormStore(config, validate$1) {
326
+ function createFormStore(config, parse) {
325
327
  const store = {};
326
328
  initializeFieldStore(store, config.schema, config.initialInput, []);
327
- store.validateOn = config.validateOn ?? "submit";
328
- store.revalidateOn = config.revalidateOn ?? "input";
329
- store.validate = validate$1;
329
+ store.validate = config.validate ?? "submit";
330
+ store.revalidate = config.revalidate ?? "input";
331
+ store.parse = parse;
330
332
  store.isSubmitting = createSignal(false);
331
333
  store.isSubmitted = createSignal(false);
332
334
  store.isValidating = createSignal(false);
@@ -335,7 +337,7 @@ function createFormStore(config, validate$1) {
335
337
  async function validateFormInput(internalFormStore, config) {
336
338
  internalFormStore.validators++;
337
339
  internalFormStore.isValidating.value = true;
338
- const result = await internalFormStore.validate(untrack(() => getFieldInput(internalFormStore)));
340
+ const result = await internalFormStore.parse(untrack(() => getFieldInput(internalFormStore)));
339
341
  let rootErrors;
340
342
  let nestedErrors;
341
343
  if (result.issues) {
@@ -375,7 +377,7 @@ async function validateFormInput(internalFormStore, config) {
375
377
  return result;
376
378
  }
377
379
  function validateIfRequired(internalFormStore, internalFieldStore, validationModes) {
378
- if (validationModes === (internalFormStore.validateOn === "initial" || (internalFormStore.validateOn === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidateOn : internalFormStore.validateOn)) validateFormInput(internalFormStore);
380
+ if (validationModes === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
379
381
  }
380
382
  const INTERNAL = "~internal";
381
383
 
@@ -399,6 +401,22 @@ function getErrors(form, config) {
399
401
  function getInput(form, config) {
400
402
  return getFieldInput(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]);
401
403
  }
404
+ function handleSubmit(form, handler) {
405
+ return async (event) => {
406
+ event.preventDefault();
407
+ const internalFormStore = form[INTERNAL];
408
+ internalFormStore.isSubmitted.value = true;
409
+ internalFormStore.isSubmitting.value = true;
410
+ try {
411
+ const result = await validateFormInput(internalFormStore, { shouldFocus: true });
412
+ if (result.success) await handler(result.output, event);
413
+ } catch (error) {
414
+ internalFormStore.errors.value = [error instanceof Error ? error.message : "An unknown error has occurred."];
415
+ } finally {
416
+ internalFormStore.isSubmitting.value = false;
417
+ }
418
+ };
419
+ }
402
420
  function insert(form, config) {
403
421
  const internalFormStore = form[INTERNAL];
404
422
  const internalArrayStore = getFieldStore(internalFormStore, config.path);
@@ -490,7 +508,7 @@ function reset(form, config) {
490
508
  });
491
509
  if (!config?.path) {
492
510
  if (!config?.keepSubmitted) internalFormStore.isSubmitted.value = false;
493
- if (internalFormStore.validateOn === "initial") validateFormInput(internalFormStore);
511
+ if (internalFormStore.validate === "initial") validateFormInput(internalFormStore);
494
512
  }
495
513
  });
496
514
  });
@@ -502,7 +520,6 @@ function setInput(form, config) {
502
520
  batch(() => {
503
521
  const internalFormStore = form[INTERNAL];
504
522
  const internalFieldStore = config.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
505
- setFieldBool(internalFieldStore, "isTouched", true);
506
523
  setFieldInput(internalFieldStore, config.input);
507
524
  validateIfRequired(internalFormStore, internalFieldStore, "input");
508
525
  });
@@ -566,15 +583,14 @@ function useField(form, config) {
566
583
  },
567
584
  autofocus: !!internalFieldStore.value.errors.value,
568
585
  ref(element) {
569
- internalFieldStore.value.elements.push(element);
586
+ if (element) internalFieldStore.value.elements.push(element);
570
587
  },
571
588
  onFocus() {
572
589
  setFieldBool(internalFieldStore.value, "isTouched", true);
573
590
  validateIfRequired(form[INTERNAL], internalFieldStore.value, "touch");
574
591
  },
575
592
  onInput(event) {
576
- const nextValue = getElementInput(event.currentTarget, internalFieldStore.value);
577
- setFieldInput(internalFieldStore.value, nextValue);
593
+ setFieldInput(internalFieldStore.value, getElementInput(event.currentTarget, internalFieldStore.value));
578
594
  validateIfRequired(form[INTERNAL], internalFieldStore.value, "input");
579
595
  },
580
596
  onChange() {
@@ -619,7 +635,7 @@ function useForm(config) {
619
635
  };
620
636
  }, []);
621
637
  useLayoutEffect(() => {
622
- if (config.validateOn === "initial") validateFormInput(form[INTERNAL]);
638
+ if (config.validate === "initial") validateFormInput(form[INTERNAL]);
623
639
  }, []);
624
640
  return form;
625
641
  }
@@ -629,9 +645,9 @@ function useForm(config) {
629
645
  /**
630
646
  * Headless form field that provides reactive properties and state.
631
647
  */
632
- function Field({ of, path, render }) {
648
+ function Field({ of, path, children }) {
633
649
  const field = useField(of, { path });
634
- return render(field);
650
+ return children(field);
635
651
  }
636
652
 
637
653
  //#endregion
@@ -639,9 +655,9 @@ function Field({ of, path, render }) {
639
655
  /**
640
656
  * Headless field array that provides reactive properties and state.
641
657
  */
642
- function FieldArray({ of, path, render }) {
658
+ function FieldArray({ of, path, children }) {
643
659
  const field = useFieldArray(of, { path });
644
- return render(field);
660
+ return children(field);
645
661
  }
646
662
 
647
663
  //#endregion
@@ -653,22 +669,9 @@ function Form({ of, onSubmit,...other }) {
653
669
  ref: (element) => {
654
670
  of[INTERNAL].element = element;
655
671
  },
656
- onSubmit: async (event) => {
657
- event.preventDefault();
658
- const internalFormStore = of[INTERNAL];
659
- internalFormStore.isSubmitted.value = true;
660
- internalFormStore.isSubmitting.value = true;
661
- try {
662
- const result = await validateFormInput(internalFormStore, { shouldFocus: true });
663
- if (result.success) await onSubmit(result.output, event);
664
- } catch (error) {
665
- internalFormStore.errors.value = [error instanceof Error ? error.message : "An unknown error has occurred."];
666
- } finally {
667
- internalFormStore.isSubmitting.value = false;
668
- }
669
- }
672
+ onSubmit: handleSubmit(of, onSubmit)
670
673
  });
671
674
  }
672
675
 
673
676
  //#endregion
674
- export { Field, FieldArray, Form, focus, getAllErrors, getErrors, getInput, insert, move, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };
677
+ export { Field, FieldArray, Form, focus, getAllErrors, getErrors, getInput, handleSubmit, insert, move, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formisch/preact",
3
3
  "description": "The modular and type-safe form library for Preact",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "license": "MIT",
6
6
  "author": "Fabian Hiller",
7
7
  "homepage": "https://formisch.dev",
@@ -11,13 +11,10 @@
11
11
  },
12
12
  "keywords": [
13
13
  "preact",
14
- "modular",
14
+ "form",
15
15
  "typescript",
16
16
  "schema",
17
- "validation",
18
- "parsing",
19
- "bundle-size",
20
- "type-safe"
17
+ "validation"
21
18
  ],
22
19
  "type": "module",
23
20
  "main": "./dist/index.js",
@@ -37,7 +34,10 @@
37
34
  },
38
35
  "scripts": {
39
36
  "build": "tsdown",
40
- "lint": "eslint \"src/**/*.ts*\""
37
+ "lint": "eslint \"src/**/*.ts*\" && tsc --noEmit",
38
+ "lint.fix": "eslint \"src/**/*.ts*\" --fix",
39
+ "format": "prettier --write ./src",
40
+ "format.check": "prettier --check ./src"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@eslint/js": "^9.31.0",