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/utils.mjs CHANGED
@@ -1,3 +1,127 @@
1
+ // src/utils/applyPatches.js
2
+ function applyPatches(doc, patches) {
3
+ if (!patches || patches.length === 0) return doc;
4
+ let out = doc ?? {};
5
+ for (const op of patches) {
6
+ out = applyOne(out, op);
7
+ }
8
+ return out;
9
+ }
10
+ function applyOne(doc, op) {
11
+ const path = parsePointer(op.path);
12
+ switch (op.op) {
13
+ case "add":
14
+ return setAt(doc, path, op.value, true);
15
+ case "replace":
16
+ return setAt(doc, path, op.value, false);
17
+ case "remove":
18
+ return removeAt(doc, path);
19
+ case "move": {
20
+ const from = parsePointer(op.from);
21
+ const value = getAt(doc, from);
22
+ const removed = removeAt(doc, from);
23
+ return setAt(removed, path, value, true);
24
+ }
25
+ case "copy": {
26
+ const from = parsePointer(op.from);
27
+ const value = getAt(doc, from);
28
+ return setAt(doc, path, deepClone(value), true);
29
+ }
30
+ default:
31
+ console.warn("[hs-uix] applyPatches: ignoring unsupported op:", op.op);
32
+ return doc;
33
+ }
34
+ }
35
+ function parsePointer(pointer) {
36
+ if (!pointer || pointer === "/") return [];
37
+ if (pointer[0] !== "/") {
38
+ throw new Error(`invalid JSON Pointer (must start with /): ${pointer}`);
39
+ }
40
+ return pointer.slice(1).split("/").map((seg) => seg.replace(/~1/g, "/").replace(/~0/g, "~"));
41
+ }
42
+ function getAt(obj, segs) {
43
+ let cur = obj;
44
+ for (const seg of segs) {
45
+ if (cur == null) return void 0;
46
+ if (Array.isArray(cur)) {
47
+ cur = cur[Number(seg)];
48
+ } else if (typeof cur === "object") {
49
+ cur = Object.prototype.hasOwnProperty.call(cur, seg) ? cur[seg] : void 0;
50
+ } else {
51
+ return void 0;
52
+ }
53
+ }
54
+ return cur;
55
+ }
56
+ function setAt(obj, segs, value, insert) {
57
+ if (segs.length === 0) return value;
58
+ const [head, ...rest] = segs;
59
+ if (Array.isArray(obj)) {
60
+ const next = obj.slice();
61
+ const idx = head === "-" ? next.length : Number(head);
62
+ if (rest.length === 0) {
63
+ if (head === "-") {
64
+ next.push(value);
65
+ } else if (insert && Number.isInteger(idx) && idx >= 0 && idx <= next.length) {
66
+ next.splice(idx, 0, value);
67
+ } else {
68
+ next[idx] = value;
69
+ }
70
+ return next;
71
+ }
72
+ const existing2 = idx >= 0 && idx < next.length ? next[idx] : void 0;
73
+ next[idx] = setAt(
74
+ existing2 ?? (looksLikeArrayIndex(rest[0]) ? [] : {}),
75
+ rest,
76
+ value,
77
+ insert
78
+ );
79
+ return next;
80
+ }
81
+ const base = obj && typeof obj === "object" ? obj : {};
82
+ if (rest.length === 0) {
83
+ return { ...base, [head]: value };
84
+ }
85
+ const existing = base[head];
86
+ const child = setAt(
87
+ existing ?? (looksLikeArrayIndex(rest[0]) ? [] : {}),
88
+ rest,
89
+ value,
90
+ insert
91
+ );
92
+ return { ...base, [head]: child };
93
+ }
94
+ function removeAt(obj, segs) {
95
+ if (segs.length === 0) return void 0;
96
+ const [head, ...rest] = segs;
97
+ if (Array.isArray(obj)) {
98
+ const idx = Number(head);
99
+ if (!Number.isInteger(idx) || idx < 0 || idx >= obj.length) return obj;
100
+ const next = obj.slice();
101
+ if (rest.length === 0) {
102
+ next.splice(idx, 1);
103
+ } else {
104
+ next[idx] = removeAt(next[idx], rest);
105
+ }
106
+ return next;
107
+ }
108
+ if (!obj || typeof obj !== "object") return obj;
109
+ if (rest.length === 0) {
110
+ const next = { ...obj };
111
+ delete next[head];
112
+ return next;
113
+ }
114
+ if (!(head in obj)) return obj;
115
+ return { ...obj, [head]: removeAt(obj[head], rest) };
116
+ }
117
+ function looksLikeArrayIndex(seg) {
118
+ return seg === "-" || /^\d+$/.test(seg);
119
+ }
120
+ function deepClone(v) {
121
+ const json = v === void 0 ? void 0 : JSON.stringify(v);
122
+ return json === void 0 ? void 0 : JSON.parse(json);
123
+ }
124
+
1
125
  // src/utils/collections.js
2
126
  var sumBy = (items, keyOrFn) => (items || []).reduce((total, item) => {
3
127
  const value = typeof keyOrFn === "function" ? keyOrFn(item) : item == null ? void 0 : item[keyOrFn];
@@ -536,7 +660,7 @@ var GENERATED_ICONS = {
536
660
  "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"] }
537
661
  };
538
662
 
539
- // src/common-components/Icon.js
663
+ // src/common-components/nativeIconNames.js
540
664
  var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
541
665
  "add",
542
666
  "appointment",
@@ -548,12 +672,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
548
672
  "block",
549
673
  "book",
550
674
  "bulb",
675
+ "callTranscript",
551
676
  "calling",
552
677
  "callingHangup",
553
678
  "callingMade",
554
679
  "callingMissed",
555
680
  "callingVoicemail",
556
- "callTranscript",
557
681
  "campaigns",
558
682
  "cap",
559
683
  "checkCircle",
@@ -582,13 +706,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
582
706
  "enroll",
583
707
  "exclamation",
584
708
  "exclamationCircle",
585
- "facebook",
586
709
  "faceHappy",
587
710
  "faceHappyFilled",
588
711
  "faceNeutral",
589
712
  "faceNeutralFilled",
590
713
  "faceSad",
591
714
  "faceSadFilled",
715
+ "facebook",
592
716
  "favoriteHollow",
593
717
  "file",
594
718
  "filledXCircleIcon",
@@ -729,6 +853,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
729
853
  "zoomIn",
730
854
  "zoomOut"
731
855
  ]);
856
+
857
+ // src/common-components/Icon.js
732
858
  var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
733
859
  var NATIVE_SIZE_TOKENS = {
734
860
  sm: "sm",
@@ -941,6 +1067,7 @@ var CollectionFilterControl = ({
941
1067
  { key: name, direction: "row", align: "center", gap: "xs" },
942
1068
  h3(DateInput, {
943
1069
  name: `${controlName}-from`,
1070
+ label: filter.fromLabel ?? labels.dateFrom,
944
1071
  placeholder: filter.fromLabel ?? labels.dateFrom,
945
1072
  format: "medium",
946
1073
  value: rangeValue.from ?? null,
@@ -949,6 +1076,7 @@ var CollectionFilterControl = ({
949
1076
  h3(Icon, { name: "right", size: "sm" }),
950
1077
  h3(DateInput, {
951
1078
  name: `${controlName}-to`,
1079
+ label: filter.toLabel ?? labels.dateTo,
952
1080
  placeholder: filter.toLabel ?? labels.dateTo,
953
1081
  format: "medium",
954
1082
  value: rangeValue.to ?? null,
@@ -1087,6 +1215,45 @@ var editValidationError = (result) => {
1087
1215
  return typeof result === "string" ? result : "Invalid value";
1088
1216
  };
1089
1217
 
1218
+ // src/datatable/rowExpansion.js
1219
+ var extractRowId = (row, rowIdField = "id", fallback = void 0) => {
1220
+ const id = row == null ? void 0 : row[rowIdField];
1221
+ return id != null ? id : fallback;
1222
+ };
1223
+ var normalizeExpandedIds = (ids) => {
1224
+ if (ids == null) return /* @__PURE__ */ new Set();
1225
+ const list = ids instanceof Set ? [...ids] : Array.isArray(ids) ? ids : [ids];
1226
+ return new Set(list.filter((id) => id != null));
1227
+ };
1228
+ var expandRowId = (expandedIds, rowId, expandSingle = false) => {
1229
+ if (rowId == null) return expandedIds;
1230
+ if (expandSingle) return /* @__PURE__ */ new Set([rowId]);
1231
+ const next = new Set(expandedIds);
1232
+ next.add(rowId);
1233
+ return next;
1234
+ };
1235
+ var collapseRowId = (expandedIds, rowId) => {
1236
+ if (rowId == null || !expandedIds.has(rowId)) return expandedIds;
1237
+ const next = new Set(expandedIds);
1238
+ next.delete(rowId);
1239
+ return next;
1240
+ };
1241
+ var toggleExpandedId = (expandedIds, rowId, expandSingle = false) => {
1242
+ if (rowId == null) return expandedIds;
1243
+ return expandedIds.has(rowId) ? collapseRowId(expandedIds, rowId) : expandRowId(expandedIds, rowId, expandSingle);
1244
+ };
1245
+ var withDetailRows = (items, expandedIds, rowIdField = "id") => {
1246
+ if (!expandedIds || expandedIds.size === 0) return items;
1247
+ const out = [];
1248
+ items.forEach((item) => {
1249
+ out.push(item);
1250
+ if (item.type === "data" && expandedIds.has(extractRowId(item.row, rowIdField))) {
1251
+ out.push({ type: "detail", row: item.row });
1252
+ }
1253
+ });
1254
+ return out;
1255
+ };
1256
+
1090
1257
  // src/datatable/DataTable.jsx
1091
1258
  import {
1092
1259
  Box as Box2,
@@ -1326,6 +1493,21 @@ var DataTable = ({
1326
1493
  hideRowActionsWhenSelectionActive = false,
1327
1494
  // hide row action column while selected-row action bar is visible
1328
1495
  // -----------------------------------------------------------------------
1496
+ // Row expansion (detail rows)
1497
+ // -----------------------------------------------------------------------
1498
+ renderExpandedRow,
1499
+ // (row) => ReactNode — providing this enables the feature
1500
+ expandedRowIds: externalExpandedRowIds,
1501
+ // controlled — array of expanded row IDs
1502
+ defaultExpandedRowIds,
1503
+ // uncontrolled — initially expanded row IDs
1504
+ onExpandedRowsChange,
1505
+ // (expandedRowIds[]) => void
1506
+ expandOn = "icon",
1507
+ // "icon" (chevron toggle column) | "row" (click row content)
1508
+ expandSingle = false,
1509
+ // accordion mode — expanding a row collapses the others
1510
+ // -----------------------------------------------------------------------
1329
1511
  // Inline editing
1330
1512
  // -----------------------------------------------------------------------
1331
1513
  editMode,
@@ -1570,6 +1752,25 @@ var DataTable = ({
1570
1752
  });
1571
1753
  return rows;
1572
1754
  }, [groupedData, paginatedRows, expandedGroups]);
1755
+ const expandable = typeof renderExpandedRow === "function";
1756
+ const showExpandColumn = expandable && expandOn === "icon";
1757
+ const [internalExpandedRowIds, setInternalExpandedRowIds] = useState2(
1758
+ () => normalizeExpandedIds(defaultExpandedRowIds)
1759
+ );
1760
+ const expandedRowIds = useMemo(
1761
+ () => externalExpandedRowIds != null ? normalizeExpandedIds(externalExpandedRowIds) : internalExpandedRowIds,
1762
+ [externalExpandedRowIds, internalExpandedRowIds]
1763
+ );
1764
+ const toggleRowExpanded = useCallback2((rowId) => {
1765
+ if (rowId == null) return;
1766
+ const next = toggleExpandedId(expandedRowIds, rowId, expandSingle);
1767
+ if (externalExpandedRowIds == null) setInternalExpandedRowIds(next);
1768
+ if (onExpandedRowsChange) onExpandedRowsChange([...next]);
1769
+ }, [expandedRowIds, expandSingle, externalExpandedRowIds, onExpandedRowsChange]);
1770
+ const renderedRows = useMemo(() => {
1771
+ if (!expandable) return displayRows;
1772
+ return withDetailRows(displayRows, expandedRowIds, rowIdField);
1773
+ }, [expandable, displayRows, expandedRowIds, rowIdField]);
1573
1774
  const footerData = serverSide ? data : filteredData;
1574
1775
  const activeChips = useMemo(
1575
1776
  () => buildActiveFilterChips(filters, filterValues),
@@ -1721,6 +1922,7 @@ var DataTable = ({
1721
1922
  const type = col.editType || "text";
1722
1923
  const rowId = row[rowIdField];
1723
1924
  const fieldName = `edit-${rowId}-${col.field}`;
1925
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
1724
1926
  const commit = (val) => commitEdit(row, col.field, val);
1725
1927
  const exitEdit = () => {
1726
1928
  if (editError) return;
@@ -1761,15 +1963,15 @@ var DataTable = ({
1761
1963
  case "multiselect":
1762
1964
  return /* @__PURE__ */ React6.createElement(MultiSelect2, { ...extra, name: fieldName, label: "", value: editValue || [], onChange: commit, options: resolveEditOptions(col, data) });
1763
1965
  case "date":
1764
- return /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
1966
+ return /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
1765
1967
  case "time":
1766
- return /* @__PURE__ */ React6.createElement(TimeInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
1968
+ return /* @__PURE__ */ React6.createElement(TimeInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
1767
1969
  case "datetime":
1768
- return /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
1970
+ return /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
1769
1971
  const next = { ...editValue, date: val };
1770
1972
  handleInput(next);
1771
1973
  commitEdit(row, col.field, next, { keepEditing: true });
1772
- }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ React6.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
1974
+ }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ React6.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
1773
1975
  const next = { ...editValue, time: val };
1774
1976
  handleInput(next);
1775
1977
  commitEdit(row, col.field, next, { keepEditing: true });
@@ -1783,7 +1985,8 @@ var DataTable = ({
1783
1985
  }
1784
1986
  };
1785
1987
  const resolvedEditMode = editMode || (columns.some((col) => col.editable) ? "discrete" : null);
1786
- const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || !renderRow;
1988
+ const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || expandable || !renderRow;
1989
+ const totalColumnCount = columns.length + (selectable ? 1 : 0) + (showExpandColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
1787
1990
  const autoWidths = useMemo(
1788
1991
  () => autoWidth ? computeAutoWidths(columns, data) : {},
1789
1992
  [columns, data, autoWidth]
@@ -1802,6 +2005,7 @@ var DataTable = ({
1802
2005
  const type = col.editType || "text";
1803
2006
  const rowId = row[rowIdField];
1804
2007
  const fieldName = `inline-${rowId}-${col.field}`;
2008
+ const inputLabel = typeof col.label === "string" ? col.label : col.field;
1805
2009
  const cellKey = `${rowId}-${col.field}`;
1806
2010
  const value = row[col.field];
1807
2011
  const validate = col.editValidate;
@@ -1850,13 +2054,13 @@ var DataTable = ({
1850
2054
  case "multiselect":
1851
2055
  return /* @__PURE__ */ React6.createElement(MultiSelect2, { ...extra, name: fieldName, label: "", value: value || [], onChange: fire, options: resolveEditOptions(col, data) });
1852
2056
  case "date":
1853
- return /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: fieldName, label: "", value, onChange: fire });
2057
+ return /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
1854
2058
  case "time":
1855
- return /* @__PURE__ */ React6.createElement(TimeInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
2059
+ return /* @__PURE__ */ React6.createElement(TimeInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
1856
2060
  case "datetime":
1857
- return /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: "", value: value == null ? void 0 : value.date, onChange: (val) => {
2061
+ return /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: value == null ? void 0 : value.date, onChange: (val) => {
1858
2062
  fire({ ...value, date: val });
1859
- } }), /* @__PURE__ */ React6.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: value == null ? void 0 : value.time, onChange: (val) => {
2063
+ } }), /* @__PURE__ */ React6.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: value == null ? void 0 : value.time, onChange: (val) => {
1860
2064
  fire({ ...value, time: val });
1861
2065
  } }));
1862
2066
  case "toggle":
@@ -2010,7 +2214,7 @@ var DataTable = ({
2010
2214
  checked: allVisibleSelected,
2011
2215
  onChange: handleSelectAll
2012
2216
  }
2013
- )), columns.map((col) => {
2217
+ )), showExpandColumn && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }), columns.map((col) => {
2014
2218
  const headerAlign = resolvedEditMode === "inline" && col.editable ? void 0 : col.align;
2015
2219
  return /* @__PURE__ */ React6.createElement(
2016
2220
  TableHeader,
@@ -2024,8 +2228,8 @@ var DataTable = ({
2024
2228
  col.description ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, col.label, "\xA0", /* @__PURE__ */ React6.createElement(Link2, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React6.createElement(Tooltip, null, col.description) }, /* @__PURE__ */ React6.createElement(Icon, { name: "info", screenReaderText: typeof col.description === "string" ? col.description : void 0 }))) : col.label
2025
2229
  );
2026
2230
  }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }))),
2027
- /* @__PURE__ */ React6.createElement(TableBody, null, displayRows.map(
2028
- (item, idx) => item.type === "group-header" ? /* @__PURE__ */ React6.createElement(TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }), columns.map((col, colIdx) => {
2231
+ /* @__PURE__ */ React6.createElement(TableBody, null, renderedRows.map(
2232
+ (item, idx) => item.type === "group-header" ? /* @__PURE__ */ React6.createElement(TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }), showExpandColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }), columns.map((col, colIdx) => {
2029
2233
  var _a, _b, _c;
2030
2234
  return /* @__PURE__ */ React6.createElement(TableCell, { key: col.field, width: getCellWidth(col), align: colIdx === 0 ? void 0 : col.align }, colIdx === 0 ? /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(
2031
2235
  Icon,
@@ -2042,7 +2246,7 @@ var DataTable = ({
2042
2246
  },
2043
2247
  /* @__PURE__ */ React6.createElement(Text2, { format: { fontWeight: "demibold" } }, item.group.label)
2044
2248
  )) : ((_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]) ?? "");
2045
- }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" })) : useColumnRendering ? /* @__PURE__ */ React6.createElement(TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React6.createElement(
2249
+ }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" })) : item.type === "detail" ? /* @__PURE__ */ React6.createElement(TableRow, { key: `detail-${item.row[rowIdField] ?? idx}` }, /* @__PURE__ */ React6.createElement(TableCell, { colSpan: totalColumnCount }, renderExpandedRow(item.row))) : useColumnRendering ? /* @__PURE__ */ React6.createElement(TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React6.createElement(
2046
2250
  Checkbox,
2047
2251
  {
2048
2252
  name: `select-${item.row[rowIdField]}`,
@@ -2050,13 +2254,22 @@ var DataTable = ({
2050
2254
  checked: selectedIds.has(item.row[rowIdField]),
2051
2255
  onChange: (checked) => handleSelectRow(item.row[rowIdField], checked)
2052
2256
  }
2257
+ )), showExpandColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React6.createElement(
2258
+ Icon,
2259
+ {
2260
+ name: expandedRowIds.has(item.row[rowIdField]) ? "upCarat" : "downCarat",
2261
+ onClick: () => toggleRowExpanded(item.row[rowIdField]),
2262
+ screenReaderText: expandedRowIds.has(item.row[rowIdField]) ? "Collapse row" : "Expand row"
2263
+ }
2053
2264
  )), columns.map((col) => {
2054
2265
  const rowId = item.row[rowIdField];
2055
2266
  const isDiscreteEditing = resolvedEditMode === "discrete" && (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
2056
2267
  const isRowEditing = editingRowId != null && rowId === editingRowId && col.editable;
2057
2268
  const isShowingInput = isDiscreteEditing || isRowEditing || resolvedEditMode === "inline" && col.editable;
2058
2269
  const cellAlign = isShowingInput ? void 0 : col.align;
2059
- return /* @__PURE__ */ React6.createElement(TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, renderCellContent(item.row, col));
2270
+ const cellContent = renderCellContent(item.row, col);
2271
+ const wrapInRowToggle = expandable && expandOn === "row" && !col.editable && !isShowingInput;
2272
+ return /* @__PURE__ */ React6.createElement(TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, wrapInRowToggle ? /* @__PURE__ */ React6.createElement(Link2, { variant: "dark", onClick: () => toggleRowExpanded(rowId) }, cellContent) : cellContent);
2060
2273
  }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React6.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, (() => {
2061
2274
  const resolvedRowActions = typeof rowActions === "function" ? rowActions(item.row) : rowActions;
2062
2275
  const actions = Array.isArray(resolvedRowActions) ? resolvedRowActions : [];
@@ -2073,17 +2286,121 @@ var DataTable = ({
2073
2286
  ));
2074
2287
  })()))) : renderRow(item.row)
2075
2288
  )),
2076
- (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ React6.createElement(TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ React6.createElement(TableRow, null, selectable && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }), columns.map((col) => {
2289
+ (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ React6.createElement(TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ React6.createElement(TableRow, null, selectable && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }), showExpandColumn && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" }), columns.map((col) => {
2077
2290
  const footerDef = col.footer;
2078
2291
  const content = typeof footerDef === "function" ? footerDef(footerData) : footerDef || "";
2079
2292
  return /* @__PURE__ */ React6.createElement(TableHeader, { key: col.field, align: col.align }, content);
2080
2293
  }), showRowActionsColumn && /* @__PURE__ */ React6.createElement(TableHeader, { width: "min" })))
2081
2294
  ));
2082
2295
  };
2296
+ DataTable.displayName = "DataTable";
2083
2297
 
2084
2298
  // src/kanban/Kanban.jsx
2085
2299
  import React9, { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
2086
2300
 
2301
+ // src/kanban/kanbanLanes.js
2302
+ var UNASSIGNED_LANE_KEY = "__unassigned";
2303
+ var UNKNOWN_STAGE_KEY = "__unknown";
2304
+ var getLaneKey = (row, swimlaneBy) => {
2305
+ const raw = typeof swimlaneBy === "function" ? swimlaneBy(row) : row == null ? void 0 : row[swimlaneBy];
2306
+ if (raw == null || raw === "") return UNASSIGNED_LANE_KEY;
2307
+ return String(raw);
2308
+ };
2309
+ var orderLaneKeys = (seenKeys, swimlaneOrder) => {
2310
+ const seen = Array.isArray(seenKeys) ? seenKeys : [];
2311
+ if (!Array.isArray(swimlaneOrder) || swimlaneOrder.length === 0) return [...seen];
2312
+ const explicit = [];
2313
+ for (const key of swimlaneOrder) {
2314
+ const normalized = String(key);
2315
+ if (!explicit.includes(normalized)) explicit.push(normalized);
2316
+ }
2317
+ const rest = seen.filter((key) => !explicit.includes(key));
2318
+ return [...explicit, ...rest];
2319
+ };
2320
+ var partitionLanes = (rows, { swimlaneBy, swimlaneOrder } = {}) => {
2321
+ const rowsByLane = {};
2322
+ const firstSeen = [];
2323
+ for (const row of rows || []) {
2324
+ const key = getLaneKey(row, swimlaneBy);
2325
+ if (!rowsByLane[key]) {
2326
+ rowsByLane[key] = [];
2327
+ firstSeen.push(key);
2328
+ }
2329
+ rowsByLane[key].push(row);
2330
+ }
2331
+ const laneKeys = orderLaneKeys(firstSeen, swimlaneOrder);
2332
+ for (const key of laneKeys) {
2333
+ if (!rowsByLane[key]) rowsByLane[key] = [];
2334
+ }
2335
+ return { laneKeys, rowsByLane };
2336
+ };
2337
+ var resolveLaneLabel = (laneKey, swimlaneLabels, rows, unassignedLabel) => {
2338
+ if (typeof swimlaneLabels === "function") {
2339
+ const out = swimlaneLabels(laneKey, rows || []);
2340
+ if (out != null) return out;
2341
+ } else if (swimlaneLabels && typeof swimlaneLabels === "object") {
2342
+ const out = swimlaneLabels[laneKey];
2343
+ if (out != null) return out;
2344
+ }
2345
+ if (laneKey === UNASSIGNED_LANE_KEY) return unassignedLabel || "Unassigned";
2346
+ return String(laneKey);
2347
+ };
2348
+ var bucketRowsByStage = (rows, stages, getStage) => {
2349
+ const map = {};
2350
+ for (const stage of stages || []) map[stage.value] = [];
2351
+ for (const row of rows || []) {
2352
+ const key = getStage(row);
2353
+ if (map[key]) {
2354
+ map[key].push(row);
2355
+ } else if ((stages || []).length > 0) {
2356
+ if (!map[UNKNOWN_STAGE_KEY]) map[UNKNOWN_STAGE_KEY] = [];
2357
+ map[UNKNOWN_STAGE_KEY].push(row);
2358
+ }
2359
+ }
2360
+ return map;
2361
+ };
2362
+ var sortBuckets = (buckets, comparator) => {
2363
+ if (!comparator) return buckets;
2364
+ const out = {};
2365
+ for (const key of Object.keys(buckets || {})) {
2366
+ out[key] = [...buckets[key]].sort(comparator);
2367
+ }
2368
+ return out;
2369
+ };
2370
+ var resolveWipLimit = (stage, wipLimits) => {
2371
+ const override = wipLimits ? wipLimits[stage == null ? void 0 : stage.value] : void 0;
2372
+ const limit = override != null ? override : stage == null ? void 0 : stage.wipLimit;
2373
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit < 0) return null;
2374
+ return limit;
2375
+ };
2376
+ var computeStageCounts = (stages, buckets, stageMeta) => {
2377
+ const counts = {};
2378
+ for (const stage of stages || []) {
2379
+ const meta = stageMeta ? stageMeta[stage.value] : void 0;
2380
+ counts[stage.value] = meta && meta.totalCount != null ? meta.totalCount : ((buckets == null ? void 0 : buckets[stage.value]) || []).length;
2381
+ }
2382
+ return counts;
2383
+ };
2384
+ var evaluateWip = (stages, counts, wipLimits) => {
2385
+ const out = {};
2386
+ for (const stage of stages || []) {
2387
+ const limit = resolveWipLimit(stage, wipLimits);
2388
+ const count = (counts == null ? void 0 : counts[stage.value]) || 0;
2389
+ out[stage.value] = { count, limit, exceeded: limit != null && count > limit };
2390
+ }
2391
+ return out;
2392
+ };
2393
+ var findNewlyExceededWip = (prev, next) => {
2394
+ const events = [];
2395
+ for (const stageId of Object.keys(next || {})) {
2396
+ const entry = next[stageId];
2397
+ if (!entry || !entry.exceeded) continue;
2398
+ if (prev && prev[stageId] && prev[stageId].exceeded) continue;
2399
+ events.push({ stageId, count: entry.count, limit: entry.limit });
2400
+ }
2401
+ return events;
2402
+ };
2403
+
2087
2404
  // src/common-components/CollectionSortSelect.js
2088
2405
  import React7, { useId as useId2 } from "react";
2089
2406
  import { Select as Select3 } from "@hubspot/ui-extensions";
@@ -2316,6 +2633,7 @@ import {
2316
2633
  Statistics,
2317
2634
  StatisticsItem,
2318
2635
  StatisticsTrend,
2636
+ StatusTag,
2319
2637
  Tag as Tag3,
2320
2638
  Text as Text3,
2321
2639
  Tile as Tile2
@@ -2353,6 +2671,12 @@ var DEFAULT_LABELS3 = {
2353
2671
  errorTitle: "Something went wrong.",
2354
2672
  errorMessage: "An error occurred while loading data.",
2355
2673
  cardCount: (n) => String(n),
2674
+ // "5 / 4" — count vs WIP limit in the stage header. The slash format is the
2675
+ // standard kanban convention; override for tighter ("5/4") or verbose forms.
2676
+ wipCount: (count, limit) => `${count} / ${limit}`,
2677
+ overWip: "Over WIP",
2678
+ laneCount: (n) => String(n),
2679
+ unassignedLane: "Unassigned",
2356
2680
  moveTo: "Move",
2357
2681
  clearAll: "Clear all",
2358
2682
  selectAll: (count, label) => `Select all ${count} ${label}`,
@@ -2579,10 +2903,14 @@ var KanbanColumn = ({
2579
2903
  onToggleCollapsed,
2580
2904
  columnFooter,
2581
2905
  countDisplay,
2906
+ wip,
2907
+ compactEmpty,
2582
2908
  labels,
2583
2909
  children
2584
2910
  }) => {
2585
- const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
2911
+ const hasWipLimit = wip != null && wip.limit != null;
2912
+ const countLabel = hasWipLimit ? labels.wipCount(wip.count, wip.limit) : labels.cardCount(totalCount != null ? totalCount : bucketCount);
2913
+ const overWipNode = wip && wip.exceeded ? /* @__PURE__ */ React9.createElement(StatusTag, { variant: "warning" }, labels.overWip) : null;
2586
2914
  const countNode = countDisplay === "text" ? /* @__PURE__ */ React9.createElement(Text3, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ React9.createElement(Tag3, { variant: "default" }, countLabel);
2587
2915
  if (collapsed) {
2588
2916
  const rotated = makeRotatedLabelDataUri(stage.label);
@@ -2615,8 +2943,11 @@ var KanbanColumn = ({
2615
2943
  }
2616
2944
  ) : null));
2617
2945
  }
2946
+ if (compactEmpty && !loading && rows.length === 0 && bucketCount === 0) {
2947
+ return /* @__PURE__ */ React9.createElement(Tile2, { compact: true }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ React9.createElement(Text3, { variant: "microcopy", format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), /* @__PURE__ */ React9.createElement(Text3, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn)));
2948
+ }
2618
2949
  const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
2619
- return /* @__PURE__ */ React9.createElement(Tile2, { compact: true }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "column", gap: "xs" }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React9.createElement(Text3, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ React9.createElement(LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ React9.createElement(Button4, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ React9.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ React9.createElement(Text3, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ React9.createElement(Divider, null), children, error ? /* @__PURE__ */ React9.createElement(Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ React9.createElement(Text3, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ React9.createElement(Button4, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", justify: "center" }, /* @__PURE__ */ React9.createElement(Link3, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ React9.createElement(LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", justify: "center" }, /* @__PURE__ */ React9.createElement(Link3, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ React9.createElement(Text3, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
2950
+ return /* @__PURE__ */ React9.createElement(Tile2, { compact: true }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "column", gap: "xs" }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React9.createElement(Text3, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, overWipNode, loading ? /* @__PURE__ */ React9.createElement(LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ React9.createElement(Button4, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ React9.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ React9.createElement(Text3, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ React9.createElement(Divider, null), children, error ? /* @__PURE__ */ React9.createElement(Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ React9.createElement(Text3, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ React9.createElement(Button4, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", justify: "center" }, /* @__PURE__ */ React9.createElement(Link3, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ React9.createElement(LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", justify: "center" }, /* @__PURE__ */ React9.createElement(Link3, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ React9.createElement(Text3, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
2620
2951
  };
2621
2952
  var renderMetricsPanel = (metrics) => {
2622
2953
  if (!metrics) return null;
@@ -2655,14 +2986,14 @@ var KanbanToolbar = ({
2655
2986
  sortOptions,
2656
2987
  sortValue,
2657
2988
  onSortChange,
2658
- metrics,
2659
- showMetrics,
2989
+ showMetricsButton,
2990
+ metricsPanel,
2660
2991
  onToggleMetrics,
2661
2992
  labels,
2662
2993
  toolbarLeftFlex,
2663
2994
  toolbarRightFlex
2664
2995
  }) => {
2665
- const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ React9.createElement(
2996
+ const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || showMetricsButton ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ React9.createElement(
2666
2997
  CollectionSortSelect,
2667
2998
  {
2668
2999
  name: "kanban-sort",
@@ -2671,7 +3002,7 @@ var KanbanToolbar = ({
2671
3002
  options: sortOptions,
2672
3003
  onChange: onSortChange
2673
3004
  }
2674
- ) : null, metrics ? /* @__PURE__ */ React9.createElement(Button4, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ React9.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
3005
+ ) : null, showMetricsButton ? /* @__PURE__ */ React9.createElement(Button4, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ React9.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
2675
3006
  return /* @__PURE__ */ React9.createElement(
2676
3007
  CollectionToolbar,
2677
3008
  {
@@ -2698,7 +3029,7 @@ var KanbanToolbar = ({
2698
3029
  onRemove: onFilterRemove
2699
3030
  },
2700
3031
  right: rightControls,
2701
- footer: showMetrics && metrics ? renderMetricsPanel(metrics) : null,
3032
+ footer: metricsPanel,
2702
3033
  labels,
2703
3034
  leftFlex: toolbarLeftFlex,
2704
3035
  rightFlex: toolbarRightFlex
@@ -2750,6 +3081,18 @@ var Kanban = ({
2750
3081
  // --- Per-stage pagination ---
2751
3082
  stageMeta,
2752
3083
  onLoadMore,
3084
+ // --- WIP limits ---
3085
+ wipLimits,
3086
+ onWipExceeded,
3087
+ // --- Swimlanes ---
3088
+ swimlaneBy,
3089
+ swimlaneLabels,
3090
+ swimlaneOrder,
3091
+ collapseLanes = true,
3092
+ collapsedLanes,
3093
+ defaultCollapsedLanes,
3094
+ onCollapsedLanesChange,
3095
+ metricsPerLane = false,
2753
3096
  // --- Selection ---
2754
3097
  selectable = false,
2755
3098
  selectedIds,
@@ -2814,6 +3157,9 @@ var Kanban = ({
2814
3157
  const [internalFilters, setInternalFilters] = useState3(() => getEmptyFilterValues(filters));
2815
3158
  const [internalSort, setInternalSort] = useState3(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
2816
3159
  const [internalCollapsed, setInternalCollapsed] = useState3([]);
3160
+ const [internalCollapsedLanes, setInternalCollapsedLanes] = useState3(
3161
+ () => defaultCollapsedLanes || []
3162
+ );
2817
3163
  const [internalExpanded, setInternalExpanded] = useState3([]);
2818
3164
  const [internalSelection, setInternalSelection] = useState3([]);
2819
3165
  const [internalShowMetrics, setInternalShowMetrics] = useState3(false);
@@ -2830,6 +3176,7 @@ var Kanban = ({
2830
3176
  const resolvedFilters = filterValues != null ? filterValues : internalFilters;
2831
3177
  const resolvedSort = sort != null ? sort : internalSort;
2832
3178
  const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
3179
+ const resolvedCollapsedLanes = collapsedLanes != null ? collapsedLanes : internalCollapsedLanes;
2833
3180
  const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
2834
3181
  const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
2835
3182
  const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
@@ -2846,9 +3193,10 @@ var Kanban = ({
2846
3193
  search: overrides.search != null ? overrides.search : resolvedSearch,
2847
3194
  filters: overrides.filters != null ? overrides.filters : resolvedFilters,
2848
3195
  sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
2849
- collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
3196
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed,
3197
+ collapsedLanes: overrides.collapsedLanes != null ? overrides.collapsedLanes : resolvedCollapsedLanes
2850
3198
  });
2851
- }, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
3199
+ }, [onParamsChange, resolvedCollapsed, resolvedCollapsedLanes, resolvedFilters, resolvedSearch, resolvedSort]);
2852
3200
  const lastAppliedSearchRef = useRef3(searchValue != null ? searchValue : "");
2853
3201
  useEffect3(() => {
2854
3202
  if (searchValue == null) return;
@@ -2907,6 +3255,15 @@ var Kanban = ({
2907
3255
  },
2908
3256
  [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
2909
3257
  );
3258
+ const handleLaneCollapsed = useCallback3(
3259
+ (laneKey) => {
3260
+ const next = resolvedCollapsedLanes.includes(laneKey) ? resolvedCollapsedLanes.filter((k) => k !== laneKey) : [...resolvedCollapsedLanes, laneKey];
3261
+ if (onCollapsedLanesChange) onCollapsedLanesChange(next);
3262
+ if (collapsedLanes == null) setInternalCollapsedLanes(next);
3263
+ fireParamsChange({ collapsedLanes: next });
3264
+ },
3265
+ [fireParamsChange, resolvedCollapsedLanes, collapsedLanes, onCollapsedLanesChange]
3266
+ );
2910
3267
  const handleExpanded = useCallback3(
2911
3268
  (stageValue) => {
2912
3269
  const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
@@ -2975,33 +3332,45 @@ var Kanban = ({
2975
3332
  }
2976
3333
  return result;
2977
3334
  }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
2978
- const buckets = useMemo2(() => {
2979
- const map = {};
2980
- for (const stage of stages) map[stage.value] = [];
2981
- for (const row of filteredData) {
2982
- const key = getStageFor(row);
2983
- if (map[key]) {
2984
- map[key].push(row);
2985
- } else if (stages.length > 0) {
2986
- if (!map.__unknown) map.__unknown = [];
2987
- map.__unknown.push(row);
2988
- }
2989
- }
2990
- return map;
2991
- }, [filteredData, stages, getStageFor]);
3335
+ const buckets = useMemo2(
3336
+ () => bucketRowsByStage(filteredData, stages, getStageFor),
3337
+ [filteredData, stages, getStageFor]
3338
+ );
2992
3339
  const sortComparator = useMemo2(() => {
2993
3340
  if (!sortOptions || !resolvedSort) return null;
2994
3341
  const opt = sortOptions.find((s) => s.value === resolvedSort);
2995
3342
  return (opt == null ? void 0 : opt.comparator) || null;
2996
3343
  }, [sortOptions, resolvedSort]);
2997
- const sortedBuckets = useMemo2(() => {
2998
- if (!sortComparator) return buckets;
3344
+ const sortedBuckets = useMemo2(() => sortBuckets(buckets, sortComparator), [buckets, sortComparator]);
3345
+ const hasLanes = swimlaneBy != null;
3346
+ const laneData = useMemo2(() => {
3347
+ if (!hasLanes) return null;
3348
+ return partitionLanes(filteredData, { swimlaneBy, swimlaneOrder });
3349
+ }, [hasLanes, filteredData, swimlaneBy, swimlaneOrder]);
3350
+ const laneBuckets = useMemo2(() => {
3351
+ if (!laneData) return null;
2999
3352
  const out = {};
3000
- for (const key of Object.keys(buckets)) {
3001
- out[key] = [...buckets[key]].sort(sortComparator);
3353
+ for (const laneKey of laneData.laneKeys) {
3354
+ out[laneKey] = sortBuckets(
3355
+ bucketRowsByStage(laneData.rowsByLane[laneKey] || [], stages, getStageFor),
3356
+ sortComparator
3357
+ );
3002
3358
  }
3003
3359
  return out;
3004
- }, [buckets, sortComparator]);
3360
+ }, [laneData, stages, getStageFor, sortComparator]);
3361
+ const wipByStage = useMemo2(
3362
+ () => evaluateWip(stages, computeStageCounts(stages, buckets, stageMeta), wipLimits),
3363
+ [stages, buckets, stageMeta, wipLimits]
3364
+ );
3365
+ const prevWipRef = useRef3({});
3366
+ useEffect3(() => {
3367
+ const newlyExceeded = findNewlyExceededWip(prevWipRef.current, wipByStage);
3368
+ prevWipRef.current = wipByStage;
3369
+ if (!onWipExceeded) return;
3370
+ for (const event of newlyExceeded) {
3371
+ onWipExceeded(event.stageId, event.count, event.limit);
3372
+ }
3373
+ }, [wipByStage, onWipExceeded]);
3005
3374
  const activeChips = useMemo2(
3006
3375
  () => buildActiveFilterChips(filters, resolvedFilters),
3007
3376
  [filters, resolvedFilters]
@@ -3128,6 +3497,52 @@ var Kanban = ({
3128
3497
  selectionActions: selectionActions || [],
3129
3498
  labels
3130
3499
  };
3500
+ const metricsProvided = metrics != null && (!Array.isArray(metrics) || metrics.length > 0);
3501
+ const perLaneMetricsActive = hasLanes && metricsPerLane && typeof metrics === "function";
3502
+ const globalMetricsContent = metricsProvided && !perLaneMetricsActive ? typeof metrics === "function" ? metrics(filteredData, null) : metrics : null;
3503
+ const renderStageColumns = (bucketMap, laneKey) => {
3504
+ const inLane = laneKey != null;
3505
+ return /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
3506
+ const stageRows = bucketMap[stage.value] || [];
3507
+ const meta = inLane ? void 0 : stageMeta == null ? void 0 : stageMeta[stage.value];
3508
+ const isExpanded = resolvedExpanded.includes(stage.value);
3509
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
3510
+ const visibleRows = stageRows.slice(0, clamp);
3511
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
3512
+ const stageWip = wipByStage[stage.value];
3513
+ const wip = inLane ? (stageWip == null ? void 0 : stageWip.exceeded) ? { count: stageWip.count, limit: null, exceeded: true } : null : stageWip;
3514
+ return /* @__PURE__ */ React9.createElement(
3515
+ AutoGrid,
3516
+ {
3517
+ key: inLane ? `${laneKey}::${stage.value}` : stage.value,
3518
+ columnWidth: isCollapsed ? 72 : effectiveColumnWidth
3519
+ },
3520
+ /* @__PURE__ */ React9.createElement(
3521
+ KanbanColumn,
3522
+ {
3523
+ stage,
3524
+ rows: visibleRows,
3525
+ bucketCount: stageRows.length,
3526
+ totalCount: meta == null ? void 0 : meta.totalCount,
3527
+ hasMore: meta == null ? void 0 : meta.hasMore,
3528
+ loading: meta == null ? void 0 : meta.loading,
3529
+ error: meta == null ? void 0 : meta.error,
3530
+ onLoadMore: inLane ? void 0 : onLoadMore,
3531
+ expanded: isExpanded,
3532
+ onToggleExpanded: () => handleExpanded(stage.value),
3533
+ collapsed: isCollapsed,
3534
+ onToggleCollapsed: () => handleCollapsed(stage.value),
3535
+ columnFooter,
3536
+ countDisplay,
3537
+ wip,
3538
+ compactEmpty: inLane,
3539
+ labels
3540
+ },
3541
+ visibleRows.map((row) => renderCardNode(row, stage))
3542
+ )
3543
+ );
3544
+ }));
3545
+ };
3131
3546
  const mainContent = error ? renderErrorState ? renderErrorState({
3132
3547
  error,
3133
3548
  title: labels.errorTitle,
@@ -3139,35 +3554,32 @@ var Kanban = ({
3139
3554
  ) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
3140
3555
  title: labels.emptyTitle,
3141
3556
  message: labels.emptyMessage
3142
- }) : /* @__PURE__ */ React9.createElement(Tile2, null, /* @__PURE__ */ React9.createElement(Flex5, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React9.createElement(EmptyState2, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ React9.createElement(Text3, null, labels.emptyMessage)))) : /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
3143
- const stageRows = sortedBuckets[stage.value] || [];
3144
- const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
3145
- const isExpanded = resolvedExpanded.includes(stage.value);
3146
- const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
3147
- const visibleRows = stageRows.slice(0, clamp);
3148
- const isCollapsed = resolvedCollapsed.includes(stage.value);
3149
- return /* @__PURE__ */ React9.createElement(AutoGrid, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ React9.createElement(
3150
- KanbanColumn,
3557
+ }) : /* @__PURE__ */ React9.createElement(Tile2, null, /* @__PURE__ */ React9.createElement(Flex5, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React9.createElement(EmptyState2, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ React9.createElement(Text3, null, labels.emptyMessage)))) : hasLanes ? /* @__PURE__ */ React9.createElement(Flex5, { direction: "column", gap: "md" }, ((laneData == null ? void 0 : laneData.laneKeys) || []).map((laneKey, laneIndex) => {
3558
+ const laneRows = laneData.rowsByLane[laneKey] || [];
3559
+ const laneLabel = resolveLaneLabel(laneKey, swimlaneLabels, laneRows, labels.unassignedLane);
3560
+ const laneLabelText = typeof laneLabel === "string" ? laneLabel : String(laneKey);
3561
+ const isLaneCollapsed = collapseLanes && resolvedCollapsedLanes.includes(laneKey);
3562
+ const laneCountLabel = labels.laneCount(laneRows.length);
3563
+ const laneCountNode = countDisplay === "none" ? null : countDisplay === "text" ? /* @__PURE__ */ React9.createElement(Text3, { format: { fontWeight: "demibold" } }, laneCountLabel) : /* @__PURE__ */ React9.createElement(Tag3, { variant: "default" }, laneCountLabel);
3564
+ const laneMetricsNode = !isLaneCollapsed && perLaneMetricsActive && resolvedShowMetrics ? renderMetricsPanel(metrics(laneRows, laneKey)) : null;
3565
+ return /* @__PURE__ */ React9.createElement(Flex5, { key: laneKey, direction: "column", gap: "xs" }, laneIndex > 0 ? /* @__PURE__ */ React9.createElement(Divider, null) : null, /* @__PURE__ */ React9.createElement(Flex5, { direction: "row", align: "center", gap: "xs" }, collapseLanes ? /* @__PURE__ */ React9.createElement(
3566
+ Button4,
3151
3567
  {
3152
- stage,
3153
- rows: visibleRows,
3154
- bucketCount: stageRows.length,
3155
- totalCount: meta == null ? void 0 : meta.totalCount,
3156
- hasMore: meta == null ? void 0 : meta.hasMore,
3157
- loading: meta == null ? void 0 : meta.loading,
3158
- error: meta == null ? void 0 : meta.error,
3159
- onLoadMore,
3160
- expanded: isExpanded,
3161
- onToggleExpanded: () => handleExpanded(stage.value),
3162
- collapsed: isCollapsed,
3163
- onToggleCollapsed: () => handleCollapsed(stage.value),
3164
- columnFooter,
3165
- countDisplay,
3166
- labels
3568
+ variant: "transparent",
3569
+ size: "sm",
3570
+ onClick: () => handleLaneCollapsed(laneKey),
3571
+ tooltip: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
3167
3572
  },
3168
- visibleRows.map((row) => renderCardNode(row, stage))
3169
- ));
3170
- }));
3573
+ /* @__PURE__ */ React9.createElement(
3574
+ Icon,
3575
+ {
3576
+ name: isLaneCollapsed ? "right" : "down",
3577
+ size: "sm",
3578
+ screenReaderText: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
3579
+ }
3580
+ )
3581
+ ) : null, /* @__PURE__ */ React9.createElement(Text3, { format: { fontWeight: "demibold" } }, laneLabel), laneCountNode), laneMetricsNode, !isLaneCollapsed ? renderStageColumns((laneBuckets == null ? void 0 : laneBuckets[laneKey]) || {}, laneKey) : null);
3582
+ })) : renderStageColumns(sortedBuckets, null);
3171
3583
  const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
3172
3584
  return /* @__PURE__ */ React9.createElement(Flex5, { direction: "column", gap: "sm" }, /* @__PURE__ */ React9.createElement(
3173
3585
  KanbanToolbar,
@@ -3187,8 +3599,8 @@ var Kanban = ({
3187
3599
  sortOptions,
3188
3600
  sortValue: resolvedSort,
3189
3601
  onSortChange: handleSort,
3190
- metrics,
3191
- showMetrics: resolvedShowMetrics,
3602
+ showMetricsButton: metricsProvided,
3603
+ metricsPanel: resolvedShowMetrics && globalMetricsContent ? renderMetricsPanel(globalMetricsContent) : null,
3192
3604
  onToggleMetrics: toggleMetrics,
3193
3605
  labels,
3194
3606
  toolbarLeftFlex,
@@ -3196,6 +3608,7 @@ var Kanban = ({
3196
3608
  }
3197
3609
  ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ React9.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
3198
3610
  };
3611
+ Kanban.displayName = "Kanban";
3199
3612
 
3200
3613
  // src/utils/objectPath.js
3201
3614
  var getByPath = (obj, path) => {
@@ -3762,6 +4175,8 @@ var CrmKanban = ({
3762
4175
  board
3763
4176
  );
3764
4177
  };
4178
+ CrmDataTable.displayName = "CrmDataTable";
4179
+ CrmKanban.displayName = "CrmKanban";
3765
4180
 
3766
4181
  // src/utils/formatters.js
3767
4182
  var DEFAULT_LOCALE = "en-US";
@@ -4072,6 +4487,7 @@ var deriveCardFieldsFromColumns = (columns, opts = {}) => {
4072
4487
  export {
4073
4488
  CrmDataTable,
4074
4489
  CrmKanban,
4490
+ applyPatches,
4075
4491
  buildActiveFilterChips,
4076
4492
  buildCrmSearchConfig,
4077
4493
  buildOptions,