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

Locations

PreviousNext

Use paths, points, ranges, selections, and anchors in a Plite root.

Locations tell Plite where to read or write inside an editor root. The core location types are Path, Point, and Range. Public read and update methods with an at option also accept a live text or element node.

Root resolution

Rootless locations resolve against the current editor or view root. The base editor uses the primary document; root-bound views use their own root. Locations in extra roots carry the root key on their points.

Path

A path is an array of indexes from a root to a node.

NodesTransforms

On This Page

Root resolutionPathNode targetsPointRangeSelectionAnchors
Build your editor
Production-ready AI template and reusable components.
Get all-access
type Path = readonly number[];
type Path = readonly number[];

In this root value, the paragraph has path [0] and the text node has path [0, 0].

const children = [
  {
    type: "paragraph",
    children: [{ text: "A line of text!" }],
  },
];
const children = [
  {
    type: "paragraph",
    children: [{ text: "A line of text!" }],
  },
];

Use [] for the root itself. For example, this selects the current root.

editor.update((tx) => {
  tx.selection.set([]);
});
editor.update((tx) => {
  tx.selection.set([]);
});

Use PathApi for path math.

import { PathApi } from "plitejs";
 
const parent = PathApi.parent([0, 0]);
const common = PathApi.common([0, 0], [0, 2]);
import { PathApi } from "plitejs";
 
const parent = PathApi.parent([0, 0]);
const common = PathApi.common([0, 0], [0, 2]);

Node targets

A node target is either a location or a descendant from the current editor.

type NodeTarget<N extends Descendant = Descendant> = Location | N;
type NodeTarget<N extends Descendant = Descendant> = Location | N;

Pass a node directly when component code already owns that element. Plite resolves its current path before running the read or update.

editor.update.nodes.set({ icon: "🔥" }, { at: calloutElement });
editor.update.nodes.set({ icon: "🔥" }, { at: calloutElement });

The target follows the same logical node through property updates and moves. It resolves only while that node belongs to the current editor root. A removed, detached, foreign-editor, or different-root node does not resolve, so writes do nothing.

Use editor.read.nodes.path(...) when the path itself is the result you need. It returns undefined when the target does not resolve.

const path = editor.read.nodes.path(calloutElement);
const path = editor.read.nodes.path(calloutElement);

Location remains Path | Point | Range. A node is accepted at the public editor lifecycle boundary; pure location utilities still operate on structural locations. Destination fields such as tx.nodes.move({ to }) remain explicit paths.

Point

A point targets an offset inside a text node.

interface Point {
  readonly path: Path;
  readonly offset: number;
  readonly root?: string;
}
interface Point {
  readonly path: Path;
  readonly offset: number;
  readonly root?: string;
}

The start of the first text node is:

const start = {
  path: [0, 0],
  offset: 0,
};
const start = {
  path: [0, 0],
  offset: 0,
};

A point in a named root carries that root key.

const headerStart = {
  path: [0, 0],
  offset: 0,
  root: "header",
};
const headerStart = {
  path: [0, 0],
  offset: 0,
  root: "header",
};

Points always resolve to text nodes. Elements do not have cursor offsets.

Range

A range spans between two points.

interface Range {
  readonly anchor: Point;
  readonly focus: Point;
}
interface Range {
  readonly anchor: Point;
  readonly focus: Point;
}

The anchor is where the user started the selection. The focus is where the user ended it. A backward range can have an anchor that appears after the focus in document order.

Use RangeApi when you need ordered edges or direction checks.

import { RangeApi } from "plitejs";
 
const [start, end] = RangeApi.edges(range);
const collapsed = RangeApi.isCollapsed(range);
const backward = RangeApi.isBackward(range);
import { RangeApi } from "plitejs";
 
const [start, end] = RangeApi.edges(range);
const collapsed = RangeApi.isCollapsed(range);
const backward = RangeApi.isBackward(range);

Both points in a range must resolve inside the same root. For named roots, put the same root on both points.

const headerRange = {
  anchor: { path: [0, 0], offset: 0, root: "header" },
  focus: { path: [0, 0], offset: 6, root: "header" },
};
const headerRange = {
  anchor: { path: [0, 0], offset: 0, root: "header" },
  focus: { path: [0, 0], offset: 6, root: "header" },
};

Selection

The editor selection is EditorSelection | null: one text selection or one directional exact node selection. Core owns mapping, serialization, and DOM projection. state.selection() returns its representative Range | null; state.runtime.snapshot().selection exposes the complete selection.

const selection = editor.read((state) => state.selection());
const selection = editor.read((state) => state.selection());
editor.update((tx) => {
  tx.selection.set({
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [0, 0], offset: 15 },
  });
});
editor.update((tx) => {
  tx.selection.set({
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [0, 0], offset: 15 },
  });
});

Set the selection to null to clear it.

editor.update((tx) => {
  tx.selection.set(null);
});
editor.update((tx) => {
  tx.selection.set(null);
});

Use read helpers for common editor-derived points and ranges.

const start = editor.read((state) => state.points.start([0]));
const end = editor.read((state) => state.points.end([0]));
const unhung = editor.read((state) => {
  const selection = state.selection();
 
  return selection ? state.ranges.unhang(selection, { voids: true }) : null;
});
const start = editor.read((state) => state.points.start([0]));
const end = editor.read((state) => state.points.end([0]));
const unhung = editor.read((state) => {
  const selection = state.selection();
 
  return selection ? state.ranges.unhang(selection, { voids: true }) : null;
});

Use Path API, Point API, and Range API for the full helper list.

Anchors

Use anchors for runtime locations that map through canonical document changes. Choose association and deletion policy explicitly. editor.anchor creates a persistent handle that the owner must release.

const anchor = editor.anchor(
  {
    anchor: { path: [0, 0], offset: 2 },
    focus: { path: [0, 0], offset: 8 },
  },
  { association: "inward", deletion: "drop" }
);
 
const range = anchor.resolve();
anchor.release();
const anchor = editor.anchor(
  {
    anchor: { path: [0, 0], offset: 2 },
    focus: { path: [0, 0], offset: 8 },
  },
  { association: "inward", deletion: "drop" }
);
 
const range = anchor.resolve();
anchor.release();

Use tx.anchor when the mapped location belongs only to one update or detached transaction builder. It supports paths, points, and ranges and expires when the callback ends.

editor.update((tx) => {
  const target = tx.anchor(range, {
    association: "inward",
    deletion: "nearest",
  });
 
  tx.nodes.unwrap();
 
  const mapped = target.resolve();
 
  if (mapped) tx.selection.set(mapped);
});
editor.update((tx) => {
  const target = tx.anchor(range, {
    association: "inward",
    deletion: "nearest",
  });
 
  tx.nodes.unwrap();
 
  const mapped = target.resolve();
 
  if (mapped) tx.selection.set(mapped);
});

See Anchor API for the two handle contracts.