react-email-studio 3.4.0 → 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.js CHANGED
@@ -464,9 +464,9 @@ var DEFAULT_BLOCK_PROPS = {
464
464
  fontFamily: "Georgia,serif",
465
465
  margin: { ...BOX0 }
466
466
  },
467
+ /** Rich HTML body lives on the block root as `content` (string), not in `props`. */
467
468
  html: {
468
469
  ...BLOCK_BG,
469
- content: "<p>Rich HTML content. Edit with the rich editor.</p>",
470
470
  fontSize: 15,
471
471
  color: "#1e293b",
472
472
  align: "left",
@@ -486,6 +486,7 @@ var DEFAULT_BLOCK_PROPS = {
486
486
  align: "center",
487
487
  padding: boxAll(8),
488
488
  link: "",
489
+ linkEnabled: false,
489
490
  linkTarget: "_blank",
490
491
  borderRadius: { tl: 0, tr: 0, br: 0, bl: 0 },
491
492
  margin: { top: 0, right: 0, bottom: 0, left: 0 }
@@ -607,10 +608,82 @@ var DEFAULT_BLOCK_PROPS = {
607
608
  margin: { ...BOX0 }
608
609
  }
609
610
  };
611
+ var DEFAULT_HTML_BLOCK_MARKUP = "<p>Rich HTML content. Edit with the rich editor.</p>";
612
+ function imageLinkActive(p) {
613
+ const url = typeof p.link === "string" ? p.link.trim() : "";
614
+ const enabled = p.linkEnabled ?? !!url;
615
+ return enabled && !!url;
616
+ }
610
617
  function isKnownBlockType(type) {
611
618
  return typeof type === "string" && type in DEFAULT_BLOCK_PROPS;
612
619
  }
613
620
 
621
+ // src/lib/columnProps.ts
622
+ var BOX02 = { top: 0, right: 0, bottom: 0, left: 0 };
623
+ var DEFAULT_COLUMN_PROPS = {
624
+ bgColor: "",
625
+ bgImage: "",
626
+ bgGradient: null,
627
+ bgSize: "cover",
628
+ bgRepeat: "no-repeat",
629
+ bgPosition: "center",
630
+ padding: { ...BOX02 },
631
+ borderRadius: 0
632
+ };
633
+ function isObj(x) {
634
+ return x !== null && typeof x === "object" && !Array.isArray(x);
635
+ }
636
+ function colCountFromContainer(c) {
637
+ const cells = Array.isArray(c.cells) ? c.cells.length : 0;
638
+ const ratios = Array.isArray(c.ratios) ? c.ratios.length : 0;
639
+ const cols = typeof c.cols === "number" && c.cols > 0 ? c.cols : 0;
640
+ return Math.max(1, cells, ratios, cols);
641
+ }
642
+ function columnsFromLegacyMap(map, count) {
643
+ const n = Math.max(1, count);
644
+ const src = isObj(map) ? map : {};
645
+ return Array.from({ length: n }, (_, i) => {
646
+ const raw = src[i] ?? src[String(i)];
647
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...isObj(raw) ? raw : {} } };
648
+ });
649
+ }
650
+ function getColumns(container) {
651
+ const count = colCountFromContainer(container);
652
+ if (Array.isArray(container.columns)) {
653
+ const cols = container.columns;
654
+ return Array.from({ length: count }, (_, i) => {
655
+ const entry = cols[i];
656
+ if (isObj(entry) && isObj(entry.props)) {
657
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...entry.props } };
658
+ }
659
+ if (isObj(entry)) {
660
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...entry } };
661
+ }
662
+ return { props: { ...DEFAULT_COLUMN_PROPS } };
663
+ });
664
+ }
665
+ if (container.columnStyles) {
666
+ return columnsFromLegacyMap(container.columnStyles, count);
667
+ }
668
+ return columnsFromLegacyMap({}, count);
669
+ }
670
+ function getColumnPropsAt(container, index) {
671
+ const cols = getColumns(container);
672
+ const i = Math.max(0, Math.min(index, cols.length - 1));
673
+ return cols[i]?.props ?? { ...DEFAULT_COLUMN_PROPS };
674
+ }
675
+ function patchColumnPropsAt(container, index, patch) {
676
+ const cols = getColumns(container);
677
+ const i = Math.max(0, index);
678
+ while (cols.length <= i) cols.push({ props: { ...DEFAULT_COLUMN_PROPS } });
679
+ cols[i] = { props: { ...cols[i].props, ...patch } };
680
+ return cols;
681
+ }
682
+ function withColumnsOnly(row, columns) {
683
+ const { columnStyles: _drop, ...rest } = row;
684
+ return { ...rest, columns };
685
+ }
686
+
614
687
  // src/lib/factories.ts
615
688
  function uid() {
616
689
  return Math.random().toString(36).slice(2, 10);
@@ -650,14 +723,30 @@ function makeNestedRowBlock(preset) {
650
723
  bgRepeat: row.bgRepeat ?? "no-repeat",
651
724
  bgPosition: row.bgPosition ?? "center",
652
725
  bgGradient: row.bgGradient ?? null,
653
- columnStyles: row.columnStyles,
726
+ columns: row.columns ?? row.cells?.map(() => ({ props: { ...DEFAULT_COLUMN_PROPS } })),
654
727
  cells: row.cells
655
728
  }
656
729
  };
657
730
  }
658
731
  function makeContentBlock(type) {
732
+ if (type === "html") {
733
+ return {
734
+ id: uid(),
735
+ type,
736
+ content: DEFAULT_HTML_BLOCK_MARKUP,
737
+ props: { ...DEFAULT_BLOCK_PROPS.html, content: DEFAULT_HTML_BLOCK_MARKUP }
738
+ };
739
+ }
659
740
  return { id: uid(), type, props: { ...DEFAULT_BLOCK_PROPS[type] } };
660
741
  }
742
+ function makeRootContentRow(contentType, preset) {
743
+ const row = makeLayoutRow(preset);
744
+ row.gap = 0;
745
+ row.padding = 0;
746
+ const block = makeContentBlock(contentType);
747
+ row.cells[0] = [block];
748
+ return { row, block };
749
+ }
661
750
 
662
751
  // src/lib/columnPath.ts
663
752
  var MAX_NESTED_LAYOUT_DEPTH = 1;
@@ -667,6 +756,13 @@ function nestedLayoutDepth(nested) {
667
756
  function isSplitLayoutBlock(b) {
668
757
  return b && b.type === "layout" && b.props && Array.isArray(b.props.cells);
669
758
  }
759
+ function isRootContentRow(row) {
760
+ if (!row?.cells || row.cells.length !== 1) return false;
761
+ const col = row.cells[0];
762
+ if (!Array.isArray(col) || col.length !== 1) return false;
763
+ const b = col[0];
764
+ return !!b && typeof b.type === "string" && b.type !== "layout" && b.type !== "nestedRow";
765
+ }
670
766
  function getColumnBlocks(rows, loc) {
671
767
  const r = rows.find((x) => x.id === loc.rowId);
672
768
  if (!r) return [];
@@ -732,56 +828,6 @@ function countBlocksInDesign(rows) {
732
828
  return rows.reduce((sum, r) => sum + r.cells.reduce((s, c) => s + colBlocks(c), 0), 0);
733
829
  }
734
830
 
735
- // src/socialIcons.tsx
736
- import { jsx as jsx2 } from "react/jsx-runtime";
737
- var GLYPHS = {
738
- 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",
739
- /** X (formerly Twitter) — Simple Icons “X” */
740
- 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",
741
- 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",
742
- 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",
743
- 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",
744
- 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",
745
- 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",
746
- 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",
747
- 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",
748
- 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"
749
- };
750
- function iconFillForNetwork(network) {
751
- return network === "snapchat" ? "#000000" : "#ffffff";
752
- }
753
- function glyphPath(network) {
754
- return GLYPHS[network] || null;
755
- }
756
- function socialIconSvgString(network, pixelSize) {
757
- const d = glyphPath(network);
758
- const fill = iconFillForNetwork(network);
759
- const s = Math.max(8, Math.round(pixelSize));
760
- if (!d) {
761
- const letter = (network && network[0] ? network[0] : "?").toUpperCase();
762
- return `<span style="font-size:${Math.floor(s * 0.44)}px;font-weight:800;line-height:1;">${letter}</span>`;
763
- }
764
- 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>`;
765
- }
766
- function SocialGlyph({ network, size }) {
767
- const d = glyphPath(network);
768
- const s = Math.max(8, Math.round(size));
769
- if (!d) {
770
- return /* @__PURE__ */ jsx2("span", { style: { fontSize: Math.floor(s * 0.44), fontWeight: 800, lineHeight: 1 }, children: (network && network[0] ? network[0] : "?").toUpperCase() });
771
- }
772
- return /* @__PURE__ */ jsx2(
773
- "svg",
774
- {
775
- width: s,
776
- height: s,
777
- viewBox: "0 0 24 24",
778
- style: { display: "block", flexShrink: 0 },
779
- "aria-hidden": true,
780
- children: /* @__PURE__ */ jsx2("path", { fill: "currentColor", d })
781
- }
782
- );
783
- }
784
-
785
831
  // src/lib/htmlUtils.ts
786
832
  function escHtmlAttr(s) {
787
833
  return String(s ?? "").replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -850,6 +896,161 @@ function normalizeRichHtmlForStorage(html) {
850
896
  function isEffectivelyEmptyRichHtml(html) {
851
897
  return normalizeRichHtmlForStorage(html) === EMPTY_RICH_HTML;
852
898
  }
899
+ var RICH_HTML_CONTENT_CSS = `
900
+ .email-rich-html-content ul,
901
+ .email-rich-html-content ol {
902
+ margin: 0.35em 0;
903
+ padding-left: 1.35rem;
904
+ list-style-position: outside;
905
+ }
906
+ .email-rich-html-content ul { list-style-type: disc; }
907
+ .email-rich-html-content ol { list-style-type: decimal; }
908
+ .email-rich-html-content li { margin: 0.2em 0; }
909
+ .email-rich-html-content li > p { margin: 0; }
910
+ .email-rich-html-content p { margin: 0.35em 0; }
911
+ .email-rich-html-content h2,
912
+ .email-rich-html-content h3,
913
+ .email-rich-html-content h4 {
914
+ margin: 0.5em 0 0.25em;
915
+ line-height: 1.25;
916
+ font-weight: 700;
917
+ }
918
+ .email-rich-html-content blockquote {
919
+ margin: 0.35em 0;
920
+ padding-left: 0.75rem;
921
+ border-left: 3px solid #cbd5e1;
922
+ }
923
+ .email-rich-html-content hr {
924
+ border: none;
925
+ border-top: 1px solid #e2e8f0;
926
+ margin: 0.75em 0;
927
+ }
928
+ .email-rich-html-content code {
929
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
930
+ font-size: 0.92em;
931
+ background: rgba(0, 0, 0, 0.06);
932
+ padding: 0.1em 0.35em;
933
+ border-radius: 3px;
934
+ }
935
+ .email-rich-html-inner ul,
936
+ .email-rich-html-inner ol {
937
+ margin: 0.35em 0;
938
+ padding-left: 1.35rem;
939
+ list-style-position: outside;
940
+ }
941
+ .email-rich-html-inner ul { list-style-type: disc; }
942
+ .email-rich-html-inner ol { list-style-type: decimal; }
943
+ .email-rich-html-inner li { margin: 0.2em 0; }
944
+ .email-rich-html-inner li > p { margin: 0; }
945
+ .email-rich-html-inner p { margin: 0.35em 0; }
946
+ .email-rich-html-inner h2,
947
+ .email-rich-html-inner h3,
948
+ .email-rich-html-inner h4 {
949
+ margin: 0.5em 0 0.25em;
950
+ line-height: 1.25;
951
+ font-weight: 700;
952
+ }
953
+ .email-rich-html-inner blockquote {
954
+ margin: 0.35em 0;
955
+ padding-left: 0.75rem;
956
+ border-left: 3px solid #cbd5e1;
957
+ }
958
+ .email-rich-html-inner hr {
959
+ border: none;
960
+ border-top: 1px solid #e2e8f0;
961
+ margin: 0.75em 0;
962
+ }
963
+ `.trim();
964
+ function applyInlineListStyles(root) {
965
+ root.querySelectorAll("ul").forEach((ul) => {
966
+ const el = ul;
967
+ el.style.margin = el.style.margin || "0.35em 0";
968
+ el.style.paddingLeft = el.style.paddingLeft || "1.35rem";
969
+ el.style.listStyleType = el.style.listStyleType || "disc";
970
+ el.style.listStylePosition = el.style.listStylePosition || "outside";
971
+ });
972
+ root.querySelectorAll("ol").forEach((ol) => {
973
+ const el = ol;
974
+ el.style.margin = el.style.margin || "0.35em 0";
975
+ el.style.paddingLeft = el.style.paddingLeft || "1.35rem";
976
+ el.style.listStyleType = el.style.listStyleType || "decimal";
977
+ el.style.listStylePosition = el.style.listStylePosition || "outside";
978
+ });
979
+ root.querySelectorAll("li").forEach((li) => {
980
+ const el = li;
981
+ if (!el.style.margin) el.style.margin = "0.2em 0";
982
+ });
983
+ root.querySelectorAll("li > p").forEach((p) => {
984
+ const el = p;
985
+ if (!el.style.margin) el.style.margin = "0";
986
+ });
987
+ }
988
+ function enhanceRichHtmlForEmail(html) {
989
+ const raw = String(html ?? "").trim();
990
+ if (!raw || raw === EMPTY_RICH_HTML) return raw || EMPTY_RICH_HTML;
991
+ if (typeof document === "undefined") return raw;
992
+ const tmp = document.createElement("div");
993
+ tmp.innerHTML = raw;
994
+ if (!tmp.querySelector("ul,ol,blockquote,h2,h3,h4,hr")) return raw;
995
+ applyInlineListStyles(tmp);
996
+ tmp.querySelectorAll("blockquote").forEach((bq) => {
997
+ const el = bq;
998
+ if (!el.style.margin) el.style.margin = "0.35em 0";
999
+ if (!el.style.paddingLeft) el.style.paddingLeft = "0.75rem";
1000
+ if (!el.style.borderLeft) el.style.borderLeft = "3px solid #cbd5e1";
1001
+ });
1002
+ return tmp.innerHTML.trim();
1003
+ }
1004
+
1005
+ // src/socialIcons.tsx
1006
+ import { jsx as jsx2 } from "react/jsx-runtime";
1007
+ var GLYPHS = {
1008
+ 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",
1009
+ /** X (formerly Twitter) — Simple Icons “X” */
1010
+ 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",
1011
+ 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",
1012
+ 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",
1013
+ 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",
1014
+ 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",
1015
+ 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",
1016
+ 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",
1017
+ 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",
1018
+ 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"
1019
+ };
1020
+ function iconFillForNetwork(network) {
1021
+ return network === "snapchat" ? "#000000" : "#ffffff";
1022
+ }
1023
+ function glyphPath(network) {
1024
+ return GLYPHS[network] || null;
1025
+ }
1026
+ function socialIconSvgString(network, pixelSize) {
1027
+ const d = glyphPath(network);
1028
+ const fill = iconFillForNetwork(network);
1029
+ const s = Math.max(8, Math.round(pixelSize));
1030
+ if (!d) {
1031
+ const letter = (network && network[0] ? network[0] : "?").toUpperCase();
1032
+ return `<span style="font-size:${Math.floor(s * 0.44)}px;font-weight:800;line-height:1;">${letter}</span>`;
1033
+ }
1034
+ 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>`;
1035
+ }
1036
+ function SocialGlyph({ network, size }) {
1037
+ const d = glyphPath(network);
1038
+ const s = Math.max(8, Math.round(size));
1039
+ if (!d) {
1040
+ return /* @__PURE__ */ jsx2("span", { style: { fontSize: Math.floor(s * 0.44), fontWeight: 800, lineHeight: 1 }, children: (network && network[0] ? network[0] : "?").toUpperCase() });
1041
+ }
1042
+ return /* @__PURE__ */ jsx2(
1043
+ "svg",
1044
+ {
1045
+ width: s,
1046
+ height: s,
1047
+ viewBox: "0 0 24 24",
1048
+ style: { display: "block", flexShrink: 0 },
1049
+ "aria-hidden": true,
1050
+ children: /* @__PURE__ */ jsx2("path", { fill: "currentColor", d })
1051
+ }
1052
+ );
1053
+ }
853
1054
 
854
1055
  // src/lib/blockBackground.ts
855
1056
  function linearGradientCssInner(g) {
@@ -979,14 +1180,16 @@ function blockToHtml(cb) {
979
1180
  case "html": {
980
1181
  const shell = emailSurfaceBgCss(p);
981
1182
  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"}`;
982
- return `<div style="${pd(p.padding)};${marginCss()}${shell}"><div style="${inner}">${p.content || "<p></p>"}</div></div>`;
1183
+ const bodyRaw = typeof cb.content === "string" ? cb.content : typeof p.content === "string" ? p.content : "";
1184
+ const body = enhanceRichHtmlForEmail(bodyRaw || "<p></p>");
1185
+ return `<div style="${pd(p.padding)};${marginCss()}${shell}"><div class="email-rich-html-inner" style="${inner}">${body}</div></div>`;
983
1186
  }
984
1187
  case "image": {
985
1188
  const of = typeof p.objectFit === "string" && p.objectFit.trim() ? p.objectFit.trim() : "cover";
986
1189
  const op = typeof p.objectPosition === "string" && p.objectPosition.trim() ? p.objectPosition.trim() : "center";
987
1190
  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};"/>`;
988
1191
  const shell = emailSurfaceBgCss(p);
989
- 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>`;
1192
+ 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>`;
990
1193
  }
991
1194
  case "button": {
992
1195
  const bd = emailBackdropBgCss(p);
@@ -1075,7 +1278,7 @@ function blockToHtml(cb) {
1075
1278
  bgRepeat: p.bgRepeat || "no-repeat",
1076
1279
  bgPosition: typeof p.bgPosition === "string" ? p.bgPosition : "center",
1077
1280
  bgGradient: p.bgGradient ?? null,
1078
- columnStyles: p.columnStyles,
1281
+ columns: getColumns(p),
1079
1282
  cells: p.cells || []
1080
1283
  };
1081
1284
  return rowToHtml(pseudo);
@@ -1089,7 +1292,7 @@ function rowToHtml(row) {
1089
1292
  const shellBg = emailSurfaceBgCss(row);
1090
1293
  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) => {
1091
1294
  const r = row.ratios[i] ?? 1;
1092
- const cs = row.columnStyles && row.columnStyles[i] ? row.columnStyles[i] : {};
1295
+ const cs = getColumnPropsAt(row, i);
1093
1296
  const colShell = emailSurfaceBgCss(cs);
1094
1297
  const pad3 = cs.padding && typeof cs.padding === "object" && !Array.isArray(cs.padding) ? `padding:${(() => {
1095
1298
  const o = cs.padding;
@@ -1137,6 +1340,7 @@ table[role="presentation"] td{min-width:0!important;word-break:break-word;}
1137
1340
  @media screen and (max-width:480px){
1138
1341
  .email-content-td{padding-left:max(10px,env(safe-area-inset-left))!important;padding-right:max(10px,env(safe-area-inset-right))!important;}
1139
1342
  }
1343
+ ${RICH_HTML_CONTENT_CSS}
1140
1344
  </style>`;
1141
1345
  function previewEmailSrcDoc(html, viewportWidthPx) {
1142
1346
  const viewportTag = `<meta name="viewport" content="width=${viewportWidthPx},initial-scale=1"/>`;
@@ -1188,6 +1392,8 @@ function designToHtml(rows, settings, opts = {}) {
1188
1392
  return `background-image:linear-gradient(${angle}deg, ${pairs.join(", ")});background-repeat:no-repeat;background-position:${pagePos};background-size:cover;`;
1189
1393
  })();
1190
1394
  const pageBgImgRaw = typeof settings.bgImage === "string" ? String(settings.bgImage).trim() : "";
1395
+ const pageBgColorRaw = typeof settings.bgColor === "string" ? String(settings.bgColor).trim() : "";
1396
+ const pageBgColor = pageBgColorRaw ? pageBgColorRaw : "transparent";
1191
1397
  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};` : "";
1192
1398
  const contentPos = typeof settings.contentBgPosition === "string" && settings.contentBgPosition.trim() ? settings.contentBgPosition.trim() : "center";
1193
1399
  const contentGrad = settings.contentBgGradient && typeof settings.contentBgGradient === "object" ? settings.contentBgGradient : null;
@@ -1210,8 +1416,8 @@ function designToHtml(rows, settings, opts = {}) {
1210
1416
  <head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/>
1211
1417
  <title>Email</title>${RESPONSIVE_EMAIL_CSS}${css}
1212
1418
  </head>
1213
- <body style="margin:0;padding:0;background:${settings.bgColor || "#f1f5f9"};${pageGradCss || pageBgImgCss}">
1214
- <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="width:100%;max-width:100%;background-color:${settings.bgColor || "#f1f5f9"};${pageGradCss || pageBgImgCss}">
1419
+ <body style="margin:0;padding:0;background:${pageBgColor};${pageGradCss || pageBgImgCss}">
1420
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="width:100%;max-width:100%;background-color:${pageBgColor};${pageGradCss || pageBgImgCss}">
1215
1421
  <tr><td align="center" style="padding:0;max-width:100%;">
1216
1422
  <table class="email-shell-table" role="presentation" width="100%" cellpadding="0" cellspacing="0"
1217
1423
  style="${contentShellStyle.join(";")}">
@@ -1245,14 +1451,49 @@ function paddingToUniform(p, fallback = 0) {
1245
1451
  function ensureId(id, prefix) {
1246
1452
  return typeof id === "string" && id.trim() ? id : `${prefix}_${uid()}`;
1247
1453
  }
1454
+ function normalizeDocBlock(b, prefix) {
1455
+ const raw = b && typeof b === "object" && !Array.isArray(b) ? b : {};
1456
+ const base = {
1457
+ id: ensureId(raw.id, prefix),
1458
+ type: typeof raw.type === "string" ? raw.type : "text",
1459
+ content: typeof raw.content === "string" ? raw.content : raw.content && typeof raw.content === "object" && !Array.isArray(raw.content) ? raw.content : {},
1460
+ ...raw.behavior && typeof raw.behavior === "object" && !Array.isArray(raw.behavior) ? { behavior: raw.behavior } : {},
1461
+ ...raw.responsive && typeof raw.responsive === "object" && !Array.isArray(raw.responsive) ? { responsive: raw.responsive } : {}
1462
+ };
1463
+ if (raw.props && typeof raw.props === "object" && !Array.isArray(raw.props)) {
1464
+ base.props = { ...raw.props };
1465
+ } else if (raw.styles && typeof raw.styles === "object" && !Array.isArray(raw.styles)) {
1466
+ base.styles = { ...raw.styles };
1467
+ }
1468
+ return base;
1469
+ }
1470
+ function normalizeDocSettings(settings) {
1471
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) return {};
1472
+ const s = { ...settings };
1473
+ delete s._reactEmailStudio;
1474
+ return s;
1475
+ }
1248
1476
  function normalizeEmailDocument(input) {
1249
1477
  if (input == null || typeof input !== "object" || Array.isArray(input)) return null;
1250
1478
  const doc = input;
1251
1479
  if (doc.type !== "email_document") return null;
1480
+ const rawBlocks = Array.isArray(doc.blocks) ? doc.blocks : [];
1481
+ const useBlocks = rawBlocks.length > 0;
1252
1482
  const rows = Array.isArray(doc.rows) ? doc.rows : [];
1253
- return {
1483
+ const base = {
1254
1484
  type: "email_document",
1255
- settings: doc.settings && typeof doc.settings === "object" && !Array.isArray(doc.settings) ? doc.settings : {},
1485
+ settings: normalizeDocSettings(doc.settings),
1486
+ globalStyles: doc.globalStyles && typeof doc.globalStyles === "object" && !Array.isArray(doc.globalStyles) ? doc.globalStyles : void 0,
1487
+ responsive: doc.responsive && typeof doc.responsive === "object" && !Array.isArray(doc.responsive) ? doc.responsive : void 0
1488
+ };
1489
+ if (useBlocks) {
1490
+ return {
1491
+ ...base,
1492
+ blocks: rawBlocks.map((b, bi) => normalizeDocBlock(b, `block${bi + 1}`))
1493
+ };
1494
+ }
1495
+ return {
1496
+ ...base,
1256
1497
  rows: rows.map((r, ri) => {
1257
1498
  const columns = Array.isArray(r.columns) ? r.columns : [];
1258
1499
  const layoutColCount = typeof r.layout?.columns === "number" && r.layout.columns > 0 ? Math.floor(r.layout.columns) : 0;
@@ -1274,28 +1515,76 @@ function normalizeEmailDocument(input) {
1274
1515
  columns: colCount,
1275
1516
  gap: typeof r.layout?.gap === "number" ? r.layout.gap : 0,
1276
1517
  stackOnMobile: r.layout?.stackOnMobile !== false,
1277
- align: r.layout?.align || "center"
1518
+ align: r.layout?.align || "center",
1519
+ ...typeof r.layout?.preset === "string" && r.layout.preset.trim() ? { preset: r.layout.preset.trim() } : {},
1520
+ ...Array.isArray(r.layout?.ratios) && r.layout.ratios.length ? {
1521
+ ratios: r.layout.ratios.filter(
1522
+ (x) => typeof x === "number" && Number.isFinite(x)
1523
+ )
1524
+ } : {}
1278
1525
  },
1279
- styles: r.styles && typeof r.styles === "object" && !Array.isArray(r.styles) ? r.styles : {},
1280
- ...typeof r._reactEmailStudio === "object" && r._reactEmailStudio !== null && !Array.isArray(r._reactEmailStudio) ? { _reactEmailStudio: r._reactEmailStudio } : {},
1526
+ props: (() => {
1527
+ const p = r.props;
1528
+ if (p && typeof p === "object" && !Array.isArray(p)) return { ...p };
1529
+ const s = r.styles;
1530
+ if (s && typeof s === "object" && !Array.isArray(s)) {
1531
+ return {
1532
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1533
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1534
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1535
+ bgSize: s.backgroundSize || "cover",
1536
+ bgPosition: s.backgroundPosition || "center",
1537
+ bgGradient: s.backgroundGradient ?? null,
1538
+ padding: s.padding,
1539
+ borderRadius: s.borderRadius,
1540
+ borderWidth: s.borderWidth,
1541
+ borderColor: s.borderColor,
1542
+ textAlign: s.textAlign
1543
+ };
1544
+ }
1545
+ return {};
1546
+ })(),
1281
1547
  columns: colsNormalized.map((c, ci) => ({
1282
1548
  id: ensureId(c.id, `col${ri + 1}_${ci + 1}`),
1283
1549
  layout: c.layout && typeof c.layout === "object" && !Array.isArray(c.layout) ? c.layout : {},
1284
- styles: c.styles && typeof c.styles === "object" && !Array.isArray(c.styles) ? c.styles : {},
1285
- blocks: Array.isArray(c.blocks) ? c.blocks.map((b, bi) => ({
1286
- ...b,
1287
- id: ensureId(b?.id, `b${ri + 1}_${ci + 1}_${bi + 1}`),
1288
- content: b?.content && typeof b.content === "object" && !Array.isArray(b.content) ? b.content : {},
1289
- styles: b?.styles && typeof b.styles === "object" && !Array.isArray(b.styles) ? b.styles : {},
1290
- behavior: b?.behavior && typeof b.behavior === "object" && !Array.isArray(b.behavior) ? b.behavior : {},
1291
- responsive: b?.responsive && typeof b.responsive === "object" && !Array.isArray(b.responsive) ? b.responsive : {},
1292
- ...b?.props && typeof b.props === "object" && !Array.isArray(b.props) ? { props: b.props } : {}
1293
- })) : []
1550
+ ...(() => {
1551
+ const p = c.props;
1552
+ if (p && typeof p === "object" && !Array.isArray(p)) return { props: { ...p } };
1553
+ const s = c.styles;
1554
+ if (s && typeof s === "object" && !Array.isArray(s)) {
1555
+ return {
1556
+ props: {
1557
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1558
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1559
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1560
+ bgSize: s.backgroundSize || "cover",
1561
+ bgPosition: s.backgroundPosition || "center",
1562
+ bgGradient: s.backgroundGradient ?? null,
1563
+ padding: s.padding,
1564
+ borderRadius: s.borderRadius
1565
+ }
1566
+ };
1567
+ }
1568
+ return {};
1569
+ })(),
1570
+ blocks: Array.isArray(c.blocks) ? c.blocks.map((b, bi) => {
1571
+ const base2 = {
1572
+ id: ensureId(b?.id, `b${ri + 1}_${ci + 1}_${bi + 1}`),
1573
+ type: typeof b?.type === "string" ? b.type : "text",
1574
+ content: typeof b?.content === "string" ? b.content : b?.content && typeof b.content === "object" && !Array.isArray(b.content) ? b.content : {},
1575
+ ...b?.behavior && typeof b.behavior === "object" && !Array.isArray(b.behavior) ? { behavior: b.behavior } : {},
1576
+ ...b?.responsive && typeof b.responsive === "object" && !Array.isArray(b.responsive) ? { responsive: b.responsive } : {}
1577
+ };
1578
+ if (b?.props && typeof b.props === "object" && !Array.isArray(b.props)) {
1579
+ base2.props = { ...b.props };
1580
+ } else if (b?.styles && typeof b.styles === "object" && !Array.isArray(b.styles)) {
1581
+ base2.styles = { ...b.styles };
1582
+ }
1583
+ return base2;
1584
+ }) : []
1294
1585
  }))
1295
1586
  };
1296
- }),
1297
- globalStyles: doc.globalStyles && typeof doc.globalStyles === "object" && !Array.isArray(doc.globalStyles) ? doc.globalStyles : void 0,
1298
- responsive: doc.responsive && typeof doc.responsive === "object" && !Array.isArray(doc.responsive) ? doc.responsive : void 0
1587
+ })
1299
1588
  };
1300
1589
  }
1301
1590
 
@@ -1308,6 +1597,60 @@ function cloneJson(v) {
1308
1597
  return v;
1309
1598
  }
1310
1599
  }
1600
+ function legacyRowStylesToProps(s) {
1601
+ if (!s || typeof s !== "object" || Array.isArray(s)) return {};
1602
+ return {
1603
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1604
+ bgImage: typeof s.backgroundImage === "string" ? s.backgroundImage : "",
1605
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1606
+ bgSize: s.backgroundSize || "cover",
1607
+ bgPosition: s.backgroundPosition || "center",
1608
+ bgGradient: s.backgroundGradient ?? null,
1609
+ padding: s.padding,
1610
+ borderRadius: s.borderRadius,
1611
+ borderWidth: s.borderWidth,
1612
+ borderColor: s.borderColor,
1613
+ textAlign: s.textAlign
1614
+ };
1615
+ }
1616
+ function rowPropsFromDocRow(r) {
1617
+ const p = r.props;
1618
+ if (p && typeof p === "object" && !Array.isArray(p)) {
1619
+ return { ...p };
1620
+ }
1621
+ return legacyRowStylesToProps(r.styles);
1622
+ }
1623
+ function applyRowPropsToEditorRow(row, r) {
1624
+ const p = rowPropsFromDocRow(r);
1625
+ if (p.padding !== void 0 && p.padding !== null) {
1626
+ row.padding = paddingToUniform(p.padding, row.padding);
1627
+ }
1628
+ if (typeof p.bgColor === "string") row.bgColor = p.bgColor.trim();
1629
+ if (typeof p.bgImage === "string") row.bgImage = p.bgImage.trim();
1630
+ if (typeof p.bgRepeat === "string") row.bgRepeat = p.bgRepeat;
1631
+ if (typeof p.bgSize === "string") row.bgSize = p.bgSize;
1632
+ if (typeof p.bgPosition === "string") row.bgPosition = p.bgPosition;
1633
+ if (p.bgGradient !== void 0) row.bgGradient = p.bgGradient;
1634
+ }
1635
+ function editorRowToDocProps(r) {
1636
+ const rowPad = r.padding && typeof r.padding === "object" && !Array.isArray(r.padding) ? layoutColumnPaddingForDoc(r.padding) : (() => {
1637
+ const n = typeof r.padding === "number" && Number.isFinite(r.padding) ? r.padding : 0;
1638
+ return { top: n, right: n, bottom: n, left: n };
1639
+ })();
1640
+ return {
1641
+ bgColor: typeof r.bgColor === "string" ? r.bgColor : "",
1642
+ bgImage: typeof r.bgImage === "string" ? r.bgImage : "",
1643
+ bgRepeat: r.bgRepeat || "no-repeat",
1644
+ bgSize: r.bgSize || "cover",
1645
+ bgPosition: r.bgPosition || "center",
1646
+ bgGradient: r.bgGradient ?? null,
1647
+ padding: rowPad,
1648
+ borderRadius: 0,
1649
+ borderWidth: 0,
1650
+ borderColor: "#e5e7eb",
1651
+ textAlign: "left"
1652
+ };
1653
+ }
1311
1654
  function layoutColumnPaddingForDoc(p) {
1312
1655
  if (typeof p === "number" && Number.isFinite(p)) {
1313
1656
  const n = Math.max(0, p);
@@ -1327,16 +1670,58 @@ function layoutColumnBorderRadiusForDoc(r) {
1327
1670
  }
1328
1671
  return 0;
1329
1672
  }
1330
- function columnPaddingNonZero(pad3) {
1331
- return pad3.top !== 0 || pad3.right !== 0 || pad3.bottom !== 0 || pad3.left !== 0;
1673
+ function normalizeLayoutContainerProps(props) {
1674
+ const columns = getColumns(props);
1675
+ const { columnStyles: _drop, ...rest } = props;
1676
+ return { ...rest, columns };
1677
+ }
1678
+ function mergeLayoutColumns(fromContent, fromHydrated) {
1679
+ const norm = (x) => {
1680
+ if (Array.isArray(x)) {
1681
+ return x.map((entry) => {
1682
+ const e = entry;
1683
+ const raw = e?.props && typeof e.props === "object" ? e.props : entry;
1684
+ return { props: { ...DEFAULT_COLUMN_PROPS, ...raw } };
1685
+ });
1686
+ }
1687
+ if (x && typeof x === "object" && !Array.isArray(x)) {
1688
+ return columnsFromLegacyMap(x, Object.keys(x).length);
1689
+ }
1690
+ return [];
1691
+ };
1692
+ const a = norm(fromContent);
1693
+ const b = norm(fromHydrated);
1694
+ const len = Math.max(a.length, b.length, 1);
1695
+ const out = [];
1696
+ for (let i = 0; i < len; i++) {
1697
+ out.push({
1698
+ props: { ...DEFAULT_COLUMN_PROPS, ...a[i]?.props ?? {}, ...b[i]?.props ?? {} }
1699
+ });
1700
+ }
1701
+ return out.length ? out : void 0;
1332
1702
  }
1333
- function columnRadiusNonZero(r) {
1334
- if (typeof r === "number") return r !== 0;
1335
- if (r && typeof r === "object" && !Array.isArray(r)) {
1336
- const o = r;
1337
- return !!(o.tl || o.tr || o.br || o.bl);
1703
+ function columnPropsFromDocColumn(c) {
1704
+ const p = c?.props;
1705
+ if (p && typeof p === "object" && !Array.isArray(p)) {
1706
+ return { ...DEFAULT_COLUMN_PROPS, ...p };
1338
1707
  }
1339
- return false;
1708
+ const s = c?.styles;
1709
+ if (!s || typeof s !== "object" || Array.isArray(s)) {
1710
+ return { ...DEFAULT_COLUMN_PROPS };
1711
+ }
1712
+ const padding = layoutColumnPaddingForDoc(s.padding);
1713
+ const borderRadius = layoutColumnBorderRadiusForDoc(s.borderRadius);
1714
+ return {
1715
+ ...DEFAULT_COLUMN_PROPS,
1716
+ bgColor: typeof s.backgroundColor === "string" ? s.backgroundColor : "",
1717
+ bgImage: typeof s.backgroundImage === "string" ? String(s.backgroundImage).trim() : "",
1718
+ bgRepeat: s.backgroundRepeat || "no-repeat",
1719
+ bgSize: s.backgroundSize || "cover",
1720
+ bgPosition: s.backgroundPosition || "center",
1721
+ bgGradient: s.backgroundGradient ?? null,
1722
+ padding,
1723
+ borderRadius
1724
+ };
1340
1725
  }
1341
1726
  function asNum(v) {
1342
1727
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
@@ -1433,13 +1818,15 @@ function internalBlockToEmailDoc(b, depth = 0) {
1433
1818
  ...p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1434
1819
  }
1435
1820
  };
1436
- case "html":
1821
+ case "html": {
1822
+ const body = typeof b.content === "string" ? b.content : typeof p.content === "string" ? p.content : "";
1437
1823
  return {
1438
1824
  id,
1439
1825
  type,
1440
- content: { html: typeof p.content === "string" ? p.content : "" },
1826
+ content: { html: normalizeRichHtmlForStorage(body) },
1441
1827
  styles: p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1442
1828
  };
1829
+ }
1443
1830
  case "image": {
1444
1831
  const br = p.borderRadius;
1445
1832
  const borderRadius = typeof br === "number" && Number.isFinite(br) ? br : br && typeof br === "object" && !Array.isArray(br) ? Math.round(
@@ -1619,7 +2006,7 @@ function internalBlockToEmailDoc(b, depth = 0) {
1619
2006
  bgRepeat: p.bgRepeat,
1620
2007
  bgPosition: p.bgPosition,
1621
2008
  bgGradient: p.bgGradient ?? null,
1622
- columnStyles: p.columnStyles,
2009
+ columns: getColumns(p).map((col) => ({ props: cloneJson(col.props) })),
1623
2010
  cells: cellsOut
1624
2011
  },
1625
2012
  styles: {}
@@ -1659,17 +2046,26 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1659
2046
  block.props.padding = paddingToUniform(s.padding, block.props.padding);
1660
2047
  }
1661
2048
  break;
1662
- case "html":
1663
- block.props.content = asStr(c.html) ?? block.props.content;
1664
- block.props.content = normalizeRichHtmlForStorage(block.props.content);
2049
+ case "html": {
2050
+ const br = b;
2051
+ const fromDoc = asStr(c.html);
2052
+ const fromRoot = typeof br.content === "string" ? String(br.content) : void 0;
2053
+ const fromLegacyProps = br.props && typeof br.props.content === "string" ? String(br.props.content) : void 0;
2054
+ const htmlNorm = normalizeRichHtmlForStorage(fromDoc ?? fromRoot ?? fromLegacyProps ?? "");
2055
+ block.content = htmlNorm;
2056
+ block.props.content = htmlNorm;
1665
2057
  if (s.padding && typeof s.padding === "object") {
1666
2058
  block.props.padding = paddingToUniform(s.padding, block.props.padding);
1667
2059
  }
1668
2060
  break;
2061
+ }
1669
2062
  case "image":
1670
2063
  block.props.src = asStr(c.src) ?? block.props.src;
1671
2064
  if (asStr(c.alt)) block.props.alt = c.alt;
1672
- if (asStr(c.link)) block.props.link = c.link;
2065
+ if (asStr(c.link)) {
2066
+ block.props.link = c.link;
2067
+ block.props.linkEnabled = true;
2068
+ }
1673
2069
  if (beh.openInNewTab === true) block.props.linkTarget = "_blank";
1674
2070
  if (asStr(s.width)) block.props.width = s.width;
1675
2071
  if (asStr(s.align)) block.props.align = s.align;
@@ -1791,8 +2187,17 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1791
2187
  if (typeof c.bgRepeat === "string") block.props.bgRepeat = c.bgRepeat;
1792
2188
  if (typeof c.bgPosition === "string") block.props.bgPosition = c.bgPosition;
1793
2189
  if (c.bgGradient !== void 0) block.props.bgGradient = c.bgGradient;
1794
- if (c.columnStyles && typeof c.columnStyles === "object" && !Array.isArray(c.columnStyles)) {
1795
- block.props.columnStyles = c.columnStyles;
2190
+ if (c.bgGradient !== void 0) block.props.bgGradient = c.bgGradient;
2191
+ if (Array.isArray(c.columns)) {
2192
+ block.props.columns = c.columns.map((col) => ({
2193
+ props: {
2194
+ ...DEFAULT_COLUMN_PROPS,
2195
+ ...col?.props && typeof col.props === "object" ? col.props : {}
2196
+ }
2197
+ }));
2198
+ } else if (c.columnStyles && typeof c.columnStyles === "object" && !Array.isArray(c.columnStyles)) {
2199
+ const ratios = Array.isArray(c.ratios) ? c.ratios : [1];
2200
+ block.props.columns = columnsFromLegacyMap(c.columnStyles, ratios.length);
1796
2201
  }
1797
2202
  block.props.cells = c.cells.map(
1798
2203
  (col) => Array.isArray(col) ? col.map((raw) => mapEmbeddedLayoutCellBlock(raw, layoutDepth + 1)).filter((x) => x != null) : []
@@ -1836,6 +2241,10 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1836
2241
  }
1837
2242
  }
1838
2243
  applyImportedEditorPropsFromDoc(block, t, b, layoutDepth);
2244
+ if (t === "image") {
2245
+ const link = asStr(block.props.link);
2246
+ if (link && block.props.linkEnabled == null) block.props.linkEnabled = true;
2247
+ }
1839
2248
  return block;
1840
2249
  }
1841
2250
  function rowToInternal(r) {
@@ -1857,55 +2266,315 @@ function rowToInternal(r) {
1857
2266
  row.id = typeof r.id === "string" ? r.id : uid();
1858
2267
  row.cols = cols;
1859
2268
  row.gap = typeof r.layout?.gap === "number" ? r.layout.gap : row.gap;
1860
- row.padding = paddingToUniform(r.styles?.padding, row.padding);
1861
- row.bgColor = (r.styles?.backgroundColor || "").trim();
1862
- row.bgImage = (r.styles?.backgroundImage || "").trim();
1863
- row.bgRepeat = r.styles?.backgroundRepeat || row.bgRepeat;
1864
- row.bgSize = r.styles?.backgroundSize || row.bgSize;
1865
- row.bgPosition = r.styles?.backgroundPosition || row.bgPosition || "center";
1866
- row.bgGradient = r.styles?.backgroundGradient || row.bgGradient || null;
2269
+ applyRowPropsToEditorRow(row, r);
2270
+ const lay = r.layout;
2271
+ if (lay && typeof lay === "object" && !Array.isArray(lay)) {
2272
+ if (typeof lay.preset === "string" && lay.preset.trim()) row.preset = lay.preset.trim();
2273
+ if (Array.isArray(lay.ratios) && lay.ratios.length) {
2274
+ const rr = lay.ratios.filter((x) => typeof x === "number" && Number.isFinite(x));
2275
+ if (rr.length) row.ratios = rr;
2276
+ }
2277
+ }
1867
2278
  row.cells = colList.map((c) => {
1868
2279
  const blocks = Array.isArray(c.blocks) ? c.blocks : [];
1869
2280
  return blocks.map((blk) => mapBlockToInternal(blk, 0)).filter((x) => x != null);
1870
2281
  });
1871
2282
  const studio = r._reactEmailStudio;
1872
2283
  const snap = studio?.row;
1873
- const columnStylesFromDoc = {};
1874
- colList.forEach((c, i) => {
1875
- const bgColor = typeof c.styles?.backgroundColor === "string" ? c.styles.backgroundColor : "";
1876
- const padding = layoutColumnPaddingForDoc(c.styles?.padding);
1877
- const borderRadius = layoutColumnBorderRadiusForDoc(c.styles?.borderRadius);
1878
- const bgImage = typeof c.styles?.backgroundImage === "string" ? String(c.styles.backgroundImage).trim() : "";
1879
- const bgRepeat = typeof c.styles?.backgroundRepeat === "string" ? c.styles.backgroundRepeat : void 0;
1880
- const bgSize = typeof c.styles?.backgroundSize === "string" ? c.styles.backgroundSize : void 0;
1881
- const bgPosition = typeof c.styles?.backgroundPosition === "string" ? c.styles.backgroundPosition : void 0;
1882
- const bgGradient = c.styles?.backgroundGradient || null;
1883
- const hasPad = columnPaddingNonZero(padding);
1884
- const hasBr = columnRadiusNonZero(borderRadius);
1885
- if (bgColor || hasPad || hasBr || bgImage || bgRepeat || bgSize || bgPosition || bgGradient) {
1886
- columnStylesFromDoc[i] = { bgColor, padding, borderRadius, bgImage, bgRepeat, bgSize, bgPosition, bgGradient };
1887
- }
1888
- });
2284
+ const columnsFromDoc = colList.map((c) => ({
2285
+ props: columnPropsFromDocColumn(c)
2286
+ }));
1889
2287
  if (snap && typeof snap === "object" && !Array.isArray(snap)) {
1890
- const { cells: _omitCells, columnStyles: snapCs, ...rest } = snap;
2288
+ const {
2289
+ cells: _omitCells,
2290
+ columnStyles: legacyCs,
2291
+ columns: snapCols,
2292
+ ...rest
2293
+ } = snap;
1891
2294
  Object.assign(row, rest);
1892
- const snapColumn = snapCs && typeof snapCs === "object" && !Array.isArray(snapCs) ? snapCs : {};
1893
- row.columnStyles = { ...snapColumn, ...columnStylesFromDoc };
1894
- if (!Object.keys(row.columnStyles).length) delete row.columnStyles;
1895
- } else if (Object.keys(columnStylesFromDoc).length) {
1896
- row.columnStyles = columnStylesFromDoc;
2295
+ const fromSnap = Array.isArray(snapCols) ? snapCols.map((col) => ({
2296
+ props: {
2297
+ ...DEFAULT_COLUMN_PROPS,
2298
+ ...col?.props && typeof col.props === "object" ? col.props : {}
2299
+ }
2300
+ })) : columnsFromLegacyMap(legacyCs, cols);
2301
+ row.columns = mergeLayoutColumns(fromSnap, columnsFromDoc) ?? columnsFromDoc;
2302
+ delete row.columnStyles;
2303
+ } else {
2304
+ row.columns = columnsFromDoc;
1897
2305
  }
1898
2306
  return row;
1899
2307
  }
1900
- function normalizeEmailDesignInput(input) {
1901
- const doc = normalizeEmailDocument(input);
1902
- if (!doc) return null;
1903
- const s = doc.settings || {};
1904
- const studioSettings = s._reactEmailStudio;
2308
+ function coerceEmailDocumentInput(input) {
2309
+ if (input == null) return input;
2310
+ if (Array.isArray(input)) {
2311
+ if (input.length === 0) return input;
2312
+ return { type: "email_document", settings: {}, blocks: input };
2313
+ }
2314
+ if (typeof input !== "object") return input;
2315
+ const o = input;
2316
+ if (o.type === "email_document") return input;
2317
+ if (Array.isArray(o.blocks)) {
2318
+ return {
2319
+ type: "email_document",
2320
+ settings: o.settings && typeof o.settings === "object" ? o.settings : {},
2321
+ blocks: o.blocks
2322
+ };
2323
+ }
2324
+ return input;
2325
+ }
2326
+ function rootLayoutBlockToDocRow(block) {
2327
+ const p = block.props && typeof block.props === "object" ? block.props : {};
2328
+ const c = block.content && typeof block.content === "object" && !Array.isArray(block.content) ? block.content : {};
2329
+ const cols = Math.max(
2330
+ 1,
2331
+ typeof p.cols === "number" && p.cols > 0 ? Math.floor(p.cols) : 0,
2332
+ typeof c.cols === "number" && c.cols > 0 ? Math.floor(c.cols) : 0,
2333
+ Array.isArray(p.cells) ? p.cells.length : 0,
2334
+ Array.isArray(c.cells) ? c.cells.length : 0
2335
+ );
2336
+ const rawCells = Array.isArray(c.cells) ? c.cells : Array.isArray(p.cells) ? p.cells : [];
2337
+ const contentColumns = Array.isArray(c.columns) ? c.columns : [];
2338
+ const propsColumns = getColumns(p);
2339
+ const columns = Array.from({ length: cols }, (_, ci) => {
2340
+ const cellRaw = rawCells[ci];
2341
+ const blocks = Array.isArray(cellRaw) ? cellRaw.filter((x) => x && typeof x === "object").map((x, bi) => {
2342
+ const blk = x;
2343
+ return {
2344
+ id: ensureBlockId(blk.id, `b_${ci}_${bi}`),
2345
+ type: typeof blk.type === "string" ? blk.type : "text",
2346
+ content: blk.content,
2347
+ ...blk.props ? { props: blk.props } : {},
2348
+ ...blk.styles ? { styles: blk.styles } : {},
2349
+ ...blk.behavior ? { behavior: blk.behavior } : {}
2350
+ };
2351
+ }) : [];
2352
+ const colProps = {
2353
+ ...DEFAULT_COLUMN_PROPS,
2354
+ ...propsColumns[ci]?.props ?? contentColumns[ci]?.props ?? {}
2355
+ };
2356
+ return {
2357
+ id: `col_${ci + 1}`,
2358
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2359
+ props: cloneJson(colProps),
2360
+ blocks
2361
+ };
2362
+ });
2363
+ const rowProps = {
2364
+ bgColor: typeof p.bgColor === "string" ? p.bgColor : "",
2365
+ bgImage: typeof p.bgImage === "string" ? p.bgImage : "",
2366
+ bgRepeat: p.bgRepeat || "no-repeat",
2367
+ bgSize: p.bgSize || "cover",
2368
+ bgPosition: p.bgPosition || "center",
2369
+ bgGradient: p.bgGradient ?? null,
2370
+ padding: typeof p.padding === "number" ? { top: p.padding, right: p.padding, bottom: p.padding, left: p.padding } : normalizePadding(p.padding)
2371
+ };
2372
+ const ratios = Array.isArray(p.ratios) ? p.ratios : Array.isArray(c.ratios) ? c.ratios : void 0;
2373
+ return {
2374
+ id: typeof block.id === "string" ? block.id : uid(),
2375
+ type: "row",
2376
+ layout: {
2377
+ columns: cols,
2378
+ gap: typeof p.gap === "number" ? p.gap : typeof c.gap === "number" ? c.gap : 0,
2379
+ stackOnMobile: true,
2380
+ align: "center",
2381
+ ...typeof p.preset === "string" && p.preset.trim() ? { preset: p.preset.trim() } : typeof c.preset === "string" && c.preset.trim() ? { preset: c.preset.trim() } : {},
2382
+ ...ratios?.length ? { ratios: [...ratios] } : {}
2383
+ },
2384
+ props: rowProps,
2385
+ columns
2386
+ };
2387
+ }
2388
+ function ensureBlockId(id, prefix) {
2389
+ return typeof id === "string" && id.trim() ? id : `${prefix}_${uid()}`;
2390
+ }
2391
+ function wrapRootContentBlock(block) {
2392
+ const row = makeLayoutRow(pickPresetByColCount(1));
2393
+ const internal = mapBlockToInternal(block, 0);
2394
+ if (internal) row.cells[0] = [internal];
2395
+ return row;
2396
+ }
2397
+ function rootBlockToEditorRow(block) {
2398
+ const t = block.type === "nestedRow" ? "layout" : block.type;
2399
+ if (t === "layout") {
2400
+ return rowToInternal(rootLayoutBlockToDocRow(block));
2401
+ }
2402
+ return wrapRootContentBlock(block);
2403
+ }
2404
+ function blocksToEditorRows(blocks) {
2405
+ return blocks.map(rootBlockToEditorRow);
2406
+ }
2407
+ function rowPropsToLegacyStyles(p) {
2408
+ return {
2409
+ backgroundColor: p.bgColor ?? "",
2410
+ backgroundImage: p.bgImage ?? "",
2411
+ backgroundRepeat: p.bgRepeat ?? "no-repeat",
2412
+ backgroundSize: p.bgSize ?? "cover",
2413
+ backgroundPosition: p.bgPosition ?? "center",
2414
+ backgroundGradient: p.bgGradient ?? null,
2415
+ padding: p.padding,
2416
+ borderRadius: p.borderRadius ?? 0,
2417
+ borderWidth: p.borderWidth ?? 0,
2418
+ borderColor: p.borderColor ?? "#e5e7eb",
2419
+ textAlign: p.textAlign ?? "left"
2420
+ };
2421
+ }
2422
+ function columnPropsToLegacyStyles(cp) {
2423
+ return {
2424
+ padding: layoutColumnPaddingForDoc(cp.padding),
2425
+ backgroundColor: cp.bgColor ?? "",
2426
+ backgroundImage: cp.bgImage ?? "",
2427
+ backgroundRepeat: cp.bgRepeat ?? "no-repeat",
2428
+ backgroundSize: cp.bgSize ?? "cover",
2429
+ backgroundPosition: cp.bgPosition ?? "center",
2430
+ backgroundGradient: cp.bgGradient ?? null,
2431
+ borderRadius: layoutColumnBorderRadiusForDoc(cp.borderRadius)
2432
+ };
2433
+ }
2434
+ function editorRowStudioSnapshot(r) {
2435
+ const p = editorRowToDocProps(r);
2436
+ const columns = getColumns(r).map((col) => ({
2437
+ props: cloneJson(col.props ?? DEFAULT_COLUMN_PROPS)
2438
+ }));
2439
+ return {
2440
+ id: r.id,
2441
+ type: "layout",
2442
+ preset: r.preset,
2443
+ cols: r.cols,
2444
+ ratios: Array.isArray(r.ratios) ? [...r.ratios] : [1],
2445
+ gap: typeof r.gap === "number" ? r.gap : 0,
2446
+ padding: p.padding,
2447
+ bgColor: p.bgColor ?? "",
2448
+ bgImage: p.bgImage ?? "",
2449
+ bgSize: p.bgSize ?? "cover",
2450
+ bgRepeat: p.bgRepeat ?? "no-repeat",
2451
+ bgPosition: p.bgPosition ?? "center",
2452
+ bgGradient: p.bgGradient ?? null,
2453
+ columns
2454
+ };
2455
+ }
2456
+ function exportBlockForLegacyRow(b, depth = 0) {
2457
+ if (!b || typeof b !== "object") {
2458
+ return { id: uid(), type: "text", content: {}, props: {} };
2459
+ }
2460
+ const rawProps = b.props && typeof b.props === "object" && !Array.isArray(b.props) ? cloneJson(b.props) : {};
2461
+ if (b.type === "layout" && Array.isArray(b.props?.cells)) {
2462
+ rawProps.cells = b.props.cells.map(
2463
+ (col) => Array.isArray(col) ? col.map((child) => exportBlockForLegacyRow(child, depth + 1)) : []
2464
+ );
2465
+ }
2466
+ if (b.type === "html") {
2467
+ const body = typeof b.content === "string" ? b.content : typeof rawProps.content === "string" ? rawProps.content : "";
2468
+ const html = normalizeRichHtmlForStorage(body);
2469
+ rawProps.content = html;
2470
+ return {
2471
+ id: typeof b.id === "string" ? b.id : uid(),
2472
+ type: "html",
2473
+ content: { html },
2474
+ props: rawProps
2475
+ };
2476
+ }
2477
+ const doc = internalBlockToEmailDoc(b, depth);
2478
+ const out = {
2479
+ id: doc.id,
2480
+ type: doc.type,
2481
+ content: doc.content ?? {},
2482
+ props: rawProps
2483
+ };
2484
+ if (doc.behavior) out.behavior = doc.behavior;
2485
+ return out;
2486
+ }
2487
+ function editorRowToEmailDocumentRow(r, rowIndex) {
2488
+ const rowProps = editorRowToDocProps(r);
2489
+ const cellArrays = Array.isArray(r.cells) ? r.cells : [];
2490
+ const declaredCols = typeof r.cols === "number" && r.cols > 0 ? Math.floor(r.cols) : 0;
2491
+ const colCount = Math.max(1, cellArrays.length, declaredCols);
2492
+ const rowColumns = getColumns(r);
2493
+ const columns = Array.from({ length: colCount }, (_, ci) => {
2494
+ const cp = {
2495
+ ...DEFAULT_COLUMN_PROPS,
2496
+ ...rowColumns[ci]?.props ?? {}
2497
+ };
2498
+ const cellBlocks = cellArrays[ci];
2499
+ const blocks = Array.isArray(cellBlocks) ? cellBlocks.map((blk) => exportBlockForLegacyRow(blk)).filter((x) => x != null) : [];
2500
+ return {
2501
+ id: `col_${rowIndex + 1}_${ci + 1}`,
2502
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2503
+ styles: columnPropsToLegacyStyles(cp),
2504
+ props: cloneJson(cp),
2505
+ blocks
2506
+ };
2507
+ });
2508
+ const ratios = Array.isArray(r.ratios) ? [...r.ratios] : void 0;
2509
+ return {
2510
+ id: typeof r.id === "string" ? r.id : uid(),
2511
+ type: "row",
2512
+ _reactEmailStudio: { row: editorRowStudioSnapshot(r) },
2513
+ layout: {
2514
+ columns: colCount,
2515
+ gap: typeof r.gap === "number" ? r.gap : 0,
2516
+ stackOnMobile: true,
2517
+ align: "center",
2518
+ ...typeof r.preset === "string" && r.preset.trim() ? { preset: r.preset.trim() } : {},
2519
+ ...ratios?.length ? { ratios } : {}
2520
+ },
2521
+ props: rowProps,
2522
+ styles: rowPropsToLegacyStyles(rowProps),
2523
+ columns
2524
+ };
2525
+ }
2526
+ function buildExportSettingsWithStudio(settings) {
2527
+ const base = buildExportSettings(settings) ?? {};
2528
+ const editorSettings = cloneJson(settings);
2529
+ return {
2530
+ ...base,
2531
+ _reactEmailStudio: {
2532
+ contentPadding: base.contentPadding ?? 24,
2533
+ contentBorderRadius: base.contentBorderRadius ?? 8,
2534
+ editorSettings
2535
+ }
2536
+ };
2537
+ }
2538
+ function editorSettingStr(settings, key, fallback = "") {
2539
+ const v = settings[key];
2540
+ return typeof v === "string" ? v : fallback;
2541
+ }
2542
+ function buildExportSettings(settings) {
2543
+ const contentWidth = typeof settings.contentWidth === "number" ? settings.contentWidth : 600;
2544
+ const bgGradient = settings.bgGradient;
2545
+ const contentBgGradient = settings.contentBgGradient;
2546
+ return {
2547
+ width: contentWidth,
2548
+ backgroundColor: editorSettingStr(settings, "bgColor", "#f1f5f9"),
2549
+ backgroundImage: editorSettingStr(settings, "bgImage"),
2550
+ backgroundRepeat: editorSettingStr(settings, "bgRepeat", "no-repeat"),
2551
+ backgroundSize: editorSettingStr(settings, "bgSize", "cover"),
2552
+ backgroundPosition: editorSettingStr(settings, "bgPosition", "center"),
2553
+ backgroundGradient: bgGradient && typeof bgGradient === "object" && !Array.isArray(bgGradient) ? bgGradient : void 0,
2554
+ contentBackgroundColor: editorSettingStr(settings, "contentBg", "#ffffff"),
2555
+ contentBackgroundImage: editorSettingStr(settings, "contentBgImage"),
2556
+ contentBackgroundRepeat: editorSettingStr(settings, "contentBgRepeat", "no-repeat"),
2557
+ contentBackgroundSize: editorSettingStr(settings, "contentBgSize", "cover"),
2558
+ contentBackgroundPosition: editorSettingStr(settings, "contentBgPosition", "center"),
2559
+ contentBackgroundGradient: contentBgGradient && typeof contentBgGradient === "object" && !Array.isArray(contentBgGradient) ? contentBgGradient : void 0,
2560
+ fontFamily: editorSettingStr(settings, "fontFamily") || editorSettingStr(settings, "pageFontFamily", "Arial, Helvetica, sans-serif"),
2561
+ lineHeightBase: settings.pageLineHeight != null && settings.pageLineHeight !== "" ? Number(settings.pageLineHeight) : 1.6,
2562
+ color: editorSettingStr(settings, "pageTextColor", "#111827"),
2563
+ responsive: typeof settings.pageResponsive === "boolean" ? settings.pageResponsive : true,
2564
+ rtl: !!settings.pageRtl,
2565
+ contentPadding: typeof settings.padding === "number" ? settings.padding : 24,
2566
+ contentBorderRadius: typeof settings.borderRadius === "number" ? settings.borderRadius : 8
2567
+ };
2568
+ }
2569
+ function editorSettingsFromDoc(s, rawSettings) {
2570
+ const studioSettings = rawSettings?._reactEmailStudio;
1905
2571
  const settings = {};
1906
2572
  if (studioSettings?.editorSettings && typeof studioSettings.editorSettings === "object" && !Array.isArray(studioSettings.editorSettings)) {
1907
2573
  Object.assign(settings, cloneJson(studioSettings.editorSettings));
1908
2574
  }
2575
+ const pageRtlFromEditor = settings.pageRtl;
2576
+ const contentPadding = typeof s.contentPadding === "number" ? s.contentPadding : typeof studioSettings?.contentPadding === "number" ? studioSettings.contentPadding : void 0;
2577
+ const contentBorderRadius = typeof s.contentBorderRadius === "number" ? s.contentBorderRadius : typeof studioSettings?.contentBorderRadius === "number" ? studioSettings.contentBorderRadius : void 0;
1909
2578
  Object.assign(settings, {
1910
2579
  bgColor: s.backgroundColor || "#f1f5f9",
1911
2580
  bgImage: s.backgroundImage || "",
@@ -1920,103 +2589,67 @@ function normalizeEmailDesignInput(input) {
1920
2589
  contentBgPosition: s.contentBackgroundPosition || "center",
1921
2590
  contentBgGradient: s.contentBackgroundGradient || null,
1922
2591
  contentWidth: typeof s.width === "number" ? s.width : 600,
1923
- padding: typeof studioSettings?.contentPadding === "number" ? studioSettings.contentPadding : typeof settings.padding === "number" ? settings.padding : 24,
1924
- borderRadius: typeof studioSettings?.contentBorderRadius === "number" ? studioSettings.contentBorderRadius : typeof settings.borderRadius === "number" ? settings.borderRadius : 8,
2592
+ padding: contentPadding ?? (typeof settings.padding === "number" ? settings.padding : 24),
2593
+ borderRadius: contentBorderRadius ?? (typeof settings.borderRadius === "number" ? settings.borderRadius : 8),
1925
2594
  pageFontFamily: s.fontFamily,
1926
2595
  pageTextColor: s.color,
1927
2596
  pageLineHeight: s.lineHeightBase != null ? String(s.lineHeightBase) : void 0,
1928
2597
  pageResponsive: s.responsive,
1929
- pageRtl: s.rtl,
2598
+ pageRtl: typeof s.rtl === "boolean" ? s.rtl : pageRtlFromEditor,
1930
2599
  fontFamily: s.fontFamily
1931
2600
  });
1932
- const rows = (doc.rows || []).map(rowToInternal);
2601
+ return settings;
2602
+ }
2603
+ function normalizeEmailDesignInput(input) {
2604
+ const coerced = coerceEmailDocumentInput(input);
2605
+ const doc = normalizeEmailDocument(coerced);
2606
+ if (!doc) return null;
2607
+ const rawDoc = input && typeof input === "object" && !Array.isArray(input) ? input : null;
2608
+ const rawSettings = rawDoc?.settings && typeof rawDoc.settings === "object" && !Array.isArray(rawDoc.settings) ? rawDoc.settings : null;
2609
+ const settings = editorSettingsFromDoc(doc.settings || {}, rawSettings);
2610
+ let rows;
2611
+ if (doc.blocks?.length) {
2612
+ rows = blocksToEditorRows(doc.blocks);
2613
+ } else {
2614
+ const rawRows = rawDoc && Array.isArray(rawDoc.rows) ? rawDoc.rows : [];
2615
+ rows = (doc.rows || []).map((r, i) => {
2616
+ const legacy = rawRows[i]?._reactEmailStudio;
2617
+ if (legacy) {
2618
+ return rowToInternal({ ...r, _reactEmailStudio: legacy });
2619
+ }
2620
+ return rowToInternal(r);
2621
+ });
2622
+ }
1933
2623
  return withHydratedRows({ rows, settings, __emailDocument: doc });
1934
2624
  }
2625
+ function emailDocumentToEditorRows(input) {
2626
+ const coerced = coerceEmailDocumentInput(input);
2627
+ const doc = normalizeEmailDocument(coerced);
2628
+ if (!doc) return null;
2629
+ const rawDoc = input && typeof input === "object" && !Array.isArray(input) ? input : null;
2630
+ if (doc.blocks?.length) {
2631
+ return blocksToEditorRows(doc.blocks);
2632
+ }
2633
+ const rawRows = rawDoc && Array.isArray(rawDoc.rows) ? rawDoc.rows : [];
2634
+ return (doc.rows || []).map((r, i) => {
2635
+ const legacy = rawRows[i]?._reactEmailStudio;
2636
+ if (legacy) {
2637
+ return rowToInternal({ ...r, _reactEmailStudio: legacy });
2638
+ }
2639
+ return rowToInternal(r);
2640
+ });
2641
+ }
1935
2642
  function designToEmailDocument(rows, settings) {
1936
- const doc = {
2643
+ return {
1937
2644
  type: "email_document",
1938
- settings: {
1939
- width: settings.contentWidth || 600,
1940
- backgroundColor: settings.bgColor || "#f1f5f9",
1941
- backgroundImage: settings.bgImage || "",
1942
- backgroundRepeat: settings.bgRepeat || "no-repeat",
1943
- backgroundSize: settings.bgSize || "cover",
1944
- backgroundPosition: settings.bgPosition || "center",
1945
- backgroundGradient: settings.bgGradient || null,
1946
- contentBackgroundColor: settings.contentBg || "#ffffff",
1947
- contentBackgroundImage: settings.contentBgImage || "",
1948
- contentBackgroundRepeat: settings.contentBgRepeat || "no-repeat",
1949
- contentBackgroundSize: settings.contentBgSize || "cover",
1950
- contentBackgroundPosition: settings.contentBgPosition || "center",
1951
- contentBackgroundGradient: settings.contentBgGradient || null,
1952
- fontFamily: settings.fontFamily || settings.pageFontFamily || "Arial, Helvetica, sans-serif",
1953
- lineHeightBase: settings.pageLineHeight ? Number(settings.pageLineHeight) : 1.6,
1954
- color: settings.pageTextColor || "#111827",
1955
- responsive: typeof settings.pageResponsive === "boolean" ? settings.pageResponsive : true,
1956
- rtl: !!settings.pageRtl,
1957
- _reactEmailStudio: {
1958
- contentPadding: settings.padding ?? 24,
1959
- contentBorderRadius: settings.borderRadius ?? 8,
1960
- editorSettings: cloneJson(settings)
1961
- }
1962
- },
1963
- rows: rows.map((r, ri) => {
1964
- const cellArrays = Array.isArray(r.cells) ? r.cells : [];
1965
- const declaredCols = typeof r.cols === "number" && r.cols > 0 ? r.cols : 0;
1966
- const cols = Math.max(1, cellArrays.length, declaredCols);
1967
- const rowPad = r.padding && typeof r.padding === "object" && !Array.isArray(r.padding) ? layoutColumnPaddingForDoc(r.padding) : (() => {
1968
- const n = typeof r.padding === "number" && Number.isFinite(r.padding) ? r.padding : 0;
1969
- return { top: n, right: n, bottom: n, left: n };
1970
- })();
1971
- const { cells: _rowCells, ...rowEditorSnap } = r;
1972
- return {
1973
- id: r.id || `row_${ri + 1}`,
1974
- type: "row",
1975
- _reactEmailStudio: {
1976
- row: cloneJson(rowEditorSnap)
1977
- },
1978
- layout: {
1979
- columns: cols,
1980
- gap: r.gap ?? 0,
1981
- stackOnMobile: true,
1982
- align: "center"
1983
- },
1984
- styles: {
1985
- backgroundColor: r.bgColor || "",
1986
- backgroundImage: r.bgImage || "",
1987
- backgroundRepeat: r.bgRepeat || "no-repeat",
1988
- backgroundSize: r.bgSize || "cover",
1989
- backgroundPosition: r.bgPosition || "center",
1990
- backgroundGradient: r.bgGradient || null,
1991
- padding: rowPad,
1992
- borderRadius: 0,
1993
- borderWidth: 0,
1994
- borderColor: "#e5e7eb",
1995
- textAlign: "left"
1996
- },
1997
- columns: Array.from({ length: cols }, (_, ci) => {
1998
- const cellBlocks = cellArrays[ci] ?? [];
1999
- const cs = r.columnStyles && r.columnStyles[ci] || {};
2000
- return {
2001
- id: `col_${ri + 1}_${ci + 1}`,
2002
- layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2003
- styles: {
2004
- padding: layoutColumnPaddingForDoc(cs.padding),
2005
- backgroundColor: cs.bgColor || "",
2006
- backgroundImage: cs.bgImage || "",
2007
- backgroundRepeat: cs.bgRepeat || "no-repeat",
2008
- backgroundSize: cs.bgSize || "cover",
2009
- backgroundPosition: cs.bgPosition || "center",
2010
- backgroundGradient: cs.bgGradient || null,
2011
- borderRadius: layoutColumnBorderRadiusForDoc(cs.borderRadius)
2012
- },
2013
- blocks: (cellBlocks || []).map((b) => exportEmailDocBlock(b))
2014
- };
2015
- })
2016
- };
2017
- })
2645
+ settings: buildExportSettingsWithStudio(settings),
2646
+ rows: rows.map((r, i) => editorRowToEmailDocumentRow(r, i))
2018
2647
  };
2019
- return doc;
2648
+ }
2649
+ function canonicalizeEmailDocument(input) {
2650
+ const loaded = normalizeEmailDesignInput(coerceEmailDocumentInput(input) ?? input);
2651
+ if (!loaded?.rows) return null;
2652
+ return designToEmailDocument(loaded.rows, loaded.settings);
2020
2653
  }
2021
2654
  function hydrateBlock(b, depth = 0) {
2022
2655
  if (!b || typeof b !== "object") return null;
@@ -2030,13 +2663,13 @@ function hydrateBlock(b, depth = 0) {
2030
2663
  ...b,
2031
2664
  type: "layout",
2032
2665
  id: typeof b.id === "string" && b.id ? b.id : uid(),
2033
- props: {
2666
+ props: normalizeLayoutContainerProps({
2034
2667
  ...defaults2,
2035
2668
  ...b.props,
2036
2669
  bgSize: b.props.bgSize ?? defaults2.bgSize,
2037
2670
  bgRepeat: b.props.bgRepeat ?? defaults2.bgRepeat,
2038
2671
  cells: ratios.map(() => [])
2039
- }
2672
+ })
2040
2673
  };
2041
2674
  }
2042
2675
  const cells = b.props.cells.map(
@@ -2046,24 +2679,33 @@ function hydrateBlock(b, depth = 0) {
2046
2679
  ...b,
2047
2680
  type: "layout",
2048
2681
  id: typeof b.id === "string" && b.id ? b.id : uid(),
2049
- props: {
2682
+ props: normalizeLayoutContainerProps({
2050
2683
  ...defaults2,
2051
2684
  ...b.props,
2052
2685
  bgSize: b.props.bgSize ?? defaults2.bgSize,
2053
2686
  bgRepeat: b.props.bgRepeat ?? defaults2.bgRepeat,
2054
2687
  cells
2055
- }
2688
+ })
2056
2689
  };
2057
2690
  }
2058
2691
  const defaults = DEFAULT_BLOCK_PROPS[t];
2059
2692
  if (typeof defaults !== "object" || defaults == null || Array.isArray(defaults)) return null;
2060
- const merged = t === "html" ? {
2061
- ...defaults,
2062
- ...b.props && typeof b.props === "object" ? b.props : {},
2063
- content: normalizeRichHtmlForStorage(
2064
- b.props && typeof b.props === "object" ? b.props.content : void 0
2065
- )
2066
- } : { ...defaults, ...b.props && typeof b.props === "object" ? b.props : {} };
2693
+ if (t === "html") {
2694
+ const rawProps = b.props && typeof b.props === "object" && !Array.isArray(b.props) ? b.props : {};
2695
+ const { content: legacyPropContent, ...restProps } = rawProps;
2696
+ const merged2 = { ...defaults, ...restProps };
2697
+ normalizeBoxStyles(merged2, t);
2698
+ const src = typeof b.content === "string" ? String(b.content) : typeof legacyPropContent === "string" ? legacyPropContent : "";
2699
+ const htmlNorm = normalizeRichHtmlForStorage(src);
2700
+ merged2.content = htmlNorm;
2701
+ return {
2702
+ ...b,
2703
+ id: typeof b.id === "string" && b.id ? b.id : uid(),
2704
+ content: htmlNorm,
2705
+ props: merged2
2706
+ };
2707
+ }
2708
+ const merged = { ...defaults, ...b.props && typeof b.props === "object" ? b.props : {} };
2067
2709
  normalizeBoxStyles(merged, t);
2068
2710
  return {
2069
2711
  ...b,
@@ -2079,70 +2721,50 @@ function mapEmbeddedLayoutCellBlock(raw, layoutDepth = 0) {
2079
2721
  }
2080
2722
  return mapBlockToInternal(r, layoutDepth);
2081
2723
  }
2082
- function mergeLayoutColumnStylesMaps(fromContent, fromHydrated) {
2083
- const isObj = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
2084
- if (!isObj(fromContent) && !isObj(fromHydrated)) return void 0;
2085
- const a = isObj(fromContent) ? fromContent : {};
2086
- const b = isObj(fromHydrated) ? fromHydrated : {};
2087
- const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
2088
- const out = {};
2089
- for (const k of keys) {
2090
- const va = a[k];
2091
- const vb = b[k];
2092
- if (isObj(va) && isObj(vb)) {
2093
- out[k] = { ...va, ...vb };
2094
- } else if (vb !== void 0) {
2095
- out[k] = vb;
2096
- } else {
2097
- out[k] = va;
2098
- }
2099
- }
2100
- return Object.keys(out).length ? out : void 0;
2101
- }
2102
2724
  function applyImportedEditorPropsFromDoc(block, t, b, layoutDepth = 0) {
2103
2725
  const exp = b.props;
2104
- if (!exp || typeof exp !== "object" || Array.isArray(exp)) return;
2726
+ if (!exp || typeof exp !== "object" || Array.isArray(exp)) {
2727
+ if (t === "html" && typeof block.content === "string") {
2728
+ block.content = normalizeRichHtmlForStorage(block.content);
2729
+ block.props.content = block.content;
2730
+ }
2731
+ return;
2732
+ }
2105
2733
  if (t === "layout" && Array.isArray(exp.cells)) {
2106
- const columnStylesFromContent = block.props.columnStyles;
2734
+ const columnsFromContent = block.props.columns ?? block.props.columnStyles;
2735
+ const expHasColumns = Array.isArray(exp.columns) || exp.columnStyles && typeof exp.columnStyles === "object" && !Array.isArray(exp.columnStyles);
2107
2736
  const hb = hydrateBlock({ type: "layout", id: block.id, props: exp }, layoutDepth);
2108
2737
  if (hb) {
2109
2738
  block.props = hb.props;
2110
- const merged = mergeLayoutColumnStylesMaps(columnStylesFromContent, block.props.columnStyles);
2111
- if (merged) block.props.columnStyles = merged;
2112
- else delete block.props.columnStyles;
2739
+ const merged = mergeLayoutColumns(
2740
+ columnsFromContent,
2741
+ expHasColumns ? block.props.columns : void 0
2742
+ );
2743
+ if (merged) block.props.columns = merged;
2744
+ else delete block.props.columns;
2745
+ delete block.props.columnStyles;
2113
2746
  if (typeof hb.id === "string" && hb.id) block.id = hb.id;
2114
2747
  }
2115
2748
  return;
2116
2749
  }
2750
+ if (t === "html") {
2751
+ const { content: expContent, ...expRest } = exp;
2752
+ Object.assign(block.props, expRest);
2753
+ normalizeBoxStyles(block.props, t);
2754
+ if (typeof expContent === "string") {
2755
+ block.content = normalizeRichHtmlForStorage(expContent);
2756
+ } else if (typeof block.content === "string") {
2757
+ block.content = normalizeRichHtmlForStorage(block.content);
2758
+ }
2759
+ block.props.content = typeof block.content === "string" ? block.content : "";
2760
+ return;
2761
+ }
2117
2762
  Object.assign(block.props, exp);
2118
2763
  normalizeBoxStyles(block.props, t);
2119
- if (t === "html" || t === "text") {
2764
+ if (t === "text") {
2120
2765
  block.props.content = normalizeRichHtmlForStorage(String(block.props.content ?? ""));
2121
2766
  }
2122
2767
  }
2123
- function exportEmailDocBlock(b, depth = 0) {
2124
- if (!b || typeof b !== "object") return { id: uid(), type: "text", content: {}, styles: {} };
2125
- const doc = internalBlockToEmailDoc(b, depth);
2126
- const out = { ...doc };
2127
- if (b.props && typeof b.props === "object" && !Array.isArray(b.props)) {
2128
- out.props = cloneJson(b.props);
2129
- }
2130
- if (b.type === "layout" && Array.isArray(b.props?.cells)) {
2131
- const baseContent = typeof doc.content === "object" && doc.content ? doc.content : {};
2132
- if (depth >= MAX_LAYOUT_TREE_DEPTH) {
2133
- const ratios = Array.isArray(b.props.ratios) && b.props.ratios.length ? b.props.ratios : [1];
2134
- out.content = { ...baseContent, cells: ratios.map(() => []) };
2135
- } else {
2136
- out.content = {
2137
- ...baseContent,
2138
- cells: b.props.cells.map(
2139
- (col) => Array.isArray(col) ? col.map((child) => exportEmailDocBlock(child, depth + 1)) : []
2140
- )
2141
- };
2142
- }
2143
- }
2144
- return out;
2145
- }
2146
2768
  function hydrateLayoutRow(row) {
2147
2769
  if (!row || typeof row !== "object") return row;
2148
2770
  const cells = (row.cells || []).map(
@@ -2185,8 +2807,85 @@ function jsonToHtml(designInput, opts = {}) {
2185
2807
  return designToHtml(d.rows, d.settings, opts);
2186
2808
  }
2187
2809
 
2810
+ // src/lib/htmlToEmailDesign.ts
2811
+ var pad = (n) => ({ top: n, right: n, bottom: n, left: n });
2812
+ function extractHtmlForDesign(html) {
2813
+ const t = String(html ?? "").trim();
2814
+ if (!t) return "";
2815
+ const head = t.slice(0, 800).toLowerCase();
2816
+ if (!head.includes("<html") && !head.includes("<!doctype")) {
2817
+ return normalizeRichHtmlForStorage(t);
2818
+ }
2819
+ try {
2820
+ const doc = new DOMParser().parseFromString(t, "text/html");
2821
+ const body = doc.body;
2822
+ if (!body) return normalizeRichHtmlForStorage(t);
2823
+ return normalizeRichHtmlForStorage(body.innerHTML);
2824
+ } catch {
2825
+ return normalizeRichHtmlForStorage(t);
2826
+ }
2827
+ }
2828
+ function htmlToEmailDesignTemplate(html) {
2829
+ const inner = extractHtmlForDesign(html);
2830
+ if (!inner) return null;
2831
+ const doc = {
2832
+ type: "email_document",
2833
+ settings: {
2834
+ width: 600,
2835
+ backgroundColor: "#f1f5f9",
2836
+ contentBackgroundColor: "#ffffff",
2837
+ fontFamily: "Arial, Helvetica, sans-serif",
2838
+ lineHeightBase: 1.6,
2839
+ color: "#111827",
2840
+ responsive: true,
2841
+ rtl: false
2842
+ },
2843
+ rows: [
2844
+ {
2845
+ id: "row_html_import",
2846
+ type: "row",
2847
+ layout: { columns: 1, gap: 0, stackOnMobile: true, align: "center" },
2848
+ styles: {
2849
+ backgroundColor: "#ffffff",
2850
+ backgroundRepeat: "no-repeat",
2851
+ backgroundSize: "cover",
2852
+ padding: pad(24),
2853
+ textAlign: "left"
2854
+ },
2855
+ columns: [
2856
+ {
2857
+ id: "col_html_import",
2858
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
2859
+ styles: { padding: pad(0), backgroundColor: "" },
2860
+ blocks: [
2861
+ {
2862
+ id: "block_html_import",
2863
+ type: "html",
2864
+ content: inner,
2865
+ props: { ...DEFAULT_BLOCK_PROPS.html }
2866
+ }
2867
+ ]
2868
+ }
2869
+ ]
2870
+ }
2871
+ ]
2872
+ };
2873
+ return doc;
2874
+ }
2875
+ function htmlToJson(html, pretty = false) {
2876
+ const doc = htmlToEmailDesignTemplate(html);
2877
+ if (!doc) return "";
2878
+ return pretty ? JSON.stringify(doc, null, 2) : JSON.stringify(doc);
2879
+ }
2880
+
2188
2881
  // src/editor/canvas/DesignCanvas.tsx
2189
- import { useState, useRef, useCallback, useEffect, Fragment } from "react";
2882
+ import {
2883
+ useState,
2884
+ useRef,
2885
+ useCallback,
2886
+ useEffect,
2887
+ Fragment
2888
+ } from "react";
2190
2889
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2191
2890
  function useLiveCountdown(endDate) {
2192
2891
  const [diff, setDiff] = useState(0);
@@ -2325,13 +3024,15 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2325
3024
  )
2326
3025
  );
2327
3026
  if (type === "html") {
2328
- const emptyRich = isEffectivelyEmptyRichHtml(p.content);
3027
+ const richHtml = typeof block.content === "string" ? block.content : p.content;
3028
+ const emptyRich = isEffectivelyEmptyRichHtml(richHtml);
2329
3029
  return wrap(
2330
3030
  /* @__PURE__ */ jsxs2("div", { style: { position: "relative", minHeight: emptyRich && !preview ? "2.75em" : void 0 }, children: [
3031
+ /* @__PURE__ */ jsx3("style", { children: RICH_HTML_CONTENT_CSS }),
2331
3032
  /* @__PURE__ */ jsx3(
2332
3033
  "div",
2333
3034
  {
2334
- className: "email-editor-rich-canvas",
3035
+ className: "email-editor-rich-canvas email-rich-html-content",
2335
3036
  style: {
2336
3037
  fontSize: p.fontSize,
2337
3038
  color: p.color,
@@ -2342,7 +3043,7 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2342
3043
  wordBreak: "break-word",
2343
3044
  minHeight: emptyRich && !preview ? "2.75em" : void 0
2344
3045
  },
2345
- dangerouslySetInnerHTML: { __html: p.content || "<p></p>" }
3046
+ dangerouslySetInnerHTML: { __html: richHtml || "<p></p>" }
2346
3047
  }
2347
3048
  ),
2348
3049
  emptyRich && !preview ? /* @__PURE__ */ jsx3(
@@ -2390,7 +3091,7 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2390
3091
  }
2391
3092
  }
2392
3093
  );
2393
- return wrap(/* @__PURE__ */ jsx3("div", { style: { textAlign: p.align }, children: p.link ? /* @__PURE__ */ jsx3("a", { href: p.link, target: p.linkTarget || "_blank", rel: "noopener", children: img }) : img }));
3094
+ return wrap(/* @__PURE__ */ jsx3("div", { style: { textAlign: p.align }, children: imageLinkActive(p) ? /* @__PURE__ */ jsx3("a", { href: p.link, target: p.linkTarget || "_blank", rel: "noopener", children: img }) : img }));
2394
3095
  }
2395
3096
  if (type === "button") return wrap(
2396
3097
  /* @__PURE__ */ jsx3("div", { style: { textAlign: p.fullWidth ? "center" : p.align }, children: /* @__PURE__ */ jsx3(
@@ -2563,7 +3264,7 @@ function NestedRowBlock({
2563
3264
  );
2564
3265
  },
2565
3266
  children: /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: p.gap ?? 12 }, children: (p.cells || []).map((innerBlocks, ici) => {
2566
- const cS = p.columnStyles && p.columnStyles[ici] || {};
3267
+ const cS = getColumnPropsAt(p, ici);
2567
3268
  return /* @__PURE__ */ jsx3(
2568
3269
  "div",
2569
3270
  {
@@ -2621,7 +3322,9 @@ function BlockItem({
2621
3322
  onContextMenu,
2622
3323
  preview,
2623
3324
  C,
2624
- Line
3325
+ Line,
3326
+ dragAsRow = false,
3327
+ beginRowDrag
2625
3328
  }) {
2626
3329
  const wrapperRef = useRef(null);
2627
3330
  const bid = editorId ? `${editorId}-block-${cb.id}` : void 0;
@@ -2669,7 +3372,7 @@ function BlockItem({
2669
3372
  {
2670
3373
  "data-bidx": ci,
2671
3374
  draggable: !preview,
2672
- title: preview ? void 0 : "Click to select \xB7 drag to reorder or move",
3375
+ title: preview ? void 0 : dragAsRow ? "Click to select \xB7 drag to reorder section" : "Click to select \xB7 drag to reorder or move",
2673
3376
  onPointerDown: (e) => {
2674
3377
  if (preview || e.button !== 0) return;
2675
3378
  selectThisBlock(e);
@@ -2677,6 +3380,10 @@ function BlockItem({
2677
3380
  onDragStart: (e) => {
2678
3381
  if (preview) return;
2679
3382
  e.stopPropagation();
3383
+ if (dragAsRow && beginRowDrag) {
3384
+ beginRowDrag(e);
3385
+ return;
3386
+ }
2680
3387
  e.dataTransfer.effectAllowed = "move";
2681
3388
  e.dataTransfer.setData(
2682
3389
  "application/json",
@@ -2748,7 +3455,9 @@ function Cell({
2748
3455
  onDeleteContent,
2749
3456
  onContextMenu,
2750
3457
  preview,
2751
- C
3458
+ C,
3459
+ dragAsRow = false,
3460
+ beginRowDrag
2752
3461
  }) {
2753
3462
  const [insertAt, setInsertAt] = useState(null);
2754
3463
  const cellRef = useRef(null);
@@ -2763,6 +3472,14 @@ function Cell({
2763
3472
  return els.length;
2764
3473
  }, [blocks.length]);
2765
3474
  const handleDragOver = (e) => {
3475
+ try {
3476
+ const raw = e.dataTransfer.getData("application/json");
3477
+ if (raw) {
3478
+ const data = JSON.parse(raw);
3479
+ if (data.moveRowId) return;
3480
+ }
3481
+ } catch {
3482
+ }
2766
3483
  e.preventDefault();
2767
3484
  e.stopPropagation();
2768
3485
  setInsertAt(calcIdx(e.clientY));
@@ -2778,6 +3495,7 @@ function Cell({
2778
3495
  setInsertAt(null);
2779
3496
  try {
2780
3497
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
3498
+ if (data.moveRowId) return;
2781
3499
  if (data.contentType) {
2782
3500
  onDropContent(rowId, cellIdx, { kind: "new", contentType: data.contentType, insertAt: idx, nested: nestedPayload });
2783
3501
  } else if (data.moveContent) {
@@ -2857,7 +3575,9 @@ function Cell({
2857
3575
  onContextMenu,
2858
3576
  preview,
2859
3577
  C,
2860
- Line
3578
+ Line,
3579
+ dragAsRow: dragAsRow && blocks.length === 1 && ci === 0,
3580
+ beginRowDrag
2861
3581
  }
2862
3582
  ) }, cb.id))
2863
3583
  ]
@@ -2878,7 +3598,8 @@ function LayoutRow({
2878
3598
  onLayoutContextMenu,
2879
3599
  setSelectedRowId,
2880
3600
  preview = false,
2881
- rowDrag,
3601
+ rootContentRow = false,
3602
+ beginRowDrag,
2882
3603
  C
2883
3604
  }) {
2884
3605
  const rowStyle = {
@@ -2910,16 +3631,9 @@ function LayoutRow({
2910
3631
  onContextMenu: preview || !onLayoutContextMenu ? void 0 : (e) => {
2911
3632
  onLayoutContextMenu(e, row.id);
2912
3633
  },
2913
- draggable: rowDrag?.draggable === true,
2914
- onDragStart: rowDrag?.onDragStart,
2915
- onDragEnd: rowDrag?.onDragEnd,
2916
- title: rowDrag?.draggable ? "Click row \xB7 drag to reorder" : void 0,
2917
- style: {
2918
- ...rowStyle,
2919
- ...rowDrag?.draggable ? { cursor: "grab" } : {}
2920
- },
3634
+ style: rowStyle,
2921
3635
  children: /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: row.gap }, children: row.cells.map((cellBlocks, ci) => {
2922
- const cS = row.columnStyles && row.columnStyles[ci] || {};
3636
+ const cS = getColumnPropsAt(row, ci);
2923
3637
  return /* @__PURE__ */ jsx3(
2924
3638
  "div",
2925
3639
  {
@@ -2955,7 +3669,9 @@ function LayoutRow({
2955
3669
  onDeleteContent,
2956
3670
  onContextMenu,
2957
3671
  preview,
2958
- C
3672
+ C,
3673
+ dragAsRow: rootContentRow && ci === 0,
3674
+ beginRowDrag
2959
3675
  }
2960
3676
  )
2961
3677
  },
@@ -2972,10 +3688,11 @@ var CanvasRow = ({
2972
3688
  selectedRowId,
2973
3689
  selContentMeta: rowSelContentMeta,
2974
3690
  dragOver,
3691
+ draggingRowId,
2975
3692
  C,
2976
3693
  setDragOver,
2977
3694
  handleRowDrop,
2978
- setDraggingRowId,
3695
+ setRowDrag,
2979
3696
  setSelectedRowId,
2980
3697
  setSelContentId,
2981
3698
  setSelMeta,
@@ -2988,61 +3705,168 @@ var CanvasRow = ({
2988
3705
  }) => {
2989
3706
  const rowSelected = selectedRowId === row.id;
2990
3707
  const rid = (s) => editorId ? `${editorId}-${s}` : void 0;
2991
- return /* @__PURE__ */ jsxs2("div", { id: rid(`canvas-row-${row.id}`), children: [
2992
- /* @__PURE__ */ jsx3(
2993
- "div",
2994
- {
2995
- id: rid(`row-drop-before-${ri}`),
2996
- onDragOver: (e) => {
2997
- e.preventDefault();
2998
- setDragOver(ri);
2999
- },
3000
- onDragLeave: () => setDragOver(null),
3001
- onDrop: (e) => {
3002
- e.preventDefault();
3003
- handleRowDrop(ri, e);
3004
- },
3005
- 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" }
3708
+ const dropH = dragOver === ri || dragOver === ri + 1 ? 28 : 10;
3709
+ const rowBodyRef = useRef(null);
3710
+ const rootContentRow = isRootContentRow(row);
3711
+ const beginRowDrag = useCallback(
3712
+ (e) => {
3713
+ e.stopPropagation();
3714
+ e.dataTransfer.effectAllowed = "move";
3715
+ e.dataTransfer.setData(
3716
+ "application/json",
3717
+ JSON.stringify({ moveRowId: row.id })
3718
+ );
3719
+ setRowDrag(row.id);
3720
+ },
3721
+ [row.id, setRowDrag]
3722
+ );
3723
+ const rowDragOver = useCallback(
3724
+ (e) => {
3725
+ if (!draggingRowId || draggingRowId === row.id) return;
3726
+ e.preventDefault();
3727
+ e.stopPropagation();
3728
+ const el = rowBodyRef.current;
3729
+ if (!el) {
3730
+ setDragOver(ri);
3731
+ return;
3006
3732
  }
3007
- ),
3008
- /* @__PURE__ */ jsx3("div", { id: rid(`row-body-${row.id}`), style: { position: "relative", width: "100%", minWidth: 0, paddingLeft: 0, boxSizing: "border-box" }, children: /* @__PURE__ */ jsx3("div", { id: rid(`row-layout-wrap-${row.id}`), style: { width: "100%", minWidth: 0 }, children: /* @__PURE__ */ jsx3(
3009
- LayoutRow,
3010
- {
3011
- editorId,
3012
- row,
3013
- selected: rowSelected,
3014
- setSelectedRowId,
3015
- onClick: () => {
3016
- setSelectedRowId(row.id);
3017
- setSelContentId(null);
3018
- setSelMeta(null);
3019
- },
3020
- selectedContentKey: selContentId,
3021
- selContentMeta: rowSelContentMeta,
3022
- onSelectContent: selectContent,
3023
- onDropContent: dropContent,
3024
- onDeleteContent: deleteContent,
3025
- onContextMenu: handleContextMenu,
3026
- onLayoutContextMenu: handleLayoutContextMenu,
3027
- rowDrag: {
3028
- draggable: true,
3029
- onDragStart: (e) => {
3030
- e.stopPropagation();
3031
- setDraggingRowId(row.id);
3032
- },
3033
- onDragEnd: () => setDraggingRowId(null)
3034
- },
3035
- C
3733
+ const rect = el.getBoundingClientRect();
3734
+ setDragOver(e.clientY < rect.top + rect.height / 2 ? ri : ri + 1);
3735
+ },
3736
+ [draggingRowId, row.id, ri, setDragOver]
3737
+ );
3738
+ const rowDrop = useCallback(
3739
+ (e) => {
3740
+ if (!draggingRowId) return;
3741
+ e.preventDefault();
3742
+ e.stopPropagation();
3743
+ const el = rowBodyRef.current;
3744
+ let targetIdx = ri;
3745
+ if (el) {
3746
+ const rect = el.getBoundingClientRect();
3747
+ targetIdx = e.clientY < rect.top + rect.height / 2 ? ri : ri + 1;
3036
3748
  }
3037
- ) }) })
3038
- ] });
3749
+ handleRowDrop(targetIdx, e);
3750
+ },
3751
+ [draggingRowId, ri, handleRowDrop]
3752
+ );
3753
+ return /* @__PURE__ */ jsxs2(
3754
+ "div",
3755
+ {
3756
+ id: rid(`canvas-row-${row.id}`),
3757
+ style: { display: "flex", alignItems: "stretch", gap: 0, margin: "2px 0" },
3758
+ children: [
3759
+ /* @__PURE__ */ jsx3(
3760
+ "div",
3761
+ {
3762
+ draggable: true,
3763
+ title: "Drag to reorder section",
3764
+ onDragStart: beginRowDrag,
3765
+ onDragEnd: () => setRowDrag(null),
3766
+ onPointerDown: (e) => e.stopPropagation(),
3767
+ onClick: (e) => {
3768
+ e.stopPropagation();
3769
+ setSelectedRowId(row.id);
3770
+ setSelContentId(null);
3771
+ setSelMeta(null);
3772
+ },
3773
+ onContextMenu: (e) => {
3774
+ e.preventDefault();
3775
+ e.stopPropagation();
3776
+ handleLayoutContextMenu(e, row.id);
3777
+ },
3778
+ style: {
3779
+ width: 20,
3780
+ flexShrink: 0,
3781
+ cursor: "grab",
3782
+ display: "flex",
3783
+ alignItems: "center",
3784
+ justifyContent: "center",
3785
+ color: rowSelected ? C.accent : C.muted,
3786
+ fontSize: 12,
3787
+ fontWeight: 700,
3788
+ letterSpacing: -3,
3789
+ userSelect: "none",
3790
+ opacity: rowSelected ? 1 : 0.5
3791
+ },
3792
+ children: "\u22EE\u22EE"
3793
+ }
3794
+ ),
3795
+ /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0 }, children: [
3796
+ /* @__PURE__ */ jsx3(
3797
+ "div",
3798
+ {
3799
+ id: rid(`row-drop-before-${ri}`),
3800
+ onDragOver: (e) => {
3801
+ e.preventDefault();
3802
+ e.stopPropagation();
3803
+ setDragOver(ri);
3804
+ },
3805
+ onDragLeave: () => setDragOver(null),
3806
+ onDrop: (e) => handleRowDrop(ri, e),
3807
+ style: {
3808
+ height: dropH,
3809
+ background: dragOver === ri ? `${C.accent}25` : "transparent",
3810
+ border: dragOver === ri ? `2px dashed ${C.accent}` : "2px solid transparent",
3811
+ borderRadius: 4,
3812
+ transition: "all .12s",
3813
+ boxSizing: "border-box"
3814
+ }
3815
+ }
3816
+ ),
3817
+ /* @__PURE__ */ jsx3(
3818
+ "div",
3819
+ {
3820
+ ref: rowBodyRef,
3821
+ id: rid(`row-body-${row.id}`),
3822
+ onDragOver: rowDragOver,
3823
+ onDrop: rowDrop,
3824
+ style: {
3825
+ position: "relative",
3826
+ width: "100%",
3827
+ minWidth: 0,
3828
+ boxSizing: "border-box",
3829
+ outline: draggingRowId && draggingRowId !== row.id && (dragOver === ri || dragOver === ri + 1) ? `2px dashed ${C.accent}` : void 0,
3830
+ outlineOffset: 2,
3831
+ borderRadius: 6
3832
+ },
3833
+ children: /* @__PURE__ */ jsx3("div", { id: rid(`row-layout-wrap-${row.id}`), style: { width: "100%", minWidth: 0 }, children: /* @__PURE__ */ jsx3(
3834
+ LayoutRow,
3835
+ {
3836
+ editorId,
3837
+ row,
3838
+ selected: rowSelected,
3839
+ setSelectedRowId,
3840
+ onClick: () => {
3841
+ setSelectedRowId(row.id);
3842
+ setSelContentId(null);
3843
+ setSelMeta(null);
3844
+ },
3845
+ selectedContentKey: selContentId,
3846
+ selContentMeta: rowSelContentMeta,
3847
+ onSelectContent: selectContent,
3848
+ onDropContent: dropContent,
3849
+ onDeleteContent: deleteContent,
3850
+ onContextMenu: handleContextMenu,
3851
+ onLayoutContextMenu: handleLayoutContextMenu,
3852
+ rootContentRow,
3853
+ beginRowDrag,
3854
+ C
3855
+ }
3856
+ ) })
3857
+ }
3858
+ )
3859
+ ] })
3860
+ ]
3861
+ }
3862
+ );
3039
3863
  };
3040
3864
 
3041
3865
  // src/ReactEmailEditor.tsx
3042
3866
  import { Image as ImageIcon2, Repeat as RepeatIcon, Scaling as Scaling2, Crosshair as Crosshair2, Blend as GradientIcon2, Droplets as ColorIcon } from "lucide-react";
3043
3867
 
3044
3868
  // src/editor/properties/PropertyEditors.tsx
3045
- import { useRef as useRef2, useState as useState3, useEffect as useEffect3, useMemo, Fragment as Fragment3 } from "react";
3869
+ import { useRef as useRef2, useState as useState3, useEffect as useEffect3, useMemo } from "react";
3046
3870
  import {
3047
3871
  AlignCenter as AlignCenter2,
3048
3872
  AlignJustify as AlignJustify2,
@@ -3059,7 +3883,7 @@ import {
3059
3883
  // src/editor/properties/TextRichEditor.tsx
3060
3884
  import { useEffect as useEffect2, useId, useState as useState2 } from "react";
3061
3885
  import { createPortal } from "react-dom";
3062
- import { useEditor, EditorContent } from "@tiptap/react";
3886
+ import { useEditor, EditorContent, useEditorState } from "@tiptap/react";
3063
3887
  import StarterKit from "@tiptap/starter-kit";
3064
3888
  import Placeholder from "@tiptap/extension-placeholder";
3065
3889
  import TextAlign from "@tiptap/extension-text-align";
@@ -3157,6 +3981,10 @@ function paddingToCss(pad3, fallback) {
3157
3981
  var FONT_SIZE_PX = [10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28, 32, 36, 42, 48, 56, 64, 72];
3158
3982
  var LINE_HEIGHT_OPTS = ["1", "1.15", "1.25", "1.5", "1.65", "1.75", "2", "2.5"];
3159
3983
  var LETTER_SPACING_OPTS = ["-0.5px", "0px", "0.5px", "1px", "1.5px", "2px", "3px", "4px"];
3984
+ function rgbChannelToHex(n) {
3985
+ const v = Math.max(0, Math.min(255, Math.round(n)));
3986
+ return v.toString(16).padStart(2, "0");
3987
+ }
3160
3988
  function hexForColorInput(color) {
3161
3989
  if (!color || typeof color !== "string") return "#000000";
3162
3990
  const c = color.trim();
@@ -3169,8 +3997,34 @@ function hexForColorInput(color) {
3169
3997
  }
3170
3998
  return c.slice(0, 7);
3171
3999
  }
4000
+ const rgb = c.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i);
4001
+ if (rgb) {
4002
+ return `#${rgbChannelToHex(+rgb[1])}${rgbChannelToHex(+rgb[2])}${rgbChannelToHex(+rgb[3])}`;
4003
+ }
3172
4004
  return "#000000";
3173
4005
  }
4006
+ function textStyleColorFromEditor(editor) {
4007
+ const ts = editor.getAttributes("textStyle");
4008
+ if (ts.color && String(ts.color).trim()) return String(ts.color).trim();
4009
+ const stored = editor.state.storedMarks;
4010
+ if (stored) {
4011
+ for (const m of stored) {
4012
+ if (m.type.name === "textStyle" && m.attrs.color) {
4013
+ return String(m.attrs.color).trim();
4014
+ }
4015
+ }
4016
+ }
4017
+ const { from, to } = editor.state.selection;
4018
+ if (from === to) return null;
4019
+ const domAt = editor.view.domAtPos(from);
4020
+ let el = domAt.node.nodeType === Node.TEXT_NODE ? domAt.node.parentElement : domAt.node;
4021
+ while (el && el !== editor.view.dom) {
4022
+ const inline = el.style?.color?.trim();
4023
+ if (inline) return inline;
4024
+ el = el.parentElement;
4025
+ }
4026
+ return null;
4027
+ }
3174
4028
  function parsePxSize(raw) {
3175
4029
  if (!raw) return "";
3176
4030
  const s = String(raw).trim();
@@ -3237,13 +4091,22 @@ function toolbarIconBtn(C, active) {
3237
4091
  }
3238
4092
  function ToolbarTypographyControls({ editor, C }) {
3239
4093
  const sel = toolbarSelect(C);
3240
- const ts = editor.getAttributes("textStyle");
4094
+ const { ts, align, textColor } = useEditorState({
4095
+ editor,
4096
+ selector: ({ editor: ed }) => {
4097
+ const attrs = ed.getAttributes("textStyle");
4098
+ return {
4099
+ ts: attrs,
4100
+ align: currentBlockTextAlign(ed),
4101
+ textColor: textStyleColorFromEditor(ed)
4102
+ };
4103
+ }
4104
+ });
3241
4105
  const fontFamily = ts.fontFamily || "";
3242
4106
  const sizeVal = parsePxSize(ts.fontSize);
3243
4107
  const lhVal = normalizeLineHeightAttr(ts.lineHeight);
3244
4108
  const lsVal = normalizeLetterSpacingAttr(ts.letterSpacing);
3245
4109
  const lsSelectValue = LETTER_SPACING_OPTS.includes(lsVal) ? lsVal : lsVal || "";
3246
- const align = currentBlockTextAlign(editor);
3247
4110
  return /* @__PURE__ */ jsxs3(Fragment2, { children: [
3248
4111
  /* @__PURE__ */ jsxs3(
3249
4112
  "select",
@@ -3326,9 +4189,10 @@ function ToolbarTypographyControls({ editor, C }) {
3326
4189
  {
3327
4190
  type: "color",
3328
4191
  title: "Text color",
3329
- value: hexForColorInput(ts.color),
4192
+ value: hexForColorInput(textColor ?? ts.color),
3330
4193
  onChange: (e) => {
3331
- void editor.chain().focus().setColor(e.target.value).run();
4194
+ const hex = e.target.value;
4195
+ void editor.chain().focus().setColor(hex).run();
3332
4196
  },
3333
4197
  style: {
3334
4198
  width: 28,
@@ -3701,7 +4565,7 @@ function TextRichEditor({
3701
4565
  }
3702
4566
  }),
3703
4567
  TextStyle,
3704
- Color,
4568
+ Color.configure({ types: ["textStyle"] }),
3705
4569
  FontFamily,
3706
4570
  FontSize,
3707
4571
  LineHeight,
@@ -4878,11 +5742,141 @@ function BlockSurfaceBgInspector({
4878
5742
  ] }) : null
4879
5743
  ] });
4880
5744
  }
5745
+ function HtmlBlockRichPanel({
5746
+ block,
5747
+ onChange,
5748
+ C
5749
+ }) {
5750
+ const p = block.props;
5751
+ const { IS } = useIS(C);
5752
+ const [mode, setMode] = useState3("visual");
5753
+ const [htmlDraft, setHtmlDraft] = useState3("");
5754
+ useEffect3(() => {
5755
+ setMode("visual");
5756
+ }, [block.id]);
5757
+ const body = normalizeRichHtmlForStorage(typeof block.content === "string" ? block.content : "");
5758
+ const applyHtml = (html) => {
5759
+ const norm = normalizeRichHtmlForStorage(html);
5760
+ onChange({ ...block, content: norm, props: { ...p, content: norm } });
5761
+ };
5762
+ const tabBtn = (active) => ({
5763
+ flex: 1,
5764
+ padding: "7px 10px",
5765
+ borderRadius: 6,
5766
+ border: `1px solid ${C.border}`,
5767
+ background: active ? C.accent : C.surface,
5768
+ color: active ? "#ffffff" : C.text,
5769
+ fontSize: 12,
5770
+ fontWeight: 700,
5771
+ cursor: "pointer"
5772
+ });
5773
+ return /* @__PURE__ */ jsx6(PR, { label: "Content", C, children: /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
5774
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 8 }, children: [
5775
+ /* @__PURE__ */ jsx6(
5776
+ "button",
5777
+ {
5778
+ type: "button",
5779
+ style: tabBtn(mode === "visual"),
5780
+ onClick: () => {
5781
+ if (mode === "html") applyHtml(htmlDraft);
5782
+ setMode("visual");
5783
+ },
5784
+ children: "Rich text"
5785
+ }
5786
+ ),
5787
+ /* @__PURE__ */ jsx6(
5788
+ "button",
5789
+ {
5790
+ type: "button",
5791
+ style: tabBtn(mode === "html"),
5792
+ onClick: () => {
5793
+ setHtmlDraft(body);
5794
+ setMode("html");
5795
+ },
5796
+ children: "HTML"
5797
+ }
5798
+ )
5799
+ ] }),
5800
+ mode === "visual" ? /* @__PURE__ */ jsx6(
5801
+ TextRichEditor,
5802
+ {
5803
+ value: body,
5804
+ onChange: applyHtml,
5805
+ typography: {
5806
+ fontSize: p.fontSize,
5807
+ color: p.color,
5808
+ align: p.align,
5809
+ fontFamily: p.fontFamily,
5810
+ lineHeight: p.lineHeight,
5811
+ letterSpacing: p.letterSpacing,
5812
+ padding: p.padding
5813
+ },
5814
+ placeholder: "Write your rich content\u2026",
5815
+ headerTitle: "Rich editor",
5816
+ C
5817
+ },
5818
+ block.id
5819
+ ) : /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
5820
+ /* @__PURE__ */ jsx6(
5821
+ "textarea",
5822
+ {
5823
+ spellCheck: false,
5824
+ value: htmlDraft,
5825
+ onChange: (e) => setHtmlDraft(e.target.value),
5826
+ placeholder: "<p>Your HTML\u2026</p>",
5827
+ style: {
5828
+ ...IS,
5829
+ minHeight: 220,
5830
+ resize: "vertical",
5831
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
5832
+ fontSize: 12,
5833
+ lineHeight: 1.45
5834
+ }
5835
+ }
5836
+ ),
5837
+ /* @__PURE__ */ jsxs4("span", { style: { fontSize: 10, color: C.muted, lineHeight: 1.45 }, children: [
5838
+ "Raw HTML. Switch to ",
5839
+ /* @__PURE__ */ jsx6("strong", { children: "Rich text" }),
5840
+ " to apply and preview in the visual editor."
5841
+ ] }),
5842
+ /* @__PURE__ */ jsx6(
5843
+ "button",
5844
+ {
5845
+ type: "button",
5846
+ onClick: () => applyHtml(htmlDraft),
5847
+ style: {
5848
+ alignSelf: "flex-start",
5849
+ background: C.surface,
5850
+ border: `1px solid ${C.border}`,
5851
+ color: C.accent,
5852
+ borderRadius: 5,
5853
+ cursor: "pointer",
5854
+ fontSize: 11,
5855
+ padding: "6px 12px",
5856
+ fontWeight: 600
5857
+ },
5858
+ children: "Apply (normalize)"
5859
+ }
5860
+ )
5861
+ ] })
5862
+ ] }) });
5863
+ }
4881
5864
  function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4882
5865
  const { IS, CI } = useIS(C);
4883
5866
  const imageFileRef = useRef2(null);
4884
5867
  const p = block.props;
4885
- const set = (k, v) => onChange({ ...block, props: { ...p, [k]: v } });
5868
+ const set = (k, v) => {
5869
+ if (block.type === "html" && k === "content") {
5870
+ onChange({ ...block, content: v, props: { ...p, content: v } });
5871
+ return;
5872
+ }
5873
+ if (block.type === "html") {
5874
+ const body = typeof block.content === "string" ? block.content : typeof p.content === "string" ? p.content : "";
5875
+ onChange({ ...block, props: { ...p, [k]: v, content: body } });
5876
+ return;
5877
+ }
5878
+ onChange({ ...block, props: { ...p, [k]: v } });
5879
+ };
4886
5880
  const Num = (k, lbl, mn = 0, mx = 300) => /* @__PURE__ */ jsx6(PR, { label: lbl, C, children: /* @__PURE__ */ jsx6("input", { type: "number", style: IS, value: p[k] ?? 0, min: mn, max: mx, onChange: (e) => set(k, +e.target.value) }) });
4887
5881
  const Txt = (k, lbl, multi = false) => /* @__PURE__ */ jsx6(PR, { label: lbl, C, children: multi ? /* @__PURE__ */ jsx6("textarea", { style: { ...IS, minHeight: 68, resize: "vertical" }, value: p[k] || "", onChange: (e) => set(k, e.target.value) }) : /* @__PURE__ */ jsx6("input", { type: "text", style: IS, value: p[k] || "", onChange: (e) => set(k, e.target.value) }) });
4888
5882
  const Col = (k, lbl) => /* @__PURE__ */ jsx6(PR, { label: lbl, C, children: /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
@@ -4976,25 +5970,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4976
5970
  ] });
4977
5971
  case "html":
4978
5972
  return /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
4979
- /* @__PURE__ */ jsx6(Fragment3, { children: /* @__PURE__ */ jsx6(
4980
- TextRichEditor,
4981
- {
4982
- value: normalizeRichHtmlForStorage(p.content),
4983
- onChange: (html) => set("content", html),
4984
- typography: {
4985
- fontSize: p.fontSize,
4986
- color: p.color,
4987
- align: p.align,
4988
- fontFamily: p.fontFamily,
4989
- lineHeight: p.lineHeight,
4990
- letterSpacing: p.letterSpacing,
4991
- padding: p.padding
4992
- },
4993
- placeholder: "Write your rich content\u2026",
4994
- headerTitle: "Rich editor",
4995
- C
4996
- }
4997
- ) }, block.id),
5973
+ /* @__PURE__ */ jsx6(HtmlBlockRichPanel, { block, onChange, C }),
4998
5974
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
4999
5975
  /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5000
5976
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
@@ -5014,8 +5990,25 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5014
5990
  "Object position"
5015
5991
  ] }), C, children: /* @__PURE__ */ jsx6("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__ */ jsx6("option", { value: v, children: v }, v)) }) }),
5016
5992
  Txt("alt", "Alt Text"),
5017
- Txt("link", "Link URL"),
5018
- Sel("linkTarget", "Open In", ["_blank", "_self"]),
5993
+ /* @__PURE__ */ jsx6(PR, { label: "Link image", C, children: /* @__PURE__ */ jsxs4("label", { style: { display: "flex", alignItems: "center", gap: 7, cursor: "pointer" }, children: [
5994
+ /* @__PURE__ */ jsx6(
5995
+ "input",
5996
+ {
5997
+ type: "checkbox",
5998
+ checked: !!(p.linkEnabled ?? (p.link && String(p.link).trim())),
5999
+ onChange: (e) => {
6000
+ if (e.target.checked) set("linkEnabled", true);
6001
+ else onChange({ ...block, props: { ...p, linkEnabled: false, link: "" } });
6002
+ },
6003
+ style: { width: 15, height: 15, accentColor: C.accent }
6004
+ }
6005
+ ),
6006
+ /* @__PURE__ */ jsx6("span", { style: { color: C.muted, fontSize: 12 }, children: p.linkEnabled ?? (p.link && String(p.link).trim()) ? "On" : "Off" })
6007
+ ] }) }),
6008
+ p.linkEnabled ?? (p.link && String(p.link).trim()) ? /* @__PURE__ */ jsxs4(Fragment4, { children: [
6009
+ Txt("link", "Link URL"),
6010
+ Sel("linkTarget", "Open In", ["_blank", "_self"])
6011
+ ] }) : null,
5019
6012
  /* @__PURE__ */ jsx6(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right"], C }),
5020
6013
  /* @__PURE__ */ jsx6(BorderRadiusEditor, { value: p.borderRadius, onChange: (br) => set("borderRadius", br), C }),
5021
6014
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
@@ -5365,7 +6358,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5365
6358
  bgPosition: p.bgPosition ?? "center",
5366
6359
  bgGradient: p.bgGradient ?? null,
5367
6360
  cells: p.cells || [],
5368
- columnStyles: p.columnStyles
6361
+ columns: getColumns(p)
5369
6362
  },
5370
6363
  onChange: (row) => onChange({
5371
6364
  ...block,
@@ -5383,7 +6376,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5383
6376
  bgGradient: row.bgGradient,
5384
6377
  ratios: [...row.ratios || [1]],
5385
6378
  cells: row.cells || [],
5386
- columnStyles: row.columnStyles
6379
+ columns: row.columns ?? getColumns(row)
5387
6380
  }
5388
6381
  }),
5389
6382
  onClose,
@@ -5402,8 +6395,8 @@ function inferLayoutRowBgStep(r) {
5402
6395
  if (String(r.bgColor || "").trim()) return "solid";
5403
6396
  return null;
5404
6397
  }
5405
- function inferColumnBgStep(columnStyles, selCol) {
5406
- const cs = (columnStyles || {})[selCol] || {};
6398
+ function inferColumnBgStep(container, selCol) {
6399
+ const cs = getColumnPropsAt(container, selCol);
5407
6400
  if (cs.bgGradient) return "gradient";
5408
6401
  if (String(cs.bgImage || "").trim()) return "image";
5409
6402
  if (String(cs.bgColor || "").trim()) return "solid";
@@ -5415,6 +6408,10 @@ function LayoutRowEditor({
5415
6408
  onClose,
5416
6409
  onUpload,
5417
6410
  initialCol = null,
6411
+ rowIndex = -1,
6412
+ rowCount = 1,
6413
+ onMoveUp,
6414
+ onMoveDown,
5418
6415
  C,
5419
6416
  /** Shown under the inspector header when editing a Layout block */
5420
6417
  customizationHint
@@ -5427,8 +6424,8 @@ function LayoutRowEditor({
5427
6424
  const [colBgPickerMode, setColBgPickerMode] = useState3(null);
5428
6425
  const inferredRowBg = useMemo(() => inferLayoutRowBgStep(row), [row.bgGradient, row.bgImage, row.bgColor]);
5429
6426
  const inferredColBg = useMemo(
5430
- () => inferColumnBgStep(row.columnStyles, selCol),
5431
- [row.columnStyles, selCol]
6427
+ () => inferColumnBgStep(row, selCol),
6428
+ [row.columns, row.columnStyles, selCol]
5432
6429
  );
5433
6430
  const rowBgUiStep = inferredRowBg ?? rowBgPickerMode;
5434
6431
  const colBgUiStep = inferredColBg ?? colBgPickerMode;
@@ -5467,13 +6464,12 @@ function LayoutRowEditor({
5467
6464
  onChange({ ...row, cols: n, preset: "custom", cells, ratios });
5468
6465
  };
5469
6466
  const updCol = (i, upd) => {
5470
- const styles = { ...row.columnStyles || {} };
5471
- styles[i] = { ...styles[i] || {}, ...upd };
5472
- set("columnStyles", styles);
6467
+ const columns = patchColumnPropsAt(row, i, upd);
6468
+ onChange(withColumnsOnly(row, columns));
5473
6469
  };
5474
6470
  const setColBgMode = (mode) => {
5475
6471
  setColBgPickerMode(mode);
5476
- const cs = (row.columnStyles || {})[selCol] || {};
6472
+ const cs = getColumnPropsAt(row, selCol);
5477
6473
  if (mode === "solid") {
5478
6474
  updCol(selCol, { bgGradient: null, bgImage: "" });
5479
6475
  return;
@@ -5505,7 +6501,51 @@ function LayoutRowEditor({
5505
6501
  set("bgGradient", { angle: 135, colors: ["#0ea5e9", "#6366f1", "#ec4899"], stops: [0, 50, 100] });
5506
6502
  }
5507
6503
  };
6504
+ const canMoveUp = rowIndex > 0;
6505
+ const canMoveDown = rowIndex >= 0 && rowIndex < rowCount - 1;
5508
6506
  return /* @__PURE__ */ jsxs4(Fragment4, { children: [
6507
+ onMoveUp && onMoveDown ? /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 6, marginBottom: 12 }, children: [
6508
+ /* @__PURE__ */ jsx6(
6509
+ "button",
6510
+ {
6511
+ type: "button",
6512
+ disabled: !canMoveUp,
6513
+ onClick: onMoveUp,
6514
+ style: {
6515
+ flex: 1,
6516
+ padding: "8px 10px",
6517
+ fontSize: 12,
6518
+ fontWeight: 600,
6519
+ borderRadius: 8,
6520
+ border: `1px solid ${C.border}`,
6521
+ background: canMoveUp ? C.surface : C.canvas,
6522
+ color: canMoveUp ? C.text : C.muted,
6523
+ cursor: canMoveUp ? "pointer" : "not-allowed"
6524
+ },
6525
+ children: "\u2191 Move section up"
6526
+ }
6527
+ ),
6528
+ /* @__PURE__ */ jsx6(
6529
+ "button",
6530
+ {
6531
+ type: "button",
6532
+ disabled: !canMoveDown,
6533
+ onClick: onMoveDown,
6534
+ style: {
6535
+ flex: 1,
6536
+ padding: "8px 10px",
6537
+ fontSize: 12,
6538
+ fontWeight: 600,
6539
+ borderRadius: 8,
6540
+ border: `1px solid ${C.border}`,
6541
+ background: canMoveDown ? C.surface : C.canvas,
6542
+ color: canMoveDown ? C.text : C.muted,
6543
+ cursor: canMoveDown ? "pointer" : "not-allowed"
6544
+ },
6545
+ children: "\u2193 Move section down"
6546
+ }
6547
+ )
6548
+ ] }) : null,
5509
6549
  customizationHint ? /* @__PURE__ */ jsx6(
5510
6550
  "div",
5511
6551
  {
@@ -5655,15 +6695,15 @@ function LayoutRowEditor({
5655
6695
  /* @__PURE__ */ jsx6(
5656
6696
  ColorSwatchGrid,
5657
6697
  {
5658
- value: (row.columnStyles || {})[selCol]?.bgColor || "",
6698
+ value: getColumnPropsAt(row, selCol)?.bgColor || "",
5659
6699
  C,
5660
6700
  compact: true,
5661
6701
  onPick: (hex) => updCol(selCol, { bgColor: hex === "#ffffff" ? "" : hex })
5662
6702
  }
5663
6703
  ),
5664
6704
  /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 5, alignItems: "center" }, children: [
5665
- /* @__PURE__ */ jsx6("input", { type: "color", style: CI, value: (row.columnStyles || {})[selCol]?.bgColor || "#ffffff", onChange: (e) => updCol(selCol, { bgColor: e.target.value === "#ffffff" ? "" : e.target.value }) }),
5666
- /* @__PURE__ */ jsx6("input", { type: "text", style: { ...IS, width: 120 }, placeholder: "transparent", value: (row.columnStyles || {})[selCol]?.bgColor || "", onChange: (e) => updCol(selCol, { bgColor: e.target.value }) })
6705
+ /* @__PURE__ */ jsx6("input", { type: "color", style: CI, value: getColumnPropsAt(row, selCol)?.bgColor || "#ffffff", onChange: (e) => updCol(selCol, { bgColor: e.target.value === "#ffffff" ? "" : e.target.value }) }),
6706
+ /* @__PURE__ */ jsx6("input", { type: "text", style: { ...IS, width: 120 }, placeholder: "transparent", value: getColumnPropsAt(row, selCol)?.bgColor || "", onChange: (e) => updCol(selCol, { bgColor: e.target.value }) })
5667
6707
  ] })
5668
6708
  ] }) }) : null,
5669
6709
  colBgUiStep === "image" ? /* @__PURE__ */ jsxs4(Fragment4, { children: [
@@ -5671,7 +6711,7 @@ function LayoutRowEditor({
5671
6711
  /* @__PURE__ */ jsx6(ImageIcon, { size: 13, strokeWidth: 2.25, "aria-hidden": true }),
5672
6712
  "bg image"
5673
6713
  ] }), C, children: /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
5674
- /* @__PURE__ */ jsx6("input", { type: "text", style: IS, placeholder: "https://\u2026", value: (row.columnStyles || {})[selCol]?.bgImage || "", onChange: (e) => updCol(selCol, { bgImage: e.target.value }) }),
6714
+ /* @__PURE__ */ jsx6("input", { type: "text", style: IS, placeholder: "https://\u2026", value: getColumnPropsAt(row, selCol)?.bgImage || "", onChange: (e) => updCol(selCol, { bgImage: e.target.value }) }),
5675
6715
  onUpload && /* @__PURE__ */ jsxs4("div", { style: { marginTop: 0 }, children: [
5676
6716
  /* @__PURE__ */ jsx6(
5677
6717
  "input",
@@ -5709,18 +6749,18 @@ function LayoutRowEditor({
5709
6749
  ] })
5710
6750
  ] }) }),
5711
6751
  /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 8, marginBottom: 12 }, children: [
5712
- /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(PR, { label: "fit", C, children: /* @__PURE__ */ jsxs4("select", { style: IS, value: (row.columnStyles || {})[selCol]?.bgSize || "cover", onChange: (e) => updCol(selCol, { bgSize: e.target.value }), children: [
6752
+ /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(PR, { label: "fit", C, children: /* @__PURE__ */ jsxs4("select", { style: IS, value: getColumnPropsAt(row, selCol)?.bgSize || "cover", onChange: (e) => updCol(selCol, { bgSize: e.target.value }), children: [
5713
6753
  /* @__PURE__ */ jsx6("option", { value: "auto", children: "Auto" }),
5714
6754
  /* @__PURE__ */ jsx6("option", { value: "cover", children: "Cover" }),
5715
6755
  /* @__PURE__ */ jsx6("option", { value: "contain", children: "Contain" })
5716
6756
  ] }) }) }),
5717
- /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(PR, { label: "repeat", C, children: /* @__PURE__ */ jsxs4("select", { style: IS, value: (row.columnStyles || {})[selCol]?.bgRepeat || "no-repeat", onChange: (e) => updCol(selCol, { bgRepeat: e.target.value }), children: [
6757
+ /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(PR, { label: "repeat", C, children: /* @__PURE__ */ jsxs4("select", { style: IS, value: getColumnPropsAt(row, selCol)?.bgRepeat || "no-repeat", onChange: (e) => updCol(selCol, { bgRepeat: e.target.value }), children: [
5718
6758
  /* @__PURE__ */ jsx6("option", { value: "no-repeat", children: "No repeat" }),
5719
6759
  /* @__PURE__ */ jsx6("option", { value: "repeat", children: "Repeat" }),
5720
6760
  /* @__PURE__ */ jsx6("option", { value: "repeat-x", children: "Repeat X" }),
5721
6761
  /* @__PURE__ */ jsx6("option", { value: "repeat-y", children: "Repeat Y" })
5722
6762
  ] }) }) }),
5723
- /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(PR, { label: "align", C, children: /* @__PURE__ */ jsx6("select", { style: IS, value: (row.columnStyles || {})[selCol]?.bgPosition || "center", onChange: (e) => updCol(selCol, { bgPosition: e.target.value }), children: POS_OPTS.map((p) => /* @__PURE__ */ jsx6("option", { value: p, children: p }, p)) }) }) })
6763
+ /* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(PR, { label: "align", C, children: /* @__PURE__ */ jsx6("select", { style: IS, value: getColumnPropsAt(row, selCol)?.bgPosition || "center", onChange: (e) => updCol(selCol, { bgPosition: e.target.value }), children: POS_OPTS.map((p) => /* @__PURE__ */ jsx6("option", { value: p, children: p }, p)) }) }) })
5724
6764
  ] })
5725
6765
  ] }) : null,
5726
6766
  colBgUiStep === "gradient" ? /* @__PURE__ */ jsxs4("details", { open: true, style: { marginBottom: 12 }, children: [
@@ -5729,15 +6769,15 @@ function LayoutRowEditor({
5729
6769
  DynamicGradientField,
5730
6770
  {
5731
6771
  label: "gradient",
5732
- value: (row.columnStyles || {})[selCol]?.bgGradient,
6772
+ value: getColumnPropsAt(row, selCol)?.bgGradient,
5733
6773
  onChange: (g) => updCol(selCol, { bgGradient: g }),
5734
6774
  defaults: { colors: ["#0ea5e9", "#6366f1", "#ec4899"] },
5735
6775
  C
5736
6776
  }
5737
6777
  ) })
5738
6778
  ] }) : null,
5739
- /* @__PURE__ */ jsx6(PaddingEditor, { label: "Column padding", value: (row.columnStyles || {})[selCol]?.padding, onChange: (pad3) => updCol(selCol, { padding: pad3 }), C }),
5740
- /* @__PURE__ */ jsx6(BorderRadiusEditor, { label: "Column radius", value: (row.columnStyles || {})[selCol]?.borderRadius, onChange: (br) => updCol(selCol, { borderRadius: br }), C })
6779
+ /* @__PURE__ */ jsx6(PaddingEditor, { label: "Column padding", value: getColumnPropsAt(row, selCol).padding, onChange: (pad3) => updCol(selCol, { padding: pad3 }), C }),
6780
+ /* @__PURE__ */ jsx6(BorderRadiusEditor, { label: "Column radius", value: getColumnPropsAt(row, selCol).borderRadius, onChange: (br) => updCol(selCol, { borderRadius: br }), C })
5741
6781
  ] })
5742
6782
  ] })
5743
6783
  ] });
@@ -6534,6 +7574,11 @@ var ReactEmailEditorComponent = forwardRef(
6534
7574
  const [selContentMeta, setSelMeta] = useState6(null);
6535
7575
  const [dragOver, setDragOver] = useState6(null);
6536
7576
  const [draggingRowId, setDraggingRowId] = useState6(null);
7577
+ const draggingRowIdRef = useRef6(null);
7578
+ const setRowDrag = useCallback2((id) => {
7579
+ draggingRowIdRef.current = id;
7580
+ setDraggingRowId(id);
7581
+ }, []);
6537
7582
  const [history, setHistory] = useState6([]);
6538
7583
  const [future, setFuture] = useState6([]);
6539
7584
  const [showPreviewModal, setShowPreview] = useState6(false);
@@ -6568,10 +7613,17 @@ var ReactEmailEditorComponent = forwardRef(
6568
7613
  });
6569
7614
  const [pageBgUiStep, setPageBgUiStep] = useState6(null);
6570
7615
  const [contentBgUiStep, setContentBgUiStep] = useState6(null);
7616
+ const pageBgLastSolidRef = useRef6("#f1f5f9");
7617
+ const pageBgIsOff = !String(settings.bgColor ?? "").trim() && !String(settings.bgImage ?? "").trim() && !settings.bgGradient;
6571
7618
  const applyPageBgMode = (mode) => {
6572
7619
  setPageBgUiStep(mode);
6573
7620
  if (mode === "solid") {
6574
- setSettings((s) => ({ ...s, bgGradient: null, bgImage: "" }));
7621
+ setSettings((s) => {
7622
+ const next = { ...s, bgGradient: null, bgImage: "" };
7623
+ const raw = typeof next.bgColor === "string" ? next.bgColor.trim() : "";
7624
+ if (!raw) next.bgColor = pageBgLastSolidRef.current || "#f1f5f9";
7625
+ return next;
7626
+ });
6575
7627
  return;
6576
7628
  }
6577
7629
  if (mode === "image") {
@@ -6681,7 +7733,7 @@ var ReactEmailEditorComponent = forwardRef(
6681
7733
  return () => window.removeEventListener("keydown", handler);
6682
7734
  }, [rows, history, future, selContentMeta]);
6683
7735
  const buildApi = useCallback2(() => ({
6684
- loadJson(input) {
7736
+ loadJson(input, options2) {
6685
7737
  setJsonLoading(true);
6686
7738
  requestAnimationFrame(() => {
6687
7739
  requestAnimationFrame(() => {
@@ -6694,7 +7746,14 @@ var ReactEmailEditorComponent = forwardRef(
6694
7746
  return;
6695
7747
  }
6696
7748
  }
6697
- const norm = normalizeEmailDesignInput(parsed);
7749
+ const coerced = coerceEmailDocumentInput(parsed);
7750
+ if (options2?.mode === "append") {
7751
+ const extra = emailDocumentToEditorRows(coerced ?? parsed);
7752
+ if (!extra?.length) return;
7753
+ setRows((prev) => [...prev, ...extra]);
7754
+ return;
7755
+ }
7756
+ const norm = normalizeEmailDesignInput(coerced ?? parsed);
6698
7757
  if (!norm) return;
6699
7758
  setRows(norm.rows);
6700
7759
  const ns = norm.settings;
@@ -6731,6 +7790,8 @@ var ReactEmailEditorComponent = forwardRef(
6731
7790
  }, []);
6732
7791
  const handleRowDrop = (targetIdx, e) => {
6733
7792
  if (e) {
7793
+ e.preventDefault();
7794
+ e.stopPropagation();
6734
7795
  try {
6735
7796
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
6736
7797
  if (data.layoutPresetKey) {
@@ -6749,18 +7810,28 @@ var ReactEmailEditorComponent = forwardRef(
6749
7810
  } catch {
6750
7811
  }
6751
7812
  }
6752
- if (draggingRowId) {
7813
+ const moveId = draggingRowIdRef.current ?? draggingRowId ?? (() => {
7814
+ if (!e) return null;
7815
+ try {
7816
+ const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
7817
+ return typeof data.moveRowId === "string" ? data.moveRowId : null;
7818
+ } catch {
7819
+ return null;
7820
+ }
7821
+ })();
7822
+ if (moveId) {
6753
7823
  mutate((prev) => {
6754
- const fi = prev.findIndex((r) => r.id === draggingRowId);
7824
+ const fi = prev.findIndex((r) => r.id === moveId);
6755
7825
  if (fi === -1) return prev;
6756
7826
  const next = [...prev];
6757
7827
  const [m] = next.splice(fi, 1);
6758
- next.splice(targetIdx > fi ? targetIdx - 1 : targetIdx, 0, m);
7828
+ const insertAt = targetIdx > fi ? targetIdx - 1 : targetIdx;
7829
+ next.splice(Math.max(0, Math.min(insertAt, next.length)), 0, m);
6759
7830
  return next;
6760
7831
  });
6761
7832
  }
6762
7833
  setDragOver(null);
6763
- setDraggingRowId(null);
7834
+ setRowDrag(null);
6764
7835
  };
6765
7836
  const addRow = (preset) => mutate((prev) => [...prev, makeLayoutRow(preset)]);
6766
7837
  const deleteRow = (id) => {
@@ -6890,8 +7961,27 @@ var ReactEmailEditorComponent = forwardRef(
6890
7961
  }
6891
7962
  }
6892
7963
  };
7964
+ const rootContentPreset = LAYOUT_PRESETS.find((p) => p.cols === 1) ?? LAYOUT_PRESETS[0];
7965
+ const insertRootContentBlock = (contentType) => {
7966
+ const { row: newRow, block: nb } = makeRootContentRow(contentType, rootContentPreset);
7967
+ const anchorRowId = selContentMeta?.rowId ?? selectedRowId ?? null;
7968
+ mutate((prev) => {
7969
+ if (anchorRowId) {
7970
+ const i = prev.findIndex((r) => r.id === anchorRowId);
7971
+ if (i >= 0) {
7972
+ const next = [...prev];
7973
+ next.splice(i + 1, 0, newRow);
7974
+ return next;
7975
+ }
7976
+ }
7977
+ return [...prev, newRow];
7978
+ });
7979
+ setSelectedRowId(null);
7980
+ setSelContentId(nb.id);
7981
+ setSelMeta({ rowId: newRow.id, cellIdx: 0, contentIdx: 0 });
7982
+ };
6893
7983
  const insertBlockFromLibrary = (contentType) => {
6894
- if (!rows.length) return;
7984
+ const isLayoutType = contentType === "layout" || contentType === "nestedRow";
6895
7985
  if (selContentMeta?.inner) {
6896
7986
  const { rowId, cellIdx, contentIdx, inner } = selContentMeta;
6897
7987
  dropContent(rowId, cellIdx, {
@@ -6902,30 +7992,26 @@ var ReactEmailEditorComponent = forwardRef(
6902
7992
  });
6903
7993
  return;
6904
7994
  }
6905
- if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0) {
6906
- const r = rows.find((x) => x.id === selContentMeta.rowId);
6907
- if (r && selContentMeta.cellIdx < r.cells.length) {
6908
- dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
6909
- kind: "new",
6910
- contentType,
6911
- insertAt: null
6912
- });
6913
- return;
7995
+ if (isLayoutType) {
7996
+ if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0 && typeof selContentMeta.contentIdx === "number" && selContentMeta.contentIdx >= 0) {
7997
+ const r = rows.find((x) => x.id === selContentMeta.rowId);
7998
+ if (r && selContentMeta.cellIdx < r.cells.length) {
7999
+ dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
8000
+ kind: "new",
8001
+ contentType,
8002
+ insertAt: null
8003
+ });
8004
+ return;
8005
+ }
6914
8006
  }
8007
+ addRow(rootContentPreset);
8008
+ return;
6915
8009
  }
6916
- if (selectedRowId) {
6917
- const targetRow = rows.find((r) => r.id === selectedRowId);
6918
- if (targetRow) {
6919
- dropContent(targetRow.id, 0, {
6920
- kind: "new",
6921
- contentType,
6922
- insertAt: null
6923
- });
6924
- return;
6925
- }
8010
+ if (!rows.length) {
8011
+ insertRootContentBlock(contentType);
8012
+ return;
6926
8013
  }
6927
- const main = rows[0];
6928
- dropContent(main.id, 0, { kind: "new", contentType, insertAt: null });
8014
+ insertRootContentBlock(contentType);
6929
8015
  };
6930
8016
  const deleteContent = (rowId, cellIdx, ci, inner = null) => {
6931
8017
  if (!inner) {
@@ -7520,6 +8606,7 @@ var ReactEmailEditorComponent = forwardRef(
7520
8606
  e.preventDefault();
7521
8607
  try {
7522
8608
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
8609
+ if (data.moveRowId) return;
7523
8610
  if (data.layoutPresetKey) {
7524
8611
  const preset = LAYOUT_PRESETS.find((p) => p.key === data.layoutPresetKey);
7525
8612
  if (preset) mutate((prev) => [...prev, makeLayoutRow(preset)]);
@@ -7574,6 +8661,7 @@ var ReactEmailEditorComponent = forwardRef(
7574
8661
  e.stopPropagation();
7575
8662
  try {
7576
8663
  const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
8664
+ if (data.moveRowId) return;
7577
8665
  if (data.contentType && typeof data.contentType === "string") {
7578
8666
  insertBlockFromLibrary(data.contentType);
7579
8667
  }
@@ -7595,10 +8683,11 @@ var ReactEmailEditorComponent = forwardRef(
7595
8683
  selectedRowId,
7596
8684
  selContentMeta,
7597
8685
  dragOver,
8686
+ draggingRowId,
7598
8687
  C,
7599
8688
  setDragOver,
7600
8689
  handleRowDrop,
7601
- setDraggingRowId,
8690
+ setRowDrag,
7602
8691
  setSelectedRowId,
7603
8692
  setSelContentId,
7604
8693
  setSelMeta,
@@ -7616,14 +8705,20 @@ var ReactEmailEditorComponent = forwardRef(
7616
8705
  id: eid("canvas-row-drop-end"),
7617
8706
  onDragOver: (e) => {
7618
8707
  e.preventDefault();
8708
+ e.stopPropagation();
7619
8709
  setDragOver(rows.length);
7620
8710
  },
7621
8711
  onDragLeave: () => setDragOver(null),
7622
- onDrop: (e) => {
7623
- e.preventDefault();
7624
- handleRowDrop(rows.length, e);
7625
- },
7626
- 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" }
8712
+ onDrop: (e) => handleRowDrop(rows.length, e),
8713
+ style: {
8714
+ height: dragOver === rows.length ? 28 : 10,
8715
+ background: dragOver === rows.length ? `${C.accent}25` : "transparent",
8716
+ border: dragOver === rows.length ? `2px dashed ${C.accent}` : "2px solid transparent",
8717
+ borderRadius: 4,
8718
+ transition: "all .12s",
8719
+ margin: "2px 0",
8720
+ boxSizing: "border-box"
8721
+ }
7627
8722
  }
7628
8723
  )
7629
8724
  ]
@@ -7735,6 +8830,10 @@ var ReactEmailEditorComponent = forwardRef(
7735
8830
  onClose: () => setSelectedRowId(null),
7736
8831
  onUpload,
7737
8832
  initialCol: selContentMeta?.rowId === selectedRowId ? selContentMeta.cellIdx : null,
8833
+ rowIndex: rows.findIndex((r) => r.id === selectedRowId),
8834
+ rowCount: rows.length,
8835
+ onMoveUp: () => selectedRow && moveRow(selectedRow.id, -1),
8836
+ onMoveDown: () => selectedRow && moveRow(selectedRow.id, 1),
7738
8837
  C
7739
8838
  }
7740
8839
  ) })
@@ -7911,7 +9010,40 @@ var ReactEmailEditorComponent = forwardRef(
7911
9010
  /* @__PURE__ */ jsx11("summary", { style: railSectionSummary, children: "Background" }),
7912
9011
  /* @__PURE__ */ jsxs9("div", { style: railSectionBody, children: [
7913
9012
  /* @__PURE__ */ jsx11("div", { style: { fontSize: 10, color: C.muted, fontWeight: 800, textTransform: "uppercase", letterSpacing: ".06em", marginBottom: 6 }, children: "Page background" }),
7914
- /* @__PURE__ */ jsx11(BgModeButtons, { value: pageBgUiStep, onChange: applyPageBgMode, C }),
9013
+ /* @__PURE__ */ jsx11("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }, children: /* @__PURE__ */ jsxs9("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", userSelect: "none" }, children: [
9014
+ /* @__PURE__ */ jsx11(
9015
+ "input",
9016
+ {
9017
+ type: "checkbox",
9018
+ checked: pageBgIsOff,
9019
+ onChange: (e) => {
9020
+ const off = e.target.checked;
9021
+ if (off) {
9022
+ const cur = String(settings.bgColor ?? "").trim();
9023
+ if (cur) pageBgLastSolidRef.current = cur;
9024
+ setPageBgUiStep(null);
9025
+ setSettings((s) => ({ ...s, bgColor: "", bgImage: "", bgGradient: null }));
9026
+ } else {
9027
+ setPageBgUiStep("solid");
9028
+ setSettings((s) => ({
9029
+ ...s,
9030
+ bgColor: pageBgLastSolidRef.current || "#f1f5f9"
9031
+ }));
9032
+ }
9033
+ },
9034
+ style: { width: 15, height: 15, accentColor: C.accent, cursor: "pointer" }
9035
+ }
9036
+ ),
9037
+ /* @__PURE__ */ jsx11("span", { style: { color: C.muted, fontSize: 12 }, children: "No page background" })
9038
+ ] }) }),
9039
+ /* @__PURE__ */ jsx11(
9040
+ BgModeButtons,
9041
+ {
9042
+ value: pageBgIsOff ? null : pageBgUiStep,
9043
+ onChange: applyPageBgMode,
9044
+ C
9045
+ }
9046
+ ),
7915
9047
  pageBgUiStep === null ? /* @__PURE__ */ jsx11("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,
7916
9048
  pageBgUiStep === "solid" ? /* @__PURE__ */ jsx11(
7917
9049
  ColorField,
@@ -7921,7 +9053,10 @@ var ReactEmailEditorComponent = forwardRef(
7921
9053
  "bg color"
7922
9054
  ] }),
7923
9055
  value: settings.bgColor,
7924
- onChange: (v) => setSettings((s) => ({ ...s, bgColor: v })),
9056
+ onChange: (v) => {
9057
+ pageBgLastSolidRef.current = v;
9058
+ setSettings((s) => ({ ...s, bgColor: v }));
9059
+ },
7925
9060
  placeholder: "#f1f5f9",
7926
9061
  C
7927
9062
  }
@@ -8162,80 +9297,17 @@ var ReactEmailEditorComponent = forwardRef(
8162
9297
  );
8163
9298
  }
8164
9299
  );
8165
- var ReactEmailEditor2 = Object.assign(ReactEmailEditorComponent, { jsonToHtml });
8166
-
8167
- // src/lib/htmlToEmailDesign.ts
8168
- var pad = (n) => ({ top: n, right: n, bottom: n, left: n });
8169
- function extractHtmlForDesign(html) {
8170
- const t = String(html ?? "").trim();
8171
- if (!t) return "";
8172
- const head = t.slice(0, 800).toLowerCase();
8173
- if (!head.includes("<html") && !head.includes("<!doctype")) {
8174
- return normalizeRichHtmlForStorage(t);
8175
- }
8176
- try {
8177
- const doc = new DOMParser().parseFromString(t, "text/html");
8178
- const body = doc.body;
8179
- if (!body) return normalizeRichHtmlForStorage(t);
8180
- return normalizeRichHtmlForStorage(body.innerHTML);
8181
- } catch {
8182
- return normalizeRichHtmlForStorage(t);
8183
- }
8184
- }
8185
- function htmlToEmailDesignTemplate(html) {
8186
- const inner = extractHtmlForDesign(html);
8187
- if (!inner) return null;
8188
- const doc = {
8189
- type: "email_document",
8190
- settings: {
8191
- width: 600,
8192
- backgroundColor: "#f1f5f9",
8193
- contentBackgroundColor: "#ffffff",
8194
- fontFamily: "Arial, Helvetica, sans-serif",
8195
- lineHeightBase: 1.6,
8196
- color: "#111827",
8197
- responsive: true,
8198
- rtl: false
8199
- },
8200
- rows: [
8201
- {
8202
- id: "row_html_import",
8203
- type: "row",
8204
- layout: { columns: 1, gap: 0, stackOnMobile: true, align: "center" },
8205
- styles: {
8206
- backgroundColor: "#ffffff",
8207
- backgroundRepeat: "no-repeat",
8208
- backgroundSize: "cover",
8209
- padding: pad(24),
8210
- textAlign: "left"
8211
- },
8212
- columns: [
8213
- {
8214
- id: "col_html_import",
8215
- layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
8216
- styles: { padding: pad(0), backgroundColor: "" },
8217
- blocks: [
8218
- {
8219
- id: "block_html_import",
8220
- type: "html",
8221
- content: { html: inner },
8222
- styles: {}
8223
- }
8224
- ]
8225
- }
8226
- ]
8227
- }
8228
- ]
8229
- };
8230
- return doc;
8231
- }
9300
+ var ReactEmailEditor2 = Object.assign(ReactEmailEditorComponent, { jsonToHtml, htmlToJson });
8232
9301
  export {
8233
9302
  PreviewModal as EmailPreviewModal,
8234
9303
  ReactEmailEditor2 as ReactEmailEditor,
8235
9304
  base64ToUtf8,
9305
+ canonicalizeEmailDocument,
9306
+ coerceEmailDocumentInput,
8236
9307
  DEVICES as emailPreviewDevices,
8237
9308
  extractHtmlForDesign,
8238
9309
  htmlToEmailDesignTemplate,
9310
+ htmlToJson,
8239
9311
  jsonToHtml,
8240
9312
  utf8ToBase64
8241
9313
  };