@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 +24 -12
- package/dist/index.d.ts +20 -17
- package/dist/index.js +34 -31
- package/package.json +7 -7
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+

|
|
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
|
|
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>;
|
|
@@ -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<
|
|
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]
|
|
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
|
|
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
|
|
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]
|
|
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]
|
|
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
|
|
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
|
|
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;
|
|
@@ -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,
|
|
326
|
+
function createFormStore(config, parse) {
|
|
325
327
|
const store = {};
|
|
326
328
|
initializeFieldStore(store, config.schema, config.initialInput, []);
|
|
327
|
-
store.
|
|
328
|
-
store.
|
|
329
|
-
store.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.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
|
-
"
|
|
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",
|