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.cjs CHANGED
@@ -152,7 +152,6 @@ var I18N = {
152
152
  delete: "Delete",
153
153
  moveUp: "Move Up",
154
154
  moveDown: "Move Down",
155
- mergeTags: "Merge Tags",
156
155
  autoSaved: "Auto-saved",
157
156
  search: "Search blocks\u2026",
158
157
  zoomIn: "Zoom In",
@@ -170,7 +169,8 @@ var I18N = {
170
169
  blockPaletteGroupWidgets: "Widgets",
171
170
  closePanel: "Close",
172
171
  canvasEmptyHint: "Drag blocks from the library into the column, or add a Layout block for multi-column sections.",
173
- emailContentSettings: "Email content"
172
+ emailContentSettings: "Email content",
173
+ loadingDesign: "Loading design\u2026"
174
174
  },
175
175
  fr: {
176
176
  layouts: "Mises en page",
@@ -210,7 +210,6 @@ var I18N = {
210
210
  delete: "Supprimer",
211
211
  moveUp: "Monter",
212
212
  moveDown: "Descendre",
213
- mergeTags: "Balises fusion",
214
213
  autoSaved: "Auto-sauvegard\xE9",
215
214
  search: "Rechercher\u2026",
216
215
  colorScheme: "Sch\xE9ma de couleurs",
@@ -223,6 +222,7 @@ var I18N = {
223
222
  blockPaletteGroupWidgets: "Widgets",
224
223
  closePanel: "Fermer",
225
224
  canvasEmptyHint: "Glissez des blocs dans la colonne, ou ajoutez un bloc Mise en page pour plusieurs colonnes.",
225
+ loadingDesign: "Chargement du design\u2026",
226
226
  emailContentSettings: "Contenu de l\u2019e-mail",
227
227
  zoomIn: "Zoom +",
228
228
  zoomOut: "Zoom -",
@@ -269,7 +269,6 @@ var I18N = {
269
269
  delete: "L\xF6schen",
270
270
  moveUp: "Nach oben",
271
271
  moveDown: "Nach unten",
272
- mergeTags: "Merge-Tags",
273
272
  autoSaved: "Auto-gespeichert",
274
273
  search: "Bl\xF6cke suchen\u2026",
275
274
  colorScheme: "Farbschema",
@@ -282,6 +281,7 @@ var I18N = {
282
281
  blockPaletteGroupWidgets: "Widgets",
283
282
  closePanel: "Schlie\xDFen",
284
283
  canvasEmptyHint: "Bl\xF6cke in die Spalte ziehen oder Layout-Block f\xFCr mehrspaltige Bereiche hinzuf\xFCgen.",
284
+ loadingDesign: "Design wird geladen\u2026",
285
285
  emailContentSettings: "E-Mail-Inhalt",
286
286
  zoomIn: "Zoom +",
287
287
  zoomOut: "Zoom -",
@@ -326,7 +326,6 @@ var I18N = {
326
326
  delete: "Eliminar",
327
327
  moveUp: "Subir",
328
328
  moveDown: "Bajar",
329
- mergeTags: "Etiquetas de fusi\xF3n",
330
329
  autoSaved: "Auto-guardado",
331
330
  search: "Buscar bloques\u2026",
332
331
  zoomIn: "Zoom +",
@@ -342,6 +341,7 @@ var I18N = {
342
341
  blockPaletteGroupWidgets: "Widgets",
343
342
  closePanel: "Cerrar",
344
343
  canvasEmptyHint: "Arrastra bloques a la columna o a\xF1ade un bloque Dise\xF1o para varias columnas.",
344
+ loadingDesign: "Cargando dise\xF1o\u2026",
345
345
  emailContentSettings: "Contenido del correo",
346
346
  resizeSidebar: "Redimensionar panel"
347
347
  }
@@ -2111,13 +2111,37 @@ function mapEmbeddedLayoutCellBlock(raw, layoutDepth = 0) {
2111
2111
  }
2112
2112
  return mapBlockToInternal(r, layoutDepth);
2113
2113
  }
2114
+ function mergeLayoutColumnStylesMaps(fromContent, fromHydrated) {
2115
+ const isObj = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
2116
+ if (!isObj(fromContent) && !isObj(fromHydrated)) return void 0;
2117
+ const a = isObj(fromContent) ? fromContent : {};
2118
+ const b = isObj(fromHydrated) ? fromHydrated : {};
2119
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
2120
+ const out = {};
2121
+ for (const k of keys) {
2122
+ const va = a[k];
2123
+ const vb = b[k];
2124
+ if (isObj(va) && isObj(vb)) {
2125
+ out[k] = { ...va, ...vb };
2126
+ } else if (vb !== void 0) {
2127
+ out[k] = vb;
2128
+ } else {
2129
+ out[k] = va;
2130
+ }
2131
+ }
2132
+ return Object.keys(out).length ? out : void 0;
2133
+ }
2114
2134
  function applyImportedEditorPropsFromDoc(block, t, b, layoutDepth = 0) {
2115
2135
  const exp = b.props;
2116
2136
  if (!exp || typeof exp !== "object" || Array.isArray(exp)) return;
2117
2137
  if (t === "layout" && Array.isArray(exp.cells)) {
2138
+ const columnStylesFromContent = block.props.columnStyles;
2118
2139
  const hb = hydrateBlock({ type: "layout", id: block.id, props: exp }, layoutDepth);
2119
2140
  if (hb) {
2120
2141
  block.props = hb.props;
2142
+ const merged = mergeLayoutColumnStylesMaps(columnStylesFromContent, block.props.columnStyles);
2143
+ if (merged) block.props.columnStyles = merged;
2144
+ else delete block.props.columnStyles;
2121
2145
  if (typeof hb.id === "string" && hb.id) block.id = hb.id;
2122
2146
  }
2123
2147
  return;
@@ -2210,9 +2234,8 @@ function useLiveCountdown(endDate) {
2210
2234
  const s = Math.floor(diff % 6e4 / 1e3);
2211
2235
  return [d, h, m, s];
2212
2236
  }
2213
- function editChromeBorder(C, preview) {
2214
- if (preview) return {};
2215
- return { border: `1px dotted ${C.border}`, boxSizing: "border-box" };
2237
+ function editChromeBorder(_C, _preview) {
2238
+ return {};
2216
2239
  }
2217
2240
  function blockLinkCaptureHandler(preview, e) {
2218
2241
  if (preview) return;
@@ -2514,6 +2537,8 @@ function NestedRowBlock({
2514
2537
  rowId,
2515
2538
  cellIdx,
2516
2539
  parentBlockIdx,
2540
+ /** When this layout sits inside another layout column, meta uses `contentIdx` + `inner` like other nested blocks. */
2541
+ selectionInner,
2517
2542
  editorId,
2518
2543
  selectedKey,
2519
2544
  selContentMeta,
@@ -2525,7 +2550,7 @@ function NestedRowBlock({
2525
2550
  C
2526
2551
  }) {
2527
2552
  const p = block.props;
2528
- const rowSelected = !preview && selContentMeta && selContentMeta.rowId === rowId && selContentMeta.cellIdx === cellIdx && selContentMeta.contentIdx === parentBlockIdx && !selContentMeta.inner && selectedKey === block.id;
2553
+ 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);
2529
2554
  const rowStyle = {
2530
2555
  padding: p.padding ?? 8,
2531
2556
  position: "relative",
@@ -2549,7 +2574,13 @@ function NestedRowBlock({
2549
2574
  style: rowStyle,
2550
2575
  onClick: preview ? void 0 : (e) => {
2551
2576
  e.stopPropagation();
2552
- onSelectContent(block.id, rowId, cellIdx, parentBlockIdx, null);
2577
+ onSelectContent(
2578
+ block.id,
2579
+ rowId,
2580
+ cellIdx,
2581
+ parentBlockIdx,
2582
+ selectionInner ?? null
2583
+ );
2553
2584
  },
2554
2585
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: p.gap ?? 12 }, children: (p.cells || []).map((innerBlocks, ici) => {
2555
2586
  const cS = p.columnStyles && p.columnStyles[ici] || {};
@@ -2615,7 +2646,13 @@ function BlockItem({
2615
2646
  if (!onSelectContent) return;
2616
2647
  e.stopPropagation();
2617
2648
  if (isSplitLayoutBlock(cb)) {
2618
- onSelectContent(cb.id, rowId, cellIdx, nest ? nest.parentBlockIdx : ci, null);
2649
+ onSelectContent(
2650
+ cb.id,
2651
+ rowId,
2652
+ cellIdx,
2653
+ nest ? nest.parentBlockIdx : ci,
2654
+ nest ? { cellIdx: innerCellIdx, contentIdx: ci } : null
2655
+ );
2619
2656
  } else {
2620
2657
  onSelectContent(
2621
2658
  cb.id,
@@ -2686,6 +2723,7 @@ function BlockItem({
2686
2723
  rowId,
2687
2724
  cellIdx,
2688
2725
  parentBlockIdx: nest ? nest.parentBlockIdx : ci,
2726
+ selectionInner: nest ? { cellIdx: innerCellIdx, contentIdx: ci } : null,
2689
2727
  editorId,
2690
2728
  selectedKey,
2691
2729
  selContentMeta,
@@ -3273,11 +3311,8 @@ function ToolbarTypographyControls({ editor, C }) {
3273
3311
  }
3274
3312
  function FormattingToolbar({
3275
3313
  editor,
3276
- C,
3277
- mergeTags,
3278
- onMergeTagInsert
3314
+ C
3279
3315
  }) {
3280
- const [mergeSel, setMergeSel] = (0, import_react2.useState)("");
3281
3316
  const [linkPanelOpen, setLinkPanelOpen] = (0, import_react2.useState)(false);
3282
3317
  const [linkUrlValue, setLinkUrlValue] = (0, import_react2.useState)("");
3283
3318
  const ibtn = (active) => toolbarIconBtn(C, active);
@@ -3597,8 +3632,6 @@ function TextRichEditor({
3597
3632
  value,
3598
3633
  onChange,
3599
3634
  typography,
3600
- mergeTags,
3601
- onMergeTagInsert,
3602
3635
  placeholder = "Write your message\u2026",
3603
3636
  headerTitle = "Rich editor",
3604
3637
  C
@@ -3742,15 +3775,7 @@ function TextRichEditor({
3742
3775
  ]
3743
3776
  }
3744
3777
  ),
3745
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3746
- FormattingToolbar,
3747
- {
3748
- editor,
3749
- C,
3750
- mergeTags,
3751
- onMergeTagInsert
3752
- }
3753
- ),
3778
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FormattingToolbar, { editor, C }),
3754
3779
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: proseStyle }),
3755
3780
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3756
3781
  "div",
@@ -3801,15 +3826,7 @@ function TextRichEditor({
3801
3826
  background: C.inputBg
3802
3827
  },
3803
3828
  children: [
3804
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3805
- FormattingToolbar,
3806
- {
3807
- editor,
3808
- C,
3809
- mergeTags,
3810
- onMergeTagInsert
3811
- }
3812
- ),
3829
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FormattingToolbar, { editor, C }),
3813
3830
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: proseStyle }),
3814
3831
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3815
3832
  "div",
@@ -4809,7 +4826,7 @@ function BlockSurfaceBgInspector({
4809
4826
  ] }) : null
4810
4827
  ] });
4811
4828
  }
4812
- function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }) {
4829
+ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4813
4830
  const { IS, CI } = useIS(C);
4814
4831
  const imageFileRef = (0, import_react4.useRef)(null);
4815
4832
  const p = block.props;
@@ -4828,7 +4845,6 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4828
4845
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "checkbox", checked: !!p[k], onChange: (e) => set(k, e.target.checked), style: { width: 15, height: 15, accentColor: C.accent } }),
4829
4846
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: C.muted, fontSize: 12 }, children: p[k] ? "On" : "Off" })
4830
4847
  ] }) });
4831
- const TagPicker = (_field) => null;
4832
4848
  const FONTS2 = ["Georgia,serif", "Arial,sans-serif", "Verdana,sans-serif", "'Courier New',monospace", "Trebuchet MS,sans-serif", "Impact,sans-serif"];
4833
4849
  const ImgUpload = (key) => {
4834
4850
  if (!onUpload) return null;
@@ -4882,7 +4898,7 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4882
4898
  ] });
4883
4899
  case "text":
4884
4900
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
4885
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "Content (HTML)", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4901
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "Content", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4886
4902
  "textarea",
4887
4903
  {
4888
4904
  style: { ...IS, minHeight: 200, resize: "vertical", fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", fontSize: 12, lineHeight: 1.45 },
@@ -4893,24 +4909,6 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4893
4909
  },
4894
4910
  block.id
4895
4911
  ) }),
4896
- mergeTags && mergeTags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: "Insert merge tag", C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4897
- "select",
4898
- {
4899
- style: IS,
4900
- defaultValue: "",
4901
- onChange: (e) => {
4902
- const v = e.target.value;
4903
- if (v) {
4904
- set("content", normalizeRichHtmlForStorage((p.content || "") + " " + v));
4905
- e.target.value = "";
4906
- }
4907
- },
4908
- children: [
4909
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "", children: "Choose\u2026" }),
4910
- mergeTags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: t.value, children: t.name }, t.name))
4911
- ]
4912
- }
4913
- ) }) : null,
4914
4912
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NumRangePx, { label: "Font size", value: p.fontSize ?? 15, onChange: (n) => set("fontSize", n), min: 8, max: 96, step: 1, C }),
4915
4913
  Col("color", "Color"),
4916
4914
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right", "justify"], C }),
@@ -4940,8 +4938,6 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
4940
4938
  letterSpacing: p.letterSpacing,
4941
4939
  padding: p.padding
4942
4940
  },
4943
- mergeTags,
4944
- onMergeTagInsert: (tag) => set("content", (p.content || "") + " " + tag),
4945
4941
  placeholder: "Write your rich content\u2026",
4946
4942
  headerTitle: "Rich editor",
4947
4943
  C
@@ -5348,6 +5344,19 @@ function ContentBlockEditor({ block, onChange, mergeTags, onClose, onUpload, C }
5348
5344
  return null;
5349
5345
  }
5350
5346
  }
5347
+ function inferLayoutRowBgStep(r) {
5348
+ if (r.bgGradient) return "gradient";
5349
+ if (String(r.bgImage || "").trim()) return "image";
5350
+ if (String(r.bgColor || "").trim()) return "solid";
5351
+ return null;
5352
+ }
5353
+ function inferColumnBgStep(columnStyles, selCol) {
5354
+ const cs = (columnStyles || {})[selCol] || {};
5355
+ if (cs.bgGradient) return "gradient";
5356
+ if (String(cs.bgImage || "").trim()) return "image";
5357
+ if (String(cs.bgColor || "").trim()) return "solid";
5358
+ return null;
5359
+ }
5351
5360
  function LayoutRowEditor({
5352
5361
  row,
5353
5362
  onChange,
@@ -5359,49 +5368,37 @@ function LayoutRowEditor({
5359
5368
  customizationHint
5360
5369
  }) {
5361
5370
  const { IS, CI } = useIS(C);
5362
- const [selCol, setSelCol] = (0, import_react4.useState)(initialCol);
5363
- const [rowBgUiStep, setRowBgUiStep] = (0, import_react4.useState)(null);
5364
- const [colBgUiStep, setColBgUiStep] = (0, import_react4.useState)(null);
5371
+ const colCount = Math.max(1, row.ratios?.length || row.cells?.length || 1);
5372
+ const ratioList = row.ratios?.length ? row.ratios : [1];
5373
+ const [selCol, setSelCol] = (0, import_react4.useState)(0);
5374
+ const [rowBgPickerMode, setRowBgPickerMode] = (0, import_react4.useState)(null);
5375
+ const [colBgPickerMode, setColBgPickerMode] = (0, import_react4.useState)(null);
5376
+ const inferredRowBg = (0, import_react4.useMemo)(() => inferLayoutRowBgStep(row), [row.bgGradient, row.bgImage, row.bgColor]);
5377
+ const inferredColBg = (0, import_react4.useMemo)(
5378
+ () => inferColumnBgStep(row.columnStyles, selCol),
5379
+ [row.columnStyles, selCol]
5380
+ );
5381
+ const rowBgUiStep = inferredRowBg ?? rowBgPickerMode;
5382
+ const colBgUiStep = inferredColBg ?? colBgPickerMode;
5365
5383
  (0, import_react4.useEffect)(() => {
5366
- if (initialCol !== null && initialCol !== void 0) {
5367
- setSelCol(initialCol);
5384
+ if (initialCol !== null && initialCol !== void 0 && initialCol >= 0) {
5385
+ setSelCol(Math.min(initialCol, colCount - 1));
5386
+ } else {
5387
+ setSelCol((c) => Math.min(Math.max(0, c), colCount - 1));
5368
5388
  }
5369
- }, [initialCol]);
5389
+ }, [initialCol, row.id, colCount]);
5370
5390
  (0, import_react4.useEffect)(() => {
5371
- if (row.bgGradient) {
5372
- setRowBgUiStep("gradient");
5373
- return;
5374
- }
5375
- if (String(row.bgImage || "").trim()) {
5376
- setRowBgUiStep("image");
5377
- return;
5378
- }
5379
- if (String(row.bgColor || "").trim()) {
5380
- setRowBgUiStep("solid");
5381
- return;
5382
- }
5383
- setRowBgUiStep(null);
5391
+ setRowBgPickerMode(null);
5384
5392
  }, [row.id]);
5385
5393
  (0, import_react4.useEffect)(() => {
5386
- if (selCol === null) {
5387
- setColBgUiStep(null);
5388
- return;
5389
- }
5390
- const cs = (row.columnStyles || {})[selCol] || {};
5391
- if (cs.bgGradient) {
5392
- setColBgUiStep("gradient");
5393
- return;
5394
- }
5395
- if (String(cs.bgImage || "").trim()) {
5396
- setColBgUiStep("image");
5397
- return;
5398
- }
5399
- if (String(cs.bgColor || "").trim()) {
5400
- setColBgUiStep("solid");
5401
- return;
5402
- }
5403
- setColBgUiStep(null);
5394
+ setColBgPickerMode(null);
5404
5395
  }, [selCol, row.id]);
5396
+ (0, import_react4.useEffect)(() => {
5397
+ if (inferredRowBg) setRowBgPickerMode(null);
5398
+ }, [inferredRowBg]);
5399
+ (0, import_react4.useEffect)(() => {
5400
+ if (inferredColBg) setColBgPickerMode(null);
5401
+ }, [inferredColBg]);
5405
5402
  const set = (k, v) => onChange({ ...row, [k]: v });
5406
5403
  const applyColumnCount = (n) => {
5407
5404
  const prevCells = [...row.cells || []];
@@ -5417,16 +5414,13 @@ function LayoutRowEditor({
5417
5414
  }
5418
5415
  onChange({ ...row, cols: n, preset: "custom", cells, ratios });
5419
5416
  };
5420
- const colCount = Math.max(1, row.ratios?.length || row.cells?.length || 1);
5421
- const ratioList = row.ratios?.length ? row.ratios : [1];
5422
5417
  const updCol = (i, upd) => {
5423
5418
  const styles = { ...row.columnStyles || {} };
5424
5419
  styles[i] = { ...styles[i] || {}, ...upd };
5425
5420
  set("columnStyles", styles);
5426
5421
  };
5427
5422
  const setColBgMode = (mode) => {
5428
- if (selCol === null) return;
5429
- setColBgUiStep(mode);
5423
+ setColBgPickerMode(mode);
5430
5424
  const cs = (row.columnStyles || {})[selCol] || {};
5431
5425
  if (mode === "solid") {
5432
5426
  updCol(selCol, { bgGradient: null, bgImage: "" });
@@ -5444,7 +5438,7 @@ function LayoutRowEditor({
5444
5438
  const total = ratioList.reduce((a, b) => a + b, 0) || 1;
5445
5439
  const POS_OPTS = ["center", "top", "bottom", "left", "right", "top left", "top right", "bottom left", "bottom right"];
5446
5440
  const setRowBgMode = (mode) => {
5447
- setRowBgUiStep(mode);
5441
+ setRowBgPickerMode(mode);
5448
5442
  if (mode === "solid") {
5449
5443
  set("bgGradient", null);
5450
5444
  set("bgImage", "");
@@ -5602,7 +5596,7 @@ function LayoutRowEditor({
5602
5596
  },
5603
5597
  i
5604
5598
  )) }),
5605
- selCol !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { background: C.surface, padding: 10, borderRadius: 8, border: `1px solid ${C.border}` }, children: [
5599
+ colCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { background: C.surface, padding: 10, borderRadius: 8, border: `1px solid ${C.border}` }, children: [
5606
5600
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BgModeButtons, { value: colBgUiStep, onChange: setColBgMode, C }),
5607
5601
  colBgUiStep === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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,
5608
5602
  colBgUiStep === "solid" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PR, { label: `col ${selCol + 1} bg`, C, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
@@ -6488,11 +6482,11 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6488
6482
  const [previewDevice, setPreviewDevice] = (0, import_react8.useState)("desktop");
6489
6483
  const [activeTab, setActiveTab] = (0, import_react8.useState)("blocks");
6490
6484
  const [templatesOpen, setTemplatesOpen] = (0, import_react8.useState)(false);
6485
+ const [jsonLoading, setJsonLoading] = (0, import_react8.useState)(false);
6491
6486
  const [autoSaveMsg, setAutoSaveMsg] = (0, import_react8.useState)("");
6492
6487
  const [locale, setLocale] = (0, import_react8.useState)(
6493
6488
  typeof opt.locale === "string" ? opt.locale : "en"
6494
6489
  );
6495
- const [mergeTags, setMergeTagsState] = (0, import_react8.useState)(() => opt.mergeTags ?? []);
6496
6490
  const [zoom, setZoom] = (0, import_react8.useState)(100);
6497
6491
  const [rightRailWidth, setRightRailWidth] = (0, import_react8.useState)(RIGHT_RAIL_DEFAULT_W);
6498
6492
  const [ctxMenu, setCtxMenu] = (0, import_react8.useState)(null);
@@ -6630,31 +6624,40 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6630
6624
  }, [rows, history, future, selContentMeta]);
6631
6625
  const buildApi = (0, import_react8.useCallback)(() => ({
6632
6626
  loadJson(input) {
6633
- let parsed = input;
6634
- if (typeof input === "string") {
6635
- try {
6636
- parsed = JSON.parse(input);
6637
- } catch {
6638
- return;
6639
- }
6640
- }
6641
- const norm = normalizeEmailDesignInput(parsed);
6642
- if (!norm) return;
6643
- setRows(norm.rows);
6644
- const ns = norm.settings;
6645
- setSettings((s) => ({ ...s, ...ns }));
6646
- const inferPage = () => {
6647
- if (ns?.bgGradient) return "gradient";
6648
- if (String(ns?.bgImage ?? "").trim()) return "image";
6649
- return "solid";
6650
- };
6651
- const inferContent = () => {
6652
- if (ns?.contentBgGradient) return "gradient";
6653
- if (String(ns?.contentBgImage ?? "").trim()) return "image";
6654
- return "solid";
6655
- };
6656
- setPageBgUiStep(inferPage());
6657
- setContentBgUiStep(inferContent());
6627
+ setJsonLoading(true);
6628
+ requestAnimationFrame(() => {
6629
+ requestAnimationFrame(() => {
6630
+ try {
6631
+ let parsed = input;
6632
+ if (typeof input === "string") {
6633
+ try {
6634
+ parsed = JSON.parse(input);
6635
+ } catch {
6636
+ return;
6637
+ }
6638
+ }
6639
+ const norm = normalizeEmailDesignInput(parsed);
6640
+ if (!norm) return;
6641
+ setRows(norm.rows);
6642
+ const ns = norm.settings;
6643
+ setSettings((s) => ({ ...s, ...ns }));
6644
+ const inferPage = () => {
6645
+ if (ns?.bgGradient) return "gradient";
6646
+ if (String(ns?.bgImage ?? "").trim()) return "image";
6647
+ return "solid";
6648
+ };
6649
+ const inferContent = () => {
6650
+ if (ns?.contentBgGradient) return "gradient";
6651
+ if (String(ns?.contentBgImage ?? "").trim()) return "image";
6652
+ return "solid";
6653
+ };
6654
+ setPageBgUiStep(inferPage());
6655
+ setContentBgUiStep(inferContent());
6656
+ } finally {
6657
+ setJsonLoading(false);
6658
+ }
6659
+ });
6660
+ });
6658
6661
  },
6659
6662
  exportJson(cb, pretty) {
6660
6663
  const design = designToEmailDocument(rowsRef.current, settingsRef.current);
@@ -6878,7 +6881,7 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
6878
6881
  }));
6879
6882
  return;
6880
6883
  }
6881
- const loc = { type: "nested", rowId, outerCellIdx: cellIdx, blockIdx: inner.parentBlockIdx, innerCellIdx: inner.cellIdx };
6884
+ const loc = { type: "nested", rowId, outerCellIdx: cellIdx, blockIdx: ci, innerCellIdx: inner.cellIdx };
6882
6885
  mutate((prev) => updateColumnAt(prev, loc, (c) => c.map((b, j) => j === inner.contentIdx ? upd : b)));
6883
6886
  };
6884
6887
  const selectContent = (contentId, rowId, cellIdx, parentBlockIdx, inner = null) => {
@@ -7041,6 +7044,7 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7041
7044
  ::-webkit-scrollbar-track{background:transparent;}
7042
7045
  ::-webkit-scrollbar-thumb{background:${C.border};border-radius:3px;}
7043
7046
  ::-webkit-scrollbar-thumb:hover{background:${C.muted};}
7047
+ @keyframes email-editor-json-spin{to{transform:rotate(360deg);}}
7044
7048
  ` }),
7045
7049
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { id: eid("workspace"), style: {
7046
7050
  display: "flex",
@@ -7608,7 +7612,6 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7608
7612
  setSelMeta(null);
7609
7613
  setSelectedRowId(null);
7610
7614
  },
7611
- mergeTags,
7612
7615
  onUpload,
7613
7616
  C
7614
7617
  }
@@ -7677,11 +7680,43 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7677
7680
  e.dataTransfer.setData("application/json", JSON.stringify({ contentType: bt.type }));
7678
7681
  },
7679
7682
  onClick: () => {
7680
- const targetRow = selectedRowId ? rows.find((r) => r.id === selectedRowId) : rows[rows.length - 1];
7681
- if (!targetRow) return;
7682
- dropContent(targetRow.id, 0, { kind: "new", contentType: bt.type, insertAt: null });
7683
+ if (!rows.length) return;
7684
+ if (selContentMeta?.inner) {
7685
+ const { rowId, cellIdx, contentIdx, inner } = selContentMeta;
7686
+ dropContent(rowId, cellIdx, {
7687
+ kind: "new",
7688
+ contentType: bt.type,
7689
+ insertAt: null,
7690
+ nested: { parentBlockIdx: contentIdx, innerCellIdx: inner.cellIdx }
7691
+ });
7692
+ return;
7693
+ }
7694
+ if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0) {
7695
+ const r = rows.find((x) => x.id === selContentMeta.rowId);
7696
+ if (r && selContentMeta.cellIdx < r.cells.length) {
7697
+ dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
7698
+ kind: "new",
7699
+ contentType: bt.type,
7700
+ insertAt: null
7701
+ });
7702
+ return;
7703
+ }
7704
+ }
7705
+ if (selectedRowId) {
7706
+ const targetRow = rows.find((r) => r.id === selectedRowId);
7707
+ if (targetRow) {
7708
+ dropContent(targetRow.id, 0, {
7709
+ kind: "new",
7710
+ contentType: bt.type,
7711
+ insertAt: null
7712
+ });
7713
+ return;
7714
+ }
7715
+ }
7716
+ const main = rows[0];
7717
+ dropContent(main.id, 0, { kind: "new", contentType: bt.type, insertAt: null });
7683
7718
  },
7684
- title: `Drag into a cell or click to add to ${selectedRowId ? "selected row" : "last row"}`,
7719
+ 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",
7685
7720
  style: {
7686
7721
  display: "flex",
7687
7722
  flexDirection: "row",
@@ -7969,7 +8004,47 @@ var ReactEmailEditor = (0, import_react8.forwardRef)(
7969
8004
  ) })
7970
8005
  ]
7971
8006
  }
7972
- )
8007
+ ),
8008
+ jsonLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
8009
+ "div",
8010
+ {
8011
+ id: eid("json-loading"),
8012
+ role: "status",
8013
+ "aria-live": "polite",
8014
+ "aria-busy": true,
8015
+ style: {
8016
+ position: "absolute",
8017
+ inset: 0,
8018
+ zIndex: 180,
8019
+ background: `${C.bg}f2`,
8020
+ backdropFilter: "blur(2px)",
8021
+ WebkitBackdropFilter: "blur(2px)",
8022
+ display: "flex",
8023
+ flexDirection: "column",
8024
+ alignItems: "center",
8025
+ justifyContent: "center",
8026
+ gap: 14,
8027
+ pointerEvents: "auto"
8028
+ },
8029
+ children: [
8030
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
8031
+ "div",
8032
+ {
8033
+ "aria-hidden": true,
8034
+ style: {
8035
+ width: 40,
8036
+ height: 40,
8037
+ borderRadius: "50%",
8038
+ border: `3px solid ${C.border}`,
8039
+ borderTopColor: C.accent,
8040
+ animation: "email-editor-json-spin 0.65s linear infinite"
8041
+ }
8042
+ }
8043
+ ),
8044
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontSize: 13, fontWeight: 600, color: C.text }, children: L("loadingDesign") })
8045
+ ]
8046
+ }
8047
+ ) : null
7973
8048
  ] }),
7974
8049
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
7975
8050
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [