or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-controls.mdcontext-hooks.mdcore-components.mdextensions.mdindex.mdlabels-i18n.mdstructure-controls.mdtext-formatting.md

context-hooks.mddocs/

0

# Context and Hooks

1

2

React Context and hooks for accessing editor state, configuration, and building custom controls. The context system enables deep integration with the editor while maintaining consistent behavior across all components.

3

4

## Capabilities

5

6

### useRichTextEditorContext Hook

7

8

Primary hook for accessing the rich text editor's context, including editor instance, styling functions, and configuration options.

9

10

```typescript { .api }

11

/**

12

* Hook for accessing rich text editor context

13

* Must be used within a RichTextEditor component tree

14

* @returns RichTextEditorContext object with editor state and configuration

15

* @throws Error if used outside of RichTextEditor provider

16

*/

17

function useRichTextEditorContext(): RichTextEditorContext;

18

19

interface RichTextEditorContext {

20

/** Current Tiptap editor instance */

21

editor: Editor | null;

22

/** Mantine styling API for consistent component styling */

23

getStyles: GetStylesApi<RichTextEditorFactory>;

24

/** Merged accessibility labels for all controls */

25

labels: RichTextEditorLabels;

26

/** Whether code highlighting styles are enabled */

27

withCodeHighlightStyles: boolean | undefined;

28

/** Whether typography styles are enabled */

29

withTypographyStyles: boolean | undefined;

30

/** Current component variant */

31

variant: string | undefined;

32

/** Whether default styles are disabled */

33

unstyled: boolean | undefined;

34

/** Callback for source code view toggle events */

35

onSourceCodeTextSwitch?: (isSourceCodeModeActive: boolean) => void;

36

}

37

```

38

39

**Usage Example:**

40

41

```typescript

42

import { useRichTextEditorContext } from "@mantine/tiptap";

43

import { IconCustomFormat } from "@tabler/icons-react";

44

45

function CustomControl() {

46

const { editor, labels, getStyles } = useRichTextEditorContext();

47

48

const isActive = editor?.isActive('bold') || false;

49

const canExecute = editor?.can().toggleBold() || false;

50

51

return (

52

<button

53

{...getStyles('control')}

54

disabled={!canExecute}

55

onClick={() => editor?.chain().focus().toggleBold().run()}

56

aria-label={labels.boldControlLabel}

57

data-active={isActive}

58

>

59

<IconCustomFormat size={16} />

60

</button>

61

);

62

}

63

```

64

65

### Context Properties

66

67

#### Editor Instance

68

69

Access the current Tiptap editor instance for direct manipulation:

70

71

```typescript

72

function EditorStateDisplay() {

73

const { editor } = useRichTextEditorContext();

74

75

if (!editor) {

76

return <div>Editor not ready</div>;

77

}

78

79

const wordCount = editor.storage.characterCount?.words() || 0;

80

const canUndo = editor.can().undo();

81

const canRedo = editor.can().redo();

82

83

return (

84

<div>

85

<p>Words: {wordCount}</p>

86

<p>Can Undo: {canUndo ? 'Yes' : 'No'}</p>

87

<p>Can Redo: {canRedo ? 'Yes' : 'No'}</p>

88

</div>

89

);

90

}

91

```

92

93

#### Styling Integration

94

95

Use the styling API to maintain consistency with built-in components:

96

97

```typescript

98

function StyledCustomControl() {

99

const { getStyles } = useRichTextEditorContext();

100

101

return (

102

<div {...getStyles('controlsGroup')}>

103

<button {...getStyles('control')}>

104

Custom Control

105

</button>

106

</div>

107

);

108

}

109

```

110

111

#### Labels and Internationalization

112

113

Access merged labels for consistent accessibility:

114

115

```typescript

116

function AccessibleControl() {

117

const { labels, editor } = useRichTextEditorContext();

118

119

return (

120

<button

121

aria-label={labels.boldControlLabel}

122

title={labels.boldControlLabel}

123

onClick={() => editor?.chain().focus().toggleBold().run()}

124

>

125

B

126

</button>

127

);

128

}

129

```

130

131

## Building Custom Controls

132

133

### Basic Custom Control

134

135

```typescript

136

import { useRichTextEditorContext } from "@mantine/tiptap";

137

138

interface CustomControlProps {

139

command: string;

140

icon: React.ReactNode;

141

label: string;

142

}

143

144

function CustomControl({ command, icon, label }: CustomControlProps) {

145

const { editor, getStyles } = useRichTextEditorContext();

146

147

const isActive = editor?.isActive(command) || false;

148

const canExecute = editor?.can()[`toggle${command}`]?.() || false;

149

150

const handleClick = () => {

151

editor?.chain().focus()[`toggle${command}`]?.().run();

152

};

153

154

return (

155

<button

156

{...getStyles('control')}

157

onClick={handleClick}

158

disabled={!canExecute}

159

aria-label={label}

160

data-active={isActive}

161

>

162

{icon}

163

</button>

164

);

165

}

166

167

// Usage

168

<CustomControl

169

command="bold"

170

icon={<IconBold />}

171

label="Toggle Bold"

172

/>

173

```

174

175

### Advanced Custom Control with State

176

177

```typescript

178

import { useState } from "react";

179

import { useRichTextEditorContext } from "@mantine/tiptap";

180

181

function FontSizeControl() {

182

const { editor, getStyles } = useRichTextEditorContext();

183

const [isOpen, setIsOpen] = useState(false);

184

185

const currentSize = editor?.getAttributes('textStyle').fontSize || '16px';

186

187

const sizes = ['12px', '14px', '16px', '18px', '20px', '24px'];

188

189

const applySize = (size: string) => {

190

editor?.chain().focus().setFontSize(size).run();

191

setIsOpen(false);

192

};

193

194

return (

195

<div style={{ position: 'relative' }}>

196

<button

197

{...getStyles('control')}

198

onClick={() => setIsOpen(!isOpen)}

199

aria-label="Font Size"

200

>

201

{currentSize}

202

</button>

203

204

{isOpen && (

205

<div style={{ position: 'absolute', top: '100%', background: 'white', border: '1px solid #ccc' }}>

206

{sizes.map(size => (

207

<button

208

key={size}

209

onClick={() => applySize(size)}

210

style={{ display: 'block', width: '100%', fontSize: size }}

211

>

212

{size}

213

</button>

214

))}

215

</div>

216

)}

217

</div>

218

);

219

}

220

```

221

222

### Building Complex Custom Controls

223

224

```typescript

225

import { useRichTextEditorContext } from "@mantine/tiptap";

226

import { IconCustomIcon } from "@tabler/icons-react";

227

228

// Build a complex custom control with multiple features

229

function CustomFormatControl() {

230

const { editor, labels, getStyles } = useRichTextEditorContext();

231

232

const isActive = editor?.isActive('customFormat') || false;

233

const canExecute = editor?.can().toggleCustomFormat?.() || false;

234

235

const handleClick = () => {

236

editor?.chain().focus().toggleCustomFormat().run();

237

};

238

239

return (

240

<button

241

{...getStyles('control')}

242

onClick={handleClick}

243

disabled={!canExecute}

244

aria-label="Apply Custom Format"

245

data-active={isActive}

246

>

247

<IconCustomIcon size={16} />

248

</button>

249

);

250

}

251

252

// Usage

253

<RichTextEditor editor={editor}>

254

<RichTextEditor.Toolbar>

255

<RichTextEditor.ControlsGroup>

256

<CustomFormatControl />

257

</RichTextEditor.ControlsGroup>

258

</RichTextEditor.Toolbar>

259

<RichTextEditor.Content />

260

</RichTextEditor>

261

```

262

263

## Context Provider

264

265

The context is automatically provided by the `RichTextEditor` component:

266

267

```typescript

268

// Context is provided automatically

269

<RichTextEditor editor={editor}>

270

{/* All child components have access to context */}

271

<RichTextEditor.Toolbar>

272

<CustomControl /> {/* Can use useRichTextEditorContext */}

273

<RichTextEditor.Bold /> {/* Uses context internally */}

274

</RichTextEditor.Toolbar>

275

<RichTextEditor.Content />

276

</RichTextEditor>

277

```

278

279

## Error Handling

280

281

The context hook includes built-in error handling:

282

283

```typescript

284

function SafeCustomControl() {

285

try {

286

const context = useRichTextEditorContext();

287

// Use context safely

288

return <button>Custom Control</button>;

289

} catch (error) {

290

// Handle context not found error

291

console.error('RichTextEditor context not found:', error);

292

return null;

293

}

294

}

295

```

296

297

## TypeScript Integration

298

299

Full TypeScript support with proper type inference:

300

301

```typescript

302

import type { Editor } from "@tiptap/react";

303

import type { RichTextEditorContext } from "@mantine/tiptap";

304

305

function TypedCustomControl() {

306

const context: RichTextEditorContext = useRichTextEditorContext();

307

const editor: Editor | null = context.editor;

308

309

// TypeScript knows the exact shape of context

310

const labels = context.labels; // RichTextEditorLabels

311

const getStyles = context.getStyles; // GetStylesApi<RichTextEditorFactory>

312

313

return (

314

<button onClick={() => editor?.chain().focus().toggleBold().run()}>

315

Bold

316

</button>

317

);

318

}

319

```

320

321

## Best Practices

322

323

### Performance Optimization

324

325

```typescript

326

import { useCallback } from "react";

327

328

function OptimizedControl() {

329

const { editor } = useRichTextEditorContext();

330

331

// Memoize event handlers

332

const handleClick = useCallback(() => {

333

editor?.chain().focus().toggleBold().run();

334

}, [editor]);

335

336

return <button onClick={handleClick}>Bold</button>;

337

}

338

```

339

340

### Conditional Rendering

341

342

```typescript

343

function ConditionalControl({ showAdvanced }: { showAdvanced: boolean }) {

344

const { editor } = useRichTextEditorContext();

345

346

if (!editor || !showAdvanced) {

347

return null;

348

}

349

350

return (

351

<button onClick={() => editor.chain().focus().toggleCode().run()}>

352

Code

353

</button>

354

);

355

}

356

```

357

358

### State Synchronization

359

360

```typescript

361

import { useEffect, useState } from "react";

362

363

function EditorStateSync() {

364

const { editor } = useRichTextEditorContext();

365

const [isBold, setIsBold] = useState(false);

366

367

useEffect(() => {

368

if (!editor) return;

369

370

const updateState = () => {

371

setIsBold(editor.isActive('bold'));

372

};

373

374

// Listen to editor updates

375

editor.on('selectionUpdate', updateState);

376

editor.on('transaction', updateState);

377

378

return () => {

379

editor.off('selectionUpdate', updateState);

380

editor.off('transaction', updateState);

381

};

382

}, [editor]);

383

384

return <div>Text is {isBold ? 'bold' : 'normal'}</div>;

385

}

386

```