@formisch/preact 0.1.0 → 0.2.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
@@ -13,10 +13,10 @@ Formisch is a schema-based, headless form library for Preact. It manages form st
13
13
 
14
14
  ## Example
15
15
 
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.
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.
17
17
 
18
18
  ```tsx
19
- import { Field, Form, useForm$ } from '@formisch/preact';
19
+ import { Field, Form, useForm } from '@formisch/preact';
20
20
  import * as v from 'valibot';
21
21
 
22
22
  const LoginSchema = v.object({
@@ -31,26 +31,22 @@ export default function LoginPage() {
31
31
 
32
32
  return (
33
33
  <Form of={loginForm} onSubmit={(output) => console.log(output)}>
34
- <Field
35
- of={loginForm}
36
- path={['email']}
37
- render={(field) => (
34
+ <Field of={loginForm} path={['email']}>
35
+ {(field) => (
38
36
  <div>
39
37
  <input {...field.props} value={field.input} type="email" />
40
38
  {field.errors.value && <div>{field.errors.value[0]}</div>}
41
39
  </div>
42
40
  )}
43
- />
44
- <Field
45
- of={loginForm}
46
- path={['password']}
47
- render={(field) => (
41
+ </Field>
42
+ <Field of={loginForm} path={['password']}>
43
+ {(field) => (
48
44
  <div>
49
45
  <input {...field.props} value={field.input} type="password" />
50
46
  {field.errors.value && <div>{field.errors.value[0]}</div>}
51
47
  </div>
52
48
  )}
53
- />
49
+ </Field>
54
50
  <button type="submit">Login</button>
55
51
  </Form>
56
52
  );
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>;
@@ -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;
@@ -321,12 +322,12 @@ function walkFieldStore(internalFieldStore, callback) {
321
322
  if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) walkFieldStore(internalFieldStore.children[index], callback);
322
323
  else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) walkFieldStore(internalFieldStore.children[key], callback);
323
324
  }
324
- function createFormStore(config, validate$1) {
325
+ function createFormStore(config, parse) {
325
326
  const store = {};
326
327
  initializeFieldStore(store, config.schema, config.initialInput, []);
327
- store.validateOn = config.validateOn ?? "submit";
328
- store.revalidateOn = config.revalidateOn ?? "input";
329
- store.validate = validate$1;
328
+ store.validate = config.validate ?? "submit";
329
+ store.revalidate = config.revalidate ?? "input";
330
+ store.parse = parse;
330
331
  store.isSubmitting = createSignal(false);
331
332
  store.isSubmitted = createSignal(false);
332
333
  store.isValidating = createSignal(false);
@@ -335,7 +336,7 @@ function createFormStore(config, validate$1) {
335
336
  async function validateFormInput(internalFormStore, config) {
336
337
  internalFormStore.validators++;
337
338
  internalFormStore.isValidating.value = true;
338
- const result = await internalFormStore.validate(untrack(() => getFieldInput(internalFormStore)));
339
+ const result = await internalFormStore.parse(untrack(() => getFieldInput(internalFormStore)));
339
340
  let rootErrors;
340
341
  let nestedErrors;
341
342
  if (result.issues) {
@@ -375,7 +376,7 @@ async function validateFormInput(internalFormStore, config) {
375
376
  return result;
376
377
  }
377
378
  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);
379
+ if (validationModes === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
379
380
  }
380
381
  const INTERNAL = "~internal";
381
382
 
@@ -399,6 +400,22 @@ function getErrors(form, config) {
399
400
  function getInput(form, config) {
400
401
  return getFieldInput(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]);
401
402
  }
403
+ function handleSubmit(form, handler) {
404
+ return async (event) => {
405
+ event.preventDefault();
406
+ const internalFormStore = form[INTERNAL];
407
+ internalFormStore.isSubmitted.value = true;
408
+ internalFormStore.isSubmitting.value = true;
409
+ try {
410
+ const result = await validateFormInput(internalFormStore, { shouldFocus: true });
411
+ if (result.success) await handler(result.output, event);
412
+ } catch (error) {
413
+ internalFormStore.errors.value = [error instanceof Error ? error.message : "An unknown error has occurred."];
414
+ } finally {
415
+ internalFormStore.isSubmitting.value = false;
416
+ }
417
+ };
418
+ }
402
419
  function insert(form, config) {
403
420
  const internalFormStore = form[INTERNAL];
404
421
  const internalArrayStore = getFieldStore(internalFormStore, config.path);
@@ -490,7 +507,7 @@ function reset(form, config) {
490
507
  });
491
508
  if (!config?.path) {
492
509
  if (!config?.keepSubmitted) internalFormStore.isSubmitted.value = false;
493
- if (internalFormStore.validateOn === "initial") validateFormInput(internalFormStore);
510
+ if (internalFormStore.validate === "initial") validateFormInput(internalFormStore);
494
511
  }
495
512
  });
496
513
  });
@@ -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.2.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",