hs-uix 1.0.0 → 1.0.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
@@ -1108,7 +1108,132 @@ var isValueEmpty = (value, field) => {
1108
1108
  if ((field.type === "toggle" || field.type === "checkbox") && value === false) return true;
1109
1109
  return false;
1110
1110
  };
1111
- var runValidators = (value, field, allValues, fieldTypes) => {
1111
+ var isPromise = (value) => value && typeof value.then === "function";
1112
+ var isAsyncFunction = (fn) => fn && fn.constructor && fn.constructor.name === "AsyncFunction";
1113
+ var normalizeValidatorResult = (result) => {
1114
+ if (result === true || result === void 0 || result === null || result === false) return null;
1115
+ return String(result);
1116
+ };
1117
+ var isDateValueObject = (value) => isPlainObject(value) && Number.isInteger(value.year) && Number.isInteger(value.month) && Number.isInteger(value.date);
1118
+ var isTimeValueObject = (value) => isPlainObject(value) && Number.isInteger(value.hours) && Number.isInteger(value.minutes);
1119
+ var compareDateValues = (a, b) => {
1120
+ if (a.year !== b.year) return a.year - b.year;
1121
+ if (a.month !== b.month) return a.month - b.month;
1122
+ return a.date - b.date;
1123
+ };
1124
+ var compareTimeValues = (a, b) => {
1125
+ if (a.hours !== b.hours) return a.hours - b.hours;
1126
+ return a.minutes - b.minutes;
1127
+ };
1128
+ var runDefaultFieldValidator = (value, field, allValues) => {
1129
+ const errorPrefix = field.label || field.name;
1130
+ switch (field.type) {
1131
+ case "text":
1132
+ case "password":
1133
+ case "textarea":
1134
+ if (typeof value !== "string") return `${errorPrefix} must be text`;
1135
+ break;
1136
+ case "number":
1137
+ case "stepper":
1138
+ case "currency":
1139
+ if (typeof value !== "number" || Number.isNaN(value)) return `${errorPrefix} must be a number`;
1140
+ break;
1141
+ case "toggle":
1142
+ case "checkbox":
1143
+ if (typeof value !== "boolean") return `${errorPrefix} must be true or false`;
1144
+ break;
1145
+ case "select":
1146
+ case "radioGroup": {
1147
+ const options = resolveOptions(field, allValues);
1148
+ if (options.length > 0 && !options.some((o) => Object.is(o.value, value))) {
1149
+ return `${errorPrefix} has an invalid selection`;
1150
+ }
1151
+ break;
1152
+ }
1153
+ case "multiselect":
1154
+ case "checkboxGroup": {
1155
+ if (!Array.isArray(value)) return `${errorPrefix} must be a list`;
1156
+ const options = resolveOptions(field, allValues);
1157
+ if (options.length > 0) {
1158
+ const validValues = new Set(options.map((o) => o.value));
1159
+ const hasInvalid = value.some((item) => !validValues.has(item));
1160
+ if (hasInvalid) return `${errorPrefix} has an invalid selection`;
1161
+ }
1162
+ break;
1163
+ }
1164
+ case "date":
1165
+ if (!isDateValueObject(value)) return `${errorPrefix} has an invalid date`;
1166
+ break;
1167
+ case "time":
1168
+ if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
1169
+ break;
1170
+ case "datetime": {
1171
+ if (isDateValueObject(value)) break;
1172
+ if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
1173
+ const hasDate = value.date !== void 0;
1174
+ const hasTime = value.time !== void 0;
1175
+ if (!hasDate && !hasTime) return `${errorPrefix} has an invalid date/time`;
1176
+ if (hasDate && !isDateValueObject(value.date)) return `${errorPrefix} has an invalid date`;
1177
+ if (hasTime && !isTimeValueObject(value.time)) return `${errorPrefix} has an invalid time`;
1178
+ break;
1179
+ }
1180
+ case "repeater":
1181
+ if (!Array.isArray(value)) return `${errorPrefix} has invalid rows`;
1182
+ break;
1183
+ default:
1184
+ break;
1185
+ }
1186
+ return null;
1187
+ };
1188
+ var runCustomSyncValidators = (value, field, allValues) => {
1189
+ const validators = Array.isArray(field.validators) ? field.validators : [];
1190
+ for (const validator of validators) {
1191
+ if (isAsyncFunction(validator)) continue;
1192
+ try {
1193
+ const result = validator(value, allValues);
1194
+ if (isPromise(result)) continue;
1195
+ const err = normalizeValidatorResult(result);
1196
+ if (err) return err;
1197
+ } catch (err) {
1198
+ return (err == null ? void 0 : err.message) || "Validation failed";
1199
+ }
1200
+ }
1201
+ if (field.validate && !isAsyncFunction(field.validate)) {
1202
+ try {
1203
+ const result = field.validate(value, allValues);
1204
+ if (!isPromise(result)) {
1205
+ const err = normalizeValidatorResult(result);
1206
+ if (err) return err;
1207
+ }
1208
+ } catch (err) {
1209
+ return (err == null ? void 0 : err.message) || "Validation failed";
1210
+ }
1211
+ }
1212
+ return null;
1213
+ };
1214
+ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
1215
+ const promises = [];
1216
+ const validators = Array.isArray(field.validators) ? field.validators : [];
1217
+ for (const validator of validators) {
1218
+ try {
1219
+ const result = validator(value, allValues, context);
1220
+ if (isPromise(result)) promises.push(result);
1221
+ } catch (err) {
1222
+ promises.push(Promise.reject(err));
1223
+ }
1224
+ }
1225
+ if (field.validate) {
1226
+ try {
1227
+ const result = field.validate(value, allValues, context);
1228
+ if (isPromise(result)) promises.push(result);
1229
+ } catch (err) {
1230
+ promises.push(Promise.reject(err));
1231
+ }
1232
+ }
1233
+ return promises;
1234
+ };
1235
+ var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1236
+ const includeCustomValidators = options.includeCustomValidators !== false;
1112
1237
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
1113
1238
  const isRequired = resolveRequired(field, allValues);
1114
1239
  const plugin = fieldTypes && fieldTypes[field.type];
@@ -1117,6 +1242,10 @@ var runValidators = (value, field, allValues, fieldTypes) => {
1117
1242
  return `${field.label} is required`;
1118
1243
  }
1119
1244
  if (empty) return null;
1245
+ if (field.useDefaultValidators !== false) {
1246
+ const typeError = runDefaultFieldValidator(value, field, allValues);
1247
+ if (typeError) return typeError;
1248
+ }
1120
1249
  if (field.pattern && typeof value === "string") {
1121
1250
  if (!field.pattern.test(value)) {
1122
1251
  return field.patternMessage || "Invalid format";
@@ -1138,10 +1267,25 @@ var runValidators = (value, field, allValues, fieldTypes) => {
1138
1267
  return `Must be no more than ${field.max}`;
1139
1268
  }
1140
1269
  }
1141
- if (field.validate) {
1142
- const result = field.validate(value, allValues);
1143
- if (result && typeof result.then === "function") return null;
1144
- if (result !== true && result) return result;
1270
+ if (field.type === "date" && isDateValueObject(value)) {
1271
+ if (field.min && isDateValueObject(field.min) && compareDateValues(value, field.min) < 0) {
1272
+ return field.minValidationMessage || "Date is too early";
1273
+ }
1274
+ if (field.max && isDateValueObject(field.max) && compareDateValues(value, field.max) > 0) {
1275
+ return field.maxValidationMessage || "Date is too late";
1276
+ }
1277
+ }
1278
+ if (field.type === "time" && isTimeValueObject(value)) {
1279
+ if (field.min && isTimeValueObject(field.min) && compareTimeValues(value, field.min) < 0) {
1280
+ return field.minValidationMessage || "Time is too early";
1281
+ }
1282
+ if (field.max && isTimeValueObject(field.max) && compareTimeValues(value, field.max) > 0) {
1283
+ return field.maxValidationMessage || "Time is too late";
1284
+ }
1285
+ }
1286
+ if (includeCustomValidators) {
1287
+ const customError = runCustomSyncValidators(value, field, allValues);
1288
+ if (customError) return customError;
1145
1289
  }
1146
1290
  return null;
1147
1291
  };
@@ -1153,6 +1297,58 @@ var resolveOptions = (field, allValues) => {
1153
1297
  if (typeof field.options === "function") return field.options(allValues);
1154
1298
  return field.options || [];
1155
1299
  };
1300
+ var getDependsOnName = (field) => field.dependsOnConfig && field.dependsOnConfig.field;
1301
+ var getDependsOnDisplay = (field) => field.dependsOnConfig && field.dependsOnConfig.display || "grouped";
1302
+ var getDependsOnLabel = (field) => field.dependsOnConfig && field.dependsOnConfig.label;
1303
+ var getDependsOnMessage = (field) => field.dependsOnConfig && field.dependsOnConfig.message;
1304
+ var getRepeaterErrorKey = (fieldName, rowIdx, subFieldName) => `${fieldName}[${rowIdx}].${subFieldName}`;
1305
+ var isPlainObject = (value) => Object.prototype.toString.call(value) === "[object Object]";
1306
+ var deepEqual = (a, b) => {
1307
+ if (Object.is(a, b)) return true;
1308
+ if (typeof a !== typeof b) return false;
1309
+ if (a == null || b == null) return false;
1310
+ if (Array.isArray(a)) {
1311
+ if (!Array.isArray(b) || a.length !== b.length) return false;
1312
+ for (let i = 0; i < a.length; i++) {
1313
+ if (!deepEqual(a[i], b[i])) return false;
1314
+ }
1315
+ return true;
1316
+ }
1317
+ if (a instanceof Date && b instanceof Date) {
1318
+ return a.getTime() === b.getTime();
1319
+ }
1320
+ if (isPlainObject(a) && isPlainObject(b)) {
1321
+ const aKeys = Object.keys(a);
1322
+ const bKeys = Object.keys(b);
1323
+ if (aKeys.length !== bKeys.length) return false;
1324
+ for (const key of aKeys) {
1325
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
1326
+ if (!deepEqual(a[key], b[key])) return false;
1327
+ }
1328
+ return true;
1329
+ }
1330
+ return false;
1331
+ };
1332
+ var fieldSetHasErrors = (errors, fields) => {
1333
+ if (!errors || !fields || fields.length === 0) return false;
1334
+ const names = new Set(fields.map((field) => field.name));
1335
+ return Object.keys(errors).some((errorKey) => {
1336
+ const base = errorKey.split("[")[0];
1337
+ return names.has(base);
1338
+ });
1339
+ };
1340
+ var deepClone = (value) => {
1341
+ if (Array.isArray(value)) return value.map(deepClone);
1342
+ if (value instanceof Date) return new Date(value.getTime());
1343
+ if (isPlainObject(value)) {
1344
+ const next = {};
1345
+ for (const key of Object.keys(value)) {
1346
+ next[key] = deepClone(value[key]);
1347
+ }
1348
+ return next;
1349
+ }
1350
+ return value;
1351
+ };
1156
1352
  var useFormPrefill = (properties, mapping) => {
1157
1353
  return useMemo2(() => {
1158
1354
  if (!properties) return {};
@@ -1222,28 +1418,30 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1222
1418
  // validate current step fields before Next
1223
1419
  } = props;
1224
1420
  const {
1225
- submitLabel = "Submit",
1226
- // submit button text
1421
+ labels,
1422
+ // { submit, cancel, back, next } — i18n label object
1227
1423
  submitVariant = "primary",
1228
1424
  // submit button variant
1229
1425
  showCancel = false,
1230
1426
  // show cancel button
1231
- cancelLabel = "Cancel",
1232
- // cancel button text
1233
1427
  onCancel,
1234
1428
  // () => void
1235
1429
  submitPosition = "bottom",
1236
1430
  // "bottom" | "none"
1237
1431
  loading: controlledLoading,
1238
1432
  // controlled loading state
1239
- disabled = false
1433
+ disabled = false,
1240
1434
  // disable entire form
1435
+ renderButtons: renderButtonsProp
1436
+ // custom action row renderer
1241
1437
  } = props;
1242
1438
  const {
1243
1439
  columns = 1,
1244
1440
  // number of grid columns (1 = full-width stack)
1245
1441
  columnWidth,
1246
1442
  // AutoGrid columnWidth — responsive layout (overrides columns)
1443
+ maxColumns,
1444
+ // cap number of columns per row in AutoGrid mode
1247
1445
  layout,
1248
1446
  // explicit row layout array (overrides columns + columnWidth)
1249
1447
  sections,
@@ -1254,6 +1452,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1254
1452
  // show * on required fields
1255
1453
  noFormWrapper = false,
1256
1454
  // skip HubSpot <Form> wrapper
1455
+ autoComplete,
1456
+ // form autoComplete attribute
1457
+ formProps,
1458
+ // pass-through props for Form wrapper
1257
1459
  fieldTypes
1258
1460
  // Record<string, FieldTypePlugin> — custom field type registry
1259
1461
  } = props;
@@ -1264,8 +1466,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1264
1466
  // string — form-level success alert
1265
1467
  readOnly: formReadOnly = false,
1266
1468
  // boolean — lock all fields
1267
- readOnlyMessage
1469
+ readOnlyMessage,
1268
1470
  // string — warning alert when readOnly
1471
+ alerts,
1472
+ // { addAlert, readOnlyTitle, errorTitle, successTitle }
1473
+ errors: controlledErrors
1474
+ // controlled validation errors
1269
1475
  } = props;
1270
1476
  const {
1271
1477
  onDirtyChange,
@@ -1273,6 +1479,38 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1273
1479
  autoSave
1274
1480
  // { debounce: number, onAutoSave: (values) => void }
1275
1481
  } = props;
1482
+ const submitButtonLabel = (labels == null ? void 0 : labels.submit) || "Submit";
1483
+ const cancelButtonLabel = (labels == null ? void 0 : labels.cancel) || "Cancel";
1484
+ const backButtonLabel = (labels == null ? void 0 : labels.back) || "Back";
1485
+ const nextButtonLabel = (labels == null ? void 0 : labels.next) || "Next";
1486
+ const addAlert = alerts == null ? void 0 : alerts.addAlert;
1487
+ const readOnlyTitle = (alerts == null ? void 0 : alerts.readOnlyTitle) || "Read Only";
1488
+ const errorTitle = (alerts == null ? void 0 : alerts.errorTitle) || "Error";
1489
+ const successTitle = (alerts == null ? void 0 : alerts.successTitle) || "Success";
1490
+ const prevErrorRef = useRef2(formError);
1491
+ const prevSuccessRef = useRef2(formSuccess);
1492
+ useEffect2(() => {
1493
+ if (!addAlert) return;
1494
+ if (formError && formError !== prevErrorRef.current) {
1495
+ addAlert({
1496
+ type: "danger",
1497
+ title: errorTitle,
1498
+ message: typeof formError === "string" ? formError : void 0
1499
+ });
1500
+ }
1501
+ prevErrorRef.current = formError;
1502
+ }, [addAlert, formError, errorTitle]);
1503
+ useEffect2(() => {
1504
+ if (!addAlert) return;
1505
+ if (formSuccess && formSuccess !== prevSuccessRef.current) {
1506
+ addAlert({
1507
+ type: "success",
1508
+ title: successTitle,
1509
+ message: formSuccess
1510
+ });
1511
+ }
1512
+ prevSuccessRef.current = formSuccess;
1513
+ }, [addAlert, formSuccess, successTitle]);
1276
1514
  const computeInitialValues = () => {
1277
1515
  const vals = {};
1278
1516
  for (const field of fields) {
@@ -1288,25 +1526,99 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1288
1526
  const [internalErrors, setInternalErrors] = useState2({});
1289
1527
  const [internalStep, setInternalStep] = useState2(0);
1290
1528
  const [internalLoading, setInternalLoading] = useState2(false);
1291
- const [touchedFields, setTouchedFields] = useState2({});
1292
1529
  const [validatingFields, setValidatingFields] = useState2({});
1293
1530
  const asyncValidationRef = useRef2(/* @__PURE__ */ new Map());
1531
+ const asyncAbortRef = useRef2(/* @__PURE__ */ new Map());
1532
+ const asyncValidationVersionRef = useRef2(/* @__PURE__ */ new Map());
1294
1533
  const debounceTimersRef = useRef2(/* @__PURE__ */ new Map());
1534
+ const inputDebounceRef = useRef2(/* @__PURE__ */ new Map());
1535
+ const rowKeyRef = useRef2(/* @__PURE__ */ new WeakMap());
1536
+ const rowKeyCounterRef = useRef2(0);
1295
1537
  const initialSnapshot = useRef2(null);
1296
1538
  if (initialSnapshot.current === null) {
1297
- initialSnapshot.current = JSON.stringify(computeInitialValues());
1539
+ initialSnapshot.current = deepClone(computeInitialValues());
1298
1540
  }
1299
1541
  const formValues = values != null ? values : internalValues;
1542
+ const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
1300
1543
  const currentStep = controlledStep != null ? controlledStep : internalStep;
1301
1544
  const isLoading = controlledLoading != null ? controlledLoading : internalLoading;
1302
1545
  const isMultiStep = Array.isArray(steps) && steps.length > 0;
1546
+ const formValuesRef = useRef2(formValues);
1547
+ const formErrorsRef = useRef2(formErrors);
1548
+ const draftValuesRef = useRef2(null);
1549
+ formValuesRef.current = formValues;
1550
+ formErrorsRef.current = formErrors;
1551
+ const fieldByName = useMemo2(() => {
1552
+ const map = /* @__PURE__ */ new Map();
1553
+ for (const field of fields) map.set(field.name, field);
1554
+ return map;
1555
+ }, [fields]);
1556
+ const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
1557
+ const configWarningsRef = useRef2(/* @__PURE__ */ new Set());
1558
+ const warnConfig = useCallback2((message) => {
1559
+ if (!isDev) return;
1560
+ if (configWarningsRef.current.has(message)) return;
1561
+ configWarningsRef.current.add(message);
1562
+ if (typeof console !== "undefined" && console.warn) {
1563
+ console.warn(`[FormBuilder] ${message}`);
1564
+ }
1565
+ }, [isDev]);
1566
+ const replaceErrors = useCallback2(
1567
+ (nextErrors) => {
1568
+ if (controlledErrors == null) setInternalErrors(nextErrors);
1569
+ if (onValidationChange) onValidationChange(nextErrors);
1570
+ },
1571
+ [controlledErrors, onValidationChange]
1572
+ );
1573
+ const updateErrors = useCallback2(
1574
+ (newErrors) => {
1575
+ const mergeErrors = (base) => {
1576
+ const merged = { ...base, ...newErrors };
1577
+ for (const key of Object.keys(newErrors)) {
1578
+ if (newErrors[key] === null || newErrors[key] === void 0) {
1579
+ delete merged[key];
1580
+ }
1581
+ }
1582
+ return merged;
1583
+ };
1584
+ if (controlledErrors != null) {
1585
+ const merged = mergeErrors(formErrorsRef.current || {});
1586
+ if (onValidationChange) onValidationChange(merged);
1587
+ return;
1588
+ }
1589
+ setInternalErrors((prev) => {
1590
+ const merged = mergeErrors(prev);
1591
+ if (onValidationChange) onValidationChange(merged);
1592
+ return merged;
1593
+ });
1594
+ },
1595
+ [controlledErrors, onValidationChange]
1596
+ );
1597
+ const getFieldEmptyValue = useCallback2(
1598
+ (field) => {
1599
+ const plugin = fieldTypes && fieldTypes[field.type];
1600
+ return plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
1601
+ },
1602
+ [fieldTypes]
1603
+ );
1604
+ const getRowKey = useCallback2((fieldName, row, index) => {
1605
+ if (!row || typeof row !== "object") return `${fieldName}-idx-${index}`;
1606
+ if (!rowKeyRef.current.has(row)) {
1607
+ rowKeyCounterRef.current += 1;
1608
+ rowKeyRef.current.set(row, `${fieldName}-row-${rowKeyCounterRef.current}`);
1609
+ }
1610
+ return rowKeyRef.current.get(row);
1611
+ }, []);
1303
1612
  useEffect2(() => {
1304
1613
  return () => {
1305
1614
  for (const timer of debounceTimersRef.current.values()) clearTimeout(timer);
1615
+ for (const timer of inputDebounceRef.current.values()) clearTimeout(timer);
1616
+ for (const controller of asyncAbortRef.current.values()) controller.abort();
1617
+ if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
1306
1618
  };
1307
1619
  }, []);
1308
1620
  const isDirty = useMemo2(() => {
1309
- return JSON.stringify(formValues) !== initialSnapshot.current;
1621
+ return !deepEqual(formValues, initialSnapshot.current);
1310
1622
  }, [formValues]);
1311
1623
  const prevDirtyRef = useRef2(false);
1312
1624
  useEffect2(() => {
@@ -1316,36 +1628,129 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1316
1628
  }
1317
1629
  }, [isDirty, onDirtyChange]);
1318
1630
  const autoSaveTimerRef = useRef2(null);
1631
+ const autoSaveRef = useRef2(autoSave);
1632
+ autoSaveRef.current = autoSave;
1633
+ const prevAutoSaveValues = useRef2(deepClone(formValues));
1319
1634
  useEffect2(() => {
1320
- if (!autoSave || !autoSave.onAutoSave || !isDirty) return;
1635
+ const cfg = autoSaveRef.current;
1636
+ if (!cfg || !cfg.onAutoSave || !isDirty) return;
1637
+ if (deepEqual(prevAutoSaveValues.current, formValues)) return;
1638
+ prevAutoSaveValues.current = deepClone(formValues);
1321
1639
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
1322
1640
  autoSaveTimerRef.current = setTimeout(() => {
1323
1641
  autoSaveTimerRef.current = null;
1324
- autoSave.onAutoSave(formValues);
1325
- }, autoSave.debounce || 1e3);
1642
+ autoSaveRef.current.onAutoSave(formValues);
1643
+ }, cfg.debounce || 1e3);
1326
1644
  return () => {
1327
1645
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
1328
1646
  };
1329
- }, [formValues, isDirty, autoSave]);
1330
- const visibleFields = useMemo2(() => {
1331
- let filtered = fields.filter((f) => {
1647
+ }, [formValues, isDirty]);
1648
+ const allVisibleFields = useMemo2(() => {
1649
+ return fields.filter((f) => {
1332
1650
  if (f.visible && !f.visible(formValues)) return false;
1333
1651
  return true;
1334
1652
  });
1653
+ }, [fields, formValues]);
1654
+ const visibleFields = useMemo2(() => {
1655
+ let filtered = allVisibleFields;
1335
1656
  if (isMultiStep && steps[currentStep] && steps[currentStep].fields) {
1336
1657
  const stepFieldNames = new Set(steps[currentStep].fields);
1337
1658
  filtered = filtered.filter((f) => stepFieldNames.has(f.name));
1338
1659
  }
1339
1660
  return filtered;
1340
- }, [fields, formValues, isMultiStep, steps, currentStep]);
1661
+ }, [allVisibleFields, isMultiStep, steps, currentStep]);
1662
+ useEffect2(() => {
1663
+ const nameSet = new Set(fields.map((f) => f.name));
1664
+ if (nameSet.size !== fields.length) {
1665
+ warnConfig("Duplicate field names detected. Field names must be unique.");
1666
+ }
1667
+ for (const field of fields) {
1668
+ const parentName = getDependsOnName(field);
1669
+ if (parentName && !nameSet.has(parentName)) {
1670
+ warnConfig(`Field "${field.name}" depends on missing field "${parentName}".`);
1671
+ }
1672
+ }
1673
+ if (steps) {
1674
+ for (let i = 0; i < steps.length; i++) {
1675
+ const step = steps[i];
1676
+ if (!step.fields) continue;
1677
+ for (const fieldName of step.fields) {
1678
+ if (!nameSet.has(fieldName)) {
1679
+ warnConfig(`Step ${i + 1} references missing field "${fieldName}".`);
1680
+ }
1681
+ }
1682
+ }
1683
+ }
1684
+ if (layout) {
1685
+ for (const row of layout) {
1686
+ for (const entry of row) {
1687
+ const fieldName = typeof entry === "string" ? entry : entry.field;
1688
+ if (!nameSet.has(fieldName)) {
1689
+ warnConfig(`Layout references missing field "${fieldName}".`);
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1694
+ if (sections) {
1695
+ for (const section of sections) {
1696
+ for (const fieldName of section.fields || []) {
1697
+ if (!nameSet.has(fieldName)) {
1698
+ warnConfig(`Section "${section.id}" references missing field "${fieldName}".`);
1699
+ }
1700
+ }
1701
+ }
1702
+ }
1703
+ }, [fields, steps, layout, sections, warnConfig]);
1704
+ const validateRepeaterField = useCallback2(
1705
+ (field, value, allValues) => {
1706
+ const errors = {};
1707
+ const rows = Array.isArray(value) ? value : [];
1708
+ const subFields = field.fields || [];
1709
+ let firstSubError = null;
1710
+ if (resolveRequired(field, allValues) && rows.length === 0) {
1711
+ const requiredError = `${field.label} is required`;
1712
+ errors[field.name] = requiredError;
1713
+ return { errors, hasErrors: true };
1714
+ }
1715
+ if (typeof field.min === "number" && rows.length < field.min) {
1716
+ errors[field.name] = `Must have at least ${field.min} ${field.min === 1 ? "row" : "rows"}`;
1717
+ } else if (typeof field.max === "number" && rows.length > field.max) {
1718
+ errors[field.name] = `Must have no more than ${field.max} ${field.max === 1 ? "row" : "rows"}`;
1719
+ }
1720
+ rows.forEach((row, rowIdx) => {
1721
+ const rowValues = { ...allValues, [field.name]: rows };
1722
+ subFields.forEach((subField) => {
1723
+ if (subField.visible && !subField.visible(rowValues)) return;
1724
+ const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes);
1725
+ if (!err) return;
1726
+ const key = getRepeaterErrorKey(field.name, rowIdx, subField.name);
1727
+ errors[key] = err;
1728
+ if (!firstSubError) firstSubError = { row: rowIdx + 1, message: err };
1729
+ });
1730
+ });
1731
+ if (!errors[field.name] && firstSubError) {
1732
+ errors[field.name] = `Row ${firstSubError.row}: ${firstSubError.message}`;
1733
+ }
1734
+ return { errors, hasErrors: Object.keys(errors).length > 0 };
1735
+ },
1736
+ [fieldTypes]
1737
+ );
1341
1738
  const validateField = useCallback2(
1342
1739
  (name, value) => {
1343
- const field = fields.find((f) => f.name === name);
1740
+ const field = fieldByName.get(name);
1344
1741
  if (!field) return null;
1345
1742
  if (field.visible && !field.visible(formValues)) return null;
1743
+ if (field.type === "repeater") {
1744
+ const repeaterResult = validateRepeaterField(
1745
+ field,
1746
+ value != null ? value : formValues[name],
1747
+ formValues
1748
+ );
1749
+ return repeaterResult.errors[name] || null;
1750
+ }
1346
1751
  return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes);
1347
1752
  },
1348
- [fields, formValues, fieldTypes]
1753
+ [fieldByName, formValues, validateRepeaterField, fieldTypes]
1349
1754
  );
1350
1755
  const validateVisibleFields = useCallback2(
1351
1756
  (fieldSubset) => {
@@ -1353,6 +1758,14 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1353
1758
  const errors = {};
1354
1759
  let hasErrors = false;
1355
1760
  for (const field of toValidate) {
1761
+ if (field.type === "repeater") {
1762
+ const repeaterResult = validateRepeaterField(field, formValues[field.name], formValues);
1763
+ if (repeaterResult.hasErrors) {
1764
+ Object.assign(errors, repeaterResult.errors);
1765
+ hasErrors = true;
1766
+ }
1767
+ continue;
1768
+ }
1356
1769
  const err = runValidators(formValues[field.name], field, formValues, fieldTypes);
1357
1770
  if (err) {
1358
1771
  errors[field.name] = err;
@@ -1361,64 +1774,87 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1361
1774
  }
1362
1775
  return { errors, hasErrors };
1363
1776
  },
1364
- [visibleFields, formValues]
1365
- );
1366
- const updateErrors = useCallback2(
1367
- (newErrors) => {
1368
- setInternalErrors((prev) => {
1369
- const merged = { ...prev, ...newErrors };
1370
- for (const key of Object.keys(merged)) {
1371
- if (newErrors[key] === null || newErrors[key] === void 0) {
1372
- delete merged[key];
1373
- }
1374
- }
1375
- if (onValidationChange) onValidationChange(merged);
1376
- return merged;
1377
- });
1378
- },
1379
- [onValidationChange]
1777
+ [visibleFields, formValues, validateRepeaterField, fieldTypes]
1380
1778
  );
1381
1779
  const runAsyncValidation = useCallback2(
1382
1780
  (name, value) => {
1383
- const field = fields.find((f) => f.name === name);
1384
- if (!field || !field.validate) return;
1781
+ const field = fieldByName.get(name);
1782
+ if (!field || field.type === "repeater") return null;
1385
1783
  const val = value != null ? value : formValues[name];
1386
- const syncError = runValidators(val, field, formValues, fieldTypes);
1387
- if (syncError) return;
1388
- const result = field.validate(val, formValues);
1389
- if (!result || typeof result.then !== "function") return;
1390
- const validationPromise = result.then(
1391
- (asyncResult) => {
1392
- if (asyncValidationRef.current.get(name) !== validationPromise) return;
1784
+ const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false });
1785
+ const prevController = asyncAbortRef.current.get(name);
1786
+ if (prevController) prevController.abort();
1787
+ asyncAbortRef.current.delete(name);
1788
+ setValidatingFields((prev) => {
1789
+ if (!prev[name]) return prev;
1790
+ const next = { ...prev };
1791
+ delete next[name];
1792
+ return next;
1793
+ });
1794
+ if (syncError) return null;
1795
+ const version = (asyncValidationVersionRef.current.get(name) || 0) + 1;
1796
+ asyncValidationVersionRef.current.set(name, version);
1797
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
1798
+ if (controller) asyncAbortRef.current.set(name, controller);
1799
+ let asyncPromises;
1800
+ try {
1801
+ asyncPromises = collectAsyncValidatorPromises(
1802
+ val,
1803
+ field,
1804
+ formValues,
1805
+ controller ? { signal: controller.signal } : void 0
1806
+ );
1807
+ } catch (err) {
1808
+ updateErrors({ [name]: (err == null ? void 0 : err.message) || "Validation failed" });
1809
+ return null;
1810
+ }
1811
+ if (asyncPromises.length === 0) {
1812
+ asyncAbortRef.current.delete(name);
1813
+ return null;
1814
+ }
1815
+ const validationPromise = Promise.all(asyncPromises).then(
1816
+ (results) => {
1817
+ if (asyncValidationVersionRef.current.get(name) !== version) return;
1393
1818
  asyncValidationRef.current.delete(name);
1819
+ asyncAbortRef.current.delete(name);
1394
1820
  setValidatingFields((prev) => {
1395
1821
  const next = { ...prev };
1396
1822
  delete next[name];
1397
1823
  return next;
1398
1824
  });
1399
- const err = asyncResult !== true && asyncResult ? asyncResult : null;
1825
+ let err = null;
1826
+ for (const result of results) {
1827
+ const normalized = normalizeValidatorResult(result);
1828
+ if (normalized) {
1829
+ err = normalized;
1830
+ break;
1831
+ }
1832
+ }
1400
1833
  updateErrors({ [name]: err });
1401
1834
  },
1402
1835
  (rejection) => {
1403
- if (asyncValidationRef.current.get(name) !== validationPromise) return;
1836
+ if (asyncValidationVersionRef.current.get(name) !== version) return;
1404
1837
  asyncValidationRef.current.delete(name);
1838
+ asyncAbortRef.current.delete(name);
1405
1839
  setValidatingFields((prev) => {
1406
1840
  const next = { ...prev };
1407
1841
  delete next[name];
1408
1842
  return next;
1409
1843
  });
1844
+ if (rejection && rejection.name === "AbortError") return;
1410
1845
  updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
1411
1846
  }
1412
1847
  );
1413
1848
  asyncValidationRef.current.set(name, validationPromise);
1414
1849
  setValidatingFields((prev) => ({ ...prev, [name]: true }));
1850
+ return validationPromise;
1415
1851
  },
1416
- [fields, formValues, updateErrors]
1852
+ [fieldByName, formValues, fieldTypes, updateErrors]
1417
1853
  );
1418
1854
  const triggerAsyncValidation = useCallback2(
1419
1855
  (name, value) => {
1420
- const field = fields.find((f) => f.name === name);
1421
- if (!field || !field.validate) return;
1856
+ const field = fieldByName.get(name);
1857
+ if (!field || field.type === "repeater") return;
1422
1858
  const debounceMs = field.validateDebounce;
1423
1859
  if (debounceMs && debounceMs > 0) {
1424
1860
  const existing = debounceTimersRef.current.get(name);
@@ -1432,44 +1868,93 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1432
1868
  runAsyncValidation(name, value);
1433
1869
  }
1434
1870
  },
1435
- [fields, runAsyncValidation]
1871
+ [fieldByName, runAsyncValidation]
1436
1872
  );
1437
- const setFieldValueSilent = useCallback2(
1438
- (name, value) => {
1873
+ const commitValues = useCallback2(
1874
+ (nextValues) => {
1875
+ formValuesRef.current = nextValues;
1439
1876
  if (values != null) {
1440
- if (onChange) onChange({ ...formValues, [name]: value });
1877
+ if (onChange) onChange(nextValues);
1441
1878
  } else {
1442
- setInternalValues((prev) => ({ ...prev, [name]: value }));
1879
+ setInternalValues(nextValues);
1443
1880
  }
1444
1881
  },
1445
- [values, onChange, formValues]
1882
+ [values, onChange]
1883
+ );
1884
+ const setFieldValueSilent = useCallback2(
1885
+ (name, value) => {
1886
+ const base = draftValuesRef.current || formValuesRef.current || {};
1887
+ const nextValues = { ...base, [name]: value };
1888
+ draftValuesRef.current = nextValues;
1889
+ commitValues(nextValues);
1890
+ },
1891
+ [commitValues]
1446
1892
  );
1447
1893
  const handleFieldChange = useCallback2(
1448
1894
  (name, value) => {
1449
- const newValues = { ...formValues, [name]: value };
1450
- if (values != null) {
1451
- if (onChange) onChange(newValues);
1452
- } else {
1453
- setInternalValues(newValues);
1895
+ const newValues = { ...formValuesRef.current, [name]: value };
1896
+ const queue = [name];
1897
+ const visited = /* @__PURE__ */ new Set();
1898
+ const clearedErrors = {};
1899
+ while (queue.length > 0) {
1900
+ const current = queue.shift();
1901
+ if (!current || visited.has(current)) continue;
1902
+ visited.add(current);
1903
+ fields.forEach((dep) => {
1904
+ const parentName = getDependsOnName(dep);
1905
+ if (parentName !== current || dep.name === current) return;
1906
+ if (!dep.options) return;
1907
+ const depOptions = resolveOptions(dep, newValues);
1908
+ const depValue = newValues[dep.name];
1909
+ if (depValue == null || depValue === "") return;
1910
+ const validValues = new Set(depOptions.map((o) => o.value));
1911
+ let nextDepValue = depValue;
1912
+ let changed = false;
1913
+ if (Array.isArray(depValue)) {
1914
+ const filtered = depValue.filter((v) => validValues.has(v));
1915
+ if (filtered.length !== depValue.length) {
1916
+ nextDepValue = filtered;
1917
+ changed = true;
1918
+ }
1919
+ } else if (!validValues.has(depValue)) {
1920
+ nextDepValue = getFieldEmptyValue(dep);
1921
+ changed = true;
1922
+ }
1923
+ if (changed) {
1924
+ newValues[dep.name] = nextDepValue;
1925
+ queue.push(dep.name);
1926
+ if (formErrorsRef.current[dep.name] != null) {
1927
+ clearedErrors[dep.name] = null;
1928
+ }
1929
+ }
1930
+ });
1454
1931
  }
1455
- if (onFieldChange) onFieldChange(name, value, newValues);
1456
- if (internalErrors[name]) {
1457
- updateErrors({ [name]: null });
1932
+ if (formErrorsRef.current[name] != null) {
1933
+ clearedErrors[name] = null;
1934
+ }
1935
+ for (const key of Object.keys(formErrorsRef.current)) {
1936
+ if (key.startsWith(`${name}[`)) {
1937
+ clearedErrors[key] = null;
1938
+ }
1458
1939
  }
1459
- const field = fields.find((f) => f.name === name);
1940
+ draftValuesRef.current = newValues;
1941
+ commitValues(newValues);
1942
+ if (onFieldChange) onFieldChange(name, value, newValues);
1943
+ if (Object.keys(clearedErrors).length > 0) updateErrors(clearedErrors);
1944
+ const field = fieldByName.get(name);
1460
1945
  if (field && field.onFieldChange) {
1461
1946
  field.onFieldChange(value, newValues, {
1462
1947
  setFieldValue: setFieldValueSilent,
1463
1948
  setFieldError: (fieldName, message) => updateErrors({ [fieldName]: message })
1464
1949
  });
1465
1950
  }
1951
+ draftValuesRef.current = null;
1466
1952
  },
1467
- [formValues, values, onChange, onFieldChange, internalErrors, updateErrors, fields, setFieldValueSilent]
1953
+ [fields, getFieldEmptyValue, commitValues, onFieldChange, updateErrors, fieldByName, setFieldValueSilent]
1468
1954
  );
1469
- const inputDebounceRef = useRef2(/* @__PURE__ */ new Map());
1470
1955
  const handleDebouncedFieldChange = useCallback2(
1471
1956
  (name, value) => {
1472
- const field = fields.find((f) => f.name === name);
1957
+ const field = fieldByName.get(name);
1473
1958
  const debounceMs = field && field.debounce;
1474
1959
  if (debounceMs && debounceMs > 0) {
1475
1960
  const existing = inputDebounceRef.current.get(name);
@@ -1483,26 +1968,24 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1483
1968
  handleFieldChange(name, value);
1484
1969
  }
1485
1970
  },
1486
- [fields, handleFieldChange]
1971
+ [fieldByName, handleFieldChange]
1487
1972
  );
1488
1973
  const handleFieldInput = useCallback2(
1489
1974
  (name, value) => {
1490
- if (validateOnChange) {
1491
- const err = validateField(name, value);
1492
- updateErrors({ [name]: err });
1493
- }
1975
+ if (!validateOnChange) return;
1976
+ const err = validateField(name, value);
1977
+ updateErrors({ [name]: err });
1494
1978
  },
1495
1979
  [validateOnChange, validateField, updateErrors]
1496
1980
  );
1497
1981
  const handleFieldBlur = useCallback2(
1498
1982
  (name, value) => {
1499
- setTouchedFields((prev) => ({ ...prev, [name]: true }));
1500
- if (validateOnBlur) {
1501
- const err = validateField(name, value != null ? value : formValues[name]);
1502
- updateErrors({ [name]: err });
1503
- if (!err) {
1504
- triggerAsyncValidation(name, value != null ? value : formValues[name]);
1505
- }
1983
+ if (!validateOnBlur) return;
1984
+ const resolvedValue = value != null ? value : formValues[name];
1985
+ const err = validateField(name, resolvedValue);
1986
+ updateErrors({ [name]: err });
1987
+ if (!err) {
1988
+ triggerAsyncValidation(name, resolvedValue);
1506
1989
  }
1507
1990
  },
1508
1991
  [validateOnBlur, validateField, updateErrors, formValues, triggerAsyncValidation]
@@ -1511,30 +1994,33 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1511
1994
  async (e) => {
1512
1995
  if (e && e.preventDefault) e.preventDefault();
1513
1996
  if (validateOnSubmit) {
1514
- const allVisible = fields.filter((f) => !f.visible || f.visible(formValues));
1515
- const { errors, hasErrors } = validateVisibleFields(allVisible);
1997
+ const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
1516
1998
  if (hasErrors) {
1517
- setInternalErrors(errors);
1518
- if (onValidationChange) onValidationChange(errors);
1999
+ replaceErrors(errors);
1519
2000
  return;
1520
2001
  }
1521
- if (asyncValidationRef.current.size > 0) {
1522
- await Promise.all(asyncValidationRef.current.values());
1523
- const currentErrors = { ...internalErrors };
1524
- const hasAsyncErrors = Object.keys(currentErrors).length > 0;
1525
- if (hasAsyncErrors) return;
2002
+ const asyncSubmitValidations = allVisibleFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
2003
+ if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
2004
+ const pendingValidations = [
2005
+ .../* @__PURE__ */ new Set([
2006
+ ...asyncSubmitValidations,
2007
+ ...Array.from(asyncValidationRef.current.values())
2008
+ ])
2009
+ ];
2010
+ await Promise.all(pendingValidations);
2011
+ if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) return;
1526
2012
  }
1527
2013
  }
1528
2014
  const reset = () => {
1529
2015
  const fresh = computeInitialValues();
1530
2016
  if (values == null) setInternalValues(fresh);
1531
- setInternalErrors({});
1532
- setTouchedFields({});
1533
- initialSnapshot.current = JSON.stringify(fresh);
2017
+ replaceErrors({});
2018
+ initialSnapshot.current = deepClone(fresh);
2019
+ prevAutoSaveValues.current = deepClone(fresh);
1534
2020
  };
1535
2021
  const rawValues = {};
1536
2022
  for (const key of Object.keys(formValues)) {
1537
- const f = fields.find((fd) => fd.name === key);
2023
+ const f = fieldByName.get(key);
1538
2024
  if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
1539
2025
  rawValues[key] = formValues[key];
1540
2026
  }
@@ -1558,25 +2044,34 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1558
2044
  if (controlledLoading == null) setInternalLoading(false);
1559
2045
  }
1560
2046
  },
1561
- [validateOnSubmit, fields, formValues, validateVisibleFields, onValidationChange, onSubmit, values, controlledLoading, internalErrors, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess]
2047
+ [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, runAsyncValidation]
1562
2048
  );
1563
- const handleNext = useCallback2(() => {
2049
+ const handleNext = useCallback2(async () => {
1564
2050
  if (!isMultiStep) return;
1565
2051
  if (validateStepOnNext && steps[currentStep] && steps[currentStep].fields) {
1566
- const stepFields = fields.filter(
1567
- (f) => steps[currentStep].fields.includes(f.name) && (!f.visible || f.visible(formValues))
1568
- );
2052
+ const stepFieldNames = new Set(steps[currentStep].fields);
2053
+ const stepFields = allVisibleFields.filter((f) => stepFieldNames.has(f.name));
1569
2054
  const { errors, hasErrors } = validateVisibleFields(stepFields);
1570
2055
  if (hasErrors) {
1571
- setInternalErrors((prev) => ({ ...prev, ...errors }));
1572
- if (onValidationChange) onValidationChange({ ...internalErrors, ...errors });
2056
+ replaceErrors({ ...formErrorsRef.current, ...errors });
1573
2057
  return;
1574
2058
  }
2059
+ const asyncStepValidations = stepFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
2060
+ if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
2061
+ const pendingValidations = [
2062
+ .../* @__PURE__ */ new Set([
2063
+ ...asyncStepValidations,
2064
+ ...Array.from(asyncValidationRef.current.values())
2065
+ ])
2066
+ ];
2067
+ await Promise.all(pendingValidations);
2068
+ if (fieldSetHasErrors(formErrorsRef.current, stepFields)) return;
2069
+ }
1575
2070
  }
1576
2071
  if (steps[currentStep] && steps[currentStep].validate) {
1577
2072
  const result = steps[currentStep].validate(formValues);
1578
2073
  if (result !== true && result) {
1579
- setInternalErrors((prev) => ({ ...prev, ...result }));
2074
+ replaceErrors({ ...formErrorsRef.current, ...result });
1580
2075
  return;
1581
2076
  }
1582
2077
  }
@@ -1586,7 +2081,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1586
2081
  } else {
1587
2082
  setInternalStep(nextStep);
1588
2083
  }
1589
- }, [isMultiStep, validateStepOnNext, steps, currentStep, fields, formValues, validateVisibleFields, onValidationChange, internalErrors, controlledStep, onStepChange]);
2084
+ }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, runAsyncValidation]);
1590
2085
  const handleBack = useCallback2(() => {
1591
2086
  if (!isMultiStep) return;
1592
2087
  const prevStep = Math.max(currentStep - 1, 0);
@@ -1611,33 +2106,56 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1611
2106
  useImperativeHandle(ref, () => ({
1612
2107
  submit: handleSubmit,
1613
2108
  validate: () => {
1614
- const allVisible = fields.filter((f) => !f.visible || f.visible(formValues));
1615
- const { errors, hasErrors } = validateVisibleFields(allVisible);
1616
- setInternalErrors(errors);
2109
+ const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
2110
+ replaceErrors(errors);
1617
2111
  return { valid: !hasErrors, errors };
1618
2112
  },
1619
2113
  reset: () => {
1620
2114
  const fresh = computeInitialValues();
1621
2115
  if (values == null) setInternalValues(fresh);
1622
- setInternalErrors({});
1623
- setTouchedFields({});
1624
- initialSnapshot.current = JSON.stringify(fresh);
2116
+ replaceErrors({});
2117
+ initialSnapshot.current = deepClone(fresh);
2118
+ prevAutoSaveValues.current = deepClone(fresh);
1625
2119
  },
1626
2120
  getValues: () => formValues,
1627
2121
  isDirty: () => isDirty,
1628
2122
  setFieldValue: (name, value) => handleFieldChange(name, value),
1629
2123
  setFieldError: (name, message) => updateErrors({ [name]: message }),
1630
2124
  setErrors: (errors) => {
1631
- setInternalErrors(errors);
1632
- if (onValidationChange) onValidationChange(errors);
2125
+ replaceErrors(errors);
1633
2126
  }
1634
2127
  }));
2128
+ const setRepeaterSubFieldError = useCallback2(
2129
+ (fieldName, rowIdx, subFieldName, errorMessage) => {
2130
+ const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
2131
+ const merged = { ...formErrorsRef.current };
2132
+ if (errorMessage) {
2133
+ merged[key] = errorMessage;
2134
+ } else {
2135
+ delete merged[key];
2136
+ }
2137
+ const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
2138
+ const match = k.match(/\[(\d+)\]\./);
2139
+ const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
2140
+ return { key: k, row };
2141
+ }).sort((a, b) => a.row - b.row);
2142
+ if (subErrors.length > 0) {
2143
+ const first = subErrors[0];
2144
+ merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
2145
+ } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
2146
+ delete merged[fieldName];
2147
+ }
2148
+ replaceErrors(merged);
2149
+ },
2150
+ [replaceErrors]
2151
+ );
1635
2152
  const renderField = (field) => {
1636
2153
  const fieldValue = formValues[field.name];
1637
- const fieldError = internalErrors[field.name] || null;
2154
+ const fieldError = formErrors[field.name] || null;
1638
2155
  const hasError = !!fieldError;
1639
2156
  const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
1640
- const isReadOnly = field.readOnly || disabled || formReadOnly;
2157
+ const isReadOnly = field.readOnly || formReadOnly;
2158
+ const isDisabled = disabled || field.disabled || formReadOnly;
1641
2159
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
1642
2160
  if (field.type === "display") {
1643
2161
  if (field.render) {
@@ -1696,6 +2214,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1696
2214
  tooltip: field.tooltip,
1697
2215
  required: isRequired,
1698
2216
  readOnly: isReadOnly,
2217
+ disabled: isDisabled,
1699
2218
  error: hasError,
1700
2219
  validationMessage: fieldError || void 0,
1701
2220
  ...field.loading || validatingFields[field.name] ? { loading: true } : {},
@@ -1828,6 +2347,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1828
2347
  maxValidationMessage: field.maxValidationMessage,
1829
2348
  onChange: (v) => {
1830
2349
  handleFieldChange(field.name, { ...fieldValue, date: v, time: timeVal });
2350
+ },
2351
+ onBlur: (v) => {
2352
+ handleFieldBlur(field.name, { ...fieldValue, date: v, time: timeVal });
1831
2353
  }
1832
2354
  }
1833
2355
  )), /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, /* @__PURE__ */ React2.createElement(
@@ -1838,12 +2360,16 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1838
2360
  description: field.description,
1839
2361
  tooltip: field.tooltip,
1840
2362
  readOnly: isReadOnly,
2363
+ disabled: isDisabled,
1841
2364
  error: hasError,
1842
2365
  value: timeVal,
1843
2366
  interval: field.interval,
1844
2367
  timezone: field.timezone,
1845
2368
  onChange: (v) => {
1846
2369
  handleFieldChange(field.name, { ...fieldValue, date: dateVal, time: v });
2370
+ },
2371
+ onBlur: (v) => {
2372
+ handleFieldBlur(field.name, { ...fieldValue, date: dateVal, time: v });
1847
2373
  }
1848
2374
  }
1849
2375
  )));
@@ -1881,6 +2407,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1881
2407
  textChecked: field.textChecked,
1882
2408
  textUnchecked: field.textUnchecked,
1883
2409
  readonly: isReadOnly,
2410
+ disabled: isDisabled,
1884
2411
  onChange: fieldOnChange,
1885
2412
  ...field.fieldProps || {}
1886
2413
  }
@@ -1893,6 +2420,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1893
2420
  checked: !!fieldValue,
1894
2421
  description: field.description,
1895
2422
  readOnly: isReadOnly,
2423
+ disabled: isDisabled,
1896
2424
  inline: field.inline,
1897
2425
  variant: field.variant,
1898
2426
  onChange: fieldOnChange,
@@ -1929,61 +2457,140 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1929
2457
  case "repeater": {
1930
2458
  const rows = Array.isArray(fieldValue) ? fieldValue : [];
1931
2459
  const subFields = field.fields || [];
1932
- const minRows = field.min || 0;
1933
- const maxRows = field.max || Infinity;
1934
- const canAdd = rows.length < maxRows && !isReadOnly;
1935
- const canRemove = rows.length > minRows && !isReadOnly;
2460
+ const minRows = typeof field.min === "number" ? field.min : 0;
2461
+ const maxRows = typeof field.max === "number" ? field.max : Infinity;
2462
+ const repeaterProps = field.repeaterProps || {};
2463
+ const renderAddControl = repeaterProps.renderAdd;
2464
+ const renderRemoveControl = repeaterProps.renderRemove;
2465
+ const renderMoveUpControl = repeaterProps.renderMoveUp;
2466
+ const renderMoveDownControl = repeaterProps.renderMoveDown;
2467
+ const addLabel = repeaterProps.addLabel || "Add";
2468
+ const removeLabel = repeaterProps.removeLabel || "Remove";
2469
+ const moveUpLabel = repeaterProps.moveUpLabel || "Up";
2470
+ const moveDownLabel = repeaterProps.moveDownLabel || "Down";
2471
+ const canEditRows = !isReadOnly && !isDisabled;
2472
+ const canAdd = rows.length < maxRows && canEditRows;
2473
+ const canRemove = rows.length > minRows && canEditRows;
2474
+ const canReorder = !!repeaterProps.reorderable && canEditRows;
2475
+ const repeaterHasNestedErrors = Object.keys(formErrors).some(
2476
+ (k) => k.startsWith(`${field.name}[`)
2477
+ );
2478
+ const firstNestedErrorKey = Object.keys(formErrors).find(
2479
+ (k) => k.startsWith(`${field.name}[`)
2480
+ );
2481
+ const repeaterErrorMessage = fieldError || (firstNestedErrorKey ? formErrors[firstNestedErrorKey] : null);
2482
+ const repeaterHasError = !!fieldError || repeaterHasNestedErrors;
1936
2483
  const addRow = () => {
1937
2484
  const emptyRow = {};
1938
2485
  for (const sf of subFields) {
1939
- emptyRow[sf.name] = sf.defaultValue !== void 0 ? sf.defaultValue : getEmptyValue(sf);
2486
+ emptyRow[sf.name] = sf.defaultValue !== void 0 ? sf.defaultValue : getFieldEmptyValue(sf);
1940
2487
  }
1941
2488
  handleFieldChange(field.name, [...rows, emptyRow]);
1942
2489
  };
1943
2490
  const removeRow = (idx) => {
1944
2491
  handleFieldChange(field.name, rows.filter((_, i) => i !== idx));
1945
2492
  };
1946
- const updateRow = (idx, subName, subValue) => {
2493
+ const moveRow = (fromIndex, toIndex) => {
2494
+ if (toIndex < 0 || toIndex >= rows.length || toIndex === fromIndex) return;
2495
+ const updated = [...rows];
2496
+ const [moved] = updated.splice(fromIndex, 1);
2497
+ updated.splice(toIndex, 0, moved);
2498
+ handleFieldChange(field.name, updated);
2499
+ };
2500
+ const validateSubField = (rowIdx, subField, subValue, nextRows) => {
2501
+ const rowValues = { ...formValues, [field.name]: nextRows };
2502
+ const err = runValidators(subValue, subField, rowValues, fieldTypes);
2503
+ setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
2504
+ };
2505
+ const handleSubFieldChange = (rowIdx, subField, subValue) => {
1947
2506
  const updated = rows.map(
1948
- (row, i) => i === idx ? { ...row, [subName]: subValue } : row
2507
+ (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
1949
2508
  );
1950
2509
  handleFieldChange(field.name, updated);
2510
+ if (validateOnChange) {
2511
+ validateSubField(rowIdx, subField, subValue, updated);
2512
+ }
2513
+ };
2514
+ const handleSubFieldBlur = (rowIdx, subField, subValue) => {
2515
+ if (!validateOnBlur) return;
2516
+ const nextRows = rows.map(
2517
+ (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
2518
+ );
2519
+ validateSubField(rowIdx, subField, subValue, nextRows);
1951
2520
  };
1952
- 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: rowIdx, direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
2521
+ 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) => {
1953
2522
  const sfValue = row[sf.name];
1954
2523
  const sfLabel = rowIdx === 0 ? sf.label : void 0;
1955
- const sfOptions = resolveOptions(sf, formValues);
2524
+ const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
2525
+ const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
1956
2526
  const sfProps = {
1957
2527
  name: `${field.name}-${rowIdx}-${sf.name}`,
1958
2528
  label: sfLabel,
1959
2529
  placeholder: sf.placeholder,
1960
- readOnly: isReadOnly,
2530
+ readOnly: sf.readOnly || isReadOnly,
2531
+ disabled: sf.disabled || isDisabled,
2532
+ error: !!sfError,
2533
+ validationMessage: sfError || void 0,
1961
2534
  ...sf.fieldProps || {}
1962
2535
  };
1963
2536
  let sfElement;
1964
2537
  switch (sf.type) {
1965
2538
  case "select":
1966
- sfElement = /* @__PURE__ */ React2.createElement(Select2, { ...sfProps, value: sfValue, options: sfOptions, onChange: (v) => updateRow(rowIdx, sf.name, v) });
2539
+ sfElement = /* @__PURE__ */ React2.createElement(
2540
+ Select2,
2541
+ {
2542
+ ...sfProps,
2543
+ value: sfValue,
2544
+ options: sfOptions,
2545
+ onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
2546
+ onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
2547
+ }
2548
+ );
1967
2549
  break;
1968
2550
  case "number":
1969
- sfElement = /* @__PURE__ */ React2.createElement(NumberInput2, { ...sfProps, value: sfValue, onChange: (v) => updateRow(rowIdx, sf.name, v) });
2551
+ sfElement = /* @__PURE__ */ React2.createElement(
2552
+ NumberInput2,
2553
+ {
2554
+ ...sfProps,
2555
+ value: sfValue,
2556
+ onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
2557
+ onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
2558
+ }
2559
+ );
1970
2560
  break;
1971
2561
  case "checkbox":
1972
- sfElement = /* @__PURE__ */ React2.createElement(Checkbox2, { ...sfProps, checked: !!sfValue, onChange: (v) => updateRow(rowIdx, sf.name, v) }, sf.label);
2562
+ sfElement = /* @__PURE__ */ React2.createElement(
2563
+ Checkbox2,
2564
+ {
2565
+ ...sfProps,
2566
+ checked: !!sfValue,
2567
+ onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
2568
+ onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
2569
+ },
2570
+ sf.label
2571
+ );
1973
2572
  break;
1974
2573
  default:
1975
- sfElement = /* @__PURE__ */ React2.createElement(Input2, { ...sfProps, value: sfValue || "", onChange: (v) => updateRow(rowIdx, sf.name, v) });
2574
+ sfElement = /* @__PURE__ */ React2.createElement(
2575
+ Input2,
2576
+ {
2577
+ ...sfProps,
2578
+ value: sfValue || "",
2579
+ onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
2580
+ onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
2581
+ }
2582
+ );
1976
2583
  }
1977
2584
  return /* @__PURE__ */ React2.createElement(Box2, { key: sf.name, flex: 1 }, sfElement);
1978
- }), canRemove && /* @__PURE__ */ React2.createElement(
2585
+ }), /* @__PURE__ */ React2.createElement(Inline, { gap: "xs" }, canReorder && rowIdx > 0 && (renderMoveUpControl ? renderMoveUpControl({ index: rowIdx, onClick: () => moveRow(rowIdx, rowIdx - 1) }) : /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", size: "sm", onClick: () => moveRow(rowIdx, rowIdx - 1) }, moveUpLabel)), canReorder && rowIdx < rows.length - 1 && (renderMoveDownControl ? renderMoveDownControl({ index: rowIdx, onClick: () => moveRow(rowIdx, rowIdx + 1) }) : /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", size: "sm", onClick: () => moveRow(rowIdx, rowIdx + 1) }, moveDownLabel)), canRemove && (renderRemoveControl ? renderRemoveControl({ index: rowIdx, onClick: () => removeRow(rowIdx) }) : /* @__PURE__ */ React2.createElement(
1979
2586
  Button2,
1980
2587
  {
1981
2588
  variant: "secondary",
1982
- size: "xs",
2589
+ size: "md",
1983
2590
  onClick: () => removeRow(rowIdx)
1984
2591
  },
1985
- "Remove"
1986
- ))), canAdd && /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", size: "sm", onClick: addRow }, "+ Add"), hasError && /* @__PURE__ */ React2.createElement(Text2, { variant: "microcopy" }, fieldError));
2592
+ removeLabel
2593
+ ))))), canAdd && (renderAddControl ? renderAddControl({ onClick: addRow, count: rows.length }) : /* @__PURE__ */ React2.createElement(Link2, { onClick: addRow }, /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", align: "center", gap: "flush" }, /* @__PURE__ */ React2.createElement(Icon2, { name: "add" }), /* @__PURE__ */ React2.createElement(Text2, { format: { fontWeight: "demibold" } }, addLabel)))), repeaterHasError && repeaterErrorMessage && /* @__PURE__ */ React2.createElement(Text2, { variant: "microcopy" }, repeaterErrorMessage));
1987
2594
  }
1988
2595
  default:
1989
2596
  return /* @__PURE__ */ React2.createElement(
@@ -2003,15 +2610,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2003
2610
  if (field.width === "full" && columns > 1) return columns;
2004
2611
  return 1;
2005
2612
  };
2006
- const getDependents = (parentField) => visibleFields.filter((f) => f.dependsOn === parentField.name && f.name !== parentField.name);
2007
- const isDependent = (field) => field.dependsOn && visibleFields.some((f) => f.name === field.dependsOn && f.name !== field.name);
2613
+ const getDependents = (parentField) => visibleFields.filter(
2614
+ (f) => getDependsOnName(f) === parentField.name && f.name !== parentField.name && getDependsOnDisplay(f) === "grouped"
2615
+ );
2616
+ const isDependent = (field) => getDependsOnName(field) && getDependsOnDisplay(field) === "grouped" && visibleFields.some((f) => f.name === getDependsOnName(field) && f.name !== field.name);
2008
2617
  const renderDependentGroup = (parentField, dependents) => {
2009
- const firstWithLabel = dependents.find((f) => f.dependsOnLabel) || dependents[0];
2010
- const firstWithMessage = dependents.find((f) => f.dependsOnMessage) || dependents[0];
2011
- const groupLabel = firstWithLabel.dependsOnLabel || "Dependent properties";
2012
- const rawMessage = firstWithMessage.dependsOnMessage;
2618
+ const firstWithLabel = dependents.find((f) => getDependsOnLabel(f)) || dependents[0];
2619
+ const firstWithMessage = dependents.find((f) => getDependsOnMessage(f)) || dependents[0];
2620
+ const groupLabel = getDependsOnLabel(firstWithLabel) || "Dependent properties";
2621
+ const rawMessage = getDependsOnMessage(firstWithMessage);
2013
2622
  const tooltipMessage = typeof rawMessage === "function" ? rawMessage(parentField.label) : rawMessage || "";
2014
- return /* @__PURE__ */ React2.createElement(Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React2.createElement(Text2, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ React2.createElement(Link2, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React2.createElement(Tooltip, null, tooltipMessage) }, /* @__PURE__ */ React2.createElement(Icon2, { name: "info" })))), dependents.map((dep) => /* @__PURE__ */ React2.createElement(React2.Fragment, { key: dep.name }, renderField(dep)))));
2623
+ return /* @__PURE__ */ React2.createElement(Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React2.createElement(Text2, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ React2.createElement(Link2, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React2.createElement(Tooltip, null, tooltipMessage) }, /* @__PURE__ */ React2.createElement(Icon2, { name: "info" })))), renderFieldSubset(dependents)));
2015
2624
  };
2016
2625
  const renderGridLayout = (fieldSubset) => {
2017
2626
  const fieldList = fieldSubset || visibleFields;
@@ -2104,7 +2713,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2104
2713
  let i = 0;
2105
2714
  while (i < fieldList.length) {
2106
2715
  const field = fieldList[i];
2107
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !field.dependsOn) {
2716
+ if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
2108
2717
  rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
2109
2718
  i += 2;
2110
2719
  } else {
@@ -2140,9 +2749,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2140
2749
  let batch = [];
2141
2750
  const flushBatch = () => {
2142
2751
  if (batch.length === 0) return;
2143
- elements.push(
2144
- /* @__PURE__ */ React2.createElement(AutoGrid, { key: `ag-${batch[0].name}`, columnWidth, flexible: true, gap }, batch.map((f) => /* @__PURE__ */ React2.createElement(React2.Fragment, { key: f.name }, renderField(f))))
2145
- );
2752
+ const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
2753
+ for (const chunk of chunks) {
2754
+ elements.push(
2755
+ /* @__PURE__ */ React2.createElement(AutoGrid, { key: `ag-${chunk[0].name}`, columnWidth, flexible: true, gap }, chunk.map((f) => /* @__PURE__ */ React2.createElement(React2.Fragment, { key: f.name }, renderField(f))))
2756
+ );
2757
+ }
2146
2758
  batch = [];
2147
2759
  };
2148
2760
  for (const field of fieldList) {
@@ -2225,7 +2837,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2225
2837
  );
2226
2838
  if (sec.info) {
2227
2839
  elements.push(
2228
- /* @__PURE__ */ React2.createElement(Flex2, { key: sec.id, direction: "row", align: "start", gap: "flush" }, /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, accordion), /* @__PURE__ */ React2.createElement(Link2, { overlay: /* @__PURE__ */ React2.createElement(Tooltip, null, sec.info) }, /* @__PURE__ */ React2.createElement(Icon2, { name: "info", size: "sm", screenReaderText: sec.info })))
2840
+ /* @__PURE__ */ React2.createElement(Flex2, { key: sec.id, direction: "row", align: "start", justify: "start", gap: "flush" }, /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, accordion), /* @__PURE__ */ React2.createElement(Link2, { variant: "dark", overlay: /* @__PURE__ */ React2.createElement(Tooltip, null, sec.info) }, /* @__PURE__ */ React2.createElement(Icon2, { name: "info", size: "sm", screenReaderText: sec.info })))
2229
2841
  );
2230
2842
  } else {
2231
2843
  elements.push(accordion);
@@ -2253,8 +2865,30 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2253
2865
  if (submitPosition === "none" || formReadOnly) return null;
2254
2866
  const isLastStep = !isMultiStep || currentStep === steps.length - 1;
2255
2867
  const isFirstStep = !isMultiStep || currentStep === 0;
2868
+ const buttonContext = {
2869
+ isMultiStep,
2870
+ isFirstStep,
2871
+ isLastStep,
2872
+ currentStep,
2873
+ totalSteps: isMultiStep ? steps.length : 1,
2874
+ disabled,
2875
+ loading: isLoading,
2876
+ labels: {
2877
+ submit: submitButtonLabel,
2878
+ cancel: cancelButtonLabel,
2879
+ back: backButtonLabel,
2880
+ next: nextButtonLabel
2881
+ },
2882
+ onBack: handleBack,
2883
+ onNext: handleNext,
2884
+ onCancel,
2885
+ onSubmit: handleSubmit
2886
+ };
2887
+ if (renderButtonsProp) {
2888
+ return renderButtonsProp(buttonContext);
2889
+ }
2256
2890
  if (isMultiStep) {
2257
- return /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", onClick: handleBack, disabled }, "Back") : showCancel ? /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", onClick: onCancel, disabled }, cancelLabel) : /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Inline, { gap: "small" }, /* @__PURE__ */ React2.createElement(Text2, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ React2.createElement(
2891
+ return /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", onClick: handleBack, disabled }, backButtonLabel) : showCancel ? /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel) : /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Inline, { gap: "small" }, /* @__PURE__ */ React2.createElement(Text2, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ React2.createElement(
2258
2892
  LoadingButton,
2259
2893
  {
2260
2894
  variant: submitVariant,
@@ -2262,10 +2896,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2262
2896
  onClick: handleSubmit,
2263
2897
  disabled
2264
2898
  },
2265
- submitLabel
2266
- ) : /* @__PURE__ */ React2.createElement(Button2, { variant: "primary", onClick: handleNext, disabled }, "Next")));
2899
+ submitButtonLabel
2900
+ ) : /* @__PURE__ */ React2.createElement(Button2, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
2267
2901
  }
2268
- return /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", justify: showCancel ? "between" : "start", gap: "sm" }, showCancel && /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", onClick: onCancel, disabled }, cancelLabel), /* @__PURE__ */ React2.createElement(
2902
+ return /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", justify: showCancel ? "between" : "start", gap: "sm" }, showCancel && /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ React2.createElement(
2269
2903
  LoadingButton,
2270
2904
  {
2271
2905
  variant: submitVariant,
@@ -2274,7 +2908,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2274
2908
  onClick: noFormWrapper ? handleSubmit : void 0,
2275
2909
  disabled
2276
2910
  },
2277
- submitLabel
2911
+ submitButtonLabel
2278
2912
  ));
2279
2913
  };
2280
2914
  const formContent = /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, isMultiStep && showStepIndicator && /* @__PURE__ */ React2.createElement(
@@ -2283,7 +2917,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2283
2917
  currentStep,
2284
2918
  stepNames: steps.map((s) => s.title)
2285
2919
  }
2286
- ), formReadOnly && readOnlyMessage && /* @__PURE__ */ React2.createElement(Alert, { title: "Read Only", variant: "warning" }, readOnlyMessage), formError && /* @__PURE__ */ React2.createElement(Alert, { title: "Error", variant: "danger" }, typeof formError === "string" ? formError : void 0), formSuccess && /* @__PURE__ */ React2.createElement(Alert, { title: "Success", variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
2920
+ ), formReadOnly && readOnlyMessage && /* @__PURE__ */ React2.createElement(Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage), !addAlert && formError && /* @__PURE__ */ React2.createElement(Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), !addAlert && formSuccess && /* @__PURE__ */ React2.createElement(Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
2287
2921
  values: formValues,
2288
2922
  goNext: handleNext,
2289
2923
  goBack: handleBack,
@@ -2295,7 +2929,15 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2295
2929
  if (noFormWrapper) {
2296
2930
  return formContent;
2297
2931
  }
2298
- return /* @__PURE__ */ React2.createElement(Form, { onSubmit: handleSubmit, autoComplete: props.autoComplete }, formContent);
2932
+ return /* @__PURE__ */ React2.createElement(
2933
+ Form,
2934
+ {
2935
+ ...formProps || {},
2936
+ onSubmit: handleSubmit,
2937
+ autoComplete
2938
+ },
2939
+ formContent
2940
+ );
2299
2941
  });
2300
2942
  export {
2301
2943
  DataTable,