@standhigher/react-email-editor 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ # 更新日志
2
+
3
+ ## 0.2.0 — 2026-10-27
4
+
5
+ ### 不兼容变更
6
+
7
+ - 文档格式由 `version: 1` 升为 `version: 2`,旧版本会被拒绝,不提供自动迁移。请使用 `createEmailDocument()` 创建当前版本文档;升级已有数据时,需同时调整区块字段和样式,不能只修改版本号。
8
+ - Heading、Text、Button 的文字格式统一存于 `fields.html`;Button 不再读取 `fields.label` 和 `style.element` 中的文字样式。`EmailElementStyle` 不再继承 `EmailTypographyStyle`。
9
+ - 内置区块按各自能力声明 `styleFields`,不再读取未启用的样式;移除内置区块的 `dimension.height` 和区块级 `typography` 配置。自定义区块仍可显式启用这些字段,`exactStyleFields` 同时约束默认样式、画布和邮件输出。
10
+ - HTML 区块保留源码内部的段落、列表、链接和显式样式,不再注入正文排版;未声明的属性仍可继承全局样式。
11
+
12
+ ### 新增与改进
13
+
14
+ - Button 支持画布内富文本编辑,采用单段内容,粘贴多段时以空格连接;邮件输出保留基础行内格式,并只生成一个按钮外层链接。
15
+ - 新增 `BlockInspectorSection`、`inspectorSections`、`RichTextProfile` 和 `inlineEditProfile`,支持自定义检查器分组与富文本编辑能力;独立 `RichText` 支持 `profile`。
16
+ - 统一颜色面板、HEX 输入和颜色重置;链接编辑改为居中弹窗,支持颜色、下划线和打开方式。
17
+ - 优化区块操作栏、拖拽反馈、三栏布局、工具栏换行和预览高度;预览跟随文档内容展示。
18
+ - 按钮默认填充改为黑色,按钮和图片的链接默认为空,空白链接不生成跳转;全局内容、区块、按钮和图片边框默认透明。
19
+ - 精简中英文 README,补充 npm keywords,发布包包含中文 README 和更新日志。
20
+
21
+ ### 修复与验证
22
+
23
+ - 修复富文本格式操作后的光标、选区和格式保留,以及列表切换时的间距和缩进保留。
24
+ - 修复按钮导出对模板表达式中引号、比较符、注释和空格的处理。
25
+ - 接入 Oxlint,补充区块配置、富文本、列表、按钮和颜色转换回归测试。
26
+
27
+ ## 0.1.0 — 2026-10-27
28
+
29
+ 首个正式发布版本(对应提交 `48011d9`)。
30
+
31
+ - 提供 React 18 编辑器、拖拽区块、富文本编辑、检查器及桌面/移动预览。
32
+ - 支持 JSON 文档、全局和区块样式、HTML 源码编辑、自定义区块与 Go template 预览。
33
+ - 提供组件 ref 数据读取、独立 HTML 渲染、ESM、TypeScript 类型和独立 CSS 入口。
34
+ - 提供 Vite playground、构建和本地 link 联调流程。
35
+
36
+ ## 0.0.0-stage — 2026-10-27
37
+
38
+ - 发布前的包分发验证版本。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Standhigher Tech Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,132 @@
1
- # Temporary Holding Version
1
+ # @standhigher/react-email-editor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ English | [简体中文](./README.zh-CN.md)
4
+
5
+ A React email template editor with drag-and-drop blocks, inline rich text, desktop/mobile previews, JSON storage, and HTML export.
6
+
7
+ ## Installation
8
+
9
+ Requires React 18 and React DOM 18. Includes ESM and TypeScript declarations.
10
+
11
+ ```bash
12
+ pnpm add @standhigher/react-email-editor
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ Import the stylesheet once in your application and use a ref to read the document and HTML. Your application handles persistence.
18
+
19
+ ```tsx
20
+ import { useRef } from "react";
21
+ import {
22
+ BLOCK_TYPES,
23
+ EmailEditor,
24
+ createBlockNode,
25
+ createEmailDocument,
26
+ type EmailEditorHandle,
27
+ type Variable,
28
+ } from "@standhigher/react-email-editor";
29
+ import "@standhigher/react-email-editor/styles.css";
30
+
31
+ const text = createBlockNode(BLOCK_TYPES.text);
32
+ if (text) text.fields.html = "<p>Hello, {{ store_name }}</p>";
33
+ const initialDocument = createEmailDocument(text ? [text] : []);
34
+ const variables: Variable[] = [
35
+ { name: "{{ store_name }}", description: "Store name", value: "Example Store" },
36
+ ];
37
+
38
+ export function EmailPage() {
39
+ const editorRef = useRef<EmailEditorHandle>(null);
40
+
41
+ function save() {
42
+ const data = editorRef.current?.getData();
43
+ if (!data) return;
44
+ // Persist data.document in your app; use data.html to export the template.
45
+ console.log(data);
46
+ }
47
+
48
+ return (
49
+ <>
50
+ <EmailEditor
51
+ ref={editorRef}
52
+ initialDocument={initialDocument}
53
+ variables={variables}
54
+ height={720}
55
+ />
56
+ <button type="button" onClick={save}>Save</button>
57
+ </>
58
+ );
59
+ }
60
+ ```
61
+
62
+ Omit `initialDocument` to start empty. In Next.js App Router, add `"use client"` to the integration component and import CSS from an allowed global stylesheet entry. When using `height="100%"`, give the parent a definite height.
63
+
64
+ ## Props
65
+
66
+ All props are optional.
67
+
68
+ | Prop | Type | Behavior |
69
+ | --- | --- | --- |
70
+ | `initialDocument` | `EmailDocument` | Read on first mount only. Remount with a new React `key` to switch documents. |
71
+ | `height` | `number \| string` | Defaults to `"100%"`; numbers are pixels. |
72
+ | `global` | `Partial<EmailGlobalSettings>` | Overrides global styles; later changes merge into the current document. Omitting a field does not restore its default. |
73
+ | `variables` | `Variable[]` | Variables for insertion and preview. Defaults to `[]`; Personalization controls are hidden when empty. |
74
+ | `blocks` | `BlockDefinition[]` | Adds custom blocks; matching types replace built-in definitions. |
75
+ | `onChange` | `(document: EmailDocument) => void` | Called on document changes, including external `global` updates that change document values. |
76
+ | `onGlobalChange` | `(global: EmailGlobalSettings) => void` | Called for global style edits inside the editor, excluding external `global` updates. |
77
+
78
+ Global style example: `global={{ contentWidth: 640, outerPadding: "24px" }}`. Defaults are exported as `DEFAULT_EMAIL_GLOBAL`.
79
+
80
+ Built-in blocks: `heading`, `text`, `button`, `image`, `divider`, `spacer`, and `html`, available through `BLOCK_TYPES`.
81
+
82
+ ## Save and export
83
+
84
+ | Ref method | Returns |
85
+ | --- | --- |
86
+ | `getData()` | `{ document, json, html, renderedHtml }` |
87
+ | `getDocument()` | An independent `EmailDocument` snapshot to persist and pass back as `initialDocument`. |
88
+ | `getJson()` | A formatted document JSON string. |
89
+ | `getHtml()` | A complete email HTML document preserving template expressions. |
90
+ | `getRenderedHtml()` | A complete email HTML document with templates resolved using preview data. |
91
+
92
+ Render a document without mounting the editor:
93
+
94
+ ```ts
95
+ import { documentToHtml } from "@standhigher/react-email-editor";
96
+
97
+ const html = documentToHtml(initialDocument, undefined, { variables });
98
+ const previewHtml = documentToHtml(initialDocument, undefined, {
99
+ variables,
100
+ resolveHtmlTemplates: true,
101
+ });
102
+ ```
103
+
104
+ A variable's `name` is inserted verbatim; `description` and preview `value` are optional. Export adds a missing dot only to tokens matching `variables[].name` exactly, preserving whitespace: `{{ store_name }}` → `{{ .store_name }}`. Other Go template expressions remain unchanged. Preview supports a subset of Go template syntax; data precedence is block `defaultData` > `templateData` (`documentToHtml` option) > `variables`.
105
+
106
+ The document format is under development. Only `version: 2` is accepted, with no automatic migration of older documents. Create data with `createEmailDocument()` and `createBlockNode()`. See [CHANGELOG](./CHANGELOG.md) for upgrade notes.
107
+
108
+ ## Custom blocks
109
+
110
+ Pass `BlockDefinition[]` through `blocks`. Required fields are `type`, `title`, and `render(ctx)`, which returns HTML. Use `fields` / `styleFields` for inspector controls and `defaultFields` / `defaultStyle` / `defaultData` for initial values. The render context includes `mode: "canvas" | "email"`.
111
+
112
+ Built-in text formatting lives in `fields.html`, container styles in `style.dimension` / `style.decorations`, and button, image, and divider element styles in `style.element`. Rendering uses only enabled style fields. See [BlockDefinition](https://github.com/standhigher/react-email-editor/blob/main/src/types/block.ts) and [EmailDocument](https://github.com/standhigher/react-email-editor/blob/main/src/types/document.ts) for full types. When calling `documentToHtml` directly, pass the complete definition list including custom blocks as its second argument.
113
+
114
+ ## Local development
115
+
116
+ Node.js 22.18+ and pnpm are recommended:
117
+
118
+ ```bash
119
+ pnpm install --frozen-lockfile
120
+ pnpm dev
121
+ ```
122
+
123
+ Open [http://localhost:5173](http://localhost:5173). The playground loads `src/` directly; reuse an existing server. Validation:
124
+
125
+ ```bash
126
+ pnpm typecheck
127
+ pnpm lint
128
+ pnpm build
129
+ node --test tests/*.test.mjs tests/*.test.ts
130
+ ```
131
+
132
+ For local consumers, use `link:../react-email-editor`, run `pnpm build` first, and keep `pnpm build:watch` running to update `dist/`. Both projects must share one React instance. Vite can use `resolve.dedupe: ["react", "react-dom"]`; Next.js can use `transpilePackages: ["@standhigher/react-email-editor"]`, with Turbopack's `root` covering the linked directory.
@@ -0,0 +1,132 @@
1
+ # @standhigher/react-email-editor
2
+
3
+ [English](./README.md) | 简体中文
4
+
5
+ React 邮件模板编辑器,支持拖拽区块、画布富文本编辑、桌面/移动预览、JSON 保存和 HTML 导出。
6
+
7
+ ## 安装
8
+
9
+ 需要 React 18 和 React DOM 18。提供 ESM 和 TypeScript 类型。
10
+
11
+ ```bash
12
+ pnpm add @standhigher/react-email-editor
13
+ ```
14
+
15
+ ## 基本接入
16
+
17
+ 在应用入口引入一次样式,通过 `ref` 读取文档和 HTML;保存逻辑由应用实现。
18
+
19
+ ```tsx
20
+ import { useRef } from "react";
21
+ import {
22
+ BLOCK_TYPES,
23
+ EmailEditor,
24
+ createBlockNode,
25
+ createEmailDocument,
26
+ type EmailEditorHandle,
27
+ type Variable,
28
+ } from "@standhigher/react-email-editor";
29
+ import "@standhigher/react-email-editor/styles.css";
30
+
31
+ const text = createBlockNode(BLOCK_TYPES.text);
32
+ if (text) text.fields.html = "<p>你好,{{ store_name }}</p>";
33
+ const initialDocument = createEmailDocument(text ? [text] : []);
34
+ const variables: Variable[] = [
35
+ { name: "{{ store_name }}", description: "店铺名称", value: "示例店铺" },
36
+ ];
37
+
38
+ export function EmailPage() {
39
+ const editorRef = useRef<EmailEditorHandle>(null);
40
+
41
+ function save() {
42
+ const data = editorRef.current?.getData();
43
+ if (!data) return;
44
+ // 将 data.document 保存到应用,data.html 用于邮件模板导出。
45
+ console.log(data);
46
+ }
47
+
48
+ return (
49
+ <>
50
+ <EmailEditor
51
+ ref={editorRef}
52
+ initialDocument={initialDocument}
53
+ variables={variables}
54
+ height={720}
55
+ />
56
+ <button type="button" onClick={save}>保存</button>
57
+ </>
58
+ );
59
+ }
60
+ ```
61
+
62
+ 省略 `initialDocument` 即为空编辑器。Next.js App Router 中,接入组件需声明 `"use client"`,CSS 在应用允许的全局样式入口引入。使用 `height="100%"` 时,父容器需要有明确高度。
63
+
64
+ ## 常用参数
65
+
66
+ 所有 Props 均可选。
67
+
68
+ | 参数 | 类型 | 行为 |
69
+ | --- | --- | --- |
70
+ | `initialDocument` | `EmailDocument` | 仅首次挂载读取;切换文档时通过新的 React `key` 重新挂载。 |
71
+ | `height` | `number \| string` | 默认 `"100%"`;数字为 px。 |
72
+ | `global` | `Partial<EmailGlobalSettings>` | 覆盖全局样式,后续更新合并到当前文档;省略字段不会恢复默认值。 |
73
+ | `variables` | `Variable[]` | 插入和预览变量;默认 `[]`,为空时隐藏 Personalization 入口。 |
74
+ | `blocks` | `BlockDefinition[]` | 添加自定义区块,同名 `type` 覆盖内置定义。 |
75
+ | `onChange` | `(document: EmailDocument) => void` | 文档变化时触发,包括实际改变文档值的外部 `global` 更新。 |
76
+ | `onGlobalChange` | `(global: EmailGlobalSettings) => void` | 编辑器内修改全局样式时触发,外部 `global` 更新不触发。 |
77
+
78
+ 全局样式示例:`global={{ contentWidth: 640, outerPadding: "24px" }}`。默认值通过 `DEFAULT_EMAIL_GLOBAL` 导出。
79
+
80
+ 内置区块:`heading`、`text`、`button`、`image`、`divider`、`spacer`、`html`,均可通过 `BLOCK_TYPES` 引用。
81
+
82
+ ## 保存与导出
83
+
84
+ | Ref 方法 | 返回值 |
85
+ | --- | --- |
86
+ | `getData()` | `{ document, json, html, renderedHtml }` |
87
+ | `getDocument()` | 独立的 `EmailDocument` 快照,可存储后传回 `initialDocument`。 |
88
+ | `getJson()` | 格式化的文档 JSON 字符串。 |
89
+ | `getHtml()` | 保留模板表达式的完整邮件 HTML。 |
90
+ | `getRenderedHtml()` | 使用预览数据解析模板后的完整邮件 HTML。 |
91
+
92
+ 不挂载编辑器时,也可直接渲染文档:
93
+
94
+ ```ts
95
+ import { documentToHtml } from "@standhigher/react-email-editor";
96
+
97
+ const html = documentToHtml(initialDocument, undefined, { variables });
98
+ const previewHtml = documentToHtml(initialDocument, undefined, {
99
+ variables,
100
+ resolveHtmlTemplates: true,
101
+ });
102
+ ```
103
+
104
+ 变量的 `name` 原样插入,`description` 和预览值 `value` 可选。导出时仅对与 `variables[].name` 原文精确匹配的 token 补点并保留空格,如 `{{ store_name }}` → `{{ .store_name }}`;其他 Go template 表达式保持原样。预览支持部分 Go template 语法,数据优先级为区块 `defaultData` > `templateData`(`documentToHtml` 选项)> `variables`。
105
+
106
+ 当前文档格式仍处于开发阶段,仅接受 `version: 2`,不自动迁移旧文档。使用 `createEmailDocument()` 和 `createBlockNode()` 创建数据。升级差异见 [CHANGELOG](./CHANGELOG.md)。
107
+
108
+ ## 自定义区块
109
+
110
+ 通过 `blocks` 传入 `BlockDefinition[]`,必填项为 `type`、`title` 和 `render(ctx)`,返回 HTML 字符串。`fields` / `styleFields` 配置检查器,`defaultFields` / `defaultStyle` / `defaultData` 设置初始值。`render` 接收 `mode: "canvas" | "email"`。
111
+
112
+ 内置文字格式存于 `fields.html`,容器样式存于 `style.dimension` / `style.decorations`,按钮、图片和分割线自身样式存于 `style.element`。渲染仅使用区块启用的样式字段。完整定义见 [BlockDefinition](https://github.com/standhigher/react-email-editor/blob/main/src/types/block.ts) 和 [EmailDocument](https://github.com/standhigher/react-email-editor/blob/main/src/types/document.ts)。独立调用 `documentToHtml` 时,第二个参数需传入包含自定义区块的完整定义列表。
113
+
114
+ ## 本地开发
115
+
116
+ 建议 Node.js 22.18+,使用 pnpm:
117
+
118
+ ```bash
119
+ pnpm install --frozen-lockfile
120
+ pnpm dev
121
+ ```
122
+
123
+ 打开 [http://localhost:5173](http://localhost:5173)。playground 直接加载 `src/`,已有服务时复用。验证命令:
124
+
125
+ ```bash
126
+ pnpm typecheck
127
+ pnpm lint
128
+ pnpm build
129
+ node --test tests/*.test.mjs tests/*.test.ts
130
+ ```
131
+
132
+ 本地消费方可使用 `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` 需覆盖链接目录。
@@ -0,0 +1,343 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ declare const TEXT_ALIGNS: {
5
+ readonly left: "left";
6
+ readonly center: "center";
7
+ readonly right: "right";
8
+ readonly justify: "justify";
9
+ };
10
+ type TextAlign = (typeof TEXT_ALIGNS)[keyof typeof TEXT_ALIGNS];
11
+ declare const HORIZONTAL_ALIGNS: {
12
+ readonly left: "left";
13
+ readonly center: "center";
14
+ readonly right: "right";
15
+ };
16
+ type HorizontalAlign = (typeof HORIZONTAL_ALIGNS)[keyof typeof HORIZONTAL_ALIGNS];
17
+ declare const FONT_WEIGHTS: {
18
+ readonly normal: "normal";
19
+ readonly bold: "bold";
20
+ };
21
+ type FontWeight = (typeof FONT_WEIGHTS)[keyof typeof FONT_WEIGHTS];
22
+ declare const BORDER_STYLES: {
23
+ readonly none: "none";
24
+ readonly solid: "solid";
25
+ readonly dashed: "dashed";
26
+ readonly dotted: "dotted";
27
+ };
28
+ type BorderStyle = (typeof BORDER_STYLES)[keyof typeof BORDER_STYLES];
29
+ declare const LINK_DECORATIONS: {
30
+ readonly underline: "underline";
31
+ readonly none: "none";
32
+ };
33
+ type LinkDecoration = (typeof LINK_DECORATIONS)[keyof typeof LINK_DECORATIONS];
34
+
35
+ declare const DOCUMENT_VERSION: 2;
36
+ declare const FONT_FAMILIES: readonly FieldOption[];
37
+ declare const DEFAULT_EMAIL_GLOBAL: EmailGlobalSettings;
38
+
39
+ interface EmailGlobalSettings {
40
+ textColor: string;
41
+ textAlign: TextAlign;
42
+ backgroundColor: string;
43
+ contentWidth: number;
44
+ contentAlign: HorizontalAlign;
45
+ fontFamily: string;
46
+ fontWeight: FontWeight;
47
+ contentBackgroundColor: string;
48
+ contentPadding: string;
49
+ outerPadding: string;
50
+ blockGap: string;
51
+ defaultFontSize: string;
52
+ defaultLineHeight: string;
53
+ paragraphSpacing: string;
54
+ listSpacing: string;
55
+ listIndent: string;
56
+ linkColor: string;
57
+ linkDecoration: LinkDecoration;
58
+ borderWidth: string;
59
+ borderStyle: BorderStyle;
60
+ borderColor: string;
61
+ borderRadius: string;
62
+ }
63
+ interface EmailDocument {
64
+ version: typeof DOCUMENT_VERSION;
65
+ subject?: string;
66
+ global: EmailGlobalSettings;
67
+ blocks: EmailBlockNode[];
68
+ }
69
+ interface EmailBlockNode {
70
+ id: string;
71
+ type: string;
72
+ fields: Record<string, unknown>;
73
+ style?: EmailBlockStyle;
74
+ defaultData?: Record<string, unknown>;
75
+ }
76
+ interface EmailDecorationStyle {
77
+ backgroundColor?: string;
78
+ borderRadius?: string;
79
+ borderWidth?: string;
80
+ borderColor?: string;
81
+ borderStyle?: BorderStyle;
82
+ }
83
+ interface EmailTypographyStyle {
84
+ fontFamily?: string;
85
+ fontSize?: string;
86
+ fontWeight?: FontWeight;
87
+ lineHeight?: string;
88
+ color?: string;
89
+ textAlign?: TextAlign;
90
+ paragraphSpacing?: string;
91
+ listSpacing?: string;
92
+ listIndent?: string;
93
+ linkColor?: string;
94
+ linkDecoration?: LinkDecoration;
95
+ }
96
+ /** 按钮、图片和分割线自身的样式,与区块容器样式独立。 */
97
+ interface EmailElementStyle extends EmailDecorationStyle {
98
+ padding?: string;
99
+ width?: string;
100
+ height?: string;
101
+ align?: HorizontalAlign;
102
+ }
103
+ interface EmailSpacingStyle {
104
+ padding?: string;
105
+ margin?: string;
106
+ /** 自定义区块可通过 styleFields 启用;内置区块不支持。只使用 px。 */
107
+ height?: string;
108
+ }
109
+ interface EmailBlockStyle {
110
+ dimension?: EmailSpacingStyle;
111
+ /** 自定义区块可显式启用;内置区块的文字格式存于 fields.html。 */
112
+ typography?: EmailTypographyStyle;
113
+ decorations?: EmailDecorationStyle;
114
+ element?: EmailElementStyle;
115
+ }
116
+
117
+ type FieldType = "text" | "textarea" | "html" | "url" | "number" | "color" | "select";
118
+ interface FieldOption {
119
+ label: string;
120
+ value: string;
121
+ }
122
+ interface FieldConfig {
123
+ type: FieldType;
124
+ name: string;
125
+ label: string;
126
+ defaultValue?: unknown;
127
+ placeholder?: string;
128
+ options?: FieldOption[];
129
+ rows?: number;
130
+ min?: number;
131
+ max?: number;
132
+ step?: number;
133
+ }
134
+ type StyleGroup = "general" | "dimension" | "typography" | "decorations";
135
+ type StyleControlType = "text" | "color" | "alignment" | "select" | "number" | "range" | "spacing" | "corners";
136
+ interface StyleFieldConfig {
137
+ /** e.g. "typography.fontSize" — which style slot this reads and writes */
138
+ id: string;
139
+ group: StyleGroup;
140
+ label: string;
141
+ control: StyleControlType;
142
+ options?: FieldOption[];
143
+ /** Unit suffix for a single-unit slider, e.g. "px". Omit for unitless values. */
144
+ unit?: string;
145
+ /** Email-safe units. A select appears only when more than one is listed. */
146
+ units?: string[];
147
+ min?: number;
148
+ max?: number;
149
+ step?: number;
150
+ defaultValue?: string;
151
+ placeholder?: string;
152
+ }
153
+ type RenderMode = "canvas" | "email";
154
+ interface BlockRenderContext {
155
+ block: EmailBlockNode;
156
+ fields: Record<string, unknown>;
157
+ style: EmailBlockStyle;
158
+ /** canvas/email identify the consumer; HTML template resolution is applied by the shared renderer. */
159
+ mode: RenderMode;
160
+ global: EmailGlobalSettings;
161
+ }
162
+ type RichTextProfile = "text" | "heading" | "button";
163
+ /** 显式控制检查器的分组及字段归属,分组按声明顺序展示。 */
164
+ interface BlockInspectorSection {
165
+ title: string;
166
+ fieldNames?: string[];
167
+ styleIds?: string[];
168
+ advancedStyleIds?: string[];
169
+ }
170
+ interface BlockDefinition {
171
+ type: string;
172
+ title: string;
173
+ icon?: ReactNode | string;
174
+ description?: string;
175
+ fields?: FieldConfig[];
176
+ styleFields?: StyleFieldConfig[];
177
+ inspectorSections?: BlockInspectorSection[];
178
+ /** 只启用 styleFields,不补容器间距和装饰;渲染也只读取启用的字段。 */
179
+ exactStyleFields?: boolean;
180
+ defaultFields?: Record<string, unknown>;
181
+ defaultStyle?: EmailBlockStyle;
182
+ /** Sample data used to render template HTML in the editor and preview. */
183
+ defaultData?: Record<string, unknown>;
184
+ /** Content is edited inline on the canvas and stored in fields.html. */
185
+ inlineEdit?: boolean;
186
+ inlineEditProfile?: RichTextProfile;
187
+ render: (ctx: BlockRenderContext) => string;
188
+ }
189
+ interface Variable {
190
+ /** 下拉展示并插入的变量,例如 `{{ store_name }}`。 */
191
+ name: string;
192
+ /** 下拉选项副文案。空字符串或不传时不渲染。 */
193
+ description?: string;
194
+ /** 预览时替换该变量的文本。 */
195
+ value?: string;
196
+ }
197
+
198
+ interface EmailEditorData {
199
+ document: EmailDocument;
200
+ json: string;
201
+ html: string;
202
+ renderedHtml: string;
203
+ }
204
+ interface EmailEditorHandle {
205
+ getData: () => EmailEditorData;
206
+ getDocument: () => EmailDocument;
207
+ getJson: () => string;
208
+ getHtml: () => string;
209
+ getRenderedHtml: () => string;
210
+ }
211
+ interface EmailEditorProps {
212
+ initialDocument?: EmailDocument;
213
+ /** External global settings. Incoming changes are applied to the open document. */
214
+ global?: Partial<EmailGlobalSettings>;
215
+ blocks?: BlockDefinition[];
216
+ variables?: Variable[];
217
+ height?: number | string;
218
+ onChange?: (document: EmailDocument) => void;
219
+ onGlobalChange?: (global: EmailGlobalSettings) => void;
220
+ }
221
+
222
+ declare const EmailEditor: react.ForwardRefExoticComponent<EmailEditorProps & react.RefAttributes<EmailEditorHandle>>;
223
+
224
+ interface RichTextInheritedStyle {
225
+ fontFamily: string;
226
+ fontSize: string;
227
+ color: string;
228
+ fontWeight: FontWeight;
229
+ lineHeight: string;
230
+ paragraphSpacing: string;
231
+ listSpacing?: string;
232
+ listIndent?: string;
233
+ linkColor?: string;
234
+ linkDecoration?: string;
235
+ }
236
+
237
+ interface RichTextProps {
238
+ initialHtml: string;
239
+ profile?: RichTextProfile;
240
+ placeholder?: string;
241
+ variables: Variable[];
242
+ selected: boolean;
243
+ toolbarId: string;
244
+ toolbarCollapsed: boolean;
245
+ inherited?: RichTextInheritedStyle;
246
+ onCommit: (html: string) => void;
247
+ onSelect: () => void;
248
+ }
249
+ declare function RichText({ initialHtml, profile, placeholder, variables, selected, toolbarId, toolbarCollapsed, inherited, onCommit, onSelect, }: RichTextProps): react.JSX.Element;
250
+
251
+ declare const DEFAULT_BLOCKS: BlockDefinition[];
252
+
253
+ declare function resolveEmailGlobal(global?: Partial<EmailGlobalSettings> | null): EmailGlobalSettings;
254
+ declare function createEmailDocument(blocks?: EmailBlockNode[]): EmailDocument;
255
+
256
+ declare function createBlockNode(type: string, definitions?: BlockDefinition[]): EmailBlockNode | null;
257
+
258
+ declare const DIMENSION_FIELDS: {
259
+ padding: StyleFieldConfig;
260
+ margin: StyleFieldConfig;
261
+ height: StyleFieldConfig;
262
+ };
263
+ declare const DECORATION_FIELDS: {
264
+ backgroundColor: StyleFieldConfig;
265
+ borderWidth: StyleFieldConfig;
266
+ borderStyle: StyleFieldConfig;
267
+ borderColor: StyleFieldConfig;
268
+ borderRadius: StyleFieldConfig;
269
+ };
270
+ declare const TYPOGRAPHY_FIELD_MAP: {
271
+ fontFamily: StyleFieldConfig;
272
+ fontSize: StyleFieldConfig;
273
+ fontWeight: StyleFieldConfig;
274
+ lineHeight: StyleFieldConfig;
275
+ color: StyleFieldConfig;
276
+ textAlign: StyleFieldConfig;
277
+ paragraphSpacing: StyleFieldConfig;
278
+ listSpacing: StyleFieldConfig;
279
+ listIndent: StyleFieldConfig;
280
+ linkColor: StyleFieldConfig;
281
+ linkDecoration: StyleFieldConfig;
282
+ };
283
+ type DimensionFieldName = keyof typeof DIMENSION_FIELDS;
284
+ type DecorationFieldName = keyof typeof DECORATION_FIELDS;
285
+ type TypographyFieldName = keyof typeof TYPOGRAPHY_FIELD_MAP;
286
+ type StyleFieldOverrideMap<Name extends string> = Partial<Record<Name, Partial<StyleFieldConfig>>>;
287
+ declare function createDimensionFields(overrides?: StyleFieldOverrideMap<DimensionFieldName>): Record<DimensionFieldName, StyleFieldConfig>;
288
+ declare function createDecorationFields(overrides?: StyleFieldOverrideMap<DecorationFieldName>): Record<DecorationFieldName, StyleFieldConfig>;
289
+ declare function createTypographyFields(overrides?: StyleFieldOverrideMap<TypographyFieldName>): Record<TypographyFieldName, StyleFieldConfig>;
290
+ /** 容器字段按固定槽位合并。相同 id 使用调用方字段,其余字段按原顺序接在后面。返回值都是副本。 */
291
+ declare function resolveBlockStyleFields(fields?: StyleFieldConfig[], exactStyleFields?: boolean): StyleFieldConfig[];
292
+
293
+ interface TemplateData {
294
+ [key: string]: unknown;
295
+ }
296
+ /** Resolves template HTML for the editor and preview while leaving stored source untouched. */
297
+ declare function renderTemplatePreview(html: string, defaultData?: TemplateData): string;
298
+
299
+ interface DocumentHtmlOptions {
300
+ resolveHtmlTemplates?: boolean;
301
+ variables?: Variable[];
302
+ templateData?: TemplateData;
303
+ }
304
+ declare function documentToHtml(document: EmailDocument, definitions?: BlockDefinition[], options?: DocumentHtmlOptions): string;
305
+
306
+ interface BlockRenderOptions {
307
+ resolveHtmlTemplate?: boolean;
308
+ templateData?: TemplateData;
309
+ variables?: Variable[];
310
+ global?: EmailGlobalSettings;
311
+ }
312
+ type BlockRenderOptionsInput = BlockRenderOptions | boolean;
313
+ /** 用变量 name 里的字段名作为键,value 作为预览替换值。 */
314
+ declare function variablesToTemplateData(variables: Variable[]): TemplateData;
315
+ declare function mergeTemplateData(variables: Variable[], defaultData?: TemplateData): TemplateData | undefined;
316
+ declare function renderBlockContent(node: EmailBlockNode, definitions?: BlockDefinition[], mode?: RenderMode, options?: BlockRenderOptionsInput): string;
317
+ declare function renderBlock(node: EmailBlockNode, definitions?: BlockDefinition[], mode?: RenderMode, global?: EmailGlobalSettings): string;
318
+ declare function blockToHtml(node: EmailBlockNode, definitions?: BlockDefinition[], mode?: RenderMode, options?: BlockRenderOptionsInput): string;
319
+
320
+ declare function buildRenderContext(node: EmailBlockNode, mode: RenderMode, global?: EmailGlobalSettings, definition?: BlockDefinition | undefined): BlockRenderContext;
321
+
322
+ declare function getBlockDefinition(type: string, definitions?: BlockDefinition[]): BlockDefinition | undefined;
323
+
324
+ declare function generateId(prefix?: string): string;
325
+
326
+ interface StyleGroupOption {
327
+ group: StyleGroup;
328
+ label: string;
329
+ }
330
+ declare const STYLE_GROUPS: StyleGroupOption[];
331
+
332
+ declare const BLOCK_TYPES: {
333
+ readonly heading: "heading";
334
+ readonly text: "text";
335
+ readonly button: "button";
336
+ readonly image: "image";
337
+ readonly divider: "divider";
338
+ readonly spacer: "spacer";
339
+ readonly html: "html";
340
+ };
341
+ type BuiltinBlockType = (typeof BLOCK_TYPES)[keyof typeof BLOCK_TYPES];
342
+
343
+ export { BLOCK_TYPES, BORDER_STYLES, type BlockDefinition, type BlockInspectorSection, type BlockRenderContext, type BlockRenderOptions, type BorderStyle, type BuiltinBlockType, DEFAULT_BLOCKS, DEFAULT_EMAIL_GLOBAL, DOCUMENT_VERSION, type DecorationFieldName, type DimensionFieldName, type DocumentHtmlOptions, type EmailBlockNode, type EmailBlockStyle, type EmailDecorationStyle, type EmailDocument, EmailEditor, type EmailEditorData, type EmailEditorHandle, type EmailEditorProps, type EmailElementStyle, type EmailGlobalSettings, type EmailSpacingStyle, type EmailTypographyStyle, FONT_FAMILIES, FONT_WEIGHTS, type FieldConfig, type FieldType, type FontWeight, HORIZONTAL_ALIGNS, type HorizontalAlign, LINK_DECORATIONS, type LinkDecoration, type RenderMode, RichText, type RichTextProfile, STYLE_GROUPS, type StyleControlType, type StyleFieldConfig, type StyleGroup, TEXT_ALIGNS, type TemplateData, type TextAlign, type TypographyFieldName, type Variable, blockToHtml, buildRenderContext, createBlockNode, createDecorationFields, createDimensionFields, createEmailDocument, createTypographyFields, documentToHtml, generateId, getBlockDefinition, mergeTemplateData, renderBlock, renderBlockContent, renderTemplatePreview, resolveBlockStyleFields, resolveEmailGlobal, variablesToTemplateData };