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/kanban.js CHANGED
@@ -30,7 +30,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  var kanban_exports = {};
31
31
  __export(kanban_exports, {
32
32
  Kanban: () => Kanban,
33
- KanbanCardActions: () => KanbanCardActions
33
+ KanbanCardActions: () => KanbanCardActions,
34
+ UNASSIGNED_LANE_KEY: () => UNASSIGNED_LANE_KEY,
35
+ computeStageCounts: () => computeStageCounts,
36
+ evaluateWip: () => evaluateWip,
37
+ findNewlyExceededWip: () => findNewlyExceededWip,
38
+ getLaneKey: () => getLaneKey,
39
+ orderLaneKeys: () => orderLaneKeys,
40
+ partitionLanes: () => partitionLanes,
41
+ resolveLaneLabel: () => resolveLaneLabel,
42
+ resolveWipLimit: () => resolveWipLimit
34
43
  });
35
44
  module.exports = __toCommonJS(kanban_exports);
36
45
 
@@ -203,6 +212,109 @@ var useSelectionReset = ({ resetKey, enabled, isControlled, clearSelection }) =>
203
212
  }, [resetKey, enabled, isControlled, clearSelection]);
204
213
  };
205
214
 
215
+ // src/kanban/kanbanLanes.js
216
+ var UNASSIGNED_LANE_KEY = "__unassigned";
217
+ var UNKNOWN_STAGE_KEY = "__unknown";
218
+ var getLaneKey = (row, swimlaneBy) => {
219
+ const raw = typeof swimlaneBy === "function" ? swimlaneBy(row) : row == null ? void 0 : row[swimlaneBy];
220
+ if (raw == null || raw === "") return UNASSIGNED_LANE_KEY;
221
+ return String(raw);
222
+ };
223
+ var orderLaneKeys = (seenKeys, swimlaneOrder) => {
224
+ const seen = Array.isArray(seenKeys) ? seenKeys : [];
225
+ if (!Array.isArray(swimlaneOrder) || swimlaneOrder.length === 0) return [...seen];
226
+ const explicit = [];
227
+ for (const key of swimlaneOrder) {
228
+ const normalized = String(key);
229
+ if (!explicit.includes(normalized)) explicit.push(normalized);
230
+ }
231
+ const rest = seen.filter((key) => !explicit.includes(key));
232
+ return [...explicit, ...rest];
233
+ };
234
+ var partitionLanes = (rows, { swimlaneBy, swimlaneOrder } = {}) => {
235
+ const rowsByLane = {};
236
+ const firstSeen = [];
237
+ for (const row of rows || []) {
238
+ const key = getLaneKey(row, swimlaneBy);
239
+ if (!rowsByLane[key]) {
240
+ rowsByLane[key] = [];
241
+ firstSeen.push(key);
242
+ }
243
+ rowsByLane[key].push(row);
244
+ }
245
+ const laneKeys = orderLaneKeys(firstSeen, swimlaneOrder);
246
+ for (const key of laneKeys) {
247
+ if (!rowsByLane[key]) rowsByLane[key] = [];
248
+ }
249
+ return { laneKeys, rowsByLane };
250
+ };
251
+ var resolveLaneLabel = (laneKey, swimlaneLabels, rows, unassignedLabel) => {
252
+ if (typeof swimlaneLabels === "function") {
253
+ const out = swimlaneLabels(laneKey, rows || []);
254
+ if (out != null) return out;
255
+ } else if (swimlaneLabels && typeof swimlaneLabels === "object") {
256
+ const out = swimlaneLabels[laneKey];
257
+ if (out != null) return out;
258
+ }
259
+ if (laneKey === UNASSIGNED_LANE_KEY) return unassignedLabel || "Unassigned";
260
+ return String(laneKey);
261
+ };
262
+ var bucketRowsByStage = (rows, stages, getStage) => {
263
+ const map = {};
264
+ for (const stage of stages || []) map[stage.value] = [];
265
+ for (const row of rows || []) {
266
+ const key = getStage(row);
267
+ if (map[key]) {
268
+ map[key].push(row);
269
+ } else if ((stages || []).length > 0) {
270
+ if (!map[UNKNOWN_STAGE_KEY]) map[UNKNOWN_STAGE_KEY] = [];
271
+ map[UNKNOWN_STAGE_KEY].push(row);
272
+ }
273
+ }
274
+ return map;
275
+ };
276
+ var sortBuckets = (buckets, comparator) => {
277
+ if (!comparator) return buckets;
278
+ const out = {};
279
+ for (const key of Object.keys(buckets || {})) {
280
+ out[key] = [...buckets[key]].sort(comparator);
281
+ }
282
+ return out;
283
+ };
284
+ var resolveWipLimit = (stage, wipLimits) => {
285
+ const override = wipLimits ? wipLimits[stage == null ? void 0 : stage.value] : void 0;
286
+ const limit = override != null ? override : stage == null ? void 0 : stage.wipLimit;
287
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit < 0) return null;
288
+ return limit;
289
+ };
290
+ var computeStageCounts = (stages, buckets, stageMeta) => {
291
+ const counts = {};
292
+ for (const stage of stages || []) {
293
+ const meta = stageMeta ? stageMeta[stage.value] : void 0;
294
+ counts[stage.value] = meta && meta.totalCount != null ? meta.totalCount : ((buckets == null ? void 0 : buckets[stage.value]) || []).length;
295
+ }
296
+ return counts;
297
+ };
298
+ var evaluateWip = (stages, counts, wipLimits) => {
299
+ const out = {};
300
+ for (const stage of stages || []) {
301
+ const limit = resolveWipLimit(stage, wipLimits);
302
+ const count = (counts == null ? void 0 : counts[stage.value]) || 0;
303
+ out[stage.value] = { count, limit, exceeded: limit != null && count > limit };
304
+ }
305
+ return out;
306
+ };
307
+ var findNewlyExceededWip = (prev, next) => {
308
+ const events = [];
309
+ for (const stageId of Object.keys(next || {})) {
310
+ const entry = next[stageId];
311
+ if (!entry || !entry.exceeded) continue;
312
+ if (prev && prev[stageId] && prev[stageId].exceeded) continue;
313
+ events.push({ stageId, count: entry.count, limit: entry.limit });
314
+ }
315
+ return events;
316
+ };
317
+
206
318
  // src/common-components/CollectionSortSelect.js
207
319
  var import_react2 = __toESM(require("react"));
208
320
  var import_ui_extensions2 = require("@hubspot/ui-extensions");
@@ -552,7 +664,7 @@ var GENERATED_ICONS = {
552
664
  "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"] }
553
665
  };
554
666
 
555
- // src/common-components/Icon.js
667
+ // src/common-components/nativeIconNames.js
556
668
  var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
557
669
  "add",
558
670
  "appointment",
@@ -564,12 +676,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
564
676
  "block",
565
677
  "book",
566
678
  "bulb",
679
+ "callTranscript",
567
680
  "calling",
568
681
  "callingHangup",
569
682
  "callingMade",
570
683
  "callingMissed",
571
684
  "callingVoicemail",
572
- "callTranscript",
573
685
  "campaigns",
574
686
  "cap",
575
687
  "checkCircle",
@@ -598,13 +710,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
598
710
  "enroll",
599
711
  "exclamation",
600
712
  "exclamationCircle",
601
- "facebook",
602
713
  "faceHappy",
603
714
  "faceHappyFilled",
604
715
  "faceNeutral",
605
716
  "faceNeutralFilled",
606
717
  "faceSad",
607
718
  "faceSadFilled",
719
+ "facebook",
608
720
  "favoriteHollow",
609
721
  "file",
610
722
  "filledXCircleIcon",
@@ -745,6 +857,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
745
857
  "zoomIn",
746
858
  "zoomOut"
747
859
  ]);
860
+
861
+ // src/common-components/Icon.js
748
862
  var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
749
863
  var NATIVE_SIZE_TOKENS = {
750
864
  sm: "sm",
@@ -957,6 +1071,7 @@ var CollectionFilterControl = ({
957
1071
  { key: name, direction: "row", align: "center", gap: "xs" },
958
1072
  h3(import_ui_extensions5.DateInput, {
959
1073
  name: `${controlName}-from`,
1074
+ label: filter.fromLabel ?? labels.dateFrom,
960
1075
  placeholder: filter.fromLabel ?? labels.dateFrom,
961
1076
  format: "medium",
962
1077
  value: rangeValue.from ?? null,
@@ -965,6 +1080,7 @@ var CollectionFilterControl = ({
965
1080
  h3(Icon, { name: "right", size: "sm" }),
966
1081
  h3(import_ui_extensions5.DateInput, {
967
1082
  name: `${controlName}-to`,
1083
+ label: filter.toLabel ?? labels.dateTo,
968
1084
  placeholder: filter.toLabel ?? labels.dateTo,
969
1085
  format: "medium",
970
1086
  value: rangeValue.to ?? null,
@@ -1309,6 +1425,12 @@ var DEFAULT_LABELS3 = {
1309
1425
  errorTitle: "Something went wrong.",
1310
1426
  errorMessage: "An error occurred while loading data.",
1311
1427
  cardCount: (n) => String(n),
1428
+ // "5 / 4" — count vs WIP limit in the stage header. The slash format is the
1429
+ // standard kanban convention; override for tighter ("5/4") or verbose forms.
1430
+ wipCount: (count, limit) => `${count} / ${limit}`,
1431
+ overWip: "Over WIP",
1432
+ laneCount: (n) => String(n),
1433
+ unassignedLane: "Unassigned",
1312
1434
  moveTo: "Move",
1313
1435
  clearAll: "Clear all",
1314
1436
  selectAll: (count, label) => `Select all ${count} ${label}`,
@@ -1535,10 +1657,14 @@ var KanbanColumn = ({
1535
1657
  onToggleCollapsed,
1536
1658
  columnFooter,
1537
1659
  countDisplay,
1660
+ wip,
1661
+ compactEmpty,
1538
1662
  labels,
1539
1663
  children
1540
1664
  }) => {
1541
- const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
1665
+ const hasWipLimit = wip != null && wip.limit != null;
1666
+ const countLabel = hasWipLimit ? labels.wipCount(wip.count, wip.limit) : labels.cardCount(totalCount != null ? totalCount : bucketCount);
1667
+ const overWipNode = wip && wip.exceeded ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.StatusTag, { variant: "warning" }, labels.overWip) : null;
1542
1668
  const countNode = countDisplay === "text" ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tag, { variant: "default" }, countLabel);
1543
1669
  if (collapsed) {
1544
1670
  const rotated = makeRotatedLabelDataUri(stage.label);
@@ -1571,8 +1697,11 @@ var KanbanColumn = ({
1571
1697
  }
1572
1698
  ) : null));
1573
1699
  }
1700
+ if (compactEmpty && !loading && rows.length === 0 && bucketCount === 0) {
1701
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tile, { compact: true }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy", format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn)));
1702
+ }
1574
1703
  const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
1575
- return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tile, { compact: true }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react8.default.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Divider, null), children, error ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
1704
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tile, { compact: true }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, overWipNode, loading ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react8.default.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Divider, null), children, error ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
1576
1705
  };
1577
1706
  var renderMetricsPanel = (metrics) => {
1578
1707
  if (!metrics) return null;
@@ -1611,14 +1740,14 @@ var KanbanToolbar = ({
1611
1740
  sortOptions,
1612
1741
  sortValue,
1613
1742
  onSortChange,
1614
- metrics,
1615
- showMetrics,
1743
+ showMetricsButton,
1744
+ metricsPanel,
1616
1745
  onToggleMetrics,
1617
1746
  labels,
1618
1747
  toolbarLeftFlex,
1619
1748
  toolbarRightFlex
1620
1749
  }) => {
1621
- const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react8.default.createElement(
1750
+ const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || showMetricsButton ? /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react8.default.createElement(
1622
1751
  CollectionSortSelect,
1623
1752
  {
1624
1753
  name: "kanban-sort",
@@ -1627,7 +1756,7 @@ var KanbanToolbar = ({
1627
1756
  options: sortOptions,
1628
1757
  onChange: onSortChange
1629
1758
  }
1630
- ) : null, metrics ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react8.default.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
1759
+ ) : null, showMetricsButton ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react8.default.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
1631
1760
  return /* @__PURE__ */ import_react8.default.createElement(
1632
1761
  CollectionToolbar,
1633
1762
  {
@@ -1654,7 +1783,7 @@ var KanbanToolbar = ({
1654
1783
  onRemove: onFilterRemove
1655
1784
  },
1656
1785
  right: rightControls,
1657
- footer: showMetrics && metrics ? renderMetricsPanel(metrics) : null,
1786
+ footer: metricsPanel,
1658
1787
  labels,
1659
1788
  leftFlex: toolbarLeftFlex,
1660
1789
  rightFlex: toolbarRightFlex
@@ -1706,6 +1835,18 @@ var Kanban = ({
1706
1835
  // --- Per-stage pagination ---
1707
1836
  stageMeta,
1708
1837
  onLoadMore,
1838
+ // --- WIP limits ---
1839
+ wipLimits,
1840
+ onWipExceeded,
1841
+ // --- Swimlanes ---
1842
+ swimlaneBy,
1843
+ swimlaneLabels,
1844
+ swimlaneOrder,
1845
+ collapseLanes = true,
1846
+ collapsedLanes,
1847
+ defaultCollapsedLanes,
1848
+ onCollapsedLanesChange,
1849
+ metricsPerLane = false,
1709
1850
  // --- Selection ---
1710
1851
  selectable = false,
1711
1852
  selectedIds,
@@ -1770,6 +1911,9 @@ var Kanban = ({
1770
1911
  const [internalFilters, setInternalFilters] = (0, import_react8.useState)(() => getEmptyFilterValues(filters));
1771
1912
  const [internalSort, setInternalSort] = (0, import_react8.useState)(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
1772
1913
  const [internalCollapsed, setInternalCollapsed] = (0, import_react8.useState)([]);
1914
+ const [internalCollapsedLanes, setInternalCollapsedLanes] = (0, import_react8.useState)(
1915
+ () => defaultCollapsedLanes || []
1916
+ );
1773
1917
  const [internalExpanded, setInternalExpanded] = (0, import_react8.useState)([]);
1774
1918
  const [internalSelection, setInternalSelection] = (0, import_react8.useState)([]);
1775
1919
  const [internalShowMetrics, setInternalShowMetrics] = (0, import_react8.useState)(false);
@@ -1786,6 +1930,7 @@ var Kanban = ({
1786
1930
  const resolvedFilters = filterValues != null ? filterValues : internalFilters;
1787
1931
  const resolvedSort = sort != null ? sort : internalSort;
1788
1932
  const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
1933
+ const resolvedCollapsedLanes = collapsedLanes != null ? collapsedLanes : internalCollapsedLanes;
1789
1934
  const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
1790
1935
  const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
1791
1936
  const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
@@ -1802,9 +1947,10 @@ var Kanban = ({
1802
1947
  search: overrides.search != null ? overrides.search : resolvedSearch,
1803
1948
  filters: overrides.filters != null ? overrides.filters : resolvedFilters,
1804
1949
  sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
1805
- collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
1950
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed,
1951
+ collapsedLanes: overrides.collapsedLanes != null ? overrides.collapsedLanes : resolvedCollapsedLanes
1806
1952
  });
1807
- }, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
1953
+ }, [onParamsChange, resolvedCollapsed, resolvedCollapsedLanes, resolvedFilters, resolvedSearch, resolvedSort]);
1808
1954
  const lastAppliedSearchRef = (0, import_react8.useRef)(searchValue != null ? searchValue : "");
1809
1955
  (0, import_react8.useEffect)(() => {
1810
1956
  if (searchValue == null) return;
@@ -1863,6 +2009,15 @@ var Kanban = ({
1863
2009
  },
1864
2010
  [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
1865
2011
  );
2012
+ const handleLaneCollapsed = (0, import_react8.useCallback)(
2013
+ (laneKey) => {
2014
+ const next = resolvedCollapsedLanes.includes(laneKey) ? resolvedCollapsedLanes.filter((k) => k !== laneKey) : [...resolvedCollapsedLanes, laneKey];
2015
+ if (onCollapsedLanesChange) onCollapsedLanesChange(next);
2016
+ if (collapsedLanes == null) setInternalCollapsedLanes(next);
2017
+ fireParamsChange({ collapsedLanes: next });
2018
+ },
2019
+ [fireParamsChange, resolvedCollapsedLanes, collapsedLanes, onCollapsedLanesChange]
2020
+ );
1866
2021
  const handleExpanded = (0, import_react8.useCallback)(
1867
2022
  (stageValue) => {
1868
2023
  const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
@@ -1931,33 +2086,45 @@ var Kanban = ({
1931
2086
  }
1932
2087
  return result;
1933
2088
  }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
1934
- const buckets = (0, import_react8.useMemo)(() => {
1935
- const map = {};
1936
- for (const stage of stages) map[stage.value] = [];
1937
- for (const row of filteredData) {
1938
- const key = getStageFor(row);
1939
- if (map[key]) {
1940
- map[key].push(row);
1941
- } else if (stages.length > 0) {
1942
- if (!map.__unknown) map.__unknown = [];
1943
- map.__unknown.push(row);
1944
- }
1945
- }
1946
- return map;
1947
- }, [filteredData, stages, getStageFor]);
2089
+ const buckets = (0, import_react8.useMemo)(
2090
+ () => bucketRowsByStage(filteredData, stages, getStageFor),
2091
+ [filteredData, stages, getStageFor]
2092
+ );
1948
2093
  const sortComparator = (0, import_react8.useMemo)(() => {
1949
2094
  if (!sortOptions || !resolvedSort) return null;
1950
2095
  const opt = sortOptions.find((s) => s.value === resolvedSort);
1951
2096
  return (opt == null ? void 0 : opt.comparator) || null;
1952
2097
  }, [sortOptions, resolvedSort]);
1953
- const sortedBuckets = (0, import_react8.useMemo)(() => {
1954
- if (!sortComparator) return buckets;
2098
+ const sortedBuckets = (0, import_react8.useMemo)(() => sortBuckets(buckets, sortComparator), [buckets, sortComparator]);
2099
+ const hasLanes = swimlaneBy != null;
2100
+ const laneData = (0, import_react8.useMemo)(() => {
2101
+ if (!hasLanes) return null;
2102
+ return partitionLanes(filteredData, { swimlaneBy, swimlaneOrder });
2103
+ }, [hasLanes, filteredData, swimlaneBy, swimlaneOrder]);
2104
+ const laneBuckets = (0, import_react8.useMemo)(() => {
2105
+ if (!laneData) return null;
1955
2106
  const out = {};
1956
- for (const key of Object.keys(buckets)) {
1957
- out[key] = [...buckets[key]].sort(sortComparator);
2107
+ for (const laneKey of laneData.laneKeys) {
2108
+ out[laneKey] = sortBuckets(
2109
+ bucketRowsByStage(laneData.rowsByLane[laneKey] || [], stages, getStageFor),
2110
+ sortComparator
2111
+ );
1958
2112
  }
1959
2113
  return out;
1960
- }, [buckets, sortComparator]);
2114
+ }, [laneData, stages, getStageFor, sortComparator]);
2115
+ const wipByStage = (0, import_react8.useMemo)(
2116
+ () => evaluateWip(stages, computeStageCounts(stages, buckets, stageMeta), wipLimits),
2117
+ [stages, buckets, stageMeta, wipLimits]
2118
+ );
2119
+ const prevWipRef = (0, import_react8.useRef)({});
2120
+ (0, import_react8.useEffect)(() => {
2121
+ const newlyExceeded = findNewlyExceededWip(prevWipRef.current, wipByStage);
2122
+ prevWipRef.current = wipByStage;
2123
+ if (!onWipExceeded) return;
2124
+ for (const event of newlyExceeded) {
2125
+ onWipExceeded(event.stageId, event.count, event.limit);
2126
+ }
2127
+ }, [wipByStage, onWipExceeded]);
1961
2128
  const activeChips = (0, import_react8.useMemo)(
1962
2129
  () => buildActiveFilterChips(filters, resolvedFilters),
1963
2130
  [filters, resolvedFilters]
@@ -2084,6 +2251,52 @@ var Kanban = ({
2084
2251
  selectionActions: selectionActions || [],
2085
2252
  labels
2086
2253
  };
2254
+ const metricsProvided = metrics != null && (!Array.isArray(metrics) || metrics.length > 0);
2255
+ const perLaneMetricsActive = hasLanes && metricsPerLane && typeof metrics === "function";
2256
+ const globalMetricsContent = metricsProvided && !perLaneMetricsActive ? typeof metrics === "function" ? metrics(filteredData, null) : metrics : null;
2257
+ const renderStageColumns = (bucketMap, laneKey) => {
2258
+ const inLane = laneKey != null;
2259
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
2260
+ const stageRows = bucketMap[stage.value] || [];
2261
+ const meta = inLane ? void 0 : stageMeta == null ? void 0 : stageMeta[stage.value];
2262
+ const isExpanded = resolvedExpanded.includes(stage.value);
2263
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
2264
+ const visibleRows = stageRows.slice(0, clamp);
2265
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
2266
+ const stageWip = wipByStage[stage.value];
2267
+ const wip = inLane ? (stageWip == null ? void 0 : stageWip.exceeded) ? { count: stageWip.count, limit: null, exceeded: true } : null : stageWip;
2268
+ return /* @__PURE__ */ import_react8.default.createElement(
2269
+ import_ui_extensions8.AutoGrid,
2270
+ {
2271
+ key: inLane ? `${laneKey}::${stage.value}` : stage.value,
2272
+ columnWidth: isCollapsed ? 72 : effectiveColumnWidth
2273
+ },
2274
+ /* @__PURE__ */ import_react8.default.createElement(
2275
+ KanbanColumn,
2276
+ {
2277
+ stage,
2278
+ rows: visibleRows,
2279
+ bucketCount: stageRows.length,
2280
+ totalCount: meta == null ? void 0 : meta.totalCount,
2281
+ hasMore: meta == null ? void 0 : meta.hasMore,
2282
+ loading: meta == null ? void 0 : meta.loading,
2283
+ error: meta == null ? void 0 : meta.error,
2284
+ onLoadMore: inLane ? void 0 : onLoadMore,
2285
+ expanded: isExpanded,
2286
+ onToggleExpanded: () => handleExpanded(stage.value),
2287
+ collapsed: isCollapsed,
2288
+ onToggleCollapsed: () => handleCollapsed(stage.value),
2289
+ columnFooter,
2290
+ countDisplay,
2291
+ wip,
2292
+ compactEmpty: inLane,
2293
+ labels
2294
+ },
2295
+ visibleRows.map((row) => renderCardNode(row, stage))
2296
+ )
2297
+ );
2298
+ }));
2299
+ };
2087
2300
  const mainContent = error ? renderErrorState ? renderErrorState({
2088
2301
  error,
2089
2302
  title: labels.errorTitle,
@@ -2095,35 +2308,32 @@ var Kanban = ({
2095
2308
  ) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
2096
2309
  title: labels.emptyTitle,
2097
2310
  message: labels.emptyMessage
2098
- }) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tile, null, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, labels.emptyMessage)))) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
2099
- const stageRows = sortedBuckets[stage.value] || [];
2100
- const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
2101
- const isExpanded = resolvedExpanded.includes(stage.value);
2102
- const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
2103
- const visibleRows = stageRows.slice(0, clamp);
2104
- const isCollapsed = resolvedCollapsed.includes(stage.value);
2105
- return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.AutoGrid, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ import_react8.default.createElement(
2106
- KanbanColumn,
2311
+ }) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tile, null, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, labels.emptyMessage)))) : hasLanes ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: "md" }, ((laneData == null ? void 0 : laneData.laneKeys) || []).map((laneKey, laneIndex) => {
2312
+ const laneRows = laneData.rowsByLane[laneKey] || [];
2313
+ const laneLabel = resolveLaneLabel(laneKey, swimlaneLabels, laneRows, labels.unassignedLane);
2314
+ const laneLabelText = typeof laneLabel === "string" ? laneLabel : String(laneKey);
2315
+ const isLaneCollapsed = collapseLanes && resolvedCollapsedLanes.includes(laneKey);
2316
+ const laneCountLabel = labels.laneCount(laneRows.length);
2317
+ const laneCountNode = countDisplay === "none" ? null : countDisplay === "text" ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, laneCountLabel) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tag, { variant: "default" }, laneCountLabel);
2318
+ const laneMetricsNode = !isLaneCollapsed && perLaneMetricsActive && resolvedShowMetrics ? renderMetricsPanel(metrics(laneRows, laneKey)) : null;
2319
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { key: laneKey, direction: "column", gap: "xs" }, laneIndex > 0 ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Divider, null) : null, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", gap: "xs" }, collapseLanes ? /* @__PURE__ */ import_react8.default.createElement(
2320
+ import_ui_extensions8.Button,
2107
2321
  {
2108
- stage,
2109
- rows: visibleRows,
2110
- bucketCount: stageRows.length,
2111
- totalCount: meta == null ? void 0 : meta.totalCount,
2112
- hasMore: meta == null ? void 0 : meta.hasMore,
2113
- loading: meta == null ? void 0 : meta.loading,
2114
- error: meta == null ? void 0 : meta.error,
2115
- onLoadMore,
2116
- expanded: isExpanded,
2117
- onToggleExpanded: () => handleExpanded(stage.value),
2118
- collapsed: isCollapsed,
2119
- onToggleCollapsed: () => handleCollapsed(stage.value),
2120
- columnFooter,
2121
- countDisplay,
2122
- labels
2322
+ variant: "transparent",
2323
+ size: "sm",
2324
+ onClick: () => handleLaneCollapsed(laneKey),
2325
+ tooltip: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
2123
2326
  },
2124
- visibleRows.map((row) => renderCardNode(row, stage))
2125
- ));
2126
- }));
2327
+ /* @__PURE__ */ import_react8.default.createElement(
2328
+ Icon,
2329
+ {
2330
+ name: isLaneCollapsed ? "right" : "down",
2331
+ size: "sm",
2332
+ screenReaderText: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
2333
+ }
2334
+ )
2335
+ ) : null, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, laneLabel), laneCountNode), laneMetricsNode, !isLaneCollapsed ? renderStageColumns((laneBuckets == null ? void 0 : laneBuckets[laneKey]) || {}, laneKey) : null);
2336
+ })) : renderStageColumns(sortedBuckets, null);
2127
2337
  const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
2128
2338
  return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react8.default.createElement(
2129
2339
  KanbanToolbar,
@@ -2143,8 +2353,8 @@ var Kanban = ({
2143
2353
  sortOptions,
2144
2354
  sortValue: resolvedSort,
2145
2355
  onSortChange: handleSort,
2146
- metrics,
2147
- showMetrics: resolvedShowMetrics,
2356
+ showMetricsButton: metricsProvided,
2357
+ metricsPanel: resolvedShowMetrics && globalMetricsContent ? renderMetricsPanel(globalMetricsContent) : null,
2148
2358
  onToggleMetrics: toggleMetrics,
2149
2359
  labels,
2150
2360
  toolbarLeftFlex,
@@ -2152,6 +2362,7 @@ var Kanban = ({
2152
2362
  }
2153
2363
  ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ import_react8.default.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
2154
2364
  };
2365
+ Kanban.displayName = "Kanban";
2155
2366
 
2156
2367
  // src/kanban/KanbanCardActions.jsx
2157
2368
  var import_react9 = __toESM(require("react"));
@@ -2214,5 +2425,14 @@ var KanbanCardActions = ({
2214
2425
  // Annotate the CommonJS export names for ESM import in node:
2215
2426
  0 && (module.exports = {
2216
2427
  Kanban,
2217
- KanbanCardActions
2428
+ KanbanCardActions,
2429
+ UNASSIGNED_LANE_KEY,
2430
+ computeStageCounts,
2431
+ evaluateWip,
2432
+ findNewlyExceededWip,
2433
+ getLaneKey,
2434
+ orderLaneKeys,
2435
+ partitionLanes,
2436
+ resolveLaneLabel,
2437
+ resolveWipLimit
2218
2438
  });