hs-uix 1.0.3 → 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.js CHANGED
@@ -162,12 +162,16 @@ var DataTable = ({
162
162
  // enable fuzzy matching via Fuse.js
163
163
  fuzzyOptions,
164
164
  // custom Fuse.js options (threshold, distance, etc.)
165
+ showSearch = true,
166
+ // show the SearchInput in the toolbar
165
167
  // Filters
166
168
  filters = [],
167
169
  showFilterBadges = true,
168
170
  // show active filter chips/badges
169
171
  showClearFiltersButton = true,
170
172
  // show "Clear all" filters reset button
173
+ filterInlineLimit = 2,
174
+ // number of filters shown inline before overflow
171
175
  // Pagination
172
176
  pageSize = 10,
173
177
  maxVisiblePageButtons,
@@ -249,6 +253,8 @@ var DataTable = ({
249
253
  // optional key to force clear uncontrolled selection memory
250
254
  resetSelectionOnQueryChange = true,
251
255
  // clear uncontrolled selection on search/filter/sort changes
256
+ showSelectionBar = true,
257
+ // show the selection action bar when rows are selected
252
258
  recordLabel,
253
259
  // { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
254
260
  // -----------------------------------------------------------------------
@@ -269,11 +275,31 @@ var DataTable = ({
269
275
  // (row, field, newValue) => void
270
276
  onRowEditInput,
271
277
  // optional live-input callback: (row, field, inputValue) => void
278
+ onEditStart,
279
+ // (row, field, currentValue) => void — fires when editing begins
280
+ onEditCancel,
281
+ // (row, field) => void — fires when editing is cancelled without commit
272
282
  // -----------------------------------------------------------------------
273
283
  // Auto-width
274
284
  // -----------------------------------------------------------------------
275
- autoWidth = true
285
+ autoWidth = true,
276
286
  // auto-compute column widths from content analysis
287
+ // -----------------------------------------------------------------------
288
+ // Labels / i18n
289
+ // -----------------------------------------------------------------------
290
+ labels,
291
+ // override hardcoded UI strings for i18n
292
+ // -----------------------------------------------------------------------
293
+ // Render overrides (Phase 3 — full replacement escape hatches)
294
+ // -----------------------------------------------------------------------
295
+ renderSelectionBar,
296
+ // ({ selectedIds, selectedCount, displayCount, countLabel, onSelectAll, onDeselectAll, selectionActions }) => ReactNode
297
+ renderEmptyState,
298
+ // ({ title, message }) => ReactNode
299
+ renderLoadingState,
300
+ // ({ label }) => ReactNode
301
+ renderErrorState
302
+ // ({ error, title, message }) => ReactNode
277
303
  }) => {
278
304
  const initialSortState = (0, import_react.useMemo)(() => {
279
305
  return normalizeSortState(columns, defaultSort);
@@ -507,11 +533,11 @@ var DataTable = ({
507
533
  const type = filter.type || "select";
508
534
  const prefix = filter.chipLabel || filter.placeholder || filter.name;
509
535
  if (type === "multiselect") {
510
- const labels = value.map((v) => {
536
+ const labels2 = value.map((v) => {
511
537
  var _a;
512
538
  return ((_a = filter.options.find((o) => o.value === v)) == null ? void 0 : _a.label) || v;
513
539
  }).join(", ");
514
- chips.push({ key: filter.name, label: `${prefix}: ${labels}` });
540
+ chips.push({ key: filter.name, label: `${prefix}: ${labels2}` });
515
541
  } else if (type === "dateRange") {
516
542
  const parts = [];
517
543
  if (value.from) parts.push(`from ${formatDateChip(value.from)}`);
@@ -552,7 +578,17 @@ var DataTable = ({
552
578
  const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
553
579
  const resolvedEmptyTitle = emptyTitle || "No results found";
554
580
  const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
555
- const resolvedLoadingLabel = `Loading ${pluralLabel}...`;
581
+ const resolvedSelectedLabel = (labels == null ? void 0 : labels.selected) || ((count, label) => `${count}\xA0${label}\xA0selected`);
582
+ const resolvedSelectAllLabel = (labels == null ? void 0 : labels.selectAll) || ((count, label) => `Select all ${count} ${label}`);
583
+ const resolvedDeselectAllLabel = (labels == null ? void 0 : labels.deselectAll) || "Deselect all";
584
+ const resolvedFiltersButtonLabel = (labels == null ? void 0 : labels.filtersButton) || "Filters";
585
+ const resolvedClearAllLabel = (labels == null ? void 0 : labels.clearAll) || "Clear all";
586
+ const resolvedDateFromLabel = (labels == null ? void 0 : labels.dateFrom) || "From";
587
+ const resolvedDateToLabel = (labels == null ? void 0 : labels.dateTo) || "To";
588
+ const resolvedLoadingLabel = (labels == null ? void 0 : labels.loading) || `Loading ${pluralLabel}...`;
589
+ const resolvedErrorTitle = (labels == null ? void 0 : labels.errorTitle) || "Something went wrong.";
590
+ const resolvedErrorMessage = (labels == null ? void 0 : labels.errorMessage) || "An error occurred while loading data.";
591
+ const resolvedRetryMessage = (labels == null ? void 0 : labels.retryMessage) || "Please try again.";
556
592
  const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
557
593
  const [internalSelectedIds, setInternalSelectedIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
558
594
  const selectionResetRef = (0, import_react.useRef)("");
@@ -641,7 +677,11 @@ var DataTable = ({
641
677
  setEditingCell({ rowId, field });
642
678
  setEditValue(currentValue);
643
679
  setEditError(null);
644
- }, []);
680
+ if (onEditStart) {
681
+ const row = data.find((r) => r[rowIdField] === rowId);
682
+ if (row) onEditStart(row, field, currentValue);
683
+ }
684
+ }, [onEditStart, data, rowIdField]);
645
685
  const commitEdit = (0, import_react.useCallback)((row, field, value) => {
646
686
  const col = columns.find((c) => c.field === field);
647
687
  if (col == null ? void 0 : col.editValidate) {
@@ -663,6 +703,7 @@ var DataTable = ({
663
703
  const commit = (val) => commitEdit(row, col.field, val);
664
704
  const exitEdit = () => {
665
705
  if (editError) return;
706
+ if (onEditCancel) onEditCancel(row, col.field);
666
707
  setEditingCell(null);
667
708
  setEditValue(null);
668
709
  };
@@ -882,7 +923,7 @@ var DataTable = ({
882
923
  {
883
924
  name: `filter-${filter.name}-from`,
884
925
  label: "",
885
- placeholder: "From",
926
+ placeholder: resolvedDateFromLabel,
886
927
  format: "medium",
887
928
  value: rangeVal.from,
888
929
  onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, from: val })
@@ -893,7 +934,7 @@ var DataTable = ({
893
934
  size: "sm",
894
935
  name: `filter-${filter.name}-to`,
895
936
  label: "",
896
- placeholder: "To",
937
+ placeholder: resolvedDateToLabel,
897
938
  format: "medium",
898
939
  value: rangeVal.to,
899
940
  onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, to: val })
@@ -916,7 +957,7 @@ var DataTable = ({
916
957
  }
917
958
  );
918
959
  };
919
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, /* @__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: "column", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, searchFields.length > 0 && /* @__PURE__ */ import_react.default.createElement(
960
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, /* @__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: "column", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ import_react.default.createElement(
920
961
  import_ui_extensions.SearchInput,
921
962
  {
922
963
  name: "datatable-search",
@@ -924,7 +965,7 @@ var DataTable = ({
924
965
  value: searchTerm,
925
966
  onChange: handleSearchChange
926
967
  }
927
- ), filters.slice(0, 2).map(renderFilterControl), filters.length > 2 && /* @__PURE__ */ import_react.default.createElement(
968
+ ), filters.slice(0, filterInlineLimit).map(renderFilterControl), filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(
928
969
  import_ui_extensions.Button,
929
970
  {
930
971
  variant: "transparent",
@@ -932,16 +973,25 @@ var DataTable = ({
932
973
  onClick: () => setShowMoreFilters((prev) => !prev)
933
974
  },
934
975
  /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "filter", size: "sm" }),
935
- " Filters"
936
- )), showMoreFilters && filters.length > 2 && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "end", gap: "sm", wrap: "wrap" }, filters.slice(2).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ import_react.default.createElement(
976
+ " ",
977
+ resolvedFiltersButtonLabel
978
+ )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "end", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ import_react.default.createElement(
937
979
  import_ui_extensions.Button,
938
980
  {
939
981
  variant: "transparent",
940
982
  size: "extra-small",
941
983
  onClick: () => handleFilterRemove("all")
942
984
  },
943
- "Clear all"
944
- )))), showRowCount && displayCount > 0 && !(selectable && selectedIds.size > 0) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), selectable && selectedIds.size > 0 && /* @__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" } }, selectedIds.size, "\xA0", countLabel(selectedIds.size), "\xA0selected"), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, "Select all ", displayCount, " ", countLabel(displayCount)), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, "Deselect all"), selectionActions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
985
+ resolvedClearAllLabel
986
+ )))), showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
987
+ selectedIds,
988
+ selectedCount: selectedIds.size,
989
+ displayCount,
990
+ countLabel,
991
+ onSelectAll: handleSelectAllRows,
992
+ onDeselectAll: handleDeselectAll,
993
+ 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(
945
995
  import_ui_extensions.Button,
946
996
  {
947
997
  key: i,
@@ -952,7 +1002,11 @@ var DataTable = ({
952
1002
  action.icon && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: action.icon, size: "sm" }),
953
1003
  " ",
954
1004
  action.label
955
- )))), showRowCount && displayCount > 0 && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), loading ? /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.LoadingSpinner, { label: resolvedLoadingLabel, layout: "centered" }) : error ? /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.ErrorState, { title: typeof error === "string" ? error : "Something went wrong." }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, null, typeof error === "string" ? "Please try again." : "An error occurred while loading data.")) : displayRows.length === 0 ? /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, null, resolvedEmptyMessage))) : /* @__PURE__ */ import_react.default.createElement(
1005
+ )))), showRowCount && displayCount > 0 && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel))))), loading ? renderLoadingState ? renderLoadingState({ label: resolvedLoadingLabel }) : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.LoadingSpinner, { label: resolvedLoadingLabel, layout: "centered" }) : error ? renderErrorState ? renderErrorState({
1006
+ error,
1007
+ title: typeof error === "string" ? error : resolvedErrorTitle,
1008
+ message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
1009
+ }) : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.ErrorState, { title: typeof error === "string" ? error : resolvedErrorTitle }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, null, typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage)) : displayRows.length === 0 ? renderEmptyState ? renderEmptyState({ title: resolvedEmptyTitle, message: resolvedEmptyMessage }) : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, null, resolvedEmptyMessage))) : /* @__PURE__ */ import_react.default.createElement(
956
1010
  import_ui_extensions.Table,
957
1011
  {
958
1012
  bordered,
@@ -1201,12 +1255,14 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
1201
1255
  };
1202
1256
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1203
1257
  const includeCustomValidators = options.includeCustomValidators !== false;
1258
+ const msg = options.messages || {};
1204
1259
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
1205
1260
  const isRequired = resolveRequired(field, allValues);
1206
1261
  const plugin = fieldTypes && fieldTypes[field.type];
1207
1262
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
1208
1263
  if (isRequired && empty) {
1209
- return `${field.label} is required`;
1264
+ const fn = msg.required || ((label) => `${label} is required`);
1265
+ return typeof fn === "function" ? fn(field.label) : fn;
1210
1266
  }
1211
1267
  if (empty) return null;
1212
1268
  if (field.useDefaultValidators !== false) {
@@ -1215,23 +1271,27 @@ var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1215
1271
  }
1216
1272
  if (field.pattern && typeof value === "string") {
1217
1273
  if (!field.pattern.test(value)) {
1218
- return field.patternMessage || "Invalid format";
1274
+ return field.patternMessage || msg.invalidFormat || "Invalid format";
1219
1275
  }
1220
1276
  }
1221
1277
  if (typeof value === "string") {
1222
1278
  if (field.minLength != null && value.length < field.minLength) {
1223
- return `Must be at least ${field.minLength} characters`;
1279
+ const fn = msg.minLength || ((min) => `Must be at least ${min} characters`);
1280
+ return typeof fn === "function" ? fn(field.minLength) : fn;
1224
1281
  }
1225
1282
  if (field.maxLength != null && value.length > field.maxLength) {
1226
- return `Must be no more than ${field.maxLength} characters`;
1283
+ const fn = msg.maxLength || ((max) => `Must be no more than ${max} characters`);
1284
+ return typeof fn === "function" ? fn(field.maxLength) : fn;
1227
1285
  }
1228
1286
  }
1229
1287
  if (typeof value === "number") {
1230
1288
  if (field.min != null && value < field.min) {
1231
- return `Must be at least ${field.min}`;
1289
+ const fn = msg.minValue || ((min) => `Must be at least ${min}`);
1290
+ return typeof fn === "function" ? fn(field.min) : fn;
1232
1291
  }
1233
1292
  if (field.max != null && value > field.max) {
1234
- return `Must be no more than ${field.max}`;
1293
+ const fn = msg.maxValue || ((max) => `Must be no more than ${max}`);
1294
+ return typeof fn === "function" ? fn(field.max) : fn;
1235
1295
  }
1236
1296
  }
1237
1297
  if (field.type === "date" && isDateValueObject(value)) {
@@ -1437,8 +1497,18 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1437
1497
  // string — warning alert when readOnly
1438
1498
  alerts,
1439
1499
  // { addAlert, readOnlyTitle, errorTitle, successTitle }
1440
- errors: controlledErrors
1500
+ errors: controlledErrors,
1441
1501
  // controlled validation errors
1502
+ showReadOnlyAlert = true,
1503
+ // show warning Alert when readOnly is true
1504
+ showInlineAlerts = true,
1505
+ // show inline form-level error/success Alerts
1506
+ renderReadOnlyAlert,
1507
+ // (context: { title, message }) => ReactNode — custom readOnly alert renderer
1508
+ renderFieldError,
1509
+ // (error: string, field: object) => ReactNode — custom field error renderer
1510
+ defaultCurrency = "USD"
1511
+ // form-level default ISO 4217 currency code for currency fields
1442
1512
  } = props;
1443
1513
  const {
1444
1514
  onDirtyChange,
@@ -1450,6 +1520,23 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1450
1520
  const cancelButtonLabel = (labels == null ? void 0 : labels.cancel) || "Cancel";
1451
1521
  const backButtonLabel = (labels == null ? void 0 : labels.back) || "Back";
1452
1522
  const nextButtonLabel = (labels == null ? void 0 : labels.next) || "Next";
1523
+ const requiredMessage = (labels == null ? void 0 : labels.required) || ((label) => `${label} is required`);
1524
+ const invalidFormatMessage = (labels == null ? void 0 : labels.invalidFormat) || "Invalid format";
1525
+ const minLengthMessage = (labels == null ? void 0 : labels.minLength) || ((min) => `Must be at least ${min} characters`);
1526
+ const maxLengthMessage = (labels == null ? void 0 : labels.maxLength) || ((max) => `Must be no more than ${max} characters`);
1527
+ const minValueMessage = (labels == null ? void 0 : labels.minValue) || ((min) => `Must be at least ${min}`);
1528
+ const maxValueMessage = (labels == null ? void 0 : labels.maxValue) || ((max) => `Must be no more than ${max}`);
1529
+ const dependentPropertiesLabel = (labels == null ? void 0 : labels.dependentProperties) || "Dependent properties";
1530
+ const repeaterAddLabel = (labels == null ? void 0 : labels.repeaterAdd) || "Add";
1531
+ const repeaterRemoveLabel = (labels == null ? void 0 : labels.repeaterRemove) || "Remove";
1532
+ const validationMessages = labels ? {
1533
+ required: requiredMessage,
1534
+ invalidFormat: invalidFormatMessage,
1535
+ minLength: minLengthMessage,
1536
+ maxLength: maxLengthMessage,
1537
+ minValue: minValueMessage,
1538
+ maxValue: maxValueMessage
1539
+ } : void 0;
1453
1540
  const addAlert = alerts == null ? void 0 : alerts.addAlert;
1454
1541
  const readOnlyTitle = (alerts == null ? void 0 : alerts.readOnlyTitle) || "Read Only";
1455
1542
  const errorTitle = (alerts == null ? void 0 : alerts.errorTitle) || "Error";
@@ -1688,7 +1775,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1688
1775
  const rowValues = { ...allValues, [field.name]: rows };
1689
1776
  subFields.forEach((subField) => {
1690
1777
  if (subField.visible && !subField.visible(rowValues)) return;
1691
- const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes);
1778
+ const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes, { messages: validationMessages });
1692
1779
  if (!err) return;
1693
1780
  const key = getRepeaterErrorKey(field.name, rowIdx, subField.name);
1694
1781
  errors[key] = err;
@@ -1715,9 +1802,9 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1715
1802
  );
1716
1803
  return repeaterResult.errors[name] || null;
1717
1804
  }
1718
- return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes);
1805
+ return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes, { messages: validationMessages });
1719
1806
  },
1720
- [fieldByName, formValues, validateRepeaterField, fieldTypes]
1807
+ [fieldByName, formValues, validateRepeaterField, fieldTypes, validationMessages]
1721
1808
  );
1722
1809
  const validateVisibleFields = (0, import_react2.useCallback)(
1723
1810
  (fieldSubset) => {
@@ -1733,7 +1820,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1733
1820
  }
1734
1821
  continue;
1735
1822
  }
1736
- const err = runValidators(formValues[field.name], field, formValues, fieldTypes);
1823
+ const err = runValidators(formValues[field.name], field, formValues, fieldTypes, { messages: validationMessages });
1737
1824
  if (err) {
1738
1825
  errors[field.name] = err;
1739
1826
  hasErrors = true;
@@ -1741,14 +1828,14 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
1741
1828
  }
1742
1829
  return { errors, hasErrors };
1743
1830
  },
1744
- [visibleFields, formValues, validateRepeaterField, fieldTypes]
1831
+ [visibleFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
1745
1832
  );
1746
1833
  const runAsyncValidation = (0, import_react2.useCallback)(
1747
1834
  (name, value) => {
1748
1835
  const field = fieldByName.get(name);
1749
1836
  if (!field || field.type === "repeater") return null;
1750
1837
  const val = value != null ? value : formValues[name];
1751
- const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false });
1838
+ const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false, messages: validationMessages });
1752
1839
  const prevController = asyncAbortRef.current.get(name);
1753
1840
  if (prevController) prevController.abort();
1754
1841
  asyncAbortRef.current.delete(name);
@@ -2117,6 +2204,12 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2117
2204
  [replaceErrors]
2118
2205
  );
2119
2206
  const renderField = (field) => {
2207
+ const fieldError = formErrors[field.name] || null;
2208
+ const rendered = renderFieldInner(field);
2209
+ if (!renderFieldError || !fieldError) return rendered;
2210
+ return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, rendered, renderFieldError(fieldError, field));
2211
+ };
2212
+ const renderFieldInner = (field) => {
2120
2213
  const fieldValue = formValues[field.name];
2121
2214
  const fieldError = formErrors[field.name] || null;
2122
2215
  const hasError = !!fieldError;
@@ -2183,7 +2276,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2183
2276
  readOnly: isReadOnly,
2184
2277
  disabled: isDisabled,
2185
2278
  error: hasError,
2186
- validationMessage: fieldError || void 0,
2279
+ validationMessage: renderFieldError ? void 0 : fieldError || void 0,
2187
2280
  ...field.loading || validatingFields[field.name] ? { loading: true } : {},
2188
2281
  ...field.fieldProps || {}
2189
2282
  };
@@ -2253,7 +2346,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2253
2346
  import_ui_extensions2.CurrencyInput,
2254
2347
  {
2255
2348
  ...commonProps,
2256
- currency: field.currency || "USD",
2349
+ currency: field.currency || defaultCurrency,
2257
2350
  value: fieldValue,
2258
2351
  min: field.min,
2259
2352
  max: field.max,
@@ -2431,8 +2524,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2431
2524
  const renderRemoveControl = repeaterProps.renderRemove;
2432
2525
  const renderMoveUpControl = repeaterProps.renderMoveUp;
2433
2526
  const renderMoveDownControl = repeaterProps.renderMoveDown;
2434
- const addLabel = repeaterProps.addLabel || "Add";
2435
- const removeLabel = repeaterProps.removeLabel || "Remove";
2527
+ const addLabel = repeaterProps.addLabel || repeaterAddLabel;
2528
+ const removeLabel = repeaterProps.removeLabel || repeaterRemoveLabel;
2436
2529
  const moveUpLabel = repeaterProps.moveUpLabel || "Up";
2437
2530
  const moveDownLabel = repeaterProps.moveDownLabel || "Down";
2438
2531
  const canEditRows = !isReadOnly && !isDisabled;
@@ -2466,7 +2559,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2466
2559
  };
2467
2560
  const validateSubField = (rowIdx, subField, subValue, nextRows) => {
2468
2561
  const rowValues = { ...formValues, [field.name]: nextRows };
2469
- const err = runValidators(subValue, subField, rowValues, fieldTypes);
2562
+ const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
2470
2563
  setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
2471
2564
  };
2472
2565
  const handleSubFieldChange = (rowIdx, subField, subValue) => {
@@ -2584,7 +2677,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2584
2677
  const renderDependentGroup = (parentField, dependents) => {
2585
2678
  const firstWithLabel = dependents.find((f) => getDependsOnLabel(f)) || dependents[0];
2586
2679
  const firstWithMessage = dependents.find((f) => getDependsOnMessage(f)) || dependents[0];
2587
- const groupLabel = getDependsOnLabel(firstWithLabel) || "Dependent properties";
2680
+ const groupLabel = getDependsOnLabel(firstWithLabel) || dependentPropertiesLabel;
2588
2681
  const rawMessage = getDependsOnMessage(firstWithMessage);
2589
2682
  const tooltipMessage = typeof rawMessage === "function" ? rawMessage(parentField.label) : rawMessage || "";
2590
2683
  return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tooltip, null, tooltipMessage) }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "info" })))), renderFieldSubset(dependents)));
@@ -2884,7 +2977,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
2884
2977
  currentStep,
2885
2978
  stepNames: steps.map((s) => s.title)
2886
2979
  }
2887
- ), formReadOnly && readOnlyMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage), !addAlert && formError && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), !addAlert && formSuccess && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
2980
+ ), showReadOnlyAlert && formReadOnly && readOnlyMessage && (renderReadOnlyAlert ? renderReadOnlyAlert({ title: readOnlyTitle, message: readOnlyMessage }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage)), showInlineAlerts && !addAlert && formError && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), showInlineAlerts && !addAlert && formSuccess && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
2888
2981
  values: formValues,
2889
2982
  goNext: handleNext,
2890
2983
  goBack: handleBack,