notform 1.0.0-alpha.3
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 +29 -0
- package/dist/index.d.ts +412 -0
- package/dist/index.js +504 -0
- package/package.json +92 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# vue-components-starter
|
|
2
|
+
|
|
3
|
+
A starter for creating a Vue component library.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
- Install dependencies:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- Run the playground:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run playground
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- Run the unit tests:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- Build the library:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run build
|
|
29
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import * as vue0 from "vue";
|
|
2
|
+
import { ComputedRef, FormHTMLAttributes, MaybeRefOrGetter, Ref, VNodeChild } from "vue";
|
|
3
|
+
import { StandardSchemaV1, StandardSchemaV1 as StandardSchemaV1$1 } from "@standard-schema/spec";
|
|
4
|
+
import { PartialDeep, Paths as Paths$1 } from "type-fest";
|
|
5
|
+
|
|
6
|
+
//#region src/types/shared.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Defines the strategy for when validation checks are executed.
|
|
9
|
+
* - 'lazy': Validation occurs only on blur or form submission.
|
|
10
|
+
* - 'eager': Validation occurs immediately on every value change.
|
|
11
|
+
*/
|
|
12
|
+
type ValidationMode = 'lazy' | 'eager';
|
|
13
|
+
/**
|
|
14
|
+
* Interaction events that can trigger a validation check for a field.
|
|
15
|
+
*/
|
|
16
|
+
type ValidationTriggers = 'blur' | 'change' | 'input' | 'mount' | 'focus';
|
|
17
|
+
/**
|
|
18
|
+
* Represents a validation schema for object-based data structures.
|
|
19
|
+
* Complies with the Standard Schema specification.
|
|
20
|
+
*/
|
|
21
|
+
type ObjectSchema = StandardSchemaV1$1 & {
|
|
22
|
+
'~standard': StandardSchemaV1$1['~standard'] & {
|
|
23
|
+
types?: {
|
|
24
|
+
input: object;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Represents a validation schema for array-based data structures.
|
|
30
|
+
* Complies with the Standard Schema specification.
|
|
31
|
+
*/
|
|
32
|
+
type ArraySchema = StandardSchemaV1$1 & {
|
|
33
|
+
'~standard': StandardSchemaV1$1['~standard'] & {
|
|
34
|
+
types?: {
|
|
35
|
+
input: Array<any>;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/types/utils.d.ts
|
|
41
|
+
/**
|
|
42
|
+
* Constructs a type where all properties of the input type are optional recursively.
|
|
43
|
+
* @template TData The base data structure to transform.
|
|
44
|
+
*/
|
|
45
|
+
type DeepPartial<TData> = PartialDeep<TData, {
|
|
46
|
+
recurseIntoArrays: true;
|
|
47
|
+
allowUndefinedInNonTupleArrays: true;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Constructs a type representing all possible dot-separated paths within an object.
|
|
51
|
+
* @template TReference The object type for which to generate paths.
|
|
52
|
+
*/
|
|
53
|
+
type Paths<TReference> = Paths$1<TReference, {
|
|
54
|
+
maxRecursionDepth: 10;
|
|
55
|
+
bracketNotation: true;
|
|
56
|
+
leavesOnly: false;
|
|
57
|
+
}> | (string & {});
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/types/form.d.ts
|
|
60
|
+
/**
|
|
61
|
+
* Configuration options for initializing a new form instance.
|
|
62
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
63
|
+
*/
|
|
64
|
+
type UseFormOptions<TSchema extends ObjectSchema> = {
|
|
65
|
+
/** Unique form identifier (autogenerated if omitted) */
|
|
66
|
+
id?: string;
|
|
67
|
+
/** The validation schema used to parse and validate form data */
|
|
68
|
+
schema: MaybeRefOrGetter<TSchema>;
|
|
69
|
+
/** Initial form data values */
|
|
70
|
+
initialState?: DeepPartial<StandardSchemaV1$1.InferInput<TSchema>>;
|
|
71
|
+
/** List of validation issues to display initially */
|
|
72
|
+
initialErrors?: StandardSchemaV1$1.Issue[];
|
|
73
|
+
/** Validation behavioral strategy (lazy or eager) */
|
|
74
|
+
mode?: ValidationMode;
|
|
75
|
+
/** Events that trigger individual field validation */
|
|
76
|
+
validateOn?: ValidationTriggers[];
|
|
77
|
+
/**
|
|
78
|
+
* Custom validation logic that runs after schema validation.
|
|
79
|
+
* @param data The validated data from the schema.
|
|
80
|
+
* @returns Success boolean, issues array, or void.
|
|
81
|
+
*/
|
|
82
|
+
onValidate?: (data: StandardSchemaV1$1.InferOutput<TSchema>) => boolean | void | StandardSchemaV1$1.Issue[] | Promise<boolean | void | StandardSchemaV1$1.Issue[]>;
|
|
83
|
+
/** Callback triggered when the form is reset */
|
|
84
|
+
onReset?: () => void;
|
|
85
|
+
/**
|
|
86
|
+
* Callback triggered when form validation fails.
|
|
87
|
+
* @param errors The list of identified issues.
|
|
88
|
+
*/
|
|
89
|
+
onError?: (errors: StandardSchemaV1$1.Issue[]) => void;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* The core state and methods provided by a form instance.
|
|
93
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
94
|
+
*/
|
|
95
|
+
type FormContext<TSchema extends ObjectSchema> = {
|
|
96
|
+
/** Unique form identifier */
|
|
97
|
+
id: string;
|
|
98
|
+
/** Deeply reactive object of field values */
|
|
99
|
+
state: Ref<StandardSchemaV1$1.InferInput<TSchema>>;
|
|
100
|
+
/**
|
|
101
|
+
* Updates the form state.
|
|
102
|
+
* @param _state The new state to apply.
|
|
103
|
+
* @param _validate Re-validate after update (default: true).
|
|
104
|
+
*/
|
|
105
|
+
setState: (_state: DeepPartial<StandardSchemaV1$1.InferInput<TSchema>>, _validate?: boolean) => void;
|
|
106
|
+
/** Baseline form data at moment of initialization */
|
|
107
|
+
initialState: StandardSchemaV1$1.InferInput<TSchema>;
|
|
108
|
+
/** Reactive array of active validation issues */
|
|
109
|
+
errors: Ref<StandardSchemaV1$1.Issue[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Directly sets the form errors.
|
|
112
|
+
* @param _errors The issues to apply.
|
|
113
|
+
*/
|
|
114
|
+
setErrors: (_errors: StandardSchemaV1$1.Issue[]) => void;
|
|
115
|
+
/** Removes all active validation issues */
|
|
116
|
+
clearErrors: () => void;
|
|
117
|
+
/**
|
|
118
|
+
* Retrieves validation issues for a specific field.
|
|
119
|
+
* @param field The path to the field.
|
|
120
|
+
* @returns Array of issues for that field.
|
|
121
|
+
*/
|
|
122
|
+
getFieldErrors: (field: Paths<StandardSchemaV1$1.InferInput<TSchema>>) => StandardSchemaV1$1.Issue[];
|
|
123
|
+
/** List of issues provided during initialization */
|
|
124
|
+
initialErrors: StandardSchemaV1$1.Issue[];
|
|
125
|
+
/** Computed reference to the active validation schema */
|
|
126
|
+
schema: ComputedRef<TSchema>;
|
|
127
|
+
/** Strategy for how/when validation occurs */
|
|
128
|
+
mode: ValidationMode;
|
|
129
|
+
/** Interaction events configured to trigger validation */
|
|
130
|
+
validateOn: ValidationTriggers[];
|
|
131
|
+
/**
|
|
132
|
+
* Validates entire form.
|
|
133
|
+
* @returns Promise resolving to validation results.
|
|
134
|
+
*/
|
|
135
|
+
validate: () => Promise<StandardSchemaV1$1.Result<StandardSchemaV1$1.InferOutput<TSchema>>>;
|
|
136
|
+
/**
|
|
137
|
+
* Validates a single specified field.
|
|
138
|
+
* @param field The path to the field.
|
|
139
|
+
* @returns Promise resolving to validation results.
|
|
140
|
+
*/
|
|
141
|
+
validateField: (field: Paths<StandardSchemaV1$1.InferInput<TSchema>>) => Promise<StandardSchemaV1$1.Result<StandardSchemaV1$1.InferOutput<TSchema>>>;
|
|
142
|
+
/**
|
|
143
|
+
* Reverts state and errors to initial or custom baseline.
|
|
144
|
+
* @param _state Optional partial state baseline.
|
|
145
|
+
* @param _errors Optional issues list baseline.
|
|
146
|
+
* @param _validate Re-validate after resetting (default: false).
|
|
147
|
+
*/
|
|
148
|
+
reset: (_state?: DeepPartial<StandardSchemaV1$1.InferInput<TSchema>>, _errors?: StandardSchemaV1$1.Issue[], _validate?: boolean) => void;
|
|
149
|
+
/** Reactive status of async validation process */
|
|
150
|
+
isValidating: Ref<boolean>;
|
|
151
|
+
/** Computed status of form validity */
|
|
152
|
+
isValid: ComputedRef<boolean>;
|
|
153
|
+
/** Computed status of field interactions */
|
|
154
|
+
isTouched: ComputedRef<boolean>;
|
|
155
|
+
/**
|
|
156
|
+
* Marks a field as interacted with.
|
|
157
|
+
* @param field The path to the field.
|
|
158
|
+
*/
|
|
159
|
+
touchField: (field: Paths<StandardSchemaV1$1.InferInput<TSchema>>) => void;
|
|
160
|
+
/** Marks all fields in the form as touched */
|
|
161
|
+
touchAllFields: () => void;
|
|
162
|
+
/** Reactive set of touched field paths */
|
|
163
|
+
touchedFields: Ref<Set<Paths<StandardSchemaV1$1.InferInput<TSchema>>>>;
|
|
164
|
+
/** Computed status of form modifications */
|
|
165
|
+
isDirty: ComputedRef<boolean>;
|
|
166
|
+
/** Reactive set of dirty field paths */
|
|
167
|
+
dirtyFields: Ref<Set<Paths<StandardSchemaV1$1.InferInput<TSchema>>>>;
|
|
168
|
+
/**
|
|
169
|
+
* Programmatically marks a field as dirty.
|
|
170
|
+
* @param field The path to the field.
|
|
171
|
+
*/
|
|
172
|
+
dirtyField: (field: Paths<StandardSchemaV1$1.InferInput<TSchema>>) => void;
|
|
173
|
+
/** Marks all fields in the form as dirty */
|
|
174
|
+
dirtyAllFields: () => void;
|
|
175
|
+
/**
|
|
176
|
+
* Validates and then triggers form submission.
|
|
177
|
+
* @param event The original form submission event.
|
|
178
|
+
* @param callback Callback executed only if validation passes.
|
|
179
|
+
*/
|
|
180
|
+
submit: (event?: Event, callback?: (data: StandardSchemaV1$1.InferOutput<TSchema>) => void | Promise<void>) => Promise<void>;
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Properties accepted by the Form component.
|
|
184
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
185
|
+
*/
|
|
186
|
+
type FormProps<TSchema extends ObjectSchema> = Pick<FormContext<TSchema>, 'id'> & /* @vue-ignore */Omit<FormHTMLAttributes, 'id'>;
|
|
187
|
+
/**
|
|
188
|
+
* Slots provided by the Form component.
|
|
189
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
190
|
+
*/
|
|
191
|
+
type FormSlots<TSchema extends ObjectSchema> = {
|
|
192
|
+
/** The default slot receives the form context as its scope */
|
|
193
|
+
default: (props: FormContext<TSchema>) => VNodeChild;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Expected return type for the useForm composable.
|
|
197
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
198
|
+
*/
|
|
199
|
+
type UseFormReturn<TSchema extends ObjectSchema> = FormContext<TSchema>;
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/types/field.d.ts
|
|
202
|
+
/** Configuration properties for the Field component */
|
|
203
|
+
type FieldProps = {
|
|
204
|
+
/** The unique name/path identifying the field within the form state */
|
|
205
|
+
name: string;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* State and methods provided to the Field component's scope.
|
|
209
|
+
*/
|
|
210
|
+
type FieldContext = {
|
|
211
|
+
/** The name/path of the field */
|
|
212
|
+
name: string;
|
|
213
|
+
/** Array of validation error messages currently active for this field */
|
|
214
|
+
errors: string[];
|
|
215
|
+
/** Indicates if the field has been interacted with (focus lost) */
|
|
216
|
+
isTouched: boolean;
|
|
217
|
+
/** Indicates if the field's current value differs from its initial value */
|
|
218
|
+
isDirty: boolean;
|
|
219
|
+
/** Indicates if the field currently passes all validation rules */
|
|
220
|
+
isValid: boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Triggers a manual validation check for only this field.
|
|
223
|
+
* @returns Resolve with the validation result object.
|
|
224
|
+
*/
|
|
225
|
+
validate: () => Promise<StandardSchemaV1$1.Result<ObjectSchema>>;
|
|
226
|
+
methods: {
|
|
227
|
+
/** Logic to execute when the field loses focus */
|
|
228
|
+
onBlur: () => void;
|
|
229
|
+
/** Logic to execute when the field's value is committed */
|
|
230
|
+
onChange: () => void;
|
|
231
|
+
/** Logic to execute when the field receives user input */
|
|
232
|
+
onInput: () => void;
|
|
233
|
+
/** Logic to execute when the field gains focus */
|
|
234
|
+
onFocus: () => void;
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Slots provided by the Field component.
|
|
239
|
+
*/
|
|
240
|
+
type FieldSlots = {
|
|
241
|
+
/** The default slot receives the field context for use within templates */
|
|
242
|
+
default: (props: FieldContext) => VNodeChild;
|
|
243
|
+
};
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/types/message.d.ts
|
|
246
|
+
/**
|
|
247
|
+
* Configuration properties for the Message component.
|
|
248
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
249
|
+
*/
|
|
250
|
+
type MessageProps<TSchema extends ObjectSchema> = {
|
|
251
|
+
/** The name/path of the field whose error message should be displayed */
|
|
252
|
+
name: Paths$1<StandardSchemaV1$1.InferInput<TSchema>>;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* State provided to the Message component's scope.
|
|
256
|
+
*/
|
|
257
|
+
type MessageContext = {
|
|
258
|
+
/** The first active validation error message for the specified field */
|
|
259
|
+
message: FieldContext['errors'][number];
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Slots provided by the Message component.
|
|
263
|
+
*/
|
|
264
|
+
type MessageSlots = {
|
|
265
|
+
/** The default slot receives the error message context for custom rendering */
|
|
266
|
+
default: (props: MessageContext) => VNodeChild;
|
|
267
|
+
};
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/components/Form.vue.d.ts
|
|
270
|
+
declare const __VLS_export$3: <TSchema extends ObjectSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$2<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
271
|
+
props: vue0.PublicProps & __VLS_PrettifyLocal$2<FormProps<TSchema>> & (typeof globalThis extends {
|
|
272
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
273
|
+
} ? P : {});
|
|
274
|
+
expose: (exposed: {}) => void;
|
|
275
|
+
attrs: any;
|
|
276
|
+
slots: FormSlots<TSchema>;
|
|
277
|
+
emit: {};
|
|
278
|
+
}>) => vue0.VNode & {
|
|
279
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
280
|
+
};
|
|
281
|
+
declare const _default$2: typeof __VLS_export$3;
|
|
282
|
+
type __VLS_PrettifyLocal$2<T> = (T extends any ? { [K in keyof T]: T[K] } : { [K in keyof T as K]: T[K] }) & {};
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/components/Field.vue.d.ts
|
|
285
|
+
/**
|
|
286
|
+
* Slots provided by the Field component.
|
|
287
|
+
*/
|
|
288
|
+
type __VLS_Slots = FieldSlots;
|
|
289
|
+
declare const __VLS_base: vue0.DefineComponent<FieldProps, {}, {}, {}, {}, vue0.ComponentOptionsMixin, vue0.ComponentOptionsMixin, {}, string, vue0.PublicProps, Readonly<FieldProps> & Readonly<{}>, {}, {}, {}, {}, string, vue0.ComponentProvideOptions, false, {}, any>;
|
|
290
|
+
declare const __VLS_export$2: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
291
|
+
declare const _default$1: typeof __VLS_export$2;
|
|
292
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
293
|
+
new (): {
|
|
294
|
+
$slots: S;
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/components/Message.vue.d.ts
|
|
299
|
+
declare const __VLS_export$1: <TSchema extends ObjectSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$1<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
300
|
+
props: vue0.PublicProps & __VLS_PrettifyLocal$1<MessageProps<TSchema>> & (typeof globalThis extends {
|
|
301
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
302
|
+
} ? P : {});
|
|
303
|
+
expose: (exposed: {}) => void;
|
|
304
|
+
attrs: any;
|
|
305
|
+
slots: MessageSlots;
|
|
306
|
+
emit: {};
|
|
307
|
+
}>) => vue0.VNode & {
|
|
308
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
309
|
+
};
|
|
310
|
+
declare const _default$3: typeof __VLS_export$1;
|
|
311
|
+
type __VLS_PrettifyLocal$1<T> = (T extends any ? { [K in keyof T]: T[K] } : { [K in keyof T as K]: T[K] }) & {};
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/types/array-field.d.ts
|
|
314
|
+
/**
|
|
315
|
+
* Properties accepted by the ArrayField component.
|
|
316
|
+
* @template TSchema The schema of the array field.
|
|
317
|
+
*/
|
|
318
|
+
type ArrayFieldProps<TSchema extends ArraySchema> = {
|
|
319
|
+
/** The unique name/path identifying the array field in the form state */
|
|
320
|
+
name: string;
|
|
321
|
+
/** The specific schema instance used to validate the array items */
|
|
322
|
+
schema: TSchema;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* The core state and methods provided by an ArrayField instance.
|
|
326
|
+
* @template TSchema The schema of the array field.
|
|
327
|
+
*/
|
|
328
|
+
type ArrayFieldContext<TSchema extends ArraySchema> = {
|
|
329
|
+
/**
|
|
330
|
+
* Array of individual field contexts for each item in the collection.
|
|
331
|
+
* Useful for mapping components to array elements.
|
|
332
|
+
*/
|
|
333
|
+
fields: Array<{
|
|
334
|
+
/** A unique identifier for the field (defaults to index) */
|
|
335
|
+
key: string | number;
|
|
336
|
+
/** The 0-based position of the field within the array */
|
|
337
|
+
index: number;
|
|
338
|
+
/** The current value of the field at this index */
|
|
339
|
+
value: StandardSchemaV1$1.InferInput<TSchema>[number];
|
|
340
|
+
/** True if this is first item in the collection */
|
|
341
|
+
first: boolean;
|
|
342
|
+
/** True if this is the last item in the collection */
|
|
343
|
+
last: boolean;
|
|
344
|
+
}>;
|
|
345
|
+
/** Adds a new item to the end of the collection */
|
|
346
|
+
append: (data: StandardSchemaV1$1.InferInput<TSchema>[number]) => void;
|
|
347
|
+
/** Adds a new item to the end of the collection (alias for append) */
|
|
348
|
+
push: (data: StandardSchemaV1$1.InferInput<TSchema>[number]) => void;
|
|
349
|
+
/** Adds a new item to the beginning of the collection */
|
|
350
|
+
prepend: (data: StandardSchemaV1$1.InferInput<TSchema>[number]) => void;
|
|
351
|
+
/**
|
|
352
|
+
* Removes the item at the specified index.
|
|
353
|
+
* @param index The index to remove.
|
|
354
|
+
*/
|
|
355
|
+
remove: (index: number) => void;
|
|
356
|
+
/**
|
|
357
|
+
* Inserts a new item at a specific position.
|
|
358
|
+
* @param index The insertion index.
|
|
359
|
+
* @param data Initial data for the new item.
|
|
360
|
+
*/
|
|
361
|
+
insert: (index: number, data: StandardSchemaV1$1.InferInput<TSchema>[number]) => void;
|
|
362
|
+
/**
|
|
363
|
+
* Replaces data at a specific index.
|
|
364
|
+
* @param index The target index.
|
|
365
|
+
* @param data New data to apply.
|
|
366
|
+
*/
|
|
367
|
+
update: (index: number, data: StandardSchemaV1$1.InferInput<TSchema>[number]) => void;
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* Slots provided by the ArrayField component.
|
|
371
|
+
* @template TSchema The schema of the array field.
|
|
372
|
+
*/
|
|
373
|
+
type ArrayFieldSlots<TSchema extends ArraySchema> = {
|
|
374
|
+
/** The default slot receives the array field context as its scope */
|
|
375
|
+
default: (props: ArrayFieldContext<TSchema>) => VNodeChild;
|
|
376
|
+
};
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/components/ArrayField.vue.d.ts
|
|
379
|
+
declare const __VLS_export: <TArraySchema extends ArraySchema, TObjectSchema extends ObjectSchema = ObjectSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
380
|
+
props: vue0.PublicProps & __VLS_PrettifyLocal<ArrayFieldProps<TArraySchema>> & (typeof globalThis extends {
|
|
381
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
382
|
+
} ? P : {});
|
|
383
|
+
expose: (exposed: {}) => void;
|
|
384
|
+
attrs: any;
|
|
385
|
+
slots: ArrayFieldSlots<TArraySchema>;
|
|
386
|
+
emit: {};
|
|
387
|
+
}>) => vue0.VNode & {
|
|
388
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
389
|
+
};
|
|
390
|
+
declare const _default: typeof __VLS_export;
|
|
391
|
+
type __VLS_PrettifyLocal<T> = (T extends any ? { [K in keyof T]: T[K] } : { [K in keyof T as K]: T[K] }) & {};
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/composables/use-form.d.ts
|
|
394
|
+
/**
|
|
395
|
+
* Initializes a form instance with validation logic, reactive state, and lifecycle methods.
|
|
396
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
397
|
+
* @param options Configuration object for the form behavior and initial state.
|
|
398
|
+
* @returns The gathered form context object.
|
|
399
|
+
*/
|
|
400
|
+
declare function useForm<TSchema extends ObjectSchema>(options: UseFormOptions<TSchema>): UseFormReturn<TSchema>;
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/utils/form-context.d.ts
|
|
403
|
+
/**
|
|
404
|
+
* Locates and returns the active form context associated with a given ID.
|
|
405
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
406
|
+
* @param id The unique identifier of the form to access.
|
|
407
|
+
* @returns The resolved form context object.
|
|
408
|
+
* @throws Error if the context cannot be found in the current component tree.
|
|
409
|
+
*/
|
|
410
|
+
declare function withContext<TSchema extends ObjectSchema>(id: string): FormContext<TSchema>;
|
|
411
|
+
//#endregion
|
|
412
|
+
export { _default as ArrayField, ArraySchema, DeepPartial, _default$1 as Field, FieldContext, FieldProps, FieldSlots, _default$2 as Form, FormContext, FormProps, FormSlots, _default$3 as Message, MessageContext, MessageProps, MessageSlots, ObjectSchema, Paths, type StandardSchemaV1, UseFormOptions, UseFormReturn, ValidationMode, ValidationTriggers, useForm, withContext };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { computed, createElementBlock, createElementVNode, defineComponent, guardReactiveProps, inject, mergeProps, nextTick, normalizeProps, onMounted, openBlock, provide, reactive, ref, renderSlot, toDisplayString, toValue, unref, useId, vShow, withDirectives } from "vue";
|
|
2
|
+
import { getProperty, parsePath, setProperty } from "dot-prop";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/form-context.ts
|
|
5
|
+
/** Internal registry mapping form IDs to their injection keys */
|
|
6
|
+
const formContextKeys = /* @__PURE__ */ new Map();
|
|
7
|
+
/** Injection key for the unique identifier of the current active form */
|
|
8
|
+
const CURRENT_FORM_ID_KEY = Symbol("valid:form:id");
|
|
9
|
+
/**
|
|
10
|
+
* Retrieves an existing injection key or creates a new one for a form ID.
|
|
11
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
12
|
+
* @param id The unique string identifier for the form.
|
|
13
|
+
* @returns A strictly typed InjectionKey for the form context.
|
|
14
|
+
*/
|
|
15
|
+
function getFormContextKey(id) {
|
|
16
|
+
if (!formContextKeys.has(id)) {
|
|
17
|
+
const key = Symbol(`valid:form:${id}:context`);
|
|
18
|
+
formContextKeys.set(id, key);
|
|
19
|
+
}
|
|
20
|
+
return formContextKeys.get(id);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Locates and returns the active form context associated with a given ID.
|
|
24
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
25
|
+
* @param id The unique identifier of the form to access.
|
|
26
|
+
* @returns The resolved form context object.
|
|
27
|
+
* @throws Error if the context cannot be found in the current component tree.
|
|
28
|
+
*/
|
|
29
|
+
function withContext(id) {
|
|
30
|
+
const context = inject(getFormContextKey(id));
|
|
31
|
+
if (!context) throw new Error(`No form context found for form with id "${id}"`);
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/components/Form.vue?vue&type=script&setup=true&lang.ts
|
|
37
|
+
const _hoisted_1 = ["id"];
|
|
38
|
+
/**
|
|
39
|
+
* Provides form context and a structural wrapper for form fields.
|
|
40
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
41
|
+
*/
|
|
42
|
+
var Form_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
43
|
+
__name: "Form",
|
|
44
|
+
props: { id: {
|
|
45
|
+
type: String,
|
|
46
|
+
required: true
|
|
47
|
+
} },
|
|
48
|
+
setup(__props) {
|
|
49
|
+
const props = __props;
|
|
50
|
+
/**
|
|
51
|
+
* Slots provided by the Form component.
|
|
52
|
+
*/
|
|
53
|
+
const form = withContext(props.id);
|
|
54
|
+
provide(CURRENT_FORM_ID_KEY, props.id);
|
|
55
|
+
return (_ctx, _cache) => {
|
|
56
|
+
return openBlock(), createElementBlock("form", mergeProps(_ctx.$attrs, { id: props.id }), [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(unref(form))))], 16, _hoisted_1);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/components/Form.vue
|
|
63
|
+
var Form_default = Form_vue_vue_type_script_setup_true_lang_default;
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/components/Field.vue?vue&type=script&setup=true&lang.ts
|
|
67
|
+
/**
|
|
68
|
+
* Component for individual form fields.
|
|
69
|
+
* Manages field-level state, validation triggers, and events.
|
|
70
|
+
*/
|
|
71
|
+
var Field_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
72
|
+
__name: "Field",
|
|
73
|
+
props: { name: {
|
|
74
|
+
type: String,
|
|
75
|
+
required: true
|
|
76
|
+
} },
|
|
77
|
+
setup(__props) {
|
|
78
|
+
const props = __props;
|
|
79
|
+
/**
|
|
80
|
+
* Slots provided by the Field component.
|
|
81
|
+
*/
|
|
82
|
+
const formID = inject(CURRENT_FORM_ID_KEY);
|
|
83
|
+
if (!formID) throw new Error("Field must be used inside a Form component");
|
|
84
|
+
const { mode, validateOn, touchedFields, dirtyFields, validateField, getFieldErrors, touchField, dirtyField } = withContext(formID);
|
|
85
|
+
/**
|
|
86
|
+
* Triggers validation for this specific field.
|
|
87
|
+
* @returns Promise resolving to the validation result.
|
|
88
|
+
*/
|
|
89
|
+
async function validate() {
|
|
90
|
+
return await validateField(props.name);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The consolidated reactive state exposed to the component's template.
|
|
94
|
+
*/
|
|
95
|
+
const context = reactive({
|
|
96
|
+
name: computed(() => props.name),
|
|
97
|
+
errors: computed(() => getFieldErrors(props.name).map((error) => error.message)),
|
|
98
|
+
isTouched: computed(() => touchedFields.value.has(props.name)),
|
|
99
|
+
isDirty: computed(() => dirtyFields.value.has(props.name)),
|
|
100
|
+
isValid: computed(() => getFieldErrors(props.name).length === 0),
|
|
101
|
+
validate,
|
|
102
|
+
methods: {
|
|
103
|
+
onBlur: function() {
|
|
104
|
+
touchField(props.name);
|
|
105
|
+
if (mode === "eager" || validateOn.includes("blur")) validate();
|
|
106
|
+
},
|
|
107
|
+
onChange: function() {
|
|
108
|
+
dirtyField(props.name);
|
|
109
|
+
if (mode === "eager" || validateOn.includes("change")) validate();
|
|
110
|
+
},
|
|
111
|
+
onInput: function() {
|
|
112
|
+
dirtyField(props.name);
|
|
113
|
+
if (mode === "eager" || validateOn.includes("input")) validate();
|
|
114
|
+
},
|
|
115
|
+
onFocus: function() {
|
|
116
|
+
dirtyField(props.name);
|
|
117
|
+
if (mode === "eager" || validateOn.includes("focus")) validate();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
onMounted(async () => {
|
|
122
|
+
await nextTick();
|
|
123
|
+
if (validateOn.includes("mount")) await validate();
|
|
124
|
+
});
|
|
125
|
+
return (_ctx, _cache) => {
|
|
126
|
+
return renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(context)));
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/components/Field.vue
|
|
133
|
+
var Field_default = Field_vue_vue_type_script_setup_true_lang_default;
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/components/Message.vue?vue&type=script&setup=true&lang.ts
|
|
137
|
+
/**
|
|
138
|
+
* Displays validation error messages for a specific form field.
|
|
139
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
140
|
+
*/
|
|
141
|
+
var Message_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
142
|
+
__name: "Message",
|
|
143
|
+
props: { name: {
|
|
144
|
+
type: null,
|
|
145
|
+
required: true
|
|
146
|
+
} },
|
|
147
|
+
setup(__props) {
|
|
148
|
+
const props = __props;
|
|
149
|
+
/**
|
|
150
|
+
* Slots provided by the Message component.
|
|
151
|
+
*/
|
|
152
|
+
const formID = inject(CURRENT_FORM_ID_KEY);
|
|
153
|
+
if (!formID) throw new Error("Message must be used inside a Form component");
|
|
154
|
+
const { getFieldErrors } = withContext(formID);
|
|
155
|
+
/**
|
|
156
|
+
* The reactive state provided to the component's slot.
|
|
157
|
+
*/
|
|
158
|
+
const context = reactive({ message: computed(() => getFieldErrors(props.name).map((error) => error.message)[0]) });
|
|
159
|
+
return (_ctx, _cache) => {
|
|
160
|
+
return renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(context)), () => [withDirectives(createElementVNode("span", null, toDisplayString(context.message), 513), [[vShow, context.message]])]);
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/components/Message.vue
|
|
167
|
+
var Message_default = Message_vue_vue_type_script_setup_true_lang_default;
|
|
168
|
+
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/components/ArrayField.vue?vue&type=script&setup=true&lang.ts
|
|
171
|
+
/**
|
|
172
|
+
* Component for managing array-based form fields.
|
|
173
|
+
* Provides methods for adding, removing, and updating array items.
|
|
174
|
+
* @template TArraySchema Schema for the array field.
|
|
175
|
+
* @template TObjectSchema Parent form object schema.
|
|
176
|
+
*/
|
|
177
|
+
var ArrayField_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
178
|
+
__name: "ArrayField",
|
|
179
|
+
props: {
|
|
180
|
+
name: {
|
|
181
|
+
type: String,
|
|
182
|
+
required: true
|
|
183
|
+
},
|
|
184
|
+
schema: {
|
|
185
|
+
type: null,
|
|
186
|
+
required: true
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
setup(__props) {
|
|
190
|
+
const props = __props;
|
|
191
|
+
/**
|
|
192
|
+
* Slots provided by the ArrayField component.
|
|
193
|
+
*/
|
|
194
|
+
const formID = inject(CURRENT_FORM_ID_KEY);
|
|
195
|
+
if (!formID) throw new Error("ArrayField must be used inside a Form component");
|
|
196
|
+
const { state, validateField } = withContext(formID);
|
|
197
|
+
/**
|
|
198
|
+
* Reactive bridge between the form state and the specific array field.
|
|
199
|
+
*/
|
|
200
|
+
const arrayState = computed({
|
|
201
|
+
get() {
|
|
202
|
+
const fieldValue = getProperty(state.value, props.name);
|
|
203
|
+
return Array.isArray(fieldValue) ? fieldValue : [];
|
|
204
|
+
},
|
|
205
|
+
set(value) {
|
|
206
|
+
setProperty(state.value, props.name, value);
|
|
207
|
+
validateField(props.name);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
/**
|
|
211
|
+
* Computes individual field contexts for each item in the array.
|
|
212
|
+
*/
|
|
213
|
+
const fields = computed(() => {
|
|
214
|
+
return arrayState.value.map((value, index) => {
|
|
215
|
+
return {
|
|
216
|
+
key: index,
|
|
217
|
+
index,
|
|
218
|
+
value,
|
|
219
|
+
first: index === 0,
|
|
220
|
+
last: index === arrayState.value.length - 1
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
/**
|
|
225
|
+
* Adds a new item to the end of the array.
|
|
226
|
+
* @param data The initial data for the new item.
|
|
227
|
+
*/
|
|
228
|
+
function append(data) {
|
|
229
|
+
arrayState.value = [...arrayState.value, data];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Adds a new item to the beginning of the array.
|
|
233
|
+
* @param data The initial data for the new item.
|
|
234
|
+
*/
|
|
235
|
+
function prepend(data) {
|
|
236
|
+
arrayState.value = [data, ...arrayState.value];
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Removes the item at the specified index.
|
|
240
|
+
* @param index The index of the item to remove.
|
|
241
|
+
*/
|
|
242
|
+
function remove(index) {
|
|
243
|
+
const newArray = [...arrayState.value];
|
|
244
|
+
newArray.splice(index, 1);
|
|
245
|
+
arrayState.value = newArray;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Inserts a new item at a specific index.
|
|
249
|
+
* @param index The insertion index.
|
|
250
|
+
* @param data The initial data for the new item.
|
|
251
|
+
*/
|
|
252
|
+
function insert(index, data) {
|
|
253
|
+
const newArray = [...arrayState.value];
|
|
254
|
+
newArray.splice(index, 0, data);
|
|
255
|
+
arrayState.value = newArray;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Replaces the data at a specific index.
|
|
259
|
+
* @param index The target index.
|
|
260
|
+
* @param data The new data to apply.
|
|
261
|
+
*/
|
|
262
|
+
function update(index, data) {
|
|
263
|
+
const newArray = [...arrayState.value];
|
|
264
|
+
newArray[index] = data;
|
|
265
|
+
arrayState.value = newArray;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Reactive context object exposed to the ArrayField slot.
|
|
269
|
+
*/
|
|
270
|
+
const context = reactive({
|
|
271
|
+
fields,
|
|
272
|
+
append,
|
|
273
|
+
push: append,
|
|
274
|
+
prepend,
|
|
275
|
+
remove,
|
|
276
|
+
insert,
|
|
277
|
+
update
|
|
278
|
+
});
|
|
279
|
+
return (_ctx, _cache) => {
|
|
280
|
+
return renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(context)));
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/components/ArrayField.vue
|
|
287
|
+
var ArrayField_default = ArrayField_vue_vue_type_script_setup_true_lang_default;
|
|
288
|
+
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/utils/helpers.ts
|
|
291
|
+
/**
|
|
292
|
+
* Normalizes a validation path segment into a standard property key.
|
|
293
|
+
* @param segment The path segment to normalize.
|
|
294
|
+
* @returns The normalized key.
|
|
295
|
+
*/
|
|
296
|
+
function normalizeSegment(segment) {
|
|
297
|
+
if (typeof segment === "object" && segment !== null && "key" in segment) return segment.key;
|
|
298
|
+
return segment;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Checks if a validation issue path matches a target field path.
|
|
302
|
+
* @param issuePath The path array from the validation issue.
|
|
303
|
+
* @param targetPath The normalized path to compare against.
|
|
304
|
+
* @returns True if the paths are equivalent.
|
|
305
|
+
*/
|
|
306
|
+
function isIssuePathEqual(issuePath, targetPath) {
|
|
307
|
+
if (!issuePath) return false;
|
|
308
|
+
if (issuePath.length !== targetPath.length) return false;
|
|
309
|
+
return issuePath.every((segment, i) => {
|
|
310
|
+
const normalizedSegment = normalizeSegment(segment);
|
|
311
|
+
const targetSegment = targetPath[i];
|
|
312
|
+
if (typeof normalizedSegment === "number" || typeof targetSegment === "number") return Number(normalizedSegment) === Number(targetSegment);
|
|
313
|
+
return normalizedSegment === targetSegment;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Recursively retrieves all reachable paths of an object as dot-separated strings.
|
|
318
|
+
* @param object The object to traverse.
|
|
319
|
+
* @param prefix An optional prefix to prepended to each generated path.
|
|
320
|
+
* @returns An array of string paths representing the object's structure.
|
|
321
|
+
*/
|
|
322
|
+
function getObjectPaths(object, prefix = "") {
|
|
323
|
+
let paths = [];
|
|
324
|
+
if (typeof object !== "object" || object === null) return paths;
|
|
325
|
+
for (const key in object) if (Object.prototype.hasOwnProperty.call(object, key)) {
|
|
326
|
+
const value = object[key];
|
|
327
|
+
const isArray = Array.isArray(object);
|
|
328
|
+
const isInt = /^\d+$/.test(key);
|
|
329
|
+
let currentKey = key;
|
|
330
|
+
if (isArray && isInt) currentKey = `[${key}]`;
|
|
331
|
+
const path = prefix ? prefix.endsWith("]") || currentKey.startsWith("[") ? `${prefix}${currentKey}` : `${prefix}.${currentKey}` : currentKey;
|
|
332
|
+
paths.push(path);
|
|
333
|
+
if (typeof value === "object" && value !== null) paths = paths.concat(getObjectPaths(value, path));
|
|
334
|
+
}
|
|
335
|
+
return paths;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/composables/use-form.ts
|
|
340
|
+
/**
|
|
341
|
+
* Initializes a form instance with validation logic, reactive state, and lifecycle methods.
|
|
342
|
+
* @template TSchema The validation schema type derived from ObjectSchema.
|
|
343
|
+
* @param options Configuration object for the form behavior and initial state.
|
|
344
|
+
* @returns The gathered form context object.
|
|
345
|
+
*/
|
|
346
|
+
function useForm(options) {
|
|
347
|
+
const { id = useId(), schema: _schema, initialErrors: _initialErrors = [], initialState: _initialState = {}, mode = "eager", validateOn = [
|
|
348
|
+
"blur",
|
|
349
|
+
"input",
|
|
350
|
+
"change"
|
|
351
|
+
], onValidate, onReset, onError } = options;
|
|
352
|
+
/** Active validation schema reference */
|
|
353
|
+
const schema = computed(() => toValue(_schema));
|
|
354
|
+
/** Baseline reactive state at creation */
|
|
355
|
+
const initialState = structuredClone(_initialState);
|
|
356
|
+
/** Baseline validation issues at creation */
|
|
357
|
+
const initialErrors = structuredClone(_initialErrors);
|
|
358
|
+
/** Internal state set by last custom reset */
|
|
359
|
+
const onResetState = ref(null);
|
|
360
|
+
/** Internal errors set by last custom reset */
|
|
361
|
+
const onResetErrors = ref(null);
|
|
362
|
+
/** The unified reactive context for the form instance */
|
|
363
|
+
const context = {
|
|
364
|
+
id,
|
|
365
|
+
schema,
|
|
366
|
+
initialState,
|
|
367
|
+
initialErrors,
|
|
368
|
+
mode,
|
|
369
|
+
validateOn,
|
|
370
|
+
state: ref(structuredClone(initialState)),
|
|
371
|
+
errors: ref([...initialErrors]),
|
|
372
|
+
isValidating: ref(false),
|
|
373
|
+
touchedFields: ref(/* @__PURE__ */ new Set()),
|
|
374
|
+
dirtyFields: ref(/* @__PURE__ */ new Set()),
|
|
375
|
+
isValid: computed(() => context.errors.value.length === 0),
|
|
376
|
+
isTouched: computed(() => context.touchedFields.value.size > 0),
|
|
377
|
+
isDirty: computed(() => context.dirtyFields.value.size > 0),
|
|
378
|
+
async validate() {
|
|
379
|
+
context.isValidating.value = true;
|
|
380
|
+
try {
|
|
381
|
+
const result = await context.schema.value["~standard"].validate(context.state.value);
|
|
382
|
+
if (result.issues) {
|
|
383
|
+
context.errors.value = [...result.issues];
|
|
384
|
+
onError?.([...result.issues]);
|
|
385
|
+
return { issues: result.issues };
|
|
386
|
+
}
|
|
387
|
+
if (onValidate) {
|
|
388
|
+
const customResult = await onValidate(result.value);
|
|
389
|
+
if (customResult === false) {
|
|
390
|
+
const formLevelError = {
|
|
391
|
+
message: "Validation failed",
|
|
392
|
+
path: []
|
|
393
|
+
};
|
|
394
|
+
context.errors.value = [formLevelError];
|
|
395
|
+
onError?.([formLevelError]);
|
|
396
|
+
return { issues: [formLevelError] };
|
|
397
|
+
}
|
|
398
|
+
if (Array.isArray(customResult) && customResult.length > 0) {
|
|
399
|
+
context.errors.value = customResult;
|
|
400
|
+
onError?.(customResult);
|
|
401
|
+
return { issues: customResult };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
context.errors.value = [];
|
|
405
|
+
return { value: result.value };
|
|
406
|
+
} finally {
|
|
407
|
+
context.isValidating.value = false;
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
async validateField(path) {
|
|
411
|
+
context.isValidating.value = true;
|
|
412
|
+
try {
|
|
413
|
+
const result = await context.schema.value["~standard"].validate(context.state.value);
|
|
414
|
+
const pathArray = parsePath(path);
|
|
415
|
+
context.errors.value = context.errors.value.filter((error) => !isIssuePathEqual(error.path, pathArray));
|
|
416
|
+
if (result.issues) {
|
|
417
|
+
const errorsForPath = result.issues.filter((error) => isIssuePathEqual(error.path, pathArray));
|
|
418
|
+
if (errorsForPath.length > 0) {
|
|
419
|
+
context.errors.value = [...context.errors.value, ...errorsForPath];
|
|
420
|
+
return { issues: errorsForPath };
|
|
421
|
+
}
|
|
422
|
+
return { value: getProperty(context.state.value, path) };
|
|
423
|
+
}
|
|
424
|
+
return { value: getProperty(result.value, path) };
|
|
425
|
+
} finally {
|
|
426
|
+
context.isValidating.value = false;
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
reset(_state, _errors, _validate = false) {
|
|
430
|
+
if (_state) {
|
|
431
|
+
onResetState.value = structuredClone(_state);
|
|
432
|
+
context.state.value = structuredClone(_state);
|
|
433
|
+
} else {
|
|
434
|
+
onResetState.value = null;
|
|
435
|
+
context.state.value = structuredClone(initialState);
|
|
436
|
+
}
|
|
437
|
+
if (_errors) {
|
|
438
|
+
onResetErrors.value = structuredClone(_errors);
|
|
439
|
+
context.errors.value = [..._errors];
|
|
440
|
+
} else {
|
|
441
|
+
onResetErrors.value = null;
|
|
442
|
+
context.errors.value = [...initialErrors];
|
|
443
|
+
}
|
|
444
|
+
context.touchedFields.value.clear();
|
|
445
|
+
context.dirtyFields.value.clear();
|
|
446
|
+
onReset?.();
|
|
447
|
+
if (_validate) context.validate();
|
|
448
|
+
},
|
|
449
|
+
setState(_state, _validate = true) {
|
|
450
|
+
context.state.value = {
|
|
451
|
+
...context.state.value,
|
|
452
|
+
..._state
|
|
453
|
+
};
|
|
454
|
+
getObjectPaths(_state).forEach((path) => {
|
|
455
|
+
context.dirtyFields.value.add(path);
|
|
456
|
+
context.touchedFields.value.add(path);
|
|
457
|
+
context.validateField(path);
|
|
458
|
+
});
|
|
459
|
+
if (_validate) context.validate();
|
|
460
|
+
},
|
|
461
|
+
setErrors(_errors) {
|
|
462
|
+
context.errors.value = [...context.errors.value, ..._errors];
|
|
463
|
+
},
|
|
464
|
+
clearErrors() {
|
|
465
|
+
context.errors.value = [];
|
|
466
|
+
},
|
|
467
|
+
getFieldErrors(field) {
|
|
468
|
+
const pathArray = parsePath(field);
|
|
469
|
+
return context.errors.value.filter((error) => isIssuePathEqual(error.path, pathArray));
|
|
470
|
+
},
|
|
471
|
+
touchField(field) {
|
|
472
|
+
context.touchedFields.value.add(field);
|
|
473
|
+
},
|
|
474
|
+
touchAllFields() {
|
|
475
|
+
const paths = getObjectPaths(context.state.value);
|
|
476
|
+
context.touchedFields.value = new Set(paths);
|
|
477
|
+
},
|
|
478
|
+
dirtyField(field) {
|
|
479
|
+
context.dirtyFields.value.add(field);
|
|
480
|
+
},
|
|
481
|
+
dirtyAllFields() {
|
|
482
|
+
const paths = getObjectPaths(context.state.value);
|
|
483
|
+
context.dirtyFields.value = new Set(paths);
|
|
484
|
+
},
|
|
485
|
+
async submit(event, callback) {
|
|
486
|
+
event?.preventDefault();
|
|
487
|
+
event?.stopPropagation();
|
|
488
|
+
const result = await context.validate();
|
|
489
|
+
if (result.issues) {
|
|
490
|
+
context.errors.value = [...result.issues];
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
context.touchAllFields();
|
|
494
|
+
context.dirtyAllFields();
|
|
495
|
+
if (callback) await callback(result.value);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
provide(getFormContextKey(id), context);
|
|
499
|
+
return context;
|
|
500
|
+
}
|
|
501
|
+
var use_form_default = useForm;
|
|
502
|
+
|
|
503
|
+
//#endregion
|
|
504
|
+
export { ArrayField_default as ArrayField, Field_default as Field, Form_default as Form, Message_default as Message, use_form_default as useForm, withContext };
|
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "notform",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"version": "1.0.0-alpha.3",
|
|
9
|
+
"description": "Smooth Vue Forms Validation",
|
|
10
|
+
"author": "Favour Emeka <favorodera@gmail.com>",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"homepage": "https://github.com/favorodera/notform#readme",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/favorodera/notform.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/favorodera/notform/issues"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./dist/index.js",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"valibot": "^1.2.0",
|
|
33
|
+
"vue": "^3.0.0",
|
|
34
|
+
"yup": "^1.6.0",
|
|
35
|
+
"zod": "^4.3.5"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"valibot": {
|
|
39
|
+
"optional": true
|
|
40
|
+
},
|
|
41
|
+
"zod": {
|
|
42
|
+
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"yup": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@stylistic/eslint-plugin": "^5.7.0",
|
|
50
|
+
"@types/node": "^25.0.3",
|
|
51
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
52
|
+
"@vitest/browser-playwright": "^4.0.16",
|
|
53
|
+
"@vitest/eslint-plugin": "^1.6.6",
|
|
54
|
+
"@vitest/ui": "^4.0.17",
|
|
55
|
+
"@vue/eslint-config-typescript": "^14.6.0",
|
|
56
|
+
"eslint": "^9.39.2",
|
|
57
|
+
"eslint-plugin-vue": "^10.7.0",
|
|
58
|
+
"playwright": "^1.57.0",
|
|
59
|
+
"tsdown": "^0.18.1",
|
|
60
|
+
"type-fest": "^5.4.1",
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"unplugin-vue": "^7.1.0",
|
|
63
|
+
"vite": "^7.3.0",
|
|
64
|
+
"vitest": "^4.0.16",
|
|
65
|
+
"vitest-browser-vue": "^2.0.1",
|
|
66
|
+
"vue": "^3.5.25",
|
|
67
|
+
"vue-tsc": "^3.2.2"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@standard-schema/spec": "^1.1.0",
|
|
71
|
+
"dot-prop": "^10.1.0"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=24.0.0"
|
|
75
|
+
},
|
|
76
|
+
"keywords": [
|
|
77
|
+
"notform",
|
|
78
|
+
"vue",
|
|
79
|
+
"form",
|
|
80
|
+
"vue form",
|
|
81
|
+
"validator",
|
|
82
|
+
"vue form validator"
|
|
83
|
+
],
|
|
84
|
+
"scripts": {
|
|
85
|
+
"build": "tsdown",
|
|
86
|
+
"dev": "tsdown --watch",
|
|
87
|
+
"test": "vitest",
|
|
88
|
+
"test:watch": "vitest watch --ui",
|
|
89
|
+
"typecheck": "vue-tsc --noEmit",
|
|
90
|
+
"lint": "eslint . --fix"
|
|
91
|
+
}
|
|
92
|
+
}
|