react-email-studio 2.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -138,7 +138,8 @@ var I18N = {
138
138
  blockPaletteGroupWidgets: "Widgets",
139
139
  closePanel: "Close",
140
140
  canvasEmptyHint: "Drag blocks from the library into the column, or add a Layout block for multi-column sections.",
141
- emailContentSettings: "Email content"
141
+ emailContentSettings: "Email content",
142
+ loadingDesign: "Loading design\u2026"
142
143
  },
143
144
  fr: {
144
145
  layouts: "Mises en page",
@@ -191,6 +192,7 @@ var I18N = {
191
192
  blockPaletteGroupWidgets: "Widgets",
192
193
  closePanel: "Fermer",
193
194
  canvasEmptyHint: "Glissez des blocs dans la colonne, ou ajoutez un bloc Mise en page pour plusieurs colonnes.",
195
+ loadingDesign: "Chargement du design\u2026",
194
196
  emailContentSettings: "Contenu de l\u2019e-mail",
195
197
  zoomIn: "Zoom +",
196
198
  zoomOut: "Zoom -",
@@ -250,6 +252,7 @@ var I18N = {
250
252
  blockPaletteGroupWidgets: "Widgets",
251
253
  closePanel: "Schlie\xDFen",
252
254
  canvasEmptyHint: "Bl\xF6cke in die Spalte ziehen oder Layout-Block f\xFCr mehrspaltige Bereiche hinzuf\xFCgen.",
255
+ loadingDesign: "Design wird geladen\u2026",
253
256
  emailContentSettings: "E-Mail-Inhalt",
254
257
  zoomIn: "Zoom +",
255
258
  zoomOut: "Zoom -",
@@ -310,6 +313,7 @@ var I18N = {
310
313
  blockPaletteGroupWidgets: "Widgets",
311
314
  closePanel: "Cerrar",
312
315
  canvasEmptyHint: "Arrastra bloques a la columna o a\xF1ade un bloque Dise\xF1o para varias columnas.",
316
+ loadingDesign: "Cargando dise\xF1o\u2026",
313
317
  emailContentSettings: "Contenido del correo",
314
318
  resizeSidebar: "Redimensionar panel"
315
319
  }
@@ -660,6 +664,10 @@ function makeContentBlock(type) {
660
664
  }
661
665
 
662
666
  // src/lib/columnPath.ts
667
+ var MAX_NESTED_LAYOUT_DEPTH = 1;
668
+ function nestedLayoutDepth(nested) {
669
+ return nested != null && nested.parentBlockIdx != null && nested.innerCellIdx != null ? 1 : 0;
670
+ }
663
671
  function isSplitLayoutBlock(b) {
664
672
  return b && b.type === "layout" && b.props && Array.isArray(b.props.cells);
665
673
  }
@@ -1251,8 +1259,18 @@ function normalizeEmailDocument(input) {
1251
1259
  settings: doc.settings && typeof doc.settings === "object" && !Array.isArray(doc.settings) ? doc.settings : {},
1252
1260
  rows: rows.map((r, ri) => {
1253
1261
  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(() => ({}));
1262
+ const layoutColCount = typeof r.layout?.columns === "number" && r.layout.columns > 0 ? Math.floor(r.layout.columns) : 0;
1263
+ const colCount = Math.max(1, columns.length, layoutColCount);
1264
+ const colsNormalized = (() => {
1265
+ if (columns.length >= colCount) return columns;
1266
+ if (columns.length > 0) {
1267
+ return [
1268
+ ...columns,
1269
+ ...Array.from({ length: colCount - columns.length }).map(() => ({}))
1270
+ ];
1271
+ }
1272
+ return Array.from({ length: colCount }).map(() => ({}));
1273
+ })();
1256
1274
  return {
1257
1275
  id: ensureId(r.id, `row${ri + 1}`),
1258
1276
  type: "row",
@@ -1263,6 +1281,7 @@ function normalizeEmailDocument(input) {
1263
1281
  align: r.layout?.align || "center"
1264
1282
  },
1265
1283
  styles: r.styles && typeof r.styles === "object" && !Array.isArray(r.styles) ? r.styles : {},
1284
+ ...typeof r._reactEmailStudio === "object" && r._reactEmailStudio !== null && !Array.isArray(r._reactEmailStudio) ? { _reactEmailStudio: r._reactEmailStudio } : {},
1266
1285
  columns: colsNormalized.map((c, ci) => ({
1267
1286
  id: ensureId(c.id, `col${ri + 1}_${ci + 1}`),
1268
1287
  layout: c.layout && typeof c.layout === "object" && !Array.isArray(c.layout) ? c.layout : {},
@@ -1273,7 +1292,8 @@ function normalizeEmailDocument(input) {
1273
1292
  content: b?.content && typeof b.content === "object" && !Array.isArray(b.content) ? b.content : {},
1274
1293
  styles: b?.styles && typeof b.styles === "object" && !Array.isArray(b.styles) ? b.styles : {},
1275
1294
  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 : {}
1295
+ responsive: b?.responsive && typeof b.responsive === "object" && !Array.isArray(b.responsive) ? b.responsive : {},
1296
+ ...b?.props && typeof b.props === "object" && !Array.isArray(b.props) ? { props: b.props } : {}
1277
1297
  })) : []
1278
1298
  }))
1279
1299
  };
@@ -1284,6 +1304,14 @@ function normalizeEmailDocument(input) {
1284
1304
  }
1285
1305
 
1286
1306
  // src/lib/emailDesignJson.ts
1307
+ var MAX_LAYOUT_TREE_DEPTH = 32;
1308
+ function cloneJson(v) {
1309
+ try {
1310
+ return JSON.parse(JSON.stringify(v));
1311
+ } catch {
1312
+ return v;
1313
+ }
1314
+ }
1287
1315
  function layoutColumnPaddingForDoc(p) {
1288
1316
  if (typeof p === "number" && Number.isFinite(p)) {
1289
1317
  const n = Math.max(0, p);
@@ -1370,7 +1398,242 @@ function normalizeBoxStyles(props, t) {
1370
1398
  }
1371
1399
  props.padding = boxPad(props.padding, uniformPadDefault(def?.padding));
1372
1400
  }
1373
- function mapBlockToInternal(b) {
1401
+ function internalBlockToEmailDoc(b, depth = 0) {
1402
+ const p = b?.props && typeof b.props === "object" ? b.props : {};
1403
+ const id = typeof b?.id === "string" ? b.id : uid();
1404
+ const type = b?.type === "nestedRow" ? "layout" : b?.type;
1405
+ if (!isKnownBlockType(type)) {
1406
+ return { id, type: String(type || "text"), content: {}, styles: {} };
1407
+ }
1408
+ switch (type) {
1409
+ case "heading": {
1410
+ 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;
1411
+ const padUniform = typeof p.padding === "number" && Number.isFinite(p.padding) ? p.padding : uniformPadDefault(p.padding);
1412
+ return {
1413
+ id,
1414
+ type,
1415
+ content: { text: typeof p.content === "string" ? p.content : "", tag: p.level || "h2" },
1416
+ styles: {
1417
+ fontSize: p.fontSize,
1418
+ fontWeight: fw,
1419
+ color: p.color,
1420
+ lineHeight: p.lineHeight,
1421
+ textAlign: p.align,
1422
+ letterSpacing: p.letterSpacing,
1423
+ marginBottom: padUniform
1424
+ }
1425
+ };
1426
+ }
1427
+ case "text":
1428
+ return {
1429
+ id,
1430
+ type,
1431
+ content: { html: typeof p.content === "string" ? p.content : "" },
1432
+ styles: {
1433
+ fontSize: p.fontSize,
1434
+ color: p.color,
1435
+ lineHeight: p.lineHeight,
1436
+ textAlign: p.align,
1437
+ ...p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1438
+ }
1439
+ };
1440
+ case "html":
1441
+ return {
1442
+ id,
1443
+ type,
1444
+ content: { html: typeof p.content === "string" ? p.content : "" },
1445
+ styles: p.padding && typeof p.padding === "object" && !Array.isArray(p.padding) ? { padding: layoutColumnPaddingForDoc(p.padding) } : {}
1446
+ };
1447
+ case "image": {
1448
+ const br = p.borderRadius;
1449
+ const borderRadius = typeof br === "number" && Number.isFinite(br) ? br : br && typeof br === "object" && !Array.isArray(br) ? Math.round(
1450
+ (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
1451
+ ) : 0;
1452
+ return {
1453
+ id,
1454
+ type,
1455
+ content: {
1456
+ src: p.src || "",
1457
+ alt: p.alt || "",
1458
+ link: p.link || ""
1459
+ },
1460
+ styles: {
1461
+ width: p.width,
1462
+ align: p.align,
1463
+ borderRadius
1464
+ },
1465
+ ...p.linkTarget === "_blank" ? { behavior: { openInNewTab: true } } : {}
1466
+ };
1467
+ }
1468
+ case "button": {
1469
+ let borderRadiusOut;
1470
+ if (typeof p.borderRadius === "number" && Number.isFinite(p.borderRadius)) {
1471
+ borderRadiusOut = p.borderRadius;
1472
+ } else if (p.borderRadius && typeof p.borderRadius === "object" && !Array.isArray(p.borderRadius)) {
1473
+ const br = p.borderRadius;
1474
+ const tl = numOr(br.tl, 0);
1475
+ const tr2 = numOr(br.tr, tl);
1476
+ const brc = numOr(br.br, tl);
1477
+ const bl = numOr(br.bl, tr2);
1478
+ borderRadiusOut = tl === tr2 && tr2 === brc && brc === bl ? tl : Math.round((tl + tr2 + brc + bl) / 4);
1479
+ }
1480
+ return {
1481
+ id,
1482
+ type,
1483
+ content: { text: p.label || "", href: p.href || "#" },
1484
+ styles: {
1485
+ backgroundColor: p.bgColor,
1486
+ color: p.textColor,
1487
+ fontSize: p.fontSize,
1488
+ fontWeight: p.fontWeight,
1489
+ ...borderRadiusOut !== void 0 ? { borderRadius: borderRadiusOut } : {},
1490
+ textAlign: p.align,
1491
+ padding: {
1492
+ top: p.paddingV ?? 11,
1493
+ right: p.paddingH ?? 24,
1494
+ bottom: p.paddingV ?? 11,
1495
+ left: p.paddingH ?? 24
1496
+ }
1497
+ },
1498
+ ...p.linkTarget === "_blank" ? { behavior: { openInNewTab: true } } : {}
1499
+ };
1500
+ }
1501
+ case "divider": {
1502
+ 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;
1503
+ return {
1504
+ id,
1505
+ type,
1506
+ content: {},
1507
+ styles: { color: p.color, thickness: p.thickness, paddingY: padY }
1508
+ };
1509
+ }
1510
+ case "spacer":
1511
+ return { id, type, content: {}, styles: { height: p.height } };
1512
+ case "social": {
1513
+ const urls = p.socialUrls && typeof p.socialUrls === "object" && !Array.isArray(p.socialUrls) ? p.socialUrls : {};
1514
+ const networks = {};
1515
+ const ordered = Array.isArray(p.networks) ? p.networks.filter((n) => typeof n === "string" && !!n) : [];
1516
+ const seen = /* @__PURE__ */ new Set();
1517
+ for (const n of ordered) {
1518
+ const u = urls[n];
1519
+ networks[n] = typeof u === "string" && u.trim() ? u : "";
1520
+ seen.add(n);
1521
+ }
1522
+ for (const key of Object.keys(urls)) {
1523
+ if (seen.has(key)) continue;
1524
+ const v = urls[key];
1525
+ if (typeof v === "string" && v.trim()) {
1526
+ networks[key] = v;
1527
+ seen.add(key);
1528
+ }
1529
+ }
1530
+ 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) } : {} : {};
1531
+ return {
1532
+ id,
1533
+ type,
1534
+ content: { networks },
1535
+ styles: {
1536
+ iconSize: p.iconSize,
1537
+ gap: p.gap,
1538
+ shape: p.shape,
1539
+ align: p.align,
1540
+ ...padStyles
1541
+ }
1542
+ };
1543
+ }
1544
+ case "table":
1545
+ return {
1546
+ id,
1547
+ type,
1548
+ content: { data: Array.isArray(p.data) ? p.data : [] },
1549
+ styles: {
1550
+ borderColor: p.cellBorder,
1551
+ cellPadding: p.cellPadding,
1552
+ headerBg: p.headerBg,
1553
+ striped: p.striped
1554
+ }
1555
+ };
1556
+ case "video":
1557
+ return {
1558
+ id,
1559
+ type,
1560
+ content: { url: p.src || "" },
1561
+ styles: { height: p.height }
1562
+ };
1563
+ case "timer": {
1564
+ let timerBr;
1565
+ if (typeof p.borderRadius === "number" && Number.isFinite(p.borderRadius)) {
1566
+ timerBr = p.borderRadius;
1567
+ } else if (p.borderRadius && typeof p.borderRadius === "object" && !Array.isArray(p.borderRadius)) {
1568
+ const br = p.borderRadius;
1569
+ const tl = numOr(br.tl, 0);
1570
+ const tr2 = numOr(br.tr, tl);
1571
+ const brc = numOr(br.br, tl);
1572
+ const bl = numOr(br.bl, tr2);
1573
+ timerBr = tl === tr2 && tr2 === brc && brc === bl ? tl : Math.round((tl + tr2 + brc + bl) / 4);
1574
+ }
1575
+ return {
1576
+ id,
1577
+ type,
1578
+ content: { endDate: p.endDate || "" },
1579
+ styles: {
1580
+ backgroundColor: p.bgColor,
1581
+ color: p.textColor,
1582
+ padding: typeof p.padding === "number" ? p.padding : uniformPadDefault(p.padding),
1583
+ ...timerBr !== void 0 ? { borderRadius: timerBr } : {},
1584
+ textAlign: p.align
1585
+ }
1586
+ };
1587
+ }
1588
+ case "menu":
1589
+ return {
1590
+ id,
1591
+ type,
1592
+ content: { items: Array.isArray(p.items) ? p.items : [] },
1593
+ styles: { fontSize: p.fontSize, color: p.color }
1594
+ };
1595
+ case "link":
1596
+ return {
1597
+ id,
1598
+ type,
1599
+ content: { text: p.label || "", href: p.href || "#" },
1600
+ styles: { color: p.color, fontSize: p.fontSize, textAlign: p.align },
1601
+ ...p.linkTarget === "_blank" ? { behavior: { openInNewTab: true } } : {}
1602
+ };
1603
+ case "layout": {
1604
+ const ratios = Array.isArray(p.ratios) ? [...p.ratios] : [1];
1605
+ const emptyCells = ratios.map(() => []);
1606
+ const rawCells = Array.isArray(p.cells) ? p.cells : [];
1607
+ const cellsOut = depth >= MAX_LAYOUT_TREE_DEPTH ? emptyCells : ratios.map((_, i) => {
1608
+ const col = rawCells[i];
1609
+ return Array.isArray(col) ? col.map((child) => internalBlockToEmailDoc(child, depth + 1)) : [];
1610
+ });
1611
+ return {
1612
+ id,
1613
+ type,
1614
+ content: {
1615
+ preset: p.preset,
1616
+ cols: p.cols,
1617
+ ratios,
1618
+ gap: p.gap,
1619
+ padding: p.padding,
1620
+ bgColor: p.bgColor,
1621
+ bgImage: p.bgImage,
1622
+ bgSize: p.bgSize,
1623
+ bgRepeat: p.bgRepeat,
1624
+ bgPosition: p.bgPosition,
1625
+ bgGradient: p.bgGradient ?? null,
1626
+ columnStyles: p.columnStyles,
1627
+ cells: cellsOut
1628
+ },
1629
+ styles: {}
1630
+ };
1631
+ }
1632
+ default:
1633
+ return { id, type, content: {}, styles: {} };
1634
+ }
1635
+ }
1636
+ function mapBlockToInternal(b, layoutDepth = 0) {
1374
1637
  const t = b.type;
1375
1638
  if (!isKnownBlockType(t)) return null;
1376
1639
  const block = makeContentBlock(t);
@@ -1441,8 +1704,28 @@ function mapBlockToInternal(b) {
1441
1704
  if (asNum(s.height) != null) block.props.height = s.height;
1442
1705
  break;
1443
1706
  case "social": {
1444
- const networksObj = c.networks && typeof c.networks === "object" ? c.networks : {};
1445
- const keys = Object.keys(networksObj || {});
1707
+ const networksRaw = c.networks;
1708
+ let networksObj = {};
1709
+ if (Array.isArray(networksRaw)) {
1710
+ const urlMap = c.socialUrls && typeof c.socialUrls === "object" && !Array.isArray(c.socialUrls) ? c.socialUrls : {};
1711
+ for (const n of networksRaw) {
1712
+ if (typeof n !== "string" || !n) continue;
1713
+ const u = urlMap[n];
1714
+ networksObj[n] = typeof u === "string" ? u : "";
1715
+ }
1716
+ for (const key of Object.keys(urlMap)) {
1717
+ if (key in networksObj) continue;
1718
+ const u = urlMap[key];
1719
+ if (typeof u === "string" && u.trim()) networksObj[key] = u;
1720
+ }
1721
+ } else if (networksRaw && typeof networksRaw === "object" && !Array.isArray(networksRaw)) {
1722
+ networksObj = { ...networksRaw };
1723
+ for (const k of Object.keys(networksObj)) {
1724
+ const v = networksObj[k];
1725
+ networksObj[k] = typeof v === "string" ? v : "";
1726
+ }
1727
+ }
1728
+ const keys = Object.keys(networksObj);
1446
1729
  if (keys.length) {
1447
1730
  block.props.networks = keys;
1448
1731
  block.props.socialUrls = networksObj;
@@ -1451,6 +1734,13 @@ function mapBlockToInternal(b) {
1451
1734
  if (asNum(s.gap) != null) block.props.gap = s.gap;
1452
1735
  if (asStr(s.shape)) block.props.shape = s.shape;
1453
1736
  if (asStr(s.align)) block.props.align = s.align;
1737
+ if (s.padding != null) {
1738
+ if (typeof s.padding === "number" && Number.isFinite(s.padding)) {
1739
+ block.props.padding = s.padding;
1740
+ } else if (s.padding && typeof s.padding === "object" && !Array.isArray(s.padding)) {
1741
+ block.props.padding = layoutColumnPaddingForDoc(s.padding);
1742
+ }
1743
+ }
1454
1744
  break;
1455
1745
  }
1456
1746
  case "table":
@@ -1491,15 +1781,81 @@ function mapBlockToInternal(b) {
1491
1781
  if (asNum(s.fontSize) != null) block.props.fontSize = s.fontSize;
1492
1782
  if (asStr(s.textAlign)) block.props.align = s.textAlign;
1493
1783
  break;
1494
- case "layout":
1784
+ case "layout": {
1785
+ if (Array.isArray(c.cells)) {
1786
+ const d = DEFAULT_BLOCK_PROPS.layout;
1787
+ block.props.preset = asStr(c.preset) ?? block.props.preset;
1788
+ if (typeof c.cols === "number") block.props.cols = c.cols;
1789
+ if (Array.isArray(c.ratios)) block.props.ratios = [...c.ratios];
1790
+ if (typeof c.gap === "number") block.props.gap = c.gap;
1791
+ if (c.padding !== void 0 && c.padding !== null) block.props.padding = c.padding;
1792
+ if (typeof c.bgColor === "string") block.props.bgColor = c.bgColor;
1793
+ if (typeof c.bgImage === "string") block.props.bgImage = c.bgImage;
1794
+ if (typeof c.bgSize === "string") block.props.bgSize = c.bgSize;
1795
+ if (typeof c.bgRepeat === "string") block.props.bgRepeat = c.bgRepeat;
1796
+ if (typeof c.bgPosition === "string") block.props.bgPosition = c.bgPosition;
1797
+ if (c.bgGradient !== void 0) block.props.bgGradient = c.bgGradient;
1798
+ if (c.columnStyles && typeof c.columnStyles === "object" && !Array.isArray(c.columnStyles)) {
1799
+ block.props.columnStyles = c.columnStyles;
1800
+ }
1801
+ block.props.cells = c.cells.map(
1802
+ (col) => Array.isArray(col) ? col.map((raw) => mapEmbeddedLayoutCellBlock(raw, layoutDepth + 1)).filter((x) => x != null) : []
1803
+ );
1804
+ } else {
1805
+ block.props.preset = asStr(c.preset) ?? block.props.preset;
1806
+ }
1495
1807
  break;
1808
+ }
1496
1809
  default:
1497
1810
  break;
1498
1811
  }
1812
+ const styleObj = s && typeof s === "object" && !Array.isArray(s) ? s : {};
1813
+ const hasDocStyles = Object.keys(styleObj).some((k) => {
1814
+ const v = styleObj[k];
1815
+ return v !== void 0 && v !== null && v !== "";
1816
+ });
1817
+ const skipContentMergeKeys = {
1818
+ heading: /* @__PURE__ */ new Set(["text", "tag"]),
1819
+ text: /* @__PURE__ */ new Set(["html", "text"]),
1820
+ html: /* @__PURE__ */ new Set(["html"]),
1821
+ image: /* @__PURE__ */ new Set(["src", "alt", "link"]),
1822
+ button: /* @__PURE__ */ new Set(["text", "href"]),
1823
+ link: /* @__PURE__ */ new Set(["text", "href"]),
1824
+ social: /* @__PURE__ */ new Set(["networks", "socialUrls"]),
1825
+ table: /* @__PURE__ */ new Set(["data"]),
1826
+ menu: /* @__PURE__ */ new Set(["items"]),
1827
+ video: /* @__PURE__ */ new Set(["url"]),
1828
+ timer: /* @__PURE__ */ new Set(["endDate"])
1829
+ };
1830
+ if (!hasDocStyles && c && typeof c === "object" && t !== "layout") {
1831
+ const known = DEFAULT_BLOCK_PROPS[t];
1832
+ const skip = skipContentMergeKeys[t];
1833
+ if (known && typeof known === "object" && !Array.isArray(known)) {
1834
+ for (const key of Object.keys(known)) {
1835
+ if (skip?.has(key)) continue;
1836
+ if (key in c && c[key] !== void 0) {
1837
+ block.props[key] = c[key];
1838
+ }
1839
+ }
1840
+ }
1841
+ }
1842
+ applyImportedEditorPropsFromDoc(block, t, b, layoutDepth);
1499
1843
  return block;
1500
1844
  }
1501
1845
  function rowToInternal(r) {
1502
- const cols = Math.max(1, r.layout?.columns || r.columns?.length || 1);
1846
+ const columns = Array.isArray(r.columns) ? r.columns : [];
1847
+ const layoutColCount = typeof r.layout?.columns === "number" && r.layout.columns > 0 ? Math.floor(r.layout.columns) : 0;
1848
+ const cols = Math.max(1, columns.length, layoutColCount);
1849
+ const colList = (() => {
1850
+ if (columns.length >= cols) return columns;
1851
+ if (columns.length > 0) {
1852
+ return [
1853
+ ...columns,
1854
+ ...Array.from({ length: cols - columns.length }).map(() => ({}))
1855
+ ];
1856
+ }
1857
+ return Array.from({ length: cols }).map(() => ({}));
1858
+ })();
1503
1859
  const preset = pickPresetByColCount(cols) || LAYOUT_PRESETS[0];
1504
1860
  const row = makeLayoutRow(preset);
1505
1861
  row.id = typeof r.id === "string" ? r.id : uid();
@@ -1512,14 +1868,14 @@ function rowToInternal(r) {
1512
1868
  row.bgSize = r.styles?.backgroundSize || row.bgSize;
1513
1869
  row.bgPosition = r.styles?.backgroundPosition || row.bgPosition || "center";
1514
1870
  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) => {
1871
+ row.cells = colList.map((c) => {
1518
1872
  const blocks = Array.isArray(c.blocks) ? c.blocks : [];
1519
- return blocks.map(mapBlockToInternal).filter((x) => x != null);
1873
+ return blocks.map((blk) => mapBlockToInternal(blk, 0)).filter((x) => x != null);
1520
1874
  });
1521
- const columnStyles = {};
1522
- colList.slice(0, cols).forEach((c, i) => {
1875
+ const studio = r._reactEmailStudio;
1876
+ const snap = studio?.row;
1877
+ const columnStylesFromDoc = {};
1878
+ colList.forEach((c, i) => {
1523
1879
  const bgColor = typeof c.styles?.backgroundColor === "string" ? c.styles.backgroundColor : "";
1524
1880
  const padding = layoutColumnPaddingForDoc(c.styles?.padding);
1525
1881
  const borderRadius = layoutColumnBorderRadiusForDoc(c.styles?.borderRadius);
@@ -1531,17 +1887,30 @@ function rowToInternal(r) {
1531
1887
  const hasPad = columnPaddingNonZero(padding);
1532
1888
  const hasBr = columnRadiusNonZero(borderRadius);
1533
1889
  if (bgColor || hasPad || hasBr || bgImage || bgRepeat || bgSize || bgPosition || bgGradient) {
1534
- columnStyles[i] = { bgColor, padding, borderRadius, bgImage, bgRepeat, bgSize, bgPosition, bgGradient };
1890
+ columnStylesFromDoc[i] = { bgColor, padding, borderRadius, bgImage, bgRepeat, bgSize, bgPosition, bgGradient };
1535
1891
  }
1536
1892
  });
1537
- if (Object.keys(columnStyles).length) row.columnStyles = columnStyles;
1893
+ if (snap && typeof snap === "object" && !Array.isArray(snap)) {
1894
+ const { cells: _omitCells, columnStyles: snapCs, ...rest } = snap;
1895
+ Object.assign(row, rest);
1896
+ const snapColumn = snapCs && typeof snapCs === "object" && !Array.isArray(snapCs) ? snapCs : {};
1897
+ row.columnStyles = { ...snapColumn, ...columnStylesFromDoc };
1898
+ if (!Object.keys(row.columnStyles).length) delete row.columnStyles;
1899
+ } else if (Object.keys(columnStylesFromDoc).length) {
1900
+ row.columnStyles = columnStylesFromDoc;
1901
+ }
1538
1902
  return row;
1539
1903
  }
1540
1904
  function normalizeEmailDesignInput(input) {
1541
1905
  const doc = normalizeEmailDocument(input);
1542
1906
  if (!doc) return null;
1543
1907
  const s = doc.settings || {};
1544
- const settings = {
1908
+ const studioSettings = s._reactEmailStudio;
1909
+ const settings = {};
1910
+ if (studioSettings?.editorSettings && typeof studioSettings.editorSettings === "object" && !Array.isArray(studioSettings.editorSettings)) {
1911
+ Object.assign(settings, cloneJson(studioSettings.editorSettings));
1912
+ }
1913
+ Object.assign(settings, {
1545
1914
  bgColor: s.backgroundColor || "#f1f5f9",
1546
1915
  bgImage: s.backgroundImage || "",
1547
1916
  bgRepeat: s.backgroundRepeat || "no-repeat",
@@ -1555,15 +1924,15 @@ function normalizeEmailDesignInput(input) {
1555
1924
  contentBgPosition: s.contentBackgroundPosition || "center",
1556
1925
  contentBgGradient: s.contentBackgroundGradient || null,
1557
1926
  contentWidth: typeof s.width === "number" ? s.width : 600,
1558
- padding: 24,
1559
- borderRadius: 8,
1927
+ padding: typeof studioSettings?.contentPadding === "number" ? studioSettings.contentPadding : typeof settings.padding === "number" ? settings.padding : 24,
1928
+ borderRadius: typeof studioSettings?.contentBorderRadius === "number" ? studioSettings.contentBorderRadius : typeof settings.borderRadius === "number" ? settings.borderRadius : 8,
1560
1929
  pageFontFamily: s.fontFamily,
1561
1930
  pageTextColor: s.color,
1562
1931
  pageLineHeight: s.lineHeightBase != null ? String(s.lineHeightBase) : void 0,
1563
1932
  pageResponsive: s.responsive,
1564
1933
  pageRtl: s.rtl,
1565
1934
  fontFamily: s.fontFamily
1566
- };
1935
+ });
1567
1936
  const rows = (doc.rows || []).map(rowToInternal);
1568
1937
  return withHydratedRows({ rows, settings, __emailDocument: doc });
1569
1938
  }
@@ -1587,14 +1956,29 @@ function designToEmailDocument(rows, settings) {
1587
1956
  fontFamily: settings.fontFamily || settings.pageFontFamily || "Arial, Helvetica, sans-serif",
1588
1957
  lineHeightBase: settings.pageLineHeight ? Number(settings.pageLineHeight) : 1.6,
1589
1958
  color: settings.pageTextColor || "#111827",
1590
- responsive: true,
1591
- rtl: false
1959
+ responsive: typeof settings.pageResponsive === "boolean" ? settings.pageResponsive : true,
1960
+ rtl: !!settings.pageRtl,
1961
+ _reactEmailStudio: {
1962
+ contentPadding: settings.padding ?? 24,
1963
+ contentBorderRadius: settings.borderRadius ?? 8,
1964
+ editorSettings: cloneJson(settings)
1965
+ }
1592
1966
  },
1593
1967
  rows: rows.map((r, ri) => {
1594
- const cols = Math.max(1, r.cols || r.cells?.length || 1);
1968
+ const cellArrays = Array.isArray(r.cells) ? r.cells : [];
1969
+ const declaredCols = typeof r.cols === "number" && r.cols > 0 ? r.cols : 0;
1970
+ const cols = Math.max(1, cellArrays.length, declaredCols);
1971
+ const rowPad = r.padding && typeof r.padding === "object" && !Array.isArray(r.padding) ? layoutColumnPaddingForDoc(r.padding) : (() => {
1972
+ const n = typeof r.padding === "number" && Number.isFinite(r.padding) ? r.padding : 0;
1973
+ return { top: n, right: n, bottom: n, left: n };
1974
+ })();
1975
+ const { cells: _rowCells, ...rowEditorSnap } = r;
1595
1976
  return {
1596
1977
  id: r.id || `row_${ri + 1}`,
1597
1978
  type: "row",
1979
+ _reactEmailStudio: {
1980
+ row: cloneJson(rowEditorSnap)
1981
+ },
1598
1982
  layout: {
1599
1983
  columns: cols,
1600
1984
  gap: r.gap ?? 0,
@@ -1608,13 +1992,14 @@ function designToEmailDocument(rows, settings) {
1608
1992
  backgroundSize: r.bgSize || "cover",
1609
1993
  backgroundPosition: r.bgPosition || "center",
1610
1994
  backgroundGradient: r.bgGradient || null,
1611
- padding: { top: r.padding ?? 0, right: r.padding ?? 0, bottom: r.padding ?? 0, left: r.padding ?? 0 },
1995
+ padding: rowPad,
1612
1996
  borderRadius: 0,
1613
1997
  borderWidth: 0,
1614
1998
  borderColor: "#e5e7eb",
1615
1999
  textAlign: "left"
1616
2000
  },
1617
- columns: (r.cells || []).slice(0, cols).map((cellBlocks, ci) => {
2001
+ columns: Array.from({ length: cols }, (_, ci) => {
2002
+ const cellBlocks = cellArrays[ci] ?? [];
1618
2003
  const cs = r.columnStyles && r.columnStyles[ci] || {};
1619
2004
  return {
1620
2005
  id: `col_${ri + 1}_${ci + 1}`,
@@ -1629,12 +2014,7 @@ function designToEmailDocument(rows, settings) {
1629
2014
  backgroundGradient: cs.bgGradient || null,
1630
2015
  borderRadius: layoutColumnBorderRadiusForDoc(cs.borderRadius)
1631
2016
  },
1632
- blocks: (cellBlocks || []).map((b) => ({
1633
- id: b.id,
1634
- type: b.type,
1635
- content: b.props,
1636
- styles: {}
1637
- }))
2017
+ blocks: (cellBlocks || []).map((b) => exportEmailDocBlock(b))
1638
2018
  };
1639
2019
  })
1640
2020
  };
@@ -1642,14 +2022,29 @@ function designToEmailDocument(rows, settings) {
1642
2022
  };
1643
2023
  return doc;
1644
2024
  }
1645
- function hydrateBlock(b) {
2025
+ function hydrateBlock(b, depth = 0) {
1646
2026
  if (!b || typeof b !== "object") return null;
1647
2027
  const t = b.type === "nestedRow" ? "layout" : b.type;
1648
2028
  if (!isKnownBlockType(t)) return null;
1649
2029
  if (t === "layout" && b.props && Array.isArray(b.props.cells)) {
1650
2030
  const defaults2 = DEFAULT_BLOCK_PROPS.layout;
2031
+ if (depth >= MAX_LAYOUT_TREE_DEPTH) {
2032
+ const ratios = Array.isArray(b.props.ratios) && b.props.ratios.length ? [...b.props.ratios] : [1];
2033
+ return {
2034
+ ...b,
2035
+ type: "layout",
2036
+ id: typeof b.id === "string" && b.id ? b.id : uid(),
2037
+ props: {
2038
+ ...defaults2,
2039
+ ...b.props,
2040
+ bgSize: b.props.bgSize ?? defaults2.bgSize,
2041
+ bgRepeat: b.props.bgRepeat ?? defaults2.bgRepeat,
2042
+ cells: ratios.map(() => [])
2043
+ }
2044
+ };
2045
+ }
1651
2046
  const cells = b.props.cells.map(
1652
- (col) => Array.isArray(col) ? col.map(hydrateBlock).filter((x) => x != null) : []
2047
+ (col) => Array.isArray(col) ? col.map((x) => hydrateBlock(x, depth + 1)).filter((x) => x != null) : []
1653
2048
  );
1654
2049
  return {
1655
2050
  ...b,
@@ -1680,6 +2075,78 @@ function hydrateBlock(b) {
1680
2075
  props: merged
1681
2076
  };
1682
2077
  }
2078
+ function mapEmbeddedLayoutCellBlock(raw, layoutDepth = 0) {
2079
+ if (!raw || typeof raw !== "object") return null;
2080
+ const r = raw;
2081
+ if (r.props && typeof r.props === "object" && typeof r.type === "string" && !("styles" in r)) {
2082
+ return hydrateBlock(r, layoutDepth);
2083
+ }
2084
+ return mapBlockToInternal(r, layoutDepth);
2085
+ }
2086
+ function mergeLayoutColumnStylesMaps(fromContent, fromHydrated) {
2087
+ const isObj = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
2088
+ if (!isObj(fromContent) && !isObj(fromHydrated)) return void 0;
2089
+ const a = isObj(fromContent) ? fromContent : {};
2090
+ const b = isObj(fromHydrated) ? fromHydrated : {};
2091
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
2092
+ const out = {};
2093
+ for (const k of keys) {
2094
+ const va = a[k];
2095
+ const vb = b[k];
2096
+ if (isObj(va) && isObj(vb)) {
2097
+ out[k] = { ...va, ...vb };
2098
+ } else if (vb !== void 0) {
2099
+ out[k] = vb;
2100
+ } else {
2101
+ out[k] = va;
2102
+ }
2103
+ }
2104
+ return Object.keys(out).length ? out : void 0;
2105
+ }
2106
+ function applyImportedEditorPropsFromDoc(block, t, b, layoutDepth = 0) {
2107
+ const exp = b.props;
2108
+ if (!exp || typeof exp !== "object" || Array.isArray(exp)) return;
2109
+ if (t === "layout" && Array.isArray(exp.cells)) {
2110
+ const columnStylesFromContent = block.props.columnStyles;
2111
+ const hb = hydrateBlock({ type: "layout", id: block.id, props: exp }, layoutDepth);
2112
+ if (hb) {
2113
+ block.props = hb.props;
2114
+ const merged = mergeLayoutColumnStylesMaps(columnStylesFromContent, block.props.columnStyles);
2115
+ if (merged) block.props.columnStyles = merged;
2116
+ else delete block.props.columnStyles;
2117
+ if (typeof hb.id === "string" && hb.id) block.id = hb.id;
2118
+ }
2119
+ return;
2120
+ }
2121
+ Object.assign(block.props, exp);
2122
+ normalizeBoxStyles(block.props, t);
2123
+ if (t === "html" || t === "text") {
2124
+ block.props.content = normalizeRichHtmlForStorage(String(block.props.content ?? ""));
2125
+ }
2126
+ }
2127
+ function exportEmailDocBlock(b, depth = 0) {
2128
+ if (!b || typeof b !== "object") return { id: uid(), type: "text", content: {}, styles: {} };
2129
+ const doc = internalBlockToEmailDoc(b, depth);
2130
+ const out = { ...doc };
2131
+ if (b.props && typeof b.props === "object" && !Array.isArray(b.props)) {
2132
+ out.props = cloneJson(b.props);
2133
+ }
2134
+ if (b.type === "layout" && Array.isArray(b.props?.cells)) {
2135
+ const baseContent = typeof doc.content === "object" && doc.content ? doc.content : {};
2136
+ if (depth >= MAX_LAYOUT_TREE_DEPTH) {
2137
+ const ratios = Array.isArray(b.props.ratios) && b.props.ratios.length ? b.props.ratios : [1];
2138
+ out.content = { ...baseContent, cells: ratios.map(() => []) };
2139
+ } else {
2140
+ out.content = {
2141
+ ...baseContent,
2142
+ cells: b.props.cells.map(
2143
+ (col) => Array.isArray(col) ? col.map((child) => exportEmailDocBlock(child, depth + 1)) : []
2144
+ )
2145
+ };
2146
+ }
2147
+ }
2148
+ return out;
2149
+ }
1683
2150
  function hydrateLayoutRow(row) {
1684
2151
  if (!row || typeof row !== "object") return row;
1685
2152
  const cells = (row.cells || []).map(
@@ -4452,32 +4919,42 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4452
4919
  ] });
4453
4920
  case "text":
4454
4921
  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,
4922
+ /* @__PURE__ */ jsx6(PR, { label: "Content (HTML)", C, children: /* @__PURE__ */ jsx6(
4923
+ "textarea",
4457
4924
  {
4925
+ style: { ...IS, minHeight: 200, resize: "vertical", fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", fontSize: 12, lineHeight: 1.45 },
4458
4926
  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
4927
+ onChange: (e) => set("content", normalizeRichHtmlForStorage(e.target.value)),
4928
+ spellCheck: false,
4929
+ placeholder: "e.g. <p>Your message</p>"
4930
+ },
4931
+ block.id
4932
+ ) }),
4933
+ mergeTags && mergeTags.length > 0 ? /* @__PURE__ */ jsx6(PR, { label: "Insert merge tag", C, children: /* @__PURE__ */ jsxs4(
4934
+ "select",
4935
+ {
4936
+ style: IS,
4937
+ defaultValue: "",
4938
+ onChange: (e) => {
4939
+ const v = e.target.value;
4940
+ if (v) {
4941
+ set("content", normalizeRichHtmlForStorage((p.content || "") + " " + v));
4942
+ e.target.value = "";
4943
+ }
4468
4944
  },
4469
- mergeTags,
4470
- onMergeTagInsert: (tag) => set("content", (p.content || "") + " " + tag),
4471
- placeholder: "Write your message\u2026",
4472
- headerTitle: "Rich text",
4473
- C
4945
+ children: [
4946
+ /* @__PURE__ */ jsx6("option", { value: "", children: "Choose\u2026" }),
4947
+ mergeTags.map((t) => /* @__PURE__ */ jsx6("option", { value: t.value, children: t.name }, t.name))
4948
+ ]
4474
4949
  }
4475
- ) }, block.id) }),
4950
+ ) }) : null,
4476
4951
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Font size", value: p.fontSize ?? 15, onChange: (n) => set("fontSize", n), min: 8, max: 96, step: 1, C }),
4477
4952
  Col("color", "Color"),
4478
4953
  /* @__PURE__ */ jsx6(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right", "justify"], C }),
4479
4954
  Sel("fontWeight", "Weight", ["400", "500", "600", "700", "800"]),
4480
4955
  Sel("fontFamily", "Font", FONTS2),
4956
+ Tog("italic", "Italic"),
4957
+ Tog("underline", "Underline"),
4481
4958
  /* @__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
4959
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Letter spacing", value: p.letterSpacing ?? 0, onChange: (n) => set("letterSpacing", n), min: 0, max: 30, step: 0.5, C }),
4483
4960
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
@@ -6054,6 +6531,7 @@ var ReactEmailEditor = forwardRef(
6054
6531
  const [previewDevice, setPreviewDevice] = useState6("desktop");
6055
6532
  const [activeTab, setActiveTab] = useState6("blocks");
6056
6533
  const [templatesOpen, setTemplatesOpen] = useState6(false);
6534
+ const [jsonLoading, setJsonLoading] = useState6(false);
6057
6535
  const [autoSaveMsg, setAutoSaveMsg] = useState6("");
6058
6536
  const [locale, setLocale] = useState6(
6059
6537
  typeof opt.locale === "string" ? opt.locale : "en"
@@ -6196,31 +6674,40 @@ var ReactEmailEditor = forwardRef(
6196
6674
  }, [rows, history, future, selContentMeta]);
6197
6675
  const buildApi = useCallback2(() => ({
6198
6676
  loadJson(input) {
6199
- let parsed = input;
6200
- if (typeof input === "string") {
6201
- try {
6202
- parsed = JSON.parse(input);
6203
- } catch {
6204
- return;
6205
- }
6206
- }
6207
- const norm = normalizeEmailDesignInput(parsed);
6208
- if (!norm) return;
6209
- setRows(norm.rows);
6210
- const ns = norm.settings;
6211
- setSettings((s) => ({ ...s, ...ns }));
6212
- const inferPage = () => {
6213
- if (ns?.bgGradient) return "gradient";
6214
- if (String(ns?.bgImage ?? "").trim()) return "image";
6215
- return "solid";
6216
- };
6217
- const inferContent = () => {
6218
- if (ns?.contentBgGradient) return "gradient";
6219
- if (String(ns?.contentBgImage ?? "").trim()) return "image";
6220
- return "solid";
6221
- };
6222
- setPageBgUiStep(inferPage());
6223
- setContentBgUiStep(inferContent());
6677
+ setJsonLoading(true);
6678
+ requestAnimationFrame(() => {
6679
+ requestAnimationFrame(() => {
6680
+ try {
6681
+ let parsed = input;
6682
+ if (typeof input === "string") {
6683
+ try {
6684
+ parsed = JSON.parse(input);
6685
+ } catch {
6686
+ return;
6687
+ }
6688
+ }
6689
+ const norm = normalizeEmailDesignInput(parsed);
6690
+ if (!norm) return;
6691
+ setRows(norm.rows);
6692
+ const ns = norm.settings;
6693
+ setSettings((s) => ({ ...s, ...ns }));
6694
+ const inferPage = () => {
6695
+ if (ns?.bgGradient) return "gradient";
6696
+ if (String(ns?.bgImage ?? "").trim()) return "image";
6697
+ return "solid";
6698
+ };
6699
+ const inferContent = () => {
6700
+ if (ns?.contentBgGradient) return "gradient";
6701
+ if (String(ns?.contentBgImage ?? "").trim()) return "image";
6702
+ return "solid";
6703
+ };
6704
+ setPageBgUiStep(inferPage());
6705
+ setContentBgUiStep(inferContent());
6706
+ } finally {
6707
+ setJsonLoading(false);
6708
+ }
6709
+ });
6710
+ });
6224
6711
  },
6225
6712
  exportJson(cb, pretty) {
6226
6713
  const design = designToEmailDocument(rowsRef.current, settingsRef.current);
@@ -6319,6 +6806,9 @@ var ReactEmailEditor = forwardRef(
6319
6806
  const nested = action.nested;
6320
6807
  const toLoc = toColumnLoc(rowId, cellIdx, nested);
6321
6808
  if (action.kind === "new") {
6809
+ if ((action.contentType === "layout" || action.contentType === "nestedRow") && nestedLayoutDepth(nested) >= MAX_NESTED_LAYOUT_DEPTH) {
6810
+ return;
6811
+ }
6322
6812
  let nb;
6323
6813
  if (action.preset && (action.contentType === "layout" || action.contentType === "nestedRow")) {
6324
6814
  nb = makeNestedRowBlock(action.preset);
@@ -6604,6 +7094,7 @@ var ReactEmailEditor = forwardRef(
6604
7094
  ::-webkit-scrollbar-track{background:transparent;}
6605
7095
  ::-webkit-scrollbar-thumb{background:${C.border};border-radius:3px;}
6606
7096
  ::-webkit-scrollbar-thumb:hover{background:${C.muted};}
7097
+ @keyframes email-editor-json-spin{to{transform:rotate(360deg);}}
6607
7098
  ` }),
6608
7099
  /* @__PURE__ */ jsxs9("div", { id: eid("workspace"), style: {
6609
7100
  display: "flex",
@@ -7532,7 +8023,47 @@ var ReactEmailEditor = forwardRef(
7532
8023
  ) })
7533
8024
  ]
7534
8025
  }
7535
- )
8026
+ ),
8027
+ jsonLoading ? /* @__PURE__ */ jsxs9(
8028
+ "div",
8029
+ {
8030
+ id: eid("json-loading"),
8031
+ role: "status",
8032
+ "aria-live": "polite",
8033
+ "aria-busy": true,
8034
+ style: {
8035
+ position: "absolute",
8036
+ inset: 0,
8037
+ zIndex: 180,
8038
+ background: `${C.bg}f2`,
8039
+ backdropFilter: "blur(2px)",
8040
+ WebkitBackdropFilter: "blur(2px)",
8041
+ display: "flex",
8042
+ flexDirection: "column",
8043
+ alignItems: "center",
8044
+ justifyContent: "center",
8045
+ gap: 14,
8046
+ pointerEvents: "auto"
8047
+ },
8048
+ children: [
8049
+ /* @__PURE__ */ jsx11(
8050
+ "div",
8051
+ {
8052
+ "aria-hidden": true,
8053
+ style: {
8054
+ width: 40,
8055
+ height: 40,
8056
+ borderRadius: "50%",
8057
+ border: `3px solid ${C.border}`,
8058
+ borderTopColor: C.accent,
8059
+ animation: "email-editor-json-spin 0.65s linear infinite"
8060
+ }
8061
+ }
8062
+ ),
8063
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 13, fontWeight: 600, color: C.text }, children: L("loadingDesign") })
8064
+ ]
8065
+ }
8066
+ ) : null
7536
8067
  ] }),
7537
8068
  /* @__PURE__ */ jsxs9("div", { id: eid("status-bar"), style: { display: "flex", alignItems: "center", gap: 12, padding: "3px 14px", background: C.toolbarBg, borderTop: `1px solid ${C.border}`, fontSize: 10, color: C.muted, flexShrink: 0, position: "sticky", bottom: 0, zIndex: 100 }, children: [
7538
8069
  /* @__PURE__ */ jsxs9("span", { children: [