@vuehookform/core 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 +312 -0
- package/dist/context.d.ts +38 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/core/formContext.d.ts +35 -0
- package/dist/core/formContext.d.ts.map +1 -0
- package/dist/core/useFieldArray.d.ts +9 -0
- package/dist/core/useFieldArray.d.ts.map +1 -0
- package/dist/core/useFieldRegistration.d.ts +10 -0
- package/dist/core/useFieldRegistration.d.ts.map +1 -0
- package/dist/core/useValidation.d.ts +8 -0
- package/dist/core/useValidation.d.ts.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/types.d.ts +290 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useController.d.ts +64 -0
- package/dist/useController.d.ts.map +1 -0
- package/dist/useForm.d.ts +21 -0
- package/dist/useForm.d.ts.map +1 -0
- package/dist/useFormState.d.ts +40 -0
- package/dist/useFormState.d.ts.map +1 -0
- package/dist/useWatch.d.ts +41 -0
- package/dist/useWatch.d.ts.map +1 -0
- package/dist/utils/paths.d.ts +37 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/vuehookform.cjs +644 -0
- package/dist/vuehookform.cjs.map +1 -0
- package/dist/vuehookform.js +637 -0
- package/dist/vuehookform.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { computed, inject, provide, reactive, ref, shallowRef } from "vue";
|
|
2
|
+
function get(obj, path) {
|
|
3
|
+
if (!path) return obj;
|
|
4
|
+
const keys = path.split(".");
|
|
5
|
+
let result = obj;
|
|
6
|
+
for (const key of keys) {
|
|
7
|
+
if (result === null || result === void 0) return;
|
|
8
|
+
result = result[key];
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
function set(obj, path, value) {
|
|
13
|
+
if (!path) return;
|
|
14
|
+
const keys = path.split(".");
|
|
15
|
+
const UNSAFE_KEYS = [
|
|
16
|
+
"__proto__",
|
|
17
|
+
"constructor",
|
|
18
|
+
"prototype"
|
|
19
|
+
];
|
|
20
|
+
if (keys.some((k) => UNSAFE_KEYS.includes(k))) return;
|
|
21
|
+
const lastKey = keys.pop();
|
|
22
|
+
let current = obj;
|
|
23
|
+
for (const key of keys) {
|
|
24
|
+
if (!(key in current) || typeof current[key] !== "object") {
|
|
25
|
+
const nextKey = keys[keys.indexOf(key) + 1];
|
|
26
|
+
current[key] = nextKey && /^\d+$/.test(nextKey) ? [] : {};
|
|
27
|
+
}
|
|
28
|
+
current = current[key];
|
|
29
|
+
}
|
|
30
|
+
current[lastKey] = value;
|
|
31
|
+
}
|
|
32
|
+
function unset(obj, path) {
|
|
33
|
+
if (!path) return;
|
|
34
|
+
const keys = path.split(".");
|
|
35
|
+
const lastKey = keys.pop();
|
|
36
|
+
let current = obj;
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
if (!(key in current)) return;
|
|
39
|
+
current = current[key];
|
|
40
|
+
}
|
|
41
|
+
delete current[lastKey];
|
|
42
|
+
}
|
|
43
|
+
var idCounter = 0;
|
|
44
|
+
function generateId() {
|
|
45
|
+
return `field_${Date.now()}_${idCounter++}`;
|
|
46
|
+
}
|
|
47
|
+
function createFormContext(options) {
|
|
48
|
+
const formData = reactive({});
|
|
49
|
+
const defaultValues = reactive({});
|
|
50
|
+
const isAsyncDefaults = typeof options.defaultValues === "function";
|
|
51
|
+
const isLoading = ref(isAsyncDefaults);
|
|
52
|
+
if (isAsyncDefaults) {
|
|
53
|
+
const asyncFn = options.defaultValues;
|
|
54
|
+
asyncFn().then((values) => {
|
|
55
|
+
Object.assign(defaultValues, values);
|
|
56
|
+
Object.assign(formData, values);
|
|
57
|
+
isLoading.value = false;
|
|
58
|
+
}).catch((error) => {
|
|
59
|
+
console.error("Failed to load async default values:", error);
|
|
60
|
+
isLoading.value = false;
|
|
61
|
+
});
|
|
62
|
+
} else if (options.defaultValues) {
|
|
63
|
+
Object.assign(defaultValues, options.defaultValues);
|
|
64
|
+
Object.assign(formData, defaultValues);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
formData,
|
|
68
|
+
defaultValues,
|
|
69
|
+
errors: shallowRef({}),
|
|
70
|
+
touchedFields: ref({}),
|
|
71
|
+
dirtyFields: ref({}),
|
|
72
|
+
isSubmitting: ref(false),
|
|
73
|
+
isLoading,
|
|
74
|
+
submitCount: ref(0),
|
|
75
|
+
fieldRefs: /* @__PURE__ */ new Map(),
|
|
76
|
+
fieldOptions: /* @__PURE__ */ new Map(),
|
|
77
|
+
fieldArrays: /* @__PURE__ */ new Map(),
|
|
78
|
+
debounceTimers: /* @__PURE__ */ new Map(),
|
|
79
|
+
validationRequestIds: /* @__PURE__ */ new Map(),
|
|
80
|
+
options
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function clearFieldErrors(errors, fieldPath) {
|
|
84
|
+
const newErrors = { ...errors };
|
|
85
|
+
for (const key of Object.keys(newErrors)) if (key === fieldPath || key.startsWith(`${fieldPath}.`)) delete newErrors[key];
|
|
86
|
+
return newErrors;
|
|
87
|
+
}
|
|
88
|
+
function groupErrorsByPath(issues) {
|
|
89
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const issue of issues) {
|
|
91
|
+
const path = issue.path.join(".");
|
|
92
|
+
const existing = grouped.get(path) || [];
|
|
93
|
+
existing.push({
|
|
94
|
+
type: issue.code,
|
|
95
|
+
message: issue.message
|
|
96
|
+
});
|
|
97
|
+
grouped.set(path, existing);
|
|
98
|
+
}
|
|
99
|
+
return grouped;
|
|
100
|
+
}
|
|
101
|
+
function createFieldError(errors) {
|
|
102
|
+
if (errors.length === 1) return errors[0].message;
|
|
103
|
+
const types = {};
|
|
104
|
+
for (const err of errors) {
|
|
105
|
+
const existing = types[err.type];
|
|
106
|
+
if (existing) types[err.type] = Array.isArray(existing) ? [...existing, err.message] : [existing, err.message];
|
|
107
|
+
else types[err.type] = err.message;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
type: errors[0].type,
|
|
111
|
+
message: errors[0].message,
|
|
112
|
+
types
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function createValidation(ctx) {
|
|
116
|
+
async function validate(fieldPath) {
|
|
117
|
+
const result = await ctx.options.schema.safeParseAsync(ctx.formData);
|
|
118
|
+
if (result.success) {
|
|
119
|
+
if (fieldPath) ctx.errors.value = clearFieldErrors(ctx.errors.value, fieldPath);
|
|
120
|
+
else ctx.errors.value = {};
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
const zodErrors = result.error.issues;
|
|
124
|
+
if (fieldPath) {
|
|
125
|
+
const fieldErrors = zodErrors.filter((issue) => {
|
|
126
|
+
const path = issue.path.join(".");
|
|
127
|
+
return path === fieldPath || path.startsWith(`${fieldPath}.`);
|
|
128
|
+
});
|
|
129
|
+
if (fieldErrors.length === 0) {
|
|
130
|
+
ctx.errors.value = clearFieldErrors(ctx.errors.value, fieldPath);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
let newErrors$1 = clearFieldErrors(ctx.errors.value, fieldPath);
|
|
134
|
+
const grouped$1 = groupErrorsByPath(fieldErrors);
|
|
135
|
+
for (const [path, errors] of grouped$1) set(newErrors$1, path, createFieldError(errors));
|
|
136
|
+
ctx.errors.value = newErrors$1;
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const newErrors = {};
|
|
140
|
+
const grouped = groupErrorsByPath(zodErrors);
|
|
141
|
+
for (const [path, errors] of grouped) set(newErrors, path, createFieldError(errors));
|
|
142
|
+
ctx.errors.value = newErrors;
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return { validate };
|
|
146
|
+
}
|
|
147
|
+
function createFieldRegistration(ctx, validate) {
|
|
148
|
+
function register(name, registerOptions) {
|
|
149
|
+
const fieldRef = ref(null);
|
|
150
|
+
ctx.fieldRefs.set(name, fieldRef);
|
|
151
|
+
if (registerOptions) ctx.fieldOptions.set(name, registerOptions);
|
|
152
|
+
if (get(ctx.formData, name) === void 0) {
|
|
153
|
+
const defaultValue = get(ctx.defaultValues, name);
|
|
154
|
+
if (defaultValue !== void 0) set(ctx.formData, name, defaultValue);
|
|
155
|
+
}
|
|
156
|
+
const runCustomValidation = async (fieldName, value, requestId) => {
|
|
157
|
+
const fieldOpts = ctx.fieldOptions.get(fieldName);
|
|
158
|
+
if (!fieldOpts?.validate || fieldOpts.disabled) return;
|
|
159
|
+
const error = await fieldOpts.validate(value);
|
|
160
|
+
if (requestId !== ctx.validationRequestIds.get(fieldName)) return;
|
|
161
|
+
if (error) ctx.errors.value = {
|
|
162
|
+
...ctx.errors.value,
|
|
163
|
+
[fieldName]: error
|
|
164
|
+
};
|
|
165
|
+
else {
|
|
166
|
+
const newErrors = { ...ctx.errors.value };
|
|
167
|
+
delete newErrors[fieldName];
|
|
168
|
+
ctx.errors.value = newErrors;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const onInput = async (e) => {
|
|
172
|
+
const target = e.target;
|
|
173
|
+
const value = target.type === "checkbox" ? target.checked : target.value;
|
|
174
|
+
set(ctx.formData, name, value);
|
|
175
|
+
ctx.dirtyFields.value = {
|
|
176
|
+
...ctx.dirtyFields.value,
|
|
177
|
+
[name]: true
|
|
178
|
+
};
|
|
179
|
+
if (ctx.options.mode === "onChange" || ctx.options.mode === "onTouched" && ctx.touchedFields.value[name] || ctx.touchedFields.value[name] && ctx.options.reValidateMode === "onChange") await validate(name);
|
|
180
|
+
const fieldOpts = ctx.fieldOptions.get(name);
|
|
181
|
+
if (fieldOpts?.validate && !fieldOpts.disabled) {
|
|
182
|
+
const requestId = Date.now() + Math.random();
|
|
183
|
+
ctx.validationRequestIds.set(name, requestId);
|
|
184
|
+
const debounceMs = fieldOpts.validateDebounce || 0;
|
|
185
|
+
if (debounceMs > 0) {
|
|
186
|
+
const existingTimer = ctx.debounceTimers.get(name);
|
|
187
|
+
if (existingTimer) clearTimeout(existingTimer);
|
|
188
|
+
const timer = setTimeout(() => {
|
|
189
|
+
ctx.debounceTimers.delete(name);
|
|
190
|
+
runCustomValidation(name, value, requestId);
|
|
191
|
+
}, debounceMs);
|
|
192
|
+
ctx.debounceTimers.set(name, timer);
|
|
193
|
+
} else await runCustomValidation(name, value, requestId);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const onBlur = async (_e) => {
|
|
197
|
+
ctx.touchedFields.value = {
|
|
198
|
+
...ctx.touchedFields.value,
|
|
199
|
+
[name]: true
|
|
200
|
+
};
|
|
201
|
+
if (ctx.options.mode === "onBlur" || ctx.options.mode === "onTouched" || ctx.submitCount.value > 0 && ctx.options.reValidateMode === "onBlur") await validate(name);
|
|
202
|
+
};
|
|
203
|
+
const refCallback = (el) => {
|
|
204
|
+
const previousEl = fieldRef.value;
|
|
205
|
+
fieldRef.value = el;
|
|
206
|
+
if (el && !registerOptions?.controlled && el instanceof HTMLInputElement) {
|
|
207
|
+
const value = get(ctx.formData, name);
|
|
208
|
+
if (value !== void 0) if (el.type === "checkbox") el.checked = value;
|
|
209
|
+
else el.value = value;
|
|
210
|
+
}
|
|
211
|
+
if (previousEl && !el) {
|
|
212
|
+
if (registerOptions?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
|
|
213
|
+
unset(ctx.formData, name);
|
|
214
|
+
const newErrors = { ...ctx.errors.value };
|
|
215
|
+
delete newErrors[name];
|
|
216
|
+
ctx.errors.value = newErrors;
|
|
217
|
+
const newTouched = { ...ctx.touchedFields.value };
|
|
218
|
+
delete newTouched[name];
|
|
219
|
+
ctx.touchedFields.value = newTouched;
|
|
220
|
+
const newDirty = { ...ctx.dirtyFields.value };
|
|
221
|
+
delete newDirty[name];
|
|
222
|
+
ctx.dirtyFields.value = newDirty;
|
|
223
|
+
ctx.fieldRefs.delete(name);
|
|
224
|
+
ctx.fieldOptions.delete(name);
|
|
225
|
+
const timer = ctx.debounceTimers.get(name);
|
|
226
|
+
if (timer) {
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
ctx.debounceTimers.delete(name);
|
|
229
|
+
}
|
|
230
|
+
ctx.validationRequestIds.delete(name);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
name,
|
|
236
|
+
ref: refCallback,
|
|
237
|
+
onInput,
|
|
238
|
+
onBlur,
|
|
239
|
+
...registerOptions?.controlled && { value: computed({
|
|
240
|
+
get: () => get(ctx.formData, name),
|
|
241
|
+
set: (val) => {
|
|
242
|
+
set(ctx.formData, name, val);
|
|
243
|
+
ctx.dirtyFields.value = {
|
|
244
|
+
...ctx.dirtyFields.value,
|
|
245
|
+
[name]: true
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}) }
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function unregister(name) {
|
|
252
|
+
ctx.fieldRefs.delete(name);
|
|
253
|
+
ctx.fieldOptions.delete(name);
|
|
254
|
+
const timer = ctx.debounceTimers.get(name);
|
|
255
|
+
if (timer) {
|
|
256
|
+
clearTimeout(timer);
|
|
257
|
+
ctx.debounceTimers.delete(name);
|
|
258
|
+
}
|
|
259
|
+
ctx.validationRequestIds.delete(name);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
register,
|
|
263
|
+
unregister
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function createFieldArrayManager(ctx, validate) {
|
|
267
|
+
function fields(name) {
|
|
268
|
+
let fieldArray = ctx.fieldArrays.get(name);
|
|
269
|
+
if (!fieldArray) {
|
|
270
|
+
const existingValues = get(ctx.formData, name) || [];
|
|
271
|
+
fieldArray = {
|
|
272
|
+
items: ref([]),
|
|
273
|
+
values: existingValues
|
|
274
|
+
};
|
|
275
|
+
ctx.fieldArrays.set(name, fieldArray);
|
|
276
|
+
if (!get(ctx.formData, name)) set(ctx.formData, name, []);
|
|
277
|
+
}
|
|
278
|
+
const fa = fieldArray;
|
|
279
|
+
const createItem = (key) => ({
|
|
280
|
+
key,
|
|
281
|
+
get index() {
|
|
282
|
+
return fa.items.value.findIndex((item) => item.key === key);
|
|
283
|
+
},
|
|
284
|
+
remove() {
|
|
285
|
+
const currentIndex = fa.items.value.findIndex((item) => item.key === key);
|
|
286
|
+
if (currentIndex !== -1) removeAt(currentIndex);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
if (fa.items.value.length === 0 && fa.values.length > 0) fa.items.value = fa.values.map(() => createItem(generateId()));
|
|
290
|
+
const append = (value) => {
|
|
291
|
+
const newValues = [...get(ctx.formData, name) || [], value];
|
|
292
|
+
set(ctx.formData, name, newValues);
|
|
293
|
+
fa.items.value = [...fa.items.value, createItem(generateId())];
|
|
294
|
+
ctx.dirtyFields.value = {
|
|
295
|
+
...ctx.dirtyFields.value,
|
|
296
|
+
[name]: true
|
|
297
|
+
};
|
|
298
|
+
if (ctx.options.mode === "onChange") validate(name);
|
|
299
|
+
};
|
|
300
|
+
const prepend = (value) => {
|
|
301
|
+
insert(0, value);
|
|
302
|
+
};
|
|
303
|
+
const update = (index, value) => {
|
|
304
|
+
const currentValues = get(ctx.formData, name) || [];
|
|
305
|
+
if (index < 0 || index >= currentValues.length) return;
|
|
306
|
+
const newValues = [...currentValues];
|
|
307
|
+
newValues[index] = value;
|
|
308
|
+
set(ctx.formData, name, newValues);
|
|
309
|
+
ctx.dirtyFields.value = {
|
|
310
|
+
...ctx.dirtyFields.value,
|
|
311
|
+
[name]: true
|
|
312
|
+
};
|
|
313
|
+
if (ctx.options.mode === "onChange") validate(name);
|
|
314
|
+
};
|
|
315
|
+
const removeAt = (index) => {
|
|
316
|
+
const newValues = (get(ctx.formData, name) || []).filter((_, i) => i !== index);
|
|
317
|
+
set(ctx.formData, name, newValues);
|
|
318
|
+
const keyToRemove = fa.items.value[index]?.key;
|
|
319
|
+
fa.items.value = fa.items.value.filter((item) => item.key !== keyToRemove);
|
|
320
|
+
ctx.dirtyFields.value = {
|
|
321
|
+
...ctx.dirtyFields.value,
|
|
322
|
+
[name]: true
|
|
323
|
+
};
|
|
324
|
+
if (ctx.options.mode === "onChange") validate(name);
|
|
325
|
+
};
|
|
326
|
+
const insert = (index, value) => {
|
|
327
|
+
const currentValues = get(ctx.formData, name) || [];
|
|
328
|
+
const newValues = [
|
|
329
|
+
...currentValues.slice(0, index),
|
|
330
|
+
value,
|
|
331
|
+
...currentValues.slice(index)
|
|
332
|
+
];
|
|
333
|
+
set(ctx.formData, name, newValues);
|
|
334
|
+
const newItem = createItem(generateId());
|
|
335
|
+
fa.items.value = [
|
|
336
|
+
...fa.items.value.slice(0, index),
|
|
337
|
+
newItem,
|
|
338
|
+
...fa.items.value.slice(index)
|
|
339
|
+
];
|
|
340
|
+
ctx.dirtyFields.value = {
|
|
341
|
+
...ctx.dirtyFields.value,
|
|
342
|
+
[name]: true
|
|
343
|
+
};
|
|
344
|
+
if (ctx.options.mode === "onChange") validate(name);
|
|
345
|
+
};
|
|
346
|
+
const swap = (indexA, indexB) => {
|
|
347
|
+
const newValues = [...get(ctx.formData, name) || []];
|
|
348
|
+
[newValues[indexA], newValues[indexB]] = [newValues[indexB], newValues[indexA]];
|
|
349
|
+
set(ctx.formData, name, newValues);
|
|
350
|
+
const newItems = [...fa.items.value];
|
|
351
|
+
const itemA = newItems[indexA];
|
|
352
|
+
const itemB = newItems[indexB];
|
|
353
|
+
if (itemA && itemB) {
|
|
354
|
+
newItems[indexA] = itemB;
|
|
355
|
+
newItems[indexB] = itemA;
|
|
356
|
+
fa.items.value = newItems;
|
|
357
|
+
}
|
|
358
|
+
ctx.dirtyFields.value = {
|
|
359
|
+
...ctx.dirtyFields.value,
|
|
360
|
+
[name]: true
|
|
361
|
+
};
|
|
362
|
+
if (ctx.options.mode === "onChange") validate(name);
|
|
363
|
+
};
|
|
364
|
+
const move = (from, to) => {
|
|
365
|
+
const newValues = [...get(ctx.formData, name) || []];
|
|
366
|
+
const [removed] = newValues.splice(from, 1);
|
|
367
|
+
if (removed !== void 0) {
|
|
368
|
+
newValues.splice(to, 0, removed);
|
|
369
|
+
set(ctx.formData, name, newValues);
|
|
370
|
+
}
|
|
371
|
+
const newItems = [...fa.items.value];
|
|
372
|
+
const [removedItem] = newItems.splice(from, 1);
|
|
373
|
+
if (removedItem) {
|
|
374
|
+
newItems.splice(to, 0, removedItem);
|
|
375
|
+
fa.items.value = newItems;
|
|
376
|
+
}
|
|
377
|
+
ctx.dirtyFields.value = {
|
|
378
|
+
...ctx.dirtyFields.value,
|
|
379
|
+
[name]: true
|
|
380
|
+
};
|
|
381
|
+
if (ctx.options.mode === "onChange") validate(name);
|
|
382
|
+
};
|
|
383
|
+
return {
|
|
384
|
+
value: fa.items.value,
|
|
385
|
+
append,
|
|
386
|
+
prepend,
|
|
387
|
+
remove: removeAt,
|
|
388
|
+
insert,
|
|
389
|
+
swap,
|
|
390
|
+
move,
|
|
391
|
+
update
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return { fields };
|
|
395
|
+
}
|
|
396
|
+
function useForm(options) {
|
|
397
|
+
const ctx = createFormContext(options);
|
|
398
|
+
const { validate } = createValidation(ctx);
|
|
399
|
+
const { register, unregister } = createFieldRegistration(ctx, validate);
|
|
400
|
+
const { fields } = createFieldArrayManager(ctx, validate);
|
|
401
|
+
const formState = computed(() => ({
|
|
402
|
+
errors: ctx.errors.value,
|
|
403
|
+
isDirty: Object.keys(ctx.dirtyFields.value).some((k) => ctx.dirtyFields.value[k]),
|
|
404
|
+
dirtyFields: ctx.dirtyFields.value,
|
|
405
|
+
isValid: (ctx.submitCount.value > 0 || Object.keys(ctx.touchedFields.value).length > 0) && Object.keys(ctx.errors.value).length === 0,
|
|
406
|
+
isSubmitting: ctx.isSubmitting.value,
|
|
407
|
+
isLoading: ctx.isLoading.value,
|
|
408
|
+
touchedFields: ctx.touchedFields.value,
|
|
409
|
+
submitCount: ctx.submitCount.value
|
|
410
|
+
}));
|
|
411
|
+
function handleSubmit(onValid, onInvalid) {
|
|
412
|
+
return async (e) => {
|
|
413
|
+
e.preventDefault();
|
|
414
|
+
ctx.isSubmitting.value = true;
|
|
415
|
+
ctx.submitCount.value++;
|
|
416
|
+
try {
|
|
417
|
+
for (const [name, fieldRef] of ctx.fieldRefs.entries()) {
|
|
418
|
+
const el = fieldRef.value;
|
|
419
|
+
if (el) {
|
|
420
|
+
if (!ctx.fieldOptions.get(name)?.controlled) {
|
|
421
|
+
const value = el.type === "checkbox" ? el.checked : el.value;
|
|
422
|
+
set(ctx.formData, name, value);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (await validate()) await onValid(ctx.formData);
|
|
427
|
+
else onInvalid?.(ctx.errors.value);
|
|
428
|
+
} finally {
|
|
429
|
+
ctx.isSubmitting.value = false;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function setValue(name, value) {
|
|
434
|
+
set(ctx.formData, name, value);
|
|
435
|
+
ctx.dirtyFields.value = {
|
|
436
|
+
...ctx.dirtyFields.value,
|
|
437
|
+
[name]: true
|
|
438
|
+
};
|
|
439
|
+
const fieldRef = ctx.fieldRefs.get(name);
|
|
440
|
+
if (fieldRef?.value) {
|
|
441
|
+
const el = fieldRef.value;
|
|
442
|
+
if (el.type === "checkbox") el.checked = value;
|
|
443
|
+
else el.value = value;
|
|
444
|
+
}
|
|
445
|
+
if (options.mode === "onChange" || ctx.touchedFields.value[name]) validate(name);
|
|
446
|
+
}
|
|
447
|
+
function getValue(name) {
|
|
448
|
+
return get(ctx.formData, name);
|
|
449
|
+
}
|
|
450
|
+
function reset(values, resetOptions) {
|
|
451
|
+
const opts = resetOptions || {};
|
|
452
|
+
if (!opts.keepDefaultValues && values) Object.assign(ctx.defaultValues, values);
|
|
453
|
+
Object.keys(ctx.formData).forEach((key) => delete ctx.formData[key]);
|
|
454
|
+
const newValues = values || ctx.defaultValues;
|
|
455
|
+
Object.assign(ctx.formData, newValues);
|
|
456
|
+
if (!opts.keepErrors) ctx.errors.value = {};
|
|
457
|
+
if (!opts.keepTouched) ctx.touchedFields.value = {};
|
|
458
|
+
if (!opts.keepDirty) ctx.dirtyFields.value = {};
|
|
459
|
+
if (!opts.keepSubmitCount) ctx.submitCount.value = 0;
|
|
460
|
+
if (!opts.keepIsSubmitting) ctx.isSubmitting.value = false;
|
|
461
|
+
ctx.fieldArrays.clear();
|
|
462
|
+
for (const [name, fieldRef] of ctx.fieldRefs.entries()) {
|
|
463
|
+
const el = fieldRef.value;
|
|
464
|
+
if (el) {
|
|
465
|
+
const value = get(newValues, name);
|
|
466
|
+
if (value !== void 0) if (el.type === "checkbox") el.checked = value;
|
|
467
|
+
else el.value = value;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function watch(name) {
|
|
472
|
+
return computed(() => {
|
|
473
|
+
if (!name) return ctx.formData;
|
|
474
|
+
if (Array.isArray(name)) return name.reduce((acc, n) => {
|
|
475
|
+
acc[n] = get(ctx.formData, n);
|
|
476
|
+
return acc;
|
|
477
|
+
}, {});
|
|
478
|
+
return get(ctx.formData, name);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function clearErrors(name) {
|
|
482
|
+
if (name === void 0) {
|
|
483
|
+
ctx.errors.value = {};
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const fieldsToClean = Array.isArray(name) ? name : [name];
|
|
487
|
+
const newErrors = { ...ctx.errors.value };
|
|
488
|
+
for (const field of fieldsToClean) for (const key of Object.keys(newErrors)) if (key === field || key.startsWith(`${field}.`)) delete newErrors[key];
|
|
489
|
+
ctx.errors.value = newErrors;
|
|
490
|
+
}
|
|
491
|
+
function setError(name, error) {
|
|
492
|
+
const newErrors = { ...ctx.errors.value };
|
|
493
|
+
set(newErrors, name, error.type ? {
|
|
494
|
+
type: error.type,
|
|
495
|
+
message: error.message
|
|
496
|
+
} : error.message);
|
|
497
|
+
ctx.errors.value = newErrors;
|
|
498
|
+
}
|
|
499
|
+
function getValues(nameOrNames) {
|
|
500
|
+
for (const [name, fieldRef] of ctx.fieldRefs.entries()) {
|
|
501
|
+
const el = fieldRef.value;
|
|
502
|
+
if (el) {
|
|
503
|
+
if (!ctx.fieldOptions.get(name)?.controlled) {
|
|
504
|
+
const value = el.type === "checkbox" ? el.checked : el.value;
|
|
505
|
+
set(ctx.formData, name, value);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (nameOrNames === void 0) return { ...ctx.formData };
|
|
510
|
+
if (Array.isArray(nameOrNames)) {
|
|
511
|
+
const result = {};
|
|
512
|
+
for (const fieldName of nameOrNames) result[fieldName] = get(ctx.formData, fieldName);
|
|
513
|
+
return result;
|
|
514
|
+
}
|
|
515
|
+
return get(ctx.formData, nameOrNames);
|
|
516
|
+
}
|
|
517
|
+
function getFieldState(name) {
|
|
518
|
+
const error = get(ctx.errors.value, name);
|
|
519
|
+
return {
|
|
520
|
+
isDirty: ctx.dirtyFields.value[name] === true,
|
|
521
|
+
isTouched: ctx.touchedFields.value[name] === true,
|
|
522
|
+
invalid: error !== void 0 && error !== null,
|
|
523
|
+
error
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
async function trigger(name) {
|
|
527
|
+
if (name === void 0) return await validate();
|
|
528
|
+
if (Array.isArray(name)) {
|
|
529
|
+
let allValid = true;
|
|
530
|
+
for (const fieldName of name) if (!await validate(fieldName)) allValid = false;
|
|
531
|
+
return allValid;
|
|
532
|
+
}
|
|
533
|
+
return await validate(name);
|
|
534
|
+
}
|
|
535
|
+
function setFocus(name, focusOptions) {
|
|
536
|
+
const fieldRef = ctx.fieldRefs.get(name);
|
|
537
|
+
if (!fieldRef?.value) return;
|
|
538
|
+
const el = fieldRef.value;
|
|
539
|
+
if (typeof el.focus === "function") {
|
|
540
|
+
el.focus();
|
|
541
|
+
if (focusOptions?.shouldSelect && el instanceof HTMLInputElement && typeof el.select === "function") el.select();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
register,
|
|
546
|
+
unregister,
|
|
547
|
+
handleSubmit,
|
|
548
|
+
formState,
|
|
549
|
+
fields,
|
|
550
|
+
setValue,
|
|
551
|
+
getValue,
|
|
552
|
+
reset,
|
|
553
|
+
watch,
|
|
554
|
+
validate,
|
|
555
|
+
clearErrors,
|
|
556
|
+
setError,
|
|
557
|
+
getValues,
|
|
558
|
+
getFieldState,
|
|
559
|
+
trigger,
|
|
560
|
+
setFocus
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
const FormContextKey = Symbol("FormContext");
|
|
564
|
+
function provideForm(methods) {
|
|
565
|
+
provide(FormContextKey, methods);
|
|
566
|
+
}
|
|
567
|
+
function useFormContext() {
|
|
568
|
+
const context = inject(FormContextKey);
|
|
569
|
+
if (!context) throw new Error("useFormContext must be used within a component tree where provideForm() has been called. Make sure to call provideForm(useForm({ schema })) in a parent component.");
|
|
570
|
+
return context;
|
|
571
|
+
}
|
|
572
|
+
function useWatch(options = {}) {
|
|
573
|
+
const { control, name, defaultValue } = options;
|
|
574
|
+
const form = control ?? useFormContext();
|
|
575
|
+
return computed(() => {
|
|
576
|
+
if (name === void 0) return form.getValues();
|
|
577
|
+
if (Array.isArray(name)) {
|
|
578
|
+
const result = {};
|
|
579
|
+
for (const fieldName of name) result[fieldName] = get(form.getValues(), fieldName) ?? defaultValue;
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
return get(form.getValues(), name) ?? defaultValue;
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function useController(options) {
|
|
586
|
+
const { name, control, defaultValue } = options;
|
|
587
|
+
const form = control ?? useFormContext();
|
|
588
|
+
const elementRef = ref(null);
|
|
589
|
+
if (defaultValue !== void 0 && form.getValue(name) === void 0) form.setValue(name, defaultValue);
|
|
590
|
+
const value = computed({
|
|
591
|
+
get: () => {
|
|
592
|
+
return form.getValue(name) ?? defaultValue;
|
|
593
|
+
},
|
|
594
|
+
set: (newValue) => {
|
|
595
|
+
form.setValue(name, newValue);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
const onChange = (newValue) => {
|
|
599
|
+
form.setValue(name, newValue);
|
|
600
|
+
};
|
|
601
|
+
const onBlur = () => {
|
|
602
|
+
form.trigger(name);
|
|
603
|
+
};
|
|
604
|
+
const refCallback = (el) => {
|
|
605
|
+
elementRef.value = el;
|
|
606
|
+
};
|
|
607
|
+
const fieldState = computed(() => {
|
|
608
|
+
return form.getFieldState(name);
|
|
609
|
+
});
|
|
610
|
+
return {
|
|
611
|
+
field: {
|
|
612
|
+
value,
|
|
613
|
+
name,
|
|
614
|
+
onChange,
|
|
615
|
+
onBlur,
|
|
616
|
+
ref: refCallback
|
|
617
|
+
},
|
|
618
|
+
fieldState
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function useFormState(options = {}) {
|
|
622
|
+
const { control, name } = options;
|
|
623
|
+
const form = control ?? useFormContext();
|
|
624
|
+
return computed(() => {
|
|
625
|
+
const fullState = form.formState.value;
|
|
626
|
+
if (name === void 0) return { ...fullState };
|
|
627
|
+
if (Array.isArray(name)) {
|
|
628
|
+
const result = {};
|
|
629
|
+
for (const key of name) result[key] = fullState[key];
|
|
630
|
+
return result;
|
|
631
|
+
}
|
|
632
|
+
return { [name]: fullState[name] };
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
export { FormContextKey, provideForm, useController, useForm, useFormContext, useFormState, useWatch };
|
|
636
|
+
|
|
637
|
+
//# sourceMappingURL=vuehookform.js.map
|