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/index.js CHANGED
@@ -31,11 +31,35 @@ var src_exports = {};
31
31
  __export(src_exports, {
32
32
  AutoStatusTag: () => AutoStatusTag,
33
33
  AutoTag: () => AutoTag,
34
+ AvatarStack: () => AvatarStack,
35
+ DEFAULT_SVG_FONT_WEIGHT: () => DEFAULT_SVG_FONT_WEIGHT,
34
36
  DataTable: () => DataTable,
35
37
  FormBuilder: () => FormBuilder,
38
+ HS_DATE_DIRECTION_LABELS: () => HS_DATE_DIRECTION_LABELS,
39
+ HS_DATE_PRESETS: () => HS_DATE_PRESETS,
40
+ HS_FONT_FAMILY: () => HS_FONT_FAMILY,
41
+ HS_MUTED_TEXT: () => HS_MUTED_TEXT,
42
+ HS_NEUTRAL_CHIP: () => HS_NEUTRAL_CHIP,
43
+ HS_SUBTLE_BG: () => HS_SUBTLE_BG,
44
+ HS_TAG_BORDER_RADIUS: () => HS_TAG_BORDER_RADIUS,
45
+ HS_TAG_BORDER_WIDTH: () => HS_TAG_BORDER_WIDTH,
46
+ HS_TAG_FONT_SIZE: () => HS_TAG_FONT_SIZE,
47
+ HS_TAG_LINE_HEIGHT: () => HS_TAG_LINE_HEIGHT,
48
+ HS_TAG_PADDING_X: () => HS_TAG_PADDING_X,
49
+ HS_TAG_PADDING_Y: () => HS_TAG_PADDING_Y,
50
+ HS_TAG_SUBTLE_BORDER: () => HS_TAG_SUBTLE_BORDER,
51
+ HS_TAG_TEXT_COLOR: () => HS_TAG_TEXT_COLOR,
52
+ HS_TEXT_COLOR: () => HS_TEXT_COLOR,
53
+ Kanban: () => Kanban,
54
+ KanbanCardActions: () => KanbanCardActions,
55
+ KeyValueList: () => KeyValueList,
56
+ SectionHeader: () => SectionHeader,
57
+ StyledText: () => StyledText,
36
58
  createStatusTagSortComparator: () => createStatusTagSortComparator,
37
59
  getAutoStatusTagVariant: () => getAutoStatusTagVariant,
38
60
  getAutoTagVariant: () => getAutoTagVariant,
61
+ makeAvatarStackDataUri: () => makeAvatarStackDataUri,
62
+ makeStyledTextDataUri: () => makeStyledTextDataUri,
39
63
  useFormPrefill: () => useFormPrefill
40
64
  });
41
65
  module.exports = __toCommonJS(src_exports);
@@ -954,7 +978,7 @@ var DataTable = ({
954
978
  }
955
979
  return isEmpty ? "--" : content;
956
980
  };
957
- const renderFilterControl = (filter) => {
981
+ const renderFilterControl2 = (filter) => {
958
982
  const type = filter.type || "select";
959
983
  if (type === "multiselect") {
960
984
  return /* @__PURE__ */ import_react.default.createElement(
@@ -1019,7 +1043,7 @@ var DataTable = ({
1019
1043
  value: internalSearchTerm,
1020
1044
  onChange: handleSearchChange
1021
1045
  }
1022
- ), filters.slice(0, filterInlineLimit).map(renderFilterControl), filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(
1046
+ ), filters.slice(0, filterInlineLimit).map(renderFilterControl2), filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(
1023
1047
  import_ui_extensions.Button,
1024
1048
  {
1025
1049
  variant: "transparent",
@@ -1029,7 +1053,7 @@ var DataTable = ({
1029
1053
  /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "filter", size: "sm" }),
1030
1054
  " ",
1031
1055
  resolvedFiltersButtonLabel
1032
- )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ import_react.default.createElement(
1056
+ )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl2)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ import_react.default.createElement(
1033
1057
  import_ui_extensions.Button,
1034
1058
  {
1035
1059
  variant: "transparent",
@@ -3126,6 +3150,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
3126
3150
  }
3127
3151
  };
3128
3152
  const getFieldColSpan = (field) => {
3153
+ if (field.colSpan === "full") return columns;
3129
3154
  if (field.colSpan != null) return Math.min(field.colSpan, columns);
3130
3155
  if (field.width === "full" && columns > 1) return columns;
3131
3156
  if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
@@ -3151,6 +3176,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
3151
3176
  let currentRowSpan = 0;
3152
3177
  const gridColumnWidth = 200;
3153
3178
  const colSpan = (field) => {
3179
+ if (field.colSpan === "full") return cols;
3154
3180
  if (field.colSpan != null) return Math.min(field.colSpan, cols);
3155
3181
  if (field.width === "full" && cols > 1) return cols;
3156
3182
  if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
@@ -3284,8 +3310,16 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
3284
3310
  }
3285
3311
  batch = [];
3286
3312
  };
3313
+ const isFullSpan = (f) => f.colSpan === "full" || typeof f.colSpan === "number" && f.colSpan > 1;
3287
3314
  for (const field of fieldList) {
3288
3315
  if (isDependent(field)) continue;
3316
+ if (isFullSpan(field)) {
3317
+ flushBatch();
3318
+ elements.push(
3319
+ /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: `full-${field.name}` }, renderField(field))
3320
+ );
3321
+ continue;
3322
+ }
3289
3323
  batch.push(field);
3290
3324
  const dependents = getDependents(field);
3291
3325
  if (dependents.length > 0) {
@@ -3297,6 +3331,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
3297
3331
  return elements;
3298
3332
  };
3299
3333
  const wrapWithGroups = (fieldList, renderFn) => {
3334
+ const formatGroupLabel = (groupName) => String(groupName || "").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
3300
3335
  const hasGroups = fieldList.some((f) => f.group);
3301
3336
  if (!hasGroups) return renderFn(fieldList);
3302
3337
  const chunks = [];
@@ -3318,6 +3353,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
3318
3353
  for (let i = 0; i < chunks.length; i++) {
3319
3354
  const chunk = chunks[i];
3320
3355
  const opts = chunk.group && groups && groups[chunk.group] || {};
3356
+ const resolvedGroupLabel = opts.label || formatGroupLabel(chunk.group);
3321
3357
  const showDivider = opts.showDivider !== false;
3322
3358
  const showLabel = opts.showLabel !== false;
3323
3359
  if (i > 0 && showDivider) {
@@ -3331,8 +3367,13 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
3331
3367
  );
3332
3368
  } else {
3333
3369
  elements.push(
3334
- /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
3370
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, resolvedGroupLabel)
3335
3371
  );
3372
+ if (opts.description) {
3373
+ elements.push(
3374
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: `group-description-${i}`, variant: "microcopy" }, opts.description)
3375
+ );
3376
+ }
3336
3377
  }
3337
3378
  }
3338
3379
  const chunkElements = renderFn(chunk.fields);
@@ -3480,10 +3521,1464 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
3480
3521
  );
3481
3522
  });
3482
3523
 
3483
- // src/common-components/AutoTag.js
3524
+ // packages/kanban/src/Kanban.jsx
3525
+ var import_react4 = __toESM(require("react"));
3526
+ var import_fuse2 = __toESM(require("fuse.js"));
3527
+
3528
+ // src/common-components/StyledText.js
3484
3529
  var import_react3 = __toESM(require("react"));
3485
3530
  var import_ui_extensions3 = require("@hubspot/ui-extensions");
3486
3531
 
3532
+ // src/common-components/svgDefaults.js
3533
+ var HS_FONT_FAMILY = '"Lexend Deca", Helvetica, Arial, sans-serif';
3534
+ var HS_TEXT_COLOR = "#33475b";
3535
+ var HS_SUBTLE_BG = "#F5F8FA";
3536
+ var HS_MUTED_TEXT = "#7C98B6";
3537
+ var HS_NEUTRAL_CHIP = "#CBD6E2";
3538
+ var HS_TAG_SUBTLE_BORDER = "#7C98B6";
3539
+ var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
3540
+ var HS_TAG_FONT_SIZE = 12;
3541
+ var HS_TAG_LINE_HEIGHT = 22;
3542
+ var HS_TAG_PADDING_X = 8;
3543
+ var HS_TAG_PADDING_Y = 0;
3544
+ var HS_TAG_BORDER_RADIUS = 0;
3545
+ var HS_TAG_BORDER_WIDTH = 1;
3546
+ var DEFAULT_SVG_FONT_WEIGHT = 600;
3547
+
3548
+ // src/common-components/StyledText.js
3549
+ var VARIANT_PRESETS = {
3550
+ bodytext: { fontSize: 14, lineHeight: 24, fontWeight: 400 },
3551
+ microcopy: { fontSize: 12, lineHeight: 18, fontWeight: 400 }
3552
+ };
3553
+ var WEIGHT_ALIASES = {
3554
+ bold: 700,
3555
+ demibold: 600,
3556
+ regular: 400
3557
+ };
3558
+ var LINE_DECORATION = {
3559
+ strikethrough: "line-through",
3560
+ underline: "underline"
3561
+ };
3562
+ var ORIENTATION_ROTATION = {
3563
+ horizontal: 0,
3564
+ "vertical-up": -90,
3565
+ "vertical-down": 90
3566
+ };
3567
+ var BACKGROUND_PRESETS = {
3568
+ tag: {
3569
+ color: HS_SUBTLE_BG,
3570
+ borderColor: HS_TAG_SUBTLE_BORDER,
3571
+ borderWidth: HS_TAG_BORDER_WIDTH,
3572
+ radius: HS_TAG_BORDER_RADIUS,
3573
+ paddingX: HS_TAG_PADDING_X,
3574
+ paddingY: HS_TAG_PADDING_Y,
3575
+ height: HS_TAG_LINE_HEIGHT,
3576
+ textColor: HS_TAG_TEXT_COLOR,
3577
+ fontSize: HS_TAG_FONT_SIZE,
3578
+ canvasPaddingX: 0,
3579
+ canvasPaddingY: 0
3580
+ }
3581
+ };
3582
+ var TAG_VARIANTS = {
3583
+ default: {
3584
+ color: HS_SUBTLE_BG,
3585
+ borderColor: HS_TAG_SUBTLE_BORDER,
3586
+ textColor: HS_TAG_TEXT_COLOR
3587
+ },
3588
+ success: {
3589
+ color: "#E5F8F6",
3590
+ borderColor: "#00BDA5",
3591
+ textColor: "#00BDA5"
3592
+ },
3593
+ warning: {
3594
+ color: "#FEF8F0",
3595
+ borderColor: "#F5C26B",
3596
+ textColor: "#D39913"
3597
+ },
3598
+ error: {
3599
+ color: "#FDEDEE",
3600
+ borderColor: "#F2545B",
3601
+ textColor: "#F2545B"
3602
+ },
3603
+ danger: {
3604
+ color: "#FDEDEE",
3605
+ borderColor: "#F2545B",
3606
+ textColor: "#F2545B"
3607
+ },
3608
+ info: {
3609
+ color: "#E5F5F8",
3610
+ borderColor: "#00A4BD",
3611
+ textColor: "#00A4BD"
3612
+ }
3613
+ };
3614
+ var NATIVE_TAG_VARIANT_ALIASES = {
3615
+ danger: "error"
3616
+ };
3617
+ var escapeSvgText = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3618
+ var applyTextTransform = (text, transform) => {
3619
+ if (!transform || transform === "none") return String(text);
3620
+ const s = String(text);
3621
+ switch (transform) {
3622
+ case "uppercase":
3623
+ return s.toUpperCase();
3624
+ case "lowercase":
3625
+ return s.toLowerCase();
3626
+ case "capitalize":
3627
+ return s.replace(/\b\w/g, (c) => c.toUpperCase());
3628
+ case "sentenceCase":
3629
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
3630
+ default:
3631
+ return s;
3632
+ }
3633
+ };
3634
+ var estimateTextWidth = (text, fontSize) => Math.max(fontSize, Math.round(String(text).length * fontSize * 0.58));
3635
+ var resolveBackground = (background) => {
3636
+ if (!background) return null;
3637
+ const preset = background.preset ? BACKGROUND_PRESETS[background.preset] : null;
3638
+ const variant = background.preset === "tag" && background.variant ? TAG_VARIANTS[background.variant] || null : null;
3639
+ return {
3640
+ ...preset || {},
3641
+ ...variant || {},
3642
+ ...background
3643
+ };
3644
+ };
3645
+ var buildBackgroundRect = ({ background, x, y, width, height }) => {
3646
+ const radius = (background == null ? void 0 : background.radius) ?? 3;
3647
+ const fill = (background == null ? void 0 : background.color) ?? "transparent";
3648
+ const borderWidth = (background == null ? void 0 : background.borderWidth) ?? 0;
3649
+ const borderColor = background == null ? void 0 : background.borderColor;
3650
+ if (!borderColor || borderWidth <= 0) {
3651
+ return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
3652
+ }
3653
+ const isTagPreset = (background == null ? void 0 : background.preset) === "tag";
3654
+ const fillRect = `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
3655
+ const strokeInset = borderWidth / 2;
3656
+ const strokeX = x + strokeInset;
3657
+ const strokeY = y + strokeInset;
3658
+ const strokeW = Math.max(0, width - borderWidth);
3659
+ const strokeH = Math.max(0, height - borderWidth);
3660
+ return fillRect + `<rect x="${strokeX}" y="${strokeY}" width="${strokeW}" height="${strokeH}" rx="${Math.max(
3661
+ 0,
3662
+ radius - strokeInset
3663
+ )}" fill="none" stroke="${borderColor}" stroke-width="${borderWidth}"${isTagPreset ? ` shape-rendering="crispEdges"` : ""} />`;
3664
+ };
3665
+ var canUseNativeTag = ({
3666
+ background,
3667
+ orientation,
3668
+ color,
3669
+ fontFamily,
3670
+ fontSize,
3671
+ width,
3672
+ height,
3673
+ paddingX,
3674
+ paddingY,
3675
+ format = {}
3676
+ }) => {
3677
+ if (!background || background.preset !== "tag") return false;
3678
+ const resolvedOrientation = typeof orientation === "number" ? orientation : ORIENTATION_ROTATION[orientation ?? "horizontal"] ?? 0;
3679
+ if (resolvedOrientation !== 0) return false;
3680
+ if (color != null || fontFamily != null || fontSize != null) return false;
3681
+ if (width != null || height != null || paddingX != null || paddingY != null) return false;
3682
+ if (background.color != null || background.textColor != null) return false;
3683
+ if (background.borderColor != null || background.borderWidth != null) return false;
3684
+ if (background.radius != null || background.height != null) return false;
3685
+ if (background.paddingX != null || background.paddingY != null) return false;
3686
+ if (background.canvasPaddingX != null || background.canvasPaddingY != null) return false;
3687
+ if (format.italic || format.lineDecoration) return false;
3688
+ if (format.textTransform && format.textTransform !== "none") return false;
3689
+ return true;
3690
+ };
3691
+ var makeStyledTextDataUri = (text, opts = {}) => {
3692
+ const {
3693
+ variant = "bodytext",
3694
+ format = {},
3695
+ orientation = "horizontal",
3696
+ color: colorProp = HS_TEXT_COLOR,
3697
+ fontFamily = HS_FONT_FAMILY,
3698
+ background: backgroundProp = null,
3699
+ paddingX: paddingXProp = 4,
3700
+ paddingY: paddingYProp = 2,
3701
+ width: widthOverride,
3702
+ height: heightOverride,
3703
+ fontSize: fontSizeOverride
3704
+ } = opts;
3705
+ const preset = VARIANT_PRESETS[variant] || VARIANT_PRESETS.bodytext;
3706
+ const background = resolveBackground(backgroundProp);
3707
+ const fontSize = fontSizeOverride ?? (background == null ? void 0 : background.fontSize) ?? preset.fontSize;
3708
+ const rawWeight = format.fontWeight;
3709
+ const fontWeight = rawWeight ? WEIGHT_ALIASES[rawWeight] ?? rawWeight : preset.fontWeight;
3710
+ const fontStyle = format.italic ? "italic" : "normal";
3711
+ const textDecoration = LINE_DECORATION[format.lineDecoration] || "none";
3712
+ const transformed = applyTextTransform(text, format.textTransform);
3713
+ const lineHeight = (background == null ? void 0 : background.height) ?? preset.lineHeight ?? fontSize;
3714
+ const color = (background == null ? void 0 : background.textColor) ?? colorProp;
3715
+ const paddingX = (background == null ? void 0 : background.canvasPaddingX) ?? paddingXProp;
3716
+ const paddingY = (background == null ? void 0 : background.canvasPaddingY) ?? paddingYProp;
3717
+ const rotate = typeof orientation === "number" ? orientation : ORIENTATION_ROTATION[orientation] ?? 0;
3718
+ const textW = estimateTextWidth(transformed, fontSize);
3719
+ let pillW = 0;
3720
+ let pillH = 0;
3721
+ if (background) {
3722
+ const bgPadX = background.paddingX ?? 6;
3723
+ const bgPadY = background.paddingY ?? 3;
3724
+ pillW = textW + bgPadX * 2;
3725
+ pillH = background.height ?? Math.max(lineHeight, fontSize + bgPadY * 2);
3726
+ }
3727
+ const intrinsicW = (background ? pillW : textW) + paddingX * 2;
3728
+ const intrinsicH = (background ? pillH : lineHeight) + paddingY * 2;
3729
+ const isOrthoRotation = rotate === 90 || rotate === -90 || rotate === 270;
3730
+ const canvasW = widthOverride ?? (isOrthoRotation ? intrinsicH : intrinsicW);
3731
+ const canvasH = heightOverride ?? (isOrthoRotation ? intrinsicW : intrinsicH);
3732
+ const cx = canvasW / 2;
3733
+ const cy = canvasH / 2;
3734
+ const rectX = cx - pillW / 2;
3735
+ const rectY = cy - pillH / 2;
3736
+ const group = (background ? buildBackgroundRect({
3737
+ background,
3738
+ x: rectX,
3739
+ y: rectY,
3740
+ width: pillW,
3741
+ height: pillH
3742
+ }) : "") + `<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>`;
3743
+ const wrapped = rotate ? `<g transform="rotate(${rotate} ${cx} ${cy})">${group}</g>` : group;
3744
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${canvasW}" height="${canvasH}">` + wrapped + `</svg>`;
3745
+ return {
3746
+ src: `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`,
3747
+ width: canvasW,
3748
+ height: canvasH
3749
+ };
3750
+ };
3751
+ var StyledText = ({
3752
+ children,
3753
+ text,
3754
+ alt,
3755
+ variant,
3756
+ format,
3757
+ orientation,
3758
+ color,
3759
+ background,
3760
+ fontFamily,
3761
+ fontSize,
3762
+ paddingX,
3763
+ paddingY,
3764
+ width,
3765
+ height
3766
+ }) => {
3767
+ const resolvedText = text ?? (typeof children === "string" ? children : "");
3768
+ if (canUseNativeTag({
3769
+ background,
3770
+ orientation,
3771
+ color,
3772
+ fontFamily,
3773
+ fontSize,
3774
+ width,
3775
+ height,
3776
+ paddingX,
3777
+ paddingY,
3778
+ format
3779
+ })) {
3780
+ const nativeVariant = NATIVE_TAG_VARIANT_ALIASES[background == null ? void 0 : background.variant] ?? (background == null ? void 0 : background.variant) ?? "default";
3781
+ return import_react3.default.createElement(import_ui_extensions3.Tag, { variant: nativeVariant }, resolvedText);
3782
+ }
3783
+ const { src, width: w, height: h } = makeStyledTextDataUri(resolvedText, {
3784
+ variant,
3785
+ format,
3786
+ orientation,
3787
+ color,
3788
+ background,
3789
+ fontFamily,
3790
+ fontSize,
3791
+ paddingX,
3792
+ paddingY,
3793
+ width,
3794
+ height
3795
+ });
3796
+ return import_react3.default.createElement(import_ui_extensions3.Image, {
3797
+ src,
3798
+ width: w,
3799
+ height: h,
3800
+ alt: alt ?? String(resolvedText)
3801
+ });
3802
+ };
3803
+
3804
+ // packages/kanban/src/Kanban.jsx
3805
+ var import_ui_extensions4 = require("@hubspot/ui-extensions");
3806
+ var DEFAULT_DENSITY = "compact";
3807
+ var DEFAULT_MAX_CARDS = 10;
3808
+ var DEFAULT_MAX_EXPANDED = 50;
3809
+ var DEFAULT_COLUMN_WIDTH = 350;
3810
+ var MIN_COLUMN_WIDTH = 350;
3811
+ var DEFAULT_FILTER_INLINE_LIMIT = 4;
3812
+ var DEFAULT_SEARCH_DEBOUNCE = 250;
3813
+ var DEFAULT_TITLE_TRUNCATE = 60;
3814
+ var applyTruncate = (value, truncate, fallback) => {
3815
+ if (truncate === false) return value;
3816
+ if (typeof value !== "string") return value;
3817
+ const limit = typeof truncate === "number" ? truncate : truncate === true ? fallback || DEFAULT_TITLE_TRUNCATE : fallback || DEFAULT_TITLE_TRUNCATE;
3818
+ if (value.length <= limit) return value;
3819
+ return value.slice(0, limit).trimEnd() + "\u2026";
3820
+ };
3821
+ var DEFAULT_LABELS = {
3822
+ search: "Search cards...",
3823
+ // Only the total is surfaced — callers asked for a single headline number
3824
+ // rather than a "loaded / total" fraction. Fall back to the bare label when
3825
+ // no total is known.
3826
+ showMore: (_shown, total) => total ? `Show more (${total})` : "Show more",
3827
+ showLess: "Show less",
3828
+ loadMore: (_loaded, total) => total ? `Load more (${total})` : "Load more",
3829
+ loadingMore: "Loading...",
3830
+ retryLoadMore: "Retry",
3831
+ emptyColumn: "\u2014",
3832
+ emptyTitle: "No cards",
3833
+ emptyMessage: "Nothing matches the current filters.",
3834
+ loading: "Loading board...",
3835
+ errorTitle: "Something went wrong.",
3836
+ errorMessage: "An error occurred while loading data.",
3837
+ cardCount: (n) => String(n),
3838
+ moveTo: "Move",
3839
+ clearAll: "Clear all",
3840
+ selectAll: (count, label) => `Select all ${count} ${label}`,
3841
+ deselectAll: "Deselect all",
3842
+ selected: (count, label) => `${count}\xA0${label}\xA0selected`,
3843
+ filtersButton: "Filters",
3844
+ dateFrom: "From",
3845
+ dateTo: "To",
3846
+ sortButton: "Sort",
3847
+ sortAscending: "Ascending",
3848
+ sortDescending: "Descending",
3849
+ metricsButton: "Metrics"
3850
+ };
3851
+ var makeRotatedTagDataUri = (label) => makeStyledTextDataUri(label, {
3852
+ variant: "microcopy",
3853
+ format: { fontWeight: "demibold" },
3854
+ orientation: "vertical-down",
3855
+ background: { preset: "tag" }
3856
+ });
3857
+ var makeRotatedLabelDataUri = (label) => makeStyledTextDataUri(label, {
3858
+ variant: "bodytext",
3859
+ format: { fontWeight: "demibold" },
3860
+ orientation: "vertical-down"
3861
+ });
3862
+ var getEmptyFilterValue2 = (filter) => {
3863
+ const type = filter.type || "select";
3864
+ if (type === "multiselect") return [];
3865
+ if (type === "dateRange") return { from: null, to: null };
3866
+ return "";
3867
+ };
3868
+ var isFilterActive2 = (filter, value) => {
3869
+ const type = filter.type || "select";
3870
+ if (type === "multiselect") return Array.isArray(value) && value.length > 0;
3871
+ if (type === "dateRange") return value && (value.from || value.to);
3872
+ return !!value;
3873
+ };
3874
+ var formatDateChip2 = (dateObj) => {
3875
+ if (!dateObj) return "";
3876
+ const { year, month, date } = dateObj;
3877
+ return new Intl.DateTimeFormat("en-US", {
3878
+ month: "short",
3879
+ day: "numeric",
3880
+ year: "numeric"
3881
+ }).format(new Date(year, month, date));
3882
+ };
3883
+ var dateToTimestamp2 = (dateObj) => {
3884
+ if (!dateObj) return null;
3885
+ return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
3886
+ };
3887
+ var toStableKey2 = (value) => {
3888
+ try {
3889
+ return JSON.stringify(value);
3890
+ } catch (_error) {
3891
+ return String(value);
3892
+ }
3893
+ };
3894
+ var canStageReceiveRow = (stage, row, canMove) => {
3895
+ if (!stage) return false;
3896
+ if (typeof canMove === "function" && !canMove(row, stage.value)) return false;
3897
+ if (typeof stage.canEnter === "function" && !stage.canEnter(row)) return false;
3898
+ return true;
3899
+ };
3900
+ var isFieldDirectionSortOption = (option) => !!(option && option.field && (option.direction === "asc" || option.direction === "desc"));
3901
+ var resolveDividers = (cardDividers, density) => {
3902
+ if (cardDividers === true) {
3903
+ return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
3904
+ }
3905
+ if (cardDividers === false) {
3906
+ return { afterTitle: false, afterSubtitle: false, afterBody: false, afterFooter: false };
3907
+ }
3908
+ if (cardDividers && typeof cardDividers === "object") {
3909
+ return {
3910
+ afterTitle: cardDividers.afterTitle ?? false,
3911
+ afterSubtitle: cardDividers.afterSubtitle ?? false,
3912
+ afterBody: cardDividers.afterBody ?? false,
3913
+ afterFooter: cardDividers.afterFooter ?? false
3914
+ };
3915
+ }
3916
+ if (density === "comfortable") {
3917
+ return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
3918
+ }
3919
+ return { afterTitle: false, afterSubtitle: false, afterBody: true, afterFooter: false };
3920
+ };
3921
+ var partitionFields = (cardFields) => {
3922
+ const buckets = { title: null, subtitle: null, meta: [], body: [], footer: [] };
3923
+ for (const field of cardFields || []) {
3924
+ const placement = field.placement || "body";
3925
+ if (placement === "title" && !buckets.title) buckets.title = field;
3926
+ else if (placement === "subtitle" && !buckets.subtitle) buckets.subtitle = field;
3927
+ else if (placement === "meta") buckets.meta.push(field);
3928
+ else if (placement === "footer") buckets.footer.push(field);
3929
+ else buckets.body.push(field);
3930
+ }
3931
+ return buckets;
3932
+ };
3933
+ var resolveFieldValue = (field, row) => {
3934
+ if (!field) return void 0;
3935
+ if (field.field && row && Object.prototype.hasOwnProperty.call(row, field.field)) {
3936
+ return row[field.field];
3937
+ }
3938
+ return void 0;
3939
+ };
3940
+ var resolveHref = (href, row) => {
3941
+ if (!href) return null;
3942
+ if (typeof href === "function") return href(row);
3943
+ return href;
3944
+ };
3945
+ var KanbanCard = ({
3946
+ row,
3947
+ rowId,
3948
+ stage,
3949
+ stages,
3950
+ fields,
3951
+ density,
3952
+ dividers,
3953
+ bodyAs,
3954
+ maxBodyLines,
3955
+ stageControl,
3956
+ stageControlPlacement,
3957
+ canMove,
3958
+ onStageChangeRequest,
3959
+ isChanging,
3960
+ selectable,
3961
+ selected,
3962
+ onToggleSelect,
3963
+ labels
3964
+ }) => {
3965
+ const titleHref = fields.title ? resolveHref(fields.title.href, row) : null;
3966
+ const rawTitleValue = fields.title ? fields.title.render ? fields.title.render(resolveFieldValue(fields.title, row), row) : resolveFieldValue(fields.title, row) : null;
3967
+ const titleValue = fields.title && typeof rawTitleValue === "string" ? applyTruncate(rawTitleValue, fields.title.truncate, DEFAULT_TITLE_TRUNCATE) : rawTitleValue;
3968
+ const titleNode = titleHref ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Link, { href: titleHref }, titleValue) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { format: { fontWeight: "demibold" } }, titleValue);
3969
+ const metaNodes = fields.meta.filter((f) => !f.visible || f.visible(row)).map((f) => {
3970
+ const val = resolveFieldValue(f, row);
3971
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { key: f.field || f.label, variant: "microcopy" }, f.render ? f.render(val, row) : val);
3972
+ });
3973
+ const showSubtitle = density === "comfortable" && fields.subtitle;
3974
+ const subtitleNode = showSubtitle ? fields.subtitle.render ? fields.subtitle.render(resolveFieldValue(fields.subtitle, row), row) : resolveFieldValue(fields.subtitle, row) : null;
3975
+ const bodyFields = fields.body.filter((f) => !f.visible || f.visible(row)).slice(0, maxBodyLines);
3976
+ const footerFields = fields.footer.filter((f) => !f.visible || f.visible(row));
3977
+ const footerAlerts = footerFields.slice(0, -1);
3978
+ const footerActionsField = footerFields.length > 0 ? footerFields[footerFields.length - 1] : null;
3979
+ const renderFooterField = (f, idx) => {
3980
+ const val = resolveFieldValue(f, row);
3981
+ const rendered = f.render ? f.render(val, row) : val;
3982
+ const key = f.key || f.field || f.label || `footer-${idx}`;
3983
+ return /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, { key }, rendered);
3984
+ };
3985
+ const stageControlNode = stageControl === "none" ? null : /* @__PURE__ */ import_react4.default.createElement(
3986
+ StageControl,
3987
+ {
3988
+ row,
3989
+ rowId,
3990
+ currentStage: stage,
3991
+ stages,
3992
+ canMove,
3993
+ isChanging,
3994
+ mode: stageControl,
3995
+ onStageChangeRequest,
3996
+ labels
3997
+ }
3998
+ );
3999
+ const titleRow = /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { flex: 1 }, titleNode), selectable ? /* @__PURE__ */ import_react4.default.createElement(
4000
+ import_ui_extensions4.Checkbox,
4001
+ {
4002
+ name: `kanban-select-${rowId}`,
4003
+ checked: selected,
4004
+ onChange: () => onToggleSelect(rowId)
4005
+ }
4006
+ ) : null);
4007
+ const metaRow = metaNodes.length === 0 ? null : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "end", align: "center", gap: "xs" }, metaNodes);
4008
+ const bodyRow = bodyFields.length === 0 ? null : bodyAs === "descriptionList" ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.DescriptionList, { direction: "row" }, bodyFields.map((f, idx) => {
4009
+ const val = resolveFieldValue(f, row);
4010
+ const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
4011
+ const key = f.key || f.field || f.label || `body-${idx}`;
4012
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.DescriptionListItem, { key, label: f.label || "" }, rendered);
4013
+ })) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "flush" }, bodyFields.map((f, idx) => {
4014
+ const val = resolveFieldValue(f, row);
4015
+ const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
4016
+ const key = f.key || f.field || f.label || `body-${idx}`;
4017
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { key, variant: "microcopy" }, f.label ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { inline: true, variant: "microcopy" }, `${f.label}: `) : null, rendered);
4018
+ }));
4019
+ const footerAlertsNode = footerAlerts.length === 0 ? null : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, footerAlerts.map((f, idx) => renderFooterField(f, idx)));
4020
+ const footerActionsNode = footerActionsField ? renderFooterField(footerActionsField, footerFields.length - 1) : null;
4021
+ const inlineStageControl = stageControlPlacement === "inline" ? stageControlNode : null;
4022
+ const separateRowStageControl = stageControlPlacement === "separateRow" ? stageControlNode : null;
4023
+ const footerMainRow = inlineStageControl || footerActionsNode ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "between", align: "start", gap: "sm" }, inlineStageControl ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { alignSelf: "center" }, inlineStageControl) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, null), footerActionsNode ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { flex: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "end", align: "start" }, footerActionsNode)) : null) : null;
4024
+ const footerRow = !footerAlertsNode && !footerMainRow ? null : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, footerAlertsNode, footerMainRow);
4025
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { compact: density === "compact" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: density === "compact" ? "xs" : "sm" }, titleRow, dividers.afterTitle && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null) : null, subtitleNode ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { variant: "microcopy" }, subtitleNode) : null, dividers.afterSubtitle && subtitleNode && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null) : null, metaRow, bodyRow, dividers.afterBody && bodyRow && (footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null) : null, footerRow, dividers.afterFooter && footerRow && separateRowStageControl ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null) : null, separateRowStageControl));
4026
+ };
4027
+ var StageControl = ({
4028
+ row,
4029
+ rowId,
4030
+ currentStage,
4031
+ stages,
4032
+ canMove,
4033
+ isChanging,
4034
+ mode,
4035
+ onStageChangeRequest,
4036
+ labels
4037
+ }) => {
4038
+ if (isChanging) {
4039
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.LoadingSpinner, { size: "xs" });
4040
+ }
4041
+ const availableStages = (stages || []).filter(
4042
+ (stage) => stage.value === currentStage.value || canStageReceiveRow(stage, row, canMove)
4043
+ );
4044
+ if (mode === "menu") {
4045
+ const targetStages = availableStages.filter((stage) => stage.value !== currentStage.value);
4046
+ if (targetStages.length === 0) {
4047
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "extra-small", disabled: true }, labels.moveTo);
4048
+ }
4049
+ return /* @__PURE__ */ import_react4.default.createElement(
4050
+ import_ui_extensions4.Dropdown,
4051
+ {
4052
+ variant: "transparent",
4053
+ buttonText: labels.moveTo,
4054
+ buttonSize: "xs"
4055
+ },
4056
+ targetStages.map((stage) => /* @__PURE__ */ import_react4.default.createElement(
4057
+ import_ui_extensions4.Dropdown.ButtonItem,
4058
+ {
4059
+ key: stage.value,
4060
+ onClick: () => onStageChangeRequest(row, stage.value, currentStage.value)
4061
+ },
4062
+ stage.shortLabel || stage.label
4063
+ ))
4064
+ );
4065
+ }
4066
+ return /* @__PURE__ */ import_react4.default.createElement(
4067
+ import_ui_extensions4.Select,
4068
+ {
4069
+ name: `stage-${rowId}`,
4070
+ label: "",
4071
+ value: currentStage.value,
4072
+ onChange: (val) => {
4073
+ if (val !== currentStage.value) onStageChangeRequest(row, val, currentStage.value);
4074
+ },
4075
+ options: availableStages.map((stage) => ({
4076
+ label: stage.shortLabel || stage.label,
4077
+ value: stage.value
4078
+ }))
4079
+ }
4080
+ );
4081
+ };
4082
+ var KanbanColumn = ({
4083
+ stage,
4084
+ rows,
4085
+ bucketCount,
4086
+ totalCount,
4087
+ hasMore,
4088
+ loading,
4089
+ error,
4090
+ onLoadMore,
4091
+ expanded,
4092
+ onToggleExpanded,
4093
+ collapsed,
4094
+ onToggleCollapsed,
4095
+ columnFooter,
4096
+ countDisplay,
4097
+ labels,
4098
+ children
4099
+ }) => {
4100
+ const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
4101
+ const countNode = countDisplay === "text" ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tag, { variant: "subtle" }, countLabel);
4102
+ if (collapsed) {
4103
+ const rotated = makeRotatedLabelDataUri(stage.label);
4104
+ const rotatedCount = countDisplay === "none" ? null : makeRotatedTagDataUri(countLabel);
4105
+ const stageIdentifier = stage.icon ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: stage.icon, size: "sm", screenReaderText: stage.label }) : /* @__PURE__ */ import_react4.default.createElement(
4106
+ import_ui_extensions4.Image,
4107
+ {
4108
+ src: rotated.src,
4109
+ width: rotated.width,
4110
+ height: rotated.height,
4111
+ alt: stage.label
4112
+ }
4113
+ );
4114
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { compact: true }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs", align: "center" }, /* @__PURE__ */ import_react4.default.createElement(
4115
+ import_ui_extensions4.Button,
4116
+ {
4117
+ variant: "transparent",
4118
+ size: "sm",
4119
+ onClick: onToggleCollapsed,
4120
+ tooltip: `Expand ${stage.label}`
4121
+ },
4122
+ /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "right", size: "sm", screenReaderText: `Expand ${stage.label}` })
4123
+ ), stageIdentifier, rotatedCount ? /* @__PURE__ */ import_react4.default.createElement(
4124
+ import_ui_extensions4.Image,
4125
+ {
4126
+ src: rotatedCount.src,
4127
+ width: rotatedCount.width,
4128
+ height: rotatedCount.height,
4129
+ alt: `${bucketCount} items`
4130
+ }
4131
+ ) : null));
4132
+ }
4133
+ const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
4134
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { compact: true }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null), children, error ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
4135
+ };
4136
+ var SortModalBody = ({ sortOptions, sortValue, onSortChange, labels }) => {
4137
+ const hasFieldDirection = Array.isArray(sortOptions) && sortOptions.length > 0 && sortOptions.every(isFieldDirectionSortOption);
4138
+ if (!hasFieldDirection) {
4139
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, sortOptions.map((opt) => /* @__PURE__ */ import_react4.default.createElement(
4140
+ import_ui_extensions4.Button,
4141
+ {
4142
+ key: opt.value,
4143
+ variant: sortValue === opt.value ? "primary" : "secondary",
4144
+ onClick: () => onSortChange(opt.value)
4145
+ },
4146
+ opt.label
4147
+ )));
4148
+ }
4149
+ const currentOption = sortOptions.find((o) => o.value === sortValue) || sortOptions[0];
4150
+ const currentField = currentOption.field;
4151
+ const currentDirection = currentOption.direction;
4152
+ const uniqueFields = [];
4153
+ const seen = /* @__PURE__ */ new Set();
4154
+ for (const opt of sortOptions) {
4155
+ if (!seen.has(opt.field)) {
4156
+ seen.add(opt.field);
4157
+ uniqueFields.push({ value: opt.field, label: opt.fieldLabel || opt.field });
4158
+ }
4159
+ }
4160
+ const fieldOpts = sortOptions.filter((o) => o.field === currentField);
4161
+ const ascOption = fieldOpts.find((o) => o.direction === "asc");
4162
+ const descOption = fieldOpts.find((o) => o.direction === "desc");
4163
+ const handleFieldChange = (newField) => {
4164
+ 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);
4165
+ if (next) onSortChange(next.value);
4166
+ };
4167
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react4.default.createElement(
4168
+ import_ui_extensions4.Select,
4169
+ {
4170
+ name: "kanban-sort-field",
4171
+ label: "",
4172
+ value: currentField,
4173
+ onChange: handleFieldChange,
4174
+ options: uniqueFields
4175
+ }
4176
+ ), /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", gap: "flush" }, descOption ? /* @__PURE__ */ import_react4.default.createElement(
4177
+ import_ui_extensions4.Button,
4178
+ {
4179
+ variant: currentDirection === "desc" ? "primary" : "secondary",
4180
+ onClick: () => onSortChange(descOption.value)
4181
+ },
4182
+ labels.sortDescending
4183
+ ) : null, ascOption ? /* @__PURE__ */ import_react4.default.createElement(
4184
+ import_ui_extensions4.Button,
4185
+ {
4186
+ variant: currentDirection === "asc" ? "primary" : "secondary",
4187
+ onClick: () => onSortChange(ascOption.value)
4188
+ },
4189
+ labels.sortAscending
4190
+ ) : null));
4191
+ };
4192
+ var renderFilterControl = ({ filter, value, onChange, labels }) => {
4193
+ const type = filter.type || "select";
4194
+ if (type === "multiselect") {
4195
+ return /* @__PURE__ */ import_react4.default.createElement(
4196
+ import_ui_extensions4.MultiSelect,
4197
+ {
4198
+ key: filter.name,
4199
+ name: `kanban-filter-${filter.name}`,
4200
+ label: "",
4201
+ placeholder: filter.placeholder || "All",
4202
+ value: value || [],
4203
+ onChange: (val) => onChange(filter.name, val),
4204
+ options: filter.options
4205
+ }
4206
+ );
4207
+ }
4208
+ if (type === "dateRange") {
4209
+ const rangeVal = value || { from: null, to: null };
4210
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { key: filter.name, direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(
4211
+ import_ui_extensions4.DateInput,
4212
+ {
4213
+ size: "sm",
4214
+ name: `kanban-filter-${filter.name}-from`,
4215
+ label: "",
4216
+ placeholder: labels.dateFrom,
4217
+ format: "medium",
4218
+ value: rangeVal.from,
4219
+ onChange: (val) => onChange(filter.name, { ...rangeVal, from: val })
4220
+ }
4221
+ ), /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "dataSync", size: "sm" }), /* @__PURE__ */ import_react4.default.createElement(
4222
+ import_ui_extensions4.DateInput,
4223
+ {
4224
+ size: "sm",
4225
+ name: `kanban-filter-${filter.name}-to`,
4226
+ label: "",
4227
+ placeholder: labels.dateTo,
4228
+ format: "medium",
4229
+ value: rangeVal.to,
4230
+ onChange: (val) => onChange(filter.name, { ...rangeVal, to: val })
4231
+ }
4232
+ ));
4233
+ }
4234
+ return /* @__PURE__ */ import_react4.default.createElement(
4235
+ import_ui_extensions4.Select,
4236
+ {
4237
+ key: filter.name,
4238
+ name: `kanban-filter-${filter.name}`,
4239
+ variant: "transparent",
4240
+ placeholder: filter.placeholder || "All",
4241
+ value,
4242
+ onChange: (val) => onChange(filter.name, val),
4243
+ options: [
4244
+ { label: filter.placeholder || "All", value: "" },
4245
+ ...filter.options
4246
+ ]
4247
+ }
4248
+ );
4249
+ };
4250
+ var renderMetricsPanel = (metrics) => {
4251
+ if (!metrics) return null;
4252
+ if (!Array.isArray(metrics)) return metrics;
4253
+ if (metrics.length === 0) return null;
4254
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Statistics, null, metrics.map((m, i) => /* @__PURE__ */ import_react4.default.createElement(
4255
+ import_ui_extensions4.StatisticsItem,
4256
+ {
4257
+ key: m.id || m.label || i,
4258
+ label: m.label,
4259
+ number: m.number != null ? String(m.number) : ""
4260
+ },
4261
+ m.trend ? /* @__PURE__ */ import_react4.default.createElement(
4262
+ import_ui_extensions4.StatisticsTrend,
4263
+ {
4264
+ direction: m.trend.direction || "increase",
4265
+ value: m.trend.value,
4266
+ color: m.trend.color
4267
+ }
4268
+ ) : null
4269
+ )));
4270
+ };
4271
+ var KanbanToolbar = ({
4272
+ showSearch,
4273
+ searchValue,
4274
+ searchPlaceholder,
4275
+ onSearchChange,
4276
+ filters,
4277
+ filterValues,
4278
+ onFilterChange,
4279
+ filterInlineLimit,
4280
+ showFilterBadges,
4281
+ showClearFiltersButton,
4282
+ activeChips,
4283
+ onFilterRemove,
4284
+ sortOptions,
4285
+ sortValue,
4286
+ onSortChange,
4287
+ metrics,
4288
+ showMetrics,
4289
+ onToggleMetrics,
4290
+ labels
4291
+ }) => {
4292
+ const [showMoreFilters, setShowMoreFilters] = (0, import_react4.useState)(false);
4293
+ const inlineFilters = (filters || []).slice(0, filterInlineLimit);
4294
+ const overflowFilters = (filters || []).slice(filterInlineLimit);
4295
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { flex: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch ? /* @__PURE__ */ import_react4.default.createElement(
4296
+ import_ui_extensions4.SearchInput,
4297
+ {
4298
+ name: "kanban-search",
4299
+ placeholder: searchPlaceholder,
4300
+ value: searchValue,
4301
+ onChange: onSearchChange
4302
+ }
4303
+ ) : null, inlineFilters.map(
4304
+ (filter) => renderFilterControl({
4305
+ filter,
4306
+ value: filterValues[filter.name],
4307
+ onChange: onFilterChange,
4308
+ labels
4309
+ })
4310
+ ), overflowFilters.length > 0 ? /* @__PURE__ */ import_react4.default.createElement(
4311
+ import_ui_extensions4.Button,
4312
+ {
4313
+ variant: "transparent",
4314
+ size: "small",
4315
+ onClick: () => setShowMoreFilters((prev) => !prev)
4316
+ },
4317
+ /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "filter", size: "sm" }),
4318
+ " ",
4319
+ labels.filtersButton
4320
+ ) : null), showMoreFilters && overflowFilters.length > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, overflowFilters.map(
4321
+ (filter) => renderFilterControl({
4322
+ filter,
4323
+ value: filterValues[filter.name],
4324
+ onChange: onFilterChange,
4325
+ labels
4326
+ })
4327
+ )) : null, activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges ? activeChips.map((chip) => /* @__PURE__ */ import_react4.default.createElement(
4328
+ import_ui_extensions4.Tag,
4329
+ {
4330
+ key: chip.key,
4331
+ variant: "default",
4332
+ onDelete: () => onFilterRemove(chip.key)
4333
+ },
4334
+ chip.label
4335
+ )) : null, showClearFiltersButton ? /* @__PURE__ */ import_react4.default.createElement(
4336
+ import_ui_extensions4.Button,
4337
+ {
4338
+ variant: "transparent",
4339
+ size: "extra-small",
4340
+ onClick: () => onFilterRemove("all")
4341
+ },
4342
+ labels.clearAll
4343
+ ) : null) : null)), (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { flex: 1, alignSelf: "start" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "sm", justify: "end" }, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react4.default.createElement(
4344
+ import_ui_extensions4.Button,
4345
+ {
4346
+ variant: "secondary",
4347
+ size: "small",
4348
+ overlay: /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Modal, { id: "kanban-sort-modal", title: labels.sortButton }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.ModalBody, null, /* @__PURE__ */ import_react4.default.createElement(
4349
+ SortModalBody,
4350
+ {
4351
+ sortOptions,
4352
+ sortValue,
4353
+ onSortChange,
4354
+ labels
4355
+ }
4356
+ )))
4357
+ },
4358
+ /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "sortAmtDesc", size: "sm" }),
4359
+ " ",
4360
+ labels.sortButton
4361
+ ) : null, metrics ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "reports", size: "sm" }), " ", labels.metricsButton) : null)) : null), showMetrics && metrics ? renderMetricsPanel(metrics) : null);
4362
+ };
4363
+ var DefaultSelectionBar = ({
4364
+ selectedIds,
4365
+ selectedCount,
4366
+ displayCount,
4367
+ countLabel,
4368
+ allSelected,
4369
+ onSelectAll,
4370
+ onDeselectAll,
4371
+ selectionActions,
4372
+ labels
4373
+ }) => {
4374
+ const pluralForCount = (n) => countLabel(n);
4375
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { compact: true }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", justify: "between", gap: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof labels.selected === "function" ? labels.selected(selectedCount, pluralForCount(selectedCount)) : `${selectedCount} selected`), !allSelected ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "extra-small", onClick: onSelectAll }, typeof labels.selectAll === "function" ? labels.selectAll(displayCount, pluralForCount(displayCount)) : labels.selectAll) : null, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "extra-small", onClick: onDeselectAll }, labels.deselectAll)), (selectionActions || []).length > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", gap: "extra-small" }, selectionActions.map((action, i) => /* @__PURE__ */ import_react4.default.createElement(
4376
+ import_ui_extensions4.Button,
4377
+ {
4378
+ key: action.key || action.label || i,
4379
+ variant: action.variant || "transparent",
4380
+ size: "extra-small",
4381
+ onClick: () => action.onClick([...selectedIds])
4382
+ },
4383
+ action.icon ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: action.icon, size: "sm" }) : null,
4384
+ " ",
4385
+ action.label
4386
+ ))) : null));
4387
+ };
4388
+ var Kanban = ({
4389
+ // --- Data ---
4390
+ data = [],
4391
+ stages = [],
4392
+ groupBy = "status",
4393
+ rowIdField = "id",
4394
+ // --- Card rendering ---
4395
+ renderCard,
4396
+ cardFields,
4397
+ cardDensity = DEFAULT_DENSITY,
4398
+ cardDividers,
4399
+ cardBodyAs = "descriptionList",
4400
+ maxBodyLines,
4401
+ maxCardsPerColumn = DEFAULT_MAX_CARDS,
4402
+ maxCardsExpanded = DEFAULT_MAX_EXPANDED,
4403
+ expandedStages,
4404
+ onExpandedStagesChange,
4405
+ // --- Per-stage pagination ---
4406
+ stageMeta,
4407
+ onLoadMore,
4408
+ // --- Selection ---
4409
+ selectable = false,
4410
+ selectedIds,
4411
+ onSelectionChange,
4412
+ selectionActions,
4413
+ recordLabel,
4414
+ selectionResetKey,
4415
+ resetSelectionOnQueryChange = true,
4416
+ showSelectionBar = true,
4417
+ renderSelectionBar,
4418
+ // --- Stage transitions ---
4419
+ stageControl,
4420
+ stageControlPlacement,
4421
+ onStageChange,
4422
+ isStageChanging,
4423
+ canMove,
4424
+ // --- Toolbar ---
4425
+ showSearch = true,
4426
+ searchFields,
4427
+ searchPlaceholder,
4428
+ searchDebounce = DEFAULT_SEARCH_DEBOUNCE,
4429
+ fuzzySearch = false,
4430
+ fuzzyOptions,
4431
+ filters,
4432
+ filterInlineLimit = DEFAULT_FILTER_INLINE_LIMIT,
4433
+ showFilterBadges = true,
4434
+ showClearFiltersButton = true,
4435
+ sortOptions,
4436
+ defaultSort,
4437
+ sort,
4438
+ onSortChange,
4439
+ // --- Column level ---
4440
+ columnFooter,
4441
+ columnWidth = DEFAULT_COLUMN_WIDTH,
4442
+ countDisplay = "tag",
4443
+ collapsedStages,
4444
+ onCollapsedStagesChange,
4445
+ // --- Metrics panel ---
4446
+ metrics,
4447
+ // Array of stat items or a ReactNode for full override
4448
+ showMetrics: controlledShowMetrics,
4449
+ onMetricsToggle,
4450
+ // --- State (controlled) ---
4451
+ searchValue,
4452
+ onSearchChange,
4453
+ filterValues,
4454
+ onFilterChange,
4455
+ onParamsChange,
4456
+ loading = false,
4457
+ error,
4458
+ // --- Labels ---
4459
+ labels: labelsProp,
4460
+ renderEmptyState,
4461
+ renderLoadingState,
4462
+ renderErrorState
4463
+ }) => {
4464
+ var _a;
4465
+ const labels = (0, import_react4.useMemo)(() => ({ ...DEFAULT_LABELS, ...labelsProp || {} }), [labelsProp]);
4466
+ const [internalSearch, setInternalSearch] = (0, import_react4.useState)(searchValue != null ? searchValue : "");
4467
+ const [internalFilters, setInternalFilters] = (0, import_react4.useState)(() => {
4468
+ const init = {};
4469
+ (filters || []).forEach((f) => {
4470
+ init[f.name] = getEmptyFilterValue2(f);
4471
+ });
4472
+ return init;
4473
+ });
4474
+ const [internalSort, setInternalSort] = (0, import_react4.useState)(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
4475
+ const [internalCollapsed, setInternalCollapsed] = (0, import_react4.useState)([]);
4476
+ const [internalExpanded, setInternalExpanded] = (0, import_react4.useState)([]);
4477
+ const [internalSelection, setInternalSelection] = (0, import_react4.useState)([]);
4478
+ const [internalShowMetrics, setInternalShowMetrics] = (0, import_react4.useState)(false);
4479
+ const [transitionPrompts, setTransitionPrompts] = (0, import_react4.useState)({});
4480
+ const selectionResetRef = (0, import_react4.useRef)("");
4481
+ const resolvedShowMetrics = controlledShowMetrics != null ? controlledShowMetrics : internalShowMetrics;
4482
+ const toggleMetrics = (0, import_react4.useCallback)(() => {
4483
+ const next = !resolvedShowMetrics;
4484
+ if (onMetricsToggle) onMetricsToggle(next);
4485
+ if (controlledShowMetrics == null) setInternalShowMetrics(next);
4486
+ }, [resolvedShowMetrics, onMetricsToggle, controlledShowMetrics]);
4487
+ const effectiveColumnWidth = Math.max(MIN_COLUMN_WIDTH, columnWidth || DEFAULT_COLUMN_WIDTH);
4488
+ const resolvedSearch = searchValue != null ? searchValue : internalSearch;
4489
+ const resolvedFilters = filterValues != null ? filterValues : internalFilters;
4490
+ const resolvedSort = sort != null ? sort : internalSort;
4491
+ const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
4492
+ const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
4493
+ const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
4494
+ const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
4495
+ const stagesByValue = (0, import_react4.useMemo)(() => {
4496
+ const map = {};
4497
+ for (const stage of stages || []) {
4498
+ map[stage.value] = stage;
4499
+ }
4500
+ return map;
4501
+ }, [stages]);
4502
+ const fireParamsChange = (0, import_react4.useCallback)((overrides = {}) => {
4503
+ if (!onParamsChange) return;
4504
+ onParamsChange({
4505
+ search: overrides.search != null ? overrides.search : resolvedSearch,
4506
+ filters: overrides.filters != null ? overrides.filters : resolvedFilters,
4507
+ sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
4508
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
4509
+ });
4510
+ }, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
4511
+ const debounceRef = (0, import_react4.useRef)(null);
4512
+ (0, import_react4.useEffect)(() => () => {
4513
+ if (debounceRef.current) clearTimeout(debounceRef.current);
4514
+ }, []);
4515
+ const handleSearch = (0, import_react4.useCallback)(
4516
+ (val) => {
4517
+ if (searchValue == null) setInternalSearch(val);
4518
+ const dispatch = () => {
4519
+ if (onSearchChange) onSearchChange(val);
4520
+ fireParamsChange({ search: val });
4521
+ };
4522
+ if (searchDebounce > 0) {
4523
+ if (debounceRef.current) clearTimeout(debounceRef.current);
4524
+ debounceRef.current = setTimeout(dispatch, searchDebounce);
4525
+ } else {
4526
+ dispatch();
4527
+ }
4528
+ },
4529
+ [fireParamsChange, onSearchChange, searchValue, searchDebounce]
4530
+ );
4531
+ const handleFilter = (0, import_react4.useCallback)(
4532
+ (name, val) => {
4533
+ const next = { ...resolvedFilters, [name]: val };
4534
+ if (filterValues == null) setInternalFilters(next);
4535
+ if (onFilterChange) onFilterChange(next);
4536
+ fireParamsChange({ filters: next });
4537
+ },
4538
+ [fireParamsChange, onFilterChange, filterValues, resolvedFilters]
4539
+ );
4540
+ const handleFilterRemove = (0, import_react4.useCallback)(
4541
+ (key) => {
4542
+ if (key === "all") {
4543
+ const cleared = {};
4544
+ (filters || []).forEach((f) => {
4545
+ cleared[f.name] = getEmptyFilterValue2(f);
4546
+ });
4547
+ if (filterValues == null) setInternalFilters(cleared);
4548
+ if (onFilterChange) onFilterChange(cleared);
4549
+ fireParamsChange({ filters: cleared });
4550
+ return;
4551
+ }
4552
+ const filter = (filters || []).find((f) => f.name === key);
4553
+ const emptyVal = filter ? getEmptyFilterValue2(filter) : "";
4554
+ const next = { ...resolvedFilters, [key]: emptyVal };
4555
+ if (filterValues == null) setInternalFilters(next);
4556
+ if (onFilterChange) onFilterChange(next);
4557
+ fireParamsChange({ filters: next });
4558
+ },
4559
+ [filters, filterValues, fireParamsChange, onFilterChange, resolvedFilters]
4560
+ );
4561
+ const handleSort = (0, import_react4.useCallback)(
4562
+ (val) => {
4563
+ if (onSortChange) onSortChange(val);
4564
+ if (sort == null) setInternalSort(val);
4565
+ fireParamsChange({ sort: val });
4566
+ },
4567
+ [fireParamsChange, onSortChange, sort]
4568
+ );
4569
+ const handleCollapsed = (0, import_react4.useCallback)(
4570
+ (stageValue) => {
4571
+ const next = resolvedCollapsed.includes(stageValue) ? resolvedCollapsed.filter((v) => v !== stageValue) : [...resolvedCollapsed, stageValue];
4572
+ if (onCollapsedStagesChange) onCollapsedStagesChange(next);
4573
+ if (collapsedStages == null) setInternalCollapsed(next);
4574
+ fireParamsChange({ collapsedStages: next });
4575
+ },
4576
+ [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
4577
+ );
4578
+ const handleExpanded = (0, import_react4.useCallback)(
4579
+ (stageValue) => {
4580
+ const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
4581
+ if (onExpandedStagesChange) onExpandedStagesChange(next);
4582
+ if (expandedStages == null) setInternalExpanded(next);
4583
+ },
4584
+ [resolvedExpanded, expandedStages, onExpandedStagesChange]
4585
+ );
4586
+ const handleToggleSelect = (0, import_react4.useCallback)(
4587
+ (rowId) => {
4588
+ const next = resolvedSelection.includes(rowId) ? resolvedSelection.filter((id) => id !== rowId) : [...resolvedSelection, rowId];
4589
+ if (onSelectionChange) onSelectionChange(next);
4590
+ if (selectedIds == null) setInternalSelection(next);
4591
+ },
4592
+ [resolvedSelection, selectedIds, onSelectionChange]
4593
+ );
4594
+ const clearTransitionPrompt = (0, import_react4.useCallback)((rowId) => {
4595
+ setTransitionPrompts((prev) => {
4596
+ if (!Object.prototype.hasOwnProperty.call(prev, rowId)) return prev;
4597
+ const next = { ...prev };
4598
+ delete next[rowId];
4599
+ return next;
4600
+ });
4601
+ }, []);
4602
+ const commitStageChange = (0, import_react4.useCallback)(
4603
+ (row, newStage, oldStage, result) => {
4604
+ clearTransitionPrompt(row[rowIdField]);
4605
+ if (onStageChange) onStageChange(row, newStage, oldStage, result);
4606
+ },
4607
+ [clearTransitionPrompt, onStageChange, rowIdField]
4608
+ );
4609
+ const selectionQueryKey = (0, import_react4.useMemo)(() => {
4610
+ if (!resetSelectionOnQueryChange) return "";
4611
+ return toStableKey2({
4612
+ search: resolvedSearch,
4613
+ filters: resolvedFilters,
4614
+ sort: resolvedSort || null
4615
+ });
4616
+ }, [resetSelectionOnQueryChange, resolvedFilters, resolvedSearch, resolvedSort]);
4617
+ const combinedSelectionResetKey = (0, import_react4.useMemo)(
4618
+ () => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey2(selectionResetKey)}`,
4619
+ [selectionQueryKey, selectionResetKey]
4620
+ );
4621
+ (0, import_react4.useEffect)(() => {
4622
+ if (!selectable || selectedIds != null) {
4623
+ selectionResetRef.current = combinedSelectionResetKey;
4624
+ return;
4625
+ }
4626
+ if (selectionResetRef.current && selectionResetRef.current !== combinedSelectionResetKey) {
4627
+ setInternalSelection([]);
4628
+ }
4629
+ selectionResetRef.current = combinedSelectionResetKey;
4630
+ }, [combinedSelectionResetKey, selectable, selectedIds]);
4631
+ const getStageFor = (0, import_react4.useCallback)(
4632
+ (row) => {
4633
+ if (typeof groupBy === "function") return groupBy(row);
4634
+ return row[groupBy];
4635
+ },
4636
+ [groupBy]
4637
+ );
4638
+ const filteredData = (0, import_react4.useMemo)(() => {
4639
+ let result = data;
4640
+ for (const filter of filters || []) {
4641
+ const val = resolvedFilters[filter.name];
4642
+ if (!isFilterActive2(filter, val)) continue;
4643
+ const type = filter.type || "select";
4644
+ if (filter.filterFn) {
4645
+ result = result.filter((row) => filter.filterFn(row, val));
4646
+ } else if (type === "multiselect") {
4647
+ result = result.filter((row) => val.includes(row[filter.name]));
4648
+ } else if (type === "dateRange") {
4649
+ const fromTs = dateToTimestamp2(val.from);
4650
+ const toTs = val.to ? dateToTimestamp2(val.to) + 864e5 - 1 : null;
4651
+ result = result.filter((row) => {
4652
+ const rowTs = new Date(row[filter.name]).getTime();
4653
+ if (Number.isNaN(rowTs)) return false;
4654
+ if (fromTs && rowTs < fromTs) return false;
4655
+ if (toTs && rowTs > toTs) return false;
4656
+ return true;
4657
+ });
4658
+ } else {
4659
+ result = result.filter((row) => row[filter.name] === val);
4660
+ }
4661
+ }
4662
+ const searchLower = (resolvedSearch || "").toLowerCase().trim();
4663
+ if (searchEnabled && searchLower) {
4664
+ if (fuzzySearch) {
4665
+ const fuse = new import_fuse2.default(result, {
4666
+ keys: searchFields,
4667
+ threshold: 0.4,
4668
+ distance: 100,
4669
+ ignoreLocation: true,
4670
+ ...fuzzyOptions
4671
+ });
4672
+ result = fuse.search(searchLower).map((r) => r.item);
4673
+ } else {
4674
+ result = result.filter(
4675
+ (row) => searchFields.some(
4676
+ (f) => String(row[f] || "").toLowerCase().includes(searchLower)
4677
+ )
4678
+ );
4679
+ }
4680
+ }
4681
+ return result;
4682
+ }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
4683
+ const buckets = (0, import_react4.useMemo)(() => {
4684
+ const map = {};
4685
+ for (const stage of stages) map[stage.value] = [];
4686
+ for (const row of filteredData) {
4687
+ const key = getStageFor(row);
4688
+ if (map[key]) {
4689
+ map[key].push(row);
4690
+ } else if (stages.length > 0) {
4691
+ if (!map.__unknown) map.__unknown = [];
4692
+ map.__unknown.push(row);
4693
+ }
4694
+ }
4695
+ return map;
4696
+ }, [filteredData, stages, getStageFor]);
4697
+ const sortComparator = (0, import_react4.useMemo)(() => {
4698
+ if (!sortOptions || !resolvedSort) return null;
4699
+ const opt = sortOptions.find((s) => s.value === resolvedSort);
4700
+ return (opt == null ? void 0 : opt.comparator) || null;
4701
+ }, [sortOptions, resolvedSort]);
4702
+ const sortedBuckets = (0, import_react4.useMemo)(() => {
4703
+ if (!sortComparator) return buckets;
4704
+ const out = {};
4705
+ for (const key of Object.keys(buckets)) {
4706
+ out[key] = [...buckets[key]].sort(sortComparator);
4707
+ }
4708
+ return out;
4709
+ }, [buckets, sortComparator]);
4710
+ const activeChips = (0, import_react4.useMemo)(() => {
4711
+ const chips = [];
4712
+ for (const filter of filters || []) {
4713
+ const val = resolvedFilters[filter.name];
4714
+ if (!isFilterActive2(filter, val)) continue;
4715
+ const type = filter.type || "select";
4716
+ const prefix = filter.chipLabel || filter.placeholder || filter.name;
4717
+ if (type === "multiselect") {
4718
+ const labelList = val.map((v) => {
4719
+ var _a2;
4720
+ return ((_a2 = filter.options.find((o) => o.value === v)) == null ? void 0 : _a2.label) || v;
4721
+ }).join(", ");
4722
+ chips.push({ key: filter.name, label: `${prefix}: ${labelList}` });
4723
+ } else if (type === "dateRange") {
4724
+ const parts = [];
4725
+ if (val.from) parts.push(`from ${formatDateChip2(val.from)}`);
4726
+ if (val.to) parts.push(`to ${formatDateChip2(val.to)}`);
4727
+ chips.push({ key: filter.name, label: `${prefix}: ${parts.join(" ")}` });
4728
+ } else {
4729
+ const opt = filter.options.find((o) => o.value === val);
4730
+ chips.push({ key: filter.name, label: `${prefix}: ${(opt == null ? void 0 : opt.label) || val}` });
4731
+ }
4732
+ }
4733
+ return chips;
4734
+ }, [filters, resolvedFilters]);
4735
+ const partitioned = (0, import_react4.useMemo)(() => partitionFields(cardFields || []), [cardFields]);
4736
+ const dividers = (0, import_react4.useMemo)(() => resolveDividers(cardDividers, cardDensity), [cardDividers, cardDensity]);
4737
+ const resolvedMaxBody = maxBodyLines || (cardDensity === "comfortable" ? 5 : 3);
4738
+ const resolvedStageControl = stageControl || (cardDensity === "comfortable" ? "select" : "menu");
4739
+ const resolvedStageControlPlacement = stageControlPlacement || (resolvedStageControl === "menu" ? "inline" : "separateRow");
4740
+ const handleStageChangeRequest = (0, import_react4.useCallback)(
4741
+ (row, newStage, oldStage) => {
4742
+ var _a2;
4743
+ if (!newStage || newStage === oldStage) return;
4744
+ const targetStage = stagesByValue[newStage];
4745
+ if (!targetStage || !canStageReceiveRow(targetStage, row, canMove)) return;
4746
+ const rowId = row[rowIdField];
4747
+ if ((_a2 = targetStage.onEnterRequired) == null ? void 0 : _a2.render) {
4748
+ setTransitionPrompts((prev) => ({
4749
+ ...prev,
4750
+ [rowId]: {
4751
+ row,
4752
+ fromStage: oldStage,
4753
+ toStage: newStage
4754
+ }
4755
+ }));
4756
+ return;
4757
+ }
4758
+ commitStageChange(row, newStage, oldStage);
4759
+ },
4760
+ [canMove, commitStageChange, rowIdField, stagesByValue]
4761
+ );
4762
+ const renderCardNode = (0, import_react4.useCallback)(
4763
+ (row, stage) => {
4764
+ var _a2;
4765
+ const rowId = row[rowIdField];
4766
+ const activePrompt = transitionPrompts[rowId];
4767
+ const promptStage = activePrompt ? stagesByValue[activePrompt.toStage] : null;
4768
+ if ((_a2 = promptStage == null ? void 0 : promptStage.onEnterRequired) == null ? void 0 : _a2.render) {
4769
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { key: rowId, compact: cardDensity === "compact" }, promptStage.onEnterRequired.render({
4770
+ row: activePrompt.row,
4771
+ fromStage: activePrompt.fromStage,
4772
+ toStage: activePrompt.toStage,
4773
+ onConfirm: (result) => commitStageChange(activePrompt.row, activePrompt.toStage, activePrompt.fromStage, result),
4774
+ onCancel: () => clearTransitionPrompt(rowId)
4775
+ }));
4776
+ }
4777
+ if (renderCard) {
4778
+ return renderCard(row, {
4779
+ stage,
4780
+ isChanging: isStageChanging ? isStageChanging(row) : false,
4781
+ density: cardDensity,
4782
+ onStageChange: (newStage) => handleStageChangeRequest(row, newStage, stage.value)
4783
+ });
4784
+ }
4785
+ return /* @__PURE__ */ import_react4.default.createElement(
4786
+ KanbanCard,
4787
+ {
4788
+ key: rowId,
4789
+ row,
4790
+ rowId,
4791
+ stage,
4792
+ stages,
4793
+ fields: partitioned,
4794
+ density: cardDensity,
4795
+ dividers,
4796
+ bodyAs: cardBodyAs,
4797
+ maxBodyLines: resolvedMaxBody,
4798
+ stageControl: resolvedStageControl,
4799
+ stageControlPlacement: resolvedStageControlPlacement,
4800
+ canMove,
4801
+ onStageChangeRequest: handleStageChangeRequest,
4802
+ isChanging: isStageChanging ? isStageChanging(row) : false,
4803
+ selectable,
4804
+ selected: resolvedSelection.includes(rowId),
4805
+ onToggleSelect: handleToggleSelect,
4806
+ labels
4807
+ }
4808
+ );
4809
+ },
4810
+ [
4811
+ clearTransitionPrompt,
4812
+ commitStageChange,
4813
+ renderCard,
4814
+ rowIdField,
4815
+ partitioned,
4816
+ cardDensity,
4817
+ dividers,
4818
+ resolvedMaxBody,
4819
+ resolvedStageControl,
4820
+ resolvedStageControlPlacement,
4821
+ canMove,
4822
+ isStageChanging,
4823
+ selectable,
4824
+ resolvedSelection,
4825
+ handleStageChangeRequest,
4826
+ handleToggleSelect,
4827
+ labels,
4828
+ stages,
4829
+ stagesByValue,
4830
+ transitionPrompts
4831
+ ]
4832
+ );
4833
+ const totalMatching = filteredData.length;
4834
+ const selectedCount = resolvedSelection.length;
4835
+ const singular = ((recordLabel == null ? void 0 : recordLabel.singular) || "card").toLowerCase();
4836
+ const plural = ((recordLabel == null ? void 0 : recordLabel.plural) || "cards").toLowerCase();
4837
+ const countLabel = (n) => n === 1 ? singular : plural;
4838
+ const resolvedSearchPlaceholder = searchPlaceholder ?? ((recordLabel == null ? void 0 : recordLabel.plural) ? `Search ${plural}...` : labels.search);
4839
+ const selectionBarProps = {
4840
+ selectedIds: resolvedSelection,
4841
+ selectedCount,
4842
+ displayCount: totalMatching,
4843
+ countLabel,
4844
+ allSelected: selectedCount >= totalMatching && totalMatching > 0,
4845
+ onSelectAll: () => {
4846
+ const allIds = filteredData.map((r) => r[rowIdField]);
4847
+ if (onSelectionChange) onSelectionChange(allIds);
4848
+ if (selectedIds == null) setInternalSelection(allIds);
4849
+ },
4850
+ onDeselectAll: () => {
4851
+ if (onSelectionChange) onSelectionChange([]);
4852
+ if (selectedIds == null) setInternalSelection([]);
4853
+ },
4854
+ selectionActions: selectionActions || [],
4855
+ labels
4856
+ };
4857
+ const mainContent = error ? renderErrorState ? renderErrorState({
4858
+ error,
4859
+ title: labels.errorTitle,
4860
+ message: typeof error === "string" ? error : labels.errorMessage
4861
+ }) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Alert, { variant: "danger", title: labels.errorTitle }, typeof error === "string" ? error : labels.errorMessage) : loading && data.length === 0 ? renderLoadingState ? renderLoadingState({ label: labels.loading }) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.LoadingSpinner, { size: "md", layout: "centered", label: labels.loading }) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
4862
+ title: labels.emptyTitle,
4863
+ message: labels.emptyMessage
4864
+ }) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, null, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.EmptyState, { title: labels.emptyTitle, layout: "vertical", reverseOrder: true }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, null, labels.emptyMessage))) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
4865
+ const stageRows = sortedBuckets[stage.value] || [];
4866
+ const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
4867
+ const isExpanded = resolvedExpanded.includes(stage.value);
4868
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
4869
+ const visibleRows = stageRows.slice(0, clamp);
4870
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
4871
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.AutoGrid, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ import_react4.default.createElement(
4872
+ KanbanColumn,
4873
+ {
4874
+ stage,
4875
+ rows: visibleRows,
4876
+ bucketCount: stageRows.length,
4877
+ totalCount: meta == null ? void 0 : meta.totalCount,
4878
+ hasMore: meta == null ? void 0 : meta.hasMore,
4879
+ loading: meta == null ? void 0 : meta.loading,
4880
+ error: meta == null ? void 0 : meta.error,
4881
+ onLoadMore,
4882
+ expanded: isExpanded,
4883
+ onToggleExpanded: () => handleExpanded(stage.value),
4884
+ collapsed: isCollapsed,
4885
+ onToggleCollapsed: () => handleCollapsed(stage.value),
4886
+ columnFooter,
4887
+ countDisplay,
4888
+ labels
4889
+ },
4890
+ visibleRows.map((row) => renderCardNode(row, stage))
4891
+ ));
4892
+ }));
4893
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react4.default.createElement(
4894
+ KanbanToolbar,
4895
+ {
4896
+ showSearch: searchEnabled,
4897
+ searchValue: resolvedSearch,
4898
+ searchPlaceholder: resolvedSearchPlaceholder,
4899
+ onSearchChange: handleSearch,
4900
+ filters,
4901
+ filterValues: resolvedFilters,
4902
+ onFilterChange: handleFilter,
4903
+ filterInlineLimit,
4904
+ showFilterBadges,
4905
+ showClearFiltersButton,
4906
+ activeChips,
4907
+ onFilterRemove: handleFilterRemove,
4908
+ sortOptions,
4909
+ sortValue: resolvedSort,
4910
+ onSortChange: handleSort,
4911
+ metrics,
4912
+ showMetrics: resolvedShowMetrics,
4913
+ onToggleMetrics: toggleMetrics,
4914
+ labels
4915
+ }
4916
+ ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ import_react4.default.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
4917
+ };
4918
+
4919
+ // packages/kanban/src/KanbanCardActions.jsx
4920
+ var import_react5 = __toESM(require("react"));
4921
+ var import_ui_extensions5 = require("@hubspot/ui-extensions");
4922
+ var renderButton = (action, display, size) => {
4923
+ const { key, label, icon, tooltip, variant = "transparent", disabled, onClick, href } = action;
4924
+ const buttonProps = {
4925
+ key: key || label,
4926
+ variant,
4927
+ size,
4928
+ disabled,
4929
+ tooltip: tooltip || label
4930
+ };
4931
+ if (href) buttonProps.href = typeof href === "string" ? href : href;
4932
+ if (onClick) buttonProps.onClick = onClick;
4933
+ if (display === "icon") {
4934
+ return /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Button, { ...buttonProps }, icon ? /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Icon, { name: icon, size: "sm", screenReaderText: label }) : label);
4935
+ }
4936
+ if (display === "label") {
4937
+ return /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Button, { ...buttonProps }, label);
4938
+ }
4939
+ return /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Button, { ...buttonProps }, /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Flex, { direction: "row", align: "center", gap: "xs" }, icon ? /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Icon, { name: icon, size: "sm", screenReaderText: label }) : null, /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Text, { variant: "microcopy" }, label)));
4940
+ };
4941
+ var KanbanCardActions = ({
4942
+ actions = [],
4943
+ display = "icon",
4944
+ // Default to xs — the Button's own padding at sm visibly pushes icons away
4945
+ // from Tile edges. Icon glyph inside stays pinned to size="sm" in renderButton
4946
+ // so the icon itself doesn't shrink, only the hit-target/button padding does.
4947
+ size = "xs",
4948
+ align = "end",
4949
+ gap = "flush",
4950
+ separator = "none",
4951
+ overflowAfter,
4952
+ overflowLabel = "More"
4953
+ }) => {
4954
+ const visible = actions.filter((a) => a.visible !== false);
4955
+ if (visible.length === 0) return null;
4956
+ const cutoff = typeof overflowAfter === "number" ? Math.max(0, overflowAfter) : visible.length;
4957
+ const primary = visible.slice(0, cutoff);
4958
+ const overflow = visible.slice(cutoff);
4959
+ const renderedPrimary = primary.map((action, idx) => {
4960
+ const button = renderButton(action, display, size);
4961
+ if (separator === "pipe" && idx > 0) {
4962
+ return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, { key: `${action.key || action.label}-sep` }, /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Text, { variant: "microcopy" }, "|"), button);
4963
+ }
4964
+ return button;
4965
+ });
4966
+ const renderedOverflow = overflow.length > 0 ? /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Dropdown, { variant: "transparent", buttonText: overflowLabel, buttonSize: size, key: "overflow" }, overflow.map((action) => /* @__PURE__ */ import_react5.default.createElement(
4967
+ import_ui_extensions5.Dropdown.ButtonItem,
4968
+ {
4969
+ key: action.key || action.label,
4970
+ onClick: action.onClick
4971
+ },
4972
+ action.label
4973
+ ))) : null;
4974
+ const justify = align === "end" ? "end" : align === "between" ? "between" : "start";
4975
+ return /* @__PURE__ */ import_react5.default.createElement(import_ui_extensions5.Flex, { direction: "row", align: "end", justify, gap }, renderedPrimary, renderedOverflow);
4976
+ };
4977
+
4978
+ // src/common-components/AutoTag.js
4979
+ var import_react6 = __toESM(require("react"));
4980
+ var import_ui_extensions6 = require("@hubspot/ui-extensions");
4981
+
3487
4982
  // src/utils/tagVariants.js
3488
4983
  var DEFAULT_VARIANT = "default";
3489
4984
  var DANGER_VARIANT = "danger";
@@ -3638,16 +5133,16 @@ var AutoTag = ({
3638
5133
  overrides,
3639
5134
  fallback
3640
5135
  });
3641
- return import_react3.default.createElement(
3642
- import_ui_extensions3.Tag,
5136
+ return import_react6.default.createElement(
5137
+ import_ui_extensions6.Tag,
3643
5138
  { variant: resolvedVariant, ...props },
3644
5139
  displayValue
3645
5140
  );
3646
5141
  };
3647
5142
 
3648
5143
  // src/common-components/AutoStatusTag.js
3649
- var import_react4 = __toESM(require("react"));
3650
- var import_ui_extensions4 = require("@hubspot/ui-extensions");
5144
+ var import_react7 = __toESM(require("react"));
5145
+ var import_ui_extensions7 = require("@hubspot/ui-extensions");
3651
5146
  var AutoStatusTag = ({
3652
5147
  value,
3653
5148
  status,
@@ -3663,28 +5158,261 @@ var AutoStatusTag = ({
3663
5158
  overrides,
3664
5159
  fallback
3665
5160
  });
3666
- return import_react4.default.createElement(
3667
- import_ui_extensions4.StatusTag,
5161
+ return import_react7.default.createElement(
5162
+ import_ui_extensions7.StatusTag,
3668
5163
  { variant: resolvedVariant, ...props },
3669
5164
  displayValue
3670
5165
  );
3671
5166
  };
3672
5167
 
5168
+ // src/common-components/AvatarStack.js
5169
+ var import_react8 = __toESM(require("react"));
5170
+ var import_ui_extensions8 = require("@hubspot/ui-extensions");
5171
+ var DEFAULT_COLORS = [
5172
+ "#0091ae",
5173
+ "#8B0000",
5174
+ "#ff5c35",
5175
+ "#00bda5",
5176
+ "#fdcc00",
5177
+ "#516f90",
5178
+ "#003366",
5179
+ "#8e7cc3"
5180
+ ];
5181
+ var SIZE_TOKENS = {
5182
+ xs: 16,
5183
+ "extra-small": 16,
5184
+ sm: 20,
5185
+ "small": 20,
5186
+ md: 24,
5187
+ "med": 24,
5188
+ "medium": 24,
5189
+ lg: 32,
5190
+ "large": 32,
5191
+ xl: 40,
5192
+ "extra-large": 40
5193
+ };
5194
+ var resolveSize = (size) => {
5195
+ if (typeof size === "number") return size;
5196
+ if (typeof size === "string" && SIZE_TOKENS[size] != null) return SIZE_TOKENS[size];
5197
+ return 24;
5198
+ };
5199
+ var isImageUri = (s) => typeof s === "string" && /^(https?:|data:image\/)/i.test(s);
5200
+ var escapeXmlAttr = (s) => String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5201
+ var pickColor = (key, palette, index) => {
5202
+ if (!key) return palette[index % palette.length];
5203
+ const code = String(key).charCodeAt(0) || 0;
5204
+ return palette[(code + index) % palette.length];
5205
+ };
5206
+ var normalizeEntry = (entry) => {
5207
+ if (entry == null) return null;
5208
+ if (typeof entry === "string") {
5209
+ if (entry.length === 0) return null;
5210
+ if (isImageUri(entry)) return { src: entry };
5211
+ return { letter: entry.slice(0, 2).toUpperCase() };
5212
+ }
5213
+ if (typeof entry === "object") {
5214
+ if (entry.src) return { src: entry.src, letter: entry.letter };
5215
+ if (entry.letter) return { letter: String(entry.letter).slice(0, 2).toUpperCase(), color: entry.color };
5216
+ }
5217
+ return null;
5218
+ };
5219
+ var makeAvatarStackDataUri = (rawEntries, opts = {}) => {
5220
+ const {
5221
+ size: sizeProp = "medium",
5222
+ step: stepProp,
5223
+ overlap: overlapProp,
5224
+ maxVisible = 4,
5225
+ colors = DEFAULT_COLORS,
5226
+ overflowBg = HS_NEUTRAL_CHIP,
5227
+ overflowColor = HS_TEXT_COLOR,
5228
+ fontFamily = HS_FONT_FAMILY
5229
+ } = opts;
5230
+ const size = resolveSize(sizeProp);
5231
+ let step;
5232
+ if (stepProp != null) {
5233
+ step = stepProp;
5234
+ } else if (overlapProp != null) {
5235
+ const clampedOverlap = Math.max(0, Math.min(size - 1, overlapProp));
5236
+ step = size - clampedOverlap;
5237
+ } else {
5238
+ step = Math.round(size * 0.65);
5239
+ }
5240
+ const entries = (rawEntries || []).map(normalizeEntry).filter(Boolean);
5241
+ if (entries.length === 0) return null;
5242
+ const visible = entries.slice(0, maxVisible);
5243
+ const overflowCount = entries.length - visible.length;
5244
+ const slots = overflowCount > 0 ? [...entries.slice(0, maxVisible - 1), { overflow: overflowCount }] : visible;
5245
+ const count = slots.length;
5246
+ const r = size / 2;
5247
+ const haloR = r + 1;
5248
+ const width = size + (count - 1) * step;
5249
+ const height = size;
5250
+ const defs = `<defs><clipPath id="hsuixAvatarClip"><circle cx="${r}" cy="${r}" r="${r}"/></clipPath></defs>`;
5251
+ const fontFamilyAttr = fontFamily.replace(/"/g, "&quot;");
5252
+ const pieces = slots.map((slot, i) => {
5253
+ const cx = r + i * step;
5254
+ const tx = i * step;
5255
+ const halo = i > 0 ? `<circle cx="${cx}" cy="${r}" r="${haloR}" fill="#ffffff" />` : "";
5256
+ if (slot.overflow) {
5257
+ return halo + `<circle cx="${cx}" cy="${r}" r="${r}" fill="${overflowBg}" /><text x="${cx}" y="${r + 1}" text-anchor="middle" dominant-baseline="central" font-family="${fontFamilyAttr}" font-size="${Math.round(size * 0.42)}" font-weight="700" fill="${overflowColor}">+${slot.overflow}</text>`;
5258
+ }
5259
+ if (slot.src) {
5260
+ return halo + `<g transform="translate(${tx}, 0)"><image href="${escapeXmlAttr(slot.src)}" x="0" y="0" width="${size}" height="${size}" preserveAspectRatio="xMidYMid slice" clip-path="url(#hsuixAvatarClip)" /></g>`;
5261
+ }
5262
+ const letter = slot.letter || "?";
5263
+ const bgColor = slot.color || pickColor(letter, colors, i);
5264
+ return halo + `<circle cx="${cx}" cy="${r}" r="${r}" fill="${bgColor}" /><text x="${cx}" y="${r + 1}" text-anchor="middle" dominant-baseline="central" font-family="${fontFamilyAttr}" font-size="${Math.round(size * 0.46)}" font-weight="700" fill="#ffffff">${escapeXmlAttr(letter)}</text>`;
5265
+ });
5266
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">` + defs + pieces.join("") + `</svg>`;
5267
+ return {
5268
+ src: `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`,
5269
+ width,
5270
+ height,
5271
+ count
5272
+ };
5273
+ };
5274
+ var AvatarStack = ({
5275
+ items,
5276
+ size,
5277
+ overlap,
5278
+ step,
5279
+ maxVisible,
5280
+ colors,
5281
+ overflowBg,
5282
+ overflowColor,
5283
+ fontFamily,
5284
+ alt
5285
+ }) => {
5286
+ const stack = makeAvatarStackDataUri(items, {
5287
+ size,
5288
+ overlap,
5289
+ step,
5290
+ maxVisible,
5291
+ colors,
5292
+ overflowBg,
5293
+ overflowColor,
5294
+ fontFamily
5295
+ });
5296
+ if (!stack) return null;
5297
+ return import_react8.default.createElement(import_ui_extensions8.Image, {
5298
+ src: stack.src,
5299
+ width: stack.width,
5300
+ height: stack.height,
5301
+ alt: alt ?? `${items.length} associated records`
5302
+ });
5303
+ };
5304
+
5305
+ // src/common-components/datePresets.js
5306
+ var HS_DATE_PRESETS = [
5307
+ { label: "Today", value: "today" },
5308
+ { label: "Yesterday", value: "yesterday" },
5309
+ { label: "Tomorrow", value: "tomorrow" },
5310
+ { label: "This week", value: "this_week" },
5311
+ { label: "Last week", value: "last_week" },
5312
+ { label: "Last 7 days", value: "7d" },
5313
+ { label: "Last 30 days", value: "30d" },
5314
+ { label: "Last 90 days", value: "90d" },
5315
+ { label: "This month", value: "this_month" },
5316
+ { label: "Last month", value: "last_month" },
5317
+ { label: "This quarter", value: "this_quarter" },
5318
+ { label: "Last quarter", value: "last_quarter" },
5319
+ { label: "This year", value: "this_year" },
5320
+ { label: "Last year", value: "last_year" }
5321
+ ];
5322
+ var HS_DATE_DIRECTION_LABELS = {
5323
+ asc: "Ascending",
5324
+ desc: "Descending"
5325
+ };
5326
+
3673
5327
  // src/common-components/KeyValueList.js
3674
- var import_react5 = __toESM(require("react"));
3675
- var import_ui_extensions5 = require("@hubspot/ui-extensions");
5328
+ var import_react9 = __toESM(require("react"));
5329
+ var import_ui_extensions9 = require("@hubspot/ui-extensions");
5330
+ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
5331
+ const rows = items.map(
5332
+ (item, index) => import_react9.default.createElement(
5333
+ import_ui_extensions9.DescriptionListItem,
5334
+ {
5335
+ key: item.key ?? item.label ?? `kv-${index}`,
5336
+ label: item.label
5337
+ },
5338
+ item.value
5339
+ )
5340
+ );
5341
+ return import_react9.default.createElement(
5342
+ import_ui_extensions9.Flex,
5343
+ { direction: "column", gap },
5344
+ import_react9.default.createElement(import_ui_extensions9.DescriptionList, { direction }, ...rows)
5345
+ );
5346
+ };
3676
5347
 
3677
5348
  // src/common-components/SectionHeader.js
3678
- var import_react6 = __toESM(require("react"));
3679
- var import_ui_extensions6 = require("@hubspot/ui-extensions");
5349
+ var import_react10 = __toESM(require("react"));
5350
+ var import_ui_extensions10 = require("@hubspot/ui-extensions");
5351
+ var SectionHeader = ({
5352
+ title,
5353
+ description,
5354
+ actions,
5355
+ children,
5356
+ gap = "xs",
5357
+ titleAs = "h2"
5358
+ }) => {
5359
+ const body = [];
5360
+ if (title != null) {
5361
+ body.push(import_react10.default.createElement(import_ui_extensions10.Heading, { key: "title", as: titleAs }, title));
5362
+ }
5363
+ if (description != null) {
5364
+ body.push(
5365
+ import_react10.default.createElement(
5366
+ import_ui_extensions10.Text,
5367
+ { key: "description", variant: "microcopy" },
5368
+ description
5369
+ )
5370
+ );
5371
+ }
5372
+ if (children != null) {
5373
+ body.push(children);
5374
+ }
5375
+ const content = import_react10.default.createElement(import_ui_extensions10.Flex, { direction: "column", gap }, ...body);
5376
+ if (actions == null) return content;
5377
+ return import_react10.default.createElement(
5378
+ import_ui_extensions10.Flex,
5379
+ { direction: "row", justify: "between", align: "start", gap: "sm" },
5380
+ content,
5381
+ actions
5382
+ );
5383
+ };
3680
5384
  // Annotate the CommonJS export names for ESM import in node:
3681
5385
  0 && (module.exports = {
3682
5386
  AutoStatusTag,
3683
5387
  AutoTag,
5388
+ AvatarStack,
5389
+ DEFAULT_SVG_FONT_WEIGHT,
3684
5390
  DataTable,
3685
5391
  FormBuilder,
5392
+ HS_DATE_DIRECTION_LABELS,
5393
+ HS_DATE_PRESETS,
5394
+ HS_FONT_FAMILY,
5395
+ HS_MUTED_TEXT,
5396
+ HS_NEUTRAL_CHIP,
5397
+ HS_SUBTLE_BG,
5398
+ HS_TAG_BORDER_RADIUS,
5399
+ HS_TAG_BORDER_WIDTH,
5400
+ HS_TAG_FONT_SIZE,
5401
+ HS_TAG_LINE_HEIGHT,
5402
+ HS_TAG_PADDING_X,
5403
+ HS_TAG_PADDING_Y,
5404
+ HS_TAG_SUBTLE_BORDER,
5405
+ HS_TAG_TEXT_COLOR,
5406
+ HS_TEXT_COLOR,
5407
+ Kanban,
5408
+ KanbanCardActions,
5409
+ KeyValueList,
5410
+ SectionHeader,
5411
+ StyledText,
3686
5412
  createStatusTagSortComparator,
3687
5413
  getAutoStatusTagVariant,
3688
5414
  getAutoTagVariant,
5415
+ makeAvatarStackDataUri,
5416
+ makeStyledTextDataUri,
3689
5417
  useFormPrefill
3690
5418
  });