@marlinjai/email-editor 0.2.0 → 0.3.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/react.d.mts +49 -2
- package/dist/react.d.ts +49 -2
- package/dist/react.js +58 -9
- package/dist/react.mjs +51 -4
- package/package.json +4 -4
package/dist/react.d.mts
CHANGED
|
@@ -1,10 +1,57 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
import { TemplateSnapshotOut, TemplateSnapshotIn, BlockDefinition } from '@marlinjai/email-editor-core';
|
|
3
4
|
export { BlockDefinition, TemplateSnapshotIn, TemplateSnapshotOut } from '@marlinjai/email-editor-core';
|
|
4
5
|
import { OnRequestImage, OnSaveSection } from '@marlinjai/email-editor-ui';
|
|
5
6
|
export { ImageRequest, OnRequestImage, OnSaveSection, RequestedImage } from '@marlinjai/email-editor-ui';
|
|
6
7
|
import { b as EditorTheme, S as SavedSectionInput } from './types-G7Ak2dWQ.mjs';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Two things about the editor are easy to get wrong, and getting either wrong
|
|
11
|
+
* loses somebody's work silently, so they live here rather than in each screen
|
|
12
|
+
* that embeds the editor:
|
|
13
|
+
*
|
|
14
|
+
* 1. **The editor reports a snapshot on mount.** It normalises what it was
|
|
15
|
+
* given, and says so. That first report is the baseline, not an edit: a
|
|
16
|
+
* screen that treated it as one would show "unsaved changes" on every open
|
|
17
|
+
* and would ask before leaving a document nobody touched. `touched` is set
|
|
18
|
+
* by a real interaction (a pointer or a key inside the editor), and only
|
|
19
|
+
* then does a report mean a change.
|
|
20
|
+
*
|
|
21
|
+
* 2. **The editor reports changes debounced.** The document it last reported
|
|
22
|
+
* may be one edit behind, so reading it the moment somebody presses Save
|
|
23
|
+
* would store the previous document and drop the edit just made. `settled()`
|
|
24
|
+
* waits out that window since the last interaction before reading.
|
|
25
|
+
*/
|
|
26
|
+
/** The store's own `onChange` debounce, plus a little room. */
|
|
27
|
+
declare const EDITOR_CHANGE_DEBOUNCE_MS = 300;
|
|
28
|
+
/** The document as a service stores it: the editor's snapshot without its local id. */
|
|
29
|
+
declare function toStoredDocument(snapshot: TemplateSnapshotOut | Record<string, unknown>): Record<string, unknown>;
|
|
30
|
+
type EditorDocument = {
|
|
31
|
+
/** Whether the person has changed anything since the last save. */
|
|
32
|
+
dirty: boolean;
|
|
33
|
+
setDirty: (dirty: boolean) => void;
|
|
34
|
+
/** Hand to the editor's `onChange`. */
|
|
35
|
+
onChange: (snapshot: TemplateSnapshotOut) => void;
|
|
36
|
+
/** Hand to the host element's `onPointerDownCapture` and `onKeyDownCapture`. */
|
|
37
|
+
interacted: () => void;
|
|
38
|
+
/** The current document, once the editor's debounce window has passed. */
|
|
39
|
+
settled: () => Promise<Record<string, unknown>>;
|
|
40
|
+
/** Replaces what is held, for a document loaded or rewritten from outside the editor. */
|
|
41
|
+
replace: (document: Record<string, unknown>, opts?: {
|
|
42
|
+
touched?: boolean;
|
|
43
|
+
}) => void;
|
|
44
|
+
/** What is held right now, without waiting. Prefer `settled()` before storing it. */
|
|
45
|
+
current: () => Record<string, unknown>;
|
|
46
|
+
};
|
|
47
|
+
declare function useEditorDocument(initial: Record<string, unknown>): EditorDocument;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* CSS custom properties for the editor's root element. Returns an empty
|
|
51
|
+
* object when there is no theme, so the stylesheet defaults apply.
|
|
52
|
+
*/
|
|
53
|
+
declare function themeToStyle(theme: EditorTheme | undefined): CSSProperties;
|
|
54
|
+
|
|
8
55
|
interface EmailEditorReactProps {
|
|
9
56
|
/** Initial template data (uncontrolled) */
|
|
10
57
|
initialTemplate?: TemplateSnapshotIn;
|
|
@@ -48,4 +95,4 @@ interface EmailEditorReactProps {
|
|
|
48
95
|
*/
|
|
49
96
|
declare function EmailEditorReact({ initialTemplate, onChange, theme, blocks, onSave, onExport, onNavigateBack, onRequestImage, savedSections, onSaveSection, builtInSections, placeholderBase, }: EmailEditorReactProps): react.JSX.Element;
|
|
50
97
|
|
|
51
|
-
export { EditorTheme, EmailEditorReact, SavedSectionInput };
|
|
98
|
+
export { EDITOR_CHANGE_DEBOUNCE_MS, type EditorDocument, EditorTheme, EmailEditorReact, type EmailEditorReactProps, SavedSectionInput, themeToStyle, toStoredDocument, useEditorDocument };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,10 +1,57 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
import { TemplateSnapshotOut, TemplateSnapshotIn, BlockDefinition } from '@marlinjai/email-editor-core';
|
|
3
4
|
export { BlockDefinition, TemplateSnapshotIn, TemplateSnapshotOut } from '@marlinjai/email-editor-core';
|
|
4
5
|
import { OnRequestImage, OnSaveSection } from '@marlinjai/email-editor-ui';
|
|
5
6
|
export { ImageRequest, OnRequestImage, OnSaveSection, RequestedImage } from '@marlinjai/email-editor-ui';
|
|
6
7
|
import { b as EditorTheme, S as SavedSectionInput } from './types-G7Ak2dWQ.js';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Two things about the editor are easy to get wrong, and getting either wrong
|
|
11
|
+
* loses somebody's work silently, so they live here rather than in each screen
|
|
12
|
+
* that embeds the editor:
|
|
13
|
+
*
|
|
14
|
+
* 1. **The editor reports a snapshot on mount.** It normalises what it was
|
|
15
|
+
* given, and says so. That first report is the baseline, not an edit: a
|
|
16
|
+
* screen that treated it as one would show "unsaved changes" on every open
|
|
17
|
+
* and would ask before leaving a document nobody touched. `touched` is set
|
|
18
|
+
* by a real interaction (a pointer or a key inside the editor), and only
|
|
19
|
+
* then does a report mean a change.
|
|
20
|
+
*
|
|
21
|
+
* 2. **The editor reports changes debounced.** The document it last reported
|
|
22
|
+
* may be one edit behind, so reading it the moment somebody presses Save
|
|
23
|
+
* would store the previous document and drop the edit just made. `settled()`
|
|
24
|
+
* waits out that window since the last interaction before reading.
|
|
25
|
+
*/
|
|
26
|
+
/** The store's own `onChange` debounce, plus a little room. */
|
|
27
|
+
declare const EDITOR_CHANGE_DEBOUNCE_MS = 300;
|
|
28
|
+
/** The document as a service stores it: the editor's snapshot without its local id. */
|
|
29
|
+
declare function toStoredDocument(snapshot: TemplateSnapshotOut | Record<string, unknown>): Record<string, unknown>;
|
|
30
|
+
type EditorDocument = {
|
|
31
|
+
/** Whether the person has changed anything since the last save. */
|
|
32
|
+
dirty: boolean;
|
|
33
|
+
setDirty: (dirty: boolean) => void;
|
|
34
|
+
/** Hand to the editor's `onChange`. */
|
|
35
|
+
onChange: (snapshot: TemplateSnapshotOut) => void;
|
|
36
|
+
/** Hand to the host element's `onPointerDownCapture` and `onKeyDownCapture`. */
|
|
37
|
+
interacted: () => void;
|
|
38
|
+
/** The current document, once the editor's debounce window has passed. */
|
|
39
|
+
settled: () => Promise<Record<string, unknown>>;
|
|
40
|
+
/** Replaces what is held, for a document loaded or rewritten from outside the editor. */
|
|
41
|
+
replace: (document: Record<string, unknown>, opts?: {
|
|
42
|
+
touched?: boolean;
|
|
43
|
+
}) => void;
|
|
44
|
+
/** What is held right now, without waiting. Prefer `settled()` before storing it. */
|
|
45
|
+
current: () => Record<string, unknown>;
|
|
46
|
+
};
|
|
47
|
+
declare function useEditorDocument(initial: Record<string, unknown>): EditorDocument;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* CSS custom properties for the editor's root element. Returns an empty
|
|
51
|
+
* object when there is no theme, so the stylesheet defaults apply.
|
|
52
|
+
*/
|
|
53
|
+
declare function themeToStyle(theme: EditorTheme | undefined): CSSProperties;
|
|
54
|
+
|
|
8
55
|
interface EmailEditorReactProps {
|
|
9
56
|
/** Initial template data (uncontrolled) */
|
|
10
57
|
initialTemplate?: TemplateSnapshotIn;
|
|
@@ -48,4 +95,4 @@ interface EmailEditorReactProps {
|
|
|
48
95
|
*/
|
|
49
96
|
declare function EmailEditorReact({ initialTemplate, onChange, theme, blocks, onSave, onExport, onNavigateBack, onRequestImage, savedSections, onSaveSection, builtInSections, placeholderBase, }: EmailEditorReactProps): react.JSX.Element;
|
|
50
97
|
|
|
51
|
-
export { EditorTheme, EmailEditorReact, SavedSectionInput };
|
|
98
|
+
export { EDITOR_CHANGE_DEBOUNCE_MS, type EditorDocument, EditorTheme, EmailEditorReact, type EmailEditorReactProps, SavedSectionInput, themeToStyle, toStoredDocument, useEditorDocument };
|
package/dist/react.js
CHANGED
|
@@ -20,11 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/react.tsx
|
|
21
21
|
var react_exports = {};
|
|
22
22
|
__export(react_exports, {
|
|
23
|
-
|
|
23
|
+
EDITOR_CHANGE_DEBOUNCE_MS: () => EDITOR_CHANGE_DEBOUNCE_MS,
|
|
24
|
+
EmailEditorReact: () => EmailEditorReact,
|
|
25
|
+
themeToStyle: () => themeToStyle,
|
|
26
|
+
toStoredDocument: () => toStoredDocument,
|
|
27
|
+
useEditorDocument: () => useEditorDocument
|
|
24
28
|
});
|
|
25
29
|
module.exports = __toCommonJS(react_exports);
|
|
26
|
-
var
|
|
27
|
-
var
|
|
30
|
+
var import_react2 = require("react");
|
|
31
|
+
var import_email_editor_core3 = require("@marlinjai/email-editor-core");
|
|
28
32
|
var import_email_editor_blocks = require("@marlinjai/email-editor-blocks");
|
|
29
33
|
var import_email_editor_ui = require("@marlinjai/email-editor-ui");
|
|
30
34
|
|
|
@@ -70,6 +74,47 @@ function assertSupportedBlocks(blocks) {
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
// src/editorDocument.ts
|
|
78
|
+
var import_react = require("react");
|
|
79
|
+
var import_email_editor_core2 = require("@marlinjai/email-editor-core");
|
|
80
|
+
var EDITOR_CHANGE_DEBOUNCE_MS = import_email_editor_core2.DEFAULT_ON_CHANGE_DEBOUNCE_MS;
|
|
81
|
+
var SETTLE_MARGIN_MS = 100;
|
|
82
|
+
function toStoredDocument(snapshot) {
|
|
83
|
+
const { id: _id, ...rest } = snapshot;
|
|
84
|
+
return rest;
|
|
85
|
+
}
|
|
86
|
+
function useEditorDocument(initial) {
|
|
87
|
+
const [dirty, setDirty] = (0, import_react.useState)(false);
|
|
88
|
+
const latest = (0, import_react.useRef)(toStoredDocument(initial));
|
|
89
|
+
const touched = (0, import_react.useRef)(false);
|
|
90
|
+
const lastInteraction = (0, import_react.useRef)(0);
|
|
91
|
+
const onChange = (0, import_react.useCallback)((snapshot) => {
|
|
92
|
+
latest.current = toStoredDocument(snapshot);
|
|
93
|
+
if (touched.current) setDirty(true);
|
|
94
|
+
}, []);
|
|
95
|
+
const interacted = (0, import_react.useCallback)(() => {
|
|
96
|
+
touched.current = true;
|
|
97
|
+
lastInteraction.current = Date.now();
|
|
98
|
+
}, []);
|
|
99
|
+
const settled = (0, import_react.useCallback)(async () => {
|
|
100
|
+
const wait = lastInteraction.current + EDITOR_CHANGE_DEBOUNCE_MS + SETTLE_MARGIN_MS - Date.now();
|
|
101
|
+
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
102
|
+
return latest.current;
|
|
103
|
+
}, []);
|
|
104
|
+
const replace = (0, import_react.useCallback)((document, opts = {}) => {
|
|
105
|
+
latest.current = toStoredDocument(document);
|
|
106
|
+
if (opts.touched) {
|
|
107
|
+
touched.current = true;
|
|
108
|
+
setDirty(true);
|
|
109
|
+
} else {
|
|
110
|
+
touched.current = false;
|
|
111
|
+
setDirty(false);
|
|
112
|
+
}
|
|
113
|
+
}, []);
|
|
114
|
+
const current = (0, import_react.useCallback)(() => latest.current, []);
|
|
115
|
+
return { dirty, setDirty, onChange, interacted, settled, replace, current };
|
|
116
|
+
}
|
|
117
|
+
|
|
73
118
|
// src/react.tsx
|
|
74
119
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
75
120
|
function EmailEditorReact({
|
|
@@ -86,7 +131,7 @@ function EmailEditorReact({
|
|
|
86
131
|
builtInSections = true,
|
|
87
132
|
placeholderBase
|
|
88
133
|
}) {
|
|
89
|
-
const [registry] = (0,
|
|
134
|
+
const [registry] = (0, import_react2.useState)(() => {
|
|
90
135
|
assertSupportedBlocks(blocks);
|
|
91
136
|
const reg = (0, import_email_editor_blocks.createStandardBlockRegistry)({ placeholderBase });
|
|
92
137
|
blocks.forEach((block) => {
|
|
@@ -95,13 +140,13 @@ function EmailEditorReact({
|
|
|
95
140
|
});
|
|
96
141
|
return reg;
|
|
97
142
|
});
|
|
98
|
-
const [prebuiltRegistry] = (0,
|
|
99
|
-
const reg = builtInSections ? (0, import_email_editor_blocks.createStandardPrebuiltRegistry)({ placeholderBase }) : (0,
|
|
143
|
+
const [prebuiltRegistry] = (0, import_react2.useState)(() => {
|
|
144
|
+
const reg = builtInSections ? (0, import_email_editor_blocks.createStandardPrebuiltRegistry)({ placeholderBase }) : (0, import_email_editor_core3.createPrebuiltTemplateRegistry)();
|
|
100
145
|
registerSavedSections(reg, savedSections ?? []);
|
|
101
146
|
return reg;
|
|
102
147
|
});
|
|
103
|
-
const savedSectionNames = (0,
|
|
104
|
-
const style = (0,
|
|
148
|
+
const savedSectionNames = (0, import_react2.useMemo)(() => (savedSections ?? []).map((s) => s.name), [savedSections]);
|
|
149
|
+
const style = (0, import_react2.useMemo)(() => themeToStyle(theme), [theme]);
|
|
105
150
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
106
151
|
import_email_editor_ui.EmailEditor,
|
|
107
152
|
{
|
|
@@ -121,5 +166,9 @@ function EmailEditorReact({
|
|
|
121
166
|
}
|
|
122
167
|
// Annotate the CommonJS export names for ESM import in node:
|
|
123
168
|
0 && (module.exports = {
|
|
124
|
-
|
|
169
|
+
EDITOR_CHANGE_DEBOUNCE_MS,
|
|
170
|
+
EmailEditorReact,
|
|
171
|
+
themeToStyle,
|
|
172
|
+
toStoredDocument,
|
|
173
|
+
useEditorDocument
|
|
125
174
|
});
|
package/dist/react.mjs
CHANGED
|
@@ -5,10 +5,53 @@ import {
|
|
|
5
5
|
} from "./chunk-EFHKH3BN.mjs";
|
|
6
6
|
|
|
7
7
|
// src/react.tsx
|
|
8
|
-
import { useMemo, useState } from "react";
|
|
8
|
+
import { useMemo, useState as useState2 } from "react";
|
|
9
9
|
import { createPrebuiltTemplateRegistry } from "@marlinjai/email-editor-core";
|
|
10
10
|
import { createStandardBlockRegistry, createStandardPrebuiltRegistry } from "@marlinjai/email-editor-blocks";
|
|
11
11
|
import { EmailEditor } from "@marlinjai/email-editor-ui";
|
|
12
|
+
|
|
13
|
+
// src/editorDocument.ts
|
|
14
|
+
import { useCallback, useRef, useState } from "react";
|
|
15
|
+
import { DEFAULT_ON_CHANGE_DEBOUNCE_MS } from "@marlinjai/email-editor-core";
|
|
16
|
+
var EDITOR_CHANGE_DEBOUNCE_MS = DEFAULT_ON_CHANGE_DEBOUNCE_MS;
|
|
17
|
+
var SETTLE_MARGIN_MS = 100;
|
|
18
|
+
function toStoredDocument(snapshot) {
|
|
19
|
+
const { id: _id, ...rest } = snapshot;
|
|
20
|
+
return rest;
|
|
21
|
+
}
|
|
22
|
+
function useEditorDocument(initial) {
|
|
23
|
+
const [dirty, setDirty] = useState(false);
|
|
24
|
+
const latest = useRef(toStoredDocument(initial));
|
|
25
|
+
const touched = useRef(false);
|
|
26
|
+
const lastInteraction = useRef(0);
|
|
27
|
+
const onChange = useCallback((snapshot) => {
|
|
28
|
+
latest.current = toStoredDocument(snapshot);
|
|
29
|
+
if (touched.current) setDirty(true);
|
|
30
|
+
}, []);
|
|
31
|
+
const interacted = useCallback(() => {
|
|
32
|
+
touched.current = true;
|
|
33
|
+
lastInteraction.current = Date.now();
|
|
34
|
+
}, []);
|
|
35
|
+
const settled = useCallback(async () => {
|
|
36
|
+
const wait = lastInteraction.current + EDITOR_CHANGE_DEBOUNCE_MS + SETTLE_MARGIN_MS - Date.now();
|
|
37
|
+
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
38
|
+
return latest.current;
|
|
39
|
+
}, []);
|
|
40
|
+
const replace = useCallback((document, opts = {}) => {
|
|
41
|
+
latest.current = toStoredDocument(document);
|
|
42
|
+
if (opts.touched) {
|
|
43
|
+
touched.current = true;
|
|
44
|
+
setDirty(true);
|
|
45
|
+
} else {
|
|
46
|
+
touched.current = false;
|
|
47
|
+
setDirty(false);
|
|
48
|
+
}
|
|
49
|
+
}, []);
|
|
50
|
+
const current = useCallback(() => latest.current, []);
|
|
51
|
+
return { dirty, setDirty, onChange, interacted, settled, replace, current };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/react.tsx
|
|
12
55
|
import { jsx } from "react/jsx-runtime";
|
|
13
56
|
function EmailEditorReact({
|
|
14
57
|
initialTemplate,
|
|
@@ -24,7 +67,7 @@ function EmailEditorReact({
|
|
|
24
67
|
builtInSections = true,
|
|
25
68
|
placeholderBase
|
|
26
69
|
}) {
|
|
27
|
-
const [registry] =
|
|
70
|
+
const [registry] = useState2(() => {
|
|
28
71
|
assertSupportedBlocks(blocks);
|
|
29
72
|
const reg = createStandardBlockRegistry({ placeholderBase });
|
|
30
73
|
blocks.forEach((block) => {
|
|
@@ -33,7 +76,7 @@ function EmailEditorReact({
|
|
|
33
76
|
});
|
|
34
77
|
return reg;
|
|
35
78
|
});
|
|
36
|
-
const [prebuiltRegistry] =
|
|
79
|
+
const [prebuiltRegistry] = useState2(() => {
|
|
37
80
|
const reg = builtInSections ? createStandardPrebuiltRegistry({ placeholderBase }) : createPrebuiltTemplateRegistry();
|
|
38
81
|
registerSavedSections(reg, savedSections ?? []);
|
|
39
82
|
return reg;
|
|
@@ -58,5 +101,9 @@ function EmailEditorReact({
|
|
|
58
101
|
);
|
|
59
102
|
}
|
|
60
103
|
export {
|
|
61
|
-
|
|
104
|
+
EDITOR_CHANGE_DEBOUNCE_MS,
|
|
105
|
+
EmailEditorReact,
|
|
106
|
+
themeToStyle,
|
|
107
|
+
toStoredDocument,
|
|
108
|
+
useEditorDocument
|
|
62
109
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marlinjai/email-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Embeddable visual email editor (React component and vanilla factory) that produces an MJML-compilable document",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"./styles.css": "./dist/styles.css"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@marlinjai/email-editor-blocks": "^0.
|
|
51
|
-
"@marlinjai/email-editor-ui": "^0.
|
|
52
|
-
"@marlinjai/email-editor-core": "^0.
|
|
50
|
+
"@marlinjai/email-editor-blocks": "^0.3.0",
|
|
51
|
+
"@marlinjai/email-editor-ui": "^0.3.0",
|
|
52
|
+
"@marlinjai/email-editor-core": "^0.3.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"react": "^18.2.0 || ^19.0.0",
|