From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
UNPUBLISHED
  • Overview
  • Why This Fork
  • Examples
Walkthroughs
  • Installing Plite
  • Adding Event Handlers
  • Defining Custom Elements
  • Applying Custom Formatting
  • Executing Commands
  • Saving to a Database
  • Canonical Change Substrate
  • Improving Performance
Concepts
  • Interfaces
  • Nodes
  • Locations
  • Transforms
  • Document Changes
  • Commands
  • Editor
  • Extensions
  • Rendering
  • Serializing
  • Normalizing
  • Using TypeScript
  • Roots
  • Document State
  • Editing Behavior
  • Selection And DOM
  • Clipboard And Paste
  • Projection And Overlays
  • Schema
API
  • 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
Libraries
  • Plite DOM
  • History Editor API
  • History Extension Setup
  • History
  • Plite History
  • Plite Hyperscript
  • Plite Layout
  • Annotations
  • DOM Coverage Boundaries
  • Editable Component
  • Plite React Event Handling
  • Virtualized Rendering
  • Plite React Hooks
  • React Editor Setup
  • React Editor
  • Plite React
  • Plite Component
  • Plite Yjs
  • Plite
General
  • Migration
  • Contributing
  • Docs Proof Map
  • 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 = number[];
type Path = 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 "@platejs/plite";
 
const parent = PathApi.parent([0, 0]);
const common = PathApi.common([0, 0], [0, 2]);
import { PathApi } from "@platejs/plite";
 
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 {
  path: Path;
  offset: number;
  root?: string;
}
interface Point {
  path: Path;
  offset: number;
  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 {
  anchor: Point;
  focus: Point;
}
interface Range {
  anchor: Point;
  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 "@platejs/plite";
 
const [start, end] = RangeApi.edges(range);
const collapsed = RangeApi.isCollapsed(range);
const backward = RangeApi.isBackward(range);
import { RangeApi } from "@platejs/plite";
 
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.

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.