react-email-studio 3.0.0 → 3.2.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.d.cts CHANGED
@@ -108,10 +108,6 @@ type ReactEmailEditorOptions = EmailHtmlOptions & {
108
108
  customThemes?: Record<string, ThemeColors>;
109
109
  };
110
110
  locale?: string;
111
- mergeTags?: {
112
- name: string;
113
- value: string;
114
- }[];
115
111
  features?: {
116
112
  autoSave?: {
117
113
  enabled?: boolean;
package/dist/index.d.ts CHANGED
@@ -108,10 +108,6 @@ type ReactEmailEditorOptions = EmailHtmlOptions & {
108
108
  customThemes?: Record<string, ThemeColors>;
109
109
  };
110
110
  locale?: string;
111
- mergeTags?: {
112
- name: string;
113
- value: string;
114
- }[];
115
111
  features?: {
116
112
  autoSave?: {
117
113
  enabled?: boolean;
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  useRef as useRef6,
5
5
  useEffect as useEffect6,
6
6
  useCallback as useCallback2,
7
- useMemo as useMemo2,
7
+ useMemo as useMemo3,
8
8
  forwardRef,
9
9
  useImperativeHandle,
10
10
  Fragment as Fragment6
@@ -120,7 +120,6 @@ var I18N = {
120
120
  delete: "Delete",
121
121
  moveUp: "Move Up",
122
122
  moveDown: "Move Down",
123
- mergeTags: "Merge Tags",
124
123
  autoSaved: "Auto-saved",
125
124
  search: "Search blocks\u2026",
126
125
  zoomIn: "Zoom In",
@@ -138,7 +137,8 @@ var I18N = {
138
137
  blockPaletteGroupWidgets: "Widgets",
139
138
  closePanel: "Close",
140
139
  canvasEmptyHint: "Drag blocks from the library into the column, or add a Layout block for multi-column sections.",
141
- emailContentSettings: "Email content"
140
+ emailContentSettings: "Email content",
141
+ loadingDesign: "Loading design\u2026"
142
142
  },
143
143
  fr: {
144
144
  layouts: "Mises en page",
@@ -178,7 +178,6 @@ var I18N = {
178
178
  delete: "Supprimer",
179
179
  moveUp: "Monter",
180
180
  moveDown: "Descendre",
181
- mergeTags: "Balises fusion",
182
181
  autoSaved: "Auto-sauvegard\xE9",
183
182
  search: "Rechercher\u2026",
184
183
  colorScheme: "Sch\xE9ma de couleurs",
@@ -191,6 +190,7 @@ var I18N = {
191
190
  blockPaletteGroupWidgets: "Widgets",
192
191
  closePanel: "Fermer",
193
192
  canvasEmptyHint: "Glissez des blocs dans la colonne, ou ajoutez un bloc Mise en page pour plusieurs colonnes.",
193
+ loadingDesign: "Chargement du design\u2026",
194
194
  emailContentSettings: "Contenu de l\u2019e-mail",
195
195
  zoomIn: "Zoom +",
196
196
  zoomOut: "Zoom -",
@@ -237,7 +237,6 @@ var I18N = {
237
237
  delete: "L\xF6schen",
238
238
  moveUp: "Nach oben",
239
239
  moveDown: "Nach unten",
240
- mergeTags: "Merge-Tags",
241
240
  autoSaved: "Auto-gespeichert",
242
241
  search: "Bl\xF6cke suchen\u2026",
243
242
  colorScheme: "Farbschema",
@@ -250,6 +249,7 @@ var I18N = {
250
249
  blockPaletteGroupWidgets: "Widgets",
251
250
  closePanel: "Schlie\xDFen",
252
251
  canvasEmptyHint: "Bl\xF6cke in die Spalte ziehen oder Layout-Block f\xFCr mehrspaltige Bereiche hinzuf\xFCgen.",
252
+ loadingDesign: "Design wird geladen\u2026",
253
253
  emailContentSettings: "E-Mail-Inhalt",
254
254
  zoomIn: "Zoom +",
255
255
  zoomOut: "Zoom -",
@@ -294,7 +294,6 @@ var I18N = {
294
294
  delete: "Eliminar",
295
295
  moveUp: "Subir",
296
296
  moveDown: "Bajar",
297
- mergeTags: "Etiquetas de fusi\xF3n",
298
297
  autoSaved: "Auto-guardado",
299
298
  search: "Buscar bloques\u2026",
300
299
  zoomIn: "Zoom +",
@@ -310,6 +309,7 @@ var I18N = {
310
309
  blockPaletteGroupWidgets: "Widgets",
311
310
  closePanel: "Cerrar",
312
311
  canvasEmptyHint: "Arrastra bloques a la columna o a\xF1ade un bloque Dise\xF1o para varias columnas.",
312
+ loadingDesign: "Cargando dise\xF1o\u2026",
313
313
  emailContentSettings: "Contenido del correo",
314
314
  resizeSidebar: "Redimensionar panel"
315
315
  }
@@ -2079,13 +2079,37 @@ function mapEmbeddedLayoutCellBlock(raw, layoutDepth = 0) {
2079
2079
  }
2080
2080
  return mapBlockToInternal(r, layoutDepth);
2081
2081
  }
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
+ }
2082
2102
  function applyImportedEditorPropsFromDoc(block, t, b, layoutDepth = 0) {
2083
2103
  const exp = b.props;
2084
2104
  if (!exp || typeof exp !== "object" || Array.isArray(exp)) return;
2085
2105
  if (t === "layout" && Array.isArray(exp.cells)) {
2106
+ const columnStylesFromContent = block.props.columnStyles;
2086
2107
  const hb = hydrateBlock({ type: "layout", id: block.id, props: exp }, layoutDepth);
2087
2108
  if (hb) {
2088
2109
  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;
2089
2113
  if (typeof hb.id === "string" && hb.id) block.id = hb.id;
2090
2114
  }
2091
2115
  return;
@@ -2178,9 +2202,8 @@ function useLiveCountdown(endDate) {
2178
2202
  const s = Math.floor(diff % 6e4 / 1e3);
2179
2203
  return [d, h, m, s];
2180
2204
  }
2181
- function editChromeBorder(C, preview) {
2182
- if (preview) return {};
2183
- return { border: `1px dotted ${C.border}`, boxSizing: "border-box" };
2205
+ function editChromeBorder(_C, _preview) {
2206
+ return {};
2184
2207
  }
2185
2208
  function blockLinkCaptureHandler(preview, e) {
2186
2209
  if (preview) return;
@@ -2482,6 +2505,8 @@ function NestedRowBlock({
2482
2505
  rowId,
2483
2506
  cellIdx,
2484
2507
  parentBlockIdx,
2508
+ /** When this layout sits inside another layout column, meta uses `contentIdx` + `inner` like other nested blocks. */
2509
+ selectionInner,
2485
2510
  editorId,
2486
2511
  selectedKey,
2487
2512
  selContentMeta,
@@ -2493,7 +2518,7 @@ function NestedRowBlock({
2493
2518
  C
2494
2519
  }) {
2495
2520
  const p = block.props;
2496
- const rowSelected = !preview && selContentMeta && selContentMeta.rowId === rowId && selContentMeta.cellIdx === cellIdx && selContentMeta.contentIdx === parentBlockIdx && !selContentMeta.inner && selectedKey === block.id;
2521
+ const rowSelected = !preview && selContentMeta && selContentMeta.rowId === rowId && selContentMeta.cellIdx === cellIdx && selectedKey === block.id && (selectionInner == null ? selContentMeta.contentIdx === parentBlockIdx && !selContentMeta.inner : selContentMeta.contentIdx === parentBlockIdx && selContentMeta.inner && selContentMeta.inner.cellIdx === selectionInner.cellIdx && selContentMeta.inner.contentIdx === selectionInner.contentIdx);
2497
2522
  const rowStyle = {
2498
2523
  padding: p.padding ?? 8,
2499
2524
  position: "relative",
@@ -2517,7 +2542,13 @@ function NestedRowBlock({
2517
2542
  style: rowStyle,
2518
2543
  onClick: preview ? void 0 : (e) => {
2519
2544
  e.stopPropagation();
2520
- onSelectContent(block.id, rowId, cellIdx, parentBlockIdx, null);
2545
+ onSelectContent(
2546
+ block.id,
2547
+ rowId,
2548
+ cellIdx,
2549
+ parentBlockIdx,
2550
+ selectionInner ?? null
2551
+ );
2521
2552
  },
2522
2553
  children: /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: p.gap ?? 12 }, children: (p.cells || []).map((innerBlocks, ici) => {
2523
2554
  const cS = p.columnStyles && p.columnStyles[ici] || {};
@@ -2583,7 +2614,13 @@ function BlockItem({
2583
2614
  if (!onSelectContent) return;
2584
2615
  e.stopPropagation();
2585
2616
  if (isSplitLayoutBlock(cb)) {
2586
- onSelectContent(cb.id, rowId, cellIdx, nest ? nest.parentBlockIdx : ci, null);
2617
+ onSelectContent(
2618
+ cb.id,
2619
+ rowId,
2620
+ cellIdx,
2621
+ nest ? nest.parentBlockIdx : ci,
2622
+ nest ? { cellIdx: innerCellIdx, contentIdx: ci } : null
2623
+ );
2587
2624
  } else {
2588
2625
  onSelectContent(
2589
2626
  cb.id,
@@ -2654,6 +2691,7 @@ function BlockItem({
2654
2691
  rowId,
2655
2692
  cellIdx,
2656
2693
  parentBlockIdx: nest ? nest.parentBlockIdx : ci,
2694
+ selectionInner: nest ? { cellIdx: innerCellIdx, contentIdx: ci } : null,
2657
2695
  editorId,
2658
2696
  selectedKey,
2659
2697
  selContentMeta,
@@ -2961,7 +2999,7 @@ var CanvasRow = ({
2961
2999
  import { Image as ImageIcon2, Repeat as RepeatIcon, Scaling as Scaling2, Crosshair as Crosshair2, Blend as GradientIcon2, Droplets as ColorIcon } from "lucide-react";
2962
3000
 
2963
3001
  // src/editor/properties/PropertyEditors.tsx
2964
- import { useRef as useRef2, useState as useState3, useEffect as useEffect3, Fragment as Fragment3 } from "react";
3002
+ import { useRef as useRef2, useState as useState3, useEffect as useEffect3, useMemo, Fragment as Fragment3 } from "react";
2965
3003
  import {
2966
3004
  AlignCenter as AlignCenter2,
2967
3005
  AlignJustify as AlignJustify2,
@@ -3282,11 +3320,8 @@ function ToolbarTypographyControls({ editor, C }) {
3282
3320
  }
3283
3321
  function FormattingToolbar({
3284
3322
  editor,
3285
- C,
3286
- mergeTags,
3287
- onMergeTagInsert
3323
+ C
3288
3324
  }) {
3289
- const [mergeSel, setMergeSel] = useState2("");
3290
3325
  const [linkPanelOpen, setLinkPanelOpen] = useState2(false);
3291
3326
  const [linkUrlValue, setLinkUrlValue] = useState2("");
3292
3327
  const ibtn = (active) => toolbarIconBtn(C, active);
@@ -3606,8 +3641,6 @@ function TextRichEditor({
3606
3641
  value,
3607
3642
  onChange,
3608
3643
  typography,
3609
- mergeTags,
3610
- onMergeTagInsert,
3611
3644
  placeholder = "Write your message\u2026",
3612
3645
  headerTitle = "Rich editor",
3613
3646
  C
@@ -3751,15 +3784,7 @@ function TextRichEditor({
3751
3784
  ]
3752
3785
  }
3753
3786
  ),
3754
- /* @__PURE__ */ jsx4(
3755
- FormattingToolbar,
3756
- {
3757
- editor,
3758
- C,
3759
- mergeTags,
3760
- onMergeTagInsert
3761
- }
3762
- ),
3787
+ /* @__PURE__ */ jsx4(FormattingToolbar, { editor, C }),
3763
3788
  /* @__PURE__ */ jsx4("style", { children: proseStyle }),
3764
3789
  /* @__PURE__ */ jsx4(
3765
3790
  "div",
@@ -3810,15 +3835,7 @@ function TextRichEditor({
3810
3835
  background: C.inputBg
3811
3836
  },
3812
3837
  children: [
3813
- /* @__PURE__ */ jsx4(
3814
- FormattingToolbar,
3815
- {
3816
- editor,
3817
- C,
3818
- mergeTags,
3819
- onMergeTagInsert
3820
- }
3821
- ),
3838
+ /* @__PURE__ */ jsx4(FormattingToolbar, { editor, C }),
3822
3839
  /* @__PURE__ */ jsx4("style", { children: proseStyle }),
3823
3840
  /* @__PURE__ */ jsx4(
3824
3841
  "div",
@@ -4818,7 +4835,7 @@ function BlockSurfaceBgInspector({
4818
4835
  ] }) : null
4819
4836
  ] });
4820
4837
  }
4821
- function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }) {
4838
+ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4822
4839
  const { IS, CI } = useIS(C);
4823
4840
  const imageFileRef = useRef2(null);
4824
4841
  const p = block.props;
@@ -4837,7 +4854,6 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4837
4854
  /* @__PURE__ */ jsx6("input", { type: "checkbox", checked: !!p[k], onChange: (e) => set(k, e.target.checked), style: { width: 15, height: 15, accentColor: C.accent } }),
4838
4855
  /* @__PURE__ */ jsx6("span", { style: { color: C.muted, fontSize: 12 }, children: p[k] ? "On" : "Off" })
4839
4856
  ] }) });
4840
- const TagPicker = (_field) => null;
4841
4857
  const FONTS2 = ["Georgia,serif", "Arial,sans-serif", "Verdana,sans-serif", "'Courier New',monospace", "Trebuchet MS,sans-serif", "Impact,sans-serif"];
4842
4858
  const ImgUpload = (key) => {
4843
4859
  if (!onUpload) return null;
@@ -4891,7 +4907,7 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4891
4907
  ] });
4892
4908
  case "text":
4893
4909
  return /* @__PURE__ */ jsxs4(Fragment4, { children: [
4894
- /* @__PURE__ */ jsx6(PR, { label: "Content (HTML)", C, children: /* @__PURE__ */ jsx6(
4910
+ /* @__PURE__ */ jsx6(PR, { label: "Content", C, children: /* @__PURE__ */ jsx6(
4895
4911
  "textarea",
4896
4912
  {
4897
4913
  style: { ...IS, minHeight: 200, resize: "vertical", fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", fontSize: 12, lineHeight: 1.45 },
@@ -4902,24 +4918,6 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4902
4918
  },
4903
4919
  block.id
4904
4920
  ) }),
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
- }
4916
- },
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
- ]
4921
- }
4922
- ) }) : null,
4923
4921
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Font size", value: p.fontSize ?? 15, onChange: (n) => set("fontSize", n), min: 8, max: 96, step: 1, C }),
4924
4922
  Col("color", "Color"),
4925
4923
  /* @__PURE__ */ jsx6(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right", "justify"], C }),
@@ -4949,8 +4947,6 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4949
4947
  letterSpacing: p.letterSpacing,
4950
4948
  padding: p.padding
4951
4949
  },
4952
- mergeTags,
4953
- onMergeTagInsert: (tag) => set("content", (p.content || "") + " " + tag),
4954
4950
  placeholder: "Write your rich content\u2026",
4955
4951
  headerTitle: "Rich editor",
4956
4952
  C
@@ -5357,6 +5353,19 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
5357
5353
  return null;
5358
5354
  }
5359
5355
  }
5356
+ function inferLayoutRowBgStep(r) {
5357
+ if (r.bgGradient) return "gradient";
5358
+ if (String(r.bgImage || "").trim()) return "image";
5359
+ if (String(r.bgColor || "").trim()) return "solid";
5360
+ return null;
5361
+ }
5362
+ function inferColumnBgStep(columnStyles, selCol) {
5363
+ const cs = (columnStyles || {})[selCol] || {};
5364
+ if (cs.bgGradient) return "gradient";
5365
+ if (String(cs.bgImage || "").trim()) return "image";
5366
+ if (String(cs.bgColor || "").trim()) return "solid";
5367
+ return null;
5368
+ }
5360
5369
  function LayoutRowEditor({
5361
5370
  row,
5362
5371
  onChange,
@@ -5368,49 +5377,37 @@ function LayoutRowEditor({
5368
5377
  customizationHint
5369
5378
  }) {
5370
5379
  const { IS, CI } = useIS(C);
5371
- const [selCol, setSelCol] = useState3(initialCol);
5372
- const [rowBgUiStep, setRowBgUiStep] = useState3(null);
5373
- const [colBgUiStep, setColBgUiStep] = useState3(null);
5380
+ const colCount = Math.max(1, row.ratios?.length || row.cells?.length || 1);
5381
+ const ratioList = row.ratios?.length ? row.ratios : [1];
5382
+ const [selCol, setSelCol] = useState3(0);
5383
+ const [rowBgPickerMode, setRowBgPickerMode] = useState3(null);
5384
+ const [colBgPickerMode, setColBgPickerMode] = useState3(null);
5385
+ const inferredRowBg = useMemo(() => inferLayoutRowBgStep(row), [row.bgGradient, row.bgImage, row.bgColor]);
5386
+ const inferredColBg = useMemo(
5387
+ () => inferColumnBgStep(row.columnStyles, selCol),
5388
+ [row.columnStyles, selCol]
5389
+ );
5390
+ const rowBgUiStep = inferredRowBg ?? rowBgPickerMode;
5391
+ const colBgUiStep = inferredColBg ?? colBgPickerMode;
5374
5392
  useEffect3(() => {
5375
- if (initialCol !== null && initialCol !== void 0) {
5376
- setSelCol(initialCol);
5393
+ if (initialCol !== null && initialCol !== void 0 && initialCol >= 0) {
5394
+ setSelCol(Math.min(initialCol, colCount - 1));
5395
+ } else {
5396
+ setSelCol((c) => Math.min(Math.max(0, c), colCount - 1));
5377
5397
  }
5378
- }, [initialCol]);
5398
+ }, [initialCol, row.id, colCount]);
5379
5399
  useEffect3(() => {
5380
- if (row.bgGradient) {
5381
- setRowBgUiStep("gradient");
5382
- return;
5383
- }
5384
- if (String(row.bgImage || "").trim()) {
5385
- setRowBgUiStep("image");
5386
- return;
5387
- }
5388
- if (String(row.bgColor || "").trim()) {
5389
- setRowBgUiStep("solid");
5390
- return;
5391
- }
5392
- setRowBgUiStep(null);
5400
+ setRowBgPickerMode(null);
5393
5401
  }, [row.id]);
5394
5402
  useEffect3(() => {
5395
- if (selCol === null) {
5396
- setColBgUiStep(null);
5397
- return;
5398
- }
5399
- const cs = (row.columnStyles || {})[selCol] || {};
5400
- if (cs.bgGradient) {
5401
- setColBgUiStep("gradient");
5402
- return;
5403
- }
5404
- if (String(cs.bgImage || "").trim()) {
5405
- setColBgUiStep("image");
5406
- return;
5407
- }
5408
- if (String(cs.bgColor || "").trim()) {
5409
- setColBgUiStep("solid");
5410
- return;
5411
- }
5412
- setColBgUiStep(null);
5403
+ setColBgPickerMode(null);
5413
5404
  }, [selCol, row.id]);
5405
+ useEffect3(() => {
5406
+ if (inferredRowBg) setRowBgPickerMode(null);
5407
+ }, [inferredRowBg]);
5408
+ useEffect3(() => {
5409
+ if (inferredColBg) setColBgPickerMode(null);
5410
+ }, [inferredColBg]);
5414
5411
  const set = (k, v) => onChange({ ...row, [k]: v });
5415
5412
  const applyColumnCount = (n) => {
5416
5413
  const prevCells = [...row.cells || []];
@@ -5426,16 +5423,13 @@ function LayoutRowEditor({
5426
5423
  }
5427
5424
  onChange({ ...row, cols: n, preset: "custom", cells, ratios });
5428
5425
  };
5429
- const colCount = Math.max(1, row.ratios?.length || row.cells?.length || 1);
5430
- const ratioList = row.ratios?.length ? row.ratios : [1];
5431
5426
  const updCol = (i, upd) => {
5432
5427
  const styles = { ...row.columnStyles || {} };
5433
5428
  styles[i] = { ...styles[i] || {}, ...upd };
5434
5429
  set("columnStyles", styles);
5435
5430
  };
5436
5431
  const setColBgMode = (mode) => {
5437
- if (selCol === null) return;
5438
- setColBgUiStep(mode);
5432
+ setColBgPickerMode(mode);
5439
5433
  const cs = (row.columnStyles || {})[selCol] || {};
5440
5434
  if (mode === "solid") {
5441
5435
  updCol(selCol, { bgGradient: null, bgImage: "" });
@@ -5453,7 +5447,7 @@ function LayoutRowEditor({
5453
5447
  const total = ratioList.reduce((a, b) => a + b, 0) || 1;
5454
5448
  const POS_OPTS = ["center", "top", "bottom", "left", "right", "top left", "top right", "bottom left", "bottom right"];
5455
5449
  const setRowBgMode = (mode) => {
5456
- setRowBgUiStep(mode);
5450
+ setRowBgPickerMode(mode);
5457
5451
  if (mode === "solid") {
5458
5452
  set("bgGradient", null);
5459
5453
  set("bgImage", "");
@@ -5611,7 +5605,7 @@ function LayoutRowEditor({
5611
5605
  },
5612
5606
  i
5613
5607
  )) }),
5614
- selCol !== null && /* @__PURE__ */ jsxs4("div", { style: { background: C.surface, padding: 10, borderRadius: 8, border: `1px solid ${C.border}` }, children: [
5608
+ colCount > 0 && /* @__PURE__ */ jsxs4("div", { style: { background: C.surface, padding: 10, borderRadius: 8, border: `1px solid ${C.border}` }, children: [
5615
5609
  /* @__PURE__ */ jsx6(BgModeButtons, { value: colBgUiStep, onChange: setColBgMode, C }),
5616
5610
  colBgUiStep === null ? /* @__PURE__ */ jsx6("div", { style: { fontSize: 11, color: C.muted, marginBottom: 10, lineHeight: 1.45 }, children: "Choose Color, Gradient, or Image \u2014 then edit column background details." }) : null,
5617
5611
  colBgUiStep === "solid" ? /* @__PURE__ */ jsx6(PR, { label: `col ${selCol + 1} bg`, C, children: /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
@@ -5710,7 +5704,7 @@ function LayoutRowEditor({
5710
5704
  import {
5711
5705
  useState as useState4,
5712
5706
  useEffect as useEffect4,
5713
- useMemo,
5707
+ useMemo as useMemo2,
5714
5708
  useRef as useRef3,
5715
5709
  useLayoutEffect
5716
5710
  } from "react";
@@ -6088,8 +6082,8 @@ function PreviewModal({
6088
6082
  const iframeHDesktop = "min(720px, calc(100vh - 200px))";
6089
6083
  const mobileIframeW = previewIframeWidthPx("mobile", mobileVariant);
6090
6084
  const tabletIframeW = previewIframeWidthPx("tablet");
6091
- const srcDocMobile = useMemo(() => previewEmailSrcDoc(html, mobileIframeW), [html, mobileIframeW]);
6092
- const srcDocTablet = useMemo(() => previewEmailSrcDoc(html, tabletIframeW), [html, tabletIframeW]);
6085
+ const srcDocMobile = useMemo2(() => previewEmailSrcDoc(html, mobileIframeW), [html, mobileIframeW]);
6086
+ const srcDocTablet = useMemo2(() => previewEmailSrcDoc(html, tabletIframeW), [html, tabletIframeW]);
6093
6087
  const IframeContent = ({
6094
6088
  srcDoc,
6095
6089
  title: iframeTitle,
@@ -6485,7 +6479,7 @@ var ReactEmailEditor = forwardRef(
6485
6479
  style = {}
6486
6480
  }, ref) {
6487
6481
  const opt = options;
6488
- const mergedThemes = useMemo2(
6482
+ const mergedThemes = useMemo3(
6489
6483
  () => ({ ...THEMES, ...opt.appearance?.customThemes || {} }),
6490
6484
  [opt.appearance?.customThemes]
6491
6485
  );
@@ -6503,11 +6497,11 @@ var ReactEmailEditor = forwardRef(
6503
6497
  const [previewDevice, setPreviewDevice] = useState6("desktop");
6504
6498
  const [activeTab, setActiveTab] = useState6("blocks");
6505
6499
  const [templatesOpen, setTemplatesOpen] = useState6(false);
6500
+ const [jsonLoading, setJsonLoading] = useState6(false);
6506
6501
  const [autoSaveMsg, setAutoSaveMsg] = useState6("");
6507
6502
  const [locale, setLocale] = useState6(
6508
6503
  typeof opt.locale === "string" ? opt.locale : "en"
6509
6504
  );
6510
- const [mergeTags, setMergeTagsState] = useState6(() => opt.mergeTags ?? []);
6511
6505
  const [zoom, setZoom] = useState6(100);
6512
6506
  const [rightRailWidth, setRightRailWidth] = useState6(RIGHT_RAIL_DEFAULT_W);
6513
6507
  const [ctxMenu, setCtxMenu] = useState6(null);
@@ -6645,31 +6639,40 @@ var ReactEmailEditor = forwardRef(
6645
6639
  }, [rows, history, future, selContentMeta]);
6646
6640
  const buildApi = useCallback2(() => ({
6647
6641
  loadJson(input) {
6648
- let parsed = input;
6649
- if (typeof input === "string") {
6650
- try {
6651
- parsed = JSON.parse(input);
6652
- } catch {
6653
- return;
6654
- }
6655
- }
6656
- const norm = normalizeEmailDesignInput(parsed);
6657
- if (!norm) return;
6658
- setRows(norm.rows);
6659
- const ns = norm.settings;
6660
- setSettings((s) => ({ ...s, ...ns }));
6661
- const inferPage = () => {
6662
- if (ns?.bgGradient) return "gradient";
6663
- if (String(ns?.bgImage ?? "").trim()) return "image";
6664
- return "solid";
6665
- };
6666
- const inferContent = () => {
6667
- if (ns?.contentBgGradient) return "gradient";
6668
- if (String(ns?.contentBgImage ?? "").trim()) return "image";
6669
- return "solid";
6670
- };
6671
- setPageBgUiStep(inferPage());
6672
- setContentBgUiStep(inferContent());
6642
+ setJsonLoading(true);
6643
+ requestAnimationFrame(() => {
6644
+ requestAnimationFrame(() => {
6645
+ try {
6646
+ let parsed = input;
6647
+ if (typeof input === "string") {
6648
+ try {
6649
+ parsed = JSON.parse(input);
6650
+ } catch {
6651
+ return;
6652
+ }
6653
+ }
6654
+ const norm = normalizeEmailDesignInput(parsed);
6655
+ if (!norm) return;
6656
+ setRows(norm.rows);
6657
+ const ns = norm.settings;
6658
+ setSettings((s) => ({ ...s, ...ns }));
6659
+ const inferPage = () => {
6660
+ if (ns?.bgGradient) return "gradient";
6661
+ if (String(ns?.bgImage ?? "").trim()) return "image";
6662
+ return "solid";
6663
+ };
6664
+ const inferContent = () => {
6665
+ if (ns?.contentBgGradient) return "gradient";
6666
+ if (String(ns?.contentBgImage ?? "").trim()) return "image";
6667
+ return "solid";
6668
+ };
6669
+ setPageBgUiStep(inferPage());
6670
+ setContentBgUiStep(inferContent());
6671
+ } finally {
6672
+ setJsonLoading(false);
6673
+ }
6674
+ });
6675
+ });
6673
6676
  },
6674
6677
  exportJson(cb, pretty) {
6675
6678
  const design = designToEmailDocument(rowsRef.current, settingsRef.current);
@@ -6893,7 +6896,7 @@ var ReactEmailEditor = forwardRef(
6893
6896
  }));
6894
6897
  return;
6895
6898
  }
6896
- const loc = { type: "nested", rowId, outerCellIdx: cellIdx, blockIdx: inner.parentBlockIdx, innerCellIdx: inner.cellIdx };
6899
+ const loc = { type: "nested", rowId, outerCellIdx: cellIdx, blockIdx: ci, innerCellIdx: inner.cellIdx };
6897
6900
  mutate((prev) => updateColumnAt(prev, loc, (c) => c.map((b, j) => j === inner.contentIdx ? upd : b)));
6898
6901
  };
6899
6902
  const selectContent = (contentId, rowId, cellIdx, parentBlockIdx, inner = null) => {
@@ -7056,6 +7059,7 @@ var ReactEmailEditor = forwardRef(
7056
7059
  ::-webkit-scrollbar-track{background:transparent;}
7057
7060
  ::-webkit-scrollbar-thumb{background:${C.border};border-radius:3px;}
7058
7061
  ::-webkit-scrollbar-thumb:hover{background:${C.muted};}
7062
+ @keyframes email-editor-json-spin{to{transform:rotate(360deg);}}
7059
7063
  ` }),
7060
7064
  /* @__PURE__ */ jsxs9("div", { id: eid("workspace"), style: {
7061
7065
  display: "flex",
@@ -7623,7 +7627,6 @@ var ReactEmailEditor = forwardRef(
7623
7627
  setSelMeta(null);
7624
7628
  setSelectedRowId(null);
7625
7629
  },
7626
- mergeTags,
7627
7630
  onUpload,
7628
7631
  C
7629
7632
  }
@@ -7692,11 +7695,43 @@ var ReactEmailEditor = forwardRef(
7692
7695
  e.dataTransfer.setData("application/json", JSON.stringify({ contentType: bt.type }));
7693
7696
  },
7694
7697
  onClick: () => {
7695
- const targetRow = selectedRowId ? rows.find((r) => r.id === selectedRowId) : rows[rows.length - 1];
7696
- if (!targetRow) return;
7697
- dropContent(targetRow.id, 0, { kind: "new", contentType: bt.type, insertAt: null });
7698
+ if (!rows.length) return;
7699
+ if (selContentMeta?.inner) {
7700
+ const { rowId, cellIdx, contentIdx, inner } = selContentMeta;
7701
+ dropContent(rowId, cellIdx, {
7702
+ kind: "new",
7703
+ contentType: bt.type,
7704
+ insertAt: null,
7705
+ nested: { parentBlockIdx: contentIdx, innerCellIdx: inner.cellIdx }
7706
+ });
7707
+ return;
7708
+ }
7709
+ if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0) {
7710
+ const r = rows.find((x) => x.id === selContentMeta.rowId);
7711
+ if (r && selContentMeta.cellIdx < r.cells.length) {
7712
+ dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
7713
+ kind: "new",
7714
+ contentType: bt.type,
7715
+ insertAt: null
7716
+ });
7717
+ return;
7718
+ }
7719
+ }
7720
+ if (selectedRowId) {
7721
+ const targetRow = rows.find((r) => r.id === selectedRowId);
7722
+ if (targetRow) {
7723
+ dropContent(targetRow.id, 0, {
7724
+ kind: "new",
7725
+ contentType: bt.type,
7726
+ insertAt: null
7727
+ });
7728
+ return;
7729
+ }
7730
+ }
7731
+ const main = rows[0];
7732
+ dropContent(main.id, 0, { kind: "new", contentType: bt.type, insertAt: null });
7698
7733
  },
7699
- title: `Drag into a cell or click to add to ${selectedRowId ? "selected row" : "last row"}`,
7734
+ title: selContentMeta?.inner ? "Click to add inside the selected nested column" : selContentMeta?.rowId ? "Click to add in the selected column" : selectedRowId ? "Click to add to column 1 of the selected row" : "Click to add to the first row, first column",
7700
7735
  style: {
7701
7736
  display: "flex",
7702
7737
  flexDirection: "row",
@@ -7984,7 +8019,47 @@ var ReactEmailEditor = forwardRef(
7984
8019
  ) })
7985
8020
  ]
7986
8021
  }
7987
- )
8022
+ ),
8023
+ jsonLoading ? /* @__PURE__ */ jsxs9(
8024
+ "div",
8025
+ {
8026
+ id: eid("json-loading"),
8027
+ role: "status",
8028
+ "aria-live": "polite",
8029
+ "aria-busy": true,
8030
+ style: {
8031
+ position: "absolute",
8032
+ inset: 0,
8033
+ zIndex: 180,
8034
+ background: `${C.bg}f2`,
8035
+ backdropFilter: "blur(2px)",
8036
+ WebkitBackdropFilter: "blur(2px)",
8037
+ display: "flex",
8038
+ flexDirection: "column",
8039
+ alignItems: "center",
8040
+ justifyContent: "center",
8041
+ gap: 14,
8042
+ pointerEvents: "auto"
8043
+ },
8044
+ children: [
8045
+ /* @__PURE__ */ jsx11(
8046
+ "div",
8047
+ {
8048
+ "aria-hidden": true,
8049
+ style: {
8050
+ width: 40,
8051
+ height: 40,
8052
+ borderRadius: "50%",
8053
+ border: `3px solid ${C.border}`,
8054
+ borderTopColor: C.accent,
8055
+ animation: "email-editor-json-spin 0.65s linear infinite"
8056
+ }
8057
+ }
8058
+ ),
8059
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 13, fontWeight: 600, color: C.text }, children: L("loadingDesign") })
8060
+ ]
8061
+ }
8062
+ ) : null
7988
8063
  ] }),
7989
8064
  /* @__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: [
7990
8065
  /* @__PURE__ */ jsxs9("span", { children: [