@codeninza/rte-core
v0.1.1
Published
Editor core: model, schema, state, view. Zero runtime deps.
Downloads
31
Readme
@codeninza/rte-core
Tiny, model-driven editor engine. Zero runtime dependencies. TypeScript-first.
The core of rich-text-editor-koti. Provides an immutable document model (ProseMirror-style), a schema, transactions, an editor state, and a DOM view that reconciles the model to a contenteditable element. No marks, no toolbar, no media — those live in @codeninza/rte-basic and @codeninza/rte-math.
Install
npm install @codeninza/rte-core
# or
pnpm add @codeninza/rte-core
# or
yarn add @codeninza/rte-coreQuick start
import { Editor, Paragraph } from '@codeninza/rte-core';
const host = document.getElementById('editor')!;
const editor = new Editor(host, {
extensions: [Paragraph()],
});
editor.commands.insertText('Hello, world.');
console.log(editor.toJSON());
// → { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello, world.' }] }] }
editor.onChange(() => console.log('doc changed:', editor.toJSON()));The host element gets contenteditable="true". Style it however you like; this package ships no CSS.
What's in the box
Document model
Immutable, JSON-serializable nodes and marks.
import { Node, Mark, Fragment, Slice } from '@codeninza/rte-core';Node— text and element nodes (atomic or with children)Mark— inline annotations attached to text (bold, color, link, …)Fragment— ordered list of sibling nodesSlice— a fragment with optional open depth on each side (for replace ops)
Schema
import { Schema } from '@codeninza/rte-core';
const schema = new Schema({
nodes: {
doc: { content: 'block+' },
paragraph: { content: 'inline*', group: 'block', toDOM: () => ['p', 0] },
text: { group: 'inline' },
},
marks: {
bold: { toDOM: () => ['strong', 0] },
},
topNode: 'doc',
});Content expressions ('block+', 'inline*', 'paragraph heading*') are validated against ContentMatch. Marks support inclusive, excludes, and per-attribute defaults.
Transactions
import { Mark } from '@codeninza/rte-core';
const tr = editor.currentState.tr;
tr.insertText(1, 'Hello ');
tr.addMark(1, 7, new Mark(editor.schema.marks.bold!, {}));
editor.dispatch(tr);Each transaction is a chained sequence of Steps (ReplaceStep, AddMarkStep, RemoveMarkStep). Steps invert cleanly, which makes history straightforward.
Editor state and view
EditorState holds the doc, selection, and plugin state. EditorView renders the doc into a host element and reconciles changes via Reconciler (atomic-aware diffing) and SelectionBridge (DOM ↔ model selection sync, including text inside marked spans).
Plugin / extension system
import type { Extension } from '@codeninza/rte-core';
function MyMark(): Extension {
return {
name: 'myMark',
marks: [{ name: 'myMark', spec: { toDOM: () => ['em', 0] } }],
};
}Pass to new Editor(host, { extensions: [MyMark()] }). Extensions can register nodes, marks, commands, keymaps, and toolbar items.
i18n
editor.locale.register('fi', { 'bold.title': 'Lihavoi' });
editor.locale.set('fi');
editor.locale.t('bold.title', 'Bold'); // → "Lihavoi"Architecture summary
┌──────────────────────────────────────────────┐
│ EditorState (doc + selection + plugin state) │
└────────────────────────┬─────────────────────┘
│ dispatch(tr)
▼
┌──────────────────────────────────────────────┐
│ EditorView │
│ ├─ Reconciler (atomic-aware DOM diff) │
│ ├─ DOMRenderer (model → HTMLElement) │
│ └─ SelectionBridge (model ⇄ DOM Range) │
└──────────────────────────────────────────────┘TypeScript
Ships with .d.ts. Strictly typed throughout.
Stability
Pre-1.0. The API may change between minor versions. Pin or use ranges accordingly.
Related packages
| Package | What it adds |
|---|---|
| @codeninza/rte-basic | Marks (bold, italic, color, highlight), headings, lists, image pipeline, history, toolbar items |
| @codeninza/rte-math | KaTeX-rendered inline + block math nodes with edit popover |
| rich-text-editor-koti | Drop-in IIFE bundle for <script>-tag use |
License
MIT © Koteshwar Rao Myneni
