@standhigher/react-email-editor 0.0.0-stage → 0.1.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.js ADDED
@@ -0,0 +1,4586 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+
22
+ // src/EmailEditor.tsx
23
+ import { forwardRef as forwardRef2 } from "react";
24
+
25
+ // src/shell/EmailEditorShell.tsx
26
+ import { forwardRef, useEffect as useEffect9, useState as useState11 } from "react";
27
+
28
+ // src/editor-dnd/EditorDnd.tsx
29
+ import { useMemo, useRef, useState } from "react";
30
+ import {
31
+ DndContext,
32
+ DragOverlay,
33
+ PointerSensor,
34
+ useSensor,
35
+ useSensors
36
+ } from "@dnd-kit/core";
37
+
38
+ // src/constants/editor.ts
39
+ var SUBJECT_MAX_LENGTH = 128;
40
+ var DRAG_ACTIVATION_DISTANCE = 6;
41
+ var CANVAS_DROP_ID = "canvas";
42
+ var CANVAS_DEVICE_WIDTH = {
43
+ desktop: 600,
44
+ mobile: 360
45
+ };
46
+ var CONTENT_WIDTH_MIN = 320;
47
+ var CONTENT_WIDTH_MAX = 900;
48
+ var CONTENT_WIDTH_STEP = 10;
49
+ function emailViewportWidth(device, contentWidth = CANVAS_DEVICE_WIDTH.desktop) {
50
+ if (device === "mobile") return CANVAS_DEVICE_WIDTH.mobile;
51
+ const width = Number.isFinite(contentWidth) ? contentWidth : CANVAS_DEVICE_WIDTH.desktop;
52
+ return Math.min(CONTENT_WIDTH_MAX, Math.max(CONTENT_WIDTH_MIN, width));
53
+ }
54
+
55
+ // src/components/BlockIcon.tsx
56
+ import { jsx } from "react/jsx-runtime";
57
+ function BlockIcon({ icon, className }) {
58
+ if (typeof icon === "string") {
59
+ return /* @__PURE__ */ jsx("span", { className, dangerouslySetInnerHTML: { __html: icon } });
60
+ }
61
+ return /* @__PURE__ */ jsx("span", { className, children: icon });
62
+ }
63
+
64
+ // src/constants/block-types.ts
65
+ var BLOCK_TYPES = {
66
+ heading: "heading",
67
+ text: "text",
68
+ button: "button",
69
+ image: "image",
70
+ divider: "divider",
71
+ spacer: "spacer",
72
+ html: "html"
73
+ };
74
+
75
+ // src/utils/html.ts
76
+ function esc(value) {
77
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
78
+ }
79
+
80
+ // src/constants/style-values.ts
81
+ var TEXT_ALIGNS = {
82
+ left: "left",
83
+ center: "center",
84
+ right: "right",
85
+ justify: "justify"
86
+ };
87
+ var HORIZONTAL_ALIGNS = {
88
+ left: "left",
89
+ center: "center",
90
+ right: "right"
91
+ };
92
+ var FONT_WEIGHTS = {
93
+ normal: "normal",
94
+ bold: "bold"
95
+ };
96
+ var BORDER_STYLES = {
97
+ none: "none",
98
+ solid: "solid",
99
+ dashed: "dashed",
100
+ dotted: "dotted"
101
+ };
102
+ var LINK_DECORATIONS = {
103
+ underline: "underline",
104
+ none: "none"
105
+ };
106
+ function isStyleValue(values, value) {
107
+ return typeof value === "string" && Object.values(values).includes(value);
108
+ }
109
+
110
+ // src/utils/css-values.ts
111
+ function cssLength(value, fallback = "0px", allowAuto = false) {
112
+ const input = String(value != null ? value : "").trim();
113
+ if (allowAuto && input === "auto") return input;
114
+ if (!/^(?:\d+(?:\.\d+)?|\.\d+)(?:px|em|rem|%)?$/.test(input)) return fallback;
115
+ return /[a-z%]$/i.test(input) ? input : `${input}px`;
116
+ }
117
+ function cssSpacing(value, fallback = "0px") {
118
+ const parts = String(value != null ? value : "").trim().split(/\s+/);
119
+ if (parts.length > 4 || parts.some((part) => !cssLength(part, ""))) return fallback;
120
+ return parts.map((part) => cssLength(part)).join(" ");
121
+ }
122
+ function cssCornerRadius(value, fallback = "0px") {
123
+ const parts = String(value != null ? value : "").trim().split(/\s+/).filter(Boolean);
124
+ if (parts.length !== 1 && parts.length !== 4 || parts.some((part) => !cssLength(part, ""))) return fallback;
125
+ return parts.map((part) => cssLength(part)).join(" ");
126
+ }
127
+ function cornerRadii(value) {
128
+ var _a, _b, _c, _d, _e;
129
+ const parts = cssCornerRadius(value).split(/\s+/);
130
+ const topLeft = (_a = parts[0]) != null ? _a : "0px";
131
+ if (parts.length === 1) return [topLeft, topLeft, topLeft, topLeft];
132
+ return [(_b = parts[0]) != null ? _b : topLeft, (_c = parts[1]) != null ? _c : topLeft, (_d = parts[2]) != null ? _d : topLeft, (_e = parts[3]) != null ? _e : topLeft];
133
+ }
134
+ function cssPx(value) {
135
+ const match = /^(\d+(?:\.\d+)?)px$/.exec(cssLength(value, ""));
136
+ if (!match) return void 0;
137
+ const px = String(Math.round(Number(match[1])));
138
+ return px === "0" ? void 0 : px;
139
+ }
140
+ function spacingSides(value) {
141
+ const [top = "0px", right = top, bottom = top, left = right] = cssSpacing(value).split(/\s+/);
142
+ return [top, right, bottom, left];
143
+ }
144
+ function cssColor(value, fallback = "transparent") {
145
+ const input = String(value != null ? value : "").trim();
146
+ return /^(?:#[\da-f]{3,8}|transparent|[a-z]+|rgba?\([\d.,%\s]+\)|hsla?\([\d.,%\s]+\))$/i.test(input) ? input : fallback;
147
+ }
148
+ function cssFont(value, fallback) {
149
+ const input = String(value != null ? value : "").trim();
150
+ return input && !/[;<>{}\\]/.test(input) ? input : fallback;
151
+ }
152
+ var EMAIL_EM_PX = 16;
153
+ function readCssLength(value, units = []) {
154
+ var _a, _b;
155
+ const input = value.trim();
156
+ const allowed = units.filter((unit2) => unit2 !== "auto");
157
+ const fallbackUnit = (_a = allowed[0]) != null ? _a : "";
158
+ if (input === "auto" && units.includes("auto")) return { amount: 0, unit: "auto" };
159
+ const match = input.match(/^(-?(?:\d+(?:\.\d+)?|\.\d+))(px|em|rem|%)?$/i);
160
+ if (!match) return { amount: 0, unit: fallbackUnit };
161
+ let amount = Number(match[1]);
162
+ let unit = ((_b = match[2]) != null ? _b : "").toLowerCase();
163
+ if (unit === "em" || unit === "rem") {
164
+ amount = Math.round(amount * EMAIL_EM_PX);
165
+ unit = "px";
166
+ }
167
+ if (!unit) unit = fallbackUnit;
168
+ if (allowed.length > 0 && !allowed.includes(unit)) unit = fallbackUnit;
169
+ return { amount: Number.isFinite(amount) ? amount : 0, unit };
170
+ }
171
+ function formatCssLength(amount, unit) {
172
+ if (unit === "auto") return "auto";
173
+ const text = Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
174
+ return unit ? `${text}${unit}` : text;
175
+ }
176
+ function cssLineHeight(value, fallback) {
177
+ const input = String(value != null ? value : "").trim();
178
+ return /^(?:\d+(?:\.\d+)?|\.\d+)$/.test(input) ? input : cssLength(input, fallback);
179
+ }
180
+ function cssBorderStyle(value) {
181
+ return isStyleValue(BORDER_STYLES, value) ? value : BORDER_STYLES.solid;
182
+ }
183
+
184
+ // src/document/constants.ts
185
+ var DOCUMENT_VERSION = 1;
186
+ function fontOption(label, value) {
187
+ return Object.freeze({ label, value });
188
+ }
189
+ var FONT_FAMILIES = Object.freeze([
190
+ fontOption("Arial", "Arial, Helvetica, sans-serif"),
191
+ fontOption("Helvetica", "Helvetica, Arial, sans-serif"),
192
+ fontOption("Verdana", "Verdana, Geneva, sans-serif"),
193
+ fontOption("Tahoma", "Tahoma, Geneva, sans-serif"),
194
+ fontOption("Trebuchet MS", "'Trebuchet MS', Helvetica, sans-serif"),
195
+ fontOption("Georgia", "Georgia, serif"),
196
+ fontOption("Times New Roman", "'Times New Roman', Times, serif"),
197
+ fontOption("Courier New", "'Courier New', Courier, monospace")
198
+ ]);
199
+ function copyFontFamilies() {
200
+ return FONT_FAMILIES.map((font) => ({ label: font.label, value: font.value }));
201
+ }
202
+ function fontFamilyLabel(value) {
203
+ var _a;
204
+ const match = FONT_FAMILIES.find((font) => font.value === value);
205
+ if (match) return match.label;
206
+ const name = (_a = value.split(",")[0]) == null ? void 0 : _a.replace(/['"]/g, "").trim();
207
+ return name || "Arial";
208
+ }
209
+ var DEFAULT_EMAIL_GLOBAL = {
210
+ textColor: "#000000",
211
+ textAlign: TEXT_ALIGNS.left,
212
+ backgroundColor: "#ffffff",
213
+ contentBackgroundColor: "#ffffff",
214
+ contentWidth: CANVAS_DEVICE_WIDTH.desktop,
215
+ contentAlign: HORIZONTAL_ALIGNS.center,
216
+ contentPadding: "0px",
217
+ outerPadding: "0px",
218
+ blockGap: "4px",
219
+ paragraphSpacing: "6px",
220
+ listSpacing: "4px",
221
+ listIndent: "22px",
222
+ borderWidth: "0px",
223
+ borderStyle: BORDER_STYLES.solid,
224
+ borderColor: "#000000",
225
+ borderRadius: "0px",
226
+ fontFamily: "Arial, Helvetica, sans-serif",
227
+ fontWeight: FONT_WEIGHTS.normal,
228
+ defaultFontSize: "16px",
229
+ defaultLineHeight: "1.5",
230
+ linkColor: "#005bd3",
231
+ linkDecoration: LINK_DECORATIONS.underline
232
+ };
233
+
234
+ // src/document/create-document.ts
235
+ function resolveEmailGlobal(global) {
236
+ var _a;
237
+ const source = global != null ? global : {};
238
+ const defaults = DEFAULT_EMAIL_GLOBAL;
239
+ const width = Number((_a = source.contentWidth) != null ? _a : defaults.contentWidth);
240
+ return {
241
+ textAlign: isStyleValue(TEXT_ALIGNS, source.textAlign) ? source.textAlign : TEXT_ALIGNS.left,
242
+ textColor: cssColor(source.textColor, defaults.textColor),
243
+ backgroundColor: cssColor(source.backgroundColor, defaults.backgroundColor),
244
+ contentBackgroundColor: cssColor(source.contentBackgroundColor, defaults.contentBackgroundColor),
245
+ contentWidth: Number.isFinite(width) ? Math.min(CONTENT_WIDTH_MAX, Math.max(CONTENT_WIDTH_MIN, Math.round(width))) : defaults.contentWidth,
246
+ contentAlign: isStyleValue(HORIZONTAL_ALIGNS, source.contentAlign) ? source.contentAlign : HORIZONTAL_ALIGNS.center,
247
+ contentPadding: cssSpacing(source.contentPadding, defaults.contentPadding),
248
+ outerPadding: cssSpacing(source.outerPadding, defaults.outerPadding),
249
+ blockGap: cssLength(source.blockGap, defaults.blockGap),
250
+ fontFamily: cssFont(source.fontFamily, defaults.fontFamily),
251
+ fontWeight: isStyleValue(FONT_WEIGHTS, source.fontWeight) ? source.fontWeight : FONT_WEIGHTS.normal,
252
+ defaultFontSize: cssLength(source.defaultFontSize, defaults.defaultFontSize),
253
+ defaultLineHeight: cssLineHeight(source.defaultLineHeight, defaults.defaultLineHeight),
254
+ paragraphSpacing: cssLength(source.paragraphSpacing, defaults.paragraphSpacing),
255
+ listSpacing: cssLength(source.listSpacing, defaults.listSpacing),
256
+ listIndent: cssLength(source.listIndent, defaults.listIndent),
257
+ linkColor: cssColor(source.linkColor, defaults.linkColor),
258
+ linkDecoration: isStyleValue(LINK_DECORATIONS, source.linkDecoration) ? source.linkDecoration : LINK_DECORATIONS.underline,
259
+ borderWidth: cssLength(source.borderWidth, defaults.borderWidth),
260
+ borderStyle: cssBorderStyle(source.borderStyle),
261
+ borderColor: cssColor(source.borderColor, defaults.borderColor),
262
+ borderRadius: cssCornerRadius(source.borderRadius, defaults.borderRadius)
263
+ };
264
+ }
265
+ function mergeEmailGlobal(base, patch) {
266
+ const current = resolveEmailGlobal(base);
267
+ const defined = Object.fromEntries(Object.entries(patch != null ? patch : {}).filter(([, value]) => value !== void 0));
268
+ return resolveEmailGlobal(__spreadValues(__spreadValues({}, current), defined));
269
+ }
270
+ function sameEmailGlobal(left, right) {
271
+ return JSON.stringify(resolveEmailGlobal(left)) === JSON.stringify(resolveEmailGlobal(right));
272
+ }
273
+ function createEmailDocument(blocks = []) {
274
+ return { version: DOCUMENT_VERSION, global: resolveEmailGlobal(), blocks };
275
+ }
276
+ function createEmptyDocument() {
277
+ return { version: DOCUMENT_VERSION, subject: "", global: resolveEmailGlobal(), blocks: [] };
278
+ }
279
+ function ensureEmailDocument(document) {
280
+ return __spreadProps(__spreadValues({}, document), { global: resolveEmailGlobal(document.global) });
281
+ }
282
+
283
+ // src/utils/style.ts
284
+ function readStyleValue(style, id) {
285
+ var _a;
286
+ const [group, key] = id.split(".");
287
+ const values = style == null ? void 0 : style[group];
288
+ return values ? String((_a = values[key]) != null ? _a : "") : "";
289
+ }
290
+ function writeStyleValue(style, id, value) {
291
+ const [group, key] = id.split(".");
292
+ const next = structuredClone(style != null ? style : {});
293
+ if (!key || !["dimension", "typography", "decorations", "element"].includes(group)) return next;
294
+ const groupKey = group;
295
+ const values = __spreadValues({}, next[groupKey]);
296
+ if (value === "") delete values[key];
297
+ else values[key] = value;
298
+ next[groupKey] = values;
299
+ return next;
300
+ }
301
+ function resolvedFontWeight(blockWeight, globalWeight) {
302
+ return isStyleValue(FONT_WEIGHTS, blockWeight) ? blockWeight : globalWeight;
303
+ }
304
+ function typographyStyle(style, global) {
305
+ var _a, _b;
306
+ const settings = resolveEmailGlobal(global);
307
+ const typography = (_a = style == null ? void 0 : style.typography) != null ? _a : {};
308
+ return {
309
+ fontFamily: cssFont(typography.fontFamily, settings.fontFamily),
310
+ fontSize: cssLength(typography.fontSize, settings.defaultFontSize),
311
+ fontWeight: resolvedFontWeight(typography.fontWeight, settings.fontWeight),
312
+ lineHeight: cssLineHeight(typography.lineHeight, settings.defaultLineHeight),
313
+ color: cssColor(typography.color, settings.textColor),
314
+ textAlign: (_b = typography.textAlign) != null ? _b : settings.textAlign
315
+ };
316
+ }
317
+ function radiusStyle(value) {
318
+ const [topLeft, topRight, bottomRight, bottomLeft] = cornerRadii(value != null ? value : "");
319
+ if (topLeft === topRight && topLeft === bottomRight && topLeft === bottomLeft) return { borderRadius: topLeft };
320
+ return {
321
+ borderTopLeftRadius: topLeft,
322
+ borderTopRightRadius: topRight,
323
+ borderBottomRightRadius: bottomRight,
324
+ borderBottomLeftRadius: bottomLeft
325
+ };
326
+ }
327
+ function decorationStyle(decoration = {}) {
328
+ return __spreadValues({
329
+ backgroundColor: cssColor(decoration.backgroundColor),
330
+ borderWidth: cssLength(decoration.borderWidth),
331
+ borderStyle: cssBorderStyle(decoration.borderStyle),
332
+ borderColor: cssColor(decoration.borderColor, "#000000")
333
+ }, radiusStyle(decoration.borderRadius));
334
+ }
335
+ function styleToCss(style) {
336
+ return Object.entries(style).filter(([, value]) => value !== void 0).map(([name, value]) => `${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}:${esc(String(value))}`).join(";");
337
+ }
338
+ function typographyCss(style, global) {
339
+ return styleToCss(typographyStyle(style, global));
340
+ }
341
+ function emailBaseCss(global) {
342
+ return typographyCss(void 0, global);
343
+ }
344
+ function blockContentStyle(style, global) {
345
+ var _a;
346
+ return __spreadProps(__spreadValues(__spreadValues({}, typographyStyle(style, global)), decorationStyle(style == null ? void 0 : style.decorations)), {
347
+ padding: cssSpacing((_a = style == null ? void 0 : style.dimension) == null ? void 0 : _a.padding),
348
+ overflowWrap: "anywhere"
349
+ });
350
+ }
351
+ function blockCellCss(style, global) {
352
+ var _a;
353
+ const css = __spreadValues({}, blockContentStyle(style, global));
354
+ const height = cssPx((_a = style == null ? void 0 : style.dimension) == null ? void 0 : _a.height);
355
+ if (height) {
356
+ css.boxSizing = "border-box";
357
+ css.height = `${height}px`;
358
+ css.verticalAlign = "top";
359
+ }
360
+ return `${styleToCss(css)};`;
361
+ }
362
+
363
+ // src/render/align.ts
364
+ function readAlign(value, fallback = HORIZONTAL_ALIGNS.center) {
365
+ return isStyleValue(HORIZONTAL_ALIGNS, value) ? value : fallback;
366
+ }
367
+ function htmlLength(value) {
368
+ const px = /^(\d+(?:\.\d+)?)px$/.exec(value.trim());
369
+ if (px) return String(Math.round(Number(px[1])));
370
+ const percent = /^(\d+(?:\.\d+)?)%$/.exec(value.trim());
371
+ if (percent) return `${Math.round(Number(percent[1]))}%`;
372
+ return void 0;
373
+ }
374
+ function pixelLength(value) {
375
+ const match = /^(\d+(?:\.\d+)?)px$/.exec(value.trim());
376
+ return match ? String(Math.round(Number(match[1]))) : void 0;
377
+ }
378
+ function imageBox(width) {
379
+ if (width !== "auto" && htmlLength(width)) return { display: "block", width: "100%", frame: "table" };
380
+ return { width: "auto", frame: "inline" };
381
+ }
382
+ function fullWidthRow(align, content) {
383
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="${align}" style="text-align:${align};">${content}</td></tr></table>`;
384
+ }
385
+ function shrinkTable(align, width, content) {
386
+ const attr = htmlLength(width);
387
+ const widthAttr = attr ? ` width="${esc(attr)}"` : "";
388
+ return `<table role="presentation" align="${align}"${widthAttr} cellpadding="0" cellspacing="0" border="0" style="width:${esc(width)};max-width:100%;"><tr>${content}</tr></table>`;
389
+ }
390
+ function renderImage(align, width, content) {
391
+ if (imageBox(width).frame === "table") return fullWidthRow(align, shrinkTable(align, width, `<td>${content}</td>`));
392
+ return fullWidthRow(align, content);
393
+ }
394
+ function renderShrinkElement(align, width, content) {
395
+ const table = width === "auto" ? `<table role="presentation" align="${align}" cellpadding="0" cellspacing="0" border="0"><tr>${content}</tr></table>` : shrinkTable(align, width, content);
396
+ return fullWidthRow(align, table);
397
+ }
398
+ function renderAligned(target, options) {
399
+ var _a;
400
+ const align = readAlign(options.align);
401
+ const width = (_a = options.width) != null ? _a : target === "divider" ? "100%" : "auto";
402
+ if (target === "image") return renderImage(align, width, options.content);
403
+ return renderShrinkElement(align, width, options.content);
404
+ }
405
+
406
+ // src/blocks/icons.ts
407
+ var ICON_TEXT = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M5 4h14v3H5V4Zm0 13h9v-3H5v3Z"/></svg>';
408
+ var ICON_HEADING = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M4 4h3v7h10V4h3v16h-3v-6H7v6H4V4Z"/></svg>';
409
+ var ICON_BUTTON = '<svg viewBox="0 0 24 24" width="18" height="18"><rect x="3" y="7" width="18" height="10" rx="2" fill="currentColor"/></svg>';
410
+ var ICON_IMAGE = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Zm1.5 11 4-4 3 3 2.5-2.5 4 4V6.5h-13V16Z"/></svg>';
411
+ var ICON_DIVIDER = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M3 11h18v2H3v-2Z"/></svg>';
412
+ var ICON_SPACER = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M4 8h16v2H4V8Zm0 6h16v2H4v-2Z"/></svg>';
413
+ var ICON_HTML = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M5 3l-3 9 3 9h3l-3-9 3-9H5Zm6 0 3 9-3 9h3l3-9-3-9h-3Zm6 0-3 9 3 9h3l-3-9 3-9h-3Z"/></svg>';
414
+
415
+ // src/blocks/style-fields.ts
416
+ var BORDER_STYLE_LABELS = {
417
+ none: "None",
418
+ solid: "Solid",
419
+ dashed: "Dashed",
420
+ dotted: "Dotted"
421
+ };
422
+ var PADDING = { id: "dimension.padding", group: "dimension", label: "Padding", control: "spacing", defaultValue: "0px" };
423
+ var MARGIN = { id: "dimension.margin", group: "dimension", label: "Outer spacing", control: "spacing", defaultValue: "0px" };
424
+ var BLOCK_HEIGHT = { id: "dimension.height", group: "dimension", label: "Block height", control: "range", unit: "px", min: 0, max: 480 };
425
+ var FONT_FAMILY = { id: "typography.fontFamily", group: "typography", label: "Font family", control: "select", options: copyFontFamilies() };
426
+ var FONT_SIZE = { id: "typography.fontSize", group: "typography", label: "Font size", control: "range", unit: "px", min: 8, max: 72 };
427
+ var FONT_WEIGHT = { id: "typography.fontWeight", group: "typography", label: "Weight", control: "select", options: [{ label: "Regular", value: FONT_WEIGHTS.normal }, { label: "Bold", value: FONT_WEIGHTS.bold }] };
428
+ var LINE_HEIGHT = { id: "typography.lineHeight", group: "typography", label: "Line height", control: "range", min: 1, max: 2.5, step: 0.05 };
429
+ var TEXT_COLOR = { id: "typography.color", group: "typography", label: "Text", control: "color" };
430
+ var TEXT_ALIGN = { id: "typography.textAlign", group: "typography", label: "Text alignment", control: "select", options: [{ label: "Left", value: TEXT_ALIGNS.left }, { label: "Center", value: TEXT_ALIGNS.center }, { label: "Right", value: TEXT_ALIGNS.right }, { label: "Justify", value: TEXT_ALIGNS.justify }] };
431
+ var BG_COLOR = { id: "decorations.backgroundColor", group: "decorations", label: "Background", control: "color", defaultValue: "transparent" };
432
+ var BORDER_RADIUS = { id: "decorations.borderRadius", group: "decorations", label: "Block corner radius", control: "corners", max: 48, defaultValue: "0px" };
433
+ var BORDER_WIDTH = { id: "decorations.borderWidth", group: "decorations", label: "Block border width", control: "range", unit: "px", min: 0, max: 12, defaultValue: "0px" };
434
+ var BORDER_COLOR = { id: "decorations.borderColor", group: "decorations", label: "Border", control: "color", defaultValue: "#000000" };
435
+ var BORDER_STYLE = {
436
+ id: "decorations.borderStyle",
437
+ group: "decorations",
438
+ label: "Block border style",
439
+ control: "select",
440
+ defaultValue: BORDER_STYLES.solid,
441
+ options: Object.keys(BORDER_STYLES).map((key) => ({
442
+ label: BORDER_STYLE_LABELS[key],
443
+ value: BORDER_STYLES[key]
444
+ }))
445
+ };
446
+ var PARAGRAPH_SPACING = { id: "typography.paragraphSpacing", group: "typography", label: "Paragraph spacing", control: "range", unit: "px", min: 0, max: 48 };
447
+ var LIST_SPACING = { id: "typography.listSpacing", group: "typography", label: "List spacing", control: "range", unit: "px", min: 0, max: 48 };
448
+ var LIST_INDENT = { id: "typography.listIndent", group: "typography", label: "List indent", control: "range", unit: "px", min: 0, max: 64 };
449
+ var LINK_COLOR = { id: "typography.linkColor", group: "typography", label: "Link", control: "color" };
450
+ var LINK_DECORATION = { id: "typography.linkDecoration", group: "typography", label: "Link decoration", control: "select", options: [{ label: "Underline", value: LINK_DECORATIONS.underline }, { label: "None", value: LINK_DECORATIONS.none }] };
451
+ var DIMENSION_FIELDS = { padding: PADDING, margin: MARGIN, height: BLOCK_HEIGHT };
452
+ var DECORATION_FIELDS = {
453
+ backgroundColor: BG_COLOR,
454
+ borderWidth: BORDER_WIDTH,
455
+ borderStyle: BORDER_STYLE,
456
+ borderColor: BORDER_COLOR,
457
+ borderRadius: BORDER_RADIUS
458
+ };
459
+ var TYPOGRAPHY_FIELD_MAP = {
460
+ fontFamily: FONT_FAMILY,
461
+ fontSize: FONT_SIZE,
462
+ fontWeight: FONT_WEIGHT,
463
+ lineHeight: LINE_HEIGHT,
464
+ color: TEXT_COLOR,
465
+ textAlign: TEXT_ALIGN,
466
+ paragraphSpacing: PARAGRAPH_SPACING,
467
+ listSpacing: LIST_SPACING,
468
+ listIndent: LIST_INDENT,
469
+ linkColor: LINK_COLOR,
470
+ linkDecoration: LINK_DECORATION
471
+ };
472
+ var COMMON_STYLE_FIELDS = [PADDING, MARGIN, BLOCK_HEIGHT, BG_COLOR, BORDER_WIDTH, BORDER_STYLE, BORDER_COLOR, BORDER_RADIUS];
473
+ var TYPOGRAPHY_FIELDS = [
474
+ FONT_FAMILY,
475
+ FONT_SIZE,
476
+ FONT_WEIGHT,
477
+ LINE_HEIGHT,
478
+ TEXT_COLOR,
479
+ TEXT_ALIGN,
480
+ PARAGRAPH_SPACING,
481
+ LIST_SPACING,
482
+ LIST_INDENT,
483
+ LINK_COLOR,
484
+ LINK_DECORATION
485
+ ];
486
+ function copyStyleField(field, overrides) {
487
+ const next = __spreadValues(__spreadValues({}, field), overrides);
488
+ if (next.options) next.options = next.options.map((option) => __spreadValues({}, option));
489
+ if (next.units) next.units = [...next.units];
490
+ return next;
491
+ }
492
+ function createFieldSet(fields, overrides) {
493
+ const result = {};
494
+ for (const name of Object.keys(fields)) {
495
+ result[name] = copyStyleField(fields[name], overrides == null ? void 0 : overrides[name]);
496
+ }
497
+ return result;
498
+ }
499
+ function createDimensionFields(overrides) {
500
+ return createFieldSet(DIMENSION_FIELDS, overrides);
501
+ }
502
+ function createDecorationFields(overrides) {
503
+ return createFieldSet(DECORATION_FIELDS, overrides);
504
+ }
505
+ function createTypographyFields(overrides) {
506
+ return createFieldSet(TYPOGRAPHY_FIELD_MAP, overrides);
507
+ }
508
+ function resolveBlockStyleFields(fields, exactStyleFields = false) {
509
+ const custom = (fields != null ? fields : []).map((field) => copyStyleField(field));
510
+ if (exactStyleFields) return custom;
511
+ const customById = /* @__PURE__ */ new Map();
512
+ for (const field of custom) {
513
+ if (!customById.has(field.id)) customById.set(field.id, field);
514
+ }
515
+ const commonIds = new Set(COMMON_STYLE_FIELDS.map((field) => field.id));
516
+ const merged = COMMON_STYLE_FIELDS.map((field) => {
517
+ var _a;
518
+ return (_a = customById.get(field.id)) != null ? _a : copyStyleField(field);
519
+ });
520
+ return [...merged, ...custom.filter((field) => !commonIds.has(field.id))];
521
+ }
522
+
523
+ // src/blocks/element-fields.ts
524
+ var elementDecoration = createDecorationFields({
525
+ borderWidth: { id: "element.borderWidth", label: "Border width" },
526
+ borderStyle: { id: "element.borderStyle", label: "Border style" },
527
+ borderColor: { id: "element.borderColor", label: "Outline" },
528
+ borderRadius: { id: "element.borderRadius", label: "Corner radius" }
529
+ });
530
+ var buttonTypography = createTypographyFields({
531
+ fontFamily: { id: "element.fontFamily" },
532
+ fontSize: { id: "element.fontSize" },
533
+ fontWeight: { id: "element.fontWeight" },
534
+ lineHeight: { id: "element.lineHeight" },
535
+ color: { id: "element.color" }
536
+ });
537
+ var BUTTON_BOX_DEFAULTS = {
538
+ align: HORIZONTAL_ALIGNS.center,
539
+ backgroundColor: "#005bd3",
540
+ borderRadius: "8px",
541
+ borderWidth: "1px",
542
+ borderColor: "#005bd3",
543
+ padding: "12px 32px"
544
+ };
545
+ var ELEMENT_BORDER_FIELDS = [
546
+ elementDecoration.borderWidth,
547
+ elementDecoration.borderStyle,
548
+ elementDecoration.borderColor,
549
+ elementDecoration.borderRadius
550
+ ];
551
+ var ELEMENT_ALIGN = { id: "element.align", group: "general", label: "Alignment", control: "alignment", defaultValue: HORIZONTAL_ALIGNS.center };
552
+ var ELEMENT_WIDTH = { id: "element.width", group: "general", label: "Width", control: "range", units: ["px", "%", "auto"], min: 0, max: 600, defaultValue: "auto" };
553
+ var BUTTON_STYLE_FIELDS = [
554
+ ELEMENT_ALIGN,
555
+ ELEMENT_WIDTH,
556
+ createDimensionFields({ padding: { id: "element.padding", label: "Button padding", defaultValue: BUTTON_BOX_DEFAULTS.padding } }).padding,
557
+ buttonTypography.fontFamily,
558
+ buttonTypography.fontSize,
559
+ buttonTypography.fontWeight,
560
+ buttonTypography.lineHeight,
561
+ buttonTypography.color,
562
+ createDecorationFields({ backgroundColor: { id: "element.backgroundColor", label: "Button", defaultValue: BUTTON_BOX_DEFAULTS.backgroundColor } }).backgroundColor,
563
+ ...ELEMENT_BORDER_FIELDS.map((field) => __spreadProps(__spreadValues({}, field), {
564
+ defaultValue: field.id === "element.borderWidth" ? BUTTON_BOX_DEFAULTS.borderWidth : field.id === "element.borderColor" ? BUTTON_BOX_DEFAULTS.borderColor : field.id === "element.borderRadius" ? BUTTON_BOX_DEFAULTS.borderRadius : field.defaultValue
565
+ }))
566
+ ];
567
+ var IMAGE_STYLE_FIELDS = [
568
+ ELEMENT_WIDTH,
569
+ { id: "element.height", group: "general", label: "Height", control: "range", units: ["px", "auto"], min: 0, max: 480, defaultValue: "auto" },
570
+ ...ELEMENT_BORDER_FIELDS
571
+ ];
572
+ var DIVIDER_STYLE_FIELDS = [
573
+ ELEMENT_ALIGN,
574
+ __spreadProps(__spreadValues({}, ELEMENT_WIDTH), { units: ["px", "%"], defaultValue: "100%" }),
575
+ __spreadProps(__spreadValues({}, ELEMENT_BORDER_FIELDS[0]), { label: "Line thickness", defaultValue: "1px" }),
576
+ __spreadProps(__spreadValues({}, ELEMENT_BORDER_FIELDS[1]), { label: "Line style" })
577
+ ];
578
+
579
+ // src/blocks/button.ts
580
+ var buttonBlock = {
581
+ type: BLOCK_TYPES.button,
582
+ title: "Button",
583
+ icon: ICON_BUTTON,
584
+ description: "Clickable CTA button",
585
+ fields: [
586
+ { type: "text", name: "label", label: "Label", defaultValue: "Buy now" },
587
+ { type: "url", name: "href", label: "Link", defaultValue: "https://example.com" }
588
+ ],
589
+ defaultStyle: {
590
+ element: __spreadValues({ fontSize: "16px", color: "#ffffff" }, BUTTON_BOX_DEFAULTS)
591
+ },
592
+ styleFields: [...COMMON_STYLE_FIELDS, ...BUTTON_STYLE_FIELDS],
593
+ render: ({ fields, style, global }) => {
594
+ var _a, _b;
595
+ const element = __spreadValues(__spreadValues({}, BUTTON_BOX_DEFAULTS), style.element);
596
+ const width = cssLength(element.width, "auto", true);
597
+ const px = pixelLength(width);
598
+ const text = styleToCss(__spreadProps(__spreadValues({}, typographyStyle({ typography: element }, global)), { textAlign: "center" }));
599
+ const box = styleToCss(__spreadProps(__spreadValues({}, decorationStyle(element)), {
600
+ padding: cssSpacing(element.padding, BUTTON_BOX_DEFAULTS.padding),
601
+ textAlign: "center"
602
+ }));
603
+ const anchor = `<a href="${esc(String((_a = fields.href) != null ? _a : "#"))}" style="display:block;text-decoration:none;${text};">${esc(String((_b = fields.label) != null ? _b : "Button"))}</a>`;
604
+ const cell = `<td align="center"${px ? ` width="${px}"` : ""} style="${box};">${anchor}</td>`;
605
+ return renderAligned("button", { align: element.align, width, content: cell });
606
+ }
607
+ };
608
+
609
+ // src/blocks/divider.ts
610
+ var dividerBlock = {
611
+ type: BLOCK_TYPES.divider,
612
+ title: "Divider",
613
+ icon: ICON_DIVIDER,
614
+ description: "Horizontal divider line",
615
+ defaultStyle: { dimension: { padding: "12px" }, element: { borderColor: "#e3e3e5" } },
616
+ styleFields: [...COMMON_STYLE_FIELDS, ...DIVIDER_STYLE_FIELDS, { id: "element.borderColor", group: "decorations", label: "Line", control: "color", defaultValue: "#e3e3e5" }],
617
+ render: ({ style }) => {
618
+ var _a;
619
+ const element = (_a = style.element) != null ? _a : {};
620
+ const width = cssLength(element.width, "100%");
621
+ const border = `${cssLength(element.borderWidth, "1px")} ${cssBorderStyle(element.borderStyle)} ${cssColor(element.borderColor, "#e3e3e5")}`;
622
+ const cell = `<td style="border-top:${esc(border)};font-size:1px;line-height:1px;">&nbsp;</td>`;
623
+ return renderAligned("divider", { align: element.align, width, content: cell });
624
+ }
625
+ };
626
+
627
+ // src/render/content-style.ts
628
+ function contentDefaults(style, global) {
629
+ var _a;
630
+ const settings = resolveEmailGlobal(global);
631
+ const typography = style == null ? void 0 : style.typography;
632
+ return {
633
+ paragraph: cssLength(typography == null ? void 0 : typography.paragraphSpacing, settings.paragraphSpacing),
634
+ listSpacing: cssLength(typography == null ? void 0 : typography.listSpacing, settings.listSpacing),
635
+ listIndent: cssLength(typography == null ? void 0 : typography.listIndent, settings.listIndent),
636
+ linkColor: cssColor(typography == null ? void 0 : typography.linkColor, settings.linkColor),
637
+ linkDecoration: (_a = typography == null ? void 0 : typography.linkDecoration) != null ? _a : settings.linkDecoration
638
+ };
639
+ }
640
+ function contentVariables(style, global) {
641
+ const values = contentDefaults(style, global);
642
+ return {
643
+ "--ree-paragraph-spacing": values.paragraph,
644
+ "--ree-list-spacing": values.listSpacing,
645
+ "--ree-list-indent": values.listIndent,
646
+ "--ree-link-color": values.linkColor,
647
+ "--ree-link-decoration": values.linkDecoration
648
+ };
649
+ }
650
+ function attributeValue(attrs, name) {
651
+ const match = attrs.match(new RegExp(`\\s${name}\\s*=\\s*(["'])(.*?)\\1`, "i"));
652
+ return match == null ? void 0 : match[2];
653
+ }
654
+ function omitAttribute(attrs, name) {
655
+ return attrs.replace(new RegExp(`\\s${name}\\s*=\\s*(["']).*?\\1`, "gi"), "");
656
+ }
657
+ function stripDeclarations(style, pattern) {
658
+ const parts = style.split(";").map((part) => part.trim()).filter(Boolean);
659
+ const kept = parts.filter((part) => !pattern.test(part));
660
+ return kept.length === parts.length ? style : kept.join(";");
661
+ }
662
+ function paragraphPlacements(html) {
663
+ var _a, _b;
664
+ const placements = [];
665
+ const topLevel = [];
666
+ let liDepth = 0;
667
+ const tokens = /<!--[\s\S]*?-->|<\/(li)\b[^>]*>|<(li|p|ul|ol)\b[^>]*>/gi;
668
+ let match;
669
+ while (match = tokens.exec(html)) {
670
+ if (match[0].startsWith("<!--")) continue;
671
+ const closing = (_a = match[1]) == null ? void 0 : _a.toLowerCase();
672
+ const opening = (_b = match[2]) == null ? void 0 : _b.toLowerCase();
673
+ if (closing === "li") liDepth = Math.max(0, liDepth - 1);
674
+ if (opening === "li") liDepth += 1;
675
+ if (opening === "p") {
676
+ placements.push({ topLevel: liDepth === 0, followsParagraph: false });
677
+ if (liDepth === 0) topLevel.push({ kind: "p", paragraph: placements.length - 1 });
678
+ }
679
+ if ((opening === "ul" || opening === "ol") && liDepth === 0) topLevel.push({ kind: "list", paragraph: -1 });
680
+ }
681
+ for (let index = 0; index < topLevel.length - 1; index += 1) {
682
+ const current = topLevel[index];
683
+ const next = topLevel[index + 1];
684
+ if ((current == null ? void 0 : current.kind) === "p" && (next == null ? void 0 : next.kind) === "p") {
685
+ const placement = placements[next.paragraph];
686
+ if (placement) placement.followsParagraph = true;
687
+ }
688
+ }
689
+ return placements;
690
+ }
691
+ function paragraphMargin(gap) {
692
+ return gap === "0px" ? "margin:0;" : `margin:${gap} 0 0 0;`;
693
+ }
694
+ function styleRichText(html, style, global) {
695
+ const values = contentDefaults(style, global);
696
+ const placements = paragraphPlacements(html);
697
+ let paragraph = 0;
698
+ return html.replace(/<!--[\s\S]*?-->|<(p|ul|ol|a)\b([^>]*)>/gi, (tag, name, attributes) => {
699
+ var _a, _b;
700
+ if (!name || tag.startsWith("<!--")) return tag;
701
+ const lower = name.toLowerCase();
702
+ const attrs = attributes != null ? attributes : "";
703
+ const match = attrs.match(/\sstyle\s*=\s*(["'])(.*?)\1/i);
704
+ let existing = (_a = match == null ? void 0 : match[2]) != null ? _a : "";
705
+ let rest = match ? attrs.replace(match[0], "") : attrs;
706
+ let defaults = lower === "a" ? `color:${values.linkColor};text-decoration:${values.linkDecoration};` : `margin:${values.listSpacing} 0;padding-left:${values.listIndent};`;
707
+ if (lower === "p") {
708
+ const placement = (_b = placements[paragraph++]) != null ? _b : { topLevel: false, followsParagraph: false };
709
+ const explicitSpacing = attributeValue(attrs, "data-spacing");
710
+ const explicitLineHeight = attributeValue(attrs, "data-line-height");
711
+ const spacing = explicitSpacing === void 0 ? "" : cssLength(explicitSpacing, "");
712
+ const lineHeight = explicitLineHeight === void 0 ? "" : cssLineHeight(explicitLineHeight, "");
713
+ const gap = placement.topLevel && spacing ? spacing : placement.followsParagraph ? values.paragraph : "0px";
714
+ existing = stripDeclarations(existing, /^--ree-paragraph-gap\s*:/i);
715
+ if (placement.topLevel && spacing) existing = stripDeclarations(existing, /^margin(?:-(?:top|right|bottom|left))?\s*:/i);
716
+ if (lineHeight) existing = stripDeclarations(existing, /^line-height\s*:/i);
717
+ rest = omitAttribute(omitAttribute(rest, "data-spacing"), "data-line-height");
718
+ defaults = `${paragraphMargin(gap)}${lineHeight ? `line-height:${lineHeight};` : ""}`;
719
+ }
720
+ return `<${name}${rest} style="${defaults}${existing.replace(/"/g, "&quot;")}">`;
721
+ });
722
+ }
723
+
724
+ // src/blocks/heading.ts
725
+ var headingBlock = {
726
+ type: BLOCK_TYPES.heading,
727
+ title: "Heading",
728
+ icon: ICON_HEADING,
729
+ description: "Large section heading",
730
+ inlineEdit: true,
731
+ defaultFields: { html: "<p>Heading</p>" },
732
+ defaultStyle: {
733
+ dimension: { padding: "16px" },
734
+ typography: {
735
+ fontSize: "26px",
736
+ fontWeight: "bold",
737
+ lineHeight: "1.25"
738
+ }
739
+ },
740
+ styleFields: [...COMMON_STYLE_FIELDS, ...TYPOGRAPHY_FIELDS],
741
+ render: ({ fields, style, global }) => {
742
+ var _a;
743
+ return `<div style="display:flow-root;${typographyCss(style, global)}">${styleRichText(String((_a = fields.html) != null ? _a : ""), style, global)}</div>`;
744
+ }
745
+ };
746
+
747
+ // src/render/html-content.ts
748
+ function htmlContent(html, style, global, id) {
749
+ var _a, _b, _c, _d;
750
+ const scope = `ree-html-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
751
+ const body = html.match(/<body\b([^>]*)>([\s\S]*?)<\/body\s*>/i);
752
+ let marker = "";
753
+ let inner = styleRichText(html, style, global);
754
+ if (body) {
755
+ const head = (_b = (_a = html.match(/<head\b[^>]*>([\s\S]*?)<\/head\s*>/i)) == null ? void 0 : _a[1]) != null ? _b : "";
756
+ const styles = (_d = (_c = head.match(/<style\b[^>]*>[\s\S]*?<\/style\s*>/gi)) == null ? void 0 : _c.join("\n")) != null ? _d : "";
757
+ marker = styles ? `<!--ree-head-->${styles}<!--/ree-head-->` : "";
758
+ inner = `<div${body[1]}>${styleRichText(body[2], style, global)}</div>`;
759
+ }
760
+ return `${marker}<div class="${scope}" style="display:flow-root;${typographyCss(style, global)};">${inner}</div>`;
761
+ }
762
+
763
+ // src/blocks/html.ts
764
+ var htmlBlock = {
765
+ type: BLOCK_TYPES.html,
766
+ title: "HTML",
767
+ icon: ICON_HTML,
768
+ description: "Paste raw HTML",
769
+ fields: [
770
+ {
771
+ type: "html",
772
+ name: "html",
773
+ label: "HTML",
774
+ defaultValue: "<p>Custom HTML block</p>",
775
+ rows: 10,
776
+ placeholder: "Paste raw HTML\u2026"
777
+ }
778
+ ],
779
+ defaultStyle: { dimension: { padding: "8px" } },
780
+ styleFields: [...COMMON_STYLE_FIELDS, ...TYPOGRAPHY_FIELDS],
781
+ render: ({ fields, style, global, block }) => {
782
+ var _a;
783
+ return htmlContent(String((_a = fields.html) != null ? _a : ""), style, global, block.id);
784
+ }
785
+ };
786
+
787
+ // src/blocks/image.ts
788
+ var imageBlock = {
789
+ type: BLOCK_TYPES.image,
790
+ title: "Image",
791
+ icon: ICON_IMAGE,
792
+ description: "Insert an image",
793
+ fields: [
794
+ { type: "url", name: "src", label: "Image URL", defaultValue: "https://placehold.co/560x200?text=Image" },
795
+ { type: "text", name: "alt", label: "Alt text", defaultValue: "Image" },
796
+ { type: "url", name: "link", label: "Link", defaultValue: "" }
797
+ ],
798
+ defaultStyle: { dimension: { padding: "8px" } },
799
+ styleFields: [...COMMON_STYLE_FIELDS, ELEMENT_ALIGN, ...IMAGE_STYLE_FIELDS],
800
+ render: ({ fields, style }) => {
801
+ var _a, _b, _c, _d;
802
+ const element = (_a = style.element) != null ? _a : {};
803
+ const width = cssLength(element.width, "auto", true);
804
+ const height = cssLength(element.height, "auto", true);
805
+ const box = imageBox(width);
806
+ const css = styleToCss(__spreadProps(__spreadValues({}, decorationStyle(element)), {
807
+ display: box.display,
808
+ width: box.width,
809
+ height,
810
+ maxWidth: "100%",
811
+ verticalAlign: box.display ? void 0 : "bottom"
812
+ }));
813
+ const widthAttr = pixelLength(width);
814
+ const heightAttr = pixelLength(height);
815
+ const image = `<img src="${esc(String((_b = fields.src) != null ? _b : ""))}" alt="${esc(String((_c = fields.alt) != null ? _c : ""))}"${widthAttr ? ` width="${widthAttr}"` : ""}${heightAttr ? ` height="${heightAttr}"` : ""} border="0" style="outline:none;text-decoration:none;${css};" />`;
816
+ const link = String((_d = fields.link) != null ? _d : "");
817
+ const content = link ? `<a href="${esc(link)}" style="text-decoration:none;${box.display === "block" ? "display:block;" : ""}">${image}</a>` : image;
818
+ return renderAligned("image", { align: element.align, width, content });
819
+ }
820
+ };
821
+
822
+ // src/render/spacer.ts
823
+ function spacerHtml(height, fallback = "0px") {
824
+ var _a;
825
+ const css = cssLength(height, fallback);
826
+ const px = (_a = pixelLength(css)) != null ? _a : "0";
827
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td height="${esc(px)}" style="height:${esc(css)};line-height:${esc(css)};font-size:${esc(css)};">&nbsp;</td></tr></table>`;
828
+ }
829
+
830
+ // src/blocks/spacer.ts
831
+ var spacerBlock = {
832
+ type: BLOCK_TYPES.spacer,
833
+ title: "Spacer",
834
+ icon: ICON_SPACER,
835
+ description: "Vertical whitespace",
836
+ fields: [{ type: "number", name: "height", label: "Height", defaultValue: 16, min: 0, max: 240, step: 1 }],
837
+ styleFields: COMMON_STYLE_FIELDS,
838
+ render: ({ fields }) => spacerHtml(fields.height, "16px")
839
+ };
840
+
841
+ // src/blocks/text.ts
842
+ var textBlock = {
843
+ type: BLOCK_TYPES.text,
844
+ title: "Text",
845
+ icon: ICON_TEXT,
846
+ description: "Paragraph of body text",
847
+ inlineEdit: true,
848
+ defaultFields: { html: "<p>Type your paragraph here\u2026</p>" },
849
+ defaultStyle: {
850
+ dimension: { padding: "16px" }
851
+ },
852
+ styleFields: [...COMMON_STYLE_FIELDS, ...TYPOGRAPHY_FIELDS],
853
+ render: ({ fields, style, global }) => {
854
+ var _a;
855
+ return `<div style="display:flow-root;${typographyCss(style, global)}">${styleRichText(String((_a = fields.html) != null ? _a : ""), style, global)}</div>`;
856
+ }
857
+ };
858
+
859
+ // src/blocks/registry.ts
860
+ var DEFAULT_BLOCKS = [
861
+ headingBlock,
862
+ textBlock,
863
+ buttonBlock,
864
+ imageBlock,
865
+ dividerBlock,
866
+ spacerBlock,
867
+ htmlBlock
868
+ ];
869
+
870
+ // src/document/registry.ts
871
+ function mergeBlockDefinitions(custom = []) {
872
+ const map = /* @__PURE__ */ new Map();
873
+ for (const definition of DEFAULT_BLOCKS) map.set(definition.type, definition);
874
+ for (const definition of custom) map.set(definition.type, definition);
875
+ return Array.from(map.values());
876
+ }
877
+ function getBlockDefinition(type, definitions = DEFAULT_BLOCKS) {
878
+ const definition = definitions.find((item) => item.type === type);
879
+ if (!definition) return void 0;
880
+ return __spreadProps(__spreadValues({}, definition), {
881
+ styleFields: resolveBlockStyleFields(definition.styleFields, definition.exactStyleFields)
882
+ });
883
+ }
884
+
885
+ // src/editor-dnd/drop-target.ts
886
+ function containsPoint(rect, point) {
887
+ return point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom;
888
+ }
889
+ function edgeAt(rect, y) {
890
+ const mid = (rect.top + rect.bottom) / 2;
891
+ return y < mid ? "before" : "after";
892
+ }
893
+ function sortDropRects(blocks) {
894
+ return [...blocks].sort((left, right) => left.top - right.top || left.left - right.left);
895
+ }
896
+ function resolveCanvasDrop(pointer, canvas, blocks) {
897
+ const ordered = sortDropRects(blocks);
898
+ const hit = ordered.find((block) => containsPoint(block, pointer));
899
+ if (hit) return { id: hit.id, edge: edgeAt(hit, pointer.y) };
900
+ if (!canvas || !containsPoint(canvas, pointer)) return null;
901
+ if (ordered.length === 0) return { id: canvas.id, edge: "after" };
902
+ const first = ordered[0];
903
+ const last = ordered[ordered.length - 1];
904
+ if (!first || !last) return { id: canvas.id, edge: "after" };
905
+ if (pointer.y <= first.top) return { id: first.id, edge: "before" };
906
+ if (pointer.y >= last.bottom) return { id: last.id, edge: "after" };
907
+ for (let index = 0; index < ordered.length - 1; index += 1) {
908
+ const current = ordered[index];
909
+ const next = ordered[index + 1];
910
+ if (!current || !next) continue;
911
+ if (pointer.y < current.bottom || pointer.y > next.top) continue;
912
+ const mid = (current.bottom + next.top) / 2;
913
+ return pointer.y < mid ? { id: current.id, edge: "after" } : { id: next.id, edge: "before" };
914
+ }
915
+ const beside = ordered.find((block) => pointer.y >= block.top && pointer.y <= block.bottom);
916
+ if (beside) return { id: beside.id, edge: edgeAt(beside, pointer.y) };
917
+ return { id: last.id, edge: "after" };
918
+ }
919
+ function insertionIndex(blockIds, target, canvasId) {
920
+ if (target.id === canvasId) return blockIds.length;
921
+ const index = blockIds.indexOf(target.id);
922
+ if (index < 0) return blockIds.length;
923
+ return target.edge === "after" ? index + 1 : index;
924
+ }
925
+ function resolveDropCollision(pointer, canvas, blocks, activeId, canvasId) {
926
+ const ordered = sortDropRects(blocks);
927
+ const target = resolveCanvasDrop(pointer, canvas, ordered);
928
+ if (!target) return null;
929
+ return {
930
+ destinationId: sortableDestinationId(
931
+ ordered.map((block) => block.id),
932
+ activeId,
933
+ target,
934
+ canvasId
935
+ ),
936
+ target
937
+ };
938
+ }
939
+ function sortableDestinationId(blockIds, activeId, target, canvasId) {
940
+ var _a;
941
+ if (!blockIds.includes(activeId)) return target.id;
942
+ if (target.id === activeId) return activeId;
943
+ const remaining = blockIds.filter((id) => id !== activeId);
944
+ const index = Math.min(
945
+ Math.max(insertionIndex(remaining, target, canvasId), 0),
946
+ blockIds.length - 1
947
+ );
948
+ return (_a = blockIds[index]) != null ? _a : activeId;
949
+ }
950
+ function indicatorForDrop(blockIds, activeId, target, canvasId) {
951
+ if (target.id === activeId) return null;
952
+ const ids = blockIds.includes(activeId) ? blockIds.filter((id2) => id2 !== activeId) : blockIds;
953
+ const index = insertionIndex(ids, target, canvasId);
954
+ if (ids.length === 0) return null;
955
+ if (index <= 0) {
956
+ const id2 = ids[0];
957
+ return id2 ? { id: id2, edge: "before" } : null;
958
+ }
959
+ if (index >= ids.length) {
960
+ const id2 = ids[ids.length - 1];
961
+ return id2 ? { id: id2, edge: "after" } : null;
962
+ }
963
+ const id = ids[index];
964
+ return id ? { id, edge: "before" } : null;
965
+ }
966
+
967
+ // src/editor-dnd/collision.ts
968
+ function toDropRect(id, rect) {
969
+ return {
970
+ id,
971
+ top: rect.top,
972
+ bottom: rect.bottom,
973
+ left: rect.left,
974
+ right: rect.right
975
+ };
976
+ }
977
+ function readDropTarget(collisions) {
978
+ var _a;
979
+ const data = (_a = collisions == null ? void 0 : collisions[0]) == null ? void 0 : _a.data;
980
+ if (!data || typeof data !== "object") return null;
981
+ const targetId = data.targetId;
982
+ const edge = data.edge;
983
+ if (typeof targetId !== "string") return null;
984
+ if (edge !== "before" && edge !== "after") return null;
985
+ return { id: targetId, edge };
986
+ }
987
+ var canvasDropCollision = ({
988
+ active,
989
+ droppableContainers,
990
+ droppableRects,
991
+ pointerCoordinates
992
+ }) => {
993
+ if (!pointerCoordinates) return [];
994
+ let canvas = null;
995
+ const blocks = [];
996
+ for (const container of droppableContainers) {
997
+ const id = String(container.id);
998
+ const rect = droppableRects.get(container.id);
999
+ if (!rect) continue;
1000
+ if (id === CANVAS_DROP_ID) {
1001
+ canvas = toDropRect(id, rect);
1002
+ continue;
1003
+ }
1004
+ blocks.push(toDropRect(id, rect));
1005
+ }
1006
+ const resolved = resolveDropCollision(pointerCoordinates, canvas, blocks, String(active.id), CANVAS_DROP_ID);
1007
+ if (!resolved) return [];
1008
+ const collision = {
1009
+ id: resolved.destinationId,
1010
+ data: {
1011
+ value: 0,
1012
+ edge: resolved.target.edge,
1013
+ targetId: resolved.target.id
1014
+ }
1015
+ };
1016
+ return [collision];
1017
+ };
1018
+
1019
+ // src/editor-dnd/drag-context.ts
1020
+ import { createContext, useContext } from "react";
1021
+ var emptyDragState = { targetId: null, dropEdge: null };
1022
+ var EditorDragContext = createContext(emptyDragState);
1023
+ function useEditorDragState() {
1024
+ return useContext(EditorDragContext);
1025
+ }
1026
+
1027
+ // src/editor-dnd/drag-data.ts
1028
+ function readDragData(data) {
1029
+ if (!data || typeof data !== "object" || !("kind" in data)) return void 0;
1030
+ const record = data;
1031
+ if (record.kind === "library" && typeof record.type === "string") {
1032
+ return { kind: "library", type: record.type };
1033
+ }
1034
+ if (record.kind === "canvas") return { kind: "canvas" };
1035
+ return void 0;
1036
+ }
1037
+
1038
+ // src/editor-dnd/EditorDnd.tsx
1039
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1040
+ function sameDrop(current, next) {
1041
+ return (current == null ? void 0 : current.id) === (next == null ? void 0 : next.id) && (current == null ? void 0 : current.edge) === (next == null ? void 0 : next.edge);
1042
+ }
1043
+ function EditorDnd({
1044
+ blocks,
1045
+ definitions,
1046
+ onInsertBlock,
1047
+ onReorder,
1048
+ onLibraryDragStart,
1049
+ children
1050
+ }) {
1051
+ const blocksRef = useRef(blocks);
1052
+ blocksRef.current = blocks;
1053
+ const [activeDrag, setActiveDrag] = useState(null);
1054
+ const [indicator, setIndicator] = useState(null);
1055
+ const sensors = useSensors(
1056
+ useSensor(PointerSensor, {
1057
+ activationConstraint: { distance: DRAG_ACTIVATION_DISTANCE }
1058
+ })
1059
+ );
1060
+ const updateIndicator = (activeId, collisions) => {
1061
+ const target = readDropTarget(collisions);
1062
+ const next = target ? indicatorForDrop(
1063
+ blocksRef.current.map((block) => block.id),
1064
+ activeId,
1065
+ target,
1066
+ CANVAS_DROP_ID
1067
+ ) : null;
1068
+ setIndicator((current) => sameDrop(current, next) ? current : next);
1069
+ };
1070
+ const handleDragStart = (event) => {
1071
+ const data = readDragData(event.active.data.current);
1072
+ setActiveDrag(data != null ? data : { kind: "canvas" });
1073
+ setIndicator(null);
1074
+ if ((data == null ? void 0 : data.kind) === "library") onLibraryDragStart();
1075
+ };
1076
+ const handleDragMove = (event) => {
1077
+ updateIndicator(String(event.active.id), event.collisions);
1078
+ };
1079
+ const clearDrag = () => {
1080
+ setActiveDrag(null);
1081
+ setIndicator(null);
1082
+ };
1083
+ const handleDragEnd = (event) => {
1084
+ const data = readDragData(event.active.data.current);
1085
+ const target = readDropTarget(event.collisions);
1086
+ const overId = event.over ? String(event.over.id) : null;
1087
+ clearDrag();
1088
+ if ((data == null ? void 0 : data.kind) === "library") {
1089
+ if (!target) return;
1090
+ onInsertBlock(
1091
+ data.type,
1092
+ insertionIndex(
1093
+ blocksRef.current.map((block) => block.id),
1094
+ target,
1095
+ CANVAS_DROP_ID
1096
+ )
1097
+ );
1098
+ return;
1099
+ }
1100
+ if (!overId || event.active.id === overId) return;
1101
+ onReorder(String(event.active.id), overId);
1102
+ };
1103
+ const overlayDefinition = (activeDrag == null ? void 0 : activeDrag.kind) === "library" ? getBlockDefinition(activeDrag.type, definitions) : void 0;
1104
+ const dragState = useMemo(
1105
+ () => {
1106
+ var _a, _b;
1107
+ return {
1108
+ targetId: (_a = indicator == null ? void 0 : indicator.id) != null ? _a : null,
1109
+ dropEdge: (_b = indicator == null ? void 0 : indicator.edge) != null ? _b : null
1110
+ };
1111
+ },
1112
+ [indicator]
1113
+ );
1114
+ return /* @__PURE__ */ jsxs(
1115
+ DndContext,
1116
+ {
1117
+ sensors,
1118
+ collisionDetection: canvasDropCollision,
1119
+ onDragStart: handleDragStart,
1120
+ onDragMove: handleDragMove,
1121
+ onDragEnd: handleDragEnd,
1122
+ onDragCancel: clearDrag,
1123
+ children: [
1124
+ /* @__PURE__ */ jsx2(EditorDragContext.Provider, { value: dragState, children }),
1125
+ /* @__PURE__ */ jsx2(DragOverlay, { children: overlayDefinition ? /* @__PURE__ */ jsxs("div", { className: "ree-drag-overlay", children: [
1126
+ /* @__PURE__ */ jsx2(BlockIcon, { icon: overlayDefinition.icon, className: "ree-lib-icon" }),
1127
+ /* @__PURE__ */ jsx2("span", { children: overlayDefinition.title })
1128
+ ] }) : null })
1129
+ ]
1130
+ }
1131
+ );
1132
+ }
1133
+
1134
+ // src/block-library/BlockLibraryItem.tsx
1135
+ import { useDraggable } from "@dnd-kit/core";
1136
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1137
+ function BlockLibraryItem({ definition, onAdd }) {
1138
+ const dragData = { kind: "library", type: definition.type };
1139
+ const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
1140
+ id: `lib-${definition.type}`,
1141
+ data: dragData
1142
+ });
1143
+ return /* @__PURE__ */ jsxs2(
1144
+ "div",
1145
+ __spreadProps(__spreadValues(__spreadValues({
1146
+ ref: setNodeRef
1147
+ }, listeners), attributes), {
1148
+ className: `ree-lib-item${isDragging ? " is-dragging" : ""}`,
1149
+ onClick: onAdd,
1150
+ title: definition.description,
1151
+ children: [
1152
+ /* @__PURE__ */ jsx3(BlockIcon, { icon: definition.icon, className: "ree-lib-icon" }),
1153
+ /* @__PURE__ */ jsx3("span", { className: "ree-lib-title", children: definition.title })
1154
+ ]
1155
+ })
1156
+ );
1157
+ }
1158
+
1159
+ // src/block-library/BlockLibrary.tsx
1160
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1161
+ function BlockLibrary({ definitions, onAdd }) {
1162
+ return /* @__PURE__ */ jsxs3("aside", { className: "ree-col ree-col-left", children: [
1163
+ /* @__PURE__ */ jsx4("div", { className: "ree-col-head", children: "Blocks" }),
1164
+ /* @__PURE__ */ jsx4("div", { className: "ree-lib", children: definitions.map((definition) => /* @__PURE__ */ jsx4(
1165
+ BlockLibraryItem,
1166
+ {
1167
+ definition,
1168
+ onAdd: () => onAdd(definition.type)
1169
+ },
1170
+ definition.type
1171
+ )) })
1172
+ ] });
1173
+ }
1174
+
1175
+ // src/inspector/global-constants.ts
1176
+ var GLOBAL_FIELD_SECTIONS = [
1177
+ { title: "General", fields: [
1178
+ { id: "contentWidth", group: "general", label: "Content width", control: "range", min: CONTENT_WIDTH_MIN, max: CONTENT_WIDTH_MAX, step: CONTENT_WIDTH_STEP, unit: "px" },
1179
+ { id: "contentAlign", group: "general", label: "Content alignment", control: "alignment" }
1180
+ ] },
1181
+ { title: "Spacing", fields: [
1182
+ { id: "contentPadding", group: "dimension", label: "Content padding", control: "spacing" },
1183
+ { id: "outerPadding", group: "dimension", label: "Outer padding", control: "spacing" },
1184
+ { id: "blockGap", group: "dimension", label: "Block gap", control: "range", min: 0, max: 80, unit: "px" }
1185
+ ] },
1186
+ { title: "Decoration", fields: [
1187
+ { id: "backgroundColor", group: "decorations", label: "Background", control: "color" },
1188
+ { id: "contentBackgroundColor", group: "decorations", label: "Content", control: "color" },
1189
+ { id: "borderWidth", group: "decorations", label: "Border width", control: "range", min: 0, max: 12, unit: "px" },
1190
+ __spreadProps(__spreadValues({}, BORDER_STYLE), { id: "borderStyle", label: "Border style" }),
1191
+ { id: "borderColor", group: "decorations", label: "Border", control: "color" },
1192
+ { id: "borderRadius", group: "decorations", label: "Corner radius", control: "corners", max: 48 }
1193
+ ] },
1194
+ { title: "Typography", fields: [
1195
+ { id: "fontFamily", group: "typography", label: "Font family", control: "select", options: copyFontFamilies() },
1196
+ __spreadProps(__spreadValues({}, FONT_WEIGHT), { id: "fontWeight" }),
1197
+ __spreadProps(__spreadValues({}, TEXT_ALIGN), { id: "textAlign" }),
1198
+ { id: "defaultFontSize", group: "typography", label: "Font size", control: "range", min: 8, max: 72, unit: "px" },
1199
+ { id: "defaultLineHeight", group: "typography", label: "Line height", control: "range", min: 1, max: 2.5, step: 0.05 },
1200
+ { id: "textColor", group: "typography", label: "Text", control: "color" },
1201
+ { id: "paragraphSpacing", group: "typography", label: "Paragraph spacing", control: "range", min: 0, max: 48, unit: "px" },
1202
+ { id: "listSpacing", group: "typography", label: "List spacing", control: "range", min: 0, max: 48, unit: "px" },
1203
+ { id: "listIndent", group: "typography", label: "List indent", control: "range", min: 0, max: 64, unit: "px" },
1204
+ { id: "linkColor", group: "typography", label: "Link", control: "color" },
1205
+ { id: "linkDecoration", group: "typography", label: "Link decoration", control: "select", options: [{ label: "Underline", value: LINK_DECORATIONS.underline }, { label: "None", value: LINK_DECORATIONS.none }] }
1206
+ ] }
1207
+ ];
1208
+
1209
+ // src/inspector/components/InspectorSection.tsx
1210
+ import { useState as useState2 } from "react";
1211
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1212
+ function InspectorSection({ title, children }) {
1213
+ const [open, setOpen] = useState2(true);
1214
+ return /* @__PURE__ */ jsxs4("section", { className: "ree-inspector-section", children: [
1215
+ /* @__PURE__ */ jsxs4(
1216
+ "button",
1217
+ {
1218
+ type: "button",
1219
+ className: "ree-inspector-section-toggle",
1220
+ "aria-expanded": open,
1221
+ onClick: () => setOpen((current) => !current),
1222
+ children: [
1223
+ /* @__PURE__ */ jsx5("span", { children: title }),
1224
+ /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("path", { d: "M4 6.25 8 10.25 12 6.25", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
1225
+ ]
1226
+ }
1227
+ ),
1228
+ open ? /* @__PURE__ */ jsx5("div", { className: "ree-inspector-section-body", children }) : null
1229
+ ] });
1230
+ }
1231
+
1232
+ // src/inspector/components/StyleFieldStack.tsx
1233
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1234
+ function StyleFieldStack({ fields, renderField }) {
1235
+ const colors = fields.filter((field) => field.control === "color");
1236
+ const rest = fields.filter((field) => field.control !== "color");
1237
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
1238
+ colors.length > 0 ? /* @__PURE__ */ jsx6("div", { className: "ree-color-grid", children: colors.map(renderField) }) : null,
1239
+ rest.map(renderField)
1240
+ ] });
1241
+ }
1242
+
1243
+ // src/inspector/StyleControl.tsx
1244
+ import { useId } from "react";
1245
+
1246
+ // src/inspector/constants.ts
1247
+ var HEX_COLOR = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
1248
+ var TEXT_ALIGN_OPTIONS = [
1249
+ { value: HORIZONTAL_ALIGNS.left, label: "Left", glyph: "\u21E4" },
1250
+ { value: HORIZONTAL_ALIGNS.center, label: "Center", glyph: "\u21D4" },
1251
+ { value: HORIZONTAL_ALIGNS.right, label: "Right", glyph: "\u21E5" }
1252
+ ];
1253
+ var GLOBAL_FONT_WEIGHTS = [
1254
+ { label: "Regular", value: FONT_WEIGHTS.normal },
1255
+ { label: "Bold", value: FONT_WEIGHTS.bold }
1256
+ ];
1257
+ var SPACING_SIDES = ["Top", "Right", "Bottom", "Left"];
1258
+ var CORNER_SIDES = ["Top left", "Top right", "Bottom right", "Bottom left"];
1259
+ var SPACING_MAX = 120;
1260
+
1261
+ // src/inspector/components/AlignControl.tsx
1262
+ import { jsx as jsx7 } from "react/jsx-runtime";
1263
+ function AlignControl({ value, onChange }) {
1264
+ return /* @__PURE__ */ jsx7("div", { className: "ree-align-row", children: TEXT_ALIGN_OPTIONS.map((option) => /* @__PURE__ */ jsx7(
1265
+ "button",
1266
+ {
1267
+ type: "button",
1268
+ className: `ree-align-btn${value === option.value ? " is-active" : ""}`,
1269
+ onClick: () => onChange(option.value),
1270
+ title: option.label,
1271
+ children: option.glyph
1272
+ },
1273
+ option.value
1274
+ )) });
1275
+ }
1276
+
1277
+ // src/inspector/utils.ts
1278
+ function colorInputValue(value, fallback = "#000000") {
1279
+ return HEX_COLOR.test(value) ? value : fallback;
1280
+ }
1281
+ function styleReset(stored, restore, inherit) {
1282
+ if (inherit) return { resetTo: "", show: stored !== "" };
1283
+ return { resetTo: restore, show: stored !== "" && stored !== restore };
1284
+ }
1285
+ function inheritedStyleValue(id, global) {
1286
+ if (!id.startsWith("typography.") && !id.startsWith("element.")) return;
1287
+ switch (id.split(".")[1]) {
1288
+ case "fontFamily":
1289
+ return global.fontFamily;
1290
+ case "fontSize":
1291
+ return global.defaultFontSize;
1292
+ case "fontWeight":
1293
+ return global.fontWeight;
1294
+ case "lineHeight":
1295
+ return global.defaultLineHeight;
1296
+ case "color":
1297
+ return global.textColor;
1298
+ case "textAlign":
1299
+ return global.textAlign;
1300
+ case "paragraphSpacing":
1301
+ return global.paragraphSpacing;
1302
+ case "listSpacing":
1303
+ return global.listSpacing;
1304
+ case "listIndent":
1305
+ return global.listIndent;
1306
+ case "linkColor":
1307
+ return global.linkColor;
1308
+ case "linkDecoration":
1309
+ return global.linkDecoration;
1310
+ }
1311
+ }
1312
+
1313
+ // src/inspector/components/ColorControl.tsx
1314
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1315
+ function ColorControl({ value, fallback = "#000000", label = "Color", onChange }) {
1316
+ const actual = value || fallback;
1317
+ const empty = !actual || actual === "transparent" || !HEX_COLOR.test(actual);
1318
+ return /* @__PURE__ */ jsxs6("label", { className: "ree-color-chip", children: [
1319
+ /* @__PURE__ */ jsxs6("span", { className: "ree-color-swatch", children: [
1320
+ /* @__PURE__ */ jsx8(
1321
+ "span",
1322
+ {
1323
+ className: `ree-color-swatch-face${empty ? " is-empty" : ""}`,
1324
+ style: empty ? void 0 : { background: actual }
1325
+ }
1326
+ ),
1327
+ /* @__PURE__ */ jsx8(
1328
+ "input",
1329
+ {
1330
+ type: "color",
1331
+ className: "ree-color-native",
1332
+ "aria-label": label,
1333
+ value: colorInputValue(actual),
1334
+ onChange: (event) => onChange(event.target.value)
1335
+ }
1336
+ )
1337
+ ] }),
1338
+ /* @__PURE__ */ jsx8("span", { className: "ree-color-chip-label", children: label })
1339
+ ] });
1340
+ }
1341
+
1342
+ // src/inspector/components/CssValueInput.tsx
1343
+ import { useEffect, useState as useState3 } from "react";
1344
+ import { jsx as jsx9 } from "react/jsx-runtime";
1345
+ function CssValueInput({ label, value, placeholder: placeholder2, onChange }) {
1346
+ const [draft, setDraft] = useState3(value);
1347
+ useEffect(() => setDraft(value), [value]);
1348
+ return /* @__PURE__ */ jsx9(
1349
+ "input",
1350
+ {
1351
+ className: "ree-input",
1352
+ "aria-label": label,
1353
+ value: draft,
1354
+ placeholder: placeholder2 != null ? placeholder2 : "0px",
1355
+ onChange: (event) => setDraft(event.target.value),
1356
+ onBlur: () => {
1357
+ onChange(draft.trim());
1358
+ setDraft(value);
1359
+ },
1360
+ onKeyDown: (event) => {
1361
+ if (event.key === "Enter") event.currentTarget.blur();
1362
+ if (event.key === "Escape") {
1363
+ setDraft(value);
1364
+ event.stopPropagation();
1365
+ }
1366
+ }
1367
+ }
1368
+ );
1369
+ }
1370
+
1371
+ // src/inspector/components/RangeControl.tsx
1372
+ import { jsx as jsx10 } from "react/jsx-runtime";
1373
+ function RangeControl({
1374
+ id,
1375
+ label,
1376
+ value,
1377
+ min = 0,
1378
+ max = 100,
1379
+ step = 1,
1380
+ onChange
1381
+ }) {
1382
+ const progress = max > min ? Math.min(100, Math.max(0, (value - min) / (max - min) * 100)) : 0;
1383
+ return /* @__PURE__ */ jsx10(
1384
+ "input",
1385
+ {
1386
+ id,
1387
+ "aria-label": label,
1388
+ type: "range",
1389
+ className: "ree-range",
1390
+ style: { "--ree-range-progress": `${progress}%` },
1391
+ value,
1392
+ min,
1393
+ max,
1394
+ step,
1395
+ onChange: (event) => onChange(Number(event.target.value))
1396
+ }
1397
+ );
1398
+ }
1399
+
1400
+ // src/inspector/components/LengthControl.tsx
1401
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1402
+ function bounds(unit, min, max, step) {
1403
+ if (unit === "%") return { min: 0, max: 100, step: 1 };
1404
+ return { min, max, step };
1405
+ }
1406
+ function snap(value, step) {
1407
+ var _a;
1408
+ const places = ((_a = String(step).split(".")[1]) != null ? _a : "").length;
1409
+ return Number((Math.round(value / step) * step).toFixed(places));
1410
+ }
1411
+ function LengthControl({
1412
+ id,
1413
+ label,
1414
+ amount,
1415
+ unit,
1416
+ units,
1417
+ min = 0,
1418
+ max = 200,
1419
+ step = 1,
1420
+ onChange
1421
+ }) {
1422
+ const range = bounds(unit, min, max, step);
1423
+ const shown = Math.min(range.max, Math.max(range.min, amount));
1424
+ const commit = (nextAmount, nextUnit) => {
1425
+ if (nextUnit === "auto") {
1426
+ onChange("auto");
1427
+ return;
1428
+ }
1429
+ const nextRange = bounds(nextUnit, min, max, step);
1430
+ const snapped = snap(Math.min(nextRange.max, Math.max(nextRange.min, nextAmount)), nextRange.step);
1431
+ onChange(formatCssLength(snapped, nextUnit));
1432
+ };
1433
+ return /* @__PURE__ */ jsxs7("div", { className: "ree-range-row", children: [
1434
+ unit === "auto" ? null : /* @__PURE__ */ jsx11(RangeControl, { id, label, value: shown, min: range.min, max: range.max, step: range.step, onChange: (next) => commit(next, unit) }),
1435
+ units.length > 1 ? /* @__PURE__ */ jsx11(
1436
+ "select",
1437
+ {
1438
+ className: "ree-unit-select",
1439
+ "aria-label": `${label} unit`,
1440
+ value: unit,
1441
+ onChange: (event) => {
1442
+ const nextUnit = event.target.value;
1443
+ if (nextUnit === unit) return;
1444
+ const seed = unit === "auto" ? nextUnit === "%" ? 100 : Math.min(max, 160) : amount;
1445
+ commit(seed, nextUnit);
1446
+ },
1447
+ children: units.map((option) => /* @__PURE__ */ jsx11("option", { value: option, children: option }, option))
1448
+ }
1449
+ ) : null
1450
+ ] });
1451
+ }
1452
+
1453
+ // src/inspector/components/ResetButton.tsx
1454
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1455
+ function ResetButton({ label, onClick }) {
1456
+ return /* @__PURE__ */ jsx12("button", { type: "button", className: "ree-reset", "aria-label": label, title: label, onClick, children: /* @__PURE__ */ jsxs8("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: [
1457
+ /* @__PURE__ */ jsx12("path", { d: "M13 8a5 5 0 1 1-1.4-3.45", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" }),
1458
+ /* @__PURE__ */ jsx12("path", { d: "M12.2 2.4v2.7h-2.7", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })
1459
+ ] }) });
1460
+ }
1461
+
1462
+ // src/inspector/components/SelectControl.tsx
1463
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1464
+ function SelectControl({ label, value, options, onChange }) {
1465
+ return /* @__PURE__ */ jsxs9("div", { className: "ree-select", children: [
1466
+ /* @__PURE__ */ jsx13(
1467
+ "select",
1468
+ {
1469
+ "aria-label": label,
1470
+ className: "ree-input",
1471
+ value,
1472
+ onChange: (event) => onChange(event.target.value),
1473
+ children: options.map((option) => /* @__PURE__ */ jsx13("option", { value: option.value, children: option.label }, option.value))
1474
+ }
1475
+ ),
1476
+ /* @__PURE__ */ jsx13("span", { className: "ree-select-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", width: "16", height: "16", fill: "none", children: /* @__PURE__ */ jsx13(
1477
+ "path",
1478
+ {
1479
+ d: "M4 6.25 8 10.25 12 6.25",
1480
+ stroke: "currentColor",
1481
+ strokeWidth: "1.5",
1482
+ strokeLinecap: "round",
1483
+ strokeLinejoin: "round"
1484
+ }
1485
+ ) }) })
1486
+ ] });
1487
+ }
1488
+
1489
+ // src/inspector/components/SpacingControl.tsx
1490
+ import { useState as useState4 } from "react";
1491
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1492
+ function sideAmount(value, max) {
1493
+ return Math.min(max, Math.max(0, readCssLength(value, ["px"]).amount));
1494
+ }
1495
+ function SpacingControl({
1496
+ label,
1497
+ value,
1498
+ max = SPACING_MAX,
1499
+ sides: sideLabels = SPACING_SIDES,
1500
+ linkLabel = "Link sides",
1501
+ uniformLabel,
1502
+ expand = spacingSides,
1503
+ onChange
1504
+ }) {
1505
+ var _a;
1506
+ const sides = expand(value);
1507
+ const equal = sides.every((side) => sideAmount(side, max) === sideAmount(sides[0], max));
1508
+ const [independent, setIndependent] = useState4(!equal);
1509
+ const slider = (sideLabel, sideValue, commit) => {
1510
+ const amount = sideAmount(sideValue, max);
1511
+ return /* @__PURE__ */ jsxs10("div", { className: "ree-field", children: [
1512
+ /* @__PURE__ */ jsxs10("div", { className: "ree-field-label", children: [
1513
+ /* @__PURE__ */ jsx14("span", { children: sideLabel }),
1514
+ /* @__PURE__ */ jsxs10("span", { className: "ree-field-value", children: [
1515
+ amount,
1516
+ "px"
1517
+ ] })
1518
+ ] }),
1519
+ /* @__PURE__ */ jsx14(RangeControl, { label: sideLabel, value: amount, min: 0, max, step: 1, onChange: (next) => commit(`${next}px`) })
1520
+ ] }, sideLabel);
1521
+ };
1522
+ return /* @__PURE__ */ jsxs10("div", { className: "ree-spacing-control", children: [
1523
+ /* @__PURE__ */ jsxs10("label", { className: "ree-spacing-link", children: [
1524
+ /* @__PURE__ */ jsx14(
1525
+ "input",
1526
+ {
1527
+ type: "checkbox",
1528
+ checked: !independent && equal,
1529
+ onChange: (event) => {
1530
+ var _a2;
1531
+ setIndependent(!event.target.checked);
1532
+ if (event.target.checked) onChange(`${sideAmount((_a2 = sides[0]) != null ? _a2 : "0px", max)}px`);
1533
+ }
1534
+ }
1535
+ ),
1536
+ linkLabel
1537
+ ] }),
1538
+ !independent && equal ? slider(uniformLabel != null ? uniformLabel : `${label} all sides`, (_a = sides[0]) != null ? _a : "0px", onChange) : /* @__PURE__ */ jsx14("div", { className: "ree-spacing-grid", children: sideLabels.map((side, index) => {
1539
+ var _a2;
1540
+ return slider(side, (_a2 = sides[index]) != null ? _a2 : "0px", (next) => {
1541
+ const updated = [...sides];
1542
+ updated[index] = next;
1543
+ onChange(updated.join(" "));
1544
+ });
1545
+ }) })
1546
+ ] });
1547
+ }
1548
+
1549
+ // src/inspector/StyleControl.tsx
1550
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1551
+ function lengthUnits(config) {
1552
+ var _a;
1553
+ if ((_a = config.units) == null ? void 0 : _a.length) return config.units;
1554
+ if (config.unit) return [config.unit];
1555
+ return [];
1556
+ }
1557
+ function StyleControl({ config, value, fallback, resetLabel = "Reset", resetTo, showReset, onChange }) {
1558
+ var _a, _b;
1559
+ const id = useId();
1560
+ const units = lengthUnits(config);
1561
+ const stored = value === "auto" && units.length > 0 && !units.includes("auto") ? "" : value;
1562
+ const actual = stored || fallback || config.defaultValue || "";
1563
+ const numeric = config.control === "range" || config.control === "number";
1564
+ const parsed = numeric ? readCssLength(actual, units) : null;
1565
+ const options = (_a = config.options) != null ? _a : [];
1566
+ const selectOptions = actual && !options.some((option) => option.value === actual) ? [{ label: actual, value: actual }, ...options] : options;
1567
+ const reset = (showReset != null ? showReset : Boolean(value)) ? /* @__PURE__ */ jsx15(ResetButton, { label: resetLabel, onClick: () => onChange(resetTo != null ? resetTo : "") }) : null;
1568
+ if (config.control === "color") {
1569
+ return /* @__PURE__ */ jsxs11("div", { className: "ree-color-chip-row", children: [
1570
+ /* @__PURE__ */ jsx15(ColorControl, { label: config.label, value: actual, onChange }),
1571
+ reset
1572
+ ] });
1573
+ }
1574
+ return /* @__PURE__ */ jsxs11("div", { className: "ree-field", children: [
1575
+ /* @__PURE__ */ jsxs11("div", { className: "ree-field-label", children: [
1576
+ /* @__PURE__ */ jsx15("label", { htmlFor: numeric ? id : void 0, children: config.label }),
1577
+ /* @__PURE__ */ jsxs11("span", { className: "ree-field-meta", children: [
1578
+ parsed ? /* @__PURE__ */ jsx15("span", { className: "ree-field-value", children: formatCssLength(parsed.amount, parsed.unit) }) : null,
1579
+ reset
1580
+ ] })
1581
+ ] }),
1582
+ config.control === "alignment" ? /* @__PURE__ */ jsx15(AlignControl, { value: actual, onChange }) : null,
1583
+ config.control === "select" ? /* @__PURE__ */ jsx15(SelectControl, { label: config.label, value: actual, options: selectOptions, onChange }) : null,
1584
+ numeric && parsed ? /* @__PURE__ */ jsx15(
1585
+ LengthControl,
1586
+ {
1587
+ id,
1588
+ label: config.label,
1589
+ amount: parsed.amount,
1590
+ unit: parsed.unit,
1591
+ units,
1592
+ min: config.min,
1593
+ max: config.max,
1594
+ step: config.step,
1595
+ onChange
1596
+ }
1597
+ ) : null,
1598
+ config.control === "text" ? /* @__PURE__ */ jsx15(
1599
+ CssValueInput,
1600
+ {
1601
+ label: config.label,
1602
+ value: actual,
1603
+ placeholder: config.placeholder,
1604
+ onChange: (next) => {
1605
+ if (!next || cssLength(next, "")) onChange(next);
1606
+ }
1607
+ }
1608
+ ) : null,
1609
+ config.control === "spacing" ? /* @__PURE__ */ jsx15(SpacingControl, { label: config.label, value: actual, onChange }) : null,
1610
+ config.control === "corners" ? /* @__PURE__ */ jsx15(
1611
+ SpacingControl,
1612
+ {
1613
+ label: config.label,
1614
+ value: actual,
1615
+ max: (_b = config.max) != null ? _b : 48,
1616
+ sides: CORNER_SIDES,
1617
+ linkLabel: "Link corners",
1618
+ uniformLabel: "All corners",
1619
+ expand: cornerRadii,
1620
+ onChange
1621
+ }
1622
+ ) : null
1623
+ ] });
1624
+ }
1625
+
1626
+ // src/inspector/GlobalFields.tsx
1627
+ import { jsx as jsx16 } from "react/jsx-runtime";
1628
+ function GlobalFields({ global, onChange }) {
1629
+ const settings = resolveEmailGlobal(global);
1630
+ const renderField = (field) => {
1631
+ const key = field.id;
1632
+ const current = String(settings[key]);
1633
+ const initial = String(DEFAULT_EMAIL_GLOBAL[key]);
1634
+ return /* @__PURE__ */ jsx16(
1635
+ StyleControl,
1636
+ {
1637
+ config: field,
1638
+ value: current === initial ? "" : current,
1639
+ fallback: initial,
1640
+ resetLabel: "Reset",
1641
+ onChange: (value) => onChange({
1642
+ [key]: value === "" ? DEFAULT_EMAIL_GLOBAL[key] : key === "contentWidth" ? parseFloat(value) : value
1643
+ })
1644
+ },
1645
+ key
1646
+ );
1647
+ };
1648
+ return /* @__PURE__ */ jsx16("div", { className: "ree-inspector ree-inspector-global", children: GLOBAL_FIELD_SECTIONS.map((section) => /* @__PURE__ */ jsx16(InspectorSection, { title: section.title, children: /* @__PURE__ */ jsx16(StyleFieldStack, { fields: section.fields, renderField }) }, section.title)) });
1649
+ }
1650
+
1651
+ // src/constants/style-groups.ts
1652
+ var STYLE_GROUPS = [
1653
+ { group: "general", label: "General" },
1654
+ { group: "dimension", label: "Spacing" },
1655
+ { group: "typography", label: "Typography" },
1656
+ { group: "decorations", label: "Decoration" }
1657
+ ];
1658
+
1659
+ // src/inspector/FieldControl.tsx
1660
+ import { useRef as useRef3 } from "react";
1661
+
1662
+ // src/inspector/HtmlCodeEditor.tsx
1663
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
1664
+ import { EditorState as EditorState2 } from "@codemirror/state";
1665
+ import { EditorView as EditorView2 } from "@codemirror/view";
1666
+
1667
+ // src/inspector/html-editor.ts
1668
+ import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
1669
+ import {
1670
+ HighlightStyle,
1671
+ LRLanguage,
1672
+ LanguageSupport,
1673
+ bracketMatching,
1674
+ indentOnInput,
1675
+ indentUnit,
1676
+ syntaxHighlighting
1677
+ } from "@codemirror/language";
1678
+ import { parser as htmlParser } from "@lezer/html";
1679
+ import { EditorState } from "@codemirror/state";
1680
+ import {
1681
+ Decoration,
1682
+ EditorView,
1683
+ MatchDecorator,
1684
+ ViewPlugin,
1685
+ drawSelection,
1686
+ highlightActiveLine,
1687
+ highlightActiveLineGutter,
1688
+ highlightSpecialChars,
1689
+ keymap,
1690
+ lineNumbers,
1691
+ placeholder
1692
+ } from "@codemirror/view";
1693
+ import { tags } from "@lezer/highlight";
1694
+ var htmlLanguage = LRLanguage.define({
1695
+ name: "html",
1696
+ parser: htmlParser.configure({ dialect: "selfClosing" }),
1697
+ languageData: {
1698
+ commentTokens: { block: { open: "<!--", close: "-->" } }
1699
+ }
1700
+ });
1701
+ var htmlHighlightStyle = HighlightStyle.define([
1702
+ { tag: tags.content, color: "#1f2328" },
1703
+ { tag: tags.comment, color: "#6e7781", fontStyle: "italic" },
1704
+ { tag: tags.string, color: "#0a3069" },
1705
+ { tag: tags.documentMeta, color: "#8250df" },
1706
+ { tag: tags.processingInstruction, color: "#8250df" },
1707
+ { tag: tags.angleBracket, color: "#6e7781" },
1708
+ { tag: tags.tagName, color: "#116329" },
1709
+ { tag: tags.attributeName, color: "#0550ae" },
1710
+ { tag: tags.definitionOperator, color: "#6e7781" },
1711
+ { tag: tags.attributeValue, color: "#0a3069" },
1712
+ { tag: tags.character, color: "#0550ae" },
1713
+ { tag: tags.invalid, color: "#cf222e" }
1714
+ ]);
1715
+ var htmlEditorTheme = EditorView.theme(
1716
+ {
1717
+ "&": {
1718
+ color: "#1f2328",
1719
+ backgroundColor: "#f6f8fa",
1720
+ fontSize: "13px"
1721
+ },
1722
+ "&.cm-focused": {
1723
+ outline: "none"
1724
+ },
1725
+ ".cm-scroller": {
1726
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
1727
+ lineHeight: "1.55"
1728
+ },
1729
+ ".cm-content": {
1730
+ padding: "8px 0",
1731
+ caretColor: "#1f2328"
1732
+ },
1733
+ ".cm-line": {
1734
+ padding: "0 12px"
1735
+ },
1736
+ ".cm-gutters": {
1737
+ backgroundColor: "#f6f8fa",
1738
+ color: "#8c959f",
1739
+ borderRight: "1px solid #e3e3e5"
1740
+ },
1741
+ ".cm-activeLine": {
1742
+ backgroundColor: "rgba(9, 105, 218, 0.06)"
1743
+ },
1744
+ ".cm-activeLineGutter": {
1745
+ backgroundColor: "rgba(9, 105, 218, 0.08)",
1746
+ color: "#1f2328"
1747
+ },
1748
+ ".cm-placeholder": {
1749
+ color: "#8c959f"
1750
+ }
1751
+ },
1752
+ { dark: false }
1753
+ );
1754
+ var templateDecoration = Decoration.mark({ class: "ree-cm-template" });
1755
+ var templateMatcher = new MatchDecorator({
1756
+ regexp: /\{\{[^{}]*\}\}/g,
1757
+ decoration: templateDecoration
1758
+ });
1759
+ var templateHighlight = ViewPlugin.fromClass(
1760
+ class {
1761
+ constructor(view) {
1762
+ this.decorations = templateMatcher.createDeco(view);
1763
+ }
1764
+ update(update) {
1765
+ this.decorations = templateMatcher.updateDeco(update, this.decorations);
1766
+ }
1767
+ },
1768
+ { decorations: (plugin) => plugin.decorations }
1769
+ );
1770
+ function createHtmlEditorExtensions(placeholderText, onChange) {
1771
+ return [
1772
+ lineNumbers(),
1773
+ highlightActiveLine(),
1774
+ highlightActiveLineGutter(),
1775
+ drawSelection(),
1776
+ highlightSpecialChars(),
1777
+ history(),
1778
+ indentOnInput(),
1779
+ bracketMatching(),
1780
+ EditorState.tabSize.of(2),
1781
+ indentUnit.of(" "),
1782
+ EditorView.lineWrapping,
1783
+ EditorView.contentAttributes.of({
1784
+ spellcheck: "false",
1785
+ autocapitalize: "off",
1786
+ autocorrect: "off",
1787
+ "aria-label": "HTML"
1788
+ }),
1789
+ htmlEditorTheme,
1790
+ syntaxHighlighting(htmlHighlightStyle),
1791
+ templateHighlight,
1792
+ new LanguageSupport(htmlLanguage),
1793
+ keymap.of([...defaultKeymap, ...historyKeymap]),
1794
+ placeholderText ? placeholder(placeholderText) : [],
1795
+ EditorView.updateListener.of((update) => {
1796
+ if (update.docChanged) onChange(update.state.doc.toString());
1797
+ })
1798
+ ];
1799
+ }
1800
+
1801
+ // src/inspector/HtmlCodeEditor.tsx
1802
+ import { jsx as jsx17 } from "react/jsx-runtime";
1803
+ var LINE_HEIGHT2 = 22;
1804
+ function HtmlCodeEditor({
1805
+ value,
1806
+ placeholder: placeholder2,
1807
+ rows = 10,
1808
+ onChange
1809
+ }) {
1810
+ const hostRef = useRef2(null);
1811
+ const viewRef = useRef2(null);
1812
+ const onChangeRef = useRef2(onChange);
1813
+ onChangeRef.current = onChange;
1814
+ useEffect2(() => {
1815
+ const parent = hostRef.current;
1816
+ if (!parent) return;
1817
+ const view = new EditorView2({
1818
+ parent,
1819
+ state: EditorState2.create({
1820
+ doc: value,
1821
+ extensions: createHtmlEditorExtensions(placeholder2 != null ? placeholder2 : "", (next) => {
1822
+ onChangeRef.current(next);
1823
+ })
1824
+ })
1825
+ });
1826
+ viewRef.current = view;
1827
+ return () => {
1828
+ view.destroy();
1829
+ viewRef.current = null;
1830
+ };
1831
+ }, []);
1832
+ useEffect2(() => {
1833
+ const view = viewRef.current;
1834
+ if (!view) return;
1835
+ const current = view.state.doc.toString();
1836
+ if (current === value) return;
1837
+ view.dispatch({
1838
+ changes: { from: 0, to: current.length, insert: value }
1839
+ });
1840
+ }, [value]);
1841
+ const minHeight = Math.max(rows, 8) * LINE_HEIGHT2;
1842
+ return /* @__PURE__ */ jsx17(
1843
+ "div",
1844
+ {
1845
+ ref: hostRef,
1846
+ className: "ree-html-editor",
1847
+ style: { "--ree-html-editor-min": `${minHeight}px` }
1848
+ }
1849
+ );
1850
+ }
1851
+
1852
+ // src/inspector/VariablesDropdown.tsx
1853
+ import { useState as useState5 } from "react";
1854
+ import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1855
+ function VariablesDropdown({ variables, onInsert }) {
1856
+ const [open, setOpen] = useState5(false);
1857
+ if (variables.length === 0) return null;
1858
+ return /* @__PURE__ */ jsxs12("div", { className: "ree-var-wrap", children: [
1859
+ /* @__PURE__ */ jsx18(
1860
+ "button",
1861
+ {
1862
+ type: "button",
1863
+ className: "ree-var-btn",
1864
+ onClick: () => setOpen((current) => !current),
1865
+ title: "Insert variable",
1866
+ children: "{ }"
1867
+ }
1868
+ ),
1869
+ open ? /* @__PURE__ */ jsx18("div", { className: "ree-var-menu", children: variables.map((variable) => /* @__PURE__ */ jsxs12(
1870
+ "button",
1871
+ {
1872
+ type: "button",
1873
+ className: "ree-var-item",
1874
+ onClick: () => {
1875
+ onInsert(variable.name);
1876
+ setOpen(false);
1877
+ },
1878
+ children: [
1879
+ /* @__PURE__ */ jsx18("span", { className: "ree-var-name", children: variable.name }),
1880
+ variable.description ? /* @__PURE__ */ jsx18("span", { className: "ree-var-label", children: variable.description }) : null
1881
+ ]
1882
+ },
1883
+ variable.name
1884
+ )) }) : null
1885
+ ] });
1886
+ }
1887
+
1888
+ // src/inspector/FieldControl.tsx
1889
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1890
+ function FieldControl({
1891
+ field,
1892
+ value,
1893
+ variables,
1894
+ onChange
1895
+ }) {
1896
+ var _a, _b, _c, _d, _e;
1897
+ const ref = useRef3(null);
1898
+ const stringValue = value == null ? "" : String(value);
1899
+ const insertVariable = (token) => {
1900
+ var _a2, _b2;
1901
+ const element = ref.current;
1902
+ if (element && "selectionStart" in element) {
1903
+ const start = (_a2 = element.selectionStart) != null ? _a2 : stringValue.length;
1904
+ const end = (_b2 = element.selectionEnd) != null ? _b2 : stringValue.length;
1905
+ onChange(stringValue.slice(0, start) + token + stringValue.slice(end));
1906
+ return;
1907
+ }
1908
+ onChange(stringValue + token);
1909
+ };
1910
+ const handleChange = (event) => onChange(event.target.value);
1911
+ const label = /* @__PURE__ */ jsxs13("div", { className: "ree-field-label", children: [
1912
+ /* @__PURE__ */ jsx19("span", { children: field.label }),
1913
+ field.type === "text" || field.type === "textarea" || field.type === "url" ? /* @__PURE__ */ jsx19(VariablesDropdown, { variables, onInsert: insertVariable }) : null
1914
+ ] });
1915
+ if (field.type === "html") {
1916
+ return /* @__PURE__ */ jsxs13("div", { className: "ree-field", children: [
1917
+ /* @__PURE__ */ jsx19("div", { className: "ree-field-label", children: /* @__PURE__ */ jsx19("span", { children: field.label }) }),
1918
+ /* @__PURE__ */ jsx19(
1919
+ HtmlCodeEditor,
1920
+ {
1921
+ value: stringValue,
1922
+ placeholder: field.placeholder,
1923
+ rows: field.rows,
1924
+ onChange
1925
+ }
1926
+ )
1927
+ ] });
1928
+ }
1929
+ if (field.type === "textarea") {
1930
+ return /* @__PURE__ */ jsxs13("div", { className: "ree-field", children: [
1931
+ label,
1932
+ /* @__PURE__ */ jsx19(
1933
+ "textarea",
1934
+ {
1935
+ ref: (element) => {
1936
+ ref.current = element;
1937
+ },
1938
+ className: "ree-input ree-textarea",
1939
+ value: stringValue,
1940
+ placeholder: field.placeholder,
1941
+ rows: (_a = field.rows) != null ? _a : 4,
1942
+ onChange: handleChange
1943
+ }
1944
+ )
1945
+ ] });
1946
+ }
1947
+ if (field.type === "select") {
1948
+ return /* @__PURE__ */ jsxs13("div", { className: "ree-field", children: [
1949
+ /* @__PURE__ */ jsx19("label", { className: "ree-field-label", children: field.label }),
1950
+ /* @__PURE__ */ jsx19(
1951
+ SelectControl,
1952
+ {
1953
+ value: stringValue,
1954
+ options: (_b = field.options) != null ? _b : [],
1955
+ onChange
1956
+ }
1957
+ )
1958
+ ] });
1959
+ }
1960
+ if (field.type === "color") {
1961
+ return /* @__PURE__ */ jsx19("div", { className: "ree-color-chip-row", children: /* @__PURE__ */ jsx19(ColorControl, { label: field.label, value: stringValue, onChange: (next) => onChange(next) }) });
1962
+ }
1963
+ if (field.type === "number") {
1964
+ const amount = Number(value);
1965
+ const max = (_c = field.max) != null ? _c : 200;
1966
+ const shown = Math.min(max, Math.max((_d = field.min) != null ? _d : 0, Number.isFinite(amount) ? amount : (_e = field.min) != null ? _e : 0));
1967
+ return /* @__PURE__ */ jsxs13("div", { className: "ree-field", children: [
1968
+ /* @__PURE__ */ jsxs13("div", { className: "ree-field-label", children: [
1969
+ /* @__PURE__ */ jsx19("span", { children: field.label }),
1970
+ /* @__PURE__ */ jsxs13("span", { className: "ree-field-value", children: [
1971
+ shown,
1972
+ "px"
1973
+ ] })
1974
+ ] }),
1975
+ /* @__PURE__ */ jsx19(RangeControl, { label: field.label, value: shown, min: field.min, max, step: field.step, onChange })
1976
+ ] });
1977
+ }
1978
+ return /* @__PURE__ */ jsxs13("div", { className: "ree-field", children: [
1979
+ label,
1980
+ /* @__PURE__ */ jsx19(
1981
+ "input",
1982
+ {
1983
+ ref: (element) => {
1984
+ ref.current = element;
1985
+ },
1986
+ className: "ree-input",
1987
+ "aria-label": field.label,
1988
+ min: field.min,
1989
+ max: field.max,
1990
+ step: field.step,
1991
+ value: stringValue,
1992
+ placeholder: field.placeholder,
1993
+ type: "text",
1994
+ onChange: handleChange
1995
+ }
1996
+ )
1997
+ ] });
1998
+ }
1999
+
2000
+ // src/inspector/InspectorFields.tsx
2001
+ import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
2002
+ function InspectorFields({
2003
+ block,
2004
+ definition,
2005
+ variables,
2006
+ global,
2007
+ onCommitField,
2008
+ onCommitStyle
2009
+ }) {
2010
+ var _a, _b, _c;
2011
+ const contentFields = (_a = definition.fields) != null ? _a : [];
2012
+ const styleFields = (_b = definition.styleFields) != null ? _b : [];
2013
+ const renderStyle = (field) => {
2014
+ const stored = readStyleValue(block.style, field.id);
2015
+ const inherited = inheritedStyleValue(field.id, global);
2016
+ const restore = readStyleValue(definition.defaultStyle, field.id) || field.defaultValue || "";
2017
+ const reset = styleReset(stored, restore, inherited !== void 0);
2018
+ return /* @__PURE__ */ jsx20(
2019
+ StyleControl,
2020
+ {
2021
+ config: field,
2022
+ value: stored,
2023
+ fallback: inherited,
2024
+ resetTo: reset.resetTo,
2025
+ showReset: reset.show,
2026
+ resetLabel: inherited !== void 0 ? "Inherit global" : "Reset",
2027
+ onChange: (value) => onCommitStyle(block.id, field.id, value)
2028
+ },
2029
+ field.id
2030
+ );
2031
+ };
2032
+ const sections = STYLE_GROUPS.map((group) => __spreadProps(__spreadValues({}, group), {
2033
+ fields: styleFields.filter((field) => field.group === group.group)
2034
+ }));
2035
+ const general = sections.find((section) => section.group === "general");
2036
+ const showGeneral = contentFields.length > 0 || ((_c = general == null ? void 0 : general.fields.length) != null ? _c : 0) > 0;
2037
+ return /* @__PURE__ */ jsxs14("div", { className: "ree-inspector", children: [
2038
+ /* @__PURE__ */ jsxs14("div", { className: "ree-inspector-head", children: [
2039
+ /* @__PURE__ */ jsx20(BlockIcon, { icon: definition.icon, className: "ree-inspector-icon" }),
2040
+ /* @__PURE__ */ jsx20("span", { className: "ree-inspector-title", children: definition.title })
2041
+ ] }),
2042
+ showGeneral ? /* @__PURE__ */ jsxs14(InspectorSection, { title: "General", children: [
2043
+ contentFields.map((field) => /* @__PURE__ */ jsx20(
2044
+ FieldControl,
2045
+ {
2046
+ field,
2047
+ value: block.fields[field.name],
2048
+ variables,
2049
+ onChange: (value) => onCommitField(block.id, field.name, value)
2050
+ },
2051
+ field.name
2052
+ )),
2053
+ general ? /* @__PURE__ */ jsx20(StyleFieldStack, { fields: general.fields, renderField: renderStyle }) : null
2054
+ ] }) : null,
2055
+ sections.filter((section) => section.group !== "general" && section.fields.length > 0).map((section) => /* @__PURE__ */ jsx20(InspectorSection, { title: section.label, children: /* @__PURE__ */ jsx20(StyleFieldStack, { fields: section.fields, renderField: renderStyle }) }, section.group))
2056
+ ] });
2057
+ }
2058
+
2059
+ // src/inspector/Inspector.tsx
2060
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
2061
+ function Inspector({
2062
+ panel,
2063
+ global,
2064
+ block,
2065
+ definition,
2066
+ variables,
2067
+ onPanelChange,
2068
+ onCommitGlobal,
2069
+ onCommitField,
2070
+ onCommitStyle
2071
+ }) {
2072
+ const showBlock = panel === "block" && block && definition;
2073
+ return /* @__PURE__ */ jsxs15("aside", { className: "ree-col ree-col-right", children: [
2074
+ /* @__PURE__ */ jsx21("div", { className: "ree-col-head ree-inspector-bar", children: /* @__PURE__ */ jsxs15("div", { className: "ree-inspector-tabs", role: "group", "aria-label": "Inspector panel", children: [
2075
+ /* @__PURE__ */ jsx21(
2076
+ "button",
2077
+ {
2078
+ type: "button",
2079
+ className: `ree-inspector-tab${panel === "global" ? " is-active" : ""}`,
2080
+ "aria-pressed": panel === "global",
2081
+ onClick: () => onPanelChange("global"),
2082
+ children: "GLOBAL"
2083
+ }
2084
+ ),
2085
+ /* @__PURE__ */ jsx21(
2086
+ "button",
2087
+ {
2088
+ type: "button",
2089
+ className: `ree-inspector-tab${showBlock ? " is-active" : ""}`,
2090
+ "aria-pressed": Boolean(showBlock),
2091
+ disabled: !block || !definition,
2092
+ onClick: () => onPanelChange("block"),
2093
+ children: "BLOCK"
2094
+ }
2095
+ )
2096
+ ] }) }),
2097
+ /* @__PURE__ */ jsx21("div", { className: "ree-inspector-scroll", children: showBlock ? /* @__PURE__ */ jsx21(
2098
+ InspectorFields,
2099
+ {
2100
+ block,
2101
+ definition,
2102
+ variables,
2103
+ global,
2104
+ onCommitField,
2105
+ onCommitStyle
2106
+ }
2107
+ ) : /* @__PURE__ */ jsx21(GlobalFields, { global, onChange: onCommitGlobal }) })
2108
+ ] });
2109
+ }
2110
+
2111
+ // src/stage/canvas/Canvas.tsx
2112
+ import { useDroppable } from "@dnd-kit/core";
2113
+ import {
2114
+ SortableContext,
2115
+ verticalListSortingStrategy
2116
+ } from "@dnd-kit/sortable";
2117
+
2118
+ // src/render/layout.ts
2119
+ function surfaceStyle(global) {
2120
+ return __spreadProps(__spreadValues({}, decorationStyle({
2121
+ backgroundColor: global.contentBackgroundColor,
2122
+ borderWidth: global.borderWidth,
2123
+ borderStyle: global.borderStyle,
2124
+ borderColor: global.borderColor,
2125
+ borderRadius: global.borderRadius
2126
+ })), {
2127
+ padding: global.contentPadding
2128
+ });
2129
+ }
2130
+ function viewportWidth(device, global) {
2131
+ const width = emailViewportWidth(device, global.contentWidth);
2132
+ if (device === "mobile") return `${width}px`;
2133
+ const [, right, , left] = spacingSides(global.outerPadding);
2134
+ const sides = [left, right];
2135
+ const ratio = sides.reduce((total, side) => total + (side.endsWith("%") ? parseFloat(side) / 100 : 0), 0);
2136
+ const lengths = sides.filter((side) => !side.endsWith("%"));
2137
+ const sum = [`${width}px`, ...lengths].join(" + ");
2138
+ return ratio < 1 ? `calc((${sum}) / ${1 - ratio})` : `${width}px`;
2139
+ }
2140
+
2141
+ // src/stage/canvas/CanvasBlock.tsx
2142
+ import { useSortable } from "@dnd-kit/sortable";
2143
+ import { CSS } from "@dnd-kit/utilities";
2144
+
2145
+ // src/render/context.ts
2146
+ function buildRenderContext(node, mode, global = DEFAULT_EMAIL_GLOBAL) {
2147
+ var _a, _b;
2148
+ return {
2149
+ block: node,
2150
+ fields: (_a = node.fields) != null ? _a : {},
2151
+ style: (_b = node.style) != null ? _b : {},
2152
+ mode,
2153
+ global
2154
+ };
2155
+ }
2156
+
2157
+ // src/render/template-preview.ts
2158
+ var TOKEN_PATTERN = /\{\{-?([\s\S]*?)-?\}\}/g;
2159
+ function tokenize(template) {
2160
+ var _a;
2161
+ const tokens = [];
2162
+ let lastIndex = 0;
2163
+ for (const match of template.matchAll(TOKEN_PATTERN)) {
2164
+ const index = (_a = match.index) != null ? _a : 0;
2165
+ if (index > lastIndex) tokens.push({ text: template.slice(lastIndex, index) });
2166
+ tokens.push({ tag: match[1].trim() });
2167
+ lastIndex = index + match[0].length;
2168
+ }
2169
+ if (lastIndex < template.length) tokens.push({ text: template.slice(lastIndex) });
2170
+ return tokens;
2171
+ }
2172
+ function parseNodes(tokens, startIndex) {
2173
+ var _a, _b;
2174
+ const nodes = [];
2175
+ let index = startIndex;
2176
+ while (index < tokens.length) {
2177
+ const token = tokens[index];
2178
+ if (token.text !== void 0) {
2179
+ nodes.push({ kind: "text", value: token.text });
2180
+ index += 1;
2181
+ continue;
2182
+ }
2183
+ const tag = (_a = token.tag) != null ? _a : "";
2184
+ if (tag === "else" || tag.startsWith("else if ")) {
2185
+ return { nodes, nextIndex: index, stopTag: "else" };
2186
+ }
2187
+ if (tag === "end") return { nodes, nextIndex: index, stopTag: "end" };
2188
+ const branchMatch = /^(if|range|with)\s+(.+)$/.exec(tag);
2189
+ if (branchMatch) {
2190
+ const branch = parseBranch(
2191
+ tokens,
2192
+ index,
2193
+ branchMatch[1],
2194
+ branchMatch[2]
2195
+ );
2196
+ nodes.push(branch.node);
2197
+ index = branch.nextIndex + (((_b = tokens[branch.nextIndex]) == null ? void 0 : _b.tag) === "end" ? 1 : 0);
2198
+ continue;
2199
+ }
2200
+ nodes.push({ kind: "expression", expression: tag });
2201
+ index += 1;
2202
+ }
2203
+ return { nodes, nextIndex: index };
2204
+ }
2205
+ function parseBranch(tokens, startIndex, kind, expression) {
2206
+ var _a, _b;
2207
+ const body = parseNodes(tokens, startIndex + 1);
2208
+ let alternate = [];
2209
+ let nextIndex = body.nextIndex;
2210
+ if (body.stopTag === "else") {
2211
+ const elseTag = (_b = (_a = tokens[body.nextIndex]) == null ? void 0 : _a.tag) != null ? _b : "";
2212
+ const elseIfMatch = /^else\s+if\s+(.+)$/.exec(elseTag);
2213
+ if (elseIfMatch) {
2214
+ const nested = parseBranch(
2215
+ tokens,
2216
+ body.nextIndex,
2217
+ "if",
2218
+ elseIfMatch[1]
2219
+ );
2220
+ alternate = [nested.node];
2221
+ nextIndex = nested.nextIndex;
2222
+ } else {
2223
+ const alternateResult = parseNodes(tokens, body.nextIndex + 1);
2224
+ alternate = alternateResult.nodes;
2225
+ nextIndex = alternateResult.nextIndex;
2226
+ }
2227
+ }
2228
+ return {
2229
+ node: { kind, expression, children: body.nodes, alternate },
2230
+ nextIndex
2231
+ };
2232
+ }
2233
+ function readPath(value, path) {
2234
+ if (path === "") return value;
2235
+ return path.split(".").reduce((current, key) => {
2236
+ if (current && typeof current === "object") return current[key];
2237
+ return void 0;
2238
+ }, value);
2239
+ }
2240
+ function resolveExpression(expression, root, current) {
2241
+ const trimmed = expression.trim();
2242
+ if (trimmed === ".") return current;
2243
+ if (trimmed === "true") return true;
2244
+ if (trimmed === "false") return false;
2245
+ if (trimmed === "nil") return null;
2246
+ if (/^\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
2247
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
2248
+ return trimmed.slice(1, -1);
2249
+ }
2250
+ if (trimmed.startsWith("$.")) return readPath(root, trimmed.slice(2));
2251
+ if (trimmed.startsWith(".")) return readPath(current, trimmed.slice(1));
2252
+ return readPath(current, trimmed);
2253
+ }
2254
+ function isTruthy(value) {
2255
+ if (value == null || value === false || value === "" || value === 0) return false;
2256
+ return !Array.isArray(value) || value.length > 0;
2257
+ }
2258
+ function escapeHtml(value) {
2259
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2260
+ }
2261
+ function renderNodes(nodes, root, current) {
2262
+ return nodes.map((node) => {
2263
+ if (node.kind === "text") return node.value;
2264
+ if (node.kind === "expression") {
2265
+ const value2 = resolveExpression(node.expression, root, current);
2266
+ return value2 == null ? "" : escapeHtml(String(value2));
2267
+ }
2268
+ const value = resolveExpression(node.expression, root, current);
2269
+ if (node.kind === "range") {
2270
+ const items = Array.isArray(value) ? value : value && typeof value === "object" ? Object.values(value) : [];
2271
+ return items.map((item) => renderNodes(node.children, root, item)).join("");
2272
+ }
2273
+ if (!isTruthy(value)) {
2274
+ return renderNodes(node.alternate, root, current);
2275
+ }
2276
+ const childContext = node.kind === "with" ? value : current;
2277
+ return renderNodes(node.children, root, childContext);
2278
+ }).join("");
2279
+ }
2280
+ function renderTemplatePreview(html, defaultData) {
2281
+ if (defaultData === void 0) return html;
2282
+ return renderNodes(parseNodes(tokenize(html), 0).nodes, defaultData, defaultData);
2283
+ }
2284
+
2285
+ // src/render/block-html.ts
2286
+ function normalizeBlockRenderOptions(options) {
2287
+ return typeof options === "boolean" ? { resolveHtmlTemplate: options } : options;
2288
+ }
2289
+ var LISTED_TOKEN = /^(\{\{-?)([\s\S]*?)(-?\}\})$/;
2290
+ function variableFieldName(name) {
2291
+ const token = /^\{\{-?\s*([\s\S]*?)\s*-?\}\}$/.exec(name.trim());
2292
+ const inner = (token ? token[1] : name).trim();
2293
+ const field = inner.startsWith(".") ? inner.slice(1) : inner;
2294
+ if (!field || !/^[A-Za-z_][\w.]*$/.test(field)) return null;
2295
+ return field;
2296
+ }
2297
+ function dottedListedToken(name) {
2298
+ const match = LISTED_TOKEN.exec(name);
2299
+ if (!match) return null;
2300
+ const [, open, raw, close] = match;
2301
+ const field = variableFieldName(name);
2302
+ if (!field || raw.trim().startsWith(".")) return null;
2303
+ const start = raw.search(/\S/);
2304
+ if (start < 0) return null;
2305
+ const dotted = `${open}${raw.slice(0, start)}.${raw.slice(start)}${close}`;
2306
+ return dotted === name ? null : dotted;
2307
+ }
2308
+ function applyListedVariableDots(html, variables) {
2309
+ const replacements = variables.map((variable) => ({ from: variable.name, to: dottedListedToken(variable.name) })).filter((item) => item.to != null).sort((left, right) => right.from.length - left.from.length);
2310
+ return replacements.reduce(
2311
+ (current, item) => current.split(item.from).join(item.to),
2312
+ html
2313
+ );
2314
+ }
2315
+ function assignTemplateField(data, field, value) {
2316
+ const parts = field.split(".");
2317
+ if (parts.length === 1) {
2318
+ data[field] = value;
2319
+ return;
2320
+ }
2321
+ let current = data;
2322
+ for (let index = 0; index < parts.length - 1; index += 1) {
2323
+ const key = parts[index];
2324
+ const next = current[key];
2325
+ if (!next || typeof next !== "object" || Array.isArray(next)) current[key] = {};
2326
+ current = current[key];
2327
+ }
2328
+ current[parts[parts.length - 1]] = value;
2329
+ }
2330
+ function variablesToTemplateData(variables) {
2331
+ const data = {};
2332
+ for (const variable of variables) {
2333
+ if (variable.value === void 0) continue;
2334
+ const field = variableFieldName(variable.name);
2335
+ if (!field) continue;
2336
+ assignTemplateField(data, field, variable.value);
2337
+ }
2338
+ return data;
2339
+ }
2340
+ function mergeTemplateData(variables, defaultData) {
2341
+ const globalData = variablesToTemplateData(variables);
2342
+ if (Object.keys(globalData).length === 0 && defaultData === void 0) {
2343
+ return void 0;
2344
+ }
2345
+ return __spreadValues(__spreadValues({}, globalData), defaultData);
2346
+ }
2347
+ function renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
2348
+ var _a, _b, _c;
2349
+ const normalizedOptions = normalizeBlockRenderOptions(options);
2350
+ const rendered = renderBlock(
2351
+ node,
2352
+ definitions,
2353
+ mode,
2354
+ (_a = normalizedOptions.global) != null ? _a : DEFAULT_EMAIL_GLOBAL
2355
+ );
2356
+ if (!normalizedOptions.resolveHtmlTemplate) {
2357
+ return applyListedVariableDots(rendered, (_b = normalizedOptions.variables) != null ? _b : []);
2358
+ }
2359
+ return renderTemplatePreview(
2360
+ rendered,
2361
+ (_c = normalizedOptions.templateData) != null ? _c : node.defaultData
2362
+ );
2363
+ }
2364
+ function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", global = DEFAULT_EMAIL_GLOBAL) {
2365
+ const definition = getBlockDefinition(node.type, definitions);
2366
+ return definition ? definition.render(buildRenderContext(node, mode, global)) : `<!-- unknown block type: ${node.type} -->`;
2367
+ }
2368
+ function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
2369
+ var _a, _b, _c, _d, _e;
2370
+ const normalizedOptions = normalizeBlockRenderOptions(options);
2371
+ const global = (_a = normalizedOptions.global) != null ? _a : DEFAULT_EMAIL_GLOBAL;
2372
+ const inner = renderBlockContent(node, definitions, mode, __spreadProps(__spreadValues({}, normalizedOptions), {
2373
+ global
2374
+ }));
2375
+ const height = cssPx((_c = (_b = node.style) == null ? void 0 : _b.dimension) == null ? void 0 : _c.height);
2376
+ const cellAttrs = height ? ` height="${esc(height)}" valign="top"` : "";
2377
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="table-layout:fixed;"><tr><td style="padding:${esc(cssSpacing((_e = (_d = node.style) == null ? void 0 : _d.dimension) == null ? void 0 : _e.margin))};"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="table-layout:fixed;"><tr><td${cellAttrs} style="${blockCellCss(node.style, global)}">${inner}</td></tr></table></td></tr></table>`;
2378
+ }
2379
+
2380
+ // src/stage/rich-text/RichText.tsx
2381
+ import { useEffect as useEffect5, useLayoutEffect as useLayoutEffect3, useRef as useRef9, useState as useState8 } from "react";
2382
+ import { createPortal } from "react-dom";
2383
+ import { EditorContent, useEditor } from "@tiptap/react";
2384
+
2385
+ // src/stage/rich-text/components/RichTextToolbar.tsx
2386
+ import { useEffect as useEffect4, useRef as useRef8, useState as useState7 } from "react";
2387
+ import { useEditorState } from "@tiptap/react";
2388
+
2389
+ // src/stage/rich-text/constants.ts
2390
+ var PARAGRAPH_SPACING_MAX = 48;
2391
+ var LINE_HEIGHT_RANGE = { min: 1, max: 2.5, step: 0.05 };
2392
+ var FONT_SIZES = [
2393
+ "12px",
2394
+ "13px",
2395
+ "14px",
2396
+ "15px",
2397
+ "16px",
2398
+ "18px",
2399
+ "20px",
2400
+ "24px",
2401
+ "28px",
2402
+ "32px",
2403
+ "36px",
2404
+ "48px"
2405
+ ];
2406
+ var TEXT_COLORS = [
2407
+ "#202223",
2408
+ "#303030",
2409
+ "#6d7175",
2410
+ "#d72c0d",
2411
+ "#b98900",
2412
+ "#008060",
2413
+ "#005bd3",
2414
+ "#6d28d9",
2415
+ "#ffffff"
2416
+ ];
2417
+ var EMOJIS = [
2418
+ "\u{1F600}",
2419
+ "\u{1F601}",
2420
+ "\u{1F602}",
2421
+ "\u{1F60A}",
2422
+ "\u{1F60D}",
2423
+ "\u{1F60E}",
2424
+ "\u{1F609}",
2425
+ "\u{1F622}",
2426
+ "\u{1F44D}",
2427
+ "\u{1F44E}",
2428
+ "\u{1F64F}",
2429
+ "\u{1F44F}",
2430
+ "\u{1F389}",
2431
+ "\u{1F525}",
2432
+ "\u2764\uFE0F",
2433
+ "\u2B50",
2434
+ "\u2705",
2435
+ "\u2728",
2436
+ "\u{1F4E6}",
2437
+ "\u{1F69A}",
2438
+ "\u{1F381}",
2439
+ "\u{1F4A1}",
2440
+ "\u{1F4E3}",
2441
+ "\u{1F6D2}",
2442
+ "\u{1F4B0}",
2443
+ "\u{1F3F7}\uFE0F",
2444
+ "\u23F0",
2445
+ "\u{1F4C5}",
2446
+ "\u{1F4CD}",
2447
+ "\u2709\uFE0F",
2448
+ "\u{1F517}",
2449
+ "\u{1F440}"
2450
+ ];
2451
+ var TEXT_ALIGNS2 = [
2452
+ { value: TEXT_ALIGNS.left, title: "Align left", icon: "align-left" },
2453
+ { value: TEXT_ALIGNS.center, title: "Align center", icon: "align-center" },
2454
+ { value: TEXT_ALIGNS.right, title: "Align right", icon: "align-right" },
2455
+ { value: TEXT_ALIGNS.justify, title: "Justify", icon: "align-justify" }
2456
+ ];
2457
+
2458
+ // src/stage/rich-text/useAnchoredToolbar.ts
2459
+ import { useLayoutEffect, useRef as useRef4 } from "react";
2460
+ function useAnchoredToolbar(anchorRef, active) {
2461
+ const toolbarRef = useRef4(null);
2462
+ useLayoutEffect(() => {
2463
+ if (!active) return;
2464
+ const anchor = anchorRef.current;
2465
+ const toolbar = toolbarRef.current;
2466
+ if (!anchor || !toolbar) return;
2467
+ const ownerDocument = anchor.ownerDocument;
2468
+ const ownerWindow = ownerDocument.defaultView;
2469
+ if (!ownerWindow) return;
2470
+ const place = () => {
2471
+ var _a;
2472
+ const block = (_a = anchor.closest(".ree-block")) != null ? _a : anchor;
2473
+ const rect = block.getBoundingClientRect();
2474
+ const offscreen = rect.width === 0 || rect.height === 0 || rect.bottom < 0 || rect.top > ownerWindow.innerHeight;
2475
+ if (offscreen) {
2476
+ toolbar.classList.remove("is-placed");
2477
+ return;
2478
+ }
2479
+ const width = Math.min(rect.width, ownerWindow.innerWidth - 16);
2480
+ toolbar.style.width = `${width}px`;
2481
+ const height = toolbar.offsetHeight;
2482
+ let top = rect.top - height - 4;
2483
+ if (top < 8) {
2484
+ const below = rect.bottom + 4;
2485
+ top = below + height <= ownerWindow.innerHeight - 8 ? below : 8;
2486
+ }
2487
+ let left = rect.left;
2488
+ const maxLeft = Math.max(8, ownerWindow.innerWidth - width - 8);
2489
+ if (left > maxLeft) left = maxLeft;
2490
+ if (left < 8) left = 8;
2491
+ toolbar.style.top = `${top}px`;
2492
+ toolbar.style.left = `${left}px`;
2493
+ toolbar.classList.add("is-placed");
2494
+ };
2495
+ place();
2496
+ const observer = new ResizeObserver(place);
2497
+ observer.observe(toolbar);
2498
+ ownerDocument.addEventListener("scroll", place, true);
2499
+ ownerWindow.addEventListener("resize", place);
2500
+ return () => {
2501
+ observer.disconnect();
2502
+ ownerDocument.removeEventListener("scroll", place, true);
2503
+ ownerWindow.removeEventListener("resize", place);
2504
+ };
2505
+ }, [active, anchorRef]);
2506
+ return toolbarRef;
2507
+ }
2508
+
2509
+ // src/stage/rich-text/utils.ts
2510
+ var ALIGNMENTS = Object.values(TEXT_ALIGNS);
2511
+ function textAttribute(value) {
2512
+ return typeof value === "string" ? value : "";
2513
+ }
2514
+ function markWeight(value) {
2515
+ return value === FONT_WEIGHTS.normal || value === FONT_WEIGHTS.bold ? value : "";
2516
+ }
2517
+ function isVisuallyBold(markWeightValue, strong, inherited) {
2518
+ if (strong || markWeightValue === FONT_WEIGHTS.bold) return true;
2519
+ if (markWeightValue === FONT_WEIGHTS.normal) return false;
2520
+ return inherited === FONT_WEIGHTS.bold;
2521
+ }
2522
+ function readToolbarState(editor) {
2523
+ var _a;
2524
+ const textStyle = editor.getAttributes("textStyle");
2525
+ const link = editor.getAttributes("link");
2526
+ return {
2527
+ bold: editor.isActive("bold"),
2528
+ italic: editor.isActive("italic"),
2529
+ underline: editor.isActive("underline"),
2530
+ strike: editor.isActive("strike"),
2531
+ bulletList: editor.isActive("bulletList"),
2532
+ orderedList: editor.isActive("orderedList"),
2533
+ align: (_a = ALIGNMENTS.find((value) => editor.isActive({ textAlign: value }))) != null ? _a : "",
2534
+ fontFamily: textAttribute(textStyle.fontFamily),
2535
+ fontSize: textAttribute(textStyle.fontSize),
2536
+ fontWeight: markWeight(textStyle.fontWeight),
2537
+ color: textAttribute(textStyle.color),
2538
+ linkHref: textAttribute(link.href),
2539
+ spacing: textAttribute(editor.getAttributes("paragraph").spacing),
2540
+ lineHeight: textAttribute(editor.getAttributes("paragraph").lineHeight)
2541
+ };
2542
+ }
2543
+ function toggleVisualBold(editor, inherited) {
2544
+ const state = readToolbarState(editor);
2545
+ const visuallyBold = isVisuallyBold(state.fontWeight, state.bold, inherited);
2546
+ const chain = editor.chain().focus().unsetBold();
2547
+ if (visuallyBold) {
2548
+ if (inherited === FONT_WEIGHTS.bold) {
2549
+ chain.setMark("textStyle", { fontWeight: FONT_WEIGHTS.normal }).run();
2550
+ return;
2551
+ }
2552
+ chain.setMark("textStyle", { fontWeight: null }).removeEmptyTextStyle().run();
2553
+ return;
2554
+ }
2555
+ if (inherited === FONT_WEIGHTS.bold) {
2556
+ chain.setMark("textStyle", { fontWeight: null }).removeEmptyTextStyle().run();
2557
+ return;
2558
+ }
2559
+ chain.setMark("textStyle", { fontWeight: FONT_WEIGHTS.bold }).run();
2560
+ }
2561
+ function toColorInputValue(value, fallback) {
2562
+ return /^#[0-9a-fA-F]{6}$/.test(value) ? value : fallback;
2563
+ }
2564
+ function focusSelection(editor, range) {
2565
+ const chain = editor.chain().focus();
2566
+ if (!range) return chain;
2567
+ return chain.setTextSelection(range);
2568
+ }
2569
+ function currentSelection(editor) {
2570
+ const { from, to } = editor.state.selection;
2571
+ return { from, to };
2572
+ }
2573
+
2574
+ // src/stage/rich-text/components/ColorMenu.tsx
2575
+ import { useRef as useRef5 } from "react";
2576
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
2577
+ function ColorMenu({ colors, value, fallback, onSelect, onClear }) {
2578
+ const inputRef = useRef5(null);
2579
+ return /* @__PURE__ */ jsxs16("div", { children: [
2580
+ /* @__PURE__ */ jsx22("div", { className: "ree-rte-swatches", children: colors.map((color) => /* @__PURE__ */ jsx22(
2581
+ "button",
2582
+ {
2583
+ type: "button",
2584
+ className: `ree-rte-swatch${sameColor(color, value) ? " is-active" : ""}`,
2585
+ style: { backgroundColor: color },
2586
+ title: color,
2587
+ "aria-label": color,
2588
+ onClick: () => onSelect(color)
2589
+ },
2590
+ color
2591
+ )) }),
2592
+ /* @__PURE__ */ jsxs16("div", { className: "ree-rte-color-actions", children: [
2593
+ /* @__PURE__ */ jsx22("button", { type: "button", className: "ree-btn", onClick: onClear, children: "Default" }),
2594
+ /* @__PURE__ */ jsx22("button", { type: "button", className: "ree-btn", onClick: () => {
2595
+ var _a;
2596
+ return (_a = inputRef.current) == null ? void 0 : _a.click();
2597
+ }, children: "Custom" }),
2598
+ /* @__PURE__ */ jsx22(
2599
+ "input",
2600
+ {
2601
+ ref: inputRef,
2602
+ className: "ree-rte-color-input",
2603
+ type: "color",
2604
+ tabIndex: -1,
2605
+ value: toColorInputValue(value, fallback),
2606
+ onChange: (event) => onSelect(event.target.value)
2607
+ }
2608
+ )
2609
+ ] })
2610
+ ] });
2611
+ }
2612
+ function sameColor(left, right) {
2613
+ return left.toLowerCase() === right.toLowerCase();
2614
+ }
2615
+
2616
+ // src/stage/rich-text/components/EmojiMenu.tsx
2617
+ import { jsx as jsx23 } from "react/jsx-runtime";
2618
+ function EmojiMenu({ onPick }) {
2619
+ return /* @__PURE__ */ jsx23("div", { className: "ree-rte-emoji-grid", children: EMOJIS.map((emoji) => /* @__PURE__ */ jsx23(
2620
+ "button",
2621
+ {
2622
+ type: "button",
2623
+ className: "ree-rte-emoji",
2624
+ title: emoji,
2625
+ onClick: () => onPick(emoji),
2626
+ children: emoji
2627
+ },
2628
+ emoji
2629
+ )) });
2630
+ }
2631
+
2632
+ // src/stage/rich-text/components/LinkPopover.tsx
2633
+ import { useEffect as useEffect3, useRef as useRef6 } from "react";
2634
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
2635
+ function LinkPopover({
2636
+ url,
2637
+ canUnlink,
2638
+ onUrlChange,
2639
+ onApply,
2640
+ onUnlink
2641
+ }) {
2642
+ const inputRef = useRef6(null);
2643
+ useEffect3(() => {
2644
+ var _a, _b;
2645
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
2646
+ (_b = inputRef.current) == null ? void 0 : _b.select();
2647
+ }, []);
2648
+ return /* @__PURE__ */ jsxs17("div", { className: "ree-rte-link", children: [
2649
+ /* @__PURE__ */ jsxs17("label", { className: "ree-field", children: [
2650
+ /* @__PURE__ */ jsx24("span", { className: "ree-field-label", children: "Link URL" }),
2651
+ /* @__PURE__ */ jsx24(
2652
+ "input",
2653
+ {
2654
+ ref: inputRef,
2655
+ className: "ree-input",
2656
+ value: url,
2657
+ placeholder: "https://",
2658
+ onChange: (event) => onUrlChange(event.target.value),
2659
+ onKeyDown: (event) => {
2660
+ if (event.key === "Enter") {
2661
+ event.preventDefault();
2662
+ onApply();
2663
+ }
2664
+ }
2665
+ }
2666
+ )
2667
+ ] }),
2668
+ /* @__PURE__ */ jsxs17("div", { className: "ree-rte-link-actions", children: [
2669
+ /* @__PURE__ */ jsx24("button", { type: "button", className: "ree-btn", disabled: !canUnlink, onClick: onUnlink, children: "Unlink" }),
2670
+ /* @__PURE__ */ jsx24("button", { type: "button", className: "ree-btn ree-btn-primary", onClick: onApply, children: "Apply" })
2671
+ ] })
2672
+ ] });
2673
+ }
2674
+
2675
+ // src/stage/rich-text/components/ToolbarButton.tsx
2676
+ import { jsx as jsx25 } from "react/jsx-runtime";
2677
+ function ToolbarButton({
2678
+ title,
2679
+ active = false,
2680
+ disabled = false,
2681
+ onClick,
2682
+ children
2683
+ }) {
2684
+ return /* @__PURE__ */ jsx25(
2685
+ "button",
2686
+ {
2687
+ type: "button",
2688
+ className: `ree-tool-btn${active ? " is-active" : ""}`,
2689
+ title,
2690
+ "aria-pressed": active,
2691
+ disabled,
2692
+ onClick,
2693
+ children
2694
+ }
2695
+ );
2696
+ }
2697
+
2698
+ // src/stage/rich-text/components/ToolbarIcon.tsx
2699
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
2700
+ function ToolbarIcon({ name }) {
2701
+ return /* @__PURE__ */ jsx26("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", fill: "none", children: iconPaths(name) });
2702
+ }
2703
+ function iconPaths(name) {
2704
+ const stroke = {
2705
+ fill: "none",
2706
+ stroke: "currentColor",
2707
+ strokeWidth: 2,
2708
+ strokeLinecap: "round",
2709
+ strokeLinejoin: "round"
2710
+ };
2711
+ switch (name) {
2712
+ case "align-left":
2713
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2714
+ /* @__PURE__ */ jsx26("path", { d: "M4 6h16" }),
2715
+ /* @__PURE__ */ jsx26("path", { d: "M4 10h10" }),
2716
+ /* @__PURE__ */ jsx26("path", { d: "M4 14h16" }),
2717
+ /* @__PURE__ */ jsx26("path", { d: "M4 18h10" })
2718
+ ] }));
2719
+ case "align-center":
2720
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2721
+ /* @__PURE__ */ jsx26("path", { d: "M4 6h16" }),
2722
+ /* @__PURE__ */ jsx26("path", { d: "M7 10h10" }),
2723
+ /* @__PURE__ */ jsx26("path", { d: "M4 14h16" }),
2724
+ /* @__PURE__ */ jsx26("path", { d: "M7 18h10" })
2725
+ ] }));
2726
+ case "align-right":
2727
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2728
+ /* @__PURE__ */ jsx26("path", { d: "M4 6h16" }),
2729
+ /* @__PURE__ */ jsx26("path", { d: "M10 10h10" }),
2730
+ /* @__PURE__ */ jsx26("path", { d: "M4 14h16" }),
2731
+ /* @__PURE__ */ jsx26("path", { d: "M10 18h10" })
2732
+ ] }));
2733
+ case "align-justify":
2734
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2735
+ /* @__PURE__ */ jsx26("path", { d: "M4 6h16" }),
2736
+ /* @__PURE__ */ jsx26("path", { d: "M4 10h16" }),
2737
+ /* @__PURE__ */ jsx26("path", { d: "M4 14h16" }),
2738
+ /* @__PURE__ */ jsx26("path", { d: "M4 18h16" })
2739
+ ] }));
2740
+ case "list-bullet":
2741
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2742
+ /* @__PURE__ */ jsx26("path", { d: "M10 7h10" }),
2743
+ /* @__PURE__ */ jsx26("path", { d: "M10 12h10" }),
2744
+ /* @__PURE__ */ jsx26("path", { d: "M10 17h10" }),
2745
+ /* @__PURE__ */ jsx26("circle", { cx: "5", cy: "7", r: "1.2", fill: "currentColor", stroke: "none" }),
2746
+ /* @__PURE__ */ jsx26("circle", { cx: "5", cy: "12", r: "1.2", fill: "currentColor", stroke: "none" }),
2747
+ /* @__PURE__ */ jsx26("circle", { cx: "5", cy: "17", r: "1.2", fill: "currentColor", stroke: "none" })
2748
+ ] }));
2749
+ case "list-ordered":
2750
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2751
+ /* @__PURE__ */ jsx26("path", { d: "M11 7h9" }),
2752
+ /* @__PURE__ */ jsx26("path", { d: "M11 12h9" }),
2753
+ /* @__PURE__ */ jsx26("path", { d: "M11 17h9" }),
2754
+ /* @__PURE__ */ jsx26("text", { x: "3", y: "9", fill: "currentColor", stroke: "none", fontSize: "7", fontFamily: "sans-serif", children: "1" }),
2755
+ /* @__PURE__ */ jsx26("text", { x: "3", y: "14", fill: "currentColor", stroke: "none", fontSize: "7", fontFamily: "sans-serif", children: "2" }),
2756
+ /* @__PURE__ */ jsx26("text", { x: "3", y: "19", fill: "currentColor", stroke: "none", fontSize: "7", fontFamily: "sans-serif", children: "3" })
2757
+ ] }));
2758
+ case "emoji":
2759
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2760
+ /* @__PURE__ */ jsx26("circle", { cx: "12", cy: "12", r: "8" }),
2761
+ /* @__PURE__ */ jsx26("path", { d: "M8.5 14.2a4.2 4.2 0 0 0 7 0" }),
2762
+ /* @__PURE__ */ jsx26("path", { d: "M9 10h.01" }),
2763
+ /* @__PURE__ */ jsx26("path", { d: "M15 10h.01" })
2764
+ ] }));
2765
+ case "link":
2766
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2767
+ /* @__PURE__ */ jsx26("path", { d: "M10 13a5 5 0 0 0 7.1.1l1.8-1.8a5 5 0 0 0-7.1-7.1L10.2 5.8" }),
2768
+ /* @__PURE__ */ jsx26("path", { d: "M14 11a5 5 0 0 0-7.1-.1L5.1 12.7a5 5 0 0 0 7.1 7.1l1.6-1.6" })
2769
+ ] }));
2770
+ case "unlink":
2771
+ return /* @__PURE__ */ jsxs18("g", __spreadProps(__spreadValues({}, stroke), { children: [
2772
+ /* @__PURE__ */ jsx26("path", { d: "M9 17H7a5 5 0 0 1 0-10h2" }),
2773
+ /* @__PURE__ */ jsx26("path", { d: "M15 7h2a5 5 0 0 1 0 10h-2" }),
2774
+ /* @__PURE__ */ jsx26("path", { d: "M8 12h4" }),
2775
+ /* @__PURE__ */ jsx26("path", { d: "m4 4 16 16" })
2776
+ ] }));
2777
+ case "chevron":
2778
+ return /* @__PURE__ */ jsx26(
2779
+ "path",
2780
+ {
2781
+ d: "M7 10.2 12 15l5-4.8",
2782
+ fill: "none",
2783
+ stroke: "currentColor",
2784
+ strokeWidth: "1.8",
2785
+ strokeLinecap: "round",
2786
+ strokeLinejoin: "round"
2787
+ }
2788
+ );
2789
+ }
2790
+ }
2791
+
2792
+ // src/stage/rich-text/components/ToolbarMenu.tsx
2793
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef7, useState as useState6 } from "react";
2794
+ import { jsx as jsx27, jsxs as jsxs19 } from "react/jsx-runtime";
2795
+ function ToolbarMenu({
2796
+ label,
2797
+ title,
2798
+ open,
2799
+ onToggle,
2800
+ align = "start",
2801
+ caret = true,
2802
+ active = false,
2803
+ variant = "select",
2804
+ panelClassName,
2805
+ children
2806
+ }) {
2807
+ const rootRef = useRef7(null);
2808
+ const [up, setUp] = useState6(false);
2809
+ useLayoutEffect2(() => {
2810
+ if (!open || !rootRef.current) return;
2811
+ const ownerWindow = rootRef.current.ownerDocument.defaultView;
2812
+ if (!ownerWindow) return;
2813
+ const rect = rootRef.current.getBoundingClientRect();
2814
+ setUp(rect.bottom + 260 > ownerWindow.innerHeight && rect.top > 260);
2815
+ }, [open]);
2816
+ const buttonClass = variant === "icon" ? `ree-tool-btn${active ? " is-active" : ""}${open ? " is-open" : ""}` : `ree-rte-select${open ? " is-open" : ""}`;
2817
+ return /* @__PURE__ */ jsxs19("div", { className: `ree-rte-pop${up ? " is-up" : ""}`, ref: rootRef, children: [
2818
+ /* @__PURE__ */ jsxs19(
2819
+ "button",
2820
+ {
2821
+ type: "button",
2822
+ className: buttonClass,
2823
+ title,
2824
+ "aria-expanded": open,
2825
+ onClick: onToggle,
2826
+ children: [
2827
+ variant === "select" ? /* @__PURE__ */ jsx27("span", { className: "ree-rte-select-label", children: label }) : label,
2828
+ caret ? /* @__PURE__ */ jsx27(ToolbarIcon, { name: "chevron" }) : null
2829
+ ]
2830
+ }
2831
+ ),
2832
+ open ? /* @__PURE__ */ jsx27(
2833
+ "div",
2834
+ {
2835
+ className: [
2836
+ "ree-rte-menu",
2837
+ align === "end" ? "is-end" : "",
2838
+ panelClassName != null ? panelClassName : ""
2839
+ ].filter(Boolean).join(" "),
2840
+ children
2841
+ }
2842
+ ) : null
2843
+ ] });
2844
+ }
2845
+
2846
+ // src/stage/rich-text/components/MetricSlider.tsx
2847
+ import { jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
2848
+ function MetricSlider({
2849
+ label,
2850
+ value,
2851
+ min,
2852
+ max,
2853
+ step,
2854
+ display,
2855
+ overridden,
2856
+ onChange,
2857
+ onReset
2858
+ }) {
2859
+ const progress = max > min ? Math.min(100, Math.max(0, (value - min) / (max - min) * 100)) : 0;
2860
+ return /* @__PURE__ */ jsxs20("div", { className: "ree-rte-metric", children: [
2861
+ /* @__PURE__ */ jsxs20("div", { className: "ree-rte-metric-label", children: [
2862
+ /* @__PURE__ */ jsx28("span", { children: label }),
2863
+ /* @__PURE__ */ jsx28("span", { children: display })
2864
+ ] }),
2865
+ /* @__PURE__ */ jsx28(
2866
+ "input",
2867
+ {
2868
+ "aria-label": label,
2869
+ type: "range",
2870
+ className: "ree-range",
2871
+ style: { "--ree-range-progress": `${progress}%` },
2872
+ value,
2873
+ min,
2874
+ max,
2875
+ step,
2876
+ onChange: (event) => onChange(Number(event.target.value))
2877
+ }
2878
+ ),
2879
+ /* @__PURE__ */ jsx28("button", { type: "button", className: "ree-rte-option", disabled: !overridden, onClick: onReset, children: "Default" })
2880
+ ] });
2881
+ }
2882
+
2883
+ // src/stage/rich-text/components/ParagraphMetricMenus.tsx
2884
+ import { Fragment as Fragment2, jsx as jsx29, jsxs as jsxs21 } from "react/jsx-runtime";
2885
+ function clampAmount(value, fallback, min, max) {
2886
+ const parsed = readCssLength(value || fallback);
2887
+ return Math.min(max, Math.max(min, parsed.amount));
2888
+ }
2889
+ function formatStep(value, step) {
2890
+ var _a;
2891
+ const places = ((_a = String(step).split(".")[1]) != null ? _a : "").length;
2892
+ return String(Number((Math.round(value / step) * step).toFixed(places)));
2893
+ }
2894
+ function ParagraphMetricMenus({
2895
+ menu,
2896
+ spacing,
2897
+ lineHeight,
2898
+ inheritedSpacing,
2899
+ inheritedLineHeight,
2900
+ onToggle,
2901
+ onSpacing,
2902
+ onLineHeight
2903
+ }) {
2904
+ const spacingValue = clampAmount(spacing, inheritedSpacing, 0, PARAGRAPH_SPACING_MAX);
2905
+ const lineHeightValue = clampAmount(lineHeight, inheritedLineHeight, LINE_HEIGHT_RANGE.min, LINE_HEIGHT_RANGE.max);
2906
+ const lineHeightLabel = formatStep(lineHeightValue, LINE_HEIGHT_RANGE.step);
2907
+ return /* @__PURE__ */ jsxs21(Fragment2, { children: [
2908
+ /* @__PURE__ */ jsx29(
2909
+ ToolbarMenu,
2910
+ {
2911
+ title: "Paragraph spacing",
2912
+ open: menu === "spacing",
2913
+ onToggle: () => onToggle("spacing"),
2914
+ label: spacing || inheritedSpacing,
2915
+ children: /* @__PURE__ */ jsx29(
2916
+ MetricSlider,
2917
+ {
2918
+ label: "Space before",
2919
+ value: spacingValue,
2920
+ min: 0,
2921
+ max: PARAGRAPH_SPACING_MAX,
2922
+ step: 1,
2923
+ display: `${spacingValue}px`,
2924
+ overridden: Boolean(spacing),
2925
+ onChange: (next) => onSpacing(`${next}px`),
2926
+ onReset: () => onSpacing(null)
2927
+ }
2928
+ )
2929
+ }
2930
+ ),
2931
+ /* @__PURE__ */ jsx29(
2932
+ ToolbarMenu,
2933
+ {
2934
+ title: "Line height",
2935
+ open: menu === "lineHeight",
2936
+ onToggle: () => onToggle("lineHeight"),
2937
+ label: lineHeight || inheritedLineHeight || lineHeightLabel,
2938
+ children: /* @__PURE__ */ jsx29(
2939
+ MetricSlider,
2940
+ {
2941
+ label: "Line height",
2942
+ value: lineHeightValue,
2943
+ min: LINE_HEIGHT_RANGE.min,
2944
+ max: LINE_HEIGHT_RANGE.max,
2945
+ step: LINE_HEIGHT_RANGE.step,
2946
+ display: lineHeightLabel,
2947
+ overridden: Boolean(lineHeight),
2948
+ onChange: (next) => onLineHeight(formatStep(next, LINE_HEIGHT_RANGE.step)),
2949
+ onReset: () => onLineHeight(null)
2950
+ }
2951
+ )
2952
+ }
2953
+ )
2954
+ ] });
2955
+ }
2956
+
2957
+ // src/stage/rich-text/template-spaces.ts
2958
+ var TOKEN = /\{\{[\s\S]*?\}\}/g;
2959
+ var SPACE_ENTITY = /\u00A0|&nbsp;|&#160;|&#x0*a0;/gi;
2960
+ function preserveTemplateSpaces(value) {
2961
+ return value.replace(TOKEN, (token) => token.replace(/ /g, "\xA0"));
2962
+ }
2963
+ function restoreTemplateSpaces(value) {
2964
+ return value.replace(TOKEN, (token) => token.replace(SPACE_ENTITY, " "));
2965
+ }
2966
+
2967
+ // src/stage/rich-text/components/VariableMenu.tsx
2968
+ import { jsx as jsx30, jsxs as jsxs22 } from "react/jsx-runtime";
2969
+ function VariableMenu({ variables, onInsert }) {
2970
+ if (variables.length === 0) {
2971
+ return /* @__PURE__ */ jsx30("div", { className: "ree-personalization-empty", children: "No personalization fields" });
2972
+ }
2973
+ return /* @__PURE__ */ jsx30("div", { className: "ree-rte-variables", children: variables.map((variable) => /* @__PURE__ */ jsxs22(
2974
+ "button",
2975
+ {
2976
+ type: "button",
2977
+ className: "ree-var-item",
2978
+ onClick: () => onInsert(variable.name),
2979
+ children: [
2980
+ /* @__PURE__ */ jsx30("span", { className: "ree-var-name", children: variable.name }),
2981
+ variable.description ? /* @__PURE__ */ jsx30("span", { className: "ree-var-label", children: variable.description }) : null
2982
+ ]
2983
+ },
2984
+ variable.name
2985
+ )) });
2986
+ }
2987
+
2988
+ // src/stage/rich-text/components/RichTextToolbar.tsx
2989
+ import { jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
2990
+ function RichTextToolbar({
2991
+ editor,
2992
+ variables,
2993
+ anchorRef,
2994
+ inherited
2995
+ }) {
2996
+ const state = useEditorState({
2997
+ editor,
2998
+ selector: ({ editor: current }) => current ? readToolbarState(current) : null
2999
+ });
3000
+ const rootRef = useAnchoredToolbar(anchorRef, Boolean(state));
3001
+ const linkRange = useRef8(null);
3002
+ const markRange = useRef8(null);
3003
+ const [menu, setMenu] = useState7(null);
3004
+ const [linkUrl, setLinkUrl] = useState7("https://");
3005
+ useEffect4(() => {
3006
+ const root = rootRef.current;
3007
+ if (!menu || !root) return;
3008
+ const ownerDocument = root.ownerDocument;
3009
+ const onPointerDown = (event) => {
3010
+ if (!root.contains(event.target)) setMenu(null);
3011
+ };
3012
+ const onKeyDown = (event) => {
3013
+ if (event.key === "Escape") setMenu(null);
3014
+ };
3015
+ ownerDocument.addEventListener("pointerdown", onPointerDown, true);
3016
+ ownerDocument.addEventListener("keydown", onKeyDown);
3017
+ return () => {
3018
+ ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
3019
+ ownerDocument.removeEventListener("keydown", onKeyDown);
3020
+ };
3021
+ }, [menu]);
3022
+ if (!state) return null;
3023
+ const fontFamily = state.fontFamily || inherited.fontFamily;
3024
+ const fontSize = state.fontSize || inherited.fontSize;
3025
+ const color = state.color || inherited.color;
3026
+ const bold = isVisuallyBold(state.fontWeight, state.bold, inherited.fontWeight);
3027
+ const toggleMenu = (next) => {
3028
+ if (menu !== next) {
3029
+ const range = currentSelection(editor);
3030
+ if (next === "link") {
3031
+ setLinkUrl(state.linkHref || "https://");
3032
+ linkRange.current = range;
3033
+ }
3034
+ if (next === "color" || next === "spacing" || next === "lineHeight") markRange.current = range;
3035
+ }
3036
+ setMenu((current) => current === next ? null : next);
3037
+ };
3038
+ const applyLink = () => {
3039
+ const href = linkUrl.trim();
3040
+ if (!href) return;
3041
+ const range = linkRange.current;
3042
+ const chain = focusSelection(editor, range);
3043
+ if (range && range.from === range.to) {
3044
+ chain.insertContent({
3045
+ type: "text",
3046
+ text: href,
3047
+ marks: [{ type: "link", attrs: { href } }]
3048
+ }).run();
3049
+ } else {
3050
+ chain.extendMarkRange("link").setLink({ href }).run();
3051
+ }
3052
+ setMenu(null);
3053
+ };
3054
+ const unlinkSaved = () => {
3055
+ focusSelection(editor, linkRange.current).extendMarkRange("link").unsetLink().run();
3056
+ setMenu(null);
3057
+ };
3058
+ const unlinkSelection = () => {
3059
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
3060
+ setMenu(null);
3061
+ };
3062
+ const run = (command) => {
3063
+ command();
3064
+ setMenu(null);
3065
+ };
3066
+ return /* @__PURE__ */ jsxs23(
3067
+ "div",
3068
+ {
3069
+ className: "ree-rte-toolbar",
3070
+ ref: rootRef,
3071
+ onMouseDown: (event) => {
3072
+ const ownerWindow = event.currentTarget.ownerDocument.defaultView;
3073
+ const target = event.target;
3074
+ if (ownerWindow && target instanceof ownerWindow.Element && target.closest("input, textarea")) return;
3075
+ event.preventDefault();
3076
+ },
3077
+ children: [
3078
+ /* @__PURE__ */ jsxs23("div", { className: "ree-rte-row", children: [
3079
+ /* @__PURE__ */ jsx31(
3080
+ ToolbarMenu,
3081
+ {
3082
+ title: "Font",
3083
+ open: menu === "font",
3084
+ onToggle: () => toggleMenu("font"),
3085
+ label: fontFamilyLabel(fontFamily),
3086
+ children: FONT_FAMILIES.map((font) => /* @__PURE__ */ jsx31(
3087
+ "button",
3088
+ {
3089
+ type: "button",
3090
+ className: `ree-rte-option${fontFamily === font.value ? " is-active" : ""}`,
3091
+ style: { fontFamily: font.value },
3092
+ onClick: () => run(() => editor.chain().focus().setFontFamily(font.value).run()),
3093
+ children: font.label
3094
+ },
3095
+ font.value
3096
+ ))
3097
+ }
3098
+ ),
3099
+ /* @__PURE__ */ jsx31(
3100
+ ToolbarMenu,
3101
+ {
3102
+ title: "Font size",
3103
+ open: menu === "size",
3104
+ onToggle: () => toggleMenu("size"),
3105
+ label: fontSize || "15px",
3106
+ children: FONT_SIZES.map((size) => /* @__PURE__ */ jsx31(
3107
+ "button",
3108
+ {
3109
+ type: "button",
3110
+ className: `ree-rte-option${fontSize === size ? " is-active" : ""}`,
3111
+ onClick: () => run(() => editor.chain().focus().setFontSize(size).run()),
3112
+ children: size
3113
+ },
3114
+ size
3115
+ ))
3116
+ }
3117
+ ),
3118
+ /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3119
+ /* @__PURE__ */ jsx31(
3120
+ ToolbarButton,
3121
+ {
3122
+ title: "Bold",
3123
+ active: bold,
3124
+ onClick: () => toggleVisualBold(editor, inherited.fontWeight),
3125
+ children: /* @__PURE__ */ jsx31("span", { className: "ree-tool-bold", children: "B" })
3126
+ }
3127
+ ),
3128
+ /* @__PURE__ */ jsx31(
3129
+ ToolbarButton,
3130
+ {
3131
+ title: "Italic",
3132
+ active: state.italic,
3133
+ onClick: () => editor.chain().focus().toggleItalic().run(),
3134
+ children: /* @__PURE__ */ jsx31("span", { className: "ree-tool-italic", children: "I" })
3135
+ }
3136
+ ),
3137
+ /* @__PURE__ */ jsx31(
3138
+ ToolbarButton,
3139
+ {
3140
+ title: "Underline",
3141
+ active: state.underline,
3142
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
3143
+ children: /* @__PURE__ */ jsx31("span", { className: "ree-tool-underline", children: "U" })
3144
+ }
3145
+ ),
3146
+ /* @__PURE__ */ jsx31(
3147
+ ToolbarButton,
3148
+ {
3149
+ title: "Strikethrough",
3150
+ active: state.strike,
3151
+ onClick: () => editor.chain().focus().toggleStrike().run(),
3152
+ children: /* @__PURE__ */ jsx31("span", { className: "ree-tool-strike", children: "S" })
3153
+ }
3154
+ ),
3155
+ /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3156
+ /* @__PURE__ */ jsx31(
3157
+ ToolbarMenu,
3158
+ {
3159
+ title: "Emoji",
3160
+ variant: "icon",
3161
+ caret: false,
3162
+ align: "end",
3163
+ open: menu === "emoji",
3164
+ onToggle: () => toggleMenu("emoji"),
3165
+ label: /* @__PURE__ */ jsx31(ToolbarIcon, { name: "emoji" }),
3166
+ children: /* @__PURE__ */ jsx31(
3167
+ EmojiMenu,
3168
+ {
3169
+ onPick: (emoji) => run(() => editor.chain().focus().insertContent(emoji).run())
3170
+ }
3171
+ )
3172
+ }
3173
+ )
3174
+ ] }),
3175
+ /* @__PURE__ */ jsxs23("div", { className: "ree-rte-row", children: [
3176
+ TEXT_ALIGNS2.map((align) => /* @__PURE__ */ jsx31(
3177
+ ToolbarButton,
3178
+ {
3179
+ title: align.title,
3180
+ active: state.align === align.value,
3181
+ onClick: () => editor.chain().focus().toggleTextAlign(align.value).run(),
3182
+ children: /* @__PURE__ */ jsx31(ToolbarIcon, { name: align.icon })
3183
+ },
3184
+ align.value
3185
+ )),
3186
+ /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3187
+ /* @__PURE__ */ jsx31(
3188
+ ParagraphMetricMenus,
3189
+ {
3190
+ menu,
3191
+ spacing: state.spacing,
3192
+ lineHeight: state.lineHeight,
3193
+ inheritedSpacing: inherited.paragraphSpacing,
3194
+ inheritedLineHeight: inherited.lineHeight,
3195
+ onToggle: toggleMenu,
3196
+ onSpacing: (value) => {
3197
+ const chain = focusSelection(editor, markRange.current);
3198
+ if (value) chain.updateAttributes("paragraph", { spacing: value }).run();
3199
+ else chain.resetAttributes("paragraph", "spacing").run();
3200
+ },
3201
+ onLineHeight: (value) => {
3202
+ const chain = focusSelection(editor, markRange.current);
3203
+ if (value) chain.updateAttributes("paragraph", { lineHeight: value }).run();
3204
+ else chain.resetAttributes("paragraph", "lineHeight").run();
3205
+ }
3206
+ }
3207
+ ),
3208
+ /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3209
+ /* @__PURE__ */ jsx31(
3210
+ ToolbarButton,
3211
+ {
3212
+ title: "Bulleted list",
3213
+ active: state.bulletList,
3214
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
3215
+ children: /* @__PURE__ */ jsx31(ToolbarIcon, { name: "list-bullet" })
3216
+ }
3217
+ ),
3218
+ /* @__PURE__ */ jsx31(
3219
+ ToolbarButton,
3220
+ {
3221
+ title: "Numbered list",
3222
+ active: state.orderedList,
3223
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
3224
+ children: /* @__PURE__ */ jsx31(ToolbarIcon, { name: "list-ordered" })
3225
+ }
3226
+ ),
3227
+ /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3228
+ /* @__PURE__ */ jsx31(
3229
+ ToolbarMenu,
3230
+ {
3231
+ title: "Text color",
3232
+ variant: "icon",
3233
+ align: "end",
3234
+ open: menu === "color",
3235
+ onToggle: () => toggleMenu("color"),
3236
+ label: /* @__PURE__ */ jsx31("span", { className: "ree-tool-color", style: { borderBottomColor: color }, children: "A" }),
3237
+ children: /* @__PURE__ */ jsx31(
3238
+ ColorMenu,
3239
+ {
3240
+ colors: TEXT_COLORS,
3241
+ value: state.color,
3242
+ fallback: inherited.color,
3243
+ onSelect: (color2) => run(() => focusSelection(editor, markRange.current).setColor(color2).run()),
3244
+ onClear: () => run(() => focusSelection(editor, markRange.current).unsetColor().run())
3245
+ }
3246
+ )
3247
+ }
3248
+ ),
3249
+ /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3250
+ /* @__PURE__ */ jsx31(
3251
+ ToolbarMenu,
3252
+ {
3253
+ title: "Link",
3254
+ variant: "icon",
3255
+ caret: false,
3256
+ active: Boolean(state.linkHref),
3257
+ align: "end",
3258
+ open: menu === "link",
3259
+ onToggle: () => toggleMenu("link"),
3260
+ label: /* @__PURE__ */ jsx31(ToolbarIcon, { name: "link" }),
3261
+ panelClassName: "is-link",
3262
+ children: /* @__PURE__ */ jsx31(
3263
+ LinkPopover,
3264
+ {
3265
+ url: linkUrl,
3266
+ canUnlink: Boolean(state.linkHref),
3267
+ onUrlChange: setLinkUrl,
3268
+ onApply: applyLink,
3269
+ onUnlink: unlinkSaved
3270
+ }
3271
+ )
3272
+ }
3273
+ ),
3274
+ /* @__PURE__ */ jsx31(ToolbarButton, { title: "Unlink", disabled: !state.linkHref, onClick: unlinkSelection, children: /* @__PURE__ */ jsx31(ToolbarIcon, { name: "unlink" }) }),
3275
+ /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3276
+ /* @__PURE__ */ jsx31(
3277
+ ToolbarMenu,
3278
+ {
3279
+ title: "Personalization",
3280
+ align: "end",
3281
+ open: menu === "variable",
3282
+ onToggle: () => toggleMenu("variable"),
3283
+ label: "Personalization",
3284
+ children: /* @__PURE__ */ jsx31(
3285
+ VariableMenu,
3286
+ {
3287
+ variables,
3288
+ onInsert: (token) => run(() => editor.chain().focus().insertContent(preserveTemplateSpaces(token)).run())
3289
+ }
3290
+ )
3291
+ }
3292
+ )
3293
+ ] })
3294
+ ]
3295
+ }
3296
+ );
3297
+ }
3298
+
3299
+ // src/stage/rich-text/extensions.ts
3300
+ import Placeholder from "@tiptap/extension-placeholder";
3301
+ import StarterKit from "@tiptap/starter-kit";
3302
+ import TextAlign from "@tiptap/extension-text-align";
3303
+ import { TextStyleKit } from "@tiptap/extension-text-style";
3304
+
3305
+ // src/stage/rich-text/font-weight.ts
3306
+ import { Extension } from "@tiptap/react";
3307
+ function parseFontWeight(value) {
3308
+ const weight = value.trim().toLowerCase();
3309
+ return isStyleValue(FONT_WEIGHTS, weight) ? weight : null;
3310
+ }
3311
+ var FontWeight = Extension.create({
3312
+ name: "fontWeight",
3313
+ addGlobalAttributes() {
3314
+ return [
3315
+ {
3316
+ types: ["textStyle"],
3317
+ attributes: {
3318
+ fontWeight: {
3319
+ default: null,
3320
+ parseHTML: (element) => parseFontWeight(element.style.fontWeight),
3321
+ renderHTML: (attributes) => {
3322
+ if (attributes.fontWeight !== FONT_WEIGHTS.normal && attributes.fontWeight !== FONT_WEIGHTS.bold) {
3323
+ return {};
3324
+ }
3325
+ return { style: `font-weight: ${attributes.fontWeight}` };
3326
+ }
3327
+ }
3328
+ }
3329
+ }
3330
+ ];
3331
+ }
3332
+ });
3333
+
3334
+ // src/stage/rich-text/paragraph-style.ts
3335
+ import { Extension as Extension2 } from "@tiptap/react";
3336
+ function lengthAttribute(value) {
3337
+ const length = cssLength(value != null ? value : "", "");
3338
+ return length || null;
3339
+ }
3340
+ function lineHeightAttribute(value) {
3341
+ const lineHeight = cssLineHeight(value != null ? value : "", "");
3342
+ return lineHeight || null;
3343
+ }
3344
+ var ParagraphStyle = Extension2.create({
3345
+ name: "paragraphStyle",
3346
+ addGlobalAttributes() {
3347
+ return [
3348
+ {
3349
+ types: ["paragraph"],
3350
+ attributes: {
3351
+ spacing: {
3352
+ default: null,
3353
+ parseHTML: (element) => lengthAttribute(
3354
+ element.getAttribute("data-spacing") || element.style.getPropertyValue("--ree-paragraph-gap")
3355
+ ),
3356
+ renderHTML: (attributes) => {
3357
+ var _a;
3358
+ const spacing = lengthAttribute((_a = attributes.spacing) != null ? _a : null);
3359
+ if (!spacing) return {};
3360
+ return { "data-spacing": spacing, style: `--ree-paragraph-gap: ${spacing}` };
3361
+ }
3362
+ },
3363
+ lineHeight: {
3364
+ default: null,
3365
+ parseHTML: (element) => lineHeightAttribute(
3366
+ element.getAttribute("data-line-height") || element.style.lineHeight
3367
+ ),
3368
+ renderHTML: (attributes) => {
3369
+ var _a;
3370
+ const lineHeight = lineHeightAttribute((_a = attributes.lineHeight) != null ? _a : null);
3371
+ if (!lineHeight) return {};
3372
+ return { "data-line-height": lineHeight, style: `line-height: ${lineHeight}` };
3373
+ }
3374
+ }
3375
+ }
3376
+ }
3377
+ ];
3378
+ }
3379
+ });
3380
+
3381
+ // src/stage/rich-text/extensions.ts
3382
+ function createRichTextExtensions(placeholder2) {
3383
+ return [
3384
+ StarterKit.configure({
3385
+ link: { openOnClick: false, autolink: true },
3386
+ blockquote: false,
3387
+ code: false,
3388
+ codeBlock: false,
3389
+ heading: false,
3390
+ horizontalRule: false
3391
+ }),
3392
+ TextStyleKit.configure({ backgroundColor: false, lineHeight: false }),
3393
+ FontWeight,
3394
+ ParagraphStyle,
3395
+ TextAlign.configure({
3396
+ types: ["paragraph"],
3397
+ alignments: Object.values(TEXT_ALIGNS)
3398
+ }),
3399
+ Placeholder.configure({ placeholder: placeholder2 })
3400
+ ];
3401
+ }
3402
+
3403
+ // src/stage/rich-text/RichText.tsx
3404
+ import { jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
3405
+ var DEFAULT_INHERITED = {
3406
+ fontFamily: DEFAULT_EMAIL_GLOBAL.fontFamily,
3407
+ fontSize: "15px",
3408
+ color: DEFAULT_EMAIL_GLOBAL.textColor,
3409
+ fontWeight: DEFAULT_EMAIL_GLOBAL.fontWeight,
3410
+ lineHeight: DEFAULT_EMAIL_GLOBAL.defaultLineHeight,
3411
+ paragraphSpacing: DEFAULT_EMAIL_GLOBAL.paragraphSpacing
3412
+ };
3413
+ function RichText({
3414
+ initialHtml,
3415
+ placeholder: placeholder2,
3416
+ variables,
3417
+ selected,
3418
+ inherited = DEFAULT_INHERITED,
3419
+ onCommit,
3420
+ onSelect
3421
+ }) {
3422
+ const rootRef = useRef9(null);
3423
+ const [host, setHost] = useState8(null);
3424
+ const editor = useEditor({
3425
+ extensions: createRichTextExtensions(placeholder2 != null ? placeholder2 : "Type something\u2026"),
3426
+ content: preserveTemplateSpaces(initialHtml),
3427
+ immediatelyRender: false,
3428
+ onUpdate: ({ editor: current }) => onCommit(restoreTemplateSpaces(current.getHTML()))
3429
+ });
3430
+ useLayoutEffect3(() => {
3431
+ var _a;
3432
+ const node = rootRef.current;
3433
+ const themeRoot = node == null ? void 0 : node.closest(".ree-root");
3434
+ setHost(themeRoot instanceof HTMLElement ? themeRoot : (_a = node == null ? void 0 : node.ownerDocument.body) != null ? _a : null);
3435
+ }, []);
3436
+ useEffect5(() => {
3437
+ if (!editor || restoreTemplateSpaces(editor.getHTML()) === initialHtml) return;
3438
+ editor.commands.setContent(preserveTemplateSpaces(initialHtml), { emitUpdate: false });
3439
+ }, [editor, initialHtml]);
3440
+ return /* @__PURE__ */ jsxs24("div", { className: "ree-rte", ref: rootRef, onClick: onSelect, children: [
3441
+ selected && editor && host ? createPortal(
3442
+ /* @__PURE__ */ jsx32(
3443
+ RichTextToolbar,
3444
+ {
3445
+ editor,
3446
+ variables,
3447
+ anchorRef: rootRef,
3448
+ inherited
3449
+ }
3450
+ ),
3451
+ host
3452
+ ) : null,
3453
+ editor ? /* @__PURE__ */ jsx32(EditorContent, { editor }) : /* @__PURE__ */ jsx32("div", { className: "ree-rte-loading", children: "Loading editor\u2026" })
3454
+ ] });
3455
+ }
3456
+
3457
+ // src/stage/canvas/components/HtmlCanvas.tsx
3458
+ import { useCallback, useEffect as useEffect6, useRef as useRef10 } from "react";
3459
+
3460
+ // src/render/head-styles.ts
3461
+ function extractHeadStyles(html) {
3462
+ const styles = [];
3463
+ const next = html.replace(/<!--ree-head-->([\s\S]*?)<!--\/ree-head-->/g, (_, css) => {
3464
+ const text = css.trim();
3465
+ if (text) styles.push(text);
3466
+ return "";
3467
+ });
3468
+ return { html: next, styles: styles.join("\n") };
3469
+ }
3470
+
3471
+ // src/stage/canvas/components/HtmlCanvas.tsx
3472
+ import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
3473
+ function frameHtml(html) {
3474
+ const { html: body, styles } = extractHeadStyles(html);
3475
+ return `<!DOCTYPE html><html><head><style>html,body{margin:0;padding:0;}body{display:flow-root;}</style>${styles}</head><body>${body}</body></html>`;
3476
+ }
3477
+ function HtmlCanvas({ html, onSelect }) {
3478
+ const hasHtmlTag = /<\/?[a-z][\w:-]*(?:\s[^>]*)?>/i.test(html);
3479
+ const containerRef = useRef10(null);
3480
+ const frameRef = useRef10(null);
3481
+ const cleanupRef = useRef10();
3482
+ const observeContent = useCallback(() => {
3483
+ var _a;
3484
+ (_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
3485
+ const container = containerRef.current;
3486
+ const frame = frameRef.current;
3487
+ const document = frame == null ? void 0 : frame.contentDocument;
3488
+ if (!container || !frame || !(document == null ? void 0 : document.body)) return;
3489
+ const window = document.defaultView;
3490
+ if (!window) return;
3491
+ let animationFrame = 0;
3492
+ const fit = () => {
3493
+ const availableWidth = container.clientWidth;
3494
+ if (!availableWidth) return;
3495
+ frame.style.width = `${availableWidth}px`;
3496
+ frame.style.height = "1px";
3497
+ const contentHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
3498
+ frame.style.height = `${contentHeight}px`;
3499
+ container.style.height = `${contentHeight}px`;
3500
+ };
3501
+ const scheduleFit = () => {
3502
+ window.cancelAnimationFrame(animationFrame);
3503
+ animationFrame = window.requestAnimationFrame(fit);
3504
+ };
3505
+ const observer = new window.ResizeObserver(scheduleFit);
3506
+ let previousWidth = container.clientWidth;
3507
+ const containerObserver = new window.ResizeObserver(() => {
3508
+ if (container.clientWidth === previousWidth) return;
3509
+ previousWidth = container.clientWidth;
3510
+ scheduleFit();
3511
+ });
3512
+ containerObserver.observe(container);
3513
+ for (const child of document.body.children) observer.observe(child);
3514
+ document.addEventListener("load", scheduleFit, true);
3515
+ fit();
3516
+ cleanupRef.current = () => {
3517
+ observer.disconnect();
3518
+ containerObserver.disconnect();
3519
+ document.removeEventListener("load", scheduleFit, true);
3520
+ window.cancelAnimationFrame(animationFrame);
3521
+ };
3522
+ }, []);
3523
+ useEffect6(() => () => {
3524
+ var _a;
3525
+ return (_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
3526
+ }, [hasHtmlTag, html]);
3527
+ if (!hasHtmlTag) {
3528
+ return /* @__PURE__ */ jsx33(
3529
+ "div",
3530
+ {
3531
+ className: "ree-html-canvas ree-html-canvas-text",
3532
+ onClick: onSelect,
3533
+ dangerouslySetInnerHTML: { __html: html }
3534
+ }
3535
+ );
3536
+ }
3537
+ return /* @__PURE__ */ jsxs25(
3538
+ "div",
3539
+ {
3540
+ ref: containerRef,
3541
+ className: "ree-html-canvas",
3542
+ onClick: () => onSelect(),
3543
+ children: [
3544
+ /* @__PURE__ */ jsx33(
3545
+ "button",
3546
+ {
3547
+ type: "button",
3548
+ className: "ree-html-canvas-hitbox",
3549
+ "aria-label": "Select HTML block",
3550
+ tabIndex: -1,
3551
+ onPointerDown: () => onSelect(),
3552
+ onClick: () => onSelect()
3553
+ }
3554
+ ),
3555
+ /* @__PURE__ */ jsx33(
3556
+ "iframe",
3557
+ {
3558
+ ref: frameRef,
3559
+ className: "ree-html-canvas-frame",
3560
+ title: "HTML block preview",
3561
+ srcDoc: frameHtml(html),
3562
+ sandbox: "allow-same-origin",
3563
+ tabIndex: -1,
3564
+ onLoad: observeContent
3565
+ }
3566
+ )
3567
+ ]
3568
+ }
3569
+ );
3570
+ }
3571
+
3572
+ // src/stage/canvas/block-style.ts
3573
+ function canvasBlockStyle(block, global) {
3574
+ var _a, _b;
3575
+ const height = cssPx((_b = (_a = block.style) == null ? void 0 : _a.dimension) == null ? void 0 : _b.height);
3576
+ return __spreadValues(__spreadValues(__spreadValues({}, blockContentStyle(block.style, global)), contentVariables(block.style, global)), height ? { boxSizing: "border-box", minHeight: `${height}px` } : {});
3577
+ }
3578
+
3579
+ // src/stage/canvas/CanvasBlock.tsx
3580
+ import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
3581
+ function CanvasBlock({
3582
+ block,
3583
+ global,
3584
+ definition,
3585
+ selected,
3586
+ dropEdge,
3587
+ variables,
3588
+ onSelect,
3589
+ onCommitField,
3590
+ onMove,
3591
+ onDuplicate,
3592
+ onRemove
3593
+ }) {
3594
+ var _a, _b, _c, _d, _e, _f, _g, _h;
3595
+ const dragData = { kind: "canvas" };
3596
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: block.id, data: dragData });
3597
+ const stop = (event) => event.stopPropagation();
3598
+ const select = (event) => {
3599
+ event == null ? void 0 : event.stopPropagation();
3600
+ onSelect();
3601
+ };
3602
+ const renderedHtml = definition ? renderBlockContent(block, [definition], "canvas", {
3603
+ resolveHtmlTemplate: block.type === "html",
3604
+ templateData: mergeTemplateData(variables, block.defaultData),
3605
+ global
3606
+ }) : "";
3607
+ const usesTemplatePreview = block.type === "html";
3608
+ return /* @__PURE__ */ jsxs26(
3609
+ "div",
3610
+ {
3611
+ ref: setNodeRef,
3612
+ style: {
3613
+ transform: CSS.Transform.toString(transform),
3614
+ transition,
3615
+ padding: cssSpacing((_b = (_a = block.style) == null ? void 0 : _a.dimension) == null ? void 0 : _b.margin)
3616
+ },
3617
+ className: [
3618
+ "ree-block",
3619
+ selected ? "is-selected" : "",
3620
+ isDragging ? "is-dragging" : "",
3621
+ dropEdge === "before" ? "is-drop-before" : "",
3622
+ dropEdge === "after" ? "is-drop-after" : ""
3623
+ ].filter(Boolean).join(" "),
3624
+ onClick: select,
3625
+ children: [
3626
+ /* @__PURE__ */ jsx34(
3627
+ "button",
3628
+ __spreadProps(__spreadValues(__spreadValues({
3629
+ type: "button",
3630
+ className: "ree-block-grip"
3631
+ }, attributes), listeners), {
3632
+ onClick: select,
3633
+ title: "Drag to reorder",
3634
+ children: /* @__PURE__ */ jsxs26("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "currentColor", children: [
3635
+ /* @__PURE__ */ jsx34("circle", { cx: "9", cy: "5", r: "1.6" }),
3636
+ /* @__PURE__ */ jsx34("circle", { cx: "15", cy: "5", r: "1.6" }),
3637
+ /* @__PURE__ */ jsx34("circle", { cx: "9", cy: "12", r: "1.6" }),
3638
+ /* @__PURE__ */ jsx34("circle", { cx: "15", cy: "12", r: "1.6" }),
3639
+ /* @__PURE__ */ jsx34("circle", { cx: "9", cy: "19", r: "1.6" }),
3640
+ /* @__PURE__ */ jsx34("circle", { cx: "15", cy: "19", r: "1.6" })
3641
+ ] })
3642
+ })
3643
+ ),
3644
+ selected ? /* @__PURE__ */ jsxs26(
3645
+ "div",
3646
+ {
3647
+ className: "ree-block-toolbar",
3648
+ onClick: stop,
3649
+ onPointerDown: stop,
3650
+ onMouseDown: stop,
3651
+ children: [
3652
+ /* @__PURE__ */ jsx34(
3653
+ "button",
3654
+ {
3655
+ type: "button",
3656
+ className: "ree-block-btn",
3657
+ title: "Move up",
3658
+ onClick: () => onMove(-1),
3659
+ children: /* @__PURE__ */ jsx34("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx34("path", { d: "m18 15-6-6-6 6" }) })
3660
+ }
3661
+ ),
3662
+ /* @__PURE__ */ jsx34(
3663
+ "button",
3664
+ {
3665
+ type: "button",
3666
+ className: "ree-block-btn",
3667
+ title: "Move down",
3668
+ onClick: () => onMove(1),
3669
+ children: /* @__PURE__ */ jsx34("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx34("path", { d: "m6 9 6 6 6-6" }) })
3670
+ }
3671
+ ),
3672
+ /* @__PURE__ */ jsx34(
3673
+ "button",
3674
+ {
3675
+ type: "button",
3676
+ className: "ree-block-btn",
3677
+ title: "Duplicate",
3678
+ onClick: onDuplicate,
3679
+ children: /* @__PURE__ */ jsxs26("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3680
+ /* @__PURE__ */ jsx34("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
3681
+ /* @__PURE__ */ jsx34("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
3682
+ ] })
3683
+ }
3684
+ ),
3685
+ /* @__PURE__ */ jsx34(
3686
+ "button",
3687
+ {
3688
+ type: "button",
3689
+ className: "ree-block-btn ree-danger",
3690
+ title: "Delete",
3691
+ onClick: onRemove,
3692
+ children: /* @__PURE__ */ jsxs26("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3693
+ /* @__PURE__ */ jsx34("path", { d: "M3 6h18" }),
3694
+ /* @__PURE__ */ jsx34("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
3695
+ /* @__PURE__ */ jsx34("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
3696
+ ] })
3697
+ }
3698
+ )
3699
+ ]
3700
+ }
3701
+ ) : null,
3702
+ /* @__PURE__ */ jsx34(
3703
+ "div",
3704
+ {
3705
+ className: `ree-block-content${(definition == null ? void 0 : definition.inlineEdit) ? " is-inline" : ""}`,
3706
+ style: canvasBlockStyle(block, global),
3707
+ children: (definition == null ? void 0 : definition.inlineEdit) ? /* @__PURE__ */ jsx34(
3708
+ RichText,
3709
+ {
3710
+ initialHtml: String((_c = block.fields.html) != null ? _c : ""),
3711
+ variables,
3712
+ selected,
3713
+ inherited: {
3714
+ fontFamily: String(typographyStyle(block.style, global).fontFamily),
3715
+ fontSize: String(typographyStyle(block.style, global).fontSize),
3716
+ color: ((_e = (_d = block.style) == null ? void 0 : _d.typography) == null ? void 0 : _e.color) || global.textColor,
3717
+ fontWeight: resolvedFontWeight(
3718
+ (_g = (_f = block.style) == null ? void 0 : _f.typography) == null ? void 0 : _g.fontWeight,
3719
+ global.fontWeight
3720
+ ),
3721
+ lineHeight: String((_h = typographyStyle(block.style, global).lineHeight) != null ? _h : ""),
3722
+ paragraphSpacing: contentDefaults(block.style, global).paragraph
3723
+ },
3724
+ onSelect,
3725
+ onCommit: (html) => onCommitField(block.id, "html", html)
3726
+ }
3727
+ ) : usesTemplatePreview ? /* @__PURE__ */ jsx34(
3728
+ HtmlCanvas,
3729
+ {
3730
+ html: renderedHtml,
3731
+ onSelect: select
3732
+ }
3733
+ ) : /* @__PURE__ */ jsx34(
3734
+ "div",
3735
+ {
3736
+ className: "ree-block-static",
3737
+ dangerouslySetInnerHTML: {
3738
+ __html: definition ? definition.render(buildRenderContext(block, "canvas", global)) : `<!-- ${block.type} -->`
3739
+ }
3740
+ }
3741
+ )
3742
+ }
3743
+ ),
3744
+ definition ? /* @__PURE__ */ jsx34("span", { className: "ree-block-badge", children: definition.title }) : null
3745
+ ]
3746
+ }
3747
+ );
3748
+ }
3749
+
3750
+ // src/stage/canvas/Canvas.tsx
3751
+ import { jsx as jsx35 } from "react/jsx-runtime";
3752
+ function Canvas({
3753
+ blocks,
3754
+ global,
3755
+ definitions,
3756
+ selectedId,
3757
+ dropTargetId,
3758
+ dropEdge,
3759
+ variables,
3760
+ device,
3761
+ onSelect,
3762
+ onCommitField,
3763
+ onMove,
3764
+ onDuplicate,
3765
+ onRemove
3766
+ }) {
3767
+ const { setNodeRef, isOver } = useDroppable({
3768
+ id: CANVAS_DROP_ID,
3769
+ data: { kind: "canvas" }
3770
+ });
3771
+ return /* @__PURE__ */ jsx35(
3772
+ "div",
3773
+ {
3774
+ ref: setNodeRef,
3775
+ className: `ree-canvas${isOver ? " is-over" : ""}`,
3776
+ onClick: (event) => {
3777
+ const ownerDocument = event.currentTarget.ownerDocument;
3778
+ const ownerWindow = ownerDocument.defaultView;
3779
+ const target = event.target;
3780
+ if (!ownerWindow || !(target instanceof ownerWindow.Element)) return;
3781
+ if (target.closest(".ree-block")) return;
3782
+ onSelect(null);
3783
+ const active = ownerDocument.activeElement;
3784
+ if (active instanceof ownerWindow.HTMLElement && active.closest(".ree-rte")) {
3785
+ active.blur();
3786
+ }
3787
+ },
3788
+ children: /* @__PURE__ */ jsx35(
3789
+ "div",
3790
+ {
3791
+ className: "ree-email-viewport",
3792
+ style: {
3793
+ width: viewportWidth(device, global),
3794
+ fontSize: global.defaultFontSize,
3795
+ fontFamily: global.fontFamily,
3796
+ backgroundColor: global.backgroundColor,
3797
+ marginLeft: global.contentAlign === "left" ? 0 : "auto",
3798
+ marginRight: global.contentAlign === "right" ? 0 : "auto"
3799
+ },
3800
+ children: /* @__PURE__ */ jsx35("div", { className: "ree-email-outer", style: { padding: global.outerPadding }, children: /* @__PURE__ */ jsx35(
3801
+ "div",
3802
+ {
3803
+ className: "ree-email-surface",
3804
+ style: __spreadProps(__spreadValues({}, surfaceStyle(global)), {
3805
+ maxWidth: global.contentWidth,
3806
+ marginLeft: global.contentAlign === "left" ? 0 : "auto",
3807
+ marginRight: global.contentAlign === "right" ? 0 : "auto"
3808
+ }),
3809
+ children: /* @__PURE__ */ jsx35(
3810
+ "div",
3811
+ {
3812
+ className: "ree-canvas-inner",
3813
+ style: { "--ree-block-gap": global.blockGap },
3814
+ children: blocks.length === 0 ? /* @__PURE__ */ jsx35("div", { className: "ree-canvas-empty", children: "Drag a block here or click one to get started" }) : /* @__PURE__ */ jsx35(
3815
+ SortableContext,
3816
+ {
3817
+ items: blocks.map((block) => block.id),
3818
+ strategy: verticalListSortingStrategy,
3819
+ children: blocks.map((block) => /* @__PURE__ */ jsx35(
3820
+ CanvasBlock,
3821
+ {
3822
+ block,
3823
+ global,
3824
+ definition: getBlockDefinition(block.type, definitions),
3825
+ selected: block.id === selectedId,
3826
+ dropEdge: dropTargetId === block.id ? dropEdge : null,
3827
+ variables,
3828
+ onSelect: () => onSelect(block.id),
3829
+ onCommitField,
3830
+ onMove: (direction) => onMove(block.id, direction),
3831
+ onDuplicate: () => onDuplicate(block.id),
3832
+ onRemove: () => onRemove(block.id)
3833
+ },
3834
+ block.id
3835
+ ))
3836
+ }
3837
+ )
3838
+ }
3839
+ )
3840
+ }
3841
+ ) })
3842
+ }
3843
+ )
3844
+ }
3845
+ );
3846
+ }
3847
+
3848
+ // src/stage/preview/Preview.tsx
3849
+ import { useMemo as useMemo2 } from "react";
3850
+
3851
+ // src/render/document-html.ts
3852
+ function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
3853
+ const source = ensureEmailDocument(document);
3854
+ const global = source.global;
3855
+ const baseCss = emailBaseCss(global);
3856
+ const rendered = source.blocks.map((node, index) => {
3857
+ var _a, _b;
3858
+ const blockData = __spreadValues(__spreadValues({}, (_a = options.templateData) != null ? _a : {}), node.defaultData);
3859
+ const block = blockToHtml(node, definitions, "email", {
3860
+ resolveHtmlTemplate: options.resolveHtmlTemplates,
3861
+ global,
3862
+ variables: options.variables,
3863
+ templateData: mergeTemplateData(
3864
+ (_b = options.variables) != null ? _b : [],
3865
+ Object.keys(blockData).length > 0 ? blockData : void 0
3866
+ )
3867
+ });
3868
+ const gap = index < source.blocks.length - 1 ? spacerHtml(global.blockGap) : "";
3869
+ return block + gap;
3870
+ }).join("\n");
3871
+ const { html: body, styles } = extractHeadStyles(rendered);
3872
+ return [
3873
+ "<!DOCTYPE html>",
3874
+ '<html lang="en">',
3875
+ "<head>",
3876
+ '<meta charset="utf-8" />',
3877
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
3878
+ "<title>Email</title>",
3879
+ styles,
3880
+ "</head>",
3881
+ `<body style="margin:0;padding:0;${baseCss};background-color:${esc(global.backgroundColor)};word-spacing:normal;">`,
3882
+ `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="${baseCss};background-color:${esc(global.backgroundColor)};table-layout:fixed;">`,
3883
+ "<tr>",
3884
+ `<td align="${esc(global.contentAlign)}" style="padding:${esc(global.outerPadding)};${baseCss};">`,
3885
+ `<table role="presentation" align="${esc(global.contentAlign)}" width="${global.contentWidth}" cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:${global.contentWidth}px;margin-left:${global.contentAlign === "left" ? "0" : "auto"};margin-right:${global.contentAlign === "right" ? "0" : "auto"};table-layout:fixed;">`,
3886
+ "<tr>",
3887
+ `<td style="box-sizing:border-box;${baseCss};${styleToCss(surfaceStyle(global))};">`,
3888
+ body,
3889
+ "</td>",
3890
+ "</tr>",
3891
+ "</table>",
3892
+ "</td>",
3893
+ "</tr>",
3894
+ "</table>",
3895
+ "</body>",
3896
+ "</html>"
3897
+ ].join("\n");
3898
+ }
3899
+
3900
+ // src/stage/preview/Preview.tsx
3901
+ import { jsx as jsx36 } from "react/jsx-runtime";
3902
+ function Preview({ document, definitions, variables, device }) {
3903
+ const html = useMemo2(
3904
+ () => documentToHtml(document, definitions, {
3905
+ resolveHtmlTemplates: true,
3906
+ variables
3907
+ }),
3908
+ [document, definitions, variables]
3909
+ );
3910
+ return /* @__PURE__ */ jsx36("div", { className: "ree-preview-stage", children: /* @__PURE__ */ jsx36(
3911
+ "iframe",
3912
+ {
3913
+ className: "ree-preview-frame",
3914
+ title: "Email preview",
3915
+ srcDoc: html,
3916
+ style: {
3917
+ width: viewportWidth(device, document.global),
3918
+ fontSize: document.global.defaultFontSize,
3919
+ marginLeft: document.global.contentAlign === "left" ? 0 : "auto",
3920
+ marginRight: document.global.contentAlign === "right" ? 0 : "auto",
3921
+ backgroundColor: document.global.backgroundColor || "#ffffff"
3922
+ }
3923
+ }
3924
+ ) });
3925
+ }
3926
+
3927
+ // src/stage/StageHeader.tsx
3928
+ import { jsx as jsx37, jsxs as jsxs27 } from "react/jsx-runtime";
3929
+ function StageHeader({
3930
+ viewMode,
3931
+ device,
3932
+ onViewModeChange,
3933
+ onDeviceChange
3934
+ }) {
3935
+ return /* @__PURE__ */ jsxs27("div", { className: "ree-col-head ree-stage-head", children: [
3936
+ /* @__PURE__ */ jsxs27("div", { className: "ree-segment", role: "group", "aria-label": "Editor mode", children: [
3937
+ /* @__PURE__ */ jsx37(
3938
+ "button",
3939
+ {
3940
+ type: "button",
3941
+ className: `ree-segment-btn${viewMode === "edit" ? " is-active" : ""}`,
3942
+ "aria-pressed": viewMode === "edit",
3943
+ "aria-label": "Edit",
3944
+ title: "Edit",
3945
+ onClick: () => onViewModeChange("edit"),
3946
+ children: /* @__PURE__ */ jsxs27("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3947
+ /* @__PURE__ */ jsx37("path", { d: "M12 20h9" }),
3948
+ /* @__PURE__ */ jsx37("path", { d: "M16.5 3.5a2.1 2.1 0 0 1 3 3L8 18l-4 1 1-4 11.5-11.5Z" })
3949
+ ] })
3950
+ }
3951
+ ),
3952
+ /* @__PURE__ */ jsx37("span", { className: "ree-segment-divider", "aria-hidden": "true" }),
3953
+ /* @__PURE__ */ jsx37(
3954
+ "button",
3955
+ {
3956
+ type: "button",
3957
+ className: `ree-segment-btn ree-segment-btn-label${viewMode === "preview" ? " is-active" : ""}`,
3958
+ "aria-pressed": viewMode === "preview",
3959
+ "aria-label": "Preview",
3960
+ title: "Preview",
3961
+ onClick: () => onViewModeChange("preview"),
3962
+ children: /* @__PURE__ */ jsxs27("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3963
+ /* @__PURE__ */ jsx37("path", { d: "M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z" }),
3964
+ /* @__PURE__ */ jsx37("circle", { cx: "12", cy: "12", r: "2.5" })
3965
+ ] })
3966
+ }
3967
+ )
3968
+ ] }),
3969
+ /* @__PURE__ */ jsxs27("div", { className: "ree-segment", role: "group", "aria-label": "Canvas device", children: [
3970
+ /* @__PURE__ */ jsx37(
3971
+ "button",
3972
+ {
3973
+ type: "button",
3974
+ className: `ree-segment-btn${device === "desktop" ? " is-active" : ""}`,
3975
+ "aria-pressed": device === "desktop",
3976
+ "aria-label": "Desktop",
3977
+ title: "Desktop",
3978
+ onClick: () => onDeviceChange("desktop"),
3979
+ children: /* @__PURE__ */ jsxs27("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3980
+ /* @__PURE__ */ jsx37("rect", { x: "3", y: "4", width: "18", height: "12", rx: "1.5" }),
3981
+ /* @__PURE__ */ jsx37("path", { d: "M8 20h8" }),
3982
+ /* @__PURE__ */ jsx37("path", { d: "M12 16v4" })
3983
+ ] })
3984
+ }
3985
+ ),
3986
+ /* @__PURE__ */ jsx37("span", { className: "ree-segment-divider", "aria-hidden": "true" }),
3987
+ /* @__PURE__ */ jsx37(
3988
+ "button",
3989
+ {
3990
+ type: "button",
3991
+ className: `ree-segment-btn${device === "mobile" ? " is-active" : ""}`,
3992
+ "aria-pressed": device === "mobile",
3993
+ "aria-label": "Mobile",
3994
+ title: "Mobile",
3995
+ onClick: () => onDeviceChange("mobile"),
3996
+ children: /* @__PURE__ */ jsxs27("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3997
+ /* @__PURE__ */ jsx37("rect", { x: "7", y: "2.5", width: "10", height: "19", rx: "2" }),
3998
+ /* @__PURE__ */ jsx37("path", { d: "M11 18.5h2" })
3999
+ ] })
4000
+ }
4001
+ )
4002
+ ] })
4003
+ ] });
4004
+ }
4005
+
4006
+ // src/stage/Stage.tsx
4007
+ import { jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
4008
+ function Stage({
4009
+ session,
4010
+ viewMode,
4011
+ device,
4012
+ onViewModeChange,
4013
+ onDeviceChange,
4014
+ onSelectBlock
4015
+ }) {
4016
+ const { targetId, dropEdge } = useEditorDragState();
4017
+ return /* @__PURE__ */ jsxs28("div", { className: "ree-col ree-col-center", children: [
4018
+ /* @__PURE__ */ jsx38(
4019
+ StageHeader,
4020
+ {
4021
+ viewMode,
4022
+ device,
4023
+ onViewModeChange,
4024
+ onDeviceChange
4025
+ }
4026
+ ),
4027
+ viewMode === "edit" ? /* @__PURE__ */ jsx38("div", { className: "ree-stage-view", children: /* @__PURE__ */ jsx38(
4028
+ Canvas,
4029
+ {
4030
+ blocks: session.document.blocks,
4031
+ global: session.document.global,
4032
+ definitions: session.definitions,
4033
+ selectedId: session.selectedId,
4034
+ dropTargetId: targetId,
4035
+ dropEdge,
4036
+ variables: session.variables,
4037
+ device,
4038
+ onSelect: onSelectBlock,
4039
+ onCommitField: session.updateField,
4040
+ onMove: session.moveBlock,
4041
+ onDuplicate: session.duplicateBlock,
4042
+ onRemove: session.removeBlock
4043
+ }
4044
+ ) }) : /* @__PURE__ */ jsx38("div", { className: "ree-stage-view", children: /* @__PURE__ */ jsx38(
4045
+ Preview,
4046
+ {
4047
+ document: session.document,
4048
+ definitions: session.definitions,
4049
+ variables: session.variables,
4050
+ device
4051
+ }
4052
+ ) })
4053
+ ] });
4054
+ }
4055
+
4056
+ // src/shell/constants.ts
4057
+ var EMPTY_BLOCK_DEFINITIONS = [];
4058
+ var EMPTY_VARIABLES = [];
4059
+ var PERSONALIZATION_HINT = "Personalization fields will appear blank if no data is available.";
4060
+
4061
+ // src/shell/SubjectBar.tsx
4062
+ import { useRef as useRef12 } from "react";
4063
+
4064
+ // src/shell/PersonalizationMenu.tsx
4065
+ import { useEffect as useEffect7, useRef as useRef11, useState as useState9 } from "react";
4066
+ import { jsx as jsx39, jsxs as jsxs29 } from "react/jsx-runtime";
4067
+ function PersonalizationMenu({
4068
+ variables,
4069
+ onInsert
4070
+ }) {
4071
+ const [open, setOpen] = useState9(false);
4072
+ const rootRef = useRef11(null);
4073
+ useEffect7(() => {
4074
+ const root = rootRef.current;
4075
+ if (!open || !root) return;
4076
+ const ownerDocument = root.ownerDocument;
4077
+ const onPointerDown = (event) => {
4078
+ if (!root.contains(event.target)) setOpen(false);
4079
+ };
4080
+ ownerDocument.addEventListener("pointerdown", onPointerDown, true);
4081
+ return () => ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
4082
+ }, [open]);
4083
+ return /* @__PURE__ */ jsxs29("div", { className: "ree-personalization", ref: rootRef, children: [
4084
+ /* @__PURE__ */ jsxs29(
4085
+ "button",
4086
+ {
4087
+ type: "button",
4088
+ className: "ree-personalization-btn",
4089
+ "aria-expanded": open,
4090
+ onClick: () => setOpen((current) => !current),
4091
+ children: [
4092
+ "Personalization",
4093
+ /* @__PURE__ */ jsx39("svg", { viewBox: "0 0 20 20", width: "16", height: "16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx39("path", { d: "M5.2 7.5 10 12.3l4.8-4.8 1.2 1.2L10 14.7 4 8.7l1.2-1.2Z" }) })
4094
+ ]
4095
+ }
4096
+ ),
4097
+ open ? /* @__PURE__ */ jsx39("div", { className: "ree-var-menu ree-personalization-menu", children: variables.length === 0 ? /* @__PURE__ */ jsx39("div", { className: "ree-personalization-empty", children: "No personalization fields" }) : variables.map((variable) => /* @__PURE__ */ jsxs29(
4098
+ "button",
4099
+ {
4100
+ type: "button",
4101
+ className: "ree-var-item",
4102
+ onClick: () => {
4103
+ onInsert(variable.name);
4104
+ setOpen(false);
4105
+ },
4106
+ children: [
4107
+ /* @__PURE__ */ jsx39("span", { className: "ree-var-name", children: variable.name }),
4108
+ variable.description ? /* @__PURE__ */ jsx39("span", { className: "ree-var-label", children: variable.description }) : null
4109
+ ]
4110
+ },
4111
+ variable.name
4112
+ )) }) : null
4113
+ ] });
4114
+ }
4115
+
4116
+ // src/shell/SubjectBar.tsx
4117
+ import { Fragment as Fragment3, jsx as jsx40, jsxs as jsxs30 } from "react/jsx-runtime";
4118
+ function SubjectBar({
4119
+ subject,
4120
+ variables,
4121
+ onSubjectChange,
4122
+ onReset
4123
+ }) {
4124
+ const inputRef = useRef12(null);
4125
+ const insertVariable = (token) => {
4126
+ var _a, _b;
4127
+ const input = inputRef.current;
4128
+ const start = (_a = input == null ? void 0 : input.selectionStart) != null ? _a : subject.length;
4129
+ const end = (_b = input == null ? void 0 : input.selectionEnd) != null ? _b : subject.length;
4130
+ const next = (subject.slice(0, start) + token + subject.slice(end)).slice(
4131
+ 0,
4132
+ SUBJECT_MAX_LENGTH
4133
+ );
4134
+ onSubjectChange(next);
4135
+ const cursor = Math.min(start + token.length, next.length);
4136
+ requestAnimationFrame(() => {
4137
+ var _a2, _b2;
4138
+ (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
4139
+ (_b2 = inputRef.current) == null ? void 0 : _b2.setSelectionRange(cursor, cursor);
4140
+ });
4141
+ };
4142
+ return /* @__PURE__ */ jsxs30("div", { className: "ree-subject-bar", children: [
4143
+ /* @__PURE__ */ jsx40("label", { className: "ree-subject-label", htmlFor: "ree-email-subject", children: "Email subject" }),
4144
+ /* @__PURE__ */ jsx40(
4145
+ "input",
4146
+ {
4147
+ id: "ree-email-subject",
4148
+ ref: inputRef,
4149
+ className: "ree-subject-input",
4150
+ value: subject,
4151
+ maxLength: SUBJECT_MAX_LENGTH,
4152
+ onChange: (event) => onSubjectChange(event.target.value)
4153
+ }
4154
+ ),
4155
+ /* @__PURE__ */ jsxs30("span", { className: "ree-subject-count", children: [
4156
+ subject.length,
4157
+ "/",
4158
+ SUBJECT_MAX_LENGTH
4159
+ ] }),
4160
+ variables.length > 0 ? /* @__PURE__ */ jsxs30(Fragment3, { children: [
4161
+ /* @__PURE__ */ jsx40(PersonalizationMenu, { variables, onInsert: insertVariable }),
4162
+ /* @__PURE__ */ jsx40("p", { className: "ree-subject-hint", children: PERSONALIZATION_HINT })
4163
+ ] }) : null,
4164
+ /* @__PURE__ */ jsx40("button", { type: "button", className: "ree-btn ree-subject-reset", onClick: onReset, children: "Reset" })
4165
+ ] });
4166
+ }
4167
+
4168
+ // src/shell/useEditorSession.ts
4169
+ import { useCallback as useCallback2, useEffect as useEffect8, useMemo as useMemo3, useRef as useRef13, useState as useState10 } from "react";
4170
+
4171
+ // src/utils/id.ts
4172
+ function generateId(prefix = "block") {
4173
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
4174
+ return `${prefix}-${crypto.randomUUID().slice(0, 8)}`;
4175
+ }
4176
+ return `${prefix}-${Math.random().toString(36).slice(2, 10)}`;
4177
+ }
4178
+
4179
+ // src/document/create-block.ts
4180
+ function createBlockNode(type, definitions) {
4181
+ var _a, _b;
4182
+ const definition = getBlockDefinition(type, definitions);
4183
+ if (!definition) return null;
4184
+ const fields = __spreadValues({}, (_a = definition.defaultFields) != null ? _a : {});
4185
+ for (const field of (_b = definition.fields) != null ? _b : []) {
4186
+ if (!(field.name in fields) && field.defaultValue !== void 0) {
4187
+ fields[field.name] = field.defaultValue;
4188
+ }
4189
+ }
4190
+ if (definition.inlineEdit && !("html" in fields)) {
4191
+ fields.html = "<p></p>";
4192
+ }
4193
+ return {
4194
+ id: generateId(type),
4195
+ type,
4196
+ fields,
4197
+ style: definition.defaultStyle ? structuredClone(definition.defaultStyle) : void 0,
4198
+ defaultData: definition.defaultData ? structuredClone(definition.defaultData) : void 0
4199
+ };
4200
+ }
4201
+
4202
+ // src/document/mutations.ts
4203
+ function moveItem(items, from, to) {
4204
+ const next = items.slice();
4205
+ const [item] = next.splice(from, 1);
4206
+ if (item === void 0) return items;
4207
+ next.splice(to, 0, item);
4208
+ return next;
4209
+ }
4210
+ function insertBlockNode(document, node, index) {
4211
+ const blocks = [...document.blocks];
4212
+ const at = index != null ? index : blocks.length;
4213
+ blocks.splice(Math.max(0, Math.min(at, blocks.length)), 0, node);
4214
+ return __spreadProps(__spreadValues({}, document), { blocks });
4215
+ }
4216
+ function updateBlockField(document, id, name, value) {
4217
+ return __spreadProps(__spreadValues({}, document), {
4218
+ blocks: document.blocks.map(
4219
+ (block) => block.id === id ? __spreadProps(__spreadValues({}, block), { fields: __spreadProps(__spreadValues({}, block.fields), { [name]: value }) }) : block
4220
+ )
4221
+ });
4222
+ }
4223
+ function updateBlockStyle(document, id, styleId, value) {
4224
+ return __spreadProps(__spreadValues({}, document), {
4225
+ blocks: document.blocks.map(
4226
+ (block) => block.id === id ? __spreadProps(__spreadValues({}, block), { style: writeStyleValue(block.style, styleId, value) }) : block
4227
+ )
4228
+ });
4229
+ }
4230
+ function moveBlockBy(document, id, direction) {
4231
+ const index = document.blocks.findIndex((block) => block.id === id);
4232
+ const target = index + direction;
4233
+ if (index < 0 || target < 0 || target >= document.blocks.length) return document;
4234
+ return __spreadProps(__spreadValues({}, document), { blocks: moveItem(document.blocks, index, target) });
4235
+ }
4236
+ function duplicateBlockNode(document, id) {
4237
+ const index = document.blocks.findIndex((block) => block.id === id);
4238
+ if (index < 0) return document;
4239
+ const source = document.blocks[index];
4240
+ const copy = __spreadProps(__spreadValues({}, structuredClone(source)), {
4241
+ id: generateId(source.type)
4242
+ });
4243
+ const blocks = [...document.blocks];
4244
+ blocks.splice(index + 1, 0, copy);
4245
+ return __spreadProps(__spreadValues({}, document), { blocks });
4246
+ }
4247
+ function removeBlockNode(document, id) {
4248
+ return __spreadProps(__spreadValues({}, document), {
4249
+ blocks: document.blocks.filter((block) => block.id !== id)
4250
+ });
4251
+ }
4252
+ function reorderBlockNodes(document, activeId, overId) {
4253
+ if (activeId === overId) return document;
4254
+ const oldIndex = document.blocks.findIndex((block) => block.id === activeId);
4255
+ const newIndex = document.blocks.findIndex((block) => block.id === overId);
4256
+ if (oldIndex < 0 || newIndex < 0) return document;
4257
+ return __spreadProps(__spreadValues({}, document), { blocks: moveItem(document.blocks, oldIndex, newIndex) });
4258
+ }
4259
+ function withSubject(document, subject) {
4260
+ return __spreadProps(__spreadValues({}, document), { subject });
4261
+ }
4262
+ function updateGlobalSettings(document, patch) {
4263
+ return __spreadProps(__spreadValues({}, document), {
4264
+ global: mergeEmailGlobal(document.global, patch)
4265
+ });
4266
+ }
4267
+
4268
+ // src/shell/useEditorSession.ts
4269
+ function useEditorSession({
4270
+ initialDocument,
4271
+ global,
4272
+ blocks = EMPTY_BLOCK_DEFINITIONS,
4273
+ variables = EMPTY_VARIABLES,
4274
+ onChange,
4275
+ onGlobalChange
4276
+ }) {
4277
+ var _a;
4278
+ const definitions = useMemo3(() => mergeBlockDefinitions(blocks), [blocks]);
4279
+ const [baseline] = useState10(() => {
4280
+ const base = ensureEmailDocument(
4281
+ structuredClone(initialDocument != null ? initialDocument : createEmptyDocument())
4282
+ );
4283
+ if (global === void 0) return base;
4284
+ return __spreadProps(__spreadValues({}, base), { global: mergeEmailGlobal(base.global, global) });
4285
+ });
4286
+ const [document, setDocument] = useState10(
4287
+ () => structuredClone(baseline)
4288
+ );
4289
+ const [selectedId, setSelectedId] = useState10(null);
4290
+ const documentRef = useRef13(document);
4291
+ documentRef.current = document;
4292
+ const globalPropRef = useRef13(global);
4293
+ globalPropRef.current = global;
4294
+ const commit = useCallback2(
4295
+ (next) => {
4296
+ documentRef.current = next;
4297
+ setDocument(next);
4298
+ onChange == null ? void 0 : onChange(next);
4299
+ },
4300
+ [onChange]
4301
+ );
4302
+ useEffect8(() => {
4303
+ if (global === void 0) return;
4304
+ const current = documentRef.current;
4305
+ const nextGlobal = mergeEmailGlobal(current.global, global);
4306
+ if (sameEmailGlobal(resolveEmailGlobal(current.global), nextGlobal)) return;
4307
+ commit(__spreadProps(__spreadValues({}, current), { global: nextGlobal }));
4308
+ }, [global, commit]);
4309
+ const getDocument = useCallback2(
4310
+ () => ensureEmailDocument(structuredClone(documentRef.current)),
4311
+ []
4312
+ );
4313
+ const selectBlock = useCallback2((id) => {
4314
+ setSelectedId(id);
4315
+ }, []);
4316
+ const insertBlock = useCallback2(
4317
+ (type, index) => {
4318
+ const node = createBlockNode(type, definitions);
4319
+ if (!node) return false;
4320
+ commit(insertBlockNode(documentRef.current, node, index));
4321
+ setSelectedId(node.id);
4322
+ return true;
4323
+ },
4324
+ [commit, definitions]
4325
+ );
4326
+ const updateField = useCallback2(
4327
+ (id, name, value) => {
4328
+ commit(updateBlockField(documentRef.current, id, name, value));
4329
+ },
4330
+ [commit]
4331
+ );
4332
+ const updateStyle = useCallback2(
4333
+ (id, styleId, value) => {
4334
+ commit(updateBlockStyle(documentRef.current, id, styleId, value));
4335
+ },
4336
+ [commit]
4337
+ );
4338
+ const updateGlobal = useCallback2(
4339
+ (patch) => {
4340
+ const next = updateGlobalSettings(documentRef.current, patch);
4341
+ commit(next);
4342
+ onGlobalChange == null ? void 0 : onGlobalChange(next.global);
4343
+ },
4344
+ [commit, onGlobalChange]
4345
+ );
4346
+ const moveBlock = useCallback2(
4347
+ (id, direction) => {
4348
+ commit(moveBlockBy(documentRef.current, id, direction));
4349
+ },
4350
+ [commit]
4351
+ );
4352
+ const duplicateBlock = useCallback2(
4353
+ (id) => {
4354
+ commit(duplicateBlockNode(documentRef.current, id));
4355
+ },
4356
+ [commit]
4357
+ );
4358
+ const removeBlock = useCallback2(
4359
+ (id) => {
4360
+ commit(removeBlockNode(documentRef.current, id));
4361
+ setSelectedId((current) => current === id ? null : current);
4362
+ },
4363
+ [commit]
4364
+ );
4365
+ const reorderBlocks = useCallback2(
4366
+ (activeId, overId) => {
4367
+ commit(reorderBlockNodes(documentRef.current, activeId, overId));
4368
+ },
4369
+ [commit]
4370
+ );
4371
+ const setSubject = useCallback2(
4372
+ (subject) => {
4373
+ commit(withSubject(documentRef.current, subject.slice(0, SUBJECT_MAX_LENGTH)));
4374
+ },
4375
+ [commit]
4376
+ );
4377
+ const reset = useCallback2(() => {
4378
+ const restored = structuredClone(baseline);
4379
+ const incoming = globalPropRef.current;
4380
+ const next = incoming === void 0 ? restored : __spreadProps(__spreadValues({}, restored), { global: mergeEmailGlobal(restored.global, incoming) });
4381
+ commit(next);
4382
+ setSelectedId(null);
4383
+ }, [baseline, commit]);
4384
+ const resolvedDocument = useMemo3(() => ensureEmailDocument(document), [document]);
4385
+ const selectedBlock = (_a = resolvedDocument.blocks.find((block) => block.id === selectedId)) != null ? _a : null;
4386
+ const selectedDefinition = selectedBlock ? getBlockDefinition(selectedBlock.type, definitions) : void 0;
4387
+ return {
4388
+ document: resolvedDocument,
4389
+ getDocument,
4390
+ definitions,
4391
+ variables,
4392
+ selectedId,
4393
+ selectedBlock,
4394
+ selectedDefinition,
4395
+ selectBlock,
4396
+ insertBlock,
4397
+ updateField,
4398
+ updateStyle,
4399
+ updateGlobal,
4400
+ moveBlock,
4401
+ duplicateBlock,
4402
+ removeBlock,
4403
+ reorderBlocks,
4404
+ setSubject,
4405
+ reset
4406
+ };
4407
+ }
4408
+
4409
+ // src/shell/useEditorHandle.ts
4410
+ import { useImperativeHandle } from "react";
4411
+ function useEditorHandle(ref, { getDocument, definitions, variables }) {
4412
+ useImperativeHandle(ref, () => ({
4413
+ getData: () => {
4414
+ const document = getDocument();
4415
+ return {
4416
+ document,
4417
+ json: JSON.stringify(document, null, 2),
4418
+ html: documentToHtml(document, definitions, { variables }),
4419
+ renderedHtml: documentToHtml(document, definitions, {
4420
+ resolveHtmlTemplates: true,
4421
+ variables
4422
+ })
4423
+ };
4424
+ },
4425
+ getDocument,
4426
+ getJson: () => JSON.stringify(getDocument(), null, 2),
4427
+ getHtml: () => documentToHtml(getDocument(), definitions, { variables }),
4428
+ getRenderedHtml: () => documentToHtml(getDocument(), definitions, {
4429
+ resolveHtmlTemplates: true,
4430
+ variables
4431
+ })
4432
+ }), [getDocument, definitions, variables]);
4433
+ }
4434
+
4435
+ // src/shell/EmailEditorShell.tsx
4436
+ import { jsx as jsx41, jsxs as jsxs31 } from "react/jsx-runtime";
4437
+ var EmailEditorShell = forwardRef(
4438
+ function EmailEditorShell2({
4439
+ initialDocument,
4440
+ global,
4441
+ blocks,
4442
+ variables = EMPTY_VARIABLES,
4443
+ height = "100%",
4444
+ onChange,
4445
+ onGlobalChange
4446
+ }, ref) {
4447
+ var _a;
4448
+ const session = useEditorSession({
4449
+ initialDocument,
4450
+ global,
4451
+ blocks,
4452
+ variables,
4453
+ onChange,
4454
+ onGlobalChange
4455
+ });
4456
+ useEditorHandle(ref, session);
4457
+ const [viewMode, setViewMode] = useState11("edit");
4458
+ const [device, setDevice] = useState11("desktop");
4459
+ const [inspectorPanel, setInspectorPanel] = useState11("global");
4460
+ useEffect9(() => {
4461
+ if (!session.selectedId) setInspectorPanel("global");
4462
+ }, [session.selectedId]);
4463
+ const selectBlock = (id) => {
4464
+ const hadSelection = session.selectedId !== null;
4465
+ session.selectBlock(id);
4466
+ if (!id) {
4467
+ setInspectorPanel("global");
4468
+ return;
4469
+ }
4470
+ if (!hadSelection) setInspectorPanel("block");
4471
+ };
4472
+ const handleKeyDown = (event) => {
4473
+ if (event.key !== "Escape") return;
4474
+ const target = event.target;
4475
+ const tag = target == null ? void 0 : target.tagName;
4476
+ if ((target == null ? void 0 : target.isContentEditable) || tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
4477
+ return;
4478
+ }
4479
+ selectBlock(null);
4480
+ };
4481
+ const showCanvas = () => setViewMode("edit");
4482
+ return /* @__PURE__ */ jsxs31("div", { className: "ree-root", style: { height }, onKeyDown: handleKeyDown, children: [
4483
+ /* @__PURE__ */ jsx41(
4484
+ SubjectBar,
4485
+ {
4486
+ subject: (_a = session.document.subject) != null ? _a : "",
4487
+ variables: session.variables,
4488
+ onSubjectChange: session.setSubject,
4489
+ onReset: session.reset
4490
+ }
4491
+ ),
4492
+ /* @__PURE__ */ jsxs31("div", { className: "ree-body", children: [
4493
+ /* @__PURE__ */ jsxs31(
4494
+ EditorDnd,
4495
+ {
4496
+ blocks: session.document.blocks,
4497
+ definitions: session.definitions,
4498
+ onInsertBlock: (type, index) => {
4499
+ showCanvas();
4500
+ if (session.insertBlock(type, index)) setInspectorPanel("block");
4501
+ },
4502
+ onReorder: session.reorderBlocks,
4503
+ onLibraryDragStart: showCanvas,
4504
+ children: [
4505
+ /* @__PURE__ */ jsx41(
4506
+ BlockLibrary,
4507
+ {
4508
+ definitions: session.definitions,
4509
+ onAdd: (type) => {
4510
+ showCanvas();
4511
+ if (session.insertBlock(type)) setInspectorPanel("block");
4512
+ }
4513
+ }
4514
+ ),
4515
+ /* @__PURE__ */ jsx41(
4516
+ Stage,
4517
+ {
4518
+ session,
4519
+ viewMode,
4520
+ device,
4521
+ onViewModeChange: setViewMode,
4522
+ onDeviceChange: setDevice,
4523
+ onSelectBlock: selectBlock
4524
+ }
4525
+ )
4526
+ ]
4527
+ }
4528
+ ),
4529
+ /* @__PURE__ */ jsx41(
4530
+ Inspector,
4531
+ {
4532
+ panel: inspectorPanel,
4533
+ global: session.document.global,
4534
+ block: session.selectedBlock,
4535
+ definition: session.selectedDefinition,
4536
+ variables: session.variables,
4537
+ onPanelChange: setInspectorPanel,
4538
+ onCommitGlobal: session.updateGlobal,
4539
+ onCommitField: session.updateField,
4540
+ onCommitStyle: session.updateStyle
4541
+ }
4542
+ )
4543
+ ] })
4544
+ ] });
4545
+ }
4546
+ );
4547
+
4548
+ // src/EmailEditor.tsx
4549
+ import { jsx as jsx42 } from "react/jsx-runtime";
4550
+ var EmailEditor = forwardRef2(
4551
+ function EmailEditor2(props, ref) {
4552
+ return /* @__PURE__ */ jsx42(EmailEditorShell, __spreadProps(__spreadValues({}, props), { ref }));
4553
+ }
4554
+ );
4555
+ export {
4556
+ BLOCK_TYPES,
4557
+ BORDER_STYLES,
4558
+ DEFAULT_BLOCKS,
4559
+ DEFAULT_EMAIL_GLOBAL,
4560
+ DOCUMENT_VERSION,
4561
+ EmailEditor,
4562
+ FONT_FAMILIES,
4563
+ FONT_WEIGHTS,
4564
+ HORIZONTAL_ALIGNS,
4565
+ LINK_DECORATIONS,
4566
+ RichText,
4567
+ STYLE_GROUPS,
4568
+ TEXT_ALIGNS,
4569
+ blockToHtml,
4570
+ buildRenderContext,
4571
+ createBlockNode,
4572
+ createDecorationFields,
4573
+ createDimensionFields,
4574
+ createEmailDocument,
4575
+ createTypographyFields,
4576
+ documentToHtml,
4577
+ generateId,
4578
+ getBlockDefinition,
4579
+ mergeTemplateData,
4580
+ renderBlock,
4581
+ renderBlockContent,
4582
+ renderTemplatePreview,
4583
+ resolveBlockStyleFields,
4584
+ resolveEmailGlobal,
4585
+ variablesToTemplateData
4586
+ };