@standhigher/react-email-editor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,15 +18,27 @@ var __spreadValues = (a, b) => {
18
18
  return a;
19
19
  };
20
20
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+ var __objRest = (source, exclude) => {
22
+ var target = {};
23
+ for (var prop in source)
24
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
25
+ target[prop] = source[prop];
26
+ if (source != null && __getOwnPropSymbols)
27
+ for (var prop of __getOwnPropSymbols(source)) {
28
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
29
+ target[prop] = source[prop];
30
+ }
31
+ return target;
32
+ };
21
33
 
22
34
  // src/EmailEditor.tsx
23
- import { forwardRef as forwardRef2 } from "react";
35
+ import { forwardRef as forwardRef3 } from "react";
24
36
 
25
37
  // src/shell/EmailEditorShell.tsx
26
- import { forwardRef, useEffect as useEffect9, useState as useState11 } from "react";
38
+ import { forwardRef as forwardRef2, useState as useState15 } from "react";
27
39
 
28
40
  // src/editor-dnd/EditorDnd.tsx
29
- import { useMemo, useRef, useState } from "react";
41
+ import { useLayoutEffect, useMemo, useRef, useState } from "react";
30
42
  import {
31
43
  DndContext,
32
44
  DragOverlay,
@@ -61,6 +73,51 @@ function BlockIcon({ icon, className }) {
61
73
  return /* @__PURE__ */ jsx("span", { className, children: icon });
62
74
  }
63
75
 
76
+ // src/blocks/inspector-sections.ts
77
+ var SPACING = {
78
+ title: "Spacing",
79
+ styleIds: ["dimension.padding", "dimension.margin"]
80
+ };
81
+ var BLOCK_BORDERS = ["decorations.borderWidth", "decorations.borderStyle", "decorations.borderColor", "decorations.borderRadius"];
82
+ var BLOCK_APPEARANCE = {
83
+ title: "Block appearance",
84
+ styleIds: ["decorations.backgroundColor", ...BLOCK_BORDERS]
85
+ };
86
+ var ADVANCED_BLOCK_APPEARANCE = {
87
+ title: "Block appearance",
88
+ styleIds: ["decorations.backgroundColor"],
89
+ advancedStyleIds: BLOCK_BORDERS
90
+ };
91
+ var ELEMENT_BORDERS = ["element.borderWidth", "element.borderStyle", "element.borderColor", "element.borderRadius"];
92
+ var HEADING_SECTIONS = [SPACING, BLOCK_APPEARANCE];
93
+ var TEXT_SECTIONS = [SPACING, BLOCK_APPEARANCE];
94
+ var HTML_SECTIONS = [
95
+ { title: "HTML", fieldNames: ["html"] },
96
+ SPACING,
97
+ BLOCK_APPEARANCE
98
+ ];
99
+ var BUTTON_SECTIONS = [
100
+ { title: "Link", fieldNames: ["href"] },
101
+ { title: "Button", styleIds: ["element.align", "element.width", "element.padding", "element.backgroundColor", ...ELEMENT_BORDERS] },
102
+ SPACING,
103
+ ADVANCED_BLOCK_APPEARANCE
104
+ ];
105
+ var IMAGE_SECTIONS = [
106
+ { title: "Image", fieldNames: ["src", "alt", "link"] },
107
+ { title: "Size & position", styleIds: ["element.width", "element.height", "element.align"] },
108
+ { title: "Image appearance", styleIds: ELEMENT_BORDERS },
109
+ SPACING,
110
+ ADVANCED_BLOCK_APPEARANCE
111
+ ];
112
+ var DIVIDER_SECTIONS = [
113
+ { title: "Line", styleIds: ["element.borderColor", "element.borderWidth", "element.borderStyle", "element.width", "element.align"] },
114
+ SPACING,
115
+ { title: "Block appearance", styleIds: ["decorations.backgroundColor"] }
116
+ ];
117
+ var SPACER_SECTIONS = [
118
+ { title: "Spacer", fieldNames: ["height"], styleIds: ["decorations.backgroundColor"] }
119
+ ];
120
+
64
121
  // src/constants/block-types.ts
65
122
  var BLOCK_TYPES = {
66
123
  heading: "heading",
@@ -76,6 +133,16 @@ var BLOCK_TYPES = {
76
133
  function esc(value) {
77
134
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
78
135
  }
136
+ var TEXT_ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0" };
137
+ function decodeHtmlText(value) {
138
+ return value.replace(/&(#x[\da-f]+|#\d+|amp|lt|gt|quot|apos|nbsp);/gi, (entity, name) => {
139
+ var _a;
140
+ if (!name.startsWith("#")) return (_a = TEXT_ENTITIES[name.toLowerCase()]) != null ? _a : entity;
141
+ const hex = name[1].toLowerCase() === "x";
142
+ const code = Number.parseInt(name.slice(hex ? 2 : 1), hex ? 16 : 10);
143
+ return code > 0 && code <= 1114111 && !(code >= 55296 && code <= 57343) ? String.fromCodePoint(code) : entity;
144
+ });
145
+ }
79
146
 
80
147
  // src/constants/style-values.ts
81
148
  var TEXT_ALIGNS = {
@@ -106,6 +173,7 @@ var LINK_DECORATIONS = {
106
173
  function isStyleValue(values, value) {
107
174
  return typeof value === "string" && Object.values(values).includes(value);
108
175
  }
176
+ var HEX_COLOR = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
109
177
 
110
178
  // src/utils/css-values.ts
111
179
  function cssLength(value, fallback = "0px", allowAuto = false) {
@@ -182,7 +250,7 @@ function cssBorderStyle(value) {
182
250
  }
183
251
 
184
252
  // src/document/constants.ts
185
- var DOCUMENT_VERSION = 1;
253
+ var DOCUMENT_VERSION = 2;
186
254
  function fontOption(label, value) {
187
255
  return Object.freeze({ label, value });
188
256
  }
@@ -221,7 +289,7 @@ var DEFAULT_EMAIL_GLOBAL = {
221
289
  listIndent: "22px",
222
290
  borderWidth: "0px",
223
291
  borderStyle: BORDER_STYLES.solid,
224
- borderColor: "#000000",
292
+ borderColor: "transparent",
225
293
  borderRadius: "0px",
226
294
  fontFamily: "Arial, Helvetica, sans-serif",
227
295
  fontWeight: FONT_WEIGHTS.normal,
@@ -277,6 +345,9 @@ function createEmptyDocument() {
277
345
  return { version: DOCUMENT_VERSION, subject: "", global: resolveEmailGlobal(), blocks: [] };
278
346
  }
279
347
  function ensureEmailDocument(document) {
348
+ if (document.version !== DOCUMENT_VERSION) {
349
+ throw new Error(`Unsupported email document version: ${document.version}. Expected ${DOCUMENT_VERSION}.`);
350
+ }
280
351
  return __spreadProps(__spreadValues({}, document), { global: resolveEmailGlobal(document.global) });
281
352
  }
282
353
 
@@ -329,7 +400,7 @@ function decorationStyle(decoration = {}) {
329
400
  backgroundColor: cssColor(decoration.backgroundColor),
330
401
  borderWidth: cssLength(decoration.borderWidth),
331
402
  borderStyle: cssBorderStyle(decoration.borderStyle),
332
- borderColor: cssColor(decoration.borderColor, "#000000")
403
+ borderColor: cssColor(decoration.borderColor, "transparent")
333
404
  }, radiusStyle(decoration.borderRadius));
334
405
  }
335
406
  function styleToCss(style) {
@@ -403,15 +474,6 @@ function renderAligned(target, options) {
403
474
  return renderShrinkElement(align, width, options.content);
404
475
  }
405
476
 
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
477
  // src/blocks/style-fields.ts
416
478
  var BORDER_STYLE_LABELS = {
417
479
  none: "None",
@@ -431,7 +493,7 @@ var TEXT_ALIGN = { id: "typography.textAlign", group: "typography", label: "Text
431
493
  var BG_COLOR = { id: "decorations.backgroundColor", group: "decorations", label: "Background", control: "color", defaultValue: "transparent" };
432
494
  var BORDER_RADIUS = { id: "decorations.borderRadius", group: "decorations", label: "Block corner radius", control: "corners", max: 48, defaultValue: "0px" };
433
495
  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" };
496
+ var BORDER_COLOR = { id: "decorations.borderColor", group: "decorations", label: "Border", control: "color", defaultValue: "transparent" };
435
497
  var BORDER_STYLE = {
436
498
  id: "decorations.borderStyle",
437
499
  group: "decorations",
@@ -470,18 +532,14 @@ var TYPOGRAPHY_FIELD_MAP = {
470
532
  linkDecoration: LINK_DECORATION
471
533
  };
472
534
  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
535
+ var WRAPPER_STYLE_FIELDS = [
536
+ __spreadProps(__spreadValues({}, PADDING), { label: "Block padding" }),
537
+ MARGIN,
538
+ __spreadProps(__spreadValues({}, BG_COLOR), { label: "Block background" }),
539
+ BORDER_WIDTH,
540
+ BORDER_STYLE,
541
+ __spreadProps(__spreadValues({}, BORDER_COLOR), { label: "Block border" }),
542
+ BORDER_RADIUS
485
543
  ];
486
544
  function copyStyleField(field, overrides) {
487
545
  const next = __spreadValues(__spreadValues({}, field), overrides);
@@ -527,19 +585,12 @@ var elementDecoration = createDecorationFields({
527
585
  borderColor: { id: "element.borderColor", label: "Outline" },
528
586
  borderRadius: { id: "element.borderRadius", label: "Corner radius" }
529
587
  });
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
588
  var BUTTON_BOX_DEFAULTS = {
538
589
  align: HORIZONTAL_ALIGNS.center,
539
- backgroundColor: "#005bd3",
590
+ backgroundColor: "#000000",
540
591
  borderRadius: "8px",
541
592
  borderWidth: "1px",
542
- borderColor: "#005bd3",
593
+ borderColor: "transparent",
543
594
  padding: "12px 32px"
544
595
  };
545
596
  var ELEMENT_BORDER_FIELDS = [
@@ -554,12 +605,7 @@ var BUTTON_STYLE_FIELDS = [
554
605
  ELEMENT_ALIGN,
555
606
  ELEMENT_WIDTH,
556
607
  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,
608
+ createDecorationFields({ backgroundColor: { id: "element.backgroundColor", label: "Button fill", defaultValue: BUTTON_BOX_DEFAULTS.backgroundColor } }).backgroundColor,
563
609
  ...ELEMENT_BORDER_FIELDS.map((field) => __spreadProps(__spreadValues({}, field), {
564
610
  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
611
  }))
@@ -576,44 +622,188 @@ var DIVIDER_STYLE_FIELDS = [
576
622
  __spreadProps(__spreadValues({}, ELEMENT_BORDER_FIELDS[1]), { label: "Line style" })
577
623
  ];
578
624
 
625
+ // src/render/button.ts
626
+ function buttonContent(block) {
627
+ var _a;
628
+ return String((_a = block.fields.html) != null ? _a : "");
629
+ }
630
+ function buttonStyles(block, global) {
631
+ var _a;
632
+ const element = __spreadValues(__spreadValues({}, BUTTON_BOX_DEFAULTS), (_a = block.style) == null ? void 0 : _a.element);
633
+ const text = __spreadProps(__spreadValues({}, typographyStyle(void 0, global)), { textAlign: "center" });
634
+ const box = __spreadProps(__spreadValues({}, decorationStyle(element)), {
635
+ padding: cssSpacing(element.padding, BUTTON_BOX_DEFAULTS.padding),
636
+ textAlign: "center"
637
+ });
638
+ return { element, width: cssLength(element.width, "auto", true), text, box };
639
+ }
640
+
641
+ // src/render/button-content.ts
642
+ import { parser } from "@lezer/html";
643
+
644
+ // src/render/template-tokens.ts
645
+ function protectTemplateTokens(html) {
646
+ var _a;
647
+ let prefix = "REE_TEMPLATE_TOKEN_";
648
+ while (html.includes(prefix)) prefix += "_";
649
+ const tokens = [];
650
+ let protectedHtml = "";
651
+ let cursor = 0;
652
+ while (cursor < html.length) {
653
+ const start = html.indexOf("{{", cursor);
654
+ if (start < 0) break;
655
+ let quote = "";
656
+ let comment = false;
657
+ let escaped = false;
658
+ let end = start + 2;
659
+ for (; end < html.length - 1; end += 1) {
660
+ const entity = html[end] === "&" ? (_a = /^&(?:#x[\da-f]+|#\d+|quot|apos);/i.exec(html.slice(end))) == null ? void 0 : _a[0] : void 0;
661
+ const char = decodeHtmlText(entity != null ? entity : html[end]);
662
+ if (comment) {
663
+ if (html.startsWith("*/", end)) {
664
+ comment = false;
665
+ end += 1;
666
+ }
667
+ } else if (quote) {
668
+ if (escaped) escaped = false;
669
+ else if (char === "\\" && quote !== "`") escaped = true;
670
+ else if (char === quote) quote = "";
671
+ } else if (html.startsWith("/*", end)) {
672
+ comment = true;
673
+ end += 1;
674
+ } else if (char === '"' || char === "'" || char === "`") {
675
+ quote = char;
676
+ } else if (html.startsWith("}}", end)) {
677
+ break;
678
+ }
679
+ if (entity) end += entity.length - 1;
680
+ }
681
+ if (end >= html.length - 1) break;
682
+ protectedHtml += html.slice(cursor, start) + `${prefix}${tokens.length}_END`;
683
+ tokens.push(decodeHtmlText(html.slice(start, end + 2)));
684
+ cursor = end + 2;
685
+ }
686
+ protectedHtml += html.slice(cursor);
687
+ return {
688
+ html: protectedHtml,
689
+ restore: (value) => value.replace(new RegExp(`${prefix}(\\d+)_END`, "g"), (token, index) => {
690
+ var _a2;
691
+ return (_a2 = tokens[Number(index)]) != null ? _a2 : token;
692
+ })
693
+ };
694
+ }
695
+
696
+ // src/render/button-content.ts
697
+ var INLINE_TAGS = { strong: "strong", b: "strong", em: "em", i: "em", u: "u", span: "span" };
698
+ var OMIT_CONTENT = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "template"]);
699
+ var BLOCK_TAGS = /* @__PURE__ */ new Set(["p", "div", "li", "br", "h1", "h2", "h3", "h4", "h5", "h6"]);
700
+ function inlineStyle(value) {
701
+ return value.split(";").flatMap((declaration) => {
702
+ const separator = declaration.indexOf(":");
703
+ const name = declaration.slice(0, separator).trim().toLowerCase();
704
+ const raw = declaration.slice(separator + 1).trim();
705
+ let normalized = "";
706
+ if (name === "font-family") normalized = cssFont(raw, "");
707
+ if (name === "font-size") normalized = cssLength(raw, "");
708
+ if (name === "color") normalized = cssColor(raw, "");
709
+ if (name === "font-weight" && /^(normal|bold|[1-9]00)$/.test(raw)) normalized = raw;
710
+ if (name === "font-style" && /^(normal|italic)$/.test(raw)) normalized = raw;
711
+ if (name === "text-decoration" && /^(none|underline)$/.test(raw)) normalized = raw;
712
+ return normalized ? [`${name}:${normalized}`] : [];
713
+ }).join(";");
714
+ }
715
+ function buttonInlineHtml(html) {
716
+ const templates = protectTemplateTokens(html);
717
+ html = templates.html;
718
+ const source = (node) => html.slice(node.from, node.to);
719
+ const tagName = (node) => {
720
+ var _a, _b;
721
+ const tag = (_b = (_a = node.getChild("OpenTag")) != null ? _a : node.getChild("SelfClosingTag")) == null ? void 0 : _b.getChild("TagName");
722
+ return tag ? source(tag).toLowerCase() : "";
723
+ };
724
+ const read = (node) => {
725
+ var _a, _b;
726
+ if (node.name === "Text") return esc(source(node));
727
+ if (node.name === "EntityReference" || node.name === "CharacterReference") return source(node);
728
+ if (node.name !== "Element" && node.name !== "Document") return "";
729
+ const open = (_a = node.getChild("OpenTag")) != null ? _a : node.getChild("SelfClosingTag");
730
+ const tag = tagName(node);
731
+ if (OMIT_CONTENT.has(tag)) return "";
732
+ let content = "";
733
+ let previousBlock = false;
734
+ for (let child = node.firstChild; child; child = child.nextSibling) {
735
+ const next = read(child);
736
+ const block = BLOCK_TAGS.has(tagName(child));
737
+ if (next && content && (previousBlock || block)) content += " ";
738
+ content += next;
739
+ if (next) previousBlock = block;
740
+ }
741
+ const inline = INLINE_TAGS[tag];
742
+ let style = "";
743
+ for (const attribute of (_b = open == null ? void 0 : open.getChildren("Attribute")) != null ? _b : []) {
744
+ const name = attribute.getChild("AttributeName");
745
+ const value = attribute.getChild("AttributeValue");
746
+ if (name && value && source(name).toLowerCase() === "style") {
747
+ style = inlineStyle(source(value).replace(/^(["'])([\s\S]*)\1$/, "$2").replace(/&quot;/gi, '"').replace(/&#39;|&apos;/gi, "'"));
748
+ }
749
+ }
750
+ const styled = style ? ` style="${esc(style)}"` : "";
751
+ if (inline) return `<${inline}${styled}>${content}</${inline}>`;
752
+ if (style) content = `<span${styled}>${content}</span>`;
753
+ return tag === "br" ? " " : content;
754
+ };
755
+ return templates.restore(read(parser.parse(html).topNode));
756
+ }
757
+
758
+ // src/blocks/icons.ts
759
+ var ICON_TEXT = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M5 4h14v3H5V4Zm0 13h9v-3H5v3Z"/></svg>';
760
+ var ICON_HEADING = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M4 4h3v7h10V4h3v16h-3v-6H7v6H4V4Z"/></svg>';
761
+ 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>';
762
+ 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>';
763
+ var ICON_DIVIDER = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M3 11h18v2H3v-2Z"/></svg>';
764
+ var ICON_SPACER = '<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M4 8h16v2H4V8Zm0 6h16v2H4v-2Z"/></svg>';
765
+ 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>';
766
+
579
767
  // src/blocks/button.ts
580
768
  var buttonBlock = {
769
+ exactStyleFields: true,
770
+ inspectorSections: BUTTON_SECTIONS,
581
771
  type: BLOCK_TYPES.button,
582
772
  title: "Button",
583
773
  icon: ICON_BUTTON,
584
774
  description: "Clickable CTA button",
775
+ inlineEdit: true,
776
+ inlineEditProfile: "button",
777
+ defaultFields: { html: '<p><span style="font-size:16px;color:#ffffff">Buy now</span></p>' },
585
778
  fields: [
586
- { type: "text", name: "label", label: "Label", defaultValue: "Buy now" },
587
- { type: "url", name: "href", label: "Link", defaultValue: "https://example.com" }
779
+ { type: "url", name: "href", label: "Link", defaultValue: "", placeholder: "https://example.com" }
588
780
  ],
589
781
  defaultStyle: {
590
- element: __spreadValues({ fontSize: "16px", color: "#ffffff" }, BUTTON_BOX_DEFAULTS)
782
+ element: __spreadValues({}, BUTTON_BOX_DEFAULTS)
591
783
  },
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);
784
+ styleFields: [...WRAPPER_STYLE_FIELDS, ...BUTTON_STYLE_FIELDS],
785
+ render: ({ block, global }) => {
786
+ var _a;
787
+ const { element, width, text, box } = buttonStyles(block, global);
597
788
  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>`;
789
+ const href = String((_a = block.fields.href) != null ? _a : "").trim();
790
+ const tag = href ? "a" : "span";
791
+ const anchor = `<${tag}${href ? ` href="${esc(href)}"` : ""} style="display:block;text-decoration:none;${styleToCss(text)};">${buttonInlineHtml(buttonContent(block))}</${tag}>`;
792
+ const cell = `<td align="center"${px ? ` width="${px}"` : ""} style="${styleToCss(box)};">${anchor}</td>`;
605
793
  return renderAligned("button", { align: element.align, width, content: cell });
606
794
  }
607
795
  };
608
796
 
609
797
  // src/blocks/divider.ts
610
798
  var dividerBlock = {
799
+ exactStyleFields: true,
800
+ inspectorSections: DIVIDER_SECTIONS,
611
801
  type: BLOCK_TYPES.divider,
612
802
  title: "Divider",
613
803
  icon: ICON_DIVIDER,
614
804
  description: "Horizontal divider line",
615
805
  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" }],
806
+ styleFields: [...WRAPPER_STYLE_FIELDS.filter((field) => !field.id.startsWith("decorations.border")), ...DIVIDER_STYLE_FIELDS, { id: "element.borderColor", group: "decorations", label: "Line", control: "color", defaultValue: "#e3e3e5" }],
617
807
  render: ({ style }) => {
618
808
  var _a;
619
809
  const element = (_a = style.element) != null ? _a : {};
@@ -723,45 +913,45 @@ function styleRichText(html, style, global) {
723
913
 
724
914
  // src/blocks/heading.ts
725
915
  var headingBlock = {
916
+ exactStyleFields: true,
917
+ inspectorSections: HEADING_SECTIONS,
726
918
  type: BLOCK_TYPES.heading,
727
919
  title: "Heading",
728
920
  icon: ICON_HEADING,
729
921
  description: "Large section heading",
730
922
  inlineEdit: true,
731
- defaultFields: { html: "<p>Heading</p>" },
923
+ inlineEditProfile: "heading",
924
+ defaultFields: { html: '<p style="line-height:1.25"><span style="font-size:26px"><strong>Heading</strong></span></p>' },
732
925
  defaultStyle: {
733
- dimension: { padding: "16px" },
734
- typography: {
735
- fontSize: "26px",
736
- fontWeight: "bold",
737
- lineHeight: "1.25"
738
- }
926
+ dimension: { padding: "16px" }
739
927
  },
740
- styleFields: [...COMMON_STYLE_FIELDS, ...TYPOGRAPHY_FIELDS],
741
- render: ({ fields, style, global }) => {
928
+ styleFields: WRAPPER_STYLE_FIELDS,
929
+ render: ({ fields, global }) => {
742
930
  var _a;
743
- return `<div style="display:flow-root;${typographyCss(style, global)}">${styleRichText(String((_a = fields.html) != null ? _a : ""), style, global)}</div>`;
931
+ return `<div style="display:flow-root;${typographyCss(void 0, global)}">${styleRichText(String((_a = fields.html) != null ? _a : ""), void 0, global)}</div>`;
744
932
  }
745
933
  };
746
934
 
747
935
  // src/render/html-content.ts
748
- function htmlContent(html, style, global, id) {
936
+ function htmlContent(html, global, id) {
749
937
  var _a, _b, _c, _d;
750
938
  const scope = `ree-html-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
751
939
  const body = html.match(/<body\b([^>]*)>([\s\S]*?)<\/body\s*>/i);
752
940
  let marker = "";
753
- let inner = styleRichText(html, style, global);
941
+ let inner = html;
754
942
  if (body) {
755
943
  const head = (_b = (_a = html.match(/<head\b[^>]*>([\s\S]*?)<\/head\s*>/i)) == null ? void 0 : _a[1]) != null ? _b : "";
756
944
  const styles = (_d = (_c = head.match(/<style\b[^>]*>[\s\S]*?<\/style\s*>/gi)) == null ? void 0 : _c.join("\n")) != null ? _d : "";
757
945
  marker = styles ? `<!--ree-head-->${styles}<!--/ree-head-->` : "";
758
- inner = `<div${body[1]}>${styleRichText(body[2], style, global)}</div>`;
946
+ inner = `<div${body[1]}>${body[2]}</div>`;
759
947
  }
760
- return `${marker}<div class="${scope}" style="display:flow-root;${typographyCss(style, global)};">${inner}</div>`;
948
+ return `${marker}<div class="${scope}" style="display:flow-root;${typographyCss(void 0, global)};">${inner}</div>`;
761
949
  }
762
950
 
763
951
  // src/blocks/html.ts
764
952
  var htmlBlock = {
953
+ exactStyleFields: true,
954
+ inspectorSections: HTML_SECTIONS,
765
955
  type: BLOCK_TYPES.html,
766
956
  title: "HTML",
767
957
  icon: ICON_HTML,
@@ -777,15 +967,17 @@ var htmlBlock = {
777
967
  }
778
968
  ],
779
969
  defaultStyle: { dimension: { padding: "8px" } },
780
- styleFields: [...COMMON_STYLE_FIELDS, ...TYPOGRAPHY_FIELDS],
781
- render: ({ fields, style, global, block }) => {
970
+ styleFields: WRAPPER_STYLE_FIELDS,
971
+ render: ({ fields, global, block }) => {
782
972
  var _a;
783
- return htmlContent(String((_a = fields.html) != null ? _a : ""), style, global, block.id);
973
+ return htmlContent(String((_a = fields.html) != null ? _a : ""), global, block.id);
784
974
  }
785
975
  };
786
976
 
787
977
  // src/blocks/image.ts
788
978
  var imageBlock = {
979
+ exactStyleFields: true,
980
+ inspectorSections: IMAGE_SECTIONS,
789
981
  type: BLOCK_TYPES.image,
790
982
  title: "Image",
791
983
  icon: ICON_IMAGE,
@@ -793,10 +985,10 @@ var imageBlock = {
793
985
  fields: [
794
986
  { type: "url", name: "src", label: "Image URL", defaultValue: "https://placehold.co/560x200?text=Image" },
795
987
  { type: "text", name: "alt", label: "Alt text", defaultValue: "Image" },
796
- { type: "url", name: "link", label: "Link", defaultValue: "" }
988
+ { type: "url", name: "link", label: "Link", defaultValue: "", placeholder: "https://example.com" }
797
989
  ],
798
990
  defaultStyle: { dimension: { padding: "8px" } },
799
- styleFields: [...COMMON_STYLE_FIELDS, ELEMENT_ALIGN, ...IMAGE_STYLE_FIELDS],
991
+ styleFields: [...WRAPPER_STYLE_FIELDS, ELEMENT_ALIGN, ...IMAGE_STYLE_FIELDS],
800
992
  render: ({ fields, style }) => {
801
993
  var _a, _b, _c, _d;
802
994
  const element = (_a = style.element) != null ? _a : {};
@@ -813,7 +1005,7 @@ var imageBlock = {
813
1005
  const widthAttr = pixelLength(width);
814
1006
  const heightAttr = pixelLength(height);
815
1007
  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 : "");
1008
+ const link = String((_d = fields.link) != null ? _d : "").trim();
817
1009
  const content = link ? `<a href="${esc(link)}" style="text-decoration:none;${box.display === "block" ? "display:block;" : ""}">${image}</a>` : image;
818
1010
  return renderAligned("image", { align: element.align, width, content });
819
1011
  }
@@ -829,30 +1021,35 @@ function spacerHtml(height, fallback = "0px") {
829
1021
 
830
1022
  // src/blocks/spacer.ts
831
1023
  var spacerBlock = {
1024
+ exactStyleFields: true,
1025
+ inspectorSections: SPACER_SECTIONS,
832
1026
  type: BLOCK_TYPES.spacer,
833
1027
  title: "Spacer",
834
1028
  icon: ICON_SPACER,
835
1029
  description: "Vertical whitespace",
836
1030
  fields: [{ type: "number", name: "height", label: "Height", defaultValue: 16, min: 0, max: 240, step: 1 }],
837
- styleFields: COMMON_STYLE_FIELDS,
1031
+ styleFields: [BG_COLOR],
838
1032
  render: ({ fields }) => spacerHtml(fields.height, "16px")
839
1033
  };
840
1034
 
841
1035
  // src/blocks/text.ts
842
1036
  var textBlock = {
1037
+ exactStyleFields: true,
1038
+ inspectorSections: TEXT_SECTIONS,
843
1039
  type: BLOCK_TYPES.text,
844
1040
  title: "Text",
845
1041
  icon: ICON_TEXT,
846
1042
  description: "Paragraph of body text",
847
1043
  inlineEdit: true,
1044
+ inlineEditProfile: "text",
848
1045
  defaultFields: { html: "<p>Type your paragraph here\u2026</p>" },
849
1046
  defaultStyle: {
850
1047
  dimension: { padding: "16px" }
851
1048
  },
852
- styleFields: [...COMMON_STYLE_FIELDS, ...TYPOGRAPHY_FIELDS],
853
- render: ({ fields, style, global }) => {
1049
+ styleFields: WRAPPER_STYLE_FIELDS,
1050
+ render: ({ fields, global }) => {
854
1051
  var _a;
855
- return `<div style="display:flow-root;${typographyCss(style, global)}">${styleRichText(String((_a = fields.html) != null ? _a : ""), style, global)}</div>`;
1052
+ return `<div style="display:flow-root;${typographyCss(void 0, global)}">${styleRichText(String((_a = fields.html) != null ? _a : ""), void 0, global)}</div>`;
856
1053
  }
857
1054
  };
858
1055
 
@@ -1049,7 +1246,9 @@ function EditorDnd({
1049
1246
  children
1050
1247
  }) {
1051
1248
  const blocksRef = useRef(blocks);
1052
- blocksRef.current = blocks;
1249
+ useLayoutEffect(() => {
1250
+ blocksRef.current = blocks;
1251
+ }, [blocks]);
1053
1252
  const [activeDrag, setActiveDrag] = useState(null);
1054
1253
  const [indicator, setIndicator] = useState(null);
1055
1254
  const sensors = useSensors(
@@ -1241,10 +1440,9 @@ function StyleFieldStack({ fields, renderField }) {
1241
1440
  }
1242
1441
 
1243
1442
  // src/inspector/StyleControl.tsx
1244
- import { useId } from "react";
1443
+ import { useId as useId2 } from "react";
1245
1444
 
1246
1445
  // src/inspector/constants.ts
1247
- var HEX_COLOR = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
1248
1446
  var TEXT_ALIGN_OPTIONS = [
1249
1447
  { value: HORIZONTAL_ALIGNS.left, label: "Left", glyph: "\u21E4" },
1250
1448
  { value: HORIZONTAL_ALIGNS.center, label: "Center", glyph: "\u21D4" },
@@ -1268,108 +1466,19 @@ function AlignControl({ value, onChange }) {
1268
1466
  className: `ree-align-btn${value === option.value ? " is-active" : ""}`,
1269
1467
  onClick: () => onChange(option.value),
1270
1468
  title: option.label,
1469
+ "aria-label": option.label,
1470
+ "aria-pressed": value === option.value,
1271
1471
  children: option.glyph
1272
1472
  },
1273
1473
  option.value
1274
1474
  )) });
1275
1475
  }
1276
1476
 
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
- }
1477
+ // src/components/ColorPicker.tsx
1478
+ import { useState as useState3 } from "react";
1370
1479
 
1371
- // src/inspector/components/RangeControl.tsx
1372
- import { jsx as jsx10 } from "react/jsx-runtime";
1480
+ // src/components/RangeControl.tsx
1481
+ import { jsx as jsx8 } from "react/jsx-runtime";
1373
1482
  function RangeControl({
1374
1483
  id,
1375
1484
  label,
@@ -1380,7 +1489,7 @@ function RangeControl({
1380
1489
  onChange
1381
1490
  }) {
1382
1491
  const progress = max > min ? Math.min(100, Math.max(0, (value - min) / (max - min) * 100)) : 0;
1383
- return /* @__PURE__ */ jsx10(
1492
+ return /* @__PURE__ */ jsx8(
1384
1493
  "input",
1385
1494
  {
1386
1495
  id,
@@ -1397,83 +1506,362 @@ function RangeControl({
1397
1506
  );
1398
1507
  }
1399
1508
 
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 };
1509
+ // src/components/TextInput.tsx
1510
+ import { forwardRef } from "react";
1511
+ import { jsx as jsx9 } from "react/jsx-runtime";
1512
+ var TextInput = forwardRef(function TextInput2(_a, ref) {
1513
+ var _b = _a, { className = "", type = "text" } = _b, props = __objRest(_b, ["className", "type"]);
1514
+ return /* @__PURE__ */ jsx9("input", __spreadProps(__spreadValues({}, props), { ref, type, className: `ree-input${className ? ` ${className}` : ""}` }));
1515
+ });
1516
+
1517
+ // src/components/ResetButton.tsx
1518
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1519
+ function ResetButton({ label, disabled = false, onClick }) {
1520
+ return /* @__PURE__ */ jsx10("button", { type: "button", className: "ree-reset", "aria-label": label, title: label, disabled, onClick, children: /* @__PURE__ */ jsxs6("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: [
1521
+ /* @__PURE__ */ jsx10("path", { d: "M13 8a5 5 0 1 1-1.4-3.45", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" }),
1522
+ /* @__PURE__ */ jsx10("path", { d: "M12.2 2.4v2.7h-2.7", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })
1523
+ ] }) });
1405
1524
  }
1406
- function snap(value, step) {
1525
+
1526
+ // src/components/color-picker/ColorSaturation.tsx
1527
+ import { jsx as jsx11 } from "react/jsx-runtime";
1528
+ function ColorSaturation({ color, onChange }) {
1529
+ const update = (event) => {
1530
+ const rect = event.currentTarget.getBoundingClientRect();
1531
+ if (!rect.width || !rect.height) return;
1532
+ onChange(__spreadProps(__spreadValues({}, color), {
1533
+ saturation: Math.min(100, Math.max(0, (event.clientX - rect.left) / rect.width * 100)),
1534
+ brightness: Math.min(100, Math.max(0, (1 - (event.clientY - rect.top) / rect.height) * 100))
1535
+ }));
1536
+ };
1537
+ return /* @__PURE__ */ jsx11(
1538
+ "div",
1539
+ {
1540
+ className: "ree-color-saturation",
1541
+ role: "slider",
1542
+ tabIndex: 0,
1543
+ "aria-label": "Saturation and brightness",
1544
+ "aria-valuemin": 0,
1545
+ "aria-valuemax": 100,
1546
+ "aria-valuenow": Math.round(color.saturation),
1547
+ "aria-valuetext": `Saturation ${Math.round(color.saturation)}%, brightness ${Math.round(color.brightness)}%. Use left/right for saturation and up/down for brightness.`,
1548
+ style: { "--ree-color-hue": `hsl(${color.hue} 100% 50%)` },
1549
+ onPointerDown: (event) => {
1550
+ if (event.button !== 0) return;
1551
+ event.currentTarget.focus({ preventScroll: true });
1552
+ event.currentTarget.setPointerCapture(event.pointerId);
1553
+ update(event);
1554
+ },
1555
+ onPointerMove: (event) => {
1556
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) update(event);
1557
+ },
1558
+ onPointerUp: (event) => {
1559
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
1560
+ },
1561
+ onKeyDown: (event) => {
1562
+ const step = event.shiftKey ? 10 : 1;
1563
+ let { saturation, brightness } = color;
1564
+ switch (event.key) {
1565
+ case "ArrowLeft":
1566
+ saturation -= step;
1567
+ break;
1568
+ case "ArrowRight":
1569
+ saturation += step;
1570
+ break;
1571
+ case "ArrowUp":
1572
+ brightness += step;
1573
+ break;
1574
+ case "ArrowDown":
1575
+ brightness -= step;
1576
+ break;
1577
+ case "Home":
1578
+ saturation = 0;
1579
+ break;
1580
+ case "End":
1581
+ saturation = 100;
1582
+ break;
1583
+ default:
1584
+ return;
1585
+ }
1586
+ event.preventDefault();
1587
+ onChange(__spreadProps(__spreadValues({}, color), {
1588
+ saturation: Math.min(100, Math.max(0, saturation)),
1589
+ brightness: Math.min(100, Math.max(0, brightness))
1590
+ }));
1591
+ },
1592
+ children: /* @__PURE__ */ jsx11("span", { className: "ree-color-cursor", style: { left: `${color.saturation}%`, top: `${100 - color.brightness}%` } })
1593
+ }
1594
+ );
1595
+ }
1596
+
1597
+ // src/components/color-picker/colors.ts
1598
+ function normalizeHex(value) {
1599
+ const input = value.trim();
1600
+ const hex = input.startsWith("#") ? input : `#${input}`;
1601
+ if (!HEX_COLOR.test(hex)) return null;
1602
+ const digits = hex.slice(1).toLowerCase();
1603
+ return `#${digits.length === 3 ? [...digits].map((digit) => digit + digit).join("") : digits}`;
1604
+ }
1605
+ function colorToHex(value) {
1606
+ const hex = normalizeHex(value);
1607
+ if (hex) return hex;
1608
+ const rgb = /^rgb\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/i.exec(value.trim());
1609
+ if (!rgb) return null;
1610
+ const channels = rgb.slice(1).map(Number);
1611
+ if (channels.some((channel) => !Number.isFinite(channel) || channel < 0 || channel > 255)) return null;
1612
+ return `#${channels.map((channel) => Math.round(channel).toString(16).padStart(2, "0")).join("")}`;
1613
+ }
1614
+ function hexToHsv(hex) {
1407
1615
  var _a;
1408
- const places = ((_a = String(step).split(".")[1]) != null ? _a : "").length;
1409
- return Number((Math.round(value / step) * step).toFixed(places));
1616
+ const normalized = (_a = normalizeHex(hex)) != null ? _a : "#000000";
1617
+ const [red, green, blue] = [1, 3, 5].map((offset) => Number.parseInt(normalized.slice(offset, offset + 2), 16) / 255);
1618
+ const max = Math.max(red, green, blue);
1619
+ const min = Math.min(red, green, blue);
1620
+ const delta = max - min;
1621
+ let hue = 0;
1622
+ if (delta) {
1623
+ if (max === red) hue = (green - blue) / delta % 6;
1624
+ else if (max === green) hue = (blue - red) / delta + 2;
1625
+ else hue = (red - green) / delta + 4;
1626
+ }
1627
+ return { hue: (hue * 60 + 360) % 360, saturation: max ? delta / max * 100 : 0, brightness: max * 100 };
1410
1628
  }
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));
1629
+ function hsvToHex({ hue, saturation, brightness }) {
1630
+ const sector = (hue % 360 + 360) % 360 / 60;
1631
+ const value = Math.min(100, Math.max(0, brightness)) / 100;
1632
+ const chroma = value * Math.min(100, Math.max(0, saturation)) / 100;
1633
+ const secondary = chroma * (1 - Math.abs(sector % 2 - 1));
1634
+ const channels = sector < 1 ? [chroma, secondary, 0] : sector < 2 ? [secondary, chroma, 0] : sector < 3 ? [0, chroma, secondary] : sector < 4 ? [0, secondary, chroma] : sector < 5 ? [secondary, 0, chroma] : [chroma, 0, secondary];
1635
+ return `#${channels.map((channel) => Math.round((channel + value - chroma) * 255).toString(16).padStart(2, "0")).join("")}`;
1636
+ }
1637
+
1638
+ // src/components/ColorPicker.tsx
1639
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1640
+ function ColorPicker({ id, label, value, fallback = "#000000", panelRef, onChange, onReset, onClose }) {
1641
+ var _a, _b;
1642
+ const initial = (_b = (_a = colorToHex(value)) != null ? _a : colorToHex(fallback)) != null ? _b : "#000000";
1643
+ const [color, setColor] = useState3(() => hexToHsv(initial));
1644
+ const [draft, setDraft] = useState3(initial);
1645
+ const invalid = !normalizeHex(draft);
1646
+ const selectColor = (next) => {
1647
+ const hex = hsvToHex(next);
1648
+ setColor(next);
1649
+ setDraft(hex);
1650
+ onChange(hex);
1432
1651
  };
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
- ] });
1652
+ const commitHex = () => {
1653
+ const hex = normalizeHex(draft);
1654
+ if (!hex) return;
1655
+ setDraft(hex);
1656
+ };
1657
+ return /* @__PURE__ */ jsxs7(
1658
+ "div",
1659
+ {
1660
+ ref: panelRef,
1661
+ id,
1662
+ className: "ree-color-picker",
1663
+ role: "dialog",
1664
+ "aria-label": `${label} picker`,
1665
+ onMouseDown: (event) => event.stopPropagation(),
1666
+ onPointerDown: (event) => event.stopPropagation(),
1667
+ onKeyDown: (event) => {
1668
+ if (event.key === "Escape") {
1669
+ event.preventDefault();
1670
+ event.stopPropagation();
1671
+ onClose();
1672
+ }
1673
+ },
1674
+ children: [
1675
+ /* @__PURE__ */ jsx12(ColorSaturation, { color, onChange: selectColor }),
1676
+ /* @__PURE__ */ jsx12("div", { className: "ree-color-hue", children: /* @__PURE__ */ jsx12(RangeControl, { label: "Hue", value: color.hue, min: 0, max: 360, onChange: (hue) => selectColor(__spreadProps(__spreadValues({}, color), { hue })) }) }),
1677
+ /* @__PURE__ */ jsxs7("div", { className: "ree-color-hex-row", children: [
1678
+ /* @__PURE__ */ jsx12("span", { className: "ree-color-preview", style: { backgroundColor: hsvToHex(color) } }),
1679
+ /* @__PURE__ */ jsxs7("label", { className: "ree-color-hex-field", children: [
1680
+ /* @__PURE__ */ jsx12("span", { children: "HEX" }),
1681
+ /* @__PURE__ */ jsx12(
1682
+ TextInput,
1683
+ {
1684
+ "aria-label": "HEX",
1685
+ "aria-invalid": invalid,
1686
+ "aria-describedby": invalid ? `${id}-error` : void 0,
1687
+ value: draft,
1688
+ autoComplete: "off",
1689
+ spellCheck: false,
1690
+ onChange: (event) => {
1691
+ const next = event.target.value;
1692
+ setDraft(next);
1693
+ const hex = normalizeHex(next);
1694
+ if (hex) {
1695
+ setColor(hexToHsv(hex));
1696
+ onChange(hex);
1697
+ }
1698
+ },
1699
+ onBlur: commitHex,
1700
+ onKeyDown: (event) => {
1701
+ if (event.key !== "Enter") return;
1702
+ event.preventDefault();
1703
+ event.stopPropagation();
1704
+ commitHex();
1705
+ }
1706
+ }
1707
+ )
1708
+ ] }),
1709
+ /* @__PURE__ */ jsx12(ResetButton, { label: `Reset ${label.toLowerCase()}`, onClick: () => {
1710
+ onReset();
1711
+ onClose();
1712
+ } })
1713
+ ] }),
1714
+ invalid ? /* @__PURE__ */ jsx12("p", { id: `${id}-error`, className: "ree-color-error", role: "status", children: "Enter a 3 or 6 digit HEX color." }) : null
1715
+ ]
1716
+ }
1717
+ );
1451
1718
  }
1452
1719
 
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
- ] }) });
1720
+ // src/components/color-picker/useColorPicker.ts
1721
+ import { useId, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState4 } from "react";
1722
+ function useColorPicker(onOpen) {
1723
+ const id = useId();
1724
+ const anchorRef = useRef2(null);
1725
+ const panelRef = useRef2(null);
1726
+ const [open, setOpen] = useState4(false);
1727
+ useLayoutEffect2(() => {
1728
+ var _a;
1729
+ const panel = panelRef.current;
1730
+ const anchor = anchorRef.current;
1731
+ if (!open || !panel || !anchor) return;
1732
+ const ownerDocument = anchor.ownerDocument;
1733
+ const ownerWindow = ownerDocument.defaultView;
1734
+ if (!ownerWindow) return;
1735
+ const place = () => {
1736
+ const rect = anchor.getBoundingClientRect();
1737
+ const width = panel.offsetWidth;
1738
+ const height = panel.offsetHeight;
1739
+ const left = Math.max(8, Math.min(rect.left, ownerWindow.innerWidth - width - 8));
1740
+ const below = rect.bottom + 6;
1741
+ const top = below + height <= ownerWindow.innerHeight - 8 ? below : Math.max(8, rect.top - height - 6);
1742
+ panel.style.left = `${left}px`;
1743
+ panel.style.top = `${Math.min(top, Math.max(8, ownerWindow.innerHeight - height - 8))}px`;
1744
+ };
1745
+ const onToggle = () => {
1746
+ if (!panel.matches(":popover-open")) setOpen(false);
1747
+ };
1748
+ panel.setAttribute("popover", "auto");
1749
+ panel.addEventListener("toggle", onToggle);
1750
+ panel.showPopover();
1751
+ place();
1752
+ (_a = panel.querySelector('input[type="text"]')) == null ? void 0 : _a.focus({ preventScroll: true });
1753
+ const observer = new ResizeObserver(place);
1754
+ observer.observe(panel);
1755
+ ownerDocument.addEventListener("scroll", place, true);
1756
+ ownerWindow.addEventListener("resize", place);
1757
+ return () => {
1758
+ panel.removeEventListener("toggle", onToggle);
1759
+ panel.hidePopover();
1760
+ observer.disconnect();
1761
+ ownerDocument.removeEventListener("scroll", place, true);
1762
+ ownerWindow.removeEventListener("resize", place);
1763
+ };
1764
+ }, [open]);
1765
+ return {
1766
+ id,
1767
+ open,
1768
+ anchorRef,
1769
+ panelRef,
1770
+ toggle: () => {
1771
+ if (!open) onOpen == null ? void 0 : onOpen();
1772
+ setOpen(!open);
1773
+ },
1774
+ close: () => {
1775
+ var _a, _b;
1776
+ setOpen(false);
1777
+ (_b = (_a = anchorRef.current) == null ? void 0 : _a.querySelector("button")) == null ? void 0 : _b.focus({ preventScroll: true });
1778
+ }
1779
+ };
1460
1780
  }
1461
1781
 
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",
1782
+ // src/components/ColorControl.tsx
1783
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1784
+ function ColorControl({ value, fallback = "#000000", label = "Color", resetTo = fallback, onChange }) {
1785
+ const { id, open, anchorRef, panelRef, toggle, close } = useColorPicker();
1786
+ const actual = value || fallback;
1787
+ const normalized = colorToHex(actual);
1788
+ const empty = normalized === null;
1789
+ return /* @__PURE__ */ jsxs8("div", { className: "ree-color-control", ref: anchorRef, children: [
1790
+ /* @__PURE__ */ jsxs8(
1791
+ "button",
1468
1792
  {
1793
+ type: "button",
1794
+ className: "ree-color-chip",
1469
1795
  "aria-label": label,
1470
- className: "ree-input",
1471
- value,
1796
+ "aria-haspopup": "dialog",
1797
+ "aria-expanded": open,
1798
+ "aria-controls": open ? id : void 0,
1799
+ onClick: toggle,
1800
+ children: [
1801
+ /* @__PURE__ */ jsx13("span", { className: "ree-color-swatch", children: /* @__PURE__ */ jsx13(
1802
+ "span",
1803
+ {
1804
+ className: `ree-color-swatch-face${empty ? " is-empty" : ""}`,
1805
+ style: normalized ? { background: normalized } : void 0
1806
+ }
1807
+ ) }),
1808
+ /* @__PURE__ */ jsx13("span", { className: "ree-color-chip-label", children: label })
1809
+ ]
1810
+ }
1811
+ ),
1812
+ open ? /* @__PURE__ */ jsx13(ColorPicker, { id, label, value: actual, fallback, panelRef, onChange, onReset: () => onChange(resetTo), onClose: close }) : null
1813
+ ] });
1814
+ }
1815
+
1816
+ // src/inspector/components/CssValueInput.tsx
1817
+ import { useState as useState5 } from "react";
1818
+ import { jsx as jsx14 } from "react/jsx-runtime";
1819
+ function CssValueInput({ label, value, placeholder: placeholder2, onChange }) {
1820
+ const [draft, setDraft] = useState5(value);
1821
+ const [previousValue, setPreviousValue] = useState5(value);
1822
+ if (previousValue !== value) {
1823
+ setPreviousValue(value);
1824
+ setDraft(value);
1825
+ }
1826
+ return /* @__PURE__ */ jsx14(
1827
+ TextInput,
1828
+ {
1829
+ "aria-label": label,
1830
+ value: draft,
1831
+ placeholder: placeholder2 != null ? placeholder2 : "0px",
1832
+ onChange: (event) => setDraft(event.target.value),
1833
+ onBlur: () => {
1834
+ onChange(draft.trim());
1835
+ setDraft(value);
1836
+ },
1837
+ onKeyDown: (event) => {
1838
+ if (event.key === "Enter") event.currentTarget.blur();
1839
+ if (event.key === "Escape") {
1840
+ setDraft(value);
1841
+ event.stopPropagation();
1842
+ }
1843
+ }
1844
+ }
1845
+ );
1846
+ }
1847
+
1848
+ // src/components/SelectControl.tsx
1849
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1850
+ function SelectControl({ id, label, value, options, compact = false, disabled, onChange }) {
1851
+ return /* @__PURE__ */ jsxs9("span", { className: `ree-select${compact ? " is-compact" : ""}`, children: [
1852
+ /* @__PURE__ */ jsx15(
1853
+ "select",
1854
+ {
1855
+ id,
1856
+ "aria-label": label,
1857
+ disabled,
1858
+ className: "ree-input",
1859
+ value,
1472
1860
  onChange: (event) => onChange(event.target.value),
1473
- children: options.map((option) => /* @__PURE__ */ jsx13("option", { value: option.value, children: option.label }, option.value))
1861
+ children: options.map((option) => /* @__PURE__ */ jsx15("option", { value: option.value, children: option.label }, option.value))
1474
1862
  }
1475
1863
  ),
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(
1864
+ /* @__PURE__ */ jsx15("span", { className: "ree-select-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx15("svg", { viewBox: "0 0 16 16", width: "16", height: "16", fill: "none", children: /* @__PURE__ */ jsx15(
1477
1865
  "path",
1478
1866
  {
1479
1867
  d: "M4 6.25 8 10.25 12 6.25",
@@ -1486,9 +1874,72 @@ function SelectControl({ label, value, options, onChange }) {
1486
1874
  ] });
1487
1875
  }
1488
1876
 
1877
+ // src/inspector/components/LengthControl.tsx
1878
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1879
+ function bounds(unit, min, max, step) {
1880
+ if (unit === "%") return { min: 0, max: 100, step: 1 };
1881
+ return { min, max, step };
1882
+ }
1883
+ function snap(value, step) {
1884
+ var _a;
1885
+ const places = ((_a = String(step).split(".")[1]) != null ? _a : "").length;
1886
+ return Number((Math.round(value / step) * step).toFixed(places));
1887
+ }
1888
+ function LengthControl({
1889
+ id,
1890
+ label,
1891
+ amount,
1892
+ unit,
1893
+ units,
1894
+ min = 0,
1895
+ max = 200,
1896
+ step = 1,
1897
+ onChange
1898
+ }) {
1899
+ const range = bounds(unit, min, max, step);
1900
+ const shown = Math.min(range.max, Math.max(range.min, amount));
1901
+ const commit = (nextAmount, nextUnit) => {
1902
+ if (nextUnit === "auto") {
1903
+ onChange("auto");
1904
+ return;
1905
+ }
1906
+ const nextRange = bounds(nextUnit, min, max, step);
1907
+ const snapped = snap(Math.min(nextRange.max, Math.max(nextRange.min, nextAmount)), nextRange.step);
1908
+ onChange(formatCssLength(snapped, nextUnit));
1909
+ };
1910
+ return /* @__PURE__ */ jsxs10("div", { className: "ree-range-row", children: [
1911
+ unit === "auto" ? null : /* @__PURE__ */ jsx16(RangeControl, { id, label, value: shown, min: range.min, max: range.max, step: range.step, onChange: (next) => commit(next, unit) }),
1912
+ units.length > 1 ? /* @__PURE__ */ jsx16(
1913
+ SelectControl,
1914
+ {
1915
+ compact: true,
1916
+ label: `${label} unit`,
1917
+ value: unit,
1918
+ options: units.map((option) => ({ value: option, label: option })),
1919
+ onChange: (nextUnit) => {
1920
+ if (nextUnit === unit) return;
1921
+ const seed = unit === "auto" ? nextUnit === "%" ? 100 : Math.min(max, 160) : amount;
1922
+ commit(seed, nextUnit);
1923
+ }
1924
+ }
1925
+ ) : null
1926
+ ] });
1927
+ }
1928
+
1929
+ // src/inspector/components/SpacingControl.tsx
1930
+ import { useState as useState6 } from "react";
1931
+
1932
+ // src/components/CheckboxControl.tsx
1933
+ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
1934
+ function CheckboxControl({ label, checked, disabled, className = "", onChange }) {
1935
+ return /* @__PURE__ */ jsxs11("label", { className: `ree-checkbox-control${className ? ` ${className}` : ""}`, children: [
1936
+ /* @__PURE__ */ jsx17("input", { type: "checkbox", checked, disabled, onChange: (event) => onChange(event.target.checked) }),
1937
+ /* @__PURE__ */ jsx17("span", { children: label })
1938
+ ] });
1939
+ }
1940
+
1489
1941
  // src/inspector/components/SpacingControl.tsx
1490
- import { useState as useState4 } from "react";
1491
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1942
+ import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1492
1943
  function sideAmount(value, max) {
1493
1944
  return Math.min(max, Math.max(0, readCssLength(value, ["px"]).amount));
1494
1945
  }
@@ -1505,37 +1956,35 @@ function SpacingControl({
1505
1956
  var _a;
1506
1957
  const sides = expand(value);
1507
1958
  const equal = sides.every((side) => sideAmount(side, max) === sideAmount(sides[0], max));
1508
- const [independent, setIndependent] = useState4(!equal);
1959
+ const [independent, setIndependent] = useState6(!equal);
1509
1960
  const slider = (sideLabel, sideValue, commit) => {
1510
1961
  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: [
1962
+ return /* @__PURE__ */ jsxs12("div", { className: "ree-field", children: [
1963
+ /* @__PURE__ */ jsxs12("div", { className: "ree-field-label", children: [
1964
+ /* @__PURE__ */ jsx18("span", { children: sideLabel }),
1965
+ /* @__PURE__ */ jsxs12("span", { className: "ree-field-value", children: [
1515
1966
  amount,
1516
1967
  "px"
1517
1968
  ] })
1518
1969
  ] }),
1519
- /* @__PURE__ */ jsx14(RangeControl, { label: sideLabel, value: amount, min: 0, max, step: 1, onChange: (next) => commit(`${next}px`) })
1970
+ /* @__PURE__ */ jsx18(RangeControl, { label: sideLabel, value: amount, min: 0, max, step: 1, onChange: (next) => commit(`${next}px`) })
1520
1971
  ] }, sideLabel);
1521
1972
  };
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
- }
1973
+ return /* @__PURE__ */ jsxs12("div", { className: "ree-spacing-control", children: [
1974
+ /* @__PURE__ */ jsx18(
1975
+ CheckboxControl,
1976
+ {
1977
+ className: "ree-spacing-link",
1978
+ label: linkLabel,
1979
+ checked: !independent && equal,
1980
+ onChange: (checked) => {
1981
+ var _a2;
1982
+ setIndependent(!checked);
1983
+ if (checked) onChange(`${sideAmount((_a2 = sides[0]) != null ? _a2 : "0px", max)}px`);
1534
1984
  }
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) => {
1985
+ }
1986
+ ),
1987
+ !independent && equal ? slider(uniformLabel != null ? uniformLabel : `${label} all sides`, (_a = sides[0]) != null ? _a : "0px", onChange) : /* @__PURE__ */ jsx18("div", { className: "ree-spacing-grid", children: sideLabels.map((side, index) => {
1539
1988
  var _a2;
1540
1989
  return slider(side, (_a2 = sides[index]) != null ? _a2 : "0px", (next) => {
1541
1990
  const updated = [...sides];
@@ -1547,7 +1996,7 @@ function SpacingControl({
1547
1996
  }
1548
1997
 
1549
1998
  // src/inspector/StyleControl.tsx
1550
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1999
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1551
2000
  function lengthUnits(config) {
1552
2001
  var _a;
1553
2002
  if ((_a = config.units) == null ? void 0 : _a.length) return config.units;
@@ -1556,7 +2005,7 @@ function lengthUnits(config) {
1556
2005
  }
1557
2006
  function StyleControl({ config, value, fallback, resetLabel = "Reset", resetTo, showReset, onChange }) {
1558
2007
  var _a, _b;
1559
- const id = useId();
2008
+ const id = useId2();
1560
2009
  const units = lengthUnits(config);
1561
2010
  const stored = value === "auto" && units.length > 0 && !units.includes("auto") ? "" : value;
1562
2011
  const actual = stored || fallback || config.defaultValue || "";
@@ -1564,24 +2013,24 @@ function StyleControl({ config, value, fallback, resetLabel = "Reset", resetTo,
1564
2013
  const parsed = numeric ? readCssLength(actual, units) : null;
1565
2014
  const options = (_a = config.options) != null ? _a : [];
1566
2015
  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;
2016
+ const reset = (showReset != null ? showReset : Boolean(value)) ? /* @__PURE__ */ jsx19(ResetButton, { label: resetLabel, onClick: () => onChange(resetTo != null ? resetTo : "") }) : null;
1568
2017
  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 }),
2018
+ return /* @__PURE__ */ jsxs13("div", { className: "ree-color-chip-row", children: [
2019
+ /* @__PURE__ */ jsx19(ColorControl, { label: config.label, value: actual, resetTo: resetTo != null ? resetTo : "", onChange }),
1571
2020
  reset
1572
2021
  ] });
1573
2022
  }
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,
2023
+ return /* @__PURE__ */ jsxs13("div", { className: "ree-field", children: [
2024
+ /* @__PURE__ */ jsxs13("div", { className: "ree-field-label", children: [
2025
+ /* @__PURE__ */ jsx19("label", { htmlFor: numeric ? id : void 0, children: config.label }),
2026
+ /* @__PURE__ */ jsxs13("span", { className: "ree-field-meta", children: [
2027
+ parsed ? /* @__PURE__ */ jsx19("span", { className: "ree-field-value", children: formatCssLength(parsed.amount, parsed.unit) }) : null,
1579
2028
  reset
1580
2029
  ] })
1581
2030
  ] }),
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(
2031
+ config.control === "alignment" ? /* @__PURE__ */ jsx19(AlignControl, { value: actual, onChange }) : null,
2032
+ config.control === "select" ? /* @__PURE__ */ jsx19(SelectControl, { label: config.label, value: actual, options: selectOptions, onChange }) : null,
2033
+ numeric && parsed ? /* @__PURE__ */ jsx19(
1585
2034
  LengthControl,
1586
2035
  {
1587
2036
  id,
@@ -1595,7 +2044,7 @@ function StyleControl({ config, value, fallback, resetLabel = "Reset", resetTo,
1595
2044
  onChange
1596
2045
  }
1597
2046
  ) : null,
1598
- config.control === "text" ? /* @__PURE__ */ jsx15(
2047
+ config.control === "text" ? /* @__PURE__ */ jsx19(
1599
2048
  CssValueInput,
1600
2049
  {
1601
2050
  label: config.label,
@@ -1606,8 +2055,8 @@ function StyleControl({ config, value, fallback, resetLabel = "Reset", resetTo,
1606
2055
  }
1607
2056
  }
1608
2057
  ) : null,
1609
- config.control === "spacing" ? /* @__PURE__ */ jsx15(SpacingControl, { label: config.label, value: actual, onChange }) : null,
1610
- config.control === "corners" ? /* @__PURE__ */ jsx15(
2058
+ config.control === "spacing" ? /* @__PURE__ */ jsx19(SpacingControl, { label: config.label, value: actual, onChange }) : null,
2059
+ config.control === "corners" ? /* @__PURE__ */ jsx19(
1611
2060
  SpacingControl,
1612
2061
  {
1613
2062
  label: config.label,
@@ -1624,14 +2073,14 @@ function StyleControl({ config, value, fallback, resetLabel = "Reset", resetTo,
1624
2073
  }
1625
2074
 
1626
2075
  // src/inspector/GlobalFields.tsx
1627
- import { jsx as jsx16 } from "react/jsx-runtime";
2076
+ import { jsx as jsx20 } from "react/jsx-runtime";
1628
2077
  function GlobalFields({ global, onChange }) {
1629
2078
  const settings = resolveEmailGlobal(global);
1630
2079
  const renderField = (field) => {
1631
2080
  const key = field.id;
1632
2081
  const current = String(settings[key]);
1633
2082
  const initial = String(DEFAULT_EMAIL_GLOBAL[key]);
1634
- return /* @__PURE__ */ jsx16(
2083
+ return /* @__PURE__ */ jsx20(
1635
2084
  StyleControl,
1636
2085
  {
1637
2086
  config: field,
@@ -1645,7 +2094,7 @@ function GlobalFields({ global, onChange }) {
1645
2094
  key
1646
2095
  );
1647
2096
  };
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)) });
2097
+ return /* @__PURE__ */ jsx20("div", { className: "ree-inspector ree-inspector-global", children: GLOBAL_FIELD_SECTIONS.map((section) => /* @__PURE__ */ jsx20(InspectorSection, { title: section.title, children: /* @__PURE__ */ jsx20(StyleFieldStack, { fields: section.fields, renderField }) }, section.title)) });
1649
2098
  }
1650
2099
 
1651
2100
  // src/constants/style-groups.ts
@@ -1656,11 +2105,44 @@ var STYLE_GROUPS = [
1656
2105
  { group: "decorations", label: "Decoration" }
1657
2106
  ];
1658
2107
 
2108
+ // src/inspector/utils.ts
2109
+ function styleReset(stored, restore, inherit) {
2110
+ if (inherit) return { resetTo: "", show: stored !== "" };
2111
+ return { resetTo: restore, show: stored !== "" && stored !== restore };
2112
+ }
2113
+ function inheritedStyleValue(id, global) {
2114
+ if (!id.startsWith("typography.") && !id.startsWith("element.")) return;
2115
+ switch (id.split(".")[1]) {
2116
+ case "fontFamily":
2117
+ return global.fontFamily;
2118
+ case "fontSize":
2119
+ return global.defaultFontSize;
2120
+ case "fontWeight":
2121
+ return global.fontWeight;
2122
+ case "lineHeight":
2123
+ return global.defaultLineHeight;
2124
+ case "color":
2125
+ return global.textColor;
2126
+ case "textAlign":
2127
+ return global.textAlign;
2128
+ case "paragraphSpacing":
2129
+ return global.paragraphSpacing;
2130
+ case "listSpacing":
2131
+ return global.listSpacing;
2132
+ case "listIndent":
2133
+ return global.listIndent;
2134
+ case "linkColor":
2135
+ return global.linkColor;
2136
+ case "linkDecoration":
2137
+ return global.linkDecoration;
2138
+ }
2139
+ }
2140
+
1659
2141
  // src/inspector/FieldControl.tsx
1660
- import { useRef as useRef3 } from "react";
2142
+ import { useRef as useRef4 } from "react";
1661
2143
 
1662
2144
  // src/inspector/HtmlCodeEditor.tsx
1663
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
2145
+ import { useEffect, useLayoutEffect as useLayoutEffect3, useRef as useRef3 } from "react";
1664
2146
  import { EditorState as EditorState2 } from "@codemirror/state";
1665
2147
  import { EditorView as EditorView2 } from "@codemirror/view";
1666
2148
 
@@ -1799,7 +2281,7 @@ function createHtmlEditorExtensions(placeholderText, onChange) {
1799
2281
  }
1800
2282
 
1801
2283
  // src/inspector/HtmlCodeEditor.tsx
1802
- import { jsx as jsx17 } from "react/jsx-runtime";
2284
+ import { jsx as jsx21 } from "react/jsx-runtime";
1803
2285
  var LINE_HEIGHT2 = 22;
1804
2286
  function HtmlCodeEditor({
1805
2287
  value,
@@ -1807,11 +2289,13 @@ function HtmlCodeEditor({
1807
2289
  rows = 10,
1808
2290
  onChange
1809
2291
  }) {
1810
- const hostRef = useRef2(null);
1811
- const viewRef = useRef2(null);
1812
- const onChangeRef = useRef2(onChange);
1813
- onChangeRef.current = onChange;
1814
- useEffect2(() => {
2292
+ const hostRef = useRef3(null);
2293
+ const viewRef = useRef3(null);
2294
+ const onChangeRef = useRef3(onChange);
2295
+ useLayoutEffect3(() => {
2296
+ onChangeRef.current = onChange;
2297
+ }, [onChange]);
2298
+ useEffect(() => {
1815
2299
  const parent = hostRef.current;
1816
2300
  if (!parent) return;
1817
2301
  const view = new EditorView2({
@@ -1829,7 +2313,7 @@ function HtmlCodeEditor({
1829
2313
  viewRef.current = null;
1830
2314
  };
1831
2315
  }, []);
1832
- useEffect2(() => {
2316
+ useEffect(() => {
1833
2317
  const view = viewRef.current;
1834
2318
  if (!view) return;
1835
2319
  const current = view.state.doc.toString();
@@ -1839,7 +2323,7 @@ function HtmlCodeEditor({
1839
2323
  });
1840
2324
  }, [value]);
1841
2325
  const minHeight = Math.max(rows, 8) * LINE_HEIGHT2;
1842
- return /* @__PURE__ */ jsx17(
2326
+ return /* @__PURE__ */ jsx21(
1843
2327
  "div",
1844
2328
  {
1845
2329
  ref: hostRef,
@@ -1850,23 +2334,25 @@ function HtmlCodeEditor({
1850
2334
  }
1851
2335
 
1852
2336
  // src/inspector/VariablesDropdown.tsx
1853
- import { useState as useState5 } from "react";
1854
- import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2337
+ import { useState as useState7 } from "react";
2338
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
1855
2339
  function VariablesDropdown({ variables, onInsert }) {
1856
- const [open, setOpen] = useState5(false);
2340
+ const [open, setOpen] = useState7(false);
1857
2341
  if (variables.length === 0) return null;
1858
- return /* @__PURE__ */ jsxs12("div", { className: "ree-var-wrap", children: [
1859
- /* @__PURE__ */ jsx18(
2342
+ return /* @__PURE__ */ jsxs14("div", { className: "ree-var-wrap", children: [
2343
+ /* @__PURE__ */ jsx22(
1860
2344
  "button",
1861
2345
  {
1862
2346
  type: "button",
1863
2347
  className: "ree-var-btn",
1864
2348
  onClick: () => setOpen((current) => !current),
1865
2349
  title: "Insert variable",
2350
+ "aria-label": "Insert variable",
2351
+ "aria-expanded": open,
1866
2352
  children: "{ }"
1867
2353
  }
1868
2354
  ),
1869
- open ? /* @__PURE__ */ jsx18("div", { className: "ree-var-menu", children: variables.map((variable) => /* @__PURE__ */ jsxs12(
2355
+ open ? /* @__PURE__ */ jsx22("div", { className: "ree-var-menu", children: variables.map((variable) => /* @__PURE__ */ jsxs14(
1870
2356
  "button",
1871
2357
  {
1872
2358
  type: "button",
@@ -1876,8 +2362,8 @@ function VariablesDropdown({ variables, onInsert }) {
1876
2362
  setOpen(false);
1877
2363
  },
1878
2364
  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
2365
+ /* @__PURE__ */ jsx22("span", { className: "ree-var-name", children: variable.name }),
2366
+ variable.description ? /* @__PURE__ */ jsx22("span", { className: "ree-var-label", children: variable.description }) : null
1881
2367
  ]
1882
2368
  },
1883
2369
  variable.name
@@ -1886,15 +2372,15 @@ function VariablesDropdown({ variables, onInsert }) {
1886
2372
  }
1887
2373
 
1888
2374
  // src/inspector/FieldControl.tsx
1889
- import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
2375
+ import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
1890
2376
  function FieldControl({
1891
2377
  field,
1892
2378
  value,
1893
2379
  variables,
1894
2380
  onChange
1895
2381
  }) {
1896
- var _a, _b, _c, _d, _e;
1897
- const ref = useRef3(null);
2382
+ var _a, _b, _c, _d, _e, _f;
2383
+ const ref = useRef4(null);
1898
2384
  const stringValue = value == null ? "" : String(value);
1899
2385
  const insertVariable = (token) => {
1900
2386
  var _a2, _b2;
@@ -1908,14 +2394,14 @@ function FieldControl({
1908
2394
  onChange(stringValue + token);
1909
2395
  };
1910
2396
  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
2397
+ const label = /* @__PURE__ */ jsxs15("div", { className: "ree-field-label", children: [
2398
+ /* @__PURE__ */ jsx23("span", { children: field.label }),
2399
+ field.type === "text" || field.type === "textarea" || field.type === "url" ? /* @__PURE__ */ jsx23(VariablesDropdown, { variables, onInsert: insertVariable }) : null
1914
2400
  ] });
1915
2401
  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(
2402
+ return /* @__PURE__ */ jsxs15("div", { className: "ree-field", children: [
2403
+ /* @__PURE__ */ jsx23("div", { className: "ree-field-label", children: /* @__PURE__ */ jsx23("span", { children: field.label }) }),
2404
+ /* @__PURE__ */ jsx23(
1919
2405
  HtmlCodeEditor,
1920
2406
  {
1921
2407
  value: stringValue,
@@ -1927,9 +2413,9 @@ function FieldControl({
1927
2413
  ] });
1928
2414
  }
1929
2415
  if (field.type === "textarea") {
1930
- return /* @__PURE__ */ jsxs13("div", { className: "ree-field", children: [
2416
+ return /* @__PURE__ */ jsxs15("div", { className: "ree-field", children: [
1931
2417
  label,
1932
- /* @__PURE__ */ jsx19(
2418
+ /* @__PURE__ */ jsx23(
1933
2419
  "textarea",
1934
2420
  {
1935
2421
  ref: (element) => {
@@ -1945,11 +2431,12 @@ function FieldControl({
1945
2431
  ] });
1946
2432
  }
1947
2433
  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(
2434
+ return /* @__PURE__ */ jsxs15("div", { className: "ree-field", children: [
2435
+ /* @__PURE__ */ jsx23("label", { className: "ree-field-label", children: field.label }),
2436
+ /* @__PURE__ */ jsx23(
1951
2437
  SelectControl,
1952
2438
  {
2439
+ label: field.label,
1953
2440
  value: stringValue,
1954
2441
  options: (_b = field.options) != null ? _b : [],
1955
2442
  onChange
@@ -1958,32 +2445,31 @@ function FieldControl({
1958
2445
  ] });
1959
2446
  }
1960
2447
  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) }) });
2448
+ return /* @__PURE__ */ jsx23("div", { className: "ree-color-chip-row", children: /* @__PURE__ */ jsx23(ColorControl, { label: field.label, value: stringValue, resetTo: String((_c = field.defaultValue) != null ? _c : ""), onChange: (next) => onChange(next) }) });
1962
2449
  }
1963
2450
  if (field.type === "number") {
1964
2451
  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: [
2452
+ const max = (_d = field.max) != null ? _d : 200;
2453
+ const shown = Math.min(max, Math.max((_e = field.min) != null ? _e : 0, Number.isFinite(amount) ? amount : (_f = field.min) != null ? _f : 0));
2454
+ return /* @__PURE__ */ jsxs15("div", { className: "ree-field", children: [
2455
+ /* @__PURE__ */ jsxs15("div", { className: "ree-field-label", children: [
2456
+ /* @__PURE__ */ jsx23("span", { children: field.label }),
2457
+ /* @__PURE__ */ jsxs15("span", { className: "ree-field-value", children: [
1971
2458
  shown,
1972
2459
  "px"
1973
2460
  ] })
1974
2461
  ] }),
1975
- /* @__PURE__ */ jsx19(RangeControl, { label: field.label, value: shown, min: field.min, max, step: field.step, onChange })
2462
+ /* @__PURE__ */ jsx23(RangeControl, { label: field.label, value: shown, min: field.min, max, step: field.step, onChange })
1976
2463
  ] });
1977
2464
  }
1978
- return /* @__PURE__ */ jsxs13("div", { className: "ree-field", children: [
2465
+ return /* @__PURE__ */ jsxs15("div", { className: "ree-field", children: [
1979
2466
  label,
1980
- /* @__PURE__ */ jsx19(
1981
- "input",
2467
+ /* @__PURE__ */ jsx23(
2468
+ TextInput,
1982
2469
  {
1983
2470
  ref: (element) => {
1984
2471
  ref.current = element;
1985
2472
  },
1986
- className: "ree-input",
1987
2473
  "aria-label": field.label,
1988
2474
  min: field.min,
1989
2475
  max: field.max,
@@ -1998,7 +2484,7 @@ function FieldControl({
1998
2484
  }
1999
2485
 
2000
2486
  // src/inspector/InspectorFields.tsx
2001
- import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
2487
+ import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
2002
2488
  function InspectorFields({
2003
2489
  block,
2004
2490
  definition,
@@ -2015,7 +2501,7 @@ function InspectorFields({
2015
2501
  const inherited = inheritedStyleValue(field.id, global);
2016
2502
  const restore = readStyleValue(definition.defaultStyle, field.id) || field.defaultValue || "";
2017
2503
  const reset = styleReset(stored, restore, inherited !== void 0);
2018
- return /* @__PURE__ */ jsx20(
2504
+ return /* @__PURE__ */ jsx24(
2019
2505
  StyleControl,
2020
2506
  {
2021
2507
  config: field,
@@ -2029,35 +2515,52 @@ function InspectorFields({
2029
2515
  field.id
2030
2516
  );
2031
2517
  };
2032
- const sections = STYLE_GROUPS.map((group) => __spreadProps(__spreadValues({}, group), {
2033
- fields: styleFields.filter((field) => field.group === group.group)
2518
+ const sections = (_c = definition.inspectorSections) != null ? _c : STYLE_GROUPS.map((group) => ({
2519
+ title: group.label,
2520
+ fieldNames: group.group === "general" ? contentFields.map((field) => field.name) : [],
2521
+ styleIds: styleFields.filter((field) => field.group === group.group).map((field) => field.id)
2034
2522
  }));
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 })
2523
+ const resolveStyles = (ids = []) => ids.flatMap((id) => {
2524
+ const field = styleFields.find((item) => item.id === id);
2525
+ return field ? [field] : [];
2526
+ });
2527
+ return /* @__PURE__ */ jsxs16("div", { className: "ree-inspector", children: [
2528
+ /* @__PURE__ */ jsxs16("div", { className: "ree-inspector-head", children: [
2529
+ /* @__PURE__ */ jsx24(BlockIcon, { icon: definition.icon, className: "ree-inspector-icon" }),
2530
+ /* @__PURE__ */ jsx24("span", { className: "ree-inspector-title", children: definition.title })
2041
2531
  ] }),
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))
2532
+ sections.map((section) => {
2533
+ var _a2;
2534
+ const fields = ((_a2 = section.fieldNames) != null ? _a2 : []).flatMap((name) => {
2535
+ const field = contentFields.find((item) => item.name === name);
2536
+ return field ? [field] : [];
2537
+ });
2538
+ const styles = resolveStyles(section.styleIds);
2539
+ const advanced = resolveStyles(section.advancedStyleIds);
2540
+ if (!fields.length && !styles.length && !advanced.length) return null;
2541
+ return /* @__PURE__ */ jsxs16(InspectorSection, { title: section.title, children: [
2542
+ fields.map((field) => /* @__PURE__ */ jsx24(
2543
+ FieldControl,
2544
+ {
2545
+ field,
2546
+ value: block.fields[field.name],
2547
+ variables,
2548
+ onChange: (value) => onCommitField(block.id, field.name, value)
2549
+ },
2550
+ field.name
2551
+ )),
2552
+ /* @__PURE__ */ jsx24(StyleFieldStack, { fields: styles, renderField: renderStyle }),
2553
+ advanced.length ? /* @__PURE__ */ jsxs16("details", { className: "ree-inspector-advanced", children: [
2554
+ /* @__PURE__ */ jsx24("summary", { children: "Advanced block border" }),
2555
+ /* @__PURE__ */ jsx24("div", { className: "ree-inspector-section-body", children: /* @__PURE__ */ jsx24(StyleFieldStack, { fields: advanced, renderField: renderStyle }) })
2556
+ ] }) : null
2557
+ ] }, `${block.type}-${section.title}`);
2558
+ })
2056
2559
  ] });
2057
2560
  }
2058
2561
 
2059
2562
  // src/inspector/Inspector.tsx
2060
- import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
2563
+ import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
2061
2564
  function Inspector({
2062
2565
  panel,
2063
2566
  global,
@@ -2070,9 +2573,9 @@ function Inspector({
2070
2573
  onCommitStyle
2071
2574
  }) {
2072
2575
  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(
2576
+ return /* @__PURE__ */ jsxs17("aside", { className: "ree-col ree-col-right", children: [
2577
+ /* @__PURE__ */ jsx25("div", { className: "ree-col-head ree-inspector-bar", children: /* @__PURE__ */ jsxs17("div", { className: "ree-inspector-tabs", role: "group", "aria-label": "Inspector panel", children: [
2578
+ /* @__PURE__ */ jsx25(
2076
2579
  "button",
2077
2580
  {
2078
2581
  type: "button",
@@ -2082,19 +2585,18 @@ function Inspector({
2082
2585
  children: "GLOBAL"
2083
2586
  }
2084
2587
  ),
2085
- /* @__PURE__ */ jsx21(
2588
+ block && definition ? /* @__PURE__ */ jsx25(
2086
2589
  "button",
2087
2590
  {
2088
2591
  type: "button",
2089
2592
  className: `ree-inspector-tab${showBlock ? " is-active" : ""}`,
2090
2593
  "aria-pressed": Boolean(showBlock),
2091
- disabled: !block || !definition,
2092
2594
  onClick: () => onPanelChange("block"),
2093
2595
  children: "BLOCK"
2094
2596
  }
2095
- )
2597
+ ) : null
2096
2598
  ] }) }),
2097
- /* @__PURE__ */ jsx21("div", { className: "ree-inspector-scroll", children: showBlock ? /* @__PURE__ */ jsx21(
2599
+ /* @__PURE__ */ jsx25("div", { className: "ree-inspector-scroll", children: showBlock ? /* @__PURE__ */ jsx25(
2098
2600
  InspectorFields,
2099
2601
  {
2100
2602
  block,
@@ -2104,7 +2606,7 @@ function Inspector({
2104
2606
  onCommitField,
2105
2607
  onCommitStyle
2106
2608
  }
2107
- ) : /* @__PURE__ */ jsx21(GlobalFields, { global, onChange: onCommitGlobal }) })
2609
+ ) : /* @__PURE__ */ jsx25(GlobalFields, { global, onChange: onCommitGlobal }) })
2108
2610
  ] });
2109
2611
  }
2110
2612
 
@@ -2139,16 +2641,29 @@ function viewportWidth(device, global) {
2139
2641
  }
2140
2642
 
2141
2643
  // src/stage/canvas/CanvasBlock.tsx
2644
+ import { useId as useId5, useState as useState12 } from "react";
2142
2645
  import { useSortable } from "@dnd-kit/sortable";
2143
2646
  import { CSS } from "@dnd-kit/utilities";
2144
2647
 
2648
+ // src/document/block-style.ts
2649
+ function resolveBlockStyle(style, definition) {
2650
+ let result = {};
2651
+ if (!definition) return result;
2652
+ for (const field of resolveBlockStyleFields(definition.styleFields, definition.exactStyleFields)) {
2653
+ const value = readStyleValue(style, field.id);
2654
+ if (value) result = writeStyleValue(result, field.id, value);
2655
+ }
2656
+ return result;
2657
+ }
2658
+
2145
2659
  // src/render/context.ts
2146
- function buildRenderContext(node, mode, global = DEFAULT_EMAIL_GLOBAL) {
2147
- var _a, _b;
2660
+ function buildRenderContext(node, mode, global = DEFAULT_EMAIL_GLOBAL, definition = getBlockDefinition(node.type)) {
2661
+ var _a;
2662
+ const block = __spreadProps(__spreadValues({}, node), { style: resolveBlockStyle(node.style, definition) });
2148
2663
  return {
2149
- block: node,
2664
+ block,
2150
2665
  fields: (_a = node.fields) != null ? _a : {},
2151
- style: (_b = node.style) != null ? _b : {},
2666
+ style: block.style,
2152
2667
  mode,
2153
2668
  global
2154
2669
  };
@@ -2363,31 +2878,38 @@ function renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email",
2363
2878
  }
2364
2879
  function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", global = DEFAULT_EMAIL_GLOBAL) {
2365
2880
  const definition = getBlockDefinition(node.type, definitions);
2366
- return definition ? definition.render(buildRenderContext(node, mode, global)) : `<!-- unknown block type: ${node.type} -->`;
2881
+ return definition ? definition.render(buildRenderContext(node, mode, global, definition)) : `<!-- unknown block type: ${node.type} -->`;
2367
2882
  }
2368
2883
  function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
2369
- var _a, _b, _c, _d, _e;
2884
+ var _a, _b, _c;
2370
2885
  const normalizedOptions = normalizeBlockRenderOptions(options);
2371
2886
  const global = (_a = normalizedOptions.global) != null ? _a : DEFAULT_EMAIL_GLOBAL;
2887
+ const style = resolveBlockStyle(node.style, getBlockDefinition(node.type, definitions));
2372
2888
  const inner = renderBlockContent(node, definitions, mode, __spreadProps(__spreadValues({}, normalizedOptions), {
2373
2889
  global
2374
2890
  }));
2375
- const height = cssPx((_c = (_b = node.style) == null ? void 0 : _b.dimension) == null ? void 0 : _c.height);
2891
+ const height = cssPx((_b = style.dimension) == null ? void 0 : _b.height);
2376
2892
  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>`;
2893
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="table-layout:fixed;"><tr><td style="padding:${esc(cssSpacing((_c = style.dimension) == null ? void 0 : _c.margin))};"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="table-layout:fixed;"><tr><td${cellAttrs} style="${blockCellCss(style, global)}">${inner}</td></tr></table></td></tr></table>`;
2378
2894
  }
2379
2895
 
2380
2896
  // src/stage/rich-text/RichText.tsx
2381
- import { useEffect as useEffect5, useLayoutEffect as useLayoutEffect3, useRef as useRef9, useState as useState8 } from "react";
2897
+ import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect6, useRef as useRef9, useState as useState11 } from "react";
2382
2898
  import { createPortal } from "react-dom";
2383
2899
  import { EditorContent, useEditor } from "@tiptap/react";
2384
2900
 
2385
2901
  // src/stage/rich-text/components/RichTextToolbar.tsx
2386
- import { useEffect as useEffect4, useRef as useRef8, useState as useState7 } from "react";
2902
+ import { useEffect as useEffect3, useRef as useRef8, useState as useState10 } from "react";
2903
+ import { isAllowedUri as isAllowedUri2 } from "@tiptap/extension-link";
2387
2904
  import { useEditorState } from "@tiptap/react";
2388
2905
 
2389
2906
  // src/stage/rich-text/constants.ts
2390
2907
  var PARAGRAPH_SPACING_MAX = 48;
2908
+ var DEFAULT_LINK_TARGET = "_blank";
2909
+ var LINK_TARGETS = [
2910
+ { value: "_self", label: "Current page" },
2911
+ { value: "_blank", label: "New page" }
2912
+ ];
2391
2913
  var LINE_HEIGHT_RANGE = { min: 1, max: 2.5, step: 0.05 };
2392
2914
  var FONT_SIZES = [
2393
2915
  "12px",
@@ -2403,17 +2925,6 @@ var FONT_SIZES = [
2403
2925
  "36px",
2404
2926
  "48px"
2405
2927
  ];
2406
- var TEXT_COLORS = [
2407
- "#202223",
2408
- "#303030",
2409
- "#6d7175",
2410
- "#d72c0d",
2411
- "#b98900",
2412
- "#008060",
2413
- "#005bd3",
2414
- "#6d28d9",
2415
- "#ffffff"
2416
- ];
2417
2928
  var EMOJIS = [
2418
2929
  "\u{1F600}",
2419
2930
  "\u{1F601}",
@@ -2456,10 +2967,11 @@ var TEXT_ALIGNS2 = [
2456
2967
  ];
2457
2968
 
2458
2969
  // 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(() => {
2970
+ import { useLayoutEffect as useLayoutEffect4, useRef as useRef5 } from "react";
2971
+ function useAnchoredToolbar(anchorRef, active, compact = false) {
2972
+ const toolbarRef = useRef5(null);
2973
+ useLayoutEffect4(() => {
2974
+ var _a;
2463
2975
  if (!active) return;
2464
2976
  const anchor = anchorRef.current;
2465
2977
  const toolbar = toolbarRef.current;
@@ -2468,8 +2980,8 @@ function useAnchoredToolbar(anchorRef, active) {
2468
2980
  const ownerWindow = ownerDocument.defaultView;
2469
2981
  if (!ownerWindow) return;
2470
2982
  const place = () => {
2471
- var _a;
2472
- const block = (_a = anchor.closest(".ree-block")) != null ? _a : anchor;
2983
+ var _a2;
2984
+ const block = (_a2 = anchor.closest(".ree-block")) != null ? _a2 : anchor;
2473
2985
  const rect = block.getBoundingClientRect();
2474
2986
  const offscreen = rect.width === 0 || rect.height === 0 || rect.bottom < 0 || rect.top > ownerWindow.innerHeight;
2475
2987
  if (offscreen) {
@@ -2477,7 +2989,13 @@ function useAnchoredToolbar(anchorRef, active) {
2477
2989
  return;
2478
2990
  }
2479
2991
  const width = Math.min(rect.width, ownerWindow.innerWidth - 16);
2480
- toolbar.style.width = `${width}px`;
2992
+ toolbar.style.width = compact ? "max-content" : `${width}px`;
2993
+ toolbar.style.maxWidth = `${width}px`;
2994
+ let previousBottom = null;
2995
+ for (const group of toolbar.querySelectorAll(":scope > .ree-rte-group")) {
2996
+ group.classList.toggle("is-row-start", previousBottom === null || group.offsetTop >= previousBottom);
2997
+ previousBottom = group.offsetTop + group.offsetHeight;
2998
+ }
2481
2999
  const height = toolbar.offsetHeight;
2482
3000
  let top = rect.top - height - 4;
2483
3001
  if (top < 8) {
@@ -2485,7 +3003,7 @@ function useAnchoredToolbar(anchorRef, active) {
2485
3003
  top = below + height <= ownerWindow.innerHeight - 8 ? below : 8;
2486
3004
  }
2487
3005
  let left = rect.left;
2488
- const maxLeft = Math.max(8, ownerWindow.innerWidth - width - 8);
3006
+ const maxLeft = Math.max(8, ownerWindow.innerWidth - toolbar.offsetWidth - 8);
2489
3007
  if (left > maxLeft) left = maxLeft;
2490
3008
  if (left < 8) left = 8;
2491
3009
  toolbar.style.top = `${top}px`;
@@ -2495,6 +3013,8 @@ function useAnchoredToolbar(anchorRef, active) {
2495
3013
  place();
2496
3014
  const observer = new ResizeObserver(place);
2497
3015
  observer.observe(toolbar);
3016
+ for (const group of toolbar.querySelectorAll(".ree-rte-group")) observer.observe(group);
3017
+ observer.observe((_a = anchor.closest(".ree-block")) != null ? _a : anchor);
2498
3018
  ownerDocument.addEventListener("scroll", place, true);
2499
3019
  ownerWindow.addEventListener("resize", place);
2500
3020
  return () => {
@@ -2502,7 +3022,7 @@ function useAnchoredToolbar(anchorRef, active) {
2502
3022
  ownerDocument.removeEventListener("scroll", place, true);
2503
3023
  ownerWindow.removeEventListener("resize", place);
2504
3024
  };
2505
- }, [active, anchorRef]);
3025
+ }, [active, anchorRef, compact]);
2506
3026
  return toolbarRef;
2507
3027
  }
2508
3028
 
@@ -2514,15 +3034,11 @@ function textAttribute(value) {
2514
3034
  function markWeight(value) {
2515
3035
  return value === FONT_WEIGHTS.normal || value === FONT_WEIGHTS.bold ? value : "";
2516
3036
  }
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
3037
  function readToolbarState(editor) {
2523
3038
  var _a;
2524
3039
  const textStyle = editor.getAttributes("textStyle");
2525
3040
  const link = editor.getAttributes("link");
3041
+ const list = editor.getAttributes(editor.isActive("orderedList") ? "orderedList" : "bulletList");
2526
3042
  return {
2527
3043
  bold: editor.isActive("bold"),
2528
3044
  italic: editor.isActive("italic"),
@@ -2536,87 +3052,85 @@ function readToolbarState(editor) {
2536
3052
  fontWeight: markWeight(textStyle.fontWeight),
2537
3053
  color: textAttribute(textStyle.color),
2538
3054
  linkHref: textAttribute(link.href),
3055
+ linkTarget: textAttribute(link.target),
3056
+ linkColor: textAttribute(link.color),
3057
+ linkDecoration: textAttribute(link.decoration),
3058
+ listSpacing: textAttribute(list.listSpacing),
3059
+ listIndent: textAttribute(list.listIndent),
2539
3060
  spacing: textAttribute(editor.getAttributes("paragraph").spacing),
2540
3061
  lineHeight: textAttribute(editor.getAttributes("paragraph").lineHeight)
2541
3062
  };
2542
3063
  }
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;
3064
+
3065
+ // src/stage/rich-text/formatting.ts
3066
+ import { getAttributes, isMarkActive } from "@tiptap/react";
3067
+ function currentSelection(editor) {
3068
+ const { selection, storedMarks } = editor.state;
3069
+ return { anchor: selection.anchor, head: selection.head, storedMarks };
2563
3070
  }
2564
- function focusSelection(editor, range) {
2565
- const chain = editor.chain().focus();
3071
+ function focusSelection(editor, range, focus = true) {
3072
+ const chain = focus ? editor.chain().focus() : editor.chain();
2566
3073
  if (!range) return chain;
2567
- return chain.setTextSelection(range);
3074
+ return chain.command(({ tr, commands }) => {
3075
+ if (tr.selection.anchor !== range.anchor || tr.selection.head !== range.head) {
3076
+ commands.setTextSelection({ from: range.anchor, to: range.head });
3077
+ }
3078
+ if (tr.selection.empty) tr.setStoredMarks(range.storedMarks);
3079
+ return true;
3080
+ });
2568
3081
  }
2569
- function currentSelection(editor) {
2570
- const { from, to } = editor.state.selection;
2571
- return { from, to };
3082
+ function formatText(editor, command, range, scope = "text", focus = true) {
3083
+ const chain = focusSelection(editor, range, focus);
3084
+ return chain.command(({ tr, chain: scopedChain }) => {
3085
+ const selection = tr.selection;
3086
+ const pendingMarks = tr.storedMarks;
3087
+ const bookmark = selection.getBookmark();
3088
+ const scoped = scopedChain();
3089
+ if (selection.empty && (scope === "paragraph" || tr.doc.textContent)) scoped.selectAll();
3090
+ return command(scoped).command(({ tr: formatted }) => {
3091
+ const marks = formatted.storedMarks;
3092
+ formatted.setSelection(bookmark.map(formatted.mapping).resolve(formatted.doc));
3093
+ if (!formatted.doc.textContent) formatted.setStoredMarks(scope === "paragraph" ? pendingMarks : marks);
3094
+ return true;
3095
+ }).run();
3096
+ }).run();
2572
3097
  }
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
- ] });
3098
+ function isVisuallyBold(markWeight2, strong, inherited) {
3099
+ if (strong || markWeight2 === "bold") return true;
3100
+ if (markWeight2 === "normal") return false;
3101
+ return inherited === "bold";
2611
3102
  }
2612
- function sameColor(left, right) {
2613
- return left.toLowerCase() === right.toLowerCase();
3103
+ function toggleVisualBold(editor, inherited) {
3104
+ return formatText(editor, (chain) => chain.command(({ state, chain: scopedChain }) => {
3105
+ const weight = getAttributes(state, "textStyle").fontWeight;
3106
+ let visuallyBold = isVisuallyBold(
3107
+ weight === "normal" || weight === "bold" ? weight : "",
3108
+ isMarkActive(state, "bold"),
3109
+ inherited
3110
+ );
3111
+ if (!state.selection.empty) {
3112
+ visuallyBold = true;
3113
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node) => {
3114
+ var _a;
3115
+ if (!node.isText) return;
3116
+ const textWeight = (_a = node.marks.find((mark) => mark.type.name === "textStyle")) == null ? void 0 : _a.attrs.fontWeight;
3117
+ if (!isVisuallyBold(
3118
+ textWeight === "normal" || textWeight === "bold" ? textWeight : "",
3119
+ node.marks.some((mark) => mark.type.name === "bold"),
3120
+ inherited
3121
+ )) visuallyBold = false;
3122
+ });
3123
+ }
3124
+ const next = scopedChain().unsetBold();
3125
+ const fontWeight = visuallyBold ? inherited === "bold" ? "normal" : null : inherited === "bold" ? null : "bold";
3126
+ return next.setMark("textStyle", { fontWeight }).removeEmptyTextStyle().run();
3127
+ }));
2614
3128
  }
2615
3129
 
2616
3130
  // src/stage/rich-text/components/EmojiMenu.tsx
2617
- import { jsx as jsx23 } from "react/jsx-runtime";
3131
+ import { jsx as jsx26 } from "react/jsx-runtime";
2618
3132
  function EmojiMenu({ onPick }) {
2619
- return /* @__PURE__ */ jsx23("div", { className: "ree-rte-emoji-grid", children: EMOJIS.map((emoji) => /* @__PURE__ */ jsx23(
3133
+ return /* @__PURE__ */ jsx26("div", { className: "ree-rte-emoji-grid", children: EMOJIS.map((emoji) => /* @__PURE__ */ jsx26(
2620
3134
  "button",
2621
3135
  {
2622
3136
  type: "button",
@@ -2629,65 +3143,27 @@ function EmojiMenu({ onPick }) {
2629
3143
  )) });
2630
3144
  }
2631
3145
 
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
3146
  // src/stage/rich-text/components/ToolbarButton.tsx
2676
- import { jsx as jsx25 } from "react/jsx-runtime";
3147
+ import { jsx as jsx27 } from "react/jsx-runtime";
2677
3148
  function ToolbarButton({
2678
3149
  title,
2679
3150
  active = false,
2680
3151
  disabled = false,
3152
+ expanded,
3153
+ controls,
2681
3154
  onClick,
2682
3155
  children
2683
3156
  }) {
2684
- return /* @__PURE__ */ jsx25(
3157
+ return /* @__PURE__ */ jsx27(
2685
3158
  "button",
2686
3159
  {
2687
3160
  type: "button",
2688
3161
  className: `ree-tool-btn${active ? " is-active" : ""}`,
2689
3162
  title,
2690
- "aria-pressed": active,
3163
+ "aria-pressed": expanded === void 0 ? active : void 0,
3164
+ "aria-haspopup": expanded === void 0 ? void 0 : "dialog",
3165
+ "aria-expanded": expanded,
3166
+ "aria-controls": controls,
2691
3167
  disabled,
2692
3168
  onClick,
2693
3169
  children
@@ -2695,10 +3171,32 @@ function ToolbarButton({
2695
3171
  );
2696
3172
  }
2697
3173
 
3174
+ // src/stage/rich-text/components/TextColorControl.tsx
3175
+ import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
3176
+ function TextColorControl({ value, fallback, onOpen, onChange, onReset }) {
3177
+ const { id, open, anchorRef, panelRef, toggle, close } = useColorPicker(onOpen);
3178
+ return /* @__PURE__ */ jsxs18("div", { className: "ree-rte-pop", ref: anchorRef, children: [
3179
+ /* @__PURE__ */ jsx28(
3180
+ ToolbarButton,
3181
+ {
3182
+ title: "Text color",
3183
+ expanded: open,
3184
+ controls: open ? id : void 0,
3185
+ onClick: toggle,
3186
+ children: /* @__PURE__ */ jsx28("span", { className: "ree-tool-color", style: { borderBottomColor: value || fallback }, children: "A" })
3187
+ }
3188
+ ),
3189
+ open ? /* @__PURE__ */ jsx28(ColorPicker, { id, label: "Text color", value, fallback, panelRef, onChange, onReset, onClose: close }) : null
3190
+ ] });
3191
+ }
3192
+
3193
+ // src/stage/rich-text/components/ToolbarMenu.tsx
3194
+ import { useLayoutEffect as useLayoutEffect5, useRef as useRef6, useState as useState8 } from "react";
3195
+
2698
3196
  // src/stage/rich-text/components/ToolbarIcon.tsx
2699
- import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
3197
+ import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
2700
3198
  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) });
3199
+ return /* @__PURE__ */ jsx29("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", fill: "none", children: iconPaths(name) });
2702
3200
  }
2703
3201
  function iconPaths(name) {
2704
3202
  const stroke = {
@@ -2709,73 +3207,83 @@ function iconPaths(name) {
2709
3207
  strokeLinejoin: "round"
2710
3208
  };
2711
3209
  switch (name) {
3210
+ case "space-before":
3211
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3212
+ /* @__PURE__ */ jsx29("path", { d: "M4 4h16M4 15h16M4 20h10" }),
3213
+ /* @__PURE__ */ jsx29("path", { d: "M12 7v5m-3-3 3 3 3-3" })
3214
+ ] }));
3215
+ case "line-height":
3216
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3217
+ /* @__PURE__ */ jsx29("path", { d: "M11 5h9M11 12h9M11 19h9" }),
3218
+ /* @__PURE__ */ jsx29("path", { d: "M5 4v16M2 7l3-3 3 3M2 17l3 3 3-3" })
3219
+ ] }));
2712
3220
  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" })
3221
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3222
+ /* @__PURE__ */ jsx29("path", { d: "M4 6h16" }),
3223
+ /* @__PURE__ */ jsx29("path", { d: "M4 10h10" }),
3224
+ /* @__PURE__ */ jsx29("path", { d: "M4 14h16" }),
3225
+ /* @__PURE__ */ jsx29("path", { d: "M4 18h10" })
2718
3226
  ] }));
2719
3227
  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" })
3228
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3229
+ /* @__PURE__ */ jsx29("path", { d: "M4 6h16" }),
3230
+ /* @__PURE__ */ jsx29("path", { d: "M7 10h10" }),
3231
+ /* @__PURE__ */ jsx29("path", { d: "M4 14h16" }),
3232
+ /* @__PURE__ */ jsx29("path", { d: "M7 18h10" })
2725
3233
  ] }));
2726
3234
  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" })
3235
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3236
+ /* @__PURE__ */ jsx29("path", { d: "M4 6h16" }),
3237
+ /* @__PURE__ */ jsx29("path", { d: "M10 10h10" }),
3238
+ /* @__PURE__ */ jsx29("path", { d: "M4 14h16" }),
3239
+ /* @__PURE__ */ jsx29("path", { d: "M10 18h10" })
2732
3240
  ] }));
2733
3241
  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" })
3242
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3243
+ /* @__PURE__ */ jsx29("path", { d: "M4 6h16" }),
3244
+ /* @__PURE__ */ jsx29("path", { d: "M4 10h16" }),
3245
+ /* @__PURE__ */ jsx29("path", { d: "M4 14h16" }),
3246
+ /* @__PURE__ */ jsx29("path", { d: "M4 18h16" })
2739
3247
  ] }));
2740
3248
  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" })
3249
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3250
+ /* @__PURE__ */ jsx29("path", { d: "M10 7h10" }),
3251
+ /* @__PURE__ */ jsx29("path", { d: "M10 12h10" }),
3252
+ /* @__PURE__ */ jsx29("path", { d: "M10 17h10" }),
3253
+ /* @__PURE__ */ jsx29("circle", { cx: "5", cy: "7", r: "1.2", fill: "currentColor", stroke: "none" }),
3254
+ /* @__PURE__ */ jsx29("circle", { cx: "5", cy: "12", r: "1.2", fill: "currentColor", stroke: "none" }),
3255
+ /* @__PURE__ */ jsx29("circle", { cx: "5", cy: "17", r: "1.2", fill: "currentColor", stroke: "none" })
2748
3256
  ] }));
2749
3257
  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" })
3258
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3259
+ /* @__PURE__ */ jsx29("path", { d: "M11 7h9" }),
3260
+ /* @__PURE__ */ jsx29("path", { d: "M11 12h9" }),
3261
+ /* @__PURE__ */ jsx29("path", { d: "M11 17h9" }),
3262
+ /* @__PURE__ */ jsx29("text", { x: "3", y: "9", fill: "currentColor", stroke: "none", fontSize: "7", fontFamily: "sans-serif", children: "1" }),
3263
+ /* @__PURE__ */ jsx29("text", { x: "3", y: "14", fill: "currentColor", stroke: "none", fontSize: "7", fontFamily: "sans-serif", children: "2" }),
3264
+ /* @__PURE__ */ jsx29("text", { x: "3", y: "19", fill: "currentColor", stroke: "none", fontSize: "7", fontFamily: "sans-serif", children: "3" })
2757
3265
  ] }));
2758
3266
  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" })
3267
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3268
+ /* @__PURE__ */ jsx29("circle", { cx: "12", cy: "12", r: "8" }),
3269
+ /* @__PURE__ */ jsx29("path", { d: "M8.5 14.2a4.2 4.2 0 0 0 7 0" }),
3270
+ /* @__PURE__ */ jsx29("path", { d: "M9 10h.01" }),
3271
+ /* @__PURE__ */ jsx29("path", { d: "M15 10h.01" })
2764
3272
  ] }));
2765
3273
  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" })
3274
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3275
+ /* @__PURE__ */ jsx29("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" }),
3276
+ /* @__PURE__ */ jsx29("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
3277
  ] }));
2770
3278
  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" })
3279
+ return /* @__PURE__ */ jsxs19("g", __spreadProps(__spreadValues({}, stroke), { children: [
3280
+ /* @__PURE__ */ jsx29("path", { d: "M9 17H7a5 5 0 0 1 0-10h2" }),
3281
+ /* @__PURE__ */ jsx29("path", { d: "M15 7h2a5 5 0 0 1 0 10h-2" }),
3282
+ /* @__PURE__ */ jsx29("path", { d: "M8 12h4" }),
3283
+ /* @__PURE__ */ jsx29("path", { d: "m4 4 16 16" })
2776
3284
  ] }));
2777
3285
  case "chevron":
2778
- return /* @__PURE__ */ jsx26(
3286
+ return /* @__PURE__ */ jsx29(
2779
3287
  "path",
2780
3288
  {
2781
3289
  d: "M7 10.2 12 15l5-4.8",
@@ -2790,8 +3298,7 @@ function iconPaths(name) {
2790
3298
  }
2791
3299
 
2792
3300
  // 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";
3301
+ import { jsx as jsx30, jsxs as jsxs20 } from "react/jsx-runtime";
2795
3302
  function ToolbarMenu({
2796
3303
  label,
2797
3304
  title,
@@ -2804,9 +3311,9 @@ function ToolbarMenu({
2804
3311
  panelClassName,
2805
3312
  children
2806
3313
  }) {
2807
- const rootRef = useRef7(null);
2808
- const [up, setUp] = useState6(false);
2809
- useLayoutEffect2(() => {
3314
+ const rootRef = useRef6(null);
3315
+ const [up, setUp] = useState8(false);
3316
+ useLayoutEffect5(() => {
2810
3317
  if (!open || !rootRef.current) return;
2811
3318
  const ownerWindow = rootRef.current.ownerDocument.defaultView;
2812
3319
  if (!ownerWindow) return;
@@ -2814,22 +3321,23 @@ function ToolbarMenu({
2814
3321
  setUp(rect.bottom + 260 > ownerWindow.innerHeight && rect.top > 260);
2815
3322
  }, [open]);
2816
3323
  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(
3324
+ return /* @__PURE__ */ jsxs20("div", { className: `ree-rte-pop${up ? " is-up" : ""}`, ref: rootRef, children: [
3325
+ /* @__PURE__ */ jsxs20(
2819
3326
  "button",
2820
3327
  {
2821
3328
  type: "button",
2822
3329
  className: buttonClass,
2823
3330
  title,
3331
+ "aria-label": variant === "icon" ? title : void 0,
2824
3332
  "aria-expanded": open,
2825
3333
  onClick: onToggle,
2826
3334
  children: [
2827
- variant === "select" ? /* @__PURE__ */ jsx27("span", { className: "ree-rte-select-label", children: label }) : label,
2828
- caret ? /* @__PURE__ */ jsx27(ToolbarIcon, { name: "chevron" }) : null
3335
+ variant === "select" ? /* @__PURE__ */ jsx30("span", { className: "ree-rte-select-label", children: label }) : label,
3336
+ caret ? /* @__PURE__ */ jsx30(ToolbarIcon, { name: "chevron" }) : null
2829
3337
  ]
2830
3338
  }
2831
3339
  ),
2832
- open ? /* @__PURE__ */ jsx27(
3340
+ open ? /* @__PURE__ */ jsx30(
2833
3341
  "div",
2834
3342
  {
2835
3343
  className: [
@@ -2843,8 +3351,151 @@ function ToolbarMenu({
2843
3351
  ] });
2844
3352
  }
2845
3353
 
3354
+ // src/stage/rich-text/components/TextFormatControls.tsx
3355
+ import { Fragment as Fragment2, jsx as jsx31, jsxs as jsxs21 } from "react/jsx-runtime";
3356
+ function TextFormatControls({ editor, profile, state, inherited, menu, range, bold, toggleMenu, run, children }) {
3357
+ const fontFamily = state.fontFamily || inherited.fontFamily;
3358
+ const fontSize = state.fontSize || inherited.fontSize;
3359
+ return /* @__PURE__ */ jsxs21(Fragment2, { children: [
3360
+ /* @__PURE__ */ jsxs21("div", { className: "ree-rte-group", role: "group", "aria-label": "Font", children: [
3361
+ /* @__PURE__ */ jsx31(
3362
+ ToolbarMenu,
3363
+ {
3364
+ title: "Font",
3365
+ open: menu === "font",
3366
+ onToggle: () => toggleMenu("font"),
3367
+ label: fontFamilyLabel(fontFamily),
3368
+ children: FONT_FAMILIES.map((font) => /* @__PURE__ */ jsx31(
3369
+ "button",
3370
+ {
3371
+ type: "button",
3372
+ className: `ree-rte-option${fontFamily === font.value ? " is-active" : ""}`,
3373
+ style: { fontFamily: font.value },
3374
+ onClick: () => run(() => formatText(editor, (chain) => chain.setFontFamily(font.value), range)),
3375
+ children: font.label
3376
+ },
3377
+ font.value
3378
+ ))
3379
+ }
3380
+ ),
3381
+ /* @__PURE__ */ jsx31(
3382
+ ToolbarMenu,
3383
+ {
3384
+ title: "Font size",
3385
+ open: menu === "size",
3386
+ onToggle: () => toggleMenu("size"),
3387
+ label: fontSize || "15px",
3388
+ children: FONT_SIZES.map((size) => /* @__PURE__ */ jsx31(
3389
+ "button",
3390
+ {
3391
+ type: "button",
3392
+ className: `ree-rte-option${fontSize === size ? " is-active" : ""}`,
3393
+ onClick: () => run(() => formatText(editor, (chain) => chain.setFontSize(size), range)),
3394
+ children: size
3395
+ },
3396
+ size
3397
+ ))
3398
+ }
3399
+ )
3400
+ ] }),
3401
+ /* @__PURE__ */ jsxs21("div", { className: "ree-rte-group", role: "group", "aria-label": "Text style", children: [
3402
+ /* @__PURE__ */ jsx31(
3403
+ ToolbarButton,
3404
+ {
3405
+ title: "Bold",
3406
+ active: bold,
3407
+ onClick: () => toggleVisualBold(editor, inherited.fontWeight),
3408
+ children: /* @__PURE__ */ jsx31("span", { className: "ree-tool-bold", children: "B" })
3409
+ }
3410
+ ),
3411
+ /* @__PURE__ */ jsx31(
3412
+ ToolbarButton,
3413
+ {
3414
+ title: "Italic",
3415
+ active: state.italic,
3416
+ onClick: () => formatText(editor, (chain) => chain.toggleItalic()),
3417
+ children: /* @__PURE__ */ jsx31("span", { className: "ree-tool-italic", children: "I" })
3418
+ }
3419
+ ),
3420
+ /* @__PURE__ */ jsx31(
3421
+ ToolbarButton,
3422
+ {
3423
+ title: "Underline",
3424
+ active: state.underline,
3425
+ onClick: () => formatText(editor, (chain) => chain.toggleUnderline()),
3426
+ children: /* @__PURE__ */ jsx31("span", { className: "ree-tool-underline", children: "U" })
3427
+ }
3428
+ ),
3429
+ profile === "text" ? /* @__PURE__ */ jsx31(
3430
+ ToolbarButton,
3431
+ {
3432
+ title: "Strikethrough",
3433
+ active: state.strike,
3434
+ onClick: () => formatText(editor, (chain) => chain.toggleStrike()),
3435
+ children: /* @__PURE__ */ jsx31("span", { className: "ree-tool-strike", children: "S" })
3436
+ }
3437
+ ) : null,
3438
+ children
3439
+ ] })
3440
+ ] });
3441
+ }
3442
+
3443
+ // src/stage/rich-text/list-formatting.ts
3444
+ import { TextSelection } from "@tiptap/pm/state";
3445
+ function isList(node) {
3446
+ return node.type.name === "bulletList" || node.type.name === "orderedList";
3447
+ }
3448
+ function toggleTextList(editor, type) {
3449
+ return editor.chain().focus().command(({ tr, chain }) => {
3450
+ const selection = tr.selection;
3451
+ const bookmark = selection.getBookmark();
3452
+ const storedMarks = tr.storedMarks;
3453
+ const from = selection.empty ? 0 : selection.from;
3454
+ let to = selection.empty ? tr.doc.content.size : selection.to;
3455
+ const last = tr.doc.lastChild;
3456
+ if (selection.empty && tr.doc.childCount > 1 && (last == null ? void 0 : last.type.name) === "paragraph" && last.content.size === 0) {
3457
+ const previous = tr.doc.child(tr.doc.childCount - 2);
3458
+ if (isList(previous)) to -= last.nodeSize;
3459
+ }
3460
+ const lists = [];
3461
+ const paragraphs = [];
3462
+ tr.doc.nodesBetween(from, to, (node, pos) => {
3463
+ if (isList(node)) {
3464
+ lists.push({ node, from: pos, to: pos + node.nodeSize });
3465
+ return false;
3466
+ }
3467
+ if (node.type.name === "paragraph") {
3468
+ const previous = paragraphs[paragraphs.length - 1];
3469
+ if ((previous == null ? void 0 : previous.to) === pos) previous.to = pos + node.nodeSize;
3470
+ else paragraphs.push({ node, from: pos, to: pos + node.nodeSize });
3471
+ return false;
3472
+ }
3473
+ });
3474
+ const remove = lists.length > 0 && paragraphs.length === 0 && lists.every((item) => item.node.type.name === type);
3475
+ if (remove) {
3476
+ for (const list of lists.reverse()) {
3477
+ const start = selection.empty ? list.from + 1 : Math.max(selection.from, list.from + 1);
3478
+ const end = selection.empty ? list.to - 1 : Math.min(selection.to, list.to - 1);
3479
+ tr.setSelection(TextSelection.between(tr.doc.resolve(start), tr.doc.resolve(end)));
3480
+ chain().liftListItem("listItem").run();
3481
+ }
3482
+ } else {
3483
+ const target = editor.schema.nodes[type];
3484
+ for (const list of lists) {
3485
+ if (list.node.type !== target) tr.setNodeMarkup(list.from, target, list.node.attrs);
3486
+ }
3487
+ for (const paragraph of paragraphs.reverse()) {
3488
+ chain().setTextSelection({ from: paragraph.from + 1, to: paragraph.to - 1 }).wrapInList(type).run();
3489
+ }
3490
+ }
3491
+ tr.setSelection(bookmark.map(tr.mapping).resolve(tr.doc));
3492
+ if (tr.selection.empty) tr.setStoredMarks(storedMarks);
3493
+ return true;
3494
+ }).run();
3495
+ }
3496
+
2846
3497
  // src/stage/rich-text/components/MetricSlider.tsx
2847
- import { jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
3498
+ import { jsx as jsx32, jsxs as jsxs22 } from "react/jsx-runtime";
2848
3499
  function MetricSlider({
2849
3500
  label,
2850
3501
  value,
@@ -2856,32 +3507,193 @@ function MetricSlider({
2856
3507
  onChange,
2857
3508
  onReset
2858
3509
  }) {
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 })
3510
+ return /* @__PURE__ */ jsxs22("div", { className: "ree-rte-metric", children: [
3511
+ /* @__PURE__ */ jsxs22("div", { className: "ree-rte-metric-label", children: [
3512
+ /* @__PURE__ */ jsx32("span", { children: label }),
3513
+ /* @__PURE__ */ jsxs22("span", { className: "ree-rte-metric-meta", children: [
3514
+ /* @__PURE__ */ jsx32("span", { children: display }),
3515
+ /* @__PURE__ */ jsx32(ResetButton, { label: `Reset ${label.toLowerCase()}`, disabled: !overridden, onClick: onReset })
3516
+ ] })
2864
3517
  ] }),
2865
- /* @__PURE__ */ jsx28(
2866
- "input",
3518
+ /* @__PURE__ */ jsx32(
3519
+ RangeControl,
2867
3520
  {
2868
- "aria-label": label,
2869
- type: "range",
2870
- className: "ree-range",
2871
- style: { "--ree-range-progress": `${progress}%` },
3521
+ label,
2872
3522
  value,
2873
3523
  min,
2874
3524
  max,
2875
3525
  step,
2876
- onChange: (event) => onChange(Number(event.target.value))
3526
+ onChange
3527
+ }
3528
+ )
3529
+ ] });
3530
+ }
3531
+
3532
+ // src/stage/rich-text/components/ListControls.tsx
3533
+ import { jsx as jsx33, jsxs as jsxs23 } from "react/jsx-runtime";
3534
+ function ListControls({ editor, state, menu, range, inherited, onToggle }) {
3535
+ const listType = state.orderedList ? "orderedList" : "bulletList";
3536
+ const spacing = state.listSpacing || inherited.listSpacing || "4px";
3537
+ const indent = state.listIndent || inherited.listIndent || "22px";
3538
+ const update = (key, value) => {
3539
+ focusSelection(editor, range).updateAttributes(listType, { [key]: value }).run();
3540
+ };
3541
+ return /* @__PURE__ */ jsxs23("div", { className: "ree-rte-group", role: "group", "aria-label": "Lists", children: [
3542
+ /* @__PURE__ */ jsx33(
3543
+ ToolbarButton,
3544
+ {
3545
+ title: "Bulleted list",
3546
+ active: state.bulletList,
3547
+ onClick: () => toggleTextList(editor, "bulletList"),
3548
+ children: /* @__PURE__ */ jsx33(ToolbarIcon, { name: "list-bullet" })
3549
+ }
3550
+ ),
3551
+ /* @__PURE__ */ jsx33(
3552
+ ToolbarButton,
3553
+ {
3554
+ title: "Numbered list",
3555
+ active: state.orderedList,
3556
+ onClick: () => toggleTextList(editor, "orderedList"),
3557
+ children: /* @__PURE__ */ jsx33(ToolbarIcon, { name: "list-ordered" })
2877
3558
  }
2878
3559
  ),
2879
- /* @__PURE__ */ jsx28("button", { type: "button", className: "ree-rte-option", disabled: !overridden, onClick: onReset, children: "Default" })
3560
+ state.bulletList || state.orderedList ? /* @__PURE__ */ jsxs23(ToolbarMenu, { title: "List settings", label: "List", open: menu === "list", onToggle: () => onToggle("list"), children: [
3561
+ /* @__PURE__ */ jsx33(
3562
+ MetricSlider,
3563
+ {
3564
+ label: "List spacing",
3565
+ value: readCssLength(spacing).amount,
3566
+ min: 0,
3567
+ max: 48,
3568
+ step: 1,
3569
+ display: spacing,
3570
+ overridden: Boolean(state.listSpacing),
3571
+ onChange: (value) => update("listSpacing", `${value}px`),
3572
+ onReset: () => update("listSpacing", null)
3573
+ }
3574
+ ),
3575
+ /* @__PURE__ */ jsx33(
3576
+ MetricSlider,
3577
+ {
3578
+ label: "List indent",
3579
+ value: readCssLength(indent).amount,
3580
+ min: 0,
3581
+ max: 64,
3582
+ step: 1,
3583
+ display: indent,
3584
+ overridden: Boolean(state.listIndent),
3585
+ onChange: (value) => update("listIndent", `${value}px`),
3586
+ onReset: () => update("listIndent", null)
3587
+ }
3588
+ )
3589
+ ] }) : null
2880
3590
  ] });
2881
3591
  }
2882
3592
 
3593
+ // src/stage/rich-text/components/LinkPopover.tsx
3594
+ import { useEffect as useEffect2, useId as useId3, useRef as useRef7, useState as useState9 } from "react";
3595
+ import { isAllowedUri } from "@tiptap/extension-link";
3596
+
3597
+ // src/components/Button.tsx
3598
+ import { jsx as jsx34 } from "react/jsx-runtime";
3599
+ function Button(_a) {
3600
+ var _b = _a, { type = "button", variant = "default", className = "" } = _b, props = __objRest(_b, ["type", "variant", "className"]);
3601
+ return /* @__PURE__ */ jsx34("button", __spreadProps(__spreadValues({}, props), { type, className: `ree-btn${variant === "default" ? "" : ` ree-btn-${variant}`}${className ? ` ${className}` : ""}` }));
3602
+ }
3603
+
3604
+ // src/stage/rich-text/components/LinkPopover.tsx
3605
+ import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
3606
+ function LinkPopover({
3607
+ url,
3608
+ color,
3609
+ decoration,
3610
+ target,
3611
+ onColorChange,
3612
+ onDecorationChange,
3613
+ onTargetChange,
3614
+ canUnlink,
3615
+ onUrlChange,
3616
+ onApply,
3617
+ onClose
3618
+ }) {
3619
+ const dialogRef = useRef7(null);
3620
+ const titleId = useId3();
3621
+ const errorId = useId3();
3622
+ const [submitted, setSubmitted] = useState9(false);
3623
+ const invalidUrl = Boolean(url.trim()) && !isAllowedUri(url.trim());
3624
+ const save = () => {
3625
+ setSubmitted(true);
3626
+ if (!url.trim() && !canUnlink || invalidUrl) return;
3627
+ onApply();
3628
+ };
3629
+ useEffect2(() => {
3630
+ var _a;
3631
+ const dialog = dialogRef.current;
3632
+ dialog == null ? void 0 : dialog.showModal();
3633
+ (_a = dialog == null ? void 0 : dialog.querySelector("input")) == null ? void 0 : _a.select();
3634
+ return () => dialog == null ? void 0 : dialog.close();
3635
+ }, []);
3636
+ return /* @__PURE__ */ jsxs24(
3637
+ "dialog",
3638
+ {
3639
+ ref: dialogRef,
3640
+ className: "ree-rte-link",
3641
+ "aria-labelledby": titleId,
3642
+ "aria-modal": "true",
3643
+ onCancel: (event) => {
3644
+ event.preventDefault();
3645
+ onClose();
3646
+ },
3647
+ onMouseDown: (event) => event.stopPropagation(),
3648
+ onPointerDown: (event) => event.stopPropagation(),
3649
+ onClick: (event) => {
3650
+ event.stopPropagation();
3651
+ if (event.target !== event.currentTarget) return;
3652
+ const rect = event.currentTarget.getBoundingClientRect();
3653
+ if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) onClose();
3654
+ },
3655
+ children: [
3656
+ /* @__PURE__ */ jsx35("h2", { id: titleId, className: "ree-rte-link-title", children: "Edit link" }),
3657
+ /* @__PURE__ */ jsxs24("div", { className: "ree-rte-link-fields", children: [
3658
+ /* @__PURE__ */ jsxs24("label", { className: "ree-field", children: [
3659
+ /* @__PURE__ */ jsx35("span", { className: "ree-field-label", children: "Link URL" }),
3660
+ /* @__PURE__ */ jsx35(
3661
+ TextInput,
3662
+ {
3663
+ value: url,
3664
+ placeholder: "https://",
3665
+ autoComplete: "off",
3666
+ "aria-invalid": submitted && invalidUrl,
3667
+ "aria-describedby": submitted && invalidUrl ? errorId : void 0,
3668
+ onChange: (event) => onUrlChange(event.target.value),
3669
+ onKeyDown: (event) => {
3670
+ if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
3671
+ event.preventDefault();
3672
+ event.stopPropagation();
3673
+ save();
3674
+ }
3675
+ }
3676
+ )
3677
+ ] }),
3678
+ submitted && invalidUrl ? /* @__PURE__ */ jsx35("p", { id: errorId, className: "ree-rte-link-error", role: "alert", children: "Enter a valid link using an allowed protocol, such as https://." }) : null,
3679
+ /* @__PURE__ */ jsx35(ColorControl, { value: color, label: "Link color", resetTo: DEFAULT_EMAIL_GLOBAL.linkColor, onChange: onColorChange }),
3680
+ /* @__PURE__ */ jsx35(CheckboxControl, { label: "Underline", checked: decoration === "underline", onChange: (checked) => onDecorationChange(checked ? "underline" : "none") }),
3681
+ /* @__PURE__ */ jsxs24("label", { className: "ree-field", children: [
3682
+ /* @__PURE__ */ jsx35("span", { className: "ree-field-label", children: "Target" }),
3683
+ /* @__PURE__ */ jsx35(SelectControl, { label: "Target", value: target, options: LINK_TARGETS, onChange: onTargetChange })
3684
+ ] })
3685
+ ] }),
3686
+ /* @__PURE__ */ jsxs24("div", { className: "ree-rte-link-actions", children: [
3687
+ /* @__PURE__ */ jsx35(Button, { onClick: onClose, children: "Cancel" }),
3688
+ /* @__PURE__ */ jsx35(Button, { variant: "solid", disabled: !url.trim() && !canUnlink, onClick: save, children: "Save" })
3689
+ ] })
3690
+ ]
3691
+ }
3692
+ );
3693
+ }
3694
+
2883
3695
  // src/stage/rich-text/components/ParagraphMetricMenus.tsx
2884
- import { Fragment as Fragment2, jsx as jsx29, jsxs as jsxs21 } from "react/jsx-runtime";
3696
+ import { Fragment as Fragment3, jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
2885
3697
  function clampAmount(value, fallback, min, max) {
2886
3698
  const parsed = readCssLength(value || fallback);
2887
3699
  return Math.min(max, Math.max(min, parsed.amount));
@@ -2893,6 +3705,7 @@ function formatStep(value, step) {
2893
3705
  }
2894
3706
  function ParagraphMetricMenus({
2895
3707
  menu,
3708
+ showSpacing = true,
2896
3709
  spacing,
2897
3710
  lineHeight,
2898
3711
  inheritedSpacing,
@@ -2904,15 +3717,17 @@ function ParagraphMetricMenus({
2904
3717
  const spacingValue = clampAmount(spacing, inheritedSpacing, 0, PARAGRAPH_SPACING_MAX);
2905
3718
  const lineHeightValue = clampAmount(lineHeight, inheritedLineHeight, LINE_HEIGHT_RANGE.min, LINE_HEIGHT_RANGE.max);
2906
3719
  const lineHeightLabel = formatStep(lineHeightValue, LINE_HEIGHT_RANGE.step);
2907
- return /* @__PURE__ */ jsxs21(Fragment2, { children: [
2908
- /* @__PURE__ */ jsx29(
3720
+ return /* @__PURE__ */ jsxs25(Fragment3, { children: [
3721
+ showSpacing ? /* @__PURE__ */ jsx36(
2909
3722
  ToolbarMenu,
2910
3723
  {
2911
- title: "Paragraph spacing",
3724
+ title: "Space before",
3725
+ variant: "icon",
3726
+ caret: false,
2912
3727
  open: menu === "spacing",
2913
3728
  onToggle: () => onToggle("spacing"),
2914
- label: spacing || inheritedSpacing,
2915
- children: /* @__PURE__ */ jsx29(
3729
+ label: /* @__PURE__ */ jsx36(ToolbarIcon, { name: "space-before" }),
3730
+ children: /* @__PURE__ */ jsx36(
2916
3731
  MetricSlider,
2917
3732
  {
2918
3733
  label: "Space before",
@@ -2927,15 +3742,17 @@ function ParagraphMetricMenus({
2927
3742
  }
2928
3743
  )
2929
3744
  }
2930
- ),
2931
- /* @__PURE__ */ jsx29(
3745
+ ) : null,
3746
+ /* @__PURE__ */ jsx36(
2932
3747
  ToolbarMenu,
2933
3748
  {
2934
3749
  title: "Line height",
3750
+ variant: "icon",
3751
+ caret: false,
2935
3752
  open: menu === "lineHeight",
2936
3753
  onToggle: () => onToggle("lineHeight"),
2937
- label: lineHeight || inheritedLineHeight || lineHeightLabel,
2938
- children: /* @__PURE__ */ jsx29(
3754
+ label: /* @__PURE__ */ jsx36(ToolbarIcon, { name: "line-height" }),
3755
+ children: /* @__PURE__ */ jsx36(
2939
3756
  MetricSlider,
2940
3757
  {
2941
3758
  label: "Line height",
@@ -2965,20 +3782,20 @@ function restoreTemplateSpaces(value) {
2965
3782
  }
2966
3783
 
2967
3784
  // src/stage/rich-text/components/VariableMenu.tsx
2968
- import { jsx as jsx30, jsxs as jsxs22 } from "react/jsx-runtime";
3785
+ import { jsx as jsx37, jsxs as jsxs26 } from "react/jsx-runtime";
2969
3786
  function VariableMenu({ variables, onInsert }) {
2970
3787
  if (variables.length === 0) {
2971
- return /* @__PURE__ */ jsx30("div", { className: "ree-personalization-empty", children: "No personalization fields" });
3788
+ return null;
2972
3789
  }
2973
- return /* @__PURE__ */ jsx30("div", { className: "ree-rte-variables", children: variables.map((variable) => /* @__PURE__ */ jsxs22(
3790
+ return /* @__PURE__ */ jsx37("div", { className: "ree-rte-variables", children: variables.map((variable) => /* @__PURE__ */ jsxs26(
2974
3791
  "button",
2975
3792
  {
2976
3793
  type: "button",
2977
3794
  className: "ree-var-item",
2978
3795
  onClick: () => onInsert(variable.name),
2979
3796
  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
3797
+ /* @__PURE__ */ jsx37("span", { className: "ree-var-name", children: variable.name }),
3798
+ variable.description ? /* @__PURE__ */ jsx37("span", { className: "ree-var-label", children: variable.description }) : null
2982
3799
  ]
2983
3800
  },
2984
3801
  variable.name
@@ -2986,8 +3803,10 @@ function VariableMenu({ variables, onInsert }) {
2986
3803
  }
2987
3804
 
2988
3805
  // src/stage/rich-text/components/RichTextToolbar.tsx
2989
- import { jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
3806
+ import { jsx as jsx38, jsxs as jsxs27 } from "react/jsx-runtime";
2990
3807
  function RichTextToolbar({
3808
+ id,
3809
+ profile,
2991
3810
  editor,
2992
3811
  variables,
2993
3812
  anchorRef,
@@ -2997,14 +3816,18 @@ function RichTextToolbar({
2997
3816
  editor,
2998
3817
  selector: ({ editor: current }) => current ? readToolbarState(current) : null
2999
3818
  });
3000
- const rootRef = useAnchoredToolbar(anchorRef, Boolean(state));
3819
+ const rootRef = useAnchoredToolbar(anchorRef, Boolean(state), profile === "button");
3001
3820
  const linkRange = useRef8(null);
3002
- const markRange = useRef8(null);
3003
- const [menu, setMenu] = useState7(null);
3004
- const [linkUrl, setLinkUrl] = useState7("https://");
3005
- useEffect4(() => {
3821
+ const colorRange = useRef8(null);
3822
+ const [markRange, setMarkRange] = useState10(null);
3823
+ const [menu, setMenu] = useState10(null);
3824
+ const [linkUrl, setLinkUrl] = useState10("");
3825
+ const [linkColor, setLinkColor] = useState10("");
3826
+ const [linkDecoration, setLinkDecoration] = useState10("");
3827
+ const [linkTarget, setLinkTarget] = useState10(DEFAULT_LINK_TARGET);
3828
+ useEffect3(() => {
3006
3829
  const root = rootRef.current;
3007
- if (!menu || !root) return;
3830
+ if (!menu || menu === "link" || !root) return;
3008
3831
  const ownerDocument = root.ownerDocument;
3009
3832
  const onPointerDown = (event) => {
3010
3833
  if (!root.contains(event.target)) setMenu(null);
@@ -3018,36 +3841,41 @@ function RichTextToolbar({
3018
3841
  ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
3019
3842
  ownerDocument.removeEventListener("keydown", onKeyDown);
3020
3843
  };
3021
- }, [menu]);
3844
+ }, [menu, rootRef]);
3022
3845
  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
3846
  const bold = isVisuallyBold(state.fontWeight, state.bold, inherited.fontWeight);
3027
3847
  const toggleMenu = (next) => {
3028
3848
  if (menu !== next) {
3029
3849
  const range = currentSelection(editor);
3030
3850
  if (next === "link") {
3031
- setLinkUrl(state.linkHref || "https://");
3851
+ setLinkUrl(state.linkHref);
3852
+ setLinkColor(state.linkColor || (state.linkHref ? inherited.linkColor : "") || DEFAULT_EMAIL_GLOBAL.linkColor);
3853
+ setLinkDecoration(state.linkDecoration || (state.linkHref ? inherited.linkDecoration : "") || "underline");
3854
+ setLinkTarget(state.linkTarget === "_self" ? "_self" : DEFAULT_LINK_TARGET);
3032
3855
  linkRange.current = range;
3033
3856
  }
3034
- if (next === "color" || next === "spacing" || next === "lineHeight") markRange.current = range;
3857
+ setMarkRange(range);
3035
3858
  }
3036
3859
  setMenu((current) => current === next ? null : next);
3037
3860
  };
3038
3861
  const applyLink = () => {
3039
3862
  const href = linkUrl.trim();
3040
- if (!href) return;
3863
+ if (!href) {
3864
+ if (state.linkHref) unlinkSaved();
3865
+ return;
3866
+ }
3867
+ if (!isAllowedUri2(href)) return;
3868
+ const attrs = { href, color: linkColor, decoration: linkDecoration, target: linkTarget };
3041
3869
  const range = linkRange.current;
3042
3870
  const chain = focusSelection(editor, range);
3043
- if (range && range.from === range.to) {
3871
+ if (range && range.anchor === range.head && !state.linkHref) {
3044
3872
  chain.insertContent({
3045
3873
  type: "text",
3046
3874
  text: href,
3047
- marks: [{ type: "link", attrs: { href } }]
3875
+ marks: [{ type: "link", attrs }]
3048
3876
  }).run();
3049
3877
  } else {
3050
- chain.extendMarkRange("link").setLink({ href }).run();
3878
+ chain.extendMarkRange("link").setLink({ href }).updateAttributes("link", attrs).run();
3051
3879
  }
3052
3880
  setMenu(null);
3053
3881
  };
@@ -3063,217 +3891,135 @@ function RichTextToolbar({
3063
3891
  command();
3064
3892
  setMenu(null);
3065
3893
  };
3066
- return /* @__PURE__ */ jsxs23(
3894
+ return /* @__PURE__ */ jsxs27(
3067
3895
  "div",
3068
3896
  {
3897
+ id,
3069
3898
  className: "ree-rte-toolbar",
3070
3899
  ref: rootRef,
3071
3900
  onMouseDown: (event) => {
3072
3901
  const ownerWindow = event.currentTarget.ownerDocument.defaultView;
3073
3902
  const target = event.target;
3074
- if (ownerWindow && target instanceof ownerWindow.Element && target.closest("input, textarea")) return;
3903
+ if (ownerWindow && target instanceof ownerWindow.Element && target.closest("input, textarea, select")) return;
3075
3904
  event.preventDefault();
3076
3905
  },
3077
3906
  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
3907
+ /* @__PURE__ */ jsx38(
3908
+ TextFormatControls,
3909
+ {
3910
+ editor,
3911
+ profile,
3912
+ state,
3913
+ inherited,
3914
+ menu,
3915
+ range: markRange,
3916
+ bold,
3917
+ toggleMenu,
3918
+ run,
3919
+ children: /* @__PURE__ */ jsx38(
3920
+ TextColorControl,
3921
+ {
3922
+ value: state.color,
3923
+ fallback: inherited.color,
3924
+ onOpen: () => {
3925
+ colorRange.current = currentSelection(editor);
3926
+ setMenu(null);
3113
3927
  },
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(
3928
+ onChange: (color) => formatText(editor, (chain) => chain.setColor(color), colorRange.current, "text", false),
3929
+ onReset: () => formatText(editor, (chain) => chain.unsetColor(), colorRange.current, "text", false)
3930
+ }
3931
+ )
3932
+ }
3933
+ ),
3934
+ profile !== "button" ? /* @__PURE__ */ jsxs27("div", { className: "ree-rte-group", role: "group", "aria-label": "Paragraph", children: [
3935
+ TEXT_ALIGNS2.filter((align) => profile === "text" || align.value !== "justify").map((align) => /* @__PURE__ */ jsx38(
3177
3936
  ToolbarButton,
3178
3937
  {
3179
3938
  title: align.title,
3180
3939
  active: state.align === align.value,
3181
- onClick: () => editor.chain().focus().toggleTextAlign(align.value).run(),
3182
- children: /* @__PURE__ */ jsx31(ToolbarIcon, { name: align.icon })
3940
+ onClick: () => formatText(editor, (chain) => chain.toggleTextAlign(align.value), null, "paragraph"),
3941
+ children: /* @__PURE__ */ jsx38(ToolbarIcon, { name: align.icon })
3183
3942
  },
3184
3943
  align.value
3185
3944
  )),
3186
- /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3187
- /* @__PURE__ */ jsx31(
3945
+ /* @__PURE__ */ jsx38(
3188
3946
  ParagraphMetricMenus,
3189
3947
  {
3190
3948
  menu,
3949
+ showSpacing: profile === "text",
3191
3950
  spacing: state.spacing,
3192
3951
  lineHeight: state.lineHeight,
3193
3952
  inheritedSpacing: inherited.paragraphSpacing,
3194
3953
  inheritedLineHeight: inherited.lineHeight,
3195
3954
  onToggle: toggleMenu,
3196
3955
  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();
3956
+ formatText(editor, (chain) => value ? chain.updateAttributes("paragraph", { spacing: value }) : chain.resetAttributes("paragraph", "spacing"), markRange, "paragraph");
3200
3957
  },
3201
3958
  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();
3959
+ formatText(editor, (chain) => value ? chain.updateAttributes("paragraph", { lineHeight: value }) : chain.resetAttributes("paragraph", "lineHeight"), markRange, "paragraph");
3205
3960
  }
3206
3961
  }
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(
3962
+ )
3963
+ ] }) : null,
3964
+ profile === "text" ? /* @__PURE__ */ jsx38(
3965
+ ListControls,
3966
+ {
3967
+ editor,
3968
+ state,
3969
+ menu,
3970
+ range: markRange,
3971
+ inherited,
3972
+ onToggle: toggleMenu
3973
+ }
3974
+ ) : null,
3975
+ profile !== "button" ? /* @__PURE__ */ jsxs27("div", { className: "ree-rte-group", role: "group", "aria-label": "Links", children: [
3976
+ /* @__PURE__ */ jsx38(
3219
3977
  ToolbarButton,
3220
3978
  {
3221
- title: "Numbered list",
3222
- active: state.orderedList,
3223
- onClick: () => editor.chain().focus().toggleOrderedList().run(),
3224
- children: /* @__PURE__ */ jsx31(ToolbarIcon, { name: "list-ordered" })
3979
+ title: "Link",
3980
+ active: Boolean(state.linkHref),
3981
+ onClick: () => toggleMenu("link"),
3982
+ children: /* @__PURE__ */ jsx38(ToolbarIcon, { name: "link" })
3225
3983
  }
3226
3984
  ),
3227
- /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3228
- /* @__PURE__ */ jsx31(
3229
- ToolbarMenu,
3985
+ menu === "link" ? /* @__PURE__ */ jsx38(
3986
+ LinkPopover,
3230
3987
  {
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
- )
3988
+ url: linkUrl,
3989
+ color: linkColor,
3990
+ decoration: linkDecoration,
3991
+ target: linkTarget,
3992
+ onColorChange: setLinkColor,
3993
+ onDecorationChange: setLinkDecoration,
3994
+ onTargetChange: setLinkTarget,
3995
+ canUnlink: Boolean(state.linkHref),
3996
+ onUrlChange: setLinkUrl,
3997
+ onApply: applyLink,
3998
+ onClose: () => setMenu(null)
3247
3999
  }
3248
- ),
3249
- /* @__PURE__ */ jsx31("span", { className: "ree-rte-sep" }),
3250
- /* @__PURE__ */ jsx31(
4000
+ ) : null,
4001
+ /* @__PURE__ */ jsx38(ToolbarButton, { title: "Unlink", disabled: !state.linkHref, onClick: unlinkSelection, children: /* @__PURE__ */ jsx38(ToolbarIcon, { name: "unlink" }) })
4002
+ ] }) : null,
4003
+ /* @__PURE__ */ jsxs27("div", { className: "ree-rte-group", role: "group", "aria-label": "Insert", children: [
4004
+ /* @__PURE__ */ jsx38(
3251
4005
  ToolbarMenu,
3252
4006
  {
3253
- title: "Link",
4007
+ title: "Emoji",
3254
4008
  variant: "icon",
3255
4009
  caret: false,
3256
- active: Boolean(state.linkHref),
3257
4010
  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,
4011
+ open: menu === "emoji",
4012
+ onToggle: () => toggleMenu("emoji"),
4013
+ label: /* @__PURE__ */ jsx38(ToolbarIcon, { name: "emoji" }),
4014
+ children: /* @__PURE__ */ jsx38(
4015
+ EmojiMenu,
3264
4016
  {
3265
- url: linkUrl,
3266
- canUnlink: Boolean(state.linkHref),
3267
- onUrlChange: setLinkUrl,
3268
- onApply: applyLink,
3269
- onUnlink: unlinkSaved
4017
+ onPick: (emoji) => run(() => editor.chain().focus().insertContent(emoji).run())
3270
4018
  }
3271
4019
  )
3272
4020
  }
3273
4021
  ),
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(
4022
+ variables.length > 0 ? /* @__PURE__ */ jsx38(
3277
4023
  ToolbarMenu,
3278
4024
  {
3279
4025
  title: "Personalization",
@@ -3281,7 +4027,7 @@ function RichTextToolbar({
3281
4027
  open: menu === "variable",
3282
4028
  onToggle: () => toggleMenu("variable"),
3283
4029
  label: "Personalization",
3284
- children: /* @__PURE__ */ jsx31(
4030
+ children: /* @__PURE__ */ jsx38(
3285
4031
  VariableMenu,
3286
4032
  {
3287
4033
  variables,
@@ -3289,7 +4035,7 @@ function RichTextToolbar({
3289
4035
  }
3290
4036
  )
3291
4037
  }
3292
- )
4038
+ ) : null
3293
4039
  ] })
3294
4040
  ]
3295
4041
  }
@@ -3331,8 +4077,76 @@ var FontWeight = Extension.create({
3331
4077
  }
3332
4078
  });
3333
4079
 
3334
- // src/stage/rich-text/paragraph-style.ts
4080
+ // src/stage/rich-text/button-document.ts
4081
+ import { Node } from "@tiptap/react";
4082
+ import { Fragment as Fragment4, Slice } from "@tiptap/pm/model";
4083
+ import { Plugin } from "@tiptap/pm/state";
4084
+ var ButtonDocument = Node.create({
4085
+ name: "doc",
4086
+ topNode: true,
4087
+ content: "paragraph",
4088
+ addKeyboardShortcuts() {
4089
+ return { Enter: () => true, "Shift-Enter": () => true };
4090
+ },
4091
+ addProseMirrorPlugins() {
4092
+ return [new Plugin({
4093
+ props: {
4094
+ transformPasted(slice) {
4095
+ const nodes = [];
4096
+ slice.content.descendants((node) => {
4097
+ if (node.isTextblock && nodes.length) nodes.push(node.type.schema.text(" "));
4098
+ if (node.isText) nodes.push(node);
4099
+ });
4100
+ return new Slice(Fragment4.from(nodes), 0, 0);
4101
+ }
4102
+ }
4103
+ })];
4104
+ }
4105
+ });
4106
+
4107
+ // src/stage/rich-text/content-attributes.ts
4108
+ import Link from "@tiptap/extension-link";
3335
4109
  import { Extension as Extension2 } from "@tiptap/react";
4110
+ var StyledLink = Link.extend({
4111
+ addAttributes() {
4112
+ var _a;
4113
+ return __spreadProps(__spreadValues({}, (_a = this.parent) == null ? void 0 : _a.call(this)), {
4114
+ color: {
4115
+ default: null,
4116
+ parseHTML: (element) => cssColor(element.style.color, "") || null,
4117
+ renderHTML: (attrs) => attrs.color ? { style: `color:${cssColor(attrs.color)}` } : {}
4118
+ },
4119
+ decoration: {
4120
+ default: null,
4121
+ parseHTML: (element) => element.style.textDecoration === "none" ? "none" : element.style.textDecoration === "underline" ? "underline" : null,
4122
+ renderHTML: (attrs) => attrs.decoration ? { style: `text-decoration:${attrs.decoration === "none" ? "none" : "underline"}` } : {}
4123
+ }
4124
+ });
4125
+ }
4126
+ }).configure({ openOnClick: false, autolink: true });
4127
+ var ListStyle = Extension2.create({
4128
+ name: "listStyle",
4129
+ addGlobalAttributes() {
4130
+ return [{
4131
+ types: ["bulletList", "orderedList"],
4132
+ attributes: {
4133
+ listSpacing: {
4134
+ default: null,
4135
+ parseHTML: (element) => cssLength(element.style.marginTop, "") || null,
4136
+ renderHTML: (attrs) => attrs.listSpacing ? { style: `margin:${cssLength(attrs.listSpacing)} 0` } : {}
4137
+ },
4138
+ listIndent: {
4139
+ default: null,
4140
+ parseHTML: (element) => cssLength(element.style.paddingLeft, "") || null,
4141
+ renderHTML: (attrs) => attrs.listIndent ? { style: `padding-left:${cssLength(attrs.listIndent)}` } : {}
4142
+ }
4143
+ }
4144
+ }];
4145
+ }
4146
+ });
4147
+
4148
+ // src/stage/rich-text/paragraph-style.ts
4149
+ import { Extension as Extension3 } from "@tiptap/react";
3336
4150
  function lengthAttribute(value) {
3337
4151
  const length = cssLength(value != null ? value : "", "");
3338
4152
  return length || null;
@@ -3341,7 +4155,7 @@ function lineHeightAttribute(value) {
3341
4155
  const lineHeight = cssLineHeight(value != null ? value : "", "");
3342
4156
  return lineHeight || null;
3343
4157
  }
3344
- var ParagraphStyle = Extension2.create({
4158
+ var ParagraphStyle = Extension3.create({
3345
4159
  name: "paragraphStyle",
3346
4160
  addGlobalAttributes() {
3347
4161
  return [
@@ -3379,10 +4193,20 @@ var ParagraphStyle = Extension2.create({
3379
4193
  });
3380
4194
 
3381
4195
  // src/stage/rich-text/extensions.ts
3382
- function createRichTextExtensions(placeholder2) {
4196
+ function createRichTextExtensions(placeholder2, profile = "text") {
4197
+ const button = profile === "button";
4198
+ const text = profile === "text";
3383
4199
  return [
3384
4200
  StarterKit.configure({
3385
- link: { openOnClick: false, autolink: true },
4201
+ document: button ? false : void 0,
4202
+ trailingNode: button ? false : void 0,
4203
+ link: false,
4204
+ bulletList: text ? void 0 : false,
4205
+ orderedList: text ? void 0 : false,
4206
+ listItem: text ? void 0 : false,
4207
+ listKeymap: text ? void 0 : false,
4208
+ strike: text ? void 0 : false,
4209
+ hardBreak: button ? false : void 0,
3386
4210
  blockquote: false,
3387
4211
  code: false,
3388
4212
  codeBlock: false,
@@ -3391,17 +4215,17 @@ function createRichTextExtensions(placeholder2) {
3391
4215
  }),
3392
4216
  TextStyleKit.configure({ backgroundColor: false, lineHeight: false }),
3393
4217
  FontWeight,
3394
- ParagraphStyle,
3395
- TextAlign.configure({
4218
+ ...button ? [ButtonDocument] : [ParagraphStyle, StyledLink, TextAlign.configure({
3396
4219
  types: ["paragraph"],
3397
- alignments: Object.values(TEXT_ALIGNS)
3398
- }),
4220
+ alignments: text ? Object.values(TEXT_ALIGNS) : [TEXT_ALIGNS.left, TEXT_ALIGNS.center, TEXT_ALIGNS.right]
4221
+ })],
4222
+ ...text ? [ListStyle] : [],
3399
4223
  Placeholder.configure({ placeholder: placeholder2 })
3400
4224
  ];
3401
4225
  }
3402
4226
 
3403
4227
  // src/stage/rich-text/RichText.tsx
3404
- import { jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
4228
+ import { jsx as jsx39, jsxs as jsxs28 } from "react/jsx-runtime";
3405
4229
  var DEFAULT_INHERITED = {
3406
4230
  fontFamily: DEFAULT_EMAIL_GLOBAL.fontFamily,
3407
4231
  fontSize: "15px",
@@ -3412,36 +4236,41 @@ var DEFAULT_INHERITED = {
3412
4236
  };
3413
4237
  function RichText({
3414
4238
  initialHtml,
4239
+ profile = "text",
3415
4240
  placeholder: placeholder2,
3416
4241
  variables,
3417
4242
  selected,
4243
+ toolbarId,
4244
+ toolbarCollapsed,
3418
4245
  inherited = DEFAULT_INHERITED,
3419
4246
  onCommit,
3420
4247
  onSelect
3421
4248
  }) {
3422
4249
  const rootRef = useRef9(null);
3423
- const [host, setHost] = useState8(null);
4250
+ const [host, setHost] = useState11(null);
3424
4251
  const editor = useEditor({
3425
- extensions: createRichTextExtensions(placeholder2 != null ? placeholder2 : "Type something\u2026"),
4252
+ extensions: createRichTextExtensions(placeholder2 != null ? placeholder2 : "Type something\u2026", profile),
3426
4253
  content: preserveTemplateSpaces(initialHtml),
3427
4254
  immediatelyRender: false,
3428
4255
  onUpdate: ({ editor: current }) => onCommit(restoreTemplateSpaces(current.getHTML()))
3429
- });
3430
- useLayoutEffect3(() => {
4256
+ }, [profile]);
4257
+ useLayoutEffect6(() => {
3431
4258
  var _a;
3432
4259
  const node = rootRef.current;
3433
4260
  const themeRoot = node == null ? void 0 : node.closest(".ree-root");
3434
4261
  setHost(themeRoot instanceof HTMLElement ? themeRoot : (_a = node == null ? void 0 : node.ownerDocument.body) != null ? _a : null);
3435
4262
  }, []);
3436
- useEffect5(() => {
3437
- if (!editor || restoreTemplateSpaces(editor.getHTML()) === initialHtml) return;
4263
+ useEffect4(() => {
4264
+ if (!editor || editor.isDestroyed || restoreTemplateSpaces(editor.getHTML()) === initialHtml) return;
3438
4265
  editor.commands.setContent(preserveTemplateSpaces(initialHtml), { emitUpdate: false });
3439
4266
  }, [editor, initialHtml]);
3440
- return /* @__PURE__ */ jsxs24("div", { className: "ree-rte", ref: rootRef, onClick: onSelect, children: [
3441
- selected && editor && host ? createPortal(
3442
- /* @__PURE__ */ jsx32(
4267
+ return /* @__PURE__ */ jsxs28("div", { className: `ree-rte ree-rte-${profile}`, ref: rootRef, onClick: onSelect, children: [
4268
+ selected && !toolbarCollapsed && editor && host ? createPortal(
4269
+ /* @__PURE__ */ jsx39(
3443
4270
  RichTextToolbar,
3444
4271
  {
4272
+ id: toolbarId,
4273
+ profile,
3445
4274
  editor,
3446
4275
  variables,
3447
4276
  anchorRef: rootRef,
@@ -3450,12 +4279,44 @@ function RichText({
3450
4279
  ),
3451
4280
  host
3452
4281
  ) : null,
3453
- editor ? /* @__PURE__ */ jsx32(EditorContent, { editor }) : /* @__PURE__ */ jsx32("div", { className: "ree-rte-loading", children: "Loading editor\u2026" })
4282
+ editor ? /* @__PURE__ */ jsx39(EditorContent, { editor }) : /* @__PURE__ */ jsx39("div", { className: "ree-rte-loading", children: "Loading editor\u2026" })
3454
4283
  ] });
3455
4284
  }
3456
4285
 
4286
+ // src/stage/canvas/components/InlineCanvas.tsx
4287
+ import { jsx as jsx40 } from "react/jsx-runtime";
4288
+ function InlineCanvas(_a) {
4289
+ var _b = _a, { block, global } = _b, props = __objRest(_b, ["block", "global"]);
4290
+ var _a2, _b2, _c, _d;
4291
+ const button = props.profile === "button";
4292
+ const styles = buttonStyles(block, global);
4293
+ const typography = button ? styles.text : typographyStyle(block.style, global);
4294
+ const defaults = contentDefaults(block.style, global);
4295
+ const editor = /* @__PURE__ */ jsx40(
4296
+ RichText,
4297
+ __spreadProps(__spreadValues({}, props), {
4298
+ initialHtml: button ? buttonContent(block) : String((_a2 = block.fields.html) != null ? _a2 : ""),
4299
+ inherited: {
4300
+ fontFamily: String(typography.fontFamily),
4301
+ fontSize: String(typography.fontSize),
4302
+ color: String(typography.color),
4303
+ fontWeight: resolvedFontWeight(button ? void 0 : (_c = (_b2 = block.style) == null ? void 0 : _b2.typography) == null ? void 0 : _c.fontWeight, global.fontWeight),
4304
+ lineHeight: String((_d = typography.lineHeight) != null ? _d : ""),
4305
+ paragraphSpacing: defaults.paragraph,
4306
+ listSpacing: defaults.listSpacing,
4307
+ listIndent: defaults.listIndent,
4308
+ linkColor: defaults.linkColor,
4309
+ linkDecoration: defaults.linkDecoration
4310
+ }
4311
+ })
4312
+ );
4313
+ if (!button) return editor;
4314
+ const align = readAlign(styles.element.align);
4315
+ return /* @__PURE__ */ jsx40("table", { role: "presentation", className: "ree-button-canvas-row", cellPadding: 0, cellSpacing: 0, children: /* @__PURE__ */ jsx40("tbody", { children: /* @__PURE__ */ jsx40("tr", { children: /* @__PURE__ */ jsx40("td", { align, children: /* @__PURE__ */ jsx40("table", { role: "presentation", align, cellPadding: 0, cellSpacing: 0, style: { width: styles.width, maxWidth: "100%" }, children: /* @__PURE__ */ jsx40("tbody", { children: /* @__PURE__ */ jsx40("tr", { children: /* @__PURE__ */ jsx40("td", { style: __spreadValues(__spreadValues({}, styles.box), styles.text), children: editor }) }) }) }) }) }) }) });
4316
+ }
4317
+
3457
4318
  // src/stage/canvas/components/HtmlCanvas.tsx
3458
- import { useCallback, useEffect as useEffect6, useRef as useRef10 } from "react";
4319
+ import { useCallback, useEffect as useEffect5, useRef as useRef10 } from "react";
3459
4320
 
3460
4321
  // src/render/head-styles.ts
3461
4322
  function extractHeadStyles(html) {
@@ -3469,7 +4330,7 @@ function extractHeadStyles(html) {
3469
4330
  }
3470
4331
 
3471
4332
  // src/stage/canvas/components/HtmlCanvas.tsx
3472
- import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
4333
+ import { jsx as jsx41, jsxs as jsxs29 } from "react/jsx-runtime";
3473
4334
  function frameHtml(html) {
3474
4335
  const { html: body, styles } = extractHeadStyles(html);
3475
4336
  return `<!DOCTYPE html><html><head><style>html,body{margin:0;padding:0;}body{display:flow-root;}</style>${styles}</head><body>${body}</body></html>`;
@@ -3520,12 +4381,12 @@ function HtmlCanvas({ html, onSelect }) {
3520
4381
  window.cancelAnimationFrame(animationFrame);
3521
4382
  };
3522
4383
  }, []);
3523
- useEffect6(() => () => {
4384
+ useEffect5(() => () => {
3524
4385
  var _a;
3525
4386
  return (_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
3526
4387
  }, [hasHtmlTag, html]);
3527
4388
  if (!hasHtmlTag) {
3528
- return /* @__PURE__ */ jsx33(
4389
+ return /* @__PURE__ */ jsx41(
3529
4390
  "div",
3530
4391
  {
3531
4392
  className: "ree-html-canvas ree-html-canvas-text",
@@ -3534,14 +4395,14 @@ function HtmlCanvas({ html, onSelect }) {
3534
4395
  }
3535
4396
  );
3536
4397
  }
3537
- return /* @__PURE__ */ jsxs25(
4398
+ return /* @__PURE__ */ jsxs29(
3538
4399
  "div",
3539
4400
  {
3540
4401
  ref: containerRef,
3541
4402
  className: "ree-html-canvas",
3542
4403
  onClick: () => onSelect(),
3543
4404
  children: [
3544
- /* @__PURE__ */ jsx33(
4405
+ /* @__PURE__ */ jsx41(
3545
4406
  "button",
3546
4407
  {
3547
4408
  type: "button",
@@ -3552,7 +4413,7 @@ function HtmlCanvas({ html, onSelect }) {
3552
4413
  onClick: () => onSelect()
3553
4414
  }
3554
4415
  ),
3555
- /* @__PURE__ */ jsx33(
4416
+ /* @__PURE__ */ jsx41(
3556
4417
  "iframe",
3557
4418
  {
3558
4419
  ref: frameRef,
@@ -3569,6 +4430,88 @@ function HtmlCanvas({ html, onSelect }) {
3569
4430
  );
3570
4431
  }
3571
4432
 
4433
+ // src/stage/canvas/components/BlockActions.tsx
4434
+ import { useId as useId4 } from "react";
4435
+ import { jsx as jsx42, jsxs as jsxs30 } from "react/jsx-runtime";
4436
+ function BlockActions({
4437
+ inlineEdit,
4438
+ toolbarId,
4439
+ toolbarCollapsed,
4440
+ onToggleToolbar,
4441
+ onDuplicate,
4442
+ onRemove
4443
+ }) {
4444
+ const tooltipId = useId4();
4445
+ const toggleLabel = toolbarCollapsed ? "Expand Toolbar" : "Collapse Toolbar";
4446
+ return /* @__PURE__ */ jsxs30(
4447
+ "div",
4448
+ {
4449
+ className: "ree-block-toolbar",
4450
+ role: "group",
4451
+ "aria-label": "Block actions",
4452
+ onClick: (event) => event.stopPropagation(),
4453
+ onPointerDown: (event) => event.stopPropagation(),
4454
+ onMouseDown: (event) => {
4455
+ event.stopPropagation();
4456
+ event.preventDefault();
4457
+ },
4458
+ children: [
4459
+ /* @__PURE__ */ jsxs30(
4460
+ "button",
4461
+ {
4462
+ type: "button",
4463
+ className: "ree-block-btn",
4464
+ "aria-label": "Duplicate",
4465
+ "aria-describedby": `${tooltipId}-duplicate`,
4466
+ onClick: onDuplicate,
4467
+ children: [
4468
+ /* @__PURE__ */ jsxs30("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
4469
+ /* @__PURE__ */ jsx42("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
4470
+ /* @__PURE__ */ jsx42("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
4471
+ ] }),
4472
+ /* @__PURE__ */ jsx42("span", { id: `${tooltipId}-duplicate`, className: "ree-block-tooltip", role: "tooltip", children: "Duplicate" })
4473
+ ]
4474
+ }
4475
+ ),
4476
+ /* @__PURE__ */ jsxs30(
4477
+ "button",
4478
+ {
4479
+ type: "button",
4480
+ className: "ree-block-btn ree-danger",
4481
+ "aria-label": "Delete",
4482
+ "aria-describedby": `${tooltipId}-delete`,
4483
+ onClick: onRemove,
4484
+ children: [
4485
+ /* @__PURE__ */ jsxs30("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
4486
+ /* @__PURE__ */ jsx42("path", { d: "M3 6h18" }),
4487
+ /* @__PURE__ */ jsx42("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
4488
+ /* @__PURE__ */ jsx42("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
4489
+ ] }),
4490
+ /* @__PURE__ */ jsx42("span", { id: `${tooltipId}-delete`, className: "ree-block-tooltip", role: "tooltip", children: "Delete" })
4491
+ ]
4492
+ }
4493
+ ),
4494
+ inlineEdit ? /* @__PURE__ */ jsxs30(
4495
+ "button",
4496
+ {
4497
+ type: "button",
4498
+ className: "ree-block-btn",
4499
+ "aria-label": toggleLabel,
4500
+ "aria-describedby": `${tooltipId}-toolbar`,
4501
+ "aria-controls": toolbarId,
4502
+ "aria-expanded": !toolbarCollapsed,
4503
+ onClick: onToggleToolbar,
4504
+ children: [
4505
+ /* @__PURE__ */ jsx42("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx42("path", { d: toolbarCollapsed ? "M8 3H3v5M3 3l7 7M16 21h5v-5m0 5-7-7" : "M3 8h5V3M8 8 2 2m19 14h-5v5m0-5 6 6" }) }),
4506
+ /* @__PURE__ */ jsx42("span", { id: `${tooltipId}-toolbar`, className: "ree-block-tooltip", role: "tooltip", children: toggleLabel })
4507
+ ]
4508
+ }
4509
+ ) : null
4510
+ ]
4511
+ }
4512
+ );
4513
+ }
4514
+
3572
4515
  // src/stage/canvas/block-style.ts
3573
4516
  function canvasBlockStyle(block, global) {
3574
4517
  var _a, _b;
@@ -3577,9 +4520,9 @@ function canvasBlockStyle(block, global) {
3577
4520
  }
3578
4521
 
3579
4522
  // src/stage/canvas/CanvasBlock.tsx
3580
- import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
4523
+ import { jsx as jsx43, jsxs as jsxs31 } from "react/jsx-runtime";
3581
4524
  function CanvasBlock({
3582
- block,
4525
+ block: sourceBlock,
3583
4526
  global,
3584
4527
  definition,
3585
4528
  selected,
@@ -3587,14 +4530,15 @@ function CanvasBlock({
3587
4530
  variables,
3588
4531
  onSelect,
3589
4532
  onCommitField,
3590
- onMove,
3591
4533
  onDuplicate,
3592
4534
  onRemove
3593
4535
  }) {
3594
- var _a, _b, _c, _d, _e, _f, _g, _h;
4536
+ var _a, _b;
4537
+ const block = __spreadProps(__spreadValues({}, sourceBlock), { style: resolveBlockStyle(sourceBlock.style, definition) });
4538
+ const toolbarId = useId5();
4539
+ const [toolbarCollapsed, setToolbarCollapsed] = useState12(false);
3595
4540
  const dragData = { kind: "canvas" };
3596
4541
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: block.id, data: dragData });
3597
- const stop = (event) => event.stopPropagation();
3598
4542
  const select = (event) => {
3599
4543
  event == null ? void 0 : event.stopPropagation();
3600
4544
  onSelect();
@@ -3605,7 +4549,7 @@ function CanvasBlock({
3605
4549
  global
3606
4550
  }) : "";
3607
4551
  const usesTemplatePreview = block.type === "html";
3608
- return /* @__PURE__ */ jsxs26(
4552
+ return /* @__PURE__ */ jsxs31(
3609
4553
  "div",
3610
4554
  {
3611
4555
  ref: setNodeRef,
@@ -3623,7 +4567,8 @@ function CanvasBlock({
3623
4567
  ].filter(Boolean).join(" "),
3624
4568
  onClick: select,
3625
4569
  children: [
3626
- /* @__PURE__ */ jsx34(
4570
+ /* @__PURE__ */ jsx43("span", { className: "ree-block-highlight", "aria-hidden": "true" }),
4571
+ /* @__PURE__ */ jsx43(
3627
4572
  "button",
3628
4573
  __spreadProps(__spreadValues(__spreadValues({
3629
4574
  type: "button",
@@ -3631,124 +4576,70 @@ function CanvasBlock({
3631
4576
  }, attributes), listeners), {
3632
4577
  onClick: select,
3633
4578
  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" })
4579
+ children: /* @__PURE__ */ jsxs31("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "currentColor", children: [
4580
+ /* @__PURE__ */ jsx43("circle", { cx: "9", cy: "5", r: "1.6" }),
4581
+ /* @__PURE__ */ jsx43("circle", { cx: "15", cy: "5", r: "1.6" }),
4582
+ /* @__PURE__ */ jsx43("circle", { cx: "9", cy: "12", r: "1.6" }),
4583
+ /* @__PURE__ */ jsx43("circle", { cx: "15", cy: "12", r: "1.6" }),
4584
+ /* @__PURE__ */ jsx43("circle", { cx: "9", cy: "19", r: "1.6" }),
4585
+ /* @__PURE__ */ jsx43("circle", { cx: "15", cy: "19", r: "1.6" })
3641
4586
  ] })
3642
4587
  })
3643
4588
  ),
3644
- selected ? /* @__PURE__ */ jsxs26(
3645
- "div",
4589
+ selected ? /* @__PURE__ */ jsx43(
4590
+ BlockActions,
3646
4591
  {
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
- ]
4592
+ inlineEdit: Boolean(definition == null ? void 0 : definition.inlineEdit),
4593
+ toolbarId,
4594
+ toolbarCollapsed,
4595
+ onToggleToolbar: () => setToolbarCollapsed((collapsed) => !collapsed),
4596
+ onDuplicate,
4597
+ onRemove
3700
4598
  }
3701
4599
  ) : null,
3702
- /* @__PURE__ */ jsx34(
4600
+ /* @__PURE__ */ jsx43(
3703
4601
  "div",
3704
4602
  {
3705
4603
  className: `ree-block-content${(definition == null ? void 0 : definition.inlineEdit) ? " is-inline" : ""}`,
3706
4604
  style: canvasBlockStyle(block, global),
3707
- children: (definition == null ? void 0 : definition.inlineEdit) ? /* @__PURE__ */ jsx34(
3708
- RichText,
4605
+ children: (definition == null ? void 0 : definition.inlineEdit) ? /* @__PURE__ */ jsx43(
4606
+ InlineCanvas,
3709
4607
  {
3710
- initialHtml: String((_c = block.fields.html) != null ? _c : ""),
4608
+ block,
4609
+ global,
4610
+ profile: definition.inlineEditProfile,
3711
4611
  variables,
3712
4612
  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
- },
4613
+ toolbarId,
4614
+ toolbarCollapsed,
3724
4615
  onSelect,
3725
4616
  onCommit: (html) => onCommitField(block.id, "html", html)
3726
4617
  }
3727
- ) : usesTemplatePreview ? /* @__PURE__ */ jsx34(
4618
+ ) : usesTemplatePreview ? /* @__PURE__ */ jsx43(
3728
4619
  HtmlCanvas,
3729
4620
  {
3730
4621
  html: renderedHtml,
3731
4622
  onSelect: select
3732
4623
  }
3733
- ) : /* @__PURE__ */ jsx34(
4624
+ ) : /* @__PURE__ */ jsx43(
3734
4625
  "div",
3735
4626
  {
3736
4627
  className: "ree-block-static",
3737
4628
  dangerouslySetInnerHTML: {
3738
- __html: definition ? definition.render(buildRenderContext(block, "canvas", global)) : `<!-- ${block.type} -->`
4629
+ __html: definition ? definition.render(buildRenderContext(block, "canvas", global, definition)) : `<!-- ${block.type} -->`
3739
4630
  }
3740
4631
  }
3741
4632
  )
3742
4633
  }
3743
4634
  ),
3744
- definition ? /* @__PURE__ */ jsx34("span", { className: "ree-block-badge", children: definition.title }) : null
4635
+ definition ? /* @__PURE__ */ jsx43("span", { className: "ree-block-badge", children: definition.title }) : null
3745
4636
  ]
3746
4637
  }
3747
4638
  );
3748
4639
  }
3749
4640
 
3750
4641
  // src/stage/canvas/Canvas.tsx
3751
- import { jsx as jsx35 } from "react/jsx-runtime";
4642
+ import { jsx as jsx44 } from "react/jsx-runtime";
3752
4643
  function Canvas({
3753
4644
  blocks,
3754
4645
  global,
@@ -3760,7 +4651,6 @@ function Canvas({
3760
4651
  device,
3761
4652
  onSelect,
3762
4653
  onCommitField,
3763
- onMove,
3764
4654
  onDuplicate,
3765
4655
  onRemove
3766
4656
  }) {
@@ -3768,7 +4658,7 @@ function Canvas({
3768
4658
  id: CANVAS_DROP_ID,
3769
4659
  data: { kind: "canvas" }
3770
4660
  });
3771
- return /* @__PURE__ */ jsx35(
4661
+ return /* @__PURE__ */ jsx44(
3772
4662
  "div",
3773
4663
  {
3774
4664
  ref: setNodeRef,
@@ -3785,7 +4675,7 @@ function Canvas({
3785
4675
  active.blur();
3786
4676
  }
3787
4677
  },
3788
- children: /* @__PURE__ */ jsx35(
4678
+ children: /* @__PURE__ */ jsx44(
3789
4679
  "div",
3790
4680
  {
3791
4681
  className: "ree-email-viewport",
@@ -3793,52 +4683,57 @@ function Canvas({
3793
4683
  width: viewportWidth(device, global),
3794
4684
  fontSize: global.defaultFontSize,
3795
4685
  fontFamily: global.fontFamily,
3796
- backgroundColor: global.backgroundColor,
3797
4686
  marginLeft: global.contentAlign === "left" ? 0 : "auto",
3798
4687
  marginRight: global.contentAlign === "right" ? 0 : "auto"
3799
4688
  },
3800
- children: /* @__PURE__ */ jsx35("div", { className: "ree-email-outer", style: { padding: global.outerPadding }, children: /* @__PURE__ */ jsx35(
4689
+ children: /* @__PURE__ */ jsx44(
3801
4690
  "div",
3802
4691
  {
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(
4692
+ className: "ree-email-outer",
4693
+ style: { padding: global.outerPadding, backgroundColor: global.backgroundColor },
4694
+ children: /* @__PURE__ */ jsx44(
3810
4695
  "div",
3811
4696
  {
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,
4697
+ className: "ree-email-surface",
4698
+ style: __spreadProps(__spreadValues({}, surfaceStyle(global)), {
4699
+ maxWidth: global.contentWidth,
4700
+ marginLeft: global.contentAlign === "left" ? 0 : "auto",
4701
+ marginRight: global.contentAlign === "right" ? 0 : "auto"
4702
+ }),
4703
+ children: /* @__PURE__ */ jsx44(
4704
+ "div",
3816
4705
  {
3817
- items: blocks.map((block) => block.id),
3818
- strategy: verticalListSortingStrategy,
3819
- children: blocks.map((block) => /* @__PURE__ */ jsx35(
3820
- CanvasBlock,
4706
+ className: "ree-canvas-inner",
4707
+ style: { "--ree-block-gap": global.blockGap },
4708
+ children: blocks.length === 0 ? /* @__PURE__ */ jsx44("div", { className: "ree-canvas-empty", children: "Drag a block here or click one to get started" }) : /* @__PURE__ */ jsx44(
4709
+ SortableContext,
3821
4710
  {
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
- ))
4711
+ items: blocks.map((block) => block.id),
4712
+ strategy: verticalListSortingStrategy,
4713
+ children: blocks.map((block) => /* @__PURE__ */ jsx44(
4714
+ CanvasBlock,
4715
+ {
4716
+ block,
4717
+ global,
4718
+ definition: getBlockDefinition(block.type, definitions),
4719
+ selected: block.id === selectedId,
4720
+ dropEdge: dropTargetId === block.id ? dropEdge : null,
4721
+ variables,
4722
+ onSelect: () => onSelect(block.id),
4723
+ onCommitField,
4724
+ onDuplicate: () => onDuplicate(block.id),
4725
+ onRemove: () => onRemove(block.id)
4726
+ },
4727
+ block.id
4728
+ ))
4729
+ }
4730
+ )
3836
4731
  }
3837
4732
  )
3838
4733
  }
3839
4734
  )
3840
4735
  }
3841
- ) })
4736
+ )
3842
4737
  }
3843
4738
  )
3844
4739
  }
@@ -3846,7 +4741,7 @@ function Canvas({
3846
4741
  }
3847
4742
 
3848
4743
  // src/stage/preview/Preview.tsx
3849
- import { useMemo as useMemo2 } from "react";
4744
+ import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef11 } from "react";
3850
4745
 
3851
4746
  // src/render/document-html.ts
3852
4747
  function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
@@ -3854,14 +4749,14 @@ function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
3854
4749
  const global = source.global;
3855
4750
  const baseCss = emailBaseCss(global);
3856
4751
  const rendered = source.blocks.map((node, index) => {
3857
- var _a, _b;
3858
- const blockData = __spreadValues(__spreadValues({}, (_a = options.templateData) != null ? _a : {}), node.defaultData);
4752
+ var _a;
4753
+ const blockData = __spreadValues(__spreadValues({}, options.templateData), node.defaultData);
3859
4754
  const block = blockToHtml(node, definitions, "email", {
3860
4755
  resolveHtmlTemplate: options.resolveHtmlTemplates,
3861
4756
  global,
3862
4757
  variables: options.variables,
3863
4758
  templateData: mergeTemplateData(
3864
- (_b = options.variables) != null ? _b : [],
4759
+ (_a = options.variables) != null ? _a : [],
3865
4760
  Object.keys(blockData).length > 0 ? blockData : void 0
3866
4761
  )
3867
4762
  });
@@ -3898,8 +4793,10 @@ function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
3898
4793
  }
3899
4794
 
3900
4795
  // src/stage/preview/Preview.tsx
3901
- import { jsx as jsx36 } from "react/jsx-runtime";
4796
+ import { jsx as jsx45 } from "react/jsx-runtime";
3902
4797
  function Preview({ document, definitions, variables, device }) {
4798
+ const frameRef = useRef11(null);
4799
+ const cleanupRef = useRef11();
3903
4800
  const html = useMemo2(
3904
4801
  () => documentToHtml(document, definitions, {
3905
4802
  resolveHtmlTemplates: true,
@@ -3907,34 +4804,73 @@ function Preview({ document, definitions, variables, device }) {
3907
4804
  }),
3908
4805
  [document, definitions, variables]
3909
4806
  );
3910
- return /* @__PURE__ */ jsx36("div", { className: "ree-preview-stage", children: /* @__PURE__ */ jsx36(
3911
- "iframe",
4807
+ const observeContent = useCallback2(() => {
4808
+ var _a;
4809
+ (_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
4810
+ const frame = frameRef.current;
4811
+ const frameDocument = frame == null ? void 0 : frame.contentDocument;
4812
+ const frameWindow = frameDocument == null ? void 0 : frameDocument.defaultView;
4813
+ if (!frame || !(frameDocument == null ? void 0 : frameDocument.body) || !frameWindow) return;
4814
+ let animationFrame = 0;
4815
+ const fit = () => {
4816
+ frame.style.height = `${frameDocument.body.getBoundingClientRect().height}px`;
4817
+ };
4818
+ const scheduleFit = () => {
4819
+ frameWindow.cancelAnimationFrame(animationFrame);
4820
+ animationFrame = frameWindow.requestAnimationFrame(fit);
4821
+ };
4822
+ const observer = new frameWindow.ResizeObserver(scheduleFit);
4823
+ observer.observe(frameDocument.body);
4824
+ frameDocument.addEventListener("load", scheduleFit, true);
4825
+ fit();
4826
+ cleanupRef.current = () => {
4827
+ observer.disconnect();
4828
+ frameDocument.removeEventListener("load", scheduleFit, true);
4829
+ frameWindow.cancelAnimationFrame(animationFrame);
4830
+ };
4831
+ }, []);
4832
+ useEffect6(() => () => {
4833
+ var _a;
4834
+ return (_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
4835
+ }, [html]);
4836
+ return /* @__PURE__ */ jsx45("div", { className: "ree-preview-stage", children: /* @__PURE__ */ jsx45(
4837
+ "div",
3912
4838
  {
3913
- className: "ree-preview-frame",
3914
- title: "Email preview",
3915
- srcDoc: html,
4839
+ className: "ree-email-viewport",
3916
4840
  style: {
3917
4841
  width: viewportWidth(device, document.global),
3918
- fontSize: document.global.defaultFontSize,
3919
4842
  marginLeft: document.global.contentAlign === "left" ? 0 : "auto",
3920
- marginRight: document.global.contentAlign === "right" ? 0 : "auto",
3921
- backgroundColor: document.global.backgroundColor || "#ffffff"
3922
- }
4843
+ marginRight: document.global.contentAlign === "right" ? 0 : "auto"
4844
+ },
4845
+ children: /* @__PURE__ */ jsx45(
4846
+ "iframe",
4847
+ {
4848
+ ref: frameRef,
4849
+ className: "ree-preview-frame",
4850
+ title: "Email preview",
4851
+ srcDoc: html,
4852
+ onLoad: observeContent,
4853
+ style: {
4854
+ fontSize: document.global.defaultFontSize,
4855
+ backgroundColor: document.global.backgroundColor || "#ffffff"
4856
+ }
4857
+ }
4858
+ )
3923
4859
  }
3924
4860
  ) });
3925
4861
  }
3926
4862
 
3927
4863
  // src/stage/StageHeader.tsx
3928
- import { jsx as jsx37, jsxs as jsxs27 } from "react/jsx-runtime";
4864
+ import { jsx as jsx46, jsxs as jsxs32 } from "react/jsx-runtime";
3929
4865
  function StageHeader({
3930
4866
  viewMode,
3931
4867
  device,
3932
4868
  onViewModeChange,
3933
4869
  onDeviceChange
3934
4870
  }) {
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(
4871
+ return /* @__PURE__ */ jsxs32("div", { className: "ree-col-head ree-stage-head", children: [
4872
+ /* @__PURE__ */ jsxs32("div", { className: "ree-segment", role: "group", "aria-label": "Editor mode", children: [
4873
+ /* @__PURE__ */ jsx46(
3938
4874
  "button",
3939
4875
  {
3940
4876
  type: "button",
@@ -3943,14 +4879,14 @@ function StageHeader({
3943
4879
  "aria-label": "Edit",
3944
4880
  title: "Edit",
3945
4881
  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" })
4882
+ children: /* @__PURE__ */ jsxs32("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: [
4883
+ /* @__PURE__ */ jsx46("path", { d: "M12 20h9" }),
4884
+ /* @__PURE__ */ jsx46("path", { d: "M16.5 3.5a2.1 2.1 0 0 1 3 3L8 18l-4 1 1-4 11.5-11.5Z" })
3949
4885
  ] })
3950
4886
  }
3951
4887
  ),
3952
- /* @__PURE__ */ jsx37("span", { className: "ree-segment-divider", "aria-hidden": "true" }),
3953
- /* @__PURE__ */ jsx37(
4888
+ /* @__PURE__ */ jsx46("span", { className: "ree-segment-divider", "aria-hidden": "true" }),
4889
+ /* @__PURE__ */ jsx46(
3954
4890
  "button",
3955
4891
  {
3956
4892
  type: "button",
@@ -3959,15 +4895,15 @@ function StageHeader({
3959
4895
  "aria-label": "Preview",
3960
4896
  title: "Preview",
3961
4897
  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" })
4898
+ children: /* @__PURE__ */ jsxs32("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: [
4899
+ /* @__PURE__ */ jsx46("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" }),
4900
+ /* @__PURE__ */ jsx46("circle", { cx: "12", cy: "12", r: "2.5" })
3965
4901
  ] })
3966
4902
  }
3967
4903
  )
3968
4904
  ] }),
3969
- /* @__PURE__ */ jsxs27("div", { className: "ree-segment", role: "group", "aria-label": "Canvas device", children: [
3970
- /* @__PURE__ */ jsx37(
4905
+ /* @__PURE__ */ jsxs32("div", { className: "ree-segment", role: "group", "aria-label": "Canvas device", children: [
4906
+ /* @__PURE__ */ jsx46(
3971
4907
  "button",
3972
4908
  {
3973
4909
  type: "button",
@@ -3976,15 +4912,15 @@ function StageHeader({
3976
4912
  "aria-label": "Desktop",
3977
4913
  title: "Desktop",
3978
4914
  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" })
4915
+ children: /* @__PURE__ */ jsxs32("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: [
4916
+ /* @__PURE__ */ jsx46("rect", { x: "3", y: "4", width: "18", height: "12", rx: "1.5" }),
4917
+ /* @__PURE__ */ jsx46("path", { d: "M8 20h8" }),
4918
+ /* @__PURE__ */ jsx46("path", { d: "M12 16v4" })
3983
4919
  ] })
3984
4920
  }
3985
4921
  ),
3986
- /* @__PURE__ */ jsx37("span", { className: "ree-segment-divider", "aria-hidden": "true" }),
3987
- /* @__PURE__ */ jsx37(
4922
+ /* @__PURE__ */ jsx46("span", { className: "ree-segment-divider", "aria-hidden": "true" }),
4923
+ /* @__PURE__ */ jsx46(
3988
4924
  "button",
3989
4925
  {
3990
4926
  type: "button",
@@ -3993,9 +4929,9 @@ function StageHeader({
3993
4929
  "aria-label": "Mobile",
3994
4930
  title: "Mobile",
3995
4931
  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" })
4932
+ children: /* @__PURE__ */ jsxs32("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: [
4933
+ /* @__PURE__ */ jsx46("rect", { x: "7", y: "2.5", width: "10", height: "19", rx: "2" }),
4934
+ /* @__PURE__ */ jsx46("path", { d: "M11 18.5h2" })
3999
4935
  ] })
4000
4936
  }
4001
4937
  )
@@ -4004,7 +4940,7 @@ function StageHeader({
4004
4940
  }
4005
4941
 
4006
4942
  // src/stage/Stage.tsx
4007
- import { jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
4943
+ import { jsx as jsx47, jsxs as jsxs33 } from "react/jsx-runtime";
4008
4944
  function Stage({
4009
4945
  session,
4010
4946
  viewMode,
@@ -4014,8 +4950,8 @@ function Stage({
4014
4950
  onSelectBlock
4015
4951
  }) {
4016
4952
  const { targetId, dropEdge } = useEditorDragState();
4017
- return /* @__PURE__ */ jsxs28("div", { className: "ree-col ree-col-center", children: [
4018
- /* @__PURE__ */ jsx38(
4953
+ return /* @__PURE__ */ jsxs33("div", { className: "ree-col ree-col-center", children: [
4954
+ /* @__PURE__ */ jsx47(
4019
4955
  StageHeader,
4020
4956
  {
4021
4957
  viewMode,
@@ -4024,7 +4960,7 @@ function Stage({
4024
4960
  onDeviceChange
4025
4961
  }
4026
4962
  ),
4027
- viewMode === "edit" ? /* @__PURE__ */ jsx38("div", { className: "ree-stage-view", children: /* @__PURE__ */ jsx38(
4963
+ viewMode === "edit" ? /* @__PURE__ */ jsx47("div", { className: "ree-stage-view", children: /* @__PURE__ */ jsx47(
4028
4964
  Canvas,
4029
4965
  {
4030
4966
  blocks: session.document.blocks,
@@ -4037,11 +4973,10 @@ function Stage({
4037
4973
  device,
4038
4974
  onSelect: onSelectBlock,
4039
4975
  onCommitField: session.updateField,
4040
- onMove: session.moveBlock,
4041
4976
  onDuplicate: session.duplicateBlock,
4042
4977
  onRemove: session.removeBlock
4043
4978
  }
4044
- ) }) : /* @__PURE__ */ jsx38("div", { className: "ree-stage-view", children: /* @__PURE__ */ jsx38(
4979
+ ) }) : /* @__PURE__ */ jsx47("div", { className: "ree-stage-view", children: /* @__PURE__ */ jsx47(
4045
4980
  Preview,
4046
4981
  {
4047
4982
  document: session.document,
@@ -4059,17 +4994,17 @@ var EMPTY_VARIABLES = [];
4059
4994
  var PERSONALIZATION_HINT = "Personalization fields will appear blank if no data is available.";
4060
4995
 
4061
4996
  // src/shell/SubjectBar.tsx
4062
- import { useRef as useRef12 } from "react";
4997
+ import { useRef as useRef13 } from "react";
4063
4998
 
4064
4999
  // 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";
5000
+ import { useEffect as useEffect7, useRef as useRef12, useState as useState13 } from "react";
5001
+ import { jsx as jsx48, jsxs as jsxs34 } from "react/jsx-runtime";
4067
5002
  function PersonalizationMenu({
4068
5003
  variables,
4069
5004
  onInsert
4070
5005
  }) {
4071
- const [open, setOpen] = useState9(false);
4072
- const rootRef = useRef11(null);
5006
+ const [open, setOpen] = useState13(false);
5007
+ const rootRef = useRef12(null);
4073
5008
  useEffect7(() => {
4074
5009
  const root = rootRef.current;
4075
5010
  if (!open || !root) return;
@@ -4080,8 +5015,9 @@ function PersonalizationMenu({
4080
5015
  ownerDocument.addEventListener("pointerdown", onPointerDown, true);
4081
5016
  return () => ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
4082
5017
  }, [open]);
4083
- return /* @__PURE__ */ jsxs29("div", { className: "ree-personalization", ref: rootRef, children: [
4084
- /* @__PURE__ */ jsxs29(
5018
+ if (variables.length === 0) return null;
5019
+ return /* @__PURE__ */ jsxs34("div", { className: "ree-personalization", ref: rootRef, children: [
5020
+ /* @__PURE__ */ jsxs34(
4085
5021
  "button",
4086
5022
  {
4087
5023
  type: "button",
@@ -4090,11 +5026,11 @@ function PersonalizationMenu({
4090
5026
  onClick: () => setOpen((current) => !current),
4091
5027
  children: [
4092
5028
  "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" }) })
5029
+ /* @__PURE__ */ jsx48("svg", { viewBox: "0 0 20 20", width: "16", height: "16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx48("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
5030
  ]
4095
5031
  }
4096
5032
  ),
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(
5033
+ open ? /* @__PURE__ */ jsx48("div", { className: "ree-var-menu ree-personalization-menu", children: variables.map((variable) => /* @__PURE__ */ jsxs34(
4098
5034
  "button",
4099
5035
  {
4100
5036
  type: "button",
@@ -4104,8 +5040,8 @@ function PersonalizationMenu({
4104
5040
  setOpen(false);
4105
5041
  },
4106
5042
  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
5043
+ /* @__PURE__ */ jsx48("span", { className: "ree-var-name", children: variable.name }),
5044
+ variable.description ? /* @__PURE__ */ jsx48("span", { className: "ree-var-label", children: variable.description }) : null
4109
5045
  ]
4110
5046
  },
4111
5047
  variable.name
@@ -4114,14 +5050,13 @@ function PersonalizationMenu({
4114
5050
  }
4115
5051
 
4116
5052
  // src/shell/SubjectBar.tsx
4117
- import { Fragment as Fragment3, jsx as jsx40, jsxs as jsxs30 } from "react/jsx-runtime";
5053
+ import { Fragment as Fragment5, jsx as jsx49, jsxs as jsxs35 } from "react/jsx-runtime";
4118
5054
  function SubjectBar({
4119
5055
  subject,
4120
5056
  variables,
4121
- onSubjectChange,
4122
- onReset
5057
+ onSubjectChange
4123
5058
  }) {
4124
- const inputRef = useRef12(null);
5059
+ const inputRef = useRef13(null);
4125
5060
  const insertVariable = (token) => {
4126
5061
  var _a, _b;
4127
5062
  const input = inputRef.current;
@@ -4139,10 +5074,10 @@ function SubjectBar({
4139
5074
  (_b2 = inputRef.current) == null ? void 0 : _b2.setSelectionRange(cursor, cursor);
4140
5075
  });
4141
5076
  };
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",
5077
+ return /* @__PURE__ */ jsxs35("div", { className: "ree-subject-bar", children: [
5078
+ /* @__PURE__ */ jsx49("label", { className: "ree-subject-label", htmlFor: "ree-email-subject", children: "Email subject" }),
5079
+ /* @__PURE__ */ jsx49(
5080
+ TextInput,
4146
5081
  {
4147
5082
  id: "ree-email-subject",
4148
5083
  ref: inputRef,
@@ -4152,21 +5087,20 @@ function SubjectBar({
4152
5087
  onChange: (event) => onSubjectChange(event.target.value)
4153
5088
  }
4154
5089
  ),
4155
- /* @__PURE__ */ jsxs30("span", { className: "ree-subject-count", children: [
5090
+ /* @__PURE__ */ jsxs35("span", { className: "ree-subject-count", children: [
4156
5091
  subject.length,
4157
5092
  "/",
4158
5093
  SUBJECT_MAX_LENGTH
4159
5094
  ] }),
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" })
5095
+ variables.length > 0 ? /* @__PURE__ */ jsxs35(Fragment5, { children: [
5096
+ /* @__PURE__ */ jsx49(PersonalizationMenu, { variables, onInsert: insertVariable }),
5097
+ /* @__PURE__ */ jsx49("p", { className: "ree-subject-hint", children: PERSONALIZATION_HINT })
5098
+ ] }) : null
4165
5099
  ] });
4166
5100
  }
4167
5101
 
4168
5102
  // src/shell/useEditorSession.ts
4169
- import { useCallback as useCallback2, useEffect as useEffect8, useMemo as useMemo3, useRef as useRef13, useState as useState10 } from "react";
5103
+ import { useCallback as useCallback3, useEffect as useEffect8, useLayoutEffect as useLayoutEffect7, useMemo as useMemo3, useRef as useRef14, useState as useState14 } from "react";
4170
5104
 
4171
5105
  // src/utils/id.ts
4172
5106
  function generateId(prefix = "block") {
@@ -4178,11 +5112,11 @@ function generateId(prefix = "block") {
4178
5112
 
4179
5113
  // src/document/create-block.ts
4180
5114
  function createBlockNode(type, definitions) {
4181
- var _a, _b;
5115
+ var _a;
4182
5116
  const definition = getBlockDefinition(type, definitions);
4183
5117
  if (!definition) return null;
4184
- const fields = __spreadValues({}, (_a = definition.defaultFields) != null ? _a : {});
4185
- for (const field of (_b = definition.fields) != null ? _b : []) {
5118
+ const fields = __spreadValues({}, definition.defaultFields);
5119
+ for (const field of (_a = definition.fields) != null ? _a : []) {
4186
5120
  if (!(field.name in fields) && field.defaultValue !== void 0) {
4187
5121
  fields[field.name] = field.defaultValue;
4188
5122
  }
@@ -4194,7 +5128,7 @@ function createBlockNode(type, definitions) {
4194
5128
  id: generateId(type),
4195
5129
  type,
4196
5130
  fields,
4197
- style: definition.defaultStyle ? structuredClone(definition.defaultStyle) : void 0,
5131
+ style: definition.defaultStyle ? resolveBlockStyle(definition.defaultStyle, definition) : void 0,
4198
5132
  defaultData: definition.defaultData ? structuredClone(definition.defaultData) : void 0
4199
5133
  };
4200
5134
  }
@@ -4276,22 +5210,19 @@ function useEditorSession({
4276
5210
  }) {
4277
5211
  var _a;
4278
5212
  const definitions = useMemo3(() => mergeBlockDefinitions(blocks), [blocks]);
4279
- const [baseline] = useState10(() => {
5213
+ const [document, setDocument] = useState14(() => {
4280
5214
  const base = ensureEmailDocument(
4281
5215
  structuredClone(initialDocument != null ? initialDocument : createEmptyDocument())
4282
5216
  );
4283
5217
  if (global === void 0) return base;
4284
5218
  return __spreadProps(__spreadValues({}, base), { global: mergeEmailGlobal(base.global, global) });
4285
5219
  });
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(
5220
+ const [selectedId, setSelectedId] = useState14(null);
5221
+ const documentRef = useRef14(document);
5222
+ useLayoutEffect7(() => {
5223
+ documentRef.current = document;
5224
+ }, [document]);
5225
+ const commit = useCallback3(
4295
5226
  (next) => {
4296
5227
  documentRef.current = next;
4297
5228
  setDocument(next);
@@ -4306,14 +5237,14 @@ function useEditorSession({
4306
5237
  if (sameEmailGlobal(resolveEmailGlobal(current.global), nextGlobal)) return;
4307
5238
  commit(__spreadProps(__spreadValues({}, current), { global: nextGlobal }));
4308
5239
  }, [global, commit]);
4309
- const getDocument = useCallback2(
5240
+ const getDocument = useCallback3(
4310
5241
  () => ensureEmailDocument(structuredClone(documentRef.current)),
4311
5242
  []
4312
5243
  );
4313
- const selectBlock = useCallback2((id) => {
5244
+ const selectBlock = useCallback3((id) => {
4314
5245
  setSelectedId(id);
4315
5246
  }, []);
4316
- const insertBlock = useCallback2(
5247
+ const insertBlock = useCallback3(
4317
5248
  (type, index) => {
4318
5249
  const node = createBlockNode(type, definitions);
4319
5250
  if (!node) return false;
@@ -4323,19 +5254,19 @@ function useEditorSession({
4323
5254
  },
4324
5255
  [commit, definitions]
4325
5256
  );
4326
- const updateField = useCallback2(
5257
+ const updateField = useCallback3(
4327
5258
  (id, name, value) => {
4328
5259
  commit(updateBlockField(documentRef.current, id, name, value));
4329
5260
  },
4330
5261
  [commit]
4331
5262
  );
4332
- const updateStyle = useCallback2(
5263
+ const updateStyle = useCallback3(
4333
5264
  (id, styleId, value) => {
4334
5265
  commit(updateBlockStyle(documentRef.current, id, styleId, value));
4335
5266
  },
4336
5267
  [commit]
4337
5268
  );
4338
- const updateGlobal = useCallback2(
5269
+ const updateGlobal = useCallback3(
4339
5270
  (patch) => {
4340
5271
  const next = updateGlobalSettings(documentRef.current, patch);
4341
5272
  commit(next);
@@ -4343,44 +5274,37 @@ function useEditorSession({
4343
5274
  },
4344
5275
  [commit, onGlobalChange]
4345
5276
  );
4346
- const moveBlock = useCallback2(
5277
+ const moveBlock = useCallback3(
4347
5278
  (id, direction) => {
4348
5279
  commit(moveBlockBy(documentRef.current, id, direction));
4349
5280
  },
4350
5281
  [commit]
4351
5282
  );
4352
- const duplicateBlock = useCallback2(
5283
+ const duplicateBlock = useCallback3(
4353
5284
  (id) => {
4354
5285
  commit(duplicateBlockNode(documentRef.current, id));
4355
5286
  },
4356
5287
  [commit]
4357
5288
  );
4358
- const removeBlock = useCallback2(
5289
+ const removeBlock = useCallback3(
4359
5290
  (id) => {
4360
5291
  commit(removeBlockNode(documentRef.current, id));
4361
5292
  setSelectedId((current) => current === id ? null : current);
4362
5293
  },
4363
5294
  [commit]
4364
5295
  );
4365
- const reorderBlocks = useCallback2(
5296
+ const reorderBlocks = useCallback3(
4366
5297
  (activeId, overId) => {
4367
5298
  commit(reorderBlockNodes(documentRef.current, activeId, overId));
4368
5299
  },
4369
5300
  [commit]
4370
5301
  );
4371
- const setSubject = useCallback2(
5302
+ const setSubject = useCallback3(
4372
5303
  (subject) => {
4373
5304
  commit(withSubject(documentRef.current, subject.slice(0, SUBJECT_MAX_LENGTH)));
4374
5305
  },
4375
5306
  [commit]
4376
5307
  );
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
5308
  const resolvedDocument = useMemo3(() => ensureEmailDocument(document), [document]);
4385
5309
  const selectedBlock = (_a = resolvedDocument.blocks.find((block) => block.id === selectedId)) != null ? _a : null;
4386
5310
  const selectedDefinition = selectedBlock ? getBlockDefinition(selectedBlock.type, definitions) : void 0;
@@ -4401,8 +5325,7 @@ function useEditorSession({
4401
5325
  duplicateBlock,
4402
5326
  removeBlock,
4403
5327
  reorderBlocks,
4404
- setSubject,
4405
- reset
5328
+ setSubject
4406
5329
  };
4407
5330
  }
4408
5331
 
@@ -4433,8 +5356,8 @@ function useEditorHandle(ref, { getDocument, definitions, variables }) {
4433
5356
  }
4434
5357
 
4435
5358
  // src/shell/EmailEditorShell.tsx
4436
- import { jsx as jsx41, jsxs as jsxs31 } from "react/jsx-runtime";
4437
- var EmailEditorShell = forwardRef(
5359
+ import { jsx as jsx50, jsxs as jsxs36 } from "react/jsx-runtime";
5360
+ var EmailEditorShell = forwardRef2(
4438
5361
  function EmailEditorShell2({
4439
5362
  initialDocument,
4440
5363
  global,
@@ -4454,12 +5377,14 @@ var EmailEditorShell = forwardRef(
4454
5377
  onGlobalChange
4455
5378
  });
4456
5379
  useEditorHandle(ref, session);
4457
- const [viewMode, setViewMode] = useState11("edit");
4458
- const [device, setDevice] = useState11("desktop");
4459
- const [inspectorPanel, setInspectorPanel] = useState11("global");
4460
- useEffect9(() => {
5380
+ const [viewMode, setViewMode] = useState15("edit");
5381
+ const [device, setDevice] = useState15("desktop");
5382
+ const [inspectorPanel, setInspectorPanel] = useState15("global");
5383
+ const [previousSelectedId, setPreviousSelectedId] = useState15(session.selectedId);
5384
+ if (previousSelectedId !== session.selectedId) {
5385
+ setPreviousSelectedId(session.selectedId);
4461
5386
  if (!session.selectedId) setInspectorPanel("global");
4462
- }, [session.selectedId]);
5387
+ }
4463
5388
  const selectBlock = (id) => {
4464
5389
  const hadSelection = session.selectedId !== null;
4465
5390
  session.selectBlock(id);
@@ -4479,18 +5404,17 @@ var EmailEditorShell = forwardRef(
4479
5404
  selectBlock(null);
4480
5405
  };
4481
5406
  const showCanvas = () => setViewMode("edit");
4482
- return /* @__PURE__ */ jsxs31("div", { className: "ree-root", style: { height }, onKeyDown: handleKeyDown, children: [
4483
- /* @__PURE__ */ jsx41(
5407
+ return /* @__PURE__ */ jsxs36("div", { className: "ree-root", style: { height }, onKeyDown: handleKeyDown, children: [
5408
+ /* @__PURE__ */ jsx50(
4484
5409
  SubjectBar,
4485
5410
  {
4486
5411
  subject: (_a = session.document.subject) != null ? _a : "",
4487
5412
  variables: session.variables,
4488
- onSubjectChange: session.setSubject,
4489
- onReset: session.reset
5413
+ onSubjectChange: session.setSubject
4490
5414
  }
4491
5415
  ),
4492
- /* @__PURE__ */ jsxs31("div", { className: "ree-body", children: [
4493
- /* @__PURE__ */ jsxs31(
5416
+ /* @__PURE__ */ jsxs36("div", { className: "ree-body", children: [
5417
+ /* @__PURE__ */ jsxs36(
4494
5418
  EditorDnd,
4495
5419
  {
4496
5420
  blocks: session.document.blocks,
@@ -4502,7 +5426,7 @@ var EmailEditorShell = forwardRef(
4502
5426
  onReorder: session.reorderBlocks,
4503
5427
  onLibraryDragStart: showCanvas,
4504
5428
  children: [
4505
- /* @__PURE__ */ jsx41(
5429
+ /* @__PURE__ */ jsx50(
4506
5430
  BlockLibrary,
4507
5431
  {
4508
5432
  definitions: session.definitions,
@@ -4512,7 +5436,7 @@ var EmailEditorShell = forwardRef(
4512
5436
  }
4513
5437
  }
4514
5438
  ),
4515
- /* @__PURE__ */ jsx41(
5439
+ /* @__PURE__ */ jsx50(
4516
5440
  Stage,
4517
5441
  {
4518
5442
  session,
@@ -4526,7 +5450,7 @@ var EmailEditorShell = forwardRef(
4526
5450
  ]
4527
5451
  }
4528
5452
  ),
4529
- /* @__PURE__ */ jsx41(
5453
+ /* @__PURE__ */ jsx50(
4530
5454
  Inspector,
4531
5455
  {
4532
5456
  panel: inspectorPanel,
@@ -4546,10 +5470,10 @@ var EmailEditorShell = forwardRef(
4546
5470
  );
4547
5471
 
4548
5472
  // src/EmailEditor.tsx
4549
- import { jsx as jsx42 } from "react/jsx-runtime";
4550
- var EmailEditor = forwardRef2(
5473
+ import { jsx as jsx51 } from "react/jsx-runtime";
5474
+ var EmailEditor = forwardRef3(
4551
5475
  function EmailEditor2(props, ref) {
4552
- return /* @__PURE__ */ jsx42(EmailEditorShell, __spreadProps(__spreadValues({}, props), { ref }));
5476
+ return /* @__PURE__ */ jsx51(EmailEditorShell, __spreadProps(__spreadValues({}, props), { ref }));
4553
5477
  }
4554
5478
  );
4555
5479
  export {