hs-uix 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +3 -1
  2. package/common-components.d.ts +319 -68
  3. package/dist/calendar.js +397 -119
  4. package/dist/calendar.mjs +399 -119
  5. package/dist/common-components.js +3546 -88
  6. package/dist/common-components.mjs +3530 -84
  7. package/dist/datatable.js +108 -18
  8. package/dist/datatable.mjs +108 -18
  9. package/dist/experimental.js +2876 -0
  10. package/dist/experimental.mjs +2883 -0
  11. package/dist/feed.js +267 -38
  12. package/dist/feed.mjs +260 -37
  13. package/dist/filter.js +1379 -0
  14. package/dist/filter.mjs +1334 -0
  15. package/dist/form.js +222 -26
  16. package/dist/form.mjs +227 -27
  17. package/dist/index.js +3255 -353
  18. package/dist/index.mjs +3199 -344
  19. package/dist/kanban.js +282 -62
  20. package/dist/kanban.mjs +273 -61
  21. package/dist/safe.js +9207 -0
  22. package/dist/safe.mjs +9298 -0
  23. package/dist/utils.js +491 -75
  24. package/dist/utils.mjs +491 -75
  25. package/experimental.d.ts +1 -0
  26. package/filter.d.ts +1 -0
  27. package/index.d.ts +45 -3
  28. package/package.json +19 -1
  29. package/safe.d.ts +1 -0
  30. package/src/calendar/README.md +76 -5
  31. package/src/calendar/index.d.ts +108 -1
  32. package/src/common-components/README.md +140 -1
  33. package/src/datatable/README.md +0 -2
  34. package/src/experimental/README.md +126 -0
  35. package/src/experimental/index.d.ts +346 -0
  36. package/src/feed/README.md +69 -0
  37. package/src/feed/index.d.ts +103 -0
  38. package/src/filter/README.md +148 -0
  39. package/src/filter/index.d.ts +221 -0
  40. package/src/form/README.md +132 -4
  41. package/src/form/index.d.ts +82 -1
  42. package/src/kanban/README.md +119 -6
  43. package/src/kanban/index.d.ts +153 -2
  44. package/src/safe/README.md +108 -0
  45. package/src/safe/index.d.ts +158 -0
  46. package/src/utils/README.md +39 -0
  47. package/src/wizard/README.md +158 -0
  48. package/src/wizard/index.d.ts +138 -0
  49. package/utils.d.ts +17 -0
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,13 +7472,16 @@ 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";
6779
7479
  import {
6780
7480
  Alert as Alert4,
7481
+ AutoGrid as AutoGrid3,
6781
7482
  Box as Box6,
6782
7483
  Button as Button8,
7484
+ DateInput as DateInput4,
6783
7485
  Divider as Divider4,
6784
7486
  EmptyState as EmptyState4,
6785
7487
  Flex as Flex9,
@@ -6787,9 +7489,9 @@ import {
6787
7489
  Image as Image5,
6788
7490
  Inline as Inline4,
6789
7491
  Link as Link6,
6790
- LoadingSpinner as LoadingSpinner2,
6791
- Modal,
6792
- ModalBody,
7492
+ LoadingSpinner as LoadingSpinner3,
7493
+ Modal as Modal2,
7494
+ ModalBody as ModalBody2,
6793
7495
  Panel,
6794
7496
  PanelBody,
6795
7497
  Select as Select6,
@@ -6799,7 +7501,7 @@ import {
6799
7501
  TableHead as TableHead2,
6800
7502
  TableHeader as TableHeader2,
6801
7503
  TableRow as TableRow2,
6802
- StatusTag as StatusTag2,
7504
+ StatusTag as StatusTag3,
6803
7505
  Tag as Tag5,
6804
7506
  Text as Text7,
6805
7507
  Tile as Tile5
@@ -6808,7 +7510,7 @@ import { Popover } from "@hubspot/ui-extensions/experimental";
6808
7510
 
6809
7511
  // src/calendar/dateUtils.js
6810
7512
  var MS_PER_DAY = 864e5;
6811
- 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";
6812
7514
  var fromEpoch = (ms) => {
6813
7515
  const d = new Date(ms);
6814
7516
  if (Number.isNaN(d.getTime())) return null;
@@ -6818,7 +7520,7 @@ var fromEpoch = (ms) => {
6818
7520
  var toDate2 = (value) => {
6819
7521
  if (value == null || value === "") return null;
6820
7522
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
6821
- if (isDateValueObject2(value)) {
7523
+ if (isDateValueObject3(value)) {
6822
7524
  return new Date(
6823
7525
  value.year,
6824
7526
  value.month,
@@ -6913,7 +7615,7 @@ var buildHours = (startHour = 8, endHour = 20) => {
6913
7615
  let e = Math.max(0, Math.min(23, Math.round(endHour)));
6914
7616
  if (s > e) [s, e] = [e, s];
6915
7617
  const hours = [];
6916
- for (let h6 = s; h6 <= e; h6 += 1) hours.push(h6);
7618
+ for (let h7 = s; h7 <= e; h7 += 1) hours.push(h7);
6917
7619
  return hours;
6918
7620
  };
6919
7621
  var WEEKDAY_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
@@ -7009,21 +7711,6 @@ var formatTimeZoneLabel = (tz, atDate = /* @__PURE__ */ new Date()) => {
7009
7711
 
7010
7712
  // src/calendar/svgChips.js
7011
7713
  var toDataUri = (svg) => `data:image/svg+xml,${encodeURIComponent(svg)}`;
7012
- var escapeXml = (s) => String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
7013
- var truncateLabel = (value, max) => {
7014
- const s = String(value == null ? "" : value);
7015
- if (!max || s.length <= max) return s;
7016
- return s.slice(0, Math.max(1, max - 1)).trimEnd() + "\u2026";
7017
- };
7018
- var CHIP_PALETTE = {
7019
- default: { fill: "#7FD1DE", text: HS_TEXT_COLOR },
7020
- info: { fill: "#00A4BD", text: "#FFFFFF" },
7021
- success: { fill: "#00BDA5", text: "#FFFFFF" },
7022
- warning: { fill: "#F5C26B", text: HS_TEXT_COLOR },
7023
- error: { fill: "#F2545B", text: "#FFFFFF" },
7024
- danger: { fill: "#F2545B", text: "#FFFFFF" }
7025
- // StatusTag spells red "danger"; accept both
7026
- };
7027
7714
  var DOT_FILL = {
7028
7715
  default: "#7C98B6",
7029
7716
  info: "#00A4BD",
@@ -7038,31 +7725,177 @@ var makeDotDataUri = (variant = "default", size = 8) => {
7038
7725
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}"><circle cx="${r}" cy="${r}" r="${r}" fill="${fill}" /></svg>`;
7039
7726
  return { src: toDataUri(svg), width: size, height: size };
7040
7727
  };
7041
- var makeEventChipDataUri = (opts) => {
7042
- const { label, width, height = 24, variant = "default" } = opts;
7043
- const palette = CHIP_PALETTE[variant] || CHIP_PALETTE.default;
7044
- const accentX = 5;
7045
- const accentW = 3;
7046
- const textX = accentX + accentW + 6;
7047
- const rightPad = 8;
7048
- const maxChars = Math.max(1, Math.floor((width - textX - rightPad) / 6));
7049
- const text = truncateLabel(label, maxChars);
7050
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect x="0.5" y="0.5" width="${width - 1}" height="${height - 1}" rx="4" ry="4" fill="#FFFFFF" stroke="#CBD6E2" stroke-width="1" /><rect x="${accentX}" y="5" width="${accentW}" height="${height - 10}" rx="1.5" ry="1.5" fill="${palette.fill}" /><text x="${textX}" y="${height / 2}" font-family='${HS_FONT_FAMILY}' font-size="12" font-weight="500" fill="${HS_TEXT_COLOR}" text-anchor="start" dominant-baseline="central">${escapeXml(text)}</text></svg>`;
7051
- return { src: toDataUri(svg), width, height };
7052
- };
7053
- var makeMoreDataUri = (opts) => {
7054
- const { label, width, height = 24 } = opts;
7055
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><text x="3" y="${height / 2}" font-family='${HS_FONT_FAMILY}' font-size="13" font-weight="700" fill="#0091AE" text-anchor="start" dominant-baseline="central">${escapeXml(label)}</text></svg>`;
7056
- return { src: toDataUri(svg), width, height };
7057
- };
7058
7728
  var makeSpacerDataUri = (height = 24, width = 4) => {
7059
7729
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect x="0" y="0" width="${width}" height="${height}" fill="#FFFFFF" fill-opacity="0" /></svg>`;
7060
7730
  return { src: toDataUri(svg), width, height };
7061
7731
  };
7062
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
+
7063
7895
  // src/calendar/Calendar.jsx
7064
7896
  var DEFAULT_MAX_EVENTS_PER_DAY = 3;
7065
7897
  var ALL_VIEWS = ["month", "week", "day", "agenda"];
7898
+ var ALL_VIEWS_WITH_RESOURCE = ["month", "week", "day", "resource", "agenda"];
7066
7899
  var DEFAULT_DAY_START_HOUR = 8;
7067
7900
  var DEFAULT_DAY_END_HOUR = 20;
7068
7901
  var DEFAULT_TIME_ZONES = [
@@ -7098,7 +7931,8 @@ var VIEW_LABELS = {
7098
7931
  month: "Month",
7099
7932
  week: "Week",
7100
7933
  day: "Day",
7101
- agenda: "Agenda"
7934
+ agenda: "Agenda",
7935
+ resource: "Resource"
7102
7936
  };
7103
7937
  var DEFAULT_LABELS5 = {
7104
7938
  today: "Today",
@@ -7115,7 +7949,11 @@ var DEFAULT_LABELS5 = {
7115
7949
  errorMessage: "An error occurred while loading events.",
7116
7950
  dayDetailTitle: (label) => label,
7117
7951
  open: "Open",
7118
- allDay: "All day"
7952
+ allDay: "All day",
7953
+ reschedule: "Reschedule",
7954
+ pickDate: "Pick date",
7955
+ unassigned: "Unassigned",
7956
+ resource: "Resource"
7119
7957
  };
7120
7958
  var DEFAULT_EVENT_FIELDS = {
7121
7959
  id: "id",
@@ -7150,11 +7988,30 @@ var STATUS_VARIANT = {
7150
7988
  error: "danger",
7151
7989
  danger: "danger"
7152
7990
  };
7991
+ var TAG_VARIANT = {
7992
+ default: "default",
7993
+ info: "info",
7994
+ success: "success",
7995
+ warning: "warning",
7996
+ error: "error",
7997
+ danger: "error"
7998
+ };
7999
+ var MONTH_EVENT_STYLES = /* @__PURE__ */ new Set(["statusTag", "tag"]);
8000
+ var monthLabelMaxChars = (style) => {
8001
+ const overhead = style === "tag" ? 34 : 46;
8002
+ return Math.max(6, Math.floor((MONTH_COL_WIDTH - overhead) / 6.6));
8003
+ };
8004
+ var truncateMonthLabel = (value, max) => {
8005
+ const s = String(value == null ? "" : value);
8006
+ if (!max || s.length <= max) return s;
8007
+ return s.slice(0, max).trimEnd();
8008
+ };
7153
8009
  var MONTH_COL_WIDTH = 160;
7154
8010
  var TIMEGRID_DAY_COL = 150;
7155
8011
  var TIMEGRID_DAY_COL_SINGLE = 560;
8012
+ var RESOURCE_LABEL_COL_WIDTH = "min";
7156
8013
  var HOUR_SLOT_HEIGHT = 64;
7157
- var EventDetail = ({ event, labels }) => {
8014
+ var EventDetail = ({ event, labels, reschedule, idSuffix = "" }) => {
7158
8015
  const { start, end, title, subtitle, href } = event;
7159
8016
  let when = "";
7160
8017
  if (start) {
@@ -7168,58 +8025,62 @@ var EventDetail = ({ event, labels }) => {
7168
8025
  when = `${formatDayTitle(start)} \u2013 ${formatDayTitle(end)}`;
7169
8026
  }
7170
8027
  }
7171
- return /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, /* @__PURE__ */ React14.createElement(Text7, { format: { fontWeight: "demibold" } }, title || "--"), when ? /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy" }, when) : null, subtitle ? /* @__PURE__ */ React14.createElement(Text7, null, subtitle) : null, href ? /* @__PURE__ */ React14.createElement(Link6, { href: href.url, external: href.external }, labels.open) : null);
7172
- };
7173
- var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "") => {
7174
- if (mode === "none") return void 0;
7175
- const body = renderEventDetail ? renderEventDetail(event) : /* @__PURE__ */ React14.createElement(EventDetail, { event, labels });
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);
8045
+ };
8046
+ var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "", reschedule = null) => {
8047
+ if (mode === "none") return void 0;
8048
+ const body = renderEventDetail ? renderEventDetail(event) : /* @__PURE__ */ React14.createElement(EventDetail, { event, labels, reschedule, idSuffix });
7176
8049
  const id = `cal-evt-${event.key}${idSuffix}`;
7177
8050
  if (mode === "modal") {
7178
- 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));
7179
8052
  }
7180
8053
  if (mode === "panel") {
7181
8054
  return /* @__PURE__ */ React14.createElement(Panel, { id, title: event.title || labels.open, width: "small", variant: "modal" }, /* @__PURE__ */ React14.createElement(PanelBody, null, body));
7182
8055
  }
7183
- return /* @__PURE__ */ React14.createElement(Popover, { id, placement: "bottom" }, /* @__PURE__ */ React14.createElement(Tile5, { compact: true }, body));
8056
+ return /* @__PURE__ */ React14.createElement(Popover, { id, placement: "bottom", variant: "longform" }, /* @__PURE__ */ React14.createElement(Tile5, { compact: true }, body));
7184
8057
  };
7185
- var AgendaEventRow = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8058
+ var AgendaEventRow = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, reschedule }) => {
7186
8059
  const variant = VALID_VARIANTS.has(event.color) ? event.color : "default";
7187
- const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-ag${day.getTime()}` : "");
8060
+ const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-ag${day.getTime()}` : "", reschedule);
7188
8061
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7189
8062
  const timeLabel = isAllDayEvent(event) ? labels.allDay : formatTime(event.start);
7190
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);
7191
8064
  };
7192
- var MONTH_CHIP_WIDTH = MONTH_COL_WIDTH - 8;
7193
- var MONTH_CHIP_HEIGHT = 24;
7194
- var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8065
+ var MONTH_SLOT_HEIGHT = 24;
8066
+ var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, monthEventStyle, monthEventMaxChars, reschedule, idScope = "" }) => {
7195
8067
  const isStartDay = !day || !event.start || isSameDay(event.start, day);
7196
- 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);
7197
8069
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7198
8070
  const variant = VALID_VARIANTS.has(event.color) ? event.color : "default";
7199
8071
  const startHasTime = event.start && (event.start.getHours() !== 0 || event.start.getMinutes() !== 0);
7200
8072
  const time = isStartDay && startHasTime ? `${formatTime(event.start)} ` : "";
7201
8073
  const prefix = isStartDay ? "" : "\u2192 ";
7202
- const chip = makeEventChipDataUri({
7203
- label: `${prefix}${time}${event.title || "--"}`,
7204
- width: MONTH_CHIP_WIDTH,
7205
- height: MONTH_CHIP_HEIGHT,
7206
- variant
7207
- });
7208
- return /* @__PURE__ */ React14.createElement(
7209
- Image5,
7210
- {
7211
- src: chip.src,
7212
- width: chip.width,
7213
- height: chip.height,
7214
- alt: event.title || "",
7215
- overlay,
7216
- onClick: handleClick
7217
- }
7218
- );
8074
+ const maxChars = monthEventMaxChars != null ? monthEventMaxChars : monthLabelMaxChars(monthEventStyle);
8075
+ const label = truncateMonthLabel(`${prefix}${time}${event.title || "--"}`, maxChars);
8076
+ if (monthEventStyle === "tag") {
8077
+ return /* @__PURE__ */ React14.createElement(Tag5, { variant: TAG_VARIANT[variant] || "default", overlay, onClick: handleClick }, label);
8078
+ }
8079
+ return /* @__PURE__ */ React14.createElement(Link6, { overlay, onClick: handleClick }, /* @__PURE__ */ React14.createElement(StatusTag3, { variant: STATUS_VARIANT[variant] || "default" }, label));
7219
8080
  };
7220
- var DayListItem = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8081
+ var DayListItem = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, reschedule, idScope = "" }) => {
7221
8082
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7222
- 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);
7223
8084
  const href = event.href;
7224
8085
  return /* @__PURE__ */ React14.createElement(Button8, { variant: "transparent", size: "sm", href: href ? href.url : void 0, overlay, onClick: handleClick }, event.title || "--");
7225
8086
  };
@@ -7284,6 +8145,40 @@ var Toolbar = ({
7284
8145
  }
7285
8146
  ));
7286
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
+ };
7287
8182
  var MonthView = ({
7288
8183
  refDate,
7289
8184
  now,
@@ -7298,51 +8193,23 @@ var MonthView = ({
7298
8193
  }) => {
7299
8194
  const headers = weekdayLabels(weekStartsOn, hideWeekends, true);
7300
8195
  const today = now || /* @__PURE__ */ new Date();
7301
- const spacer24 = makeSpacerDataUri(MONTH_CHIP_HEIGHT, MONTH_COL_WIDTH);
7302
8196
  const renderCell = (day) => {
7303
8197
  const dayEvents = eventsForDay(day);
7304
8198
  const inMonth = isSameMonth(day, refDate);
7305
8199
  const isToday = isSameDay(day, today);
7306
8200
  if (renderDayCell) return renderDayCell(day, dayEvents);
7307
- const shown = dayEvents.slice(0, maxEventsPerDay);
7308
- const hasOverflow = dayEvents.length > maxEventsPerDay;
7309
- const slots = [];
7310
- for (let i = 0; i < maxEventsPerDay; i++) {
7311
- if (i < shown.length) {
7312
- slots.push(/* @__PURE__ */ React14.createElement(MonthChip, { key: shown[i].key, event: shown[i], day, ...chipProps }));
7313
- } else {
7314
- slots.push(
7315
- /* @__PURE__ */ React14.createElement(Image5, { key: `sp-${i}`, src: spacer24.src, width: spacer24.width, height: spacer24.height, alt: "" })
7316
- );
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
7317
8209
  }
7318
- }
7319
- if (hasOverflow) {
7320
- const more = makeMoreDataUri({
7321
- label: labels.more(dayEvents.length - maxEventsPerDay),
7322
- width: MONTH_COL_WIDTH,
7323
- height: MONTH_CHIP_HEIGHT
7324
- });
7325
- slots.push(
7326
- /* @__PURE__ */ React14.createElement(
7327
- Image5,
7328
- {
7329
- key: "more",
7330
- src: more.src,
7331
- width: more.width,
7332
- height: more.height,
7333
- alt: labels.more(dayEvents.length - maxEventsPerDay),
7334
- 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", align: "center", gap: "sm" }, /* @__PURE__ */ React14.createElement(Heading, null, 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)))))
7335
- }
7336
- )
7337
- );
7338
- } else {
7339
- slots.push(
7340
- /* @__PURE__ */ React14.createElement(Image5, { key: "more-sp", src: spacer24.src, width: spacer24.width, height: spacer24.height, alt: "" })
7341
- );
7342
- }
7343
- return /* @__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
+ )));
7344
8211
  };
7345
- 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) => {
7346
8213
  const days = hideWeekends ? week.filter((d) => d.getDay() !== 0 && d.getDay() !== 6) : week;
7347
8214
  return /* @__PURE__ */ React14.createElement(TableRow2, { key: wi }, days.map((day) => /* @__PURE__ */ React14.createElement(TableCell2, { key: day.getTime(), width: "min" }, renderCell(day))));
7348
8215
  })));
@@ -7362,13 +8229,40 @@ var AgendaView = ({ rangeStart, rangeEnd, eventsForDay, chipProps, labels, rende
7362
8229
  }
7363
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 })))));
7364
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
+ };
7365
8259
  var formatTimedDuration = (start, end) => {
7366
8260
  const mins = Math.max(0, Math.round(((end || start).getTime() - start.getTime()) / 6e4));
7367
- const h6 = Math.floor(mins / 60);
8261
+ const h7 = Math.floor(mins / 60);
7368
8262
  const m = mins % 60;
7369
- if (h6 === 0) return `${m} min`;
7370
- if (m === 0) return `${h6} hr`;
7371
- 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`;
7372
8266
  };
7373
8267
  var hourSpan = (event) => {
7374
8268
  const start = event.start;
@@ -7383,6 +8277,7 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7383
8277
  const today = now || /* @__PURE__ */ new Date();
7384
8278
  const centerDays = days.length === 1;
7385
8279
  const dayColWidth = days.length === 1 ? TIMEGRID_DAY_COL_SINGLE : TIMEGRID_DAY_COL;
8280
+ const weekTitleMaxChars = Math.max(6, Math.floor((TIMEGRID_DAY_COL - 46) / 6.6));
7386
8281
  const todayInView = days.some((d) => isSameDay(d, today));
7387
8282
  const nowHour = today.getHours();
7388
8283
  const dayData = days.map((day) => {
@@ -7408,7 +8303,8 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7408
8303
  chipProps.overlayMode,
7409
8304
  chipProps.renderEventDetail,
7410
8305
  chipProps.labels,
7411
- `-tg${mode}${hour}-${dayMs}`
8306
+ `-tg${mode}${hour}-${dayMs}`,
8307
+ chipProps.reschedule
7412
8308
  );
7413
8309
  const handleClick = chipProps.onEventClick ? () => chipProps.onEventClick(e.raw, e) : void 0;
7414
8310
  const variant = VALID_VARIANTS.has(e.color) ? e.color : "default";
@@ -7427,10 +8323,10 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7427
8323
  } else if (mode === "cont") {
7428
8324
  sub = `\u2191 cont. through ${endLabel}`;
7429
8325
  }
7430
- 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" }, e.title || "--")), sub ? /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy" }, sub) : null);
8326
+ const titleLabel = centerDays ? e.title || "--" : truncateMonthLabel(e.title || "--", weekTitleMaxChars);
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);
7431
8328
  };
7432
- const daySpacer = makeSpacerDataUri(1, dayColWidth);
7433
- const dayCell = (key, content) => /* @__PURE__ */ React14.createElement(TableCell2, { key, width: centerDays ? "max" : "min", align: "left" }, /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "xs" }, content, centerDays ? null : /* @__PURE__ */ React14.createElement(Image5, { src: daySpacer.src, width: daySpacer.width, height: daySpacer.height, alt: "" })));
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)));
7434
8330
  const slotSpacer = makeSpacerDataUri(HOUR_SLOT_HEIGHT, 1);
7435
8331
  const emptyCell = null;
7436
8332
  return /* @__PURE__ */ React14.createElement(Table2, { bordered: true, flush: true, density: "compact" }, /* @__PURE__ */ React14.createElement(TableHead2, null, /* @__PURE__ */ React14.createElement(TableRow2, null, /* @__PURE__ */ React14.createElement(TableHeader2, { width: "min" }, "TIME"), dayData.map(({ day }) => {
@@ -7447,7 +8343,7 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7447
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 ? (
7448
8344
  // Trailing nbsp pads the chip's right edge so the final letter
7449
8345
  // ("M" in "11 AM") isn't clipped by the min-width TIME column.
7450
- /* @__PURE__ */ React14.createElement(StatusTag2, { variant: "info" }, `${formatHourLabel(hour)}\xA0`)
8346
+ /* @__PURE__ */ React14.createElement(StatusTag3, { variant: "info" }, `${formatHourLabel(hour)}\xA0`)
7451
8347
  ) : /* @__PURE__ */ React14.createElement(Text7, { variant: "microcopy" }, formatHourLabel(hour)))), dayData.map(({ day, timed }) => {
7452
8348
  const occupying = timed.map((t) => {
7453
8349
  let visStart = Math.max(t.sHour, dayStartHour);
@@ -7486,9 +8382,21 @@ var Calendar = (props) => {
7486
8382
  weekStartsOn = 0,
7487
8383
  hideWeekends = false,
7488
8384
  maxEventsPerDay = DEFAULT_MAX_EVENTS_PER_DAY,
8385
+ // month-grid event token style: "statusTag" (dot + text, default) | "tag" (pill)
8386
+ monthEventStyle = "statusTag",
8387
+ // max characters for a month-cell label before "…" (default derived per style)
8388
+ monthEventMaxChars,
7489
8389
  // time grid (week / day)
7490
8390
  dayStartHour = DEFAULT_DAY_START_HOUR,
7491
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,
7492
8400
  // timezone
7493
8401
  timeZone: controlledTimeZone,
7494
8402
  defaultTimeZone,
@@ -7527,10 +8435,12 @@ var Calendar = (props) => {
7527
8435
  const fields = useMemo5(() => ({ ...DEFAULT_EVENT_FIELDS, ...eventFields || {} }), [eventFields]);
7528
8436
  const [internalView, setInternalView] = useState6(defaultView);
7529
8437
  const view = controlledView != null ? controlledView : internalView;
8438
+ const resourceEnabled = resources && resources.length > 0 || resourceField != null;
7530
8439
  const enabledViews = useMemo5(() => {
7531
- const base = viewsProp && viewsProp.length > 0 ? viewsProp : ALL_VIEWS;
7532
- return base.filter((v) => ALL_VIEWS.includes(v));
7533
- }, [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]);
7534
8444
  const setView = useCallback5(
7535
8445
  (next) => {
7536
8446
  if (controlledView == null) setInternalView(next);
@@ -7555,7 +8465,9 @@ var Calendar = (props) => {
7555
8465
  const focusedDate = (controlledFocusedDate != null ? toDate2(controlledFocusedDate) : internalDate) || startOfDay(nowWall);
7556
8466
  const stepFor = useCallback5(
7557
8467
  (dir) => {
7558
- 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
+ }
7559
8471
  if (view === "day") return addDays(focusedDate, dir);
7560
8472
  return addMonths(focusedDate, dir);
7561
8473
  },
@@ -7582,7 +8494,7 @@ var Calendar = (props) => {
7582
8494
  rangeEnd: endOfDay(flat[flat.length - 1])
7583
8495
  };
7584
8496
  }
7585
- if (view === "week") {
8497
+ if (view === "week" || view === "resource") {
7586
8498
  const days = buildWeekDays(focusedDate, weekStartsOn, hideWeekends);
7587
8499
  return {
7588
8500
  weeks: null,
@@ -7655,14 +8567,18 @@ var Calendar = (props) => {
7655
8567
  );
7656
8568
  const normalized = useMemo5(
7657
8569
  () => (events || []).map((raw, index) => {
7658
- const start = toWallClock(toDate2(resolveField(raw, fields.start)), timeZone);
7659
- 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);
7660
8574
  const id = resolveField(raw, fields.id);
7661
8575
  return {
7662
8576
  key: id != null ? String(id) : `evt-${index}`,
7663
8577
  id,
7664
8578
  start,
7665
8579
  end: endRaw || start,
8580
+ sourceStart,
8581
+ sourceEnd: sourceEnd || sourceStart,
7666
8582
  title: resolveField(raw, fields.title),
7667
8583
  subtitle: resolveField(raw, fields.subtitle),
7668
8584
  color: resolveField(raw, fields.color),
@@ -7699,13 +8615,60 @@ var Calendar = (props) => {
7699
8615
  }, [rangeKey]);
7700
8616
  const title = useMemo5(() => {
7701
8617
  if (view === "day") return formatDayTitle(focusedDate);
7702
- if (view === "week" || view === "agenda") {
7703
- 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);
7704
8620
  return formatRangeTitle(days[0], days[days.length - 1]);
7705
8621
  }
7706
8622
  return formatMonthTitle(focusedDate);
7707
8623
  }, [view, focusedDate, weekStartsOn, hideWeekends]);
7708
- const chipProps = { overlayMode, renderEventDetail, onEventClick, labels };
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]);
8652
+ const safeMonthEventStyle = MONTH_EVENT_STYLES.has(monthEventStyle) ? monthEventStyle : "statusTag";
8653
+ const chipProps = {
8654
+ overlayMode,
8655
+ renderEventDetail,
8656
+ onEventClick,
8657
+ labels,
8658
+ monthEventStyle: safeMonthEventStyle,
8659
+ monthEventMaxChars,
8660
+ reschedule
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]);
7709
8672
  const timeZoneOptions = useMemo5(() => {
7710
8673
  if (!showTimeZoneSelect) return null;
7711
8674
  const base = (timeZoneOptionsProp && timeZoneOptionsProp.length ? timeZoneOptionsProp : DEFAULT_TIME_ZONES).map(
@@ -7747,7 +8710,7 @@ var Calendar = (props) => {
7747
8710
  );
7748
8711
  let body;
7749
8712
  if (loading) {
7750
- 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 }));
7751
8714
  } else if (error) {
7752
8715
  body = renderErrorState ? renderErrorState({ error }) : /* @__PURE__ */ React14.createElement(Alert4, { title: labels.errorTitle, variant: "error" }, typeof error === "string" ? error : labels.errorMessage);
7753
8716
  } else if (view === "month") {
@@ -7766,6 +8729,19 @@ var Calendar = (props) => {
7766
8729
  labels
7767
8730
  }
7768
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
+ );
7769
8745
  } else if (view === "week" || view === "day") {
7770
8746
  body = /* @__PURE__ */ React14.createElement(
7771
8747
  TimeGridView,
@@ -7795,9 +8771,696 @@ var Calendar = (props) => {
7795
8771
  }
7796
8772
  return /* @__PURE__ */ React14.createElement(Flex9, { direction: "column", gap: "sm" }, toolbar, body);
7797
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
+ };
7798
9461
 
7799
9462
  // src/common-components/AutoTag.js
7800
- import React15 from "react";
9463
+ import React16 from "react";
7801
9464
  import { Tag as Tag6 } from "@hubspot/ui-extensions";
7802
9465
 
7803
9466
  // src/utils/tagVariants.js
@@ -7996,7 +9659,7 @@ var AutoTag = ({
7996
9659
  overrides,
7997
9660
  fallback
7998
9661
  });
7999
- return React15.createElement(
9662
+ return React16.createElement(
8000
9663
  Tag6,
8001
9664
  { variant: resolvedVariant, ...props },
8002
9665
  displayValue
@@ -8004,8 +9667,8 @@ var AutoTag = ({
8004
9667
  };
8005
9668
 
8006
9669
  // src/common-components/AutoStatusTag.js
8007
- import React16 from "react";
8008
- import { StatusTag as StatusTag3 } from "@hubspot/ui-extensions";
9670
+ import React17 from "react";
9671
+ import { StatusTag as StatusTag4 } from "@hubspot/ui-extensions";
8009
9672
  var AutoStatusTag = ({
8010
9673
  value,
8011
9674
  status,
@@ -8021,20 +9684,20 @@ var AutoStatusTag = ({
8021
9684
  overrides,
8022
9685
  fallback
8023
9686
  });
8024
- return React16.createElement(
8025
- StatusTag3,
9687
+ return React17.createElement(
9688
+ StatusTag4,
8026
9689
  { variant: resolvedVariant, ...props },
8027
9690
  displayValue
8028
9691
  );
8029
9692
  };
8030
9693
 
8031
9694
  // src/common-components/CrmLookupSelect.js
8032
- import React18, { useMemo as useMemo7, useState as useState8 } from "react";
8033
- 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";
8034
9697
 
8035
9698
  // src/utils/crmSearchAdapters.js
8036
- import React17, { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef5, useState as useState7 } from "react";
8037
- 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";
8038
9701
 
8039
9702
  // src/utils/objectPath.js
8040
9703
  var getByPath = (obj, path) => {
@@ -8151,9 +9814,9 @@ var useCrmSearchDataSource = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) =>
8151
9814
  error,
8152
9815
  mapResponse
8153
9816
  } = options;
8154
- const config = useMemo6(() => buildCrmSearchConfig(params, options), [params, options]);
9817
+ const config = useMemo7(() => buildCrmSearchConfig(params, options), [params, options]);
8155
9818
  const response = useCrmSearch(config, format);
8156
- return useMemo6(() => {
9819
+ return useMemo7(() => {
8157
9820
  var _a;
8158
9821
  const rows = mapResponse ? mapResponse(response) : normalizeCrmSearchRows(response, { idField: rowIdField, ...row || EMPTY_OBJECT });
8159
9822
  const resolvedTotal = typeof totalCount === "function" ? totalCount(response) : totalCount ?? pickTotal(response, rows.length);
@@ -8191,7 +9854,7 @@ var crmSearchResultToOption = (row, options = EMPTY_OBJECT) => {
8191
9854
  var useCrmSearchOptions = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
8192
9855
  const dataSource = useCrmSearchDataSource(params, options);
8193
9856
  const optionConfig = options.option || options;
8194
- return useMemo6(() => ({
9857
+ return useMemo7(() => ({
8195
9858
  ...dataSource,
8196
9859
  options: dataSource.rows.map((row) => crmSearchResultToOption(row, optionConfig))
8197
9860
  }), [dataSource, optionConfig]);
@@ -8320,29 +9983,29 @@ var CrmDataTable = ({
8320
9983
  ...props
8321
9984
  }) => {
8322
9985
  var _a, _b;
8323
- const [params, setParams] = useState7({ search: "", filters: {}, sort: null });
8324
- const resolvedProperties = useMemo6(() => properties, [properties]);
8325
- const resolvedColumns = useMemo6(
9986
+ const [params, setParams] = useState8({ search: "", filters: {}, sort: null });
9987
+ const resolvedProperties = useMemo7(() => properties, [properties]);
9988
+ const resolvedColumns = useMemo7(
8326
9989
  () => columns || inferCrmColumns(resolvedProperties),
8327
9990
  [columns, resolvedProperties]
8328
9991
  );
8329
9992
  const resolvedSearchFields = searchFields || resolvedProperties;
8330
- const autoFilterFields = useMemo6(
9993
+ const autoFilterFields = useMemo7(
8331
9994
  () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
8332
9995
  [autoFilters, resolvedProperties]
8333
9996
  );
8334
9997
  const autoFilterLabelsRef = useRef5({});
8335
- const defaultPropertyMap = useMemo6(
9998
+ const defaultPropertyMap = useMemo7(
8336
9999
  () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
8337
10000
  [resolvedProperties]
8338
10001
  );
8339
10002
  const effectivePropertyMap = propertyMap || defaultPropertyMap;
8340
- const resolvedSortMap = useMemo6(
10003
+ const resolvedSortMap = useMemo7(
8341
10004
  () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
8342
10005
  [sortMap, effectivePropertyMap]
8343
10006
  );
8344
10007
  const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
8345
- const dataSourceOptions = useMemo6(
10008
+ const dataSourceOptions = useMemo7(
8346
10009
  () => ({
8347
10010
  objectType: resolveCrmObjectType(objectType),
8348
10011
  properties: resolvedProperties,
@@ -8356,15 +10019,15 @@ var CrmDataTable = ({
8356
10019
  }),
8357
10020
  [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
8358
10021
  );
8359
- const [serverQuerying, setServerQuerying] = useState7(!!serverSide);
10022
+ const [serverQuerying, setServerQuerying] = useState8(!!serverSide);
8360
10023
  const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
8361
10024
  const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
8362
- const queryKey = useMemo6(
10025
+ const queryKey = useMemo7(
8363
10026
  () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
8364
10027
  [effectiveParams, objectType, resolvedProperties, pageLength]
8365
10028
  );
8366
- const [accumulatedRows, setAccumulatedRows] = useState7(EMPTY_ARRAY);
8367
- const [requestedPage, setRequestedPage] = useState7(1);
10029
+ const [accumulatedRows, setAccumulatedRows] = useState8(EMPTY_ARRAY);
10030
+ const [requestedPage, setRequestedPage] = useState8(1);
8368
10031
  const lastQueryKeyRef = useRef5(queryKey);
8369
10032
  const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
8370
10033
  useEffect7(() => {
@@ -8393,7 +10056,7 @@ var CrmDataTable = ({
8393
10056
  useEffect7(() => {
8394
10057
  ensurePageLoaded(requestedPage);
8395
10058
  }, [requestedPage, ensurePageLoaded]);
8396
- const generatedFilters = useMemo6(
10059
+ const generatedFilters = useMemo7(
8397
10060
  () => buildAutoFiltersFromRows({
8398
10061
  rows: loadedRows,
8399
10062
  fields: autoFilterFields,
@@ -8403,7 +10066,7 @@ var CrmDataTable = ({
8403
10066
  [loadedRows, autoFilterFields, autoFilterMaxOptions]
8404
10067
  );
8405
10068
  const resolvedFilters = filters || generatedFilters;
8406
- const table = React17.createElement(DataTable, {
10069
+ const table = React18.createElement(DataTable, {
8407
10070
  title: title || `${prettifyPropertyName(objectType)} records`,
8408
10071
  data: loadedRows,
8409
10072
  loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
@@ -8427,11 +10090,11 @@ var CrmDataTable = ({
8427
10090
  const total = dataSource.totalCount;
8428
10091
  const capped = typeof total === "number" && total > loadedRows.length;
8429
10092
  if (!capped) return table;
8430
- return React17.createElement(
8431
- Flex10,
10093
+ return React18.createElement(
10094
+ Flex11,
8432
10095
  { direction: "column", gap: "xs" },
8433
- React17.createElement(
8434
- Text8,
10096
+ React18.createElement(
10097
+ Text9,
8435
10098
  { variant: "microcopy" },
8436
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.`
8437
10100
  ),
@@ -8465,25 +10128,25 @@ var CrmKanban = ({
8465
10128
  ...props
8466
10129
  }) => {
8467
10130
  var _a, _b;
8468
- const [params, setParams] = useState7(EMPTY_CRM_PARAMS);
8469
- const resolvedProperties = useMemo6(() => properties, [properties]);
10131
+ const [params, setParams] = useState8(EMPTY_CRM_PARAMS);
10132
+ const resolvedProperties = useMemo7(() => properties, [properties]);
8470
10133
  const resolvedSearchFields = searchFields || resolvedProperties;
8471
- const autoFilterFields = useMemo6(
10134
+ const autoFilterFields = useMemo7(
8472
10135
  () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
8473
10136
  [autoFilters, resolvedProperties]
8474
10137
  );
8475
10138
  const autoFilterLabelsRef = useRef5({});
8476
- const defaultPropertyMap = useMemo6(
10139
+ const defaultPropertyMap = useMemo7(
8477
10140
  () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
8478
10141
  [resolvedProperties]
8479
10142
  );
8480
10143
  const effectivePropertyMap = propertyMap || defaultPropertyMap;
8481
- const resolvedSortMap = useMemo6(
10144
+ const resolvedSortMap = useMemo7(
8482
10145
  () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
8483
10146
  [sortMap, effectivePropertyMap]
8484
10147
  );
8485
10148
  const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
8486
- const dataSourceOptions = useMemo6(
10149
+ const dataSourceOptions = useMemo7(
8487
10150
  () => ({
8488
10151
  objectType: resolveCrmObjectType(objectType),
8489
10152
  properties: resolvedProperties,
@@ -8497,14 +10160,14 @@ var CrmKanban = ({
8497
10160
  }),
8498
10161
  [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
8499
10162
  );
8500
- const [serverQuerying, setServerQuerying] = useState7(!!serverSide);
10163
+ const [serverQuerying, setServerQuerying] = useState8(!!serverSide);
8501
10164
  const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
8502
10165
  const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
8503
- const queryKey = useMemo6(
10166
+ const queryKey = useMemo7(
8504
10167
  () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
8505
10168
  [effectiveParams, objectType, resolvedProperties, pageLength]
8506
10169
  );
8507
- const [accumulatedRows, setAccumulatedRows] = useState7(EMPTY_ARRAY);
10170
+ const [accumulatedRows, setAccumulatedRows] = useState8(EMPTY_ARRAY);
8508
10171
  const lastQueryKeyRef = useRef5(queryKey);
8509
10172
  const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
8510
10173
  useEffect7(() => {
@@ -8531,7 +10194,7 @@ var CrmKanban = ({
8531
10194
  if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
8532
10195
  (_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
8533
10196
  }, [onLoadMore, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
8534
- const generatedFilters = useMemo6(
10197
+ const generatedFilters = useMemo7(
8535
10198
  () => buildAutoFiltersFromRows({
8536
10199
  rows: loadedRows,
8537
10200
  fields: autoFilterFields,
@@ -8541,7 +10204,7 @@ var CrmKanban = ({
8541
10204
  [loadedRows, autoFilterFields, autoFilterMaxOptions]
8542
10205
  );
8543
10206
  const resolvedFilters = filters || generatedFilters;
8544
- const resolvedStages = useMemo6(() => {
10207
+ const resolvedStages = useMemo7(() => {
8545
10208
  if (stages) return stages;
8546
10209
  const seen = [];
8547
10210
  for (const row of loadedRows) {
@@ -8553,7 +10216,7 @@ var CrmKanban = ({
8553
10216
  label: typeof stageLabels === "function" ? stageLabels(value) : stageLabels && stageLabels[value] || prettifyPropertyName(String(value))
8554
10217
  }));
8555
10218
  }, [stages, stageLabels, loadedRows, groupBy]);
8556
- const resolvedStageMeta = useMemo6(() => {
10219
+ const resolvedStageMeta = useMemo7(() => {
8557
10220
  if (stageMeta || !dataSource.hasMore) return stageMeta;
8558
10221
  return Object.fromEntries(resolvedStages.map((stage) => {
8559
10222
  var _a2;
@@ -8567,7 +10230,7 @@ var CrmKanban = ({
8567
10230
  ];
8568
10231
  }));
8569
10232
  }, [stageMeta, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.totalCount, resolvedStages]);
8570
- const board = React17.createElement(Kanban, {
10233
+ const board = React18.createElement(Kanban, {
8571
10234
  title: title || `${prettifyPropertyName(objectType)} board`,
8572
10235
  data: loadedRows,
8573
10236
  loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
@@ -8590,17 +10253,19 @@ var CrmKanban = ({
8590
10253
  const total = dataSource.totalCount;
8591
10254
  const capped = typeof total === "number" && total > loadedRows.length;
8592
10255
  if (!capped) return board;
8593
- return React17.createElement(
8594
- Flex10,
10256
+ return React18.createElement(
10257
+ Flex11,
8595
10258
  { direction: "column", gap: "xs" },
8596
- React17.createElement(
8597
- Text8,
10259
+ React18.createElement(
10260
+ Text9,
8598
10261
  { variant: "microcopy" },
8599
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.`
8600
10263
  ),
8601
10264
  board
8602
10265
  );
8603
10266
  };
10267
+ CrmDataTable.displayName = "CrmDataTable";
10268
+ CrmKanban.displayName = "CrmKanban";
8604
10269
 
8605
10270
  // src/common-components/CrmLookupSelect.js
8606
10271
  var EMPTY_ARRAY2 = [];
@@ -8653,12 +10318,12 @@ var CrmLookupSelect = ({
8653
10318
  loadingOption,
8654
10319
  selectProps = EMPTY_OBJECT2
8655
10320
  }) => {
8656
- const [inputValue, setInputValue] = useState8(query || "");
8657
- const [pickedOptions, setPickedOptions] = useState8(EMPTY_ARRAY2);
10321
+ const [inputValue, setInputValue] = useState9(query || "");
10322
+ const [pickedOptions, setPickedOptions] = useState9(EMPTY_ARRAY2);
8658
10323
  const debouncedInput = useDebounce2(inputValue, debounce > 0 ? debounce : 1);
8659
10324
  const search = debounce > 0 ? debouncedInput : inputValue;
8660
10325
  const effectiveSearch = search && search.length >= minSearchLength ? search : "";
8661
- const optionConfig = useMemo7(
10326
+ const optionConfig = useMemo8(
8662
10327
  () => makeOptionConfig({ option, labelProperty, valueProperty, descriptionProperty }),
8663
10328
  [option, labelProperty, valueProperty, descriptionProperty]
8664
10329
  );
@@ -8676,7 +10341,7 @@ var CrmLookupSelect = ({
8676
10341
  );
8677
10342
  const isSearching = dataSource.loading || inputValue.trim() !== (search || "").trim();
8678
10343
  const hasQuery = effectiveSearch.length > 0;
8679
- const options = useMemo7(() => {
10344
+ const options = useMemo8(() => {
8680
10345
  const remembered = [...selectedOptions || EMPTY_ARRAY2, ...pickedOptions];
8681
10346
  const baseOptions = mergeSelectedOptions(dataSource.options || EMPTY_ARRAY2, remembered, value);
8682
10347
  if (isSearching && loadingOption) return [loadingOption, ...baseOptions];
@@ -8715,7 +10380,281 @@ var CrmLookupSelect = ({
8715
10380
  },
8716
10381
  ...selectProps
8717
10382
  };
8718
- 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
+ );
8719
10658
  };
8720
10659
 
8721
10660
  // src/common-components/datePresets.js
@@ -8740,12 +10679,480 @@ var HS_DATE_DIRECTION_LABELS = {
8740
10679
  desc: "Descending"
8741
10680
  };
8742
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
+
8743
11150
  // src/common-components/KeyValueList.js
8744
- import React19 from "react";
8745
- 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";
8746
11153
  var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
8747
11154
  const rows = items.map(
8748
- (item, index) => React19.createElement(
11155
+ (item, index) => React22.createElement(
8749
11156
  DescriptionListItem3,
8750
11157
  {
8751
11158
  key: item.key ?? item.label ?? `kv-${index}`,
@@ -8754,16 +11161,17 @@ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
8754
11161
  item.value
8755
11162
  )
8756
11163
  );
8757
- return React19.createElement(
8758
- Flex11,
11164
+ return React22.createElement(
11165
+ Flex14,
8759
11166
  { direction: "column", gap },
8760
- React19.createElement(DescriptionList3, { direction }, ...rows)
11167
+ React22.createElement(DescriptionList3, { direction }, ...rows)
8761
11168
  );
8762
11169
  };
11170
+ KeyValueList.displayName = "KeyValueList";
8763
11171
 
8764
11172
  // src/common-components/SectionHeader.js
8765
- import React20 from "react";
8766
- 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";
8767
11175
  var SectionHeader = ({
8768
11176
  title,
8769
11177
  description,
@@ -8774,12 +11182,12 @@ var SectionHeader = ({
8774
11182
  }) => {
8775
11183
  const body = [];
8776
11184
  if (title != null) {
8777
- body.push(React20.createElement(Heading2, { key: "title", as: titleAs }, title));
11185
+ body.push(React23.createElement(Heading2, { key: "title", as: titleAs }, title));
8778
11186
  }
8779
11187
  if (description != null) {
8780
11188
  body.push(
8781
- React20.createElement(
8782
- Text9,
11189
+ React23.createElement(
11190
+ Text11,
8783
11191
  { key: "description", variant: "microcopy" },
8784
11192
  description
8785
11193
  )
@@ -8788,10 +11196,10 @@ var SectionHeader = ({
8788
11196
  if (children != null) {
8789
11197
  body.push(children);
8790
11198
  }
8791
- const content = React20.createElement(Flex12, { direction: "column", gap }, ...body);
11199
+ const content = React23.createElement(Flex15, { direction: "column", gap }, ...body);
8792
11200
  if (actions == null) return content;
8793
- return React20.createElement(
8794
- Flex12,
11201
+ return React23.createElement(
11202
+ Flex15,
8795
11203
  { direction: "row", justify: "between", align: "start", gap: "sm" },
8796
11204
  content,
8797
11205
  actions
@@ -8799,8 +11207,8 @@ var SectionHeader = ({
8799
11207
  };
8800
11208
 
8801
11209
  // src/common-components/Spinner.js
8802
- import React21, { useEffect as useEffect8, useRef as useRef6, useState as useState9 } from "react";
8803
- 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";
8804
11212
 
8805
11213
  // src/common-components/spinners.js
8806
11214
  var BRAILLE_DOT_MAP = [
@@ -9140,8 +11548,8 @@ var Spinner = ({
9140
11548
  const preset = SPINNERS[name] || SPINNERS[DEFAULT_NAME];
9141
11549
  const resolvedFrames = Array.isArray(frames) && frames.length > 0 ? frames : preset.frames;
9142
11550
  const resolvedInterval = Number.isFinite(interval) ? interval : preset.interval;
9143
- const [index, setIndex] = useState9(0);
9144
- const indexRef = useRef6(0);
11551
+ const [index, setIndex] = useState12(0);
11552
+ const indexRef = useRef7(0);
9145
11553
  indexRef.current = index;
9146
11554
  useEffect8(() => {
9147
11555
  if (paused || resolvedFrames.length <= 1) return void 0;
@@ -9158,10 +11566,10 @@ var Spinner = ({
9158
11566
  const frame = resolvedFrames[index % resolvedFrames.length];
9159
11567
  const suffix = children != null ? children : label;
9160
11568
  if (suffix == null || suffix === "") {
9161
- return React21.createElement(Text10, rest, frame);
11569
+ return React24.createElement(Text12, rest, frame);
9162
11570
  }
9163
- return React21.createElement(
9164
- Text10,
11571
+ return React24.createElement(
11572
+ Text12,
9165
11573
  rest,
9166
11574
  frame,
9167
11575
  gap,
@@ -9169,6 +11577,378 @@ var Spinner = ({
9169
11577
  );
9170
11578
  };
9171
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
+
9172
11952
  // src/utils/collections.js
9173
11953
  var sumBy = (items, keyOrFn) => (items || []).reduce((total, item) => {
9174
11954
  const value = typeof keyOrFn === "function" ? keyOrFn(item) : item == null ? void 0 : item[keyOrFn];
@@ -9256,9 +12036,19 @@ export {
9256
12036
  CrmDataTable,
9257
12037
  CrmKanban,
9258
12038
  CrmLookupSelect,
12039
+ CrmRecordPicker,
12040
+ DATE_FILTER_OPERATORS,
12041
+ DATE_RANGE_CUSTOM_VALUE,
12042
+ DATE_ROLLING_UNIT_OPTIONS,
12043
+ DEFAULT_FEED_TYPE_PRESETS,
9259
12044
  DEFAULT_SVG_FONT_WEIGHT,
9260
12045
  DataTable,
12046
+ DateRangePicker,
12047
+ EMPTY_STATE_IMAGES,
12048
+ EMPTY_STATE_IMAGE_ALIASES,
12049
+ FILTER_OPERATORS,
9261
12050
  Feed,
12051
+ FilterBuilder,
9262
12052
  FormBuilder,
9263
12053
  HS_DATE_DIRECTION_LABELS,
9264
12054
  HS_DATE_PRESETS,
@@ -9277,21 +12067,61 @@ export {
9277
12067
  HS_TEXT_COLOR,
9278
12068
  ICONS,
9279
12069
  ICON_NAMES,
12070
+ ICON_NAME_ALIASES2 as ICON_NAME_ALIASES,
9280
12071
  Icon,
9281
12072
  Kanban,
9282
12073
  KanbanCardActions,
9283
12074
  KeyValueList,
12075
+ NATIVE_ICON_NAMES,
9284
12076
  NATIVE_ICON_NAME_LIST,
12077
+ SAFE_ARRAY_PROPS,
12078
+ SAFE_DERIVE_PROPS,
12079
+ SKELETON_FILL,
9285
12080
  SPINNERS,
9286
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,
9287
12099
  SectionHeader,
9288
12100
  Spinner,
9289
12101
  StyledText,
12102
+ TREND_DIRECTIONS,
12103
+ TREND_DIRECTION_ALIASES,
12104
+ UNASSIGNED_LANE_KEY,
12105
+ addFilter,
12106
+ applyPatches,
12107
+ applyTypePreset,
9290
12108
  buildCrmSearchConfig,
9291
12109
  buildOptions,
12110
+ changeConditionOperator,
12111
+ changeConditionProperty,
12112
+ compareHsDateValues,
12113
+ computeStageCounts,
12114
+ conditionToCrmFilter,
12115
+ countConditions,
12116
+ createCondition,
12117
+ createGroup,
9292
12118
  createStatusTagSortComparator,
9293
12119
  crmSearchResultToOption,
12120
+ evaluateWip,
12121
+ fieldsFromHubSpotProperties,
12122
+ findNewlyExceededWip,
9294
12123
  findOptionLabel2 as findOptionLabel,
12124
+ flushBuffer,
9295
12125
  formatCurrency,
9296
12126
  formatCurrencyCompact,
9297
12127
  formatDate,
@@ -9299,7 +12129,14 @@ export {
9299
12129
  formatPercentage,
9300
12130
  getAutoStatusTagVariant,
9301
12131
  getAutoTagVariant,
12132
+ getLaneKey,
12133
+ getNodeAtPath,
12134
+ getOperatorOptions,
9302
12135
  gridToBraille,
12136
+ isConditionNode,
12137
+ isGroupNode,
12138
+ isValidDateRange,
12139
+ lookupTypePreset,
9303
12140
  makeAvatarStackDataUri,
9304
12141
  makeCrmSearchMultiSelectField,
9305
12142
  makeCrmSearchSelectField,
@@ -9308,10 +12145,28 @@ export {
9308
12145
  makeStyledTextDataUri,
9309
12146
  normalizeCrmSearchRecord,
9310
12147
  normalizeCrmSearchRows,
12148
+ operatorExpectsHighValue,
12149
+ operatorExpectsValue,
12150
+ operatorExpectsValues,
12151
+ orderLaneKeys,
12152
+ partitionLanes,
12153
+ partitionNewItems,
12154
+ presetToRange,
12155
+ removeFilter,
12156
+ resetSafeWarnings,
9311
12157
  resolveCrmObjectType,
12158
+ resolveLaneLabel,
12159
+ resolveWipLimit,
9312
12160
  sumBy,
9313
12161
  svgToIconEntry,
12162
+ toCrmSearchFilterGroups,
12163
+ toHsDateValue,
12164
+ toTimestampMs,
12165
+ updateFilter,
9314
12166
  useCrmSearchDataSource,
9315
12167
  useCrmSearchOptions,
9316
- useFormPrefill
12168
+ useFormPrefill,
12169
+ validateTree,
12170
+ warnOnce,
12171
+ withSafeArrayProps
9317
12172
  };