hs-uix 1.3.0 → 1.4.1

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/form.mjs CHANGED
@@ -59,6 +59,7 @@ var getEmptyValue = (field) => {
59
59
  case "datetime":
60
60
  return void 0;
61
61
  case "display":
62
+ case "slot":
62
63
  case "crmPropertyList":
63
64
  case "crmAssociationPropertyList":
64
65
  return void 0;
@@ -202,7 +203,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
202
203
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
203
204
  const includeCustomValidators = options.includeCustomValidators !== false;
204
205
  const msg = options.messages || {};
205
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
206
+ if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
206
207
  const isRequired = resolveRequired(field, allValues);
207
208
  const plugin = fieldTypes && fieldTypes[field.type];
208
209
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -425,6 +426,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
425
426
  // explicit row layout array (overrides columns + columnWidth)
426
427
  sections,
427
428
  // FormBuilderSection[] — accordion field grouping
429
+ groups,
430
+ // Record<string, FormBuilderGroupOptions> — per-group rendering options keyed by group name
428
431
  gap = "sm",
429
432
  // gap between fields
430
433
  showRequiredIndicator = true,
@@ -634,7 +637,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
634
637
  const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
635
638
  const vals = {};
636
639
  for (const field of fields) {
637
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
640
+ if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
638
641
  if (field.type === "fieldGroup" && field.items && field.fields) {
639
642
  for (const item of field.items) {
640
643
  const subFields = field.fields(item);
@@ -668,9 +671,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
668
671
  const inputDebounceRef = useRef(/* @__PURE__ */ new Map());
669
672
  const rowKeyRef = useRef(/* @__PURE__ */ new WeakMap());
670
673
  const rowKeyCounterRef = useRef(0);
674
+ const controlledBaselineLockedRef = useRef(false);
671
675
  const initialSnapshot = useRef(null);
672
676
  if (initialSnapshot.current === null) {
673
- initialSnapshot.current = deepClone(computeInitialValues());
677
+ initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
674
678
  }
675
679
  const formValues = values != null ? values : internalValues;
676
680
  const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
@@ -682,6 +686,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
682
686
  const draftValuesRef = useRef(null);
683
687
  formValuesRef.current = formValues;
684
688
  formErrorsRef.current = formErrors;
689
+ const syncDirtyBaseline = useCallback((nextValues) => {
690
+ initialSnapshot.current = deepClone(nextValues || {});
691
+ prevAutoSaveValues.current = deepClone(nextValues || {});
692
+ }, []);
685
693
  const fieldByName = useMemo(() => {
686
694
  const map = /* @__PURE__ */ new Map();
687
695
  for (const field of fields) {
@@ -758,6 +766,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
758
766
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
759
767
  };
760
768
  }, []);
769
+ useEffect(() => {
770
+ if (values == null) return;
771
+ if (controlledBaselineLockedRef.current) return;
772
+ syncDirtyBaseline(values);
773
+ }, [values, syncDirtyBaseline]);
761
774
  const isDirty = useMemo(() => {
762
775
  return !deepEqual(formValues, initialSnapshot.current);
763
776
  }, [formValues]);
@@ -876,6 +889,50 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
876
889
  },
877
890
  [fieldTypes]
878
891
  );
892
+ const setRepeaterSubFieldError = useCallback(
893
+ (fieldName, rowIdx, subFieldName, errorMessage) => {
894
+ const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
895
+ const merged = { ...formErrorsRef.current };
896
+ if (errorMessage) {
897
+ merged[key] = errorMessage;
898
+ } else {
899
+ delete merged[key];
900
+ }
901
+ const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
902
+ const match = k.match(/\[(\d+)\]\./);
903
+ const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
904
+ return { key: k, row };
905
+ }).sort((a, b) => a.row - b.row);
906
+ if (subErrors.length > 0) {
907
+ const first = subErrors[0];
908
+ merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
909
+ } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
910
+ delete merged[fieldName];
911
+ }
912
+ replaceErrors(merged);
913
+ },
914
+ [replaceErrors]
915
+ );
916
+ const expandValidationFields = useCallback(
917
+ (fieldSubset) => {
918
+ const toValidate = fieldSubset || visibleFields;
919
+ const expanded = [];
920
+ for (const field of toValidate) {
921
+ if (field.type === "fieldGroup" && field.items && field.fields) {
922
+ for (const item of field.items) {
923
+ for (const subField of field.fields(item)) {
924
+ if (subField.visible && !subField.visible(formValues)) continue;
925
+ expanded.push(subField);
926
+ }
927
+ }
928
+ continue;
929
+ }
930
+ expanded.push(field);
931
+ }
932
+ return expanded;
933
+ },
934
+ [visibleFields, formValues]
935
+ );
879
936
  const validateField = useCallback(
880
937
  (name, value) => {
881
938
  const field = fieldByName.get(name);
@@ -895,7 +952,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
895
952
  );
896
953
  const validateVisibleFields = useCallback(
897
954
  (fieldSubset) => {
898
- const toValidate = fieldSubset || visibleFields;
955
+ const toValidate = expandValidationFields(fieldSubset);
899
956
  const errors = {};
900
957
  let hasErrors = false;
901
958
  for (const field of toValidate) {
@@ -915,52 +972,54 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
915
972
  }
916
973
  return { errors, hasErrors };
917
974
  },
918
- [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
975
+ [expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
919
976
  );
920
- const runAsyncValidation = useCallback(
921
- (name, value) => {
922
- const field = fieldByName.get(name);
923
- if (!field || field.type === "repeater") return null;
924
- const val = value != null ? value : formValues[name];
925
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
926
- const prevController = asyncAbortRef.current.get(name);
977
+ const runAsyncValidationTarget = useCallback(
978
+ (target) => {
979
+ const { validationKey, field, value, allValues, applyError } = target || {};
980
+ if (!field || !validationKey || field.type === "repeater" || field.type === "fieldGroup") return null;
981
+ const syncError = runValidators(value, field, allValues, fieldTypes, {
982
+ includeCustomValidators: false,
983
+ messages: validationMessages
984
+ });
985
+ const prevController = asyncAbortRef.current.get(validationKey);
927
986
  if (prevController) prevController.abort();
928
- asyncAbortRef.current.delete(name);
987
+ asyncAbortRef.current.delete(validationKey);
929
988
  setValidatingFields((prev) => {
930
- if (!prev[name]) return prev;
989
+ if (!prev[validationKey]) return prev;
931
990
  const next = { ...prev };
932
- delete next[name];
991
+ delete next[validationKey];
933
992
  return next;
934
993
  });
935
994
  if (syncError) return null;
936
- const version = (asyncValidationVersionRef.current.get(name) || 0) + 1;
937
- asyncValidationVersionRef.current.set(name, version);
995
+ const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
996
+ asyncValidationVersionRef.current.set(validationKey, version);
938
997
  const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
939
- if (controller) asyncAbortRef.current.set(name, controller);
998
+ if (controller) asyncAbortRef.current.set(validationKey, controller);
940
999
  let asyncPromises;
941
1000
  try {
942
1001
  asyncPromises = collectAsyncValidatorPromises(
943
- val,
1002
+ value,
944
1003
  field,
945
- formValues,
1004
+ allValues,
946
1005
  controller ? { signal: controller.signal } : void 0
947
1006
  );
948
1007
  } catch (err) {
949
- updateErrors({ [name]: (err == null ? void 0 : err.message) || "Validation failed" });
1008
+ applyError((err == null ? void 0 : err.message) || "Validation failed");
950
1009
  return null;
951
1010
  }
952
1011
  if (asyncPromises.length === 0) {
953
- asyncAbortRef.current.delete(name);
1012
+ asyncAbortRef.current.delete(validationKey);
954
1013
  return null;
955
1014
  }
956
1015
  const validationPromise = Promise.all(asyncPromises).then(
957
1016
  (results) => {
958
- if (asyncValidationVersionRef.current.get(name) !== version) return;
959
- asyncValidationRef.current.delete(name);
960
- asyncAbortRef.current.delete(name);
1017
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
1018
+ asyncValidationRef.current.delete(validationKey);
1019
+ asyncAbortRef.current.delete(validationKey);
961
1020
  setValidatingFields((prev) => {
962
1021
  const next = { ...prev };
963
- delete next[name];
1022
+ delete next[validationKey];
964
1023
  return next;
965
1024
  });
966
1025
  let err = null;
@@ -971,50 +1030,128 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
971
1030
  break;
972
1031
  }
973
1032
  }
974
- updateErrors({ [name]: err });
1033
+ applyError(err);
975
1034
  },
976
1035
  (rejection) => {
977
- if (asyncValidationVersionRef.current.get(name) !== version) return;
978
- asyncValidationRef.current.delete(name);
979
- asyncAbortRef.current.delete(name);
1036
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
1037
+ asyncValidationRef.current.delete(validationKey);
1038
+ asyncAbortRef.current.delete(validationKey);
980
1039
  setValidatingFields((prev) => {
981
1040
  const next = { ...prev };
982
- delete next[name];
1041
+ delete next[validationKey];
983
1042
  return next;
984
1043
  });
985
1044
  if (rejection && rejection.name === "AbortError") return;
986
- updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
1045
+ applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
987
1046
  }
988
1047
  );
989
- asyncValidationRef.current.set(name, validationPromise);
990
- setValidatingFields((prev) => ({ ...prev, [name]: true }));
1048
+ asyncValidationRef.current.set(validationKey, validationPromise);
1049
+ setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
991
1050
  return validationPromise;
992
1051
  },
993
- [fieldByName, formValues, fieldTypes, updateErrors]
1052
+ [fieldTypes, validationMessages]
994
1053
  );
995
- const triggerAsyncValidation = useCallback(
1054
+ const runAsyncValidation = useCallback(
996
1055
  (name, value) => {
997
1056
  const field = fieldByName.get(name);
998
- if (!field || field.type === "repeater") return;
999
- const debounceMs = field.validateDebounce;
1057
+ if (!field || field.type === "repeater" || field.type === "fieldGroup") return null;
1058
+ return runAsyncValidationTarget({
1059
+ validationKey: name,
1060
+ field,
1061
+ value: value != null ? value : formValues[name],
1062
+ allValues: formValues,
1063
+ applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
1064
+ });
1065
+ },
1066
+ [fieldByName, formValues, runAsyncValidationTarget, updateErrors]
1067
+ );
1068
+ const triggerAsyncValidationTarget = useCallback(
1069
+ (target) => {
1070
+ if (!(target == null ? void 0 : target.field) || !target.validationKey) return;
1071
+ const debounceMs = target.field.validateDebounce;
1000
1072
  if (debounceMs && debounceMs > 0) {
1001
- const existing = debounceTimersRef.current.get(name);
1073
+ const existing = debounceTimersRef.current.get(target.validationKey);
1002
1074
  if (existing) clearTimeout(existing);
1003
1075
  const timer = setTimeout(() => {
1004
- debounceTimersRef.current.delete(name);
1005
- runAsyncValidation(name, value);
1076
+ debounceTimersRef.current.delete(target.validationKey);
1077
+ runAsyncValidationTarget(target);
1006
1078
  }, debounceMs);
1007
- debounceTimersRef.current.set(name, timer);
1079
+ debounceTimersRef.current.set(target.validationKey, timer);
1008
1080
  } else {
1009
- runAsyncValidation(name, value);
1081
+ runAsyncValidationTarget(target);
1010
1082
  }
1011
1083
  },
1012
- [fieldByName, runAsyncValidation]
1084
+ [runAsyncValidationTarget]
1085
+ );
1086
+ const triggerAsyncValidation = useCallback(
1087
+ (name, value) => {
1088
+ const field = fieldByName.get(name);
1089
+ if (!field || field.type === "repeater" || field.type === "fieldGroup") return;
1090
+ triggerAsyncValidationTarget({
1091
+ validationKey: name,
1092
+ field,
1093
+ value: value != null ? value : formValuesRef.current[name],
1094
+ allValues: formValuesRef.current,
1095
+ applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
1096
+ });
1097
+ },
1098
+ [fieldByName, triggerAsyncValidationTarget, updateErrors]
1099
+ );
1100
+ const getAsyncValidationTargets = useCallback(
1101
+ (fieldSubset) => {
1102
+ const toValidate = fieldSubset || visibleFields;
1103
+ const targets = [];
1104
+ for (const field of toValidate) {
1105
+ if (field.type === "fieldGroup" && field.items && field.fields) {
1106
+ for (const item of field.items) {
1107
+ for (const subField of field.fields(item)) {
1108
+ if (subField.visible && !subField.visible(formValues)) continue;
1109
+ targets.push({
1110
+ validationKey: subField.name,
1111
+ field: subField,
1112
+ value: formValues[subField.name],
1113
+ allValues: formValues,
1114
+ applyError: (errorMessage) => updateErrors({ [subField.name]: errorMessage })
1115
+ });
1116
+ }
1117
+ }
1118
+ continue;
1119
+ }
1120
+ if (field.type === "repeater") {
1121
+ const rows = Array.isArray(formValues[field.name]) ? formValues[field.name] : [];
1122
+ const subFields = field.fields || [];
1123
+ rows.forEach((row, rowIdx) => {
1124
+ const rowValues = { ...formValues, [field.name]: rows };
1125
+ subFields.forEach((subField) => {
1126
+ if (subField.visible && !subField.visible(rowValues)) return;
1127
+ targets.push({
1128
+ validationKey: getRepeaterErrorKey(field.name, rowIdx, subField.name),
1129
+ field: subField,
1130
+ value: row == null ? void 0 : row[subField.name],
1131
+ allValues: rowValues,
1132
+ applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
1133
+ });
1134
+ });
1135
+ });
1136
+ continue;
1137
+ }
1138
+ targets.push({
1139
+ validationKey: field.name,
1140
+ field,
1141
+ value: formValues[field.name],
1142
+ allValues: formValues,
1143
+ applyError: (errorMessage) => updateErrors({ [field.name]: errorMessage })
1144
+ });
1145
+ }
1146
+ return targets;
1147
+ },
1148
+ [visibleFields, formValues, setRepeaterSubFieldError, updateErrors]
1013
1149
  );
1014
1150
  const commitValues = useCallback(
1015
1151
  (nextValues) => {
1016
1152
  formValuesRef.current = nextValues;
1017
1153
  if (values != null) {
1154
+ controlledBaselineLockedRef.current = true;
1018
1155
  if (onChange) onChange(nextValues);
1019
1156
  } else {
1020
1157
  setInternalValues(nextValues);
@@ -1032,7 +1169,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1032
1169
  [commitValues]
1033
1170
  );
1034
1171
  const handleFieldChange = useCallback(
1035
- (name, value) => {
1172
+ (name, value, options = {}) => {
1173
+ const { clearNestedErrors = true } = options;
1036
1174
  const newValues = { ...formValuesRef.current, [name]: value };
1037
1175
  const queue = [name];
1038
1176
  const visited = /* @__PURE__ */ new Set();
@@ -1073,9 +1211,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1073
1211
  if (formErrorsRef.current[name] != null) {
1074
1212
  clearedErrors[name] = null;
1075
1213
  }
1076
- for (const key of Object.keys(formErrorsRef.current)) {
1077
- if (key.startsWith(`${name}[`)) {
1078
- clearedErrors[key] = null;
1214
+ if (clearNestedErrors) {
1215
+ for (const key of Object.keys(formErrorsRef.current)) {
1216
+ if (key.startsWith(`${name}[`)) {
1217
+ clearedErrors[key] = null;
1218
+ }
1079
1219
  }
1080
1220
  }
1081
1221
  draftValuesRef.current = newValues;
@@ -1144,7 +1284,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1144
1284
  replaceErrors(errors);
1145
1285
  return;
1146
1286
  }
1147
- const asyncSubmitValidations = allVisibleFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
1287
+ const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
1148
1288
  if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
1149
1289
  const pendingValidations = [
1150
1290
  .../* @__PURE__ */ new Set([
@@ -1159,6 +1299,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1159
1299
  const reset = () => {
1160
1300
  const fresh = computeInitialValues();
1161
1301
  if (values == null) setInternalValues(fresh);
1302
+ controlledBaselineLockedRef.current = false;
1162
1303
  replaceErrors({});
1163
1304
  initialSnapshot.current = deepClone(fresh);
1164
1305
  prevAutoSaveValues.current = deepClone(fresh);
@@ -1166,7 +1307,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1166
1307
  const rawValues = {};
1167
1308
  for (const key of Object.keys(formValues)) {
1168
1309
  const f = fieldByName.get(key);
1169
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
1310
+ if (f && (f.type === "display" || f.type === "slot" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
1170
1311
  rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
1171
1312
  }
1172
1313
  for (const f of fields) {
@@ -1198,7 +1339,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1198
1339
  if (controlledLoading == null) setInternalLoading(false);
1199
1340
  }
1200
1341
  },
1201
- [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, runAsyncValidation]
1342
+ [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
1202
1343
  );
1203
1344
  const handleNext = useCallback(async () => {
1204
1345
  if (!isMultiStep) return;
@@ -1210,7 +1351,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1210
1351
  replaceErrors({ ...formErrorsRef.current, ...errors });
1211
1352
  return;
1212
1353
  }
1213
- const asyncStepValidations = stepFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
1354
+ const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
1214
1355
  if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
1215
1356
  const pendingValidations = [
1216
1357
  .../* @__PURE__ */ new Set([
@@ -1235,7 +1376,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1235
1376
  } else {
1236
1377
  setInternalStep(nextStep);
1237
1378
  }
1238
- }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, runAsyncValidation]);
1379
+ }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
1239
1380
  const handleBack = useCallback(() => {
1240
1381
  if (!isMultiStep) return;
1241
1382
  const prevStep = Math.max(currentStep - 1, 0);
@@ -1267,6 +1408,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1267
1408
  reset: () => {
1268
1409
  const fresh = computeInitialValues();
1269
1410
  if (values == null) setInternalValues(fresh);
1411
+ controlledBaselineLockedRef.current = false;
1270
1412
  replaceErrors({});
1271
1413
  initialSnapshot.current = deepClone(fresh);
1272
1414
  prevAutoSaveValues.current = deepClone(fresh);
@@ -1279,30 +1421,6 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1279
1421
  replaceErrors(errors);
1280
1422
  }
1281
1423
  }));
1282
- const setRepeaterSubFieldError = useCallback(
1283
- (fieldName, rowIdx, subFieldName, errorMessage) => {
1284
- const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
1285
- const merged = { ...formErrorsRef.current };
1286
- if (errorMessage) {
1287
- merged[key] = errorMessage;
1288
- } else {
1289
- delete merged[key];
1290
- }
1291
- const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
1292
- const match = k.match(/\[(\d+)\]\./);
1293
- const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
1294
- return { key: k, row };
1295
- }).sort((a, b) => a.row - b.row);
1296
- if (subErrors.length > 0) {
1297
- const first = subErrors[0];
1298
- merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
1299
- } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
1300
- delete merged[fieldName];
1301
- }
1302
- replaceErrors(merged);
1303
- },
1304
- [replaceErrors]
1305
- );
1306
1424
  const renderField = (field) => {
1307
1425
  const fieldError = formErrors[field.name] || null;
1308
1426
  const rendered = renderFieldInner(field);
@@ -1317,7 +1435,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1317
1435
  const isReadOnly = field.readOnly || formReadOnly;
1318
1436
  const isDisabled = disabled || resolveDisabled(field, formValues) || formReadOnly;
1319
1437
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
1320
- if (field.type === "display") {
1438
+ if (field.type === "display" || field.type === "slot") {
1321
1439
  if (field.render) {
1322
1440
  return field.render({
1323
1441
  values: formValues,
@@ -1782,12 +1900,13 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1782
1900
  const rowValues = { ...formValues, [field.name]: nextRows };
1783
1901
  const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
1784
1902
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
1903
+ return err;
1785
1904
  };
1786
1905
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
1787
1906
  const updated = rows.map(
1788
1907
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
1789
1908
  );
1790
- handleFieldChange(field.name, updated);
1909
+ handleFieldChange(field.name, updated, { clearNestedErrors: false });
1791
1910
  if (validateOnChange) {
1792
1911
  validateSubField(rowIdx, subField, subValue, updated);
1793
1912
  }
@@ -1797,13 +1916,24 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1797
1916
  const nextRows = rows.map(
1798
1917
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
1799
1918
  );
1800
- validateSubField(rowIdx, subField, subValue, nextRows);
1919
+ const err = validateSubField(rowIdx, subField, subValue, nextRows);
1920
+ if (err) return;
1921
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, subField.name);
1922
+ const rowValues = { ...formValues, [field.name]: nextRows };
1923
+ triggerAsyncValidationTarget({
1924
+ validationKey,
1925
+ field: subField,
1926
+ value: subValue,
1927
+ allValues: rowValues,
1928
+ applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
1929
+ });
1801
1930
  };
1802
1931
  return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ React.createElement(Text, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ React.createElement(Flex, { key: getRowKey(field.name, row, rowIdx), direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
1803
1932
  const sfValue = row[sf.name];
1804
1933
  const sfLabel = rowIdx === 0 ? sf.label : void 0;
1805
1934
  const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
1806
1935
  const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
1936
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
1807
1937
  const sfProps = {
1808
1938
  name: `${field.name}-${rowIdx}-${sf.name}`,
1809
1939
  label: sfLabel,
@@ -1812,6 +1942,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1812
1942
  disabled: resolveDisabled(sf, formValues) || isDisabled,
1813
1943
  error: !!sfError,
1814
1944
  validationMessage: sfError || void 0,
1945
+ ...validatingFields[validationKey] ? { loading: true } : {},
1815
1946
  ...sf.fieldProps || {}
1816
1947
  };
1817
1948
  let sfElement;
@@ -1889,7 +2020,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1889
2020
  const getFieldColSpan = (field) => {
1890
2021
  if (field.colSpan != null) return Math.min(field.colSpan, columns);
1891
2022
  if (field.width === "full" && columns > 1) return columns;
1892
- if (columns > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
2023
+ if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
1893
2024
  return 1;
1894
2025
  };
1895
2026
  const getDependents = (parentField) => visibleFields.filter(
@@ -1914,7 +2045,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1914
2045
  const colSpan = (field) => {
1915
2046
  if (field.colSpan != null) return Math.min(field.colSpan, cols);
1916
2047
  if (field.width === "full" && cols > 1) return cols;
1917
- if (cols > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
2048
+ if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
1918
2049
  return 1;
1919
2050
  };
1920
2051
  const flushRow = () => {
@@ -2078,13 +2209,23 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2078
2209
  const elements = [];
2079
2210
  for (let i = 0; i < chunks.length; i++) {
2080
2211
  const chunk = chunks[i];
2081
- if (i > 0) {
2212
+ const opts = chunk.group && groups && groups[chunk.group] || {};
2213
+ const showDivider = opts.showDivider !== false;
2214
+ const showLabel = opts.showLabel !== false;
2215
+ if (i > 0 && showDivider) {
2082
2216
  elements.push(/* @__PURE__ */ React.createElement(Divider, { key: `group-div-${i}` }));
2083
2217
  }
2084
- if (chunk.group) {
2085
- elements.push(
2086
- /* @__PURE__ */ React.createElement(Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, chunk.group)
2087
- );
2218
+ if (chunk.group && showLabel) {
2219
+ if (typeof opts.renderHeader === "function") {
2220
+ const header = opts.renderHeader(chunk.group, chunk.fields, formValues);
2221
+ if (header) elements.push(
2222
+ /* @__PURE__ */ React.createElement(React.Fragment, { key: `group-header-${i}` }, header)
2223
+ );
2224
+ } else {
2225
+ elements.push(
2226
+ /* @__PURE__ */ React.createElement(Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
2227
+ );
2228
+ }
2088
2229
  }
2089
2230
  const chunkElements = renderFn(chunk.fields);
2090
2231
  if (Array.isArray(chunkElements)) {