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.js CHANGED
@@ -55,6 +55,7 @@ var getEmptyValue = (field) => {
55
55
  case "datetime":
56
56
  return void 0;
57
57
  case "display":
58
+ case "slot":
58
59
  case "crmPropertyList":
59
60
  case "crmAssociationPropertyList":
60
61
  return void 0;
@@ -198,7 +199,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
198
199
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
199
200
  const includeCustomValidators = options.includeCustomValidators !== false;
200
201
  const msg = options.messages || {};
201
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
202
+ if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
202
203
  const isRequired = resolveRequired(field, allValues);
203
204
  const plugin = fieldTypes && fieldTypes[field.type];
204
205
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -421,6 +422,8 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
421
422
  // explicit row layout array (overrides columns + columnWidth)
422
423
  sections,
423
424
  // FormBuilderSection[] — accordion field grouping
425
+ groups,
426
+ // Record<string, FormBuilderGroupOptions> — per-group rendering options keyed by group name
424
427
  gap = "sm",
425
428
  // gap between fields
426
429
  showRequiredIndicator = true,
@@ -630,7 +633,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
630
633
  const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
631
634
  const vals = {};
632
635
  for (const field of fields) {
633
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
636
+ if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
634
637
  if (field.type === "fieldGroup" && field.items && field.fields) {
635
638
  for (const item of field.items) {
636
639
  const subFields = field.fields(item);
@@ -664,9 +667,10 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
664
667
  const inputDebounceRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
665
668
  const rowKeyRef = (0, import_react.useRef)(/* @__PURE__ */ new WeakMap());
666
669
  const rowKeyCounterRef = (0, import_react.useRef)(0);
670
+ const controlledBaselineLockedRef = (0, import_react.useRef)(false);
667
671
  const initialSnapshot = (0, import_react.useRef)(null);
668
672
  if (initialSnapshot.current === null) {
669
- initialSnapshot.current = deepClone(computeInitialValues());
673
+ initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
670
674
  }
671
675
  const formValues = values != null ? values : internalValues;
672
676
  const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
@@ -678,6 +682,10 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
678
682
  const draftValuesRef = (0, import_react.useRef)(null);
679
683
  formValuesRef.current = formValues;
680
684
  formErrorsRef.current = formErrors;
685
+ const syncDirtyBaseline = (0, import_react.useCallback)((nextValues) => {
686
+ initialSnapshot.current = deepClone(nextValues || {});
687
+ prevAutoSaveValues.current = deepClone(nextValues || {});
688
+ }, []);
681
689
  const fieldByName = (0, import_react.useMemo)(() => {
682
690
  const map = /* @__PURE__ */ new Map();
683
691
  for (const field of fields) {
@@ -754,6 +762,11 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
754
762
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
755
763
  };
756
764
  }, []);
765
+ (0, import_react.useEffect)(() => {
766
+ if (values == null) return;
767
+ if (controlledBaselineLockedRef.current) return;
768
+ syncDirtyBaseline(values);
769
+ }, [values, syncDirtyBaseline]);
757
770
  const isDirty = (0, import_react.useMemo)(() => {
758
771
  return !deepEqual(formValues, initialSnapshot.current);
759
772
  }, [formValues]);
@@ -872,6 +885,50 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
872
885
  },
873
886
  [fieldTypes]
874
887
  );
888
+ const setRepeaterSubFieldError = (0, import_react.useCallback)(
889
+ (fieldName, rowIdx, subFieldName, errorMessage) => {
890
+ const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
891
+ const merged = { ...formErrorsRef.current };
892
+ if (errorMessage) {
893
+ merged[key] = errorMessage;
894
+ } else {
895
+ delete merged[key];
896
+ }
897
+ const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
898
+ const match = k.match(/\[(\d+)\]\./);
899
+ const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
900
+ return { key: k, row };
901
+ }).sort((a, b) => a.row - b.row);
902
+ if (subErrors.length > 0) {
903
+ const first = subErrors[0];
904
+ merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
905
+ } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
906
+ delete merged[fieldName];
907
+ }
908
+ replaceErrors(merged);
909
+ },
910
+ [replaceErrors]
911
+ );
912
+ const expandValidationFields = (0, import_react.useCallback)(
913
+ (fieldSubset) => {
914
+ const toValidate = fieldSubset || visibleFields;
915
+ const expanded = [];
916
+ for (const field of toValidate) {
917
+ if (field.type === "fieldGroup" && field.items && field.fields) {
918
+ for (const item of field.items) {
919
+ for (const subField of field.fields(item)) {
920
+ if (subField.visible && !subField.visible(formValues)) continue;
921
+ expanded.push(subField);
922
+ }
923
+ }
924
+ continue;
925
+ }
926
+ expanded.push(field);
927
+ }
928
+ return expanded;
929
+ },
930
+ [visibleFields, formValues]
931
+ );
875
932
  const validateField = (0, import_react.useCallback)(
876
933
  (name, value) => {
877
934
  const field = fieldByName.get(name);
@@ -891,7 +948,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
891
948
  );
892
949
  const validateVisibleFields = (0, import_react.useCallback)(
893
950
  (fieldSubset) => {
894
- const toValidate = fieldSubset || visibleFields;
951
+ const toValidate = expandValidationFields(fieldSubset);
895
952
  const errors = {};
896
953
  let hasErrors = false;
897
954
  for (const field of toValidate) {
@@ -911,52 +968,54 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
911
968
  }
912
969
  return { errors, hasErrors };
913
970
  },
914
- [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
971
+ [expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
915
972
  );
916
- const runAsyncValidation = (0, import_react.useCallback)(
917
- (name, value) => {
918
- const field = fieldByName.get(name);
919
- if (!field || field.type === "repeater") return null;
920
- const val = value != null ? value : formValues[name];
921
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
922
- const prevController = asyncAbortRef.current.get(name);
973
+ const runAsyncValidationTarget = (0, import_react.useCallback)(
974
+ (target) => {
975
+ const { validationKey, field, value, allValues, applyError } = target || {};
976
+ if (!field || !validationKey || field.type === "repeater" || field.type === "fieldGroup") return null;
977
+ const syncError = runValidators(value, field, allValues, fieldTypes, {
978
+ includeCustomValidators: false,
979
+ messages: validationMessages
980
+ });
981
+ const prevController = asyncAbortRef.current.get(validationKey);
923
982
  if (prevController) prevController.abort();
924
- asyncAbortRef.current.delete(name);
983
+ asyncAbortRef.current.delete(validationKey);
925
984
  setValidatingFields((prev) => {
926
- if (!prev[name]) return prev;
985
+ if (!prev[validationKey]) return prev;
927
986
  const next = { ...prev };
928
- delete next[name];
987
+ delete next[validationKey];
929
988
  return next;
930
989
  });
931
990
  if (syncError) return null;
932
- const version = (asyncValidationVersionRef.current.get(name) || 0) + 1;
933
- asyncValidationVersionRef.current.set(name, version);
991
+ const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
992
+ asyncValidationVersionRef.current.set(validationKey, version);
934
993
  const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
935
- if (controller) asyncAbortRef.current.set(name, controller);
994
+ if (controller) asyncAbortRef.current.set(validationKey, controller);
936
995
  let asyncPromises;
937
996
  try {
938
997
  asyncPromises = collectAsyncValidatorPromises(
939
- val,
998
+ value,
940
999
  field,
941
- formValues,
1000
+ allValues,
942
1001
  controller ? { signal: controller.signal } : void 0
943
1002
  );
944
1003
  } catch (err) {
945
- updateErrors({ [name]: (err == null ? void 0 : err.message) || "Validation failed" });
1004
+ applyError((err == null ? void 0 : err.message) || "Validation failed");
946
1005
  return null;
947
1006
  }
948
1007
  if (asyncPromises.length === 0) {
949
- asyncAbortRef.current.delete(name);
1008
+ asyncAbortRef.current.delete(validationKey);
950
1009
  return null;
951
1010
  }
952
1011
  const validationPromise = Promise.all(asyncPromises).then(
953
1012
  (results) => {
954
- if (asyncValidationVersionRef.current.get(name) !== version) return;
955
- asyncValidationRef.current.delete(name);
956
- asyncAbortRef.current.delete(name);
1013
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
1014
+ asyncValidationRef.current.delete(validationKey);
1015
+ asyncAbortRef.current.delete(validationKey);
957
1016
  setValidatingFields((prev) => {
958
1017
  const next = { ...prev };
959
- delete next[name];
1018
+ delete next[validationKey];
960
1019
  return next;
961
1020
  });
962
1021
  let err = null;
@@ -967,50 +1026,128 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
967
1026
  break;
968
1027
  }
969
1028
  }
970
- updateErrors({ [name]: err });
1029
+ applyError(err);
971
1030
  },
972
1031
  (rejection) => {
973
- if (asyncValidationVersionRef.current.get(name) !== version) return;
974
- asyncValidationRef.current.delete(name);
975
- asyncAbortRef.current.delete(name);
1032
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
1033
+ asyncValidationRef.current.delete(validationKey);
1034
+ asyncAbortRef.current.delete(validationKey);
976
1035
  setValidatingFields((prev) => {
977
1036
  const next = { ...prev };
978
- delete next[name];
1037
+ delete next[validationKey];
979
1038
  return next;
980
1039
  });
981
1040
  if (rejection && rejection.name === "AbortError") return;
982
- updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
1041
+ applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
983
1042
  }
984
1043
  );
985
- asyncValidationRef.current.set(name, validationPromise);
986
- setValidatingFields((prev) => ({ ...prev, [name]: true }));
1044
+ asyncValidationRef.current.set(validationKey, validationPromise);
1045
+ setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
987
1046
  return validationPromise;
988
1047
  },
989
- [fieldByName, formValues, fieldTypes, updateErrors]
1048
+ [fieldTypes, validationMessages]
990
1049
  );
991
- const triggerAsyncValidation = (0, import_react.useCallback)(
1050
+ const runAsyncValidation = (0, import_react.useCallback)(
992
1051
  (name, value) => {
993
1052
  const field = fieldByName.get(name);
994
- if (!field || field.type === "repeater") return;
995
- const debounceMs = field.validateDebounce;
1053
+ if (!field || field.type === "repeater" || field.type === "fieldGroup") return null;
1054
+ return runAsyncValidationTarget({
1055
+ validationKey: name,
1056
+ field,
1057
+ value: value != null ? value : formValues[name],
1058
+ allValues: formValues,
1059
+ applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
1060
+ });
1061
+ },
1062
+ [fieldByName, formValues, runAsyncValidationTarget, updateErrors]
1063
+ );
1064
+ const triggerAsyncValidationTarget = (0, import_react.useCallback)(
1065
+ (target) => {
1066
+ if (!(target == null ? void 0 : target.field) || !target.validationKey) return;
1067
+ const debounceMs = target.field.validateDebounce;
996
1068
  if (debounceMs && debounceMs > 0) {
997
- const existing = debounceTimersRef.current.get(name);
1069
+ const existing = debounceTimersRef.current.get(target.validationKey);
998
1070
  if (existing) clearTimeout(existing);
999
1071
  const timer = setTimeout(() => {
1000
- debounceTimersRef.current.delete(name);
1001
- runAsyncValidation(name, value);
1072
+ debounceTimersRef.current.delete(target.validationKey);
1073
+ runAsyncValidationTarget(target);
1002
1074
  }, debounceMs);
1003
- debounceTimersRef.current.set(name, timer);
1075
+ debounceTimersRef.current.set(target.validationKey, timer);
1004
1076
  } else {
1005
- runAsyncValidation(name, value);
1077
+ runAsyncValidationTarget(target);
1006
1078
  }
1007
1079
  },
1008
- [fieldByName, runAsyncValidation]
1080
+ [runAsyncValidationTarget]
1081
+ );
1082
+ const triggerAsyncValidation = (0, import_react.useCallback)(
1083
+ (name, value) => {
1084
+ const field = fieldByName.get(name);
1085
+ if (!field || field.type === "repeater" || field.type === "fieldGroup") return;
1086
+ triggerAsyncValidationTarget({
1087
+ validationKey: name,
1088
+ field,
1089
+ value: value != null ? value : formValuesRef.current[name],
1090
+ allValues: formValuesRef.current,
1091
+ applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
1092
+ });
1093
+ },
1094
+ [fieldByName, triggerAsyncValidationTarget, updateErrors]
1095
+ );
1096
+ const getAsyncValidationTargets = (0, import_react.useCallback)(
1097
+ (fieldSubset) => {
1098
+ const toValidate = fieldSubset || visibleFields;
1099
+ const targets = [];
1100
+ for (const field of toValidate) {
1101
+ if (field.type === "fieldGroup" && field.items && field.fields) {
1102
+ for (const item of field.items) {
1103
+ for (const subField of field.fields(item)) {
1104
+ if (subField.visible && !subField.visible(formValues)) continue;
1105
+ targets.push({
1106
+ validationKey: subField.name,
1107
+ field: subField,
1108
+ value: formValues[subField.name],
1109
+ allValues: formValues,
1110
+ applyError: (errorMessage) => updateErrors({ [subField.name]: errorMessage })
1111
+ });
1112
+ }
1113
+ }
1114
+ continue;
1115
+ }
1116
+ if (field.type === "repeater") {
1117
+ const rows = Array.isArray(formValues[field.name]) ? formValues[field.name] : [];
1118
+ const subFields = field.fields || [];
1119
+ rows.forEach((row, rowIdx) => {
1120
+ const rowValues = { ...formValues, [field.name]: rows };
1121
+ subFields.forEach((subField) => {
1122
+ if (subField.visible && !subField.visible(rowValues)) return;
1123
+ targets.push({
1124
+ validationKey: getRepeaterErrorKey(field.name, rowIdx, subField.name),
1125
+ field: subField,
1126
+ value: row == null ? void 0 : row[subField.name],
1127
+ allValues: rowValues,
1128
+ applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
1129
+ });
1130
+ });
1131
+ });
1132
+ continue;
1133
+ }
1134
+ targets.push({
1135
+ validationKey: field.name,
1136
+ field,
1137
+ value: formValues[field.name],
1138
+ allValues: formValues,
1139
+ applyError: (errorMessage) => updateErrors({ [field.name]: errorMessage })
1140
+ });
1141
+ }
1142
+ return targets;
1143
+ },
1144
+ [visibleFields, formValues, setRepeaterSubFieldError, updateErrors]
1009
1145
  );
1010
1146
  const commitValues = (0, import_react.useCallback)(
1011
1147
  (nextValues) => {
1012
1148
  formValuesRef.current = nextValues;
1013
1149
  if (values != null) {
1150
+ controlledBaselineLockedRef.current = true;
1014
1151
  if (onChange) onChange(nextValues);
1015
1152
  } else {
1016
1153
  setInternalValues(nextValues);
@@ -1028,7 +1165,8 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1028
1165
  [commitValues]
1029
1166
  );
1030
1167
  const handleFieldChange = (0, import_react.useCallback)(
1031
- (name, value) => {
1168
+ (name, value, options = {}) => {
1169
+ const { clearNestedErrors = true } = options;
1032
1170
  const newValues = { ...formValuesRef.current, [name]: value };
1033
1171
  const queue = [name];
1034
1172
  const visited = /* @__PURE__ */ new Set();
@@ -1069,9 +1207,11 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1069
1207
  if (formErrorsRef.current[name] != null) {
1070
1208
  clearedErrors[name] = null;
1071
1209
  }
1072
- for (const key of Object.keys(formErrorsRef.current)) {
1073
- if (key.startsWith(`${name}[`)) {
1074
- clearedErrors[key] = null;
1210
+ if (clearNestedErrors) {
1211
+ for (const key of Object.keys(formErrorsRef.current)) {
1212
+ if (key.startsWith(`${name}[`)) {
1213
+ clearedErrors[key] = null;
1214
+ }
1075
1215
  }
1076
1216
  }
1077
1217
  draftValuesRef.current = newValues;
@@ -1140,7 +1280,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1140
1280
  replaceErrors(errors);
1141
1281
  return;
1142
1282
  }
1143
- const asyncSubmitValidations = allVisibleFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
1283
+ const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
1144
1284
  if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
1145
1285
  const pendingValidations = [
1146
1286
  .../* @__PURE__ */ new Set([
@@ -1155,6 +1295,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1155
1295
  const reset = () => {
1156
1296
  const fresh = computeInitialValues();
1157
1297
  if (values == null) setInternalValues(fresh);
1298
+ controlledBaselineLockedRef.current = false;
1158
1299
  replaceErrors({});
1159
1300
  initialSnapshot.current = deepClone(fresh);
1160
1301
  prevAutoSaveValues.current = deepClone(fresh);
@@ -1162,7 +1303,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1162
1303
  const rawValues = {};
1163
1304
  for (const key of Object.keys(formValues)) {
1164
1305
  const f = fieldByName.get(key);
1165
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
1306
+ if (f && (f.type === "display" || f.type === "slot" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
1166
1307
  rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
1167
1308
  }
1168
1309
  for (const f of fields) {
@@ -1194,7 +1335,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1194
1335
  if (controlledLoading == null) setInternalLoading(false);
1195
1336
  }
1196
1337
  },
1197
- [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, runAsyncValidation]
1338
+ [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
1198
1339
  );
1199
1340
  const handleNext = (0, import_react.useCallback)(async () => {
1200
1341
  if (!isMultiStep) return;
@@ -1206,7 +1347,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1206
1347
  replaceErrors({ ...formErrorsRef.current, ...errors });
1207
1348
  return;
1208
1349
  }
1209
- const asyncStepValidations = stepFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
1350
+ const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
1210
1351
  if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
1211
1352
  const pendingValidations = [
1212
1353
  .../* @__PURE__ */ new Set([
@@ -1231,7 +1372,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1231
1372
  } else {
1232
1373
  setInternalStep(nextStep);
1233
1374
  }
1234
- }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, runAsyncValidation]);
1375
+ }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
1235
1376
  const handleBack = (0, import_react.useCallback)(() => {
1236
1377
  if (!isMultiStep) return;
1237
1378
  const prevStep = Math.max(currentStep - 1, 0);
@@ -1263,6 +1404,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1263
1404
  reset: () => {
1264
1405
  const fresh = computeInitialValues();
1265
1406
  if (values == null) setInternalValues(fresh);
1407
+ controlledBaselineLockedRef.current = false;
1266
1408
  replaceErrors({});
1267
1409
  initialSnapshot.current = deepClone(fresh);
1268
1410
  prevAutoSaveValues.current = deepClone(fresh);
@@ -1275,30 +1417,6 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1275
1417
  replaceErrors(errors);
1276
1418
  }
1277
1419
  }));
1278
- const setRepeaterSubFieldError = (0, import_react.useCallback)(
1279
- (fieldName, rowIdx, subFieldName, errorMessage) => {
1280
- const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
1281
- const merged = { ...formErrorsRef.current };
1282
- if (errorMessage) {
1283
- merged[key] = errorMessage;
1284
- } else {
1285
- delete merged[key];
1286
- }
1287
- const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
1288
- const match = k.match(/\[(\d+)\]\./);
1289
- const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
1290
- return { key: k, row };
1291
- }).sort((a, b) => a.row - b.row);
1292
- if (subErrors.length > 0) {
1293
- const first = subErrors[0];
1294
- merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
1295
- } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
1296
- delete merged[fieldName];
1297
- }
1298
- replaceErrors(merged);
1299
- },
1300
- [replaceErrors]
1301
- );
1302
1420
  const renderField = (field) => {
1303
1421
  const fieldError = formErrors[field.name] || null;
1304
1422
  const rendered = renderFieldInner(field);
@@ -1313,7 +1431,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1313
1431
  const isReadOnly = field.readOnly || formReadOnly;
1314
1432
  const isDisabled = disabled || resolveDisabled(field, formValues) || formReadOnly;
1315
1433
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
1316
- if (field.type === "display") {
1434
+ if (field.type === "display" || field.type === "slot") {
1317
1435
  if (field.render) {
1318
1436
  return field.render({
1319
1437
  values: formValues,
@@ -1778,12 +1896,13 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1778
1896
  const rowValues = { ...formValues, [field.name]: nextRows };
1779
1897
  const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
1780
1898
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
1899
+ return err;
1781
1900
  };
1782
1901
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
1783
1902
  const updated = rows.map(
1784
1903
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
1785
1904
  );
1786
- handleFieldChange(field.name, updated);
1905
+ handleFieldChange(field.name, updated, { clearNestedErrors: false });
1787
1906
  if (validateOnChange) {
1788
1907
  validateSubField(rowIdx, subField, subValue, updated);
1789
1908
  }
@@ -1793,13 +1912,24 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1793
1912
  const nextRows = rows.map(
1794
1913
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
1795
1914
  );
1796
- validateSubField(rowIdx, subField, subValue, nextRows);
1915
+ const err = validateSubField(rowIdx, subField, subValue, nextRows);
1916
+ if (err) return;
1917
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, subField.name);
1918
+ const rowValues = { ...formValues, [field.name]: nextRows };
1919
+ triggerAsyncValidationTarget({
1920
+ validationKey,
1921
+ field: subField,
1922
+ value: subValue,
1923
+ allValues: rowValues,
1924
+ applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
1925
+ });
1797
1926
  };
1798
1927
  return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { key: getRowKey(field.name, row, rowIdx), direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
1799
1928
  const sfValue = row[sf.name];
1800
1929
  const sfLabel = rowIdx === 0 ? sf.label : void 0;
1801
1930
  const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
1802
1931
  const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
1932
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
1803
1933
  const sfProps = {
1804
1934
  name: `${field.name}-${rowIdx}-${sf.name}`,
1805
1935
  label: sfLabel,
@@ -1808,6 +1938,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1808
1938
  disabled: resolveDisabled(sf, formValues) || isDisabled,
1809
1939
  error: !!sfError,
1810
1940
  validationMessage: sfError || void 0,
1941
+ ...validatingFields[validationKey] ? { loading: true } : {},
1811
1942
  ...sf.fieldProps || {}
1812
1943
  };
1813
1944
  let sfElement;
@@ -1885,7 +2016,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1885
2016
  const getFieldColSpan = (field) => {
1886
2017
  if (field.colSpan != null) return Math.min(field.colSpan, columns);
1887
2018
  if (field.width === "full" && columns > 1) return columns;
1888
- if (columns > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
2019
+ if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
1889
2020
  return 1;
1890
2021
  };
1891
2022
  const getDependents = (parentField) => visibleFields.filter(
@@ -1910,7 +2041,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1910
2041
  const colSpan = (field) => {
1911
2042
  if (field.colSpan != null) return Math.min(field.colSpan, cols);
1912
2043
  if (field.width === "full" && cols > 1) return cols;
1913
- if (cols > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
2044
+ if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
1914
2045
  return 1;
1915
2046
  };
1916
2047
  const flushRow = () => {
@@ -2074,13 +2205,23 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
2074
2205
  const elements = [];
2075
2206
  for (let i = 0; i < chunks.length; i++) {
2076
2207
  const chunk = chunks[i];
2077
- if (i > 0) {
2208
+ const opts = chunk.group && groups && groups[chunk.group] || {};
2209
+ const showDivider = opts.showDivider !== false;
2210
+ const showLabel = opts.showLabel !== false;
2211
+ if (i > 0 && showDivider) {
2078
2212
  elements.push(/* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Divider, { key: `group-div-${i}` }));
2079
2213
  }
2080
- if (chunk.group) {
2081
- elements.push(
2082
- /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, chunk.group)
2083
- );
2214
+ if (chunk.group && showLabel) {
2215
+ if (typeof opts.renderHeader === "function") {
2216
+ const header = opts.renderHeader(chunk.group, chunk.fields, formValues);
2217
+ if (header) elements.push(
2218
+ /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, { key: `group-header-${i}` }, header)
2219
+ );
2220
+ } else {
2221
+ elements.push(
2222
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
2223
+ );
2224
+ }
2084
2225
  }
2085
2226
  const chunkElements = renderFn(chunk.fields);
2086
2227
  if (Array.isArray(chunkElements)) {