hs-uix 1.0.4 → 1.2.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 +7 -4
- package/dist/datatable.js +89 -22
- package/dist/datatable.mjs +89 -22
- package/dist/form.js +246 -64
- package/dist/form.mjs +246 -64
- package/dist/index.js +335 -86
- package/dist/index.mjs +335 -86
- package/form.d.ts +1 -0
- package/index.d.ts +1 -0
- package/package.json +1 -1
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](https://github.com/05bmckay/hs-uix/blob/main/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
|

|
|
169
174
|
|
|
170
|
-
> **Full documentation:** [FormBuilder README](https://github.com/05bmckay/hs-uix/blob/main/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
|
@@ -89,12 +89,19 @@ var computeAutoWidths = (columns, data) => {
|
|
|
89
89
|
columns.forEach((col) => {
|
|
90
90
|
if (col.width && col.cellWidth) return;
|
|
91
91
|
const values = sample.map((row) => row[col.field]).filter((v) => v != null);
|
|
92
|
-
const strings = values.map((v) =>
|
|
92
|
+
const strings = values.map((v) => {
|
|
93
|
+
const s = String(v);
|
|
94
|
+
const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
|
|
95
|
+
return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
|
|
96
|
+
});
|
|
93
97
|
let widthHint = null;
|
|
94
98
|
let cellWidthHint = null;
|
|
95
99
|
if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
|
|
96
100
|
cellWidthHint = "min";
|
|
97
101
|
}
|
|
102
|
+
if (col.truncate === true) {
|
|
103
|
+
cellWidthHint = cellWidthHint || "min";
|
|
104
|
+
}
|
|
98
105
|
if (strings.length > 0) {
|
|
99
106
|
const lengths = strings.map((s) => s.length);
|
|
100
107
|
const maxLen = Math.max(...lengths);
|
|
@@ -160,12 +167,16 @@ var DataTable = ({
|
|
|
160
167
|
// enable fuzzy matching via Fuse.js
|
|
161
168
|
fuzzyOptions,
|
|
162
169
|
// custom Fuse.js options (threshold, distance, etc.)
|
|
170
|
+
showSearch = true,
|
|
171
|
+
// show the SearchInput in the toolbar
|
|
163
172
|
// Filters
|
|
164
173
|
filters = [],
|
|
165
174
|
showFilterBadges = true,
|
|
166
175
|
// show active filter chips/badges
|
|
167
176
|
showClearFiltersButton = true,
|
|
168
177
|
// show "Clear all" filters reset button
|
|
178
|
+
filterInlineLimit = 2,
|
|
179
|
+
// number of filters shown inline before overflow
|
|
169
180
|
// Pagination
|
|
170
181
|
pageSize = 10,
|
|
171
182
|
maxVisiblePageButtons,
|
|
@@ -247,6 +258,8 @@ var DataTable = ({
|
|
|
247
258
|
// optional key to force clear uncontrolled selection memory
|
|
248
259
|
resetSelectionOnQueryChange = true,
|
|
249
260
|
// clear uncontrolled selection on search/filter/sort changes
|
|
261
|
+
showSelectionBar = true,
|
|
262
|
+
// show the selection action bar when rows are selected
|
|
250
263
|
recordLabel,
|
|
251
264
|
// { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
|
|
252
265
|
// -----------------------------------------------------------------------
|
|
@@ -267,11 +280,31 @@ var DataTable = ({
|
|
|
267
280
|
// (row, field, newValue) => void
|
|
268
281
|
onRowEditInput,
|
|
269
282
|
// optional live-input callback: (row, field, inputValue) => void
|
|
283
|
+
onEditStart,
|
|
284
|
+
// (row, field, currentValue) => void — fires when editing begins
|
|
285
|
+
onEditCancel,
|
|
286
|
+
// (row, field) => void — fires when editing is cancelled without commit
|
|
270
287
|
// -----------------------------------------------------------------------
|
|
271
288
|
// Auto-width
|
|
272
289
|
// -----------------------------------------------------------------------
|
|
273
|
-
autoWidth = true
|
|
290
|
+
autoWidth = true,
|
|
274
291
|
// auto-compute column widths from content analysis
|
|
292
|
+
// -----------------------------------------------------------------------
|
|
293
|
+
// Labels / i18n
|
|
294
|
+
// -----------------------------------------------------------------------
|
|
295
|
+
labels,
|
|
296
|
+
// override hardcoded UI strings for i18n
|
|
297
|
+
// -----------------------------------------------------------------------
|
|
298
|
+
// Render overrides (Phase 3 — full replacement escape hatches)
|
|
299
|
+
// -----------------------------------------------------------------------
|
|
300
|
+
renderSelectionBar,
|
|
301
|
+
// ({ selectedIds, selectedCount, displayCount, countLabel, onSelectAll, onDeselectAll, selectionActions }) => ReactNode
|
|
302
|
+
renderEmptyState,
|
|
303
|
+
// ({ title, message }) => ReactNode
|
|
304
|
+
renderLoadingState,
|
|
305
|
+
// ({ label }) => ReactNode
|
|
306
|
+
renderErrorState
|
|
307
|
+
// ({ error, title, message }) => ReactNode
|
|
275
308
|
}) => {
|
|
276
309
|
const initialSortState = (0, import_react.useMemo)(() => {
|
|
277
310
|
return normalizeSortState(columns, defaultSort);
|
|
@@ -505,11 +538,11 @@ var DataTable = ({
|
|
|
505
538
|
const type = filter.type || "select";
|
|
506
539
|
const prefix = filter.chipLabel || filter.placeholder || filter.name;
|
|
507
540
|
if (type === "multiselect") {
|
|
508
|
-
const
|
|
541
|
+
const labels2 = value.map((v) => {
|
|
509
542
|
var _a;
|
|
510
543
|
return ((_a = filter.options.find((o) => o.value === v)) == null ? void 0 : _a.label) || v;
|
|
511
544
|
}).join(", ");
|
|
512
|
-
chips.push({ key: filter.name, label: `${prefix}: ${
|
|
545
|
+
chips.push({ key: filter.name, label: `${prefix}: ${labels2}` });
|
|
513
546
|
} else if (type === "dateRange") {
|
|
514
547
|
const parts = [];
|
|
515
548
|
if (value.from) parts.push(`from ${formatDateChip(value.from)}`);
|
|
@@ -550,7 +583,17 @@ var DataTable = ({
|
|
|
550
583
|
const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
|
|
551
584
|
const resolvedEmptyTitle = emptyTitle || "No results found";
|
|
552
585
|
const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
|
|
553
|
-
const
|
|
586
|
+
const resolvedSelectedLabel = (labels == null ? void 0 : labels.selected) || ((count, label) => `${count}\xA0${label}\xA0selected`);
|
|
587
|
+
const resolvedSelectAllLabel = (labels == null ? void 0 : labels.selectAll) || ((count, label) => `Select all ${count} ${label}`);
|
|
588
|
+
const resolvedDeselectAllLabel = (labels == null ? void 0 : labels.deselectAll) || "Deselect all";
|
|
589
|
+
const resolvedFiltersButtonLabel = (labels == null ? void 0 : labels.filtersButton) || "Filters";
|
|
590
|
+
const resolvedClearAllLabel = (labels == null ? void 0 : labels.clearAll) || "Clear all";
|
|
591
|
+
const resolvedDateFromLabel = (labels == null ? void 0 : labels.dateFrom) || "From";
|
|
592
|
+
const resolvedDateToLabel = (labels == null ? void 0 : labels.dateTo) || "To";
|
|
593
|
+
const resolvedLoadingLabel = (labels == null ? void 0 : labels.loading) || `Loading ${pluralLabel}...`;
|
|
594
|
+
const resolvedErrorTitle = (labels == null ? void 0 : labels.errorTitle) || "Something went wrong.";
|
|
595
|
+
const resolvedErrorMessage = (labels == null ? void 0 : labels.errorMessage) || "An error occurred while loading data.";
|
|
596
|
+
const resolvedRetryMessage = (labels == null ? void 0 : labels.retryMessage) || "Please try again.";
|
|
554
597
|
const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
|
|
555
598
|
const [internalSelectedIds, setInternalSelectedIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
|
|
556
599
|
const selectionResetRef = (0, import_react.useRef)("");
|
|
@@ -639,7 +682,11 @@ var DataTable = ({
|
|
|
639
682
|
setEditingCell({ rowId, field });
|
|
640
683
|
setEditValue(currentValue);
|
|
641
684
|
setEditError(null);
|
|
642
|
-
|
|
685
|
+
if (onEditStart) {
|
|
686
|
+
const row = data.find((r) => r[rowIdField] === rowId);
|
|
687
|
+
if (row) onEditStart(row, field, currentValue);
|
|
688
|
+
}
|
|
689
|
+
}, [onEditStart, data, rowIdField]);
|
|
643
690
|
const commitEdit = (0, import_react.useCallback)((row, field, value) => {
|
|
644
691
|
const col = columns.find((c) => c.field === field);
|
|
645
692
|
if (col == null ? void 0 : col.editValidate) {
|
|
@@ -661,6 +708,7 @@ var DataTable = ({
|
|
|
661
708
|
const commit = (val) => commitEdit(row, col.field, val);
|
|
662
709
|
const exitEdit = () => {
|
|
663
710
|
if (editError) return;
|
|
711
|
+
if (onEditCancel) onEditCancel(row, col.field);
|
|
664
712
|
setEditingCell(null);
|
|
665
713
|
setEditValue(null);
|
|
666
714
|
};
|
|
@@ -827,20 +875,26 @@ var DataTable = ({
|
|
|
827
875
|
const rawStr = String(rawValue ?? "");
|
|
828
876
|
if (col.truncate && rawStr.length > 0) {
|
|
829
877
|
if (col.truncate === true) {
|
|
830
|
-
|
|
878
|
+
if (col.renderCell) {
|
|
879
|
+
const content2 = col.renderCell(rawValue, row);
|
|
880
|
+
if (col.editable) {
|
|
881
|
+
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
|
|
882
|
+
}
|
|
883
|
+
return content2;
|
|
884
|
+
}
|
|
831
885
|
if (col.editable) {
|
|
832
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) },
|
|
886
|
+
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, rawStr || "--"));
|
|
833
887
|
}
|
|
834
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } },
|
|
888
|
+
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, rawStr);
|
|
835
889
|
}
|
|
836
|
-
const maxLen = col.truncate.maxLength || 100;
|
|
890
|
+
const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
|
|
837
891
|
if (rawStr.length > maxLen) {
|
|
838
892
|
const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
|
|
839
|
-
const
|
|
893
|
+
const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
|
|
840
894
|
if (col.editable) {
|
|
841
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) },
|
|
895
|
+
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
|
|
842
896
|
}
|
|
843
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } },
|
|
897
|
+
return col.renderCell ? content2 : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, content2 || "--");
|
|
844
898
|
}
|
|
845
899
|
}
|
|
846
900
|
const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
|
|
@@ -880,7 +934,7 @@ var DataTable = ({
|
|
|
880
934
|
{
|
|
881
935
|
name: `filter-${filter.name}-from`,
|
|
882
936
|
label: "",
|
|
883
|
-
placeholder:
|
|
937
|
+
placeholder: resolvedDateFromLabel,
|
|
884
938
|
format: "medium",
|
|
885
939
|
value: rangeVal.from,
|
|
886
940
|
onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, from: val })
|
|
@@ -891,7 +945,7 @@ var DataTable = ({
|
|
|
891
945
|
size: "sm",
|
|
892
946
|
name: `filter-${filter.name}-to`,
|
|
893
947
|
label: "",
|
|
894
|
-
placeholder:
|
|
948
|
+
placeholder: resolvedDateToLabel,
|
|
895
949
|
format: "medium",
|
|
896
950
|
value: rangeVal.to,
|
|
897
951
|
onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, to: val })
|
|
@@ -914,7 +968,7 @@ var DataTable = ({
|
|
|
914
968
|
}
|
|
915
969
|
);
|
|
916
970
|
};
|
|
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(
|
|
971
|
+
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
972
|
import_ui_extensions.SearchInput,
|
|
919
973
|
{
|
|
920
974
|
name: "datatable-search",
|
|
@@ -922,7 +976,7 @@ var DataTable = ({
|
|
|
922
976
|
value: searchTerm,
|
|
923
977
|
onChange: handleSearchChange
|
|
924
978
|
}
|
|
925
|
-
), filters.slice(0,
|
|
979
|
+
), filters.slice(0, filterInlineLimit).map(renderFilterControl), filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(
|
|
926
980
|
import_ui_extensions.Button,
|
|
927
981
|
{
|
|
928
982
|
variant: "transparent",
|
|
@@ -930,16 +984,25 @@ var DataTable = ({
|
|
|
930
984
|
onClick: () => setShowMoreFilters((prev) => !prev)
|
|
931
985
|
},
|
|
932
986
|
/* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "filter", size: "sm" }),
|
|
933
|
-
"
|
|
934
|
-
|
|
987
|
+
" ",
|
|
988
|
+
resolvedFiltersButtonLabel
|
|
989
|
+
)), 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
990
|
import_ui_extensions.Button,
|
|
936
991
|
{
|
|
937
992
|
variant: "transparent",
|
|
938
993
|
size: "extra-small",
|
|
939
994
|
onClick: () => handleFilterRemove("all")
|
|
940
995
|
},
|
|
941
|
-
|
|
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 &&
|
|
996
|
+
resolvedClearAllLabel
|
|
997
|
+
)))), 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({
|
|
998
|
+
selectedIds,
|
|
999
|
+
selectedCount: selectedIds.size,
|
|
1000
|
+
displayCount,
|
|
1001
|
+
countLabel,
|
|
1002
|
+
onSelectAll: handleSelectAllRows,
|
|
1003
|
+
onDeselectAll: handleDeselectAll,
|
|
1004
|
+
selectionActions
|
|
1005
|
+
}) : /* @__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
1006
|
import_ui_extensions.Button,
|
|
944
1007
|
{
|
|
945
1008
|
key: i,
|
|
@@ -950,7 +1013,11 @@ var DataTable = ({
|
|
|
950
1013
|
action.icon && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: action.icon, size: "sm" }),
|
|
951
1014
|
" ",
|
|
952
1015
|
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 ?
|
|
1016
|
+
)))), 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({
|
|
1017
|
+
error,
|
|
1018
|
+
title: typeof error === "string" ? error : resolvedErrorTitle,
|
|
1019
|
+
message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
|
|
1020
|
+
}) : /* @__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
1021
|
import_ui_extensions.Table,
|
|
955
1022
|
{
|
|
956
1023
|
bordered,
|
package/dist/datatable.mjs
CHANGED
|
@@ -84,12 +84,19 @@ var computeAutoWidths = (columns, data) => {
|
|
|
84
84
|
columns.forEach((col) => {
|
|
85
85
|
if (col.width && col.cellWidth) return;
|
|
86
86
|
const values = sample.map((row) => row[col.field]).filter((v) => v != null);
|
|
87
|
-
const strings = values.map((v) =>
|
|
87
|
+
const strings = values.map((v) => {
|
|
88
|
+
const s = String(v);
|
|
89
|
+
const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
|
|
90
|
+
return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
|
|
91
|
+
});
|
|
88
92
|
let widthHint = null;
|
|
89
93
|
let cellWidthHint = null;
|
|
90
94
|
if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
|
|
91
95
|
cellWidthHint = "min";
|
|
92
96
|
}
|
|
97
|
+
if (col.truncate === true) {
|
|
98
|
+
cellWidthHint = cellWidthHint || "min";
|
|
99
|
+
}
|
|
93
100
|
if (strings.length > 0) {
|
|
94
101
|
const lengths = strings.map((s) => s.length);
|
|
95
102
|
const maxLen = Math.max(...lengths);
|
|
@@ -155,12 +162,16 @@ var DataTable = ({
|
|
|
155
162
|
// enable fuzzy matching via Fuse.js
|
|
156
163
|
fuzzyOptions,
|
|
157
164
|
// custom Fuse.js options (threshold, distance, etc.)
|
|
165
|
+
showSearch = true,
|
|
166
|
+
// show the SearchInput in the toolbar
|
|
158
167
|
// Filters
|
|
159
168
|
filters = [],
|
|
160
169
|
showFilterBadges = true,
|
|
161
170
|
// show active filter chips/badges
|
|
162
171
|
showClearFiltersButton = true,
|
|
163
172
|
// show "Clear all" filters reset button
|
|
173
|
+
filterInlineLimit = 2,
|
|
174
|
+
// number of filters shown inline before overflow
|
|
164
175
|
// Pagination
|
|
165
176
|
pageSize = 10,
|
|
166
177
|
maxVisiblePageButtons,
|
|
@@ -242,6 +253,8 @@ var DataTable = ({
|
|
|
242
253
|
// optional key to force clear uncontrolled selection memory
|
|
243
254
|
resetSelectionOnQueryChange = true,
|
|
244
255
|
// clear uncontrolled selection on search/filter/sort changes
|
|
256
|
+
showSelectionBar = true,
|
|
257
|
+
// show the selection action bar when rows are selected
|
|
245
258
|
recordLabel,
|
|
246
259
|
// { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
|
|
247
260
|
// -----------------------------------------------------------------------
|
|
@@ -262,11 +275,31 @@ var DataTable = ({
|
|
|
262
275
|
// (row, field, newValue) => void
|
|
263
276
|
onRowEditInput,
|
|
264
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
|
|
265
282
|
// -----------------------------------------------------------------------
|
|
266
283
|
// Auto-width
|
|
267
284
|
// -----------------------------------------------------------------------
|
|
268
|
-
autoWidth = true
|
|
285
|
+
autoWidth = true,
|
|
269
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
|
|
270
303
|
}) => {
|
|
271
304
|
const initialSortState = useMemo(() => {
|
|
272
305
|
return normalizeSortState(columns, defaultSort);
|
|
@@ -500,11 +533,11 @@ var DataTable = ({
|
|
|
500
533
|
const type = filter.type || "select";
|
|
501
534
|
const prefix = filter.chipLabel || filter.placeholder || filter.name;
|
|
502
535
|
if (type === "multiselect") {
|
|
503
|
-
const
|
|
536
|
+
const labels2 = value.map((v) => {
|
|
504
537
|
var _a;
|
|
505
538
|
return ((_a = filter.options.find((o) => o.value === v)) == null ? void 0 : _a.label) || v;
|
|
506
539
|
}).join(", ");
|
|
507
|
-
chips.push({ key: filter.name, label: `${prefix}: ${
|
|
540
|
+
chips.push({ key: filter.name, label: `${prefix}: ${labels2}` });
|
|
508
541
|
} else if (type === "dateRange") {
|
|
509
542
|
const parts = [];
|
|
510
543
|
if (value.from) parts.push(`from ${formatDateChip(value.from)}`);
|
|
@@ -545,7 +578,17 @@ var DataTable = ({
|
|
|
545
578
|
const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
|
|
546
579
|
const resolvedEmptyTitle = emptyTitle || "No results found";
|
|
547
580
|
const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
|
|
548
|
-
const
|
|
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.";
|
|
549
592
|
const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
|
|
550
593
|
const [internalSelectedIds, setInternalSelectedIds] = useState(/* @__PURE__ */ new Set());
|
|
551
594
|
const selectionResetRef = useRef("");
|
|
@@ -634,7 +677,11 @@ var DataTable = ({
|
|
|
634
677
|
setEditingCell({ rowId, field });
|
|
635
678
|
setEditValue(currentValue);
|
|
636
679
|
setEditError(null);
|
|
637
|
-
|
|
680
|
+
if (onEditStart) {
|
|
681
|
+
const row = data.find((r) => r[rowIdField] === rowId);
|
|
682
|
+
if (row) onEditStart(row, field, currentValue);
|
|
683
|
+
}
|
|
684
|
+
}, [onEditStart, data, rowIdField]);
|
|
638
685
|
const commitEdit = useCallback((row, field, value) => {
|
|
639
686
|
const col = columns.find((c) => c.field === field);
|
|
640
687
|
if (col == null ? void 0 : col.editValidate) {
|
|
@@ -656,6 +703,7 @@ var DataTable = ({
|
|
|
656
703
|
const commit = (val) => commitEdit(row, col.field, val);
|
|
657
704
|
const exitEdit = () => {
|
|
658
705
|
if (editError) return;
|
|
706
|
+
if (onEditCancel) onEditCancel(row, col.field);
|
|
659
707
|
setEditingCell(null);
|
|
660
708
|
setEditValue(null);
|
|
661
709
|
};
|
|
@@ -822,20 +870,26 @@ var DataTable = ({
|
|
|
822
870
|
const rawStr = String(rawValue ?? "");
|
|
823
871
|
if (col.truncate && rawStr.length > 0) {
|
|
824
872
|
if (col.truncate === true) {
|
|
825
|
-
|
|
873
|
+
if (col.renderCell) {
|
|
874
|
+
const content2 = col.renderCell(rawValue, row);
|
|
875
|
+
if (col.editable) {
|
|
876
|
+
return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
|
|
877
|
+
}
|
|
878
|
+
return content2;
|
|
879
|
+
}
|
|
826
880
|
if (col.editable) {
|
|
827
|
-
return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) },
|
|
881
|
+
return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, rawStr || "--"));
|
|
828
882
|
}
|
|
829
|
-
return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } },
|
|
883
|
+
return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, rawStr);
|
|
830
884
|
}
|
|
831
|
-
const maxLen = col.truncate.maxLength || 100;
|
|
885
|
+
const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
|
|
832
886
|
if (rawStr.length > maxLen) {
|
|
833
887
|
const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
|
|
834
|
-
const
|
|
888
|
+
const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
|
|
835
889
|
if (col.editable) {
|
|
836
|
-
return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) },
|
|
890
|
+
return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
|
|
837
891
|
}
|
|
838
|
-
return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } },
|
|
892
|
+
return col.renderCell ? content2 : /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, content2 || "--");
|
|
839
893
|
}
|
|
840
894
|
}
|
|
841
895
|
const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
|
|
@@ -875,7 +929,7 @@ var DataTable = ({
|
|
|
875
929
|
{
|
|
876
930
|
name: `filter-${filter.name}-from`,
|
|
877
931
|
label: "",
|
|
878
|
-
placeholder:
|
|
932
|
+
placeholder: resolvedDateFromLabel,
|
|
879
933
|
format: "medium",
|
|
880
934
|
value: rangeVal.from,
|
|
881
935
|
onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, from: val })
|
|
@@ -886,7 +940,7 @@ var DataTable = ({
|
|
|
886
940
|
size: "sm",
|
|
887
941
|
name: `filter-${filter.name}-to`,
|
|
888
942
|
label: "",
|
|
889
|
-
placeholder:
|
|
943
|
+
placeholder: resolvedDateToLabel,
|
|
890
944
|
format: "medium",
|
|
891
945
|
value: rangeVal.to,
|
|
892
946
|
onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, to: val })
|
|
@@ -909,7 +963,7 @@ var DataTable = ({
|
|
|
909
963
|
}
|
|
910
964
|
);
|
|
911
965
|
};
|
|
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(
|
|
966
|
+
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
967
|
SearchInput,
|
|
914
968
|
{
|
|
915
969
|
name: "datatable-search",
|
|
@@ -917,7 +971,7 @@ var DataTable = ({
|
|
|
917
971
|
value: searchTerm,
|
|
918
972
|
onChange: handleSearchChange
|
|
919
973
|
}
|
|
920
|
-
), filters.slice(0,
|
|
974
|
+
), filters.slice(0, filterInlineLimit).map(renderFilterControl), filters.length > filterInlineLimit && /* @__PURE__ */ React.createElement(
|
|
921
975
|
Button,
|
|
922
976
|
{
|
|
923
977
|
variant: "transparent",
|
|
@@ -925,16 +979,25 @@ var DataTable = ({
|
|
|
925
979
|
onClick: () => setShowMoreFilters((prev) => !prev)
|
|
926
980
|
},
|
|
927
981
|
/* @__PURE__ */ React.createElement(Icon, { name: "filter", size: "sm" }),
|
|
928
|
-
"
|
|
929
|
-
|
|
982
|
+
" ",
|
|
983
|
+
resolvedFiltersButtonLabel
|
|
984
|
+
)), 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
985
|
Button,
|
|
931
986
|
{
|
|
932
987
|
variant: "transparent",
|
|
933
988
|
size: "extra-small",
|
|
934
989
|
onClick: () => handleFilterRemove("all")
|
|
935
990
|
},
|
|
936
|
-
|
|
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 &&
|
|
991
|
+
resolvedClearAllLabel
|
|
992
|
+
)))), 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({
|
|
993
|
+
selectedIds,
|
|
994
|
+
selectedCount: selectedIds.size,
|
|
995
|
+
displayCount,
|
|
996
|
+
countLabel,
|
|
997
|
+
onSelectAll: handleSelectAllRows,
|
|
998
|
+
onDeselectAll: handleDeselectAll,
|
|
999
|
+
selectionActions
|
|
1000
|
+
}) : /* @__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
1001
|
Button,
|
|
939
1002
|
{
|
|
940
1003
|
key: i,
|
|
@@ -945,7 +1008,11 @@ var DataTable = ({
|
|
|
945
1008
|
action.icon && /* @__PURE__ */ React.createElement(Icon, { name: action.icon, size: "sm" }),
|
|
946
1009
|
" ",
|
|
947
1010
|
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 ?
|
|
1011
|
+
)))), 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({
|
|
1012
|
+
error,
|
|
1013
|
+
title: typeof error === "string" ? error : resolvedErrorTitle,
|
|
1014
|
+
message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
|
|
1015
|
+
}) : /* @__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
1016
|
Table,
|
|
950
1017
|
{
|
|
951
1018
|
bordered,
|