react-email-studio 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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 DROP_HERE_HEIGHT_PX = 45;
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 : DROP_HERE_HEIGHT_PX,
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,44 @@ function Cell({
2784
2800
  onDragLeave: handleDragLeave,
2785
2801
  onDrop: handleDrop,
2786
2802
  style: {
2787
- flex: 1,
2788
- minHeight: preview ? void 0 : 44,
2803
+ display: "flex",
2804
+ flexDirection: "column",
2805
+ minHeight: preview ? void 0 : cellEmpty ? DROP_HERE_HEIGHT_PX : 44,
2789
2806
  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",
2807
+ ...preview ? {} : insertAt !== null && !cellEmpty ? { border: `1px dotted ${C.accent}`, background: `${C.accent}0a` } : {},
2808
+ background: insertAt !== null && !cellEmpty ? `${C.accent}0a` : "transparent",
2792
2809
  transition: "border-color .15s,background .15s",
2793
2810
  padding: preview ? 0 : 0,
2794
2811
  position: "relative"
2795
2812
  },
2796
2813
  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" }),
2814
+ cellEmpty && /* @__PURE__ */ jsx3(
2815
+ "div",
2816
+ {
2817
+ style: {
2818
+ height: DROP_HERE_HEIGHT_PX,
2819
+ minHeight: DROP_HERE_HEIGHT_PX,
2820
+ maxHeight: DROP_HERE_HEIGHT_PX,
2821
+ display: "flex",
2822
+ alignItems: "center",
2823
+ justifyContent: "center",
2824
+ margin: 0,
2825
+ borderRadius: 6,
2826
+ boxSizing: "border-box",
2827
+ textAlign: "center",
2828
+ userSelect: "none",
2829
+ transition: "color .15s, border-color .15s, background .15s, box-shadow .15s",
2830
+ border: insertAt !== null ? `2px dashed ${C.accent}` : `2px dashed ${C.border}`,
2831
+ background: insertAt !== null ? `${C.accent}14` : `${C.canvas}`,
2832
+ boxShadow: insertAt !== null ? `inset 0 0 0 1px ${C.accent}22` : `inset 0 0 0 1px ${C.border}99`,
2833
+ color: insertAt !== null ? C.accent2 : C.muted,
2834
+ fontSize: 10,
2835
+ fontWeight: insertAt !== null ? 700 : 500,
2836
+ letterSpacing: "0.02em"
2837
+ },
2838
+ children: insertAt !== null ? "Release to drop" : "Drop here"
2839
+ }
2840
+ ),
2798
2841
  /* @__PURE__ */ jsx3(Line, { idx: 0 }),
2799
2842
  blocks.map((cb, ci) => /* @__PURE__ */ jsx3(Fragment, { children: /* @__PURE__ */ jsx3(
2800
2843
  BlockItem,
@@ -2845,7 +2888,6 @@ function LayoutRow({
2845
2888
  transition: "outline .12s",
2846
2889
  padding: row.padding,
2847
2890
  position: "relative",
2848
- ...editChromeBorder(C, preview),
2849
2891
  ...backgroundLayerStyle({
2850
2892
  bgColor: row.bgColor,
2851
2893
  bgImage: row.bgImage,
@@ -2884,8 +2926,9 @@ function LayoutRow({
2884
2926
  id: editorId ? `${editorId}-col-${row.id}-${ci}` : void 0,
2885
2927
  style: {
2886
2928
  flex: row.ratios[ci] ?? 1,
2887
- minHeight: 50,
2888
- ...editChromeBorder(C, preview),
2929
+ minHeight: preview ? 50 : DROP_HERE_HEIGHT_PX,
2930
+ display: "flex",
2931
+ flexDirection: "column",
2889
2932
  ...backgroundLayerStyle(cS),
2890
2933
  padding: paddingCssLayout(cS.padding),
2891
2934
  borderRadius: radiusCssLayout(cS.borderRadius),
@@ -3100,13 +3143,13 @@ var FONTS = [
3100
3143
  "Trebuchet MS,sans-serif",
3101
3144
  "Impact,sans-serif"
3102
3145
  ];
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;
3146
+ function paddingToCss(pad3, fallback) {
3147
+ if (typeof pad3 === "number" && Number.isFinite(pad3)) return `${Math.max(0, pad3)}px`;
3148
+ if (pad3 && typeof pad3 === "object" && !Array.isArray(pad3)) {
3149
+ const t = typeof pad3.top === "number" && Number.isFinite(pad3.top) ? pad3.top : fallback;
3150
+ const r = typeof pad3.right === "number" && Number.isFinite(pad3.right) ? pad3.right : t;
3151
+ const b = typeof pad3.bottom === "number" && Number.isFinite(pad3.bottom) ? pad3.bottom : t;
3152
+ const l = typeof pad3.left === "number" && Number.isFinite(pad3.left) ? pad3.left : r;
3110
3153
  return `${t}px ${r}px ${b}px ${l}px`;
3111
3154
  }
3112
3155
  return `${fallback}px`;
@@ -4902,7 +4945,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4902
4945
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Letter spacing", value: p.letterSpacing ?? 0, onChange: (n) => set("letterSpacing", n), min: 0, max: 30, step: 0.5, C }),
4903
4946
  /* @__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
4947
  /* @__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 }),
4948
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4906
4949
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
4907
4950
  ] });
4908
4951
  case "text":
@@ -4928,7 +4971,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4928
4971
  /* @__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
4972
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Letter spacing", value: p.letterSpacing ?? 0, onChange: (n) => set("letterSpacing", n), min: 0, max: 30, step: 0.5, C }),
4930
4973
  /* @__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 }),
4974
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4932
4975
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
4933
4976
  ] });
4934
4977
  case "html":
@@ -4953,7 +4996,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4953
4996
  }
4954
4997
  ) }, block.id),
4955
4998
  /* @__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 }),
4999
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4957
5000
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
4958
5001
  ] });
4959
5002
  case "image":
@@ -4976,7 +5019,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4976
5019
  /* @__PURE__ */ jsx6(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right"], C }),
4977
5020
  /* @__PURE__ */ jsx6(BorderRadiusEditor, { value: p.borderRadius, onChange: (br) => set("borderRadius", br), C }),
4978
5021
  /* @__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 }),
5022
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
4980
5023
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
4981
5024
  ] });
4982
5025
  case "button":
@@ -4996,7 +5039,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
4996
5039
  Sel("fontWeight", "Weight", ["400", "500", "600", "700", "800"]),
4997
5040
  Tog("fullWidth", "Full Width"),
4998
5041
  /* @__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 }),
5042
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5000
5043
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5001
5044
  ] });
5002
5045
  case "link":
@@ -5011,7 +5054,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5011
5054
  Sel("fontFamily", "Font", FONTS2),
5012
5055
  Tog("underline", "Underline"),
5013
5056
  /* @__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 }),
5057
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5015
5058
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5016
5059
  ] });
5017
5060
  case "divider":
@@ -5020,14 +5063,14 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5020
5063
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Thickness", value: p.thickness ?? 1, onChange: (n) => set("thickness", n), min: 1, max: 20, step: 1, C }),
5021
5064
  /* @__PURE__ */ jsx6(DividerStyleButtons, { value: p.style, onChange: (s) => set("style", s), C }),
5022
5065
  /* @__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 }),
5066
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5024
5067
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5025
5068
  ] });
5026
5069
  case "spacer":
5027
5070
  return /* @__PURE__ */ jsxs4(Fragment4, { children: [
5028
5071
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Height", value: p.height ?? 24, onChange: (n) => set("height", n), min: 4, max: 300, step: 1, C }),
5029
5072
  /* @__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 }),
5073
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5031
5074
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5032
5075
  ] });
5033
5076
  case "social":
@@ -5076,7 +5119,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5076
5119
  Sel("shape", "Shape", ["circle", "square"]),
5077
5120
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Gap", value: p.gap ?? 6, onChange: (n) => set("gap", n), min: 0, max: 40, step: 1, C }),
5078
5121
  /* @__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 }),
5122
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5080
5123
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5081
5124
  ] });
5082
5125
  case "video":
@@ -5136,7 +5179,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5136
5179
  ] }),
5137
5180
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Height", value: p.height ?? 280, onChange: (n) => set("height", n), min: 80, max: 720, step: 1, C }),
5138
5181
  /* @__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 }),
5182
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5140
5183
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5141
5184
  ] });
5142
5185
  case "menu":
@@ -5162,7 +5205,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5162
5205
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Font size", value: p.fontSize ?? 14, onChange: (n) => set("fontSize", n), min: 8, max: 48, step: 1, C }),
5163
5206
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Item gap", value: p.gap ?? 20, onChange: (n) => set("gap", n), min: 0, max: 80, step: 1, C }),
5164
5207
  /* @__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 }),
5208
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5166
5209
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5167
5210
  ] });
5168
5211
  case "timer": {
@@ -5202,7 +5245,7 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5202
5245
  /* @__PURE__ */ jsx6(AlignButtons, { value: p.align, onChange: (v) => set("align", v), options: ["left", "center", "right"], C }),
5203
5246
  Tog("showLabels", "Show Labels"),
5204
5247
  /* @__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 }),
5248
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5206
5249
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5207
5250
  ] });
5208
5251
  }
@@ -5299,8 +5342,8 @@ function ContentBlockEditor({ block, onChange, onClose, onUpload, C }) {
5299
5342
  Tog("striped", "Striped rows"),
5300
5343
  /* @__PURE__ */ jsx6(NumRangePx, { label: "Font size", value: p.fontSize ?? 13, onChange: (n) => set("fontSize", n), min: 10, max: 24, step: 1, C }),
5301
5344
  /* @__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 }),
5345
+ /* @__PURE__ */ jsx6(PaddingEditor, { value: p.padding, onChange: (pad3) => set("padding", pad3), C }),
5346
+ /* @__PURE__ */ jsx6(PaddingEditor, { label: "Cell padding", value: p.cellPadding, onChange: (pad3) => set("cellPadding", pad3), C }),
5304
5347
  /* @__PURE__ */ jsx6(MarginEditor, { value: p.margin, onChange: (m) => set("margin", m), C })
5305
5348
  ] });
5306
5349
  case "layout":
@@ -5693,7 +5736,7 @@ function LayoutRowEditor({
5693
5736
  }
5694
5737
  ) })
5695
5738
  ] }) : null,
5696
- /* @__PURE__ */ jsx6(PaddingEditor, { label: "Column padding", value: (row.columnStyles || {})[selCol]?.padding, onChange: (pad) => updCol(selCol, { padding: pad }), C }),
5739
+ /* @__PURE__ */ jsx6(PaddingEditor, { label: "Column padding", value: (row.columnStyles || {})[selCol]?.padding, onChange: (pad3) => updCol(selCol, { padding: pad3 }), C }),
5697
5740
  /* @__PURE__ */ jsx6(BorderRadiusEditor, { label: "Column radius", value: (row.columnStyles || {})[selCol]?.borderRadius, onChange: (br) => updCol(selCol, { borderRadius: br }), C })
5698
5741
  ] })
5699
5742
  ] })
@@ -5976,8 +6019,8 @@ function MobilePhoneScaleSlot({ variantKey, children }) {
5976
6019
  const pw = phone.offsetWidth;
5977
6020
  const ph = phone.offsetHeight;
5978
6021
  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);
6022
+ const pad3 = 20;
6023
+ const raw = Math.min(1, (cw - pad3) / pw, (ch - pad3) / ph);
5981
6024
  const s = Number.isFinite(raw) ? Math.max(0.2, raw) : 1;
5982
6025
  setFit({ s, w: pw * s, h: ph * s });
5983
6026
  };
@@ -6847,6 +6890,43 @@ var ReactEmailEditor = forwardRef(
6847
6890
  }
6848
6891
  }
6849
6892
  };
6893
+ const insertBlockFromLibrary = (contentType) => {
6894
+ if (!rows.length) return;
6895
+ if (selContentMeta?.inner) {
6896
+ const { rowId, cellIdx, contentIdx, inner } = selContentMeta;
6897
+ dropContent(rowId, cellIdx, {
6898
+ kind: "new",
6899
+ contentType,
6900
+ insertAt: null,
6901
+ nested: { parentBlockIdx: contentIdx, innerCellIdx: inner.cellIdx }
6902
+ });
6903
+ return;
6904
+ }
6905
+ if (selContentMeta?.rowId && typeof selContentMeta.cellIdx === "number" && selContentMeta.cellIdx >= 0) {
6906
+ const r = rows.find((x) => x.id === selContentMeta.rowId);
6907
+ if (r && selContentMeta.cellIdx < r.cells.length) {
6908
+ dropContent(selContentMeta.rowId, selContentMeta.cellIdx, {
6909
+ kind: "new",
6910
+ contentType,
6911
+ insertAt: null
6912
+ });
6913
+ return;
6914
+ }
6915
+ }
6916
+ if (selectedRowId) {
6917
+ const targetRow = rows.find((r) => r.id === selectedRowId);
6918
+ if (targetRow) {
6919
+ dropContent(targetRow.id, 0, {
6920
+ kind: "new",
6921
+ contentType,
6922
+ insertAt: null
6923
+ });
6924
+ return;
6925
+ }
6926
+ }
6927
+ const main = rows[0];
6928
+ dropContent(main.id, 0, { kind: "new", contentType, insertAt: null });
6929
+ };
6850
6930
  const deleteContent = (rowId, cellIdx, ci, inner = null) => {
6851
6931
  if (!inner) {
6852
6932
  mutate((prev) => prev.map((r) => {
@@ -7443,6 +7523,8 @@ var ReactEmailEditor = forwardRef(
7443
7523
  if (data.layoutPresetKey) {
7444
7524
  const preset = LAYOUT_PRESETS.find((p) => p.key === data.layoutPresetKey);
7445
7525
  if (preset) mutate((prev) => [...prev, makeLayoutRow(preset)]);
7526
+ } else if (data.contentType && typeof data.contentType === "string") {
7527
+ insertBlockFromLibrary(data.contentType);
7446
7528
  }
7447
7529
  } catch {
7448
7530
  }
@@ -7483,6 +7565,21 @@ var ReactEmailEditor = forwardRef(
7483
7565
  }
7484
7566
  return base;
7485
7567
  })(),
7568
+ onDragOver: (e) => {
7569
+ e.preventDefault();
7570
+ e.stopPropagation();
7571
+ },
7572
+ onDrop: (e) => {
7573
+ e.preventDefault();
7574
+ e.stopPropagation();
7575
+ try {
7576
+ const data = JSON.parse(e.dataTransfer.getData("application/json") || "{}");
7577
+ if (data.contentType && typeof data.contentType === "string") {
7578
+ insertBlockFromLibrary(data.contentType);
7579
+ }
7580
+ } catch {
7581
+ }
7582
+ },
7486
7583
  children: [
7487
7584
  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
7585
  /* @__PURE__ */ jsx11("div", { style: { fontSize: 30, marginBottom: 10 }, children: "\u2709" }),
@@ -7694,43 +7791,7 @@ var ReactEmailEditor = forwardRef(
7694
7791
  onDragStart: (e) => {
7695
7792
  e.dataTransfer.setData("application/json", JSON.stringify({ contentType: bt.type }));
7696
7793
  },
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
- },
7794
+ onClick: () => insertBlockFromLibrary(bt.type),
7734
7795
  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
7796
  style: {
7736
7797
  display: "flex",
@@ -8101,11 +8162,79 @@ var ReactEmailEditor = forwardRef(
8101
8162
  );
8102
8163
  }
8103
8164
  );
8165
+
8166
+ // src/lib/htmlToEmailDesign.ts
8167
+ var pad = (n) => ({ top: n, right: n, bottom: n, left: n });
8168
+ function extractHtmlForDesign(html) {
8169
+ const t = String(html ?? "").trim();
8170
+ if (!t) return "";
8171
+ const head = t.slice(0, 800).toLowerCase();
8172
+ if (!head.includes("<html") && !head.includes("<!doctype")) {
8173
+ return normalizeRichHtmlForStorage(t);
8174
+ }
8175
+ try {
8176
+ const doc = new DOMParser().parseFromString(t, "text/html");
8177
+ const body = doc.body;
8178
+ if (!body) return normalizeRichHtmlForStorage(t);
8179
+ return normalizeRichHtmlForStorage(body.innerHTML);
8180
+ } catch {
8181
+ return normalizeRichHtmlForStorage(t);
8182
+ }
8183
+ }
8184
+ function htmlToEmailDesignTemplate(html) {
8185
+ const inner = extractHtmlForDesign(html);
8186
+ if (!inner) return null;
8187
+ const doc = {
8188
+ type: "email_document",
8189
+ settings: {
8190
+ width: 600,
8191
+ backgroundColor: "#f1f5f9",
8192
+ contentBackgroundColor: "#ffffff",
8193
+ fontFamily: "Arial, Helvetica, sans-serif",
8194
+ lineHeightBase: 1.6,
8195
+ color: "#111827",
8196
+ responsive: true,
8197
+ rtl: false
8198
+ },
8199
+ rows: [
8200
+ {
8201
+ id: "row_html_import",
8202
+ type: "row",
8203
+ layout: { columns: 1, gap: 0, stackOnMobile: true, align: "center" },
8204
+ styles: {
8205
+ backgroundColor: "#ffffff",
8206
+ backgroundRepeat: "no-repeat",
8207
+ backgroundSize: "cover",
8208
+ padding: pad(24),
8209
+ textAlign: "left"
8210
+ },
8211
+ columns: [
8212
+ {
8213
+ id: "col_html_import",
8214
+ layout: { width: "100%", verticalAlign: "top", mobileWidth: "100%" },
8215
+ styles: { padding: pad(0), backgroundColor: "" },
8216
+ blocks: [
8217
+ {
8218
+ id: "block_html_import",
8219
+ type: "html",
8220
+ content: { html: inner },
8221
+ styles: {}
8222
+ }
8223
+ ]
8224
+ }
8225
+ ]
8226
+ }
8227
+ ]
8228
+ };
8229
+ return doc;
8230
+ }
8104
8231
  export {
8105
8232
  PreviewModal as EmailPreviewModal,
8106
8233
  ReactEmailEditor,
8107
8234
  base64ToUtf8,
8108
8235
  DEVICES as emailPreviewDevices,
8236
+ extractHtmlForDesign,
8237
+ htmlToEmailDesignTemplate,
8109
8238
  jsonToHtml,
8110
8239
  utf8ToBase64
8111
8240
  };