hs-uix 2.1.1 → 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 +355 -57
  4. package/dist/calendar.mjs +356 -57
  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 +3208 -287
  18. package/dist/index.mjs +3156 -283
  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 +74 -5
  31. package/src/calendar/index.d.ts +95 -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
package/dist/index.mjs CHANGED
@@ -267,6 +267,7 @@ var HS_TEXT_COLOR = "#33475b";
267
267
  var HS_SUBTLE_BG = "#F5F8FA";
268
268
  var HS_MUTED_TEXT = "#7C98B6";
269
269
  var HS_NEUTRAL_CHIP = "#CBD6E2";
270
+ var SKELETON_FILL = "#DFE3EB";
270
271
  var HS_TAG_SUBTLE_BORDER = "#7C98B6";
271
272
  var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
272
273
  var HS_TAG_FONT_SIZE = 12;
@@ -529,7 +530,7 @@ var GENERATED_ICONS = {
529
530
  "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"] }
530
531
  };
531
532
 
532
- // src/common-components/Icon.js
533
+ // src/common-components/nativeIconNames.js
533
534
  var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
534
535
  "add",
535
536
  "appointment",
@@ -541,12 +542,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
541
542
  "block",
542
543
  "book",
543
544
  "bulb",
545
+ "callTranscript",
544
546
  "calling",
545
547
  "callingHangup",
546
548
  "callingMade",
547
549
  "callingMissed",
548
550
  "callingVoicemail",
549
- "callTranscript",
550
551
  "campaigns",
551
552
  "cap",
552
553
  "checkCircle",
@@ -575,13 +576,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
575
576
  "enroll",
576
577
  "exclamation",
577
578
  "exclamationCircle",
578
- "facebook",
579
579
  "faceHappy",
580
580
  "faceHappyFilled",
581
581
  "faceNeutral",
582
582
  "faceNeutralFilled",
583
583
  "faceSad",
584
584
  "faceSadFilled",
585
+ "facebook",
585
586
  "favoriteHollow",
586
587
  "file",
587
588
  "filledXCircleIcon",
@@ -722,6 +723,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
722
723
  "zoomIn",
723
724
  "zoomOut"
724
725
  ]);
726
+
727
+ // src/common-components/Icon.js
725
728
  var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
726
729
  var NATIVE_SIZE_TOKENS = {
727
730
  sm: "sm",
@@ -963,6 +966,7 @@ var CollectionFilterControl = ({
963
966
  { key: name, direction: "row", align: "center", gap: "xs" },
964
967
  h3(DateInput, {
965
968
  name: `${controlName}-from`,
969
+ label: filter.fromLabel ?? labels.dateFrom,
966
970
  placeholder: filter.fromLabel ?? labels.dateFrom,
967
971
  format: "medium",
968
972
  value: rangeValue.from ?? null,
@@ -971,6 +975,7 @@ var CollectionFilterControl = ({
971
975
  h3(Icon, { name: "right", size: "sm" }),
972
976
  h3(DateInput, {
973
977
  name: `${controlName}-to`,
978
+ label: filter.toLabel ?? labels.dateTo,
974
979
  placeholder: filter.toLabel ?? labels.dateTo,
975
980
  format: "medium",
976
981
  value: rangeValue.to ?? null,
@@ -1109,6 +1114,45 @@ var editValidationError = (result) => {
1109
1114
  return typeof result === "string" ? result : "Invalid value";
1110
1115
  };
1111
1116
 
1117
+ // src/datatable/rowExpansion.js
1118
+ var extractRowId = (row, rowIdField = "id", fallback = void 0) => {
1119
+ const id = row == null ? void 0 : row[rowIdField];
1120
+ return id != null ? id : fallback;
1121
+ };
1122
+ var normalizeExpandedIds = (ids) => {
1123
+ if (ids == null) return /* @__PURE__ */ new Set();
1124
+ const list = ids instanceof Set ? [...ids] : Array.isArray(ids) ? ids : [ids];
1125
+ return new Set(list.filter((id) => id != null));
1126
+ };
1127
+ var expandRowId = (expandedIds, rowId, expandSingle = false) => {
1128
+ if (rowId == null) return expandedIds;
1129
+ if (expandSingle) return /* @__PURE__ */ new Set([rowId]);
1130
+ const next = new Set(expandedIds);
1131
+ next.add(rowId);
1132
+ return next;
1133
+ };
1134
+ var collapseRowId = (expandedIds, rowId) => {
1135
+ if (rowId == null || !expandedIds.has(rowId)) return expandedIds;
1136
+ const next = new Set(expandedIds);
1137
+ next.delete(rowId);
1138
+ return next;
1139
+ };
1140
+ var toggleExpandedId = (expandedIds, rowId, expandSingle = false) => {
1141
+ if (rowId == null) return expandedIds;
1142
+ return expandedIds.has(rowId) ? collapseRowId(expandedIds, rowId) : expandRowId(expandedIds, rowId, expandSingle);
1143
+ };
1144
+ var withDetailRows = (items, expandedIds, rowIdField = "id") => {
1145
+ if (!expandedIds || expandedIds.size === 0) return items;
1146
+ const out = [];
1147
+ items.forEach((item) => {
1148
+ out.push(item);
1149
+ if (item.type === "data" && expandedIds.has(extractRowId(item.row, rowIdField))) {
1150
+ out.push({ type: "detail", row: item.row });
1151
+ }
1152
+ });
1153
+ return out;
1154
+ };
1155
+
1112
1156
  // src/datatable/DataTable.jsx
1113
1157
  import {
1114
1158
  Box as Box2,
@@ -1348,6 +1392,21 @@ var DataTable = ({
1348
1392
  hideRowActionsWhenSelectionActive = false,
1349
1393
  // hide row action column while selected-row action bar is visible
1350
1394
  // -----------------------------------------------------------------------
1395
+ // Row expansion (detail rows)
1396
+ // -----------------------------------------------------------------------
1397
+ renderExpandedRow,
1398
+ // (row) => ReactNode — providing this enables the feature
1399
+ expandedRowIds: externalExpandedRowIds,
1400
+ // controlled — array of expanded row IDs
1401
+ defaultExpandedRowIds,
1402
+ // uncontrolled — initially expanded row IDs
1403
+ onExpandedRowsChange,
1404
+ // (expandedRowIds[]) => void
1405
+ expandOn = "icon",
1406
+ // "icon" (chevron toggle column) | "row" (click row content)
1407
+ expandSingle = false,
1408
+ // accordion mode — expanding a row collapses the others
1409
+ // -----------------------------------------------------------------------
1351
1410
  // Inline editing
1352
1411
  // -----------------------------------------------------------------------
1353
1412
  editMode,
@@ -1592,6 +1651,25 @@ var DataTable = ({
1592
1651
  });
1593
1652
  return rows;
1594
1653
  }, [groupedData, paginatedRows, expandedGroups]);
1654
+ const expandable = typeof renderExpandedRow === "function";
1655
+ const showExpandColumn = expandable && expandOn === "icon";
1656
+ const [internalExpandedRowIds, setInternalExpandedRowIds] = useState2(
1657
+ () => normalizeExpandedIds(defaultExpandedRowIds)
1658
+ );
1659
+ const expandedRowIds = useMemo(
1660
+ () => externalExpandedRowIds != null ? normalizeExpandedIds(externalExpandedRowIds) : internalExpandedRowIds,
1661
+ [externalExpandedRowIds, internalExpandedRowIds]
1662
+ );
1663
+ const toggleRowExpanded = useCallback2((rowId) => {
1664
+ if (rowId == null) return;
1665
+ const next = toggleExpandedId(expandedRowIds, rowId, expandSingle);
1666
+ if (externalExpandedRowIds == null) setInternalExpandedRowIds(next);
1667
+ if (onExpandedRowsChange) onExpandedRowsChange([...next]);
1668
+ }, [expandedRowIds, expandSingle, externalExpandedRowIds, onExpandedRowsChange]);
1669
+ const renderedRows = useMemo(() => {
1670
+ if (!expandable) return displayRows;
1671
+ return withDetailRows(displayRows, expandedRowIds, rowIdField);
1672
+ }, [expandable, displayRows, expandedRowIds, rowIdField]);
1595
1673
  const footerData = serverSide ? data : filteredData;
1596
1674
  const activeChips = useMemo(
1597
1675
  () => buildActiveFilterChips(filters, filterValues),
@@ -1743,6 +1821,7 @@ var DataTable = ({
1743
1821
  const type = col.editType || "text";
1744
1822
  const rowId = row[rowIdField];
1745
1823
  const fieldName = `edit-${rowId}-${col.field}`;
1824
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
1746
1825
  const commit = (val) => commitEdit(row, col.field, val);
1747
1826
  const exitEdit = () => {
1748
1827
  if (editError) return;
@@ -1783,15 +1862,15 @@ var DataTable = ({
1783
1862
  case "multiselect":
1784
1863
  return /* @__PURE__ */ React6.createElement(MultiSelect2, { ...extra, name: fieldName, label: "", value: editValue || [], onChange: commit, options: resolveEditOptions(col, data) });
1785
1864
  case "date":
1786
- return /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
1865
+ return /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
1787
1866
  case "time":
1788
- return /* @__PURE__ */ React6.createElement(TimeInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
1867
+ return /* @__PURE__ */ React6.createElement(TimeInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
1789
1868
  case "datetime":
1790
- return /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
1869
+ return /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
1791
1870
  const next = { ...editValue, date: val };
1792
1871
  handleInput(next);
1793
1872
  commitEdit(row, col.field, next, { keepEditing: true });
1794
- }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ React6.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
1873
+ }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ React6.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
1795
1874
  const next = { ...editValue, time: val };
1796
1875
  handleInput(next);
1797
1876
  commitEdit(row, col.field, next, { keepEditing: true });
@@ -1805,7 +1884,8 @@ var DataTable = ({
1805
1884
  }
1806
1885
  };
1807
1886
  const resolvedEditMode = editMode || (columns.some((col) => col.editable) ? "discrete" : null);
1808
- const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || !renderRow;
1887
+ const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || expandable || !renderRow;
1888
+ const totalColumnCount = columns.length + (selectable ? 1 : 0) + (showExpandColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
1809
1889
  const autoWidths = useMemo(
1810
1890
  () => autoWidth ? computeAutoWidths(columns, data) : {},
1811
1891
  [columns, data, autoWidth]
@@ -1824,6 +1904,7 @@ var DataTable = ({
1824
1904
  const type = col.editType || "text";
1825
1905
  const rowId = row[rowIdField];
1826
1906
  const fieldName = `inline-${rowId}-${col.field}`;
1907
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
1827
1908
  const cellKey = `${rowId}-${col.field}`;
1828
1909
  const value = row[col.field];
1829
1910
  const validate = col.editValidate;
@@ -1872,13 +1953,13 @@ var DataTable = ({
1872
1953
  case "multiselect":
1873
1954
  return /* @__PURE__ */ React6.createElement(MultiSelect2, { ...extra, name: fieldName, label: "", value: value || [], onChange: fire, options: resolveEditOptions(col, data) });
1874
1955
  case "date":
1875
- return /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: fieldName, label: "", value, onChange: fire });
1956
+ return /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
1876
1957
  case "time":
1877
- return /* @__PURE__ */ React6.createElement(TimeInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
1958
+ return /* @__PURE__ */ React6.createElement(TimeInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
1878
1959
  case "datetime":
1879
- return /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: "", value: value == null ? void 0 : value.date, onChange: (val) => {
1960
+ return /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: value == null ? void 0 : value.date, onChange: (val) => {
1880
1961
  fire({ ...value, date: val });
1881
- } }), /* @__PURE__ */ React6.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: value == null ? void 0 : value.time, onChange: (val) => {
1962
+ } }), /* @__PURE__ */ React6.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: value == null ? void 0 : value.time, onChange: (val) => {
1882
1963
  fire({ ...value, time: val });
1883
1964
  } }));
1884
1965
  case "toggle":
@@ -2032,7 +2113,7 @@ var DataTable = ({
2032
2113
  checked: allVisibleSelected,
2033
2114
  onChange: handleSelectAll
2034
2115
  }
2035
- )), columns.map((col) => {
2116
+ )), showExpandColumn && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }), columns.map((col) => {
2036
2117
  const headerAlign = resolvedEditMode === "inline" && col.editable ? void 0 : col.align;
2037
2118
  return /* @__PURE__ */ React6.createElement(
2038
2119
  TableHeader,
@@ -2046,8 +2127,8 @@ var DataTable = ({
2046
2127
  col.description ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, col.label, "\xA0", /* @__PURE__ */ React6.createElement(Link2, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React6.createElement(Tooltip, null, col.description) }, /* @__PURE__ */ React6.createElement(Icon, { name: "info", screenReaderText: typeof col.description === "string" ? col.description : void 0 }))) : col.label
2047
2128
  );
2048
2129
  }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }))),
2049
- /* @__PURE__ */ React6.createElement(TableBody, null, displayRows.map(
2050
- (item, idx) => item.type === "group-header" ? /* @__PURE__ */ React6.createElement(TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }), columns.map((col, colIdx) => {
2130
+ /* @__PURE__ */ React6.createElement(TableBody, null, renderedRows.map(
2131
+ (item, idx) => item.type === "group-header" ? /* @__PURE__ */ React6.createElement(TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }), showExpandColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }), columns.map((col, colIdx) => {
2051
2132
  var _a, _b, _c;
2052
2133
  return /* @__PURE__ */ React6.createElement(TableCell, { key: col.field, width: getCellWidth(col), align: colIdx === 0 ? void 0 : col.align }, colIdx === 0 ? /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(
2053
2134
  Icon,
@@ -2064,7 +2145,7 @@ var DataTable = ({
2064
2145
  },
2065
2146
  /* @__PURE__ */ React6.createElement(Text2, { format: { fontWeight: "demibold" } }, item.group.label)
2066
2147
  )) : ((_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]) ?? "");
2067
- }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" })) : useColumnRendering ? /* @__PURE__ */ React6.createElement(TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React6.createElement(
2148
+ }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" })) : item.type === "detail" ? /* @__PURE__ */ React6.createElement(TableRow, { key: `detail-${item.row[rowIdField] ?? idx}` }, /* @__PURE__ */ React6.createElement(TableCell, { colSpan: totalColumnCount }, renderExpandedRow(item.row))) : useColumnRendering ? /* @__PURE__ */ React6.createElement(TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React6.createElement(
2068
2149
  Checkbox,
2069
2150
  {
2070
2151
  name: `select-${item.row[rowIdField]}`,
@@ -2072,13 +2153,22 @@ var DataTable = ({
2072
2153
  checked: selectedIds.has(item.row[rowIdField]),
2073
2154
  onChange: (checked) => handleSelectRow(item.row[rowIdField], checked)
2074
2155
  }
2156
+ )), showExpandColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React6.createElement(
2157
+ Icon,
2158
+ {
2159
+ name: expandedRowIds.has(item.row[rowIdField]) ? "upCarat" : "downCarat",
2160
+ onClick: () => toggleRowExpanded(item.row[rowIdField]),
2161
+ screenReaderText: expandedRowIds.has(item.row[rowIdField]) ? "Collapse row" : "Expand row"
2162
+ }
2075
2163
  )), columns.map((col) => {
2076
2164
  const rowId = item.row[rowIdField];
2077
2165
  const isDiscreteEditing = resolvedEditMode === "discrete" && (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
2078
2166
  const isRowEditing = editingRowId != null && rowId === editingRowId && col.editable;
2079
2167
  const isShowingInput = isDiscreteEditing || isRowEditing || resolvedEditMode === "inline" && col.editable;
2080
2168
  const cellAlign = isShowingInput ? void 0 : col.align;
2081
- return /* @__PURE__ */ React6.createElement(TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, renderCellContent(item.row, col));
2169
+ const cellContent = renderCellContent(item.row, col);
2170
+ const wrapInRowToggle = expandable && expandOn === "row" && !col.editable && !isShowingInput;
2171
+ return /* @__PURE__ */ React6.createElement(TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, wrapInRowToggle ? /* @__PURE__ */ React6.createElement(Link2, { variant: "dark", onClick: () => toggleRowExpanded(rowId) }, cellContent) : cellContent);
2082
2172
  }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, (() => {
2083
2173
  const resolvedRowActions = typeof rowActions === "function" ? rowActions(item.row) : rowActions;
2084
2174
  const actions = Array.isArray(resolvedRowActions) ? resolvedRowActions : [];
@@ -2095,13 +2185,14 @@ var DataTable = ({
2095
2185
  ));
2096
2186
  })()))) : renderRow(item.row)
2097
2187
  )),
2098
- (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ React6.createElement(TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ React6.createElement(TableRow, null, selectable && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }), columns.map((col) => {
2188
+ (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ React6.createElement(TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ React6.createElement(TableRow, null, selectable && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }), showExpandColumn && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }), columns.map((col) => {
2099
2189
  const footerDef = col.footer;
2100
2190
  const content = typeof footerDef === "function" ? footerDef(footerData) : footerDef || "";
2101
2191
  return /* @__PURE__ */ React6.createElement(TableHeader, { key: col.field, align: col.align }, content);
2102
2192
  }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" })))
2103
2193
  ));
2104
2194
  };
2195
+ DataTable.displayName = "DataTable";
2105
2196
 
2106
2197
  // src/form/FormBuilder.jsx
2107
2198
  import React7, {
@@ -2140,7 +2231,12 @@ import {
2140
2231
  TimeInput as TimeInput2,
2141
2232
  Toggle as Toggle2,
2142
2233
  Checkbox as Checkbox2,
2143
- ToggleGroup
2234
+ ToggleGroup,
2235
+ Modal,
2236
+ ModalBody,
2237
+ ModalFooter,
2238
+ LoadingSpinner,
2239
+ useExtensionActions
2144
2240
  } from "@hubspot/ui-extensions";
2145
2241
  import {
2146
2242
  CrmPropertyList,
@@ -2467,8 +2563,25 @@ var resolveDependentCascade = ({ name, value, fields, values, getEmptyValueForFi
2467
2563
  return { newValues, changedDependents };
2468
2564
  };
2469
2565
 
2566
+ // src/form/formDirty.js
2567
+ var isFormDirty = (values, initialValues) => !deepEqual(values || {}, initialValues || {});
2568
+ var getDirtyFields = (values, initialValues) => {
2569
+ const current = values || {};
2570
+ const baseline = initialValues || {};
2571
+ const names = [...Object.keys(current)];
2572
+ for (const name of Object.keys(baseline)) {
2573
+ if (!Object.prototype.hasOwnProperty.call(current, name)) names.push(name);
2574
+ }
2575
+ return names.filter((name) => !deepEqual(current[name], baseline[name]));
2576
+ };
2577
+
2470
2578
  // src/form/FormBuilder.jsx
2471
2579
  var getRepeaterErrorKey = (fieldName, rowIdx, subFieldName) => `${fieldName}[${rowIdx}].${subFieldName}`;
2580
+ var withFieldLoadingSpinner = (element, loading) => {
2581
+ if (!loading) return element;
2582
+ return /* @__PURE__ */ React7.createElement(Flex5, { direction: "row", gap: "xs", align: "end" }, /* @__PURE__ */ React7.createElement(Box3, { flex: 1 }, element), /* @__PURE__ */ React7.createElement(LoadingSpinner, { size: "xs", layout: "inline" }));
2583
+ };
2584
+ var formBuilderInstanceCounter = 0;
2472
2585
  var fieldSetHasErrors = (errors, fields) => {
2473
2586
  if (!errors || !fields || fields.length === 0) return false;
2474
2587
  const names = new Set(fields.map((field) => field.name));
@@ -2568,8 +2681,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2568
2681
  // controlled loading state
2569
2682
  disabled = false,
2570
2683
  // disable entire form
2571
- renderButtons: renderButtonsProp
2684
+ renderButtons: renderButtonsProp,
2572
2685
  // custom action row renderer
2686
+ confirmDiscard
2687
+ // true | { title, message, confirmLabel, cancelLabel } — confirm before the built-in Cancel discards dirty changes
2573
2688
  } = props;
2574
2689
  const {
2575
2690
  columns = 1,
@@ -2688,6 +2803,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2688
2803
  "readOnly",
2689
2804
  "alwaysEditable",
2690
2805
  "disabled",
2806
+ "loading",
2691
2807
  "defaultValue",
2692
2808
  "fieldProps",
2693
2809
  "colSpan",
@@ -2944,7 +3060,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2944
3060
  syncDirtyBaseline(values);
2945
3061
  }, [values, syncDirtyBaseline]);
2946
3062
  const isDirty = useMemo2(() => {
2947
- return !deepEqual(formValues, initialSnapshot.current);
3063
+ return isFormDirty(formValues, initialSnapshot.current);
2948
3064
  }, [formValues]);
2949
3065
  const prevDirtyRef = useRef3(false);
2950
3066
  useEffect3(() => {
@@ -2953,6 +3069,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2953
3069
  if (onDirtyChange) onDirtyChange(isDirty);
2954
3070
  }
2955
3071
  }, [isDirty, onDirtyChange]);
3072
+ let hostActions = null;
3073
+ try {
3074
+ hostActions = useExtensionActions();
3075
+ } catch (err) {
3076
+ hostActions = null;
3077
+ }
3078
+ const discardModalIdRef = useRef3(null);
3079
+ if (discardModalIdRef.current === null) {
3080
+ formBuilderInstanceCounter += 1;
3081
+ discardModalIdRef.current = `hs-uix-form-discard-${formBuilderInstanceCounter}`;
3082
+ }
3083
+ const discardConfig = confirmDiscard ? confirmDiscard === true ? {} : confirmDiscard : null;
3084
+ const discardTitle = discardConfig && discardConfig.title || "Discard changes?";
3085
+ const discardMessage = discardConfig && discardConfig.message || "You have unsaved changes. If you discard now, they will be lost.";
3086
+ const discardConfirmLabel = discardConfig && discardConfig.confirmLabel || "Discard changes";
3087
+ const discardCancelLabel = discardConfig && discardConfig.cancelLabel || "Keep editing";
3088
+ const closeDiscardModal = useCallback3(() => {
3089
+ if (hostActions && typeof hostActions.closeOverlay === "function") {
3090
+ hostActions.closeOverlay(discardModalIdRef.current);
3091
+ }
3092
+ }, [hostActions]);
2956
3093
  const autoSaveTimerRef = useRef3(null);
2957
3094
  const autoSaveRef = useRef3(autoSave);
2958
3095
  autoSaveRef.current = autoSave;
@@ -3591,6 +3728,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
3591
3728
  },
3592
3729
  getValues: () => formValues,
3593
3730
  isDirty: () => isDirty,
3731
+ getDirtyFields: () => getDirtyFields(formValuesRef.current, initialSnapshot.current),
3594
3732
  setFieldValue: (name, value) => handleFieldChange(name, value),
3595
3733
  setFieldError: (name, message) => updateErrors({ [name]: message }),
3596
3734
  setErrors: (errors) => {
@@ -3610,7 +3748,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
3610
3748
  const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
3611
3749
  const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
3612
3750
  const isReadOnly = field.readOnly || fieldFormReadOnly;
3613
- const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
3751
+ const isFieldLoading = !!field.loading;
3752
+ const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly || isFieldLoading;
3614
3753
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
3615
3754
  if (field.type === "display" || field.type === "slot") {
3616
3755
  if (field.render) {
@@ -3794,7 +3933,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
3794
3933
  disabled: isDisabled,
3795
3934
  error: hasError,
3796
3935
  validationMessage: renderFieldError ? void 0 : fieldError || void 0,
3797
- ...field.loading || validatingFields[field.name] ? { loading: true } : {},
3936
+ ...validatingFields[field.name] ? { loading: true } : {},
3798
3937
  ...field.fieldProps || {}
3799
3938
  };
3800
3939
  const options = resolveOptions(field, formValues);
@@ -3952,25 +4091,31 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
3952
4091
  )));
3953
4092
  }
3954
4093
  case "select":
3955
- return /* @__PURE__ */ React7.createElement(
3956
- Select3,
3957
- {
3958
- ...commonProps,
3959
- value: fieldValue,
3960
- options,
3961
- variant: field.variant,
3962
- onChange: fieldOnChange
3963
- }
4094
+ return withFieldLoadingSpinner(
4095
+ /* @__PURE__ */ React7.createElement(
4096
+ Select3,
4097
+ {
4098
+ ...commonProps,
4099
+ value: fieldValue,
4100
+ options,
4101
+ variant: field.variant,
4102
+ onChange: fieldOnChange
4103
+ }
4104
+ ),
4105
+ isFieldLoading
3964
4106
  );
3965
4107
  case "multiselect":
3966
- return /* @__PURE__ */ React7.createElement(
3967
- MultiSelect3,
3968
- {
3969
- ...commonProps,
3970
- value: fieldValue || [],
3971
- options,
3972
- onChange: fieldOnChange
3973
- }
4108
+ return withFieldLoadingSpinner(
4109
+ /* @__PURE__ */ React7.createElement(
4110
+ MultiSelect3,
4111
+ {
4112
+ ...commonProps,
4113
+ value: fieldValue || [],
4114
+ options,
4115
+ onChange: fieldOnChange
4116
+ }
4117
+ ),
4118
+ isFieldLoading
3974
4119
  );
3975
4120
  case "toggle":
3976
4121
  return /* @__PURE__ */ React7.createElement(
@@ -4491,6 +4636,30 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
4491
4636
  if (layout) return renderExplicitLayout();
4492
4637
  return renderFieldSubset(visibleFields);
4493
4638
  };
4639
+ const renderCancelButton = () => {
4640
+ if (discardConfig && isDirty) {
4641
+ return /* @__PURE__ */ React7.createElement(
4642
+ Button4,
4643
+ {
4644
+ variant: "secondary",
4645
+ disabled,
4646
+ overlay: /* @__PURE__ */ React7.createElement(Modal, { id: discardModalIdRef.current, title: discardTitle, width: "small" }, /* @__PURE__ */ React7.createElement(ModalBody, null, /* @__PURE__ */ React7.createElement(Text3, null, discardMessage)), /* @__PURE__ */ React7.createElement(ModalFooter, null, /* @__PURE__ */ React7.createElement(Button4, { variant: "secondary", onClick: closeDiscardModal }, discardCancelLabel), /* @__PURE__ */ React7.createElement(
4647
+ Button4,
4648
+ {
4649
+ variant: "destructive",
4650
+ onClick: () => {
4651
+ closeDiscardModal();
4652
+ if (onCancel) onCancel();
4653
+ }
4654
+ },
4655
+ discardConfirmLabel
4656
+ )))
4657
+ },
4658
+ cancelButtonLabel
4659
+ );
4660
+ }
4661
+ return /* @__PURE__ */ React7.createElement(Button4, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel);
4662
+ };
4494
4663
  const renderButtons = () => {
4495
4664
  if (submitPosition === "none" || formReadOnly) return null;
4496
4665
  const isLastStep = !isMultiStep || currentStep === steps.length - 1;
@@ -4504,6 +4673,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
4504
4673
  totalSteps: isMultiStep ? steps.length : 1,
4505
4674
  disabled,
4506
4675
  loading: isLoading,
4676
+ isDirty,
4507
4677
  labels: {
4508
4678
  submit: submitButtonLabel,
4509
4679
  cancel: cancelButtonLabel,
@@ -4519,7 +4689,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
4519
4689
  return renderButtonsProp(buttonContext);
4520
4690
  }
4521
4691
  if (isMultiStep) {
4522
- return /* @__PURE__ */ React7.createElement(Flex5, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ React7.createElement(Button4, { variant: "secondary", onClick: handleBack, disabled }, backButtonLabel) : showCancel ? /* @__PURE__ */ React7.createElement(Button4, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel) : /* @__PURE__ */ React7.createElement(Text3, null, " "), /* @__PURE__ */ React7.createElement(Inline, { gap: "small" }, /* @__PURE__ */ React7.createElement(Text3, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ React7.createElement(
4692
+ return /* @__PURE__ */ React7.createElement(Flex5, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ React7.createElement(Button4, { variant: "secondary", onClick: handleBack, disabled }, backButtonLabel) : showCancel ? renderCancelButton() : /* @__PURE__ */ React7.createElement(Text3, null, " "), /* @__PURE__ */ React7.createElement(Inline, { gap: "small" }, /* @__PURE__ */ React7.createElement(Text3, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ React7.createElement(
4523
4693
  LoadingButton,
4524
4694
  {
4525
4695
  variant: submitVariant,
@@ -4530,7 +4700,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
4530
4700
  submitButtonLabel
4531
4701
  ) : /* @__PURE__ */ React7.createElement(Button4, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
4532
4702
  }
4533
- return /* @__PURE__ */ React7.createElement(Flex5, { direction: "row", justify: singleStepJustify, gap: "sm" }, showCancel && /* @__PURE__ */ React7.createElement(Button4, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ React7.createElement(
4703
+ return /* @__PURE__ */ React7.createElement(Flex5, { direction: "row", justify: singleStepJustify, gap: "sm" }, showCancel && renderCancelButton(), /* @__PURE__ */ React7.createElement(
4534
4704
  LoadingButton,
4535
4705
  {
4536
4706
  variant: submitVariant,
@@ -4570,10 +4740,231 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
4570
4740
  formContent
4571
4741
  );
4572
4742
  });
4743
+ FormBuilder.displayName = "FormBuilder";
4744
+
4745
+ // src/form/hubspotSchema.js
4746
+ var coerceBool = (value) => value === true || value === "true" || value === "Yes" || value === "yes" || value === "1";
4747
+ var coerceNumber = (value) => {
4748
+ if (value === null || value === void 0 || value === "") return void 0;
4749
+ if (typeof value === "number") return value;
4750
+ const parsed = Number(value);
4751
+ return Number.isNaN(parsed) ? void 0 : parsed;
4752
+ };
4753
+ var mapPropertyOptions = (options) => {
4754
+ if (!Array.isArray(options)) return void 0;
4755
+ const mapped = options.filter((opt) => opt && opt.hidden !== true).map((opt) => {
4756
+ const result = { label: opt.label != null ? opt.label : String(opt.value), value: opt.value };
4757
+ if (opt.description != null && opt.description !== "") result.description = opt.description;
4758
+ return result;
4759
+ });
4760
+ return mapped;
4761
+ };
4762
+ var resolveFieldType = (property) => {
4763
+ const { type, fieldType } = property;
4764
+ if (type === "datetime") return "datetime";
4765
+ if (type === "date" || fieldType === "date") return "date";
4766
+ switch (fieldType) {
4767
+ case "select":
4768
+ return "select";
4769
+ // FormBuilder's radio rendering is the "radioGroup" type (native
4770
+ // ToggleGroup with toggleType="radioButtonList").
4771
+ case "radio":
4772
+ return "radioGroup";
4773
+ // HubSpot "checkbox" = multiple checkboxes over an enumeration → multiselect.
4774
+ case "checkbox":
4775
+ return "multiselect";
4776
+ case "booleancheckbox":
4777
+ return "toggle";
4778
+ case "number":
4779
+ return "number";
4780
+ case "textarea":
4781
+ return "textarea";
4782
+ case "text":
4783
+ case "phonenumber":
4784
+ return "text";
4785
+ default:
4786
+ break;
4787
+ }
4788
+ switch (type) {
4789
+ case "enumeration":
4790
+ return "select";
4791
+ case "number":
4792
+ return "number";
4793
+ case "bool":
4794
+ return "toggle";
4795
+ default:
4796
+ return "text";
4797
+ }
4798
+ };
4799
+ var isPropertyReadOnly = (property) => property.calculated === true || property.modificationMetadata && property.modificationMetadata.readOnlyValue === true;
4800
+ var resolveRequiredOverride = (requiredOverrides, name) => {
4801
+ if (!requiredOverrides) return void 0;
4802
+ if (Array.isArray(requiredOverrides)) {
4803
+ return requiredOverrides.includes(name) ? true : void 0;
4804
+ }
4805
+ if (Object.prototype.hasOwnProperty.call(requiredOverrides, name)) {
4806
+ return !!requiredOverrides[name];
4807
+ }
4808
+ return void 0;
4809
+ };
4810
+ var fieldsFromHubSpotProperties = (properties, options = {}) => {
4811
+ if (!Array.isArray(properties)) return [];
4812
+ const {
4813
+ include,
4814
+ exclude,
4815
+ overrides,
4816
+ requiredOverrides,
4817
+ includeDescriptions = false
4818
+ } = options;
4819
+ const includeSet = Array.isArray(include) ? new Set(include) : null;
4820
+ const excludeSet = Array.isArray(exclude) ? new Set(exclude) : null;
4821
+ let selected = properties.filter((property) => {
4822
+ if (!property || !property.name) return false;
4823
+ if (includeSet && !includeSet.has(property.name)) return false;
4824
+ if (excludeSet && excludeSet.has(property.name)) return false;
4825
+ if (property.hidden === true && !includeSet) return false;
4826
+ return true;
4827
+ });
4828
+ if (includeSet) {
4829
+ const order = new Map(include.map((name, idx) => [name, idx]));
4830
+ selected = [...selected].sort((a, b) => order.get(a.name) - order.get(b.name));
4831
+ }
4832
+ return selected.map((property) => {
4833
+ const type = resolveFieldType(property);
4834
+ const field = {
4835
+ name: property.name,
4836
+ type,
4837
+ label: property.label || property.name
4838
+ };
4839
+ if (includeDescriptions && property.description) {
4840
+ field.description = property.description;
4841
+ }
4842
+ if (type === "select" || type === "multiselect" || type === "radioGroup") {
4843
+ field.options = mapPropertyOptions(property.options) || [];
4844
+ }
4845
+ if (type === "toggle") {
4846
+ field.transformIn = coerceBool;
4847
+ field.transformOut = (value) => !!value;
4848
+ }
4849
+ if (type === "number") {
4850
+ field.transformIn = coerceNumber;
4851
+ }
4852
+ if (isPropertyReadOnly(property)) {
4853
+ field.readOnly = true;
4854
+ }
4855
+ const required = resolveRequiredOverride(requiredOverrides, property.name);
4856
+ if (required !== void 0) field.required = required;
4857
+ const override = overrides && overrides[property.name];
4858
+ return override ? { ...field, ...override } : field;
4859
+ });
4860
+ };
4573
4861
 
4574
4862
  // src/kanban/Kanban.jsx
4575
4863
  import React10, { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
4576
4864
 
4865
+ // src/kanban/kanbanLanes.js
4866
+ var UNASSIGNED_LANE_KEY = "__unassigned";
4867
+ var UNKNOWN_STAGE_KEY = "__unknown";
4868
+ var getLaneKey = (row, swimlaneBy) => {
4869
+ const raw = typeof swimlaneBy === "function" ? swimlaneBy(row) : row == null ? void 0 : row[swimlaneBy];
4870
+ if (raw == null || raw === "") return UNASSIGNED_LANE_KEY;
4871
+ return String(raw);
4872
+ };
4873
+ var orderLaneKeys = (seenKeys, swimlaneOrder) => {
4874
+ const seen = Array.isArray(seenKeys) ? seenKeys : [];
4875
+ if (!Array.isArray(swimlaneOrder) || swimlaneOrder.length === 0) return [...seen];
4876
+ const explicit = [];
4877
+ for (const key of swimlaneOrder) {
4878
+ const normalized = String(key);
4879
+ if (!explicit.includes(normalized)) explicit.push(normalized);
4880
+ }
4881
+ const rest = seen.filter((key) => !explicit.includes(key));
4882
+ return [...explicit, ...rest];
4883
+ };
4884
+ var partitionLanes = (rows, { swimlaneBy, swimlaneOrder } = {}) => {
4885
+ const rowsByLane = {};
4886
+ const firstSeen = [];
4887
+ for (const row of rows || []) {
4888
+ const key = getLaneKey(row, swimlaneBy);
4889
+ if (!rowsByLane[key]) {
4890
+ rowsByLane[key] = [];
4891
+ firstSeen.push(key);
4892
+ }
4893
+ rowsByLane[key].push(row);
4894
+ }
4895
+ const laneKeys = orderLaneKeys(firstSeen, swimlaneOrder);
4896
+ for (const key of laneKeys) {
4897
+ if (!rowsByLane[key]) rowsByLane[key] = [];
4898
+ }
4899
+ return { laneKeys, rowsByLane };
4900
+ };
4901
+ var resolveLaneLabel = (laneKey, swimlaneLabels, rows, unassignedLabel) => {
4902
+ if (typeof swimlaneLabels === "function") {
4903
+ const out = swimlaneLabels(laneKey, rows || []);
4904
+ if (out != null) return out;
4905
+ } else if (swimlaneLabels && typeof swimlaneLabels === "object") {
4906
+ const out = swimlaneLabels[laneKey];
4907
+ if (out != null) return out;
4908
+ }
4909
+ if (laneKey === UNASSIGNED_LANE_KEY) return unassignedLabel || "Unassigned";
4910
+ return String(laneKey);
4911
+ };
4912
+ var bucketRowsByStage = (rows, stages, getStage) => {
4913
+ const map = {};
4914
+ for (const stage of stages || []) map[stage.value] = [];
4915
+ for (const row of rows || []) {
4916
+ const key = getStage(row);
4917
+ if (map[key]) {
4918
+ map[key].push(row);
4919
+ } else if ((stages || []).length > 0) {
4920
+ if (!map[UNKNOWN_STAGE_KEY]) map[UNKNOWN_STAGE_KEY] = [];
4921
+ map[UNKNOWN_STAGE_KEY].push(row);
4922
+ }
4923
+ }
4924
+ return map;
4925
+ };
4926
+ var sortBuckets = (buckets, comparator) => {
4927
+ if (!comparator) return buckets;
4928
+ const out = {};
4929
+ for (const key of Object.keys(buckets || {})) {
4930
+ out[key] = [...buckets[key]].sort(comparator);
4931
+ }
4932
+ return out;
4933
+ };
4934
+ var resolveWipLimit = (stage, wipLimits) => {
4935
+ const override = wipLimits ? wipLimits[stage == null ? void 0 : stage.value] : void 0;
4936
+ const limit = override != null ? override : stage == null ? void 0 : stage.wipLimit;
4937
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit < 0) return null;
4938
+ return limit;
4939
+ };
4940
+ var computeStageCounts = (stages, buckets, stageMeta) => {
4941
+ const counts = {};
4942
+ for (const stage of stages || []) {
4943
+ const meta = stageMeta ? stageMeta[stage.value] : void 0;
4944
+ counts[stage.value] = meta && meta.totalCount != null ? meta.totalCount : ((buckets == null ? void 0 : buckets[stage.value]) || []).length;
4945
+ }
4946
+ return counts;
4947
+ };
4948
+ var evaluateWip = (stages, counts, wipLimits) => {
4949
+ const out = {};
4950
+ for (const stage of stages || []) {
4951
+ const limit = resolveWipLimit(stage, wipLimits);
4952
+ const count = (counts == null ? void 0 : counts[stage.value]) || 0;
4953
+ out[stage.value] = { count, limit, exceeded: limit != null && count > limit };
4954
+ }
4955
+ return out;
4956
+ };
4957
+ var findNewlyExceededWip = (prev, next) => {
4958
+ const events = [];
4959
+ for (const stageId of Object.keys(next || {})) {
4960
+ const entry = next[stageId];
4961
+ if (!entry || !entry.exceeded) continue;
4962
+ if (prev && prev[stageId] && prev[stageId].exceeded) continue;
4963
+ events.push({ stageId, count: entry.count, limit: entry.limit });
4964
+ }
4965
+ return events;
4966
+ };
4967
+
4577
4968
  // src/common-components/CollectionSortSelect.js
4578
4969
  import React8, { useId as useId2 } from "react";
4579
4970
  import { Select as Select4 } from "@hubspot/ui-extensions";
@@ -4845,7 +5236,7 @@ var StyledText = ({
4845
5236
  const nativeVariant = NATIVE_TAG_VARIANT_ALIASES[background == null ? void 0 : background.variant] ?? (background == null ? void 0 : background.variant) ?? "default";
4846
5237
  return React9.createElement(Tag2, { variant: nativeVariant }, resolvedText);
4847
5238
  }
4848
- const { src, width: w, height: h6 } = makeStyledTextDataUri(resolvedText, {
5239
+ const { src, width: w, height: h7 } = makeStyledTextDataUri(resolvedText, {
4849
5240
  variant,
4850
5241
  format,
4851
5242
  orientation,
@@ -4861,7 +5252,7 @@ var StyledText = ({
4861
5252
  return React9.createElement(Image2, {
4862
5253
  src,
4863
5254
  width: w,
4864
- height: h6,
5255
+ height: h7,
4865
5256
  alt: alt ?? String(resolvedText)
4866
5257
  });
4867
5258
  };
@@ -4882,11 +5273,12 @@ import {
4882
5273
  Image as Image3,
4883
5274
  Inline as Inline2,
4884
5275
  Link as Link4,
4885
- LoadingSpinner,
5276
+ LoadingSpinner as LoadingSpinner2,
4886
5277
  Select as Select5,
4887
5278
  Statistics,
4888
5279
  StatisticsItem,
4889
5280
  StatisticsTrend,
5281
+ StatusTag,
4890
5282
  Tag as Tag3,
4891
5283
  Text as Text4,
4892
5284
  Tile as Tile3
@@ -4924,6 +5316,12 @@ var DEFAULT_LABELS3 = {
4924
5316
  errorTitle: "Something went wrong.",
4925
5317
  errorMessage: "An error occurred while loading data.",
4926
5318
  cardCount: (n) => String(n),
5319
+ // "5 / 4" — count vs WIP limit in the stage header. The slash format is the
5320
+ // standard kanban convention; override for tighter ("5/4") or verbose forms.
5321
+ wipCount: (count, limit) => `${count} / ${limit}`,
5322
+ overWip: "Over WIP",
5323
+ laneCount: (n) => String(n),
5324
+ unassignedLane: "Unassigned",
4927
5325
  moveTo: "Move",
4928
5326
  clearAll: "Clear all",
4929
5327
  selectAll: (count, label) => `Select all ${count} ${label}`,
@@ -5092,7 +5490,7 @@ var StageControl = ({
5092
5490
  labels
5093
5491
  }) => {
5094
5492
  if (isChanging) {
5095
- return /* @__PURE__ */ React10.createElement(LoadingSpinner, { size: "xs" });
5493
+ return /* @__PURE__ */ React10.createElement(LoadingSpinner2, { size: "xs" });
5096
5494
  }
5097
5495
  const availableStages = (stages || []).filter(
5098
5496
  (stage) => stage.value === currentStage.value || canStageReceiveRow(stage, row, canMove)
@@ -5150,10 +5548,14 @@ var KanbanColumn = ({
5150
5548
  onToggleCollapsed,
5151
5549
  columnFooter,
5152
5550
  countDisplay,
5551
+ wip,
5552
+ compactEmpty,
5153
5553
  labels,
5154
5554
  children
5155
5555
  }) => {
5156
- const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
5556
+ const hasWipLimit = wip != null && wip.limit != null;
5557
+ const countLabel = hasWipLimit ? labels.wipCount(wip.count, wip.limit) : labels.cardCount(totalCount != null ? totalCount : bucketCount);
5558
+ const overWipNode = wip && wip.exceeded ? /* @__PURE__ */ React10.createElement(StatusTag, { variant: "warning" }, labels.overWip) : null;
5157
5559
  const countNode = countDisplay === "text" ? /* @__PURE__ */ React10.createElement(Text4, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ React10.createElement(Tag3, { variant: "default" }, countLabel);
5158
5560
  if (collapsed) {
5159
5561
  const rotated = makeRotatedLabelDataUri(stage.label);
@@ -5186,8 +5588,11 @@ var KanbanColumn = ({
5186
5588
  }
5187
5589
  ) : null));
5188
5590
  }
5591
+ if (compactEmpty && !loading && rows.length === 0 && bucketCount === 0) {
5592
+ return /* @__PURE__ */ React10.createElement(Tile3, { compact: true }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ React10.createElement(Text4, { variant: "microcopy", format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), /* @__PURE__ */ React10.createElement(Text4, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn)));
5593
+ }
5189
5594
  const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
5190
- return /* @__PURE__ */ React10.createElement(Tile3, { compact: true }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "column", gap: "xs" }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React10.createElement(Text4, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ React10.createElement(LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ React10.createElement(Button5, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ React10.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ React10.createElement(Text4, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ React10.createElement(Divider2, null), children, error ? /* @__PURE__ */ React10.createElement(Alert2, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ React10.createElement(Text4, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ React10.createElement(Button5, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", justify: "center" }, /* @__PURE__ */ React10.createElement(Link4, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ React10.createElement(LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", justify: "center" }, /* @__PURE__ */ React10.createElement(Link4, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ React10.createElement(Text4, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
5595
+ return /* @__PURE__ */ React10.createElement(Tile3, { compact: true }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "column", gap: "xs" }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React10.createElement(Text4, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, overWipNode, loading ? /* @__PURE__ */ React10.createElement(LoadingSpinner2, { size: "xs" }) : null), /* @__PURE__ */ React10.createElement(Button5, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ React10.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ React10.createElement(Text4, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ React10.createElement(Divider2, null), children, error ? /* @__PURE__ */ React10.createElement(Alert2, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ React10.createElement(Text4, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ React10.createElement(Button5, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", justify: "center" }, /* @__PURE__ */ React10.createElement(Link4, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ React10.createElement(LoadingSpinner2, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", justify: "center" }, /* @__PURE__ */ React10.createElement(Link4, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ React10.createElement(Text4, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
5191
5596
  };
5192
5597
  var renderMetricsPanel = (metrics) => {
5193
5598
  if (!metrics) return null;
@@ -5226,14 +5631,14 @@ var KanbanToolbar = ({
5226
5631
  sortOptions,
5227
5632
  sortValue,
5228
5633
  onSortChange,
5229
- metrics,
5230
- showMetrics,
5634
+ showMetricsButton,
5635
+ metricsPanel,
5231
5636
  onToggleMetrics,
5232
5637
  labels,
5233
5638
  toolbarLeftFlex,
5234
5639
  toolbarRightFlex
5235
5640
  }) => {
5236
- const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ React10.createElement(
5641
+ const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || showMetricsButton ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ React10.createElement(
5237
5642
  CollectionSortSelect,
5238
5643
  {
5239
5644
  name: "kanban-sort",
@@ -5242,7 +5647,7 @@ var KanbanToolbar = ({
5242
5647
  options: sortOptions,
5243
5648
  onChange: onSortChange
5244
5649
  }
5245
- ) : null, metrics ? /* @__PURE__ */ React10.createElement(Button5, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ React10.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
5650
+ ) : null, showMetricsButton ? /* @__PURE__ */ React10.createElement(Button5, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ React10.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
5246
5651
  return /* @__PURE__ */ React10.createElement(
5247
5652
  CollectionToolbar,
5248
5653
  {
@@ -5269,7 +5674,7 @@ var KanbanToolbar = ({
5269
5674
  onRemove: onFilterRemove
5270
5675
  },
5271
5676
  right: rightControls,
5272
- footer: showMetrics && metrics ? renderMetricsPanel(metrics) : null,
5677
+ footer: metricsPanel,
5273
5678
  labels,
5274
5679
  leftFlex: toolbarLeftFlex,
5275
5680
  rightFlex: toolbarRightFlex
@@ -5321,6 +5726,18 @@ var Kanban = ({
5321
5726
  // --- Per-stage pagination ---
5322
5727
  stageMeta,
5323
5728
  onLoadMore,
5729
+ // --- WIP limits ---
5730
+ wipLimits,
5731
+ onWipExceeded,
5732
+ // --- Swimlanes ---
5733
+ swimlaneBy,
5734
+ swimlaneLabels,
5735
+ swimlaneOrder,
5736
+ collapseLanes = true,
5737
+ collapsedLanes,
5738
+ defaultCollapsedLanes,
5739
+ onCollapsedLanesChange,
5740
+ metricsPerLane = false,
5324
5741
  // --- Selection ---
5325
5742
  selectable = false,
5326
5743
  selectedIds,
@@ -5385,6 +5802,9 @@ var Kanban = ({
5385
5802
  const [internalFilters, setInternalFilters] = useState4(() => getEmptyFilterValues(filters));
5386
5803
  const [internalSort, setInternalSort] = useState4(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
5387
5804
  const [internalCollapsed, setInternalCollapsed] = useState4([]);
5805
+ const [internalCollapsedLanes, setInternalCollapsedLanes] = useState4(
5806
+ () => defaultCollapsedLanes || []
5807
+ );
5388
5808
  const [internalExpanded, setInternalExpanded] = useState4([]);
5389
5809
  const [internalSelection, setInternalSelection] = useState4([]);
5390
5810
  const [internalShowMetrics, setInternalShowMetrics] = useState4(false);
@@ -5401,6 +5821,7 @@ var Kanban = ({
5401
5821
  const resolvedFilters = filterValues != null ? filterValues : internalFilters;
5402
5822
  const resolvedSort = sort != null ? sort : internalSort;
5403
5823
  const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
5824
+ const resolvedCollapsedLanes = collapsedLanes != null ? collapsedLanes : internalCollapsedLanes;
5404
5825
  const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
5405
5826
  const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
5406
5827
  const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
@@ -5417,9 +5838,10 @@ var Kanban = ({
5417
5838
  search: overrides.search != null ? overrides.search : resolvedSearch,
5418
5839
  filters: overrides.filters != null ? overrides.filters : resolvedFilters,
5419
5840
  sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
5420
- collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
5841
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed,
5842
+ collapsedLanes: overrides.collapsedLanes != null ? overrides.collapsedLanes : resolvedCollapsedLanes
5421
5843
  });
5422
- }, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
5844
+ }, [onParamsChange, resolvedCollapsed, resolvedCollapsedLanes, resolvedFilters, resolvedSearch, resolvedSort]);
5423
5845
  const lastAppliedSearchRef = useRef4(searchValue != null ? searchValue : "");
5424
5846
  useEffect4(() => {
5425
5847
  if (searchValue == null) return;
@@ -5478,6 +5900,15 @@ var Kanban = ({
5478
5900
  },
5479
5901
  [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
5480
5902
  );
5903
+ const handleLaneCollapsed = useCallback4(
5904
+ (laneKey) => {
5905
+ const next = resolvedCollapsedLanes.includes(laneKey) ? resolvedCollapsedLanes.filter((k) => k !== laneKey) : [...resolvedCollapsedLanes, laneKey];
5906
+ if (onCollapsedLanesChange) onCollapsedLanesChange(next);
5907
+ if (collapsedLanes == null) setInternalCollapsedLanes(next);
5908
+ fireParamsChange({ collapsedLanes: next });
5909
+ },
5910
+ [fireParamsChange, resolvedCollapsedLanes, collapsedLanes, onCollapsedLanesChange]
5911
+ );
5481
5912
  const handleExpanded = useCallback4(
5482
5913
  (stageValue) => {
5483
5914
  const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
@@ -5546,33 +5977,45 @@ var Kanban = ({
5546
5977
  }
5547
5978
  return result;
5548
5979
  }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
5549
- const buckets = useMemo3(() => {
5550
- const map = {};
5551
- for (const stage of stages) map[stage.value] = [];
5552
- for (const row of filteredData) {
5553
- const key = getStageFor(row);
5554
- if (map[key]) {
5555
- map[key].push(row);
5556
- } else if (stages.length > 0) {
5557
- if (!map.__unknown) map.__unknown = [];
5558
- map.__unknown.push(row);
5559
- }
5560
- }
5561
- return map;
5562
- }, [filteredData, stages, getStageFor]);
5980
+ const buckets = useMemo3(
5981
+ () => bucketRowsByStage(filteredData, stages, getStageFor),
5982
+ [filteredData, stages, getStageFor]
5983
+ );
5563
5984
  const sortComparator = useMemo3(() => {
5564
5985
  if (!sortOptions || !resolvedSort) return null;
5565
5986
  const opt = sortOptions.find((s) => s.value === resolvedSort);
5566
5987
  return (opt == null ? void 0 : opt.comparator) || null;
5567
5988
  }, [sortOptions, resolvedSort]);
5568
- const sortedBuckets = useMemo3(() => {
5569
- if (!sortComparator) return buckets;
5989
+ const sortedBuckets = useMemo3(() => sortBuckets(buckets, sortComparator), [buckets, sortComparator]);
5990
+ const hasLanes = swimlaneBy != null;
5991
+ const laneData = useMemo3(() => {
5992
+ if (!hasLanes) return null;
5993
+ return partitionLanes(filteredData, { swimlaneBy, swimlaneOrder });
5994
+ }, [hasLanes, filteredData, swimlaneBy, swimlaneOrder]);
5995
+ const laneBuckets = useMemo3(() => {
5996
+ if (!laneData) return null;
5570
5997
  const out = {};
5571
- for (const key of Object.keys(buckets)) {
5572
- out[key] = [...buckets[key]].sort(sortComparator);
5998
+ for (const laneKey of laneData.laneKeys) {
5999
+ out[laneKey] = sortBuckets(
6000
+ bucketRowsByStage(laneData.rowsByLane[laneKey] || [], stages, getStageFor),
6001
+ sortComparator
6002
+ );
5573
6003
  }
5574
6004
  return out;
5575
- }, [buckets, sortComparator]);
6005
+ }, [laneData, stages, getStageFor, sortComparator]);
6006
+ const wipByStage = useMemo3(
6007
+ () => evaluateWip(stages, computeStageCounts(stages, buckets, stageMeta), wipLimits),
6008
+ [stages, buckets, stageMeta, wipLimits]
6009
+ );
6010
+ const prevWipRef = useRef4({});
6011
+ useEffect4(() => {
6012
+ const newlyExceeded = findNewlyExceededWip(prevWipRef.current, wipByStage);
6013
+ prevWipRef.current = wipByStage;
6014
+ if (!onWipExceeded) return;
6015
+ for (const event of newlyExceeded) {
6016
+ onWipExceeded(event.stageId, event.count, event.limit);
6017
+ }
6018
+ }, [wipByStage, onWipExceeded]);
5576
6019
  const activeChips = useMemo3(
5577
6020
  () => buildActiveFilterChips(filters, resolvedFilters),
5578
6021
  [filters, resolvedFilters]
@@ -5699,6 +6142,52 @@ var Kanban = ({
5699
6142
  selectionActions: selectionActions || [],
5700
6143
  labels
5701
6144
  };
6145
+ const metricsProvided = metrics != null && (!Array.isArray(metrics) || metrics.length > 0);
6146
+ const perLaneMetricsActive = hasLanes && metricsPerLane && typeof metrics === "function";
6147
+ const globalMetricsContent = metricsProvided && !perLaneMetricsActive ? typeof metrics === "function" ? metrics(filteredData, null) : metrics : null;
6148
+ const renderStageColumns = (bucketMap, laneKey) => {
6149
+ const inLane = laneKey != null;
6150
+ return /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
6151
+ const stageRows = bucketMap[stage.value] || [];
6152
+ const meta = inLane ? void 0 : stageMeta == null ? void 0 : stageMeta[stage.value];
6153
+ const isExpanded = resolvedExpanded.includes(stage.value);
6154
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
6155
+ const visibleRows = stageRows.slice(0, clamp);
6156
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
6157
+ const stageWip = wipByStage[stage.value];
6158
+ const wip = inLane ? (stageWip == null ? void 0 : stageWip.exceeded) ? { count: stageWip.count, limit: null, exceeded: true } : null : stageWip;
6159
+ return /* @__PURE__ */ React10.createElement(
6160
+ AutoGrid2,
6161
+ {
6162
+ key: inLane ? `${laneKey}::${stage.value}` : stage.value,
6163
+ columnWidth: isCollapsed ? 72 : effectiveColumnWidth
6164
+ },
6165
+ /* @__PURE__ */ React10.createElement(
6166
+ KanbanColumn,
6167
+ {
6168
+ stage,
6169
+ rows: visibleRows,
6170
+ bucketCount: stageRows.length,
6171
+ totalCount: meta == null ? void 0 : meta.totalCount,
6172
+ hasMore: meta == null ? void 0 : meta.hasMore,
6173
+ loading: meta == null ? void 0 : meta.loading,
6174
+ error: meta == null ? void 0 : meta.error,
6175
+ onLoadMore: inLane ? void 0 : onLoadMore,
6176
+ expanded: isExpanded,
6177
+ onToggleExpanded: () => handleExpanded(stage.value),
6178
+ collapsed: isCollapsed,
6179
+ onToggleCollapsed: () => handleCollapsed(stage.value),
6180
+ columnFooter,
6181
+ countDisplay,
6182
+ wip,
6183
+ compactEmpty: inLane,
6184
+ labels
6185
+ },
6186
+ visibleRows.map((row) => renderCardNode(row, stage))
6187
+ )
6188
+ );
6189
+ }));
6190
+ };
5702
6191
  const mainContent = error ? renderErrorState ? renderErrorState({
5703
6192
  error,
5704
6193
  title: labels.errorTitle,
@@ -5710,35 +6199,32 @@ var Kanban = ({
5710
6199
  ) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
5711
6200
  title: labels.emptyTitle,
5712
6201
  message: labels.emptyMessage
5713
- }) : /* @__PURE__ */ React10.createElement(Tile3, null, /* @__PURE__ */ React10.createElement(Flex6, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React10.createElement(EmptyState2, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ React10.createElement(Text4, null, labels.emptyMessage)))) : /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
5714
- const stageRows = sortedBuckets[stage.value] || [];
5715
- const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
5716
- const isExpanded = resolvedExpanded.includes(stage.value);
5717
- const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
5718
- const visibleRows = stageRows.slice(0, clamp);
5719
- const isCollapsed = resolvedCollapsed.includes(stage.value);
5720
- return /* @__PURE__ */ React10.createElement(AutoGrid2, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ React10.createElement(
5721
- KanbanColumn,
6202
+ }) : /* @__PURE__ */ React10.createElement(Tile3, null, /* @__PURE__ */ React10.createElement(Flex6, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React10.createElement(EmptyState2, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ React10.createElement(Text4, null, labels.emptyMessage)))) : hasLanes ? /* @__PURE__ */ React10.createElement(Flex6, { direction: "column", gap: "md" }, ((laneData == null ? void 0 : laneData.laneKeys) || []).map((laneKey, laneIndex) => {
6203
+ const laneRows = laneData.rowsByLane[laneKey] || [];
6204
+ const laneLabel = resolveLaneLabel(laneKey, swimlaneLabels, laneRows, labels.unassignedLane);
6205
+ const laneLabelText = typeof laneLabel === "string" ? laneLabel : String(laneKey);
6206
+ const isLaneCollapsed = collapseLanes && resolvedCollapsedLanes.includes(laneKey);
6207
+ const laneCountLabel = labels.laneCount(laneRows.length);
6208
+ const laneCountNode = countDisplay === "none" ? null : countDisplay === "text" ? /* @__PURE__ */ React10.createElement(Text4, { format: { fontWeight: "demibold" } }, laneCountLabel) : /* @__PURE__ */ React10.createElement(Tag3, { variant: "default" }, laneCountLabel);
6209
+ const laneMetricsNode = !isLaneCollapsed && perLaneMetricsActive && resolvedShowMetrics ? renderMetricsPanel(metrics(laneRows, laneKey)) : null;
6210
+ return /* @__PURE__ */ React10.createElement(Flex6, { key: laneKey, direction: "column", gap: "xs" }, laneIndex > 0 ? /* @__PURE__ */ React10.createElement(Divider2, null) : null, /* @__PURE__ */ React10.createElement(Flex6, { direction: "row", align: "center", gap: "xs" }, collapseLanes ? /* @__PURE__ */ React10.createElement(
6211
+ Button5,
5722
6212
  {
5723
- stage,
5724
- rows: visibleRows,
5725
- bucketCount: stageRows.length,
5726
- totalCount: meta == null ? void 0 : meta.totalCount,
5727
- hasMore: meta == null ? void 0 : meta.hasMore,
5728
- loading: meta == null ? void 0 : meta.loading,
5729
- error: meta == null ? void 0 : meta.error,
5730
- onLoadMore,
5731
- expanded: isExpanded,
5732
- onToggleExpanded: () => handleExpanded(stage.value),
5733
- collapsed: isCollapsed,
5734
- onToggleCollapsed: () => handleCollapsed(stage.value),
5735
- columnFooter,
5736
- countDisplay,
5737
- labels
6213
+ variant: "transparent",
6214
+ size: "sm",
6215
+ onClick: () => handleLaneCollapsed(laneKey),
6216
+ tooltip: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
5738
6217
  },
5739
- visibleRows.map((row) => renderCardNode(row, stage))
5740
- ));
5741
- }));
6218
+ /* @__PURE__ */ React10.createElement(
6219
+ Icon,
6220
+ {
6221
+ name: isLaneCollapsed ? "right" : "down",
6222
+ size: "sm",
6223
+ screenReaderText: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
6224
+ }
6225
+ )
6226
+ ) : null, /* @__PURE__ */ React10.createElement(Text4, { format: { fontWeight: "demibold" } }, laneLabel), laneCountNode), laneMetricsNode, !isLaneCollapsed ? renderStageColumns((laneBuckets == null ? void 0 : laneBuckets[laneKey]) || {}, laneKey) : null);
6227
+ })) : renderStageColumns(sortedBuckets, null);
5742
6228
  const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
5743
6229
  return /* @__PURE__ */ React10.createElement(Flex6, { direction: "column", gap: "sm" }, /* @__PURE__ */ React10.createElement(
5744
6230
  KanbanToolbar,
@@ -5758,8 +6244,8 @@ var Kanban = ({
5758
6244
  sortOptions,
5759
6245
  sortValue: resolvedSort,
5760
6246
  onSortChange: handleSort,
5761
- metrics,
5762
- showMetrics: resolvedShowMetrics,
6247
+ showMetricsButton: metricsProvided,
6248
+ metricsPanel: resolvedShowMetrics && globalMetricsContent ? renderMetricsPanel(globalMetricsContent) : null,
5763
6249
  onToggleMetrics: toggleMetrics,
5764
6250
  labels,
5765
6251
  toolbarLeftFlex,
@@ -5767,6 +6253,7 @@ var Kanban = ({
5767
6253
  }
5768
6254
  ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ React10.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
5769
6255
  };
6256
+ Kanban.displayName = "Kanban";
5770
6257
 
5771
6258
  // src/kanban/KanbanCardActions.jsx
5772
6259
  import React11 from "react";
@@ -5847,7 +6334,7 @@ import {
5847
6334
  Inline as Inline3,
5848
6335
  Link as Link5,
5849
6336
  List,
5850
- StatusTag,
6337
+ StatusTag as StatusTag2,
5851
6338
  Tab,
5852
6339
  Tabs,
5853
6340
  Tag as Tag4,
@@ -5992,6 +6479,110 @@ var AvatarStack = ({
5992
6479
  });
5993
6480
  };
5994
6481
 
6482
+ // src/feed/feedLiveBuffer.js
6483
+ var isDateValueObject2 = (v) => v != null && typeof v === "object" && typeof v.year === "number" && typeof v.month === "number" && typeof v.date === "number";
6484
+ var toTimestampMs = (value) => {
6485
+ if (value == null || value === "") return null;
6486
+ if (value instanceof Date) {
6487
+ const time2 = value.getTime();
6488
+ return Number.isNaN(time2) ? null : time2;
6489
+ }
6490
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
6491
+ if (isDateValueObject2(value)) {
6492
+ return new Date(value.year, value.month, value.date, value.hour || 0, value.minute || 0).getTime();
6493
+ }
6494
+ const parsed = new Date(value);
6495
+ const time = parsed.getTime();
6496
+ return Number.isNaN(time) ? null : time;
6497
+ };
6498
+ var defaultGetId = (item, index) => (item == null ? void 0 : item.id) ?? (item == null ? void 0 : item.key) ?? index;
6499
+ var partitionNewItems = (prevNewestTs, items, getTs, options = {}) => {
6500
+ const { knownIds = null, getId = defaultGetId } = options;
6501
+ const safeItems = Array.isArray(items) ? items : [];
6502
+ const firstLoad = prevNewestTs == null;
6503
+ const visible = [];
6504
+ const visibleIds = [];
6505
+ const buffered = [];
6506
+ const bufferedIds = [];
6507
+ let newestTs = typeof prevNewestTs === "number" ? prevNewestTs : null;
6508
+ safeItems.forEach((item, index) => {
6509
+ const id = getId(item, index);
6510
+ const ts = toTimestampMs(typeof getTs === "function" ? getTs(item) : void 0);
6511
+ const isKnown = knownIds != null && id !== void 0 && knownIds.has(id);
6512
+ const isNewArrival = !firstLoad && !isKnown && ts != null && ts > prevNewestTs;
6513
+ if (isNewArrival) {
6514
+ buffered.push(item);
6515
+ bufferedIds.push(id);
6516
+ return;
6517
+ }
6518
+ visible.push(item);
6519
+ visibleIds.push(id);
6520
+ if (ts != null && (newestTs == null || ts > newestTs)) newestTs = ts;
6521
+ });
6522
+ return { visible, buffered, visibleIds, bufferedIds, newestTs };
6523
+ };
6524
+ var flushBuffer = (visible, buffered, getTs) => {
6525
+ const safeVisible = Array.isArray(visible) ? visible : [];
6526
+ const safeBuffered = Array.isArray(buffered) ? buffered : [];
6527
+ const items = [...safeBuffered, ...safeVisible];
6528
+ let newestTs = null;
6529
+ items.forEach((item) => {
6530
+ const ts = toTimestampMs(typeof getTs === "function" ? getTs(item) : void 0);
6531
+ if (ts != null && (newestTs == null || ts > newestTs)) newestTs = ts;
6532
+ });
6533
+ return { items, flushed: safeBuffered, newestTs };
6534
+ };
6535
+
6536
+ // src/feed/feedTypePresets.js
6537
+ var hasValue = (value) => value != null && value !== false && value !== "";
6538
+ var DEFAULT_FEED_TYPE_PRESETS = {
6539
+ call: { icon: "calling", label: "Call" },
6540
+ email: { icon: "email", label: "Email" },
6541
+ incoming_email: { icon: "inbox", label: "Incoming email" },
6542
+ forwarded_email: { icon: "forward", label: "Forwarded email" },
6543
+ meeting: { icon: "appointment", label: "Meeting" },
6544
+ note: { icon: "comment", label: "Note" },
6545
+ task: { icon: "tasks", label: "Task" },
6546
+ sms: { icon: "messages", label: "SMS" },
6547
+ whatsapp: { icon: "messages", label: "WhatsApp" },
6548
+ linkedin_message: { icon: "linkedin", label: "LinkedIn message" },
6549
+ postal_mail: { icon: "send", label: "Postal mail" },
6550
+ conversation: { icon: "questionAnswer", label: "Conversation" }
6551
+ };
6552
+ var lookupTypePreset = (type, presets) => {
6553
+ if (!presets || typeof presets !== "object") return null;
6554
+ if (type == null || type === "") return null;
6555
+ if (Object.prototype.hasOwnProperty.call(presets, type)) return presets[type];
6556
+ const lower = String(type).toLowerCase();
6557
+ if (Object.prototype.hasOwnProperty.call(presets, lower)) return presets[lower];
6558
+ const snake = lower.replace(/[\s-]+/g, "_");
6559
+ if (Object.prototype.hasOwnProperty.call(presets, snake)) return presets[snake];
6560
+ return null;
6561
+ };
6562
+ var applyTypePreset = (item, typePresets) => {
6563
+ if (item == null || typeof item !== "object") return item;
6564
+ const preset = lookupTypePreset(item.type, typePresets);
6565
+ if (!preset || typeof preset !== "object") return item;
6566
+ let next = null;
6567
+ const fill = (key, value) => {
6568
+ if (next === null) next = { ...item };
6569
+ next[key] = value;
6570
+ };
6571
+ if (!hasValue(item.icon) && !hasValue(item.iconName) && hasValue(preset.icon)) {
6572
+ fill("iconName", preset.icon);
6573
+ }
6574
+ if (!hasValue(item.iconColor) && hasValue(preset.color)) {
6575
+ fill("iconColor", preset.color);
6576
+ }
6577
+ if (!hasValue(item.typeLabel) && hasValue(preset.label)) {
6578
+ fill("typeLabel", preset.label);
6579
+ }
6580
+ if (item.statusVariant == null && item.outcomeVariant == null && item.severityVariant == null && hasValue(preset.statusVariant)) {
6581
+ fill("statusVariant", preset.statusVariant);
6582
+ }
6583
+ return next ?? item;
6584
+ };
6585
+
5995
6586
  // src/feed/Feed.jsx
5996
6587
  var DEFAULT_LABELS4 = {
5997
6588
  search: "Search activity...",
@@ -6005,6 +6596,8 @@ var DEFAULT_LABELS4 = {
6005
6596
  loadingMessage: "This should only take a moment.",
6006
6597
  loadingMore: "Loading...",
6007
6598
  loadMore: "View more",
6599
+ newItems: (count) => count === 1 ? "Show 1 new item" : `Show ${count} new items`,
6600
+ newItemTag: "New",
6008
6601
  collapseAll: "Collapse all",
6009
6602
  expandAll: "Expand all",
6010
6603
  emptyTitle: "No activity yet",
@@ -6017,7 +6610,15 @@ var DEFAULT_LABELS4 = {
6017
6610
  var DEFAULT_RECORD_LABEL = { singular: "item", plural: "items" };
6018
6611
  var DEFAULT_SEARCH_FIELDS = ["title", "subject", "body", "description", "content", "preview", "type", "typeLabel", "actorName", "author"];
6019
6612
  var DEFAULT_PAGE_SIZE = 5;
6020
- var hasValue = (value) => value != null && value !== false && value !== "";
6613
+ var EMPTY_ITEMS = [];
6614
+ var INITIAL_LIVE_STATE = {
6615
+ source: null,
6616
+ watermark: null,
6617
+ bufferedKeys: EMPTY_ITEMS,
6618
+ knownKeys: EMPTY_ITEMS,
6619
+ newKeys: EMPTY_ITEMS
6620
+ };
6621
+ var hasValue2 = (value) => value != null && value !== false && value !== "";
6021
6622
  var keepWordsTogether = (value) => typeof value === "string" ? value.replace(/\s+/g, "\xA0") : value;
6022
6623
  var getItemKey = (item, index, getKey) => {
6023
6624
  if (typeof getKey === "function") return getKey(item, index);
@@ -6044,11 +6645,11 @@ var pickHeaderActions = (item) => item == null ? void 0 : item.headerActions;
6044
6645
  var itemHasExpandableContent = (item, fields) => {
6045
6646
  if ((item == null ? void 0 : item.collapsible) === false) return false;
6046
6647
  if ((item == null ? void 0 : item.collapsible) === true) return true;
6047
- if (hasValue(pickBody(item))) return true;
6048
- if (hasValue(pickActor(item))) return true;
6049
- if (hasValue(item == null ? void 0 : item.actions)) return true;
6050
- if (hasValue(item == null ? void 0 : item.footer)) return true;
6051
- if (hasValue(item == null ? void 0 : item.meta) || hasValue(item == null ? void 0 : item.metadata)) return true;
6648
+ if (hasValue2(pickBody(item))) return true;
6649
+ if (hasValue2(pickActor(item))) return true;
6650
+ if (hasValue2(item == null ? void 0 : item.actions)) return true;
6651
+ if (hasValue2(item == null ? void 0 : item.footer)) return true;
6652
+ if (hasValue2(item == null ? void 0 : item.meta) || hasValue2(item == null ? void 0 : item.metadata)) return true;
6052
6653
  if (Array.isArray(fields)) {
6053
6654
  if (fields.some((f) => ["body", "footer"].includes(f.placement ?? "body"))) return true;
6054
6655
  }
@@ -6130,7 +6731,7 @@ var getRecordLabel = (recordLabel, count) => {
6130
6731
  var FeedActorAvatar = ({ item, avatarSize }) => {
6131
6732
  const actor = pickActor(item);
6132
6733
  const avatar = (item == null ? void 0 : item.avatar) ?? pickActorAvatar(actor);
6133
- if (!hasValue(avatar)) return null;
6734
+ if (!hasValue2(avatar)) return null;
6134
6735
  return /* @__PURE__ */ React13.createElement(
6135
6736
  AvatarStack,
6136
6737
  {
@@ -6142,10 +6743,10 @@ var FeedActorAvatar = ({ item, avatarSize }) => {
6142
6743
  );
6143
6744
  };
6144
6745
  var FeedTypeIcon = ({ item, iconSize }) => {
6145
- if (hasValue(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
6746
+ if (hasValue2(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
6146
6747
  const iconName = typeof (item == null ? void 0 : item.icon) === "string" ? item.icon : item == null ? void 0 : item.iconName;
6147
- if (!hasValue(iconName)) return null;
6148
- return /* @__PURE__ */ React13.createElement(Icon, { name: iconName, size: iconSize, purpose: "decorative" });
6748
+ if (!hasValue2(iconName)) return null;
6749
+ return /* @__PURE__ */ React13.createElement(Icon, { name: iconName, size: iconSize, color: item == null ? void 0 : item.iconColor, purpose: "decorative" });
6149
6750
  };
6150
6751
  var FeedActions = ({ actions }) => {
6151
6752
  if (!Array.isArray(actions) || actions.length === 0) return actions || null;
@@ -6168,14 +6769,14 @@ var FeedField = ({ field, item, index }) => {
6168
6769
  if (field.visible && !field.visible(item)) return null;
6169
6770
  const value = getValue(item, field.field);
6170
6771
  const rendered = field.render ? field.render(value, item, index) : value;
6171
- if (!hasValue(rendered)) return null;
6772
+ if (!hasValue2(rendered)) return null;
6172
6773
  if (field.href) {
6173
6774
  const href = typeof field.href === "function" ? field.href(item) : field.href;
6174
6775
  return /* @__PURE__ */ React13.createElement(Link5, { href }, rendered);
6175
6776
  }
6176
6777
  if (field.type === "status") {
6177
6778
  const variant = typeof field.variant === "function" ? field.variant(value, item) : field.variant;
6178
- return /* @__PURE__ */ React13.createElement(StatusTag, { variant: variant ?? "default" }, rendered);
6779
+ return /* @__PURE__ */ React13.createElement(StatusTag2, { variant: variant ?? "default" }, rendered);
6179
6780
  }
6180
6781
  if (field.type === "tag") {
6181
6782
  const variant = typeof field.variant === "function" ? field.variant(value, item) : field.variant;
@@ -6205,7 +6806,7 @@ var renderPlacedFields = ({ fields, placement, item, index, inline = false }) =>
6205
6806
  return /* @__PURE__ */ React13.createElement(Flex8, { direction: "column", gap: "xs" }, nodes);
6206
6807
  };
6207
6808
  var renderHeaderActions = (headerActions) => {
6208
- if (!hasValue(headerActions)) return null;
6809
+ if (!hasValue2(headerActions)) return null;
6209
6810
  if (!Array.isArray(headerActions)) return headerActions;
6210
6811
  return /* @__PURE__ */ React13.createElement(Inline3, { gap: "sm", align: "center" }, headerActions.filter(Boolean).map((action, index) => /* @__PURE__ */ React13.createElement(
6211
6812
  Link5,
@@ -6227,6 +6828,8 @@ var DefaultFeedItem = ({
6227
6828
  collapsible,
6228
6829
  expanded,
6229
6830
  onToggleExpanded,
6831
+ isNew,
6832
+ newItemTagLabel,
6230
6833
  renderActor,
6231
6834
  renderTimestamp,
6232
6835
  renderMeta,
@@ -6246,7 +6849,7 @@ var DefaultFeedItem = ({
6246
6849
  const body = pickBody(item);
6247
6850
  const avatar = /* @__PURE__ */ React13.createElement(FeedActorAvatar, { item, avatarSize });
6248
6851
  const typeIcon = /* @__PURE__ */ React13.createElement(FeedTypeIcon, { item, iconSize });
6249
- const hasAvatarNode = hasValue(item == null ? void 0 : item.avatar) || hasValue(pickActorAvatar(rawActor));
6852
+ const hasAvatarNode = hasValue2(item == null ? void 0 : item.avatar) || hasValue2(pickActorAvatar(rawActor));
6250
6853
  const titleFields = fieldsForPlacement(fields, "title");
6251
6854
  const titleField = titleFields.length > 0 ? /* @__PURE__ */ React13.createElement(FeedField, { field: titleFields[0], item, index }) : null;
6252
6855
  const subtitleFields = renderPlacedFields({ fields, placement: "subtitle", item, index, inline: true });
@@ -6254,8 +6857,8 @@ var DefaultFeedItem = ({
6254
6857
  const bodyFields = renderPlacedFields({ fields, placement: "body", item, index });
6255
6858
  const footerFields = renderPlacedFields({ fields, placement: "footer", item, index, inline: true });
6256
6859
  const titleContent = titleField ?? (item == null ? void 0 : item.title) ?? (item == null ? void 0 : item.subject);
6257
- const title = hasValue(item == null ? void 0 : item.href) ? /* @__PURE__ */ React13.createElement(Link5, { href: item.href }, titleContent) : titleContent;
6258
- const titleText = hasValue(title) ? /* @__PURE__ */ React13.createElement(Text6, { format: { fontWeight: "demibold" }, truncate: true }, title) : null;
6860
+ const title = hasValue2(item == null ? void 0 : item.href) ? /* @__PURE__ */ React13.createElement(Link5, { href: item.href }, titleContent) : titleContent;
6861
+ const titleText = hasValue2(title) ? /* @__PURE__ */ React13.createElement(Text6, { format: { fontWeight: "demibold" }, truncate: true }, title) : null;
6259
6862
  const headerLeft = /* @__PURE__ */ React13.createElement(Flex8, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, collapsible ? /* @__PURE__ */ React13.createElement(Box5, { flex: "none", alignSelf: "center" }, /* @__PURE__ */ React13.createElement(Link5, { variant: "dark", onClick: onToggleExpanded }, /* @__PURE__ */ React13.createElement(
6260
6863
  Icon,
6261
6864
  {
@@ -6263,10 +6866,10 @@ var DefaultFeedItem = ({
6263
6866
  size: "md",
6264
6867
  screenReaderText: expanded ? "Collapse" : "Expand"
6265
6868
  }
6266
- ))) : null, typeIcon, titleText);
6267
- const headerRight = hasValue(headerActions) || hasValue(timestamp) || hasValue(type) ? /* @__PURE__ */ React13.createElement(Inline3, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue(type) && /* @__PURE__ */ React13.createElement(Text6, { variant: "microcopy" }, type), hasValue(timestamp) && /* @__PURE__ */ React13.createElement(Text6, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
6869
+ ))) : null, typeIcon, titleText, isNew ? /* @__PURE__ */ React13.createElement(Box5, { flex: "none" }, /* @__PURE__ */ React13.createElement(Tag4, { variant: "info" }, newItemTagLabel ?? "New")) : null);
6870
+ const headerRight = hasValue2(headerActions) || hasValue2(timestamp) || hasValue2(type) ? /* @__PURE__ */ React13.createElement(Inline3, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue2(type) && /* @__PURE__ */ React13.createElement(Text6, { variant: "microcopy" }, type), hasValue2(timestamp) && /* @__PURE__ */ React13.createElement(Text6, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
6268
6871
  const showBody = !collapsible || expanded;
6269
- return /* @__PURE__ */ React13.createElement(Flex8, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ React13.createElement(Flex8, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ React13.createElement(Box5, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ React13.createElement(Flex8, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue(actor) || hasValue(subtitleFields) || hasValue(status)) && /* @__PURE__ */ React13.createElement(Flex8, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue(actor) && /* @__PURE__ */ React13.createElement(Text6, { variant: "microcopy" }, actor), subtitleFields, hasValue(status) && /* @__PURE__ */ React13.createElement(StatusTag, { variant: statusVariant, hollow: true }, status)), hasValue(body) && /* @__PURE__ */ React13.createElement(Text6, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ React13.createElement(List, { variant: "inline-divided" }, meta) : metaFields : hasValue(meta) ? /* @__PURE__ */ React13.createElement(Inline3, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue(actions) && /* @__PURE__ */ React13.createElement(FeedActions, { actions }), (hasValue(footer) || hasValue(footerFields)) && /* @__PURE__ */ React13.createElement(Inline3, { gap: "xs", align: "center" }, footerFields, footer)) : null);
6872
+ return /* @__PURE__ */ React13.createElement(Flex8, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ React13.createElement(Flex8, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ React13.createElement(Box5, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ React13.createElement(Flex8, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue2(actor) || hasValue2(subtitleFields) || hasValue2(status)) && /* @__PURE__ */ React13.createElement(Flex8, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue2(actor) && /* @__PURE__ */ React13.createElement(Text6, { variant: "microcopy" }, actor), subtitleFields, hasValue2(status) && /* @__PURE__ */ React13.createElement(StatusTag2, { variant: statusVariant, hollow: true }, status)), hasValue2(body) && /* @__PURE__ */ React13.createElement(Text6, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ React13.createElement(List, { variant: "inline-divided" }, meta) : metaFields : hasValue2(meta) ? /* @__PURE__ */ React13.createElement(Inline3, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue2(actions) && /* @__PURE__ */ React13.createElement(FeedActions, { actions }), (hasValue2(footer) || hasValue2(footerFields)) && /* @__PURE__ */ React13.createElement(Inline3, { gap: "xs", align: "center" }, footerFields, footer)) : null);
6270
6873
  };
6271
6874
  var applyTab = (items, activeTab, tabField) => {
6272
6875
  if (!activeTab || activeTab === "all") return items;
@@ -6490,10 +7093,19 @@ var Feed = ({
6490
7093
  collapsedIds,
6491
7094
  onCollapsedIdsChange,
6492
7095
  showCollapseToggle = true,
6493
- alignToolbarWithGroups = "auto"
7096
+ alignToolbarWithGroups = "auto",
7097
+ newItemsBehavior = "immediate",
7098
+ onNewItemsFlush,
7099
+ highlightNew = false,
7100
+ typePresets
6494
7101
  }) => {
6495
- const labels = { ...DEFAULT_LABELS4, ...labelOverrides || {} };
6496
- const safeItems = Array.isArray(items) ? items : [];
7102
+ const labels = useMemo4(() => ({ ...DEFAULT_LABELS4, ...labelOverrides || {} }), [labelOverrides]);
7103
+ const safeItems = Array.isArray(items) ? items : EMPTY_ITEMS;
7104
+ const resolvedTypePresets = typePresets === true ? DEFAULT_FEED_TYPE_PRESETS : typePresets;
7105
+ const presetItems = useMemo4(
7106
+ () => resolvedTypePresets ? safeItems.map((item) => applyTypePreset(item, resolvedTypePresets)) : safeItems,
7107
+ [safeItems, resolvedTypePresets]
7108
+ );
6497
7109
  const [internalTab, setInternalTab] = useState5(tabValue ?? defaultTab ?? "all");
6498
7110
  const [internalSearch, setInternalSearch] = useState5(searchValue ?? "");
6499
7111
  const [internalFilters, setInternalFilters] = useState5(filterValues ?? defaultFilterValues);
@@ -6506,6 +7118,47 @@ var Feed = ({
6506
7118
  return [];
6507
7119
  };
6508
7120
  const [internalCollapsedIds, setInternalCollapsedIds] = useState5(computeInitialCollapsed);
7121
+ const bufferNewItems = newItemsBehavior === "pill";
7122
+ const highlightMs = typeof highlightNew === "number" && highlightNew > 0 ? highlightNew : 0;
7123
+ const trackNewItems = bufferNewItems || highlightMs > 0;
7124
+ const [liveState, setLiveState] = useState5(INITIAL_LIVE_STATE);
7125
+ const liveKeyOf = (item, index) => getItemKey(item, index, getKey);
7126
+ if (trackNewItems && liveState.source !== presetItems) {
7127
+ const firstLoad = liveState.source === null;
7128
+ const partition = partitionNewItems(liveState.watermark, presetItems, pickTimestamp, {
7129
+ knownIds: new Set(liveState.knownKeys),
7130
+ getId: liveKeyOf
7131
+ });
7132
+ const now = Date.now();
7133
+ const keptNewKeys = highlightMs > 0 ? liveState.newKeys.filter((entry) => now - entry.at < highlightMs) : EMPTY_ITEMS;
7134
+ if (bufferNewItems) {
7135
+ setLiveState({
7136
+ source: presetItems,
7137
+ watermark: partition.newestTs,
7138
+ bufferedKeys: partition.bufferedIds,
7139
+ knownKeys: partition.visibleIds,
7140
+ newKeys: keptNewKeys
7141
+ });
7142
+ } else {
7143
+ const { newestTs } = flushBuffer(partition.visible, partition.buffered, pickTimestamp);
7144
+ setLiveState({
7145
+ source: presetItems,
7146
+ watermark: newestTs ?? partition.newestTs,
7147
+ bufferedKeys: EMPTY_ITEMS,
7148
+ knownKeys: [...partition.visibleIds, ...partition.bufferedIds],
7149
+ newKeys: highlightMs > 0 && !firstLoad ? [...keptNewKeys, ...partition.bufferedIds.map((key) => ({ key, at: now }))] : keptNewKeys
7150
+ });
7151
+ }
7152
+ }
7153
+ const bufferedKeySet = useMemo4(() => new Set(liveState.bufferedKeys), [liveState.bufferedKeys]);
7154
+ const newKeySet = useMemo4(
7155
+ () => new Set(liveState.newKeys.map((entry) => entry.key)),
7156
+ [liveState.newKeys]
7157
+ );
7158
+ const sourceItems = useMemo4(() => {
7159
+ if (!bufferNewItems || bufferedKeySet.size === 0) return presetItems;
7160
+ return presetItems.filter((item, index) => !bufferedKeySet.has(getItemKey(item, index, getKey)));
7161
+ }, [presetItems, bufferNewItems, bufferedKeySet, getKey]);
6509
7162
  const activeTab = tabValue !== void 0 ? tabValue : internalTab;
6510
7163
  const activeSearch = searchValue !== void 0 ? searchValue : internalSearch;
6511
7164
  const activeFilters = filterValues !== void 0 ? filterValues : internalFilters;
@@ -6528,6 +7181,17 @@ var Feed = ({
6528
7181
  useEffect5(() => {
6529
7182
  if (Array.isArray(collapsedIds)) setInternalCollapsedIds(collapsedIds);
6530
7183
  }, [collapsedIds]);
7184
+ useEffect5(() => {
7185
+ if (!highlightMs || liveState.newKeys.length === 0) return void 0;
7186
+ const earliestAt = Math.min(...liveState.newKeys.map((entry) => entry.at));
7187
+ const timer = setTimeout(() => {
7188
+ setLiveState((prev) => ({
7189
+ ...prev,
7190
+ newKeys: prev.newKeys.filter((entry) => Date.now() - entry.at < highlightMs)
7191
+ }));
7192
+ }, Math.max(16, earliestAt + highlightMs - Date.now()));
7193
+ return () => clearTimeout(timer);
7194
+ }, [highlightMs, liveState.newKeys]);
6531
7195
  const emitParamsChange = (next) => {
6532
7196
  if (typeof onParamsChange === "function") {
6533
7197
  onParamsChange({ tab: activeTab, search: activeSearch, filters: activeFilters, sort: activeSort, ...next });
@@ -6581,13 +7245,40 @@ var Feed = ({
6581
7245
  setCollapsedIds(collapsibleKeys);
6582
7246
  };
6583
7247
  const handleExpandAll = () => setCollapsedIds([]);
7248
+ const handleFlushNewItems = () => {
7249
+ const flushedItems = [];
7250
+ const flushedKeys = [];
7251
+ const keptItems = [];
7252
+ presetItems.forEach((item, index) => {
7253
+ const key = getItemKey(item, index, getKey);
7254
+ if (bufferedKeySet.has(key)) {
7255
+ flushedItems.push(item);
7256
+ flushedKeys.push(key);
7257
+ } else {
7258
+ keptItems.push(item);
7259
+ }
7260
+ });
7261
+ const { newestTs } = flushBuffer(keptItems, flushedItems, pickTimestamp);
7262
+ const now = Date.now();
7263
+ setLiveState((prev) => ({
7264
+ source: presetItems,
7265
+ watermark: newestTs ?? prev.watermark,
7266
+ bufferedKeys: EMPTY_ITEMS,
7267
+ knownKeys: presetItems.map((item, index) => getItemKey(item, index, getKey)),
7268
+ newKeys: highlightMs > 0 ? [
7269
+ ...prev.newKeys.filter((entry) => now - entry.at < highlightMs),
7270
+ ...flushedKeys.map((key) => ({ key, at: now }))
7271
+ ] : prev.newKeys
7272
+ }));
7273
+ onNewItemsFlush == null ? void 0 : onNewItemsFlush(flushedItems);
7274
+ };
6584
7275
  const processedItems = useMemo4(() => {
6585
- if (serverSide) return safeItems;
6586
- const tabbed = applyTab(safeItems, activeTab, tabField);
7276
+ if (serverSide) return sourceItems;
7277
+ const tabbed = applyTab(sourceItems, activeTab, tabField);
6587
7278
  const searched = applySearch(tabbed, activeSearch, searchFields);
6588
7279
  const filtered = applyFilters(searched, filters, activeFilters);
6589
7280
  return applySort(filtered, activeSort, sortOptions);
6590
- }, [safeItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
7281
+ }, [sourceItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
6591
7282
  const visibleItems = useMemo4(
6592
7283
  () => processedItems.slice(0, Math.max(0, resolvedMaxItems)),
6593
7284
  [processedItems, resolvedMaxItems]
@@ -6614,8 +7305,8 @@ var Feed = ({
6614
7305
  const canViewMore = visibleItems.length < processedItems.length;
6615
7306
  const shouldShowExternalLoadMore = hasMore && onLoadMore;
6616
7307
  const normalizedTabs = useMemo4(
6617
- () => normalizeTabs(tabs, safeItems, tabField, labels),
6618
- [tabs, safeItems, tabField, labels]
7308
+ () => normalizeTabs(tabs, presetItems, tabField, labels),
7309
+ [tabs, presetItems, tabField, labels]
6619
7310
  );
6620
7311
  const resolvedShowTabs = showTabs ?? normalizedTabs.length > 1;
6621
7312
  const sortControl = Array.isArray(sortOptions) && sortOptions.length > 0 ? /* @__PURE__ */ React13.createElement(
@@ -6630,7 +7321,7 @@ var Feed = ({
6630
7321
  ) : null;
6631
7322
  const countControl = showItemCount ? /* @__PURE__ */ React13.createElement(CollectionCount, { text: itemCountLabel }) : null;
6632
7323
  const toolbarRight = sortControl || countControl ? /* @__PURE__ */ React13.createElement(Inline3, { gap: "sm", align: "center" }, sortControl, countControl) : null;
6633
- const firstGroupHasLabel = groups.length > 0 && hasValue(groups[0].label);
7324
+ const firstGroupHasLabel = groups.length > 0 && hasValue2(groups[0].label);
6634
7325
  const hasLeftToolbarControls = resolvedShowSearch || Array.isArray(filters) && filters.length > 0 || activeChips.length > 0;
6635
7326
  const alignControlsWithFirstGroup = !renderToolbar && showToolbar && !loading && !error && processedItems.length > 0 && !hasLeftToolbarControls && !!toolbarRight && firstGroupHasLabel && (alignToolbarWithGroups === true || alignToolbarWithGroups === "auto" && (groupByDate || !!groupBy));
6636
7327
  const toolbarNode = renderToolbar ? renderToolbar({
@@ -6680,15 +7371,21 @@ var Feed = ({
6680
7371
  return activeCollapsedIds.includes(getItemKey(item, i >= 0 ? i : idx, getKey));
6681
7372
  });
6682
7373
  const collapseToggle = showCollapseToggle && collapsibleVisibleItems.length > 1 && !loading && !error ? /* @__PURE__ */ React13.createElement(Link5, { onClick: allCollapsed ? handleExpandAll : handleCollapseAll }, keepWordsTogether(allCollapsed ? labels.expandAll : labels.collapseAll)) : null;
6683
- if (hasValue(title) || hasValue(description) || hasValue(actions) || hasValue(children) || collapseToggle) {
6684
- const headerBody = /* @__PURE__ */ React13.createElement(Flex8, { direction: "column", gap: "xs" }, hasValue(title) && /* @__PURE__ */ React13.createElement(Text6, { format: { fontWeight: "demibold" } }, title), hasValue(description) && /* @__PURE__ */ React13.createElement(Text6, null, description), children);
6685
- const headerRight = hasValue(actions) || collapseToggle ? /* @__PURE__ */ React13.createElement(Inline3, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
7374
+ if (hasValue2(title) || hasValue2(description) || hasValue2(actions) || hasValue2(children) || collapseToggle) {
7375
+ const headerBody = /* @__PURE__ */ React13.createElement(Flex8, { direction: "column", gap: "xs" }, hasValue2(title) && /* @__PURE__ */ React13.createElement(Text6, { format: { fontWeight: "demibold" } }, title), hasValue2(description) && /* @__PURE__ */ React13.createElement(Text6, null, description), children);
7376
+ const headerRight = hasValue2(actions) || collapseToggle ? /* @__PURE__ */ React13.createElement(Inline3, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
6686
7377
  content.push(
6687
7378
  /* @__PURE__ */ React13.createElement(Flex8, { key: "header", direction: "row", justify: "between", align: "start", gap: "sm" }, headerBody, headerRight)
6688
7379
  );
6689
7380
  }
6690
7381
  const bodyContent = [];
6691
7382
  if (toolbarNode) bodyContent.push(/* @__PURE__ */ React13.createElement(React13.Fragment, { key: "toolbar" }, toolbarNode));
7383
+ const pendingNewCount = bufferNewItems ? liveState.bufferedKeys.length : 0;
7384
+ if (pendingNewCount > 0 && !loading && !error) {
7385
+ bodyContent.push(
7386
+ /* @__PURE__ */ React13.createElement(Flex8, { key: "new-items-pill", direction: "row", justify: "center" }, /* @__PURE__ */ React13.createElement(Button7, { variant: "secondary", size: "small", onClick: handleFlushNewItems }, typeof labels.newItems === "function" ? labels.newItems(pendingNewCount) : labels.newItems))
7387
+ );
7388
+ }
6692
7389
  if (loading) {
6693
7390
  bodyContent.push(
6694
7391
  renderLoadingState ? renderLoadingState({ label: labels.loading }) : (
@@ -6705,14 +7402,14 @@ var Feed = ({
6705
7402
  message: labels.errorMessage
6706
7403
  }) : /* @__PURE__ */ React13.createElement(Alert3, { key: "error", variant: "danger", title: typeof error === "string" ? error : labels.errorTitle }, /* @__PURE__ */ React13.createElement(Text6, null, labels.errorMessage))
6707
7404
  );
6708
- } else if (processedItems.length === 0) {
7405
+ } else if (processedItems.length === 0 && pendingNewCount === 0) {
6709
7406
  bodyContent.push(
6710
7407
  renderEmptyState ? renderEmptyState({ title: labels.emptyTitle, message: labels.emptyMessage }) : /* @__PURE__ */ React13.createElement(Tile4, { key: "empty" }, /* @__PURE__ */ React13.createElement(Flex8, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React13.createElement(EmptyState3, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ React13.createElement(Text6, null, labels.emptyMessage))))
6711
7408
  );
6712
7409
  } else {
6713
7410
  bodyContent.push(
6714
- /* @__PURE__ */ React13.createElement(Flex8, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group, groupIndex) => /* @__PURE__ */ React13.createElement(Flex8, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue(group.label) && (alignControlsWithFirstGroup && groupIndex === 0 ? /* @__PURE__ */ React13.createElement(Flex8, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ React13.createElement(Text6, { format: { fontWeight: "demibold" } }, group.label), toolbarRight) : /* @__PURE__ */ React13.createElement(Text6, { format: { fontWeight: "demibold" } }, group.label)), group.items.map((item, index) => {
6715
- const globalIndex = safeItems.indexOf(item);
7411
+ /* @__PURE__ */ React13.createElement(Flex8, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group, groupIndex) => /* @__PURE__ */ React13.createElement(Flex8, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue2(group.label) && (alignControlsWithFirstGroup && groupIndex === 0 ? /* @__PURE__ */ React13.createElement(Flex8, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ React13.createElement(Text6, { format: { fontWeight: "demibold" } }, group.label), toolbarRight) : /* @__PURE__ */ React13.createElement(Text6, { format: { fontWeight: "demibold" } }, group.label)), group.items.map((item, index) => {
7412
+ const globalIndex = presetItems.indexOf(item);
6716
7413
  const itemIndex = globalIndex >= 0 ? globalIndex : index;
6717
7414
  const key = getItemKey(item, itemIndex, getKey);
6718
7415
  const node = renderItem ? renderItem(item, itemIndex) : /* @__PURE__ */ React13.createElement(
@@ -6727,6 +7424,8 @@ var Feed = ({
6727
7424
  collapsible: collapsible !== false && itemHasExpandableContent(item, fields),
6728
7425
  expanded: !activeCollapsedIds.includes(key),
6729
7426
  onToggleExpanded: () => toggleItemExpanded(key),
7427
+ isNew: highlightMs > 0 && newKeySet.has(key),
7428
+ newItemTagLabel: labels.newItemTag,
6730
7429
  renderActor,
6731
7430
  renderTimestamp,
6732
7431
  renderMeta,
@@ -6773,6 +7472,7 @@ var Feed = ({
6773
7472
  if (container === "card" || container === "tile") return /* @__PURE__ */ React13.createElement(Tile4, { compact: true }, feed);
6774
7473
  return feed;
6775
7474
  };
7475
+ Feed.displayName = "Feed";
6776
7476
 
6777
7477
  // src/calendar/Calendar.jsx
6778
7478
  import React14, { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo5, useState as useState6 } from "react";
@@ -6781,6 +7481,7 @@ import {
6781
7481
  AutoGrid as AutoGrid3,
6782
7482
  Box as Box6,
6783
7483
  Button as Button8,
7484
+ DateInput as DateInput4,
6784
7485
  Divider as Divider4,
6785
7486
  EmptyState as EmptyState4,
6786
7487
  Flex as Flex9,
@@ -6788,9 +7489,9 @@ import {
6788
7489
  Image as Image5,
6789
7490
  Inline as Inline4,
6790
7491
  Link as Link6,
6791
- LoadingSpinner as LoadingSpinner2,
6792
- Modal,
6793
- ModalBody,
7492
+ LoadingSpinner as LoadingSpinner3,
7493
+ Modal as Modal2,
7494
+ ModalBody as ModalBody2,
6794
7495
  Panel,
6795
7496
  PanelBody,
6796
7497
  Select as Select6,
@@ -6800,7 +7501,7 @@ import {
6800
7501
  TableHead as TableHead2,
6801
7502
  TableHeader as TableHeader2,
6802
7503
  TableRow as TableRow2,
6803
- StatusTag as StatusTag2,
7504
+ StatusTag as StatusTag3,
6804
7505
  Tag as Tag5,
6805
7506
  Text as Text7,
6806
7507
  Tile as Tile5
@@ -6809,7 +7510,7 @@ import { Popover } from "@hubspot/ui-extensions/experimental";
6809
7510
 
6810
7511
  // src/calendar/dateUtils.js
6811
7512
  var MS_PER_DAY = 864e5;
6812
- var isDateValueObject2 = (v) => v != null && typeof v === "object" && typeof v.year === "number" && typeof v.month === "number" && typeof v.date === "number";
7513
+ var isDateValueObject3 = (v) => v != null && typeof v === "object" && typeof v.year === "number" && typeof v.month === "number" && typeof v.date === "number";
6813
7514
  var fromEpoch = (ms) => {
6814
7515
  const d = new Date(ms);
6815
7516
  if (Number.isNaN(d.getTime())) return null;
@@ -6819,7 +7520,7 @@ var fromEpoch = (ms) => {
6819
7520
  var toDate2 = (value) => {
6820
7521
  if (value == null || value === "") return null;
6821
7522
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
6822
- if (isDateValueObject2(value)) {
7523
+ if (isDateValueObject3(value)) {
6823
7524
  return new Date(
6824
7525
  value.year,
6825
7526
  value.month,
@@ -6914,7 +7615,7 @@ var buildHours = (startHour = 8, endHour = 20) => {
6914
7615
  let e = Math.max(0, Math.min(23, Math.round(endHour)));
6915
7616
  if (s > e) [s, e] = [e, s];
6916
7617
  const hours = [];
6917
- for (let h6 = s; h6 <= e; h6 += 1) hours.push(h6);
7618
+ for (let h7 = s; h7 <= e; h7 += 1) hours.push(h7);
6918
7619
  return hours;
6919
7620
  };
6920
7621
  var WEEKDAY_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
@@ -7029,9 +7730,172 @@ var makeSpacerDataUri = (height = 24, width = 4) => {
7029
7730
  return { src: toDataUri(svg), width, height };
7030
7731
  };
7031
7732
 
7733
+ // src/calendar/rescheduleUtils.js
7734
+ var shiftDate = (date, shift) => {
7735
+ const d = toDate2(date);
7736
+ if (!d) return null;
7737
+ if (!shift || typeof shift !== "object") return new Date(d);
7738
+ const days = (shift.days || 0) + (shift.weeks || 0) * 7;
7739
+ let next = days ? addDays(d, days) : new Date(d);
7740
+ const ms = (shift.hours || 0) * 36e5 + (shift.minutes || 0) * 6e4;
7741
+ if (ms) next = new Date(next.getTime() + ms);
7742
+ return next;
7743
+ };
7744
+ var shiftEvent = (range, shift) => {
7745
+ const start = toDate2(range && range.start);
7746
+ if (!start) return null;
7747
+ const end = toDate2(range && range.end) || start;
7748
+ return { start: shiftDate(start, shift), end: shiftDate(end, shift) };
7749
+ };
7750
+ var calendarDayDelta = (a, b) => Math.round((startOfDay(b).getTime() - startOfDay(a).getTime()) / MS_PER_DAY);
7751
+ var msIntoDay = (d) => ((d.getHours() * 60 + d.getMinutes()) * 60 + d.getSeconds()) * 1e3 + d.getMilliseconds();
7752
+ var rescheduleToStart = (range, newStart) => {
7753
+ const start = toDate2(range && range.start);
7754
+ const target = toDate2(newStart);
7755
+ if (!start || !target) return null;
7756
+ const end = toDate2(range && range.end) || start;
7757
+ const dayDelta = calendarDayDelta(start, target);
7758
+ const timeDelta = msIntoDay(target) - msIntoDay(start);
7759
+ const endOnDay = addDays(end, dayDelta);
7760
+ if (!timeDelta) return { start: target, end: endOnDay };
7761
+ return {
7762
+ start: target,
7763
+ end: new Date(
7764
+ endOnDay.getFullYear(),
7765
+ endOnDay.getMonth(),
7766
+ endOnDay.getDate(),
7767
+ 0,
7768
+ 0,
7769
+ 0,
7770
+ msIntoDay(endOnDay) + timeDelta
7771
+ )
7772
+ };
7773
+ };
7774
+ var applyDatePick = (start, value) => {
7775
+ if (!isDateValueObject3(value)) return null;
7776
+ const s = toDate2(start);
7777
+ return new Date(
7778
+ value.year,
7779
+ value.month,
7780
+ value.date,
7781
+ s ? s.getHours() : 0,
7782
+ s ? s.getMinutes() : 0,
7783
+ s ? s.getSeconds() : 0,
7784
+ s ? s.getMilliseconds() : 0
7785
+ );
7786
+ };
7787
+ var DEFAULT_RESCHEDULE_PRESETS = [
7788
+ { label: "+1 hour", shift: { hours: 1 } },
7789
+ { label: "+1 day", shift: { days: 1 } },
7790
+ { label: "Next week", shift: { weeks: 1 } }
7791
+ ];
7792
+ var normalizeRescheduleOptions = (options) => {
7793
+ if (!options) return [];
7794
+ if (options === true) return DEFAULT_RESCHEDULE_PRESETS;
7795
+ if (!Array.isArray(options)) return [];
7796
+ const out = [];
7797
+ for (const opt of options) {
7798
+ if (typeof opt === "function") {
7799
+ out.push({ label: opt.label || opt.name || "Reschedule", getStart: opt });
7800
+ } else if (opt && typeof opt === "object" && opt.label != null) {
7801
+ if (typeof opt.shift === "function") {
7802
+ out.push({ label: opt.label, getStart: opt.shift });
7803
+ } else if (typeof opt.getStart === "function") {
7804
+ out.push({ label: opt.label, getStart: opt.getStart });
7805
+ } else if (opt.shift && typeof opt.shift === "object") {
7806
+ out.push({ label: opt.label, shift: opt.shift });
7807
+ }
7808
+ }
7809
+ }
7810
+ return out;
7811
+ };
7812
+ var resolveRescheduleTarget = (range, option, fnArg) => {
7813
+ if (!range || !toDate2(range.start) || !option) return null;
7814
+ if (typeof option.getStart === "function") {
7815
+ const next = toDate2(option.getStart(fnArg !== void 0 ? fnArg : range));
7816
+ return next ? rescheduleToStart(range, next) : null;
7817
+ }
7818
+ if (option.shift && typeof option.shift === "object") {
7819
+ return shiftEvent(range, option.shift);
7820
+ }
7821
+ return null;
7822
+ };
7823
+
7824
+ // src/calendar/resourceLanes.js
7825
+ var resolveResourceId = (record, spec) => {
7826
+ if (record == null || spec == null) return null;
7827
+ const value = typeof spec === "function" ? spec(record) : record[spec];
7828
+ if (value == null || value === "") return null;
7829
+ return value;
7830
+ };
7831
+ var buildResourceLanes = (events, options = {}) => {
7832
+ const {
7833
+ resources,
7834
+ resourceLabels,
7835
+ getId,
7836
+ showUnassignedLane = true,
7837
+ unassignedLabel = "Unassigned"
7838
+ } = options;
7839
+ const labelFor = (id) => {
7840
+ if (resourceLabels && resourceLabels[id] != null) return resourceLabels[id];
7841
+ return String(id);
7842
+ };
7843
+ const lanes = [];
7844
+ const byKey = /* @__PURE__ */ new Map();
7845
+ const addLane = (id, label, declared) => {
7846
+ const key = String(id);
7847
+ if (byKey.has(key)) return byKey.get(key);
7848
+ const lane = { id, key, label, events: [], unassigned: false, declared };
7849
+ byKey.set(key, lane);
7850
+ lanes.push(lane);
7851
+ return lane;
7852
+ };
7853
+ (resources || []).forEach((resource) => {
7854
+ if (resource == null) return;
7855
+ if (typeof resource === "object") {
7856
+ addLane(resource.id, resource.label != null ? resource.label : labelFor(resource.id), true);
7857
+ } else {
7858
+ addLane(resource, labelFor(resource), true);
7859
+ }
7860
+ });
7861
+ const unassigned = {
7862
+ id: null,
7863
+ key: "__unassigned__",
7864
+ label: unassignedLabel,
7865
+ events: [],
7866
+ unassigned: true,
7867
+ declared: false
7868
+ };
7869
+ (events || []).forEach((event) => {
7870
+ const id = getId ? getId(event) : null;
7871
+ if (id == null || id === "") {
7872
+ unassigned.events.push(event);
7873
+ return;
7874
+ }
7875
+ const lane = byKey.get(String(id)) || addLane(id, labelFor(id), false);
7876
+ lane.events.push(event);
7877
+ });
7878
+ if (showUnassignedLane && unassigned.events.length > 0) lanes.push(unassigned);
7879
+ return lanes;
7880
+ };
7881
+ var eventsIntersectingRange = (events, rangeStart, rangeEnd) => {
7882
+ const rs = rangeStart.getTime();
7883
+ const re = rangeEnd.getTime();
7884
+ return (events || []).filter((event) => {
7885
+ if (!event || !event.start) return false;
7886
+ const es = event.start.getTime();
7887
+ const ee = (event.end || event.start).getTime();
7888
+ return es <= re && ee >= rs;
7889
+ });
7890
+ };
7891
+ var laneEventsForDay = (events, day) => eventsIntersectingRange(events, startOfDay(day), endOfDay(day)).sort(
7892
+ (a, b) => a.start.getTime() - b.start.getTime()
7893
+ );
7894
+
7032
7895
  // src/calendar/Calendar.jsx
7033
7896
  var DEFAULT_MAX_EVENTS_PER_DAY = 3;
7034
7897
  var ALL_VIEWS = ["month", "week", "day", "agenda"];
7898
+ var ALL_VIEWS_WITH_RESOURCE = ["month", "week", "day", "resource", "agenda"];
7035
7899
  var DEFAULT_DAY_START_HOUR = 8;
7036
7900
  var DEFAULT_DAY_END_HOUR = 20;
7037
7901
  var DEFAULT_TIME_ZONES = [
@@ -7067,7 +7931,8 @@ var VIEW_LABELS = {
7067
7931
  month: "Month",
7068
7932
  week: "Week",
7069
7933
  day: "Day",
7070
- agenda: "Agenda"
7934
+ agenda: "Agenda",
7935
+ resource: "Resource"
7071
7936
  };
7072
7937
  var DEFAULT_LABELS5 = {
7073
7938
  today: "Today",
@@ -7084,7 +7949,11 @@ var DEFAULT_LABELS5 = {
7084
7949
  errorMessage: "An error occurred while loading events.",
7085
7950
  dayDetailTitle: (label) => label,
7086
7951
  open: "Open",
7087
- allDay: "All day"
7952
+ allDay: "All day",
7953
+ reschedule: "Reschedule",
7954
+ pickDate: "Pick date",
7955
+ unassigned: "Unassigned",
7956
+ resource: "Resource"
7088
7957
  };
7089
7958
  var DEFAULT_EVENT_FIELDS = {
7090
7959
  id: "id",
@@ -7140,8 +8009,9 @@ var truncateMonthLabel = (value, max) => {
7140
8009
  var MONTH_COL_WIDTH = 160;
7141
8010
  var TIMEGRID_DAY_COL = 150;
7142
8011
  var TIMEGRID_DAY_COL_SINGLE = 560;
8012
+ var RESOURCE_LABEL_COL_WIDTH = "min";
7143
8013
  var HOUR_SLOT_HEIGHT = 64;
7144
- var EventDetail = ({ event, labels }) => {
8014
+ var EventDetail = ({ event, labels, reschedule, idSuffix = "" }) => {
7145
8015
  const { start, end, title, subtitle, href } = event;
7146
8016
  let when = "";
7147
8017
  if (start) {
@@ -7155,31 +8025,47 @@ var EventDetail = ({ event, labels }) => {
7155
8025
  when = `${formatDayTitle(start)} \u2013 ${formatDayTitle(end)}`;
7156
8026
  }
7157
8027
  }
7158
- return /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "demibold" }, truncate: true }, title || "--"), when ? /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy", truncate: true }, when) : null, subtitle ? /* @__PURE__ */ React14.createElement(Text7, { truncate: true }, subtitle) : null, href ? /* @__PURE__ */ React14.createElement(Link6, { href: href.url, external: href.external }, labels.open) : null);
8028
+ return /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "demibold" }, truncate: true }, title || "--"), when ? /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy", truncate: true }, when) : null, subtitle ? /* @__PURE__ */ React14.createElement(Text7, { truncate: true }, subtitle) : null, href ? /* @__PURE__ */ React14.createElement(Link6, { href: href.url, external: href.external }, labels.open) : null, reschedule ? /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(Divider4, null), /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy", format: { fontWeight: "demibold" } }, labels.reschedule), reschedule.options.length > 0 ? /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", gap: "xs", wrap: "wrap" }, reschedule.options.map((option, i) => /* @__PURE__ */ React14.createElement(
8029
+ Button8,
8030
+ {
8031
+ key: `${option.label}-${i}`,
8032
+ size: "xs",
8033
+ variant: "secondary",
8034
+ onClick: () => reschedule.onPreset(event, option)
8035
+ },
8036
+ option.label
8037
+ ))) : null, /* @__PURE__ */ React14.createElement(
8038
+ DateInput4,
8039
+ {
8040
+ name: `cal-resched-${event.key}${idSuffix}`,
8041
+ label: labels.pickDate,
8042
+ onChange: (value) => reschedule.onPick(event, value)
8043
+ }
8044
+ )) : null);
7159
8045
  };
7160
- var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "") => {
8046
+ var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "", reschedule = null) => {
7161
8047
  if (mode === "none") return void 0;
7162
- const body = renderEventDetail ? renderEventDetail(event) : /* @__PURE__ */ React14.createElement(EventDetail, { event, labels });
8048
+ const body = renderEventDetail ? renderEventDetail(event) : /* @__PURE__ */ React14.createElement(EventDetail, { event, labels, reschedule, idSuffix });
7163
8049
  const id = `cal-evt-${event.key}${idSuffix}`;
7164
8050
  if (mode === "modal") {
7165
- return /* @__PURE__ */ React14.createElement(Modal, { id, title: event.title || labels.open, width: "small" }, /* @__PURE__ */ React14.createElement(ModalBody, null, body));
8051
+ return /* @__PURE__ */ React14.createElement(Modal2, { id, title: event.title || labels.open, width: "small" }, /* @__PURE__ */ React14.createElement(ModalBody2, null, body));
7166
8052
  }
7167
8053
  if (mode === "panel") {
7168
8054
  return /* @__PURE__ */ React14.createElement(Panel, { id, title: event.title || labels.open, width: "small", variant: "modal" }, /* @__PURE__ */ React14.createElement(PanelBody, null, body));
7169
8055
  }
7170
8056
  return /* @__PURE__ */ React14.createElement(Popover, { id, placement: "bottom", variant: "longform" }, /* @__PURE__ */ React14.createElement(Tile5, { compact: true }, body));
7171
8057
  };
7172
- var AgendaEventRow = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8058
+ var AgendaEventRow = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, reschedule }) => {
7173
8059
  const variant = VALID_VARIANTS.has(event.color) ? event.color : "default";
7174
- const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-ag${day.getTime()}` : "");
8060
+ const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-ag${day.getTime()}` : "", reschedule);
7175
8061
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7176
8062
  const timeLabel = isAllDayEvent(event) ? labels.allDay : formatTime(event.start);
7177
8063
  return /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", align: "center", gap: "sm" }, /* @__PURE__ */ React14.createElement(Box6, { flex: 2 }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", align: "center" }, /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy", format: { fontWeight: "demibold" } }, timeLabel))), /* @__PURE__ */ React14.createElement(Box6, { flex: 11 }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React14.createElement(ColorDot, { variant }), /* @__PURE__ */ React14.createElement(Text7, { truncate: true }, /* @__PURE__ */ React14.createElement(Link6, { overlay, onClick: handleClick }, event.title || "--")))), event.subtitle ? /* @__PURE__ */ React14.createElement(Box6, { flex: 4 }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", align: "center" }, /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy", truncate: true }, event.subtitle))) : null);
7178
8064
  };
7179
8065
  var MONTH_SLOT_HEIGHT = 24;
7180
- var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, monthEventStyle, monthEventMaxChars }) => {
8066
+ var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, monthEventStyle, monthEventMaxChars, reschedule, idScope = "" }) => {
7181
8067
  const isStartDay = !day || !event.start || isSameDay(event.start, day);
7182
- const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-m${day.getTime()}` : "");
8068
+ const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-m${idScope}${day.getTime()}` : "", reschedule);
7183
8069
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7184
8070
  const variant = VALID_VARIANTS.has(event.color) ? event.color : "default";
7185
8071
  const startHasTime = event.start && (event.start.getHours() !== 0 || event.start.getMinutes() !== 0);
@@ -7190,11 +8076,11 @@ var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, lab
7190
8076
  if (monthEventStyle === "tag") {
7191
8077
  return /* @__PURE__ */ React14.createElement(Tag5, { variant: TAG_VARIANT[variant] || "default", overlay, onClick: handleClick }, label);
7192
8078
  }
7193
- return /* @__PURE__ */ React14.createElement(Link6, { overlay, onClick: handleClick }, /* @__PURE__ */ React14.createElement(StatusTag2, { variant: STATUS_VARIANT[variant] || "default" }, label));
8079
+ return /* @__PURE__ */ React14.createElement(Link6, { overlay, onClick: handleClick }, /* @__PURE__ */ React14.createElement(StatusTag3, { variant: STATUS_VARIANT[variant] || "default" }, label));
7194
8080
  };
7195
- var DayListItem = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8081
+ var DayListItem = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, reschedule, idScope = "" }) => {
7196
8082
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7197
- const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-more${day.getTime()}` : "-more");
8083
+ const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-more${idScope}${day.getTime()}` : "-more", reschedule);
7198
8084
  const href = event.href;
7199
8085
  return /* @__PURE__ */ React14.createElement(Button8, { variant: "transparent", size: "sm", href: href ? href.url : void 0, overlay, onClick: handleClick }, event.title || "--");
7200
8086
  };
@@ -7259,6 +8145,40 @@ var Toolbar = ({
7259
8145
  }
7260
8146
  ));
7261
8147
  };
8148
+ var DayChipStack = ({ day, events, maxEventsPerDay, chipProps, labels, idScope = "" }) => {
8149
+ const slotSpacer = makeSpacerDataUri(MONTH_SLOT_HEIGHT, 1);
8150
+ const shown = events.slice(0, maxEventsPerDay);
8151
+ const hasOverflow = events.length > maxEventsPerDay;
8152
+ const heightSpacer = /* @__PURE__ */ React14.createElement(Image5, { src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" });
8153
+ const slotRow = (key, content) => /* @__PURE__ */ React14.createElement(Flex9, { key, direction: "row", align: "center", gap: "flush" }, heightSpacer, content);
8154
+ const slots = [];
8155
+ for (let i = 0; i < maxEventsPerDay; i++) {
8156
+ if (i < shown.length) {
8157
+ slots.push(slotRow(shown[i].key, /* @__PURE__ */ React14.createElement(MonthChip, { event: shown[i], day, idScope, ...chipProps })));
8158
+ } else {
8159
+ slots.push(/* @__PURE__ */ React14.createElement(Image5, { key: `sp-${i}`, src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" }));
8160
+ }
8161
+ }
8162
+ if (hasOverflow) {
8163
+ slots.push(
8164
+ slotRow(
8165
+ "more",
8166
+ /* @__PURE__ */ React14.createElement(
8167
+ Link6,
8168
+ {
8169
+ overlay: /* @__PURE__ */ React14.createElement(Popover, { id: `cal-day-${idScope}${day.getTime()}`, placement: "top", variant: "longform" }, /* @__PURE__ */ React14.createElement(Tile5, { compact: true }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "sm" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", justify: "center", align: "center", gap: "xs" }, /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "bold" } }, String(events.length)), /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "demibold" } }, labels.onThisDate)), /* @__PURE__ */ React14.createElement(Divider4, null), events.map((event, i) => /* @__PURE__ */ React14.createElement(React14.Fragment, { key: event.key }, /* @__PURE__ */ React14.createElement(DayListItem, { event, day, idScope, ...chipProps }), i < events.length - 1 ? /* @__PURE__ */ React14.createElement(Divider4, null) : null)))))
8170
+ },
8171
+ labels.more(events.length - maxEventsPerDay)
8172
+ )
8173
+ )
8174
+ );
8175
+ } else {
8176
+ slots.push(
8177
+ /* @__PURE__ */ React14.createElement(Image5, { key: "more-sp", src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" })
8178
+ );
8179
+ }
8180
+ return /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, slots);
8181
+ };
7262
8182
  var MonthView = ({
7263
8183
  refDate,
7264
8184
  now,
@@ -7273,45 +8193,23 @@ var MonthView = ({
7273
8193
  }) => {
7274
8194
  const headers = weekdayLabels(weekStartsOn, hideWeekends, true);
7275
8195
  const today = now || /* @__PURE__ */ new Date();
7276
- const slotSpacer = makeSpacerDataUri(MONTH_SLOT_HEIGHT, 1);
7277
8196
  const renderCell = (day) => {
7278
8197
  const dayEvents = eventsForDay(day);
7279
8198
  const inMonth = isSameMonth(day, refDate);
7280
8199
  const isToday = isSameDay(day, today);
7281
8200
  if (renderDayCell) return renderDayCell(day, dayEvents);
7282
- const shown = dayEvents.slice(0, maxEventsPerDay);
7283
- const hasOverflow = dayEvents.length > maxEventsPerDay;
7284
- const heightSpacer = /* @__PURE__ */ React14.createElement(Image5, { src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" });
7285
- const slotRow = (key, content) => /* @__PURE__ */ React14.createElement(Flex9, { key, direction: "row", align: "center", gap: "flush" }, heightSpacer, content);
7286
- const slots = [];
7287
- for (let i = 0; i < maxEventsPerDay; i++) {
7288
- if (i < shown.length) {
7289
- slots.push(slotRow(shown[i].key, /* @__PURE__ */ React14.createElement(MonthChip, { event: shown[i], day, ...chipProps })));
7290
- } else {
7291
- slots.push(/* @__PURE__ */ React14.createElement(Image5, { key: `sp-${i}`, src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" }));
8201
+ return /* @__PURE__ */ React14.createElement(AutoGrid3, { columnWidth: MONTH_COL_WIDTH, gap: "flush" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", align: "center", gap: "xs" }, isToday ? /* @__PURE__ */ React14.createElement(ColorDot, { variant: "info" }) : null, /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy", format: { fontWeight: inMonth ? "demibold" : "regular" } }, String(day.getDate()))), /* @__PURE__ */ React14.createElement(
8202
+ DayChipStack,
8203
+ {
8204
+ day,
8205
+ events: dayEvents,
8206
+ maxEventsPerDay,
8207
+ chipProps,
8208
+ labels
7292
8209
  }
7293
- }
7294
- if (hasOverflow) {
7295
- slots.push(
7296
- slotRow(
7297
- "more",
7298
- /* @__PURE__ */ React14.createElement(
7299
- Link6,
7300
- {
7301
- overlay: /* @__PURE__ */ React14.createElement(Popover, { id: `cal-day-${day.getTime()}`, placement: "top", variant: "longform" }, /* @__PURE__ */ React14.createElement(Tile5, { compact: true }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "sm" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", justify: "center", align: "center", gap: "xs" }, /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "bold" } }, String(dayEvents.length)), /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "demibold" } }, labels.onThisDate)), /* @__PURE__ */ React14.createElement(Divider4, null), dayEvents.map((event, i) => /* @__PURE__ */ React14.createElement(React14.Fragment, { key: event.key }, /* @__PURE__ */ React14.createElement(DayListItem, { event, day, ...chipProps }), i < dayEvents.length - 1 ? /* @__PURE__ */ React14.createElement(Divider4, null) : null)))))
7302
- },
7303
- labels.more(dayEvents.length - maxEventsPerDay)
7304
- )
7305
- )
7306
- );
7307
- } else {
7308
- slots.push(
7309
- /* @__PURE__ */ React14.createElement(Image5, { key: "more-sp", src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" })
7310
- );
7311
- }
7312
- return /* @__PURE__ */ React14.createElement(AutoGrid3, { columnWidth: MONTH_COL_WIDTH, gap: "flush" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", align: "center", gap: "xs" }, isToday ? /* @__PURE__ */ React14.createElement(ColorDot, { variant: "info" }) : null, /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy", format: { fontWeight: inMonth ? "demibold" : "regular" } }, String(day.getDate()))), slots));
8210
+ )));
7313
8211
  };
7314
- return /* @__PURE__ */ React14.createElement(Table2, { bordered: true, flush: true }, /* @__PURE__ */ React14.createElement(TableHead2, null, /* @__PURE__ */ React14.createElement(TableRow2, null, headers.map((h6) => /* @__PURE__ */ React14.createElement(TableHeader2, { key: h6, width: "min", align: "center" }, h6.toUpperCase())))), /* @__PURE__ */ React14.createElement(TableBody2, null, weeks.map((week, wi) => {
8212
+ return /* @__PURE__ */ React14.createElement(Table2, { bordered: true, flush: true }, /* @__PURE__ */ React14.createElement(TableHead2, null, /* @__PURE__ */ React14.createElement(TableRow2, null, headers.map((h7) => /* @__PURE__ */ React14.createElement(TableHeader2, { key: h7, width: "min", align: "center" }, h7.toUpperCase())))), /* @__PURE__ */ React14.createElement(TableBody2, null, weeks.map((week, wi) => {
7315
8213
  const days = hideWeekends ? week.filter((d) => d.getDay() !== 0 && d.getDay() !== 6) : week;
7316
8214
  return /* @__PURE__ */ React14.createElement(TableRow2, { key: wi }, days.map((day) => /* @__PURE__ */ React14.createElement(TableCell2, { key: day.getTime(), width: "min" }, renderCell(day))));
7317
8215
  })));
@@ -7331,13 +8229,40 @@ var AgendaView = ({ rangeStart, rangeEnd, eventsForDay, chipProps, labels, rende
7331
8229
  }
7332
8230
  return /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "lg" }, days.map(({ day, events }) => /* @__PURE__ */ React14.createElement(Flex9, { key: day.getTime(), direction: "column", gap: "sm" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", justify: "between", align: "end" }, /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "demibold" } }, formatDayTitle(day)), /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy" }, `${events.length} ${events.length === 1 ? "event" : "events"}`)), /* @__PURE__ */ React14.createElement(Divider4, null), events.map((event) => /* @__PURE__ */ React14.createElement(AgendaEventRow, { key: event.key, event, day, ...chipProps })))));
7333
8231
  };
8232
+ var ResourceView = ({ days, now, lanes, maxEventsPerDay, chipProps, labels, renderEmptyState }) => {
8233
+ const today = now || /* @__PURE__ */ new Date();
8234
+ if (!lanes || lanes.length === 0) {
8235
+ if (renderEmptyState) return renderEmptyState({});
8236
+ return /* @__PURE__ */ React14.createElement(EmptyState4, { title: labels.noEventsTitle }, /* @__PURE__ */ React14.createElement(Text7, null, labels.noEventsMessage));
8237
+ }
8238
+ const rangeStart = startOfDay(days[0]);
8239
+ const rangeEnd = endOfDay(days[days.length - 1]);
8240
+ return /* @__PURE__ */ React14.createElement(Table2, { bordered: true, flush: true }, /* @__PURE__ */ React14.createElement(TableHead2, null, /* @__PURE__ */ React14.createElement(TableRow2, null, /* @__PURE__ */ React14.createElement(TableHeader2, { width: RESOURCE_LABEL_COL_WIDTH }, String(labels.resource).toUpperCase()), days.map((day) => {
8241
+ const isToday = isSameDay(day, today);
8242
+ const label = `${formatWeekdayShort(day)} ${formatMonthShort(day)} ${day.getDate()}`;
8243
+ return /* @__PURE__ */ React14.createElement(TableHeader2, { key: day.getTime(), width: "min", align: "center" }, isToday ? `${label} \xB7 Today` : label);
8244
+ }))), /* @__PURE__ */ React14.createElement(TableBody2, null, lanes.map((lane, laneIndex) => {
8245
+ const visible = eventsIntersectingRange(lane.events, rangeStart, rangeEnd);
8246
+ return /* @__PURE__ */ React14.createElement(TableRow2, { key: lane.key }, /* @__PURE__ */ React14.createElement(TableCell2, { width: RESOURCE_LABEL_COL_WIDTH }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "flush" }, /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "demibold" } }, lane.label), /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy" }, `${visible.length} ${visible.length === 1 ? "event" : "events"}`))), days.map((day) => /* @__PURE__ */ React14.createElement(TableCell2, { key: day.getTime(), width: "min" }, /* @__PURE__ */ React14.createElement(AutoGrid3, { columnWidth: MONTH_COL_WIDTH, gap: "flush" }, /* @__PURE__ */ React14.createElement(
8247
+ DayChipStack,
8248
+ {
8249
+ day,
8250
+ events: laneEventsForDay(visible, day),
8251
+ maxEventsPerDay,
8252
+ chipProps,
8253
+ labels,
8254
+ idScope: `r${laneIndex}-`
8255
+ }
8256
+ )))));
8257
+ })));
8258
+ };
7334
8259
  var formatTimedDuration = (start, end) => {
7335
8260
  const mins = Math.max(0, Math.round(((end || start).getTime() - start.getTime()) / 6e4));
7336
- const h6 = Math.floor(mins / 60);
8261
+ const h7 = Math.floor(mins / 60);
7337
8262
  const m = mins % 60;
7338
- if (h6 === 0) return `${m} min`;
7339
- if (m === 0) return `${h6} hr`;
7340
- return `${h6} hr ${m} min`;
8263
+ if (h7 === 0) return `${m} min`;
8264
+ if (m === 0) return `${h7} hr`;
8265
+ return `${h7} hr ${m} min`;
7341
8266
  };
7342
8267
  var hourSpan = (event) => {
7343
8268
  const start = event.start;
@@ -7378,7 +8303,8 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7378
8303
  chipProps.overlayMode,
7379
8304
  chipProps.renderEventDetail,
7380
8305
  chipProps.labels,
7381
- `-tg${mode}${hour}-${dayMs}`
8306
+ `-tg${mode}${hour}-${dayMs}`,
8307
+ chipProps.reschedule
7382
8308
  );
7383
8309
  const handleClick = chipProps.onEventClick ? () => chipProps.onEventClick(e.raw, e) : void 0;
7384
8310
  const variant = VALID_VARIANTS.has(e.color) ? e.color : "default";
@@ -7398,7 +8324,7 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7398
8324
  sub = `\u2191 cont. through ${endLabel}`;
7399
8325
  }
7400
8326
  const titleLabel = centerDays ? e.title || "--" : truncateMonthLabel(e.title || "--", weekTitleMaxChars);
7401
- return /* @__PURE__ */ React14.createElement(Flex9, { key: `${e.key}-${mode}-${hour}`, direction: "column", gap: "flush" }, /* @__PURE__ */ React14.createElement(Link6, { overlay, onClick: handleClick }, /* @__PURE__ */ React14.createElement(StatusTag2, { variant: STATUS_VARIANT[variant] || "default" }, titleLabel)), sub ? /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy" }, sub) : null);
8327
+ return /* @__PURE__ */ React14.createElement(Flex9, { key: `${e.key}-${mode}-${hour}`, direction: "column", gap: "flush" }, /* @__PURE__ */ React14.createElement(Link6, { overlay, onClick: handleClick }, /* @__PURE__ */ React14.createElement(StatusTag3, { variant: STATUS_VARIANT[variant] || "default" }, titleLabel)), sub ? /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy" }, sub) : null);
7402
8328
  };
7403
8329
  const dayCell = (key, content) => /* @__PURE__ */ React14.createElement(TableCell2, { key, width: centerDays ? "max" : "min", align: "left" }, centerDays ? /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, content) : /* @__PURE__ */ React14.createElement(AutoGrid3, { columnWidth: dayColWidth, gap: "flush" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, content)));
7404
8330
  const slotSpacer = makeSpacerDataUri(HOUR_SLOT_HEIGHT, 1);
@@ -7417,7 +8343,7 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7417
8343
  return /* @__PURE__ */ React14.createElement(TableRow2, { key: hour }, /* @__PURE__ */ React14.createElement(TableCell2, { width: "min" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React14.createElement(Image5, { src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" }), isNow ? (
7418
8344
  // Trailing nbsp pads the chip's right edge so the final letter
7419
8345
  // ("M" in "11 AM") isn't clipped by the min-width TIME column.
7420
- /* @__PURE__ */ React14.createElement(StatusTag2, { variant: "info" }, `${formatHourLabel(hour)}\xA0`)
8346
+ /* @__PURE__ */ React14.createElement(StatusTag3, { variant: "info" }, `${formatHourLabel(hour)}\xA0`)
7421
8347
  ) : /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy" }, formatHourLabel(hour)))), dayData.map(({ day, timed }) => {
7422
8348
  const occupying = timed.map((t) => {
7423
8349
  let visStart = Math.max(t.sHour, dayStartHour);
@@ -7463,6 +8389,14 @@ var Calendar = (props) => {
7463
8389
  // time grid (week / day)
7464
8390
  dayStartHour = DEFAULT_DAY_START_HOUR,
7465
8391
  dayEndHour = DEFAULT_DAY_END_HOUR,
8392
+ // resource / lane view (rows = resources, columns = the focused week's days)
8393
+ resources,
8394
+ resourceField,
8395
+ resourceLabels,
8396
+ showUnassignedLane = true,
8397
+ // drag-free reschedule (presets + date picker in the event-detail overlay)
8398
+ rescheduleOptions,
8399
+ onEventReschedule,
7466
8400
  // timezone
7467
8401
  timeZone: controlledTimeZone,
7468
8402
  defaultTimeZone,
@@ -7501,10 +8435,12 @@ var Calendar = (props) => {
7501
8435
  const fields = useMemo5(() => ({ ...DEFAULT_EVENT_FIELDS, ...eventFields || {} }), [eventFields]);
7502
8436
  const [internalView, setInternalView] = useState6(defaultView);
7503
8437
  const view = controlledView != null ? controlledView : internalView;
8438
+ const resourceEnabled = resources && resources.length > 0 || resourceField != null;
7504
8439
  const enabledViews = useMemo5(() => {
7505
- const base = viewsProp && viewsProp.length > 0 ? viewsProp : ALL_VIEWS;
7506
- return base.filter((v) => ALL_VIEWS.includes(v));
7507
- }, [viewsProp]);
8440
+ const all = resourceEnabled ? ALL_VIEWS_WITH_RESOURCE : ALL_VIEWS;
8441
+ const base = viewsProp && viewsProp.length > 0 ? viewsProp : all;
8442
+ return base.filter((v) => all.includes(v));
8443
+ }, [viewsProp, resourceEnabled]);
7508
8444
  const setView = useCallback5(
7509
8445
  (next) => {
7510
8446
  if (controlledView == null) setInternalView(next);
@@ -7529,7 +8465,9 @@ var Calendar = (props) => {
7529
8465
  const focusedDate = (controlledFocusedDate != null ? toDate2(controlledFocusedDate) : internalDate) || startOfDay(nowWall);
7530
8466
  const stepFor = useCallback5(
7531
8467
  (dir) => {
7532
- if (view === "week" || view === "agenda") return addDays(focusedDate, dir * 7);
8468
+ if (view === "week" || view === "agenda" || view === "resource") {
8469
+ return addDays(focusedDate, dir * 7);
8470
+ }
7533
8471
  if (view === "day") return addDays(focusedDate, dir);
7534
8472
  return addMonths(focusedDate, dir);
7535
8473
  },
@@ -7556,7 +8494,7 @@ var Calendar = (props) => {
7556
8494
  rangeEnd: endOfDay(flat[flat.length - 1])
7557
8495
  };
7558
8496
  }
7559
- if (view === "week") {
8497
+ if (view === "week" || view === "resource") {
7560
8498
  const days = buildWeekDays(focusedDate, weekStartsOn, hideWeekends);
7561
8499
  return {
7562
8500
  weeks: null,
@@ -7629,14 +8567,18 @@ var Calendar = (props) => {
7629
8567
  );
7630
8568
  const normalized = useMemo5(
7631
8569
  () => (events || []).map((raw, index) => {
7632
- const start = toWallClock(toDate2(resolveField(raw, fields.start)), timeZone);
7633
- const endRaw = toWallClock(toDate2(resolveField(raw, fields.end)), timeZone);
8570
+ const sourceStart = toDate2(resolveField(raw, fields.start));
8571
+ const sourceEnd = toDate2(resolveField(raw, fields.end));
8572
+ const start = toWallClock(sourceStart, timeZone);
8573
+ const endRaw = toWallClock(sourceEnd, timeZone);
7634
8574
  const id = resolveField(raw, fields.id);
7635
8575
  return {
7636
8576
  key: id != null ? String(id) : `evt-${index}`,
7637
8577
  id,
7638
8578
  start,
7639
8579
  end: endRaw || start,
8580
+ sourceStart,
8581
+ sourceEnd: sourceEnd || sourceStart,
7640
8582
  title: resolveField(raw, fields.title),
7641
8583
  subtitle: resolveField(raw, fields.subtitle),
7642
8584
  color: resolveField(raw, fields.color),
@@ -7673,12 +8615,40 @@ var Calendar = (props) => {
7673
8615
  }, [rangeKey]);
7674
8616
  const title = useMemo5(() => {
7675
8617
  if (view === "day") return formatDayTitle(focusedDate);
7676
- if (view === "week" || view === "agenda") {
7677
- const days = buildWeekDays(focusedDate, weekStartsOn, view === "week" && hideWeekends);
8618
+ if (view === "week" || view === "agenda" || view === "resource") {
8619
+ const days = buildWeekDays(focusedDate, weekStartsOn, view !== "agenda" && hideWeekends);
7678
8620
  return formatRangeTitle(days[0], days[days.length - 1]);
7679
8621
  }
7680
8622
  return formatMonthTitle(focusedDate);
7681
8623
  }, [view, focusedDate, weekStartsOn, hideWeekends]);
8624
+ const handleReschedulePreset = useCallback5(
8625
+ (event, option) => {
8626
+ const target = resolveRescheduleTarget(
8627
+ { start: event.sourceStart, end: event.sourceEnd },
8628
+ option,
8629
+ event
8630
+ );
8631
+ if (target && onEventReschedule) onEventReschedule(event.raw, target, event);
8632
+ },
8633
+ [onEventReschedule]
8634
+ );
8635
+ const handleReschedulePick = useCallback5(
8636
+ (event, value) => {
8637
+ const newStart = applyDatePick(event.sourceStart, value);
8638
+ if (!newStart) return;
8639
+ const target = rescheduleToStart({ start: event.sourceStart, end: event.sourceEnd }, newStart);
8640
+ if (target && onEventReschedule) onEventReschedule(event.raw, target, event);
8641
+ },
8642
+ [onEventReschedule]
8643
+ );
8644
+ const reschedule = useMemo5(() => {
8645
+ if (!rescheduleOptions) return null;
8646
+ return {
8647
+ options: normalizeRescheduleOptions(rescheduleOptions),
8648
+ onPreset: handleReschedulePreset,
8649
+ onPick: handleReschedulePick
8650
+ };
8651
+ }, [rescheduleOptions, handleReschedulePreset, handleReschedulePick]);
7682
8652
  const safeMonthEventStyle = MONTH_EVENT_STYLES.has(monthEventStyle) ? monthEventStyle : "statusTag";
7683
8653
  const chipProps = {
7684
8654
  overlayMode,
@@ -7686,8 +8656,19 @@ var Calendar = (props) => {
7686
8656
  onEventClick,
7687
8657
  labels,
7688
8658
  monthEventStyle: safeMonthEventStyle,
7689
- monthEventMaxChars
8659
+ monthEventMaxChars,
8660
+ reschedule
7690
8661
  };
8662
+ const resourceLaneData = useMemo5(() => {
8663
+ if (view !== "resource") return null;
8664
+ return buildResourceLanes(queried, {
8665
+ resources,
8666
+ resourceLabels,
8667
+ getId: (e) => resolveResourceId(e.raw, resourceField),
8668
+ showUnassignedLane,
8669
+ unassignedLabel: labels.unassigned
8670
+ });
8671
+ }, [view, queried, resources, resourceLabels, resourceField, showUnassignedLane, labels]);
7691
8672
  const timeZoneOptions = useMemo5(() => {
7692
8673
  if (!showTimeZoneSelect) return null;
7693
8674
  const base = (timeZoneOptionsProp && timeZoneOptionsProp.length ? timeZoneOptionsProp : DEFAULT_TIME_ZONES).map(
@@ -7729,7 +8710,7 @@ var Calendar = (props) => {
7729
8710
  );
7730
8711
  let body;
7731
8712
  if (loading) {
7732
- body = renderLoadingState ? renderLoadingState({}) : /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", justify: "center" }, /* @__PURE__ */ React14.createElement(LoadingSpinner2, { label: labels.loading }));
8713
+ body = renderLoadingState ? renderLoadingState({}) : /* @__PURE__ */ React14.createElement(Flex9, { direction: "row", justify: "center" }, /* @__PURE__ */ React14.createElement(LoadingSpinner3, { label: labels.loading }));
7733
8714
  } else if (error) {
7734
8715
  body = renderErrorState ? renderErrorState({ error }) : /* @__PURE__ */ React14.createElement(Alert4, { title: labels.errorTitle, variant: "error" }, typeof error === "string" ? error : labels.errorMessage);
7735
8716
  } else if (view === "month") {
@@ -7748,6 +8729,19 @@ var Calendar = (props) => {
7748
8729
  labels
7749
8730
  }
7750
8731
  );
8732
+ } else if (view === "resource") {
8733
+ body = /* @__PURE__ */ React14.createElement(
8734
+ ResourceView,
8735
+ {
8736
+ days: gridDays,
8737
+ now: nowWall,
8738
+ lanes: resourceLaneData,
8739
+ maxEventsPerDay,
8740
+ chipProps,
8741
+ labels,
8742
+ renderEmptyState
8743
+ }
8744
+ );
7751
8745
  } else if (view === "week" || view === "day") {
7752
8746
  body = /* @__PURE__ */ React14.createElement(
7753
8747
  TimeGridView,
@@ -7777,9 +8771,696 @@ var Calendar = (props) => {
7777
8771
  }
7778
8772
  return /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "sm" }, toolbar, body);
7779
8773
  };
8774
+ Calendar.displayName = "Calendar";
8775
+
8776
+ // src/filter/FilterBuilder.jsx
8777
+ import React15, { useMemo as useMemo6, useState as useState7 } from "react";
8778
+ import {
8779
+ Box as Box7,
8780
+ Button as Button9,
8781
+ DateInput as DateInput5,
8782
+ Flex as Flex10,
8783
+ Input as Input3,
8784
+ MultiSelect as MultiSelect4,
8785
+ NumberInput as NumberInput3,
8786
+ Select as Select7,
8787
+ Text as Text8,
8788
+ Tile as Tile6
8789
+ } from "@hubspot/ui-extensions";
8790
+
8791
+ // src/filter/filterTree.js
8792
+ var NUMERIC_OPERATORS = ["EQ", "NEQ", "GT", "GTE", "LT", "LTE", "BETWEEN", "HAS_PROPERTY", "NOT_HAS_PROPERTY"];
8793
+ var FILTER_OPERATORS = {
8794
+ string: ["EQ", "NEQ", "CONTAINS_TOKEN", "NOT_CONTAINS_TOKEN", "HAS_PROPERTY", "NOT_HAS_PROPERTY"],
8795
+ number: NUMERIC_OPERATORS,
8796
+ date: NUMERIC_OPERATORS,
8797
+ datetime: NUMERIC_OPERATORS,
8798
+ enum: ["IN", "NOT_IN", "HAS_PROPERTY", "NOT_HAS_PROPERTY"],
8799
+ bool: ["EQ"]
8800
+ };
8801
+ var ALL_OPERATORS = [...new Set(Object.values(FILTER_OPERATORS).flat())];
8802
+ var BASE_OPERATOR_LABELS = {
8803
+ EQ: "is equal to",
8804
+ NEQ: "is not equal to",
8805
+ CONTAINS_TOKEN: "contains",
8806
+ NOT_CONTAINS_TOKEN: "doesn't contain",
8807
+ GT: "is greater than",
8808
+ GTE: "is greater than or equal to",
8809
+ LT: "is less than",
8810
+ LTE: "is less than or equal to",
8811
+ BETWEEN: "is between",
8812
+ IN: "is any of",
8813
+ NOT_IN: "is none of",
8814
+ HAS_PROPERTY: "is known",
8815
+ NOT_HAS_PROPERTY: "is unknown"
8816
+ };
8817
+ var DATE_OPERATOR_LABELS = {
8818
+ EQ: "is",
8819
+ NEQ: "is not",
8820
+ GT: "is after",
8821
+ GTE: "is on or after",
8822
+ LT: "is before",
8823
+ LTE: "is on or before"
8824
+ };
8825
+ var getOperatorOptions = (type, labelOverrides) => {
8826
+ const operators = FILTER_OPERATORS[type] || [];
8827
+ const dateLabels = type === "date" || type === "datetime" ? DATE_OPERATOR_LABELS : null;
8828
+ return operators.map((operator) => ({
8829
+ label: (labelOverrides == null ? void 0 : labelOverrides[operator]) ?? (dateLabels == null ? void 0 : dateLabels[operator]) ?? BASE_OPERATOR_LABELS[operator] ?? operator,
8830
+ value: operator
8831
+ }));
8832
+ };
8833
+ var operatorExpectsValue = (operator) => operator !== "HAS_PROPERTY" && operator !== "NOT_HAS_PROPERTY";
8834
+ var operatorExpectsHighValue = (operator) => operator === "BETWEEN";
8835
+ var operatorExpectsValues = (operator) => operator === "IN" || operator === "NOT_IN";
8836
+ var isGroupNode = (node) => node != null && node.type === "group";
8837
+ var isConditionNode = (node) => node != null && node.type === "condition";
8838
+ var createCondition = (property = "", operator = "", value, highValue) => {
8839
+ const node = { type: "condition", property, operator };
8840
+ if (value !== void 0) node.value = value;
8841
+ if (highValue !== void 0) node.highValue = highValue;
8842
+ return node;
8843
+ };
8844
+ var createGroup = (operator = "AND", filters = []) => ({
8845
+ type: "group",
8846
+ operator,
8847
+ filters
8848
+ });
8849
+ var getNodeAtPath = (tree, path) => {
8850
+ let node = tree;
8851
+ for (const index of path || []) {
8852
+ if (!isGroupNode(node) || !Array.isArray(node.filters)) return void 0;
8853
+ node = node.filters[index];
8854
+ if (node === void 0) return void 0;
8855
+ }
8856
+ return node;
8857
+ };
8858
+ var updateNodeAtPath = (node, path, fn) => {
8859
+ if (!path || path.length === 0) return fn(node);
8860
+ if (!isGroupNode(node) || !Array.isArray(node.filters)) {
8861
+ throw new Error("filterTree: path descends into a non-group node");
8862
+ }
8863
+ const [index, ...rest] = path;
8864
+ if (typeof index !== "number" || index < 0 || index >= node.filters.length) {
8865
+ throw new Error(`filterTree: no filter at index ${index} (group has ${node.filters.length})`);
8866
+ }
8867
+ const filters = node.filters.slice();
8868
+ filters[index] = updateNodeAtPath(filters[index], rest, fn);
8869
+ return { ...node, filters };
8870
+ };
8871
+ var addFilter = (tree, path, node) => updateNodeAtPath(tree, path, (group) => {
8872
+ if (!isGroupNode(group)) {
8873
+ throw new Error("filterTree: addFilter path must point at a group node");
8874
+ }
8875
+ return { ...group, filters: [...group.filters || [], node] };
8876
+ });
8877
+ var updateFilter = (tree, path, patch) => updateNodeAtPath(
8878
+ tree,
8879
+ path,
8880
+ (node) => typeof patch === "function" ? patch(node) : { ...node, ...patch }
8881
+ );
8882
+ var removeFilter = (tree, path, options = {}) => {
8883
+ if (!path || path.length === 0) {
8884
+ throw new Error("filterTree: cannot remove the root group");
8885
+ }
8886
+ const doRemove = (currentTree, targetPath) => {
8887
+ const parentPath = targetPath.slice(0, -1);
8888
+ const index = targetPath[targetPath.length - 1];
8889
+ return updateNodeAtPath(currentTree, parentPath, (group) => {
8890
+ if (!isGroupNode(group)) {
8891
+ throw new Error("filterTree: removeFilter parent is not a group node");
8892
+ }
8893
+ if (typeof index !== "number" || index < 0 || index >= group.filters.length) {
8894
+ throw new Error(`filterTree: no filter at index ${index} (group has ${group.filters.length})`);
8895
+ }
8896
+ return { ...group, filters: group.filters.filter((_, i) => i !== index) };
8897
+ });
8898
+ };
8899
+ let next = doRemove(tree, path);
8900
+ if (options.pruneEmptyGroups) {
8901
+ let parentPath = path.slice(0, -1);
8902
+ while (parentPath.length > 0) {
8903
+ const parent = getNodeAtPath(next, parentPath);
8904
+ if (!isGroupNode(parent) || parent.filters.length > 0) break;
8905
+ next = doRemove(next, parentPath);
8906
+ parentPath = parentPath.slice(0, -1);
8907
+ }
8908
+ }
8909
+ return next;
8910
+ };
8911
+ var cloneNode = (node) => {
8912
+ if (Array.isArray(node)) return node.map(cloneNode);
8913
+ if (node !== null && typeof node === "object") {
8914
+ const copy = {};
8915
+ for (const key of Object.keys(node)) copy[key] = cloneNode(node[key]);
8916
+ return copy;
8917
+ }
8918
+ return node;
8919
+ };
8920
+ var duplicateFilter = (tree, path) => {
8921
+ if (!path || path.length === 0) {
8922
+ throw new Error("filterTree: cannot duplicate the root group");
8923
+ }
8924
+ const parentPath = path.slice(0, -1);
8925
+ const index = path[path.length - 1];
8926
+ return updateNodeAtPath(tree, parentPath, (group) => {
8927
+ if (!isGroupNode(group)) {
8928
+ throw new Error("filterTree: duplicateFilter parent is not a group node");
8929
+ }
8930
+ if (typeof index !== "number" || index < 0 || index >= group.filters.length) {
8931
+ throw new Error(`filterTree: no filter at index ${index} (group has ${group.filters.length})`);
8932
+ }
8933
+ const filters = group.filters.slice();
8934
+ filters.splice(index + 1, 0, cloneNode(group.filters[index]));
8935
+ return { ...group, filters };
8936
+ });
8937
+ };
8938
+ var countConditions = (node) => {
8939
+ if (isConditionNode(node)) return 1;
8940
+ if (!isGroupNode(node) || !Array.isArray(node.filters)) return 0;
8941
+ return node.filters.reduce((sum, child) => sum + countConditions(child), 0);
8942
+ };
8943
+ var changeConditionProperty = (condition, property) => {
8944
+ const name = typeof property === "string" ? property : (property == null ? void 0 : property.name) ?? "";
8945
+ const type = typeof property === "object" && property !== null ? property.type : void 0;
8946
+ const operators = FILTER_OPERATORS[type] || [];
8947
+ const operator = operators.includes(condition == null ? void 0 : condition.operator) ? condition.operator : operators[0] ?? "";
8948
+ const next = { type: "condition", property: name, operator };
8949
+ if (operatorExpectsValues(operator)) next.value = [];
8950
+ return next;
8951
+ };
8952
+ var changeConditionOperator = (condition, operator) => {
8953
+ const next = { type: "condition", property: (condition == null ? void 0 : condition.property) ?? "", operator };
8954
+ if (!operatorExpectsValue(operator)) return next;
8955
+ if (operatorExpectsValues(operator)) {
8956
+ next.value = Array.isArray(condition == null ? void 0 : condition.value) ? condition.value : [];
8957
+ return next;
8958
+ }
8959
+ if ((condition == null ? void 0 : condition.value) !== void 0 && !Array.isArray(condition.value)) {
8960
+ next.value = condition.value;
8961
+ }
8962
+ if (operatorExpectsHighValue(operator) && (condition == null ? void 0 : condition.highValue) !== void 0) {
8963
+ next.highValue = condition.highValue;
8964
+ }
8965
+ return next;
8966
+ };
8967
+ var isMissing = (value) => value == null || value === "";
8968
+ var validateTree = (tree, properties) => {
8969
+ const errors = [];
8970
+ const byName = Array.isArray(properties) ? new Map(properties.map((property) => [property.name, property])) : null;
8971
+ if (!isGroupNode(tree)) {
8972
+ return { valid: false, errors: [{ path: [], message: "Root must be a group node." }] };
8973
+ }
8974
+ const visitCondition = (node, path) => {
8975
+ if (!node.property) {
8976
+ errors.push({ path, message: "Condition is missing a property." });
8977
+ return;
8978
+ }
8979
+ let type;
8980
+ if (byName) {
8981
+ const def = byName.get(node.property);
8982
+ if (!def) {
8983
+ errors.push({ path, message: `Unknown property "${node.property}".` });
8984
+ return;
8985
+ }
8986
+ type = def.type;
8987
+ }
8988
+ const allowed = type !== void 0 ? FILTER_OPERATORS[type] || [] : ALL_OPERATORS;
8989
+ if (!node.operator) {
8990
+ errors.push({ path, message: "Condition is missing an operator." });
8991
+ return;
8992
+ }
8993
+ if (!allowed.includes(node.operator)) {
8994
+ errors.push({
8995
+ path,
8996
+ message: type !== void 0 ? `Operator "${node.operator}" is not valid for type "${type}".` : `Unknown operator "${node.operator}".`
8997
+ });
8998
+ return;
8999
+ }
9000
+ if (!operatorExpectsValue(node.operator)) return;
9001
+ if (operatorExpectsValues(node.operator)) {
9002
+ if (!Array.isArray(node.value) || node.value.length === 0) {
9003
+ errors.push({ path, message: `Operator "${node.operator}" requires at least one value.` });
9004
+ }
9005
+ return;
9006
+ }
9007
+ if (isMissing(node.value)) {
9008
+ errors.push({ path, message: `Operator "${node.operator}" requires a value.` });
9009
+ }
9010
+ if (operatorExpectsHighValue(node.operator) && isMissing(node.highValue)) {
9011
+ errors.push({ path, message: 'Operator "BETWEEN" requires an upper bound (highValue).' });
9012
+ }
9013
+ };
9014
+ const visit = (node, path) => {
9015
+ if (node == null || typeof node !== "object") {
9016
+ errors.push({ path, message: "Filter node must be an object." });
9017
+ return;
9018
+ }
9019
+ if (node.type === "group") {
9020
+ if (node.operator !== "AND" && node.operator !== "OR") {
9021
+ errors.push({ path, message: `Group operator must be "AND" or "OR" (got "${node.operator}").` });
9022
+ }
9023
+ if (!Array.isArray(node.filters)) {
9024
+ errors.push({ path, message: "Group filters must be an array." });
9025
+ return;
9026
+ }
9027
+ if (node.filters.length === 0 && path.length > 0) {
9028
+ errors.push({ path, message: "Group has no filters." });
9029
+ }
9030
+ node.filters.forEach((child, index) => visit(child, [...path, index]));
9031
+ return;
9032
+ }
9033
+ if (node.type === "condition") {
9034
+ visitCondition(node, path);
9035
+ return;
9036
+ }
9037
+ errors.push({ path, message: `Unknown node type "${node.type}".` });
9038
+ };
9039
+ visit(tree, []);
9040
+ return { valid: errors.length === 0, errors };
9041
+ };
9042
+ var isDateValueObject4 = (value) => value != null && typeof value === "object" && typeof value.year === "number" && typeof value.month === "number" && typeof value.date === "number";
9043
+ var dateValueToTimestamp = (dateObj) => new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
9044
+ var coerceCrmValue = (value) => {
9045
+ if (isDateValueObject4(value)) return dateValueToTimestamp(value);
9046
+ if (typeof value === "boolean") return String(value);
9047
+ return value;
9048
+ };
9049
+ var conditionToCrmFilter = (condition, options = {}) => {
9050
+ const { coerceValues = true } = options;
9051
+ if (!isConditionNode(condition)) {
9052
+ throw new Error("filterTree: conditionToCrmFilter expects a condition node");
9053
+ }
9054
+ if (!condition.property) {
9055
+ throw new Error("filterTree: condition is missing a property");
9056
+ }
9057
+ if (!condition.operator) {
9058
+ throw new Error(`filterTree: condition on "${condition.property}" is missing an operator`);
9059
+ }
9060
+ const coerce = coerceValues ? coerceCrmValue : (value) => value;
9061
+ const filter = { propertyName: condition.property, operator: condition.operator };
9062
+ if (!operatorExpectsValue(condition.operator)) return filter;
9063
+ if (operatorExpectsValues(condition.operator)) {
9064
+ const values = Array.isArray(condition.value) ? condition.value : condition.value == null ? [] : [condition.value];
9065
+ filter.values = values.map(coerce);
9066
+ return filter;
9067
+ }
9068
+ filter.value = coerce(condition.value);
9069
+ if (operatorExpectsHighValue(condition.operator)) {
9070
+ filter.highValue = coerce(condition.highValue);
9071
+ }
9072
+ return filter;
9073
+ };
9074
+ var nodeToDnf = (node, path, options) => {
9075
+ if (isConditionNode(node)) {
9076
+ return [[conditionToCrmFilter(node, options)]];
9077
+ }
9078
+ if (!isGroupNode(node)) {
9079
+ throw new Error(`filterTree: unknown node type "${node == null ? void 0 : node.type}" at [${path.join(", ")}]`);
9080
+ }
9081
+ if (!Array.isArray(node.filters) || node.filters.length === 0) {
9082
+ throw new Error(
9083
+ `filterTree: empty group at [${path.join(", ")}] cannot be converted \u2014 remove it or add a condition (run validateTree first)`
9084
+ );
9085
+ }
9086
+ const childDnfs = node.filters.map((child, index) => nodeToDnf(child, [...path, index], options));
9087
+ if (node.operator === "OR") {
9088
+ return childDnfs.flat();
9089
+ }
9090
+ return childDnfs.reduce(
9091
+ (acc, childDnf) => {
9092
+ const out = [];
9093
+ for (const left of acc) {
9094
+ for (const right of childDnf) out.push([...left, ...right]);
9095
+ }
9096
+ return out;
9097
+ },
9098
+ [[]]
9099
+ );
9100
+ };
9101
+ var toCrmSearchFilterGroups = (tree, options = {}) => {
9102
+ const {
9103
+ maxGroups = 5,
9104
+ maxFiltersPerGroup = 6,
9105
+ maxTotalFilters = 18,
9106
+ enforceLimits = true,
9107
+ coerceValues = true
9108
+ } = options;
9109
+ if (!isGroupNode(tree)) {
9110
+ throw new Error("filterTree: toCrmSearchFilterGroups expects a group node at the root");
9111
+ }
9112
+ if (!Array.isArray(tree.filters) || tree.filters.length === 0) {
9113
+ return { filterGroups: [] };
9114
+ }
9115
+ const conjunctions = nodeToDnf(tree, [], { coerceValues });
9116
+ if (enforceLimits) {
9117
+ if (conjunctions.length > maxGroups) {
9118
+ throw new Error(
9119
+ `filterTree: tree expands to ${conjunctions.length} filterGroups; HubSpot CRM search allows at most ${maxGroups}. Reduce OR branches (each OR nested under an AND multiplies groups).`
9120
+ );
9121
+ }
9122
+ const oversized = conjunctions.findIndex((filters) => filters.length > maxFiltersPerGroup);
9123
+ if (oversized !== -1) {
9124
+ throw new Error(
9125
+ `filterTree: filterGroup ${oversized} has ${conjunctions[oversized].length} filters; HubSpot CRM search allows at most ${maxFiltersPerGroup} per group.`
9126
+ );
9127
+ }
9128
+ const total = conjunctions.reduce((sum, filters) => sum + filters.length, 0);
9129
+ if (total > maxTotalFilters) {
9130
+ throw new Error(
9131
+ `filterTree: tree expands to ${total} total filters; HubSpot CRM search allows at most ${maxTotalFilters}.`
9132
+ );
9133
+ }
9134
+ }
9135
+ return { filterGroups: conjunctions.map((filters) => ({ filters })) };
9136
+ };
9137
+
9138
+ // src/filter/FilterBuilder.jsx
9139
+ var DEFAULT_LABELS6 = {
9140
+ addFilter: "Add filter",
9141
+ addGroup: "Add filter group",
9142
+ remove: "Remove filter",
9143
+ removeGroup: "Delete group",
9144
+ cloneGroup: "Clone group",
9145
+ group: "Group",
9146
+ and: "AND",
9147
+ or: "OR",
9148
+ property: "Select a property",
9149
+ operator: "Select an operator",
9150
+ value: "Enter a value",
9151
+ values: "Select values",
9152
+ between: "and",
9153
+ empty: "No filters yet.",
9154
+ true: "True",
9155
+ false: "False"
9156
+ };
9157
+ var GROUP_OPERATOR_OPTIONS = (labels) => [
9158
+ { label: labels.and, value: "AND" },
9159
+ { label: labels.or, value: "OR" }
9160
+ ];
9161
+ var normalizeTree = (tree) => isGroupNode(tree) ? tree : createGroup("AND", []);
9162
+ var pathName = (prefix, path, suffix) => `${prefix}-${path.length ? path.join("-") : "root"}-${suffix}`;
9163
+ var ValueEditor = ({ condition, propertyDef, path, namePrefix, labels, readOnly, onPatch }) => {
9164
+ const { operator } = condition;
9165
+ if (!operator || !operatorExpectsValue(operator)) return null;
9166
+ const type = (propertyDef == null ? void 0 : propertyDef.type) || "string";
9167
+ const setValue = (value) => onPatch(path, { value });
9168
+ const setHighValue = (highValue) => onPatch(path, { highValue });
9169
+ if (operatorExpectsValues(operator)) {
9170
+ return /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9171
+ MultiSelect4,
9172
+ {
9173
+ label: "",
9174
+ name: pathName(namePrefix, path, "value"),
9175
+ placeholder: labels.values,
9176
+ options: (propertyDef == null ? void 0 : propertyDef.options) || [],
9177
+ value: Array.isArray(condition.value) ? condition.value : [],
9178
+ readOnly,
9179
+ onChange: setValue
9180
+ }
9181
+ ));
9182
+ }
9183
+ if (type === "bool") {
9184
+ return /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9185
+ Select7,
9186
+ {
9187
+ label: "",
9188
+ name: pathName(namePrefix, path, "value"),
9189
+ placeholder: labels.value,
9190
+ options: [
9191
+ { label: labels.true, value: "true" },
9192
+ { label: labels.false, value: "false" }
9193
+ ],
9194
+ value: condition.value,
9195
+ readOnly,
9196
+ onChange: setValue
9197
+ }
9198
+ ));
9199
+ }
9200
+ if (type === "enum") {
9201
+ return /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9202
+ Select7,
9203
+ {
9204
+ label: "",
9205
+ name: pathName(namePrefix, path, "value"),
9206
+ placeholder: labels.value,
9207
+ options: (propertyDef == null ? void 0 : propertyDef.options) || [],
9208
+ value: condition.value,
9209
+ readOnly,
9210
+ onChange: setValue
9211
+ }
9212
+ ));
9213
+ }
9214
+ const isBetween = operatorExpectsHighValue(operator);
9215
+ if (type === "number") {
9216
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9217
+ NumberInput3,
9218
+ {
9219
+ label: "",
9220
+ name: pathName(namePrefix, path, "value"),
9221
+ placeholder: labels.value,
9222
+ value: condition.value ?? "",
9223
+ readOnly,
9224
+ onChange: setValue
9225
+ }
9226
+ )), isBetween && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Text8, { variant: "microcopy" }, labels.between), /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9227
+ NumberInput3,
9228
+ {
9229
+ label: "",
9230
+ name: pathName(namePrefix, path, "high-value"),
9231
+ placeholder: labels.value,
9232
+ value: condition.highValue ?? "",
9233
+ readOnly,
9234
+ onChange: setHighValue
9235
+ }
9236
+ ))));
9237
+ }
9238
+ if (type === "date" || type === "datetime") {
9239
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9240
+ DateInput5,
9241
+ {
9242
+ label: "",
9243
+ name: pathName(namePrefix, path, "value"),
9244
+ format: "medium",
9245
+ value: condition.value ?? null,
9246
+ readOnly,
9247
+ onChange: setValue
9248
+ }
9249
+ )), isBetween && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Text8, { variant: "microcopy" }, labels.between), /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9250
+ DateInput5,
9251
+ {
9252
+ label: "",
9253
+ name: pathName(namePrefix, path, "high-value"),
9254
+ format: "medium",
9255
+ value: condition.highValue ?? null,
9256
+ readOnly,
9257
+ onChange: setHighValue
9258
+ }
9259
+ ))));
9260
+ }
9261
+ return /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9262
+ Input3,
9263
+ {
9264
+ label: "",
9265
+ name: pathName(namePrefix, path, "value"),
9266
+ placeholder: labels.value,
9267
+ value: condition.value ?? "",
9268
+ readOnly,
9269
+ onChange: setValue
9270
+ }
9271
+ ));
9272
+ };
9273
+ var IconButton = ({ icon, label, onClick, size }) => /* @__PURE__ */ React15.createElement(Button9, { size: "extra-small", variant: "transparent", onClick }, /* @__PURE__ */ React15.createElement(Icon, { name: icon, screenReaderText: label, ...size ? { size } : {} }));
9274
+ var ConditionRow = ({
9275
+ condition,
9276
+ path,
9277
+ properties,
9278
+ propertyOptions,
9279
+ namePrefix,
9280
+ labels,
9281
+ operatorLabels,
9282
+ readOnly,
9283
+ onPropertyChange,
9284
+ onOperatorChange,
9285
+ onPatch,
9286
+ onRemove
9287
+ }) => {
9288
+ const propertyDef = properties.find((property) => property.name === condition.property);
9289
+ const operatorOptions = getOperatorOptions(propertyDef == null ? void 0 : propertyDef.type, operatorLabels);
9290
+ return /* @__PURE__ */ React15.createElement(Flex10, { direction: "row", gap: "xs", align: "center", wrap: "wrap" }, /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9291
+ Select7,
9292
+ {
9293
+ label: "",
9294
+ name: pathName(namePrefix, path, "property"),
9295
+ placeholder: labels.property,
9296
+ options: propertyOptions,
9297
+ value: condition.property || void 0,
9298
+ readOnly,
9299
+ onChange: (name) => onPropertyChange(path, name)
9300
+ }
9301
+ )), /* @__PURE__ */ React15.createElement(Box7, { flex: 1 }, /* @__PURE__ */ React15.createElement(
9302
+ Select7,
9303
+ {
9304
+ label: "",
9305
+ name: pathName(namePrefix, path, "operator"),
9306
+ placeholder: labels.operator,
9307
+ options: operatorOptions,
9308
+ value: condition.operator || void 0,
9309
+ readOnly: readOnly || !condition.property,
9310
+ onChange: (operator) => onOperatorChange(path, operator)
9311
+ }
9312
+ )), /* @__PURE__ */ React15.createElement(
9313
+ ValueEditor,
9314
+ {
9315
+ condition,
9316
+ propertyDef,
9317
+ path,
9318
+ namePrefix,
9319
+ labels,
9320
+ readOnly,
9321
+ onPatch
9322
+ }
9323
+ ), !readOnly && /* @__PURE__ */ React15.createElement(IconButton, { icon: "remove", label: labels.remove, onClick: () => onRemove(path) }));
9324
+ };
9325
+ var GroupOperatorSeparator = ({ group, path, namePrefix, labels, readOnly, index, onGroupOperatorChange }) => {
9326
+ if (readOnly) {
9327
+ return /* @__PURE__ */ React15.createElement(Text8, { variant: "microcopy", format: { fontWeight: "demibold" } }, group.operator === "OR" ? labels.or : labels.and);
9328
+ }
9329
+ return /* @__PURE__ */ React15.createElement(Box7, { alignSelf: "start" }, /* @__PURE__ */ React15.createElement(
9330
+ Select7,
9331
+ {
9332
+ label: "",
9333
+ name: pathName(namePrefix, path, `separator-${index}`),
9334
+ variant: "transparent",
9335
+ options: GROUP_OPERATOR_OPTIONS(labels),
9336
+ value: group.operator,
9337
+ onChange: (operator) => onGroupOperatorChange(path, operator)
9338
+ }
9339
+ ));
9340
+ };
9341
+ var GroupEditor = ({ group, path, depth, ctx }) => {
9342
+ const { labels, maxDepth, readOnly, namePrefix, handlers } = ctx;
9343
+ const isRoot = path.length === 0;
9344
+ const filters = Array.isArray(group.filters) ? group.filters : [];
9345
+ let groupNumber = 0;
9346
+ const children = filters.map((child, index) => {
9347
+ const childPath = [...path, index];
9348
+ if (isGroupNode(child)) groupNumber += 1;
9349
+ const row = isGroupNode(child) ? /* @__PURE__ */ React15.createElement(Tile6, { compact: true }, /* @__PURE__ */ React15.createElement(Flex10, { direction: "column", gap: "xs" }, /* @__PURE__ */ React15.createElement(Flex10, { direction: "row", justify: "between", align: "center" }, /* @__PURE__ */ React15.createElement(Text8, { format: { fontWeight: "demibold" } }, `${labels.group}\xA0${groupNumber}`), !readOnly && /* @__PURE__ */ React15.createElement(Flex10, { direction: "row", gap: "xs", justify: "end" }, /* @__PURE__ */ React15.createElement(
9350
+ IconButton,
9351
+ {
9352
+ icon: "copy",
9353
+ label: labels.cloneGroup,
9354
+ size: "sm",
9355
+ onClick: () => handlers.onDuplicate(childPath)
9356
+ }
9357
+ ), /* @__PURE__ */ React15.createElement(
9358
+ IconButton,
9359
+ {
9360
+ icon: "delete",
9361
+ label: labels.removeGroup,
9362
+ size: "sm",
9363
+ onClick: () => handlers.onRemove(childPath)
9364
+ }
9365
+ ))), /* @__PURE__ */ React15.createElement(GroupEditor, { group: child, path: childPath, depth: depth + 1, ctx }))) : /* @__PURE__ */ React15.createElement(
9366
+ ConditionRow,
9367
+ {
9368
+ condition: child,
9369
+ path: childPath,
9370
+ properties: ctx.properties,
9371
+ propertyOptions: ctx.propertyOptions,
9372
+ namePrefix,
9373
+ labels,
9374
+ operatorLabels: ctx.operatorLabels,
9375
+ readOnly,
9376
+ onPropertyChange: handlers.onPropertyChange,
9377
+ onOperatorChange: handlers.onOperatorChange,
9378
+ onPatch: handlers.onPatch,
9379
+ onRemove: handlers.onRemove
9380
+ }
9381
+ );
9382
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, { key: childPath.join("-") }, index > 0 && /* @__PURE__ */ React15.createElement(
9383
+ GroupOperatorSeparator,
9384
+ {
9385
+ group,
9386
+ path,
9387
+ namePrefix,
9388
+ labels,
9389
+ readOnly,
9390
+ index,
9391
+ onGroupOperatorChange: handlers.onGroupOperatorChange
9392
+ }
9393
+ ), row);
9394
+ });
9395
+ return (
9396
+ // Nested groups pack tight (row / AND-OR / row reads as one unit, like
9397
+ // HubSpot's builder); the root keeps more air between its sections.
9398
+ /* @__PURE__ */ React15.createElement(Flex10, { direction: "column", gap: isRoot ? "sm" : "xs" }, isRoot && filters.length === 0 && /* @__PURE__ */ React15.createElement(Text8, { variant: "microcopy" }, labels.empty), children, !readOnly && /* @__PURE__ */ React15.createElement(Flex10, { direction: "row", gap: "md", align: "center" }, /* @__PURE__ */ React15.createElement(
9399
+ Button9,
9400
+ {
9401
+ size: "extra-small",
9402
+ variant: "transparent",
9403
+ onClick: () => handlers.onAddCondition(path)
9404
+ },
9405
+ /* @__PURE__ */ React15.createElement(Icon, { name: "add" }),
9406
+ " ",
9407
+ labels.addFilter
9408
+ ), depth < maxDepth && /* @__PURE__ */ React15.createElement(
9409
+ Button9,
9410
+ {
9411
+ size: "extra-small",
9412
+ variant: "transparent",
9413
+ onClick: () => handlers.onAddGroup(path)
9414
+ },
9415
+ /* @__PURE__ */ React15.createElement(Icon, { name: "add" }),
9416
+ " ",
9417
+ labels.addGroup
9418
+ )))
9419
+ );
9420
+ };
9421
+ var FilterBuilder = ({
9422
+ properties = [],
9423
+ value,
9424
+ defaultValue,
9425
+ onChange,
9426
+ maxDepth = 2,
9427
+ labels: labelOverrides,
9428
+ operatorLabels,
9429
+ readOnly = false,
9430
+ namePrefix = "filter-builder",
9431
+ ...rest
9432
+ }) => {
9433
+ const labels = useMemo6(() => ({ ...DEFAULT_LABELS6, ...labelOverrides || {} }), [labelOverrides]);
9434
+ const [internalTree, setInternalTree] = useState7(() => normalizeTree(defaultValue));
9435
+ const isControlled = value !== void 0;
9436
+ const tree = isControlled ? normalizeTree(value) : internalTree;
9437
+ const propertyOptions = useMemo6(
9438
+ () => properties.map((property) => ({ label: property.label ?? property.name, value: property.name })),
9439
+ [properties]
9440
+ );
9441
+ const commit = (next) => {
9442
+ if (!isControlled) setInternalTree(next);
9443
+ onChange == null ? void 0 : onChange(next);
9444
+ };
9445
+ const handlers = {
9446
+ onAddCondition: (groupPath) => commit(addFilter(tree, groupPath, createCondition())),
9447
+ onAddGroup: (groupPath) => commit(addFilter(tree, groupPath, createGroup("AND", [createCondition()]))),
9448
+ onRemove: (path) => commit(removeFilter(tree, path, { pruneEmptyGroups: true })),
9449
+ onDuplicate: (path) => commit(duplicateFilter(tree, path)),
9450
+ onGroupOperatorChange: (groupPath, operator) => commit(updateFilter(tree, groupPath, { operator })),
9451
+ onPropertyChange: (path, name) => {
9452
+ const def = properties.find((property) => property.name === name);
9453
+ commit(updateFilter(tree, path, (node) => changeConditionProperty(node, def ?? name)));
9454
+ },
9455
+ onOperatorChange: (path, operator) => commit(updateFilter(tree, path, (node) => changeConditionOperator(node, operator))),
9456
+ onPatch: (path, patch) => commit(updateFilter(tree, path, patch))
9457
+ };
9458
+ const ctx = { properties, propertyOptions, labels, operatorLabels, maxDepth, readOnly, namePrefix, handlers };
9459
+ return /* @__PURE__ */ React15.createElement(Flex10, { direction: "column", gap: "sm", ...rest }, /* @__PURE__ */ React15.createElement(GroupEditor, { group: tree, path: [], depth: 1, ctx }));
9460
+ };
7780
9461
 
7781
9462
  // src/common-components/AutoTag.js
7782
- import React15 from "react";
9463
+ import React16 from "react";
7783
9464
  import { Tag as Tag6 } from "@hubspot/ui-extensions";
7784
9465
 
7785
9466
  // src/utils/tagVariants.js
@@ -7978,7 +9659,7 @@ var AutoTag = ({
7978
9659
  overrides,
7979
9660
  fallback
7980
9661
  });
7981
- return React15.createElement(
9662
+ return React16.createElement(
7982
9663
  Tag6,
7983
9664
  { variant: resolvedVariant, ...props },
7984
9665
  displayValue
@@ -7986,8 +9667,8 @@ var AutoTag = ({
7986
9667
  };
7987
9668
 
7988
9669
  // src/common-components/AutoStatusTag.js
7989
- import React16 from "react";
7990
- import { StatusTag as StatusTag3 } from "@hubspot/ui-extensions";
9670
+ import React17 from "react";
9671
+ import { StatusTag as StatusTag4 } from "@hubspot/ui-extensions";
7991
9672
  var AutoStatusTag = ({
7992
9673
  value,
7993
9674
  status,
@@ -8003,20 +9684,20 @@ var AutoStatusTag = ({
8003
9684
  overrides,
8004
9685
  fallback
8005
9686
  });
8006
- return React16.createElement(
8007
- StatusTag3,
9687
+ return React17.createElement(
9688
+ StatusTag4,
8008
9689
  { variant: resolvedVariant, ...props },
8009
9690
  displayValue
8010
9691
  );
8011
9692
  };
8012
9693
 
8013
9694
  // src/common-components/CrmLookupSelect.js
8014
- import React18, { useMemo as useMemo7, useState as useState8 } from "react";
8015
- import { MultiSelect as MultiSelect4, Select as Select7, useDebounce as useDebounce2 } from "@hubspot/ui-extensions";
9695
+ import React19, { useMemo as useMemo8, useState as useState9 } from "react";
9696
+ import { MultiSelect as MultiSelect5, Select as Select8, useDebounce as useDebounce2 } from "@hubspot/ui-extensions";
8016
9697
 
8017
9698
  // src/utils/crmSearchAdapters.js
8018
- import React17, { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef5, useState as useState7 } from "react";
8019
- import { useCrmSearch, Flex as Flex10, Text as Text8 } from "@hubspot/ui-extensions";
9699
+ import React18, { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo7, useRef as useRef5, useState as useState8 } from "react";
9700
+ import { useCrmSearch, Flex as Flex11, Text as Text9 } from "@hubspot/ui-extensions";
8020
9701
 
8021
9702
  // src/utils/objectPath.js
8022
9703
  var getByPath = (obj, path) => {
@@ -8133,9 +9814,9 @@ var useCrmSearchDataSource = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) =>
8133
9814
  error,
8134
9815
  mapResponse
8135
9816
  } = options;
8136
- const config = useMemo6(() => buildCrmSearchConfig(params, options), [params, options]);
9817
+ const config = useMemo7(() => buildCrmSearchConfig(params, options), [params, options]);
8137
9818
  const response = useCrmSearch(config, format);
8138
- return useMemo6(() => {
9819
+ return useMemo7(() => {
8139
9820
  var _a;
8140
9821
  const rows = mapResponse ? mapResponse(response) : normalizeCrmSearchRows(response, { idField: rowIdField, ...row || EMPTY_OBJECT });
8141
9822
  const resolvedTotal = typeof totalCount === "function" ? totalCount(response) : totalCount ?? pickTotal(response, rows.length);
@@ -8173,7 +9854,7 @@ var crmSearchResultToOption = (row, options = EMPTY_OBJECT) => {
8173
9854
  var useCrmSearchOptions = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
8174
9855
  const dataSource = useCrmSearchDataSource(params, options);
8175
9856
  const optionConfig = options.option || options;
8176
- return useMemo6(() => ({
9857
+ return useMemo7(() => ({
8177
9858
  ...dataSource,
8178
9859
  options: dataSource.rows.map((row) => crmSearchResultToOption(row, optionConfig))
8179
9860
  }), [dataSource, optionConfig]);
@@ -8302,29 +9983,29 @@ var CrmDataTable = ({
8302
9983
  ...props
8303
9984
  }) => {
8304
9985
  var _a, _b;
8305
- const [params, setParams] = useState7({ search: "", filters: {}, sort: null });
8306
- const resolvedProperties = useMemo6(() => properties, [properties]);
8307
- const resolvedColumns = useMemo6(
9986
+ const [params, setParams] = useState8({ search: "", filters: {}, sort: null });
9987
+ const resolvedProperties = useMemo7(() => properties, [properties]);
9988
+ const resolvedColumns = useMemo7(
8308
9989
  () => columns || inferCrmColumns(resolvedProperties),
8309
9990
  [columns, resolvedProperties]
8310
9991
  );
8311
9992
  const resolvedSearchFields = searchFields || resolvedProperties;
8312
- const autoFilterFields = useMemo6(
9993
+ const autoFilterFields = useMemo7(
8313
9994
  () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
8314
9995
  [autoFilters, resolvedProperties]
8315
9996
  );
8316
9997
  const autoFilterLabelsRef = useRef5({});
8317
- const defaultPropertyMap = useMemo6(
9998
+ const defaultPropertyMap = useMemo7(
8318
9999
  () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
8319
10000
  [resolvedProperties]
8320
10001
  );
8321
10002
  const effectivePropertyMap = propertyMap || defaultPropertyMap;
8322
- const resolvedSortMap = useMemo6(
10003
+ const resolvedSortMap = useMemo7(
8323
10004
  () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
8324
10005
  [sortMap, effectivePropertyMap]
8325
10006
  );
8326
10007
  const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
8327
- const dataSourceOptions = useMemo6(
10008
+ const dataSourceOptions = useMemo7(
8328
10009
  () => ({
8329
10010
  objectType: resolveCrmObjectType(objectType),
8330
10011
  properties: resolvedProperties,
@@ -8338,15 +10019,15 @@ var CrmDataTable = ({
8338
10019
  }),
8339
10020
  [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
8340
10021
  );
8341
- const [serverQuerying, setServerQuerying] = useState7(!!serverSide);
10022
+ const [serverQuerying, setServerQuerying] = useState8(!!serverSide);
8342
10023
  const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
8343
10024
  const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
8344
- const queryKey = useMemo6(
10025
+ const queryKey = useMemo7(
8345
10026
  () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
8346
10027
  [effectiveParams, objectType, resolvedProperties, pageLength]
8347
10028
  );
8348
- const [accumulatedRows, setAccumulatedRows] = useState7(EMPTY_ARRAY);
8349
- const [requestedPage, setRequestedPage] = useState7(1);
10029
+ const [accumulatedRows, setAccumulatedRows] = useState8(EMPTY_ARRAY);
10030
+ const [requestedPage, setRequestedPage] = useState8(1);
8350
10031
  const lastQueryKeyRef = useRef5(queryKey);
8351
10032
  const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
8352
10033
  useEffect7(() => {
@@ -8375,7 +10056,7 @@ var CrmDataTable = ({
8375
10056
  useEffect7(() => {
8376
10057
  ensurePageLoaded(requestedPage);
8377
10058
  }, [requestedPage, ensurePageLoaded]);
8378
- const generatedFilters = useMemo6(
10059
+ const generatedFilters = useMemo7(
8379
10060
  () => buildAutoFiltersFromRows({
8380
10061
  rows: loadedRows,
8381
10062
  fields: autoFilterFields,
@@ -8385,7 +10066,7 @@ var CrmDataTable = ({
8385
10066
  [loadedRows, autoFilterFields, autoFilterMaxOptions]
8386
10067
  );
8387
10068
  const resolvedFilters = filters || generatedFilters;
8388
- const table = React17.createElement(DataTable, {
10069
+ const table = React18.createElement(DataTable, {
8389
10070
  title: title || `${prettifyPropertyName(objectType)} records`,
8390
10071
  data: loadedRows,
8391
10072
  loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
@@ -8409,11 +10090,11 @@ var CrmDataTable = ({
8409
10090
  const total = dataSource.totalCount;
8410
10091
  const capped = typeof total === "number" && total > loadedRows.length;
8411
10092
  if (!capped) return table;
8412
- return React17.createElement(
8413
- Flex10,
10093
+ return React18.createElement(
10094
+ Flex11,
8414
10095
  { direction: "column", gap: "xs" },
8415
- React17.createElement(
8416
- Text8,
10096
+ React18.createElement(
10097
+ Text9,
8417
10098
  { variant: "microcopy" },
8418
10099
  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.`
8419
10100
  ),
@@ -8447,25 +10128,25 @@ var CrmKanban = ({
8447
10128
  ...props
8448
10129
  }) => {
8449
10130
  var _a, _b;
8450
- const [params, setParams] = useState7(EMPTY_CRM_PARAMS);
8451
- const resolvedProperties = useMemo6(() => properties, [properties]);
10131
+ const [params, setParams] = useState8(EMPTY_CRM_PARAMS);
10132
+ const resolvedProperties = useMemo7(() => properties, [properties]);
8452
10133
  const resolvedSearchFields = searchFields || resolvedProperties;
8453
- const autoFilterFields = useMemo6(
10134
+ const autoFilterFields = useMemo7(
8454
10135
  () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
8455
10136
  [autoFilters, resolvedProperties]
8456
10137
  );
8457
10138
  const autoFilterLabelsRef = useRef5({});
8458
- const defaultPropertyMap = useMemo6(
10139
+ const defaultPropertyMap = useMemo7(
8459
10140
  () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
8460
10141
  [resolvedProperties]
8461
10142
  );
8462
10143
  const effectivePropertyMap = propertyMap || defaultPropertyMap;
8463
- const resolvedSortMap = useMemo6(
10144
+ const resolvedSortMap = useMemo7(
8464
10145
  () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
8465
10146
  [sortMap, effectivePropertyMap]
8466
10147
  );
8467
10148
  const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
8468
- const dataSourceOptions = useMemo6(
10149
+ const dataSourceOptions = useMemo7(
8469
10150
  () => ({
8470
10151
  objectType: resolveCrmObjectType(objectType),
8471
10152
  properties: resolvedProperties,
@@ -8479,14 +10160,14 @@ var CrmKanban = ({
8479
10160
  }),
8480
10161
  [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
8481
10162
  );
8482
- const [serverQuerying, setServerQuerying] = useState7(!!serverSide);
10163
+ const [serverQuerying, setServerQuerying] = useState8(!!serverSide);
8483
10164
  const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
8484
10165
  const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
8485
- const queryKey = useMemo6(
10166
+ const queryKey = useMemo7(
8486
10167
  () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
8487
10168
  [effectiveParams, objectType, resolvedProperties, pageLength]
8488
10169
  );
8489
- const [accumulatedRows, setAccumulatedRows] = useState7(EMPTY_ARRAY);
10170
+ const [accumulatedRows, setAccumulatedRows] = useState8(EMPTY_ARRAY);
8490
10171
  const lastQueryKeyRef = useRef5(queryKey);
8491
10172
  const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
8492
10173
  useEffect7(() => {
@@ -8513,7 +10194,7 @@ var CrmKanban = ({
8513
10194
  if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
8514
10195
  (_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
8515
10196
  }, [onLoadMore, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
8516
- const generatedFilters = useMemo6(
10197
+ const generatedFilters = useMemo7(
8517
10198
  () => buildAutoFiltersFromRows({
8518
10199
  rows: loadedRows,
8519
10200
  fields: autoFilterFields,
@@ -8523,7 +10204,7 @@ var CrmKanban = ({
8523
10204
  [loadedRows, autoFilterFields, autoFilterMaxOptions]
8524
10205
  );
8525
10206
  const resolvedFilters = filters || generatedFilters;
8526
- const resolvedStages = useMemo6(() => {
10207
+ const resolvedStages = useMemo7(() => {
8527
10208
  if (stages) return stages;
8528
10209
  const seen = [];
8529
10210
  for (const row of loadedRows) {
@@ -8535,7 +10216,7 @@ var CrmKanban = ({
8535
10216
  label: typeof stageLabels === "function" ? stageLabels(value) : stageLabels && stageLabels[value] || prettifyPropertyName(String(value))
8536
10217
  }));
8537
10218
  }, [stages, stageLabels, loadedRows, groupBy]);
8538
- const resolvedStageMeta = useMemo6(() => {
10219
+ const resolvedStageMeta = useMemo7(() => {
8539
10220
  if (stageMeta || !dataSource.hasMore) return stageMeta;
8540
10221
  return Object.fromEntries(resolvedStages.map((stage) => {
8541
10222
  var _a2;
@@ -8549,7 +10230,7 @@ var CrmKanban = ({
8549
10230
  ];
8550
10231
  }));
8551
10232
  }, [stageMeta, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.totalCount, resolvedStages]);
8552
- const board = React17.createElement(Kanban, {
10233
+ const board = React18.createElement(Kanban, {
8553
10234
  title: title || `${prettifyPropertyName(objectType)} board`,
8554
10235
  data: loadedRows,
8555
10236
  loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
@@ -8572,17 +10253,19 @@ var CrmKanban = ({
8572
10253
  const total = dataSource.totalCount;
8573
10254
  const capped = typeof total === "number" && total > loadedRows.length;
8574
10255
  if (!capped) return board;
8575
- return React17.createElement(
8576
- Flex10,
10256
+ return React18.createElement(
10257
+ Flex11,
8577
10258
  { direction: "column", gap: "xs" },
8578
- React17.createElement(
8579
- Text8,
10259
+ React18.createElement(
10260
+ Text9,
8580
10261
  { variant: "microcopy" },
8581
10262
  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.`
8582
10263
  ),
8583
10264
  board
8584
10265
  );
8585
10266
  };
10267
+ CrmDataTable.displayName = "CrmDataTable";
10268
+ CrmKanban.displayName = "CrmKanban";
8586
10269
 
8587
10270
  // src/common-components/CrmLookupSelect.js
8588
10271
  var EMPTY_ARRAY2 = [];
@@ -8635,12 +10318,12 @@ var CrmLookupSelect = ({
8635
10318
  loadingOption,
8636
10319
  selectProps = EMPTY_OBJECT2
8637
10320
  }) => {
8638
- const [inputValue, setInputValue] = useState8(query || "");
8639
- const [pickedOptions, setPickedOptions] = useState8(EMPTY_ARRAY2);
10321
+ const [inputValue, setInputValue] = useState9(query || "");
10322
+ const [pickedOptions, setPickedOptions] = useState9(EMPTY_ARRAY2);
8640
10323
  const debouncedInput = useDebounce2(inputValue, debounce > 0 ? debounce : 1);
8641
10324
  const search = debounce > 0 ? debouncedInput : inputValue;
8642
10325
  const effectiveSearch = search && search.length >= minSearchLength ? search : "";
8643
- const optionConfig = useMemo7(
10326
+ const optionConfig = useMemo8(
8644
10327
  () => makeOptionConfig({ option, labelProperty, valueProperty, descriptionProperty }),
8645
10328
  [option, labelProperty, valueProperty, descriptionProperty]
8646
10329
  );
@@ -8658,7 +10341,7 @@ var CrmLookupSelect = ({
8658
10341
  );
8659
10342
  const isSearching = dataSource.loading || inputValue.trim() !== (search || "").trim();
8660
10343
  const hasQuery = effectiveSearch.length > 0;
8661
- const options = useMemo7(() => {
10344
+ const options = useMemo8(() => {
8662
10345
  const remembered = [...selectedOptions || EMPTY_ARRAY2, ...pickedOptions];
8663
10346
  const baseOptions = mergeSelectedOptions(dataSource.options || EMPTY_ARRAY2, remembered, value);
8664
10347
  if (isSearching && loadingOption) return [loadingOption, ...baseOptions];
@@ -8697,7 +10380,281 @@ var CrmLookupSelect = ({
8697
10380
  },
8698
10381
  ...selectProps
8699
10382
  };
8700
- return React18.createElement(multiple ? MultiSelect4 : Select7, commonProps);
10383
+ return React19.createElement(multiple ? MultiSelect5 : Select8, commonProps);
10384
+ };
10385
+
10386
+ // src/common-components/CrmRecordPicker.js
10387
+ import React20, { useMemo as useMemo9, useRef as useRef6, useState as useState10 } from "react";
10388
+ import { Flex as Flex12, MultiSelect as MultiSelect6, SearchInput as SearchInput2, Select as Select9, useDebounce as useDebounce3 } from "@hubspot/ui-extensions";
10389
+
10390
+ // src/common-components/recordPickerCore.js
10391
+ var EMPTY_ARRAY3 = [];
10392
+ var CREATE_OPTION_VALUE = "__create__";
10393
+ var isRecordLike = (value) => value != null && typeof value === "object" && !Array.isArray(value);
10394
+ var getRecordId = (record) => {
10395
+ if (!isRecordLike(record)) return void 0;
10396
+ const id = record.objectId ?? record.id ?? record.hs_object_id ?? getByPath(record, "properties.hs_object_id");
10397
+ return id == null ? void 0 : String(id);
10398
+ };
10399
+ var toList = (value) => Array.isArray(value) ? value : value == null || value === "" ? EMPTY_ARRAY3 : [value];
10400
+ var normalizeRecordSelection = (value) => {
10401
+ const ids = [];
10402
+ const records = [];
10403
+ const seen = /* @__PURE__ */ new Set();
10404
+ for (const entry of toList(value)) {
10405
+ const rawId = isRecordLike(entry) ? getRecordId(entry) : entry;
10406
+ const id = rawId == null || rawId === "" ? rawId : String(rawId);
10407
+ if (id == null || id === "" || seen.has(id)) continue;
10408
+ seen.add(id);
10409
+ ids.push(id);
10410
+ if (isRecordLike(entry)) records.push(entry);
10411
+ }
10412
+ return { ids, records };
10413
+ };
10414
+ var recordToPickerOption = (record, config = {}) => {
10415
+ const { labelField, descriptionField, fallbackLabel = "Untitled record" } = config;
10416
+ const label = (labelField ? getByPath(record, labelField) : void 0) ?? (record == null ? void 0 : record.name) ?? getByPath(record, "properties.name") ?? fallbackLabel;
10417
+ const option = { label, value: getRecordId(record) };
10418
+ const description = descriptionField ? getByPath(record, descriptionField) : void 0;
10419
+ if (description != null && description !== "") option.description = description;
10420
+ return option;
10421
+ };
10422
+ var mergePickerOptions = (options, selectedOptions) => {
10423
+ const base = Array.isArray(options) ? options : EMPTY_ARRAY3;
10424
+ const selected = toList(selectedOptions);
10425
+ if (!selected.length) return base;
10426
+ const existing = new Set(base.map((option) => option == null ? void 0 : option.value));
10427
+ const missing = selected.filter((option) => option && !existing.has(option.value));
10428
+ return missing.length ? [...missing, ...base] : base;
10429
+ };
10430
+ var enforceSelectionMax = (ids, max) => {
10431
+ const list = toList(ids);
10432
+ if (!Number.isFinite(max) || max <= 0 || list.length <= max) return list;
10433
+ return list.slice(0, max);
10434
+ };
10435
+ var shouldShowCreateOption = ({
10436
+ allowCreate,
10437
+ searchTerm,
10438
+ options,
10439
+ searching = false,
10440
+ createPending = false,
10441
+ atMax = false
10442
+ } = {}) => {
10443
+ if (!allowCreate || typeof allowCreate.onCreate !== "function") return false;
10444
+ if (createPending || searching || atMax) return false;
10445
+ const term = String(searchTerm ?? "").trim();
10446
+ if (!term) return false;
10447
+ const lower = term.toLowerCase();
10448
+ return !(options || EMPTY_ARRAY3).some(
10449
+ (option) => String((option == null ? void 0 : option.label) ?? "").trim().toLowerCase() === lower
10450
+ );
10451
+ };
10452
+ var makeCreateOption = (term, label) => ({
10453
+ label: typeof label === "function" ? label(term) : label || `Create "${term}"`,
10454
+ value: CREATE_OPTION_VALUE
10455
+ });
10456
+ var splitCreateSelection = (next) => {
10457
+ const list = toList(next);
10458
+ const ids = list.filter((value) => value !== CREATE_OPTION_VALUE);
10459
+ return { ids, create: ids.length !== list.length };
10460
+ };
10461
+ var mapIdsToRecords = (ids, recordsById) => {
10462
+ const lookup = recordsById instanceof Map ? (id) => recordsById.get(id) : (id) => recordsById ? recordsById[id] : void 0;
10463
+ return toList(ids).map((id) => lookup(id) ?? { objectId: id });
10464
+ };
10465
+ var upsertRecords = (records, additions) => {
10466
+ const incoming = toList(additions).filter((record) => getRecordId(record) != null);
10467
+ if (!incoming.length) return Array.isArray(records) ? records : EMPTY_ARRAY3;
10468
+ const byId = new Map(toList(records).map((record) => [getRecordId(record), record]));
10469
+ for (const record of incoming) byId.set(getRecordId(record), record);
10470
+ return [...byId.values()];
10471
+ };
10472
+
10473
+ // src/common-components/CrmRecordPicker.js
10474
+ var EMPTY_ARRAY4 = [];
10475
+ var defaultMapRecord = (record) => ({ objectId: record.objectId, ...record.properties });
10476
+ var CrmRecordPicker = ({
10477
+ objectType,
10478
+ properties = EMPTY_ARRAY4,
10479
+ labelField,
10480
+ descriptionField,
10481
+ value,
10482
+ defaultValue,
10483
+ onChange,
10484
+ multi = true,
10485
+ max,
10486
+ placeholder,
10487
+ label,
10488
+ name,
10489
+ required,
10490
+ readOnly,
10491
+ error,
10492
+ validationMessage,
10493
+ description,
10494
+ tooltip,
10495
+ variant,
10496
+ pageLength = 20,
10497
+ debounce = 300,
10498
+ minSearchLength = 0,
10499
+ filterMap,
10500
+ allowCreate = false,
10501
+ fallbackLabel = "Untitled record",
10502
+ format,
10503
+ baseConfig,
10504
+ onSearchChange,
10505
+ ...rest
10506
+ }) => {
10507
+ const isControlled = value !== void 0;
10508
+ const [internalValue, setInternalValue] = useState10(defaultValue);
10509
+ const effectiveValue = isControlled ? value : internalValue;
10510
+ const selection = useMemo9(() => normalizeRecordSelection(effectiveValue), [effectiveValue]);
10511
+ const [inputValue, setInputValue] = useState10("");
10512
+ const [seenRecords, setSeenRecords] = useState10(EMPTY_ARRAY4);
10513
+ const [createPending, setCreatePending] = useState10(false);
10514
+ const [createError, setCreateError] = useState10(null);
10515
+ const createPendingRef = useRef6(false);
10516
+ const debouncedInput = useDebounce3(inputValue, debounce > 0 ? debounce : 1);
10517
+ const search = debounce > 0 ? debouncedInput : inputValue;
10518
+ const effectiveSearch = search && search.length >= minSearchLength ? search : "";
10519
+ const optionConfig = useMemo9(
10520
+ () => ({ labelField, descriptionField, fallbackLabel }),
10521
+ [labelField, descriptionField, fallbackLabel]
10522
+ );
10523
+ const searchParams = useMemo9(() => ({ search: effectiveSearch }), [effectiveSearch]);
10524
+ const dataSourceOptions = useMemo9(
10525
+ () => ({
10526
+ objectType: resolveCrmObjectType(objectType),
10527
+ properties,
10528
+ pageLength,
10529
+ format,
10530
+ filterMap,
10531
+ baseConfig,
10532
+ row: { mapRecord: defaultMapRecord },
10533
+ option: { mapOption: (row) => recordToPickerOption(row, optionConfig) }
10534
+ }),
10535
+ [objectType, properties, pageLength, format, filterMap, baseConfig, optionConfig]
10536
+ );
10537
+ const dataSource = useCrmSearchOptions(searchParams, dataSourceOptions);
10538
+ const recordsById = useMemo9(() => {
10539
+ const map = /* @__PURE__ */ new Map();
10540
+ for (const record of selection.records) map.set(getRecordId(record), record);
10541
+ for (const record of seenRecords) map.set(getRecordId(record), record);
10542
+ for (const row of dataSource.rows || EMPTY_ARRAY4) {
10543
+ const id = getRecordId(row);
10544
+ if (id != null) map.set(id, row);
10545
+ }
10546
+ return map;
10547
+ }, [selection.records, seenRecords, dataSource.rows]);
10548
+ const selectedOptions = useMemo9(
10549
+ () => selection.ids.map((id) => {
10550
+ const record = recordsById.get(id);
10551
+ return record ? recordToPickerOption(record, optionConfig) : { label: String(id), value: id };
10552
+ }),
10553
+ [selection.ids, recordsById, optionConfig]
10554
+ );
10555
+ const isSearching = dataSource.loading || inputValue.trim() !== (search || "").trim();
10556
+ const atMax = multi && Number.isFinite(max) && max > 0 && selection.ids.length >= max;
10557
+ const options = useMemo9(() => {
10558
+ const merged = mergePickerOptions(dataSource.options || EMPTY_ARRAY4, selectedOptions);
10559
+ const showCreate = shouldShowCreateOption({
10560
+ allowCreate,
10561
+ searchTerm: effectiveSearch,
10562
+ options: merged,
10563
+ searching: isSearching,
10564
+ createPending,
10565
+ atMax
10566
+ });
10567
+ if (!showCreate) return merged;
10568
+ return [...merged, makeCreateOption(effectiveSearch.trim(), allowCreate == null ? void 0 : allowCreate.label)];
10569
+ }, [dataSource.options, selectedOptions, allowCreate, effectiveSearch, isSearching, createPending, atMax]);
10570
+ const commitChange = (ids, extraRecords) => {
10571
+ let map = recordsById;
10572
+ if (extraRecords && extraRecords.length) {
10573
+ map = new Map(recordsById);
10574
+ for (const record of extraRecords) {
10575
+ const id = getRecordId(record);
10576
+ if (id != null) map.set(id, record);
10577
+ }
10578
+ }
10579
+ const trimmed = multi ? enforceSelectionMax(ids, max) : ids.slice(0, 1);
10580
+ const records = mapIdsToRecords(trimmed, map);
10581
+ if (!isControlled) setInternalValue(multi ? trimmed : trimmed[0] ?? null);
10582
+ if (onChange) {
10583
+ if (multi) onChange(trimmed, records);
10584
+ else onChange(trimmed[0] ?? null, records[0] ?? null);
10585
+ }
10586
+ };
10587
+ const startCreate = (term, baseIds) => {
10588
+ const onCreate = allowCreate && typeof allowCreate.onCreate === "function" ? allowCreate.onCreate : null;
10589
+ if (!onCreate || createPendingRef.current) return;
10590
+ createPendingRef.current = true;
10591
+ setCreatePending(true);
10592
+ setCreateError(null);
10593
+ Promise.resolve(onCreate(term)).then((created) => {
10594
+ const record = isRecordLike(created) ? created : created != null && created !== "" ? { objectId: created, name: term } : null;
10595
+ const id = getRecordId(record);
10596
+ if (id == null) return;
10597
+ setSeenRecords((prev) => upsertRecords(prev, [record]));
10598
+ const nextIds = multi ? [...baseIds.filter((v) => v !== id), id] : [id];
10599
+ commitChange(nextIds, [record]);
10600
+ }).catch((err) => {
10601
+ setCreateError((err == null ? void 0 : err.message) || "Could not create the record.");
10602
+ }).finally(() => {
10603
+ createPendingRef.current = false;
10604
+ setCreatePending(false);
10605
+ });
10606
+ };
10607
+ const handleChange = (next) => {
10608
+ if (createError) setCreateError(null);
10609
+ const { ids, create } = splitCreateSelection(next);
10610
+ const picked = ids.map((id) => recordsById.get(id)).filter(Boolean);
10611
+ if (picked.length) setSeenRecords((prev) => upsertRecords(prev, picked));
10612
+ if (create) {
10613
+ startCreate(effectiveSearch.trim(), ids);
10614
+ if (multi) commitChange(ids);
10615
+ return;
10616
+ }
10617
+ commitChange(ids);
10618
+ };
10619
+ const handleSearchInput = (next) => {
10620
+ setInputValue(next || "");
10621
+ if (onSearchChange) onSearchChange(next || "");
10622
+ };
10623
+ const commonProps = {
10624
+ name,
10625
+ label,
10626
+ value: multi ? selection.ids : selection.ids[0],
10627
+ options,
10628
+ placeholder: placeholder || (createPending ? "Creating record..." : dataSource.loading ? "Searching CRM..." : "Search CRM records..."),
10629
+ description,
10630
+ tooltip,
10631
+ required,
10632
+ readOnly: readOnly || createPending,
10633
+ error: error || !!createError || !!dataSource.error,
10634
+ validationMessage: validationMessage || createError || (typeof dataSource.error === "string" ? dataSource.error : void 0),
10635
+ variant,
10636
+ onChange: handleChange,
10637
+ ...rest
10638
+ };
10639
+ if (!multi) {
10640
+ return React20.createElement(Select9, { ...commonProps, onInput: handleSearchInput });
10641
+ }
10642
+ return React20.createElement(
10643
+ Flex12,
10644
+ { direction: "column", gap: "xs" },
10645
+ React20.createElement(SearchInput2, {
10646
+ name: name ? `${name}-search` : void 0,
10647
+ label: "",
10648
+ placeholder: "Search CRM records...",
10649
+ value: inputValue,
10650
+ readOnly: readOnly || createPending,
10651
+ onInput: handleSearchInput,
10652
+ // The clear "x" emits onChange (not onInput) — wire both so clearing
10653
+ // resets the term (same pattern as CollectionToolbar).
10654
+ onChange: handleSearchInput
10655
+ }),
10656
+ React20.createElement(MultiSelect6, commonProps)
10657
+ );
8701
10658
  };
8702
10659
 
8703
10660
  // src/common-components/datePresets.js
@@ -8722,12 +10679,480 @@ var HS_DATE_DIRECTION_LABELS = {
8722
10679
  desc: "Descending"
8723
10680
  };
8724
10681
 
10682
+ // src/common-components/dateRangePresets.js
10683
+ var DATE_RANGE_CUSTOM_VALUE = "custom";
10684
+ var DATE_FILTER_OPERATORS = [
10685
+ { label: "is", value: "InRollingDateRange" },
10686
+ { label: "is equal to", value: "Equal" },
10687
+ { label: "is before", value: "BeforeDateStaticOrDynamic" },
10688
+ { label: "is after", value: "AfterDateStaticOrDynamic" },
10689
+ { label: "is between", value: "InRange" },
10690
+ { label: "is more than", value: "GreaterRolling" },
10691
+ { label: "is less than", value: "LessRolling" },
10692
+ { label: "is known", value: "Known" },
10693
+ { label: "is unknown", value: "NotKnown" }
10694
+ ];
10695
+ var DATE_ROLLING_UNIT_OPTIONS = [
10696
+ { label: "day ago", value: "day:backward" },
10697
+ { label: "days from now", value: "day:forward" },
10698
+ { label: "week ago", value: "week:backward" },
10699
+ { label: "weeks from now", value: "week:forward" },
10700
+ { label: "month ago", value: "month:backward" },
10701
+ { label: "months from now", value: "month:forward" },
10702
+ { label: "year ago", value: "year:backward" },
10703
+ { label: "years from now", value: "year:forward" }
10704
+ ];
10705
+ var toHsDateValue = (date) => {
10706
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) return null;
10707
+ return { year: date.getFullYear(), month: date.getMonth(), date: date.getDate() };
10708
+ };
10709
+ var compareHsDateValues = (a, b) => {
10710
+ if (!a || !b) return 0;
10711
+ return a.year - b.year || a.month - b.month || a.date - b.date;
10712
+ };
10713
+ var isValidDateRange = (range) => {
10714
+ if (!range) return true;
10715
+ return compareHsDateValues(range.from, range.to) <= 0;
10716
+ };
10717
+ var dayAt = (now, offset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth(), now.getDate() + offset));
10718
+ var monthStart = (now, monthOffset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth() + monthOffset, 1));
10719
+ var monthEnd = (now, monthOffset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth() + monthOffset + 1, 0));
10720
+ var quarterStartMonth = (now, quarterOffset = 0) => Math.floor(now.getMonth() / 3) * 3 + quarterOffset * 3;
10721
+ var presetToRange = (presetKey, now = /* @__PURE__ */ new Date()) => {
10722
+ if (!presetKey || presetKey === DATE_RANGE_CUSTOM_VALUE) return null;
10723
+ if (!(now instanceof Date) || Number.isNaN(now.getTime())) return null;
10724
+ const dow = now.getDay();
10725
+ const year = now.getFullYear();
10726
+ const qm = quarterStartMonth(now);
10727
+ switch (presetKey) {
10728
+ case "today":
10729
+ return { from: dayAt(now, 0), to: dayAt(now, 0) };
10730
+ case "yesterday":
10731
+ return { from: dayAt(now, -1), to: dayAt(now, -1) };
10732
+ case "tomorrow":
10733
+ return { from: dayAt(now, 1), to: dayAt(now, 1) };
10734
+ case "this_week":
10735
+ return { from: dayAt(now, -dow), to: dayAt(now, 6 - dow) };
10736
+ case "last_week":
10737
+ return { from: dayAt(now, -dow - 7), to: dayAt(now, -dow - 1) };
10738
+ case "7d":
10739
+ return { from: dayAt(now, -6), to: dayAt(now, 0) };
10740
+ case "30d":
10741
+ return { from: dayAt(now, -29), to: dayAt(now, 0) };
10742
+ case "90d":
10743
+ return { from: dayAt(now, -89), to: dayAt(now, 0) };
10744
+ case "this_month":
10745
+ return { from: monthStart(now, 0), to: monthEnd(now, 0) };
10746
+ case "last_month":
10747
+ return { from: monthStart(now, -1), to: monthEnd(now, -1) };
10748
+ case "this_quarter":
10749
+ return {
10750
+ from: toHsDateValue(new Date(year, qm, 1)),
10751
+ to: toHsDateValue(new Date(year, qm + 3, 0))
10752
+ };
10753
+ case "last_quarter":
10754
+ return {
10755
+ from: toHsDateValue(new Date(year, qm - 3, 1)),
10756
+ to: toHsDateValue(new Date(year, qm, 0))
10757
+ };
10758
+ case "this_year":
10759
+ return { from: { year, month: 0, date: 1 }, to: { year, month: 11, date: 31 } };
10760
+ case "last_year":
10761
+ return {
10762
+ from: { year: year - 1, month: 0, date: 1 },
10763
+ to: { year: year - 1, month: 11, date: 31 }
10764
+ };
10765
+ default:
10766
+ return null;
10767
+ }
10768
+ };
10769
+
10770
+ // src/common-components/DateRangePicker.js
10771
+ import React21, { useState as useState11 } from "react";
10772
+ import {
10773
+ AutoGrid as AutoGrid4,
10774
+ Box as Box8,
10775
+ DateInput as DateInput6,
10776
+ Flex as Flex13,
10777
+ Link as Link7,
10778
+ NumberInput as NumberInput4,
10779
+ Select as Select10,
10780
+ Text as Text10
10781
+ } from "@hubspot/ui-extensions";
10782
+ var h6 = React21.createElement;
10783
+ var IN_ROLLING = "InRollingDateRange";
10784
+ var EQUAL = "Equal";
10785
+ var BEFORE = "BeforeDateStaticOrDynamic";
10786
+ var AFTER = "AfterDateStaticOrDynamic";
10787
+ var GREATER_ROLLING = "GreaterRolling";
10788
+ var LESS_ROLLING = "LessRolling";
10789
+ var IN_RANGE = "InRange";
10790
+ var KNOWN = "Known";
10791
+ var NOT_KNOWN = "NotKnown";
10792
+ var EMPTY_RANGE = { from: null, to: null };
10793
+ var EMPTY_DATE = { date: null };
10794
+ var COMPACT_LABEL = "";
10795
+ var STATIC_DATE_OPERATORS = /* @__PURE__ */ new Set([EQUAL, BEFORE, AFTER]);
10796
+ var ROLLING_OPERATORS = /* @__PURE__ */ new Set([GREATER_ROLLING, LESS_ROLLING]);
10797
+ var PRESENCE_OPERATORS = /* @__PURE__ */ new Set([KNOWN, NOT_KNOWN]);
10798
+ var keyOfDate = (v) => v ? `${v.year}-${v.month}-${v.date}` : "";
10799
+ var keyOfRange = (r) => `${keyOfDate(r == null ? void 0 : r.from)}|${keyOfDate(r == null ? void 0 : r.to)}`;
10800
+ var isRangeLike = (value) => value && typeof value === "object" && ("from" in value || "to" in value) && !("operator" in value);
10801
+ var normalizeValue = (value) => {
10802
+ if (isRangeLike(value)) {
10803
+ return { operator: IN_RANGE, from: value.from ?? null, to: value.to ?? null };
10804
+ }
10805
+ if (!value || typeof value !== "object") {
10806
+ return { operator: IN_ROLLING, preset: "today" };
10807
+ }
10808
+ const operator = value.operator || IN_ROLLING;
10809
+ if (operator === IN_RANGE) {
10810
+ return { operator, from: value.from ?? null, to: value.to ?? null };
10811
+ }
10812
+ if (STATIC_DATE_OPERATORS.has(operator)) {
10813
+ return { operator, date: value.date ?? null };
10814
+ }
10815
+ if (ROLLING_OPERATORS.has(operator)) {
10816
+ return {
10817
+ operator,
10818
+ amount: Number.isFinite(Number(value.amount)) ? Number(value.amount) : 1,
10819
+ unit: value.unit || "day",
10820
+ direction: value.direction || "backward"
10821
+ };
10822
+ }
10823
+ if (PRESENCE_OPERATORS.has(operator)) {
10824
+ return { operator };
10825
+ }
10826
+ return { operator: IN_ROLLING, preset: value.preset || value.value || "today" };
10827
+ };
10828
+ var rangeFromValue = (value) => ({
10829
+ from: (value == null ? void 0 : value.from) ?? null,
10830
+ to: (value == null ? void 0 : value.to) ?? null
10831
+ });
10832
+ var getPresetOptions = (presets, customPresetLabel) => {
10833
+ const presetList = presets === true ? HS_DATE_PRESETS : Array.isArray(presets) ? presets : null;
10834
+ if (!presetList) return null;
10835
+ return [
10836
+ ...presetList.map((p) => ({ label: p.label, value: p.value })),
10837
+ ...presetList.some((p) => p.value === DATE_RANGE_CUSTOM_VALUE) ? [] : [{ label: customPresetLabel, value: DATE_RANGE_CUSTOM_VALUE }]
10838
+ ];
10839
+ };
10840
+ var DateRangePicker = ({
10841
+ value,
10842
+ defaultValue,
10843
+ onChange,
10844
+ label,
10845
+ name = "date-range",
10846
+ field,
10847
+ defaultField,
10848
+ onFieldChange,
10849
+ showFieldSelect = false,
10850
+ fieldOptions = [],
10851
+ operator,
10852
+ defaultOperator = IN_ROLLING,
10853
+ onOperatorChange,
10854
+ showOperatorSelect = true,
10855
+ operatorOptions = DATE_FILTER_OPERATORS,
10856
+ presets = true,
10857
+ rollingUnitOptions = DATE_ROLLING_UNIT_OPTIONS,
10858
+ direction = "row",
10859
+ clearable = false,
10860
+ min,
10861
+ max,
10862
+ fromLabel = "Start date",
10863
+ toLabel = "End date",
10864
+ dateLabel = "Date",
10865
+ showDateLabels = false,
10866
+ format = "medium",
10867
+ presetPlaceholder = "Enter value",
10868
+ customPresetLabel = "Custom",
10869
+ clearLabel = "Clear",
10870
+ invalidRangeMessage = "Start date must be on or before end date",
10871
+ readOnly = false,
10872
+ gap,
10873
+ gridColumnWidth = 260
10874
+ }) => {
10875
+ const isControlled = value !== void 0;
10876
+ const controlledValue = normalizeValue(value);
10877
+ const [internalValue, setInternalValue] = useState11(
10878
+ () => normalizeValue(defaultValue ?? { operator: defaultOperator })
10879
+ );
10880
+ const [internalField, setInternalField] = useState11(
10881
+ () => {
10882
+ var _a;
10883
+ return defaultField ?? ((_a = fieldOptions == null ? void 0 : fieldOptions[0]) == null ? void 0 : _a.value) ?? "";
10884
+ }
10885
+ );
10886
+ const current = normalizeValue(isControlled ? controlledValue : internalValue);
10887
+ const currentOperator = operator || current.operator || defaultOperator;
10888
+ const currentField = field !== void 0 ? field : internalField;
10889
+ const resolvedCurrent = normalizeValue({ ...current, operator: currentOperator });
10890
+ const [pending, setPending] = useState11(null);
10891
+ const [lastPreset, setLastPreset] = useState11({
10892
+ key: resolvedCurrent.preset || "",
10893
+ rangeKey: null
10894
+ });
10895
+ const isColumn = direction === "column";
10896
+ const presetOptions = getPresetOptions(presets, customPresetLabel);
10897
+ const showClear = clearable && !readOnly && (resolvedCurrent.preset || resolvedCurrent.date || resolvedCurrent.amount || resolvedCurrent.from || resolvedCurrent.to || pending);
10898
+ const emit = (next, meta = {}) => {
10899
+ const normalized = normalizeValue(next);
10900
+ if (!isControlled) setInternalValue(normalized);
10901
+ if (normalized.operator !== currentOperator) {
10902
+ onOperatorChange == null ? void 0 : onOperatorChange(normalized.operator);
10903
+ }
10904
+ onChange == null ? void 0 : onChange(normalized, {
10905
+ operator: normalized.operator,
10906
+ field: currentField || null,
10907
+ preset: normalized.operator === IN_ROLLING ? normalized.preset ?? null : null,
10908
+ ...meta
10909
+ });
10910
+ };
10911
+ const handleFieldChange = (nextField) => {
10912
+ if (field === void 0) setInternalField(nextField);
10913
+ onFieldChange == null ? void 0 : onFieldChange(nextField);
10914
+ onChange == null ? void 0 : onChange(resolvedCurrent, {
10915
+ operator: resolvedCurrent.operator,
10916
+ field: nextField || null,
10917
+ preset: resolvedCurrent.operator === IN_ROLLING ? resolvedCurrent.preset ?? null : null
10918
+ });
10919
+ };
10920
+ const handleOperatorChange = (nextOperator) => {
10921
+ setPending(null);
10922
+ if (nextOperator === IN_RANGE) {
10923
+ emit({ operator: IN_RANGE, ...EMPTY_RANGE }, { previousOperator: currentOperator });
10924
+ } else if (STATIC_DATE_OPERATORS.has(nextOperator)) {
10925
+ emit({ operator: nextOperator, ...EMPTY_DATE }, { previousOperator: currentOperator });
10926
+ } else if (ROLLING_OPERATORS.has(nextOperator)) {
10927
+ emit(
10928
+ { operator: nextOperator, amount: 1, unit: "day", direction: "backward" },
10929
+ { previousOperator: currentOperator }
10930
+ );
10931
+ } else if (PRESENCE_OPERATORS.has(nextOperator)) {
10932
+ emit({ operator: nextOperator }, { previousOperator: currentOperator });
10933
+ } else {
10934
+ emit({ operator: IN_ROLLING, preset: "today" }, { previousOperator: currentOperator });
10935
+ }
10936
+ };
10937
+ const handlePresetChange = (preset) => {
10938
+ if (!preset || preset === DATE_RANGE_CUSTOM_VALUE) {
10939
+ emit({ operator: IN_ROLLING, preset: DATE_RANGE_CUSTOM_VALUE });
10940
+ return;
10941
+ }
10942
+ const option = presets === true ? HS_DATE_PRESETS.find((p) => p.value === preset) : Array.isArray(presets) ? presets.find((p) => p.value === preset) : null;
10943
+ const range = option && typeof option.getRange === "function" ? option.getRange(/* @__PURE__ */ new Date()) : presetToRange(preset);
10944
+ setLastPreset({ key: preset, rangeKey: range ? keyOfRange(range) : null });
10945
+ emit({ operator: IN_ROLLING, preset }, { range });
10946
+ };
10947
+ const handleRollingUnitChange = (compound) => {
10948
+ const [unit, unitDirection] = String(compound || "day:backward").split(":");
10949
+ emit({
10950
+ operator: currentOperator,
10951
+ amount: resolvedCurrent.amount || 1,
10952
+ unit,
10953
+ direction: unitDirection || "backward"
10954
+ });
10955
+ };
10956
+ const handleDateChange = (side, next) => {
10957
+ const displayRange = {
10958
+ ...rangeFromValue(resolvedCurrent),
10959
+ ...pending ? { [pending.side]: pending.value } : {}
10960
+ };
10961
+ const candidate = { ...displayRange, [side]: next ?? null };
10962
+ if (isValidDateRange(candidate)) {
10963
+ setPending(null);
10964
+ setLastPreset({ key: "", rangeKey: null });
10965
+ emit({ operator: IN_RANGE, ...candidate });
10966
+ } else {
10967
+ setPending({ side, value: next ?? null });
10968
+ }
10969
+ };
10970
+ const handleStaticDateChange = (next) => {
10971
+ emit({ operator: currentOperator, date: next ?? null });
10972
+ };
10973
+ const handleClear = () => {
10974
+ setPending(null);
10975
+ setLastPreset({ key: "", rangeKey: null });
10976
+ if (currentOperator === IN_RANGE) {
10977
+ emit({ operator: IN_RANGE, ...EMPTY_RANGE });
10978
+ } else if (STATIC_DATE_OPERATORS.has(currentOperator)) {
10979
+ emit({ operator: currentOperator, ...EMPTY_DATE });
10980
+ } else if (ROLLING_OPERATORS.has(currentOperator)) {
10981
+ emit({ operator: currentOperator, amount: 1, unit: "day", direction: "backward" });
10982
+ } else if (PRESENCE_OPERATORS.has(currentOperator)) {
10983
+ emit({ operator: currentOperator });
10984
+ } else {
10985
+ emit({ operator: IN_ROLLING, preset: "" });
10986
+ }
10987
+ };
10988
+ const operatorSelect = showOperatorSelect ? h6(Select10, {
10989
+ key: "operator",
10990
+ name: `${name}-operator`,
10991
+ label: COMPACT_LABEL,
10992
+ options: operatorOptions,
10993
+ value: currentOperator,
10994
+ onChange: handleOperatorChange,
10995
+ readOnly
10996
+ }) : null;
10997
+ const fieldSelect = showFieldSelect ? h6(Select10, {
10998
+ key: "field",
10999
+ name: `${name}-field`,
11000
+ label: "",
11001
+ options: fieldOptions,
11002
+ value: currentField,
11003
+ onChange: handleFieldChange,
11004
+ readOnly
11005
+ }) : null;
11006
+ let valueInput = null;
11007
+ const fromInputLabel = showDateLabels ? fromLabel : COMPACT_LABEL;
11008
+ const toInputLabel = showDateLabels ? toLabel : COMPACT_LABEL;
11009
+ const singleDateInputLabel = showDateLabels ? dateLabel : COMPACT_LABEL;
11010
+ if (currentOperator === IN_RANGE) {
11011
+ const committed = rangeFromValue(resolvedCurrent);
11012
+ const display = {
11013
+ ...committed,
11014
+ ...pending ? { [pending.side]: pending.value } : {}
11015
+ };
11016
+ const invalidSide = pending ? pending.side : null;
11017
+ valueInput = [
11018
+ h6(DateInput6, {
11019
+ key: "from",
11020
+ name: `${name}-from`,
11021
+ label: fromInputLabel,
11022
+ format,
11023
+ value: display.from ?? null,
11024
+ onChange: (next) => handleDateChange("from", next),
11025
+ min,
11026
+ max,
11027
+ readOnly,
11028
+ error: invalidSide === "from",
11029
+ validationMessage: invalidSide === "from" ? invalidRangeMessage : void 0
11030
+ }),
11031
+ isColumn ? null : h6(Text10, { key: "to" }, "to"),
11032
+ h6(DateInput6, {
11033
+ key: "toDate",
11034
+ name: `${name}-to`,
11035
+ label: toInputLabel,
11036
+ format,
11037
+ value: display.to ?? null,
11038
+ onChange: (next) => handleDateChange("to", next),
11039
+ min,
11040
+ max,
11041
+ readOnly,
11042
+ error: invalidSide === "to",
11043
+ validationMessage: invalidSide === "to" ? invalidRangeMessage : void 0
11044
+ })
11045
+ ];
11046
+ } else if (STATIC_DATE_OPERATORS.has(currentOperator)) {
11047
+ valueInput = h6(DateInput6, {
11048
+ key: "date",
11049
+ name: `${name}-date`,
11050
+ label: singleDateInputLabel,
11051
+ format,
11052
+ value: resolvedCurrent.date ?? null,
11053
+ onChange: handleStaticDateChange,
11054
+ min,
11055
+ max,
11056
+ readOnly
11057
+ });
11058
+ } else if (ROLLING_OPERATORS.has(currentOperator)) {
11059
+ const compound = `${resolvedCurrent.unit || "day"}:${resolvedCurrent.direction || "backward"}`;
11060
+ valueInput = [
11061
+ h6(NumberInput4, {
11062
+ key: "amount",
11063
+ name: `${name}-amount`,
11064
+ label: COMPACT_LABEL,
11065
+ min: 0,
11066
+ value: resolvedCurrent.amount ?? 1,
11067
+ onChange: (amount) => emit({
11068
+ operator: currentOperator,
11069
+ amount: Number.isFinite(Number(amount)) ? Number(amount) : 0,
11070
+ unit: resolvedCurrent.unit || "day",
11071
+ direction: resolvedCurrent.direction || "backward"
11072
+ }),
11073
+ readOnly
11074
+ }),
11075
+ h6(Select10, {
11076
+ key: "unit",
11077
+ name: `${name}-rolling-unit`,
11078
+ label: COMPACT_LABEL,
11079
+ options: rollingUnitOptions,
11080
+ value: compound,
11081
+ onChange: handleRollingUnitChange,
11082
+ readOnly
11083
+ })
11084
+ ];
11085
+ } else if (PRESENCE_OPERATORS.has(currentOperator)) {
11086
+ valueInput = null;
11087
+ } else {
11088
+ const range = presetToRange(resolvedCurrent.preset);
11089
+ const presetValue = resolvedCurrent.preset || (lastPreset.rangeKey && range && lastPreset.rangeKey === keyOfRange(range) ? lastPreset.key : "");
11090
+ valueInput = h6(Select10, {
11091
+ key: "preset",
11092
+ name: `${name}-preset`,
11093
+ label: COMPACT_LABEL,
11094
+ placeholder: presetPlaceholder,
11095
+ options: presetOptions || [],
11096
+ value: presetValue,
11097
+ onChange: handlePresetChange,
11098
+ readOnly
11099
+ });
11100
+ }
11101
+ const valueChildren = [
11102
+ ...Array.isArray(valueInput) ? valueInput : valueInput ? [valueInput] : [],
11103
+ showClear ? h6(Link7, { key: "clear", onClick: handleClear }, clearLabel) : null
11104
+ ];
11105
+ const children = [operatorSelect, ...valueChildren];
11106
+ if (fieldSelect) {
11107
+ const rowChildren = [
11108
+ h6(Box8, { key: "field-box", flex: "auto", alignSelf: "stretch" }, fieldSelect),
11109
+ operatorSelect ? h6(Box8, { key: "operator-box", flex: "auto", alignSelf: "stretch" }, operatorSelect) : null,
11110
+ ...valueChildren.map(
11111
+ (child, index) => (child == null ? void 0 : child.type) === Text10 || (child == null ? void 0 : child.type) === Link7 ? child : h6(Box8, { key: `value-box-${index}`, flex: "auto", alignSelf: "stretch" }, child)
11112
+ )
11113
+ ].filter(Boolean);
11114
+ const fieldControl = h6(
11115
+ AutoGrid4,
11116
+ {
11117
+ columnWidth: gridColumnWidth,
11118
+ flexible: true,
11119
+ gap: gap ?? "xs"
11120
+ },
11121
+ ...rowChildren
11122
+ );
11123
+ if (!label) return fieldControl;
11124
+ return h6(
11125
+ Flex13,
11126
+ { direction: "column", gap: "xs" },
11127
+ h6(Text10, { format: { fontWeight: "demibold" } }, label),
11128
+ fieldControl
11129
+ );
11130
+ }
11131
+ const control = h6(
11132
+ Flex13,
11133
+ {
11134
+ direction: isColumn ? "column" : "row",
11135
+ align: isColumn ? "stretch" : "center",
11136
+ gap: gap ?? (isColumn ? "sm" : "xs"),
11137
+ wrap: isColumn ? void 0 : "wrap"
11138
+ },
11139
+ ...children
11140
+ );
11141
+ if (!label) return control;
11142
+ return h6(
11143
+ Flex13,
11144
+ { direction: "column", gap: "xs" },
11145
+ h6(Text10, { format: { fontWeight: "demibold" } }, label),
11146
+ control
11147
+ );
11148
+ };
11149
+
8725
11150
  // src/common-components/KeyValueList.js
8726
- import React19 from "react";
8727
- import { DescriptionList as DescriptionList3, DescriptionListItem as DescriptionListItem3, Flex as Flex11 } from "@hubspot/ui-extensions";
11151
+ import React22 from "react";
11152
+ import { DescriptionList as DescriptionList3, DescriptionListItem as DescriptionListItem3, Flex as Flex14 } from "@hubspot/ui-extensions";
8728
11153
  var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
8729
11154
  const rows = items.map(
8730
- (item, index) => React19.createElement(
11155
+ (item, index) => React22.createElement(
8731
11156
  DescriptionListItem3,
8732
11157
  {
8733
11158
  key: item.key ?? item.label ?? `kv-${index}`,
@@ -8736,16 +11161,17 @@ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
8736
11161
  item.value
8737
11162
  )
8738
11163
  );
8739
- return React19.createElement(
8740
- Flex11,
11164
+ return React22.createElement(
11165
+ Flex14,
8741
11166
  { direction: "column", gap },
8742
- React19.createElement(DescriptionList3, { direction }, ...rows)
11167
+ React22.createElement(DescriptionList3, { direction }, ...rows)
8743
11168
  );
8744
11169
  };
11170
+ KeyValueList.displayName = "KeyValueList";
8745
11171
 
8746
11172
  // src/common-components/SectionHeader.js
8747
- import React20 from "react";
8748
- import { Flex as Flex12, Heading as Heading2, Text as Text9 } from "@hubspot/ui-extensions";
11173
+ import React23 from "react";
11174
+ import { Flex as Flex15, Heading as Heading2, Text as Text11 } from "@hubspot/ui-extensions";
8749
11175
  var SectionHeader = ({
8750
11176
  title,
8751
11177
  description,
@@ -8756,12 +11182,12 @@ var SectionHeader = ({
8756
11182
  }) => {
8757
11183
  const body = [];
8758
11184
  if (title != null) {
8759
- body.push(React20.createElement(Heading2, { key: "title", as: titleAs }, title));
11185
+ body.push(React23.createElement(Heading2, { key: "title", as: titleAs }, title));
8760
11186
  }
8761
11187
  if (description != null) {
8762
11188
  body.push(
8763
- React20.createElement(
8764
- Text9,
11189
+ React23.createElement(
11190
+ Text11,
8765
11191
  { key: "description", variant: "microcopy" },
8766
11192
  description
8767
11193
  )
@@ -8770,10 +11196,10 @@ var SectionHeader = ({
8770
11196
  if (children != null) {
8771
11197
  body.push(children);
8772
11198
  }
8773
- const content = React20.createElement(Flex12, { direction: "column", gap }, ...body);
11199
+ const content = React23.createElement(Flex15, { direction: "column", gap }, ...body);
8774
11200
  if (actions == null) return content;
8775
- return React20.createElement(
8776
- Flex12,
11201
+ return React23.createElement(
11202
+ Flex15,
8777
11203
  { direction: "row", justify: "between", align: "start", gap: "sm" },
8778
11204
  content,
8779
11205
  actions
@@ -8781,8 +11207,8 @@ var SectionHeader = ({
8781
11207
  };
8782
11208
 
8783
11209
  // src/common-components/Spinner.js
8784
- import React21, { useEffect as useEffect8, useRef as useRef6, useState as useState9 } from "react";
8785
- import { Text as Text10 } from "@hubspot/ui-extensions";
11210
+ import React24, { useEffect as useEffect8, useRef as useRef7, useState as useState12 } from "react";
11211
+ import { Text as Text12 } from "@hubspot/ui-extensions";
8786
11212
 
8787
11213
  // src/common-components/spinners.js
8788
11214
  var BRAILLE_DOT_MAP = [
@@ -9122,8 +11548,8 @@ var Spinner = ({
9122
11548
  const preset = SPINNERS[name] || SPINNERS[DEFAULT_NAME];
9123
11549
  const resolvedFrames = Array.isArray(frames) && frames.length > 0 ? frames : preset.frames;
9124
11550
  const resolvedInterval = Number.isFinite(interval) ? interval : preset.interval;
9125
- const [index, setIndex] = useState9(0);
9126
- const indexRef = useRef6(0);
11551
+ const [index, setIndex] = useState12(0);
11552
+ const indexRef = useRef7(0);
9127
11553
  indexRef.current = index;
9128
11554
  useEffect8(() => {
9129
11555
  if (paused || resolvedFrames.length <= 1) return void 0;
@@ -9140,10 +11566,10 @@ var Spinner = ({
9140
11566
  const frame = resolvedFrames[index % resolvedFrames.length];
9141
11567
  const suffix = children != null ? children : label;
9142
11568
  if (suffix == null || suffix === "") {
9143
- return React21.createElement(Text10, rest, frame);
11569
+ return React24.createElement(Text12, rest, frame);
9144
11570
  }
9145
- return React21.createElement(
9146
- Text10,
11571
+ return React24.createElement(
11572
+ Text12,
9147
11573
  rest,
9148
11574
  frame,
9149
11575
  gap,
@@ -9151,6 +11577,378 @@ var Spinner = ({
9151
11577
  );
9152
11578
  };
9153
11579
 
11580
+ // src/safe/catalogs.js
11581
+ var ICON_NAME_ALIASES2 = {
11582
+ alert: "warning",
11583
+ // "alert" is a color, not an icon
11584
+ check: "success",
11585
+ checkmark: "success",
11586
+ danger: "xCircle",
11587
+ // "danger" is a StatusTag variant
11588
+ duplicate: "copy",
11589
+ error: "xCircle",
11590
+ // "error" is a Tag variant, not an icon
11591
+ trash: "delete",
11592
+ pencil: "edit",
11593
+ arrowLeft: "left",
11594
+ arrowRight: "right",
11595
+ arrowUp: "upCarat",
11596
+ arrowDown: "downCarat",
11597
+ cog: "settings",
11598
+ gear: "settings",
11599
+ close: "xCircle",
11600
+ plus: "add",
11601
+ minus: "remove",
11602
+ ok: "success"
11603
+ };
11604
+ var EMPTY_STATE_IMAGES = /* @__PURE__ */ new Set([
11605
+ "addOnReporting",
11606
+ "announcement",
11607
+ "api",
11608
+ "automatedTesting",
11609
+ "beta",
11610
+ "building",
11611
+ "callingSetUp",
11612
+ "companies",
11613
+ "components",
11614
+ "cone",
11615
+ "contacts",
11616
+ "contentStrategy",
11617
+ "customObjects",
11618
+ "customerExperience",
11619
+ "customerSupport",
11620
+ "deals",
11621
+ "developerSecurityUpdate",
11622
+ "electronicSignature",
11623
+ "electronicSignatureEmptyState",
11624
+ "emailConfirmation",
11625
+ "emptyStateCharts",
11626
+ "idea",
11627
+ "integrations",
11628
+ "leads",
11629
+ "lock",
11630
+ "missedGoal",
11631
+ "multipleObjects",
11632
+ "object",
11633
+ "productsShoppingCart",
11634
+ "registration",
11635
+ "sandboxAddOn",
11636
+ "social",
11637
+ "store",
11638
+ "storeDisabled",
11639
+ "successfullyConnectedEmail",
11640
+ "target",
11641
+ "task",
11642
+ "voteAndSearch",
11643
+ "meetings",
11644
+ "tickets"
11645
+ ]);
11646
+ var EMPTY_STATE_IMAGE_ALIASES = {
11647
+ "new-project": "components",
11648
+ newProject: "components",
11649
+ empty: "components",
11650
+ default: "components"
11651
+ };
11652
+ var TREND_DIRECTIONS = /* @__PURE__ */ new Set(["increase", "decrease"]);
11653
+ var TREND_DIRECTION_ALIASES = {
11654
+ increasing: "increase",
11655
+ decreasing: "decrease",
11656
+ up: "increase",
11657
+ down: "decrease",
11658
+ positive: "increase",
11659
+ negative: "decrease"
11660
+ };
11661
+ var SAFE_ARRAY_PROPS = {
11662
+ // native @hubspot/ui-extensions
11663
+ Select: ["options"],
11664
+ MultiSelect: ["options"],
11665
+ ToggleGroup: ["options"],
11666
+ StepIndicator: ["stepNames"],
11667
+ // hs-uix
11668
+ DataTable: ["data", "columns", "searchFields", "filters", "selectionActions"],
11669
+ Kanban: ["data", "stages"],
11670
+ FormBuilder: ["fields"],
11671
+ AvatarStack: ["items"],
11672
+ KeyValueList: ["items"],
11673
+ Feed: ["items", "fields"],
11674
+ Calendar: ["events"],
11675
+ CrmKanban: ["cardFields"]
11676
+ };
11677
+ var SAFE_DERIVE_PROPS = {
11678
+ CrmDataTable: ["columns"],
11679
+ CrmKanban: ["stages"]
11680
+ };
11681
+
11682
+ // src/safe/warnings.js
11683
+ var warned = /* @__PURE__ */ new Set();
11684
+ function warnOnce(key, message) {
11685
+ if (warned.has(key)) return;
11686
+ warned.add(key);
11687
+ console.warn(message);
11688
+ }
11689
+ function resetSafeWarnings() {
11690
+ warned.clear();
11691
+ }
11692
+
11693
+ // src/safe/withSafeArrayProps.js
11694
+ import React25 from "react";
11695
+ var arrayProp = (value, componentName, propName) => {
11696
+ if (Array.isArray(value)) return value;
11697
+ if (value == null) return [];
11698
+ warnOnce(
11699
+ `${componentName}-${propName}-not-array`,
11700
+ `[hs-uix/safe] ${componentName}.${propName} must be an array \u2014 received ${typeof value}; using [].`
11701
+ );
11702
+ return [];
11703
+ };
11704
+ function withSafeArrayProps(Component, componentName, propNames, derivePropNames = []) {
11705
+ if (!Component) return void 0;
11706
+ const SafeComponent = React25.forwardRef((props, ref) => {
11707
+ const next = { ...props || {} };
11708
+ for (const propName of propNames) {
11709
+ next[propName] = arrayProp(next[propName], componentName, propName);
11710
+ }
11711
+ for (const propName of derivePropNames) {
11712
+ const value = next[propName];
11713
+ if (value == null || Array.isArray(value)) continue;
11714
+ warnOnce(
11715
+ `${componentName}-${propName}-not-array`,
11716
+ `[hs-uix/safe] ${componentName}.${propName} must be an array \u2014 received ${typeof value}; omitting it so ${componentName} derives it automatically.`
11717
+ );
11718
+ delete next[propName];
11719
+ }
11720
+ return React25.createElement(Component, ref != null ? { ...next, ref } : next);
11721
+ });
11722
+ SafeComponent.displayName = `Safe${componentName}`;
11723
+ return SafeComponent;
11724
+ }
11725
+
11726
+ // src/safe/SafeIcon.js
11727
+ import React26 from "react";
11728
+ import { Icon as Icon2 } from "@hubspot/ui-extensions";
11729
+ var SafeIcon = (props) => {
11730
+ const { name, ...rest } = props || {};
11731
+ if (typeof name === "string" && NATIVE_ICON_NAMES.has(name)) {
11732
+ return React26.createElement(Icon2, { name, ...rest });
11733
+ }
11734
+ const alias = ICON_NAME_ALIASES2[name];
11735
+ if (alias && NATIVE_ICON_NAMES.has(alias)) {
11736
+ warnOnce(
11737
+ `icon-alias-${name}`,
11738
+ `[hs-uix/safe] Icon name "${name}" is not in the catalog \u2014 auto-repaired to "${alias}".`
11739
+ );
11740
+ return React26.createElement(Icon2, { name: alias, ...rest });
11741
+ }
11742
+ warnOnce(
11743
+ `icon-invalid-${name}`,
11744
+ `[hs-uix/safe] Icon name "${name}" is not in the catalog. Rendering a red xCircle placeholder. See NATIVE_ICON_NAMES for the valid list.`
11745
+ );
11746
+ return React26.createElement(Icon2, {
11747
+ ...rest,
11748
+ name: "xCircle",
11749
+ color: "alert",
11750
+ screenReaderText: `Invalid icon: ${name ?? "(missing)"}`
11751
+ });
11752
+ };
11753
+ SafeIcon.displayName = "SafeIcon";
11754
+
11755
+ // src/safe/SafeEmptyState.js
11756
+ import React27 from "react";
11757
+ import { EmptyState as EmptyState5 } from "@hubspot/ui-extensions";
11758
+ var SafeEmptyState = (props) => {
11759
+ const { imageName, ...rest } = props || {};
11760
+ if (imageName == null || EMPTY_STATE_IMAGES.has(imageName)) {
11761
+ return React27.createElement(EmptyState5, { imageName, ...rest });
11762
+ }
11763
+ const alias = EMPTY_STATE_IMAGE_ALIASES[imageName];
11764
+ const fallback = alias && EMPTY_STATE_IMAGES.has(alias) ? alias : "components";
11765
+ warnOnce(
11766
+ `emptystate-image-${imageName}`,
11767
+ `[hs-uix/safe] EmptyState imageName "${imageName}" is not valid \u2014 using "${fallback}". See EMPTY_STATE_IMAGES for the valid list.`
11768
+ );
11769
+ return React27.createElement(EmptyState5, { imageName: fallback, ...rest });
11770
+ };
11771
+ SafeEmptyState.displayName = "SafeEmptyState";
11772
+
11773
+ // src/safe/SafeStatisticsTrend.js
11774
+ import React28 from "react";
11775
+ import { StatisticsTrend as StatisticsTrend2 } from "@hubspot/ui-extensions";
11776
+ var SafeStatisticsTrend = (props) => {
11777
+ const { direction, ...rest } = props || {};
11778
+ if (typeof direction !== "string" || TREND_DIRECTIONS.has(direction)) {
11779
+ return React28.createElement(StatisticsTrend2, { direction, ...rest });
11780
+ }
11781
+ const alias = TREND_DIRECTION_ALIASES[direction];
11782
+ if (alias && TREND_DIRECTIONS.has(alias)) {
11783
+ warnOnce(
11784
+ `trend-alias-${direction}`,
11785
+ `[hs-uix/safe] StatisticsTrend direction "${direction}" \u2192 "${alias}". Valid values: increase, decrease.`
11786
+ );
11787
+ return React28.createElement(StatisticsTrend2, { direction: alias, ...rest });
11788
+ }
11789
+ warnOnce(
11790
+ `trend-invalid-${direction}`,
11791
+ `[hs-uix/safe] StatisticsTrend direction "${direction}" is not valid \u2014 defaulting to "increase".`
11792
+ );
11793
+ return React28.createElement(StatisticsTrend2, { ...rest, direction: "increase" });
11794
+ };
11795
+ SafeStatisticsTrend.displayName = "SafeStatisticsTrend";
11796
+
11797
+ // src/safe/SafePopover.js
11798
+ import React29 from "react";
11799
+ import { Tile as Tile7 } from "@hubspot/ui-extensions";
11800
+ import { Popover as Popover2 } from "@hubspot/ui-extensions/experimental";
11801
+ var SafePopover = (props) => {
11802
+ const { children, ...rest } = props || {};
11803
+ return React29.createElement(
11804
+ Popover2,
11805
+ rest,
11806
+ React29.createElement(Tile7, { compact: true }, children)
11807
+ );
11808
+ };
11809
+ SafePopover.displayName = "SafePopover";
11810
+
11811
+ // src/safe/safeComponents.js
11812
+ import { MultiSelect as MultiSelect7, Select as Select11, StepIndicator as StepIndicator2, ToggleGroup as ToggleGroup2 } from "@hubspot/ui-extensions";
11813
+ var wrap = (Component, name) => withSafeArrayProps(Component, name, SAFE_ARRAY_PROPS[name] ?? [], SAFE_DERIVE_PROPS[name] ?? []);
11814
+ var SafeSelect = wrap(Select11, "Select");
11815
+ var SafeMultiSelect = wrap(MultiSelect7, "MultiSelect");
11816
+ var SafeToggleGroup = wrap(ToggleGroup2, "ToggleGroup");
11817
+ var SafeStepIndicator = wrap(StepIndicator2, "StepIndicator");
11818
+ var SafeDataTable = wrap(DataTable, "DataTable");
11819
+ var SafeKanban = wrap(Kanban, "Kanban");
11820
+ var SafeFormBuilder = wrap(FormBuilder, "FormBuilder");
11821
+ var SafeAvatarStack = wrap(AvatarStack, "AvatarStack");
11822
+ var SafeKeyValueList = wrap(KeyValueList, "KeyValueList");
11823
+ var SafeFeed = wrap(Feed, "Feed");
11824
+ var SafeCalendar = wrap(Calendar, "Calendar");
11825
+ var SafeCrmDataTable = wrap(CrmDataTable, "CrmDataTable");
11826
+ var SafeCrmKanban = wrap(CrmKanban, "CrmKanban");
11827
+
11828
+ // src/utils/applyPatches.js
11829
+ function applyPatches(doc, patches) {
11830
+ if (!patches || patches.length === 0) return doc;
11831
+ let out = doc ?? {};
11832
+ for (const op of patches) {
11833
+ out = applyOne(out, op);
11834
+ }
11835
+ return out;
11836
+ }
11837
+ function applyOne(doc, op) {
11838
+ const path = parsePointer(op.path);
11839
+ switch (op.op) {
11840
+ case "add":
11841
+ return setAt(doc, path, op.value, true);
11842
+ case "replace":
11843
+ return setAt(doc, path, op.value, false);
11844
+ case "remove":
11845
+ return removeAt(doc, path);
11846
+ case "move": {
11847
+ const from = parsePointer(op.from);
11848
+ const value = getAt(doc, from);
11849
+ const removed = removeAt(doc, from);
11850
+ return setAt(removed, path, value, true);
11851
+ }
11852
+ case "copy": {
11853
+ const from = parsePointer(op.from);
11854
+ const value = getAt(doc, from);
11855
+ return setAt(doc, path, deepClone2(value), true);
11856
+ }
11857
+ default:
11858
+ console.warn("[hs-uix] applyPatches: ignoring unsupported op:", op.op);
11859
+ return doc;
11860
+ }
11861
+ }
11862
+ function parsePointer(pointer) {
11863
+ if (!pointer || pointer === "/") return [];
11864
+ if (pointer[0] !== "/") {
11865
+ throw new Error(`invalid JSON Pointer (must start with /): ${pointer}`);
11866
+ }
11867
+ return pointer.slice(1).split("/").map((seg) => seg.replace(/~1/g, "/").replace(/~0/g, "~"));
11868
+ }
11869
+ function getAt(obj, segs) {
11870
+ let cur = obj;
11871
+ for (const seg of segs) {
11872
+ if (cur == null) return void 0;
11873
+ if (Array.isArray(cur)) {
11874
+ cur = cur[Number(seg)];
11875
+ } else if (typeof cur === "object") {
11876
+ cur = Object.prototype.hasOwnProperty.call(cur, seg) ? cur[seg] : void 0;
11877
+ } else {
11878
+ return void 0;
11879
+ }
11880
+ }
11881
+ return cur;
11882
+ }
11883
+ function setAt(obj, segs, value, insert) {
11884
+ if (segs.length === 0) return value;
11885
+ const [head, ...rest] = segs;
11886
+ if (Array.isArray(obj)) {
11887
+ const next = obj.slice();
11888
+ const idx = head === "-" ? next.length : Number(head);
11889
+ if (rest.length === 0) {
11890
+ if (head === "-") {
11891
+ next.push(value);
11892
+ } else if (insert && Number.isInteger(idx) && idx >= 0 && idx <= next.length) {
11893
+ next.splice(idx, 0, value);
11894
+ } else {
11895
+ next[idx] = value;
11896
+ }
11897
+ return next;
11898
+ }
11899
+ const existing2 = idx >= 0 && idx < next.length ? next[idx] : void 0;
11900
+ next[idx] = setAt(
11901
+ existing2 ?? (looksLikeArrayIndex(rest[0]) ? [] : {}),
11902
+ rest,
11903
+ value,
11904
+ insert
11905
+ );
11906
+ return next;
11907
+ }
11908
+ const base = obj && typeof obj === "object" ? obj : {};
11909
+ if (rest.length === 0) {
11910
+ return { ...base, [head]: value };
11911
+ }
11912
+ const existing = base[head];
11913
+ const child = setAt(
11914
+ existing ?? (looksLikeArrayIndex(rest[0]) ? [] : {}),
11915
+ rest,
11916
+ value,
11917
+ insert
11918
+ );
11919
+ return { ...base, [head]: child };
11920
+ }
11921
+ function removeAt(obj, segs) {
11922
+ if (segs.length === 0) return void 0;
11923
+ const [head, ...rest] = segs;
11924
+ if (Array.isArray(obj)) {
11925
+ const idx = Number(head);
11926
+ if (!Number.isInteger(idx) || idx < 0 || idx >= obj.length) return obj;
11927
+ const next = obj.slice();
11928
+ if (rest.length === 0) {
11929
+ next.splice(idx, 1);
11930
+ } else {
11931
+ next[idx] = removeAt(next[idx], rest);
11932
+ }
11933
+ return next;
11934
+ }
11935
+ if (!obj || typeof obj !== "object") return obj;
11936
+ if (rest.length === 0) {
11937
+ const next = { ...obj };
11938
+ delete next[head];
11939
+ return next;
11940
+ }
11941
+ if (!(head in obj)) return obj;
11942
+ return { ...obj, [head]: removeAt(obj[head], rest) };
11943
+ }
11944
+ function looksLikeArrayIndex(seg) {
11945
+ return seg === "-" || /^\d+$/.test(seg);
11946
+ }
11947
+ function deepClone2(v) {
11948
+ const json = v === void 0 ? void 0 : JSON.stringify(v);
11949
+ return json === void 0 ? void 0 : JSON.parse(json);
11950
+ }
11951
+
9154
11952
  // src/utils/collections.js
9155
11953
  var sumBy = (items, keyOrFn) => (items || []).reduce((total, item) => {
9156
11954
  const value = typeof keyOrFn === "function" ? keyOrFn(item) : item == null ? void 0 : item[keyOrFn];
@@ -9238,9 +12036,19 @@ export {
9238
12036
  CrmDataTable,
9239
12037
  CrmKanban,
9240
12038
  CrmLookupSelect,
12039
+ CrmRecordPicker,
12040
+ DATE_FILTER_OPERATORS,
12041
+ DATE_RANGE_CUSTOM_VALUE,
12042
+ DATE_ROLLING_UNIT_OPTIONS,
12043
+ DEFAULT_FEED_TYPE_PRESETS,
9241
12044
  DEFAULT_SVG_FONT_WEIGHT,
9242
12045
  DataTable,
12046
+ DateRangePicker,
12047
+ EMPTY_STATE_IMAGES,
12048
+ EMPTY_STATE_IMAGE_ALIASES,
12049
+ FILTER_OPERATORS,
9243
12050
  Feed,
12051
+ FilterBuilder,
9244
12052
  FormBuilder,
9245
12053
  HS_DATE_DIRECTION_LABELS,
9246
12054
  HS_DATE_PRESETS,
@@ -9259,21 +12067,61 @@ export {
9259
12067
  HS_TEXT_COLOR,
9260
12068
  ICONS,
9261
12069
  ICON_NAMES,
12070
+ ICON_NAME_ALIASES2 as ICON_NAME_ALIASES,
9262
12071
  Icon,
9263
12072
  Kanban,
9264
12073
  KanbanCardActions,
9265
12074
  KeyValueList,
12075
+ NATIVE_ICON_NAMES,
9266
12076
  NATIVE_ICON_NAME_LIST,
12077
+ SAFE_ARRAY_PROPS,
12078
+ SAFE_DERIVE_PROPS,
12079
+ SKELETON_FILL,
9267
12080
  SPINNERS,
9268
12081
  SPINNER_NAMES,
12082
+ SafeAvatarStack,
12083
+ SafeCalendar,
12084
+ SafeCrmDataTable,
12085
+ SafeCrmKanban,
12086
+ SafeDataTable,
12087
+ SafeEmptyState,
12088
+ SafeFeed,
12089
+ SafeFormBuilder,
12090
+ SafeIcon,
12091
+ SafeKanban,
12092
+ SafeKeyValueList,
12093
+ SafeMultiSelect,
12094
+ SafePopover,
12095
+ SafeSelect,
12096
+ SafeStatisticsTrend,
12097
+ SafeStepIndicator,
12098
+ SafeToggleGroup,
9269
12099
  SectionHeader,
9270
12100
  Spinner,
9271
12101
  StyledText,
12102
+ TREND_DIRECTIONS,
12103
+ TREND_DIRECTION_ALIASES,
12104
+ UNASSIGNED_LANE_KEY,
12105
+ addFilter,
12106
+ applyPatches,
12107
+ applyTypePreset,
9272
12108
  buildCrmSearchConfig,
9273
12109
  buildOptions,
12110
+ changeConditionOperator,
12111
+ changeConditionProperty,
12112
+ compareHsDateValues,
12113
+ computeStageCounts,
12114
+ conditionToCrmFilter,
12115
+ countConditions,
12116
+ createCondition,
12117
+ createGroup,
9274
12118
  createStatusTagSortComparator,
9275
12119
  crmSearchResultToOption,
12120
+ evaluateWip,
12121
+ fieldsFromHubSpotProperties,
12122
+ findNewlyExceededWip,
9276
12123
  findOptionLabel2 as findOptionLabel,
12124
+ flushBuffer,
9277
12125
  formatCurrency,
9278
12126
  formatCurrencyCompact,
9279
12127
  formatDate,
@@ -9281,7 +12129,14 @@ export {
9281
12129
  formatPercentage,
9282
12130
  getAutoStatusTagVariant,
9283
12131
  getAutoTagVariant,
12132
+ getLaneKey,
12133
+ getNodeAtPath,
12134
+ getOperatorOptions,
9284
12135
  gridToBraille,
12136
+ isConditionNode,
12137
+ isGroupNode,
12138
+ isValidDateRange,
12139
+ lookupTypePreset,
9285
12140
  makeAvatarStackDataUri,
9286
12141
  makeCrmSearchMultiSelectField,
9287
12142
  makeCrmSearchSelectField,
@@ -9290,10 +12145,28 @@ export {
9290
12145
  makeStyledTextDataUri,
9291
12146
  normalizeCrmSearchRecord,
9292
12147
  normalizeCrmSearchRows,
12148
+ operatorExpectsHighValue,
12149
+ operatorExpectsValue,
12150
+ operatorExpectsValues,
12151
+ orderLaneKeys,
12152
+ partitionLanes,
12153
+ partitionNewItems,
12154
+ presetToRange,
12155
+ removeFilter,
12156
+ resetSafeWarnings,
9293
12157
  resolveCrmObjectType,
12158
+ resolveLaneLabel,
12159
+ resolveWipLimit,
9294
12160
  sumBy,
9295
12161
  svgToIconEntry,
12162
+ toCrmSearchFilterGroups,
12163
+ toHsDateValue,
12164
+ toTimestampMs,
12165
+ updateFilter,
9296
12166
  useCrmSearchDataSource,
9297
12167
  useCrmSearchOptions,
9298
- useFormPrefill
12168
+ useFormPrefill,
12169
+ validateTree,
12170
+ warnOnce,
12171
+ withSafeArrayProps
9299
12172
  };