@vuehookform/core 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let vue = require("vue");
3
3
  function get(obj, path) {
4
- if (!path) return obj;
4
+ if (!path || obj === null || obj === void 0) return obj;
5
5
  const keys = path.split(".");
6
6
  let result = obj;
7
7
  for (const key of keys) {
@@ -21,9 +21,14 @@ function set(obj, path, value) {
21
21
  if (keys.some((k) => UNSAFE_KEYS.includes(k))) return;
22
22
  const lastKey = keys.pop();
23
23
  let current = obj;
24
- for (const key of keys) {
25
- if (!(key in current) || typeof current[key] !== "object") {
26
- const nextKey = keys[keys.indexOf(key) + 1];
24
+ for (let i = 0; i < keys.length; i++) {
25
+ const key = keys[i];
26
+ const existing = current[key];
27
+ if (existing !== void 0 && existing !== null && typeof existing !== "object") try {
28
+ if (globalThis.process?.env?.NODE_ENV !== "production") console.warn(`[vue-hook-form] set(): Overwriting primitive value at path "${keys.slice(0, i + 1).join(".")}" with an object. Previous value: ${JSON.stringify(existing)}`);
29
+ } catch {}
30
+ if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
31
+ const nextKey = keys[i + 1];
27
32
  current[key] = nextKey && /^\d+$/.test(nextKey) ? [] : {};
28
33
  }
29
34
  current = current[key];
@@ -37,13 +42,16 @@ function unset(obj, path) {
37
42
  let current = obj;
38
43
  for (const key of keys) {
39
44
  if (!(key in current)) return;
40
- current = current[key];
45
+ const next = current[key];
46
+ if (next === null || typeof next !== "object") return;
47
+ current = next;
41
48
  }
42
49
  delete current[lastKey];
43
50
  }
44
51
  var idCounter = 0;
45
52
  function generateId() {
46
- return `field_${Date.now()}_${idCounter++}`;
53
+ const random = Math.random().toString(36).substring(2, 11);
54
+ return `field_${Date.now()}_${idCounter++}_${random}`;
47
55
  }
48
56
  function createFormContext(options) {
49
57
  const formData = (0, vue.reactive)({});
@@ -58,26 +66,81 @@ function createFormContext(options) {
58
66
  isLoading.value = false;
59
67
  }).catch((error) => {
60
68
  console.error("Failed to load async default values:", error);
69
+ defaultValuesError.value = error;
61
70
  isLoading.value = false;
71
+ options.onDefaultValuesError?.(error);
62
72
  });
63
73
  } else if (options.defaultValues) {
64
74
  Object.assign(defaultValues, options.defaultValues);
65
75
  Object.assign(formData, defaultValues);
66
76
  }
77
+ const errors = (0, vue.shallowRef)({});
78
+ const touchedFields = (0, vue.shallowRef)({});
79
+ const dirtyFields = (0, vue.shallowRef)({});
80
+ const isSubmitting = (0, vue.ref)(false);
81
+ const submitCount = (0, vue.ref)(0);
82
+ const defaultValuesError = (0, vue.ref)(null);
83
+ const isSubmitSuccessful = (0, vue.ref)(false);
84
+ const validatingFields = (0, vue.shallowRef)({});
85
+ const externalErrors = (0, vue.shallowRef)({});
86
+ const errorDelayTimers = /* @__PURE__ */ new Map();
87
+ const pendingErrors = /* @__PURE__ */ new Map();
88
+ const fieldRefs = /* @__PURE__ */ new Map();
89
+ const fieldOptions = /* @__PURE__ */ new Map();
90
+ const fieldArrays = /* @__PURE__ */ new Map();
91
+ const fieldHandlers = /* @__PURE__ */ new Map();
92
+ const debounceTimers = /* @__PURE__ */ new Map();
93
+ const validationRequestIds = /* @__PURE__ */ new Map();
94
+ const resetGeneration = (0, vue.ref)(0);
95
+ if (options.values !== void 0) {
96
+ const initialValues = (0, vue.toValue)(options.values);
97
+ if (initialValues && !isAsyncDefaults) {
98
+ for (const [key, value] of Object.entries(initialValues)) if (value !== void 0) set(formData, key, value);
99
+ }
100
+ (0, vue.watch)(() => (0, vue.toValue)(options.values), (newValues) => {
101
+ if (newValues) {
102
+ for (const [key, value] of Object.entries(newValues)) if (value !== void 0) {
103
+ set(formData, key, value);
104
+ const fieldRef = fieldRefs.get(key);
105
+ const opts = fieldOptions.get(key);
106
+ if (fieldRef?.value && !opts?.controlled) {
107
+ const el = fieldRef.value;
108
+ if (el.type === "checkbox") el.checked = value;
109
+ else el.value = value;
110
+ }
111
+ }
112
+ }
113
+ }, { deep: true });
114
+ }
115
+ if (options.errors !== void 0) {
116
+ const initialErrors = (0, vue.toValue)(options.errors);
117
+ if (initialErrors) externalErrors.value = initialErrors;
118
+ (0, vue.watch)(() => (0, vue.toValue)(options.errors), (newErrors) => {
119
+ externalErrors.value = newErrors || {};
120
+ }, { deep: true });
121
+ }
67
122
  return {
68
123
  formData,
69
124
  defaultValues,
70
- errors: (0, vue.shallowRef)({}),
71
- touchedFields: (0, vue.ref)({}),
72
- dirtyFields: (0, vue.ref)({}),
73
- isSubmitting: (0, vue.ref)(false),
125
+ errors,
126
+ touchedFields,
127
+ dirtyFields,
128
+ isSubmitting,
74
129
  isLoading,
75
- submitCount: (0, vue.ref)(0),
76
- fieldRefs: /* @__PURE__ */ new Map(),
77
- fieldOptions: /* @__PURE__ */ new Map(),
78
- fieldArrays: /* @__PURE__ */ new Map(),
79
- debounceTimers: /* @__PURE__ */ new Map(),
80
- validationRequestIds: /* @__PURE__ */ new Map(),
130
+ submitCount,
131
+ defaultValuesError,
132
+ isSubmitSuccessful,
133
+ validatingFields,
134
+ externalErrors,
135
+ errorDelayTimers,
136
+ pendingErrors,
137
+ fieldRefs,
138
+ fieldOptions,
139
+ fieldArrays,
140
+ fieldHandlers,
141
+ debounceTimers,
142
+ validationRequestIds,
143
+ resetGeneration,
81
144
  options
82
145
  };
83
146
  }
@@ -86,6 +149,17 @@ function clearFieldErrors(errors, fieldPath) {
86
149
  for (const key of Object.keys(newErrors)) if (key === fieldPath || key.startsWith(`${fieldPath}.`)) delete newErrors[key];
87
150
  return newErrors;
88
151
  }
152
+ function setValidating(ctx, fieldPath, isValidating) {
153
+ if (isValidating) ctx.validatingFields.value = {
154
+ ...ctx.validatingFields.value,
155
+ [fieldPath]: true
156
+ };
157
+ else {
158
+ const newValidating = { ...ctx.validatingFields.value };
159
+ delete newValidating[fieldPath];
160
+ ctx.validatingFields.value = newValidating;
161
+ }
162
+ }
89
163
  function groupErrorsByPath(issues) {
90
164
  const grouped = /* @__PURE__ */ new Map();
91
165
  for (const issue of issues) {
@@ -99,8 +173,11 @@ function groupErrorsByPath(issues) {
99
173
  }
100
174
  return grouped;
101
175
  }
102
- function createFieldError(errors) {
103
- if (errors.length === 1) return errors[0].message;
176
+ function createFieldError(errors, criteriaMode = "firstError") {
177
+ const firstError = errors[0];
178
+ if (!firstError) return "";
179
+ if (criteriaMode === "firstError") return firstError.message;
180
+ if (errors.length === 1) return firstError.message;
104
181
  const types = {};
105
182
  for (const err of errors) {
106
183
  const existing = types[err.type];
@@ -108,135 +185,215 @@ function createFieldError(errors) {
108
185
  else types[err.type] = err.message;
109
186
  }
110
187
  return {
111
- type: errors[0].type,
112
- message: errors[0].message,
188
+ type: firstError.type,
189
+ message: firstError.message,
113
190
  types
114
191
  };
115
192
  }
116
193
  function createValidation(ctx) {
117
- async function validate(fieldPath) {
118
- const result = await ctx.options.schema.safeParseAsync(ctx.formData);
119
- if (result.success) {
120
- if (fieldPath) ctx.errors.value = clearFieldErrors(ctx.errors.value, fieldPath);
121
- else ctx.errors.value = {};
122
- return true;
194
+ function scheduleError(fieldPath, error) {
195
+ const delayMs = ctx.options.delayError || 0;
196
+ if (delayMs <= 0) {
197
+ const newErrors = { ...ctx.errors.value };
198
+ set(newErrors, fieldPath, error);
199
+ ctx.errors.value = newErrors;
200
+ return;
123
201
  }
124
- const zodErrors = result.error.issues;
125
- if (fieldPath) {
126
- const fieldErrors = zodErrors.filter((issue) => {
127
- const path = issue.path.join(".");
128
- return path === fieldPath || path.startsWith(`${fieldPath}.`);
129
- });
130
- if (fieldErrors.length === 0) {
131
- ctx.errors.value = clearFieldErrors(ctx.errors.value, fieldPath);
202
+ const existingTimer = ctx.errorDelayTimers.get(fieldPath);
203
+ if (existingTimer) clearTimeout(existingTimer);
204
+ ctx.pendingErrors.set(fieldPath, error);
205
+ const timer = setTimeout(() => {
206
+ ctx.errorDelayTimers.delete(fieldPath);
207
+ const pendingError = ctx.pendingErrors.get(fieldPath);
208
+ if (pendingError !== void 0) {
209
+ ctx.pendingErrors.delete(fieldPath);
210
+ const newErrors = { ...ctx.errors.value };
211
+ set(newErrors, fieldPath, pendingError);
212
+ ctx.errors.value = newErrors;
213
+ }
214
+ }, delayMs);
215
+ ctx.errorDelayTimers.set(fieldPath, timer);
216
+ }
217
+ function cancelError(fieldPath) {
218
+ const timer = ctx.errorDelayTimers.get(fieldPath);
219
+ if (timer) {
220
+ clearTimeout(timer);
221
+ ctx.errorDelayTimers.delete(fieldPath);
222
+ }
223
+ ctx.pendingErrors.delete(fieldPath);
224
+ return clearFieldErrors(ctx.errors.value, fieldPath);
225
+ }
226
+ function clearAllPendingErrors() {
227
+ for (const timer of ctx.errorDelayTimers.values()) clearTimeout(timer);
228
+ ctx.errorDelayTimers.clear();
229
+ ctx.pendingErrors.clear();
230
+ }
231
+ async function validate(fieldPath) {
232
+ const generationAtStart = ctx.resetGeneration.value;
233
+ const criteriaMode = ctx.options.criteriaMode || "firstError";
234
+ const validatingKey = fieldPath || "_form";
235
+ setValidating(ctx, validatingKey, true);
236
+ try {
237
+ const result = await ctx.options.schema.safeParseAsync(ctx.formData);
238
+ if (ctx.resetGeneration.value !== generationAtStart) return true;
239
+ if (result.success) {
240
+ if (fieldPath) ctx.errors.value = cancelError(fieldPath);
241
+ else {
242
+ clearAllPendingErrors();
243
+ ctx.errors.value = {};
244
+ }
132
245
  return true;
133
246
  }
134
- let newErrors$1 = clearFieldErrors(ctx.errors.value, fieldPath);
135
- const grouped$1 = groupErrorsByPath(fieldErrors);
136
- for (const [path, errors] of grouped$1) set(newErrors$1, path, createFieldError(errors));
137
- ctx.errors.value = newErrors$1;
247
+ const zodErrors = result.error.issues;
248
+ if (fieldPath) {
249
+ const fieldErrors = zodErrors.filter((issue) => {
250
+ const path = issue.path.join(".");
251
+ return path === fieldPath || path.startsWith(`${fieldPath}.`);
252
+ });
253
+ if (fieldErrors.length === 0) {
254
+ ctx.errors.value = cancelError(fieldPath);
255
+ return true;
256
+ }
257
+ ctx.errors.value = cancelError(fieldPath);
258
+ const grouped$1 = groupErrorsByPath(fieldErrors);
259
+ for (const [path, errors] of grouped$1) scheduleError(path, createFieldError(errors, criteriaMode));
260
+ return false;
261
+ }
262
+ clearAllPendingErrors();
263
+ ctx.errors.value = {};
264
+ const grouped = groupErrorsByPath(zodErrors);
265
+ for (const [path, errors] of grouped) scheduleError(path, createFieldError(errors, criteriaMode));
138
266
  return false;
267
+ } finally {
268
+ setValidating(ctx, validatingKey, false);
139
269
  }
140
- const newErrors = {};
141
- const grouped = groupErrorsByPath(zodErrors);
142
- for (const [path, errors] of grouped) set(newErrors, path, createFieldError(errors));
143
- ctx.errors.value = newErrors;
144
- return false;
145
270
  }
146
- return { validate };
271
+ return {
272
+ validate,
273
+ clearAllPendingErrors
274
+ };
147
275
  }
276
+ var validationRequestCounter = 0;
148
277
  function createFieldRegistration(ctx, validate) {
149
278
  function register(name, registerOptions) {
150
- const fieldRef = (0, vue.ref)(null);
151
- ctx.fieldRefs.set(name, fieldRef);
152
- if (registerOptions) ctx.fieldOptions.set(name, registerOptions);
153
- if (get(ctx.formData, name) === void 0) {
154
- const defaultValue = get(ctx.defaultValues, name);
155
- if (defaultValue !== void 0) set(ctx.formData, name, defaultValue);
279
+ let fieldRef = ctx.fieldRefs.get(name);
280
+ if (!fieldRef) {
281
+ fieldRef = (0, vue.ref)(null);
282
+ ctx.fieldRefs.set(name, fieldRef);
283
+ if (get(ctx.formData, name) === void 0) {
284
+ const defaultValue = get(ctx.defaultValues, name);
285
+ if (defaultValue !== void 0) set(ctx.formData, name, defaultValue);
286
+ }
156
287
  }
157
- const runCustomValidation = async (fieldName, value, requestId) => {
158
- const fieldOpts = ctx.fieldOptions.get(fieldName);
159
- if (!fieldOpts?.validate || fieldOpts.disabled) return;
160
- const error = await fieldOpts.validate(value);
161
- if (requestId !== ctx.validationRequestIds.get(fieldName)) return;
162
- if (error) ctx.errors.value = {
163
- ...ctx.errors.value,
164
- [fieldName]: error
288
+ if (registerOptions) ctx.fieldOptions.set(name, registerOptions);
289
+ let handlers = ctx.fieldHandlers.get(name);
290
+ if (!handlers) {
291
+ const runCustomValidation = async (fieldName, value, requestId, resetGenAtStart) => {
292
+ const fieldOpts = ctx.fieldOptions.get(fieldName);
293
+ if (!fieldOpts?.validate || fieldOpts.disabled) return;
294
+ const error = await fieldOpts.validate(value);
295
+ if (requestId !== ctx.validationRequestIds.get(fieldName)) return;
296
+ if (ctx.resetGeneration.value !== resetGenAtStart) return;
297
+ if (error) ctx.errors.value = {
298
+ ...ctx.errors.value,
299
+ [fieldName]: error
300
+ };
301
+ else {
302
+ const newErrors = { ...ctx.errors.value };
303
+ delete newErrors[fieldName];
304
+ ctx.errors.value = newErrors;
305
+ }
165
306
  };
166
- else {
167
- const newErrors = { ...ctx.errors.value };
168
- delete newErrors[fieldName];
169
- ctx.errors.value = newErrors;
170
- }
171
- };
172
- const onInput = async (e) => {
173
- const target = e.target;
174
- const value = target.type === "checkbox" ? target.checked : target.value;
175
- set(ctx.formData, name, value);
176
- ctx.dirtyFields.value = {
177
- ...ctx.dirtyFields.value,
178
- [name]: true
307
+ const onInput = async (e) => {
308
+ const target = e.target;
309
+ const value = target.type === "checkbox" ? target.checked : target.value;
310
+ set(ctx.formData, name, value);
311
+ ctx.dirtyFields.value = {
312
+ ...ctx.dirtyFields.value,
313
+ [name]: true
314
+ };
315
+ if (ctx.options.mode === "onChange" || ctx.options.mode === "onTouched" && ctx.touchedFields.value[name] || ctx.touchedFields.value[name] && ctx.options.reValidateMode === "onChange") {
316
+ await validate(name);
317
+ const fieldOpts$1 = ctx.fieldOptions.get(name);
318
+ if (fieldOpts$1?.deps && fieldOpts$1.deps.length > 0) for (const depField of fieldOpts$1.deps) validate(depField);
319
+ }
320
+ const fieldOpts = ctx.fieldOptions.get(name);
321
+ if (fieldOpts?.validate && !fieldOpts.disabled) {
322
+ const requestId = ++validationRequestCounter;
323
+ ctx.validationRequestIds.set(name, requestId);
324
+ const resetGenAtStart = ctx.resetGeneration.value;
325
+ const debounceMs = fieldOpts.validateDebounce || 0;
326
+ if (debounceMs > 0) {
327
+ const existingTimer = ctx.debounceTimers.get(name);
328
+ if (existingTimer) clearTimeout(existingTimer);
329
+ const timer = setTimeout(() => {
330
+ ctx.debounceTimers.delete(name);
331
+ runCustomValidation(name, value, requestId, resetGenAtStart);
332
+ }, debounceMs);
333
+ ctx.debounceTimers.set(name, timer);
334
+ } else await runCustomValidation(name, value, requestId, resetGenAtStart);
335
+ }
179
336
  };
180
- if (ctx.options.mode === "onChange" || ctx.options.mode === "onTouched" && ctx.touchedFields.value[name] || ctx.touchedFields.value[name] && ctx.options.reValidateMode === "onChange") await validate(name);
181
- const fieldOpts = ctx.fieldOptions.get(name);
182
- if (fieldOpts?.validate && !fieldOpts.disabled) {
183
- const requestId = Date.now() + Math.random();
184
- ctx.validationRequestIds.set(name, requestId);
185
- const debounceMs = fieldOpts.validateDebounce || 0;
186
- if (debounceMs > 0) {
187
- const existingTimer = ctx.debounceTimers.get(name);
188
- if (existingTimer) clearTimeout(existingTimer);
189
- const timer = setTimeout(() => {
190
- ctx.debounceTimers.delete(name);
191
- runCustomValidation(name, value, requestId);
192
- }, debounceMs);
193
- ctx.debounceTimers.set(name, timer);
194
- } else await runCustomValidation(name, value, requestId);
195
- }
196
- };
197
- const onBlur = async (_e) => {
198
- ctx.touchedFields.value = {
199
- ...ctx.touchedFields.value,
200
- [name]: true
337
+ const onBlur = async (_e) => {
338
+ ctx.touchedFields.value = {
339
+ ...ctx.touchedFields.value,
340
+ [name]: true
341
+ };
342
+ if (ctx.options.mode === "onBlur" || ctx.options.mode === "onTouched" || ctx.submitCount.value > 0 && ctx.options.reValidateMode === "onBlur") {
343
+ await validate(name);
344
+ const fieldOpts = ctx.fieldOptions.get(name);
345
+ if (fieldOpts?.deps && fieldOpts.deps.length > 0) for (const depField of fieldOpts.deps) validate(depField);
346
+ }
201
347
  };
202
- if (ctx.options.mode === "onBlur" || ctx.options.mode === "onTouched" || ctx.submitCount.value > 0 && ctx.options.reValidateMode === "onBlur") await validate(name);
203
- };
204
- const refCallback = (el) => {
205
- const previousEl = fieldRef.value;
206
- fieldRef.value = el;
207
- if (el && !registerOptions?.controlled && el instanceof HTMLInputElement) {
208
- const value = get(ctx.formData, name);
209
- if (value !== void 0) if (el.type === "checkbox") el.checked = value;
210
- else el.value = value;
211
- }
212
- if (previousEl && !el) {
213
- if (registerOptions?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
214
- unset(ctx.formData, name);
215
- const newErrors = { ...ctx.errors.value };
216
- delete newErrors[name];
217
- ctx.errors.value = newErrors;
218
- const newTouched = { ...ctx.touchedFields.value };
219
- delete newTouched[name];
220
- ctx.touchedFields.value = newTouched;
221
- const newDirty = { ...ctx.dirtyFields.value };
222
- delete newDirty[name];
223
- ctx.dirtyFields.value = newDirty;
224
- ctx.fieldRefs.delete(name);
225
- ctx.fieldOptions.delete(name);
348
+ const refCallback = (el) => {
349
+ const currentFieldRef = ctx.fieldRefs.get(name);
350
+ if (!currentFieldRef) return;
351
+ const previousEl = currentFieldRef.value;
352
+ if (previousEl === el) return;
353
+ if (previousEl && el) return;
354
+ currentFieldRef.value = el;
355
+ const opts = ctx.fieldOptions.get(name);
356
+ if (el && !opts?.controlled && el instanceof HTMLInputElement) {
357
+ const value = get(ctx.formData, name);
358
+ if (value !== void 0) if (el.type === "checkbox") el.checked = value;
359
+ else el.value = value;
360
+ }
361
+ if (previousEl && !el) {
226
362
  const timer = ctx.debounceTimers.get(name);
227
363
  if (timer) {
228
364
  clearTimeout(timer);
229
365
  ctx.debounceTimers.delete(name);
230
366
  }
231
367
  ctx.validationRequestIds.delete(name);
368
+ if (opts?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
369
+ unset(ctx.formData, name);
370
+ const newErrors = { ...ctx.errors.value };
371
+ delete newErrors[name];
372
+ ctx.errors.value = newErrors;
373
+ const newTouched = { ...ctx.touchedFields.value };
374
+ delete newTouched[name];
375
+ ctx.touchedFields.value = newTouched;
376
+ const newDirty = { ...ctx.dirtyFields.value };
377
+ delete newDirty[name];
378
+ ctx.dirtyFields.value = newDirty;
379
+ ctx.fieldRefs.delete(name);
380
+ ctx.fieldOptions.delete(name);
381
+ ctx.fieldHandlers.delete(name);
382
+ }
232
383
  }
233
- }
234
- };
384
+ };
385
+ handlers = {
386
+ onInput,
387
+ onBlur,
388
+ refCallback
389
+ };
390
+ ctx.fieldHandlers.set(name, handlers);
391
+ }
235
392
  return {
236
393
  name,
237
- ref: refCallback,
238
- onInput,
239
- onBlur,
394
+ ref: handlers.refCallback,
395
+ onInput: handlers.onInput,
396
+ onBlur: handlers.onBlur,
240
397
  ...registerOptions?.controlled && { value: (0, vue.computed)({
241
398
  get: () => get(ctx.formData, name),
242
399
  set: (val) => {
@@ -249,9 +406,27 @@ function createFieldRegistration(ctx, validate) {
249
406
  }) }
250
407
  };
251
408
  }
252
- function unregister(name) {
409
+ function unregister(name, options) {
410
+ const opts = options || {};
411
+ if (!opts.keepValue) unset(ctx.formData, name);
412
+ if (!opts.keepError) {
413
+ const newErrors = { ...ctx.errors.value };
414
+ delete newErrors[name];
415
+ ctx.errors.value = newErrors;
416
+ }
417
+ if (!opts.keepTouched) {
418
+ const newTouched = { ...ctx.touchedFields.value };
419
+ delete newTouched[name];
420
+ ctx.touchedFields.value = newTouched;
421
+ }
422
+ if (!opts.keepDirty) {
423
+ const newDirty = { ...ctx.dirtyFields.value };
424
+ delete newDirty[name];
425
+ ctx.dirtyFields.value = newDirty;
426
+ }
253
427
  ctx.fieldRefs.delete(name);
254
428
  ctx.fieldOptions.delete(name);
429
+ ctx.fieldHandlers.delete(name);
255
430
  const timer = ctx.debounceTimers.get(name);
256
431
  if (timer) {
257
432
  clearTimeout(timer);
@@ -264,42 +439,89 @@ function createFieldRegistration(ctx, validate) {
264
439
  unregister
265
440
  };
266
441
  }
267
- function createFieldArrayManager(ctx, validate) {
268
- function fields(name) {
442
+ function createFieldArrayManager(ctx, validate, setFocus) {
443
+ function fields(name, options) {
269
444
  let fieldArray = ctx.fieldArrays.get(name);
270
445
  if (!fieldArray) {
271
446
  const existingValues = get(ctx.formData, name) || [];
272
447
  fieldArray = {
273
448
  items: (0, vue.ref)([]),
274
- values: existingValues
449
+ values: existingValues,
450
+ indexCache: /* @__PURE__ */ new Map(),
451
+ rules: options?.rules
275
452
  };
276
453
  ctx.fieldArrays.set(name, fieldArray);
277
454
  if (!get(ctx.formData, name)) set(ctx.formData, name, []);
278
- }
455
+ } else if (options?.rules) fieldArray.rules = options.rules;
279
456
  const fa = fieldArray;
457
+ const indexCache = fa.indexCache;
458
+ const rebuildIndexCache = () => {
459
+ indexCache.clear();
460
+ fa.items.value.forEach((item, idx) => {
461
+ indexCache.set(item.key, idx);
462
+ });
463
+ };
280
464
  const createItem = (key) => ({
281
465
  key,
282
466
  get index() {
283
- return fa.items.value.findIndex((item) => item.key === key);
467
+ return indexCache.get(key) ?? -1;
284
468
  },
285
469
  remove() {
286
- const currentIndex = fa.items.value.findIndex((item) => item.key === key);
470
+ const currentIndex = indexCache.get(key) ?? -1;
287
471
  if (currentIndex !== -1) removeAt(currentIndex);
288
472
  }
289
473
  });
290
- if (fa.items.value.length === 0 && fa.values.length > 0) fa.items.value = fa.values.map(() => createItem(generateId()));
291
- const append = (value) => {
292
- const newValues = [...get(ctx.formData, name) || [], value];
474
+ if (fa.items.value.length === 0 && fa.values.length > 0) {
475
+ fa.items.value = fa.values.map(() => createItem(generateId()));
476
+ rebuildIndexCache();
477
+ }
478
+ const handleFocus = async (baseIndex, addedCount, focusOptions) => {
479
+ if (!focusOptions?.shouldFocus) return;
480
+ await (0, vue.nextTick)();
481
+ const focusItemOffset = focusOptions?.focusIndex ?? 0;
482
+ let fieldPath = `${name}.${baseIndex + Math.min(focusItemOffset, addedCount - 1)}`;
483
+ if (focusOptions?.focusName) fieldPath = `${fieldPath}.${focusOptions.focusName}`;
484
+ setFocus(fieldPath);
485
+ };
486
+ const normalizeToArray = (value) => {
487
+ return Array.isArray(value) ? value : [value];
488
+ };
489
+ const append = (value, focusOptions) => {
490
+ const values = normalizeToArray(value);
491
+ if (values.length === 0) return;
492
+ const currentValues = get(ctx.formData, name) || [];
493
+ const insertIndex = currentValues.length;
494
+ const rules = fa.rules;
495
+ if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) return;
496
+ const newValues = [...currentValues, ...values];
293
497
  set(ctx.formData, name, newValues);
294
- fa.items.value = [...fa.items.value, createItem(generateId())];
498
+ const newItems = values.map(() => createItem(generateId()));
499
+ fa.items.value = [...fa.items.value, ...newItems];
500
+ rebuildIndexCache();
295
501
  ctx.dirtyFields.value = {
296
502
  ...ctx.dirtyFields.value,
297
503
  [name]: true
298
504
  };
299
505
  if (ctx.options.mode === "onChange") validate(name);
506
+ handleFocus(insertIndex, values.length, focusOptions);
300
507
  };
301
- const prepend = (value) => {
302
- insert(0, value);
508
+ const prepend = (value, focusOptions) => {
509
+ const values = normalizeToArray(value);
510
+ if (values.length === 0) return;
511
+ const currentValues = get(ctx.formData, name) || [];
512
+ const rules = fa.rules;
513
+ if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) return;
514
+ const newValues = [...values, ...currentValues];
515
+ set(ctx.formData, name, newValues);
516
+ const newItems = values.map(() => createItem(generateId()));
517
+ fa.items.value = [...newItems, ...fa.items.value];
518
+ rebuildIndexCache();
519
+ ctx.dirtyFields.value = {
520
+ ...ctx.dirtyFields.value,
521
+ [name]: true
522
+ };
523
+ if (ctx.options.mode === "onChange") validate(name);
524
+ handleFocus(0, values.length, focusOptions);
303
525
  };
304
526
  const update = (index, value) => {
305
527
  const currentValues = get(ctx.formData, name) || [];
@@ -314,38 +536,52 @@ function createFieldArrayManager(ctx, validate) {
314
536
  if (ctx.options.mode === "onChange") validate(name);
315
537
  };
316
538
  const removeAt = (index) => {
317
- const newValues = (get(ctx.formData, name) || []).filter((_, i) => i !== index);
539
+ const currentValues = get(ctx.formData, name) || [];
540
+ if (index < 0 || index >= currentValues.length) return;
541
+ const rules = fa.rules;
542
+ if (rules?.minLength && currentValues.length - 1 < rules.minLength.value) return;
543
+ const newValues = currentValues.filter((_, i) => i !== index);
318
544
  set(ctx.formData, name, newValues);
319
545
  const keyToRemove = fa.items.value[index]?.key;
320
546
  fa.items.value = fa.items.value.filter((item) => item.key !== keyToRemove);
547
+ rebuildIndexCache();
321
548
  ctx.dirtyFields.value = {
322
549
  ...ctx.dirtyFields.value,
323
550
  [name]: true
324
551
  };
325
552
  if (ctx.options.mode === "onChange") validate(name);
326
553
  };
327
- const insert = (index, value) => {
554
+ const insert = (index, value, focusOptions) => {
555
+ const values = normalizeToArray(value);
556
+ if (values.length === 0) return;
328
557
  const currentValues = get(ctx.formData, name) || [];
558
+ const rules = fa.rules;
559
+ if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) return;
560
+ const clampedIndex = Math.max(0, Math.min(index, currentValues.length));
329
561
  const newValues = [
330
- ...currentValues.slice(0, index),
331
- value,
332
- ...currentValues.slice(index)
562
+ ...currentValues.slice(0, clampedIndex),
563
+ ...values,
564
+ ...currentValues.slice(clampedIndex)
333
565
  ];
334
566
  set(ctx.formData, name, newValues);
335
- const newItem = createItem(generateId());
567
+ const newItems = values.map(() => createItem(generateId()));
336
568
  fa.items.value = [
337
- ...fa.items.value.slice(0, index),
338
- newItem,
339
- ...fa.items.value.slice(index)
569
+ ...fa.items.value.slice(0, clampedIndex),
570
+ ...newItems,
571
+ ...fa.items.value.slice(clampedIndex)
340
572
  ];
573
+ rebuildIndexCache();
341
574
  ctx.dirtyFields.value = {
342
575
  ...ctx.dirtyFields.value,
343
576
  [name]: true
344
577
  };
345
578
  if (ctx.options.mode === "onChange") validate(name);
579
+ handleFocus(clampedIndex, values.length, focusOptions);
346
580
  };
347
581
  const swap = (indexA, indexB) => {
348
- const newValues = [...get(ctx.formData, name) || []];
582
+ const currentValues = get(ctx.formData, name) || [];
583
+ if (indexA < 0 || indexB < 0 || indexA >= currentValues.length || indexB >= currentValues.length) return;
584
+ const newValues = [...currentValues];
349
585
  [newValues[indexA], newValues[indexB]] = [newValues[indexB], newValues[indexA]];
350
586
  set(ctx.formData, name, newValues);
351
587
  const newItems = [...fa.items.value];
@@ -355,6 +591,7 @@ function createFieldArrayManager(ctx, validate) {
355
591
  newItems[indexA] = itemB;
356
592
  newItems[indexB] = itemA;
357
593
  fa.items.value = newItems;
594
+ rebuildIndexCache();
358
595
  }
359
596
  ctx.dirtyFields.value = {
360
597
  ...ctx.dirtyFields.value,
@@ -363,17 +600,22 @@ function createFieldArrayManager(ctx, validate) {
363
600
  if (ctx.options.mode === "onChange") validate(name);
364
601
  };
365
602
  const move = (from, to) => {
366
- const newValues = [...get(ctx.formData, name) || []];
603
+ const currentValues = get(ctx.formData, name) || [];
604
+ if (from < 0 || from >= currentValues.length || to < 0) return;
605
+ const newValues = [...currentValues];
367
606
  const [removed] = newValues.splice(from, 1);
368
607
  if (removed !== void 0) {
369
- newValues.splice(to, 0, removed);
608
+ const clampedTo = Math.min(to, newValues.length);
609
+ newValues.splice(clampedTo, 0, removed);
370
610
  set(ctx.formData, name, newValues);
371
611
  }
372
612
  const newItems = [...fa.items.value];
373
613
  const [removedItem] = newItems.splice(from, 1);
374
614
  if (removedItem) {
375
- newItems.splice(to, 0, removedItem);
615
+ const clampedTo = Math.min(to, newItems.length);
616
+ newItems.splice(clampedTo, 0, removedItem);
376
617
  fa.items.value = newItems;
618
+ rebuildIndexCache();
377
619
  }
378
620
  ctx.dirtyFields.value = {
379
621
  ...ctx.dirtyFields.value,
@@ -381,6 +623,17 @@ function createFieldArrayManager(ctx, validate) {
381
623
  };
382
624
  if (ctx.options.mode === "onChange") validate(name);
383
625
  };
626
+ const replace = (newValues) => {
627
+ if (!Array.isArray(newValues)) return;
628
+ set(ctx.formData, name, newValues);
629
+ fa.items.value = newValues.map(() => createItem(generateId()));
630
+ rebuildIndexCache();
631
+ ctx.dirtyFields.value = {
632
+ ...ctx.dirtyFields.value,
633
+ [name]: true
634
+ };
635
+ if (ctx.options.mode === "onChange") validate(name);
636
+ };
384
637
  return {
385
638
  value: fa.items.value,
386
639
  append,
@@ -389,31 +642,56 @@ function createFieldArrayManager(ctx, validate) {
389
642
  insert,
390
643
  swap,
391
644
  move,
392
- update
645
+ update,
646
+ replace
393
647
  };
394
648
  }
395
649
  return { fields };
396
650
  }
397
651
  function useForm(options) {
398
652
  const ctx = createFormContext(options);
399
- const { validate } = createValidation(ctx);
653
+ const { validate, clearAllPendingErrors } = createValidation(ctx);
400
654
  const { register, unregister } = createFieldRegistration(ctx, validate);
401
- const { fields } = createFieldArrayManager(ctx, validate);
402
- const formState = (0, vue.computed)(() => ({
403
- errors: ctx.errors.value,
404
- isDirty: Object.keys(ctx.dirtyFields.value).some((k) => ctx.dirtyFields.value[k]),
405
- dirtyFields: ctx.dirtyFields.value,
406
- isValid: (ctx.submitCount.value > 0 || Object.keys(ctx.touchedFields.value).length > 0) && Object.keys(ctx.errors.value).length === 0,
407
- isSubmitting: ctx.isSubmitting.value,
408
- isLoading: ctx.isLoading.value,
409
- touchedFields: ctx.touchedFields.value,
410
- submitCount: ctx.submitCount.value
411
- }));
655
+ function setFocus(name, focusOptions) {
656
+ const fieldRef = ctx.fieldRefs.get(name);
657
+ if (!fieldRef?.value) return;
658
+ const el = fieldRef.value;
659
+ if (typeof el.focus === "function") {
660
+ el.focus();
661
+ if (focusOptions?.shouldSelect && el instanceof HTMLInputElement && typeof el.select === "function") el.select();
662
+ }
663
+ }
664
+ const setFocusWrapper = (name) => setFocus(name);
665
+ const { fields } = createFieldArrayManager(ctx, validate, setFocusWrapper);
666
+ const formState = (0, vue.computed)(() => {
667
+ const mergedErrors = {
668
+ ...ctx.errors.value,
669
+ ...ctx.externalErrors.value
670
+ };
671
+ return {
672
+ errors: mergedErrors,
673
+ isDirty: Object.keys(ctx.dirtyFields.value).some((k) => ctx.dirtyFields.value[k]),
674
+ dirtyFields: ctx.dirtyFields.value,
675
+ isValid: (ctx.submitCount.value > 0 || Object.keys(ctx.touchedFields.value).length > 0) && Object.keys(mergedErrors).length === 0,
676
+ isSubmitting: ctx.isSubmitting.value,
677
+ isLoading: ctx.isLoading.value,
678
+ isReady: !ctx.isLoading.value,
679
+ isValidating: Object.keys(ctx.validatingFields.value).some((k) => ctx.validatingFields.value[k]),
680
+ validatingFields: ctx.validatingFields.value,
681
+ touchedFields: ctx.touchedFields.value,
682
+ submitCount: ctx.submitCount.value,
683
+ defaultValuesError: ctx.defaultValuesError.value,
684
+ isSubmitted: ctx.submitCount.value > 0,
685
+ isSubmitSuccessful: ctx.isSubmitSuccessful.value
686
+ };
687
+ });
412
688
  function handleSubmit(onValid, onInvalid) {
413
689
  return async (e) => {
414
690
  e.preventDefault();
691
+ if (ctx.isSubmitting.value) return;
415
692
  ctx.isSubmitting.value = true;
416
693
  ctx.submitCount.value++;
694
+ ctx.isSubmitSuccessful.value = false;
417
695
  try {
418
696
  for (const [name, fieldRef] of ctx.fieldRefs.entries()) {
419
697
  const el = fieldRef.value;
@@ -424,41 +702,60 @@ function useForm(options) {
424
702
  }
425
703
  }
426
704
  }
427
- if (await validate()) await onValid(ctx.formData);
428
- else onInvalid?.(ctx.errors.value);
705
+ if (await validate()) {
706
+ await onValid(ctx.formData);
707
+ ctx.isSubmitSuccessful.value = true;
708
+ } else {
709
+ onInvalid?.(formState.value.errors);
710
+ if (options.shouldFocusError !== false) {
711
+ const firstErrorField = Object.keys(formState.value.errors)[0];
712
+ if (firstErrorField) setFocus(firstErrorField);
713
+ }
714
+ }
429
715
  } finally {
430
716
  ctx.isSubmitting.value = false;
431
717
  }
432
718
  };
433
719
  }
434
- function setValue(name, value) {
720
+ function setValue(name, value, setValueOptions) {
435
721
  set(ctx.formData, name, value);
436
- ctx.dirtyFields.value = {
722
+ if (setValueOptions?.shouldDirty !== false) ctx.dirtyFields.value = {
437
723
  ...ctx.dirtyFields.value,
438
724
  [name]: true
439
725
  };
440
- const fieldRef = ctx.fieldRefs.get(name);
441
- if (fieldRef?.value) {
442
- const el = fieldRef.value;
443
- if (el.type === "checkbox") el.checked = value;
444
- else el.value = value;
726
+ if (setValueOptions?.shouldTouch) ctx.touchedFields.value = {
727
+ ...ctx.touchedFields.value,
728
+ [name]: true
729
+ };
730
+ if (!ctx.fieldOptions.get(name)?.controlled) {
731
+ const fieldRef = ctx.fieldRefs.get(name);
732
+ if (fieldRef?.value) {
733
+ const el = fieldRef.value;
734
+ if (el.type === "checkbox") el.checked = value;
735
+ else el.value = value;
736
+ }
445
737
  }
446
- if (options.mode === "onChange" || ctx.touchedFields.value[name]) validate(name);
738
+ if (setValueOptions?.shouldValidate) validate(name);
447
739
  }
448
740
  function getValue(name) {
449
741
  return get(ctx.formData, name);
450
742
  }
451
743
  function reset(values, resetOptions) {
452
744
  const opts = resetOptions || {};
745
+ ctx.resetGeneration.value++;
746
+ clearAllPendingErrors();
747
+ ctx.validatingFields.value = {};
453
748
  if (!opts.keepDefaultValues && values) Object.assign(ctx.defaultValues, values);
454
749
  Object.keys(ctx.formData).forEach((key) => delete ctx.formData[key]);
455
- const newValues = values || ctx.defaultValues;
750
+ const sourceValues = values || ctx.defaultValues;
751
+ const newValues = JSON.parse(JSON.stringify(sourceValues));
456
752
  Object.assign(ctx.formData, newValues);
457
753
  if (!opts.keepErrors) ctx.errors.value = {};
458
754
  if (!opts.keepTouched) ctx.touchedFields.value = {};
459
755
  if (!opts.keepDirty) ctx.dirtyFields.value = {};
460
756
  if (!opts.keepSubmitCount) ctx.submitCount.value = 0;
461
757
  if (!opts.keepIsSubmitting) ctx.isSubmitting.value = false;
758
+ if (!opts.keepIsSubmitSuccessful) ctx.isSubmitSuccessful.value = false;
462
759
  ctx.fieldArrays.clear();
463
760
  for (const [name, fieldRef] of ctx.fieldRefs.entries()) {
464
761
  const el = fieldRef.value;
@@ -469,13 +766,54 @@ function useForm(options) {
469
766
  }
470
767
  }
471
768
  }
472
- function watch(name) {
769
+ function resetField(name, resetFieldOptions) {
770
+ const opts = resetFieldOptions || {};
771
+ ctx.resetGeneration.value++;
772
+ const errorTimer = ctx.errorDelayTimers.get(name);
773
+ if (errorTimer) {
774
+ clearTimeout(errorTimer);
775
+ ctx.errorDelayTimers.delete(name);
776
+ }
777
+ ctx.pendingErrors.delete(name);
778
+ let defaultValue = opts.defaultValue;
779
+ if (defaultValue === void 0) defaultValue = get(ctx.defaultValues, name);
780
+ else set(ctx.defaultValues, name, defaultValue);
781
+ const clonedValue = defaultValue !== void 0 ? JSON.parse(JSON.stringify(defaultValue)) : void 0;
782
+ set(ctx.formData, name, clonedValue);
783
+ if (!opts.keepError) {
784
+ const newErrors = { ...ctx.errors.value };
785
+ for (const key of Object.keys(newErrors)) if (key === name || key.startsWith(`${name}.`)) delete newErrors[key];
786
+ ctx.errors.value = newErrors;
787
+ }
788
+ if (!opts.keepDirty) {
789
+ const newDirty = { ...ctx.dirtyFields.value };
790
+ delete newDirty[name];
791
+ ctx.dirtyFields.value = newDirty;
792
+ }
793
+ if (!opts.keepTouched) {
794
+ const newTouched = { ...ctx.touchedFields.value };
795
+ delete newTouched[name];
796
+ ctx.touchedFields.value = newTouched;
797
+ }
798
+ if (!ctx.fieldOptions.get(name)?.controlled) {
799
+ const fieldRef = ctx.fieldRefs.get(name);
800
+ if (fieldRef?.value) {
801
+ const el = fieldRef.value;
802
+ if (clonedValue !== void 0) if (el.type === "checkbox") el.checked = clonedValue;
803
+ else el.value = clonedValue;
804
+ else if (el.type === "checkbox") el.checked = false;
805
+ else el.value = "";
806
+ }
807
+ }
808
+ }
809
+ function watch$1(name) {
473
810
  return (0, vue.computed)(() => {
474
811
  if (!name) return ctx.formData;
475
- if (Array.isArray(name)) return name.reduce((acc, n) => {
476
- acc[n] = get(ctx.formData, n);
477
- return acc;
478
- }, {});
812
+ if (Array.isArray(name)) {
813
+ const result = {};
814
+ for (const n of name) result[n] = get(ctx.formData, n);
815
+ return result;
816
+ }
479
817
  return get(ctx.formData, name);
480
818
  });
481
819
  }
@@ -533,15 +871,6 @@ function useForm(options) {
533
871
  }
534
872
  return await validate(name);
535
873
  }
536
- function setFocus(name, focusOptions) {
537
- const fieldRef = ctx.fieldRefs.get(name);
538
- if (!fieldRef?.value) return;
539
- const el = fieldRef.value;
540
- if (typeof el.focus === "function") {
541
- el.focus();
542
- if (focusOptions?.shouldSelect && el instanceof HTMLInputElement && typeof el.select === "function") el.select();
543
- }
544
- }
545
874
  return {
546
875
  register,
547
876
  unregister,
@@ -551,7 +880,8 @@ function useForm(options) {
551
880
  setValue,
552
881
  getValue,
553
882
  reset,
554
- watch,
883
+ resetField,
884
+ watch: watch$1,
555
885
  validate,
556
886
  clearErrors,
557
887
  setError,
@@ -633,7 +963,11 @@ function useFormState(options = {}) {
633
963
  return { [name]: fullState[name] };
634
964
  });
635
965
  }
966
+ function isFieldError(error) {
967
+ return typeof error === "object" && error !== null && "type" in error && "message" in error && typeof error.type === "string" && typeof error.message === "string";
968
+ }
636
969
  exports.FormContextKey = FormContextKey;
970
+ exports.isFieldError = isFieldError;
637
971
  exports.provideForm = provideForm;
638
972
  exports.useController = useController;
639
973
  exports.useForm = useForm;