@standhigher/react-email-editor 0.3.0 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/api.md ADDED
@@ -0,0 +1,277 @@
1
+ # API reference
2
+
3
+ [简体中文](./api.zh-CN.md) | [Back to README](../README.md)
4
+
5
+ Import all APIs below from `@standhigher/react-email-editor`, and import `@standhigher/react-email-editor/styles.css` once. The published `dist/index.d.ts` includes English JSDoc for externally accessible fields, available through IDE hover information. See [basic examples](./basic-examples.md) and [advanced examples](./advanced-examples.md) for integration code.
6
+
7
+ ## Editor component and 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
+ | Parameter | Type | Meaning and default |
22
+ | --- | --- | --- |
23
+ | `editor` | `EmailEditorInstance` | Required instance returned by `useEmailEditor()`. |
24
+ | `height` | `number \| string` | Default `"100%"`; numbers use px; percentages require a parent with an explicit height. |
25
+
26
+ The component provides UI; the instance works independently of mounting it. Component refs, legacy configuration factories and document props are unsupported.
27
+
28
+ ### useEmailEditor(options?) → EmailEditorInstance
29
+
30
+ All options are optional. Defaults to an empty document and all builtin blocks. Follow React Hook rules.
31
+
32
+ | Parameter | Type | Meaning |
33
+ | --- | --- | --- |
34
+ | `initialDocument` | `EmailDocument` | Read only during initialization; use `setDocument()` for later replacements. |
35
+ | `body` | `Partial<EmailBodySettings>` | Initial overrides; later changes merge into the current document. Omitted fields retain their values. |
36
+ | `config` | `EmailEditorConfig` | Result of `useEmailEditorConfig()`; updates preserve the document. |
37
+ | `variables` | `Variable[]` | Insertable preview variables, default `[]`; updates affect preview and export. |
38
+ | `onChange` | `(document) => void` | Detached snapshot after edits, replacements, resets and effective external body updates; no initialization call. |
39
+ | `onBodyChange` | `(body) => void` | Detached body snapshot after body edits inside the editor only. |
40
+
41
+ The instance reference is stable; instances are isolated. Configuration, variables, body and callbacks synchronize during React commit. Interrupted renders do not change the committed instance. Child layout effects can read updated options; subscriptions and external body `onChange` notifications run during layout. A component using only this Hook does not rerender on document edits.
42
+
43
+ ### Instance methods
44
+
45
+ | Method | Arguments | Return and behavior |
46
+ | --- | --- | --- |
47
+ | `getData()` | None | `EmailEditorData`: `document`, formatted `json`, template `html`, and preview `renderedHtml`; rendering errors throw. |
48
+ | `getDocument()` | None | Detached `EmailDocument` snapshot to persist or modify before passing to `setDocument()`. |
49
+ | `getJson()` | None | Pretty-printed document JSON string. |
50
+ | `getHtml()` | None | Complete email HTML preserving templates. |
51
+ | `getRenderedHtml()` | None | Complete HTML using current variables and block `defaultData`. |
52
+ | `setDocument(document)` | v4 `EmailDocument` | `void`; copies and replaces the document, clears selection/focus/control state and calls `onChange`. |
53
+ | `reset()` | None | `void`; restores the initialized document, including initial body overrides, clears interaction state and calls `onChange`. |
54
+ | `validate()` | None | `ValidationIssue[]`; validates controls and missing plugins without rendering HTML. Rendering errors still need handling. |
55
+
56
+ Exports and callback arguments do not expose mutable internal state. Missing plugins, invalid controls and rendering failures throw during export; catch these in your application.
57
+
58
+ ### useEmailEditorState(editor) → EmailEditorState
59
+
60
+ Requires an instance from `useEmailEditor()`. Returns cached `{ document }`, a deeply readonly snapshot, and notifies only on document changes. Use it in components that need reactive document display; update through instance methods.
61
+
62
+ ## Shared configuration and plugins
63
+
64
+ ### useEmailEditorConfig(options?) → EmailEditorConfig
65
+
66
+ | Parameter | Type | Default and meaning |
67
+ | --- | --- | --- |
68
+ | `plugins` | `BlockPlugin[]` | `[]`; plugins created by `defineBlock()`. |
69
+ | `builtins` | `false \| string[]` | All by default; false hides all builtins; arrays filter and order them. Hidden blocks still render existing nodes. Custom types are appended. |
70
+ | `pickImage` | `(context: ImagePickerContext) => Promise<string \| null>` | Resolve a URL; null cancels; throwing shows retry. Context contains `value/label/signal`. |
71
+
72
+ Declare plugins at module scope, or use `useMemo` when they depend on application state. Stable option references reuse configuration; inline arrays do not recreate the editor or reset its document. The returned object contains:
73
+
74
+ | Member | Arguments and meaning |
75
+ | --- | --- |
76
+ | `definitions` | Readonly list of all registered plugins, including hidden builtins. |
77
+ | `library` | Readonly list of insertable blocks in display order. |
78
+ | `pickImage` | Registered resource picker. |
79
+ | `createBlock(type)` | Registered type string → detached default `EmailBlockNode`; unknown types throw. |
80
+ | `toHtml(document, options?)` | v4 document and `DocumentHtmlOptions` → complete HTML using this registry. |
81
+ | `validate(document)` | Document → `ValidationIssue[]`, without rendering HTML. |
82
+
83
+ Duplicate custom types, duplicate builtin entries and unknown builtin names throw. Replacing a builtin requires `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: "Coupon",
93
+ controls: { code: EmailControls.text({ label: "Code", defaultValue: "SAVE20" }) },
94
+ renderEmail: ({ controls, email }) => email.html`<p>${controls.code}</p>`,
95
+ });
96
+ ```
97
+
98
+ | Input member | Meaning |
99
+ | --- | --- |
100
+ | `type` / `title` | Required nonempty unique type and library title. |
101
+ | `renderEmail(ctx)` | Required; returns inner HTML or `EmailMarkup`; ctx contains `block/controls/style/body/mode/email`. |
102
+ | `controls` | Map of names to EmailControls declarations, default `{}`; values and defaults are inferred. |
103
+ | `icon` / `description` | Optional ReactNode/string icon and description. |
104
+ | `override` | Must be true to replace a builtin with the same type. |
105
+ | `containerStyles` | Spacing and appearance enabled by default; false disables all; `{ spacing?: boolean, appearance?: boolean }` controls each group. |
106
+ | `styleControls` | Additional styles; matching IDs replace default container controls. |
107
+ | `inspectorSections` | Ordered inspector sections and content/style controls. |
108
+ | `defaultStyle` | New-node styles, taking precedence over style control defaultValue. |
109
+ | `defaultData` | Template preview data for new nodes. |
110
+ | `Canvas` / `Inspector` | Optional React editing components receiving `PluginEditorProps<ControlValues<Schema>>`. |
111
+
112
+ `Schema` infers control values; `Type` preserves the type literal. Declarations are copied and frozen. Multiple inline rich-text regions require a Canvas. See the [custom block guide](./custom-blocks.md) for detailed behavior and inherited runtime members.
113
+
114
+ ### EmailControls
115
+
116
+ Each factory takes an options object and returns `ControlDefinition<T>`. Common options apply only to relevant control kinds.
117
+
118
+ | Parameter | Meaning |
119
+ | --- | --- |
120
+ | `label` | Required display label. |
121
+ | `defaultValue` | Fills only persisted undefined; does not replace `""`, `0` or false. |
122
+ | `placeholder` / `rows` | Empty hint / multiline editor rows. |
123
+ | `options` | Allowed `ControlOption[]`, each with label and value. |
124
+ | `min` / `max` / `step` / `unit` | Numeric limits, increment and unit label; list min/max limit row counts. |
125
+ | `profile` / `inline` | Rich-text feature profile / direct editing on the default canvas. |
126
+ | `component` | Custom React control receiving `ControlComponentProps<T>`. |
127
+ | `validate(value)` | Normalized value → error string, or undefined when valid. |
128
+
129
+ | Factory | Value and fallback | Additional behavior |
130
+ | --- | --- | --- |
131
+ | `text/textarea/html/url/image/color/select` | string, default `""` | URL/image protocol validation; select options validation. |
132
+ | `richText` | HTML string, default `"<p></p>"` | profile text/heading/button, inline editing. |
133
+ | `number` | number, default 0 | Invalid input becomes NaN and fails validation. |
134
+ | `boolean` | boolean, default false | Only actual booleans are accepted; other values normalize to false. |
135
+ | `list` | Inferred row array, default [] | Required `controls: ControlSchema`; recursive parsing and validation. |
136
+ | `custom<T>` | Application-defined | Required defaultValue, pure `read(value: unknown): T`, and component; values must be JSON-persistable. |
137
+
138
+ ### Plugin React components
139
+
140
+ `PluginEditorProps` provides `block/controls/style/body/variables` snapshots, selected, renderedHtml, select(), setControl(name, value), setControls(patch), and setStyle(id, value). Batch control updates shallow-merge once and notify once; replace nested objects entirely. renderedHtml contains resolved inner HTML in Canvas and is empty in Inspector.
141
+
142
+ | Component | Parameters |
143
+ | --- | --- |
144
+ | `ControlEditor` | Required control string, reusing its declaration, validation and resource adapter. Unknown names throw. |
145
+ | `EditableRichText` | Required control string referencing richText; optional profile and placeholder overrides. |
146
+
147
+ Use these components only within a plugin Canvas or Inspector.
148
+
149
+ ### Style control factories
150
+
151
+ `createDimensionControls(overrides?)`, `createDecorationControls(overrides?)`, and `createTypographyControls(overrides?)` return independent StyleControlConfig objects keyed by capability name. Overrides are `Partial<Record<Name, Partial<StyleControlConfig>>>`, modifying labels, defaults, units and ranges. Add selected entries to plugin styleControls.
152
+
153
+ | Factory / name type | Capability names |
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
+ ## Document model and creation
160
+
161
+ | API | Arguments | Return and limitations |
162
+ | --- | --- | --- |
163
+ | `createEmailDocument(blocks = [])` | Ordered EmailBlockNode array | v4 document with full default body. Retains the supplied array; does not deep-clone it. |
164
+ | `resolveEmailBody(body?)` | `Partial<EmailBodySettings> \| null` | Complete normalized body; invalid values fall back; width is clamped to 320–900px. |
165
+ | `createBlockNode(type, definitions?)` | Type and optional registry, default builtins | Detached default node; unknown type returns null. Prefer config.createBlock for application plugins. |
166
+ | `getBlockDefinition(type, definitions?)` | Type and optional registry, default builtins | Matching runtime definition or undefined. Do not mutate frozen configuration definitions. |
167
+ | `generateId(prefix = "block")` | String ID prefix | String with a random suffix; applications must still ensure IDs are unique within a document. |
168
+
169
+ EmailDocument contains version/body/blocks/subject?; EmailBlockNode contains id/type/controls/style?/defaultData?. Only DOCUMENT_VERSION = 4 is supported; older documents are not migrated. JSON stores no components or render functions; register matching plugins again when loading it.
170
+
171
+ Canvas, preview and email HTML share body settings:
172
+
173
+ | EmailBodySettings member | Default and meaning |
174
+ | --- | --- |
175
+ | textColor | #000000; default text color. |
176
+ | backgroundColor | #ffffff; centered content background; outside remains white. |
177
+ | contentWidth | 600; maximum content width in px. |
178
+ | fontFamily | Arial, Helvetica, sans-serif. |
179
+ | fontWeight | normal; also supports bold. |
180
+ | linkColor / linkDecoration | #005bd3 / underline. |
181
+ | contentPadding | 0px; CSS padding inside the border. |
182
+ | blockGap | 4px; spacing between adjacent blocks. |
183
+ | borderWidth / borderStyle / borderColor | 0px / solid / transparent. |
184
+
185
+ The body is centered without body radius or extra outer padding. Explicit rich-text formats override body defaults; internal defaults provide 16px font size and 1.5 line height. HTML blocks retain their internal links/lists. Block container styles live in dimension/decorations, inner elements in element, and custom blocks can enable typography. Only enabled styles are read.
186
+
187
+ ## HTML rendering and templates
188
+
189
+ ### documentToHtml(document, definitions?, options?) → string
190
+
191
+ The required document must use v4. Definitions default to DEFAULT_BLOCKS; for custom plugins pass config.definitions or use config.toHtml. Returns a complete email document with one doctype/html/head/body, tables and inline styles.
192
+
193
+ | DocumentHtmlOptions member | Default and meaning |
194
+ | --- | --- |
195
+ | resolveHtmlTemplates | false preserves templates; true resolves preview data. |
196
+ | variables | [] of exact inserted tokens and preview values. |
197
+ | templateData | Optional TemplateData for global objects and arrays. |
198
+
199
+ Data is shallow-merged at the top level: node defaultData > templateData > variables. Default export only adds missing dots to tokens exactly matching variables[].name, preserving spaces: `{{ store_name }}` → `{{ .store_name }}`. Unlisted Go templates stay unchanged. Empty variable descriptions are hidden; value is only preview data.
200
+
201
+ ### Single-block and low-level helpers
202
+
203
+ | API | Arguments and defaults | Output |
204
+ | --- | --- | --- |
205
+ | `renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body = DEFAULT_EMAIL_BODY)` | Node, registry, RenderMode, full body | Plugin inner HTML without template resolution or dot normalization. |
206
+ | `renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {})` | Final argument is BlockRenderOptions | Inner HTML with dot normalization or template resolution. |
207
+ | `blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {})` | Same | Email table fragment with container styles and spacing, not a complete document. |
208
+ | `buildRenderContext(node, mode, body = DEFAULT_EMAIL_BODY, definition = builtin lookup)` | Required mode; explicitly pass a custom definition | Detached normalized BlockRenderContext without rendering. |
209
+ | `variablesToTemplateData(variables)` | Required Variable[] | TemplateData keyed by token paths with value as preview data; ignores absent values. |
210
+ | `mergeTemplateData(variables, defaultData?)` | Variables and optional object | Shallow merge, defaultData wins; undefined if both contain no data. |
211
+ | `renderTemplatePreview(html, defaultData?)` | HTML string and optional data | Supports a subset of Go paths, range, if and with; preserves input when data is omitted. |
212
+
213
+ BlockRenderOptions contains resolveHtmlTemplate?: boolean (singular), templateData?: TemplateData, variables?: Variable[], body?: EmailBodySettings. Body defaults to DEFAULT_EMAIL_BODY; templates are preserved by default. Single-block preview uses templateData ?? node.defaultData and does not merge variables automatically; call mergeTemplateData explicitly when needed. The fourth argument must be an object; boolean or null throws TypeError.
214
+
215
+ Mode selects the consumer: canvas for editing, email for control validation. It does not enable template resolution. Preview is a subset of Go templates, not a complete Go engine, HTML sanitizer or security sandbox.
216
+
217
+ ### email helpers in renderEmail
218
+
219
+ | Method | Arguments and output |
220
+ | --- | --- |
221
+ | ``email.html`…${value}…` `` | Escapes ordinary interpolations; composes EmailMarkup and arrays; preserves template tokens. |
222
+ | `email.raw(html)` | Raw HTML → EmailMarkup, without sanitization. |
223
+ | `email.richText(html)` | Rich-text HTML → EmailMarkup with body defaults. |
224
+ | `email.table(rows)` | EmailMarkup[][] → email table fragment. |
225
+ | `email.image({ src, alt?, width? })` | Required src; alt defaults empty, width to 100%; validates image protocols. |
226
+ | `email.button({ href, label })` | Required URL and plain-text label; empty/invalid URL produces no link. |
227
+ | `email.url(value, image = false)` | Allowed URL or empty string; true selects image protocol rules. |
228
+
229
+ Links allow http/https/mailto/tel, anchors or templates; images allow http/https/cid or templates. Validate protocols before escaping attributes; plugins returning plain strings handle escaping themselves.
230
+
231
+ ## Base UI components
232
+
233
+ Use these in business inspectors. They provide UI without automatically binding document state. Callbacks receive values, except TextInput which uses native React events.
234
+
235
+ | Component | Parameters and defaults |
236
+ | --- | --- |
237
+ | TextInput | Native React input attributes and ref; type=text, allowing text/url/email/search/tel/password/number. |
238
+ | SelectControl | Required value/options/onChange(value); optional id, accessible label, emptyLabel, compact=false, disabled. |
239
+ | CheckboxControl | Required label/checked/onChange(checked); optional disabled, className, variant=checkbox or switch. |
240
+ | ColorControl | Required value/onChange(value); fallback=#000000, label=Color, resetTo=fallback. |
241
+ | RangeControl | Required value/onChange(value); optional id/label; min=0, max=100, step=1. |
242
+ | Button | Native React button attributes, type=button; variant=default/primary/solid/icon; optional tooltip. |
243
+ | RichText | Required initialHtml, variables, selected, toolbarId, toolbarCollapsed, onCommit(html), onSelect(); optional profile=text, placeholder, inherited. toolbarId must be unique. Prefer EditableRichText for plugin binding. |
244
+
245
+ Props and `RichText.inherited` fields are documented in the published `.d.ts`; use IDE hover information or go to definition. Native attributes follow React types; props interfaces are not added as named package exports.
246
+
247
+ ## Constants and public types
248
+
249
+ | Export | Purpose |
250
+ | --- | --- |
251
+ | BLOCK_TYPES / BuiltinBlockType | heading/text/button/image/divider/spacer/html. |
252
+ | DEFAULT_BLOCKS | All builtin BlockPlugin definitions; custom registries use config.definitions. |
253
+ | DEFAULT_EMAIL_BODY / DOCUMENT_VERSION | Default body / document version 4. Do not mutate the default object. |
254
+ | FONT_FAMILIES | Readonly label/value font options. |
255
+ | STYLE_GROUPS / StyleGroup | general/dimension/typography/decorations identifiers and display names. |
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
+ Fields in editor, document, control, plugin, style, rendering and template types are documented through JSDoc in the published `.d.ts`. BlockDefinition is internal and not exported from the package entry; use BlockPlugin for public plugin definitions.
263
+
264
+ ## Development and local linking
265
+
266
+ ```bash
267
+ pnpm typecheck
268
+ pnpm lint
269
+ pnpm build
270
+ node --test tests/*.test.mjs tests/*.test.ts
271
+ ```
272
+
273
+ Use `pnpm check` to run all four checks in order, or `pnpm test` after building to run only the Node tests. Node 24 LTS and pnpm 10.16.1 are the repository defaults. See the [release guide](./release.md) for package verification and publishing.
274
+
275
+ Build before tests that depend on dist. Internal TypeScript tests require Node 22.18+. The package ships ESM, declarations and flattened CSS. React/React DOM are peer dependencies; TipTap, dnd-kit, CodeMirror and Lezer are dependencies, externalized during builds rather than bundled.
276
+
277
+ Local consumers can use link:../react-email-editor. Run pnpm build first and pnpm build:watch 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 root covering the linked directory.
@@ -0,0 +1,277 @@
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
+ `pnpm check` 按上述顺序执行全部检查;只运行 Node 测试时,构建后执行 `pnpm test`。本仓库默认使用 Node 24 LTS 和 pnpm 10.16.1。打包检查与发布流程见[发布指南](./release.zh-CN.md)。
274
+
275
+ 先构建再运行依赖 dist 的测试;TypeScript 内部测试需要 Node 22.18+。包提供 ESM、类型与展开后的 CSS。React/React DOM 为 peer dependencies;TipTap、dnd-kit、CodeMirror、Lezer 等为 dependencies,构建时 external,不内联到发布包。
276
+
277
+ 消费方可使用 `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 需覆盖链接目录。