Trevixal API
    Preparing search index...

    Module @trevixal/react

    @trevixal/react

    CI npm types license

    Documentation · Live editor · Changelog · Issues

    The Trevixal editor

    • useEditor, EditorContent and a reference-stable snapshot hook
    • Typing does not re-render your tree: the editor DOM lives outside reconciliation
    • React components inside the document through portal-based node views
    • 1.0 kB minified and gzipped, with TypeScript types in the package

    React bindings for the Trevixal editor: a hook, a component, and portal-based node views.

    npm install @trevixal/core @trevixal/react
    
    import { Schema, defaultNodes, defaultMarks } from '@trevixal/core'
    import { useEditor, useEditorSnapshot, EditorContent } from '@trevixal/react'

    const schema = new Schema({ nodes: defaultNodes(), marks: defaultMarks() })

    function Bold({ editor }) {
    // Reference-stable snapshot: this re-renders when the toolbar state
    // changes, and never on plain typing.
    const snapshot = useEditorSnapshot(editor)
    return (
    <button
    aria-pressed={snapshot?.activeMarks.includes('bold')}
    onClick={() => editor.commands.toggleMark('bold')}
    >
    Bold
    </button>
    )
    }

    export function Editor() {
    const editor = useEditor({ schema })
    return (
    <div className="trevixal">
    <Bold editor={editor} />
    <EditorContent editor={editor} placeholder="Write something…" />
    </div>
    )
    }

    The editor's DOM lives outside React's reconciliation, React mounts an empty container and the view owns what is inside it (ADR-0007). Toolbars subscribe to snapshots through useSyncExternalStore, so a keystroke re-renders only the components whose state actually changed. A test asserts that a sibling which does not subscribe renders zero times while typing.

    import type { NodeViewProps } from '@trevixal/react'

    function Counter({ node, updateAttributes }: NodeViewProps) {
    return <button onClick={() => updateAttributes({ count: Number(node.attrs.count) + 1 })}>
    {String(node.attrs.count)}
    </button>
    }

    <EditorContent editor={editor} nodeViews={{ counter: Counter }} />

    Node views render through portals, so they are ordinary React components with hooks, context and all, and their clicks dispatch real editor transactions.

    useEditor, useEditorSnapshot, EditorContent, EditorProvider, useCurrentEditor, and the EditorContentProps / NodeViewProps types.

    Apache-2.0

    EditorContentProps
    EditorProviderProps
    NodeViewProps
    EditorContent
    EditorProvider
    useCurrentEditor
    useEditor
    useEditorSnapshot