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.mjs CHANGED
@@ -502,28 +502,33 @@ var DataTable = ({
502
502
  return next;
503
503
  });
504
504
  }, []);
505
- const flatRows = useMemo(() => {
506
- if (!groupedData) return (serverSide ? data : sortedData).map((row) => ({ type: "data", row }));
507
- const flat = [];
508
- groupedData.forEach((group) => {
509
- flat.push({ type: "group-header", group });
510
- if (expandedGroups.has(group.key)) {
511
- group.rows.forEach((row) => flat.push({ type: "data", row }));
512
- }
513
- });
514
- return flat;
515
- }, [groupedData, sortedData, data, serverSide, expandedGroups]);
516
- const totalItems = serverSide ? totalCount || data.length : flatRows.length;
505
+ const datasetRows = useMemo(() => {
506
+ if (!groupedData) return serverSide ? data : sortedData;
507
+ return groupedData.flatMap((group) => group.rows);
508
+ }, [groupedData, sortedData, data, serverSide]);
509
+ const totalItems = serverSide ? totalCount || data.length : datasetRows.length;
517
510
  const pageCount = Math.ceil(totalItems / pageSize);
518
- let displayRows;
519
- if (serverSide) {
520
- displayRows = groupBy ? flatRows : data.map((row) => ({ type: "data", row }));
521
- } else {
522
- displayRows = flatRows.slice(
511
+ const paginatedRows = useMemo(() => {
512
+ if (serverSide) return datasetRows;
513
+ return datasetRows.slice(
523
514
  (activePage - 1) * pageSize,
524
515
  activePage * pageSize
525
516
  );
526
- }
517
+ }, [serverSide, datasetRows, activePage, pageSize]);
518
+ const displayRows = useMemo(() => {
519
+ if (!groupedData) return paginatedRows.map((row) => ({ type: "data", row }));
520
+ const pageRows = new Set(paginatedRows);
521
+ const rows = [];
522
+ groupedData.forEach((group) => {
523
+ const groupPageRows = group.rows.filter((row) => pageRows.has(row));
524
+ if (groupPageRows.length === 0) return;
525
+ rows.push({ type: "group-header", group });
526
+ if (expandedGroups.has(group.key)) {
527
+ groupPageRows.forEach((row) => rows.push({ type: "data", row }));
528
+ }
529
+ });
530
+ return rows;
531
+ }, [groupedData, paginatedRows, expandedGroups]);
527
532
  const footerData = serverSide ? data : filteredData;
528
533
  const activeChips = useMemo(() => {
529
534
  const chips = [];
@@ -634,8 +639,8 @@ var DataTable = ({
634
639
  return displayRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null);
635
640
  }, [serverSide, data, displayRows, rowIdField]);
636
641
  const allRowIds = useMemo(
637
- () => flatRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null),
638
- [flatRows, rowIdField]
642
+ () => datasetRows.map((row) => row[rowIdField]).filter((id) => id != null),
643
+ [datasetRows, rowIdField]
639
644
  );
640
645
  const handleSelectRow = useCallback((rowId, checked) => {
641
646
  const next = new Set(selectedIds);
@@ -682,19 +687,25 @@ var DataTable = ({
682
687
  if (row) onEditStart(row, field, currentValue);
683
688
  }
684
689
  }, [onEditStart, data, rowIdField]);
685
- const commitEdit = useCallback((row, field, value) => {
690
+ const commitEdit = useCallback((row, field, value, options = {}) => {
691
+ const { keepEditing = false } = options;
686
692
  const col = columns.find((c) => c.field === field);
687
693
  if (col == null ? void 0 : col.editValidate) {
688
694
  const result = col.editValidate(value, row);
689
695
  if (result !== true && result !== void 0 && result !== null) {
690
696
  setEditError(typeof result === "string" ? result : "Invalid value");
691
- return;
697
+ return false;
692
698
  }
693
699
  }
694
700
  if (onRowEdit) onRowEdit(row, field, value);
695
- setEditingCell(null);
696
- setEditValue(null);
701
+ if (!keepEditing) {
702
+ setEditingCell(null);
703
+ setEditValue(null);
704
+ } else {
705
+ setEditValue(value);
706
+ }
697
707
  setEditError(null);
708
+ return true;
698
709
  }, [onRowEdit, columns]);
699
710
  const renderEditControl = (col, row) => {
700
711
  const type = col.editType || "text";
@@ -753,12 +764,12 @@ var DataTable = ({
753
764
  case "datetime":
754
765
  return /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React.createElement(DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
755
766
  const next = { ...editValue, date: val };
756
- setEditValue(next);
757
- if (onRowEdit) onRowEdit(row, col.field, next);
767
+ handleInput(next);
768
+ commitEdit(row, col.field, next, { keepEditing: true });
758
769
  }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ React.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
759
770
  const next = { ...editValue, time: val };
760
- setEditValue(next);
761
- if (onRowEdit) onRowEdit(row, col.field, next);
771
+ handleInput(next);
772
+ commitEdit(row, col.field, next, { keepEditing: true });
762
773
  }, onBlur: maybeExitDatetimeEdit }));
763
774
  case "toggle":
764
775
  return /* @__PURE__ */ React.createElement(Toggle, { ...extra, name: fieldName, label: "", checked: !!editValue, onChange: commit });
@@ -1159,6 +1170,7 @@ var getEmptyValue = (field) => {
1159
1170
  case "datetime":
1160
1171
  return void 0;
1161
1172
  case "display":
1173
+ case "slot":
1162
1174
  case "crmPropertyList":
1163
1175
  case "crmAssociationPropertyList":
1164
1176
  return void 0;
@@ -1302,7 +1314,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
1302
1314
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1303
1315
  const includeCustomValidators = options.includeCustomValidators !== false;
1304
1316
  const msg = options.messages || {};
1305
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
1317
+ if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
1306
1318
  const isRequired = resolveRequired(field, allValues);
1307
1319
  const plugin = fieldTypes && fieldTypes[field.type];
1308
1320
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -1525,6 +1537,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1525
1537
  // explicit row layout array (overrides columns + columnWidth)
1526
1538
  sections,
1527
1539
  // FormBuilderSection[] — accordion field grouping
1540
+ groups,
1541
+ // Record<string, FormBuilderGroupOptions> — per-group rendering options keyed by group name
1528
1542
  gap = "sm",
1529
1543
  // gap between fields
1530
1544
  showRequiredIndicator = true,
@@ -1734,7 +1748,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1734
1748
  const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
1735
1749
  const vals = {};
1736
1750
  for (const field of fields) {
1737
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
1751
+ if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
1738
1752
  if (field.type === "fieldGroup" && field.items && field.fields) {
1739
1753
  for (const item of field.items) {
1740
1754
  const subFields = field.fields(item);
@@ -1768,9 +1782,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1768
1782
  const inputDebounceRef = useRef2(/* @__PURE__ */ new Map());
1769
1783
  const rowKeyRef = useRef2(/* @__PURE__ */ new WeakMap());
1770
1784
  const rowKeyCounterRef = useRef2(0);
1785
+ const controlledBaselineLockedRef = useRef2(false);
1771
1786
  const initialSnapshot = useRef2(null);
1772
1787
  if (initialSnapshot.current === null) {
1773
- initialSnapshot.current = deepClone(computeInitialValues());
1788
+ initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
1774
1789
  }
1775
1790
  const formValues = values != null ? values : internalValues;
1776
1791
  const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
@@ -1782,6 +1797,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1782
1797
  const draftValuesRef = useRef2(null);
1783
1798
  formValuesRef.current = formValues;
1784
1799
  formErrorsRef.current = formErrors;
1800
+ const syncDirtyBaseline = useCallback2((nextValues) => {
1801
+ initialSnapshot.current = deepClone(nextValues || {});
1802
+ prevAutoSaveValues.current = deepClone(nextValues || {});
1803
+ }, []);
1785
1804
  const fieldByName = useMemo2(() => {
1786
1805
  const map = /* @__PURE__ */ new Map();
1787
1806
  for (const field of fields) {
@@ -1858,6 +1877,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1858
1877
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
1859
1878
  };
1860
1879
  }, []);
1880
+ useEffect2(() => {
1881
+ if (values == null) return;
1882
+ if (controlledBaselineLockedRef.current) return;
1883
+ syncDirtyBaseline(values);
1884
+ }, [values, syncDirtyBaseline]);
1861
1885
  const isDirty = useMemo2(() => {
1862
1886
  return !deepEqual(formValues, initialSnapshot.current);
1863
1887
  }, [formValues]);
@@ -1976,6 +2000,50 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1976
2000
  },
1977
2001
  [fieldTypes]
1978
2002
  );
2003
+ const setRepeaterSubFieldError = useCallback2(
2004
+ (fieldName, rowIdx, subFieldName, errorMessage) => {
2005
+ const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
2006
+ const merged = { ...formErrorsRef.current };
2007
+ if (errorMessage) {
2008
+ merged[key] = errorMessage;
2009
+ } else {
2010
+ delete merged[key];
2011
+ }
2012
+ const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
2013
+ const match = k.match(/\[(\d+)\]\./);
2014
+ const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
2015
+ return { key: k, row };
2016
+ }).sort((a, b) => a.row - b.row);
2017
+ if (subErrors.length > 0) {
2018
+ const first = subErrors[0];
2019
+ merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
2020
+ } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
2021
+ delete merged[fieldName];
2022
+ }
2023
+ replaceErrors(merged);
2024
+ },
2025
+ [replaceErrors]
2026
+ );
2027
+ const expandValidationFields = useCallback2(
2028
+ (fieldSubset) => {
2029
+ const toValidate = fieldSubset || visibleFields;
2030
+ const expanded = [];
2031
+ for (const field of toValidate) {
2032
+ if (field.type === "fieldGroup" && field.items && field.fields) {
2033
+ for (const item of field.items) {
2034
+ for (const subField of field.fields(item)) {
2035
+ if (subField.visible && !subField.visible(formValues)) continue;
2036
+ expanded.push(subField);
2037
+ }
2038
+ }
2039
+ continue;
2040
+ }
2041
+ expanded.push(field);
2042
+ }
2043
+ return expanded;
2044
+ },
2045
+ [visibleFields, formValues]
2046
+ );
1979
2047
  const validateField = useCallback2(
1980
2048
  (name, value) => {
1981
2049
  const field = fieldByName.get(name);
@@ -1995,7 +2063,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1995
2063
  );
1996
2064
  const validateVisibleFields = useCallback2(
1997
2065
  (fieldSubset) => {
1998
- const toValidate = fieldSubset || visibleFields;
2066
+ const toValidate = expandValidationFields(fieldSubset);
1999
2067
  const errors = {};
2000
2068
  let hasErrors = false;
2001
2069
  for (const field of toValidate) {
@@ -2015,52 +2083,54 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2015
2083
  }
2016
2084
  return { errors, hasErrors };
2017
2085
  },
2018
- [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
2086
+ [expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
2019
2087
  );
2020
- const runAsyncValidation = useCallback2(
2021
- (name, value) => {
2022
- const field = fieldByName.get(name);
2023
- if (!field || field.type === "repeater") return null;
2024
- const val = value != null ? value : formValues[name];
2025
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
2026
- const prevController = asyncAbortRef.current.get(name);
2088
+ const runAsyncValidationTarget = useCallback2(
2089
+ (target) => {
2090
+ const { validationKey, field, value, allValues, applyError } = target || {};
2091
+ if (!field || !validationKey || field.type === "repeater" || field.type === "fieldGroup") return null;
2092
+ const syncError = runValidators(value, field, allValues, fieldTypes, {
2093
+ includeCustomValidators: false,
2094
+ messages: validationMessages
2095
+ });
2096
+ const prevController = asyncAbortRef.current.get(validationKey);
2027
2097
  if (prevController) prevController.abort();
2028
- asyncAbortRef.current.delete(name);
2098
+ asyncAbortRef.current.delete(validationKey);
2029
2099
  setValidatingFields((prev) => {
2030
- if (!prev[name]) return prev;
2100
+ if (!prev[validationKey]) return prev;
2031
2101
  const next = { ...prev };
2032
- delete next[name];
2102
+ delete next[validationKey];
2033
2103
  return next;
2034
2104
  });
2035
2105
  if (syncError) return null;
2036
- const version = (asyncValidationVersionRef.current.get(name) || 0) + 1;
2037
- asyncValidationVersionRef.current.set(name, version);
2106
+ const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
2107
+ asyncValidationVersionRef.current.set(validationKey, version);
2038
2108
  const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
2039
- if (controller) asyncAbortRef.current.set(name, controller);
2109
+ if (controller) asyncAbortRef.current.set(validationKey, controller);
2040
2110
  let asyncPromises;
2041
2111
  try {
2042
2112
  asyncPromises = collectAsyncValidatorPromises(
2043
- val,
2113
+ value,
2044
2114
  field,
2045
- formValues,
2115
+ allValues,
2046
2116
  controller ? { signal: controller.signal } : void 0
2047
2117
  );
2048
2118
  } catch (err) {
2049
- updateErrors({ [name]: (err == null ? void 0 : err.message) || "Validation failed" });
2119
+ applyError((err == null ? void 0 : err.message) || "Validation failed");
2050
2120
  return null;
2051
2121
  }
2052
2122
  if (asyncPromises.length === 0) {
2053
- asyncAbortRef.current.delete(name);
2123
+ asyncAbortRef.current.delete(validationKey);
2054
2124
  return null;
2055
2125
  }
2056
2126
  const validationPromise = Promise.all(asyncPromises).then(
2057
2127
  (results) => {
2058
- if (asyncValidationVersionRef.current.get(name) !== version) return;
2059
- asyncValidationRef.current.delete(name);
2060
- asyncAbortRef.current.delete(name);
2128
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
2129
+ asyncValidationRef.current.delete(validationKey);
2130
+ asyncAbortRef.current.delete(validationKey);
2061
2131
  setValidatingFields((prev) => {
2062
2132
  const next = { ...prev };
2063
- delete next[name];
2133
+ delete next[validationKey];
2064
2134
  return next;
2065
2135
  });
2066
2136
  let err = null;
@@ -2071,50 +2141,128 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2071
2141
  break;
2072
2142
  }
2073
2143
  }
2074
- updateErrors({ [name]: err });
2144
+ applyError(err);
2075
2145
  },
2076
2146
  (rejection) => {
2077
- if (asyncValidationVersionRef.current.get(name) !== version) return;
2078
- asyncValidationRef.current.delete(name);
2079
- asyncAbortRef.current.delete(name);
2147
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
2148
+ asyncValidationRef.current.delete(validationKey);
2149
+ asyncAbortRef.current.delete(validationKey);
2080
2150
  setValidatingFields((prev) => {
2081
2151
  const next = { ...prev };
2082
- delete next[name];
2152
+ delete next[validationKey];
2083
2153
  return next;
2084
2154
  });
2085
2155
  if (rejection && rejection.name === "AbortError") return;
2086
- updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
2156
+ applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
2087
2157
  }
2088
2158
  );
2089
- asyncValidationRef.current.set(name, validationPromise);
2090
- setValidatingFields((prev) => ({ ...prev, [name]: true }));
2159
+ asyncValidationRef.current.set(validationKey, validationPromise);
2160
+ setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
2091
2161
  return validationPromise;
2092
2162
  },
2093
- [fieldByName, formValues, fieldTypes, updateErrors]
2163
+ [fieldTypes, validationMessages]
2094
2164
  );
2095
- const triggerAsyncValidation = useCallback2(
2165
+ const runAsyncValidation = useCallback2(
2096
2166
  (name, value) => {
2097
2167
  const field = fieldByName.get(name);
2098
- if (!field || field.type === "repeater") return;
2099
- const debounceMs = field.validateDebounce;
2168
+ if (!field || field.type === "repeater" || field.type === "fieldGroup") return null;
2169
+ return runAsyncValidationTarget({
2170
+ validationKey: name,
2171
+ field,
2172
+ value: value != null ? value : formValues[name],
2173
+ allValues: formValues,
2174
+ applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
2175
+ });
2176
+ },
2177
+ [fieldByName, formValues, runAsyncValidationTarget, updateErrors]
2178
+ );
2179
+ const triggerAsyncValidationTarget = useCallback2(
2180
+ (target) => {
2181
+ if (!(target == null ? void 0 : target.field) || !target.validationKey) return;
2182
+ const debounceMs = target.field.validateDebounce;
2100
2183
  if (debounceMs && debounceMs > 0) {
2101
- const existing = debounceTimersRef.current.get(name);
2184
+ const existing = debounceTimersRef.current.get(target.validationKey);
2102
2185
  if (existing) clearTimeout(existing);
2103
2186
  const timer = setTimeout(() => {
2104
- debounceTimersRef.current.delete(name);
2105
- runAsyncValidation(name, value);
2187
+ debounceTimersRef.current.delete(target.validationKey);
2188
+ runAsyncValidationTarget(target);
2106
2189
  }, debounceMs);
2107
- debounceTimersRef.current.set(name, timer);
2190
+ debounceTimersRef.current.set(target.validationKey, timer);
2108
2191
  } else {
2109
- runAsyncValidation(name, value);
2192
+ runAsyncValidationTarget(target);
2110
2193
  }
2111
2194
  },
2112
- [fieldByName, runAsyncValidation]
2195
+ [runAsyncValidationTarget]
2196
+ );
2197
+ const triggerAsyncValidation = useCallback2(
2198
+ (name, value) => {
2199
+ const field = fieldByName.get(name);
2200
+ if (!field || field.type === "repeater" || field.type === "fieldGroup") return;
2201
+ triggerAsyncValidationTarget({
2202
+ validationKey: name,
2203
+ field,
2204
+ value: value != null ? value : formValuesRef.current[name],
2205
+ allValues: formValuesRef.current,
2206
+ applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
2207
+ });
2208
+ },
2209
+ [fieldByName, triggerAsyncValidationTarget, updateErrors]
2210
+ );
2211
+ const getAsyncValidationTargets = useCallback2(
2212
+ (fieldSubset) => {
2213
+ const toValidate = fieldSubset || visibleFields;
2214
+ const targets = [];
2215
+ for (const field of toValidate) {
2216
+ if (field.type === "fieldGroup" && field.items && field.fields) {
2217
+ for (const item of field.items) {
2218
+ for (const subField of field.fields(item)) {
2219
+ if (subField.visible && !subField.visible(formValues)) continue;
2220
+ targets.push({
2221
+ validationKey: subField.name,
2222
+ field: subField,
2223
+ value: formValues[subField.name],
2224
+ allValues: formValues,
2225
+ applyError: (errorMessage) => updateErrors({ [subField.name]: errorMessage })
2226
+ });
2227
+ }
2228
+ }
2229
+ continue;
2230
+ }
2231
+ if (field.type === "repeater") {
2232
+ const rows = Array.isArray(formValues[field.name]) ? formValues[field.name] : [];
2233
+ const subFields = field.fields || [];
2234
+ rows.forEach((row, rowIdx) => {
2235
+ const rowValues = { ...formValues, [field.name]: rows };
2236
+ subFields.forEach((subField) => {
2237
+ if (subField.visible && !subField.visible(rowValues)) return;
2238
+ targets.push({
2239
+ validationKey: getRepeaterErrorKey(field.name, rowIdx, subField.name),
2240
+ field: subField,
2241
+ value: row == null ? void 0 : row[subField.name],
2242
+ allValues: rowValues,
2243
+ applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
2244
+ });
2245
+ });
2246
+ });
2247
+ continue;
2248
+ }
2249
+ targets.push({
2250
+ validationKey: field.name,
2251
+ field,
2252
+ value: formValues[field.name],
2253
+ allValues: formValues,
2254
+ applyError: (errorMessage) => updateErrors({ [field.name]: errorMessage })
2255
+ });
2256
+ }
2257
+ return targets;
2258
+ },
2259
+ [visibleFields, formValues, setRepeaterSubFieldError, updateErrors]
2113
2260
  );
2114
2261
  const commitValues = useCallback2(
2115
2262
  (nextValues) => {
2116
2263
  formValuesRef.current = nextValues;
2117
2264
  if (values != null) {
2265
+ controlledBaselineLockedRef.current = true;
2118
2266
  if (onChange) onChange(nextValues);
2119
2267
  } else {
2120
2268
  setInternalValues(nextValues);
@@ -2132,7 +2280,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2132
2280
  [commitValues]
2133
2281
  );
2134
2282
  const handleFieldChange = useCallback2(
2135
- (name, value) => {
2283
+ (name, value, options = {}) => {
2284
+ const { clearNestedErrors = true } = options;
2136
2285
  const newValues = { ...formValuesRef.current, [name]: value };
2137
2286
  const queue = [name];
2138
2287
  const visited = /* @__PURE__ */ new Set();
@@ -2173,9 +2322,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2173
2322
  if (formErrorsRef.current[name] != null) {
2174
2323
  clearedErrors[name] = null;
2175
2324
  }
2176
- for (const key of Object.keys(formErrorsRef.current)) {
2177
- if (key.startsWith(`${name}[`)) {
2178
- clearedErrors[key] = null;
2325
+ if (clearNestedErrors) {
2326
+ for (const key of Object.keys(formErrorsRef.current)) {
2327
+ if (key.startsWith(`${name}[`)) {
2328
+ clearedErrors[key] = null;
2329
+ }
2179
2330
  }
2180
2331
  }
2181
2332
  draftValuesRef.current = newValues;
@@ -2244,7 +2395,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2244
2395
  replaceErrors(errors);
2245
2396
  return;
2246
2397
  }
2247
- const asyncSubmitValidations = allVisibleFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
2398
+ const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
2248
2399
  if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
2249
2400
  const pendingValidations = [
2250
2401
  .../* @__PURE__ */ new Set([
@@ -2259,6 +2410,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2259
2410
  const reset = () => {
2260
2411
  const fresh = computeInitialValues();
2261
2412
  if (values == null) setInternalValues(fresh);
2413
+ controlledBaselineLockedRef.current = false;
2262
2414
  replaceErrors({});
2263
2415
  initialSnapshot.current = deepClone(fresh);
2264
2416
  prevAutoSaveValues.current = deepClone(fresh);
@@ -2266,7 +2418,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2266
2418
  const rawValues = {};
2267
2419
  for (const key of Object.keys(formValues)) {
2268
2420
  const f = fieldByName.get(key);
2269
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
2421
+ if (f && (f.type === "display" || f.type === "slot" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
2270
2422
  rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
2271
2423
  }
2272
2424
  for (const f of fields) {
@@ -2298,7 +2450,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2298
2450
  if (controlledLoading == null) setInternalLoading(false);
2299
2451
  }
2300
2452
  },
2301
- [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, runAsyncValidation]
2453
+ [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
2302
2454
  );
2303
2455
  const handleNext = useCallback2(async () => {
2304
2456
  if (!isMultiStep) return;
@@ -2310,7 +2462,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2310
2462
  replaceErrors({ ...formErrorsRef.current, ...errors });
2311
2463
  return;
2312
2464
  }
2313
- const asyncStepValidations = stepFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
2465
+ const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
2314
2466
  if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
2315
2467
  const pendingValidations = [
2316
2468
  .../* @__PURE__ */ new Set([
@@ -2335,7 +2487,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2335
2487
  } else {
2336
2488
  setInternalStep(nextStep);
2337
2489
  }
2338
- }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, runAsyncValidation]);
2490
+ }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
2339
2491
  const handleBack = useCallback2(() => {
2340
2492
  if (!isMultiStep) return;
2341
2493
  const prevStep = Math.max(currentStep - 1, 0);
@@ -2367,6 +2519,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2367
2519
  reset: () => {
2368
2520
  const fresh = computeInitialValues();
2369
2521
  if (values == null) setInternalValues(fresh);
2522
+ controlledBaselineLockedRef.current = false;
2370
2523
  replaceErrors({});
2371
2524
  initialSnapshot.current = deepClone(fresh);
2372
2525
  prevAutoSaveValues.current = deepClone(fresh);
@@ -2379,30 +2532,6 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2379
2532
  replaceErrors(errors);
2380
2533
  }
2381
2534
  }));
2382
- const setRepeaterSubFieldError = useCallback2(
2383
- (fieldName, rowIdx, subFieldName, errorMessage) => {
2384
- const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
2385
- const merged = { ...formErrorsRef.current };
2386
- if (errorMessage) {
2387
- merged[key] = errorMessage;
2388
- } else {
2389
- delete merged[key];
2390
- }
2391
- const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
2392
- const match = k.match(/\[(\d+)\]\./);
2393
- const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
2394
- return { key: k, row };
2395
- }).sort((a, b) => a.row - b.row);
2396
- if (subErrors.length > 0) {
2397
- const first = subErrors[0];
2398
- merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
2399
- } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
2400
- delete merged[fieldName];
2401
- }
2402
- replaceErrors(merged);
2403
- },
2404
- [replaceErrors]
2405
- );
2406
2535
  const renderField = (field) => {
2407
2536
  const fieldError = formErrors[field.name] || null;
2408
2537
  const rendered = renderFieldInner(field);
@@ -2417,7 +2546,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2417
2546
  const isReadOnly = field.readOnly || formReadOnly;
2418
2547
  const isDisabled = disabled || resolveDisabled(field, formValues) || formReadOnly;
2419
2548
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
2420
- if (field.type === "display") {
2549
+ if (field.type === "display" || field.type === "slot") {
2421
2550
  if (field.render) {
2422
2551
  return field.render({
2423
2552
  values: formValues,
@@ -2882,12 +3011,13 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2882
3011
  const rowValues = { ...formValues, [field.name]: nextRows };
2883
3012
  const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
2884
3013
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
3014
+ return err;
2885
3015
  };
2886
3016
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
2887
3017
  const updated = rows.map(
2888
3018
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
2889
3019
  );
2890
- handleFieldChange(field.name, updated);
3020
+ handleFieldChange(field.name, updated, { clearNestedErrors: false });
2891
3021
  if (validateOnChange) {
2892
3022
  validateSubField(rowIdx, subField, subValue, updated);
2893
3023
  }
@@ -2897,13 +3027,24 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2897
3027
  const nextRows = rows.map(
2898
3028
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
2899
3029
  );
2900
- validateSubField(rowIdx, subField, subValue, nextRows);
3030
+ const err = validateSubField(rowIdx, subField, subValue, nextRows);
3031
+ if (err) return;
3032
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, subField.name);
3033
+ const rowValues = { ...formValues, [field.name]: nextRows };
3034
+ triggerAsyncValidationTarget({
3035
+ validationKey,
3036
+ field: subField,
3037
+ value: subValue,
3038
+ allValues: rowValues,
3039
+ applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
3040
+ });
2901
3041
  };
2902
3042
  return /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ React2.createElement(Text2, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ React2.createElement(Text2, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ React2.createElement(Flex2, { key: getRowKey(field.name, row, rowIdx), direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
2903
3043
  const sfValue = row[sf.name];
2904
3044
  const sfLabel = rowIdx === 0 ? sf.label : void 0;
2905
3045
  const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
2906
3046
  const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
3047
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
2907
3048
  const sfProps = {
2908
3049
  name: `${field.name}-${rowIdx}-${sf.name}`,
2909
3050
  label: sfLabel,
@@ -2912,6 +3053,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2912
3053
  disabled: resolveDisabled(sf, formValues) || isDisabled,
2913
3054
  error: !!sfError,
2914
3055
  validationMessage: sfError || void 0,
3056
+ ...validatingFields[validationKey] ? { loading: true } : {},
2915
3057
  ...sf.fieldProps || {}
2916
3058
  };
2917
3059
  let sfElement;
@@ -2989,7 +3131,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2989
3131
  const getFieldColSpan = (field) => {
2990
3132
  if (field.colSpan != null) return Math.min(field.colSpan, columns);
2991
3133
  if (field.width === "full" && columns > 1) return columns;
2992
- if (columns > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
3134
+ if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
2993
3135
  return 1;
2994
3136
  };
2995
3137
  const getDependents = (parentField) => visibleFields.filter(
@@ -3014,7 +3156,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
3014
3156
  const colSpan = (field) => {
3015
3157
  if (field.colSpan != null) return Math.min(field.colSpan, cols);
3016
3158
  if (field.width === "full" && cols > 1) return cols;
3017
- if (cols > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
3159
+ if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
3018
3160
  return 1;
3019
3161
  };
3020
3162
  const flushRow = () => {
@@ -3178,13 +3320,23 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
3178
3320
  const elements = [];
3179
3321
  for (let i = 0; i < chunks.length; i++) {
3180
3322
  const chunk = chunks[i];
3181
- if (i > 0) {
3323
+ const opts = chunk.group && groups && groups[chunk.group] || {};
3324
+ const showDivider = opts.showDivider !== false;
3325
+ const showLabel = opts.showLabel !== false;
3326
+ if (i > 0 && showDivider) {
3182
3327
  elements.push(/* @__PURE__ */ React2.createElement(Divider, { key: `group-div-${i}` }));
3183
3328
  }
3184
- if (chunk.group) {
3185
- elements.push(
3186
- /* @__PURE__ */ React2.createElement(Text2, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, chunk.group)
3187
- );
3329
+ if (chunk.group && showLabel) {
3330
+ if (typeof opts.renderHeader === "function") {
3331
+ const header = opts.renderHeader(chunk.group, chunk.fields, formValues);
3332
+ if (header) elements.push(
3333
+ /* @__PURE__ */ React2.createElement(React2.Fragment, { key: `group-header-${i}` }, header)
3334
+ );
3335
+ } else {
3336
+ elements.push(
3337
+ /* @__PURE__ */ React2.createElement(Text2, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
3338
+ );
3339
+ }
3188
3340
  }
3189
3341
  const chunkElements = renderFn(chunk.fields);
3190
3342
  if (Array.isArray(chunkElements)) {