@standhigher/react-email-editor 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,
@@ -4638,8 +4635,10 @@ function renderTemplatePreview(html, defaultData) {
4638
4635
  }
4639
4636
 
4640
4637
  // src/render/block-html.ts
4641
- function normalizeBlockRenderOptions(options) {
4642
- return typeof options === "boolean" ? { resolveHtmlTemplate: options } : options;
4638
+ function assertBlockRenderOptions(options) {
4639
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
4640
+ throw new TypeError("Block render options must be an object.");
4641
+ }
4643
4642
  }
4644
4643
  function renderPreparedBlock(context, definition) {
4645
4644
  try {
@@ -4712,23 +4711,23 @@ function mergeTemplateData(variables, defaultData) {
4712
4711
  }
4713
4712
  function renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
4714
4713
  var _a;
4715
- const normalizedOptions = normalizeBlockRenderOptions(options);
4714
+ assertBlockRenderOptions(options);
4716
4715
  const rendered = renderBlock(
4717
4716
  node,
4718
4717
  definitions,
4719
4718
  mode,
4720
- (_a = normalizedOptions.body) != null ? _a : DEFAULT_EMAIL_BODY
4719
+ (_a = options.body) != null ? _a : DEFAULT_EMAIL_BODY
4721
4720
  );
4722
- return resolveBlockContent(rendered, node, normalizedOptions);
4721
+ return resolveBlockContent(rendered, node, options);
4723
4722
  }
4724
- function resolveBlockContent(rendered, node, normalizedOptions) {
4723
+ function resolveBlockContent(rendered, node, options) {
4725
4724
  var _a, _b;
4726
- if (!normalizedOptions.resolveHtmlTemplate) {
4727
- return applyListedVariableDots(rendered, (_a = normalizedOptions.variables) != null ? _a : []);
4725
+ if (!options.resolveHtmlTemplate) {
4726
+ return applyListedVariableDots(rendered, (_a = options.variables) != null ? _a : []);
4728
4727
  }
4729
4728
  return renderTemplatePreview(
4730
4729
  rendered,
4731
- (_b = normalizedOptions.templateData) != null ? _b : node.defaultData
4730
+ (_b = options.templateData) != null ? _b : node.defaultData
4732
4731
  );
4733
4732
  }
4734
4733
  function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body = DEFAULT_EMAIL_BODY) {
@@ -4742,10 +4741,10 @@ function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body =
4742
4741
  }
4743
4742
  function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
4744
4743
  var _a, _b, _c;
4745
- const normalizedOptions = normalizeBlockRenderOptions(options);
4746
- const body = (_a = normalizedOptions.body) != null ? _a : DEFAULT_EMAIL_BODY;
4744
+ assertBlockRenderOptions(options);
4745
+ const body = (_a = options.body) != null ? _a : DEFAULT_EMAIL_BODY;
4747
4746
  const style = resolveBlockStyle(node.style, getBlockDefinition(node.type, definitions));
4748
- const inner = renderBlockContent(node, definitions, mode, __spreadProps(__spreadValues({}, normalizedOptions), {
4747
+ const inner = renderBlockContent(node, definitions, mode, __spreadProps(__spreadValues({}, options), {
4749
4748
  body
4750
4749
  }));
4751
4750
  const height = cssPx((_b = style.dimension) == null ? void 0 : _b.height);
@@ -4785,8 +4784,8 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
4785
4784
  const frame = frameRef.current;
4786
4785
  const document = frame == null ? void 0 : frame.contentDocument;
4787
4786
  if (!container || !frame || !(document == null ? void 0 : document.body)) return;
4788
- const window = document.defaultView;
4789
- if (!window) return;
4787
+ const window2 = document.defaultView;
4788
+ if (!window2) return;
4790
4789
  let animationFrame = 0;
4791
4790
  const fit = () => {
4792
4791
  const availableWidth = container.clientWidth;
@@ -4798,12 +4797,12 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
4798
4797
  container.style.height = `${contentHeight}px`;
4799
4798
  };
4800
4799
  const scheduleFit = () => {
4801
- window.cancelAnimationFrame(animationFrame);
4802
- animationFrame = window.requestAnimationFrame(fit);
4800
+ window2.cancelAnimationFrame(animationFrame);
4801
+ animationFrame = window2.requestAnimationFrame(fit);
4803
4802
  };
4804
- const observer = new window.ResizeObserver(scheduleFit);
4803
+ const observer = new window2.ResizeObserver(scheduleFit);
4805
4804
  let previousWidth = container.clientWidth;
4806
- const containerObserver = new window.ResizeObserver(() => {
4805
+ const containerObserver = new window2.ResizeObserver(() => {
4807
4806
  if (container.clientWidth === previousWidth) return;
4808
4807
  previousWidth = container.clientWidth;
4809
4808
  scheduleFit();
@@ -4816,7 +4815,7 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
4816
4815
  observer.disconnect();
4817
4816
  containerObserver.disconnect();
4818
4817
  document.removeEventListener("load", scheduleFit, true);
4819
- window.cancelAnimationFrame(animationFrame);
4818
+ window2.cancelAnimationFrame(animationFrame);
4820
4819
  };
4821
4820
  }, []);
4822
4821
  useEffect6(() => () => {
@@ -5420,13 +5419,13 @@ function Stage({
5420
5419
  ] });
5421
5420
  }
5422
5421
 
5422
+ // src/shell/SubjectBar.tsx
5423
+ import { useId as useId13, useRef as useRef15 } from "react";
5424
+
5423
5425
  // src/shell/constants.ts
5424
5426
  var EMPTY_VARIABLES = [];
5425
5427
  var PERSONALIZATION_HINT = "Personalization fields will appear blank if no data is available.";
5426
5428
 
5427
- // src/shell/SubjectBar.tsx
5428
- import { useId as useId13, useRef as useRef15 } from "react";
5429
-
5430
5429
  // src/shell/PersonalizationMenu.tsx
5431
5430
  import { useEffect as useEffect8, useRef as useRef14, useState as useState16 } from "react";
5432
5431
  import { jsx as jsx56, jsxs as jsxs43 } from "react/jsx-runtime";
@@ -5488,6 +5487,7 @@ function SubjectBar({
5488
5487
  onSubjectChange
5489
5488
  }) {
5490
5489
  const inputRef = useRef15(null);
5490
+ const subjectId = useId13();
5491
5491
  const countId = useId13();
5492
5492
  const insertVariable = (token) => {
5493
5493
  var _a, _b;
@@ -5507,12 +5507,12 @@ function SubjectBar({
5507
5507
  });
5508
5508
  };
5509
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" }),
5510
+ /* @__PURE__ */ jsx57("label", { className: "ree-subject-label", htmlFor: subjectId, children: "Email subject" }),
5511
5511
  /* @__PURE__ */ jsxs44("div", { className: "ree-subject-field", children: [
5512
5512
  /* @__PURE__ */ jsx57(
5513
5513
  TextInput,
5514
5514
  {
5515
- id: "ree-email-subject",
5515
+ id: subjectId,
5516
5516
  ref: inputRef,
5517
5517
  className: "ree-subject-input",
5518
5518
  value: subject,
@@ -5534,6 +5534,9 @@ function SubjectBar({
5534
5534
  ] });
5535
5535
  }
5536
5536
 
5537
+ // src/shell/useEditorSession.ts
5538
+ import { useCallback as useCallback3, useState as useState17, useSyncExternalStore } from "react";
5539
+
5537
5540
  // src/utils/id.ts
5538
5541
  function generateId(prefix = "block") {
5539
5542
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -5542,62 +5545,6 @@ function generateId(prefix = "block") {
5542
5545
  return `${prefix}-${Math.random().toString(36).slice(2, 10)}`;
5543
5546
  }
5544
5547
 
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
5548
  // src/document/mutations.ts
5602
5549
  function moveItem(items, from, to) {
5603
5550
  const next = items.slice();
@@ -5666,68 +5613,215 @@ function updateBodySettings(document, patch) {
5666
5613
  });
5667
5614
  }
5668
5615
 
5616
+ // src/document/create-block.ts
5617
+ function createBlockNode(type, definitions) {
5618
+ var _a, _b;
5619
+ const definition = getBlockDefinition(type, definitions);
5620
+ if (!definition) return null;
5621
+ const controls = structuredClone(definition.readControls({}));
5622
+ let style = resolveBlockStyle(definition.defaultStyle, definition);
5623
+ for (const control of (_a = definition.styleControls) != null ? _a : []) {
5624
+ const [group, key] = control.id.split(".");
5625
+ const stored = (_b = definition.defaultStyle) == null ? void 0 : _b[group];
5626
+ if (control.defaultValue !== void 0 && (!stored || !(key in stored))) style = writeStyleValue(style, control.id, control.defaultValue);
5627
+ }
5628
+ return {
5629
+ id: generateId(type),
5630
+ type,
5631
+ controls,
5632
+ style,
5633
+ defaultData: definition.defaultData ? structuredClone(definition.defaultData) : void 0
5634
+ };
5635
+ }
5636
+
5637
+ // src/plugins/config.ts
5638
+ function buildEmailEditorConfig(options = {}) {
5639
+ var _a, _b, _c;
5640
+ const definitions = new Map(DEFAULT_BLOCKS.map((block) => [block.type, block]));
5641
+ const customTypes = /* @__PURE__ */ new Set();
5642
+ for (const plugin of (_a = options.plugins) != null ? _a : []) {
5643
+ if (customTypes.has(plugin.type)) throw new Error(`Duplicate block plugin: ${plugin.type}`);
5644
+ if (definitions.has(plugin.type) && !plugin.override) throw new Error(`Block ${plugin.type} already exists. Set override: true to replace it.`);
5645
+ customTypes.add(plugin.type);
5646
+ definitions.set(plugin.type, plugin);
5647
+ }
5648
+ const builtinTypes = new Set(DEFAULT_BLOCKS.map((block) => block.type));
5649
+ const visibleBuiltins = options.builtins === false ? [] : (_b = options.builtins) != null ? _b : [...builtinTypes];
5650
+ if (new Set(visibleBuiltins).size !== visibleBuiltins.length) throw new Error("Duplicate builtin in library order.");
5651
+ for (const type of visibleBuiltins) if (!builtinTypes.has(type)) throw new Error(`Unknown builtin block: ${type}`);
5652
+ const all = Object.freeze([...definitions.values()].map((plugin) => snapshotValue(plugin)));
5653
+ const registered = new Map(all.map((plugin) => [plugin.type, plugin]));
5654
+ return Object.freeze({
5655
+ definitions: all,
5656
+ 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))]),
5657
+ pickImage: options.pickImage,
5658
+ createBlock: (type) => {
5659
+ const block = createBlockNode(type, all);
5660
+ if (!block) throw new Error(`Unknown block plugin: ${type}`);
5661
+ return block;
5662
+ },
5663
+ toHtml: (document, htmlOptions) => documentToHtml(document, all, htmlOptions),
5664
+ validate: (document) => validateDocument(document, all)
5665
+ });
5666
+ }
5667
+ var DEFAULT_EDITOR_CONFIG = buildEmailEditorConfig();
5668
+
5669
+ // src/shell/editor-store.ts
5670
+ var stores = /* @__PURE__ */ new WeakMap();
5671
+ function copyDocument(document) {
5672
+ return ensureEmailDocument(structuredClone(document));
5673
+ }
5674
+ function getEditorStore(editor) {
5675
+ const store = stores.get(editor);
5676
+ if (!store) throw new Error("EmailEditor requires an instance returned by useEmailEditor().");
5677
+ return store;
5678
+ }
5679
+ function createEditorStore(options) {
5680
+ var _a, _b, _c, _d;
5681
+ let callbacks = options;
5682
+ const base = copyDocument((_a = options.initialDocument) != null ? _a : createEmptyDocument());
5683
+ const initial = options.body === void 0 ? base : __spreadProps(__spreadValues({}, base), { body: mergeEmailBody(base.body, options.body) });
5684
+ let snapshot = {
5685
+ document: initial,
5686
+ config: (_b = options.config) != null ? _b : DEFAULT_EDITOR_CONFIG,
5687
+ variables: structuredClone((_c = options.variables) != null ? _c : EMPTY_VARIABLES),
5688
+ resetKey: generateId("session")
5689
+ };
5690
+ let state = snapshotValue({ document: copyDocument(initial) });
5691
+ let bodyKey = JSON.stringify(options.body);
5692
+ let variablesKey = JSON.stringify((_d = options.variables) != null ? _d : EMPTY_VARIABLES);
5693
+ const listeners = /* @__PURE__ */ new Set();
5694
+ const notify = () => listeners.forEach((listener) => listener());
5695
+ let optionsChanged = false;
5696
+ let pendingBodyDocument;
5697
+ const setSnapshot = (next) => {
5698
+ if (next.document !== snapshot.document) state = snapshotValue({ document: copyDocument(next.document) });
5699
+ snapshot = next;
5700
+ };
5701
+ const flushOptions = () => {
5702
+ const shouldNotify = optionsChanged;
5703
+ const document = pendingBodyDocument;
5704
+ const onChange = callbacks.onChange;
5705
+ optionsChanged = false;
5706
+ pendingBodyDocument = void 0;
5707
+ if (shouldNotify) notify();
5708
+ if (document) onChange == null ? void 0 : onChange(copyDocument(document));
5709
+ };
5710
+ const publish = (next) => {
5711
+ setSnapshot(next);
5712
+ notify();
5713
+ };
5714
+ const commit = (document, bodyChange = false) => {
5715
+ var _a2, _b2;
5716
+ flushOptions();
5717
+ if (document === snapshot.document) return;
5718
+ const next = copyDocument(document);
5719
+ publish(__spreadProps(__spreadValues({}, snapshot), { document: next }));
5720
+ (_a2 = callbacks.onChange) == null ? void 0 : _a2.call(callbacks, copyDocument(next));
5721
+ if (bodyChange) (_b2 = callbacks.onBodyChange) == null ? void 0 : _b2.call(callbacks, structuredClone(next.body));
5722
+ };
5723
+ const replace = (document) => {
5724
+ var _a2;
5725
+ flushOptions();
5726
+ const next = copyDocument(document);
5727
+ publish(__spreadProps(__spreadValues({}, snapshot), { document: next, resetKey: generateId("session") }));
5728
+ (_a2 = callbacks.onChange) == null ? void 0 : _a2.call(callbacks, copyDocument(next));
5729
+ };
5730
+ const getDocument = () => copyDocument(snapshot.document);
5731
+ const editor = Object.freeze({
5732
+ getDocument,
5733
+ getJson: () => JSON.stringify(getDocument(), null, 2),
5734
+ getHtml: () => snapshot.config.toHtml(getDocument(), { variables: structuredClone(snapshot.variables) }),
5735
+ getRenderedHtml: () => snapshot.config.toHtml(getDocument(), { variables: structuredClone(snapshot.variables), resolveHtmlTemplates: true }),
5736
+ getData: () => {
5737
+ const document = getDocument();
5738
+ const { config } = snapshot;
5739
+ const variables = structuredClone(snapshot.variables);
5740
+ return {
5741
+ document,
5742
+ json: JSON.stringify(document, null, 2),
5743
+ html: config.toHtml(document, { variables }),
5744
+ renderedHtml: config.toHtml(document, { variables, resolveHtmlTemplates: true })
5745
+ };
5746
+ },
5747
+ setDocument: replace,
5748
+ reset: () => replace(initial),
5749
+ validate: () => snapshot.config.validate(getDocument())
5750
+ });
5751
+ const store = {
5752
+ editor,
5753
+ subscribe: (listener) => {
5754
+ listeners.add(listener);
5755
+ return () => {
5756
+ listeners.delete(listener);
5757
+ };
5758
+ },
5759
+ getSnapshot: () => snapshot,
5760
+ getState: () => state,
5761
+ commit,
5762
+ flushOptions,
5763
+ updateOptions: (nextOptions) => {
5764
+ var _a2, _b2, _c2;
5765
+ callbacks = nextOptions;
5766
+ const nextVariablesKey = JSON.stringify((_a2 = nextOptions.variables) != null ? _a2 : EMPTY_VARIABLES);
5767
+ const nextBodyKey = JSON.stringify(nextOptions.body);
5768
+ let next = snapshot;
5769
+ const config = (_b2 = nextOptions.config) != null ? _b2 : DEFAULT_EDITOR_CONFIG;
5770
+ if (config !== next.config || nextVariablesKey !== variablesKey) {
5771
+ next = __spreadProps(__spreadValues({}, next), { config, variables: structuredClone((_c2 = nextOptions.variables) != null ? _c2 : EMPTY_VARIABLES) });
5772
+ }
5773
+ variablesKey = nextVariablesKey;
5774
+ let bodyChanged = false;
5775
+ if (bodyKey !== nextBodyKey && nextOptions.body !== void 0) {
5776
+ const body = mergeEmailBody(next.document.body, nextOptions.body);
5777
+ bodyChanged = !sameEmailBody(next.document.body, body);
5778
+ if (bodyChanged) next = __spreadProps(__spreadValues({}, next), { document: __spreadProps(__spreadValues({}, next.document), { body }) });
5779
+ }
5780
+ bodyKey = nextBodyKey;
5781
+ if (next !== snapshot) {
5782
+ setSnapshot(next);
5783
+ optionsChanged = true;
5784
+ }
5785
+ if (bodyChanged) pendingBodyDocument = next.document;
5786
+ }
5787
+ };
5788
+ stores.set(editor, store);
5789
+ return store;
5790
+ }
5791
+
5669
5792
  // 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
- }) {
5793
+ function useEditorSession(editor) {
5678
5794
  var _a;
5795
+ const store = getEditorStore(editor);
5796
+ const { document, config, variables, resetKey } = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
5679
5797
  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
5798
  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
- );
5799
+ const [previousReset, setPreviousReset] = useState17({ editor, resetKey });
5800
+ if (previousReset.editor !== editor || previousReset.resetKey !== resetKey) {
5801
+ setPreviousReset({ editor, resetKey });
5802
+ setSelectedId(null);
5803
+ }
5804
+ const commit = store.commit;
5711
5805
  const selectBlock = useCallback3((id) => {
5712
5806
  setSelectedId(id);
5713
5807
  }, []);
5714
5808
  const insertBlock = useCallback3(
5715
5809
  (type, index) => {
5716
- const node = createBlockNode(type, definitions);
5810
+ const node = definitions.some((definition) => definition.type === type) ? config.createBlock(type) : null;
5717
5811
  if (!node) return false;
5718
- commit(insertBlockNode(documentRef.current, node, index));
5812
+ commit(insertBlockNode(store.getSnapshot().document, node, index));
5719
5813
  setSelectedId(node.id);
5720
5814
  return true;
5721
5815
  },
5722
- [commit, definitions]
5816
+ [commit, definitions, config, store]
5723
5817
  );
5724
5818
  const updateControls = useCallback3(
5725
5819
  (id, patch) => {
5726
- const current = documentRef.current;
5820
+ const current = store.getSnapshot().document;
5727
5821
  const next = updateBlockControls(current, id, patch);
5728
5822
  if (next !== current) commit(next);
5729
5823
  },
5730
- [commit]
5824
+ [commit, store]
5731
5825
  );
5732
5826
  const updateControl = useCallback3(
5733
5827
  (id, name, value) => updateControls(id, { [name]: value }),
@@ -5735,58 +5829,56 @@ function useEditorSession({
5735
5829
  );
5736
5830
  const updateStyle = useCallback3(
5737
5831
  (id, styleId, value) => {
5738
- commit(updateBlockStyle(documentRef.current, id, styleId, value));
5832
+ commit(updateBlockStyle(store.getSnapshot().document, id, styleId, value));
5739
5833
  },
5740
- [commit]
5834
+ [commit, store]
5741
5835
  );
5742
5836
  const updateBody = useCallback3(
5743
5837
  (patch) => {
5744
- const next = updateBodySettings(documentRef.current, patch);
5745
- commit(next);
5746
- onBodyChange == null ? void 0 : onBodyChange(structuredClone(next.body));
5838
+ const next = updateBodySettings(store.getSnapshot().document, patch);
5839
+ store.commit(next, true);
5747
5840
  },
5748
- [commit, onBodyChange]
5841
+ [store]
5749
5842
  );
5750
5843
  const moveBlock = useCallback3(
5751
5844
  (id, direction) => {
5752
- commit(moveBlockBy(documentRef.current, id, direction));
5845
+ commit(moveBlockBy(store.getSnapshot().document, id, direction));
5753
5846
  },
5754
- [commit]
5847
+ [commit, store]
5755
5848
  );
5756
5849
  const duplicateBlock = useCallback3(
5757
5850
  (id) => {
5758
- commit(duplicateBlockNode(documentRef.current, id));
5851
+ commit(duplicateBlockNode(store.getSnapshot().document, id));
5759
5852
  },
5760
- [commit]
5853
+ [commit, store]
5761
5854
  );
5762
5855
  const removeBlock = useCallback3(
5763
5856
  (id) => {
5764
- commit(removeBlockNode(documentRef.current, id));
5857
+ commit(removeBlockNode(store.getSnapshot().document, id));
5765
5858
  setSelectedId((current) => current === id ? null : current);
5766
5859
  },
5767
- [commit]
5860
+ [commit, store]
5768
5861
  );
5769
5862
  const reorderBlocks = useCallback3(
5770
5863
  (activeId, overId) => {
5771
- commit(reorderBlockNodes(documentRef.current, activeId, overId));
5864
+ commit(reorderBlockNodes(store.getSnapshot().document, activeId, overId));
5772
5865
  },
5773
- [commit]
5866
+ [commit, store]
5774
5867
  );
5775
5868
  const setSubject = useCallback3(
5776
5869
  (subject) => {
5777
- commit(withSubject(documentRef.current, subject.slice(0, SUBJECT_MAX_LENGTH)));
5870
+ commit(withSubject(store.getSnapshot().document, subject.slice(0, SUBJECT_MAX_LENGTH)));
5778
5871
  },
5779
- [commit]
5872
+ [commit, store]
5780
5873
  );
5781
- const resolvedDocument = useMemo5(() => ensureEmailDocument(document), [document]);
5782
- const selectedBlock = (_a = resolvedDocument.blocks.find((block) => block.id === selectedId)) != null ? _a : null;
5874
+ const selectedBlock = (_a = document.blocks.find((block) => block.id === selectedId)) != null ? _a : null;
5783
5875
  const selectedDefinition = selectedBlock ? getBlockDefinition(selectedBlock.type, definitions) : void 0;
5784
- return {
5785
- document: resolvedDocument,
5786
- getDocument,
5876
+ return __spreadProps(__spreadValues({}, editor), {
5877
+ document,
5787
5878
  definitions,
5788
5879
  config,
5789
5880
  variables,
5881
+ resetKey,
5790
5882
  selectedId,
5791
5883
  selectedBlock,
5792
5884
  selectedDefinition,
@@ -5801,158 +5893,144 @@ function useEditorSession({
5801
5893
  removeBlock,
5802
5894
  reorderBlocks,
5803
5895
  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]);
5896
+ });
5831
5897
  }
5832
5898
 
5833
5899
  // src/shell/EmailEditorShell.tsx
5834
5900
  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");
5901
+ function EmailEditorShell({ editor, height = "100%" }) {
5902
+ var _a;
5903
+ const session = useEditorSession(editor);
5904
+ const [activeRichText, setActiveRichText] = useState18(null);
5905
+ const [viewMode, setViewMode] = useState18("edit");
5906
+ const [device, setDevice] = useState18("desktop");
5907
+ const [inspectorPanel, setInspectorPanel] = useState18("body");
5908
+ const [previousReset, setPreviousReset] = useState18({ editor, key: session.resetKey });
5909
+ if (previousReset.editor !== editor || previousReset.key !== session.resetKey) {
5910
+ setPreviousReset({ editor, key: session.resetKey });
5911
+ setActiveRichText(null);
5912
+ setInspectorPanel("body");
5913
+ }
5914
+ const [previousSelectedId, setPreviousSelectedId] = useState18(session.selectedId);
5915
+ if (previousSelectedId !== session.selectedId) {
5916
+ setPreviousSelectedId(session.selectedId);
5917
+ if (!session.selectedId) setInspectorPanel("body");
5918
+ }
5919
+ const selectBlock = (id) => {
5920
+ const hadSelection = session.selectedId !== null;
5921
+ session.selectBlock(id);
5922
+ if (!id) {
5923
+ setInspectorPanel("body");
5924
+ return;
5863
5925
  }
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;
5926
+ if (!hadSelection) setInspectorPanel("block");
5927
+ };
5928
+ const handleKeyDown = (event) => {
5929
+ if (event.key !== "Escape") return;
5930
+ const target = event.target;
5931
+ const tag = target == null ? void 0 : target.tagName;
5932
+ if ((target == null ? void 0 : target.isContentEditable) || tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
5933
+ return;
5934
+ }
5935
+ selectBlock(null);
5936
+ };
5937
+ const showCanvas = () => setViewMode("edit");
5938
+ 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: [
5939
+ /* @__PURE__ */ jsx58(
5940
+ SubjectBar,
5941
+ {
5942
+ subject: (_a = session.document.subject) != null ? _a : "",
5943
+ variables: session.variables,
5944
+ onSubjectChange: session.setSubject
5879
5945
  }
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: [
5946
+ ),
5947
+ /* @__PURE__ */ jsxs45("div", { className: "ree-body", children: [
5948
+ /* @__PURE__ */ jsxs45(
5949
+ EditorDnd,
5950
+ {
5951
+ blocks: session.document.blocks,
5952
+ definitions: session.definitions,
5953
+ onInsertBlock: (type, index) => {
5954
+ showCanvas();
5955
+ if (session.insertBlock(type, index)) setInspectorPanel("block");
5956
+ },
5957
+ onReorder: session.reorderBlocks,
5958
+ onLibraryDragStart: showCanvas,
5959
+ children: [
5960
+ /* @__PURE__ */ jsx58(
5961
+ BlockLibrary,
5962
+ {
5963
+ definitions: session.config.library,
5964
+ onAdd: (type) => {
5965
+ showCanvas();
5966
+ if (session.insertBlock(type)) setInspectorPanel("block");
5967
+ }
5968
+ }
5969
+ ),
5970
+ /* @__PURE__ */ jsx58(
5971
+ Stage,
5972
+ {
5973
+ session,
5974
+ viewMode,
5975
+ device,
5976
+ onViewModeChange: setViewMode,
5977
+ onDeviceChange: setDevice,
5978
+ onSelectBlock: selectBlock
5979
+ }
5980
+ )
5981
+ ]
5982
+ }
5983
+ ),
5884
5984
  /* @__PURE__ */ jsx58(
5885
- SubjectBar,
5985
+ Inspector,
5886
5986
  {
5887
- subject: (_a = session.document.subject) != null ? _a : "",
5987
+ panel: inspectorPanel,
5988
+ body: session.document.body,
5989
+ block: session.selectedBlock,
5990
+ definition: session.selectedDefinition,
5888
5991
  variables: session.variables,
5889
- onSubjectChange: session.setSubject
5992
+ onPanelChange: setInspectorPanel,
5993
+ onCommitBody: session.updateBody,
5994
+ onCommitControl: session.updateControl,
5995
+ onCommitControls: session.updateControls,
5996
+ onCommitStyle: session.updateStyle
5890
5997
  }
5891
- ),
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
- );
5998
+ )
5999
+ ] })
6000
+ ] }, session.resetKey) }) });
6001
+ }
5948
6002
 
5949
6003
  // src/EmailEditor.tsx
5950
6004
  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
- );
6005
+ function EmailEditor(props) {
6006
+ return /* @__PURE__ */ jsx59(EmailEditorShell, __spreadValues({}, props));
6007
+ }
6008
+
6009
+ // src/plugins/useEmailEditorConfig.ts
6010
+ import { useMemo as useMemo5 } from "react";
6011
+ function useEmailEditorConfig(options = {}) {
6012
+ const { plugins, builtins, pickImage } = options;
6013
+ return useMemo5(() => buildEmailEditorConfig({ plugins, builtins, pickImage }), [plugins, builtins, pickImage]);
6014
+ }
6015
+
6016
+ // src/shell/useEmailEditor.ts
6017
+ import { useEffect as useEffect9, useInsertionEffect, useLayoutEffect as useLayoutEffect8, useState as useState19 } from "react";
6018
+ var useOptionsEffect = typeof window === "undefined" ? useEffect9 : useLayoutEffect8;
6019
+ function useEmailEditor(options = {}) {
6020
+ const [store] = useState19(() => createEditorStore(options));
6021
+ useInsertionEffect(() => {
6022
+ store.updateOptions(options);
6023
+ });
6024
+ useOptionsEffect(store.flushOptions);
6025
+ return store.editor;
6026
+ }
6027
+
6028
+ // src/shell/useEmailEditorState.ts
6029
+ import { useSyncExternalStore as useSyncExternalStore2 } from "react";
6030
+ function useEmailEditorState(editor) {
6031
+ const store = getEditorStore(editor);
6032
+ return useSyncExternalStore2(store.subscribe, store.getState, store.getState);
6033
+ }
5956
6034
 
5957
6035
  // src/plugins/components/ControlEditor.tsx
5958
6036
  import { jsx as jsx60 } from "react/jsx-runtime";
@@ -5991,7 +6069,6 @@ export {
5991
6069
  createDecorationControls,
5992
6070
  createDimensionControls,
5993
6071
  createEmailDocument,
5994
- createEmailEditorConfig,
5995
6072
  createTypographyControls,
5996
6073
  defineBlock,
5997
6074
  documentToHtml,
@@ -6002,5 +6079,8 @@ export {
6002
6079
  renderBlockContent,
6003
6080
  renderTemplatePreview,
6004
6081
  resolveEmailBody,
6082
+ useEmailEditor,
6083
+ useEmailEditorConfig,
6084
+ useEmailEditorState,
6005
6085
  variablesToTemplateData
6006
6086
  };