or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-editor-view.mdcustom-views.mddecoration-system.mdeditor-props.mdindex.mdinput-handling.mdposition-mapping.md

decoration-system.mddocs/

0

# Decoration System

1

2

The decoration system provides visual annotations and styling for document content without modifying the underlying document structure. Decorations can be widgets (DOM elements at specific positions), inline styling (CSS attributes applied to text ranges), or node attributes (applied to entire nodes).

3

4

## Capabilities

5

6

### Decoration Class

7

8

The base class for all decorations.

9

10

```typescript { .api }

11

/**

12

* Decoration objects can be provided to the view through the

13

* decorations prop. They come in several variants.

14

*/

15

class Decoration {

16

/** The start position of the decoration */

17

readonly from: number;

18

19

/** The end position. Will be the same as `from` for widget decorations */

20

readonly to: number;

21

22

/**

23

* The spec provided when creating this decoration. Can be useful

24

* if you've stored extra information in that object.

25

*/

26

readonly spec: any;

27

}

28

```

29

30

### Widget Decorations

31

32

Widget decorations insert DOM elements at specific document positions.

33

34

```typescript { .api }

35

class Decoration {

36

/**

37

* Creates a widget decoration, which is a DOM node that's shown in

38

* the document at the given position. It is recommended that you

39

* delay rendering the widget by passing a function that will be

40

* called when the widget is actually drawn in a view, but you can

41

* also directly pass a DOM node.

42

*/

43

static widget(

44

pos: number,

45

toDOM: WidgetConstructor,

46

spec?: {

47

/**

48

* Controls which side of the document position this widget is

49

* associated with. When negative, it is drawn before a cursor

50

* at its position. When zero (default) or positive, the

51

* widget is drawn after the cursor.

52

*/

53

side?: number;

54

55

/**

56

* By default, the cursor will be strictly kept on the side

57

* indicated by `side`. Set this to true to allow the DOM

58

* selection to stay on the other side.

59

*/

60

relaxedSide?: boolean;

61

62

/** The precise set of marks to draw around the widget */

63

marks?: readonly Mark[];

64

65

/** Control which DOM events the editor view should ignore */

66

stopEvent?: (event: Event) => boolean;

67

68

/**

69

* When set (defaults to false), selection changes inside the

70

* widget are ignored.

71

*/

72

ignoreSelection?: boolean;

73

74

/**

75

* Key for comparing decorations. Widgets with the same key

76

* are considered interchangeable.

77

*/

78

key?: string;

79

80

/** Called when the widget decoration is removed */

81

destroy?: (node: DOMNode) => void;

82

83

[key: string]: any;

84

}

85

): Decoration;

86

}

87

```

88

89

**Usage Examples:**

90

91

```typescript

92

import { Decoration } from "prosemirror-view";

93

94

// Simple widget with DOM element

95

const buttonWidget = Decoration.widget(10, document.createElement("button"));

96

97

// Widget with render function

98

const dynamicWidget = Decoration.widget(20, (view, getPos) => {

99

const button = document.createElement("button");

100

button.textContent = `Position: ${getPos()}`;

101

button.addEventListener("click", () => {

102

console.log("Widget clicked at position", getPos());

103

});

104

return button;

105

});

106

107

// Widget with advanced options

108

const annotationWidget = Decoration.widget(30, (view) => {

109

const span = document.createElement("span");

110

span.className = "annotation";

111

span.textContent = "📝";

112

return span;

113

}, {

114

side: -1, // Show before cursor

115

marks: [], // No marks

116

stopEvent: (event) => event.type === "click",

117

key: "annotation-30"

118

});

119

```

120

121

### Inline Decorations

122

123

Inline decorations apply CSS attributes to text ranges.

124

125

```typescript { .api }

126

class Decoration {

127

/**

128

* Creates an inline decoration, which adds the given attributes to

129

* each inline node between `from` and `to`.

130

*/

131

static inline(

132

from: number,

133

to: number,

134

attrs: DecorationAttrs,

135

spec?: {

136

/**

137

* Determines how the left side of the decoration is mapped

138

* when content is inserted directly at that position.

139

*/

140

inclusiveStart?: boolean;

141

142

/** Determines how the right side of the decoration is mapped */

143

inclusiveEnd?: boolean;

144

145

[key: string]: any;

146

}

147

): Decoration;

148

}

149

```

150

151

**Usage Examples:**

152

153

```typescript

154

// Highlight text range

155

const highlight = Decoration.inline(10, 20, {

156

class: "highlight",

157

style: "background-color: yellow;"

158

});

159

160

// Add custom attributes

161

const customDecoration = Decoration.inline(30, 40, {

162

"data-comment-id": "123",

163

class: "comment-range",

164

style: "border-bottom: 2px solid blue;"

165

}, {

166

inclusiveStart: true,

167

inclusiveEnd: false

168

});

169

170

// Strike through text

171

const strikethrough = Decoration.inline(50, 60, {

172

style: "text-decoration: line-through; color: red;"

173

});

174

```

175

176

### Node Decorations

177

178

Node decorations apply attributes to entire document nodes.

179

180

```typescript { .api }

181

class Decoration {

182

/**

183

* Creates a node decoration. `from` and `to` should point precisely

184

* before and after a node in the document. That node, and only that

185

* node, will receive the given attributes.

186

*/

187

static node(

188

from: number,

189

to: number,

190

attrs: DecorationAttrs,

191

spec?: any

192

): Decoration;

193

}

194

```

195

196

**Usage Examples:**

197

198

```typescript

199

// Add class to a paragraph

200

const styledParagraph = Decoration.node(0, 15, {

201

class: "special-paragraph",

202

style: "border-left: 3px solid blue; padding-left: 10px;"

203

});

204

205

// Wrap node in custom element

206

const wrappedNode = Decoration.node(20, 35, {

207

nodeName: "section",

208

class: "content-section",

209

"data-section-id": "intro"

210

});

211

```

212

213

### DecorationSet Class

214

215

Efficient collection and management of decorations.

216

217

```typescript { .api }

218

/**

219

* A collection of decorations, organized in such a way that the drawing

220

* algorithm can efficiently use and compare them. This is a persistent

221

* data structure—it is not modified, updates create a new value.

222

*/

223

class DecorationSet implements DecorationSource {

224

/** Empty decoration set constant */

225

static empty: DecorationSet;

226

227

/**

228

* Create a set of decorations, using the structure of the given

229

* document. This will consume (modify) the `decorations` array.

230

*/

231

static create(doc: Node, decorations: readonly Decoration[]): DecorationSet;

232

}

233

```

234

235

### Finding Decorations

236

237

Methods for querying decorations within a set.

238

239

```typescript { .api }

240

class DecorationSet {

241

/**

242

* Find all decorations in this set which touch the given range

243

* (including decorations that start or end directly at the

244

* boundaries) and match the given predicate on their spec.

245

*/

246

find(

247

start?: number,

248

end?: number,

249

predicate?: (spec: any) => boolean

250

): Decoration[];

251

}

252

```

253

254

**Usage Examples:**

255

256

```typescript

257

// Find all decorations in range

258

const decorationsInRange = decorationSet.find(10, 50);

259

260

// Find decorations with specific spec property

261

const commentDecorations = decorationSet.find(0, doc.content.size,

262

spec => spec.type === "comment"

263

);

264

265

// Find all decorations

266

const allDecorations = decorationSet.find();

267

```

268

269

### Mapping and Updates

270

271

Methods for updating decoration sets when the document changes.

272

273

```typescript { .api }

274

class DecorationSet {

275

/**

276

* Map the set of decorations in response to a change in the document.

277

*/

278

map(

279

mapping: Mappable,

280

doc: Node,

281

options?: {

282

/** Called when a decoration is removed due to mapping */

283

onRemove?: (decorationSpec: any) => void;

284

}

285

): DecorationSet;

286

287

/**

288

* Add the given array of decorations to the ones in the set,

289

* producing a new set.

290

*/

291

add(doc: Node, decorations: readonly Decoration[]): DecorationSet;

292

293

/**

294

* Create a new set that contains the given decorations minus

295

* the ones in the given array.

296

*/

297

remove(decorations: readonly Decoration[]): DecorationSet;

298

}

299

```

300

301

**Usage Examples:**

302

303

```typescript

304

import { Mapping } from "prosemirror-transform";

305

306

// Map decorations after document change

307

const mapping = new Mapping();

308

// ... apply document changes to mapping

309

const newDecorationSet = decorationSet.map(mapping, newDoc, {

310

onRemove: (spec) => console.log("Decoration removed:", spec)

311

});

312

313

// Add new decorations

314

const moreDecorations = [

315

Decoration.inline(5, 10, { class: "new-highlight" }),

316

Decoration.widget(15, () => document.createElement("span"))

317

];

318

const expandedSet = decorationSet.add(doc, moreDecorations);

319

320

// Remove specific decorations

321

const reducedSet = decorationSet.remove([decoration1, decoration2]);

322

```

323

324

### DecorationSource Interface

325

326

Interface for objects that can provide decorations to child nodes.

327

328

```typescript { .api }

329

/**

330

* An object that can provide decorations. Implemented by DecorationSet,

331

* and passed to node views.

332

*/

333

interface DecorationSource {

334

/** Map the set of decorations in response to a change in the document */

335

map(mapping: Mapping, node: Node): DecorationSource;

336

337

/** Get decorations that apply locally to the given node */

338

locals(node: Node): readonly Decoration[];

339

340

/** Extract decorations for the given child node at the given offset */

341

forChild(offset: number, child: Node): DecorationSource;

342

343

/** Compare this decoration source to another */

344

eq(other: DecorationSource): boolean;

345

346

/** Call the given function for each decoration set in the group */

347

forEachSet(f: (set: DecorationSet) => void): void;

348

}

349

```

350

351

### Decoration Attributes

352

353

Type definition for decoration attributes.

354

355

```typescript { .api }

356

/**

357

* A set of attributes to add to a decorated node. Most properties

358

* simply directly correspond to DOM attributes of the same name.

359

*/

360

type DecorationAttrs = {

361

/**

362

* When non-null, the target node is wrapped in a DOM element

363

* of this type (and the other attributes are applied to this element).

364

*/

365

nodeName?: string;

366

367

/**

368

* A CSS class name or a space-separated set of class names to be

369

* added to the classes that the node already had.

370

*/

371

class?: string;

372

373

/**

374

* A string of CSS to be added to the node's existing `style` property.

375

*/

376

style?: string;

377

378

/** Any other properties are treated as regular DOM attributes */

379

[attribute: string]: string | undefined;

380

};

381

```

382

383

### Widget Constructor Type

384

385

Type definition for widget constructors.

386

387

```typescript { .api }

388

/**

389

* The type of function provided to create node views.

390

*/

391

type WidgetConstructor =

392

| ((view: EditorView, getPos: () => number | undefined) => DOMNode)

393

| DOMNode;

394

```

395

396

**Complete Usage Example:**

397

398

```typescript

399

import { EditorView, Decoration, DecorationSet } from "prosemirror-view";

400

import { EditorState } from "prosemirror-state";

401

402

class CommentPlugin {

403

constructor() {

404

this.comments = new Map();

405

}

406

407

addComment(pos, text) {

408

const id = Math.random().toString(36);

409

this.comments.set(id, { pos, text });

410

return id;

411

}

412

413

getDecorations(doc) {

414

const decorations = [];

415

416

for (const [id, comment] of this.comments) {

417

// Add highlight decoration

418

decorations.push(

419

Decoration.inline(comment.pos, comment.pos + 10, {

420

class: "comment-highlight",

421

"data-comment-id": id

422

})

423

);

424

425

// Add comment widget

426

decorations.push(

427

Decoration.widget(comment.pos + 10, (view) => {

428

const widget = document.createElement("span");

429

widget.className = "comment-widget";

430

widget.textContent = "đź’¬";

431

widget.title = comment.text;

432

return widget;

433

}, {

434

side: 1,

435

key: `comment-${id}`

436

})

437

);

438

}

439

440

return DecorationSet.create(doc, decorations);

441

}

442

}

443

444

// Usage in editor

445

const commentPlugin = new CommentPlugin();

446

447

const view = new EditorView(document.querySelector("#editor"), {

448

state: EditorState.create({

449

schema: mySchema,

450

doc: myDoc

451

}),

452

decorations: (state) => commentPlugin.getDecorations(state.doc)

453

});

454

455

// Add a comment

456

commentPlugin.addComment(15, "This needs revision");

457

view.updateState(view.state); // Trigger decoration update

458

```