react-email-studio 3.2.0 → 3.3.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
@@ -286,6 +286,18 @@ type JsonToHtmlOptions = EmailHtmlOptions;
286
286
  */
287
287
  declare function jsonToHtml(designInput: unknown, opts?: JsonToHtmlOptions): string;
288
288
 
289
+ /**
290
+ * Pull usable HTML from a full document (`<!DOCTYPE` / `<html>`) or a fragment string.
291
+ */
292
+ declare function extractHtmlForDesign(html: string): string;
293
+ /**
294
+ * Wrap HTML (fragment or full email/page document) in a minimal `email_document`
295
+ * with one row, one column, and a single **html** block — suitable for `loadJson`.
296
+ *
297
+ * @returns `null` when there is no non-empty HTML after extraction/normalization.
298
+ */
299
+ declare function htmlToEmailDesignTemplate(html: string): EmailDocument | null;
300
+
289
301
  declare function utf8ToBase64(raw: string): string;
290
302
  declare function base64ToUtf8(b64: string): string;
291
303
 
@@ -413,4 +425,4 @@ declare const DEVICES: ({
413
425
  })[];
414
426
  declare function PreviewModal({ html, contentWidth, rowCount, blockCount, editorZoom, locale, initialDevice, initialMobileVariant, onClose, C, title, secondaryActionLabel, onSecondaryAction, }: EmailPreviewModalProps): react_jsx_runtime.JSX.Element;
415
427
 
416
- export { type BlockBase, type EmailDocument, type EmailDocumentColumn, type EmailDocumentRow, type EmailDocumentSettings, type EmailHtmlOptions, PreviewModal as EmailPreviewModal, type EmailPreviewModalProps, type JsonToHtmlOptions, type MobilePreviewVariant, ReactEmailEditor, type ReactEmailEditorOptions, type ReactEmailEditorProps, type ReactEmailEditorRef, base64ToUtf8, DEVICES as emailPreviewDevices, jsonToHtml, utf8ToBase64 };
428
+ export { type BlockBase, type EmailDocument, type EmailDocumentColumn, type EmailDocumentRow, type EmailDocumentSettings, type EmailHtmlOptions, PreviewModal as EmailPreviewModal, type EmailPreviewModalProps, type JsonToHtmlOptions, type MobilePreviewVariant, ReactEmailEditor, type ReactEmailEditorOptions, type ReactEmailEditorProps, type ReactEmailEditorRef, base64ToUtf8, DEVICES as emailPreviewDevices, extractHtmlForDesign, htmlToEmailDesignTemplate, jsonToHtml, utf8ToBase64 };
package/dist/index.d.ts CHANGED
@@ -286,6 +286,18 @@ type JsonToHtmlOptions = EmailHtmlOptions;
286
286
  */
287
287
  declare function jsonToHtml(designInput: unknown, opts?: JsonToHtmlOptions): string;
288
288
 
289
+ /**
290
+ * Pull usable HTML from a full document (`<!DOCTYPE` / `<html>`) or a fragment string.
291
+ */
292
+ declare function extractHtmlForDesign(html: string): string;
293
+ /**
294
+ * Wrap HTML (fragment or full email/page document) in a minimal `email_document`
295
+ * with one row, one column, and a single **html** block — suitable for `loadJson`.
296
+ *
297
+ * @returns `null` when there is no non-empty HTML after extraction/normalization.
298
+ */
299
+ declare function htmlToEmailDesignTemplate(html: string): EmailDocument | null;
300
+
289
301
  declare function utf8ToBase64(raw: string): string;
290
302
  declare function base64ToUtf8(b64: string): string;
291
303
 
@@ -413,4 +425,4 @@ declare const DEVICES: ({
413
425
  })[];
414
426
  declare function PreviewModal({ html, contentWidth, rowCount, blockCount, editorZoom, locale, initialDevice, initialMobileVariant, onClose, C, title, secondaryActionLabel, onSecondaryAction, }: EmailPreviewModalProps): react_jsx_runtime.JSX.Element;
415
427
 
416
- export { type BlockBase, type EmailDocument, type EmailDocumentColumn, type EmailDocumentRow, type EmailDocumentSettings, type EmailHtmlOptions, PreviewModal as EmailPreviewModal, type EmailPreviewModalProps, type JsonToHtmlOptions, type MobilePreviewVariant, ReactEmailEditor, type ReactEmailEditorOptions, type ReactEmailEditorProps, type ReactEmailEditorRef, base64ToUtf8, DEVICES as emailPreviewDevices, jsonToHtml, utf8ToBase64 };
428
+ export { type BlockBase, type EmailDocument, type EmailDocumentColumn, type EmailDocumentRow, type EmailDocumentSettings, type EmailHtmlOptions, PreviewModal as EmailPreviewModal, type EmailPreviewModalProps, type JsonToHtmlOptions, type MobilePreviewVariant, ReactEmailEditor, type ReactEmailEditorOptions, type ReactEmailEditorProps, type ReactEmailEditorRef, base64ToUtf8, DEVICES as emailPreviewDevices, extractHtmlForDesign, htmlToEmailDesignTemplate, jsonToHtml, utf8ToBase64 };
package/dist/index.js CHANGED
@@ -136,7 +136,7 @@ var I18N = {
136
136
  blockPaletteGroupActions: "Buttons & links",
137
137
  blockPaletteGroupWidgets: "Widgets",
138
138
  closePanel: "Close",
139
- canvasEmptyHint: "Drag blocks from the library into the column, or add a Layout block for multi-column sections.",
139
+ canvasEmptyHint: "Drag row layouts or blocks from the library. Drop blocks into a column or onto the mail preview area.",
140
140
  emailContentSettings: "Email content",
141
141
  loadingDesign: "Loading design\u2026"
142
142
  },
@@ -1091,7 +1091,7 @@ function rowToHtml(row) {
1091
1091
  const r = row.ratios[i] ?? 1;
1092
1092
  const cs = row.columnStyles && row.columnStyles[i] ? row.columnStyles[i] : {};
1093
1093
  const colShell = emailSurfaceBgCss(cs);
1094
- const pad = cs.padding && typeof cs.padding === "object" && !Array.isArray(cs.padding) ? `padding:${(() => {
1094
+ const pad3 = cs.padding && typeof cs.padding === "object" && !Array.isArray(cs.padding) ? `padding:${(() => {
1095
1095
  const o = cs.padding;
1096
1096
  const t = numOr2(o.top, 0);
1097
1097
  const rgt = numOr2(o.right, t);
@@ -1107,7 +1107,7 @@ function rowToHtml(row) {
1107
1107
  const bl = numOr2(x.bl, tr2);
1108
1108
  return `border-radius:${tl}px ${tr2}px ${br}px ${bl}px;`;
1109
1109
  })() : typeof cs.borderRadius === "number" && Number.isFinite(cs.borderRadius) ? `border-radius:${cs.borderRadius}px;` : "";
1110
- return `<div style="flex:${r} 1 0;min-width:0;box-sizing:border-box;${pad}${rad}${colShell}">${cell.map(blockToHtml).join("")}</div>`;
1110
+ return `<div style="flex:${r} 1 0;min-width:0;box-sizing:border-box;${pad3}${rad}${colShell}">${cell.map(blockToHtml).join("")}</div>`;
1111
1111
  }).join("")}</div>`;
1112
1112
  const innerTrim = inner.replace(/>\s+</g, "><").trim();
1113
1113
  const body = innerTrim || "&nbsp;";
@@ -1327,8 +1327,8 @@ function layoutColumnBorderRadiusForDoc(r) {
1327
1327
  }
1328
1328
  return 0;
1329
1329
  }
1330
- function columnPaddingNonZero(pad) {
1331
- return pad.top !== 0 || pad.right !== 0 || pad.bottom !== 0 || pad.left !== 0;
1330
+ function columnPaddingNonZero(pad3) {
1331
+ return pad3.top !== 0 || pad3.right !== 0 || pad3.bottom !== 0 || pad3.left !== 0;
1332
1332
  }
1333
1333
  function columnRadiusNonZero(r) {
1334
1334
  if (typeof r === "number") return r !== 0;
@@ -1685,9 +1685,9 @@ function mapBlockToInternal(b, layoutDepth = 0) {
1685
1685
  if (asNum(s.fontWeight) != null) block.props.fontWeight = s.fontWeight;
1686
1686
  if (asNum(s.borderRadius) != null) block.props.borderRadius = s.borderRadius;
1687
1687
  if (s.padding && typeof s.padding === "object") {
1688
- const pad = s.padding;
1689
- block.props.paddingV = asNum(pad.top) ?? block.props.paddingV;
1690
- block.props.paddingH = asNum(pad.right) ?? block.props.paddingH;
1688
+ const pad3 = s.padding;
1689
+ block.props.paddingV = asNum(pad3.top) ?? block.props.paddingV;
1690
+ block.props.paddingH = asNum(pad3.right) ?? block.props.paddingH;
1691
1691
  }
1692
1692
  if (asStr(s.textAlign)) block.props.align = s.textAlign;
1693
1693
  break;
@@ -2202,9 +2202,15 @@ function useLiveCountdown(endDate) {
2202
2202
  const s = Math.floor(diff % 6e4 / 1e3);
2203
2203
  return [d, h, m, s];
2204
2204
  }
2205
- function editChromeBorder(_C, _preview) {
2206
- return {};
2205
+ function layoutSplitChromeBorder(_C, preview) {
2206
+ if (preview) return {};
2207
+ return {
2208
+ border: "1px dotted rgba(148, 163, 184, 0.5)",
2209
+ boxSizing: "border-box"
2210
+ };
2207
2211
  }
2212
+ var CONTENT_BLOCK_HOVER_BORDER = "1px dotted rgba(148, 163, 184, 0.5)";
2213
+ var EMPTY_CELL_COLUMN_MIN_H = "min(320px, 42vh)";
2208
2214
  function blockLinkCaptureHandler(preview, e) {
2209
2215
  if (preview) return;
2210
2216
  const t = e.target;
@@ -2236,6 +2242,7 @@ function radiusCssLayout(v) {
2236
2242
  return "0px";
2237
2243
  }
2238
2244
  function ContentBlock({ block, selected, onClick, preview, C }) {
2245
+ const [hover, setHover] = useState(false);
2239
2246
  const { type, props: p } = block;
2240
2247
  const numOr2 = (v, fb) => typeof v === "number" && Number.isFinite(v) ? v : fb;
2241
2248
  const boxPx = (v) => {
@@ -2279,14 +2286,19 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
2279
2286
  onClick: preview ? void 0 : onClick || void 0,
2280
2287
  onClickCapture: (e) => blockLinkCaptureHandler(preview, e),
2281
2288
  onAuxClickCapture: (e) => blockLinkCaptureHandler(preview, e),
2289
+ onMouseEnter: preview ? void 0 : () => setHover(true),
2290
+ onMouseLeave: preview ? void 0 : () => setHover(false),
2282
2291
  style: {
2283
2292
  cursor: preview ? "default" : onClick ? "pointer" : "default",
2284
2293
  outline: selected && !preview ? `2px solid ${C.accent}` : "2px solid transparent",
2285
2294
  outlineOffset: 1,
2286
2295
  borderRadius: 3,
2287
- transition: "outline .12s",
2296
+ transition: "outline .12s, border-color .12s",
2288
2297
  position: "relative",
2289
- ...editChromeBorder(C, preview),
2298
+ ...preview ? {} : {
2299
+ border: hover ? CONTENT_BLOCK_HOVER_BORDER : "1px solid transparent",
2300
+ boxSizing: "border-box"
2301
+ },
2290
2302
  ...shellBg,
2291
2303
  padding: paddingCss(p.padding),
2292
2304
  margin: marginCss(p.margin)
@@ -2526,7 +2538,7 @@ function NestedRowBlock({
2526
2538
  outline: rowSelected ? `2px solid ${C.accent}` : "2px solid transparent",
2527
2539
  outlineOffset: 1,
2528
2540
  transition: "outline .12s",
2529
- ...editChromeBorder(C, preview),
2541
+ ...layoutSplitChromeBorder(C, preview),
2530
2542
  ...backgroundLayerStyle({
2531
2543
  bgColor: p.bgColor,
2532
2544
  bgImage: p.bgImage,
@@ -2558,7 +2570,10 @@ function NestedRowBlock({
2558
2570
  style: {
2559
2571
  flex: p.ratios?.[ici] ?? 1,
2560
2572
  minWidth: 0,
2561
- ...editChromeBorder(C, preview),
2573
+ minHeight: preview ? void 0 : EMPTY_CELL_COLUMN_MIN_H,
2574
+ display: "flex",
2575
+ flexDirection: "column",
2576
+ ...layoutSplitChromeBorder(C, preview),
2562
2577
  ...backgroundLayerStyle(cS),
2563
2578
  padding: paddingCssLayout(cS.padding),
2564
2579
  borderRadius: radiusCssLayout(cS.borderRadius),
@@ -2775,6 +2790,7 @@ function Cell({
2775
2790
  /* @__PURE__ */ jsx3("div", { style: { position: "absolute", left: -4, top: "50%", transform: "translateY(-50%)", width: 8, height: 8, borderRadius: "50%", background: C.accent } }),
2776
2791
  /* @__PURE__ */ jsx3("div", { style: { position: "absolute", right: -4, top: "50%", transform: "translateY(-50%)", width: 8, height: 8, borderRadius: "50%", background: C.accent } })
2777
2792
  ] }) : null;
2793
+ const cellEmpty = !preview && blocks.length === 0;
2778
2794
  return /* @__PURE__ */ jsxs2(
2779
2795
  "div",
2780
2796
  {
@@ -2784,17 +2800,43 @@ function Cell({
2784
2800
  onDragLeave: handleDragLeave,
2785
2801
  onDrop: handleDrop,
2786
2802
  style: {
2787
- flex: 1,
2788
- minHeight: preview ? void 0 : 44,
2803
+ flex: cellEmpty ? 1 : void 0,
2804
+ display: "flex",
2805
+ flexDirection: "column",
2806
+ minHeight: preview ? void 0 : cellEmpty ? 0 : 44,
2789
2807
  borderRadius: 4,
2790
- ...preview ? {} : insertAt !== null ? { border: `1px dotted ${C.accent}`, background: `${C.accent}0a` } : editChromeBorder(C, false),
2791
- background: insertAt !== null ? `${C.accent}0a` : "transparent",
2808
+ ...preview ? {} : insertAt !== null && !cellEmpty ? { border: `1px dotted ${C.accent}`, background: `${C.accent}0a` } : {},
2809
+ background: insertAt !== null && !cellEmpty ? `${C.accent}0a` : "transparent",
2792
2810
  transition: "border-color .15s,background .15s",
2793
2811
  padding: preview ? 0 : 0,
2794
2812
  position: "relative"
2795
2813
  },
2796
2814
  children: [
2797
- !preview && blocks.length === 0 && /* @__PURE__ */ jsx3("div", { style: { padding: "10px 4px", textAlign: "center", color: insertAt !== null ? C.accent2 : C.muted, fontSize: 10, userSelect: "none", fontWeight: insertAt !== null ? 700 : 400, transition: "color .15s" }, children: insertAt !== null ? "Release to drop" : "Drop here" }),
2815
+ cellEmpty && /* @__PURE__ */ jsx3(
2816
+ "div",
2817
+ {
2818
+ style: {
2819
+ flex: 1,
2820
+ display: "flex",
2821
+ alignItems: "center",
2822
+ justifyContent: "center",
2823
+ margin: 4,
2824
+ borderRadius: 8,
2825
+ boxSizing: "border-box",
2826
+ textAlign: "center",
2827
+ userSelect: "none",
2828
+ transition: "color .15s, border-color .15s, background .15s, box-shadow .15s",
2829
+ border: insertAt !== null ? `2px dashed ${C.accent}` : `2px dashed ${C.border}`,
2830
+ background: insertAt !== null ? `${C.accent}14` : `${C.canvas}`,
2831
+ boxShadow: insertAt !== null ? `inset 0 0 0 1px ${C.accent}22` : `inset 0 0 0 1px ${C.border}99`,
2832
+ color: insertAt !== null ? C.accent2 : C.muted,
2833
+ fontSize: 11,
2834
+ fontWeight: insertAt !== null ? 700 : 500,
2835
+ letterSpacing: "0.02em"
2836
+ },
2837
+ children: insertAt !== null ? "Release to drop" : "Drop here"
2838
+ }
2839
+ ),
2798
2840
  /* @__PURE__ */ jsx3(Line, { idx: 0 }),
2799
2841
  blocks.map((cb, ci) => /* @__PURE__ */ jsx3(Fragment, { children: /* @__PURE__ */ jsx3(
2800
2842
  BlockItem,
@@ -2845,7 +2887,6 @@ function LayoutRow({
2845
2887
  transition: "outline .12s",
2846
2888
  padding: row.padding,
2847
2889
  position: "relative",
2848
- ...editChromeBorder(C, preview),
2849
2890
  ...backgroundLayerStyle({
2850
2891
  bgColor: row.bgColor,
2851
2892
  bgImage: row.bgImage,
@@ -2884,8 +2925,9 @@ function LayoutRow({
2884
2925
  id: editorId ? `${editorId}-col-${row.id}-${ci}` : void 0,
2885
2926
  style: {
2886
2927
  flex: row.ratios[ci] ?? 1,
2887
- minHeight: 50,
2888
- ...editChromeBorder(C, preview),
2928
+ minHeight: preview ? 50 : EMPTY_CELL_COLUMN_MIN_H,
2929
+ display: "flex",
2930
+ flexDirection: "column",
2889
2931
  ...backgroundLayerStyle(cS),
2890
2932
  padding: paddingCssLayout(cS.padding),
2891
2933
  borderRadius: radiusCssLayout(cS.borderRadius),
@@ -3100,13 +3142,13 @@ var FONTS = [
3100
3142
  "Trebuchet MS,sans-serif",
3101
3143
  "Impact,sans-serif"
3102
3144
  ];
3103
- function paddingToCss(pad, fallback) {
3104
- if (typeof pad === "number" && Number.isFinite(pad)) return `${Math.max(0, pad)}px`;
3105
- if (pad && typeof pad === "object" && !Array.isArray(pad)) {
3106
- const t = typeof pad.top === "number" && Number.isFinite(pad.top) ? pad.top : fallback;
3107
- const r = typeof pad.right === "number" && Number.isFinite(pad.right) ? pad.right : t;
3108
- const b = typeof pad.bottom === "number" && Number.isFinite(pad.bottom) ? pad.bottom : t;
3109
- const l = typeof pad.left === "number" && Number.isFinite(pad.left) ? pad.left : r;
3145
+ function paddingToCss(pad3, fallback) {
3146
+ if (typeof pad3 === "number" && Number.isFinite(pad3)) return `${Math.max(0, pad3)}px`;
3147
+ if (pad3 && typeof pad3 === "object" && !Array.isArray(pad3)) {
3148
+ const t = typeof pad3.top === "number" && Number.isFinite(pad3.top) ? pad3.top : fallback;
3149
+ const r = typeof pad3.right === "number" && Number.isFinite(pad3.right) ? pad3.right : t;
3150
+ const b = typeof pad3.bottom === "number" && Number.isFinite(pad3.bottom) ? pad3.bottom : t;
3151
+ const l = typeof pad3.left === "number" && Number.isFinite(pad3.left) ? pad3.left : r;
3110
3152
  return `${t}px ${r}px ${b}px ${l}px`;
3111
3153
  }
3112
3154
  return `${fallback}px`;
@@ -4902,7 +4944,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4902
4944
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Letter spacing", value: p.letterSpacing ?? 0, onChange: (n) => set("letterSpacing", n), min: 0, max: 30, step: 0.5, C }),
4903
4945
  /* @__PURE__ */ jsx6(PR, { label: "Line height", C, children: /* @__PURE__ */ jsx6("input", { type: "number", style: useIS(C).IS, min: 0.8, max: 4, step: 0.05, value: p.lineHeight ?? 1.2, onChange: (e) => set("lineHeight", +e.target.value) }) }),
4904
4946
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
4905
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
4947
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4906
4948
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
4907
4949
  ] });
4908
4950
  case "text":
@@ -4928,7 +4970,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4928
4970
  /* @__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) }) }),
4929
4971
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Letter spacing", value: p.letterSpacing ?? 0, onChange: (n) => set("letterSpacing", n), min: 0, max: 30, step: 0.5, C }),
4930
4972
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
4931
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
4973
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4932
4974
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
4933
4975
  ] });
4934
4976
  case "html":
@@ -4953,7 +4995,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4953
4995
  }
4954
4996
  ) }, block.id),
4955
4997
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
4956
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
4998
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4957
4999
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
4958
5000
  ] });
4959
5001
  case "image":
@@ -4976,7 +5018,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4976
5018
  /* @__PURE__ */ jsx6(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right"], C }),
4977
5019
  /* @__PURE__ */ jsx6(BorderRadiusEditor, { value: p.borderRadius, onChange: (br) => set("borderRadius", br), C }),
4978
5020
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
4979
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5021
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4980
5022
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
4981
5023
  ] });
4982
5024
  case "button":
@@ -4996,7 +5038,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4996
5038
  Sel("fontWeight", "Weight", ["400", "500", "600", "700", "800"]),
4997
5039
  Tog("fullWidth", "Full Width"),
4998
5040
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "backdrop", p, set, C, onUpload, syncKey: block.id }),
4999
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5041
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5000
5042
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5001
5043
  ] });
5002
5044
  case "link":
@@ -5011,7 +5053,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5011
5053
  Sel("fontFamily", "Font", FONTS2),
5012
5054
  Tog("underline", "Underline"),
5013
5055
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
5014
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5056
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5015
5057
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5016
5058
  ] });
5017
5059
  case "divider":
@@ -5020,14 +5062,14 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5020
5062
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Thickness", value: p.thickness ?? 1, onChange: (n) => set("thickness", n), min: 1, max: 20, step: 1, C }),
5021
5063
  /* @__PURE__ */ jsx6(DividerStyleButtons, { value: p.style, onChange: (s) => set("style", s), C }),
5022
5064
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
5023
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5065
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5024
5066
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5025
5067
  ] });
5026
5068
  case "spacer":
5027
5069
  return /* @__PURE__ */ jsxs4(Fragment4, { children: [
5028
5070
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Height", value: p.height ?? 24, onChange: (n) => set("height", n), min: 4, max: 300, step: 1, C }),
5029
5071
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
5030
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5072
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5031
5073
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5032
5074
  ] });
5033
5075
  case "social":
@@ -5076,7 +5118,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5076
5118
  Sel("shape", "Shape", ["circle", "square"]),
5077
5119
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Gap", value: p.gap ?? 6, onChange: (n) => set("gap", n), min: 0, max: 40, step: 1, C }),
5078
5120
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
5079
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5121
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5080
5122
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5081
5123
  ] });
5082
5124
  case "video":
@@ -5136,7 +5178,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5136
5178
  ] }),
5137
5179
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Height", value: p.height ?? 280, onChange: (n) => set("height", n), min: 80, max: 720, step: 1, C }),
5138
5180
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
5139
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5181
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5140
5182
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5141
5183
  ] });
5142
5184
  case "menu":
@@ -5162,7 +5204,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5162
5204
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Font size", value: p.fontSize ?? 14, onChange: (n) => set("fontSize", n), min: 8, max: 48, step: 1, C }),
5163
5205
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Item gap", value: p.gap ?? 20, onChange: (n) => set("gap", n), min: 0, max: 80, step: 1, C }),
5164
5206
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
5165
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5207
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5166
5208
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5167
5209
  ] });
5168
5210
  case "timer": {
@@ -5202,7 +5244,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5202
5244
  /* @__PURE__ */ jsx6(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right"], C }),
5203
5245
  Tog("showLabels", "Show Labels"),
5204
5246
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "backdrop", p, set, C, onUpload, syncKey: block.id }),
5205
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5247
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5206
5248
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5207
5249
  ] });
5208
5250
  }
@@ -5299,8 +5341,8 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5299
5341
  Tog("striped", "Striped rows"),
5300
5342
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Font size", value: p.fontSize ?? 13, onChange: (n) => set("fontSize", n), min: 10, max: 24, step: 1, C }),
5301
5343
  /* @__PURE__ */ jsx6(BlockSurfaceBgInspector, { variant: "surface", p, set, C, onUpload, syncKey: block.id }),
5302
- /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad) => set("padding", pad), C }),
5303
- /* @__PURE__ */ jsx6(PaddingEditor, { label: "Cell padding", value: p.cellPadding, onChange: (pad) => set("cellPadding", pad), C }),
5344
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5345
+ /* @__PURE__ */ jsx6(PaddingEditor, { label: "Cell padding", value: p.cellPadding, onChange: (pad3) => set("cellPadding", pad3), C }),
5304
5346
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5305
5347
  ] });
5306
5348
  case "layout":
@@ -5693,7 +5735,7 @@ function LayoutRowEditor({
5693
5735
  }
5694
5736
  ) })
5695
5737
  ] }) : null,
5696
- /* @__PURE__ */ jsx6(PaddingEditor, { label: "Column padding", value: (row.columnStyles || {})[selCol]?.padding, onChange: (pad) => updCol(selCol, { padding: pad }), C }),
5738
+ /* @__PURE__ */ jsx6(PaddingEditor, { label: "Column padding", value: (row.columnStyles || {})[selCol]?.padding, onChange: (pad3) => updCol(selCol, { padding: pad3 }), C }),
5697
5739
  /* @__PURE__ */ jsx6(BorderRadiusEditor, { label: "Column radius", value: (row.columnStyles || {})[selCol]?.borderRadius, onChange: (br) => updCol(selCol, { borderRadius: br }), C })
5698
5740
  ] })
5699
5741
  ] })
@@ -5976,8 +6018,8 @@ function MobilePhoneScaleSlot({ variantKey, children }) {
5976
6018
  const pw = phone.offsetWidth;
5977
6019
  const ph = phone.offsetHeight;
5978
6020
  if (cw < 12 || ch < 12 || pw < 12 || ph < 12) return;
5979
- const pad = 20;
5980
- const raw = Math.min(1, (cw - pad) / pw, (ch - pad) / ph);
6021
+ const pad3 = 20;
6022
+ const raw = Math.min(1, (cw - pad3) / pw, (ch - pad3) / ph);
5981
6023
  const s = Number.isFinite(raw) ? Math.max(0.2, raw) : 1;
5982
6024
  setFit({ s, w: pw * s, h: ph * s });
5983
6025
  };
@@ -6847,6 +6889,43 @@ var ReactEmailEditor = forwardRef(
6847
6889
  }
6848
6890
  }
6849
6891
  };
6892
+ const insertBlockFromLibrary = (contentType) => {
6893
+ if (!rows.length) return;
6894
+ if (selContentMeta?.inner) {
6895
+ const { rowId, cellIdx, contentIdx, inner } = selContentMeta;
6896
+ dropContent(rowId, cellIdx, {
6897
+ kind: "new",
6898
+ contentType,
6899
+ insertAt: null,
6900
+ nested: { parentBlockIdx: contentIdx, innerCellIdx: inner.cellIdx }
6901
+ });
6902
+ return;
6903
+ }
6904
+ if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0) {
6905
+ const r = rows.find((x) => x.id === selContentMeta.rowId);
6906
+ if (r && selContentMeta.cellIdx < r.cells.length) {
6907
+ dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
6908
+ kind: "new",
6909
+ contentType,
6910
+ insertAt: null
6911
+ });
6912
+ return;
6913
+ }
6914
+ }
6915
+ if (selectedRowId) {
6916
+ const targetRow = rows.find((r) => r.id === selectedRowId);
6917
+ if (targetRow) {
6918
+ dropContent(targetRow.id, 0, {
6919
+ kind: "new",
6920
+ contentType,
6921
+ insertAt: null
6922
+ });
6923
+ return;
6924
+ }
6925
+ }
6926
+ const main = rows[0];
6927
+ dropContent(main.id, 0, { kind: "new", contentType, insertAt: null });
6928
+ };
6850
6929
  const deleteContent = (rowId, cellIdx, ci, inner = null) => {
6851
6930
  if (!inner) {
6852
6931
  mutate((prev) => prev.map((r) => {
@@ -7443,6 +7522,8 @@ var ReactEmailEditor = forwardRef(
7443
7522
  if (data.layoutPresetKey) {
7444
7523
  const preset = LAYOUT_PRESETS.find((p) => p.key === data.layoutPresetKey);
7445
7524
  if (preset) mutate((prev) => [...prev, makeLayoutRow(preset)]);
7525
+ } else if (data.contentType && typeof data.contentType === "string") {
7526
+ insertBlockFromLibrary(data.contentType);
7446
7527
  }
7447
7528
  } catch {
7448
7529
  }
@@ -7483,6 +7564,21 @@ var ReactEmailEditor = forwardRef(
7483
7564
  }
7484
7565
  return base;
7485
7566
  })(),
7567
+ onDragOver: (e) => {
7568
+ e.preventDefault();
7569
+ e.stopPropagation();
7570
+ },
7571
+ onDrop: (e) => {
7572
+ e.preventDefault();
7573
+ e.stopPropagation();
7574
+ try {
7575
+ const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
7576
+ if (data.contentType && typeof data.contentType === "string") {
7577
+ insertBlockFromLibrary(data.contentType);
7578
+ }
7579
+ } catch {
7580
+ }
7581
+ },
7486
7582
  children: [
7487
7583
  rows.length === 0 && /* @__PURE__ */ jsxs9("div", { id: eid("canvas-empty"), style: { padding: "56px 20px", textAlign: "center", color: C.muted, border: `2px dashed ${C.border}`, borderRadius: 7 }, children: [
7488
7584
  /* @__PURE__ */ jsx11("div", { style: { fontSize: 30, marginBottom: 10 }, children: "\u2709" }),
@@ -7694,43 +7790,7 @@ var ReactEmailEditor = forwardRef(
7694
7790
  onDragStart: (e) => {
7695
7791
  e.dataTransfer.setData("application/json", JSON.stringify({ contentType: bt.type }));
7696
7792
  },
7697
- onClick: () => {
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 });
7733
- },
7793
+ onClick: () => insertBlockFromLibrary(bt.type),
7734
7794
  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",
7735
7795
  style: {
7736
7796
  display: "flex",
@@ -8101,11 +8161,79 @@ var ReactEmailEditor = forwardRef(
8101
8161
  );
8102
8162
  }
8103
8163
  );
8164
+
8165
+ // src/lib/htmlToEmailDesign.ts
8166
+ var pad = (n) => ({ top: n, right: n, bottom: n, left: n });
8167
+ function extractHtmlForDesign(html) {
8168
+ const t = String(html ?? "").trim();
8169
+ if (!t) return "";
8170
+ const head = t.slice(0, 800).toLowerCase();
8171
+ if (!head.includes("<html") && !head.includes("<!doctype")) {
8172
+ return normalizeRichHtmlForStorage(t);
8173
+ }
8174
+ try {
8175
+ const doc = new DOMParser().parseFromString(t, "text/html");
8176
+ const body = doc.body;
8177
+ if (!body) return normalizeRichHtmlForStorage(t);
8178
+ return normalizeRichHtmlForStorage(body.innerHTML);
8179
+ } catch {
8180
+ return normalizeRichHtmlForStorage(t);
8181
+ }
8182
+ }
8183
+ function htmlToEmailDesignTemplate(html) {
8184
+ const inner = extractHtmlForDesign(html);
8185
+ if (!inner) return null;
8186
+ const doc = {
8187
+ type: "email_document",
8188
+ settings: {
8189
+ width: 600,
8190
+ backgroundColor: "#f1f5f9",
8191
+ contentBackgroundColor: "#ffffff",
8192
+ fontFamily: "Arial, Helvetica, sans-serif",
8193
+ lineHeightBase: 1.6,
8194
+ color: "#111827",
8195
+ responsive: true,
8196
+ rtl: false
8197
+ },
8198
+ rows: [
8199
+ {
8200
+ id: "row_html_import",
8201
+ type: "row",
8202
+ layout: { columns: 1, gap: 0, stackOnMobile: true, align: "center" },
8203
+ styles: {
8204
+ backgroundColor: "#ffffff",
8205
+ backgroundRepeat: "no-repeat",
8206
+ backgroundSize: "cover",
8207
+ padding: pad(24),
8208
+ textAlign: "left"
8209
+ },
8210
+ columns: [
8211
+ {
8212
+ id: "col_html_import",
8213
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
8214
+ styles: { padding: pad(0), backgroundColor: "" },
8215
+ blocks: [
8216
+ {
8217
+ id: "block_html_import",
8218
+ type: "html",
8219
+ content: { html: inner },
8220
+ styles: {}
8221
+ }
8222
+ ]
8223
+ }
8224
+ ]
8225
+ }
8226
+ ]
8227
+ };
8228
+ return doc;
8229
+ }
8104
8230
  export {
8105
8231
  PreviewModal as EmailPreviewModal,
8106
8232
  ReactEmailEditor,
8107
8233
  base64ToUtf8,
8108
8234
  DEVICES as emailPreviewDevices,
8235
+ extractHtmlForDesign,
8236
+ htmlToEmailDesignTemplate,
8109
8237
  jsonToHtml,
8110
8238
  utf8ToBase64
8111
8239
  };