hs-uix 1.0.4 → 1.1.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/index.mjs CHANGED
@@ -155,12 +155,16 @@ var DataTable = ({
155
155
  // enable fuzzy matching via Fuse.js
156
156
  fuzzyOptions,
157
157
  // custom Fuse.js options (threshold, distance, etc.)
158
+ showSearch = true,
159
+ // show the SearchInput in the toolbar
158
160
  // Filters
159
161
  filters = [],
160
162
  showFilterBadges = true,
161
163
  // show active filter chips/badges
162
164
  showClearFiltersButton = true,
163
165
  // show "Clear all" filters reset button
166
+ filterInlineLimit = 2,
167
+ // number of filters shown inline before overflow
164
168
  // Pagination
165
169
  pageSize = 10,
166
170
  maxVisiblePageButtons,
@@ -242,6 +246,8 @@ var DataTable = ({
242
246
  // optional key to force clear uncontrolled selection memory
243
247
  resetSelectionOnQueryChange = true,
244
248
  // clear uncontrolled selection on search/filter/sort changes
249
+ showSelectionBar = true,
250
+ // show the selection action bar when rows are selected
245
251
  recordLabel,
246
252
  // { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
247
253
  // -----------------------------------------------------------------------
@@ -262,11 +268,31 @@ var DataTable = ({
262
268
  // (row, field, newValue) => void
263
269
  onRowEditInput,
264
270
  // optional live-input callback: (row, field, inputValue) => void
271
+ onEditStart,
272
+ // (row, field, currentValue) => void — fires when editing begins
273
+ onEditCancel,
274
+ // (row, field) => void — fires when editing is cancelled without commit
265
275
  // -----------------------------------------------------------------------
266
276
  // Auto-width
267
277
  // -----------------------------------------------------------------------
268
- autoWidth = true
278
+ autoWidth = true,
269
279
  // auto-compute column widths from content analysis
280
+ // -----------------------------------------------------------------------
281
+ // Labels / i18n
282
+ // -----------------------------------------------------------------------
283
+ labels,
284
+ // override hardcoded UI strings for i18n
285
+ // -----------------------------------------------------------------------
286
+ // Render overrides (Phase 3 — full replacement escape hatches)
287
+ // -----------------------------------------------------------------------
288
+ renderSelectionBar,
289
+ // ({ selectedIds, selectedCount, displayCount, countLabel, onSelectAll, onDeselectAll, selectionActions }) => ReactNode
290
+ renderEmptyState,
291
+ // ({ title, message }) => ReactNode
292
+ renderLoadingState,
293
+ // ({ label }) => ReactNode
294
+ renderErrorState
295
+ // ({ error, title, message }) => ReactNode
270
296
  }) => {
271
297
  const initialSortState = useMemo(() => {
272
298
  return normalizeSortState(columns, defaultSort);
@@ -500,11 +526,11 @@ var DataTable = ({
500
526
  const type = filter.type || "select";
501
527
  const prefix = filter.chipLabel || filter.placeholder || filter.name;
502
528
  if (type === "multiselect") {
503
- const labels = value.map((v) => {
529
+ const labels2 = value.map((v) => {
504
530
  var _a;
505
531
  return ((_a = filter.options.find((o) => o.value === v)) == null ? void 0 : _a.label) || v;
506
532
  }).join(", ");
507
- chips.push({ key: filter.name, label: `${prefix}: ${labels}` });
533
+ chips.push({ key: filter.name, label: `${prefix}: ${labels2}` });
508
534
  } else if (type === "dateRange") {
509
535
  const parts = [];
510
536
  if (value.from) parts.push(`from ${formatDateChip(value.from)}`);
@@ -545,7 +571,17 @@ var DataTable = ({
545
571
  const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
546
572
  const resolvedEmptyTitle = emptyTitle || "No results found";
547
573
  const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
548
- const resolvedLoadingLabel = `Loading ${pluralLabel}...`;
574
+ const resolvedSelectedLabel = (labels == null ? void 0 : labels.selected) || ((count, label) => `${count}\xA0${label}\xA0selected`);
575
+ const resolvedSelectAllLabel = (labels == null ? void 0 : labels.selectAll) || ((count, label) => `Select all ${count} ${label}`);
576
+ const resolvedDeselectAllLabel = (labels == null ? void 0 : labels.deselectAll) || "Deselect all";
577
+ const resolvedFiltersButtonLabel = (labels == null ? void 0 : labels.filtersButton) || "Filters";
578
+ const resolvedClearAllLabel = (labels == null ? void 0 : labels.clearAll) || "Clear all";
579
+ const resolvedDateFromLabel = (labels == null ? void 0 : labels.dateFrom) || "From";
580
+ const resolvedDateToLabel = (labels == null ? void 0 : labels.dateTo) || "To";
581
+ const resolvedLoadingLabel = (labels == null ? void 0 : labels.loading) || `Loading ${pluralLabel}...`;
582
+ const resolvedErrorTitle = (labels == null ? void 0 : labels.errorTitle) || "Something went wrong.";
583
+ const resolvedErrorMessage = (labels == null ? void 0 : labels.errorMessage) || "An error occurred while loading data.";
584
+ const resolvedRetryMessage = (labels == null ? void 0 : labels.retryMessage) || "Please try again.";
549
585
  const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
550
586
  const [internalSelectedIds, setInternalSelectedIds] = useState(/* @__PURE__ */ new Set());
551
587
  const selectionResetRef = useRef("");
@@ -634,7 +670,11 @@ var DataTable = ({
634
670
  setEditingCell({ rowId, field });
635
671
  setEditValue(currentValue);
636
672
  setEditError(null);
637
- }, []);
673
+ if (onEditStart) {
674
+ const row = data.find((r) => r[rowIdField] === rowId);
675
+ if (row) onEditStart(row, field, currentValue);
676
+ }
677
+ }, [onEditStart, data, rowIdField]);
638
678
  const commitEdit = useCallback((row, field, value) => {
639
679
  const col = columns.find((c) => c.field === field);
640
680
  if (col == null ? void 0 : col.editValidate) {
@@ -656,6 +696,7 @@ var DataTable = ({
656
696
  const commit = (val) => commitEdit(row, col.field, val);
657
697
  const exitEdit = () => {
658
698
  if (editError) return;
699
+ if (onEditCancel) onEditCancel(row, col.field);
659
700
  setEditingCell(null);
660
701
  setEditValue(null);
661
702
  };
@@ -875,7 +916,7 @@ var DataTable = ({
875
916
  {
876
917
  name: `filter-${filter.name}-from`,
877
918
  label: "",
878
- placeholder: "From",
919
+ placeholder: resolvedDateFromLabel,
879
920
  format: "medium",
880
921
  value: rangeVal.from,
881
922
  onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, from: val })
@@ -886,7 +927,7 @@ var DataTable = ({
886
927
  size: "sm",
887
928
  name: `filter-${filter.name}-to`,
888
929
  label: "",
889
- placeholder: "To",
930
+ placeholder: resolvedDateToLabel,
890
931
  format: "medium",
891
932
  value: rangeVal.to,
892
933
  onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, to: val })
@@ -909,7 +950,7 @@ var DataTable = ({
909
950
  }
910
951
  );
911
952
  };
912
- return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, searchFields.length > 0 && /* @__PURE__ */ React.createElement(
953
+ return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ React.createElement(
913
954
  SearchInput,
914
955
  {
915
956
  name: "datatable-search",
@@ -917,7 +958,7 @@ var DataTable = ({
917
958
  value: searchTerm,
918
959
  onChange: handleSearchChange
919
960
  }
920
- ), filters.slice(0, 2).map(renderFilterControl), filters.length > 2 && /* @__PURE__ */ React.createElement(
961
+ ), filters.slice(0, filterInlineLimit).map(renderFilterControl), filters.length > filterInlineLimit && /* @__PURE__ */ React.createElement(
921
962
  Button,
922
963
  {
923
964
  variant: "transparent",
@@ -925,16 +966,25 @@ var DataTable = ({
925
966
  onClick: () => setShowMoreFilters((prev) => !prev)
926
967
  },
927
968
  /* @__PURE__ */ React.createElement(Icon, { name: "filter", size: "sm" }),
928
- " Filters"
929
- )), showMoreFilters && filters.length > 2 && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "end", gap: "sm", wrap: "wrap" }, filters.slice(2).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ React.createElement(Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ React.createElement(
969
+ " ",
970
+ resolvedFiltersButtonLabel
971
+ )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "end", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ React.createElement(Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ React.createElement(
930
972
  Button,
931
973
  {
932
974
  variant: "transparent",
933
975
  size: "extra-small",
934
976
  onClick: () => handleFilterRemove("all")
935
977
  },
936
- "Clear all"
937
- )))), showRowCount && displayCount > 0 && !(selectable && selectedIds.size > 0) && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), selectable && selectedIds.size > 0 && /* @__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" } }, selectedIds.size, "\xA0", countLabel(selectedIds.size), "\xA0selected"), /* @__PURE__ */ React.createElement(Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, "Select all ", displayCount, " ", countLabel(displayCount)), /* @__PURE__ */ React.createElement(Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, "Deselect all"), selectionActions.map((action, i) => /* @__PURE__ */ React.createElement(
978
+ resolvedClearAllLabel
979
+ )))), showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0) && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
980
+ selectedIds,
981
+ selectedCount: selectedIds.size,
982
+ displayCount,
983
+ countLabel,
984
+ onSelectAll: handleSelectAllRows,
985
+ onDeselectAll: handleDeselectAll,
986
+ 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(
938
988
  Button,
939
989
  {
940
990
  key: i,
@@ -945,7 +995,11 @@ var DataTable = ({
945
995
  action.icon && /* @__PURE__ */ React.createElement(Icon, { name: action.icon, size: "sm" }),
946
996
  " ",
947
997
  action.label
948
- )))), showRowCount && displayCount > 0 && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), loading ? /* @__PURE__ */ React.createElement(LoadingSpinner, { label: resolvedLoadingLabel, layout: "centered" }) : error ? /* @__PURE__ */ React.createElement(ErrorState, { title: typeof error === "string" ? error : "Something went wrong." }, /* @__PURE__ */ React.createElement(Text, null, typeof error === "string" ? "Please try again." : "An error occurred while loading data.")) : displayRows.length === 0 ? /* @__PURE__ */ React.createElement(Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React.createElement(EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ React.createElement(Text, null, resolvedEmptyMessage))) : /* @__PURE__ */ React.createElement(
998
+ )))), showRowCount && displayCount > 0 && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel))))), loading ? renderLoadingState ? renderLoadingState({ label: resolvedLoadingLabel }) : /* @__PURE__ */ React.createElement(LoadingSpinner, { label: resolvedLoadingLabel, layout: "centered" }) : error ? renderErrorState ? renderErrorState({
999
+ error,
1000
+ title: typeof error === "string" ? error : resolvedErrorTitle,
1001
+ message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
1002
+ }) : /* @__PURE__ */ React.createElement(ErrorState, { title: typeof error === "string" ? error : resolvedErrorTitle }, /* @__PURE__ */ React.createElement(Text, null, typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage)) : displayRows.length === 0 ? renderEmptyState ? renderEmptyState({ title: resolvedEmptyTitle, message: resolvedEmptyMessage }) : /* @__PURE__ */ React.createElement(Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React.createElement(EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ React.createElement(Text, null, resolvedEmptyMessage))) : /* @__PURE__ */ React.createElement(
949
1003
  Table,
950
1004
  {
951
1005
  bordered,
@@ -1234,12 +1288,14 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
1234
1288
  };
1235
1289
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1236
1290
  const includeCustomValidators = options.includeCustomValidators !== false;
1291
+ const msg = options.messages || {};
1237
1292
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
1238
1293
  const isRequired = resolveRequired(field, allValues);
1239
1294
  const plugin = fieldTypes && fieldTypes[field.type];
1240
1295
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
1241
1296
  if (isRequired && empty) {
1242
- return `${field.label} is required`;
1297
+ const fn = msg.required || ((label) => `${label} is required`);
1298
+ return typeof fn === "function" ? fn(field.label) : fn;
1243
1299
  }
1244
1300
  if (empty) return null;
1245
1301
  if (field.useDefaultValidators !== false) {
@@ -1248,23 +1304,27 @@ var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1248
1304
  }
1249
1305
  if (field.pattern && typeof value === "string") {
1250
1306
  if (!field.pattern.test(value)) {
1251
- return field.patternMessage || "Invalid format";
1307
+ return field.patternMessage || msg.invalidFormat || "Invalid format";
1252
1308
  }
1253
1309
  }
1254
1310
  if (typeof value === "string") {
1255
1311
  if (field.minLength != null && value.length < field.minLength) {
1256
- return `Must be at least ${field.minLength} characters`;
1312
+ const fn = msg.minLength || ((min) => `Must be at least ${min} characters`);
1313
+ return typeof fn === "function" ? fn(field.minLength) : fn;
1257
1314
  }
1258
1315
  if (field.maxLength != null && value.length > field.maxLength) {
1259
- return `Must be no more than ${field.maxLength} characters`;
1316
+ const fn = msg.maxLength || ((max) => `Must be no more than ${max} characters`);
1317
+ return typeof fn === "function" ? fn(field.maxLength) : fn;
1260
1318
  }
1261
1319
  }
1262
1320
  if (typeof value === "number") {
1263
1321
  if (field.min != null && value < field.min) {
1264
- return `Must be at least ${field.min}`;
1322
+ const fn = msg.minValue || ((min) => `Must be at least ${min}`);
1323
+ return typeof fn === "function" ? fn(field.min) : fn;
1265
1324
  }
1266
1325
  if (field.max != null && value > field.max) {
1267
- return `Must be no more than ${field.max}`;
1326
+ const fn = msg.maxValue || ((max) => `Must be no more than ${max}`);
1327
+ return typeof fn === "function" ? fn(field.max) : fn;
1268
1328
  }
1269
1329
  }
1270
1330
  if (field.type === "date" && isDateValueObject(value)) {
@@ -1470,8 +1530,18 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1470
1530
  // string — warning alert when readOnly
1471
1531
  alerts,
1472
1532
  // { addAlert, readOnlyTitle, errorTitle, successTitle }
1473
- errors: controlledErrors
1533
+ errors: controlledErrors,
1474
1534
  // controlled validation errors
1535
+ showReadOnlyAlert = true,
1536
+ // show warning Alert when readOnly is true
1537
+ showInlineAlerts = true,
1538
+ // show inline form-level error/success Alerts
1539
+ renderReadOnlyAlert,
1540
+ // (context: { title, message }) => ReactNode — custom readOnly alert renderer
1541
+ renderFieldError,
1542
+ // (error: string, field: object) => ReactNode — custom field error renderer
1543
+ defaultCurrency = "USD"
1544
+ // form-level default ISO 4217 currency code for currency fields
1475
1545
  } = props;
1476
1546
  const {
1477
1547
  onDirtyChange,
@@ -1483,6 +1553,23 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1483
1553
  const cancelButtonLabel = (labels == null ? void 0 : labels.cancel) || "Cancel";
1484
1554
  const backButtonLabel = (labels == null ? void 0 : labels.back) || "Back";
1485
1555
  const nextButtonLabel = (labels == null ? void 0 : labels.next) || "Next";
1556
+ const requiredMessage = (labels == null ? void 0 : labels.required) || ((label) => `${label} is required`);
1557
+ const invalidFormatMessage = (labels == null ? void 0 : labels.invalidFormat) || "Invalid format";
1558
+ const minLengthMessage = (labels == null ? void 0 : labels.minLength) || ((min) => `Must be at least ${min} characters`);
1559
+ const maxLengthMessage = (labels == null ? void 0 : labels.maxLength) || ((max) => `Must be no more than ${max} characters`);
1560
+ const minValueMessage = (labels == null ? void 0 : labels.minValue) || ((min) => `Must be at least ${min}`);
1561
+ const maxValueMessage = (labels == null ? void 0 : labels.maxValue) || ((max) => `Must be no more than ${max}`);
1562
+ const dependentPropertiesLabel = (labels == null ? void 0 : labels.dependentProperties) || "Dependent properties";
1563
+ const repeaterAddLabel = (labels == null ? void 0 : labels.repeaterAdd) || "Add";
1564
+ const repeaterRemoveLabel = (labels == null ? void 0 : labels.repeaterRemove) || "Remove";
1565
+ const validationMessages = labels ? {
1566
+ required: requiredMessage,
1567
+ invalidFormat: invalidFormatMessage,
1568
+ minLength: minLengthMessage,
1569
+ maxLength: maxLengthMessage,
1570
+ minValue: minValueMessage,
1571
+ maxValue: maxValueMessage
1572
+ } : void 0;
1486
1573
  const addAlert = alerts == null ? void 0 : alerts.addAlert;
1487
1574
  const readOnlyTitle = (alerts == null ? void 0 : alerts.readOnlyTitle) || "Read Only";
1488
1575
  const errorTitle = (alerts == null ? void 0 : alerts.errorTitle) || "Error";
@@ -1721,7 +1808,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1721
1808
  const rowValues = { ...allValues, [field.name]: rows };
1722
1809
  subFields.forEach((subField) => {
1723
1810
  if (subField.visible && !subField.visible(rowValues)) return;
1724
- const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes);
1811
+ const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes, { messages: validationMessages });
1725
1812
  if (!err) return;
1726
1813
  const key = getRepeaterErrorKey(field.name, rowIdx, subField.name);
1727
1814
  errors[key] = err;
@@ -1748,9 +1835,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1748
1835
  );
1749
1836
  return repeaterResult.errors[name] || null;
1750
1837
  }
1751
- return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes);
1838
+ return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes, { messages: validationMessages });
1752
1839
  },
1753
- [fieldByName, formValues, validateRepeaterField, fieldTypes]
1840
+ [fieldByName, formValues, validateRepeaterField, fieldTypes, validationMessages]
1754
1841
  );
1755
1842
  const validateVisibleFields = useCallback2(
1756
1843
  (fieldSubset) => {
@@ -1766,7 +1853,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1766
1853
  }
1767
1854
  continue;
1768
1855
  }
1769
- const err = runValidators(formValues[field.name], field, formValues, fieldTypes);
1856
+ const err = runValidators(formValues[field.name], field, formValues, fieldTypes, { messages: validationMessages });
1770
1857
  if (err) {
1771
1858
  errors[field.name] = err;
1772
1859
  hasErrors = true;
@@ -1774,14 +1861,14 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1774
1861
  }
1775
1862
  return { errors, hasErrors };
1776
1863
  },
1777
- [visibleFields, formValues, validateRepeaterField, fieldTypes]
1864
+ [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
1778
1865
  );
1779
1866
  const runAsyncValidation = useCallback2(
1780
1867
  (name, value) => {
1781
1868
  const field = fieldByName.get(name);
1782
1869
  if (!field || field.type === "repeater") return null;
1783
1870
  const val = value != null ? value : formValues[name];
1784
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false });
1871
+ const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
1785
1872
  const prevController = asyncAbortRef.current.get(name);
1786
1873
  if (prevController) prevController.abort();
1787
1874
  asyncAbortRef.current.delete(name);
@@ -2150,6 +2237,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2150
2237
  [replaceErrors]
2151
2238
  );
2152
2239
  const renderField = (field) => {
2240
+ const fieldError = formErrors[field.name] || null;
2241
+ const rendered = renderFieldInner(field);
2242
+ if (!renderFieldError || !fieldError) return rendered;
2243
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, rendered, renderFieldError(fieldError, field));
2244
+ };
2245
+ const renderFieldInner = (field) => {
2153
2246
  const fieldValue = formValues[field.name];
2154
2247
  const fieldError = formErrors[field.name] || null;
2155
2248
  const hasError = !!fieldError;
@@ -2216,7 +2309,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2216
2309
  readOnly: isReadOnly,
2217
2310
  disabled: isDisabled,
2218
2311
  error: hasError,
2219
- validationMessage: fieldError || void 0,
2312
+ validationMessage: renderFieldError ? void 0 : fieldError || void 0,
2220
2313
  ...field.loading || validatingFields[field.name] ? { loading: true } : {},
2221
2314
  ...field.fieldProps || {}
2222
2315
  };
@@ -2286,7 +2379,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2286
2379
  CurrencyInput2,
2287
2380
  {
2288
2381
  ...commonProps,
2289
- currency: field.currency || "USD",
2382
+ currency: field.currency || defaultCurrency,
2290
2383
  value: fieldValue,
2291
2384
  min: field.min,
2292
2385
  max: field.max,
@@ -2464,8 +2557,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2464
2557
  const renderRemoveControl = repeaterProps.renderRemove;
2465
2558
  const renderMoveUpControl = repeaterProps.renderMoveUp;
2466
2559
  const renderMoveDownControl = repeaterProps.renderMoveDown;
2467
- const addLabel = repeaterProps.addLabel || "Add";
2468
- const removeLabel = repeaterProps.removeLabel || "Remove";
2560
+ const addLabel = repeaterProps.addLabel || repeaterAddLabel;
2561
+ const removeLabel = repeaterProps.removeLabel || repeaterRemoveLabel;
2469
2562
  const moveUpLabel = repeaterProps.moveUpLabel || "Up";
2470
2563
  const moveDownLabel = repeaterProps.moveDownLabel || "Down";
2471
2564
  const canEditRows = !isReadOnly && !isDisabled;
@@ -2499,7 +2592,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2499
2592
  };
2500
2593
  const validateSubField = (rowIdx, subField, subValue, nextRows) => {
2501
2594
  const rowValues = { ...formValues, [field.name]: nextRows };
2502
- const err = runValidators(subValue, subField, rowValues, fieldTypes);
2595
+ const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
2503
2596
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
2504
2597
  };
2505
2598
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
@@ -2617,7 +2710,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2617
2710
  const renderDependentGroup = (parentField, dependents) => {
2618
2711
  const firstWithLabel = dependents.find((f) => getDependsOnLabel(f)) || dependents[0];
2619
2712
  const firstWithMessage = dependents.find((f) => getDependsOnMessage(f)) || dependents[0];
2620
- const groupLabel = getDependsOnLabel(firstWithLabel) || "Dependent properties";
2713
+ const groupLabel = getDependsOnLabel(firstWithLabel) || dependentPropertiesLabel;
2621
2714
  const rawMessage = getDependsOnMessage(firstWithMessage);
2622
2715
  const tooltipMessage = typeof rawMessage === "function" ? rawMessage(parentField.label) : rawMessage || "";
2623
2716
  return /* @__PURE__ */ React2.createElement(Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React2.createElement(Text2, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ React2.createElement(Link2, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React2.createElement(Tooltip, null, tooltipMessage) }, /* @__PURE__ */ React2.createElement(Icon2, { name: "info" })))), renderFieldSubset(dependents)));
@@ -2917,7 +3010,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2917
3010
  currentStep,
2918
3011
  stepNames: steps.map((s) => s.title)
2919
3012
  }
2920
- ), formReadOnly && readOnlyMessage && /* @__PURE__ */ React2.createElement(Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage), !addAlert && formError && /* @__PURE__ */ React2.createElement(Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), !addAlert && formSuccess && /* @__PURE__ */ React2.createElement(Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
3013
+ ), showReadOnlyAlert && formReadOnly && readOnlyMessage && (renderReadOnlyAlert ? renderReadOnlyAlert({ title: readOnlyTitle, message: readOnlyMessage }) : /* @__PURE__ */ React2.createElement(Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage)), showInlineAlerts && !addAlert && formError && /* @__PURE__ */ React2.createElement(Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), showInlineAlerts && !addAlert && formSuccess && /* @__PURE__ */ React2.createElement(Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
2921
3014
  values: formValues,
2922
3015
  goNext: handleNext,
2923
3016
  goBack: handleBack,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hs-uix",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
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",