Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Overview
    • Why This Fork
    • Examples
    • Installing Plite
    • Adding Event Handlers
    • Defining Custom Elements
    • Applying Custom Formatting
    • Executing Commands
    • Saving to a Database
    • Canonical Change Substrate
    • Improving Performance
    • Interfaces
    • Nodes
    • Locations
    • Transforms
    • Document Changes
    • Commands
    • Editor
    • Plugins
    • Rendering
    • Serializing
    • Normalizing
    • Using TypeScript
    • Roots
    • Document State
    • Editing Behavior
    • Selection And DOM
    • Clipboard And Paste
    • Decorations, annotations, and widgets
    • Schema
    • Anchor API
    • Location API
    • Path API
    • PointEntry API
    • Point API
    • Range API
    • Selection API
    • Location Types APIs
    • Span API
    • Editor
    • Element API
    • NodeEntry API
    • Node API
    • Node Types APIs
    • Text API
    • Debug Value Scrubbing
    • Transforms API
    • Plite DOM
    • History Editor API
    • History Plugin Setup
    • History
    • Plite History
    • Plite Hyperscript
    • Plite PaginationExperimental
    • Annotations
    • DOM Coverage Boundaries
    • Editable Component
    • External Text Views
    • Plite React Event Handling
    • Virtualized Rendering
    • Plite React Hooks
    • React Editor Setup
    • React Editor
    • Plite React
    • Plite Component
    • Plite Yjs
    • Plite
    • Migration
    • Contributing
    • FAQ
    • Resources

External Text Views

PreviousNext

Render one canonical Plite Text through an adapter-owned editing surface.

Use slots.externalText when another text engine should own a block's DOM and local input. Plite keeps the document, selection, history, and remote changes; the adapter is a view, not another editor document.

Example

The External Text example renders the same document through native Plite and an app-local textarea adapter. It includes shared undo, a second external view, named roots, read-only state, and decoration delivery. The textarea demonstrates the protocol; it is not a large-code editor and cannot paint individual decoration ranges inside its input.

Supported model

The block's compiled schema must permit exactly one direct Text:

Editable ComponentPlite React Event Handling

On This Page

ExampleSupported modelRender slotAdapter lifecycleActionsSelection and native featuresRelated
Build your editor
Production-ready AI template and reusable components.
Get all-access
import { defineEditorSchema, schema } from "plitejs"; const sourceSchema = defineEditorSchema("schema:source", { elements: { source: { content: schema.content.text({ min: 1, max: 1 }) }, }, root: schema.content.type("source"), });
import { defineEditorSchema, schema } from "plitejs";
 
const sourceSchema = defineEditorSchema("schema:source", {
  elements: {
    source: { content: schema.content.text({ min: 1, max: 1 }) },
  },
  root: schema.content.type("source"),
});

The live block must also contain one Text and must not be inline, void, or atomic. Newlines stay in that Text. Whole-Text properties survive offset edits; rich children and partial marks use native Plite rendering instead. The slot rejects unsupported content before mounting an adapter.

Render slot

Pass an app-owned ExternalTextAdapter from plitejs/react. Keep its identity stable across renders.

<Editable
  renderElement={({ attributes, element, slots }) =>
    element.type === "source" ? (
      <pre {...attributes}>
        {slots.externalText({ adapter, ariaLabel: "Source text" })}
      </pre>
    ) : (
      <p {...attributes}>{slots.children()}</p>
    )
  }
/>
<Editable
  renderElement={({ attributes, element, slots }) =>
    element.type === "source" ? (
      <pre {...attributes}>
        {slots.externalText({ adapter, ariaLabel: "Source text" })}
      </pre>
    ) : (
      <p {...attributes}>{slots.children()}</p>
    )
  }
/>

Choose exactly one projection per element. In the external branch, do not read children or call slots.children(). Plite mounts a labelled, noneditable host with no hidden duplicate of the canonical text.

ariaLabel is required and nonempty. The adapter labels its actual input from the host. An adapter's TConfig determines the config option's type; config updates reach the mounted view without recreating it.

Adapter lifecycle

ExternalTextAdapter<TConfig>.mount({ actions, host, state }) creates the local DOM and returns an ExternalTextView<TConfig>:

MethodContract
update({ changes, state })Apply exact patches and the supplied canonical state without dispatching another edit.
focus()Install the delivered state.selection, then focus the local input.
offsetAt?({ edge, x })Optional. Return the offset nearest client x on the first ('start') or last ('end') visual line, or null. Plite asks before ArrowUp or ArrowDown enters the view, then writes the selection itself. Read layout only: never dispatch, change text or selection, focus, scroll, or run deferred layout work. Without it, the caret enters at the first or last offset.
destroy()Release every local listener and resource and remove the adapter's DOM.

state contains text, version, selection, readOnly, decorations, and config. Retain the canonical text reference; do not diff whole strings to discover outbound Plite edits. Decorations are keyed { start, end, attributes } slices in this Text's offsets. The adapter owns their visual representation.

changes has three meanings:

ValueMeaning
Patch listApply sorted, non-overlapping { from, to, insert } replacements in the previous text's UTF-16 coordinates. Apply right to left if mutating a string in place.
[]Acknowledge a local edit or update selection, configuration, read-only state, or decorations without changing text.
nullReset to state.text, cancel local composition, and discard an unaccepted local prediction.

Structural replacement, stale input, canonical correction, and conflicting remote composition can require resets. Ordinary text edits, history replay, and non-conflicting remote patches do not require a reset.

Mounting is client-only. SSR emits the empty host. Unmounting, root changes, and explicit document virtualization destroy the view; remount from canonical state. Adapter failures reach the editor's lifecycleErrorSink with source: "external-text". A failed view rejects edits until reset or remount.

Actions

Pass the last delivered state.version as baseVersion. Plite rejects stale, invalid, and read-only edits without changing the document.

ActionContract
dispatch({ baseVersion, changes, intent, selection })Submit one edit with input, paste, cut, drop, or composition intent. Selection uses the resulting text's directed UTF-16 offsets.
select({ baseVersion, selection })Set directed { anchor, focus } offsets without changing text.
navigateOut({ baseVersion, direction, extend?, x? })Ask Plite to move into neighboring document content, optionally extending selection. For ArrowUp or ArrowDown, pass the caret's goal client x so Plite keeps the column; omit it for other keys.
deleteOut({ baseVersion, direction })Delete across a block edge when the canonical caret is collapsed at that edge.
composition("start" | "end")Join and finish Plite's composition epoch. Submit composition edits with intent: "composition".
history("undo" | "redo")Use the installed Plite history plugin. Returns false when no history action applies.

Versioned actions return { status: "applied" | "stale" | "read-only" }. Adapters disable independent undo stacks and never dispatch from mount, update, focus, or destroy callbacks.

Selection and native features

Only the focused view receives selection.mode: "native" for a selection wholly inside its Text. Other views and cross-boundary selections receive "model"; paint that intersection without changing browser selection or focus. Avoid redundant native selection writes during focus transitions.

The adapter owns local input, local clipboard formats, accessibility, search, spelling, and printing. Plite copies a cross-boundary selection from the full canonical model, including text absent from native Plite DOM. Rich drag moves between engines are outside this contract; plain-text transfers must not also delete their source.

There is no fixed-height requirement. Adapter-internal virtualization and Plite's explicit document virtualization are separate choices. Neither this slot nor the textarea example guarantees browser find, full screen-reader traversal, or print over unmounted text.

Related

  • Editable Component
  • DOM Coverage Boundaries
  • Selection And DOM
  • History