react-email-studio 2.0.0 → 3.0.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
@@ -660,6 +660,10 @@ function makeContentBlock(type) {
660
660
  }
661
661
 
662
662
  // src/lib/columnPath.ts
663
+ var MAX_NESTED_LAYOUT_DEPTH = 1;
664
+ function nestedLayoutDepth(nested) {
665
+ return nested != null && nested.parentBlockIdx != null && nested.innerCellIdx != null ? 1 : 0;
666
+ }
663
667
  function isSplitLayoutBlock(b) {
664
668
  return b && b.type === "layout" && b.props && Array.isArray(b.props.cells);
665
669
  }
@@ -1251,8 +1255,18 @@ function normalizeEmailDocument(input) {
1251
1255
  settings: doc.settings && typeof doc.settings === "object" && !Array.isArray(doc.settings) ? doc.settings : {},
1252
1256
  rows: rows.map((r, ri) => {
1253
1257
  const columns = Array.isArray(r.columns) ? r.columns : [];
1254
- const colCount = typeof r.layout?.columns === "number" && r.layout.columns > 0 ? Math.floor(r.layout.columns) : Math.max(1, columns.length || 1);
1255
- const colsNormalized = columns.length > 0 ? columns : Array.from({ length: colCount }).map(() => ({}));
1258
+ const layoutColCount = typeof r.layout?.columns === "number" && r.layout.columns > 0 ? Math.floor(r.layout.columns) : 0;
1259
+ const colCount = Math.max(1, columns.length, layoutColCount);
1260
+ const colsNormalized = (() => {
1261
+ if (columns.length >= colCount) return columns;
1262
+ if (columns.length > 0) {
1263
+ return [
1264
+ ...columns,
1265
+ ...Array.from({ length: colCount - columns.length }).map(() => ({}))
1266
+ ];
1267
+ }
1268
+ return Array.from({ length: colCount }).map(() => ({}));
1269
+ })();
1256
1270
  return {
1257
1271
  id: ensureId(r.id, `row${ri + 1}`),
1258
1272
  type: "row",
@@ -1263,6 +1277,7 @@ function normalizeEmailDocument(input) {
1263
1277
  align: r.layout?.align || "center"
1264
1278
  },
1265
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 } : {},
1266
1281
  columns: colsNormalized.map((c, ci) => ({
1267
1282
  id: ensureId(c.id, `col${ri + 1}_${ci + 1}`),
1268
1283
  layout: c.layout && typeof c.layout === "object" && !Array.isArray(c.layout) ? c.layout : {},
@@ -1273,7 +1288,8 @@ function normalizeEmailDocument(input) {
1273
1288
  content: b?.content && typeof b.content === "object" && !Array.isArray(b.content) ? b.content : {},
1274
1289
  styles: b?.styles && typeof b.styles === "object" && !Array.isArray(b.styles) ? b.styles : {},
1275
1290
  behavior: b?.behavior && typeof b.behavior === "object" && !Array.isArray(b.behavior) ? b.behavior : {},
1276
- responsive: b?.responsive && typeof b.responsive === "object" && !Array.isArray(b.responsive) ? b.responsive : {}
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 } : {}
1277
1293
  })) : []
1278
1294
  }))
1279
1295
  };
@@ -1284,6 +1300,14 @@ function normalizeEmailDocument(input) {
1284
1300
  }
1285
1301
 
1286
1302
  // src/lib/emailDesignJson.ts
1303
+ var MAX_LAYOUT_TREE_DEPTH = 32;
1304
+ function cloneJson(v) {
1305
+ try {
1306
+ return JSON.parse(JSON.stringify(v));
1307
+ } catch {
1308
+ return v;
1309
+ }
1310
+ }
1287
1311
  function layoutColumnPaddingForDoc(p) {
1288
1312
  if (typeof p === "number" && Number.isFinite(p)) {
1289
1313
  const n = Math.max(0, p);
@@ -1370,7 +1394,242 @@ function normalizeBoxStyles(props, t) {
1370
1394
  }
1371
1395
  props.padding = boxPad(props.padding, uniformPadDefault(def?.padding));
1372
1396
  }
1373
- function mapBlockToInternal(b) {
1397
+ function internalBlockToEmailDoc(b, depth = 0) {
1398
+ const p = b?.props && typeof b.props === "object" ? b.props : {};
1399
+ const id = typeof b?.id === "string" ? b.id : uid();
1400
+ const type = b?.type === "nestedRow" ? "layout" : b?.type;
1401
+ if (!isKnownBlockType(type)) {
1402
+ return { id, type: String(type || "text"), content: {}, styles: {} };
1403
+ }
1404
+ switch (type) {
1405
+ case "heading": {
1406
+ const fw = typeof p.fontWeight === "number" ? p.fontWeight : typeof p.fontWeight === "string" ? Number.parseInt(String(p.fontWeight), 10) || (p.bold ? 700 : 400) : p.bold ? 700 : 400;
1407
+ const padUniform = typeof p.padding === "number" && Number.isFinite(p.padding) ? p.padding : uniformPadDefault(p.padding);
1408
+ return {
1409
+ id,
1410
+ type,
1411
+ content: { text: typeof p.content === "string" ? p.content : "", tag: p.level || "h2" },
1412
+ styles: {
1413
+ fontSize: p.fontSize,
1414
+ fontWeight: fw,
1415
+ color: p.color,
1416
+ lineHeight: p.lineHeight,
1417
+ textAlign: p.align,
1418
+ letterSpacing: p.letterSpacing,
1419
+ marginBottom: padUniform
1420
+ }
1421
+ };
1422
+ }
1423
+ case "text":
1424
+ return {
1425
+ id,
1426
+ type,
1427
+ content: { html: typeof p.content === "string" ? p.content : "" },
1428
+ styles: {
1429
+ fontSize: p.fontSize,
1430
+ color: p.color,
1431
+ lineHeight: p.lineHeight,
1432
+ textAlign: p.align,
1433
+ ...p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1434
+ }
1435
+ };
1436
+ case "html":
1437
+ return {
1438
+ id,
1439
+ type,
1440
+ content: { html: typeof p.content === "string" ? p.content : "" },
1441
+ styles: p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1442
+ };
1443
+ case "image": {
1444
+ const br = p.borderRadius;
1445
+ const borderRadius = typeof br === "number" && Number.isFinite(br) ? br : br && typeof br === "object" && !Array.isArray(br) ? Math.round(
1446
+ (numOr(br.tl, 0) + numOr(br.tr, numOr(br.tl, 0)) + numOr(br.br, numOr(br.tl, 0)) + numOr(br.bl, numOr(br.tr, 0))) / 4
1447
+ ) : 0;
1448
+ return {
1449
+ id,
1450
+ type,
1451
+ content: {
1452
+ src: p.src || "",
1453
+ alt: p.alt || "",
1454
+ link: p.link || ""
1455
+ },
1456
+ styles: {
1457
+ width: p.width,
1458
+ align: p.align,
1459
+ borderRadius
1460
+ },
1461
+ ...p.linkTarget === "_blank" ? { behavior: { openInNewTab: true } } : {}
1462
+ };
1463
+ }
1464
+ case "button": {
1465
+ let borderRadiusOut;
1466
+ if (typeof p.borderRadius === "number" && Number.isFinite(p.borderRadius)) {
1467
+ borderRadiusOut = p.borderRadius;
1468
+ } else if (p.borderRadius && typeof p.borderRadius === "object" && !Array.isArray(p.borderRadius)) {
1469
+ const br = p.borderRadius;
1470
+ const tl = numOr(br.tl, 0);
1471
+ const tr2 = numOr(br.tr, tl);
1472
+ const brc = numOr(br.br, tl);
1473
+ const bl = numOr(br.bl, tr2);
1474
+ borderRadiusOut = tl === tr2 && tr2 === brc && brc === bl ? tl : Math.round((tl + tr2 + brc + bl) / 4);
1475
+ }
1476
+ return {
1477
+ id,
1478
+ type,
1479
+ content: { text: p.label || "", href: p.href || "#" },
1480
+ styles: {
1481
+ backgroundColor: p.bgColor,
1482
+ color: p.textColor,
1483
+ fontSize: p.fontSize,
1484
+ fontWeight: p.fontWeight,
1485
+ ...borderRadiusOut !== void 0 ? { borderRadius: borderRadiusOut } : {},
1486
+ textAlign: p.align,
1487
+ padding: {
1488
+ top: p.paddingV ?? 11,
1489
+ right: p.paddingH ?? 24,
1490
+ bottom: p.paddingV ?? 11,
1491
+ left: p.paddingH ?? 24
1492
+ }
1493
+ },
1494
+ ...p.linkTarget === "_blank" ? { behavior: { openInNewTab: true } } : {}
1495
+ };
1496
+ }
1497
+ case "divider": {
1498
+ const padY = typeof p.padding === "number" && Number.isFinite(p.padding) ? p.padding : p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? numOr(p.padding.top, 12) : 12;
1499
+ return {
1500
+ id,
1501
+ type,
1502
+ content: {},
1503
+ styles: { color: p.color, thickness: p.thickness, paddingY: padY }
1504
+ };
1505
+ }
1506
+ case "spacer":
1507
+ return { id, type, content: {}, styles: { height: p.height } };
1508
+ case "social": {
1509
+ const urls = p.socialUrls && typeof p.socialUrls === "object" && !Array.isArray(p.socialUrls) ? p.socialUrls : {};
1510
+ const networks = {};
1511
+ const ordered = Array.isArray(p.networks) ? p.networks.filter((n) => typeof n === "string" && !!n) : [];
1512
+ const seen = /* @__PURE__ */ new Set();
1513
+ for (const n of ordered) {
1514
+ const u = urls[n];
1515
+ networks[n] = typeof u === "string" && u.trim() ? u : "";
1516
+ seen.add(n);
1517
+ }
1518
+ for (const key of Object.keys(urls)) {
1519
+ if (seen.has(key)) continue;
1520
+ const v = urls[key];
1521
+ if (typeof v === "string" && v.trim()) {
1522
+ networks[key] = v;
1523
+ seen.add(key);
1524
+ }
1525
+ }
1526
+ const padStyles = p.padding != null ? typeof p.padding === "number" && Number.isFinite(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {} : {};
1527
+ return {
1528
+ id,
1529
+ type,
1530
+ content: { networks },
1531
+ styles: {
1532
+ iconSize: p.iconSize,
1533
+ gap: p.gap,
1534
+ shape: p.shape,
1535
+ align: p.align,
1536
+ ...padStyles
1537
+ }
1538
+ };
1539
+ }
1540
+ case "table":
1541
+ return {
1542
+ id,
1543
+ type,
1544
+ content: { data: Array.isArray(p.data) ? p.data : [] },
1545
+ styles: {
1546
+ borderColor: p.cellBorder,
1547
+ cellPadding: p.cellPadding,
1548
+ headerBg: p.headerBg,
1549
+ striped: p.striped
1550
+ }
1551
+ };
1552
+ case "video":
1553
+ return {
1554
+ id,
1555
+ type,
1556
+ content: { url: p.src || "" },
1557
+ styles: { height: p.height }
1558
+ };
1559
+ case "timer": {
1560
+ let timerBr;
1561
+ if (typeof p.borderRadius === "number" && Number.isFinite(p.borderRadius)) {
1562
+ timerBr = p.borderRadius;
1563
+ } else if (p.borderRadius && typeof p.borderRadius === "object" && !Array.isArray(p.borderRadius)) {
1564
+ const br = p.borderRadius;
1565
+ const tl = numOr(br.tl, 0);
1566
+ const tr2 = numOr(br.tr, tl);
1567
+ const brc = numOr(br.br, tl);
1568
+ const bl = numOr(br.bl, tr2);
1569
+ timerBr = tl === tr2 && tr2 === brc && brc === bl ? tl : Math.round((tl + tr2 + brc + bl) / 4);
1570
+ }
1571
+ return {
1572
+ id,
1573
+ type,
1574
+ content: { endDate: p.endDate || "" },
1575
+ styles: {
1576
+ backgroundColor: p.bgColor,
1577
+ color: p.textColor,
1578
+ padding: typeof p.padding === "number" ? p.padding : uniformPadDefault(p.padding),
1579
+ ...timerBr !== void 0 ? { borderRadius: timerBr } : {},
1580
+ textAlign: p.align
1581
+ }
1582
+ };
1583
+ }
1584
+ case "menu":
1585
+ return {
1586
+ id,
1587
+ type,
1588
+ content: { items: Array.isArray(p.items) ? p.items : [] },
1589
+ styles: { fontSize: p.fontSize, color: p.color }
1590
+ };
1591
+ case "link":
1592
+ return {
1593
+ id,
1594
+ type,
1595
+ content: { text: p.label || "", href: p.href || "#" },
1596
+ styles: { color: p.color, fontSize: p.fontSize, textAlign: p.align },
1597
+ ...p.linkTarget === "_blank" ? { behavior: { openInNewTab: true } } : {}
1598
+ };
1599
+ case "layout": {
1600
+ const ratios = Array.isArray(p.ratios) ? [...p.ratios] : [1];
1601
+ const emptyCells = ratios.map(() => []);
1602
+ const rawCells = Array.isArray(p.cells) ? p.cells : [];
1603
+ const cellsOut = depth >= MAX_LAYOUT_TREE_DEPTH ? emptyCells : ratios.map((_, i) => {
1604
+ const col = rawCells[i];
1605
+ return Array.isArray(col) ? col.map((child) => internalBlockToEmailDoc(child, depth + 1)) : [];
1606
+ });
1607
+ return {
1608
+ id,
1609
+ type,
1610
+ content: {
1611
+ preset: p.preset,
1612
+ cols: p.cols,
1613
+ ratios,
1614
+ gap: p.gap,
1615
+ padding: p.padding,
1616
+ bgColor: p.bgColor,
1617
+ bgImage: p.bgImage,
1618
+ bgSize: p.bgSize,
1619
+ bgRepeat: p.bgRepeat,
1620
+ bgPosition: p.bgPosition,
1621
+ bgGradient: p.bgGradient ?? null,
1622
+ columnStyles: p.columnStyles,
1623
+ cells: cellsOut
1624
+ },
1625
+ styles: {}
1626
+ };
1627
+ }
1628
+ default:
1629
+ return { id, type, content: {}, styles: {} };
1630
+ }
1631
+ }
1632
+ function mapBlockToInternal(b, layoutDepth = 0) {
1374
1633
  const t = b.type;
1375
1634
  if (!isKnownBlockType(t)) return null;
1376
1635
  const block = makeContentBlock(t);
@@ -1441,8 +1700,28 @@ function mapBlockToInternal(b) {
1441
1700
  if (asNum(s.height) != null) block.props.height = s.height;
1442
1701
  break;
1443
1702
  case "social": {
1444
- const networksObj = c.networks && typeof c.networks === "object" ? c.networks : {};
1445
- const keys = Object.keys(networksObj || {});
1703
+ const networksRaw = c.networks;
1704
+ let networksObj = {};
1705
+ if (Array.isArray(networksRaw)) {
1706
+ const urlMap = c.socialUrls && typeof c.socialUrls === "object" && !Array.isArray(c.socialUrls) ? c.socialUrls : {};
1707
+ for (const n of networksRaw) {
1708
+ if (typeof n !== "string" || !n) continue;
1709
+ const u = urlMap[n];
1710
+ networksObj[n] = typeof u === "string" ? u : "";
1711
+ }
1712
+ for (const key of Object.keys(urlMap)) {
1713
+ if (key in networksObj) continue;
1714
+ const u = urlMap[key];
1715
+ if (typeof u === "string" && u.trim()) networksObj[key] = u;
1716
+ }
1717
+ } else if (networksRaw && typeof networksRaw === "object" && !Array.isArray(networksRaw)) {
1718
+ networksObj = { ...networksRaw };
1719
+ for (const k of Object.keys(networksObj)) {
1720
+ const v = networksObj[k];
1721
+ networksObj[k] = typeof v === "string" ? v : "";
1722
+ }
1723
+ }
1724
+ const keys = Object.keys(networksObj);
1446
1725
  if (keys.length) {
1447
1726
  block.props.networks = keys;
1448
1727
  block.props.socialUrls = networksObj;
@@ -1451,6 +1730,13 @@ function mapBlockToInternal(b) {
1451
1730
  if (asNum(s.gap) != null) block.props.gap = s.gap;
1452
1731
  if (asStr(s.shape)) block.props.shape = s.shape;
1453
1732
  if (asStr(s.align)) block.props.align = s.align;
1733
+ if (s.padding != null) {
1734
+ if (typeof s.padding === "number" && Number.isFinite(s.padding)) {
1735
+ block.props.padding = s.padding;
1736
+ } else if (s.padding && typeof s.padding === "object" && !Array.isArray(s.padding)) {
1737
+ block.props.padding = layoutColumnPaddingForDoc(s.padding);
1738
+ }
1739
+ }
1454
1740
  break;
1455
1741
  }
1456
1742
  case "table":
@@ -1491,15 +1777,81 @@ function mapBlockToInternal(b) {
1491
1777
  if (asNum(s.fontSize) != null) block.props.fontSize = s.fontSize;
1492
1778
  if (asStr(s.textAlign)) block.props.align = s.textAlign;
1493
1779
  break;
1494
- case "layout":
1780
+ case "layout": {
1781
+ if (Array.isArray(c.cells)) {
1782
+ const d = DEFAULT_BLOCK_PROPS.layout;
1783
+ block.props.preset = asStr(c.preset) ?? block.props.preset;
1784
+ if (typeof c.cols === "number") block.props.cols = c.cols;
1785
+ if (Array.isArray(c.ratios)) block.props.ratios = [...c.ratios];
1786
+ if (typeof c.gap === "number") block.props.gap = c.gap;
1787
+ if (c.padding !== void 0 && c.padding !== null) block.props.padding = c.padding;
1788
+ if (typeof c.bgColor === "string") block.props.bgColor = c.bgColor;
1789
+ if (typeof c.bgImage === "string") block.props.bgImage = c.bgImage;
1790
+ if (typeof c.bgSize === "string") block.props.bgSize = c.bgSize;
1791
+ if (typeof c.bgRepeat === "string") block.props.bgRepeat = c.bgRepeat;
1792
+ if (typeof c.bgPosition === "string") block.props.bgPosition = c.bgPosition;
1793
+ 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;
1796
+ }
1797
+ block.props.cells = c.cells.map(
1798
+ (col) => Array.isArray(col) ? col.map((raw) => mapEmbeddedLayoutCellBlock(raw, layoutDepth + 1)).filter((x) => x != null) : []
1799
+ );
1800
+ } else {
1801
+ block.props.preset = asStr(c.preset) ?? block.props.preset;
1802
+ }
1495
1803
  break;
1804
+ }
1496
1805
  default:
1497
1806
  break;
1498
1807
  }
1808
+ const styleObj = s && typeof s === "object" && !Array.isArray(s) ? s : {};
1809
+ const hasDocStyles = Object.keys(styleObj).some((k) => {
1810
+ const v = styleObj[k];
1811
+ return v !== void 0 && v !== null && v !== "";
1812
+ });
1813
+ const skipContentMergeKeys = {
1814
+ heading: /* @__PURE__ */ new Set(["text", "tag"]),
1815
+ text: /* @__PURE__ */ new Set(["html", "text"]),
1816
+ html: /* @__PURE__ */ new Set(["html"]),
1817
+ image: /* @__PURE__ */ new Set(["src", "alt", "link"]),
1818
+ button: /* @__PURE__ */ new Set(["text", "href"]),
1819
+ link: /* @__PURE__ */ new Set(["text", "href"]),
1820
+ social: /* @__PURE__ */ new Set(["networks", "socialUrls"]),
1821
+ table: /* @__PURE__ */ new Set(["data"]),
1822
+ menu: /* @__PURE__ */ new Set(["items"]),
1823
+ video: /* @__PURE__ */ new Set(["url"]),
1824
+ timer: /* @__PURE__ */ new Set(["endDate"])
1825
+ };
1826
+ if (!hasDocStyles && c && typeof c === "object" && t !== "layout") {
1827
+ const known = DEFAULT_BLOCK_PROPS[t];
1828
+ const skip = skipContentMergeKeys[t];
1829
+ if (known && typeof known === "object" && !Array.isArray(known)) {
1830
+ for (const key of Object.keys(known)) {
1831
+ if (skip?.has(key)) continue;
1832
+ if (key in c && c[key] !== void 0) {
1833
+ block.props[key] = c[key];
1834
+ }
1835
+ }
1836
+ }
1837
+ }
1838
+ applyImportedEditorPropsFromDoc(block, t, b, layoutDepth);
1499
1839
  return block;
1500
1840
  }
1501
1841
  function rowToInternal(r) {
1502
- const cols = Math.max(1, r.layout?.columns || r.columns?.length || 1);
1842
+ const columns = Array.isArray(r.columns) ? r.columns : [];
1843
+ const layoutColCount = typeof r.layout?.columns === "number" && r.layout.columns > 0 ? Math.floor(r.layout.columns) : 0;
1844
+ const cols = Math.max(1, columns.length, layoutColCount);
1845
+ const colList = (() => {
1846
+ if (columns.length >= cols) return columns;
1847
+ if (columns.length > 0) {
1848
+ return [
1849
+ ...columns,
1850
+ ...Array.from({ length: cols - columns.length }).map(() => ({}))
1851
+ ];
1852
+ }
1853
+ return Array.from({ length: cols }).map(() => ({}));
1854
+ })();
1503
1855
  const preset = pickPresetByColCount(cols) || LAYOUT_PRESETS[0];
1504
1856
  const row = makeLayoutRow(preset);
1505
1857
  row.id = typeof r.id === "string" ? r.id : uid();
@@ -1512,14 +1864,14 @@ function rowToInternal(r) {
1512
1864
  row.bgSize = r.styles?.backgroundSize || row.bgSize;
1513
1865
  row.bgPosition = r.styles?.backgroundPosition || row.bgPosition || "center";
1514
1866
  row.bgGradient = r.styles?.backgroundGradient || row.bgGradient || null;
1515
- const columns = Array.isArray(r.columns) ? r.columns : [];
1516
- const colList = columns.length ? columns : Array.from({ length: cols }).map(() => ({}));
1517
- row.cells = colList.slice(0, cols).map((c) => {
1867
+ row.cells = colList.map((c) => {
1518
1868
  const blocks = Array.isArray(c.blocks) ? c.blocks : [];
1519
- return blocks.map(mapBlockToInternal).filter((x) => x != null);
1869
+ return blocks.map((blk) => mapBlockToInternal(blk, 0)).filter((x) => x != null);
1520
1870
  });
1521
- const columnStyles = {};
1522
- colList.slice(0, cols).forEach((c, i) => {
1871
+ const studio = r._reactEmailStudio;
1872
+ const snap = studio?.row;
1873
+ const columnStylesFromDoc = {};
1874
+ colList.forEach((c, i) => {
1523
1875
  const bgColor = typeof c.styles?.backgroundColor === "string" ? c.styles.backgroundColor : "";
1524
1876
  const padding = layoutColumnPaddingForDoc(c.styles?.padding);
1525
1877
  const borderRadius = layoutColumnBorderRadiusForDoc(c.styles?.borderRadius);
@@ -1531,17 +1883,30 @@ function rowToInternal(r) {
1531
1883
  const hasPad = columnPaddingNonZero(padding);
1532
1884
  const hasBr = columnRadiusNonZero(borderRadius);
1533
1885
  if (bgColor || hasPad || hasBr || bgImage || bgRepeat || bgSize || bgPosition || bgGradient) {
1534
- columnStyles[i] = { bgColor, padding, borderRadius, bgImage, bgRepeat, bgSize, bgPosition, bgGradient };
1886
+ columnStylesFromDoc[i] = { bgColor, padding, borderRadius, bgImage, bgRepeat, bgSize, bgPosition, bgGradient };
1535
1887
  }
1536
1888
  });
1537
- if (Object.keys(columnStyles).length) row.columnStyles = columnStyles;
1889
+ if (snap && typeof snap === "object" && !Array.isArray(snap)) {
1890
+ const { cells: _omitCells, columnStyles: snapCs, ...rest } = snap;
1891
+ 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;
1897
+ }
1538
1898
  return row;
1539
1899
  }
1540
1900
  function normalizeEmailDesignInput(input) {
1541
1901
  const doc = normalizeEmailDocument(input);
1542
1902
  if (!doc) return null;
1543
1903
  const s = doc.settings || {};
1544
- const settings = {
1904
+ const studioSettings = s._reactEmailStudio;
1905
+ const settings = {};
1906
+ if (studioSettings?.editorSettings && typeof studioSettings.editorSettings === "object" && !Array.isArray(studioSettings.editorSettings)) {
1907
+ Object.assign(settings, cloneJson(studioSettings.editorSettings));
1908
+ }
1909
+ Object.assign(settings, {
1545
1910
  bgColor: s.backgroundColor || "#f1f5f9",
1546
1911
  bgImage: s.backgroundImage || "",
1547
1912
  bgRepeat: s.backgroundRepeat || "no-repeat",
@@ -1555,15 +1920,15 @@ function normalizeEmailDesignInput(input) {
1555
1920
  contentBgPosition: s.contentBackgroundPosition || "center",
1556
1921
  contentBgGradient: s.contentBackgroundGradient || null,
1557
1922
  contentWidth: typeof s.width === "number" ? s.width : 600,
1558
- padding: 24,
1559
- borderRadius: 8,
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,
1560
1925
  pageFontFamily: s.fontFamily,
1561
1926
  pageTextColor: s.color,
1562
1927
  pageLineHeight: s.lineHeightBase != null ? String(s.lineHeightBase) : void 0,
1563
1928
  pageResponsive: s.responsive,
1564
1929
  pageRtl: s.rtl,
1565
1930
  fontFamily: s.fontFamily
1566
- };
1931
+ });
1567
1932
  const rows = (doc.rows || []).map(rowToInternal);
1568
1933
  return withHydratedRows({ rows, settings, __emailDocument: doc });
1569
1934
  }
@@ -1587,14 +1952,29 @@ function designToEmailDocument(rows, settings) {
1587
1952
  fontFamily: settings.fontFamily || settings.pageFontFamily || "Arial, Helvetica, sans-serif",
1588
1953
  lineHeightBase: settings.pageLineHeight ? Number(settings.pageLineHeight) : 1.6,
1589
1954
  color: settings.pageTextColor || "#111827",
1590
- responsive: true,
1591
- rtl: false
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
+ }
1592
1962
  },
1593
1963
  rows: rows.map((r, ri) => {
1594
- const cols = Math.max(1, r.cols || r.cells?.length || 1);
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;
1595
1972
  return {
1596
1973
  id: r.id || `row_${ri + 1}`,
1597
1974
  type: "row",
1975
+ _reactEmailStudio: {
1976
+ row: cloneJson(rowEditorSnap)
1977
+ },
1598
1978
  layout: {
1599
1979
  columns: cols,
1600
1980
  gap: r.gap ?? 0,
@@ -1608,13 +1988,14 @@ function designToEmailDocument(rows, settings) {
1608
1988
  backgroundSize: r.bgSize || "cover",
1609
1989
  backgroundPosition: r.bgPosition || "center",
1610
1990
  backgroundGradient: r.bgGradient || null,
1611
- padding: { top: r.padding ?? 0, right: r.padding ?? 0, bottom: r.padding ?? 0, left: r.padding ?? 0 },
1991
+ padding: rowPad,
1612
1992
  borderRadius: 0,
1613
1993
  borderWidth: 0,
1614
1994
  borderColor: "#e5e7eb",
1615
1995
  textAlign: "left"
1616
1996
  },
1617
- columns: (r.cells || []).slice(0, cols).map((cellBlocks, ci) => {
1997
+ columns: Array.from({ length: cols }, (_, ci) => {
1998
+ const cellBlocks = cellArrays[ci] ?? [];
1618
1999
  const cs = r.columnStyles && r.columnStyles[ci] || {};
1619
2000
  return {
1620
2001
  id: `col_${ri + 1}_${ci + 1}`,
@@ -1629,12 +2010,7 @@ function designToEmailDocument(rows, settings) {
1629
2010
  backgroundGradient: cs.bgGradient || null,
1630
2011
  borderRadius: layoutColumnBorderRadiusForDoc(cs.borderRadius)
1631
2012
  },
1632
- blocks: (cellBlocks || []).map((b) => ({
1633
- id: b.id,
1634
- type: b.type,
1635
- content: b.props,
1636
- styles: {}
1637
- }))
2013
+ blocks: (cellBlocks || []).map((b) => exportEmailDocBlock(b))
1638
2014
  };
1639
2015
  })
1640
2016
  };
@@ -1642,14 +2018,29 @@ function designToEmailDocument(rows, settings) {
1642
2018
  };
1643
2019
  return doc;
1644
2020
  }
1645
- function hydrateBlock(b) {
2021
+ function hydrateBlock(b, depth = 0) {
1646
2022
  if (!b || typeof b !== "object") return null;
1647
2023
  const t = b.type === "nestedRow" ? "layout" : b.type;
1648
2024
  if (!isKnownBlockType(t)) return null;
1649
2025
  if (t === "layout" && b.props && Array.isArray(b.props.cells)) {
1650
2026
  const defaults2 = DEFAULT_BLOCK_PROPS.layout;
2027
+ if (depth >= MAX_LAYOUT_TREE_DEPTH) {
2028
+ const ratios = Array.isArray(b.props.ratios) && b.props.ratios.length ? [...b.props.ratios] : [1];
2029
+ return {
2030
+ ...b,
2031
+ type: "layout",
2032
+ id: typeof b.id === "string" && b.id ? b.id : uid(),
2033
+ props: {
2034
+ ...defaults2,
2035
+ ...b.props,
2036
+ bgSize: b.props.bgSize ?? defaults2.bgSize,
2037
+ bgRepeat: b.props.bgRepeat ?? defaults2.bgRepeat,
2038
+ cells: ratios.map(() => [])
2039
+ }
2040
+ };
2041
+ }
1651
2042
  const cells = b.props.cells.map(
1652
- (col) => Array.isArray(col) ? col.map(hydrateBlock).filter((x) => x != null) : []
2043
+ (col) => Array.isArray(col) ? col.map((x) => hydrateBlock(x, depth + 1)).filter((x) => x != null) : []
1653
2044
  );
1654
2045
  return {
1655
2046
  ...b,
@@ -1680,6 +2071,54 @@ function hydrateBlock(b) {
1680
2071
  props: merged
1681
2072
  };
1682
2073
  }
2074
+ function mapEmbeddedLayoutCellBlock(raw, layoutDepth = 0) {
2075
+ if (!raw || typeof raw !== "object") return null;
2076
+ const r = raw;
2077
+ if (r.props && typeof r.props === "object" && typeof r.type === "string" && !("styles" in r)) {
2078
+ return hydrateBlock(r, layoutDepth);
2079
+ }
2080
+ return mapBlockToInternal(r, layoutDepth);
2081
+ }
2082
+ function applyImportedEditorPropsFromDoc(block, t, b, layoutDepth = 0) {
2083
+ const exp = b.props;
2084
+ if (!exp || typeof exp !== "object" || Array.isArray(exp)) return;
2085
+ if (t === "layout" && Array.isArray(exp.cells)) {
2086
+ const hb = hydrateBlock({ type: "layout", id: block.id, props: exp }, layoutDepth);
2087
+ if (hb) {
2088
+ block.props = hb.props;
2089
+ if (typeof hb.id === "string" && hb.id) block.id = hb.id;
2090
+ }
2091
+ return;
2092
+ }
2093
+ Object.assign(block.props, exp);
2094
+ normalizeBoxStyles(block.props, t);
2095
+ if (t === "html" || t === "text") {
2096
+ block.props.content = normalizeRichHtmlForStorage(String(block.props.content ?? ""));
2097
+ }
2098
+ }
2099
+ function exportEmailDocBlock(b, depth = 0) {
2100
+ if (!b || typeof b !== "object") return { id: uid(), type: "text", content: {}, styles: {} };
2101
+ const doc = internalBlockToEmailDoc(b, depth);
2102
+ const out = { ...doc };
2103
+ if (b.props && typeof b.props === "object" && !Array.isArray(b.props)) {
2104
+ out.props = cloneJson(b.props);
2105
+ }
2106
+ if (b.type === "layout" && Array.isArray(b.props?.cells)) {
2107
+ const baseContent = typeof doc.content === "object" && doc.content ? doc.content : {};
2108
+ if (depth >= MAX_LAYOUT_TREE_DEPTH) {
2109
+ const ratios = Array.isArray(b.props.ratios) && b.props.ratios.length ? b.props.ratios : [1];
2110
+ out.content = { ...baseContent, cells: ratios.map(() => []) };
2111
+ } else {
2112
+ out.content = {
2113
+ ...baseContent,
2114
+ cells: b.props.cells.map(
2115
+ (col) => Array.isArray(col) ? col.map((child) => exportEmailDocBlock(child, depth + 1)) : []
2116
+ )
2117
+ };
2118
+ }
2119
+ }
2120
+ return out;
2121
+ }
1683
2122
  function hydrateLayoutRow(row) {
1684
2123
  if (!row || typeof row !== "object") return row;
1685
2124
  const cells = (row.cells || []).map(
@@ -4452,32 +4891,42 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4452
4891
  ] });
4453
4892
  case "text":
4454
4893
  return /* @__PURE__ */ jsxs4(Fragment4, { children: [
4455
- /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: /* @__PURE__ */ jsx6(Fragment3, { children: /* @__PURE__ */ jsx6(
4456
- TextRichEditor,
4894
+ /* @__PURE__ */ jsx6(PR, { label: "Content (HTML)", C, children: /* @__PURE__ */ jsx6(
4895
+ "textarea",
4457
4896
  {
4897
+ style: { ...IS, minHeight: 200, resize: "vertical", fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", fontSize: 12, lineHeight: 1.45 },
4458
4898
  value: normalizeRichHtmlForStorage(p.content),
4459
- onChange: (html) => set("content", html),
4460
- typography: {
4461
- fontSize: p.fontSize,
4462
- color: p.color,
4463
- align: p.align,
4464
- fontFamily: p.fontFamily,
4465
- lineHeight: p.lineHeight,
4466
- letterSpacing: p.letterSpacing,
4467
- padding: p.padding
4899
+ onChange: (e) => set("content", normalizeRichHtmlForStorage(e.target.value)),
4900
+ spellCheck: false,
4901
+ placeholder: "e.g. <p>Your message</p>"
4902
+ },
4903
+ block.id
4904
+ ) }),
4905
+ mergeTags && mergeTags.length > 0 ? /* @__PURE__ */ jsx6(PR, { label: "Insert merge tag", C, children: /* @__PURE__ */ jsxs4(
4906
+ "select",
4907
+ {
4908
+ style: IS,
4909
+ defaultValue: "",
4910
+ onChange: (e) => {
4911
+ const v = e.target.value;
4912
+ if (v) {
4913
+ set("content", normalizeRichHtmlForStorage((p.content || "") + " " + v));
4914
+ e.target.value = "";
4915
+ }
4468
4916
  },
4469
- mergeTags,
4470
- onMergeTagInsert: (tag) => set("content", (p.content || "") + " " + tag),
4471
- placeholder: "Write your message\u2026",
4472
- headerTitle: "Rich text",
4473
- C
4917
+ children: [
4918
+ /* @__PURE__ */ jsx6("option", { value: "", children: "Choose\u2026" }),
4919
+ mergeTags.map((t) => /* @__PURE__ */ jsx6("option", { value: t.value, children: t.name }, t.name))
4920
+ ]
4474
4921
  }
4475
- ) }, block.id) }),
4922
+ ) }) : null,
4476
4923
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Font size", value: p.fontSize ?? 15, onChange: (n) => set("fontSize", n), min: 8, max: 96, step: 1, C }),
4477
4924
  Col("color", "Color"),
4478
4925
  /* @__PURE__ */ jsx6(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right", "justify"], C }),
4479
4926
  Sel("fontWeight", "Weight", ["400", "500", "600", "700", "800"]),
4480
4927
  Sel("fontFamily", "Font", FONTS2),
4928
+ Tog("italic", "Italic"),
4929
+ Tog("underline", "Underline"),
4481
4930
  /* @__PURE__ */ jsx6(PR, { label: "Line height", C, children: /* @__PURE__ */ jsx6("input", { type: "number", style: useIS(C).IS, min: 1, max: 4, step: 0.05, value: p.lineHeight ?? 1.65, onChange: (e) => set("lineHeight", +e.target.value) }) }),
4482
4931
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Letter spacing", value: p.letterSpacing ?? 0, onChange: (n) => set("letterSpacing", n), min: 0, max: 30, step: 0.5, C }),
4483
4932
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
@@ -6319,6 +6768,9 @@ var ReactEmailEditor = forwardRef(
6319
6768
  const nested = action.nested;
6320
6769
  const toLoc = toColumnLoc(rowId, cellIdx, nested);
6321
6770
  if (action.kind === "new") {
6771
+ if ((action.contentType === "layout" || action.contentType === "nestedRow") && nestedLayoutDepth(nested) >= MAX_NESTED_LAYOUT_DEPTH) {
6772
+ return;
6773
+ }
6322
6774
  let nb;
6323
6775
  if (action.preset && (action.contentType === "layout" || action.contentType === "nestedRow")) {
6324
6776
  nb = makeNestedRowBlock(action.preset);