hs-uix 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +3 -1
  2. package/common-components.d.ts +319 -68
  3. package/dist/calendar.js +397 -119
  4. package/dist/calendar.mjs +399 -119
  5. package/dist/common-components.js +3546 -88
  6. package/dist/common-components.mjs +3530 -84
  7. package/dist/datatable.js +108 -18
  8. package/dist/datatable.mjs +108 -18
  9. package/dist/experimental.js +2876 -0
  10. package/dist/experimental.mjs +2883 -0
  11. package/dist/feed.js +267 -38
  12. package/dist/feed.mjs +260 -37
  13. package/dist/filter.js +1379 -0
  14. package/dist/filter.mjs +1334 -0
  15. package/dist/form.js +222 -26
  16. package/dist/form.mjs +227 -27
  17. package/dist/index.js +3255 -353
  18. package/dist/index.mjs +3199 -344
  19. package/dist/kanban.js +282 -62
  20. package/dist/kanban.mjs +273 -61
  21. package/dist/safe.js +9207 -0
  22. package/dist/safe.mjs +9298 -0
  23. package/dist/utils.js +491 -75
  24. package/dist/utils.mjs +491 -75
  25. package/experimental.d.ts +1 -0
  26. package/filter.d.ts +1 -0
  27. package/index.d.ts +45 -3
  28. package/package.json +19 -1
  29. package/safe.d.ts +1 -0
  30. package/src/calendar/README.md +76 -5
  31. package/src/calendar/index.d.ts +108 -1
  32. package/src/common-components/README.md +140 -1
  33. package/src/datatable/README.md +0 -2
  34. package/src/experimental/README.md +126 -0
  35. package/src/experimental/index.d.ts +346 -0
  36. package/src/feed/README.md +69 -0
  37. package/src/feed/index.d.ts +103 -0
  38. package/src/filter/README.md +148 -0
  39. package/src/filter/index.d.ts +221 -0
  40. package/src/form/README.md +132 -4
  41. package/src/form/index.d.ts +82 -1
  42. package/src/kanban/README.md +119 -6
  43. package/src/kanban/index.d.ts +153 -2
  44. package/src/safe/README.md +108 -0
  45. package/src/safe/index.d.ts +158 -0
  46. package/src/utils/README.md +39 -0
  47. package/src/wizard/README.md +158 -0
  48. package/src/wizard/index.d.ts +138 -0
  49. package/utils.d.ts +17 -0
package/dist/utils.js CHANGED
@@ -31,6 +31,7 @@ var utils_exports = {};
31
31
  __export(utils_exports, {
32
32
  CrmDataTable: () => CrmDataTable,
33
33
  CrmKanban: () => CrmKanban,
34
+ applyPatches: () => applyPatches,
34
35
  buildActiveFilterChips: () => buildActiveFilterChips,
35
36
  buildCrmSearchConfig: () => buildCrmSearchConfig,
36
37
  buildOptions: () => buildOptions,
@@ -69,6 +70,130 @@ __export(utils_exports, {
69
70
  });
70
71
  module.exports = __toCommonJS(utils_exports);
71
72
 
73
+ // src/utils/applyPatches.js
74
+ function applyPatches(doc, patches) {
75
+ if (!patches || patches.length === 0) return doc;
76
+ let out = doc ?? {};
77
+ for (const op of patches) {
78
+ out = applyOne(out, op);
79
+ }
80
+ return out;
81
+ }
82
+ function applyOne(doc, op) {
83
+ const path = parsePointer(op.path);
84
+ switch (op.op) {
85
+ case "add":
86
+ return setAt(doc, path, op.value, true);
87
+ case "replace":
88
+ return setAt(doc, path, op.value, false);
89
+ case "remove":
90
+ return removeAt(doc, path);
91
+ case "move": {
92
+ const from = parsePointer(op.from);
93
+ const value = getAt(doc, from);
94
+ const removed = removeAt(doc, from);
95
+ return setAt(removed, path, value, true);
96
+ }
97
+ case "copy": {
98
+ const from = parsePointer(op.from);
99
+ const value = getAt(doc, from);
100
+ return setAt(doc, path, deepClone(value), true);
101
+ }
102
+ default:
103
+ console.warn("[hs-uix] applyPatches: ignoring unsupported op:", op.op);
104
+ return doc;
105
+ }
106
+ }
107
+ function parsePointer(pointer) {
108
+ if (!pointer || pointer === "/") return [];
109
+ if (pointer[0] !== "/") {
110
+ throw new Error(`invalid JSON Pointer (must start with /): ${pointer}`);
111
+ }
112
+ return pointer.slice(1).split("/").map((seg) => seg.replace(/~1/g, "/").replace(/~0/g, "~"));
113
+ }
114
+ function getAt(obj, segs) {
115
+ let cur = obj;
116
+ for (const seg of segs) {
117
+ if (cur == null) return void 0;
118
+ if (Array.isArray(cur)) {
119
+ cur = cur[Number(seg)];
120
+ } else if (typeof cur === "object") {
121
+ cur = Object.prototype.hasOwnProperty.call(cur, seg) ? cur[seg] : void 0;
122
+ } else {
123
+ return void 0;
124
+ }
125
+ }
126
+ return cur;
127
+ }
128
+ function setAt(obj, segs, value, insert) {
129
+ if (segs.length === 0) return value;
130
+ const [head, ...rest] = segs;
131
+ if (Array.isArray(obj)) {
132
+ const next = obj.slice();
133
+ const idx = head === "-" ? next.length : Number(head);
134
+ if (rest.length === 0) {
135
+ if (head === "-") {
136
+ next.push(value);
137
+ } else if (insert && Number.isInteger(idx) && idx >= 0 && idx <= next.length) {
138
+ next.splice(idx, 0, value);
139
+ } else {
140
+ next[idx] = value;
141
+ }
142
+ return next;
143
+ }
144
+ const existing2 = idx >= 0 && idx < next.length ? next[idx] : void 0;
145
+ next[idx] = setAt(
146
+ existing2 ?? (looksLikeArrayIndex(rest[0]) ? [] : {}),
147
+ rest,
148
+ value,
149
+ insert
150
+ );
151
+ return next;
152
+ }
153
+ const base = obj && typeof obj === "object" ? obj : {};
154
+ if (rest.length === 0) {
155
+ return { ...base, [head]: value };
156
+ }
157
+ const existing = base[head];
158
+ const child = setAt(
159
+ existing ?? (looksLikeArrayIndex(rest[0]) ? [] : {}),
160
+ rest,
161
+ value,
162
+ insert
163
+ );
164
+ return { ...base, [head]: child };
165
+ }
166
+ function removeAt(obj, segs) {
167
+ if (segs.length === 0) return void 0;
168
+ const [head, ...rest] = segs;
169
+ if (Array.isArray(obj)) {
170
+ const idx = Number(head);
171
+ if (!Number.isInteger(idx) || idx < 0 || idx >= obj.length) return obj;
172
+ const next = obj.slice();
173
+ if (rest.length === 0) {
174
+ next.splice(idx, 1);
175
+ } else {
176
+ next[idx] = removeAt(next[idx], rest);
177
+ }
178
+ return next;
179
+ }
180
+ if (!obj || typeof obj !== "object") return obj;
181
+ if (rest.length === 0) {
182
+ const next = { ...obj };
183
+ delete next[head];
184
+ return next;
185
+ }
186
+ if (!(head in obj)) return obj;
187
+ return { ...obj, [head]: removeAt(obj[head], rest) };
188
+ }
189
+ function looksLikeArrayIndex(seg) {
190
+ return seg === "-" || /^\d+$/.test(seg);
191
+ }
192
+ function deepClone(v) {
193
+ const json = v === void 0 ? void 0 : JSON.stringify(v);
194
+ return json === void 0 ? void 0 : JSON.parse(json);
195
+ }
196
+
72
197
  // src/utils/collections.js
73
198
  var sumBy = (items, keyOrFn) => (items || []).reduce((total, item) => {
74
199
  const value = typeof keyOrFn === "function" ? keyOrFn(item) : item == null ? void 0 : item[keyOrFn];
@@ -597,7 +722,7 @@ var GENERATED_ICONS = {
597
722
  "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"] }
598
723
  };
599
724
 
600
- // src/common-components/Icon.js
725
+ // src/common-components/nativeIconNames.js
601
726
  var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
602
727
  "add",
603
728
  "appointment",
@@ -609,12 +734,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
609
734
  "block",
610
735
  "book",
611
736
  "bulb",
737
+ "callTranscript",
612
738
  "calling",
613
739
  "callingHangup",
614
740
  "callingMade",
615
741
  "callingMissed",
616
742
  "callingVoicemail",
617
- "callTranscript",
618
743
  "campaigns",
619
744
  "cap",
620
745
  "checkCircle",
@@ -643,13 +768,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
643
768
  "enroll",
644
769
  "exclamation",
645
770
  "exclamationCircle",
646
- "facebook",
647
771
  "faceHappy",
648
772
  "faceHappyFilled",
649
773
  "faceNeutral",
650
774
  "faceNeutralFilled",
651
775
  "faceSad",
652
776
  "faceSadFilled",
777
+ "facebook",
653
778
  "favoriteHollow",
654
779
  "file",
655
780
  "filledXCircleIcon",
@@ -790,6 +915,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
790
915
  "zoomIn",
791
916
  "zoomOut"
792
917
  ]);
918
+
919
+ // src/common-components/Icon.js
793
920
  var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
794
921
  var NATIVE_SIZE_TOKENS = {
795
922
  sm: "sm",
@@ -1002,6 +1129,7 @@ var CollectionFilterControl = ({
1002
1129
  { key: name, direction: "row", align: "center", gap: "xs" },
1003
1130
  h3(import_ui_extensions5.DateInput, {
1004
1131
  name: `${controlName}-from`,
1132
+ label: filter.fromLabel ?? labels.dateFrom,
1005
1133
  placeholder: filter.fromLabel ?? labels.dateFrom,
1006
1134
  format: "medium",
1007
1135
  value: rangeValue.from ?? null,
@@ -1010,6 +1138,7 @@ var CollectionFilterControl = ({
1010
1138
  h3(Icon, { name: "right", size: "sm" }),
1011
1139
  h3(import_ui_extensions5.DateInput, {
1012
1140
  name: `${controlName}-to`,
1141
+ label: filter.toLabel ?? labels.dateTo,
1013
1142
  placeholder: filter.toLabel ?? labels.dateTo,
1014
1143
  format: "medium",
1015
1144
  value: rangeValue.to ?? null,
@@ -1148,6 +1277,45 @@ var editValidationError = (result) => {
1148
1277
  return typeof result === "string" ? result : "Invalid value";
1149
1278
  };
1150
1279
 
1280
+ // src/datatable/rowExpansion.js
1281
+ var extractRowId = (row, rowIdField = "id", fallback = void 0) => {
1282
+ const id = row == null ? void 0 : row[rowIdField];
1283
+ return id != null ? id : fallback;
1284
+ };
1285
+ var normalizeExpandedIds = (ids) => {
1286
+ if (ids == null) return /* @__PURE__ */ new Set();
1287
+ const list = ids instanceof Set ? [...ids] : Array.isArray(ids) ? ids : [ids];
1288
+ return new Set(list.filter((id) => id != null));
1289
+ };
1290
+ var expandRowId = (expandedIds, rowId, expandSingle = false) => {
1291
+ if (rowId == null) return expandedIds;
1292
+ if (expandSingle) return /* @__PURE__ */ new Set([rowId]);
1293
+ const next = new Set(expandedIds);
1294
+ next.add(rowId);
1295
+ return next;
1296
+ };
1297
+ var collapseRowId = (expandedIds, rowId) => {
1298
+ if (rowId == null || !expandedIds.has(rowId)) return expandedIds;
1299
+ const next = new Set(expandedIds);
1300
+ next.delete(rowId);
1301
+ return next;
1302
+ };
1303
+ var toggleExpandedId = (expandedIds, rowId, expandSingle = false) => {
1304
+ if (rowId == null) return expandedIds;
1305
+ return expandedIds.has(rowId) ? collapseRowId(expandedIds, rowId) : expandRowId(expandedIds, rowId, expandSingle);
1306
+ };
1307
+ var withDetailRows = (items, expandedIds, rowIdField = "id") => {
1308
+ if (!expandedIds || expandedIds.size === 0) return items;
1309
+ const out = [];
1310
+ items.forEach((item) => {
1311
+ out.push(item);
1312
+ if (item.type === "data" && expandedIds.has(extractRowId(item.row, rowIdField))) {
1313
+ out.push({ type: "detail", row: item.row });
1314
+ }
1315
+ });
1316
+ return out;
1317
+ };
1318
+
1151
1319
  // src/datatable/DataTable.jsx
1152
1320
  var import_ui_extensions7 = require("@hubspot/ui-extensions");
1153
1321
  var NARROW_EDIT_TYPES = /* @__PURE__ */ new Set(["checkbox", "toggle"]);
@@ -1359,6 +1527,21 @@ var DataTable = ({
1359
1527
  hideRowActionsWhenSelectionActive = false,
1360
1528
  // hide row action column while selected-row action bar is visible
1361
1529
  // -----------------------------------------------------------------------
1530
+ // Row expansion (detail rows)
1531
+ // -----------------------------------------------------------------------
1532
+ renderExpandedRow,
1533
+ // (row) => ReactNode — providing this enables the feature
1534
+ expandedRowIds: externalExpandedRowIds,
1535
+ // controlled — array of expanded row IDs
1536
+ defaultExpandedRowIds,
1537
+ // uncontrolled — initially expanded row IDs
1538
+ onExpandedRowsChange,
1539
+ // (expandedRowIds[]) => void
1540
+ expandOn = "icon",
1541
+ // "icon" (chevron toggle column) | "row" (click row content)
1542
+ expandSingle = false,
1543
+ // accordion mode — expanding a row collapses the others
1544
+ // -----------------------------------------------------------------------
1362
1545
  // Inline editing
1363
1546
  // -----------------------------------------------------------------------
1364
1547
  editMode,
@@ -1603,6 +1786,25 @@ var DataTable = ({
1603
1786
  });
1604
1787
  return rows;
1605
1788
  }, [groupedData, paginatedRows, expandedGroups]);
1789
+ const expandable = typeof renderExpandedRow === "function";
1790
+ const showExpandColumn = expandable && expandOn === "icon";
1791
+ const [internalExpandedRowIds, setInternalExpandedRowIds] = (0, import_react7.useState)(
1792
+ () => normalizeExpandedIds(defaultExpandedRowIds)
1793
+ );
1794
+ const expandedRowIds = (0, import_react7.useMemo)(
1795
+ () => externalExpandedRowIds != null ? normalizeExpandedIds(externalExpandedRowIds) : internalExpandedRowIds,
1796
+ [externalExpandedRowIds, internalExpandedRowIds]
1797
+ );
1798
+ const toggleRowExpanded = (0, import_react7.useCallback)((rowId) => {
1799
+ if (rowId == null) return;
1800
+ const next = toggleExpandedId(expandedRowIds, rowId, expandSingle);
1801
+ if (externalExpandedRowIds == null) setInternalExpandedRowIds(next);
1802
+ if (onExpandedRowsChange) onExpandedRowsChange([...next]);
1803
+ }, [expandedRowIds, expandSingle, externalExpandedRowIds, onExpandedRowsChange]);
1804
+ const renderedRows = (0, import_react7.useMemo)(() => {
1805
+ if (!expandable) return displayRows;
1806
+ return withDetailRows(displayRows, expandedRowIds, rowIdField);
1807
+ }, [expandable, displayRows, expandedRowIds, rowIdField]);
1606
1808
  const footerData = serverSide ? data : filteredData;
1607
1809
  const activeChips = (0, import_react7.useMemo)(
1608
1810
  () => buildActiveFilterChips(filters, filterValues),
@@ -1754,6 +1956,7 @@ var DataTable = ({
1754
1956
  const type = col.editType || "text";
1755
1957
  const rowId = row[rowIdField];
1756
1958
  const fieldName = `edit-${rowId}-${col.field}`;
1959
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
1757
1960
  const commit = (val) => commitEdit(row, col.field, val);
1758
1961
  const exitEdit = () => {
1759
1962
  if (editError) return;
@@ -1794,15 +1997,15 @@ var DataTable = ({
1794
1997
  case "multiselect":
1795
1998
  return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.MultiSelect, { ...extra, name: fieldName, label: "", value: editValue || [], onChange: commit, options: resolveEditOptions(col, data) });
1796
1999
  case "date":
1797
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
2000
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
1798
2001
  case "time":
1799
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
2002
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
1800
2003
  case "datetime":
1801
- 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) => {
2004
+ 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) => {
1802
2005
  const next = { ...editValue, date: val };
1803
2006
  handleInput(next);
1804
2007
  commitEdit(row, col.field, next, { keepEditing: true });
1805
- }, 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) => {
2008
+ }, 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) => {
1806
2009
  const next = { ...editValue, time: val };
1807
2010
  handleInput(next);
1808
2011
  commitEdit(row, col.field, next, { keepEditing: true });
@@ -1816,7 +2019,8 @@ var DataTable = ({
1816
2019
  }
1817
2020
  };
1818
2021
  const resolvedEditMode = editMode || (columns.some((col) => col.editable) ? "discrete" : null);
1819
- const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || !renderRow;
2022
+ const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || expandable || !renderRow;
2023
+ const totalColumnCount = columns.length + (selectable ? 1 : 0) + (showExpandColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
1820
2024
  const autoWidths = (0, import_react7.useMemo)(
1821
2025
  () => autoWidth ? computeAutoWidths(columns, data) : {},
1822
2026
  [columns, data, autoWidth]
@@ -1835,6 +2039,7 @@ var DataTable = ({
1835
2039
  const type = col.editType || "text";
1836
2040
  const rowId = row[rowIdField];
1837
2041
  const fieldName = `inline-${rowId}-${col.field}`;
2042
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
1838
2043
  const cellKey = `${rowId}-${col.field}`;
1839
2044
  const value = row[col.field];
1840
2045
  const validate = col.editValidate;
@@ -1883,13 +2088,13 @@ var DataTable = ({
1883
2088
  case "multiselect":
1884
2089
  return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.MultiSelect, { ...extra, name: fieldName, label: "", value: value || [], onChange: fire, options: resolveEditOptions(col, data) });
1885
2090
  case "date":
1886
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
2091
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.DateInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
1887
2092
  case "time":
1888
- return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
2093
+ return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
1889
2094
  case "datetime":
1890
- 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) => {
2095
+ 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) => {
1891
2096
  fire({ ...value, date: val });
1892
- } }), /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: value == null ? void 0 : value.time, onChange: (val) => {
2097
+ } }), /* @__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) => {
1893
2098
  fire({ ...value, time: val });
1894
2099
  } }));
1895
2100
  case "toggle":
@@ -2043,7 +2248,7 @@ var DataTable = ({
2043
2248
  checked: allVisibleSelected,
2044
2249
  onChange: handleSelectAll
2045
2250
  }
2046
- )), columns.map((col) => {
2251
+ )), showExpandColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" }), columns.map((col) => {
2047
2252
  const headerAlign = resolvedEditMode === "inline" && col.editable ? void 0 : col.align;
2048
2253
  return /* @__PURE__ */ import_react7.default.createElement(
2049
2254
  import_ui_extensions7.TableHeader,
@@ -2057,8 +2262,8 @@ var DataTable = ({
2057
2262
  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
2058
2263
  );
2059
2264
  }), showRowActionsColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" }))),
2060
- /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableBody, null, displayRows.map(
2061
- (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) => {
2265
+ /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableBody, null, renderedRows.map(
2266
+ (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) => {
2062
2267
  var _a, _b, _c;
2063
2268
  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(
2064
2269
  Icon,
@@ -2075,7 +2280,7 @@ var DataTable = ({
2075
2280
  },
2076
2281
  /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.Text, { format: { fontWeight: "demibold" } }, item.group.label)
2077
2282
  )) : ((_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]) ?? "");
2078
- }), 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(
2283
+ }), 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(
2079
2284
  import_ui_extensions7.Checkbox,
2080
2285
  {
2081
2286
  name: `select-${item.row[rowIdField]}`,
@@ -2083,13 +2288,22 @@ var DataTable = ({
2083
2288
  checked: selectedIds.has(item.row[rowIdField]),
2084
2289
  onChange: (checked) => handleSelectRow(item.row[rowIdField], checked)
2085
2290
  }
2291
+ )), showExpandColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableCell, { width: "min" }, /* @__PURE__ */ import_react7.default.createElement(
2292
+ Icon,
2293
+ {
2294
+ name: expandedRowIds.has(item.row[rowIdField]) ? "upCarat" : "downCarat",
2295
+ onClick: () => toggleRowExpanded(item.row[rowIdField]),
2296
+ screenReaderText: expandedRowIds.has(item.row[rowIdField]) ? "Collapse row" : "Expand row"
2297
+ }
2086
2298
  )), columns.map((col) => {
2087
2299
  const rowId = item.row[rowIdField];
2088
2300
  const isDiscreteEditing = resolvedEditMode === "discrete" && (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
2089
2301
  const isRowEditing = editingRowId != null && rowId === editingRowId && col.editable;
2090
2302
  const isShowingInput = isDiscreteEditing || isRowEditing || resolvedEditMode === "inline" && col.editable;
2091
2303
  const cellAlign = isShowingInput ? void 0 : col.align;
2092
- 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));
2304
+ const cellContent = renderCellContent(item.row, col);
2305
+ const wrapInRowToggle = expandable && expandOn === "row" && !col.editable && !isShowingInput;
2306
+ 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);
2093
2307
  }), 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" }, (() => {
2094
2308
  const resolvedRowActions = typeof rowActions === "function" ? rowActions(item.row) : rowActions;
2095
2309
  const actions = Array.isArray(resolvedRowActions) ? resolvedRowActions : [];
@@ -2106,17 +2320,121 @@ var DataTable = ({
2106
2320
  ));
2107
2321
  })()))) : renderRow(item.row)
2108
2322
  )),
2109
- (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) => {
2323
+ (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) => {
2110
2324
  const footerDef = col.footer;
2111
2325
  const content = typeof footerDef === "function" ? footerDef(footerData) : footerDef || "";
2112
2326
  return /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { key: col.field, align: col.align }, content);
2113
2327
  }), showRowActionsColumn && /* @__PURE__ */ import_react7.default.createElement(import_ui_extensions7.TableHeader, { width: "min" })))
2114
2328
  ));
2115
2329
  };
2330
+ DataTable.displayName = "DataTable";
2116
2331
 
2117
2332
  // src/kanban/Kanban.jsx
2118
2333
  var import_react10 = __toESM(require("react"));
2119
2334
 
2335
+ // src/kanban/kanbanLanes.js
2336
+ var UNASSIGNED_LANE_KEY = "__unassigned";
2337
+ var UNKNOWN_STAGE_KEY = "__unknown";
2338
+ var getLaneKey = (row, swimlaneBy) => {
2339
+ const raw = typeof swimlaneBy === "function" ? swimlaneBy(row) : row == null ? void 0 : row[swimlaneBy];
2340
+ if (raw == null || raw === "") return UNASSIGNED_LANE_KEY;
2341
+ return String(raw);
2342
+ };
2343
+ var orderLaneKeys = (seenKeys, swimlaneOrder) => {
2344
+ const seen = Array.isArray(seenKeys) ? seenKeys : [];
2345
+ if (!Array.isArray(swimlaneOrder) || swimlaneOrder.length === 0) return [...seen];
2346
+ const explicit = [];
2347
+ for (const key of swimlaneOrder) {
2348
+ const normalized = String(key);
2349
+ if (!explicit.includes(normalized)) explicit.push(normalized);
2350
+ }
2351
+ const rest = seen.filter((key) => !explicit.includes(key));
2352
+ return [...explicit, ...rest];
2353
+ };
2354
+ var partitionLanes = (rows, { swimlaneBy, swimlaneOrder } = {}) => {
2355
+ const rowsByLane = {};
2356
+ const firstSeen = [];
2357
+ for (const row of rows || []) {
2358
+ const key = getLaneKey(row, swimlaneBy);
2359
+ if (!rowsByLane[key]) {
2360
+ rowsByLane[key] = [];
2361
+ firstSeen.push(key);
2362
+ }
2363
+ rowsByLane[key].push(row);
2364
+ }
2365
+ const laneKeys = orderLaneKeys(firstSeen, swimlaneOrder);
2366
+ for (const key of laneKeys) {
2367
+ if (!rowsByLane[key]) rowsByLane[key] = [];
2368
+ }
2369
+ return { laneKeys, rowsByLane };
2370
+ };
2371
+ var resolveLaneLabel = (laneKey, swimlaneLabels, rows, unassignedLabel) => {
2372
+ if (typeof swimlaneLabels === "function") {
2373
+ const out = swimlaneLabels(laneKey, rows || []);
2374
+ if (out != null) return out;
2375
+ } else if (swimlaneLabels && typeof swimlaneLabels === "object") {
2376
+ const out = swimlaneLabels[laneKey];
2377
+ if (out != null) return out;
2378
+ }
2379
+ if (laneKey === UNASSIGNED_LANE_KEY) return unassignedLabel || "Unassigned";
2380
+ return String(laneKey);
2381
+ };
2382
+ var bucketRowsByStage = (rows, stages, getStage) => {
2383
+ const map = {};
2384
+ for (const stage of stages || []) map[stage.value] = [];
2385
+ for (const row of rows || []) {
2386
+ const key = getStage(row);
2387
+ if (map[key]) {
2388
+ map[key].push(row);
2389
+ } else if ((stages || []).length > 0) {
2390
+ if (!map[UNKNOWN_STAGE_KEY]) map[UNKNOWN_STAGE_KEY] = [];
2391
+ map[UNKNOWN_STAGE_KEY].push(row);
2392
+ }
2393
+ }
2394
+ return map;
2395
+ };
2396
+ var sortBuckets = (buckets, comparator) => {
2397
+ if (!comparator) return buckets;
2398
+ const out = {};
2399
+ for (const key of Object.keys(buckets || {})) {
2400
+ out[key] = [...buckets[key]].sort(comparator);
2401
+ }
2402
+ return out;
2403
+ };
2404
+ var resolveWipLimit = (stage, wipLimits) => {
2405
+ const override = wipLimits ? wipLimits[stage == null ? void 0 : stage.value] : void 0;
2406
+ const limit = override != null ? override : stage == null ? void 0 : stage.wipLimit;
2407
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit < 0) return null;
2408
+ return limit;
2409
+ };
2410
+ var computeStageCounts = (stages, buckets, stageMeta) => {
2411
+ const counts = {};
2412
+ for (const stage of stages || []) {
2413
+ const meta = stageMeta ? stageMeta[stage.value] : void 0;
2414
+ counts[stage.value] = meta && meta.totalCount != null ? meta.totalCount : ((buckets == null ? void 0 : buckets[stage.value]) || []).length;
2415
+ }
2416
+ return counts;
2417
+ };
2418
+ var evaluateWip = (stages, counts, wipLimits) => {
2419
+ const out = {};
2420
+ for (const stage of stages || []) {
2421
+ const limit = resolveWipLimit(stage, wipLimits);
2422
+ const count = (counts == null ? void 0 : counts[stage.value]) || 0;
2423
+ out[stage.value] = { count, limit, exceeded: limit != null && count > limit };
2424
+ }
2425
+ return out;
2426
+ };
2427
+ var findNewlyExceededWip = (prev, next) => {
2428
+ const events = [];
2429
+ for (const stageId of Object.keys(next || {})) {
2430
+ const entry = next[stageId];
2431
+ if (!entry || !entry.exceeded) continue;
2432
+ if (prev && prev[stageId] && prev[stageId].exceeded) continue;
2433
+ events.push({ stageId, count: entry.count, limit: entry.limit });
2434
+ }
2435
+ return events;
2436
+ };
2437
+
2120
2438
  // src/common-components/CollectionSortSelect.js
2121
2439
  var import_react8 = __toESM(require("react"));
2122
2440
  var import_ui_extensions8 = require("@hubspot/ui-extensions");
@@ -2363,6 +2681,12 @@ var DEFAULT_LABELS3 = {
2363
2681
  errorTitle: "Something went wrong.",
2364
2682
  errorMessage: "An error occurred while loading data.",
2365
2683
  cardCount: (n) => String(n),
2684
+ // "5 / 4" — count vs WIP limit in the stage header. The slash format is the
2685
+ // standard kanban convention; override for tighter ("5/4") or verbose forms.
2686
+ wipCount: (count, limit) => `${count} / ${limit}`,
2687
+ overWip: "Over WIP",
2688
+ laneCount: (n) => String(n),
2689
+ unassignedLane: "Unassigned",
2366
2690
  moveTo: "Move",
2367
2691
  clearAll: "Clear all",
2368
2692
  selectAll: (count, label) => `Select all ${count} ${label}`,
@@ -2589,10 +2913,14 @@ var KanbanColumn = ({
2589
2913
  onToggleCollapsed,
2590
2914
  columnFooter,
2591
2915
  countDisplay,
2916
+ wip,
2917
+ compactEmpty,
2592
2918
  labels,
2593
2919
  children
2594
2920
  }) => {
2595
- const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
2921
+ const hasWipLimit = wip != null && wip.limit != null;
2922
+ const countLabel = hasWipLimit ? labels.wipCount(wip.count, wip.limit) : labels.cardCount(totalCount != null ? totalCount : bucketCount);
2923
+ const overWipNode = wip && wip.exceeded ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.StatusTag, { variant: "warning" }, labels.overWip) : null;
2596
2924
  const countNode = countDisplay === "text" ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tag, { variant: "default" }, countLabel);
2597
2925
  if (collapsed) {
2598
2926
  const rotated = makeRotatedLabelDataUri(stage.label);
@@ -2625,8 +2953,11 @@ var KanbanColumn = ({
2625
2953
  }
2626
2954
  ) : null));
2627
2955
  }
2956
+ if (compactEmpty && !loading && rows.length === 0 && bucketCount === 0) {
2957
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tile, { compact: true }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { variant: "microcopy", format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn)));
2958
+ }
2628
2959
  const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
2629
- return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tile, { compact: true }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react10.default.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Divider, null), children, error ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
2960
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tile, { compact: true }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, overWipNode, loading ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react10.default.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Divider, null), children, error ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
2630
2961
  };
2631
2962
  var renderMetricsPanel = (metrics) => {
2632
2963
  if (!metrics) return null;
@@ -2665,14 +2996,14 @@ var KanbanToolbar = ({
2665
2996
  sortOptions,
2666
2997
  sortValue,
2667
2998
  onSortChange,
2668
- metrics,
2669
- showMetrics,
2999
+ showMetricsButton,
3000
+ metricsPanel,
2670
3001
  onToggleMetrics,
2671
3002
  labels,
2672
3003
  toolbarLeftFlex,
2673
3004
  toolbarRightFlex
2674
3005
  }) => {
2675
- const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react10.default.createElement(
3006
+ const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || showMetricsButton ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react10.default.createElement(
2676
3007
  CollectionSortSelect,
2677
3008
  {
2678
3009
  name: "kanban-sort",
@@ -2681,7 +3012,7 @@ var KanbanToolbar = ({
2681
3012
  options: sortOptions,
2682
3013
  onChange: onSortChange
2683
3014
  }
2684
- ) : null, metrics ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react10.default.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
3015
+ ) : null, showMetricsButton ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react10.default.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
2685
3016
  return /* @__PURE__ */ import_react10.default.createElement(
2686
3017
  CollectionToolbar,
2687
3018
  {
@@ -2708,7 +3039,7 @@ var KanbanToolbar = ({
2708
3039
  onRemove: onFilterRemove
2709
3040
  },
2710
3041
  right: rightControls,
2711
- footer: showMetrics && metrics ? renderMetricsPanel(metrics) : null,
3042
+ footer: metricsPanel,
2712
3043
  labels,
2713
3044
  leftFlex: toolbarLeftFlex,
2714
3045
  rightFlex: toolbarRightFlex
@@ -2760,6 +3091,18 @@ var Kanban = ({
2760
3091
  // --- Per-stage pagination ---
2761
3092
  stageMeta,
2762
3093
  onLoadMore,
3094
+ // --- WIP limits ---
3095
+ wipLimits,
3096
+ onWipExceeded,
3097
+ // --- Swimlanes ---
3098
+ swimlaneBy,
3099
+ swimlaneLabels,
3100
+ swimlaneOrder,
3101
+ collapseLanes = true,
3102
+ collapsedLanes,
3103
+ defaultCollapsedLanes,
3104
+ onCollapsedLanesChange,
3105
+ metricsPerLane = false,
2763
3106
  // --- Selection ---
2764
3107
  selectable = false,
2765
3108
  selectedIds,
@@ -2824,6 +3167,9 @@ var Kanban = ({
2824
3167
  const [internalFilters, setInternalFilters] = (0, import_react10.useState)(() => getEmptyFilterValues(filters));
2825
3168
  const [internalSort, setInternalSort] = (0, import_react10.useState)(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
2826
3169
  const [internalCollapsed, setInternalCollapsed] = (0, import_react10.useState)([]);
3170
+ const [internalCollapsedLanes, setInternalCollapsedLanes] = (0, import_react10.useState)(
3171
+ () => defaultCollapsedLanes || []
3172
+ );
2827
3173
  const [internalExpanded, setInternalExpanded] = (0, import_react10.useState)([]);
2828
3174
  const [internalSelection, setInternalSelection] = (0, import_react10.useState)([]);
2829
3175
  const [internalShowMetrics, setInternalShowMetrics] = (0, import_react10.useState)(false);
@@ -2840,6 +3186,7 @@ var Kanban = ({
2840
3186
  const resolvedFilters = filterValues != null ? filterValues : internalFilters;
2841
3187
  const resolvedSort = sort != null ? sort : internalSort;
2842
3188
  const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
3189
+ const resolvedCollapsedLanes = collapsedLanes != null ? collapsedLanes : internalCollapsedLanes;
2843
3190
  const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
2844
3191
  const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
2845
3192
  const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
@@ -2856,9 +3203,10 @@ var Kanban = ({
2856
3203
  search: overrides.search != null ? overrides.search : resolvedSearch,
2857
3204
  filters: overrides.filters != null ? overrides.filters : resolvedFilters,
2858
3205
  sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
2859
- collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
3206
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed,
3207
+ collapsedLanes: overrides.collapsedLanes != null ? overrides.collapsedLanes : resolvedCollapsedLanes
2860
3208
  });
2861
- }, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
3209
+ }, [onParamsChange, resolvedCollapsed, resolvedCollapsedLanes, resolvedFilters, resolvedSearch, resolvedSort]);
2862
3210
  const lastAppliedSearchRef = (0, import_react10.useRef)(searchValue != null ? searchValue : "");
2863
3211
  (0, import_react10.useEffect)(() => {
2864
3212
  if (searchValue == null) return;
@@ -2917,6 +3265,15 @@ var Kanban = ({
2917
3265
  },
2918
3266
  [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
2919
3267
  );
3268
+ const handleLaneCollapsed = (0, import_react10.useCallback)(
3269
+ (laneKey) => {
3270
+ const next = resolvedCollapsedLanes.includes(laneKey) ? resolvedCollapsedLanes.filter((k) => k !== laneKey) : [...resolvedCollapsedLanes, laneKey];
3271
+ if (onCollapsedLanesChange) onCollapsedLanesChange(next);
3272
+ if (collapsedLanes == null) setInternalCollapsedLanes(next);
3273
+ fireParamsChange({ collapsedLanes: next });
3274
+ },
3275
+ [fireParamsChange, resolvedCollapsedLanes, collapsedLanes, onCollapsedLanesChange]
3276
+ );
2920
3277
  const handleExpanded = (0, import_react10.useCallback)(
2921
3278
  (stageValue) => {
2922
3279
  const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
@@ -2985,33 +3342,45 @@ var Kanban = ({
2985
3342
  }
2986
3343
  return result;
2987
3344
  }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
2988
- const buckets = (0, import_react10.useMemo)(() => {
2989
- const map = {};
2990
- for (const stage of stages) map[stage.value] = [];
2991
- for (const row of filteredData) {
2992
- const key = getStageFor(row);
2993
- if (map[key]) {
2994
- map[key].push(row);
2995
- } else if (stages.length > 0) {
2996
- if (!map.__unknown) map.__unknown = [];
2997
- map.__unknown.push(row);
2998
- }
2999
- }
3000
- return map;
3001
- }, [filteredData, stages, getStageFor]);
3345
+ const buckets = (0, import_react10.useMemo)(
3346
+ () => bucketRowsByStage(filteredData, stages, getStageFor),
3347
+ [filteredData, stages, getStageFor]
3348
+ );
3002
3349
  const sortComparator = (0, import_react10.useMemo)(() => {
3003
3350
  if (!sortOptions || !resolvedSort) return null;
3004
3351
  const opt = sortOptions.find((s) => s.value === resolvedSort);
3005
3352
  return (opt == null ? void 0 : opt.comparator) || null;
3006
3353
  }, [sortOptions, resolvedSort]);
3007
- const sortedBuckets = (0, import_react10.useMemo)(() => {
3008
- if (!sortComparator) return buckets;
3354
+ const sortedBuckets = (0, import_react10.useMemo)(() => sortBuckets(buckets, sortComparator), [buckets, sortComparator]);
3355
+ const hasLanes = swimlaneBy != null;
3356
+ const laneData = (0, import_react10.useMemo)(() => {
3357
+ if (!hasLanes) return null;
3358
+ return partitionLanes(filteredData, { swimlaneBy, swimlaneOrder });
3359
+ }, [hasLanes, filteredData, swimlaneBy, swimlaneOrder]);
3360
+ const laneBuckets = (0, import_react10.useMemo)(() => {
3361
+ if (!laneData) return null;
3009
3362
  const out = {};
3010
- for (const key of Object.keys(buckets)) {
3011
- out[key] = [...buckets[key]].sort(sortComparator);
3363
+ for (const laneKey of laneData.laneKeys) {
3364
+ out[laneKey] = sortBuckets(
3365
+ bucketRowsByStage(laneData.rowsByLane[laneKey] || [], stages, getStageFor),
3366
+ sortComparator
3367
+ );
3012
3368
  }
3013
3369
  return out;
3014
- }, [buckets, sortComparator]);
3370
+ }, [laneData, stages, getStageFor, sortComparator]);
3371
+ const wipByStage = (0, import_react10.useMemo)(
3372
+ () => evaluateWip(stages, computeStageCounts(stages, buckets, stageMeta), wipLimits),
3373
+ [stages, buckets, stageMeta, wipLimits]
3374
+ );
3375
+ const prevWipRef = (0, import_react10.useRef)({});
3376
+ (0, import_react10.useEffect)(() => {
3377
+ const newlyExceeded = findNewlyExceededWip(prevWipRef.current, wipByStage);
3378
+ prevWipRef.current = wipByStage;
3379
+ if (!onWipExceeded) return;
3380
+ for (const event of newlyExceeded) {
3381
+ onWipExceeded(event.stageId, event.count, event.limit);
3382
+ }
3383
+ }, [wipByStage, onWipExceeded]);
3015
3384
  const activeChips = (0, import_react10.useMemo)(
3016
3385
  () => buildActiveFilterChips(filters, resolvedFilters),
3017
3386
  [filters, resolvedFilters]
@@ -3138,6 +3507,52 @@ var Kanban = ({
3138
3507
  selectionActions: selectionActions || [],
3139
3508
  labels
3140
3509
  };
3510
+ const metricsProvided = metrics != null && (!Array.isArray(metrics) || metrics.length > 0);
3511
+ const perLaneMetricsActive = hasLanes && metricsPerLane && typeof metrics === "function";
3512
+ const globalMetricsContent = metricsProvided && !perLaneMetricsActive ? typeof metrics === "function" ? metrics(filteredData, null) : metrics : null;
3513
+ const renderStageColumns = (bucketMap, laneKey) => {
3514
+ const inLane = laneKey != null;
3515
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
3516
+ const stageRows = bucketMap[stage.value] || [];
3517
+ const meta = inLane ? void 0 : stageMeta == null ? void 0 : stageMeta[stage.value];
3518
+ const isExpanded = resolvedExpanded.includes(stage.value);
3519
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
3520
+ const visibleRows = stageRows.slice(0, clamp);
3521
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
3522
+ const stageWip = wipByStage[stage.value];
3523
+ const wip = inLane ? (stageWip == null ? void 0 : stageWip.exceeded) ? { count: stageWip.count, limit: null, exceeded: true } : null : stageWip;
3524
+ return /* @__PURE__ */ import_react10.default.createElement(
3525
+ import_ui_extensions10.AutoGrid,
3526
+ {
3527
+ key: inLane ? `${laneKey}::${stage.value}` : stage.value,
3528
+ columnWidth: isCollapsed ? 72 : effectiveColumnWidth
3529
+ },
3530
+ /* @__PURE__ */ import_react10.default.createElement(
3531
+ KanbanColumn,
3532
+ {
3533
+ stage,
3534
+ rows: visibleRows,
3535
+ bucketCount: stageRows.length,
3536
+ totalCount: meta == null ? void 0 : meta.totalCount,
3537
+ hasMore: meta == null ? void 0 : meta.hasMore,
3538
+ loading: meta == null ? void 0 : meta.loading,
3539
+ error: meta == null ? void 0 : meta.error,
3540
+ onLoadMore: inLane ? void 0 : onLoadMore,
3541
+ expanded: isExpanded,
3542
+ onToggleExpanded: () => handleExpanded(stage.value),
3543
+ collapsed: isCollapsed,
3544
+ onToggleCollapsed: () => handleCollapsed(stage.value),
3545
+ columnFooter,
3546
+ countDisplay,
3547
+ wip,
3548
+ compactEmpty: inLane,
3549
+ labels
3550
+ },
3551
+ visibleRows.map((row) => renderCardNode(row, stage))
3552
+ )
3553
+ );
3554
+ }));
3555
+ };
3141
3556
  const mainContent = error ? renderErrorState ? renderErrorState({
3142
3557
  error,
3143
3558
  title: labels.errorTitle,
@@ -3149,35 +3564,32 @@ var Kanban = ({
3149
3564
  ) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
3150
3565
  title: labels.emptyTitle,
3151
3566
  message: labels.emptyMessage
3152
- }) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tile, null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, null, labels.emptyMessage)))) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
3153
- const stageRows = sortedBuckets[stage.value] || [];
3154
- const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
3155
- const isExpanded = resolvedExpanded.includes(stage.value);
3156
- const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
3157
- const visibleRows = stageRows.slice(0, clamp);
3158
- const isCollapsed = resolvedCollapsed.includes(stage.value);
3159
- return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.AutoGrid, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ import_react10.default.createElement(
3160
- KanbanColumn,
3567
+ }) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tile, null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, null, labels.emptyMessage)))) : hasLanes ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", gap: "md" }, ((laneData == null ? void 0 : laneData.laneKeys) || []).map((laneKey, laneIndex) => {
3568
+ const laneRows = laneData.rowsByLane[laneKey] || [];
3569
+ const laneLabel = resolveLaneLabel(laneKey, swimlaneLabels, laneRows, labels.unassignedLane);
3570
+ const laneLabelText = typeof laneLabel === "string" ? laneLabel : String(laneKey);
3571
+ const isLaneCollapsed = collapseLanes && resolvedCollapsedLanes.includes(laneKey);
3572
+ const laneCountLabel = labels.laneCount(laneRows.length);
3573
+ const laneCountNode = countDisplay === "none" ? null : countDisplay === "text" ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { format: { fontWeight: "demibold" } }, laneCountLabel) : /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Tag, { variant: "default" }, laneCountLabel);
3574
+ const laneMetricsNode = !isLaneCollapsed && perLaneMetricsActive && resolvedShowMetrics ? renderMetricsPanel(metrics(laneRows, laneKey)) : null;
3575
+ return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { key: laneKey, direction: "column", gap: "xs" }, laneIndex > 0 ? /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Divider, null) : null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "row", align: "center", gap: "xs" }, collapseLanes ? /* @__PURE__ */ import_react10.default.createElement(
3576
+ import_ui_extensions10.Button,
3161
3577
  {
3162
- stage,
3163
- rows: visibleRows,
3164
- bucketCount: stageRows.length,
3165
- totalCount: meta == null ? void 0 : meta.totalCount,
3166
- hasMore: meta == null ? void 0 : meta.hasMore,
3167
- loading: meta == null ? void 0 : meta.loading,
3168
- error: meta == null ? void 0 : meta.error,
3169
- onLoadMore,
3170
- expanded: isExpanded,
3171
- onToggleExpanded: () => handleExpanded(stage.value),
3172
- collapsed: isCollapsed,
3173
- onToggleCollapsed: () => handleCollapsed(stage.value),
3174
- columnFooter,
3175
- countDisplay,
3176
- labels
3578
+ variant: "transparent",
3579
+ size: "sm",
3580
+ onClick: () => handleLaneCollapsed(laneKey),
3581
+ tooltip: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
3177
3582
  },
3178
- visibleRows.map((row) => renderCardNode(row, stage))
3179
- ));
3180
- }));
3583
+ /* @__PURE__ */ import_react10.default.createElement(
3584
+ Icon,
3585
+ {
3586
+ name: isLaneCollapsed ? "right" : "down",
3587
+ size: "sm",
3588
+ screenReaderText: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
3589
+ }
3590
+ )
3591
+ ) : null, /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Text, { format: { fontWeight: "demibold" } }, laneLabel), laneCountNode), laneMetricsNode, !isLaneCollapsed ? renderStageColumns((laneBuckets == null ? void 0 : laneBuckets[laneKey]) || {}, laneKey) : null);
3592
+ })) : renderStageColumns(sortedBuckets, null);
3181
3593
  const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
3182
3594
  return /* @__PURE__ */ import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react10.default.createElement(
3183
3595
  KanbanToolbar,
@@ -3197,8 +3609,8 @@ var Kanban = ({
3197
3609
  sortOptions,
3198
3610
  sortValue: resolvedSort,
3199
3611
  onSortChange: handleSort,
3200
- metrics,
3201
- showMetrics: resolvedShowMetrics,
3612
+ showMetricsButton: metricsProvided,
3613
+ metricsPanel: resolvedShowMetrics && globalMetricsContent ? renderMetricsPanel(globalMetricsContent) : null,
3202
3614
  onToggleMetrics: toggleMetrics,
3203
3615
  labels,
3204
3616
  toolbarLeftFlex,
@@ -3206,6 +3618,7 @@ var Kanban = ({
3206
3618
  }
3207
3619
  ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ import_react10.default.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
3208
3620
  };
3621
+ Kanban.displayName = "Kanban";
3209
3622
 
3210
3623
  // src/utils/objectPath.js
3211
3624
  var getByPath = (obj, path) => {
@@ -3772,6 +4185,8 @@ var CrmKanban = ({
3772
4185
  board
3773
4186
  );
3774
4187
  };
4188
+ CrmDataTable.displayName = "CrmDataTable";
4189
+ CrmKanban.displayName = "CrmKanban";
3775
4190
 
3776
4191
  // src/utils/formatters.js
3777
4192
  var DEFAULT_LOCALE = "en-US";
@@ -4083,6 +4498,7 @@ var deriveCardFieldsFromColumns = (columns, opts = {}) => {
4083
4498
  0 && (module.exports = {
4084
4499
  CrmDataTable,
4085
4500
  CrmKanban,
4501
+ applyPatches,
4086
4502
  buildActiveFilterChips,
4087
4503
  buildCrmSearchConfig,
4088
4504
  buildOptions,