@domternal/core 0.11.1 → 0.12.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/README.md +80 -29
- package/dist/index.cjs +41 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +41 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,42 +3,93 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@domternal/core)
|
|
4
4
|
[](https://github.com/domternal/domternal/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
The framework-agnostic editor engine of [Domternal](https://domternal.dev), built on
|
|
7
|
+
[ProseMirror](https://prosemirror.net/). It provides the headless `Editor` class, the
|
|
8
|
+
extension system (`Extension`, `Node`, `Mark`), a chainable command API, and the
|
|
9
|
+
built-in nodes, marks, and behaviors (paragraph, heading, lists, tasks, links,
|
|
10
|
+
inline formatting, history, keymaps) bundled as `StarterKit`. Use it directly with
|
|
11
|
+
vanilla JS/TS, or as the runtime under the Angular, React, Vue, and Vanilla wrappers.
|
|
12
|
+
Every export is tree-shakeable, so unused extensions are stripped from your bundle.
|
|
8
13
|
|
|
9
14
|
## Links
|
|
10
15
|
|
|
11
|
-
<u>[Website](https://domternal.dev)</u> • <u>[Documentation](https://domternal.dev/v1/
|
|
12
|
-
<u>[StackBlitz (Angular)](https://stackblitz.com/edit/domternal-angular-full-example)</u> • <u>[StackBlitz (React)](https://stackblitz.com/edit/domternal-react-full-example)</u> • <u>[StackBlitz (Vue)](https://stackblitz.com/edit/domternal-vue-full-example)</u> • <u>[StackBlitz (Vanilla TS)](https://stackblitz.com/edit/domternal-vanilla-full-example)</u>
|
|
16
|
+
<u>[Website](https://domternal.dev)</u> • <u>[Documentation](https://domternal.dev/v1/getting-started)</u> • <u>[Live examples](https://domternal.dev/examples)</u>
|
|
13
17
|
|
|
14
|
-
##
|
|
18
|
+
## Install
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @domternal/core
|
|
22
|
+
```
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- **React components** - composable `Domternal` component, toolbar, bubble menu, floating menu, emoji picker, notion color picker, custom node views (React 18+)
|
|
21
|
-
- **Vue components** - composable `Domternal` component, `useEditor`/`useEditorState` composables, toolbar, bubble menu, floating menu, emoji picker, notion color picker, custom node views (Vue 3.3+)
|
|
22
|
-
- **Vanilla wrapper** - framework-free class-based API for Astro, Svelte, Solid, plain HTML, and Web Components - editor, toolbar, bubble menu, floating menu, emoji picker, notion color picker
|
|
23
|
-
- **Notion-style block UX** - drag-to-reorder, block context menu, slash command, smart paste, keyboard reorder, floating Table of Contents
|
|
24
|
-
- **70+ extensions across 16 packages** - nodes, marks, and behavior extensions
|
|
25
|
-
- **125+ chainable commands** - `editor.chain().focus().toggleBold().run()`
|
|
26
|
-
- **Full table support** - cell merging, column resize, row/column controls, cell toolbar, all free and MIT licensed
|
|
27
|
-
- **Tree-shakeable** - import only what you use, your bundler strips the rest
|
|
28
|
-
- **~44 KB gzipped** (own code), <u>[see Packages](https://domternal.dev/v1/packages)</u> for full bundle breakdown with ProseMirror
|
|
29
|
-
- **TypeScript first** - 100% typed, zero `any`
|
|
30
|
-
- **15,000+ tests** - 4,000+ unit and 11,000+ E2E across 230+ Playwright specs and 4 demo apps
|
|
31
|
-
- **Light and dark theme** - 120+ CSS custom properties for full visual control
|
|
32
|
-
- **Inline styles export** - `getHTML({ styled: true })` produces inline CSS ready for email clients, CMS, and Google Docs
|
|
33
|
-
- **SSR helpers** - `generateHTML`, `generateJSON`, `generateText` for server-side rendering
|
|
24
|
+
`linkedom` is an optional peer dependency: install it only if you call the SSR
|
|
25
|
+
helpers (`generateHTML`, `generateJSON`, `generateText`) outside a browser.
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add linkedom
|
|
29
|
+
```
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
- <u>[Introduction](https://domternal.dev/v1/introduction)</u> - core concepts, architecture, and design decisions
|
|
39
|
-
- <u>[Packages & Bundle Size](https://domternal.dev/v1/packages)</u> - what each package includes and bundle size breakdown
|
|
40
|
-
- <u>[Blog](https://domternal.dev/blog)</u>
|
|
31
|
+
## Usage
|
|
41
32
|
|
|
42
|
-
|
|
33
|
+
```ts
|
|
34
|
+
import { Editor, StarterKit } from '@domternal/core';
|
|
43
35
|
|
|
44
|
-
|
|
36
|
+
const editor = new Editor({
|
|
37
|
+
element: document.getElementById('editor')!,
|
|
38
|
+
extensions: [StarterKit],
|
|
39
|
+
content: '<p>Hello <strong>world</strong></p>',
|
|
40
|
+
onUpdate: ({ editor }) => {
|
|
41
|
+
console.log(editor.getJSON());
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Chainable command API
|
|
46
|
+
editor.chain().focus().toggleBold().run();
|
|
47
|
+
|
|
48
|
+
// Read content
|
|
49
|
+
const html = editor.getHTML();
|
|
50
|
+
const json = editor.getJSON();
|
|
51
|
+
|
|
52
|
+
// Tear down
|
|
53
|
+
editor.destroy();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The engine ships no styles. Import [`@domternal/theme`](https://www.npmjs.com/package/@domternal/theme) for ready-made light/dark editor styling, or supply your own CSS.
|
|
57
|
+
|
|
58
|
+
`StarterKit` bundles the common nodes, marks, and behaviors; each entry can be
|
|
59
|
+
configured or disabled individually.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
StarterKit.configure({
|
|
63
|
+
codeBlock: false, // disable an extension
|
|
64
|
+
heading: { levels: [1, 2, 3] }, // configure an extension
|
|
65
|
+
link: { openOnClick: false },
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
To trim the bundle further, skip `StarterKit` and compose only the extensions you
|
|
70
|
+
need:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { Editor, Document, Paragraph, Text, Bold, History } from '@domternal/core';
|
|
74
|
+
|
|
75
|
+
const editor = new Editor({
|
|
76
|
+
element: document.getElementById('editor')!,
|
|
77
|
+
extensions: [Document, Paragraph, Text, Bold, History],
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## SSR
|
|
82
|
+
|
|
83
|
+
The `generateHTML`, `generateJSON`, and `generateText` helpers render content
|
|
84
|
+
without an editor instance, for example on the server.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { generateHTML, StarterKit } from '@domternal/core';
|
|
88
|
+
|
|
89
|
+
const html = generateHTML(
|
|
90
|
+
{ type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hi' }] }] },
|
|
91
|
+
[StarterKit],
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`generateJSON(html, extensions)` does the reverse (HTML to doc JSON) and `generateText` extracts plain text.
|
package/dist/index.cjs
CHANGED
|
@@ -217,6 +217,14 @@ function inputRulesPlugin({ rules }) {
|
|
|
217
217
|
return plugin;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
// src/ExtensionConfigurationError.ts
|
|
221
|
+
var ExtensionConfigurationError = class extends Error {
|
|
222
|
+
constructor(message) {
|
|
223
|
+
super(message);
|
|
224
|
+
this.name = "ExtensionConfigurationError";
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
220
228
|
// src/helpers/callOrReturn.ts
|
|
221
229
|
function callOrReturn(value, context, ...args) {
|
|
222
230
|
if (typeof value === "function") {
|
|
@@ -845,6 +853,9 @@ var ExtensionManager = class {
|
|
|
845
853
|
}
|
|
846
854
|
return result;
|
|
847
855
|
} catch (error) {
|
|
856
|
+
if (error instanceof ExtensionConfigurationError) {
|
|
857
|
+
throw error;
|
|
858
|
+
}
|
|
848
859
|
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
849
860
|
this.editor.emit?.("error", { error: errorObj, context });
|
|
850
861
|
return void 0;
|
|
@@ -3460,7 +3471,7 @@ function normalizeColor(color) {
|
|
|
3460
3471
|
}
|
|
3461
3472
|
|
|
3462
3473
|
// src/Editor.ts
|
|
3463
|
-
var Editor = class extends EventEmitter {
|
|
3474
|
+
var Editor = class _Editor extends EventEmitter {
|
|
3464
3475
|
/**
|
|
3465
3476
|
* Editor configuration options
|
|
3466
3477
|
*/
|
|
@@ -3493,6 +3504,10 @@ var Editor = class extends EventEmitter {
|
|
|
3493
3504
|
* Timer for autofocus (cleared on destroy to prevent memory leaks)
|
|
3494
3505
|
*/
|
|
3495
3506
|
_autofocusTimer = null;
|
|
3507
|
+
/**
|
|
3508
|
+
* True while EditorView's constructor runs; see buildViewDispatch.
|
|
3509
|
+
*/
|
|
3510
|
+
_isViewConstructing = false;
|
|
3496
3511
|
/**
|
|
3497
3512
|
* Creates a new Editor instance
|
|
3498
3513
|
*
|
|
@@ -3912,6 +3927,9 @@ var Editor = class extends EventEmitter {
|
|
|
3912
3927
|
* Creates the editor instance
|
|
3913
3928
|
*/
|
|
3914
3929
|
createEditor() {
|
|
3930
|
+
this.on("error", (props) => {
|
|
3931
|
+
this.options.onError?.(props);
|
|
3932
|
+
});
|
|
3915
3933
|
this.emit("beforeCreate", { editor: this });
|
|
3916
3934
|
this.options.onBeforeCreate?.({ editor: this });
|
|
3917
3935
|
this._extensionManager = new ExtensionManager(
|
|
@@ -3951,9 +3969,10 @@ var Editor = class extends EventEmitter {
|
|
|
3951
3969
|
});
|
|
3952
3970
|
const element = this.options.element ?? document.createElement("div");
|
|
3953
3971
|
const nodeViews = this._extensionManager.nodeViews;
|
|
3972
|
+
this._isViewConstructing = true;
|
|
3954
3973
|
this.view = new view.EditorView(element, {
|
|
3955
3974
|
state: state$1,
|
|
3956
|
-
dispatchTransaction:
|
|
3975
|
+
dispatchTransaction: _Editor.buildViewDispatch(this),
|
|
3957
3976
|
editable: () => this.options.editable ?? true,
|
|
3958
3977
|
attributes: () => ({
|
|
3959
3978
|
role: "textbox",
|
|
@@ -3980,6 +3999,7 @@ var Editor = class extends EventEmitter {
|
|
|
3980
3999
|
}
|
|
3981
4000
|
}
|
|
3982
4001
|
});
|
|
4002
|
+
this._isViewConstructing = false;
|
|
3983
4003
|
this.emit("mount", { editor: this, view: this.view });
|
|
3984
4004
|
this.options.onMount?.({ editor: this, view: this.view });
|
|
3985
4005
|
this.commandManager = new CommandManager(this);
|
|
@@ -3991,13 +4011,28 @@ var Editor = class extends EventEmitter {
|
|
|
3991
4011
|
}
|
|
3992
4012
|
}, 0);
|
|
3993
4013
|
}
|
|
3994
|
-
this.on("error", (props) => {
|
|
3995
|
-
this.options.onError?.(props);
|
|
3996
|
-
});
|
|
3997
4014
|
this.emit("create", { editor: this });
|
|
3998
4015
|
this.options.onCreate?.({ editor: this });
|
|
3999
4016
|
this._extensionManager.callOnCreate();
|
|
4000
4017
|
}
|
|
4018
|
+
/**
|
|
4019
|
+
* Builds the dispatchTransaction prop. Plugin views can dispatch synchronously
|
|
4020
|
+
* inside EditorView's constructor, before `editor.view` is assigned; ProseMirror
|
|
4021
|
+
* binds the prop to the view, so the instance is captured early (plugin code
|
|
4022
|
+
* such as appendTransaction may read `editor.view` during the apply) and the
|
|
4023
|
+
* transaction is applied directly, like the default dispatch, skipping events:
|
|
4024
|
+
* it is initial state, not an update.
|
|
4025
|
+
*/
|
|
4026
|
+
static buildViewDispatch(editor) {
|
|
4027
|
+
return function(transaction) {
|
|
4028
|
+
if (editor._isViewConstructing) {
|
|
4029
|
+
editor.view ??= this;
|
|
4030
|
+
this.updateState(this.state.apply(transaction));
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
editor.dispatchTransaction(transaction);
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4001
4036
|
/**
|
|
4002
4037
|
* Handles ProseMirror transactions
|
|
4003
4038
|
*/
|
|
@@ -10539,6 +10574,7 @@ exports.Dropcursor = Dropcursor;
|
|
|
10539
10574
|
exports.Editor = Editor;
|
|
10540
10575
|
exports.EventEmitter = EventEmitter;
|
|
10541
10576
|
exports.Extension = Extension;
|
|
10577
|
+
exports.ExtensionConfigurationError = ExtensionConfigurationError;
|
|
10542
10578
|
exports.ExtensionManager = ExtensionManager;
|
|
10543
10579
|
exports.FLOATING_MENU_META = FLOATING_MENU_META;
|
|
10544
10580
|
exports.FLOATING_MENU_NO_FOCUS = FLOATING_MENU_NO_FOCUS;
|