react-email-studio 3.3.1 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,11 +31,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  EmailPreviewModal: () => PreviewModal,
34
- ReactEmailEditor: () => ReactEmailEditor,
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;");
@@ -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) {
@@ -1013,14 +1217,16 @@ function blockToHtml(cb) {
1013
1217
  case "html": {
1014
1218
  const shell = emailSurfaceBgCss(p);
1015
1219
  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>`;
1220
+ const bodyRaw = typeof cb.content === "string" ? cb.content : typeof p.content === "string" ? p.content : "";
1221
+ const body = enhanceRichHtmlForEmail(bodyRaw || "<p></p>");
1222
+ return `<div style="${pd(p.padding)};${marginCss()}${shell}"><div class="email-rich-html-inner" style="${inner}">${body}</div></div>`;
1017
1223
  }
1018
1224
  case "image": {
1019
1225
  const of = typeof p.objectFit === "string" && p.objectFit.trim() ? p.objectFit.trim() : "cover";
1020
1226
  const op = typeof p.objectPosition === "string" && p.objectPosition.trim() ? p.objectPosition.trim() : "center";
1021
1227
  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
1228
  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>`;
1229
+ 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
1230
  }
1025
1231
  case "button": {
1026
1232
  const bd = emailBackdropBgCss(p);
@@ -1109,7 +1315,7 @@ function blockToHtml(cb) {
1109
1315
  bgRepeat: p.bgRepeat || "no-repeat",
1110
1316
  bgPosition: typeof p.bgPosition === "string" ? p.bgPosition : "center",
1111
1317
  bgGradient: p.bgGradient ?? null,
1112
- columnStyles: p.columnStyles,
1318
+ columns: getColumns(p),
1113
1319
  cells: p.cells || []
1114
1320
  };
1115
1321
  return rowToHtml(pseudo);
@@ -1123,7 +1329,7 @@ function rowToHtml(row) {
1123
1329
  const shellBg = emailSurfaceBgCss(row);
1124
1330
  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
1331
  const r = row.ratios[i] ?? 1;
1126
- const cs = row.columnStyles && row.columnStyles[i] ? row.columnStyles[i] : {};
1332
+ const cs = getColumnPropsAt(row, i);
1127
1333
  const colShell = emailSurfaceBgCss(cs);
1128
1334
  const pad3 = cs.padding && typeof cs.padding === "object" && !Array.isArray(cs.padding) ? `padding:${(() => {
1129
1335
  const o = cs.padding;
@@ -1171,6 +1377,7 @@ table[role="presentation"] td{min-width:0!important;word-break:break-word;}
1171
1377
  @media screen and (max-width:480px){
1172
1378
  .email-content-td{padding-left:max(10px,env(safe-area-inset-left))!important;padding-right:max(10px,env(safe-area-inset-right))!important;}
1173
1379
  }
1380
+ ${RICH_HTML_CONTENT_CSS}
1174
1381
  </style>`;
1175
1382
  function previewEmailSrcDoc(html, viewportWidthPx) {
1176
1383
  const viewportTag = `<meta name="viewport" content="width=${viewportWidthPx},initial-scale=1"/>`;
@@ -1222,6 +1429,8 @@ function designToHtml(rows, settings, opts = {}) {
1222
1429
  return `background-image:linear-gradient(${angle}deg, ${pairs.join(", ")});background-repeat:no-repeat;background-position:${pagePos};background-size:cover;`;
1223
1430
  })();
1224
1431
  const pageBgImgRaw = typeof settings.bgImage === "string" ? String(settings.bgImage).trim() : "";
1432
+ const pageBgColorRaw = typeof settings.bgColor === "string" ? String(settings.bgColor).trim() : "";
1433
+ const pageBgColor = pageBgColorRaw ? pageBgColorRaw : "transparent";
1225
1434
  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
1435
  const contentPos = typeof settings.contentBgPosition === "string" && settings.contentBgPosition.trim() ? settings.contentBgPosition.trim() : "center";
1227
1436
  const contentGrad = settings.contentBgGradient && typeof settings.contentBgGradient === "object" ? settings.contentBgGradient : null;
@@ -1244,8 +1453,8 @@ function designToHtml(rows, settings, opts = {}) {
1244
1453
  <head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/>
1245
1454
  <title>Email</title>${RESPONSIVE_EMAIL_CSS}${css}
1246
1455
  </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}">
1456
+ <body style="margin:0;padding:0;background:${pageBgColor};${pageGradCss || pageBgImgCss}">
1457
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="width:100%;max-width:100%;background-color:${pageBgColor};${pageGradCss || pageBgImgCss}">
1249
1458
  <tr><td align="center" style="padding:0;max-width:100%;">
1250
1459
  <table class="email-shell-table" role="presentation" width="100%" cellpadding="0" cellspacing="0"
1251
1460
  style="${contentShellStyle.join(";")}">
@@ -1279,14 +1488,49 @@ function paddingToUniform(p, fallback = 0) {
1279
1488
  function ensureId(id, prefix) {
1280
1489
  return typeof id === "string" && id.trim() ? id : `${prefix}_${uid()}`;
1281
1490
  }
1491
+ function normalizeDocBlock(b, prefix) {
1492
+ const raw = b && typeof b === "object" && !Array.isArray(b) ? b : {};
1493
+ const base = {
1494
+ id: ensureId(raw.id, prefix),
1495
+ type: typeof raw.type === "string" ? raw.type : "text",
1496
+ content: typeof raw.content === "string" ? raw.content : raw.content && typeof raw.content === "object" && !Array.isArray(raw.content) ? raw.content : {},
1497
+ ...raw.behavior && typeof raw.behavior === "object" && !Array.isArray(raw.behavior) ? { behavior: raw.behavior } : {},
1498
+ ...raw.responsive && typeof raw.responsive === "object" && !Array.isArray(raw.responsive) ? { responsive: raw.responsive } : {}
1499
+ };
1500
+ if (raw.props && typeof raw.props === "object" && !Array.isArray(raw.props)) {
1501
+ base.props = { ...raw.props };
1502
+ } else if (raw.styles && typeof raw.styles === "object" && !Array.isArray(raw.styles)) {
1503
+ base.styles = { ...raw.styles };
1504
+ }
1505
+ return base;
1506
+ }
1507
+ function normalizeDocSettings(settings) {
1508
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) return {};
1509
+ const s = { ...settings };
1510
+ delete s._reactEmailStudio;
1511
+ return s;
1512
+ }
1282
1513
  function normalizeEmailDocument(input) {
1283
1514
  if (input == null || typeof input !== "object" || Array.isArray(input)) return null;
1284
1515
  const doc = input;
1285
1516
  if (doc.type !== "email_document") return null;
1517
+ const rawBlocks = Array.isArray(doc.blocks) ? doc.blocks : [];
1518
+ const useBlocks = rawBlocks.length > 0;
1286
1519
  const rows = Array.isArray(doc.rows) ? doc.rows : [];
1287
- return {
1520
+ const base = {
1288
1521
  type: "email_document",
1289
- settings: doc.settings && typeof doc.settings === "object" && !Array.isArray(doc.settings) ? doc.settings : {},
1522
+ settings: normalizeDocSettings(doc.settings),
1523
+ globalStyles: doc.globalStyles && typeof doc.globalStyles === "object" && !Array.isArray(doc.globalStyles) ? doc.globalStyles : void 0,
1524
+ responsive: doc.responsive && typeof doc.responsive === "object" && !Array.isArray(doc.responsive) ? doc.responsive : void 0
1525
+ };
1526
+ if (useBlocks) {
1527
+ return {
1528
+ ...base,
1529
+ blocks: rawBlocks.map((b, bi) => normalizeDocBlock(b, `block${bi + 1}`))
1530
+ };
1531
+ }
1532
+ return {
1533
+ ...base,
1290
1534
  rows: rows.map((r, ri) => {
1291
1535
  const columns = Array.isArray(r.columns) ? r.columns : [];
1292
1536
  const layoutColCount = typeof r.layout?.columns === "number" && r.layout.columns > 0 ? Math.floor(r.layout.columns) : 0;
@@ -1308,28 +1552,76 @@ function normalizeEmailDocument(input) {
1308
1552
  columns: colCount,
1309
1553
  gap: typeof r.layout?.gap === "number" ? r.layout.gap : 0,
1310
1554
  stackOnMobile: r.layout?.stackOnMobile !== false,
1311
- align: r.layout?.align || "center"
1555
+ align: r.layout?.align || "center",
1556
+ ...typeof r.layout?.preset === "string" && r.layout.preset.trim() ? { preset: r.layout.preset.trim() } : {},
1557
+ ...Array.isArray(r.layout?.ratios) && r.layout.ratios.length ? {
1558
+ ratios: r.layout.ratios.filter(
1559
+ (x) => typeof x === "number" && Number.isFinite(x)
1560
+ )
1561
+ } : {}
1312
1562
  },
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 } : {},
1563
+ props: (() => {
1564
+ const p = r.props;
1565
+ if (p && typeof p === "object" && !Array.isArray(p)) return { ...p };
1566
+ const s = r.styles;
1567
+ if (s && typeof s === "object" && !Array.isArray(s)) {
1568
+ return {
1569
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1570
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1571
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1572
+ bgSize: s.backgroundSize || "cover",
1573
+ bgPosition: s.backgroundPosition || "center",
1574
+ bgGradient: s.backgroundGradient ?? null,
1575
+ padding: s.padding,
1576
+ borderRadius: s.borderRadius,
1577
+ borderWidth: s.borderWidth,
1578
+ borderColor: s.borderColor,
1579
+ textAlign: s.textAlign
1580
+ };
1581
+ }
1582
+ return {};
1583
+ })(),
1315
1584
  columns: colsNormalized.map((c, ci) => ({
1316
1585
  id: ensureId(c.id, `col${ri + 1}_${ci + 1}`),
1317
1586
  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
- })) : []
1587
+ ...(() => {
1588
+ const p = c.props;
1589
+ if (p && typeof p === "object" && !Array.isArray(p)) return { props: { ...p } };
1590
+ const s = c.styles;
1591
+ if (s && typeof s === "object" && !Array.isArray(s)) {
1592
+ return {
1593
+ props: {
1594
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1595
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1596
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1597
+ bgSize: s.backgroundSize || "cover",
1598
+ bgPosition: s.backgroundPosition || "center",
1599
+ bgGradient: s.backgroundGradient ?? null,
1600
+ padding: s.padding,
1601
+ borderRadius: s.borderRadius
1602
+ }
1603
+ };
1604
+ }
1605
+ return {};
1606
+ })(),
1607
+ blocks: Array.isArray(c.blocks) ? c.blocks.map((b, bi) => {
1608
+ const base2 = {
1609
+ id: ensureId(b?.id, `b${ri + 1}_${ci + 1}_${bi + 1}`),
1610
+ type: typeof b?.type === "string" ? b.type : "text",
1611
+ content: typeof b?.content === "string" ? b.content : b?.content && typeof b.content === "object" && !Array.isArray(b.content) ? b.content : {},
1612
+ ...b?.behavior && typeof b.behavior === "object" && !Array.isArray(b.behavior) ? { behavior: b.behavior } : {},
1613
+ ...b?.responsive && typeof b.responsive === "object" && !Array.isArray(b.responsive) ? { responsive: b.responsive } : {}
1614
+ };
1615
+ if (b?.props && typeof b.props === "object" && !Array.isArray(b.props)) {
1616
+ base2.props = { ...b.props };
1617
+ } else if (b?.styles && typeof b.styles === "object" && !Array.isArray(b.styles)) {
1618
+ base2.styles = { ...b.styles };
1619
+ }
1620
+ return base2;
1621
+ }) : []
1328
1622
  }))
1329
1623
  };
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
1624
+ })
1333
1625
  };
1334
1626
  }
1335
1627
 
@@ -1342,6 +1634,60 @@ function cloneJson(v) {
1342
1634
  return v;
1343
1635
  }
1344
1636
  }
1637
+ function legacyRowStylesToProps(s) {
1638
+ if (!s || typeof s !== "object" || Array.isArray(s)) return {};
1639
+ return {
1640
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1641
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1642
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1643
+ bgSize: s.backgroundSize || "cover",
1644
+ bgPosition: s.backgroundPosition || "center",
1645
+ bgGradient: s.backgroundGradient ?? null,
1646
+ padding: s.padding,
1647
+ borderRadius: s.borderRadius,
1648
+ borderWidth: s.borderWidth,
1649
+ borderColor: s.borderColor,
1650
+ textAlign: s.textAlign
1651
+ };
1652
+ }
1653
+ function rowPropsFromDocRow(r) {
1654
+ const p = r.props;
1655
+ if (p && typeof p === "object" && !Array.isArray(p)) {
1656
+ return { ...p };
1657
+ }
1658
+ return legacyRowStylesToProps(r.styles);
1659
+ }
1660
+ function applyRowPropsToEditorRow(row, r) {
1661
+ const p = rowPropsFromDocRow(r);
1662
+ if (p.padding !== void 0 && p.padding !== null) {
1663
+ row.padding = paddingToUniform(p.padding, row.padding);
1664
+ }
1665
+ if (typeof p.bgColor === "string") row.bgColor = p.bgColor.trim();
1666
+ if (typeof p.bgImage === "string") row.bgImage = p.bgImage.trim();
1667
+ if (typeof p.bgRepeat === "string") row.bgRepeat = p.bgRepeat;
1668
+ if (typeof p.bgSize === "string") row.bgSize = p.bgSize;
1669
+ if (typeof p.bgPosition === "string") row.bgPosition = p.bgPosition;
1670
+ if (p.bgGradient !== void 0) row.bgGradient = p.bgGradient;
1671
+ }
1672
+ function editorRowToDocProps(r) {
1673
+ const rowPad = r.padding && typeof r.padding === "object" && !Array.isArray(r.padding) ? layoutColumnPaddingForDoc(r.padding) : (() => {
1674
+ const n = typeof r.padding === "number" && Number.isFinite(r.padding) ? r.padding : 0;
1675
+ return { top: n, right: n, bottom: n, left: n };
1676
+ })();
1677
+ return {
1678
+ bgColor: typeof r.bgColor === "string" ? r.bgColor : "",
1679
+ bgImage: typeof r.bgImage === "string" ? r.bgImage : "",
1680
+ bgRepeat: r.bgRepeat || "no-repeat",
1681
+ bgSize: r.bgSize || "cover",
1682
+ bgPosition: r.bgPosition || "center",
1683
+ bgGradient: r.bgGradient ?? null,
1684
+ padding: rowPad,
1685
+ borderRadius: 0,
1686
+ borderWidth: 0,
1687
+ borderColor: "#e5e7eb",
1688
+ textAlign: "left"
1689
+ };
1690
+ }
1345
1691
  function layoutColumnPaddingForDoc(p) {
1346
1692
  if (typeof p === "number" && Number.isFinite(p)) {
1347
1693
  const n = Math.max(0, p);
@@ -1361,16 +1707,58 @@ function layoutColumnBorderRadiusForDoc(r) {
1361
1707
  }
1362
1708
  return 0;
1363
1709
  }
1364
- function columnPaddingNonZero(pad3) {
1365
- return pad3.top !== 0 || pad3.right !== 0 || pad3.bottom !== 0 || pad3.left !== 0;
1710
+ function normalizeLayoutContainerProps(props) {
1711
+ const columns = getColumns(props);
1712
+ const { columnStyles: _drop, ...rest } = props;
1713
+ return { ...rest, columns };
1714
+ }
1715
+ function mergeLayoutColumns(fromContent, fromHydrated) {
1716
+ const norm = (x) => {
1717
+ if (Array.isArray(x)) {
1718
+ return x.map((entry) => {
1719
+ const e = entry;
1720
+ const raw = e?.props && typeof e.props === "object" ? e.props : entry;
1721
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...raw } };
1722
+ });
1723
+ }
1724
+ if (x && typeof x === "object" && !Array.isArray(x)) {
1725
+ return columnsFromLegacyMap(x, Object.keys(x).length);
1726
+ }
1727
+ return [];
1728
+ };
1729
+ const a = norm(fromContent);
1730
+ const b = norm(fromHydrated);
1731
+ const len = Math.max(a.length, b.length, 1);
1732
+ const out = [];
1733
+ for (let i = 0; i < len; i++) {
1734
+ out.push({
1735
+ props: { ...DEFAULT_COLUMN_PROPS, ...a[i]?.props ?? {}, ...b[i]?.props ?? {} }
1736
+ });
1737
+ }
1738
+ return out.length ? out : void 0;
1366
1739
  }
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);
1740
+ function columnPropsFromDocColumn(c) {
1741
+ const p = c?.props;
1742
+ if (p && typeof p === "object" && !Array.isArray(p)) {
1743
+ return { ...DEFAULT_COLUMN_PROPS, ...p };
1372
1744
  }
1373
- return false;
1745
+ const s = c?.styles;
1746
+ if (!s || typeof s !== "object" || Array.isArray(s)) {
1747
+ return { ...DEFAULT_COLUMN_PROPS };
1748
+ }
1749
+ const padding = layoutColumnPaddingForDoc(s.padding);
1750
+ const borderRadius = layoutColumnBorderRadiusForDoc(s.borderRadius);
1751
+ return {
1752
+ ...DEFAULT_COLUMN_PROPS,
1753
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1754
+ bgImage: typeof s.backgroundImage === "string" ? String(s.backgroundImage).trim() : "",
1755
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1756
+ bgSize: s.backgroundSize || "cover",
1757
+ bgPosition: s.backgroundPosition || "center",
1758
+ bgGradient: s.backgroundGradient ?? null,
1759
+ padding,
1760
+ borderRadius
1761
+ };
1374
1762
  }
1375
1763
  function asNum(v) {
1376
1764
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
@@ -1467,13 +1855,15 @@ function internalBlockToEmailDoc(b, depth = 0) {
1467
1855
  ...p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1468
1856
  }
1469
1857
  };
1470
- case "html":
1858
+ case "html": {
1859
+ const body = typeof b.content === "string" ? b.content : typeof p.content === "string" ? p.content : "";
1471
1860
  return {
1472
1861
  id,
1473
1862
  type,
1474
- content: { html: typeof p.content === "string" ? p.content : "" },
1863
+ content: { html: normalizeRichHtmlForStorage(body) },
1475
1864
  styles: p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1476
1865
  };
1866
+ }
1477
1867
  case "image": {
1478
1868
  const br = p.borderRadius;
1479
1869
  const borderRadius = typeof br === "number" && Number.isFinite(br) ? br : br && typeof br === "object" && !Array.isArray(br) ? Math.round(
@@ -1653,7 +2043,7 @@ function internalBlockToEmailDoc(b, depth = 0) {
1653
2043
  bgRepeat: p.bgRepeat,
1654
2044
  bgPosition: p.bgPosition,
1655
2045
  bgGradient: p.bgGradient ?? null,
1656
- columnStyles: p.columnStyles,
2046
+ columns: getColumns(p).map((col) => ({ props: cloneJson(col.props) })),
1657
2047
  cells: cellsOut
1658
2048
  },
1659
2049
  styles: {}
@@ -1693,17 +2083,26 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1693
2083
  block.props.padding = paddingToUniform(s.padding, block.props.padding);
1694
2084
  }
1695
2085
  break;
1696
- case "html":
1697
- block.props.content = asStr(c.html) ?? block.props.content;
1698
- block.props.content = normalizeRichHtmlForStorage(block.props.content);
2086
+ case "html": {
2087
+ const br = b;
2088
+ const fromDoc = asStr(c.html);
2089
+ const fromRoot = typeof br.content === "string" ? String(br.content) : void 0;
2090
+ const fromLegacyProps = br.props && typeof br.props.content === "string" ? String(br.props.content) : void 0;
2091
+ const htmlNorm = normalizeRichHtmlForStorage(fromDoc ?? fromRoot ?? fromLegacyProps ?? "");
2092
+ block.content = htmlNorm;
2093
+ block.props.content = htmlNorm;
1699
2094
  if (s.padding && typeof s.padding === "object") {
1700
2095
  block.props.padding = paddingToUniform(s.padding, block.props.padding);
1701
2096
  }
1702
2097
  break;
2098
+ }
1703
2099
  case "image":
1704
2100
  block.props.src = asStr(c.src) ?? block.props.src;
1705
2101
  if (asStr(c.alt)) block.props.alt = c.alt;
1706
- if (asStr(c.link)) block.props.link = c.link;
2102
+ if (asStr(c.link)) {
2103
+ block.props.link = c.link;
2104
+ block.props.linkEnabled = true;
2105
+ }
1707
2106
  if (beh.openInNewTab === true) block.props.linkTarget = "_blank";
1708
2107
  if (asStr(s.width)) block.props.width = s.width;
1709
2108
  if (asStr(s.align)) block.props.align = s.align;
@@ -1825,8 +2224,17 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1825
2224
  if (typeof c.bgRepeat === "string") block.props.bgRepeat = c.bgRepeat;
1826
2225
  if (typeof c.bgPosition === "string") block.props.bgPosition = c.bgPosition;
1827
2226
  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;
2227
+ if (c.bgGradient !== void 0) block.props.bgGradient = c.bgGradient;
2228
+ if (Array.isArray(c.columns)) {
2229
+ block.props.columns = c.columns.map((col) => ({
2230
+ props: {
2231
+ ...DEFAULT_COLUMN_PROPS,
2232
+ ...col?.props && typeof col.props === "object" ? col.props : {}
2233
+ }
2234
+ }));
2235
+ } else if (c.columnStyles && typeof c.columnStyles === "object" && !Array.isArray(c.columnStyles)) {
2236
+ const ratios = Array.isArray(c.ratios) ? c.ratios : [1];
2237
+ block.props.columns = columnsFromLegacyMap(c.columnStyles, ratios.length);
1830
2238
  }
1831
2239
  block.props.cells = c.cells.map(
1832
2240
  (col) => Array.isArray(col) ? col.map((raw) => mapEmbeddedLayoutCellBlock(raw, layoutDepth + 1)).filter((x) => x != null) : []
@@ -1870,6 +2278,10 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1870
2278
  }
1871
2279
  }
1872
2280
  applyImportedEditorPropsFromDoc(block, t, b, layoutDepth);
2281
+ if (t === "image") {
2282
+ const link = asStr(block.props.link);
2283
+ if (link && block.props.linkEnabled == null) block.props.linkEnabled = true;
2284
+ }
1873
2285
  return block;
1874
2286
  }
1875
2287
  function rowToInternal(r) {
@@ -1891,55 +2303,315 @@ function rowToInternal(r) {
1891
2303
  row.id = typeof r.id === "string" ? r.id : uid();
1892
2304
  row.cols = cols;
1893
2305
  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;
2306
+ applyRowPropsToEditorRow(row, r);
2307
+ const lay = r.layout;
2308
+ if (lay && typeof lay === "object" && !Array.isArray(lay)) {
2309
+ if (typeof lay.preset === "string" && lay.preset.trim()) row.preset = lay.preset.trim();
2310
+ if (Array.isArray(lay.ratios) && lay.ratios.length) {
2311
+ const rr = lay.ratios.filter((x) => typeof x === "number" && Number.isFinite(x));
2312
+ if (rr.length) row.ratios = rr;
2313
+ }
2314
+ }
1901
2315
  row.cells = colList.map((c) => {
1902
2316
  const blocks = Array.isArray(c.blocks) ? c.blocks : [];
1903
2317
  return blocks.map((blk) => mapBlockToInternal(blk, 0)).filter((x) => x != null);
1904
2318
  });
1905
2319
  const studio = r._reactEmailStudio;
1906
2320
  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
- });
2321
+ const columnsFromDoc = colList.map((c) => ({
2322
+ props: columnPropsFromDocColumn(c)
2323
+ }));
1923
2324
  if (snap && typeof snap === "object" && !Array.isArray(snap)) {
1924
- const { cells: _omitCells, columnStyles: snapCs, ...rest } = snap;
2325
+ const {
2326
+ cells: _omitCells,
2327
+ columnStyles: legacyCs,
2328
+ columns: snapCols,
2329
+ ...rest
2330
+ } = snap;
1925
2331
  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;
2332
+ const fromSnap = Array.isArray(snapCols) ? snapCols.map((col) => ({
2333
+ props: {
2334
+ ...DEFAULT_COLUMN_PROPS,
2335
+ ...col?.props && typeof col.props === "object" ? col.props : {}
2336
+ }
2337
+ })) : columnsFromLegacyMap(legacyCs, cols);
2338
+ row.columns = mergeLayoutColumns(fromSnap, columnsFromDoc) ?? columnsFromDoc;
2339
+ delete row.columnStyles;
2340
+ } else {
2341
+ row.columns = columnsFromDoc;
1931
2342
  }
1932
2343
  return row;
1933
2344
  }
1934
- function normalizeEmailDesignInput(input) {
1935
- const doc = normalizeEmailDocument(input);
1936
- if (!doc) return null;
1937
- const s = doc.settings || {};
1938
- const studioSettings = s._reactEmailStudio;
2345
+ function coerceEmailDocumentInput(input) {
2346
+ if (input == null) return input;
2347
+ if (Array.isArray(input)) {
2348
+ if (input.length === 0) return input;
2349
+ return { type: "email_document", settings: {}, blocks: input };
2350
+ }
2351
+ if (typeof input !== "object") return input;
2352
+ const o = input;
2353
+ if (o.type === "email_document") return input;
2354
+ if (Array.isArray(o.blocks)) {
2355
+ return {
2356
+ type: "email_document",
2357
+ settings: o.settings && typeof o.settings === "object" ? o.settings : {},
2358
+ blocks: o.blocks
2359
+ };
2360
+ }
2361
+ return input;
2362
+ }
2363
+ function rootLayoutBlockToDocRow(block) {
2364
+ const p = block.props && typeof block.props === "object" ? block.props : {};
2365
+ const c = block.content && typeof block.content === "object" && !Array.isArray(block.content) ? block.content : {};
2366
+ const cols = Math.max(
2367
+ 1,
2368
+ typeof p.cols === "number" && p.cols > 0 ? Math.floor(p.cols) : 0,
2369
+ typeof c.cols === "number" && c.cols > 0 ? Math.floor(c.cols) : 0,
2370
+ Array.isArray(p.cells) ? p.cells.length : 0,
2371
+ Array.isArray(c.cells) ? c.cells.length : 0
2372
+ );
2373
+ const rawCells = Array.isArray(c.cells) ? c.cells : Array.isArray(p.cells) ? p.cells : [];
2374
+ const contentColumns = Array.isArray(c.columns) ? c.columns : [];
2375
+ const propsColumns = getColumns(p);
2376
+ const columns = Array.from({ length: cols }, (_, ci) => {
2377
+ const cellRaw = rawCells[ci];
2378
+ const blocks = Array.isArray(cellRaw) ? cellRaw.filter((x) => x && typeof x === "object").map((x, bi) => {
2379
+ const blk = x;
2380
+ return {
2381
+ id: ensureBlockId(blk.id, `b_${ci}_${bi}`),
2382
+ type: typeof blk.type === "string" ? blk.type : "text",
2383
+ content: blk.content,
2384
+ ...blk.props ? { props: blk.props } : {},
2385
+ ...blk.styles ? { styles: blk.styles } : {},
2386
+ ...blk.behavior ? { behavior: blk.behavior } : {}
2387
+ };
2388
+ }) : [];
2389
+ const colProps = {
2390
+ ...DEFAULT_COLUMN_PROPS,
2391
+ ...propsColumns[ci]?.props ?? contentColumns[ci]?.props ?? {}
2392
+ };
2393
+ return {
2394
+ id: `col_${ci + 1}`,
2395
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2396
+ props: cloneJson(colProps),
2397
+ blocks
2398
+ };
2399
+ });
2400
+ const rowProps = {
2401
+ bgColor: typeof p.bgColor === "string" ? p.bgColor : "",
2402
+ bgImage: typeof p.bgImage === "string" ? p.bgImage : "",
2403
+ bgRepeat: p.bgRepeat || "no-repeat",
2404
+ bgSize: p.bgSize || "cover",
2405
+ bgPosition: p.bgPosition || "center",
2406
+ bgGradient: p.bgGradient ?? null,
2407
+ padding: typeof p.padding === "number" ? { top: p.padding, right: p.padding, bottom: p.padding, left: p.padding } : normalizePadding(p.padding)
2408
+ };
2409
+ const ratios = Array.isArray(p.ratios) ? p.ratios : Array.isArray(c.ratios) ? c.ratios : void 0;
2410
+ return {
2411
+ id: typeof block.id === "string" ? block.id : uid(),
2412
+ type: "row",
2413
+ layout: {
2414
+ columns: cols,
2415
+ gap: typeof p.gap === "number" ? p.gap : typeof c.gap === "number" ? c.gap : 0,
2416
+ stackOnMobile: true,
2417
+ align: "center",
2418
+ ...typeof p.preset === "string" && p.preset.trim() ? { preset: p.preset.trim() } : typeof c.preset === "string" && c.preset.trim() ? { preset: c.preset.trim() } : {},
2419
+ ...ratios?.length ? { ratios: [...ratios] } : {}
2420
+ },
2421
+ props: rowProps,
2422
+ columns
2423
+ };
2424
+ }
2425
+ function ensureBlockId(id, prefix) {
2426
+ return typeof id === "string" && id.trim() ? id : `${prefix}_${uid()}`;
2427
+ }
2428
+ function wrapRootContentBlock(block) {
2429
+ const row = makeLayoutRow(pickPresetByColCount(1));
2430
+ const internal = mapBlockToInternal(block, 0);
2431
+ if (internal) row.cells[0] = [internal];
2432
+ return row;
2433
+ }
2434
+ function rootBlockToEditorRow(block) {
2435
+ const t = block.type === "nestedRow" ? "layout" : block.type;
2436
+ if (t === "layout") {
2437
+ return rowToInternal(rootLayoutBlockToDocRow(block));
2438
+ }
2439
+ return wrapRootContentBlock(block);
2440
+ }
2441
+ function blocksToEditorRows(blocks) {
2442
+ return blocks.map(rootBlockToEditorRow);
2443
+ }
2444
+ function rowPropsToLegacyStyles(p) {
2445
+ return {
2446
+ backgroundColor: p.bgColor ?? "",
2447
+ backgroundImage: p.bgImage ?? "",
2448
+ backgroundRepeat: p.bgRepeat ?? "no-repeat",
2449
+ backgroundSize: p.bgSize ?? "cover",
2450
+ backgroundPosition: p.bgPosition ?? "center",
2451
+ backgroundGradient: p.bgGradient ?? null,
2452
+ padding: p.padding,
2453
+ borderRadius: p.borderRadius ?? 0,
2454
+ borderWidth: p.borderWidth ?? 0,
2455
+ borderColor: p.borderColor ?? "#e5e7eb",
2456
+ textAlign: p.textAlign ?? "left"
2457
+ };
2458
+ }
2459
+ function columnPropsToLegacyStyles(cp) {
2460
+ return {
2461
+ padding: layoutColumnPaddingForDoc(cp.padding),
2462
+ backgroundColor: cp.bgColor ?? "",
2463
+ backgroundImage: cp.bgImage ?? "",
2464
+ backgroundRepeat: cp.bgRepeat ?? "no-repeat",
2465
+ backgroundSize: cp.bgSize ?? "cover",
2466
+ backgroundPosition: cp.bgPosition ?? "center",
2467
+ backgroundGradient: cp.bgGradient ?? null,
2468
+ borderRadius: layoutColumnBorderRadiusForDoc(cp.borderRadius)
2469
+ };
2470
+ }
2471
+ function editorRowStudioSnapshot(r) {
2472
+ const p = editorRowToDocProps(r);
2473
+ const columns = getColumns(r).map((col) => ({
2474
+ props: cloneJson(col.props ?? DEFAULT_COLUMN_PROPS)
2475
+ }));
2476
+ return {
2477
+ id: r.id,
2478
+ type: "layout",
2479
+ preset: r.preset,
2480
+ cols: r.cols,
2481
+ ratios: Array.isArray(r.ratios) ? [...r.ratios] : [1],
2482
+ gap: typeof r.gap === "number" ? r.gap : 0,
2483
+ padding: p.padding,
2484
+ bgColor: p.bgColor ?? "",
2485
+ bgImage: p.bgImage ?? "",
2486
+ bgSize: p.bgSize ?? "cover",
2487
+ bgRepeat: p.bgRepeat ?? "no-repeat",
2488
+ bgPosition: p.bgPosition ?? "center",
2489
+ bgGradient: p.bgGradient ?? null,
2490
+ columns
2491
+ };
2492
+ }
2493
+ function exportBlockForLegacyRow(b, depth = 0) {
2494
+ if (!b || typeof b !== "object") {
2495
+ return { id: uid(), type: "text", content: {}, props: {} };
2496
+ }
2497
+ const rawProps = b.props && typeof b.props === "object" && !Array.isArray(b.props) ? cloneJson(b.props) : {};
2498
+ if (b.type === "layout" && Array.isArray(b.props?.cells)) {
2499
+ rawProps.cells = b.props.cells.map(
2500
+ (col) => Array.isArray(col) ? col.map((child) => exportBlockForLegacyRow(child, depth + 1)) : []
2501
+ );
2502
+ }
2503
+ if (b.type === "html") {
2504
+ const body = typeof b.content === "string" ? b.content : typeof rawProps.content === "string" ? rawProps.content : "";
2505
+ const html = normalizeRichHtmlForStorage(body);
2506
+ rawProps.content = html;
2507
+ return {
2508
+ id: typeof b.id === "string" ? b.id : uid(),
2509
+ type: "html",
2510
+ content: { html },
2511
+ props: rawProps
2512
+ };
2513
+ }
2514
+ const doc = internalBlockToEmailDoc(b, depth);
2515
+ const out = {
2516
+ id: doc.id,
2517
+ type: doc.type,
2518
+ content: doc.content ?? {},
2519
+ props: rawProps
2520
+ };
2521
+ if (doc.behavior) out.behavior = doc.behavior;
2522
+ return out;
2523
+ }
2524
+ function editorRowToEmailDocumentRow(r, rowIndex) {
2525
+ const rowProps = editorRowToDocProps(r);
2526
+ const cellArrays = Array.isArray(r.cells) ? r.cells : [];
2527
+ const declaredCols = typeof r.cols === "number" && r.cols > 0 ? Math.floor(r.cols) : 0;
2528
+ const colCount = Math.max(1, cellArrays.length, declaredCols);
2529
+ const rowColumns = getColumns(r);
2530
+ const columns = Array.from({ length: colCount }, (_, ci) => {
2531
+ const cp = {
2532
+ ...DEFAULT_COLUMN_PROPS,
2533
+ ...rowColumns[ci]?.props ?? {}
2534
+ };
2535
+ const cellBlocks = cellArrays[ci];
2536
+ const blocks = Array.isArray(cellBlocks) ? cellBlocks.map((blk) => exportBlockForLegacyRow(blk)).filter((x) => x != null) : [];
2537
+ return {
2538
+ id: `col_${rowIndex + 1}_${ci + 1}`,
2539
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2540
+ styles: columnPropsToLegacyStyles(cp),
2541
+ props: cloneJson(cp),
2542
+ blocks
2543
+ };
2544
+ });
2545
+ const ratios = Array.isArray(r.ratios) ? [...r.ratios] : void 0;
2546
+ return {
2547
+ id: typeof r.id === "string" ? r.id : uid(),
2548
+ type: "row",
2549
+ _reactEmailStudio: { row: editorRowStudioSnapshot(r) },
2550
+ layout: {
2551
+ columns: colCount,
2552
+ gap: typeof r.gap === "number" ? r.gap : 0,
2553
+ stackOnMobile: true,
2554
+ align: "center",
2555
+ ...typeof r.preset === "string" && r.preset.trim() ? { preset: r.preset.trim() } : {},
2556
+ ...ratios?.length ? { ratios } : {}
2557
+ },
2558
+ props: rowProps,
2559
+ styles: rowPropsToLegacyStyles(rowProps),
2560
+ columns
2561
+ };
2562
+ }
2563
+ function buildExportSettingsWithStudio(settings) {
2564
+ const base = buildExportSettings(settings) ?? {};
2565
+ const editorSettings = cloneJson(settings);
2566
+ return {
2567
+ ...base,
2568
+ _reactEmailStudio: {
2569
+ contentPadding: base.contentPadding ?? 24,
2570
+ contentBorderRadius: base.contentBorderRadius ?? 8,
2571
+ editorSettings
2572
+ }
2573
+ };
2574
+ }
2575
+ function editorSettingStr(settings, key, fallback = "") {
2576
+ const v = settings[key];
2577
+ return typeof v === "string" ? v : fallback;
2578
+ }
2579
+ function buildExportSettings(settings) {
2580
+ const contentWidth = typeof settings.contentWidth === "number" ? settings.contentWidth : 600;
2581
+ const bgGradient = settings.bgGradient;
2582
+ const contentBgGradient = settings.contentBgGradient;
2583
+ return {
2584
+ width: contentWidth,
2585
+ backgroundColor: editorSettingStr(settings, "bgColor", "#f1f5f9"),
2586
+ backgroundImage: editorSettingStr(settings, "bgImage"),
2587
+ backgroundRepeat: editorSettingStr(settings, "bgRepeat", "no-repeat"),
2588
+ backgroundSize: editorSettingStr(settings, "bgSize", "cover"),
2589
+ backgroundPosition: editorSettingStr(settings, "bgPosition", "center"),
2590
+ backgroundGradient: bgGradient && typeof bgGradient === "object" && !Array.isArray(bgGradient) ? bgGradient : void 0,
2591
+ contentBackgroundColor: editorSettingStr(settings, "contentBg", "#ffffff"),
2592
+ contentBackgroundImage: editorSettingStr(settings, "contentBgImage"),
2593
+ contentBackgroundRepeat: editorSettingStr(settings, "contentBgRepeat", "no-repeat"),
2594
+ contentBackgroundSize: editorSettingStr(settings, "contentBgSize", "cover"),
2595
+ contentBackgroundPosition: editorSettingStr(settings, "contentBgPosition", "center"),
2596
+ contentBackgroundGradient: contentBgGradient && typeof contentBgGradient === "object" && !Array.isArray(contentBgGradient) ? contentBgGradient : void 0,
2597
+ fontFamily: editorSettingStr(settings, "fontFamily") || editorSettingStr(settings, "pageFontFamily", "Arial, Helvetica, sans-serif"),
2598
+ lineHeightBase: settings.pageLineHeight != null && settings.pageLineHeight !== "" ? Number(settings.pageLineHeight) : 1.6,
2599
+ color: editorSettingStr(settings, "pageTextColor", "#111827"),
2600
+ responsive: typeof settings.pageResponsive === "boolean" ? settings.pageResponsive : true,
2601
+ rtl: !!settings.pageRtl,
2602
+ contentPadding: typeof settings.padding === "number" ? settings.padding : 24,
2603
+ contentBorderRadius: typeof settings.borderRadius === "number" ? settings.borderRadius : 8
2604
+ };
2605
+ }
2606
+ function editorSettingsFromDoc(s, rawSettings) {
2607
+ const studioSettings = rawSettings?._reactEmailStudio;
1939
2608
  const settings = {};
1940
2609
  if (studioSettings?.editorSettings && typeof studioSettings.editorSettings === "object" && !Array.isArray(studioSettings.editorSettings)) {
1941
2610
  Object.assign(settings, cloneJson(studioSettings.editorSettings));
1942
2611
  }
2612
+ const pageRtlFromEditor = settings.pageRtl;
2613
+ const contentPadding = typeof s.contentPadding === "number" ? s.contentPadding : typeof studioSettings?.contentPadding === "number" ? studioSettings.contentPadding : void 0;
2614
+ const contentBorderRadius = typeof s.contentBorderRadius === "number" ? s.contentBorderRadius : typeof studioSettings?.contentBorderRadius === "number" ? studioSettings.contentBorderRadius : void 0;
1943
2615
  Object.assign(settings, {
1944
2616
  bgColor: s.backgroundColor || "#f1f5f9",
1945
2617
  bgImage: s.backgroundImage || "",
@@ -1954,103 +2626,67 @@ function normalizeEmailDesignInput(input) {
1954
2626
  contentBgPosition: s.contentBackgroundPosition || "center",
1955
2627
  contentBgGradient: s.contentBackgroundGradient || null,
1956
2628
  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,
2629
+ padding: contentPadding ?? (typeof settings.padding === "number" ? settings.padding : 24),
2630
+ borderRadius: contentBorderRadius ?? (typeof settings.borderRadius === "number" ? settings.borderRadius : 8),
1959
2631
  pageFontFamily: s.fontFamily,
1960
2632
  pageTextColor: s.color,
1961
2633
  pageLineHeight: s.lineHeightBase != null ? String(s.lineHeightBase) : void 0,
1962
2634
  pageResponsive: s.responsive,
1963
- pageRtl: s.rtl,
2635
+ pageRtl: typeof s.rtl === "boolean" ? s.rtl : pageRtlFromEditor,
1964
2636
  fontFamily: s.fontFamily
1965
2637
  });
1966
- const rows = (doc.rows || []).map(rowToInternal);
2638
+ return settings;
2639
+ }
2640
+ function normalizeEmailDesignInput(input) {
2641
+ const coerced = coerceEmailDocumentInput(input);
2642
+ const doc = normalizeEmailDocument(coerced);
2643
+ if (!doc) return null;
2644
+ const rawDoc = input && typeof input === "object" && !Array.isArray(input) ? input : null;
2645
+ const rawSettings = rawDoc?.settings && typeof rawDoc.settings === "object" && !Array.isArray(rawDoc.settings) ? rawDoc.settings : null;
2646
+ const settings = editorSettingsFromDoc(doc.settings || {}, rawSettings);
2647
+ let rows;
2648
+ if (doc.blocks?.length) {
2649
+ rows = blocksToEditorRows(doc.blocks);
2650
+ } else {
2651
+ const rawRows = rawDoc && Array.isArray(rawDoc.rows) ? rawDoc.rows : [];
2652
+ rows = (doc.rows || []).map((r, i) => {
2653
+ const legacy = rawRows[i]?._reactEmailStudio;
2654
+ if (legacy) {
2655
+ return rowToInternal({ ...r, _reactEmailStudio: legacy });
2656
+ }
2657
+ return rowToInternal(r);
2658
+ });
2659
+ }
1967
2660
  return withHydratedRows({ rows, settings, __emailDocument: doc });
1968
2661
  }
2662
+ function emailDocumentToEditorRows(input) {
2663
+ const coerced = coerceEmailDocumentInput(input);
2664
+ const doc = normalizeEmailDocument(coerced);
2665
+ if (!doc) return null;
2666
+ const rawDoc = input && typeof input === "object" && !Array.isArray(input) ? input : null;
2667
+ if (doc.blocks?.length) {
2668
+ return blocksToEditorRows(doc.blocks);
2669
+ }
2670
+ const rawRows = rawDoc && Array.isArray(rawDoc.rows) ? rawDoc.rows : [];
2671
+ return (doc.rows || []).map((r, i) => {
2672
+ const legacy = rawRows[i]?._reactEmailStudio;
2673
+ if (legacy) {
2674
+ return rowToInternal({ ...r, _reactEmailStudio: legacy });
2675
+ }
2676
+ return rowToInternal(r);
2677
+ });
2678
+ }
1969
2679
  function designToEmailDocument(rows, settings) {
1970
- const doc = {
2680
+ return {
1971
2681
  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
- })
2682
+ settings: buildExportSettingsWithStudio(settings),
2683
+ rows: rows.map((r, i) => editorRowToEmailDocumentRow(r, i))
2052
2684
  };
2053
- return doc;
2685
+ }
2686
+ function canonicalizeEmailDocument(input) {
2687
+ const loaded = normalizeEmailDesignInput(coerceEmailDocumentInput(input) ?? input);
2688
+ if (!loaded?.rows) return null;
2689
+ return designToEmailDocument(loaded.rows, loaded.settings);
2054
2690
  }
2055
2691
  function hydrateBlock(b, depth = 0) {
2056
2692
  if (!b || typeof b !== "object") return null;
@@ -2064,13 +2700,13 @@ function hydrateBlock(b, depth = 0) {
2064
2700
  ...b,
2065
2701
  type: "layout",
2066
2702
  id: typeof b.id === "string" && b.id ? b.id : uid(),
2067
- props: {
2703
+ props: normalizeLayoutContainerProps({
2068
2704
  ...defaults2,
2069
2705
  ...b.props,
2070
2706
  bgSize: b.props.bgSize ?? defaults2.bgSize,
2071
2707
  bgRepeat: b.props.bgRepeat ?? defaults2.bgRepeat,
2072
2708
  cells: ratios.map(() => [])
2073
- }
2709
+ })
2074
2710
  };
2075
2711
  }
2076
2712
  const cells = b.props.cells.map(
@@ -2080,24 +2716,33 @@ function hydrateBlock(b, depth = 0) {
2080
2716
  ...b,
2081
2717
  type: "layout",
2082
2718
  id: typeof b.id === "string" && b.id ? b.id : uid(),
2083
- props: {
2719
+ props: normalizeLayoutContainerProps({
2084
2720
  ...defaults2,
2085
2721
  ...b.props,
2086
2722
  bgSize: b.props.bgSize ?? defaults2.bgSize,
2087
2723
  bgRepeat: b.props.bgRepeat ?? defaults2.bgRepeat,
2088
2724
  cells
2089
- }
2725
+ })
2090
2726
  };
2091
2727
  }
2092
2728
  const defaults = DEFAULT_BLOCK_PROPS[t];
2093
2729
  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 : {} };
2730
+ if (t === "html") {
2731
+ const rawProps = b.props && typeof b.props === "object" && !Array.isArray(b.props) ? b.props : {};
2732
+ const { content: legacyPropContent, ...restProps } = rawProps;
2733
+ const merged2 = { ...defaults, ...restProps };
2734
+ normalizeBoxStyles(merged2, t);
2735
+ const src = typeof b.content === "string" ? String(b.content) : typeof legacyPropContent === "string" ? legacyPropContent : "";
2736
+ const htmlNorm = normalizeRichHtmlForStorage(src);
2737
+ merged2.content = htmlNorm;
2738
+ return {
2739
+ ...b,
2740
+ id: typeof b.id === "string" && b.id ? b.id : uid(),
2741
+ content: htmlNorm,
2742
+ props: merged2
2743
+ };
2744
+ }
2745
+ const merged = { ...defaults, ...b.props && typeof b.props === "object" ? b.props : {} };
2101
2746
  normalizeBoxStyles(merged, t);
2102
2747
  return {
2103
2748
  ...b,
@@ -2113,70 +2758,50 @@ function mapEmbeddedLayoutCellBlock(raw, layoutDepth = 0) {
2113
2758
  }
2114
2759
  return mapBlockToInternal(r, layoutDepth);
2115
2760
  }
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
2761
  function applyImportedEditorPropsFromDoc(block, t, b, layoutDepth = 0) {
2137
2762
  const exp = b.props;
2138
- if (!exp || typeof exp !== "object" || Array.isArray(exp)) return;
2763
+ if (!exp || typeof exp !== "object" || Array.isArray(exp)) {
2764
+ if (t === "html" && typeof block.content === "string") {
2765
+ block.content = normalizeRichHtmlForStorage(block.content);
2766
+ block.props.content = block.content;
2767
+ }
2768
+ return;
2769
+ }
2139
2770
  if (t === "layout" && Array.isArray(exp.cells)) {
2140
- const columnStylesFromContent = block.props.columnStyles;
2771
+ const columnsFromContent = block.props.columns ?? block.props.columnStyles;
2772
+ const expHasColumns = Array.isArray(exp.columns) || exp.columnStyles && typeof exp.columnStyles === "object" && !Array.isArray(exp.columnStyles);
2141
2773
  const hb = hydrateBlock({ type: "layout", id: block.id, props: exp }, layoutDepth);
2142
2774
  if (hb) {
2143
2775
  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;
2776
+ const merged = mergeLayoutColumns(
2777
+ columnsFromContent,
2778
+ expHasColumns ? block.props.columns : void 0
2779
+ );
2780
+ if (merged) block.props.columns = merged;
2781
+ else delete block.props.columns;
2782
+ delete block.props.columnStyles;
2147
2783
  if (typeof hb.id === "string" && hb.id) block.id = hb.id;
2148
2784
  }
2149
2785
  return;
2150
2786
  }
2787
+ if (t === "html") {
2788
+ const { content: expContent, ...expRest } = exp;
2789
+ Object.assign(block.props, expRest);
2790
+ normalizeBoxStyles(block.props, t);
2791
+ if (typeof expContent === "string") {
2792
+ block.content = normalizeRichHtmlForStorage(expContent);
2793
+ } else if (typeof block.content === "string") {
2794
+ block.content = normalizeRichHtmlForStorage(block.content);
2795
+ }
2796
+ block.props.content = typeof block.content === "string" ? block.content : "";
2797
+ return;
2798
+ }
2151
2799
  Object.assign(block.props, exp);
2152
2800
  normalizeBoxStyles(block.props, t);
2153
- if (t === "html" || t === "text") {
2801
+ if (t === "text") {
2154
2802
  block.props.content = normalizeRichHtmlForStorage(String(block.props.content ?? ""));
2155
2803
  }
2156
2804
  }
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
2805
  function hydrateLayoutRow(row) {
2181
2806
  if (!row || typeof row !== "object") return row;
2182
2807
  const cells = (row.cells || []).map(
@@ -2219,6 +2844,77 @@ function jsonToHtml(designInput, opts = {}) {
2219
2844
  return designToHtml(d.rows, d.settings, opts);
2220
2845
  }
2221
2846
 
2847
+ // src/lib/htmlToEmailDesign.ts
2848
+ var pad = (n) => ({ top: n, right: n, bottom: n, left: n });
2849
+ function extractHtmlForDesign(html) {
2850
+ const t = String(html ?? "").trim();
2851
+ if (!t) return "";
2852
+ const head = t.slice(0, 800).toLowerCase();
2853
+ if (!head.includes("<html") && !head.includes("<!doctype")) {
2854
+ return normalizeRichHtmlForStorage(t);
2855
+ }
2856
+ try {
2857
+ const doc = new DOMParser().parseFromString(t, "text/html");
2858
+ const body = doc.body;
2859
+ if (!body) return normalizeRichHtmlForStorage(t);
2860
+ return normalizeRichHtmlForStorage(body.innerHTML);
2861
+ } catch {
2862
+ return normalizeRichHtmlForStorage(t);
2863
+ }
2864
+ }
2865
+ function htmlToEmailDesignTemplate(html) {
2866
+ const inner = extractHtmlForDesign(html);
2867
+ if (!inner) return null;
2868
+ const doc = {
2869
+ type: "email_document",
2870
+ settings: {
2871
+ width: 600,
2872
+ backgroundColor: "#f1f5f9",
2873
+ contentBackgroundColor: "#ffffff",
2874
+ fontFamily: "Arial, Helvetica, sans-serif",
2875
+ lineHeightBase: 1.6,
2876
+ color: "#111827",
2877
+ responsive: true,
2878
+ rtl: false
2879
+ },
2880
+ rows: [
2881
+ {
2882
+ id: "row_html_import",
2883
+ type: "row",
2884
+ layout: { columns: 1, gap: 0, stackOnMobile: true, align: "center" },
2885
+ styles: {
2886
+ backgroundColor: "#ffffff",
2887
+ backgroundRepeat: "no-repeat",
2888
+ backgroundSize: "cover",
2889
+ padding: pad(24),
2890
+ textAlign: "left"
2891
+ },
2892
+ columns: [
2893
+ {
2894
+ id: "col_html_import",
2895
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2896
+ styles: { padding: pad(0), backgroundColor: "" },
2897
+ blocks: [
2898
+ {
2899
+ id: "block_html_import",
2900
+ type: "html",
2901
+ content: inner,
2902
+ props: { ...DEFAULT_BLOCK_PROPS.html }
2903
+ }
2904
+ ]
2905
+ }
2906
+ ]
2907
+ }
2908
+ ]
2909
+ };
2910
+ return doc;
2911
+ }
2912
+ function htmlToJson(html, pretty = false) {
2913
+ const doc = htmlToEmailDesignTemplate(html);
2914
+ if (!doc) return "";
2915
+ return pretty ? JSON.stringify(doc, null, 2) : JSON.stringify(doc);
2916
+ }
2917
+
2222
2918
  // src/editor/canvas/DesignCanvas.tsx
2223
2919
  var import_react = require("react");
2224
2920
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -2359,13 +3055,15 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2359
3055
  )
2360
3056
  );
2361
3057
  if (type === "html") {
2362
- const emptyRich = isEffectivelyEmptyRichHtml(p.content);
3058
+ const richHtml = typeof block.content === "string" ? block.content : p.content;
3059
+ const emptyRich = isEffectivelyEmptyRichHtml(richHtml);
2363
3060
  return wrap(
2364
3061
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative", minHeight: emptyRich && !preview ? "2.75em" : void 0 }, children: [
3062
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: RICH_HTML_CONTENT_CSS }),
2365
3063
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2366
3064
  "div",
2367
3065
  {
2368
- className: "email-editor-rich-canvas",
3066
+ className: "email-editor-rich-canvas email-rich-html-content",
2369
3067
  style: {
2370
3068
  fontSize: p.fontSize,
2371
3069
  color: p.color,
@@ -2376,7 +3074,7 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2376
3074
  wordBreak: "break-word",
2377
3075
  minHeight: emptyRich && !preview ? "2.75em" : void 0
2378
3076
  },
2379
- dangerouslySetInnerHTML: { __html: p.content || "<p></p>" }
3077
+ dangerouslySetInnerHTML: { __html: richHtml || "<p></p>" }
2380
3078
  }
2381
3079
  ),
2382
3080
  emptyRich && !preview ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2424,7 +3122,7 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2424
3122
  }
2425
3123
  }
2426
3124
  );
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 }));
3125
+ 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
3126
  }
2429
3127
  if (type === "button") return wrap(
2430
3128
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { textAlign: p.fullWidth ? "center" : p.align }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2597,7 +3295,7 @@ function NestedRowBlock({
2597
3295
  );
2598
3296
  },
2599
3297
  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] || {};
3298
+ const cS = getColumnPropsAt(p, ici);
2601
3299
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2602
3300
  "div",
2603
3301
  {
@@ -2655,7 +3353,9 @@ function BlockItem({
2655
3353
  onContextMenu,
2656
3354
  preview,
2657
3355
  C,
2658
- Line
3356
+ Line,
3357
+ dragAsRow = false,
3358
+ beginRowDrag
2659
3359
  }) {
2660
3360
  const wrapperRef = (0, import_react.useRef)(null);
2661
3361
  const bid = editorId ? `${editorId}-block-${cb.id}` : void 0;
@@ -2703,7 +3403,7 @@ function BlockItem({
2703
3403
  {
2704
3404
  "data-bidx": ci,
2705
3405
  draggable: !preview,
2706
- title: preview ? void 0 : "Click to select \xB7 drag to reorder or move",
3406
+ title: preview ? void 0 : dragAsRow ? "Click to select \xB7 drag to reorder section" : "Click to select \xB7 drag to reorder or move",
2707
3407
  onPointerDown: (e) => {
2708
3408
  if (preview || e.button !== 0) return;
2709
3409
  selectThisBlock(e);
@@ -2711,6 +3411,10 @@ function BlockItem({
2711
3411
  onDragStart: (e) => {
2712
3412
  if (preview) return;
2713
3413
  e.stopPropagation();
3414
+ if (dragAsRow && beginRowDrag) {
3415
+ beginRowDrag(e);
3416
+ return;
3417
+ }
2714
3418
  e.dataTransfer.effectAllowed = "move";
2715
3419
  e.dataTransfer.setData(
2716
3420
  "application/json",
@@ -2782,7 +3486,9 @@ function Cell({
2782
3486
  onDeleteContent,
2783
3487
  onContextMenu,
2784
3488
  preview,
2785
- C
3489
+ C,
3490
+ dragAsRow = false,
3491
+ beginRowDrag
2786
3492
  }) {
2787
3493
  const [insertAt, setInsertAt] = (0, import_react.useState)(null);
2788
3494
  const cellRef = (0, import_react.useRef)(null);
@@ -2797,6 +3503,14 @@ function Cell({
2797
3503
  return els.length;
2798
3504
  }, [blocks.length]);
2799
3505
  const handleDragOver = (e) => {
3506
+ try {
3507
+ const raw = e.dataTransfer.getData("application/json");
3508
+ if (raw) {
3509
+ const data = JSON.parse(raw);
3510
+ if (data.moveRowId) return;
3511
+ }
3512
+ } catch {
3513
+ }
2800
3514
  e.preventDefault();
2801
3515
  e.stopPropagation();
2802
3516
  setInsertAt(calcIdx(e.clientY));
@@ -2812,6 +3526,7 @@ function Cell({
2812
3526
  setInsertAt(null);
2813
3527
  try {
2814
3528
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
3529
+ if (data.moveRowId) return;
2815
3530
  if (data.contentType) {
2816
3531
  onDropContent(rowId, cellIdx, { kind: "new", contentType: data.contentType, insertAt: idx, nested: nestedPayload });
2817
3532
  } else if (data.moveContent) {
@@ -2891,7 +3606,9 @@ function Cell({
2891
3606
  onContextMenu,
2892
3607
  preview,
2893
3608
  C,
2894
- Line
3609
+ Line,
3610
+ dragAsRow: dragAsRow && blocks.length === 1 && ci === 0,
3611
+ beginRowDrag
2895
3612
  }
2896
3613
  ) }, cb.id))
2897
3614
  ]
@@ -2912,7 +3629,8 @@ function LayoutRow({
2912
3629
  onLayoutContextMenu,
2913
3630
  setSelectedRowId,
2914
3631
  preview = false,
2915
- rowDrag,
3632
+ rootContentRow = false,
3633
+ beginRowDrag,
2916
3634
  C
2917
3635
  }) {
2918
3636
  const rowStyle = {
@@ -2944,16 +3662,9 @@ function LayoutRow({
2944
3662
  onContextMenu: preview || !onLayoutContextMenu ? void 0 : (e) => {
2945
3663
  onLayoutContextMenu(e, row.id);
2946
3664
  },
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
- },
3665
+ style: rowStyle,
2955
3666
  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] || {};
3667
+ const cS = getColumnPropsAt(row, ci);
2957
3668
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2958
3669
  "div",
2959
3670
  {
@@ -2989,7 +3700,9 @@ function LayoutRow({
2989
3700
  onDeleteContent,
2990
3701
  onContextMenu,
2991
3702
  preview,
2992
- C
3703
+ C,
3704
+ dragAsRow: rootContentRow && ci === 0,
3705
+ beginRowDrag
2993
3706
  }
2994
3707
  )
2995
3708
  },
@@ -3006,10 +3719,11 @@ var CanvasRow = ({
3006
3719
  selectedRowId,
3007
3720
  selContentMeta: rowSelContentMeta,
3008
3721
  dragOver,
3722
+ draggingRowId,
3009
3723
  C,
3010
3724
  setDragOver,
3011
3725
  handleRowDrop,
3012
- setDraggingRowId,
3726
+ setRowDrag,
3013
3727
  setSelectedRowId,
3014
3728
  setSelContentId,
3015
3729
  setSelMeta,
@@ -3022,54 +3736,161 @@ var CanvasRow = ({
3022
3736
  }) => {
3023
3737
  const rowSelected = selectedRowId === row.id;
3024
3738
  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" }
3739
+ const dropH = dragOver === ri || dragOver === ri + 1 ? 28 : 10;
3740
+ const rowBodyRef = (0, import_react.useRef)(null);
3741
+ const rootContentRow = isRootContentRow(row);
3742
+ const beginRowDrag = (0, import_react.useCallback)(
3743
+ (e) => {
3744
+ e.stopPropagation();
3745
+ e.dataTransfer.effectAllowed = "move";
3746
+ e.dataTransfer.setData(
3747
+ "application/json",
3748
+ JSON.stringify({ moveRowId: row.id })
3749
+ );
3750
+ setRowDrag(row.id);
3751
+ },
3752
+ [row.id, setRowDrag]
3753
+ );
3754
+ const rowDragOver = (0, import_react.useCallback)(
3755
+ (e) => {
3756
+ if (!draggingRowId || draggingRowId === row.id) return;
3757
+ e.preventDefault();
3758
+ e.stopPropagation();
3759
+ const el = rowBodyRef.current;
3760
+ if (!el) {
3761
+ setDragOver(ri);
3762
+ return;
3040
3763
  }
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
3764
+ const rect = el.getBoundingClientRect();
3765
+ setDragOver(e.clientY < rect.top + rect.height / 2 ? ri : ri + 1);
3766
+ },
3767
+ [draggingRowId, row.id, ri, setDragOver]
3768
+ );
3769
+ const rowDrop = (0, import_react.useCallback)(
3770
+ (e) => {
3771
+ if (!draggingRowId) return;
3772
+ e.preventDefault();
3773
+ e.stopPropagation();
3774
+ const el = rowBodyRef.current;
3775
+ let targetIdx = ri;
3776
+ if (el) {
3777
+ const rect = el.getBoundingClientRect();
3778
+ targetIdx = e.clientY < rect.top + rect.height / 2 ? ri : ri + 1;
3070
3779
  }
3071
- ) }) })
3072
- ] });
3780
+ handleRowDrop(targetIdx, e);
3781
+ },
3782
+ [draggingRowId, ri, handleRowDrop]
3783
+ );
3784
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3785
+ "div",
3786
+ {
3787
+ id: rid(`canvas-row-${row.id}`),
3788
+ style: { display: "flex", alignItems: "stretch", gap: 0, margin: "2px 0" },
3789
+ children: [
3790
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3791
+ "div",
3792
+ {
3793
+ draggable: true,
3794
+ title: "Drag to reorder section",
3795
+ onDragStart: beginRowDrag,
3796
+ onDragEnd: () => setRowDrag(null),
3797
+ onPointerDown: (e) => e.stopPropagation(),
3798
+ onClick: (e) => {
3799
+ e.stopPropagation();
3800
+ setSelectedRowId(row.id);
3801
+ setSelContentId(null);
3802
+ setSelMeta(null);
3803
+ },
3804
+ onContextMenu: (e) => {
3805
+ e.preventDefault();
3806
+ e.stopPropagation();
3807
+ handleLayoutContextMenu(e, row.id);
3808
+ },
3809
+ style: {
3810
+ width: 20,
3811
+ flexShrink: 0,
3812
+ cursor: "grab",
3813
+ display: "flex",
3814
+ alignItems: "center",
3815
+ justifyContent: "center",
3816
+ color: rowSelected ? C.accent : C.muted,
3817
+ fontSize: 12,
3818
+ fontWeight: 700,
3819
+ letterSpacing: -3,
3820
+ userSelect: "none",
3821
+ opacity: rowSelected ? 1 : 0.5
3822
+ },
3823
+ children: "\u22EE\u22EE"
3824
+ }
3825
+ ),
3826
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
3827
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3828
+ "div",
3829
+ {
3830
+ id: rid(`row-drop-before-${ri}`),
3831
+ onDragOver: (e) => {
3832
+ e.preventDefault();
3833
+ e.stopPropagation();
3834
+ setDragOver(ri);
3835
+ },
3836
+ onDragLeave: () => setDragOver(null),
3837
+ onDrop: (e) => handleRowDrop(ri, e),
3838
+ style: {
3839
+ height: dropH,
3840
+ background: dragOver === ri ? `${C.accent}25` : "transparent",
3841
+ border: dragOver === ri ? `2px dashed ${C.accent}` : "2px solid transparent",
3842
+ borderRadius: 4,
3843
+ transition: "all .12s",
3844
+ boxSizing: "border-box"
3845
+ }
3846
+ }
3847
+ ),
3848
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3849
+ "div",
3850
+ {
3851
+ ref: rowBodyRef,
3852
+ id: rid(`row-body-${row.id}`),
3853
+ onDragOver: rowDragOver,
3854
+ onDrop: rowDrop,
3855
+ style: {
3856
+ position: "relative",
3857
+ width: "100%",
3858
+ minWidth: 0,
3859
+ boxSizing: "border-box",
3860
+ outline: draggingRowId && draggingRowId !== row.id && (dragOver === ri || dragOver === ri + 1) ? `2px dashed ${C.accent}` : void 0,
3861
+ outlineOffset: 2,
3862
+ borderRadius: 6
3863
+ },
3864
+ 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)(
3865
+ LayoutRow,
3866
+ {
3867
+ editorId,
3868
+ row,
3869
+ selected: rowSelected,
3870
+ setSelectedRowId,
3871
+ onClick: () => {
3872
+ setSelectedRowId(row.id);
3873
+ setSelContentId(null);
3874
+ setSelMeta(null);
3875
+ },
3876
+ selectedContentKey: selContentId,
3877
+ selContentMeta: rowSelContentMeta,
3878
+ onSelectContent: selectContent,
3879
+ onDropContent: dropContent,
3880
+ onDeleteContent: deleteContent,
3881
+ onContextMenu: handleContextMenu,
3882
+ onLayoutContextMenu: handleLayoutContextMenu,
3883
+ rootContentRow,
3884
+ beginRowDrag,
3885
+ C
3886
+ }
3887
+ ) })
3888
+ }
3889
+ )
3890
+ ] })
3891
+ ]
3892
+ }
3893
+ );
3073
3894
  };
3074
3895
 
3075
3896
  // src/ReactEmailEditor.tsx
@@ -3150,6 +3971,10 @@ function paddingToCss(pad3, fallback) {
3150
3971
  var FONT_SIZE_PX = [10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28, 32, 36, 42, 48, 56, 64, 72];
3151
3972
  var LINE_HEIGHT_OPTS = ["1", "1.15", "1.25", "1.5", "1.65", "1.75", "2", "2.5"];
3152
3973
  var LETTER_SPACING_OPTS = ["-0.5px", "0px", "0.5px", "1px", "1.5px", "2px", "3px", "4px"];
3974
+ function rgbChannelToHex(n) {
3975
+ const v = Math.max(0, Math.min(255, Math.round(n)));
3976
+ return v.toString(16).padStart(2, "0");
3977
+ }
3153
3978
  function hexForColorInput(color) {
3154
3979
  if (!color || typeof color !== "string") return "#000000";
3155
3980
  const c = color.trim();
@@ -3162,8 +3987,34 @@ function hexForColorInput(color) {
3162
3987
  }
3163
3988
  return c.slice(0, 7);
3164
3989
  }
3990
+ const rgb = c.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i);
3991
+ if (rgb) {
3992
+ return `#${rgbChannelToHex(+rgb[1])}${rgbChannelToHex(+rgb[2])}${rgbChannelToHex(+rgb[3])}`;
3993
+ }
3165
3994
  return "#000000";
3166
3995
  }
3996
+ function textStyleColorFromEditor(editor) {
3997
+ const ts = editor.getAttributes("textStyle");
3998
+ if (ts.color && String(ts.color).trim()) return String(ts.color).trim();
3999
+ const stored = editor.state.storedMarks;
4000
+ if (stored) {
4001
+ for (const m of stored) {
4002
+ if (m.type.name === "textStyle" && m.attrs.color) {
4003
+ return String(m.attrs.color).trim();
4004
+ }
4005
+ }
4006
+ }
4007
+ const { from, to } = editor.state.selection;
4008
+ if (from === to) return null;
4009
+ const domAt = editor.view.domAtPos(from);
4010
+ let el = domAt.node.nodeType === Node.TEXT_NODE ? domAt.node.parentElement : domAt.node;
4011
+ while (el && el !== editor.view.dom) {
4012
+ const inline = el.style?.color?.trim();
4013
+ if (inline) return inline;
4014
+ el = el.parentElement;
4015
+ }
4016
+ return null;
4017
+ }
3167
4018
  function parsePxSize(raw) {
3168
4019
  if (!raw) return "";
3169
4020
  const s = String(raw).trim();
@@ -3230,13 +4081,22 @@ function toolbarIconBtn(C, active) {
3230
4081
  }
3231
4082
  function ToolbarTypographyControls({ editor, C }) {
3232
4083
  const sel = toolbarSelect(C);
3233
- const ts = editor.getAttributes("textStyle");
4084
+ const { ts, align, textColor } = (0, import_react3.useEditorState)({
4085
+ editor,
4086
+ selector: ({ editor: ed }) => {
4087
+ const attrs = ed.getAttributes("textStyle");
4088
+ return {
4089
+ ts: attrs,
4090
+ align: currentBlockTextAlign(ed),
4091
+ textColor: textStyleColorFromEditor(ed)
4092
+ };
4093
+ }
4094
+ });
3234
4095
  const fontFamily = ts.fontFamily || "";
3235
4096
  const sizeVal = parsePxSize(ts.fontSize);
3236
4097
  const lhVal = normalizeLineHeightAttr(ts.lineHeight);
3237
4098
  const lsVal = normalizeLetterSpacingAttr(ts.letterSpacing);
3238
4099
  const lsSelectValue = LETTER_SPACING_OPTS.includes(lsVal) ? lsVal : lsVal || "";
3239
- const align = currentBlockTextAlign(editor);
3240
4100
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3241
4101
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3242
4102
  "select",
@@ -3319,9 +4179,10 @@ function ToolbarTypographyControls({ editor, C }) {
3319
4179
  {
3320
4180
  type: "color",
3321
4181
  title: "Text color",
3322
- value: hexForColorInput(ts.color),
4182
+ value: hexForColorInput(textColor ?? ts.color),
3323
4183
  onChange: (e) => {
3324
- void editor.chain().focus().setColor(e.target.value).run();
4184
+ const hex = e.target.value;
4185
+ void editor.chain().focus().setColor(hex).run();
3325
4186
  },
3326
4187
  style: {
3327
4188
  width: 28,
@@ -3694,7 +4555,7 @@ function TextRichEditor({
3694
4555
  }
3695
4556
  }),
3696
4557
  import_extension_text_style.TextStyle,
3697
- import_extension_text_style.Color,
4558
+ import_extension_text_style.Color.configure({ types: ["textStyle"] }),
3698
4559
  import_extension_text_style.FontFamily,
3699
4560
  import_extension_text_style.FontSize,
3700
4561
  import_extension_text_style.LineHeight,
@@ -4871,11 +5732,141 @@ function BlockSurfaceBgInspector({
4871
5732
  ] }) : null
4872
5733
  ] });
4873
5734
  }
5735
+ function HtmlBlockRichPanel({
5736
+ block,
5737
+ onChange,
5738
+ C
5739
+ }) {
5740
+ const p = block.props;
5741
+ const { IS } = useIS(C);
5742
+ const [mode, setMode] = (0, import_react4.useState)("visual");
5743
+ const [htmlDraft, setHtmlDraft] = (0, import_react4.useState)("");
5744
+ (0, import_react4.useEffect)(() => {
5745
+ setMode("visual");
5746
+ }, [block.id]);
5747
+ const body = normalizeRichHtmlForStorage(typeof block.content === "string" ? block.content : "");
5748
+ const applyHtml = (html) => {
5749
+ const norm = normalizeRichHtmlForStorage(html);
5750
+ onChange({ ...block, content: norm, props: { ...p, content: norm } });
5751
+ };
5752
+ const tabBtn = (active) => ({
5753
+ flex: 1,
5754
+ padding: "7px 10px",
5755
+ borderRadius: 6,
5756
+ border: `1px solid ${C.border}`,
5757
+ background: active ? C.accent : C.surface,
5758
+ color: active ? "#ffffff" : C.text,
5759
+ fontSize: 12,
5760
+ fontWeight: 700,
5761
+ cursor: "pointer"
5762
+ });
5763
+ 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: [
5764
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
5765
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5766
+ "button",
5767
+ {
5768
+ type: "button",
5769
+ style: tabBtn(mode === "visual"),
5770
+ onClick: () => {
5771
+ if (mode === "html") applyHtml(htmlDraft);
5772
+ setMode("visual");
5773
+ },
5774
+ children: "Rich text"
5775
+ }
5776
+ ),
5777
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5778
+ "button",
5779
+ {
5780
+ type: "button",
5781
+ style: tabBtn(mode === "html"),
5782
+ onClick: () => {
5783
+ setHtmlDraft(body);
5784
+ setMode("html");
5785
+ },
5786
+ children: "HTML"
5787
+ }
5788
+ )
5789
+ ] }),
5790
+ mode === "visual" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5791
+ TextRichEditor,
5792
+ {
5793
+ value: body,
5794
+ onChange: applyHtml,
5795
+ typography: {
5796
+ fontSize: p.fontSize,
5797
+ color: p.color,
5798
+ align: p.align,
5799
+ fontFamily: p.fontFamily,
5800
+ lineHeight: p.lineHeight,
5801
+ letterSpacing: p.letterSpacing,
5802
+ padding: p.padding
5803
+ },
5804
+ placeholder: "Write your rich content\u2026",
5805
+ headerTitle: "Rich editor",
5806
+ C
5807
+ },
5808
+ block.id
5809
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
5810
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5811
+ "textarea",
5812
+ {
5813
+ spellCheck: false,
5814
+ value: htmlDraft,
5815
+ onChange: (e) => setHtmlDraft(e.target.value),
5816
+ placeholder: "<p>Your HTML\u2026</p>",
5817
+ style: {
5818
+ ...IS,
5819
+ minHeight: 220,
5820
+ resize: "vertical",
5821
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
5822
+ fontSize: 12,
5823
+ lineHeight: 1.45
5824
+ }
5825
+ }
5826
+ ),
5827
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: 10, color: C.muted, lineHeight: 1.45 }, children: [
5828
+ "Raw HTML. Switch to ",
5829
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Rich text" }),
5830
+ " to apply and preview in the visual editor."
5831
+ ] }),
5832
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5833
+ "button",
5834
+ {
5835
+ type: "button",
5836
+ onClick: () => applyHtml(htmlDraft),
5837
+ style: {
5838
+ alignSelf: "flex-start",
5839
+ background: C.surface,
5840
+ border: `1px solid ${C.border}`,
5841
+ color: C.accent,
5842
+ borderRadius: 5,
5843
+ cursor: "pointer",
5844
+ fontSize: 11,
5845
+ padding: "6px 12px",
5846
+ fontWeight: 600
5847
+ },
5848
+ children: "Apply (normalize)"
5849
+ }
5850
+ )
5851
+ ] })
5852
+ ] }) });
5853
+ }
4874
5854
  function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4875
5855
  const { IS, CI } = useIS(C);
4876
5856
  const imageFileRef = (0, import_react4.useRef)(null);
4877
5857
  const p = block.props;
4878
- const set = (k, v) => onChange({ ...block, props: { ...p, [k]: v } });
5858
+ const set = (k, v) => {
5859
+ if (block.type === "html" && k === "content") {
5860
+ onChange({ ...block, content: v, props: { ...p, content: v } });
5861
+ return;
5862
+ }
5863
+ if (block.type === "html") {
5864
+ const body = typeof block.content === "string" ? block.content : typeof p.content === "string" ? p.content : "";
5865
+ onChange({ ...block, props: { ...p, [k]: v, content: body } });
5866
+ return;
5867
+ }
5868
+ onChange({ ...block, props: { ...p, [k]: v } });
5869
+ };
4879
5870
  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
5871
  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
5872
  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: [
@@ -4969,25 +5960,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4969
5960
  ] });
4970
5961
  case "html":
4971
5962
  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),
5963
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HtmlBlockRichPanel, { block, onChange, C }),
4991
5964
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
4992
5965
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4993
5966
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
@@ -5007,8 +5980,25 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5007
5980
  "Object position"
5008
5981
  ] }), 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
5982
  Txt("alt", "Alt Text"),
5010
- Txt("link", "Link URL"),
5011
- Sel("linkTarget", "Open In", ["_blank", "_self"]),
5983
+ /* @__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: [
5984
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5985
+ "input",
5986
+ {
5987
+ type: "checkbox",
5988
+ checked: !!(p.linkEnabled ?? (p.link && String(p.link).trim())),
5989
+ onChange: (e) => {
5990
+ if (e.target.checked) set("linkEnabled", true);
5991
+ else onChange({ ...block, props: { ...p, linkEnabled: false, link: "" } });
5992
+ },
5993
+ style: { width: 15, height: 15, accentColor: C.accent }
5994
+ }
5995
+ ),
5996
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: C.muted, fontSize: 12 }, children: p.linkEnabled ?? (p.link && String(p.link).trim()) ? "On" : "Off" })
5997
+ ] }) }),
5998
+ p.linkEnabled ?? (p.link && String(p.link).trim()) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5999
+ Txt("link", "Link URL"),
6000
+ Sel("linkTarget", "Open In", ["_blank", "_self"])
6001
+ ] }) : null,
5012
6002
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right"], C }),
5013
6003
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BorderRadiusEditor, { value: p.borderRadius, onChange: (br) => set("borderRadius", br), C }),
5014
6004
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
@@ -5358,7 +6348,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5358
6348
  bgPosition: p.bgPosition ?? "center",
5359
6349
  bgGradient: p.bgGradient ?? null,
5360
6350
  cells: p.cells || [],
5361
- columnStyles: p.columnStyles
6351
+ columns: getColumns(p)
5362
6352
  },
5363
6353
  onChange: (row) => onChange({
5364
6354
  ...block,
@@ -5376,7 +6366,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5376
6366
  bgGradient: row.bgGradient,
5377
6367
  ratios: [...row.ratios || [1]],
5378
6368
  cells: row.cells || [],
5379
- columnStyles: row.columnStyles
6369
+ columns: row.columns ?? getColumns(row)
5380
6370
  }
5381
6371
  }),
5382
6372
  onClose,
@@ -5395,8 +6385,8 @@ function inferLayoutRowBgStep(r) {
5395
6385
  if (String(r.bgColor || "").trim()) return "solid";
5396
6386
  return null;
5397
6387
  }
5398
- function inferColumnBgStep(columnStyles, selCol) {
5399
- const cs = (columnStyles || {})[selCol] || {};
6388
+ function inferColumnBgStep(container, selCol) {
6389
+ const cs = getColumnPropsAt(container, selCol);
5400
6390
  if (cs.bgGradient) return "gradient";
5401
6391
  if (String(cs.bgImage || "").trim()) return "image";
5402
6392
  if (String(cs.bgColor || "").trim()) return "solid";
@@ -5408,6 +6398,10 @@ function LayoutRowEditor({
5408
6398
  onClose,
5409
6399
  onUpload,
5410
6400
  initialCol = null,
6401
+ rowIndex = -1,
6402
+ rowCount = 1,
6403
+ onMoveUp,
6404
+ onMoveDown,
5411
6405
  C,
5412
6406
  /** Shown under the inspector header when editing a Layout block */
5413
6407
  customizationHint
@@ -5420,8 +6414,8 @@ function LayoutRowEditor({
5420
6414
  const [colBgPickerMode, setColBgPickerMode] = (0, import_react4.useState)(null);
5421
6415
  const inferredRowBg = (0, import_react4.useMemo)(() => inferLayoutRowBgStep(row), [row.bgGradient, row.bgImage, row.bgColor]);
5422
6416
  const inferredColBg = (0, import_react4.useMemo)(
5423
- () => inferColumnBgStep(row.columnStyles, selCol),
5424
- [row.columnStyles, selCol]
6417
+ () => inferColumnBgStep(row, selCol),
6418
+ [row.columns, row.columnStyles, selCol]
5425
6419
  );
5426
6420
  const rowBgUiStep = inferredRowBg ?? rowBgPickerMode;
5427
6421
  const colBgUiStep = inferredColBg ?? colBgPickerMode;
@@ -5460,13 +6454,12 @@ function LayoutRowEditor({
5460
6454
  onChange({ ...row, cols: n, preset: "custom", cells, ratios });
5461
6455
  };
5462
6456
  const updCol = (i, upd) => {
5463
- const styles = { ...row.columnStyles || {} };
5464
- styles[i] = { ...styles[i] || {}, ...upd };
5465
- set("columnStyles", styles);
6457
+ const columns = patchColumnPropsAt(row, i, upd);
6458
+ onChange(withColumnsOnly(row, columns));
5466
6459
  };
5467
6460
  const setColBgMode = (mode) => {
5468
6461
  setColBgPickerMode(mode);
5469
- const cs = (row.columnStyles || {})[selCol] || {};
6462
+ const cs = getColumnPropsAt(row, selCol);
5470
6463
  if (mode === "solid") {
5471
6464
  updCol(selCol, { bgGradient: null, bgImage: "" });
5472
6465
  return;
@@ -5498,7 +6491,51 @@ function LayoutRowEditor({
5498
6491
  set("bgGradient", { angle: 135, colors: ["#0ea5e9", "#6366f1", "#ec4899"], stops: [0, 50, 100] });
5499
6492
  }
5500
6493
  };
6494
+ const canMoveUp = rowIndex > 0;
6495
+ const canMoveDown = rowIndex >= 0 && rowIndex < rowCount - 1;
5501
6496
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
6497
+ onMoveUp && onMoveDown ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 6, marginBottom: 12 }, children: [
6498
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
6499
+ "button",
6500
+ {
6501
+ type: "button",
6502
+ disabled: !canMoveUp,
6503
+ onClick: onMoveUp,
6504
+ style: {
6505
+ flex: 1,
6506
+ padding: "8px 10px",
6507
+ fontSize: 12,
6508
+ fontWeight: 600,
6509
+ borderRadius: 8,
6510
+ border: `1px solid ${C.border}`,
6511
+ background: canMoveUp ? C.surface : C.canvas,
6512
+ color: canMoveUp ? C.text : C.muted,
6513
+ cursor: canMoveUp ? "pointer" : "not-allowed"
6514
+ },
6515
+ children: "\u2191 Move section up"
6516
+ }
6517
+ ),
6518
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
6519
+ "button",
6520
+ {
6521
+ type: "button",
6522
+ disabled: !canMoveDown,
6523
+ onClick: onMoveDown,
6524
+ style: {
6525
+ flex: 1,
6526
+ padding: "8px 10px",
6527
+ fontSize: 12,
6528
+ fontWeight: 600,
6529
+ borderRadius: 8,
6530
+ border: `1px solid ${C.border}`,
6531
+ background: canMoveDown ? C.surface : C.canvas,
6532
+ color: canMoveDown ? C.text : C.muted,
6533
+ cursor: canMoveDown ? "pointer" : "not-allowed"
6534
+ },
6535
+ children: "\u2193 Move section down"
6536
+ }
6537
+ )
6538
+ ] }) : null,
5502
6539
  customizationHint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5503
6540
  "div",
5504
6541
  {
@@ -5648,15 +6685,15 @@ function LayoutRowEditor({
5648
6685
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5649
6686
  ColorSwatchGrid,
5650
6687
  {
5651
- value: (row.columnStyles || {})[selCol]?.bgColor || "",
6688
+ value: getColumnPropsAt(row, selCol)?.bgColor || "",
5652
6689
  C,
5653
6690
  compact: true,
5654
6691
  onPick: (hex) => updCol(selCol, { bgColor: hex === "#ffffff" ? "" : hex })
5655
6692
  }
5656
6693
  ),
5657
6694
  /* @__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 }) })
6695
+ /* @__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 }) }),
6696
+ /* @__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
6697
  ] })
5661
6698
  ] }) }) : null,
5662
6699
  colBgUiStep === "image" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
@@ -5664,7 +6701,7 @@ function LayoutRowEditor({
5664
6701
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Image, { size: 13, strokeWidth: 2.25, "aria-hidden": true }),
5665
6702
  "bg image"
5666
6703
  ] }), 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 }) }),
6704
+ /* @__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
6705
  onUpload && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { marginTop: 0 }, children: [
5669
6706
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5670
6707
  "input",
@@ -5702,18 +6739,18 @@ function LayoutRowEditor({
5702
6739
  ] })
5703
6740
  ] }) }),
5704
6741
  /* @__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: [
6742
+ /* @__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
6743
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "auto", children: "Auto" }),
5707
6744
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "cover", children: "Cover" }),
5708
6745
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "contain", children: "Contain" })
5709
6746
  ] }) }) }),
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: [
6747
+ /* @__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
6748
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "no-repeat", children: "No repeat" }),
5712
6749
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "repeat", children: "Repeat" }),
5713
6750
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "repeat-x", children: "Repeat X" }),
5714
6751
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "repeat-y", children: "Repeat Y" })
5715
6752
  ] }) }) }),
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)) }) }) })
6753
+ /* @__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
6754
  ] })
5718
6755
  ] }) : null,
5719
6756
  colBgUiStep === "gradient" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("details", { open: true, style: { marginBottom: 12 }, children: [
@@ -5722,15 +6759,15 @@ function LayoutRowEditor({
5722
6759
  DynamicGradientField,
5723
6760
  {
5724
6761
  label: "gradient",
5725
- value: (row.columnStyles || {})[selCol]?.bgGradient,
6762
+ value: getColumnPropsAt(row, selCol)?.bgGradient,
5726
6763
  onChange: (g) => updCol(selCol, { bgGradient: g }),
5727
6764
  defaults: { colors: ["#0ea5e9", "#6366f1", "#ec4899"] },
5728
6765
  C
5729
6766
  }
5730
6767
  ) })
5731
6768
  ] }) : 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 })
6769
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaddingEditor, { label: "Column padding", value: getColumnPropsAt(row, selCol).padding, onChange: (pad3) => updCol(selCol, { padding: pad3 }), C }),
6770
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BorderRadiusEditor, { label: "Column radius", value: getColumnPropsAt(row, selCol).borderRadius, onChange: (br) => updCol(selCol, { borderRadius: br }), C })
5734
6771
  ] })
5735
6772
  ] })
5736
6773
  ] });
@@ -6496,8 +7533,8 @@ var import_jsx_runtime11 = require("react/jsx-runtime");
6496
7533
  var RIGHT_RAIL_DEFAULT_W = 328;
6497
7534
  var RIGHT_RAIL_MIN_W = 260;
6498
7535
  var RIGHT_RAIL_MAX_W = 720;
6499
- var ReactEmailEditor = (0, import_react8.forwardRef)(
6500
- function ReactEmailEditor2({
7536
+ var ReactEmailEditorComponent = (0, import_react8.forwardRef)(
7537
+ function ReactEmailEditor({
6501
7538
  minHeight = "100vh",
6502
7539
  editorId = "email-editor",
6503
7540
  onLoad,
@@ -6521,6 +7558,11 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6521
7558
  const [selContentMeta, setSelMeta] = (0, import_react8.useState)(null);
6522
7559
  const [dragOver, setDragOver] = (0, import_react8.useState)(null);
6523
7560
  const [draggingRowId, setDraggingRowId] = (0, import_react8.useState)(null);
7561
+ const draggingRowIdRef = (0, import_react8.useRef)(null);
7562
+ const setRowDrag = (0, import_react8.useCallback)((id) => {
7563
+ draggingRowIdRef.current = id;
7564
+ setDraggingRowId(id);
7565
+ }, []);
6524
7566
  const [history, setHistory] = (0, import_react8.useState)([]);
6525
7567
  const [future, setFuture] = (0, import_react8.useState)([]);
6526
7568
  const [showPreviewModal, setShowPreview] = (0, import_react8.useState)(false);
@@ -6555,10 +7597,17 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6555
7597
  });
6556
7598
  const [pageBgUiStep, setPageBgUiStep] = (0, import_react8.useState)(null);
6557
7599
  const [contentBgUiStep, setContentBgUiStep] = (0, import_react8.useState)(null);
7600
+ const pageBgLastSolidRef = (0, import_react8.useRef)("#f1f5f9");
7601
+ const pageBgIsOff = !String(settings.bgColor ?? "").trim() && !String(settings.bgImage ?? "").trim() && !settings.bgGradient;
6558
7602
  const applyPageBgMode = (mode) => {
6559
7603
  setPageBgUiStep(mode);
6560
7604
  if (mode === "solid") {
6561
- setSettings((s) => ({ ...s, bgGradient: null, bgImage: "" }));
7605
+ setSettings((s) => {
7606
+ const next = { ...s, bgGradient: null, bgImage: "" };
7607
+ const raw = typeof next.bgColor === "string" ? next.bgColor.trim() : "";
7608
+ if (!raw) next.bgColor = pageBgLastSolidRef.current || "#f1f5f9";
7609
+ return next;
7610
+ });
6562
7611
  return;
6563
7612
  }
6564
7613
  if (mode === "image") {
@@ -6668,7 +7717,7 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6668
7717
  return () => window.removeEventListener("keydown", handler);
6669
7718
  }, [rows, history, future, selContentMeta]);
6670
7719
  const buildApi = (0, import_react8.useCallback)(() => ({
6671
- loadJson(input) {
7720
+ loadJson(input, options2) {
6672
7721
  setJsonLoading(true);
6673
7722
  requestAnimationFrame(() => {
6674
7723
  requestAnimationFrame(() => {
@@ -6681,7 +7730,14 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6681
7730
  return;
6682
7731
  }
6683
7732
  }
6684
- const norm = normalizeEmailDesignInput(parsed);
7733
+ const coerced = coerceEmailDocumentInput(parsed);
7734
+ if (options2?.mode === "append") {
7735
+ const extra = emailDocumentToEditorRows(coerced ?? parsed);
7736
+ if (!extra?.length) return;
7737
+ setRows((prev) => [...prev, ...extra]);
7738
+ return;
7739
+ }
7740
+ const norm = normalizeEmailDesignInput(coerced ?? parsed);
6685
7741
  if (!norm) return;
6686
7742
  setRows(norm.rows);
6687
7743
  const ns = norm.settings;
@@ -6718,6 +7774,8 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6718
7774
  }, []);
6719
7775
  const handleRowDrop = (targetIdx, e) => {
6720
7776
  if (e) {
7777
+ e.preventDefault();
7778
+ e.stopPropagation();
6721
7779
  try {
6722
7780
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
6723
7781
  if (data.layoutPresetKey) {
@@ -6736,18 +7794,28 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6736
7794
  } catch {
6737
7795
  }
6738
7796
  }
6739
- if (draggingRowId) {
7797
+ const moveId = draggingRowIdRef.current ?? draggingRowId ?? (() => {
7798
+ if (!e) return null;
7799
+ try {
7800
+ const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
7801
+ return typeof data.moveRowId === "string" ? data.moveRowId : null;
7802
+ } catch {
7803
+ return null;
7804
+ }
7805
+ })();
7806
+ if (moveId) {
6740
7807
  mutate((prev) => {
6741
- const fi = prev.findIndex((r) => r.id === draggingRowId);
7808
+ const fi = prev.findIndex((r) => r.id === moveId);
6742
7809
  if (fi === -1) return prev;
6743
7810
  const next = [...prev];
6744
7811
  const [m] = next.splice(fi, 1);
6745
- next.splice(targetIdx > fi ? targetIdx - 1 : targetIdx, 0, m);
7812
+ const insertAt = targetIdx > fi ? targetIdx - 1 : targetIdx;
7813
+ next.splice(Math.max(0, Math.min(insertAt, next.length)), 0, m);
6746
7814
  return next;
6747
7815
  });
6748
7816
  }
6749
7817
  setDragOver(null);
6750
- setDraggingRowId(null);
7818
+ setRowDrag(null);
6751
7819
  };
6752
7820
  const addRow = (preset) => mutate((prev) => [...prev, makeLayoutRow(preset)]);
6753
7821
  const deleteRow = (id) => {
@@ -6877,8 +7945,27 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6877
7945
  }
6878
7946
  }
6879
7947
  };
7948
+ const rootContentPreset = LAYOUT_PRESETS.find((p) => p.cols === 1) ?? LAYOUT_PRESETS[0];
7949
+ const insertRootContentBlock = (contentType) => {
7950
+ const { row: newRow, block: nb } = makeRootContentRow(contentType, rootContentPreset);
7951
+ const anchorRowId = selContentMeta?.rowId ?? selectedRowId ?? null;
7952
+ mutate((prev) => {
7953
+ if (anchorRowId) {
7954
+ const i = prev.findIndex((r) => r.id === anchorRowId);
7955
+ if (i >= 0) {
7956
+ const next = [...prev];
7957
+ next.splice(i + 1, 0, newRow);
7958
+ return next;
7959
+ }
7960
+ }
7961
+ return [...prev, newRow];
7962
+ });
7963
+ setSelectedRowId(null);
7964
+ setSelContentId(nb.id);
7965
+ setSelMeta({ rowId: newRow.id, cellIdx: 0, contentIdx: 0 });
7966
+ };
6880
7967
  const insertBlockFromLibrary = (contentType) => {
6881
- if (!rows.length) return;
7968
+ const isLayoutType = contentType === "layout" || contentType === "nestedRow";
6882
7969
  if (selContentMeta?.inner) {
6883
7970
  const { rowId, cellIdx, contentIdx, inner } = selContentMeta;
6884
7971
  dropContent(rowId, cellIdx, {
@@ -6889,30 +7976,26 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6889
7976
  });
6890
7977
  return;
6891
7978
  }
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;
7979
+ if (isLayoutType) {
7980
+ if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0 && typeof selContentMeta.contentIdx === "number" && selContentMeta.contentIdx >= 0) {
7981
+ const r = rows.find((x) => x.id === selContentMeta.rowId);
7982
+ if (r && selContentMeta.cellIdx < r.cells.length) {
7983
+ dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
7984
+ kind: "new",
7985
+ contentType,
7986
+ insertAt: null
7987
+ });
7988
+ return;
7989
+ }
6901
7990
  }
7991
+ addRow(rootContentPreset);
7992
+ return;
6902
7993
  }
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
- }
7994
+ if (!rows.length) {
7995
+ insertRootContentBlock(contentType);
7996
+ return;
6913
7997
  }
6914
- const main = rows[0];
6915
- dropContent(main.id, 0, { kind: "new", contentType, insertAt: null });
7998
+ insertRootContentBlock(contentType);
6916
7999
  };
6917
8000
  const deleteContent = (rowId, cellIdx, ci, inner = null) => {
6918
8001
  if (!inner) {
@@ -7507,6 +8590,7 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7507
8590
  e.preventDefault();
7508
8591
  try {
7509
8592
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
8593
+ if (data.moveRowId) return;
7510
8594
  if (data.layoutPresetKey) {
7511
8595
  const preset = LAYOUT_PRESETS.find((p) => p.key === data.layoutPresetKey);
7512
8596
  if (preset) mutate((prev) => [...prev, makeLayoutRow(preset)]);
@@ -7561,6 +8645,7 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7561
8645
  e.stopPropagation();
7562
8646
  try {
7563
8647
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
8648
+ if (data.moveRowId) return;
7564
8649
  if (data.contentType && typeof data.contentType === "string") {
7565
8650
  insertBlockFromLibrary(data.contentType);
7566
8651
  }
@@ -7582,10 +8667,11 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7582
8667
  selectedRowId,
7583
8668
  selContentMeta,
7584
8669
  dragOver,
8670
+ draggingRowId,
7585
8671
  C,
7586
8672
  setDragOver,
7587
8673
  handleRowDrop,
7588
- setDraggingRowId,
8674
+ setRowDrag,
7589
8675
  setSelectedRowId,
7590
8676
  setSelContentId,
7591
8677
  setSelMeta,
@@ -7603,14 +8689,20 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7603
8689
  id: eid("canvas-row-drop-end"),
7604
8690
  onDragOver: (e) => {
7605
8691
  e.preventDefault();
8692
+ e.stopPropagation();
7606
8693
  setDragOver(rows.length);
7607
8694
  },
7608
8695
  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" }
8696
+ onDrop: (e) => handleRowDrop(rows.length, e),
8697
+ style: {
8698
+ height: dragOver === rows.length ? 28 : 10,
8699
+ background: dragOver === rows.length ? `${C.accent}25` : "transparent",
8700
+ border: dragOver === rows.length ? `2px dashed ${C.accent}` : "2px solid transparent",
8701
+ borderRadius: 4,
8702
+ transition: "all .12s",
8703
+ margin: "2px 0",
8704
+ boxSizing: "border-box"
8705
+ }
7614
8706
  }
7615
8707
  )
7616
8708
  ]
@@ -7722,6 +8814,10 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7722
8814
  onClose: () => setSelectedRowId(null),
7723
8815
  onUpload,
7724
8816
  initialCol: selContentMeta?.rowId === selectedRowId ? selContentMeta.cellIdx : null,
8817
+ rowIndex: rows.findIndex((r) => r.id === selectedRowId),
8818
+ rowCount: rows.length,
8819
+ onMoveUp: () => selectedRow && moveRow(selectedRow.id, -1),
8820
+ onMoveDown: () => selectedRow && moveRow(selectedRow.id, 1),
7725
8821
  C
7726
8822
  }
7727
8823
  ) })
@@ -7898,7 +8994,40 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7898
8994
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("summary", { style: railSectionSummary, children: "Background" }),
7899
8995
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: railSectionBody, children: [
7900
8996
  /* @__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 }),
8997
+ /* @__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: [
8998
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
8999
+ "input",
9000
+ {
9001
+ type: "checkbox",
9002
+ checked: pageBgIsOff,
9003
+ onChange: (e) => {
9004
+ const off = e.target.checked;
9005
+ if (off) {
9006
+ const cur = String(settings.bgColor ?? "").trim();
9007
+ if (cur) pageBgLastSolidRef.current = cur;
9008
+ setPageBgUiStep(null);
9009
+ setSettings((s) => ({ ...s, bgColor: "", bgImage: "", bgGradient: null }));
9010
+ } else {
9011
+ setPageBgUiStep("solid");
9012
+ setSettings((s) => ({
9013
+ ...s,
9014
+ bgColor: pageBgLastSolidRef.current || "#f1f5f9"
9015
+ }));
9016
+ }
9017
+ },
9018
+ style: { width: 15, height: 15, accentColor: C.accent, cursor: "pointer" }
9019
+ }
9020
+ ),
9021
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { color: C.muted, fontSize: 12 }, children: "No page background" })
9022
+ ] }) }),
9023
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
9024
+ BgModeButtons,
9025
+ {
9026
+ value: pageBgIsOff ? null : pageBgUiStep,
9027
+ onChange: applyPageBgMode,
9028
+ C
9029
+ }
9030
+ ),
7902
9031
  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
9032
  pageBgUiStep === "solid" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7904
9033
  ColorField,
@@ -7908,7 +9037,10 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7908
9037
  "bg color"
7909
9038
  ] }),
7910
9039
  value: settings.bgColor,
7911
- onChange: (v) => setSettings((s) => ({ ...s, bgColor: v })),
9040
+ onChange: (v) => {
9041
+ pageBgLastSolidRef.current = v;
9042
+ setSettings((s) => ({ ...s, bgColor: v }));
9043
+ },
7912
9044
  placeholder: "#f1f5f9",
7913
9045
  C
7914
9046
  }
@@ -8149,80 +9281,18 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
8149
9281
  );
8150
9282
  }
8151
9283
  );
8152
-
8153
- // src/lib/htmlToEmailDesign.ts
8154
- var pad = (n) => ({ top: n, right: n, bottom: n, left: n });
8155
- function extractHtmlForDesign(html) {
8156
- const t = String(html ?? "").trim();
8157
- if (!t) return "";
8158
- const head = t.slice(0, 800).toLowerCase();
8159
- if (!head.includes("<html") && !head.includes("<!doctype")) {
8160
- return normalizeRichHtmlForStorage(t);
8161
- }
8162
- try {
8163
- const doc = new DOMParser().parseFromString(t, "text/html");
8164
- const body = doc.body;
8165
- if (!body) return normalizeRichHtmlForStorage(t);
8166
- return normalizeRichHtmlForStorage(body.innerHTML);
8167
- } catch {
8168
- return normalizeRichHtmlForStorage(t);
8169
- }
8170
- }
8171
- function htmlToEmailDesignTemplate(html) {
8172
- const inner = extractHtmlForDesign(html);
8173
- if (!inner) return null;
8174
- const doc = {
8175
- type: "email_document",
8176
- settings: {
8177
- width: 600,
8178
- backgroundColor: "#f1f5f9",
8179
- contentBackgroundColor: "#ffffff",
8180
- fontFamily: "Arial, Helvetica, sans-serif",
8181
- lineHeightBase: 1.6,
8182
- color: "#111827",
8183
- responsive: true,
8184
- rtl: false
8185
- },
8186
- rows: [
8187
- {
8188
- id: "row_html_import",
8189
- type: "row",
8190
- layout: { columns: 1, gap: 0, stackOnMobile: true, align: "center" },
8191
- styles: {
8192
- backgroundColor: "#ffffff",
8193
- backgroundRepeat: "no-repeat",
8194
- backgroundSize: "cover",
8195
- padding: pad(24),
8196
- textAlign: "left"
8197
- },
8198
- columns: [
8199
- {
8200
- id: "col_html_import",
8201
- layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
8202
- styles: { padding: pad(0), backgroundColor: "" },
8203
- blocks: [
8204
- {
8205
- id: "block_html_import",
8206
- type: "html",
8207
- content: { html: inner },
8208
- styles: {}
8209
- }
8210
- ]
8211
- }
8212
- ]
8213
- }
8214
- ]
8215
- };
8216
- return doc;
8217
- }
9284
+ var ReactEmailEditor2 = Object.assign(ReactEmailEditorComponent, { jsonToHtml, htmlToJson });
8218
9285
  // Annotate the CommonJS export names for ESM import in node:
8219
9286
  0 && (module.exports = {
8220
9287
  EmailPreviewModal,
8221
9288
  ReactEmailEditor,
8222
9289
  base64ToUtf8,
9290
+ canonicalizeEmailDocument,
9291
+ coerceEmailDocumentInput,
8223
9292
  emailPreviewDevices,
8224
9293
  extractHtmlForDesign,
8225
9294
  htmlToEmailDesignTemplate,
9295
+ htmlToJson,
8226
9296
  jsonToHtml,
8227
9297
  utf8ToBase64
8228
9298
  });