hs-uix 2.1.0 → 2.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.
Files changed (49) hide show
  1. package/README.md +3 -1
  2. package/common-components.d.ts +319 -68
  3. package/dist/calendar.js +397 -119
  4. package/dist/calendar.mjs +399 -119
  5. package/dist/common-components.js +3546 -88
  6. package/dist/common-components.mjs +3530 -84
  7. package/dist/datatable.js +108 -18
  8. package/dist/datatable.mjs +108 -18
  9. package/dist/experimental.js +2876 -0
  10. package/dist/experimental.mjs +2883 -0
  11. package/dist/feed.js +267 -38
  12. package/dist/feed.mjs +260 -37
  13. package/dist/filter.js +1379 -0
  14. package/dist/filter.mjs +1334 -0
  15. package/dist/form.js +222 -26
  16. package/dist/form.mjs +227 -27
  17. package/dist/index.js +3255 -353
  18. package/dist/index.mjs +3199 -344
  19. package/dist/kanban.js +282 -62
  20. package/dist/kanban.mjs +273 -61
  21. package/dist/safe.js +9207 -0
  22. package/dist/safe.mjs +9298 -0
  23. package/dist/utils.js +491 -75
  24. package/dist/utils.mjs +491 -75
  25. package/experimental.d.ts +1 -0
  26. package/filter.d.ts +1 -0
  27. package/index.d.ts +45 -3
  28. package/package.json +19 -1
  29. package/safe.d.ts +1 -0
  30. package/src/calendar/README.md +76 -5
  31. package/src/calendar/index.d.ts +108 -1
  32. package/src/common-components/README.md +140 -1
  33. package/src/datatable/README.md +0 -2
  34. package/src/experimental/README.md +126 -0
  35. package/src/experimental/index.d.ts +346 -0
  36. package/src/feed/README.md +69 -0
  37. package/src/feed/index.d.ts +103 -0
  38. package/src/filter/README.md +148 -0
  39. package/src/filter/index.d.ts +221 -0
  40. package/src/form/README.md +132 -4
  41. package/src/form/index.d.ts +82 -1
  42. package/src/kanban/README.md +119 -6
  43. package/src/kanban/index.d.ts +153 -2
  44. package/src/safe/README.md +108 -0
  45. package/src/safe/index.d.ts +158 -0
  46. package/src/utils/README.md +39 -0
  47. package/src/wizard/README.md +158 -0
  48. package/src/wizard/index.d.ts +138 -0
  49. package/utils.d.ts +17 -0
@@ -33,12 +33,18 @@ __export(common_components_exports, {
33
33
  AutoStatusTag: () => AutoStatusTag,
34
34
  AutoTag: () => AutoTag,
35
35
  AvatarStack: () => AvatarStack,
36
+ CREATE_OPTION_VALUE: () => CREATE_OPTION_VALUE,
36
37
  CollectionCount: () => CollectionCount,
37
38
  CollectionFilterControl: () => CollectionFilterControl,
38
39
  CollectionSortSelect: () => CollectionSortSelect,
39
40
  CollectionToolbar: () => CollectionToolbar,
40
41
  CrmLookupSelect: () => CrmLookupSelect,
42
+ CrmRecordPicker: () => CrmRecordPicker,
43
+ DATE_FILTER_OPERATORS: () => DATE_FILTER_OPERATORS,
44
+ DATE_RANGE_CUSTOM_VALUE: () => DATE_RANGE_CUSTOM_VALUE,
45
+ DATE_ROLLING_UNIT_OPTIONS: () => DATE_ROLLING_UNIT_OPTIONS,
41
46
  DEFAULT_SVG_FONT_WEIGHT: () => DEFAULT_SVG_FONT_WEIGHT,
47
+ DateRangePicker: () => DateRangePicker,
42
48
  HS_DATE_DIRECTION_LABELS: () => HS_DATE_DIRECTION_LABELS,
43
49
  HS_DATE_PRESETS: () => HS_DATE_PRESETS,
44
50
  HS_FONT_FAMILY: () => HS_FONT_FAMILY,
@@ -59,18 +65,34 @@ __export(common_components_exports, {
59
65
  Icon: () => Icon,
60
66
  KeyValueList: () => KeyValueList,
61
67
  NATIVE_ICON_NAME_LIST: () => NATIVE_ICON_NAME_LIST,
68
+ SKELETON_FILL: () => SKELETON_FILL,
62
69
  SPINNERS: () => SPINNERS,
63
70
  SPINNER_NAMES: () => SPINNER_NAMES,
64
71
  SectionHeader: () => SectionHeader,
65
72
  Spinner: () => Spinner,
66
73
  StyledText: () => StyledText,
74
+ compareHsDateValues: () => compareHsDateValues,
75
+ enforceSelectionMax: () => enforceSelectionMax,
67
76
  formatCollectionCount: () => formatCollectionCount,
77
+ getRecordId: () => getRecordId,
68
78
  gridToBraille: () => gridToBraille,
79
+ isRecordLike: () => isRecordLike,
80
+ isValidDateRange: () => isValidDateRange,
69
81
  makeAvatarStackDataUri: () => makeAvatarStackDataUri,
82
+ makeCreateOption: () => makeCreateOption,
70
83
  makeGrid: () => makeGrid,
71
84
  makeIconDataUri: () => makeIconDataUri,
72
85
  makeStyledTextDataUri: () => makeStyledTextDataUri,
73
- svgToIconEntry: () => svgToIconEntry
86
+ mapIdsToRecords: () => mapIdsToRecords,
87
+ mergePickerOptions: () => mergePickerOptions,
88
+ normalizeRecordSelection: () => normalizeRecordSelection,
89
+ presetToRange: () => presetToRange,
90
+ recordToPickerOption: () => recordToPickerOption,
91
+ shouldShowCreateOption: () => shouldShowCreateOption,
92
+ splitCreateSelection: () => splitCreateSelection,
93
+ svgToIconEntry: () => svgToIconEntry,
94
+ toHsDateValue: () => toHsDateValue,
95
+ upsertRecords: () => upsertRecords
74
96
  });
75
97
  module.exports = __toCommonJS(common_components_exports);
76
98
 
@@ -322,6 +344,7 @@ var HS_TEXT_COLOR = "#33475b";
322
344
  var HS_SUBTLE_BG = "#F5F8FA";
323
345
  var HS_MUTED_TEXT = "#7C98B6";
324
346
  var HS_NEUTRAL_CHIP = "#CBD6E2";
347
+ var SKELETON_FILL = "#DFE3EB";
325
348
  var HS_TAG_SUBTLE_BORDER = "#7C98B6";
326
349
  var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
327
350
  var HS_TAG_FONT_SIZE = 12;
@@ -480,10 +503,169 @@ var import_react10 = __toESM(require("react"));
480
503
 
481
504
  // src/utils/query.js
482
505
  var import_fuse = __toESM(require("fuse.js"));
506
+ var getEmptyFilterValue = (filter) => {
507
+ const type = filter.type || "select";
508
+ if (type === "multiselect") return [];
509
+ if (type === "dateRange") return { from: null, to: null };
510
+ if (Object.prototype.hasOwnProperty.call(filter, "emptyValue")) return filter.emptyValue;
511
+ return "";
512
+ };
513
+ var isFilterActive = (filter, value) => {
514
+ const type = filter.type || "select";
515
+ if (type === "multiselect") return Array.isArray(value) && value.length > 0;
516
+ if (type === "dateRange") return value && (value.from || value.to);
517
+ if (value == null) return false;
518
+ if (Object.prototype.hasOwnProperty.call(filter, "emptyValue")) return value !== filter.emptyValue;
519
+ return !!value;
520
+ };
521
+ var formatDateChip = (dateObj) => {
522
+ if (!dateObj) return "";
523
+ const { year, month, date } = dateObj;
524
+ return new Intl.DateTimeFormat("en-US", {
525
+ month: "short",
526
+ day: "numeric",
527
+ year: "numeric"
528
+ }).format(new Date(year, month, date));
529
+ };
530
+ var dateToTimestamp = (dateObj) => {
531
+ if (!dateObj) return null;
532
+ return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
533
+ };
534
+ var getEmptyFilterValues = (filters, options = {}) => {
535
+ const out = {};
536
+ for (const filter of filters || []) {
537
+ out[filter.name] = typeof options.getEmptyValue === "function" ? options.getEmptyValue(filter) : getEmptyFilterValue(filter);
538
+ }
539
+ return out;
540
+ };
541
+ var resetFilterValues = (filters, values = {}, key = "all", options = {}) => {
542
+ if (key === "all") return getEmptyFilterValues(filters, options);
543
+ const filter = (filters || []).find((item) => item.name === key);
544
+ const emptyValue = filter ? typeof options.getEmptyValue === "function" ? options.getEmptyValue(filter) : getEmptyFilterValue(filter) : options.fallbackEmptyValue ?? "";
545
+ return { ...values || {}, [key]: emptyValue };
546
+ };
547
+ var findOptionLabel = (filter, value) => {
548
+ var _a;
549
+ return ((_a = (filter.options || []).find((option) => option.value === value)) == null ? void 0 : _a.label) || value;
550
+ };
551
+ var buildActiveFilterChips = (filters, values = {}, options = {}) => {
552
+ const chips = [];
553
+ const isActive = options.isFilterActive || isFilterActive;
554
+ const dateFormatter = options.formatDate || formatDateChip;
555
+ const dateJoiner = options.dateJoiner ?? " ";
556
+ for (const filter of filters || []) {
557
+ const value = values == null ? void 0 : values[filter.name];
558
+ if (!isActive(filter, value)) continue;
559
+ const type = filter.type || "select";
560
+ const prefix = filter.chipLabel || filter.placeholder || filter.label || filter.name;
561
+ if (type === "multiselect") {
562
+ const labels = (Array.isArray(value) ? value : []).map((item) => findOptionLabel(filter, item)).join(", ");
563
+ chips.push({ key: filter.name, label: `${prefix}: ${labels}` });
564
+ } else if (type === "dateRange") {
565
+ const parts = [];
566
+ if (value == null ? void 0 : value.from) parts.push(`${options.dateFromPrefix ?? "from "}${dateFormatter(value.from)}`);
567
+ if (value == null ? void 0 : value.to) parts.push(`${options.dateToPrefix ?? "to "}${dateFormatter(value.to)}`);
568
+ chips.push({ key: filter.name, label: `${prefix}: ${parts.join(dateJoiner)}` });
569
+ } else {
570
+ chips.push({ key: filter.name, label: `${prefix}: ${findOptionLabel(filter, value)}` });
571
+ }
572
+ }
573
+ return chips;
574
+ };
575
+ var toStableKey = (value) => {
576
+ try {
577
+ return JSON.stringify(value);
578
+ } catch (_error) {
579
+ return String(value);
580
+ }
581
+ };
582
+ var filterRows = (rows, filters, values = {}) => {
583
+ let result = rows;
584
+ for (const filter of filters || []) {
585
+ const value = values[filter.name];
586
+ if (!isFilterActive(filter, value)) continue;
587
+ const type = filter.type || "select";
588
+ if (filter.filterFn) {
589
+ result = result.filter((row) => filter.filterFn(row, value));
590
+ } else if (type === "multiselect") {
591
+ result = result.filter((row) => value.includes(row[filter.name]));
592
+ } else if (type === "dateRange") {
593
+ const fromTs = dateToTimestamp(value.from);
594
+ const toTs = value.to ? dateToTimestamp(value.to) + 864e5 - 1 : null;
595
+ result = result.filter((row) => {
596
+ const rowTs = new Date(row[filter.name]).getTime();
597
+ if (Number.isNaN(rowTs)) return false;
598
+ if (fromTs && rowTs < fromTs) return false;
599
+ if (toTs && rowTs > toTs) return false;
600
+ return true;
601
+ });
602
+ } else {
603
+ result = result.filter((row) => row[filter.name] === value);
604
+ }
605
+ }
606
+ return result;
607
+ };
608
+ var searchRows = (rows, term, fields, opts = {}) => {
609
+ const { fuzzy = false, fuzzyOptions } = opts;
610
+ const t = String(term ?? "").toLowerCase();
611
+ if (!t || !fields || fields.length === 0) return rows;
612
+ if (fuzzy) {
613
+ const fuse = new import_fuse.default(rows, {
614
+ keys: fields,
615
+ threshold: 0.4,
616
+ distance: 100,
617
+ ignoreLocation: true,
618
+ ...fuzzyOptions
619
+ });
620
+ return fuse.search(t).map((r) => r.item);
621
+ }
622
+ return rows.filter(
623
+ (row) => fields.some((field) => {
624
+ const val = row[field];
625
+ return val && String(val).toLowerCase().includes(t);
626
+ })
627
+ );
628
+ };
483
629
 
484
630
  // src/utils/interactionHooks.js
485
631
  var import_react5 = require("react");
486
632
  var import_ui_extensions5 = require("@hubspot/ui-extensions");
633
+ var useDebouncedDispatch = (value, debounceMs, dispatch) => {
634
+ const debounced = (0, import_ui_extensions5.useDebounce)(value, debounceMs > 0 ? debounceMs : 300);
635
+ const pendingRef = (0, import_react5.useRef)(null);
636
+ (0, import_react5.useEffect)(() => {
637
+ if (debounceMs <= 0) return;
638
+ if (pendingRef.current == null) return;
639
+ if (debounced !== pendingRef.current) return;
640
+ const next = pendingRef.current;
641
+ pendingRef.current = null;
642
+ dispatch(next);
643
+ }, [debounceMs, debounced, dispatch]);
644
+ return (0, import_react5.useCallback)(
645
+ (next) => {
646
+ if (debounceMs > 0) {
647
+ pendingRef.current = next;
648
+ } else {
649
+ pendingRef.current = null;
650
+ dispatch(next);
651
+ }
652
+ },
653
+ [debounceMs, dispatch]
654
+ );
655
+ };
656
+ var useSelectionReset = ({ resetKey, enabled, isControlled, clearSelection }) => {
657
+ const ref = (0, import_react5.useRef)("");
658
+ (0, import_react5.useEffect)(() => {
659
+ if (!enabled || isControlled) {
660
+ ref.current = resetKey;
661
+ return;
662
+ }
663
+ if (ref.current && ref.current !== resetKey) {
664
+ clearSelection();
665
+ }
666
+ ref.current = resetKey;
667
+ }, [resetKey, enabled, isControlled, clearSelection]);
668
+ };
487
669
 
488
670
  // src/common-components/CollectionCount.js
489
671
  var import_react6 = __toESM(require("react"));
@@ -783,7 +965,7 @@ var GENERATED_ICONS = {
783
965
  "ZoomOut": { "viewBox": "0 0 32 32", "paths": ["M14.42 26.75c2.85 0 5.47-.97 7.56-2.6l-.03.02 5.28 5.34a1.619 1.619 0 0 0 2.76-1.15c0-.45-.18-.85-.47-1.14l-5.33-5.33c1.59-2.06 2.55-4.68 2.55-7.52C26.74 7.54 21.2 2 14.37 2S2 7.55 2 14.38s5.54 12.37 12.37 12.37h.05m0-21.55c5.06 0 9.16 4.1 9.16 9.16s-4.1 9.16-9.16 9.16-9.16-4.1-9.16-9.16c.01-5.05 4.11-9.14 9.16-9.15Zm-4.31 10.78h8.62c.89 0 1.62-.72 1.62-1.62s-.72-1.62-1.62-1.62h-8.62c-.89 0-1.62.72-1.62 1.62s.72 1.62 1.62 1.62"] }
784
966
  };
785
967
 
786
- // src/common-components/Icon.js
968
+ // src/common-components/nativeIconNames.js
787
969
  var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
788
970
  "add",
789
971
  "appointment",
@@ -795,12 +977,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
795
977
  "block",
796
978
  "book",
797
979
  "bulb",
980
+ "callTranscript",
798
981
  "calling",
799
982
  "callingHangup",
800
983
  "callingMade",
801
984
  "callingMissed",
802
985
  "callingVoicemail",
803
- "callTranscript",
804
986
  "campaigns",
805
987
  "cap",
806
988
  "checkCircle",
@@ -829,13 +1011,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
829
1011
  "enroll",
830
1012
  "exclamation",
831
1013
  "exclamationCircle",
832
- "facebook",
833
1014
  "faceHappy",
834
1015
  "faceHappyFilled",
835
1016
  "faceNeutral",
836
1017
  "faceNeutralFilled",
837
1018
  "faceSad",
838
1019
  "faceSadFilled",
1020
+ "facebook",
839
1021
  "favoriteHollow",
840
1022
  "file",
841
1023
  "filledXCircleIcon",
@@ -976,6 +1158,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
976
1158
  "zoomIn",
977
1159
  "zoomOut"
978
1160
  ]);
1161
+
1162
+ // src/common-components/Icon.js
979
1163
  var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
980
1164
  var NATIVE_SIZE_TOKENS = {
981
1165
  sm: "sm",
@@ -1217,6 +1401,7 @@ var CollectionFilterControl = ({
1217
1401
  { key: name, direction: "row", align: "center", gap: "xs" },
1218
1402
  h3(import_ui_extensions8.DateInput, {
1219
1403
  name: `${controlName}-from`,
1404
+ label: filter.fromLabel ?? labels.dateFrom,
1220
1405
  placeholder: filter.fromLabel ?? labels.dateFrom,
1221
1406
  format: "medium",
1222
1407
  value: rangeValue.from ?? null,
@@ -1225,6 +1410,7 @@ var CollectionFilterControl = ({
1225
1410
  h3(Icon, { name: "right", size: "sm" }),
1226
1411
  h3(import_ui_extensions8.DateInput, {
1227
1412
  name: `${controlName}-to`,
1413
+ label: filter.toLabel ?? labels.dateTo,
1228
1414
  placeholder: filter.toLabel ?? labels.dateTo,
1229
1415
  format: "medium",
1230
1416
  value: rangeValue.to ?? null,
@@ -1357,12 +1543,1170 @@ var CollectionToolbar = ({
1357
1543
  );
1358
1544
  };
1359
1545
 
1546
+ // src/datatable/editValidation.js
1547
+ var editValidationError = (result) => {
1548
+ if (result === true || result === void 0 || result === null) return null;
1549
+ return typeof result === "string" ? result : "Invalid value";
1550
+ };
1551
+
1552
+ // src/datatable/rowExpansion.js
1553
+ var extractRowId = (row, rowIdField = "id", fallback = void 0) => {
1554
+ const id = row == null ? void 0 : row[rowIdField];
1555
+ return id != null ? id : fallback;
1556
+ };
1557
+ var normalizeExpandedIds = (ids) => {
1558
+ if (ids == null) return /* @__PURE__ */ new Set();
1559
+ const list = ids instanceof Set ? [...ids] : Array.isArray(ids) ? ids : [ids];
1560
+ return new Set(list.filter((id) => id != null));
1561
+ };
1562
+ var expandRowId = (expandedIds, rowId, expandSingle = false) => {
1563
+ if (rowId == null) return expandedIds;
1564
+ if (expandSingle) return /* @__PURE__ */ new Set([rowId]);
1565
+ const next = new Set(expandedIds);
1566
+ next.add(rowId);
1567
+ return next;
1568
+ };
1569
+ var collapseRowId = (expandedIds, rowId) => {
1570
+ if (rowId == null || !expandedIds.has(rowId)) return expandedIds;
1571
+ const next = new Set(expandedIds);
1572
+ next.delete(rowId);
1573
+ return next;
1574
+ };
1575
+ var toggleExpandedId = (expandedIds, rowId, expandSingle = false) => {
1576
+ if (rowId == null) return expandedIds;
1577
+ return expandedIds.has(rowId) ? collapseRowId(expandedIds, rowId) : expandRowId(expandedIds, rowId, expandSingle);
1578
+ };
1579
+ var withDetailRows = (items, expandedIds, rowIdField = "id") => {
1580
+ if (!expandedIds || expandedIds.size === 0) return items;
1581
+ const out = [];
1582
+ items.forEach((item) => {
1583
+ out.push(item);
1584
+ if (item.type === "data" && expandedIds.has(extractRowId(item.row, rowIdField))) {
1585
+ out.push({ type: "detail", row: item.row });
1586
+ }
1587
+ });
1588
+ return out;
1589
+ };
1590
+
1360
1591
  // src/datatable/DataTable.jsx
1361
1592
  var import_ui_extensions10 = require("@hubspot/ui-extensions");
1593
+ var NARROW_EDIT_TYPES = /* @__PURE__ */ new Set(["checkbox", "toggle"]);
1594
+ var DATE_PATTERN = /^\d{4}[-/]\d{2}[-/]\d{2}/;
1595
+ var BOOL_VALUES = /* @__PURE__ */ new Set(["true", "false", "yes", "no", "0", "1"]);
1596
+ var SORT_DIRECTIONS = /* @__PURE__ */ new Set(["ascending", "descending", "none"]);
1597
+ var normalizeSortState = (columns, sort) => {
1598
+ const normalized = {};
1599
+ columns.forEach((col) => {
1600
+ if (col.sortable) normalized[col.field] = "none";
1601
+ });
1602
+ if (!sort) return normalized;
1603
+ if (sort.field && SORT_DIRECTIONS.has(sort.direction) && sort.field in normalized) {
1604
+ normalized[sort.field] = sort.direction;
1605
+ return normalized;
1606
+ }
1607
+ Object.keys(normalized).forEach((field) => {
1608
+ const direction = sort[field];
1609
+ if (SORT_DIRECTIONS.has(direction)) normalized[field] = direction;
1610
+ });
1611
+ return normalized;
1612
+ };
1613
+ var serializeSortState = (sortState) => {
1614
+ const activeField = Object.keys(sortState).find((field) => sortState[field] !== "none");
1615
+ if (!activeField) return null;
1616
+ return { field: activeField, direction: sortState[activeField] };
1617
+ };
1618
+ var computeAutoWidths = (columns, data) => {
1619
+ if (!data || data.length === 0) return {};
1620
+ const sample = data.slice(0, 50);
1621
+ const results = {};
1622
+ columns.forEach((col) => {
1623
+ if (col.width && col.cellWidth) return;
1624
+ const values = sample.map((row) => row[col.field]).filter((v) => v != null);
1625
+ const strings = values.map((v) => {
1626
+ const s = String(v);
1627
+ const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
1628
+ return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
1629
+ });
1630
+ let widthHint = null;
1631
+ let cellWidthHint = null;
1632
+ if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
1633
+ cellWidthHint = "min";
1634
+ }
1635
+ if (col.truncate === true) {
1636
+ cellWidthHint = cellWidthHint || "min";
1637
+ }
1638
+ if (strings.length > 0) {
1639
+ const lengths = strings.map((s) => s.length);
1640
+ const maxLen = Math.max(...lengths);
1641
+ const uniqueCount = new Set(strings).size;
1642
+ if (values.every((v) => typeof v === "boolean") || strings.every((s) => BOOL_VALUES.has(s.toLowerCase()))) {
1643
+ widthHint = widthHint || "min";
1644
+ cellWidthHint = cellWidthHint || "min";
1645
+ } else if (strings.every((s) => DATE_PATTERN.test(s))) {
1646
+ widthHint = widthHint || "min";
1647
+ cellWidthHint = cellWidthHint || "auto";
1648
+ } else if (values.every((v) => typeof v === "number")) {
1649
+ widthHint = widthHint || "auto";
1650
+ cellWidthHint = cellWidthHint || "auto";
1651
+ } else if (uniqueCount <= 5 && maxLen <= 15) {
1652
+ widthHint = widthHint || "min";
1653
+ cellWidthHint = cellWidthHint || "auto";
1654
+ } else {
1655
+ widthHint = widthHint || "auto";
1656
+ cellWidthHint = cellWidthHint || "auto";
1657
+ }
1658
+ }
1659
+ if (col.editable && !NARROW_EDIT_TYPES.has(col.editType) && widthHint === "min") {
1660
+ widthHint = "auto";
1661
+ }
1662
+ results[col.field] = {
1663
+ width: widthHint || "auto",
1664
+ cellWidth: cellWidthHint || "auto"
1665
+ };
1666
+ });
1667
+ return results;
1668
+ };
1669
+ var BOOLEAN_SELECT_OPTIONS = [
1670
+ { label: "Yes", value: true },
1671
+ { label: "No", value: false }
1672
+ ];
1673
+ var resolveEditOptions = (col, data) => {
1674
+ if (col.editOptions && col.editOptions.length > 0) return col.editOptions;
1675
+ const sample = data.find((row) => row[col.field] != null);
1676
+ if (sample && typeof sample[col.field] === "boolean") return BOOLEAN_SELECT_OPTIONS;
1677
+ return [];
1678
+ };
1679
+ var DataTable = ({
1680
+ // Data
1681
+ data,
1682
+ columns,
1683
+ renderRow,
1684
+ // Search
1685
+ searchFields = [],
1686
+ searchPlaceholder = "Search...",
1687
+ fuzzySearch = false,
1688
+ // enable fuzzy matching via Fuse.js
1689
+ fuzzyOptions,
1690
+ // custom Fuse.js options (threshold, distance, etc.)
1691
+ showSearch = true,
1692
+ // show the SearchInput in the toolbar
1693
+ // Filters
1694
+ filters = [],
1695
+ showFilterBadges = true,
1696
+ // show active filter chips/badges
1697
+ showClearFiltersButton,
1698
+ // show "Clear all" reset button; defaults to showFilterBadges when omitted
1699
+ filterInlineLimit = 2,
1700
+ // number of filters shown inline before overflow
1701
+ toolbarLeftFlex = 3,
1702
+ // left toolbar column flex weight (search/filters/chips)
1703
+ toolbarRightFlex = 1,
1704
+ // right toolbar column flex weight (count/actions)
1705
+ // Pagination
1706
+ pageSize = 10,
1707
+ maxVisiblePageButtons,
1708
+ // max page number buttons to show
1709
+ showButtonLabels = true,
1710
+ // show First/Prev/Next/Last text labels
1711
+ showFirstLastButtons,
1712
+ // show First/Last page buttons (default: auto when pageCount > 5)
1713
+ // Row count
1714
+ title,
1715
+ // optional title shown as demibold text above the table toolbar
1716
+ showRowCount = true,
1717
+ // show "X records" / "X of Y records" text
1718
+ rowCountBold = false,
1719
+ // bold the row count text
1720
+ rowCountText,
1721
+ // custom formatter: (shownOnPage, totalMatching) => string
1722
+ // Table appearance
1723
+ bordered = true,
1724
+ // show table borders
1725
+ flush = true,
1726
+ // remove bottom margin
1727
+ scrollable = false,
1728
+ // allow horizontal overflow with scrollbar
1729
+ // Sorting
1730
+ defaultSort = {},
1731
+ // Grouping
1732
+ groupBy,
1733
+ // Footer
1734
+ footer,
1735
+ // Empty state
1736
+ emptyTitle,
1737
+ emptyMessage,
1738
+ // -----------------------------------------------------------------------
1739
+ // Server-side mode
1740
+ // -----------------------------------------------------------------------
1741
+ serverSide = false,
1742
+ loading = false,
1743
+ // show loading spinner over the table
1744
+ error,
1745
+ // error message string or boolean — shows ErrorState
1746
+ totalCount,
1747
+ // server total (server-side only)
1748
+ clientTotalCount,
1749
+ // optional client-mode total when data is lazy-loaded
1750
+ page: externalPage,
1751
+ // controlled page (server-side only)
1752
+ searchValue,
1753
+ // controlled search term (server-side only)
1754
+ filterValues: externalFilterValues,
1755
+ // controlled filter values (server-side only)
1756
+ sort: externalSort,
1757
+ // controlled sort state, e.g. { field: "ascending" }
1758
+ searchDebounce = 0,
1759
+ // ms to debounce onSearchChange callback
1760
+ resetPageOnChange = true,
1761
+ // auto-reset to page 1 on search/filter/sort change
1762
+ onSearchChange,
1763
+ // (searchTerm) => void
1764
+ onFilterChange,
1765
+ // (filterValues) => void
1766
+ onSortChange,
1767
+ // (field, direction) => void
1768
+ onPageChange,
1769
+ // (page) => void
1770
+ onParamsChange,
1771
+ // ({ search, filters, sort, page }) => void
1772
+ // -----------------------------------------------------------------------
1773
+ // Row selection
1774
+ // -----------------------------------------------------------------------
1775
+ selectable = false,
1776
+ rowIdField = "id",
1777
+ // field name used as unique row identifier
1778
+ selectedIds: externalSelectedIds,
1779
+ // controlled selection — array of row IDs
1780
+ onSelectionChange,
1781
+ // (selectedIds[]) => void
1782
+ onSelectAllRequest,
1783
+ // server-side: ({ selectedIds, pageIds, totalCount }) => void
1784
+ selectionActions = [],
1785
+ // [{ label, onClick(selectedIds[]), icon?, variant? }]
1786
+ selectionResetKey,
1787
+ // optional key to force clear uncontrolled selection memory
1788
+ resetSelectionOnQueryChange = true,
1789
+ // clear uncontrolled selection on search/filter/sort changes
1790
+ showSelectionBar = true,
1791
+ // show the selection action bar when rows are selected
1792
+ recordLabel,
1793
+ // { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
1794
+ // -----------------------------------------------------------------------
1795
+ // Row actions
1796
+ // -----------------------------------------------------------------------
1797
+ rowActions,
1798
+ // [{ label, onClick(row), icon?, variant? }] or (row) => actions[]
1799
+ hideRowActionsWhenSelectionActive = false,
1800
+ // hide row action column while selected-row action bar is visible
1801
+ // -----------------------------------------------------------------------
1802
+ // Row expansion (detail rows)
1803
+ // -----------------------------------------------------------------------
1804
+ renderExpandedRow,
1805
+ // (row) => ReactNode — providing this enables the feature
1806
+ expandedRowIds: externalExpandedRowIds,
1807
+ // controlled — array of expanded row IDs
1808
+ defaultExpandedRowIds,
1809
+ // uncontrolled — initially expanded row IDs
1810
+ onExpandedRowsChange,
1811
+ // (expandedRowIds[]) => void
1812
+ expandOn = "icon",
1813
+ // "icon" (chevron toggle column) | "row" (click row content)
1814
+ expandSingle = false,
1815
+ // accordion mode — expanding a row collapses the others
1816
+ // -----------------------------------------------------------------------
1817
+ // Inline editing
1818
+ // -----------------------------------------------------------------------
1819
+ editMode,
1820
+ // "discrete" (click-to-edit) | "inline" (always show inputs)
1821
+ editingRowId,
1822
+ // controlled — row ID currently in full-row edit mode
1823
+ onRowEdit,
1824
+ // (row, field, newValue) => void
1825
+ onRowEditInput,
1826
+ // optional live-input callback: (row, field, inputValue) => void
1827
+ onEditStart,
1828
+ // (row, field, currentValue) => void — fires when editing begins
1829
+ onEditCancel,
1830
+ // (row, field) => void — fires when editing is cancelled without commit
1831
+ // -----------------------------------------------------------------------
1832
+ // Auto-width
1833
+ // -----------------------------------------------------------------------
1834
+ autoWidth = true,
1835
+ // auto-compute column widths from content analysis
1836
+ // -----------------------------------------------------------------------
1837
+ // Labels / i18n
1838
+ // -----------------------------------------------------------------------
1839
+ labels,
1840
+ // override hardcoded UI strings for i18n
1841
+ // -----------------------------------------------------------------------
1842
+ // Render overrides (Phase 3 — full replacement escape hatches)
1843
+ // -----------------------------------------------------------------------
1844
+ renderSelectionBar,
1845
+ // ({ selectedIds, selectedCount, displayCount, countLabel, onSelectAll, onDeselectAll, selectionActions }) => ReactNode
1846
+ renderEmptyState,
1847
+ // ({ title, message }) => ReactNode
1848
+ renderLoadingState,
1849
+ // ({ label }) => ReactNode
1850
+ renderErrorState
1851
+ // ({ error, title, message }) => ReactNode
1852
+ }) => {
1853
+ const initialSortState = (0, import_react10.useMemo)(() => {
1854
+ return normalizeSortState(columns, defaultSort);
1855
+ }, [columns, defaultSort]);
1856
+ const [internalSearchTerm, setInternalSearchTerm] = (0, import_react10.useState)(
1857
+ () => serverSide && searchValue != null ? searchValue : ""
1858
+ );
1859
+ const [internalFilterValues, setInternalFilterValues] = (0, import_react10.useState)(() => getEmptyFilterValues(filters));
1860
+ const [internalSortState, setInternalSortState] = (0, import_react10.useState)(initialSortState);
1861
+ const [currentPage, setCurrentPage] = (0, import_react10.useState)(1);
1862
+ const lastAppliedSearchRef = (0, import_react10.useRef)(
1863
+ serverSide && searchValue != null ? searchValue : ""
1864
+ );
1865
+ const searchTerm = serverSide && searchValue != null ? searchValue : internalSearchTerm;
1866
+ (0, import_react10.useEffect)(() => {
1867
+ if (!serverSide || searchValue == null) return;
1868
+ if (searchValue === lastAppliedSearchRef.current) return;
1869
+ lastAppliedSearchRef.current = searchValue;
1870
+ setInternalSearchTerm(searchValue);
1871
+ }, [serverSide, searchValue]);
1872
+ const filterValues = serverSide && externalFilterValues != null ? externalFilterValues : internalFilterValues;
1873
+ const externalSortState = (0, import_react10.useMemo)(
1874
+ () => normalizeSortState(columns, externalSort),
1875
+ [columns, externalSort]
1876
+ );
1877
+ const sortState = serverSide && externalSort != null ? externalSortState : internalSortState;
1878
+ const activePage = serverSide && externalPage != null ? externalPage : currentPage;
1879
+ (0, import_react10.useEffect)(() => {
1880
+ if (!serverSide) setCurrentPage(1);
1881
+ }, [internalSearchTerm, internalFilterValues, internalSortState, serverSide]);
1882
+ const fireSearchCallback = (0, import_react10.useCallback)((term) => {
1883
+ if (serverSide && onSearchChange) onSearchChange(term);
1884
+ }, [serverSide, onSearchChange]);
1885
+ const fireParamsChange = (0, import_react10.useCallback)((overrides) => {
1886
+ if (!onParamsChange) return;
1887
+ const nextSortState = overrides.sort != null ? normalizeSortState(columns, overrides.sort) : sortState;
1888
+ onParamsChange({
1889
+ search: overrides.search != null ? overrides.search : searchTerm,
1890
+ filters: overrides.filters != null ? overrides.filters : filterValues,
1891
+ sort: serializeSortState(nextSortState),
1892
+ page: overrides.page != null ? overrides.page : activePage
1893
+ });
1894
+ }, [onParamsChange, columns, searchTerm, filterValues, sortState, activePage]);
1895
+ const resetPage = (0, import_react10.useCallback)(() => {
1896
+ if (resetPageOnChange) {
1897
+ setCurrentPage(1);
1898
+ if (serverSide && onPageChange) onPageChange(1);
1899
+ }
1900
+ }, [resetPageOnChange, serverSide, onPageChange]);
1901
+ const dispatchSearchChange = (0, import_react10.useCallback)((term) => {
1902
+ lastAppliedSearchRef.current = term;
1903
+ fireSearchCallback(term);
1904
+ fireParamsChange({ search: term, page: resetPageOnChange ? 1 : void 0 });
1905
+ }, [fireSearchCallback, fireParamsChange, resetPageOnChange]);
1906
+ const dispatchSearchDebounced = useDebouncedDispatch(
1907
+ internalSearchTerm,
1908
+ searchDebounce,
1909
+ dispatchSearchChange
1910
+ );
1911
+ const handleSearchChange = (0, import_react10.useCallback)((term) => {
1912
+ setInternalSearchTerm(term);
1913
+ resetPage();
1914
+ dispatchSearchDebounced(term);
1915
+ }, [dispatchSearchDebounced, resetPage]);
1916
+ const handleFilterChange = (0, import_react10.useCallback)((name, value) => {
1917
+ const next = { ...filterValues, [name]: value };
1918
+ setInternalFilterValues(next);
1919
+ if (serverSide && onFilterChange) onFilterChange(next);
1920
+ resetPage();
1921
+ fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
1922
+ }, [filterValues, serverSide, onFilterChange, fireParamsChange, resetPage, resetPageOnChange]);
1923
+ const handleSortChange = (0, import_react10.useCallback)((field) => {
1924
+ const current = sortState[field] || "none";
1925
+ const nextDirection = current === "none" ? "ascending" : current === "ascending" ? "descending" : "none";
1926
+ const reset = {};
1927
+ columns.forEach((col) => {
1928
+ if (col.sortable) reset[col.field] = "none";
1929
+ });
1930
+ const next = nextDirection === "none" ? reset : { ...reset, [field]: nextDirection };
1931
+ setInternalSortState(next);
1932
+ if (serverSide && onSortChange) onSortChange(field, nextDirection);
1933
+ resetPage();
1934
+ fireParamsChange({ sort: next, page: resetPageOnChange ? 1 : void 0 });
1935
+ }, [sortState, columns, serverSide, onSortChange, fireParamsChange, resetPage, resetPageOnChange]);
1936
+ const handlePageChange = (0, import_react10.useCallback)((page) => {
1937
+ setCurrentPage(page);
1938
+ if (serverSide && onPageChange) onPageChange(page);
1939
+ fireParamsChange({ page });
1940
+ }, [serverSide, onPageChange, fireParamsChange]);
1941
+ const filteredData = (0, import_react10.useMemo)(() => {
1942
+ if (serverSide) return data;
1943
+ let result = filterRows(data, filters, filterValues);
1944
+ if (searchTerm && searchFields.length > 0) {
1945
+ result = searchRows(result, searchTerm, searchFields, {
1946
+ fuzzy: fuzzySearch,
1947
+ fuzzyOptions
1948
+ });
1949
+ }
1950
+ return result;
1951
+ }, [data, filterValues, searchTerm, filters, searchFields, serverSide, fuzzySearch, fuzzyOptions]);
1952
+ const sortedData = (0, import_react10.useMemo)(() => {
1953
+ if (serverSide) return filteredData;
1954
+ const activeField = Object.keys(sortState).find((k) => sortState[k] !== "none");
1955
+ if (!activeField) return filteredData;
1956
+ const activeCol = columns.find((c) => c.field === activeField);
1957
+ const sortOrder = Array.isArray(activeCol == null ? void 0 : activeCol.sortOrder) ? activeCol.sortOrder : null;
1958
+ const sortOrderIndex = (val) => {
1959
+ const idx = sortOrder.indexOf(val);
1960
+ return idx === -1 ? sortOrder.length : idx;
1961
+ };
1962
+ return [...filteredData].sort((a, b) => {
1963
+ const dir = sortState[activeField] === "ascending" ? 1 : -1;
1964
+ const aVal = a[activeField];
1965
+ const bVal = b[activeField];
1966
+ if (typeof (activeCol == null ? void 0 : activeCol.sortComparator) === "function") {
1967
+ return dir * activeCol.sortComparator(aVal, bVal, a, b);
1968
+ }
1969
+ if (sortOrder) {
1970
+ const diff = sortOrderIndex(aVal) - sortOrderIndex(bVal);
1971
+ if (diff !== 0) return dir * diff;
1972
+ }
1973
+ if (aVal == null && bVal == null) return 0;
1974
+ if (aVal == null) return 1;
1975
+ if (bVal == null) return -1;
1976
+ if (aVal < bVal) return -dir;
1977
+ if (aVal > bVal) return dir;
1978
+ return 0;
1979
+ });
1980
+ }, [filteredData, sortState, serverSide, columns]);
1981
+ const groupedData = (0, import_react10.useMemo)(() => {
1982
+ if (!groupBy) return null;
1983
+ const source = serverSide ? data : sortedData;
1984
+ const groups = {};
1985
+ source.forEach((row) => {
1986
+ const key = row[groupBy.field] ?? "--";
1987
+ if (!groups[key]) groups[key] = [];
1988
+ groups[key].push(row);
1989
+ });
1990
+ let groupKeys = Object.keys(groups);
1991
+ if (groupBy.sort) {
1992
+ if (typeof groupBy.sort === "function") {
1993
+ groupKeys.sort(groupBy.sort);
1994
+ } else {
1995
+ const dir = groupBy.sort === "desc" ? -1 : 1;
1996
+ groupKeys.sort((a, b) => a < b ? -dir : a > b ? dir : 0);
1997
+ }
1998
+ }
1999
+ return groupKeys.map((key) => ({
2000
+ key,
2001
+ label: groupBy.label ? groupBy.label(key, groups[key]) : key,
2002
+ rows: groups[key]
2003
+ }));
2004
+ }, [sortedData, data, groupBy, serverSide]);
2005
+ const [expandedGroups, setExpandedGroups] = (0, import_react10.useState)(() => {
2006
+ if (!groupBy) return /* @__PURE__ */ new Set();
2007
+ const defaultExpanded = groupBy.defaultExpanded !== false;
2008
+ if (defaultExpanded && groupedData) {
2009
+ return new Set(groupedData.map((g) => g.key));
2010
+ }
2011
+ return /* @__PURE__ */ new Set();
2012
+ });
2013
+ (0, import_react10.useEffect)(() => {
2014
+ if (!groupedData) return;
2015
+ const defaultExpanded = (groupBy == null ? void 0 : groupBy.defaultExpanded) !== false;
2016
+ if (defaultExpanded) {
2017
+ setExpandedGroups((prev) => {
2018
+ const next = new Set(prev);
2019
+ groupedData.forEach((g) => next.add(g.key));
2020
+ return next;
2021
+ });
2022
+ }
2023
+ }, [groupedData, groupBy]);
2024
+ const toggleGroup = (0, import_react10.useCallback)((key) => {
2025
+ setExpandedGroups((prev) => {
2026
+ const next = new Set(prev);
2027
+ if (next.has(key)) next.delete(key);
2028
+ else next.add(key);
2029
+ return next;
2030
+ });
2031
+ }, []);
2032
+ const datasetRows = (0, import_react10.useMemo)(() => {
2033
+ if (!groupedData) return serverSide ? data : sortedData;
2034
+ return groupedData.flatMap((group) => group.rows);
2035
+ }, [groupedData, sortedData, data, serverSide]);
2036
+ const resolvedTotalCount = typeof totalCount === "number" ? totalCount : void 0;
2037
+ const resolvedClientTotalCount = typeof clientTotalCount === "number" ? clientTotalCount : void 0;
2038
+ const totalItems = serverSide ? resolvedTotalCount || data.length : Math.max(datasetRows.length, resolvedClientTotalCount || 0);
2039
+ const pageCount = Math.ceil(totalItems / pageSize);
2040
+ const paginatedRows = (0, import_react10.useMemo)(() => {
2041
+ if (serverSide) return datasetRows;
2042
+ return datasetRows.slice(
2043
+ (activePage - 1) * pageSize,
2044
+ activePage * pageSize
2045
+ );
2046
+ }, [serverSide, datasetRows, activePage, pageSize]);
2047
+ const displayRows = (0, import_react10.useMemo)(() => {
2048
+ if (!groupedData) return paginatedRows.map((row) => ({ type: "data", row }));
2049
+ const pageRows = new Set(paginatedRows);
2050
+ const rows = [];
2051
+ groupedData.forEach((group) => {
2052
+ const groupPageRows = group.rows.filter((row) => pageRows.has(row));
2053
+ if (groupPageRows.length === 0) return;
2054
+ rows.push({ type: "group-header", group });
2055
+ if (expandedGroups.has(group.key)) {
2056
+ groupPageRows.forEach((row) => rows.push({ type: "data", row }));
2057
+ }
2058
+ });
2059
+ return rows;
2060
+ }, [groupedData, paginatedRows, expandedGroups]);
2061
+ const expandable = typeof renderExpandedRow === "function";
2062
+ const showExpandColumn = expandable && expandOn === "icon";
2063
+ const [internalExpandedRowIds, setInternalExpandedRowIds] = (0, import_react10.useState)(
2064
+ () => normalizeExpandedIds(defaultExpandedRowIds)
2065
+ );
2066
+ const expandedRowIds = (0, import_react10.useMemo)(
2067
+ () => externalExpandedRowIds != null ? normalizeExpandedIds(externalExpandedRowIds) : internalExpandedRowIds,
2068
+ [externalExpandedRowIds, internalExpandedRowIds]
2069
+ );
2070
+ const toggleRowExpanded = (0, import_react10.useCallback)((rowId) => {
2071
+ if (rowId == null) return;
2072
+ const next = toggleExpandedId(expandedRowIds, rowId, expandSingle);
2073
+ if (externalExpandedRowIds == null) setInternalExpandedRowIds(next);
2074
+ if (onExpandedRowsChange) onExpandedRowsChange([...next]);
2075
+ }, [expandedRowIds, expandSingle, externalExpandedRowIds, onExpandedRowsChange]);
2076
+ const renderedRows = (0, import_react10.useMemo)(() => {
2077
+ if (!expandable) return displayRows;
2078
+ return withDetailRows(displayRows, expandedRowIds, rowIdField);
2079
+ }, [expandable, displayRows, expandedRowIds, rowIdField]);
2080
+ const footerData = serverSide ? data : filteredData;
2081
+ const activeChips = (0, import_react10.useMemo)(
2082
+ () => buildActiveFilterChips(filters, filterValues),
2083
+ [filterValues, filters]
2084
+ );
2085
+ const handleFilterRemove = (0, import_react10.useCallback)((key) => {
2086
+ const next = resetFilterValues(filters, filterValues, key);
2087
+ setInternalFilterValues(next);
2088
+ if (serverSide && onFilterChange) onFilterChange(next);
2089
+ resetPage();
2090
+ fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
2091
+ }, [filters, filterValues, serverSide, onFilterChange, resetPage, fireParamsChange, resetPageOnChange]);
2092
+ const displayCount = serverSide ? resolvedTotalCount || data.length : Math.max(filteredData.length, resolvedClientTotalCount || 0);
2093
+ const totalDataCount = serverSide ? resolvedTotalCount || data.length : Math.max(data.length, resolvedClientTotalCount || 0);
2094
+ const shownOnPageCount = displayRows.filter((item) => item.type === "data").length;
2095
+ const pluralLabel = ((recordLabel == null ? void 0 : recordLabel.plural) || "records").toLowerCase();
2096
+ const singularLabel = ((recordLabel == null ? void 0 : recordLabel.singular) || "record").toLowerCase();
2097
+ const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
2098
+ const resolvedEmptyTitle = emptyTitle || "No results found";
2099
+ const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
2100
+ const resolvedSelectedLabel = (labels == null ? void 0 : labels.selected) || ((count, label) => `${count}\xA0${label}\xA0selected`);
2101
+ const resolvedSelectAllLabel = (labels == null ? void 0 : labels.selectAll) || ((count, label) => `Select all ${count} ${label}`);
2102
+ const resolvedDeselectAllLabel = (labels == null ? void 0 : labels.deselectAll) || "Deselect all";
2103
+ const resolvedFiltersButtonLabel = (labels == null ? void 0 : labels.filtersButton) || "Filters";
2104
+ const resolvedClearAllLabel = (labels == null ? void 0 : labels.clearAll) || "Clear all";
2105
+ const resolvedDateFromLabel = (labels == null ? void 0 : labels.dateFrom) || "From";
2106
+ const resolvedDateToLabel = (labels == null ? void 0 : labels.dateTo) || "To";
2107
+ const resolvedLoadingLabel = (labels == null ? void 0 : labels.loading) || `Loading ${pluralLabel}...`;
2108
+ const resolvedLoadingMessage = (labels == null ? void 0 : labels.loadingMessage) || "This should only take a moment.";
2109
+ const resolvedErrorTitle = (labels == null ? void 0 : labels.errorTitle) || "Something went wrong.";
2110
+ const resolvedErrorMessage = (labels == null ? void 0 : labels.errorMessage) || "An error occurred while loading data.";
2111
+ const resolvedRetryMessage = (labels == null ? void 0 : labels.retryMessage) || "Please try again.";
2112
+ const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
2113
+ const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
2114
+ const [internalSelectedIds, setInternalSelectedIds] = (0, import_react10.useState)(/* @__PURE__ */ new Set());
2115
+ (0, import_react10.useEffect)(() => {
2116
+ if (externalSelectedIds != null) {
2117
+ setInternalSelectedIds(new Set(externalSelectedIds));
2118
+ }
2119
+ }, [externalSelectedIds]);
2120
+ const selectionQueryKey = (0, import_react10.useMemo)(() => {
2121
+ if (!resetSelectionOnQueryChange) return "";
2122
+ return toStableKey({
2123
+ search: searchTerm,
2124
+ filters: filterValues,
2125
+ sort: serializeSortState(sortState)
2126
+ });
2127
+ }, [searchTerm, filterValues, sortState, resetSelectionOnQueryChange]);
2128
+ const combinedSelectionResetKey = (0, import_react10.useMemo)(
2129
+ () => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
2130
+ [selectionQueryKey, selectionResetKey]
2131
+ );
2132
+ const clearSelection = (0, import_react10.useCallback)(() => setInternalSelectedIds(/* @__PURE__ */ new Set()), []);
2133
+ useSelectionReset({
2134
+ resetKey: combinedSelectionResetKey,
2135
+ enabled: selectable,
2136
+ isControlled: externalSelectedIds != null,
2137
+ clearSelection
2138
+ });
2139
+ const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
2140
+ const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
2141
+ const hasToolbarLeft = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || resolvedShowClearFiltersButton);
2142
+ const countInTitleRow = !!title && showToolbarCount && !hasToolbarLeft;
2143
+ const countInToolbar = showToolbarCount && !countInTitleRow;
2144
+ const hasToolbarContent = hasToolbarLeft || countInToolbar;
2145
+ const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
2146
+ const applySelection = (0, import_react10.useCallback)((nextSet) => {
2147
+ if (externalSelectedIds == null) {
2148
+ setInternalSelectedIds(nextSet);
2149
+ }
2150
+ if (onSelectionChange) onSelectionChange([...nextSet]);
2151
+ }, [externalSelectedIds, onSelectionChange]);
2152
+ const pageRowIds = (0, import_react10.useMemo)(() => {
2153
+ if (serverSide) {
2154
+ return data.map((row) => row[rowIdField]).filter((id) => id != null);
2155
+ }
2156
+ return displayRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null);
2157
+ }, [serverSide, data, displayRows, rowIdField]);
2158
+ const allRowIds = (0, import_react10.useMemo)(
2159
+ () => datasetRows.map((row) => row[rowIdField]).filter((id) => id != null),
2160
+ [datasetRows, rowIdField]
2161
+ );
2162
+ const handleSelectRow = (0, import_react10.useCallback)((rowId, checked) => {
2163
+ const next = new Set(selectedIds);
2164
+ if (checked) next.add(rowId);
2165
+ else next.delete(rowId);
2166
+ applySelection(next);
2167
+ }, [selectedIds, applySelection]);
2168
+ const handleSelectAll = (0, import_react10.useCallback)((checked) => {
2169
+ const next = new Set(selectedIds);
2170
+ pageRowIds.forEach((id) => {
2171
+ if (checked) next.add(id);
2172
+ else next.delete(id);
2173
+ });
2174
+ applySelection(next);
2175
+ }, [selectedIds, pageRowIds, applySelection]);
2176
+ const allVisibleSelected = (0, import_react10.useMemo)(() => {
2177
+ return pageRowIds.length > 0 && pageRowIds.every((id) => selectedIds.has(id));
2178
+ }, [pageRowIds, selectedIds]);
2179
+ const handleSelectAllRows = (0, import_react10.useCallback)(() => {
2180
+ const idsToAdd = serverSide ? pageRowIds : allRowIds;
2181
+ const next = new Set(selectedIds);
2182
+ idsToAdd.forEach((id) => next.add(id));
2183
+ applySelection(next);
2184
+ if (serverSide && onSelectAllRequest) {
2185
+ onSelectAllRequest({
2186
+ selectedIds: [...next],
2187
+ pageIds: pageRowIds,
2188
+ totalCount: totalCount || data.length
2189
+ });
2190
+ }
2191
+ }, [serverSide, pageRowIds, allRowIds, selectedIds, applySelection, onSelectAllRequest, totalCount, data.length]);
2192
+ const handleDeselectAll = (0, import_react10.useCallback)(() => {
2193
+ applySelection(/* @__PURE__ */ new Set());
2194
+ }, [applySelection]);
2195
+ const [editingCell, setEditingCell] = (0, import_react10.useState)(null);
2196
+ const [editValue, setEditValue] = (0, import_react10.useState)(null);
2197
+ const [editError, setEditError] = (0, import_react10.useState)(null);
2198
+ const startEditing = (0, import_react10.useCallback)((rowId, field, currentValue) => {
2199
+ setEditingCell({ rowId, field });
2200
+ setEditValue(currentValue);
2201
+ setEditError(null);
2202
+ if (onEditStart) {
2203
+ const row = data.find((r) => r[rowIdField] === rowId);
2204
+ if (row) onEditStart(row, field, currentValue);
2205
+ }
2206
+ }, [onEditStart, data, rowIdField]);
2207
+ const commitEdit = (0, import_react10.useCallback)((row, field, value, options = {}) => {
2208
+ const { keepEditing = false } = options;
2209
+ const col = columns.find((c) => c.field === field);
2210
+ if (col == null ? void 0 : col.editValidate) {
2211
+ const err = editValidationError(col.editValidate(value, row));
2212
+ if (err) {
2213
+ setEditError(err);
2214
+ return false;
2215
+ }
2216
+ }
2217
+ if (onRowEdit) onRowEdit(row, field, value);
2218
+ if (!keepEditing) {
2219
+ setEditingCell(null);
2220
+ setEditValue(null);
2221
+ } else {
2222
+ setEditValue(value);
2223
+ }
2224
+ setEditError(null);
2225
+ return true;
2226
+ }, [onRowEdit, columns]);
2227
+ const renderEditControl = (col, row) => {
2228
+ const type = col.editType || "text";
2229
+ const rowId = row[rowIdField];
2230
+ const fieldName = `edit-${rowId}-${col.field}`;
2231
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
2232
+ const commit = (val) => commitEdit(row, col.field, val);
2233
+ const exitEdit = () => {
2234
+ if (editError) return;
2235
+ if (onEditCancel) onEditCancel(row, col.field);
2236
+ setEditingCell(null);
2237
+ setEditValue(null);
2238
+ };
2239
+ const extra = col.editProps || {};
2240
+ const validate = col.editValidate;
2241
+ const validationProps = validate && editError ? { error: true, validationMessage: editError } : {};
2242
+ const onInputValidate = validate ? (val) => setEditError(editValidationError(validate(val, row))) : void 0;
2243
+ const handleInput = (val) => {
2244
+ setEditValue(val);
2245
+ if (onInputValidate) onInputValidate(val);
2246
+ if (onRowEditInput) onRowEditInput(row, col.field, val);
2247
+ };
2248
+ const maybeExitDatetimeEdit = () => {
2249
+ if (typeof document === "undefined") return;
2250
+ setTimeout(() => {
2251
+ var _a, _b;
2252
+ const activeName = (_b = (_a = document.activeElement) == null ? void 0 : _a.getAttribute) == null ? void 0 : _b.call(_a, "name");
2253
+ if (activeName !== `${fieldName}-date` && activeName !== `${fieldName}-time`) {
2254
+ exitEdit();
2255
+ }
2256
+ }, 0);
2257
+ };
2258
+ switch (type) {
2259
+ case "textarea":
2260
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TextArea, { ...extra, name: fieldName, label: "", value: editValue ?? "", onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
2261
+ case "number":
2262
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.NumberInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
2263
+ case "currency":
2264
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.CurrencyInput, { currencyCode: "USD", ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
2265
+ case "stepper":
2266
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.StepperInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
2267
+ case "select":
2268
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Select, { variant: "transparent", ...extra, name: fieldName, label: "", value: editValue, onChange: commit, options: resolveEditOptions(col, data) });
2269
+ case "multiselect":
2270
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.MultiSelect, { ...extra, name: fieldName, label: "", value: editValue || [], onChange: commit, options: resolveEditOptions(col, data) });
2271
+ case "date":
2272
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.DateInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
2273
+ case "time":
2274
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TimeInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
2275
+ case "datetime":
2276
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.DateInput, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
2277
+ const next = { ...editValue, date: val };
2278
+ handleInput(next);
2279
+ commitEdit(row, col.field, next, { keepEditing: true });
2280
+ }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
2281
+ const next = { ...editValue, time: val };
2282
+ handleInput(next);
2283
+ commitEdit(row, col.field, next, { keepEditing: true });
2284
+ }, onBlur: maybeExitDatetimeEdit }));
2285
+ case "toggle":
2286
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Toggle, { ...extra, name: fieldName, label: "", checked: !!editValue, onChange: commit });
2287
+ case "checkbox":
2288
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Checkbox, { ...extra, name: fieldName, checked: !!editValue, onChange: commit });
2289
+ default:
2290
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Input, { ...extra, name: fieldName, label: "", value: editValue ?? "", onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
2291
+ }
2292
+ };
2293
+ const resolvedEditMode = editMode || (columns.some((col) => col.editable) ? "discrete" : null);
2294
+ const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || expandable || !renderRow;
2295
+ const totalColumnCount = columns.length + (selectable ? 1 : 0) + (showExpandColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
2296
+ const autoWidths = (0, import_react10.useMemo)(
2297
+ () => autoWidth ? computeAutoWidths(columns, data) : {},
2298
+ [columns, data, autoWidth]
2299
+ );
2300
+ const defaultWidth = scrollable ? "min" : "auto";
2301
+ const getHeaderWidth = (col) => {
2302
+ var _a;
2303
+ return col.width || ((_a = autoWidths[col.field]) == null ? void 0 : _a.width) || defaultWidth;
2304
+ };
2305
+ const getCellWidth = (col) => {
2306
+ var _a;
2307
+ return col.cellWidth || col.width || ((_a = autoWidths[col.field]) == null ? void 0 : _a.cellWidth) || defaultWidth;
2308
+ };
2309
+ const [inlineErrors, setInlineErrors] = (0, import_react10.useState)({});
2310
+ const renderInlineControl = (col, row) => {
2311
+ const type = col.editType || "text";
2312
+ const rowId = row[rowIdField];
2313
+ const fieldName = `inline-${rowId}-${col.field}`;
2314
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
2315
+ const cellKey = `${rowId}-${col.field}`;
2316
+ const value = row[col.field];
2317
+ const validate = col.editValidate;
2318
+ const fire = (val) => {
2319
+ if (validate) {
2320
+ const err = editValidationError(validate(val, row));
2321
+ if (err) {
2322
+ setInlineErrors((prev) => ({ ...prev, [cellKey]: err }));
2323
+ return;
2324
+ }
2325
+ setInlineErrors((prev) => {
2326
+ const next = { ...prev };
2327
+ delete next[cellKey];
2328
+ return next;
2329
+ });
2330
+ }
2331
+ if (onRowEdit) onRowEdit(row, col.field, val);
2332
+ };
2333
+ const extra = col.editProps || {};
2334
+ const cellError = inlineErrors[cellKey];
2335
+ const validationProps = cellError ? { error: true, validationMessage: cellError } : {};
2336
+ const onInputValidate = validate ? (val) => {
2337
+ const err = editValidationError(validate(val, row));
2338
+ setInlineErrors((prev) => {
2339
+ if (err) return { ...prev, [cellKey]: err };
2340
+ const next = { ...prev };
2341
+ delete next[cellKey];
2342
+ return next;
2343
+ });
2344
+ } : void 0;
2345
+ const emitInput = (val) => {
2346
+ if (onInputValidate) onInputValidate(val);
2347
+ if (onRowEditInput) onRowEditInput(row, col.field, val);
2348
+ };
2349
+ switch (type) {
2350
+ case "textarea":
2351
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TextArea, { ...extra, name: fieldName, label: "", value: value ?? "", onChange: fire, ...validationProps, onInput: emitInput });
2352
+ case "number":
2353
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.NumberInput, { ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
2354
+ case "currency":
2355
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.CurrencyInput, { currencyCode: "USD", ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
2356
+ case "stepper":
2357
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.StepperInput, { ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
2358
+ case "select":
2359
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Select, { ...extra, name: fieldName, label: "", value, onChange: fire, options: resolveEditOptions(col, data) });
2360
+ case "multiselect":
2361
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.MultiSelect, { ...extra, name: fieldName, label: "", value: value || [], onChange: fire, options: resolveEditOptions(col, data) });
2362
+ case "date":
2363
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.DateInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
2364
+ case "time":
2365
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TimeInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
2366
+ case "datetime":
2367
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.DateInput, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: value == null ? void 0 : value.date, onChange: (val) => {
2368
+ fire({ ...value, date: val });
2369
+ } }), /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: value == null ? void 0 : value.time, onChange: (val) => {
2370
+ fire({ ...value, time: val });
2371
+ } }));
2372
+ case "toggle":
2373
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Toggle, { ...extra, name: fieldName, label: "", checked: !!value, onChange: fire });
2374
+ case "checkbox":
2375
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Checkbox, { ...extra, name: fieldName, checked: !!value, onChange: fire });
2376
+ default:
2377
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Input, { ...extra, name: fieldName, label: "", value: value ?? "", onChange: fire, ...validationProps, onInput: emitInput });
2378
+ }
2379
+ };
2380
+ const renderCellContent = (row, col) => {
2381
+ const rowId = row[rowIdField];
2382
+ if (resolvedEditMode === "inline" && col.editable) {
2383
+ return renderInlineControl(col, row);
2384
+ }
2385
+ if (editingRowId != null && rowId === editingRowId && col.editable) {
2386
+ return renderInlineControl(col, row);
2387
+ }
2388
+ const isEditing = (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
2389
+ if (isEditing && col.editable) return renderEditControl(col, row);
2390
+ if (resolvedEditMode === "discrete" && col.editable && (col.editType === "select" || col.editType === "multiselect")) {
2391
+ const extra = col.editProps || {};
2392
+ const fieldName = `edit-${rowId}-${col.field}`;
2393
+ const value = row[col.field];
2394
+ const commit = (val) => commitEdit(row, col.field, val);
2395
+ const options = resolveEditOptions(col, data);
2396
+ const placeholder = extra.placeholder ?? "Select";
2397
+ return col.editType === "select" ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Select, { variant: "transparent", placeholder, ...extra, name: fieldName, label: "", value, onChange: commit, options }) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.MultiSelect, { placeholder, ...extra, name: fieldName, label: "", value: value || [], onChange: commit, options });
2398
+ }
2399
+ const rawValue = row[col.field];
2400
+ const rawStr = String(rawValue ?? "");
2401
+ if (col.truncate && rawStr.length > 0) {
2402
+ if (col.truncate === true) {
2403
+ if (col.renderCell) {
2404
+ const content2 = col.renderCell(rawValue, row);
2405
+ if (col.editable) {
2406
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
2407
+ }
2408
+ return content2;
2409
+ }
2410
+ if (col.editable) {
2411
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, rawStr || "--"));
2412
+ }
2413
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { truncate: { tooltipText: rawStr } }, rawStr);
2414
+ }
2415
+ const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
2416
+ if (rawStr.length > maxLen) {
2417
+ const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
2418
+ const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
2419
+ if (col.editable) {
2420
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
2421
+ }
2422
+ return col.renderCell ? content2 : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { truncate: { tooltipText: rawStr } }, content2 || "--");
2423
+ }
2424
+ }
2425
+ const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
2426
+ const isEmpty = content == null || content === "";
2427
+ if (col.editable) {
2428
+ return /* @__PURE__ */ import_react10.default.createElement(
2429
+ import_ui_extensions10.Link,
2430
+ {
2431
+ variant: "dark",
2432
+ onClick: () => startEditing(rowId, col.field, rawValue)
2433
+ },
2434
+ isEmpty ? "--" : content
2435
+ );
2436
+ }
2437
+ return isEmpty ? "--" : content;
2438
+ };
2439
+ const toolbarCount = countInToolbar ? /* @__PURE__ */ import_react10.default.createElement(CollectionCount, { text: recordCountLabel, bold: rowCountBold }) : null;
2440
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { format: { fontWeight: "demibold" } }, title), countInTitleRow && /* @__PURE__ */ import_react10.default.createElement(CollectionCount, { text: recordCountLabel, bold: rowCountBold })), hasToolbarContent && /* @__PURE__ */ import_react10.default.createElement(
2441
+ CollectionToolbar,
2442
+ {
2443
+ search: {
2444
+ visible: showSearch && searchFields.length > 0,
2445
+ name: "datatable-search",
2446
+ placeholder: searchPlaceholder,
2447
+ value: internalSearchTerm,
2448
+ onChange: handleSearchChange
2449
+ },
2450
+ filters: {
2451
+ items: filters,
2452
+ values: filterValues,
2453
+ inlineLimit: filterInlineLimit,
2454
+ namePrefix: "datatable-filter",
2455
+ onChange: handleFilterChange,
2456
+ filtersButtonLabel: resolvedFiltersButtonLabel,
2457
+ labels: {
2458
+ all: "All",
2459
+ dateFrom: resolvedDateFromLabel,
2460
+ dateTo: resolvedDateToLabel
2461
+ }
2462
+ },
2463
+ chips: {
2464
+ items: activeChips,
2465
+ showBadges: showFilterBadges,
2466
+ showClearAll: resolvedShowClearFiltersButton,
2467
+ clearAllLabel: resolvedClearAllLabel,
2468
+ onRemove: handleFilterRemove
2469
+ },
2470
+ right: toolbarCount,
2471
+ leftFlex: toolbarLeftFlex,
2472
+ rightFlex: toolbarRightFlex
2473
+ }
2474
+ ), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
2475
+ selectedIds,
2476
+ selectedCount: selectedIds.size,
2477
+ displayCount,
2478
+ countLabel,
2479
+ allSelected: selectedIds.size >= (serverSide ? totalCount || data.length : allRowIds.length),
2480
+ onSelectAll: handleSelectAllRows,
2481
+ onDeselectAll: handleDeselectAll,
2482
+ selectionActions
2483
+ }) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Box, { flex: 3 }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof resolvedSelectedLabel === "function" ? resolvedSelectedLabel(selectedIds.size, countLabel(selectedIds.size)) : resolvedSelectedLabel), selectedIds.size < (serverSide ? totalCount || data.length : allRowIds.length) && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, typeof resolvedSelectAllLabel === "function" ? resolvedSelectAllLabel(displayCount, countLabel(displayCount)) : resolvedSelectAllLabel), /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, resolvedDeselectAllLabel), selectionActions.map((action, i) => /* @__PURE__ */ import_react10.default.createElement(
2484
+ import_ui_extensions10.Button,
2485
+ {
2486
+ key: i,
2487
+ variant: action.variant || "transparent",
2488
+ size: "extra-small",
2489
+ onClick: () => action.onClick([...selectedIds])
2490
+ },
2491
+ action.icon && /* @__PURE__ */ import_react10.default.createElement(Icon, { name: action.icon, size: "sm" }),
2492
+ " ",
2493
+ action.label
2494
+ )))), showRowCount && displayCount > 0 && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react10.default.createElement(CollectionCount, { text: recordCountLabel, bold: rowCountBold }))))), loading ? renderLoadingState ? renderLoadingState({ label: resolvedLoadingLabel }) : (
2495
+ // Same EmptyState layout as the empty state, just the "building" image
2496
+ // + a loading message — so loading and empty match with no layout shift.
2497
+ /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tile, null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.EmptyState, { title: resolvedLoadingLabel, imageName: "building", layout: "vertical" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, null, resolvedLoadingMessage))))
2498
+ ) : error ? renderErrorState ? renderErrorState({
2499
+ error,
2500
+ title: typeof error === "string" ? error : resolvedErrorTitle,
2501
+ message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
2502
+ }) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.ErrorState, { title: typeof error === "string" ? error : resolvedErrorTitle }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, null, typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage)) : displayRows.length === 0 ? renderEmptyState ? renderEmptyState({ title: resolvedEmptyTitle, message: resolvedEmptyMessage }) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tile, null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, null, resolvedEmptyMessage)))) : /* @__PURE__ */ import_react10.default.createElement(
2503
+ import_ui_extensions10.Table,
2504
+ {
2505
+ bordered,
2506
+ flush,
2507
+ paginated: pageCount > 1,
2508
+ page: activePage,
2509
+ pageCount,
2510
+ onPageChange: handlePageChange,
2511
+ showFirstLastButtons: showFirstLastButtons != null ? showFirstLastButtons : pageCount > 5,
2512
+ showButtonLabels,
2513
+ ...maxVisiblePageButtons != null ? { maxVisiblePageButtons } : {}
2514
+ },
2515
+ /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableHead, null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableRow, null, selectable && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableHeader, { width: "min" }, /* @__PURE__ */ import_react10.default.createElement(
2516
+ import_ui_extensions10.Checkbox,
2517
+ {
2518
+ name: "datatable-select-all",
2519
+ "aria-label": "Select all rows",
2520
+ checked: allVisibleSelected,
2521
+ onChange: handleSelectAll
2522
+ }
2523
+ )), showExpandColumn && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableHeader, { width: "min" }), columns.map((col) => {
2524
+ const headerAlign = resolvedEditMode === "inline" && col.editable ? void 0 : col.align;
2525
+ return /* @__PURE__ */ import_react10.default.createElement(
2526
+ import_ui_extensions10.TableHeader,
2527
+ {
2528
+ key: col.field,
2529
+ width: getHeaderWidth(col),
2530
+ align: headerAlign,
2531
+ sortDirection: col.sortable ? sortState[col.field] || "none" : "never",
2532
+ onSortChange: col.sortable ? () => handleSortChange(col.field) : void 0
2533
+ },
2534
+ col.description ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, col.label, "\xA0", /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tooltip, null, col.description) }, /* @__PURE__ */ import_react10.default.createElement(Icon, { name: "info", screenReaderText: typeof col.description === "string" ? col.description : void 0 }))) : col.label
2535
+ );
2536
+ }), showRowActionsColumn && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableHeader, { width: "min" }))),
2537
+ /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableBody, null, renderedRows.map(
2538
+ (item, idx) => item.type === "group-header" ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { width: "min" }), showExpandColumn && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { width: "min" }), columns.map((col, colIdx) => {
2539
+ var _a, _b, _c;
2540
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { key: col.field, width: getCellWidth(col), align: colIdx === 0 ? void 0 : col.align }, colIdx === 0 ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react10.default.createElement(
2541
+ Icon,
2542
+ {
2543
+ name: expandedGroups.has(item.group.key) ? "Down" : "Right",
2544
+ onClick: () => toggleGroup(item.group.key),
2545
+ screenReaderText: expandedGroups.has(item.group.key) ? "Collapse group" : "Expand group"
2546
+ }
2547
+ ), /* @__PURE__ */ import_react10.default.createElement(
2548
+ import_ui_extensions10.Link,
2549
+ {
2550
+ variant: "dark",
2551
+ onClick: () => toggleGroup(item.group.key)
2552
+ },
2553
+ /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { format: { fontWeight: "demibold" } }, item.group.label)
2554
+ )) : ((_a = groupBy.aggregations) == null ? void 0 : _a[col.field]) ? groupBy.aggregations[col.field](item.group.rows, item.group.key) : ((_c = (_b = groupBy.groupValues) == null ? void 0 : _b[item.group.key]) == null ? void 0 : _c[col.field]) ?? "");
2555
+ }), showRowActionsColumn && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { width: "min" })) : item.type === "detail" ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableRow, { key: `detail-${item.row[rowIdField] ?? idx}` }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { colSpan: totalColumnCount }, renderExpandedRow(item.row))) : useColumnRendering ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { width: "min" }, /* @__PURE__ */ import_react10.default.createElement(
2556
+ import_ui_extensions10.Checkbox,
2557
+ {
2558
+ name: `select-${item.row[rowIdField]}`,
2559
+ "aria-label": "Select row",
2560
+ checked: selectedIds.has(item.row[rowIdField]),
2561
+ onChange: (checked) => handleSelectRow(item.row[rowIdField], checked)
2562
+ }
2563
+ )), showExpandColumn && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { width: "min" }, /* @__PURE__ */ import_react10.default.createElement(
2564
+ Icon,
2565
+ {
2566
+ name: expandedRowIds.has(item.row[rowIdField]) ? "upCarat" : "downCarat",
2567
+ onClick: () => toggleRowExpanded(item.row[rowIdField]),
2568
+ screenReaderText: expandedRowIds.has(item.row[rowIdField]) ? "Collapse row" : "Expand row"
2569
+ }
2570
+ )), columns.map((col) => {
2571
+ const rowId = item.row[rowIdField];
2572
+ const isDiscreteEditing = resolvedEditMode === "discrete" && (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
2573
+ const isRowEditing = editingRowId != null && rowId === editingRowId && col.editable;
2574
+ const isShowingInput = isDiscreteEditing || isRowEditing || resolvedEditMode === "inline" && col.editable;
2575
+ const cellAlign = isShowingInput ? void 0 : col.align;
2576
+ const cellContent = renderCellContent(item.row, col);
2577
+ const wrapInRowToggle = expandable && expandOn === "row" && !col.editable && !isShowingInput;
2578
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, wrapInRowToggle ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { variant: "dark", onClick: () => toggleRowExpanded(rowId) }, cellContent) : cellContent);
2579
+ }), showRowActionsColumn && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableCell, { width: "min" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, (() => {
2580
+ const resolvedRowActions = typeof rowActions === "function" ? rowActions(item.row) : rowActions;
2581
+ const actions = Array.isArray(resolvedRowActions) ? resolvedRowActions : [];
2582
+ return actions.map((action, i) => /* @__PURE__ */ import_react10.default.createElement(
2583
+ import_ui_extensions10.Button,
2584
+ {
2585
+ key: i,
2586
+ variant: action.variant || "transparent",
2587
+ size: "extra-small",
2588
+ onClick: () => action.onClick(item.row)
2589
+ },
2590
+ action.icon && /* @__PURE__ */ import_react10.default.createElement(Icon, { name: action.icon, size: "sm" }),
2591
+ action.label && ` ${action.label}`
2592
+ ));
2593
+ })()))) : renderRow(item.row)
2594
+ )),
2595
+ (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableRow, null, selectable && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableHeader, { width: "min" }), showExpandColumn && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableHeader, { width: "min" }), columns.map((col) => {
2596
+ const footerDef = col.footer;
2597
+ const content = typeof footerDef === "function" ? footerDef(footerData) : footerDef || "";
2598
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableHeader, { key: col.field, align: col.align }, content);
2599
+ }), showRowActionsColumn && /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.TableHeader, { width: "min" })))
2600
+ ));
2601
+ };
2602
+ DataTable.displayName = "DataTable";
1362
2603
 
1363
2604
  // src/kanban/Kanban.jsx
1364
2605
  var import_react13 = __toESM(require("react"));
1365
2606
 
2607
+ // src/kanban/kanbanLanes.js
2608
+ var UNASSIGNED_LANE_KEY = "__unassigned";
2609
+ var UNKNOWN_STAGE_KEY = "__unknown";
2610
+ var getLaneKey = (row, swimlaneBy) => {
2611
+ const raw = typeof swimlaneBy === "function" ? swimlaneBy(row) : row == null ? void 0 : row[swimlaneBy];
2612
+ if (raw == null || raw === "") return UNASSIGNED_LANE_KEY;
2613
+ return String(raw);
2614
+ };
2615
+ var orderLaneKeys = (seenKeys, swimlaneOrder) => {
2616
+ const seen = Array.isArray(seenKeys) ? seenKeys : [];
2617
+ if (!Array.isArray(swimlaneOrder) || swimlaneOrder.length === 0) return [...seen];
2618
+ const explicit = [];
2619
+ for (const key of swimlaneOrder) {
2620
+ const normalized = String(key);
2621
+ if (!explicit.includes(normalized)) explicit.push(normalized);
2622
+ }
2623
+ const rest = seen.filter((key) => !explicit.includes(key));
2624
+ return [...explicit, ...rest];
2625
+ };
2626
+ var partitionLanes = (rows, { swimlaneBy, swimlaneOrder } = {}) => {
2627
+ const rowsByLane = {};
2628
+ const firstSeen = [];
2629
+ for (const row of rows || []) {
2630
+ const key = getLaneKey(row, swimlaneBy);
2631
+ if (!rowsByLane[key]) {
2632
+ rowsByLane[key] = [];
2633
+ firstSeen.push(key);
2634
+ }
2635
+ rowsByLane[key].push(row);
2636
+ }
2637
+ const laneKeys = orderLaneKeys(firstSeen, swimlaneOrder);
2638
+ for (const key of laneKeys) {
2639
+ if (!rowsByLane[key]) rowsByLane[key] = [];
2640
+ }
2641
+ return { laneKeys, rowsByLane };
2642
+ };
2643
+ var resolveLaneLabel = (laneKey, swimlaneLabels, rows, unassignedLabel) => {
2644
+ if (typeof swimlaneLabels === "function") {
2645
+ const out = swimlaneLabels(laneKey, rows || []);
2646
+ if (out != null) return out;
2647
+ } else if (swimlaneLabels && typeof swimlaneLabels === "object") {
2648
+ const out = swimlaneLabels[laneKey];
2649
+ if (out != null) return out;
2650
+ }
2651
+ if (laneKey === UNASSIGNED_LANE_KEY) return unassignedLabel || "Unassigned";
2652
+ return String(laneKey);
2653
+ };
2654
+ var bucketRowsByStage = (rows, stages, getStage) => {
2655
+ const map = {};
2656
+ for (const stage of stages || []) map[stage.value] = [];
2657
+ for (const row of rows || []) {
2658
+ const key = getStage(row);
2659
+ if (map[key]) {
2660
+ map[key].push(row);
2661
+ } else if ((stages || []).length > 0) {
2662
+ if (!map[UNKNOWN_STAGE_KEY]) map[UNKNOWN_STAGE_KEY] = [];
2663
+ map[UNKNOWN_STAGE_KEY].push(row);
2664
+ }
2665
+ }
2666
+ return map;
2667
+ };
2668
+ var sortBuckets = (buckets, comparator) => {
2669
+ if (!comparator) return buckets;
2670
+ const out = {};
2671
+ for (const key of Object.keys(buckets || {})) {
2672
+ out[key] = [...buckets[key]].sort(comparator);
2673
+ }
2674
+ return out;
2675
+ };
2676
+ var resolveWipLimit = (stage, wipLimits) => {
2677
+ const override = wipLimits ? wipLimits[stage == null ? void 0 : stage.value] : void 0;
2678
+ const limit = override != null ? override : stage == null ? void 0 : stage.wipLimit;
2679
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit < 0) return null;
2680
+ return limit;
2681
+ };
2682
+ var computeStageCounts = (stages, buckets, stageMeta) => {
2683
+ const counts = {};
2684
+ for (const stage of stages || []) {
2685
+ const meta = stageMeta ? stageMeta[stage.value] : void 0;
2686
+ counts[stage.value] = meta && meta.totalCount != null ? meta.totalCount : ((buckets == null ? void 0 : buckets[stage.value]) || []).length;
2687
+ }
2688
+ return counts;
2689
+ };
2690
+ var evaluateWip = (stages, counts, wipLimits) => {
2691
+ const out = {};
2692
+ for (const stage of stages || []) {
2693
+ const limit = resolveWipLimit(stage, wipLimits);
2694
+ const count = (counts == null ? void 0 : counts[stage.value]) || 0;
2695
+ out[stage.value] = { count, limit, exceeded: limit != null && count > limit };
2696
+ }
2697
+ return out;
2698
+ };
2699
+ var findNewlyExceededWip = (prev, next) => {
2700
+ const events = [];
2701
+ for (const stageId of Object.keys(next || {})) {
2702
+ const entry = next[stageId];
2703
+ if (!entry || !entry.exceeded) continue;
2704
+ if (prev && prev[stageId] && prev[stageId].exceeded) continue;
2705
+ events.push({ stageId, count: entry.count, limit: entry.limit });
2706
+ }
2707
+ return events;
2708
+ };
2709
+
1366
2710
  // src/common-components/CollectionSortSelect.js
1367
2711
  var import_react11 = __toESM(require("react"));
1368
2712
  var import_ui_extensions11 = require("@hubspot/ui-extensions");
@@ -1634,7 +2978,7 @@ var StyledText = ({
1634
2978
  const nativeVariant = NATIVE_TAG_VARIANT_ALIASES[background == null ? void 0 : background.variant] ?? (background == null ? void 0 : background.variant) ?? "default";
1635
2979
  return import_react12.default.createElement(import_ui_extensions12.Tag, { variant: nativeVariant }, resolvedText);
1636
2980
  }
1637
- const { src, width: w, height: h6 } = makeStyledTextDataUri(resolvedText, {
2981
+ const { src, width: w, height: h7 } = makeStyledTextDataUri(resolvedText, {
1638
2982
  variant,
1639
2983
  format,
1640
2984
  orientation,
@@ -1650,78 +2994,1050 @@ var StyledText = ({
1650
2994
  return import_react12.default.createElement(import_ui_extensions12.Image, {
1651
2995
  src,
1652
2996
  width: w,
1653
- height: h6,
2997
+ height: h7,
1654
2998
  alt: alt ?? String(resolvedText)
1655
2999
  });
1656
3000
  };
1657
3001
 
1658
3002
  // src/kanban/Kanban.jsx
1659
3003
  var import_ui_extensions13 = require("@hubspot/ui-extensions");
1660
-
1661
- // src/utils/objectPath.js
1662
- var getByPath = (obj, path) => {
1663
- if (!path) return void 0;
1664
- if (typeof path === "function") return path(obj);
1665
- return String(path).split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
3004
+ var DEFAULT_DENSITY = "compact";
3005
+ var DEFAULT_MAX_CARDS = 10;
3006
+ var DEFAULT_MAX_EXPANDED = 50;
3007
+ var DEFAULT_COLUMN_WIDTH = 350;
3008
+ var MIN_COLUMN_WIDTH = 350;
3009
+ var DEFAULT_FILTER_INLINE_LIMIT = 4;
3010
+ var DEFAULT_SEARCH_DEBOUNCE = 250;
3011
+ var DEFAULT_TITLE_TRUNCATE = 60;
3012
+ var applyTruncate = (value, truncate, fallback) => {
3013
+ if (truncate === false) return value;
3014
+ if (typeof value !== "string") return value;
3015
+ const limit = typeof truncate === "number" ? truncate : truncate === true ? fallback || DEFAULT_TITLE_TRUNCATE : fallback || DEFAULT_TITLE_TRUNCATE;
3016
+ if (value.length <= limit) return value;
3017
+ return value.slice(0, limit).trimEnd() + "\u2026";
1666
3018
  };
1667
-
1668
- // src/utils/crmSearchAdapters.js
1669
- var EMPTY_ARRAY = [];
1670
- var EMPTY_OBJECT = {};
1671
- var isPlainObject = (value) => value != null && Object.prototype.toString.call(value) === "[object Object]";
1672
- var coerceError = (error) => {
1673
- if (!error) return false;
1674
- if (typeof error === "string") return error;
1675
- if (error.message) return error.message;
1676
- return true;
3019
+ var DEFAULT_LABELS3 = {
3020
+ search: "Search cards...",
3021
+ // Only the total is surfaced — callers asked for a single headline number
3022
+ // rather than a "loaded / total" fraction. Fall back to the bare label when
3023
+ // no total is known.
3024
+ showMore: (_shown, total) => total ? `Show more (${total})` : "Show more",
3025
+ showLess: "Show less",
3026
+ loadMore: (_loaded, total) => total ? `Load more (${total})` : "Load more",
3027
+ loadingMore: "Loading...",
3028
+ retryLoadMore: "Retry",
3029
+ emptyColumn: "\u2014",
3030
+ emptyTitle: "No cards",
3031
+ emptyMessage: "Nothing matches the current filters.",
3032
+ loading: "Loading board...",
3033
+ loadingMessage: "This should only take a moment.",
3034
+ errorTitle: "Something went wrong.",
3035
+ errorMessage: "An error occurred while loading data.",
3036
+ cardCount: (n) => String(n),
3037
+ // "5 / 4" — count vs WIP limit in the stage header. The slash format is the
3038
+ // standard kanban convention; override for tighter ("5/4") or verbose forms.
3039
+ wipCount: (count, limit) => `${count} / ${limit}`,
3040
+ overWip: "Over WIP",
3041
+ laneCount: (n) => String(n),
3042
+ unassignedLane: "Unassigned",
3043
+ moveTo: "Move",
3044
+ clearAll: "Clear all",
3045
+ selectAll: (count, label) => `Select all ${count} ${label}`,
3046
+ deselectAll: "Deselect all",
3047
+ selected: (count, label) => `${count}\xA0${label}\xA0selected`,
3048
+ filtersButton: "Filters",
3049
+ dateFrom: "From",
3050
+ dateTo: "To",
3051
+ sortButton: "Sort",
3052
+ sortAscending: "Ascending",
3053
+ sortDescending: "Descending",
3054
+ metricsButton: "Metrics"
1677
3055
  };
1678
- var pickArray = (response) => {
1679
- if (Array.isArray(response)) return response;
1680
- if (!response) return EMPTY_ARRAY;
1681
- return response.results || response.data || response.items || response.records || response.objects || EMPTY_ARRAY;
3056
+ var makeRotatedTagDataUri = (label) => makeStyledTextDataUri(label, {
3057
+ variant: "microcopy",
3058
+ format: { fontWeight: "demibold" },
3059
+ orientation: "vertical-down",
3060
+ background: { preset: "tag" }
3061
+ });
3062
+ var makeRotatedLabelDataUri = (label) => makeStyledTextDataUri(label, {
3063
+ variant: "bodytext",
3064
+ format: { fontWeight: "demibold" },
3065
+ orientation: "vertical-down"
3066
+ });
3067
+ var canStageReceiveRow = (stage, row, canMove) => {
3068
+ if (!stage) return false;
3069
+ if (typeof canMove === "function" && !canMove(row, stage.value)) return false;
3070
+ if (typeof stage.canEnter === "function" && !stage.canEnter(row)) return false;
3071
+ return true;
1682
3072
  };
1683
- var pickTotal = (response, fallbackLength) => {
1684
- var _a;
1685
- if (!response || Array.isArray(response)) return fallbackLength;
1686
- return response.total ?? response.totalCount ?? response.totalResults ?? ((_a = response.paging) == null ? void 0 : _a.total) ?? fallbackLength;
3073
+ var resolveDividers = (cardDividers, density) => {
3074
+ if (cardDividers === true) {
3075
+ return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
3076
+ }
3077
+ if (cardDividers === false) {
3078
+ return { afterTitle: false, afterSubtitle: false, afterBody: false, afterFooter: false };
3079
+ }
3080
+ if (cardDividers && typeof cardDividers === "object") {
3081
+ return {
3082
+ afterTitle: cardDividers.afterTitle ?? false,
3083
+ afterSubtitle: cardDividers.afterSubtitle ?? false,
3084
+ afterBody: cardDividers.afterBody ?? false,
3085
+ afterFooter: cardDividers.afterFooter ?? false
3086
+ };
3087
+ }
3088
+ if (density === "comfortable") {
3089
+ return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
3090
+ }
3091
+ return { afterTitle: false, afterSubtitle: false, afterBody: true, afterFooter: false };
1687
3092
  };
1688
- var normalizeCrmSearchRecord = (record, options = EMPTY_OBJECT) => {
1689
- const {
1690
- idField = "id",
1691
- objectIdField = "objectId",
1692
- propertiesKey = "properties",
1693
- flattenProperties = true,
1694
- propertyValueKey,
1695
- mapRecord
1696
- } = options;
1697
- if (mapRecord) return mapRecord(record);
1698
- const objectId = (record == null ? void 0 : record.objectId) ?? (record == null ? void 0 : record.id) ?? (record == null ? void 0 : record.hs_object_id) ?? getByPath(record, `${propertiesKey}.hs_object_id`);
1699
- const properties = (record == null ? void 0 : record[propertiesKey]) || EMPTY_OBJECT;
1700
- const flattened = {};
1701
- if (flattenProperties && isPlainObject(properties)) {
1702
- for (const [key, value] of Object.entries(properties)) {
1703
- flattened[key] = propertyValueKey && isPlainObject(value) ? value[propertyValueKey] : value;
1704
- }
3093
+ var partitionFields = (cardFields) => {
3094
+ const buckets = { title: null, subtitle: null, meta: [], body: [], footer: [] };
3095
+ for (const field of cardFields || []) {
3096
+ const placement = field.placement || "body";
3097
+ if (placement === "title" && !buckets.title) buckets.title = field;
3098
+ else if (placement === "subtitle" && !buckets.subtitle) buckets.subtitle = field;
3099
+ else if (placement === "meta") buckets.meta.push(field);
3100
+ else if (placement === "footer") buckets.footer.push(field);
3101
+ else buckets.body.push(field);
1705
3102
  }
1706
- return {
1707
- ...flattenProperties ? flattened : EMPTY_OBJECT,
1708
- ...record,
1709
- [idField]: objectId,
1710
- [objectIdField]: objectId,
1711
- [propertiesKey]: properties
1712
- };
3103
+ return buckets;
1713
3104
  };
1714
- var normalizeCrmSearchRows = (response, options = EMPTY_OBJECT) => {
1715
- const records = pickArray(response);
1716
- return records.map((record) => normalizeCrmSearchRecord(record, options));
3105
+ var resolveFieldValue = (field, row) => {
3106
+ if (!field) return void 0;
3107
+ if (field.field && row && Object.prototype.hasOwnProperty.call(row, field.field)) {
3108
+ return row[field.field];
3109
+ }
3110
+ return void 0;
1717
3111
  };
1718
- var STABLE_SORT_TIEBREAKER = { propertyName: "hs_object_id", direction: "ASCENDING" };
1719
- var withStableSort = (sorts) => {
1720
- const base = Array.isArray(sorts) ? sorts : [];
1721
- if (base.some((s) => s && s.propertyName === STABLE_SORT_TIEBREAKER.propertyName)) return base;
1722
- return [...base, STABLE_SORT_TIEBREAKER];
3112
+ var resolveHref = (href, row) => {
3113
+ if (!href) return null;
3114
+ if (typeof href === "function") return href(row);
3115
+ return href;
1723
3116
  };
1724
- var buildCrmSearchConfig = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
3117
+ var KanbanCard = ({
3118
+ row,
3119
+ rowId,
3120
+ stage,
3121
+ stages,
3122
+ fields,
3123
+ density,
3124
+ dividers,
3125
+ bodyAs,
3126
+ maxBodyLines,
3127
+ stageControl,
3128
+ stageControlPlacement,
3129
+ canMove,
3130
+ onStageChangeRequest,
3131
+ isChanging,
3132
+ selectable,
3133
+ selected,
3134
+ onToggleSelect,
3135
+ labels
3136
+ }) => {
3137
+ const titleHref = fields.title ? resolveHref(fields.title.href, row) : null;
3138
+ const rawTitleValue = fields.title ? fields.title.render ? fields.title.render(resolveFieldValue(fields.title, row), row) : resolveFieldValue(fields.title, row) : null;
3139
+ const titleValue = fields.title && typeof rawTitleValue === "string" ? applyTruncate(rawTitleValue, fields.title.truncate, DEFAULT_TITLE_TRUNCATE) : rawTitleValue;
3140
+ const titleNode = titleHref ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Link, { href: titleHref }, titleValue) : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { format: { fontWeight: "demibold" } }, titleValue);
3141
+ const metaNodes = fields.meta.filter((f) => !f.visible || f.visible(row)).map((f) => {
3142
+ const val = resolveFieldValue(f, row);
3143
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { key: f.field || f.label, variant: "microcopy" }, f.render ? f.render(val, row) : val);
3144
+ });
3145
+ const showSubtitle = density === "comfortable" && fields.subtitle;
3146
+ const subtitleNode = showSubtitle ? fields.subtitle.render ? fields.subtitle.render(resolveFieldValue(fields.subtitle, row), row) : resolveFieldValue(fields.subtitle, row) : null;
3147
+ const bodyFields = fields.body.filter((f) => !f.visible || f.visible(row)).slice(0, maxBodyLines);
3148
+ const footerFields = fields.footer.filter((f) => !f.visible || f.visible(row));
3149
+ const footerAlerts = footerFields.slice(0, -1);
3150
+ const footerActionsField = footerFields.length > 0 ? footerFields[footerFields.length - 1] : null;
3151
+ const renderFooterField = (f, idx) => {
3152
+ const val = resolveFieldValue(f, row);
3153
+ const rendered = f.render ? f.render(val, row) : val;
3154
+ const key = f.key || f.field || f.label || `footer-${idx}`;
3155
+ return /* @__PURE__ */ import_react13.default.createElement(import_react13.default.Fragment, { key }, rendered);
3156
+ };
3157
+ const stageControlNode = stageControl === "none" ? null : /* @__PURE__ */ import_react13.default.createElement(
3158
+ StageControl,
3159
+ {
3160
+ row,
3161
+ rowId,
3162
+ currentStage: stage,
3163
+ stages,
3164
+ canMove,
3165
+ isChanging,
3166
+ mode: stageControl,
3167
+ onStageChangeRequest,
3168
+ labels
3169
+ }
3170
+ );
3171
+ const titleRow = /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Box, { flex: 1 }, titleNode), selectable ? /* @__PURE__ */ import_react13.default.createElement(
3172
+ import_ui_extensions13.Checkbox,
3173
+ {
3174
+ name: `kanban-select-${rowId}`,
3175
+ checked: selected,
3176
+ onChange: () => onToggleSelect(rowId)
3177
+ }
3178
+ ) : null);
3179
+ const metaRow = metaNodes.length === 0 ? null : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", justify: "end", align: "center", gap: "xs" }, metaNodes);
3180
+ const bodyRow = bodyFields.length === 0 ? null : bodyAs === "descriptionList" ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.DescriptionList, { direction: "row" }, bodyFields.map((f, idx) => {
3181
+ const val = resolveFieldValue(f, row);
3182
+ const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
3183
+ const key = f.key || f.field || f.label || `body-${idx}`;
3184
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.DescriptionListItem, { key, label: f.label || "" }, rendered);
3185
+ })) : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", gap: "flush" }, bodyFields.map((f, idx) => {
3186
+ const val = resolveFieldValue(f, row);
3187
+ const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
3188
+ const key = f.key || f.field || f.label || `body-${idx}`;
3189
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { key, variant: "microcopy" }, f.label ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { inline: true, variant: "microcopy" }, `${f.label}: `) : null, rendered);
3190
+ }));
3191
+ const footerAlertsNode = footerAlerts.length === 0 ? null : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", gap: "xs" }, footerAlerts.map((f, idx) => renderFooterField(f, idx)));
3192
+ const footerActionsNode = footerActionsField ? renderFooterField(footerActionsField, footerFields.length - 1) : null;
3193
+ const inlineStageControl = stageControlPlacement === "inline" ? stageControlNode : null;
3194
+ const separateRowStageControl = stageControlPlacement === "separateRow" ? stageControlNode : null;
3195
+ const footerMainRow = inlineStageControl || footerActionsNode ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", justify: "between", align: "start", gap: "sm" }, inlineStageControl ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Box, { alignSelf: "center" }, inlineStageControl) : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Box, null), footerActionsNode ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Box, { flex: 1 }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", justify: "end", align: "start" }, footerActionsNode)) : null) : null;
3196
+ const footerRow = !footerAlertsNode && !footerMainRow ? null : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", gap: "xs" }, footerAlertsNode, footerMainRow);
3197
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tile, { compact: density === "compact" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", gap: density === "compact" ? "xs" : "sm" }, titleRow, dividers.afterTitle && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Divider, null) : null, subtitleNode ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { variant: "microcopy" }, subtitleNode) : null, dividers.afterSubtitle && subtitleNode && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Divider, null) : null, metaRow, bodyRow, dividers.afterBody && bodyRow && (footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Divider, null) : null, footerRow, dividers.afterFooter && footerRow && separateRowStageControl ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Divider, null) : null, separateRowStageControl));
3198
+ };
3199
+ var StageControl = ({
3200
+ row,
3201
+ rowId,
3202
+ currentStage,
3203
+ stages,
3204
+ canMove,
3205
+ isChanging,
3206
+ mode,
3207
+ onStageChangeRequest,
3208
+ labels
3209
+ }) => {
3210
+ if (isChanging) {
3211
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.LoadingSpinner, { size: "xs" });
3212
+ }
3213
+ const availableStages = (stages || []).filter(
3214
+ (stage) => stage.value === currentStage.value || canStageReceiveRow(stage, row, canMove)
3215
+ );
3216
+ if (mode === "menu") {
3217
+ const targetStages = availableStages.filter((stage) => stage.value !== currentStage.value);
3218
+ if (targetStages.length === 0) {
3219
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Button, { variant: "transparent", size: "extra-small", disabled: true }, labels.moveTo);
3220
+ }
3221
+ return /* @__PURE__ */ import_react13.default.createElement(
3222
+ import_ui_extensions13.Dropdown,
3223
+ {
3224
+ variant: "transparent",
3225
+ buttonText: labels.moveTo,
3226
+ buttonSize: "xs"
3227
+ },
3228
+ targetStages.map((stage) => /* @__PURE__ */ import_react13.default.createElement(
3229
+ import_ui_extensions13.Dropdown.ButtonItem,
3230
+ {
3231
+ key: stage.value,
3232
+ onClick: () => onStageChangeRequest(row, stage.value, currentStage.value)
3233
+ },
3234
+ stage.shortLabel || stage.label
3235
+ ))
3236
+ );
3237
+ }
3238
+ return /* @__PURE__ */ import_react13.default.createElement(
3239
+ import_ui_extensions13.Select,
3240
+ {
3241
+ name: `stage-${rowId}`,
3242
+ label: "",
3243
+ value: currentStage.value,
3244
+ onChange: (val) => {
3245
+ if (val !== currentStage.value) onStageChangeRequest(row, val, currentStage.value);
3246
+ },
3247
+ options: availableStages.map((stage) => ({
3248
+ label: stage.shortLabel || stage.label,
3249
+ value: stage.value
3250
+ }))
3251
+ }
3252
+ );
3253
+ };
3254
+ var KanbanColumn = ({
3255
+ stage,
3256
+ rows,
3257
+ bucketCount,
3258
+ totalCount,
3259
+ hasMore,
3260
+ loading,
3261
+ error,
3262
+ onLoadMore,
3263
+ expanded,
3264
+ onToggleExpanded,
3265
+ collapsed,
3266
+ onToggleCollapsed,
3267
+ columnFooter,
3268
+ countDisplay,
3269
+ wip,
3270
+ compactEmpty,
3271
+ labels,
3272
+ children
3273
+ }) => {
3274
+ const hasWipLimit = wip != null && wip.limit != null;
3275
+ const countLabel = hasWipLimit ? labels.wipCount(wip.count, wip.limit) : labels.cardCount(totalCount != null ? totalCount : bucketCount);
3276
+ const overWipNode = wip && wip.exceeded ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.StatusTag, { variant: "warning" }, labels.overWip) : null;
3277
+ const countNode = countDisplay === "text" ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tag, { variant: "default" }, countLabel);
3278
+ if (collapsed) {
3279
+ const rotated = makeRotatedLabelDataUri(stage.label);
3280
+ const rotatedCount = countDisplay === "none" ? null : makeRotatedTagDataUri(countLabel);
3281
+ const stageIdentifier = stage.icon ? /* @__PURE__ */ import_react13.default.createElement(Icon, { name: stage.icon, size: "sm", screenReaderText: stage.label }) : /* @__PURE__ */ import_react13.default.createElement(
3282
+ import_ui_extensions13.Image,
3283
+ {
3284
+ src: rotated.src,
3285
+ width: rotated.width,
3286
+ height: rotated.height,
3287
+ alt: stage.label
3288
+ }
3289
+ );
3290
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tile, { compact: true }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", gap: "xs", align: "center" }, /* @__PURE__ */ import_react13.default.createElement(
3291
+ import_ui_extensions13.Button,
3292
+ {
3293
+ variant: "transparent",
3294
+ size: "sm",
3295
+ onClick: onToggleCollapsed,
3296
+ tooltip: `Expand ${stage.label}`
3297
+ },
3298
+ /* @__PURE__ */ import_react13.default.createElement(Icon, { name: "right", size: "sm", screenReaderText: `Expand ${stage.label}` })
3299
+ ), stageIdentifier, rotatedCount ? /* @__PURE__ */ import_react13.default.createElement(
3300
+ import_ui_extensions13.Image,
3301
+ {
3302
+ src: rotatedCount.src,
3303
+ width: rotatedCount.width,
3304
+ height: rotatedCount.height,
3305
+ alt: `${bucketCount} items`
3306
+ }
3307
+ ) : null));
3308
+ }
3309
+ if (compactEmpty && !loading && rows.length === 0 && bucketCount === 0) {
3310
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tile, { compact: true }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { variant: "microcopy", format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn)));
3311
+ }
3312
+ const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
3313
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tile, { compact: true }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, overWipNode, loading ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react13.default.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Divider, null), children, error ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
3314
+ };
3315
+ var renderMetricsPanel = (metrics) => {
3316
+ if (!metrics) return null;
3317
+ if (!Array.isArray(metrics)) return metrics;
3318
+ if (metrics.length === 0) return null;
3319
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Statistics, null, metrics.map((m, i) => /* @__PURE__ */ import_react13.default.createElement(
3320
+ import_ui_extensions13.StatisticsItem,
3321
+ {
3322
+ key: m.id || m.label || i,
3323
+ label: m.label,
3324
+ number: m.number != null ? String(m.number) : ""
3325
+ },
3326
+ m.trend ? /* @__PURE__ */ import_react13.default.createElement(
3327
+ import_ui_extensions13.StatisticsTrend,
3328
+ {
3329
+ direction: m.trend.direction || "increase",
3330
+ value: m.trend.value,
3331
+ color: m.trend.color
3332
+ }
3333
+ ) : null
3334
+ )));
3335
+ };
3336
+ var KanbanToolbar = ({
3337
+ showSearch,
3338
+ searchValue,
3339
+ searchPlaceholder,
3340
+ onSearchChange,
3341
+ filters,
3342
+ filterValues,
3343
+ onFilterChange,
3344
+ filterInlineLimit,
3345
+ showFilterBadges,
3346
+ showClearFiltersButton,
3347
+ activeChips,
3348
+ onFilterRemove,
3349
+ sortOptions,
3350
+ sortValue,
3351
+ onSortChange,
3352
+ showMetricsButton,
3353
+ metricsPanel,
3354
+ onToggleMetrics,
3355
+ labels,
3356
+ toolbarLeftFlex,
3357
+ toolbarRightFlex
3358
+ }) => {
3359
+ const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || showMetricsButton ? /* @__PURE__ */ import_react13.default.createElement(import_react13.default.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react13.default.createElement(
3360
+ CollectionSortSelect,
3361
+ {
3362
+ name: "kanban-sort",
3363
+ value: sortValue,
3364
+ placeholder: labels.sortButton,
3365
+ options: sortOptions,
3366
+ onChange: onSortChange
3367
+ }
3368
+ ) : null, showMetricsButton ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react13.default.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
3369
+ return /* @__PURE__ */ import_react13.default.createElement(
3370
+ CollectionToolbar,
3371
+ {
3372
+ search: {
3373
+ visible: showSearch,
3374
+ name: "kanban-search",
3375
+ placeholder: searchPlaceholder,
3376
+ value: searchValue,
3377
+ onChange: onSearchChange
3378
+ },
3379
+ filters: {
3380
+ items: filters,
3381
+ values: filterValues,
3382
+ inlineLimit: filterInlineLimit,
3383
+ namePrefix: "kanban-filter",
3384
+ onChange: onFilterChange,
3385
+ labels
3386
+ },
3387
+ chips: {
3388
+ items: activeChips,
3389
+ showBadges: showFilterBadges,
3390
+ showClearAll: showClearFiltersButton,
3391
+ clearAllLabel: labels.clearAll,
3392
+ onRemove: onFilterRemove
3393
+ },
3394
+ right: rightControls,
3395
+ footer: metricsPanel,
3396
+ labels,
3397
+ leftFlex: toolbarLeftFlex,
3398
+ rightFlex: toolbarRightFlex
3399
+ }
3400
+ );
3401
+ };
3402
+ var DefaultSelectionBar = ({
3403
+ selectedIds,
3404
+ selectedCount,
3405
+ displayCount,
3406
+ countLabel,
3407
+ allSelected,
3408
+ onSelectAll,
3409
+ onDeselectAll,
3410
+ selectionActions,
3411
+ labels
3412
+ }) => {
3413
+ const pluralForCount = (n) => countLabel(n);
3414
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tile, { compact: true }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Inline, { align: "center", justify: "between", gap: "small" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof labels.selected === "function" ? labels.selected(selectedCount, pluralForCount(selectedCount)) : `${selectedCount} selected`), !allSelected ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Button, { variant: "transparent", size: "extra-small", onClick: onSelectAll }, typeof labels.selectAll === "function" ? labels.selectAll(displayCount, pluralForCount(displayCount)) : labels.selectAll) : null, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Button, { variant: "transparent", size: "extra-small", onClick: onDeselectAll }, labels.deselectAll)), (selectionActions || []).length > 0 ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Inline, { align: "center", gap: "extra-small" }, selectionActions.map((action, i) => /* @__PURE__ */ import_react13.default.createElement(
3415
+ import_ui_extensions13.Button,
3416
+ {
3417
+ key: action.key || action.label || i,
3418
+ variant: action.variant || "transparent",
3419
+ size: "extra-small",
3420
+ onClick: () => action.onClick([...selectedIds])
3421
+ },
3422
+ action.icon ? /* @__PURE__ */ import_react13.default.createElement(Icon, { name: action.icon, size: "sm" }) : null,
3423
+ " ",
3424
+ action.label
3425
+ ))) : null));
3426
+ };
3427
+ var Kanban = ({
3428
+ // --- Data ---
3429
+ data = [],
3430
+ stages = [],
3431
+ groupBy = "status",
3432
+ rowIdField = "id",
3433
+ // --- Card rendering ---
3434
+ renderCard,
3435
+ cardFields,
3436
+ cardDensity = DEFAULT_DENSITY,
3437
+ cardDividers,
3438
+ cardBodyAs = "descriptionList",
3439
+ maxBodyLines,
3440
+ maxCardsPerColumn = DEFAULT_MAX_CARDS,
3441
+ maxCardsExpanded = DEFAULT_MAX_EXPANDED,
3442
+ expandedStages,
3443
+ onExpandedStagesChange,
3444
+ // --- Per-stage pagination ---
3445
+ stageMeta,
3446
+ onLoadMore,
3447
+ // --- WIP limits ---
3448
+ wipLimits,
3449
+ onWipExceeded,
3450
+ // --- Swimlanes ---
3451
+ swimlaneBy,
3452
+ swimlaneLabels,
3453
+ swimlaneOrder,
3454
+ collapseLanes = true,
3455
+ collapsedLanes,
3456
+ defaultCollapsedLanes,
3457
+ onCollapsedLanesChange,
3458
+ metricsPerLane = false,
3459
+ // --- Selection ---
3460
+ selectable = false,
3461
+ selectedIds,
3462
+ onSelectionChange,
3463
+ selectionActions,
3464
+ recordLabel,
3465
+ selectionResetKey,
3466
+ resetSelectionOnQueryChange = true,
3467
+ showSelectionBar = true,
3468
+ renderSelectionBar,
3469
+ // --- Stage transitions ---
3470
+ stageControl,
3471
+ stageControlPlacement,
3472
+ onStageChange,
3473
+ isStageChanging,
3474
+ canMove,
3475
+ // --- Toolbar ---
3476
+ showSearch = true,
3477
+ searchFields,
3478
+ searchPlaceholder,
3479
+ searchDebounce = DEFAULT_SEARCH_DEBOUNCE,
3480
+ fuzzySearch = false,
3481
+ fuzzyOptions,
3482
+ filters,
3483
+ filterInlineLimit = DEFAULT_FILTER_INLINE_LIMIT,
3484
+ showFilterBadges = true,
3485
+ showClearFiltersButton,
3486
+ toolbarLeftFlex = 3,
3487
+ toolbarRightFlex = 1,
3488
+ sortOptions,
3489
+ defaultSort,
3490
+ sort,
3491
+ onSortChange,
3492
+ // --- Column level ---
3493
+ columnFooter,
3494
+ columnWidth = DEFAULT_COLUMN_WIDTH,
3495
+ countDisplay = "tag",
3496
+ collapsedStages,
3497
+ onCollapsedStagesChange,
3498
+ // --- Metrics panel ---
3499
+ metrics,
3500
+ // Array of stat items or a ReactNode for full override
3501
+ showMetrics: controlledShowMetrics,
3502
+ onMetricsToggle,
3503
+ // --- State (controlled) ---
3504
+ searchValue,
3505
+ onSearchChange,
3506
+ filterValues,
3507
+ onFilterChange,
3508
+ onParamsChange,
3509
+ loading = false,
3510
+ error,
3511
+ // --- Labels ---
3512
+ labels: labelsProp,
3513
+ renderEmptyState,
3514
+ renderLoadingState,
3515
+ renderErrorState
3516
+ }) => {
3517
+ var _a;
3518
+ const labels = (0, import_react13.useMemo)(() => ({ ...DEFAULT_LABELS3, ...labelsProp || {} }), [labelsProp]);
3519
+ const [internalSearch, setInternalSearch] = (0, import_react13.useState)(searchValue != null ? searchValue : "");
3520
+ const [internalFilters, setInternalFilters] = (0, import_react13.useState)(() => getEmptyFilterValues(filters));
3521
+ const [internalSort, setInternalSort] = (0, import_react13.useState)(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
3522
+ const [internalCollapsed, setInternalCollapsed] = (0, import_react13.useState)([]);
3523
+ const [internalCollapsedLanes, setInternalCollapsedLanes] = (0, import_react13.useState)(
3524
+ () => defaultCollapsedLanes || []
3525
+ );
3526
+ const [internalExpanded, setInternalExpanded] = (0, import_react13.useState)([]);
3527
+ const [internalSelection, setInternalSelection] = (0, import_react13.useState)([]);
3528
+ const [internalShowMetrics, setInternalShowMetrics] = (0, import_react13.useState)(false);
3529
+ const [transitionPrompts, setTransitionPrompts] = (0, import_react13.useState)({});
3530
+ const resolvedShowMetrics = controlledShowMetrics != null ? controlledShowMetrics : internalShowMetrics;
3531
+ const toggleMetrics = (0, import_react13.useCallback)(() => {
3532
+ const next = !resolvedShowMetrics;
3533
+ if (onMetricsToggle) onMetricsToggle(next);
3534
+ if (controlledShowMetrics == null) setInternalShowMetrics(next);
3535
+ }, [resolvedShowMetrics, onMetricsToggle, controlledShowMetrics]);
3536
+ const effectiveColumnWidth = Math.max(MIN_COLUMN_WIDTH, columnWidth || DEFAULT_COLUMN_WIDTH);
3537
+ const resolvedSearch = searchValue != null ? searchValue : internalSearch;
3538
+ const searchInputValue = searchDebounce > 0 ? internalSearch : resolvedSearch;
3539
+ const resolvedFilters = filterValues != null ? filterValues : internalFilters;
3540
+ const resolvedSort = sort != null ? sort : internalSort;
3541
+ const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
3542
+ const resolvedCollapsedLanes = collapsedLanes != null ? collapsedLanes : internalCollapsedLanes;
3543
+ const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
3544
+ const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
3545
+ const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
3546
+ const stagesByValue = (0, import_react13.useMemo)(() => {
3547
+ const map = {};
3548
+ for (const stage of stages || []) {
3549
+ map[stage.value] = stage;
3550
+ }
3551
+ return map;
3552
+ }, [stages]);
3553
+ const fireParamsChange = (0, import_react13.useCallback)((overrides = {}) => {
3554
+ if (!onParamsChange) return;
3555
+ onParamsChange({
3556
+ search: overrides.search != null ? overrides.search : resolvedSearch,
3557
+ filters: overrides.filters != null ? overrides.filters : resolvedFilters,
3558
+ sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
3559
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed,
3560
+ collapsedLanes: overrides.collapsedLanes != null ? overrides.collapsedLanes : resolvedCollapsedLanes
3561
+ });
3562
+ }, [onParamsChange, resolvedCollapsed, resolvedCollapsedLanes, resolvedFilters, resolvedSearch, resolvedSort]);
3563
+ const lastAppliedSearchRef = (0, import_react13.useRef)(searchValue != null ? searchValue : "");
3564
+ (0, import_react13.useEffect)(() => {
3565
+ if (searchValue == null) return;
3566
+ if (searchValue === lastAppliedSearchRef.current) return;
3567
+ lastAppliedSearchRef.current = searchValue;
3568
+ setInternalSearch(searchValue);
3569
+ }, [searchValue]);
3570
+ const dispatchSearch = (0, import_react13.useCallback)(
3571
+ (val) => {
3572
+ lastAppliedSearchRef.current = val;
3573
+ if (onSearchChange) onSearchChange(val);
3574
+ fireParamsChange({ search: val });
3575
+ },
3576
+ [fireParamsChange, onSearchChange]
3577
+ );
3578
+ const dispatchSearchDebounced = useDebouncedDispatch(internalSearch, searchDebounce, dispatchSearch);
3579
+ const handleSearch = (0, import_react13.useCallback)(
3580
+ (val) => {
3581
+ setInternalSearch(val);
3582
+ dispatchSearchDebounced(val);
3583
+ },
3584
+ [dispatchSearchDebounced]
3585
+ );
3586
+ const handleFilter = (0, import_react13.useCallback)(
3587
+ (name, val) => {
3588
+ const next = { ...resolvedFilters, [name]: val };
3589
+ if (filterValues == null) setInternalFilters(next);
3590
+ if (onFilterChange) onFilterChange(next);
3591
+ fireParamsChange({ filters: next });
3592
+ },
3593
+ [fireParamsChange, onFilterChange, filterValues, resolvedFilters]
3594
+ );
3595
+ const handleFilterRemove = (0, import_react13.useCallback)(
3596
+ (key) => {
3597
+ const next = resetFilterValues(filters, resolvedFilters, key);
3598
+ if (filterValues == null) setInternalFilters(next);
3599
+ if (onFilterChange) onFilterChange(next);
3600
+ fireParamsChange({ filters: next });
3601
+ },
3602
+ [filters, filterValues, fireParamsChange, onFilterChange, resolvedFilters]
3603
+ );
3604
+ const handleSort = (0, import_react13.useCallback)(
3605
+ (val) => {
3606
+ if (onSortChange) onSortChange(val);
3607
+ if (sort == null) setInternalSort(val);
3608
+ fireParamsChange({ sort: val });
3609
+ },
3610
+ [fireParamsChange, onSortChange, sort]
3611
+ );
3612
+ const handleCollapsed = (0, import_react13.useCallback)(
3613
+ (stageValue) => {
3614
+ const next = resolvedCollapsed.includes(stageValue) ? resolvedCollapsed.filter((v) => v !== stageValue) : [...resolvedCollapsed, stageValue];
3615
+ if (onCollapsedStagesChange) onCollapsedStagesChange(next);
3616
+ if (collapsedStages == null) setInternalCollapsed(next);
3617
+ fireParamsChange({ collapsedStages: next });
3618
+ },
3619
+ [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
3620
+ );
3621
+ const handleLaneCollapsed = (0, import_react13.useCallback)(
3622
+ (laneKey) => {
3623
+ const next = resolvedCollapsedLanes.includes(laneKey) ? resolvedCollapsedLanes.filter((k) => k !== laneKey) : [...resolvedCollapsedLanes, laneKey];
3624
+ if (onCollapsedLanesChange) onCollapsedLanesChange(next);
3625
+ if (collapsedLanes == null) setInternalCollapsedLanes(next);
3626
+ fireParamsChange({ collapsedLanes: next });
3627
+ },
3628
+ [fireParamsChange, resolvedCollapsedLanes, collapsedLanes, onCollapsedLanesChange]
3629
+ );
3630
+ const handleExpanded = (0, import_react13.useCallback)(
3631
+ (stageValue) => {
3632
+ const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
3633
+ if (onExpandedStagesChange) onExpandedStagesChange(next);
3634
+ if (expandedStages == null) setInternalExpanded(next);
3635
+ },
3636
+ [resolvedExpanded, expandedStages, onExpandedStagesChange]
3637
+ );
3638
+ const handleToggleSelect = (0, import_react13.useCallback)(
3639
+ (rowId) => {
3640
+ const next = resolvedSelection.includes(rowId) ? resolvedSelection.filter((id) => id !== rowId) : [...resolvedSelection, rowId];
3641
+ if (onSelectionChange) onSelectionChange(next);
3642
+ if (selectedIds == null) setInternalSelection(next);
3643
+ },
3644
+ [resolvedSelection, selectedIds, onSelectionChange]
3645
+ );
3646
+ const clearTransitionPrompt = (0, import_react13.useCallback)((rowId) => {
3647
+ setTransitionPrompts((prev) => {
3648
+ if (!Object.prototype.hasOwnProperty.call(prev, rowId)) return prev;
3649
+ const next = { ...prev };
3650
+ delete next[rowId];
3651
+ return next;
3652
+ });
3653
+ }, []);
3654
+ const commitStageChange = (0, import_react13.useCallback)(
3655
+ (row, newStage, oldStage, result) => {
3656
+ clearTransitionPrompt(row[rowIdField]);
3657
+ if (onStageChange) onStageChange(row, newStage, oldStage, result);
3658
+ },
3659
+ [clearTransitionPrompt, onStageChange, rowIdField]
3660
+ );
3661
+ const selectionQueryKey = (0, import_react13.useMemo)(() => {
3662
+ if (!resetSelectionOnQueryChange) return "";
3663
+ return toStableKey({
3664
+ search: resolvedSearch,
3665
+ filters: resolvedFilters,
3666
+ sort: resolvedSort || null
3667
+ });
3668
+ }, [resetSelectionOnQueryChange, resolvedFilters, resolvedSearch, resolvedSort]);
3669
+ const combinedSelectionResetKey = (0, import_react13.useMemo)(
3670
+ () => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
3671
+ [selectionQueryKey, selectionResetKey]
3672
+ );
3673
+ const clearSelection = (0, import_react13.useCallback)(() => setInternalSelection([]), []);
3674
+ useSelectionReset({
3675
+ resetKey: combinedSelectionResetKey,
3676
+ enabled: selectable,
3677
+ isControlled: selectedIds != null,
3678
+ clearSelection
3679
+ });
3680
+ const getStageFor = (0, import_react13.useCallback)(
3681
+ (row) => {
3682
+ if (typeof groupBy === "function") return groupBy(row);
3683
+ return row[groupBy];
3684
+ },
3685
+ [groupBy]
3686
+ );
3687
+ const filteredData = (0, import_react13.useMemo)(() => {
3688
+ let result = filterRows(data, filters, resolvedFilters);
3689
+ const searchLower = (resolvedSearch || "").toLowerCase().trim();
3690
+ if (searchEnabled && searchLower) {
3691
+ result = searchRows(result, searchLower, searchFields, {
3692
+ fuzzy: fuzzySearch,
3693
+ fuzzyOptions
3694
+ });
3695
+ }
3696
+ return result;
3697
+ }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
3698
+ const buckets = (0, import_react13.useMemo)(
3699
+ () => bucketRowsByStage(filteredData, stages, getStageFor),
3700
+ [filteredData, stages, getStageFor]
3701
+ );
3702
+ const sortComparator = (0, import_react13.useMemo)(() => {
3703
+ if (!sortOptions || !resolvedSort) return null;
3704
+ const opt = sortOptions.find((s) => s.value === resolvedSort);
3705
+ return (opt == null ? void 0 : opt.comparator) || null;
3706
+ }, [sortOptions, resolvedSort]);
3707
+ const sortedBuckets = (0, import_react13.useMemo)(() => sortBuckets(buckets, sortComparator), [buckets, sortComparator]);
3708
+ const hasLanes = swimlaneBy != null;
3709
+ const laneData = (0, import_react13.useMemo)(() => {
3710
+ if (!hasLanes) return null;
3711
+ return partitionLanes(filteredData, { swimlaneBy, swimlaneOrder });
3712
+ }, [hasLanes, filteredData, swimlaneBy, swimlaneOrder]);
3713
+ const laneBuckets = (0, import_react13.useMemo)(() => {
3714
+ if (!laneData) return null;
3715
+ const out = {};
3716
+ for (const laneKey of laneData.laneKeys) {
3717
+ out[laneKey] = sortBuckets(
3718
+ bucketRowsByStage(laneData.rowsByLane[laneKey] || [], stages, getStageFor),
3719
+ sortComparator
3720
+ );
3721
+ }
3722
+ return out;
3723
+ }, [laneData, stages, getStageFor, sortComparator]);
3724
+ const wipByStage = (0, import_react13.useMemo)(
3725
+ () => evaluateWip(stages, computeStageCounts(stages, buckets, stageMeta), wipLimits),
3726
+ [stages, buckets, stageMeta, wipLimits]
3727
+ );
3728
+ const prevWipRef = (0, import_react13.useRef)({});
3729
+ (0, import_react13.useEffect)(() => {
3730
+ const newlyExceeded = findNewlyExceededWip(prevWipRef.current, wipByStage);
3731
+ prevWipRef.current = wipByStage;
3732
+ if (!onWipExceeded) return;
3733
+ for (const event of newlyExceeded) {
3734
+ onWipExceeded(event.stageId, event.count, event.limit);
3735
+ }
3736
+ }, [wipByStage, onWipExceeded]);
3737
+ const activeChips = (0, import_react13.useMemo)(
3738
+ () => buildActiveFilterChips(filters, resolvedFilters),
3739
+ [filters, resolvedFilters]
3740
+ );
3741
+ const partitioned = (0, import_react13.useMemo)(() => partitionFields(cardFields || []), [cardFields]);
3742
+ const dividers = (0, import_react13.useMemo)(() => resolveDividers(cardDividers, cardDensity), [cardDividers, cardDensity]);
3743
+ const resolvedMaxBody = maxBodyLines || (cardDensity === "comfortable" ? 5 : 3);
3744
+ const resolvedStageControl = stageControl || (cardDensity === "comfortable" ? "select" : "menu");
3745
+ const resolvedStageControlPlacement = stageControlPlacement || (resolvedStageControl === "menu" ? "inline" : "separateRow");
3746
+ const handleStageChangeRequest = (0, import_react13.useCallback)(
3747
+ (row, newStage, oldStage) => {
3748
+ var _a2;
3749
+ if (!newStage || newStage === oldStage) return;
3750
+ const targetStage = stagesByValue[newStage];
3751
+ if (!targetStage || !canStageReceiveRow(targetStage, row, canMove)) return;
3752
+ const rowId = row[rowIdField];
3753
+ if ((_a2 = targetStage.onEnterRequired) == null ? void 0 : _a2.render) {
3754
+ setTransitionPrompts((prev) => ({
3755
+ ...prev,
3756
+ [rowId]: {
3757
+ row,
3758
+ fromStage: oldStage,
3759
+ toStage: newStage
3760
+ }
3761
+ }));
3762
+ return;
3763
+ }
3764
+ commitStageChange(row, newStage, oldStage);
3765
+ },
3766
+ [canMove, commitStageChange, rowIdField, stagesByValue]
3767
+ );
3768
+ const renderCardNode = (0, import_react13.useCallback)(
3769
+ (row, stage) => {
3770
+ var _a2;
3771
+ const rowId = row[rowIdField];
3772
+ const activePrompt = transitionPrompts[rowId];
3773
+ const promptStage = activePrompt ? stagesByValue[activePrompt.toStage] : null;
3774
+ if ((_a2 = promptStage == null ? void 0 : promptStage.onEnterRequired) == null ? void 0 : _a2.render) {
3775
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tile, { key: rowId, compact: cardDensity === "compact" }, promptStage.onEnterRequired.render({
3776
+ row: activePrompt.row,
3777
+ fromStage: activePrompt.fromStage,
3778
+ toStage: activePrompt.toStage,
3779
+ onConfirm: (result) => commitStageChange(activePrompt.row, activePrompt.toStage, activePrompt.fromStage, result),
3780
+ onCancel: () => clearTransitionPrompt(rowId)
3781
+ }));
3782
+ }
3783
+ if (renderCard) {
3784
+ return renderCard(row, {
3785
+ stage,
3786
+ isChanging: isStageChanging ? isStageChanging(row) : false,
3787
+ density: cardDensity,
3788
+ onStageChange: (newStage) => handleStageChangeRequest(row, newStage, stage.value)
3789
+ });
3790
+ }
3791
+ return /* @__PURE__ */ import_react13.default.createElement(
3792
+ KanbanCard,
3793
+ {
3794
+ key: rowId,
3795
+ row,
3796
+ rowId,
3797
+ stage,
3798
+ stages,
3799
+ fields: partitioned,
3800
+ density: cardDensity,
3801
+ dividers,
3802
+ bodyAs: cardBodyAs,
3803
+ maxBodyLines: resolvedMaxBody,
3804
+ stageControl: resolvedStageControl,
3805
+ stageControlPlacement: resolvedStageControlPlacement,
3806
+ canMove,
3807
+ onStageChangeRequest: handleStageChangeRequest,
3808
+ isChanging: isStageChanging ? isStageChanging(row) : false,
3809
+ selectable,
3810
+ selected: resolvedSelection.includes(rowId),
3811
+ onToggleSelect: handleToggleSelect,
3812
+ labels
3813
+ }
3814
+ );
3815
+ },
3816
+ [
3817
+ clearTransitionPrompt,
3818
+ commitStageChange,
3819
+ renderCard,
3820
+ rowIdField,
3821
+ partitioned,
3822
+ cardDensity,
3823
+ dividers,
3824
+ resolvedMaxBody,
3825
+ resolvedStageControl,
3826
+ resolvedStageControlPlacement,
3827
+ canMove,
3828
+ isStageChanging,
3829
+ selectable,
3830
+ resolvedSelection,
3831
+ handleStageChangeRequest,
3832
+ handleToggleSelect,
3833
+ labels,
3834
+ stages,
3835
+ stagesByValue,
3836
+ transitionPrompts
3837
+ ]
3838
+ );
3839
+ const totalMatching = filteredData.length;
3840
+ const selectedCount = resolvedSelection.length;
3841
+ const singular = ((recordLabel == null ? void 0 : recordLabel.singular) || "card").toLowerCase();
3842
+ const plural = ((recordLabel == null ? void 0 : recordLabel.plural) || "cards").toLowerCase();
3843
+ const countLabel = (n) => n === 1 ? singular : plural;
3844
+ const resolvedSearchPlaceholder = searchPlaceholder ?? ((recordLabel == null ? void 0 : recordLabel.plural) ? `Search ${plural}...` : labels.search);
3845
+ const selectionBarProps = {
3846
+ selectedIds: resolvedSelection,
3847
+ selectedCount,
3848
+ displayCount: totalMatching,
3849
+ countLabel,
3850
+ allSelected: selectedCount >= totalMatching && totalMatching > 0,
3851
+ onSelectAll: () => {
3852
+ const allIds = filteredData.map((r) => r[rowIdField]);
3853
+ if (onSelectionChange) onSelectionChange(allIds);
3854
+ if (selectedIds == null) setInternalSelection(allIds);
3855
+ },
3856
+ onDeselectAll: () => {
3857
+ if (onSelectionChange) onSelectionChange([]);
3858
+ if (selectedIds == null) setInternalSelection([]);
3859
+ },
3860
+ selectionActions: selectionActions || [],
3861
+ labels
3862
+ };
3863
+ const metricsProvided = metrics != null && (!Array.isArray(metrics) || metrics.length > 0);
3864
+ const perLaneMetricsActive = hasLanes && metricsPerLane && typeof metrics === "function";
3865
+ const globalMetricsContent = metricsProvided && !perLaneMetricsActive ? typeof metrics === "function" ? metrics(filteredData, null) : metrics : null;
3866
+ const renderStageColumns = (bucketMap, laneKey) => {
3867
+ const inLane = laneKey != null;
3868
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
3869
+ const stageRows = bucketMap[stage.value] || [];
3870
+ const meta = inLane ? void 0 : stageMeta == null ? void 0 : stageMeta[stage.value];
3871
+ const isExpanded = resolvedExpanded.includes(stage.value);
3872
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
3873
+ const visibleRows = stageRows.slice(0, clamp);
3874
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
3875
+ const stageWip = wipByStage[stage.value];
3876
+ const wip = inLane ? (stageWip == null ? void 0 : stageWip.exceeded) ? { count: stageWip.count, limit: null, exceeded: true } : null : stageWip;
3877
+ return /* @__PURE__ */ import_react13.default.createElement(
3878
+ import_ui_extensions13.AutoGrid,
3879
+ {
3880
+ key: inLane ? `${laneKey}::${stage.value}` : stage.value,
3881
+ columnWidth: isCollapsed ? 72 : effectiveColumnWidth
3882
+ },
3883
+ /* @__PURE__ */ import_react13.default.createElement(
3884
+ KanbanColumn,
3885
+ {
3886
+ stage,
3887
+ rows: visibleRows,
3888
+ bucketCount: stageRows.length,
3889
+ totalCount: meta == null ? void 0 : meta.totalCount,
3890
+ hasMore: meta == null ? void 0 : meta.hasMore,
3891
+ loading: meta == null ? void 0 : meta.loading,
3892
+ error: meta == null ? void 0 : meta.error,
3893
+ onLoadMore: inLane ? void 0 : onLoadMore,
3894
+ expanded: isExpanded,
3895
+ onToggleExpanded: () => handleExpanded(stage.value),
3896
+ collapsed: isCollapsed,
3897
+ onToggleCollapsed: () => handleCollapsed(stage.value),
3898
+ columnFooter,
3899
+ countDisplay,
3900
+ wip,
3901
+ compactEmpty: inLane,
3902
+ labels
3903
+ },
3904
+ visibleRows.map((row) => renderCardNode(row, stage))
3905
+ )
3906
+ );
3907
+ }));
3908
+ };
3909
+ const mainContent = error ? renderErrorState ? renderErrorState({
3910
+ error,
3911
+ title: labels.errorTitle,
3912
+ message: typeof error === "string" ? error : labels.errorMessage
3913
+ }) : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Alert, { variant: "danger", title: labels.errorTitle }, typeof error === "string" ? error : labels.errorMessage) : loading && data.length === 0 ? renderLoadingState ? renderLoadingState({ label: labels.loading }) : (
3914
+ // Same EmptyState layout as the empty state (just the "building" image +
3915
+ // a loading message) so loading and empty match with no layout shift.
3916
+ /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tile, null, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.EmptyState, { title: labels.loading, imageName: "building", layout: "vertical" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, null, labels.loadingMessage))))
3917
+ ) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
3918
+ title: labels.emptyTitle,
3919
+ message: labels.emptyMessage
3920
+ }) : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tile, null, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, null, labels.emptyMessage)))) : hasLanes ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", gap: "md" }, ((laneData == null ? void 0 : laneData.laneKeys) || []).map((laneKey, laneIndex) => {
3921
+ const laneRows = laneData.rowsByLane[laneKey] || [];
3922
+ const laneLabel = resolveLaneLabel(laneKey, swimlaneLabels, laneRows, labels.unassignedLane);
3923
+ const laneLabelText = typeof laneLabel === "string" ? laneLabel : String(laneKey);
3924
+ const isLaneCollapsed = collapseLanes && resolvedCollapsedLanes.includes(laneKey);
3925
+ const laneCountLabel = labels.laneCount(laneRows.length);
3926
+ const laneCountNode = countDisplay === "none" ? null : countDisplay === "text" ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { format: { fontWeight: "demibold" } }, laneCountLabel) : /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Tag, { variant: "default" }, laneCountLabel);
3927
+ const laneMetricsNode = !isLaneCollapsed && perLaneMetricsActive && resolvedShowMetrics ? renderMetricsPanel(metrics(laneRows, laneKey)) : null;
3928
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { key: laneKey, direction: "column", gap: "xs" }, laneIndex > 0 ? /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Divider, null) : null, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "row", align: "center", gap: "xs" }, collapseLanes ? /* @__PURE__ */ import_react13.default.createElement(
3929
+ import_ui_extensions13.Button,
3930
+ {
3931
+ variant: "transparent",
3932
+ size: "sm",
3933
+ onClick: () => handleLaneCollapsed(laneKey),
3934
+ tooltip: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
3935
+ },
3936
+ /* @__PURE__ */ import_react13.default.createElement(
3937
+ Icon,
3938
+ {
3939
+ name: isLaneCollapsed ? "right" : "down",
3940
+ size: "sm",
3941
+ screenReaderText: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
3942
+ }
3943
+ )
3944
+ ) : null, /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Text, { format: { fontWeight: "demibold" } }, laneLabel), laneCountNode), laneMetricsNode, !isLaneCollapsed ? renderStageColumns((laneBuckets == null ? void 0 : laneBuckets[laneKey]) || {}, laneKey) : null);
3945
+ })) : renderStageColumns(sortedBuckets, null);
3946
+ const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
3947
+ return /* @__PURE__ */ import_react13.default.createElement(import_ui_extensions13.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react13.default.createElement(
3948
+ KanbanToolbar,
3949
+ {
3950
+ showSearch: searchEnabled,
3951
+ searchValue: searchInputValue,
3952
+ searchPlaceholder: resolvedSearchPlaceholder,
3953
+ onSearchChange: handleSearch,
3954
+ filters,
3955
+ filterValues: resolvedFilters,
3956
+ onFilterChange: handleFilter,
3957
+ filterInlineLimit,
3958
+ showFilterBadges,
3959
+ showClearFiltersButton: resolvedShowClearFiltersButton,
3960
+ activeChips,
3961
+ onFilterRemove: handleFilterRemove,
3962
+ sortOptions,
3963
+ sortValue: resolvedSort,
3964
+ onSortChange: handleSort,
3965
+ showMetricsButton: metricsProvided,
3966
+ metricsPanel: resolvedShowMetrics && globalMetricsContent ? renderMetricsPanel(globalMetricsContent) : null,
3967
+ onToggleMetrics: toggleMetrics,
3968
+ labels,
3969
+ toolbarLeftFlex,
3970
+ toolbarRightFlex
3971
+ }
3972
+ ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ import_react13.default.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
3973
+ };
3974
+ Kanban.displayName = "Kanban";
3975
+
3976
+ // src/utils/objectPath.js
3977
+ var getByPath = (obj, path) => {
3978
+ if (!path) return void 0;
3979
+ if (typeof path === "function") return path(obj);
3980
+ return String(path).split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
3981
+ };
3982
+
3983
+ // src/utils/crmSearchAdapters.js
3984
+ var EMPTY_ARRAY = [];
3985
+ var EMPTY_OBJECT = {};
3986
+ var EMPTY_CRM_PARAMS = { search: "", filters: {}, sort: null };
3987
+ var isPlainObject = (value) => value != null && Object.prototype.toString.call(value) === "[object Object]";
3988
+ var coerceError = (error) => {
3989
+ if (!error) return false;
3990
+ if (typeof error === "string") return error;
3991
+ if (error.message) return error.message;
3992
+ return true;
3993
+ };
3994
+ var pickArray = (response) => {
3995
+ if (Array.isArray(response)) return response;
3996
+ if (!response) return EMPTY_ARRAY;
3997
+ return response.results || response.data || response.items || response.records || response.objects || EMPTY_ARRAY;
3998
+ };
3999
+ var pickTotal = (response, fallbackLength) => {
4000
+ var _a;
4001
+ if (!response || Array.isArray(response)) return fallbackLength;
4002
+ return response.total ?? response.totalCount ?? response.totalResults ?? ((_a = response.paging) == null ? void 0 : _a.total) ?? fallbackLength;
4003
+ };
4004
+ var normalizeCrmSearchRecord = (record, options = EMPTY_OBJECT) => {
4005
+ const {
4006
+ idField = "id",
4007
+ objectIdField = "objectId",
4008
+ propertiesKey = "properties",
4009
+ flattenProperties = true,
4010
+ propertyValueKey,
4011
+ mapRecord
4012
+ } = options;
4013
+ if (mapRecord) return mapRecord(record);
4014
+ const objectId = (record == null ? void 0 : record.objectId) ?? (record == null ? void 0 : record.id) ?? (record == null ? void 0 : record.hs_object_id) ?? getByPath(record, `${propertiesKey}.hs_object_id`);
4015
+ const properties = (record == null ? void 0 : record[propertiesKey]) || EMPTY_OBJECT;
4016
+ const flattened = {};
4017
+ if (flattenProperties && isPlainObject(properties)) {
4018
+ for (const [key, value] of Object.entries(properties)) {
4019
+ flattened[key] = propertyValueKey && isPlainObject(value) ? value[propertyValueKey] : value;
4020
+ }
4021
+ }
4022
+ return {
4023
+ ...flattenProperties ? flattened : EMPTY_OBJECT,
4024
+ ...record,
4025
+ [idField]: objectId,
4026
+ [objectIdField]: objectId,
4027
+ [propertiesKey]: properties
4028
+ };
4029
+ };
4030
+ var normalizeCrmSearchRows = (response, options = EMPTY_OBJECT) => {
4031
+ const records = pickArray(response);
4032
+ return records.map((record) => normalizeCrmSearchRecord(record, options));
4033
+ };
4034
+ var STABLE_SORT_TIEBREAKER = { propertyName: "hs_object_id", direction: "ASCENDING" };
4035
+ var withStableSort = (sorts) => {
4036
+ const base = Array.isArray(sorts) ? sorts : [];
4037
+ if (base.some((s) => s && s.propertyName === STABLE_SORT_TIEBREAKER.propertyName)) return base;
4038
+ return [...base, STABLE_SORT_TIEBREAKER];
4039
+ };
4040
+ var buildCrmSearchConfig = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
1725
4041
  const {
1726
4042
  objectType,
1727
4043
  properties = EMPTY_ARRAY,
@@ -1825,7 +4141,393 @@ var CRM_OBJECT_TYPES = {
1825
4141
  deal: "0-3",
1826
4142
  deals: "0-3"
1827
4143
  };
4144
+ var prettifyPropertyName = (name) => String(name || "").replace(/^hs_/, "").replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
4145
+ var inferCrmColumns = (properties = EMPTY_ARRAY) => properties.map((property) => ({
4146
+ field: property,
4147
+ label: prettifyPropertyName(property),
4148
+ sortable: true
4149
+ }));
4150
+ var normalizeAutoFilterFields = (autoFilters, properties = EMPTY_ARRAY) => {
4151
+ if (!autoFilters) return EMPTY_ARRAY;
4152
+ if (Array.isArray(autoFilters)) return autoFilters;
4153
+ if (typeof autoFilters === "object" && Array.isArray(autoFilters.fields)) return autoFilters.fields;
4154
+ return properties.filter((property) => !["id", "objectId", "hs_object_id", "email", "firstname", "lastname", "name", "domain"].includes(property));
4155
+ };
4156
+ var buildAutoFiltersFromRows = ({ rows, fields, labelsRef, maxOptions = 25 }) => {
4157
+ if (!fields.length) return EMPTY_ARRAY;
4158
+ for (const row of rows || EMPTY_ARRAY) {
4159
+ for (const field of fields) {
4160
+ const value = getByPath(row, field);
4161
+ if (value == null || value === "" || Array.isArray(value) || isPlainObject(value)) continue;
4162
+ if (!labelsRef.current[field]) labelsRef.current[field] = /* @__PURE__ */ new Map();
4163
+ const map = labelsRef.current[field];
4164
+ if (map.size < maxOptions || map.has(value)) map.set(value, String(value));
4165
+ }
4166
+ }
4167
+ return fields.map((field) => {
4168
+ const map = labelsRef.current[field];
4169
+ if (!map || map.size === 0 || map.size > maxOptions) return null;
4170
+ return {
4171
+ name: field,
4172
+ label: prettifyPropertyName(field),
4173
+ placeholder: `Any ${prettifyPropertyName(field).toLowerCase()}`,
4174
+ options: Array.from(map.entries()).map(([value, label]) => ({ value, label }))
4175
+ };
4176
+ }).filter(Boolean);
4177
+ };
1828
4178
  var resolveCrmObjectType = (objectType) => CRM_OBJECT_TYPES[objectType] || objectType;
4179
+ var DEFAULT_CRM_FORMAT = { propertiesToFormat: "all" };
4180
+ var defaultCrmMapRecord = (record) => ({ objectId: record.objectId, ...record.properties });
4181
+ var stableStringify = (value) => {
4182
+ const normalize = (item) => {
4183
+ if (Array.isArray(item)) return item.map(normalize);
4184
+ if (isPlainObject(item)) {
4185
+ return Object.keys(item).sort().reduce((acc, key) => {
4186
+ acc[key] = normalize(item[key]);
4187
+ return acc;
4188
+ }, {});
4189
+ }
4190
+ if (typeof item === "function") return item.name || "[function]";
4191
+ return item;
4192
+ };
4193
+ try {
4194
+ return JSON.stringify(normalize(value));
4195
+ } catch {
4196
+ return String(value);
4197
+ }
4198
+ };
4199
+ var appendUniqueRows = (previousRows, nextRows, rowIdField) => {
4200
+ if (!previousRows.length) return nextRows || EMPTY_ARRAY;
4201
+ if (!(nextRows == null ? void 0 : nextRows.length)) return previousRows;
4202
+ const seen = new Set(previousRows.map((row) => getByPath(row, rowIdField) ?? (row == null ? void 0 : row.objectId) ?? (row == null ? void 0 : row.id)));
4203
+ const additions = nextRows.filter((row) => {
4204
+ const id = getByPath(row, rowIdField) ?? (row == null ? void 0 : row.objectId) ?? (row == null ? void 0 : row.id);
4205
+ if (id == null) return true;
4206
+ if (seen.has(id)) return false;
4207
+ seen.add(id);
4208
+ return true;
4209
+ });
4210
+ return additions.length ? [...previousRows, ...additions] : previousRows;
4211
+ };
4212
+ var crmSortsFromState = (sort, propertyMap) => {
4213
+ if (!sort || !sort.field || !sort.direction) return void 0;
4214
+ const propertyName = propertyMap && propertyMap[sort.field] || sort.field;
4215
+ return [{ propertyName, direction: sort.direction === "descending" ? "DESCENDING" : "ASCENDING" }];
4216
+ };
4217
+ var CrmDataTable = ({
4218
+ objectType,
4219
+ properties = EMPTY_ARRAY,
4220
+ columns,
4221
+ title,
4222
+ pageLength = 100,
4223
+ // CRM batch fetched per request (CRM search max)
4224
+ pageSize = 10,
4225
+ // client-side page size
4226
+ // Hybrid model: fetch ONE batch and do everything client-side while the whole
4227
+ // result set fits in the batch (no refetch). Once a fetch comes back capped
4228
+ // (more matches than the batch), search / filter / sort start refetching a
4229
+ // fresh batch server-side so they reach the whole dataset. Pagination stays
4230
+ // client-side over the fetched batch; `serverSide` forces server-side querying
4231
+ // from the first render.
4232
+ serverSide = false,
4233
+ filters,
4234
+ autoFilters = false,
4235
+ autoFilterMaxOptions = 25,
4236
+ filterMap,
4237
+ propertyMap,
4238
+ sortMap,
4239
+ searchFields,
4240
+ searchPlaceholder,
4241
+ format = DEFAULT_CRM_FORMAT,
4242
+ mapRecord,
4243
+ rowIdField = "objectId",
4244
+ dataTableProps = EMPTY_OBJECT,
4245
+ ...props
4246
+ }) => {
4247
+ var _a, _b;
4248
+ const [params, setParams] = (0, import_react14.useState)({ search: "", filters: {}, sort: null });
4249
+ const resolvedProperties = (0, import_react14.useMemo)(() => properties, [properties]);
4250
+ const resolvedColumns = (0, import_react14.useMemo)(
4251
+ () => columns || inferCrmColumns(resolvedProperties),
4252
+ [columns, resolvedProperties]
4253
+ );
4254
+ const resolvedSearchFields = searchFields || resolvedProperties;
4255
+ const autoFilterFields = (0, import_react14.useMemo)(
4256
+ () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
4257
+ [autoFilters, resolvedProperties]
4258
+ );
4259
+ const autoFilterLabelsRef = (0, import_react14.useRef)({});
4260
+ const defaultPropertyMap = (0, import_react14.useMemo)(
4261
+ () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
4262
+ [resolvedProperties]
4263
+ );
4264
+ const effectivePropertyMap = propertyMap || defaultPropertyMap;
4265
+ const resolvedSortMap = (0, import_react14.useMemo)(
4266
+ () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
4267
+ [sortMap, effectivePropertyMap]
4268
+ );
4269
+ const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
4270
+ const dataSourceOptions = (0, import_react14.useMemo)(
4271
+ () => ({
4272
+ objectType: resolveCrmObjectType(objectType),
4273
+ properties: resolvedProperties,
4274
+ pageLength,
4275
+ format,
4276
+ filterMap,
4277
+ propertyMap: effectivePropertyMap,
4278
+ sortMap: resolvedSortMap,
4279
+ rowIdField,
4280
+ row: { idField: rowIdField, mapRecord: resolvedMapRecord }
4281
+ }),
4282
+ [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
4283
+ );
4284
+ const [serverQuerying, setServerQuerying] = (0, import_react14.useState)(!!serverSide);
4285
+ const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
4286
+ const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
4287
+ const queryKey = (0, import_react14.useMemo)(
4288
+ () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
4289
+ [effectiveParams, objectType, resolvedProperties, pageLength]
4290
+ );
4291
+ const [accumulatedRows, setAccumulatedRows] = (0, import_react14.useState)(EMPTY_ARRAY);
4292
+ const [requestedPage, setRequestedPage] = (0, import_react14.useState)(1);
4293
+ const lastQueryKeyRef = (0, import_react14.useRef)(queryKey);
4294
+ const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
4295
+ (0, import_react14.useEffect)(() => {
4296
+ var _a2;
4297
+ if (lastQueryKeyRef.current !== queryKey) {
4298
+ lastQueryKeyRef.current = queryKey;
4299
+ setAccumulatedRows(dataSource.data);
4300
+ return;
4301
+ }
4302
+ const currentPage = ((_a2 = dataSource.pagination) == null ? void 0 : _a2.currentPage) || 1;
4303
+ setAccumulatedRows((prev) => currentPage <= 1 ? dataSource.data : appendUniqueRows(prev, dataSource.data, rowIdField));
4304
+ }, [queryKey, dataSource.data, (_a = dataSource.pagination) == null ? void 0 : _a.currentPage, rowIdField]);
4305
+ (0, import_react14.useEffect)(() => {
4306
+ if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > loadedRows.length) {
4307
+ setServerQuerying(true);
4308
+ }
4309
+ }, [serverQuerying, dataSource.totalCount, loadedRows.length]);
4310
+ const ensurePageLoaded = (0, import_react14.useCallback)((page) => {
4311
+ var _a2, _b2, _c;
4312
+ const pageNumber = Number(page) || 1;
4313
+ const requiredRows = pageNumber * pageSize;
4314
+ if (requiredRows <= loadedRows.length) return;
4315
+ if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
4316
+ (_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
4317
+ }, [pageSize, loadedRows.length, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
4318
+ (0, import_react14.useEffect)(() => {
4319
+ ensurePageLoaded(requestedPage);
4320
+ }, [requestedPage, ensurePageLoaded]);
4321
+ const generatedFilters = (0, import_react14.useMemo)(
4322
+ () => buildAutoFiltersFromRows({
4323
+ rows: loadedRows,
4324
+ fields: autoFilterFields,
4325
+ labelsRef: autoFilterLabelsRef,
4326
+ maxOptions: autoFilterMaxOptions
4327
+ }),
4328
+ [loadedRows, autoFilterFields, autoFilterMaxOptions]
4329
+ );
4330
+ const resolvedFilters = filters || generatedFilters;
4331
+ const table = import_react14.default.createElement(DataTable, {
4332
+ title: title || `${prettifyPropertyName(objectType)} records`,
4333
+ data: loadedRows,
4334
+ loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
4335
+ error: dataSource.error,
4336
+ columns: resolvedColumns,
4337
+ rowIdField,
4338
+ pageSize,
4339
+ clientTotalCount: dataSource.totalCount,
4340
+ filters: resolvedFilters,
4341
+ searchFields: resolvedSearchFields,
4342
+ searchPlaceholder: searchPlaceholder || `Search ${prettifyPropertyName(objectType).toLowerCase()}...`,
4343
+ searchDebounce: 300,
4344
+ onParamsChange: (next) => {
4345
+ setParams((prev) => ({ ...prev, search: next.search, filters: next.filters, sort: next.sort }));
4346
+ setRequestedPage(next.page || 1);
4347
+ ensurePageLoaded(next.page);
4348
+ },
4349
+ ...dataTableProps,
4350
+ ...props
4351
+ });
4352
+ const total = dataSource.totalCount;
4353
+ const capped = typeof total === "number" && total > loadedRows.length;
4354
+ if (!capped) return table;
4355
+ return import_react14.default.createElement(
4356
+ import_ui_extensions14.Flex,
4357
+ { direction: "column", gap: "xs" },
4358
+ import_react14.default.createElement(
4359
+ import_ui_extensions14.Text,
4360
+ { variant: "microcopy" },
4361
+ dataSource.hasMore ? `Loaded ${loadedRows.length} of ${total} matching. Use the table pagination to load more CRM results.` : `Showing ${loadedRows.length} of ${total} matching. Refine your search or filters to narrow the results.`
4362
+ ),
4363
+ table
4364
+ );
4365
+ };
4366
+ var CrmKanban = ({
4367
+ objectType,
4368
+ properties = EMPTY_ARRAY,
4369
+ groupBy,
4370
+ stages,
4371
+ stageLabels,
4372
+ // object { value: label } or (value) => label
4373
+ title,
4374
+ pageLength = 100,
4375
+ serverSide = false,
4376
+ filters,
4377
+ autoFilters = false,
4378
+ autoFilterMaxOptions = 25,
4379
+ filterMap,
4380
+ propertyMap,
4381
+ sortMap,
4382
+ searchFields,
4383
+ searchPlaceholder,
4384
+ format = DEFAULT_CRM_FORMAT,
4385
+ mapRecord,
4386
+ rowIdField = "objectId",
4387
+ stageMeta,
4388
+ onLoadMore,
4389
+ kanbanProps = EMPTY_OBJECT,
4390
+ ...props
4391
+ }) => {
4392
+ var _a, _b;
4393
+ const [params, setParams] = (0, import_react14.useState)(EMPTY_CRM_PARAMS);
4394
+ const resolvedProperties = (0, import_react14.useMemo)(() => properties, [properties]);
4395
+ const resolvedSearchFields = searchFields || resolvedProperties;
4396
+ const autoFilterFields = (0, import_react14.useMemo)(
4397
+ () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
4398
+ [autoFilters, resolvedProperties]
4399
+ );
4400
+ const autoFilterLabelsRef = (0, import_react14.useRef)({});
4401
+ const defaultPropertyMap = (0, import_react14.useMemo)(
4402
+ () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
4403
+ [resolvedProperties]
4404
+ );
4405
+ const effectivePropertyMap = propertyMap || defaultPropertyMap;
4406
+ const resolvedSortMap = (0, import_react14.useMemo)(
4407
+ () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
4408
+ [sortMap, effectivePropertyMap]
4409
+ );
4410
+ const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
4411
+ const dataSourceOptions = (0, import_react14.useMemo)(
4412
+ () => ({
4413
+ objectType: resolveCrmObjectType(objectType),
4414
+ properties: resolvedProperties,
4415
+ pageLength,
4416
+ format,
4417
+ filterMap,
4418
+ propertyMap: effectivePropertyMap,
4419
+ sortMap: resolvedSortMap,
4420
+ rowIdField,
4421
+ row: { idField: rowIdField, mapRecord: resolvedMapRecord }
4422
+ }),
4423
+ [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
4424
+ );
4425
+ const [serverQuerying, setServerQuerying] = (0, import_react14.useState)(!!serverSide);
4426
+ const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
4427
+ const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
4428
+ const queryKey = (0, import_react14.useMemo)(
4429
+ () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
4430
+ [effectiveParams, objectType, resolvedProperties, pageLength]
4431
+ );
4432
+ const [accumulatedRows, setAccumulatedRows] = (0, import_react14.useState)(EMPTY_ARRAY);
4433
+ const lastQueryKeyRef = (0, import_react14.useRef)(queryKey);
4434
+ const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
4435
+ (0, import_react14.useEffect)(() => {
4436
+ var _a2;
4437
+ if (lastQueryKeyRef.current !== queryKey) {
4438
+ lastQueryKeyRef.current = queryKey;
4439
+ setAccumulatedRows(dataSource.data);
4440
+ return;
4441
+ }
4442
+ const currentPage = ((_a2 = dataSource.pagination) == null ? void 0 : _a2.currentPage) || 1;
4443
+ setAccumulatedRows((prev) => currentPage <= 1 ? dataSource.data : appendUniqueRows(prev, dataSource.data, rowIdField));
4444
+ }, [queryKey, dataSource.data, (_a = dataSource.pagination) == null ? void 0 : _a.currentPage, rowIdField]);
4445
+ (0, import_react14.useEffect)(() => {
4446
+ if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > loadedRows.length) {
4447
+ setServerQuerying(true);
4448
+ }
4449
+ }, [serverQuerying, dataSource.totalCount, loadedRows.length]);
4450
+ const handleLoadMore = (0, import_react14.useCallback)((stage) => {
4451
+ var _a2, _b2, _c;
4452
+ if (onLoadMore) {
4453
+ onLoadMore(stage);
4454
+ return;
4455
+ }
4456
+ if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
4457
+ (_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
4458
+ }, [onLoadMore, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
4459
+ const generatedFilters = (0, import_react14.useMemo)(
4460
+ () => buildAutoFiltersFromRows({
4461
+ rows: loadedRows,
4462
+ fields: autoFilterFields,
4463
+ labelsRef: autoFilterLabelsRef,
4464
+ maxOptions: autoFilterMaxOptions
4465
+ }),
4466
+ [loadedRows, autoFilterFields, autoFilterMaxOptions]
4467
+ );
4468
+ const resolvedFilters = filters || generatedFilters;
4469
+ const resolvedStages = (0, import_react14.useMemo)(() => {
4470
+ if (stages) return stages;
4471
+ const seen = [];
4472
+ for (const row of loadedRows) {
4473
+ const value = typeof groupBy === "function" ? groupBy(row) : row[groupBy];
4474
+ if (value != null && value !== "" && !seen.includes(value)) seen.push(value);
4475
+ }
4476
+ return seen.map((value) => ({
4477
+ value,
4478
+ label: typeof stageLabels === "function" ? stageLabels(value) : stageLabels && stageLabels[value] || prettifyPropertyName(String(value))
4479
+ }));
4480
+ }, [stages, stageLabels, loadedRows, groupBy]);
4481
+ const resolvedStageMeta = (0, import_react14.useMemo)(() => {
4482
+ if (stageMeta || !dataSource.hasMore) return stageMeta;
4483
+ return Object.fromEntries(resolvedStages.map((stage) => {
4484
+ var _a2;
4485
+ return [
4486
+ stage.value,
4487
+ {
4488
+ hasMore: true,
4489
+ loading: dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching),
4490
+ totalCount: dataSource.totalCount
4491
+ }
4492
+ ];
4493
+ }));
4494
+ }, [stageMeta, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.totalCount, resolvedStages]);
4495
+ const board = import_react14.default.createElement(Kanban, {
4496
+ title: title || `${prettifyPropertyName(objectType)} board`,
4497
+ data: loadedRows,
4498
+ loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
4499
+ error: dataSource.error,
4500
+ rowIdField,
4501
+ groupBy,
4502
+ stages: resolvedStages,
4503
+ stageMeta: resolvedStageMeta,
4504
+ onLoadMore: dataSource.hasMore || onLoadMore ? handleLoadMore : void 0,
4505
+ filters: resolvedFilters,
4506
+ searchFields: resolvedSearchFields,
4507
+ searchPlaceholder: searchPlaceholder || `Search ${prettifyPropertyName(objectType).toLowerCase()}...`,
4508
+ searchDebounce: 300,
4509
+ onParamsChange: (next) => {
4510
+ setParams((prev) => ({ ...prev, search: next.search, filters: next.filters }));
4511
+ },
4512
+ ...kanbanProps,
4513
+ ...props
4514
+ });
4515
+ const total = dataSource.totalCount;
4516
+ const capped = typeof total === "number" && total > loadedRows.length;
4517
+ if (!capped) return board;
4518
+ return import_react14.default.createElement(
4519
+ import_ui_extensions14.Flex,
4520
+ { direction: "column", gap: "xs" },
4521
+ import_react14.default.createElement(
4522
+ import_ui_extensions14.Text,
4523
+ { variant: "microcopy" },
4524
+ dataSource.hasMore ? `Loaded ${loadedRows.length} of ${total} matching. Use Load more to fetch more CRM results.` : `Showing ${loadedRows.length} of ${total} matching. Refine your search or filters to narrow the results.`
4525
+ ),
4526
+ board
4527
+ );
4528
+ };
4529
+ CrmDataTable.displayName = "CrmDataTable";
4530
+ CrmKanban.displayName = "CrmKanban";
1829
4531
 
1830
4532
  // src/common-components/CrmLookupSelect.js
1831
4533
  var EMPTY_ARRAY2 = [];
@@ -1943,6 +4645,280 @@ var CrmLookupSelect = ({
1943
4645
  return import_react15.default.createElement(multiple ? import_ui_extensions15.MultiSelect : import_ui_extensions15.Select, commonProps);
1944
4646
  };
1945
4647
 
4648
+ // src/common-components/CrmRecordPicker.js
4649
+ var import_react16 = __toESM(require("react"));
4650
+ var import_ui_extensions16 = require("@hubspot/ui-extensions");
4651
+
4652
+ // src/common-components/recordPickerCore.js
4653
+ var EMPTY_ARRAY3 = [];
4654
+ var CREATE_OPTION_VALUE = "__create__";
4655
+ var isRecordLike = (value) => value != null && typeof value === "object" && !Array.isArray(value);
4656
+ var getRecordId = (record) => {
4657
+ if (!isRecordLike(record)) return void 0;
4658
+ const id = record.objectId ?? record.id ?? record.hs_object_id ?? getByPath(record, "properties.hs_object_id");
4659
+ return id == null ? void 0 : String(id);
4660
+ };
4661
+ var toList = (value) => Array.isArray(value) ? value : value == null || value === "" ? EMPTY_ARRAY3 : [value];
4662
+ var normalizeRecordSelection = (value) => {
4663
+ const ids = [];
4664
+ const records = [];
4665
+ const seen = /* @__PURE__ */ new Set();
4666
+ for (const entry of toList(value)) {
4667
+ const rawId = isRecordLike(entry) ? getRecordId(entry) : entry;
4668
+ const id = rawId == null || rawId === "" ? rawId : String(rawId);
4669
+ if (id == null || id === "" || seen.has(id)) continue;
4670
+ seen.add(id);
4671
+ ids.push(id);
4672
+ if (isRecordLike(entry)) records.push(entry);
4673
+ }
4674
+ return { ids, records };
4675
+ };
4676
+ var recordToPickerOption = (record, config = {}) => {
4677
+ const { labelField, descriptionField, fallbackLabel = "Untitled record" } = config;
4678
+ const label = (labelField ? getByPath(record, labelField) : void 0) ?? (record == null ? void 0 : record.name) ?? getByPath(record, "properties.name") ?? fallbackLabel;
4679
+ const option = { label, value: getRecordId(record) };
4680
+ const description = descriptionField ? getByPath(record, descriptionField) : void 0;
4681
+ if (description != null && description !== "") option.description = description;
4682
+ return option;
4683
+ };
4684
+ var mergePickerOptions = (options, selectedOptions) => {
4685
+ const base = Array.isArray(options) ? options : EMPTY_ARRAY3;
4686
+ const selected = toList(selectedOptions);
4687
+ if (!selected.length) return base;
4688
+ const existing = new Set(base.map((option) => option == null ? void 0 : option.value));
4689
+ const missing = selected.filter((option) => option && !existing.has(option.value));
4690
+ return missing.length ? [...missing, ...base] : base;
4691
+ };
4692
+ var enforceSelectionMax = (ids, max) => {
4693
+ const list = toList(ids);
4694
+ if (!Number.isFinite(max) || max <= 0 || list.length <= max) return list;
4695
+ return list.slice(0, max);
4696
+ };
4697
+ var shouldShowCreateOption = ({
4698
+ allowCreate,
4699
+ searchTerm,
4700
+ options,
4701
+ searching = false,
4702
+ createPending = false,
4703
+ atMax = false
4704
+ } = {}) => {
4705
+ if (!allowCreate || typeof allowCreate.onCreate !== "function") return false;
4706
+ if (createPending || searching || atMax) return false;
4707
+ const term = String(searchTerm ?? "").trim();
4708
+ if (!term) return false;
4709
+ const lower = term.toLowerCase();
4710
+ return !(options || EMPTY_ARRAY3).some(
4711
+ (option) => String((option == null ? void 0 : option.label) ?? "").trim().toLowerCase() === lower
4712
+ );
4713
+ };
4714
+ var makeCreateOption = (term, label) => ({
4715
+ label: typeof label === "function" ? label(term) : label || `Create "${term}"`,
4716
+ value: CREATE_OPTION_VALUE
4717
+ });
4718
+ var splitCreateSelection = (next) => {
4719
+ const list = toList(next);
4720
+ const ids = list.filter((value) => value !== CREATE_OPTION_VALUE);
4721
+ return { ids, create: ids.length !== list.length };
4722
+ };
4723
+ var mapIdsToRecords = (ids, recordsById) => {
4724
+ const lookup = recordsById instanceof Map ? (id) => recordsById.get(id) : (id) => recordsById ? recordsById[id] : void 0;
4725
+ return toList(ids).map((id) => lookup(id) ?? { objectId: id });
4726
+ };
4727
+ var upsertRecords = (records, additions) => {
4728
+ const incoming = toList(additions).filter((record) => getRecordId(record) != null);
4729
+ if (!incoming.length) return Array.isArray(records) ? records : EMPTY_ARRAY3;
4730
+ const byId = new Map(toList(records).map((record) => [getRecordId(record), record]));
4731
+ for (const record of incoming) byId.set(getRecordId(record), record);
4732
+ return [...byId.values()];
4733
+ };
4734
+
4735
+ // src/common-components/CrmRecordPicker.js
4736
+ var EMPTY_ARRAY4 = [];
4737
+ var defaultMapRecord = (record) => ({ objectId: record.objectId, ...record.properties });
4738
+ var CrmRecordPicker = ({
4739
+ objectType,
4740
+ properties = EMPTY_ARRAY4,
4741
+ labelField,
4742
+ descriptionField,
4743
+ value,
4744
+ defaultValue,
4745
+ onChange,
4746
+ multi = true,
4747
+ max,
4748
+ placeholder,
4749
+ label,
4750
+ name,
4751
+ required,
4752
+ readOnly,
4753
+ error,
4754
+ validationMessage,
4755
+ description,
4756
+ tooltip,
4757
+ variant,
4758
+ pageLength = 20,
4759
+ debounce = 300,
4760
+ minSearchLength = 0,
4761
+ filterMap,
4762
+ allowCreate = false,
4763
+ fallbackLabel = "Untitled record",
4764
+ format,
4765
+ baseConfig,
4766
+ onSearchChange,
4767
+ ...rest
4768
+ }) => {
4769
+ const isControlled = value !== void 0;
4770
+ const [internalValue, setInternalValue] = (0, import_react16.useState)(defaultValue);
4771
+ const effectiveValue = isControlled ? value : internalValue;
4772
+ const selection = (0, import_react16.useMemo)(() => normalizeRecordSelection(effectiveValue), [effectiveValue]);
4773
+ const [inputValue, setInputValue] = (0, import_react16.useState)("");
4774
+ const [seenRecords, setSeenRecords] = (0, import_react16.useState)(EMPTY_ARRAY4);
4775
+ const [createPending, setCreatePending] = (0, import_react16.useState)(false);
4776
+ const [createError, setCreateError] = (0, import_react16.useState)(null);
4777
+ const createPendingRef = (0, import_react16.useRef)(false);
4778
+ const debouncedInput = (0, import_ui_extensions16.useDebounce)(inputValue, debounce > 0 ? debounce : 1);
4779
+ const search = debounce > 0 ? debouncedInput : inputValue;
4780
+ const effectiveSearch = search && search.length >= minSearchLength ? search : "";
4781
+ const optionConfig = (0, import_react16.useMemo)(
4782
+ () => ({ labelField, descriptionField, fallbackLabel }),
4783
+ [labelField, descriptionField, fallbackLabel]
4784
+ );
4785
+ const searchParams = (0, import_react16.useMemo)(() => ({ search: effectiveSearch }), [effectiveSearch]);
4786
+ const dataSourceOptions = (0, import_react16.useMemo)(
4787
+ () => ({
4788
+ objectType: resolveCrmObjectType(objectType),
4789
+ properties,
4790
+ pageLength,
4791
+ format,
4792
+ filterMap,
4793
+ baseConfig,
4794
+ row: { mapRecord: defaultMapRecord },
4795
+ option: { mapOption: (row) => recordToPickerOption(row, optionConfig) }
4796
+ }),
4797
+ [objectType, properties, pageLength, format, filterMap, baseConfig, optionConfig]
4798
+ );
4799
+ const dataSource = useCrmSearchOptions(searchParams, dataSourceOptions);
4800
+ const recordsById = (0, import_react16.useMemo)(() => {
4801
+ const map = /* @__PURE__ */ new Map();
4802
+ for (const record of selection.records) map.set(getRecordId(record), record);
4803
+ for (const record of seenRecords) map.set(getRecordId(record), record);
4804
+ for (const row of dataSource.rows || EMPTY_ARRAY4) {
4805
+ const id = getRecordId(row);
4806
+ if (id != null) map.set(id, row);
4807
+ }
4808
+ return map;
4809
+ }, [selection.records, seenRecords, dataSource.rows]);
4810
+ const selectedOptions = (0, import_react16.useMemo)(
4811
+ () => selection.ids.map((id) => {
4812
+ const record = recordsById.get(id);
4813
+ return record ? recordToPickerOption(record, optionConfig) : { label: String(id), value: id };
4814
+ }),
4815
+ [selection.ids, recordsById, optionConfig]
4816
+ );
4817
+ const isSearching = dataSource.loading || inputValue.trim() !== (search || "").trim();
4818
+ const atMax = multi && Number.isFinite(max) && max > 0 && selection.ids.length >= max;
4819
+ const options = (0, import_react16.useMemo)(() => {
4820
+ const merged = mergePickerOptions(dataSource.options || EMPTY_ARRAY4, selectedOptions);
4821
+ const showCreate = shouldShowCreateOption({
4822
+ allowCreate,
4823
+ searchTerm: effectiveSearch,
4824
+ options: merged,
4825
+ searching: isSearching,
4826
+ createPending,
4827
+ atMax
4828
+ });
4829
+ if (!showCreate) return merged;
4830
+ return [...merged, makeCreateOption(effectiveSearch.trim(), allowCreate == null ? void 0 : allowCreate.label)];
4831
+ }, [dataSource.options, selectedOptions, allowCreate, effectiveSearch, isSearching, createPending, atMax]);
4832
+ const commitChange = (ids, extraRecords) => {
4833
+ let map = recordsById;
4834
+ if (extraRecords && extraRecords.length) {
4835
+ map = new Map(recordsById);
4836
+ for (const record of extraRecords) {
4837
+ const id = getRecordId(record);
4838
+ if (id != null) map.set(id, record);
4839
+ }
4840
+ }
4841
+ const trimmed = multi ? enforceSelectionMax(ids, max) : ids.slice(0, 1);
4842
+ const records = mapIdsToRecords(trimmed, map);
4843
+ if (!isControlled) setInternalValue(multi ? trimmed : trimmed[0] ?? null);
4844
+ if (onChange) {
4845
+ if (multi) onChange(trimmed, records);
4846
+ else onChange(trimmed[0] ?? null, records[0] ?? null);
4847
+ }
4848
+ };
4849
+ const startCreate = (term, baseIds) => {
4850
+ const onCreate = allowCreate && typeof allowCreate.onCreate === "function" ? allowCreate.onCreate : null;
4851
+ if (!onCreate || createPendingRef.current) return;
4852
+ createPendingRef.current = true;
4853
+ setCreatePending(true);
4854
+ setCreateError(null);
4855
+ Promise.resolve(onCreate(term)).then((created) => {
4856
+ const record = isRecordLike(created) ? created : created != null && created !== "" ? { objectId: created, name: term } : null;
4857
+ const id = getRecordId(record);
4858
+ if (id == null) return;
4859
+ setSeenRecords((prev) => upsertRecords(prev, [record]));
4860
+ const nextIds = multi ? [...baseIds.filter((v) => v !== id), id] : [id];
4861
+ commitChange(nextIds, [record]);
4862
+ }).catch((err) => {
4863
+ setCreateError((err == null ? void 0 : err.message) || "Could not create the record.");
4864
+ }).finally(() => {
4865
+ createPendingRef.current = false;
4866
+ setCreatePending(false);
4867
+ });
4868
+ };
4869
+ const handleChange = (next) => {
4870
+ if (createError) setCreateError(null);
4871
+ const { ids, create } = splitCreateSelection(next);
4872
+ const picked = ids.map((id) => recordsById.get(id)).filter(Boolean);
4873
+ if (picked.length) setSeenRecords((prev) => upsertRecords(prev, picked));
4874
+ if (create) {
4875
+ startCreate(effectiveSearch.trim(), ids);
4876
+ if (multi) commitChange(ids);
4877
+ return;
4878
+ }
4879
+ commitChange(ids);
4880
+ };
4881
+ const handleSearchInput = (next) => {
4882
+ setInputValue(next || "");
4883
+ if (onSearchChange) onSearchChange(next || "");
4884
+ };
4885
+ const commonProps = {
4886
+ name,
4887
+ label,
4888
+ value: multi ? selection.ids : selection.ids[0],
4889
+ options,
4890
+ placeholder: placeholder || (createPending ? "Creating record..." : dataSource.loading ? "Searching CRM..." : "Search CRM records..."),
4891
+ description,
4892
+ tooltip,
4893
+ required,
4894
+ readOnly: readOnly || createPending,
4895
+ error: error || !!createError || !!dataSource.error,
4896
+ validationMessage: validationMessage || createError || (typeof dataSource.error === "string" ? dataSource.error : void 0),
4897
+ variant,
4898
+ onChange: handleChange,
4899
+ ...rest
4900
+ };
4901
+ if (!multi) {
4902
+ return import_react16.default.createElement(import_ui_extensions16.Select, { ...commonProps, onInput: handleSearchInput });
4903
+ }
4904
+ return import_react16.default.createElement(
4905
+ import_ui_extensions16.Flex,
4906
+ { direction: "column", gap: "xs" },
4907
+ import_react16.default.createElement(import_ui_extensions16.SearchInput, {
4908
+ name: name ? `${name}-search` : void 0,
4909
+ label: "",
4910
+ placeholder: "Search CRM records...",
4911
+ value: inputValue,
4912
+ readOnly: readOnly || createPending,
4913
+ onInput: handleSearchInput,
4914
+ // The clear "x" emits onChange (not onInput) — wire both so clearing
4915
+ // resets the term (same pattern as CollectionToolbar).
4916
+ onChange: handleSearchInput
4917
+ }),
4918
+ import_react16.default.createElement(import_ui_extensions16.MultiSelect, commonProps)
4919
+ );
4920
+ };
4921
+
1946
4922
  // src/common-components/datePresets.js
1947
4923
  var HS_DATE_PRESETS = [
1948
4924
  { label: "Today", value: "today" },
@@ -1965,13 +4941,472 @@ var HS_DATE_DIRECTION_LABELS = {
1965
4941
  desc: "Descending"
1966
4942
  };
1967
4943
 
4944
+ // src/common-components/dateRangePresets.js
4945
+ var DATE_RANGE_CUSTOM_VALUE = "custom";
4946
+ var DATE_FILTER_OPERATORS = [
4947
+ { label: "is", value: "InRollingDateRange" },
4948
+ { label: "is equal to", value: "Equal" },
4949
+ { label: "is before", value: "BeforeDateStaticOrDynamic" },
4950
+ { label: "is after", value: "AfterDateStaticOrDynamic" },
4951
+ { label: "is between", value: "InRange" },
4952
+ { label: "is more than", value: "GreaterRolling" },
4953
+ { label: "is less than", value: "LessRolling" },
4954
+ { label: "is known", value: "Known" },
4955
+ { label: "is unknown", value: "NotKnown" }
4956
+ ];
4957
+ var DATE_ROLLING_UNIT_OPTIONS = [
4958
+ { label: "day ago", value: "day:backward" },
4959
+ { label: "days from now", value: "day:forward" },
4960
+ { label: "week ago", value: "week:backward" },
4961
+ { label: "weeks from now", value: "week:forward" },
4962
+ { label: "month ago", value: "month:backward" },
4963
+ { label: "months from now", value: "month:forward" },
4964
+ { label: "year ago", value: "year:backward" },
4965
+ { label: "years from now", value: "year:forward" }
4966
+ ];
4967
+ var toHsDateValue = (date) => {
4968
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) return null;
4969
+ return { year: date.getFullYear(), month: date.getMonth(), date: date.getDate() };
4970
+ };
4971
+ var compareHsDateValues = (a, b) => {
4972
+ if (!a || !b) return 0;
4973
+ return a.year - b.year || a.month - b.month || a.date - b.date;
4974
+ };
4975
+ var isValidDateRange = (range) => {
4976
+ if (!range) return true;
4977
+ return compareHsDateValues(range.from, range.to) <= 0;
4978
+ };
4979
+ var dayAt = (now, offset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth(), now.getDate() + offset));
4980
+ var monthStart = (now, monthOffset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth() + monthOffset, 1));
4981
+ var monthEnd = (now, monthOffset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth() + monthOffset + 1, 0));
4982
+ var quarterStartMonth = (now, quarterOffset = 0) => Math.floor(now.getMonth() / 3) * 3 + quarterOffset * 3;
4983
+ var presetToRange = (presetKey, now = /* @__PURE__ */ new Date()) => {
4984
+ if (!presetKey || presetKey === DATE_RANGE_CUSTOM_VALUE) return null;
4985
+ if (!(now instanceof Date) || Number.isNaN(now.getTime())) return null;
4986
+ const dow = now.getDay();
4987
+ const year = now.getFullYear();
4988
+ const qm = quarterStartMonth(now);
4989
+ switch (presetKey) {
4990
+ case "today":
4991
+ return { from: dayAt(now, 0), to: dayAt(now, 0) };
4992
+ case "yesterday":
4993
+ return { from: dayAt(now, -1), to: dayAt(now, -1) };
4994
+ case "tomorrow":
4995
+ return { from: dayAt(now, 1), to: dayAt(now, 1) };
4996
+ case "this_week":
4997
+ return { from: dayAt(now, -dow), to: dayAt(now, 6 - dow) };
4998
+ case "last_week":
4999
+ return { from: dayAt(now, -dow - 7), to: dayAt(now, -dow - 1) };
5000
+ case "7d":
5001
+ return { from: dayAt(now, -6), to: dayAt(now, 0) };
5002
+ case "30d":
5003
+ return { from: dayAt(now, -29), to: dayAt(now, 0) };
5004
+ case "90d":
5005
+ return { from: dayAt(now, -89), to: dayAt(now, 0) };
5006
+ case "this_month":
5007
+ return { from: monthStart(now, 0), to: monthEnd(now, 0) };
5008
+ case "last_month":
5009
+ return { from: monthStart(now, -1), to: monthEnd(now, -1) };
5010
+ case "this_quarter":
5011
+ return {
5012
+ from: toHsDateValue(new Date(year, qm, 1)),
5013
+ to: toHsDateValue(new Date(year, qm + 3, 0))
5014
+ };
5015
+ case "last_quarter":
5016
+ return {
5017
+ from: toHsDateValue(new Date(year, qm - 3, 1)),
5018
+ to: toHsDateValue(new Date(year, qm, 0))
5019
+ };
5020
+ case "this_year":
5021
+ return { from: { year, month: 0, date: 1 }, to: { year, month: 11, date: 31 } };
5022
+ case "last_year":
5023
+ return {
5024
+ from: { year: year - 1, month: 0, date: 1 },
5025
+ to: { year: year - 1, month: 11, date: 31 }
5026
+ };
5027
+ default:
5028
+ return null;
5029
+ }
5030
+ };
5031
+
5032
+ // src/common-components/DateRangePicker.js
5033
+ var import_react17 = __toESM(require("react"));
5034
+ var import_ui_extensions17 = require("@hubspot/ui-extensions");
5035
+ var h6 = import_react17.default.createElement;
5036
+ var IN_ROLLING = "InRollingDateRange";
5037
+ var EQUAL = "Equal";
5038
+ var BEFORE = "BeforeDateStaticOrDynamic";
5039
+ var AFTER = "AfterDateStaticOrDynamic";
5040
+ var GREATER_ROLLING = "GreaterRolling";
5041
+ var LESS_ROLLING = "LessRolling";
5042
+ var IN_RANGE = "InRange";
5043
+ var KNOWN = "Known";
5044
+ var NOT_KNOWN = "NotKnown";
5045
+ var EMPTY_RANGE = { from: null, to: null };
5046
+ var EMPTY_DATE = { date: null };
5047
+ var COMPACT_LABEL = "";
5048
+ var STATIC_DATE_OPERATORS = /* @__PURE__ */ new Set([EQUAL, BEFORE, AFTER]);
5049
+ var ROLLING_OPERATORS = /* @__PURE__ */ new Set([GREATER_ROLLING, LESS_ROLLING]);
5050
+ var PRESENCE_OPERATORS = /* @__PURE__ */ new Set([KNOWN, NOT_KNOWN]);
5051
+ var keyOfDate = (v) => v ? `${v.year}-${v.month}-${v.date}` : "";
5052
+ var keyOfRange = (r) => `${keyOfDate(r == null ? void 0 : r.from)}|${keyOfDate(r == null ? void 0 : r.to)}`;
5053
+ var isRangeLike = (value) => value && typeof value === "object" && ("from" in value || "to" in value) && !("operator" in value);
5054
+ var normalizeValue = (value) => {
5055
+ if (isRangeLike(value)) {
5056
+ return { operator: IN_RANGE, from: value.from ?? null, to: value.to ?? null };
5057
+ }
5058
+ if (!value || typeof value !== "object") {
5059
+ return { operator: IN_ROLLING, preset: "today" };
5060
+ }
5061
+ const operator = value.operator || IN_ROLLING;
5062
+ if (operator === IN_RANGE) {
5063
+ return { operator, from: value.from ?? null, to: value.to ?? null };
5064
+ }
5065
+ if (STATIC_DATE_OPERATORS.has(operator)) {
5066
+ return { operator, date: value.date ?? null };
5067
+ }
5068
+ if (ROLLING_OPERATORS.has(operator)) {
5069
+ return {
5070
+ operator,
5071
+ amount: Number.isFinite(Number(value.amount)) ? Number(value.amount) : 1,
5072
+ unit: value.unit || "day",
5073
+ direction: value.direction || "backward"
5074
+ };
5075
+ }
5076
+ if (PRESENCE_OPERATORS.has(operator)) {
5077
+ return { operator };
5078
+ }
5079
+ return { operator: IN_ROLLING, preset: value.preset || value.value || "today" };
5080
+ };
5081
+ var rangeFromValue = (value) => ({
5082
+ from: (value == null ? void 0 : value.from) ?? null,
5083
+ to: (value == null ? void 0 : value.to) ?? null
5084
+ });
5085
+ var getPresetOptions = (presets, customPresetLabel) => {
5086
+ const presetList = presets === true ? HS_DATE_PRESETS : Array.isArray(presets) ? presets : null;
5087
+ if (!presetList) return null;
5088
+ return [
5089
+ ...presetList.map((p) => ({ label: p.label, value: p.value })),
5090
+ ...presetList.some((p) => p.value === DATE_RANGE_CUSTOM_VALUE) ? [] : [{ label: customPresetLabel, value: DATE_RANGE_CUSTOM_VALUE }]
5091
+ ];
5092
+ };
5093
+ var DateRangePicker = ({
5094
+ value,
5095
+ defaultValue,
5096
+ onChange,
5097
+ label,
5098
+ name = "date-range",
5099
+ field,
5100
+ defaultField,
5101
+ onFieldChange,
5102
+ showFieldSelect = false,
5103
+ fieldOptions = [],
5104
+ operator,
5105
+ defaultOperator = IN_ROLLING,
5106
+ onOperatorChange,
5107
+ showOperatorSelect = true,
5108
+ operatorOptions = DATE_FILTER_OPERATORS,
5109
+ presets = true,
5110
+ rollingUnitOptions = DATE_ROLLING_UNIT_OPTIONS,
5111
+ direction = "row",
5112
+ clearable = false,
5113
+ min,
5114
+ max,
5115
+ fromLabel = "Start date",
5116
+ toLabel = "End date",
5117
+ dateLabel = "Date",
5118
+ showDateLabels = false,
5119
+ format = "medium",
5120
+ presetPlaceholder = "Enter value",
5121
+ customPresetLabel = "Custom",
5122
+ clearLabel = "Clear",
5123
+ invalidRangeMessage = "Start date must be on or before end date",
5124
+ readOnly = false,
5125
+ gap,
5126
+ gridColumnWidth = 260
5127
+ }) => {
5128
+ const isControlled = value !== void 0;
5129
+ const controlledValue = normalizeValue(value);
5130
+ const [internalValue, setInternalValue] = (0, import_react17.useState)(
5131
+ () => normalizeValue(defaultValue ?? { operator: defaultOperator })
5132
+ );
5133
+ const [internalField, setInternalField] = (0, import_react17.useState)(
5134
+ () => {
5135
+ var _a;
5136
+ return defaultField ?? ((_a = fieldOptions == null ? void 0 : fieldOptions[0]) == null ? void 0 : _a.value) ?? "";
5137
+ }
5138
+ );
5139
+ const current = normalizeValue(isControlled ? controlledValue : internalValue);
5140
+ const currentOperator = operator || current.operator || defaultOperator;
5141
+ const currentField = field !== void 0 ? field : internalField;
5142
+ const resolvedCurrent = normalizeValue({ ...current, operator: currentOperator });
5143
+ const [pending, setPending] = (0, import_react17.useState)(null);
5144
+ const [lastPreset, setLastPreset] = (0, import_react17.useState)({
5145
+ key: resolvedCurrent.preset || "",
5146
+ rangeKey: null
5147
+ });
5148
+ const isColumn = direction === "column";
5149
+ const presetOptions = getPresetOptions(presets, customPresetLabel);
5150
+ const showClear = clearable && !readOnly && (resolvedCurrent.preset || resolvedCurrent.date || resolvedCurrent.amount || resolvedCurrent.from || resolvedCurrent.to || pending);
5151
+ const emit = (next, meta = {}) => {
5152
+ const normalized = normalizeValue(next);
5153
+ if (!isControlled) setInternalValue(normalized);
5154
+ if (normalized.operator !== currentOperator) {
5155
+ onOperatorChange == null ? void 0 : onOperatorChange(normalized.operator);
5156
+ }
5157
+ onChange == null ? void 0 : onChange(normalized, {
5158
+ operator: normalized.operator,
5159
+ field: currentField || null,
5160
+ preset: normalized.operator === IN_ROLLING ? normalized.preset ?? null : null,
5161
+ ...meta
5162
+ });
5163
+ };
5164
+ const handleFieldChange = (nextField) => {
5165
+ if (field === void 0) setInternalField(nextField);
5166
+ onFieldChange == null ? void 0 : onFieldChange(nextField);
5167
+ onChange == null ? void 0 : onChange(resolvedCurrent, {
5168
+ operator: resolvedCurrent.operator,
5169
+ field: nextField || null,
5170
+ preset: resolvedCurrent.operator === IN_ROLLING ? resolvedCurrent.preset ?? null : null
5171
+ });
5172
+ };
5173
+ const handleOperatorChange = (nextOperator) => {
5174
+ setPending(null);
5175
+ if (nextOperator === IN_RANGE) {
5176
+ emit({ operator: IN_RANGE, ...EMPTY_RANGE }, { previousOperator: currentOperator });
5177
+ } else if (STATIC_DATE_OPERATORS.has(nextOperator)) {
5178
+ emit({ operator: nextOperator, ...EMPTY_DATE }, { previousOperator: currentOperator });
5179
+ } else if (ROLLING_OPERATORS.has(nextOperator)) {
5180
+ emit(
5181
+ { operator: nextOperator, amount: 1, unit: "day", direction: "backward" },
5182
+ { previousOperator: currentOperator }
5183
+ );
5184
+ } else if (PRESENCE_OPERATORS.has(nextOperator)) {
5185
+ emit({ operator: nextOperator }, { previousOperator: currentOperator });
5186
+ } else {
5187
+ emit({ operator: IN_ROLLING, preset: "today" }, { previousOperator: currentOperator });
5188
+ }
5189
+ };
5190
+ const handlePresetChange = (preset) => {
5191
+ if (!preset || preset === DATE_RANGE_CUSTOM_VALUE) {
5192
+ emit({ operator: IN_ROLLING, preset: DATE_RANGE_CUSTOM_VALUE });
5193
+ return;
5194
+ }
5195
+ const option = presets === true ? HS_DATE_PRESETS.find((p) => p.value === preset) : Array.isArray(presets) ? presets.find((p) => p.value === preset) : null;
5196
+ const range = option && typeof option.getRange === "function" ? option.getRange(/* @__PURE__ */ new Date()) : presetToRange(preset);
5197
+ setLastPreset({ key: preset, rangeKey: range ? keyOfRange(range) : null });
5198
+ emit({ operator: IN_ROLLING, preset }, { range });
5199
+ };
5200
+ const handleRollingUnitChange = (compound) => {
5201
+ const [unit, unitDirection] = String(compound || "day:backward").split(":");
5202
+ emit({
5203
+ operator: currentOperator,
5204
+ amount: resolvedCurrent.amount || 1,
5205
+ unit,
5206
+ direction: unitDirection || "backward"
5207
+ });
5208
+ };
5209
+ const handleDateChange = (side, next) => {
5210
+ const displayRange = {
5211
+ ...rangeFromValue(resolvedCurrent),
5212
+ ...pending ? { [pending.side]: pending.value } : {}
5213
+ };
5214
+ const candidate = { ...displayRange, [side]: next ?? null };
5215
+ if (isValidDateRange(candidate)) {
5216
+ setPending(null);
5217
+ setLastPreset({ key: "", rangeKey: null });
5218
+ emit({ operator: IN_RANGE, ...candidate });
5219
+ } else {
5220
+ setPending({ side, value: next ?? null });
5221
+ }
5222
+ };
5223
+ const handleStaticDateChange = (next) => {
5224
+ emit({ operator: currentOperator, date: next ?? null });
5225
+ };
5226
+ const handleClear = () => {
5227
+ setPending(null);
5228
+ setLastPreset({ key: "", rangeKey: null });
5229
+ if (currentOperator === IN_RANGE) {
5230
+ emit({ operator: IN_RANGE, ...EMPTY_RANGE });
5231
+ } else if (STATIC_DATE_OPERATORS.has(currentOperator)) {
5232
+ emit({ operator: currentOperator, ...EMPTY_DATE });
5233
+ } else if (ROLLING_OPERATORS.has(currentOperator)) {
5234
+ emit({ operator: currentOperator, amount: 1, unit: "day", direction: "backward" });
5235
+ } else if (PRESENCE_OPERATORS.has(currentOperator)) {
5236
+ emit({ operator: currentOperator });
5237
+ } else {
5238
+ emit({ operator: IN_ROLLING, preset: "" });
5239
+ }
5240
+ };
5241
+ const operatorSelect = showOperatorSelect ? h6(import_ui_extensions17.Select, {
5242
+ key: "operator",
5243
+ name: `${name}-operator`,
5244
+ label: COMPACT_LABEL,
5245
+ options: operatorOptions,
5246
+ value: currentOperator,
5247
+ onChange: handleOperatorChange,
5248
+ readOnly
5249
+ }) : null;
5250
+ const fieldSelect = showFieldSelect ? h6(import_ui_extensions17.Select, {
5251
+ key: "field",
5252
+ name: `${name}-field`,
5253
+ label: "",
5254
+ options: fieldOptions,
5255
+ value: currentField,
5256
+ onChange: handleFieldChange,
5257
+ readOnly
5258
+ }) : null;
5259
+ let valueInput = null;
5260
+ const fromInputLabel = showDateLabels ? fromLabel : COMPACT_LABEL;
5261
+ const toInputLabel = showDateLabels ? toLabel : COMPACT_LABEL;
5262
+ const singleDateInputLabel = showDateLabels ? dateLabel : COMPACT_LABEL;
5263
+ if (currentOperator === IN_RANGE) {
5264
+ const committed = rangeFromValue(resolvedCurrent);
5265
+ const display = {
5266
+ ...committed,
5267
+ ...pending ? { [pending.side]: pending.value } : {}
5268
+ };
5269
+ const invalidSide = pending ? pending.side : null;
5270
+ valueInput = [
5271
+ h6(import_ui_extensions17.DateInput, {
5272
+ key: "from",
5273
+ name: `${name}-from`,
5274
+ label: fromInputLabel,
5275
+ format,
5276
+ value: display.from ?? null,
5277
+ onChange: (next) => handleDateChange("from", next),
5278
+ min,
5279
+ max,
5280
+ readOnly,
5281
+ error: invalidSide === "from",
5282
+ validationMessage: invalidSide === "from" ? invalidRangeMessage : void 0
5283
+ }),
5284
+ isColumn ? null : h6(import_ui_extensions17.Text, { key: "to" }, "to"),
5285
+ h6(import_ui_extensions17.DateInput, {
5286
+ key: "toDate",
5287
+ name: `${name}-to`,
5288
+ label: toInputLabel,
5289
+ format,
5290
+ value: display.to ?? null,
5291
+ onChange: (next) => handleDateChange("to", next),
5292
+ min,
5293
+ max,
5294
+ readOnly,
5295
+ error: invalidSide === "to",
5296
+ validationMessage: invalidSide === "to" ? invalidRangeMessage : void 0
5297
+ })
5298
+ ];
5299
+ } else if (STATIC_DATE_OPERATORS.has(currentOperator)) {
5300
+ valueInput = h6(import_ui_extensions17.DateInput, {
5301
+ key: "date",
5302
+ name: `${name}-date`,
5303
+ label: singleDateInputLabel,
5304
+ format,
5305
+ value: resolvedCurrent.date ?? null,
5306
+ onChange: handleStaticDateChange,
5307
+ min,
5308
+ max,
5309
+ readOnly
5310
+ });
5311
+ } else if (ROLLING_OPERATORS.has(currentOperator)) {
5312
+ const compound = `${resolvedCurrent.unit || "day"}:${resolvedCurrent.direction || "backward"}`;
5313
+ valueInput = [
5314
+ h6(import_ui_extensions17.NumberInput, {
5315
+ key: "amount",
5316
+ name: `${name}-amount`,
5317
+ label: COMPACT_LABEL,
5318
+ min: 0,
5319
+ value: resolvedCurrent.amount ?? 1,
5320
+ onChange: (amount) => emit({
5321
+ operator: currentOperator,
5322
+ amount: Number.isFinite(Number(amount)) ? Number(amount) : 0,
5323
+ unit: resolvedCurrent.unit || "day",
5324
+ direction: resolvedCurrent.direction || "backward"
5325
+ }),
5326
+ readOnly
5327
+ }),
5328
+ h6(import_ui_extensions17.Select, {
5329
+ key: "unit",
5330
+ name: `${name}-rolling-unit`,
5331
+ label: COMPACT_LABEL,
5332
+ options: rollingUnitOptions,
5333
+ value: compound,
5334
+ onChange: handleRollingUnitChange,
5335
+ readOnly
5336
+ })
5337
+ ];
5338
+ } else if (PRESENCE_OPERATORS.has(currentOperator)) {
5339
+ valueInput = null;
5340
+ } else {
5341
+ const range = presetToRange(resolvedCurrent.preset);
5342
+ const presetValue = resolvedCurrent.preset || (lastPreset.rangeKey && range && lastPreset.rangeKey === keyOfRange(range) ? lastPreset.key : "");
5343
+ valueInput = h6(import_ui_extensions17.Select, {
5344
+ key: "preset",
5345
+ name: `${name}-preset`,
5346
+ label: COMPACT_LABEL,
5347
+ placeholder: presetPlaceholder,
5348
+ options: presetOptions || [],
5349
+ value: presetValue,
5350
+ onChange: handlePresetChange,
5351
+ readOnly
5352
+ });
5353
+ }
5354
+ const valueChildren = [
5355
+ ...Array.isArray(valueInput) ? valueInput : valueInput ? [valueInput] : [],
5356
+ showClear ? h6(import_ui_extensions17.Link, { key: "clear", onClick: handleClear }, clearLabel) : null
5357
+ ];
5358
+ const children = [operatorSelect, ...valueChildren];
5359
+ if (fieldSelect) {
5360
+ const rowChildren = [
5361
+ h6(import_ui_extensions17.Box, { key: "field-box", flex: "auto", alignSelf: "stretch" }, fieldSelect),
5362
+ operatorSelect ? h6(import_ui_extensions17.Box, { key: "operator-box", flex: "auto", alignSelf: "stretch" }, operatorSelect) : null,
5363
+ ...valueChildren.map(
5364
+ (child, index) => (child == null ? void 0 : child.type) === import_ui_extensions17.Text || (child == null ? void 0 : child.type) === import_ui_extensions17.Link ? child : h6(import_ui_extensions17.Box, { key: `value-box-${index}`, flex: "auto", alignSelf: "stretch" }, child)
5365
+ )
5366
+ ].filter(Boolean);
5367
+ const fieldControl = h6(
5368
+ import_ui_extensions17.AutoGrid,
5369
+ {
5370
+ columnWidth: gridColumnWidth,
5371
+ flexible: true,
5372
+ gap: gap ?? "xs"
5373
+ },
5374
+ ...rowChildren
5375
+ );
5376
+ if (!label) return fieldControl;
5377
+ return h6(
5378
+ import_ui_extensions17.Flex,
5379
+ { direction: "column", gap: "xs" },
5380
+ h6(import_ui_extensions17.Text, { format: { fontWeight: "demibold" } }, label),
5381
+ fieldControl
5382
+ );
5383
+ }
5384
+ const control = h6(
5385
+ import_ui_extensions17.Flex,
5386
+ {
5387
+ direction: isColumn ? "column" : "row",
5388
+ align: isColumn ? "stretch" : "center",
5389
+ gap: gap ?? (isColumn ? "sm" : "xs"),
5390
+ wrap: isColumn ? void 0 : "wrap"
5391
+ },
5392
+ ...children
5393
+ );
5394
+ if (!label) return control;
5395
+ return h6(
5396
+ import_ui_extensions17.Flex,
5397
+ { direction: "column", gap: "xs" },
5398
+ h6(import_ui_extensions17.Text, { format: { fontWeight: "demibold" } }, label),
5399
+ control
5400
+ );
5401
+ };
5402
+
1968
5403
  // src/common-components/KeyValueList.js
1969
- var import_react16 = __toESM(require("react"));
1970
- var import_ui_extensions16 = require("@hubspot/ui-extensions");
5404
+ var import_react18 = __toESM(require("react"));
5405
+ var import_ui_extensions18 = require("@hubspot/ui-extensions");
1971
5406
  var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
1972
5407
  const rows = items.map(
1973
- (item, index) => import_react16.default.createElement(
1974
- import_ui_extensions16.DescriptionListItem,
5408
+ (item, index) => import_react18.default.createElement(
5409
+ import_ui_extensions18.DescriptionListItem,
1975
5410
  {
1976
5411
  key: item.key ?? item.label ?? `kv-${index}`,
1977
5412
  label: item.label
@@ -1979,16 +5414,17 @@ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
1979
5414
  item.value
1980
5415
  )
1981
5416
  );
1982
- return import_react16.default.createElement(
1983
- import_ui_extensions16.Flex,
5417
+ return import_react18.default.createElement(
5418
+ import_ui_extensions18.Flex,
1984
5419
  { direction: "column", gap },
1985
- import_react16.default.createElement(import_ui_extensions16.DescriptionList, { direction }, ...rows)
5420
+ import_react18.default.createElement(import_ui_extensions18.DescriptionList, { direction }, ...rows)
1986
5421
  );
1987
5422
  };
5423
+ KeyValueList.displayName = "KeyValueList";
1988
5424
 
1989
5425
  // src/common-components/SectionHeader.js
1990
- var import_react17 = __toESM(require("react"));
1991
- var import_ui_extensions17 = require("@hubspot/ui-extensions");
5426
+ var import_react19 = __toESM(require("react"));
5427
+ var import_ui_extensions19 = require("@hubspot/ui-extensions");
1992
5428
  var SectionHeader = ({
1993
5429
  title,
1994
5430
  description,
@@ -1999,12 +5435,12 @@ var SectionHeader = ({
1999
5435
  }) => {
2000
5436
  const body = [];
2001
5437
  if (title != null) {
2002
- body.push(import_react17.default.createElement(import_ui_extensions17.Heading, { key: "title", as: titleAs }, title));
5438
+ body.push(import_react19.default.createElement(import_ui_extensions19.Heading, { key: "title", as: titleAs }, title));
2003
5439
  }
2004
5440
  if (description != null) {
2005
5441
  body.push(
2006
- import_react17.default.createElement(
2007
- import_ui_extensions17.Text,
5442
+ import_react19.default.createElement(
5443
+ import_ui_extensions19.Text,
2008
5444
  { key: "description", variant: "microcopy" },
2009
5445
  description
2010
5446
  )
@@ -2013,10 +5449,10 @@ var SectionHeader = ({
2013
5449
  if (children != null) {
2014
5450
  body.push(children);
2015
5451
  }
2016
- const content = import_react17.default.createElement(import_ui_extensions17.Flex, { direction: "column", gap }, ...body);
5452
+ const content = import_react19.default.createElement(import_ui_extensions19.Flex, { direction: "column", gap }, ...body);
2017
5453
  if (actions == null) return content;
2018
- return import_react17.default.createElement(
2019
- import_ui_extensions17.Flex,
5454
+ return import_react19.default.createElement(
5455
+ import_ui_extensions19.Flex,
2020
5456
  { direction: "row", justify: "between", align: "start", gap: "sm" },
2021
5457
  content,
2022
5458
  actions
@@ -2024,8 +5460,8 @@ var SectionHeader = ({
2024
5460
  };
2025
5461
 
2026
5462
  // src/common-components/Spinner.js
2027
- var import_react18 = __toESM(require("react"));
2028
- var import_ui_extensions18 = require("@hubspot/ui-extensions");
5463
+ var import_react20 = __toESM(require("react"));
5464
+ var import_ui_extensions20 = require("@hubspot/ui-extensions");
2029
5465
 
2030
5466
  // src/common-components/spinners.js
2031
5467
  var BRAILLE_DOT_MAP = [
@@ -2365,10 +5801,10 @@ var Spinner = ({
2365
5801
  const preset = SPINNERS[name] || SPINNERS[DEFAULT_NAME];
2366
5802
  const resolvedFrames = Array.isArray(frames) && frames.length > 0 ? frames : preset.frames;
2367
5803
  const resolvedInterval = Number.isFinite(interval) ? interval : preset.interval;
2368
- const [index, setIndex] = (0, import_react18.useState)(0);
2369
- const indexRef = (0, import_react18.useRef)(0);
5804
+ const [index, setIndex] = (0, import_react20.useState)(0);
5805
+ const indexRef = (0, import_react20.useRef)(0);
2370
5806
  indexRef.current = index;
2371
- (0, import_react18.useEffect)(() => {
5807
+ (0, import_react20.useEffect)(() => {
2372
5808
  if (paused || resolvedFrames.length <= 1) return void 0;
2373
5809
  const id = setInterval(() => {
2374
5810
  indexRef.current = (indexRef.current + 1) % resolvedFrames.length;
@@ -2376,17 +5812,17 @@ var Spinner = ({
2376
5812
  }, Math.max(16, resolvedInterval));
2377
5813
  return () => clearInterval(id);
2378
5814
  }, [paused, resolvedFrames, resolvedInterval]);
2379
- (0, import_react18.useEffect)(() => {
5815
+ (0, import_react20.useEffect)(() => {
2380
5816
  indexRef.current = 0;
2381
5817
  setIndex(0);
2382
5818
  }, [resolvedFrames]);
2383
5819
  const frame = resolvedFrames[index % resolvedFrames.length];
2384
5820
  const suffix = children != null ? children : label;
2385
5821
  if (suffix == null || suffix === "") {
2386
- return import_react18.default.createElement(import_ui_extensions18.Text, rest, frame);
5822
+ return import_react20.default.createElement(import_ui_extensions20.Text, rest, frame);
2387
5823
  }
2388
- return import_react18.default.createElement(
2389
- import_ui_extensions18.Text,
5824
+ return import_react20.default.createElement(
5825
+ import_ui_extensions20.Text,
2390
5826
  rest,
2391
5827
  frame,
2392
5828
  gap,
@@ -2399,12 +5835,18 @@ var Spinner = ({
2399
5835
  AutoStatusTag,
2400
5836
  AutoTag,
2401
5837
  AvatarStack,
5838
+ CREATE_OPTION_VALUE,
2402
5839
  CollectionCount,
2403
5840
  CollectionFilterControl,
2404
5841
  CollectionSortSelect,
2405
5842
  CollectionToolbar,
2406
5843
  CrmLookupSelect,
5844
+ CrmRecordPicker,
5845
+ DATE_FILTER_OPERATORS,
5846
+ DATE_RANGE_CUSTOM_VALUE,
5847
+ DATE_ROLLING_UNIT_OPTIONS,
2407
5848
  DEFAULT_SVG_FONT_WEIGHT,
5849
+ DateRangePicker,
2408
5850
  HS_DATE_DIRECTION_LABELS,
2409
5851
  HS_DATE_PRESETS,
2410
5852
  HS_FONT_FAMILY,
@@ -2425,16 +5867,32 @@ var Spinner = ({
2425
5867
  Icon,
2426
5868
  KeyValueList,
2427
5869
  NATIVE_ICON_NAME_LIST,
5870
+ SKELETON_FILL,
2428
5871
  SPINNERS,
2429
5872
  SPINNER_NAMES,
2430
5873
  SectionHeader,
2431
5874
  Spinner,
2432
5875
  StyledText,
5876
+ compareHsDateValues,
5877
+ enforceSelectionMax,
2433
5878
  formatCollectionCount,
5879
+ getRecordId,
2434
5880
  gridToBraille,
5881
+ isRecordLike,
5882
+ isValidDateRange,
2435
5883
  makeAvatarStackDataUri,
5884
+ makeCreateOption,
2436
5885
  makeGrid,
2437
5886
  makeIconDataUri,
2438
5887
  makeStyledTextDataUri,
2439
- svgToIconEntry
5888
+ mapIdsToRecords,
5889
+ mergePickerOptions,
5890
+ normalizeRecordSelection,
5891
+ presetToRange,
5892
+ recordToPickerOption,
5893
+ shouldShowCreateOption,
5894
+ splitCreateSelection,
5895
+ svgToIconEntry,
5896
+ toHsDateValue,
5897
+ upsertRecords
2440
5898
  });