email-builder-utils 1.1.28 → 1.1.29

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Template.d.ts","sourceRoot":"","sources":["../../src/types/Template.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,IAAI,YAAY;IAChB,KAAK,UAAU;IACf,QAAQ,WAAW;IACnB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;IAC3B,KAAK,UAAU;IACf,KAAK,UAAU;IACf,QAAQ,aAAa;CACtB;AAED,oBAAY,UAAU;IACpB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;CAC5B;AAED,UAAU,MAAM;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,MAAM;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH"}
1
+ {"version":3,"file":"Template.d.ts","sourceRoot":"","sources":["../../src/types/Template.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,IAAI,YAAY;IAChB,KAAK,UAAU;IACf,QAAQ,WAAW;IACnB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;IAC3B,KAAK,UAAU;IACf,KAAK,UAAU;IACf,QAAQ,aAAa;CACtB;AAED,oBAAY,UAAU;IACpB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;CAC5B;AAED,UAAU,MAAM;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAGhB;AAED,UAAU,MAAM;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAUrC,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,cAAc,CAAC;QACtB,KAAK,EAAE,GAAG,CAAC;QACX,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KAC7B,CAAC;CACH;AAYD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AA2DhF,wBAAsB,aAAa,CACjC,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,MAAM,mBAwBtB;AAobD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBA8K5E"}
1
+ {"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAUrC,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAGhB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,cAAc,CAAC;QACtB,KAAK,EAAE,GAAG,CAAC;QACX,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KAC7B,CAAC;CACH;AAYD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AA2DhF,wBAAsB,aAAa,CACjC,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,MAAM,mBAwBtB;AAsbD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBA8K5E"}
@@ -90,16 +90,17 @@ function appendOutlookSupport(content, contentStyle) {
90
90
  `;
91
91
  }
92
92
  function convertDividerBlockToHtml(blockData) {
93
- const { style } = blockData.data;
94
- const { thickness, dividerColor, ...rest } = style;
93
+ const { style, props } = blockData.data;
94
+ const { thickness, dividerColor, width, ...rest } = style;
95
95
  const convertedStyle = buildStyles(rest, {
96
96
  perChanges: [],
97
97
  pxChanges: allPxAttributes,
98
98
  });
99
+ const dividerWidth = width || "100%";
99
100
  const dividerContent = `
100
- <table width="100%" cellpadding="0" cellspacing="0">
101
+ <table width="${dividerWidth}%" cellpadding="0" cellspacing="0">
101
102
  <tr>
102
- <td height="${thickness}" style="font-size:1px; line-height:1px; background:${dividerColor};">&nbsp;</td>
103
+ <td height="${thickness}" style="font-size:1px; line-height:1px; background:${dividerColor}; width:${dividerWidth};">&nbsp;</td>
103
104
  </tr>
104
105
  </table>
105
106
  `;
@@ -522,10 +523,278 @@ function computeArcSize(borderRadius, widthPx) {
522
523
  return Math.min(px / widthPx, 1).toFixed(2);
523
524
  }
524
525
  // ---------- Updated convertShapeBlock function ----------
526
+ // async function convertShapeBlock(blockData: IBlockData) {
527
+ // const { style, props } = blockData.data;
528
+ // const { shape, text, imageUrl } = props as any;
529
+ // const {
530
+ // width = "100",
531
+ // height = "150",
532
+ // padding = {},
533
+ // backgroundColor = "#2F80ED",
534
+ // borderRadius,
535
+ // borderWidth = 0,
536
+ // borderStyle = "solid",
537
+ // borderColor = "transparent",
538
+ // customCss,
539
+ // shapeColor,
540
+ // alignment = "left",
541
+ // msoBakeImageWithText,
542
+ // color = "#000000",
543
+ // fontSize = 14,
544
+ // verticalAlign = "center",
545
+ // } = style || {};
546
+ // const borderRadiusMap: Record<string, string> = {
547
+ // rectangle: "0",
548
+ // rounded: "10px",
549
+ // circle: "50%",
550
+ // oval: "50%",
551
+ // };
552
+ // let resolvedBorderRadius = borderRadius || borderRadiusMap[shape] || "0";
553
+ // let resolvedWidthPx =
554
+ // typeof width === "number"
555
+ // ? width
556
+ // : parseInt(width.toString().replace("px", ""), 10) || 100;
557
+ // let resolvedHeightPx =
558
+ // typeof height === "number"
559
+ // ? height
560
+ // : parseInt(height.toString().replace("px", ""), 10) || 150;
561
+ // // --- Shape specific constraints ---
562
+ // if (shape === "circle") {
563
+ // const side = Math.min(resolvedWidthPx, resolvedHeightPx);
564
+ // resolvedWidthPx = side;
565
+ // resolvedHeightPx = side;
566
+ // resolvedBorderRadius = "50%";
567
+ // } else if (shape === "oval") {
568
+ // resolvedBorderRadius = "50% / 50%";
569
+ // }
570
+ // const finalBackgroundColor = shapeColor || backgroundColor;
571
+ // const alignmentStyles = {
572
+ // left: "margin-right:auto;margin-left:0;",
573
+ // center: "margin-left:auto;margin-right:auto;",
574
+ // right: "margin-left:auto;margin-right:0;",
575
+ // };
576
+ // const alignmentStyle =
577
+ // alignmentStyles[alignment as keyof typeof alignmentStyles] || "";
578
+ // const verticalAlignStyles = {
579
+ // top: "align-items:flex-start;padding-top:8px;",
580
+ // center: "align-items:center;",
581
+ // bottom: "align-items:flex-end;padding-bottom:8px;",
582
+ // };
583
+ // const verticalAlignStyle =
584
+ // verticalAlignStyles[verticalAlign as keyof typeof verticalAlignStyles] ||
585
+ // verticalAlignStyles.center;
586
+ // // Text styling (safe across clients)
587
+ // const textSizeStyle = `font-size:${fontSize}px;line-height:1.3;word-break:break-word;overflow-wrap:break-word;text-align:center;color:${color};`;
588
+ // // ============================
589
+ // // Modern HTML (non-MSO)
590
+ // // ============================
591
+ // let nonMsoContent = "";
592
+ // if (imageUrl && text) {
593
+ // nonMsoContent = `
594
+ // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
595
+ // border:${borderWidth}px ${borderStyle} ${borderColor};
596
+ // border-radius:${resolvedBorderRadius};
597
+ // background:${finalBackgroundColor} url('${imageUrl}') center/cover no-repeat;
598
+ // overflow:hidden;${alignmentStyle}${customCss || ""}">
599
+ // <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;overflow:hidden;">
600
+ // <div style="${textSizeStyle}padding:6px;max-width:90%;-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden;">
601
+ // ${text}
602
+ // </div>
603
+ // </div>
604
+ // </div>`;
605
+ // } else if (imageUrl) {
606
+ // nonMsoContent = `
607
+ // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
608
+ // border:${borderWidth}px ${borderStyle} ${borderColor};
609
+ // border-radius:${resolvedBorderRadius};
610
+ // overflow:hidden;${alignmentStyle}${customCss || ""}">
611
+ // <img src="${imageUrl}" alt="${text || "shape image"}"
612
+ // width="${resolvedWidthPx}" height="${resolvedHeightPx}"
613
+ // style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
614
+ // </div>`;
615
+ // } else {
616
+ // const circlePadding =
617
+ // shape === "circle" ? Math.round(resolvedHeightPx * 0.15) : 8;
618
+ // nonMsoContent = `
619
+ // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
620
+ // background:${finalBackgroundColor};
621
+ // border:${borderWidth}px ${borderStyle} ${borderColor};
622
+ // border-radius:${resolvedBorderRadius};
623
+ // overflow:hidden;${alignmentStyle}${customCss || ""}">
624
+ // <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;padding:${circlePadding}px;box-sizing:border-box;">
625
+ // <div style="${textSizeStyle}max-width:90%;overflow:hidden;">
626
+ // ${text || ""}
627
+ // </div>
628
+ // </div>
629
+ // </div>`;
630
+ // }
631
+ // // ============================
632
+ // // Outlook (VML) version
633
+ // // ============================
634
+ // const outlookContent = await appendOutlookForShape(
635
+ // nonMsoContent,
636
+ // resolvedWidthPx,
637
+ // resolvedWidthPx,
638
+ // {
639
+ // shape,
640
+ // imageUrl,
641
+ // backgroundColor,
642
+ // shapeColor,
643
+ // borderWidth,
644
+ // borderColor,
645
+ // borderRadius: resolvedBorderRadius,
646
+ // heightPx: resolvedHeightPx,
647
+ // text,
648
+ // textColor: color,
649
+ // textSize: fontSize,
650
+ // verticalAlign,
651
+ // alignment,
652
+ // padding,
653
+ // msoBakeImageWithText,
654
+ // }
655
+ // );
656
+ // // ============================
657
+ // // Final combined block
658
+ // // ============================
659
+ // return `
660
+ // <table width="100%" style="border-collapse:collapse;table-layout:fixed;">
661
+ // <tr>
662
+ // <td style="padding:${padding.top || 0}px ${padding.right || 0}px ${
663
+ // padding.bottom || 0
664
+ // }px ${padding.left || 0}px;text-align:${alignment};">
665
+ // ${outlookContent}
666
+ // <!--[if !mso]><!-->
667
+ // ${nonMsoContent}
668
+ // <!--<![endif]-->
669
+ // </td>
670
+ // </tr>
671
+ // </table>`;
672
+ // }
673
+ // // ---------- Updated VML builder with better text containment ----------
674
+ // function buildVMLShape({
675
+ // shape,
676
+ // widthPx,
677
+ // heightPx,
678
+ // imageUrl,
679
+ // backgroundColor,
680
+ // borderWidth,
681
+ // borderColor,
682
+ // borderRadius,
683
+ // text,
684
+ // textColor = "#000000",
685
+ // textSize = 14,
686
+ // verticalAlign = "center",
687
+ // msoHasBakedText = false,
688
+ // }: any) {
689
+ // // --- Basic setup ---
690
+ // const bw = borderWidth || 0;
691
+ // const bc = borderColor || "transparent";
692
+ // const borderAttrs =
693
+ // bw > 0 ? `strokeweight="${bw}px" strokecolor="${bc}"` : `stroked="false"`;
694
+ // const fillColor = backgroundColor || "#2F80ED";
695
+ // const fillMarkup = `<v:fill ${
696
+ // imageUrl ? `src="${imageUrl}" type="frame" aspect="atleast"` : ""
697
+ // } color="${fillColor}" />`;
698
+ // // --- Shape tag ---
699
+ // let tag = "rect";
700
+ // let extraAttr = "";
701
+ // if (shape === "circle" || shape === "oval") {
702
+ // tag = "oval";
703
+ // } else if (shape === "rounded") {
704
+ // tag = "roundrect";
705
+ // extraAttr = `arcsize="${computeArcSize(borderRadius, widthPx)}"`;
706
+ // }
707
+ // // --- Text alignment ---
708
+ // const vAlignMap = { top: "top", center: "middle", bottom: "bottom" };
709
+ // const vAlign = vAlignMap[verticalAlign as keyof typeof vAlignMap] || "middle";
710
+ // const safeFontSize = Math.max(textSize, 10);
711
+ // // --- Text inside shape ---
712
+ // const textboxMarkup =
713
+ // text && !msoHasBakedText
714
+ // ? `<v:textbox inset="6pt,6pt,6pt,6pt" style="mso-fit-shape-to-text:false;">
715
+ // <div style="display:table;width:100%;height:100%;">
716
+ // <div style="display:table-cell;vertical-align:${vAlign};text-align:center;">
717
+ // <div style="color:${textColor};font-family:Arial, sans-serif;font-size:${safeFontSize}px;line-height:1.3;word-wrap:break-word;">
718
+ // ${text}
719
+ // </div>
720
+ // </div>
721
+ // </div>
722
+ // </v:textbox>`
723
+ // : `<v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>`;
724
+ // // --- Final shape markup ---
725
+ // return `
726
+ // <v:${tag} xmlns:v="urn:schemas-microsoft-com:vml"
727
+ // style="width:${widthPx}px;height:${heightPx}px;
728
+ // mso-position-horizontal:center;
729
+ // mso-position-vertical:center;"
730
+ // ${borderAttrs}
731
+ // fill="true" fillcolor="${fillColor}"${extraAttr}>
732
+ // ${fillMarkup}
733
+ // ${textboxMarkup}
734
+ // </v:${tag}>`;
735
+ // }
736
+ // // ---------- Updated appendOutlookForShape ----------
737
+ // async function appendOutlookForShape(
738
+ // content: string,
739
+ // outerContainerWidth: number,
740
+ // innerContainerWidth: number,
741
+ // opts: {
742
+ // shape: string;
743
+ // imageUrl?: string;
744
+ // backgroundColor?: string;
745
+ // shapeColor?: string;
746
+ // borderWidth?: number;
747
+ // borderColor?: string;
748
+ // borderRadius?: string | number;
749
+ // heightPx: number;
750
+ // text?: string;
751
+ // textColor?: string;
752
+ // textSize?: number;
753
+ // verticalAlign?: "top" | "middle" | "bottom";
754
+ // alignment?: "left" | "center" | "right";
755
+ // padding?: { top?: number; right?: number; bottom?: number; left?: number };
756
+ // msoBakeImageWithText?: string;
757
+ // }
758
+ // ) {
759
+ // const widthPx = Math.round(
760
+ // Math.min(outerContainerWidth, innerContainerWidth)
761
+ // );
762
+ // const heightPx = Math.max(1, Math.round(opts.heightPx));
763
+ // const vml = buildVMLShape({
764
+ // shape: opts.shape,
765
+ // widthPx,
766
+ // heightPx,
767
+ // imageUrl: opts.msoBakeImageWithText || opts.imageUrl,
768
+ // backgroundColor: opts.shapeColor || opts.backgroundColor,
769
+ // borderWidth: opts.borderWidth,
770
+ // borderColor: opts.borderColor,
771
+ // borderRadius: opts.borderRadius,
772
+ // text: opts.text,
773
+ // textColor: opts.textColor,
774
+ // textSize: opts.textSize,
775
+ // msoHasBakedText: Boolean(opts.msoBakeImageWithText),
776
+ // });
777
+ // const pad = opts.padding || {};
778
+ // const align = opts.alignment || "left";
779
+ // const valign = opts.verticalAlign || "middle";
780
+ // return `<!--[if mso]>
781
+ // <table align="${align}" border="0" cellpadding="0" cellspacing="0"
782
+ // style="width:${widthPx}px;height:${heightPx}px;">
783
+ // <tr>
784
+ // <td valign="${valign}"
785
+ // style="padding:${pad.top || 0}px ${pad.right || 0}px ${
786
+ // pad.bottom || 0
787
+ // }px ${pad.left || 0}px;">
788
+ // ${vml}
789
+ // </td>
790
+ // </tr>
791
+ // </table>
792
+ // <![endif]-->`;
793
+ // }
525
794
  async function convertShapeBlock(blockData) {
526
795
  const { style, props } = blockData.data;
527
796
  const { shape, text, imageUrl } = props;
528
- const { width = "100", height = "150", padding = {}, backgroundColor = "#2F80ED", borderRadius, borderWidth = 0, borderStyle = "solid", borderColor = "transparent", customCss, shapeColor, alignment = "left", msoBakeImageWithText, color = "#000000", fontSize = 14, verticalAlign = "center", } = style || {};
797
+ const { width = "100", height = "150", padding = {}, backgroundColor = "#2F80ED", borderRadius, borderWidth = 0, borderStyle = "solid", borderColor = "transparent", customCss, shapeColor, alignment = "left", msoBakeImageWithText, color = "#000000", fontSize = 14, textAlign = "center", verticalAlign = "middle", } = style || {};
529
798
  const borderRadiusMap = {
530
799
  rectangle: "0",
531
800
  rounded: "10px",
@@ -539,7 +808,7 @@ async function convertShapeBlock(blockData) {
539
808
  let resolvedHeightPx = typeof height === "number"
540
809
  ? height
541
810
  : parseInt(height.toString().replace("px", ""), 10) || 150;
542
- // --- Shape specific constraints ---
811
+ // --- Shape-specific constraints ---
543
812
  if (shape === "circle") {
544
813
  const side = Math.min(resolvedWidthPx, resolvedHeightPx);
545
814
  resolvedWidthPx = side;
@@ -550,25 +819,38 @@ async function convertShapeBlock(blockData) {
550
819
  resolvedBorderRadius = "50% / 50%";
551
820
  }
552
821
  const finalBackgroundColor = shapeColor || backgroundColor;
822
+ // --- Horizontal alignment for outer container ---
553
823
  const alignmentStyles = {
554
824
  left: "margin-right:auto;margin-left:0;",
555
825
  center: "margin-left:auto;margin-right:auto;",
556
826
  right: "margin-left:auto;margin-right:0;",
557
827
  };
558
828
  const alignmentStyle = alignmentStyles[alignment] || "";
559
- const verticalAlignStyles = {
560
- top: "align-items:flex-start;padding-top:8px;",
561
- center: "align-items:center;",
562
- bottom: "align-items:flex-end;padding-bottom:8px;",
829
+ // --- Text + vertical alignment maps ---
830
+ const textAlignMap = {
831
+ left: "left",
832
+ center: "center",
833
+ right: "right",
834
+ justify: "justify",
563
835
  };
564
- const verticalAlignStyle = verticalAlignStyles[verticalAlign] ||
565
- verticalAlignStyles.center;
566
- // Text styling (safe across clients)
567
- const textSizeStyle = `font-size:${fontSize}px;line-height:1.3;word-break:break-word;overflow-wrap:break-word;text-align:center;color:${color};`;
836
+ const textAlignStyle = textAlignMap[textAlign] || "center";
837
+ const flexJustify = textAlign === "left"
838
+ ? "flex-start"
839
+ : textAlign === "right"
840
+ ? "flex-end"
841
+ : "center";
842
+ const flexAlign = verticalAlign === "top"
843
+ ? "flex-start"
844
+ : verticalAlign === "bottom"
845
+ ? "flex-end"
846
+ : "center";
847
+ // --- Text styling ---
848
+ const textSizeStyle = `font-size:${fontSize}px;line-height:1.3;word-break:break-word;overflow-wrap:break-word;color:${color};`;
568
849
  // ============================
569
850
  // Modern HTML (non-MSO)
570
851
  // ============================
571
852
  let nonMsoContent = "";
853
+ // --- Case 1: Image + Text ---
572
854
  if (imageUrl && text) {
573
855
  nonMsoContent = `
574
856
  <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
@@ -576,13 +858,14 @@ async function convertShapeBlock(blockData) {
576
858
  border-radius:${resolvedBorderRadius};
577
859
  background:${finalBackgroundColor} url('${imageUrl}') center/cover no-repeat;
578
860
  overflow:hidden;${alignmentStyle}${customCss || ""}">
579
- <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;overflow:hidden;">
580
- <div style="${textSizeStyle}padding:6px;max-width:90%;-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden;">
861
+ <div style="width:100%;height:100%;display:flex;justify-content:${flexJustify};align-items:${flexAlign};overflow:hidden;padding:6px;box-sizing:border-box;">
862
+ <div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">
581
863
  ${text}
582
864
  </div>
583
865
  </div>
584
866
  </div>`;
585
867
  }
868
+ // --- Case 2: Image only ---
586
869
  else if (imageUrl) {
587
870
  nonMsoContent = `
588
871
  <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
@@ -594,24 +877,22 @@ async function convertShapeBlock(blockData) {
594
877
  style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
595
878
  </div>`;
596
879
  }
880
+ // --- Case 3: Text only ---
597
881
  else {
598
- const circlePadding = shape === "circle" ? Math.round(resolvedHeightPx * 0.15) : 8;
599
882
  nonMsoContent = `
600
883
  <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
601
884
  background:${finalBackgroundColor};
602
885
  border:${borderWidth}px ${borderStyle} ${borderColor};
603
886
  border-radius:${resolvedBorderRadius};
604
887
  overflow:hidden;${alignmentStyle}${customCss || ""}">
605
- <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;padding:${circlePadding}px;box-sizing:border-box;">
606
- <div style="${textSizeStyle}max-width:90%;overflow:hidden;">
888
+ <div style="width:100%;height:100%;display:flex;justify-content:${flexJustify};align-items:${flexAlign};padding:8px;box-sizing:border-box;">
889
+ <div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">
607
890
  ${text || ""}
608
891
  </div>
609
892
  </div>
610
893
  </div>`;
611
894
  }
612
- // ============================
613
- // Outlook (VML) version
614
- // ============================
895
+ // Outlook (VML) fallback
615
896
  const outlookContent = await appendOutlookForShape(nonMsoContent, resolvedWidthPx, resolvedWidthPx, {
616
897
  shape,
617
898
  imageUrl,
@@ -625,13 +906,12 @@ async function convertShapeBlock(blockData) {
625
906
  textColor: color,
626
907
  textSize: fontSize,
627
908
  verticalAlign,
909
+ textAlign, // ✅ added
628
910
  alignment,
629
911
  padding,
630
912
  msoBakeImageWithText,
631
913
  });
632
- // ============================
633
- // Final combined block
634
- // ============================
914
+ // Combine into table wrapper
635
915
  return `
636
916
  <table width="100%" style="border-collapse:collapse;table-layout:fixed;">
637
917
  <tr>
@@ -644,15 +924,14 @@ async function convertShapeBlock(blockData) {
644
924
  </tr>
645
925
  </table>`;
646
926
  }
647
- // ---------- Updated VML builder with better text containment ----------
648
- function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, borderWidth, borderColor, borderRadius, text, textColor = "#000000", textSize = 14, verticalAlign = "center", msoHasBakedText = false, }) {
649
- // --- Basic setup ---
927
+ // ---------- Updated VML builder ----------
928
+ function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, borderWidth, borderColor, borderRadius, text, textColor = "#000000", textSize = 14, verticalAlign = "middle", textAlign = "center", msoHasBakedText = false, }) {
650
929
  const bw = borderWidth || 0;
651
930
  const bc = borderColor || "transparent";
652
931
  const borderAttrs = bw > 0 ? `strokeweight="${bw}px" strokecolor="${bc}"` : `stroked="false"`;
653
932
  const fillColor = backgroundColor || "#2F80ED";
933
+ // Use frame for img fill so sizing is preserved
654
934
  const fillMarkup = `<v:fill ${imageUrl ? `src="${imageUrl}" type="frame" aspect="atleast"` : ""} color="${fillColor}" />`;
655
- // --- Shape tag ---
656
935
  let tag = "rect";
657
936
  let extraAttr = "";
658
937
  if (shape === "circle" || shape === "oval") {
@@ -662,15 +941,22 @@ function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, bo
662
941
  tag = "roundrect";
663
942
  extraAttr = `arcsize="${computeArcSize(borderRadius, widthPx)}"`;
664
943
  }
665
- // --- Text alignment ---
666
- const vAlignMap = { top: "top", center: "middle", bottom: "bottom" };
944
+ // maps for vml
945
+ const vAlignMap = { top: "top", middle: "middle", bottom: "bottom" };
946
+ const hAlignMap = {
947
+ left: "left",
948
+ center: "center",
949
+ right: "right",
950
+ justify: "left",
951
+ }; // justify -> left fallback in VML
667
952
  const vAlign = vAlignMap[verticalAlign] || "middle";
668
- const safeFontSize = Math.max(textSize, 10);
669
- // --- Text inside shape ---
953
+ const hAlign = hAlignMap[textAlign] || "center";
954
+ const safeFontSize = Math.max(Math.round(textSize), 10);
955
+ // Build the textbox with table/cell for reliable vertical centering in Outlook
670
956
  const textboxMarkup = text && !msoHasBakedText
671
957
  ? `<v:textbox inset="6pt,6pt,6pt,6pt" style="mso-fit-shape-to-text:false;">
672
958
  <div style="display:table;width:100%;height:100%;">
673
- <div style="display:table-cell;vertical-align:${vAlign};text-align:center;">
959
+ <div style="display:table-cell;vertical-align:${vAlign};text-align:${hAlign};padding:0 6px;">
674
960
  <div style="color:${textColor};font-family:Arial, sans-serif;font-size:${safeFontSize}px;line-height:1.3;word-wrap:break-word;">
675
961
  ${text}
676
962
  </div>
@@ -678,20 +964,17 @@ function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, bo
678
964
  </div>
679
965
  </v:textbox>`
680
966
  : `<v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>`;
681
- // --- Final shape markup ---
967
+ // Return VML shape
682
968
  return `
683
969
  <v:${tag} xmlns:v="urn:schemas-microsoft-com:vml"
684
- style="width:${widthPx}px;height:${heightPx}px;
685
- mso-position-horizontal:center;
686
- mso-position-vertical:center;"
970
+ style="width:${widthPx}px;height:${heightPx}px;display:inline-block;"
687
971
  ${borderAttrs}
688
972
  fill="true" fillcolor="${fillColor}"${extraAttr}>
689
973
  ${fillMarkup}
690
974
  ${textboxMarkup}
691
975
  </v:${tag}>`;
692
976
  }
693
- // ---------- Updated appendOutlookForShape ----------
694
- async function appendOutlookForShape(content, outerContainerWidth, innerContainerWidth, opts) {
977
+ function appendOutlookForShape(content, outerContainerWidth, innerContainerWidth, opts) {
695
978
  const widthPx = Math.round(Math.min(outerContainerWidth, innerContainerWidth));
696
979
  const heightPx = Math.max(1, Math.round(opts.heightPx));
697
980
  const vml = buildVMLShape({
@@ -706,14 +989,17 @@ async function appendOutlookForShape(content, outerContainerWidth, innerContaine
706
989
  text: opts.text,
707
990
  textColor: opts.textColor,
708
991
  textSize: opts.textSize,
992
+ verticalAlign: opts.verticalAlign,
993
+ textAlign: opts.textAlign,
709
994
  msoHasBakedText: Boolean(opts.msoBakeImageWithText),
710
995
  });
711
996
  const pad = opts.padding || {};
712
997
  const align = opts.alignment || "left";
713
998
  const valign = opts.verticalAlign || "middle";
999
+ // Outlook wrapper table ensures the cell's valign works if vml alone doesn't
714
1000
  return `<!--[if mso]>
715
1001
  <table align="${align}" border="0" cellpadding="0" cellspacing="0"
716
- style="width:${widthPx}px;height:${heightPx}px;">
1002
+ style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;">
717
1003
  <tr>
718
1004
  <td valign="${valign}"
719
1005
  style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
@@ -721,7 +1007,7 @@ async function appendOutlookForShape(content, outerContainerWidth, innerContaine
721
1007
  </td>
722
1008
  </tr>
723
1009
  </table>
724
- <![endif]-->`;
1010
+ <![endif]-->`;
725
1011
  }
726
1012
  function convertVerticalDividerBlockToHtml(blockData) {
727
1013
  const { style } = blockData.data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "email-builder-utils",
3
- "version": "1.1.28",
3
+ "version": "1.1.29",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [