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/README.md CHANGED
@@ -21,6 +21,13 @@ import { DataTable, FormBuilder } from "hs-uix";
21
21
 
22
22
  Requires `react` >= 18.0.0 and `@hubspot/ui-extensions` >= 0.12.0 as peer dependencies (already present in any HubSpot UI Extensions project).
23
23
 
24
+ ## Components
25
+
26
+ | Component | Description | Docs |
27
+ |-----------|-------------|------|
28
+ | **DataTable** | Filterable, sortable, paginated table with auto-sized columns, inline editing, row grouping, and more | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/packages/datatable/README.md) |
29
+ | **FormBuilder** | Declarative, config-driven form with validation, multi-step wizards, and 20+ field types | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/packages/form/README.md) |
30
+
24
31
  ---
25
32
 
26
33
  # DataTable
@@ -90,8 +97,6 @@ Two edit modes: **discrete** (click-to-edit, default) and **inline** (always-vis
90
97
 
91
98
  Connect live CRM data (contacts, deals, tickets, etc.) to a DataTable with `useAssociations` from `@hubspot/ui-extensions/crm`.
92
99
 
93
- > **Full documentation:** [DataTable README](./packages/datatable/README.md) — includes full API reference, all examples, server-side mode, and more.
94
-
95
100
  ---
96
101
 
97
102
  # FormBuilder
@@ -167,8 +172,6 @@ const fields = [
167
172
 
168
173
  ![Read-Only Mode](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/readonly-autosave-dirty.png)
169
174
 
170
- > **Full documentation:** [FormBuilder README](./packages/form/README.md) — includes full API reference, all field types, validation details, props tables, and more.
171
-
172
175
  ---
173
176
 
174
177
  ## Migrating from `@hs-uix/datatable` or `@hs-uix/form`
package/dist/datatable.js CHANGED
@@ -160,12 +160,16 @@ var DataTable = ({
160
160
  // enable fuzzy matching via Fuse.js
161
161
  fuzzyOptions,
162
162
  // custom Fuse.js options (threshold, distance, etc.)
163
+ showSearch = true,
164
+ // show the SearchInput in the toolbar
163
165
  // Filters
164
166
  filters = [],
165
167
  showFilterBadges = true,
166
168
  // show active filter chips/badges
167
169
  showClearFiltersButton = true,
168
170
  // show "Clear all" filters reset button
171
+ filterInlineLimit = 2,
172
+ // number of filters shown inline before overflow
169
173
  // Pagination
170
174
  pageSize = 10,
171
175
  maxVisiblePageButtons,
@@ -247,6 +251,8 @@ var DataTable = ({
247
251
  // optional key to force clear uncontrolled selection memory
248
252
  resetSelectionOnQueryChange = true,
249
253
  // clear uncontrolled selection on search/filter/sort changes
254
+ showSelectionBar = true,
255
+ // show the selection action bar when rows are selected
250
256
  recordLabel,
251
257
  // { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
252
258
  // -----------------------------------------------------------------------
@@ -267,11 +273,31 @@ var DataTable = ({
267
273
  // (row, field, newValue) => void
268
274
  onRowEditInput,
269
275
  // optional live-input callback: (row, field, inputValue) => void
276
+ onEditStart,
277
+ // (row, field, currentValue) => void — fires when editing begins
278
+ onEditCancel,
279
+ // (row, field) => void — fires when editing is cancelled without commit
270
280
  // -----------------------------------------------------------------------
271
281
  // Auto-width
272
282
  // -----------------------------------------------------------------------
273
- autoWidth = true
283
+ autoWidth = true,
274
284
  // auto-compute column widths from content analysis
285
+ // -----------------------------------------------------------------------
286
+ // Labels / i18n
287
+ // -----------------------------------------------------------------------
288
+ labels,
289
+ // override hardcoded UI strings for i18n
290
+ // -----------------------------------------------------------------------
291
+ // Render overrides (Phase 3 — full replacement escape hatches)
292
+ // -----------------------------------------------------------------------
293
+ renderSelectionBar,
294
+ // ({ selectedIds, selectedCount, displayCount, countLabel, onSelectAll, onDeselectAll, selectionActions }) => ReactNode
295
+ renderEmptyState,
296
+ // ({ title, message }) => ReactNode
297
+ renderLoadingState,
298
+ // ({ label }) => ReactNode
299
+ renderErrorState
300
+ // ({ error, title, message }) => ReactNode
275
301
  }) => {
276
302
  const initialSortState = (0, import_react.useMemo)(() => {
277
303
  return normalizeSortState(columns, defaultSort);
@@ -505,11 +531,11 @@ var DataTable = ({
505
531
  const type = filter.type || "select";
506
532
  const prefix = filter.chipLabel || filter.placeholder || filter.name;
507
533
  if (type === "multiselect") {
508
- const labels = value.map((v) => {
534
+ const labels2 = value.map((v) => {
509
535
  var _a;
510
536
  return ((_a = filter.options.find((o) => o.value === v)) == null ? void 0 : _a.label) || v;
511
537
  }).join(", ");
512
- chips.push({ key: filter.name, label: `${prefix}: ${labels}` });
538
+ chips.push({ key: filter.name, label: `${prefix}: ${labels2}` });
513
539
  } else if (type === "dateRange") {
514
540
  const parts = [];
515
541
  if (value.from) parts.push(`from ${formatDateChip(value.from)}`);
@@ -550,7 +576,17 @@ var DataTable = ({
550
576
  const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
551
577
  const resolvedEmptyTitle = emptyTitle || "No results found";
552
578
  const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
553
- const resolvedLoadingLabel = `Loading ${pluralLabel}...`;
579
+ const resolvedSelectedLabel = (labels == null ? void 0 : labels.selected) || ((count, label) => `${count}\xA0${label}\xA0selected`);
580
+ const resolvedSelectAllLabel = (labels == null ? void 0 : labels.selectAll) || ((count, label) => `Select all ${count} ${label}`);
581
+ const resolvedDeselectAllLabel = (labels == null ? void 0 : labels.deselectAll) || "Deselect all";
582
+ const resolvedFiltersButtonLabel = (labels == null ? void 0 : labels.filtersButton) || "Filters";
583
+ const resolvedClearAllLabel = (labels == null ? void 0 : labels.clearAll) || "Clear all";
584
+ const resolvedDateFromLabel = (labels == null ? void 0 : labels.dateFrom) || "From";
585
+ const resolvedDateToLabel = (labels == null ? void 0 : labels.dateTo) || "To";
586
+ const resolvedLoadingLabel = (labels == null ? void 0 : labels.loading) || `Loading ${pluralLabel}...`;
587
+ const resolvedErrorTitle = (labels == null ? void 0 : labels.errorTitle) || "Something went wrong.";
588
+ const resolvedErrorMessage = (labels == null ? void 0 : labels.errorMessage) || "An error occurred while loading data.";
589
+ const resolvedRetryMessage = (labels == null ? void 0 : labels.retryMessage) || "Please try again.";
554
590
  const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
555
591
  const [internalSelectedIds, setInternalSelectedIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
556
592
  const selectionResetRef = (0, import_react.useRef)("");
@@ -639,7 +675,11 @@ var DataTable = ({
639
675
  setEditingCell({ rowId, field });
640
676
  setEditValue(currentValue);
641
677
  setEditError(null);
642
- }, []);
678
+ if (onEditStart) {
679
+ const row = data.find((r) => r[rowIdField] === rowId);
680
+ if (row) onEditStart(row, field, currentValue);
681
+ }
682
+ }, [onEditStart, data, rowIdField]);
643
683
  const commitEdit = (0, import_react.useCallback)((row, field, value) => {
644
684
  const col = columns.find((c) => c.field === field);
645
685
  if (col == null ? void 0 : col.editValidate) {
@@ -661,6 +701,7 @@ var DataTable = ({
661
701
  const commit = (val) => commitEdit(row, col.field, val);
662
702
  const exitEdit = () => {
663
703
  if (editError) return;
704
+ if (onEditCancel) onEditCancel(row, col.field);
664
705
  setEditingCell(null);
665
706
  setEditValue(null);
666
707
  };
@@ -880,7 +921,7 @@ var DataTable = ({
880
921
  {
881
922
  name: `filter-${filter.name}-from`,
882
923
  label: "",
883
- placeholder: "From",
924
+ placeholder: resolvedDateFromLabel,
884
925
  format: "medium",
885
926
  value: rangeVal.from,
886
927
  onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, from: val })
@@ -891,7 +932,7 @@ var DataTable = ({
891
932
  size: "sm",
892
933
  name: `filter-${filter.name}-to`,
893
934
  label: "",
894
- placeholder: "To",
935
+ placeholder: resolvedDateToLabel,
895
936
  format: "medium",
896
937
  value: rangeVal.to,
897
938
  onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, to: val })
@@ -914,7 +955,7 @@ var DataTable = ({
914
955
  }
915
956
  );
916
957
  };
917
- 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(
958
+ 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(
918
959
  import_ui_extensions.SearchInput,
919
960
  {
920
961
  name: "datatable-search",
@@ -922,7 +963,7 @@ var DataTable = ({
922
963
  value: searchTerm,
923
964
  onChange: handleSearchChange
924
965
  }
925
- ), filters.slice(0, 2).map(renderFilterControl), filters.length > 2 && /* @__PURE__ */ import_react.default.createElement(
966
+ ), filters.slice(0, filterInlineLimit).map(renderFilterControl), filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(
926
967
  import_ui_extensions.Button,
927
968
  {
928
969
  variant: "transparent",
@@ -930,16 +971,25 @@ var DataTable = ({
930
971
  onClick: () => setShowMoreFilters((prev) => !prev)
931
972
  },
932
973
  /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "filter", size: "sm" }),
933
- " Filters"
934
- )), 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(
974
+ " ",
975
+ resolvedFiltersButtonLabel
976
+ )), 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(
935
977
  import_ui_extensions.Button,
936
978
  {
937
979
  variant: "transparent",
938
980
  size: "extra-small",
939
981
  onClick: () => handleFilterRemove("all")
940
982
  },
941
- "Clear all"
942
- )))), 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(
983
+ resolvedClearAllLabel
984
+ )))), 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({
985
+ selectedIds,
986
+ selectedCount: selectedIds.size,
987
+ displayCount,
988
+ countLabel,
989
+ onSelectAll: handleSelectAllRows,
990
+ onDeselectAll: handleDeselectAll,
991
+ 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(
943
993
  import_ui_extensions.Button,
944
994
  {
945
995
  key: i,
@@ -950,7 +1000,11 @@ var DataTable = ({
950
1000
  action.icon && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: action.icon, size: "sm" }),
951
1001
  " ",
952
1002
  action.label
953
- )))), 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(
1003
+ )))), 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({
1004
+ error,
1005
+ title: typeof error === "string" ? error : resolvedErrorTitle,
1006
+ message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
1007
+ }) : /* @__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(
954
1008
  import_ui_extensions.Table,
955
1009
  {
956
1010
  bordered,
@@ -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,