@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 +8 -12
- package/dist/index.d.ts +13 -10
- package/dist/index.js +33 -30
- package/package.json +7 -7
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
81
|
-
readonly
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
325
|
+
function createFormStore(config, parse) {
|
|
325
326
|
const store = {};
|
|
326
327
|
initializeFieldStore(store, config.schema, config.initialInput, []);
|
|
327
|
-
store.
|
|
328
|
-
store.
|
|
329
|
-
store.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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,
|
|
648
|
+
function Field({ of, path, children }) {
|
|
633
649
|
const field = useField(of, { path });
|
|
634
|
-
return
|
|
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,
|
|
658
|
+
function FieldArray({ of, path, children }) {
|
|
643
659
|
const field = useFieldArray(of, { path });
|
|
644
|
-
return
|
|
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:
|
|
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.
|
|
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
|
-
"
|
|
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",
|