@marlinjai/email-editor-ui 0.3.0 → 0.4.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/dist/index.d.mts +159 -147
- package/dist/index.d.ts +159 -147
- package/dist/index.js +2651 -2256
- package/dist/index.mjs +2521 -2135
- package/dist/styles.css +1 -1
- package/package.json +5 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { Provider } from 'react';
|
|
2
|
-
import { TemplateSnapshotIn, TemplateSnapshotOut, BlockRegistryImpl, PrebuiltTemplateRegistry, RootStoreInstance, EditorUIInstance, BlockInstance, ColumnInstance, SectionInstance, TemplateInstance, BackgroundGradient, SubColumnInstance, BlockDefinition, PrebuiltTemplate } from '@marlinjai/email-editor-core';
|
|
2
|
+
import { TemplateSnapshotIn, TemplateSnapshotOut, BlockRegistryImpl, PrebuiltTemplateRegistry, RootStoreInstance, EditorUIInstance, BlockInstance, ColumnInstance, SectionInstance, TemplateInstance, EmailTemplate, CompileResult, TextBlock, BackgroundGradient, SubColumnInstance, BlockDefinition, PrebuiltTemplate } from '@marlinjai/email-editor-core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* What the editor tells the host when it asks for an image.
|
|
@@ -71,6 +71,12 @@ interface EmailEditorProps {
|
|
|
71
71
|
initialTemplate?: TemplateSnapshotIn;
|
|
72
72
|
/** Called when template changes */
|
|
73
73
|
onChange?: (template: TemplateSnapshotOut) => void;
|
|
74
|
+
/**
|
|
75
|
+
* The messages of the host's most recent server compile, when it refused
|
|
76
|
+
* something. Shown as a badge in the toolbar; the canvas itself is a browser
|
|
77
|
+
* compile that cannot run the service's asset policy.
|
|
78
|
+
*/
|
|
79
|
+
policyErrors?: string[];
|
|
74
80
|
/** Block registry for available block types */
|
|
75
81
|
blockRegistry: BlockRegistryImpl;
|
|
76
82
|
/** Pre-built template registry */
|
|
@@ -142,168 +148,172 @@ declare function useSelectedSection(): SectionInstance | undefined;
|
|
|
142
148
|
*/
|
|
143
149
|
declare function useSelectedColumn(): ColumnInstance | undefined;
|
|
144
150
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* EmailRenderer - Root renderer for email templates
|
|
151
|
-
*
|
|
152
|
-
* This is the main preview component that renders the entire email template
|
|
153
|
-
* using React components instead of MJML compilation.
|
|
154
|
-
*
|
|
155
|
-
* Key benefits:
|
|
156
|
-
* - Instant updates (<16ms) on property changes
|
|
157
|
-
* - Fine-grained reactivity (only changed blocks re-render)
|
|
158
|
-
* - No iframe needed
|
|
159
|
-
* - MJML compilation only happens on export
|
|
160
|
-
*/
|
|
161
|
-
declare const EmailRenderer: (({ className }: EmailRendererProps) => React.JSX.Element) & {
|
|
162
|
-
displayName: string;
|
|
163
|
-
};
|
|
164
|
-
/**
|
|
165
|
-
* Hook to get the email renderer width
|
|
166
|
-
*/
|
|
167
|
-
declare function usePreviewWidth(): number;
|
|
151
|
+
declare const DESKTOP_WIDTH = 600;
|
|
152
|
+
declare const MOBILE_WIDTH = 375;
|
|
153
|
+
/** The frame runs nothing: same origin so the overlay can measure and hit-test, no scripts so nothing a Raw block brought along can run. */
|
|
154
|
+
declare const FRAME_SANDBOX = "allow-same-origin";
|
|
155
|
+
declare const CompiledCanvas: React.FunctionComponent<object>;
|
|
168
156
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
157
|
+
type NodeKind = 'block' | 'column' | 'subcolumn' | 'section' | 'wrapper';
|
|
158
|
+
interface NodeRef {
|
|
159
|
+
id: string;
|
|
160
|
+
kind: NodeKind;
|
|
172
161
|
}
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
* - Column distribution
|
|
180
|
-
* - Background styling
|
|
181
|
-
*/
|
|
182
|
-
declare const SectionRenderer: (({ section, sectionIndex }: SectionRendererProps) => React.JSX.Element) & {
|
|
183
|
-
displayName: string;
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
interface ColumnRendererProps {
|
|
187
|
-
column: ColumnInstance;
|
|
188
|
-
section: SectionInstance;
|
|
189
|
-
columnIndex: number;
|
|
162
|
+
/** A node's box, in the frame document's coordinates (the frame never scrolls, so these are also offsets from the frame's top-left). */
|
|
163
|
+
interface Box {
|
|
164
|
+
x: number;
|
|
165
|
+
y: number;
|
|
166
|
+
width: number;
|
|
167
|
+
height: number;
|
|
190
168
|
}
|
|
191
|
-
/**
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* -
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
169
|
+
/** Every node id in the document and what it is, so an `el-<id>` class in the frame resolves to a node the store knows. */
|
|
170
|
+
declare function nodeIndex(template: TemplateInstance): Map<string, NodeKind>;
|
|
171
|
+
/** The node an element belongs to: itself or its nearest ancestor that carries an `el-<id>` of the document, or a tagged Raw block's markup. */
|
|
172
|
+
declare function nodeFromElement(element: Element | null, index: Map<string, NodeKind>): NodeRef | null;
|
|
173
|
+
/**
|
|
174
|
+
* A Raw block's markup is emitted verbatim, between `<!--ee:raw:id-->` and
|
|
175
|
+
* `<!--/ee:raw:id-->` in editor mode, because MJML drops `css-class` on
|
|
176
|
+
* `mj-raw`. This tags every element between the two comments with the id so
|
|
177
|
+
* hit-testing and measuring treat them as the block. Display-side only.
|
|
178
|
+
*/
|
|
179
|
+
declare function tagRawBlocks(doc: Document): void;
|
|
180
|
+
/** The elements that make up a node: one for most, several for a Raw block. */
|
|
181
|
+
declare function elementsOf(doc: Document, id: string): Element[];
|
|
182
|
+
/** The box of every node that rendered, in frame coordinates. Nodes with nothing on screen (a hidden column, empty raw markup) are absent. */
|
|
183
|
+
declare function measureNodes(doc: Document, index: Map<string, NodeKind>): Map<string, Box>;
|
|
184
|
+
/** The `<head>` of a compiled document, for deciding whether a morph can apply the next one. */
|
|
185
|
+
declare function headOf(html: string): string;
|
|
186
|
+
/**
|
|
187
|
+
* Put a compiled document into the frame. The first document, or one whose
|
|
188
|
+
* head changed (fonts, styles), loads through `srcdoc`; any other is morphed
|
|
189
|
+
* into the live body so images do not reflash and the scroll position holds.
|
|
190
|
+
* Returns how it was applied; after a reload the caller waits for `load`.
|
|
191
|
+
*/
|
|
192
|
+
declare function applyHtml(iframe: HTMLIFrameElement, html: string, previous: string | null): 'reload' | 'morph';
|
|
193
|
+
/**
|
|
194
|
+
* Nothing in the frame may navigate or submit: the overlay owns pointer
|
|
195
|
+
* events, and this is the second guard, for the moments it lets them through
|
|
196
|
+
* (in-place editing) and for keyboard focus reaching an anchor. Capture phase,
|
|
197
|
+
* so a handler in the mail could not run first even if scripts were allowed.
|
|
198
|
+
*/
|
|
199
|
+
declare function guardNavigation(doc: Document): void;
|
|
203
200
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
*
|
|
212
|
-
* Key features:
|
|
213
|
-
* - Wraps blocks with selection/hover UI
|
|
214
|
-
* - Handles click to select
|
|
215
|
-
* - Shows hidden block placeholder
|
|
216
|
-
* - Routes to appropriate block renderer
|
|
217
|
-
*/
|
|
218
|
-
declare const BlockRenderer: (({ block, isEditing }: BlockRendererProps) => React.JSX.Element) & {
|
|
219
|
-
displayName: string;
|
|
201
|
+
/** The data a band carries; the editor's drop handler reads it off `over.data`. */
|
|
202
|
+
type DropTarget = {
|
|
203
|
+
columnId: string;
|
|
204
|
+
index: number;
|
|
205
|
+
} | {
|
|
206
|
+
subColumnId: string;
|
|
207
|
+
index: number;
|
|
220
208
|
};
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
209
|
+
interface Band {
|
|
210
|
+
id: string;
|
|
211
|
+
target: DropTarget;
|
|
212
|
+
x: number;
|
|
213
|
+
y: number;
|
|
214
|
+
width: number;
|
|
224
215
|
}
|
|
225
|
-
/**
|
|
226
|
-
|
|
227
|
-
*
|
|
228
|
-
* Key implementation details:
|
|
229
|
-
* - Uses contenteditable for direct text editing when selected
|
|
230
|
-
* - Does NOT use dangerouslySetInnerHTML when editing (causes cursor reset)
|
|
231
|
-
* - Syncs content to MST only on blur (not on every keystroke)
|
|
232
|
-
* - Stops keyboard event propagation to prevent block deletion on backspace
|
|
233
|
-
*/
|
|
234
|
-
declare const TextBlock: (({ block }: TextBlockProps) => React.JSX.Element) & {
|
|
235
|
-
displayName: string;
|
|
236
|
-
};
|
|
216
|
+
/** The bands of a document, from its nodes' boxes. Pure, so it is testable without dnd-kit. */
|
|
217
|
+
declare function bandsFor(template: any, boxes: Map<string, Box>): Band[];
|
|
237
218
|
|
|
238
|
-
interface
|
|
239
|
-
|
|
219
|
+
interface CompileSchedulerOptions<S, R> {
|
|
220
|
+
/** The compile itself. May be synchronous or return a promise. */
|
|
221
|
+
compile: (snapshot: S) => R | Promise<R>;
|
|
222
|
+
/** Called with a result that is newer than every result applied before it. */
|
|
223
|
+
onResult: (result: R, version: number) => void;
|
|
224
|
+
/** Called when a compile throws or rejects; the version is dropped. */
|
|
225
|
+
onError?: (error: unknown, version: number) => void;
|
|
226
|
+
/** Quiet time after the last schedule before compiling. Default 100. */
|
|
227
|
+
delayMs?: number;
|
|
240
228
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
229
|
+
interface CompileScheduler<S> {
|
|
230
|
+
/** A new snapshot: compiles after the quiet time, superseding any earlier pending one. */
|
|
231
|
+
schedule(snapshot: S): number;
|
|
232
|
+
/** Compile the pending snapshot now, without waiting for the quiet time. */
|
|
233
|
+
flush(): void;
|
|
234
|
+
/** The version of the newest snapshot scheduled so far (0 before the first). */
|
|
235
|
+
readonly version: number;
|
|
236
|
+
/** The version of the newest result applied so far (0 before the first). */
|
|
237
|
+
readonly applied: number;
|
|
238
|
+
dispose(): void;
|
|
250
239
|
}
|
|
251
|
-
|
|
252
|
-
* ButtonBlock - Renders a button/CTA block in the email preview
|
|
253
|
-
*/
|
|
254
|
-
declare const ButtonBlock: (({ block }: ButtonBlockProps) => React.JSX.Element) & {
|
|
255
|
-
displayName: string;
|
|
256
|
-
};
|
|
240
|
+
declare function createCompileScheduler<S, R>(options: CompileSchedulerOptions<S, R>): CompileScheduler<S>;
|
|
257
241
|
|
|
258
|
-
interface
|
|
259
|
-
|
|
242
|
+
interface CompiledDocument {
|
|
243
|
+
/** Monotonic; a later document always has a higher version. */
|
|
244
|
+
version: number;
|
|
245
|
+
html: string;
|
|
246
|
+
/** MJML's soft-validation messages, when any. */
|
|
247
|
+
errors?: string[];
|
|
260
248
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
249
|
+
type CompileStatus = {
|
|
250
|
+
kind: 'loading';
|
|
251
|
+
} | {
|
|
252
|
+
kind: 'ready';
|
|
253
|
+
} | {
|
|
254
|
+
kind: 'failed';
|
|
255
|
+
message: string;
|
|
266
256
|
};
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
257
|
+
type Compile = (template: EmailTemplate, options: {
|
|
258
|
+
editor: boolean;
|
|
259
|
+
}) => CompileResult | Promise<CompileResult>;
|
|
260
|
+
/** Tests only: swap the loader. */
|
|
261
|
+
declare function setBrowserCompilerLoader(loader: () => Promise<Compile>): void;
|
|
262
|
+
/** The browser compiler, loaded once per page and shared by every caller. */
|
|
263
|
+
declare function browserCompiler(): Promise<Compile>;
|
|
264
|
+
/** The browser compiler once it has loaded, null before; failures stay null (the canvas reports them). */
|
|
265
|
+
declare function useBrowserCompiler(): Compile | null;
|
|
266
|
+
declare function useCompiledDocument(template: TemplateInstance, hold: boolean, delayMs?: number): {
|
|
267
|
+
document: CompiledDocument | null;
|
|
268
|
+
status: CompileStatus;
|
|
269
|
+
retry: () => void;
|
|
276
270
|
};
|
|
277
271
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
declare const SocialBlock: (({ block }: SocialBlockProps) => React.JSX.Element) & {
|
|
285
|
-
displayName: string;
|
|
286
|
-
};
|
|
272
|
+
declare function SectionThumbnail({ section, width, height, className }: {
|
|
273
|
+
section: Record<string, unknown>;
|
|
274
|
+
width: number;
|
|
275
|
+
height: number;
|
|
276
|
+
className?: string;
|
|
277
|
+
}): React.JSX.Element;
|
|
287
278
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
279
|
+
/** The `contenteditable` element of the in-place edit in progress, or null. */
|
|
280
|
+
declare function activeEditingHost(): HTMLElement | null;
|
|
291
281
|
/**
|
|
292
|
-
*
|
|
282
|
+
* Whether a pointer press at `target` belongs to the edit rather than ending
|
|
283
|
+
* it: the formatting toolbar and any dialog it opens carry `data-ee-keeps-edit`.
|
|
293
284
|
*/
|
|
294
|
-
declare
|
|
295
|
-
displayName: string;
|
|
296
|
-
};
|
|
285
|
+
declare function keepsEdit(target: EventTarget | null): boolean;
|
|
297
286
|
|
|
298
|
-
interface RawBlockProps {
|
|
299
|
-
block: BlockInstance;
|
|
300
|
-
}
|
|
301
287
|
/**
|
|
302
|
-
*
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
288
|
+
* The element whose innerHTML is the block's content. MJML renders an
|
|
289
|
+
* `mj-text` as `<td class="el-text el-<id>"><div style=...>CONTENT</div></td>`;
|
|
290
|
+
* when the block carries inline styles the compiler wraps CONTENT in one more
|
|
291
|
+
* `<div style>`, by the rule `textInlineStyles` states.
|
|
292
|
+
*/
|
|
293
|
+
declare function editableFor(doc: Document, block: {
|
|
294
|
+
id: string;
|
|
295
|
+
} & Partial<Pick<TextBlock, 'align' | 'color' | 'fontSize' | 'fontFamily' | 'lineHeight'>>): HTMLElement | null;
|
|
296
|
+
interface InlineEdit {
|
|
297
|
+
/** The content as it was when editing began. */
|
|
298
|
+
readonly original: string;
|
|
299
|
+
/** What is in the element now. */
|
|
300
|
+
current(): string;
|
|
301
|
+
/** Store the content if it changed, without ending the edit. */
|
|
302
|
+
sync(): void;
|
|
303
|
+
/** Store the content if it changed and end the edit. */
|
|
304
|
+
commit(): void;
|
|
305
|
+
/** Put the original content back and end the edit. */
|
|
306
|
+
cancel(): void;
|
|
307
|
+
/** Whether this edit is still live. */
|
|
308
|
+
readonly active: boolean;
|
|
309
|
+
}
|
|
310
|
+
interface InlineEditHandlers {
|
|
311
|
+
/** Content to store; called on every sync or commit whose content differs from the last stored one. */
|
|
312
|
+
onChange: (content: string) => void;
|
|
313
|
+
/** The edit ended, by commit, cancel or a press elsewhere. */
|
|
314
|
+
onEnd: () => void;
|
|
315
|
+
}
|
|
316
|
+
declare function beginInlineEdit(element: HTMLElement, handlers: InlineEditHandlers): InlineEdit;
|
|
307
317
|
|
|
308
318
|
interface PropertyInspectorProps {
|
|
309
319
|
onDeleteBlock?: (blockId: string) => void;
|
|
@@ -332,10 +342,12 @@ declare const PropertyInspector: React.FunctionComponent<PropertyInspectorProps>
|
|
|
332
342
|
declare function normalizeSpacingValue(value: string): string | undefined;
|
|
333
343
|
/**
|
|
334
344
|
* Formatting toolbar for text editing
|
|
335
|
-
* Uses execCommand for contenteditable WYSIWYG editing
|
|
345
|
+
* Uses execCommand for contenteditable WYSIWYG editing, on the document the
|
|
346
|
+
* edited text lives in (the canvas frame).
|
|
336
347
|
*
|
|
337
348
|
* Important: Uses onMouseDown with preventDefault to keep focus
|
|
338
|
-
* in the contenteditable text block while clicking buttons
|
|
349
|
+
* in the contenteditable text block while clicking buttons, and carries
|
|
350
|
+
* `data-ee-keeps-edit` so a press on it does not end the in-place edit.
|
|
339
351
|
*/
|
|
340
352
|
declare function FormattingToolbar(): React.JSX.Element;
|
|
341
353
|
/**
|
|
@@ -511,4 +523,4 @@ interface DragOverlayContentProps {
|
|
|
511
523
|
}
|
|
512
524
|
declare function DragOverlayContent({ item }: DragOverlayContentProps): React.JSX.Element;
|
|
513
525
|
|
|
514
|
-
export { AlignmentField,
|
|
526
|
+
export { AlignmentField, type Band, BlockProperties, type Box, ButtonGroupField, CheckboxField, ColorField, ColumnProperties, type CompileScheduler, type CompileSchedulerOptions, type CompileStatus, CompiledCanvas, type CompiledDocument, DESKTOP_WIDTH, DragOverlayContent, type DropTarget, type EditorHostHooks, EditorHostProvider, ElementsPanel, EmailEditor, type EmailEditorProps, FRAME_SANDBOX, FormattingToolbar, GradientField, type ImageRequest, type InlineEdit, type InlineEditHandlers, LayersPanel, LayoutPanel, LeftSidebar, MOBILE_WIDTH, type NodeKind, type NodeRef, type OnRequestImage, type OnSaveSection, PropertyInspector, RangeField, type RequestedImage, SectionProperties, SectionThumbnail, SelectField, SpacingField, StoreProvider, SubColumnProperties, TemplateSettingsPanel, TextField, activeEditingHost, applyHtml, bandsFor, beginInlineEdit, browserCompiler, createCompileScheduler, editableFor, elementsOf, guardNavigation, headOf, keepsEdit, measureNodes, nodeFromElement, nodeIndex, normalizeSpacingValue, setBrowserCompilerLoader, tagRawBlocks, useBrowserCompiler, useCompiledDocument, useEditorHost, useEditorUI, useSelectedBlock, useSelectedColumn, useSelectedSection, useStore, useTemplate };
|