@formisch/react 0.5.0 → 0.7.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
@@ -123,11 +123,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
123
123
  else {
124
124
  internalFieldStore.schema = schema;
125
125
  internalFieldStore.name = JSON.stringify(path);
126
+ internalFieldStore.path = path;
126
127
  const initialElements = [];
127
128
  internalFieldStore.initialElements = initialElements;
128
129
  internalFieldStore.elements = initialElements;
129
130
  internalFieldStore.errors = /* @__PURE__ */ createSignal(null);
130
131
  internalFieldStore.isTouched = /* @__PURE__ */ createSignal(false);
132
+ internalFieldStore.isEdited = /* @__PURE__ */ createSignal(false);
131
133
  internalFieldStore.isDirty = /* @__PURE__ */ createSignal(false);
132
134
  if (schema.type === "array" || schema.type === "loose_tuple" || schema.type === "strict_tuple" || schema.type === "tuple") {
133
135
  if (internalFieldStore.kind && internalFieldStore.kind !== "array") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "array"`);
@@ -137,16 +139,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
137
139
  if (schema.type === "array") {
138
140
  if (initialInput) for (let index = 0; index < initialInput.length; index++) {
139
141
  internalFieldStore.children[index] = {};
140
- path.push(index);
141
- initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], path);
142
- path.pop();
142
+ initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], [...path, index]);
143
143
  }
144
144
  } else for (let index = 0; index < schema.items.length; index++) {
145
145
  internalFieldStore.children[index] = {};
146
- path.push(index);
147
- initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], path);
148
- path.pop();
146
+ initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], [...path, index]);
149
147
  }
148
+ internalFieldStore.isNullish = nullish;
150
149
  const arrayInput = nullish && initialInput == null ? initialInput : true;
151
150
  internalFieldStore.initialInput = /* @__PURE__ */ createSignal(arrayInput);
152
151
  internalFieldStore.startInput = /* @__PURE__ */ createSignal(arrayInput);
@@ -163,10 +162,9 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
163
162
  internalFieldStore.children ??= {};
164
163
  for (const key in schema.entries) {
165
164
  internalFieldStore.children[key] ??= {};
166
- path.push(key);
167
- initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
168
- path.pop();
165
+ initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], [...path, key]);
169
166
  }
167
+ internalFieldStore.isNullish = nullish;
170
168
  const objectInput = nullish && initialInput == null ? initialInput : true;
171
169
  internalFieldStore.initialInput = /* @__PURE__ */ createSignal(objectInput);
172
170
  internalFieldStore.startInput = /* @__PURE__ */ createSignal(objectInput);
@@ -186,8 +184,9 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
186
184
  /**
187
185
  * Copies the deeply nested state (signal values) from one field store to
188
186
  * another. This includes the `elements`, `errors`, `startInput`, `input`,
189
- * `isTouched`, `isDirty`, and for arrays `startItems` and `items` properties.
190
- * Recursively walks through the field stores and copies all signal values.
187
+ * `isTouched`, `isEdited`, `isDirty`, and for arrays `startItems` and `items`
188
+ * properties. Recursively walks through the field stores and copies all signal
189
+ * values.
191
190
  *
192
191
  * @param fromInternalFieldStore The source field store to copy from.
193
192
  * @param toInternalFieldStore The destination field store to copy to.
@@ -200,19 +199,16 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
200
199
  toInternalFieldStore.startInput.value = fromInternalFieldStore.startInput.value;
201
200
  toInternalFieldStore.input.value = fromInternalFieldStore.input.value;
202
201
  toInternalFieldStore.isTouched.value = fromInternalFieldStore.isTouched.value;
202
+ toInternalFieldStore.isEdited.value = fromInternalFieldStore.isEdited.value;
203
203
  toInternalFieldStore.isDirty.value = fromInternalFieldStore.isDirty.value;
204
204
  if (fromInternalFieldStore.kind === "array" && toInternalFieldStore.kind === "array") {
205
205
  const fromItems = fromInternalFieldStore.items.value;
206
206
  toInternalFieldStore.startItems.value = fromInternalFieldStore.startItems.value;
207
207
  toInternalFieldStore.items.value = fromItems;
208
- let path;
209
208
  for (let index = 0; index < fromItems.length; index++) {
210
209
  if (!toInternalFieldStore.children[index]) {
211
- path ??= JSON.parse(toInternalFieldStore.name);
212
210
  toInternalFieldStore.children[index] = {};
213
- path.push(index);
214
- initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, path);
215
- path.pop();
211
+ initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, [...toInternalFieldStore.path, index]);
216
212
  }
217
213
  copyItemState(fromInternalFieldStore.children[index], toInternalFieldStore.children[index]);
218
214
  }
@@ -222,45 +218,61 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
222
218
  }
223
219
  /**
224
220
  * Resets the state of a field store (signal values) deeply nested. Sets
225
- * `elements` to empty array, `errors` to `null`, `isTouched` and `isDirty` to
226
- * `false`, and `startInput`, `input`, `startItems`, and `items` to the new
227
- * input value. Keeps the `initialInput` and `initialItems` state unchanged for
228
- * form reset functionality.
221
+ * `elements` to empty array, `errors` to `null`, `isTouched`, `isEdited` and
222
+ * `isDirty` to `false`, and `startInput`, `input`, `startItems`, and `items` to
223
+ * the new input value. Keeps the `initialInput` and `initialItems` state
224
+ * unchanged for form reset functionality.
229
225
  *
230
226
  * @param internalFieldStore The field store to reset.
231
- * @param initialInput The new input value (can be any type including array or object).
227
+ * @param input The new input value (can be any type including array or object).
228
+ * @param keepStart Whether to keep `startInput` and `startItems` as the dirty
229
+ * baseline instead of resetting them to the new input. Used when a field store
230
+ * is reused for an in-place edit so its dirty state is detected correctly.
232
231
  */
233
- function resetItemState(internalFieldStore, initialInput) {
232
+ function resetItemState(internalFieldStore, input, keepStart = false) {
234
233
  batch(() => {
235
- internalFieldStore.elements = [];
234
+ const elements = [];
235
+ if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
236
+ internalFieldStore.elements = elements;
236
237
  internalFieldStore.errors.value = null;
237
238
  internalFieldStore.isTouched.value = false;
239
+ internalFieldStore.isEdited.value = false;
238
240
  internalFieldStore.isDirty.value = false;
239
241
  if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
240
- const objectInput = initialInput == null ? initialInput : true;
241
- internalFieldStore.startInput.value = objectInput;
242
+ const objectInput = internalFieldStore.isNullish && input == null ? input : true;
243
+ if (!keepStart) internalFieldStore.startInput.value = objectInput;
242
244
  internalFieldStore.input.value = objectInput;
243
- if (internalFieldStore.kind === "array") if (initialInput) {
244
- const newItems = initialInput.map(createId);
245
- internalFieldStore.startItems.value = newItems;
246
- internalFieldStore.items.value = newItems;
247
- for (let index = 0; index < initialInput.length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], initialInput[index]);
248
- } else {
249
- internalFieldStore.startItems.value = [];
250
- internalFieldStore.items.value = [];
251
- }
252
- else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], initialInput?.[key]);
245
+ if (internalFieldStore.kind === "array") {
246
+ const isTuple = internalFieldStore.schema.type !== "array";
247
+ if (input || isTuple) {
248
+ const length = isTuple ? internalFieldStore.children.length : input.length;
249
+ const newItems = Array.from({ length }, createId);
250
+ if (!keepStart) internalFieldStore.startItems.value = newItems;
251
+ internalFieldStore.items.value = newItems;
252
+ for (let index = 0; index < length; index++) {
253
+ const itemInput = input?.[index];
254
+ if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], itemInput, keepStart);
255
+ else {
256
+ internalFieldStore.children[index] = {};
257
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, itemInput, [...internalFieldStore.path, index]);
258
+ }
259
+ }
260
+ } else {
261
+ if (!keepStart) internalFieldStore.startItems.value = [];
262
+ internalFieldStore.items.value = [];
263
+ }
264
+ } else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
253
265
  } else {
254
- internalFieldStore.startInput.value = initialInput;
255
- internalFieldStore.input.value = initialInput;
266
+ if (!keepStart) internalFieldStore.startInput.value = input;
267
+ internalFieldStore.input.value = input;
256
268
  }
257
269
  });
258
270
  }
259
271
  /**
260
272
  * Swaps the deeply nested state (signal values) between two field stores. This
261
273
  * includes the `elements`, `errors`, `startInput`, `input`, `isTouched`,
262
- * `isDirty`, and for arrays `startItems` and `items` properties. Recursively
263
- * walks through the field stores and swaps all signal values.
274
+ * `isEdited`, `isDirty`, and for arrays `startItems` and `items` properties.
275
+ * Recursively walks through the field stores and swaps all signal values.
264
276
  *
265
277
  * @param firstInternalFieldStore The first field store to swap.
266
278
  * @param secondInternalFieldStore The second field store to swap.
@@ -283,6 +295,9 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
283
295
  const tempIsTouched = firstInternalFieldStore.isTouched.value;
284
296
  firstInternalFieldStore.isTouched.value = secondInternalFieldStore.isTouched.value;
285
297
  secondInternalFieldStore.isTouched.value = tempIsTouched;
298
+ const tempIsEdited = firstInternalFieldStore.isEdited.value;
299
+ firstInternalFieldStore.isEdited.value = secondInternalFieldStore.isEdited.value;
300
+ secondInternalFieldStore.isEdited.value = tempIsEdited;
286
301
  const tempIsDirty = firstInternalFieldStore.isDirty.value;
287
302
  firstInternalFieldStore.isDirty.value = secondInternalFieldStore.isDirty.value;
288
303
  secondInternalFieldStore.isDirty.value = tempIsDirty;
@@ -295,22 +310,14 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
295
310
  firstInternalFieldStore.items.value = secondItems;
296
311
  secondInternalFieldStore.items.value = firstItems;
297
312
  const maxLength = Math.max(firstItems.length, secondItems.length);
298
- let firstPath;
299
- let secondPath;
300
313
  for (let index = 0; index < maxLength; index++) {
301
314
  if (!firstInternalFieldStore.children[index]) {
302
- firstPath ??= JSON.parse(firstInternalFieldStore.name);
303
315
  firstInternalFieldStore.children[index] = {};
304
- firstPath.push(index);
305
- initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, firstPath);
306
- firstPath.pop();
316
+ initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, [...firstInternalFieldStore.path, index]);
307
317
  }
308
318
  if (!secondInternalFieldStore.children[index]) {
309
- secondPath ??= JSON.parse(secondInternalFieldStore.name);
310
319
  secondInternalFieldStore.children[index] = {};
311
- secondPath.push(index);
312
- initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, secondPath);
313
- secondPath.pop();
320
+ initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, [...secondInternalFieldStore.path, index]);
314
321
  }
315
322
  swapItemState(firstInternalFieldStore.children[index], secondInternalFieldStore.children[index]);
316
323
  }
@@ -319,6 +326,51 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
319
326
  });
320
327
  }
321
328
  /**
329
+ * Focuses the first focusable element of a field store. The elements are tried
330
+ * in order and the first one that actually receives focus wins, so detached,
331
+ * disabled or hidden elements are skipped. The browser decides focusability,
332
+ * which is read back via the element's root `activeElement` so elements in a
333
+ * shadow root or another document are handled correctly.
334
+ *
335
+ * Hint: A `display: none` or `hidden` element is correctly skipped in real
336
+ * browsers, but jsdom has no layout and focuses it anyway, so that case cannot
337
+ * be covered by unit tests.
338
+ *
339
+ * @param internalFieldStore The field store to focus.
340
+ *
341
+ * @returns Whether an element was focused.
342
+ */
343
+ function focusFieldElement(internalFieldStore) {
344
+ for (const element of internalFieldStore.elements) {
345
+ element.focus();
346
+ if (element.getRootNode().activeElement === element) return true;
347
+ }
348
+ return false;
349
+ }
350
+ /**
351
+ * Walks through the field store and all nested children, calling the callback
352
+ * for each field store in depth-first order. The callback may return `true` to
353
+ * stop the walk early, in which case `walkFieldStore` returns `true` as well.
354
+ *
355
+ * The walk reads array `items` reactively, so a reactive caller subscribes to
356
+ * structural changes naturally. Imperative callers that must not subscribe
357
+ * (e.g. when invoked inside an effect) should wrap the call in `untrack`.
358
+ *
359
+ * @param internalFieldStore The field store to walk.
360
+ * @param callback The callback to invoke for each field store. Return `true` to stop the walk early.
361
+ *
362
+ * @returns Whether the walk was stopped early by the callback.
363
+ */
364
+ function walkFieldStore(internalFieldStore, callback) {
365
+ if (callback(internalFieldStore)) return true;
366
+ if (internalFieldStore.kind === "array") {
367
+ for (let index = 0; index < internalFieldStore.items.value.length; index++) if (walkFieldStore(internalFieldStore.children[index], callback)) return true;
368
+ } else if (internalFieldStore.kind === "object") {
369
+ for (const key in internalFieldStore.children) if (walkFieldStore(internalFieldStore.children[key], callback)) return true;
370
+ }
371
+ return false;
372
+ }
373
+ /**
322
374
  * Returns whether the specified boolean property is true for the field store
323
375
  * or any of its nested children. Recursively checks arrays and objects.
324
376
  *
@@ -329,16 +381,7 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
329
381
  */
330
382
  /* @__NO_SIDE_EFFECTS__ */
331
383
  function getFieldBool(internalFieldStore, type) {
332
- if (internalFieldStore[type].value) return true;
333
- if (internalFieldStore.kind === "array") {
334
- for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
335
- return false;
336
- }
337
- if (internalFieldStore.kind == "object") {
338
- for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
339
- return false;
340
- }
341
- return false;
384
+ return walkFieldStore(internalFieldStore, (internalFieldStore$1) => Boolean(internalFieldStore$1[type].value));
342
385
  }
343
386
  /**
344
387
  * Returns only the dirty input of the field store. Arrays are treated as
@@ -456,11 +499,11 @@ function getFieldStore(internalFormStore, path) {
456
499
  */
457
500
  function setFieldBool(internalFieldStore, type, bool) {
458
501
  batch(() => {
459
- if (internalFieldStore.kind === "array") {
460
- internalFieldStore[type].value = bool;
461
- for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
462
- } else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
463
- else internalFieldStore[type].value = bool;
502
+ untrack(() => {
503
+ walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
504
+ internalFieldStore$1[type].value = bool;
505
+ });
506
+ });
464
507
  });
465
508
  }
466
509
  /**
@@ -472,23 +515,21 @@ function setFieldBool(internalFieldStore, type, bool) {
472
515
  */
473
516
  function setNestedInput(internalFieldStore, input) {
474
517
  internalFieldStore.isTouched.value = true;
518
+ internalFieldStore.isEdited.value = true;
475
519
  if (internalFieldStore.kind === "array") {
476
520
  const arrayInput = input ?? [];
477
521
  const items = internalFieldStore.items.value;
478
- if (arrayInput.length < items.length) internalFieldStore.items.value = items.slice(0, arrayInput.length);
479
- else if (arrayInput.length > items.length) {
480
- if (arrayInput.length > internalFieldStore.children.length) {
481
- const path = JSON.parse(internalFieldStore.name);
482
- for (let index = internalFieldStore.children.length; index < arrayInput.length; index++) {
483
- internalFieldStore.children[index] = {};
484
- path.push(index);
485
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
486
- path.pop();
487
- }
522
+ const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
523
+ if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
524
+ else if (length > items.length) {
525
+ for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
526
+ else {
527
+ internalFieldStore.children[index] = {};
528
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], [...internalFieldStore.path, index]);
488
529
  }
489
- internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createId)];
530
+ internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
490
531
  }
491
- for (let index = 0; index < arrayInput.length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
532
+ for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
492
533
  internalFieldStore.input.value = input == null ? input : true;
493
534
  internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
494
535
  } else if (internalFieldStore.kind === "object") {
@@ -532,38 +573,22 @@ function setFieldInput(internalFormStore, path, input) {
532
573
  function setInitialFieldInput(internalFieldStore, initialInput) {
533
574
  batch(() => {
534
575
  if (internalFieldStore.kind === "array") {
535
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
576
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
536
577
  const initialArrayInput = initialInput ?? [];
537
- if (initialArrayInput.length > internalFieldStore.children.length) {
538
- const path = JSON.parse(internalFieldStore.name);
539
- for (let index = internalFieldStore.children.length; index < initialArrayInput.length; index++) {
540
- internalFieldStore.children[index] = {};
541
- path.push(index);
542
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
543
- path.pop();
544
- }
578
+ const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
579
+ if (length > internalFieldStore.children.length) for (let index = internalFieldStore.children.length; index < length; index++) {
580
+ internalFieldStore.children[index] = {};
581
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], [...internalFieldStore.path, index]);
545
582
  }
546
- internalFieldStore.initialItems.value = initialArrayInput.map(createId);
583
+ internalFieldStore.initialItems.value = Array.from({ length }, createId);
547
584
  for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
548
585
  } else if (internalFieldStore.kind === "object") {
549
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
586
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
550
587
  for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
551
588
  } else internalFieldStore.initialInput.value = initialInput;
552
589
  });
553
590
  }
554
591
  /**
555
- * Walks through the field store and all nested children, calling the callback
556
- * for each field store in depth-first order.
557
- *
558
- * @param internalFieldStore The field store to walk.
559
- * @param callback The callback to invoke for each field store.
560
- */
561
- function walkFieldStore(internalFieldStore, callback) {
562
- callback(internalFieldStore);
563
- if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) walkFieldStore(internalFieldStore.children[index], callback);
564
- else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) walkFieldStore(internalFieldStore.children[key], callback);
565
- }
566
- /**
567
592
  * Creates a new internal form store from the provided configuration.
568
593
  * Initializes the field store hierarchy, sets validation modes, and
569
594
  * creates form state signals.
@@ -598,44 +623,51 @@ function createFormStore(config, parse) {
598
623
  async function validateFormInput(internalFormStore, config) {
599
624
  internalFormStore.validators++;
600
625
  internalFormStore.isValidating.value = true;
601
- const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
602
- let rootErrors;
603
- let nestedErrors;
604
- if (result.issues) {
605
- nestedErrors = {};
606
- for (const issue of result.issues) if (issue.path) {
607
- const path = [];
608
- for (const pathItem of issue.path) {
609
- const key = pathItem.key;
610
- const keyType = typeof key;
611
- const itemType = pathItem.type;
612
- if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
613
- path.push(key);
614
- }
615
- const name = JSON.stringify(path);
616
- const fieldErrors = nestedErrors[name];
617
- if (fieldErrors) fieldErrors.push(issue.message);
618
- else nestedErrors[name] = [issue.message];
619
- } else if (rootErrors) rootErrors.push(issue.message);
620
- else rootErrors = [issue.message];
621
- }
622
- let shouldFocus = config?.shouldFocus ?? false;
623
- batch(() => {
624
- walkFieldStore(internalFormStore, (internalFieldStore) => {
625
- if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
626
- else {
627
- const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
628
- internalFieldStore.errors.value = fieldErrors;
629
- if (shouldFocus && fieldErrors) {
630
- internalFieldStore.elements[0]?.focus();
631
- shouldFocus = false;
626
+ try {
627
+ const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
628
+ let rootErrors;
629
+ let nestedErrors;
630
+ if (result.issues) {
631
+ nestedErrors = {};
632
+ for (const issue of result.issues) if (issue.path) {
633
+ const path = [];
634
+ for (const pathItem of issue.path) {
635
+ const key = pathItem.key;
636
+ const keyType = typeof key;
637
+ const itemType = pathItem.type;
638
+ if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
639
+ path.push(key);
632
640
  }
633
- }
641
+ const name = JSON.stringify(path);
642
+ const fieldErrors = nestedErrors[name];
643
+ if (fieldErrors) fieldErrors.push(issue.message);
644
+ else nestedErrors[name] = [issue.message];
645
+ } else if (rootErrors) rootErrors.push(issue.message);
646
+ else rootErrors = [issue.message];
647
+ }
648
+ let shouldFocus = config?.shouldFocus ?? false;
649
+ batch(() => {
650
+ untrack(() => {
651
+ walkFieldStore(internalFormStore, (internalFieldStore) => {
652
+ if (internalFieldStore.path.length === 0) internalFieldStore.errors.value = rootErrors ?? null;
653
+ else {
654
+ const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
655
+ internalFieldStore.errors.value = fieldErrors;
656
+ if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
657
+ }
658
+ });
659
+ });
660
+ internalFormStore.validators--;
661
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
634
662
  });
635
- internalFormStore.validators--;
636
- internalFormStore.isValidating.value = internalFormStore.validators > 0;
637
- });
638
- return result;
663
+ return result;
664
+ } catch (error) {
665
+ batch(() => {
666
+ internalFormStore.validators--;
667
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
668
+ });
669
+ throw error;
670
+ }
639
671
  }
640
672
  /**
641
673
  * Validates the form input if required based on the validation mode and form
@@ -657,7 +689,7 @@ const INTERNAL = "~internal";
657
689
  //#endregion
658
690
  //#region ../../packages/methods/dist/index.react.js
659
691
  /**
660
- * Focuses the first input element of a field. This is useful for
692
+ * Focuses the first focusable input element of a field. This is useful for
661
693
  * programmatically setting focus to a specific field, such as after
662
694
  * validation errors or user interactions.
663
695
  *
@@ -665,27 +697,29 @@ const INTERNAL = "~internal";
665
697
  * @param config The focus field configuration.
666
698
  */
667
699
  function focus(form, config) {
668
- getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
700
+ focusFieldElement(getFieldStore(form[INTERNAL], config.path));
701
+ }
702
+ /* @__NO_SIDE_EFFECTS__ */
703
+ function getDeepErrorEntries(form, config) {
704
+ const entries = [];
705
+ walkFieldStore(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], (internalFieldStore) => {
706
+ const errors = internalFieldStore.errors.value;
707
+ if (errors) entries.push({
708
+ path: internalFieldStore.path,
709
+ errors
710
+ });
711
+ });
712
+ return entries;
669
713
  }
670
- /**
671
- * Retrieves all error messages from all fields in the form by walking through
672
- * the entire field store tree. This is useful for displaying a summary of all
673
- * validation errors across the form.
674
- *
675
- * @param form The form store to retrieve errors from.
676
- *
677
- * @returns A non-empty array of error messages, or null if no errors exist.
678
- */
679
714
  /* @__NO_SIDE_EFFECTS__ */
680
- function getAllErrors(form) {
681
- let allErrors = null;
682
- walkFieldStore(form[INTERNAL], (internalFieldStore) => {
683
- if (internalFieldStore.kind === "array") internalFieldStore.items.value;
715
+ function getDeepErrors(form, config) {
716
+ let deepErrors = null;
717
+ walkFieldStore(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], (internalFieldStore) => {
684
718
  const errors = internalFieldStore.errors.value;
685
- if (errors) if (allErrors) allErrors.push(...errors);
686
- else allErrors = [...errors];
719
+ if (errors) if (deepErrors) deepErrors.push(...errors);
720
+ else deepErrors = [...errors];
687
721
  });
688
- return allErrors;
722
+ return deepErrors;
689
723
  }
690
724
  /* @__NO_SIDE_EFFECTS__ */
691
725
  function getDirtyInput(form, config) {
@@ -693,9 +727,8 @@ function getDirtyInput(form, config) {
693
727
  }
694
728
  /* @__NO_SIDE_EFFECTS__ */
695
729
  function getDirtyPaths(form, config) {
696
- config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
697
730
  const paths = [];
698
- config?.path && [...config.path];
731
+ config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
699
732
  return paths;
700
733
  }
701
734
  /* @__NO_SIDE_EFFECTS__ */
@@ -750,25 +783,38 @@ function insert(form, config) {
750
783
  internalArrayStore.items.value = newItems;
751
784
  for (let index = items.length; index > insertIndex; index--) {
752
785
  if (!internalArrayStore.children[index]) {
753
- const path = JSON.parse(internalArrayStore.name);
754
786
  internalArrayStore.children[index] = {};
755
- path.push(index);
756
- initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, path);
787
+ initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, [...internalArrayStore.path, index]);
757
788
  }
758
789
  copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
759
790
  }
760
791
  if (!internalArrayStore.children[insertIndex]) {
761
- const path = JSON.parse(internalArrayStore.name);
762
792
  internalArrayStore.children[insertIndex] = {};
763
- path.push(insertIndex);
764
- initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, path);
793
+ initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, [...internalArrayStore.path, insertIndex]);
765
794
  } else resetItemState(internalArrayStore.children[insertIndex], config.initialInput);
766
795
  internalArrayStore.input.value = true;
767
796
  internalArrayStore.isTouched.value = true;
797
+ internalArrayStore.isEdited.value = true;
768
798
  internalArrayStore.isDirty.value = true;
769
799
  validateIfRequired(internalFormStore, internalArrayStore, "input");
770
800
  });
771
801
  }
802
+ /* @__NO_SIDE_EFFECTS__ */
803
+ function isDirty(form, config) {
804
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isDirty");
805
+ }
806
+ /* @__NO_SIDE_EFFECTS__ */
807
+ function isEdited(form, config) {
808
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isEdited");
809
+ }
810
+ /* @__NO_SIDE_EFFECTS__ */
811
+ function isTouched(form, config) {
812
+ return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isTouched");
813
+ }
814
+ /* @__NO_SIDE_EFFECTS__ */
815
+ function isValid(form, config) {
816
+ return !getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "errors");
817
+ }
772
818
  /**
773
819
  * Moves an item from one index to another within a field array. All items
774
820
  * between the source and destination indices are shifted accordingly.
@@ -791,6 +837,7 @@ function move(form, config) {
791
837
  else for (let index = config.from; index > config.to; index--) copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
792
838
  copyItemState(tempInternalFieldStore, internalArrayStore.children[config.to]);
793
839
  internalArrayStore.isTouched.value = true;
840
+ internalArrayStore.isEdited.value = true;
794
841
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
795
842
  validateIfRequired(internalFormStore, internalArrayStore, "input");
796
843
  });
@@ -854,6 +901,7 @@ function remove(form, config) {
854
901
  internalArrayStore.items.value = newItems;
855
902
  for (let index = config.at; index < items.length - 1; index++) copyItemState(internalArrayStore.children[index + 1], internalArrayStore.children[index]);
856
903
  internalArrayStore.isTouched.value = true;
904
+ internalArrayStore.isEdited.value = true;
857
905
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
858
906
  validateIfRequired(internalFormStore, internalArrayStore, "input");
859
907
  });
@@ -874,6 +922,7 @@ function replace(form, config) {
874
922
  internalArrayStore.items.value = newItems;
875
923
  resetItemState(internalArrayStore.children[config.at], config.initialInput);
876
924
  internalArrayStore.isTouched.value = true;
925
+ internalArrayStore.isEdited.value = true;
877
926
  internalArrayStore.isDirty.value = true;
878
927
  validateIfRequired(internalFormStore, internalArrayStore, "input");
879
928
  });
@@ -888,6 +937,7 @@ function reset(form, config) {
888
937
  internalFieldStore$1.elements = internalFieldStore$1.initialElements;
889
938
  if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
890
939
  if (!config?.keepTouched) internalFieldStore$1.isTouched.value = false;
940
+ if (!config?.keepEdited) internalFieldStore$1.isEdited.value = false;
891
941
  internalFieldStore$1.startInput.value = internalFieldStore$1.initialInput.value;
892
942
  if (!config?.keepInput) internalFieldStore$1.input.value = internalFieldStore$1.initialInput.value;
893
943
  if (internalFieldStore$1.kind === "array") {
@@ -948,6 +998,7 @@ function swap(form, config) {
948
998
  internalArrayStore.items.value = newItems;
949
999
  swapItemState(internalArrayStore.children[config.at], internalArrayStore.children[config.and]);
950
1000
  internalArrayStore.isTouched.value = true;
1001
+ internalArrayStore.isEdited.value = true;
951
1002
  internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
952
1003
  validateIfRequired(internalFormStore, internalArrayStore, "input");
953
1004
  });
@@ -1001,7 +1052,9 @@ function useField(form, config) {
1001
1052
  const internalFieldStore = getFieldStore(internalFormStore, config.path);
1002
1053
  useEffect(() => {
1003
1054
  return () => {
1004
- internalFieldStore.elements = internalFieldStore.elements.filter((element) => element.isConnected);
1055
+ const elements = internalFieldStore.elements.filter((element) => element.isConnected);
1056
+ if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
1057
+ internalFieldStore.elements = elements;
1005
1058
  };
1006
1059
  }, [internalFieldStore]);
1007
1060
  return useMemo(() => ({
@@ -1015,6 +1068,9 @@ function useField(form, config) {
1015
1068
  get isTouched() {
1016
1069
  return getFieldBool(internalFieldStore, "isTouched");
1017
1070
  },
1071
+ get isEdited() {
1072
+ return getFieldBool(internalFieldStore, "isEdited");
1073
+ },
1018
1074
  get isDirty() {
1019
1075
  return getFieldBool(internalFieldStore, "isDirty");
1020
1076
  },
@@ -1066,6 +1122,9 @@ function useFieldArray(form, config) {
1066
1122
  get isTouched() {
1067
1123
  return getFieldBool(internalFieldStore, "isTouched");
1068
1124
  },
1125
+ get isEdited() {
1126
+ return getFieldBool(internalFieldStore, "isEdited");
1127
+ },
1069
1128
  get isDirty() {
1070
1129
  return getFieldBool(internalFieldStore, "isDirty");
1071
1130
  },
@@ -1098,6 +1157,9 @@ function useForm(config) {
1098
1157
  get isTouched() {
1099
1158
  return getFieldBool(internalFormStore, "isTouched");
1100
1159
  },
1160
+ get isEdited() {
1161
+ return getFieldBool(internalFormStore, "isEdited");
1162
+ },
1101
1163
  get isDirty() {
1102
1164
  return getFieldBool(internalFormStore, "isDirty");
1103
1165
  },
@@ -1158,4 +1220,4 @@ function Form({ of, onSubmit, ...other }) {
1158
1220
  }
1159
1221
 
1160
1222
  //#endregion
1161
- export { Field, FieldArray, Form, focus, getAllErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };
1223
+ export { Field, FieldArray, 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 };