or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

caret-system.mdcommand-system.mdeditor-management.mdindex.mdnode-system.mdselection-system.mdstate-management.mdutilities-helpers.md
tile.json

node-system.mddocs/

0

# Node System

1

2

Comprehensive node types and utilities for representing and manipulating document content. Lexical uses a tree-based node structure where each node represents a piece of content with specific behavior and rendering characteristics.

3

4

## Capabilities

5

6

### Base Node Class

7

8

Abstract base class that all Lexical nodes extend. Provides core functionality for serialization, DOM interaction, and tree manipulation.

9

10

```typescript { .api }

11

/**

12

* Abstract base class for all Lexical nodes

13

*/

14

abstract class LexicalNode {

15

/** Get the unique key for this node */

16

getKey(): NodeKey;

17

/** Get the type identifier for this node */

18

getType(): string;

19

/** Create a clone of this node */

20

clone(): LexicalNode;

21

/** Create DOM representation of this node */

22

createDOM(config: EditorConfig): HTMLElement;

23

/** Update existing DOM element for this node */

24

updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean;

25

/** Export node to JSON format */

26

exportJSON(): SerializedLexicalNode;

27

/** Export node to DOM format */

28

exportDOM(editor: LexicalEditor): DOMExportOutput;

29

/** Get the parent node */

30

getParent(): ElementNode | null;

31

/** Get the parent node or throw if none */

32

getParentOrThrow(): ElementNode;

33

/** Get all ancestors of this node */

34

getParents(): Array<ElementNode>;

35

/** Get the root node */

36

getTopLevelElement(): ElementNode | null;

37

/** Check if this node is attached to the root */

38

isAttached(): boolean;

39

/** Check if this node is selected */

40

isSelected(): boolean;

41

/** Remove this node from its parent */

42

remove(preserveEmptyParent?: boolean): void;

43

/** Replace this node with another node */

44

replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N;

45

/** Insert a node after this node */

46

insertAfter(nodeToInsert: LexicalNode, restoreSelection?: boolean): LexicalNode;

47

/** Insert a node before this node */

48

insertBefore(nodeToInsert: LexicalNode, restoreSelection?: boolean): LexicalNode;

49

/** Check if this node can be replaced by another node */

50

canReplaceWith(replacement: LexicalNode): boolean;

51

/** Select this node */

52

selectStart(): RangeSelection;

53

/** Select to the end of this node */

54

selectEnd(): RangeSelection;

55

/** Select the entire node */

56

selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection;

57

/** Select the previous node */

58

selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection;

59

}

60

61

interface SerializedLexicalNode {

62

type: string;

63

version: number;

64

}

65

66

interface DOMExportOutput {

67

element: HTMLElement;

68

after?: (generatedElement: HTMLElement) => void;

69

}

70

71

type NodeKey = string;

72

```

73

74

### Text Node

75

76

Node type for text content with formatting capabilities.

77

78

```typescript { .api }

79

/**

80

* Node for text content with formatting

81

*/

82

class TextNode extends LexicalNode {

83

/** Create a new TextNode */

84

constructor(text: string, key?: NodeKey);

85

/** Get the text content */

86

getTextContent(): string;

87

/** Set the text content */

88

setTextContent(text: string): this;

89

/** Check if text has specific formatting */

90

hasFormat(type: TextFormatType): boolean;

91

/** Toggle specific text formatting */

92

toggleFormat(type: TextFormatType): this;

93

/** Get all applied formats */

94

getFormat(): number;

95

/** Set text formatting using bitmask */

96

setFormat(format: number): this;

97

/** Get text mode (normal, token, segmented) */

98

getMode(): TextModeType;

99

/** Set text mode */

100

setMode(mode: TextModeType): this;

101

/** Check if node can be merged with adjacent text */

102

canHaveFormat(): boolean;

103

/** Split text at offset */

104

splitText(...splitOffsets: Array<number>): Array<TextNode>;

105

/** Merge with adjacent text node */

106

mergeWithSibling(target: TextNode): TextNode;

107

}

108

109

/**

110

* Create a new TextNode

111

* @param text - Initial text content

112

* @returns New TextNode instance

113

*/

114

function $createTextNode(text?: string): TextNode;

115

116

/**

117

* Check if node is a TextNode

118

* @param node - Node to check

119

* @returns True if node is TextNode

120

*/

121

function $isTextNode(node: LexicalNode | null | undefined): node is TextNode;

122

123

interface SerializedTextNode extends SerializedLexicalNode {

124

detail: number;

125

format: number;

126

mode: TextModeType;

127

style: string;

128

text: string;

129

}

130

131

type TextFormatType = 'bold' | 'italic' | 'strikethrough' | 'underline' | 'code' | 'subscript' | 'superscript' | 'highlight';

132

type TextModeType = 'normal' | 'token' | 'segmented';

133

```

134

135

**Usage Examples:**

136

137

```typescript

138

import { $createTextNode, $isTextNode } from "lexical";

139

140

// Create text node

141

const textNode = $createTextNode('Hello, world!');

142

143

// Format text

144

textNode.toggleFormat('bold');

145

textNode.toggleFormat('italic');

146

147

// Check formatting

148

if (textNode.hasFormat('bold')) {

149

console.log('Text is bold');

150

}

151

152

// Split text

153

const [first, second] = textNode.splitText(5); // Split at "Hello"

154

155

// Type checking

156

if ($isTextNode(someNode)) {

157

console.log(someNode.getTextContent());

158

}

159

```

160

161

### Element Node

162

163

Base class for nodes that can contain child nodes.

164

165

```typescript { .api }

166

/**

167

* Base class for nodes with children

168

*/

169

class ElementNode extends LexicalNode {

170

/** Get all child nodes */

171

getChildren<T extends LexicalNode>(): Array<T>;

172

/** Get child nodes filtered by type */

173

getChildrenByType<T extends LexicalNode>(type: string): Array<T>;

174

/** Get number of child nodes */

175

getChildrenSize(): number;

176

/** Check if node is empty (no children) */

177

isEmpty(): boolean;

178

/** Check if node is dirty (needs DOM update) */

179

isDirty(): boolean;

180

/** Get all descendant nodes */

181

getAllTextNodes(): Array<TextNode>;

182

/** Get first child node */

183

getFirstChild<T extends LexicalNode>(): T | null;

184

/** Get first child or throw if none */

185

getFirstChildOrThrow<T extends LexicalNode>(): T;

186

/** Get last child node */

187

getLastChild<T extends LexicalNode>(): T | null;

188

/** Get last child or throw if none */

189

getLastChildOrThrow<T extends LexicalNode>(): T;

190

/** Get child at specific index */

191

getChildAtIndex<T extends LexicalNode>(index: number): T | null;

192

/** Get text content of all children */

193

getTextContent(): string;

194

/** Get direction (ltr or rtl) */

195

getDirection(): 'ltr' | 'rtl' | null;

196

/** Get element format (alignment) */

197

getFormatType(): ElementFormatType;

198

/** Set element format */

199

setFormat(type: ElementFormatType): this;

200

/** Get indentation level */

201

getIndent(): number;

202

/** Set indentation level */

203

setIndent(indentLevel: number): this;

204

/** Append child nodes */

205

append(...nodesToAppend: LexicalNode[]): this;

206

/** Clear all children */

207

clear(): this;

208

/** Insert child at index */

209

splice(start: number, deleteCount: number, ...nodesToInsert: LexicalNode[]): this;

210

/** Select this element */

211

select(anchorOffset?: number, focusOffset?: number): RangeSelection;

212

/** Select all content in this element */

213

selectStart(): RangeSelection;

214

/** Select to end of this element */

215

selectEnd(): RangeSelection;

216

/** Can extract with child nodes during operations */

217

extractWithChild(child: LexicalNode, selection: BaseSelection, destination: 'clone' | 'html'): boolean;

218

/** Can be merged with adjacent elements */

219

canMergeWith(node: ElementNode): boolean;

220

/** Can insert text before this element */

221

canInsertTextBefore(): boolean;

222

/** Can insert text after this element */

223

canInsertTextAfter(): boolean;

224

/** Can be indented */

225

canIndent(): boolean;

226

}

227

228

/**

229

* Check if node is an ElementNode

230

* @param node - Node to check

231

* @returns True if node is ElementNode

232

*/

233

function $isElementNode(node: LexicalNode | null | undefined): node is ElementNode;

234

235

interface SerializedElementNode extends SerializedLexicalNode {

236

children: Array<SerializedLexicalNode>;

237

direction: 'ltr' | 'rtl' | null;

238

format: ElementFormatType | '';

239

indent: number;

240

}

241

242

type ElementFormatType = 'left' | 'center' | 'right' | 'justify' | 'start' | 'end';

243

```

244

245

### Paragraph Node

246

247

Standard paragraph node for block-level text content.

248

249

```typescript { .api }

250

/**

251

* Node for paragraph elements

252

*/

253

class ParagraphNode extends ElementNode {

254

/** Create a new ParagraphNode */

255

constructor(key?: NodeKey);

256

/** Get text format for paragraph-level formatting */

257

getTextFormat(): number;

258

/** Set text format for paragraph */

259

setTextFormat(format: number): this;

260

/** Check if paragraph has text format */

261

hasTextFormat(type: TextFormatType): boolean;

262

}

263

264

/**

265

* Create a new ParagraphNode

266

* @returns New ParagraphNode instance

267

*/

268

function $createParagraphNode(): ParagraphNode;

269

270

/**

271

* Check if node is a ParagraphNode

272

* @param node - Node to check

273

* @returns True if node is ParagraphNode

274

*/

275

function $isParagraphNode(node: LexicalNode | null | undefined): node is ParagraphNode;

276

277

interface SerializedParagraphNode extends SerializedElementNode {

278

textFormat: number;

279

textStyle: string;

280

}

281

```

282

283

### Root Node

284

285

Special node serving as the document root.

286

287

```typescript { .api }

288

/**

289

* Root node of the editor (singleton)

290

*/

291

class RootNode extends ElementNode {

292

/** Create a new RootNode (typically only used internally) */

293

constructor();

294

/** Root nodes cannot be removed */

295

remove(): never;

296

/** Root nodes cannot be replaced */

297

replace<N extends LexicalNode>(node: N): never;

298

/** Root nodes cannot be inserted after */

299

insertAfter(node: LexicalNode): never;

300

/** Root nodes cannot be inserted before */

301

insertBefore(node: LexicalNode): never;

302

}

303

304

/**

305

* Check if node is the RootNode

306

* @param node - Node to check

307

* @returns True if node is RootNode

308

*/

309

function $isRootNode(node: LexicalNode | null | undefined): node is RootNode;

310

311

interface SerializedRootNode extends SerializedElementNode {

312

// Root node has no additional properties

313

}

314

```

315

316

### Line Break Node

317

318

Node for line breaks (br elements).

319

320

```typescript { .api }

321

/**

322

* Node for line breaks

323

*/

324

class LineBreakNode extends LexicalNode {

325

/** Create a new LineBreakNode */

326

constructor(key?: NodeKey);

327

/** Line breaks have no text content */

328

getTextContent(): '';

329

}

330

331

/**

332

* Create a new LineBreakNode

333

* @returns New LineBreakNode instance

334

*/

335

function $createLineBreakNode(): LineBreakNode;

336

337

/**

338

* Check if node is a LineBreakNode

339

* @param node - Node to check

340

* @returns True if node is LineBreakNode

341

*/

342

function $isLineBreakNode(node: LexicalNode | null | undefined): node is LineBreakNode;

343

344

interface SerializedLineBreakNode extends SerializedLexicalNode {

345

// LineBreak node has no additional properties

346

}

347

```

348

349

### Tab Node

350

351

Specialized TextNode for tab characters.

352

353

```typescript { .api }

354

/**

355

* Specialized TextNode for tab characters

356

*/

357

class TabNode extends TextNode {

358

/** Create a new TabNode */

359

constructor(key?: NodeKey);

360

/** Tabs cannot have format */

361

canHaveFormat(): false;

362

}

363

364

/**

365

* Create a new TabNode

366

* @returns New TabNode instance

367

*/

368

function $createTabNode(): TabNode;

369

370

/**

371

* Check if node is a TabNode

372

* @param node - Node to check

373

* @returns True if node is TabNode

374

*/

375

function $isTabNode(node: LexicalNode | null | undefined): node is TabNode;

376

377

interface SerializedTabNode extends SerializedTextNode {

378

// Tab inherits from text but has constrained behavior

379

}

380

```

381

382

### Decorator Node

383

384

Base class for custom decorator nodes that render non-text content.

385

386

```typescript { .api }

387

/**

388

* Base class for custom decorator nodes

389

*/

390

abstract class DecoratorNode<T = unknown> extends LexicalNode {

391

/** Create DOM representation for decorator */

392

abstract decorate(editor: LexicalEditor, config: EditorConfig): T;

393

/** Whether decorator is inline or block */

394

isIsolated(): boolean;

395

/** Whether decorator can be selected */

396

isKeyboardSelectable(): boolean;

397

/** Whether selection can be placed before this node */

398

canInsertTextBefore(): boolean;

399

/** Whether selection can be placed after this node */

400

canInsertTextAfter(): boolean;

401

}

402

403

/**

404

* Check if node is a DecoratorNode

405

* @param node - Node to check

406

* @returns True if node is DecoratorNode

407

*/

408

function $isDecoratorNode(node: LexicalNode | null | undefined): node is DecoratorNode;

409

```

410

411

### Node Utilities

412

413

Utility functions for working with nodes.

414

415

```typescript { .api }

416

/**

417

* Build import map for node serialization

418

* @param nodes - Array of node classes or replacements

419

* @returns Map of node types to classes

420

*/

421

function buildImportMap(

422

nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>

423

): Map<string, Klass<LexicalNode>>;

424

425

// Node-related type definitions

426

type Klass<T extends LexicalNode> = {

427

new (...args: any[]): T;

428

} & Omit<LexicalNode, keyof T>;

429

430

interface LexicalNodeReplacement {

431

replace: Klass<LexicalNode>;

432

with: <T extends LexicalNode>(node: LexicalNode) => T;

433

withKlass: Klass<LexicalNode>;

434

}

435

436

type NodeMap = Map<NodeKey, LexicalNode>;

437

```

438

439

**Usage Examples:**

440

441

```typescript

442

import {

443

$createParagraphNode,

444

$createTextNode,

445

$isParagraphNode,

446

$getRoot

447

} from "lexical";

448

449

// Create and structure nodes

450

const paragraph = $createParagraphNode();

451

const text = $createTextNode('Hello, world!');

452

const breakNode = $createLineBreakNode();

453

const moreText = $createTextNode('Second line');

454

455

// Build document structure

456

paragraph.append(text, breakNode, moreText);

457

458

// Get root and append

459

const root = $getRoot();

460

root.append(paragraph);

461

462

// Work with children

463

const children = paragraph.getChildren();

464

console.log('Number of children:', paragraph.getChildrenSize());

465

466

// Type checking and manipulation

467

if ($isParagraphNode(someNode)) {

468

someNode.setFormat('center');

469

someNode.setIndent(1);

470

}

471

```