hs-uix 1.0.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/form.js CHANGED
@@ -132,7 +132,6 @@ var runDefaultFieldValidator = (value, field, allValues) => {
132
132
  if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
133
133
  break;
134
134
  case "datetime": {
135
- if (isDateValueObject(value)) break;
136
135
  if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
137
136
  const hasDate = value.date !== void 0;
138
137
  const hasTime = value.time !== void 0;
@@ -198,12 +197,14 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
198
197
  };
199
198
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
200
199
  const includeCustomValidators = options.includeCustomValidators !== false;
201
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
200
+ const msg = options.messages || {};
201
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
202
202
  const isRequired = resolveRequired(field, allValues);
203
203
  const plugin = fieldTypes && fieldTypes[field.type];
204
204
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
205
205
  if (isRequired && empty) {
206
- return `${field.label} is required`;
206
+ const fn = msg.required || ((label) => `${label} is required`);
207
+ return typeof fn === "function" ? fn(field.label) : fn;
207
208
  }
208
209
  if (empty) return null;
209
210
  if (field.useDefaultValidators !== false) {
@@ -212,23 +213,27 @@ var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
212
213
  }
213
214
  if (field.pattern && typeof value === "string") {
214
215
  if (!field.pattern.test(value)) {
215
- return field.patternMessage || "Invalid format";
216
+ return field.patternMessage || msg.invalidFormat || "Invalid format";
216
217
  }
217
218
  }
218
219
  if (typeof value === "string") {
219
220
  if (field.minLength != null && value.length < field.minLength) {
220
- return `Must be at least ${field.minLength} characters`;
221
+ const fn = msg.minLength || ((min) => `Must be at least ${min} characters`);
222
+ return typeof fn === "function" ? fn(field.minLength) : fn;
221
223
  }
222
224
  if (field.maxLength != null && value.length > field.maxLength) {
223
- return `Must be no more than ${field.maxLength} characters`;
225
+ const fn = msg.maxLength || ((max) => `Must be no more than ${max} characters`);
226
+ return typeof fn === "function" ? fn(field.maxLength) : fn;
224
227
  }
225
228
  }
226
229
  if (typeof value === "number") {
227
230
  if (field.min != null && value < field.min) {
228
- return `Must be at least ${field.min}`;
231
+ const fn = msg.minValue || ((min) => `Must be at least ${min}`);
232
+ return typeof fn === "function" ? fn(field.min) : fn;
229
233
  }
230
234
  if (field.max != null && value > field.max) {
231
- return `Must be no more than ${field.max}`;
235
+ const fn = msg.maxValue || ((max) => `Must be no more than ${max}`);
236
+ return typeof fn === "function" ? fn(field.max) : fn;
232
237
  }
233
238
  }
234
239
  if (field.type === "date" && isDateValueObject(value)) {
@@ -340,6 +345,8 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
340
345
  // (values, { reset, rawValues }) => void | Promise
341
346
  transformValues,
342
347
  // (values) => values — reshape before submit
348
+ transformInitialValues,
349
+ // (rawInitialValues) => values — reshape raw data on load
343
350
  onBeforeSubmit,
344
351
  // (values) => boolean | Promise<boolean> — intercept submit
345
352
  onSubmitSuccess,
@@ -434,8 +441,18 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
434
441
  // string — warning alert when readOnly
435
442
  alerts,
436
443
  // { addAlert, readOnlyTitle, errorTitle, successTitle }
437
- errors: controlledErrors
444
+ errors: controlledErrors,
438
445
  // controlled validation errors
446
+ showReadOnlyAlert = true,
447
+ // show warning Alert when readOnly is true
448
+ showInlineAlerts = true,
449
+ // show inline form-level error/success Alerts
450
+ renderReadOnlyAlert,
451
+ // (context: { title, message }) => ReactNode — custom readOnly alert renderer
452
+ renderFieldError,
453
+ // (error: string, field: object) => ReactNode — custom field error renderer
454
+ defaultCurrency = "USD"
455
+ // form-level default ISO 4217 currency code for currency fields
439
456
  } = props;
440
457
  const {
441
458
  onDirtyChange,
@@ -447,6 +464,23 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
447
464
  const cancelButtonLabel = (labels == null ? void 0 : labels.cancel) || "Cancel";
448
465
  const backButtonLabel = (labels == null ? void 0 : labels.back) || "Back";
449
466
  const nextButtonLabel = (labels == null ? void 0 : labels.next) || "Next";
467
+ const requiredMessage = (labels == null ? void 0 : labels.required) || ((label) => `${label} is required`);
468
+ const invalidFormatMessage = (labels == null ? void 0 : labels.invalidFormat) || "Invalid format";
469
+ const minLengthMessage = (labels == null ? void 0 : labels.minLength) || ((min) => `Must be at least ${min} characters`);
470
+ const maxLengthMessage = (labels == null ? void 0 : labels.maxLength) || ((max) => `Must be no more than ${max} characters`);
471
+ const minValueMessage = (labels == null ? void 0 : labels.minValue) || ((min) => `Must be at least ${min}`);
472
+ const maxValueMessage = (labels == null ? void 0 : labels.maxValue) || ((max) => `Must be no more than ${max}`);
473
+ const dependentPropertiesLabel = (labels == null ? void 0 : labels.dependentProperties) || "Dependent properties";
474
+ const repeaterAddLabel = (labels == null ? void 0 : labels.repeaterAdd) || "Add";
475
+ const repeaterRemoveLabel = (labels == null ? void 0 : labels.repeaterRemove) || "Remove";
476
+ const validationMessages = labels ? {
477
+ required: requiredMessage,
478
+ invalidFormat: invalidFormatMessage,
479
+ minLength: minLengthMessage,
480
+ maxLength: maxLengthMessage,
481
+ minValue: minValueMessage,
482
+ maxValue: maxValueMessage
483
+ } : void 0;
450
484
  const addAlert = alerts == null ? void 0 : alerts.addAlert;
451
485
  const readOnlyTitle = (alerts == null ? void 0 : alerts.readOnlyTitle) || "Read Only";
452
486
  const errorTitle = (alerts == null ? void 0 : alerts.errorTitle) || "Error";
@@ -476,12 +510,27 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
476
510
  prevSuccessRef.current = formSuccess;
477
511
  }, [addAlert, formSuccess, successTitle]);
478
512
  const computeInitialValues = () => {
513
+ const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
479
514
  const vals = {};
480
515
  for (const field of fields) {
481
516
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
517
+ if (field.type === "fieldGroup" && field.items && field.fields) {
518
+ for (const item of field.items) {
519
+ const subFields = field.fields(item);
520
+ for (const sf of subFields) {
521
+ const plugin2 = fieldTypes && fieldTypes[sf.type];
522
+ const emptyValue2 = plugin2 && plugin2.getEmptyValue ? plugin2.getEmptyValue() : getEmptyValue(sf);
523
+ let init2 = resolved && resolved[sf.name] !== void 0 ? resolved[sf.name] : sf.defaultValue !== void 0 ? sf.defaultValue : emptyValue2;
524
+ if (sf.transformIn) init2 = sf.transformIn(init2);
525
+ vals[sf.name] = init2;
526
+ }
527
+ }
528
+ continue;
529
+ }
482
530
  const plugin = fieldTypes && fieldTypes[field.type];
483
531
  const emptyValue = plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
484
- const init = initialValues && initialValues[field.name] !== void 0 ? initialValues[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
532
+ let init = resolved && resolved[field.name] !== void 0 ? resolved[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
533
+ if (field.transformIn) init = field.transformIn(init);
485
534
  vals[field.name] = init;
486
535
  }
487
536
  return vals;
@@ -514,7 +563,14 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
514
563
  formErrorsRef.current = formErrors;
515
564
  const fieldByName = (0, import_react.useMemo)(() => {
516
565
  const map = /* @__PURE__ */ new Map();
517
- for (const field of fields) map.set(field.name, field);
566
+ for (const field of fields) {
567
+ map.set(field.name, field);
568
+ if (field.type === "fieldGroup" && field.items && field.fields) {
569
+ for (const item of field.items) {
570
+ for (const sf of field.fields(item)) map.set(sf.name, sf);
571
+ }
572
+ }
573
+ }
518
574
  return map;
519
575
  }, [fields]);
520
576
  const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
@@ -685,7 +741,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
685
741
  const rowValues = { ...allValues, [field.name]: rows };
686
742
  subFields.forEach((subField) => {
687
743
  if (subField.visible && !subField.visible(rowValues)) return;
688
- const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes);
744
+ const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes, { messages: validationMessages });
689
745
  if (!err) return;
690
746
  const key = getRepeaterErrorKey(field.name, rowIdx, subField.name);
691
747
  errors[key] = err;
@@ -712,9 +768,9 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
712
768
  );
713
769
  return repeaterResult.errors[name] || null;
714
770
  }
715
- return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes);
771
+ return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes, { messages: validationMessages });
716
772
  },
717
- [fieldByName, formValues, validateRepeaterField, fieldTypes]
773
+ [fieldByName, formValues, validateRepeaterField, fieldTypes, validationMessages]
718
774
  );
719
775
  const validateVisibleFields = (0, import_react.useCallback)(
720
776
  (fieldSubset) => {
@@ -730,7 +786,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
730
786
  }
731
787
  continue;
732
788
  }
733
- const err = runValidators(formValues[field.name], field, formValues, fieldTypes);
789
+ const err = runValidators(formValues[field.name], field, formValues, fieldTypes, { messages: validationMessages });
734
790
  if (err) {
735
791
  errors[field.name] = err;
736
792
  hasErrors = true;
@@ -738,14 +794,14 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
738
794
  }
739
795
  return { errors, hasErrors };
740
796
  },
741
- [visibleFields, formValues, validateRepeaterField, fieldTypes]
797
+ [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
742
798
  );
743
799
  const runAsyncValidation = (0, import_react.useCallback)(
744
800
  (name, value) => {
745
801
  const field = fieldByName.get(name);
746
802
  if (!field || field.type === "repeater") return null;
747
803
  const val = value != null ? value : formValues[name];
748
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false });
804
+ const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
749
805
  const prevController = asyncAbortRef.current.get(name);
750
806
  if (prevController) prevController.abort();
751
807
  asyncAbortRef.current.delete(name);
@@ -936,23 +992,27 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
936
992
  );
937
993
  const handleFieldInput = (0, import_react.useCallback)(
938
994
  (name, value) => {
995
+ handleFieldChange(name, value);
939
996
  if (!validateOnChange) return;
940
997
  const err = validateField(name, value);
941
998
  updateErrors({ [name]: err });
942
999
  },
943
- [validateOnChange, validateField, updateErrors]
1000
+ [validateOnChange, validateField, updateErrors, handleFieldChange]
944
1001
  );
945
1002
  const handleFieldBlur = (0, import_react.useCallback)(
946
1003
  (name, value) => {
947
- if (!validateOnBlur) return;
948
1004
  const resolvedValue = value != null ? value : formValuesRef.current[name];
1005
+ if (value != null && value !== formValuesRef.current[name]) {
1006
+ handleFieldChange(name, value);
1007
+ }
1008
+ if (!validateOnBlur) return;
949
1009
  const err = validateField(name, resolvedValue);
950
1010
  updateErrors({ [name]: err });
951
1011
  if (!err) {
952
1012
  triggerAsyncValidation(name, resolvedValue);
953
1013
  }
954
1014
  },
955
- [validateOnBlur, validateField, updateErrors, triggerAsyncValidation]
1015
+ [validateOnBlur, validateField, updateErrors, triggerAsyncValidation, handleFieldChange]
956
1016
  );
957
1017
  const handleSubmit = (0, import_react.useCallback)(
958
1018
  async (e) => {
@@ -985,8 +1045,17 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
985
1045
  const rawValues = {};
986
1046
  for (const key of Object.keys(formValues)) {
987
1047
  const f = fieldByName.get(key);
988
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
989
- rawValues[key] = formValues[key];
1048
+ if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
1049
+ rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
1050
+ }
1051
+ for (const f of fields) {
1052
+ if (f.type !== "fieldGroup" || !f.items || !f.fields) continue;
1053
+ for (const item of f.items) {
1054
+ for (const sf of f.fields(item)) {
1055
+ if (formValues[sf.name] === void 0) continue;
1056
+ rawValues[sf.name] = sf.transformOut ? sf.transformOut(formValues[sf.name]) : formValues[sf.name];
1057
+ }
1058
+ }
990
1059
  }
991
1060
  const submitValues = transformValues ? transformValues(rawValues) : rawValues;
992
1061
  if (onBeforeSubmit) {
@@ -1114,6 +1183,12 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1114
1183
  [replaceErrors]
1115
1184
  );
1116
1185
  const renderField = (field) => {
1186
+ const fieldError = formErrors[field.name] || null;
1187
+ const rendered = renderFieldInner(field);
1188
+ if (!renderFieldError || !fieldError) return rendered;
1189
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, rendered, renderFieldError(fieldError, field));
1190
+ };
1191
+ const renderFieldInner = (field) => {
1117
1192
  const fieldValue = formValues[field.name];
1118
1193
  const fieldError = formErrors[field.name] || null;
1119
1194
  const hasError = !!fieldError;
@@ -1123,10 +1198,125 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1123
1198
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
1124
1199
  if (field.type === "display") {
1125
1200
  if (field.render) {
1126
- return field.render({ allValues: formValues });
1201
+ return field.render({
1202
+ allValues: formValues,
1203
+ setFieldValue: (name, value) => handleFieldChange(name, value),
1204
+ setFieldError: (name, message) => updateErrors({ [name]: message })
1205
+ });
1127
1206
  }
1128
1207
  return null;
1129
1208
  }
1209
+ if (field.type === "fieldGroup") {
1210
+ const items = field.items || [];
1211
+ const fieldsFn = field.fields;
1212
+ if (!fieldsFn) return null;
1213
+ const groupColumns = field.columns || 1;
1214
+ const showItemLabel = field.showItemLabel !== false;
1215
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { format: { fontWeight: "demibold" } }, field.label), field.description && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy" }, field.description), items.map((item, itemIdx) => {
1216
+ const subFields = fieldsFn(item);
1217
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { key: item.key || itemIdx, direction: "row", gap: "xs", align: "end" }, showItemLabel && item.label && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1 }, itemIdx === 0 ? /* @__PURE__ */ import_react.default.createElement(
1218
+ import_ui_extensions.Input,
1219
+ {
1220
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
1221
+ label: "\xA0",
1222
+ value: item.label,
1223
+ readOnly: true,
1224
+ disabled: true
1225
+ }
1226
+ ) : /* @__PURE__ */ import_react.default.createElement(
1227
+ import_ui_extensions.Input,
1228
+ {
1229
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
1230
+ value: item.label,
1231
+ readOnly: true,
1232
+ disabled: true
1233
+ }
1234
+ )), subFields.map((sf) => {
1235
+ const sfValue = formValues[sf.name];
1236
+ const sfError = formErrors[sf.name] || null;
1237
+ const sfLabel = itemIdx === 0 ? sf.label : void 0;
1238
+ const sfReadOnly = sf.readOnly || formReadOnly;
1239
+ const sfDisabled = disabled || sf.disabled || formReadOnly;
1240
+ const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
1241
+ const sfProps = {
1242
+ name: sf.name,
1243
+ label: sfLabel,
1244
+ placeholder: sf.placeholder,
1245
+ description: itemIdx === 0 ? sf.description : void 0,
1246
+ readOnly: sfReadOnly,
1247
+ disabled: sfDisabled,
1248
+ error: !!sfError,
1249
+ validationMessage: sfError || void 0,
1250
+ ...sf.fieldProps || {}
1251
+ };
1252
+ let sfElement;
1253
+ switch (sf.type) {
1254
+ case "select":
1255
+ sfElement = /* @__PURE__ */ import_react.default.createElement(
1256
+ import_ui_extensions.Select,
1257
+ {
1258
+ ...sfProps,
1259
+ value: sfValue,
1260
+ options: resolveOptions(sf, formValues),
1261
+ onChange: sfOnChange
1262
+ }
1263
+ );
1264
+ break;
1265
+ case "number":
1266
+ sfElement = /* @__PURE__ */ import_react.default.createElement(
1267
+ import_ui_extensions.NumberInput,
1268
+ {
1269
+ ...sfProps,
1270
+ value: sfValue,
1271
+ onChange: sfOnChange,
1272
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1273
+ }
1274
+ );
1275
+ break;
1276
+ case "toggle":
1277
+ sfElement = /* @__PURE__ */ import_react.default.createElement(
1278
+ import_ui_extensions.Toggle,
1279
+ {
1280
+ name: sf.name,
1281
+ label: sfLabel || sf.label,
1282
+ checked: !!sfValue,
1283
+ size: sf.size || "md",
1284
+ labelDisplay: sf.labelDisplay || "top",
1285
+ readonly: sfReadOnly,
1286
+ disabled: sfDisabled,
1287
+ onChange: sfOnChange,
1288
+ ...sf.fieldProps || {}
1289
+ }
1290
+ );
1291
+ break;
1292
+ case "time":
1293
+ sfElement = /* @__PURE__ */ import_react.default.createElement(
1294
+ import_ui_extensions.TimeInput,
1295
+ {
1296
+ ...sfProps,
1297
+ value: sfValue,
1298
+ interval: sf.interval,
1299
+ onChange: sfOnChange,
1300
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1301
+ }
1302
+ );
1303
+ break;
1304
+ default:
1305
+ sfElement = /* @__PURE__ */ import_react.default.createElement(
1306
+ import_ui_extensions.Input,
1307
+ {
1308
+ ...sfProps,
1309
+ value: sfValue || "",
1310
+ onChange: sfOnChange,
1311
+ onInput: (v) => handleFieldInput(sf.name, v),
1312
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1313
+ }
1314
+ );
1315
+ }
1316
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { key: sf.name, flex: 1 }, sfElement);
1317
+ }));
1318
+ }));
1319
+ }
1130
1320
  if (field.type === "crmPropertyList") {
1131
1321
  return /* @__PURE__ */ import_react.default.createElement(
1132
1322
  import_crm.CrmPropertyList,
@@ -1180,7 +1370,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1180
1370
  readOnly: isReadOnly,
1181
1371
  disabled: isDisabled,
1182
1372
  error: hasError,
1183
- validationMessage: fieldError || void 0,
1373
+ validationMessage: renderFieldError ? void 0 : fieldError || void 0,
1184
1374
  ...field.loading || validatingFields[field.name] ? { loading: true } : {},
1185
1375
  ...field.fieldProps || {}
1186
1376
  };
@@ -1250,7 +1440,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1250
1440
  import_ui_extensions.CurrencyInput,
1251
1441
  {
1252
1442
  ...commonProps,
1253
- currency: field.currency || "USD",
1443
+ currency: field.currency || defaultCurrency,
1254
1444
  value: fieldValue,
1255
1445
  min: field.min,
1256
1446
  max: field.max,
@@ -1428,8 +1618,8 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1428
1618
  const renderRemoveControl = repeaterProps.renderRemove;
1429
1619
  const renderMoveUpControl = repeaterProps.renderMoveUp;
1430
1620
  const renderMoveDownControl = repeaterProps.renderMoveDown;
1431
- const addLabel = repeaterProps.addLabel || "Add";
1432
- const removeLabel = repeaterProps.removeLabel || "Remove";
1621
+ const addLabel = repeaterProps.addLabel || repeaterAddLabel;
1622
+ const removeLabel = repeaterProps.removeLabel || repeaterRemoveLabel;
1433
1623
  const moveUpLabel = repeaterProps.moveUpLabel || "Up";
1434
1624
  const moveDownLabel = repeaterProps.moveDownLabel || "Down";
1435
1625
  const canEditRows = !isReadOnly && !isDisabled;
@@ -1463,7 +1653,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1463
1653
  };
1464
1654
  const validateSubField = (rowIdx, subField, subValue, nextRows) => {
1465
1655
  const rowValues = { ...formValues, [field.name]: nextRows };
1466
- const err = runValidators(subValue, subField, rowValues, fieldTypes);
1656
+ const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
1467
1657
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
1468
1658
  };
1469
1659
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
@@ -1581,7 +1771,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1581
1771
  const renderDependentGroup = (parentField, dependents) => {
1582
1772
  const firstWithLabel = dependents.find((f) => getDependsOnLabel(f)) || dependents[0];
1583
1773
  const firstWithMessage = dependents.find((f) => getDependsOnMessage(f)) || dependents[0];
1584
- const groupLabel = getDependsOnLabel(firstWithLabel) || "Dependent properties";
1774
+ const groupLabel = getDependsOnLabel(firstWithLabel) || dependentPropertiesLabel;
1585
1775
  const rawMessage = getDependsOnMessage(firstWithMessage);
1586
1776
  const tooltipMessage = typeof rawMessage === "function" ? rawMessage(parentField.label) : rawMessage || "";
1587
1777
  return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Tooltip, null, tooltipMessage) }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "info" })))), renderFieldSubset(dependents)));
@@ -1671,38 +1861,19 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1671
1861
  }
1672
1862
  return elements;
1673
1863
  };
1674
- const renderLegacyLayout = (fieldSubset) => {
1864
+ const renderSingleColumnLayout = (fieldSubset) => {
1675
1865
  const fieldList = fieldSubset || visibleFields;
1676
- const rows = [];
1677
- let i = 0;
1678
- while (i < fieldList.length) {
1679
- const field = fieldList[i];
1680
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
1681
- rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
1682
- i += 2;
1683
- } else {
1684
- rows.push({ type: "single", field });
1685
- i++;
1686
- }
1687
- }
1688
1866
  const elements = [];
1689
1867
  const processedDeps = /* @__PURE__ */ new Set();
1690
- for (const row of rows) {
1691
- if (row.type === "pair") {
1692
- elements.push(
1693
- /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { key: `pair-${row.fields[0].name}`, direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1 }, renderField(row.fields[0])), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1 }, renderField(row.fields[1])))
1694
- );
1695
- } else {
1696
- const field = row.field;
1697
- if (processedDeps.has(field.name)) continue;
1698
- elements.push(
1699
- /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, { key: field.name }, renderField(field))
1700
- );
1701
- const dependents = getDependents(field);
1702
- if (dependents.length > 0) {
1703
- for (const dep of dependents) processedDeps.add(dep.name);
1704
- elements.push(renderDependentGroup(field, dependents));
1705
- }
1868
+ for (const field of fieldList) {
1869
+ if (processedDeps.has(field.name)) continue;
1870
+ elements.push(
1871
+ /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, { key: field.name }, renderField(field))
1872
+ );
1873
+ const dependents = getDependents(field);
1874
+ if (dependents.length > 0) {
1875
+ for (const dep of dependents) processedDeps.add(dep.name);
1876
+ elements.push(renderDependentGroup(field, dependents));
1706
1877
  }
1707
1878
  }
1708
1879
  return elements;
@@ -1713,10 +1884,20 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1713
1884
  let batch = [];
1714
1885
  const flushBatch = () => {
1715
1886
  if (batch.length === 0) return;
1716
- const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
1717
- for (const chunk of chunks) {
1887
+ if (maxColumns) {
1888
+ const chunks = Array.from(
1889
+ { length: Math.ceil(batch.length / maxColumns) },
1890
+ (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)
1891
+ );
1892
+ for (const chunk of chunks) {
1893
+ const remainder = maxColumns - chunk.length;
1894
+ elements.push(
1895
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { key: `ag-${chunk[0].name}`, direction: "row", gap }, chunk.map((f) => /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { key: f.name, flex: 1 }, renderField(f))), remainder > 0 && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: remainder }))
1896
+ );
1897
+ }
1898
+ } else {
1718
1899
  elements.push(
1719
- /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.AutoGrid, { key: `ag-${chunk[0].name}`, columnWidth, flexible: true, gap }, chunk.map((f) => /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, { key: f.name }, renderField(f))))
1900
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.AutoGrid, { key: `ag-${batch[0].name}`, columnWidth, flexible: true, gap }, batch.map((f) => /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, { key: f.name }, renderField(f))))
1720
1901
  );
1721
1902
  }
1722
1903
  batch = [];
@@ -1775,7 +1956,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1775
1956
  if (layout && fieldSubset === visibleFields) return renderExplicitLayout();
1776
1957
  if (columnWidth) return renderAutoGridLayout(fieldSubset);
1777
1958
  if (columns > 1) return renderGridLayout(fieldSubset);
1778
- return renderLegacyLayout(fieldSubset);
1959
+ return renderSingleColumnLayout(fieldSubset);
1779
1960
  };
1780
1961
  const renderSections = () => {
1781
1962
  const hasSections = Array.isArray(sections) && sections.length > 0;
@@ -1788,7 +1969,8 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1788
1969
  for (const sec of sections) {
1789
1970
  const sectionFields = sec.fields ? visibleFields.filter((f) => sec.fields.includes(f.name)) : [];
1790
1971
  if (sectionFields.length === 0) continue;
1791
- const accordionContent = /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap }, renderFieldSubset(sectionFields));
1972
+ const sectionContext = { values: formValues, errors: formErrors };
1973
+ const accordionContent = /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields), sec.renderAfter && sec.renderAfter(sectionContext));
1792
1974
  const accordion = /* @__PURE__ */ import_react.default.createElement(
1793
1975
  import_ui_extensions.Accordion,
1794
1976
  {
@@ -1881,7 +2063,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1881
2063
  currentStep,
1882
2064
  stepNames: steps.map((s) => s.title)
1883
2065
  }
1884
- ), formReadOnly && readOnlyMessage && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage), !addAlert && formError && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), !addAlert && formSuccess && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
2066
+ ), showReadOnlyAlert && formReadOnly && readOnlyMessage && (renderReadOnlyAlert ? renderReadOnlyAlert({ title: readOnlyTitle, message: readOnlyMessage }) : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage)), showInlineAlerts && !addAlert && formError && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), showInlineAlerts && !addAlert && formSuccess && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
1885
2067
  values: formValues,
1886
2068
  goNext: handleNext,
1887
2069
  goBack: handleBack,