@dynamic-field-kit/react 1.5.0 → 1.6.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
@@ -32,24 +32,29 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  DynamicFormDevTools: () => DynamicFormDevTools,
34
34
  DynamicInput: () => DynamicInput_default,
35
+ FIELD_RENDERER_PROP_KEYS: () => import_core7.FIELD_RENDERER_PROP_KEYS,
35
36
  FieldInput: () => FieldInput_default,
36
37
  FieldRegistry: () => import_core6.FieldRegistry,
37
38
  FieldRegistryProvider: () => FieldRegistryProvider,
38
39
  MultiFieldInput: () => MultiFieldInput_default,
40
+ buildFieldRendererProps: () => import_core7.buildFieldRendererProps,
41
+ collectFieldPaths: () => import_core8.collectFieldPaths,
39
42
  defaultRenderersMap: () => defaultRenderersMap,
40
43
  fieldRegistry: () => fieldRegistry,
41
44
  getDefaultRenderer: () => getDefaultRenderer,
45
+ indexGroupPathMap: () => import_core8.indexGroupPathMap,
42
46
  layoutRegistry: () => layoutRegistry,
43
- resolveDisabled: () => import_core7.resolveDisabled,
44
- resolveOptions: () => import_core7.resolveOptions,
45
- resolveReadOnly: () => import_core7.resolveReadOnly,
47
+ makeFieldId: () => import_core7.makeFieldId,
48
+ resolveDisabled: () => import_core8.resolveDisabled,
49
+ resolveOptions: () => import_core8.resolveOptions,
50
+ resolveReadOnly: () => import_core8.resolveReadOnly,
46
51
  useDynamicForm: () => useDynamicForm,
47
52
  useFieldRegistry: () => useFieldRegistry,
48
- validateField: () => import_core7.validateField,
49
- validateFieldAsync: () => import_core7.validateFieldAsync,
50
- validateFields: () => import_core7.validateFields,
51
- validateFieldsAsync: () => import_core7.validateFieldsAsync,
52
- validators: () => import_core7.validators
53
+ validateField: () => import_core8.validateField,
54
+ validateFieldAsync: () => import_core8.validateFieldAsync,
55
+ validateFields: () => import_core8.validateFields,
56
+ validateFieldsAsync: () => import_core8.validateFieldsAsync,
57
+ validators: () => import_core8.validators
53
58
  });
54
59
  module.exports = __toCommonJS(index_exports);
55
60
 
@@ -491,24 +496,10 @@ function useFieldRegistry() {
491
496
  var import_jsx_runtime5 = require("react/jsx-runtime");
492
497
  var DynamicInputInner = ({
493
498
  type,
494
- value,
495
499
  onChange,
496
500
  onBlur,
497
- label,
498
- options,
499
- className,
500
- description,
501
- disabled,
502
- readOnly,
503
- required,
504
- touched,
505
- dirty,
506
- error,
507
- id,
508
- ariaInvalid,
509
- ariaDescribedBy,
510
- ariaRequired,
511
- extraProps
501
+ extraProps,
502
+ ...rendererProps
512
503
  }) => {
513
504
  const registry = useFieldRegistry();
514
505
  const Renderer = (0, import_react3.useMemo)(
@@ -523,23 +514,9 @@ var DynamicInputInner = ({
523
514
  }
524
515
  return import_react3.default.createElement(Renderer, {
525
516
  ...extraProps,
526
- value,
517
+ ...rendererProps,
527
518
  onValueChange: onChange,
528
- onBlur,
529
- label,
530
- options,
531
- className,
532
- description,
533
- disabled,
534
- readOnly,
535
- required,
536
- touched,
537
- dirty,
538
- error,
539
- id,
540
- ariaInvalid,
541
- ariaDescribedBy,
542
- ariaRequired
519
+ onBlur
543
520
  });
544
521
  };
545
522
  var DynamicInput = /* @__PURE__ */ import_react3.default.memo(
@@ -557,7 +534,7 @@ var import_react5 = require("react");
557
534
 
558
535
  // src/components/MultiFieldInput.tsx
559
536
  var import_core2 = require("@dynamic-field-kit/core");
560
- var import_react4 = require("react");
537
+ var import_react4 = __toESM(require("react"));
561
538
  var import_jsx_runtime6 = require("react/jsx-runtime");
562
539
  function resolveLayout(layout) {
563
540
  if (!layout) {
@@ -568,25 +545,39 @@ function resolveLayout(layout) {
568
545
  }
569
546
  return { type: layout.type, config: layout };
570
547
  }
571
- var MultiFieldInput = ({
548
+ var MultiFieldInputInner = ({
572
549
  fieldDescriptions,
573
550
  properties,
574
551
  onChange,
575
552
  layout,
553
+ idPrefix,
576
554
  rootData,
577
555
  onValidityChange,
578
- onBlurField
579
- }) => {
556
+ onBlurField,
557
+ touched: touchedProp,
558
+ errors: errorsProp,
559
+ onTouchedChange,
560
+ form
561
+ }, ref) => {
562
+ const effectiveProperties = properties ?? form?.data;
563
+ const effectiveOnChange = onChange ?? form?.handleChange;
564
+ const effectiveOnBlurField = onBlurField ?? form?.handleBlur;
565
+ const controlledTouched = touchedProp ?? form?.touched;
566
+ const effectiveErrors = errorsProp ?? form?.errors;
580
567
  const [data, setData] = (0, import_react4.useState)({});
581
- const [touchedFields, setTouchedFields] = (0, import_react4.useState)(
582
- {}
583
- );
584
- const initialPropertiesRef = (0, import_react4.useRef)(properties ?? {});
568
+ const [internalTouched, setInternalTouched] = (0, import_react4.useState)({});
569
+ const initialPropertiesRef = (0, import_react4.useRef)(effectiveProperties ?? {});
570
+ const autoId = (0, import_react4.useId)().replace(/[^a-zA-Z0-9]/g, "");
571
+ const effectiveIdPrefix = idPrefix ?? `dfk-${autoId}`;
572
+ const isTouchedControlled = controlledTouched !== void 0;
573
+ const effectiveTouched = controlledTouched ?? internalTouched;
585
574
  (0, import_react4.useEffect)(() => {
586
- if (properties) {
587
- setData((0, import_core2.applyComputedValues)(fieldDescriptions, properties, rootData));
575
+ if (effectiveProperties) {
576
+ setData(
577
+ (0, import_core2.applyComputedValues)(fieldDescriptions, effectiveProperties, rootData)
578
+ );
588
579
  }
589
- }, [properties]);
580
+ }, [effectiveProperties]);
590
581
  const effectiveRoot = rootData ?? data;
591
582
  const visibleFields = (0, import_react4.useMemo)(
592
583
  () => fieldDescriptions.filter(
@@ -596,16 +587,22 @@ var MultiFieldInput = ({
596
587
  );
597
588
  const dataRef = (0, import_react4.useRef)(data);
598
589
  dataRef.current = data;
599
- const onChangeRef = (0, import_react4.useRef)(onChange);
600
- onChangeRef.current = onChange;
590
+ const onChangeRef = (0, import_react4.useRef)(effectiveOnChange);
591
+ onChangeRef.current = effectiveOnChange;
601
592
  const fieldDescriptionsRef = (0, import_react4.useRef)(fieldDescriptions);
602
593
  fieldDescriptionsRef.current = fieldDescriptions;
603
594
  const rootDataRef = (0, import_react4.useRef)(rootData);
604
595
  rootDataRef.current = rootData;
605
596
  const onValidityChangeRef = (0, import_react4.useRef)(onValidityChange);
606
597
  onValidityChangeRef.current = onValidityChange;
607
- const onBlurFieldRef = (0, import_react4.useRef)(onBlurField);
608
- onBlurFieldRef.current = onBlurField;
598
+ const onBlurFieldRef = (0, import_react4.useRef)(effectiveOnBlurField);
599
+ onBlurFieldRef.current = effectiveOnBlurField;
600
+ const onTouchedChangeRef = (0, import_react4.useRef)(onTouchedChange);
601
+ onTouchedChangeRef.current = onTouchedChange;
602
+ const isTouchedControlledRef = (0, import_react4.useRef)(isTouchedControlled);
603
+ isTouchedControlledRef.current = isTouchedControlled;
604
+ const effectiveTouchedRef = (0, import_react4.useRef)(effectiveTouched);
605
+ effectiveTouchedRef.current = effectiveTouched;
609
606
  (0, import_react4.useEffect)(() => {
610
607
  onValidityChangeRef.current?.(
611
608
  (0, import_core2.validateFields)(fieldDescriptions, data, rootData)
@@ -622,10 +619,39 @@ var MultiFieldInput = ({
622
619
  setData(next);
623
620
  onChangeRef.current?.(next);
624
621
  }, []);
625
- const handleBlurField = (0, import_react4.useCallback)((key) => {
626
- setTouchedFields((prev) => prev[key] ? prev : { ...prev, [key]: true });
627
- onBlurFieldRef.current?.(key);
622
+ const markTouched = (0, import_react4.useCallback)((key, isTouched) => {
623
+ const current = effectiveTouchedRef.current;
624
+ if (Boolean(current[key]) === isTouched) {
625
+ return;
626
+ }
627
+ const next = { ...current, [key]: isTouched };
628
+ if (!isTouchedControlledRef.current) {
629
+ effectiveTouchedRef.current = next;
630
+ setInternalTouched(next);
631
+ }
632
+ onTouchedChangeRef.current?.(next);
628
633
  }, []);
634
+ const handleBlurField = (0, import_react4.useCallback)(
635
+ (key) => {
636
+ markTouched(key, true);
637
+ onBlurFieldRef.current?.(key);
638
+ },
639
+ [markTouched]
640
+ );
641
+ (0, import_react4.useImperativeHandle)(
642
+ ref,
643
+ () => ({
644
+ resetTouched: () => {
645
+ if (!isTouchedControlledRef.current) {
646
+ effectiveTouchedRef.current = {};
647
+ }
648
+ setInternalTouched({});
649
+ },
650
+ setFieldTouched: (fieldName, isTouched = true) => markTouched(fieldName, isTouched),
651
+ getTouched: () => effectiveTouchedRef.current
652
+ }),
653
+ [markTouched]
654
+ );
629
655
  const { type, config } = resolveLayout(layout);
630
656
  const Layout = layoutRegistry.get(type);
631
657
  if (!Layout) {
@@ -637,7 +663,10 @@ var MultiFieldInput = ({
637
663
  fieldDescription: f,
638
664
  renderInfos: data,
639
665
  rootData: effectiveRoot,
640
- touched: Boolean(touchedFields[f.name]),
666
+ idPrefix: effectiveIdPrefix,
667
+ touched: Boolean(effectiveTouched[f.name]),
668
+ touchedMap: (0, import_core2.isFieldGroup)(f) ? effectiveTouched : void 0,
669
+ errors: effectiveErrors,
641
670
  dirty: data[f.name] !== initialPropertiesRef.current[f.name],
642
671
  onBlurField: handleBlurField,
643
672
  onValueChangeField: handleValueChangeField
@@ -645,14 +674,20 @@ var MultiFieldInput = ({
645
674
  f.name
646
675
  )) });
647
676
  };
677
+ var MultiFieldInput = /* @__PURE__ */ import_react4.default.forwardRef(MultiFieldInputInner);
678
+ MultiFieldInput.displayName = "MultiFieldInput";
648
679
  var MultiFieldInput_default = MultiFieldInput;
649
680
 
650
681
  // src/components/FieldGroupInput.tsx
651
682
  var import_jsx_runtime7 = require("react/jsx-runtime");
683
+ var EMPTY_TOUCHED = Object.freeze({});
652
684
  var FieldGroupInput = ({
653
685
  fieldDescription,
654
686
  items,
655
687
  rootData,
688
+ errors,
689
+ touched,
690
+ onBlurField,
656
691
  onChange
657
692
  }) => {
658
693
  const {
@@ -666,6 +701,14 @@ var FieldGroupInput = ({
666
701
  const addText = addLabel ?? "Add";
667
702
  const removeText = removeLabel ?? "Remove";
668
703
  const groupName = label ?? fieldDescription.name;
704
+ const errorsByItem = (0, import_react5.useMemo)(
705
+ () => (0, import_core3.indexGroupPathMap)(errors, fieldDescription.name),
706
+ [errors, fieldDescription.name]
707
+ );
708
+ const touchedByItem = (0, import_react5.useMemo)(
709
+ () => (0, import_core3.indexGroupPathMap)(touched, fieldDescription.name),
710
+ [touched, fieldDescription.name]
711
+ );
669
712
  const handleItemChange = (0, import_react5.useCallback)(
670
713
  (index, next) => {
671
714
  const nextItems = items.slice();
@@ -702,6 +745,9 @@ var FieldGroupInput = ({
702
745
  fieldDescriptions: fields,
703
746
  properties: item,
704
747
  rootData,
748
+ errors: errorsByItem?.[index],
749
+ touched: touched === void 0 ? void 0 : touchedByItem?.[index] ?? EMPTY_TOUCHED,
750
+ onBlurField: (key) => onBlurField?.(`${fieldDescription.name}[${index}].${key}`),
705
751
  onChange: (next) => handleItemChange(index, next)
706
752
  }
707
753
  ) }),
@@ -739,12 +785,15 @@ var FieldInputInner = ({
739
785
  fieldDescription,
740
786
  renderInfos,
741
787
  rootData,
788
+ idPrefix = "dfk-field",
742
789
  touched,
790
+ touchedMap,
743
791
  dirty,
792
+ errors,
744
793
  onBlurField,
745
794
  onValueChangeField
746
795
  }) => {
747
- const { name, type, label, className, description, props, fields, required } = fieldDescription;
796
+ const { name, fields } = fieldDescription;
748
797
  const handleChange = (0, import_react6.useCallback)(
749
798
  (v) => onValueChangeField(v, name),
750
799
  [onValueChangeField, name]
@@ -761,43 +810,27 @@ var FieldInputInner = ({
761
810
  fieldDescription,
762
811
  items,
763
812
  rootData,
813
+ errors,
814
+ touched: touchedMap,
815
+ onBlurField,
764
816
  onChange: handleChange
765
817
  }
766
818
  );
767
819
  }
768
- const effectiveDisabled = (0, import_core4.resolveDisabled)(
820
+ const rendererProps = (0, import_core4.buildFieldRendererProps)({
769
821
  fieldDescription,
770
- renderInfos,
771
- rootData
772
- );
773
- const readOnly = (0, import_core4.resolveReadOnly)(fieldDescription, renderInfos, rootData);
774
- const resolvedOptionsList = (0, import_core4.resolveOptions)(
775
- fieldDescription,
776
- renderInfos,
777
- rootData
778
- );
779
- const errors = effectiveDisabled ? [] : (0, import_core4.validateField)(fieldDescription, renderInfos[name], renderInfos, rootData);
780
- const error = errors.length > 0 ? errors : void 0;
781
- const fieldId = `dfk-field-${name}`;
822
+ data: renderInfos,
823
+ rootData,
824
+ id: (0, import_core4.makeFieldId)(fieldDescription, idPrefix),
825
+ touched,
826
+ dirty,
827
+ validationErrors: errors === void 0 ? void 0 : errors[name] ?? []
828
+ });
782
829
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
783
830
  DynamicInput_default,
784
831
  {
785
- id: fieldId,
786
- type,
787
- label,
788
- value: renderInfos[name],
789
- options: resolvedOptionsList,
790
- className,
791
- description,
792
- disabled: effectiveDisabled,
793
- readOnly,
794
- required,
795
- touched,
796
- dirty,
797
- error,
798
- ariaInvalid: Boolean(error),
799
- ariaRequired: Boolean(required),
800
- extraProps: props,
832
+ ...rendererProps,
833
+ description: rendererProps.description,
801
834
  onChange: handleChange,
802
835
  onBlur: handleBlur
803
836
  }
@@ -805,7 +838,7 @@ var FieldInputInner = ({
805
838
  };
806
839
  var FieldInput = /* @__PURE__ */ import_react6.default.memo(FieldInputInner, (prev, next) => {
807
840
  const name = prev.fieldDescription.name;
808
- return prev.fieldDescription === next.fieldDescription && prev.onValueChangeField === next.onValueChangeField && prev.onBlurField === next.onBlurField && prev.rootData === next.rootData && prev.touched === next.touched && prev.dirty === next.dirty && prev.renderInfos[name] === next.renderInfos[name];
841
+ return prev.fieldDescription === next.fieldDescription && prev.onValueChangeField === next.onValueChangeField && prev.onBlurField === next.onBlurField && prev.rootData === next.rootData && prev.idPrefix === next.idPrefix && prev.touched === next.touched && prev.touchedMap === next.touchedMap && prev.dirty === next.dirty && prev.errors === next.errors && prev.renderInfos[name] === next.renderInfos[name];
809
842
  });
810
843
  var FieldInput_default = FieldInput;
811
844
 
@@ -1012,6 +1045,15 @@ var DynamicFormDevTools = ({
1012
1045
  // src/useDynamicForm.ts
1013
1046
  var import_core5 = require("@dynamic-field-kit/core");
1014
1047
  var import_react8 = require("react");
1048
+ function sameStringMap(left, right) {
1049
+ const keys = Object.keys(left);
1050
+ return keys.length === Object.keys(right).length && keys.every(
1051
+ (key) => left[key]?.length === right[key]?.length && left[key]?.every((value, index) => value === right[key]?.[index])
1052
+ );
1053
+ }
1054
+ function sameValidationResult(left, right) {
1055
+ return left.valid === right.valid && left.complete === right.complete && left.status === right.status && (left.pending ?? []).join("\0") === (right.pending ?? []).join("\0") && sameStringMap(left.errors, right.errors);
1056
+ }
1015
1057
  function useDynamicForm({
1016
1058
  fields,
1017
1059
  initialValues = {},
@@ -1026,22 +1068,76 @@ function useDynamicForm({
1026
1068
  const [touched, setTouched] = (0, import_react8.useState)({});
1027
1069
  const [isSubmitting, setIsSubmitting] = (0, import_react8.useState)(false);
1028
1070
  const [isSubmitted, setIsSubmitted] = (0, import_react8.useState)(false);
1071
+ const [validationResult, setValidationResult] = (0, import_react8.useState)(
1072
+ () => (0, import_core5.validateFields)(fields, data)
1073
+ );
1074
+ const [isValidating, setIsValidating] = (0, import_react8.useState)(false);
1075
+ const validationRunRef = (0, import_react8.useRef)(0);
1076
+ const validationAbortRef = (0, import_react8.useRef)(void 0);
1077
+ const submitRunRef = (0, import_react8.useRef)(0);
1078
+ const submitAbortRef = (0, import_react8.useRef)(void 0);
1079
+ const dataRef = (0, import_react8.useRef)(data);
1080
+ dataRef.current = data;
1081
+ const commitSyncResult = (0, import_react8.useCallback)((res) => {
1082
+ setValidationResult(
1083
+ (previous) => sameValidationResult(previous, res) ? previous : res
1084
+ );
1085
+ return res.valid;
1086
+ }, []);
1087
+ (0, import_react8.useEffect)(() => {
1088
+ commitSyncResult((0, import_core5.validateFields)(fields, data));
1089
+ }, [fields, data, commitSyncResult]);
1090
+ (0, import_react8.useEffect)(
1091
+ () => () => {
1092
+ validationAbortRef.current?.abort();
1093
+ submitAbortRef.current?.abort();
1094
+ },
1095
+ []
1096
+ );
1029
1097
  const validate = (0, import_react8.useCallback)(() => {
1030
1098
  const res = (0, import_core5.validateFields)(fields, data);
1031
1099
  setErrors(res.errors);
1032
- return res.valid;
1100
+ return commitSyncResult(res);
1101
+ }, [fields, data, commitSyncResult]);
1102
+ const validateAsync = (0, import_react8.useCallback)(async () => {
1103
+ const run = ++validationRunRef.current;
1104
+ validationAbortRef.current?.abort();
1105
+ const controller = new AbortController();
1106
+ validationAbortRef.current = controller;
1107
+ const snapshot = data;
1108
+ setIsValidating(true);
1109
+ try {
1110
+ const res = await (0, import_core5.validateFieldsAsync)(fields, snapshot, snapshot, {
1111
+ signal: controller.signal
1112
+ });
1113
+ if (run !== validationRunRef.current || dataRef.current !== snapshot) {
1114
+ return res.valid;
1115
+ }
1116
+ setErrors(res.errors);
1117
+ setValidationResult(res);
1118
+ return res.valid;
1119
+ } finally {
1120
+ if (run === validationRunRef.current) {
1121
+ setIsValidating(false);
1122
+ }
1123
+ }
1033
1124
  }, [fields, data]);
1034
1125
  const handleChange = (0, import_react8.useCallback)(
1035
1126
  (newData) => {
1036
1127
  const next = (0, import_core5.applyComputedValues)(fields, newData);
1037
1128
  setData(next);
1129
+ dataRef.current = next;
1038
1130
  setIsDirty(true);
1131
+ validationAbortRef.current?.abort();
1132
+ validationRunRef.current += 1;
1133
+ setIsValidating(false);
1134
+ const res = (0, import_core5.validateFields)(fields, next);
1135
+ commitSyncResult(res);
1039
1136
  if (validateOnChange) {
1040
- const res = (0, import_core5.validateFields)(fields, next);
1041
1137
  setErrors(res.errors);
1042
1138
  }
1043
1139
  },
1044
- [fields, validateOnChange]
1140
+ [fields, validateOnChange, commitSyncResult]
1045
1141
  );
1046
1142
  const setFieldValue = (0, import_react8.useCallback)(
1047
1143
  (name, value) => {
@@ -1052,26 +1148,39 @@ function useDynamicForm({
1052
1148
  const setFieldTouched = (0, import_react8.useCallback)((name, isTouched = true) => {
1053
1149
  setTouched((prev) => ({ ...prev, [name]: isTouched }));
1054
1150
  }, []);
1151
+ const touchAll = (0, import_react8.useCallback)(() => {
1152
+ setTouched(
1153
+ Object.fromEntries(
1154
+ (0, import_core5.collectFieldPaths)(fields, data).map((path) => [path, true])
1155
+ )
1156
+ );
1157
+ }, [fields, data]);
1158
+ const resetTouched = (0, import_react8.useCallback)(() => setTouched({}), []);
1055
1159
  const handleBlur = (0, import_react8.useCallback)(
1056
1160
  (fieldName) => {
1057
1161
  setFieldTouched(fieldName, true);
1058
1162
  if (validateOnBlur) {
1059
1163
  const res = (0, import_core5.validateFields)(fields, data);
1060
1164
  setErrors(res.errors);
1165
+ commitSyncResult(res);
1061
1166
  }
1062
1167
  },
1063
- [fields, data, validateOnBlur, setFieldTouched]
1168
+ [fields, data, validateOnBlur, setFieldTouched, commitSyncResult]
1064
1169
  );
1065
1170
  const reset = (0, import_react8.useCallback)(
1066
1171
  (newValues) => {
1067
1172
  const seed = newValues ?? initialValues;
1068
1173
  const next = (0, import_core5.applyComputedValues)(fields, seed);
1069
1174
  setData(next);
1175
+ dataRef.current = next;
1070
1176
  setErrors({});
1071
1177
  setIsDirty(false);
1072
1178
  setTouched({});
1073
1179
  setIsSubmitting(false);
1074
1180
  setIsSubmitted(false);
1181
+ validationAbortRef.current?.abort();
1182
+ validationRunRef.current += 1;
1183
+ setIsValidating(false);
1075
1184
  },
1076
1185
  [fields, initialValues]
1077
1186
  );
@@ -1081,26 +1190,54 @@ function useDynamicForm({
1081
1190
  e.preventDefault();
1082
1191
  }
1083
1192
  setIsSubmitting(true);
1193
+ const submitRun = ++submitRunRef.current;
1084
1194
  try {
1085
- const res = (0, import_core5.validateFields)(fields, data);
1086
- setErrors(res.errors);
1195
+ touchAll();
1196
+ const run = ++validationRunRef.current;
1197
+ validationAbortRef.current?.abort();
1198
+ submitAbortRef.current?.abort();
1199
+ const controller = new AbortController();
1200
+ submitAbortRef.current = controller;
1201
+ const snapshot = data;
1202
+ setIsValidating(true);
1203
+ const res = await (0, import_core5.validateFieldsAsync)(fields, snapshot, snapshot, {
1204
+ signal: controller.signal
1205
+ });
1206
+ if (submitRun !== submitRunRef.current) {
1207
+ return;
1208
+ }
1209
+ if (dataRef.current === snapshot && run === validationRunRef.current) {
1210
+ setErrors(res.errors);
1211
+ setValidationResult(res);
1212
+ } else {
1213
+ const live = (0, import_core5.validateFields)(fields, dataRef.current);
1214
+ setErrors(live.errors);
1215
+ commitSyncResult(live);
1216
+ }
1087
1217
  setIsSubmitted(true);
1088
1218
  if (res.valid) {
1089
- await onValid(data);
1219
+ await onValid(snapshot);
1090
1220
  } else if (onInvalid) {
1091
1221
  onInvalid(res.errors);
1092
1222
  }
1093
1223
  } finally {
1224
+ if (submitRun === submitRunRef.current) {
1225
+ setIsValidating(false);
1226
+ }
1094
1227
  setIsSubmitting(false);
1095
1228
  }
1096
1229
  },
1097
- [fields, data]
1230
+ [fields, data, touchAll, commitSyncResult]
1098
1231
  );
1099
- const isValid = Object.keys(errors).length === 0;
1100
1232
  return {
1101
1233
  data,
1102
1234
  errors,
1103
- isValid,
1235
+ isValid: validationResult.valid,
1236
+ isValidating,
1237
+ // Matches Vue and Angular: a run still in flight is not complete, whatever
1238
+ // the last finished pass concluded.
1239
+ isValidationComplete: validationResult.complete && !isValidating,
1240
+ validationStatus: isValidating ? "pending" : validationResult.status,
1104
1241
  isDirty,
1105
1242
  isSubmitting,
1106
1243
  isSubmitted,
@@ -1108,10 +1245,14 @@ function useDynamicForm({
1108
1245
  setData,
1109
1246
  setFieldValue,
1110
1247
  setFieldTouched,
1248
+ setTouched,
1249
+ touchAll,
1250
+ resetTouched,
1111
1251
  handleChange,
1112
1252
  handleBlur,
1113
1253
  reset,
1114
1254
  validate,
1255
+ validateAsync,
1115
1256
  handleSubmit
1116
1257
  };
1117
1258
  }
@@ -1119,18 +1260,24 @@ function useDynamicForm({
1119
1260
  // src/index.ts
1120
1261
  var import_core6 = require("@dynamic-field-kit/core");
1121
1262
  var import_core7 = require("@dynamic-field-kit/core");
1263
+ var import_core8 = require("@dynamic-field-kit/core");
1122
1264
  // Annotate the CommonJS export names for ESM import in node:
1123
1265
  0 && (module.exports = {
1124
1266
  DynamicFormDevTools,
1125
1267
  DynamicInput,
1268
+ FIELD_RENDERER_PROP_KEYS,
1126
1269
  FieldInput,
1127
1270
  FieldRegistry,
1128
1271
  FieldRegistryProvider,
1129
1272
  MultiFieldInput,
1273
+ buildFieldRendererProps,
1274
+ collectFieldPaths,
1130
1275
  defaultRenderersMap,
1131
1276
  fieldRegistry,
1132
1277
  getDefaultRenderer,
1278
+ indexGroupPathMap,
1133
1279
  layoutRegistry,
1280
+ makeFieldId,
1134
1281
  resolveDisabled,
1135
1282
  resolveOptions,
1136
1283
  resolveReadOnly,