@standhigher/react-email-editor 0.2.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.
@@ -0,0 +1,275 @@
1
+ # API 参考
2
+
3
+ [English](./api.md) | [返回 README](../README.zh-CN.md)
4
+
5
+ 所有下列 API 从 `@standhigher/react-email-editor` 导入;样式独立从 `@standhigher/react-email-editor/styles.css` 引入一次。发布包的 `dist/index.d.ts` 包含对外字段的英文 JSDoc,可在 IDE 中悬停查看字段作用与参数含义。[基础示例](./basic-examples.zh-CN.md)提供可直接组合的接入代码,[复杂示例](./advanced-examples.zh-CN.md)展示业务集成。
6
+
7
+ ## 编辑器组件与 Hooks
8
+
9
+ ```tsx
10
+ import { EmailEditor, useEmailEditor } from "@standhigher/react-email-editor";
11
+ import "@standhigher/react-email-editor/styles.css";
12
+
13
+ export function EmailPage() {
14
+ const editor = useEmailEditor();
15
+ return <EmailEditor editor={editor} height={720} />;
16
+ }
17
+ ```
18
+
19
+ ### EmailEditor
20
+
21
+ | 参数 | 类型 | 含义与默认值 |
22
+ | --- | --- | --- |
23
+ | `editor` | `EmailEditorInstance` | 必填,来自 `useEmailEditor()`。 |
24
+ | `height` | `number \| string` | 默认 `"100%"`;数字单位 px,百分比要求父容器有明确高度。 |
25
+
26
+ 组件只负责 UI;实例独立于 UI 挂载。不支持组件 ref、旧配置工厂或旧文档 Props。
27
+
28
+ ### useEmailEditor(options?) → EmailEditorInstance
29
+
30
+ 所有参数可选,默认空文档与全部内置区块。遵循 React Hook 调用规则。
31
+
32
+ | 参数 | 类型 | 含义 |
33
+ | --- | --- | --- |
34
+ | `initialDocument` | `EmailDocument` | 仅初始化读取;后续用 `setDocument()` 切换。 |
35
+ | `body` | `Partial<EmailBodySettings>` | 初始化覆盖主体设置;后续变化合并到当前文档,省略字段保持原值。 |
36
+ | `config` | `EmailEditorConfig` | `useEmailEditorConfig()` 的结果;更新不重置文档。 |
37
+ | `variables` | `Variable[]` | 插入与预览变量,默认 `[]`;后续更新影响预览和导出。 |
38
+ | `onChange` | `(document) => void` | 编辑、替换、重置及有效外部主体更新后返回独立快照;初始化不调用。 |
39
+ | `onBodyChange` | `(body) => void` | 仅编辑器内部修改主体设置时返回独立主体快照。 |
40
+
41
+ 实例引用稳定,多实例互相隔离。配置、变量、主体及回调在 React 提交阶段同步,被中断的 render 不改变已提交实例。子组件 layout effect 可读取更新后的实例;订阅通知和外部主体更新的 `onChange` 在 layout 阶段执行。仅使用该 Hook 的组件不会因文档输入重新渲染。
42
+
43
+ ### 实例方法
44
+
45
+ | 方法 | 参数 | 返回与行为 |
46
+ | --- | --- | --- |
47
+ | `getData()` | 无 | `EmailEditorData`:`document`、格式化 `json`、保留模板的 `html`、解析预览数据的 `renderedHtml`;任一渲染失败会抛错。 |
48
+ | `getDocument()` | 无 | 独立的 `EmailDocument` 快照,可持久化或修改后传给 `setDocument()`。 |
49
+ | `getJson()` | 无 | 格式化文档 JSON 字符串。 |
50
+ | `getHtml()` | 无 | 保留模板的完整邮件 HTML。 |
51
+ | `getRenderedHtml()` | 无 | 用当前变量与区块 `defaultData` 生成预览 HTML。 |
52
+ | `setDocument(document)` | v4 `EmailDocument` | 返回 `void`;复制并替换文档,清空选择、焦点和旧控件状态,触发 `onChange`。 |
53
+ | `reset()` | 无 | 返回 `void`;恢复初始化完成时的文档(含初始 `body` 覆盖),清空交互状态并触发 `onChange`。 |
54
+ | `validate()` | 无 | `ValidationIssue[]`;检查控件与缺失插件,不渲染 HTML,不代替渲染错误处理。 |
55
+
56
+ 导出数据与回调参数不会暴露可变内部状态。导出模板缺失插件、控件无效或渲染失败时抛错,应由应用捕获。
57
+
58
+ ### useEmailEditorState(editor) → EmailEditorState
59
+
60
+ 必填参数 `editor` 为 `useEmailEditor()` 创建的实例。返回缓存的 `{ document }` 深层只读快照,仅文档变化时通知。只在需要响应式展示的组件中使用;编辑通过实例方法提交。
61
+
62
+ ## 统一配置与插件
63
+
64
+ ### useEmailEditorConfig(options?) → EmailEditorConfig
65
+
66
+ | 参数 | 类型 | 默认与说明 |
67
+ | --- | --- | --- |
68
+ | `plugins` | `BlockPlugin[]` | `[]`,由 `defineBlock()` 创建。 |
69
+ | `builtins` | `false \| string[]` | 省略显示全部;`false` 隐藏全部内置区块;数组筛选并排序。隐藏不影响已有节点渲染。自定义类型追加到后面。 |
70
+ | `pickImage` | `(context: ImagePickerContext) => Promise<string \| null>` | 图片资源选择器;返回 URL,`null` 取消,抛错显示重试;`context` 提供 `value/label/signal`。 |
71
+
72
+ 插件建议模块级声明;依赖状态时用 `useMemo`。保持参数引用稳定可复用配置;内联数组不会重建编辑器或清空文档。配置返回值:
73
+
74
+ | 成员 | 参数与含义 |
75
+ | --- | --- |
76
+ | `definitions` | 全部注册插件的只读列表,包含隐藏的内置区块。 |
77
+ | `library` | 按展示顺序排列的可插入区块只读列表。 |
78
+ | `pickImage` | 注册的资源选择器。 |
79
+ | `createBlock(type)` | 已注册的类型字符串 → 独立默认 `EmailBlockNode`;未知类型抛错。 |
80
+ | `toHtml(document, options?)` | v4 文档与 `DocumentHtmlOptions` → 完整 HTML;自动使用当前注册表。 |
81
+ | `validate(document)` | 文档 → `ValidationIssue[]`,不渲染 HTML。 |
82
+
83
+ 重复自定义类型、重复或未知的 `builtins` 条目会抛错。覆盖内置区块必须声明 `override: true`。
84
+
85
+ ### defineBlock(input) → BlockPlugin<Schema, Type>
86
+
87
+ ```ts
88
+ import { defineBlock, EmailControls } from "@standhigher/react-email-editor";
89
+
90
+ export const coupon = defineBlock({
91
+ type: "shop.coupon",
92
+ title: "优惠券",
93
+ controls: { code: EmailControls.text({ label: "优惠码", defaultValue: "SAVE20" }) },
94
+ renderEmail: ({ controls, email }) => email.html`<p>${controls.code}</p>`,
95
+ });
96
+ ```
97
+
98
+ | `input` 参数 | 说明 |
99
+ | --- | --- |
100
+ | `type` / `title` | 必填非空字符串,分别为唯一类型与区块库标题。 |
101
+ | `renderEmail(ctx)` | 必填,返回内部 HTML 字符串或 `EmailMarkup`;`ctx` 包含 `block/controls/style/body/mode/email`。 |
102
+ | `controls` | 名称到 `EmailControls` 声明的映射,默认 `{}`;自动推导值类型与默认值。 |
103
+ | `icon` / `description` | 可选图标(ReactNode 或字符串)及说明。 |
104
+ | `override` | 替换内置同名类型时必需设为 `true`。 |
105
+ | `containerStyles` | 默认提供间距与外观;`false` 全禁用;`{ spacing?: boolean, appearance?: boolean }` 可分别禁用。 |
106
+ | `styleControls` | 额外样式能力;同 `id` 覆盖默认容器能力。 |
107
+ | `inspectorSections` | 检查器分组及内容、样式控件的排列。 |
108
+ | `defaultStyle` | 新节点的默认样式,优先于样式控件 `defaultValue`。 |
109
+ | `defaultData` | 新节点的模板预览数据。 |
110
+ | `Canvas` / `Inspector` | 可选 React 编辑组件,接收 `PluginEditorProps<ControlValues<Schema>>`。 |
111
+
112
+ `Schema` 为控件声明类型,`Type` 保留 `type` 字面量。声明会复制并冻结。多个 `inline` 富文本区域必须提供 `Canvas`。完整规则及继承的运行时成员见[自定义区块指南](./custom-blocks.zh-CN.md)。
113
+
114
+ ### EmailControls
115
+
116
+ 所有工厂接收一个参数对象并返回 `ControlDefinition<T>`。通用参数如下;不同控件只使用相关参数。
117
+
118
+ | 参数 | 作用 |
119
+ | --- | --- |
120
+ | `label` | 必填控件名称。 |
121
+ | `defaultValue` | 默认值;只补持久化的 `undefined`,不覆盖 `""`、`0`、`false`。 |
122
+ | `placeholder` / `rows` | 空输入提示 / 多行编辑区行数。 |
123
+ | `options` | `ControlOption[]`,下拉合法选项 `{ label, value }`。 |
124
+ | `min` / `max` / `step` / `unit` | 数字界限、步长、单位文案;列表的 min/max 限制条数。 |
125
+ | `profile` / `inline` | 富文本功能集 / 是否由默认画布直接编辑。 |
126
+ | `component` | 自定义 React 控件,接收 `ControlComponentProps<T>`。 |
127
+ | `validate(value)` | 已归一化值 → 错误字符串或 `undefined` 表示通过。 |
128
+
129
+ | 工厂 | 值与默认值 | 补充参数 |
130
+ | --- | --- | --- |
131
+ | `text/textarea/html/url/image/color/select` | `string`,默认 `""` | URL 和图片按协议校验,select 按 options 校验。 |
132
+ | `richText` | HTML `string`,默认 `"<p></p>"` | `profile: "text" \| "heading" \| "button"`,`inline`。 |
133
+ | `number` | `number`,默认 `0` | 非法值为 NaN 并校验失败。 |
134
+ | `boolean` | `boolean`,默认 `false` | 只接受真正的 boolean,其余归一化为 false。 |
135
+ | `list` | 推导的行对象数组,默认 `[]` | 必填 `controls: ControlSchema`;递归解析和校验。 |
136
+ | `custom<T>` | 由调用方决定 | 必填 `defaultValue`、纯函数 `read(value: unknown): T` 和 `component`;值应可 JSON 持久化。 |
137
+
138
+ ### 插件 React 编辑组件
139
+
140
+ `PluginEditorProps` 提供 `block/controls/style/body/variables` 快照、`selected`、`renderedHtml`、`select()`、`setControl(name, value)`、`setControls(patch)` 和 `setStyle(id, value)`。批量更新浅合并顶层控件,触发一次文档通知;嵌套对象需提交完整新值。`renderedHtml` 在 Canvas 为解析后的内部 HTML,在 Inspector 为空。
141
+
142
+ | 组件 | 参数 |
143
+ | --- | --- |
144
+ | `ControlEditor` | 必填 `control: string`,复用已声明控件及其校验和资源适配。未知控件抛错。 |
145
+ | `EditableRichText` | 必填 `control: string` 指向 richText;可选 `profile` 和 `placeholder` 覆盖声明。 |
146
+
147
+ 以上两个组件仅在插件 Canvas / Inspector 上下文中使用。
148
+
149
+ ### 样式控件工厂
150
+
151
+ `createDimensionControls(overrides?)`、`createDecorationControls(overrides?)`、`createTypographyControls(overrides?)` 返回按能力名索引的独立 `StyleControlConfig` 对象。参数是 `Partial<Record<能力名, Partial<StyleControlConfig>>>`,用于覆盖名称、默认值、单位、范围等;将选中的能力放入插件 `styleControls`。
152
+
153
+ | 工厂 / 名称类型 | 可选能力名 |
154
+ | --- | --- |
155
+ | `createDimensionControls` / `DimensionControlName` | `padding/margin/height`。 |
156
+ | `createDecorationControls` / `DecorationControlName` | `backgroundColor/borderWidth/borderStyle/borderColor/borderRadius`。 |
157
+ | `createTypographyControls` / `TypographyControlName` | `fontFamily/fontSize/fontWeight/lineHeight/color/textAlign/paragraphSpacing/listSpacing/listIndent/linkColor/linkDecoration`。 |
158
+
159
+ ## 文档模型与创建
160
+
161
+ | API | 参数 | 返回与边界 |
162
+ | --- | --- | --- |
163
+ | `createEmailDocument(blocks = [])` | 有序 `EmailBlockNode[]` | v4 文档与完整默认主体。传入数组直接作为 blocks,不负责深拷贝。 |
164
+ | `resolveEmailBody(body?)` | `Partial<EmailBodySettings> \| null` | 完整并归一化的主体设置;非法值回退、宽度限制在 320–900px。 |
165
+ | `createBlockNode(type, definitions?)` | 类型与可选注册表,默认内置定义 | 独立默认节点;未知类型返回 `null`。业务插件优先用 `config.createBlock()`。 |
166
+ | `getBlockDefinition(type, definitions?)` | 类型与可选注册表,默认内置定义 | 匹配的运行时定义或 `undefined`。不要修改配置中的冻结定义。 |
167
+ | `generateId(prefix = "block")` | ID 前缀字符串 | 带随机后缀的字符串;应用仍需保持文档内 ID 唯一。 |
168
+
169
+ `EmailDocument` 含 `version/body/blocks/subject?`;`EmailBlockNode` 含 `id/type/controls/style?/defaultData?`。只支持 `DOCUMENT_VERSION = 4`,不迁移旧文档。JSON 不包含插件组件与渲染逻辑,重新加载时仍需注册相同类型。
170
+
171
+ 主体设置由画布、预览和邮件 HTML 共用:
172
+
173
+ | `EmailBodySettings` 参数 | 默认值与含义 |
174
+ | --- | --- |
175
+ | `textColor` | `#000000`,文字颜色。 |
176
+ | `backgroundColor` | `#ffffff`,居中内容容器背景;外侧白色。 |
177
+ | `contentWidth` | `600`,内容最大宽度 px。 |
178
+ | `fontFamily` | `Arial, Helvetica, sans-serif`。 |
179
+ | `fontWeight` | `normal`;也可 `bold`。 |
180
+ | `linkColor` / `linkDecoration` | `#005bd3` / `underline`。 |
181
+ | `contentPadding` | `0px`,边框内部 CSS padding。 |
182
+ | `blockGap` | `4px`,相邻区块间距。 |
183
+ | `borderWidth` / `borderStyle` / `borderColor` | `0px` / `solid` / `transparent`。 |
184
+
185
+ 主体固定居中,无主体圆角或额外外层 padding。显式富文本格式优先于主体默认值;基础字号 16px、行高 1.5 由内部默认排版提供。HTML 区块不重写内部链接和列表。区块容器样式存于 `dimension/decorations`,元素样式存于 `element`,自定义区块可启用 `typography`;只读取已启用的样式。
186
+
187
+ ## HTML 渲染与模板
188
+
189
+ ### documentToHtml(document, definitions?, options?) → string
190
+
191
+ `document` 必填 v4 文档;`definitions` 默认 `DEFAULT_BLOCKS`,含自定义区块时使用 `config.definitions` 或直接调用 `config.toHtml()`。返回只有一套 doctype/html/head/body 的完整邮件 HTML,采用表格与内联样式。
192
+
193
+ | `DocumentHtmlOptions` 参数 | 默认与含义 |
194
+ | --- | --- |
195
+ | `resolveHtmlTemplates` | `false` 保留模板;`true` 解析预览值。 |
196
+ | `variables` | `[]`,变量原文与预览文本。 |
197
+ | `templateData` | 可选 `TemplateData`,全局对象或列表数据。 |
198
+
199
+ 数据按顶层浅合并,优先级:节点 `defaultData` > `templateData` > `variables`。默认导出仅给与 `variables[].name` 精确匹配且缺点的 token 补点,并保留空格,例如 `{{ store_name }}` → `{{ .store_name }}`;列表外的 Go template 保持原样。变量 `description` 为空时不显示副文案,`value` 只用于预览。
200
+
201
+ ### 单区块与底层工具
202
+
203
+ | API | 参数与默认值 | 输出 |
204
+ | --- | --- | --- |
205
+ | `renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body = DEFAULT_EMAIL_BODY)` | 节点、注册表、`RenderMode`、完整主体 | 插件内部 HTML;不做变量补点或模板解析。 |
206
+ | `renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {})` | 最后参数为 `BlockRenderOptions` | 内部 HTML,加变量补点或模板解析。 |
207
+ | `blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {})` | 同上 | 带区块间距、容器样式的邮件表格片段;不是完整文档。 |
208
+ | `buildRenderContext(node, mode, body = DEFAULT_EMAIL_BODY, definition = 内置查找结果)` | mode 必填;自定义区块需显式传入 definition | 独立归一化 `BlockRenderContext`,不渲染。 |
209
+ | `variablesToTemplateData(variables)` | 必填 `Variable[]` | 以 name 路径为键、value 为值的 `TemplateData`;支持嵌套路径,忽略无 value 的项。 |
210
+ | `mergeTemplateData(variables, defaultData?)` | 变量数组与可选对象 | 顶层合并,defaultData 覆盖变量;两者均无数据时返回 undefined。 |
211
+ | `renderTemplatePreview(html, defaultData?)` | HTML 字符串与可选预览对象 | 支持部分字段路径、range、if、with 等 Go template 语法;省略数据时保留原文。 |
212
+
213
+ `BlockRenderOptions` 含 `resolveHtmlTemplate?: boolean`(注意单数)、`templateData?: TemplateData`、`variables?: Variable[]`、`body?: EmailBodySettings`。`body` 默认 `DEFAULT_EMAIL_BODY`;模板默认不解析。单区块预览取 `templateData ?? node.defaultData`,不会自动合并 variables;需要时显式调用 `mergeTemplateData()`。第四个参数必须为对象,传 boolean 或 null 会抛出 TypeError。
214
+
215
+ `mode` 表示消费场景:`canvas` 供画布,`email` 执行邮件控件校验;它不决定是否解析模板。模板预览不是完整 Go 引擎,也不是 HTML 消毒器或安全沙箱。
216
+
217
+ ### renderEmail 中的 email 工具
218
+
219
+ | 方法 | 参数与输出 |
220
+ | --- | --- |
221
+ | ``email.html`…${value}…` `` | 标签模板;普通值转义,EmailMarkup 与数组可组合,模板 token 保留。 |
222
+ | `email.raw(html)` | 原始 HTML → EmailMarkup,不消毒。 |
223
+ | `email.richText(html)` | 富文本 HTML → EmailMarkup,补主体默认格式。 |
224
+ | `email.table(rows)` | `EmailMarkup[][]` → 邮件表格片段。 |
225
+ | `email.image({ src, alt?, width? })` | src 必填,alt 默认空字符串,width 默认 100%;校验图片协议。 |
226
+ | `email.button({ href, label })` | 必填地址和纯文本文案;空或非法地址不生成链接。 |
227
+ | `email.url(value, image = false)` | 返回允许的 URL 或空字符串;image 为 true 使用图片协议规则。 |
228
+
229
+ 链接允许 http/https/mailto/tel、锚点或模板;图片允许 http/https/cid 或模板。自行拼接属性时需先校验协议再转义;返回普通字符串的插件自行负责转义。
230
+
231
+ ## 基础 UI 组件
232
+
233
+ 可用于业务 Inspector;只负责 UI,不会自动绑定文档。值变更回调接收值,`TextInput` 则沿用原生 React event。
234
+
235
+ | 组件 | 参数与默认值 |
236
+ | --- | --- |
237
+ | `TextInput` | 原生 React input 属性与 ref;type 默认 text,仅支持 text/url/email/search/tel/password/number。 |
238
+ | `SelectControl` | 必填 value、options、onChange(value);可选 id、label(无障碍)、emptyLabel、compact=false、disabled。 |
239
+ | `CheckboxControl` | 必填 label、checked、onChange(checked);可选 disabled、className、variant=checkbox(或 switch)。 |
240
+ | `ColorControl` | 必填 value、onChange(value);fallback=#000000、label=Color、resetTo=fallback。 |
241
+ | `RangeControl` | 必填 value、onChange(value);可选 id、label;min=0、max=100、step=1。 |
242
+ | `Button` | 原生 React button 属性,type=button;variant=default(或 primary/solid/icon);可选 tooltip。 |
243
+ | `RichText` | 底层富文本组件:必填 initialHtml、variables、selected、toolbarId、toolbarCollapsed、onCommit(html)、onSelect();可选 profile=text、placeholder、inherited。toolbarId 需唯一。插件通常使用 EditableRichText 自动绑定。 |
244
+
245
+ Props 及 `RichText.inherited` 的字段说明包含在发布包的 `.d.ts` 注释中,可通过 IDE 悬停或跳转到定义查看。原生属性按 React 类型定义,不新增同名导出类型。
246
+
247
+ ## 常量与公共类型
248
+
249
+ | 导出 | 用途 |
250
+ | --- | --- |
251
+ | `BLOCK_TYPES` / `BuiltinBlockType` | heading、text、button、image、divider、spacer、html。 |
252
+ | `DEFAULT_BLOCKS` | 所有内置 BlockPlugin 定义;自定义注册表使用 config.definitions。 |
253
+ | `DEFAULT_EMAIL_BODY` / `DOCUMENT_VERSION` | 默认主体设置 / 文档版本 4。不要直接修改默认对象。 |
254
+ | `FONT_FAMILIES` | 只读字体选项 `{ label, value }[]`。 |
255
+ | `STYLE_GROUPS` / `StyleGroup` | general/dimension/typography/decorations 的标识及展示名称。 |
256
+ | `BORDER_STYLES` / `BorderStyle` | none/solid/dashed/dotted。 |
257
+ | `FONT_WEIGHTS` / `FontWeight` | normal/bold。 |
258
+ | `HORIZONTAL_ALIGNS` / `HorizontalAlign` | left/center/right。 |
259
+ | `TEXT_ALIGNS` / `TextAlign` | left/center/right/justify。 |
260
+ | `LINK_DECORATIONS` / `LinkDecoration` | underline/none。 |
261
+
262
+ 编辑器、文档、控件、插件、样式、渲染与模板类型的字段含义,均通过发布包 `.d.ts` 中的 JSDoc 提供。`BlockDefinition` 是内部类型,不从包入口导出;使用 `BlockPlugin` 接收公共插件定义。
263
+
264
+ ## 开发与本地联调
265
+
266
+ ```bash
267
+ pnpm typecheck
268
+ pnpm lint
269
+ pnpm build
270
+ node --test tests/*.test.mjs tests/*.test.ts
271
+ ```
272
+
273
+ 先构建再运行依赖 dist 的测试;TypeScript 内部测试需要 Node 22.18+。包提供 ESM、类型与展开后的 CSS。React/React DOM 为 peer dependencies;TipTap、dnd-kit、CodeMirror、Lezer 等为 dependencies,构建时 external,不内联到发布包。
274
+
275
+ 消费方可使用 `link:../react-email-editor`;先 `pnpm build`,再 `pnpm build:watch` 保持 dist 更新。两项目需共用一个 React 实例:Vite 可设置 `resolve.dedupe: ["react", "react-dom"]`;Next.js 可设置 `transpilePackages: ["@standhigher/react-email-editor"]`,Turbopack 的 root 需覆盖链接目录。
Binary file
@@ -0,0 +1,84 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1300" viewBox="0 0 1440 1300" role="img" aria-labelledby="title description" xml:lang="en"><title id="title">Project module architecture</title><desc id="description">8 connected modules: choose content → edit → preview → save or export. Content block library, Drag-and-drop canvas, Block &amp; body settings, Subject &amp; personalization, Desktop / mobile preview, Custom blocks &amp; resources, Document &amp; editing state, Email rendering &amp; validation.</desc><defs><marker id="arrow" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M1 1 L7 4.5 L1 8" fill="none" stroke="#91a2b8" stroke-width="1.6"/></marker></defs><g font-family="'PingFang SC','Helvetica Neue',Arial,sans-serif">
2
+ <rect x="0" y="0" width="1440" height="1300" rx="0" fill="#ffffff" stroke="#ffffff"/>
3
+ <text x="48" y="59" font-size="38" font-weight="700" fill="#152033">Project module architecture</text>
4
+ <text x="48" y="101" font-size="23" font-weight="400" fill="#64748b">8 connected modules: choose content → edit → preview → save or export</text>
5
+ <rect x="48" y="135" width="1344" height="88" rx="16" fill="#eff4fb" stroke="#d8e1ed"/>
6
+ <text x="72" y="173" font-size="26" font-weight="650" fill="#152033">Embed in your application</text>
7
+ <text x="72" y="205" font-size="22" font-weight="400" fill="#475569">Add email editing to your website, admin app or business system</text>
8
+ <path d="M720 223 V250" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
9
+ <rect x="48" y="262" width="1344" height="514" rx="20" fill="#f8fafc" stroke="#ccd7e5"/>
10
+ <text x="72" y="305" font-size="27" font-weight="700" fill="#152033">Modules people interact with</text>
11
+ <text x="560" y="304" font-size="21" font-weight="400" fill="#64748b">Six core capabilities for creating an email</text>
12
+ <rect x="72" y="332" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
13
+ <rect x="90" y="350" width="38" height="34" rx="9" fill="#efeaff" stroke="#efeaff"/>
14
+ <text x="97" y="375" font-size="18" font-weight="700" fill="#6653c1">01</text>
15
+ <text x="142" y="376" font-size="23" font-weight="650" fill="#152033">Content block library</text>
16
+ <text x="92" y="420" font-size="20" font-weight="400" fill="#475569">Heading, text, button and image</text>
17
+ <text x="92" y="453" font-size="20" font-weight="400" fill="#475569">Divider, spacer and custom HTML</text>
18
+ <text x="92" y="486" font-size="20" font-weight="400" fill="#475569">Drag the content into your email</text>
19
+ <rect x="512" y="332" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
20
+ <rect x="530" y="350" width="38" height="34" rx="9" fill="#e7f1ff" stroke="#e7f1ff"/>
21
+ <text x="537" y="375" font-size="18" font-weight="700" fill="#266ab5">02</text>
22
+ <text x="582" y="376" font-size="23" font-weight="650" fill="#152033">Drag-and-drop canvas</text>
23
+ <text x="532" y="420" font-size="20" font-weight="400" fill="#475569">Reorder, duplicate and delete blocks</text>
24
+ <text x="532" y="453" font-size="20" font-weight="400" fill="#475569">Edit text and rich text directly</text>
25
+ <text x="532" y="486" font-size="20" font-weight="400" fill="#475569">See the layout while you edit</text>
26
+ <rect x="952" y="332" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
27
+ <rect x="970" y="350" width="38" height="34" rx="9" fill="#fff1e1" stroke="#fff1e1"/>
28
+ <text x="977" y="375" font-size="18" font-weight="700" fill="#a3652d">03</text>
29
+ <text x="1022" y="376" font-size="23" font-weight="650" fill="#152033">Block &amp; body settings</text>
30
+ <text x="972" y="420" font-size="20" font-weight="400" fill="#475569">Change content, colors and borders</text>
31
+ <text x="972" y="453" font-size="20" font-weight="400" fill="#475569">Adjust width, padding and spacing</text>
32
+ <text x="972" y="486" font-size="20" font-weight="400" fill="#475569">Set email-wide fonts and background</text>
33
+ <rect x="72" y="545" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
34
+ <rect x="90" y="563" width="38" height="34" rx="9" fill="#e7f6ef" stroke="#e7f6ef"/>
35
+ <text x="97" y="588" font-size="18" font-weight="700" fill="#268163">04</text>
36
+ <text x="142" y="589" font-size="23" font-weight="650" fill="#152033">Subject &amp; personalization</text>
37
+ <text x="92" y="633" font-size="20" font-weight="400" fill="#475569">Set the email subject</text>
38
+ <text x="92" y="666" font-size="20" font-weight="400" fill="#475569">Insert customer or store variables</text>
39
+ <text x="92" y="699" font-size="20" font-weight="400" fill="#475569">See replacements with sample data</text>
40
+ <rect x="512" y="545" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
41
+ <rect x="530" y="563" width="38" height="34" rx="9" fill="#e7f1ff" stroke="#e7f1ff"/>
42
+ <text x="537" y="588" font-size="18" font-weight="700" fill="#266ab5">05</text>
43
+ <text x="582" y="589" font-size="23" font-weight="650" fill="#152033">Desktop / mobile preview</text>
44
+ <text x="532" y="633" font-size="20" font-weight="400" fill="#475569">Switch between desktop and mobile</text>
45
+ <text x="532" y="666" font-size="20" font-weight="400" fill="#475569">Preview resolved template variables</text>
46
+ <text x="532" y="699" font-size="20" font-weight="400" fill="#475569">Review content and layout before sending</text>
47
+ <rect x="952" y="545" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
48
+ <rect x="970" y="563" width="38" height="34" rx="9" fill="#efeaff" stroke="#efeaff"/>
49
+ <text x="977" y="588" font-size="18" font-weight="700" fill="#6653c1">06</text>
50
+ <text x="1022" y="589" font-size="23" font-weight="650" fill="#152033">Custom blocks &amp; resources</text>
51
+ <text x="972" y="633" font-size="20" font-weight="400" fill="#475569">Add business blocks, e.g. product cards</text>
52
+ <text x="972" y="666" font-size="20" font-weight="400" fill="#475569">Customize editing UI and controls</text>
53
+ <text x="972" y="699" font-size="20" font-weight="400" fill="#475569">Connect your image resource picker</text>
54
+ <path d="M494 426 H504" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
55
+ <path d="M944 426 H934" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
56
+ <path d="M720 776 V812" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" />
57
+ <text x="48" y="826" font-size="24" font-weight="650" fill="#475569">Shared capabilities supporting all editing modules</text>
58
+ <path d="M720 845 V856 H386 V876" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
59
+ <path d="M720 856 H1054 V876" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
60
+ <rect x="48" y="889" width="642" height="153" rx="16" fill="#f3efff" stroke="#dcd1f5"/>
61
+ <rect x="70" y="909" width="38" height="34" rx="9" fill="#e7defb" stroke="#e7defb"/>
62
+ <text x="77" y="934" font-size="18" font-weight="700" fill="#6653c1">07</text>
63
+ <text x="122" y="936" font-size="26" font-weight="650" fill="#40347a">Document &amp; editing state</text>
64
+ <text x="72" y="976" font-size="21" font-weight="400" fill="#475569">Keep content, block order, styles and subject together</text>
65
+ <text x="72" y="1010" font-size="21" font-weight="400" fill="#475569">Load, replace and reset; editor instances stay isolated</text>
66
+ <rect x="750" y="889" width="642" height="153" rx="16" fill="#eef5ff" stroke="#cddff7"/>
67
+ <rect x="772" y="909" width="38" height="34" rx="9" fill="#dfeaff" stroke="#dfeaff"/>
68
+ <text x="779" y="934" font-size="18" font-weight="700" fill="#266ab5">08</text>
69
+ <text x="824" y="936" font-size="26" font-weight="650" fill="#225897">Email rendering &amp; validation</text>
70
+ <text x="774" y="976" font-size="21" font-weight="400" fill="#475569">Validate controls and blocks; generate email HTML</text>
71
+ <text x="774" y="1010" font-size="21" font-weight="400" fill="#475569">Keep templates for delivery or resolve data for preview</text>
72
+ <path d="M690 965 H738" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
73
+ <path d="M369 1042 V1081" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
74
+ <path d="M1071 1042 V1081" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
75
+ <rect x="48" y="1094" width="642" height="137" rx="16" fill="#edf8f1" stroke="#cce4d5"/>
76
+ <text x="72" y="1134" font-size="27" font-weight="700" fill="#286844">JSON document</text>
77
+ <text x="72" y="1170" font-size="23" font-weight="400" fill="#475569">Your app saves the draft</text>
78
+ <text x="72" y="1202" font-size="23" font-weight="400" fill="#475569">Load it later and continue editing</text>
79
+ <rect x="750" y="1094" width="642" height="137" rx="16" fill="#edf5ff" stroke="#cddff7"/>
80
+ <text x="774" y="1134" font-size="27" font-weight="700" fill="#225897">Email HTML</text>
81
+ <text x="774" y="1170" font-size="23" font-weight="400" fill="#475569">Export templates or preview output</text>
82
+ <text x="774" y="1202" font-size="23" font-weight="400" fill="#475569">Pass it to your email delivery system</text>
83
+ <text x="48" y="1272" font-size="21" font-weight="400" fill="#64748b">This project builds, previews and exports emails. Your app handles storage, uploads and delivery.</text>
84
+ </g></svg>
@@ -0,0 +1,84 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1300" viewBox="0 0 1440 1300" role="img" aria-labelledby="title description" xml:lang="zh-CN"><title id="title">项目模块架构图</title><desc id="description">8 个模块协同:选内容 → 编邮件 → 看预览 → 保存或导出. 内容区块库, 拖拽编辑画布, 属性与主体设置, 主题与个性化, 桌面 / 移动预览, 自定义区块与资源, 文档与编辑状态, 邮件渲染与校验.</desc><defs><marker id="arrow" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M1 1 L7 4.5 L1 8" fill="none" stroke="#91a2b8" stroke-width="1.6"/></marker></defs><g font-family="'PingFang SC','Helvetica Neue',Arial,sans-serif">
2
+ <rect x="0" y="0" width="1440" height="1300" rx="0" fill="#ffffff" stroke="#ffffff"/>
3
+ <text x="48" y="59" font-size="38" font-weight="700" fill="#152033">项目模块架构图</text>
4
+ <text x="48" y="101" font-size="23" font-weight="400" fill="#64748b">8 个模块协同:选内容 → 编邮件 → 看预览 → 保存或导出</text>
5
+ <rect x="48" y="135" width="1344" height="88" rx="16" fill="#eff4fb" stroke="#d8e1ed"/>
6
+ <text x="72" y="173" font-size="26" font-weight="650" fill="#152033">接入你的应用</text>
7
+ <text x="72" y="205" font-size="22" font-weight="400" fill="#475569">网站、运营后台或业务系统,把邮件编辑能力嵌入现有页面</text>
8
+ <path d="M720 223 V250" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
9
+ <rect x="48" y="262" width="1344" height="514" rx="20" fill="#f8fafc" stroke="#ccd7e5"/>
10
+ <text x="72" y="305" font-size="27" font-weight="700" fill="#152033">用户直接操作的模块</text>
11
+ <text x="560" y="304" font-size="21" font-weight="400" fill="#64748b">制作一封邮件时使用的 6 项核心能力</text>
12
+ <rect x="72" y="332" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
13
+ <rect x="90" y="350" width="38" height="34" rx="9" fill="#efeaff" stroke="#efeaff"/>
14
+ <text x="97" y="375" font-size="18" font-weight="700" fill="#6653c1">01</text>
15
+ <text x="142" y="376" font-size="25" font-weight="650" fill="#152033">内容区块库</text>
16
+ <text x="92" y="420" font-size="23" font-weight="400" fill="#475569">标题、正文、按钮、图片</text>
17
+ <text x="92" y="453" font-size="23" font-weight="400" fill="#475569">分割线、留白、自定义 HTML</text>
18
+ <text x="92" y="486" font-size="23" font-weight="400" fill="#475569">把需要的内容拖入邮件</text>
19
+ <rect x="512" y="332" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
20
+ <rect x="530" y="350" width="38" height="34" rx="9" fill="#e7f1ff" stroke="#e7f1ff"/>
21
+ <text x="537" y="375" font-size="18" font-weight="700" fill="#266ab5">02</text>
22
+ <text x="582" y="376" font-size="25" font-weight="650" fill="#152033">拖拽编辑画布</text>
23
+ <text x="532" y="420" font-size="23" font-weight="400" fill="#475569">排列、复制、删除内容区块</text>
24
+ <text x="532" y="453" font-size="23" font-weight="400" fill="#475569">直接编辑文字与富文本</text>
25
+ <text x="532" y="486" font-size="23" font-weight="400" fill="#475569">边编辑边查看邮件布局</text>
26
+ <rect x="952" y="332" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
27
+ <rect x="970" y="350" width="38" height="34" rx="9" fill="#fff1e1" stroke="#fff1e1"/>
28
+ <text x="977" y="375" font-size="18" font-weight="700" fill="#a3652d">03</text>
29
+ <text x="1022" y="376" font-size="25" font-weight="650" fill="#152033">属性与主体设置</text>
30
+ <text x="972" y="420" font-size="23" font-weight="400" fill="#475569">修改区块内容、颜色与边框</text>
31
+ <text x="972" y="453" font-size="23" font-weight="400" fill="#475569">调整宽度、内边距与间距</text>
32
+ <text x="972" y="486" font-size="23" font-weight="400" fill="#475569">统一设置邮件字体与背景</text>
33
+ <rect x="72" y="545" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
34
+ <rect x="90" y="563" width="38" height="34" rx="9" fill="#e7f6ef" stroke="#e7f6ef"/>
35
+ <text x="97" y="588" font-size="18" font-weight="700" fill="#268163">04</text>
36
+ <text x="142" y="589" font-size="25" font-weight="650" fill="#152033">主题与个性化</text>
37
+ <text x="92" y="633" font-size="23" font-weight="400" fill="#475569">设置邮件主题</text>
38
+ <text x="92" y="666" font-size="23" font-weight="400" fill="#475569">插入姓名、店铺等模板变量</text>
39
+ <text x="92" y="699" font-size="23" font-weight="400" fill="#475569">使用示例数据查看替换结果</text>
40
+ <rect x="512" y="545" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
41
+ <rect x="530" y="563" width="38" height="34" rx="9" fill="#e7f1ff" stroke="#e7f1ff"/>
42
+ <text x="537" y="588" font-size="18" font-weight="700" fill="#266ab5">05</text>
43
+ <text x="582" y="589" font-size="25" font-weight="650" fill="#152033">桌面 / 移动预览</text>
44
+ <text x="532" y="633" font-size="23" font-weight="400" fill="#475569">切换桌面与手机显示效果</text>
45
+ <text x="532" y="666" font-size="23" font-weight="400" fill="#475569">预览模板变量替换后的内容</text>
46
+ <text x="532" y="699" font-size="23" font-weight="400" fill="#475569">发送前检查邮件内容与版式</text>
47
+ <rect x="952" y="545" width="416" height="188" rx="16" fill="#ffffff" stroke="#d8e1ed"/>
48
+ <rect x="970" y="563" width="38" height="34" rx="9" fill="#efeaff" stroke="#efeaff"/>
49
+ <text x="977" y="588" font-size="18" font-weight="700" fill="#6653c1">06</text>
50
+ <text x="1022" y="589" font-size="25" font-weight="650" fill="#152033">自定义区块与资源</text>
51
+ <text x="972" y="633" font-size="23" font-weight="400" fill="#475569">增加商品卡片等业务区块</text>
52
+ <text x="972" y="666" font-size="23" font-weight="400" fill="#475569">定制编辑画布和属性控件</text>
53
+ <text x="972" y="699" font-size="23" font-weight="400" fill="#475569">接入已有图片资源选择器</text>
54
+ <path d="M494 426 H504" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
55
+ <path d="M944 426 H934" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
56
+ <path d="M720 776 V812" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" />
57
+ <text x="48" y="826" font-size="24" font-weight="650" fill="#475569">支撑所有编辑模块的共享能力</text>
58
+ <path d="M720 845 V856 H386 V876" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
59
+ <path d="M720 856 H1054 V876" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
60
+ <rect x="48" y="889" width="642" height="153" rx="16" fill="#f3efff" stroke="#dcd1f5"/>
61
+ <rect x="70" y="909" width="38" height="34" rx="9" fill="#e7defb" stroke="#e7defb"/>
62
+ <text x="77" y="934" font-size="18" font-weight="700" fill="#6653c1">07</text>
63
+ <text x="122" y="936" font-size="26" font-weight="650" fill="#40347a">文档与编辑状态</text>
64
+ <text x="72" y="976" font-size="23" font-weight="400" fill="#475569">统一记录邮件内容、顺序、样式与主题</text>
65
+ <text x="72" y="1010" font-size="23" font-weight="400" fill="#475569">支持载入、替换、重置;多个编辑器互不干扰</text>
66
+ <rect x="750" y="889" width="642" height="153" rx="16" fill="#eef5ff" stroke="#cddff7"/>
67
+ <rect x="772" y="909" width="38" height="34" rx="9" fill="#dfeaff" stroke="#dfeaff"/>
68
+ <text x="779" y="934" font-size="18" font-weight="700" fill="#266ab5">08</text>
69
+ <text x="824" y="936" font-size="26" font-weight="650" fill="#225897">邮件渲染与校验</text>
70
+ <text x="774" y="976" font-size="23" font-weight="400" fill="#475569">检查控件值与区块配置,生成邮件 HTML</text>
71
+ <text x="774" y="1010" font-size="23" font-weight="400" fill="#475569">保留模板供发送使用,或解析示例数据用于预览</text>
72
+ <path d="M690 965 H738" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
73
+ <path d="M369 1042 V1081" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
74
+ <path d="M1071 1042 V1081" fill="none" stroke="#91a2b8" stroke-width="2.5" stroke-linejoin="round" marker-end="url(#arrow)"/>
75
+ <rect x="48" y="1094" width="642" height="137" rx="16" fill="#edf8f1" stroke="#cce4d5"/>
76
+ <text x="72" y="1134" font-size="27" font-weight="700" fill="#286844">JSON 文档</text>
77
+ <text x="72" y="1170" font-size="23" font-weight="400" fill="#475569">交给应用保存草稿</text>
78
+ <text x="72" y="1202" font-size="23" font-weight="400" fill="#475569">下次载入,继续编辑</text>
79
+ <rect x="750" y="1094" width="642" height="137" rx="16" fill="#edf5ff" stroke="#cddff7"/>
80
+ <text x="774" y="1134" font-size="27" font-weight="700" fill="#225897">邮件 HTML</text>
81
+ <text x="774" y="1170" font-size="23" font-weight="400" fill="#475569">导出邮件模板或预览结果</text>
82
+ <text x="774" y="1202" font-size="23" font-weight="400" fill="#475569">交给应用的邮件发送系统</text>
83
+ <text x="48" y="1272" font-size="22" font-weight="400" fill="#64748b">本项目负责邮件制作、预览与导出;数据存储、文件上传、实际发信由接入应用完成。</text>
84
+ </g></svg>
@@ -0,0 +1,113 @@
1
+ # Basic examples
2
+
3
+ [简体中文](./basic-examples.zh-CN.md) | [API reference](./api.md) | [Advanced examples](./advanced-examples.md)
4
+
5
+ Import `@standhigher/react-email-editor/styles.css` once from your global stylesheet entry. Each component example can live in its own `.tsx` file. Add `"use client"` with Next.js App Router.
6
+
7
+ ## 1. Empty editor and JSON saving
8
+
9
+ ```tsx
10
+ import { useState } from "react";
11
+ import { EmailEditor, useEmailEditor } from "@standhigher/react-email-editor";
12
+
13
+ export function EmailComposer() {
14
+ const editor = useEmailEditor();
15
+ const [savedJson, setSavedJson] = useState("");
16
+
17
+ return (
18
+ <>
19
+ <EmailEditor editor={editor} height={720} />
20
+ <button type="button" onClick={() => setSavedJson(editor.getJson())}>Save JSON</button>
21
+ <output>{savedJson ? "Saved in page state" : "Not saved yet"}</output>
22
+ </>
23
+ );
24
+ }
25
+ ```
26
+
27
+ This example saves to React state, which does not survive a page reload. Your app implements database or file persistence. getDocument() returns a detached JSON object, getJson() a formatted string, and getHtml() template HTML for a sending system.
28
+
29
+ ## 2. Initial content, document switching and reset
30
+
31
+ ```tsx
32
+ import {
33
+ BLOCK_TYPES, createBlockNode, createEmailDocument,
34
+ EmailEditor, useEmailEditor, type EmailDocument,
35
+ } from "@standhigher/react-email-editor";
36
+
37
+ const heading = createBlockNode(BLOCK_TYPES.heading);
38
+ if (heading) heading.controls.html = "<h1>Welcome to our newsletter</h1>";
39
+ const initialDocument = createEmailDocument(heading ? [heading] : []);
40
+
41
+ interface RestorableEditorProps {
42
+ savedDocument: EmailDocument;
43
+ }
44
+
45
+ export function RestorableEditor({ savedDocument }: RestorableEditorProps) {
46
+ const editor = useEmailEditor({ initialDocument });
47
+ return (
48
+ <>
49
+ <EmailEditor editor={editor} height={640} />
50
+ <button type="button" onClick={() => editor.setDocument(savedDocument)}>Load saved document</button>
51
+ <button type="button" onClick={() => editor.reset()}>Restore initial content</button>
52
+ </>
53
+ );
54
+ }
55
+ ```
56
+
57
+ Your app loads and validates savedDocument as a current v4 document. TypeScript types do not replace runtime JSON validation. initialDocument is read once; use setDocument() for later replacements. reset() restores the first initialized content, not the most recently loaded document.
58
+
59
+ ## 3. Body styles, variables and preview export
60
+
61
+ ```tsx
62
+ import { useState } from "react";
63
+ import {
64
+ BLOCK_TYPES, createBlockNode, createEmailDocument, EmailEditor, useEmailEditor,
65
+ } from "@standhigher/react-email-editor";
66
+
67
+ const text = createBlockNode(BLOCK_TYPES.text);
68
+ if (text) text.controls.html = "<p>Hello, {{ customer_name }}, welcome to {{ store_name }}</p>";
69
+ const initialDocument = createEmailDocument(text ? [text] : []);
70
+
71
+ export function PersonalizedEditor() {
72
+ const [html, setHtml] = useState("");
73
+ const editor = useEmailEditor({
74
+ initialDocument,
75
+ body: { contentWidth: 640, contentPadding: "24px", backgroundColor: "#f7f7f7" },
76
+ variables: [
77
+ { name: "{{ store_name }}", description: "Store name", value: "Example Store" },
78
+ { name: "{{ customer_name }}", value: "Sam" },
79
+ ],
80
+ });
81
+ return (
82
+ <>
83
+ <EmailEditor editor={editor} height={720} />
84
+ <button type="button" onClick={() => setHtml(editor.getHtml())}>Read template HTML</button>
85
+ <button type="button" onClick={() => setHtml(editor.getRenderedHtml())}>Read preview HTML</button>
86
+ <pre>{html}</pre>
87
+ </>
88
+ );
89
+ }
90
+ ```
91
+
92
+ Template export preserves variables and dots listed tokens: {{ store_name }} becomes {{ .store_name }}. Preview export substitutes Example Store and other values. The editor includes desktop and mobile preview. This example displays source in a pre element; your app can download or upload it. See [validated saving](./advanced-examples.md#1-validate-before-async-saving) for export error handling.
93
+
94
+ ## 4. Export without mounting the editor
95
+
96
+ ```ts
97
+ import {
98
+ BLOCK_TYPES, createBlockNode, createEmailDocument, documentToHtml,
99
+ } from "@standhigher/react-email-editor";
100
+
101
+ const block = createBlockNode(BLOCK_TYPES.html);
102
+ if (!block) throw new Error("HTML block is not registered");
103
+ block.controls.html = "<p>Hello, {{.customer.name}}</p>";
104
+ const document = createEmailDocument([block]);
105
+
106
+ export const templateHtml = documentToHtml(document);
107
+ export const previewHtml = documentToHtml(document, undefined, {
108
+ resolveHtmlTemplates: true,
109
+ templateData: { customer: { name: "Sam" } },
110
+ });
111
+ ```
112
+
113
+ Each export contains one doctype, html, head and body. Custom blocks need a shared registry; see [advanced examples](./advanced-examples.md#3-business-block-with-multiple-rich-text-regions-and-a-list).