@dynamic-field-kit/react 1.6.0 → 1.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.mjs CHANGED
@@ -111,6 +111,9 @@ layoutRegistry.register("responsive", ({ children, config }) => {
111
111
  });
112
112
 
113
113
  // src/components/DynamicInput.tsx
114
+ import {
115
+ makeErrorId
116
+ } from "@dynamic-field-kit/core";
114
117
  import React3, { useMemo } from "react";
115
118
 
116
119
  // src/defaultRenderers.tsx
@@ -435,31 +438,45 @@ function useFieldRegistry() {
435
438
  }
436
439
 
437
440
  // src/components/DynamicInput.tsx
438
- import { jsxs as jsxs3 } from "react/jsx-runtime";
441
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
439
442
  var DynamicInputInner = ({
440
443
  type,
441
444
  onChange,
442
445
  onBlur,
443
446
  extraProps,
447
+ onOptionsQuery,
444
448
  ...rendererProps
445
449
  }) => {
446
450
  const registry = useFieldRegistry();
447
- const Renderer = useMemo(
448
- () => registry.get(type) || getDefaultRenderer(type),
449
- [registry, type]
450
- );
451
+ const { Renderer, isDefault } = useMemo(() => {
452
+ const registered = registry.get(type);
453
+ return {
454
+ Renderer: registered ?? getDefaultRenderer(type),
455
+ isDefault: !registered
456
+ };
457
+ }, [registry, type]);
451
458
  if (!Renderer) {
452
459
  return /* @__PURE__ */ jsxs3("div", { children: [
453
460
  "Unknown field type: ",
454
461
  type
455
462
  ] });
456
463
  }
457
- return React3.createElement(Renderer, {
464
+ const { error, id } = rendererProps;
465
+ const control = React3.createElement(Renderer, {
458
466
  ...extraProps,
459
467
  ...rendererProps,
460
468
  onValueChange: onChange,
461
- onBlur
469
+ onBlur,
470
+ onOptionsQuery
462
471
  });
472
+ const firstError = Array.isArray(error) ? error[0] : error;
473
+ if (!isDefault || !firstError || !id) {
474
+ return control;
475
+ }
476
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
477
+ control,
478
+ /* @__PURE__ */ jsx5("div", { id: makeErrorId(id), className: "dfk-field-error", role: "alert", children: firstError })
479
+ ] });
463
480
  };
464
481
  var DynamicInput = /* @__PURE__ */ React3.memo(
465
482
  DynamicInputInner
@@ -469,9 +486,11 @@ var DynamicInput_default = DynamicInput;
469
486
  // src/components/FieldInput.tsx
470
487
  import {
471
488
  buildFieldRendererProps,
489
+ createOptionsLoader,
490
+ isAsyncOptions,
472
491
  makeFieldId
473
492
  } from "@dynamic-field-kit/core";
474
- import React6, { useCallback as useCallback3 } from "react";
493
+ import React6, { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
475
494
 
476
495
  // src/components/FieldGroupInput.tsx
477
496
  import {
@@ -497,7 +516,7 @@ import React4, {
497
516
  useRef,
498
517
  useState as useState2
499
518
  } from "react";
500
- import { jsx as jsx5 } from "react/jsx-runtime";
519
+ import { jsx as jsx6 } from "react/jsx-runtime";
501
520
  function resolveLayout(layout) {
502
521
  if (!layout) {
503
522
  return { type: "column", config: {} };
@@ -513,6 +532,7 @@ var MultiFieldInputInner = ({
513
532
  onChange,
514
533
  layout,
515
534
  idPrefix,
535
+ initialProperties,
516
536
  rootData,
517
537
  onValidityChange,
518
538
  onBlurField,
@@ -528,7 +548,13 @@ var MultiFieldInputInner = ({
528
548
  const effectiveErrors = errorsProp ?? form?.errors;
529
549
  const [data, setData] = useState2({});
530
550
  const [internalTouched, setInternalTouched] = useState2({});
531
- const initialPropertiesRef = useRef(effectiveProperties ?? {});
551
+ const fallbackBaselineRef = useRef(
552
+ effectiveProperties
553
+ );
554
+ if (fallbackBaselineRef.current === void 0) {
555
+ fallbackBaselineRef.current = effectiveProperties;
556
+ }
557
+ const baseline = initialProperties ?? form?.baselineValues ?? fallbackBaselineRef.current ?? {};
532
558
  const autoId = useId().replace(/[^a-zA-Z0-9]/g, "");
533
559
  const effectiveIdPrefix = idPrefix ?? `dfk-${autoId}`;
534
560
  const isTouchedControlled = controlledTouched !== void 0;
@@ -619,7 +645,7 @@ var MultiFieldInputInner = ({
619
645
  if (!Layout) {
620
646
  throw new Error(`Unknown layout: ${type}`);
621
647
  }
622
- return /* @__PURE__ */ jsx5(Layout, { config, children: visibleFields.map((f) => /* @__PURE__ */ jsx5(
648
+ return /* @__PURE__ */ jsx6(Layout, { config, children: visibleFields.map((f) => /* @__PURE__ */ jsx6(
623
649
  FieldInput_default,
624
650
  {
625
651
  fieldDescription: f,
@@ -629,7 +655,7 @@ var MultiFieldInputInner = ({
629
655
  touched: Boolean(effectiveTouched[f.name]),
630
656
  touchedMap: isFieldGroup(f) ? effectiveTouched : void 0,
631
657
  errors: effectiveErrors,
632
- dirty: data[f.name] !== initialPropertiesRef.current[f.name],
658
+ dirty: !Object.is(data[f.name], baseline[f.name]),
633
659
  onBlurField: handleBlurField,
634
660
  onValueChangeField: handleValueChangeField
635
661
  },
@@ -641,7 +667,7 @@ MultiFieldInput.displayName = "MultiFieldInput";
641
667
  var MultiFieldInput_default = MultiFieldInput;
642
668
 
643
669
  // src/components/FieldGroupInput.tsx
644
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
670
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
645
671
  var EMPTY_TOUCHED = Object.freeze({});
646
672
  var FieldGroupInput = ({
647
673
  fieldDescription,
@@ -695,13 +721,13 @@ var FieldGroupInput = ({
695
721
  [fieldDescription, items, onChange]
696
722
  );
697
723
  return /* @__PURE__ */ jsxs4("div", { className: fieldDescription.className, children: [
698
- label && /* @__PURE__ */ jsx6("div", { children: label }),
724
+ label && /* @__PURE__ */ jsx7("div", { children: label }),
699
725
  items.map((item, index) => /* @__PURE__ */ jsxs4(
700
726
  "div",
701
727
  {
702
728
  style: { display: "flex", alignItems: "flex-start", gap: 8 },
703
729
  children: [
704
- /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(
730
+ /* @__PURE__ */ jsx7("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx7(
705
731
  MultiFieldInput_default,
706
732
  {
707
733
  fieldDescriptions: fields,
@@ -713,7 +739,7 @@ var FieldGroupInput = ({
713
739
  onChange: (next) => handleItemChange(index, next)
714
740
  }
715
741
  ) }),
716
- /* @__PURE__ */ jsx6(
742
+ /* @__PURE__ */ jsx7(
717
743
  "button",
718
744
  {
719
745
  type: "button",
@@ -727,7 +753,7 @@ var FieldGroupInput = ({
727
753
  },
728
754
  itemKey(item, index)
729
755
  )),
730
- /* @__PURE__ */ jsx6(
756
+ /* @__PURE__ */ jsx7(
731
757
  "button",
732
758
  {
733
759
  type: "button",
@@ -742,7 +768,7 @@ var FieldGroupInput = ({
742
768
  var FieldGroupInput_default = FieldGroupInput;
743
769
 
744
770
  // src/components/FieldInput.tsx
745
- import { jsx as jsx7 } from "react/jsx-runtime";
771
+ import { jsx as jsx8 } from "react/jsx-runtime";
746
772
  var FieldInputInner = ({
747
773
  fieldDescription,
748
774
  renderInfos,
@@ -756,6 +782,41 @@ var FieldInputInner = ({
756
782
  onValueChangeField
757
783
  }) => {
758
784
  const { name, fields } = fieldDescription;
785
+ const isAsync = isAsyncOptions(fieldDescription);
786
+ const [optionsState, setOptionsState] = useState3(
787
+ isAsync ? { status: "idle" } : void 0
788
+ );
789
+ const loaderRef = useRef2(void 0);
790
+ const fieldRef = useRef2(fieldDescription);
791
+ fieldRef.current = fieldDescription;
792
+ const ensureLoader = useCallback3(() => {
793
+ if (!isAsyncOptions(fieldRef.current)) {
794
+ return void 0;
795
+ }
796
+ if (!loaderRef.current) {
797
+ loaderRef.current = createOptionsLoader(
798
+ fieldRef.current,
799
+ setOptionsState
800
+ );
801
+ }
802
+ return loaderRef.current;
803
+ }, []);
804
+ useEffect3(
805
+ () => () => {
806
+ loaderRef.current?.dispose();
807
+ loaderRef.current = void 0;
808
+ },
809
+ []
810
+ );
811
+ useEffect3(() => {
812
+ ensureLoader()?.update(renderInfos, rootData);
813
+ });
814
+ const handleOptionsQuery = useCallback3(
815
+ (query) => {
816
+ ensureLoader()?.setQuery(query);
817
+ },
818
+ [ensureLoader]
819
+ );
759
820
  const handleChange = useCallback3(
760
821
  (v) => onValueChangeField(v, name),
761
822
  [onValueChangeField, name]
@@ -766,7 +827,7 @@ var FieldInputInner = ({
766
827
  );
767
828
  if (fields) {
768
829
  const items = Array.isArray(renderInfos[name]) ? renderInfos[name] : [];
769
- return /* @__PURE__ */ jsx7(
830
+ return /* @__PURE__ */ jsx8(
770
831
  FieldGroupInput_default,
771
832
  {
772
833
  fieldDescription,
@@ -786,27 +847,32 @@ var FieldInputInner = ({
786
847
  id: makeFieldId(fieldDescription, idPrefix),
787
848
  touched,
788
849
  dirty,
789
- validationErrors: errors === void 0 ? void 0 : errors[name] ?? []
850
+ validationErrors: errors === void 0 ? void 0 : errors[name] ?? [],
851
+ optionsState
790
852
  });
791
- return /* @__PURE__ */ jsx7(
853
+ return /* @__PURE__ */ jsx8(
792
854
  DynamicInput_default,
793
855
  {
794
856
  ...rendererProps,
795
857
  description: rendererProps.description,
796
858
  onChange: handleChange,
797
- onBlur: handleBlur
859
+ onBlur: handleBlur,
860
+ onOptionsQuery: isAsync ? handleOptionsQuery : void 0
798
861
  }
799
862
  );
800
863
  };
801
864
  var FieldInput = /* @__PURE__ */ React6.memo(FieldInputInner, (prev, next) => {
802
865
  const name = prev.fieldDescription.name;
866
+ if (isAsyncOptions(prev.fieldDescription) && prev.renderInfos !== next.renderInfos) {
867
+ return false;
868
+ }
803
869
  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];
804
870
  });
805
871
  var FieldInput_default = FieldInput;
806
872
 
807
873
  // src/components/DynamicFormDevTools.tsx
808
- import { useState as useState3 } from "react";
809
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
874
+ import { useState as useState4 } from "react";
875
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
810
876
  var DynamicFormDevTools = ({
811
877
  data,
812
878
  errors = {},
@@ -815,8 +881,8 @@ var DynamicFormDevTools = ({
815
881
  fields = [],
816
882
  position = "bottom-right"
817
883
  }) => {
818
- const [isOpen, setIsOpen] = useState3(false);
819
- const [activeTab, setActiveTab] = useState3("data");
884
+ const [isOpen, setIsOpen] = useState4(false);
885
+ const [activeTab, setActiveTab] = useState4("data");
820
886
  const errorCount = Object.keys(errors).length;
821
887
  const posStyle = position === "bottom-left" ? { left: "16px", bottom: "16px" } : { right: "16px", bottom: "16px" };
822
888
  if (!isOpen) {
@@ -843,8 +909,8 @@ var DynamicFormDevTools = ({
843
909
  gap: "6px"
844
910
  },
845
911
  children: [
846
- /* @__PURE__ */ jsx8("span", { children: "\u{1F50D} DevTools" }),
847
- errorCount > 0 && /* @__PURE__ */ jsx8(
912
+ /* @__PURE__ */ jsx9("span", { children: "\u{1F50D} DevTools" }),
913
+ errorCount > 0 && /* @__PURE__ */ jsx9(
848
914
  "span",
849
915
  {
850
916
  style: {
@@ -894,8 +960,8 @@ var DynamicFormDevTools = ({
894
960
  borderBottom: "1px solid #334155"
895
961
  },
896
962
  children: [
897
- /* @__PURE__ */ jsx8("span", { style: { fontWeight: "bold", color: "#38bdf8" }, children: "\u{1F6E0}\uFE0F Form DevTools" }),
898
- /* @__PURE__ */ jsx8(
963
+ /* @__PURE__ */ jsx9("span", { style: { fontWeight: "bold", color: "#38bdf8" }, children: "\u{1F6E0}\uFE0F Form DevTools" }),
964
+ /* @__PURE__ */ jsx9(
899
965
  "button",
900
966
  {
901
967
  type: "button",
@@ -913,7 +979,7 @@ var DynamicFormDevTools = ({
913
979
  ]
914
980
  }
915
981
  ),
916
- /* @__PURE__ */ jsx8(
982
+ /* @__PURE__ */ jsx9(
917
983
  "div",
918
984
  {
919
985
  style: {
@@ -948,7 +1014,7 @@ var DynamicFormDevTools = ({
948
1014
  }
949
1015
  ),
950
1016
  /* @__PURE__ */ jsxs5("div", { style: { padding: "12px", overflowY: "auto", flex: 1 }, children: [
951
- activeTab === "data" && /* @__PURE__ */ jsx8(
1017
+ activeTab === "data" && /* @__PURE__ */ jsx9(
952
1018
  "pre",
953
1019
  {
954
1020
  style: {
@@ -960,24 +1026,24 @@ var DynamicFormDevTools = ({
960
1026
  children: JSON.stringify(data, null, 2)
961
1027
  }
962
1028
  ),
963
- activeTab === "errors" && /* @__PURE__ */ jsx8("div", { children: Object.keys(errors).length === 0 ? /* @__PURE__ */ jsx8("span", { style: { color: "#4ade80" }, children: "\u2713 No validation errors" }) : Object.entries(errors).map(([field, msgs]) => /* @__PURE__ */ jsxs5("div", { style: { marginBottom: "8px" }, children: [
1029
+ activeTab === "errors" && /* @__PURE__ */ jsx9("div", { children: Object.keys(errors).length === 0 ? /* @__PURE__ */ jsx9("span", { style: { color: "#4ade80" }, children: "\u2713 No validation errors" }) : Object.entries(errors).map(([field, msgs]) => /* @__PURE__ */ jsxs5("div", { style: { marginBottom: "8px" }, children: [
964
1030
  /* @__PURE__ */ jsxs5("span", { style: { color: "#f87171", fontWeight: "bold" }, children: [
965
1031
  field,
966
1032
  ":"
967
1033
  ] }),
968
- /* @__PURE__ */ jsx8("ul", { style: { margin: "4px 0 0 16px", padding: 0 }, children: msgs.map((m, i) => /* @__PURE__ */ jsx8("li", { style: { color: "#fca5a5" }, children: m }, i)) })
1034
+ /* @__PURE__ */ jsx9("ul", { style: { margin: "4px 0 0 16px", padding: 0 }, children: msgs.map((m, i) => /* @__PURE__ */ jsx9("li", { style: { color: "#fca5a5" }, children: m }, i)) })
969
1035
  ] }, field)) }),
970
1036
  activeTab === "meta" && /* @__PURE__ */ jsxs5("div", { children: [
971
1037
  /* @__PURE__ */ jsxs5("div", { style: { marginBottom: "6px" }, children: [
972
- /* @__PURE__ */ jsx8("span", { style: { color: "#94a3b8" }, children: "isDirty: " }),
973
- /* @__PURE__ */ jsx8("span", { style: { color: isDirty ? "#facc15" : "#4ade80" }, children: String(isDirty) })
1038
+ /* @__PURE__ */ jsx9("span", { style: { color: "#94a3b8" }, children: "isDirty: " }),
1039
+ /* @__PURE__ */ jsx9("span", { style: { color: isDirty ? "#facc15" : "#4ade80" }, children: String(isDirty) })
974
1040
  ] }),
975
1041
  /* @__PURE__ */ jsxs5("div", { children: [
976
- /* @__PURE__ */ jsx8("span", { style: { color: "#94a3b8" }, children: "Touched Fields:" }),
977
- /* @__PURE__ */ jsx8("pre", { style: { margin: "4px 0 0 0", color: "#cbd5e1" }, children: JSON.stringify(touched, null, 2) })
1042
+ /* @__PURE__ */ jsx9("span", { style: { color: "#94a3b8" }, children: "Touched Fields:" }),
1043
+ /* @__PURE__ */ jsx9("pre", { style: { margin: "4px 0 0 0", color: "#cbd5e1" }, children: JSON.stringify(touched, null, 2) })
978
1044
  ] })
979
1045
  ] }),
980
- activeTab === "fields" && /* @__PURE__ */ jsx8("div", { children: fields.length === 0 ? /* @__PURE__ */ jsx8("span", { style: { color: "#94a3b8" }, children: "No field descriptions passed" }) : fields.map((f) => /* @__PURE__ */ jsxs5(
1046
+ activeTab === "fields" && /* @__PURE__ */ jsx9("div", { children: fields.length === 0 ? /* @__PURE__ */ jsx9("span", { style: { color: "#94a3b8" }, children: "No field descriptions passed" }) : fields.map((f) => /* @__PURE__ */ jsxs5(
981
1047
  "div",
982
1048
  {
983
1049
  style: {
@@ -987,7 +1053,7 @@ var DynamicFormDevTools = ({
987
1053
  borderRadius: "4px"
988
1054
  },
989
1055
  children: [
990
- /* @__PURE__ */ jsx8("div", { style: { color: "#38bdf8", fontWeight: "bold" }, children: f.name }),
1056
+ /* @__PURE__ */ jsx9("div", { style: { color: "#38bdf8", fontWeight: "bold" }, children: f.name }),
991
1057
  /* @__PURE__ */ jsxs5("div", { style: { color: "#94a3b8", fontSize: "10px" }, children: [
992
1058
  "type: ",
993
1059
  f.type,
@@ -1008,10 +1074,11 @@ var DynamicFormDevTools = ({
1008
1074
  import {
1009
1075
  applyComputedValues as applyComputedValues2,
1010
1076
  collectFieldPaths,
1077
+ createMessageResolver,
1011
1078
  validateFields as validateFields2,
1012
1079
  validateFieldsAsync
1013
1080
  } from "@dynamic-field-kit/core";
1014
- import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
1081
+ import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
1015
1082
  function sameStringMap(left, right) {
1016
1083
  const keys = Object.keys(left);
1017
1084
  return keys.length === Object.keys(right).length && keys.every(
@@ -1025,36 +1092,57 @@ function useDynamicForm({
1025
1092
  fields,
1026
1093
  initialValues = {},
1027
1094
  validateOnBlur = true,
1028
- validateOnChange = false
1095
+ validateOnChange = false,
1096
+ messages
1029
1097
  }) {
1030
- const [data, setData] = useState4(
1098
+ const validationContextRef = useRef3({
1099
+ t: createMessageResolver(messages)
1100
+ });
1101
+ validationContextRef.current.t = createMessageResolver(messages);
1102
+ const [data, setData] = useState5(
1031
1103
  () => applyComputedValues2(fields, initialValues)
1032
1104
  );
1033
- const [errors, setErrors] = useState4({});
1034
- const [isDirty, setIsDirty] = useState4(false);
1035
- const [touched, setTouched] = useState4({});
1036
- const [isSubmitting, setIsSubmitting] = useState4(false);
1037
- const [isSubmitted, setIsSubmitted] = useState4(false);
1038
- const [validationResult, setValidationResult] = useState4(
1039
- () => validateFields2(fields, data)
1105
+ const [errors, setErrors] = useState5({});
1106
+ const [isDirty, setIsDirty] = useState5(false);
1107
+ const [touched, setTouched] = useState5({});
1108
+ const [isSubmitting, setIsSubmitting] = useState5(false);
1109
+ const [isSubmitted, setIsSubmitted] = useState5(false);
1110
+ const [validationResult, setValidationResult] = useState5(
1111
+ () => validateFields2(fields, data, void 0, validationContextRef.current)
1040
1112
  );
1041
- const [isValidating, setIsValidating] = useState4(false);
1042
- const validationRunRef = useRef2(0);
1043
- const validationAbortRef = useRef2(void 0);
1044
- const submitRunRef = useRef2(0);
1045
- const submitAbortRef = useRef2(void 0);
1046
- const dataRef = useRef2(data);
1113
+ const [isValidating, setIsValidating] = useState5(false);
1114
+ const validationRunRef = useRef3(0);
1115
+ const validationAbortRef = useRef3(void 0);
1116
+ const submitRunRef = useRef3(0);
1117
+ const submitAbortRef = useRef3(void 0);
1118
+ const dataRef = useRef3(data);
1047
1119
  dataRef.current = data;
1120
+ const baselineRef = useRef3(dataRef.current);
1121
+ const [baselineValues, setBaselineValues] = useState5(
1122
+ () => baselineRef.current
1123
+ );
1048
1124
  const commitSyncResult = useCallback4((res) => {
1049
1125
  setValidationResult(
1050
1126
  (previous) => sameValidationResult(previous, res) ? previous : res
1051
1127
  );
1052
1128
  return res.valid;
1053
1129
  }, []);
1054
- useEffect3(() => {
1055
- commitSyncResult(validateFields2(fields, data));
1130
+ const lastValidatedRef = useRef3(void 0);
1131
+ const fieldsRef = useRef3(fields);
1132
+ if (fieldsRef.current !== fields) {
1133
+ fieldsRef.current = fields;
1134
+ lastValidatedRef.current = void 0;
1135
+ }
1136
+ useEffect4(() => {
1137
+ if (lastValidatedRef.current === data) {
1138
+ return;
1139
+ }
1140
+ lastValidatedRef.current = data;
1141
+ commitSyncResult(
1142
+ validateFields2(fields, data, void 0, validationContextRef.current)
1143
+ );
1056
1144
  }, [fields, data, commitSyncResult]);
1057
- useEffect3(
1145
+ useEffect4(
1058
1146
  () => () => {
1059
1147
  validationAbortRef.current?.abort();
1060
1148
  submitAbortRef.current?.abort();
@@ -1062,7 +1150,12 @@ function useDynamicForm({
1062
1150
  []
1063
1151
  );
1064
1152
  const validate = useCallback4(() => {
1065
- const res = validateFields2(fields, data);
1153
+ const res = validateFields2(
1154
+ fields,
1155
+ data,
1156
+ void 0,
1157
+ validationContextRef.current
1158
+ );
1066
1159
  setErrors(res.errors);
1067
1160
  return commitSyncResult(res);
1068
1161
  }, [fields, data, commitSyncResult]);
@@ -1075,6 +1168,7 @@ function useDynamicForm({
1075
1168
  setIsValidating(true);
1076
1169
  try {
1077
1170
  const res = await validateFieldsAsync(fields, snapshot, snapshot, {
1171
+ ...validationContextRef.current,
1078
1172
  signal: controller.signal
1079
1173
  });
1080
1174
  if (run !== validationRunRef.current || dataRef.current !== snapshot) {
@@ -1098,7 +1192,13 @@ function useDynamicForm({
1098
1192
  validationAbortRef.current?.abort();
1099
1193
  validationRunRef.current += 1;
1100
1194
  setIsValidating(false);
1101
- const res = validateFields2(fields, next);
1195
+ lastValidatedRef.current = next;
1196
+ const res = validateFields2(
1197
+ fields,
1198
+ next,
1199
+ void 0,
1200
+ validationContextRef.current
1201
+ );
1102
1202
  commitSyncResult(res);
1103
1203
  if (validateOnChange) {
1104
1204
  setErrors(res.errors);
@@ -1123,11 +1223,27 @@ function useDynamicForm({
1123
1223
  );
1124
1224
  }, [fields, data]);
1125
1225
  const resetTouched = useCallback4(() => setTouched({}), []);
1226
+ const getDirtyValues = useCallback4(() => {
1227
+ const baseline = baselineRef.current;
1228
+ const current = dataRef.current;
1229
+ const dirty = {};
1230
+ for (const key of Object.keys(current)) {
1231
+ if (!Object.is(current[key], baseline[key])) {
1232
+ dirty[key] = current[key];
1233
+ }
1234
+ }
1235
+ return dirty;
1236
+ }, []);
1126
1237
  const handleBlur = useCallback4(
1127
1238
  (fieldName) => {
1128
1239
  setFieldTouched(fieldName, true);
1129
1240
  if (validateOnBlur) {
1130
- const res = validateFields2(fields, data);
1241
+ const res = validateFields2(
1242
+ fields,
1243
+ data,
1244
+ void 0,
1245
+ validationContextRef.current
1246
+ );
1131
1247
  setErrors(res.errors);
1132
1248
  commitSyncResult(res);
1133
1249
  }
@@ -1140,6 +1256,8 @@ function useDynamicForm({
1140
1256
  const next = applyComputedValues2(fields, seed);
1141
1257
  setData(next);
1142
1258
  dataRef.current = next;
1259
+ baselineRef.current = next;
1260
+ setBaselineValues(next);
1143
1261
  setErrors({});
1144
1262
  setIsDirty(false);
1145
1263
  setTouched({});
@@ -1168,6 +1286,7 @@ function useDynamicForm({
1168
1286
  const snapshot = data;
1169
1287
  setIsValidating(true);
1170
1288
  const res = await validateFieldsAsync(fields, snapshot, snapshot, {
1289
+ ...validationContextRef.current,
1171
1290
  signal: controller.signal
1172
1291
  });
1173
1292
  if (submitRun !== submitRunRef.current) {
@@ -1177,7 +1296,12 @@ function useDynamicForm({
1177
1296
  setErrors(res.errors);
1178
1297
  setValidationResult(res);
1179
1298
  } else {
1180
- const live = validateFields2(fields, dataRef.current);
1299
+ const live = validateFields2(
1300
+ fields,
1301
+ dataRef.current,
1302
+ void 0,
1303
+ validationContextRef.current
1304
+ );
1181
1305
  setErrors(live.errors);
1182
1306
  commitSyncResult(live);
1183
1307
  }
@@ -1206,6 +1330,8 @@ function useDynamicForm({
1206
1330
  isValidationComplete: validationResult.complete && !isValidating,
1207
1331
  validationStatus: isValidating ? "pending" : validationResult.status,
1208
1332
  isDirty,
1333
+ baselineValues,
1334
+ getDirtyValues,
1209
1335
  isSubmitting,
1210
1336
  isSubmitted,
1211
1337
  touched,
@@ -1229,6 +1355,7 @@ import { FieldRegistry } from "@dynamic-field-kit/core";
1229
1355
  import {
1230
1356
  buildFieldRendererProps as buildFieldRendererProps2,
1231
1357
  makeFieldId as makeFieldId2,
1358
+ makeErrorId as makeErrorId2,
1232
1359
  FIELD_RENDERER_PROP_KEYS
1233
1360
  } from "@dynamic-field-kit/core";
1234
1361
  import {
@@ -1258,6 +1385,7 @@ export {
1258
1385
  getDefaultRenderer,
1259
1386
  indexGroupPathMap2 as indexGroupPathMap,
1260
1387
  layoutRegistry,
1388
+ makeErrorId2 as makeErrorId,
1261
1389
  makeFieldId2 as makeFieldId,
1262
1390
  resolveDisabled,
1263
1391
  resolveOptions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-field-kit/react",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "React renderer for dynamic-field-kit",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -26,7 +26,7 @@
26
26
  "react-dom": "^18.0.0 || ^19.0.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@dynamic-field-kit/core": "^1.6.0",
29
+ "@dynamic-field-kit/core": "^1.7.0",
30
30
  "@testing-library/dom": "^10.0.0",
31
31
  "@testing-library/jest-dom": "^7.0.1",
32
32
  "@testing-library/react": "^16.0.0",