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.mjs CHANGED
@@ -84,12 +84,19 @@ var computeAutoWidths = (columns, data) => {
84
84
  columns.forEach((col) => {
85
85
  if (col.width && col.cellWidth) return;
86
86
  const values = sample.map((row) => row[col.field]).filter((v) => v != null);
87
- const strings = values.map((v) => String(v));
87
+ const strings = values.map((v) => {
88
+ const s = String(v);
89
+ const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
90
+ return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
91
+ });
88
92
  let widthHint = null;
89
93
  let cellWidthHint = null;
90
94
  if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
91
95
  cellWidthHint = "min";
92
96
  }
97
+ if (col.truncate === true) {
98
+ cellWidthHint = cellWidthHint || "min";
99
+ }
93
100
  if (strings.length > 0) {
94
101
  const lengths = strings.map((s) => s.length);
95
102
  const maxLen = Math.max(...lengths);
@@ -863,20 +870,26 @@ var DataTable = ({
863
870
  const rawStr = String(rawValue ?? "");
864
871
  if (col.truncate && rawStr.length > 0) {
865
872
  if (col.truncate === true) {
866
- const content2 = col.renderCell ? col.renderCell(rawValue, row) : rawStr;
873
+ if (col.renderCell) {
874
+ const content2 = col.renderCell(rawValue, row);
875
+ if (col.editable) {
876
+ return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
877
+ }
878
+ return content2;
879
+ }
867
880
  if (col.editable) {
868
- return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--"));
881
+ return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, rawStr || "--"));
869
882
  }
870
- return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, content2);
883
+ return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, rawStr);
871
884
  }
872
- const maxLen = col.truncate.maxLength || 100;
885
+ const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
873
886
  if (rawStr.length > maxLen) {
874
887
  const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
875
- const truncatedContent = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
888
+ const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
876
889
  if (col.editable) {
877
- return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, truncatedContent || "--");
890
+ return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
878
891
  }
879
- return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, truncatedContent || "--");
892
+ return col.renderCell ? content2 : /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, content2 || "--");
880
893
  }
881
894
  }
882
895
  const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
@@ -981,10 +994,11 @@ var DataTable = ({
981
994
  selectedCount: selectedIds.size,
982
995
  displayCount,
983
996
  countLabel,
997
+ allSelected: selectedIds.size >= (serverSide ? totalCount || data.length : allRowIds.length),
984
998
  onSelectAll: handleSelectAllRows,
985
999
  onDeselectAll: handleDeselectAll,
986
1000
  selectionActions
987
- }) : /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ React.createElement(Text, { inline: true, format: { fontWeight: "demibold" } }, typeof resolvedSelectedLabel === "function" ? resolvedSelectedLabel(selectedIds.size, countLabel(selectedIds.size)) : resolvedSelectedLabel), /* @__PURE__ */ React.createElement(Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, typeof resolvedSelectAllLabel === "function" ? resolvedSelectAllLabel(displayCount, countLabel(displayCount)) : resolvedSelectAllLabel), /* @__PURE__ */ React.createElement(Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, resolvedDeselectAllLabel), selectionActions.map((action, i) => /* @__PURE__ */ React.createElement(
1001
+ }) : /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ React.createElement(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__ */ React.createElement(Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, typeof resolvedSelectAllLabel === "function" ? resolvedSelectAllLabel(displayCount, countLabel(displayCount)) : resolvedSelectAllLabel), /* @__PURE__ */ React.createElement(Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, resolvedDeselectAllLabel), selectionActions.map((action, i) => /* @__PURE__ */ React.createElement(
988
1002
  Button,
989
1003
  {
990
1004
  key: i,
@@ -1222,7 +1236,6 @@ var runDefaultFieldValidator = (value, field, allValues) => {
1222
1236
  if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
1223
1237
  break;
1224
1238
  case "datetime": {
1225
- if (isDateValueObject(value)) break;
1226
1239
  if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
1227
1240
  const hasDate = value.date !== void 0;
1228
1241
  const hasTime = value.time !== void 0;
@@ -1289,7 +1302,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
1289
1302
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1290
1303
  const includeCustomValidators = options.includeCustomValidators !== false;
1291
1304
  const msg = options.messages || {};
1292
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
1305
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
1293
1306
  const isRequired = resolveRequired(field, allValues);
1294
1307
  const plugin = fieldTypes && fieldTypes[field.type];
1295
1308
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -1436,6 +1449,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1436
1449
  // (values, { reset, rawValues }) => void | Promise
1437
1450
  transformValues,
1438
1451
  // (values) => values — reshape before submit
1452
+ transformInitialValues,
1453
+ // (rawInitialValues) => values — reshape raw data on load
1439
1454
  onBeforeSubmit,
1440
1455
  // (values) => boolean | Promise<boolean> — intercept submit
1441
1456
  onSubmitSuccess,
@@ -1599,12 +1614,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1599
1614
  prevSuccessRef.current = formSuccess;
1600
1615
  }, [addAlert, formSuccess, successTitle]);
1601
1616
  const computeInitialValues = () => {
1617
+ const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
1602
1618
  const vals = {};
1603
1619
  for (const field of fields) {
1604
1620
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
1621
+ if (field.type === "fieldGroup" && field.items && field.fields) {
1622
+ for (const item of field.items) {
1623
+ const subFields = field.fields(item);
1624
+ for (const sf of subFields) {
1625
+ const plugin2 = fieldTypes && fieldTypes[sf.type];
1626
+ const emptyValue2 = plugin2 && plugin2.getEmptyValue ? plugin2.getEmptyValue() : getEmptyValue(sf);
1627
+ let init2 = resolved && resolved[sf.name] !== void 0 ? resolved[sf.name] : sf.defaultValue !== void 0 ? sf.defaultValue : emptyValue2;
1628
+ if (sf.transformIn) init2 = sf.transformIn(init2);
1629
+ vals[sf.name] = init2;
1630
+ }
1631
+ }
1632
+ continue;
1633
+ }
1605
1634
  const plugin = fieldTypes && fieldTypes[field.type];
1606
1635
  const emptyValue = plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
1607
- const init = initialValues && initialValues[field.name] !== void 0 ? initialValues[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
1636
+ let init = resolved && resolved[field.name] !== void 0 ? resolved[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
1637
+ if (field.transformIn) init = field.transformIn(init);
1608
1638
  vals[field.name] = init;
1609
1639
  }
1610
1640
  return vals;
@@ -1637,7 +1667,14 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1637
1667
  formErrorsRef.current = formErrors;
1638
1668
  const fieldByName = useMemo2(() => {
1639
1669
  const map = /* @__PURE__ */ new Map();
1640
- for (const field of fields) map.set(field.name, field);
1670
+ for (const field of fields) {
1671
+ map.set(field.name, field);
1672
+ if (field.type === "fieldGroup" && field.items && field.fields) {
1673
+ for (const item of field.items) {
1674
+ for (const sf of field.fields(item)) map.set(sf.name, sf);
1675
+ }
1676
+ }
1677
+ }
1641
1678
  return map;
1642
1679
  }, [fields]);
1643
1680
  const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
@@ -2059,23 +2096,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2059
2096
  );
2060
2097
  const handleFieldInput = useCallback2(
2061
2098
  (name, value) => {
2099
+ handleFieldChange(name, value);
2062
2100
  if (!validateOnChange) return;
2063
2101
  const err = validateField(name, value);
2064
2102
  updateErrors({ [name]: err });
2065
2103
  },
2066
- [validateOnChange, validateField, updateErrors]
2104
+ [validateOnChange, validateField, updateErrors, handleFieldChange]
2067
2105
  );
2068
2106
  const handleFieldBlur = useCallback2(
2069
2107
  (name, value) => {
2070
- if (!validateOnBlur) return;
2071
2108
  const resolvedValue = value != null ? value : formValuesRef.current[name];
2109
+ if (value != null && value !== formValuesRef.current[name]) {
2110
+ handleFieldChange(name, value);
2111
+ }
2112
+ if (!validateOnBlur) return;
2072
2113
  const err = validateField(name, resolvedValue);
2073
2114
  updateErrors({ [name]: err });
2074
2115
  if (!err) {
2075
2116
  triggerAsyncValidation(name, resolvedValue);
2076
2117
  }
2077
2118
  },
2078
- [validateOnBlur, validateField, updateErrors, triggerAsyncValidation]
2119
+ [validateOnBlur, validateField, updateErrors, triggerAsyncValidation, handleFieldChange]
2079
2120
  );
2080
2121
  const handleSubmit = useCallback2(
2081
2122
  async (e) => {
@@ -2108,8 +2149,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2108
2149
  const rawValues = {};
2109
2150
  for (const key of Object.keys(formValues)) {
2110
2151
  const f = fieldByName.get(key);
2111
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
2112
- rawValues[key] = formValues[key];
2152
+ if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
2153
+ rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
2154
+ }
2155
+ for (const f of fields) {
2156
+ if (f.type !== "fieldGroup" || !f.items || !f.fields) continue;
2157
+ for (const item of f.items) {
2158
+ for (const sf of f.fields(item)) {
2159
+ if (formValues[sf.name] === void 0) continue;
2160
+ rawValues[sf.name] = sf.transformOut ? sf.transformOut(formValues[sf.name]) : formValues[sf.name];
2161
+ }
2162
+ }
2113
2163
  }
2114
2164
  const submitValues = transformValues ? transformValues(rawValues) : rawValues;
2115
2165
  if (onBeforeSubmit) {
@@ -2252,10 +2302,125 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2252
2302
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
2253
2303
  if (field.type === "display") {
2254
2304
  if (field.render) {
2255
- return field.render({ allValues: formValues });
2305
+ return field.render({
2306
+ allValues: formValues,
2307
+ setFieldValue: (name, value) => handleFieldChange(name, value),
2308
+ setFieldError: (name, message) => updateErrors({ [name]: message })
2309
+ });
2256
2310
  }
2257
2311
  return null;
2258
2312
  }
2313
+ if (field.type === "fieldGroup") {
2314
+ const items = field.items || [];
2315
+ const fieldsFn = field.fields;
2316
+ if (!fieldsFn) return null;
2317
+ const groupColumns = field.columns || 1;
2318
+ const showItemLabel = field.showItemLabel !== false;
2319
+ return /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ React2.createElement(Text2, { format: { fontWeight: "demibold" } }, field.label), field.description && /* @__PURE__ */ React2.createElement(Text2, { variant: "microcopy" }, field.description), items.map((item, itemIdx) => {
2320
+ const subFields = fieldsFn(item);
2321
+ return /* @__PURE__ */ React2.createElement(Flex2, { key: item.key || itemIdx, direction: "row", gap: "xs", align: "end" }, showItemLabel && item.label && /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, itemIdx === 0 ? /* @__PURE__ */ React2.createElement(
2322
+ Input2,
2323
+ {
2324
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
2325
+ label: "\xA0",
2326
+ value: item.label,
2327
+ readOnly: true,
2328
+ disabled: true
2329
+ }
2330
+ ) : /* @__PURE__ */ React2.createElement(
2331
+ Input2,
2332
+ {
2333
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
2334
+ value: item.label,
2335
+ readOnly: true,
2336
+ disabled: true
2337
+ }
2338
+ )), subFields.map((sf) => {
2339
+ const sfValue = formValues[sf.name];
2340
+ const sfError = formErrors[sf.name] || null;
2341
+ const sfLabel = itemIdx === 0 ? sf.label : void 0;
2342
+ const sfReadOnly = sf.readOnly || formReadOnly;
2343
+ const sfDisabled = disabled || sf.disabled || formReadOnly;
2344
+ const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
2345
+ const sfProps = {
2346
+ name: sf.name,
2347
+ label: sfLabel,
2348
+ placeholder: sf.placeholder,
2349
+ description: itemIdx === 0 ? sf.description : void 0,
2350
+ readOnly: sfReadOnly,
2351
+ disabled: sfDisabled,
2352
+ error: !!sfError,
2353
+ validationMessage: sfError || void 0,
2354
+ ...sf.fieldProps || {}
2355
+ };
2356
+ let sfElement;
2357
+ switch (sf.type) {
2358
+ case "select":
2359
+ sfElement = /* @__PURE__ */ React2.createElement(
2360
+ Select2,
2361
+ {
2362
+ ...sfProps,
2363
+ value: sfValue,
2364
+ options: resolveOptions(sf, formValues),
2365
+ onChange: sfOnChange
2366
+ }
2367
+ );
2368
+ break;
2369
+ case "number":
2370
+ sfElement = /* @__PURE__ */ React2.createElement(
2371
+ NumberInput2,
2372
+ {
2373
+ ...sfProps,
2374
+ value: sfValue,
2375
+ onChange: sfOnChange,
2376
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2377
+ }
2378
+ );
2379
+ break;
2380
+ case "toggle":
2381
+ sfElement = /* @__PURE__ */ React2.createElement(
2382
+ Toggle2,
2383
+ {
2384
+ name: sf.name,
2385
+ label: sfLabel || sf.label,
2386
+ checked: !!sfValue,
2387
+ size: sf.size || "md",
2388
+ labelDisplay: sf.labelDisplay || "top",
2389
+ readonly: sfReadOnly,
2390
+ disabled: sfDisabled,
2391
+ onChange: sfOnChange,
2392
+ ...sf.fieldProps || {}
2393
+ }
2394
+ );
2395
+ break;
2396
+ case "time":
2397
+ sfElement = /* @__PURE__ */ React2.createElement(
2398
+ TimeInput2,
2399
+ {
2400
+ ...sfProps,
2401
+ value: sfValue,
2402
+ interval: sf.interval,
2403
+ onChange: sfOnChange,
2404
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2405
+ }
2406
+ );
2407
+ break;
2408
+ default:
2409
+ sfElement = /* @__PURE__ */ React2.createElement(
2410
+ Input2,
2411
+ {
2412
+ ...sfProps,
2413
+ value: sfValue || "",
2414
+ onChange: sfOnChange,
2415
+ onInput: (v) => handleFieldInput(sf.name, v),
2416
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2417
+ }
2418
+ );
2419
+ }
2420
+ return /* @__PURE__ */ React2.createElement(Box2, { key: sf.name, flex: 1 }, sfElement);
2421
+ }));
2422
+ }));
2423
+ }
2259
2424
  if (field.type === "crmPropertyList") {
2260
2425
  return /* @__PURE__ */ React2.createElement(
2261
2426
  CrmPropertyList,
@@ -2800,38 +2965,19 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2800
2965
  }
2801
2966
  return elements;
2802
2967
  };
2803
- const renderLegacyLayout = (fieldSubset) => {
2968
+ const renderSingleColumnLayout = (fieldSubset) => {
2804
2969
  const fieldList = fieldSubset || visibleFields;
2805
- const rows = [];
2806
- let i = 0;
2807
- while (i < fieldList.length) {
2808
- const field = fieldList[i];
2809
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
2810
- rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
2811
- i += 2;
2812
- } else {
2813
- rows.push({ type: "single", field });
2814
- i++;
2815
- }
2816
- }
2817
2970
  const elements = [];
2818
2971
  const processedDeps = /* @__PURE__ */ new Set();
2819
- for (const row of rows) {
2820
- if (row.type === "pair") {
2821
- elements.push(
2822
- /* @__PURE__ */ React2.createElement(Flex2, { key: `pair-${row.fields[0].name}`, direction: "row", gap: "sm" }, /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, renderField(row.fields[0])), /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, renderField(row.fields[1])))
2823
- );
2824
- } else {
2825
- const field = row.field;
2826
- if (processedDeps.has(field.name)) continue;
2827
- elements.push(
2828
- /* @__PURE__ */ React2.createElement(React2.Fragment, { key: field.name }, renderField(field))
2829
- );
2830
- const dependents = getDependents(field);
2831
- if (dependents.length > 0) {
2832
- for (const dep of dependents) processedDeps.add(dep.name);
2833
- elements.push(renderDependentGroup(field, dependents));
2834
- }
2972
+ for (const field of fieldList) {
2973
+ if (processedDeps.has(field.name)) continue;
2974
+ elements.push(
2975
+ /* @__PURE__ */ React2.createElement(React2.Fragment, { key: field.name }, renderField(field))
2976
+ );
2977
+ const dependents = getDependents(field);
2978
+ if (dependents.length > 0) {
2979
+ for (const dep of dependents) processedDeps.add(dep.name);
2980
+ elements.push(renderDependentGroup(field, dependents));
2835
2981
  }
2836
2982
  }
2837
2983
  return elements;
@@ -2842,10 +2988,20 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2842
2988
  let batch = [];
2843
2989
  const flushBatch = () => {
2844
2990
  if (batch.length === 0) return;
2845
- const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
2846
- for (const chunk of chunks) {
2991
+ if (maxColumns) {
2992
+ const chunks = Array.from(
2993
+ { length: Math.ceil(batch.length / maxColumns) },
2994
+ (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)
2995
+ );
2996
+ for (const chunk of chunks) {
2997
+ const remainder = maxColumns - chunk.length;
2998
+ elements.push(
2999
+ /* @__PURE__ */ React2.createElement(Flex2, { key: `ag-${chunk[0].name}`, direction: "row", gap }, chunk.map((f) => /* @__PURE__ */ React2.createElement(Box2, { key: f.name, flex: 1 }, renderField(f))), remainder > 0 && /* @__PURE__ */ React2.createElement(Box2, { flex: remainder }))
3000
+ );
3001
+ }
3002
+ } else {
2847
3003
  elements.push(
2848
- /* @__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))))
3004
+ /* @__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))))
2849
3005
  );
2850
3006
  }
2851
3007
  batch = [];
@@ -2904,7 +3060,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2904
3060
  if (layout && fieldSubset === visibleFields) return renderExplicitLayout();
2905
3061
  if (columnWidth) return renderAutoGridLayout(fieldSubset);
2906
3062
  if (columns > 1) return renderGridLayout(fieldSubset);
2907
- return renderLegacyLayout(fieldSubset);
3063
+ return renderSingleColumnLayout(fieldSubset);
2908
3064
  };
2909
3065
  const renderSections = () => {
2910
3066
  const hasSections = Array.isArray(sections) && sections.length > 0;
@@ -2917,7 +3073,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2917
3073
  for (const sec of sections) {
2918
3074
  const sectionFields = sec.fields ? visibleFields.filter((f) => sec.fields.includes(f.name)) : [];
2919
3075
  if (sectionFields.length === 0) continue;
2920
- const accordionContent = /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, renderFieldSubset(sectionFields));
3076
+ const sectionContext = { values: formValues, errors: formErrors };
3077
+ const accordionContent = /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields), sec.renderAfter && sec.renderAfter(sectionContext));
2921
3078
  const accordion = /* @__PURE__ */ React2.createElement(
2922
3079
  Accordion,
2923
3080
  {
package/form.d.ts CHANGED
@@ -11,6 +11,7 @@ export {
11
11
  FormBuilderLayout,
12
12
  FormBuilderLayoutEntry,
13
13
  FormBuilderSection,
14
+ FormBuilderSectionContext,
14
15
  FormBuilderStep,
15
16
  FormBuilderRef,
16
17
  FieldTypePlugin,
package/index.d.ts CHANGED
@@ -32,6 +32,7 @@ export type {
32
32
  FormBuilderLayout,
33
33
  FormBuilderLayoutEntry,
34
34
  FormBuilderSection,
35
+ FormBuilderSectionContext,
35
36
  FormBuilderStep,
36
37
  FormBuilderRef,
37
38
  FieldTypePlugin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hs-uix",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Production-ready UI components for HubSpot UI Extensions — DataTable, FormBuilder, and more",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",