@formisch/vue 0.8.0 → 0.10.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/dist/index.js CHANGED
@@ -57,11 +57,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
57
57
  else {
58
58
  internalFieldStore.schema = schema;
59
59
  internalFieldStore.name = JSON.stringify(path);
60
+ internalFieldStore.path = path;
60
61
  const initialElements = [];
61
62
  internalFieldStore.initialElements = initialElements;
62
63
  internalFieldStore.elements = initialElements;
63
64
  internalFieldStore.errors = createSignal(null);
64
65
  internalFieldStore.isTouched = createSignal(false);
66
+ internalFieldStore.isEdited = createSignal(false);
65
67
  internalFieldStore.isDirty = createSignal(false);
66
68
  if (schema.type === "array" || schema.type === "loose_tuple" || schema.type === "strict_tuple" || schema.type === "tuple") {
67
69
  if (internalFieldStore.kind && internalFieldStore.kind !== "array") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "array"`);
@@ -71,16 +73,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
71
73
  if (schema.type === "array") {
72
74
  if (initialInput) for (let index = 0; index < initialInput.length; index++) {
73
75
  internalFieldStore.children[index] = {};
74
- path.push(index);
75
- initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], path);
76
- path.pop();
76
+ initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], [...path, index]);
77
77
  }
78
78
  } else for (let index = 0; index < schema.items.length; index++) {
79
79
  internalFieldStore.children[index] = {};
80
- path.push(index);
81
- initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], path);
82
- path.pop();
80
+ initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], [...path, index]);
83
81
  }
82
+ internalFieldStore.isNullish = nullish;
84
83
  const arrayInput = nullish && initialInput == null ? initialInput : true;
85
84
  internalFieldStore.initialInput = createSignal(arrayInput);
86
85
  internalFieldStore.startInput = createSignal(arrayInput);
@@ -97,10 +96,9 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
97
96
  internalFieldStore.children ??= {};
98
97
  for (const key in schema.entries) {
99
98
  internalFieldStore.children[key] ??= {};
100
- path.push(key);
101
- initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
102
- path.pop();
99
+ initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], [...path, key]);
103
100
  }
101
+ internalFieldStore.isNullish = nullish;
104
102
  const objectInput = nullish && initialInput == null ? initialInput : true;
105
103
  internalFieldStore.initialInput = createSignal(objectInput);
106
104
  internalFieldStore.startInput = createSignal(objectInput);
@@ -120,8 +118,9 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
120
118
  /**
121
119
  * Copies the deeply nested state (signal values) from one field store to
122
120
  * another. This includes the `elements`, `errors`, `startInput`, `input`,
123
- * `isTouched`, `isDirty`, and for arrays `startItems` and `items` properties.
124
- * Recursively walks through the field stores and copies all signal values.
121
+ * `isTouched`, `isEdited`, `isDirty`, and for arrays `startItems` and `items`
122
+ * properties. Recursively walks through the field stores and copies all signal
123
+ * values.
125
124
  *
126
125
  * @param fromInternalFieldStore The source field store to copy from.
127
126
  * @param toInternalFieldStore The destination field store to copy to.
@@ -134,19 +133,16 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
134
133
  toInternalFieldStore.startInput.value = fromInternalFieldStore.startInput.value;
135
134
  toInternalFieldStore.input.value = fromInternalFieldStore.input.value;
136
135
  toInternalFieldStore.isTouched.value = fromInternalFieldStore.isTouched.value;
136
+ toInternalFieldStore.isEdited.value = fromInternalFieldStore.isEdited.value;
137
137
  toInternalFieldStore.isDirty.value = fromInternalFieldStore.isDirty.value;
138
138
  if (fromInternalFieldStore.kind === "array" && toInternalFieldStore.kind === "array") {
139
139
  const fromItems = fromInternalFieldStore.items.value;
140
140
  toInternalFieldStore.startItems.value = fromInternalFieldStore.startItems.value;
141
141
  toInternalFieldStore.items.value = fromItems;
142
- let path;
143
142
  for (let index = 0; index < fromItems.length; index++) {
144
143
  if (!toInternalFieldStore.children[index]) {
145
- path ??= JSON.parse(toInternalFieldStore.name);
146
144
  toInternalFieldStore.children[index] = {};
147
- path.push(index);
148
- initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, path);
149
- path.pop();
145
+ initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, [...toInternalFieldStore.path, index]);
150
146
  }
151
147
  copyItemState(fromInternalFieldStore.children[index], toInternalFieldStore.children[index]);
152
148
  }
@@ -156,45 +152,61 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
156
152
  }
157
153
  /**
158
154
  * Resets the state of a field store (signal values) deeply nested. Sets
159
- * `elements` to empty array, `errors` to `null`, `isTouched` and `isDirty` to
160
- * `false`, and `startInput`, `input`, `startItems`, and `items` to the new
161
- * input value. Keeps the `initialInput` and `initialItems` state unchanged for
162
- * form reset functionality.
155
+ * `elements` to empty array, `errors` to `null`, `isTouched`, `isEdited` and
156
+ * `isDirty` to `false`, and `startInput`, `input`, `startItems`, and `items` to
157
+ * the new input value. Keeps the `initialInput` and `initialItems` state
158
+ * unchanged for form reset functionality.
163
159
  *
164
160
  * @param internalFieldStore The field store to reset.
165
- * @param initialInput The new input value (can be any type including array or object).
161
+ * @param input The new input value (can be any type including array or object).
162
+ * @param keepStart Whether to keep `startInput` and `startItems` as the dirty
163
+ * baseline instead of resetting them to the new input. Used when a field store
164
+ * is reused for an in-place edit so its dirty state is detected correctly.
166
165
  */
167
- function resetItemState(internalFieldStore, initialInput) {
166
+ function resetItemState(internalFieldStore, input, keepStart = false) {
168
167
  batch(() => {
169
- internalFieldStore.elements = [];
168
+ const elements = [];
169
+ if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
170
+ internalFieldStore.elements = elements;
170
171
  internalFieldStore.errors.value = null;
171
172
  internalFieldStore.isTouched.value = false;
173
+ internalFieldStore.isEdited.value = false;
172
174
  internalFieldStore.isDirty.value = false;
173
175
  if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
174
- const objectInput = initialInput == null ? initialInput : true;
175
- internalFieldStore.startInput.value = objectInput;
176
+ const objectInput = internalFieldStore.isNullish && input == null ? input : true;
177
+ if (!keepStart) internalFieldStore.startInput.value = objectInput;
176
178
  internalFieldStore.input.value = objectInput;
177
- if (internalFieldStore.kind === "array") if (initialInput) {
178
- const newItems = initialInput.map(createId);
179
- internalFieldStore.startItems.value = newItems;
180
- internalFieldStore.items.value = newItems;
181
- for (let index = 0; index < initialInput.length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], initialInput[index]);
182
- } else {
183
- internalFieldStore.startItems.value = [];
184
- internalFieldStore.items.value = [];
185
- }
186
- else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], initialInput?.[key]);
179
+ if (internalFieldStore.kind === "array") {
180
+ const isTuple = internalFieldStore.schema.type !== "array";
181
+ if (input || isTuple) {
182
+ const length = isTuple ? internalFieldStore.children.length : input.length;
183
+ const newItems = Array.from({ length }, createId);
184
+ if (!keepStart) internalFieldStore.startItems.value = newItems;
185
+ internalFieldStore.items.value = newItems;
186
+ for (let index = 0; index < length; index++) {
187
+ const itemInput = input?.[index];
188
+ if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], itemInput, keepStart);
189
+ else {
190
+ internalFieldStore.children[index] = {};
191
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, itemInput, [...internalFieldStore.path, index]);
192
+ }
193
+ }
194
+ } else {
195
+ if (!keepStart) internalFieldStore.startItems.value = [];
196
+ internalFieldStore.items.value = [];
197
+ }
198
+ } else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
187
199
  } else {
188
- internalFieldStore.startInput.value = initialInput;
189
- internalFieldStore.input.value = initialInput;
200
+ if (!keepStart) internalFieldStore.startInput.value = input;
201
+ internalFieldStore.input.value = input;
190
202
  }
191
203
  });
192
204
  }
193
205
  /**
194
206
  * Swaps the deeply nested state (signal values) between two field stores. This
195
207
  * includes the `elements`, `errors`, `startInput`, `input`, `isTouched`,
196
- * `isDirty`, and for arrays `startItems` and `items` properties. Recursively
197
- * walks through the field stores and swaps all signal values.
208
+ * `isEdited`, `isDirty`, and for arrays `startItems` and `items` properties.
209
+ * Recursively walks through the field stores and swaps all signal values.
198
210
  *
199
211
  * @param firstInternalFieldStore The first field store to swap.
200
212
  * @param secondInternalFieldStore The second field store to swap.
@@ -217,6 +229,9 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
217
229
  const tempIsTouched = firstInternalFieldStore.isTouched.value;
218
230
  firstInternalFieldStore.isTouched.value = secondInternalFieldStore.isTouched.value;
219
231
  secondInternalFieldStore.isTouched.value = tempIsTouched;
232
+ const tempIsEdited = firstInternalFieldStore.isEdited.value;
233
+ firstInternalFieldStore.isEdited.value = secondInternalFieldStore.isEdited.value;
234
+ secondInternalFieldStore.isEdited.value = tempIsEdited;
220
235
  const tempIsDirty = firstInternalFieldStore.isDirty.value;
221
236
  firstInternalFieldStore.isDirty.value = secondInternalFieldStore.isDirty.value;
222
237
  secondInternalFieldStore.isDirty.value = tempIsDirty;
@@ -229,22 +244,14 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
229
244
  firstInternalFieldStore.items.value = secondItems;
230
245
  secondInternalFieldStore.items.value = firstItems;
231
246
  const maxLength = Math.max(firstItems.length, secondItems.length);
232
- let firstPath;
233
- let secondPath;
234
247
  for (let index = 0; index < maxLength; index++) {
235
248
  if (!firstInternalFieldStore.children[index]) {
236
- firstPath ??= JSON.parse(firstInternalFieldStore.name);
237
249
  firstInternalFieldStore.children[index] = {};
238
- firstPath.push(index);
239
- initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, firstPath);
240
- firstPath.pop();
250
+ initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, [...firstInternalFieldStore.path, index]);
241
251
  }
242
252
  if (!secondInternalFieldStore.children[index]) {
243
- secondPath ??= JSON.parse(secondInternalFieldStore.name);
244
253
  secondInternalFieldStore.children[index] = {};
245
- secondPath.push(index);
246
- initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, secondPath);
247
- secondPath.pop();
254
+ initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, [...secondInternalFieldStore.path, index]);
248
255
  }
249
256
  swapItemState(firstInternalFieldStore.children[index], secondInternalFieldStore.children[index]);
250
257
  }
@@ -253,6 +260,51 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
253
260
  });
254
261
  }
255
262
  /**
263
+ * Focuses the first focusable element of a field store. The elements are tried
264
+ * in order and the first one that actually receives focus wins, so detached,
265
+ * disabled or hidden elements are skipped. The browser decides focusability,
266
+ * which is read back via the element's root `activeElement` so elements in a
267
+ * shadow root or another document are handled correctly.
268
+ *
269
+ * Hint: A `display: none` or `hidden` element is correctly skipped in real
270
+ * browsers, but jsdom has no layout and focuses it anyway, so that case cannot
271
+ * be covered by unit tests.
272
+ *
273
+ * @param internalFieldStore The field store to focus.
274
+ *
275
+ * @returns Whether an element was focused.
276
+ */
277
+ function focusFieldElement(internalFieldStore) {
278
+ for (const element of internalFieldStore.elements) {
279
+ element.focus();
280
+ if (element.getRootNode().activeElement === element) return true;
281
+ }
282
+ return false;
283
+ }
284
+ /**
285
+ * Walks through the field store and all nested children, calling the callback
286
+ * for each field store in depth-first order. The callback may return `true` to
287
+ * stop the walk early, in which case `walkFieldStore` returns `true` as well.
288
+ *
289
+ * The walk reads array `items` reactively, so a reactive caller subscribes to
290
+ * structural changes naturally. Imperative callers that must not subscribe
291
+ * (e.g. when invoked inside an effect) should wrap the call in `untrack`.
292
+ *
293
+ * @param internalFieldStore The field store to walk.
294
+ * @param callback The callback to invoke for each field store. Return `true` to stop the walk early.
295
+ *
296
+ * @returns Whether the walk was stopped early by the callback.
297
+ */
298
+ function walkFieldStore(internalFieldStore, callback) {
299
+ if (callback(internalFieldStore)) return true;
300
+ if (internalFieldStore.kind === "array") {
301
+ for (let index = 0; index < internalFieldStore.items.value.length; index++) if (walkFieldStore(internalFieldStore.children[index], callback)) return true;
302
+ } else if (internalFieldStore.kind === "object") {
303
+ for (const key in internalFieldStore.children) if (walkFieldStore(internalFieldStore.children[key], callback)) return true;
304
+ }
305
+ return false;
306
+ }
307
+ /**
256
308
  * Returns whether the specified boolean property is true for the field store
257
309
  * or any of its nested children. Recursively checks arrays and objects.
258
310
  *
@@ -263,16 +315,7 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
263
315
  */
264
316
  /* @__NO_SIDE_EFFECTS__ */
265
317
  function getFieldBool(internalFieldStore, type) {
266
- if (internalFieldStore[type].value) return true;
267
- if (internalFieldStore.kind === "array") {
268
- for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
269
- return false;
270
- }
271
- if (internalFieldStore.kind == "object") {
272
- for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
273
- return false;
274
- }
275
- return false;
318
+ return walkFieldStore(internalFieldStore, (internalFieldStore$1) => Boolean(internalFieldStore$1[type].value));
276
319
  }
277
320
  /**
278
321
  * Returns only the dirty input of the field store. Arrays are treated as
@@ -363,11 +406,11 @@ function getFieldStore(internalFormStore, path) {
363
406
  */
364
407
  function setFieldBool(internalFieldStore, type, bool) {
365
408
  batch(() => {
366
- if (internalFieldStore.kind === "array") {
367
- internalFieldStore[type].value = bool;
368
- for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
369
- } else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
370
- else internalFieldStore[type].value = bool;
409
+ untrack(() => {
410
+ walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
411
+ internalFieldStore$1[type].value = bool;
412
+ });
413
+ });
371
414
  });
372
415
  }
373
416
  /**
@@ -379,23 +422,21 @@ function setFieldBool(internalFieldStore, type, bool) {
379
422
  */
380
423
  function setNestedInput(internalFieldStore, input) {
381
424
  internalFieldStore.isTouched.value = true;
425
+ internalFieldStore.isEdited.value = true;
382
426
  if (internalFieldStore.kind === "array") {
383
427
  const arrayInput = input ?? [];
384
428
  const items = internalFieldStore.items.value;
385
- if (arrayInput.length < items.length) internalFieldStore.items.value = items.slice(0, arrayInput.length);
386
- else if (arrayInput.length > items.length) {
387
- if (arrayInput.length > internalFieldStore.children.length) {
388
- const path = JSON.parse(internalFieldStore.name);
389
- for (let index = internalFieldStore.children.length; index < arrayInput.length; index++) {
390
- internalFieldStore.children[index] = {};
391
- path.push(index);
392
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
393
- path.pop();
394
- }
429
+ const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
430
+ if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
431
+ else if (length > items.length) {
432
+ for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
433
+ else {
434
+ internalFieldStore.children[index] = {};
435
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], [...internalFieldStore.path, index]);
395
436
  }
396
- internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createId)];
437
+ internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
397
438
  }
398
- for (let index = 0; index < arrayInput.length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
439
+ for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
399
440
  internalFieldStore.input.value = input == null ? input : true;
400
441
  internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
401
442
  } else if (internalFieldStore.kind === "object") {
@@ -439,38 +480,22 @@ function setFieldInput(internalFormStore, path, input) {
439
480
  function setInitialFieldInput(internalFieldStore, initialInput) {
440
481
  batch(() => {
441
482
  if (internalFieldStore.kind === "array") {
442
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
483
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
443
484
  const initialArrayInput = initialInput ?? [];
444
- if (initialArrayInput.length > internalFieldStore.children.length) {
445
- const path = JSON.parse(internalFieldStore.name);
446
- for (let index = internalFieldStore.children.length; index < initialArrayInput.length; index++) {
447
- internalFieldStore.children[index] = {};
448
- path.push(index);
449
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
450
- path.pop();
451
- }
485
+ const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
486
+ if (length > internalFieldStore.children.length) for (let index = internalFieldStore.children.length; index < length; index++) {
487
+ internalFieldStore.children[index] = {};
488
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], [...internalFieldStore.path, index]);
452
489
  }
453
- internalFieldStore.initialItems.value = initialArrayInput.map(createId);
490
+ internalFieldStore.initialItems.value = Array.from({ length }, createId);
454
491
  for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
455
492
  } else if (internalFieldStore.kind === "object") {
456
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
493
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
457
494
  for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
458
495
  } else internalFieldStore.initialInput.value = initialInput;
459
496
  });
460
497
  }
461
498
  /**
462
- * Walks through the field store and all nested children, calling the callback
463
- * for each field store in depth-first order.
464
- *
465
- * @param internalFieldStore The field store to walk.
466
- * @param callback The callback to invoke for each field store.
467
- */
468
- function walkFieldStore(internalFieldStore, callback) {
469
- callback(internalFieldStore);
470
- if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) walkFieldStore(internalFieldStore.children[index], callback);
471
- else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) walkFieldStore(internalFieldStore.children[key], callback);
472
- }
473
- /**
474
499
  * Creates a new internal form store from the provided configuration.
475
500
  * Initializes the field store hierarchy, sets validation modes, and
476
501
  * creates form state signals.
@@ -505,44 +530,51 @@ function createFormStore(config, parse) {
505
530
  async function validateFormInput(internalFormStore, config) {
506
531
  internalFormStore.validators++;
507
532
  internalFormStore.isValidating.value = true;
508
- const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
509
- let rootErrors;
510
- let nestedErrors;
511
- if (result.issues) {
512
- nestedErrors = {};
513
- for (const issue of result.issues) if (issue.path) {
514
- const path = [];
515
- for (const pathItem of issue.path) {
516
- const key = pathItem.key;
517
- const keyType = typeof key;
518
- const itemType = pathItem.type;
519
- if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
520
- path.push(key);
521
- }
522
- const name = JSON.stringify(path);
523
- const fieldErrors = nestedErrors[name];
524
- if (fieldErrors) fieldErrors.push(issue.message);
525
- else nestedErrors[name] = [issue.message];
526
- } else if (rootErrors) rootErrors.push(issue.message);
527
- else rootErrors = [issue.message];
528
- }
529
- let shouldFocus = config?.shouldFocus ?? false;
530
- batch(() => {
531
- walkFieldStore(internalFormStore, (internalFieldStore) => {
532
- if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
533
- else {
534
- const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
535
- internalFieldStore.errors.value = fieldErrors;
536
- if (shouldFocus && fieldErrors) {
537
- internalFieldStore.elements[0]?.focus();
538
- shouldFocus = false;
533
+ try {
534
+ const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
535
+ let rootErrors;
536
+ let nestedErrors;
537
+ if (result.issues) {
538
+ nestedErrors = {};
539
+ for (const issue of result.issues) if (issue.path) {
540
+ const path = [];
541
+ for (const pathItem of issue.path) {
542
+ const key = pathItem.key;
543
+ const keyType = typeof key;
544
+ const itemType = pathItem.type;
545
+ if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
546
+ path.push(key);
539
547
  }
540
- }
548
+ const name = JSON.stringify(path);
549
+ const fieldErrors = nestedErrors[name];
550
+ if (fieldErrors) fieldErrors.push(issue.message);
551
+ else nestedErrors[name] = [issue.message];
552
+ } else if (rootErrors) rootErrors.push(issue.message);
553
+ else rootErrors = [issue.message];
554
+ }
555
+ let shouldFocus = config?.shouldFocus ?? false;
556
+ batch(() => {
557
+ untrack(() => {
558
+ walkFieldStore(internalFormStore, (internalFieldStore) => {
559
+ if (internalFieldStore.path.length === 0) internalFieldStore.errors.value = rootErrors ?? null;
560
+ else {
561
+ const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
562
+ internalFieldStore.errors.value = fieldErrors;
563
+ if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
564
+ }
565
+ });
566
+ });
567
+ internalFormStore.validators--;
568
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
541
569
  });
542
- internalFormStore.validators--;
543
- internalFormStore.isValidating.value = internalFormStore.validators > 0;
544
- });
545
- return result;
570
+ return result;
571
+ } catch (error) {
572
+ batch(() => {
573
+ internalFormStore.validators--;
574
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
575
+ });
576
+ throw error;
577
+ }
546
578
  }
547
579
  /**
548
580
  * Validates the form input if required based on the validation mode and form
@@ -564,7 +596,7 @@ const INTERNAL = "~internal";
564
596
  //#endregion
565
597
  //#region ../../packages/methods/dist/index.vue.js
566
598
  /**
567
- * Focuses the first input element of a field. This is useful for
599
+ * Focuses the first focusable input element of a field. This is useful for
568
600
  * programmatically setting focus to a specific field, such as after
569
601
  * validation errors or user interactions.
570
602
  *
@@ -572,27 +604,29 @@ const INTERNAL = "~internal";
572
604
  * @param config The focus field configuration.
573
605
  */
574
606
  function focus(form, config) {
575
- getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
607
+ focusFieldElement(getFieldStore(form[INTERNAL], config.path));
608
+ }
609
+ /* @__NO_SIDE_EFFECTS__ */
610
+ function getDeepErrorEntries(form, config) {
611
+ const entries = [];
612
+ walkFieldStore(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], (internalFieldStore) => {
613
+ const errors = internalFieldStore.errors.value;
614
+ if (errors) entries.push({
615
+ path: internalFieldStore.path,
616
+ errors
617
+ });
618
+ });
619
+ return entries;
576
620
  }
577
- /**
578
- * Retrieves all error messages from all fields in the form by walking through
579
- * the entire field store tree. This is useful for displaying a summary of all
580
- * validation errors across the form.
581
- *
582
- * @param form The form store to retrieve errors from.
583
- *
584
- * @returns A non-empty array of error messages, or null if no errors exist.
585
- */
586
621
  /* @__NO_SIDE_EFFECTS__ */
587
- function getAllErrors(form) {
588
- let allErrors = null;
589
- walkFieldStore(form[INTERNAL], (internalFieldStore) => {
590
- if (internalFieldStore.kind === "array") internalFieldStore.items.value;
622
+ function getDeepErrors(form, config) {
623
+ let deepErrors = null;
624
+ walkFieldStore(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], (internalFieldStore) => {
591
625
  const errors = internalFieldStore.errors.value;
592
- if (errors) if (allErrors) allErrors.push(...errors);
593
- else allErrors = [...errors];
626
+ if (errors) if (deepErrors) deepErrors.push(...errors);
627
+ else deepErrors = [...errors];
594
628
  });
595
- return allErrors;
629
+ return deepErrors;
596
630
  }
597
631
  /* @__NO_SIDE_EFFECTS__ */
598
632
  function getDirtyInput(form, config) {
@@ -600,9 +634,8 @@ function getDirtyInput(form, config) {
600
634
  }
601
635
  /* @__NO_SIDE_EFFECTS__ */
602
636
  function getDirtyPaths(form, config) {
603
- config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
604
637
  const paths = [];
605
- config?.path && [...config.path];
638
+ config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
606
639
  return paths;
607
640
  }
608
641
  /* @__NO_SIDE_EFFECTS__ */
@@ -653,25 +686,38 @@ function insert(form, config) {
653
686
  internalArrayStore.items.value = newItems;
654
687
  for (let index = items.length; index > insertIndex; index--) {
655
688
  if (!internalArrayStore.children[index]) {
656
- const path = JSON.parse(internalArrayStore.name);
657
689
  internalArrayStore.children[index] = {};
658
- path.push(index);
659
- initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, path);
690
+ initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, [...internalArrayStore.path, index]);
660
691
  }
661
692
  copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
662
693
  }
663
694
  if (!internalArrayStore.children[insertIndex]) {
664
- const path = JSON.parse(internalArrayStore.name);
665
695
  internalArrayStore.children[insertIndex] = {};
666
- path.push(insertIndex);
667
- initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, path);
696
+ initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, [...internalArrayStore.path, insertIndex]);
668
697
  } else resetItemState(internalArrayStore.children[insertIndex], config.initialInput);
669
698
  internalArrayStore.input.value = true;
670
699
  internalArrayStore.isTouched.value = true;
700
+ internalArrayStore.isEdited.value = true;
671
701
  internalArrayStore.isDirty.value = true;
672
702
  validateIfRequired(internalFormStore, internalArrayStore, "input");
673
703
  });
674
704
  }
705
+ /* @__NO_SIDE_EFFECTS__ */
706
+ function isDirty(form, config) {
707
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isDirty");
708
+ }
709
+ /* @__NO_SIDE_EFFECTS__ */
710
+ function isEdited(form, config) {
711
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isEdited");
712
+ }
713
+ /* @__NO_SIDE_EFFECTS__ */
714
+ function isTouched(form, config) {
715
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isTouched");
716
+ }
717
+ /* @__NO_SIDE_EFFECTS__ */
718
+ function isValid(form, config) {
719
+ return !getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "errors");
720
+ }
675
721
  /**
676
722
  * Moves an item from one index to another within a field array. All items
677
723
  * between the source and destination indices are shifted accordingly.
@@ -694,6 +740,7 @@ function move(form, config) {
694
740
  else for (let index = config.from; index > config.to; index--) copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
695
741
  copyItemState(tempInternalFieldStore, internalArrayStore.children[config.to]);
696
742
  internalArrayStore.isTouched.value = true;
743
+ internalArrayStore.isEdited.value = true;
697
744
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
698
745
  validateIfRequired(internalFormStore, internalArrayStore, "input");
699
746
  });
@@ -757,6 +804,7 @@ function remove(form, config) {
757
804
  internalArrayStore.items.value = newItems;
758
805
  for (let index = config.at; index < items.length - 1; index++) copyItemState(internalArrayStore.children[index + 1], internalArrayStore.children[index]);
759
806
  internalArrayStore.isTouched.value = true;
807
+ internalArrayStore.isEdited.value = true;
760
808
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
761
809
  validateIfRequired(internalFormStore, internalArrayStore, "input");
762
810
  });
@@ -777,6 +825,7 @@ function replace(form, config) {
777
825
  internalArrayStore.items.value = newItems;
778
826
  resetItemState(internalArrayStore.children[config.at], config.initialInput);
779
827
  internalArrayStore.isTouched.value = true;
828
+ internalArrayStore.isEdited.value = true;
780
829
  internalArrayStore.isDirty.value = true;
781
830
  validateIfRequired(internalFormStore, internalArrayStore, "input");
782
831
  });
@@ -791,6 +840,7 @@ function reset(form, config) {
791
840
  internalFieldStore$1.elements = internalFieldStore$1.initialElements;
792
841
  if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
793
842
  if (!config?.keepTouched) internalFieldStore$1.isTouched.value = false;
843
+ if (!config?.keepEdited) internalFieldStore$1.isEdited.value = false;
794
844
  internalFieldStore$1.startInput.value = internalFieldStore$1.initialInput.value;
795
845
  if (!config?.keepInput) internalFieldStore$1.input.value = internalFieldStore$1.initialInput.value;
796
846
  if (internalFieldStore$1.kind === "array") {
@@ -849,6 +899,7 @@ function swap(form, config) {
849
899
  internalArrayStore.items.value = newItems;
850
900
  swapItemState(internalArrayStore.children[config.at], internalArrayStore.children[config.and]);
851
901
  internalArrayStore.isTouched.value = true;
902
+ internalArrayStore.isEdited.value = true;
852
903
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
853
904
  validateIfRequired(internalFormStore, internalArrayStore, "input");
854
905
  });
@@ -875,12 +926,16 @@ function useField(form, config) {
875
926
  const internalFormStore = computed(() => toValue(form)[INTERNAL]);
876
927
  const internalFieldStore = computed(() => getFieldStore(internalFormStore.value, path.value));
877
928
  onUnmounted(() => {
878
- internalFieldStore.value.elements = internalFieldStore.value.elements.filter((element) => element.isConnected);
929
+ const internalFieldStoreValue = internalFieldStore.value;
930
+ const elements = internalFieldStoreValue.elements.filter((element) => element.isConnected);
931
+ if (internalFieldStoreValue.elements === internalFieldStoreValue.initialElements) internalFieldStoreValue.initialElements = elements;
932
+ internalFieldStoreValue.elements = elements;
879
933
  });
880
934
  const input = computed(() => getFieldInput(internalFieldStore.value));
881
- const isTouched = computed(() => getFieldBool(internalFieldStore.value, "isTouched"));
882
- const isDirty = computed(() => getFieldBool(internalFieldStore.value, "isDirty"));
883
- const isValid = computed(() => !getFieldBool(internalFieldStore.value, "errors"));
935
+ const isTouched$1 = computed(() => getFieldBool(internalFieldStore.value, "isTouched"));
936
+ const isEdited$1 = computed(() => getFieldBool(internalFieldStore.value, "isEdited"));
937
+ const isDirty$1 = computed(() => getFieldBool(internalFieldStore.value, "isDirty"));
938
+ const isValid$1 = computed(() => !getFieldBool(internalFieldStore.value, "errors"));
884
939
  return {
885
940
  get path() {
886
941
  return path.value;
@@ -896,13 +951,16 @@ function useField(form, config) {
896
951
  return internalFieldStore.value.errors.value;
897
952
  },
898
953
  get isTouched() {
899
- return isTouched.value;
954
+ return isTouched$1.value;
955
+ },
956
+ get isEdited() {
957
+ return isEdited$1.value;
900
958
  },
901
959
  get isDirty() {
902
- return isDirty.value;
960
+ return isDirty$1.value;
903
961
  },
904
962
  get isValid() {
905
- return isValid.value;
963
+ return isValid$1.value;
906
964
  },
907
965
  props: {
908
966
  get name() {
@@ -931,9 +989,10 @@ function useField(form, config) {
931
989
  /* @__NO_SIDE_EFFECTS__ */
932
990
  function useFieldArray(form, config) {
933
991
  const internalFieldStore = computed(() => getFieldStore(toValue(form)[INTERNAL], toValue(config).path));
934
- const isTouched = computed(() => getFieldBool(internalFieldStore.value, "isTouched"));
935
- const isDirty = computed(() => getFieldBool(internalFieldStore.value, "isDirty"));
936
- const isValid = computed(() => !getFieldBool(internalFieldStore.value, "errors"));
992
+ const isTouched$1 = computed(() => getFieldBool(internalFieldStore.value, "isTouched"));
993
+ const isEdited$1 = computed(() => getFieldBool(internalFieldStore.value, "isEdited"));
994
+ const isDirty$1 = computed(() => getFieldBool(internalFieldStore.value, "isDirty"));
995
+ const isValid$1 = computed(() => !getFieldBool(internalFieldStore.value, "errors"));
937
996
  return {
938
997
  get path() {
939
998
  return toValue(config).path;
@@ -945,13 +1004,16 @@ function useFieldArray(form, config) {
945
1004
  return internalFieldStore.value.errors.value;
946
1005
  },
947
1006
  get isTouched() {
948
- return isTouched.value;
1007
+ return isTouched$1.value;
1008
+ },
1009
+ get isEdited() {
1010
+ return isEdited$1.value;
949
1011
  },
950
1012
  get isDirty() {
951
- return isDirty.value;
1013
+ return isDirty$1.value;
952
1014
  },
953
1015
  get isValid() {
954
- return isValid.value;
1016
+ return isValid$1.value;
955
1017
  }
956
1018
  };
957
1019
  }
@@ -964,9 +1026,10 @@ function useForm(config) {
964
1026
  onBeforeMount(async () => {
965
1027
  if (config.validate === "initial") await validateFormInput(internalFormStore);
966
1028
  });
967
- const isTouched = computed(() => getFieldBool(internalFormStore, "isTouched"));
968
- const isDirty = computed(() => getFieldBool(internalFormStore, "isDirty"));
969
- const isValid = computed(() => !getFieldBool(internalFormStore, "errors"));
1029
+ const isTouched$1 = computed(() => getFieldBool(internalFormStore, "isTouched"));
1030
+ const isEdited$1 = computed(() => getFieldBool(internalFormStore, "isEdited"));
1031
+ const isDirty$1 = computed(() => getFieldBool(internalFormStore, "isDirty"));
1032
+ const isValid$1 = computed(() => !getFieldBool(internalFormStore, "errors"));
970
1033
  return {
971
1034
  [INTERNAL]: internalFormStore,
972
1035
  get isSubmitting() {
@@ -979,13 +1042,16 @@ function useForm(config) {
979
1042
  return internalFormStore.isValidating.value;
980
1043
  },
981
1044
  get isTouched() {
982
- return isTouched.value;
1045
+ return isTouched$1.value;
1046
+ },
1047
+ get isEdited() {
1048
+ return isEdited$1.value;
983
1049
  },
984
1050
  get isDirty() {
985
- return isDirty.value;
1051
+ return isDirty$1.value;
986
1052
  },
987
1053
  get isValid() {
988
- return isValid.value;
1054
+ return isValid$1.value;
989
1055
  },
990
1056
  get errors() {
991
1057
  return internalFormStore.errors.value;
@@ -1065,4 +1131,4 @@ var Form_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCom
1065
1131
  var Form_default = Form_vue_vue_type_script_setup_true_lang_default;
1066
1132
 
1067
1133
  //#endregion
1068
- export { Field_default as Field, FieldArray_default as FieldArray, Form_default as Form, focus, getAllErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };
1134
+ export { Field_default as Field, FieldArray_default as FieldArray, Form_default as Form, focus, getDeepErrorEntries, getDeepErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, isDirty, isEdited, isTouched, isValid, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };