@standhigher/react-email-editor 0.3.0 → 0.4.3

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
@@ -31,9 +31,6 @@ var __objRest = (source, exclude) => {
31
31
  return target;
32
32
  };
33
33
 
34
- // src/EmailEditor.tsx
35
- import { forwardRef as forwardRef3 } from "react";
36
-
37
34
  // src/plugins/editor-context.ts
38
35
  import { createContext, useContext } from "react";
39
36
  var RichTextFocusContext = createContext({ active: null, activate: () => {
@@ -47,7 +44,7 @@ function usePluginEditor() {
47
44
  }
48
45
 
49
46
  // src/shell/EmailEditorShell.tsx
50
- import { forwardRef as forwardRef2, useState as useState18 } from "react";
47
+ import { useState as useState18 } from "react";
51
48
 
52
49
  // src/editor-dnd/EditorDnd.tsx
53
50
  import { useLayoutEffect as useLayoutEffect5, useMemo, useRef as useRef7, useState as useState7 } from "react";
@@ -2730,13 +2727,13 @@ function bindPluginComponent(Component2) {
2730
2727
  // src/plugins/controls.ts
2731
2728
  import { createElement as createElement2 } from "react";
2732
2729
 
2733
- // src/plugins/config-snapshot.ts
2730
+ // src/utils/snapshot.ts
2734
2731
  import { isValidElement } from "react";
2735
- function snapshotConfig(value) {
2736
- if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshotConfig(item)));
2732
+ function snapshotValue(value) {
2733
+ if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshotValue(item)));
2737
2734
  if (!value || typeof value !== "object" || isValidElement(value)) return value;
2738
2735
  if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return value;
2739
- return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshotConfig(item)])));
2736
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshotValue(item)])));
2740
2737
  }
2741
2738
 
2742
2739
  // src/plugins/control-validation.ts
@@ -2761,10 +2758,10 @@ function controlConstraintError(kind, options, value) {
2761
2758
  // src/plugins/controls.ts
2762
2759
  function buildControl(kind, options, fallback, parse) {
2763
2760
  var _b;
2764
- const _a = snapshotConfig(options), { component: Component2, validate } = _a, config = __objRest(_a, ["component", "validate"]);
2761
+ const _a = snapshotValue(options), { component: Component2, validate } = _a, config = __objRest(_a, ["component", "validate"]);
2765
2762
  const defaultValue = structuredClone((_b = config.defaultValue) != null ? _b : fallback);
2766
2763
  const read = (value) => value === void 0 ? structuredClone(defaultValue) : parse(value);
2767
- return snapshotConfig(__spreadProps(__spreadValues({}, config), {
2764
+ return snapshotValue(__spreadProps(__spreadValues({}, config), {
2768
2765
  kind,
2769
2766
  defaultValue,
2770
2767
  read,
@@ -2799,8 +2796,8 @@ var EmailControls = {
2799
2796
  }),
2800
2797
  list(options) {
2801
2798
  const _a = options, { controls } = _a, config = __objRest(_a, ["controls"]);
2802
- const schema = snapshotConfig(controls);
2803
- return snapshotConfig(__spreadProps(__spreadValues({}, buildControl("list", config, [], (value) => Array.isArray(value) ? value.map((item) => readControlValues(schema, item && typeof item === "object" ? item : {})) : [])), { itemControls: schema }));
2799
+ const schema = snapshotValue(controls);
2800
+ return snapshotValue(__spreadProps(__spreadValues({}, buildControl("list", config, [], (value) => Array.isArray(value) ? value.map((item) => readControlValues(schema, item && typeof item === "object" ? item : {})) : [])), { itemControls: schema }));
2804
2801
  },
2805
2802
  custom(options) {
2806
2803
  const _a = options, { read } = _a, config = __objRest(_a, ["read"]);
@@ -2811,7 +2808,7 @@ var EmailControls = {
2811
2808
  // src/plugins/define-block.ts
2812
2809
  function defineBlock(input) {
2813
2810
  var _a, _b, _c;
2814
- const options = snapshotConfig(input);
2811
+ const options = snapshotValue(input);
2815
2812
  if (!options.type.trim() || !options.title.trim()) throw new Error("Block type and title are required.");
2816
2813
  const schema = (_a = options.controls) != null ? _a : {};
2817
2814
  const readControls = (source) => {
@@ -2824,7 +2821,7 @@ function defineBlock(input) {
2824
2821
  for (const item of (_b = options.styleControls) != null ? _b : []) styles.set(item.id, item);
2825
2822
  const inline = Object.entries(schema).filter(([, spec]) => spec.kind === "richText" && spec.inline);
2826
2823
  if (inline.length > 1 && !options.Canvas) throw new Error(`${options.type}: multiple inline controls require a Canvas component.`);
2827
- return snapshotConfig({
2824
+ return snapshotValue({
2828
2825
  type: options.type,
2829
2826
  title: options.title,
2830
2827
  description: options.description,
@@ -3843,7 +3840,7 @@ function buildRenderContext(node, mode, body = DEFAULT_EMAIL_BODY, definition =
3843
3840
  }
3844
3841
 
3845
3842
  // src/inspector/InspectorControls.tsx
3846
- import { useId as useId10 } from "react";
3843
+ import { useId as useId11 } from "react";
3847
3844
 
3848
3845
  // src/constants/style-groups.ts
3849
3846
  var STYLE_GROUPS = [
@@ -3888,7 +3885,7 @@ function inheritedStyleValue(id, body) {
3888
3885
  }
3889
3886
 
3890
3887
  // src/inspector/components/BasicControl.tsx
3891
- import { useRef as useRef9, useId as useId6 } from "react";
3888
+ import { useRef as useRef10, useId as useId7 } from "react";
3892
3889
 
3893
3890
  // src/inspector/HtmlCodeEditor.tsx
3894
3891
  import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect6, useRef as useRef8 } from "react";
@@ -4082,41 +4079,147 @@ function HtmlCodeEditor({
4082
4079
  );
4083
4080
  }
4084
4081
 
4085
- // src/inspector/VariablesDropdown.tsx
4086
- import { useState as useState12 } from "react";
4082
+ // src/components/PersonalizationMenu.tsx
4083
+ import { useId as useId6, useRef as useRef9, useState as useState13 } from "react";
4084
+ import { createPortal as createPortal2 } from "react-dom";
4085
+
4086
+ // src/components/usePersonalizationMenu.ts
4087
+ import { useLayoutEffect as useLayoutEffect7, useState as useState12 } from "react";
4088
+ function usePersonalizationMenu(open, rootRef, menuRef, focusOnOpen, setOpen) {
4089
+ const [position, setPosition] = useState12({ left: 0, top: 0, maxHeight: 240 });
4090
+ useLayoutEffect7(() => {
4091
+ var _a;
4092
+ const root = rootRef.current;
4093
+ const menu = menuRef.current;
4094
+ if (!open || !root || !menu) return;
4095
+ const ownerDocument = root.ownerDocument;
4096
+ const ownerWindow = ownerDocument.defaultView;
4097
+ if (!ownerWindow) return;
4098
+ const place = () => {
4099
+ var _a2, _b;
4100
+ const anchor = root.getBoundingClientRect();
4101
+ const inputBounds = (_b = (_a2 = root.closest(".ree-input-with-variable")) == null ? void 0 : _a2.getBoundingClientRect()) != null ? _b : anchor;
4102
+ const width = menu.offsetWidth;
4103
+ const height = Math.min(menu.scrollHeight + 2, 240);
4104
+ const below = Math.max(0, ownerWindow.innerHeight - inputBounds.bottom - 8);
4105
+ const above = Math.max(0, inputBounds.top - 8);
4106
+ const up = below < height && above > below;
4107
+ const maxHeight = Math.min(240, up ? above : below);
4108
+ const left = anchor.left + width > ownerWindow.innerWidth - 8 ? anchor.right - width : anchor.left;
4109
+ setPosition({
4110
+ left: Math.max(8, Math.min(left, ownerWindow.innerWidth - width - 8)),
4111
+ top: up ? inputBounds.top - Math.min(height, maxHeight) : inputBounds.bottom,
4112
+ maxHeight
4113
+ });
4114
+ };
4115
+ const onPointerDown = (event) => {
4116
+ if (!root.contains(event.target) && !menu.contains(event.target)) setOpen(false);
4117
+ };
4118
+ const onKeyDown = (event) => {
4119
+ var _a2, _b;
4120
+ const target = event.target;
4121
+ if (!root.contains(target) && !menu.contains(target)) return;
4122
+ if (event.key === "Tab" && menu.contains(target)) {
4123
+ const buttons = menu.querySelectorAll("button");
4124
+ if (target !== (event.shiftKey ? buttons[0] : buttons[buttons.length - 1])) return;
4125
+ if (event.shiftKey) event.preventDefault();
4126
+ setOpen(false);
4127
+ (_a2 = root.querySelector("button")) == null ? void 0 : _a2.focus({ preventScroll: true });
4128
+ return;
4129
+ }
4130
+ if (event.key !== "Escape") return;
4131
+ event.preventDefault();
4132
+ event.stopPropagation();
4133
+ setOpen(false);
4134
+ (_b = root.querySelector("button")) == null ? void 0 : _b.focus({ preventScroll: true });
4135
+ };
4136
+ menu.setAttribute("popover", "manual");
4137
+ menu.showPopover();
4138
+ place();
4139
+ if (focusOnOpen.current) (_a = menu.querySelector("button")) == null ? void 0 : _a.focus({ preventScroll: true });
4140
+ const observer = new ResizeObserver(place);
4141
+ observer.observe(root);
4142
+ observer.observe(menu);
4143
+ ownerWindow.addEventListener("resize", place);
4144
+ ownerDocument.addEventListener("scroll", place, true);
4145
+ ownerDocument.addEventListener("pointerdown", onPointerDown, true);
4146
+ ownerDocument.addEventListener("keydown", onKeyDown, true);
4147
+ return () => {
4148
+ menu.hidePopover();
4149
+ observer.disconnect();
4150
+ ownerWindow.removeEventListener("resize", place);
4151
+ ownerDocument.removeEventListener("scroll", place, true);
4152
+ ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
4153
+ ownerDocument.removeEventListener("keydown", onKeyDown, true);
4154
+ };
4155
+ }, [open, rootRef, menuRef, focusOnOpen, setOpen]);
4156
+ return position;
4157
+ }
4158
+
4159
+ // src/components/PersonalizationMenu.tsx
4087
4160
  import { jsx as jsx40, jsxs as jsxs29 } from "react/jsx-runtime";
4088
- function VariablesDropdown({ variables, onInsert }) {
4089
- const [open, setOpen] = useState12(false);
4161
+ function PersonalizationMenu({
4162
+ variables,
4163
+ onInsert,
4164
+ suffix = false
4165
+ }) {
4166
+ const [open, setOpen] = useState13(false);
4167
+ const [host, setHost] = useState13(null);
4168
+ const id = useId6();
4169
+ const rootRef = useRef9(null);
4170
+ const menuRef = useRef9(null);
4171
+ const focusOnOpen = useRef9(false);
4172
+ const position = usePersonalizationMenu(open && variables.length > 0, rootRef, menuRef, focusOnOpen, setOpen);
4090
4173
  if (variables.length === 0) return null;
4091
- return /* @__PURE__ */ jsxs29("div", { className: "ree-var-wrap", children: [
4092
- /* @__PURE__ */ jsx40(
4174
+ return /* @__PURE__ */ jsxs29("div", { className: suffix ? "ree-personalization ree-personalization-suffix" : "ree-personalization", ref: rootRef, children: [
4175
+ /* @__PURE__ */ jsxs29(
4093
4176
  "button",
4094
4177
  {
4095
4178
  type: "button",
4096
- className: "ree-var-btn",
4097
- onClick: () => setOpen((current) => !current),
4098
- title: "Insert variable",
4099
- "aria-label": "Insert variable",
4179
+ className: suffix ? "ree-personalization-suffix-btn" : "ree-personalization-btn",
4180
+ "aria-label": suffix ? "Insert variable" : void 0,
4181
+ title: suffix ? "Personalization" : void 0,
4100
4182
  "aria-expanded": open,
4101
- children: "{ }"
4102
- }
4103
- ),
4104
- open ? /* @__PURE__ */ jsx40("div", { className: "ree-var-menu", children: variables.map((variable) => /* @__PURE__ */ jsxs29(
4105
- "button",
4106
- {
4107
- type: "button",
4108
- className: "ree-var-item",
4109
- onClick: () => {
4110
- onInsert(variable.name);
4111
- setOpen(false);
4183
+ "aria-controls": open ? id : void 0,
4184
+ onClick: (event) => {
4185
+ var _a;
4186
+ focusOnOpen.current = event.detail === 0;
4187
+ const root = rootRef.current;
4188
+ if (root) setHost((_a = root.closest(".ree-root")) != null ? _a : root.ownerDocument.body);
4189
+ setOpen((current) => !current);
4190
+ },
4191
+ onKeyDown: (event) => {
4192
+ var _a, _b;
4193
+ if (event.key === "Tab" && open && !event.shiftKey) {
4194
+ event.preventDefault();
4195
+ (_b = (_a = menuRef.current) == null ? void 0 : _a.querySelector("button")) == null ? void 0 : _b.focus();
4196
+ }
4112
4197
  },
4113
4198
  children: [
4114
- /* @__PURE__ */ jsx40("span", { className: "ree-var-name", children: variable.name }),
4115
- variable.description ? /* @__PURE__ */ jsx40("span", { className: "ree-var-label", children: variable.description }) : null
4199
+ suffix ? "{ }" : "Personalization",
4200
+ suffix ? null : /* @__PURE__ */ jsx40("svg", { viewBox: "0 0 20 20", width: "16", height: "16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx40("path", { d: "M5.2 7.5 10 12.3l4.8-4.8 1.2 1.2L10 14.7 4 8.7l1.2-1.2Z" }) })
4116
4201
  ]
4117
- },
4118
- variable.name
4119
- )) }) : null
4202
+ }
4203
+ ),
4204
+ open && host ? createPortal2(
4205
+ /* @__PURE__ */ jsx40("div", { id, ref: menuRef, className: "ree-var-menu ree-personalization-menu", style: position, children: variables.map((variable) => /* @__PURE__ */ jsxs29(
4206
+ "button",
4207
+ {
4208
+ type: "button",
4209
+ className: "ree-var-item",
4210
+ onClick: () => {
4211
+ onInsert(variable.name);
4212
+ setOpen(false);
4213
+ },
4214
+ children: [
4215
+ /* @__PURE__ */ jsx40("span", { className: "ree-var-name", children: variable.name }),
4216
+ variable.description ? /* @__PURE__ */ jsx40("span", { className: "ree-var-label", children: variable.description }) : null
4217
+ ]
4218
+ },
4219
+ variable.name
4220
+ )) }),
4221
+ host
4222
+ ) : null
4120
4223
  ] });
4121
4224
  }
4122
4225
 
@@ -4124,8 +4227,8 @@ function VariablesDropdown({ variables, onInsert }) {
4124
4227
  import { jsx as jsx41, jsxs as jsxs30 } from "react/jsx-runtime";
4125
4228
  function BasicControl({ control, value, variables, onChange }) {
4126
4229
  var _a, _b, _c, _d, _e;
4127
- const ref = useRef9(null);
4128
- const id = useId6();
4230
+ const ref = useRef10(null);
4231
+ const id = useId7();
4129
4232
  const shown = value == null || typeof value === "number" && !Number.isFinite(value) ? "" : String(value);
4130
4233
  const insertVariable = (token) => {
4131
4234
  var _a2, _b2;
@@ -4133,6 +4236,11 @@ function BasicControl({ control, value, variables, onChange }) {
4133
4236
  const start = (_a2 = element == null ? void 0 : element.selectionStart) != null ? _a2 : shown.length;
4134
4237
  const end = (_b2 = element == null ? void 0 : element.selectionEnd) != null ? _b2 : shown.length;
4135
4238
  onChange(shown.slice(0, start) + token + shown.slice(end));
4239
+ requestAnimationFrame(() => {
4240
+ var _a3, _b3;
4241
+ (_a3 = ref.current) == null ? void 0 : _a3.focus();
4242
+ (_b3 = ref.current) == null ? void 0 : _b3.setSelectionRange(start + token.length, start + token.length);
4243
+ });
4136
4244
  };
4137
4245
  if (control.kind === "boolean") return /* @__PURE__ */ jsx41(CheckboxControl, { label: control.label, checked: Boolean(value), onChange });
4138
4246
  if (control.kind === "color") return /* @__PURE__ */ jsx41(ColorControl, { label: control.label, value: shown, resetTo: String((_a = control.defaultValue) != null ? _a : ""), onChange });
@@ -4141,30 +4249,32 @@ function BasicControl({ control, value, variables, onChange }) {
4141
4249
  /* @__PURE__ */ jsx41(HtmlCodeEditor, { value: shown, placeholder: control.placeholder, rows: control.rows, onChange })
4142
4250
  ] });
4143
4251
  const textual = control.kind === "text" || control.kind === "textarea" || control.kind === "url";
4252
+ const hasVariables = textual && variables.length > 0;
4144
4253
  const invalidUrl = control.kind === "url" && shown.trim() && !safeEmailUrl(shown);
4254
+ const input = control.kind === "select" ? /* @__PURE__ */ jsx41(SelectControl, { id, label: control.label, value: shown, options: (_b = control.options) != null ? _b : [], emptyLabel: (_c = control.placeholder) != null ? _c : "None", onChange }) : control.kind === "textarea" ? /* @__PURE__ */ jsx41("textarea", { id, ref: (element) => {
4255
+ ref.current = element;
4256
+ }, className: "ree-input ree-textarea", value: shown, placeholder: control.placeholder, rows: (_d = control.rows) != null ? _d : 4, onChange: (event) => onChange(event.target.value) }) : /* @__PURE__ */ jsx41(TextInput, { id, ref: (element) => {
4257
+ ref.current = element;
4258
+ }, type: control.kind === "number" ? "number" : "text", value: shown, min: control.min, max: control.max, step: control.step, placeholder: control.placeholder, "aria-invalid": Boolean(invalidUrl) || Boolean((_e = control.validateValue) == null ? void 0 : _e.call(control, value)), onChange: (event) => onChange(control.kind === "number" && event.target.value !== "" ? Number(event.target.value) : event.target.value) });
4145
4259
  return /* @__PURE__ */ jsxs30("div", { className: "ree-field", children: [
4146
- /* @__PURE__ */ jsxs30("div", { className: "ree-field-label", children: [
4147
- /* @__PURE__ */ jsxs30("label", { htmlFor: id, children: [
4148
- control.label,
4149
- control.unit ? ` (${control.unit})` : ""
4150
- ] }),
4151
- textual ? /* @__PURE__ */ jsx41(VariablesDropdown, { variables, onInsert: insertVariable }) : null
4152
- ] }),
4153
- control.kind === "select" ? /* @__PURE__ */ jsx41(SelectControl, { id, label: control.label, value: shown, options: (_b = control.options) != null ? _b : [], emptyLabel: (_c = control.placeholder) != null ? _c : "None", onChange }) : control.kind === "textarea" ? /* @__PURE__ */ jsx41("textarea", { id, ref: (element) => {
4154
- ref.current = element;
4155
- }, className: "ree-input ree-textarea", value: shown, placeholder: control.placeholder, rows: (_d = control.rows) != null ? _d : 4, onChange: (event) => onChange(event.target.value) }) : /* @__PURE__ */ jsx41(TextInput, { id, ref: (element) => {
4156
- ref.current = element;
4157
- }, type: control.kind === "number" ? "number" : "text", value: shown, min: control.min, max: control.max, step: control.step, placeholder: control.placeholder, "aria-invalid": Boolean(invalidUrl) || Boolean((_e = control.validateValue) == null ? void 0 : _e.call(control, value)), onChange: (event) => onChange(control.kind === "number" && event.target.value !== "" ? Number(event.target.value) : event.target.value) })
4260
+ /* @__PURE__ */ jsx41("div", { className: "ree-field-label", children: /* @__PURE__ */ jsxs30("label", { htmlFor: id, children: [
4261
+ control.label,
4262
+ control.unit ? ` (${control.unit})` : ""
4263
+ ] }) }),
4264
+ hasVariables ? /* @__PURE__ */ jsxs30("div", { className: "ree-input-with-variable", children: [
4265
+ input,
4266
+ /* @__PURE__ */ jsx41(PersonalizationMenu, { suffix: true, variables, onInsert: insertVariable })
4267
+ ] }) : input
4158
4268
  ] });
4159
4269
  }
4160
4270
 
4161
4271
  // src/inspector/components/ListControl.tsx
4162
- import { useId as useId7 } from "react";
4272
+ import { useId as useId8 } from "react";
4163
4273
 
4164
4274
  // src/inspector/components/useListRows.ts
4165
- import { useLayoutEffect as useLayoutEffect7, useRef as useRef10, useState as useState13 } from "react";
4275
+ import { useLayoutEffect as useLayoutEffect8, useRef as useRef11, useState as useState14 } from "react";
4166
4276
  function useListRows(value, onChange) {
4167
- const [state, setState] = useState13({ source: void 0, rows: [], nextKey: 0 });
4277
+ const [state, setState] = useState14({ source: void 0, rows: [], nextKey: 0 });
4168
4278
  if (state.source !== value) {
4169
4279
  const available = [...state.rows];
4170
4280
  let nextKey = state.nextKey;
@@ -4176,8 +4286,8 @@ function useListRows(value, onChange) {
4176
4286
  });
4177
4287
  setState({ source: value, rows, nextKey });
4178
4288
  }
4179
- const latest = useRef10(state);
4180
- useLayoutEffect7(() => {
4289
+ const latest = useRef11(state);
4290
+ useLayoutEffect8(() => {
4181
4291
  latest.current = state;
4182
4292
  return () => {
4183
4293
  latest.current = null;
@@ -4222,7 +4332,7 @@ import { jsx as jsx42, jsxs as jsxs31 } from "react/jsx-runtime";
4222
4332
  function ListControl(_a) {
4223
4333
  var _b = _a, { control, value, onChange } = _b, props = __objRest(_b, ["control", "value", "onChange"]);
4224
4334
  var _a2;
4225
- const id = useId7();
4335
+ const id = useId8();
4226
4336
  const schema = (_a2 = control.itemControls) != null ? _a2 : {};
4227
4337
  const { rows, updateRow, moveRow, removeRow, addRow } = useListRows(value, onChange);
4228
4338
  return /* @__PURE__ */ jsxs31("fieldset", { className: "ree-plugin-list", children: [
@@ -4242,15 +4352,15 @@ function ListControl(_a) {
4242
4352
  }
4243
4353
 
4244
4354
  // src/inspector/components/ImageControl.tsx
4245
- import { useContext as useContext4, useEffect as useEffect5, useId as useId8, useRef as useRef11, useState as useState14 } from "react";
4355
+ import { useContext as useContext4, useEffect as useEffect5, useId as useId9, useRef as useRef12, useState as useState15 } from "react";
4246
4356
  import { jsx as jsx43, jsxs as jsxs32 } from "react/jsx-runtime";
4247
4357
  function ImageControl({ control, value, onChange }) {
4248
4358
  var _a;
4249
4359
  const config = useContext4(EditorConfigContext);
4250
- const controller = useRef11(null);
4251
- const id = useId8();
4252
- const [loading, setLoading] = useState14(false);
4253
- const [error, setError] = useState14("");
4360
+ const controller = useRef12(null);
4361
+ const id = useId9();
4362
+ const [loading, setLoading] = useState15(false);
4363
+ const [error, setError] = useState15("");
4254
4364
  const shown = String(value != null ? value : "");
4255
4365
  const url = safeEmailUrl(shown, true);
4256
4366
  useEffect5(() => () => {
@@ -4287,12 +4397,12 @@ function ImageControl({ control, value, onChange }) {
4287
4397
  }
4288
4398
 
4289
4399
  // src/inspector/components/RichTextControl.tsx
4290
- import { useContext as useContext5, useId as useId9 } from "react";
4400
+ import { useContext as useContext5, useId as useId10 } from "react";
4291
4401
  import { jsx as jsx44, jsxs as jsxs33 } from "react/jsx-runtime";
4292
4402
  function RichTextControl({ control, value, variables, body, onChange }) {
4293
4403
  var _a, _b;
4294
4404
  const parent = useContext5(PluginEditorContext);
4295
- const id = useId9();
4405
+ const id = useId10();
4296
4406
  const context = {
4297
4407
  block: (_a = parent == null ? void 0 : parent.block) != null ? _a : { id, type: "control", controls: {} },
4298
4408
  controls: { [control.name]: value },
@@ -4350,7 +4460,7 @@ function InspectorControls({
4350
4460
  onCommitStyle
4351
4461
  }) {
4352
4462
  var _a, _b, _c, _d, _e;
4353
- const toolbarId = useId10();
4463
+ const toolbarId = useId11();
4354
4464
  const contentControls = ((_a = definition.controls) != null ? _a : []).filter((control) => control.name !== definition.inlineControl);
4355
4465
  const CustomInspector = definition.inspector;
4356
4466
  const context = buildRenderContext(block, "canvas", body, definition);
@@ -4502,7 +4612,7 @@ function viewportWidth(device, body) {
4502
4612
  }
4503
4613
 
4504
4614
  // src/stage/canvas/CanvasBlock.tsx
4505
- import { useId as useId12, useMemo as useMemo3, useState as useState15 } from "react";
4615
+ import { useId as useId13, useMemo as useMemo3, useState as useState16 } from "react";
4506
4616
  import { useSortable } from "@dnd-kit/sortable";
4507
4617
  import { CSS } from "@dnd-kit/utilities";
4508
4618
 
@@ -4638,8 +4748,10 @@ function renderTemplatePreview(html, defaultData) {
4638
4748
  }
4639
4749
 
4640
4750
  // src/render/block-html.ts
4641
- function normalizeBlockRenderOptions(options) {
4642
- return typeof options === "boolean" ? { resolveHtmlTemplate: options } : options;
4751
+ function assertBlockRenderOptions(options) {
4752
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
4753
+ throw new TypeError("Block render options must be an object.");
4754
+ }
4643
4755
  }
4644
4756
  function renderPreparedBlock(context, definition) {
4645
4757
  try {
@@ -4712,23 +4824,23 @@ function mergeTemplateData(variables, defaultData) {
4712
4824
  }
4713
4825
  function renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
4714
4826
  var _a;
4715
- const normalizedOptions = normalizeBlockRenderOptions(options);
4827
+ assertBlockRenderOptions(options);
4716
4828
  const rendered = renderBlock(
4717
4829
  node,
4718
4830
  definitions,
4719
4831
  mode,
4720
- (_a = normalizedOptions.body) != null ? _a : DEFAULT_EMAIL_BODY
4832
+ (_a = options.body) != null ? _a : DEFAULT_EMAIL_BODY
4721
4833
  );
4722
- return resolveBlockContent(rendered, node, normalizedOptions);
4834
+ return resolveBlockContent(rendered, node, options);
4723
4835
  }
4724
- function resolveBlockContent(rendered, node, normalizedOptions) {
4836
+ function resolveBlockContent(rendered, node, options) {
4725
4837
  var _a, _b;
4726
- if (!normalizedOptions.resolveHtmlTemplate) {
4727
- return applyListedVariableDots(rendered, (_a = normalizedOptions.variables) != null ? _a : []);
4838
+ if (!options.resolveHtmlTemplate) {
4839
+ return applyListedVariableDots(rendered, (_a = options.variables) != null ? _a : []);
4728
4840
  }
4729
4841
  return renderTemplatePreview(
4730
4842
  rendered,
4731
- (_b = normalizedOptions.templateData) != null ? _b : node.defaultData
4843
+ (_b = options.templateData) != null ? _b : node.defaultData
4732
4844
  );
4733
4845
  }
4734
4846
  function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body = DEFAULT_EMAIL_BODY) {
@@ -4742,10 +4854,10 @@ function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body =
4742
4854
  }
4743
4855
  function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
4744
4856
  var _a, _b, _c;
4745
- const normalizedOptions = normalizeBlockRenderOptions(options);
4746
- const body = (_a = normalizedOptions.body) != null ? _a : DEFAULT_EMAIL_BODY;
4857
+ assertBlockRenderOptions(options);
4858
+ const body = (_a = options.body) != null ? _a : DEFAULT_EMAIL_BODY;
4747
4859
  const style = resolveBlockStyle(node.style, getBlockDefinition(node.type, definitions));
4748
- const inner = renderBlockContent(node, definitions, mode, __spreadProps(__spreadValues({}, normalizedOptions), {
4860
+ const inner = renderBlockContent(node, definitions, mode, __spreadProps(__spreadValues({}, options), {
4749
4861
  body
4750
4862
  }));
4751
4863
  const height = cssPx((_b = style.dimension) == null ? void 0 : _b.height);
@@ -4754,7 +4866,7 @@ function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options
4754
4866
  }
4755
4867
 
4756
4868
  // src/stage/canvas/components/HtmlCanvas.tsx
4757
- import { useCallback, useEffect as useEffect6, useRef as useRef12 } from "react";
4869
+ import { useCallback, useEffect as useEffect6, useRef as useRef13 } from "react";
4758
4870
 
4759
4871
  // src/render/head-styles.ts
4760
4872
  function extractHeadStyles(html) {
@@ -4775,9 +4887,9 @@ function frameHtml(html, baseCss) {
4775
4887
  }
4776
4888
  function HtmlCanvas({ html, baseCss = "", onSelect }) {
4777
4889
  const hasHtmlTag = /<\/?[a-z][\w:-]*(?:\s[^>]*)?>/i.test(html);
4778
- const containerRef = useRef12(null);
4779
- const frameRef = useRef12(null);
4780
- const cleanupRef = useRef12();
4890
+ const containerRef = useRef13(null);
4891
+ const frameRef = useRef13(null);
4892
+ const cleanupRef = useRef13();
4781
4893
  const observeContent = useCallback(() => {
4782
4894
  var _a;
4783
4895
  (_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
@@ -4785,8 +4897,8 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
4785
4897
  const frame = frameRef.current;
4786
4898
  const document = frame == null ? void 0 : frame.contentDocument;
4787
4899
  if (!container || !frame || !(document == null ? void 0 : document.body)) return;
4788
- const window = document.defaultView;
4789
- if (!window) return;
4900
+ const window2 = document.defaultView;
4901
+ if (!window2) return;
4790
4902
  let animationFrame = 0;
4791
4903
  const fit = () => {
4792
4904
  const availableWidth = container.clientWidth;
@@ -4798,12 +4910,12 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
4798
4910
  container.style.height = `${contentHeight}px`;
4799
4911
  };
4800
4912
  const scheduleFit = () => {
4801
- window.cancelAnimationFrame(animationFrame);
4802
- animationFrame = window.requestAnimationFrame(fit);
4913
+ window2.cancelAnimationFrame(animationFrame);
4914
+ animationFrame = window2.requestAnimationFrame(fit);
4803
4915
  };
4804
- const observer = new window.ResizeObserver(scheduleFit);
4916
+ const observer = new window2.ResizeObserver(scheduleFit);
4805
4917
  let previousWidth = container.clientWidth;
4806
- const containerObserver = new window.ResizeObserver(() => {
4918
+ const containerObserver = new window2.ResizeObserver(() => {
4807
4919
  if (container.clientWidth === previousWidth) return;
4808
4920
  previousWidth = container.clientWidth;
4809
4921
  scheduleFit();
@@ -4816,7 +4928,7 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
4816
4928
  observer.disconnect();
4817
4929
  containerObserver.disconnect();
4818
4930
  document.removeEventListener("load", scheduleFit, true);
4819
- window.cancelAnimationFrame(animationFrame);
4931
+ window2.cancelAnimationFrame(animationFrame);
4820
4932
  };
4821
4933
  }, []);
4822
4934
  useEffect6(() => () => {
@@ -4898,7 +5010,7 @@ function PluginCanvasContent({ block, body, definition, variables, selected, too
4898
5010
  }
4899
5011
 
4900
5012
  // src/stage/canvas/components/BlockActions.tsx
4901
- import { useId as useId11 } from "react";
5013
+ import { useId as useId12 } from "react";
4902
5014
  import { jsx as jsx50, jsxs as jsxs39 } from "react/jsx-runtime";
4903
5015
  function BlockActions({
4904
5016
  inlineEdit,
@@ -4908,7 +5020,7 @@ function BlockActions({
4908
5020
  onDuplicate,
4909
5021
  onRemove
4910
5022
  }) {
4911
- const tooltipId = useId11();
5023
+ const tooltipId = useId12();
4912
5024
  const toggleLabel = toolbarCollapsed ? "Expand Toolbar" : "Collapse Toolbar";
4913
5025
  return /* @__PURE__ */ jsxs39(
4914
5026
  "div",
@@ -5004,8 +5116,8 @@ function CanvasBlock({
5004
5116
  }) {
5005
5117
  var _a, _b, _c;
5006
5118
  const block = useMemo3(() => __spreadProps(__spreadValues({}, sourceBlock), { style: resolveBlockStyle(sourceBlock.style, definition) }), [sourceBlock, definition]);
5007
- const toolbarId = useId12();
5008
- const [toolbarCollapsed, setToolbarCollapsed] = useState15(false);
5119
+ const toolbarId = useId13();
5120
+ const [toolbarCollapsed, setToolbarCollapsed] = useState16(false);
5009
5121
  const dragData = { kind: "canvas" };
5010
5122
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: block.id, data: dragData });
5011
5123
  const select = (event) => {
@@ -5172,7 +5284,7 @@ function Canvas({
5172
5284
  }
5173
5285
 
5174
5286
  // src/stage/preview/Preview.tsx
5175
- import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef13 } from "react";
5287
+ import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef14 } from "react";
5176
5288
 
5177
5289
  // src/render/document-html.ts
5178
5290
  function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
@@ -5226,8 +5338,8 @@ function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
5226
5338
  // src/stage/preview/Preview.tsx
5227
5339
  import { jsx as jsx53 } from "react/jsx-runtime";
5228
5340
  function Preview({ document, definitions, variables, device }) {
5229
- const frameRef = useRef13(null);
5230
- const cleanupRef = useRef13();
5341
+ const frameRef = useRef14(null);
5342
+ const cleanupRef = useRef14();
5231
5343
  const html = useMemo4(
5232
5344
  () => documentToHtml(document, definitions, {
5233
5345
  resolveHtmlTemplates: true,
@@ -5420,75 +5532,23 @@ function Stage({
5420
5532
  ] });
5421
5533
  }
5422
5534
 
5535
+ // src/shell/SubjectBar.tsx
5536
+ import { useId as useId14, useRef as useRef15 } from "react";
5537
+
5423
5538
  // src/shell/constants.ts
5424
5539
  var EMPTY_VARIABLES = [];
5425
5540
  var PERSONALIZATION_HINT = "Personalization fields will appear blank if no data is available.";
5426
5541
 
5427
5542
  // src/shell/SubjectBar.tsx
5428
- import { useId as useId13, useRef as useRef15 } from "react";
5429
-
5430
- // src/shell/PersonalizationMenu.tsx
5431
- import { useEffect as useEffect8, useRef as useRef14, useState as useState16 } from "react";
5432
- import { jsx as jsx56, jsxs as jsxs43 } from "react/jsx-runtime";
5433
- function PersonalizationMenu({
5434
- variables,
5435
- onInsert
5436
- }) {
5437
- const [open, setOpen] = useState16(false);
5438
- const rootRef = useRef14(null);
5439
- useEffect8(() => {
5440
- const root = rootRef.current;
5441
- if (!open || !root) return;
5442
- const ownerDocument = root.ownerDocument;
5443
- const onPointerDown = (event) => {
5444
- if (!root.contains(event.target)) setOpen(false);
5445
- };
5446
- ownerDocument.addEventListener("pointerdown", onPointerDown, true);
5447
- return () => ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
5448
- }, [open]);
5449
- if (variables.length === 0) return null;
5450
- return /* @__PURE__ */ jsxs43("div", { className: "ree-personalization", ref: rootRef, children: [
5451
- /* @__PURE__ */ jsxs43(
5452
- "button",
5453
- {
5454
- type: "button",
5455
- className: "ree-personalization-btn",
5456
- "aria-expanded": open,
5457
- onClick: () => setOpen((current) => !current),
5458
- children: [
5459
- "Personalization",
5460
- /* @__PURE__ */ jsx56("svg", { viewBox: "0 0 20 20", width: "16", height: "16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx56("path", { d: "M5.2 7.5 10 12.3l4.8-4.8 1.2 1.2L10 14.7 4 8.7l1.2-1.2Z" }) })
5461
- ]
5462
- }
5463
- ),
5464
- open ? /* @__PURE__ */ jsx56("div", { className: "ree-var-menu ree-personalization-menu", children: variables.map((variable) => /* @__PURE__ */ jsxs43(
5465
- "button",
5466
- {
5467
- type: "button",
5468
- className: "ree-var-item",
5469
- onClick: () => {
5470
- onInsert(variable.name);
5471
- setOpen(false);
5472
- },
5473
- children: [
5474
- /* @__PURE__ */ jsx56("span", { className: "ree-var-name", children: variable.name }),
5475
- variable.description ? /* @__PURE__ */ jsx56("span", { className: "ree-var-label", children: variable.description }) : null
5476
- ]
5477
- },
5478
- variable.name
5479
- )) }) : null
5480
- ] });
5481
- }
5482
-
5483
- // src/shell/SubjectBar.tsx
5484
- import { Fragment as Fragment5, jsx as jsx57, jsxs as jsxs44 } from "react/jsx-runtime";
5543
+ import { Fragment as Fragment5, jsx as jsx56, jsxs as jsxs43 } from "react/jsx-runtime";
5485
5544
  function SubjectBar({
5486
5545
  subject,
5487
5546
  variables,
5488
5547
  onSubjectChange
5489
5548
  }) {
5490
5549
  const inputRef = useRef15(null);
5491
- const countId = useId13();
5550
+ const subjectId = useId14();
5551
+ const countId = useId14();
5492
5552
  const insertVariable = (token) => {
5493
5553
  var _a, _b;
5494
5554
  const input = inputRef.current;
@@ -5506,13 +5566,13 @@ function SubjectBar({
5506
5566
  (_b2 = inputRef.current) == null ? void 0 : _b2.setSelectionRange(cursor, cursor);
5507
5567
  });
5508
5568
  };
5509
- return /* @__PURE__ */ jsxs44("div", { className: "ree-subject-bar", children: [
5510
- /* @__PURE__ */ jsx57("label", { className: "ree-subject-label", htmlFor: "ree-email-subject", children: "Email subject" }),
5511
- /* @__PURE__ */ jsxs44("div", { className: "ree-subject-field", children: [
5512
- /* @__PURE__ */ jsx57(
5569
+ return /* @__PURE__ */ jsxs43("div", { className: "ree-subject-bar", children: [
5570
+ /* @__PURE__ */ jsx56("label", { className: "ree-subject-label", htmlFor: subjectId, children: "Email subject" }),
5571
+ /* @__PURE__ */ jsxs43("div", { className: "ree-subject-field", children: [
5572
+ /* @__PURE__ */ jsx56(
5513
5573
  TextInput,
5514
5574
  {
5515
- id: "ree-email-subject",
5575
+ id: subjectId,
5516
5576
  ref: inputRef,
5517
5577
  className: "ree-subject-input",
5518
5578
  value: subject,
@@ -5521,19 +5581,22 @@ function SubjectBar({
5521
5581
  onChange: (event) => onSubjectChange(event.target.value)
5522
5582
  }
5523
5583
  ),
5524
- /* @__PURE__ */ jsxs44("span", { id: countId, className: "ree-subject-count", children: [
5584
+ /* @__PURE__ */ jsxs43("span", { id: countId, className: "ree-subject-count", children: [
5525
5585
  subject.length,
5526
5586
  "/",
5527
5587
  SUBJECT_MAX_LENGTH
5528
5588
  ] })
5529
5589
  ] }),
5530
- variables.length > 0 ? /* @__PURE__ */ jsxs44(Fragment5, { children: [
5531
- /* @__PURE__ */ jsx57(PersonalizationMenu, { variables, onInsert: insertVariable }),
5532
- /* @__PURE__ */ jsx57("p", { className: "ree-subject-hint", children: PERSONALIZATION_HINT })
5590
+ variables.length > 0 ? /* @__PURE__ */ jsxs43(Fragment5, { children: [
5591
+ /* @__PURE__ */ jsx56(PersonalizationMenu, { variables, onInsert: insertVariable }),
5592
+ /* @__PURE__ */ jsx56("p", { className: "ree-subject-hint", children: PERSONALIZATION_HINT })
5533
5593
  ] }) : null
5534
5594
  ] });
5535
5595
  }
5536
5596
 
5597
+ // src/shell/useEditorSession.ts
5598
+ import { useCallback as useCallback3, useState as useState17, useSyncExternalStore } from "react";
5599
+
5537
5600
  // src/utils/id.ts
5538
5601
  function generateId(prefix = "block") {
5539
5602
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -5542,62 +5605,6 @@ function generateId(prefix = "block") {
5542
5605
  return `${prefix}-${Math.random().toString(36).slice(2, 10)}`;
5543
5606
  }
5544
5607
 
5545
- // src/document/create-block.ts
5546
- function createBlockNode(type, definitions) {
5547
- var _a, _b;
5548
- const definition = getBlockDefinition(type, definitions);
5549
- if (!definition) return null;
5550
- const controls = structuredClone(definition.readControls({}));
5551
- let style = resolveBlockStyle(definition.defaultStyle, definition);
5552
- for (const control of (_a = definition.styleControls) != null ? _a : []) {
5553
- const [group, key] = control.id.split(".");
5554
- const stored = (_b = definition.defaultStyle) == null ? void 0 : _b[group];
5555
- if (control.defaultValue !== void 0 && (!stored || !(key in stored))) style = writeStyleValue(style, control.id, control.defaultValue);
5556
- }
5557
- return {
5558
- id: generateId(type),
5559
- type,
5560
- controls,
5561
- style,
5562
- defaultData: definition.defaultData ? structuredClone(definition.defaultData) : void 0
5563
- };
5564
- }
5565
-
5566
- // src/plugins/config.ts
5567
- function createEmailEditorConfig(options = {}) {
5568
- var _a, _b, _c;
5569
- const definitions = new Map(DEFAULT_BLOCKS.map((block) => [block.type, block]));
5570
- const customTypes = /* @__PURE__ */ new Set();
5571
- for (const plugin of (_a = options.plugins) != null ? _a : []) {
5572
- if (customTypes.has(plugin.type)) throw new Error(`Duplicate block plugin: ${plugin.type}`);
5573
- if (definitions.has(plugin.type) && !plugin.override) throw new Error(`Block ${plugin.type} already exists. Set override: true to replace it.`);
5574
- customTypes.add(plugin.type);
5575
- definitions.set(plugin.type, plugin);
5576
- }
5577
- const builtinTypes = new Set(DEFAULT_BLOCKS.map((block) => block.type));
5578
- const visibleBuiltins = options.builtins === false ? [] : (_b = options.builtins) != null ? _b : [...builtinTypes];
5579
- if (new Set(visibleBuiltins).size !== visibleBuiltins.length) throw new Error("Duplicate builtin in library order.");
5580
- for (const type of visibleBuiltins) if (!builtinTypes.has(type)) throw new Error(`Unknown builtin block: ${type}`);
5581
- const all = Object.freeze([...definitions.values()].map((plugin) => snapshotConfig(plugin)));
5582
- const registered = new Map(all.map((plugin) => [plugin.type, plugin]));
5583
- return Object.freeze({
5584
- definitions: all,
5585
- library: Object.freeze([...visibleBuiltins.map((type) => registered.get(type)), ...((_c = options.plugins) != null ? _c : []).filter((plugin) => !builtinTypes.has(plugin.type)).map((plugin) => registered.get(plugin.type))]),
5586
- pickImage: options.pickImage,
5587
- createBlock: (type) => {
5588
- const block = createBlockNode(type, all);
5589
- if (!block) throw new Error(`Unknown block plugin: ${type}`);
5590
- return block;
5591
- },
5592
- toHtml: (document, htmlOptions) => documentToHtml(document, all, htmlOptions),
5593
- validate: (document) => validateDocument(document, all)
5594
- });
5595
- }
5596
- var DEFAULT_EDITOR_CONFIG = createEmailEditorConfig();
5597
-
5598
- // src/shell/useEditorSession.ts
5599
- import { useCallback as useCallback3, useEffect as useEffect9, useLayoutEffect as useLayoutEffect8, useMemo as useMemo5, useRef as useRef16, useState as useState17 } from "react";
5600
-
5601
5608
  // src/document/mutations.ts
5602
5609
  function moveItem(items, from, to) {
5603
5610
  const next = items.slice();
@@ -5666,68 +5673,215 @@ function updateBodySettings(document, patch) {
5666
5673
  });
5667
5674
  }
5668
5675
 
5676
+ // src/document/create-block.ts
5677
+ function createBlockNode(type, definitions) {
5678
+ var _a, _b;
5679
+ const definition = getBlockDefinition(type, definitions);
5680
+ if (!definition) return null;
5681
+ const controls = structuredClone(definition.readControls({}));
5682
+ let style = resolveBlockStyle(definition.defaultStyle, definition);
5683
+ for (const control of (_a = definition.styleControls) != null ? _a : []) {
5684
+ const [group, key] = control.id.split(".");
5685
+ const stored = (_b = definition.defaultStyle) == null ? void 0 : _b[group];
5686
+ if (control.defaultValue !== void 0 && (!stored || !(key in stored))) style = writeStyleValue(style, control.id, control.defaultValue);
5687
+ }
5688
+ return {
5689
+ id: generateId(type),
5690
+ type,
5691
+ controls,
5692
+ style,
5693
+ defaultData: definition.defaultData ? structuredClone(definition.defaultData) : void 0
5694
+ };
5695
+ }
5696
+
5697
+ // src/plugins/config.ts
5698
+ function buildEmailEditorConfig(options = {}) {
5699
+ var _a, _b, _c;
5700
+ const definitions = new Map(DEFAULT_BLOCKS.map((block) => [block.type, block]));
5701
+ const customTypes = /* @__PURE__ */ new Set();
5702
+ for (const plugin of (_a = options.plugins) != null ? _a : []) {
5703
+ if (customTypes.has(plugin.type)) throw new Error(`Duplicate block plugin: ${plugin.type}`);
5704
+ if (definitions.has(plugin.type) && !plugin.override) throw new Error(`Block ${plugin.type} already exists. Set override: true to replace it.`);
5705
+ customTypes.add(plugin.type);
5706
+ definitions.set(plugin.type, plugin);
5707
+ }
5708
+ const builtinTypes = new Set(DEFAULT_BLOCKS.map((block) => block.type));
5709
+ const visibleBuiltins = options.builtins === false ? [] : (_b = options.builtins) != null ? _b : [...builtinTypes];
5710
+ if (new Set(visibleBuiltins).size !== visibleBuiltins.length) throw new Error("Duplicate builtin in library order.");
5711
+ for (const type of visibleBuiltins) if (!builtinTypes.has(type)) throw new Error(`Unknown builtin block: ${type}`);
5712
+ const all = Object.freeze([...definitions.values()].map((plugin) => snapshotValue(plugin)));
5713
+ const registered = new Map(all.map((plugin) => [plugin.type, plugin]));
5714
+ return Object.freeze({
5715
+ definitions: all,
5716
+ library: Object.freeze([...visibleBuiltins.map((type) => registered.get(type)), ...((_c = options.plugins) != null ? _c : []).filter((plugin) => !builtinTypes.has(plugin.type)).map((plugin) => registered.get(plugin.type))]),
5717
+ pickImage: options.pickImage,
5718
+ createBlock: (type) => {
5719
+ const block = createBlockNode(type, all);
5720
+ if (!block) throw new Error(`Unknown block plugin: ${type}`);
5721
+ return block;
5722
+ },
5723
+ toHtml: (document, htmlOptions) => documentToHtml(document, all, htmlOptions),
5724
+ validate: (document) => validateDocument(document, all)
5725
+ });
5726
+ }
5727
+ var DEFAULT_EDITOR_CONFIG = buildEmailEditorConfig();
5728
+
5729
+ // src/shell/editor-store.ts
5730
+ var stores = /* @__PURE__ */ new WeakMap();
5731
+ function copyDocument(document) {
5732
+ return ensureEmailDocument(structuredClone(document));
5733
+ }
5734
+ function getEditorStore(editor) {
5735
+ const store = stores.get(editor);
5736
+ if (!store) throw new Error("EmailEditor requires an instance returned by useEmailEditor().");
5737
+ return store;
5738
+ }
5739
+ function createEditorStore(options) {
5740
+ var _a, _b, _c, _d;
5741
+ let callbacks = options;
5742
+ const base = copyDocument((_a = options.initialDocument) != null ? _a : createEmptyDocument());
5743
+ const initial = options.body === void 0 ? base : __spreadProps(__spreadValues({}, base), { body: mergeEmailBody(base.body, options.body) });
5744
+ let snapshot = {
5745
+ document: initial,
5746
+ config: (_b = options.config) != null ? _b : DEFAULT_EDITOR_CONFIG,
5747
+ variables: structuredClone((_c = options.variables) != null ? _c : EMPTY_VARIABLES),
5748
+ resetKey: generateId("session")
5749
+ };
5750
+ let state = snapshotValue({ document: copyDocument(initial) });
5751
+ let bodyKey = JSON.stringify(options.body);
5752
+ let variablesKey = JSON.stringify((_d = options.variables) != null ? _d : EMPTY_VARIABLES);
5753
+ const listeners = /* @__PURE__ */ new Set();
5754
+ const notify = () => listeners.forEach((listener) => listener());
5755
+ let optionsChanged = false;
5756
+ let pendingBodyDocument;
5757
+ const setSnapshot = (next) => {
5758
+ if (next.document !== snapshot.document) state = snapshotValue({ document: copyDocument(next.document) });
5759
+ snapshot = next;
5760
+ };
5761
+ const flushOptions = () => {
5762
+ const shouldNotify = optionsChanged;
5763
+ const document = pendingBodyDocument;
5764
+ const onChange = callbacks.onChange;
5765
+ optionsChanged = false;
5766
+ pendingBodyDocument = void 0;
5767
+ if (shouldNotify) notify();
5768
+ if (document) onChange == null ? void 0 : onChange(copyDocument(document));
5769
+ };
5770
+ const publish = (next) => {
5771
+ setSnapshot(next);
5772
+ notify();
5773
+ };
5774
+ const commit = (document, bodyChange = false) => {
5775
+ var _a2, _b2;
5776
+ flushOptions();
5777
+ if (document === snapshot.document) return;
5778
+ const next = copyDocument(document);
5779
+ publish(__spreadProps(__spreadValues({}, snapshot), { document: next }));
5780
+ (_a2 = callbacks.onChange) == null ? void 0 : _a2.call(callbacks, copyDocument(next));
5781
+ if (bodyChange) (_b2 = callbacks.onBodyChange) == null ? void 0 : _b2.call(callbacks, structuredClone(next.body));
5782
+ };
5783
+ const replace = (document) => {
5784
+ var _a2;
5785
+ flushOptions();
5786
+ const next = copyDocument(document);
5787
+ publish(__spreadProps(__spreadValues({}, snapshot), { document: next, resetKey: generateId("session") }));
5788
+ (_a2 = callbacks.onChange) == null ? void 0 : _a2.call(callbacks, copyDocument(next));
5789
+ };
5790
+ const getDocument = () => copyDocument(snapshot.document);
5791
+ const editor = Object.freeze({
5792
+ getDocument,
5793
+ getJson: () => JSON.stringify(getDocument(), null, 2),
5794
+ getHtml: () => snapshot.config.toHtml(getDocument(), { variables: structuredClone(snapshot.variables) }),
5795
+ getRenderedHtml: () => snapshot.config.toHtml(getDocument(), { variables: structuredClone(snapshot.variables), resolveHtmlTemplates: true }),
5796
+ getData: () => {
5797
+ const document = getDocument();
5798
+ const { config } = snapshot;
5799
+ const variables = structuredClone(snapshot.variables);
5800
+ return {
5801
+ document,
5802
+ json: JSON.stringify(document, null, 2),
5803
+ html: config.toHtml(document, { variables }),
5804
+ renderedHtml: config.toHtml(document, { variables, resolveHtmlTemplates: true })
5805
+ };
5806
+ },
5807
+ setDocument: replace,
5808
+ reset: () => replace(initial),
5809
+ validate: () => snapshot.config.validate(getDocument())
5810
+ });
5811
+ const store = {
5812
+ editor,
5813
+ subscribe: (listener) => {
5814
+ listeners.add(listener);
5815
+ return () => {
5816
+ listeners.delete(listener);
5817
+ };
5818
+ },
5819
+ getSnapshot: () => snapshot,
5820
+ getState: () => state,
5821
+ commit,
5822
+ flushOptions,
5823
+ updateOptions: (nextOptions) => {
5824
+ var _a2, _b2, _c2;
5825
+ callbacks = nextOptions;
5826
+ const nextVariablesKey = JSON.stringify((_a2 = nextOptions.variables) != null ? _a2 : EMPTY_VARIABLES);
5827
+ const nextBodyKey = JSON.stringify(nextOptions.body);
5828
+ let next = snapshot;
5829
+ const config = (_b2 = nextOptions.config) != null ? _b2 : DEFAULT_EDITOR_CONFIG;
5830
+ if (config !== next.config || nextVariablesKey !== variablesKey) {
5831
+ next = __spreadProps(__spreadValues({}, next), { config, variables: structuredClone((_c2 = nextOptions.variables) != null ? _c2 : EMPTY_VARIABLES) });
5832
+ }
5833
+ variablesKey = nextVariablesKey;
5834
+ let bodyChanged = false;
5835
+ if (bodyKey !== nextBodyKey && nextOptions.body !== void 0) {
5836
+ const body = mergeEmailBody(next.document.body, nextOptions.body);
5837
+ bodyChanged = !sameEmailBody(next.document.body, body);
5838
+ if (bodyChanged) next = __spreadProps(__spreadValues({}, next), { document: __spreadProps(__spreadValues({}, next.document), { body }) });
5839
+ }
5840
+ bodyKey = nextBodyKey;
5841
+ if (next !== snapshot) {
5842
+ setSnapshot(next);
5843
+ optionsChanged = true;
5844
+ }
5845
+ if (bodyChanged) pendingBodyDocument = next.document;
5846
+ }
5847
+ };
5848
+ stores.set(editor, store);
5849
+ return store;
5850
+ }
5851
+
5669
5852
  // src/shell/useEditorSession.ts
5670
- function useEditorSession({
5671
- initialDocument,
5672
- body,
5673
- config = DEFAULT_EDITOR_CONFIG,
5674
- variables = EMPTY_VARIABLES,
5675
- onChange,
5676
- onBodyChange
5677
- }) {
5853
+ function useEditorSession(editor) {
5678
5854
  var _a;
5855
+ const store = getEditorStore(editor);
5856
+ const { document, config, variables, resetKey } = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
5679
5857
  const definitions = config.definitions;
5680
- const [document, setDocument] = useState17(() => {
5681
- const base = ensureEmailDocument(
5682
- structuredClone(initialDocument != null ? initialDocument : createEmptyDocument())
5683
- );
5684
- if (body === void 0) return base;
5685
- return __spreadProps(__spreadValues({}, base), { body: mergeEmailBody(base.body, body) });
5686
- });
5687
5858
  const [selectedId, setSelectedId] = useState17(null);
5688
- const documentRef = useRef16(document);
5689
- useLayoutEffect8(() => {
5690
- documentRef.current = document;
5691
- }, [document]);
5692
- const commit = useCallback3(
5693
- (next) => {
5694
- documentRef.current = next;
5695
- setDocument(next);
5696
- onChange == null ? void 0 : onChange(structuredClone(next));
5697
- },
5698
- [onChange]
5699
- );
5700
- useEffect9(() => {
5701
- if (body === void 0) return;
5702
- const current = documentRef.current;
5703
- const nextBody = mergeEmailBody(current.body, body);
5704
- if (sameEmailBody(resolveEmailBody(current.body), nextBody)) return;
5705
- commit(__spreadProps(__spreadValues({}, current), { body: nextBody }));
5706
- }, [body, commit]);
5707
- const getDocument = useCallback3(
5708
- () => ensureEmailDocument(structuredClone(documentRef.current)),
5709
- []
5710
- );
5859
+ const [previousReset, setPreviousReset] = useState17({ editor, resetKey });
5860
+ if (previousReset.editor !== editor || previousReset.resetKey !== resetKey) {
5861
+ setPreviousReset({ editor, resetKey });
5862
+ setSelectedId(null);
5863
+ }
5864
+ const commit = store.commit;
5711
5865
  const selectBlock = useCallback3((id) => {
5712
5866
  setSelectedId(id);
5713
5867
  }, []);
5714
5868
  const insertBlock = useCallback3(
5715
5869
  (type, index) => {
5716
- const node = createBlockNode(type, definitions);
5870
+ const node = definitions.some((definition) => definition.type === type) ? config.createBlock(type) : null;
5717
5871
  if (!node) return false;
5718
- commit(insertBlockNode(documentRef.current, node, index));
5872
+ commit(insertBlockNode(store.getSnapshot().document, node, index));
5719
5873
  setSelectedId(node.id);
5720
5874
  return true;
5721
5875
  },
5722
- [commit, definitions]
5876
+ [commit, definitions, config, store]
5723
5877
  );
5724
5878
  const updateControls = useCallback3(
5725
5879
  (id, patch) => {
5726
- const current = documentRef.current;
5880
+ const current = store.getSnapshot().document;
5727
5881
  const next = updateBlockControls(current, id, patch);
5728
5882
  if (next !== current) commit(next);
5729
5883
  },
5730
- [commit]
5884
+ [commit, store]
5731
5885
  );
5732
5886
  const updateControl = useCallback3(
5733
5887
  (id, name, value) => updateControls(id, { [name]: value }),
@@ -5735,58 +5889,56 @@ function useEditorSession({
5735
5889
  );
5736
5890
  const updateStyle = useCallback3(
5737
5891
  (id, styleId, value) => {
5738
- commit(updateBlockStyle(documentRef.current, id, styleId, value));
5892
+ commit(updateBlockStyle(store.getSnapshot().document, id, styleId, value));
5739
5893
  },
5740
- [commit]
5894
+ [commit, store]
5741
5895
  );
5742
5896
  const updateBody = useCallback3(
5743
5897
  (patch) => {
5744
- const next = updateBodySettings(documentRef.current, patch);
5745
- commit(next);
5746
- onBodyChange == null ? void 0 : onBodyChange(structuredClone(next.body));
5898
+ const next = updateBodySettings(store.getSnapshot().document, patch);
5899
+ store.commit(next, true);
5747
5900
  },
5748
- [commit, onBodyChange]
5901
+ [store]
5749
5902
  );
5750
5903
  const moveBlock = useCallback3(
5751
5904
  (id, direction) => {
5752
- commit(moveBlockBy(documentRef.current, id, direction));
5905
+ commit(moveBlockBy(store.getSnapshot().document, id, direction));
5753
5906
  },
5754
- [commit]
5907
+ [commit, store]
5755
5908
  );
5756
5909
  const duplicateBlock = useCallback3(
5757
5910
  (id) => {
5758
- commit(duplicateBlockNode(documentRef.current, id));
5911
+ commit(duplicateBlockNode(store.getSnapshot().document, id));
5759
5912
  },
5760
- [commit]
5913
+ [commit, store]
5761
5914
  );
5762
5915
  const removeBlock = useCallback3(
5763
5916
  (id) => {
5764
- commit(removeBlockNode(documentRef.current, id));
5917
+ commit(removeBlockNode(store.getSnapshot().document, id));
5765
5918
  setSelectedId((current) => current === id ? null : current);
5766
5919
  },
5767
- [commit]
5920
+ [commit, store]
5768
5921
  );
5769
5922
  const reorderBlocks = useCallback3(
5770
5923
  (activeId, overId) => {
5771
- commit(reorderBlockNodes(documentRef.current, activeId, overId));
5924
+ commit(reorderBlockNodes(store.getSnapshot().document, activeId, overId));
5772
5925
  },
5773
- [commit]
5926
+ [commit, store]
5774
5927
  );
5775
5928
  const setSubject = useCallback3(
5776
5929
  (subject) => {
5777
- commit(withSubject(documentRef.current, subject.slice(0, SUBJECT_MAX_LENGTH)));
5930
+ commit(withSubject(store.getSnapshot().document, subject.slice(0, SUBJECT_MAX_LENGTH)));
5778
5931
  },
5779
- [commit]
5932
+ [commit, store]
5780
5933
  );
5781
- const resolvedDocument = useMemo5(() => ensureEmailDocument(document), [document]);
5782
- const selectedBlock = (_a = resolvedDocument.blocks.find((block) => block.id === selectedId)) != null ? _a : null;
5934
+ const selectedBlock = (_a = document.blocks.find((block) => block.id === selectedId)) != null ? _a : null;
5783
5935
  const selectedDefinition = selectedBlock ? getBlockDefinition(selectedBlock.type, definitions) : void 0;
5784
- return {
5785
- document: resolvedDocument,
5786
- getDocument,
5936
+ return __spreadProps(__spreadValues({}, editor), {
5937
+ document,
5787
5938
  definitions,
5788
5939
  config,
5789
5940
  variables,
5941
+ resetKey,
5790
5942
  selectedId,
5791
5943
  selectedBlock,
5792
5944
  selectedDefinition,
@@ -5801,166 +5953,152 @@ function useEditorSession({
5801
5953
  removeBlock,
5802
5954
  reorderBlocks,
5803
5955
  setSubject
5804
- };
5805
- }
5806
-
5807
- // src/shell/useEditorHandle.ts
5808
- import { useImperativeHandle } from "react";
5809
- function useEditorHandle(ref, { getDocument, definitions, variables }) {
5810
- useImperativeHandle(ref, () => ({
5811
- getData: () => {
5812
- const document = getDocument();
5813
- return {
5814
- document,
5815
- json: JSON.stringify(document, null, 2),
5816
- html: documentToHtml(document, definitions, { variables }),
5817
- renderedHtml: documentToHtml(document, definitions, {
5818
- resolveHtmlTemplates: true,
5819
- variables
5820
- })
5821
- };
5822
- },
5823
- getDocument,
5824
- getJson: () => JSON.stringify(getDocument(), null, 2),
5825
- getHtml: () => documentToHtml(getDocument(), definitions, { variables }),
5826
- getRenderedHtml: () => documentToHtml(getDocument(), definitions, {
5827
- resolveHtmlTemplates: true,
5828
- variables
5829
- })
5830
- }), [getDocument, definitions, variables]);
5956
+ });
5831
5957
  }
5832
5958
 
5833
5959
  // src/shell/EmailEditorShell.tsx
5834
- import { jsx as jsx58, jsxs as jsxs45 } from "react/jsx-runtime";
5835
- var EmailEditorShell = forwardRef2(
5836
- function EmailEditorShell2({
5837
- initialDocument,
5838
- body,
5839
- config,
5840
- variables = EMPTY_VARIABLES,
5841
- height = "100%",
5842
- onChange,
5843
- onBodyChange
5844
- }, ref) {
5845
- var _a;
5846
- const session = useEditorSession({
5847
- initialDocument,
5848
- body,
5849
- config,
5850
- variables,
5851
- onChange,
5852
- onBodyChange
5853
- });
5854
- useEditorHandle(ref, session);
5855
- const [activeRichText, setActiveRichText] = useState18(null);
5856
- const [viewMode, setViewMode] = useState18("edit");
5857
- const [device, setDevice] = useState18("desktop");
5858
- const [inspectorPanel, setInspectorPanel] = useState18("body");
5859
- const [previousSelectedId, setPreviousSelectedId] = useState18(session.selectedId);
5860
- if (previousSelectedId !== session.selectedId) {
5861
- setPreviousSelectedId(session.selectedId);
5862
- if (!session.selectedId) setInspectorPanel("body");
5960
+ import { jsx as jsx57, jsxs as jsxs44 } from "react/jsx-runtime";
5961
+ function EmailEditorShell({ editor, height = "100%" }) {
5962
+ var _a;
5963
+ const session = useEditorSession(editor);
5964
+ const [activeRichText, setActiveRichText] = useState18(null);
5965
+ const [viewMode, setViewMode] = useState18("edit");
5966
+ const [device, setDevice] = useState18("desktop");
5967
+ const [inspectorPanel, setInspectorPanel] = useState18("body");
5968
+ const [previousReset, setPreviousReset] = useState18({ editor, key: session.resetKey });
5969
+ if (previousReset.editor !== editor || previousReset.key !== session.resetKey) {
5970
+ setPreviousReset({ editor, key: session.resetKey });
5971
+ setActiveRichText(null);
5972
+ setInspectorPanel("body");
5973
+ }
5974
+ const [previousSelectedId, setPreviousSelectedId] = useState18(session.selectedId);
5975
+ if (previousSelectedId !== session.selectedId) {
5976
+ setPreviousSelectedId(session.selectedId);
5977
+ if (!session.selectedId) setInspectorPanel("body");
5978
+ }
5979
+ const selectBlock = (id) => {
5980
+ const hadSelection = session.selectedId !== null;
5981
+ session.selectBlock(id);
5982
+ if (!id) {
5983
+ setInspectorPanel("body");
5984
+ return;
5863
5985
  }
5864
- const selectBlock = (id) => {
5865
- const hadSelection = session.selectedId !== null;
5866
- session.selectBlock(id);
5867
- if (!id) {
5868
- setInspectorPanel("body");
5869
- return;
5870
- }
5871
- if (!hadSelection) setInspectorPanel("block");
5872
- };
5873
- const handleKeyDown = (event) => {
5874
- if (event.key !== "Escape") return;
5875
- const target = event.target;
5876
- const tag = target == null ? void 0 : target.tagName;
5877
- if ((target == null ? void 0 : target.isContentEditable) || tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
5878
- return;
5986
+ if (!hadSelection) setInspectorPanel("block");
5987
+ };
5988
+ const handleKeyDown = (event) => {
5989
+ if (event.key !== "Escape") return;
5990
+ const target = event.target;
5991
+ const tag = target == null ? void 0 : target.tagName;
5992
+ if ((target == null ? void 0 : target.isContentEditable) || tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
5993
+ return;
5994
+ }
5995
+ selectBlock(null);
5996
+ };
5997
+ const showCanvas = () => setViewMode("edit");
5998
+ return /* @__PURE__ */ jsx57(EditorConfigContext.Provider, { value: session.config, children: /* @__PURE__ */ jsx57(RichTextFocusContext.Provider, { value: { active: activeRichText, activate: setActiveRichText }, children: /* @__PURE__ */ jsxs44("div", { className: "ree-root", style: { height }, onKeyDown: handleKeyDown, children: [
5999
+ /* @__PURE__ */ jsx57(
6000
+ SubjectBar,
6001
+ {
6002
+ subject: (_a = session.document.subject) != null ? _a : "",
6003
+ variables: session.variables,
6004
+ onSubjectChange: session.setSubject
5879
6005
  }
5880
- selectBlock(null);
5881
- };
5882
- const showCanvas = () => setViewMode("edit");
5883
- return /* @__PURE__ */ jsx58(EditorConfigContext.Provider, { value: session.config, children: /* @__PURE__ */ jsx58(RichTextFocusContext.Provider, { value: { active: activeRichText, activate: setActiveRichText }, children: /* @__PURE__ */ jsxs45("div", { className: "ree-root", style: { height }, onKeyDown: handleKeyDown, children: [
5884
- /* @__PURE__ */ jsx58(
5885
- SubjectBar,
6006
+ ),
6007
+ /* @__PURE__ */ jsxs44("div", { className: "ree-body", children: [
6008
+ /* @__PURE__ */ jsxs44(
6009
+ EditorDnd,
5886
6010
  {
5887
- subject: (_a = session.document.subject) != null ? _a : "",
5888
- variables: session.variables,
5889
- onSubjectChange: session.setSubject
6011
+ blocks: session.document.blocks,
6012
+ definitions: session.definitions,
6013
+ onInsertBlock: (type, index) => {
6014
+ showCanvas();
6015
+ if (session.insertBlock(type, index)) setInspectorPanel("block");
6016
+ },
6017
+ onReorder: session.reorderBlocks,
6018
+ onLibraryDragStart: showCanvas,
6019
+ children: [
6020
+ /* @__PURE__ */ jsx57(
6021
+ BlockLibrary,
6022
+ {
6023
+ definitions: session.config.library,
6024
+ onAdd: (type) => {
6025
+ showCanvas();
6026
+ if (session.insertBlock(type)) setInspectorPanel("block");
6027
+ }
6028
+ }
6029
+ ),
6030
+ /* @__PURE__ */ jsx57(
6031
+ Stage,
6032
+ {
6033
+ session,
6034
+ viewMode,
6035
+ device,
6036
+ onViewModeChange: setViewMode,
6037
+ onDeviceChange: setDevice,
6038
+ onSelectBlock: selectBlock
6039
+ }
6040
+ )
6041
+ ]
5890
6042
  }
5891
6043
  ),
5892
- /* @__PURE__ */ jsxs45("div", { className: "ree-body", children: [
5893
- /* @__PURE__ */ jsxs45(
5894
- EditorDnd,
5895
- {
5896
- blocks: session.document.blocks,
5897
- definitions: session.definitions,
5898
- onInsertBlock: (type, index) => {
5899
- showCanvas();
5900
- if (session.insertBlock(type, index)) setInspectorPanel("block");
5901
- },
5902
- onReorder: session.reorderBlocks,
5903
- onLibraryDragStart: showCanvas,
5904
- children: [
5905
- /* @__PURE__ */ jsx58(
5906
- BlockLibrary,
5907
- {
5908
- definitions: session.config.library,
5909
- onAdd: (type) => {
5910
- showCanvas();
5911
- if (session.insertBlock(type)) setInspectorPanel("block");
5912
- }
5913
- }
5914
- ),
5915
- /* @__PURE__ */ jsx58(
5916
- Stage,
5917
- {
5918
- session,
5919
- viewMode,
5920
- device,
5921
- onViewModeChange: setViewMode,
5922
- onDeviceChange: setDevice,
5923
- onSelectBlock: selectBlock
5924
- }
5925
- )
5926
- ]
5927
- }
5928
- ),
5929
- /* @__PURE__ */ jsx58(
5930
- Inspector,
5931
- {
5932
- panel: inspectorPanel,
5933
- body: session.document.body,
5934
- block: session.selectedBlock,
5935
- definition: session.selectedDefinition,
5936
- variables: session.variables,
5937
- onPanelChange: setInspectorPanel,
5938
- onCommitBody: session.updateBody,
5939
- onCommitControl: session.updateControl,
5940
- onCommitControls: session.updateControls,
5941
- onCommitStyle: session.updateStyle
5942
- }
5943
- )
5944
- ] })
5945
- ] }) }) });
5946
- }
5947
- );
6044
+ /* @__PURE__ */ jsx57(
6045
+ Inspector,
6046
+ {
6047
+ panel: inspectorPanel,
6048
+ body: session.document.body,
6049
+ block: session.selectedBlock,
6050
+ definition: session.selectedDefinition,
6051
+ variables: session.variables,
6052
+ onPanelChange: setInspectorPanel,
6053
+ onCommitBody: session.updateBody,
6054
+ onCommitControl: session.updateControl,
6055
+ onCommitControls: session.updateControls,
6056
+ onCommitStyle: session.updateStyle
6057
+ }
6058
+ )
6059
+ ] })
6060
+ ] }, session.resetKey) }) });
6061
+ }
5948
6062
 
5949
6063
  // src/EmailEditor.tsx
5950
- import { jsx as jsx59 } from "react/jsx-runtime";
5951
- var EmailEditor = forwardRef3(
5952
- function EmailEditor2(props, ref) {
5953
- return /* @__PURE__ */ jsx59(EmailEditorShell, __spreadProps(__spreadValues({}, props), { ref }));
5954
- }
5955
- );
6064
+ import { jsx as jsx58 } from "react/jsx-runtime";
6065
+ function EmailEditor(props) {
6066
+ return /* @__PURE__ */ jsx58(EmailEditorShell, __spreadValues({}, props));
6067
+ }
6068
+
6069
+ // src/plugins/useEmailEditorConfig.ts
6070
+ import { useMemo as useMemo5 } from "react";
6071
+ function useEmailEditorConfig(options = {}) {
6072
+ const { plugins, builtins, pickImage } = options;
6073
+ return useMemo5(() => buildEmailEditorConfig({ plugins, builtins, pickImage }), [plugins, builtins, pickImage]);
6074
+ }
6075
+
6076
+ // src/shell/useEmailEditor.ts
6077
+ import { useEffect as useEffect8, useInsertionEffect, useLayoutEffect as useLayoutEffect9, useState as useState19 } from "react";
6078
+ var useOptionsEffect = typeof window === "undefined" ? useEffect8 : useLayoutEffect9;
6079
+ function useEmailEditor(options = {}) {
6080
+ const [store] = useState19(() => createEditorStore(options));
6081
+ useInsertionEffect(() => {
6082
+ store.updateOptions(options);
6083
+ });
6084
+ useOptionsEffect(store.flushOptions);
6085
+ return store.editor;
6086
+ }
6087
+
6088
+ // src/shell/useEmailEditorState.ts
6089
+ import { useSyncExternalStore as useSyncExternalStore2 } from "react";
6090
+ function useEmailEditorState(editor) {
6091
+ const store = getEditorStore(editor);
6092
+ return useSyncExternalStore2(store.subscribe, store.getState, store.getState);
6093
+ }
5956
6094
 
5957
6095
  // src/plugins/components/ControlEditor.tsx
5958
- import { jsx as jsx60 } from "react/jsx-runtime";
6096
+ import { jsx as jsx59 } from "react/jsx-runtime";
5959
6097
  function ControlEditor({ control }) {
5960
6098
  const context = usePluginEditor();
5961
6099
  const definition = context.controlDefinitions.find((item) => item.name === control);
5962
6100
  if (!definition) throw new Error(`Unknown plugin control: ${control}`);
5963
- return /* @__PURE__ */ jsx60(ControlRenderer, { control: definition, value: context.controls[control], body: context.body, variables: context.variables, onChange: (value) => context.setControl(control, value) });
6101
+ return /* @__PURE__ */ jsx59(ControlRenderer, { control: definition, value: context.controls[control], body: context.body, variables: context.variables, onChange: (value) => context.setControl(control, value) });
5964
6102
  }
5965
6103
  export {
5966
6104
  BLOCK_TYPES,
@@ -5991,7 +6129,6 @@ export {
5991
6129
  createDecorationControls,
5992
6130
  createDimensionControls,
5993
6131
  createEmailDocument,
5994
- createEmailEditorConfig,
5995
6132
  createTypographyControls,
5996
6133
  defineBlock,
5997
6134
  documentToHtml,
@@ -6002,5 +6139,8 @@ export {
6002
6139
  renderBlockContent,
6003
6140
  renderTemplatePreview,
6004
6141
  resolveEmailBody,
6142
+ useEmailEditor,
6143
+ useEmailEditorConfig,
6144
+ useEmailEditorState,
6005
6145
  variablesToTemplateData
6006
6146
  };