hs-uix 1.5.0 → 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.
@@ -156,12 +156,189 @@ var AutoStatusTag = ({
156
156
  );
157
157
  };
158
158
 
159
- // src/common-components/KeyValueList.js
159
+ // src/common-components/AvatarStack.js
160
160
  import React3 from "react";
161
+ import { Image } from "@hubspot/ui-extensions";
162
+
163
+ // src/common-components/svgDefaults.js
164
+ var HS_FONT_FAMILY = '"Lexend Deca", Helvetica, Arial, sans-serif';
165
+ var HS_TEXT_COLOR = "#33475b";
166
+ var HS_SUBTLE_BG = "#F5F8FA";
167
+ var HS_MUTED_TEXT = "#7C98B6";
168
+ var HS_NEUTRAL_CHIP = "#CBD6E2";
169
+ var HS_TAG_SUBTLE_BORDER = "#7C98B6";
170
+ var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
171
+ var HS_TAG_FONT_SIZE = 12;
172
+ var HS_TAG_LINE_HEIGHT = 22;
173
+ var HS_TAG_PADDING_X = 8;
174
+ var HS_TAG_PADDING_Y = 0;
175
+ var HS_TAG_BORDER_RADIUS = 0;
176
+ var HS_TAG_BORDER_WIDTH = 1;
177
+ var DEFAULT_SVG_FONT_WEIGHT = 600;
178
+
179
+ // src/common-components/AvatarStack.js
180
+ var DEFAULT_COLORS = [
181
+ "#0091ae",
182
+ "#8B0000",
183
+ "#ff5c35",
184
+ "#00bda5",
185
+ "#fdcc00",
186
+ "#516f90",
187
+ "#003366",
188
+ "#8e7cc3"
189
+ ];
190
+ var SIZE_TOKENS = {
191
+ xs: 16,
192
+ "extra-small": 16,
193
+ sm: 20,
194
+ "small": 20,
195
+ md: 24,
196
+ "med": 24,
197
+ "medium": 24,
198
+ lg: 32,
199
+ "large": 32,
200
+ xl: 40,
201
+ "extra-large": 40
202
+ };
203
+ var resolveSize = (size) => {
204
+ if (typeof size === "number") return size;
205
+ if (typeof size === "string" && SIZE_TOKENS[size] != null) return SIZE_TOKENS[size];
206
+ return 24;
207
+ };
208
+ var isImageUri = (s) => typeof s === "string" && /^(https?:|data:image\/)/i.test(s);
209
+ var escapeXmlAttr = (s) => String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
210
+ var pickColor = (key, palette, index) => {
211
+ if (!key) return palette[index % palette.length];
212
+ const code = String(key).charCodeAt(0) || 0;
213
+ return palette[(code + index) % palette.length];
214
+ };
215
+ var normalizeEntry = (entry) => {
216
+ if (entry == null) return null;
217
+ if (typeof entry === "string") {
218
+ if (entry.length === 0) return null;
219
+ if (isImageUri(entry)) return { src: entry };
220
+ return { letter: entry.slice(0, 2).toUpperCase() };
221
+ }
222
+ if (typeof entry === "object") {
223
+ if (entry.src) return { src: entry.src, letter: entry.letter };
224
+ if (entry.letter) return { letter: String(entry.letter).slice(0, 2).toUpperCase(), color: entry.color };
225
+ }
226
+ return null;
227
+ };
228
+ var makeAvatarStackDataUri = (rawEntries, opts = {}) => {
229
+ const {
230
+ size: sizeProp = "medium",
231
+ step: stepProp,
232
+ overlap: overlapProp,
233
+ maxVisible = 4,
234
+ colors = DEFAULT_COLORS,
235
+ overflowBg = HS_NEUTRAL_CHIP,
236
+ overflowColor = HS_TEXT_COLOR,
237
+ fontFamily = HS_FONT_FAMILY
238
+ } = opts;
239
+ const size = resolveSize(sizeProp);
240
+ let step;
241
+ if (stepProp != null) {
242
+ step = stepProp;
243
+ } else if (overlapProp != null) {
244
+ const clampedOverlap = Math.max(0, Math.min(size - 1, overlapProp));
245
+ step = size - clampedOverlap;
246
+ } else {
247
+ step = Math.round(size * 0.65);
248
+ }
249
+ const entries = (rawEntries || []).map(normalizeEntry).filter(Boolean);
250
+ if (entries.length === 0) return null;
251
+ const visible = entries.slice(0, maxVisible);
252
+ const overflowCount = entries.length - visible.length;
253
+ const slots = overflowCount > 0 ? [...entries.slice(0, maxVisible - 1), { overflow: overflowCount }] : visible;
254
+ const count = slots.length;
255
+ const r = size / 2;
256
+ const haloR = r + 1;
257
+ const width = size + (count - 1) * step;
258
+ const height = size;
259
+ const defs = `<defs><clipPath id="hsuixAvatarClip"><circle cx="${r}" cy="${r}" r="${r}"/></clipPath></defs>`;
260
+ const fontFamilyAttr = fontFamily.replace(/"/g, "&quot;");
261
+ const pieces = slots.map((slot, i) => {
262
+ const cx = r + i * step;
263
+ const tx = i * step;
264
+ const halo = i > 0 ? `<circle cx="${cx}" cy="${r}" r="${haloR}" fill="#ffffff" />` : "";
265
+ if (slot.overflow) {
266
+ 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>`;
267
+ }
268
+ if (slot.src) {
269
+ 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>`;
270
+ }
271
+ const letter = slot.letter || "?";
272
+ const bgColor = slot.color || pickColor(letter, colors, i);
273
+ 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>`;
274
+ });
275
+ 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>`;
276
+ return {
277
+ src: `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`,
278
+ width,
279
+ height,
280
+ count
281
+ };
282
+ };
283
+ var AvatarStack = ({
284
+ items,
285
+ size,
286
+ overlap,
287
+ step,
288
+ maxVisible,
289
+ colors,
290
+ overflowBg,
291
+ overflowColor,
292
+ fontFamily,
293
+ alt
294
+ }) => {
295
+ const stack = makeAvatarStackDataUri(items, {
296
+ size,
297
+ overlap,
298
+ step,
299
+ maxVisible,
300
+ colors,
301
+ overflowBg,
302
+ overflowColor,
303
+ fontFamily
304
+ });
305
+ if (!stack) return null;
306
+ return React3.createElement(Image, {
307
+ src: stack.src,
308
+ width: stack.width,
309
+ height: stack.height,
310
+ alt: alt ?? `${items.length} associated records`
311
+ });
312
+ };
313
+
314
+ // src/common-components/datePresets.js
315
+ var HS_DATE_PRESETS = [
316
+ { label: "Today", value: "today" },
317
+ { label: "Yesterday", value: "yesterday" },
318
+ { label: "Tomorrow", value: "tomorrow" },
319
+ { label: "This week", value: "this_week" },
320
+ { label: "Last week", value: "last_week" },
321
+ { label: "Last 7 days", value: "7d" },
322
+ { label: "Last 30 days", value: "30d" },
323
+ { label: "Last 90 days", value: "90d" },
324
+ { label: "This month", value: "this_month" },
325
+ { label: "Last month", value: "last_month" },
326
+ { label: "This quarter", value: "this_quarter" },
327
+ { label: "Last quarter", value: "last_quarter" },
328
+ { label: "This year", value: "this_year" },
329
+ { label: "Last year", value: "last_year" }
330
+ ];
331
+ var HS_DATE_DIRECTION_LABELS = {
332
+ asc: "Ascending",
333
+ desc: "Descending"
334
+ };
335
+
336
+ // src/common-components/KeyValueList.js
337
+ import React4 from "react";
161
338
  import { DescriptionList, DescriptionListItem, Flex } from "@hubspot/ui-extensions";
162
339
  var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
163
340
  const rows = items.map(
164
- (item, index) => React3.createElement(
341
+ (item, index) => React4.createElement(
165
342
  DescriptionListItem,
166
343
  {
167
344
  key: item.key ?? item.label ?? `kv-${index}`,
@@ -170,15 +347,15 @@ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
170
347
  item.value
171
348
  )
172
349
  );
173
- return React3.createElement(
350
+ return React4.createElement(
174
351
  Flex,
175
352
  { direction: "column", gap },
176
- React3.createElement(DescriptionList, { direction }, ...rows)
353
+ React4.createElement(DescriptionList, { direction }, ...rows)
177
354
  );
178
355
  };
179
356
 
180
357
  // src/common-components/SectionHeader.js
181
- import React4 from "react";
358
+ import React5 from "react";
182
359
  import { Flex as Flex2, Heading, Text } from "@hubspot/ui-extensions";
183
360
  var SectionHeader = ({
184
361
  title,
@@ -190,11 +367,11 @@ var SectionHeader = ({
190
367
  }) => {
191
368
  const body = [];
192
369
  if (title != null) {
193
- body.push(React4.createElement(Heading, { key: "title", as: titleAs }, title));
370
+ body.push(React5.createElement(Heading, { key: "title", as: titleAs }, title));
194
371
  }
195
372
  if (description != null) {
196
373
  body.push(
197
- React4.createElement(
374
+ React5.createElement(
198
375
  Text,
199
376
  { key: "description", variant: "microcopy" },
200
377
  description
@@ -204,18 +381,296 @@ var SectionHeader = ({
204
381
  if (children != null) {
205
382
  body.push(children);
206
383
  }
207
- const content = React4.createElement(Flex2, { direction: "column", gap }, ...body);
384
+ const content = React5.createElement(Flex2, { direction: "column", gap }, ...body);
208
385
  if (actions == null) return content;
209
- return React4.createElement(
386
+ return React5.createElement(
210
387
  Flex2,
211
388
  { direction: "row", justify: "between", align: "start", gap: "sm" },
212
389
  content,
213
390
  actions
214
391
  );
215
392
  };
393
+
394
+ // src/common-components/StyledText.js
395
+ import React6 from "react";
396
+ import { Image as Image2, Tag as Tag2 } from "@hubspot/ui-extensions";
397
+ var VARIANT_PRESETS = {
398
+ bodytext: { fontSize: 14, lineHeight: 24, fontWeight: 400 },
399
+ microcopy: { fontSize: 12, lineHeight: 18, fontWeight: 400 }
400
+ };
401
+ var WEIGHT_ALIASES = {
402
+ bold: 700,
403
+ demibold: 600,
404
+ regular: 400
405
+ };
406
+ var LINE_DECORATION = {
407
+ strikethrough: "line-through",
408
+ underline: "underline"
409
+ };
410
+ var ORIENTATION_ROTATION = {
411
+ horizontal: 0,
412
+ "vertical-up": -90,
413
+ "vertical-down": 90
414
+ };
415
+ var BACKGROUND_PRESETS = {
416
+ tag: {
417
+ color: HS_SUBTLE_BG,
418
+ borderColor: HS_TAG_SUBTLE_BORDER,
419
+ borderWidth: HS_TAG_BORDER_WIDTH,
420
+ radius: HS_TAG_BORDER_RADIUS,
421
+ paddingX: HS_TAG_PADDING_X,
422
+ paddingY: HS_TAG_PADDING_Y,
423
+ height: HS_TAG_LINE_HEIGHT,
424
+ textColor: HS_TAG_TEXT_COLOR,
425
+ fontSize: HS_TAG_FONT_SIZE,
426
+ canvasPaddingX: 0,
427
+ canvasPaddingY: 0
428
+ }
429
+ };
430
+ var TAG_VARIANTS = {
431
+ default: {
432
+ color: HS_SUBTLE_BG,
433
+ borderColor: HS_TAG_SUBTLE_BORDER,
434
+ textColor: HS_TAG_TEXT_COLOR
435
+ },
436
+ success: {
437
+ color: "#E5F8F6",
438
+ borderColor: "#00BDA5",
439
+ textColor: "#00BDA5"
440
+ },
441
+ warning: {
442
+ color: "#FEF8F0",
443
+ borderColor: "#F5C26B",
444
+ textColor: "#D39913"
445
+ },
446
+ error: {
447
+ color: "#FDEDEE",
448
+ borderColor: "#F2545B",
449
+ textColor: "#F2545B"
450
+ },
451
+ danger: {
452
+ color: "#FDEDEE",
453
+ borderColor: "#F2545B",
454
+ textColor: "#F2545B"
455
+ },
456
+ info: {
457
+ color: "#E5F5F8",
458
+ borderColor: "#00A4BD",
459
+ textColor: "#00A4BD"
460
+ }
461
+ };
462
+ var NATIVE_TAG_VARIANT_ALIASES = {
463
+ danger: "error"
464
+ };
465
+ var escapeSvgText = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
466
+ var applyTextTransform = (text, transform) => {
467
+ if (!transform || transform === "none") return String(text);
468
+ const s = String(text);
469
+ switch (transform) {
470
+ case "uppercase":
471
+ return s.toUpperCase();
472
+ case "lowercase":
473
+ return s.toLowerCase();
474
+ case "capitalize":
475
+ return s.replace(/\b\w/g, (c) => c.toUpperCase());
476
+ case "sentenceCase":
477
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
478
+ default:
479
+ return s;
480
+ }
481
+ };
482
+ var estimateTextWidth = (text, fontSize) => Math.max(fontSize, Math.round(String(text).length * fontSize * 0.58));
483
+ var resolveBackground = (background) => {
484
+ if (!background) return null;
485
+ const preset = background.preset ? BACKGROUND_PRESETS[background.preset] : null;
486
+ const variant = background.preset === "tag" && background.variant ? TAG_VARIANTS[background.variant] || null : null;
487
+ return {
488
+ ...preset || {},
489
+ ...variant || {},
490
+ ...background
491
+ };
492
+ };
493
+ var buildBackgroundRect = ({ background, x, y, width, height }) => {
494
+ const radius = (background == null ? void 0 : background.radius) ?? 3;
495
+ const fill = (background == null ? void 0 : background.color) ?? "transparent";
496
+ const borderWidth = (background == null ? void 0 : background.borderWidth) ?? 0;
497
+ const borderColor = background == null ? void 0 : background.borderColor;
498
+ if (!borderColor || borderWidth <= 0) {
499
+ return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
500
+ }
501
+ const isTagPreset = (background == null ? void 0 : background.preset) === "tag";
502
+ const fillRect = `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
503
+ const strokeInset = borderWidth / 2;
504
+ const strokeX = x + strokeInset;
505
+ const strokeY = y + strokeInset;
506
+ const strokeW = Math.max(0, width - borderWidth);
507
+ const strokeH = Math.max(0, height - borderWidth);
508
+ return fillRect + `<rect x="${strokeX}" y="${strokeY}" width="${strokeW}" height="${strokeH}" rx="${Math.max(
509
+ 0,
510
+ radius - strokeInset
511
+ )}" fill="none" stroke="${borderColor}" stroke-width="${borderWidth}"${isTagPreset ? ` shape-rendering="crispEdges"` : ""} />`;
512
+ };
513
+ var canUseNativeTag = ({
514
+ background,
515
+ orientation,
516
+ color,
517
+ fontFamily,
518
+ fontSize,
519
+ width,
520
+ height,
521
+ paddingX,
522
+ paddingY,
523
+ format = {}
524
+ }) => {
525
+ if (!background || background.preset !== "tag") return false;
526
+ const resolvedOrientation = typeof orientation === "number" ? orientation : ORIENTATION_ROTATION[orientation ?? "horizontal"] ?? 0;
527
+ if (resolvedOrientation !== 0) return false;
528
+ if (color != null || fontFamily != null || fontSize != null) return false;
529
+ if (width != null || height != null || paddingX != null || paddingY != null) return false;
530
+ if (background.color != null || background.textColor != null) return false;
531
+ if (background.borderColor != null || background.borderWidth != null) return false;
532
+ if (background.radius != null || background.height != null) return false;
533
+ if (background.paddingX != null || background.paddingY != null) return false;
534
+ if (background.canvasPaddingX != null || background.canvasPaddingY != null) return false;
535
+ if (format.italic || format.lineDecoration) return false;
536
+ if (format.textTransform && format.textTransform !== "none") return false;
537
+ return true;
538
+ };
539
+ var makeStyledTextDataUri = (text, opts = {}) => {
540
+ const {
541
+ variant = "bodytext",
542
+ format = {},
543
+ orientation = "horizontal",
544
+ color: colorProp = HS_TEXT_COLOR,
545
+ fontFamily = HS_FONT_FAMILY,
546
+ background: backgroundProp = null,
547
+ paddingX: paddingXProp = 4,
548
+ paddingY: paddingYProp = 2,
549
+ width: widthOverride,
550
+ height: heightOverride,
551
+ fontSize: fontSizeOverride
552
+ } = opts;
553
+ const preset = VARIANT_PRESETS[variant] || VARIANT_PRESETS.bodytext;
554
+ const background = resolveBackground(backgroundProp);
555
+ const fontSize = fontSizeOverride ?? (background == null ? void 0 : background.fontSize) ?? preset.fontSize;
556
+ const rawWeight = format.fontWeight;
557
+ const fontWeight = rawWeight ? WEIGHT_ALIASES[rawWeight] ?? rawWeight : preset.fontWeight;
558
+ const fontStyle = format.italic ? "italic" : "normal";
559
+ const textDecoration = LINE_DECORATION[format.lineDecoration] || "none";
560
+ const transformed = applyTextTransform(text, format.textTransform);
561
+ const lineHeight = (background == null ? void 0 : background.height) ?? preset.lineHeight ?? fontSize;
562
+ const color = (background == null ? void 0 : background.textColor) ?? colorProp;
563
+ const paddingX = (background == null ? void 0 : background.canvasPaddingX) ?? paddingXProp;
564
+ const paddingY = (background == null ? void 0 : background.canvasPaddingY) ?? paddingYProp;
565
+ const rotate = typeof orientation === "number" ? orientation : ORIENTATION_ROTATION[orientation] ?? 0;
566
+ const textW = estimateTextWidth(transformed, fontSize);
567
+ let pillW = 0;
568
+ let pillH = 0;
569
+ if (background) {
570
+ const bgPadX = background.paddingX ?? 6;
571
+ const bgPadY = background.paddingY ?? 3;
572
+ pillW = textW + bgPadX * 2;
573
+ pillH = background.height ?? Math.max(lineHeight, fontSize + bgPadY * 2);
574
+ }
575
+ const intrinsicW = (background ? pillW : textW) + paddingX * 2;
576
+ const intrinsicH = (background ? pillH : lineHeight) + paddingY * 2;
577
+ const isOrthoRotation = rotate === 90 || rotate === -90 || rotate === 270;
578
+ const canvasW = widthOverride ?? (isOrthoRotation ? intrinsicH : intrinsicW);
579
+ const canvasH = heightOverride ?? (isOrthoRotation ? intrinsicW : intrinsicH);
580
+ const cx = canvasW / 2;
581
+ const cy = canvasH / 2;
582
+ const rectX = cx - pillW / 2;
583
+ const rectY = cy - pillH / 2;
584
+ const group = (background ? buildBackgroundRect({
585
+ background,
586
+ x: rectX,
587
+ y: rectY,
588
+ width: pillW,
589
+ height: pillH
590
+ }) : "") + `<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>`;
591
+ const wrapped = rotate ? `<g transform="rotate(${rotate} ${cx} ${cy})">${group}</g>` : group;
592
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${canvasW}" height="${canvasH}">` + wrapped + `</svg>`;
593
+ return {
594
+ src: `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`,
595
+ width: canvasW,
596
+ height: canvasH
597
+ };
598
+ };
599
+ var StyledText = ({
600
+ children,
601
+ text,
602
+ alt,
603
+ variant,
604
+ format,
605
+ orientation,
606
+ color,
607
+ background,
608
+ fontFamily,
609
+ fontSize,
610
+ paddingX,
611
+ paddingY,
612
+ width,
613
+ height
614
+ }) => {
615
+ const resolvedText = text ?? (typeof children === "string" ? children : "");
616
+ if (canUseNativeTag({
617
+ background,
618
+ orientation,
619
+ color,
620
+ fontFamily,
621
+ fontSize,
622
+ width,
623
+ height,
624
+ paddingX,
625
+ paddingY,
626
+ format
627
+ })) {
628
+ const nativeVariant = NATIVE_TAG_VARIANT_ALIASES[background == null ? void 0 : background.variant] ?? (background == null ? void 0 : background.variant) ?? "default";
629
+ return React6.createElement(Tag2, { variant: nativeVariant }, resolvedText);
630
+ }
631
+ const { src, width: w, height: h } = makeStyledTextDataUri(resolvedText, {
632
+ variant,
633
+ format,
634
+ orientation,
635
+ color,
636
+ background,
637
+ fontFamily,
638
+ fontSize,
639
+ paddingX,
640
+ paddingY,
641
+ width,
642
+ height
643
+ });
644
+ return React6.createElement(Image2, {
645
+ src,
646
+ width: w,
647
+ height: h,
648
+ alt: alt ?? String(resolvedText)
649
+ });
650
+ };
216
651
  export {
217
652
  AutoStatusTag,
218
653
  AutoTag,
654
+ AvatarStack,
655
+ DEFAULT_SVG_FONT_WEIGHT,
656
+ HS_DATE_DIRECTION_LABELS,
657
+ HS_DATE_PRESETS,
658
+ HS_FONT_FAMILY,
659
+ HS_MUTED_TEXT,
660
+ HS_NEUTRAL_CHIP,
661
+ HS_SUBTLE_BG,
662
+ HS_TAG_BORDER_RADIUS,
663
+ HS_TAG_BORDER_WIDTH,
664
+ HS_TAG_FONT_SIZE,
665
+ HS_TAG_LINE_HEIGHT,
666
+ HS_TAG_PADDING_X,
667
+ HS_TAG_PADDING_Y,
668
+ HS_TAG_SUBTLE_BORDER,
669
+ HS_TAG_TEXT_COLOR,
670
+ HS_TEXT_COLOR,
219
671
  KeyValueList,
220
- SectionHeader
672
+ SectionHeader,
673
+ StyledText,
674
+ makeAvatarStackDataUri,
675
+ makeStyledTextDataUri
221
676
  };
package/dist/datatable.js CHANGED
@@ -1004,7 +1004,7 @@ var DataTable = ({
1004
1004
  }
1005
1005
  );
1006
1006
  };
1007
- return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 3 }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "end", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ import_react.default.createElement(
1007
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 3 }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ import_react.default.createElement(
1008
1008
  import_ui_extensions.SearchInput,
1009
1009
  {
1010
1010
  name: "datatable-search",
@@ -1022,7 +1022,7 @@ var DataTable = ({
1022
1022
  /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "filter", size: "sm" }),
1023
1023
  " ",
1024
1024
  resolvedFiltersButtonLabel
1025
- )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "end", 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(
1025
+ )), 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(
1026
1026
  import_ui_extensions.Button,
1027
1027
  {
1028
1028
  variant: "transparent",
@@ -1086,7 +1086,7 @@ var DataTable = ({
1086
1086
  sortDirection: col.sortable ? sortState[col.field] || "none" : "never",
1087
1087
  onSortChange: col.sortable ? () => handleSortChange(col.field) : void 0
1088
1088
  },
1089
- col.label
1089
+ col.description ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, col.label, "\xA0", /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Tooltip, null, col.description) }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "info", screenReaderText: typeof col.description === "string" ? col.description : void 0 }))) : col.label
1090
1090
  );
1091
1091
  }), showRowActionsColumn && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableHeader, { width: "min" }))),
1092
1092
  /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableBody, null, displayRows.map(
@@ -30,7 +30,8 @@ import {
30
30
  Text,
31
31
  TextArea,
32
32
  TimeInput,
33
- Toggle
33
+ Toggle,
34
+ Tooltip
34
35
  } from "@hubspot/ui-extensions";
35
36
  var formatDateChip = (dateObj) => {
36
37
  if (!dateObj) return "";
@@ -999,7 +1000,7 @@ var DataTable = ({
999
1000
  }
1000
1001
  );
1001
1002
  };
1002
- return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "end", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ React.createElement(
1003
+ return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ React.createElement(
1003
1004
  SearchInput,
1004
1005
  {
1005
1006
  name: "datatable-search",
@@ -1017,7 +1018,7 @@ var DataTable = ({
1017
1018
  /* @__PURE__ */ React.createElement(Icon, { name: "filter", size: "sm" }),
1018
1019
  " ",
1019
1020
  resolvedFiltersButtonLabel
1020
- )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "end", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ React.createElement(Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ React.createElement(
1021
+ )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ React.createElement(Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ React.createElement(
1021
1022
  Button,
1022
1023
  {
1023
1024
  variant: "transparent",
@@ -1081,7 +1082,7 @@ var DataTable = ({
1081
1082
  sortDirection: col.sortable ? sortState[col.field] || "none" : "never",
1082
1083
  onSortChange: col.sortable ? () => handleSortChange(col.field) : void 0
1083
1084
  },
1084
- col.label
1085
+ col.description ? /* @__PURE__ */ React.createElement(React.Fragment, null, col.label, "\xA0", /* @__PURE__ */ React.createElement(Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React.createElement(Tooltip, null, col.description) }, /* @__PURE__ */ React.createElement(Icon, { name: "info", screenReaderText: typeof col.description === "string" ? col.description : void 0 }))) : col.label
1085
1086
  );
1086
1087
  }), showRowActionsColumn && /* @__PURE__ */ React.createElement(TableHeader, { width: "min" }))),
1087
1088
  /* @__PURE__ */ React.createElement(TableBody, null, displayRows.map(
package/dist/form.js CHANGED
@@ -2014,6 +2014,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
2014
2014
  }
2015
2015
  };
2016
2016
  const getFieldColSpan = (field) => {
2017
+ if (field.colSpan === "full") return columns;
2017
2018
  if (field.colSpan != null) return Math.min(field.colSpan, columns);
2018
2019
  if (field.width === "full" && columns > 1) return columns;
2019
2020
  if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
@@ -2039,6 +2040,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
2039
2040
  let currentRowSpan = 0;
2040
2041
  const gridColumnWidth = 200;
2041
2042
  const colSpan = (field) => {
2043
+ if (field.colSpan === "full") return cols;
2042
2044
  if (field.colSpan != null) return Math.min(field.colSpan, cols);
2043
2045
  if (field.width === "full" && cols > 1) return cols;
2044
2046
  if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
@@ -2172,8 +2174,16 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
2172
2174
  }
2173
2175
  batch = [];
2174
2176
  };
2177
+ const isFullSpan = (f) => f.colSpan === "full" || typeof f.colSpan === "number" && f.colSpan > 1;
2175
2178
  for (const field of fieldList) {
2176
2179
  if (isDependent(field)) continue;
2180
+ if (isFullSpan(field)) {
2181
+ flushBatch();
2182
+ elements.push(
2183
+ /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, { key: `full-${field.name}` }, renderField(field))
2184
+ );
2185
+ continue;
2186
+ }
2177
2187
  batch.push(field);
2178
2188
  const dependents = getDependents(field);
2179
2189
  if (dependents.length > 0) {
@@ -2185,6 +2195,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
2185
2195
  return elements;
2186
2196
  };
2187
2197
  const wrapWithGroups = (fieldList, renderFn) => {
2198
+ const formatGroupLabel = (groupName) => String(groupName || "").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
2188
2199
  const hasGroups = fieldList.some((f) => f.group);
2189
2200
  if (!hasGroups) return renderFn(fieldList);
2190
2201
  const chunks = [];
@@ -2206,6 +2217,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
2206
2217
  for (let i = 0; i < chunks.length; i++) {
2207
2218
  const chunk = chunks[i];
2208
2219
  const opts = chunk.group && groups && groups[chunk.group] || {};
2220
+ const resolvedGroupLabel = opts.label || formatGroupLabel(chunk.group);
2209
2221
  const showDivider = opts.showDivider !== false;
2210
2222
  const showLabel = opts.showLabel !== false;
2211
2223
  if (i > 0 && showDivider) {
@@ -2219,8 +2231,13 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
2219
2231
  );
2220
2232
  } else {
2221
2233
  elements.push(
2222
- /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
2234
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, resolvedGroupLabel)
2223
2235
  );
2236
+ if (opts.description) {
2237
+ elements.push(
2238
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { key: `group-description-${i}`, variant: "microcopy" }, opts.description)
2239
+ );
2240
+ }
2224
2241
  }
2225
2242
  }
2226
2243
  const chunkElements = renderFn(chunk.fields);