@formisch/vue 0.1.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 +58 -0
- package/dist/index.d.ts +413 -0
- package/dist/index.js +718 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Formisch for Vue
|
|
2
|
+
|
|
3
|
+
Formisch is a schema-based, headless form library for Vue. 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-vue)!
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Small bundle size starting at 2.5 kB
|
|
8
|
+
- Schema-based validation with Valibot
|
|
9
|
+
- Type safety with autocompletion in editor
|
|
10
|
+
- It's fast – DOM updates are fine-grained
|
|
11
|
+
- Minimal, readable and well thought out API
|
|
12
|
+
- Supports all native HTML form fields
|
|
13
|
+
|
|
14
|
+
## Example
|
|
15
|
+
|
|
16
|
+
Every form starts with the `useForm` composable. 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` composable or the `<Field />` component to connect your inputs.
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { Field, Form, type SubmitHandler, useForm } from '@formisch/vue';
|
|
21
|
+
import * as v from 'valibot';
|
|
22
|
+
|
|
23
|
+
const LoginSchema = v.object({
|
|
24
|
+
email: v.pipe(v.string(), v.email()),
|
|
25
|
+
password: v.pipe(v.string(), v.minLength(8)),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const loginForm = useForm({ schema: LoginSchema });
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<Form :of="loginForm" @submit="(output) => console.log(output)">
|
|
33
|
+
<Field :of="loginForm" :path="['email']" v-slot="field">
|
|
34
|
+
<div>
|
|
35
|
+
<input v-model="field.input" v-bind="field.props" type="email" />
|
|
36
|
+
<div v-if="field.errors">{{ field.errors[0] }}</div>
|
|
37
|
+
</div>
|
|
38
|
+
</Field>
|
|
39
|
+
<Field :of="loginForm" :path="['password']" v-slot="field">
|
|
40
|
+
<div>
|
|
41
|
+
<input v-model="field.input" v-bind="field.props" type="password" />
|
|
42
|
+
<div v-if="field.errors">{{ field.errors[0] }}</div>
|
|
43
|
+
</div>
|
|
44
|
+
</Field>
|
|
45
|
+
<button type="submit">Login</button>
|
|
46
|
+
</Form>
|
|
47
|
+
</template>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
## Feedback
|
|
53
|
+
|
|
54
|
+
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!
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
This project is available free of charge and licensed under the [MIT license](https://github.com/fabian-hiller/formisch/blob/main/LICENSE.md).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
import * as vue0 from "vue";
|
|
3
|
+
import { ComponentPublicInstance, MaybeRefOrGetter, ShallowRef as Signal } from "vue";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/core/dist/index.vue.d.ts
|
|
6
|
+
//#region src/types/schema.d.ts
|
|
7
|
+
type Schema = v.GenericSchema | v.GenericSchemaAsync;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/types/signal.d.ts
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/types/field.d.ts
|
|
13
|
+
/**
|
|
14
|
+
* Value type of the field element.
|
|
15
|
+
*/
|
|
16
|
+
type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
17
|
+
interface InternalBaseStore {
|
|
18
|
+
kind: "array" | "object" | "value";
|
|
19
|
+
name: string;
|
|
20
|
+
schema: Schema;
|
|
21
|
+
elements: FieldElement[];
|
|
22
|
+
errors: Signal<[string, ...string[]] | null>;
|
|
23
|
+
}
|
|
24
|
+
interface InternalArrayStore extends InternalBaseStore {
|
|
25
|
+
kind: "array";
|
|
26
|
+
children: InternalFieldStore[];
|
|
27
|
+
initialItems: Signal<string[]>;
|
|
28
|
+
startItems: Signal<string[]>;
|
|
29
|
+
items: Signal<string[]>;
|
|
30
|
+
isTouched: Signal<boolean>;
|
|
31
|
+
isDirty: Signal<boolean>;
|
|
32
|
+
}
|
|
33
|
+
interface InternalObjectStore extends InternalBaseStore {
|
|
34
|
+
kind: "object";
|
|
35
|
+
children: Record<string, InternalFieldStore>;
|
|
36
|
+
}
|
|
37
|
+
interface InternalValueStore extends InternalBaseStore {
|
|
38
|
+
kind: "value";
|
|
39
|
+
initialInput: Signal<unknown>;
|
|
40
|
+
startInput: Signal<unknown>;
|
|
41
|
+
input: Signal<unknown>;
|
|
42
|
+
isTouched: Signal<boolean>;
|
|
43
|
+
isDirty: Signal<boolean>;
|
|
44
|
+
}
|
|
45
|
+
type InternalFieldStore = InternalArrayStore | InternalObjectStore | InternalValueStore;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/values.d.ts
|
|
48
|
+
declare const INTERNAL: "~internal";
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/types/utils.d.ts
|
|
51
|
+
/**
|
|
52
|
+
* Checks if a type is `any`.
|
|
53
|
+
*/
|
|
54
|
+
type IsAny<Type> = 0 extends 1 & Type ? true : false;
|
|
55
|
+
/**
|
|
56
|
+
* Checks if a type is `never`.
|
|
57
|
+
*/
|
|
58
|
+
type IsNever<Type> = [Type] extends [never] ? true : false;
|
|
59
|
+
/**
|
|
60
|
+
* Constructs a type that is maybe a promise.
|
|
61
|
+
*/
|
|
62
|
+
type MaybePromise<TValue> = TValue | Promise<TValue>;
|
|
63
|
+
/**
|
|
64
|
+
* Makes all properties deeply optional.
|
|
65
|
+
*/
|
|
66
|
+
type DeepPartial<TValue> = TValue extends readonly unknown[] ? number extends TValue["length"] ? TValue : { [Key in keyof TValue]?: DeepPartial<TValue[Key]> | undefined } : TValue extends Record<PropertyKey, unknown> ? { [Key in keyof TValue]?: DeepPartial<TValue[Key]> | undefined } : TValue | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Makes all value properties optional.
|
|
69
|
+
*/
|
|
70
|
+
type PartialValues<TValue> = TValue extends readonly unknown[] ? number extends TValue["length"] ? TValue : { [Key in keyof TValue]: PartialValues<TValue[Key]> } : TValue extends Record<PropertyKey, unknown> ? { [Key in keyof TValue]: PartialValues<TValue[Key]> } : TValue | undefined;
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/types/form.d.ts
|
|
73
|
+
/**
|
|
74
|
+
* Value type of the validation mode.
|
|
75
|
+
*/
|
|
76
|
+
type ValidationMode = "initial" | "touch" | "input" | "change" | "blur" | "submit";
|
|
77
|
+
interface FormConfig<TSchema extends Schema = Schema> {
|
|
78
|
+
readonly schema: TSchema;
|
|
79
|
+
readonly initialInput?: DeepPartial<v.InferInput<TSchema>> | undefined;
|
|
80
|
+
readonly validate?: ValidationMode | undefined;
|
|
81
|
+
readonly revalidate?: Exclude<ValidationMode, "initial"> | undefined;
|
|
82
|
+
}
|
|
83
|
+
interface InternalFormStore<TSchema extends Schema = Schema> extends InternalObjectStore {
|
|
84
|
+
element?: HTMLFormElement;
|
|
85
|
+
validators: number;
|
|
86
|
+
validate: ValidationMode;
|
|
87
|
+
revalidate: Exclude<ValidationMode, "initial">;
|
|
88
|
+
parse: (input: unknown) => Promise<v.SafeParseResult<TSchema>>;
|
|
89
|
+
isSubmitting: Signal<boolean>;
|
|
90
|
+
isSubmitted: Signal<boolean>;
|
|
91
|
+
isValidating: Signal<boolean>;
|
|
92
|
+
}
|
|
93
|
+
interface BaseFormStore<TSchema extends Schema = Schema> {
|
|
94
|
+
[INTERNAL]: InternalFormStore<TSchema>;
|
|
95
|
+
}
|
|
96
|
+
type SubmitHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>, event: SubmitEvent) => MaybePromise<void>;
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/types/path.d.ts
|
|
99
|
+
/**
|
|
100
|
+
* Path key type.
|
|
101
|
+
*/
|
|
102
|
+
type PathKey = string | number;
|
|
103
|
+
/**
|
|
104
|
+
* Path type.
|
|
105
|
+
*/
|
|
106
|
+
type Path = readonly PathKey[];
|
|
107
|
+
/**
|
|
108
|
+
* Required path type.
|
|
109
|
+
*/
|
|
110
|
+
type RequiredPath = readonly [PathKey, ...Path];
|
|
111
|
+
/**
|
|
112
|
+
* Extracts the exact keys of a tuple, array or object.
|
|
113
|
+
*/
|
|
114
|
+
type KeyOf<TValue> = IsAny<TValue> extends true ? never : TValue extends readonly unknown[] ? number extends TValue["length"] ? number : { [TKey in keyof TValue]: TKey extends `${infer TIndex extends number}` ? TIndex : never }[number] : TValue extends Record<string, unknown> ? keyof TValue & PathKey : never;
|
|
115
|
+
/**
|
|
116
|
+
* Merges array and object unions into a single object.
|
|
117
|
+
*
|
|
118
|
+
* Hint: This is necessary to make any property accessible. By default,
|
|
119
|
+
* properties that do not exist in all union options are not accessible
|
|
120
|
+
* and result in "any" when accessed.
|
|
121
|
+
*/
|
|
122
|
+
type MergeUnion<T> = { [K in KeyOf<T>]: T extends Record<K, infer V> ? V : never };
|
|
123
|
+
/**
|
|
124
|
+
* Lazily evaluate only the first valid path segment based on the given value.
|
|
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;
|
|
127
|
+
/**
|
|
128
|
+
* Returns the path if valid, otherwise the first possible valid path based on
|
|
129
|
+
* the given value.
|
|
130
|
+
*/
|
|
131
|
+
type ValidPath<TValue, TPath extends RequiredPath> = TPath extends LazyPath<TValue, TPath> ? TPath : LazyPath<TValue, TPath>;
|
|
132
|
+
/**
|
|
133
|
+
* Extracts the value type at the given path.
|
|
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;
|
|
136
|
+
/**
|
|
137
|
+
* Checks if a value is an array or contains one.
|
|
138
|
+
*/
|
|
139
|
+
type IsOrHasArray<TValue> = IsAny<TValue> extends true ? false : TValue extends readonly unknown[] ? true : TValue extends Record<string, unknown> ? true extends { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> }[keyof TValue] ? true : false : false;
|
|
140
|
+
/**
|
|
141
|
+
* Extracts the exact keys of a tuple, array or object that contain arrays.
|
|
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;
|
|
144
|
+
/**
|
|
145
|
+
* Lazily evaluate only the first valid array path segment based on the given value.
|
|
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;
|
|
148
|
+
/**
|
|
149
|
+
* Returns the path if valid, otherwise the first possible valid array path based on
|
|
150
|
+
* the given value.
|
|
151
|
+
*/
|
|
152
|
+
type ValidArrayPath<TValue, TPath extends RequiredPath> = TPath extends LazyArrayPath<TValue, TPath> ? TPath : LazyArrayPath<TValue, TPath>;
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/array/copyItemState/copyItemState.d.ts
|
|
155
|
+
/**
|
|
156
|
+
* Copies the deeply nested state (signal values) from one array item to another.
|
|
157
|
+
* This includes the `isTouched`, `isDirty`, `startInput`, `input`, `startItems`, and `items` properties.
|
|
158
|
+
* Recursively walks through the field stores and copies all signal values.
|
|
159
|
+
*
|
|
160
|
+
* @param internalArrayStore - The field store of the array (not the array item)
|
|
161
|
+
* @param fromIndex - The source index to copy from
|
|
162
|
+
* @param toIndex - The destination index to copy to
|
|
163
|
+
*/
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region ../../packages/methods/dist/index.vue.d.ts
|
|
166
|
+
//#region src/focus/focus.d.ts
|
|
167
|
+
interface FocusFieldConfig<TSchema extends Schema, TFieldPath extends RequiredPath> {
|
|
168
|
+
readonly form: BaseFormStore<TSchema>;
|
|
169
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
170
|
+
}
|
|
171
|
+
declare function focus<TSchema extends Schema, TFieldPath extends RequiredPath>(config: FocusFieldConfig<TSchema, TFieldPath>): void;
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/getAllErrors/getAllErrors.d.ts
|
|
174
|
+
declare function getAllErrors(form: BaseFormStore): [string, ...string[]] | null;
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/getErrors/getErrors.d.ts
|
|
177
|
+
interface GetFormErrorsConfig {
|
|
178
|
+
readonly path?: undefined;
|
|
179
|
+
}
|
|
180
|
+
interface GetFieldErrorsConfig<TSchema extends Schema, TFieldPath extends RequiredPath> {
|
|
181
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
182
|
+
}
|
|
183
|
+
declare function getErrors<TSchema extends Schema>(form: BaseFormStore<TSchema>): [string, ...string[]] | null;
|
|
184
|
+
declare function getErrors<TSchema extends Schema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? GetFieldErrorsConfig<TSchema, TFieldPath> : GetFormErrorsConfig): [string, ...string[]] | null;
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/getInput/getInput.d.ts
|
|
187
|
+
interface GetFormInputConfig {
|
|
188
|
+
readonly path?: undefined;
|
|
189
|
+
}
|
|
190
|
+
interface GetFieldInputConfig<TSchema extends Schema, TFieldPath extends RequiredPath> {
|
|
191
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
192
|
+
}
|
|
193
|
+
declare function getInput<TSchema extends Schema>(form: BaseFormStore<TSchema>): PartialValues<v.InferInput<TSchema>>;
|
|
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
|
+
//#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
|
|
199
|
+
//#region src/insert/insert.d.ts
|
|
200
|
+
interface InsertConfig<TSchema extends Schema, TFieldArrayPath extends RequiredPath> {
|
|
201
|
+
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
202
|
+
readonly at?: number | undefined;
|
|
203
|
+
readonly initialInput?: DeepPartial<PathValue<v.InferInput<TSchema>, [...TFieldArrayPath, number]>> | undefined;
|
|
204
|
+
}
|
|
205
|
+
declare function insert<TSchema extends Schema, TFieldArrayPath extends RequiredPath>(form: BaseFormStore<TSchema>, config: InsertConfig<TSchema, TFieldArrayPath>): void;
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/move/move.d.ts
|
|
208
|
+
interface MoveConfig<TSchema extends Schema, TFieldArrayPath extends RequiredPath> {
|
|
209
|
+
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
210
|
+
readonly from: number;
|
|
211
|
+
readonly to: number;
|
|
212
|
+
}
|
|
213
|
+
declare function move<TSchema extends Schema, TFieldArrayPath extends RequiredPath>(form: BaseFormStore<TSchema>, config: MoveConfig<TSchema, TFieldArrayPath>): void;
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/remove/remove.d.ts
|
|
216
|
+
interface RemoveConfig<TSchema extends Schema, TFieldArrayPath extends RequiredPath> {
|
|
217
|
+
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
218
|
+
readonly at: number;
|
|
219
|
+
}
|
|
220
|
+
declare function remove<TSchema extends Schema, TFieldArrayPath extends RequiredPath>(form: BaseFormStore<TSchema>, config: RemoveConfig<TSchema, TFieldArrayPath>): void;
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/replace/replace.d.ts
|
|
223
|
+
interface ReplaceConfig<TSchema extends Schema, TFieldArrayPath extends RequiredPath> {
|
|
224
|
+
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
225
|
+
readonly at: number;
|
|
226
|
+
readonly initialInput?: DeepPartial<PathValue<v.InferInput<TSchema>, [...TFieldArrayPath, number]>> | undefined;
|
|
227
|
+
}
|
|
228
|
+
declare function replace<TSchema extends Schema, TFieldArrayPath extends RequiredPath>(form: BaseFormStore<TSchema>, config: ReplaceConfig<TSchema, TFieldArrayPath>): void;
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/reset/reset.d.ts
|
|
231
|
+
interface ResetBaseConfig {
|
|
232
|
+
readonly keepInput?: boolean | undefined;
|
|
233
|
+
readonly keepTouched?: boolean | undefined;
|
|
234
|
+
readonly keepErrors?: boolean | undefined;
|
|
235
|
+
}
|
|
236
|
+
interface ResetFormConfig<TSchema extends Schema> extends ResetBaseConfig {
|
|
237
|
+
readonly path?: undefined;
|
|
238
|
+
readonly initialInput?: DeepPartial<v.InferInput<TSchema>> | undefined;
|
|
239
|
+
readonly keepSubmitCount?: boolean | undefined;
|
|
240
|
+
readonly keepSubmitted?: boolean | undefined;
|
|
241
|
+
}
|
|
242
|
+
interface ResetFieldConfig<TSchema extends Schema, TFieldPath extends RequiredPath> extends ResetBaseConfig {
|
|
243
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
244
|
+
readonly initialInput?: DeepPartial<PathValue<v.InferInput<TSchema>, TFieldPath>>;
|
|
245
|
+
}
|
|
246
|
+
declare function reset(form: BaseFormStore): void;
|
|
247
|
+
declare function reset<TSchema extends Schema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? ResetFieldConfig<TSchema, TFieldPath> : ResetFormConfig<TSchema>): void;
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/setErrors/setErrors.d.ts
|
|
250
|
+
interface SetFormErrorsConfig {
|
|
251
|
+
readonly path?: undefined;
|
|
252
|
+
readonly errors: [string, ...string[]] | null;
|
|
253
|
+
}
|
|
254
|
+
interface SetFieldErrorsConfig<TSchema extends Schema, TFieldPath extends RequiredPath> {
|
|
255
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
256
|
+
readonly errors: [string, ...string[]] | null;
|
|
257
|
+
}
|
|
258
|
+
declare function setErrors<TSchema extends Schema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? SetFieldErrorsConfig<TSchema, TFieldPath> : SetFormErrorsConfig): void;
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/setInput/setInput.d.ts
|
|
261
|
+
interface SetFormInputConfig<TSchema extends Schema> {
|
|
262
|
+
readonly path?: undefined;
|
|
263
|
+
readonly input: v.InferInput<TSchema>;
|
|
264
|
+
}
|
|
265
|
+
interface SetFieldInputConfig<TSchema extends Schema, TFieldPath extends RequiredPath> {
|
|
266
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
267
|
+
readonly input: PathValue<v.InferInput<TSchema>, TFieldPath>;
|
|
268
|
+
}
|
|
269
|
+
declare function setInput<TSchema extends Schema>(form: BaseFormStore<TSchema>, config: SetFormInputConfig<TSchema>): void;
|
|
270
|
+
declare function setInput<TSchema extends Schema, TFieldPath extends RequiredPath | undefined = undefined>(form: BaseFormStore<TSchema>, config: TFieldPath extends RequiredPath ? SetFieldInputConfig<TSchema, TFieldPath> : SetFormInputConfig<TSchema>): void;
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/submit/submit.d.ts
|
|
273
|
+
declare function submit(form: BaseFormStore): void;
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/swap/swap.d.ts
|
|
276
|
+
interface SwapConfig<TSchema extends Schema, TFieldArrayPath extends RequiredPath> {
|
|
277
|
+
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
278
|
+
readonly at: number;
|
|
279
|
+
readonly and: number;
|
|
280
|
+
}
|
|
281
|
+
declare function swap<TSchema extends Schema, TFieldArrayPath extends RequiredPath>(form: BaseFormStore<TSchema>, config: SwapConfig<TSchema, TFieldArrayPath>): void;
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/validate/validate.d.ts
|
|
284
|
+
interface ValidateFormConfig {
|
|
285
|
+
readonly shouldFocus?: boolean | undefined;
|
|
286
|
+
}
|
|
287
|
+
declare function validate<TSchema extends Schema>(form: BaseFormStore<TSchema>, config?: ValidateFormConfig): Promise<v.SafeParseResult<TSchema>>;
|
|
288
|
+
//#endregion
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/types/field.d.ts
|
|
291
|
+
/**
|
|
292
|
+
* Value type of the field element props.
|
|
293
|
+
*/
|
|
294
|
+
interface FieldElementProps {
|
|
295
|
+
readonly name: string;
|
|
296
|
+
readonly autofocus: boolean;
|
|
297
|
+
readonly ref: (element: Element | ComponentPublicInstance | null) => void;
|
|
298
|
+
readonly onFocus: (event: FocusEvent) => void;
|
|
299
|
+
readonly onChange: (event: Event) => void;
|
|
300
|
+
readonly onBlur: (event: FocusEvent) => void;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Value type of the field store.
|
|
304
|
+
*/
|
|
305
|
+
interface FieldStore<TSchema extends Schema = Schema, TFieldPath extends RequiredPath = RequiredPath> {
|
|
306
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
307
|
+
get input(): PartialValues<PathValue<v.InferInput<TSchema>, TFieldPath>>;
|
|
308
|
+
set input(value: PartialValues<PathValue<v.InferInput<TSchema>, TFieldPath>>);
|
|
309
|
+
readonly errors: [string, ...string[]] | null;
|
|
310
|
+
readonly isTouched: boolean;
|
|
311
|
+
readonly isDirty: boolean;
|
|
312
|
+
readonly isValid: boolean;
|
|
313
|
+
readonly props: FieldElementProps;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Value type of the field array store.
|
|
317
|
+
*/
|
|
318
|
+
interface FieldArrayStore<TSchema extends Schema = Schema, TFieldArrayPath extends RequiredPath = RequiredPath> {
|
|
319
|
+
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
320
|
+
readonly items: string[];
|
|
321
|
+
readonly errors: [string, ...string[]] | null;
|
|
322
|
+
readonly isTouched: boolean;
|
|
323
|
+
readonly isDirty: boolean;
|
|
324
|
+
readonly isValid: boolean;
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/types/form.d.ts
|
|
328
|
+
interface FormStore<TSchema extends Schema = Schema> extends BaseFormStore<TSchema> {
|
|
329
|
+
readonly isSubmitting: boolean;
|
|
330
|
+
readonly isSubmitted: boolean;
|
|
331
|
+
readonly isValidating: boolean;
|
|
332
|
+
readonly isTouched: boolean;
|
|
333
|
+
readonly isDirty: boolean;
|
|
334
|
+
readonly isValid: boolean;
|
|
335
|
+
readonly errors: [string, ...string[]] | null;
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/components/Field/Field.vue.d.ts
|
|
339
|
+
/**
|
|
340
|
+
* Properties of the `Field` component.
|
|
341
|
+
*/
|
|
342
|
+
interface FieldProps<TSchema extends Schema = Schema, TFieldPath extends RequiredPath = RequiredPath> {
|
|
343
|
+
readonly of: FormStore<TSchema>;
|
|
344
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
345
|
+
}
|
|
346
|
+
declare const _default: <TSchema extends Schema, TFieldPath extends RequiredPath>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$2<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
347
|
+
props: __VLS_PrettifyLocal$2<Pick<Partial<{}> & Omit<{} & vue0.VNodeProps & vue0.AllowedComponentProps & vue0.ComponentCustomProps, never>, never> & FieldProps<TSchema, TFieldPath> & Partial<{}>> & vue0.PublicProps;
|
|
348
|
+
expose(exposed: vue0.ShallowUnwrapRef<{}>): void;
|
|
349
|
+
attrs: any;
|
|
350
|
+
slots: {
|
|
351
|
+
default(props: FieldStore<TSchema, TFieldPath>): any;
|
|
352
|
+
};
|
|
353
|
+
emit: {};
|
|
354
|
+
}>) => vue0.VNode & {
|
|
355
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
356
|
+
};
|
|
357
|
+
type __VLS_PrettifyLocal$2<T> = { [K in keyof T as K]: T[K] } & {};
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/components/FieldArray/FieldArray.vue.d.ts
|
|
360
|
+
/**
|
|
361
|
+
* Properties of the `FieldArray` component.
|
|
362
|
+
*/
|
|
363
|
+
interface FieldArrayProps<TSchema extends Schema = Schema, TFieldArrayPath extends RequiredPath = RequiredPath> {
|
|
364
|
+
readonly of: FormStore<TSchema>;
|
|
365
|
+
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
366
|
+
}
|
|
367
|
+
declare const _default$1: <TSchema extends Schema, TFieldArrayPath extends RequiredPath>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$1<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
368
|
+
props: __VLS_PrettifyLocal$1<Pick<Partial<{}> & Omit<{} & vue0.VNodeProps & vue0.AllowedComponentProps & vue0.ComponentCustomProps, never>, never> & FieldArrayProps<TSchema, TFieldArrayPath> & Partial<{}>> & vue0.PublicProps;
|
|
369
|
+
expose(exposed: vue0.ShallowUnwrapRef<{}>): void;
|
|
370
|
+
attrs: any;
|
|
371
|
+
slots: {
|
|
372
|
+
default(props: FieldArrayStore<TSchema, TFieldArrayPath>): any;
|
|
373
|
+
};
|
|
374
|
+
emit: {};
|
|
375
|
+
}>) => vue0.VNode & {
|
|
376
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
377
|
+
};
|
|
378
|
+
type __VLS_PrettifyLocal$1<T> = { [K in keyof T as K]: T[K] } & {};
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/components/Form/Form.vue.d.ts
|
|
381
|
+
type FormProps<TSchema extends Schema = Schema> = {
|
|
382
|
+
of: FormStore<TSchema>;
|
|
383
|
+
onSubmit: SubmitHandler<TSchema>;
|
|
384
|
+
};
|
|
385
|
+
declare const _default$2: <TSchema extends Schema = Schema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
386
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{} & vue0.VNodeProps & vue0.AllowedComponentProps & vue0.ComponentCustomProps, never>, never> & FormProps<TSchema> & Partial<{}>> & vue0.PublicProps;
|
|
387
|
+
expose(exposed: vue0.ShallowUnwrapRef<{}>): void;
|
|
388
|
+
attrs: any;
|
|
389
|
+
slots: {
|
|
390
|
+
default?: (props: {}) => any;
|
|
391
|
+
};
|
|
392
|
+
emit: {};
|
|
393
|
+
}>) => vue0.VNode & {
|
|
394
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
395
|
+
};
|
|
396
|
+
type __VLS_PrettifyLocal<T> = { [K in keyof T as K]: T[K] } & {};
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/composables/useField/useField.d.ts
|
|
399
|
+
interface UseFieldConfig<TSchema extends Schema = Schema, TFieldPath extends RequiredPath = RequiredPath> {
|
|
400
|
+
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
401
|
+
}
|
|
402
|
+
declare function useField<TSchema extends Schema, TFieldPath extends RequiredPath>(form: MaybeRefOrGetter<FormStore<TSchema>>, config: MaybeRefOrGetter<UseFieldConfig<TSchema, TFieldPath>>): FieldStore<TSchema, TFieldPath>;
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/composables/useFieldArray/useFieldArray.d.ts
|
|
405
|
+
interface UseFieldArrayConfig<TSchema extends Schema = Schema, TFieldArrayPath extends RequiredPath = RequiredPath> {
|
|
406
|
+
readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
|
|
407
|
+
}
|
|
408
|
+
declare function useFieldArray<TSchema extends Schema, TFieldArrayPath extends RequiredPath>(form: MaybeRefOrGetter<FormStore<TSchema>>, config: MaybeRefOrGetter<UseFieldArrayConfig<TSchema, TFieldArrayPath>>): FieldArrayStore<TSchema, TFieldArrayPath>;
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/composables/useForm/useForm.d.ts
|
|
411
|
+
declare function useForm<TSchema extends Schema>(config: FormConfig<TSchema>): FormStore<TSchema>;
|
|
412
|
+
//#endregion
|
|
413
|
+
export { _default as Field, _default$1 as FieldArray, FieldArrayStore, FieldElementProps, FieldStore, FocusFieldConfig, _default$2 as Form, 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
ADDED
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
import { computed, createElementBlock, defineComponent, guardReactiveProps, normalizeProps, onBeforeMount, onUnmounted, openBlock, renderSlot, shallowRef as createSignal, toValue, unref } from "vue";
|
|
3
|
+
|
|
4
|
+
//#region ../../packages/core/dist/index.vue.js
|
|
5
|
+
const framework = "vue";
|
|
6
|
+
function createId() {
|
|
7
|
+
return Math.random().toString(36).slice(2);
|
|
8
|
+
}
|
|
9
|
+
function batch(fn) {
|
|
10
|
+
return fn();
|
|
11
|
+
}
|
|
12
|
+
function untrack(fn) {
|
|
13
|
+
return fn();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* TODO: Add comment
|
|
17
|
+
* TODO: Should this stay in /primitives or move to /utils?
|
|
18
|
+
*/
|
|
19
|
+
function initializeFieldStore(internalFieldStore, schema, initialInput, path) {
|
|
20
|
+
if (framework === "qwik" && schema.type === "lazy" || schema.type === "object_with_rest" || schema.type === "record" || schema.type === "tuple_with_rest" || schema.type === "promise") throw new Error(`"${schema.type}" schema is not supported`);
|
|
21
|
+
else if (schema.type === "lazy") initializeFieldStore(internalFieldStore, schema.getter(void 0), initialInput, path);
|
|
22
|
+
else if (schema.type === "exact_optional" || schema.type === "non_nullable" || schema.type === "non_nullish" || schema.type === "non_optional" || schema.type === "nullable" || schema.type === "nullish" || schema.type === "optional" || schema.type === "undefinedable") initializeFieldStore(internalFieldStore, schema.wrapped, initialInput ?? v.getDefault(schema), path);
|
|
23
|
+
else if (schema.type === "intersect" || schema.type === "union" || schema.type === "variant") for (const schemaOption of schema.options) initializeFieldStore(internalFieldStore, schemaOption, initialInput, path);
|
|
24
|
+
else {
|
|
25
|
+
internalFieldStore.schema = schema;
|
|
26
|
+
internalFieldStore.name = JSON.stringify(path);
|
|
27
|
+
internalFieldStore.elements = [];
|
|
28
|
+
internalFieldStore.errors = createSignal(null);
|
|
29
|
+
if (schema.type === "array" || schema.type === "loose_tuple" || schema.type === "strict_tuple" || schema.type === "tuple") {
|
|
30
|
+
if (internalFieldStore.kind && internalFieldStore.kind !== "array") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "array"`);
|
|
31
|
+
internalFieldStore.kind = "array";
|
|
32
|
+
if (internalFieldStore.kind === "array") {
|
|
33
|
+
internalFieldStore.children ??= [];
|
|
34
|
+
if (schema.type === "array") {
|
|
35
|
+
if (initialInput) for (let index = 0; index < initialInput.length; index++) {
|
|
36
|
+
internalFieldStore.children[index] = {};
|
|
37
|
+
path.push(index);
|
|
38
|
+
initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], path);
|
|
39
|
+
path.pop();
|
|
40
|
+
}
|
|
41
|
+
} else for (let index = 0; index < schema.items; index++) {
|
|
42
|
+
internalFieldStore.children[index] = {};
|
|
43
|
+
path.push(index);
|
|
44
|
+
initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput && initialInput[index], path);
|
|
45
|
+
path.pop();
|
|
46
|
+
}
|
|
47
|
+
const initialItems = internalFieldStore.children.map(createId);
|
|
48
|
+
internalFieldStore.initialItems = createSignal(initialItems);
|
|
49
|
+
internalFieldStore.startItems = createSignal(initialItems);
|
|
50
|
+
internalFieldStore.items = createSignal(initialItems);
|
|
51
|
+
internalFieldStore.isTouched = createSignal(false);
|
|
52
|
+
internalFieldStore.isDirty = createSignal(false);
|
|
53
|
+
}
|
|
54
|
+
} else if (schema.type === "loose_object" || schema.type === "object" || schema.type === "strict_object") {
|
|
55
|
+
if (internalFieldStore.kind && internalFieldStore.kind !== "object") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "object"`);
|
|
56
|
+
internalFieldStore.kind = "object";
|
|
57
|
+
if (internalFieldStore.kind === "object") {
|
|
58
|
+
internalFieldStore.children ??= {};
|
|
59
|
+
for (const key in schema.entries) {
|
|
60
|
+
internalFieldStore.children[key] = {};
|
|
61
|
+
path.push(key);
|
|
62
|
+
initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput && initialInput[key], path);
|
|
63
|
+
path.pop();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
internalFieldStore.kind = "value";
|
|
68
|
+
if (internalFieldStore.kind === "value") {
|
|
69
|
+
internalFieldStore.initialInput = createSignal(initialInput);
|
|
70
|
+
internalFieldStore.startInput = createSignal(initialInput);
|
|
71
|
+
internalFieldStore.input = createSignal(initialInput);
|
|
72
|
+
internalFieldStore.isTouched = createSignal(false);
|
|
73
|
+
internalFieldStore.isDirty = createSignal(false);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Copies the deeply nested state (signal values) from one array item to another.
|
|
80
|
+
* This includes the `isTouched`, `isDirty`, `startInput`, `input`, `startItems`, and `items` properties.
|
|
81
|
+
* Recursively walks through the field stores and copies all signal values.
|
|
82
|
+
*
|
|
83
|
+
* @param internalArrayStore - The field store of the array (not the array item)
|
|
84
|
+
* @param fromIndex - The source index to copy from
|
|
85
|
+
* @param toIndex - The destination index to copy to
|
|
86
|
+
*/
|
|
87
|
+
function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
88
|
+
batch(() => {
|
|
89
|
+
untrack(() => {
|
|
90
|
+
if (fromInternalFieldStore.kind === "array" && toInternalFieldStore.kind === "array") {
|
|
91
|
+
const fromItems = fromInternalFieldStore.items.value;
|
|
92
|
+
toInternalFieldStore.isTouched.value = fromInternalFieldStore.isTouched.value;
|
|
93
|
+
toInternalFieldStore.isDirty.value = fromInternalFieldStore.isDirty.value;
|
|
94
|
+
toInternalFieldStore.startItems.value = fromInternalFieldStore.startItems.value;
|
|
95
|
+
toInternalFieldStore.items.value = fromItems;
|
|
96
|
+
let path;
|
|
97
|
+
for (let index = 0; index < fromItems.length; index++) {
|
|
98
|
+
if (!toInternalFieldStore.children[index]) {
|
|
99
|
+
path ??= JSON.parse(toInternalFieldStore.name);
|
|
100
|
+
toInternalFieldStore.children[index] = {};
|
|
101
|
+
path.push(index);
|
|
102
|
+
initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, path);
|
|
103
|
+
path.pop();
|
|
104
|
+
}
|
|
105
|
+
copyItemState(fromInternalFieldStore.children[index], toInternalFieldStore.children[index]);
|
|
106
|
+
}
|
|
107
|
+
} else if (fromInternalFieldStore.kind === "object" && toInternalFieldStore.kind === "object") for (const key in fromInternalFieldStore.children) copyItemState(fromInternalFieldStore.children[key], toInternalFieldStore.children[key]);
|
|
108
|
+
else if (fromInternalFieldStore.kind === "value" && toInternalFieldStore.kind === "value") {
|
|
109
|
+
toInternalFieldStore.isTouched.value = fromInternalFieldStore.isTouched.value;
|
|
110
|
+
toInternalFieldStore.isDirty.value = fromInternalFieldStore.isDirty.value;
|
|
111
|
+
toInternalFieldStore.startInput.value = fromInternalFieldStore.startInput.value;
|
|
112
|
+
toInternalFieldStore.input.value = fromInternalFieldStore.input.value;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resets the state of an array item (signal values) deeply nested.
|
|
119
|
+
* Sets `isTouched` and `isDirty` to `false` and `startInput`, `input`,
|
|
120
|
+
* `startItems` and `items` to the new input.
|
|
121
|
+
* Keeps the `initialInput` and `initialItems` state unchanged for form reset functionality.
|
|
122
|
+
*
|
|
123
|
+
* @param internalFieldStore - The field store of the array item
|
|
124
|
+
* @param initialInput - The new input value (can be any type including array or object)
|
|
125
|
+
*/
|
|
126
|
+
function resetItemState(internalFieldStore, initialInput) {
|
|
127
|
+
batch(() => {
|
|
128
|
+
internalFieldStore.errors.value = null;
|
|
129
|
+
if (internalFieldStore.kind === "array") {
|
|
130
|
+
internalFieldStore.isTouched.value = false;
|
|
131
|
+
internalFieldStore.isDirty.value = false;
|
|
132
|
+
if (initialInput) {
|
|
133
|
+
const newItems = initialInput.map(createId);
|
|
134
|
+
internalFieldStore.startItems.value = newItems;
|
|
135
|
+
internalFieldStore.items.value = newItems;
|
|
136
|
+
for (let index = 0; index < initialInput.length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], initialInput[index]);
|
|
137
|
+
} else {
|
|
138
|
+
internalFieldStore.startItems.value = [];
|
|
139
|
+
internalFieldStore.items.value = [];
|
|
140
|
+
}
|
|
141
|
+
} else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], initialInput?.[key]);
|
|
142
|
+
else {
|
|
143
|
+
internalFieldStore.isTouched.value = false;
|
|
144
|
+
internalFieldStore.isDirty.value = false;
|
|
145
|
+
internalFieldStore.startInput.value = initialInput;
|
|
146
|
+
internalFieldStore.input.value = initialInput;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Swaps the deeply nested state (signal values) between two field stores.
|
|
152
|
+
* This includes the `isTouched`, `isDirty`, `startInput`, `input`, `startItems`, and `items` properties.
|
|
153
|
+
* Recursively walks through the field stores and swaps all signal values.
|
|
154
|
+
*
|
|
155
|
+
* @param firstInternalFieldStore - The first field store to swap
|
|
156
|
+
* @param secondInternalFieldStore - The second field store to swap
|
|
157
|
+
*/
|
|
158
|
+
function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
159
|
+
batch(() => {
|
|
160
|
+
untrack(() => {
|
|
161
|
+
if (firstInternalFieldStore.kind === "array" && secondInternalFieldStore.kind === "array") {
|
|
162
|
+
const firstItems = firstInternalFieldStore.items.value;
|
|
163
|
+
const secondItems = secondInternalFieldStore.items.value;
|
|
164
|
+
const tempIsTouched = firstInternalFieldStore.isTouched.value;
|
|
165
|
+
firstInternalFieldStore.isTouched.value = secondInternalFieldStore.isTouched.value;
|
|
166
|
+
secondInternalFieldStore.isTouched.value = tempIsTouched;
|
|
167
|
+
const tempIsDirty = firstInternalFieldStore.isDirty.value;
|
|
168
|
+
firstInternalFieldStore.isDirty.value = secondInternalFieldStore.isDirty.value;
|
|
169
|
+
secondInternalFieldStore.isDirty.value = tempIsDirty;
|
|
170
|
+
const tempStartItems = firstInternalFieldStore.startItems.value;
|
|
171
|
+
firstInternalFieldStore.startItems.value = secondInternalFieldStore.startItems.value;
|
|
172
|
+
secondInternalFieldStore.startItems.value = tempStartItems;
|
|
173
|
+
firstInternalFieldStore.items.value = secondItems;
|
|
174
|
+
secondInternalFieldStore.items.value = firstItems;
|
|
175
|
+
const maxLength = Math.max(firstItems.length, secondItems.length);
|
|
176
|
+
let firstPath;
|
|
177
|
+
let secondPath;
|
|
178
|
+
for (let index = 0; index < maxLength; index++) {
|
|
179
|
+
if (!firstInternalFieldStore.children[index]) {
|
|
180
|
+
firstPath ??= JSON.parse(firstInternalFieldStore.name);
|
|
181
|
+
firstInternalFieldStore.children[index] = {};
|
|
182
|
+
firstPath.push(index);
|
|
183
|
+
initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, firstPath);
|
|
184
|
+
firstPath.pop();
|
|
185
|
+
}
|
|
186
|
+
if (!secondInternalFieldStore.children[index]) {
|
|
187
|
+
secondPath ??= JSON.parse(secondInternalFieldStore.name);
|
|
188
|
+
secondInternalFieldStore.children[index] = {};
|
|
189
|
+
secondPath.push(index);
|
|
190
|
+
initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, secondPath);
|
|
191
|
+
secondPath.pop();
|
|
192
|
+
}
|
|
193
|
+
swapItemState(firstInternalFieldStore.children[index], secondInternalFieldStore.children[index]);
|
|
194
|
+
}
|
|
195
|
+
} else if (firstInternalFieldStore.kind === "object" && secondInternalFieldStore.kind === "object") for (const key in firstInternalFieldStore.children) swapItemState(firstInternalFieldStore.children[key], secondInternalFieldStore.children[key]);
|
|
196
|
+
else if (firstInternalFieldStore.kind === "value" && secondInternalFieldStore.kind === "value") {
|
|
197
|
+
const tempIsTouched = firstInternalFieldStore.isTouched.value;
|
|
198
|
+
firstInternalFieldStore.isTouched.value = secondInternalFieldStore.isTouched.value;
|
|
199
|
+
secondInternalFieldStore.isTouched.value = tempIsTouched;
|
|
200
|
+
const tempIsDirty = firstInternalFieldStore.isDirty.value;
|
|
201
|
+
firstInternalFieldStore.isDirty.value = secondInternalFieldStore.isDirty.value;
|
|
202
|
+
secondInternalFieldStore.isDirty.value = tempIsDirty;
|
|
203
|
+
const tempStartInput = firstInternalFieldStore.startInput.value;
|
|
204
|
+
firstInternalFieldStore.startInput.value = secondInternalFieldStore.startInput.value;
|
|
205
|
+
secondInternalFieldStore.startInput.value = tempStartInput;
|
|
206
|
+
const tempInput = firstInternalFieldStore.input.value;
|
|
207
|
+
firstInternalFieldStore.input.value = secondInternalFieldStore.input.value;
|
|
208
|
+
secondInternalFieldStore.input.value = tempInput;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function getFieldInput(internalFieldStore) {
|
|
214
|
+
if (internalFieldStore.kind === "array") {
|
|
215
|
+
const value = [];
|
|
216
|
+
for (let index = 0; index < internalFieldStore.items.value.length; index++) value[index] = getFieldInput(internalFieldStore.children[index]);
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
if (internalFieldStore.kind === "object") {
|
|
220
|
+
const value = {};
|
|
221
|
+
for (const key in internalFieldStore.children) value[key] = getFieldInput(internalFieldStore.children[key]);
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
return internalFieldStore.input.value;
|
|
225
|
+
}
|
|
226
|
+
function getFieldBool(internalFieldStore, type) {
|
|
227
|
+
if (internalFieldStore.kind === "array") {
|
|
228
|
+
if (internalFieldStore[type].value) return true;
|
|
229
|
+
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (getFieldBool(internalFieldStore.children[index], type)) return true;
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
if (internalFieldStore.kind == "object") {
|
|
233
|
+
if (type === "errors" && internalFieldStore[type].value) return true;
|
|
234
|
+
for (const key in internalFieldStore.children) if (getFieldBool(internalFieldStore.children[key], type)) return true;
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
return !!internalFieldStore[type].value;
|
|
238
|
+
}
|
|
239
|
+
function getFieldStore(internalFormStore, path) {
|
|
240
|
+
let internalFieldStore = internalFormStore;
|
|
241
|
+
for (const key of path) internalFieldStore = internalFieldStore.children[key];
|
|
242
|
+
return internalFieldStore;
|
|
243
|
+
}
|
|
244
|
+
function setFieldBool(internalFieldStore, type, bool) {
|
|
245
|
+
batch(() => {
|
|
246
|
+
if (internalFieldStore.kind === "array") {
|
|
247
|
+
internalFieldStore[type].value = bool;
|
|
248
|
+
for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
|
|
249
|
+
} else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
250
|
+
else internalFieldStore[type].value = bool;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function setFieldInput(internalFieldStore, input) {
|
|
254
|
+
batch(() => {
|
|
255
|
+
if (internalFieldStore.kind === "array") {
|
|
256
|
+
const items = untrack(() => internalFieldStore.items.value);
|
|
257
|
+
if (input.length < items.length) internalFieldStore.items.value = items.slice(0, input.length);
|
|
258
|
+
else if (input.length > items.length) {
|
|
259
|
+
if (input.length > internalFieldStore.children.length) {
|
|
260
|
+
const path = JSON.parse(internalFieldStore.name);
|
|
261
|
+
for (let index = internalFieldStore.children.length; index < input.length; index++) {
|
|
262
|
+
internalFieldStore.children[index] = {};
|
|
263
|
+
path.push(index);
|
|
264
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
|
|
265
|
+
path.pop();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
internalFieldStore.items.value = [...items, ...input.slice(items.length).map(createId)];
|
|
269
|
+
}
|
|
270
|
+
for (let index = 0; index < items.length; index++) setFieldInput(internalFieldStore.children[index], input[index]);
|
|
271
|
+
internalFieldStore.isDirty.value = untrack(() => internalFieldStore.startItems.value).length !== items.length;
|
|
272
|
+
} else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldInput(internalFieldStore.children[key], input[key]);
|
|
273
|
+
else {
|
|
274
|
+
internalFieldStore.input.value = input;
|
|
275
|
+
internalFieldStore.isTouched.value = true;
|
|
276
|
+
const startInput = untrack(() => internalFieldStore.startInput.value);
|
|
277
|
+
internalFieldStore.isDirty.value = startInput !== input && (startInput !== void 0 || input !== "" && !Number.isNaN(input));
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
282
|
+
batch(() => {
|
|
283
|
+
if (internalFieldStore.kind === "array") {
|
|
284
|
+
if (initialInput.length > internalFieldStore.children.length) {
|
|
285
|
+
const path = JSON.parse(internalFieldStore.name);
|
|
286
|
+
for (let index = internalFieldStore.children.length; index < initialInput.length; index++) {
|
|
287
|
+
internalFieldStore.children[index] = {};
|
|
288
|
+
path.push(index);
|
|
289
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialInput[index], path);
|
|
290
|
+
path.pop();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
internalFieldStore.initialItems.value = initialInput.map(createId);
|
|
294
|
+
for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialInput?.[index]);
|
|
295
|
+
} else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
|
|
296
|
+
else internalFieldStore.initialInput.value = initialInput;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function walkFieldStore(internalFieldStore, callback) {
|
|
300
|
+
callback(internalFieldStore);
|
|
301
|
+
if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) walkFieldStore(internalFieldStore.children[index], callback);
|
|
302
|
+
else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) walkFieldStore(internalFieldStore.children[key], callback);
|
|
303
|
+
}
|
|
304
|
+
function createFormStore(config, parse) {
|
|
305
|
+
const store = {};
|
|
306
|
+
initializeFieldStore(store, config.schema, config.initialInput, []);
|
|
307
|
+
store.validate = config.validate ?? "submit";
|
|
308
|
+
store.revalidate = config.revalidate ?? "input";
|
|
309
|
+
store.parse = parse;
|
|
310
|
+
store.isSubmitting = createSignal(false);
|
|
311
|
+
store.isSubmitted = createSignal(false);
|
|
312
|
+
store.isValidating = createSignal(false);
|
|
313
|
+
return store;
|
|
314
|
+
}
|
|
315
|
+
async function validateFormInput(internalFormStore, config) {
|
|
316
|
+
internalFormStore.validators++;
|
|
317
|
+
internalFormStore.isValidating.value = true;
|
|
318
|
+
const result = await internalFormStore.parse(untrack(() => getFieldInput(internalFormStore)));
|
|
319
|
+
let rootErrors;
|
|
320
|
+
let nestedErrors;
|
|
321
|
+
if (result.issues) {
|
|
322
|
+
nestedErrors = {};
|
|
323
|
+
for (const issue of result.issues) if (issue.path) {
|
|
324
|
+
const path = [];
|
|
325
|
+
for (const pathItem of issue.path) {
|
|
326
|
+
const key = pathItem.key;
|
|
327
|
+
const keyType = typeof key;
|
|
328
|
+
const itemType = pathItem.type;
|
|
329
|
+
if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
|
|
330
|
+
path.push(key);
|
|
331
|
+
}
|
|
332
|
+
const name = JSON.stringify(path);
|
|
333
|
+
const fieldErrors = nestedErrors[name];
|
|
334
|
+
if (fieldErrors) fieldErrors.push(issue.message);
|
|
335
|
+
else nestedErrors[name] = [issue.message];
|
|
336
|
+
} else if (rootErrors) rootErrors.push(issue.message);
|
|
337
|
+
else rootErrors = [issue.message];
|
|
338
|
+
}
|
|
339
|
+
let shouldFocus = config?.shouldFocus ?? false;
|
|
340
|
+
batch(() => {
|
|
341
|
+
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
342
|
+
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
343
|
+
else {
|
|
344
|
+
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
345
|
+
internalFieldStore.errors.value = fieldErrors;
|
|
346
|
+
if (shouldFocus && fieldErrors) {
|
|
347
|
+
internalFieldStore.elements[0]?.focus();
|
|
348
|
+
shouldFocus = false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
internalFormStore.validators--;
|
|
353
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
354
|
+
});
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
function validateIfRequired(internalFormStore, internalFieldStore, validationModes) {
|
|
358
|
+
if (validationModes === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
|
|
359
|
+
}
|
|
360
|
+
const INTERNAL = "~internal";
|
|
361
|
+
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region ../../packages/methods/dist/index.vue.js
|
|
364
|
+
function focus(config) {
|
|
365
|
+
getFieldStore(config.form[INTERNAL], config.path).elements[0]?.focus();
|
|
366
|
+
}
|
|
367
|
+
function getAllErrors(form) {
|
|
368
|
+
let allErrors = null;
|
|
369
|
+
walkFieldStore(form[INTERNAL], (internalFieldStore) => {
|
|
370
|
+
const errors = internalFieldStore.errors.value;
|
|
371
|
+
if (errors) if (allErrors) allErrors.push(...errors);
|
|
372
|
+
else allErrors = [...errors];
|
|
373
|
+
});
|
|
374
|
+
return allErrors;
|
|
375
|
+
}
|
|
376
|
+
function getErrors(form, config) {
|
|
377
|
+
return (config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]).errors.value;
|
|
378
|
+
}
|
|
379
|
+
function getInput(form, config) {
|
|
380
|
+
return getFieldInput(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]);
|
|
381
|
+
}
|
|
382
|
+
function handleSubmit(form, handler) {
|
|
383
|
+
return async (event) => {
|
|
384
|
+
event.preventDefault();
|
|
385
|
+
const internalFormStore = form[INTERNAL];
|
|
386
|
+
internalFormStore.isSubmitted.value = true;
|
|
387
|
+
internalFormStore.isSubmitting.value = true;
|
|
388
|
+
try {
|
|
389
|
+
const result = await validateFormInput(internalFormStore, { shouldFocus: true });
|
|
390
|
+
if (result.success) await handler(result.output, event);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
internalFormStore.errors.value = [error instanceof Error ? error.message : "An unknown error has occurred."];
|
|
393
|
+
} finally {
|
|
394
|
+
internalFormStore.isSubmitting.value = false;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function insert(form, config) {
|
|
399
|
+
const internalFormStore = form[INTERNAL];
|
|
400
|
+
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
401
|
+
const items = untrack(() => internalArrayStore.items.value);
|
|
402
|
+
const insertIndex = config.at === void 0 ? items.length : config.at;
|
|
403
|
+
if (insertIndex >= 0 && insertIndex <= items.length) batch(() => {
|
|
404
|
+
const newItems = [...items];
|
|
405
|
+
newItems.splice(insertIndex, 0, createId());
|
|
406
|
+
internalArrayStore.items.value = newItems;
|
|
407
|
+
for (let index = items.length; index > insertIndex; index--) copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
|
|
408
|
+
if (!internalArrayStore.children[insertIndex]) {
|
|
409
|
+
const path = JSON.parse(internalArrayStore.name);
|
|
410
|
+
internalArrayStore.children[insertIndex] = {};
|
|
411
|
+
path.push(insertIndex);
|
|
412
|
+
initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, path);
|
|
413
|
+
} else resetItemState(internalArrayStore.children[insertIndex], config.initialInput);
|
|
414
|
+
internalArrayStore.isTouched.value = true;
|
|
415
|
+
internalArrayStore.isDirty.value = true;
|
|
416
|
+
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function move(form, config) {
|
|
420
|
+
const internalFormStore = form[INTERNAL];
|
|
421
|
+
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
422
|
+
const items = untrack(() => internalArrayStore.items.value);
|
|
423
|
+
if (config.from >= 0 && config.from <= items.length - 1 && config.to >= 0 && config.to <= items.length - 1 && config.from !== config.to) batch(() => {
|
|
424
|
+
const newItems = [...items];
|
|
425
|
+
newItems.splice(config.to, 0, newItems.splice(config.from, 1)[0]);
|
|
426
|
+
internalArrayStore.items.value = newItems;
|
|
427
|
+
const tempInternalFieldStore = {};
|
|
428
|
+
initializeFieldStore(tempInternalFieldStore, internalArrayStore.schema.item, void 0, []);
|
|
429
|
+
copyItemState(internalArrayStore.children[config.from < config.to ? config.from : config.to], tempInternalFieldStore);
|
|
430
|
+
if (config.from < config.to) for (let index = config.from; index < config.to; index++) copyItemState(internalArrayStore.children[index + 1], internalArrayStore.children[index]);
|
|
431
|
+
else for (let index = config.from; index > config.to; index--) copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
|
|
432
|
+
copyItemState(tempInternalFieldStore, internalArrayStore.children[config.from < config.to ? config.to : config.from]);
|
|
433
|
+
internalArrayStore.isTouched.value = true;
|
|
434
|
+
internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
|
|
435
|
+
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function remove(form, config) {
|
|
439
|
+
const internalFormStore = form[INTERNAL];
|
|
440
|
+
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
441
|
+
const items = untrack(() => internalArrayStore.items.value);
|
|
442
|
+
if (config.at >= 0 && config.at <= items.length - 1) batch(() => {
|
|
443
|
+
const newItems = [...items];
|
|
444
|
+
newItems.splice(config.at, 1);
|
|
445
|
+
internalArrayStore.items.value = newItems;
|
|
446
|
+
for (let index = config.at; index < items.length - 1; index++) copyItemState(internalArrayStore.children[index + 1], internalArrayStore.children[index]);
|
|
447
|
+
internalArrayStore.isTouched.value = true;
|
|
448
|
+
internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
|
|
449
|
+
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
function replace(form, config) {
|
|
453
|
+
const internalFormStore = form[INTERNAL];
|
|
454
|
+
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
455
|
+
const items = untrack(() => internalArrayStore.items.value);
|
|
456
|
+
if (config.at >= 0 && config.at <= items.length - 1) batch(() => {
|
|
457
|
+
const newItems = [...items];
|
|
458
|
+
newItems[config.at] = createId();
|
|
459
|
+
internalArrayStore.items.value = newItems;
|
|
460
|
+
resetItemState(internalArrayStore.children[config.at], config.initialInput);
|
|
461
|
+
internalArrayStore.isTouched.value = true;
|
|
462
|
+
internalArrayStore.isDirty.value = true;
|
|
463
|
+
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
function reset(form, config) {
|
|
467
|
+
batch(() => {
|
|
468
|
+
untrack(() => {
|
|
469
|
+
const internalFormStore = form[INTERNAL];
|
|
470
|
+
const internalFieldStore = config?.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
|
|
471
|
+
if (config?.initialInput) setInitialFieldInput(internalFieldStore, config.initialInput);
|
|
472
|
+
walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
|
|
473
|
+
if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
|
|
474
|
+
if (internalFieldStore$1.kind === "array") {
|
|
475
|
+
internalFieldStore$1.startItems.value = internalFieldStore$1.initialItems.value;
|
|
476
|
+
if (!config?.keepInput || internalFieldStore$1.startItems.value.length === internalFieldStore$1.items.value.length) internalFieldStore$1.items.value = internalFieldStore$1.initialItems.value;
|
|
477
|
+
if (!config?.keepTouched) internalFieldStore$1.isTouched.value = false;
|
|
478
|
+
internalFieldStore$1.isDirty.value = internalFieldStore$1.startItems.value !== internalFieldStore$1.items.value;
|
|
479
|
+
} else if (internalFieldStore$1.kind === "value") {
|
|
480
|
+
internalFieldStore$1.startInput.value = internalFieldStore$1.initialInput.value;
|
|
481
|
+
if (!config?.keepInput) internalFieldStore$1.input.value = internalFieldStore$1.initialInput.value;
|
|
482
|
+
if (!config?.keepTouched) internalFieldStore$1.isTouched.value = false;
|
|
483
|
+
internalFieldStore$1.isDirty.value = internalFieldStore$1.startInput.value !== internalFieldStore$1.input.value;
|
|
484
|
+
for (const element of internalFieldStore$1.elements) if (element.type === "file") element.value = "";
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
if (!config?.path) {
|
|
488
|
+
if (!config?.keepSubmitted) internalFormStore.isSubmitted.value = false;
|
|
489
|
+
if (internalFormStore.validate === "initial") validateFormInput(internalFormStore);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
function setErrors(form, config) {
|
|
495
|
+
(config.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]).errors.value = config.errors;
|
|
496
|
+
}
|
|
497
|
+
function setInput(form, config) {
|
|
498
|
+
batch(() => {
|
|
499
|
+
const internalFormStore = form[INTERNAL];
|
|
500
|
+
const internalFieldStore = config.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
|
|
501
|
+
setFieldInput(internalFieldStore, config.input);
|
|
502
|
+
validateIfRequired(internalFormStore, internalFieldStore, "input");
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
function submit(form) {
|
|
506
|
+
form[INTERNAL].element?.requestSubmit();
|
|
507
|
+
}
|
|
508
|
+
function swap(form, config) {
|
|
509
|
+
const internalFormStore = form[INTERNAL];
|
|
510
|
+
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
511
|
+
const items = untrack(() => internalArrayStore.items.value);
|
|
512
|
+
if (config.at >= 0 && config.at <= items.length - 1 && config.and >= 0 && config.and <= items.length - 1 && config.at !== config.and) batch(() => {
|
|
513
|
+
const newItems = [...items];
|
|
514
|
+
const tempItemId = newItems[config.at];
|
|
515
|
+
newItems[config.at] = newItems[config.and];
|
|
516
|
+
newItems[config.and] = tempItemId;
|
|
517
|
+
internalArrayStore.items.value = newItems;
|
|
518
|
+
swapItemState(internalArrayStore.children[config.at], internalArrayStore.children[config.and]);
|
|
519
|
+
internalArrayStore.isTouched.value = true;
|
|
520
|
+
internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
|
|
521
|
+
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
function validate(form, config) {
|
|
525
|
+
return validateFormInput(form[INTERNAL], config);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/composables/useField/useField.ts
|
|
530
|
+
function useField(form, config) {
|
|
531
|
+
const internalFieldStore = computed(() => getFieldStore(toValue(form)[INTERNAL], toValue(config).path));
|
|
532
|
+
onUnmounted(() => {
|
|
533
|
+
internalFieldStore.value.elements = internalFieldStore.value.elements.filter((element) => element.isConnected);
|
|
534
|
+
});
|
|
535
|
+
const input = computed(() => getFieldInput(internalFieldStore.value));
|
|
536
|
+
const isTouched = computed(() => getFieldBool(internalFieldStore.value, "isTouched"));
|
|
537
|
+
const isDirty = computed(() => getFieldBool(internalFieldStore.value, "isDirty"));
|
|
538
|
+
const isValid = computed(() => !getFieldBool(internalFieldStore.value, "errors"));
|
|
539
|
+
return {
|
|
540
|
+
get path() {
|
|
541
|
+
return toValue(config).path;
|
|
542
|
+
},
|
|
543
|
+
get input() {
|
|
544
|
+
return input.value;
|
|
545
|
+
},
|
|
546
|
+
set input(value) {
|
|
547
|
+
setFieldInput(internalFieldStore.value, value);
|
|
548
|
+
validateIfRequired(toValue(form)[INTERNAL], internalFieldStore.value, "input");
|
|
549
|
+
},
|
|
550
|
+
get errors() {
|
|
551
|
+
return internalFieldStore.value.errors.value;
|
|
552
|
+
},
|
|
553
|
+
get isTouched() {
|
|
554
|
+
return isTouched.value;
|
|
555
|
+
},
|
|
556
|
+
get isDirty() {
|
|
557
|
+
return isDirty.value;
|
|
558
|
+
},
|
|
559
|
+
get isValid() {
|
|
560
|
+
return isValid.value;
|
|
561
|
+
},
|
|
562
|
+
props: {
|
|
563
|
+
get name() {
|
|
564
|
+
return internalFieldStore.value.name;
|
|
565
|
+
},
|
|
566
|
+
autofocus: !!internalFieldStore.value.errors.value,
|
|
567
|
+
ref(element) {
|
|
568
|
+
if (element) internalFieldStore.value.elements.push(element);
|
|
569
|
+
},
|
|
570
|
+
onFocus() {
|
|
571
|
+
setFieldBool(internalFieldStore.value, "isTouched", true);
|
|
572
|
+
validateIfRequired(toValue(form)[INTERNAL], internalFieldStore.value, "touch");
|
|
573
|
+
},
|
|
574
|
+
onChange() {
|
|
575
|
+
validateIfRequired(toValue(form)[INTERNAL], internalFieldStore.value, "change");
|
|
576
|
+
},
|
|
577
|
+
onBlur() {
|
|
578
|
+
validateIfRequired(toValue(form)[INTERNAL], internalFieldStore.value, "blur");
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/composables/useFieldArray/useFieldArray.ts
|
|
586
|
+
function useFieldArray(form, config) {
|
|
587
|
+
const internalFieldStore = computed(() => getFieldStore(toValue(form)[INTERNAL], toValue(config).path));
|
|
588
|
+
const isTouched = computed(() => getFieldBool(internalFieldStore.value, "isTouched"));
|
|
589
|
+
const isDirty = computed(() => getFieldBool(internalFieldStore.value, "isDirty"));
|
|
590
|
+
const isValid = computed(() => !getFieldBool(internalFieldStore.value, "errors"));
|
|
591
|
+
return {
|
|
592
|
+
get path() {
|
|
593
|
+
return toValue(config).path;
|
|
594
|
+
},
|
|
595
|
+
get items() {
|
|
596
|
+
return internalFieldStore.value.items.value;
|
|
597
|
+
},
|
|
598
|
+
get errors() {
|
|
599
|
+
return internalFieldStore.value.errors.value;
|
|
600
|
+
},
|
|
601
|
+
get isTouched() {
|
|
602
|
+
return isTouched.value;
|
|
603
|
+
},
|
|
604
|
+
get isDirty() {
|
|
605
|
+
return isDirty.value;
|
|
606
|
+
},
|
|
607
|
+
get isValid() {
|
|
608
|
+
return isValid.value;
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
//#endregion
|
|
614
|
+
//#region src/composables/useForm/useForm.ts
|
|
615
|
+
function useForm(config) {
|
|
616
|
+
const internalFormStore = createFormStore(config, async (input) => v.safeParseAsync(config.schema, input));
|
|
617
|
+
onBeforeMount(async () => {
|
|
618
|
+
if (config.validate === "initial") await validateFormInput(internalFormStore);
|
|
619
|
+
});
|
|
620
|
+
const isTouched = computed(() => getFieldBool(internalFormStore, "isTouched"));
|
|
621
|
+
const isDirty = computed(() => getFieldBool(internalFormStore, "isDirty"));
|
|
622
|
+
const isValid = computed(() => !getFieldBool(internalFormStore, "errors"));
|
|
623
|
+
return {
|
|
624
|
+
[INTERNAL]: internalFormStore,
|
|
625
|
+
get isSubmitting() {
|
|
626
|
+
return internalFormStore.isSubmitting.value;
|
|
627
|
+
},
|
|
628
|
+
get isSubmitted() {
|
|
629
|
+
return internalFormStore.isSubmitted.value;
|
|
630
|
+
},
|
|
631
|
+
get isValidating() {
|
|
632
|
+
return internalFormStore.isValidating.value;
|
|
633
|
+
},
|
|
634
|
+
get isTouched() {
|
|
635
|
+
return isTouched.value;
|
|
636
|
+
},
|
|
637
|
+
get isDirty() {
|
|
638
|
+
return isDirty.value;
|
|
639
|
+
},
|
|
640
|
+
get isValid() {
|
|
641
|
+
return isValid.value;
|
|
642
|
+
},
|
|
643
|
+
get errors() {
|
|
644
|
+
return internalFormStore.errors.value;
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/components/Field/Field.vue?vue&type=script&setup=true&lang.ts
|
|
651
|
+
var Field_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
652
|
+
inheritAttrs: false,
|
|
653
|
+
__name: "Field",
|
|
654
|
+
props: {
|
|
655
|
+
of: {},
|
|
656
|
+
path: {}
|
|
657
|
+
},
|
|
658
|
+
setup(__props) {
|
|
659
|
+
const props = __props;
|
|
660
|
+
const field = useField(() => props.of, () => ({ path: props.path }));
|
|
661
|
+
return (_ctx, _cache) => {
|
|
662
|
+
return renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(unref(field))));
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
//#endregion
|
|
668
|
+
//#region src/components/Field/Field.vue
|
|
669
|
+
var Field_default = Field_vue_vue_type_script_setup_true_lang_default;
|
|
670
|
+
|
|
671
|
+
//#endregion
|
|
672
|
+
//#region src/components/FieldArray/FieldArray.vue?vue&type=script&setup=true&lang.ts
|
|
673
|
+
var FieldArray_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
674
|
+
inheritAttrs: false,
|
|
675
|
+
__name: "FieldArray",
|
|
676
|
+
props: {
|
|
677
|
+
of: {},
|
|
678
|
+
path: {}
|
|
679
|
+
},
|
|
680
|
+
setup(__props) {
|
|
681
|
+
const props = __props;
|
|
682
|
+
const field = useFieldArray(() => props.of, () => ({ path: props.path }));
|
|
683
|
+
return (_ctx, _cache) => {
|
|
684
|
+
return renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(unref(field))));
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/components/FieldArray/FieldArray.vue
|
|
691
|
+
var FieldArray_default = FieldArray_vue_vue_type_script_setup_true_lang_default;
|
|
692
|
+
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region src/components/Form/Form.vue?vue&type=script&setup=true&lang.ts
|
|
695
|
+
var Form_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
696
|
+
__name: "Form",
|
|
697
|
+
props: {
|
|
698
|
+
of: {},
|
|
699
|
+
onSubmit: { type: Function }
|
|
700
|
+
},
|
|
701
|
+
setup(__props) {
|
|
702
|
+
const props = __props;
|
|
703
|
+
const handler = handleSubmit(props.of, props.onSubmit);
|
|
704
|
+
return (_ctx, _cache) => {
|
|
705
|
+
return openBlock(), createElementBlock("form", {
|
|
706
|
+
novalidate: "",
|
|
707
|
+
onSubmit: _cache[0] || (_cache[0] = (...args) => unref(handler) && unref(handler)(...args))
|
|
708
|
+
}, [renderSlot(_ctx.$slots, "default")], 32);
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/components/Form/Form.vue
|
|
715
|
+
var Form_default = Form_vue_vue_type_script_setup_true_lang_default;
|
|
716
|
+
|
|
717
|
+
//#endregion
|
|
718
|
+
export { Field_default as Field, FieldArray_default as FieldArray, Form_default as Form, focus, getAllErrors, getErrors, getInput, handleSubmit, insert, move, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@formisch/vue",
|
|
3
|
+
"description": "The modular and type-safe form library for Vue",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Fabian Hiller",
|
|
7
|
+
"homepage": "https://formisch.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/fabian-hiller/formisch"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"vue",
|
|
14
|
+
"form",
|
|
15
|
+
"typescript",
|
|
16
|
+
"schema",
|
|
17
|
+
"validation"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/types/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/types/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsdown",
|
|
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
|
+
"vite": "vite"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@formisch/core": "workspace:*",
|
|
45
|
+
"@formisch/methods": "workspace:*",
|
|
46
|
+
"@types/node": "^24.1.0",
|
|
47
|
+
"@vue/eslint-config-typescript": "^14.6.0",
|
|
48
|
+
"eslint": "^9.32.0",
|
|
49
|
+
"eslint-plugin-vue": "~10.3.0",
|
|
50
|
+
"tsdown": "^0.13.0",
|
|
51
|
+
"typescript": "~5.8.3",
|
|
52
|
+
"unplugin-vue": "^7.0.0",
|
|
53
|
+
"valibot": "^1.1.0",
|
|
54
|
+
"vue": "^3.5.18",
|
|
55
|
+
"vue-tsc": "^3.0.4"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"typescript": ">=5",
|
|
59
|
+
"valibot": "^1.0.0",
|
|
60
|
+
"vue": "^3.3.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"typescript": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|