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.mjs CHANGED
@@ -136,7 +136,6 @@ var runDefaultFieldValidator = (value, field, allValues) => {
136
136
  if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
137
137
  break;
138
138
  case "datetime": {
139
- if (isDateValueObject(value)) break;
140
139
  if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
141
140
  const hasDate = value.date !== void 0;
142
141
  const hasTime = value.time !== void 0;
@@ -202,12 +201,14 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
202
201
  };
203
202
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
204
203
  const includeCustomValidators = options.includeCustomValidators !== false;
205
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
204
+ const msg = options.messages || {};
205
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
206
206
  const isRequired = resolveRequired(field, allValues);
207
207
  const plugin = fieldTypes && fieldTypes[field.type];
208
208
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
209
209
  if (isRequired && empty) {
210
- return `${field.label} is required`;
210
+ const fn = msg.required || ((label) => `${label} is required`);
211
+ return typeof fn === "function" ? fn(field.label) : fn;
211
212
  }
212
213
  if (empty) return null;
213
214
  if (field.useDefaultValidators !== false) {
@@ -216,23 +217,27 @@ var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
216
217
  }
217
218
  if (field.pattern && typeof value === "string") {
218
219
  if (!field.pattern.test(value)) {
219
- return field.patternMessage || "Invalid format";
220
+ return field.patternMessage || msg.invalidFormat || "Invalid format";
220
221
  }
221
222
  }
222
223
  if (typeof value === "string") {
223
224
  if (field.minLength != null && value.length < field.minLength) {
224
- return `Must be at least ${field.minLength} characters`;
225
+ const fn = msg.minLength || ((min) => `Must be at least ${min} characters`);
226
+ return typeof fn === "function" ? fn(field.minLength) : fn;
225
227
  }
226
228
  if (field.maxLength != null && value.length > field.maxLength) {
227
- return `Must be no more than ${field.maxLength} characters`;
229
+ const fn = msg.maxLength || ((max) => `Must be no more than ${max} characters`);
230
+ return typeof fn === "function" ? fn(field.maxLength) : fn;
228
231
  }
229
232
  }
230
233
  if (typeof value === "number") {
231
234
  if (field.min != null && value < field.min) {
232
- return `Must be at least ${field.min}`;
235
+ const fn = msg.minValue || ((min) => `Must be at least ${min}`);
236
+ return typeof fn === "function" ? fn(field.min) : fn;
233
237
  }
234
238
  if (field.max != null && value > field.max) {
235
- return `Must be no more than ${field.max}`;
239
+ const fn = msg.maxValue || ((max) => `Must be no more than ${max}`);
240
+ return typeof fn === "function" ? fn(field.max) : fn;
236
241
  }
237
242
  }
238
243
  if (field.type === "date" && isDateValueObject(value)) {
@@ -344,6 +349,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
344
349
  // (values, { reset, rawValues }) => void | Promise
345
350
  transformValues,
346
351
  // (values) => values — reshape before submit
352
+ transformInitialValues,
353
+ // (rawInitialValues) => values — reshape raw data on load
347
354
  onBeforeSubmit,
348
355
  // (values) => boolean | Promise<boolean> — intercept submit
349
356
  onSubmitSuccess,
@@ -438,8 +445,18 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
438
445
  // string — warning alert when readOnly
439
446
  alerts,
440
447
  // { addAlert, readOnlyTitle, errorTitle, successTitle }
441
- errors: controlledErrors
448
+ errors: controlledErrors,
442
449
  // controlled validation errors
450
+ showReadOnlyAlert = true,
451
+ // show warning Alert when readOnly is true
452
+ showInlineAlerts = true,
453
+ // show inline form-level error/success Alerts
454
+ renderReadOnlyAlert,
455
+ // (context: { title, message }) => ReactNode — custom readOnly alert renderer
456
+ renderFieldError,
457
+ // (error: string, field: object) => ReactNode — custom field error renderer
458
+ defaultCurrency = "USD"
459
+ // form-level default ISO 4217 currency code for currency fields
443
460
  } = props;
444
461
  const {
445
462
  onDirtyChange,
@@ -451,6 +468,23 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
451
468
  const cancelButtonLabel = (labels == null ? void 0 : labels.cancel) || "Cancel";
452
469
  const backButtonLabel = (labels == null ? void 0 : labels.back) || "Back";
453
470
  const nextButtonLabel = (labels == null ? void 0 : labels.next) || "Next";
471
+ const requiredMessage = (labels == null ? void 0 : labels.required) || ((label) => `${label} is required`);
472
+ const invalidFormatMessage = (labels == null ? void 0 : labels.invalidFormat) || "Invalid format";
473
+ const minLengthMessage = (labels == null ? void 0 : labels.minLength) || ((min) => `Must be at least ${min} characters`);
474
+ const maxLengthMessage = (labels == null ? void 0 : labels.maxLength) || ((max) => `Must be no more than ${max} characters`);
475
+ const minValueMessage = (labels == null ? void 0 : labels.minValue) || ((min) => `Must be at least ${min}`);
476
+ const maxValueMessage = (labels == null ? void 0 : labels.maxValue) || ((max) => `Must be no more than ${max}`);
477
+ const dependentPropertiesLabel = (labels == null ? void 0 : labels.dependentProperties) || "Dependent properties";
478
+ const repeaterAddLabel = (labels == null ? void 0 : labels.repeaterAdd) || "Add";
479
+ const repeaterRemoveLabel = (labels == null ? void 0 : labels.repeaterRemove) || "Remove";
480
+ const validationMessages = labels ? {
481
+ required: requiredMessage,
482
+ invalidFormat: invalidFormatMessage,
483
+ minLength: minLengthMessage,
484
+ maxLength: maxLengthMessage,
485
+ minValue: minValueMessage,
486
+ maxValue: maxValueMessage
487
+ } : void 0;
454
488
  const addAlert = alerts == null ? void 0 : alerts.addAlert;
455
489
  const readOnlyTitle = (alerts == null ? void 0 : alerts.readOnlyTitle) || "Read Only";
456
490
  const errorTitle = (alerts == null ? void 0 : alerts.errorTitle) || "Error";
@@ -480,12 +514,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
480
514
  prevSuccessRef.current = formSuccess;
481
515
  }, [addAlert, formSuccess, successTitle]);
482
516
  const computeInitialValues = () => {
517
+ const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
483
518
  const vals = {};
484
519
  for (const field of fields) {
485
520
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
521
+ if (field.type === "fieldGroup" && field.items && field.fields) {
522
+ for (const item of field.items) {
523
+ const subFields = field.fields(item);
524
+ for (const sf of subFields) {
525
+ const plugin2 = fieldTypes && fieldTypes[sf.type];
526
+ const emptyValue2 = plugin2 && plugin2.getEmptyValue ? plugin2.getEmptyValue() : getEmptyValue(sf);
527
+ let init2 = resolved && resolved[sf.name] !== void 0 ? resolved[sf.name] : sf.defaultValue !== void 0 ? sf.defaultValue : emptyValue2;
528
+ if (sf.transformIn) init2 = sf.transformIn(init2);
529
+ vals[sf.name] = init2;
530
+ }
531
+ }
532
+ continue;
533
+ }
486
534
  const plugin = fieldTypes && fieldTypes[field.type];
487
535
  const emptyValue = plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
488
- const init = initialValues && initialValues[field.name] !== void 0 ? initialValues[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
536
+ let init = resolved && resolved[field.name] !== void 0 ? resolved[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
537
+ if (field.transformIn) init = field.transformIn(init);
489
538
  vals[field.name] = init;
490
539
  }
491
540
  return vals;
@@ -518,7 +567,14 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
518
567
  formErrorsRef.current = formErrors;
519
568
  const fieldByName = useMemo(() => {
520
569
  const map = /* @__PURE__ */ new Map();
521
- for (const field of fields) map.set(field.name, field);
570
+ for (const field of fields) {
571
+ map.set(field.name, field);
572
+ if (field.type === "fieldGroup" && field.items && field.fields) {
573
+ for (const item of field.items) {
574
+ for (const sf of field.fields(item)) map.set(sf.name, sf);
575
+ }
576
+ }
577
+ }
522
578
  return map;
523
579
  }, [fields]);
524
580
  const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
@@ -689,7 +745,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
689
745
  const rowValues = { ...allValues, [field.name]: rows };
690
746
  subFields.forEach((subField) => {
691
747
  if (subField.visible && !subField.visible(rowValues)) return;
692
- const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes);
748
+ const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes, { messages: validationMessages });
693
749
  if (!err) return;
694
750
  const key = getRepeaterErrorKey(field.name, rowIdx, subField.name);
695
751
  errors[key] = err;
@@ -716,9 +772,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
716
772
  );
717
773
  return repeaterResult.errors[name] || null;
718
774
  }
719
- return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes);
775
+ return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes, { messages: validationMessages });
720
776
  },
721
- [fieldByName, formValues, validateRepeaterField, fieldTypes]
777
+ [fieldByName, formValues, validateRepeaterField, fieldTypes, validationMessages]
722
778
  );
723
779
  const validateVisibleFields = useCallback(
724
780
  (fieldSubset) => {
@@ -734,7 +790,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
734
790
  }
735
791
  continue;
736
792
  }
737
- const err = runValidators(formValues[field.name], field, formValues, fieldTypes);
793
+ const err = runValidators(formValues[field.name], field, formValues, fieldTypes, { messages: validationMessages });
738
794
  if (err) {
739
795
  errors[field.name] = err;
740
796
  hasErrors = true;
@@ -742,14 +798,14 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
742
798
  }
743
799
  return { errors, hasErrors };
744
800
  },
745
- [visibleFields, formValues, validateRepeaterField, fieldTypes]
801
+ [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
746
802
  );
747
803
  const runAsyncValidation = useCallback(
748
804
  (name, value) => {
749
805
  const field = fieldByName.get(name);
750
806
  if (!field || field.type === "repeater") return null;
751
807
  const val = value != null ? value : formValues[name];
752
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false });
808
+ const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
753
809
  const prevController = asyncAbortRef.current.get(name);
754
810
  if (prevController) prevController.abort();
755
811
  asyncAbortRef.current.delete(name);
@@ -940,23 +996,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
940
996
  );
941
997
  const handleFieldInput = useCallback(
942
998
  (name, value) => {
999
+ handleFieldChange(name, value);
943
1000
  if (!validateOnChange) return;
944
1001
  const err = validateField(name, value);
945
1002
  updateErrors({ [name]: err });
946
1003
  },
947
- [validateOnChange, validateField, updateErrors]
1004
+ [validateOnChange, validateField, updateErrors, handleFieldChange]
948
1005
  );
949
1006
  const handleFieldBlur = useCallback(
950
1007
  (name, value) => {
951
- if (!validateOnBlur) return;
952
1008
  const resolvedValue = value != null ? value : formValuesRef.current[name];
1009
+ if (value != null && value !== formValuesRef.current[name]) {
1010
+ handleFieldChange(name, value);
1011
+ }
1012
+ if (!validateOnBlur) return;
953
1013
  const err = validateField(name, resolvedValue);
954
1014
  updateErrors({ [name]: err });
955
1015
  if (!err) {
956
1016
  triggerAsyncValidation(name, resolvedValue);
957
1017
  }
958
1018
  },
959
- [validateOnBlur, validateField, updateErrors, triggerAsyncValidation]
1019
+ [validateOnBlur, validateField, updateErrors, triggerAsyncValidation, handleFieldChange]
960
1020
  );
961
1021
  const handleSubmit = useCallback(
962
1022
  async (e) => {
@@ -989,8 +1049,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
989
1049
  const rawValues = {};
990
1050
  for (const key of Object.keys(formValues)) {
991
1051
  const f = fieldByName.get(key);
992
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
993
- rawValues[key] = formValues[key];
1052
+ if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
1053
+ rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
1054
+ }
1055
+ for (const f of fields) {
1056
+ if (f.type !== "fieldGroup" || !f.items || !f.fields) continue;
1057
+ for (const item of f.items) {
1058
+ for (const sf of f.fields(item)) {
1059
+ if (formValues[sf.name] === void 0) continue;
1060
+ rawValues[sf.name] = sf.transformOut ? sf.transformOut(formValues[sf.name]) : formValues[sf.name];
1061
+ }
1062
+ }
994
1063
  }
995
1064
  const submitValues = transformValues ? transformValues(rawValues) : rawValues;
996
1065
  if (onBeforeSubmit) {
@@ -1118,6 +1187,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1118
1187
  [replaceErrors]
1119
1188
  );
1120
1189
  const renderField = (field) => {
1190
+ const fieldError = formErrors[field.name] || null;
1191
+ const rendered = renderFieldInner(field);
1192
+ if (!renderFieldError || !fieldError) return rendered;
1193
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, rendered, renderFieldError(fieldError, field));
1194
+ };
1195
+ const renderFieldInner = (field) => {
1121
1196
  const fieldValue = formValues[field.name];
1122
1197
  const fieldError = formErrors[field.name] || null;
1123
1198
  const hasError = !!fieldError;
@@ -1127,10 +1202,125 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1127
1202
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
1128
1203
  if (field.type === "display") {
1129
1204
  if (field.render) {
1130
- return field.render({ allValues: formValues });
1205
+ return field.render({
1206
+ allValues: formValues,
1207
+ setFieldValue: (name, value) => handleFieldChange(name, value),
1208
+ setFieldError: (name, message) => updateErrors({ [name]: message })
1209
+ });
1131
1210
  }
1132
1211
  return null;
1133
1212
  }
1213
+ if (field.type === "fieldGroup") {
1214
+ const items = field.items || [];
1215
+ const fieldsFn = field.fields;
1216
+ if (!fieldsFn) return null;
1217
+ const groupColumns = field.columns || 1;
1218
+ const showItemLabel = field.showItemLabel !== false;
1219
+ return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, field.label), field.description && /* @__PURE__ */ React.createElement(Text, { variant: "microcopy" }, field.description), items.map((item, itemIdx) => {
1220
+ const subFields = fieldsFn(item);
1221
+ return /* @__PURE__ */ React.createElement(Flex, { key: item.key || itemIdx, direction: "row", gap: "xs", align: "end" }, showItemLabel && item.label && /* @__PURE__ */ React.createElement(Box, { flex: 1 }, itemIdx === 0 ? /* @__PURE__ */ React.createElement(
1222
+ Input,
1223
+ {
1224
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
1225
+ label: "\xA0",
1226
+ value: item.label,
1227
+ readOnly: true,
1228
+ disabled: true
1229
+ }
1230
+ ) : /* @__PURE__ */ React.createElement(
1231
+ Input,
1232
+ {
1233
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
1234
+ value: item.label,
1235
+ readOnly: true,
1236
+ disabled: true
1237
+ }
1238
+ )), subFields.map((sf) => {
1239
+ const sfValue = formValues[sf.name];
1240
+ const sfError = formErrors[sf.name] || null;
1241
+ const sfLabel = itemIdx === 0 ? sf.label : void 0;
1242
+ const sfReadOnly = sf.readOnly || formReadOnly;
1243
+ const sfDisabled = disabled || sf.disabled || formReadOnly;
1244
+ const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
1245
+ const sfProps = {
1246
+ name: sf.name,
1247
+ label: sfLabel,
1248
+ placeholder: sf.placeholder,
1249
+ description: itemIdx === 0 ? sf.description : void 0,
1250
+ readOnly: sfReadOnly,
1251
+ disabled: sfDisabled,
1252
+ error: !!sfError,
1253
+ validationMessage: sfError || void 0,
1254
+ ...sf.fieldProps || {}
1255
+ };
1256
+ let sfElement;
1257
+ switch (sf.type) {
1258
+ case "select":
1259
+ sfElement = /* @__PURE__ */ React.createElement(
1260
+ Select,
1261
+ {
1262
+ ...sfProps,
1263
+ value: sfValue,
1264
+ options: resolveOptions(sf, formValues),
1265
+ onChange: sfOnChange
1266
+ }
1267
+ );
1268
+ break;
1269
+ case "number":
1270
+ sfElement = /* @__PURE__ */ React.createElement(
1271
+ NumberInput,
1272
+ {
1273
+ ...sfProps,
1274
+ value: sfValue,
1275
+ onChange: sfOnChange,
1276
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1277
+ }
1278
+ );
1279
+ break;
1280
+ case "toggle":
1281
+ sfElement = /* @__PURE__ */ React.createElement(
1282
+ Toggle,
1283
+ {
1284
+ name: sf.name,
1285
+ label: sfLabel || sf.label,
1286
+ checked: !!sfValue,
1287
+ size: sf.size || "md",
1288
+ labelDisplay: sf.labelDisplay || "top",
1289
+ readonly: sfReadOnly,
1290
+ disabled: sfDisabled,
1291
+ onChange: sfOnChange,
1292
+ ...sf.fieldProps || {}
1293
+ }
1294
+ );
1295
+ break;
1296
+ case "time":
1297
+ sfElement = /* @__PURE__ */ React.createElement(
1298
+ TimeInput,
1299
+ {
1300
+ ...sfProps,
1301
+ value: sfValue,
1302
+ interval: sf.interval,
1303
+ onChange: sfOnChange,
1304
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1305
+ }
1306
+ );
1307
+ break;
1308
+ default:
1309
+ sfElement = /* @__PURE__ */ React.createElement(
1310
+ Input,
1311
+ {
1312
+ ...sfProps,
1313
+ value: sfValue || "",
1314
+ onChange: sfOnChange,
1315
+ onInput: (v) => handleFieldInput(sf.name, v),
1316
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1317
+ }
1318
+ );
1319
+ }
1320
+ return /* @__PURE__ */ React.createElement(Box, { key: sf.name, flex: 1 }, sfElement);
1321
+ }));
1322
+ }));
1323
+ }
1134
1324
  if (field.type === "crmPropertyList") {
1135
1325
  return /* @__PURE__ */ React.createElement(
1136
1326
  CrmPropertyList,
@@ -1184,7 +1374,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1184
1374
  readOnly: isReadOnly,
1185
1375
  disabled: isDisabled,
1186
1376
  error: hasError,
1187
- validationMessage: fieldError || void 0,
1377
+ validationMessage: renderFieldError ? void 0 : fieldError || void 0,
1188
1378
  ...field.loading || validatingFields[field.name] ? { loading: true } : {},
1189
1379
  ...field.fieldProps || {}
1190
1380
  };
@@ -1254,7 +1444,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1254
1444
  CurrencyInput,
1255
1445
  {
1256
1446
  ...commonProps,
1257
- currency: field.currency || "USD",
1447
+ currency: field.currency || defaultCurrency,
1258
1448
  value: fieldValue,
1259
1449
  min: field.min,
1260
1450
  max: field.max,
@@ -1432,8 +1622,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1432
1622
  const renderRemoveControl = repeaterProps.renderRemove;
1433
1623
  const renderMoveUpControl = repeaterProps.renderMoveUp;
1434
1624
  const renderMoveDownControl = repeaterProps.renderMoveDown;
1435
- const addLabel = repeaterProps.addLabel || "Add";
1436
- const removeLabel = repeaterProps.removeLabel || "Remove";
1625
+ const addLabel = repeaterProps.addLabel || repeaterAddLabel;
1626
+ const removeLabel = repeaterProps.removeLabel || repeaterRemoveLabel;
1437
1627
  const moveUpLabel = repeaterProps.moveUpLabel || "Up";
1438
1628
  const moveDownLabel = repeaterProps.moveDownLabel || "Down";
1439
1629
  const canEditRows = !isReadOnly && !isDisabled;
@@ -1467,7 +1657,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1467
1657
  };
1468
1658
  const validateSubField = (rowIdx, subField, subValue, nextRows) => {
1469
1659
  const rowValues = { ...formValues, [field.name]: nextRows };
1470
- const err = runValidators(subValue, subField, rowValues, fieldTypes);
1660
+ const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
1471
1661
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
1472
1662
  };
1473
1663
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
@@ -1585,7 +1775,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1585
1775
  const renderDependentGroup = (parentField, dependents) => {
1586
1776
  const firstWithLabel = dependents.find((f) => getDependsOnLabel(f)) || dependents[0];
1587
1777
  const firstWithMessage = dependents.find((f) => getDependsOnMessage(f)) || dependents[0];
1588
- const groupLabel = getDependsOnLabel(firstWithLabel) || "Dependent properties";
1778
+ const groupLabel = getDependsOnLabel(firstWithLabel) || dependentPropertiesLabel;
1589
1779
  const rawMessage = getDependsOnMessage(firstWithMessage);
1590
1780
  const tooltipMessage = typeof rawMessage === "function" ? rawMessage(parentField.label) : rawMessage || "";
1591
1781
  return /* @__PURE__ */ React.createElement(Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ React.createElement(Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React.createElement(Tooltip, null, tooltipMessage) }, /* @__PURE__ */ React.createElement(Icon, { name: "info" })))), renderFieldSubset(dependents)));
@@ -1675,38 +1865,19 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1675
1865
  }
1676
1866
  return elements;
1677
1867
  };
1678
- const renderLegacyLayout = (fieldSubset) => {
1868
+ const renderSingleColumnLayout = (fieldSubset) => {
1679
1869
  const fieldList = fieldSubset || visibleFields;
1680
- const rows = [];
1681
- let i = 0;
1682
- while (i < fieldList.length) {
1683
- const field = fieldList[i];
1684
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
1685
- rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
1686
- i += 2;
1687
- } else {
1688
- rows.push({ type: "single", field });
1689
- i++;
1690
- }
1691
- }
1692
1870
  const elements = [];
1693
1871
  const processedDeps = /* @__PURE__ */ new Set();
1694
- for (const row of rows) {
1695
- if (row.type === "pair") {
1696
- elements.push(
1697
- /* @__PURE__ */ React.createElement(Flex, { key: `pair-${row.fields[0].name}`, direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 1 }, renderField(row.fields[0])), /* @__PURE__ */ React.createElement(Box, { flex: 1 }, renderField(row.fields[1])))
1698
- );
1699
- } else {
1700
- const field = row.field;
1701
- if (processedDeps.has(field.name)) continue;
1702
- elements.push(
1703
- /* @__PURE__ */ React.createElement(React.Fragment, { key: field.name }, renderField(field))
1704
- );
1705
- const dependents = getDependents(field);
1706
- if (dependents.length > 0) {
1707
- for (const dep of dependents) processedDeps.add(dep.name);
1708
- elements.push(renderDependentGroup(field, dependents));
1709
- }
1872
+ for (const field of fieldList) {
1873
+ if (processedDeps.has(field.name)) continue;
1874
+ elements.push(
1875
+ /* @__PURE__ */ React.createElement(React.Fragment, { key: field.name }, renderField(field))
1876
+ );
1877
+ const dependents = getDependents(field);
1878
+ if (dependents.length > 0) {
1879
+ for (const dep of dependents) processedDeps.add(dep.name);
1880
+ elements.push(renderDependentGroup(field, dependents));
1710
1881
  }
1711
1882
  }
1712
1883
  return elements;
@@ -1717,10 +1888,20 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1717
1888
  let batch = [];
1718
1889
  const flushBatch = () => {
1719
1890
  if (batch.length === 0) return;
1720
- const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
1721
- for (const chunk of chunks) {
1891
+ if (maxColumns) {
1892
+ const chunks = Array.from(
1893
+ { length: Math.ceil(batch.length / maxColumns) },
1894
+ (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)
1895
+ );
1896
+ for (const chunk of chunks) {
1897
+ const remainder = maxColumns - chunk.length;
1898
+ elements.push(
1899
+ /* @__PURE__ */ React.createElement(Flex, { key: `ag-${chunk[0].name}`, direction: "row", gap }, chunk.map((f) => /* @__PURE__ */ React.createElement(Box, { key: f.name, flex: 1 }, renderField(f))), remainder > 0 && /* @__PURE__ */ React.createElement(Box, { flex: remainder }))
1900
+ );
1901
+ }
1902
+ } else {
1722
1903
  elements.push(
1723
- /* @__PURE__ */ React.createElement(AutoGrid, { key: `ag-${chunk[0].name}`, columnWidth, flexible: true, gap }, chunk.map((f) => /* @__PURE__ */ React.createElement(React.Fragment, { key: f.name }, renderField(f))))
1904
+ /* @__PURE__ */ React.createElement(AutoGrid, { key: `ag-${batch[0].name}`, columnWidth, flexible: true, gap }, batch.map((f) => /* @__PURE__ */ React.createElement(React.Fragment, { key: f.name }, renderField(f))))
1724
1905
  );
1725
1906
  }
1726
1907
  batch = [];
@@ -1779,7 +1960,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1779
1960
  if (layout && fieldSubset === visibleFields) return renderExplicitLayout();
1780
1961
  if (columnWidth) return renderAutoGridLayout(fieldSubset);
1781
1962
  if (columns > 1) return renderGridLayout(fieldSubset);
1782
- return renderLegacyLayout(fieldSubset);
1963
+ return renderSingleColumnLayout(fieldSubset);
1783
1964
  };
1784
1965
  const renderSections = () => {
1785
1966
  const hasSections = Array.isArray(sections) && sections.length > 0;
@@ -1792,7 +1973,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1792
1973
  for (const sec of sections) {
1793
1974
  const sectionFields = sec.fields ? visibleFields.filter((f) => sec.fields.includes(f.name)) : [];
1794
1975
  if (sectionFields.length === 0) continue;
1795
- const accordionContent = /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, renderFieldSubset(sectionFields));
1976
+ const sectionContext = { values: formValues, errors: formErrors };
1977
+ const accordionContent = /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields), sec.renderAfter && sec.renderAfter(sectionContext));
1796
1978
  const accordion = /* @__PURE__ */ React.createElement(
1797
1979
  Accordion,
1798
1980
  {
@@ -1885,7 +2067,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1885
2067
  currentStep,
1886
2068
  stepNames: steps.map((s) => s.title)
1887
2069
  }
1888
- ), formReadOnly && readOnlyMessage && /* @__PURE__ */ React.createElement(Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage), !addAlert && formError && /* @__PURE__ */ React.createElement(Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), !addAlert && formSuccess && /* @__PURE__ */ React.createElement(Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
2070
+ ), showReadOnlyAlert && formReadOnly && readOnlyMessage && (renderReadOnlyAlert ? renderReadOnlyAlert({ title: readOnlyTitle, message: readOnlyMessage }) : /* @__PURE__ */ React.createElement(Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage)), showInlineAlerts && !addAlert && formError && /* @__PURE__ */ React.createElement(Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), showInlineAlerts && !addAlert && formSuccess && /* @__PURE__ */ React.createElement(Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
1889
2071
  values: formValues,
1890
2072
  goNext: handleNext,
1891
2073
  goBack: handleBack,