hs-uix 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +3 -1
  2. package/common-components.d.ts +319 -68
  3. package/dist/calendar.js +355 -57
  4. package/dist/calendar.mjs +356 -57
  5. package/dist/common-components.js +3546 -88
  6. package/dist/common-components.mjs +3530 -84
  7. package/dist/datatable.js +108 -18
  8. package/dist/datatable.mjs +108 -18
  9. package/dist/experimental.js +2876 -0
  10. package/dist/experimental.mjs +2883 -0
  11. package/dist/feed.js +267 -38
  12. package/dist/feed.mjs +260 -37
  13. package/dist/filter.js +1379 -0
  14. package/dist/filter.mjs +1334 -0
  15. package/dist/form.js +222 -26
  16. package/dist/form.mjs +227 -27
  17. package/dist/index.js +3208 -287
  18. package/dist/index.mjs +3156 -283
  19. package/dist/kanban.js +282 -62
  20. package/dist/kanban.mjs +273 -61
  21. package/dist/safe.js +9207 -0
  22. package/dist/safe.mjs +9298 -0
  23. package/dist/utils.js +491 -75
  24. package/dist/utils.mjs +491 -75
  25. package/experimental.d.ts +1 -0
  26. package/filter.d.ts +1 -0
  27. package/index.d.ts +45 -3
  28. package/package.json +19 -1
  29. package/safe.d.ts +1 -0
  30. package/src/calendar/README.md +74 -5
  31. package/src/calendar/index.d.ts +95 -1
  32. package/src/common-components/README.md +140 -1
  33. package/src/datatable/README.md +0 -2
  34. package/src/experimental/README.md +126 -0
  35. package/src/experimental/index.d.ts +346 -0
  36. package/src/feed/README.md +69 -0
  37. package/src/feed/index.d.ts +103 -0
  38. package/src/filter/README.md +148 -0
  39. package/src/filter/index.d.ts +221 -0
  40. package/src/form/README.md +132 -4
  41. package/src/form/index.d.ts +82 -1
  42. package/src/kanban/README.md +119 -6
  43. package/src/kanban/index.d.ts +153 -2
  44. package/src/safe/README.md +108 -0
  45. package/src/safe/index.d.ts +158 -0
  46. package/src/utils/README.md +39 -0
  47. package/src/wizard/README.md +158 -0
  48. package/src/wizard/index.d.ts +138 -0
  49. package/utils.d.ts +17 -0
package/dist/index.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"];
@@ -6976,9 +7745,172 @@ var makeSpacerDataUri = (height = 24, width = 4) => {
6976
7745
  return { src: toDataUri(svg), width, height };
6977
7746
  };
6978
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
+
6979
7910
  // src/calendar/Calendar.jsx
6980
7911
  var DEFAULT_MAX_EVENTS_PER_DAY = 3;
6981
7912
  var ALL_VIEWS = ["month", "week", "day", "agenda"];
7913
+ var ALL_VIEWS_WITH_RESOURCE = ["month", "week", "day", "resource", "agenda"];
6982
7914
  var DEFAULT_DAY_START_HOUR = 8;
6983
7915
  var DEFAULT_DAY_END_HOUR = 20;
6984
7916
  var DEFAULT_TIME_ZONES = [
@@ -7014,7 +7946,8 @@ var VIEW_LABELS = {
7014
7946
  month: "Month",
7015
7947
  week: "Week",
7016
7948
  day: "Day",
7017
- agenda: "Agenda"
7949
+ agenda: "Agenda",
7950
+ resource: "Resource"
7018
7951
  };
7019
7952
  var DEFAULT_LABELS5 = {
7020
7953
  today: "Today",
@@ -7031,7 +7964,11 @@ var DEFAULT_LABELS5 = {
7031
7964
  errorMessage: "An error occurred while loading events.",
7032
7965
  dayDetailTitle: (label) => label,
7033
7966
  open: "Open",
7034
- allDay: "All day"
7967
+ allDay: "All day",
7968
+ reschedule: "Reschedule",
7969
+ pickDate: "Pick date",
7970
+ unassigned: "Unassigned",
7971
+ resource: "Resource"
7035
7972
  };
7036
7973
  var DEFAULT_EVENT_FIELDS = {
7037
7974
  id: "id",
@@ -7087,8 +8024,9 @@ var truncateMonthLabel = (value, max) => {
7087
8024
  var MONTH_COL_WIDTH = 160;
7088
8025
  var TIMEGRID_DAY_COL = 150;
7089
8026
  var TIMEGRID_DAY_COL_SINGLE = 560;
8027
+ var RESOURCE_LABEL_COL_WIDTH = "min";
7090
8028
  var HOUR_SLOT_HEIGHT = 64;
7091
- var EventDetail = ({ event, labels }) => {
8029
+ var EventDetail = ({ event, labels, reschedule, idSuffix = "" }) => {
7092
8030
  const { start, end, title, subtitle, href } = event;
7093
8031
  let when = "";
7094
8032
  if (start) {
@@ -7102,11 +8040,27 @@ var EventDetail = ({ event, labels }) => {
7102
8040
  when = `${formatDayTitle(start)} \u2013 ${formatDayTitle(end)}`;
7103
8041
  }
7104
8042
  }
7105
- 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);
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);
7106
8060
  };
7107
- var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "") => {
8061
+ var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "", reschedule = null) => {
7108
8062
  if (mode === "none") return void 0;
7109
- 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 });
7110
8064
  const id = `cal-evt-${event.key}${idSuffix}`;
7111
8065
  if (mode === "modal") {
7112
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));
@@ -7116,17 +8070,17 @@ var buildOverlay = (event, mode, renderEventDetail, labels, idSuffix = "") => {
7116
8070
  }
7117
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));
7118
8072
  };
7119
- var AgendaEventRow = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8073
+ var AgendaEventRow = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, reschedule }) => {
7120
8074
  const variant = VALID_VARIANTS.has(event.color) ? event.color : "default";
7121
- const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-ag${day.getTime()}` : "");
8075
+ const overlay = buildOverlay(event, overlayMode, renderEventDetail, labels, day ? `-ag${day.getTime()}` : "", reschedule);
7122
8076
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7123
8077
  const timeLabel = isAllDayEvent(event) ? labels.allDay : formatTime(event.start);
7124
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);
7125
8079
  };
7126
8080
  var MONTH_SLOT_HEIGHT = 24;
7127
- var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, monthEventStyle, monthEventMaxChars }) => {
8081
+ var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, monthEventStyle, monthEventMaxChars, reschedule, idScope = "" }) => {
7128
8082
  const isStartDay = !day || !event.start || isSameDay(event.start, day);
7129
- 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);
7130
8084
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7131
8085
  const variant = VALID_VARIANTS.has(event.color) ? event.color : "default";
7132
8086
  const startHasTime = event.start && (event.start.getHours() !== 0 || event.start.getMinutes() !== 0);
@@ -7139,9 +8093,9 @@ var MonthChip = ({ event, day, overlayMode, renderEventDetail, onEventClick, lab
7139
8093
  }
7140
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));
7141
8095
  };
7142
- var DayListItem = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels }) => {
8096
+ var DayListItem = ({ event, day, overlayMode, renderEventDetail, onEventClick, labels, reschedule, idScope = "" }) => {
7143
8097
  const handleClick = onEventClick ? () => onEventClick(event.raw, event) : void 0;
7144
- 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);
7145
8099
  const href = event.href;
7146
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 || "--");
7147
8101
  };
@@ -7206,6 +8160,40 @@ var Toolbar = ({
7206
8160
  }
7207
8161
  ));
7208
8162
  };
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
+ };
7209
8197
  var MonthView = ({
7210
8198
  refDate,
7211
8199
  now,
@@ -7220,45 +8208,23 @@ var MonthView = ({
7220
8208
  }) => {
7221
8209
  const headers = weekdayLabels(weekStartsOn, hideWeekends, true);
7222
8210
  const today = now || /* @__PURE__ */ new Date();
7223
- const slotSpacer = makeSpacerDataUri(MONTH_SLOT_HEIGHT, 1);
7224
8211
  const renderCell = (day) => {
7225
8212
  const dayEvents = eventsForDay(day);
7226
8213
  const inMonth = isSameMonth(day, refDate);
7227
8214
  const isToday = isSameDay(day, today);
7228
8215
  if (renderDayCell) return renderDayCell(day, dayEvents);
7229
- const shown = dayEvents.slice(0, maxEventsPerDay);
7230
- const hasOverflow = dayEvents.length > maxEventsPerDay;
7231
- const heightSpacer = /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" });
7232
- const slotRow = (key, content) => /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { key, direction: "row", align: "center", gap: "flush" }, heightSpacer, content);
7233
- const slots = [];
7234
- for (let i = 0; i < maxEventsPerDay; i++) {
7235
- if (i < shown.length) {
7236
- slots.push(slotRow(shown[i].key, /* @__PURE__ */ import_react15.default.createElement(MonthChip, { event: shown[i], day, ...chipProps })));
7237
- } else {
7238
- slots.push(/* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { key: `sp-${i}`, src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" }));
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
7239
8224
  }
7240
- }
7241
- if (hasOverflow) {
7242
- slots.push(
7243
- slotRow(
7244
- "more",
7245
- /* @__PURE__ */ import_react15.default.createElement(
7246
- import_ui_extensions15.Link,
7247
- {
7248
- 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", justify: "center", align: "center", gap: "xs" }, /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Text, { format: { fontWeight: "bold" } }, 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)))))
7249
- },
7250
- labels.more(dayEvents.length - maxEventsPerDay)
7251
- )
7252
- )
7253
- );
7254
- } else {
7255
- slots.push(
7256
- /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Image, { key: "more-sp", src: slotSpacer.src, width: slotSpacer.width, height: slotSpacer.height, alt: "" })
7257
- );
7258
- }
7259
- 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()))), slots));
8225
+ )));
7260
8226
  };
7261
- 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) => {
7262
8228
  const days = hideWeekends ? week.filter((d) => d.getDay() !== 0 && d.getDay() !== 6) : week;
7263
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))));
7264
8230
  })));
@@ -7278,13 +8244,40 @@ var AgendaView = ({ rangeStart, rangeEnd, eventsForDay, chipProps, labels, rende
7278
8244
  }
7279
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 })))));
7280
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
+ };
7281
8274
  var formatTimedDuration = (start, end) => {
7282
8275
  const mins = Math.max(0, Math.round(((end || start).getTime() - start.getTime()) / 6e4));
7283
- const h6 = Math.floor(mins / 60);
8276
+ const h7 = Math.floor(mins / 60);
7284
8277
  const m = mins % 60;
7285
- if (h6 === 0) return `${m} min`;
7286
- if (m === 0) return `${h6} hr`;
7287
- 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`;
7288
8281
  };
7289
8282
  var hourSpan = (event) => {
7290
8283
  const start = event.start;
@@ -7325,7 +8318,8 @@ var TimeGridView = ({ days, now, hours, dayStartHour, dayEndHour, eventsForDay,
7325
8318
  chipProps.overlayMode,
7326
8319
  chipProps.renderEventDetail,
7327
8320
  chipProps.labels,
7328
- `-tg${mode}${hour}-${dayMs}`
8321
+ `-tg${mode}${hour}-${dayMs}`,
8322
+ chipProps.reschedule
7329
8323
  );
7330
8324
  const handleClick = chipProps.onEventClick ? () => chipProps.onEventClick(e.raw, e) : void 0;
7331
8325
  const variant = VALID_VARIANTS.has(e.color) ? e.color : "default";
@@ -7410,6 +8404,14 @@ var Calendar = (props) => {
7410
8404
  // time grid (week / day)
7411
8405
  dayStartHour = DEFAULT_DAY_START_HOUR,
7412
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,
7413
8415
  // timezone
7414
8416
  timeZone: controlledTimeZone,
7415
8417
  defaultTimeZone,
@@ -7448,10 +8450,12 @@ var Calendar = (props) => {
7448
8450
  const fields = (0, import_react15.useMemo)(() => ({ ...DEFAULT_EVENT_FIELDS, ...eventFields || {} }), [eventFields]);
7449
8451
  const [internalView, setInternalView] = (0, import_react15.useState)(defaultView);
7450
8452
  const view = controlledView != null ? controlledView : internalView;
8453
+ const resourceEnabled = resources && resources.length > 0 || resourceField != null;
7451
8454
  const enabledViews = (0, import_react15.useMemo)(() => {
7452
- const base = viewsProp && viewsProp.length > 0 ? viewsProp : ALL_VIEWS;
7453
- return base.filter((v) => ALL_VIEWS.includes(v));
7454
- }, [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]);
7455
8459
  const setView = (0, import_react15.useCallback)(
7456
8460
  (next) => {
7457
8461
  if (controlledView == null) setInternalView(next);
@@ -7476,7 +8480,9 @@ var Calendar = (props) => {
7476
8480
  const focusedDate = (controlledFocusedDate != null ? toDate2(controlledFocusedDate) : internalDate) || startOfDay(nowWall);
7477
8481
  const stepFor = (0, import_react15.useCallback)(
7478
8482
  (dir) => {
7479
- 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
+ }
7480
8486
  if (view === "day") return addDays(focusedDate, dir);
7481
8487
  return addMonths(focusedDate, dir);
7482
8488
  },
@@ -7503,7 +8509,7 @@ var Calendar = (props) => {
7503
8509
  rangeEnd: endOfDay(flat[flat.length - 1])
7504
8510
  };
7505
8511
  }
7506
- if (view === "week") {
8512
+ if (view === "week" || view === "resource") {
7507
8513
  const days = buildWeekDays(focusedDate, weekStartsOn, hideWeekends);
7508
8514
  return {
7509
8515
  weeks: null,
@@ -7576,14 +8582,18 @@ var Calendar = (props) => {
7576
8582
  );
7577
8583
  const normalized = (0, import_react15.useMemo)(
7578
8584
  () => (events || []).map((raw, index) => {
7579
- const start = toWallClock(toDate2(resolveField(raw, fields.start)), timeZone);
7580
- 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);
7581
8589
  const id = resolveField(raw, fields.id);
7582
8590
  return {
7583
8591
  key: id != null ? String(id) : `evt-${index}`,
7584
8592
  id,
7585
8593
  start,
7586
8594
  end: endRaw || start,
8595
+ sourceStart,
8596
+ sourceEnd: sourceEnd || sourceStart,
7587
8597
  title: resolveField(raw, fields.title),
7588
8598
  subtitle: resolveField(raw, fields.subtitle),
7589
8599
  color: resolveField(raw, fields.color),
@@ -7620,12 +8630,40 @@ var Calendar = (props) => {
7620
8630
  }, [rangeKey]);
7621
8631
  const title = (0, import_react15.useMemo)(() => {
7622
8632
  if (view === "day") return formatDayTitle(focusedDate);
7623
- if (view === "week" || view === "agenda") {
7624
- 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);
7625
8635
  return formatRangeTitle(days[0], days[days.length - 1]);
7626
8636
  }
7627
8637
  return formatMonthTitle(focusedDate);
7628
8638
  }, [view, focusedDate, weekStartsOn, hideWeekends]);
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]);
7629
8667
  const safeMonthEventStyle = MONTH_EVENT_STYLES.has(monthEventStyle) ? monthEventStyle : "statusTag";
7630
8668
  const chipProps = {
7631
8669
  overlayMode,
@@ -7633,8 +8671,19 @@ var Calendar = (props) => {
7633
8671
  onEventClick,
7634
8672
  labels,
7635
8673
  monthEventStyle: safeMonthEventStyle,
7636
- monthEventMaxChars
8674
+ monthEventMaxChars,
8675
+ reschedule
7637
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]);
7638
8687
  const timeZoneOptions = (0, import_react15.useMemo)(() => {
7639
8688
  if (!showTimeZoneSelect) return null;
7640
8689
  const base = (timeZoneOptionsProp && timeZoneOptionsProp.length ? timeZoneOptionsProp : DEFAULT_TIME_ZONES).map(
@@ -7695,6 +8744,19 @@ var Calendar = (props) => {
7695
8744
  labels
7696
8745
  }
7697
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
+ );
7698
8760
  } else if (view === "week" || view === "day") {
7699
8761
  body = /* @__PURE__ */ import_react15.default.createElement(
7700
8762
  TimeGridView,
@@ -7724,11 +8786,687 @@ var Calendar = (props) => {
7724
8786
  }
7725
8787
  return /* @__PURE__ */ import_react15.default.createElement(import_ui_extensions15.Flex, { direction: "column", gap: "sm" }, toolbar, body);
7726
8788
  };
8789
+ Calendar.displayName = "Calendar";
7727
8790
 
7728
- // src/common-components/AutoTag.js
8791
+ // src/filter/FilterBuilder.jsx
7729
8792
  var import_react16 = __toESM(require("react"));
7730
8793
  var import_ui_extensions16 = require("@hubspot/ui-extensions");
7731
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
+
7732
9470
  // src/utils/tagVariants.js
7733
9471
  var DEFAULT_VARIANT = "default";
7734
9472
  var DANGER_VARIANT = "danger";
@@ -7925,16 +9663,16 @@ var AutoTag = ({
7925
9663
  overrides,
7926
9664
  fallback
7927
9665
  });
7928
- return import_react16.default.createElement(
7929
- import_ui_extensions16.Tag,
9666
+ return import_react17.default.createElement(
9667
+ import_ui_extensions17.Tag,
7930
9668
  { variant: resolvedVariant, ...props },
7931
9669
  displayValue
7932
9670
  );
7933
9671
  };
7934
9672
 
7935
9673
  // src/common-components/AutoStatusTag.js
7936
- var import_react17 = __toESM(require("react"));
7937
- var import_ui_extensions17 = require("@hubspot/ui-extensions");
9674
+ var import_react18 = __toESM(require("react"));
9675
+ var import_ui_extensions18 = require("@hubspot/ui-extensions");
7938
9676
  var AutoStatusTag = ({
7939
9677
  value,
7940
9678
  status,
@@ -7950,20 +9688,20 @@ var AutoStatusTag = ({
7950
9688
  overrides,
7951
9689
  fallback
7952
9690
  });
7953
- return import_react17.default.createElement(
7954
- import_ui_extensions17.StatusTag,
9691
+ return import_react18.default.createElement(
9692
+ import_ui_extensions18.StatusTag,
7955
9693
  { variant: resolvedVariant, ...props },
7956
9694
  displayValue
7957
9695
  );
7958
9696
  };
7959
9697
 
7960
9698
  // src/common-components/CrmLookupSelect.js
7961
- var import_react19 = __toESM(require("react"));
7962
- var import_ui_extensions19 = require("@hubspot/ui-extensions");
9699
+ var import_react20 = __toESM(require("react"));
9700
+ var import_ui_extensions20 = require("@hubspot/ui-extensions");
7963
9701
 
7964
9702
  // src/utils/crmSearchAdapters.js
7965
- var import_react18 = __toESM(require("react"));
7966
- var import_ui_extensions18 = require("@hubspot/ui-extensions");
9703
+ var import_react19 = __toESM(require("react"));
9704
+ var import_ui_extensions19 = require("@hubspot/ui-extensions");
7967
9705
 
7968
9706
  // src/utils/objectPath.js
7969
9707
  var getByPath = (obj, path) => {
@@ -8080,9 +9818,9 @@ var useCrmSearchDataSource = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) =>
8080
9818
  error,
8081
9819
  mapResponse
8082
9820
  } = options;
8083
- const config = (0, import_react18.useMemo)(() => buildCrmSearchConfig(params, options), [params, options]);
8084
- const response = (0, import_ui_extensions18.useCrmSearch)(config, format);
8085
- 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)(() => {
8086
9824
  var _a;
8087
9825
  const rows = mapResponse ? mapResponse(response) : normalizeCrmSearchRows(response, { idField: rowIdField, ...row || EMPTY_OBJECT });
8088
9826
  const resolvedTotal = typeof totalCount === "function" ? totalCount(response) : totalCount ?? pickTotal(response, rows.length);
@@ -8120,7 +9858,7 @@ var crmSearchResultToOption = (row, options = EMPTY_OBJECT) => {
8120
9858
  var useCrmSearchOptions = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
8121
9859
  const dataSource = useCrmSearchDataSource(params, options);
8122
9860
  const optionConfig = options.option || options;
8123
- return (0, import_react18.useMemo)(() => ({
9861
+ return (0, import_react19.useMemo)(() => ({
8124
9862
  ...dataSource,
8125
9863
  options: dataSource.rows.map((row) => crmSearchResultToOption(row, optionConfig))
8126
9864
  }), [dataSource, optionConfig]);
@@ -8249,29 +9987,29 @@ var CrmDataTable = ({
8249
9987
  ...props
8250
9988
  }) => {
8251
9989
  var _a, _b;
8252
- const [params, setParams] = (0, import_react18.useState)({ search: "", filters: {}, sort: null });
8253
- const resolvedProperties = (0, import_react18.useMemo)(() => properties, [properties]);
8254
- 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)(
8255
9993
  () => columns || inferCrmColumns(resolvedProperties),
8256
9994
  [columns, resolvedProperties]
8257
9995
  );
8258
9996
  const resolvedSearchFields = searchFields || resolvedProperties;
8259
- const autoFilterFields = (0, import_react18.useMemo)(
9997
+ const autoFilterFields = (0, import_react19.useMemo)(
8260
9998
  () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
8261
9999
  [autoFilters, resolvedProperties]
8262
10000
  );
8263
- const autoFilterLabelsRef = (0, import_react18.useRef)({});
8264
- const defaultPropertyMap = (0, import_react18.useMemo)(
10001
+ const autoFilterLabelsRef = (0, import_react19.useRef)({});
10002
+ const defaultPropertyMap = (0, import_react19.useMemo)(
8265
10003
  () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
8266
10004
  [resolvedProperties]
8267
10005
  );
8268
10006
  const effectivePropertyMap = propertyMap || defaultPropertyMap;
8269
- const resolvedSortMap = (0, import_react18.useMemo)(
10007
+ const resolvedSortMap = (0, import_react19.useMemo)(
8270
10008
  () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
8271
10009
  [sortMap, effectivePropertyMap]
8272
10010
  );
8273
10011
  const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
8274
- const dataSourceOptions = (0, import_react18.useMemo)(
10012
+ const dataSourceOptions = (0, import_react19.useMemo)(
8275
10013
  () => ({
8276
10014
  objectType: resolveCrmObjectType(objectType),
8277
10015
  properties: resolvedProperties,
@@ -8285,18 +10023,18 @@ var CrmDataTable = ({
8285
10023
  }),
8286
10024
  [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
8287
10025
  );
8288
- const [serverQuerying, setServerQuerying] = (0, import_react18.useState)(!!serverSide);
10026
+ const [serverQuerying, setServerQuerying] = (0, import_react19.useState)(!!serverSide);
8289
10027
  const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
8290
10028
  const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
8291
- const queryKey = (0, import_react18.useMemo)(
10029
+ const queryKey = (0, import_react19.useMemo)(
8292
10030
  () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
8293
10031
  [effectiveParams, objectType, resolvedProperties, pageLength]
8294
10032
  );
8295
- const [accumulatedRows, setAccumulatedRows] = (0, import_react18.useState)(EMPTY_ARRAY);
8296
- const [requestedPage, setRequestedPage] = (0, import_react18.useState)(1);
8297
- 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);
8298
10036
  const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
8299
- (0, import_react18.useEffect)(() => {
10037
+ (0, import_react19.useEffect)(() => {
8300
10038
  var _a2;
8301
10039
  if (lastQueryKeyRef.current !== queryKey) {
8302
10040
  lastQueryKeyRef.current = queryKey;
@@ -8306,12 +10044,12 @@ var CrmDataTable = ({
8306
10044
  const currentPage = ((_a2 = dataSource.pagination) == null ? void 0 : _a2.currentPage) || 1;
8307
10045
  setAccumulatedRows((prev) => currentPage <= 1 ? dataSource.data : appendUniqueRows(prev, dataSource.data, rowIdField));
8308
10046
  }, [queryKey, dataSource.data, (_a = dataSource.pagination) == null ? void 0 : _a.currentPage, rowIdField]);
8309
- (0, import_react18.useEffect)(() => {
10047
+ (0, import_react19.useEffect)(() => {
8310
10048
  if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > loadedRows.length) {
8311
10049
  setServerQuerying(true);
8312
10050
  }
8313
10051
  }, [serverQuerying, dataSource.totalCount, loadedRows.length]);
8314
- const ensurePageLoaded = (0, import_react18.useCallback)((page) => {
10052
+ const ensurePageLoaded = (0, import_react19.useCallback)((page) => {
8315
10053
  var _a2, _b2, _c;
8316
10054
  const pageNumber = Number(page) || 1;
8317
10055
  const requiredRows = pageNumber * pageSize;
@@ -8319,10 +10057,10 @@ var CrmDataTable = ({
8319
10057
  if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
8320
10058
  (_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
8321
10059
  }, [pageSize, loadedRows.length, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
8322
- (0, import_react18.useEffect)(() => {
10060
+ (0, import_react19.useEffect)(() => {
8323
10061
  ensurePageLoaded(requestedPage);
8324
10062
  }, [requestedPage, ensurePageLoaded]);
8325
- const generatedFilters = (0, import_react18.useMemo)(
10063
+ const generatedFilters = (0, import_react19.useMemo)(
8326
10064
  () => buildAutoFiltersFromRows({
8327
10065
  rows: loadedRows,
8328
10066
  fields: autoFilterFields,
@@ -8332,7 +10070,7 @@ var CrmDataTable = ({
8332
10070
  [loadedRows, autoFilterFields, autoFilterMaxOptions]
8333
10071
  );
8334
10072
  const resolvedFilters = filters || generatedFilters;
8335
- const table = import_react18.default.createElement(DataTable, {
10073
+ const table = import_react19.default.createElement(DataTable, {
8336
10074
  title: title || `${prettifyPropertyName(objectType)} records`,
8337
10075
  data: loadedRows,
8338
10076
  loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
@@ -8356,11 +10094,11 @@ var CrmDataTable = ({
8356
10094
  const total = dataSource.totalCount;
8357
10095
  const capped = typeof total === "number" && total > loadedRows.length;
8358
10096
  if (!capped) return table;
8359
- return import_react18.default.createElement(
8360
- import_ui_extensions18.Flex,
10097
+ return import_react19.default.createElement(
10098
+ import_ui_extensions19.Flex,
8361
10099
  { direction: "column", gap: "xs" },
8362
- import_react18.default.createElement(
8363
- import_ui_extensions18.Text,
10100
+ import_react19.default.createElement(
10101
+ import_ui_extensions19.Text,
8364
10102
  { variant: "microcopy" },
8365
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.`
8366
10104
  ),
@@ -8394,25 +10132,25 @@ var CrmKanban = ({
8394
10132
  ...props
8395
10133
  }) => {
8396
10134
  var _a, _b;
8397
- const [params, setParams] = (0, import_react18.useState)(EMPTY_CRM_PARAMS);
8398
- 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]);
8399
10137
  const resolvedSearchFields = searchFields || resolvedProperties;
8400
- const autoFilterFields = (0, import_react18.useMemo)(
10138
+ const autoFilterFields = (0, import_react19.useMemo)(
8401
10139
  () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
8402
10140
  [autoFilters, resolvedProperties]
8403
10141
  );
8404
- const autoFilterLabelsRef = (0, import_react18.useRef)({});
8405
- const defaultPropertyMap = (0, import_react18.useMemo)(
10142
+ const autoFilterLabelsRef = (0, import_react19.useRef)({});
10143
+ const defaultPropertyMap = (0, import_react19.useMemo)(
8406
10144
  () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
8407
10145
  [resolvedProperties]
8408
10146
  );
8409
10147
  const effectivePropertyMap = propertyMap || defaultPropertyMap;
8410
- const resolvedSortMap = (0, import_react18.useMemo)(
10148
+ const resolvedSortMap = (0, import_react19.useMemo)(
8411
10149
  () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
8412
10150
  [sortMap, effectivePropertyMap]
8413
10151
  );
8414
10152
  const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
8415
- const dataSourceOptions = (0, import_react18.useMemo)(
10153
+ const dataSourceOptions = (0, import_react19.useMemo)(
8416
10154
  () => ({
8417
10155
  objectType: resolveCrmObjectType(objectType),
8418
10156
  properties: resolvedProperties,
@@ -8426,17 +10164,17 @@ var CrmKanban = ({
8426
10164
  }),
8427
10165
  [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
8428
10166
  );
8429
- const [serverQuerying, setServerQuerying] = (0, import_react18.useState)(!!serverSide);
10167
+ const [serverQuerying, setServerQuerying] = (0, import_react19.useState)(!!serverSide);
8430
10168
  const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
8431
10169
  const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
8432
- const queryKey = (0, import_react18.useMemo)(
10170
+ const queryKey = (0, import_react19.useMemo)(
8433
10171
  () => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
8434
10172
  [effectiveParams, objectType, resolvedProperties, pageLength]
8435
10173
  );
8436
- const [accumulatedRows, setAccumulatedRows] = (0, import_react18.useState)(EMPTY_ARRAY);
8437
- 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);
8438
10176
  const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
8439
- (0, import_react18.useEffect)(() => {
10177
+ (0, import_react19.useEffect)(() => {
8440
10178
  var _a2;
8441
10179
  if (lastQueryKeyRef.current !== queryKey) {
8442
10180
  lastQueryKeyRef.current = queryKey;
@@ -8446,12 +10184,12 @@ var CrmKanban = ({
8446
10184
  const currentPage = ((_a2 = dataSource.pagination) == null ? void 0 : _a2.currentPage) || 1;
8447
10185
  setAccumulatedRows((prev) => currentPage <= 1 ? dataSource.data : appendUniqueRows(prev, dataSource.data, rowIdField));
8448
10186
  }, [queryKey, dataSource.data, (_a = dataSource.pagination) == null ? void 0 : _a.currentPage, rowIdField]);
8449
- (0, import_react18.useEffect)(() => {
10187
+ (0, import_react19.useEffect)(() => {
8450
10188
  if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > loadedRows.length) {
8451
10189
  setServerQuerying(true);
8452
10190
  }
8453
10191
  }, [serverQuerying, dataSource.totalCount, loadedRows.length]);
8454
- const handleLoadMore = (0, import_react18.useCallback)((stage) => {
10192
+ const handleLoadMore = (0, import_react19.useCallback)((stage) => {
8455
10193
  var _a2, _b2, _c;
8456
10194
  if (onLoadMore) {
8457
10195
  onLoadMore(stage);
@@ -8460,7 +10198,7 @@ var CrmKanban = ({
8460
10198
  if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
8461
10199
  (_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
8462
10200
  }, [onLoadMore, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
8463
- const generatedFilters = (0, import_react18.useMemo)(
10201
+ const generatedFilters = (0, import_react19.useMemo)(
8464
10202
  () => buildAutoFiltersFromRows({
8465
10203
  rows: loadedRows,
8466
10204
  fields: autoFilterFields,
@@ -8470,7 +10208,7 @@ var CrmKanban = ({
8470
10208
  [loadedRows, autoFilterFields, autoFilterMaxOptions]
8471
10209
  );
8472
10210
  const resolvedFilters = filters || generatedFilters;
8473
- const resolvedStages = (0, import_react18.useMemo)(() => {
10211
+ const resolvedStages = (0, import_react19.useMemo)(() => {
8474
10212
  if (stages) return stages;
8475
10213
  const seen = [];
8476
10214
  for (const row of loadedRows) {
@@ -8482,7 +10220,7 @@ var CrmKanban = ({
8482
10220
  label: typeof stageLabels === "function" ? stageLabels(value) : stageLabels && stageLabels[value] || prettifyPropertyName(String(value))
8483
10221
  }));
8484
10222
  }, [stages, stageLabels, loadedRows, groupBy]);
8485
- const resolvedStageMeta = (0, import_react18.useMemo)(() => {
10223
+ const resolvedStageMeta = (0, import_react19.useMemo)(() => {
8486
10224
  if (stageMeta || !dataSource.hasMore) return stageMeta;
8487
10225
  return Object.fromEntries(resolvedStages.map((stage) => {
8488
10226
  var _a2;
@@ -8496,7 +10234,7 @@ var CrmKanban = ({
8496
10234
  ];
8497
10235
  }));
8498
10236
  }, [stageMeta, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.totalCount, resolvedStages]);
8499
- const board = import_react18.default.createElement(Kanban, {
10237
+ const board = import_react19.default.createElement(Kanban, {
8500
10238
  title: title || `${prettifyPropertyName(objectType)} board`,
8501
10239
  data: loadedRows,
8502
10240
  loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
@@ -8519,17 +10257,19 @@ var CrmKanban = ({
8519
10257
  const total = dataSource.totalCount;
8520
10258
  const capped = typeof total === "number" && total > loadedRows.length;
8521
10259
  if (!capped) return board;
8522
- return import_react18.default.createElement(
8523
- import_ui_extensions18.Flex,
10260
+ return import_react19.default.createElement(
10261
+ import_ui_extensions19.Flex,
8524
10262
  { direction: "column", gap: "xs" },
8525
- import_react18.default.createElement(
8526
- import_ui_extensions18.Text,
10263
+ import_react19.default.createElement(
10264
+ import_ui_extensions19.Text,
8527
10265
  { variant: "microcopy" },
8528
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.`
8529
10267
  ),
8530
10268
  board
8531
10269
  );
8532
10270
  };
10271
+ CrmDataTable.displayName = "CrmDataTable";
10272
+ CrmKanban.displayName = "CrmKanban";
8533
10273
 
8534
10274
  // src/common-components/CrmLookupSelect.js
8535
10275
  var EMPTY_ARRAY2 = [];
@@ -8582,12 +10322,12 @@ var CrmLookupSelect = ({
8582
10322
  loadingOption,
8583
10323
  selectProps = EMPTY_OBJECT2
8584
10324
  }) => {
8585
- const [inputValue, setInputValue] = (0, import_react19.useState)(query || "");
8586
- const [pickedOptions, setPickedOptions] = (0, import_react19.useState)(EMPTY_ARRAY2);
8587
- 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);
8588
10328
  const search = debounce > 0 ? debouncedInput : inputValue;
8589
10329
  const effectiveSearch = search && search.length >= minSearchLength ? search : "";
8590
- const optionConfig = (0, import_react19.useMemo)(
10330
+ const optionConfig = (0, import_react20.useMemo)(
8591
10331
  () => makeOptionConfig({ option, labelProperty, valueProperty, descriptionProperty }),
8592
10332
  [option, labelProperty, valueProperty, descriptionProperty]
8593
10333
  );
@@ -8605,7 +10345,7 @@ var CrmLookupSelect = ({
8605
10345
  );
8606
10346
  const isSearching = dataSource.loading || inputValue.trim() !== (search || "").trim();
8607
10347
  const hasQuery = effectiveSearch.length > 0;
8608
- const options = (0, import_react19.useMemo)(() => {
10348
+ const options = (0, import_react20.useMemo)(() => {
8609
10349
  const remembered = [...selectedOptions || EMPTY_ARRAY2, ...pickedOptions];
8610
10350
  const baseOptions = mergeSelectedOptions(dataSource.options || EMPTY_ARRAY2, remembered, value);
8611
10351
  if (isSearching && loadingOption) return [loadingOption, ...baseOptions];
@@ -8644,7 +10384,281 @@ var CrmLookupSelect = ({
8644
10384
  },
8645
10385
  ...selectProps
8646
10386
  };
8647
- 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
+ );
8648
10662
  };
8649
10663
 
8650
10664
  // src/common-components/datePresets.js
@@ -8669,13 +10683,472 @@ var HS_DATE_DIRECTION_LABELS = {
8669
10683
  desc: "Descending"
8670
10684
  };
8671
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
+
8672
11145
  // src/common-components/KeyValueList.js
8673
- var import_react20 = __toESM(require("react"));
8674
- var import_ui_extensions20 = require("@hubspot/ui-extensions");
11146
+ var import_react23 = __toESM(require("react"));
11147
+ var import_ui_extensions23 = require("@hubspot/ui-extensions");
8675
11148
  var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
8676
11149
  const rows = items.map(
8677
- (item, index) => import_react20.default.createElement(
8678
- import_ui_extensions20.DescriptionListItem,
11150
+ (item, index) => import_react23.default.createElement(
11151
+ import_ui_extensions23.DescriptionListItem,
8679
11152
  {
8680
11153
  key: item.key ?? item.label ?? `kv-${index}`,
8681
11154
  label: item.label
@@ -8683,16 +11156,17 @@ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
8683
11156
  item.value
8684
11157
  )
8685
11158
  );
8686
- return import_react20.default.createElement(
8687
- import_ui_extensions20.Flex,
11159
+ return import_react23.default.createElement(
11160
+ import_ui_extensions23.Flex,
8688
11161
  { direction: "column", gap },
8689
- import_react20.default.createElement(import_ui_extensions20.DescriptionList, { direction }, ...rows)
11162
+ import_react23.default.createElement(import_ui_extensions23.DescriptionList, { direction }, ...rows)
8690
11163
  );
8691
11164
  };
11165
+ KeyValueList.displayName = "KeyValueList";
8692
11166
 
8693
11167
  // src/common-components/SectionHeader.js
8694
- var import_react21 = __toESM(require("react"));
8695
- var import_ui_extensions21 = require("@hubspot/ui-extensions");
11168
+ var import_react24 = __toESM(require("react"));
11169
+ var import_ui_extensions24 = require("@hubspot/ui-extensions");
8696
11170
  var SectionHeader = ({
8697
11171
  title,
8698
11172
  description,
@@ -8703,12 +11177,12 @@ var SectionHeader = ({
8703
11177
  }) => {
8704
11178
  const body = [];
8705
11179
  if (title != null) {
8706
- 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));
8707
11181
  }
8708
11182
  if (description != null) {
8709
11183
  body.push(
8710
- import_react21.default.createElement(
8711
- import_ui_extensions21.Text,
11184
+ import_react24.default.createElement(
11185
+ import_ui_extensions24.Text,
8712
11186
  { key: "description", variant: "microcopy" },
8713
11187
  description
8714
11188
  )
@@ -8717,10 +11191,10 @@ var SectionHeader = ({
8717
11191
  if (children != null) {
8718
11192
  body.push(children);
8719
11193
  }
8720
- 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);
8721
11195
  if (actions == null) return content;
8722
- return import_react21.default.createElement(
8723
- import_ui_extensions21.Flex,
11196
+ return import_react24.default.createElement(
11197
+ import_ui_extensions24.Flex,
8724
11198
  { direction: "row", justify: "between", align: "start", gap: "sm" },
8725
11199
  content,
8726
11200
  actions
@@ -8728,8 +11202,8 @@ var SectionHeader = ({
8728
11202
  };
8729
11203
 
8730
11204
  // src/common-components/Spinner.js
8731
- var import_react22 = __toESM(require("react"));
8732
- var import_ui_extensions22 = require("@hubspot/ui-extensions");
11205
+ var import_react25 = __toESM(require("react"));
11206
+ var import_ui_extensions25 = require("@hubspot/ui-extensions");
8733
11207
 
8734
11208
  // src/common-components/spinners.js
8735
11209
  var BRAILLE_DOT_MAP = [
@@ -9069,10 +11543,10 @@ var Spinner = ({
9069
11543
  const preset = SPINNERS[name] || SPINNERS[DEFAULT_NAME];
9070
11544
  const resolvedFrames = Array.isArray(frames) && frames.length > 0 ? frames : preset.frames;
9071
11545
  const resolvedInterval = Number.isFinite(interval) ? interval : preset.interval;
9072
- const [index, setIndex] = (0, import_react22.useState)(0);
9073
- const indexRef = (0, import_react22.useRef)(0);
11546
+ const [index, setIndex] = (0, import_react25.useState)(0);
11547
+ const indexRef = (0, import_react25.useRef)(0);
9074
11548
  indexRef.current = index;
9075
- (0, import_react22.useEffect)(() => {
11549
+ (0, import_react25.useEffect)(() => {
9076
11550
  if (paused || resolvedFrames.length <= 1) return void 0;
9077
11551
  const id = setInterval(() => {
9078
11552
  indexRef.current = (indexRef.current + 1) % resolvedFrames.length;
@@ -9080,17 +11554,17 @@ var Spinner = ({
9080
11554
  }, Math.max(16, resolvedInterval));
9081
11555
  return () => clearInterval(id);
9082
11556
  }, [paused, resolvedFrames, resolvedInterval]);
9083
- (0, import_react22.useEffect)(() => {
11557
+ (0, import_react25.useEffect)(() => {
9084
11558
  indexRef.current = 0;
9085
11559
  setIndex(0);
9086
11560
  }, [resolvedFrames]);
9087
11561
  const frame = resolvedFrames[index % resolvedFrames.length];
9088
11562
  const suffix = children != null ? children : label;
9089
11563
  if (suffix == null || suffix === "") {
9090
- return import_react22.default.createElement(import_ui_extensions22.Text, rest, frame);
11564
+ return import_react25.default.createElement(import_ui_extensions25.Text, rest, frame);
9091
11565
  }
9092
- return import_react22.default.createElement(
9093
- import_ui_extensions22.Text,
11566
+ return import_react25.default.createElement(
11567
+ import_ui_extensions25.Text,
9094
11568
  rest,
9095
11569
  frame,
9096
11570
  gap,
@@ -9098,6 +11572,378 @@ var Spinner = ({
9098
11572
  );
9099
11573
  };
9100
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
+
9101
11947
  // src/utils/collections.js
9102
11948
  var sumBy = (items, keyOrFn) => (items || []).reduce((total, item) => {
9103
11949
  const value = typeof keyOrFn === "function" ? keyOrFn(item) : item == null ? void 0 : item[keyOrFn];
@@ -9186,9 +12032,19 @@ var findOptionLabel2 = (options, value, fallback = "") => {
9186
12032
  CrmDataTable,
9187
12033
  CrmKanban,
9188
12034
  CrmLookupSelect,
12035
+ CrmRecordPicker,
12036
+ DATE_FILTER_OPERATORS,
12037
+ DATE_RANGE_CUSTOM_VALUE,
12038
+ DATE_ROLLING_UNIT_OPTIONS,
12039
+ DEFAULT_FEED_TYPE_PRESETS,
9189
12040
  DEFAULT_SVG_FONT_WEIGHT,
9190
12041
  DataTable,
12042
+ DateRangePicker,
12043
+ EMPTY_STATE_IMAGES,
12044
+ EMPTY_STATE_IMAGE_ALIASES,
12045
+ FILTER_OPERATORS,
9191
12046
  Feed,
12047
+ FilterBuilder,
9192
12048
  FormBuilder,
9193
12049
  HS_DATE_DIRECTION_LABELS,
9194
12050
  HS_DATE_PRESETS,
@@ -9207,21 +12063,61 @@ var findOptionLabel2 = (options, value, fallback = "") => {
9207
12063
  HS_TEXT_COLOR,
9208
12064
  ICONS,
9209
12065
  ICON_NAMES,
12066
+ ICON_NAME_ALIASES,
9210
12067
  Icon,
9211
12068
  Kanban,
9212
12069
  KanbanCardActions,
9213
12070
  KeyValueList,
12071
+ NATIVE_ICON_NAMES,
9214
12072
  NATIVE_ICON_NAME_LIST,
12073
+ SAFE_ARRAY_PROPS,
12074
+ SAFE_DERIVE_PROPS,
12075
+ SKELETON_FILL,
9215
12076
  SPINNERS,
9216
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,
9217
12095
  SectionHeader,
9218
12096
  Spinner,
9219
12097
  StyledText,
12098
+ TREND_DIRECTIONS,
12099
+ TREND_DIRECTION_ALIASES,
12100
+ UNASSIGNED_LANE_KEY,
12101
+ addFilter,
12102
+ applyPatches,
12103
+ applyTypePreset,
9220
12104
  buildCrmSearchConfig,
9221
12105
  buildOptions,
12106
+ changeConditionOperator,
12107
+ changeConditionProperty,
12108
+ compareHsDateValues,
12109
+ computeStageCounts,
12110
+ conditionToCrmFilter,
12111
+ countConditions,
12112
+ createCondition,
12113
+ createGroup,
9222
12114
  createStatusTagSortComparator,
9223
12115
  crmSearchResultToOption,
12116
+ evaluateWip,
12117
+ fieldsFromHubSpotProperties,
12118
+ findNewlyExceededWip,
9224
12119
  findOptionLabel,
12120
+ flushBuffer,
9225
12121
  formatCurrency,
9226
12122
  formatCurrencyCompact,
9227
12123
  formatDate,
@@ -9229,7 +12125,14 @@ var findOptionLabel2 = (options, value, fallback = "") => {
9229
12125
  formatPercentage,
9230
12126
  getAutoStatusTagVariant,
9231
12127
  getAutoTagVariant,
12128
+ getLaneKey,
12129
+ getNodeAtPath,
12130
+ getOperatorOptions,
9232
12131
  gridToBraille,
12132
+ isConditionNode,
12133
+ isGroupNode,
12134
+ isValidDateRange,
12135
+ lookupTypePreset,
9233
12136
  makeAvatarStackDataUri,
9234
12137
  makeCrmSearchMultiSelectField,
9235
12138
  makeCrmSearchSelectField,
@@ -9238,10 +12141,28 @@ var findOptionLabel2 = (options, value, fallback = "") => {
9238
12141
  makeStyledTextDataUri,
9239
12142
  normalizeCrmSearchRecord,
9240
12143
  normalizeCrmSearchRows,
12144
+ operatorExpectsHighValue,
12145
+ operatorExpectsValue,
12146
+ operatorExpectsValues,
12147
+ orderLaneKeys,
12148
+ partitionLanes,
12149
+ partitionNewItems,
12150
+ presetToRange,
12151
+ removeFilter,
12152
+ resetSafeWarnings,
9241
12153
  resolveCrmObjectType,
12154
+ resolveLaneLabel,
12155
+ resolveWipLimit,
9242
12156
  sumBy,
9243
12157
  svgToIconEntry,
12158
+ toCrmSearchFilterGroups,
12159
+ toHsDateValue,
12160
+ toTimestampMs,
12161
+ updateFilter,
9244
12162
  useCrmSearchDataSource,
9245
12163
  useCrmSearchOptions,
9246
- useFormPrefill
12164
+ useFormPrefill,
12165
+ validateTree,
12166
+ warnOnce,
12167
+ withSafeArrayProps
9247
12168
  });