hs-uix 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -91,12 +91,19 @@ var computeAutoWidths = (columns, data) => {
91
91
  columns.forEach((col) => {
92
92
  if (col.width && col.cellWidth) return;
93
93
  const values = sample.map((row) => row[col.field]).filter((v) => v != null);
94
- const strings = values.map((v) => String(v));
94
+ const strings = values.map((v) => {
95
+ const s = String(v);
96
+ const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
97
+ return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
98
+ });
95
99
  let widthHint = null;
96
100
  let cellWidthHint = null;
97
101
  if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
98
102
  cellWidthHint = "min";
99
103
  }
104
+ if (col.truncate === true) {
105
+ cellWidthHint = cellWidthHint || "min";
106
+ }
100
107
  if (strings.length > 0) {
101
108
  const lengths = strings.map((s) => s.length);
102
109
  const maxLen = Math.max(...lengths);
@@ -870,20 +877,26 @@ var DataTable = ({
870
877
  const rawStr = String(rawValue ?? "");
871
878
  if (col.truncate && rawStr.length > 0) {
872
879
  if (col.truncate === true) {
873
- const content2 = col.renderCell ? col.renderCell(rawValue, row) : rawStr;
880
+ if (col.renderCell) {
881
+ const content2 = col.renderCell(rawValue, row);
882
+ if (col.editable) {
883
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
884
+ }
885
+ return content2;
886
+ }
874
887
  if (col.editable) {
875
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--"));
888
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, rawStr || "--"));
876
889
  }
877
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, content2);
890
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, rawStr);
878
891
  }
879
- const maxLen = col.truncate.maxLength || 100;
892
+ const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
880
893
  if (rawStr.length > maxLen) {
881
894
  const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
882
- const truncatedContent = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
895
+ const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
883
896
  if (col.editable) {
884
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, truncatedContent || "--");
897
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
885
898
  }
886
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, truncatedContent || "--");
899
+ return col.renderCell ? content2 : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, content2 || "--");
887
900
  }
888
901
  }
889
902
  const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
@@ -988,10 +1001,11 @@ var DataTable = ({
988
1001
  selectedCount: selectedIds.size,
989
1002
  displayCount,
990
1003
  countLabel,
1004
+ allSelected: selectedIds.size >= (serverSide ? totalCount || data.length : allRowIds.length),
991
1005
  onSelectAll: handleSelectAllRows,
992
1006
  onDeselectAll: handleDeselectAll,
993
1007
  selectionActions
994
- }) : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 3 }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof resolvedSelectedLabel === "function" ? resolvedSelectedLabel(selectedIds.size, countLabel(selectedIds.size)) : resolvedSelectedLabel), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, typeof resolvedSelectAllLabel === "function" ? resolvedSelectAllLabel(displayCount, countLabel(displayCount)) : resolvedSelectAllLabel), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, resolvedDeselectAllLabel), selectionActions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
1008
+ }) : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 3 }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof resolvedSelectedLabel === "function" ? resolvedSelectedLabel(selectedIds.size, countLabel(selectedIds.size)) : resolvedSelectedLabel), selectedIds.size < (serverSide ? totalCount || data.length : allRowIds.length) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, typeof resolvedSelectAllLabel === "function" ? resolvedSelectAllLabel(displayCount, countLabel(displayCount)) : resolvedSelectAllLabel), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, resolvedDeselectAllLabel), selectionActions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
995
1009
  import_ui_extensions.Button,
996
1010
  {
997
1011
  key: i,
@@ -1189,7 +1203,6 @@ var runDefaultFieldValidator = (value, field, allValues) => {
1189
1203
  if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
1190
1204
  break;
1191
1205
  case "datetime": {
1192
- if (isDateValueObject(value)) break;
1193
1206
  if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
1194
1207
  const hasDate = value.date !== void 0;
1195
1208
  const hasTime = value.time !== void 0;
@@ -1256,7 +1269,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
1256
1269
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1257
1270
  const includeCustomValidators = options.includeCustomValidators !== false;
1258
1271
  const msg = options.messages || {};
1259
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
1272
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
1260
1273
  const isRequired = resolveRequired(field, allValues);
1261
1274
  const plugin = fieldTypes && fieldTypes[field.type];
1262
1275
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -1403,6 +1416,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1403
1416
  // (values, { reset, rawValues }) => void | Promise
1404
1417
  transformValues,
1405
1418
  // (values) => values — reshape before submit
1419
+ transformInitialValues,
1420
+ // (rawInitialValues) => values — reshape raw data on load
1406
1421
  onBeforeSubmit,
1407
1422
  // (values) => boolean | Promise<boolean> — intercept submit
1408
1423
  onSubmitSuccess,
@@ -1566,12 +1581,27 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1566
1581
  prevSuccessRef.current = formSuccess;
1567
1582
  }, [addAlert, formSuccess, successTitle]);
1568
1583
  const computeInitialValues = () => {
1584
+ const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
1569
1585
  const vals = {};
1570
1586
  for (const field of fields) {
1571
1587
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
1588
+ if (field.type === "fieldGroup" && field.items && field.fields) {
1589
+ for (const item of field.items) {
1590
+ const subFields = field.fields(item);
1591
+ for (const sf of subFields) {
1592
+ const plugin2 = fieldTypes && fieldTypes[sf.type];
1593
+ const emptyValue2 = plugin2 && plugin2.getEmptyValue ? plugin2.getEmptyValue() : getEmptyValue(sf);
1594
+ let init2 = resolved && resolved[sf.name] !== void 0 ? resolved[sf.name] : sf.defaultValue !== void 0 ? sf.defaultValue : emptyValue2;
1595
+ if (sf.transformIn) init2 = sf.transformIn(init2);
1596
+ vals[sf.name] = init2;
1597
+ }
1598
+ }
1599
+ continue;
1600
+ }
1572
1601
  const plugin = fieldTypes && fieldTypes[field.type];
1573
1602
  const emptyValue = plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
1574
- const init = initialValues && initialValues[field.name] !== void 0 ? initialValues[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
1603
+ let init = resolved && resolved[field.name] !== void 0 ? resolved[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
1604
+ if (field.transformIn) init = field.transformIn(init);
1575
1605
  vals[field.name] = init;
1576
1606
  }
1577
1607
  return vals;
@@ -1604,7 +1634,14 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1604
1634
  formErrorsRef.current = formErrors;
1605
1635
  const fieldByName = (0, import_react2.useMemo)(() => {
1606
1636
  const map = /* @__PURE__ */ new Map();
1607
- for (const field of fields) map.set(field.name, field);
1637
+ for (const field of fields) {
1638
+ map.set(field.name, field);
1639
+ if (field.type === "fieldGroup" && field.items && field.fields) {
1640
+ for (const item of field.items) {
1641
+ for (const sf of field.fields(item)) map.set(sf.name, sf);
1642
+ }
1643
+ }
1644
+ }
1608
1645
  return map;
1609
1646
  }, [fields]);
1610
1647
  const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
@@ -2026,23 +2063,27 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2026
2063
  );
2027
2064
  const handleFieldInput = (0, import_react2.useCallback)(
2028
2065
  (name, value) => {
2066
+ handleFieldChange(name, value);
2029
2067
  if (!validateOnChange) return;
2030
2068
  const err = validateField(name, value);
2031
2069
  updateErrors({ [name]: err });
2032
2070
  },
2033
- [validateOnChange, validateField, updateErrors]
2071
+ [validateOnChange, validateField, updateErrors, handleFieldChange]
2034
2072
  );
2035
2073
  const handleFieldBlur = (0, import_react2.useCallback)(
2036
2074
  (name, value) => {
2037
- if (!validateOnBlur) return;
2038
2075
  const resolvedValue = value != null ? value : formValuesRef.current[name];
2076
+ if (value != null && value !== formValuesRef.current[name]) {
2077
+ handleFieldChange(name, value);
2078
+ }
2079
+ if (!validateOnBlur) return;
2039
2080
  const err = validateField(name, resolvedValue);
2040
2081
  updateErrors({ [name]: err });
2041
2082
  if (!err) {
2042
2083
  triggerAsyncValidation(name, resolvedValue);
2043
2084
  }
2044
2085
  },
2045
- [validateOnBlur, validateField, updateErrors, triggerAsyncValidation]
2086
+ [validateOnBlur, validateField, updateErrors, triggerAsyncValidation, handleFieldChange]
2046
2087
  );
2047
2088
  const handleSubmit = (0, import_react2.useCallback)(
2048
2089
  async (e) => {
@@ -2075,8 +2116,17 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2075
2116
  const rawValues = {};
2076
2117
  for (const key of Object.keys(formValues)) {
2077
2118
  const f = fieldByName.get(key);
2078
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
2079
- rawValues[key] = formValues[key];
2119
+ if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
2120
+ rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
2121
+ }
2122
+ for (const f of fields) {
2123
+ if (f.type !== "fieldGroup" || !f.items || !f.fields) continue;
2124
+ for (const item of f.items) {
2125
+ for (const sf of f.fields(item)) {
2126
+ if (formValues[sf.name] === void 0) continue;
2127
+ rawValues[sf.name] = sf.transformOut ? sf.transformOut(formValues[sf.name]) : formValues[sf.name];
2128
+ }
2129
+ }
2080
2130
  }
2081
2131
  const submitValues = transformValues ? transformValues(rawValues) : rawValues;
2082
2132
  if (onBeforeSubmit) {
@@ -2219,10 +2269,125 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2219
2269
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
2220
2270
  if (field.type === "display") {
2221
2271
  if (field.render) {
2222
- return field.render({ allValues: formValues });
2272
+ return field.render({
2273
+ allValues: formValues,
2274
+ setFieldValue: (name, value) => handleFieldChange(name, value),
2275
+ setFieldError: (name, message) => updateErrors({ [name]: message })
2276
+ });
2223
2277
  }
2224
2278
  return null;
2225
2279
  }
2280
+ if (field.type === "fieldGroup") {
2281
+ const items = field.items || [];
2282
+ const fieldsFn = field.fields;
2283
+ if (!fieldsFn) return null;
2284
+ const groupColumns = field.columns || 1;
2285
+ const showItemLabel = field.showItemLabel !== false;
2286
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, field.label), field.description && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, field.description), items.map((item, itemIdx) => {
2287
+ const subFields = fieldsFn(item);
2288
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: item.key || itemIdx, direction: "row", gap: "xs", align: "end" }, showItemLabel && item.label && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, itemIdx === 0 ? /* @__PURE__ */ import_react2.default.createElement(
2289
+ import_ui_extensions2.Input,
2290
+ {
2291
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
2292
+ label: "\xA0",
2293
+ value: item.label,
2294
+ readOnly: true,
2295
+ disabled: true
2296
+ }
2297
+ ) : /* @__PURE__ */ import_react2.default.createElement(
2298
+ import_ui_extensions2.Input,
2299
+ {
2300
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
2301
+ value: item.label,
2302
+ readOnly: true,
2303
+ disabled: true
2304
+ }
2305
+ )), subFields.map((sf) => {
2306
+ const sfValue = formValues[sf.name];
2307
+ const sfError = formErrors[sf.name] || null;
2308
+ const sfLabel = itemIdx === 0 ? sf.label : void 0;
2309
+ const sfReadOnly = sf.readOnly || formReadOnly;
2310
+ const sfDisabled = disabled || sf.disabled || formReadOnly;
2311
+ const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
2312
+ const sfProps = {
2313
+ name: sf.name,
2314
+ label: sfLabel,
2315
+ placeholder: sf.placeholder,
2316
+ description: itemIdx === 0 ? sf.description : void 0,
2317
+ readOnly: sfReadOnly,
2318
+ disabled: sfDisabled,
2319
+ error: !!sfError,
2320
+ validationMessage: sfError || void 0,
2321
+ ...sf.fieldProps || {}
2322
+ };
2323
+ let sfElement;
2324
+ switch (sf.type) {
2325
+ case "select":
2326
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(
2327
+ import_ui_extensions2.Select,
2328
+ {
2329
+ ...sfProps,
2330
+ value: sfValue,
2331
+ options: resolveOptions(sf, formValues),
2332
+ onChange: sfOnChange
2333
+ }
2334
+ );
2335
+ break;
2336
+ case "number":
2337
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(
2338
+ import_ui_extensions2.NumberInput,
2339
+ {
2340
+ ...sfProps,
2341
+ value: sfValue,
2342
+ onChange: sfOnChange,
2343
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2344
+ }
2345
+ );
2346
+ break;
2347
+ case "toggle":
2348
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(
2349
+ import_ui_extensions2.Toggle,
2350
+ {
2351
+ name: sf.name,
2352
+ label: sfLabel || sf.label,
2353
+ checked: !!sfValue,
2354
+ size: sf.size || "md",
2355
+ labelDisplay: sf.labelDisplay || "top",
2356
+ readonly: sfReadOnly,
2357
+ disabled: sfDisabled,
2358
+ onChange: sfOnChange,
2359
+ ...sf.fieldProps || {}
2360
+ }
2361
+ );
2362
+ break;
2363
+ case "time":
2364
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(
2365
+ import_ui_extensions2.TimeInput,
2366
+ {
2367
+ ...sfProps,
2368
+ value: sfValue,
2369
+ interval: sf.interval,
2370
+ onChange: sfOnChange,
2371
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2372
+ }
2373
+ );
2374
+ break;
2375
+ default:
2376
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(
2377
+ import_ui_extensions2.Input,
2378
+ {
2379
+ ...sfProps,
2380
+ value: sfValue || "",
2381
+ onChange: sfOnChange,
2382
+ onInput: (v) => handleFieldInput(sf.name, v),
2383
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2384
+ }
2385
+ );
2386
+ }
2387
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { key: sf.name, flex: 1 }, sfElement);
2388
+ }));
2389
+ }));
2390
+ }
2226
2391
  if (field.type === "crmPropertyList") {
2227
2392
  return /* @__PURE__ */ import_react2.default.createElement(
2228
2393
  import_crm.CrmPropertyList,
@@ -2767,38 +2932,19 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2767
2932
  }
2768
2933
  return elements;
2769
2934
  };
2770
- const renderLegacyLayout = (fieldSubset) => {
2935
+ const renderSingleColumnLayout = (fieldSubset) => {
2771
2936
  const fieldList = fieldSubset || visibleFields;
2772
- const rows = [];
2773
- let i = 0;
2774
- while (i < fieldList.length) {
2775
- const field = fieldList[i];
2776
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
2777
- rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
2778
- i += 2;
2779
- } else {
2780
- rows.push({ type: "single", field });
2781
- i++;
2782
- }
2783
- }
2784
2937
  const elements = [];
2785
2938
  const processedDeps = /* @__PURE__ */ new Set();
2786
- for (const row of rows) {
2787
- if (row.type === "pair") {
2788
- elements.push(
2789
- /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: `pair-${row.fields[0].name}`, direction: "row", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, renderField(row.fields[0])), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, renderField(row.fields[1])))
2790
- );
2791
- } else {
2792
- const field = row.field;
2793
- if (processedDeps.has(field.name)) continue;
2794
- elements.push(
2795
- /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: field.name }, renderField(field))
2796
- );
2797
- const dependents = getDependents(field);
2798
- if (dependents.length > 0) {
2799
- for (const dep of dependents) processedDeps.add(dep.name);
2800
- elements.push(renderDependentGroup(field, dependents));
2801
- }
2939
+ for (const field of fieldList) {
2940
+ if (processedDeps.has(field.name)) continue;
2941
+ elements.push(
2942
+ /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: field.name }, renderField(field))
2943
+ );
2944
+ const dependents = getDependents(field);
2945
+ if (dependents.length > 0) {
2946
+ for (const dep of dependents) processedDeps.add(dep.name);
2947
+ elements.push(renderDependentGroup(field, dependents));
2802
2948
  }
2803
2949
  }
2804
2950
  return elements;
@@ -2809,10 +2955,20 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2809
2955
  let batch = [];
2810
2956
  const flushBatch = () => {
2811
2957
  if (batch.length === 0) return;
2812
- const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
2813
- for (const chunk of chunks) {
2958
+ if (maxColumns) {
2959
+ const chunks = Array.from(
2960
+ { length: Math.ceil(batch.length / maxColumns) },
2961
+ (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)
2962
+ );
2963
+ for (const chunk of chunks) {
2964
+ const remainder = maxColumns - chunk.length;
2965
+ elements.push(
2966
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: `ag-${chunk[0].name}`, direction: "row", gap }, chunk.map((f) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { key: f.name, flex: 1 }, renderField(f))), remainder > 0 && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: remainder }))
2967
+ );
2968
+ }
2969
+ } else {
2814
2970
  elements.push(
2815
- /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.AutoGrid, { key: `ag-${chunk[0].name}`, columnWidth, flexible: true, gap }, chunk.map((f) => /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: f.name }, renderField(f))))
2971
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.AutoGrid, { key: `ag-${batch[0].name}`, columnWidth, flexible: true, gap }, batch.map((f) => /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: f.name }, renderField(f))))
2816
2972
  );
2817
2973
  }
2818
2974
  batch = [];
@@ -2871,7 +3027,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2871
3027
  if (layout && fieldSubset === visibleFields) return renderExplicitLayout();
2872
3028
  if (columnWidth) return renderAutoGridLayout(fieldSubset);
2873
3029
  if (columns > 1) return renderGridLayout(fieldSubset);
2874
- return renderLegacyLayout(fieldSubset);
3030
+ return renderSingleColumnLayout(fieldSubset);
2875
3031
  };
2876
3032
  const renderSections = () => {
2877
3033
  const hasSections = Array.isArray(sections) && sections.length > 0;
@@ -2884,7 +3040,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2884
3040
  for (const sec of sections) {
2885
3041
  const sectionFields = sec.fields ? visibleFields.filter((f) => sec.fields.includes(f.name)) : [];
2886
3042
  if (sectionFields.length === 0) continue;
2887
- const accordionContent = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, renderFieldSubset(sectionFields));
3043
+ const sectionContext = { values: formValues, errors: formErrors };
3044
+ const accordionContent = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields), sec.renderAfter && sec.renderAfter(sectionContext));
2888
3045
  const accordion = /* @__PURE__ */ import_react2.default.createElement(
2889
3046
  import_ui_extensions2.Accordion,
2890
3047
  {