hs-uix 1.4.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 });
@@ -1738,9 +1749,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1738
1749
  const inputDebounceRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1739
1750
  const rowKeyRef = (0, import_react2.useRef)(/* @__PURE__ */ new WeakMap());
1740
1751
  const rowKeyCounterRef = (0, import_react2.useRef)(0);
1752
+ const controlledBaselineLockedRef = (0, import_react2.useRef)(false);
1741
1753
  const initialSnapshot = (0, import_react2.useRef)(null);
1742
1754
  if (initialSnapshot.current === null) {
1743
- initialSnapshot.current = deepClone(computeInitialValues());
1755
+ initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
1744
1756
  }
1745
1757
  const formValues = values != null ? values : internalValues;
1746
1758
  const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
@@ -1752,6 +1764,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1752
1764
  const draftValuesRef = (0, import_react2.useRef)(null);
1753
1765
  formValuesRef.current = formValues;
1754
1766
  formErrorsRef.current = formErrors;
1767
+ const syncDirtyBaseline = (0, import_react2.useCallback)((nextValues) => {
1768
+ initialSnapshot.current = deepClone(nextValues || {});
1769
+ prevAutoSaveValues.current = deepClone(nextValues || {});
1770
+ }, []);
1755
1771
  const fieldByName = (0, import_react2.useMemo)(() => {
1756
1772
  const map = /* @__PURE__ */ new Map();
1757
1773
  for (const field of fields) {
@@ -1828,6 +1844,11 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1828
1844
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
1829
1845
  };
1830
1846
  }, []);
1847
+ (0, import_react2.useEffect)(() => {
1848
+ if (values == null) return;
1849
+ if (controlledBaselineLockedRef.current) return;
1850
+ syncDirtyBaseline(values);
1851
+ }, [values, syncDirtyBaseline]);
1831
1852
  const isDirty = (0, import_react2.useMemo)(() => {
1832
1853
  return !deepEqual(formValues, initialSnapshot.current);
1833
1854
  }, [formValues]);
@@ -1946,6 +1967,50 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1946
1967
  },
1947
1968
  [fieldTypes]
1948
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
+ );
1949
2014
  const validateField = (0, import_react2.useCallback)(
1950
2015
  (name, value) => {
1951
2016
  const field = fieldByName.get(name);
@@ -1965,7 +2030,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1965
2030
  );
1966
2031
  const validateVisibleFields = (0, import_react2.useCallback)(
1967
2032
  (fieldSubset) => {
1968
- const toValidate = fieldSubset || visibleFields;
2033
+ const toValidate = expandValidationFields(fieldSubset);
1969
2034
  const errors = {};
1970
2035
  let hasErrors = false;
1971
2036
  for (const field of toValidate) {
@@ -1985,52 +2050,54 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1985
2050
  }
1986
2051
  return { errors, hasErrors };
1987
2052
  },
1988
- [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
2053
+ [expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
1989
2054
  );
1990
- const runAsyncValidation = (0, import_react2.useCallback)(
1991
- (name, value) => {
1992
- const field = fieldByName.get(name);
1993
- if (!field || field.type === "repeater") return null;
1994
- const val = value != null ? value : formValues[name];
1995
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
1996
- 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);
1997
2064
  if (prevController) prevController.abort();
1998
- asyncAbortRef.current.delete(name);
2065
+ asyncAbortRef.current.delete(validationKey);
1999
2066
  setValidatingFields((prev) => {
2000
- if (!prev[name]) return prev;
2067
+ if (!prev[validationKey]) return prev;
2001
2068
  const next = { ...prev };
2002
- delete next[name];
2069
+ delete next[validationKey];
2003
2070
  return next;
2004
2071
  });
2005
2072
  if (syncError) return null;
2006
- const version = (asyncValidationVersionRef.current.get(name) || 0) + 1;
2007
- asyncValidationVersionRef.current.set(name, version);
2073
+ const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
2074
+ asyncValidationVersionRef.current.set(validationKey, version);
2008
2075
  const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
2009
- if (controller) asyncAbortRef.current.set(name, controller);
2076
+ if (controller) asyncAbortRef.current.set(validationKey, controller);
2010
2077
  let asyncPromises;
2011
2078
  try {
2012
2079
  asyncPromises = collectAsyncValidatorPromises(
2013
- val,
2080
+ value,
2014
2081
  field,
2015
- formValues,
2082
+ allValues,
2016
2083
  controller ? { signal: controller.signal } : void 0
2017
2084
  );
2018
2085
  } catch (err) {
2019
- updateErrors({ [name]: (err == null ? void 0 : err.message) || "Validation failed" });
2086
+ applyError((err == null ? void 0 : err.message) || "Validation failed");
2020
2087
  return null;
2021
2088
  }
2022
2089
  if (asyncPromises.length === 0) {
2023
- asyncAbortRef.current.delete(name);
2090
+ asyncAbortRef.current.delete(validationKey);
2024
2091
  return null;
2025
2092
  }
2026
2093
  const validationPromise = Promise.all(asyncPromises).then(
2027
2094
  (results) => {
2028
- if (asyncValidationVersionRef.current.get(name) !== version) return;
2029
- asyncValidationRef.current.delete(name);
2030
- asyncAbortRef.current.delete(name);
2095
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
2096
+ asyncValidationRef.current.delete(validationKey);
2097
+ asyncAbortRef.current.delete(validationKey);
2031
2098
  setValidatingFields((prev) => {
2032
2099
  const next = { ...prev };
2033
- delete next[name];
2100
+ delete next[validationKey];
2034
2101
  return next;
2035
2102
  });
2036
2103
  let err = null;
@@ -2041,50 +2108,128 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2041
2108
  break;
2042
2109
  }
2043
2110
  }
2044
- updateErrors({ [name]: err });
2111
+ applyError(err);
2045
2112
  },
2046
2113
  (rejection) => {
2047
- if (asyncValidationVersionRef.current.get(name) !== version) return;
2048
- asyncValidationRef.current.delete(name);
2049
- asyncAbortRef.current.delete(name);
2114
+ if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
2115
+ asyncValidationRef.current.delete(validationKey);
2116
+ asyncAbortRef.current.delete(validationKey);
2050
2117
  setValidatingFields((prev) => {
2051
2118
  const next = { ...prev };
2052
- delete next[name];
2119
+ delete next[validationKey];
2053
2120
  return next;
2054
2121
  });
2055
2122
  if (rejection && rejection.name === "AbortError") return;
2056
- updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
2123
+ applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
2057
2124
  }
2058
2125
  );
2059
- asyncValidationRef.current.set(name, validationPromise);
2060
- setValidatingFields((prev) => ({ ...prev, [name]: true }));
2126
+ asyncValidationRef.current.set(validationKey, validationPromise);
2127
+ setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
2061
2128
  return validationPromise;
2062
2129
  },
2063
- [fieldByName, formValues, fieldTypes, updateErrors]
2130
+ [fieldTypes, validationMessages]
2064
2131
  );
2065
- const triggerAsyncValidation = (0, import_react2.useCallback)(
2132
+ const runAsyncValidation = (0, import_react2.useCallback)(
2066
2133
  (name, value) => {
2067
2134
  const field = fieldByName.get(name);
2068
- if (!field || field.type === "repeater") return;
2069
- 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;
2070
2150
  if (debounceMs && debounceMs > 0) {
2071
- const existing = debounceTimersRef.current.get(name);
2151
+ const existing = debounceTimersRef.current.get(target.validationKey);
2072
2152
  if (existing) clearTimeout(existing);
2073
2153
  const timer = setTimeout(() => {
2074
- debounceTimersRef.current.delete(name);
2075
- runAsyncValidation(name, value);
2154
+ debounceTimersRef.current.delete(target.validationKey);
2155
+ runAsyncValidationTarget(target);
2076
2156
  }, debounceMs);
2077
- debounceTimersRef.current.set(name, timer);
2157
+ debounceTimersRef.current.set(target.validationKey, timer);
2078
2158
  } else {
2079
- runAsyncValidation(name, value);
2159
+ runAsyncValidationTarget(target);
2080
2160
  }
2081
2161
  },
2082
- [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]
2083
2227
  );
2084
2228
  const commitValues = (0, import_react2.useCallback)(
2085
2229
  (nextValues) => {
2086
2230
  formValuesRef.current = nextValues;
2087
2231
  if (values != null) {
2232
+ controlledBaselineLockedRef.current = true;
2088
2233
  if (onChange) onChange(nextValues);
2089
2234
  } else {
2090
2235
  setInternalValues(nextValues);
@@ -2102,7 +2247,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2102
2247
  [commitValues]
2103
2248
  );
2104
2249
  const handleFieldChange = (0, import_react2.useCallback)(
2105
- (name, value) => {
2250
+ (name, value, options = {}) => {
2251
+ const { clearNestedErrors = true } = options;
2106
2252
  const newValues = { ...formValuesRef.current, [name]: value };
2107
2253
  const queue = [name];
2108
2254
  const visited = /* @__PURE__ */ new Set();
@@ -2143,9 +2289,11 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2143
2289
  if (formErrorsRef.current[name] != null) {
2144
2290
  clearedErrors[name] = null;
2145
2291
  }
2146
- for (const key of Object.keys(formErrorsRef.current)) {
2147
- if (key.startsWith(`${name}[`)) {
2148
- 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
+ }
2149
2297
  }
2150
2298
  }
2151
2299
  draftValuesRef.current = newValues;
@@ -2214,7 +2362,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2214
2362
  replaceErrors(errors);
2215
2363
  return;
2216
2364
  }
2217
- const asyncSubmitValidations = allVisibleFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
2365
+ const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
2218
2366
  if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
2219
2367
  const pendingValidations = [
2220
2368
  .../* @__PURE__ */ new Set([
@@ -2229,6 +2377,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2229
2377
  const reset = () => {
2230
2378
  const fresh = computeInitialValues();
2231
2379
  if (values == null) setInternalValues(fresh);
2380
+ controlledBaselineLockedRef.current = false;
2232
2381
  replaceErrors({});
2233
2382
  initialSnapshot.current = deepClone(fresh);
2234
2383
  prevAutoSaveValues.current = deepClone(fresh);
@@ -2268,7 +2417,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2268
2417
  if (controlledLoading == null) setInternalLoading(false);
2269
2418
  }
2270
2419
  },
2271
- [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]
2272
2421
  );
2273
2422
  const handleNext = (0, import_react2.useCallback)(async () => {
2274
2423
  if (!isMultiStep) return;
@@ -2280,7 +2429,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2280
2429
  replaceErrors({ ...formErrorsRef.current, ...errors });
2281
2430
  return;
2282
2431
  }
2283
- const asyncStepValidations = stepFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
2432
+ const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
2284
2433
  if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
2285
2434
  const pendingValidations = [
2286
2435
  .../* @__PURE__ */ new Set([
@@ -2305,7 +2454,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2305
2454
  } else {
2306
2455
  setInternalStep(nextStep);
2307
2456
  }
2308
- }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, runAsyncValidation]);
2457
+ }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
2309
2458
  const handleBack = (0, import_react2.useCallback)(() => {
2310
2459
  if (!isMultiStep) return;
2311
2460
  const prevStep = Math.max(currentStep - 1, 0);
@@ -2337,6 +2486,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2337
2486
  reset: () => {
2338
2487
  const fresh = computeInitialValues();
2339
2488
  if (values == null) setInternalValues(fresh);
2489
+ controlledBaselineLockedRef.current = false;
2340
2490
  replaceErrors({});
2341
2491
  initialSnapshot.current = deepClone(fresh);
2342
2492
  prevAutoSaveValues.current = deepClone(fresh);
@@ -2349,30 +2499,6 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2349
2499
  replaceErrors(errors);
2350
2500
  }
2351
2501
  }));
2352
- const setRepeaterSubFieldError = (0, import_react2.useCallback)(
2353
- (fieldName, rowIdx, subFieldName, errorMessage) => {
2354
- const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
2355
- const merged = { ...formErrorsRef.current };
2356
- if (errorMessage) {
2357
- merged[key] = errorMessage;
2358
- } else {
2359
- delete merged[key];
2360
- }
2361
- const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
2362
- const match = k.match(/\[(\d+)\]\./);
2363
- const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
2364
- return { key: k, row };
2365
- }).sort((a, b) => a.row - b.row);
2366
- if (subErrors.length > 0) {
2367
- const first = subErrors[0];
2368
- merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
2369
- } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
2370
- delete merged[fieldName];
2371
- }
2372
- replaceErrors(merged);
2373
- },
2374
- [replaceErrors]
2375
- );
2376
2502
  const renderField = (field) => {
2377
2503
  const fieldError = formErrors[field.name] || null;
2378
2504
  const rendered = renderFieldInner(field);
@@ -2852,12 +2978,13 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2852
2978
  const rowValues = { ...formValues, [field.name]: nextRows };
2853
2979
  const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
2854
2980
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
2981
+ return err;
2855
2982
  };
2856
2983
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
2857
2984
  const updated = rows.map(
2858
2985
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
2859
2986
  );
2860
- handleFieldChange(field.name, updated);
2987
+ handleFieldChange(field.name, updated, { clearNestedErrors: false });
2861
2988
  if (validateOnChange) {
2862
2989
  validateSubField(rowIdx, subField, subValue, updated);
2863
2990
  }
@@ -2867,13 +2994,24 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2867
2994
  const nextRows = rows.map(
2868
2995
  (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
2869
2996
  );
2870
- 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
+ });
2871
3008
  };
2872
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) => {
2873
3010
  const sfValue = row[sf.name];
2874
3011
  const sfLabel = rowIdx === 0 ? sf.label : void 0;
2875
3012
  const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
2876
3013
  const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
3014
+ const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
2877
3015
  const sfProps = {
2878
3016
  name: `${field.name}-${rowIdx}-${sf.name}`,
2879
3017
  label: sfLabel,
@@ -2882,6 +3020,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2882
3020
  disabled: resolveDisabled(sf, formValues) || isDisabled,
2883
3021
  error: !!sfError,
2884
3022
  validationMessage: sfError || void 0,
3023
+ ...validatingFields[validationKey] ? { loading: true } : {},
2885
3024
  ...sf.fieldProps || {}
2886
3025
  };
2887
3026
  let sfElement;