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.js CHANGED
@@ -36,9 +36,19 @@ __export(src_exports, {
36
36
  CrmDataTable: () => CrmDataTable,
37
37
  CrmKanban: () => CrmKanban,
38
38
  CrmLookupSelect: () => CrmLookupSelect,
39
+ CrmRecordPicker: () => CrmRecordPicker,
40
+ DATE_FILTER_OPERATORS: () => DATE_FILTER_OPERATORS,
41
+ DATE_RANGE_CUSTOM_VALUE: () => DATE_RANGE_CUSTOM_VALUE,
42
+ DATE_ROLLING_UNIT_OPTIONS: () => DATE_ROLLING_UNIT_OPTIONS,
43
+ DEFAULT_FEED_TYPE_PRESETS: () => DEFAULT_FEED_TYPE_PRESETS,
39
44
  DEFAULT_SVG_FONT_WEIGHT: () => DEFAULT_SVG_FONT_WEIGHT,
40
45
  DataTable: () => DataTable,
46
+ DateRangePicker: () => DateRangePicker,
47
+ EMPTY_STATE_IMAGES: () => EMPTY_STATE_IMAGES,
48
+ EMPTY_STATE_IMAGE_ALIASES: () => EMPTY_STATE_IMAGE_ALIASES,
49
+ FILTER_OPERATORS: () => FILTER_OPERATORS,
41
50
  Feed: () => Feed,
51
+ FilterBuilder: () => FilterBuilder,
42
52
  FormBuilder: () => FormBuilder,
43
53
  HS_DATE_DIRECTION_LABELS: () => HS_DATE_DIRECTION_LABELS,
44
54
  HS_DATE_PRESETS: () => HS_DATE_PRESETS,
@@ -57,21 +67,61 @@ __export(src_exports, {
57
67
  HS_TEXT_COLOR: () => HS_TEXT_COLOR,
58
68
  ICONS: () => ICONS,
59
69
  ICON_NAMES: () => ICON_NAMES,
70
+ ICON_NAME_ALIASES: () => ICON_NAME_ALIASES2,
60
71
  Icon: () => Icon,
61
72
  Kanban: () => Kanban,
62
73
  KanbanCardActions: () => KanbanCardActions,
63
74
  KeyValueList: () => KeyValueList,
75
+ NATIVE_ICON_NAMES: () => NATIVE_ICON_NAMES,
64
76
  NATIVE_ICON_NAME_LIST: () => NATIVE_ICON_NAME_LIST,
77
+ SAFE_ARRAY_PROPS: () => SAFE_ARRAY_PROPS,
78
+ SAFE_DERIVE_PROPS: () => SAFE_DERIVE_PROPS,
79
+ SKELETON_FILL: () => SKELETON_FILL,
65
80
  SPINNERS: () => SPINNERS,
66
81
  SPINNER_NAMES: () => SPINNER_NAMES,
82
+ SafeAvatarStack: () => SafeAvatarStack,
83
+ SafeCalendar: () => SafeCalendar,
84
+ SafeCrmDataTable: () => SafeCrmDataTable,
85
+ SafeCrmKanban: () => SafeCrmKanban,
86
+ SafeDataTable: () => SafeDataTable,
87
+ SafeEmptyState: () => SafeEmptyState,
88
+ SafeFeed: () => SafeFeed,
89
+ SafeFormBuilder: () => SafeFormBuilder,
90
+ SafeIcon: () => SafeIcon,
91
+ SafeKanban: () => SafeKanban,
92
+ SafeKeyValueList: () => SafeKeyValueList,
93
+ SafeMultiSelect: () => SafeMultiSelect,
94
+ SafePopover: () => SafePopover,
95
+ SafeSelect: () => SafeSelect,
96
+ SafeStatisticsTrend: () => SafeStatisticsTrend,
97
+ SafeStepIndicator: () => SafeStepIndicator,
98
+ SafeToggleGroup: () => SafeToggleGroup,
67
99
  SectionHeader: () => SectionHeader,
68
100
  Spinner: () => Spinner,
69
101
  StyledText: () => StyledText,
102
+ TREND_DIRECTIONS: () => TREND_DIRECTIONS,
103
+ TREND_DIRECTION_ALIASES: () => TREND_DIRECTION_ALIASES,
104
+ UNASSIGNED_LANE_KEY: () => UNASSIGNED_LANE_KEY,
105
+ addFilter: () => addFilter,
106
+ applyPatches: () => applyPatches,
107
+ applyTypePreset: () => applyTypePreset,
70
108
  buildCrmSearchConfig: () => buildCrmSearchConfig,
71
109
  buildOptions: () => buildOptions,
110
+ changeConditionOperator: () => changeConditionOperator,
111
+ changeConditionProperty: () => changeConditionProperty,
112
+ compareHsDateValues: () => compareHsDateValues,
113
+ computeStageCounts: () => computeStageCounts,
114
+ conditionToCrmFilter: () => conditionToCrmFilter,
115
+ countConditions: () => countConditions,
116
+ createCondition: () => createCondition,
117
+ createGroup: () => createGroup,
72
118
  createStatusTagSortComparator: () => createStatusTagSortComparator,
73
119
  crmSearchResultToOption: () => crmSearchResultToOption,
120
+ evaluateWip: () => evaluateWip,
121
+ fieldsFromHubSpotProperties: () => fieldsFromHubSpotProperties,
122
+ findNewlyExceededWip: () => findNewlyExceededWip,
74
123
  findOptionLabel: () => findOptionLabel2,
124
+ flushBuffer: () => flushBuffer,
75
125
  formatCurrency: () => formatCurrency,
76
126
  formatCurrencyCompact: () => formatCurrencyCompact,
77
127
  formatDate: () => formatDate,
@@ -79,7 +129,14 @@ __export(src_exports, {
79
129
  formatPercentage: () => formatPercentage,
80
130
  getAutoStatusTagVariant: () => getAutoStatusTagVariant,
81
131
  getAutoTagVariant: () => getAutoTagVariant,
132
+ getLaneKey: () => getLaneKey,
133
+ getNodeAtPath: () => getNodeAtPath,
134
+ getOperatorOptions: () => getOperatorOptions,
82
135
  gridToBraille: () => gridToBraille,
136
+ isConditionNode: () => isConditionNode,
137
+ isGroupNode: () => isGroupNode,
138
+ isValidDateRange: () => isValidDateRange,
139
+ lookupTypePreset: () => lookupTypePreset,
83
140
  makeAvatarStackDataUri: () => makeAvatarStackDataUri,
84
141
  makeCrmSearchMultiSelectField: () => makeCrmSearchMultiSelectField,
85
142
  makeCrmSearchSelectField: () => makeCrmSearchSelectField,
@@ -88,12 +145,30 @@ __export(src_exports, {
88
145
  makeStyledTextDataUri: () => makeStyledTextDataUri,
89
146
  normalizeCrmSearchRecord: () => normalizeCrmSearchRecord,
90
147
  normalizeCrmSearchRows: () => normalizeCrmSearchRows,
148
+ operatorExpectsHighValue: () => operatorExpectsHighValue,
149
+ operatorExpectsValue: () => operatorExpectsValue,
150
+ operatorExpectsValues: () => operatorExpectsValues,
151
+ orderLaneKeys: () => orderLaneKeys,
152
+ partitionLanes: () => partitionLanes,
153
+ partitionNewItems: () => partitionNewItems,
154
+ presetToRange: () => presetToRange,
155
+ removeFilter: () => removeFilter,
156
+ resetSafeWarnings: () => resetSafeWarnings,
91
157
  resolveCrmObjectType: () => resolveCrmObjectType,
158
+ resolveLaneLabel: () => resolveLaneLabel,
159
+ resolveWipLimit: () => resolveWipLimit,
92
160
  sumBy: () => sumBy,
93
161
  svgToIconEntry: () => svgToIconEntry,
162
+ toCrmSearchFilterGroups: () => toCrmSearchFilterGroups,
163
+ toHsDateValue: () => toHsDateValue,
164
+ toTimestampMs: () => toTimestampMs,
165
+ updateFilter: () => updateFilter,
94
166
  useCrmSearchDataSource: () => useCrmSearchDataSource,
95
167
  useCrmSearchOptions: () => useCrmSearchOptions,
96
- useFormPrefill: () => useFormPrefill
168
+ useFormPrefill: () => useFormPrefill,
169
+ validateTree: () => validateTree,
170
+ warnOnce: () => warnOnce,
171
+ withSafeArrayProps: () => withSafeArrayProps
97
172
  });
98
173
  module.exports = __toCommonJS(src_exports);
99
174
 
@@ -356,6 +431,7 @@ var HS_TEXT_COLOR = "#33475b";
356
431
  var HS_SUBTLE_BG = "#F5F8FA";
357
432
  var HS_MUTED_TEXT = "#7C98B6";
358
433
  var HS_NEUTRAL_CHIP = "#CBD6E2";
434
+ var SKELETON_FILL = "#DFE3EB";
359
435
  var HS_TAG_SUBTLE_BORDER = "#7C98B6";
360
436
  var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
361
437
  var HS_TAG_FONT_SIZE = 12;
@@ -618,7 +694,7 @@ var GENERATED_ICONS = {
618
694
  "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"] }
619
695
  };
620
696
 
621
- // src/common-components/Icon.js
697
+ // src/common-components/nativeIconNames.js
622
698
  var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
623
699
  "add",
624
700
  "appointment",
@@ -630,12 +706,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
630
706
  "block",
631
707
  "book",
632
708
  "bulb",
709
+ "callTranscript",
633
710
  "calling",
634
711
  "callingHangup",
635
712
  "callingMade",
636
713
  "callingMissed",
637
714
  "callingVoicemail",
638
- "callTranscript",
639
715
  "campaigns",
640
716
  "cap",
641
717
  "checkCircle",
@@ -664,13 +740,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
664
740
  "enroll",
665
741
  "exclamation",
666
742
  "exclamationCircle",
667
- "facebook",
668
743
  "faceHappy",
669
744
  "faceHappyFilled",
670
745
  "faceNeutral",
671
746
  "faceNeutralFilled",
672
747
  "faceSad",
673
748
  "faceSadFilled",
749
+ "facebook",
674
750
  "favoriteHollow",
675
751
  "file",
676
752
  "filledXCircleIcon",
@@ -811,6 +887,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
811
887
  "zoomIn",
812
888
  "zoomOut"
813
889
  ]);
890
+
891
+ // src/common-components/Icon.js
814
892
  var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
815
893
  var NATIVE_SIZE_TOKENS = {
816
894
  sm: "sm",
@@ -1052,6 +1130,7 @@ var CollectionFilterControl = ({
1052
1130
  { key: name, direction: "row", align: "center", gap: "xs" },
1053
1131
  h3(import_ui_extensions5.DateInput, {
1054
1132
  name: `${controlName}-from`,
1133
+ label: filter.fromLabel ?? labels.dateFrom,
1055
1134
  placeholder: filter.fromLabel ?? labels.dateFrom,
1056
1135
  format: "medium",
1057
1136
  value: rangeValue.from ?? null,
@@ -1060,6 +1139,7 @@ var CollectionFilterControl = ({
1060
1139
  h3(Icon, { name: "right", size: "sm" }),
1061
1140
  h3(import_ui_extensions5.DateInput, {
1062
1141
  name: `${controlName}-to`,
1142
+ label: filter.toLabel ?? labels.dateTo,
1063
1143
  placeholder: filter.toLabel ?? labels.dateTo,
1064
1144
  format: "medium",
1065
1145
  value: rangeValue.to ?? null,
@@ -1198,6 +1278,45 @@ var editValidationError = (result) => {
1198
1278
  return typeof result === "string" ? result : "Invalid value";
1199
1279
  };
1200
1280
 
1281
+ // src/datatable/rowExpansion.js
1282
+ var extractRowId = (row, rowIdField = "id", fallback = void 0) => {
1283
+ const id = row == null ? void 0 : row[rowIdField];
1284
+ return id != null ? id : fallback;
1285
+ };
1286
+ var normalizeExpandedIds = (ids) => {
1287
+ if (ids == null) return /* @__PURE__ */ new Set();
1288
+ const list = ids instanceof Set ? [...ids] : Array.isArray(ids) ? ids : [ids];
1289
+ return new Set(list.filter((id) => id != null));
1290
+ };
1291
+ var expandRowId = (expandedIds, rowId, expandSingle = false) => {
1292
+ if (rowId == null) return expandedIds;
1293
+ if (expandSingle) return /* @__PURE__ */ new Set([rowId]);
1294
+ const next = new Set(expandedIds);
1295
+ next.add(rowId);
1296
+ return next;
1297
+ };
1298
+ var collapseRowId = (expandedIds, rowId) => {
1299
+ if (rowId == null || !expandedIds.has(rowId)) return expandedIds;
1300
+ const next = new Set(expandedIds);
1301
+ next.delete(rowId);
1302
+ return next;
1303
+ };
1304
+ var toggleExpandedId = (expandedIds, rowId, expandSingle = false) => {
1305
+ if (rowId == null) return expandedIds;
1306
+ return expandedIds.has(rowId) ? collapseRowId(expandedIds, rowId) : expandRowId(expandedIds, rowId, expandSingle);
1307
+ };
1308
+ var withDetailRows = (items, expandedIds, rowIdField = "id") => {
1309
+ if (!expandedIds || expandedIds.size === 0) return items;
1310
+ const out = [];
1311
+ items.forEach((item) => {
1312
+ out.push(item);
1313
+ if (item.type === "data" && expandedIds.has(extractRowId(item.row, rowIdField))) {
1314
+ out.push({ type: "detail", row: item.row });
1315
+ }
1316
+ });
1317
+ return out;
1318
+ };
1319
+
1201
1320
  // src/datatable/DataTable.jsx
1202
1321
  var import_ui_extensions7 = require("@hubspot/ui-extensions");
1203
1322
  var NARROW_EDIT_TYPES = /* @__PURE__ */ new Set(["checkbox", "toggle"]);
@@ -1409,6 +1528,21 @@ var DataTable = ({
1409
1528
  hideRowActionsWhenSelectionActive = false,
1410
1529
  // hide row action column while selected-row action bar is visible
1411
1530
  // -----------------------------------------------------------------------
1531
+ // Row expansion (detail rows)
1532
+ // -----------------------------------------------------------------------
1533
+ renderExpandedRow,
1534
+ // (row) => ReactNode — providing this enables the feature
1535
+ expandedRowIds: externalExpandedRowIds,
1536
+ // controlled — array of expanded row IDs
1537
+ defaultExpandedRowIds,
1538
+ // uncontrolled — initially expanded row IDs
1539
+ onExpandedRowsChange,
1540
+ // (expandedRowIds[]) => void
1541
+ expandOn = "icon",
1542
+ // "icon" (chevron toggle column) | "row" (click row content)
1543
+ expandSingle = false,
1544
+ // accordion mode — expanding a row collapses the others
1545
+ // -----------------------------------------------------------------------
1412
1546
  // Inline editing
1413
1547
  // -----------------------------------------------------------------------
1414
1548
  editMode,
@@ -1653,6 +1787,25 @@ var DataTable = ({
1653
1787
  });
1654
1788
  return rows;
1655
1789
  }, [groupedData, paginatedRows, expandedGroups]);
1790
+ const expandable = typeof renderExpandedRow === "function";
1791
+ const showExpandColumn = expandable && expandOn === "icon";
1792
+ const [internalExpandedRowIds, setInternalExpandedRowIds] = (0, import_react7.useState)(
1793
+ () => normalizeExpandedIds(defaultExpandedRowIds)
1794
+ );
1795
+ const expandedRowIds = (0, import_react7.useMemo)(
1796
+ () => externalExpandedRowIds != null ? normalizeExpandedIds(externalExpandedRowIds) : internalExpandedRowIds,
1797
+ [externalExpandedRowIds, internalExpandedRowIds]
1798
+ );
1799
+ const toggleRowExpanded = (0, import_react7.useCallback)((rowId) => {
1800
+ if (rowId == null) return;
1801
+ const next = toggleExpandedId(expandedRowIds, rowId, expandSingle);
1802
+ if (externalExpandedRowIds == null) setInternalExpandedRowIds(next);
1803
+ if (onExpandedRowsChange) onExpandedRowsChange([...next]);
1804
+ }, [expandedRowIds, expandSingle, externalExpandedRowIds, onExpandedRowsChange]);
1805
+ const renderedRows = (0, import_react7.useMemo)(() => {
1806
+ if (!expandable) return displayRows;
1807
+ return withDetailRows(displayRows, expandedRowIds, rowIdField);
1808
+ }, [expandable, displayRows, expandedRowIds, rowIdField]);
1656
1809
  const footerData = serverSide ? data : filteredData;
1657
1810
  const activeChips = (0, import_react7.useMemo)(
1658
1811
  () => buildActiveFilterChips(filters, filterValues),
@@ -1804,6 +1957,7 @@ var DataTable = ({
1804
1957
  const type = col.editType || "text";
1805
1958
  const rowId = row[rowIdField];
1806
1959
  const fieldName = `edit-${rowId}-${col.field}`;
1960
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
1807
1961
  const commit = (val) => commitEdit(row, col.field, val);
1808
1962
  const exitEdit = () => {
1809
1963
  if (editError) return;
@@ -1844,15 +1998,15 @@ var DataTable = ({
1844
1998
  case "multiselect":
1845
1999
  return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.MultiSelect, { ...extra, name: fieldName, label: "", value: editValue || [], onChange: commit, options: resolveEditOptions(col, data) });
1846
2000
  case "date":
1847
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
2001
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
1848
2002
  case "time":
1849
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
2003
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
1850
2004
  case "datetime":
1851
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
2005
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
1852
2006
  const next = { ...editValue, date: val };
1853
2007
  handleInput(next);
1854
2008
  commitEdit(row, col.field, next, { keepEditing: true });
1855
- }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
2009
+ }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
1856
2010
  const next = { ...editValue, time: val };
1857
2011
  handleInput(next);
1858
2012
  commitEdit(row, col.field, next, { keepEditing: true });
@@ -1866,7 +2020,8 @@ var DataTable = ({
1866
2020
  }
1867
2021
  };
1868
2022
  const resolvedEditMode = editMode || (columns.some((col) => col.editable) ? "discrete" : null);
1869
- const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || !renderRow;
2023
+ const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || expandable || !renderRow;
2024
+ const totalColumnCount = columns.length + (selectable ? 1 : 0) + (showExpandColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
1870
2025
  const autoWidths = (0, import_react7.useMemo)(
1871
2026
  () => autoWidth ? computeAutoWidths(columns, data) : {},
1872
2027
  [columns, data, autoWidth]
@@ -1885,6 +2040,7 @@ var DataTable = ({
1885
2040
  const type = col.editType || "text";
1886
2041
  const rowId = row[rowIdField];
1887
2042
  const fieldName = `inline-${rowId}-${col.field}`;
2043
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
1888
2044
  const cellKey = `${rowId}-${col.field}`;
1889
2045
  const value = row[col.field];
1890
2046
  const validate = col.editValidate;
@@ -1933,13 +2089,13 @@ var DataTable = ({
1933
2089
  case "multiselect":
1934
2090
  return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.MultiSelect, { ...extra, name: fieldName, label: "", value: value || [], onChange: fire, options: resolveEditOptions(col, data) });
1935
2091
  case "date":
1936
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
2092
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
1937
2093
  case "time":
1938
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
2094
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
1939
2095
  case "datetime":
1940
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: value == null ? void 0 : value.date, onChange: (val) => {
2096
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: value == null ? void 0 : value.date, onChange: (val) => {
1941
2097
  fire({ ...value, date: val });
1942
- } }), /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: value == null ? void 0 : value.time, onChange: (val) => {
2098
+ } }), /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: value == null ? void 0 : value.time, onChange: (val) => {
1943
2099
  fire({ ...value, time: val });
1944
2100
  } }));
1945
2101
  case "toggle":
@@ -2093,7 +2249,7 @@ var DataTable = ({
2093
2249
  checked: allVisibleSelected,
2094
2250
  onChange: handleSelectAll
2095
2251
  }
2096
- )), columns.map((col) => {
2252
+ )), showExpandColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" }), columns.map((col) => {
2097
2253
  const headerAlign = resolvedEditMode === "inline" && col.editable ? void 0 : col.align;
2098
2254
  return /* @__PURE__ */ import_react7.default.createElement(
2099
2255
  import_ui_extensions7.TableHeader,
@@ -2107,8 +2263,8 @@ var DataTable = ({
2107
2263
  col.description ? /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, col.label, "\xA0", /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Tooltip, null, col.description) }, /* @__PURE__ */ import_react7.default.createElement(Icon, { name: "info", screenReaderText: typeof col.description === "string" ? col.description : void 0 }))) : col.label
2108
2264
  );
2109
2265
  }), showRowActionsColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" }))),
2110
- /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableBody, null, displayRows.map(
2111
- (item, idx) => item.type === "group-header" ? /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" }), columns.map((col, colIdx) => {
2266
+ /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableBody, null, renderedRows.map(
2267
+ (item, idx) => item.type === "group-header" ? /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" }), showExpandColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" }), columns.map((col, colIdx) => {
2112
2268
  var _a, _b, _c;
2113
2269
  return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { key: col.field, width: getCellWidth(col), align: colIdx === 0 ? void 0 : col.align }, colIdx === 0 ? /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react7.default.createElement(
2114
2270
  Icon,
@@ -2125,7 +2281,7 @@ var DataTable = ({
2125
2281
  },
2126
2282
  /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Text, { format: { fontWeight: "demibold" } }, item.group.label)
2127
2283
  )) : ((_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]) ?? "");
2128
- }), showRowActionsColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" })) : useColumnRendering ? /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" }, /* @__PURE__ */ import_react7.default.createElement(
2284
+ }), showRowActionsColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" })) : item.type === "detail" ? /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableRow, { key: `detail-${item.row[rowIdField] ?? idx}` }, /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { colSpan: totalColumnCount }, renderExpandedRow(item.row))) : useColumnRendering ? /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" }, /* @__PURE__ */ import_react7.default.createElement(
2129
2285
  import_ui_extensions7.Checkbox,
2130
2286
  {
2131
2287
  name: `select-${item.row[rowIdField]}`,
@@ -2133,13 +2289,22 @@ var DataTable = ({
2133
2289
  checked: selectedIds.has(item.row[rowIdField]),
2134
2290
  onChange: (checked) => handleSelectRow(item.row[rowIdField], checked)
2135
2291
  }
2292
+ )), showExpandColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" }, /* @__PURE__ */ import_react7.default.createElement(
2293
+ Icon,
2294
+ {
2295
+ name: expandedRowIds.has(item.row[rowIdField]) ? "upCarat" : "downCarat",
2296
+ onClick: () => toggleRowExpanded(item.row[rowIdField]),
2297
+ screenReaderText: expandedRowIds.has(item.row[rowIdField]) ? "Collapse row" : "Expand row"
2298
+ }
2136
2299
  )), columns.map((col) => {
2137
2300
  const rowId = item.row[rowIdField];
2138
2301
  const isDiscreteEditing = resolvedEditMode === "discrete" && (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
2139
2302
  const isRowEditing = editingRowId != null && rowId === editingRowId && col.editable;
2140
2303
  const isShowingInput = isDiscreteEditing || isRowEditing || resolvedEditMode === "inline" && col.editable;
2141
2304
  const cellAlign = isShowingInput ? void 0 : col.align;
2142
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, renderCellContent(item.row, col));
2305
+ const cellContent = renderCellContent(item.row, col);
2306
+ const wrapInRowToggle = expandable && expandOn === "row" && !col.editable && !isShowingInput;
2307
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, wrapInRowToggle ? /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Link, { variant: "dark", onClick: () => toggleRowExpanded(rowId) }, cellContent) : cellContent);
2143
2308
  }), showRowActionsColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" }, /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, (() => {
2144
2309
  const resolvedRowActions = typeof rowActions === "function" ? rowActions(item.row) : rowActions;
2145
2310
  const actions = Array.isArray(resolvedRowActions) ? resolvedRowActions : [];
@@ -2156,13 +2321,14 @@ var DataTable = ({
2156
2321
  ));
2157
2322
  })()))) : renderRow(item.row)
2158
2323
  )),
2159
- (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableRow, null, selectable && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" }), columns.map((col) => {
2324
+ (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableRow, null, selectable && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" }), showExpandColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" }), columns.map((col) => {
2160
2325
  const footerDef = col.footer;
2161
2326
  const content = typeof footerDef === "function" ? footerDef(footerData) : footerDef || "";
2162
2327
  return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { key: col.field, align: col.align }, content);
2163
2328
  }), showRowActionsColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" })))
2164
2329
  ));
2165
2330
  };
2331
+ DataTable.displayName = "DataTable";
2166
2332
 
2167
2333
  // src/form/FormBuilder.jsx
2168
2334
  var import_react8 = __toESM(require("react"));
@@ -2489,8 +2655,25 @@ var resolveDependentCascade = ({ name, value, fields, values, getEmptyValueForFi
2489
2655
  return { newValues, changedDependents };
2490
2656
  };
2491
2657
 
2658
+ // src/form/formDirty.js
2659
+ var isFormDirty = (values, initialValues) => !deepEqual(values || {}, initialValues || {});
2660
+ var getDirtyFields = (values, initialValues) => {
2661
+ const current = values || {};
2662
+ const baseline = initialValues || {};
2663
+ const names = [...Object.keys(current)];
2664
+ for (const name of Object.keys(baseline)) {
2665
+ if (!Object.prototype.hasOwnProperty.call(current, name)) names.push(name);
2666
+ }
2667
+ return names.filter((name) => !deepEqual(current[name], baseline[name]));
2668
+ };
2669
+
2492
2670
  // src/form/FormBuilder.jsx
2493
2671
  var getRepeaterErrorKey = (fieldName, rowIdx, subFieldName) => `${fieldName}[${rowIdx}].${subFieldName}`;
2672
+ var withFieldLoadingSpinner = (element, loading) => {
2673
+ if (!loading) return element;
2674
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", gap: "xs", align: "end" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Box, { flex: 1 }, element), /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.LoadingSpinner, { size: "xs", layout: "inline" }));
2675
+ };
2676
+ var formBuilderInstanceCounter = 0;
2494
2677
  var fieldSetHasErrors = (errors, fields) => {
2495
2678
  if (!errors || !fields || fields.length === 0) return false;
2496
2679
  const names = new Set(fields.map((field) => field.name));
@@ -2590,8 +2773,10 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
2590
2773
  // controlled loading state
2591
2774
  disabled = false,
2592
2775
  // disable entire form
2593
- renderButtons: renderButtonsProp
2776
+ renderButtons: renderButtonsProp,
2594
2777
  // custom action row renderer
2778
+ confirmDiscard
2779
+ // true | { title, message, confirmLabel, cancelLabel } — confirm before the built-in Cancel discards dirty changes
2595
2780
  } = props;
2596
2781
  const {
2597
2782
  columns = 1,
@@ -2710,6 +2895,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
2710
2895
  "readOnly",
2711
2896
  "alwaysEditable",
2712
2897
  "disabled",
2898
+ "loading",
2713
2899
  "defaultValue",
2714
2900
  "fieldProps",
2715
2901
  "colSpan",
@@ -2966,7 +3152,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
2966
3152
  syncDirtyBaseline(values);
2967
3153
  }, [values, syncDirtyBaseline]);
2968
3154
  const isDirty = (0, import_react8.useMemo)(() => {
2969
- return !deepEqual(formValues, initialSnapshot.current);
3155
+ return isFormDirty(formValues, initialSnapshot.current);
2970
3156
  }, [formValues]);
2971
3157
  const prevDirtyRef = (0, import_react8.useRef)(false);
2972
3158
  (0, import_react8.useEffect)(() => {
@@ -2975,6 +3161,27 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
2975
3161
  if (onDirtyChange) onDirtyChange(isDirty);
2976
3162
  }
2977
3163
  }, [isDirty, onDirtyChange]);
3164
+ let hostActions = null;
3165
+ try {
3166
+ hostActions = (0, import_ui_extensions8.useExtensionActions)();
3167
+ } catch (err) {
3168
+ hostActions = null;
3169
+ }
3170
+ const discardModalIdRef = (0, import_react8.useRef)(null);
3171
+ if (discardModalIdRef.current === null) {
3172
+ formBuilderInstanceCounter += 1;
3173
+ discardModalIdRef.current = `hs-uix-form-discard-${formBuilderInstanceCounter}`;
3174
+ }
3175
+ const discardConfig = confirmDiscard ? confirmDiscard === true ? {} : confirmDiscard : null;
3176
+ const discardTitle = discardConfig && discardConfig.title || "Discard changes?";
3177
+ const discardMessage = discardConfig && discardConfig.message || "You have unsaved changes. If you discard now, they will be lost.";
3178
+ const discardConfirmLabel = discardConfig && discardConfig.confirmLabel || "Discard changes";
3179
+ const discardCancelLabel = discardConfig && discardConfig.cancelLabel || "Keep editing";
3180
+ const closeDiscardModal = (0, import_react8.useCallback)(() => {
3181
+ if (hostActions && typeof hostActions.closeOverlay === "function") {
3182
+ hostActions.closeOverlay(discardModalIdRef.current);
3183
+ }
3184
+ }, [hostActions]);
2978
3185
  const autoSaveTimerRef = (0, import_react8.useRef)(null);
2979
3186
  const autoSaveRef = (0, import_react8.useRef)(autoSave);
2980
3187
  autoSaveRef.current = autoSave;
@@ -3613,6 +3820,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
3613
3820
  },
3614
3821
  getValues: () => formValues,
3615
3822
  isDirty: () => isDirty,
3823
+ getDirtyFields: () => getDirtyFields(formValuesRef.current, initialSnapshot.current),
3616
3824
  setFieldValue: (name, value) => handleFieldChange(name, value),
3617
3825
  setFieldError: (name, message) => updateErrors({ [name]: message }),
3618
3826
  setErrors: (errors) => {
@@ -3632,7 +3840,8 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
3632
3840
  const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
3633
3841
  const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
3634
3842
  const isReadOnly = field.readOnly || fieldFormReadOnly;
3635
- const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
3843
+ const isFieldLoading = !!field.loading;
3844
+ const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly || isFieldLoading;
3636
3845
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
3637
3846
  if (field.type === "display" || field.type === "slot") {
3638
3847
  if (field.render) {
@@ -3816,7 +4025,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
3816
4025
  disabled: isDisabled,
3817
4026
  error: hasError,
3818
4027
  validationMessage: renderFieldError ? void 0 : fieldError || void 0,
3819
- ...field.loading || validatingFields[field.name] ? { loading: true } : {},
4028
+ ...validatingFields[field.name] ? { loading: true } : {},
3820
4029
  ...field.fieldProps || {}
3821
4030
  };
3822
4031
  const options = resolveOptions(field, formValues);
@@ -3974,25 +4183,31 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
3974
4183
  )));
3975
4184
  }
3976
4185
  case "select":
3977
- return /* @__PURE__ */ import_react8.default.createElement(
3978
- import_ui_extensions8.Select,
3979
- {
3980
- ...commonProps,
3981
- value: fieldValue,
3982
- options,
3983
- variant: field.variant,
3984
- onChange: fieldOnChange
3985
- }
4186
+ return withFieldLoadingSpinner(
4187
+ /* @__PURE__ */ import_react8.default.createElement(
4188
+ import_ui_extensions8.Select,
4189
+ {
4190
+ ...commonProps,
4191
+ value: fieldValue,
4192
+ options,
4193
+ variant: field.variant,
4194
+ onChange: fieldOnChange
4195
+ }
4196
+ ),
4197
+ isFieldLoading
3986
4198
  );
3987
4199
  case "multiselect":
3988
- return /* @__PURE__ */ import_react8.default.createElement(
3989
- import_ui_extensions8.MultiSelect,
3990
- {
3991
- ...commonProps,
3992
- value: fieldValue || [],
3993
- options,
3994
- onChange: fieldOnChange
3995
- }
4200
+ return withFieldLoadingSpinner(
4201
+ /* @__PURE__ */ import_react8.default.createElement(
4202
+ import_ui_extensions8.MultiSelect,
4203
+ {
4204
+ ...commonProps,
4205
+ value: fieldValue || [],
4206
+ options,
4207
+ onChange: fieldOnChange
4208
+ }
4209
+ ),
4210
+ isFieldLoading
3996
4211
  );
3997
4212
  case "toggle":
3998
4213
  return /* @__PURE__ */ import_react8.default.createElement(
@@ -4513,6 +4728,30 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
4513
4728
  if (layout) return renderExplicitLayout();
4514
4729
  return renderFieldSubset(visibleFields);
4515
4730
  };
4731
+ const renderCancelButton = () => {
4732
+ if (discardConfig && isDirty) {
4733
+ return /* @__PURE__ */ import_react8.default.createElement(
4734
+ import_ui_extensions8.Button,
4735
+ {
4736
+ variant: "secondary",
4737
+ disabled,
4738
+ overlay: /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Modal, { id: discardModalIdRef.current, title: discardTitle, width: "small" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.ModalBody, null, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, discardMessage)), /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.ModalFooter, null, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", onClick: closeDiscardModal }, discardCancelLabel), /* @__PURE__ */ import_react8.default.createElement(
4739
+ import_ui_extensions8.Button,
4740
+ {
4741
+ variant: "destructive",
4742
+ onClick: () => {
4743
+ closeDiscardModal();
4744
+ if (onCancel) onCancel();
4745
+ }
4746
+ },
4747
+ discardConfirmLabel
4748
+ )))
4749
+ },
4750
+ cancelButtonLabel
4751
+ );
4752
+ }
4753
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel);
4754
+ };
4516
4755
  const renderButtons = () => {
4517
4756
  if (submitPosition === "none" || formReadOnly) return null;
4518
4757
  const isLastStep = !isMultiStep || currentStep === steps.length - 1;
@@ -4526,6 +4765,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
4526
4765
  totalSteps: isMultiStep ? steps.length : 1,
4527
4766
  disabled,
4528
4767
  loading: isLoading,
4768
+ isDirty,
4529
4769
  labels: {
4530
4770
  submit: submitButtonLabel,
4531
4771
  cancel: cancelButtonLabel,
@@ -4541,7 +4781,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
4541
4781
  return renderButtonsProp(buttonContext);
4542
4782
  }
4543
4783
  if (isMultiStep) {
4544
- return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", onClick: handleBack, disabled }, backButtonLabel) : showCancel ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, " "), /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "small" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ import_react8.default.createElement(
4784
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", onClick: handleBack, disabled }, backButtonLabel) : showCancel ? renderCancelButton() : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, " "), /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "small" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ import_react8.default.createElement(
4545
4785
  import_ui_extensions8.LoadingButton,
4546
4786
  {
4547
4787
  variant: submitVariant,
@@ -4552,7 +4792,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
4552
4792
  submitButtonLabel
4553
4793
  ) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
4554
4794
  }
4555
- return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: singleStepJustify, gap: "sm" }, showCancel && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ import_react8.default.createElement(
4795
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: singleStepJustify, gap: "sm" }, showCancel && renderCancelButton(), /* @__PURE__ */ import_react8.default.createElement(
4556
4796
  import_ui_extensions8.LoadingButton,
4557
4797
  {
4558
4798
  variant: submitVariant,
@@ -4592,10 +4832,231 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2(props, ref
4592
4832
  formContent
4593
4833
  );
4594
4834
  });
4835
+ FormBuilder.displayName = "FormBuilder";
4836
+
4837
+ // src/form/hubspotSchema.js
4838
+ var coerceBool = (value) => value === true || value === "true" || value === "Yes" || value === "yes" || value === "1";
4839
+ var coerceNumber = (value) => {
4840
+ if (value === null || value === void 0 || value === "") return void 0;
4841
+ if (typeof value === "number") return value;
4842
+ const parsed = Number(value);
4843
+ return Number.isNaN(parsed) ? void 0 : parsed;
4844
+ };
4845
+ var mapPropertyOptions = (options) => {
4846
+ if (!Array.isArray(options)) return void 0;
4847
+ const mapped = options.filter((opt) => opt && opt.hidden !== true).map((opt) => {
4848
+ const result = { label: opt.label != null ? opt.label : String(opt.value), value: opt.value };
4849
+ if (opt.description != null && opt.description !== "") result.description = opt.description;
4850
+ return result;
4851
+ });
4852
+ return mapped;
4853
+ };
4854
+ var resolveFieldType = (property) => {
4855
+ const { type, fieldType } = property;
4856
+ if (type === "datetime") return "datetime";
4857
+ if (type === "date" || fieldType === "date") return "date";
4858
+ switch (fieldType) {
4859
+ case "select":
4860
+ return "select";
4861
+ // FormBuilder's radio rendering is the "radioGroup" type (native
4862
+ // ToggleGroup with toggleType="radioButtonList").
4863
+ case "radio":
4864
+ return "radioGroup";
4865
+ // HubSpot "checkbox" = multiple checkboxes over an enumeration → multiselect.
4866
+ case "checkbox":
4867
+ return "multiselect";
4868
+ case "booleancheckbox":
4869
+ return "toggle";
4870
+ case "number":
4871
+ return "number";
4872
+ case "textarea":
4873
+ return "textarea";
4874
+ case "text":
4875
+ case "phonenumber":
4876
+ return "text";
4877
+ default:
4878
+ break;
4879
+ }
4880
+ switch (type) {
4881
+ case "enumeration":
4882
+ return "select";
4883
+ case "number":
4884
+ return "number";
4885
+ case "bool":
4886
+ return "toggle";
4887
+ default:
4888
+ return "text";
4889
+ }
4890
+ };
4891
+ var isPropertyReadOnly = (property) => property.calculated === true || property.modificationMetadata && property.modificationMetadata.readOnlyValue === true;
4892
+ var resolveRequiredOverride = (requiredOverrides, name) => {
4893
+ if (!requiredOverrides) return void 0;
4894
+ if (Array.isArray(requiredOverrides)) {
4895
+ return requiredOverrides.includes(name) ? true : void 0;
4896
+ }
4897
+ if (Object.prototype.hasOwnProperty.call(requiredOverrides, name)) {
4898
+ return !!requiredOverrides[name];
4899
+ }
4900
+ return void 0;
4901
+ };
4902
+ var fieldsFromHubSpotProperties = (properties, options = {}) => {
4903
+ if (!Array.isArray(properties)) return [];
4904
+ const {
4905
+ include,
4906
+ exclude,
4907
+ overrides,
4908
+ requiredOverrides,
4909
+ includeDescriptions = false
4910
+ } = options;
4911
+ const includeSet = Array.isArray(include) ? new Set(include) : null;
4912
+ const excludeSet = Array.isArray(exclude) ? new Set(exclude) : null;
4913
+ let selected = properties.filter((property) => {
4914
+ if (!property || !property.name) return false;
4915
+ if (includeSet && !includeSet.has(property.name)) return false;
4916
+ if (excludeSet && excludeSet.has(property.name)) return false;
4917
+ if (property.hidden === true && !includeSet) return false;
4918
+ return true;
4919
+ });
4920
+ if (includeSet) {
4921
+ const order = new Map(include.map((name, idx) => [name, idx]));
4922
+ selected = [...selected].sort((a, b) => order.get(a.name) - order.get(b.name));
4923
+ }
4924
+ return selected.map((property) => {
4925
+ const type = resolveFieldType(property);
4926
+ const field = {
4927
+ name: property.name,
4928
+ type,
4929
+ label: property.label || property.name
4930
+ };
4931
+ if (includeDescriptions && property.description) {
4932
+ field.description = property.description;
4933
+ }
4934
+ if (type === "select" || type === "multiselect" || type === "radioGroup") {
4935
+ field.options = mapPropertyOptions(property.options) || [];
4936
+ }
4937
+ if (type === "toggle") {
4938
+ field.transformIn = coerceBool;
4939
+ field.transformOut = (value) => !!value;
4940
+ }
4941
+ if (type === "number") {
4942
+ field.transformIn = coerceNumber;
4943
+ }
4944
+ if (isPropertyReadOnly(property)) {
4945
+ field.readOnly = true;
4946
+ }
4947
+ const required = resolveRequiredOverride(requiredOverrides, property.name);
4948
+ if (required !== void 0) field.required = required;
4949
+ const override = overrides && overrides[property.name];
4950
+ return override ? { ...field, ...override } : field;
4951
+ });
4952
+ };
4595
4953
 
4596
4954
  // src/kanban/Kanban.jsx
4597
4955
  var import_react11 = __toESM(require("react"));
4598
4956
 
4957
+ // src/kanban/kanbanLanes.js
4958
+ var UNASSIGNED_LANE_KEY = "__unassigned";
4959
+ var UNKNOWN_STAGE_KEY = "__unknown";
4960
+ var getLaneKey = (row, swimlaneBy) => {
4961
+ const raw = typeof swimlaneBy === "function" ? swimlaneBy(row) : row == null ? void 0 : row[swimlaneBy];
4962
+ if (raw == null || raw === "") return UNASSIGNED_LANE_KEY;
4963
+ return String(raw);
4964
+ };
4965
+ var orderLaneKeys = (seenKeys, swimlaneOrder) => {
4966
+ const seen = Array.isArray(seenKeys) ? seenKeys : [];
4967
+ if (!Array.isArray(swimlaneOrder) || swimlaneOrder.length === 0) return [...seen];
4968
+ const explicit = [];
4969
+ for (const key of swimlaneOrder) {
4970
+ const normalized = String(key);
4971
+ if (!explicit.includes(normalized)) explicit.push(normalized);
4972
+ }
4973
+ const rest = seen.filter((key) => !explicit.includes(key));
4974
+ return [...explicit, ...rest];
4975
+ };
4976
+ var partitionLanes = (rows, { swimlaneBy, swimlaneOrder } = {}) => {
4977
+ const rowsByLane = {};
4978
+ const firstSeen = [];
4979
+ for (const row of rows || []) {
4980
+ const key = getLaneKey(row, swimlaneBy);
4981
+ if (!rowsByLane[key]) {
4982
+ rowsByLane[key] = [];
4983
+ firstSeen.push(key);
4984
+ }
4985
+ rowsByLane[key].push(row);
4986
+ }
4987
+ const laneKeys = orderLaneKeys(firstSeen, swimlaneOrder);
4988
+ for (const key of laneKeys) {
4989
+ if (!rowsByLane[key]) rowsByLane[key] = [];
4990
+ }
4991
+ return { laneKeys, rowsByLane };
4992
+ };
4993
+ var resolveLaneLabel = (laneKey, swimlaneLabels, rows, unassignedLabel) => {
4994
+ if (typeof swimlaneLabels === "function") {
4995
+ const out = swimlaneLabels(laneKey, rows || []);
4996
+ if (out != null) return out;
4997
+ } else if (swimlaneLabels && typeof swimlaneLabels === "object") {
4998
+ const out = swimlaneLabels[laneKey];
4999
+ if (out != null) return out;
5000
+ }
5001
+ if (laneKey === UNASSIGNED_LANE_KEY) return unassignedLabel || "Unassigned";
5002
+ return String(laneKey);
5003
+ };
5004
+ var bucketRowsByStage = (rows, stages, getStage) => {
5005
+ const map = {};
5006
+ for (const stage of stages || []) map[stage.value] = [];
5007
+ for (const row of rows || []) {
5008
+ const key = getStage(row);
5009
+ if (map[key]) {
5010
+ map[key].push(row);
5011
+ } else if ((stages || []).length > 0) {
5012
+ if (!map[UNKNOWN_STAGE_KEY]) map[UNKNOWN_STAGE_KEY] = [];
5013
+ map[UNKNOWN_STAGE_KEY].push(row);
5014
+ }
5015
+ }
5016
+ return map;
5017
+ };
5018
+ var sortBuckets = (buckets, comparator) => {
5019
+ if (!comparator) return buckets;
5020
+ const out = {};
5021
+ for (const key of Object.keys(buckets || {})) {
5022
+ out[key] = [...buckets[key]].sort(comparator);
5023
+ }
5024
+ return out;
5025
+ };
5026
+ var resolveWipLimit = (stage, wipLimits) => {
5027
+ const override = wipLimits ? wipLimits[stage == null ? void 0 : stage.value] : void 0;
5028
+ const limit = override != null ? override : stage == null ? void 0 : stage.wipLimit;
5029
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit < 0) return null;
5030
+ return limit;
5031
+ };
5032
+ var computeStageCounts = (stages, buckets, stageMeta) => {
5033
+ const counts = {};
5034
+ for (const stage of stages || []) {
5035
+ const meta = stageMeta ? stageMeta[stage.value] : void 0;
5036
+ counts[stage.value] = meta && meta.totalCount != null ? meta.totalCount : ((buckets == null ? void 0 : buckets[stage.value]) || []).length;
5037
+ }
5038
+ return counts;
5039
+ };
5040
+ var evaluateWip = (stages, counts, wipLimits) => {
5041
+ const out = {};
5042
+ for (const stage of stages || []) {
5043
+ const limit = resolveWipLimit(stage, wipLimits);
5044
+ const count = (counts == null ? void 0 : counts[stage.value]) || 0;
5045
+ out[stage.value] = { count, limit, exceeded: limit != null && count > limit };
5046
+ }
5047
+ return out;
5048
+ };
5049
+ var findNewlyExceededWip = (prev, next) => {
5050
+ const events = [];
5051
+ for (const stageId of Object.keys(next || {})) {
5052
+ const entry = next[stageId];
5053
+ if (!entry || !entry.exceeded) continue;
5054
+ if (prev && prev[stageId] && prev[stageId].exceeded) continue;
5055
+ events.push({ stageId, count: entry.count, limit: entry.limit });
5056
+ }
5057
+ return events;
5058
+ };
5059
+
4599
5060
  // src/common-components/CollectionSortSelect.js
4600
5061
  var import_react9 = __toESM(require("react"));
4601
5062
  var import_ui_extensions9 = require("@hubspot/ui-extensions");
@@ -4867,7 +5328,7 @@ var StyledText = ({
4867
5328
  const nativeVariant = NATIVE_TAG_VARIANT_ALIASES[background == null ? void 0 : background.variant] ?? (background == null ? void 0 : background.variant) ?? "default";
4868
5329
  return import_react10.default.createElement(import_ui_extensions10.Tag, { variant: nativeVariant }, resolvedText);
4869
5330
  }
4870
- const { src, width: w, height: h6 } = makeStyledTextDataUri(resolvedText, {
5331
+ const { src, width: w, height: h7 } = makeStyledTextDataUri(resolvedText, {
4871
5332
  variant,
4872
5333
  format,
4873
5334
  orientation,
@@ -4883,7 +5344,7 @@ var StyledText = ({
4883
5344
  return import_react10.default.createElement(import_ui_extensions10.Image, {
4884
5345
  src,
4885
5346
  width: w,
4886
- height: h6,
5347
+ height: h7,
4887
5348
  alt: alt ?? String(resolvedText)
4888
5349
  });
4889
5350
  };
@@ -4923,6 +5384,12 @@ var DEFAULT_LABELS3 = {
4923
5384
  errorTitle: "Something went wrong.",
4924
5385
  errorMessage: "An error occurred while loading data.",
4925
5386
  cardCount: (n) => String(n),
5387
+ // "5 / 4" — count vs WIP limit in the stage header. The slash format is the
5388
+ // standard kanban convention; override for tighter ("5/4") or verbose forms.
5389
+ wipCount: (count, limit) => `${count} / ${limit}`,
5390
+ overWip: "Over WIP",
5391
+ laneCount: (n) => String(n),
5392
+ unassignedLane: "Unassigned",
4926
5393
  moveTo: "Move",
4927
5394
  clearAll: "Clear all",
4928
5395
  selectAll: (count, label) => `Select all ${count} ${label}`,
@@ -5149,10 +5616,14 @@ var KanbanColumn = ({
5149
5616
  onToggleCollapsed,
5150
5617
  columnFooter,
5151
5618
  countDisplay,
5619
+ wip,
5620
+ compactEmpty,
5152
5621
  labels,
5153
5622
  children
5154
5623
  }) => {
5155
- const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
5624
+ const hasWipLimit = wip != null && wip.limit != null;
5625
+ const countLabel = hasWipLimit ? labels.wipCount(wip.count, wip.limit) : labels.cardCount(totalCount != null ? totalCount : bucketCount);
5626
+ const overWipNode = wip && wip.exceeded ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.StatusTag, { variant: "warning" }, labels.overWip) : null;
5156
5627
  const countNode = countDisplay === "text" ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Tag, { variant: "default" }, countLabel);
5157
5628
  if (collapsed) {
5158
5629
  const rotated = makeRotatedLabelDataUri(stage.label);
@@ -5185,8 +5656,11 @@ var KanbanColumn = ({
5185
5656
  }
5186
5657
  ) : null));
5187
5658
  }
5659
+ if (compactEmpty && !loading && rows.length === 0 && bucketCount === 0) {
5660
+ return /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Tile, { compact: true }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { variant: "microcopy", format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn)));
5661
+ }
5188
5662
  const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
5189
- return /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Tile, { compact: true }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react11.default.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Divider, null), children, error ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
5663
+ return /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Tile, { compact: true }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, overWipNode, loading ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react11.default.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Divider, null), children, error ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
5190
5664
  };
5191
5665
  var renderMetricsPanel = (metrics) => {
5192
5666
  if (!metrics) return null;
@@ -5225,14 +5699,14 @@ var KanbanToolbar = ({
5225
5699
  sortOptions,
5226
5700
  sortValue,
5227
5701
  onSortChange,
5228
- metrics,
5229
- showMetrics,
5702
+ showMetricsButton,
5703
+ metricsPanel,
5230
5704
  onToggleMetrics,
5231
5705
  labels,
5232
5706
  toolbarLeftFlex,
5233
5707
  toolbarRightFlex
5234
5708
  }) => {
5235
- const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react11.default.createElement(
5709
+ const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || showMetricsButton ? /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react11.default.createElement(
5236
5710
  CollectionSortSelect,
5237
5711
  {
5238
5712
  name: "kanban-sort",
@@ -5241,7 +5715,7 @@ var KanbanToolbar = ({
5241
5715
  options: sortOptions,
5242
5716
  onChange: onSortChange
5243
5717
  }
5244
- ) : null, metrics ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react11.default.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
5718
+ ) : null, showMetricsButton ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react11.default.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
5245
5719
  return /* @__PURE__ */ import_react11.default.createElement(
5246
5720
  CollectionToolbar,
5247
5721
  {
@@ -5268,7 +5742,7 @@ var KanbanToolbar = ({
5268
5742
  onRemove: onFilterRemove
5269
5743
  },
5270
5744
  right: rightControls,
5271
- footer: showMetrics && metrics ? renderMetricsPanel(metrics) : null,
5745
+ footer: metricsPanel,
5272
5746
  labels,
5273
5747
  leftFlex: toolbarLeftFlex,
5274
5748
  rightFlex: toolbarRightFlex
@@ -5320,6 +5794,18 @@ var Kanban = ({
5320
5794
  // --- Per-stage pagination ---
5321
5795
  stageMeta,
5322
5796
  onLoadMore,
5797
+ // --- WIP limits ---
5798
+ wipLimits,
5799
+ onWipExceeded,
5800
+ // --- Swimlanes ---
5801
+ swimlaneBy,
5802
+ swimlaneLabels,
5803
+ swimlaneOrder,
5804
+ collapseLanes = true,
5805
+ collapsedLanes,
5806
+ defaultCollapsedLanes,
5807
+ onCollapsedLanesChange,
5808
+ metricsPerLane = false,
5323
5809
  // --- Selection ---
5324
5810
  selectable = false,
5325
5811
  selectedIds,
@@ -5384,6 +5870,9 @@ var Kanban = ({
5384
5870
  const [internalFilters, setInternalFilters] = (0, import_react11.useState)(() => getEmptyFilterValues(filters));
5385
5871
  const [internalSort, setInternalSort] = (0, import_react11.useState)(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
5386
5872
  const [internalCollapsed, setInternalCollapsed] = (0, import_react11.useState)([]);
5873
+ const [internalCollapsedLanes, setInternalCollapsedLanes] = (0, import_react11.useState)(
5874
+ () => defaultCollapsedLanes || []
5875
+ );
5387
5876
  const [internalExpanded, setInternalExpanded] = (0, import_react11.useState)([]);
5388
5877
  const [internalSelection, setInternalSelection] = (0, import_react11.useState)([]);
5389
5878
  const [internalShowMetrics, setInternalShowMetrics] = (0, import_react11.useState)(false);
@@ -5400,6 +5889,7 @@ var Kanban = ({
5400
5889
  const resolvedFilters = filterValues != null ? filterValues : internalFilters;
5401
5890
  const resolvedSort = sort != null ? sort : internalSort;
5402
5891
  const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
5892
+ const resolvedCollapsedLanes = collapsedLanes != null ? collapsedLanes : internalCollapsedLanes;
5403
5893
  const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
5404
5894
  const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
5405
5895
  const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
@@ -5416,9 +5906,10 @@ var Kanban = ({
5416
5906
  search: overrides.search != null ? overrides.search : resolvedSearch,
5417
5907
  filters: overrides.filters != null ? overrides.filters : resolvedFilters,
5418
5908
  sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
5419
- collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
5909
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed,
5910
+ collapsedLanes: overrides.collapsedLanes != null ? overrides.collapsedLanes : resolvedCollapsedLanes
5420
5911
  });
5421
- }, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
5912
+ }, [onParamsChange, resolvedCollapsed, resolvedCollapsedLanes, resolvedFilters, resolvedSearch, resolvedSort]);
5422
5913
  const lastAppliedSearchRef = (0, import_react11.useRef)(searchValue != null ? searchValue : "");
5423
5914
  (0, import_react11.useEffect)(() => {
5424
5915
  if (searchValue == null) return;
@@ -5477,6 +5968,15 @@ var Kanban = ({
5477
5968
  },
5478
5969
  [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
5479
5970
  );
5971
+ const handleLaneCollapsed = (0, import_react11.useCallback)(
5972
+ (laneKey) => {
5973
+ const next = resolvedCollapsedLanes.includes(laneKey) ? resolvedCollapsedLanes.filter((k) => k !== laneKey) : [...resolvedCollapsedLanes, laneKey];
5974
+ if (onCollapsedLanesChange) onCollapsedLanesChange(next);
5975
+ if (collapsedLanes == null) setInternalCollapsedLanes(next);
5976
+ fireParamsChange({ collapsedLanes: next });
5977
+ },
5978
+ [fireParamsChange, resolvedCollapsedLanes, collapsedLanes, onCollapsedLanesChange]
5979
+ );
5480
5980
  const handleExpanded = (0, import_react11.useCallback)(
5481
5981
  (stageValue) => {
5482
5982
  const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
@@ -5545,33 +6045,45 @@ var Kanban = ({
5545
6045
  }
5546
6046
  return result;
5547
6047
  }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
5548
- const buckets = (0, import_react11.useMemo)(() => {
5549
- const map = {};
5550
- for (const stage of stages) map[stage.value] = [];
5551
- for (const row of filteredData) {
5552
- const key = getStageFor(row);
5553
- if (map[key]) {
5554
- map[key].push(row);
5555
- } else if (stages.length > 0) {
5556
- if (!map.__unknown) map.__unknown = [];
5557
- map.__unknown.push(row);
5558
- }
5559
- }
5560
- return map;
5561
- }, [filteredData, stages, getStageFor]);
6048
+ const buckets = (0, import_react11.useMemo)(
6049
+ () => bucketRowsByStage(filteredData, stages, getStageFor),
6050
+ [filteredData, stages, getStageFor]
6051
+ );
5562
6052
  const sortComparator = (0, import_react11.useMemo)(() => {
5563
6053
  if (!sortOptions || !resolvedSort) return null;
5564
6054
  const opt = sortOptions.find((s) => s.value === resolvedSort);
5565
6055
  return (opt == null ? void 0 : opt.comparator) || null;
5566
6056
  }, [sortOptions, resolvedSort]);
5567
- const sortedBuckets = (0, import_react11.useMemo)(() => {
5568
- if (!sortComparator) return buckets;
6057
+ const sortedBuckets = (0, import_react11.useMemo)(() => sortBuckets(buckets, sortComparator), [buckets, sortComparator]);
6058
+ const hasLanes = swimlaneBy != null;
6059
+ const laneData = (0, import_react11.useMemo)(() => {
6060
+ if (!hasLanes) return null;
6061
+ return partitionLanes(filteredData, { swimlaneBy, swimlaneOrder });
6062
+ }, [hasLanes, filteredData, swimlaneBy, swimlaneOrder]);
6063
+ const laneBuckets = (0, import_react11.useMemo)(() => {
6064
+ if (!laneData) return null;
5569
6065
  const out = {};
5570
- for (const key of Object.keys(buckets)) {
5571
- out[key] = [...buckets[key]].sort(sortComparator);
6066
+ for (const laneKey of laneData.laneKeys) {
6067
+ out[laneKey] = sortBuckets(
6068
+ bucketRowsByStage(laneData.rowsByLane[laneKey] || [], stages, getStageFor),
6069
+ sortComparator
6070
+ );
5572
6071
  }
5573
6072
  return out;
5574
- }, [buckets, sortComparator]);
6073
+ }, [laneData, stages, getStageFor, sortComparator]);
6074
+ const wipByStage = (0, import_react11.useMemo)(
6075
+ () => evaluateWip(stages, computeStageCounts(stages, buckets, stageMeta), wipLimits),
6076
+ [stages, buckets, stageMeta, wipLimits]
6077
+ );
6078
+ const prevWipRef = (0, import_react11.useRef)({});
6079
+ (0, import_react11.useEffect)(() => {
6080
+ const newlyExceeded = findNewlyExceededWip(prevWipRef.current, wipByStage);
6081
+ prevWipRef.current = wipByStage;
6082
+ if (!onWipExceeded) return;
6083
+ for (const event of newlyExceeded) {
6084
+ onWipExceeded(event.stageId, event.count, event.limit);
6085
+ }
6086
+ }, [wipByStage, onWipExceeded]);
5575
6087
  const activeChips = (0, import_react11.useMemo)(
5576
6088
  () => buildActiveFilterChips(filters, resolvedFilters),
5577
6089
  [filters, resolvedFilters]
@@ -5698,6 +6210,52 @@ var Kanban = ({
5698
6210
  selectionActions: selectionActions || [],
5699
6211
  labels
5700
6212
  };
6213
+ const metricsProvided = metrics != null && (!Array.isArray(metrics) || metrics.length > 0);
6214
+ const perLaneMetricsActive = hasLanes && metricsPerLane && typeof metrics === "function";
6215
+ const globalMetricsContent = metricsProvided && !perLaneMetricsActive ? typeof metrics === "function" ? metrics(filteredData, null) : metrics : null;
6216
+ const renderStageColumns = (bucketMap, laneKey) => {
6217
+ const inLane = laneKey != null;
6218
+ return /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
6219
+ const stageRows = bucketMap[stage.value] || [];
6220
+ const meta = inLane ? void 0 : stageMeta == null ? void 0 : stageMeta[stage.value];
6221
+ const isExpanded = resolvedExpanded.includes(stage.value);
6222
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
6223
+ const visibleRows = stageRows.slice(0, clamp);
6224
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
6225
+ const stageWip = wipByStage[stage.value];
6226
+ const wip = inLane ? (stageWip == null ? void 0 : stageWip.exceeded) ? { count: stageWip.count, limit: null, exceeded: true } : null : stageWip;
6227
+ return /* @__PURE__ */ import_react11.default.createElement(
6228
+ import_ui_extensions11.AutoGrid,
6229
+ {
6230
+ key: inLane ? `${laneKey}::${stage.value}` : stage.value,
6231
+ columnWidth: isCollapsed ? 72 : effectiveColumnWidth
6232
+ },
6233
+ /* @__PURE__ */ import_react11.default.createElement(
6234
+ KanbanColumn,
6235
+ {
6236
+ stage,
6237
+ rows: visibleRows,
6238
+ bucketCount: stageRows.length,
6239
+ totalCount: meta == null ? void 0 : meta.totalCount,
6240
+ hasMore: meta == null ? void 0 : meta.hasMore,
6241
+ loading: meta == null ? void 0 : meta.loading,
6242
+ error: meta == null ? void 0 : meta.error,
6243
+ onLoadMore: inLane ? void 0 : onLoadMore,
6244
+ expanded: isExpanded,
6245
+ onToggleExpanded: () => handleExpanded(stage.value),
6246
+ collapsed: isCollapsed,
6247
+ onToggleCollapsed: () => handleCollapsed(stage.value),
6248
+ columnFooter,
6249
+ countDisplay,
6250
+ wip,
6251
+ compactEmpty: inLane,
6252
+ labels
6253
+ },
6254
+ visibleRows.map((row) => renderCardNode(row, stage))
6255
+ )
6256
+ );
6257
+ }));
6258
+ };
5701
6259
  const mainContent = error ? renderErrorState ? renderErrorState({
5702
6260
  error,
5703
6261
  title: labels.errorTitle,
@@ -5709,35 +6267,32 @@ var Kanban = ({
5709
6267
  ) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
5710
6268
  title: labels.emptyTitle,
5711
6269
  message: labels.emptyMessage
5712
- }) : /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Tile, null, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, null, labels.emptyMessage)))) : /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
5713
- const stageRows = sortedBuckets[stage.value] || [];
5714
- const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
5715
- const isExpanded = resolvedExpanded.includes(stage.value);
5716
- const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
5717
- const visibleRows = stageRows.slice(0, clamp);
5718
- const isCollapsed = resolvedCollapsed.includes(stage.value);
5719
- return /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.AutoGrid, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ import_react11.default.createElement(
5720
- KanbanColumn,
6270
+ }) : /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Tile, null, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, null, labels.emptyMessage)))) : hasLanes ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "column", gap: "md" }, ((laneData == null ? void 0 : laneData.laneKeys) || []).map((laneKey, laneIndex) => {
6271
+ const laneRows = laneData.rowsByLane[laneKey] || [];
6272
+ const laneLabel = resolveLaneLabel(laneKey, swimlaneLabels, laneRows, labels.unassignedLane);
6273
+ const laneLabelText = typeof laneLabel === "string" ? laneLabel : String(laneKey);
6274
+ const isLaneCollapsed = collapseLanes && resolvedCollapsedLanes.includes(laneKey);
6275
+ const laneCountLabel = labels.laneCount(laneRows.length);
6276
+ const laneCountNode = countDisplay === "none" ? null : countDisplay === "text" ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { format: { fontWeight: "demibold" } }, laneCountLabel) : /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Tag, { variant: "default" }, laneCountLabel);
6277
+ const laneMetricsNode = !isLaneCollapsed && perLaneMetricsActive && resolvedShowMetrics ? renderMetricsPanel(metrics(laneRows, laneKey)) : null;
6278
+ return /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { key: laneKey, direction: "column", gap: "xs" }, laneIndex > 0 ? /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Divider, null) : null, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "row", align: "center", gap: "xs" }, collapseLanes ? /* @__PURE__ */ import_react11.default.createElement(
6279
+ import_ui_extensions11.Button,
5721
6280
  {
5722
- stage,
5723
- rows: visibleRows,
5724
- bucketCount: stageRows.length,
5725
- totalCount: meta == null ? void 0 : meta.totalCount,
5726
- hasMore: meta == null ? void 0 : meta.hasMore,
5727
- loading: meta == null ? void 0 : meta.loading,
5728
- error: meta == null ? void 0 : meta.error,
5729
- onLoadMore,
5730
- expanded: isExpanded,
5731
- onToggleExpanded: () => handleExpanded(stage.value),
5732
- collapsed: isCollapsed,
5733
- onToggleCollapsed: () => handleCollapsed(stage.value),
5734
- columnFooter,
5735
- countDisplay,
5736
- labels
6281
+ variant: "transparent",
6282
+ size: "sm",
6283
+ onClick: () => handleLaneCollapsed(laneKey),
6284
+ tooltip: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
5737
6285
  },
5738
- visibleRows.map((row) => renderCardNode(row, stage))
5739
- ));
5740
- }));
6286
+ /* @__PURE__ */ import_react11.default.createElement(
6287
+ Icon,
6288
+ {
6289
+ name: isLaneCollapsed ? "right" : "down",
6290
+ size: "sm",
6291
+ screenReaderText: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
6292
+ }
6293
+ )
6294
+ ) : null, /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Text, { format: { fontWeight: "demibold" } }, laneLabel), laneCountNode), laneMetricsNode, !isLaneCollapsed ? renderStageColumns((laneBuckets == null ? void 0 : laneBuckets[laneKey]) || {}, laneKey) : null);
6295
+ })) : renderStageColumns(sortedBuckets, null);
5741
6296
  const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
5742
6297
  return /* @__PURE__ */ import_react11.default.createElement(import_ui_extensions11.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react11.default.createElement(
5743
6298
  KanbanToolbar,
@@ -5757,8 +6312,8 @@ var Kanban = ({
5757
6312
  sortOptions,
5758
6313
  sortValue: resolvedSort,
5759
6314
  onSortChange: handleSort,
5760
- metrics,
5761
- showMetrics: resolvedShowMetrics,
6315
+ showMetricsButton: metricsProvided,
6316
+ metricsPanel: resolvedShowMetrics && globalMetricsContent ? renderMetricsPanel(globalMetricsContent) : null,
5762
6317
  onToggleMetrics: toggleMetrics,
5763
6318
  labels,
5764
6319
  toolbarLeftFlex,
@@ -5766,6 +6321,7 @@ var Kanban = ({
5766
6321
  }
5767
6322
  ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ import_react11.default.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
5768
6323
  };
6324
+ Kanban.displayName = "Kanban";
5769
6325
 
5770
6326
  // src/kanban/KanbanCardActions.jsx
5771
6327
  var import_react12 = __toESM(require("react"));
@@ -5967,6 +6523,110 @@ var AvatarStack = ({
5967
6523
  });
5968
6524
  };
5969
6525
 
6526
+ // src/feed/feedLiveBuffer.js
6527
+ var isDateValueObject2 = (v) => v != null && typeof v === "object" && typeof v.year === "number" && typeof v.month === "number" && typeof v.date === "number";
6528
+ var toTimestampMs = (value) => {
6529
+ if (value == null || value === "") return null;
6530
+ if (value instanceof Date) {
6531
+ const time2 = value.getTime();
6532
+ return Number.isNaN(time2) ? null : time2;
6533
+ }
6534
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
6535
+ if (isDateValueObject2(value)) {
6536
+ return new Date(value.year, value.month, value.date, value.hour || 0, value.minute || 0).getTime();
6537
+ }
6538
+ const parsed = new Date(value);
6539
+ const time = parsed.getTime();
6540
+ return Number.isNaN(time) ? null : time;
6541
+ };
6542
+ var defaultGetId = (item, index) => (item == null ? void 0 : item.id) ?? (item == null ? void 0 : item.key) ?? index;
6543
+ var partitionNewItems = (prevNewestTs, items, getTs, options = {}) => {
6544
+ const { knownIds = null, getId = defaultGetId } = options;
6545
+ const safeItems = Array.isArray(items) ? items : [];
6546
+ const firstLoad = prevNewestTs == null;
6547
+ const visible = [];
6548
+ const visibleIds = [];
6549
+ const buffered = [];
6550
+ const bufferedIds = [];
6551
+ let newestTs = typeof prevNewestTs === "number" ? prevNewestTs : null;
6552
+ safeItems.forEach((item, index) => {
6553
+ const id = getId(item, index);
6554
+ const ts = toTimestampMs(typeof getTs === "function" ? getTs(item) : void 0);
6555
+ const isKnown = knownIds != null && id !== void 0 && knownIds.has(id);
6556
+ const isNewArrival = !firstLoad && !isKnown && ts != null && ts > prevNewestTs;
6557
+ if (isNewArrival) {
6558
+ buffered.push(item);
6559
+ bufferedIds.push(id);
6560
+ return;
6561
+ }
6562
+ visible.push(item);
6563
+ visibleIds.push(id);
6564
+ if (ts != null && (newestTs == null || ts > newestTs)) newestTs = ts;
6565
+ });
6566
+ return { visible, buffered, visibleIds, bufferedIds, newestTs };
6567
+ };
6568
+ var flushBuffer = (visible, buffered, getTs) => {
6569
+ const safeVisible = Array.isArray(visible) ? visible : [];
6570
+ const safeBuffered = Array.isArray(buffered) ? buffered : [];
6571
+ const items = [...safeBuffered, ...safeVisible];
6572
+ let newestTs = null;
6573
+ items.forEach((item) => {
6574
+ const ts = toTimestampMs(typeof getTs === "function" ? getTs(item) : void 0);
6575
+ if (ts != null && (newestTs == null || ts > newestTs)) newestTs = ts;
6576
+ });
6577
+ return { items, flushed: safeBuffered, newestTs };
6578
+ };
6579
+
6580
+ // src/feed/feedTypePresets.js
6581
+ var hasValue = (value) => value != null && value !== false && value !== "";
6582
+ var DEFAULT_FEED_TYPE_PRESETS = {
6583
+ call: { icon: "calling", label: "Call" },
6584
+ email: { icon: "email", label: "Email" },
6585
+ incoming_email: { icon: "inbox", label: "Incoming email" },
6586
+ forwarded_email: { icon: "forward", label: "Forwarded email" },
6587
+ meeting: { icon: "appointment", label: "Meeting" },
6588
+ note: { icon: "comment", label: "Note" },
6589
+ task: { icon: "tasks", label: "Task" },
6590
+ sms: { icon: "messages", label: "SMS" },
6591
+ whatsapp: { icon: "messages", label: "WhatsApp" },
6592
+ linkedin_message: { icon: "linkedin", label: "LinkedIn message" },
6593
+ postal_mail: { icon: "send", label: "Postal mail" },
6594
+ conversation: { icon: "questionAnswer", label: "Conversation" }
6595
+ };
6596
+ var lookupTypePreset = (type, presets) => {
6597
+ if (!presets || typeof presets !== "object") return null;
6598
+ if (type == null || type === "") return null;
6599
+ if (Object.prototype.hasOwnProperty.call(presets, type)) return presets[type];
6600
+ const lower = String(type).toLowerCase();
6601
+ if (Object.prototype.hasOwnProperty.call(presets, lower)) return presets[lower];
6602
+ const snake = lower.replace(/[\s-]+/g, "_");
6603
+ if (Object.prototype.hasOwnProperty.call(presets, snake)) return presets[snake];
6604
+ return null;
6605
+ };
6606
+ var applyTypePreset = (item, typePresets) => {
6607
+ if (item == null || typeof item !== "object") return item;
6608
+ const preset = lookupTypePreset(item.type, typePresets);
6609
+ if (!preset || typeof preset !== "object") return item;
6610
+ let next = null;
6611
+ const fill = (key, value) => {
6612
+ if (next === null) next = { ...item };
6613
+ next[key] = value;
6614
+ };
6615
+ if (!hasValue(item.icon) && !hasValue(item.iconName) && hasValue(preset.icon)) {
6616
+ fill("iconName", preset.icon);
6617
+ }
6618
+ if (!hasValue(item.iconColor) && hasValue(preset.color)) {
6619
+ fill("iconColor", preset.color);
6620
+ }
6621
+ if (!hasValue(item.typeLabel) && hasValue(preset.label)) {
6622
+ fill("typeLabel", preset.label);
6623
+ }
6624
+ if (item.statusVariant == null && item.outcomeVariant == null && item.severityVariant == null && hasValue(preset.statusVariant)) {
6625
+ fill("statusVariant", preset.statusVariant);
6626
+ }
6627
+ return next ?? item;
6628
+ };
6629
+
5970
6630
  // src/feed/Feed.jsx
5971
6631
  var DEFAULT_LABELS4 = {
5972
6632
  search: "Search activity...",
@@ -5980,6 +6640,8 @@ var DEFAULT_LABELS4 = {
5980
6640
  loadingMessage: "This should only take a moment.",
5981
6641
  loadingMore: "Loading...",
5982
6642
  loadMore: "View more",
6643
+ newItems: (count) => count === 1 ? "Show 1 new item" : `Show ${count} new items`,
6644
+ newItemTag: "New",
5983
6645
  collapseAll: "Collapse all",
5984
6646
  expandAll: "Expand all",
5985
6647
  emptyTitle: "No activity yet",
@@ -5992,7 +6654,15 @@ var DEFAULT_LABELS4 = {
5992
6654
  var DEFAULT_RECORD_LABEL = { singular: "item", plural: "items" };
5993
6655
  var DEFAULT_SEARCH_FIELDS = ["title", "subject", "body", "description", "content", "preview", "type", "typeLabel", "actorName", "author"];
5994
6656
  var DEFAULT_PAGE_SIZE = 5;
5995
- var hasValue = (value) => value != null && value !== false && value !== "";
6657
+ var EMPTY_ITEMS = [];
6658
+ var INITIAL_LIVE_STATE = {
6659
+ source: null,
6660
+ watermark: null,
6661
+ bufferedKeys: EMPTY_ITEMS,
6662
+ knownKeys: EMPTY_ITEMS,
6663
+ newKeys: EMPTY_ITEMS
6664
+ };
6665
+ var hasValue2 = (value) => value != null && value !== false && value !== "";
5996
6666
  var keepWordsTogether = (value) => typeof value === "string" ? value.replace(/\s+/g, "\xA0") : value;
5997
6667
  var getItemKey = (item, index, getKey) => {
5998
6668
  if (typeof getKey === "function") return getKey(item, index);
@@ -6019,11 +6689,11 @@ var pickHeaderActions = (item) => item == null ? void 0 : item.headerActions;
6019
6689
  var itemHasExpandableContent = (item, fields) => {
6020
6690
  if ((item == null ? void 0 : item.collapsible) === false) return false;
6021
6691
  if ((item == null ? void 0 : item.collapsible) === true) return true;
6022
- if (hasValue(pickBody(item))) return true;
6023
- if (hasValue(pickActor(item))) return true;
6024
- if (hasValue(item == null ? void 0 : item.actions)) return true;
6025
- if (hasValue(item == null ? void 0 : item.footer)) return true;
6026
- if (hasValue(item == null ? void 0 : item.meta) || hasValue(item == null ? void 0 : item.metadata)) return true;
6692
+ if (hasValue2(pickBody(item))) return true;
6693
+ if (hasValue2(pickActor(item))) return true;
6694
+ if (hasValue2(item == null ? void 0 : item.actions)) return true;
6695
+ if (hasValue2(item == null ? void 0 : item.footer)) return true;
6696
+ if (hasValue2(item == null ? void 0 : item.meta) || hasValue2(item == null ? void 0 : item.metadata)) return true;
6027
6697
  if (Array.isArray(fields)) {
6028
6698
  if (fields.some((f) => ["body", "footer"].includes(f.placement ?? "body"))) return true;
6029
6699
  }
@@ -6105,7 +6775,7 @@ var getRecordLabel = (recordLabel, count) => {
6105
6775
  var FeedActorAvatar = ({ item, avatarSize }) => {
6106
6776
  const actor = pickActor(item);
6107
6777
  const avatar = (item == null ? void 0 : item.avatar) ?? pickActorAvatar(actor);
6108
- if (!hasValue(avatar)) return null;
6778
+ if (!hasValue2(avatar)) return null;
6109
6779
  return /* @__PURE__ */ import_react14.default.createElement(
6110
6780
  AvatarStack,
6111
6781
  {
@@ -6117,10 +6787,10 @@ var FeedActorAvatar = ({ item, avatarSize }) => {
6117
6787
  );
6118
6788
  };
6119
6789
  var FeedTypeIcon = ({ item, iconSize }) => {
6120
- if (hasValue(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
6790
+ if (hasValue2(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
6121
6791
  const iconName = typeof (item == null ? void 0 : item.icon) === "string" ? item.icon : item == null ? void 0 : item.iconName;
6122
- if (!hasValue(iconName)) return null;
6123
- return /* @__PURE__ */ import_react14.default.createElement(Icon, { name: iconName, size: iconSize, purpose: "decorative" });
6792
+ if (!hasValue2(iconName)) return null;
6793
+ return /* @__PURE__ */ import_react14.default.createElement(Icon, { name: iconName, size: iconSize, color: item == null ? void 0 : item.iconColor, purpose: "decorative" });
6124
6794
  };
6125
6795
  var FeedActions = ({ actions }) => {
6126
6796
  if (!Array.isArray(actions) || actions.length === 0) return actions || null;
@@ -6143,7 +6813,7 @@ var FeedField = ({ field, item, index }) => {
6143
6813
  if (field.visible && !field.visible(item)) return null;
6144
6814
  const value = getValue(item, field.field);
6145
6815
  const rendered = field.render ? field.render(value, item, index) : value;
6146
- if (!hasValue(rendered)) return null;
6816
+ if (!hasValue2(rendered)) return null;
6147
6817
  if (field.href) {
6148
6818
  const href = typeof field.href === "function" ? field.href(item) : field.href;
6149
6819
  return /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Link, { href }, rendered);
@@ -6180,7 +6850,7 @@ var renderPlacedFields = ({ fields, placement, item, index, inline = false }) =>
6180
6850
  return /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "column", gap: "xs" }, nodes);
6181
6851
  };
6182
6852
  var renderHeaderActions = (headerActions) => {
6183
- if (!hasValue(headerActions)) return null;
6853
+ if (!hasValue2(headerActions)) return null;
6184
6854
  if (!Array.isArray(headerActions)) return headerActions;
6185
6855
  return /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "sm", align: "center" }, headerActions.filter(Boolean).map((action, index) => /* @__PURE__ */ import_react14.default.createElement(
6186
6856
  import_ui_extensions14.Link,
@@ -6202,6 +6872,8 @@ var DefaultFeedItem = ({
6202
6872
  collapsible,
6203
6873
  expanded,
6204
6874
  onToggleExpanded,
6875
+ isNew,
6876
+ newItemTagLabel,
6205
6877
  renderActor,
6206
6878
  renderTimestamp,
6207
6879
  renderMeta,
@@ -6221,7 +6893,7 @@ var DefaultFeedItem = ({
6221
6893
  const body = pickBody(item);
6222
6894
  const avatar = /* @__PURE__ */ import_react14.default.createElement(FeedActorAvatar, { item, avatarSize });
6223
6895
  const typeIcon = /* @__PURE__ */ import_react14.default.createElement(FeedTypeIcon, { item, iconSize });
6224
- const hasAvatarNode = hasValue(item == null ? void 0 : item.avatar) || hasValue(pickActorAvatar(rawActor));
6896
+ const hasAvatarNode = hasValue2(item == null ? void 0 : item.avatar) || hasValue2(pickActorAvatar(rawActor));
6225
6897
  const titleFields = fieldsForPlacement(fields, "title");
6226
6898
  const titleField = titleFields.length > 0 ? /* @__PURE__ */ import_react14.default.createElement(FeedField, { field: titleFields[0], item, index }) : null;
6227
6899
  const subtitleFields = renderPlacedFields({ fields, placement: "subtitle", item, index, inline: true });
@@ -6229,8 +6901,8 @@ var DefaultFeedItem = ({
6229
6901
  const bodyFields = renderPlacedFields({ fields, placement: "body", item, index });
6230
6902
  const footerFields = renderPlacedFields({ fields, placement: "footer", item, index, inline: true });
6231
6903
  const titleContent = titleField ?? (item == null ? void 0 : item.title) ?? (item == null ? void 0 : item.subject);
6232
- const title = hasValue(item == null ? void 0 : item.href) ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Link, { href: item.href }, titleContent) : titleContent;
6233
- const titleText = hasValue(title) ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { format: { fontWeight: "demibold" }, truncate: true }, title) : null;
6904
+ const title = hasValue2(item == null ? void 0 : item.href) ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Link, { href: item.href }, titleContent) : titleContent;
6905
+ const titleText = hasValue2(title) ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { format: { fontWeight: "demibold" }, truncate: true }, title) : null;
6234
6906
  const headerLeft = /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, collapsible ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Box, { flex: "none", alignSelf: "center" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Link, { variant: "dark", onClick: onToggleExpanded }, /* @__PURE__ */ import_react14.default.createElement(
6235
6907
  Icon,
6236
6908
  {
@@ -6238,10 +6910,10 @@ var DefaultFeedItem = ({
6238
6910
  size: "md",
6239
6911
  screenReaderText: expanded ? "Collapse" : "Expand"
6240
6912
  }
6241
- ))) : null, typeIcon, titleText);
6242
- const headerRight = hasValue(headerActions) || hasValue(timestamp) || hasValue(type) ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue(type) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { variant: "microcopy" }, type), hasValue(timestamp) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
6913
+ ))) : null, typeIcon, titleText, isNew ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Box, { flex: "none" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Tag, { variant: "info" }, newItemTagLabel ?? "New")) : null);
6914
+ const headerRight = hasValue2(headerActions) || hasValue2(timestamp) || hasValue2(type) ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue2(type) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { variant: "microcopy" }, type), hasValue2(timestamp) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
6243
6915
  const showBody = !collapsible || expanded;
6244
- return /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Box, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue(actor) || hasValue(subtitleFields) || hasValue(status)) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue(actor) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { variant: "microcopy" }, actor), subtitleFields, hasValue(status) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.StatusTag, { variant: statusVariant, hollow: true }, status)), hasValue(body) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.List, { variant: "inline-divided" }, meta) : metaFields : hasValue(meta) ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue(actions) && /* @__PURE__ */ import_react14.default.createElement(FeedActions, { actions }), (hasValue(footer) || hasValue(footerFields)) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "xs", align: "center" }, footerFields, footer)) : null);
6916
+ return /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Box, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue2(actor) || hasValue2(subtitleFields) || hasValue2(status)) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue2(actor) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { variant: "microcopy" }, actor), subtitleFields, hasValue2(status) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.StatusTag, { variant: statusVariant, hollow: true }, status)), hasValue2(body) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.List, { variant: "inline-divided" }, meta) : metaFields : hasValue2(meta) ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue2(actions) && /* @__PURE__ */ import_react14.default.createElement(FeedActions, { actions }), (hasValue2(footer) || hasValue2(footerFields)) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "xs", align: "center" }, footerFields, footer)) : null);
6245
6917
  };
6246
6918
  var applyTab = (items, activeTab, tabField) => {
6247
6919
  if (!activeTab || activeTab === "all") return items;
@@ -6465,10 +7137,19 @@ var Feed = ({
6465
7137
  collapsedIds,
6466
7138
  onCollapsedIdsChange,
6467
7139
  showCollapseToggle = true,
6468
- alignToolbarWithGroups = "auto"
7140
+ alignToolbarWithGroups = "auto",
7141
+ newItemsBehavior = "immediate",
7142
+ onNewItemsFlush,
7143
+ highlightNew = false,
7144
+ typePresets
6469
7145
  }) => {
6470
- const labels = { ...DEFAULT_LABELS4, ...labelOverrides || {} };
6471
- const safeItems = Array.isArray(items) ? items : [];
7146
+ const labels = (0, import_react14.useMemo)(() => ({ ...DEFAULT_LABELS4, ...labelOverrides || {} }), [labelOverrides]);
7147
+ const safeItems = Array.isArray(items) ? items : EMPTY_ITEMS;
7148
+ const resolvedTypePresets = typePresets === true ? DEFAULT_FEED_TYPE_PRESETS : typePresets;
7149
+ const presetItems = (0, import_react14.useMemo)(
7150
+ () => resolvedTypePresets ? safeItems.map((item) => applyTypePreset(item, resolvedTypePresets)) : safeItems,
7151
+ [safeItems, resolvedTypePresets]
7152
+ );
6472
7153
  const [internalTab, setInternalTab] = (0, import_react14.useState)(tabValue ?? defaultTab ?? "all");
6473
7154
  const [internalSearch, setInternalSearch] = (0, import_react14.useState)(searchValue ?? "");
6474
7155
  const [internalFilters, setInternalFilters] = (0, import_react14.useState)(filterValues ?? defaultFilterValues);
@@ -6481,6 +7162,47 @@ var Feed = ({
6481
7162
  return [];
6482
7163
  };
6483
7164
  const [internalCollapsedIds, setInternalCollapsedIds] = (0, import_react14.useState)(computeInitialCollapsed);
7165
+ const bufferNewItems = newItemsBehavior === "pill";
7166
+ const highlightMs = typeof highlightNew === "number" && highlightNew > 0 ? highlightNew : 0;
7167
+ const trackNewItems = bufferNewItems || highlightMs > 0;
7168
+ const [liveState, setLiveState] = (0, import_react14.useState)(INITIAL_LIVE_STATE);
7169
+ const liveKeyOf = (item, index) => getItemKey(item, index, getKey);
7170
+ if (trackNewItems && liveState.source !== presetItems) {
7171
+ const firstLoad = liveState.source === null;
7172
+ const partition = partitionNewItems(liveState.watermark, presetItems, pickTimestamp, {
7173
+ knownIds: new Set(liveState.knownKeys),
7174
+ getId: liveKeyOf
7175
+ });
7176
+ const now = Date.now();
7177
+ const keptNewKeys = highlightMs > 0 ? liveState.newKeys.filter((entry) => now - entry.at < highlightMs) : EMPTY_ITEMS;
7178
+ if (bufferNewItems) {
7179
+ setLiveState({
7180
+ source: presetItems,
7181
+ watermark: partition.newestTs,
7182
+ bufferedKeys: partition.bufferedIds,
7183
+ knownKeys: partition.visibleIds,
7184
+ newKeys: keptNewKeys
7185
+ });
7186
+ } else {
7187
+ const { newestTs } = flushBuffer(partition.visible, partition.buffered, pickTimestamp);
7188
+ setLiveState({
7189
+ source: presetItems,
7190
+ watermark: newestTs ?? partition.newestTs,
7191
+ bufferedKeys: EMPTY_ITEMS,
7192
+ knownKeys: [...partition.visibleIds, ...partition.bufferedIds],
7193
+ newKeys: highlightMs > 0 && !firstLoad ? [...keptNewKeys, ...partition.bufferedIds.map((key) => ({ key, at: now }))] : keptNewKeys
7194
+ });
7195
+ }
7196
+ }
7197
+ const bufferedKeySet = (0, import_react14.useMemo)(() => new Set(liveState.bufferedKeys), [liveState.bufferedKeys]);
7198
+ const newKeySet = (0, import_react14.useMemo)(
7199
+ () => new Set(liveState.newKeys.map((entry) => entry.key)),
7200
+ [liveState.newKeys]
7201
+ );
7202
+ const sourceItems = (0, import_react14.useMemo)(() => {
7203
+ if (!bufferNewItems || bufferedKeySet.size === 0) return presetItems;
7204
+ return presetItems.filter((item, index) => !bufferedKeySet.has(getItemKey(item, index, getKey)));
7205
+ }, [presetItems, bufferNewItems, bufferedKeySet, getKey]);
6484
7206
  const activeTab = tabValue !== void 0 ? tabValue : internalTab;
6485
7207
  const activeSearch = searchValue !== void 0 ? searchValue : internalSearch;
6486
7208
  const activeFilters = filterValues !== void 0 ? filterValues : internalFilters;
@@ -6503,6 +7225,17 @@ var Feed = ({
6503
7225
  (0, import_react14.useEffect)(() => {
6504
7226
  if (Array.isArray(collapsedIds)) setInternalCollapsedIds(collapsedIds);
6505
7227
  }, [collapsedIds]);
7228
+ (0, import_react14.useEffect)(() => {
7229
+ if (!highlightMs || liveState.newKeys.length === 0) return void 0;
7230
+ const earliestAt = Math.min(...liveState.newKeys.map((entry) => entry.at));
7231
+ const timer = setTimeout(() => {
7232
+ setLiveState((prev) => ({
7233
+ ...prev,
7234
+ newKeys: prev.newKeys.filter((entry) => Date.now() - entry.at < highlightMs)
7235
+ }));
7236
+ }, Math.max(16, earliestAt + highlightMs - Date.now()));
7237
+ return () => clearTimeout(timer);
7238
+ }, [highlightMs, liveState.newKeys]);
6506
7239
  const emitParamsChange = (next) => {
6507
7240
  if (typeof onParamsChange === "function") {
6508
7241
  onParamsChange({ tab: activeTab, search: activeSearch, filters: activeFilters, sort: activeSort, ...next });
@@ -6556,13 +7289,40 @@ var Feed = ({
6556
7289
  setCollapsedIds(collapsibleKeys);
6557
7290
  };
6558
7291
  const handleExpandAll = () => setCollapsedIds([]);
7292
+ const handleFlushNewItems = () => {
7293
+ const flushedItems = [];
7294
+ const flushedKeys = [];
7295
+ const keptItems = [];
7296
+ presetItems.forEach((item, index) => {
7297
+ const key = getItemKey(item, index, getKey);
7298
+ if (bufferedKeySet.has(key)) {
7299
+ flushedItems.push(item);
7300
+ flushedKeys.push(key);
7301
+ } else {
7302
+ keptItems.push(item);
7303
+ }
7304
+ });
7305
+ const { newestTs } = flushBuffer(keptItems, flushedItems, pickTimestamp);
7306
+ const now = Date.now();
7307
+ setLiveState((prev) => ({
7308
+ source: presetItems,
7309
+ watermark: newestTs ?? prev.watermark,
7310
+ bufferedKeys: EMPTY_ITEMS,
7311
+ knownKeys: presetItems.map((item, index) => getItemKey(item, index, getKey)),
7312
+ newKeys: highlightMs > 0 ? [
7313
+ ...prev.newKeys.filter((entry) => now - entry.at < highlightMs),
7314
+ ...flushedKeys.map((key) => ({ key, at: now }))
7315
+ ] : prev.newKeys
7316
+ }));
7317
+ onNewItemsFlush == null ? void 0 : onNewItemsFlush(flushedItems);
7318
+ };
6559
7319
  const processedItems = (0, import_react14.useMemo)(() => {
6560
- if (serverSide) return safeItems;
6561
- const tabbed = applyTab(safeItems, activeTab, tabField);
7320
+ if (serverSide) return sourceItems;
7321
+ const tabbed = applyTab(sourceItems, activeTab, tabField);
6562
7322
  const searched = applySearch(tabbed, activeSearch, searchFields);
6563
7323
  const filtered = applyFilters(searched, filters, activeFilters);
6564
7324
  return applySort(filtered, activeSort, sortOptions);
6565
- }, [safeItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
7325
+ }, [sourceItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
6566
7326
  const visibleItems = (0, import_react14.useMemo)(
6567
7327
  () => processedItems.slice(0, Math.max(0, resolvedMaxItems)),
6568
7328
  [processedItems, resolvedMaxItems]
@@ -6589,8 +7349,8 @@ var Feed = ({
6589
7349
  const canViewMore = visibleItems.length < processedItems.length;
6590
7350
  const shouldShowExternalLoadMore = hasMore && onLoadMore;
6591
7351
  const normalizedTabs = (0, import_react14.useMemo)(
6592
- () => normalizeTabs(tabs, safeItems, tabField, labels),
6593
- [tabs, safeItems, tabField, labels]
7352
+ () => normalizeTabs(tabs, presetItems, tabField, labels),
7353
+ [tabs, presetItems, tabField, labels]
6594
7354
  );
6595
7355
  const resolvedShowTabs = showTabs ?? normalizedTabs.length > 1;
6596
7356
  const sortControl = Array.isArray(sortOptions) && sortOptions.length > 0 ? /* @__PURE__ */ import_react14.default.createElement(
@@ -6605,7 +7365,7 @@ var Feed = ({
6605
7365
  ) : null;
6606
7366
  const countControl = showItemCount ? /* @__PURE__ */ import_react14.default.createElement(CollectionCount, { text: itemCountLabel }) : null;
6607
7367
  const toolbarRight = sortControl || countControl ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "sm", align: "center" }, sortControl, countControl) : null;
6608
- const firstGroupHasLabel = groups.length > 0 && hasValue(groups[0].label);
7368
+ const firstGroupHasLabel = groups.length > 0 && hasValue2(groups[0].label);
6609
7369
  const hasLeftToolbarControls = resolvedShowSearch || Array.isArray(filters) && filters.length > 0 || activeChips.length > 0;
6610
7370
  const alignControlsWithFirstGroup = !renderToolbar && showToolbar && !loading && !error && processedItems.length > 0 && !hasLeftToolbarControls && !!toolbarRight && firstGroupHasLabel && (alignToolbarWithGroups === true || alignToolbarWithGroups === "auto" && (groupByDate || !!groupBy));
6611
7371
  const toolbarNode = renderToolbar ? renderToolbar({
@@ -6655,15 +7415,21 @@ var Feed = ({
6655
7415
  return activeCollapsedIds.includes(getItemKey(item, i >= 0 ? i : idx, getKey));
6656
7416
  });
6657
7417
  const collapseToggle = showCollapseToggle && collapsibleVisibleItems.length > 1 && !loading && !error ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Link, { onClick: allCollapsed ? handleExpandAll : handleCollapseAll }, keepWordsTogether(allCollapsed ? labels.expandAll : labels.collapseAll)) : null;
6658
- if (hasValue(title) || hasValue(description) || hasValue(actions) || hasValue(children) || collapseToggle) {
6659
- const headerBody = /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "column", gap: "xs" }, hasValue(title) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { format: { fontWeight: "demibold" } }, title), hasValue(description) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, null, description), children);
6660
- const headerRight = hasValue(actions) || collapseToggle ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
7418
+ if (hasValue2(title) || hasValue2(description) || hasValue2(actions) || hasValue2(children) || collapseToggle) {
7419
+ const headerBody = /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "column", gap: "xs" }, hasValue2(title) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { format: { fontWeight: "demibold" } }, title), hasValue2(description) && /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, null, description), children);
7420
+ const headerRight = hasValue2(actions) || collapseToggle ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Inline, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
6661
7421
  content.push(
6662
7422
  /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { key: "header", direction: "row", justify: "between", align: "start", gap: "sm" }, headerBody, headerRight)
6663
7423
  );
6664
7424
  }
6665
7425
  const bodyContent = [];
6666
7426
  if (toolbarNode) bodyContent.push(/* @__PURE__ */ import_react14.default.createElement(import_react14.default.Fragment, { key: "toolbar" }, toolbarNode));
7427
+ const pendingNewCount = bufferNewItems ? liveState.bufferedKeys.length : 0;
7428
+ if (pendingNewCount > 0 && !loading && !error) {
7429
+ bodyContent.push(
7430
+ /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { key: "new-items-pill", direction: "row", justify: "center" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Button, { variant: "secondary", size: "small", onClick: handleFlushNewItems }, typeof labels.newItems === "function" ? labels.newItems(pendingNewCount) : labels.newItems))
7431
+ );
7432
+ }
6667
7433
  if (loading) {
6668
7434
  bodyContent.push(
6669
7435
  renderLoadingState ? renderLoadingState({ label: labels.loading }) : (
@@ -6680,14 +7446,14 @@ var Feed = ({
6680
7446
  message: labels.errorMessage
6681
7447
  }) : /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Alert, { key: "error", variant: "danger", title: typeof error === "string" ? error : labels.errorTitle }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, null, labels.errorMessage))
6682
7448
  );
6683
- } else if (processedItems.length === 0) {
7449
+ } else if (processedItems.length === 0 && pendingNewCount === 0) {
6684
7450
  bodyContent.push(
6685
7451
  renderEmptyState ? renderEmptyState({ title: labels.emptyTitle, message: labels.emptyMessage }) : /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Tile, { key: "empty" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, null, labels.emptyMessage))))
6686
7452
  );
6687
7453
  } else {
6688
7454
  bodyContent.push(
6689
- /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group, groupIndex) => /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue(group.label) && (alignControlsWithFirstGroup && groupIndex === 0 ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { format: { fontWeight: "demibold" } }, group.label), toolbarRight) : /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { format: { fontWeight: "demibold" } }, group.label)), group.items.map((item, index) => {
6690
- const globalIndex = safeItems.indexOf(item);
7455
+ /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group, groupIndex) => /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue2(group.label) && (alignControlsWithFirstGroup && groupIndex === 0 ? /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { format: { fontWeight: "demibold" } }, group.label), toolbarRight) : /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Text, { format: { fontWeight: "demibold" } }, group.label)), group.items.map((item, index) => {
7456
+ const globalIndex = presetItems.indexOf(item);
6691
7457
  const itemIndex = globalIndex >= 0 ? globalIndex : index;
6692
7458
  const key = getItemKey(item, itemIndex, getKey);
6693
7459
  const node = renderItem ? renderItem(item, itemIndex) : /* @__PURE__ */ import_react14.default.createElement(
@@ -6702,6 +7468,8 @@ var Feed = ({
6702
7468
  collapsible: collapsible !== false && itemHasExpandableContent(item, fields),
6703
7469
  expanded: !activeCollapsedIds.includes(key),
6704
7470
  onToggleExpanded: () => toggleItemExpanded(key),
7471
+ isNew: highlightMs > 0 && newKeySet.has(key),
7472
+ newItemTagLabel: labels.newItemTag,
6705
7473
  renderActor,
6706
7474
  renderTimestamp,
6707
7475
  renderMeta,
@@ -6748,6 +7516,7 @@ var Feed = ({
6748
7516
  if (container === "card" || container === "tile") return /* @__PURE__ */ import_react14.default.createElement(import_ui_extensions14.Tile, { compact: true }, feed);
6749
7517
  return feed;
6750
7518
  };
7519
+ Feed.displayName = "Feed";
6751
7520
 
6752
7521
  // src/calendar/Calendar.jsx
6753
7522
  var import_react15 = __toESM(require("react"));
@@ -6756,7 +7525,7 @@ var import_experimental = require("@hubspot/ui-extensions/experimental");
6756
7525
 
6757
7526
  // src/calendar/dateUtils.js
6758
7527
  var MS_PER_DAY = 864e5;
6759
- var isDateValueObject2 = (v) => v != null && typeof v === "object" && typeof v.year === "number" && typeof v.month === "number" && typeof v.date === "number";
7528
+ var isDateValueObject3 = (v) => v != null && typeof v === "object" && typeof v.year === "number" && typeof v.month === "number" && typeof v.date === "number";
6760
7529
  var fromEpoch = (ms) => {
6761
7530
  const d = new Date(ms);
6762
7531
  if (Number.isNaN(d.getTime())) return null;
@@ -6766,7 +7535,7 @@ var fromEpoch = (ms) => {
6766
7535
  var toDate2 = (value) => {
6767
7536
  if (value == null || value === "") return null;
6768
7537
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
6769
- if (isDateValueObject2(value)) {
7538
+ if (isDateValueObject3(value)) {
6770
7539
  return new Date(
6771
7540
  value.year,
6772
7541
  value.month,
@@ -6861,7 +7630,7 @@ var buildHours = (startHour = 8, endHour = 20) => {
6861
7630
  let e = Math.max(0, Math.min(23, Math.round(endHour)));
6862
7631
  if (s > e) [s, e] = [e, s];
6863
7632
  const hours = [];
6864
- for (let h6 = s; h6 <= e; h6 += 1) hours.push(h6);
7633
+ for (let h7 = s; h7 <= e; h7 += 1) hours.push(h7);
6865
7634
  return hours;
6866
7635
  };
6867
7636
  var WEEKDAY_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
@@ -6957,21 +7726,6 @@ var formatTimeZoneLabel = (tz, atDate = /* @__PURE__ */ new Date()) => {
6957
7726
 
6958
7727
  // src/calendar/svgChips.js
6959
7728
  var toDataUri = (svg) => `data:image/svg+xml,${encodeURIComponent(svg)}`;
6960
- var escapeXml = (s) => String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6961
- var truncateLabel = (value, max) => {
6962
- const s = String(value == null ? "" : value);
6963
- if (!max || s.length <= max) return s;
6964
- return s.slice(0, Math.max(1, max - 1)).trimEnd() + "\u2026";
6965
- };
6966
- var CHIP_PALETTE = {
6967
- default: { fill: "#7FD1DE", text: HS_TEXT_COLOR },
6968
- info: { fill: "#00A4BD", text: "#FFFFFF" },
6969
- success: { fill: "#00BDA5", text: "#FFFFFF" },
6970
- warning: { fill: "#F5C26B", text: HS_TEXT_COLOR },
6971
- error: { fill: "#F2545B", text: "#FFFFFF" },
6972
- danger: { fill: "#F2545B", text: "#FFFFFF" }
6973
- // StatusTag spells red "danger"; accept both
6974
- };
6975
7729
  var DOT_FILL = {
6976
7730
  default: "#7C98B6",
6977
7731
  info: "#00A4BD",
@@ -6986,31 +7740,177 @@ var makeDotDataUri = (variant = "default", size = 8) => {
6986
7740
  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>`;
6987
7741
  return { src: toDataUri(svg), width: size, height: size };
6988
7742
  };
6989
- var makeEventChipDataUri = (opts) => {
6990
- const { label, width, height = 24, variant = "default" } = opts;
6991
- const palette = CHIP_PALETTE[variant] || CHIP_PALETTE.default;
6992
- const accentX = 5;
6993
- const accentW = 3;
6994
- const textX = accentX + accentW + 6;
6995
- const rightPad = 8;
6996
- const maxChars = Math.max(1, Math.floor((width - textX - rightPad) / 6));
6997
- const text = truncateLabel(label, maxChars);
6998
- 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>`;
6999
- return { src: toDataUri(svg), width, height };
7000
- };
7001
- var makeMoreDataUri = (opts) => {
7002
- const { label, width, height = 24 } = opts;
7003
- 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>`;
7004
- return { src: toDataUri(svg), width, height };
7005
- };
7006
7743
  var makeSpacerDataUri = (height = 24, width = 4) => {
7007
7744
  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>`;
7008
7745
  return { src: toDataUri(svg), width, height };
7009
7746
  };
7010
7747
 
7748
+ // src/calendar/rescheduleUtils.js
7749
+ var shiftDate = (date, shift) => {
7750
+ const d = toDate2(date);
7751
+ if (!d) return null;
7752
+ if (!shift || typeof shift !== "object") return new Date(d);
7753
+ const days = (shift.days || 0) + (shift.weeks || 0) * 7;
7754
+ let next = days ? addDays(d, days) : new Date(d);
7755
+ const ms = (shift.hours || 0) * 36e5 + (shift.minutes || 0) * 6e4;
7756
+ if (ms) next = new Date(next.getTime() + ms);
7757
+ return next;
7758
+ };
7759
+ var shiftEvent = (range, shift) => {
7760
+ const start = toDate2(range && range.start);
7761
+ if (!start) return null;
7762
+ const end = toDate2(range && range.end) || start;
7763
+ return { start: shiftDate(start, shift), end: shiftDate(end, shift) };
7764
+ };
7765
+ var calendarDayDelta = (a, b) => Math.round((startOfDay(b).getTime() - startOfDay(a).getTime()) / MS_PER_DAY);
7766
+ var msIntoDay = (d) => ((d.getHours() * 60 + d.getMinutes()) * 60 + d.getSeconds()) * 1e3 + d.getMilliseconds();
7767
+ var rescheduleToStart = (range, newStart) => {
7768
+ const start = toDate2(range && range.start);
7769
+ const target = toDate2(newStart);
7770
+ if (!start || !target) return null;
7771
+ const end = toDate2(range && range.end) || start;
7772
+ const dayDelta = calendarDayDelta(start, target);
7773
+ const timeDelta = msIntoDay(target) - msIntoDay(start);
7774
+ const endOnDay = addDays(end, dayDelta);
7775
+ if (!timeDelta) return { start: target, end: endOnDay };
7776
+ return {
7777
+ start: target,
7778
+ end: new Date(
7779
+ endOnDay.getFullYear(),
7780
+ endOnDay.getMonth(),
7781
+ endOnDay.getDate(),
7782
+ 0,
7783
+ 0,
7784
+ 0,
7785
+ msIntoDay(endOnDay) + timeDelta
7786
+ )
7787
+ };
7788
+ };
7789
+ var applyDatePick = (start, value) => {
7790
+ if (!isDateValueObject3(value)) return null;
7791
+ const s = toDate2(start);
7792
+ return new Date(
7793
+ value.year,
7794
+ value.month,
7795
+ value.date,
7796
+ s ? s.getHours() : 0,
7797
+ s ? s.getMinutes() : 0,
7798
+ s ? s.getSeconds() : 0,
7799
+ s ? s.getMilliseconds() : 0
7800
+ );
7801
+ };
7802
+ var DEFAULT_RESCHEDULE_PRESETS = [
7803
+ { label: "+1 hour", shift: { hours: 1 } },
7804
+ { label: "+1 day", shift: { days: 1 } },
7805
+ { label: "Next week", shift: { weeks: 1 } }
7806
+ ];
7807
+ var normalizeRescheduleOptions = (options) => {
7808
+ if (!options) return [];
7809
+ if (options === true) return DEFAULT_RESCHEDULE_PRESETS;
7810
+ if (!Array.isArray(options)) return [];
7811
+ const out = [];
7812
+ for (const opt of options) {
7813
+ if (typeof opt === "function") {
7814
+ out.push({ label: opt.label || opt.name || "Reschedule", getStart: opt });
7815
+ } else if (opt && typeof opt === "object" && opt.label != null) {
7816
+ if (typeof opt.shift === "function") {
7817
+ out.push({ label: opt.label, getStart: opt.shift });
7818
+ } else if (typeof opt.getStart === "function") {
7819
+ out.push({ label: opt.label, getStart: opt.getStart });
7820
+ } else if (opt.shift && typeof opt.shift === "object") {
7821
+ out.push({ label: opt.label, shift: opt.shift });
7822
+ }
7823
+ }
7824
+ }
7825
+ return out;
7826
+ };
7827
+ var resolveRescheduleTarget = (range, option, fnArg) => {
7828
+ if (!range || !toDate2(range.start) || !option) return null;
7829
+ if (typeof option.getStart === "function") {
7830
+ const next = toDate2(option.getStart(fnArg !== void 0 ? fnArg : range));
7831
+ return next ? rescheduleToStart(range, next) : null;
7832
+ }
7833
+ if (option.shift && typeof option.shift === "object") {
7834
+ return shiftEvent(range, option.shift);
7835
+ }
7836
+ return null;
7837
+ };
7838
+
7839
+ // src/calendar/resourceLanes.js
7840
+ var resolveResourceId = (record, spec) => {
7841
+ if (record == null || spec == null) return null;
7842
+ const value = typeof spec === "function" ? spec(record) : record[spec];
7843
+ if (value == null || value === "") return null;
7844
+ return value;
7845
+ };
7846
+ var buildResourceLanes = (events, options = {}) => {
7847
+ const {
7848
+ resources,
7849
+ resourceLabels,
7850
+ getId,
7851
+ showUnassignedLane = true,
7852
+ unassignedLabel = "Unassigned"
7853
+ } = options;
7854
+ const labelFor = (id) => {
7855
+ if (resourceLabels && resourceLabels[id] != null) return resourceLabels[id];
7856
+ return String(id);
7857
+ };
7858
+ const lanes = [];
7859
+ const byKey = /* @__PURE__ */ new Map();
7860
+ const addLane = (id, label, declared) => {
7861
+ const key = String(id);
7862
+ if (byKey.has(key)) return byKey.get(key);
7863
+ const lane = { id, key, label, events: [], unassigned: false, declared };
7864
+ byKey.set(key, lane);
7865
+ lanes.push(lane);
7866
+ return lane;
7867
+ };
7868
+ (resources || []).forEach((resource) => {
7869
+ if (resource == null) return;
7870
+ if (typeof resource === "object") {
7871
+ addLane(resource.id, resource.label != null ? resource.label : labelFor(resource.id), true);
7872
+ } else {
7873
+ addLane(resource, labelFor(resource), true);
7874
+ }
7875
+ });
7876
+ const unassigned = {
7877
+ id: null,
7878
+ key: "__unassigned__",
7879
+ label: unassignedLabel,
7880
+ events: [],
7881
+ unassigned: true,
7882
+ declared: false
7883
+ };
7884
+ (events || []).forEach((event) => {
7885
+ const id = getId ? getId(event) : null;
7886
+ if (id == null || id === "") {
7887
+ unassigned.events.push(event);
7888
+ return;
7889
+ }
7890
+ const lane = byKey.get(String(id)) || addLane(id, labelFor(id), false);
7891
+ lane.events.push(event);
7892
+ });
7893
+ if (showUnassignedLane && unassigned.events.length > 0) lanes.push(unassigned);
7894
+ return lanes;
7895
+ };
7896
+ var eventsIntersectingRange = (events, rangeStart, rangeEnd) => {
7897
+ const rs = rangeStart.getTime();
7898
+ const re = rangeEnd.getTime();
7899
+ return (events || []).filter((event) => {
7900
+ if (!event || !event.start) return false;
7901
+ const es = event.start.getTime();
7902
+ const ee = (event.end || event.start).getTime();
7903
+ return es <= re && ee >= rs;
7904
+ });
7905
+ };
7906
+ var laneEventsForDay = (events, day) => eventsIntersectingRange(events, startOfDay(day), endOfDay(day)).sort(
7907
+ (a, b) => a.start.getTime() - b.start.getTime()
7908
+ );
7909
+
7011
7910
  // src/calendar/Calendar.jsx
7012
7911
  var DEFAULT_MAX_EVENTS_PER_DAY = 3;
7013
7912
  var ALL_VIEWS = ["month", "week", "day", "agenda"];
7913
+ var ALL_VIEWS_WITH_RESOURCE = ["month", "week", "day", "resource", "agenda"];
7014
7914
  var DEFAULT_DAY_START_HOUR = 8;
7015
7915
  var DEFAULT_DAY_END_HOUR = 20;
7016
7916
  var DEFAULT_TIME_ZONES = [
@@ -7046,7 +7946,8 @@ var VIEW_LABELS = {
7046
7946
  month: "Month",
7047
7947
  week: "Week",
7048
7948
  day: "Day",
7049
- agenda: "Agenda"
7949
+ agenda: "Agenda",
7950
+ resource: "Resource"
7050
7951
  };
7051
7952
  var DEFAULT_LABELS5 = {
7052
7953
  today: "Today",
@@ -7063,7 +7964,11 @@ var DEFAULT_LABELS5 = {
7063
7964
  errorMessage: "An error occurred while loading events.",
7064
7965
  dayDetailTitle: (label) => label,
7065
7966
  open: "Open",
7066
- allDay: "All day"
7967
+ allDay: "All day",
7968
+ reschedule: "Reschedule",
7969
+ pickDate: "Pick date",
7970
+ unassigned: "Unassigned",
7971
+ resource: "Resource"
7067
7972
  };
7068
7973
  var DEFAULT_EVENT_FIELDS = {
7069
7974
  id: "id",
@@ -7098,11 +8003,30 @@ var STATUS_VARIANT = {
7098
8003
  error: "danger",
7099
8004
  danger: "danger"
7100
8005
  };
8006
+ var TAG_VARIANT = {
8007
+ default: "default",
8008
+ info: "info",
8009
+ success: "success",
8010
+ warning: "warning",
8011
+ error: "error",
8012
+ danger: "error"
8013
+ };
8014
+ var MONTH_EVENT_STYLES = /* @__PURE__ */ new Set(["statusTag", "tag"]);
8015
+ var monthLabelMaxChars = (style) => {
8016
+ const overhead = style === "tag" ? 34 : 46;
8017
+ return Math.max(6, Math.floor((MONTH_COL_WIDTH - overhead) / 6.6));
8018
+ };
8019
+ var truncateMonthLabel = (value, max) => {
8020
+ const s = String(value == null ? "" : value);
8021
+ if (!max || s.length <= max) return s;
8022
+ return s.slice(0, max).trimEnd();
8023
+ };
7101
8024
  var MONTH_COL_WIDTH = 160;
7102
8025
  var TIMEGRID_DAY_COL = 150;
7103
8026
  var TIMEGRID_DAY_COL_SINGLE = 560;
8027
+ var RESOURCE_LABEL_COL_WIDTH = "min";
7104
8028
  var HOUR_SLOT_HEIGHT = 64;
7105
- var EventDetail = ({ event, labels }) => {
8029
+ var EventDetail = ({ event, labels, reschedule, idSuffix = "" }) => {
7106
8030
  const { start, end, title, subtitle, href } = event;
7107
8031
  let when = "";
7108
8032
  if (start) {
@@ -7116,11 +8040,27 @@ var EventDetail = ({ event, labels }) => {
7116
8040
  when = `${formatDayTitle(start)} \u2013 ${formatDayTitle(end)}`;
7117
8041
  }
7118
8042
  }
7119
- return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { format: { fontWeight: "demibold" } }, title || "--"), when ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy" }, when) : null, subtitle ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, null, subtitle) : null, href ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Link, { href: href.url, external: href.external }, labels.open) : null);
8043
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { format: { fontWeight: "demibold" }, truncate: true }, title || "--"), when ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy", truncate: true }, when) : null, subtitle ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { truncate: true }, subtitle) : null, href ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Link, { href: href.url, external: href.external }, labels.open) : null, reschedule ? /* @__PURE__ */ import_react15.default.createElement(import_react15.default.Fragment, null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Divider, null), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy", format: { fontWeight: "demibold" } }, labels.reschedule), reschedule.options.length > 0 ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", gap: "xs", wrap: "wrap" }, reschedule.options.map((option, i) => /* @__PURE__ */ import_react15.default.createElement(
8044
+ import_ui_extensions15.Button,
8045
+ {
8046
+ key: `${option.label}-${i}`,
8047
+ size: "xs",
8048
+ variant: "secondary",
8049
+ onClick: () => reschedule.onPreset(event, option)
8050
+ },
8051
+ option.label
8052
+ ))) : null, /* @__PURE__ */ import_react15.default.createElement(
8053
+ import_ui_extensions15.DateInput,
8054
+ {
8055
+ name: `cal-resched-${event.key}${idSuffix}`,
8056
+ label: labels.pickDate,
8057
+ onChange: (value) => reschedule.onPick(event, value)
8058
+ }
8059
+ )) : null);
7120
8060
  };
7121
- var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "") => {
8061
+ var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "", reschedule = null) => {
7122
8062
  if (mode === "none") return void 0;
7123
- const body = renderEventDetail ? renderEventDetail(event) : /* @__PURE__ */ import_react15.default.createElement(EventDetail, { event, labels });
8063
+ const body = renderEventDetail ? renderEventDetail(event) : /* @__PURE__ */ import_react15.default.createElement(EventDetail, { event, labels, reschedule, idSuffix });
7124
8064
  const id = `cal-evt-${event.key}${idSuffix}`;
7125
8065
  if (mode === "modal") {
7126
8066
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Modal, { id, title: event.title || labels.open, width: "small" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.ModalBody, null, body));
@@ -7128,46 +8068,34 @@ var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "") => {
7128
8068
  if (mode === "panel") {
7129
8069
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Panel, { id, title: event.title || labels.open, width: "small", variant: "modal" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.PanelBody, null, body));
7130
8070
  }
7131
- return /* @__PURE__ */ import_react15.default.createElement(import_experimental.Popover, { id, placement: "bottom" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Tile, { compact: true }, body));
8071
+ return /* @__PURE__ */ import_react15.default.createElement(import_experimental.Popover, { id, placement: "bottom", variant: "longform" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Tile, { compact: true }, body));
7132
8072
  };
7133
- var AgendaEventRow = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8073
+ var AgendaEventRow = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, reschedule }) => {
7134
8074
  const variant = VALID_VARIANTS.has(event.color) ? event.color : "default";
7135
- const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-ag${day.getTime()}` : "");
8075
+ const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-ag${day.getTime()}` : "", reschedule);
7136
8076
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7137
8077
  const timeLabel = isAllDayEvent(event) ? labels.allDay : formatTime(event.start);
7138
8078
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", align: "center", gap: "sm" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Box, { flex: 2 }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", align: "center" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy", format: { fontWeight: "demibold" } }, timeLabel))), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Box, { flex: 11 }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react15.default.createElement(ColorDot, { variant }), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { truncate: true }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Link, { overlay, onClick: handleClick }, event.title || "--")))), event.subtitle ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Box, { flex: 4 }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", align: "center" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy", truncate: true }, event.subtitle))) : null);
7139
8079
  };
7140
- var MONTH_CHIP_WIDTH = MONTH_COL_WIDTH - 8;
7141
- var MONTH_CHIP_HEIGHT = 24;
7142
- var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8080
+ var MONTH_SLOT_HEIGHT = 24;
8081
+ var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, monthEventStyle, monthEventMaxChars, reschedule, idScope = "" }) => {
7143
8082
  const isStartDay = !day || !event.start || isSameDay(event.start, day);
7144
- const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-m${day.getTime()}` : "");
8083
+ const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-m${idScope}${day.getTime()}` : "", reschedule);
7145
8084
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7146
8085
  const variant = VALID_VARIANTS.has(event.color) ? event.color : "default";
7147
8086
  const startHasTime = event.start && (event.start.getHours() !== 0 || event.start.getMinutes() !== 0);
7148
8087
  const time = isStartDay && startHasTime ? `${formatTime(event.start)} ` : "";
7149
8088
  const prefix = isStartDay ? "" : "\u2192 ";
7150
- const chip = makeEventChipDataUri({
7151
- label: `${prefix}${time}${event.title || "--"}`,
7152
- width: MONTH_CHIP_WIDTH,
7153
- height: MONTH_CHIP_HEIGHT,
7154
- variant
7155
- });
7156
- return /* @__PURE__ */ import_react15.default.createElement(
7157
- import_ui_extensions15.Image,
7158
- {
7159
- src: chip.src,
7160
- width: chip.width,
7161
- height: chip.height,
7162
- alt: event.title || "",
7163
- overlay,
7164
- onClick: handleClick
7165
- }
7166
- );
8089
+ const maxChars = monthEventMaxChars != null ? monthEventMaxChars : monthLabelMaxChars(monthEventStyle);
8090
+ const label = truncateMonthLabel(`${prefix}${time}${event.title || "--"}`, maxChars);
8091
+ if (monthEventStyle === "tag") {
8092
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Tag, { variant: TAG_VARIANT[variant] || "default", overlay, onClick: handleClick }, label);
8093
+ }
8094
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Link, { overlay, onClick: handleClick }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.StatusTag, { variant: STATUS_VARIANT[variant] || "default" }, label));
7167
8095
  };
7168
- var DayListItem = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8096
+ var DayListItem = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, reschedule, idScope = "" }) => {
7169
8097
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7170
- const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-more${day.getTime()}` : "-more");
8098
+ const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-more${idScope}${day.getTime()}` : "-more", reschedule);
7171
8099
  const href = event.href;
7172
8100
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Button, { variant: "transparent", size: "sm", href: href ? href.url : void 0, overlay, onClick: handleClick }, event.title || "--");
7173
8101
  };
@@ -7232,11 +8160,45 @@ var Toolbar = ({
7232
8160
  }
7233
8161
  ));
7234
8162
  };
7235
- var MonthView = ({
7236
- refDate,
7237
- now,
7238
- weekStartsOn,
7239
- hideWeekends,
8163
+ var DayChipStack = ({ day, events, maxEventsPerDay, chipProps, labels, idScope = "" }) => {
8164
+ const slotSpacer = makeSpacerDataUri(MONTH_SLOT_HEIGHT, 1);
8165
+ const shown = events.slice(0, maxEventsPerDay);
8166
+ const hasOverflow = events.length > maxEventsPerDay;
8167
+ const heightSpacer = /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" });
8168
+ const slotRow = (key, content) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { key, direction: "row", align: "center", gap: "flush" }, heightSpacer, content);
8169
+ const slots = [];
8170
+ for (let i = 0; i < maxEventsPerDay; i++) {
8171
+ if (i < shown.length) {
8172
+ slots.push(slotRow(shown[i].key, /* @__PURE__ */ import_react15.default.createElement(MonthChip, { event: shown[i], day, idScope, ...chipProps })));
8173
+ } else {
8174
+ slots.push(/* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { key: `sp-${i}`, src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" }));
8175
+ }
8176
+ }
8177
+ if (hasOverflow) {
8178
+ slots.push(
8179
+ slotRow(
8180
+ "more",
8181
+ /* @__PURE__ */ import_react15.default.createElement(
8182
+ import_ui_extensions15.Link,
8183
+ {
8184
+ overlay: /* @__PURE__ */ import_react15.default.createElement(import_experimental.Popover, { id: `cal-day-${idScope}${day.getTime()}`, placement: "top", variant: "longform" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Tile, { compact: true }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", justify: "center", align: "center", gap: "xs" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { format: { fontWeight: "bold" } }, String(events.length)), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { format: { fontWeight: "demibold" } }, labels.onThisDate)), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Divider, null), events.map((event, i) => /* @__PURE__ */ import_react15.default.createElement(import_react15.default.Fragment, { key: event.key }, /* @__PURE__ */ import_react15.default.createElement(DayListItem, { event, day, idScope, ...chipProps }), i < events.length - 1 ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Divider, null) : null)))))
8185
+ },
8186
+ labels.more(events.length - maxEventsPerDay)
8187
+ )
8188
+ )
8189
+ );
8190
+ } else {
8191
+ slots.push(
8192
+ /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { key: "more-sp", src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" })
8193
+ );
8194
+ }
8195
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "xs" }, slots);
8196
+ };
8197
+ var MonthView = ({
8198
+ refDate,
8199
+ now,
8200
+ weekStartsOn,
8201
+ hideWeekends,
7240
8202
  weeks,
7241
8203
  eventsForDay,
7242
8204
  maxEventsPerDay,
@@ -7246,51 +8208,23 @@ var MonthView = ({
7246
8208
  }) => {
7247
8209
  const headers = weekdayLabels(weekStartsOn, hideWeekends, true);
7248
8210
  const today = now || /* @__PURE__ */ new Date();
7249
- const spacer24 = makeSpacerDataUri(MONTH_CHIP_HEIGHT, MONTH_COL_WIDTH);
7250
8211
  const renderCell = (day) => {
7251
8212
  const dayEvents = eventsForDay(day);
7252
8213
  const inMonth = isSameMonth(day, refDate);
7253
8214
  const isToday = isSameDay(day, today);
7254
8215
  if (renderDayCell) return renderDayCell(day, dayEvents);
7255
- const shown = dayEvents.slice(0, maxEventsPerDay);
7256
- const hasOverflow = dayEvents.length > maxEventsPerDay;
7257
- const slots = [];
7258
- for (let i = 0; i < maxEventsPerDay; i++) {
7259
- if (i < shown.length) {
7260
- slots.push(/* @__PURE__ */ import_react15.default.createElement(MonthChip, { key: shown[i].key, event: shown[i], day, ...chipProps }));
7261
- } else {
7262
- slots.push(
7263
- /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { key: `sp-${i}`, src: spacer24.src, width: spacer24.width, height: spacer24.height, alt: "" })
7264
- );
8216
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.AutoGrid, { columnWidth: MONTH_COL_WIDTH, gap: "flush" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", align: "center", gap: "xs" }, isToday ? /* @__PURE__ */ import_react15.default.createElement(ColorDot, { variant: "info" }) : null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy", format: { fontWeight: inMonth ? "demibold" : "regular" } }, String(day.getDate()))), /* @__PURE__ */ import_react15.default.createElement(
8217
+ DayChipStack,
8218
+ {
8219
+ day,
8220
+ events: dayEvents,
8221
+ maxEventsPerDay,
8222
+ chipProps,
8223
+ labels
7265
8224
  }
7266
- }
7267
- if (hasOverflow) {
7268
- const more = makeMoreDataUri({
7269
- label: labels.more(dayEvents.length - maxEventsPerDay),
7270
- width: MONTH_COL_WIDTH,
7271
- height: MONTH_CHIP_HEIGHT
7272
- });
7273
- slots.push(
7274
- /* @__PURE__ */ import_react15.default.createElement(
7275
- import_ui_extensions15.Image,
7276
- {
7277
- key: "more",
7278
- src: more.src,
7279
- width: more.width,
7280
- height: more.height,
7281
- alt: labels.more(dayEvents.length - maxEventsPerDay),
7282
- overlay: /* @__PURE__ */ import_react15.default.createElement(import_experimental.Popover, { id: `cal-day-${day.getTime()}`, placement: "top", variant: "longform" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Tile, { compact: true }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", align: "center", gap: "sm" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Heading, null, String(dayEvents.length)), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { format: { fontWeight: "demibold" } }, labels.onThisDate)), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Divider, null), dayEvents.map((event, i) => /* @__PURE__ */ import_react15.default.createElement(import_react15.default.Fragment, { key: event.key }, /* @__PURE__ */ import_react15.default.createElement(DayListItem, { event, day, ...chipProps }), i < dayEvents.length - 1 ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Divider, null) : null)))))
7283
- }
7284
- )
7285
- );
7286
- } else {
7287
- slots.push(
7288
- /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { key: "more-sp", src: spacer24.src, width: spacer24.width, height: spacer24.height, alt: "" })
7289
- );
7290
- }
7291
- return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", align: "center", gap: "xs" }, isToday ? /* @__PURE__ */ import_react15.default.createElement(ColorDot, { variant: "info" }) : null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy", format: { fontWeight: inMonth ? "demibold" : "regular" } }, String(day.getDate()))), slots);
8225
+ )));
7292
8226
  };
7293
- return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Table, { bordered: true, flush: true }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHead, null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableRow, null, headers.map((h6) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHeader, { key: h6, width: "min", align: "center" }, h6.toUpperCase())))), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableBody, null, weeks.map((week, wi) => {
8227
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Table, { bordered: true, flush: true }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHead, null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableRow, null, headers.map((h7) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHeader, { key: h7, width: "min", align: "center" }, h7.toUpperCase())))), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableBody, null, weeks.map((week, wi) => {
7294
8228
  const days = hideWeekends ? week.filter((d) => d.getDay() !== 0 && d.getDay() !== 6) : week;
7295
8229
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableRow, { key: wi }, days.map((day) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableCell, { key: day.getTime(), width: "min" }, renderCell(day))));
7296
8230
  })));
@@ -7310,13 +8244,40 @@ var AgendaView = ({ rangeStart, rangeEnd, eventsForDay, chipProps, labels, rende
7310
8244
  }
7311
8245
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "lg" }, days.map(({ day, events }) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { key: day.getTime(), direction: "column", gap: "sm" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "row", justify: "between", align: "end" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { format: { fontWeight: "demibold" } }, formatDayTitle(day)), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy" }, `${events.length} ${events.length === 1 ? "event" : "events"}`)), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Divider, null), events.map((event) => /* @__PURE__ */ import_react15.default.createElement(AgendaEventRow, { key: event.key, event, day, ...chipProps })))));
7312
8246
  };
8247
+ var ResourceView = ({ days, now, lanes, maxEventsPerDay, chipProps, labels, renderEmptyState }) => {
8248
+ const today = now || /* @__PURE__ */ new Date();
8249
+ if (!lanes || lanes.length === 0) {
8250
+ if (renderEmptyState) return renderEmptyState({});
8251
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.EmptyState, { title: labels.noEventsTitle }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, null, labels.noEventsMessage));
8252
+ }
8253
+ const rangeStart = startOfDay(days[0]);
8254
+ const rangeEnd = endOfDay(days[days.length - 1]);
8255
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Table, { bordered: true, flush: true }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHead, null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableRow, null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHeader, { width: RESOURCE_LABEL_COL_WIDTH }, String(labels.resource).toUpperCase()), days.map((day) => {
8256
+ const isToday = isSameDay(day, today);
8257
+ const label = `${formatWeekdayShort(day)} ${formatMonthShort(day)} ${day.getDate()}`;
8258
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHeader, { key: day.getTime(), width: "min", align: "center" }, isToday ? `${label} \xB7 Today` : label);
8259
+ }))), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableBody, null, lanes.map((lane, laneIndex) => {
8260
+ const visible = eventsIntersectingRange(lane.events, rangeStart, rangeEnd);
8261
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableRow, { key: lane.key }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableCell, { width: RESOURCE_LABEL_COL_WIDTH }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "flush" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { format: { fontWeight: "demibold" } }, lane.label), /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy" }, `${visible.length} ${visible.length === 1 ? "event" : "events"}`))), days.map((day) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableCell, { key: day.getTime(), width: "min" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.AutoGrid, { columnWidth: MONTH_COL_WIDTH, gap: "flush" }, /* @__PURE__ */ import_react15.default.createElement(
8262
+ DayChipStack,
8263
+ {
8264
+ day,
8265
+ events: laneEventsForDay(visible, day),
8266
+ maxEventsPerDay,
8267
+ chipProps,
8268
+ labels,
8269
+ idScope: `r${laneIndex}-`
8270
+ }
8271
+ )))));
8272
+ })));
8273
+ };
7313
8274
  var formatTimedDuration = (start, end) => {
7314
8275
  const mins = Math.max(0, Math.round(((end || start).getTime() - start.getTime()) / 6e4));
7315
- const h6 = Math.floor(mins / 60);
8276
+ const h7 = Math.floor(mins / 60);
7316
8277
  const m = mins % 60;
7317
- if (h6 === 0) return `${m} min`;
7318
- if (m === 0) return `${h6} hr`;
7319
- return `${h6} hr ${m} min`;
8278
+ if (h7 === 0) return `${m} min`;
8279
+ if (m === 0) return `${h7} hr`;
8280
+ return `${h7} hr ${m} min`;
7320
8281
  };
7321
8282
  var hourSpan = (event) => {
7322
8283
  const start = event.start;
@@ -7331,6 +8292,7 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7331
8292
  const today = now || /* @__PURE__ */ new Date();
7332
8293
  const centerDays = days.length === 1;
7333
8294
  const dayColWidth = days.length === 1 ? TIMEGRID_DAY_COL_SINGLE : TIMEGRID_DAY_COL;
8295
+ const weekTitleMaxChars = Math.max(6, Math.floor((TIMEGRID_DAY_COL - 46) / 6.6));
7334
8296
  const todayInView = days.some((d) => isSameDay(d, today));
7335
8297
  const nowHour = today.getHours();
7336
8298
  const dayData = days.map((day) => {
@@ -7356,7 +8318,8 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7356
8318
  chipProps.overlayMode,
7357
8319
  chipProps.renderEventDetail,
7358
8320
  chipProps.labels,
7359
- `-tg${mode}${hour}-${dayMs}`
8321
+ `-tg${mode}${hour}-${dayMs}`,
8322
+ chipProps.reschedule
7360
8323
  );
7361
8324
  const handleClick = chipProps.onEventClick ? () => chipProps.onEventClick(e.raw, e) : void 0;
7362
8325
  const variant = VALID_VARIANTS.has(e.color) ? e.color : "default";
@@ -7375,10 +8338,10 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7375
8338
  } else if (mode === "cont") {
7376
8339
  sub = `\u2191 cont. through ${endLabel}`;
7377
8340
  }
7378
- return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { key: `${e.key}-${mode}-${hour}`, direction: "column", gap: "flush" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Link, { overlay, onClick: handleClick }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.StatusTag, { variant: STATUS_VARIANT[variant] || "default" }, e.title || "--")), sub ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy" }, sub) : null);
8341
+ const titleLabel = centerDays ? e.title || "--" : truncateMonthLabel(e.title || "--", weekTitleMaxChars);
8342
+ return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { key: `${e.key}-${mode}-${hour}`, direction: "column", gap: "flush" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Link, { overlay, onClick: handleClick }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.StatusTag, { variant: STATUS_VARIANT[variant] || "default" }, titleLabel)), sub ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { variant: "microcopy" }, sub) : null);
7379
8343
  };
7380
- const daySpacer = makeSpacerDataUri(1, dayColWidth);
7381
- const dayCell = (key, content) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableCell, { key, width: centerDays ? "max" : "min", align: "left" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "xs" }, content, centerDays ? null : /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { src: daySpacer.src, width: daySpacer.width, height: daySpacer.height, alt: "" })));
8344
+ const dayCell = (key, content) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableCell, { key, width: centerDays ? "max" : "min", align: "left" }, centerDays ? /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "xs" }, content) : /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.AutoGrid, { columnWidth: dayColWidth, gap: "flush" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "xs" }, content)));
7382
8345
  const slotSpacer = makeSpacerDataUri(HOUR_SLOT_HEIGHT, 1);
7383
8346
  const emptyCell = null;
7384
8347
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Table, { bordered: true, flush: true, density: "compact" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHead, null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableRow, null, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.TableHeader, { width: "min" }, "TIME"), dayData.map(({ day }) => {
@@ -7434,9 +8397,21 @@ var Calendar = (props) => {
7434
8397
  weekStartsOn = 0,
7435
8398
  hideWeekends = false,
7436
8399
  maxEventsPerDay = DEFAULT_MAX_EVENTS_PER_DAY,
8400
+ // month-grid event token style: "statusTag" (dot + text, default) | "tag" (pill)
8401
+ monthEventStyle = "statusTag",
8402
+ // max characters for a month-cell label before "…" (default derived per style)
8403
+ monthEventMaxChars,
7437
8404
  // time grid (week / day)
7438
8405
  dayStartHour = DEFAULT_DAY_START_HOUR,
7439
8406
  dayEndHour = DEFAULT_DAY_END_HOUR,
8407
+ // resource / lane view (rows = resources, columns = the focused week's days)
8408
+ resources,
8409
+ resourceField,
8410
+ resourceLabels,
8411
+ showUnassignedLane = true,
8412
+ // drag-free reschedule (presets + date picker in the event-detail overlay)
8413
+ rescheduleOptions,
8414
+ onEventReschedule,
7440
8415
  // timezone
7441
8416
  timeZone: controlledTimeZone,
7442
8417
  defaultTimeZone,
@@ -7475,10 +8450,12 @@ var Calendar = (props) => {
7475
8450
  const fields = (0, import_react15.useMemo)(() => ({ ...DEFAULT_EVENT_FIELDS, ...eventFields || {} }), [eventFields]);
7476
8451
  const [internalView, setInternalView] = (0, import_react15.useState)(defaultView);
7477
8452
  const view = controlledView != null ? controlledView : internalView;
8453
+ const resourceEnabled = resources && resources.length > 0 || resourceField != null;
7478
8454
  const enabledViews = (0, import_react15.useMemo)(() => {
7479
- const base = viewsProp && viewsProp.length > 0 ? viewsProp : ALL_VIEWS;
7480
- return base.filter((v) => ALL_VIEWS.includes(v));
7481
- }, [viewsProp]);
8455
+ const all = resourceEnabled ? ALL_VIEWS_WITH_RESOURCE : ALL_VIEWS;
8456
+ const base = viewsProp && viewsProp.length > 0 ? viewsProp : all;
8457
+ return base.filter((v) => all.includes(v));
8458
+ }, [viewsProp, resourceEnabled]);
7482
8459
  const setView = (0, import_react15.useCallback)(
7483
8460
  (next) => {
7484
8461
  if (controlledView == null) setInternalView(next);
@@ -7503,7 +8480,9 @@ var Calendar = (props) => {
7503
8480
  const focusedDate = (controlledFocusedDate != null ? toDate2(controlledFocusedDate) : internalDate) || startOfDay(nowWall);
7504
8481
  const stepFor = (0, import_react15.useCallback)(
7505
8482
  (dir) => {
7506
- if (view === "week" || view === "agenda") return addDays(focusedDate, dir * 7);
8483
+ if (view === "week" || view === "agenda" || view === "resource") {
8484
+ return addDays(focusedDate, dir * 7);
8485
+ }
7507
8486
  if (view === "day") return addDays(focusedDate, dir);
7508
8487
  return addMonths(focusedDate, dir);
7509
8488
  },
@@ -7530,7 +8509,7 @@ var Calendar = (props) => {
7530
8509
  rangeEnd: endOfDay(flat[flat.length - 1])
7531
8510
  };
7532
8511
  }
7533
- if (view === "week") {
8512
+ if (view === "week" || view === "resource") {
7534
8513
  const days = buildWeekDays(focusedDate, weekStartsOn, hideWeekends);
7535
8514
  return {
7536
8515
  weeks: null,
@@ -7603,14 +8582,18 @@ var Calendar = (props) => {
7603
8582
  );
7604
8583
  const normalized = (0, import_react15.useMemo)(
7605
8584
  () => (events || []).map((raw, index) => {
7606
- const start = toWallClock(toDate2(resolveField(raw, fields.start)), timeZone);
7607
- const endRaw = toWallClock(toDate2(resolveField(raw, fields.end)), timeZone);
8585
+ const sourceStart = toDate2(resolveField(raw, fields.start));
8586
+ const sourceEnd = toDate2(resolveField(raw, fields.end));
8587
+ const start = toWallClock(sourceStart, timeZone);
8588
+ const endRaw = toWallClock(sourceEnd, timeZone);
7608
8589
  const id = resolveField(raw, fields.id);
7609
8590
  return {
7610
8591
  key: id != null ? String(id) : `evt-${index}`,
7611
8592
  id,
7612
8593
  start,
7613
8594
  end: endRaw || start,
8595
+ sourceStart,
8596
+ sourceEnd: sourceEnd || sourceStart,
7614
8597
  title: resolveField(raw, fields.title),
7615
8598
  subtitle: resolveField(raw, fields.subtitle),
7616
8599
  color: resolveField(raw, fields.color),
@@ -7647,13 +8630,60 @@ var Calendar = (props) => {
7647
8630
  }, [rangeKey]);
7648
8631
  const title = (0, import_react15.useMemo)(() => {
7649
8632
  if (view === "day") return formatDayTitle(focusedDate);
7650
- if (view === "week" || view === "agenda") {
7651
- const days = buildWeekDays(focusedDate, weekStartsOn, view === "week" && hideWeekends);
8633
+ if (view === "week" || view === "agenda" || view === "resource") {
8634
+ const days = buildWeekDays(focusedDate, weekStartsOn, view !== "agenda" && hideWeekends);
7652
8635
  return formatRangeTitle(days[0], days[days.length - 1]);
7653
8636
  }
7654
8637
  return formatMonthTitle(focusedDate);
7655
8638
  }, [view, focusedDate, weekStartsOn, hideWeekends]);
7656
- const chipProps = { overlayMode, renderEventDetail, onEventClick, labels };
8639
+ const handleReschedulePreset = (0, import_react15.useCallback)(
8640
+ (event, option) => {
8641
+ const target = resolveRescheduleTarget(
8642
+ { start: event.sourceStart, end: event.sourceEnd },
8643
+ option,
8644
+ event
8645
+ );
8646
+ if (target && onEventReschedule) onEventReschedule(event.raw, target, event);
8647
+ },
8648
+ [onEventReschedule]
8649
+ );
8650
+ const handleReschedulePick = (0, import_react15.useCallback)(
8651
+ (event, value) => {
8652
+ const newStart = applyDatePick(event.sourceStart, value);
8653
+ if (!newStart) return;
8654
+ const target = rescheduleToStart({ start: event.sourceStart, end: event.sourceEnd }, newStart);
8655
+ if (target && onEventReschedule) onEventReschedule(event.raw, target, event);
8656
+ },
8657
+ [onEventReschedule]
8658
+ );
8659
+ const reschedule = (0, import_react15.useMemo)(() => {
8660
+ if (!rescheduleOptions) return null;
8661
+ return {
8662
+ options: normalizeRescheduleOptions(rescheduleOptions),
8663
+ onPreset: handleReschedulePreset,
8664
+ onPick: handleReschedulePick
8665
+ };
8666
+ }, [rescheduleOptions, handleReschedulePreset, handleReschedulePick]);
8667
+ const safeMonthEventStyle = MONTH_EVENT_STYLES.has(monthEventStyle) ? monthEventStyle : "statusTag";
8668
+ const chipProps = {
8669
+ overlayMode,
8670
+ renderEventDetail,
8671
+ onEventClick,
8672
+ labels,
8673
+ monthEventStyle: safeMonthEventStyle,
8674
+ monthEventMaxChars,
8675
+ reschedule
8676
+ };
8677
+ const resourceLaneData = (0, import_react15.useMemo)(() => {
8678
+ if (view !== "resource") return null;
8679
+ return buildResourceLanes(queried, {
8680
+ resources,
8681
+ resourceLabels,
8682
+ getId: (e) => resolveResourceId(e.raw, resourceField),
8683
+ showUnassignedLane,
8684
+ unassignedLabel: labels.unassigned
8685
+ });
8686
+ }, [view, queried, resources, resourceLabels, resourceField, showUnassignedLane, labels]);
7657
8687
  const timeZoneOptions = (0, import_react15.useMemo)(() => {
7658
8688
  if (!showTimeZoneSelect) return null;
7659
8689
  const base = (timeZoneOptionsProp && timeZoneOptionsProp.length ? timeZoneOptionsProp : DEFAULT_TIME_ZONES).map(
@@ -7714,6 +8744,19 @@ var Calendar = (props) => {
7714
8744
  labels
7715
8745
  }
7716
8746
  );
8747
+ } else if (view === "resource") {
8748
+ body = /* @__PURE__ */ import_react15.default.createElement(
8749
+ ResourceView,
8750
+ {
8751
+ days: gridDays,
8752
+ now: nowWall,
8753
+ lanes: resourceLaneData,
8754
+ maxEventsPerDay,
8755
+ chipProps,
8756
+ labels,
8757
+ renderEmptyState
8758
+ }
8759
+ );
7717
8760
  } else if (view === "week" || view === "day") {
7718
8761
  body = /* @__PURE__ */ import_react15.default.createElement(
7719
8762
  TimeGridView,
@@ -7743,11 +8786,687 @@ var Calendar = (props) => {
7743
8786
  }
7744
8787
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "sm" }, toolbar, body);
7745
8788
  };
8789
+ Calendar.displayName = "Calendar";
7746
8790
 
7747
- // src/common-components/AutoTag.js
8791
+ // src/filter/FilterBuilder.jsx
7748
8792
  var import_react16 = __toESM(require("react"));
7749
8793
  var import_ui_extensions16 = require("@hubspot/ui-extensions");
7750
8794
 
8795
+ // src/filter/filterTree.js
8796
+ var NUMERIC_OPERATORS = ["EQ", "NEQ", "GT", "GTE", "LT", "LTE", "BETWEEN", "HAS_PROPERTY", "NOT_HAS_PROPERTY"];
8797
+ var FILTER_OPERATORS = {
8798
+ string: ["EQ", "NEQ", "CONTAINS_TOKEN", "NOT_CONTAINS_TOKEN", "HAS_PROPERTY", "NOT_HAS_PROPERTY"],
8799
+ number: NUMERIC_OPERATORS,
8800
+ date: NUMERIC_OPERATORS,
8801
+ datetime: NUMERIC_OPERATORS,
8802
+ enum: ["IN", "NOT_IN", "HAS_PROPERTY", "NOT_HAS_PROPERTY"],
8803
+ bool: ["EQ"]
8804
+ };
8805
+ var ALL_OPERATORS = [...new Set(Object.values(FILTER_OPERATORS).flat())];
8806
+ var BASE_OPERATOR_LABELS = {
8807
+ EQ: "is equal to",
8808
+ NEQ: "is not equal to",
8809
+ CONTAINS_TOKEN: "contains",
8810
+ NOT_CONTAINS_TOKEN: "doesn't contain",
8811
+ GT: "is greater than",
8812
+ GTE: "is greater than or equal to",
8813
+ LT: "is less than",
8814
+ LTE: "is less than or equal to",
8815
+ BETWEEN: "is between",
8816
+ IN: "is any of",
8817
+ NOT_IN: "is none of",
8818
+ HAS_PROPERTY: "is known",
8819
+ NOT_HAS_PROPERTY: "is unknown"
8820
+ };
8821
+ var DATE_OPERATOR_LABELS = {
8822
+ EQ: "is",
8823
+ NEQ: "is not",
8824
+ GT: "is after",
8825
+ GTE: "is on or after",
8826
+ LT: "is before",
8827
+ LTE: "is on or before"
8828
+ };
8829
+ var getOperatorOptions = (type, labelOverrides) => {
8830
+ const operators = FILTER_OPERATORS[type] || [];
8831
+ const dateLabels = type === "date" || type === "datetime" ? DATE_OPERATOR_LABELS : null;
8832
+ return operators.map((operator) => ({
8833
+ label: (labelOverrides == null ? void 0 : labelOverrides[operator]) ?? (dateLabels == null ? void 0 : dateLabels[operator]) ?? BASE_OPERATOR_LABELS[operator] ?? operator,
8834
+ value: operator
8835
+ }));
8836
+ };
8837
+ var operatorExpectsValue = (operator) => operator !== "HAS_PROPERTY" && operator !== "NOT_HAS_PROPERTY";
8838
+ var operatorExpectsHighValue = (operator) => operator === "BETWEEN";
8839
+ var operatorExpectsValues = (operator) => operator === "IN" || operator === "NOT_IN";
8840
+ var isGroupNode = (node) => node != null && node.type === "group";
8841
+ var isConditionNode = (node) => node != null && node.type === "condition";
8842
+ var createCondition = (property = "", operator = "", value, highValue) => {
8843
+ const node = { type: "condition", property, operator };
8844
+ if (value !== void 0) node.value = value;
8845
+ if (highValue !== void 0) node.highValue = highValue;
8846
+ return node;
8847
+ };
8848
+ var createGroup = (operator = "AND", filters = []) => ({
8849
+ type: "group",
8850
+ operator,
8851
+ filters
8852
+ });
8853
+ var getNodeAtPath = (tree, path) => {
8854
+ let node = tree;
8855
+ for (const index of path || []) {
8856
+ if (!isGroupNode(node) || !Array.isArray(node.filters)) return void 0;
8857
+ node = node.filters[index];
8858
+ if (node === void 0) return void 0;
8859
+ }
8860
+ return node;
8861
+ };
8862
+ var updateNodeAtPath = (node, path, fn) => {
8863
+ if (!path || path.length === 0) return fn(node);
8864
+ if (!isGroupNode(node) || !Array.isArray(node.filters)) {
8865
+ throw new Error("filterTree: path descends into a non-group node");
8866
+ }
8867
+ const [index, ...rest] = path;
8868
+ if (typeof index !== "number" || index < 0 || index >= node.filters.length) {
8869
+ throw new Error(`filterTree: no filter at index ${index} (group has ${node.filters.length})`);
8870
+ }
8871
+ const filters = node.filters.slice();
8872
+ filters[index] = updateNodeAtPath(filters[index], rest, fn);
8873
+ return { ...node, filters };
8874
+ };
8875
+ var addFilter = (tree, path, node) => updateNodeAtPath(tree, path, (group) => {
8876
+ if (!isGroupNode(group)) {
8877
+ throw new Error("filterTree: addFilter path must point at a group node");
8878
+ }
8879
+ return { ...group, filters: [...group.filters || [], node] };
8880
+ });
8881
+ var updateFilter = (tree, path, patch) => updateNodeAtPath(
8882
+ tree,
8883
+ path,
8884
+ (node) => typeof patch === "function" ? patch(node) : { ...node, ...patch }
8885
+ );
8886
+ var removeFilter = (tree, path, options = {}) => {
8887
+ if (!path || path.length === 0) {
8888
+ throw new Error("filterTree: cannot remove the root group");
8889
+ }
8890
+ const doRemove = (currentTree, targetPath) => {
8891
+ const parentPath = targetPath.slice(0, -1);
8892
+ const index = targetPath[targetPath.length - 1];
8893
+ return updateNodeAtPath(currentTree, parentPath, (group) => {
8894
+ if (!isGroupNode(group)) {
8895
+ throw new Error("filterTree: removeFilter parent is not a group node");
8896
+ }
8897
+ if (typeof index !== "number" || index < 0 || index >= group.filters.length) {
8898
+ throw new Error(`filterTree: no filter at index ${index} (group has ${group.filters.length})`);
8899
+ }
8900
+ return { ...group, filters: group.filters.filter((_, i) => i !== index) };
8901
+ });
8902
+ };
8903
+ let next = doRemove(tree, path);
8904
+ if (options.pruneEmptyGroups) {
8905
+ let parentPath = path.slice(0, -1);
8906
+ while (parentPath.length > 0) {
8907
+ const parent = getNodeAtPath(next, parentPath);
8908
+ if (!isGroupNode(parent) || parent.filters.length > 0) break;
8909
+ next = doRemove(next, parentPath);
8910
+ parentPath = parentPath.slice(0, -1);
8911
+ }
8912
+ }
8913
+ return next;
8914
+ };
8915
+ var cloneNode = (node) => {
8916
+ if (Array.isArray(node)) return node.map(cloneNode);
8917
+ if (node !== null && typeof node === "object") {
8918
+ const copy = {};
8919
+ for (const key of Object.keys(node)) copy[key] = cloneNode(node[key]);
8920
+ return copy;
8921
+ }
8922
+ return node;
8923
+ };
8924
+ var duplicateFilter = (tree, path) => {
8925
+ if (!path || path.length === 0) {
8926
+ throw new Error("filterTree: cannot duplicate the root group");
8927
+ }
8928
+ const parentPath = path.slice(0, -1);
8929
+ const index = path[path.length - 1];
8930
+ return updateNodeAtPath(tree, parentPath, (group) => {
8931
+ if (!isGroupNode(group)) {
8932
+ throw new Error("filterTree: duplicateFilter parent is not a group node");
8933
+ }
8934
+ if (typeof index !== "number" || index < 0 || index >= group.filters.length) {
8935
+ throw new Error(`filterTree: no filter at index ${index} (group has ${group.filters.length})`);
8936
+ }
8937
+ const filters = group.filters.slice();
8938
+ filters.splice(index + 1, 0, cloneNode(group.filters[index]));
8939
+ return { ...group, filters };
8940
+ });
8941
+ };
8942
+ var countConditions = (node) => {
8943
+ if (isConditionNode(node)) return 1;
8944
+ if (!isGroupNode(node) || !Array.isArray(node.filters)) return 0;
8945
+ return node.filters.reduce((sum, child) => sum + countConditions(child), 0);
8946
+ };
8947
+ var changeConditionProperty = (condition, property) => {
8948
+ const name = typeof property === "string" ? property : (property == null ? void 0 : property.name) ?? "";
8949
+ const type = typeof property === "object" && property !== null ? property.type : void 0;
8950
+ const operators = FILTER_OPERATORS[type] || [];
8951
+ const operator = operators.includes(condition == null ? void 0 : condition.operator) ? condition.operator : operators[0] ?? "";
8952
+ const next = { type: "condition", property: name, operator };
8953
+ if (operatorExpectsValues(operator)) next.value = [];
8954
+ return next;
8955
+ };
8956
+ var changeConditionOperator = (condition, operator) => {
8957
+ const next = { type: "condition", property: (condition == null ? void 0 : condition.property) ?? "", operator };
8958
+ if (!operatorExpectsValue(operator)) return next;
8959
+ if (operatorExpectsValues(operator)) {
8960
+ next.value = Array.isArray(condition == null ? void 0 : condition.value) ? condition.value : [];
8961
+ return next;
8962
+ }
8963
+ if ((condition == null ? void 0 : condition.value) !== void 0 && !Array.isArray(condition.value)) {
8964
+ next.value = condition.value;
8965
+ }
8966
+ if (operatorExpectsHighValue(operator) && (condition == null ? void 0 : condition.highValue) !== void 0) {
8967
+ next.highValue = condition.highValue;
8968
+ }
8969
+ return next;
8970
+ };
8971
+ var isMissing = (value) => value == null || value === "";
8972
+ var validateTree = (tree, properties) => {
8973
+ const errors = [];
8974
+ const byName = Array.isArray(properties) ? new Map(properties.map((property) => [property.name, property])) : null;
8975
+ if (!isGroupNode(tree)) {
8976
+ return { valid: false, errors: [{ path: [], message: "Root must be a group node." }] };
8977
+ }
8978
+ const visitCondition = (node, path) => {
8979
+ if (!node.property) {
8980
+ errors.push({ path, message: "Condition is missing a property." });
8981
+ return;
8982
+ }
8983
+ let type;
8984
+ if (byName) {
8985
+ const def = byName.get(node.property);
8986
+ if (!def) {
8987
+ errors.push({ path, message: `Unknown property "${node.property}".` });
8988
+ return;
8989
+ }
8990
+ type = def.type;
8991
+ }
8992
+ const allowed = type !== void 0 ? FILTER_OPERATORS[type] || [] : ALL_OPERATORS;
8993
+ if (!node.operator) {
8994
+ errors.push({ path, message: "Condition is missing an operator." });
8995
+ return;
8996
+ }
8997
+ if (!allowed.includes(node.operator)) {
8998
+ errors.push({
8999
+ path,
9000
+ message: type !== void 0 ? `Operator "${node.operator}" is not valid for type "${type}".` : `Unknown operator "${node.operator}".`
9001
+ });
9002
+ return;
9003
+ }
9004
+ if (!operatorExpectsValue(node.operator)) return;
9005
+ if (operatorExpectsValues(node.operator)) {
9006
+ if (!Array.isArray(node.value) || node.value.length === 0) {
9007
+ errors.push({ path, message: `Operator "${node.operator}" requires at least one value.` });
9008
+ }
9009
+ return;
9010
+ }
9011
+ if (isMissing(node.value)) {
9012
+ errors.push({ path, message: `Operator "${node.operator}" requires a value.` });
9013
+ }
9014
+ if (operatorExpectsHighValue(node.operator) && isMissing(node.highValue)) {
9015
+ errors.push({ path, message: 'Operator "BETWEEN" requires an upper bound (highValue).' });
9016
+ }
9017
+ };
9018
+ const visit = (node, path) => {
9019
+ if (node == null || typeof node !== "object") {
9020
+ errors.push({ path, message: "Filter node must be an object." });
9021
+ return;
9022
+ }
9023
+ if (node.type === "group") {
9024
+ if (node.operator !== "AND" && node.operator !== "OR") {
9025
+ errors.push({ path, message: `Group operator must be "AND" or "OR" (got "${node.operator}").` });
9026
+ }
9027
+ if (!Array.isArray(node.filters)) {
9028
+ errors.push({ path, message: "Group filters must be an array." });
9029
+ return;
9030
+ }
9031
+ if (node.filters.length === 0 && path.length > 0) {
9032
+ errors.push({ path, message: "Group has no filters." });
9033
+ }
9034
+ node.filters.forEach((child, index) => visit(child, [...path, index]));
9035
+ return;
9036
+ }
9037
+ if (node.type === "condition") {
9038
+ visitCondition(node, path);
9039
+ return;
9040
+ }
9041
+ errors.push({ path, message: `Unknown node type "${node.type}".` });
9042
+ };
9043
+ visit(tree, []);
9044
+ return { valid: errors.length === 0, errors };
9045
+ };
9046
+ var isDateValueObject4 = (value) => value != null && typeof value === "object" && typeof value.year === "number" && typeof value.month === "number" && typeof value.date === "number";
9047
+ var dateValueToTimestamp = (dateObj) => new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
9048
+ var coerceCrmValue = (value) => {
9049
+ if (isDateValueObject4(value)) return dateValueToTimestamp(value);
9050
+ if (typeof value === "boolean") return String(value);
9051
+ return value;
9052
+ };
9053
+ var conditionToCrmFilter = (condition, options = {}) => {
9054
+ const { coerceValues = true } = options;
9055
+ if (!isConditionNode(condition)) {
9056
+ throw new Error("filterTree: conditionToCrmFilter expects a condition node");
9057
+ }
9058
+ if (!condition.property) {
9059
+ throw new Error("filterTree: condition is missing a property");
9060
+ }
9061
+ if (!condition.operator) {
9062
+ throw new Error(`filterTree: condition on "${condition.property}" is missing an operator`);
9063
+ }
9064
+ const coerce = coerceValues ? coerceCrmValue : (value) => value;
9065
+ const filter = { propertyName: condition.property, operator: condition.operator };
9066
+ if (!operatorExpectsValue(condition.operator)) return filter;
9067
+ if (operatorExpectsValues(condition.operator)) {
9068
+ const values = Array.isArray(condition.value) ? condition.value : condition.value == null ? [] : [condition.value];
9069
+ filter.values = values.map(coerce);
9070
+ return filter;
9071
+ }
9072
+ filter.value = coerce(condition.value);
9073
+ if (operatorExpectsHighValue(condition.operator)) {
9074
+ filter.highValue = coerce(condition.highValue);
9075
+ }
9076
+ return filter;
9077
+ };
9078
+ var nodeToDnf = (node, path, options) => {
9079
+ if (isConditionNode(node)) {
9080
+ return [[conditionToCrmFilter(node, options)]];
9081
+ }
9082
+ if (!isGroupNode(node)) {
9083
+ throw new Error(`filterTree: unknown node type "${node == null ? void 0 : node.type}" at [${path.join(", ")}]`);
9084
+ }
9085
+ if (!Array.isArray(node.filters) || node.filters.length === 0) {
9086
+ throw new Error(
9087
+ `filterTree: empty group at [${path.join(", ")}] cannot be converted \u2014 remove it or add a condition (run validateTree first)`
9088
+ );
9089
+ }
9090
+ const childDnfs = node.filters.map((child, index) => nodeToDnf(child, [...path, index], options));
9091
+ if (node.operator === "OR") {
9092
+ return childDnfs.flat();
9093
+ }
9094
+ return childDnfs.reduce(
9095
+ (acc, childDnf) => {
9096
+ const out = [];
9097
+ for (const left of acc) {
9098
+ for (const right of childDnf) out.push([...left, ...right]);
9099
+ }
9100
+ return out;
9101
+ },
9102
+ [[]]
9103
+ );
9104
+ };
9105
+ var toCrmSearchFilterGroups = (tree, options = {}) => {
9106
+ const {
9107
+ maxGroups = 5,
9108
+ maxFiltersPerGroup = 6,
9109
+ maxTotalFilters = 18,
9110
+ enforceLimits = true,
9111
+ coerceValues = true
9112
+ } = options;
9113
+ if (!isGroupNode(tree)) {
9114
+ throw new Error("filterTree: toCrmSearchFilterGroups expects a group node at the root");
9115
+ }
9116
+ if (!Array.isArray(tree.filters) || tree.filters.length === 0) {
9117
+ return { filterGroups: [] };
9118
+ }
9119
+ const conjunctions = nodeToDnf(tree, [], { coerceValues });
9120
+ if (enforceLimits) {
9121
+ if (conjunctions.length > maxGroups) {
9122
+ throw new Error(
9123
+ `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).`
9124
+ );
9125
+ }
9126
+ const oversized = conjunctions.findIndex((filters) => filters.length > maxFiltersPerGroup);
9127
+ if (oversized !== -1) {
9128
+ throw new Error(
9129
+ `filterTree: filterGroup ${oversized} has ${conjunctions[oversized].length} filters; HubSpot CRM search allows at most ${maxFiltersPerGroup} per group.`
9130
+ );
9131
+ }
9132
+ const total = conjunctions.reduce((sum, filters) => sum + filters.length, 0);
9133
+ if (total > maxTotalFilters) {
9134
+ throw new Error(
9135
+ `filterTree: tree expands to ${total} total filters; HubSpot CRM search allows at most ${maxTotalFilters}.`
9136
+ );
9137
+ }
9138
+ }
9139
+ return { filterGroups: conjunctions.map((filters) => ({ filters })) };
9140
+ };
9141
+
9142
+ // src/filter/FilterBuilder.jsx
9143
+ var DEFAULT_LABELS6 = {
9144
+ addFilter: "Add filter",
9145
+ addGroup: "Add filter group",
9146
+ remove: "Remove filter",
9147
+ removeGroup: "Delete group",
9148
+ cloneGroup: "Clone group",
9149
+ group: "Group",
9150
+ and: "AND",
9151
+ or: "OR",
9152
+ property: "Select a property",
9153
+ operator: "Select an operator",
9154
+ value: "Enter a value",
9155
+ values: "Select values",
9156
+ between: "and",
9157
+ empty: "No filters yet.",
9158
+ true: "True",
9159
+ false: "False"
9160
+ };
9161
+ var GROUP_OPERATOR_OPTIONS = (labels) => [
9162
+ { label: labels.and, value: "AND" },
9163
+ { label: labels.or, value: "OR" }
9164
+ ];
9165
+ var normalizeTree = (tree) => isGroupNode(tree) ? tree : createGroup("AND", []);
9166
+ var pathName = (prefix, path, suffix) => `${prefix}-${path.length ? path.join("-") : "root"}-${suffix}`;
9167
+ var ValueEditor = ({ condition, propertyDef, path, namePrefix, labels, readOnly, onPatch }) => {
9168
+ const { operator } = condition;
9169
+ if (!operator || !operatorExpectsValue(operator)) return null;
9170
+ const type = (propertyDef == null ? void 0 : propertyDef.type) || "string";
9171
+ const setValue = (value) => onPatch(path, { value });
9172
+ const setHighValue = (highValue) => onPatch(path, { highValue });
9173
+ if (operatorExpectsValues(operator)) {
9174
+ return /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9175
+ import_ui_extensions16.MultiSelect,
9176
+ {
9177
+ label: "",
9178
+ name: pathName(namePrefix, path, "value"),
9179
+ placeholder: labels.values,
9180
+ options: (propertyDef == null ? void 0 : propertyDef.options) || [],
9181
+ value: Array.isArray(condition.value) ? condition.value : [],
9182
+ readOnly,
9183
+ onChange: setValue
9184
+ }
9185
+ ));
9186
+ }
9187
+ if (type === "bool") {
9188
+ return /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9189
+ import_ui_extensions16.Select,
9190
+ {
9191
+ label: "",
9192
+ name: pathName(namePrefix, path, "value"),
9193
+ placeholder: labels.value,
9194
+ options: [
9195
+ { label: labels.true, value: "true" },
9196
+ { label: labels.false, value: "false" }
9197
+ ],
9198
+ value: condition.value,
9199
+ readOnly,
9200
+ onChange: setValue
9201
+ }
9202
+ ));
9203
+ }
9204
+ if (type === "enum") {
9205
+ return /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9206
+ import_ui_extensions16.Select,
9207
+ {
9208
+ label: "",
9209
+ name: pathName(namePrefix, path, "value"),
9210
+ placeholder: labels.value,
9211
+ options: (propertyDef == null ? void 0 : propertyDef.options) || [],
9212
+ value: condition.value,
9213
+ readOnly,
9214
+ onChange: setValue
9215
+ }
9216
+ ));
9217
+ }
9218
+ const isBetween = operatorExpectsHighValue(operator);
9219
+ if (type === "number") {
9220
+ return /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, null, /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9221
+ import_ui_extensions16.NumberInput,
9222
+ {
9223
+ label: "",
9224
+ name: pathName(namePrefix, path, "value"),
9225
+ placeholder: labels.value,
9226
+ value: condition.value ?? "",
9227
+ readOnly,
9228
+ onChange: setValue
9229
+ }
9230
+ )), isBetween && /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, null, /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Text, { variant: "microcopy" }, labels.between), /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9231
+ import_ui_extensions16.NumberInput,
9232
+ {
9233
+ label: "",
9234
+ name: pathName(namePrefix, path, "high-value"),
9235
+ placeholder: labels.value,
9236
+ value: condition.highValue ?? "",
9237
+ readOnly,
9238
+ onChange: setHighValue
9239
+ }
9240
+ ))));
9241
+ }
9242
+ if (type === "date" || type === "datetime") {
9243
+ return /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, null, /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9244
+ import_ui_extensions16.DateInput,
9245
+ {
9246
+ label: "",
9247
+ name: pathName(namePrefix, path, "value"),
9248
+ format: "medium",
9249
+ value: condition.value ?? null,
9250
+ readOnly,
9251
+ onChange: setValue
9252
+ }
9253
+ )), isBetween && /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, null, /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Text, { variant: "microcopy" }, labels.between), /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9254
+ import_ui_extensions16.DateInput,
9255
+ {
9256
+ label: "",
9257
+ name: pathName(namePrefix, path, "high-value"),
9258
+ format: "medium",
9259
+ value: condition.highValue ?? null,
9260
+ readOnly,
9261
+ onChange: setHighValue
9262
+ }
9263
+ ))));
9264
+ }
9265
+ return /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9266
+ import_ui_extensions16.Input,
9267
+ {
9268
+ label: "",
9269
+ name: pathName(namePrefix, path, "value"),
9270
+ placeholder: labels.value,
9271
+ value: condition.value ?? "",
9272
+ readOnly,
9273
+ onChange: setValue
9274
+ }
9275
+ ));
9276
+ };
9277
+ var IconButton = ({ icon, label, onClick, size }) => /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Button, { size: "extra-small", variant: "transparent", onClick }, /* @__PURE__ */ import_react16.default.createElement(Icon, { name: icon, screenReaderText: label, ...size ? { size } : {} }));
9278
+ var ConditionRow = ({
9279
+ condition,
9280
+ path,
9281
+ properties,
9282
+ propertyOptions,
9283
+ namePrefix,
9284
+ labels,
9285
+ operatorLabels,
9286
+ readOnly,
9287
+ onPropertyChange,
9288
+ onOperatorChange,
9289
+ onPatch,
9290
+ onRemove
9291
+ }) => {
9292
+ const propertyDef = properties.find((property) => property.name === condition.property);
9293
+ const operatorOptions = getOperatorOptions(propertyDef == null ? void 0 : propertyDef.type, operatorLabels);
9294
+ return /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Flex, { direction: "row", gap: "xs", align: "center", wrap: "wrap" }, /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9295
+ import_ui_extensions16.Select,
9296
+ {
9297
+ label: "",
9298
+ name: pathName(namePrefix, path, "property"),
9299
+ placeholder: labels.property,
9300
+ options: propertyOptions,
9301
+ value: condition.property || void 0,
9302
+ readOnly,
9303
+ onChange: (name) => onPropertyChange(path, name)
9304
+ }
9305
+ )), /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { flex: 1 }, /* @__PURE__ */ import_react16.default.createElement(
9306
+ import_ui_extensions16.Select,
9307
+ {
9308
+ label: "",
9309
+ name: pathName(namePrefix, path, "operator"),
9310
+ placeholder: labels.operator,
9311
+ options: operatorOptions,
9312
+ value: condition.operator || void 0,
9313
+ readOnly: readOnly || !condition.property,
9314
+ onChange: (operator) => onOperatorChange(path, operator)
9315
+ }
9316
+ )), /* @__PURE__ */ import_react16.default.createElement(
9317
+ ValueEditor,
9318
+ {
9319
+ condition,
9320
+ propertyDef,
9321
+ path,
9322
+ namePrefix,
9323
+ labels,
9324
+ readOnly,
9325
+ onPatch
9326
+ }
9327
+ ), !readOnly && /* @__PURE__ */ import_react16.default.createElement(IconButton, { icon: "remove", label: labels.remove, onClick: () => onRemove(path) }));
9328
+ };
9329
+ var GroupOperatorSeparator = ({ group, path, namePrefix, labels, readOnly, index, onGroupOperatorChange }) => {
9330
+ if (readOnly) {
9331
+ return /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Text, { variant: "microcopy", format: { fontWeight: "demibold" } }, group.operator === "OR" ? labels.or : labels.and);
9332
+ }
9333
+ return /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Box, { alignSelf: "start" }, /* @__PURE__ */ import_react16.default.createElement(
9334
+ import_ui_extensions16.Select,
9335
+ {
9336
+ label: "",
9337
+ name: pathName(namePrefix, path, `separator-${index}`),
9338
+ variant: "transparent",
9339
+ options: GROUP_OPERATOR_OPTIONS(labels),
9340
+ value: group.operator,
9341
+ onChange: (operator) => onGroupOperatorChange(path, operator)
9342
+ }
9343
+ ));
9344
+ };
9345
+ var GroupEditor = ({ group, path, depth, ctx }) => {
9346
+ const { labels, maxDepth, readOnly, namePrefix, handlers } = ctx;
9347
+ const isRoot = path.length === 0;
9348
+ const filters = Array.isArray(group.filters) ? group.filters : [];
9349
+ let groupNumber = 0;
9350
+ const children = filters.map((child, index) => {
9351
+ const childPath = [...path, index];
9352
+ if (isGroupNode(child)) groupNumber += 1;
9353
+ const row = isGroupNode(child) ? /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Tile, { compact: true }, /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Flex, { direction: "row", justify: "between", align: "center" }, /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Text, { format: { fontWeight: "demibold" } }, `${labels.group}\xA0${groupNumber}`), !readOnly && /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Flex, { direction: "row", gap: "xs", justify: "end" }, /* @__PURE__ */ import_react16.default.createElement(
9354
+ IconButton,
9355
+ {
9356
+ icon: "copy",
9357
+ label: labels.cloneGroup,
9358
+ size: "sm",
9359
+ onClick: () => handlers.onDuplicate(childPath)
9360
+ }
9361
+ ), /* @__PURE__ */ import_react16.default.createElement(
9362
+ IconButton,
9363
+ {
9364
+ icon: "delete",
9365
+ label: labels.removeGroup,
9366
+ size: "sm",
9367
+ onClick: () => handlers.onRemove(childPath)
9368
+ }
9369
+ ))), /* @__PURE__ */ import_react16.default.createElement(GroupEditor, { group: child, path: childPath, depth: depth + 1, ctx }))) : /* @__PURE__ */ import_react16.default.createElement(
9370
+ ConditionRow,
9371
+ {
9372
+ condition: child,
9373
+ path: childPath,
9374
+ properties: ctx.properties,
9375
+ propertyOptions: ctx.propertyOptions,
9376
+ namePrefix,
9377
+ labels,
9378
+ operatorLabels: ctx.operatorLabels,
9379
+ readOnly,
9380
+ onPropertyChange: handlers.onPropertyChange,
9381
+ onOperatorChange: handlers.onOperatorChange,
9382
+ onPatch: handlers.onPatch,
9383
+ onRemove: handlers.onRemove
9384
+ }
9385
+ );
9386
+ return /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, { key: childPath.join("-") }, index > 0 && /* @__PURE__ */ import_react16.default.createElement(
9387
+ GroupOperatorSeparator,
9388
+ {
9389
+ group,
9390
+ path,
9391
+ namePrefix,
9392
+ labels,
9393
+ readOnly,
9394
+ index,
9395
+ onGroupOperatorChange: handlers.onGroupOperatorChange
9396
+ }
9397
+ ), row);
9398
+ });
9399
+ return (
9400
+ // Nested groups pack tight (row / AND-OR / row reads as one unit, like
9401
+ // HubSpot's builder); the root keeps more air between its sections.
9402
+ /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Flex, { direction: "column", gap: isRoot ? "sm" : "xs" }, isRoot && filters.length === 0 && /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Text, { variant: "microcopy" }, labels.empty), children, !readOnly && /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Flex, { direction: "row", gap: "md", align: "center" }, /* @__PURE__ */ import_react16.default.createElement(
9403
+ import_ui_extensions16.Button,
9404
+ {
9405
+ size: "extra-small",
9406
+ variant: "transparent",
9407
+ onClick: () => handlers.onAddCondition(path)
9408
+ },
9409
+ /* @__PURE__ */ import_react16.default.createElement(Icon, { name: "add" }),
9410
+ " ",
9411
+ labels.addFilter
9412
+ ), depth < maxDepth && /* @__PURE__ */ import_react16.default.createElement(
9413
+ import_ui_extensions16.Button,
9414
+ {
9415
+ size: "extra-small",
9416
+ variant: "transparent",
9417
+ onClick: () => handlers.onAddGroup(path)
9418
+ },
9419
+ /* @__PURE__ */ import_react16.default.createElement(Icon, { name: "add" }),
9420
+ " ",
9421
+ labels.addGroup
9422
+ )))
9423
+ );
9424
+ };
9425
+ var FilterBuilder = ({
9426
+ properties = [],
9427
+ value,
9428
+ defaultValue,
9429
+ onChange,
9430
+ maxDepth = 2,
9431
+ labels: labelOverrides,
9432
+ operatorLabels,
9433
+ readOnly = false,
9434
+ namePrefix = "filter-builder",
9435
+ ...rest
9436
+ }) => {
9437
+ const labels = (0, import_react16.useMemo)(() => ({ ...DEFAULT_LABELS6, ...labelOverrides || {} }), [labelOverrides]);
9438
+ const [internalTree, setInternalTree] = (0, import_react16.useState)(() => normalizeTree(defaultValue));
9439
+ const isControlled = value !== void 0;
9440
+ const tree = isControlled ? normalizeTree(value) : internalTree;
9441
+ const propertyOptions = (0, import_react16.useMemo)(
9442
+ () => properties.map((property) => ({ label: property.label ?? property.name, value: property.name })),
9443
+ [properties]
9444
+ );
9445
+ const commit = (next) => {
9446
+ if (!isControlled) setInternalTree(next);
9447
+ onChange == null ? void 0 : onChange(next);
9448
+ };
9449
+ const handlers = {
9450
+ onAddCondition: (groupPath) => commit(addFilter(tree, groupPath, createCondition())),
9451
+ onAddGroup: (groupPath) => commit(addFilter(tree, groupPath, createGroup("AND", [createCondition()]))),
9452
+ onRemove: (path) => commit(removeFilter(tree, path, { pruneEmptyGroups: true })),
9453
+ onDuplicate: (path) => commit(duplicateFilter(tree, path)),
9454
+ onGroupOperatorChange: (groupPath, operator) => commit(updateFilter(tree, groupPath, { operator })),
9455
+ onPropertyChange: (path, name) => {
9456
+ const def = properties.find((property) => property.name === name);
9457
+ commit(updateFilter(tree, path, (node) => changeConditionProperty(node, def ?? name)));
9458
+ },
9459
+ onOperatorChange: (path, operator) => commit(updateFilter(tree, path, (node) => changeConditionOperator(node, operator))),
9460
+ onPatch: (path, patch) => commit(updateFilter(tree, path, patch))
9461
+ };
9462
+ const ctx = { properties, propertyOptions, labels, operatorLabels, maxDepth, readOnly, namePrefix, handlers };
9463
+ return /* @__PURE__ */ import_react16.default.createElement(import_ui_extensions16.Flex, { direction: "column", gap: "sm", ...rest }, /* @__PURE__ */ import_react16.default.createElement(GroupEditor, { group: tree, path: [], depth: 1, ctx }));
9464
+ };
9465
+
9466
+ // src/common-components/AutoTag.js
9467
+ var import_react17 = __toESM(require("react"));
9468
+ var import_ui_extensions17 = require("@hubspot/ui-extensions");
9469
+
7751
9470
  // src/utils/tagVariants.js
7752
9471
  var DEFAULT_VARIANT = "default";
7753
9472
  var DANGER_VARIANT = "danger";
@@ -7944,16 +9663,16 @@ var AutoTag = ({
7944
9663
  overrides,
7945
9664
  fallback
7946
9665
  });
7947
- return import_react16.default.createElement(
7948
- import_ui_extensions16.Tag,
9666
+ return import_react17.default.createElement(
9667
+ import_ui_extensions17.Tag,
7949
9668
  { variant: resolvedVariant, ...props },
7950
9669
  displayValue
7951
9670
  );
7952
9671
  };
7953
9672
 
7954
9673
  // src/common-components/AutoStatusTag.js
7955
- var import_react17 = __toESM(require("react"));
7956
- var import_ui_extensions17 = require("@hubspot/ui-extensions");
9674
+ var import_react18 = __toESM(require("react"));
9675
+ var import_ui_extensions18 = require("@hubspot/ui-extensions");
7957
9676
  var AutoStatusTag = ({
7958
9677
  value,
7959
9678
  status,
@@ -7969,20 +9688,20 @@ var AutoStatusTag = ({
7969
9688
  overrides,
7970
9689
  fallback
7971
9690
  });
7972
- return import_react17.default.createElement(
7973
- import_ui_extensions17.StatusTag,
9691
+ return import_react18.default.createElement(
9692
+ import_ui_extensions18.StatusTag,
7974
9693
  { variant: resolvedVariant, ...props },
7975
9694
  displayValue
7976
9695
  );
7977
9696
  };
7978
9697
 
7979
9698
  // src/common-components/CrmLookupSelect.js
7980
- var import_react19 = __toESM(require("react"));
7981
- var import_ui_extensions19 = require("@hubspot/ui-extensions");
9699
+ var import_react20 = __toESM(require("react"));
9700
+ var import_ui_extensions20 = require("@hubspot/ui-extensions");
7982
9701
 
7983
9702
  // src/utils/crmSearchAdapters.js
7984
- var import_react18 = __toESM(require("react"));
7985
- var import_ui_extensions18 = require("@hubspot/ui-extensions");
9703
+ var import_react19 = __toESM(require("react"));
9704
+ var import_ui_extensions19 = require("@hubspot/ui-extensions");
7986
9705
 
7987
9706
  // src/utils/objectPath.js
7988
9707
  var getByPath = (obj, path) => {
@@ -8099,9 +9818,9 @@ var useCrmSearchDataSource = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) =>
8099
9818
  error,
8100
9819
  mapResponse
8101
9820
  } = options;
8102
- const config = (0, import_react18.useMemo)(() => buildCrmSearchConfig(params, options), [params, options]);
8103
- const response = (0, import_ui_extensions18.useCrmSearch)(config, format);
8104
- return (0, import_react18.useMemo)(() => {
9821
+ const config = (0, import_react19.useMemo)(() => buildCrmSearchConfig(params, options), [params, options]);
9822
+ const response = (0, import_ui_extensions19.useCrmSearch)(config, format);
9823
+ return (0, import_react19.useMemo)(() => {
8105
9824
  var _a;
8106
9825
  const rows = mapResponse ? mapResponse(response) : normalizeCrmSearchRows(response, { idField: rowIdField, ...row || EMPTY_OBJECT });
8107
9826
  const resolvedTotal = typeof totalCount === "function" ? totalCount(response) : totalCount ?? pickTotal(response, rows.length);
@@ -8139,7 +9858,7 @@ var crmSearchResultToOption = (row, options = EMPTY_OBJECT) => {
8139
9858
  var useCrmSearchOptions = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
8140
9859
  const dataSource = useCrmSearchDataSource(params, options);
8141
9860
  const optionConfig = options.option || options;
8142
- return (0, import_react18.useMemo)(() => ({
9861
+ return (0, import_react19.useMemo)(() => ({
8143
9862
  ...dataSource,
8144
9863
  options: dataSource.rows.map((row) => crmSearchResultToOption(row, optionConfig))
8145
9864
  }), [dataSource, optionConfig]);
@@ -8268,29 +9987,29 @@ var CrmDataTable = ({
8268
9987
  ...props
8269
9988
  }) => {
8270
9989
  var _a, _b;
8271
- const [params, setParams] = (0, import_react18.useState)({ search: "", filters: {}, sort: null });
8272
- const resolvedProperties = (0, import_react18.useMemo)(() => properties, [properties]);
8273
- const resolvedColumns = (0, import_react18.useMemo)(
9990
+ const [params, setParams] = (0, import_react19.useState)({ search: "", filters: {}, sort: null });
9991
+ const resolvedProperties = (0, import_react19.useMemo)(() => properties, [properties]);
9992
+ const resolvedColumns = (0, import_react19.useMemo)(
8274
9993
  () => columns || inferCrmColumns(resolvedProperties),
8275
9994
  [columns, resolvedProperties]
8276
9995
  );
8277
9996
  const resolvedSearchFields = searchFields || resolvedProperties;
8278
- const autoFilterFields = (0, import_react18.useMemo)(
9997
+ const autoFilterFields = (0, import_react19.useMemo)(
8279
9998
  () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
8280
9999
  [autoFilters, resolvedProperties]
8281
10000
  );
8282
- const autoFilterLabelsRef = (0, import_react18.useRef)({});
8283
- const defaultPropertyMap = (0, import_react18.useMemo)(
10001
+ const autoFilterLabelsRef = (0, import_react19.useRef)({});
10002
+ const defaultPropertyMap = (0, import_react19.useMemo)(
8284
10003
  () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
8285
10004
  [resolvedProperties]
8286
10005
  );
8287
10006
  const effectivePropertyMap = propertyMap || defaultPropertyMap;
8288
- const resolvedSortMap = (0, import_react18.useMemo)(
10007
+ const resolvedSortMap = (0, import_react19.useMemo)(
8289
10008
  () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
8290
10009
  [sortMap, effectivePropertyMap]
8291
10010
  );
8292
10011
  const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
8293
- const dataSourceOptions = (0, import_react18.useMemo)(
10012
+ const dataSourceOptions = (0, import_react19.useMemo)(
8294
10013
  () => ({
8295
10014
  objectType: resolveCrmObjectType(objectType),
8296
10015
  properties: resolvedProperties,
@@ -8304,18 +10023,18 @@ var CrmDataTable = ({
8304
10023
  }),
8305
10024
  [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
8306
10025
  );
8307
- const [serverQuerying, setServerQuerying] = (0, import_react18.useState)(!!serverSide);
10026
+ const [serverQuerying, setServerQuerying] = (0, import_react19.useState)(!!serverSide);
8308
10027
  const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
8309
10028
  const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
8310
- const queryKey = (0, import_react18.useMemo)(
10029
+ const queryKey = (0, import_react19.useMemo)(
8311
10030
  () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
8312
10031
  [effectiveParams, objectType, resolvedProperties, pageLength]
8313
10032
  );
8314
- const [accumulatedRows, setAccumulatedRows] = (0, import_react18.useState)(EMPTY_ARRAY);
8315
- const [requestedPage, setRequestedPage] = (0, import_react18.useState)(1);
8316
- const lastQueryKeyRef = (0, import_react18.useRef)(queryKey);
10033
+ const [accumulatedRows, setAccumulatedRows] = (0, import_react19.useState)(EMPTY_ARRAY);
10034
+ const [requestedPage, setRequestedPage] = (0, import_react19.useState)(1);
10035
+ const lastQueryKeyRef = (0, import_react19.useRef)(queryKey);
8317
10036
  const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
8318
- (0, import_react18.useEffect)(() => {
10037
+ (0, import_react19.useEffect)(() => {
8319
10038
  var _a2;
8320
10039
  if (lastQueryKeyRef.current !== queryKey) {
8321
10040
  lastQueryKeyRef.current = queryKey;
@@ -8325,12 +10044,12 @@ var CrmDataTable = ({
8325
10044
  const currentPage = ((_a2 = dataSource.pagination) == null ? void 0 : _a2.currentPage) || 1;
8326
10045
  setAccumulatedRows((prev) => currentPage <= 1 ? dataSource.data : appendUniqueRows(prev, dataSource.data, rowIdField));
8327
10046
  }, [queryKey, dataSource.data, (_a = dataSource.pagination) == null ? void 0 : _a.currentPage, rowIdField]);
8328
- (0, import_react18.useEffect)(() => {
10047
+ (0, import_react19.useEffect)(() => {
8329
10048
  if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > loadedRows.length) {
8330
10049
  setServerQuerying(true);
8331
10050
  }
8332
10051
  }, [serverQuerying, dataSource.totalCount, loadedRows.length]);
8333
- const ensurePageLoaded = (0, import_react18.useCallback)((page) => {
10052
+ const ensurePageLoaded = (0, import_react19.useCallback)((page) => {
8334
10053
  var _a2, _b2, _c;
8335
10054
  const pageNumber = Number(page) || 1;
8336
10055
  const requiredRows = pageNumber * pageSize;
@@ -8338,10 +10057,10 @@ var CrmDataTable = ({
8338
10057
  if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
8339
10058
  (_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
8340
10059
  }, [pageSize, loadedRows.length, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
8341
- (0, import_react18.useEffect)(() => {
10060
+ (0, import_react19.useEffect)(() => {
8342
10061
  ensurePageLoaded(requestedPage);
8343
10062
  }, [requestedPage, ensurePageLoaded]);
8344
- const generatedFilters = (0, import_react18.useMemo)(
10063
+ const generatedFilters = (0, import_react19.useMemo)(
8345
10064
  () => buildAutoFiltersFromRows({
8346
10065
  rows: loadedRows,
8347
10066
  fields: autoFilterFields,
@@ -8351,7 +10070,7 @@ var CrmDataTable = ({
8351
10070
  [loadedRows, autoFilterFields, autoFilterMaxOptions]
8352
10071
  );
8353
10072
  const resolvedFilters = filters || generatedFilters;
8354
- const table = import_react18.default.createElement(DataTable, {
10073
+ const table = import_react19.default.createElement(DataTable, {
8355
10074
  title: title || `${prettifyPropertyName(objectType)} records`,
8356
10075
  data: loadedRows,
8357
10076
  loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
@@ -8375,11 +10094,11 @@ var CrmDataTable = ({
8375
10094
  const total = dataSource.totalCount;
8376
10095
  const capped = typeof total === "number" && total > loadedRows.length;
8377
10096
  if (!capped) return table;
8378
- return import_react18.default.createElement(
8379
- import_ui_extensions18.Flex,
10097
+ return import_react19.default.createElement(
10098
+ import_ui_extensions19.Flex,
8380
10099
  { direction: "column", gap: "xs" },
8381
- import_react18.default.createElement(
8382
- import_ui_extensions18.Text,
10100
+ import_react19.default.createElement(
10101
+ import_ui_extensions19.Text,
8383
10102
  { variant: "microcopy" },
8384
10103
  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.`
8385
10104
  ),
@@ -8413,25 +10132,25 @@ var CrmKanban = ({
8413
10132
  ...props
8414
10133
  }) => {
8415
10134
  var _a, _b;
8416
- const [params, setParams] = (0, import_react18.useState)(EMPTY_CRM_PARAMS);
8417
- const resolvedProperties = (0, import_react18.useMemo)(() => properties, [properties]);
10135
+ const [params, setParams] = (0, import_react19.useState)(EMPTY_CRM_PARAMS);
10136
+ const resolvedProperties = (0, import_react19.useMemo)(() => properties, [properties]);
8418
10137
  const resolvedSearchFields = searchFields || resolvedProperties;
8419
- const autoFilterFields = (0, import_react18.useMemo)(
10138
+ const autoFilterFields = (0, import_react19.useMemo)(
8420
10139
  () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
8421
10140
  [autoFilters, resolvedProperties]
8422
10141
  );
8423
- const autoFilterLabelsRef = (0, import_react18.useRef)({});
8424
- const defaultPropertyMap = (0, import_react18.useMemo)(
10142
+ const autoFilterLabelsRef = (0, import_react19.useRef)({});
10143
+ const defaultPropertyMap = (0, import_react19.useMemo)(
8425
10144
  () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
8426
10145
  [resolvedProperties]
8427
10146
  );
8428
10147
  const effectivePropertyMap = propertyMap || defaultPropertyMap;
8429
- const resolvedSortMap = (0, import_react18.useMemo)(
10148
+ const resolvedSortMap = (0, import_react19.useMemo)(
8430
10149
  () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
8431
10150
  [sortMap, effectivePropertyMap]
8432
10151
  );
8433
10152
  const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
8434
- const dataSourceOptions = (0, import_react18.useMemo)(
10153
+ const dataSourceOptions = (0, import_react19.useMemo)(
8435
10154
  () => ({
8436
10155
  objectType: resolveCrmObjectType(objectType),
8437
10156
  properties: resolvedProperties,
@@ -8445,17 +10164,17 @@ var CrmKanban = ({
8445
10164
  }),
8446
10165
  [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
8447
10166
  );
8448
- const [serverQuerying, setServerQuerying] = (0, import_react18.useState)(!!serverSide);
10167
+ const [serverQuerying, setServerQuerying] = (0, import_react19.useState)(!!serverSide);
8449
10168
  const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
8450
10169
  const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
8451
- const queryKey = (0, import_react18.useMemo)(
10170
+ const queryKey = (0, import_react19.useMemo)(
8452
10171
  () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
8453
10172
  [effectiveParams, objectType, resolvedProperties, pageLength]
8454
10173
  );
8455
- const [accumulatedRows, setAccumulatedRows] = (0, import_react18.useState)(EMPTY_ARRAY);
8456
- const lastQueryKeyRef = (0, import_react18.useRef)(queryKey);
10174
+ const [accumulatedRows, setAccumulatedRows] = (0, import_react19.useState)(EMPTY_ARRAY);
10175
+ const lastQueryKeyRef = (0, import_react19.useRef)(queryKey);
8457
10176
  const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
8458
- (0, import_react18.useEffect)(() => {
10177
+ (0, import_react19.useEffect)(() => {
8459
10178
  var _a2;
8460
10179
  if (lastQueryKeyRef.current !== queryKey) {
8461
10180
  lastQueryKeyRef.current = queryKey;
@@ -8465,12 +10184,12 @@ var CrmKanban = ({
8465
10184
  const currentPage = ((_a2 = dataSource.pagination) == null ? void 0 : _a2.currentPage) || 1;
8466
10185
  setAccumulatedRows((prev) => currentPage <= 1 ? dataSource.data : appendUniqueRows(prev, dataSource.data, rowIdField));
8467
10186
  }, [queryKey, dataSource.data, (_a = dataSource.pagination) == null ? void 0 : _a.currentPage, rowIdField]);
8468
- (0, import_react18.useEffect)(() => {
10187
+ (0, import_react19.useEffect)(() => {
8469
10188
  if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > loadedRows.length) {
8470
10189
  setServerQuerying(true);
8471
10190
  }
8472
10191
  }, [serverQuerying, dataSource.totalCount, loadedRows.length]);
8473
- const handleLoadMore = (0, import_react18.useCallback)((stage) => {
10192
+ const handleLoadMore = (0, import_react19.useCallback)((stage) => {
8474
10193
  var _a2, _b2, _c;
8475
10194
  if (onLoadMore) {
8476
10195
  onLoadMore(stage);
@@ -8479,7 +10198,7 @@ var CrmKanban = ({
8479
10198
  if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
8480
10199
  (_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
8481
10200
  }, [onLoadMore, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
8482
- const generatedFilters = (0, import_react18.useMemo)(
10201
+ const generatedFilters = (0, import_react19.useMemo)(
8483
10202
  () => buildAutoFiltersFromRows({
8484
10203
  rows: loadedRows,
8485
10204
  fields: autoFilterFields,
@@ -8489,7 +10208,7 @@ var CrmKanban = ({
8489
10208
  [loadedRows, autoFilterFields, autoFilterMaxOptions]
8490
10209
  );
8491
10210
  const resolvedFilters = filters || generatedFilters;
8492
- const resolvedStages = (0, import_react18.useMemo)(() => {
10211
+ const resolvedStages = (0, import_react19.useMemo)(() => {
8493
10212
  if (stages) return stages;
8494
10213
  const seen = [];
8495
10214
  for (const row of loadedRows) {
@@ -8501,7 +10220,7 @@ var CrmKanban = ({
8501
10220
  label: typeof stageLabels === "function" ? stageLabels(value) : stageLabels && stageLabels[value] || prettifyPropertyName(String(value))
8502
10221
  }));
8503
10222
  }, [stages, stageLabels, loadedRows, groupBy]);
8504
- const resolvedStageMeta = (0, import_react18.useMemo)(() => {
10223
+ const resolvedStageMeta = (0, import_react19.useMemo)(() => {
8505
10224
  if (stageMeta || !dataSource.hasMore) return stageMeta;
8506
10225
  return Object.fromEntries(resolvedStages.map((stage) => {
8507
10226
  var _a2;
@@ -8515,7 +10234,7 @@ var CrmKanban = ({
8515
10234
  ];
8516
10235
  }));
8517
10236
  }, [stageMeta, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.totalCount, resolvedStages]);
8518
- const board = import_react18.default.createElement(Kanban, {
10237
+ const board = import_react19.default.createElement(Kanban, {
8519
10238
  title: title || `${prettifyPropertyName(objectType)} board`,
8520
10239
  data: loadedRows,
8521
10240
  loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
@@ -8538,17 +10257,19 @@ var CrmKanban = ({
8538
10257
  const total = dataSource.totalCount;
8539
10258
  const capped = typeof total === "number" && total > loadedRows.length;
8540
10259
  if (!capped) return board;
8541
- return import_react18.default.createElement(
8542
- import_ui_extensions18.Flex,
10260
+ return import_react19.default.createElement(
10261
+ import_ui_extensions19.Flex,
8543
10262
  { direction: "column", gap: "xs" },
8544
- import_react18.default.createElement(
8545
- import_ui_extensions18.Text,
10263
+ import_react19.default.createElement(
10264
+ import_ui_extensions19.Text,
8546
10265
  { variant: "microcopy" },
8547
10266
  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.`
8548
10267
  ),
8549
10268
  board
8550
10269
  );
8551
10270
  };
10271
+ CrmDataTable.displayName = "CrmDataTable";
10272
+ CrmKanban.displayName = "CrmKanban";
8552
10273
 
8553
10274
  // src/common-components/CrmLookupSelect.js
8554
10275
  var EMPTY_ARRAY2 = [];
@@ -8601,12 +10322,12 @@ var CrmLookupSelect = ({
8601
10322
  loadingOption,
8602
10323
  selectProps = EMPTY_OBJECT2
8603
10324
  }) => {
8604
- const [inputValue, setInputValue] = (0, import_react19.useState)(query || "");
8605
- const [pickedOptions, setPickedOptions] = (0, import_react19.useState)(EMPTY_ARRAY2);
8606
- const debouncedInput = (0, import_ui_extensions19.useDebounce)(inputValue, debounce > 0 ? debounce : 1);
10325
+ const [inputValue, setInputValue] = (0, import_react20.useState)(query || "");
10326
+ const [pickedOptions, setPickedOptions] = (0, import_react20.useState)(EMPTY_ARRAY2);
10327
+ const debouncedInput = (0, import_ui_extensions20.useDebounce)(inputValue, debounce > 0 ? debounce : 1);
8607
10328
  const search = debounce > 0 ? debouncedInput : inputValue;
8608
10329
  const effectiveSearch = search && search.length >= minSearchLength ? search : "";
8609
- const optionConfig = (0, import_react19.useMemo)(
10330
+ const optionConfig = (0, import_react20.useMemo)(
8610
10331
  () => makeOptionConfig({ option, labelProperty, valueProperty, descriptionProperty }),
8611
10332
  [option, labelProperty, valueProperty, descriptionProperty]
8612
10333
  );
@@ -8624,7 +10345,7 @@ var CrmLookupSelect = ({
8624
10345
  );
8625
10346
  const isSearching = dataSource.loading || inputValue.trim() !== (search || "").trim();
8626
10347
  const hasQuery = effectiveSearch.length > 0;
8627
- const options = (0, import_react19.useMemo)(() => {
10348
+ const options = (0, import_react20.useMemo)(() => {
8628
10349
  const remembered = [...selectedOptions || EMPTY_ARRAY2, ...pickedOptions];
8629
10350
  const baseOptions = mergeSelectedOptions(dataSource.options || EMPTY_ARRAY2, remembered, value);
8630
10351
  if (isSearching && loadingOption) return [loadingOption, ...baseOptions];
@@ -8663,7 +10384,281 @@ var CrmLookupSelect = ({
8663
10384
  },
8664
10385
  ...selectProps
8665
10386
  };
8666
- return import_react19.default.createElement(multiple ? import_ui_extensions19.MultiSelect : import_ui_extensions19.Select, commonProps);
10387
+ return import_react20.default.createElement(multiple ? import_ui_extensions20.MultiSelect : import_ui_extensions20.Select, commonProps);
10388
+ };
10389
+
10390
+ // src/common-components/CrmRecordPicker.js
10391
+ var import_react21 = __toESM(require("react"));
10392
+ var import_ui_extensions21 = require("@hubspot/ui-extensions");
10393
+
10394
+ // src/common-components/recordPickerCore.js
10395
+ var EMPTY_ARRAY3 = [];
10396
+ var CREATE_OPTION_VALUE = "__create__";
10397
+ var isRecordLike = (value) => value != null && typeof value === "object" && !Array.isArray(value);
10398
+ var getRecordId = (record) => {
10399
+ if (!isRecordLike(record)) return void 0;
10400
+ const id = record.objectId ?? record.id ?? record.hs_object_id ?? getByPath(record, "properties.hs_object_id");
10401
+ return id == null ? void 0 : String(id);
10402
+ };
10403
+ var toList = (value) => Array.isArray(value) ? value : value == null || value === "" ? EMPTY_ARRAY3 : [value];
10404
+ var normalizeRecordSelection = (value) => {
10405
+ const ids = [];
10406
+ const records = [];
10407
+ const seen = /* @__PURE__ */ new Set();
10408
+ for (const entry of toList(value)) {
10409
+ const rawId = isRecordLike(entry) ? getRecordId(entry) : entry;
10410
+ const id = rawId == null || rawId === "" ? rawId : String(rawId);
10411
+ if (id == null || id === "" || seen.has(id)) continue;
10412
+ seen.add(id);
10413
+ ids.push(id);
10414
+ if (isRecordLike(entry)) records.push(entry);
10415
+ }
10416
+ return { ids, records };
10417
+ };
10418
+ var recordToPickerOption = (record, config = {}) => {
10419
+ const { labelField, descriptionField, fallbackLabel = "Untitled record" } = config;
10420
+ const label = (labelField ? getByPath(record, labelField) : void 0) ?? (record == null ? void 0 : record.name) ?? getByPath(record, "properties.name") ?? fallbackLabel;
10421
+ const option = { label, value: getRecordId(record) };
10422
+ const description = descriptionField ? getByPath(record, descriptionField) : void 0;
10423
+ if (description != null && description !== "") option.description = description;
10424
+ return option;
10425
+ };
10426
+ var mergePickerOptions = (options, selectedOptions) => {
10427
+ const base = Array.isArray(options) ? options : EMPTY_ARRAY3;
10428
+ const selected = toList(selectedOptions);
10429
+ if (!selected.length) return base;
10430
+ const existing = new Set(base.map((option) => option == null ? void 0 : option.value));
10431
+ const missing = selected.filter((option) => option && !existing.has(option.value));
10432
+ return missing.length ? [...missing, ...base] : base;
10433
+ };
10434
+ var enforceSelectionMax = (ids, max) => {
10435
+ const list = toList(ids);
10436
+ if (!Number.isFinite(max) || max <= 0 || list.length <= max) return list;
10437
+ return list.slice(0, max);
10438
+ };
10439
+ var shouldShowCreateOption = ({
10440
+ allowCreate,
10441
+ searchTerm,
10442
+ options,
10443
+ searching = false,
10444
+ createPending = false,
10445
+ atMax = false
10446
+ } = {}) => {
10447
+ if (!allowCreate || typeof allowCreate.onCreate !== "function") return false;
10448
+ if (createPending || searching || atMax) return false;
10449
+ const term = String(searchTerm ?? "").trim();
10450
+ if (!term) return false;
10451
+ const lower = term.toLowerCase();
10452
+ return !(options || EMPTY_ARRAY3).some(
10453
+ (option) => String((option == null ? void 0 : option.label) ?? "").trim().toLowerCase() === lower
10454
+ );
10455
+ };
10456
+ var makeCreateOption = (term, label) => ({
10457
+ label: typeof label === "function" ? label(term) : label || `Create "${term}"`,
10458
+ value: CREATE_OPTION_VALUE
10459
+ });
10460
+ var splitCreateSelection = (next) => {
10461
+ const list = toList(next);
10462
+ const ids = list.filter((value) => value !== CREATE_OPTION_VALUE);
10463
+ return { ids, create: ids.length !== list.length };
10464
+ };
10465
+ var mapIdsToRecords = (ids, recordsById) => {
10466
+ const lookup = recordsById instanceof Map ? (id) => recordsById.get(id) : (id) => recordsById ? recordsById[id] : void 0;
10467
+ return toList(ids).map((id) => lookup(id) ?? { objectId: id });
10468
+ };
10469
+ var upsertRecords = (records, additions) => {
10470
+ const incoming = toList(additions).filter((record) => getRecordId(record) != null);
10471
+ if (!incoming.length) return Array.isArray(records) ? records : EMPTY_ARRAY3;
10472
+ const byId = new Map(toList(records).map((record) => [getRecordId(record), record]));
10473
+ for (const record of incoming) byId.set(getRecordId(record), record);
10474
+ return [...byId.values()];
10475
+ };
10476
+
10477
+ // src/common-components/CrmRecordPicker.js
10478
+ var EMPTY_ARRAY4 = [];
10479
+ var defaultMapRecord = (record) => ({ objectId: record.objectId, ...record.properties });
10480
+ var CrmRecordPicker = ({
10481
+ objectType,
10482
+ properties = EMPTY_ARRAY4,
10483
+ labelField,
10484
+ descriptionField,
10485
+ value,
10486
+ defaultValue,
10487
+ onChange,
10488
+ multi = true,
10489
+ max,
10490
+ placeholder,
10491
+ label,
10492
+ name,
10493
+ required,
10494
+ readOnly,
10495
+ error,
10496
+ validationMessage,
10497
+ description,
10498
+ tooltip,
10499
+ variant,
10500
+ pageLength = 20,
10501
+ debounce = 300,
10502
+ minSearchLength = 0,
10503
+ filterMap,
10504
+ allowCreate = false,
10505
+ fallbackLabel = "Untitled record",
10506
+ format,
10507
+ baseConfig,
10508
+ onSearchChange,
10509
+ ...rest
10510
+ }) => {
10511
+ const isControlled = value !== void 0;
10512
+ const [internalValue, setInternalValue] = (0, import_react21.useState)(defaultValue);
10513
+ const effectiveValue = isControlled ? value : internalValue;
10514
+ const selection = (0, import_react21.useMemo)(() => normalizeRecordSelection(effectiveValue), [effectiveValue]);
10515
+ const [inputValue, setInputValue] = (0, import_react21.useState)("");
10516
+ const [seenRecords, setSeenRecords] = (0, import_react21.useState)(EMPTY_ARRAY4);
10517
+ const [createPending, setCreatePending] = (0, import_react21.useState)(false);
10518
+ const [createError, setCreateError] = (0, import_react21.useState)(null);
10519
+ const createPendingRef = (0, import_react21.useRef)(false);
10520
+ const debouncedInput = (0, import_ui_extensions21.useDebounce)(inputValue, debounce > 0 ? debounce : 1);
10521
+ const search = debounce > 0 ? debouncedInput : inputValue;
10522
+ const effectiveSearch = search && search.length >= minSearchLength ? search : "";
10523
+ const optionConfig = (0, import_react21.useMemo)(
10524
+ () => ({ labelField, descriptionField, fallbackLabel }),
10525
+ [labelField, descriptionField, fallbackLabel]
10526
+ );
10527
+ const searchParams = (0, import_react21.useMemo)(() => ({ search: effectiveSearch }), [effectiveSearch]);
10528
+ const dataSourceOptions = (0, import_react21.useMemo)(
10529
+ () => ({
10530
+ objectType: resolveCrmObjectType(objectType),
10531
+ properties,
10532
+ pageLength,
10533
+ format,
10534
+ filterMap,
10535
+ baseConfig,
10536
+ row: { mapRecord: defaultMapRecord },
10537
+ option: { mapOption: (row) => recordToPickerOption(row, optionConfig) }
10538
+ }),
10539
+ [objectType, properties, pageLength, format, filterMap, baseConfig, optionConfig]
10540
+ );
10541
+ const dataSource = useCrmSearchOptions(searchParams, dataSourceOptions);
10542
+ const recordsById = (0, import_react21.useMemo)(() => {
10543
+ const map = /* @__PURE__ */ new Map();
10544
+ for (const record of selection.records) map.set(getRecordId(record), record);
10545
+ for (const record of seenRecords) map.set(getRecordId(record), record);
10546
+ for (const row of dataSource.rows || EMPTY_ARRAY4) {
10547
+ const id = getRecordId(row);
10548
+ if (id != null) map.set(id, row);
10549
+ }
10550
+ return map;
10551
+ }, [selection.records, seenRecords, dataSource.rows]);
10552
+ const selectedOptions = (0, import_react21.useMemo)(
10553
+ () => selection.ids.map((id) => {
10554
+ const record = recordsById.get(id);
10555
+ return record ? recordToPickerOption(record, optionConfig) : { label: String(id), value: id };
10556
+ }),
10557
+ [selection.ids, recordsById, optionConfig]
10558
+ );
10559
+ const isSearching = dataSource.loading || inputValue.trim() !== (search || "").trim();
10560
+ const atMax = multi && Number.isFinite(max) && max > 0 && selection.ids.length >= max;
10561
+ const options = (0, import_react21.useMemo)(() => {
10562
+ const merged = mergePickerOptions(dataSource.options || EMPTY_ARRAY4, selectedOptions);
10563
+ const showCreate = shouldShowCreateOption({
10564
+ allowCreate,
10565
+ searchTerm: effectiveSearch,
10566
+ options: merged,
10567
+ searching: isSearching,
10568
+ createPending,
10569
+ atMax
10570
+ });
10571
+ if (!showCreate) return merged;
10572
+ return [...merged, makeCreateOption(effectiveSearch.trim(), allowCreate == null ? void 0 : allowCreate.label)];
10573
+ }, [dataSource.options, selectedOptions, allowCreate, effectiveSearch, isSearching, createPending, atMax]);
10574
+ const commitChange = (ids, extraRecords) => {
10575
+ let map = recordsById;
10576
+ if (extraRecords && extraRecords.length) {
10577
+ map = new Map(recordsById);
10578
+ for (const record of extraRecords) {
10579
+ const id = getRecordId(record);
10580
+ if (id != null) map.set(id, record);
10581
+ }
10582
+ }
10583
+ const trimmed = multi ? enforceSelectionMax(ids, max) : ids.slice(0, 1);
10584
+ const records = mapIdsToRecords(trimmed, map);
10585
+ if (!isControlled) setInternalValue(multi ? trimmed : trimmed[0] ?? null);
10586
+ if (onChange) {
10587
+ if (multi) onChange(trimmed, records);
10588
+ else onChange(trimmed[0] ?? null, records[0] ?? null);
10589
+ }
10590
+ };
10591
+ const startCreate = (term, baseIds) => {
10592
+ const onCreate = allowCreate && typeof allowCreate.onCreate === "function" ? allowCreate.onCreate : null;
10593
+ if (!onCreate || createPendingRef.current) return;
10594
+ createPendingRef.current = true;
10595
+ setCreatePending(true);
10596
+ setCreateError(null);
10597
+ Promise.resolve(onCreate(term)).then((created) => {
10598
+ const record = isRecordLike(created) ? created : created != null && created !== "" ? { objectId: created, name: term } : null;
10599
+ const id = getRecordId(record);
10600
+ if (id == null) return;
10601
+ setSeenRecords((prev) => upsertRecords(prev, [record]));
10602
+ const nextIds = multi ? [...baseIds.filter((v) => v !== id), id] : [id];
10603
+ commitChange(nextIds, [record]);
10604
+ }).catch((err) => {
10605
+ setCreateError((err == null ? void 0 : err.message) || "Could not create the record.");
10606
+ }).finally(() => {
10607
+ createPendingRef.current = false;
10608
+ setCreatePending(false);
10609
+ });
10610
+ };
10611
+ const handleChange = (next) => {
10612
+ if (createError) setCreateError(null);
10613
+ const { ids, create } = splitCreateSelection(next);
10614
+ const picked = ids.map((id) => recordsById.get(id)).filter(Boolean);
10615
+ if (picked.length) setSeenRecords((prev) => upsertRecords(prev, picked));
10616
+ if (create) {
10617
+ startCreate(effectiveSearch.trim(), ids);
10618
+ if (multi) commitChange(ids);
10619
+ return;
10620
+ }
10621
+ commitChange(ids);
10622
+ };
10623
+ const handleSearchInput = (next) => {
10624
+ setInputValue(next || "");
10625
+ if (onSearchChange) onSearchChange(next || "");
10626
+ };
10627
+ const commonProps = {
10628
+ name,
10629
+ label,
10630
+ value: multi ? selection.ids : selection.ids[0],
10631
+ options,
10632
+ placeholder: placeholder || (createPending ? "Creating record..." : dataSource.loading ? "Searching CRM..." : "Search CRM records..."),
10633
+ description,
10634
+ tooltip,
10635
+ required,
10636
+ readOnly: readOnly || createPending,
10637
+ error: error || !!createError || !!dataSource.error,
10638
+ validationMessage: validationMessage || createError || (typeof dataSource.error === "string" ? dataSource.error : void 0),
10639
+ variant,
10640
+ onChange: handleChange,
10641
+ ...rest
10642
+ };
10643
+ if (!multi) {
10644
+ return import_react21.default.createElement(import_ui_extensions21.Select, { ...commonProps, onInput: handleSearchInput });
10645
+ }
10646
+ return import_react21.default.createElement(
10647
+ import_ui_extensions21.Flex,
10648
+ { direction: "column", gap: "xs" },
10649
+ import_react21.default.createElement(import_ui_extensions21.SearchInput, {
10650
+ name: name ? `${name}-search` : void 0,
10651
+ label: "",
10652
+ placeholder: "Search CRM records...",
10653
+ value: inputValue,
10654
+ readOnly: readOnly || createPending,
10655
+ onInput: handleSearchInput,
10656
+ // The clear "x" emits onChange (not onInput) — wire both so clearing
10657
+ // resets the term (same pattern as CollectionToolbar).
10658
+ onChange: handleSearchInput
10659
+ }),
10660
+ import_react21.default.createElement(import_ui_extensions21.MultiSelect, commonProps)
10661
+ );
8667
10662
  };
8668
10663
 
8669
10664
  // src/common-components/datePresets.js
@@ -8688,13 +10683,472 @@ var HS_DATE_DIRECTION_LABELS = {
8688
10683
  desc: "Descending"
8689
10684
  };
8690
10685
 
10686
+ // src/common-components/dateRangePresets.js
10687
+ var DATE_RANGE_CUSTOM_VALUE = "custom";
10688
+ var DATE_FILTER_OPERATORS = [
10689
+ { label: "is", value: "InRollingDateRange" },
10690
+ { label: "is equal to", value: "Equal" },
10691
+ { label: "is before", value: "BeforeDateStaticOrDynamic" },
10692
+ { label: "is after", value: "AfterDateStaticOrDynamic" },
10693
+ { label: "is between", value: "InRange" },
10694
+ { label: "is more than", value: "GreaterRolling" },
10695
+ { label: "is less than", value: "LessRolling" },
10696
+ { label: "is known", value: "Known" },
10697
+ { label: "is unknown", value: "NotKnown" }
10698
+ ];
10699
+ var DATE_ROLLING_UNIT_OPTIONS = [
10700
+ { label: "day ago", value: "day:backward" },
10701
+ { label: "days from now", value: "day:forward" },
10702
+ { label: "week ago", value: "week:backward" },
10703
+ { label: "weeks from now", value: "week:forward" },
10704
+ { label: "month ago", value: "month:backward" },
10705
+ { label: "months from now", value: "month:forward" },
10706
+ { label: "year ago", value: "year:backward" },
10707
+ { label: "years from now", value: "year:forward" }
10708
+ ];
10709
+ var toHsDateValue = (date) => {
10710
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) return null;
10711
+ return { year: date.getFullYear(), month: date.getMonth(), date: date.getDate() };
10712
+ };
10713
+ var compareHsDateValues = (a, b) => {
10714
+ if (!a || !b) return 0;
10715
+ return a.year - b.year || a.month - b.month || a.date - b.date;
10716
+ };
10717
+ var isValidDateRange = (range) => {
10718
+ if (!range) return true;
10719
+ return compareHsDateValues(range.from, range.to) <= 0;
10720
+ };
10721
+ var dayAt = (now, offset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth(), now.getDate() + offset));
10722
+ var monthStart = (now, monthOffset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth() + monthOffset, 1));
10723
+ var monthEnd = (now, monthOffset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth() + monthOffset + 1, 0));
10724
+ var quarterStartMonth = (now, quarterOffset = 0) => Math.floor(now.getMonth() / 3) * 3 + quarterOffset * 3;
10725
+ var presetToRange = (presetKey, now = /* @__PURE__ */ new Date()) => {
10726
+ if (!presetKey || presetKey === DATE_RANGE_CUSTOM_VALUE) return null;
10727
+ if (!(now instanceof Date) || Number.isNaN(now.getTime())) return null;
10728
+ const dow = now.getDay();
10729
+ const year = now.getFullYear();
10730
+ const qm = quarterStartMonth(now);
10731
+ switch (presetKey) {
10732
+ case "today":
10733
+ return { from: dayAt(now, 0), to: dayAt(now, 0) };
10734
+ case "yesterday":
10735
+ return { from: dayAt(now, -1), to: dayAt(now, -1) };
10736
+ case "tomorrow":
10737
+ return { from: dayAt(now, 1), to: dayAt(now, 1) };
10738
+ case "this_week":
10739
+ return { from: dayAt(now, -dow), to: dayAt(now, 6 - dow) };
10740
+ case "last_week":
10741
+ return { from: dayAt(now, -dow - 7), to: dayAt(now, -dow - 1) };
10742
+ case "7d":
10743
+ return { from: dayAt(now, -6), to: dayAt(now, 0) };
10744
+ case "30d":
10745
+ return { from: dayAt(now, -29), to: dayAt(now, 0) };
10746
+ case "90d":
10747
+ return { from: dayAt(now, -89), to: dayAt(now, 0) };
10748
+ case "this_month":
10749
+ return { from: monthStart(now, 0), to: monthEnd(now, 0) };
10750
+ case "last_month":
10751
+ return { from: monthStart(now, -1), to: monthEnd(now, -1) };
10752
+ case "this_quarter":
10753
+ return {
10754
+ from: toHsDateValue(new Date(year, qm, 1)),
10755
+ to: toHsDateValue(new Date(year, qm + 3, 0))
10756
+ };
10757
+ case "last_quarter":
10758
+ return {
10759
+ from: toHsDateValue(new Date(year, qm - 3, 1)),
10760
+ to: toHsDateValue(new Date(year, qm, 0))
10761
+ };
10762
+ case "this_year":
10763
+ return { from: { year, month: 0, date: 1 }, to: { year, month: 11, date: 31 } };
10764
+ case "last_year":
10765
+ return {
10766
+ from: { year: year - 1, month: 0, date: 1 },
10767
+ to: { year: year - 1, month: 11, date: 31 }
10768
+ };
10769
+ default:
10770
+ return null;
10771
+ }
10772
+ };
10773
+
10774
+ // src/common-components/DateRangePicker.js
10775
+ var import_react22 = __toESM(require("react"));
10776
+ var import_ui_extensions22 = require("@hubspot/ui-extensions");
10777
+ var h6 = import_react22.default.createElement;
10778
+ var IN_ROLLING = "InRollingDateRange";
10779
+ var EQUAL = "Equal";
10780
+ var BEFORE = "BeforeDateStaticOrDynamic";
10781
+ var AFTER = "AfterDateStaticOrDynamic";
10782
+ var GREATER_ROLLING = "GreaterRolling";
10783
+ var LESS_ROLLING = "LessRolling";
10784
+ var IN_RANGE = "InRange";
10785
+ var KNOWN = "Known";
10786
+ var NOT_KNOWN = "NotKnown";
10787
+ var EMPTY_RANGE = { from: null, to: null };
10788
+ var EMPTY_DATE = { date: null };
10789
+ var COMPACT_LABEL = "";
10790
+ var STATIC_DATE_OPERATORS = /* @__PURE__ */ new Set([EQUAL, BEFORE, AFTER]);
10791
+ var ROLLING_OPERATORS = /* @__PURE__ */ new Set([GREATER_ROLLING, LESS_ROLLING]);
10792
+ var PRESENCE_OPERATORS = /* @__PURE__ */ new Set([KNOWN, NOT_KNOWN]);
10793
+ var keyOfDate = (v) => v ? `${v.year}-${v.month}-${v.date}` : "";
10794
+ var keyOfRange = (r) => `${keyOfDate(r == null ? void 0 : r.from)}|${keyOfDate(r == null ? void 0 : r.to)}`;
10795
+ var isRangeLike = (value) => value && typeof value === "object" && ("from" in value || "to" in value) && !("operator" in value);
10796
+ var normalizeValue = (value) => {
10797
+ if (isRangeLike(value)) {
10798
+ return { operator: IN_RANGE, from: value.from ?? null, to: value.to ?? null };
10799
+ }
10800
+ if (!value || typeof value !== "object") {
10801
+ return { operator: IN_ROLLING, preset: "today" };
10802
+ }
10803
+ const operator = value.operator || IN_ROLLING;
10804
+ if (operator === IN_RANGE) {
10805
+ return { operator, from: value.from ?? null, to: value.to ?? null };
10806
+ }
10807
+ if (STATIC_DATE_OPERATORS.has(operator)) {
10808
+ return { operator, date: value.date ?? null };
10809
+ }
10810
+ if (ROLLING_OPERATORS.has(operator)) {
10811
+ return {
10812
+ operator,
10813
+ amount: Number.isFinite(Number(value.amount)) ? Number(value.amount) : 1,
10814
+ unit: value.unit || "day",
10815
+ direction: value.direction || "backward"
10816
+ };
10817
+ }
10818
+ if (PRESENCE_OPERATORS.has(operator)) {
10819
+ return { operator };
10820
+ }
10821
+ return { operator: IN_ROLLING, preset: value.preset || value.value || "today" };
10822
+ };
10823
+ var rangeFromValue = (value) => ({
10824
+ from: (value == null ? void 0 : value.from) ?? null,
10825
+ to: (value == null ? void 0 : value.to) ?? null
10826
+ });
10827
+ var getPresetOptions = (presets, customPresetLabel) => {
10828
+ const presetList = presets === true ? HS_DATE_PRESETS : Array.isArray(presets) ? presets : null;
10829
+ if (!presetList) return null;
10830
+ return [
10831
+ ...presetList.map((p) => ({ label: p.label, value: p.value })),
10832
+ ...presetList.some((p) => p.value === DATE_RANGE_CUSTOM_VALUE) ? [] : [{ label: customPresetLabel, value: DATE_RANGE_CUSTOM_VALUE }]
10833
+ ];
10834
+ };
10835
+ var DateRangePicker = ({
10836
+ value,
10837
+ defaultValue,
10838
+ onChange,
10839
+ label,
10840
+ name = "date-range",
10841
+ field,
10842
+ defaultField,
10843
+ onFieldChange,
10844
+ showFieldSelect = false,
10845
+ fieldOptions = [],
10846
+ operator,
10847
+ defaultOperator = IN_ROLLING,
10848
+ onOperatorChange,
10849
+ showOperatorSelect = true,
10850
+ operatorOptions = DATE_FILTER_OPERATORS,
10851
+ presets = true,
10852
+ rollingUnitOptions = DATE_ROLLING_UNIT_OPTIONS,
10853
+ direction = "row",
10854
+ clearable = false,
10855
+ min,
10856
+ max,
10857
+ fromLabel = "Start date",
10858
+ toLabel = "End date",
10859
+ dateLabel = "Date",
10860
+ showDateLabels = false,
10861
+ format = "medium",
10862
+ presetPlaceholder = "Enter value",
10863
+ customPresetLabel = "Custom",
10864
+ clearLabel = "Clear",
10865
+ invalidRangeMessage = "Start date must be on or before end date",
10866
+ readOnly = false,
10867
+ gap,
10868
+ gridColumnWidth = 260
10869
+ }) => {
10870
+ const isControlled = value !== void 0;
10871
+ const controlledValue = normalizeValue(value);
10872
+ const [internalValue, setInternalValue] = (0, import_react22.useState)(
10873
+ () => normalizeValue(defaultValue ?? { operator: defaultOperator })
10874
+ );
10875
+ const [internalField, setInternalField] = (0, import_react22.useState)(
10876
+ () => {
10877
+ var _a;
10878
+ return defaultField ?? ((_a = fieldOptions == null ? void 0 : fieldOptions[0]) == null ? void 0 : _a.value) ?? "";
10879
+ }
10880
+ );
10881
+ const current = normalizeValue(isControlled ? controlledValue : internalValue);
10882
+ const currentOperator = operator || current.operator || defaultOperator;
10883
+ const currentField = field !== void 0 ? field : internalField;
10884
+ const resolvedCurrent = normalizeValue({ ...current, operator: currentOperator });
10885
+ const [pending, setPending] = (0, import_react22.useState)(null);
10886
+ const [lastPreset, setLastPreset] = (0, import_react22.useState)({
10887
+ key: resolvedCurrent.preset || "",
10888
+ rangeKey: null
10889
+ });
10890
+ const isColumn = direction === "column";
10891
+ const presetOptions = getPresetOptions(presets, customPresetLabel);
10892
+ const showClear = clearable && !readOnly && (resolvedCurrent.preset || resolvedCurrent.date || resolvedCurrent.amount || resolvedCurrent.from || resolvedCurrent.to || pending);
10893
+ const emit = (next, meta = {}) => {
10894
+ const normalized = normalizeValue(next);
10895
+ if (!isControlled) setInternalValue(normalized);
10896
+ if (normalized.operator !== currentOperator) {
10897
+ onOperatorChange == null ? void 0 : onOperatorChange(normalized.operator);
10898
+ }
10899
+ onChange == null ? void 0 : onChange(normalized, {
10900
+ operator: normalized.operator,
10901
+ field: currentField || null,
10902
+ preset: normalized.operator === IN_ROLLING ? normalized.preset ?? null : null,
10903
+ ...meta
10904
+ });
10905
+ };
10906
+ const handleFieldChange = (nextField) => {
10907
+ if (field === void 0) setInternalField(nextField);
10908
+ onFieldChange == null ? void 0 : onFieldChange(nextField);
10909
+ onChange == null ? void 0 : onChange(resolvedCurrent, {
10910
+ operator: resolvedCurrent.operator,
10911
+ field: nextField || null,
10912
+ preset: resolvedCurrent.operator === IN_ROLLING ? resolvedCurrent.preset ?? null : null
10913
+ });
10914
+ };
10915
+ const handleOperatorChange = (nextOperator) => {
10916
+ setPending(null);
10917
+ if (nextOperator === IN_RANGE) {
10918
+ emit({ operator: IN_RANGE, ...EMPTY_RANGE }, { previousOperator: currentOperator });
10919
+ } else if (STATIC_DATE_OPERATORS.has(nextOperator)) {
10920
+ emit({ operator: nextOperator, ...EMPTY_DATE }, { previousOperator: currentOperator });
10921
+ } else if (ROLLING_OPERATORS.has(nextOperator)) {
10922
+ emit(
10923
+ { operator: nextOperator, amount: 1, unit: "day", direction: "backward" },
10924
+ { previousOperator: currentOperator }
10925
+ );
10926
+ } else if (PRESENCE_OPERATORS.has(nextOperator)) {
10927
+ emit({ operator: nextOperator }, { previousOperator: currentOperator });
10928
+ } else {
10929
+ emit({ operator: IN_ROLLING, preset: "today" }, { previousOperator: currentOperator });
10930
+ }
10931
+ };
10932
+ const handlePresetChange = (preset) => {
10933
+ if (!preset || preset === DATE_RANGE_CUSTOM_VALUE) {
10934
+ emit({ operator: IN_ROLLING, preset: DATE_RANGE_CUSTOM_VALUE });
10935
+ return;
10936
+ }
10937
+ const option = presets === true ? HS_DATE_PRESETS.find((p) => p.value === preset) : Array.isArray(presets) ? presets.find((p) => p.value === preset) : null;
10938
+ const range = option && typeof option.getRange === "function" ? option.getRange(/* @__PURE__ */ new Date()) : presetToRange(preset);
10939
+ setLastPreset({ key: preset, rangeKey: range ? keyOfRange(range) : null });
10940
+ emit({ operator: IN_ROLLING, preset }, { range });
10941
+ };
10942
+ const handleRollingUnitChange = (compound) => {
10943
+ const [unit, unitDirection] = String(compound || "day:backward").split(":");
10944
+ emit({
10945
+ operator: currentOperator,
10946
+ amount: resolvedCurrent.amount || 1,
10947
+ unit,
10948
+ direction: unitDirection || "backward"
10949
+ });
10950
+ };
10951
+ const handleDateChange = (side, next) => {
10952
+ const displayRange = {
10953
+ ...rangeFromValue(resolvedCurrent),
10954
+ ...pending ? { [pending.side]: pending.value } : {}
10955
+ };
10956
+ const candidate = { ...displayRange, [side]: next ?? null };
10957
+ if (isValidDateRange(candidate)) {
10958
+ setPending(null);
10959
+ setLastPreset({ key: "", rangeKey: null });
10960
+ emit({ operator: IN_RANGE, ...candidate });
10961
+ } else {
10962
+ setPending({ side, value: next ?? null });
10963
+ }
10964
+ };
10965
+ const handleStaticDateChange = (next) => {
10966
+ emit({ operator: currentOperator, date: next ?? null });
10967
+ };
10968
+ const handleClear = () => {
10969
+ setPending(null);
10970
+ setLastPreset({ key: "", rangeKey: null });
10971
+ if (currentOperator === IN_RANGE) {
10972
+ emit({ operator: IN_RANGE, ...EMPTY_RANGE });
10973
+ } else if (STATIC_DATE_OPERATORS.has(currentOperator)) {
10974
+ emit({ operator: currentOperator, ...EMPTY_DATE });
10975
+ } else if (ROLLING_OPERATORS.has(currentOperator)) {
10976
+ emit({ operator: currentOperator, amount: 1, unit: "day", direction: "backward" });
10977
+ } else if (PRESENCE_OPERATORS.has(currentOperator)) {
10978
+ emit({ operator: currentOperator });
10979
+ } else {
10980
+ emit({ operator: IN_ROLLING, preset: "" });
10981
+ }
10982
+ };
10983
+ const operatorSelect = showOperatorSelect ? h6(import_ui_extensions22.Select, {
10984
+ key: "operator",
10985
+ name: `${name}-operator`,
10986
+ label: COMPACT_LABEL,
10987
+ options: operatorOptions,
10988
+ value: currentOperator,
10989
+ onChange: handleOperatorChange,
10990
+ readOnly
10991
+ }) : null;
10992
+ const fieldSelect = showFieldSelect ? h6(import_ui_extensions22.Select, {
10993
+ key: "field",
10994
+ name: `${name}-field`,
10995
+ label: "",
10996
+ options: fieldOptions,
10997
+ value: currentField,
10998
+ onChange: handleFieldChange,
10999
+ readOnly
11000
+ }) : null;
11001
+ let valueInput = null;
11002
+ const fromInputLabel = showDateLabels ? fromLabel : COMPACT_LABEL;
11003
+ const toInputLabel = showDateLabels ? toLabel : COMPACT_LABEL;
11004
+ const singleDateInputLabel = showDateLabels ? dateLabel : COMPACT_LABEL;
11005
+ if (currentOperator === IN_RANGE) {
11006
+ const committed = rangeFromValue(resolvedCurrent);
11007
+ const display = {
11008
+ ...committed,
11009
+ ...pending ? { [pending.side]: pending.value } : {}
11010
+ };
11011
+ const invalidSide = pending ? pending.side : null;
11012
+ valueInput = [
11013
+ h6(import_ui_extensions22.DateInput, {
11014
+ key: "from",
11015
+ name: `${name}-from`,
11016
+ label: fromInputLabel,
11017
+ format,
11018
+ value: display.from ?? null,
11019
+ onChange: (next) => handleDateChange("from", next),
11020
+ min,
11021
+ max,
11022
+ readOnly,
11023
+ error: invalidSide === "from",
11024
+ validationMessage: invalidSide === "from" ? invalidRangeMessage : void 0
11025
+ }),
11026
+ isColumn ? null : h6(import_ui_extensions22.Text, { key: "to" }, "to"),
11027
+ h6(import_ui_extensions22.DateInput, {
11028
+ key: "toDate",
11029
+ name: `${name}-to`,
11030
+ label: toInputLabel,
11031
+ format,
11032
+ value: display.to ?? null,
11033
+ onChange: (next) => handleDateChange("to", next),
11034
+ min,
11035
+ max,
11036
+ readOnly,
11037
+ error: invalidSide === "to",
11038
+ validationMessage: invalidSide === "to" ? invalidRangeMessage : void 0
11039
+ })
11040
+ ];
11041
+ } else if (STATIC_DATE_OPERATORS.has(currentOperator)) {
11042
+ valueInput = h6(import_ui_extensions22.DateInput, {
11043
+ key: "date",
11044
+ name: `${name}-date`,
11045
+ label: singleDateInputLabel,
11046
+ format,
11047
+ value: resolvedCurrent.date ?? null,
11048
+ onChange: handleStaticDateChange,
11049
+ min,
11050
+ max,
11051
+ readOnly
11052
+ });
11053
+ } else if (ROLLING_OPERATORS.has(currentOperator)) {
11054
+ const compound = `${resolvedCurrent.unit || "day"}:${resolvedCurrent.direction || "backward"}`;
11055
+ valueInput = [
11056
+ h6(import_ui_extensions22.NumberInput, {
11057
+ key: "amount",
11058
+ name: `${name}-amount`,
11059
+ label: COMPACT_LABEL,
11060
+ min: 0,
11061
+ value: resolvedCurrent.amount ?? 1,
11062
+ onChange: (amount) => emit({
11063
+ operator: currentOperator,
11064
+ amount: Number.isFinite(Number(amount)) ? Number(amount) : 0,
11065
+ unit: resolvedCurrent.unit || "day",
11066
+ direction: resolvedCurrent.direction || "backward"
11067
+ }),
11068
+ readOnly
11069
+ }),
11070
+ h6(import_ui_extensions22.Select, {
11071
+ key: "unit",
11072
+ name: `${name}-rolling-unit`,
11073
+ label: COMPACT_LABEL,
11074
+ options: rollingUnitOptions,
11075
+ value: compound,
11076
+ onChange: handleRollingUnitChange,
11077
+ readOnly
11078
+ })
11079
+ ];
11080
+ } else if (PRESENCE_OPERATORS.has(currentOperator)) {
11081
+ valueInput = null;
11082
+ } else {
11083
+ const range = presetToRange(resolvedCurrent.preset);
11084
+ const presetValue = resolvedCurrent.preset || (lastPreset.rangeKey && range && lastPreset.rangeKey === keyOfRange(range) ? lastPreset.key : "");
11085
+ valueInput = h6(import_ui_extensions22.Select, {
11086
+ key: "preset",
11087
+ name: `${name}-preset`,
11088
+ label: COMPACT_LABEL,
11089
+ placeholder: presetPlaceholder,
11090
+ options: presetOptions || [],
11091
+ value: presetValue,
11092
+ onChange: handlePresetChange,
11093
+ readOnly
11094
+ });
11095
+ }
11096
+ const valueChildren = [
11097
+ ...Array.isArray(valueInput) ? valueInput : valueInput ? [valueInput] : [],
11098
+ showClear ? h6(import_ui_extensions22.Link, { key: "clear", onClick: handleClear }, clearLabel) : null
11099
+ ];
11100
+ const children = [operatorSelect, ...valueChildren];
11101
+ if (fieldSelect) {
11102
+ const rowChildren = [
11103
+ h6(import_ui_extensions22.Box, { key: "field-box", flex: "auto", alignSelf: "stretch" }, fieldSelect),
11104
+ operatorSelect ? h6(import_ui_extensions22.Box, { key: "operator-box", flex: "auto", alignSelf: "stretch" }, operatorSelect) : null,
11105
+ ...valueChildren.map(
11106
+ (child, index) => (child == null ? void 0 : child.type) === import_ui_extensions22.Text || (child == null ? void 0 : child.type) === import_ui_extensions22.Link ? child : h6(import_ui_extensions22.Box, { key: `value-box-${index}`, flex: "auto", alignSelf: "stretch" }, child)
11107
+ )
11108
+ ].filter(Boolean);
11109
+ const fieldControl = h6(
11110
+ import_ui_extensions22.AutoGrid,
11111
+ {
11112
+ columnWidth: gridColumnWidth,
11113
+ flexible: true,
11114
+ gap: gap ?? "xs"
11115
+ },
11116
+ ...rowChildren
11117
+ );
11118
+ if (!label) return fieldControl;
11119
+ return h6(
11120
+ import_ui_extensions22.Flex,
11121
+ { direction: "column", gap: "xs" },
11122
+ h6(import_ui_extensions22.Text, { format: { fontWeight: "demibold" } }, label),
11123
+ fieldControl
11124
+ );
11125
+ }
11126
+ const control = h6(
11127
+ import_ui_extensions22.Flex,
11128
+ {
11129
+ direction: isColumn ? "column" : "row",
11130
+ align: isColumn ? "stretch" : "center",
11131
+ gap: gap ?? (isColumn ? "sm" : "xs"),
11132
+ wrap: isColumn ? void 0 : "wrap"
11133
+ },
11134
+ ...children
11135
+ );
11136
+ if (!label) return control;
11137
+ return h6(
11138
+ import_ui_extensions22.Flex,
11139
+ { direction: "column", gap: "xs" },
11140
+ h6(import_ui_extensions22.Text, { format: { fontWeight: "demibold" } }, label),
11141
+ control
11142
+ );
11143
+ };
11144
+
8691
11145
  // src/common-components/KeyValueList.js
8692
- var import_react20 = __toESM(require("react"));
8693
- var import_ui_extensions20 = require("@hubspot/ui-extensions");
11146
+ var import_react23 = __toESM(require("react"));
11147
+ var import_ui_extensions23 = require("@hubspot/ui-extensions");
8694
11148
  var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
8695
11149
  const rows = items.map(
8696
- (item, index) => import_react20.default.createElement(
8697
- import_ui_extensions20.DescriptionListItem,
11150
+ (item, index) => import_react23.default.createElement(
11151
+ import_ui_extensions23.DescriptionListItem,
8698
11152
  {
8699
11153
  key: item.key ?? item.label ?? `kv-${index}`,
8700
11154
  label: item.label
@@ -8702,16 +11156,17 @@ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
8702
11156
  item.value
8703
11157
  )
8704
11158
  );
8705
- return import_react20.default.createElement(
8706
- import_ui_extensions20.Flex,
11159
+ return import_react23.default.createElement(
11160
+ import_ui_extensions23.Flex,
8707
11161
  { direction: "column", gap },
8708
- import_react20.default.createElement(import_ui_extensions20.DescriptionList, { direction }, ...rows)
11162
+ import_react23.default.createElement(import_ui_extensions23.DescriptionList, { direction }, ...rows)
8709
11163
  );
8710
11164
  };
11165
+ KeyValueList.displayName = "KeyValueList";
8711
11166
 
8712
11167
  // src/common-components/SectionHeader.js
8713
- var import_react21 = __toESM(require("react"));
8714
- var import_ui_extensions21 = require("@hubspot/ui-extensions");
11168
+ var import_react24 = __toESM(require("react"));
11169
+ var import_ui_extensions24 = require("@hubspot/ui-extensions");
8715
11170
  var SectionHeader = ({
8716
11171
  title,
8717
11172
  description,
@@ -8722,12 +11177,12 @@ var SectionHeader = ({
8722
11177
  }) => {
8723
11178
  const body = [];
8724
11179
  if (title != null) {
8725
- body.push(import_react21.default.createElement(import_ui_extensions21.Heading, { key: "title", as: titleAs }, title));
11180
+ body.push(import_react24.default.createElement(import_ui_extensions24.Heading, { key: "title", as: titleAs }, title));
8726
11181
  }
8727
11182
  if (description != null) {
8728
11183
  body.push(
8729
- import_react21.default.createElement(
8730
- import_ui_extensions21.Text,
11184
+ import_react24.default.createElement(
11185
+ import_ui_extensions24.Text,
8731
11186
  { key: "description", variant: "microcopy" },
8732
11187
  description
8733
11188
  )
@@ -8736,10 +11191,10 @@ var SectionHeader = ({
8736
11191
  if (children != null) {
8737
11192
  body.push(children);
8738
11193
  }
8739
- const content = import_react21.default.createElement(import_ui_extensions21.Flex, { direction: "column", gap }, ...body);
11194
+ const content = import_react24.default.createElement(import_ui_extensions24.Flex, { direction: "column", gap }, ...body);
8740
11195
  if (actions == null) return content;
8741
- return import_react21.default.createElement(
8742
- import_ui_extensions21.Flex,
11196
+ return import_react24.default.createElement(
11197
+ import_ui_extensions24.Flex,
8743
11198
  { direction: "row", justify: "between", align: "start", gap: "sm" },
8744
11199
  content,
8745
11200
  actions
@@ -8747,8 +11202,8 @@ var SectionHeader = ({
8747
11202
  };
8748
11203
 
8749
11204
  // src/common-components/Spinner.js
8750
- var import_react22 = __toESM(require("react"));
8751
- var import_ui_extensions22 = require("@hubspot/ui-extensions");
11205
+ var import_react25 = __toESM(require("react"));
11206
+ var import_ui_extensions25 = require("@hubspot/ui-extensions");
8752
11207
 
8753
11208
  // src/common-components/spinners.js
8754
11209
  var BRAILLE_DOT_MAP = [
@@ -9088,10 +11543,10 @@ var Spinner = ({
9088
11543
  const preset = SPINNERS[name] || SPINNERS[DEFAULT_NAME];
9089
11544
  const resolvedFrames = Array.isArray(frames) && frames.length > 0 ? frames : preset.frames;
9090
11545
  const resolvedInterval = Number.isFinite(interval) ? interval : preset.interval;
9091
- const [index, setIndex] = (0, import_react22.useState)(0);
9092
- const indexRef = (0, import_react22.useRef)(0);
11546
+ const [index, setIndex] = (0, import_react25.useState)(0);
11547
+ const indexRef = (0, import_react25.useRef)(0);
9093
11548
  indexRef.current = index;
9094
- (0, import_react22.useEffect)(() => {
11549
+ (0, import_react25.useEffect)(() => {
9095
11550
  if (paused || resolvedFrames.length <= 1) return void 0;
9096
11551
  const id = setInterval(() => {
9097
11552
  indexRef.current = (indexRef.current + 1) % resolvedFrames.length;
@@ -9099,17 +11554,17 @@ var Spinner = ({
9099
11554
  }, Math.max(16, resolvedInterval));
9100
11555
  return () => clearInterval(id);
9101
11556
  }, [paused, resolvedFrames, resolvedInterval]);
9102
- (0, import_react22.useEffect)(() => {
11557
+ (0, import_react25.useEffect)(() => {
9103
11558
  indexRef.current = 0;
9104
11559
  setIndex(0);
9105
11560
  }, [resolvedFrames]);
9106
11561
  const frame = resolvedFrames[index % resolvedFrames.length];
9107
11562
  const suffix = children != null ? children : label;
9108
11563
  if (suffix == null || suffix === "") {
9109
- return import_react22.default.createElement(import_ui_extensions22.Text, rest, frame);
11564
+ return import_react25.default.createElement(import_ui_extensions25.Text, rest, frame);
9110
11565
  }
9111
- return import_react22.default.createElement(
9112
- import_ui_extensions22.Text,
11566
+ return import_react25.default.createElement(
11567
+ import_ui_extensions25.Text,
9113
11568
  rest,
9114
11569
  frame,
9115
11570
  gap,
@@ -9117,6 +11572,378 @@ var Spinner = ({
9117
11572
  );
9118
11573
  };
9119
11574
 
11575
+ // src/safe/catalogs.js
11576
+ var ICON_NAME_ALIASES2 = {
11577
+ alert: "warning",
11578
+ // "alert" is a color, not an icon
11579
+ check: "success",
11580
+ checkmark: "success",
11581
+ danger: "xCircle",
11582
+ // "danger" is a StatusTag variant
11583
+ duplicate: "copy",
11584
+ error: "xCircle",
11585
+ // "error" is a Tag variant, not an icon
11586
+ trash: "delete",
11587
+ pencil: "edit",
11588
+ arrowLeft: "left",
11589
+ arrowRight: "right",
11590
+ arrowUp: "upCarat",
11591
+ arrowDown: "downCarat",
11592
+ cog: "settings",
11593
+ gear: "settings",
11594
+ close: "xCircle",
11595
+ plus: "add",
11596
+ minus: "remove",
11597
+ ok: "success"
11598
+ };
11599
+ var EMPTY_STATE_IMAGES = /* @__PURE__ */ new Set([
11600
+ "addOnReporting",
11601
+ "announcement",
11602
+ "api",
11603
+ "automatedTesting",
11604
+ "beta",
11605
+ "building",
11606
+ "callingSetUp",
11607
+ "companies",
11608
+ "components",
11609
+ "cone",
11610
+ "contacts",
11611
+ "contentStrategy",
11612
+ "customObjects",
11613
+ "customerExperience",
11614
+ "customerSupport",
11615
+ "deals",
11616
+ "developerSecurityUpdate",
11617
+ "electronicSignature",
11618
+ "electronicSignatureEmptyState",
11619
+ "emailConfirmation",
11620
+ "emptyStateCharts",
11621
+ "idea",
11622
+ "integrations",
11623
+ "leads",
11624
+ "lock",
11625
+ "missedGoal",
11626
+ "multipleObjects",
11627
+ "object",
11628
+ "productsShoppingCart",
11629
+ "registration",
11630
+ "sandboxAddOn",
11631
+ "social",
11632
+ "store",
11633
+ "storeDisabled",
11634
+ "successfullyConnectedEmail",
11635
+ "target",
11636
+ "task",
11637
+ "voteAndSearch",
11638
+ "meetings",
11639
+ "tickets"
11640
+ ]);
11641
+ var EMPTY_STATE_IMAGE_ALIASES = {
11642
+ "new-project": "components",
11643
+ newProject: "components",
11644
+ empty: "components",
11645
+ default: "components"
11646
+ };
11647
+ var TREND_DIRECTIONS = /* @__PURE__ */ new Set(["increase", "decrease"]);
11648
+ var TREND_DIRECTION_ALIASES = {
11649
+ increasing: "increase",
11650
+ decreasing: "decrease",
11651
+ up: "increase",
11652
+ down: "decrease",
11653
+ positive: "increase",
11654
+ negative: "decrease"
11655
+ };
11656
+ var SAFE_ARRAY_PROPS = {
11657
+ // native @hubspot/ui-extensions
11658
+ Select: ["options"],
11659
+ MultiSelect: ["options"],
11660
+ ToggleGroup: ["options"],
11661
+ StepIndicator: ["stepNames"],
11662
+ // hs-uix
11663
+ DataTable: ["data", "columns", "searchFields", "filters", "selectionActions"],
11664
+ Kanban: ["data", "stages"],
11665
+ FormBuilder: ["fields"],
11666
+ AvatarStack: ["items"],
11667
+ KeyValueList: ["items"],
11668
+ Feed: ["items", "fields"],
11669
+ Calendar: ["events"],
11670
+ CrmKanban: ["cardFields"]
11671
+ };
11672
+ var SAFE_DERIVE_PROPS = {
11673
+ CrmDataTable: ["columns"],
11674
+ CrmKanban: ["stages"]
11675
+ };
11676
+
11677
+ // src/safe/warnings.js
11678
+ var warned = /* @__PURE__ */ new Set();
11679
+ function warnOnce(key, message) {
11680
+ if (warned.has(key)) return;
11681
+ warned.add(key);
11682
+ console.warn(message);
11683
+ }
11684
+ function resetSafeWarnings() {
11685
+ warned.clear();
11686
+ }
11687
+
11688
+ // src/safe/withSafeArrayProps.js
11689
+ var import_react26 = __toESM(require("react"));
11690
+ var arrayProp = (value, componentName, propName) => {
11691
+ if (Array.isArray(value)) return value;
11692
+ if (value == null) return [];
11693
+ warnOnce(
11694
+ `${componentName}-${propName}-not-array`,
11695
+ `[hs-uix/safe] ${componentName}.${propName} must be an array \u2014 received ${typeof value}; using [].`
11696
+ );
11697
+ return [];
11698
+ };
11699
+ function withSafeArrayProps(Component, componentName, propNames, derivePropNames = []) {
11700
+ if (!Component) return void 0;
11701
+ const SafeComponent = import_react26.default.forwardRef((props, ref) => {
11702
+ const next = { ...props || {} };
11703
+ for (const propName of propNames) {
11704
+ next[propName] = arrayProp(next[propName], componentName, propName);
11705
+ }
11706
+ for (const propName of derivePropNames) {
11707
+ const value = next[propName];
11708
+ if (value == null || Array.isArray(value)) continue;
11709
+ warnOnce(
11710
+ `${componentName}-${propName}-not-array`,
11711
+ `[hs-uix/safe] ${componentName}.${propName} must be an array \u2014 received ${typeof value}; omitting it so ${componentName} derives it automatically.`
11712
+ );
11713
+ delete next[propName];
11714
+ }
11715
+ return import_react26.default.createElement(Component, ref != null ? { ...next, ref } : next);
11716
+ });
11717
+ SafeComponent.displayName = `Safe${componentName}`;
11718
+ return SafeComponent;
11719
+ }
11720
+
11721
+ // src/safe/SafeIcon.js
11722
+ var import_react27 = __toESM(require("react"));
11723
+ var import_ui_extensions26 = require("@hubspot/ui-extensions");
11724
+ var SafeIcon = (props) => {
11725
+ const { name, ...rest } = props || {};
11726
+ if (typeof name === "string" && NATIVE_ICON_NAMES.has(name)) {
11727
+ return import_react27.default.createElement(import_ui_extensions26.Icon, { name, ...rest });
11728
+ }
11729
+ const alias = ICON_NAME_ALIASES2[name];
11730
+ if (alias && NATIVE_ICON_NAMES.has(alias)) {
11731
+ warnOnce(
11732
+ `icon-alias-${name}`,
11733
+ `[hs-uix/safe] Icon name "${name}" is not in the catalog \u2014 auto-repaired to "${alias}".`
11734
+ );
11735
+ return import_react27.default.createElement(import_ui_extensions26.Icon, { name: alias, ...rest });
11736
+ }
11737
+ warnOnce(
11738
+ `icon-invalid-${name}`,
11739
+ `[hs-uix/safe] Icon name "${name}" is not in the catalog. Rendering a red xCircle placeholder. See NATIVE_ICON_NAMES for the valid list.`
11740
+ );
11741
+ return import_react27.default.createElement(import_ui_extensions26.Icon, {
11742
+ ...rest,
11743
+ name: "xCircle",
11744
+ color: "alert",
11745
+ screenReaderText: `Invalid icon: ${name ?? "(missing)"}`
11746
+ });
11747
+ };
11748
+ SafeIcon.displayName = "SafeIcon";
11749
+
11750
+ // src/safe/SafeEmptyState.js
11751
+ var import_react28 = __toESM(require("react"));
11752
+ var import_ui_extensions27 = require("@hubspot/ui-extensions");
11753
+ var SafeEmptyState = (props) => {
11754
+ const { imageName, ...rest } = props || {};
11755
+ if (imageName == null || EMPTY_STATE_IMAGES.has(imageName)) {
11756
+ return import_react28.default.createElement(import_ui_extensions27.EmptyState, { imageName, ...rest });
11757
+ }
11758
+ const alias = EMPTY_STATE_IMAGE_ALIASES[imageName];
11759
+ const fallback = alias && EMPTY_STATE_IMAGES.has(alias) ? alias : "components";
11760
+ warnOnce(
11761
+ `emptystate-image-${imageName}`,
11762
+ `[hs-uix/safe] EmptyState imageName "${imageName}" is not valid \u2014 using "${fallback}". See EMPTY_STATE_IMAGES for the valid list.`
11763
+ );
11764
+ return import_react28.default.createElement(import_ui_extensions27.EmptyState, { imageName: fallback, ...rest });
11765
+ };
11766
+ SafeEmptyState.displayName = "SafeEmptyState";
11767
+
11768
+ // src/safe/SafeStatisticsTrend.js
11769
+ var import_react29 = __toESM(require("react"));
11770
+ var import_ui_extensions28 = require("@hubspot/ui-extensions");
11771
+ var SafeStatisticsTrend = (props) => {
11772
+ const { direction, ...rest } = props || {};
11773
+ if (typeof direction !== "string" || TREND_DIRECTIONS.has(direction)) {
11774
+ return import_react29.default.createElement(import_ui_extensions28.StatisticsTrend, { direction, ...rest });
11775
+ }
11776
+ const alias = TREND_DIRECTION_ALIASES[direction];
11777
+ if (alias && TREND_DIRECTIONS.has(alias)) {
11778
+ warnOnce(
11779
+ `trend-alias-${direction}`,
11780
+ `[hs-uix/safe] StatisticsTrend direction "${direction}" \u2192 "${alias}". Valid values: increase, decrease.`
11781
+ );
11782
+ return import_react29.default.createElement(import_ui_extensions28.StatisticsTrend, { direction: alias, ...rest });
11783
+ }
11784
+ warnOnce(
11785
+ `trend-invalid-${direction}`,
11786
+ `[hs-uix/safe] StatisticsTrend direction "${direction}" is not valid \u2014 defaulting to "increase".`
11787
+ );
11788
+ return import_react29.default.createElement(import_ui_extensions28.StatisticsTrend, { ...rest, direction: "increase" });
11789
+ };
11790
+ SafeStatisticsTrend.displayName = "SafeStatisticsTrend";
11791
+
11792
+ // src/safe/SafePopover.js
11793
+ var import_react30 = __toESM(require("react"));
11794
+ var import_ui_extensions29 = require("@hubspot/ui-extensions");
11795
+ var import_experimental2 = require("@hubspot/ui-extensions/experimental");
11796
+ var SafePopover = (props) => {
11797
+ const { children, ...rest } = props || {};
11798
+ return import_react30.default.createElement(
11799
+ import_experimental2.Popover,
11800
+ rest,
11801
+ import_react30.default.createElement(import_ui_extensions29.Tile, { compact: true }, children)
11802
+ );
11803
+ };
11804
+ SafePopover.displayName = "SafePopover";
11805
+
11806
+ // src/safe/safeComponents.js
11807
+ var import_ui_extensions30 = require("@hubspot/ui-extensions");
11808
+ var wrap = (Component, name) => withSafeArrayProps(Component, name, SAFE_ARRAY_PROPS[name] ?? [], SAFE_DERIVE_PROPS[name] ?? []);
11809
+ var SafeSelect = wrap(import_ui_extensions30.Select, "Select");
11810
+ var SafeMultiSelect = wrap(import_ui_extensions30.MultiSelect, "MultiSelect");
11811
+ var SafeToggleGroup = wrap(import_ui_extensions30.ToggleGroup, "ToggleGroup");
11812
+ var SafeStepIndicator = wrap(import_ui_extensions30.StepIndicator, "StepIndicator");
11813
+ var SafeDataTable = wrap(DataTable, "DataTable");
11814
+ var SafeKanban = wrap(Kanban, "Kanban");
11815
+ var SafeFormBuilder = wrap(FormBuilder, "FormBuilder");
11816
+ var SafeAvatarStack = wrap(AvatarStack, "AvatarStack");
11817
+ var SafeKeyValueList = wrap(KeyValueList, "KeyValueList");
11818
+ var SafeFeed = wrap(Feed, "Feed");
11819
+ var SafeCalendar = wrap(Calendar, "Calendar");
11820
+ var SafeCrmDataTable = wrap(CrmDataTable, "CrmDataTable");
11821
+ var SafeCrmKanban = wrap(CrmKanban, "CrmKanban");
11822
+
11823
+ // src/utils/applyPatches.js
11824
+ function applyPatches(doc, patches) {
11825
+ if (!patches || patches.length === 0) return doc;
11826
+ let out = doc ?? {};
11827
+ for (const op of patches) {
11828
+ out = applyOne(out, op);
11829
+ }
11830
+ return out;
11831
+ }
11832
+ function applyOne(doc, op) {
11833
+ const path = parsePointer(op.path);
11834
+ switch (op.op) {
11835
+ case "add":
11836
+ return setAt(doc, path, op.value, true);
11837
+ case "replace":
11838
+ return setAt(doc, path, op.value, false);
11839
+ case "remove":
11840
+ return removeAt(doc, path);
11841
+ case "move": {
11842
+ const from = parsePointer(op.from);
11843
+ const value = getAt(doc, from);
11844
+ const removed = removeAt(doc, from);
11845
+ return setAt(removed, path, value, true);
11846
+ }
11847
+ case "copy": {
11848
+ const from = parsePointer(op.from);
11849
+ const value = getAt(doc, from);
11850
+ return setAt(doc, path, deepClone2(value), true);
11851
+ }
11852
+ default:
11853
+ console.warn("[hs-uix] applyPatches: ignoring unsupported op:", op.op);
11854
+ return doc;
11855
+ }
11856
+ }
11857
+ function parsePointer(pointer) {
11858
+ if (!pointer || pointer === "/") return [];
11859
+ if (pointer[0] !== "/") {
11860
+ throw new Error(`invalid JSON Pointer (must start with /): ${pointer}`);
11861
+ }
11862
+ return pointer.slice(1).split("/").map((seg) => seg.replace(/~1/g, "/").replace(/~0/g, "~"));
11863
+ }
11864
+ function getAt(obj, segs) {
11865
+ let cur = obj;
11866
+ for (const seg of segs) {
11867
+ if (cur == null) return void 0;
11868
+ if (Array.isArray(cur)) {
11869
+ cur = cur[Number(seg)];
11870
+ } else if (typeof cur === "object") {
11871
+ cur = Object.prototype.hasOwnProperty.call(cur, seg) ? cur[seg] : void 0;
11872
+ } else {
11873
+ return void 0;
11874
+ }
11875
+ }
11876
+ return cur;
11877
+ }
11878
+ function setAt(obj, segs, value, insert) {
11879
+ if (segs.length === 0) return value;
11880
+ const [head, ...rest] = segs;
11881
+ if (Array.isArray(obj)) {
11882
+ const next = obj.slice();
11883
+ const idx = head === "-" ? next.length : Number(head);
11884
+ if (rest.length === 0) {
11885
+ if (head === "-") {
11886
+ next.push(value);
11887
+ } else if (insert && Number.isInteger(idx) && idx >= 0 && idx <= next.length) {
11888
+ next.splice(idx, 0, value);
11889
+ } else {
11890
+ next[idx] = value;
11891
+ }
11892
+ return next;
11893
+ }
11894
+ const existing2 = idx >= 0 && idx < next.length ? next[idx] : void 0;
11895
+ next[idx] = setAt(
11896
+ existing2 ?? (looksLikeArrayIndex(rest[0]) ? [] : {}),
11897
+ rest,
11898
+ value,
11899
+ insert
11900
+ );
11901
+ return next;
11902
+ }
11903
+ const base = obj && typeof obj === "object" ? obj : {};
11904
+ if (rest.length === 0) {
11905
+ return { ...base, [head]: value };
11906
+ }
11907
+ const existing = base[head];
11908
+ const child = setAt(
11909
+ existing ?? (looksLikeArrayIndex(rest[0]) ? [] : {}),
11910
+ rest,
11911
+ value,
11912
+ insert
11913
+ );
11914
+ return { ...base, [head]: child };
11915
+ }
11916
+ function removeAt(obj, segs) {
11917
+ if (segs.length === 0) return void 0;
11918
+ const [head, ...rest] = segs;
11919
+ if (Array.isArray(obj)) {
11920
+ const idx = Number(head);
11921
+ if (!Number.isInteger(idx) || idx < 0 || idx >= obj.length) return obj;
11922
+ const next = obj.slice();
11923
+ if (rest.length === 0) {
11924
+ next.splice(idx, 1);
11925
+ } else {
11926
+ next[idx] = removeAt(next[idx], rest);
11927
+ }
11928
+ return next;
11929
+ }
11930
+ if (!obj || typeof obj !== "object") return obj;
11931
+ if (rest.length === 0) {
11932
+ const next = { ...obj };
11933
+ delete next[head];
11934
+ return next;
11935
+ }
11936
+ if (!(head in obj)) return obj;
11937
+ return { ...obj, [head]: removeAt(obj[head], rest) };
11938
+ }
11939
+ function looksLikeArrayIndex(seg) {
11940
+ return seg === "-" || /^\d+$/.test(seg);
11941
+ }
11942
+ function deepClone2(v) {
11943
+ const json = v === void 0 ? void 0 : JSON.stringify(v);
11944
+ return json === void 0 ? void 0 : JSON.parse(json);
11945
+ }
11946
+
9120
11947
  // src/utils/collections.js
9121
11948
  var sumBy = (items, keyOrFn) => (items || []).reduce((total, item) => {
9122
11949
  const value = typeof keyOrFn === "function" ? keyOrFn(item) : item == null ? void 0 : item[keyOrFn];
@@ -9205,9 +12032,19 @@ var findOptionLabel2 = (options, value, fallback = "") => {
9205
12032
  CrmDataTable,
9206
12033
  CrmKanban,
9207
12034
  CrmLookupSelect,
12035
+ CrmRecordPicker,
12036
+ DATE_FILTER_OPERATORS,
12037
+ DATE_RANGE_CUSTOM_VALUE,
12038
+ DATE_ROLLING_UNIT_OPTIONS,
12039
+ DEFAULT_FEED_TYPE_PRESETS,
9208
12040
  DEFAULT_SVG_FONT_WEIGHT,
9209
12041
  DataTable,
12042
+ DateRangePicker,
12043
+ EMPTY_STATE_IMAGES,
12044
+ EMPTY_STATE_IMAGE_ALIASES,
12045
+ FILTER_OPERATORS,
9210
12046
  Feed,
12047
+ FilterBuilder,
9211
12048
  FormBuilder,
9212
12049
  HS_DATE_DIRECTION_LABELS,
9213
12050
  HS_DATE_PRESETS,
@@ -9226,21 +12063,61 @@ var findOptionLabel2 = (options, value, fallback = "") => {
9226
12063
  HS_TEXT_COLOR,
9227
12064
  ICONS,
9228
12065
  ICON_NAMES,
12066
+ ICON_NAME_ALIASES,
9229
12067
  Icon,
9230
12068
  Kanban,
9231
12069
  KanbanCardActions,
9232
12070
  KeyValueList,
12071
+ NATIVE_ICON_NAMES,
9233
12072
  NATIVE_ICON_NAME_LIST,
12073
+ SAFE_ARRAY_PROPS,
12074
+ SAFE_DERIVE_PROPS,
12075
+ SKELETON_FILL,
9234
12076
  SPINNERS,
9235
12077
  SPINNER_NAMES,
12078
+ SafeAvatarStack,
12079
+ SafeCalendar,
12080
+ SafeCrmDataTable,
12081
+ SafeCrmKanban,
12082
+ SafeDataTable,
12083
+ SafeEmptyState,
12084
+ SafeFeed,
12085
+ SafeFormBuilder,
12086
+ SafeIcon,
12087
+ SafeKanban,
12088
+ SafeKeyValueList,
12089
+ SafeMultiSelect,
12090
+ SafePopover,
12091
+ SafeSelect,
12092
+ SafeStatisticsTrend,
12093
+ SafeStepIndicator,
12094
+ SafeToggleGroup,
9236
12095
  SectionHeader,
9237
12096
  Spinner,
9238
12097
  StyledText,
12098
+ TREND_DIRECTIONS,
12099
+ TREND_DIRECTION_ALIASES,
12100
+ UNASSIGNED_LANE_KEY,
12101
+ addFilter,
12102
+ applyPatches,
12103
+ applyTypePreset,
9239
12104
  buildCrmSearchConfig,
9240
12105
  buildOptions,
12106
+ changeConditionOperator,
12107
+ changeConditionProperty,
12108
+ compareHsDateValues,
12109
+ computeStageCounts,
12110
+ conditionToCrmFilter,
12111
+ countConditions,
12112
+ createCondition,
12113
+ createGroup,
9241
12114
  createStatusTagSortComparator,
9242
12115
  crmSearchResultToOption,
12116
+ evaluateWip,
12117
+ fieldsFromHubSpotProperties,
12118
+ findNewlyExceededWip,
9243
12119
  findOptionLabel,
12120
+ flushBuffer,
9244
12121
  formatCurrency,
9245
12122
  formatCurrencyCompact,
9246
12123
  formatDate,
@@ -9248,7 +12125,14 @@ var findOptionLabel2 = (options, value, fallback = "") => {
9248
12125
  formatPercentage,
9249
12126
  getAutoStatusTagVariant,
9250
12127
  getAutoTagVariant,
12128
+ getLaneKey,
12129
+ getNodeAtPath,
12130
+ getOperatorOptions,
9251
12131
  gridToBraille,
12132
+ isConditionNode,
12133
+ isGroupNode,
12134
+ isValidDateRange,
12135
+ lookupTypePreset,
9252
12136
  makeAvatarStackDataUri,
9253
12137
  makeCrmSearchMultiSelectField,
9254
12138
  makeCrmSearchSelectField,
@@ -9257,10 +12141,28 @@ var findOptionLabel2 = (options, value, fallback = "") => {
9257
12141
  makeStyledTextDataUri,
9258
12142
  normalizeCrmSearchRecord,
9259
12143
  normalizeCrmSearchRows,
12144
+ operatorExpectsHighValue,
12145
+ operatorExpectsValue,
12146
+ operatorExpectsValues,
12147
+ orderLaneKeys,
12148
+ partitionLanes,
12149
+ partitionNewItems,
12150
+ presetToRange,
12151
+ removeFilter,
12152
+ resetSafeWarnings,
9260
12153
  resolveCrmObjectType,
12154
+ resolveLaneLabel,
12155
+ resolveWipLimit,
9261
12156
  sumBy,
9262
12157
  svgToIconEntry,
12158
+ toCrmSearchFilterGroups,
12159
+ toHsDateValue,
12160
+ toTimestampMs,
12161
+ updateFilter,
9263
12162
  useCrmSearchDataSource,
9264
12163
  useCrmSearchOptions,
9265
- useFormPrefill
12164
+ useFormPrefill,
12165
+ validateTree,
12166
+ warnOnce,
12167
+ withSafeArrayProps
9266
12168
  });