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/index.js CHANGED
@@ -509,28 +509,33 @@ var DataTable = ({
509
509
  return next;
510
510
  });
511
511
  }, []);
512
- const flatRows = (0, import_react.useMemo)(() => {
513
- if (!groupedData) return (serverSide ? data : sortedData).map((row) => ({ type: "data", row }));
514
- const flat = [];
515
- groupedData.forEach((group) => {
516
- flat.push({ type: "group-header", group });
517
- if (expandedGroups.has(group.key)) {
518
- group.rows.forEach((row) => flat.push({ type: "data", row }));
519
- }
520
- });
521
- return flat;
522
- }, [groupedData, sortedData, data, serverSide, expandedGroups]);
523
- const totalItems = serverSide ? totalCount || data.length : flatRows.length;
512
+ const datasetRows = (0, import_react.useMemo)(() => {
513
+ if (!groupedData) return serverSide ? data : sortedData;
514
+ return groupedData.flatMap((group) => group.rows);
515
+ }, [groupedData, sortedData, data, serverSide]);
516
+ const totalItems = serverSide ? totalCount || data.length : datasetRows.length;
524
517
  const pageCount = Math.ceil(totalItems / pageSize);
525
- let displayRows;
526
- if (serverSide) {
527
- displayRows = groupBy ? flatRows : data.map((row) => ({ type: "data", row }));
528
- } else {
529
- displayRows = flatRows.slice(
518
+ const paginatedRows = (0, import_react.useMemo)(() => {
519
+ if (serverSide) return datasetRows;
520
+ return datasetRows.slice(
530
521
  (activePage - 1) * pageSize,
531
522
  activePage * pageSize
532
523
  );
533
- }
524
+ }, [serverSide, datasetRows, activePage, pageSize]);
525
+ const displayRows = (0, import_react.useMemo)(() => {
526
+ if (!groupedData) return paginatedRows.map((row) => ({ type: "data", row }));
527
+ const pageRows = new Set(paginatedRows);
528
+ const rows = [];
529
+ groupedData.forEach((group) => {
530
+ const groupPageRows = group.rows.filter((row) => pageRows.has(row));
531
+ if (groupPageRows.length === 0) return;
532
+ rows.push({ type: "group-header", group });
533
+ if (expandedGroups.has(group.key)) {
534
+ groupPageRows.forEach((row) => rows.push({ type: "data", row }));
535
+ }
536
+ });
537
+ return rows;
538
+ }, [groupedData, paginatedRows, expandedGroups]);
534
539
  const footerData = serverSide ? data : filteredData;
535
540
  const activeChips = (0, import_react.useMemo)(() => {
536
541
  const chips = [];
@@ -641,8 +646,8 @@ var DataTable = ({
641
646
  return displayRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null);
642
647
  }, [serverSide, data, displayRows, rowIdField]);
643
648
  const allRowIds = (0, import_react.useMemo)(
644
- () => flatRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null),
645
- [flatRows, rowIdField]
649
+ () => datasetRows.map((row) => row[rowIdField]).filter((id) => id != null),
650
+ [datasetRows, rowIdField]
646
651
  );
647
652
  const handleSelectRow = (0, import_react.useCallback)((rowId, checked) => {
648
653
  const next = new Set(selectedIds);
@@ -689,19 +694,25 @@ var DataTable = ({
689
694
  if (row) onEditStart(row, field, currentValue);
690
695
  }
691
696
  }, [onEditStart, data, rowIdField]);
692
- const commitEdit = (0, import_react.useCallback)((row, field, value) => {
697
+ const commitEdit = (0, import_react.useCallback)((row, field, value, options = {}) => {
698
+ const { keepEditing = false } = options;
693
699
  const col = columns.find((c) => c.field === field);
694
700
  if (col == null ? void 0 : col.editValidate) {
695
701
  const result = col.editValidate(value, row);
696
702
  if (result !== true && result !== void 0 && result !== null) {
697
703
  setEditError(typeof result === "string" ? result : "Invalid value");
698
- return;
704
+ return false;
699
705
  }
700
706
  }
701
707
  if (onRowEdit) onRowEdit(row, field, value);
702
- setEditingCell(null);
703
- setEditValue(null);
708
+ if (!keepEditing) {
709
+ setEditingCell(null);
710
+ setEditValue(null);
711
+ } else {
712
+ setEditValue(value);
713
+ }
704
714
  setEditError(null);
715
+ return true;
705
716
  }, [onRowEdit, columns]);
706
717
  const renderEditControl = (col, row) => {
707
718
  const type = col.editType || "text";
@@ -760,12 +771,12 @@ var DataTable = ({
760
771
  case "datetime":
761
772
  return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
762
773
  const next = { ...editValue, date: val };
763
- setEditValue(next);
764
- if (onRowEdit) onRowEdit(row, col.field, next);
774
+ handleInput(next);
775
+ commitEdit(row, col.field, next, { keepEditing: true });
765
776
  }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
766
777
  const next = { ...editValue, time: val };
767
- setEditValue(next);
768
- if (onRowEdit) onRowEdit(row, col.field, next);
778
+ handleInput(next);
779
+ commitEdit(row, col.field, next, { keepEditing: true });
769
780
  }, onBlur: maybeExitDatetimeEdit }));
770
781
  case "toggle":
771
782
  return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Toggle, { ...extra, name: fieldName, label: "", checked: !!editValue, onChange: commit });
@@ -1126,6 +1137,7 @@ var getEmptyValue = (field) => {
1126
1137
  case "datetime":
1127
1138
  return void 0;
1128
1139
  case "display":
1140
+ case "slot":
1129
1141
  case "crmPropertyList":
1130
1142
  case "crmAssociationPropertyList":
1131
1143
  return void 0;
@@ -1269,7 +1281,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
1269
1281
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1270
1282
  const includeCustomValidators = options.includeCustomValidators !== false;
1271
1283
  const msg = options.messages || {};
1272
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
1284
+ if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
1273
1285
  const isRequired = resolveRequired(field, allValues);
1274
1286
  const plugin = fieldTypes && fieldTypes[field.type];
1275
1287
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -1492,6 +1504,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1492
1504
  // explicit row layout array (overrides columns + columnWidth)
1493
1505
  sections,
1494
1506
  // FormBuilderSection[] — accordion field grouping
1507
+ groups,
1508
+ // Record<string, FormBuilderGroupOptions> — per-group rendering options keyed by group name
1495
1509
  gap = "sm",
1496
1510
  // gap between fields
1497
1511
  showRequiredIndicator = true,
@@ -1701,7 +1715,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1701
1715
  const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
1702
1716
  const vals = {};
1703
1717
  for (const field of fields) {
1704
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
1718
+ if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
1705
1719
  if (field.type === "fieldGroup" && field.items && field.fields) {
1706
1720
  for (const item of field.items) {
1707
1721
  const subFields = field.fields(item);
@@ -1735,9 +1749,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1735
1749
  const inputDebounceRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1736
1750
  const rowKeyRef = (0, import_react2.useRef)(/* @__PURE__ */ new WeakMap());
1737
1751
  const rowKeyCounterRef = (0, import_react2.useRef)(0);
1752
+ const controlledBaselineLockedRef = (0, import_react2.useRef)(false);
1738
1753
  const initialSnapshot = (0, import_react2.useRef)(null);
1739
1754
  if (initialSnapshot.current === null) {
1740
- initialSnapshot.current = deepClone(computeInitialValues());
1755
+ initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
1741
1756
  }
1742
1757
  const formValues = values != null ? values : internalValues;
1743
1758
  const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
@@ -1749,6 +1764,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1749
1764
  const draftValuesRef = (0, import_react2.useRef)(null);
1750
1765
  formValuesRef.current = formValues;
1751
1766
  formErrorsRef.current = formErrors;
1767
+ const syncDirtyBaseline = (0, import_react2.useCallback)((nextValues) => {
1768
+ initialSnapshot.current = deepClone(nextValues || {});
1769
+ prevAutoSaveValues.current = deepClone(nextValues || {});
1770
+ }, []);
1752
1771
  const fieldByName = (0, import_react2.useMemo)(() => {
1753
1772
  const map = /* @__PURE__ */ new Map();
1754
1773
  for (const field of fields) {
@@ -1825,6 +1844,11 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1825
1844
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
1826
1845
  };
1827
1846
  }, []);
1847
+ (0, import_react2.useEffect)(() => {
1848
+ if (values == null) return;
1849
+ if (controlledBaselineLockedRef.current) return;
1850
+ syncDirtyBaseline(values);
1851
+ }, [values, syncDirtyBaseline]);
1828
1852
  const isDirty = (0, import_react2.useMemo)(() => {
1829
1853
  return !deepEqual(formValues, initialSnapshot.current);
1830
1854
  }, [formValues]);
@@ -1943,6 +1967,50 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1943
1967
  },
1944
1968
  [fieldTypes]
1945
1969
  );
1970
+ const setRepeaterSubFieldError = (0, import_react2.useCallback)(
1971
+ (fieldName, rowIdx, subFieldName, errorMessage) => {
1972
+ const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
1973
+ const merged = { ...formErrorsRef.current };
1974
+ if (errorMessage) {
1975
+ merged[key] = errorMessage;
1976
+ } else {
1977
+ delete merged[key];
1978
+ }
1979
+ const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
1980
+ const match = k.match(/\[(\d+)\]\./);
1981
+ const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
1982
+ return { key: k, row };
1983
+ }).sort((a, b) => a.row - b.row);
1984
+ if (subErrors.length > 0) {
1985
+ const first = subErrors[0];
1986
+ merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
1987
+ } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
1988
+ delete merged[fieldName];
1989
+ }
1990
+ replaceErrors(merged);
1991
+ },
1992
+ [replaceErrors]
1993
+ );
1994
+ const expandValidationFields = (0, import_react2.useCallback)(
1995
+ (fieldSubset) => {
1996
+ const toValidate = fieldSubset || visibleFields;
1997
+ const expanded = [];
1998
+ for (const field of toValidate) {
1999
+ if (field.type === "fieldGroup" && field.items && field.fields) {
2000
+ for (const item of field.items) {
2001
+ for (const subField of field.fields(item)) {
2002
+ if (subField.visible && !subField.visible(formValues)) continue;
2003
+ expanded.push(subField);
2004
+ }
2005
+ }
2006
+ continue;
2007
+ }
2008
+ expanded.push(field);
2009
+ }
2010
+ return expanded;
2011
+ },
2012
+ [visibleFields, formValues]
2013
+ );
1946
2014
  const validateField = (0, import_react2.useCallback)(
1947
2015
  (name, value) => {
1948
2016
  const field = fieldByName.get(name);
@@ -1962,7 +2030,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1962
2030
  );
1963
2031
  const validateVisibleFields = (0, import_react2.useCallback)(
1964
2032
  (fieldSubset) => {
1965
- const toValidate = fieldSubset || visibleFields;
2033
+ const toValidate = expandValidationFields(fieldSubset);
1966
2034
  const errors = {};
1967
2035
  let hasErrors = false;
1968
2036
  for (const field of toValidate) {
@@ -1982,52 +2050,54 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1982
2050
  }
1983
2051
  return { errors, hasErrors };
1984
2052
  },
1985
- [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
2053
+ [expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
1986
2054
  );
1987
- const runAsyncValidation = (0, import_react2.useCallback)(
1988
- (name, value) => {
1989
- const field = fieldByName.get(name);
1990
- if (!field || field.type === "repeater") return null;
1991
- const val = value != null ? value : formValues[name];
1992
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
1993
- const prevController = asyncAbortRef.current.get(name);
2055
+ const runAsyncValidationTarget = (0, import_react2.useCallback)(
2056
+ (target) => {
2057
+ const { validationKey, field, value, allValues, applyError } = target || {};
2058
+ if (!field || !validationKey || field.type === "repeater" || field.type === "fieldGroup") return null;
2059
+ const syncError = runValidators(value, field, allValues, fieldTypes, {
2060
+ includeCustomValidators: false,
2061
+ messages: validationMessages
2062
+ });
2063
+ const prevController = asyncAbortRef.current.get(validationKey);
1994
2064
  if (prevController) prevController.abort();
1995
- asyncAbortRef.current.delete(name);
2065
+ asyncAbortRef.current.delete(validationKey);
1996
2066
  setValidatingFields((prev) => {
1997
- if (!prev[name]) return prev;
2067
+ if (!prev[validationKey]) return prev;
1998
2068
  const next = { ...prev };
1999
- delete next[name];
2069
+ delete next[validationKey];
2000
2070
  return next;
2001
2071
  });
2002
2072
  if (syncError) return null;
2003
- const version = (asyncValidationVersionRef.current.get(name) || 0) + 1;
2004
- asyncValidationVersionRef.current.set(name, version);
2073
+ const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
2074
+ asyncValidationVersionRef.current.set(validationKey, version);
2005
2075
  const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
2006
- if (controller) asyncAbortRef.current.set(name, controller);
2076
+ if (controller) asyncAbortRef.current.set(validationKey, controller);
2007
2077
  let asyncPromises;
2008
2078
  try {
2009
2079
  asyncPromises = collectAsyncValidatorPromises(
2010
- val,
2080
+ value,
2011
2081
  field,
2012
- formValues,
2082
+ allValues,
2013
2083
  controller ? { signal: controller.signal } : void 0
2014
2084
  );
2015
2085
  } catch (err) {
2016
- updateErrors({ [name]: (err == null ? void 0 : err.message) || "Validation failed" });
2086
+ applyError((err == null ? void 0 : err.message) || "Validation failed");
2017
2087
  return null;
2018
2088
  }
2019
2089
  if (asyncPromises.length === 0) {
2020
- asyncAbortRef.current.delete(name);
2090
+ asyncAbortRef.current.delete(validationKey);
2021
2091
  return null;
2022
2092
  }
2023
2093
  const validationPromise = Promise.all(asyncPromises).then(
2024
2094
  (results) => {
2025
- if (asyncValidationVersionRef.current.get(name) !== version) return;
2026
- asyncValidationRef.current.delete(name);
2027
- asyncAbortRef.current.delete(name);
2095
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
2096
+ asyncValidationRef.current.delete(validationKey);
2097
+ asyncAbortRef.current.delete(validationKey);
2028
2098
  setValidatingFields((prev) => {
2029
2099
  const next = { ...prev };
2030
- delete next[name];
2100
+ delete next[validationKey];
2031
2101
  return next;
2032
2102
  });
2033
2103
  let err = null;
@@ -2038,50 +2108,128 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2038
2108
  break;
2039
2109
  }
2040
2110
  }
2041
- updateErrors({ [name]: err });
2111
+ applyError(err);
2042
2112
  },
2043
2113
  (rejection) => {
2044
- if (asyncValidationVersionRef.current.get(name) !== version) return;
2045
- asyncValidationRef.current.delete(name);
2046
- asyncAbortRef.current.delete(name);
2114
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
2115
+ asyncValidationRef.current.delete(validationKey);
2116
+ asyncAbortRef.current.delete(validationKey);
2047
2117
  setValidatingFields((prev) => {
2048
2118
  const next = { ...prev };
2049
- delete next[name];
2119
+ delete next[validationKey];
2050
2120
  return next;
2051
2121
  });
2052
2122
  if (rejection && rejection.name === "AbortError") return;
2053
- updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
2123
+ applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
2054
2124
  }
2055
2125
  );
2056
- asyncValidationRef.current.set(name, validationPromise);
2057
- setValidatingFields((prev) => ({ ...prev, [name]: true }));
2126
+ asyncValidationRef.current.set(validationKey, validationPromise);
2127
+ setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
2058
2128
  return validationPromise;
2059
2129
  },
2060
- [fieldByName, formValues, fieldTypes, updateErrors]
2130
+ [fieldTypes, validationMessages]
2061
2131
  );
2062
- const triggerAsyncValidation = (0, import_react2.useCallback)(
2132
+ const runAsyncValidation = (0, import_react2.useCallback)(
2063
2133
  (name, value) => {
2064
2134
  const field = fieldByName.get(name);
2065
- if (!field || field.type === "repeater") return;
2066
- const debounceMs = field.validateDebounce;
2135
+ if (!field || field.type === "repeater" || field.type === "fieldGroup") return null;
2136
+ return runAsyncValidationTarget({
2137
+ validationKey: name,
2138
+ field,
2139
+ value: value != null ? value : formValues[name],
2140
+ allValues: formValues,
2141
+ applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
2142
+ });
2143
+ },
2144
+ [fieldByName, formValues, runAsyncValidationTarget, updateErrors]
2145
+ );
2146
+ const triggerAsyncValidationTarget = (0, import_react2.useCallback)(
2147
+ (target) => {
2148
+ if (!(target == null ? void 0 : target.field) || !target.validationKey) return;
2149
+ const debounceMs = target.field.validateDebounce;
2067
2150
  if (debounceMs && debounceMs > 0) {
2068
- const existing = debounceTimersRef.current.get(name);
2151
+ const existing = debounceTimersRef.current.get(target.validationKey);
2069
2152
  if (existing) clearTimeout(existing);
2070
2153
  const timer = setTimeout(() => {
2071
- debounceTimersRef.current.delete(name);
2072
- runAsyncValidation(name, value);
2154
+ debounceTimersRef.current.delete(target.validationKey);
2155
+ runAsyncValidationTarget(target);
2073
2156
  }, debounceMs);
2074
- debounceTimersRef.current.set(name, timer);
2157
+ debounceTimersRef.current.set(target.validationKey, timer);
2075
2158
  } else {
2076
- runAsyncValidation(name, value);
2159
+ runAsyncValidationTarget(target);
2077
2160
  }
2078
2161
  },
2079
- [fieldByName, runAsyncValidation]
2162
+ [runAsyncValidationTarget]
2163
+ );
2164
+ const triggerAsyncValidation = (0, import_react2.useCallback)(
2165
+ (name, value) => {
2166
+ const field = fieldByName.get(name);
2167
+ if (!field || field.type === "repeater" || field.type === "fieldGroup") return;
2168
+ triggerAsyncValidationTarget({
2169
+ validationKey: name,
2170
+ field,
2171
+ value: value != null ? value : formValuesRef.current[name],
2172
+ allValues: formValuesRef.current,
2173
+ applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
2174
+ });
2175
+ },
2176
+ [fieldByName, triggerAsyncValidationTarget, updateErrors]
2177
+ );
2178
+ const getAsyncValidationTargets = (0, import_react2.useCallback)(
2179
+ (fieldSubset) => {
2180
+ const toValidate = fieldSubset || visibleFields;
2181
+ const targets = [];
2182
+ for (const field of toValidate) {
2183
+ if (field.type === "fieldGroup" && field.items && field.fields) {
2184
+ for (const item of field.items) {
2185
+ for (const subField of field.fields(item)) {
2186
+ if (subField.visible && !subField.visible(formValues)) continue;
2187
+ targets.push({
2188
+ validationKey: subField.name,
2189
+ field: subField,
2190
+ value: formValues[subField.name],
2191
+ allValues: formValues,
2192
+ applyError: (errorMessage) => updateErrors({ [subField.name]: errorMessage })
2193
+ });
2194
+ }
2195
+ }
2196
+ continue;
2197
+ }
2198
+ if (field.type === "repeater") {
2199
+ const rows = Array.isArray(formValues[field.name]) ? formValues[field.name] : [];
2200
+ const subFields = field.fields || [];
2201
+ rows.forEach((row, rowIdx) => {
2202
+ const rowValues = { ...formValues, [field.name]: rows };
2203
+ subFields.forEach((subField) => {
2204
+ if (subField.visible && !subField.visible(rowValues)) return;
2205
+ targets.push({
2206
+ validationKey: getRepeaterErrorKey(field.name, rowIdx, subField.name),
2207
+ field: subField,
2208
+ value: row == null ? void 0 : row[subField.name],
2209
+ allValues: rowValues,
2210
+ applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
2211
+ });
2212
+ });
2213
+ });
2214
+ continue;
2215
+ }
2216
+ targets.push({
2217
+ validationKey: field.name,
2218
+ field,
2219
+ value: formValues[field.name],
2220
+ allValues: formValues,
2221
+ applyError: (errorMessage) => updateErrors({ [field.name]: errorMessage })
2222
+ });
2223
+ }
2224
+ return targets;
2225
+ },
2226
+ [visibleFields, formValues, setRepeaterSubFieldError, updateErrors]
2080
2227
  );
2081
2228
  const commitValues = (0, import_react2.useCallback)(
2082
2229
  (nextValues) => {
2083
2230
  formValuesRef.current = nextValues;
2084
2231
  if (values != null) {
2232
+ controlledBaselineLockedRef.current = true;
2085
2233
  if (onChange) onChange(nextValues);
2086
2234
  } else {
2087
2235
  setInternalValues(nextValues);
@@ -2099,7 +2247,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2099
2247
  [commitValues]
2100
2248
  );
2101
2249
  const handleFieldChange = (0, import_react2.useCallback)(
2102
- (name, value) => {
2250
+ (name, value, options = {}) => {
2251
+ const { clearNestedErrors = true } = options;
2103
2252
  const newValues = { ...formValuesRef.current, [name]: value };
2104
2253
  const queue = [name];
2105
2254
  const visited = /* @__PURE__ */ new Set();
@@ -2140,9 +2289,11 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2140
2289
  if (formErrorsRef.current[name] != null) {
2141
2290
  clearedErrors[name] = null;
2142
2291
  }
2143
- for (const key of Object.keys(formErrorsRef.current)) {
2144
- if (key.startsWith(`${name}[`)) {
2145
- clearedErrors[key] = null;
2292
+ if (clearNestedErrors) {
2293
+ for (const key of Object.keys(formErrorsRef.current)) {
2294
+ if (key.startsWith(`${name}[`)) {
2295
+ clearedErrors[key] = null;
2296
+ }
2146
2297
  }
2147
2298
  }
2148
2299
  draftValuesRef.current = newValues;
@@ -2211,7 +2362,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2211
2362
  replaceErrors(errors);
2212
2363
  return;
2213
2364
  }
2214
- const asyncSubmitValidations = allVisibleFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
2365
+ const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
2215
2366
  if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
2216
2367
  const pendingValidations = [
2217
2368
  .../* @__PURE__ */ new Set([
@@ -2226,6 +2377,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2226
2377
  const reset = () => {
2227
2378
  const fresh = computeInitialValues();
2228
2379
  if (values == null) setInternalValues(fresh);
2380
+ controlledBaselineLockedRef.current = false;
2229
2381
  replaceErrors({});
2230
2382
  initialSnapshot.current = deepClone(fresh);
2231
2383
  prevAutoSaveValues.current = deepClone(fresh);
@@ -2233,7 +2385,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2233
2385
  const rawValues = {};
2234
2386
  for (const key of Object.keys(formValues)) {
2235
2387
  const f = fieldByName.get(key);
2236
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
2388
+ if (f && (f.type === "display" || f.type === "slot" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
2237
2389
  rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
2238
2390
  }
2239
2391
  for (const f of fields) {
@@ -2265,7 +2417,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2265
2417
  if (controlledLoading == null) setInternalLoading(false);
2266
2418
  }
2267
2419
  },
2268
- [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, runAsyncValidation]
2420
+ [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
2269
2421
  );
2270
2422
  const handleNext = (0, import_react2.useCallback)(async () => {
2271
2423
  if (!isMultiStep) return;
@@ -2277,7 +2429,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2277
2429
  replaceErrors({ ...formErrorsRef.current, ...errors });
2278
2430
  return;
2279
2431
  }
2280
- const asyncStepValidations = stepFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
2432
+ const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
2281
2433
  if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
2282
2434
  const pendingValidations = [
2283
2435
  .../* @__PURE__ */ new Set([
@@ -2302,7 +2454,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2302
2454
  } else {
2303
2455
  setInternalStep(nextStep);
2304
2456
  }
2305
- }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, runAsyncValidation]);
2457
+ }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
2306
2458
  const handleBack = (0, import_react2.useCallback)(() => {
2307
2459
  if (!isMultiStep) return;
2308
2460
  const prevStep = Math.max(currentStep - 1, 0);
@@ -2334,6 +2486,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2334
2486
  reset: () => {
2335
2487
  const fresh = computeInitialValues();
2336
2488
  if (values == null) setInternalValues(fresh);
2489
+ controlledBaselineLockedRef.current = false;
2337
2490
  replaceErrors({});
2338
2491
  initialSnapshot.current = deepClone(fresh);
2339
2492
  prevAutoSaveValues.current = deepClone(fresh);
@@ -2346,30 +2499,6 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2346
2499
  replaceErrors(errors);
2347
2500
  }
2348
2501
  }));
2349
- const setRepeaterSubFieldError = (0, import_react2.useCallback)(
2350
- (fieldName, rowIdx, subFieldName, errorMessage) => {
2351
- const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
2352
- const merged = { ...formErrorsRef.current };
2353
- if (errorMessage) {
2354
- merged[key] = errorMessage;
2355
- } else {
2356
- delete merged[key];
2357
- }
2358
- const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
2359
- const match = k.match(/\[(\d+)\]\./);
2360
- const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
2361
- return { key: k, row };
2362
- }).sort((a, b) => a.row - b.row);
2363
- if (subErrors.length > 0) {
2364
- const first = subErrors[0];
2365
- merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
2366
- } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
2367
- delete merged[fieldName];
2368
- }
2369
- replaceErrors(merged);
2370
- },
2371
- [replaceErrors]
2372
- );
2373
2502
  const renderField = (field) => {
2374
2503
  const fieldError = formErrors[field.name] || null;
2375
2504
  const rendered = renderFieldInner(field);
@@ -2384,7 +2513,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2384
2513
  const isReadOnly = field.readOnly || formReadOnly;
2385
2514
  const isDisabled = disabled || resolveDisabled(field, formValues) || formReadOnly;
2386
2515
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
2387
- if (field.type === "display") {
2516
+ if (field.type === "display" || field.type === "slot") {
2388
2517
  if (field.render) {
2389
2518
  return field.render({
2390
2519
  values: formValues,
@@ -2849,12 +2978,13 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2849
2978
  const rowValues = { ...formValues, [field.name]: nextRows };
2850
2979
  const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
2851
2980
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
2981
+ return err;
2852
2982
  };
2853
2983
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
2854
2984
  const updated = rows.map(
2855
2985
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
2856
2986
  );
2857
- handleFieldChange(field.name, updated);
2987
+ handleFieldChange(field.name, updated, { clearNestedErrors: false });
2858
2988
  if (validateOnChange) {
2859
2989
  validateSubField(rowIdx, subField, subValue, updated);
2860
2990
  }
@@ -2864,13 +2994,24 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2864
2994
  const nextRows = rows.map(
2865
2995
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
2866
2996
  );
2867
- validateSubField(rowIdx, subField, subValue, nextRows);
2997
+ const err = validateSubField(rowIdx, subField, subValue, nextRows);
2998
+ if (err) return;
2999
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, subField.name);
3000
+ const rowValues = { ...formValues, [field.name]: nextRows };
3001
+ triggerAsyncValidationTarget({
3002
+ validationKey,
3003
+ field: subField,
3004
+ value: subValue,
3005
+ allValues: rowValues,
3006
+ applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
3007
+ });
2868
3008
  };
2869
3009
  return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: getRowKey(field.name, row, rowIdx), direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
2870
3010
  const sfValue = row[sf.name];
2871
3011
  const sfLabel = rowIdx === 0 ? sf.label : void 0;
2872
3012
  const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
2873
3013
  const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
3014
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
2874
3015
  const sfProps = {
2875
3016
  name: `${field.name}-${rowIdx}-${sf.name}`,
2876
3017
  label: sfLabel,
@@ -2879,6 +3020,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2879
3020
  disabled: resolveDisabled(sf, formValues) || isDisabled,
2880
3021
  error: !!sfError,
2881
3022
  validationMessage: sfError || void 0,
3023
+ ...validatingFields[validationKey] ? { loading: true } : {},
2882
3024
  ...sf.fieldProps || {}
2883
3025
  };
2884
3026
  let sfElement;
@@ -2956,7 +3098,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2956
3098
  const getFieldColSpan = (field) => {
2957
3099
  if (field.colSpan != null) return Math.min(field.colSpan, columns);
2958
3100
  if (field.width === "full" && columns > 1) return columns;
2959
- if (columns > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
3101
+ if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
2960
3102
  return 1;
2961
3103
  };
2962
3104
  const getDependents = (parentField) => visibleFields.filter(
@@ -2981,7 +3123,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2981
3123
  const colSpan = (field) => {
2982
3124
  if (field.colSpan != null) return Math.min(field.colSpan, cols);
2983
3125
  if (field.width === "full" && cols > 1) return cols;
2984
- if (cols > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
3126
+ if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
2985
3127
  return 1;
2986
3128
  };
2987
3129
  const flushRow = () => {
@@ -3145,13 +3287,23 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
3145
3287
  const elements = [];
3146
3288
  for (let i = 0; i < chunks.length; i++) {
3147
3289
  const chunk = chunks[i];
3148
- if (i > 0) {
3290
+ const opts = chunk.group && groups && groups[chunk.group] || {};
3291
+ const showDivider = opts.showDivider !== false;
3292
+ const showLabel = opts.showLabel !== false;
3293
+ if (i > 0 && showDivider) {
3149
3294
  elements.push(/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, { key: `group-div-${i}` }));
3150
3295
  }
3151
- if (chunk.group) {
3152
- elements.push(
3153
- /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, chunk.group)
3154
- );
3296
+ if (chunk.group && showLabel) {
3297
+ if (typeof opts.renderHeader === "function") {
3298
+ const header = opts.renderHeader(chunk.group, chunk.fields, formValues);
3299
+ if (header) elements.push(
3300
+ /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: `group-header-${i}` }, header)
3301
+ );
3302
+ } else {
3303
+ elements.push(
3304
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
3305
+ );
3306
+ }
3155
3307
  }
3156
3308
  const chunkElements = renderFn(chunk.fields);
3157
3309
  if (Array.isArray(chunkElements)) {