hs-uix 1.5.1 → 1.6.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.
package/dist/kanban.js ADDED
@@ -0,0 +1,1410 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/kanban.js
30
+ var kanban_exports = {};
31
+ __export(kanban_exports, {
32
+ Kanban: () => Kanban,
33
+ KanbanCardActions: () => KanbanCardActions
34
+ });
35
+ module.exports = __toCommonJS(kanban_exports);
36
+
37
+ // packages/kanban/src/Kanban.jsx
38
+ var import_react2 = __toESM(require("react"));
39
+ var import_fuse = __toESM(require("fuse.js"));
40
+
41
+ // src/common-components/StyledText.js
42
+ var import_react = __toESM(require("react"));
43
+ var import_ui_extensions = require("@hubspot/ui-extensions");
44
+
45
+ // src/common-components/svgDefaults.js
46
+ var HS_FONT_FAMILY = '"Lexend Deca", Helvetica, Arial, sans-serif';
47
+ var HS_TEXT_COLOR = "#33475b";
48
+ var HS_SUBTLE_BG = "#F5F8FA";
49
+ var HS_TAG_SUBTLE_BORDER = "#7C98B6";
50
+ var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
51
+ var HS_TAG_FONT_SIZE = 12;
52
+ var HS_TAG_LINE_HEIGHT = 22;
53
+ var HS_TAG_PADDING_X = 8;
54
+ var HS_TAG_PADDING_Y = 0;
55
+ var HS_TAG_BORDER_RADIUS = 0;
56
+ var HS_TAG_BORDER_WIDTH = 1;
57
+
58
+ // src/common-components/StyledText.js
59
+ var VARIANT_PRESETS = {
60
+ bodytext: { fontSize: 14, lineHeight: 24, fontWeight: 400 },
61
+ microcopy: { fontSize: 12, lineHeight: 18, fontWeight: 400 }
62
+ };
63
+ var WEIGHT_ALIASES = {
64
+ bold: 700,
65
+ demibold: 600,
66
+ regular: 400
67
+ };
68
+ var LINE_DECORATION = {
69
+ strikethrough: "line-through",
70
+ underline: "underline"
71
+ };
72
+ var ORIENTATION_ROTATION = {
73
+ horizontal: 0,
74
+ "vertical-up": -90,
75
+ "vertical-down": 90
76
+ };
77
+ var BACKGROUND_PRESETS = {
78
+ tag: {
79
+ color: HS_SUBTLE_BG,
80
+ borderColor: HS_TAG_SUBTLE_BORDER,
81
+ borderWidth: HS_TAG_BORDER_WIDTH,
82
+ radius: HS_TAG_BORDER_RADIUS,
83
+ paddingX: HS_TAG_PADDING_X,
84
+ paddingY: HS_TAG_PADDING_Y,
85
+ height: HS_TAG_LINE_HEIGHT,
86
+ textColor: HS_TAG_TEXT_COLOR,
87
+ fontSize: HS_TAG_FONT_SIZE,
88
+ canvasPaddingX: 0,
89
+ canvasPaddingY: 0
90
+ }
91
+ };
92
+ var TAG_VARIANTS = {
93
+ default: {
94
+ color: HS_SUBTLE_BG,
95
+ borderColor: HS_TAG_SUBTLE_BORDER,
96
+ textColor: HS_TAG_TEXT_COLOR
97
+ },
98
+ success: {
99
+ color: "#E5F8F6",
100
+ borderColor: "#00BDA5",
101
+ textColor: "#00BDA5"
102
+ },
103
+ warning: {
104
+ color: "#FEF8F0",
105
+ borderColor: "#F5C26B",
106
+ textColor: "#D39913"
107
+ },
108
+ error: {
109
+ color: "#FDEDEE",
110
+ borderColor: "#F2545B",
111
+ textColor: "#F2545B"
112
+ },
113
+ danger: {
114
+ color: "#FDEDEE",
115
+ borderColor: "#F2545B",
116
+ textColor: "#F2545B"
117
+ },
118
+ info: {
119
+ color: "#E5F5F8",
120
+ borderColor: "#00A4BD",
121
+ textColor: "#00A4BD"
122
+ }
123
+ };
124
+ var escapeSvgText = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
125
+ var applyTextTransform = (text, transform) => {
126
+ if (!transform || transform === "none") return String(text);
127
+ const s = String(text);
128
+ switch (transform) {
129
+ case "uppercase":
130
+ return s.toUpperCase();
131
+ case "lowercase":
132
+ return s.toLowerCase();
133
+ case "capitalize":
134
+ return s.replace(/\b\w/g, (c) => c.toUpperCase());
135
+ case "sentenceCase":
136
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
137
+ default:
138
+ return s;
139
+ }
140
+ };
141
+ var estimateTextWidth = (text, fontSize) => Math.max(fontSize, Math.round(String(text).length * fontSize * 0.58));
142
+ var resolveBackground = (background) => {
143
+ if (!background) return null;
144
+ const preset = background.preset ? BACKGROUND_PRESETS[background.preset] : null;
145
+ const variant = background.preset === "tag" && background.variant ? TAG_VARIANTS[background.variant] || null : null;
146
+ return {
147
+ ...preset || {},
148
+ ...variant || {},
149
+ ...background
150
+ };
151
+ };
152
+ var buildBackgroundRect = ({ background, x, y, width, height }) => {
153
+ const radius = (background == null ? void 0 : background.radius) ?? 3;
154
+ const fill = (background == null ? void 0 : background.color) ?? "transparent";
155
+ const borderWidth = (background == null ? void 0 : background.borderWidth) ?? 0;
156
+ const borderColor = background == null ? void 0 : background.borderColor;
157
+ if (!borderColor || borderWidth <= 0) {
158
+ return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
159
+ }
160
+ const isTagPreset = (background == null ? void 0 : background.preset) === "tag";
161
+ const fillRect = `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
162
+ const strokeInset = borderWidth / 2;
163
+ const strokeX = x + strokeInset;
164
+ const strokeY = y + strokeInset;
165
+ const strokeW = Math.max(0, width - borderWidth);
166
+ const strokeH = Math.max(0, height - borderWidth);
167
+ return fillRect + `<rect x="${strokeX}" y="${strokeY}" width="${strokeW}" height="${strokeH}" rx="${Math.max(
168
+ 0,
169
+ radius - strokeInset
170
+ )}" fill="none" stroke="${borderColor}" stroke-width="${borderWidth}"${isTagPreset ? ` shape-rendering="crispEdges"` : ""} />`;
171
+ };
172
+ var makeStyledTextDataUri = (text, opts = {}) => {
173
+ const {
174
+ variant = "bodytext",
175
+ format = {},
176
+ orientation = "horizontal",
177
+ color: colorProp = HS_TEXT_COLOR,
178
+ fontFamily = HS_FONT_FAMILY,
179
+ background: backgroundProp = null,
180
+ paddingX: paddingXProp = 4,
181
+ paddingY: paddingYProp = 2,
182
+ width: widthOverride,
183
+ height: heightOverride,
184
+ fontSize: fontSizeOverride
185
+ } = opts;
186
+ const preset = VARIANT_PRESETS[variant] || VARIANT_PRESETS.bodytext;
187
+ const background = resolveBackground(backgroundProp);
188
+ const fontSize = fontSizeOverride ?? (background == null ? void 0 : background.fontSize) ?? preset.fontSize;
189
+ const rawWeight = format.fontWeight;
190
+ const fontWeight = rawWeight ? WEIGHT_ALIASES[rawWeight] ?? rawWeight : preset.fontWeight;
191
+ const fontStyle = format.italic ? "italic" : "normal";
192
+ const textDecoration = LINE_DECORATION[format.lineDecoration] || "none";
193
+ const transformed = applyTextTransform(text, format.textTransform);
194
+ const lineHeight = (background == null ? void 0 : background.height) ?? preset.lineHeight ?? fontSize;
195
+ const color = (background == null ? void 0 : background.textColor) ?? colorProp;
196
+ const paddingX = (background == null ? void 0 : background.canvasPaddingX) ?? paddingXProp;
197
+ const paddingY = (background == null ? void 0 : background.canvasPaddingY) ?? paddingYProp;
198
+ const rotate = typeof orientation === "number" ? orientation : ORIENTATION_ROTATION[orientation] ?? 0;
199
+ const textW = estimateTextWidth(transformed, fontSize);
200
+ let pillW = 0;
201
+ let pillH = 0;
202
+ if (background) {
203
+ const bgPadX = background.paddingX ?? 6;
204
+ const bgPadY = background.paddingY ?? 3;
205
+ pillW = textW + bgPadX * 2;
206
+ pillH = background.height ?? Math.max(lineHeight, fontSize + bgPadY * 2);
207
+ }
208
+ const intrinsicW = (background ? pillW : textW) + paddingX * 2;
209
+ const intrinsicH = (background ? pillH : lineHeight) + paddingY * 2;
210
+ const isOrthoRotation = rotate === 90 || rotate === -90 || rotate === 270;
211
+ const canvasW = widthOverride ?? (isOrthoRotation ? intrinsicH : intrinsicW);
212
+ const canvasH = heightOverride ?? (isOrthoRotation ? intrinsicW : intrinsicH);
213
+ const cx = canvasW / 2;
214
+ const cy = canvasH / 2;
215
+ const rectX = cx - pillW / 2;
216
+ const rectY = cy - pillH / 2;
217
+ const group = (background ? buildBackgroundRect({
218
+ background,
219
+ x: rectX,
220
+ y: rectY,
221
+ width: pillW,
222
+ height: pillH
223
+ }) : "") + `<text x="${cx}" y="${cy}" text-anchor="middle" dominant-baseline="central" font-family="${fontFamily.replace(/"/g, "&quot;")}" font-size="${fontSize}" font-weight="${fontWeight}" font-style="${fontStyle}" text-decoration="${textDecoration}" fill="${color}">${escapeSvgText(transformed)}</text>`;
224
+ const wrapped = rotate ? `<g transform="rotate(${rotate} ${cx} ${cy})">${group}</g>` : group;
225
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${canvasW}" height="${canvasH}">` + wrapped + `</svg>`;
226
+ return {
227
+ src: `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`,
228
+ width: canvasW,
229
+ height: canvasH
230
+ };
231
+ };
232
+
233
+ // packages/kanban/src/Kanban.jsx
234
+ var import_ui_extensions2 = require("@hubspot/ui-extensions");
235
+ var DEFAULT_DENSITY = "compact";
236
+ var DEFAULT_MAX_CARDS = 10;
237
+ var DEFAULT_MAX_EXPANDED = 50;
238
+ var DEFAULT_COLUMN_WIDTH = 350;
239
+ var MIN_COLUMN_WIDTH = 350;
240
+ var DEFAULT_FILTER_INLINE_LIMIT = 4;
241
+ var DEFAULT_SEARCH_DEBOUNCE = 250;
242
+ var DEFAULT_TITLE_TRUNCATE = 60;
243
+ var applyTruncate = (value, truncate, fallback) => {
244
+ if (truncate === false) return value;
245
+ if (typeof value !== "string") return value;
246
+ const limit = typeof truncate === "number" ? truncate : truncate === true ? fallback || DEFAULT_TITLE_TRUNCATE : fallback || DEFAULT_TITLE_TRUNCATE;
247
+ if (value.length <= limit) return value;
248
+ return value.slice(0, limit).trimEnd() + "\u2026";
249
+ };
250
+ var DEFAULT_LABELS = {
251
+ search: "Search cards...",
252
+ // Only the total is surfaced — callers asked for a single headline number
253
+ // rather than a "loaded / total" fraction. Fall back to the bare label when
254
+ // no total is known.
255
+ showMore: (_shown, total) => total ? `Show more (${total})` : "Show more",
256
+ showLess: "Show less",
257
+ loadMore: (_loaded, total) => total ? `Load more (${total})` : "Load more",
258
+ loadingMore: "Loading...",
259
+ retryLoadMore: "Retry",
260
+ emptyColumn: "\u2014",
261
+ emptyTitle: "No cards",
262
+ emptyMessage: "Nothing matches the current filters.",
263
+ loading: "Loading board...",
264
+ errorTitle: "Something went wrong.",
265
+ errorMessage: "An error occurred while loading data.",
266
+ cardCount: (n) => String(n),
267
+ moveTo: "Move",
268
+ clearAll: "Clear all",
269
+ selectAll: (count, label) => `Select all ${count} ${label}`,
270
+ deselectAll: "Deselect all",
271
+ selected: (count, label) => `${count}\xA0${label}\xA0selected`,
272
+ filtersButton: "Filters",
273
+ dateFrom: "From",
274
+ dateTo: "To",
275
+ sortButton: "Sort",
276
+ sortAscending: "Ascending",
277
+ sortDescending: "Descending",
278
+ metricsButton: "Metrics"
279
+ };
280
+ var makeRotatedTagDataUri = (label) => makeStyledTextDataUri(label, {
281
+ variant: "microcopy",
282
+ format: { fontWeight: "demibold" },
283
+ orientation: "vertical-down",
284
+ background: { preset: "tag" }
285
+ });
286
+ var makeRotatedLabelDataUri = (label) => makeStyledTextDataUri(label, {
287
+ variant: "bodytext",
288
+ format: { fontWeight: "demibold" },
289
+ orientation: "vertical-down"
290
+ });
291
+ var getEmptyFilterValue = (filter) => {
292
+ const type = filter.type || "select";
293
+ if (type === "multiselect") return [];
294
+ if (type === "dateRange") return { from: null, to: null };
295
+ return "";
296
+ };
297
+ var isFilterActive = (filter, value) => {
298
+ const type = filter.type || "select";
299
+ if (type === "multiselect") return Array.isArray(value) && value.length > 0;
300
+ if (type === "dateRange") return value && (value.from || value.to);
301
+ return !!value;
302
+ };
303
+ var formatDateChip = (dateObj) => {
304
+ if (!dateObj) return "";
305
+ const { year, month, date } = dateObj;
306
+ return new Intl.DateTimeFormat("en-US", {
307
+ month: "short",
308
+ day: "numeric",
309
+ year: "numeric"
310
+ }).format(new Date(year, month, date));
311
+ };
312
+ var dateToTimestamp = (dateObj) => {
313
+ if (!dateObj) return null;
314
+ return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
315
+ };
316
+ var toStableKey = (value) => {
317
+ try {
318
+ return JSON.stringify(value);
319
+ } catch (_error) {
320
+ return String(value);
321
+ }
322
+ };
323
+ var canStageReceiveRow = (stage, row, canMove) => {
324
+ if (!stage) return false;
325
+ if (typeof canMove === "function" && !canMove(row, stage.value)) return false;
326
+ if (typeof stage.canEnter === "function" && !stage.canEnter(row)) return false;
327
+ return true;
328
+ };
329
+ var isFieldDirectionSortOption = (option) => !!(option && option.field && (option.direction === "asc" || option.direction === "desc"));
330
+ var resolveDividers = (cardDividers, density) => {
331
+ if (cardDividers === true) {
332
+ return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
333
+ }
334
+ if (cardDividers === false) {
335
+ return { afterTitle: false, afterSubtitle: false, afterBody: false, afterFooter: false };
336
+ }
337
+ if (cardDividers && typeof cardDividers === "object") {
338
+ return {
339
+ afterTitle: cardDividers.afterTitle ?? false,
340
+ afterSubtitle: cardDividers.afterSubtitle ?? false,
341
+ afterBody: cardDividers.afterBody ?? false,
342
+ afterFooter: cardDividers.afterFooter ?? false
343
+ };
344
+ }
345
+ if (density === "comfortable") {
346
+ return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
347
+ }
348
+ return { afterTitle: false, afterSubtitle: false, afterBody: true, afterFooter: false };
349
+ };
350
+ var partitionFields = (cardFields) => {
351
+ const buckets = { title: null, subtitle: null, meta: [], body: [], footer: [] };
352
+ for (const field of cardFields || []) {
353
+ const placement = field.placement || "body";
354
+ if (placement === "title" && !buckets.title) buckets.title = field;
355
+ else if (placement === "subtitle" && !buckets.subtitle) buckets.subtitle = field;
356
+ else if (placement === "meta") buckets.meta.push(field);
357
+ else if (placement === "footer") buckets.footer.push(field);
358
+ else buckets.body.push(field);
359
+ }
360
+ return buckets;
361
+ };
362
+ var resolveFieldValue = (field, row) => {
363
+ if (!field) return void 0;
364
+ if (field.field && row && Object.prototype.hasOwnProperty.call(row, field.field)) {
365
+ return row[field.field];
366
+ }
367
+ return void 0;
368
+ };
369
+ var resolveHref = (href, row) => {
370
+ if (!href) return null;
371
+ if (typeof href === "function") return href(row);
372
+ return href;
373
+ };
374
+ var KanbanCard = ({
375
+ row,
376
+ rowId,
377
+ stage,
378
+ stages,
379
+ fields,
380
+ density,
381
+ dividers,
382
+ bodyAs,
383
+ maxBodyLines,
384
+ stageControl,
385
+ stageControlPlacement,
386
+ canMove,
387
+ onStageChangeRequest,
388
+ isChanging,
389
+ selectable,
390
+ selected,
391
+ onToggleSelect,
392
+ labels
393
+ }) => {
394
+ const titleHref = fields.title ? resolveHref(fields.title.href, row) : null;
395
+ const rawTitleValue = fields.title ? fields.title.render ? fields.title.render(resolveFieldValue(fields.title, row), row) : resolveFieldValue(fields.title, row) : null;
396
+ const titleValue = fields.title && typeof rawTitleValue === "string" ? applyTruncate(rawTitleValue, fields.title.truncate, DEFAULT_TITLE_TRUNCATE) : rawTitleValue;
397
+ const titleNode = titleHref ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { href: titleHref }, titleValue) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, titleValue);
398
+ const metaNodes = fields.meta.filter((f) => !f.visible || f.visible(row)).map((f) => {
399
+ const val = resolveFieldValue(f, row);
400
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: f.field || f.label, variant: "microcopy" }, f.render ? f.render(val, row) : val);
401
+ });
402
+ const showSubtitle = density === "comfortable" && fields.subtitle;
403
+ const subtitleNode = showSubtitle ? fields.subtitle.render ? fields.subtitle.render(resolveFieldValue(fields.subtitle, row), row) : resolveFieldValue(fields.subtitle, row) : null;
404
+ const bodyFields = fields.body.filter((f) => !f.visible || f.visible(row)).slice(0, maxBodyLines);
405
+ const footerFields = fields.footer.filter((f) => !f.visible || f.visible(row));
406
+ const footerAlerts = footerFields.slice(0, -1);
407
+ const footerActionsField = footerFields.length > 0 ? footerFields[footerFields.length - 1] : null;
408
+ const renderFooterField = (f, idx) => {
409
+ const val = resolveFieldValue(f, row);
410
+ const rendered = f.render ? f.render(val, row) : val;
411
+ const key = f.key || f.field || f.label || `footer-${idx}`;
412
+ return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key }, rendered);
413
+ };
414
+ const stageControlNode = stageControl === "none" ? null : /* @__PURE__ */ import_react2.default.createElement(
415
+ StageControl,
416
+ {
417
+ row,
418
+ rowId,
419
+ currentStage: stage,
420
+ stages,
421
+ canMove,
422
+ isChanging,
423
+ mode: stageControl,
424
+ onStageChangeRequest,
425
+ labels
426
+ }
427
+ );
428
+ const titleRow = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, titleNode), selectable ? /* @__PURE__ */ import_react2.default.createElement(
429
+ import_ui_extensions2.Checkbox,
430
+ {
431
+ name: `kanban-select-${rowId}`,
432
+ checked: selected,
433
+ onChange: () => onToggleSelect(rowId)
434
+ }
435
+ ) : null);
436
+ const metaRow = metaNodes.length === 0 ? null : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "end", align: "center", gap: "xs" }, metaNodes);
437
+ const bodyRow = bodyFields.length === 0 ? null : bodyAs === "descriptionList" ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DescriptionList, { direction: "row" }, bodyFields.map((f, idx) => {
438
+ const val = resolveFieldValue(f, row);
439
+ const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
440
+ const key = f.key || f.field || f.label || `body-${idx}`;
441
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DescriptionListItem, { key, label: f.label || "" }, rendered);
442
+ })) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "flush" }, bodyFields.map((f, idx) => {
443
+ const val = resolveFieldValue(f, row);
444
+ const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
445
+ const key = f.key || f.field || f.label || `body-${idx}`;
446
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key, variant: "microcopy" }, f.label ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { inline: true, variant: "microcopy" }, `${f.label}: `) : null, rendered);
447
+ }));
448
+ const footerAlertsNode = footerAlerts.length === 0 ? null : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, footerAlerts.map((f, idx) => renderFooterField(f, idx)));
449
+ const footerActionsNode = footerActionsField ? renderFooterField(footerActionsField, footerFields.length - 1) : null;
450
+ const inlineStageControl = stageControlPlacement === "inline" ? stageControlNode : null;
451
+ const separateRowStageControl = stageControlPlacement === "separateRow" ? stageControlNode : null;
452
+ const footerMainRow = inlineStageControl || footerActionsNode ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "between", align: "start", gap: "sm" }, inlineStageControl ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { alignSelf: "center" }, inlineStageControl) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, null), footerActionsNode ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "end", align: "start" }, footerActionsNode)) : null) : null;
453
+ const footerRow = !footerAlertsNode && !footerMainRow ? null : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, footerAlertsNode, footerMainRow);
454
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: density === "compact" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: density === "compact" ? "xs" : "sm" }, titleRow, dividers.afterTitle && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null) : null, subtitleNode ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, subtitleNode) : null, dividers.afterSubtitle && subtitleNode && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null) : null, metaRow, bodyRow, dividers.afterBody && bodyRow && (footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null) : null, footerRow, dividers.afterFooter && footerRow && separateRowStageControl ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null) : null, separateRowStageControl));
455
+ };
456
+ var StageControl = ({
457
+ row,
458
+ rowId,
459
+ currentStage,
460
+ stages,
461
+ canMove,
462
+ isChanging,
463
+ mode,
464
+ onStageChangeRequest,
465
+ labels
466
+ }) => {
467
+ if (isChanging) {
468
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.LoadingSpinner, { size: "xs" });
469
+ }
470
+ const availableStages = (stages || []).filter(
471
+ (stage) => stage.value === currentStage.value || canStageReceiveRow(stage, row, canMove)
472
+ );
473
+ if (mode === "menu") {
474
+ const targetStages = availableStages.filter((stage) => stage.value !== currentStage.value);
475
+ if (targetStages.length === 0) {
476
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "extra-small", disabled: true }, labels.moveTo);
477
+ }
478
+ return /* @__PURE__ */ import_react2.default.createElement(
479
+ import_ui_extensions2.Dropdown,
480
+ {
481
+ variant: "transparent",
482
+ buttonText: labels.moveTo,
483
+ buttonSize: "xs"
484
+ },
485
+ targetStages.map((stage) => /* @__PURE__ */ import_react2.default.createElement(
486
+ import_ui_extensions2.Dropdown.ButtonItem,
487
+ {
488
+ key: stage.value,
489
+ onClick: () => onStageChangeRequest(row, stage.value, currentStage.value)
490
+ },
491
+ stage.shortLabel || stage.label
492
+ ))
493
+ );
494
+ }
495
+ return /* @__PURE__ */ import_react2.default.createElement(
496
+ import_ui_extensions2.Select,
497
+ {
498
+ name: `stage-${rowId}`,
499
+ label: "",
500
+ value: currentStage.value,
501
+ onChange: (val) => {
502
+ if (val !== currentStage.value) onStageChangeRequest(row, val, currentStage.value);
503
+ },
504
+ options: availableStages.map((stage) => ({
505
+ label: stage.shortLabel || stage.label,
506
+ value: stage.value
507
+ }))
508
+ }
509
+ );
510
+ };
511
+ var KanbanColumn = ({
512
+ stage,
513
+ rows,
514
+ bucketCount,
515
+ totalCount,
516
+ hasMore,
517
+ loading,
518
+ error,
519
+ onLoadMore,
520
+ expanded,
521
+ onToggleExpanded,
522
+ collapsed,
523
+ onToggleCollapsed,
524
+ columnFooter,
525
+ countDisplay,
526
+ labels,
527
+ children
528
+ }) => {
529
+ const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
530
+ const countNode = countDisplay === "text" ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tag, { variant: "subtle" }, countLabel);
531
+ if (collapsed) {
532
+ const rotated = makeRotatedLabelDataUri(stage.label);
533
+ const rotatedCount = countDisplay === "none" ? null : makeRotatedTagDataUri(countLabel);
534
+ const stageIdentifier = stage.icon ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: stage.icon, size: "sm", screenReaderText: stage.label }) : /* @__PURE__ */ import_react2.default.createElement(
535
+ import_ui_extensions2.Image,
536
+ {
537
+ src: rotated.src,
538
+ width: rotated.width,
539
+ height: rotated.height,
540
+ alt: stage.label
541
+ }
542
+ );
543
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs", align: "center" }, /* @__PURE__ */ import_react2.default.createElement(
544
+ import_ui_extensions2.Button,
545
+ {
546
+ variant: "transparent",
547
+ size: "sm",
548
+ onClick: onToggleCollapsed,
549
+ tooltip: `Expand ${stage.label}`
550
+ },
551
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "right", size: "sm", screenReaderText: `Expand ${stage.label}` })
552
+ ), stageIdentifier, rotatedCount ? /* @__PURE__ */ import_react2.default.createElement(
553
+ import_ui_extensions2.Image,
554
+ {
555
+ src: rotatedCount.src,
556
+ width: rotatedCount.width,
557
+ height: rotatedCount.height,
558
+ alt: `${bucketCount} items`
559
+ }
560
+ ) : null));
561
+ }
562
+ const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
563
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null), children, error ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
564
+ };
565
+ var SortModalBody = ({ sortOptions, sortValue, onSortChange, labels }) => {
566
+ const hasFieldDirection = Array.isArray(sortOptions) && sortOptions.length > 0 && sortOptions.every(isFieldDirectionSortOption);
567
+ if (!hasFieldDirection) {
568
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, sortOptions.map((opt) => /* @__PURE__ */ import_react2.default.createElement(
569
+ import_ui_extensions2.Button,
570
+ {
571
+ key: opt.value,
572
+ variant: sortValue === opt.value ? "primary" : "secondary",
573
+ onClick: () => onSortChange(opt.value)
574
+ },
575
+ opt.label
576
+ )));
577
+ }
578
+ const currentOption = sortOptions.find((o) => o.value === sortValue) || sortOptions[0];
579
+ const currentField = currentOption.field;
580
+ const currentDirection = currentOption.direction;
581
+ const uniqueFields = [];
582
+ const seen = /* @__PURE__ */ new Set();
583
+ for (const opt of sortOptions) {
584
+ if (!seen.has(opt.field)) {
585
+ seen.add(opt.field);
586
+ uniqueFields.push({ value: opt.field, label: opt.fieldLabel || opt.field });
587
+ }
588
+ }
589
+ const fieldOpts = sortOptions.filter((o) => o.field === currentField);
590
+ const ascOption = fieldOpts.find((o) => o.direction === "asc");
591
+ const descOption = fieldOpts.find((o) => o.direction === "desc");
592
+ const handleFieldChange = (newField) => {
593
+ const next = sortOptions.find((o) => o.field === newField && o.direction === currentDirection) || sortOptions.find((o) => o.field === newField && o.direction === "desc") || sortOptions.find((o) => o.field === newField);
594
+ if (next) onSortChange(next.value);
595
+ };
596
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react2.default.createElement(
597
+ import_ui_extensions2.Select,
598
+ {
599
+ name: "kanban-sort-field",
600
+ label: "",
601
+ value: currentField,
602
+ onChange: handleFieldChange,
603
+ options: uniqueFields
604
+ }
605
+ ), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", gap: "flush" }, descOption ? /* @__PURE__ */ import_react2.default.createElement(
606
+ import_ui_extensions2.Button,
607
+ {
608
+ variant: currentDirection === "desc" ? "primary" : "secondary",
609
+ onClick: () => onSortChange(descOption.value)
610
+ },
611
+ labels.sortDescending
612
+ ) : null, ascOption ? /* @__PURE__ */ import_react2.default.createElement(
613
+ import_ui_extensions2.Button,
614
+ {
615
+ variant: currentDirection === "asc" ? "primary" : "secondary",
616
+ onClick: () => onSortChange(ascOption.value)
617
+ },
618
+ labels.sortAscending
619
+ ) : null));
620
+ };
621
+ var renderFilterControl = ({ filter, value, onChange, labels }) => {
622
+ const type = filter.type || "select";
623
+ if (type === "multiselect") {
624
+ return /* @__PURE__ */ import_react2.default.createElement(
625
+ import_ui_extensions2.MultiSelect,
626
+ {
627
+ key: filter.name,
628
+ name: `kanban-filter-${filter.name}`,
629
+ label: "",
630
+ placeholder: filter.placeholder || "All",
631
+ value: value || [],
632
+ onChange: (val) => onChange(filter.name, val),
633
+ options: filter.options
634
+ }
635
+ );
636
+ }
637
+ if (type === "dateRange") {
638
+ const rangeVal = value || { from: null, to: null };
639
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: filter.name, direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(
640
+ import_ui_extensions2.DateInput,
641
+ {
642
+ size: "sm",
643
+ name: `kanban-filter-${filter.name}-from`,
644
+ label: "",
645
+ placeholder: labels.dateFrom,
646
+ format: "medium",
647
+ value: rangeVal.from,
648
+ onChange: (val) => onChange(filter.name, { ...rangeVal, from: val })
649
+ }
650
+ ), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "dataSync", size: "sm" }), /* @__PURE__ */ import_react2.default.createElement(
651
+ import_ui_extensions2.DateInput,
652
+ {
653
+ size: "sm",
654
+ name: `kanban-filter-${filter.name}-to`,
655
+ label: "",
656
+ placeholder: labels.dateTo,
657
+ format: "medium",
658
+ value: rangeVal.to,
659
+ onChange: (val) => onChange(filter.name, { ...rangeVal, to: val })
660
+ }
661
+ ));
662
+ }
663
+ return /* @__PURE__ */ import_react2.default.createElement(
664
+ import_ui_extensions2.Select,
665
+ {
666
+ key: filter.name,
667
+ name: `kanban-filter-${filter.name}`,
668
+ variant: "transparent",
669
+ placeholder: filter.placeholder || "All",
670
+ value,
671
+ onChange: (val) => onChange(filter.name, val),
672
+ options: [
673
+ { label: filter.placeholder || "All", value: "" },
674
+ ...filter.options
675
+ ]
676
+ }
677
+ );
678
+ };
679
+ var renderMetricsPanel = (metrics) => {
680
+ if (!metrics) return null;
681
+ if (!Array.isArray(metrics)) return metrics;
682
+ if (metrics.length === 0) return null;
683
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Statistics, null, metrics.map((m, i) => /* @__PURE__ */ import_react2.default.createElement(
684
+ import_ui_extensions2.StatisticsItem,
685
+ {
686
+ key: m.id || m.label || i,
687
+ label: m.label,
688
+ number: m.number != null ? String(m.number) : ""
689
+ },
690
+ m.trend ? /* @__PURE__ */ import_react2.default.createElement(
691
+ import_ui_extensions2.StatisticsTrend,
692
+ {
693
+ direction: m.trend.direction || "increase",
694
+ value: m.trend.value,
695
+ color: m.trend.color
696
+ }
697
+ ) : null
698
+ )));
699
+ };
700
+ var KanbanToolbar = ({
701
+ showSearch,
702
+ searchValue,
703
+ searchPlaceholder,
704
+ onSearchChange,
705
+ filters,
706
+ filterValues,
707
+ onFilterChange,
708
+ filterInlineLimit,
709
+ showFilterBadges,
710
+ showClearFiltersButton,
711
+ activeChips,
712
+ onFilterRemove,
713
+ sortOptions,
714
+ sortValue,
715
+ onSortChange,
716
+ metrics,
717
+ showMetrics,
718
+ onToggleMetrics,
719
+ labels
720
+ }) => {
721
+ const [showMoreFilters, setShowMoreFilters] = (0, import_react2.useState)(false);
722
+ const inlineFilters = (filters || []).slice(0, filterInlineLimit);
723
+ const overflowFilters = (filters || []).slice(filterInlineLimit);
724
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 3 }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch ? /* @__PURE__ */ import_react2.default.createElement(
725
+ import_ui_extensions2.SearchInput,
726
+ {
727
+ name: "kanban-search",
728
+ placeholder: searchPlaceholder,
729
+ value: searchValue,
730
+ onChange: onSearchChange
731
+ }
732
+ ) : null, inlineFilters.map(
733
+ (filter) => renderFilterControl({
734
+ filter,
735
+ value: filterValues[filter.name],
736
+ onChange: onFilterChange,
737
+ labels
738
+ })
739
+ ), overflowFilters.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(
740
+ import_ui_extensions2.Button,
741
+ {
742
+ variant: "transparent",
743
+ size: "small",
744
+ onClick: () => setShowMoreFilters((prev) => !prev)
745
+ },
746
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "filter", size: "sm" }),
747
+ " ",
748
+ labels.filtersButton
749
+ ) : null), showMoreFilters && overflowFilters.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, overflowFilters.map(
750
+ (filter) => renderFilterControl({
751
+ filter,
752
+ value: filterValues[filter.name],
753
+ onChange: onFilterChange,
754
+ labels
755
+ })
756
+ )) : null, activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges ? activeChips.map((chip) => /* @__PURE__ */ import_react2.default.createElement(
757
+ import_ui_extensions2.Tag,
758
+ {
759
+ key: chip.key,
760
+ variant: "default",
761
+ onDelete: () => onFilterRemove(chip.key)
762
+ },
763
+ chip.label
764
+ )) : null, showClearFiltersButton ? /* @__PURE__ */ import_react2.default.createElement(
765
+ import_ui_extensions2.Button,
766
+ {
767
+ variant: "transparent",
768
+ size: "extra-small",
769
+ onClick: () => onFilterRemove("all")
770
+ },
771
+ labels.clearAll
772
+ ) : null) : null)), (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1, alignSelf: "start" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", justify: "end" }, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(
773
+ import_ui_extensions2.Button,
774
+ {
775
+ variant: "secondary",
776
+ size: "small",
777
+ overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Modal, { id: "kanban-sort-modal", title: labels.sortButton }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.ModalBody, null, /* @__PURE__ */ import_react2.default.createElement(
778
+ SortModalBody,
779
+ {
780
+ sortOptions,
781
+ sortValue,
782
+ onSortChange,
783
+ labels
784
+ }
785
+ )))
786
+ },
787
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "sortAmtDesc", size: "sm" }),
788
+ " ",
789
+ labels.sortButton
790
+ ) : null, metrics ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "reports", size: "sm" }), " ", labels.metricsButton) : null)) : null), showMetrics && metrics ? renderMetricsPanel(metrics) : null);
791
+ };
792
+ var DefaultSelectionBar = ({
793
+ selectedIds,
794
+ selectedCount,
795
+ displayCount,
796
+ countLabel,
797
+ allSelected,
798
+ onSelectAll,
799
+ onDeselectAll,
800
+ selectionActions,
801
+ labels
802
+ }) => {
803
+ const pluralForCount = (n) => countLabel(n);
804
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", justify: "between", gap: "small" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof labels.selected === "function" ? labels.selected(selectedCount, pluralForCount(selectedCount)) : `${selectedCount} selected`), !allSelected ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "extra-small", onClick: onSelectAll }, typeof labels.selectAll === "function" ? labels.selectAll(displayCount, pluralForCount(displayCount)) : labels.selectAll) : null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "extra-small", onClick: onDeselectAll }, labels.deselectAll)), (selectionActions || []).length > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", gap: "extra-small" }, selectionActions.map((action, i) => /* @__PURE__ */ import_react2.default.createElement(
805
+ import_ui_extensions2.Button,
806
+ {
807
+ key: action.key || action.label || i,
808
+ variant: action.variant || "transparent",
809
+ size: "extra-small",
810
+ onClick: () => action.onClick([...selectedIds])
811
+ },
812
+ action.icon ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: action.icon, size: "sm" }) : null,
813
+ " ",
814
+ action.label
815
+ ))) : null));
816
+ };
817
+ var Kanban = ({
818
+ // --- Data ---
819
+ data = [],
820
+ stages = [],
821
+ groupBy = "status",
822
+ rowIdField = "id",
823
+ // --- Card rendering ---
824
+ renderCard,
825
+ cardFields,
826
+ cardDensity = DEFAULT_DENSITY,
827
+ cardDividers,
828
+ cardBodyAs = "descriptionList",
829
+ maxBodyLines,
830
+ maxCardsPerColumn = DEFAULT_MAX_CARDS,
831
+ maxCardsExpanded = DEFAULT_MAX_EXPANDED,
832
+ expandedStages,
833
+ onExpandedStagesChange,
834
+ // --- Per-stage pagination ---
835
+ stageMeta,
836
+ onLoadMore,
837
+ // --- Selection ---
838
+ selectable = false,
839
+ selectedIds,
840
+ onSelectionChange,
841
+ selectionActions,
842
+ recordLabel,
843
+ selectionResetKey,
844
+ resetSelectionOnQueryChange = true,
845
+ showSelectionBar = true,
846
+ renderSelectionBar,
847
+ // --- Stage transitions ---
848
+ stageControl,
849
+ stageControlPlacement,
850
+ onStageChange,
851
+ isStageChanging,
852
+ canMove,
853
+ // --- Toolbar ---
854
+ showSearch = true,
855
+ searchFields,
856
+ searchPlaceholder,
857
+ searchDebounce = DEFAULT_SEARCH_DEBOUNCE,
858
+ fuzzySearch = false,
859
+ fuzzyOptions,
860
+ filters,
861
+ filterInlineLimit = DEFAULT_FILTER_INLINE_LIMIT,
862
+ showFilterBadges = true,
863
+ showClearFiltersButton = true,
864
+ sortOptions,
865
+ defaultSort,
866
+ sort,
867
+ onSortChange,
868
+ // --- Column level ---
869
+ columnFooter,
870
+ columnWidth = DEFAULT_COLUMN_WIDTH,
871
+ countDisplay = "tag",
872
+ collapsedStages,
873
+ onCollapsedStagesChange,
874
+ // --- Metrics panel ---
875
+ metrics,
876
+ // Array of stat items or a ReactNode for full override
877
+ showMetrics: controlledShowMetrics,
878
+ onMetricsToggle,
879
+ // --- State (controlled) ---
880
+ searchValue,
881
+ onSearchChange,
882
+ filterValues,
883
+ onFilterChange,
884
+ onParamsChange,
885
+ loading = false,
886
+ error,
887
+ // --- Labels ---
888
+ labels: labelsProp,
889
+ renderEmptyState,
890
+ renderLoadingState,
891
+ renderErrorState
892
+ }) => {
893
+ var _a;
894
+ const labels = (0, import_react2.useMemo)(() => ({ ...DEFAULT_LABELS, ...labelsProp || {} }), [labelsProp]);
895
+ const [internalSearch, setInternalSearch] = (0, import_react2.useState)(searchValue != null ? searchValue : "");
896
+ const [internalFilters, setInternalFilters] = (0, import_react2.useState)(() => {
897
+ const init = {};
898
+ (filters || []).forEach((f) => {
899
+ init[f.name] = getEmptyFilterValue(f);
900
+ });
901
+ return init;
902
+ });
903
+ const [internalSort, setInternalSort] = (0, import_react2.useState)(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
904
+ const [internalCollapsed, setInternalCollapsed] = (0, import_react2.useState)([]);
905
+ const [internalExpanded, setInternalExpanded] = (0, import_react2.useState)([]);
906
+ const [internalSelection, setInternalSelection] = (0, import_react2.useState)([]);
907
+ const [internalShowMetrics, setInternalShowMetrics] = (0, import_react2.useState)(false);
908
+ const [transitionPrompts, setTransitionPrompts] = (0, import_react2.useState)({});
909
+ const selectionResetRef = (0, import_react2.useRef)("");
910
+ const resolvedShowMetrics = controlledShowMetrics != null ? controlledShowMetrics : internalShowMetrics;
911
+ const toggleMetrics = (0, import_react2.useCallback)(() => {
912
+ const next = !resolvedShowMetrics;
913
+ if (onMetricsToggle) onMetricsToggle(next);
914
+ if (controlledShowMetrics == null) setInternalShowMetrics(next);
915
+ }, [resolvedShowMetrics, onMetricsToggle, controlledShowMetrics]);
916
+ const effectiveColumnWidth = Math.max(MIN_COLUMN_WIDTH, columnWidth || DEFAULT_COLUMN_WIDTH);
917
+ const resolvedSearch = searchValue != null ? searchValue : internalSearch;
918
+ const resolvedFilters = filterValues != null ? filterValues : internalFilters;
919
+ const resolvedSort = sort != null ? sort : internalSort;
920
+ const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
921
+ const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
922
+ const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
923
+ const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
924
+ const stagesByValue = (0, import_react2.useMemo)(() => {
925
+ const map = {};
926
+ for (const stage of stages || []) {
927
+ map[stage.value] = stage;
928
+ }
929
+ return map;
930
+ }, [stages]);
931
+ const fireParamsChange = (0, import_react2.useCallback)((overrides = {}) => {
932
+ if (!onParamsChange) return;
933
+ onParamsChange({
934
+ search: overrides.search != null ? overrides.search : resolvedSearch,
935
+ filters: overrides.filters != null ? overrides.filters : resolvedFilters,
936
+ sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
937
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
938
+ });
939
+ }, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
940
+ const debounceRef = (0, import_react2.useRef)(null);
941
+ (0, import_react2.useEffect)(() => () => {
942
+ if (debounceRef.current) clearTimeout(debounceRef.current);
943
+ }, []);
944
+ const handleSearch = (0, import_react2.useCallback)(
945
+ (val) => {
946
+ if (searchValue == null) setInternalSearch(val);
947
+ const dispatch = () => {
948
+ if (onSearchChange) onSearchChange(val);
949
+ fireParamsChange({ search: val });
950
+ };
951
+ if (searchDebounce > 0) {
952
+ if (debounceRef.current) clearTimeout(debounceRef.current);
953
+ debounceRef.current = setTimeout(dispatch, searchDebounce);
954
+ } else {
955
+ dispatch();
956
+ }
957
+ },
958
+ [fireParamsChange, onSearchChange, searchValue, searchDebounce]
959
+ );
960
+ const handleFilter = (0, import_react2.useCallback)(
961
+ (name, val) => {
962
+ const next = { ...resolvedFilters, [name]: val };
963
+ if (filterValues == null) setInternalFilters(next);
964
+ if (onFilterChange) onFilterChange(next);
965
+ fireParamsChange({ filters: next });
966
+ },
967
+ [fireParamsChange, onFilterChange, filterValues, resolvedFilters]
968
+ );
969
+ const handleFilterRemove = (0, import_react2.useCallback)(
970
+ (key) => {
971
+ if (key === "all") {
972
+ const cleared = {};
973
+ (filters || []).forEach((f) => {
974
+ cleared[f.name] = getEmptyFilterValue(f);
975
+ });
976
+ if (filterValues == null) setInternalFilters(cleared);
977
+ if (onFilterChange) onFilterChange(cleared);
978
+ fireParamsChange({ filters: cleared });
979
+ return;
980
+ }
981
+ const filter = (filters || []).find((f) => f.name === key);
982
+ const emptyVal = filter ? getEmptyFilterValue(filter) : "";
983
+ const next = { ...resolvedFilters, [key]: emptyVal };
984
+ if (filterValues == null) setInternalFilters(next);
985
+ if (onFilterChange) onFilterChange(next);
986
+ fireParamsChange({ filters: next });
987
+ },
988
+ [filters, filterValues, fireParamsChange, onFilterChange, resolvedFilters]
989
+ );
990
+ const handleSort = (0, import_react2.useCallback)(
991
+ (val) => {
992
+ if (onSortChange) onSortChange(val);
993
+ if (sort == null) setInternalSort(val);
994
+ fireParamsChange({ sort: val });
995
+ },
996
+ [fireParamsChange, onSortChange, sort]
997
+ );
998
+ const handleCollapsed = (0, import_react2.useCallback)(
999
+ (stageValue) => {
1000
+ const next = resolvedCollapsed.includes(stageValue) ? resolvedCollapsed.filter((v) => v !== stageValue) : [...resolvedCollapsed, stageValue];
1001
+ if (onCollapsedStagesChange) onCollapsedStagesChange(next);
1002
+ if (collapsedStages == null) setInternalCollapsed(next);
1003
+ fireParamsChange({ collapsedStages: next });
1004
+ },
1005
+ [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
1006
+ );
1007
+ const handleExpanded = (0, import_react2.useCallback)(
1008
+ (stageValue) => {
1009
+ const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
1010
+ if (onExpandedStagesChange) onExpandedStagesChange(next);
1011
+ if (expandedStages == null) setInternalExpanded(next);
1012
+ },
1013
+ [resolvedExpanded, expandedStages, onExpandedStagesChange]
1014
+ );
1015
+ const handleToggleSelect = (0, import_react2.useCallback)(
1016
+ (rowId) => {
1017
+ const next = resolvedSelection.includes(rowId) ? resolvedSelection.filter((id) => id !== rowId) : [...resolvedSelection, rowId];
1018
+ if (onSelectionChange) onSelectionChange(next);
1019
+ if (selectedIds == null) setInternalSelection(next);
1020
+ },
1021
+ [resolvedSelection, selectedIds, onSelectionChange]
1022
+ );
1023
+ const clearTransitionPrompt = (0, import_react2.useCallback)((rowId) => {
1024
+ setTransitionPrompts((prev) => {
1025
+ if (!Object.prototype.hasOwnProperty.call(prev, rowId)) return prev;
1026
+ const next = { ...prev };
1027
+ delete next[rowId];
1028
+ return next;
1029
+ });
1030
+ }, []);
1031
+ const commitStageChange = (0, import_react2.useCallback)(
1032
+ (row, newStage, oldStage, result) => {
1033
+ clearTransitionPrompt(row[rowIdField]);
1034
+ if (onStageChange) onStageChange(row, newStage, oldStage, result);
1035
+ },
1036
+ [clearTransitionPrompt, onStageChange, rowIdField]
1037
+ );
1038
+ const selectionQueryKey = (0, import_react2.useMemo)(() => {
1039
+ if (!resetSelectionOnQueryChange) return "";
1040
+ return toStableKey({
1041
+ search: resolvedSearch,
1042
+ filters: resolvedFilters,
1043
+ sort: resolvedSort || null
1044
+ });
1045
+ }, [resetSelectionOnQueryChange, resolvedFilters, resolvedSearch, resolvedSort]);
1046
+ const combinedSelectionResetKey = (0, import_react2.useMemo)(
1047
+ () => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
1048
+ [selectionQueryKey, selectionResetKey]
1049
+ );
1050
+ (0, import_react2.useEffect)(() => {
1051
+ if (!selectable || selectedIds != null) {
1052
+ selectionResetRef.current = combinedSelectionResetKey;
1053
+ return;
1054
+ }
1055
+ if (selectionResetRef.current && selectionResetRef.current !== combinedSelectionResetKey) {
1056
+ setInternalSelection([]);
1057
+ }
1058
+ selectionResetRef.current = combinedSelectionResetKey;
1059
+ }, [combinedSelectionResetKey, selectable, selectedIds]);
1060
+ const getStageFor = (0, import_react2.useCallback)(
1061
+ (row) => {
1062
+ if (typeof groupBy === "function") return groupBy(row);
1063
+ return row[groupBy];
1064
+ },
1065
+ [groupBy]
1066
+ );
1067
+ const filteredData = (0, import_react2.useMemo)(() => {
1068
+ let result = data;
1069
+ for (const filter of filters || []) {
1070
+ const val = resolvedFilters[filter.name];
1071
+ if (!isFilterActive(filter, val)) continue;
1072
+ const type = filter.type || "select";
1073
+ if (filter.filterFn) {
1074
+ result = result.filter((row) => filter.filterFn(row, val));
1075
+ } else if (type === "multiselect") {
1076
+ result = result.filter((row) => val.includes(row[filter.name]));
1077
+ } else if (type === "dateRange") {
1078
+ const fromTs = dateToTimestamp(val.from);
1079
+ const toTs = val.to ? dateToTimestamp(val.to) + 864e5 - 1 : null;
1080
+ result = result.filter((row) => {
1081
+ const rowTs = new Date(row[filter.name]).getTime();
1082
+ if (Number.isNaN(rowTs)) return false;
1083
+ if (fromTs && rowTs < fromTs) return false;
1084
+ if (toTs && rowTs > toTs) return false;
1085
+ return true;
1086
+ });
1087
+ } else {
1088
+ result = result.filter((row) => row[filter.name] === val);
1089
+ }
1090
+ }
1091
+ const searchLower = (resolvedSearch || "").toLowerCase().trim();
1092
+ if (searchEnabled && searchLower) {
1093
+ if (fuzzySearch) {
1094
+ const fuse = new import_fuse.default(result, {
1095
+ keys: searchFields,
1096
+ threshold: 0.4,
1097
+ distance: 100,
1098
+ ignoreLocation: true,
1099
+ ...fuzzyOptions
1100
+ });
1101
+ result = fuse.search(searchLower).map((r) => r.item);
1102
+ } else {
1103
+ result = result.filter(
1104
+ (row) => searchFields.some(
1105
+ (f) => String(row[f] || "").toLowerCase().includes(searchLower)
1106
+ )
1107
+ );
1108
+ }
1109
+ }
1110
+ return result;
1111
+ }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
1112
+ const buckets = (0, import_react2.useMemo)(() => {
1113
+ const map = {};
1114
+ for (const stage of stages) map[stage.value] = [];
1115
+ for (const row of filteredData) {
1116
+ const key = getStageFor(row);
1117
+ if (map[key]) {
1118
+ map[key].push(row);
1119
+ } else if (stages.length > 0) {
1120
+ if (!map.__unknown) map.__unknown = [];
1121
+ map.__unknown.push(row);
1122
+ }
1123
+ }
1124
+ return map;
1125
+ }, [filteredData, stages, getStageFor]);
1126
+ const sortComparator = (0, import_react2.useMemo)(() => {
1127
+ if (!sortOptions || !resolvedSort) return null;
1128
+ const opt = sortOptions.find((s) => s.value === resolvedSort);
1129
+ return (opt == null ? void 0 : opt.comparator) || null;
1130
+ }, [sortOptions, resolvedSort]);
1131
+ const sortedBuckets = (0, import_react2.useMemo)(() => {
1132
+ if (!sortComparator) return buckets;
1133
+ const out = {};
1134
+ for (const key of Object.keys(buckets)) {
1135
+ out[key] = [...buckets[key]].sort(sortComparator);
1136
+ }
1137
+ return out;
1138
+ }, [buckets, sortComparator]);
1139
+ const activeChips = (0, import_react2.useMemo)(() => {
1140
+ const chips = [];
1141
+ for (const filter of filters || []) {
1142
+ const val = resolvedFilters[filter.name];
1143
+ if (!isFilterActive(filter, val)) continue;
1144
+ const type = filter.type || "select";
1145
+ const prefix = filter.chipLabel || filter.placeholder || filter.name;
1146
+ if (type === "multiselect") {
1147
+ const labelList = val.map((v) => {
1148
+ var _a2;
1149
+ return ((_a2 = filter.options.find((o) => o.value === v)) == null ? void 0 : _a2.label) || v;
1150
+ }).join(", ");
1151
+ chips.push({ key: filter.name, label: `${prefix}: ${labelList}` });
1152
+ } else if (type === "dateRange") {
1153
+ const parts = [];
1154
+ if (val.from) parts.push(`from ${formatDateChip(val.from)}`);
1155
+ if (val.to) parts.push(`to ${formatDateChip(val.to)}`);
1156
+ chips.push({ key: filter.name, label: `${prefix}: ${parts.join(" ")}` });
1157
+ } else {
1158
+ const opt = filter.options.find((o) => o.value === val);
1159
+ chips.push({ key: filter.name, label: `${prefix}: ${(opt == null ? void 0 : opt.label) || val}` });
1160
+ }
1161
+ }
1162
+ return chips;
1163
+ }, [filters, resolvedFilters]);
1164
+ const partitioned = (0, import_react2.useMemo)(() => partitionFields(cardFields || []), [cardFields]);
1165
+ const dividers = (0, import_react2.useMemo)(() => resolveDividers(cardDividers, cardDensity), [cardDividers, cardDensity]);
1166
+ const resolvedMaxBody = maxBodyLines || (cardDensity === "comfortable" ? 5 : 3);
1167
+ const resolvedStageControl = stageControl || (cardDensity === "comfortable" ? "select" : "menu");
1168
+ const resolvedStageControlPlacement = stageControlPlacement || (resolvedStageControl === "menu" ? "inline" : "separateRow");
1169
+ const handleStageChangeRequest = (0, import_react2.useCallback)(
1170
+ (row, newStage, oldStage) => {
1171
+ var _a2;
1172
+ if (!newStage || newStage === oldStage) return;
1173
+ const targetStage = stagesByValue[newStage];
1174
+ if (!targetStage || !canStageReceiveRow(targetStage, row, canMove)) return;
1175
+ const rowId = row[rowIdField];
1176
+ if ((_a2 = targetStage.onEnterRequired) == null ? void 0 : _a2.render) {
1177
+ setTransitionPrompts((prev) => ({
1178
+ ...prev,
1179
+ [rowId]: {
1180
+ row,
1181
+ fromStage: oldStage,
1182
+ toStage: newStage
1183
+ }
1184
+ }));
1185
+ return;
1186
+ }
1187
+ commitStageChange(row, newStage, oldStage);
1188
+ },
1189
+ [canMove, commitStageChange, rowIdField, stagesByValue]
1190
+ );
1191
+ const renderCardNode = (0, import_react2.useCallback)(
1192
+ (row, stage) => {
1193
+ var _a2;
1194
+ const rowId = row[rowIdField];
1195
+ const activePrompt = transitionPrompts[rowId];
1196
+ const promptStage = activePrompt ? stagesByValue[activePrompt.toStage] : null;
1197
+ if ((_a2 = promptStage == null ? void 0 : promptStage.onEnterRequired) == null ? void 0 : _a2.render) {
1198
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { key: rowId, compact: cardDensity === "compact" }, promptStage.onEnterRequired.render({
1199
+ row: activePrompt.row,
1200
+ fromStage: activePrompt.fromStage,
1201
+ toStage: activePrompt.toStage,
1202
+ onConfirm: (result) => commitStageChange(activePrompt.row, activePrompt.toStage, activePrompt.fromStage, result),
1203
+ onCancel: () => clearTransitionPrompt(rowId)
1204
+ }));
1205
+ }
1206
+ if (renderCard) {
1207
+ return renderCard(row, {
1208
+ stage,
1209
+ isChanging: isStageChanging ? isStageChanging(row) : false,
1210
+ density: cardDensity,
1211
+ onStageChange: (newStage) => handleStageChangeRequest(row, newStage, stage.value)
1212
+ });
1213
+ }
1214
+ return /* @__PURE__ */ import_react2.default.createElement(
1215
+ KanbanCard,
1216
+ {
1217
+ key: rowId,
1218
+ row,
1219
+ rowId,
1220
+ stage,
1221
+ stages,
1222
+ fields: partitioned,
1223
+ density: cardDensity,
1224
+ dividers,
1225
+ bodyAs: cardBodyAs,
1226
+ maxBodyLines: resolvedMaxBody,
1227
+ stageControl: resolvedStageControl,
1228
+ stageControlPlacement: resolvedStageControlPlacement,
1229
+ canMove,
1230
+ onStageChangeRequest: handleStageChangeRequest,
1231
+ isChanging: isStageChanging ? isStageChanging(row) : false,
1232
+ selectable,
1233
+ selected: resolvedSelection.includes(rowId),
1234
+ onToggleSelect: handleToggleSelect,
1235
+ labels
1236
+ }
1237
+ );
1238
+ },
1239
+ [
1240
+ clearTransitionPrompt,
1241
+ commitStageChange,
1242
+ renderCard,
1243
+ rowIdField,
1244
+ partitioned,
1245
+ cardDensity,
1246
+ dividers,
1247
+ resolvedMaxBody,
1248
+ resolvedStageControl,
1249
+ resolvedStageControlPlacement,
1250
+ canMove,
1251
+ isStageChanging,
1252
+ selectable,
1253
+ resolvedSelection,
1254
+ handleStageChangeRequest,
1255
+ handleToggleSelect,
1256
+ labels,
1257
+ stages,
1258
+ stagesByValue,
1259
+ transitionPrompts
1260
+ ]
1261
+ );
1262
+ const totalMatching = filteredData.length;
1263
+ const selectedCount = resolvedSelection.length;
1264
+ const singular = ((recordLabel == null ? void 0 : recordLabel.singular) || "card").toLowerCase();
1265
+ const plural = ((recordLabel == null ? void 0 : recordLabel.plural) || "cards").toLowerCase();
1266
+ const countLabel = (n) => n === 1 ? singular : plural;
1267
+ const resolvedSearchPlaceholder = searchPlaceholder ?? ((recordLabel == null ? void 0 : recordLabel.plural) ? `Search ${plural}...` : labels.search);
1268
+ const selectionBarProps = {
1269
+ selectedIds: resolvedSelection,
1270
+ selectedCount,
1271
+ displayCount: totalMatching,
1272
+ countLabel,
1273
+ allSelected: selectedCount >= totalMatching && totalMatching > 0,
1274
+ onSelectAll: () => {
1275
+ const allIds = filteredData.map((r) => r[rowIdField]);
1276
+ if (onSelectionChange) onSelectionChange(allIds);
1277
+ if (selectedIds == null) setInternalSelection(allIds);
1278
+ },
1279
+ onDeselectAll: () => {
1280
+ if (onSelectionChange) onSelectionChange([]);
1281
+ if (selectedIds == null) setInternalSelection([]);
1282
+ },
1283
+ selectionActions: selectionActions || [],
1284
+ labels
1285
+ };
1286
+ const mainContent = error ? renderErrorState ? renderErrorState({
1287
+ error,
1288
+ title: labels.errorTitle,
1289
+ message: typeof error === "string" ? error : labels.errorMessage
1290
+ }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { variant: "danger", title: labels.errorTitle }, typeof error === "string" ? error : labels.errorMessage) : loading && data.length === 0 ? renderLoadingState ? renderLoadingState({ label: labels.loading }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.LoadingSpinner, { size: "md", layout: "centered", label: labels.loading }) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
1291
+ title: labels.emptyTitle,
1292
+ message: labels.emptyMessage
1293
+ }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.EmptyState, { title: labels.emptyTitle, layout: "vertical", reverseOrder: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, labels.emptyMessage))) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
1294
+ const stageRows = sortedBuckets[stage.value] || [];
1295
+ const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
1296
+ const isExpanded = resolvedExpanded.includes(stage.value);
1297
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
1298
+ const visibleRows = stageRows.slice(0, clamp);
1299
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
1300
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.AutoGrid, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ import_react2.default.createElement(
1301
+ KanbanColumn,
1302
+ {
1303
+ stage,
1304
+ rows: visibleRows,
1305
+ bucketCount: stageRows.length,
1306
+ totalCount: meta == null ? void 0 : meta.totalCount,
1307
+ hasMore: meta == null ? void 0 : meta.hasMore,
1308
+ loading: meta == null ? void 0 : meta.loading,
1309
+ error: meta == null ? void 0 : meta.error,
1310
+ onLoadMore,
1311
+ expanded: isExpanded,
1312
+ onToggleExpanded: () => handleExpanded(stage.value),
1313
+ collapsed: isCollapsed,
1314
+ onToggleCollapsed: () => handleCollapsed(stage.value),
1315
+ columnFooter,
1316
+ countDisplay,
1317
+ labels
1318
+ },
1319
+ visibleRows.map((row) => renderCardNode(row, stage))
1320
+ ));
1321
+ }));
1322
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(
1323
+ KanbanToolbar,
1324
+ {
1325
+ showSearch: searchEnabled,
1326
+ searchValue: resolvedSearch,
1327
+ searchPlaceholder: resolvedSearchPlaceholder,
1328
+ onSearchChange: handleSearch,
1329
+ filters,
1330
+ filterValues: resolvedFilters,
1331
+ onFilterChange: handleFilter,
1332
+ filterInlineLimit,
1333
+ showFilterBadges,
1334
+ showClearFiltersButton,
1335
+ activeChips,
1336
+ onFilterRemove: handleFilterRemove,
1337
+ sortOptions,
1338
+ sortValue: resolvedSort,
1339
+ onSortChange: handleSort,
1340
+ metrics,
1341
+ showMetrics: resolvedShowMetrics,
1342
+ onToggleMetrics: toggleMetrics,
1343
+ labels
1344
+ }
1345
+ ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ import_react2.default.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
1346
+ };
1347
+
1348
+ // packages/kanban/src/KanbanCardActions.jsx
1349
+ var import_react3 = __toESM(require("react"));
1350
+ var import_ui_extensions3 = require("@hubspot/ui-extensions");
1351
+ var renderButton = (action, display, size) => {
1352
+ const { key, label, icon, tooltip, variant = "transparent", disabled, onClick, href } = action;
1353
+ const buttonProps = {
1354
+ key: key || label,
1355
+ variant,
1356
+ size,
1357
+ disabled,
1358
+ tooltip: tooltip || label
1359
+ };
1360
+ if (href) buttonProps.href = typeof href === "string" ? href : href;
1361
+ if (onClick) buttonProps.onClick = onClick;
1362
+ if (display === "icon") {
1363
+ return /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Button, { ...buttonProps }, icon ? /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Icon, { name: icon, size: "sm", screenReaderText: label }) : label);
1364
+ }
1365
+ if (display === "label") {
1366
+ return /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Button, { ...buttonProps }, label);
1367
+ }
1368
+ return /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Button, { ...buttonProps }, /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Flex, { direction: "row", align: "center", gap: "xs" }, icon ? /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Icon, { name: icon, size: "sm", screenReaderText: label }) : null, /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Text, { variant: "microcopy" }, label)));
1369
+ };
1370
+ var KanbanCardActions = ({
1371
+ actions = [],
1372
+ display = "icon",
1373
+ // Default to xs — the Button's own padding at sm visibly pushes icons away
1374
+ // from Tile edges. Icon glyph inside stays pinned to size="sm" in renderButton
1375
+ // so the icon itself doesn't shrink, only the hit-target/button padding does.
1376
+ size = "xs",
1377
+ align = "end",
1378
+ gap = "flush",
1379
+ separator = "none",
1380
+ overflowAfter,
1381
+ overflowLabel = "More"
1382
+ }) => {
1383
+ const visible = actions.filter((a) => a.visible !== false);
1384
+ if (visible.length === 0) return null;
1385
+ const cutoff = typeof overflowAfter === "number" ? Math.max(0, overflowAfter) : visible.length;
1386
+ const primary = visible.slice(0, cutoff);
1387
+ const overflow = visible.slice(cutoff);
1388
+ const renderedPrimary = primary.map((action, idx) => {
1389
+ const button = renderButton(action, display, size);
1390
+ if (separator === "pipe" && idx > 0) {
1391
+ return /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, { key: `${action.key || action.label}-sep` }, /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Text, { variant: "microcopy" }, "|"), button);
1392
+ }
1393
+ return button;
1394
+ });
1395
+ const renderedOverflow = overflow.length > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Dropdown, { variant: "transparent", buttonText: overflowLabel, buttonSize: size, key: "overflow" }, overflow.map((action) => /* @__PURE__ */ import_react3.default.createElement(
1396
+ import_ui_extensions3.Dropdown.ButtonItem,
1397
+ {
1398
+ key: action.key || action.label,
1399
+ onClick: action.onClick
1400
+ },
1401
+ action.label
1402
+ ))) : null;
1403
+ const justify = align === "end" ? "end" : align === "between" ? "between" : "start";
1404
+ return /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Flex, { direction: "row", align: "end", justify, gap }, renderedPrimary, renderedOverflow);
1405
+ };
1406
+ // Annotate the CommonJS export names for ESM import in node:
1407
+ 0 && (module.exports = {
1408
+ Kanban,
1409
+ KanbanCardActions
1410
+ });