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/datatable.js CHANGED
@@ -89,12 +89,19 @@ var computeAutoWidths = (columns, data) => {
89
89
  columns.forEach((col) => {
90
90
  if (col.width && col.cellWidth) return;
91
91
  const values = sample.map((row) => row[col.field]).filter((v) => v != null);
92
- const strings = values.map((v) => String(v));
92
+ const strings = values.map((v) => {
93
+ const s = String(v);
94
+ const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
95
+ return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
96
+ });
93
97
  let widthHint = null;
94
98
  let cellWidthHint = null;
95
99
  if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
96
100
  cellWidthHint = "min";
97
101
  }
102
+ if (col.truncate === true) {
103
+ cellWidthHint = cellWidthHint || "min";
104
+ }
98
105
  if (strings.length > 0) {
99
106
  const lengths = strings.map((s) => s.length);
100
107
  const maxLen = Math.max(...lengths);
@@ -868,20 +875,26 @@ var DataTable = ({
868
875
  const rawStr = String(rawValue ?? "");
869
876
  if (col.truncate && rawStr.length > 0) {
870
877
  if (col.truncate === true) {
871
- const content2 = col.renderCell ? col.renderCell(rawValue, row) : rawStr;
878
+ if (col.renderCell) {
879
+ const content2 = col.renderCell(rawValue, row);
880
+ if (col.editable) {
881
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
882
+ }
883
+ return content2;
884
+ }
872
885
  if (col.editable) {
873
- 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 || "--"));
886
+ 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 || "--"));
874
887
  }
875
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, content2);
888
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, rawStr);
876
889
  }
877
- const maxLen = col.truncate.maxLength || 100;
890
+ const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
878
891
  if (rawStr.length > maxLen) {
879
892
  const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
880
- const truncatedContent = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
893
+ const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
881
894
  if (col.editable) {
882
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, truncatedContent || "--");
895
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
883
896
  }
884
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, truncatedContent || "--");
897
+ return col.renderCell ? content2 : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, content2 || "--");
885
898
  }
886
899
  }
887
900
  const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
@@ -986,10 +999,11 @@ var DataTable = ({
986
999
  selectedCount: selectedIds.size,
987
1000
  displayCount,
988
1001
  countLabel,
1002
+ allSelected: selectedIds.size >= (serverSide ? totalCount || data.length : allRowIds.length),
989
1003
  onSelectAll: handleSelectAllRows,
990
1004
  onDeselectAll: handleDeselectAll,
991
1005
  selectionActions
992
- }) : /* @__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(
1006
+ }) : /* @__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(
993
1007
  import_ui_extensions.Button,
994
1008
  {
995
1009
  key: i,
@@ -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,
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;
@@ -199,7 +198,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
199
198
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
200
199
  const includeCustomValidators = options.includeCustomValidators !== false;
201
200
  const msg = options.messages || {};
202
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
201
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
203
202
  const isRequired = resolveRequired(field, allValues);
204
203
  const plugin = fieldTypes && fieldTypes[field.type];
205
204
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -346,6 +345,8 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
346
345
  // (values, { reset, rawValues }) => void | Promise
347
346
  transformValues,
348
347
  // (values) => values — reshape before submit
348
+ transformInitialValues,
349
+ // (rawInitialValues) => values — reshape raw data on load
349
350
  onBeforeSubmit,
350
351
  // (values) => boolean | Promise<boolean> — intercept submit
351
352
  onSubmitSuccess,
@@ -509,12 +510,27 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
509
510
  prevSuccessRef.current = formSuccess;
510
511
  }, [addAlert, formSuccess, successTitle]);
511
512
  const computeInitialValues = () => {
513
+ const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
512
514
  const vals = {};
513
515
  for (const field of fields) {
514
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
+ }
515
530
  const plugin = fieldTypes && fieldTypes[field.type];
516
531
  const emptyValue = plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
517
- 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);
518
534
  vals[field.name] = init;
519
535
  }
520
536
  return vals;
@@ -547,7 +563,14 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
547
563
  formErrorsRef.current = formErrors;
548
564
  const fieldByName = (0, import_react.useMemo)(() => {
549
565
  const map = /* @__PURE__ */ new Map();
550
- 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
+ }
551
574
  return map;
552
575
  }, [fields]);
553
576
  const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
@@ -969,23 +992,27 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
969
992
  );
970
993
  const handleFieldInput = (0, import_react.useCallback)(
971
994
  (name, value) => {
995
+ handleFieldChange(name, value);
972
996
  if (!validateOnChange) return;
973
997
  const err = validateField(name, value);
974
998
  updateErrors({ [name]: err });
975
999
  },
976
- [validateOnChange, validateField, updateErrors]
1000
+ [validateOnChange, validateField, updateErrors, handleFieldChange]
977
1001
  );
978
1002
  const handleFieldBlur = (0, import_react.useCallback)(
979
1003
  (name, value) => {
980
- if (!validateOnBlur) return;
981
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;
982
1009
  const err = validateField(name, resolvedValue);
983
1010
  updateErrors({ [name]: err });
984
1011
  if (!err) {
985
1012
  triggerAsyncValidation(name, resolvedValue);
986
1013
  }
987
1014
  },
988
- [validateOnBlur, validateField, updateErrors, triggerAsyncValidation]
1015
+ [validateOnBlur, validateField, updateErrors, triggerAsyncValidation, handleFieldChange]
989
1016
  );
990
1017
  const handleSubmit = (0, import_react.useCallback)(
991
1018
  async (e) => {
@@ -1018,8 +1045,17 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1018
1045
  const rawValues = {};
1019
1046
  for (const key of Object.keys(formValues)) {
1020
1047
  const f = fieldByName.get(key);
1021
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
1022
- 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
+ }
1023
1059
  }
1024
1060
  const submitValues = transformValues ? transformValues(rawValues) : rawValues;
1025
1061
  if (onBeforeSubmit) {
@@ -1162,10 +1198,125 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1162
1198
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
1163
1199
  if (field.type === "display") {
1164
1200
  if (field.render) {
1165
- 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
+ });
1166
1206
  }
1167
1207
  return null;
1168
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
+ }
1169
1320
  if (field.type === "crmPropertyList") {
1170
1321
  return /* @__PURE__ */ import_react.default.createElement(
1171
1322
  import_crm.CrmPropertyList,
@@ -1710,38 +1861,19 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1710
1861
  }
1711
1862
  return elements;
1712
1863
  };
1713
- const renderLegacyLayout = (fieldSubset) => {
1864
+ const renderSingleColumnLayout = (fieldSubset) => {
1714
1865
  const fieldList = fieldSubset || visibleFields;
1715
- const rows = [];
1716
- let i = 0;
1717
- while (i < fieldList.length) {
1718
- const field = fieldList[i];
1719
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
1720
- rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
1721
- i += 2;
1722
- } else {
1723
- rows.push({ type: "single", field });
1724
- i++;
1725
- }
1726
- }
1727
1866
  const elements = [];
1728
1867
  const processedDeps = /* @__PURE__ */ new Set();
1729
- for (const row of rows) {
1730
- if (row.type === "pair") {
1731
- elements.push(
1732
- /* @__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])))
1733
- );
1734
- } else {
1735
- const field = row.field;
1736
- if (processedDeps.has(field.name)) continue;
1737
- elements.push(
1738
- /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, { key: field.name }, renderField(field))
1739
- );
1740
- const dependents = getDependents(field);
1741
- if (dependents.length > 0) {
1742
- for (const dep of dependents) processedDeps.add(dep.name);
1743
- elements.push(renderDependentGroup(field, dependents));
1744
- }
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));
1745
1877
  }
1746
1878
  }
1747
1879
  return elements;
@@ -1752,10 +1884,20 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1752
1884
  let batch = [];
1753
1885
  const flushBatch = () => {
1754
1886
  if (batch.length === 0) return;
1755
- const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
1756
- 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 {
1757
1899
  elements.push(
1758
- /* @__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))))
1759
1901
  );
1760
1902
  }
1761
1903
  batch = [];
@@ -1814,7 +1956,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1814
1956
  if (layout && fieldSubset === visibleFields) return renderExplicitLayout();
1815
1957
  if (columnWidth) return renderAutoGridLayout(fieldSubset);
1816
1958
  if (columns > 1) return renderGridLayout(fieldSubset);
1817
- return renderLegacyLayout(fieldSubset);
1959
+ return renderSingleColumnLayout(fieldSubset);
1818
1960
  };
1819
1961
  const renderSections = () => {
1820
1962
  const hasSections = Array.isArray(sections) && sections.length > 0;
@@ -1827,7 +1969,8 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
1827
1969
  for (const sec of sections) {
1828
1970
  const sectionFields = sec.fields ? visibleFields.filter((f) => sec.fields.includes(f.name)) : [];
1829
1971
  if (sectionFields.length === 0) continue;
1830
- 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));
1831
1974
  const accordion = /* @__PURE__ */ import_react.default.createElement(
1832
1975
  import_ui_extensions.Accordion,
1833
1976
  {