Skip to content

Repository files navigation

taino-edit

Native Rust WYSIWYG rich-text editor framework for Leptos — pure Rust at runtime, no JavaScript bridge.

CI License

taino-edit is a ProseMirror/TipTap-inspired editor — typed document model, invertible transforms, history, commands and a Leptos component — built reactive-first for Rust web frameworks. Unlike leptos-tiptap (a wasm-bindgen wrapper around the TypeScript TipTap bundle), there is no JS dependency at runtime.

It is part of the taino-* family, following taino-dnd-*.

Status: v0.6.0 released

Seven crates on crates.io. v0.6 brings Leptos SSR: <TainoEditor> server-renders the initial document as real HTML (visible and indexable before any wasm loads) and hydrates it into the live editor with no visual change — backed by a browser-pinned markup contract (doc_view_htmlEditorView::mount). The adapter is now render-mode neutral (no forced csr; your app picks csr/hydrate/ssr), and the new ssr-leptos example ships the full axum + cargo leptos setup. Before that, the v0.5 line added a schema! macro, inline (range-level) decorations drawn as a scroll-aware overlay, editor-owned keyboard input in both adapters (synchronous, live-selection — no stale-caret bugs), and a string of editing fixes surfaced by dog-fooding (caret motion across block boundaries, ordered-list numbering, multi-block commands, selection-mirror race). Tests pass workspace-wide:

Host tests 214 (model, schema, content automaton, replace, steps, transforms, state, history, commands, keymap, input-rules, plugin registry, Markdown serializer + parser, the Selection::Cell variant, and 13 extensions including the full table command set)
Browser tests wasm-bindgen cases in headless Chromium 148 — mount, diff/patch, selection sync, DOM-typing → Transform, IME, clipboard, drag/drop, focus, node + inline decorations, Leptos + Dioxus component/event/keymap wiring, table rendering, the ViewPlugin infra, and TableView pointer interaction (cell drag-select, highlight, resize)

See DESIGN_NOTES.md for the architecture, the scope budget, and the resolved design decisions; ROADMAP.md tracks phase progress and what's deferred.

What's new in v0.4 / v0.5 (2026-05-26 … 2026-06-11)

  • schema! { .. } macro — declarative sugar over SchemaBuilder (plain macro_rules!, no proc-macro crate).
  • Inline (range-level) decorationsDecoration::Inline highlights arbitrary inline ranges (search hits, comments, remote selections) as a scroll-aware overlay that never splits the editable text nodes.
  • The editor owns keyboard input in both adapters: keydown reads the live DOM selection, runs the keymap command and applies the result synchronously, eliminating the "stale model selection" class of bugs.
  • Coordinate-free table primitivesselect_caret_row() / select_caret_column() make "merge this row/column" one-liners that stay correct as the table grows.
  • Code inline mark (Mod-e) with Markdown backtick round-trip (shipped in v0.3.1).
  • Dioxus ViewPlugin parity — interactive tables (drag-select, highlight, resize) in Dioxus too; basic-dioxus runs the same 13-extension toolbar as basic-leptos.
  • Editing fixes: caret motion across block boundaries, ordered-list numbering after lifting a middle item, block commands applying to every selected block, empty-textblock focusability, and the selection-mirror race that clipped multi-word drag-selections.

What's new in v0.3 (2026-05-22)

  • Full tables — a Table extension (table/table_row/table_cell with colspan/rowspan/header/colwidth) whose every command is span-correct: insert, add/delete rows & columns, header toggle, Tab cell-navigation, cell-range selection, merge/split, and column resize. A logical-grid placement model + compaction render guarantee no orphan spans or empty rows under any sequence of edits.
  • ViewPlugin platform (taino-edit-dom) — DOM-aware event + decoration hooks so an extension can add real pointer interaction without coupling the generic adapter to it. New crate taino-edit-table-view implements table cell drag-select, selection highlight and column-resize on top of it; the Leptos <TainoEditor> takes an optional plugins prop.
  • Dioxus adapter parity — input → transform, IME, paste and selectionchange all wired, matching the Leptos adapter.

What shipped in v0.2 (2026-05-21)

  • Complete list UX (smart Enter / sink / lift), the Plugin trait + typed-state registry, Markdown round-trip, and the first real Dioxus adapter.

What ships in v0.1

  • A typed, immutable document tree (ProseMirror-style Node / Mark / Fragment / Slice).
  • A Schema + SchemaBuilder with a Thompson-NFA-to-DFA content automaton (paragraph+, (text | image)*, + * ?).
  • Schema-checked JSON round-trip (Node::to_jsonSchema::node_from_json).
  • A dependency-free escaped HTML serializer and a strict, depth-bounded HTML parser (rejects unknown tags, can't be tricked into injecting markup).
  • Invertible, mappable Steps (ReplaceStep, ReplaceAroundStep, AddMark/RemoveMark/AttrStep), Mapping with mirror/recover, and a Transform builder.
  • An EditorState with Selection, Transaction, and a bounded groupable undo/redo History.
  • A standard command vocabulary (select_all, toggle_mark, set_block_type, wrap_in, lift, split_block, join_…, …), a cross-platform Keymap (Mod = Ctrl/Cmd) and a base_keymap.
  • Regex input rules (## → heading, > → blockquote, …).
  • A real contenteditable DOM bridge (taino-edit-dom): mount, incremental diff/patch, bidirectional selection sync, IME composition, clipboard paste sanitized through the schema, drag-and-drop primitives, focus management and node decorations.
  • A first-class Leptos adapter: <TainoEditor state=signal /> mounts the editor, wires every event (including selectionchange) back through the state signal, and is tested inside the real Leptos CSR runtime.
  • Twelve built-in extensions, enough to drop into a real project:
    • Inline marks: Bold (Mod-b), Italic (Mod-i), Link (set_link / remove_link commands; the host wires the URL prompt).
    • Block nodes: Paragraph (Mod-Alt-0), Heading H1–H3 (Mod-Alt-1..3), Blockquote (Mod->), CodeBlock (Mod-`), and the Lists trio (BulletList/OrderedList
      • ListItem, Mod-Shift-8/Mod-Shift-7 + Shift-Tab to lift).
    • Inline atoms: Image (insert_image command).
    • Attribute / selection commands: Align (align_left/center/right/justify, Mod-Shift-{l,e,r,j}), TransformCase (to_uppercase / to_lowercase).
    • Undo/redo: History (Mod-z / Mod-Shift-z).

Explicitly deferred to v0.2: generic plugin registry, inline-range decorations, a richer per-node NodeView trait, the Dioxus adapter, loro CRDT integration behind a collab feature, Markdown serializer/parser, smart Enter / nested-list sink (indent) for lists, and richer extensions (tables, footnotes, mentions, math).

Workspace layout

Crate Role
taino-edit-core Framework-agnostic model, transforms, state, history, commands, keymap, input rules, Markdown, Plugin trait
taino-edit-dom contenteditable/DOM bridge + ViewPlugin (web-sys, wasm-bindgen, js-sys)
taino-edit-extensions The 13 built-in extensions (marks, blocks, lists, tables, …) + the Extension trait
taino-edit-leptos Leptos adapter (<TainoEditor>)
taino-edit-dioxus Dioxus adapter (<TainoEditor>)
taino-edit-table-view Table pointer interaction (cell drag-select, resize) as a ViewPlugin
taino-edit Umbrella crate, feature-gated re-exports

Examples under examples/:

  • basic-leptos — a trunk serve-buildable demo with the full toolbar, tables (drag-select / merge / resize) and live JSON + HTML panels.
  • basic-dioxus — the same editor in Dioxus.
  • ssr-leptos — Leptos SSR + hydration: the initial document is server-rendered HTML (curl it!), then hydrated into a live editor. Run with cargo leptos watch. (Its own workspace — Leptos's csr and ssr/hydrate modes can't share one feature graph.)
  • headless-core — server-side / CLI demo proving taino-edit-core runs identically without a DOM.

Install

[dependencies]
taino-edit = { version = "0.6", features = ["leptos"] }  # or "dioxus"

No adapter is enabled by default — pick leptos or dioxus. Add the table-view feature for table pointer interaction.

The Leptos adapter is render-mode neutral: it does not enable any of Leptos's csr/hydrate/ssr features (a library never should — the modes are mutually exclusive and cargo unifies features). Your app enables exactly one, as it already does for every other Leptos component library.

Use it (Leptos)

use leptos::prelude::*;
use taino_edit_leptos::{
    build_keymap_with, build_schema_with, Bold, DomSpec, EditorState,
    Italic, NodeSpec, SchemaBuilder, TainoEditor,
};

#[component]
fn App() -> impl IntoView {
    // Compose a schema on top of the universal doc/text primitives.
    let base = SchemaBuilder::new()
        .node("doc",  NodeSpec { content: Some("block+".into()),  ..Default::default() })
        .node("text", NodeSpec { group:   Some("inline".into()),  ..Default::default() });
    // Paragraph etc. come from `taino-edit-extensions`.
    let exts: Vec<&dyn taino_edit_extensions::Extension> =
        vec![&taino_edit_extensions::Paragraph, &Bold, &Italic];
    let schema = build_schema_with(base, &exts, "doc").unwrap();

    let txt   = schema.text("Hello from Rust!", vec![]).unwrap();
    let para  = schema.node("paragraph", Default::default(), vec![txt], vec![]).unwrap();
    let doc   = schema.node("doc",       Default::default(), vec![para], vec![]).unwrap();
    let state = RwSignal::new(EditorState::new(doc, schema));

    view! { <TainoEditor state=state /> }
}

Server-side rendering

The same component SSRs the initial document as real HTML — no JavaScript required for first paint, and the content is indexable. Under ssr, <TainoEditor> serializes the initial doc with doc_view_html, which emits — by tested contract — exactly the markup the mounted editor builds, so hydration swaps the live editor in with no visual change. The pre-hydration document is deliberately not editable (edits typed before the wasm boots would be lost); it becomes editable the moment the editor mounts.

See examples/ssr-leptos for a full axum + cargo leptos setup.

Build & test

Requires the Rust toolchain pinned in rust-toolchain.toml (stable, MSRV 1.80).

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo doc --no-deps --all-features

Browser tests for taino-edit-dom and taino-edit-leptos use a small locally-patched wasm-bindgen-cli; first time only run ./scripts/install-wasm-test-runner.sh, after that ./scripts/wasm-test.sh runs them in headless Chromium 148. See vendor/README.md for the rationale.

Contributing

See CONTRIBUTING.md. The roadmap marks community contribution surfaces (the Dioxus adapter, richer extensions, native renderers) explicitly.

License

Dual-licensed under either of

at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.

About

An extensible, tree-based rich text editor for Rust and Leptos...

Resources

Contributing

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages