react-email-studio 3.4.0 → 3.8.1

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.cjs CHANGED
@@ -33,9 +33,12 @@ __export(index_exports, {
33
33
  EmailPreviewModal: () => PreviewModal,
34
34
  ReactEmailEditor: () => ReactEmailEditor2,
35
35
  base64ToUtf8: () => base64ToUtf8,
36
+ canonicalizeEmailDocument: () => canonicalizeEmailDocument,
37
+ coerceEmailDocumentInput: () => coerceEmailDocumentInput,
36
38
  emailPreviewDevices: () => DEVICES,
37
39
  extractHtmlForDesign: () => extractHtmlForDesign,
38
40
  htmlToEmailDesignTemplate: () => htmlToEmailDesignTemplate,
41
+ htmlToJson: () => htmlToJson,
39
42
  jsonToHtml: () => jsonToHtml,
40
43
  utf8ToBase64: () => utf8ToBase64
41
44
  });
@@ -498,9 +501,9 @@ var DEFAULT_BLOCK_PROPS = {
498
501
  fontFamily: "Georgia,serif",
499
502
  margin: { ...BOX0 }
500
503
  },
504
+ /** Rich HTML body lives on the block root as `content` (string), not in `props`. */
501
505
  html: {
502
506
  ...BLOCK_BG,
503
- content: "<p>Rich HTML content. Edit with the rich editor.</p>",
504
507
  fontSize: 15,
505
508
  color: "#1e293b",
506
509
  align: "left",
@@ -520,6 +523,7 @@ var DEFAULT_BLOCK_PROPS = {
520
523
  align: "center",
521
524
  padding: boxAll(8),
522
525
  link: "",
526
+ linkEnabled: false,
523
527
  linkTarget: "_blank",
524
528
  borderRadius: { tl: 0, tr: 0, br: 0, bl: 0 },
525
529
  margin: { top: 0, right: 0, bottom: 0, left: 0 }
@@ -641,10 +645,82 @@ var DEFAULT_BLOCK_PROPS = {
641
645
  margin: { ...BOX0 }
642
646
  }
643
647
  };
648
+ var DEFAULT_HTML_BLOCK_MARKUP = "<p>Rich HTML content. Edit with the rich editor.</p>";
649
+ function imageLinkActive(p) {
650
+ const url = typeof p.link === "string" ? p.link.trim() : "";
651
+ const enabled = p.linkEnabled ?? !!url;
652
+ return enabled && !!url;
653
+ }
644
654
  function isKnownBlockType(type) {
645
655
  return typeof type === "string" && type in DEFAULT_BLOCK_PROPS;
646
656
  }
647
657
 
658
+ // src/lib/columnProps.ts
659
+ var BOX02 = { top: 0, right: 0, bottom: 0, left: 0 };
660
+ var DEFAULT_COLUMN_PROPS = {
661
+ bgColor: "",
662
+ bgImage: "",
663
+ bgGradient: null,
664
+ bgSize: "cover",
665
+ bgRepeat: "no-repeat",
666
+ bgPosition: "center",
667
+ padding: { ...BOX02 },
668
+ borderRadius: 0
669
+ };
670
+ function isObj(x) {
671
+ return x !== null && typeof x === "object" && !Array.isArray(x);
672
+ }
673
+ function colCountFromContainer(c) {
674
+ const cells = Array.isArray(c.cells) ? c.cells.length : 0;
675
+ const ratios = Array.isArray(c.ratios) ? c.ratios.length : 0;
676
+ const cols = typeof c.cols === "number" && c.cols > 0 ? c.cols : 0;
677
+ return Math.max(1, cells, ratios, cols);
678
+ }
679
+ function columnsFromLegacyMap(map, count) {
680
+ const n = Math.max(1, count);
681
+ const src = isObj(map) ? map : {};
682
+ return Array.from({ length: n }, (_, i) => {
683
+ const raw = src[i] ?? src[String(i)];
684
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...isObj(raw) ? raw : {} } };
685
+ });
686
+ }
687
+ function getColumns(container) {
688
+ const count = colCountFromContainer(container);
689
+ if (Array.isArray(container.columns)) {
690
+ const cols = container.columns;
691
+ return Array.from({ length: count }, (_, i) => {
692
+ const entry = cols[i];
693
+ if (isObj(entry) && isObj(entry.props)) {
694
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...entry.props } };
695
+ }
696
+ if (isObj(entry)) {
697
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...entry } };
698
+ }
699
+ return { props: { ...DEFAULT_COLUMN_PROPS } };
700
+ });
701
+ }
702
+ if (container.columnStyles) {
703
+ return columnsFromLegacyMap(container.columnStyles, count);
704
+ }
705
+ return columnsFromLegacyMap({}, count);
706
+ }
707
+ function getColumnPropsAt(container, index) {
708
+ const cols = getColumns(container);
709
+ const i = Math.max(0, Math.min(index, cols.length - 1));
710
+ return cols[i]?.props ?? { ...DEFAULT_COLUMN_PROPS };
711
+ }
712
+ function patchColumnPropsAt(container, index, patch) {
713
+ const cols = getColumns(container);
714
+ const i = Math.max(0, index);
715
+ while (cols.length <= i) cols.push({ props: { ...DEFAULT_COLUMN_PROPS } });
716
+ cols[i] = { props: { ...cols[i].props, ...patch } };
717
+ return cols;
718
+ }
719
+ function withColumnsOnly(row, columns) {
720
+ const { columnStyles: _drop, ...rest } = row;
721
+ return { ...rest, columns };
722
+ }
723
+
648
724
  // src/lib/factories.ts
649
725
  function uid() {
650
726
  return Math.random().toString(36).slice(2, 10);
@@ -684,14 +760,30 @@ function makeNestedRowBlock(preset) {
684
760
  bgRepeat: row.bgRepeat ?? "no-repeat",
685
761
  bgPosition: row.bgPosition ?? "center",
686
762
  bgGradient: row.bgGradient ?? null,
687
- columnStyles: row.columnStyles,
763
+ columns: row.columns ?? row.cells?.map(() => ({ props: { ...DEFAULT_COLUMN_PROPS } })),
688
764
  cells: row.cells
689
765
  }
690
766
  };
691
767
  }
692
768
  function makeContentBlock(type) {
769
+ if (type === "html") {
770
+ return {
771
+ id: uid(),
772
+ type,
773
+ content: DEFAULT_HTML_BLOCK_MARKUP,
774
+ props: { ...DEFAULT_BLOCK_PROPS.html, content: DEFAULT_HTML_BLOCK_MARKUP }
775
+ };
776
+ }
693
777
  return { id: uid(), type, props: { ...DEFAULT_BLOCK_PROPS[type] } };
694
778
  }
779
+ function makeRootContentRow(contentType, preset) {
780
+ const row = makeLayoutRow(preset);
781
+ row.gap = 0;
782
+ row.padding = 0;
783
+ const block = makeContentBlock(contentType);
784
+ row.cells[0] = [block];
785
+ return { row, block };
786
+ }
695
787
 
696
788
  // src/lib/columnPath.ts
697
789
  var MAX_NESTED_LAYOUT_DEPTH = 1;
@@ -701,6 +793,13 @@ function nestedLayoutDepth(nested) {
701
793
  function isSplitLayoutBlock(b) {
702
794
  return b && b.type === "layout" && b.props && Array.isArray(b.props.cells);
703
795
  }
796
+ function isRootContentRow(row) {
797
+ if (!row?.cells || row.cells.length !== 1) return false;
798
+ const col = row.cells[0];
799
+ if (!Array.isArray(col) || col.length !== 1) return false;
800
+ const b = col[0];
801
+ return !!b && typeof b.type === "string" && b.type !== "layout" && b.type !== "nestedRow";
802
+ }
704
803
  function getColumnBlocks(rows, loc) {
705
804
  const r = rows.find((x) => x.id === loc.rowId);
706
805
  if (!r) return [];
@@ -766,56 +865,6 @@ function countBlocksInDesign(rows) {
766
865
  return rows.reduce((sum, r) => sum + r.cells.reduce((s, c) => s + colBlocks(c), 0), 0);
767
866
  }
768
867
 
769
- // src/socialIcons.tsx
770
- var import_jsx_runtime2 = require("react/jsx-runtime");
771
- var GLYPHS = {
772
- facebook: "M24 12.073C24 5.446 18.627 0 12 0S0 5.446 0 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z",
773
- /** X (formerly Twitter) — Simple Icons “X” */
774
- twitter: "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z",
775
- instagram: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.98-6.98.072-1.28.07-1.689.07-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z",
776
- linkedin: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
777
- youtube: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
778
- pinterest: "M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.174-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.966-2.901 2.165-2.901 1.021 0 1.512.765 1.512 1.682 0 1.025-.653 2.555-.99 3.978-.281 1.189.597 2.159 1.775 2.159 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.402.165-1.495-.698-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026z",
779
- tiktok: "M19.59 6.69a4.83 4.83 0 01-3.77-4.245V2h-3.45v13.672a2.896 2.896 0 01-5.201 1.743 2.895 2.895 0 012.31-4.629 2.89 2.89 0 01.878.14V9.4a6.84 6.84 0 00-1-.05A6.33 6.33 0 005 20.1a6.34 6.34 0 0010.86-4.43v-7.36a8.16 8.16 0 004.77 1.52v-3.4a4.85 4.85 0 01-1-.1z",
780
- snapchat: "M12 0C5.373 0 0 5.372 0 12c0 4.991 3.657 9.128 8.438 9.879-.118-.949-.227-2.422.045-3.461.222-.95 1.444-6.037 1.444-6.037s-.367-.744-.367-1.844c0-1.726.999-3.012 2.243-3.012 1.058 0 1.569.795 1.569 1.748 0 1.065-.679 2.653-1.029 4.13-.292 1.237.618 2.246 1.835 2.246 2.203 0 3.895-2.323 3.895-5.67 0-2.965-2.129-5.038-5.167-5.038-3.52 0-5.585 2.643-5.585 5.589 0 1.106.425 2.295.956 2.943a.77.77 0 01.194.569c-.21.997-.679 3.135-.769 3.568-.121.588-.402.712-.929.431-3.485-1.624-5.667-6.731-5.667-10.839C2.5 6.506 6.847 2.344 12.001 2.344c4.391 0 7.809 2.776 7.809 6.586 0 4.308-2.694 7.635-6.369 7.635-1.271 0-2.464-.677-2.871-1.475 0 0-.669 2.562-.828 3.193-.301 1.301-1.12 2.929-1.668 3.922.125.047.256.07.395.07 1 0 1.826-.827 2.13-1.856.169-.617.982-3.857.982-3.857z",
781
- github: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
782
- discord: "M20.317 4.37a19.791 19.791 0 00-4.885-1.515.074.074 0 00-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 00-5.487 0 12.64 12.64 0 00-.617-1.25.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 00-.041-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.292.074.074 0 01.077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 01.078.01c.12.098.246.198.373.292a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.077.077 0 00-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 00.084.028 19.839 19.839 0 006.002-3.03.077.077 0 00.032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 00-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"
783
- };
784
- function iconFillForNetwork(network) {
785
- return network === "snapchat" ? "#000000" : "#ffffff";
786
- }
787
- function glyphPath(network) {
788
- return GLYPHS[network] || null;
789
- }
790
- function socialIconSvgString(network, pixelSize) {
791
- const d = glyphPath(network);
792
- const fill = iconFillForNetwork(network);
793
- const s = Math.max(8, Math.round(pixelSize));
794
- if (!d) {
795
- const letter = (network && network[0] ? network[0] : "?").toUpperCase();
796
- return `<span style="font-size:${Math.floor(s * 0.44)}px;font-weight:800;line-height:1;">${letter}</span>`;
797
- }
798
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="${s}" height="${s}" style="display:block;flex-shrink:0;" aria-hidden="true"><path fill="${fill}" d="${d}"/></svg>`;
799
- }
800
- function SocialGlyph({ network, size }) {
801
- const d = glyphPath(network);
802
- const s = Math.max(8, Math.round(size));
803
- if (!d) {
804
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: Math.floor(s * 0.44), fontWeight: 800, lineHeight: 1 }, children: (network && network[0] ? network[0] : "?").toUpperCase() });
805
- }
806
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
807
- "svg",
808
- {
809
- width: s,
810
- height: s,
811
- viewBox: "0 0 24 24",
812
- style: { display: "block", flexShrink: 0 },
813
- "aria-hidden": true,
814
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "currentColor", d })
815
- }
816
- );
817
- }
818
-
819
868
  // src/lib/htmlUtils.ts
820
869
  function escHtmlAttr(s) {
821
870
  return String(s ?? "").replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -857,7 +906,7 @@ function videoUrlToEmbedSrc(url) {
857
906
  if (vi) return `https://player.vimeo.com/video/${vi[1]}`;
858
907
  return url;
859
908
  }
860
- var EMPTY_RICH_HTML = "<p></p>";
909
+ var EMPTY_RICH_HTML = "";
861
910
  function unwrapAllDivElements(container) {
862
911
  let el;
863
912
  while (el = container.querySelector("div")) {
@@ -884,6 +933,161 @@ function normalizeRichHtmlForStorage(html) {
884
933
  function isEffectivelyEmptyRichHtml(html) {
885
934
  return normalizeRichHtmlForStorage(html) === EMPTY_RICH_HTML;
886
935
  }
936
+ var RICH_HTML_CONTENT_CSS = `
937
+ .email-rich-html-content ul,
938
+ .email-rich-html-content ol {
939
+ margin: 0.35em 0;
940
+ padding-left: 1.35rem;
941
+ list-style-position: outside;
942
+ }
943
+ .email-rich-html-content ul { list-style-type: disc; }
944
+ .email-rich-html-content ol { list-style-type: decimal; }
945
+ .email-rich-html-content li { margin: 0.2em 0; }
946
+ .email-rich-html-content li > p { margin: 0; }
947
+ .email-rich-html-content p { margin: 0.35em 0; }
948
+ .email-rich-html-content h2,
949
+ .email-rich-html-content h3,
950
+ .email-rich-html-content h4 {
951
+ margin: 0.5em 0 0.25em;
952
+ line-height: 1.25;
953
+ font-weight: 700;
954
+ }
955
+ .email-rich-html-content blockquote {
956
+ margin: 0.35em 0;
957
+ padding-left: 0.75rem;
958
+ border-left: 3px solid #cbd5e1;
959
+ }
960
+ .email-rich-html-content hr {
961
+ border: none;
962
+ border-top: 1px solid #e2e8f0;
963
+ margin: 0.75em 0;
964
+ }
965
+ .email-rich-html-content code {
966
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
967
+ font-size: 0.92em;
968
+ background: rgba(0, 0, 0, 0.06);
969
+ padding: 0.1em 0.35em;
970
+ border-radius: 3px;
971
+ }
972
+ .email-rich-html-inner ul,
973
+ .email-rich-html-inner ol {
974
+ margin: 0.35em 0;
975
+ padding-left: 1.35rem;
976
+ list-style-position: outside;
977
+ }
978
+ .email-rich-html-inner ul { list-style-type: disc; }
979
+ .email-rich-html-inner ol { list-style-type: decimal; }
980
+ .email-rich-html-inner li { margin: 0.2em 0; }
981
+ .email-rich-html-inner li > p { margin: 0; }
982
+ .email-rich-html-inner p { margin: 0.35em 0; }
983
+ .email-rich-html-inner h2,
984
+ .email-rich-html-inner h3,
985
+ .email-rich-html-inner h4 {
986
+ margin: 0.5em 0 0.25em;
987
+ line-height: 1.25;
988
+ font-weight: 700;
989
+ }
990
+ .email-rich-html-inner blockquote {
991
+ margin: 0.35em 0;
992
+ padding-left: 0.75rem;
993
+ border-left: 3px solid #cbd5e1;
994
+ }
995
+ .email-rich-html-inner hr {
996
+ border: none;
997
+ border-top: 1px solid #e2e8f0;
998
+ margin: 0.75em 0;
999
+ }
1000
+ `.trim();
1001
+ function applyInlineListStyles(root) {
1002
+ root.querySelectorAll("ul").forEach((ul) => {
1003
+ const el = ul;
1004
+ el.style.margin = el.style.margin || "0.35em 0";
1005
+ el.style.paddingLeft = el.style.paddingLeft || "1.35rem";
1006
+ el.style.listStyleType = el.style.listStyleType || "disc";
1007
+ el.style.listStylePosition = el.style.listStylePosition || "outside";
1008
+ });
1009
+ root.querySelectorAll("ol").forEach((ol) => {
1010
+ const el = ol;
1011
+ el.style.margin = el.style.margin || "0.35em 0";
1012
+ el.style.paddingLeft = el.style.paddingLeft || "1.35rem";
1013
+ el.style.listStyleType = el.style.listStyleType || "decimal";
1014
+ el.style.listStylePosition = el.style.listStylePosition || "outside";
1015
+ });
1016
+ root.querySelectorAll("li").forEach((li) => {
1017
+ const el = li;
1018
+ if (!el.style.margin) el.style.margin = "0.2em 0";
1019
+ });
1020
+ root.querySelectorAll("li > p").forEach((p) => {
1021
+ const el = p;
1022
+ if (!el.style.margin) el.style.margin = "0";
1023
+ });
1024
+ }
1025
+ function enhanceRichHtmlForEmail(html) {
1026
+ const raw = String(html ?? "").trim();
1027
+ if (!raw || raw === EMPTY_RICH_HTML) return raw || EMPTY_RICH_HTML;
1028
+ if (typeof document === "undefined") return raw;
1029
+ const tmp = document.createElement("div");
1030
+ tmp.innerHTML = raw;
1031
+ if (!tmp.querySelector("ul,ol,blockquote,h2,h3,h4,hr")) return raw;
1032
+ applyInlineListStyles(tmp);
1033
+ tmp.querySelectorAll("blockquote").forEach((bq) => {
1034
+ const el = bq;
1035
+ if (!el.style.margin) el.style.margin = "0.35em 0";
1036
+ if (!el.style.paddingLeft) el.style.paddingLeft = "0.75rem";
1037
+ if (!el.style.borderLeft) el.style.borderLeft = "3px solid #cbd5e1";
1038
+ });
1039
+ return tmp.innerHTML.trim();
1040
+ }
1041
+
1042
+ // src/socialIcons.tsx
1043
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1044
+ var GLYPHS = {
1045
+ facebook: "M24 12.073C24 5.446 18.627 0 12 0S0 5.446 0 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z",
1046
+ /** X (formerly Twitter) — Simple Icons “X” */
1047
+ twitter: "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z",
1048
+ instagram: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.98-6.98.072-1.28.07-1.689.07-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z",
1049
+ linkedin: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
1050
+ youtube: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
1051
+ pinterest: "M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.174-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.966-2.901 2.165-2.901 1.021 0 1.512.765 1.512 1.682 0 1.025-.653 2.555-.99 3.978-.281 1.189.597 2.159 1.775 2.159 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.402.165-1.495-.698-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026z",
1052
+ tiktok: "M19.59 6.69a4.83 4.83 0 01-3.77-4.245V2h-3.45v13.672a2.896 2.896 0 01-5.201 1.743 2.895 2.895 0 012.31-4.629 2.89 2.89 0 01.878.14V9.4a6.84 6.84 0 00-1-.05A6.33 6.33 0 005 20.1a6.34 6.34 0 0010.86-4.43v-7.36a8.16 8.16 0 004.77 1.52v-3.4a4.85 4.85 0 01-1-.1z",
1053
+ snapchat: "M12 0C5.373 0 0 5.372 0 12c0 4.991 3.657 9.128 8.438 9.879-.118-.949-.227-2.422.045-3.461.222-.95 1.444-6.037 1.444-6.037s-.367-.744-.367-1.844c0-1.726.999-3.012 2.243-3.012 1.058 0 1.569.795 1.569 1.748 0 1.065-.679 2.653-1.029 4.13-.292 1.237.618 2.246 1.835 2.246 2.203 0 3.895-2.323 3.895-5.67 0-2.965-2.129-5.038-5.167-5.038-3.52 0-5.585 2.643-5.585 5.589 0 1.106.425 2.295.956 2.943a.77.77 0 01.194.569c-.21.997-.679 3.135-.769 3.568-.121.588-.402.712-.929.431-3.485-1.624-5.667-6.731-5.667-10.839C2.5 6.506 6.847 2.344 12.001 2.344c4.391 0 7.809 2.776 7.809 6.586 0 4.308-2.694 7.635-6.369 7.635-1.271 0-2.464-.677-2.871-1.475 0 0-.669 2.562-.828 3.193-.301 1.301-1.12 2.929-1.668 3.922.125.047.256.07.395.07 1 0 1.826-.827 2.13-1.856.169-.617.982-3.857.982-3.857z",
1054
+ github: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
1055
+ discord: "M20.317 4.37a19.791 19.791 0 00-4.885-1.515.074.074 0 00-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 00-5.487 0 12.64 12.64 0 00-.617-1.25.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 00-.041-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.292.074.074 0 01.077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 01.078.01c.12.098.246.198.373.292a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.077.077 0 00-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 00.084.028 19.839 19.839 0 006.002-3.03.077.077 0 00.032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 00-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"
1056
+ };
1057
+ function iconFillForNetwork(network) {
1058
+ return network === "snapchat" ? "#000000" : "#ffffff";
1059
+ }
1060
+ function glyphPath(network) {
1061
+ return GLYPHS[network] || null;
1062
+ }
1063
+ function socialIconSvgString(network, pixelSize) {
1064
+ const d = glyphPath(network);
1065
+ const fill = iconFillForNetwork(network);
1066
+ const s = Math.max(8, Math.round(pixelSize));
1067
+ if (!d) {
1068
+ const letter = (network && network[0] ? network[0] : "?").toUpperCase();
1069
+ return `<span style="font-size:${Math.floor(s * 0.44)}px;font-weight:800;line-height:1;">${letter}</span>`;
1070
+ }
1071
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="${s}" height="${s}" style="display:block;flex-shrink:0;" aria-hidden="true"><path fill="${fill}" d="${d}"/></svg>`;
1072
+ }
1073
+ function SocialGlyph({ network, size }) {
1074
+ const d = glyphPath(network);
1075
+ const s = Math.max(8, Math.round(size));
1076
+ if (!d) {
1077
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: Math.floor(s * 0.44), fontWeight: 800, lineHeight: 1 }, children: (network && network[0] ? network[0] : "?").toUpperCase() });
1078
+ }
1079
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1080
+ "svg",
1081
+ {
1082
+ width: s,
1083
+ height: s,
1084
+ viewBox: "0 0 24 24",
1085
+ style: { display: "block", flexShrink: 0 },
1086
+ "aria-hidden": true,
1087
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "currentColor", d })
1088
+ }
1089
+ );
1090
+ }
887
1091
 
888
1092
  // src/lib/blockBackground.ts
889
1093
  function linearGradientCssInner(g) {
@@ -1008,19 +1212,23 @@ function blockToHtml(cb) {
1008
1212
  case "text": {
1009
1213
  const shell = emailSurfaceBgCss(p);
1010
1214
  const inner = `font-size:${lenPx(p.fontSize)};color:${p.color};text-align:${p.align};font-weight:${p.fontWeight || 400};font-style:${p.italic ? "italic" : "normal"};text-decoration:${p.underline ? "underline" : "none"};line-height:${lh(p.lineHeight)};letter-spacing:${lenPx(p.letterSpacing)};font-family:${p.fontFamily || "Georgia,serif"}`;
1011
- return `<div style="${pd(p.padding)};${marginCss()}${shell}"><div style="${inner}">${p.content}</div></div>`;
1215
+ const content = typeof p.content === "string" ? p.content : "";
1216
+ const body = /^\s*<[^>]+>/.test(content) ? content : escHtml(content).replace(/\r?\n/g, "<br/>");
1217
+ return `<div style="${pd(p.padding)};${marginCss()}${shell}"><div style="${inner}">${body}</div></div>`;
1012
1218
  }
1013
1219
  case "html": {
1014
1220
  const shell = emailSurfaceBgCss(p);
1015
1221
  const inner = `font-size:${lenPx(p.fontSize)};color:${p.color};text-align:${p.align};line-height:${lh(p.lineHeight)};letter-spacing:${lenPx(p.letterSpacing)};font-family:${p.fontFamily || "Georgia,serif"}`;
1016
- return `<div style="${pd(p.padding)};${marginCss()}${shell}"><div style="${inner}">${p.content || "<p></p>"}</div></div>`;
1222
+ const bodyRaw = typeof cb.content === "string" ? cb.content : typeof p.content === "string" ? p.content : "";
1223
+ const body = enhanceRichHtmlForEmail(bodyRaw || "");
1224
+ return `<div style="${pd(p.padding)};${marginCss()}${shell}"><div class="email-rich-html-inner" style="${inner}">${body}</div></div>`;
1017
1225
  }
1018
1226
  case "image": {
1019
1227
  const of = typeof p.objectFit === "string" && p.objectFit.trim() ? p.objectFit.trim() : "cover";
1020
1228
  const op = typeof p.objectPosition === "string" && p.objectPosition.trim() ? p.objectPosition.trim() : "center";
1021
1229
  const img = `<img src="${p.src}" alt="${p.alt || ""}" style="max-width:${p.width};width:${p.width || "100%"};display:inline-block;border-radius:${radiusPx(p.borderRadius)};object-fit:${of};object-position:${op};"/>`;
1022
1230
  const shell = emailSurfaceBgCss(p);
1023
- return `<div style="${pd(p.padding)};text-align:${p.align};${marginCss()}${shell}">${p.link ? `<a href="${p.link}" target="${p.linkTarget || "_blank"}">${img}</a>` : img}</div>`;
1231
+ return `<div style="${pd(p.padding)};text-align:${p.align};${marginCss()}${shell}">${imageLinkActive(p) ? `<a href="${escHtmlAttr(p.link)}" target="${escHtmlAttr(p.linkTarget || "_blank")}">${img}</a>` : img}</div>`;
1024
1232
  }
1025
1233
  case "button": {
1026
1234
  const bd = emailBackdropBgCss(p);
@@ -1109,7 +1317,7 @@ function blockToHtml(cb) {
1109
1317
  bgRepeat: p.bgRepeat || "no-repeat",
1110
1318
  bgPosition: typeof p.bgPosition === "string" ? p.bgPosition : "center",
1111
1319
  bgGradient: p.bgGradient ?? null,
1112
- columnStyles: p.columnStyles,
1320
+ columns: getColumns(p),
1113
1321
  cells: p.cells || []
1114
1322
  };
1115
1323
  return rowToHtml(pseudo);
@@ -1123,7 +1331,7 @@ function rowToHtml(row) {
1123
1331
  const shellBg = emailSurfaceBgCss(row);
1124
1332
  const inner = row.cells.length === 1 ? row.cells[0].map(blockToHtml).join("") : `<div class="email-row-flex" style="display:flex;flex-direction:row;flex-wrap:nowrap;align-items:flex-start;gap:${row.gap ?? 12}px;width:100%;max-width:100%;box-sizing:border-box;">${row.cells.map((cell, i) => {
1125
1333
  const r = row.ratios[i] ?? 1;
1126
- const cs = row.columnStyles && row.columnStyles[i] ? row.columnStyles[i] : {};
1334
+ const cs = getColumnPropsAt(row, i);
1127
1335
  const colShell = emailSurfaceBgCss(cs);
1128
1336
  const pad3 = cs.padding && typeof cs.padding === "object" && !Array.isArray(cs.padding) ? `padding:${(() => {
1129
1337
  const o = cs.padding;
@@ -1171,6 +1379,7 @@ table[role="presentation"] td{min-width:0!important;word-break:break-word;}
1171
1379
  @media screen and (max-width:480px){
1172
1380
  .email-content-td{padding-left:max(10px,env(safe-area-inset-left))!important;padding-right:max(10px,env(safe-area-inset-right))!important;}
1173
1381
  }
1382
+ ${RICH_HTML_CONTENT_CSS}
1174
1383
  </style>`;
1175
1384
  function previewEmailSrcDoc(html, viewportWidthPx) {
1176
1385
  const viewportTag = `<meta name="viewport" content="width=${viewportWidthPx},initial-scale=1"/>`;
@@ -1222,6 +1431,8 @@ function designToHtml(rows, settings, opts = {}) {
1222
1431
  return `background-image:linear-gradient(${angle}deg, ${pairs.join(", ")});background-repeat:no-repeat;background-position:${pagePos};background-size:cover;`;
1223
1432
  })();
1224
1433
  const pageBgImgRaw = typeof settings.bgImage === "string" ? String(settings.bgImage).trim() : "";
1434
+ const pageBgColorRaw = typeof settings.bgColor === "string" ? String(settings.bgColor).trim() : "";
1435
+ const pageBgColor = pageBgColorRaw ? pageBgColorRaw : "transparent";
1225
1436
  const pageBgImgCss = !pageGradCss && pageBgImgRaw ? `background-image:url('${pageBgImgRaw.replace(/'/g, "%27")}');background-size:${settings.bgSize || "cover"};background-repeat:${settings.bgRepeat || "no-repeat"};background-position:${pagePos};` : "";
1226
1437
  const contentPos = typeof settings.contentBgPosition === "string" && settings.contentBgPosition.trim() ? settings.contentBgPosition.trim() : "center";
1227
1438
  const contentGrad = settings.contentBgGradient && typeof settings.contentBgGradient === "object" ? settings.contentBgGradient : null;
@@ -1244,8 +1455,8 @@ function designToHtml(rows, settings, opts = {}) {
1244
1455
  <head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/>
1245
1456
  <title>Email</title>${RESPONSIVE_EMAIL_CSS}${css}
1246
1457
  </head>
1247
- <body style="margin:0;padding:0;background:${settings.bgColor || "#f1f5f9"};${pageGradCss || pageBgImgCss}">
1248
- <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="width:100%;max-width:100%;background-color:${settings.bgColor || "#f1f5f9"};${pageGradCss || pageBgImgCss}">
1458
+ <body style="margin:0;padding:0;background:${pageBgColor};${pageGradCss || pageBgImgCss}">
1459
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="width:100%;max-width:100%;background-color:${pageBgColor};${pageGradCss || pageBgImgCss}">
1249
1460
  <tr><td align="center" style="padding:0;max-width:100%;">
1250
1461
  <table class="email-shell-table" role="presentation" width="100%" cellpadding="0" cellspacing="0"
1251
1462
  style="${contentShellStyle.join(";")}">
@@ -1279,14 +1490,49 @@ function paddingToUniform(p, fallback = 0) {
1279
1490
  function ensureId(id, prefix) {
1280
1491
  return typeof id === "string" && id.trim() ? id : `${prefix}_${uid()}`;
1281
1492
  }
1493
+ function normalizeDocBlock(b, prefix) {
1494
+ const raw = b && typeof b === "object" && !Array.isArray(b) ? b : {};
1495
+ const base = {
1496
+ id: ensureId(raw.id, prefix),
1497
+ type: typeof raw.type === "string" ? raw.type : "text",
1498
+ content: typeof raw.content === "string" ? raw.content : raw.content && typeof raw.content === "object" && !Array.isArray(raw.content) ? raw.content : {},
1499
+ ...raw.behavior && typeof raw.behavior === "object" && !Array.isArray(raw.behavior) ? { behavior: raw.behavior } : {},
1500
+ ...raw.responsive && typeof raw.responsive === "object" && !Array.isArray(raw.responsive) ? { responsive: raw.responsive } : {}
1501
+ };
1502
+ if (raw.props && typeof raw.props === "object" && !Array.isArray(raw.props)) {
1503
+ base.props = { ...raw.props };
1504
+ } else if (raw.styles && typeof raw.styles === "object" && !Array.isArray(raw.styles)) {
1505
+ base.styles = { ...raw.styles };
1506
+ }
1507
+ return base;
1508
+ }
1509
+ function normalizeDocSettings(settings) {
1510
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) return {};
1511
+ const s = { ...settings };
1512
+ delete s._reactEmailStudio;
1513
+ return s;
1514
+ }
1282
1515
  function normalizeEmailDocument(input) {
1283
1516
  if (input == null || typeof input !== "object" || Array.isArray(input)) return null;
1284
1517
  const doc = input;
1285
1518
  if (doc.type !== "email_document") return null;
1519
+ const rawBlocks = Array.isArray(doc.blocks) ? doc.blocks : [];
1520
+ const useBlocks = rawBlocks.length > 0;
1286
1521
  const rows = Array.isArray(doc.rows) ? doc.rows : [];
1287
- return {
1522
+ const base = {
1288
1523
  type: "email_document",
1289
- settings: doc.settings && typeof doc.settings === "object" && !Array.isArray(doc.settings) ? doc.settings : {},
1524
+ settings: normalizeDocSettings(doc.settings),
1525
+ globalStyles: doc.globalStyles && typeof doc.globalStyles === "object" && !Array.isArray(doc.globalStyles) ? doc.globalStyles : void 0,
1526
+ responsive: doc.responsive && typeof doc.responsive === "object" && !Array.isArray(doc.responsive) ? doc.responsive : void 0
1527
+ };
1528
+ if (useBlocks) {
1529
+ return {
1530
+ ...base,
1531
+ blocks: rawBlocks.map((b, bi) => normalizeDocBlock(b, `block${bi + 1}`))
1532
+ };
1533
+ }
1534
+ return {
1535
+ ...base,
1290
1536
  rows: rows.map((r, ri) => {
1291
1537
  const columns = Array.isArray(r.columns) ? r.columns : [];
1292
1538
  const layoutColCount = typeof r.layout?.columns === "number" && r.layout.columns > 0 ? Math.floor(r.layout.columns) : 0;
@@ -1308,28 +1554,76 @@ function normalizeEmailDocument(input) {
1308
1554
  columns: colCount,
1309
1555
  gap: typeof r.layout?.gap === "number" ? r.layout.gap : 0,
1310
1556
  stackOnMobile: r.layout?.stackOnMobile !== false,
1311
- align: r.layout?.align || "center"
1557
+ align: r.layout?.align || "center",
1558
+ ...typeof r.layout?.preset === "string" && r.layout.preset.trim() ? { preset: r.layout.preset.trim() } : {},
1559
+ ...Array.isArray(r.layout?.ratios) && r.layout.ratios.length ? {
1560
+ ratios: r.layout.ratios.filter(
1561
+ (x) => typeof x === "number" && Number.isFinite(x)
1562
+ )
1563
+ } : {}
1312
1564
  },
1313
- styles: r.styles && typeof r.styles === "object" && !Array.isArray(r.styles) ? r.styles : {},
1314
- ...typeof r._reactEmailStudio === "object" && r._reactEmailStudio !== null && !Array.isArray(r._reactEmailStudio) ? { _reactEmailStudio: r._reactEmailStudio } : {},
1565
+ props: (() => {
1566
+ const p = r.props;
1567
+ if (p && typeof p === "object" && !Array.isArray(p)) return { ...p };
1568
+ const s = r.styles;
1569
+ if (s && typeof s === "object" && !Array.isArray(s)) {
1570
+ return {
1571
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1572
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1573
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1574
+ bgSize: s.backgroundSize || "cover",
1575
+ bgPosition: s.backgroundPosition || "center",
1576
+ bgGradient: s.backgroundGradient ?? null,
1577
+ padding: s.padding,
1578
+ borderRadius: s.borderRadius,
1579
+ borderWidth: s.borderWidth,
1580
+ borderColor: s.borderColor,
1581
+ textAlign: s.textAlign
1582
+ };
1583
+ }
1584
+ return {};
1585
+ })(),
1315
1586
  columns: colsNormalized.map((c, ci) => ({
1316
1587
  id: ensureId(c.id, `col${ri + 1}_${ci + 1}`),
1317
1588
  layout: c.layout && typeof c.layout === "object" && !Array.isArray(c.layout) ? c.layout : {},
1318
- styles: c.styles && typeof c.styles === "object" && !Array.isArray(c.styles) ? c.styles : {},
1319
- blocks: Array.isArray(c.blocks) ? c.blocks.map((b, bi) => ({
1320
- ...b,
1321
- id: ensureId(b?.id, `b${ri + 1}_${ci + 1}_${bi + 1}`),
1322
- content: b?.content && typeof b.content === "object" && !Array.isArray(b.content) ? b.content : {},
1323
- styles: b?.styles && typeof b.styles === "object" && !Array.isArray(b.styles) ? b.styles : {},
1324
- behavior: b?.behavior && typeof b.behavior === "object" && !Array.isArray(b.behavior) ? b.behavior : {},
1325
- responsive: b?.responsive && typeof b.responsive === "object" && !Array.isArray(b.responsive) ? b.responsive : {},
1326
- ...b?.props && typeof b.props === "object" && !Array.isArray(b.props) ? { props: b.props } : {}
1327
- })) : []
1589
+ ...(() => {
1590
+ const p = c.props;
1591
+ if (p && typeof p === "object" && !Array.isArray(p)) return { props: { ...p } };
1592
+ const s = c.styles;
1593
+ if (s && typeof s === "object" && !Array.isArray(s)) {
1594
+ return {
1595
+ props: {
1596
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1597
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1598
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1599
+ bgSize: s.backgroundSize || "cover",
1600
+ bgPosition: s.backgroundPosition || "center",
1601
+ bgGradient: s.backgroundGradient ?? null,
1602
+ padding: s.padding,
1603
+ borderRadius: s.borderRadius
1604
+ }
1605
+ };
1606
+ }
1607
+ return {};
1608
+ })(),
1609
+ blocks: Array.isArray(c.blocks) ? c.blocks.map((b, bi) => {
1610
+ const base2 = {
1611
+ id: ensureId(b?.id, `b${ri + 1}_${ci + 1}_${bi + 1}`),
1612
+ type: typeof b?.type === "string" ? b.type : "text",
1613
+ content: typeof b?.content === "string" ? b.content : b?.content && typeof b.content === "object" && !Array.isArray(b.content) ? b.content : {},
1614
+ ...b?.behavior && typeof b.behavior === "object" && !Array.isArray(b.behavior) ? { behavior: b.behavior } : {},
1615
+ ...b?.responsive && typeof b.responsive === "object" && !Array.isArray(b.responsive) ? { responsive: b.responsive } : {}
1616
+ };
1617
+ if (b?.props && typeof b.props === "object" && !Array.isArray(b.props)) {
1618
+ base2.props = { ...b.props };
1619
+ } else if (b?.styles && typeof b.styles === "object" && !Array.isArray(b.styles)) {
1620
+ base2.styles = { ...b.styles };
1621
+ }
1622
+ return base2;
1623
+ }) : []
1328
1624
  }))
1329
1625
  };
1330
- }),
1331
- globalStyles: doc.globalStyles && typeof doc.globalStyles === "object" && !Array.isArray(doc.globalStyles) ? doc.globalStyles : void 0,
1332
- responsive: doc.responsive && typeof doc.responsive === "object" && !Array.isArray(doc.responsive) ? doc.responsive : void 0
1626
+ })
1333
1627
  };
1334
1628
  }
1335
1629
 
@@ -1342,6 +1636,60 @@ function cloneJson(v) {
1342
1636
  return v;
1343
1637
  }
1344
1638
  }
1639
+ function legacyRowStylesToProps(s) {
1640
+ if (!s || typeof s !== "object" || Array.isArray(s)) return {};
1641
+ return {
1642
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1643
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1644
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1645
+ bgSize: s.backgroundSize || "cover",
1646
+ bgPosition: s.backgroundPosition || "center",
1647
+ bgGradient: s.backgroundGradient ?? null,
1648
+ padding: s.padding,
1649
+ borderRadius: s.borderRadius,
1650
+ borderWidth: s.borderWidth,
1651
+ borderColor: s.borderColor,
1652
+ textAlign: s.textAlign
1653
+ };
1654
+ }
1655
+ function rowPropsFromDocRow(r) {
1656
+ const p = r.props;
1657
+ if (p && typeof p === "object" && !Array.isArray(p)) {
1658
+ return { ...p };
1659
+ }
1660
+ return legacyRowStylesToProps(r.styles);
1661
+ }
1662
+ function applyRowPropsToEditorRow(row, r) {
1663
+ const p = rowPropsFromDocRow(r);
1664
+ if (p.padding !== void 0 && p.padding !== null) {
1665
+ row.padding = paddingToUniform(p.padding, row.padding);
1666
+ }
1667
+ if (typeof p.bgColor === "string") row.bgColor = p.bgColor.trim();
1668
+ if (typeof p.bgImage === "string") row.bgImage = p.bgImage.trim();
1669
+ if (typeof p.bgRepeat === "string") row.bgRepeat = p.bgRepeat;
1670
+ if (typeof p.bgSize === "string") row.bgSize = p.bgSize;
1671
+ if (typeof p.bgPosition === "string") row.bgPosition = p.bgPosition;
1672
+ if (p.bgGradient !== void 0) row.bgGradient = p.bgGradient;
1673
+ }
1674
+ function editorRowToDocProps(r) {
1675
+ const rowPad = r.padding && typeof r.padding === "object" && !Array.isArray(r.padding) ? layoutColumnPaddingForDoc(r.padding) : (() => {
1676
+ const n = typeof r.padding === "number" && Number.isFinite(r.padding) ? r.padding : 0;
1677
+ return { top: n, right: n, bottom: n, left: n };
1678
+ })();
1679
+ return {
1680
+ bgColor: typeof r.bgColor === "string" ? r.bgColor : "",
1681
+ bgImage: typeof r.bgImage === "string" ? r.bgImage : "",
1682
+ bgRepeat: r.bgRepeat || "no-repeat",
1683
+ bgSize: r.bgSize || "cover",
1684
+ bgPosition: r.bgPosition || "center",
1685
+ bgGradient: r.bgGradient ?? null,
1686
+ padding: rowPad,
1687
+ borderRadius: 0,
1688
+ borderWidth: 0,
1689
+ borderColor: "#e5e7eb",
1690
+ textAlign: "left"
1691
+ };
1692
+ }
1345
1693
  function layoutColumnPaddingForDoc(p) {
1346
1694
  if (typeof p === "number" && Number.isFinite(p)) {
1347
1695
  const n = Math.max(0, p);
@@ -1361,16 +1709,58 @@ function layoutColumnBorderRadiusForDoc(r) {
1361
1709
  }
1362
1710
  return 0;
1363
1711
  }
1364
- function columnPaddingNonZero(pad3) {
1365
- return pad3.top !== 0 || pad3.right !== 0 || pad3.bottom !== 0 || pad3.left !== 0;
1712
+ function normalizeLayoutContainerProps(props) {
1713
+ const columns = getColumns(props);
1714
+ const { columnStyles: _drop, ...rest } = props;
1715
+ return { ...rest, columns };
1716
+ }
1717
+ function mergeLayoutColumns(fromContent, fromHydrated) {
1718
+ const norm = (x) => {
1719
+ if (Array.isArray(x)) {
1720
+ return x.map((entry) => {
1721
+ const e = entry;
1722
+ const raw = e?.props && typeof e.props === "object" ? e.props : entry;
1723
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...raw } };
1724
+ });
1725
+ }
1726
+ if (x && typeof x === "object" && !Array.isArray(x)) {
1727
+ return columnsFromLegacyMap(x, Object.keys(x).length);
1728
+ }
1729
+ return [];
1730
+ };
1731
+ const a = norm(fromContent);
1732
+ const b = norm(fromHydrated);
1733
+ const len = Math.max(a.length, b.length, 1);
1734
+ const out = [];
1735
+ for (let i = 0; i < len; i++) {
1736
+ out.push({
1737
+ props: { ...DEFAULT_COLUMN_PROPS, ...a[i]?.props ?? {}, ...b[i]?.props ?? {} }
1738
+ });
1739
+ }
1740
+ return out.length ? out : void 0;
1366
1741
  }
1367
- function columnRadiusNonZero(r) {
1368
- if (typeof r === "number") return r !== 0;
1369
- if (r && typeof r === "object" && !Array.isArray(r)) {
1370
- const o = r;
1371
- return !!(o.tl || o.tr || o.br || o.bl);
1742
+ function columnPropsFromDocColumn(c) {
1743
+ const p = c?.props;
1744
+ if (p && typeof p === "object" && !Array.isArray(p)) {
1745
+ return { ...DEFAULT_COLUMN_PROPS, ...p };
1372
1746
  }
1373
- return false;
1747
+ const s = c?.styles;
1748
+ if (!s || typeof s !== "object" || Array.isArray(s)) {
1749
+ return { ...DEFAULT_COLUMN_PROPS };
1750
+ }
1751
+ const padding = layoutColumnPaddingForDoc(s.padding);
1752
+ const borderRadius = layoutColumnBorderRadiusForDoc(s.borderRadius);
1753
+ return {
1754
+ ...DEFAULT_COLUMN_PROPS,
1755
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1756
+ bgImage: typeof s.backgroundImage === "string" ? String(s.backgroundImage).trim() : "",
1757
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1758
+ bgSize: s.backgroundSize || "cover",
1759
+ bgPosition: s.backgroundPosition || "center",
1760
+ bgGradient: s.backgroundGradient ?? null,
1761
+ padding,
1762
+ borderRadius
1763
+ };
1374
1764
  }
1375
1765
  function asNum(v) {
1376
1766
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
@@ -1467,13 +1857,15 @@ function internalBlockToEmailDoc(b, depth = 0) {
1467
1857
  ...p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1468
1858
  }
1469
1859
  };
1470
- case "html":
1860
+ case "html": {
1861
+ const body = typeof b.content === "string" ? b.content : typeof p.content === "string" ? p.content : "";
1471
1862
  return {
1472
1863
  id,
1473
1864
  type,
1474
- content: { html: typeof p.content === "string" ? p.content : "" },
1865
+ content: { html: normalizeRichHtmlForStorage(body) },
1475
1866
  styles: p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1476
1867
  };
1868
+ }
1477
1869
  case "image": {
1478
1870
  const br = p.borderRadius;
1479
1871
  const borderRadius = typeof br === "number" && Number.isFinite(br) ? br : br && typeof br === "object" && !Array.isArray(br) ? Math.round(
@@ -1653,7 +2045,7 @@ function internalBlockToEmailDoc(b, depth = 0) {
1653
2045
  bgRepeat: p.bgRepeat,
1654
2046
  bgPosition: p.bgPosition,
1655
2047
  bgGradient: p.bgGradient ?? null,
1656
- columnStyles: p.columnStyles,
2048
+ columns: getColumns(p).map((col) => ({ props: cloneJson(col.props) })),
1657
2049
  cells: cellsOut
1658
2050
  },
1659
2051
  styles: {}
@@ -1693,17 +2085,26 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1693
2085
  block.props.padding = paddingToUniform(s.padding, block.props.padding);
1694
2086
  }
1695
2087
  break;
1696
- case "html":
1697
- block.props.content = asStr(c.html) ?? block.props.content;
1698
- block.props.content = normalizeRichHtmlForStorage(block.props.content);
2088
+ case "html": {
2089
+ const br = b;
2090
+ const fromDoc = asStr(c.html);
2091
+ const fromRoot = typeof br.content === "string" ? String(br.content) : void 0;
2092
+ const fromLegacyProps = br.props && typeof br.props.content === "string" ? String(br.props.content) : void 0;
2093
+ const htmlNorm = normalizeRichHtmlForStorage(fromDoc ?? fromRoot ?? fromLegacyProps ?? "");
2094
+ block.content = htmlNorm;
2095
+ block.props.content = htmlNorm;
1699
2096
  if (s.padding && typeof s.padding === "object") {
1700
2097
  block.props.padding = paddingToUniform(s.padding, block.props.padding);
1701
2098
  }
1702
2099
  break;
2100
+ }
1703
2101
  case "image":
1704
2102
  block.props.src = asStr(c.src) ?? block.props.src;
1705
2103
  if (asStr(c.alt)) block.props.alt = c.alt;
1706
- if (asStr(c.link)) block.props.link = c.link;
2104
+ if (asStr(c.link)) {
2105
+ block.props.link = c.link;
2106
+ block.props.linkEnabled = true;
2107
+ }
1707
2108
  if (beh.openInNewTab === true) block.props.linkTarget = "_blank";
1708
2109
  if (asStr(s.width)) block.props.width = s.width;
1709
2110
  if (asStr(s.align)) block.props.align = s.align;
@@ -1825,8 +2226,17 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1825
2226
  if (typeof c.bgRepeat === "string") block.props.bgRepeat = c.bgRepeat;
1826
2227
  if (typeof c.bgPosition === "string") block.props.bgPosition = c.bgPosition;
1827
2228
  if (c.bgGradient !== void 0) block.props.bgGradient = c.bgGradient;
1828
- if (c.columnStyles && typeof c.columnStyles === "object" && !Array.isArray(c.columnStyles)) {
1829
- block.props.columnStyles = c.columnStyles;
2229
+ if (c.bgGradient !== void 0) block.props.bgGradient = c.bgGradient;
2230
+ if (Array.isArray(c.columns)) {
2231
+ block.props.columns = c.columns.map((col) => ({
2232
+ props: {
2233
+ ...DEFAULT_COLUMN_PROPS,
2234
+ ...col?.props && typeof col.props === "object" ? col.props : {}
2235
+ }
2236
+ }));
2237
+ } else if (c.columnStyles && typeof c.columnStyles === "object" && !Array.isArray(c.columnStyles)) {
2238
+ const ratios = Array.isArray(c.ratios) ? c.ratios : [1];
2239
+ block.props.columns = columnsFromLegacyMap(c.columnStyles, ratios.length);
1830
2240
  }
1831
2241
  block.props.cells = c.cells.map(
1832
2242
  (col) => Array.isArray(col) ? col.map((raw) => mapEmbeddedLayoutCellBlock(raw, layoutDepth + 1)).filter((x) => x != null) : []
@@ -1870,6 +2280,10 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1870
2280
  }
1871
2281
  }
1872
2282
  applyImportedEditorPropsFromDoc(block, t, b, layoutDepth);
2283
+ if (t === "image") {
2284
+ const link = asStr(block.props.link);
2285
+ if (link && block.props.linkEnabled == null) block.props.linkEnabled = true;
2286
+ }
1873
2287
  return block;
1874
2288
  }
1875
2289
  function rowToInternal(r) {
@@ -1891,55 +2305,315 @@ function rowToInternal(r) {
1891
2305
  row.id = typeof r.id === "string" ? r.id : uid();
1892
2306
  row.cols = cols;
1893
2307
  row.gap = typeof r.layout?.gap === "number" ? r.layout.gap : row.gap;
1894
- row.padding = paddingToUniform(r.styles?.padding, row.padding);
1895
- row.bgColor = (r.styles?.backgroundColor || "").trim();
1896
- row.bgImage = (r.styles?.backgroundImage || "").trim();
1897
- row.bgRepeat = r.styles?.backgroundRepeat || row.bgRepeat;
1898
- row.bgSize = r.styles?.backgroundSize || row.bgSize;
1899
- row.bgPosition = r.styles?.backgroundPosition || row.bgPosition || "center";
1900
- row.bgGradient = r.styles?.backgroundGradient || row.bgGradient || null;
2308
+ applyRowPropsToEditorRow(row, r);
2309
+ const lay = r.layout;
2310
+ if (lay && typeof lay === "object" && !Array.isArray(lay)) {
2311
+ if (typeof lay.preset === "string" && lay.preset.trim()) row.preset = lay.preset.trim();
2312
+ if (Array.isArray(lay.ratios) && lay.ratios.length) {
2313
+ const rr = lay.ratios.filter((x) => typeof x === "number" && Number.isFinite(x));
2314
+ if (rr.length) row.ratios = rr;
2315
+ }
2316
+ }
1901
2317
  row.cells = colList.map((c) => {
1902
2318
  const blocks = Array.isArray(c.blocks) ? c.blocks : [];
1903
2319
  return blocks.map((blk) => mapBlockToInternal(blk, 0)).filter((x) => x != null);
1904
2320
  });
1905
2321
  const studio = r._reactEmailStudio;
1906
2322
  const snap = studio?.row;
1907
- const columnStylesFromDoc = {};
1908
- colList.forEach((c, i) => {
1909
- const bgColor = typeof c.styles?.backgroundColor === "string" ? c.styles.backgroundColor : "";
1910
- const padding = layoutColumnPaddingForDoc(c.styles?.padding);
1911
- const borderRadius = layoutColumnBorderRadiusForDoc(c.styles?.borderRadius);
1912
- const bgImage = typeof c.styles?.backgroundImage === "string" ? String(c.styles.backgroundImage).trim() : "";
1913
- const bgRepeat = typeof c.styles?.backgroundRepeat === "string" ? c.styles.backgroundRepeat : void 0;
1914
- const bgSize = typeof c.styles?.backgroundSize === "string" ? c.styles.backgroundSize : void 0;
1915
- const bgPosition = typeof c.styles?.backgroundPosition === "string" ? c.styles.backgroundPosition : void 0;
1916
- const bgGradient = c.styles?.backgroundGradient || null;
1917
- const hasPad = columnPaddingNonZero(padding);
1918
- const hasBr = columnRadiusNonZero(borderRadius);
1919
- if (bgColor || hasPad || hasBr || bgImage || bgRepeat || bgSize || bgPosition || bgGradient) {
1920
- columnStylesFromDoc[i] = { bgColor, padding, borderRadius, bgImage, bgRepeat, bgSize, bgPosition, bgGradient };
1921
- }
1922
- });
2323
+ const columnsFromDoc = colList.map((c) => ({
2324
+ props: columnPropsFromDocColumn(c)
2325
+ }));
1923
2326
  if (snap && typeof snap === "object" && !Array.isArray(snap)) {
1924
- const { cells: _omitCells, columnStyles: snapCs, ...rest } = snap;
2327
+ const {
2328
+ cells: _omitCells,
2329
+ columnStyles: legacyCs,
2330
+ columns: snapCols,
2331
+ ...rest
2332
+ } = snap;
1925
2333
  Object.assign(row, rest);
1926
- const snapColumn = snapCs && typeof snapCs === "object" && !Array.isArray(snapCs) ? snapCs : {};
1927
- row.columnStyles = { ...snapColumn, ...columnStylesFromDoc };
1928
- if (!Object.keys(row.columnStyles).length) delete row.columnStyles;
1929
- } else if (Object.keys(columnStylesFromDoc).length) {
1930
- row.columnStyles = columnStylesFromDoc;
2334
+ const fromSnap = Array.isArray(snapCols) ? snapCols.map((col) => ({
2335
+ props: {
2336
+ ...DEFAULT_COLUMN_PROPS,
2337
+ ...col?.props && typeof col.props === "object" ? col.props : {}
2338
+ }
2339
+ })) : columnsFromLegacyMap(legacyCs, cols);
2340
+ row.columns = mergeLayoutColumns(fromSnap, columnsFromDoc) ?? columnsFromDoc;
2341
+ delete row.columnStyles;
2342
+ } else {
2343
+ row.columns = columnsFromDoc;
1931
2344
  }
1932
2345
  return row;
1933
2346
  }
1934
- function normalizeEmailDesignInput(input) {
1935
- const doc = normalizeEmailDocument(input);
1936
- if (!doc) return null;
1937
- const s = doc.settings || {};
1938
- const studioSettings = s._reactEmailStudio;
2347
+ function coerceEmailDocumentInput(input) {
2348
+ if (input == null) return input;
2349
+ if (Array.isArray(input)) {
2350
+ if (input.length === 0) return input;
2351
+ return { type: "email_document", settings: {}, blocks: input };
2352
+ }
2353
+ if (typeof input !== "object") return input;
2354
+ const o = input;
2355
+ if (o.type === "email_document") return input;
2356
+ if (Array.isArray(o.blocks)) {
2357
+ return {
2358
+ type: "email_document",
2359
+ settings: o.settings && typeof o.settings === "object" ? o.settings : {},
2360
+ blocks: o.blocks
2361
+ };
2362
+ }
2363
+ return input;
2364
+ }
2365
+ function rootLayoutBlockToDocRow(block) {
2366
+ const p = block.props && typeof block.props === "object" ? block.props : {};
2367
+ const c = block.content && typeof block.content === "object" && !Array.isArray(block.content) ? block.content : {};
2368
+ const cols = Math.max(
2369
+ 1,
2370
+ typeof p.cols === "number" && p.cols > 0 ? Math.floor(p.cols) : 0,
2371
+ typeof c.cols === "number" && c.cols > 0 ? Math.floor(c.cols) : 0,
2372
+ Array.isArray(p.cells) ? p.cells.length : 0,
2373
+ Array.isArray(c.cells) ? c.cells.length : 0
2374
+ );
2375
+ const rawCells = Array.isArray(c.cells) ? c.cells : Array.isArray(p.cells) ? p.cells : [];
2376
+ const contentColumns = Array.isArray(c.columns) ? c.columns : [];
2377
+ const propsColumns = getColumns(p);
2378
+ const columns = Array.from({ length: cols }, (_, ci) => {
2379
+ const cellRaw = rawCells[ci];
2380
+ const blocks = Array.isArray(cellRaw) ? cellRaw.filter((x) => x && typeof x === "object").map((x, bi) => {
2381
+ const blk = x;
2382
+ return {
2383
+ id: ensureBlockId(blk.id, `b_${ci}_${bi}`),
2384
+ type: typeof blk.type === "string" ? blk.type : "text",
2385
+ content: blk.content,
2386
+ ...blk.props ? { props: blk.props } : {},
2387
+ ...blk.styles ? { styles: blk.styles } : {},
2388
+ ...blk.behavior ? { behavior: blk.behavior } : {}
2389
+ };
2390
+ }) : [];
2391
+ const colProps = {
2392
+ ...DEFAULT_COLUMN_PROPS,
2393
+ ...propsColumns[ci]?.props ?? contentColumns[ci]?.props ?? {}
2394
+ };
2395
+ return {
2396
+ id: `col_${ci + 1}`,
2397
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2398
+ props: cloneJson(colProps),
2399
+ blocks
2400
+ };
2401
+ });
2402
+ const rowProps = {
2403
+ bgColor: typeof p.bgColor === "string" ? p.bgColor : "",
2404
+ bgImage: typeof p.bgImage === "string" ? p.bgImage : "",
2405
+ bgRepeat: p.bgRepeat || "no-repeat",
2406
+ bgSize: p.bgSize || "cover",
2407
+ bgPosition: p.bgPosition || "center",
2408
+ bgGradient: p.bgGradient ?? null,
2409
+ padding: typeof p.padding === "number" ? { top: p.padding, right: p.padding, bottom: p.padding, left: p.padding } : normalizePadding(p.padding)
2410
+ };
2411
+ const ratios = Array.isArray(p.ratios) ? p.ratios : Array.isArray(c.ratios) ? c.ratios : void 0;
2412
+ return {
2413
+ id: typeof block.id === "string" ? block.id : uid(),
2414
+ type: "row",
2415
+ layout: {
2416
+ columns: cols,
2417
+ gap: typeof p.gap === "number" ? p.gap : typeof c.gap === "number" ? c.gap : 0,
2418
+ stackOnMobile: true,
2419
+ align: "center",
2420
+ ...typeof p.preset === "string" && p.preset.trim() ? { preset: p.preset.trim() } : typeof c.preset === "string" && c.preset.trim() ? { preset: c.preset.trim() } : {},
2421
+ ...ratios?.length ? { ratios: [...ratios] } : {}
2422
+ },
2423
+ props: rowProps,
2424
+ columns
2425
+ };
2426
+ }
2427
+ function ensureBlockId(id, prefix) {
2428
+ return typeof id === "string" && id.trim() ? id : `${prefix}_${uid()}`;
2429
+ }
2430
+ function wrapRootContentBlock(block) {
2431
+ const row = makeLayoutRow(pickPresetByColCount(1));
2432
+ const internal = mapBlockToInternal(block, 0);
2433
+ if (internal) row.cells[0] = [internal];
2434
+ return row;
2435
+ }
2436
+ function rootBlockToEditorRow(block) {
2437
+ const t = block.type === "nestedRow" ? "layout" : block.type;
2438
+ if (t === "layout") {
2439
+ return rowToInternal(rootLayoutBlockToDocRow(block));
2440
+ }
2441
+ return wrapRootContentBlock(block);
2442
+ }
2443
+ function blocksToEditorRows(blocks) {
2444
+ return blocks.map(rootBlockToEditorRow);
2445
+ }
2446
+ function rowPropsToLegacyStyles(p) {
2447
+ return {
2448
+ backgroundColor: p.bgColor ?? "",
2449
+ backgroundImage: p.bgImage ?? "",
2450
+ backgroundRepeat: p.bgRepeat ?? "no-repeat",
2451
+ backgroundSize: p.bgSize ?? "cover",
2452
+ backgroundPosition: p.bgPosition ?? "center",
2453
+ backgroundGradient: p.bgGradient ?? null,
2454
+ padding: p.padding,
2455
+ borderRadius: p.borderRadius ?? 0,
2456
+ borderWidth: p.borderWidth ?? 0,
2457
+ borderColor: p.borderColor ?? "#e5e7eb",
2458
+ textAlign: p.textAlign ?? "left"
2459
+ };
2460
+ }
2461
+ function columnPropsToLegacyStyles(cp) {
2462
+ return {
2463
+ padding: layoutColumnPaddingForDoc(cp.padding),
2464
+ backgroundColor: cp.bgColor ?? "",
2465
+ backgroundImage: cp.bgImage ?? "",
2466
+ backgroundRepeat: cp.bgRepeat ?? "no-repeat",
2467
+ backgroundSize: cp.bgSize ?? "cover",
2468
+ backgroundPosition: cp.bgPosition ?? "center",
2469
+ backgroundGradient: cp.bgGradient ?? null,
2470
+ borderRadius: layoutColumnBorderRadiusForDoc(cp.borderRadius)
2471
+ };
2472
+ }
2473
+ function editorRowStudioSnapshot(r) {
2474
+ const p = editorRowToDocProps(r);
2475
+ const columns = getColumns(r).map((col) => ({
2476
+ props: cloneJson(col.props ?? DEFAULT_COLUMN_PROPS)
2477
+ }));
2478
+ return {
2479
+ id: r.id,
2480
+ type: "layout",
2481
+ preset: r.preset,
2482
+ cols: r.cols,
2483
+ ratios: Array.isArray(r.ratios) ? [...r.ratios] : [1],
2484
+ gap: typeof r.gap === "number" ? r.gap : 0,
2485
+ padding: p.padding,
2486
+ bgColor: p.bgColor ?? "",
2487
+ bgImage: p.bgImage ?? "",
2488
+ bgSize: p.bgSize ?? "cover",
2489
+ bgRepeat: p.bgRepeat ?? "no-repeat",
2490
+ bgPosition: p.bgPosition ?? "center",
2491
+ bgGradient: p.bgGradient ?? null,
2492
+ columns
2493
+ };
2494
+ }
2495
+ function exportBlockForLegacyRow(b, depth = 0) {
2496
+ if (!b || typeof b !== "object") {
2497
+ return { id: uid(), type: "text", content: {}, props: {} };
2498
+ }
2499
+ const rawProps = b.props && typeof b.props === "object" && !Array.isArray(b.props) ? cloneJson(b.props) : {};
2500
+ if (b.type === "layout" && Array.isArray(b.props?.cells)) {
2501
+ rawProps.cells = b.props.cells.map(
2502
+ (col) => Array.isArray(col) ? col.map((child) => exportBlockForLegacyRow(child, depth + 1)) : []
2503
+ );
2504
+ }
2505
+ if (b.type === "html") {
2506
+ const body = typeof b.content === "string" ? b.content : typeof rawProps.content === "string" ? rawProps.content : "";
2507
+ const html = normalizeRichHtmlForStorage(body);
2508
+ rawProps.content = html;
2509
+ return {
2510
+ id: typeof b.id === "string" ? b.id : uid(),
2511
+ type: "html",
2512
+ content: { html },
2513
+ props: rawProps
2514
+ };
2515
+ }
2516
+ const doc = internalBlockToEmailDoc(b, depth);
2517
+ const out = {
2518
+ id: doc.id,
2519
+ type: doc.type,
2520
+ content: doc.content ?? {},
2521
+ props: rawProps
2522
+ };
2523
+ if (doc.behavior) out.behavior = doc.behavior;
2524
+ return out;
2525
+ }
2526
+ function editorRowToEmailDocumentRow(r, rowIndex) {
2527
+ const rowProps = editorRowToDocProps(r);
2528
+ const cellArrays = Array.isArray(r.cells) ? r.cells : [];
2529
+ const declaredCols = typeof r.cols === "number" && r.cols > 0 ? Math.floor(r.cols) : 0;
2530
+ const colCount = Math.max(1, cellArrays.length, declaredCols);
2531
+ const rowColumns = getColumns(r);
2532
+ const columns = Array.from({ length: colCount }, (_, ci) => {
2533
+ const cp = {
2534
+ ...DEFAULT_COLUMN_PROPS,
2535
+ ...rowColumns[ci]?.props ?? {}
2536
+ };
2537
+ const cellBlocks = cellArrays[ci];
2538
+ const blocks = Array.isArray(cellBlocks) ? cellBlocks.map((blk) => exportBlockForLegacyRow(blk)).filter((x) => x != null) : [];
2539
+ return {
2540
+ id: `col_${rowIndex + 1}_${ci + 1}`,
2541
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2542
+ styles: columnPropsToLegacyStyles(cp),
2543
+ props: cloneJson(cp),
2544
+ blocks
2545
+ };
2546
+ });
2547
+ const ratios = Array.isArray(r.ratios) ? [...r.ratios] : void 0;
2548
+ return {
2549
+ id: typeof r.id === "string" ? r.id : uid(),
2550
+ type: "row",
2551
+ _reactEmailStudio: { row: editorRowStudioSnapshot(r) },
2552
+ layout: {
2553
+ columns: colCount,
2554
+ gap: typeof r.gap === "number" ? r.gap : 0,
2555
+ stackOnMobile: true,
2556
+ align: "center",
2557
+ ...typeof r.preset === "string" && r.preset.trim() ? { preset: r.preset.trim() } : {},
2558
+ ...ratios?.length ? { ratios } : {}
2559
+ },
2560
+ props: rowProps,
2561
+ styles: rowPropsToLegacyStyles(rowProps),
2562
+ columns
2563
+ };
2564
+ }
2565
+ function buildExportSettingsWithStudio(settings) {
2566
+ const base = buildExportSettings(settings) ?? {};
2567
+ const editorSettings = cloneJson(settings);
2568
+ return {
2569
+ ...base,
2570
+ _reactEmailStudio: {
2571
+ contentPadding: base.contentPadding ?? 24,
2572
+ contentBorderRadius: base.contentBorderRadius ?? 8,
2573
+ editorSettings
2574
+ }
2575
+ };
2576
+ }
2577
+ function editorSettingStr(settings, key, fallback = "") {
2578
+ const v = settings[key];
2579
+ return typeof v === "string" ? v : fallback;
2580
+ }
2581
+ function buildExportSettings(settings) {
2582
+ const contentWidth = typeof settings.contentWidth === "number" ? settings.contentWidth : 600;
2583
+ const bgGradient = settings.bgGradient;
2584
+ const contentBgGradient = settings.contentBgGradient;
2585
+ return {
2586
+ width: contentWidth,
2587
+ backgroundColor: editorSettingStr(settings, "bgColor", "#f1f5f9"),
2588
+ backgroundImage: editorSettingStr(settings, "bgImage"),
2589
+ backgroundRepeat: editorSettingStr(settings, "bgRepeat", "no-repeat"),
2590
+ backgroundSize: editorSettingStr(settings, "bgSize", "cover"),
2591
+ backgroundPosition: editorSettingStr(settings, "bgPosition", "center"),
2592
+ backgroundGradient: bgGradient && typeof bgGradient === "object" && !Array.isArray(bgGradient) ? bgGradient : void 0,
2593
+ contentBackgroundColor: editorSettingStr(settings, "contentBg", "#ffffff"),
2594
+ contentBackgroundImage: editorSettingStr(settings, "contentBgImage"),
2595
+ contentBackgroundRepeat: editorSettingStr(settings, "contentBgRepeat", "no-repeat"),
2596
+ contentBackgroundSize: editorSettingStr(settings, "contentBgSize", "cover"),
2597
+ contentBackgroundPosition: editorSettingStr(settings, "contentBgPosition", "center"),
2598
+ contentBackgroundGradient: contentBgGradient && typeof contentBgGradient === "object" && !Array.isArray(contentBgGradient) ? contentBgGradient : void 0,
2599
+ fontFamily: editorSettingStr(settings, "fontFamily") || editorSettingStr(settings, "pageFontFamily", "Arial, Helvetica, sans-serif"),
2600
+ lineHeightBase: settings.pageLineHeight != null && settings.pageLineHeight !== "" ? Number(settings.pageLineHeight) : 1.6,
2601
+ color: editorSettingStr(settings, "pageTextColor", "#111827"),
2602
+ responsive: typeof settings.pageResponsive === "boolean" ? settings.pageResponsive : true,
2603
+ rtl: !!settings.pageRtl,
2604
+ contentPadding: typeof settings.padding === "number" ? settings.padding : 24,
2605
+ contentBorderRadius: typeof settings.borderRadius === "number" ? settings.borderRadius : 8
2606
+ };
2607
+ }
2608
+ function editorSettingsFromDoc(s, rawSettings) {
2609
+ const studioSettings = rawSettings?._reactEmailStudio;
1939
2610
  const settings = {};
1940
2611
  if (studioSettings?.editorSettings && typeof studioSettings.editorSettings === "object" && !Array.isArray(studioSettings.editorSettings)) {
1941
2612
  Object.assign(settings, cloneJson(studioSettings.editorSettings));
1942
2613
  }
2614
+ const pageRtlFromEditor = settings.pageRtl;
2615
+ const contentPadding = typeof s.contentPadding === "number" ? s.contentPadding : typeof studioSettings?.contentPadding === "number" ? studioSettings.contentPadding : void 0;
2616
+ const contentBorderRadius = typeof s.contentBorderRadius === "number" ? s.contentBorderRadius : typeof studioSettings?.contentBorderRadius === "number" ? studioSettings.contentBorderRadius : void 0;
1943
2617
  Object.assign(settings, {
1944
2618
  bgColor: s.backgroundColor || "#f1f5f9",
1945
2619
  bgImage: s.backgroundImage || "",
@@ -1954,103 +2628,67 @@ function normalizeEmailDesignInput(input) {
1954
2628
  contentBgPosition: s.contentBackgroundPosition || "center",
1955
2629
  contentBgGradient: s.contentBackgroundGradient || null,
1956
2630
  contentWidth: typeof s.width === "number" ? s.width : 600,
1957
- padding: typeof studioSettings?.contentPadding === "number" ? studioSettings.contentPadding : typeof settings.padding === "number" ? settings.padding : 24,
1958
- borderRadius: typeof studioSettings?.contentBorderRadius === "number" ? studioSettings.contentBorderRadius : typeof settings.borderRadius === "number" ? settings.borderRadius : 8,
2631
+ padding: contentPadding ?? (typeof settings.padding === "number" ? settings.padding : 24),
2632
+ borderRadius: contentBorderRadius ?? (typeof settings.borderRadius === "number" ? settings.borderRadius : 8),
1959
2633
  pageFontFamily: s.fontFamily,
1960
2634
  pageTextColor: s.color,
1961
2635
  pageLineHeight: s.lineHeightBase != null ? String(s.lineHeightBase) : void 0,
1962
2636
  pageResponsive: s.responsive,
1963
- pageRtl: s.rtl,
2637
+ pageRtl: typeof s.rtl === "boolean" ? s.rtl : pageRtlFromEditor,
1964
2638
  fontFamily: s.fontFamily
1965
2639
  });
1966
- const rows = (doc.rows || []).map(rowToInternal);
2640
+ return settings;
2641
+ }
2642
+ function normalizeEmailDesignInput(input) {
2643
+ const coerced = coerceEmailDocumentInput(input);
2644
+ const doc = normalizeEmailDocument(coerced);
2645
+ if (!doc) return null;
2646
+ const rawDoc = input && typeof input === "object" && !Array.isArray(input) ? input : null;
2647
+ const rawSettings = rawDoc?.settings && typeof rawDoc.settings === "object" && !Array.isArray(rawDoc.settings) ? rawDoc.settings : null;
2648
+ const settings = editorSettingsFromDoc(doc.settings || {}, rawSettings);
2649
+ let rows;
2650
+ if (doc.blocks?.length) {
2651
+ rows = blocksToEditorRows(doc.blocks);
2652
+ } else {
2653
+ const rawRows = rawDoc && Array.isArray(rawDoc.rows) ? rawDoc.rows : [];
2654
+ rows = (doc.rows || []).map((r, i) => {
2655
+ const legacy = rawRows[i]?._reactEmailStudio;
2656
+ if (legacy) {
2657
+ return rowToInternal({ ...r, _reactEmailStudio: legacy });
2658
+ }
2659
+ return rowToInternal(r);
2660
+ });
2661
+ }
1967
2662
  return withHydratedRows({ rows, settings, __emailDocument: doc });
1968
2663
  }
2664
+ function emailDocumentToEditorRows(input) {
2665
+ const coerced = coerceEmailDocumentInput(input);
2666
+ const doc = normalizeEmailDocument(coerced);
2667
+ if (!doc) return null;
2668
+ const rawDoc = input && typeof input === "object" && !Array.isArray(input) ? input : null;
2669
+ if (doc.blocks?.length) {
2670
+ return blocksToEditorRows(doc.blocks);
2671
+ }
2672
+ const rawRows = rawDoc && Array.isArray(rawDoc.rows) ? rawDoc.rows : [];
2673
+ return (doc.rows || []).map((r, i) => {
2674
+ const legacy = rawRows[i]?._reactEmailStudio;
2675
+ if (legacy) {
2676
+ return rowToInternal({ ...r, _reactEmailStudio: legacy });
2677
+ }
2678
+ return rowToInternal(r);
2679
+ });
2680
+ }
1969
2681
  function designToEmailDocument(rows, settings) {
1970
- const doc = {
2682
+ return {
1971
2683
  type: "email_document",
1972
- settings: {
1973
- width: settings.contentWidth || 600,
1974
- backgroundColor: settings.bgColor || "#f1f5f9",
1975
- backgroundImage: settings.bgImage || "",
1976
- backgroundRepeat: settings.bgRepeat || "no-repeat",
1977
- backgroundSize: settings.bgSize || "cover",
1978
- backgroundPosition: settings.bgPosition || "center",
1979
- backgroundGradient: settings.bgGradient || null,
1980
- contentBackgroundColor: settings.contentBg || "#ffffff",
1981
- contentBackgroundImage: settings.contentBgImage || "",
1982
- contentBackgroundRepeat: settings.contentBgRepeat || "no-repeat",
1983
- contentBackgroundSize: settings.contentBgSize || "cover",
1984
- contentBackgroundPosition: settings.contentBgPosition || "center",
1985
- contentBackgroundGradient: settings.contentBgGradient || null,
1986
- fontFamily: settings.fontFamily || settings.pageFontFamily || "Arial, Helvetica, sans-serif",
1987
- lineHeightBase: settings.pageLineHeight ? Number(settings.pageLineHeight) : 1.6,
1988
- color: settings.pageTextColor || "#111827",
1989
- responsive: typeof settings.pageResponsive === "boolean" ? settings.pageResponsive : true,
1990
- rtl: !!settings.pageRtl,
1991
- _reactEmailStudio: {
1992
- contentPadding: settings.padding ?? 24,
1993
- contentBorderRadius: settings.borderRadius ?? 8,
1994
- editorSettings: cloneJson(settings)
1995
- }
1996
- },
1997
- rows: rows.map((r, ri) => {
1998
- const cellArrays = Array.isArray(r.cells) ? r.cells : [];
1999
- const declaredCols = typeof r.cols === "number" && r.cols > 0 ? r.cols : 0;
2000
- const cols = Math.max(1, cellArrays.length, declaredCols);
2001
- const rowPad = r.padding && typeof r.padding === "object" && !Array.isArray(r.padding) ? layoutColumnPaddingForDoc(r.padding) : (() => {
2002
- const n = typeof r.padding === "number" && Number.isFinite(r.padding) ? r.padding : 0;
2003
- return { top: n, right: n, bottom: n, left: n };
2004
- })();
2005
- const { cells: _rowCells, ...rowEditorSnap } = r;
2006
- return {
2007
- id: r.id || `row_${ri + 1}`,
2008
- type: "row",
2009
- _reactEmailStudio: {
2010
- row: cloneJson(rowEditorSnap)
2011
- },
2012
- layout: {
2013
- columns: cols,
2014
- gap: r.gap ?? 0,
2015
- stackOnMobile: true,
2016
- align: "center"
2017
- },
2018
- styles: {
2019
- backgroundColor: r.bgColor || "",
2020
- backgroundImage: r.bgImage || "",
2021
- backgroundRepeat: r.bgRepeat || "no-repeat",
2022
- backgroundSize: r.bgSize || "cover",
2023
- backgroundPosition: r.bgPosition || "center",
2024
- backgroundGradient: r.bgGradient || null,
2025
- padding: rowPad,
2026
- borderRadius: 0,
2027
- borderWidth: 0,
2028
- borderColor: "#e5e7eb",
2029
- textAlign: "left"
2030
- },
2031
- columns: Array.from({ length: cols }, (_, ci) => {
2032
- const cellBlocks = cellArrays[ci] ?? [];
2033
- const cs = r.columnStyles && r.columnStyles[ci] || {};
2034
- return {
2035
- id: `col_${ri + 1}_${ci + 1}`,
2036
- layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2037
- styles: {
2038
- padding: layoutColumnPaddingForDoc(cs.padding),
2039
- backgroundColor: cs.bgColor || "",
2040
- backgroundImage: cs.bgImage || "",
2041
- backgroundRepeat: cs.bgRepeat || "no-repeat",
2042
- backgroundSize: cs.bgSize || "cover",
2043
- backgroundPosition: cs.bgPosition || "center",
2044
- backgroundGradient: cs.bgGradient || null,
2045
- borderRadius: layoutColumnBorderRadiusForDoc(cs.borderRadius)
2046
- },
2047
- blocks: (cellBlocks || []).map((b) => exportEmailDocBlock(b))
2048
- };
2049
- })
2050
- };
2051
- })
2684
+ settings: buildExportSettingsWithStudio(settings),
2685
+ rows: rows.map((r, i) => editorRowToEmailDocumentRow(r, i))
2052
2686
  };
2053
- return doc;
2687
+ }
2688
+ function canonicalizeEmailDocument(input) {
2689
+ const loaded = normalizeEmailDesignInput(coerceEmailDocumentInput(input) ?? input);
2690
+ if (!loaded?.rows) return null;
2691
+ return designToEmailDocument(loaded.rows, loaded.settings);
2054
2692
  }
2055
2693
  function hydrateBlock(b, depth = 0) {
2056
2694
  if (!b || typeof b !== "object") return null;
@@ -2064,13 +2702,13 @@ function hydrateBlock(b, depth = 0) {
2064
2702
  ...b,
2065
2703
  type: "layout",
2066
2704
  id: typeof b.id === "string" && b.id ? b.id : uid(),
2067
- props: {
2705
+ props: normalizeLayoutContainerProps({
2068
2706
  ...defaults2,
2069
2707
  ...b.props,
2070
2708
  bgSize: b.props.bgSize ?? defaults2.bgSize,
2071
2709
  bgRepeat: b.props.bgRepeat ?? defaults2.bgRepeat,
2072
2710
  cells: ratios.map(() => [])
2073
- }
2711
+ })
2074
2712
  };
2075
2713
  }
2076
2714
  const cells = b.props.cells.map(
@@ -2080,24 +2718,33 @@ function hydrateBlock(b, depth = 0) {
2080
2718
  ...b,
2081
2719
  type: "layout",
2082
2720
  id: typeof b.id === "string" && b.id ? b.id : uid(),
2083
- props: {
2721
+ props: normalizeLayoutContainerProps({
2084
2722
  ...defaults2,
2085
2723
  ...b.props,
2086
2724
  bgSize: b.props.bgSize ?? defaults2.bgSize,
2087
2725
  bgRepeat: b.props.bgRepeat ?? defaults2.bgRepeat,
2088
2726
  cells
2089
- }
2727
+ })
2090
2728
  };
2091
2729
  }
2092
2730
  const defaults = DEFAULT_BLOCK_PROPS[t];
2093
2731
  if (typeof defaults !== "object" || defaults == null || Array.isArray(defaults)) return null;
2094
- const merged = t === "html" ? {
2095
- ...defaults,
2096
- ...b.props && typeof b.props === "object" ? b.props : {},
2097
- content: normalizeRichHtmlForStorage(
2098
- b.props && typeof b.props === "object" ? b.props.content : void 0
2099
- )
2100
- } : { ...defaults, ...b.props && typeof b.props === "object" ? b.props : {} };
2732
+ if (t === "html") {
2733
+ const rawProps = b.props && typeof b.props === "object" && !Array.isArray(b.props) ? b.props : {};
2734
+ const { content: legacyPropContent, ...restProps } = rawProps;
2735
+ const merged2 = { ...defaults, ...restProps };
2736
+ normalizeBoxStyles(merged2, t);
2737
+ const src = typeof b.content === "string" ? String(b.content) : typeof legacyPropContent === "string" ? legacyPropContent : "";
2738
+ const htmlNorm = normalizeRichHtmlForStorage(src);
2739
+ merged2.content = htmlNorm;
2740
+ return {
2741
+ ...b,
2742
+ id: typeof b.id === "string" && b.id ? b.id : uid(),
2743
+ content: htmlNorm,
2744
+ props: merged2
2745
+ };
2746
+ }
2747
+ const merged = { ...defaults, ...b.props && typeof b.props === "object" ? b.props : {} };
2101
2748
  normalizeBoxStyles(merged, t);
2102
2749
  return {
2103
2750
  ...b,
@@ -2113,70 +2760,50 @@ function mapEmbeddedLayoutCellBlock(raw, layoutDepth = 0) {
2113
2760
  }
2114
2761
  return mapBlockToInternal(r, layoutDepth);
2115
2762
  }
2116
- function mergeLayoutColumnStylesMaps(fromContent, fromHydrated) {
2117
- const isObj = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
2118
- if (!isObj(fromContent) && !isObj(fromHydrated)) return void 0;
2119
- const a = isObj(fromContent) ? fromContent : {};
2120
- const b = isObj(fromHydrated) ? fromHydrated : {};
2121
- const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
2122
- const out = {};
2123
- for (const k of keys) {
2124
- const va = a[k];
2125
- const vb = b[k];
2126
- if (isObj(va) && isObj(vb)) {
2127
- out[k] = { ...va, ...vb };
2128
- } else if (vb !== void 0) {
2129
- out[k] = vb;
2130
- } else {
2131
- out[k] = va;
2132
- }
2133
- }
2134
- return Object.keys(out).length ? out : void 0;
2135
- }
2136
2763
  function applyImportedEditorPropsFromDoc(block, t, b, layoutDepth = 0) {
2137
2764
  const exp = b.props;
2138
- if (!exp || typeof exp !== "object" || Array.isArray(exp)) return;
2765
+ if (!exp || typeof exp !== "object" || Array.isArray(exp)) {
2766
+ if (t === "html" && typeof block.content === "string") {
2767
+ block.content = normalizeRichHtmlForStorage(block.content);
2768
+ block.props.content = block.content;
2769
+ }
2770
+ return;
2771
+ }
2139
2772
  if (t === "layout" && Array.isArray(exp.cells)) {
2140
- const columnStylesFromContent = block.props.columnStyles;
2773
+ const columnsFromContent = block.props.columns ?? block.props.columnStyles;
2774
+ const expHasColumns = Array.isArray(exp.columns) || exp.columnStyles && typeof exp.columnStyles === "object" && !Array.isArray(exp.columnStyles);
2141
2775
  const hb = hydrateBlock({ type: "layout", id: block.id, props: exp }, layoutDepth);
2142
2776
  if (hb) {
2143
2777
  block.props = hb.props;
2144
- const merged = mergeLayoutColumnStylesMaps(columnStylesFromContent, block.props.columnStyles);
2145
- if (merged) block.props.columnStyles = merged;
2146
- else delete block.props.columnStyles;
2778
+ const merged = mergeLayoutColumns(
2779
+ columnsFromContent,
2780
+ expHasColumns ? block.props.columns : void 0
2781
+ );
2782
+ if (merged) block.props.columns = merged;
2783
+ else delete block.props.columns;
2784
+ delete block.props.columnStyles;
2147
2785
  if (typeof hb.id === "string" && hb.id) block.id = hb.id;
2148
2786
  }
2149
2787
  return;
2150
2788
  }
2789
+ if (t === "html") {
2790
+ const { content: expContent, ...expRest } = exp;
2791
+ Object.assign(block.props, expRest);
2792
+ normalizeBoxStyles(block.props, t);
2793
+ if (typeof expContent === "string") {
2794
+ block.content = normalizeRichHtmlForStorage(expContent);
2795
+ } else if (typeof block.content === "string") {
2796
+ block.content = normalizeRichHtmlForStorage(block.content);
2797
+ }
2798
+ block.props.content = typeof block.content === "string" ? block.content : "";
2799
+ return;
2800
+ }
2151
2801
  Object.assign(block.props, exp);
2152
2802
  normalizeBoxStyles(block.props, t);
2153
- if (t === "html" || t === "text") {
2803
+ if (t === "text") {
2154
2804
  block.props.content = normalizeRichHtmlForStorage(String(block.props.content ?? ""));
2155
2805
  }
2156
2806
  }
2157
- function exportEmailDocBlock(b, depth = 0) {
2158
- if (!b || typeof b !== "object") return { id: uid(), type: "text", content: {}, styles: {} };
2159
- const doc = internalBlockToEmailDoc(b, depth);
2160
- const out = { ...doc };
2161
- if (b.props && typeof b.props === "object" && !Array.isArray(b.props)) {
2162
- out.props = cloneJson(b.props);
2163
- }
2164
- if (b.type === "layout" && Array.isArray(b.props?.cells)) {
2165
- const baseContent = typeof doc.content === "object" && doc.content ? doc.content : {};
2166
- if (depth >= MAX_LAYOUT_TREE_DEPTH) {
2167
- const ratios = Array.isArray(b.props.ratios) && b.props.ratios.length ? b.props.ratios : [1];
2168
- out.content = { ...baseContent, cells: ratios.map(() => []) };
2169
- } else {
2170
- out.content = {
2171
- ...baseContent,
2172
- cells: b.props.cells.map(
2173
- (col) => Array.isArray(col) ? col.map((child) => exportEmailDocBlock(child, depth + 1)) : []
2174
- )
2175
- };
2176
- }
2177
- }
2178
- return out;
2179
- }
2180
2807
  function hydrateLayoutRow(row) {
2181
2808
  if (!row || typeof row !== "object") return row;
2182
2809
  const cells = (row.cells || []).map(
@@ -2219,6 +2846,77 @@ function jsonToHtml(designInput, opts = {}) {
2219
2846
  return designToHtml(d.rows, d.settings, opts);
2220
2847
  }
2221
2848
 
2849
+ // src/lib/htmlToEmailDesign.ts
2850
+ var pad = (n) => ({ top: n, right: n, bottom: n, left: n });
2851
+ function extractHtmlForDesign(html) {
2852
+ const t = String(html ?? "").trim();
2853
+ if (!t) return "";
2854
+ const head = t.slice(0, 800).toLowerCase();
2855
+ if (!head.includes("<html") && !head.includes("<!doctype")) {
2856
+ return normalizeRichHtmlForStorage(t);
2857
+ }
2858
+ try {
2859
+ const doc = new DOMParser().parseFromString(t, "text/html");
2860
+ const body = doc.body;
2861
+ if (!body) return normalizeRichHtmlForStorage(t);
2862
+ return normalizeRichHtmlForStorage(body.innerHTML);
2863
+ } catch {
2864
+ return normalizeRichHtmlForStorage(t);
2865
+ }
2866
+ }
2867
+ function htmlToEmailDesignTemplate(html) {
2868
+ const inner = extractHtmlForDesign(html);
2869
+ if (!inner) return null;
2870
+ const doc = {
2871
+ type: "email_document",
2872
+ settings: {
2873
+ width: 600,
2874
+ backgroundColor: "#f1f5f9",
2875
+ contentBackgroundColor: "#ffffff",
2876
+ fontFamily: "Arial, Helvetica, sans-serif",
2877
+ lineHeightBase: 1.6,
2878
+ color: "#111827",
2879
+ responsive: true,
2880
+ rtl: false
2881
+ },
2882
+ rows: [
2883
+ {
2884
+ id: "row_html_import",
2885
+ type: "row",
2886
+ layout: { columns: 1, gap: 0, stackOnMobile: true, align: "center" },
2887
+ styles: {
2888
+ backgroundColor: "#ffffff",
2889
+ backgroundRepeat: "no-repeat",
2890
+ backgroundSize: "cover",
2891
+ padding: pad(24),
2892
+ textAlign: "left"
2893
+ },
2894
+ columns: [
2895
+ {
2896
+ id: "col_html_import",
2897
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2898
+ styles: { padding: pad(0), backgroundColor: "" },
2899
+ blocks: [
2900
+ {
2901
+ id: "block_html_import",
2902
+ type: "html",
2903
+ content: inner,
2904
+ props: { ...DEFAULT_BLOCK_PROPS.html }
2905
+ }
2906
+ ]
2907
+ }
2908
+ ]
2909
+ }
2910
+ ]
2911
+ };
2912
+ return doc;
2913
+ }
2914
+ function htmlToJson(html, pretty = false) {
2915
+ const doc = htmlToEmailDesignTemplate(html);
2916
+ if (!doc) return "";
2917
+ return pretty ? JSON.stringify(doc, null, 2) : JSON.stringify(doc);
2918
+ }
2919
+
2222
2920
  // src/editor/canvas/DesignCanvas.tsx
2223
2921
  var import_react = require("react");
2224
2922
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -2359,13 +3057,15 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2359
3057
  )
2360
3058
  );
2361
3059
  if (type === "html") {
2362
- const emptyRich = isEffectivelyEmptyRichHtml(p.content);
3060
+ const richHtml = typeof block.content === "string" ? block.content : p.content;
3061
+ const emptyRich = isEffectivelyEmptyRichHtml(richHtml);
2363
3062
  return wrap(
2364
3063
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative", minHeight: emptyRich && !preview ? "2.75em" : void 0 }, children: [
3064
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: RICH_HTML_CONTENT_CSS }),
2365
3065
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2366
3066
  "div",
2367
3067
  {
2368
- className: "email-editor-rich-canvas",
3068
+ className: "email-editor-rich-canvas email-rich-html-content",
2369
3069
  style: {
2370
3070
  fontSize: p.fontSize,
2371
3071
  color: p.color,
@@ -2376,7 +3076,7 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2376
3076
  wordBreak: "break-word",
2377
3077
  minHeight: emptyRich && !preview ? "2.75em" : void 0
2378
3078
  },
2379
- dangerouslySetInnerHTML: { __html: p.content || "<p></p>" }
3079
+ dangerouslySetInnerHTML: { __html: richHtml || "" }
2380
3080
  }
2381
3081
  ),
2382
3082
  emptyRich && !preview ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2424,7 +3124,7 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2424
3124
  }
2425
3125
  }
2426
3126
  );
2427
- return wrap(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { textAlign: p.align }, children: p.link ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: p.link, target: p.linkTarget || "_blank", rel: "noopener", children: img }) : img }));
3127
+ return wrap(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { textAlign: p.align }, children: imageLinkActive(p) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: p.link, target: p.linkTarget || "_blank", rel: "noopener", children: img }) : img }));
2428
3128
  }
2429
3129
  if (type === "button") return wrap(
2430
3130
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { textAlign: p.fullWidth ? "center" : p.align }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2597,7 +3297,7 @@ function NestedRowBlock({
2597
3297
  );
2598
3298
  },
2599
3299
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: p.gap ?? 12 }, children: (p.cells || []).map((innerBlocks, ici) => {
2600
- const cS = p.columnStyles && p.columnStyles[ici] || {};
3300
+ const cS = getColumnPropsAt(p, ici);
2601
3301
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2602
3302
  "div",
2603
3303
  {
@@ -2655,7 +3355,9 @@ function BlockItem({
2655
3355
  onContextMenu,
2656
3356
  preview,
2657
3357
  C,
2658
- Line
3358
+ Line,
3359
+ dragAsRow = false,
3360
+ beginRowDrag
2659
3361
  }) {
2660
3362
  const wrapperRef = (0, import_react.useRef)(null);
2661
3363
  const bid = editorId ? `${editorId}-block-${cb.id}` : void 0;
@@ -2703,7 +3405,7 @@ function BlockItem({
2703
3405
  {
2704
3406
  "data-bidx": ci,
2705
3407
  draggable: !preview,
2706
- title: preview ? void 0 : "Click to select \xB7 drag to reorder or move",
3408
+ title: preview ? void 0 : dragAsRow ? "Click to select \xB7 drag to reorder section" : "Click to select \xB7 drag to reorder or move",
2707
3409
  onPointerDown: (e) => {
2708
3410
  if (preview || e.button !== 0) return;
2709
3411
  selectThisBlock(e);
@@ -2711,6 +3413,10 @@ function BlockItem({
2711
3413
  onDragStart: (e) => {
2712
3414
  if (preview) return;
2713
3415
  e.stopPropagation();
3416
+ if (dragAsRow && beginRowDrag) {
3417
+ beginRowDrag(e);
3418
+ return;
3419
+ }
2714
3420
  e.dataTransfer.effectAllowed = "move";
2715
3421
  e.dataTransfer.setData(
2716
3422
  "application/json",
@@ -2782,7 +3488,9 @@ function Cell({
2782
3488
  onDeleteContent,
2783
3489
  onContextMenu,
2784
3490
  preview,
2785
- C
3491
+ C,
3492
+ dragAsRow = false,
3493
+ beginRowDrag
2786
3494
  }) {
2787
3495
  const [insertAt, setInsertAt] = (0, import_react.useState)(null);
2788
3496
  const cellRef = (0, import_react.useRef)(null);
@@ -2797,6 +3505,14 @@ function Cell({
2797
3505
  return els.length;
2798
3506
  }, [blocks.length]);
2799
3507
  const handleDragOver = (e) => {
3508
+ try {
3509
+ const raw = e.dataTransfer.getData("application/json");
3510
+ if (raw) {
3511
+ const data = JSON.parse(raw);
3512
+ if (data.moveRowId) return;
3513
+ }
3514
+ } catch {
3515
+ }
2800
3516
  e.preventDefault();
2801
3517
  e.stopPropagation();
2802
3518
  setInsertAt(calcIdx(e.clientY));
@@ -2812,6 +3528,7 @@ function Cell({
2812
3528
  setInsertAt(null);
2813
3529
  try {
2814
3530
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
3531
+ if (data.moveRowId) return;
2815
3532
  if (data.contentType) {
2816
3533
  onDropContent(rowId, cellIdx, { kind: "new", contentType: data.contentType, insertAt: idx, nested: nestedPayload });
2817
3534
  } else if (data.moveContent) {
@@ -2891,7 +3608,9 @@ function Cell({
2891
3608
  onContextMenu,
2892
3609
  preview,
2893
3610
  C,
2894
- Line
3611
+ Line,
3612
+ dragAsRow: dragAsRow && blocks.length === 1 && ci === 0,
3613
+ beginRowDrag
2895
3614
  }
2896
3615
  ) }, cb.id))
2897
3616
  ]
@@ -2912,7 +3631,8 @@ function LayoutRow({
2912
3631
  onLayoutContextMenu,
2913
3632
  setSelectedRowId,
2914
3633
  preview = false,
2915
- rowDrag,
3634
+ rootContentRow = false,
3635
+ beginRowDrag,
2916
3636
  C
2917
3637
  }) {
2918
3638
  const rowStyle = {
@@ -2944,16 +3664,9 @@ function LayoutRow({
2944
3664
  onContextMenu: preview || !onLayoutContextMenu ? void 0 : (e) => {
2945
3665
  onLayoutContextMenu(e, row.id);
2946
3666
  },
2947
- draggable: rowDrag?.draggable === true,
2948
- onDragStart: rowDrag?.onDragStart,
2949
- onDragEnd: rowDrag?.onDragEnd,
2950
- title: rowDrag?.draggable ? "Click row \xB7 drag to reorder" : void 0,
2951
- style: {
2952
- ...rowStyle,
2953
- ...rowDrag?.draggable ? { cursor: "grab" } : {}
2954
- },
3667
+ style: rowStyle,
2955
3668
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: row.gap }, children: row.cells.map((cellBlocks, ci) => {
2956
- const cS = row.columnStyles && row.columnStyles[ci] || {};
3669
+ const cS = getColumnPropsAt(row, ci);
2957
3670
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2958
3671
  "div",
2959
3672
  {
@@ -2989,7 +3702,9 @@ function LayoutRow({
2989
3702
  onDeleteContent,
2990
3703
  onContextMenu,
2991
3704
  preview,
2992
- C
3705
+ C,
3706
+ dragAsRow: rootContentRow && ci === 0,
3707
+ beginRowDrag
2993
3708
  }
2994
3709
  )
2995
3710
  },
@@ -3006,10 +3721,11 @@ var CanvasRow = ({
3006
3721
  selectedRowId,
3007
3722
  selContentMeta: rowSelContentMeta,
3008
3723
  dragOver,
3724
+ draggingRowId,
3009
3725
  C,
3010
3726
  setDragOver,
3011
3727
  handleRowDrop,
3012
- setDraggingRowId,
3728
+ setRowDrag,
3013
3729
  setSelectedRowId,
3014
3730
  setSelContentId,
3015
3731
  setSelMeta,
@@ -3022,54 +3738,161 @@ var CanvasRow = ({
3022
3738
  }) => {
3023
3739
  const rowSelected = selectedRowId === row.id;
3024
3740
  const rid = (s) => editorId ? `${editorId}-${s}` : void 0;
3025
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { id: rid(`canvas-row-${row.id}`), children: [
3026
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3027
- "div",
3028
- {
3029
- id: rid(`row-drop-before-${ri}`),
3030
- onDragOver: (e) => {
3031
- e.preventDefault();
3032
- setDragOver(ri);
3033
- },
3034
- onDragLeave: () => setDragOver(null),
3035
- onDrop: (e) => {
3036
- e.preventDefault();
3037
- handleRowDrop(ri, e);
3038
- },
3039
- style: { height: dragOver === ri ? 24 : 3, background: dragOver === ri ? `${C.accent}25` : "transparent", border: dragOver === ri ? `2px dashed ${C.accent}` : "2px solid transparent", borderRadius: 4, transition: "all .12s", margin: "2px 0" }
3741
+ const dropH = dragOver === ri || dragOver === ri + 1 ? 28 : 10;
3742
+ const rowBodyRef = (0, import_react.useRef)(null);
3743
+ const rootContentRow = isRootContentRow(row);
3744
+ const beginRowDrag = (0, import_react.useCallback)(
3745
+ (e) => {
3746
+ e.stopPropagation();
3747
+ e.dataTransfer.effectAllowed = "move";
3748
+ e.dataTransfer.setData(
3749
+ "application/json",
3750
+ JSON.stringify({ moveRowId: row.id })
3751
+ );
3752
+ setRowDrag(row.id);
3753
+ },
3754
+ [row.id, setRowDrag]
3755
+ );
3756
+ const rowDragOver = (0, import_react.useCallback)(
3757
+ (e) => {
3758
+ if (!draggingRowId || draggingRowId === row.id) return;
3759
+ e.preventDefault();
3760
+ e.stopPropagation();
3761
+ const el = rowBodyRef.current;
3762
+ if (!el) {
3763
+ setDragOver(ri);
3764
+ return;
3040
3765
  }
3041
- ),
3042
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { id: rid(`row-body-${row.id}`), style: { position: "relative", width: "100%", minWidth: 0, paddingLeft: 0, boxSizing: "border-box" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { id: rid(`row-layout-wrap-${row.id}`), style: { width: "100%", minWidth: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3043
- LayoutRow,
3044
- {
3045
- editorId,
3046
- row,
3047
- selected: rowSelected,
3048
- setSelectedRowId,
3049
- onClick: () => {
3050
- setSelectedRowId(row.id);
3051
- setSelContentId(null);
3052
- setSelMeta(null);
3053
- },
3054
- selectedContentKey: selContentId,
3055
- selContentMeta: rowSelContentMeta,
3056
- onSelectContent: selectContent,
3057
- onDropContent: dropContent,
3058
- onDeleteContent: deleteContent,
3059
- onContextMenu: handleContextMenu,
3060
- onLayoutContextMenu: handleLayoutContextMenu,
3061
- rowDrag: {
3062
- draggable: true,
3063
- onDragStart: (e) => {
3064
- e.stopPropagation();
3065
- setDraggingRowId(row.id);
3066
- },
3067
- onDragEnd: () => setDraggingRowId(null)
3068
- },
3069
- C
3766
+ const rect = el.getBoundingClientRect();
3767
+ setDragOver(e.clientY < rect.top + rect.height / 2 ? ri : ri + 1);
3768
+ },
3769
+ [draggingRowId, row.id, ri, setDragOver]
3770
+ );
3771
+ const rowDrop = (0, import_react.useCallback)(
3772
+ (e) => {
3773
+ if (!draggingRowId) return;
3774
+ e.preventDefault();
3775
+ e.stopPropagation();
3776
+ const el = rowBodyRef.current;
3777
+ let targetIdx = ri;
3778
+ if (el) {
3779
+ const rect = el.getBoundingClientRect();
3780
+ targetIdx = e.clientY < rect.top + rect.height / 2 ? ri : ri + 1;
3070
3781
  }
3071
- ) }) })
3072
- ] });
3782
+ handleRowDrop(targetIdx, e);
3783
+ },
3784
+ [draggingRowId, ri, handleRowDrop]
3785
+ );
3786
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3787
+ "div",
3788
+ {
3789
+ id: rid(`canvas-row-${row.id}`),
3790
+ style: { display: "flex", alignItems: "stretch", gap: 0, margin: "2px 0" },
3791
+ children: [
3792
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3793
+ "div",
3794
+ {
3795
+ draggable: true,
3796
+ title: "Drag to reorder section",
3797
+ onDragStart: beginRowDrag,
3798
+ onDragEnd: () => setRowDrag(null),
3799
+ onPointerDown: (e) => e.stopPropagation(),
3800
+ onClick: (e) => {
3801
+ e.stopPropagation();
3802
+ setSelectedRowId(row.id);
3803
+ setSelContentId(null);
3804
+ setSelMeta(null);
3805
+ },
3806
+ onContextMenu: (e) => {
3807
+ e.preventDefault();
3808
+ e.stopPropagation();
3809
+ handleLayoutContextMenu(e, row.id);
3810
+ },
3811
+ style: {
3812
+ width: 20,
3813
+ flexShrink: 0,
3814
+ cursor: "grab",
3815
+ display: "flex",
3816
+ alignItems: "center",
3817
+ justifyContent: "center",
3818
+ color: rowSelected ? C.accent : C.muted,
3819
+ fontSize: 12,
3820
+ fontWeight: 700,
3821
+ letterSpacing: -3,
3822
+ userSelect: "none",
3823
+ opacity: rowSelected ? 1 : 0.5
3824
+ },
3825
+ children: "\u22EE\u22EE"
3826
+ }
3827
+ ),
3828
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
3829
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3830
+ "div",
3831
+ {
3832
+ id: rid(`row-drop-before-${ri}`),
3833
+ onDragOver: (e) => {
3834
+ e.preventDefault();
3835
+ e.stopPropagation();
3836
+ setDragOver(ri);
3837
+ },
3838
+ onDragLeave: () => setDragOver(null),
3839
+ onDrop: (e) => handleRowDrop(ri, e),
3840
+ style: {
3841
+ height: dropH,
3842
+ background: dragOver === ri ? `${C.accent}25` : "transparent",
3843
+ border: dragOver === ri ? `2px dashed ${C.accent}` : "2px solid transparent",
3844
+ borderRadius: 4,
3845
+ transition: "all .12s",
3846
+ boxSizing: "border-box"
3847
+ }
3848
+ }
3849
+ ),
3850
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3851
+ "div",
3852
+ {
3853
+ ref: rowBodyRef,
3854
+ id: rid(`row-body-${row.id}`),
3855
+ onDragOver: rowDragOver,
3856
+ onDrop: rowDrop,
3857
+ style: {
3858
+ position: "relative",
3859
+ width: "100%",
3860
+ minWidth: 0,
3861
+ boxSizing: "border-box",
3862
+ outline: draggingRowId && draggingRowId !== row.id && (dragOver === ri || dragOver === ri + 1) ? `2px dashed ${C.accent}` : void 0,
3863
+ outlineOffset: 2,
3864
+ borderRadius: 6
3865
+ },
3866
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { id: rid(`row-layout-wrap-${row.id}`), style: { width: "100%", minWidth: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3867
+ LayoutRow,
3868
+ {
3869
+ editorId,
3870
+ row,
3871
+ selected: rowSelected,
3872
+ setSelectedRowId,
3873
+ onClick: () => {
3874
+ setSelectedRowId(row.id);
3875
+ setSelContentId(null);
3876
+ setSelMeta(null);
3877
+ },
3878
+ selectedContentKey: selContentId,
3879
+ selContentMeta: rowSelContentMeta,
3880
+ onSelectContent: selectContent,
3881
+ onDropContent: dropContent,
3882
+ onDeleteContent: deleteContent,
3883
+ onContextMenu: handleContextMenu,
3884
+ onLayoutContextMenu: handleLayoutContextMenu,
3885
+ rootContentRow,
3886
+ beginRowDrag,
3887
+ C
3888
+ }
3889
+ ) })
3890
+ }
3891
+ )
3892
+ ] })
3893
+ ]
3894
+ }
3895
+ );
3073
3896
  };
3074
3897
 
3075
3898
  // src/ReactEmailEditor.tsx
@@ -3150,6 +3973,10 @@ function paddingToCss(pad3, fallback) {
3150
3973
  var FONT_SIZE_PX = [10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28, 32, 36, 42, 48, 56, 64, 72];
3151
3974
  var LINE_HEIGHT_OPTS = ["1", "1.15", "1.25", "1.5", "1.65", "1.75", "2", "2.5"];
3152
3975
  var LETTER_SPACING_OPTS = ["-0.5px", "0px", "0.5px", "1px", "1.5px", "2px", "3px", "4px"];
3976
+ function rgbChannelToHex(n) {
3977
+ const v = Math.max(0, Math.min(255, Math.round(n)));
3978
+ return v.toString(16).padStart(2, "0");
3979
+ }
3153
3980
  function hexForColorInput(color) {
3154
3981
  if (!color || typeof color !== "string") return "#000000";
3155
3982
  const c = color.trim();
@@ -3162,8 +3989,34 @@ function hexForColorInput(color) {
3162
3989
  }
3163
3990
  return c.slice(0, 7);
3164
3991
  }
3992
+ const rgb = c.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i);
3993
+ if (rgb) {
3994
+ return `#${rgbChannelToHex(+rgb[1])}${rgbChannelToHex(+rgb[2])}${rgbChannelToHex(+rgb[3])}`;
3995
+ }
3165
3996
  return "#000000";
3166
3997
  }
3998
+ function textStyleColorFromEditor(editor) {
3999
+ const ts = editor.getAttributes("textStyle");
4000
+ if (ts.color && String(ts.color).trim()) return String(ts.color).trim();
4001
+ const stored = editor.state.storedMarks;
4002
+ if (stored) {
4003
+ for (const m of stored) {
4004
+ if (m.type.name === "textStyle" && m.attrs.color) {
4005
+ return String(m.attrs.color).trim();
4006
+ }
4007
+ }
4008
+ }
4009
+ const { from, to } = editor.state.selection;
4010
+ if (from === to) return null;
4011
+ const domAt = editor.view.domAtPos(from);
4012
+ let el = domAt.node.nodeType === Node.TEXT_NODE ? domAt.node.parentElement : domAt.node;
4013
+ while (el && el !== editor.view.dom) {
4014
+ const inline = el.style?.color?.trim();
4015
+ if (inline) return inline;
4016
+ el = el.parentElement;
4017
+ }
4018
+ return null;
4019
+ }
3167
4020
  function parsePxSize(raw) {
3168
4021
  if (!raw) return "";
3169
4022
  const s = String(raw).trim();
@@ -3230,13 +4083,22 @@ function toolbarIconBtn(C, active) {
3230
4083
  }
3231
4084
  function ToolbarTypographyControls({ editor, C }) {
3232
4085
  const sel = toolbarSelect(C);
3233
- const ts = editor.getAttributes("textStyle");
4086
+ const { ts, align, textColor } = (0, import_react3.useEditorState)({
4087
+ editor,
4088
+ selector: ({ editor: ed }) => {
4089
+ const attrs = ed.getAttributes("textStyle");
4090
+ return {
4091
+ ts: attrs,
4092
+ align: currentBlockTextAlign(ed),
4093
+ textColor: textStyleColorFromEditor(ed)
4094
+ };
4095
+ }
4096
+ });
3234
4097
  const fontFamily = ts.fontFamily || "";
3235
4098
  const sizeVal = parsePxSize(ts.fontSize);
3236
4099
  const lhVal = normalizeLineHeightAttr(ts.lineHeight);
3237
4100
  const lsVal = normalizeLetterSpacingAttr(ts.letterSpacing);
3238
4101
  const lsSelectValue = LETTER_SPACING_OPTS.includes(lsVal) ? lsVal : lsVal || "";
3239
- const align = currentBlockTextAlign(editor);
3240
4102
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3241
4103
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3242
4104
  "select",
@@ -3319,9 +4181,10 @@ function ToolbarTypographyControls({ editor, C }) {
3319
4181
  {
3320
4182
  type: "color",
3321
4183
  title: "Text color",
3322
- value: hexForColorInput(ts.color),
4184
+ value: hexForColorInput(textColor ?? ts.color),
3323
4185
  onChange: (e) => {
3324
- void editor.chain().focus().setColor(e.target.value).run();
4186
+ const hex = e.target.value;
4187
+ void editor.chain().focus().setColor(hex).run();
3325
4188
  },
3326
4189
  style: {
3327
4190
  width: 28,
@@ -3694,7 +4557,7 @@ function TextRichEditor({
3694
4557
  }
3695
4558
  }),
3696
4559
  import_extension_text_style.TextStyle,
3697
- import_extension_text_style.Color,
4560
+ import_extension_text_style.Color.configure({ types: ["textStyle"] }),
3698
4561
  import_extension_text_style.FontFamily,
3699
4562
  import_extension_text_style.FontSize,
3700
4563
  import_extension_text_style.LineHeight,
@@ -4871,11 +5734,141 @@ function BlockSurfaceBgInspector({
4871
5734
  ] }) : null
4872
5735
  ] });
4873
5736
  }
5737
+ function HtmlBlockRichPanel({
5738
+ block,
5739
+ onChange,
5740
+ C
5741
+ }) {
5742
+ const p = block.props;
5743
+ const { IS } = useIS(C);
5744
+ const [mode, setMode] = (0, import_react4.useState)("visual");
5745
+ const [htmlDraft, setHtmlDraft] = (0, import_react4.useState)("");
5746
+ (0, import_react4.useEffect)(() => {
5747
+ setMode("visual");
5748
+ }, [block.id]);
5749
+ const body = normalizeRichHtmlForStorage(typeof block.content === "string" ? block.content : "");
5750
+ const applyHtml = (html) => {
5751
+ const norm = normalizeRichHtmlForStorage(html);
5752
+ onChange({ ...block, content: norm, props: { ...p, content: norm } });
5753
+ };
5754
+ const tabBtn = (active) => ({
5755
+ flex: 1,
5756
+ padding: "7px 10px",
5757
+ borderRadius: 6,
5758
+ border: `1px solid ${C.border}`,
5759
+ background: active ? C.accent : C.surface,
5760
+ color: active ? "#ffffff" : C.text,
5761
+ fontSize: 12,
5762
+ fontWeight: 700,
5763
+ cursor: "pointer"
5764
+ });
5765
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "Content", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
5766
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
5767
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5768
+ "button",
5769
+ {
5770
+ type: "button",
5771
+ style: tabBtn(mode === "visual"),
5772
+ onClick: () => {
5773
+ if (mode === "html") applyHtml(htmlDraft);
5774
+ setMode("visual");
5775
+ },
5776
+ children: "Rich text"
5777
+ }
5778
+ ),
5779
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5780
+ "button",
5781
+ {
5782
+ type: "button",
5783
+ style: tabBtn(mode === "html"),
5784
+ onClick: () => {
5785
+ setHtmlDraft(body);
5786
+ setMode("html");
5787
+ },
5788
+ children: "HTML"
5789
+ }
5790
+ )
5791
+ ] }),
5792
+ mode === "visual" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5793
+ TextRichEditor,
5794
+ {
5795
+ value: body,
5796
+ onChange: applyHtml,
5797
+ typography: {
5798
+ fontSize: p.fontSize,
5799
+ color: p.color,
5800
+ align: p.align,
5801
+ fontFamily: p.fontFamily,
5802
+ lineHeight: p.lineHeight,
5803
+ letterSpacing: p.letterSpacing,
5804
+ padding: p.padding
5805
+ },
5806
+ placeholder: "Write your rich content\u2026",
5807
+ headerTitle: "Rich editor",
5808
+ C
5809
+ },
5810
+ block.id
5811
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
5812
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5813
+ "textarea",
5814
+ {
5815
+ spellCheck: false,
5816
+ value: htmlDraft,
5817
+ onChange: (e) => setHtmlDraft(e.target.value),
5818
+ placeholder: "<p>Your HTML\u2026</p>",
5819
+ style: {
5820
+ ...IS,
5821
+ minHeight: 220,
5822
+ resize: "vertical",
5823
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
5824
+ fontSize: 12,
5825
+ lineHeight: 1.45
5826
+ }
5827
+ }
5828
+ ),
5829
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: 10, color: C.muted, lineHeight: 1.45 }, children: [
5830
+ "Raw HTML. Switch to ",
5831
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Rich text" }),
5832
+ " to apply and preview in the visual editor."
5833
+ ] }),
5834
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5835
+ "button",
5836
+ {
5837
+ type: "button",
5838
+ onClick: () => applyHtml(htmlDraft),
5839
+ style: {
5840
+ alignSelf: "flex-start",
5841
+ background: C.surface,
5842
+ border: `1px solid ${C.border}`,
5843
+ color: C.accent,
5844
+ borderRadius: 5,
5845
+ cursor: "pointer",
5846
+ fontSize: 11,
5847
+ padding: "6px 12px",
5848
+ fontWeight: 600
5849
+ },
5850
+ children: "Apply (normalize)"
5851
+ }
5852
+ )
5853
+ ] })
5854
+ ] }) });
5855
+ }
4874
5856
  function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4875
5857
  const { IS, CI } = useIS(C);
4876
5858
  const imageFileRef = (0, import_react4.useRef)(null);
4877
5859
  const p = block.props;
4878
- const set = (k, v) => onChange({ ...block, props: { ...p, [k]: v } });
5860
+ const set = (k, v) => {
5861
+ if (block.type === "html" && k === "content") {
5862
+ onChange({ ...block, content: v, props: { ...p, content: v } });
5863
+ return;
5864
+ }
5865
+ if (block.type === "html") {
5866
+ const body = typeof block.content === "string" ? block.content : typeof p.content === "string" ? p.content : "";
5867
+ onChange({ ...block, props: { ...p, [k]: v, content: body } });
5868
+ return;
5869
+ }
5870
+ onChange({ ...block, props: { ...p, [k]: v } });
5871
+ };
4879
5872
  const Num = (k, lbl, mn = 0, mx = 300) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: lbl, C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "number", style: IS, value: p[k] ?? 0, min: mn, max: mx, onChange: (e) => set(k, +e.target.value) }) });
4880
5873
  const Txt = (k, lbl, multi = false) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: lbl, C, children: multi ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("textarea", { style: { ...IS, minHeight: 68, resize: "vertical" }, value: p[k] || "", onChange: (e) => set(k, e.target.value) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "text", style: IS, value: p[k] || "", onChange: (e) => set(k, e.target.value) }) });
4881
5874
  const Col = (k, lbl) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: lbl, C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
@@ -4947,10 +5940,10 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4947
5940
  "textarea",
4948
5941
  {
4949
5942
  style: { ...IS, minHeight: 200, resize: "vertical", fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", fontSize: 12, lineHeight: 1.45 },
4950
- value: normalizeRichHtmlForStorage(p.content),
4951
- onChange: (e) => set("content", normalizeRichHtmlForStorage(e.target.value)),
5943
+ value: String(p.content ?? ""),
5944
+ onChange: (e) => set("content", e.target.value),
4952
5945
  spellCheck: false,
4953
- placeholder: "e.g. <p>Your message</p>"
5946
+ placeholder: "Write your text\u2026"
4954
5947
  },
4955
5948
  block.id
4956
5949
  ) }),
@@ -4969,25 +5962,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4969
5962
  ] });
4970
5963
  case "html":
4971
5964
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
4972
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react4.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4973
- TextRichEditor,
4974
- {
4975
- value: normalizeRichHtmlForStorage(p.content),
4976
- onChange: (html) => set("content", html),
4977
- typography: {
4978
- fontSize: p.fontSize,
4979
- color: p.color,
4980
- align: p.align,
4981
- fontFamily: p.fontFamily,
4982
- lineHeight: p.lineHeight,
4983
- letterSpacing: p.letterSpacing,
4984
- padding: p.padding
4985
- },
4986
- placeholder: "Write your rich content\u2026",
4987
- headerTitle: "Rich editor",
4988
- C
4989
- }
4990
- ) }, block.id),
5965
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HtmlBlockRichPanel, { block, onChange, C }),
4991
5966
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
4992
5967
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4993
5968
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
@@ -5007,8 +5982,25 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5007
5982
  "Object position"
5008
5983
  ] }), C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { style: IS, value: p.objectPosition || "center", onChange: (e) => set("objectPosition", e.target.value), children: ["center", "top", "bottom", "left", "right", "top left", "top right", "bottom left", "bottom right"].map((v) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: v, children: v }, v)) }) }),
5009
5984
  Txt("alt", "Alt Text"),
5010
- Txt("link", "Link URL"),
5011
- Sel("linkTarget", "Open In", ["_blank", "_self"]),
5985
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "Link image", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { style: { display: "flex", alignItems: "center", gap: 7, cursor: "pointer" }, children: [
5986
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5987
+ "input",
5988
+ {
5989
+ type: "checkbox",
5990
+ checked: !!(p.linkEnabled ?? (p.link && String(p.link).trim())),
5991
+ onChange: (e) => {
5992
+ if (e.target.checked) set("linkEnabled", true);
5993
+ else onChange({ ...block, props: { ...p, linkEnabled: false, link: "" } });
5994
+ },
5995
+ style: { width: 15, height: 15, accentColor: C.accent }
5996
+ }
5997
+ ),
5998
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: C.muted, fontSize: 12 }, children: p.linkEnabled ?? (p.link && String(p.link).trim()) ? "On" : "Off" })
5999
+ ] }) }),
6000
+ p.linkEnabled ?? (p.link && String(p.link).trim()) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
6001
+ Txt("link", "Link URL"),
6002
+ Sel("linkTarget", "Open In", ["_blank", "_self"])
6003
+ ] }) : null,
5012
6004
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right"], C }),
5013
6005
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BorderRadiusEditor, { value: p.borderRadius, onChange: (br) => set("borderRadius", br), C }),
5014
6006
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
@@ -5358,7 +6350,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5358
6350
  bgPosition: p.bgPosition ?? "center",
5359
6351
  bgGradient: p.bgGradient ?? null,
5360
6352
  cells: p.cells || [],
5361
- columnStyles: p.columnStyles
6353
+ columns: getColumns(p)
5362
6354
  },
5363
6355
  onChange: (row) => onChange({
5364
6356
  ...block,
@@ -5376,7 +6368,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5376
6368
  bgGradient: row.bgGradient,
5377
6369
  ratios: [...row.ratios || [1]],
5378
6370
  cells: row.cells || [],
5379
- columnStyles: row.columnStyles
6371
+ columns: row.columns ?? getColumns(row)
5380
6372
  }
5381
6373
  }),
5382
6374
  onClose,
@@ -5395,8 +6387,8 @@ function inferLayoutRowBgStep(r) {
5395
6387
  if (String(r.bgColor || "").trim()) return "solid";
5396
6388
  return null;
5397
6389
  }
5398
- function inferColumnBgStep(columnStyles, selCol) {
5399
- const cs = (columnStyles || {})[selCol] || {};
6390
+ function inferColumnBgStep(container, selCol) {
6391
+ const cs = getColumnPropsAt(container, selCol);
5400
6392
  if (cs.bgGradient) return "gradient";
5401
6393
  if (String(cs.bgImage || "").trim()) return "image";
5402
6394
  if (String(cs.bgColor || "").trim()) return "solid";
@@ -5408,6 +6400,10 @@ function LayoutRowEditor({
5408
6400
  onClose,
5409
6401
  onUpload,
5410
6402
  initialCol = null,
6403
+ rowIndex = -1,
6404
+ rowCount = 1,
6405
+ onMoveUp,
6406
+ onMoveDown,
5411
6407
  C,
5412
6408
  /** Shown under the inspector header when editing a Layout block */
5413
6409
  customizationHint
@@ -5420,8 +6416,8 @@ function LayoutRowEditor({
5420
6416
  const [colBgPickerMode, setColBgPickerMode] = (0, import_react4.useState)(null);
5421
6417
  const inferredRowBg = (0, import_react4.useMemo)(() => inferLayoutRowBgStep(row), [row.bgGradient, row.bgImage, row.bgColor]);
5422
6418
  const inferredColBg = (0, import_react4.useMemo)(
5423
- () => inferColumnBgStep(row.columnStyles, selCol),
5424
- [row.columnStyles, selCol]
6419
+ () => inferColumnBgStep(row, selCol),
6420
+ [row.columns, row.columnStyles, selCol]
5425
6421
  );
5426
6422
  const rowBgUiStep = inferredRowBg ?? rowBgPickerMode;
5427
6423
  const colBgUiStep = inferredColBg ?? colBgPickerMode;
@@ -5460,13 +6456,12 @@ function LayoutRowEditor({
5460
6456
  onChange({ ...row, cols: n, preset: "custom", cells, ratios });
5461
6457
  };
5462
6458
  const updCol = (i, upd) => {
5463
- const styles = { ...row.columnStyles || {} };
5464
- styles[i] = { ...styles[i] || {}, ...upd };
5465
- set("columnStyles", styles);
6459
+ const columns = patchColumnPropsAt(row, i, upd);
6460
+ onChange(withColumnsOnly(row, columns));
5466
6461
  };
5467
6462
  const setColBgMode = (mode) => {
5468
6463
  setColBgPickerMode(mode);
5469
- const cs = (row.columnStyles || {})[selCol] || {};
6464
+ const cs = getColumnPropsAt(row, selCol);
5470
6465
  if (mode === "solid") {
5471
6466
  updCol(selCol, { bgGradient: null, bgImage: "" });
5472
6467
  return;
@@ -5498,7 +6493,51 @@ function LayoutRowEditor({
5498
6493
  set("bgGradient", { angle: 135, colors: ["#0ea5e9", "#6366f1", "#ec4899"], stops: [0, 50, 100] });
5499
6494
  }
5500
6495
  };
6496
+ const canMoveUp = rowIndex > 0;
6497
+ const canMoveDown = rowIndex >= 0 && rowIndex < rowCount - 1;
5501
6498
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
6499
+ onMoveUp && onMoveDown ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 6, marginBottom: 12 }, children: [
6500
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
6501
+ "button",
6502
+ {
6503
+ type: "button",
6504
+ disabled: !canMoveUp,
6505
+ onClick: onMoveUp,
6506
+ style: {
6507
+ flex: 1,
6508
+ padding: "8px 10px",
6509
+ fontSize: 12,
6510
+ fontWeight: 600,
6511
+ borderRadius: 8,
6512
+ border: `1px solid ${C.border}`,
6513
+ background: canMoveUp ? C.surface : C.canvas,
6514
+ color: canMoveUp ? C.text : C.muted,
6515
+ cursor: canMoveUp ? "pointer" : "not-allowed"
6516
+ },
6517
+ children: "\u2191 Move section up"
6518
+ }
6519
+ ),
6520
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
6521
+ "button",
6522
+ {
6523
+ type: "button",
6524
+ disabled: !canMoveDown,
6525
+ onClick: onMoveDown,
6526
+ style: {
6527
+ flex: 1,
6528
+ padding: "8px 10px",
6529
+ fontSize: 12,
6530
+ fontWeight: 600,
6531
+ borderRadius: 8,
6532
+ border: `1px solid ${C.border}`,
6533
+ background: canMoveDown ? C.surface : C.canvas,
6534
+ color: canMoveDown ? C.text : C.muted,
6535
+ cursor: canMoveDown ? "pointer" : "not-allowed"
6536
+ },
6537
+ children: "\u2193 Move section down"
6538
+ }
6539
+ )
6540
+ ] }) : null,
5502
6541
  customizationHint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5503
6542
  "div",
5504
6543
  {
@@ -5648,15 +6687,15 @@ function LayoutRowEditor({
5648
6687
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5649
6688
  ColorSwatchGrid,
5650
6689
  {
5651
- value: (row.columnStyles || {})[selCol]?.bgColor || "",
6690
+ value: getColumnPropsAt(row, selCol)?.bgColor || "",
5652
6691
  C,
5653
6692
  compact: true,
5654
6693
  onPick: (hex) => updCol(selCol, { bgColor: hex === "#ffffff" ? "" : hex })
5655
6694
  }
5656
6695
  ),
5657
6696
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 5, alignItems: "center" }, children: [
5658
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "color", style: CI, value: (row.columnStyles || {})[selCol]?.bgColor || "#ffffff", onChange: (e) => updCol(selCol, { bgColor: e.target.value === "#ffffff" ? "" : e.target.value }) }),
5659
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "text", style: { ...IS, width: 120 }, placeholder: "transparent", value: (row.columnStyles || {})[selCol]?.bgColor || "", onChange: (e) => updCol(selCol, { bgColor: e.target.value }) })
6697
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "color", style: CI, value: getColumnPropsAt(row, selCol)?.bgColor || "#ffffff", onChange: (e) => updCol(selCol, { bgColor: e.target.value === "#ffffff" ? "" : e.target.value }) }),
6698
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "text", style: { ...IS, width: 120 }, placeholder: "transparent", value: getColumnPropsAt(row, selCol)?.bgColor || "", onChange: (e) => updCol(selCol, { bgColor: e.target.value }) })
5660
6699
  ] })
5661
6700
  ] }) }) : null,
5662
6701
  colBgUiStep === "image" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
@@ -5664,7 +6703,7 @@ function LayoutRowEditor({
5664
6703
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Image, { size: 13, strokeWidth: 2.25, "aria-hidden": true }),
5665
6704
  "bg image"
5666
6705
  ] }), C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
5667
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "text", style: IS, placeholder: "https://\u2026", value: (row.columnStyles || {})[selCol]?.bgImage || "", onChange: (e) => updCol(selCol, { bgImage: e.target.value }) }),
6706
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "text", style: IS, placeholder: "https://\u2026", value: getColumnPropsAt(row, selCol)?.bgImage || "", onChange: (e) => updCol(selCol, { bgImage: e.target.value }) }),
5668
6707
  onUpload && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { marginTop: 0 }, children: [
5669
6708
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5670
6709
  "input",
@@ -5702,18 +6741,18 @@ function LayoutRowEditor({
5702
6741
  ] })
5703
6742
  ] }) }),
5704
6743
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 12 }, children: [
5705
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "fit", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("select", { style: IS, value: (row.columnStyles || {})[selCol]?.bgSize || "cover", onChange: (e) => updCol(selCol, { bgSize: e.target.value }), children: [
6744
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "fit", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("select", { style: IS, value: getColumnPropsAt(row, selCol)?.bgSize || "cover", onChange: (e) => updCol(selCol, { bgSize: e.target.value }), children: [
5706
6745
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "auto", children: "Auto" }),
5707
6746
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "cover", children: "Cover" }),
5708
6747
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "contain", children: "Contain" })
5709
6748
  ] }) }) }),
5710
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "repeat", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("select", { style: IS, value: (row.columnStyles || {})[selCol]?.bgRepeat || "no-repeat", onChange: (e) => updCol(selCol, { bgRepeat: e.target.value }), children: [
6749
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "repeat", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("select", { style: IS, value: getColumnPropsAt(row, selCol)?.bgRepeat || "no-repeat", onChange: (e) => updCol(selCol, { bgRepeat: e.target.value }), children: [
5711
6750
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "no-repeat", children: "No repeat" }),
5712
6751
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "repeat", children: "Repeat" }),
5713
6752
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "repeat-x", children: "Repeat X" }),
5714
6753
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "repeat-y", children: "Repeat Y" })
5715
6754
  ] }) }) }),
5716
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "align", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { style: IS, value: (row.columnStyles || {})[selCol]?.bgPosition || "center", onChange: (e) => updCol(selCol, { bgPosition: e.target.value }), children: POS_OPTS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: p, children: p }, p)) }) }) })
6755
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "align", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { style: IS, value: getColumnPropsAt(row, selCol)?.bgPosition || "center", onChange: (e) => updCol(selCol, { bgPosition: e.target.value }), children: POS_OPTS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: p, children: p }, p)) }) }) })
5717
6756
  ] })
5718
6757
  ] }) : null,
5719
6758
  colBgUiStep === "gradient" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("details", { open: true, style: { marginBottom: 12 }, children: [
@@ -5722,15 +6761,15 @@ function LayoutRowEditor({
5722
6761
  DynamicGradientField,
5723
6762
  {
5724
6763
  label: "gradient",
5725
- value: (row.columnStyles || {})[selCol]?.bgGradient,
6764
+ value: getColumnPropsAt(row, selCol)?.bgGradient,
5726
6765
  onChange: (g) => updCol(selCol, { bgGradient: g }),
5727
6766
  defaults: { colors: ["#0ea5e9", "#6366f1", "#ec4899"] },
5728
6767
  C
5729
6768
  }
5730
6769
  ) })
5731
6770
  ] }) : null,
5732
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaddingEditor, { label: "Column padding", value: (row.columnStyles || {})[selCol]?.padding, onChange: (pad3) => updCol(selCol, { padding: pad3 }), C }),
5733
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BorderRadiusEditor, { label: "Column radius", value: (row.columnStyles || {})[selCol]?.borderRadius, onChange: (br) => updCol(selCol, { borderRadius: br }), C })
6771
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaddingEditor, { label: "Column padding", value: getColumnPropsAt(row, selCol).padding, onChange: (pad3) => updCol(selCol, { padding: pad3 }), C }),
6772
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BorderRadiusEditor, { label: "Column radius", value: getColumnPropsAt(row, selCol).borderRadius, onChange: (br) => updCol(selCol, { borderRadius: br }), C })
5734
6773
  ] })
5735
6774
  ] })
5736
6775
  ] });
@@ -6521,6 +7560,11 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
6521
7560
  const [selContentMeta, setSelMeta] = (0, import_react8.useState)(null);
6522
7561
  const [dragOver, setDragOver] = (0, import_react8.useState)(null);
6523
7562
  const [draggingRowId, setDraggingRowId] = (0, import_react8.useState)(null);
7563
+ const draggingRowIdRef = (0, import_react8.useRef)(null);
7564
+ const setRowDrag = (0, import_react8.useCallback)((id) => {
7565
+ draggingRowIdRef.current = id;
7566
+ setDraggingRowId(id);
7567
+ }, []);
6524
7568
  const [history, setHistory] = (0, import_react8.useState)([]);
6525
7569
  const [future, setFuture] = (0, import_react8.useState)([]);
6526
7570
  const [showPreviewModal, setShowPreview] = (0, import_react8.useState)(false);
@@ -6555,10 +7599,17 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
6555
7599
  });
6556
7600
  const [pageBgUiStep, setPageBgUiStep] = (0, import_react8.useState)(null);
6557
7601
  const [contentBgUiStep, setContentBgUiStep] = (0, import_react8.useState)(null);
7602
+ const pageBgLastSolidRef = (0, import_react8.useRef)("#f1f5f9");
7603
+ const pageBgIsOff = !String(settings.bgColor ?? "").trim() && !String(settings.bgImage ?? "").trim() && !settings.bgGradient;
6558
7604
  const applyPageBgMode = (mode) => {
6559
7605
  setPageBgUiStep(mode);
6560
7606
  if (mode === "solid") {
6561
- setSettings((s) => ({ ...s, bgGradient: null, bgImage: "" }));
7607
+ setSettings((s) => {
7608
+ const next = { ...s, bgGradient: null, bgImage: "" };
7609
+ const raw = typeof next.bgColor === "string" ? next.bgColor.trim() : "";
7610
+ if (!raw) next.bgColor = pageBgLastSolidRef.current || "#f1f5f9";
7611
+ return next;
7612
+ });
6562
7613
  return;
6563
7614
  }
6564
7615
  if (mode === "image") {
@@ -6668,7 +7719,7 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
6668
7719
  return () => window.removeEventListener("keydown", handler);
6669
7720
  }, [rows, history, future, selContentMeta]);
6670
7721
  const buildApi = (0, import_react8.useCallback)(() => ({
6671
- loadJson(input) {
7722
+ loadJson(input, options2) {
6672
7723
  setJsonLoading(true);
6673
7724
  requestAnimationFrame(() => {
6674
7725
  requestAnimationFrame(() => {
@@ -6681,7 +7732,14 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
6681
7732
  return;
6682
7733
  }
6683
7734
  }
6684
- const norm = normalizeEmailDesignInput(parsed);
7735
+ const coerced = coerceEmailDocumentInput(parsed);
7736
+ if (options2?.mode === "append") {
7737
+ const extra = emailDocumentToEditorRows(coerced ?? parsed);
7738
+ if (!extra?.length) return;
7739
+ setRows((prev) => [...prev, ...extra]);
7740
+ return;
7741
+ }
7742
+ const norm = normalizeEmailDesignInput(coerced ?? parsed);
6685
7743
  if (!norm) return;
6686
7744
  setRows(norm.rows);
6687
7745
  const ns = norm.settings;
@@ -6718,6 +7776,8 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
6718
7776
  }, []);
6719
7777
  const handleRowDrop = (targetIdx, e) => {
6720
7778
  if (e) {
7779
+ e.preventDefault();
7780
+ e.stopPropagation();
6721
7781
  try {
6722
7782
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
6723
7783
  if (data.layoutPresetKey) {
@@ -6736,18 +7796,28 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
6736
7796
  } catch {
6737
7797
  }
6738
7798
  }
6739
- if (draggingRowId) {
7799
+ const moveId = draggingRowIdRef.current ?? draggingRowId ?? (() => {
7800
+ if (!e) return null;
7801
+ try {
7802
+ const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
7803
+ return typeof data.moveRowId === "string" ? data.moveRowId : null;
7804
+ } catch {
7805
+ return null;
7806
+ }
7807
+ })();
7808
+ if (moveId) {
6740
7809
  mutate((prev) => {
6741
- const fi = prev.findIndex((r) => r.id === draggingRowId);
7810
+ const fi = prev.findIndex((r) => r.id === moveId);
6742
7811
  if (fi === -1) return prev;
6743
7812
  const next = [...prev];
6744
7813
  const [m] = next.splice(fi, 1);
6745
- next.splice(targetIdx > fi ? targetIdx - 1 : targetIdx, 0, m);
7814
+ const insertAt = targetIdx > fi ? targetIdx - 1 : targetIdx;
7815
+ next.splice(Math.max(0, Math.min(insertAt, next.length)), 0, m);
6746
7816
  return next;
6747
7817
  });
6748
7818
  }
6749
7819
  setDragOver(null);
6750
- setDraggingRowId(null);
7820
+ setRowDrag(null);
6751
7821
  };
6752
7822
  const addRow = (preset) => mutate((prev) => [...prev, makeLayoutRow(preset)]);
6753
7823
  const deleteRow = (id) => {
@@ -6877,8 +7947,27 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
6877
7947
  }
6878
7948
  }
6879
7949
  };
7950
+ const rootContentPreset = LAYOUT_PRESETS.find((p) => p.cols === 1) ?? LAYOUT_PRESETS[0];
7951
+ const insertRootContentBlock = (contentType) => {
7952
+ const { row: newRow, block: nb } = makeRootContentRow(contentType, rootContentPreset);
7953
+ const anchorRowId = selContentMeta?.rowId ?? selectedRowId ?? null;
7954
+ mutate((prev) => {
7955
+ if (anchorRowId) {
7956
+ const i = prev.findIndex((r) => r.id === anchorRowId);
7957
+ if (i >= 0) {
7958
+ const next = [...prev];
7959
+ next.splice(i + 1, 0, newRow);
7960
+ return next;
7961
+ }
7962
+ }
7963
+ return [...prev, newRow];
7964
+ });
7965
+ setSelectedRowId(null);
7966
+ setSelContentId(nb.id);
7967
+ setSelMeta({ rowId: newRow.id, cellIdx: 0, contentIdx: 0 });
7968
+ };
6880
7969
  const insertBlockFromLibrary = (contentType) => {
6881
- if (!rows.length) return;
7970
+ const isLayoutType = contentType === "layout" || contentType === "nestedRow";
6882
7971
  if (selContentMeta?.inner) {
6883
7972
  const { rowId, cellIdx, contentIdx, inner } = selContentMeta;
6884
7973
  dropContent(rowId, cellIdx, {
@@ -6889,30 +7978,26 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
6889
7978
  });
6890
7979
  return;
6891
7980
  }
6892
- if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0) {
6893
- const r = rows.find((x) => x.id === selContentMeta.rowId);
6894
- if (r && selContentMeta.cellIdx < r.cells.length) {
6895
- dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
6896
- kind: "new",
6897
- contentType,
6898
- insertAt: null
6899
- });
6900
- return;
7981
+ if (isLayoutType) {
7982
+ if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0 && typeof selContentMeta.contentIdx === "number" && selContentMeta.contentIdx >= 0) {
7983
+ const r = rows.find((x) => x.id === selContentMeta.rowId);
7984
+ if (r && selContentMeta.cellIdx < r.cells.length) {
7985
+ dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
7986
+ kind: "new",
7987
+ contentType,
7988
+ insertAt: null
7989
+ });
7990
+ return;
7991
+ }
6901
7992
  }
7993
+ addRow(rootContentPreset);
7994
+ return;
6902
7995
  }
6903
- if (selectedRowId) {
6904
- const targetRow = rows.find((r) => r.id === selectedRowId);
6905
- if (targetRow) {
6906
- dropContent(targetRow.id, 0, {
6907
- kind: "new",
6908
- contentType,
6909
- insertAt: null
6910
- });
6911
- return;
6912
- }
7996
+ if (!rows.length) {
7997
+ insertRootContentBlock(contentType);
7998
+ return;
6913
7999
  }
6914
- const main = rows[0];
6915
- dropContent(main.id, 0, { kind: "new", contentType, insertAt: null });
8000
+ insertRootContentBlock(contentType);
6916
8001
  };
6917
8002
  const deleteContent = (rowId, cellIdx, ci, inner = null) => {
6918
8003
  if (!inner) {
@@ -7507,6 +8592,7 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
7507
8592
  e.preventDefault();
7508
8593
  try {
7509
8594
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
8595
+ if (data.moveRowId) return;
7510
8596
  if (data.layoutPresetKey) {
7511
8597
  const preset = LAYOUT_PRESETS.find((p) => p.key === data.layoutPresetKey);
7512
8598
  if (preset) mutate((prev) => [...prev, makeLayoutRow(preset)]);
@@ -7561,6 +8647,7 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
7561
8647
  e.stopPropagation();
7562
8648
  try {
7563
8649
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
8650
+ if (data.moveRowId) return;
7564
8651
  if (data.contentType && typeof data.contentType === "string") {
7565
8652
  insertBlockFromLibrary(data.contentType);
7566
8653
  }
@@ -7582,10 +8669,11 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
7582
8669
  selectedRowId,
7583
8670
  selContentMeta,
7584
8671
  dragOver,
8672
+ draggingRowId,
7585
8673
  C,
7586
8674
  setDragOver,
7587
8675
  handleRowDrop,
7588
- setDraggingRowId,
8676
+ setRowDrag,
7589
8677
  setSelectedRowId,
7590
8678
  setSelContentId,
7591
8679
  setSelMeta,
@@ -7603,14 +8691,20 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
7603
8691
  id: eid("canvas-row-drop-end"),
7604
8692
  onDragOver: (e) => {
7605
8693
  e.preventDefault();
8694
+ e.stopPropagation();
7606
8695
  setDragOver(rows.length);
7607
8696
  },
7608
8697
  onDragLeave: () => setDragOver(null),
7609
- onDrop: (e) => {
7610
- e.preventDefault();
7611
- handleRowDrop(rows.length, e);
7612
- },
7613
- style: { height: dragOver === rows.length ? 24 : 3, background: dragOver === rows.length ? `${C.accent}25` : "transparent", border: dragOver === rows.length ? `2px dashed ${C.accent}` : "2px solid transparent", borderRadius: 4, transition: "all .12s", margin: "2px 0" }
8698
+ onDrop: (e) => handleRowDrop(rows.length, e),
8699
+ style: {
8700
+ height: dragOver === rows.length ? 28 : 10,
8701
+ background: dragOver === rows.length ? `${C.accent}25` : "transparent",
8702
+ border: dragOver === rows.length ? `2px dashed ${C.accent}` : "2px solid transparent",
8703
+ borderRadius: 4,
8704
+ transition: "all .12s",
8705
+ margin: "2px 0",
8706
+ boxSizing: "border-box"
8707
+ }
7614
8708
  }
7615
8709
  )
7616
8710
  ]
@@ -7722,6 +8816,10 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
7722
8816
  onClose: () => setSelectedRowId(null),
7723
8817
  onUpload,
7724
8818
  initialCol: selContentMeta?.rowId === selectedRowId ? selContentMeta.cellIdx : null,
8819
+ rowIndex: rows.findIndex((r) => r.id === selectedRowId),
8820
+ rowCount: rows.length,
8821
+ onMoveUp: () => selectedRow && moveRow(selectedRow.id, -1),
8822
+ onMoveDown: () => selectedRow && moveRow(selectedRow.id, 1),
7725
8823
  C
7726
8824
  }
7727
8825
  ) })
@@ -7898,7 +8996,40 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
7898
8996
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("summary", { style: railSectionSummary, children: "Background" }),
7899
8997
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: railSectionBody, children: [
7900
8998
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 10, color: C.muted, fontWeight: 800, textTransform: "uppercase", letterSpacing: ".06em", marginBottom: 6 }, children: "Page background" }),
7901
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BgModeButtons, { value: pageBgUiStep, onChange: applyPageBgMode, C }),
8999
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", userSelect: "none" }, children: [
9000
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
9001
+ "input",
9002
+ {
9003
+ type: "checkbox",
9004
+ checked: pageBgIsOff,
9005
+ onChange: (e) => {
9006
+ const off = e.target.checked;
9007
+ if (off) {
9008
+ const cur = String(settings.bgColor ?? "").trim();
9009
+ if (cur) pageBgLastSolidRef.current = cur;
9010
+ setPageBgUiStep(null);
9011
+ setSettings((s) => ({ ...s, bgColor: "", bgImage: "", bgGradient: null }));
9012
+ } else {
9013
+ setPageBgUiStep("solid");
9014
+ setSettings((s) => ({
9015
+ ...s,
9016
+ bgColor: pageBgLastSolidRef.current || "#f1f5f9"
9017
+ }));
9018
+ }
9019
+ },
9020
+ style: { width: 15, height: 15, accentColor: C.accent, cursor: "pointer" }
9021
+ }
9022
+ ),
9023
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { color: C.muted, fontSize: 12 }, children: "No page background" })
9024
+ ] }) }),
9025
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
9026
+ BgModeButtons,
9027
+ {
9028
+ value: pageBgIsOff ? null : pageBgUiStep,
9029
+ onChange: applyPageBgMode,
9030
+ C
9031
+ }
9032
+ ),
7902
9033
  pageBgUiStep === null ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 11, color: C.muted, marginBottom: 12, lineHeight: 1.45 }, children: "Choose Color, Gradient, or Image \u2014 then the matching settings appear below." }) : null,
7903
9034
  pageBgUiStep === "solid" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7904
9035
  ColorField,
@@ -7908,7 +9039,10 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
7908
9039
  "bg color"
7909
9040
  ] }),
7910
9041
  value: settings.bgColor,
7911
- onChange: (v) => setSettings((s) => ({ ...s, bgColor: v })),
9042
+ onChange: (v) => {
9043
+ pageBgLastSolidRef.current = v;
9044
+ setSettings((s) => ({ ...s, bgColor: v }));
9045
+ },
7912
9046
  placeholder: "#f1f5f9",
7913
9047
  C
7914
9048
  }
@@ -8149,81 +9283,18 @@ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
8149
9283
  );
8150
9284
  }
8151
9285
  );
8152
- var ReactEmailEditor2 = Object.assign(ReactEmailEditorComponent, { jsonToHtml });
8153
-
8154
- // src/lib/htmlToEmailDesign.ts
8155
- var pad = (n) => ({ top: n, right: n, bottom: n, left: n });
8156
- function extractHtmlForDesign(html) {
8157
- const t = String(html ?? "").trim();
8158
- if (!t) return "";
8159
- const head = t.slice(0, 800).toLowerCase();
8160
- if (!head.includes("<html") && !head.includes("<!doctype")) {
8161
- return normalizeRichHtmlForStorage(t);
8162
- }
8163
- try {
8164
- const doc = new DOMParser().parseFromString(t, "text/html");
8165
- const body = doc.body;
8166
- if (!body) return normalizeRichHtmlForStorage(t);
8167
- return normalizeRichHtmlForStorage(body.innerHTML);
8168
- } catch {
8169
- return normalizeRichHtmlForStorage(t);
8170
- }
8171
- }
8172
- function htmlToEmailDesignTemplate(html) {
8173
- const inner = extractHtmlForDesign(html);
8174
- if (!inner) return null;
8175
- const doc = {
8176
- type: "email_document",
8177
- settings: {
8178
- width: 600,
8179
- backgroundColor: "#f1f5f9",
8180
- contentBackgroundColor: "#ffffff",
8181
- fontFamily: "Arial, Helvetica, sans-serif",
8182
- lineHeightBase: 1.6,
8183
- color: "#111827",
8184
- responsive: true,
8185
- rtl: false
8186
- },
8187
- rows: [
8188
- {
8189
- id: "row_html_import",
8190
- type: "row",
8191
- layout: { columns: 1, gap: 0, stackOnMobile: true, align: "center" },
8192
- styles: {
8193
- backgroundColor: "#ffffff",
8194
- backgroundRepeat: "no-repeat",
8195
- backgroundSize: "cover",
8196
- padding: pad(24),
8197
- textAlign: "left"
8198
- },
8199
- columns: [
8200
- {
8201
- id: "col_html_import",
8202
- layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
8203
- styles: { padding: pad(0), backgroundColor: "" },
8204
- blocks: [
8205
- {
8206
- id: "block_html_import",
8207
- type: "html",
8208
- content: { html: inner },
8209
- styles: {}
8210
- }
8211
- ]
8212
- }
8213
- ]
8214
- }
8215
- ]
8216
- };
8217
- return doc;
8218
- }
9286
+ var ReactEmailEditor2 = Object.assign(ReactEmailEditorComponent, { jsonToHtml, htmlToJson });
8219
9287
  // Annotate the CommonJS export names for ESM import in node:
8220
9288
  0 && (module.exports = {
8221
9289
  EmailPreviewModal,
8222
9290
  ReactEmailEditor,
8223
9291
  base64ToUtf8,
9292
+ canonicalizeEmailDocument,
9293
+ coerceEmailDocumentInput,
8224
9294
  emailPreviewDevices,
8225
9295
  extractHtmlForDesign,
8226
9296
  htmlToEmailDesignTemplate,
9297
+ htmlToJson,
8227
9298
  jsonToHtml,
8228
9299
  utf8ToBase64
8229
9300
  });